Skip to content

Commit d94b710

Browse files
committed
Conversion from Expr and JuliaSyntax bump
1 parent 982199f commit d94b710

File tree

6 files changed

+771
-28
lines changed

6 files changed

+771
-28
lines changed

src/JuliaLowering.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ using JuliaSyntax: highlight, Kind, @KSet_str
1313
using JuliaSyntax: is_leaf, children, numchildren, head, kind, flags, has_flags, numeric_flags
1414
using JuliaSyntax: filename, first_byte, last_byte, byte_range, sourcefile, source_location, span, sourcetext
1515

16-
using JuliaSyntax: is_literal, is_number, is_operator, is_prec_assignment, is_prefix_call, is_infix_op_call, is_postfix_op_call, is_error, is_dotted
16+
using JuliaSyntax: is_literal, is_number, is_operator, is_prec_assignment, is_prefix_call, is_infix_op_call, is_postfix_op_call, is_error
1717

1818
_include("kinds.jl")
1919
_register_kinds()
@@ -32,6 +32,7 @@ _include("runtime.jl")
3232
_include("syntax_macros.jl")
3333

3434
_include("eval.jl")
35+
_include("compat.jl")
3536

3637
_include("hooks.jl")
3738

src/compat.jl

Lines changed: 373 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,373 @@
1+
const JS = JuliaSyntax
2+
3+
function _insert_tree_node(graph::SyntaxGraph, k::JuliaSyntax.Kind,
4+
src::SourceAttrType, flags::UInt16=0x0000)
5+
id = newnode!(graph)
6+
sethead!(graph, id, k)
7+
setattr!(graph, id, source=src)
8+
setflags!(graph, id, flags)
9+
return id
10+
end
11+
12+
"""
13+
An Expr -> SyntaxTree transformation that should preserve semantics, but will
14+
have low-quality provenance info (namely, each tree node will be associated with
15+
the last seen LineNumberNode in the pre-order expr traversal).
16+
17+
Last-resort option so that, for example, we can lower the output of old
18+
Expr-producing macros. Always prefer re-parsing source text over using this.
19+
20+
Does not support lowered exprs.
21+
"""
22+
function expr_to_syntaxtree(@nospecialize(e), lnn::Union{LineNumberNode, Nothing}=nothing)
23+
graph = SyntaxGraph()
24+
ensure_attributes!(graph,
25+
kind=Kind, syntax_flags=UInt16,
26+
source=SourceAttrType, var_id=Int, value=Any,
27+
name_val=String, is_toplevel_thunk=Bool)
28+
toplevel_src = if isnothing(lnn)
29+
# Provenance sinkhole for all nodes until we hit a linenode
30+
dummy_src = SourceRef(
31+
SourceFile("No source for expression $e"),
32+
1, JuliaSyntax.GreenNode(K"None", 0))
33+
_insert_tree_node(graph, K"None", dummy_src)
34+
else
35+
lnn
36+
end
37+
st_id, _ = _insert_convert_expr(e, graph, toplevel_src)
38+
out = SyntaxTree(graph, st_id)
39+
return out
40+
end
41+
42+
function _expr_replace!(@nospecialize(e), replace_pred::Function, replacer!::Function,
43+
recurse_pred=(@nospecialize e)->true)
44+
if replace_pred(e)
45+
replacer!(e)
46+
end
47+
if e isa Expr && recurse_pred(e)
48+
for a in e.args
49+
_expr_replace!(a, replace_pred, replacer!, recurse_pred)
50+
end
51+
end
52+
end
53+
54+
function _to_iterspec(exs::Vector)
55+
if length(exs) === 1 && exs[1].head === :filter
56+
@assert length(exs[1].args) >= 2
57+
return Expr(:filter, _to_iterspec(exs[1].args[2:end]), exs[1].args[1])
58+
end
59+
outex = Expr(:iteration)
60+
for e in exs
61+
if e.head === :block
62+
for iter in e.args
63+
push!(outex.args, Expr(:in, iter.args...))
64+
end
65+
elseif e.head === :(=)
66+
push!(outex.args, Expr(:in, e.args...))
67+
else
68+
@assert false "unknown iterspec in $e"
69+
end
70+
end
71+
return outex
72+
end
73+
74+
"""
75+
Return `e.args`, but with any parameters in SyntaxTree (flattened, source) order.
76+
Parameters are expected to be as `e.args[pos]`.
77+
78+
e.g. orderings of (a,b,c;d;e;f):
79+
Expr: (tuple (parameters (parameters (parameters f) e) d) a b c)
80+
SyntaxTree: (tuple a b c (parameters d) (parameters e) (parameters f))
81+
"""
82+
function collect_expr_parameters(e::Expr, pos::Int)
83+
params = expr_parameters(e, pos)
84+
isnothing(params) && return e.args
85+
args = Any[e.args[1:pos-1]..., e.args[pos+1:end]...]
86+
return _flatten_params(params, args)
87+
end
88+
function _flatten_params(p::Expr, out::Vector{Any})
89+
p1 = expr_parameters(p, 1)
90+
if !isnothing(p1)
91+
push!(out, Expr(:parameters, p.args[2:end]...))
92+
_flatten_params(p1, out)
93+
else
94+
push!(out, p::Any)
95+
end
96+
return out
97+
end
98+
function expr_parameters(p::Expr, pos::Int)
99+
if length(p.args) >= pos &&
100+
p.args[pos] isa Expr &&
101+
p.args[pos].head === :parameters
102+
return p.args[pos]
103+
end
104+
return nothing
105+
end
106+
107+
# Get kind by string if exists. TODO relies on internals
108+
function find_kind(s::String)
109+
out = get(JuliaSyntax._kind_str_to_int, s, nothing)
110+
return isnothing(out) ? nothing : JuliaSyntax.Kind(out)
111+
end
112+
113+
function is_dotted_operator(s::AbstractString)
114+
return length(s) >= 2 &&
115+
s[1] === '.' &&
116+
JS.is_operator(something(find_kind(s[2:end]), K"None"))
117+
end
118+
119+
"""
120+
Insert `e` converted to a syntaxtree into graph and recurse on children. Return
121+
a pair (my_node_id, last_srcloc). Should not mutate `e`.
122+
123+
`src` is the latest location found in the pre-order traversal, and is the line
124+
number node to be associated with `e`.
125+
"""
126+
function _insert_convert_expr(@nospecialize(e), graph::SyntaxGraph, src::SourceAttrType)
127+
if e isa Symbol
128+
estr = String(e)
129+
st_k = K"Identifier"
130+
id = _insert_tree_node(graph, st_k, src)
131+
setattr!(graph, id, name_val=estr)
132+
if !Base.isoperator(e) && !Base.is_valid_identifier(e)
133+
# Expr doesn't record whether or not var"" was used, so assume it's
134+
# necessary here and wrap with K"var", but lose the information otherwise.
135+
var_id = _insert_tree_node(graph, K"var", src)
136+
setchildren!(graph, var_id, [id])
137+
return (var_id, src)
138+
end
139+
return (id, src)
140+
elseif e isa LineNumberNode
141+
return (nothing, e)
142+
elseif e isa QuoteNode
143+
id = _insert_tree_node(graph, K"quote", src)
144+
quote_child, _ = _insert_convert_expr(e.value, graph, src)
145+
setchildren!(graph, id, NodeId[quote_child])
146+
return (id, src)
147+
elseif e isa String
148+
id = _insert_tree_node(graph, K"string", src)
149+
id_inner = _insert_tree_node(graph, K"String", src)
150+
setattr!(graph, id_inner, value=e)
151+
setchildren!(graph, id, [id_inner])
152+
return (id, src)
153+
elseif !(e isa Expr)
154+
if !(e isa Union{Number, Bool, Char, GlobalRef})
155+
@info "what is this" e typeof(e)
156+
end
157+
# TODO: st_k of Float. others?
158+
st_k = e isa Integer ? K"Integer" : find_kind(string(typeof(e)))
159+
id = _insert_tree_node(graph, isnothing(st_k) ? K"Value" : st_k, src)
160+
setattr!(graph, id, value=e)
161+
return (id, src)
162+
end
163+
nargs = length(e.args)
164+
165+
# `e` is an expr. In many cases, it suffices to
166+
# - guess that the kind name is the same as the expr head
167+
# - add no syntax flags
168+
# - map e.args to syntax tree children one-to-one
169+
maybe_kind = find_kind(string(e.head))
170+
st_k = isnothing(maybe_kind) ? K"None" : maybe_kind
171+
st_flags = 0x0000
172+
child_exprs = copy(e.args)
173+
174+
# The following are special cases where the kind, flags, or children are
175+
# different from what we guessed above.
176+
if Base.isoperator(e.head) && st_k === K"None"
177+
# e.head is an updating assignment operator (+=, .-=, etc). Non-=
178+
# dotted ops are wrapped in a call, so we don't reach this.
179+
s = string(e.head)
180+
@assert s[end] === '=' && nargs === 2
181+
if s[1] === '.'
182+
st_k = K".op="
183+
op = s[2:end-1]
184+
else
185+
st_k = K"op="
186+
op = s[1:end-1]
187+
end
188+
child_exprs = [e.args[1], Symbol(op), e.args[2]]
189+
elseif e.head === :comparison
190+
for i = 2:2:length(child_exprs)
191+
op = child_exprs[i]
192+
@assert op isa Symbol
193+
op_s = string(op)
194+
if is_dotted_operator(op_s)
195+
child_exprs[i] = Expr(:., Symbol(op_s[2:end]))
196+
end
197+
end
198+
elseif e.head === :macrocall
199+
@assert nargs >= 1
200+
a1 = e.args[1]
201+
child_exprs = collect_expr_parameters(e, 2)
202+
if a1 isa Symbol
203+
child_exprs[1] = Expr(:MacroName, a1)
204+
elseif a1 isa Expr && a1.head === :(.) && a1.args[2] isa QuoteNode
205+
child_exprs[1] = Expr(:(.), a1.args[1], Expr(:MacroName, a1.args[2].value))
206+
elseif a1 isa GlobalRef
207+
# syntax-introduced macrocalls
208+
if a1.name === Symbol("@cmd")
209+
# expr_children = []
210+
elseif a1.name === Symbol("@doc")
211+
elseif a1.name === Symbol("@int128_str")
212+
elseif a1.name === Symbol("@int128_str")
213+
elseif a1.name === Symbol("@big_str")
214+
end
215+
else
216+
dump(e)
217+
error("Unknown macrocall form $e")
218+
end
219+
220+
# TODO node->expr handles do blocks here?
221+
elseif e.head === Symbol("'")
222+
@assert nargs === 1
223+
st_k = K"call"
224+
st_flags |= JS.POSTFIX_OP_FLAG
225+
child_exprs = [e.head, e.args[1]]
226+
elseif e.head === :. && nargs === 2
227+
a2 = e.args[2]
228+
if a2 isa Expr && a2.head === :tuple
229+
st_k = K"dotcall"
230+
tuple_exprs = collect_expr_parameters(a2, 1)
231+
child_exprs = pushfirst!(tuple_exprs, e.args[1])
232+
elseif a2 isa QuoteNode && a2.value isa Symbol
233+
e.args[2] = a2.value
234+
elseif a2 isa Expr && a2.head === :MacroName
235+
else
236+
@error "Unknown 2-arg dot form?" e
237+
end
238+
elseif e.head === :for
239+
@assert nargs === 2
240+
child_exprs = [_to_iterspec([e.args[1]]), e.args[2]]
241+
elseif e.head === :where
242+
@assert nargs >= 2
243+
if !(e.args[2] isa Expr && e.args[2].head === :braces)
244+
child_exprs = [e.args[1], Expr(:braces, e.args[2:end]...)]
245+
end
246+
elseif e.head in (:tuple, :vect, :braces)
247+
child_exprs = collect_expr_parameters(e, 1)
248+
elseif e.head in (:curly, :ref)
249+
child_exprs = collect_expr_parameters(e, 2)
250+
elseif e.head === :try
251+
child_exprs = Any[e.args[1]]
252+
# Expr:
253+
# (try (block ...) var (block ...) [block ...] [block ...])
254+
# # try catch_var catch finally else
255+
# SyntaxTree:
256+
# (try (block ...)
257+
# [catch var (block ...)]
258+
# [else (block ...)]
259+
# [finally (block ...)])
260+
if e.args[2] != false || e.args[3] != false
261+
push!(child_exprs,
262+
Expr(:catch,
263+
e.args[2] === false ? Expr(:catch_var_placeholder) : e.args[2],
264+
e.args[3] === false ? nothing : e.args[3]))
265+
end
266+
if nargs >= 5
267+
push!(child_exprs, Expr(:else, e.args[5]))
268+
end
269+
if nargs >= 4
270+
push!(child_exprs,
271+
Expr(:finally, e.args[4] === false ? nothing : e.args[4]))
272+
end
273+
elseif e.head === :generator # TODO flatten
274+
child_exprs = [e.args[1], _to_iterspec(e.args[2:end])]
275+
elseif e.head === :ncat || e.head === :nrow
276+
st_flags |= JS.set_numeric_flags(e.args[1])
277+
child_exprs = child_exprs[2:end]
278+
elseif e.head === :typed_ncat
279+
st_flags |= JS.set_numeric_flags(e.args[2])
280+
deleteat!(child_exprs, 2)
281+
elseif e.head === :(->)
282+
@assert nargs === 2
283+
if e.args[1] isa Symbol
284+
child_exprs[1] = Expr(:tuple, e.args[1])
285+
end
286+
elseif e.head === :call
287+
child_exprs = collect_expr_parameters(e, 2)
288+
a1 = child_exprs[1]
289+
if a1 isa Symbol
290+
a1s = string(a1)
291+
if is_dotted_operator(a1s)
292+
# non-assigning dotop like .+ or .==
293+
st_k = K"dotcall"
294+
child_exprs[1] = Symbol(a1s[2:end])
295+
end
296+
end
297+
elseif e.head === :(=)
298+
if e.args[1] isa Expr && e.args[1].head === :call
299+
st_k = K"function"
300+
st_flags |= JuliaSyntax.SHORT_FORM_FUNCTION_FLAG
301+
if e.args[2] isa Expr && e.args[2].head === :block
302+
# Strip block in (= (call ...) (block x)) if x has no siblings
303+
a2a = filter(a -> !(a isa LineNumberNode), e.args[2].args)
304+
length(a2a) === 1 && (child_exprs[2] = a2a[1])
305+
end
306+
end
307+
elseif e.head === :module
308+
@assert nargs === 3
309+
if !e.args[1]
310+
st_flags |= JS.BARE_MODULE_FLAG
311+
end
312+
child_exprs = [e.args[2], e.args[3]]
313+
elseif e.head === :do
314+
# Expr:
315+
# (do (call f args...) (-> (tuple lam_args...) (block ...)))
316+
# SyntaxTree:
317+
# (call f args... (do (tuple lam_args...) (block ...)))
318+
st_k = K"call"
319+
child_exprs = [e.args[1].args..., Expr(:do_lambda, e.args[2].args...)]
320+
elseif e.head === :let
321+
if nargs >= 1 && e.args[1] isa Expr && e.args[1].head !== :block
322+
child_exprs[1] = Expr(:block, e.args[1])
323+
end
324+
elseif e.head === :struct
325+
e.args[1] && (st_flags |= JS.MUTABLE_FLAG)
326+
child_exprs = child_exprs[2:end]
327+
# TODO handle docstrings after refactor
328+
elseif (e.head === :using || e.head === :import)
329+
_expr_replace!(e,
330+
(e)->(e isa Expr && e.head === :.),
331+
(e)->(e.head = :importpath))
332+
elseif e.head === :kw
333+
st_k = K"="
334+
end
335+
336+
# Temporary heads introduced by converting the parent expr
337+
if e.head === :MacroName
338+
@assert nargs === 1
339+
st_id = _insert_tree_node(graph, K"MacroName", src, st_flags)
340+
mac_name = string(e.args[1])
341+
setattr!(graph, st_id, name_val=mac_name == "@__dot__" ? "@." : mac_name)
342+
if !Base.is_valid_identifier(mac_name[2:end])
343+
var_id = _insert_tree_node(graph, K"var", src)
344+
setchildren!(graph, var_id, [st_id])
345+
return (var_id, src)
346+
end
347+
return (st_id, src)
348+
elseif e.head === :catch_var_placeholder
349+
st_id = _insert_tree_node(graph, K"Placeholder", src, st_flags)
350+
setattr!(graph, st_id, name_val="")
351+
return (st_id, src)
352+
elseif e.head === :do_lambda
353+
st_k = K"do"
354+
end
355+
356+
if st_k === K"None"
357+
dump(e)
358+
error("no kind for $(e.head)")
359+
end
360+
361+
st_flags |= JS.NON_TERMINAL_FLAG
362+
st_id = _insert_tree_node(graph, st_k, src, st_flags)
363+
st_child_ids = NodeId[]
364+
last_src = src
365+
for c in child_exprs
366+
(c_id, c_src) = _insert_convert_expr(c, graph, last_src)
367+
isnothing(c_id) || push!(st_child_ids, c_id)
368+
last_src = something(c_src, src)
369+
end
370+
371+
setchildren!(graph, st_id, st_child_ids)
372+
return (st_id, last_src)
373+
end

0 commit comments

Comments
 (0)