Skip to content

Commit f60aefe

Browse files
committed
Swappable entry point Core._parse for Julia parser
Add Core._parse binding as the internal entry point for parsing of Julia code. This is called by both C and Julia code which wishes to parse Julia. For now, it points at the flisp parser by default. Refactor include() implementation to be done in Julia code, and parsing rearrangement: * Implement include() and include_string() in Julia. This also reunifies the versions of include() in MainInclude and Base which had started diverging. * Add internal Core.Compiler.fl_parse and set this as the default value of the Core._parse binding. * Use Core._parse from Meta * Add `Meta.parseall()` for top-level parsing. For now as an internal function, but this will likely be made public shortly. Refactoring in C runtime code: * Remove flisp code from jl_parse_eval_all. This makes the top level parse-lower-eval loop independent of flisp internals. * Remove jl_parse_eval_all from use as the include implementation (still used during bootstrap). This also allowed the removal of mapexpr handling from the C code. * 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) * Unify flisp parsing to always parse from string rather than directly from file.
1 parent fb1970c commit f60aefe

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+
_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)