From e129fcfaa72995ec6bcd5dcf4812c8fe14ce41a0 Mon Sep 17 00:00:00 2001 From: Willow Ahrens Date: Wed, 24 Jan 2024 10:56:39 -0500 Subject: [PATCH 01/18] this is the compromise, modulo similarterm --- src/TermInterface.jl | 26 ++++---------------------- src/expr.jl | 27 +++++++-------------------- test/runtests.jl | 11 ++++------- 3 files changed, 15 insertions(+), 49 deletions(-) diff --git a/src/TermInterface.jl b/src/TermInterface.jl index 78b57ca..c2911a6 100644 --- a/src/TermInterface.jl +++ b/src/TermInterface.jl @@ -31,21 +31,6 @@ 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 - - """ operation(x) @@ -64,7 +49,6 @@ Get the arguments of `x`, must be defined if `istree(x)` is `true`. function arguments end export arguments - """ unsorted_arguments(x::T) @@ -106,18 +90,16 @@ function metadata(x, data) error("Setting metadata on $x is not possible") end - """ - similarterm(x, head, args, symtype=nothing; metadata=nothing, exprhead=:call) + similarterm(x, head, args, symtype=nothing; metadata=nothing) 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. +`x` parameter can also be a `Type`. """ -function similarterm(x, head, args, symtype = nothing; metadata = nothing, exprhead = nothing) - head(args...) +function similarterm(x, head, args, symtype = nothing; metadata = nothing) + head(args...) end export similarterm diff --git a/src/expr.jl b/src/expr.jl index 4bc6788..569dc3d 100644 --- a/src/expr.jl +++ b/src/expr.jl @@ -2,25 +2,12 @@ # Builtin Expr type. istree(x::Expr) = true -exprhead(e::Expr) = e.head -operation(e::Expr) = expr_operation(e, Val{exprhead(e)}()) -arguments(e::Expr) = expr_arguments(e, Val{exprhead(e)}()) +operation(e::Expr) = e.head +arguments(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 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...) +function similarterm(x::Expr, head, args, symtype = nothing; metadata = nothing) + res = Expr(head) + res.args = args + res +end \ No newline at end of file diff --git a/test/runtests.jl b/test/runtests.jl index c344173..8f928dc 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -3,15 +3,12 @@ using Test @testset "Expr" begin 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 operation(ex) == :call + @test arguments(ex) == [:f, :a, :b] + @test ex == similarterm(ex, :call, [:f, :a, :b]) ex = :(arr[i, j]) - @test operation(ex) == getindex + @test operation(ex) == :ref @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]) end From b915767f0eae82390362c98350eb61f801f467fe Mon Sep 17 00:00:00 2001 From: Willow Ahrens Date: Wed, 24 Jan 2024 10:58:45 -0500 Subject: [PATCH 02/18] fix --- src/TermInterface.jl | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/TermInterface.jl b/src/TermInterface.jl index c2911a6..56bb6c9 100644 --- a/src/TermInterface.jl +++ b/src/TermInterface.jl @@ -96,9 +96,14 @@ end 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`. +`x` parameter can also be a `Type`. Implementers should define similarterm on the +type of `x` and not on `x` itself. """ function similarterm(x, head, args, symtype = nothing; metadata = nothing) + similarterm(typeof(x), head, args, symtype = symtype, metadata = metadata) +end + +function similarterm(x::DataType, head, args, symtype = nothing; metadata = nothing) head(args...) end From cdcdd5a4579a9d8e6b0e9fd400eeac559845acd4 Mon Sep 17 00:00:00 2001 From: Willow Ahrens Date: Wed, 24 Jan 2024 10:59:26 -0500 Subject: [PATCH 03/18] signalling --- src/TermInterface.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/TermInterface.jl b/src/TermInterface.jl index 56bb6c9..f6c3dd1 100644 --- a/src/TermInterface.jl +++ b/src/TermInterface.jl @@ -103,7 +103,7 @@ function similarterm(x, head, args, symtype = nothing; metadata = nothing) similarterm(typeof(x), head, args, symtype = symtype, metadata = metadata) end -function similarterm(x::DataType, head, args, symtype = nothing; metadata = nothing) +function similarterm(T::DataType, head, args, symtype = nothing; metadata = nothing) head(args...) end From 402ced74641b70302aed219c4907dabae4e7cf21 Mon Sep 17 00:00:00 2001 From: Willow Ahrens Date: Wed, 24 Jan 2024 13:30:07 -0500 Subject: [PATCH 04/18] splatted the args. --- src/expr.jl | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/expr.jl b/src/expr.jl index 569dc3d..ecccb57 100644 --- a/src/expr.jl +++ b/src/expr.jl @@ -7,7 +7,5 @@ operation(e::Expr) = e.head arguments(e::Expr) = e.args function similarterm(x::Expr, head, args, symtype = nothing; metadata = nothing) - res = Expr(head) - res.args = args - res + Expr(head, args...) end \ No newline at end of file From 9e9af75feac72fd40d0e6b0f5643d281d46c6297 Mon Sep 17 00:00:00 2001 From: Willow Ahrens Date: Wed, 24 Jan 2024 13:34:52 -0500 Subject: [PATCH 05/18] move to using Type --- src/TermInterface.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/TermInterface.jl b/src/TermInterface.jl index f6c3dd1..b3e37b7 100644 --- a/src/TermInterface.jl +++ b/src/TermInterface.jl @@ -103,7 +103,7 @@ function similarterm(x, head, args, symtype = nothing; metadata = nothing) similarterm(typeof(x), head, args, symtype = symtype, metadata = metadata) end -function similarterm(T::DataType, head, args, symtype = nothing; metadata = nothing) +function similarterm(T::Type, head, args, symtype = nothing; metadata = nothing) head(args...) end From e6ae7a9761c3f6b44365f0c2cee5c55eebabe028 Mon Sep 17 00:00:00 2001 From: Shashi Gowda Date: Sat, 10 Feb 2024 08:56:42 +0530 Subject: [PATCH 06/18] shashi's update --- src/TermInterface.jl | 114 +++++++++++++++++++++++++++++++------------ src/expr.jl | 14 +++--- 2 files changed, 90 insertions(+), 38 deletions(-) diff --git a/src/TermInterface.jl b/src/TermInterface.jl index b3e37b7..916525c 100644 --- a/src/TermInterface.jl +++ b/src/TermInterface.jl @@ -1,18 +1,31 @@ module TermInterface """ - istree(x) + iscall(x) +Returns `true` if `x` is a function call expression. If true, `operation`, `arguments` +must also be defined for `x`. +""" +iscall(x) = false +export iscall + +""" + istree(x) -Returns `true` if `x` is a term. If true, `operation`, `arguments` -must also be defined for `x` appropriately. +Alias of `iscall` """ -istree(x) = false -export istree +@depricate_binding istree iscall """ - symtype(x) + isexpr(x) +Returns `true` if `x` is an expression tree (an S-expression). If true, `head` and `children` methods must be defined for `x`. +""" +isexpr(x) = false +export isexpr -Returns the symbolic type of `x`. By default this is just `typeof(x)`. +""" + symtype(expr) + +Returns the symbolic type of `expr`. By default this is just `typeof(expr)`. Define this for your symbolic types if you want `SymbolicUtils.simplify` to apply rules specific to numbers (such as commutativity of multiplication). Or such rules that may be implemented in the future. @@ -23,7 +36,7 @@ end export symtype """ - issym(x) + issym(x) Returns `true` if `x` is a symbol. If true, `nameof` must be defined on `x` and must return a Symbol. @@ -31,28 +44,41 @@ on `x` and must return a Symbol. issym(x) = false export issym +""" + head(x) +Returns the head of the S-expression. +""" +function head end + +""" + children(x) +Returns the children (aka tail) of the S-expression. +""" +function children end + """ 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 -is the function being called. +Returns the function a function call expression is calling. +`iscall(x)` must be true as a precondition. """ -function operation end +operation(x) = iscall(x) ? first(children(x)) : error("operation called on a non-function call expression") export operation """ arguments(x) -Get the arguments of `x`, must be defined if `istree(x)` is `true`. +Returns the arguments to the function call in a function call expression. +`iscall(x)` must be true as a precondition. """ function arguments end +arguments(x) = iscall(x) ? Iterators.drop(children(x), 1) : error("arguments called on a non-function call expression") export arguments """ unsorted_arguments(x::T) -If x is a term satisfying `istree(x)` and your term type `T` orovides +If x is a expression satisfying `iscall(x)` and your expression type `T` provides 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. @@ -64,8 +90,8 @@ export unsorted_arguments """ arity(x) -Returns the number of arguments of `x`. Implicitly defined -if `arguments(x)` is defined. +When `x` satisfies `iscall`, returns the number of arguments of `x`. +Implicitly defined if `arguments(x)` is defined. """ arity(x) = length(arguments(x)) export arity @@ -74,41 +100,65 @@ export arity """ metadata(x) -Return the metadata attached to `x`. +Returns the metadata attached to `x`. """ metadata(x) = nothing export metadata """ - metadata(x, md) + metadata(expr, md) -Returns a new term which has the structure of `x` but also has -the metadata `md` attached to it. +Returns a `expr` with metadata `md` attached to it. """ function metadata(x, data) - error("Setting metadata on $x is not possible") + error("Setting metadata on $x is not implemented") end """ - similarterm(x, head, args, symtype=nothing; metadata=nothing) + similarterm(x, op, args, symtype=nothing; metadata=nothing) -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`. Implementers should define similarterm on the -type of `x` and not on `x` itself. """ -function similarterm(x, head, args, symtype = nothing; metadata = nothing) - similarterm(typeof(x), head, args, symtype = symtype, metadata = metadata) +function similarterm(x, op, args, symtype = nothing; metadata = nothing) + maketerm(typeof(x), callhead(x), [op, args...], symtype, metadata) end - -function similarterm(T::Type, head, args, symtype = nothing; metadata = nothing) - head(args...) +# Old fallback +function similarterm(T::Type, op, args, symtype = nothing; metadata = nothing) + op(args...) end export similarterm + +""" + callhead(x) +Used in this deprecation cycle of `similarterm` to find the `head` argument to +`makterm`. Do not implement this, or use `similarterm` if you're using this package. +""" +callhead(x) = typeof(x) +callhead(x::Expr) = Expr + +""" + maketerm(T, head, children, type, metadata) + +Constructs an expression. `T` is a constructor type, `head` and `children` are +the head and tail of the S-expression, `type` is the `type` of the S-expression. +`metadata` is any metadata attached to this expression. + +Note that `maketerm` may not necessarily return an object of type `T`. For example, +it may return a representation which is more efficient. + +This function is used by term-manipulation routines to construct terms generically. +Packages providing expression types must implement this method for each expression type. + +If your types do not support type information or metadata, you still need to accept +these arguments and may choose to not use them. +""" + +function maketerm(T::Type, head, children, type, metadata) + error("maketerm for $T is not impmlemented") +end + include("utils.jl") include("expr.jl") diff --git a/src/expr.jl b/src/expr.jl index ecccb57..810ef97 100644 --- a/src/expr.jl +++ b/src/expr.jl @@ -1,11 +1,13 @@ # This file contains default definitions for TermInterface methods on Julia # Builtin Expr type. -istree(x::Expr) = true +iscall(x::Expr) = x.head == :call -operation(e::Expr) = e.head -arguments(e::Expr) = e.args +head(e::Expr) = e.head +children(e::Expr) = e.args -function similarterm(x::Expr, head, args, symtype = nothing; metadata = nothing) - Expr(head, args...) -end \ No newline at end of file +# ^ this will implicitly define operation and arguments + +function maketerm(::Type{Expr}, head, args, symtype, metadata) + Expr(head, args...) +end From 56b41e1e2fd946734667687d0b1d21dfb331a14d Mon Sep 17 00:00:00 2001 From: Shashi Gowda Date: Sat, 10 Feb 2024 09:09:03 +0530 Subject: [PATCH 07/18] add deprecations --- src/TermInterface.jl | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/TermInterface.jl b/src/TermInterface.jl index 916525c..f858d03 100644 --- a/src/TermInterface.jl +++ b/src/TermInterface.jl @@ -13,7 +13,7 @@ export iscall Alias of `iscall` """ -@depricate_binding istree iscall +Base.@deprecate_binding istree iscall """ isexpr(x) @@ -120,10 +120,18 @@ end """ function similarterm(x, op, args, symtype = nothing; metadata = nothing) + Base.depwarn("""`similarterm` is deprecated, use `maketerm` instead. + See https://github.com/JuliaSymbolics/TermInterface.jl for details. + The present call can be replaced by + `maketerm(typeof(x), $(callhead(x)), [op, args...], symtype, metadata)`""") + maketerm(typeof(x), callhead(x), [op, args...], symtype, metadata) end + # Old fallback function similarterm(T::Type, op, args, symtype = nothing; metadata = nothing) + Base.depwarn("`similarterm` is deprecated, use `maketerm` instead." * + "See https://github.com/JuliaSymbolics/TermInterface.jl for details.") op(args...) end @@ -149,7 +157,11 @@ Note that `maketerm` may not necessarily return an object of type `T`. For examp it may return a representation which is more efficient. This function is used by term-manipulation routines to construct terms generically. -Packages providing expression types must implement this method for each expression type. +In these routines, `T` is usually the type of the input expression which is being manipulated. +For example, when a subexpression is substituted, the outer expression is re-constructed with +the sub-expression. `T` will be the type of the outer expression. + +Packages providing expression types _must_ implement this method for each expression type. If your types do not support type information or metadata, you still need to accept these arguments and may choose to not use them. From d1b008e8c3c8789b2a97fa8f8c6eefebf9a70dcd Mon Sep 17 00:00:00 2001 From: Shashi Gowda Date: Sat, 10 Feb 2024 09:26:33 +0530 Subject: [PATCH 08/18] bump version --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 2de42ff..5ab3346 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.0" [compat] julia = "1" From 294a2a94674200ba4f52a6c1fd5bca52451b1ca6 Mon Sep 17 00:00:00 2001 From: Shashi Gowda Date: Sat, 10 Feb 2024 09:36:18 +0530 Subject: [PATCH 09/18] load order fix --- src/TermInterface.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/TermInterface.jl b/src/TermInterface.jl index f858d03..73636fd 100644 --- a/src/TermInterface.jl +++ b/src/TermInterface.jl @@ -8,12 +8,13 @@ must also be defined for `x`. iscall(x) = false export iscall +Base.@deprecate_binding istree iscall """ istree(x) Alias of `iscall` """ -Base.@deprecate_binding istree iscall +istree """ isexpr(x) From 35d2c4c06762262e4460e1b79fbf662b6af722b6 Mon Sep 17 00:00:00 2001 From: Shashi Gowda Date: Tue, 13 Feb 2024 17:25:12 +0530 Subject: [PATCH 10/18] updates --- src/TermInterface.jl | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/TermInterface.jl b/src/TermInterface.jl index 73636fd..b109971 100644 --- a/src/TermInterface.jl +++ b/src/TermInterface.jl @@ -124,7 +124,7 @@ function similarterm(x, op, args, symtype = nothing; metadata = nothing) Base.depwarn("""`similarterm` is deprecated, use `maketerm` instead. See https://github.com/JuliaSymbolics/TermInterface.jl for details. The present call can be replaced by - `maketerm(typeof(x), $(callhead(x)), [op, args...], symtype, metadata)`""") + `maketerm(typeof(x), $(callhead(x)), [op, args...], symtype, metadata)`""", :similarterm) maketerm(typeof(x), callhead(x), [op, args...], symtype, metadata) end @@ -132,7 +132,7 @@ end # Old fallback function similarterm(T::Type, op, args, symtype = nothing; metadata = nothing) Base.depwarn("`similarterm` is deprecated, use `maketerm` instead." * - "See https://github.com/JuliaSymbolics/TermInterface.jl for details.") + "See https://github.com/JuliaSymbolics/TermInterface.jl for details.", :similarterm) op(args...) end @@ -145,7 +145,6 @@ Used in this deprecation cycle of `similarterm` to find the `head` argument to `makterm`. Do not implement this, or use `similarterm` if you're using this package. """ callhead(x) = typeof(x) -callhead(x::Expr) = Expr """ maketerm(T, head, children, type, metadata) @@ -169,7 +168,7 @@ these arguments and may choose to not use them. """ function maketerm(T::Type, head, children, type, metadata) - error("maketerm for $T is not impmlemented") + error("maketerm for $T is not implemented") end include("utils.jl") From 5b19f95b9ce722ca8e0203779c9f358aeb13ec29 Mon Sep 17 00:00:00 2001 From: Shashi Gowda Date: Wed, 21 Feb 2024 10:06:33 +0530 Subject: [PATCH 11/18] no default operation and arguments --- src/TermInterface.jl | 3 +-- src/expr.jl | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/TermInterface.jl b/src/TermInterface.jl index b109971..d23b7cc 100644 --- a/src/TermInterface.jl +++ b/src/TermInterface.jl @@ -63,7 +63,7 @@ function children end Returns the function a function call expression is calling. `iscall(x)` must be true as a precondition. """ -operation(x) = iscall(x) ? first(children(x)) : error("operation called on a non-function call expression") +function operation end export operation """ @@ -73,7 +73,6 @@ Returns the arguments to the function call in a function call expression. `iscall(x)` must be true as a precondition. """ function arguments end -arguments(x) = iscall(x) ? Iterators.drop(children(x), 1) : error("arguments called on a non-function call expression") export arguments """ diff --git a/src/expr.jl b/src/expr.jl index 810ef97..69cca8e 100644 --- a/src/expr.jl +++ b/src/expr.jl @@ -6,7 +6,8 @@ iscall(x::Expr) = x.head == :call head(e::Expr) = e.head children(e::Expr) = e.args -# ^ this will implicitly define operation and arguments +operation(e::Expr) = e.args[1] +arguments(e::Expr) = e.args[2:end] function maketerm(::Type{Expr}, head, args, symtype, metadata) Expr(head, args...) From 70820e91b7e88ffafb501f70ec18c82865360bc4 Mon Sep 17 00:00:00 2001 From: a Date: Wed, 21 Feb 2024 21:50:28 +0100 Subject: [PATCH 12/18] adjustments --- src/TermInterface.jl | 30 ++++++++++++++++-------------- src/expr.jl | 5 +++-- test/runtests.jl | 19 +++++++++++++------ 3 files changed, 32 insertions(+), 22 deletions(-) diff --git a/src/TermInterface.jl b/src/TermInterface.jl index b109971..e2febad 100644 --- a/src/TermInterface.jl +++ b/src/TermInterface.jl @@ -50,12 +50,14 @@ export issym Returns the head of the S-expression. """ function head end +export head """ children(x) Returns the children (aka tail) of the S-expression. """ function children end +export children """ operation(x) @@ -63,7 +65,7 @@ function children end Returns the function a function call expression is calling. `iscall(x)` must be true as a precondition. """ -operation(x) = iscall(x) ? first(children(x)) : error("operation called on a non-function call expression") +function operation(x) end export operation """ @@ -73,7 +75,6 @@ Returns the arguments to the function call in a function call expression. `iscall(x)` must be true as a precondition. """ function arguments end -arguments(x) = iscall(x) ? Iterators.drop(children(x), 1) : error("arguments called on a non-function call expression") export arguments """ @@ -120,20 +121,20 @@ end similarterm(x, op, args, symtype=nothing; metadata=nothing) """ -function similarterm(x, op, args, symtype = nothing; metadata = nothing) - Base.depwarn("""`similarterm` is deprecated, use `maketerm` instead. - See https://github.com/JuliaSymbolics/TermInterface.jl for details. - The present call can be replaced by - `maketerm(typeof(x), $(callhead(x)), [op, args...], symtype, metadata)`""", :similarterm) +function similarterm(x, op, args, symtype=nothing; metadata=nothing) + Base.depwarn("""`similarterm` is deprecated, use `maketerm` instead. + See https://github.com/JuliaSymbolics/TermInterface.jl for details. + The present call can be replaced by + `maketerm(typeof(x), $(head(x)), [op, args...], symtype, metadata)`""", :similarterm) - maketerm(typeof(x), callhead(x), [op, args...], symtype, metadata) + maketerm(typeof(x), head(x), [op, args...], symtype, metadata) end # Old fallback -function similarterm(T::Type, op, args, symtype = nothing; metadata = nothing) - Base.depwarn("`similarterm` is deprecated, use `maketerm` instead." * - "See https://github.com/JuliaSymbolics/TermInterface.jl for details.", :similarterm) - op(args...) +function similarterm(T::Type, op, args, symtype=nothing; metadata=nothing) + Base.depwarn("`similarterm` is deprecated, use `maketerm` instead." * + "See https://github.com/JuliaSymbolics/TermInterface.jl for details.", :similarterm) + op(args...) end export similarterm @@ -167,9 +168,10 @@ If your types do not support type information or metadata, you still need to acc these arguments and may choose to not use them. """ -function maketerm(T::Type, head, children, type, metadata) - error("maketerm for $T is not implemented") +function maketerm(T::Type, head, children, type=nothing, metadata=nothing) + error("maketerm for $T is not implemented") end +export maketerm include("utils.jl") diff --git a/src/expr.jl b/src/expr.jl index 810ef97..f2850c4 100644 --- a/src/expr.jl +++ b/src/expr.jl @@ -6,8 +6,9 @@ iscall(x::Expr) = x.head == :call head(e::Expr) = e.head children(e::Expr) = e.args -# ^ this will implicitly define operation and arguments +operation(e::Expr) = iscall(e) ? first(children(e)) : error("operation called on a non-function call expression") +arguments(e::Expr) = iscall(e) ? @view(e.args[2:end]) : error("arguments called on a non-function call expression") -function maketerm(::Type{Expr}, head, args, symtype, metadata) +function maketerm(::Type{Expr}, head, args, symtype=nothing, metadata=nothing) Expr(head, args...) end diff --git a/test/runtests.jl b/test/runtests.jl index 8f928dc..24e1dfd 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -3,12 +3,19 @@ using Test @testset "Expr" begin ex = :(f(a, b)) - @test operation(ex) == :call - @test arguments(ex) == [:f, :a, :b] - @test ex == similarterm(ex, :call, [:f, :a, :b]) + @test head(ex) == :call + @test children(ex) == [:f, :a, :b] + @test operation(ex) == :f + @test arguments(ex) == [:a, :b] + @test iscall(ex) + @test ex == similarterm(ex, :f, [:a, :b]) + @test ex == maketerm(Expr, :call, [:f, :a, :b]) + ex = :(arr[i, j]) - @test operation(ex) == :ref - @test arguments(ex) == [:arr, :i, :j] - @test ex == similarterm(ex, :ref, [:arr, :i, :j]) + @test head(ex) == :ref + @test_throws ErrorException operation(ex) + @test_throws ErrorException arguments(ex) + @test !iscall(ex) + @test ex == maketerm(Expr, :ref, [:arr, :i, :j]) end From 7cd310c0792658b60aa0616c7c4a81da07518bbd Mon Sep 17 00:00:00 2001 From: a Date: Sun, 25 Feb 2024 19:33:53 +0100 Subject: [PATCH 13/18] adjust docstring --- src/TermInterface.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/TermInterface.jl b/src/TermInterface.jl index e2febad..3e4df49 100644 --- a/src/TermInterface.jl +++ b/src/TermInterface.jl @@ -143,7 +143,7 @@ export similarterm """ callhead(x) Used in this deprecation cycle of `similarterm` to find the `head` argument to -`makterm`. Do not implement this, or use `similarterm` if you're using this package. +`maketerm`. Do not implement this, or use `similarterm` if you're using this package. """ callhead(x) = typeof(x) From 7d0abf57f36804900b4d507960aa00b0249d6a9f Mon Sep 17 00:00:00 2001 From: a Date: Sun, 25 Feb 2024 19:38:36 +0100 Subject: [PATCH 14/18] callhead --- src/TermInterface.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/TermInterface.jl b/src/TermInterface.jl index 3e4df49..abb548b 100644 --- a/src/TermInterface.jl +++ b/src/TermInterface.jl @@ -127,7 +127,7 @@ function similarterm(x, op, args, symtype=nothing; metadata=nothing) The present call can be replaced by `maketerm(typeof(x), $(head(x)), [op, args...], symtype, metadata)`""", :similarterm) - maketerm(typeof(x), head(x), [op, args...], symtype, metadata) + maketerm(typeof(x), callhead(x), [op, args...], symtype, metadata) end # Old fallback From d707674e0ba107718f7a11ea8e1379eaf2711883 Mon Sep 17 00:00:00 2001 From: a Date: Sun, 25 Feb 2024 19:39:20 +0100 Subject: [PATCH 15/18] callhead --- src/expr.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/expr.jl b/src/expr.jl index f2850c4..f2f3c0c 100644 --- a/src/expr.jl +++ b/src/expr.jl @@ -3,6 +3,7 @@ iscall(x::Expr) = x.head == :call +callhead(e::Expr) = e.head head(e::Expr) = e.head children(e::Expr) = e.args From 155d705e5670d4fb7bdc338df80692e322993dd1 Mon Sep 17 00:00:00 2001 From: a Date: Wed, 28 Feb 2024 14:55:56 +0100 Subject: [PATCH 16/18] README --- README.md | 79 ++++++++++++++++++++++++-------------------- src/TermInterface.jl | 10 +----- 2 files changed, 44 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index 583c171..82a6aab 100644 --- a/README.md +++ b/README.md @@ -9,62 +9,69 @@ You should define the following methods for an expression tree type `T` with sym with TermInterface.jl, and therefore with [SymbolicUtils.jl](https://github.com/JuliaSymbolics/SymbolicUtils.jl) and [Metatheory.jl](https://github.com/0x0f0f0f/Metatheory.jl). -#### `istree(x::T)` or `istree(x::Type{T})` +#### `isexpr(x::T)` -Check if `x` represents an expression tree. If returns true, -it will be assumed that `operation(::T)` and `arguments(::T)` -methods are defined. Definining these three should allow use -of `SymbolicUtils.simplify` on custom types. Optionally `symtype(x)` can be -defined to return the expected type of the symbolic expression. +Returns `true` if `x` is an expression tree (an S-expression). If true, `head` +and `children` methods must be defined for `x`. +#### `iscall(x::T)` -#### `exprhead(x)` +Returns `true` if `x` is a function call expression. If true, `operation`, `arguments` must also be defined for `x::T`. -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. -#### `operation(x::T)` +#### `head(x)` -Returns the head (a function object) performed by an expression -tree. Called only if `istree(::T)` is true. Part of the API required -for `simplify` to work. Other required methods are `arguments` and `istree` +Returns the head of the S-expression. -#### `arguments(x::T)` +#### `children(x)` -Returns the arguments (a `Vector`) for an expression tree. -Called only if `istree(x)` is `true`. Part of the API required -for `simplify` to work. Other required methods are `operation` and `istree` +Returns the children (aka tail) of the S-expression. -In addition, the methods for `Base.hash` and `Base.isequal` should also be implemented by the types for the purposes of substitution and equality matching respectively. -#### `similarterm(t::MyType, f, args, symtype=T; metadata=nothing, exprhead=exprhead(t))` +#### `operation(x)` -Or `similarterm(t::Type{MyType}, f, args, symtype=T; metadata=nothing, exprhead=:call)`. +Returns the function a function call expression is calling. `iscall(x)` must be +true as a precondition. -Construct a new term with the operation `f` and arguments `args`, the term should be similar to `t` in type. if `t` is a `SymbolicUtils.Term` object a new Term is created with the same symtype as `t`. If not, the result is computed as `f(args...)`. Defining this method for your term type will reduce any performance loss in performing `f(args...)` (esp. the splatting, and redundant type computation). T is the symtype of the output term. You can use `SymbolicUtils.promote_symtype` to infer this type. The `exprhead` keyword argument is useful when creating `Expr`s. +#### `arguments(x)` + +Returns the arguments to the function call in a function call expression. +`iscall(x)` must be true as a precondition. + +#### `maketerm(T, head, children, type=nothing, metadata=nothing)` + +Constructs an expression. `T` is a constructor type, `head` and `children` are +the head and tail of the S-expression, `type` is the `type` of the S-expression. +`metadata` is any metadata attached to this expression. + +Note that `maketerm` may not necessarily return an object of type `T`. For example, +it may return a representation which is more efficient. + +This function is used by term-manipulation routines to construct terms generically. +In these routines, `T` is usually the type of the input expression which is being manipulated. +For example, when a subexpression is substituted, the outer expression is re-constructed with +the sub-expression. `T` will be the type of the outer expression. + +Packages providing expression types _must_ implement this method for each expression type. + +If your types do not support type information or metadata, you still need to accept +these arguments and may choose to not use them. ### Optional -#### `unsorted_arguments(x)` +#### `arity(x)` + +When `x` satisfies `iscall`, returns the number of arguments of `x`. +Implicitly defined if `arguments(x)` is defined. -If x is a term satisfying `istree(x)` and your term type `T` provides -an 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. Defaults to `arguments(x)`. -#### `symtype(x)` +#### `metadata(x)` -The supposed type of values in the domain of x. Tracing tools can use this type to -pick the right method to run or analyse code. +Returns the metadata attached to `x`. -This defaults to `typeof(x)` if `x` is numeric, or `Any` otherwise. -For the types defined in this SymbolicUtils.jl, namely `T<:Symbolic{S}` it is `S`. +#### `symtype(expr)` +Returns the symbolic type of `expr`. By default this is just `typeof(expr)`. Define this for your symbolic types if you want `SymbolicUtils.simplify` to apply rules specific to numbers (such as commutativity of multiplication). Or such rules that may be implemented in the future. diff --git a/src/TermInterface.jl b/src/TermInterface.jl index 512d9ab..774de18 100644 --- a/src/TermInterface.jl +++ b/src/TermInterface.jl @@ -8,14 +8,6 @@ must also be defined for `x`. iscall(x) = false export iscall -Base.@deprecate_binding istree iscall -""" - istree(x) - -Alias of `iscall` -""" -istree - """ isexpr(x) Returns `true` if `x` is an expression tree (an S-expression). If true, `head` and `children` methods must be defined for `x`. @@ -148,7 +140,7 @@ Used in this deprecation cycle of `similarterm` to find the `head` argument to callhead(x) = typeof(x) """ - maketerm(T, head, children, type, metadata) + maketerm(T, head, children, type=nothing, metadata=nothing) Constructs an expression. `T` is a constructor type, `head` and `children` are the head and tail of the S-expression, `type` is the `type` of the S-expression. From b00af4d1c1b71df2a56db918f2c3d11bfd1b4b3e Mon Sep 17 00:00:00 2001 From: a Date: Wed, 28 Feb 2024 14:56:45 +0100 Subject: [PATCH 17/18] adjust --- src/utils.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/utils.jl b/src/utils.jl index b9c732f..3f4cfc6 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -2,15 +2,15 @@ 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` +the argument to the predicate satisfies `iscall` and `operation(x) == f` """ -is_operation(f) = @nospecialize(x) -> istree(x) && (operation(x) == f) +is_operation(f) = @nospecialize(x) -> iscall(x) && (operation(x) == f) export is_operation """ node_count(t) -Count the nodes in a symbolic expression tree satisfying `istree` and `arguments`. +Count the nodes in a symbolic expression tree satisfying `isexpr` and `arguments`. """ -node_count(t) = istree(t) ? reduce(+, node_count(x) for x in arguments(t), init = 0) + 1 : 1 +node_count(t) = isexpr(t) ? reduce(+, node_count(x) for x in children(t); init=0) + 1 : 1 export node_count \ No newline at end of file From 82ed37ca69957c72e157b4e0ad1fd3bce08bd709 Mon Sep 17 00:00:00 2001 From: Shashi Gowda Date: Thu, 29 Feb 2024 09:34:43 +0530 Subject: [PATCH 18/18] remove similarterm --- src/TermInterface.jl | 29 ----------------------------- test/runtests.jl | 1 - 2 files changed, 30 deletions(-) diff --git a/src/TermInterface.jl b/src/TermInterface.jl index 512d9ab..10d7948 100644 --- a/src/TermInterface.jl +++ b/src/TermInterface.jl @@ -117,35 +117,6 @@ function metadata(x, data) error("Setting metadata on $x is not implemented") end -""" - similarterm(x, op, args, symtype=nothing; metadata=nothing) - -""" -function similarterm(x, op, args, symtype=nothing; metadata=nothing) - Base.depwarn("""`similarterm` is deprecated, use `maketerm` instead. - See https://github.com/JuliaSymbolics/TermInterface.jl for details. - The present call can be replaced by - `maketerm(typeof(x), $(head(x)), [op, args...], symtype, metadata)`""", :similarterm) - - maketerm(typeof(x), callhead(x), [op, args...], symtype, metadata) -end - -# Old fallback -function similarterm(T::Type, op, args, symtype=nothing; metadata=nothing) - Base.depwarn("`similarterm` is deprecated, use `maketerm` instead." * - "See https://github.com/JuliaSymbolics/TermInterface.jl for details.", :similarterm) - op(args...) -end - -export similarterm - - -""" - callhead(x) -Used in this deprecation cycle of `similarterm` to find the `head` argument to -`maketerm`. Do not implement this, or use `similarterm` if you're using this package. -""" -callhead(x) = typeof(x) """ maketerm(T, head, children, type, metadata) diff --git a/test/runtests.jl b/test/runtests.jl index 24e1dfd..94963c3 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -8,7 +8,6 @@ using Test @test operation(ex) == :f @test arguments(ex) == [:a, :b] @test iscall(ex) - @test ex == similarterm(ex, :f, [:a, :b]) @test ex == maketerm(Expr, :call, [:f, :a, :b])