Skip to content

Commit 4263649

Browse files
authored
Merge pull request #25 from willow-ahrens/compromise-interface
Compromise Interface Redesign
2 parents d4d270b + 736fe48 commit 4263649

File tree

6 files changed

+122
-109
lines changed

6 files changed

+122
-109
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "TermInterface"
22
uuid = "8ea1fca8-c5ef-4a55-8b96-4e9afe9c9a3c"
33
authors = ["Shashi Gowda <gowda@mit.edu>", "Alessandro Cheli <sudo-woodo3@protonmail.com>"]
4-
version = "0.3.3"
4+
version = "0.4.0"
55

66
[compat]
77
julia = "1"

README.md

Lines changed: 43 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -9,62 +9,69 @@ You should define the following methods for an expression tree type `T` with sym
99
with TermInterface.jl, and therefore with [SymbolicUtils.jl](https://github.com/JuliaSymbolics/SymbolicUtils.jl)
1010
and [Metatheory.jl](https://github.com/0x0f0f0f/Metatheory.jl).
1111

12-
#### `istree(x::T)` or `istree(x::Type{T})`
12+
#### `isexpr(x::T)`
1313

14-
Check if `x` represents an expression tree. If returns true,
15-
it will be assumed that `operation(::T)` and `arguments(::T)`
16-
methods are defined. Definining these three should allow use
17-
of `SymbolicUtils.simplify` on custom types. Optionally `symtype(x)` can be
18-
defined to return the expected type of the symbolic expression.
14+
Returns `true` if `x` is an expression tree (an S-expression). If true, `head`
15+
and `children` methods must be defined for `x`.
1916

17+
#### `iscall(x::T)`
2018

21-
#### `exprhead(x)`
19+
Returns `true` if `x` is a function call expression. If true, `operation`, `arguments` must also be defined for `x::T`.
2220

23-
If `x` is a term as defined by `istree(x)`, `exprhead(x)` must return a symbol,
24-
corresponding to the head of the `Expr` most similar to the term `x`.
25-
If `x` represents a function call, for example, the `exprhead` is `:call`.
26-
If `x` represents an indexing operation, such as `arr[i]`, then `exprhead` is `:ref`.
27-
Note that `exprhead` is different from `operation` and both functions should
28-
be defined correctly in order to let other packages provide code generation
29-
and pattern matching features.
3021

31-
#### `operation(x::T)`
22+
#### `head(x)`
3223

33-
Returns the head (a function object) performed by an expression
34-
tree. Called only if `istree(::T)` is true. Part of the API required
35-
for `simplify` to work. Other required methods are `arguments` and `istree`
24+
Returns the head of the S-expression.
3625

37-
#### `arguments(x::T)`
26+
#### `children(x)`
3827

39-
Returns the arguments (a `Vector`) for an expression tree.
40-
Called only if `istree(x)` is `true`. Part of the API required
41-
for `simplify` to work. Other required methods are `operation` and `istree`
28+
Returns the children (aka tail) of the S-expression.
4229

43-
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.
4430

45-
#### `similarterm(t::MyType, f, args, symtype=T; metadata=nothing, exprhead=exprhead(t))`
31+
#### `operation(x)`
4632

47-
Or `similarterm(t::Type{MyType}, f, args, symtype=T; metadata=nothing, exprhead=:call)`.
33+
Returns the function a function call expression is calling. `iscall(x)` must be
34+
true as a precondition.
4835

49-
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.
36+
#### `arguments(x)`
37+
38+
Returns the arguments to the function call in a function call expression.
39+
`iscall(x)` must be true as a precondition.
40+
41+
#### `maketerm(T, head, children, type=nothing, metadata=nothing)`
42+
43+
Constructs an expression. `T` is a constructor type, `head` and `children` are
44+
the head and tail of the S-expression, `type` is the `type` of the S-expression.
45+
`metadata` is any metadata attached to this expression.
46+
47+
Note that `maketerm` may not necessarily return an object of type `T`. For example,
48+
it may return a representation which is more efficient.
49+
50+
This function is used by term-manipulation routines to construct terms generically.
51+
In these routines, `T` is usually the type of the input expression which is being manipulated.
52+
For example, when a subexpression is substituted, the outer expression is re-constructed with
53+
the sub-expression. `T` will be the type of the outer expression.
54+
55+
Packages providing expression types _must_ implement this method for each expression type.
56+
57+
If your types do not support type information or metadata, you still need to accept
58+
these arguments and may choose to not use them.
5059

5160
### Optional
5261

53-
#### `unsorted_arguments(x)`
62+
#### `arity(x)`
63+
64+
When `x` satisfies `iscall`, returns the number of arguments of `x`.
65+
Implicitly defined if `arguments(x)` is defined.
5466

55-
If x is a term satisfying `istree(x)` and your term type `T` provides
56-
an optimized implementation for storing the arguments, this function can
57-
be used to retrieve the arguments when the order of arguments does not matter
58-
but the speed of the operation does. Defaults to `arguments(x)`.
5967

60-
#### `symtype(x)`
68+
#### `metadata(x)`
6169

62-
The supposed type of values in the domain of x. Tracing tools can use this type to
63-
pick the right method to run or analyse code.
70+
Returns the metadata attached to `x`.
6471

65-
This defaults to `typeof(x)` if `x` is numeric, or `Any` otherwise.
66-
For the types defined in this SymbolicUtils.jl, namely `T<:Symbolic{S}` it is `S`.
72+
#### `symtype(expr)`
6773

74+
Returns the symbolic type of `expr`. By default this is just `typeof(expr)`.
6875
Define this for your symbolic types if you want `SymbolicUtils.simplify` to apply rules
6976
specific to numbers (such as commutativity of multiplication). Or such
7077
rules that may be implemented in the future.

src/TermInterface.jl

Lines changed: 56 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,24 @@
11
module TermInterface
22

33
"""
4-
istree(x)
4+
iscall(x)
5+
Returns `true` if `x` is a function call expression. If true, `operation`, `arguments`
6+
must also be defined for `x`.
7+
"""
8+
iscall(x) = false
9+
export iscall
510

6-
Returns `true` if `x` is a term. If true, `operation`, `arguments`
7-
must also be defined for `x` appropriately.
811
"""
9-
istree(x) = false
10-
export istree
12+
isexpr(x)
13+
Returns `true` if `x` is an expression tree (an S-expression). If true, `head` and `children` methods must be defined for `x`.
14+
"""
15+
isexpr(x) = false
16+
export isexpr
1117

1218
"""
13-
symtype(x)
19+
symtype(expr)
1420
15-
Returns the symbolic type of `x`. By default this is just `typeof(x)`.
21+
Returns the symbolic type of `expr`. By default this is just `typeof(expr)`.
1622
Define this for your symbolic types if you want `SymbolicUtils.simplify` to apply rules
1723
specific to numbers (such as commutativity of multiplication). Or such
1824
rules that may be implemented in the future.
@@ -23,7 +29,7 @@ end
2329
export symtype
2430

2531
"""
26-
issym(x)
32+
issym(x)
2733
2834
Returns `true` if `x` is a symbol. If true, `nameof` must be defined
2935
on `x` and must return a Symbol.
@@ -32,43 +38,41 @@ issym(x) = false
3238
export issym
3339

3440
"""
35-
exprhead(x)
36-
37-
If `x` is a term as defined by `istree(x)`, `exprhead(x)` must return a symbol,
38-
corresponding to the head of the `Expr` most similar to the term `x`.
39-
If `x` represents a function call, for example, the `exprhead` is `:call`.
40-
If `x` represents an indexing operation, such as `arr[i]`, then `exprhead` is `:ref`.
41-
Note that `exprhead` is different from `operation` and both functions should
42-
be defined correctly in order to let other packages provide code generation
43-
and pattern matching features.
41+
head(x)
42+
Returns the head of the S-expression.
4443
"""
45-
function exprhead end
46-
export exprhead
44+
function head end
45+
export head
4746

47+
"""
48+
children(x)
49+
Returns the children (aka tail) of the S-expression.
50+
"""
51+
function children end
52+
export children
4853

4954
"""
5055
operation(x)
5156
52-
If `x` is a term as defined by `istree(x)`, `operation(x)` returns the
53-
head of the term if `x` represents a function call, for example, the head
54-
is the function being called.
57+
Returns the function a function call expression is calling.
58+
`iscall(x)` must be true as a precondition.
5559
"""
5660
function operation end
5761
export operation
5862

5963
"""
6064
arguments(x)
6165
62-
Get the arguments of `x`, must be defined if `istree(x)` is `true`.
66+
Returns the arguments to the function call in a function call expression.
67+
`iscall(x)` must be true as a precondition.
6368
"""
6469
function arguments end
6570
export arguments
6671

67-
6872
"""
6973
unsorted_arguments(x::T)
7074
71-
If x is a term satisfying `istree(x)` and your term type `T` orovides
75+
If x is a expression satisfying `iscall(x)` and your expression type `T` provides
7276
and optimized implementation for storing the arguments, this function can
7377
be used to retrieve the arguments when the order of arguments does not matter
7478
but the speed of the operation does.
@@ -80,8 +84,8 @@ export unsorted_arguments
8084
"""
8185
arity(x)
8286
83-
Returns the number of arguments of `x`. Implicitly defined
84-
if `arguments(x)` is defined.
87+
When `x` satisfies `iscall`, returns the number of arguments of `x`.
88+
Implicitly defined if `arguments(x)` is defined.
8589
"""
8690
arity(x) = length(arguments(x))
8791
export arity
@@ -90,37 +94,47 @@ export arity
9094
"""
9195
metadata(x)
9296
93-
Return the metadata attached to `x`.
97+
Returns the metadata attached to `x`.
9498
"""
9599
metadata(x) = nothing
96100
export metadata
97101

98102

99103
"""
100-
metadata(x, md)
104+
metadata(expr, md)
101105
102-
Returns a new term which has the structure of `x` but also has
103-
the metadata `md` attached to it.
106+
Returns a `expr` with metadata `md` attached to it.
104107
"""
105108
function metadata(x, data)
106-
error("Setting metadata on $x is not possible")
109+
error("Setting metadata on $x is not implemented")
107110
end
108111

109112

110113
"""
111-
similarterm(x, head, args, symtype=nothing; metadata=nothing, exprhead=:call)
114+
maketerm(T, head, children, type=nothing, metadata=nothing)
112115
113-
Returns a term that is in the same closure of types as `typeof(x)`,
114-
with `head` as the head and `args` as the arguments, `type` as the symtype
115-
and `metadata` as the metadata. By default this will execute `head(args...)`.
116-
`x` parameter can also be a `Type`. The `exprhead` keyword argument is useful
117-
when manipulating `Expr`s.
116+
Constructs an expression. `T` is a constructor type, `head` and `children` are
117+
the head and tail of the S-expression, `type` is the `type` of the S-expression.
118+
`metadata` is any metadata attached to this expression.
119+
120+
Note that `maketerm` may not necessarily return an object of type `T`. For example,
121+
it may return a representation which is more efficient.
122+
123+
This function is used by term-manipulation routines to construct terms generically.
124+
In these routines, `T` is usually the type of the input expression which is being manipulated.
125+
For example, when a subexpression is substituted, the outer expression is re-constructed with
126+
the sub-expression. `T` will be the type of the outer expression.
127+
128+
Packages providing expression types _must_ implement this method for each expression type.
129+
130+
If your types do not support type information or metadata, you still need to accept
131+
these arguments and may choose to not use them.
118132
"""
119-
function similarterm(x, head, args, symtype = nothing; metadata = nothing, exprhead = nothing)
120-
head(args...)
121-
end
122133

123-
export similarterm
134+
function maketerm(T::Type, head, children, type=nothing, metadata=nothing)
135+
error("maketerm for $T is not implemented")
136+
end
137+
export maketerm
124138

125139
include("utils.jl")
126140

src/expr.jl

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,15 @@
11
# This file contains default definitions for TermInterface methods on Julia
22
# Builtin Expr type.
33

4-
istree(x::Expr) = true
5-
exprhead(e::Expr) = e.head
4+
iscall(x::Expr) = x.head == :call
65

7-
operation(e::Expr) = expr_operation(e, Val{exprhead(e)}())
8-
arguments(e::Expr) = expr_arguments(e, Val{exprhead(e)}())
6+
callhead(e::Expr) = e.head
7+
head(e::Expr) = e.head
8+
children(e::Expr) = e.args
99

10-
# See https://docs.julialang.org/en/v1/devdocs/ast/
11-
expr_operation(e::Expr, ::Union{Val{:call},Val{:macrocall}}) = e.args[1]
12-
expr_operation(e::Expr, ::Union{Val{:ref}}) = getindex
13-
expr_operation(e::Expr, ::Val{T}) where {T} = T
10+
operation(e::Expr) = iscall(e) ? first(children(e)) : error("operation called on a non-function call expression")
11+
arguments(e::Expr) = iscall(e) ? @view(e.args[2:end]) : error("arguments called on a non-function call expression")
1412

15-
expr_arguments(e::Expr, ::Union{Val{:call},Val{:macrocall}}) = e.args[2:end]
16-
expr_arguments(e::Expr, _) = e.args
17-
18-
19-
function similarterm(x::Expr, head, args, symtype = nothing; metadata = nothing, exprhead = exprhead(x))
20-
expr_similarterm(head, args, Val{exprhead}())
13+
function maketerm(::Type{Expr}, head, args, symtype=nothing, metadata=nothing)
14+
Expr(head, args...)
2115
end
22-
23-
24-
expr_similarterm(head, args, ::Val{:call}) = Expr(:call, head, args...)
25-
expr_similarterm(head, args, ::Val{:macrocall}) = Expr(:macrocall, head, args...) # discard linenumbernodes?
26-
expr_similarterm(head, args, ::Val{eh}) where {eh} = Expr(eh, args...)

src/utils.jl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@
22
is_operation(f)
33
44
Returns a single argument anonymous function predicate, that returns `true` if and only if
5-
the argument to the predicate satisfies `istree` and `operation(x) == f`
5+
the argument to the predicate satisfies `iscall` and `operation(x) == f`
66
"""
7-
is_operation(f) = @nospecialize(x) -> istree(x) && (operation(x) == f)
7+
is_operation(f) = @nospecialize(x) -> iscall(x) && (operation(x) == f)
88
export is_operation
99

1010

1111
"""
1212
node_count(t)
13-
Count the nodes in a symbolic expression tree satisfying `istree` and `arguments`.
13+
Count the nodes in a symbolic expression tree satisfying `isexpr` and `arguments`.
1414
"""
15-
node_count(t) = istree(t) ? reduce(+, node_count(x) for x in arguments(t), init = 0) + 1 : 1
15+
node_count(t) = isexpr(t) ? reduce(+, node_count(x) for x in children(t); init=0) + 1 : 1
1616
export node_count

test/runtests.jl

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,18 @@ using Test
33

44
@testset "Expr" begin
55
ex = :(f(a, b))
6+
@test head(ex) == :call
7+
@test children(ex) == [:f, :a, :b]
68
@test operation(ex) == :f
79
@test arguments(ex) == [:a, :b]
8-
@test exprhead(ex) == :call
9-
@test ex == similarterm(ex, :f, [:a, :b])
10+
@test iscall(ex)
11+
@test ex == maketerm(Expr, :call, [:f, :a, :b])
12+
1013

1114
ex = :(arr[i, j])
12-
@test operation(ex) == getindex
13-
@test arguments(ex) == [:arr, :i, :j]
14-
@test exprhead(ex) == :ref
15-
@test ex == similarterm(ex, :ref, [:arr, :i, :j]; exprhead = :ref)
16-
@test ex == similarterm(ex, :ref, [:arr, :i, :j])
15+
@test head(ex) == :ref
16+
@test_throws ErrorException operation(ex)
17+
@test_throws ErrorException arguments(ex)
18+
@test !iscall(ex)
19+
@test ex == maketerm(Expr, :ref, [:arr, :i, :j])
1720
end

0 commit comments

Comments
 (0)