Skip to content

Commit bd0525c

Browse files
committed
Replaceable jl_parse as single parser entry point
* Unify flisp parsing to always parse from string rather than file. * Put all code-loading functions together into toplevel.c * Remove flisp code from jl_parse_eval_all. This makes the top level parse-lower-eval loop independent of flisp internals. * Keep jl_load and jl_load_file_string from julia.h unchanged for compatibility with the existing C API. (No julia code `ccall`s these anymore) * Replaceable jl_parse as single parser entry point. `jl_parse` calls a C function pointer which can be replaced with jl_set_parser(). This makes it possible to replace all use of the flisp parser with julia code at runtime. Create frontend.c to contain the shim code for this and as a place to move non-flisp related frontend code in the future. .... Move include() implementation to Julia; add Meta.parseall() Move the implementation include into Julia, essentially replacing jl_parse_eval_all (except for within bootstrap). This also reunifies the versions of include() in MainInclude and Base which have started diverging after being duplicated for improved stack traces. As part of this, add `Meta.parseall()` for top-level parsing. For now as an internal function, but this will likely be made public shortly. .... Remove include mapexpr handling from C code mapexpr is now handled in Julia code so we can use a simplified version of include during bootstrap. .... General parser options for improved flexibility This change allows general options to be passed through to the parser. .... Apply suggestions from code review Co-Authored-By: Jameson Nash <[email protected]> .... Move code back into ast.c from frontend.c. .... Julia ABI and implementation for swappable parser This moves the parser-swapping implementation almost entirely into Julia, leaving only a few generic pieces in C and some code which is required to call into the Julia parser. * Add Core.Compiler.parse as the internal entry point for parsing, and move parser-swapping machinery there. * New builtin Core._apply_in_world to allow frozen-world APIs to be implemented in pure Julia code. * Make filename a String for convenience (no big deal if it needs to be copied, compared to copying the source text itself). * Rename some variables in parser APIs (particularly pos vs offset) for consistency. .... Use latest world for calling Core.Compiler.parse (fixup! Julia ABI and implementation for swappable parser) Restoring the world age this way doesn't seem entirely consistent with the dynamically scoped way it's used elsewhere. .... More flexibility in freezing parser world age This separates the decision about whether to freeze the parser world age from Core.Compiler.parse. The builtin _apply_in_world allows this to be done with more flexibility from outside (for example, one might want to do some argument conversion in the current world before calling through to a fixed world for the parser internals. .... Apply suggestions from code review Co-authored-by: Jameson Nash <[email protected]> .... Put replaceable parser shim in Core Avoids introducing (more?) dependency between Base and Core.Compiler. .... Simplify Core._parse
1 parent fb1970c commit bd0525c

File tree

18 files changed

+426
-278
lines changed

18 files changed

+426
-278
lines changed

base/Base.jl

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -365,31 +365,8 @@ for m in methods(include)
365365
end
366366
# These functions are duplicated in client.jl/include(::String) for
367367
# nicer stacktraces. Modifications here have to be backported there
368-
include(mod::Module, _path::AbstractString) = include(identity, mod, _path)
369-
function include(mapexpr::Function, mod::Module, _path::AbstractString)
370-
path, prev = _include_dependency(mod, _path)
371-
for callback in include_callbacks # to preserve order, must come before Core.include
372-
invokelatest(callback, mod, path)
373-
end
374-
tls = task_local_storage()
375-
tls[:SOURCE_PATH] = path
376-
local result
377-
try
378-
# result = Core.include(mod, path)
379-
if mapexpr === identity
380-
result = ccall(:jl_load, Any, (Any, Cstring), mod, path)
381-
else
382-
result = ccall(:jl_load_rewrite, Any, (Any, Cstring, Any), mod, path, mapexpr)
383-
end
384-
finally
385-
if prev === nothing
386-
delete!(tls, :SOURCE_PATH)
387-
else
388-
tls[:SOURCE_PATH] = prev
389-
end
390-
end
391-
return result
392-
end
368+
include(mod::Module, _path::AbstractString) = _include(identity, mod, _path)
369+
include(mapexpr::Function, mod::Module, _path::AbstractString) = _include(mapexpr, mod, _path)
393370

394371
end_base_include = time_ns()
395372

base/boot.jl

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -740,4 +740,19 @@ Unsigned(x::Union{Float32, Float64, Bool}) = UInt(x)
740740
Integer(x::Integer) = x
741741
Integer(x::Union{Float32, Float64}) = Int(x)
742742

743+
# Binding for the julia parser, called as
744+
#
745+
# Core._parse(text, filename, offset, options)
746+
#
747+
# Parse Julia code from the buffer `text`, starting at `offset` and attributing
748+
# it to `filename`. `text` may be a `String` or `svec(ptr::Ptr{UInt8},
749+
# len::Int)` for a raw unmanaged buffer. `options` should be one of `:atom`,
750+
# `:statement` or `:all`, indicating how much the parser will consume.
751+
#
752+
# `_parse` must return an `svec` containing an `Expr` and the new offset as an
753+
# `Int`.
754+
#
755+
# The internal jl_parse which will call into Core._parse if not `nothing`.
756+
eval(Core, :(_parse = nothing))
757+
743758
ccall(:jl_set_istopmod, Cvoid, (Any, Bool), Core, true)

base/client.jl

Lines changed: 5 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -154,8 +154,7 @@ function eval_user_input(errio, @nospecialize(ast), show_value::Bool)
154154
end
155155

156156
function _parse_input_line_core(s::String, filename::String)
157-
ex = ccall(:jl_parse_all, Any, (Ptr{UInt8}, Csize_t, Ptr{UInt8}, Csize_t),
158-
s, sizeof(s), filename, sizeof(filename))
157+
ex = Meta.parseall(s, filename=filename)
159158
if ex isa Expr && ex.head === :toplevel
160159
if isempty(ex.args)
161160
return nothing
@@ -439,30 +438,12 @@ end
439438
# MainInclude exists to hide Main.include and eval from `names(Main)`.
440439
baremodule MainInclude
441440
using ..Base
442-
include(mapexpr::Function, fname::AbstractString) = Base.include(mapexpr, Main, fname)
443-
# We inline the definition of include from loading.jl/include_relative to get one-frame stacktraces
444-
# for the common case of include(fname). Otherwise we would use:
445-
# include(fname::AbstractString) = Base.include(Main, fname)
441+
# These definitions calls Base._include rather than Base.include to get
442+
# one-frame stacktraces for the common case of using include(fname) in Main.
443+
include(mapexpr::Function, fname::AbstractString) = Base._include(mapexpr, Main, fname)
446444
function include(fname::AbstractString)
447-
mod = Main
448445
isa(fname, String) || (fname = Base.convert(String, fname)::String)
449-
path, prev = Base._include_dependency(mod, fname)
450-
for callback in Base.include_callbacks # to preserve order, must come before Core.include
451-
Base.invokelatest(callback, mod, path)
452-
end
453-
tls = Base.task_local_storage()
454-
tls[:SOURCE_PATH] = path
455-
local result
456-
try
457-
result = ccall(:jl_load, Any, (Any, Cstring), mod, path)
458-
finally
459-
if prev === nothing
460-
Base.delete!(tls, :SOURCE_PATH)
461-
else
462-
tls[:SOURCE_PATH] = prev
463-
end
464-
end
465-
return result
446+
Base._include(identity, Main, fname)
466447
end
467448
eval(x) = Core.eval(Main, x)
468449
end

base/compiler/compiler.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,5 +113,9 @@ include("compiler/optimize.jl") # TODO: break this up further + extract utilitie
113113
include("compiler/bootstrap.jl")
114114
ccall(:jl_set_typeinf_func, Cvoid, (Any,), typeinf_ext_toplevel)
115115

116+
include("compiler/parsing.jl")
117+
Core.eval(Core, :(_parse = Compiler.fl_parse))
118+
116119
end # baremodule Compiler
117120
))
121+

