Skip to content

Commit 78a76c3

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

File tree

6 files changed

+755
-28
lines changed

6 files changed

+755
-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: 364 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,364 @@
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+
if !Base.is_valid_identifier(e)
129+
# Expr doesn't record whether or not var"" was used. Should we
130+
# re-introduce a K"var" here?
131+
end
132+
estr = String(e)
133+
st_k = K"Identifier"
134+
id = _insert_tree_node(graph, st_k, src)
135+
setattr!(graph, id, name_val=estr)
136+
return (id, src)
137+
elseif e isa LineNumberNode
138+
return (nothing, e)
139+
elseif e isa QuoteNode
140+
id = _insert_tree_node(graph, K"quote", src)
141+
quote_child, _ = _insert_convert_expr(e.value, graph, src)
142+
setchildren!(graph, id, NodeId[quote_child])
143+
return (id, src)
144+
elseif e isa String
145+
id = _insert_tree_node(graph, K"string", src)
146+
id_inner = _insert_tree_node(graph, K"String", src)
147+
setattr!(graph, id_inner, value=e)
148+
setchildren!(graph, id, [id_inner])
149+
return (id, src)
150+
elseif !(e isa Expr)
151+
if !(e isa Union{Number, Bool, Char, GlobalRef})
152+
@info "what is this" e typeof(e)
153+
end
154+
# TODO: st_k of Float. others?
155+
st_k = e isa Integer ? K"Integer" : find_kind(string(typeof(e)))
156+
id = _insert_tree_node(graph, isnothing(st_k) ? K"Value" : st_k, src)
157+
setattr!(graph, id, value=e)
158+
return (id, src)
159+
end
160+
nargs = length(e.args)
161+
162+
# `e` is an expr. In many cases, it suffices to
163+
# - guess that the kind name is the same as the expr head
164+
# - add no syntax flags
165+
# - map e.args to syntax tree children one-to-one
166+
maybe_kind = find_kind(string(e.head))
167+
st_k = isnothing(maybe_kind) ? K"None" : maybe_kind
168+
st_flags = 0x0000
169+
child_exprs = copy(e.args)
170+
171+
# The following are special cases where the kind, flags, or children are
172+
# different from what we guessed above.
173+
if Base.isoperator(e.head) && st_k === K"None"
174+
# e.head is an updating assignment operator (+=, .-=, etc). Non-=
175+
# dotted ops are wrapped in a call, so we don't reach this.
176+
s = string(e.head)
177+
@assert s[end] === '=' && nargs === 2
178+
if s[1] === '.'
179+
st_k = K".op="
180+
op = s[2:end-1]
181+
else
182+
st_k = K"op="
183+
op = s[1:end-1]
184+
end
185+
child_exprs = [e.args[1], Symbol(op), e.args[2]]
186+
elseif e.head === :comparison
187+
for i = 2:2:length(child_exprs)
188+
op = child_exprs[i]
189+
@assert op isa Symbol
190+
op_s = string(op)
191+
if is_dotted_operator(op_s)
192+
child_exprs[i] = Expr(:., Symbol(op_s[2:end]))
193+
end
194+
end
195+
elseif e.head === :macrocall
196+
@assert nargs >= 1
197+
a1 = e.args[1]
198+
child_exprs = collect_expr_parameters(e, 2)
199+
if a1 isa Symbol
200+
child_exprs[1] = Expr(:MacroName, a1)
201+
elseif a1 isa Expr && a1.head === :(.) && a1.args[2] isa QuoteNode
202+
child_exprs[1] = Expr(:(.), a1.args[1], Expr(:MacroName, a1.args[2].value))
203+
elseif a1 isa GlobalRef
204+
# syntax-introduced macrocalls
205+
if a1.name === Symbol("@cmd")
206+
# expr_children = []
207+
elseif a1.name === Symbol("@doc")
208+
elseif a1.name === Symbol("@int128_str")
209+
elseif a1.name === Symbol("@int128_str")
210+
elseif a1.name === Symbol("@big_str")
211+
end
212+
else
213+
dump(e)
214+
error("Unknown macrocall form $e")
215+
end
216+
217+
# TODO node->expr handles do blocks here?
218+
elseif e.head === Symbol("'")
219+
@assert nargs === 1
220+
st_k = K"call"
221+
st_flags |= JS.POSTFIX_OP_FLAG
222+
child_exprs = [e.head, e.args[1]]
223+
elseif e.head === :. && nargs === 2
224+
a2 = e.args[2]
225+
if a2 isa Expr && a2.head === :tuple
226+
st_k = K"dotcall"
227+
tuple_exprs = collect_expr_parameters(a2, 1)
228+
child_exprs = pushfirst!(tuple_exprs, e.args[1])
229+
elseif a2 isa QuoteNode && a2.value isa Symbol
230+
e.args[2] = a2.value
231+
elseif a2 isa Expr && a2.head === :MacroName
232+
else
233+
@error "Unknown 2-arg dot form?" e
234+
end
235+
elseif e.head === :for
236+
@assert nargs === 2
237+
child_exprs = [_to_iterspec([e.args[1]]), e.args[2]]
238+
elseif e.head === :where
239+
@assert nargs >= 2
240+
if !(e.args[2] isa Expr && e.args[2].head === :braces)
241+
child_exprs = [e.args[1], Expr(:braces, e.args[2:end]...)]
242+
end
243+
elseif e.head in (:tuple, :vect, :braces)
244+
child_exprs = collect_expr_parameters(e, 1)
245+
elseif e.head in (:curly, :ref)
246+
child_exprs = collect_expr_parameters(e, 2)
247+
elseif e.head === :try
248+
child_exprs = Any[e.args[1]]
249+
# Expr:
250+
# (try (block ...) var (block ...) [block ...] [block ...])
251+
# # try catch_var catch finally else
252+
# SyntaxTree:
253+
# (try (block ...)
254+
# [catch var (block ...)]
255+
# [else (block ...)]
256+
# [finally (block ...)])
257+
if e.args[2] != false || e.args[3] != false
258+
push!(child_exprs,
259+
Expr(:catch,
260+
e.args[2] === false ? Expr(:catch_var_placeholder) : e.args[2],
261+
e.args[3] === false ? nothing : e.args[3]))
262+
end
263+
if nargs >= 5
264+
push!(child_exprs, Expr(:else, e.args[5]))
265+
end
266+
if nargs >= 4
267+
push!(child_exprs,
268+
Expr(:finally, e.args[4] === false ? nothing : e.args[4]))
269+
end
270+
elseif e.head === :generator # TODO flatten
271+
child_exprs = [e.args[1], _to_iterspec(e.args[2:end])]
272+
elseif e.head === :ncat || e.head === :nrow
273+
st_flags |= JS.set_numeric_flags(e.args[1])
274+
child_exprs = child_exprs[2:end]
275+
elseif e.head === :typed_ncat
276+
st_flags |= JS.set_numeric_flags(e.args[2])
277+
deleteat!(child_exprs, 2)
278+
elseif e.head === :(->)
279+
@assert nargs === 2
280+
if e.args[1] isa Symbol
281+
child_exprs[1] = Expr(:tuple, e.args[1])
282+
end
283+
elseif e.head === :call
284+
child_exprs = collect_expr_parameters(e, 2)
285+
a1 = child_exprs[1]
286+
if a1 isa Symbol
287+
a1s = string(a1)
288+
if is_dotted_operator(a1s)
289+
# non-assigning dotop like .+ or .==
290+
st_k = K"dotcall"
291+
child_exprs[1] = Symbol(a1s[2:end])
292+
end
293+
end
294+
elseif e.head === :(=)
295+
if e.args[1] isa Expr && e.args[1].head === :call
296+
st_k = K"function"
297+
st_flags |= JuliaSyntax.SHORT_FORM_FUNCTION_FLAG
298+
# we wrap with a block going st->expr, but do we need to strip it here?
299+
# let body = e.args[2]
300+
# if body isa Expr && body.head === :block && length(body.args) === 1
301+
# child_exprs[2] = body[1]
302+
# end
303+
# end
304+
end
305+
elseif e.head === :module
306+
@assert nargs === 3
307+
if !e.args[1]
308+
st_flags |= JS.BARE_MODULE_FLAG
309+
end
310+
child_exprs = [e.args[2], e.args[3]]
311+
elseif e.head === :do
312+
# Expr:
313+
# (do (call f args...) (-> (tuple lam_args...) (block ...)))
314+
# SyntaxTree:
315+
# (call f args... (do (tuple lam_args...) (block ...)))
316+
st_k = K"call"
317+
child_exprs = [e.args[1].args..., Expr(:do_lambda, e.args[2].args...)]
318+
elseif e.head === :let
319+
if nargs >= 1 && e.args[1] isa Expr && e.args[1].head !== :block
320+
child_exprs[1] = Expr(:block, e.args[1])
321+
end
322+
elseif e.head === :struct
323+
e.args[1] && (st_flags |= JS.MUTABLE_FLAG)
324+
child_exprs = child_exprs[2:end]
325+
# TODO handle docstrings after refactor
326+
elseif (e.head === :using || e.head === :import)
327+
_expr_replace!(e,
328+
(e)->(e isa Expr && e.head === :.),
329+
(e)->(e.head = :importpath))
330+
elseif e.head === :kw
331+
st_k = K"="
332+
end
333+
334+
# Temporary heads introduced by converting the parent expr
335+
if e.head === :MacroName
336+
@assert nargs === 1
337+
st_id = _insert_tree_node(graph, K"MacroName", src, st_flags)
338+
setattr!(graph, st_id, name_val=string(e.args[1]))
339+
return (st_id, src)
340+
elseif e.head === :catch_var_placeholder
341+
st_id = _insert_tree_node(graph, K"Placeholder", src, st_flags)
342+
setattr!(graph, st_id, name_val="")
343+
return (st_id, src)
344+
elseif e.head === :do_lambda
345+
st_k = K"do"
346+
end
347+
348+
if st_k === K"None"
349+
error("no kind for", e)
350+
end
351+
352+
st_flags |= JS.NON_TERMINAL_FLAG
353+
st_id = _insert_tree_node(graph, st_k, src, st_flags)
354+
st_child_ids = NodeId[]
355+
last_src = src
356+
for c in child_exprs
357+
(c_id, c_src) = _insert_convert_expr(c, graph, last_src)
358+
isnothing(c_id) || push!(st_child_ids, c_id)
359+
last_src = something(c_src, src)
360+
end
361+
362+
setchildren!(graph, st_id, st_child_ids)
363+
return (st_id, last_src)
364+
end

0 commit comments

Comments
 (0)