Skip to content

Commit 97e3fe8

Browse files
authored
Merge pull request #35243 from JuliaLang/cjf/parser-api
Replaceable single entry point for parser
2 parents 07ff9a9 + 3c131bf commit 97e3fe8

24 files changed

+507
-290
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/errorshow.jl

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -681,6 +681,41 @@ function is_kw_sorter_name(name::Symbol)
681681
return !startswith(sn, '#') && endswith(sn, "##kw")
682682
end
683683

684+
# For improved user experience, filter out frames for include() implementation
685+
# - see #33065. See also #35371 for extended discussion of internal frames.
686+
function _simplify_include_frames(trace)
687+
i = length(trace)
688+
kept_frames = trues(i)
689+
first_ignored = nothing
690+
while i >= 1
691+
frame, _ = trace[i]
692+
mod = parentmodule(frame)
693+
if isnothing(first_ignored)
694+
if mod === Base && frame.func === :_include
695+
# Hide include() machinery by default
696+
first_ignored = i
697+
end
698+
else
699+
# Hack: allow `mod==nothing` as a workaround for inlined functions.
700+
# TODO: Fix this by improving debug info.
701+
if mod in (Base,Core,nothing) && 1+first_ignored-i <= 5
702+
if frame.func == :eval
703+
kept_frames[i:first_ignored] .= false
704+
first_ignored = nothing
705+
end
706+
else
707+
# Bail out to avoid hiding frames in unexpected circumstances
708+
first_ignored = nothing
709+
end
710+
end
711+
i -= 1
712+
end
713+
if !isnothing(first_ignored)
714+
kept_frames[i:first_ignored] .= false
715+
end
716+
return trace[kept_frames]
717+
end
718+
684719
function process_backtrace(t::Vector, limit::Int=typemax(Int); skipC = true)
685720
n = 0
686721
last_frame = StackTraces.UNKNOWN
@@ -721,7 +756,7 @@ function process_backtrace(t::Vector, limit::Int=typemax(Int); skipC = true)
721756
if n > 0
722757
push!(ret, (last_frame, n))
723758
end
724-
return ret
759+
return _simplify_include_frames(ret)
725760
end
726761

727762
function show_exception_stack(io::IO, stack::Vector)

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},

base/stacktraces.jl

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -268,22 +268,28 @@ function show(io::IO, frame::StackFrame; full_path::Bool=false)
268268
end
269269
end
270270

271+
function Base.parentmodule(frame::StackFrame)
272+
if frame.linfo isa MethodInstance
273+
def = frame.linfo.def
274+
if def isa Module
275+
return def
276+
else
277+
return (def::Method).module
278+
end
279+
else
280+
# The module is not always available (common reasons include inlined
281+
# frames and frames arising from the interpreter)
282+
nothing
283+
end
284+
end
285+
271286
"""
272287
from(frame::StackFrame, filter_mod::Module) -> Bool
273288
274289
Returns whether the `frame` is from the provided `Module`
275290
"""
276291
function from(frame::StackFrame, m::Module)
277-
finfo = frame.linfo
278-
result = false
279-
280-
if finfo isa MethodInstance
281-
frame_m = finfo.def
282-
isa(frame_m, Method) && (frame_m = frame_m.module)
283-
result = nameof(frame_m) === nameof(m)
284-
end
285-
286-
return result
292+
return parentmodule(frame) === m
287293
end
288294

289295
end

src/.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,7 @@
2020
/julia_version.h
2121
/flisp/host
2222
/support/host
23+
24+
# Clang compilation database
25+
/compile_commands*.json
26+
.clangd/

0 commit comments

Comments
 (0)