Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mapexpr argument for include: transform each parsed expression #34595

Merged
merged 6 commits into from
Feb 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ New library functions
`merge!` are still available for backward compatibility ([#34296]).
* The new `isdisjoint` function indicates whether two collections are disjoint ([#34427]).
* Add function `ismutable` and deprecate `isimmutable` to check whether something is mutable.([#34652])
* `include` now accepts an optional `mapexpr` first argument to transform the parsed
expressions before they are evaluated ([#34595]).

New library features
--------------------
Expand Down
10 changes: 7 additions & 3 deletions base/Base.jl
Original file line number Diff line number Diff line change
Expand Up @@ -365,8 +365,8 @@ for m in methods(include)
end
# These functions are duplicated in client.jl/include(::String) for
# nicer stacktraces. Modifications here have to be backported there
include(mod::Module, path::AbstractString) = include(mod, convert(String, path))
function include(mod::Module, _path::String)
include(mod::Module, _path::AbstractString) = include(identity, mod, _path)
function include(mapexpr::Function, mod::Module, _path::AbstractString)
path, prev = _include_dependency(mod, _path)
for callback in include_callbacks # to preserve order, must come before Core.include
invokelatest(callback, mod, path)
Expand All @@ -376,7 +376,11 @@ function include(mod::Module, _path::String)
local result
try
# result = Core.include(mod, path)
result = ccall(:jl_load_, Any, (Any, Any), mod, path)
if mapexpr === identity
result = ccall(:jl_load, Any, (Any, Cstring), mod, path)
else
result = ccall(:jl_load_rewrite, Any, (Any, Cstring, Any), mod, path, mapexpr)
end
finally
if prev === nothing
delete!(tls, :SOURCE_PATH)
Expand Down
16 changes: 11 additions & 5 deletions base/client.jl
Original file line number Diff line number Diff line change
Expand Up @@ -423,8 +423,10 @@ end
# MainInclude exists to hide Main.include and eval from `names(Main)`.
baremodule MainInclude
using ..Base
# We inline the definition of include from loading.jl/include_relative to get one-frame stacktraces.
# include(fname::AbstractString) = Main.Base.include(Main, fname)
include(mapexpr::Function, fname::AbstractString) = Base.include(mapexpr, Main, fname)
# We inline the definition of include from loading.jl/include_relative to get one-frame stacktraces
# for the common case of include(fname). Otherwise we would use:
# include(fname::AbstractString) = Base.include(Main, fname)
function include(fname::AbstractString)
mod = Main
isa(fname, String) || (fname = Base.convert(String, fname)::String)
Expand All @@ -436,7 +438,7 @@ function include(fname::AbstractString)
tls[:SOURCE_PATH] = path
local result
try
result = ccall(:jl_load_, Any, (Any, Any), mod, path)
result = ccall(:jl_load, Any, (Any, Cstring), mod, path)
finally
if prev === nothing
Base.delete!(tls, :SOURCE_PATH)
Expand All @@ -459,16 +461,20 @@ definition of `eval`, which evaluates expressions in that module.
MainInclude.eval

"""
include(path::AbstractString)
include([mapexpr::Function,] path::AbstractString)

Evaluate the contents of the input source file in the global scope of the containing module.
Every module (except those defined with `baremodule`) has its own 1-argument
Every module (except those defined with `baremodule`) has its own
definition of `include`, which evaluates the file in that module.
Returns the result of the last evaluated expression of the input file. During including,
a task-local include path is set to the directory containing the file. Nested calls to
`include` will search relative to that path. This function is typically used to load source
interactively, or to combine files in packages that are broken into multiple source files.

The optional first argument `mapexpr` can be used to transform the included code before
it is evaluated: for each parsed expression `expr` in `path`, the `include` function
actually evaluates `mapexpr(expr)`. If it is omitted, `mapexpr` defaults to [`identity`](@ref).

Use [`Base.include`](@ref) to evaluate a file into another module.
"""
MainInclude.include
Expand Down
32 changes: 24 additions & 8 deletions base/loading.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1073,16 +1073,27 @@ end
# relative-path load

"""
include_string(m::Module, code::AbstractString, filename::AbstractString="string")
include_string([mapexpr::Function,] m::Module, code::AbstractString, filename::AbstractString="string")

Like [`include`](@ref), except reads code from the given string rather than from a file.

The optional first argument `mapexpr` can be used to transform the included code before
it is evaluated: for each parsed expression `expr` in `code`, the `include_string` function
actually evaluates `mapexpr(expr)`. If it is omitted, `mapexpr` defaults to [`identity`](@ref).
"""
include_string(m::Module, txt::String, fname::String) =
ccall(:jl_load_file_string, Any, (Ptr{UInt8}, Csize_t, Cstring, Any),
txt, sizeof(txt), fname, m)
function include_string(mapexpr::Function, m::Module, txt_::AbstractString, fname::AbstractString="string")
txt = String(txt_)
if mapexpr === identity
ccall(:jl_load_file_string, Any, (Ptr{UInt8}, Csize_t, Cstring, Any),
txt, sizeof(txt), String(fname), m)
else
ccall(:jl_load_rewrite_file_string, Any, (Ptr{UInt8}, Csize_t, Cstring, Any, Any),
txt, sizeof(txt), String(fname), m, mapexpr)
end
end

include_string(m::Module, txt::AbstractString, fname::AbstractString="string") =
include_string(m, String(txt), String(fname))
include_string(identity, m, txt, fname)

function source_path(default::Union{AbstractString,Nothing}="")
s = current_task().storage
Expand All @@ -1098,15 +1109,19 @@ function source_dir()
end

"""
Base.include([m::Module,] path::AbstractString)
Base.include([mapexpr::Function,] [m::Module,] path::AbstractString)

Evaluate the contents of the input source file in the global scope of module `m`.
Every module (except those defined with [`baremodule`](@ref)) has its own 1-argument
definition of `include`, which evaluates the file in that module.
Every module (except those defined with [`baremodule`](@ref)) has its own
definition of `include` omitting the `m` argument, which evaluates the file in that module.
Returns the result of the last evaluated expression of the input file. During including,
a task-local include path is set to the directory containing the file. Nested calls to
`include` will search relative to that path. This function is typically used to load source
interactively, or to combine files in packages that are broken into multiple source files.

The optional first argument `mapexpr` can be used to transform the included code before
it is evaluated: for each parsed expression `expr` in `path`, the `include` function
actually evaluates `mapexpr(expr)`. If it is omitted, `mapexpr` defaults to [`identity`](@ref).
"""
Base.include # defined in sysimg.jl

Expand All @@ -1122,6 +1137,7 @@ function evalfile(path::AbstractString, args::Vector{String}=String[])
:(const ARGS = $args),
:(eval(x) = $(Expr(:core, :eval))(__anon__, x)),
:(include(x) = $(Expr(:top, :include))(__anon__, x)),
:(include(mapexpr::Function, x) = $(Expr(:top, :include))(mapexpr, __anon__, x)),
:(include($path))))
end
evalfile(path::AbstractString, args::Vector) = evalfile(path, String[args...])
Expand Down
19 changes: 17 additions & 2 deletions src/ast.c
Original file line number Diff line number Diff line change
Expand Up @@ -828,7 +828,8 @@ JL_DLLEXPORT jl_value_t *jl_parse_string(const char *str, size_t len,
// parse and eval a whole file, possibly reading from a string (`content`)
jl_value_t *jl_parse_eval_all(const char *fname,
const char *content, size_t contentlen,
jl_module_t *inmodule)
jl_module_t *inmodule,
jl_value_t *mapexpr)
{
jl_ptls_t ptls = jl_get_ptls_states();
if (ptls->in_pure_callback)
Expand Down Expand Up @@ -879,9 +880,16 @@ jl_value_t *jl_parse_eval_all(const char *fname,
JL_TIMING(LOWERING);
if (fl_ctx->T == fl_applyn(fl_ctx, 1, symbol_value(symbol(fl_ctx, "contains-macrocall")), expression)) {
form = scm_to_julia(fl_ctx, expression, inmodule);
if (mapexpr)
form = jl_call1(mapexpr, form);
form = jl_expand_macros(form, inmodule, NULL, 0);
expression = julia_to_scm(fl_ctx, form);
}
else if (mapexpr) {
form = scm_to_julia(fl_ctx, expression, inmodule);
form = jl_call1(mapexpr, form);
expression = julia_to_scm(fl_ctx, form);
}
// expand non-final expressions in statement position (value unused)
expression =
fl_applyn(fl_ctx, 4,
Expand Down Expand Up @@ -925,10 +933,17 @@ jl_value_t *jl_parse_eval_all(const char *fname,
return result;
}

JL_DLLEXPORT jl_value_t *jl_load_rewrite_file_string(const char *text, size_t len,
char *filename, jl_module_t *inmodule,
jl_value_t *mapexpr)
{
return jl_parse_eval_all(filename, text, len, inmodule, mapexpr);
}

JL_DLLEXPORT jl_value_t *jl_load_file_string(const char *text, size_t len,
char *filename, jl_module_t *inmodule)
{
return jl_parse_eval_all(filename, text, len, inmodule);
return jl_parse_eval_all(filename, text, len, inmodule, NULL);
}

// returns either an expression or a thunk
Expand Down
9 changes: 7 additions & 2 deletions src/jlfrontend.scm
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,8 @@
(loc (if (and (pair? loc) (eq? (car loc) 'line))
(list loc)
'()))
(x (if (eq? name 'x) 'y 'x)))
(x (if (eq? name 'x) 'y 'x))
(mex (if (eq? name 'mapexpr) 'map_expr 'mapexpr)))
`(block
(= (call eval ,x)
(block
Expand All @@ -189,7 +190,11 @@
(= (call include ,x)
(block
,@loc
(call (top include) ,name ,x)))))
(call (top include) ,name ,x)))
(= (call include (:: ,mex (top Function)) ,x)
(block
,@loc
(call (top include) ,mex ,name ,x)))))
'none 0))

; run whole frontend on a string. useful for testing.
Expand Down
3 changes: 2 additions & 1 deletion src/julia_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -452,7 +452,8 @@ jl_value_t *jl_toplevel_eval_flex(jl_module_t *m, jl_value_t *e, int fast, int e
jl_value_t *jl_eval_global_var(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *e);
jl_value_t *jl_parse_eval_all(const char *fname,
const char *content, size_t contentlen,
jl_module_t *inmodule);
jl_module_t *inmodule,
jl_value_t *mapexpr);
jl_value_t *jl_interpret_toplevel_thunk(jl_module_t *m, jl_code_info_t *src);
jl_value_t *jl_interpret_toplevel_expr_in(jl_module_t *m, jl_value_t *e,
jl_code_info_t *src,
Expand Down
9 changes: 7 additions & 2 deletions src/toplevel.c
Original file line number Diff line number Diff line change
Expand Up @@ -863,13 +863,18 @@ JL_DLLEXPORT jl_value_t *jl_infer_thunk(jl_code_info_t *thk, jl_module_t *m)
return (jl_value_t*)jl_any_type;
}

JL_DLLEXPORT jl_value_t *jl_load(jl_module_t *module, const char *fname)
JL_DLLEXPORT jl_value_t *jl_load_rewrite(jl_module_t *module, const char *fname, jl_value_t *mapexpr)
{
uv_stat_t stbuf;
if (jl_stat(fname, (char*)&stbuf) != 0 || (stbuf.st_mode & S_IFMT) != S_IFREG) {
jl_errorf("could not open file %s", fname);
}
return jl_parse_eval_all(fname, NULL, 0, module);
return jl_parse_eval_all(fname, NULL, 0, module, mapexpr);
}

JL_DLLEXPORT jl_value_t *jl_load(jl_module_t *module, const char *fname)
{
return jl_load_rewrite(module, fname, NULL);
}

// load from filename given as a String object
Expand Down
22 changes: 22 additions & 0 deletions test/loading.jl
Original file line number Diff line number Diff line change
Expand Up @@ -660,3 +660,25 @@ Base.ACTIVE_PROJECT[] = saved_active_project
module Foo; import Libdl; end
import .Foo.Libdl; import Libdl
@test Foo.Libdl === Libdl

@testset "include with mapexpr" begin
let exprs = Any[]
@test 13 === include_string(@__MODULE__, "1+1\n3*4") do ex
ex isa LineNumberNode || push!(exprs, ex)
Meta.isexpr(ex, :call) ? :(1 + $ex) : ex
end
@test exprs == [:(1 + 1), :(3 * 4)]
end
# test using test_exec.jl, just because that is the shortest handy file
for incl in (include, (mapexpr,path) -> Base.include(mapexpr, @__MODULE__, path))
let exprs = Any[]
incl("test_exec.jl") do ex
ex isa LineNumberNode || push!(exprs, ex)
Meta.isexpr(ex, :macrocall) ? :nothing : ex
end
@test length(exprs) == 2 && exprs[1] == :(using Test)
@test Meta.isexpr(exprs[2], :macrocall) &&
exprs[2].args[[1,3]] == [Symbol("@test"), :(1 == 2)]
end
end
end