base/compiler/parsing.jl

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# This file is a part of Julia. License is MIT: https://julialang.org/license
2+
3+
# Call Julia's builtin flisp-based parser. `offset` is 0-based offset into the
4+
# byte buffer or string.
5+
function fl_parse(text::Union{Core.SimpleVector,String},
6+
filename::String, offset, options)
7+
if text isa Core.SimpleVector
8+
# Will be generated by C entry points jl_parse_string etc
9+
text, text_len = text
10+
else
11+
text_len = sizeof(text)
12+
end
13+
ccall(:jl_fl_parse, Any, (Ptr{UInt8}, Csize_t, Any, Csize_t, Any),
14+
text, text_len, filename, offset, options)
15+
end
16+
17+
function fl_parse(text::AbstractString, filename::AbstractString, offset, options)
18+
fl_parse(String(text), String(filename), offset, options)
19+
end

base/loading.jl

Lines changed: 47 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1082,14 +1082,31 @@ The optional first argument `mapexpr` can be used to transform the included code
10821082
it is evaluated: for each parsed expression `expr` in `code`, the `include_string` function
10831083
actually evaluates `mapexpr(expr)`. If it is omitted, `mapexpr` defaults to [`identity`](@ref).
10841084
"""
1085-
function include_string(mapexpr::Function, m::Module, txt_::AbstractString, fname::AbstractString="string")
1086-
txt = String(txt_)
1087-
if mapexpr === identity
1088-
ccall(:jl_load_file_string, Any, (Ptr{UInt8}, Csize_t, Cstring, Any),
1089-
txt, sizeof(txt), String(fname), m)
1090-
else
1091-
ccall(:jl_load_rewrite_file_string, Any, (Ptr{UInt8}, Csize_t, Cstring, Any, Any),
1092-
txt, sizeof(txt), String(fname), m, mapexpr)
1085+
function include_string(mapexpr::Function, mod::Module, code::AbstractString,
1086+
filename::AbstractString="string")
1087+
loc = LineNumberNode(1, Symbol(filename))
1088+
try
1089+
ast = Meta.parseall(code, filename=filename)
1090+
@assert Meta.isexpr(ast, :toplevel)
1091+
result = nothing
1092+
line_and_ex = Expr(:toplevel, loc, nothing)
1093+
for ex in ast.args
1094+
if ex isa LineNumberNode
1095+
loc = ex
1096+
line_and_ex.args[1] = ex
1097+
continue
1098+
end
1099+
ex = mapexpr(ex)
1100+
# Wrap things to be eval'd in a :toplevel expr to carry line
1101+
# information as part of the expr.
1102+
line_and_ex.args[2] = ex
1103+
result = Core.eval(mod, line_and_ex)
1104+
end
1105+
return result
1106+
catch exc
1107+
# TODO: Now that stacktraces are more reliable we should remove
1108+
# LoadError and expose the real error type directly.
1109+
rethrow(LoadError(filename, loc.line, exc))
10931110
end
10941111
end
10951112

@@ -1124,7 +1141,28 @@ The optional first argument `mapexpr` can be used to transform the included code
11241141
it is evaluated: for each parsed expression `expr` in `path`, the `include` function
11251142
actually evaluates `mapexpr(expr)`. If it is omitted, `mapexpr` defaults to [`identity`](@ref).
11261143
"""
1127-
Base.include # defined in sysimg.jl
1144+
Base.include # defined in Base.jl
1145+
1146+
# Full include() implementation which is used after bootstrap
1147+
function _include(mapexpr::Function, mod::Module, _path::AbstractString)
1148+
@_noinline_meta # Workaround for module availability in _simplify_include_frames
1149+
path, prev = _include_dependency(mod, _path)
1150+
for callback in include_callbacks # to preserve order, must come before eval in include_string
1151+
invokelatest(callback, mod, path)
1152+
end
1153+
code = read(path, String)
1154+
tls = task_local_storage()
1155+
tls[:SOURCE_PATH] = path
1156+
try
1157+
return include_string(mapexpr, mod, code, path)
1158+
finally
1159+
if prev === nothing
1160+
delete!(tls, :SOURCE_PATH)
1161+
else
1162+
tls[:SOURCE_PATH] = prev
1163+
end
1164+
end
1165+
end
11281166

