Skip to content

Commit 8a123a1

Browse files
authored
Hashing fixup, equality support, and serialization support (#452)
1 parent f425045 commit 8a123a1

File tree

7 files changed

+64
-2
lines changed

7 files changed

+64
-2
lines changed

Project.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ authors = ["Claire Foster <[email protected]> and contributors"]
44
version = "0.4.6"
55

66
[compat]
7+
Serialization = "1.0"
78
julia = "1.0"
89

910
[deps]
1011

1112
[extras]
1213
Logging = "56ddb016-857b-54e1-b83d-db4d58db5568"
14+
Serialization = "9e88b42a-f829-5b0c-bbe9-9e923198166b"
1315
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
1416

1517
[targets]
16-
test = ["Test", "Logging"]
18+
test = ["Test", "Serialization", "Logging"]

src/green_tree.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ head(node::GreenNode) = node.head
3838

3939
Base.summary(node::GreenNode) = summary(node.head)
4040

41+
Base.hash(node::GreenNode, h::UInt) = hash((node.head, node.span, node.args), h)
4142
function Base.:(==)(n1::GreenNode, n2::GreenNode)
4243
n1.head == n2.head && n1.span == n2.span && n1.args == n2.args
4344
end

src/kinds.jl

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -922,7 +922,7 @@ const _kind_names =
922922

923923
"""
924924
K"name"
925-
Kind(namestr)
925+
Kind(id)
926926
927927
`Kind` is a type tag for specifying the type of tokens and interior nodes of
928928
a syntax tree. Abstractly, this tag is used to define our own *sum types* for
@@ -999,6 +999,18 @@ function Base.show(io::IO, k::Kind)
999999
print(io, "K\"$(convert(String, k))\"")
10001000
end
10011001

1002+
# Save the string representation rather than the bit pattern so that kinds
1003+
# can be serialized and deserialized across different JuliaSyntax versions.
1004+
function Base.write(io::IO, k::Kind)
1005+
str = convert(String, k)
1006+
write(io, UInt8(length(str))) + write(io, str)
1007+
end
1008+
function Base.read(io::IO, ::Type{Kind})
1009+
len = read(io, UInt8)
1010+
str = String(read(io, len))
1011+
convert(Kind, str)
1012+
end
1013+
10021014
#-------------------------------------------------------------------------------
10031015

10041016
"""

src/source_files.jl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ struct SourceFile
2323
line_starts::Vector{Int}
2424
end
2525

26+
Base.hash(s::SourceFile, h::UInt) = hash((s.code, s.byte_offset, s.filename, s.first_line, s.line_starts), h)
27+
function Base.:(==)(a::SourceFile, b::SourceFile)
28+
a.code == b.code && a.byte_offset == b.byte_offset && a.filename == b.filename &&
29+
a.first_line == b.first_line && a.line_starts == b.line_starts
30+
end
31+
2632
function SourceFile(code::AbstractString; filename=nothing, first_line=1,
2733
first_index=1)
2834
line_starts = Int[1]

src/syntax_tree.jl

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ mutable struct TreeNode{NodeData} # ? prevent others from using this with Node
1717
end
1818
end
1919

20+
# Exclude parent from hash and equality checks. This means that subtrees can compare equal.
21+
Base.hash(node::TreeNode, h::UInt) = hash((node.children, node.data), h)
22+
function Base.:(==)(a::TreeNode{T}, b::TreeNode{T}) where T
23+
a.children == b.children && a.data == b.data
24+
end
25+
2026
# Implement "pass-through" semantics for field access: access fields of `data`
2127
# as if they were part of `TreeNode`
2228
function Base.getproperty(node::TreeNode, name::Symbol)
@@ -44,6 +50,11 @@ struct SyntaxData <: AbstractSyntaxData
4450
val::Any
4551
end
4652

53+
Base.hash(data::SyntaxData, h::UInt) = hash((data.source, data.raw, data.position, data.val), h)
54+
function Base.:(==)(a::SyntaxData, b::SyntaxData)
55+
a.source == b.source && a.raw == b.raw && a.position == b.position && a.val == b.val
56+
end
57+
4758
"""
4859
SyntaxNode(source::SourceFile, raw::GreenNode{SyntaxHead};
4960
keep_parens=false, position::Integer=1)

test/runtests.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,4 @@ if VERSION >= v"1.6"
3737
include("parse_packages.jl")
3838
end
3939

40+
include("serialization.jl")

test/serialization.jl

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using Serialization
2+
3+
@testset "Equality $T" for T in [Expr, SyntaxNode, JuliaSyntax.GreenNode]
4+
x = JuliaSyntax.parsestmt(T, "f(x) = x + 2")
5+
y = JuliaSyntax.parsestmt(T, "f(x) = x + 2")
6+
z = JuliaSyntax.parsestmt(T, "f(x) = 2 + x")
7+
@test x == y
8+
@test x != z
9+
@test y != z
10+
end
11+
12+
@testset "Hashing $T" for T in [Expr, SyntaxNode, JuliaSyntax.GreenNode]
13+
x = hash(JuliaSyntax.parsestmt(T, "f(x) = x + 2"))::UInt
14+
y = hash(JuliaSyntax.parsestmt(T, "f(x) = x + 2"))::UInt
15+
z = hash(JuliaSyntax.parsestmt(T, "f(x) = 2 + x"))::UInt
16+
@test x == y # Correctness
17+
@test x != z # Collision
18+
@test y != z # Collision
19+
end
20+
21+
@testset "Serialization $T" for T in [Expr, SyntaxNode, JuliaSyntax.GreenNode]
22+
x = JuliaSyntax.parsestmt(T, "f(x) = x + 2")
23+
f = tempname()
24+
open(f, "w") do io
25+
serialize(io, x)
26+
end
27+
y = open(deserialize, f, "r")
28+
@test x == y
29+
end

0 commit comments

Comments
 (0)