From 5f0b5d988e75f7b10aa389eb6d79270c4bfc20c8 Mon Sep 17 00:00:00 2001
From: a
Date: Mon, 4 Dec 2023 18:38:05 +0100
Subject: [PATCH 01/13] change interface
---
src/TermInterface.jl | 50 ++++++++++++++++++++++++++++++++++----------
src/expr.jl | 45 ++++++++++++++++++++++++++-------------
src/utils.jl | 16 --------------
3 files changed, 69 insertions(+), 42 deletions(-)
delete mode 100644 src/utils.jl
diff --git a/src/TermInterface.jl b/src/TermInterface.jl
index 78b57ca..a2f7ee6 100644
--- a/src/TermInterface.jl
+++ b/src/TermInterface.jl
@@ -45,12 +45,29 @@ and pattern matching features.
function exprhead end
export exprhead
+"""
+ head(x)
+
+If `x` is a term as defined by `istree(x)`, `head(x)` returns the
+head of the term if `x`. The `head` type has to be provided by the package.
+"""
+function head end
+export head
+
+"""
+ tail(x)
+
+Get the arguments of `x`, must be defined if `istree(x)` is `true`.
+"""
+function tail end
+export tail
+
"""
operation(x)
If `x` is a term as defined by `istree(x)`, `operation(x)` returns the
-head of the term if `x` represents a function call, for example, the head
+operation of the term if `x` represents a function call, for example, the head
is the function being called.
"""
function operation end
@@ -108,21 +125,32 @@ end
"""
- similarterm(x, head, args, symtype=nothing; metadata=nothing, exprhead=:call)
+ maketerm(head::H, tail; type=Any, metadata=nothing)
+Has to be implemented by the provider of H.
Returns a term that is in the same closure of types as `typeof(x)`,
-with `head` as the head and `args` as the arguments, `type` as the symtype
-and `metadata` as the metadata. By default this will execute `head(args...)`.
-`x` parameter can also be a `Type`. The `exprhead` keyword argument is useful
-when manipulating `Expr`s.
+with `head` as the head and `tail` as the arguments, `type` as the symtype
+and `metadata` as the metadata.
"""
-function similarterm(x, head, args, symtype = nothing; metadata = nothing, exprhead = nothing)
- head(args...)
-end
+function maketerm(head, tail; type=Any, metadata=nothing) end
+export maketerm
+
+"""
+ is_operation(f)
+
+Returns a single argument anonymous function predicate, that returns `true` if and only if
+the argument to the predicate satisfies `istree` and `operation(x) == f`
+"""
+is_operation(f) = @nospecialize(x) -> istree(x) && (operation(x) == f)
+export is_operation
-export similarterm
-include("utils.jl")
+"""
+ node_count(t)
+Count the nodes in a symbolic expression tree satisfying `istree` and `arguments`.
+"""
+node_count(t) = istree(t) ? reduce(+, node_count(x) for x in arguments(t), init = 0) + 1 : 1
+export node_count
include("expr.jl")
diff --git a/src/expr.jl b/src/expr.jl
index 4bc6788..39f8fd3 100644
--- a/src/expr.jl
+++ b/src/expr.jl
@@ -1,26 +1,41 @@
# This file contains default definitions for TermInterface methods on Julia
# Builtin Expr type.
-istree(x::Expr) = true
-exprhead(e::Expr) = e.head
+struct ExprHead
+ head
+end
+export ExprHead
-operation(e::Expr) = expr_operation(e, Val{exprhead(e)}())
-arguments(e::Expr) = expr_arguments(e, Val{exprhead(e)}())
+istree(x::Expr) = true
+head(e::Expr) = ExprHead(head)
+tail(e::Expr) = e.args
# See https://docs.julialang.org/en/v1/devdocs/ast/
-expr_operation(e::Expr, ::Union{Val{:call},Val{:macrocall}}) = e.args[1]
-expr_operation(e::Expr, ::Union{Val{:ref}}) = getindex
-expr_operation(e::Expr, ::Val{T}) where {T} = T
-
-expr_arguments(e::Expr, ::Union{Val{:call},Val{:macrocall}}) = e.args[2:end]
-expr_arguments(e::Expr, _) = e.args
+function operation(e::Expr)
+ h = head(e)
+ hh = h.head
+ if hh in (:call, :macrocall)
+ e.args[1]
+ elseif hh == :ref
+ getindex
+ else
+ hh
+ end
+end
+function arguments(e::Expr)
+ h = head(e)
+ hh = h.head
+ if hh in (:call, :macrocall)
+ e.args[2:end]
+ else
+ e.args
+ end
+ expr_arguments(e, Val{exprhead(e)}())
+end
-function similarterm(x::Expr, head, args, symtype = nothing; metadata = nothing, exprhead = exprhead(x))
+function similarterm(x::Expr, head, args, symtype=nothing; metadata=nothing, exprhead=exprhead(x))
expr_similarterm(head, args, Val{exprhead}())
end
-
-expr_similarterm(head, args, ::Val{:call}) = Expr(:call, head, args...)
-expr_similarterm(head, args, ::Val{:macrocall}) = Expr(:macrocall, head, args...) # discard linenumbernodes?
-expr_similarterm(head, args, ::Val{eh}) where {eh} = Expr(eh, args...)
+maketerm(head::ExprHead, tail; type=Any, metadata=nothing) = Expr(head.head, tail...)
\ No newline at end of file
diff --git a/src/utils.jl b/src/utils.jl
deleted file mode 100644
index b9c732f..0000000
--- a/src/utils.jl
+++ /dev/null
@@ -1,16 +0,0 @@
-"""
- is_operation(f)
-
-Returns a single argument anonymous function predicate, that returns `true` if and only if
-the argument to the predicate satisfies `istree` and `operation(x) == f`
-"""
-is_operation(f) = @nospecialize(x) -> istree(x) && (operation(x) == f)
-export is_operation
-
-
-"""
- node_count(t)
-Count the nodes in a symbolic expression tree satisfying `istree` and `arguments`.
-"""
-node_count(t) = istree(t) ? reduce(+, node_count(x) for x in arguments(t), init = 0) + 1 : 1
-export node_count
\ No newline at end of file
From 2052ebdfb0f91ce7aef0db24c5510c8c14695346 Mon Sep 17 00:00:00 2001
From: a
Date: Mon, 4 Dec 2023 18:47:40 +0100
Subject: [PATCH 02/13] add tests
---
src/expr.jl | 7 +------
test/runtests.jl | 16 +++++++++++-----
2 files changed, 12 insertions(+), 11 deletions(-)
diff --git a/src/expr.jl b/src/expr.jl
index 39f8fd3..1a4b6f8 100644
--- a/src/expr.jl
+++ b/src/expr.jl
@@ -7,7 +7,7 @@ end
export ExprHead
istree(x::Expr) = true
-head(e::Expr) = ExprHead(head)
+head(e::Expr) = ExprHead(e.head)
tail(e::Expr) = e.args
# See https://docs.julialang.org/en/v1/devdocs/ast/
@@ -31,11 +31,6 @@ function arguments(e::Expr)
else
e.args
end
- expr_arguments(e, Val{exprhead(e)}())
-end
-
-function similarterm(x::Expr, head, args, symtype=nothing; metadata=nothing, exprhead=exprhead(x))
- expr_similarterm(head, args, Val{exprhead}())
end
maketerm(head::ExprHead, tail; type=Any, metadata=nothing) = Expr(head.head, tail...)
\ No newline at end of file
diff --git a/test/runtests.jl b/test/runtests.jl
index c344173..56b202b 100644
--- a/test/runtests.jl
+++ b/test/runtests.jl
@@ -3,15 +3,21 @@ using Test
@testset "Expr" begin
ex = :(f(a, b))
+ @test head(ex) == ExprHead(:call)
+ @test tail(ex) == [:f, :a, :b]
@test operation(ex) == :f
@test arguments(ex) == [:a, :b]
- @test exprhead(ex) == :call
- @test ex == similarterm(ex, :f, [:a, :b])
+ @test ex == maketerm(ExprHead(:call), [:f, :a, :b])
ex = :(arr[i, j])
+ @test head(ex) == ExprHead(:ref)
@test operation(ex) == getindex
@test arguments(ex) == [:arr, :i, :j]
- @test exprhead(ex) == :ref
- @test ex == similarterm(ex, :ref, [:arr, :i, :j]; exprhead = :ref)
- @test ex == similarterm(ex, :ref, [:arr, :i, :j])
+ @test ex == maketerm(ExprHead(:ref), [:arr, :i, :j])
+
+ ex = Expr(:block, :a, :b, :c)
+ @test head(ex) == ExprHead(:block)
+ @test operation(ex) == :block
+ @test tail(ex) == arguments(ex) == [:a, :b, :c]
+ @test ex == maketerm(ExprHead(:block), [:a, :b, :c])
end
From 3cb75688cbc13f8ab8c8b8bf3e4c5f1dae605e2e Mon Sep 17 00:00:00 2001
From: a
Date: Tue, 5 Dec 2023 12:17:45 +0100
Subject: [PATCH 03/13] head_symbol
---
src/TermInterface.jl | 16 ++++++++++++++--
src/expr.jl | 10 +++++++++-
2 files changed, 23 insertions(+), 3 deletions(-)
diff --git a/src/TermInterface.jl b/src/TermInterface.jl
index a2f7ee6..580ef95 100644
--- a/src/TermInterface.jl
+++ b/src/TermInterface.jl
@@ -48,12 +48,24 @@ export exprhead
"""
head(x)
-If `x` is a term as defined by `istree(x)`, `head(x)` returns the
-head of the term if `x`. The `head` type has to be provided by the package.
+If `x` is a term as defined by `istree(x)`, `head(x)` returns the head of the
+term if `x`. The `head` type has to be provided by the package.
"""
function head end
export head
+"""
+ head_symbol(x::HeadType)
+
+If `x` is a head object, `head_symbol(T, x)` returns a `Symbol` object that
+corresponds to `y.head` if `y` was the representation of the corresponding term
+as a Julia Expression. This is useful to define interoperability between
+symbolic term types defined in different packages and should be used when
+calling `maketerm`.
+"""
+function head_symbol end
+export head_symbol
+
"""
tail(x)
diff --git a/src/expr.jl b/src/expr.jl
index 1a4b6f8..8d69ba1 100644
--- a/src/expr.jl
+++ b/src/expr.jl
@@ -6,6 +6,8 @@ struct ExprHead
end
export ExprHead
+head_symbol(eh::ExprHead) = eh.head
+
istree(x::Expr) = true
head(e::Expr) = ExprHead(e.head)
tail(e::Expr) = e.args
@@ -33,4 +35,10 @@ function arguments(e::Expr)
end
end
-maketerm(head::ExprHead, tail; type=Any, metadata=nothing) = Expr(head.head, tail...)
\ No newline at end of file
+function maketerm(head::ExprHead, tail; type=Any, metadata=nothing)
+ if !isempty(tail) && first(tail) isa Union{Function,DataType}
+ Expr(head.head, nameof(first(tail)), @view(tail[2:end])...)
+ else
+ Expr(head.head, tail...)
+ end
+end
From f2356866761f3e3ee921e88b5141531424802926 Mon Sep 17 00:00:00 2001
From: a
Date: Tue, 5 Dec 2023 12:24:56 +0100
Subject: [PATCH 04/13] add tests
---
src/TermInterface.jl | 36 ++++++++++++++++++++++++++++++++++++
test/runtests.jl | 42 ++++++++++++++++++++++++++++++++++++++++--
2 files changed, 76 insertions(+), 2 deletions(-)
diff --git a/src/TermInterface.jl b/src/TermInterface.jl
index 580ef95..006c3d1 100644
--- a/src/TermInterface.jl
+++ b/src/TermInterface.jl
@@ -166,5 +166,41 @@ export node_count
include("expr.jl")
+"""
+Take a struct definition and automatically define `TermInterface` methods.
+This will automatically define a head type. If the struct is called `Foo`, then
+the head type will be called `FooHead`. The `head_symbol` of such head types
+will default to `:call`.
+"""
+macro matchable(expr)
+ @assert expr.head == :struct
+ name = expr.args[2]
+ if name isa Expr
+ name.head === :(<:) && (name = name.args[1])
+ name isa Expr && name.head === :curly && (name = name.args[1])
+ end
+ fields = filter(x -> x isa Symbol || (x isa Expr && x.head == :(==)), expr.args[3].args)
+ get_name(s::Symbol) = s
+ get_name(e::Expr) = (@assert(e.head == :(::)); e.args[1])
+ fields = map(get_name, fields)
+ head_name = Symbol(name, :Head)
+ quote
+ $expr
+ struct $head_name
+ head
+ end
+ TermInterface.head_symbol(x::$head_name) = x.head
+ # TODO default to call?
+ TermInterface.head(::$name) = $head_name(:call)
+ TermInterface.istree(::$name) = true
+ TermInterface.operation(::$name) = $name
+ TermInterface.arguments(x::$name) = getfield.((x,), ($(QuoteNode.(fields)...),))
+ TermInterface.tail(x::$name) = [operation(x); arguments(x)...]
+ TermInterface.arity(x::$name) = $(length(fields))
+ Base.length(x::$name) = $(length(fields) + 1)
+ end |> esc
+end
+export @matchable
+
end # module
diff --git a/test/runtests.jl b/test/runtests.jl
index 56b202b..fb5e799 100644
--- a/test/runtests.jl
+++ b/test/runtests.jl
@@ -1,5 +1,4 @@
-using TermInterface
-using Test
+using TermInterface, Test
@testset "Expr" begin
ex = :(f(a, b))
@@ -21,3 +20,42 @@ using Test
@test tail(ex) == arguments(ex) == [:a, :b, :c]
@test ex == maketerm(ExprHead(:block), [:a, :b, :c])
end
+
+@testset "Custom Struct" begin
+ struct Foo
+ args
+ Foo(args...) = new(args)
+ end
+ struct FooHead
+ head
+ end
+ TermInterface.head(::Foo) = FooHead(:call)
+ TermInterface.head_symbol(q::FooHead) = q.head
+ TermInterface.operation(::Foo) = Foo
+ TermInterface.istree(::Foo) = true
+ TermInterface.arguments(x::Foo) = [x.args...]
+ TermInterface.tail(x::Foo) = [operation(x); x.args...]
+
+ t = Foo(1, 2)
+ @test head(t) == FooHead(:call)
+ @test head_symbol(head(t)) == :call
+ @test operation(t) == Foo
+ @test istree(t) == true
+ @test arguments(t) == [1, 2]
+ @test tail(t) == [Foo, 1, 2]
+end
+
+@testset "Automatically Generated Methods" begin
+ @matchable struct Bar
+ a
+ b
+ end
+
+ t = Bar(1, 2)
+ @test head(t) == BarHead(:call)
+ @test head_symbol(head(t)) == :call
+ @test operation(t) == Bar
+ @test istree(t) == true
+ @test arguments(t) == (1, 2)
+ @test tail(t) == [Bar, 1, 2]
+end
\ No newline at end of file
From bc1ec14fd9160caa2c3a0ad1909b30ea9695b448 Mon Sep 17 00:00:00 2001
From: a
Date: Tue, 5 Dec 2023 14:33:15 +0100
Subject: [PATCH 05/13] adjust matchable
---
src/TermInterface.jl | 14 +++++++++-----
test/runtests.jl | 2 +-
2 files changed, 10 insertions(+), 6 deletions(-)
diff --git a/src/TermInterface.jl b/src/TermInterface.jl
index 006c3d1..6f7582e 100644
--- a/src/TermInterface.jl
+++ b/src/TermInterface.jl
@@ -167,23 +167,27 @@ export node_count
include("expr.jl")
"""
-Take a struct definition and automatically define `TermInterface` methods.
-This will automatically define a head type. If the struct is called `Foo`, then
+ @matchable struct Foo fields... end [HeadType]
+
+Take a struct definition and automatically define `TermInterface` methods. This
+will automatically define a head type. If `HeadType` is given then it will be
+used as `head(::Foo)`. If it is omitted, and the struct is called `Foo`, then
the head type will be called `FooHead`. The `head_symbol` of such head types
will default to `:call`.
"""
-macro matchable(expr)
+macro matchable(expr, head_name=nothing)
@assert expr.head == :struct
name = expr.args[2]
if name isa Expr
name.head === :(<:) && (name = name.args[1])
name isa Expr && name.head === :curly && (name = name.args[1])
end
- fields = filter(x -> x isa Symbol || (x isa Expr && x.head == :(==)), expr.args[3].args)
+ fields = filter(x -> x isa Symbol || (x isa Expr && x.head == :(::)), expr.args[3].args)
get_name(s::Symbol) = s
get_name(e::Expr) = (@assert(e.head == :(::)); e.args[1])
fields = map(get_name, fields)
- head_name = Symbol(name, :Head)
+ head_name = isnothing(head_name) ? Symbol(name, :Head) : head_name
+
quote
$expr
struct $head_name
diff --git a/test/runtests.jl b/test/runtests.jl
index fb5e799..8190dde 100644
--- a/test/runtests.jl
+++ b/test/runtests.jl
@@ -48,7 +48,7 @@ end
@testset "Automatically Generated Methods" begin
@matchable struct Bar
a
- b
+ b::Int
end
t = Bar(1, 2)
From bcc4574060c376e67baef9466126002e9926c39d Mon Sep 17 00:00:00 2001
From: a
Date: Tue, 5 Dec 2023 15:19:15 +0100
Subject: [PATCH 06/13] adjust some of the tutorial
---
src/expr.jl | 2 --
1 file changed, 2 deletions(-)
diff --git a/src/expr.jl b/src/expr.jl
index 8d69ba1..81610dc 100644
--- a/src/expr.jl
+++ b/src/expr.jl
@@ -18,8 +18,6 @@ function operation(e::Expr)
hh = h.head
if hh in (:call, :macrocall)
e.args[1]
- elseif hh == :ref
- getindex
else
hh
end
From e2aef1a3bd76328936090fa4ec75a27584428ebf Mon Sep 17 00:00:00 2001
From: a
Date: Tue, 5 Dec 2023 15:43:06 +0100
Subject: [PATCH 07/13] adjust tests
---
test/runtests.jl | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/test/runtests.jl b/test/runtests.jl
index 8190dde..173e8ae 100644
--- a/test/runtests.jl
+++ b/test/runtests.jl
@@ -10,10 +10,19 @@ using TermInterface, Test
ex = :(arr[i, j])
@test head(ex) == ExprHead(:ref)
- @test operation(ex) == getindex
+ @test operation(ex) == :ref
@test arguments(ex) == [:arr, :i, :j]
@test ex == maketerm(ExprHead(:ref), [:arr, :i, :j])
+
+ ex = :(i, j)
+ @test head(ex) == ExprHead(:tuple)
+ @test operation(ex) == :tuple
+ @test arguments(ex) == [:i, :j]
+ @test tail(ex) == [:i, :j]
+ @test ex == maketerm(ExprHead(:tuple), [:i, :j])
+
+
ex = Expr(:block, :a, :b, :c)
@test head(ex) == ExprHead(:block)
@test operation(ex) == :block
From 47610e739890fa035d37b5afc125970d4bcbfabb Mon Sep 17 00:00:00 2001
From: a
Date: Tue, 5 Dec 2023 15:43:52 +0100
Subject: [PATCH 08/13] version bump
---
Project.toml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Project.toml b/Project.toml
index 2de42ff..a414a10 100644
--- a/Project.toml
+++ b/Project.toml
@@ -1,7 +1,7 @@
name = "TermInterface"
uuid = "8ea1fca8-c5ef-4a55-8b96-4e9afe9c9a3c"
authors = ["Shashi Gowda ", "Alessandro Cheli "]
-version = "0.3.3"
+version = "0.4"
[compat]
julia = "1"
From 85d0931ff36cec7a5dd4c02a56eef7f34941b8c8 Mon Sep 17 00:00:00 2001
From: a
Date: Tue, 5 Dec 2023 16:57:51 +0100
Subject: [PATCH 09/13] apply suggestions
---
src/TermInterface.jl | 14 +++++++-------
src/expr.jl | 10 +++++-----
test/runtests.jl | 12 ++++++------
3 files changed, 18 insertions(+), 18 deletions(-)
diff --git a/src/TermInterface.jl b/src/TermInterface.jl
index 6f7582e..f22ca5c 100644
--- a/src/TermInterface.jl
+++ b/src/TermInterface.jl
@@ -67,12 +67,12 @@ function head_symbol end
export head_symbol
"""
- tail(x)
+ children(x)
Get the arguments of `x`, must be defined if `istree(x)` is `true`.
"""
-function tail end
-export tail
+function children end
+export children
"""
@@ -137,14 +137,14 @@ end
"""
- maketerm(head::H, tail; type=Any, metadata=nothing)
+ maketerm(head::H, children; type=Any, metadata=nothing)
Has to be implemented by the provider of H.
Returns a term that is in the same closure of types as `typeof(x)`,
-with `head` as the head and `tail` as the arguments, `type` as the symtype
+with `head` as the head and `children` as the arguments, `type` as the symtype
and `metadata` as the metadata.
"""
-function maketerm(head, tail; type=Any, metadata=nothing) end
+function maketerm end
export maketerm
"""
@@ -199,7 +199,7 @@ macro matchable(expr, head_name=nothing)
TermInterface.istree(::$name) = true
TermInterface.operation(::$name) = $name
TermInterface.arguments(x::$name) = getfield.((x,), ($(QuoteNode.(fields)...),))
- TermInterface.tail(x::$name) = [operation(x); arguments(x)...]
+ TermInterface.children(x::$name) = [operation(x); arguments(x)...]
TermInterface.arity(x::$name) = $(length(fields))
Base.length(x::$name) = $(length(fields) + 1)
end |> esc
diff --git a/src/expr.jl b/src/expr.jl
index 81610dc..9a0fdd2 100644
--- a/src/expr.jl
+++ b/src/expr.jl
@@ -10,7 +10,7 @@ head_symbol(eh::ExprHead) = eh.head
istree(x::Expr) = true
head(e::Expr) = ExprHead(e.head)
-tail(e::Expr) = e.args
+children(e::Expr) = e.args
# See https://docs.julialang.org/en/v1/devdocs/ast/
function operation(e::Expr)
@@ -33,10 +33,10 @@ function arguments(e::Expr)
end
end
-function maketerm(head::ExprHead, tail; type=Any, metadata=nothing)
- if !isempty(tail) && first(tail) isa Union{Function,DataType}
- Expr(head.head, nameof(first(tail)), @view(tail[2:end])...)
+function maketerm(head::ExprHead, children; type=Any, metadata=nothing)
+ if !isempty(children) && first(children) isa Union{Function,DataType}
+ Expr(head.head, nameof(first(children)), @view(children[2:end])...)
else
- Expr(head.head, tail...)
+ Expr(head.head, children...)
end
end
diff --git a/test/runtests.jl b/test/runtests.jl
index 173e8ae..17c791c 100644
--- a/test/runtests.jl
+++ b/test/runtests.jl
@@ -3,7 +3,7 @@ using TermInterface, Test
@testset "Expr" begin
ex = :(f(a, b))
@test head(ex) == ExprHead(:call)
- @test tail(ex) == [:f, :a, :b]
+ @test children(ex) == [:f, :a, :b]
@test operation(ex) == :f
@test arguments(ex) == [:a, :b]
@test ex == maketerm(ExprHead(:call), [:f, :a, :b])
@@ -19,14 +19,14 @@ using TermInterface, Test
@test head(ex) == ExprHead(:tuple)
@test operation(ex) == :tuple
@test arguments(ex) == [:i, :j]
- @test tail(ex) == [:i, :j]
+ @test children(ex) == [:i, :j]
@test ex == maketerm(ExprHead(:tuple), [:i, :j])
ex = Expr(:block, :a, :b, :c)
@test head(ex) == ExprHead(:block)
@test operation(ex) == :block
- @test tail(ex) == arguments(ex) == [:a, :b, :c]
+ @test children(ex) == arguments(ex) == [:a, :b, :c]
@test ex == maketerm(ExprHead(:block), [:a, :b, :c])
end
@@ -43,7 +43,7 @@ end
TermInterface.operation(::Foo) = Foo
TermInterface.istree(::Foo) = true
TermInterface.arguments(x::Foo) = [x.args...]
- TermInterface.tail(x::Foo) = [operation(x); x.args...]
+ TermInterface.children(x::Foo) = [operation(x); x.args...]
t = Foo(1, 2)
@test head(t) == FooHead(:call)
@@ -51,7 +51,7 @@ end
@test operation(t) == Foo
@test istree(t) == true
@test arguments(t) == [1, 2]
- @test tail(t) == [Foo, 1, 2]
+ @test children(t) == [Foo, 1, 2]
end
@testset "Automatically Generated Methods" begin
@@ -66,5 +66,5 @@ end
@test operation(t) == Bar
@test istree(t) == true
@test arguments(t) == (1, 2)
- @test tail(t) == [Bar, 1, 2]
+ @test children(t) == [Bar, 1, 2]
end
\ No newline at end of file
From 62ff72aaab2c1e4f62eb7974a7962151e228562f Mon Sep 17 00:00:00 2001
From: a
Date: Sun, 14 Jan 2024 17:38:17 +0100
Subject: [PATCH 10/13] adjusted proposal
---
src/TermInterface.jl | 140 ++++++++++++++++---------------------------
src/expr.jl | 45 +++++---------
2 files changed, 68 insertions(+), 117 deletions(-)
diff --git a/src/TermInterface.jl b/src/TermInterface.jl
index f22ca5c..a192215 100644
--- a/src/TermInterface.jl
+++ b/src/TermInterface.jl
@@ -1,14 +1,36 @@
+"""
+This module defines a contains definitions for common functions that are useful
+for symbolic expression manipulation. Its purpose is to provide a shared
+interface between various symbolic programming Julia packages.
+
+This is currently borrowed from TermInterface.jl. If you want to use
+Metatheory.jl, please use this internal interface, as we are waiting that a
+redesign proposal of the interface package will reach consensus. When this
+happens, this module will be moved back into a separate package.
+
+See https://github.com/JuliaSymbolics/TermInterface.jl/pull/22
+"""
module TermInterface
"""
istree(x)
-Returns `true` if `x` is a term. If true, `operation`, `arguments`
-must also be defined for `x` appropriately.
+Returns `true` if `x` is a term. If true, `head`, `children` and
+`is_function_call` must also be defined for `x` appropriately.
"""
istree(x) = false
export istree
+"""
+ is_function_call(x)
+
+Returns true if a term abstractly represents a function call or function application.
+Must be defined if `istree(x)` is defined.
+Can be true only if `istree(x)` is true.
+"""
+function is_function_call end
+export is_function_call
+
"""
symtype(x)
@@ -22,6 +44,7 @@ function symtype(x)
end
export symtype
+
"""
issym(x)
@@ -31,75 +54,32 @@ on `x` and must return a Symbol.
issym(x) = false
export issym
-"""
- exprhead(x)
-
-If `x` is a term as defined by `istree(x)`, `exprhead(x)` must return a symbol,
-corresponding to the head of the `Expr` most similar to the term `x`.
-If `x` represents a function call, for example, the `exprhead` is `:call`.
-If `x` represents an indexing operation, such as `arr[i]`, then `exprhead` is `:ref`.
-Note that `exprhead` is different from `operation` and both functions should
-be defined correctly in order to let other packages provide code generation
-and pattern matching features.
-"""
-function exprhead end
-export exprhead
"""
head(x)
If `x` is a term as defined by `istree(x)`, `head(x)` returns the head of the
-term if `x`. The `head` type has to be provided by the package.
+term. If `x` represents a function call term like `f(a,b)`, the head
+is the function being called, `f`.
"""
function head end
export head
-"""
- head_symbol(x::HeadType)
-
-If `x` is a head object, `head_symbol(T, x)` returns a `Symbol` object that
-corresponds to `y.head` if `y` was the representation of the corresponding term
-as a Julia Expression. This is useful to define interoperability between
-symbolic term types defined in different packages and should be used when
-calling `maketerm`.
-"""
-function head_symbol end
-export head_symbol
"""
children(x)
-Get the arguments of `x`, must be defined if `istree(x)` is `true`.
+Get the children of a term `x`, must be defined if `istree(x)` is `true`.
"""
function children end
export children
-
-"""
- operation(x)
-
-If `x` is a term as defined by `istree(x)`, `operation(x)` returns the
-operation of the term if `x` represents a function call, for example, the head
-is the function being called.
-"""
-function operation end
-export operation
-
-"""
- arguments(x)
-
-Get the arguments of `x`, must be defined if `istree(x)` is `true`.
"""
-function arguments end
-export arguments
+ unsorted_children(x::T)
-
-"""
- unsorted_arguments(x::T)
-
-If x is a term satisfying `istree(x)` and your term type `T` orovides
-and optimized implementation for storing the arguments, this function can
-be used to retrieve the arguments when the order of arguments does not matter
+If x is a term satisfying `istree(x)` and your term type `T` provides
+and optimized implementation for storing the children, this function can
+be used to retrieve the children when the order of arguments does not matter
but the speed of the operation does.
"""
unsorted_arguments(x) = arguments(x)
@@ -109,10 +89,10 @@ export unsorted_arguments
"""
arity(x)
-Returns the number of arguments of `x`. Implicitly defined
-if `arguments(x)` is defined.
+Returns the number of children of `x`. Implicitly defined
+if `children(x)` is defined.
"""
-arity(x) = length(arguments(x))
+arity(x)::Int = length(children(x))
export arity
@@ -131,51 +111,40 @@ export metadata
Returns a new term which has the structure of `x` but also has
the metadata `md` attached to it.
"""
-function metadata(x, data)
- error("Setting metadata on $x is not possible")
-end
+function metadata(x, data) end
"""
- maketerm(head::H, children; type=Any, metadata=nothing)
+ maketerm(T::Type, head, children; is_call = true, type=Any, metadata=nothing)
-Has to be implemented by the provider of H.
-Returns a term that is in the same closure of types as `typeof(x)`,
+Has to be implemented by the provider of the expression type T.
+Returns a term that is in the same closure of types as `T`,
with `head` as the head and `children` as the arguments, `type` as the symtype
and `metadata` as the metadata.
+
+`is_call` is used to determine if the constructed term represents a function
+call. If `is_call = true`, then it must construct a term `x` such that
+`is_function_call(x) = true`, and vice-versa for `is_call = false`.
"""
function maketerm end
export maketerm
-"""
- is_operation(f)
-
-Returns a single argument anonymous function predicate, that returns `true` if and only if
-the argument to the predicate satisfies `istree` and `operation(x) == f`
-"""
-is_operation(f) = @nospecialize(x) -> istree(x) && (operation(x) == f)
-export is_operation
"""
node_count(t)
Count the nodes in a symbolic expression tree satisfying `istree` and `arguments`.
"""
-node_count(t) = istree(t) ? reduce(+, node_count(x) for x in arguments(t), init = 0) + 1 : 1
+node_count(t) = istree(t) ? reduce(+, node_count(x) for x in children(t), init in 0) + 1 : 1
export node_count
-include("expr.jl")
-
"""
@matchable struct Foo fields... end [HeadType]
-Take a struct definition and automatically define `TermInterface` methods. This
-will automatically define a head type. If `HeadType` is given then it will be
-used as `head(::Foo)`. If it is omitted, and the struct is called `Foo`, then
-the head type will be called `FooHead`. The `head_symbol` of such head types
-will default to `:call`.
+Take a struct definition and automatically define `TermInterface` methods.
+`is_function_call` of such type will default to `true`.
"""
-macro matchable(expr, head_name=nothing)
+macro matchable(expr)
@assert expr.head == :struct
name = expr.args[2]
if name isa Expr
@@ -186,25 +155,20 @@ macro matchable(expr, head_name=nothing)
get_name(s::Symbol) = s
get_name(e::Expr) = (@assert(e.head == :(::)); e.args[1])
fields = map(get_name, fields)
- head_name = isnothing(head_name) ? Symbol(name, :Head) : head_name
quote
$expr
- struct $head_name
- head
- end
- TermInterface.head_symbol(x::$head_name) = x.head
- # TODO default to call?
- TermInterface.head(::$name) = $head_name(:call)
TermInterface.istree(::$name) = true
- TermInterface.operation(::$name) = $name
- TermInterface.arguments(x::$name) = getfield.((x,), ($(QuoteNode.(fields)...),))
- TermInterface.children(x::$name) = [operation(x); arguments(x)...]
+ TermInterface.is_function_call(::$name) = true
+ TermInterface.head(::$name) = $name
+ TermInterface.children(x::$name) = getfield.((x,), ($(QuoteNode.(fields)...),))
TermInterface.arity(x::$name) = $(length(fields))
Base.length(x::$name) = $(length(fields) + 1)
end |> esc
end
export @matchable
+include("expr.jl")
+
end # module
diff --git a/src/expr.jl b/src/expr.jl
index 9a0fdd2..3ec4434 100644
--- a/src/expr.jl
+++ b/src/expr.jl
@@ -1,42 +1,29 @@
+
# This file contains default definitions for TermInterface methods on Julia
# Builtin Expr type.
-struct ExprHead
- head
-end
-export ExprHead
-
-head_symbol(eh::ExprHead) = eh.head
+is_function_call(e::Expr) = _is_function_call_expr_head(e.head)
+_is_function_call_expr_head(x::Symbol) = x in (:call, :macrocall)
istree(x::Expr) = true
-head(e::Expr) = ExprHead(e.head)
-children(e::Expr) = e.args
# See https://docs.julialang.org/en/v1/devdocs/ast/
-function operation(e::Expr)
- h = head(e)
- hh = h.head
- if hh in (:call, :macrocall)
- e.args[1]
- else
- hh
- end
-end
+head(e::Expr) = is_function_call(e) ? e.args[1] : e.head
+children(e::Expr) = is_function_call(e) ? e.args[2:end] : e.args
-function arguments(e::Expr)
- h = head(e)
- hh = h.head
- if hh in (:call, :macrocall)
- e.args[2:end]
- else
- e.args
- end
+function arity(e::Expr)::Int
+ l = length(e.args)
+ is_function_call(e) ? l - 1 : l
end
-function maketerm(head::ExprHead, children; type=Any, metadata=nothing)
- if !isempty(children) && first(children) isa Union{Function,DataType}
- Expr(head.head, nameof(first(children)), @view(children[2:end])...)
+function maketerm(T::Type{Expr}, head, children; is_call=true, type=Any, metadata=nothing)
+ if is_call
+ Expr(:call, head, children...)
else
- Expr(head.head, children...)
+ Expr(head, children...)
end
end
+
+maketerm(T::Type{Expr}, head::Union{Function,DataType}, children; is_call=true, type=Any, metadata=nothing) =
+ maketerm(T, nameof(head), children; is_call, type, metadata)
+
From 59da1f7f85d71125987ec6534d820ff163ce2962 Mon Sep 17 00:00:00 2001
From: a
Date: Sun, 14 Jan 2024 17:55:58 +0100
Subject: [PATCH 11/13] adjust tests
---
test/runtests.jl | 68 +++++++++++++++++++++---------------------------
1 file changed, 30 insertions(+), 38 deletions(-)
diff --git a/test/runtests.jl b/test/runtests.jl
index 17c791c..498e53a 100644
--- a/test/runtests.jl
+++ b/test/runtests.jl
@@ -2,32 +2,33 @@ using TermInterface, Test
@testset "Expr" begin
ex = :(f(a, b))
- @test head(ex) == ExprHead(:call)
- @test children(ex) == [:f, :a, :b]
- @test operation(ex) == :f
- @test arguments(ex) == [:a, :b]
- @test ex == maketerm(ExprHead(:call), [:f, :a, :b])
+ @test istree(ex)
+ @test is_function_call(ex)
+ @test head(ex) == :f
+ @test children(ex) == [:a, :b]
+ @test ex == maketerm(Expr, :f, [:a, :b])
ex = :(arr[i, j])
- @test head(ex) == ExprHead(:ref)
- @test operation(ex) == :ref
- @test arguments(ex) == [:arr, :i, :j]
- @test ex == maketerm(ExprHead(:ref), [:arr, :i, :j])
+ @test istree(ex)
+ @test !is_function_call(ex)
+ @test head(ex) == :ref
+ @test children(ex) == [:arr, :i, :j]
+ @test ex == maketerm(Expr, :ref, [:arr, :i, :j]; is_call=false)
ex = :(i, j)
- @test head(ex) == ExprHead(:tuple)
- @test operation(ex) == :tuple
- @test arguments(ex) == [:i, :j]
+ @test istree(ex)
+ @test !is_function_call(ex)
+ @test head(ex) == :tuple
@test children(ex) == [:i, :j]
- @test ex == maketerm(ExprHead(:tuple), [:i, :j])
-
+ @test ex == maketerm(Expr, :tuple, [:i, :j]; is_call=false)
ex = Expr(:block, :a, :b, :c)
- @test head(ex) == ExprHead(:block)
- @test operation(ex) == :block
- @test children(ex) == arguments(ex) == [:a, :b, :c]
- @test ex == maketerm(ExprHead(:block), [:a, :b, :c])
+ @test istree(ex)
+ @test !is_function_call(ex)
+ @test head(ex) == :block
+ @test children(ex) == [:a, :b, :c]
+ @test ex == maketerm(Expr, :block, [:a, :b, :c]; is_call=false)
end
@testset "Custom Struct" begin
@@ -35,23 +36,16 @@ end
args
Foo(args...) = new(args)
end
- struct FooHead
- head
- end
- TermInterface.head(::Foo) = FooHead(:call)
- TermInterface.head_symbol(q::FooHead) = q.head
- TermInterface.operation(::Foo) = Foo
TermInterface.istree(::Foo) = true
- TermInterface.arguments(x::Foo) = [x.args...]
- TermInterface.children(x::Foo) = [operation(x); x.args...]
+ TermInterface.is_function_call(::Foo) = true
+ TermInterface.head(::Foo) = Foo
+ TermInterface.children(x::Foo) = collect(x.args)
t = Foo(1, 2)
- @test head(t) == FooHead(:call)
- @test head_symbol(head(t)) == :call
- @test operation(t) == Foo
- @test istree(t) == true
- @test arguments(t) == [1, 2]
- @test children(t) == [Foo, 1, 2]
+ @test istree(t)
+ @test is_function_call(t)
+ @test head(t) == Foo
+ @test children(t) == [1, 2]
end
@testset "Automatically Generated Methods" begin
@@ -61,10 +55,8 @@ end
end
t = Bar(1, 2)
- @test head(t) == BarHead(:call)
- @test head_symbol(head(t)) == :call
- @test operation(t) == Bar
- @test istree(t) == true
- @test arguments(t) == (1, 2)
- @test children(t) == [Bar, 1, 2]
+ @test istree(t)
+ @test is_function_call(t)
+ @test head(t) == Bar
+ @test children(t) == (1, 2)
end
\ No newline at end of file
From eef82732cf4fec153424a9689be67c396a7ef2c6 Mon Sep 17 00:00:00 2001
From: a
Date: Wed, 24 Jan 2024 16:49:52 +0100
Subject: [PATCH 12/13] minimal example
---
src/TermInterface.jl | 78 +++++++++++---------------------------------
src/expr.jl | 29 ----------------
test/runtests.jl | 61 +---------------------------------
3 files changed, 20 insertions(+), 148 deletions(-)
delete mode 100644 src/expr.jl
diff --git a/src/TermInterface.jl b/src/TermInterface.jl
index a192215..4b3b0ff 100644
--- a/src/TermInterface.jl
+++ b/src/TermInterface.jl
@@ -15,21 +15,12 @@ module TermInterface
"""
istree(x)
-Returns `true` if `x` is a term. If true, `head`, `children` and
+Returns `true` if `x` is a term. If true, `operation`, `arguments` and
`is_function_call` must also be defined for `x` appropriately.
"""
istree(x) = false
export istree
-"""
- is_function_call(x)
-
-Returns true if a term abstractly represents a function call or function application.
-Must be defined if `istree(x)` is defined.
-Can be true only if `istree(x)` is true.
-"""
-function is_function_call end
-export is_function_call
"""
symtype(x)
@@ -56,30 +47,30 @@ export issym
"""
- head(x)
+ operation(x)
-If `x` is a term as defined by `istree(x)`, `head(x)` returns the head of the
-term. If `x` represents a function call term like `f(a,b)`, the head
+If `x` is a term as defined by `istree(x)`, `operation(x)` returns the operation of the
+term. If `x` represents a function call term like `f(a,b)`, the operation
is the function being called, `f`.
"""
-function head end
-export head
+function operation end
+export operation
"""
- children(x)
+ arguments(x)
-Get the children of a term `x`, must be defined if `istree(x)` is `true`.
+Get the arguments of a term `x`, must be defined if `istree(x)` is `true`.
"""
-function children end
-export children
+function arguments end
+export arguments
"""
- unsorted_children(x::T)
+ unsorted_arguments(x::T)
If x is a term satisfying `istree(x)` and your term type `T` provides
-and optimized implementation for storing the children, this function can
-be used to retrieve the children when the order of arguments does not matter
+and optimized implementation for storing the arguments, this function can
+be used to retrieve the arguments when the order of arguments does not matter
but the speed of the operation does.
"""
unsorted_arguments(x) = arguments(x)
@@ -89,10 +80,10 @@ export unsorted_arguments
"""
arity(x)
-Returns the number of children of `x`. Implicitly defined
-if `children(x)` is defined.
+Returns the number of arguments of `x`. Implicitly defined
+if `arguments(x)` is defined.
"""
-arity(x)::Int = length(children(x))
+arity(x)::Int = length(arguments(x))
export arity
@@ -115,11 +106,11 @@ function metadata(x, data) end
"""
- maketerm(T::Type, head, children; is_call = true, type=Any, metadata=nothing)
+ maketerm(T::Type, operation, arguments; is_call = true, type=Any, metadata=nothing)
Has to be implemented by the provider of the expression type T.
Returns a term that is in the same closure of types as `T`,
-with `head` as the head and `children` as the arguments, `type` as the symtype
+with `operation` as the operation and `arguments` as the arguments, `type` as the symtype
and `metadata` as the metadata.
`is_call` is used to determine if the constructed term represents a function
@@ -135,40 +126,9 @@ export maketerm
node_count(t)
Count the nodes in a symbolic expression tree satisfying `istree` and `arguments`.
"""
-node_count(t) = istree(t) ? reduce(+, node_count(x) for x in children(t), init in 0) + 1 : 1
+node_count(t) = istree(t) ? reduce(+, node_count(x) for x in arguments(t), init in 0) + 1 : 1
export node_count
-"""
- @matchable struct Foo fields... end [HeadType]
-
-Take a struct definition and automatically define `TermInterface` methods.
-`is_function_call` of such type will default to `true`.
-"""
-macro matchable(expr)
- @assert expr.head == :struct
- name = expr.args[2]
- if name isa Expr
- name.head === :(<:) && (name = name.args[1])
- name isa Expr && name.head === :curly && (name = name.args[1])
- end
- fields = filter(x -> x isa Symbol || (x isa Expr && x.head == :(::)), expr.args[3].args)
- get_name(s::Symbol) = s
- get_name(e::Expr) = (@assert(e.head == :(::)); e.args[1])
- fields = map(get_name, fields)
-
- quote
- $expr
- TermInterface.istree(::$name) = true
- TermInterface.is_function_call(::$name) = true
- TermInterface.head(::$name) = $name
- TermInterface.children(x::$name) = getfield.((x,), ($(QuoteNode.(fields)...),))
- TermInterface.arity(x::$name) = $(length(fields))
- Base.length(x::$name) = $(length(fields) + 1)
- end |> esc
-end
-export @matchable
-
-include("expr.jl")
end # module
diff --git a/src/expr.jl b/src/expr.jl
deleted file mode 100644
index 3ec4434..0000000
--- a/src/expr.jl
+++ /dev/null
@@ -1,29 +0,0 @@
-
-# This file contains default definitions for TermInterface methods on Julia
-# Builtin Expr type.
-
-is_function_call(e::Expr) = _is_function_call_expr_head(e.head)
-_is_function_call_expr_head(x::Symbol) = x in (:call, :macrocall)
-
-istree(x::Expr) = true
-
-# See https://docs.julialang.org/en/v1/devdocs/ast/
-head(e::Expr) = is_function_call(e) ? e.args[1] : e.head
-children(e::Expr) = is_function_call(e) ? e.args[2:end] : e.args
-
-function arity(e::Expr)::Int
- l = length(e.args)
- is_function_call(e) ? l - 1 : l
-end
-
-function maketerm(T::Type{Expr}, head, children; is_call=true, type=Any, metadata=nothing)
- if is_call
- Expr(:call, head, children...)
- else
- Expr(head, children...)
- end
-end
-
-maketerm(T::Type{Expr}, head::Union{Function,DataType}, children; is_call=true, type=Any, metadata=nothing) =
- maketerm(T, nameof(head), children; is_call, type, metadata)
-
diff --git a/test/runtests.jl b/test/runtests.jl
index 498e53a..c1fc239 100644
--- a/test/runtests.jl
+++ b/test/runtests.jl
@@ -1,62 +1,3 @@
using TermInterface, Test
-@testset "Expr" begin
- ex = :(f(a, b))
- @test istree(ex)
- @test is_function_call(ex)
- @test head(ex) == :f
- @test children(ex) == [:a, :b]
- @test ex == maketerm(Expr, :f, [:a, :b])
-
- ex = :(arr[i, j])
- @test istree(ex)
- @test !is_function_call(ex)
- @test head(ex) == :ref
- @test children(ex) == [:arr, :i, :j]
- @test ex == maketerm(Expr, :ref, [:arr, :i, :j]; is_call=false)
-
-
- ex = :(i, j)
- @test istree(ex)
- @test !is_function_call(ex)
- @test head(ex) == :tuple
- @test children(ex) == [:i, :j]
- @test ex == maketerm(Expr, :tuple, [:i, :j]; is_call=false)
-
- ex = Expr(:block, :a, :b, :c)
- @test istree(ex)
- @test !is_function_call(ex)
- @test head(ex) == :block
- @test children(ex) == [:a, :b, :c]
- @test ex == maketerm(Expr, :block, [:a, :b, :c]; is_call=false)
-end
-
-@testset "Custom Struct" begin
- struct Foo
- args
- Foo(args...) = new(args)
- end
- TermInterface.istree(::Foo) = true
- TermInterface.is_function_call(::Foo) = true
- TermInterface.head(::Foo) = Foo
- TermInterface.children(x::Foo) = collect(x.args)
-
- t = Foo(1, 2)
- @test istree(t)
- @test is_function_call(t)
- @test head(t) == Foo
- @test children(t) == [1, 2]
-end
-
-@testset "Automatically Generated Methods" begin
- @matchable struct Bar
- a
- b::Int
- end
-
- t = Bar(1, 2)
- @test istree(t)
- @test is_function_call(t)
- @test head(t) == Bar
- @test children(t) == (1, 2)
-end
\ No newline at end of file
+@test true
\ No newline at end of file
From a3eacead14ec88aaccdf018f3cfbe0f24aa6ab41 Mon Sep 17 00:00:00 2001
From: a
Date: Wed, 24 Jan 2024 16:51:27 +0100
Subject: [PATCH 13/13] adjust docstring
---
src/TermInterface.jl | 6 +-----
1 file changed, 1 insertion(+), 5 deletions(-)
diff --git a/src/TermInterface.jl b/src/TermInterface.jl
index 4b3b0ff..8c2d0ab 100644
--- a/src/TermInterface.jl
+++ b/src/TermInterface.jl
@@ -106,16 +106,12 @@ function metadata(x, data) end
"""
- maketerm(T::Type, operation, arguments; is_call = true, type=Any, metadata=nothing)
+ maketerm(T::Type, operation, arguments; type=Any, metadata=nothing)
Has to be implemented by the provider of the expression type T.
Returns a term that is in the same closure of types as `T`,
with `operation` as the operation and `arguments` as the arguments, `type` as the symtype
and `metadata` as the metadata.
-
-`is_call` is used to determine if the constructed term represents a function
-call. If `is_call = true`, then it must construct a term `x` such that
-`is_function_call(x) = true`, and vice-versa for `is_call = false`.
"""
function maketerm end
export maketerm