Skip to content

Commit 23a5d78

Browse files
committed
Add hook for testing JuliaLowering in core
Once the corresponding bindings are merged into julia, `@activate JuliaLowering` will set both `Core._parse` and `Core._lower` so that parsing produces our `SyntaxTree`s and we become the default lowerer. The parser hook copied from JuliaSyntax is unfortunate, but I think moving this hook there and keeping it in sync would be a bigger headache while JuliaLowering is still under development.
1 parent 8ab2d64 commit 23a5d78

File tree

2 files changed

+149
-0
lines changed

2 files changed

+149
-0
lines changed

src/JuliaLowering.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ _include("syntax_macros.jl")
3333

3434
_include("eval.jl")
3535

36+
_include("hooks.jl")
37+
3638
function __init__()
3739
_register_kinds()
3840
end

src/hooks.jl

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
using JuliaSyntax
2+
using JuliaLowering
3+
4+
# Becomes `Core._lower()` upon activating JuliaLowering. Returns an svec with
5+
# the lowered expr or linenode as its first element, and whatever we want after
6+
# it, until the API stabilizes
7+
function core_lowerer_hook(code, mod::Module, file="none", line=0, world=typemax(Csize_t), warn=false)
8+
if Base.isexpr(code, :syntaxtree)
9+
# Getting toplevel.c to check for types it doesn't know about is hard.
10+
# We wrap SyntaxTrees with this random expr head so that the call to
11+
# `jl_needs_lowering` in `jl_toplevel_eval_flex` returns true; this way
12+
# the SyntaxTree is handed back to us, unwraped here, and lowered.
13+
code = code.args[1]
14+
end
15+
if code isa Expr
16+
@warn("""JuliaLowering received an Expr instead of a SyntaxTree.
17+
This is currently expected when evaluating modules.
18+
Falling back to flisp...""",
19+
code=code, file=file, line=line, mod=mod)
20+
return Base.fl_lower(code, mod, file, line, world, warn)
21+
elseif code isa LineNumberNode
22+
return Core.svec(code)
23+
end
24+
try
25+
ctx1, st1 = expand_forms_1( mod, code)
26+
ctx2, st2 = expand_forms_2( ctx1, st1)
27+
ctx3, st3 = resolve_scopes( ctx2, st2)
28+
ctx4, st4 = convert_closures(ctx3, st3)
29+
ctx5, st5 = linearize_ir( ctx4, st4)
30+
ex = to_lowered_expr(mod, st5)
31+
return Core.svec(ex, st5, ctx5)
32+
catch exc
33+
@error("JuliaLowering failed — falling back to flisp!",
34+
exception=(exc,catch_backtrace()),
35+
code=code, file=file, line=line, mod=mod)
36+
return Base.fl_lower(st0, mod, file, line, world, warn)
37+
end
38+
end
39+
40+
# TODO: This is code copied from JuliaSyntax, adapted to produce
41+
# `Expr(:syntaxtree, st::SyntaxTree)`.
42+
function core_parse_for_lowering_hook(code, filename::String, lineno::Int, offset::Int, options::Symbol)
43+
try
44+
# TODO: Check that we do all this input wrangling without copying the
45+
# code buffer
46+
if code isa Core.SimpleVector
47+
# The C entry points will pass us this form.
48+
(ptr,len) = code
49+
code = String(unsafe_wrap(Array, ptr, len))
50+
elseif !(code isa String || code isa SubString || code isa Vector{UInt8})
51+
# For non-Base string types, convert to UTF-8 encoding, using an
52+
# invokelatest to avoid world age issues.
53+
code = Base.invokelatest(String, code)
54+
end
55+
stream = JuliaSyntax.ParseStream(code, offset+1)
56+
if options === :statement || options === :atom
57+
# To copy the flisp parser driver:
58+
# * Parsing atoms consumes leading trivia
59+
# * Parsing statements consumes leading+trailing trivia
60+
JuliaSyntax.bump_trivia(stream)
61+
if peek(stream) == K"EndMarker"
62+
# If we're at the end of stream after skipping whitespace, just
63+
# return `nothing` to indicate this rather than attempting to
64+
# parse a statement or atom and failing.
65+
return Core.svec(nothing, last_byte(stream))
66+
end
67+
end
68+
JuliaSyntax.parse!(stream; rule=options)
69+
if options === :statement
70+
JuliaSyntax.bump_trivia(stream; skip_newlines=false)
71+
if peek(stream) == K"NewlineWs"
72+
JuliaSyntax.bump(stream)
73+
end
74+
end
75+
76+
if JuliaSyntax.any_error(stream)
77+
pos_before_comments = JuliaSyntax.last_non_whitespace_byte(stream)
78+
tree = JuliaSyntax.build_tree(SyntaxNode, stream, first_line=lineno, filename=filename)
79+
tag = JuliaSyntax._incomplete_tag(tree, pos_before_comments)
80+
exc = JuliaSyntax.ParseError(stream, filename=filename, first_line=lineno,
81+
incomplete_tag=tag)
82+
msg = sprint(showerror, exc)
83+
error_ex = Expr(tag === :none ? :error : :incomplete,
84+
Meta.ParseError(msg, exc))
85+
ex = if options === :all
86+
# When encountering a toplevel error, the reference parser
87+
# * truncates the top level expression arg list before that error
88+
# * includes the last line number
89+
# * appends the error message
90+
topex = Expr(tree)
91+
@assert topex.head == :toplevel
92+
i = findfirst(JuliaSyntax._has_nested_error, topex.args)
93+
if i > 1 && topex.args[i-1] isa LineNumberNode
94+
i -= 1
95+
end
96+
resize!(topex.args, i-1)
97+
_,errort = JuliaSyntax._first_error(tree)
98+
push!(topex.args, LineNumberNode(JuliaSyntax.source_line(errort), filename))
99+
push!(topex.args, error_ex)
100+
topex
101+
else
102+
error_ex
103+
end
104+
else
105+
# See unwrapping of `:syntaxtree` above.
106+
ex = Expr(:syntaxtree, JuliaSyntax.build_tree(SyntaxTree, stream; filename=filename, first_line=lineno))
107+
end
108+
109+
# Note the next byte in 1-based indexing is `last_byte(stream) + 1` but
110+
# the Core hook must return an offset (ie, it's 0-based) so the factors
111+
# of one cancel here.
112+
last_offset = last_byte(stream)
113+
114+
# Rewrap result in an svec for use by the C code
115+
return Core.svec(ex, last_offset)
116+
catch exc
117+
@error("""JuliaSyntax parser failed — falling back to flisp!
118+
This is not your fault. Please submit a bug report to https://github.com/JuliaLang/JuliaSyntax.jl/issues""",
119+
exception=(exc,catch_backtrace()),
120+
offset=offset,
121+
code=code)
122+
123+
Base.fl_parse(code, filename, lineno, offset, options)
124+
end
125+
end
126+
127+
const _has_v1_13_hooks = isdefined(Core, :_lower)
128+
129+
function activate!(enable=true)
130+
if !_has_v1_13_hooks
131+
error("Cannot use JuliaLowering without `Core._lower` binding or in $VERSION < 1.13")
132+
end
133+
134+
if enable
135+
if !isnothing(Base.active_repl_backend)
136+
# TODO: These act on parsed exprs, which we don't have.
137+
# Reimplementation needed (e.g. for scoping rules).
138+
empty!(Base.active_repl_backend.ast_transforms)
139+
end
140+
141+
Core._setlowerer!(core_lowerer_hook)
142+
Core._setparser!(core_parse_for_lowering_hook)
143+
else
144+
Core._setlowerer!(Base.fl_lower)
145+
Core._setparser!(JuliaSyntax.core_parse_hook)
146+
end
147+
end

0 commit comments

Comments
 (0)