11291167
"""
11301168
evalfile(path::AbstractString, args::Vector{String}=String[])

base/meta.jl

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,15 @@ struct ParseError <: Exception
149149
msg::AbstractString
150150
end
151151

152+
function _parse_string(text::AbstractString, filename::AbstractString,
153+
index::Integer, options)
154+
if index < 1 || index > ncodeunits(text) + 1
155+
throw(BoundsError(text, index))
156+
end
157+
ex, offset = Core._parse(text, filename, index-1, options)
158+
ex, offset+1
159+
end
160+
152161
"""
153162
parse(str, start; greedy=true, raise=true, depwarn=true)
154163
@@ -171,19 +180,11 @@ julia> Meta.parse("x = 3, y = 5", 5)
171180
"""
172181
function parse(str::AbstractString, pos::Integer; greedy::Bool=true, raise::Bool=true,
173182
depwarn::Bool=true)
174-
# pos is one based byte offset.
175-
# returns (expr, end_pos). expr is () in case of parse error.
176-
bstr = String(str)
177-
# For now, assume all parser warnings are depwarns
178-
ex, pos = with_logger(depwarn ? current_logger() : NullLogger()) do
179-
ccall(:jl_parse_string, Any,
180-
(Ptr{UInt8}, Csize_t, Int32, Int32),
181-
bstr, sizeof(bstr), pos-1, greedy ? 1 : 0)
182-
end
183+
ex, pos = _parse_string(str, "none", pos, greedy ? :statement : :atom)
183184
if raise && isa(ex,Expr) && ex.head === :error
184185
throw(ParseError(ex.args[1]))
185186
end
186-
return ex, pos+1 # C is zero-based, Julia is 1-based
187+
return ex, pos
187188
end
188189

189190
"""
@@ -223,6 +224,15 @@ function parse(str::AbstractString; raise::Bool=true, depwarn::Bool=true)
223224
return ex
224225
end
225226

227+
function parseatom(text::AbstractString, pos::Integer; filename="none")
228+
return _parse_string(text, filename, pos, :atom)
229+
end
230+
231+
function parseall(text::AbstractString; filename="none")
232+
ex,_ = _parse_string(text, filename, 1, :all)
233+
return ex
234+
end
235+
226236
"""
227237
partially_inline!(code::Vector{Any}, slot_replacements::Vector{Any},
228238
type_signature::Type{<:Tuple}, static_param_values::Vector{Any},

0 commit comments

Comments
 (0)