Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hashing fixup, equality support, and serialization support #452

Merged
merged 2 commits into from
Jul 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading