Skip to content

Commit

Permalink
Hashing fixup, equality support, and serialization support (#452)
Browse files Browse the repository at this point in the history
  • Loading branch information
LilithHafner committed Jul 6, 2024
1 parent f425045 commit 8a123a1
Show file tree
Hide file tree
Showing 7 changed files with 64 additions and 2 deletions.
4 changes: 3 additions & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ authors = ["Claire Foster <aka.c42f@gmail.com> and contributors"]
version = "0.4.6"

[compat]
Serialization = "1.0"
julia = "1.0"

[deps]

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

[targets]
test = ["Test", "Logging"]
test = ["Test", "Serialization", "Logging"]
1 change: 1 addition & 0 deletions src/green_tree.jl
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ head(node::GreenNode) = node.head

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

Base.hash(node::GreenNode, h::UInt) = hash((node.head, node.span, node.args), h)
function Base.:(==)(n1::GreenNode, n2::GreenNode)
n1.head == n2.head && n1.span == n2.span && n1.args == n2.args
end
Expand Down
14 changes: 13 additions & 1 deletion src/kinds.jl
Original file line number Diff line number Diff line change
Expand Up @@ -922,7 +922,7 @@ const _kind_names =

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

# Save the string representation rather than the bit pattern so that kinds
# can be serialized and deserialized across different JuliaSyntax versions.
function Base.write(io::IO, k::Kind)
str = convert(String, k)
write(io, UInt8(length(str))) + write(io, str)
end
function Base.read(io::IO, ::Type{Kind})
len = read(io, UInt8)
str = String(read(io, len))
convert(Kind, str)
end

#-------------------------------------------------------------------------------

"""
Expand Down
6 changes: 6 additions & 0 deletions src/source_files.jl
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ struct SourceFile
line_starts::Vector{Int}
end

Base.hash(s::SourceFile, h::UInt) = hash((s.code, s.byte_offset, s.filename, s.first_line, s.line_starts), h)
function Base.:(==)(a::SourceFile, b::SourceFile)
a.code == b.code && a.byte_offset == b.byte_offset && a.filename == b.filename &&
a.first_line == b.first_line && a.line_starts == b.line_starts
end

function SourceFile(code::AbstractString; filename=nothing, first_line=1,
first_index=1)
line_starts = Int[1]
Expand Down
11 changes: 11 additions & 0 deletions src/syntax_tree.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ mutable struct TreeNode{NodeData} # ? prevent others from using this with Node
end
end

# Exclude parent from hash and equality checks. This means that subtrees can compare equal.
Base.hash(node::TreeNode, h::UInt) = hash((node.children, node.data), h)
function Base.:(==)(a::TreeNode{T}, b::TreeNode{T}) where T
a.children == b.children && a.data == b.data
end

# Implement "pass-through" semantics for field access: access fields of `data`
# as if they were part of `TreeNode`
function Base.getproperty(node::TreeNode, name::Symbol)
Expand Down Expand Up @@ -44,6 +50,11 @@ struct SyntaxData <: AbstractSyntaxData
val::Any
end

Base.hash(data::SyntaxData, h::UInt) = hash((data.source, data.raw, data.position, data.val), h)
function Base.:(==)(a::SyntaxData, b::SyntaxData)
a.source == b.source && a.raw == b.raw && a.position == b.position && a.val == b.val
end

"""
SyntaxNode(source::SourceFile, raw::GreenNode{SyntaxHead};
keep_parens=false, position::Integer=1)
Expand Down
1 change: 1 addition & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,4 @@ if VERSION >= v"1.6"
include("parse_packages.jl")
end

include("serialization.jl")
29 changes: 29 additions & 0 deletions test/serialization.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using Serialization

@testset "Equality $T" for T in [Expr, SyntaxNode, JuliaSyntax.GreenNode]
x = JuliaSyntax.parsestmt(T, "f(x) = x + 2")
y = JuliaSyntax.parsestmt(T, "f(x) = x + 2")
z = JuliaSyntax.parsestmt(T, "f(x) = 2 + x")
@test x == y
@test x != z
@test y != z
end

@testset "Hashing $T" for T in [Expr, SyntaxNode, JuliaSyntax.GreenNode]
x = hash(JuliaSyntax.parsestmt(T, "f(x) = x + 2"))::UInt
y = hash(JuliaSyntax.parsestmt(T, "f(x) = x + 2"))::UInt
z = hash(JuliaSyntax.parsestmt(T, "f(x) = 2 + x"))::UInt
@test x == y # Correctness
@test x != z # Collision
@test y != z # Collision
end

@testset "Serialization $T" for T in [Expr, SyntaxNode, JuliaSyntax.GreenNode]
x = JuliaSyntax.parsestmt(T, "f(x) = x + 2")
f = tempname()
open(f, "w") do io
serialize(io, x)
end
y = open(deserialize, f, "r")
@test x == y
end

0 comments on commit 8a123a1

Please sign in to comment.