Skip to content

Commit 0b1aa97

Browse files
timholyc42f
andauthored
Split SyntaxNode into TreeNode & SyntaxData (#193)
Closes #192 Co-authored-by: c42f <[email protected]>
1 parent 9fa5661 commit 0b1aa97

File tree

2 files changed

+55
-24
lines changed

2 files changed

+55
-24
lines changed

src/syntax_tree.jl

Lines changed: 48 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,43 @@
11
#-------------------------------------------------------------------------------
22
# AST interface, built on top of raw tree
33

4-
"""
5-
Design options:
6-
* rust-analyzer treats their version of an untyped syntax node as a cursor into
7-
the green tree. They deallocate aggressively.
8-
"""
9-
mutable struct SyntaxNode
4+
abstract type AbstractSyntaxData end
5+
6+
mutable struct TreeNode{NodeData} # ? prevent others from using this with NodeData <: AbstractSyntaxData?
7+
parent::Union{Nothing,TreeNode{NodeData}}
8+
children::Union{Nothing,Vector{TreeNode{NodeData}}}
9+
data::Union{Nothing,NodeData}
10+
end
11+
12+
# Implement "pass-through" semantics for field access: access fields of `data`
13+
# as if they were part of `TreeNode`
14+
function Base.getproperty(node::TreeNode, name::Symbol)
15+
name === :parent && return getfield(node, :parent)
16+
name === :children && return getfield(node, :children)
17+
d = getfield(node, :data)
18+
name === :data && return d
19+
return getproperty(d, name)
20+
end
21+
22+
function Base.setproperty!(node::TreeNode, name::Symbol, x)
23+
name === :parent && return setfield!(node, :parent, x)
24+
name === :children && return setfield!(node, :children, x)
25+
name === :data && return setfield!(node, :data, x)
26+
d = getfield(node, :data)
27+
return setfield!(d, name, x)
28+
end
29+
30+
const AbstractSyntaxNode = TreeNode{<:AbstractSyntaxData}
31+
32+
struct SyntaxData <: AbstractSyntaxData
1033
source::SourceFile
1134
raw::GreenNode{SyntaxHead}
1235
position::Int
13-
parent::Union{Nothing,SyntaxNode}
14-
is_leaf::Bool
1536
val::Any
1637
end
1738

39+
const SyntaxNode = TreeNode{SyntaxData}
40+
1841
# Value of an error node with no children
1942
struct ErrorVal
2043
end
@@ -106,7 +129,7 @@ function SyntaxNode(source::SourceFile, raw::GreenNode{SyntaxHead}, position::In
106129
@debug "Leaf node of kind $k unknown to SyntaxNode"
107130
ErrorVal()
108131
end
109-
return SyntaxNode(source, raw, position, nothing, true, val)
132+
return SyntaxNode(nothing, nothing, SyntaxData(source, raw, position, val))
110133
else
111134
cs = SyntaxNode[]
112135
pos = position
@@ -117,30 +140,31 @@ function SyntaxNode(source::SourceFile, raw::GreenNode{SyntaxHead}, position::In
117140
end
118141
pos += rawchild.span
119142
end
120-
node = SyntaxNode(source, raw, position, nothing, false, cs)
143+
node = SyntaxNode(nothing, cs, SyntaxData(source, raw, position, nothing))
121144
for c in cs
122145
c.parent = node
123146
end
124147
return node
125148
end
126149
end
127150

128-
head(node::SyntaxNode) = head(node.raw)
151+
haschildren(node::TreeNode) = node.children !== nothing
152+
children(node::TreeNode) = (c = node.children; return c === nothing ? () : c)
153+
129154

130-
haschildren(node::SyntaxNode) = !node.is_leaf
131-
children(node::SyntaxNode) = haschildren(node) ? node.val::Vector{SyntaxNode} : ()
155+
head(node::SyntaxNode) = head(node.raw)
132156

133157
span(node::SyntaxNode) = span(node.raw)
134158

135-
first_byte(node::SyntaxNode) = node.position
136-
last_byte(node::SyntaxNode) = node.position + span(node) - 1
159+
first_byte(node::AbstractSyntaxNode) = node.position
160+
last_byte(node::AbstractSyntaxNode) = node.position + span(node) - 1
137161

138162
"""
139163
sourcetext(node)
140164
141165
Get the full source text of a node.
142166
"""
143-
function sourcetext(node::SyntaxNode)
167+
function sourcetext(node::AbstractSyntaxNode)
144168
val_range = (node.position-1) .+ (1:span(node))
145169
view(node.source, val_range)
146170
end
@@ -150,7 +174,7 @@ function interpolate_literal(node::SyntaxNode, val)
150174
SyntaxNode(node.source, node.raw, node.position, node.parent, true, val)
151175
end
152176

153-
function _show_syntax_node(io, current_filename, node::SyntaxNode, indent)
177+
function _show_syntax_node(io, current_filename, node::AbstractSyntaxNode, indent)
154178
fname = node.source.filename
155179
line, col = source_location(node.source, node.position)
156180
posstr = "$(lpad(line, 4)):$(rpad(col,3))$(lpad(first_byte(node),6)):$(rpad(last_byte(node),6))"
@@ -173,7 +197,7 @@ function _show_syntax_node(io, current_filename, node::SyntaxNode, indent)
173197
end
174198
end
175199

176-
function _show_syntax_node_sexpr(io, node::SyntaxNode)
200+
function _show_syntax_node_sexpr(io, node::AbstractSyntaxNode)
177201
if !haschildren(node)
178202
if is_error(node)
179203
print(io, "(", untokenize(head(node)), ")")
@@ -193,24 +217,24 @@ function _show_syntax_node_sexpr(io, node::SyntaxNode)
193217
end
194218
end
195219

196-
function Base.show(io::IO, ::MIME"text/plain", node::SyntaxNode)
220+
function Base.show(io::IO, ::MIME"text/plain", node::AbstractSyntaxNode)
197221
println(io, "line:col│ byte_range │ tree │ file_name")
198222
_show_syntax_node(io, Ref{Union{Nothing,String}}(nothing), node, "")
199223
end
200224

201-
function Base.show(io::IO, ::MIME"text/x.sexpression", node::SyntaxNode)
225+
function Base.show(io::IO, ::MIME"text/x.sexpression", node::AbstractSyntaxNode)
202226
_show_syntax_node_sexpr(io, node)
203227
end
204228

205-
function Base.show(io::IO, node::SyntaxNode)
229+
function Base.show(io::IO, node::AbstractSyntaxNode)
206230
_show_syntax_node_sexpr(io, node)
207231
end
208232

209-
function Base.push!(node::SyntaxNode, child::SyntaxNode)
233+
function Base.push!(node::SN, child::SN) where SN<:AbstractSyntaxNode
210234
if !haschildren(node)
211235
error("Cannot add children")
212236
end
213-
args = node.val::Vector{SyntaxNode}
237+
args = children(node)
214238
push!(args, child)
215239
end
216240

@@ -239,7 +263,7 @@ end
239263

240264
function setchild!(node::SyntaxNode, path, x)
241265
n1 = child(node, path[1:end-1]...)
242-
n1.val[path[end]] = x
266+
n1.children[path[end]] = x
243267
end
244268

245269
# We can overload multidimensional Base.getindex / Base.setindex! for node

test/syntax_tree.jl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,13 @@
2727
@test sprint(JuliaSyntax.highlight, tt, t, 1, 3) == "a*\e[48;2;40;40;70mb\e[0;0m + c"
2828
@test sprint(JuliaSyntax.highlight, tt, t.raw, 5) == "a*b + \e[48;2;40;40;70mc\e[0;0m"
2929

30+
# Pass-through field access
31+
node = child(t, 1, 1)
32+
@test node.val === :a
33+
# The specific error text has evolved over Julia versions. Check that it involves `SyntaxData` and immutability
34+
e = try node.val = :q catch e e end
35+
@test occursin("immutable", e.msg) && occursin("SyntaxData", e.msg)
36+
3037
node = parse(SyntaxNode, "f()")
3138
push!(node, parse(SyntaxNode, "x"))
3239
@test length(children(node)) == 2

0 commit comments

Comments
 (0)