Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Remove type argument, symtype, update docs #39

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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "TermInterface"
uuid = "8ea1fca8-c5ef-4a55-8b96-4e9afe9c9a3c"
authors = ["Shashi Gowda <[email protected]>", "Alessandro Cheli <[email protected]>"]
version = "0.4.1"
version = "0.5.0"

[compat]
julia = "1"
Expand Down
92 changes: 71 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,38 @@ and [Metatheory.jl](https://github.com/0x0f0f0f/Metatheory.jl).

#### `isexpr(x::T)`

Returns `true` if `x` is an expression tree (an S-expression). If true, `head`
and `children` methods must be defined for `x`.
Returns `true` if `x` is an expression tree. If true, `head(x)` and `children(x)` methods must be defined for `x`.
Optionally, if `x` represents a function call, `iscall(x)` should be true, and `operation(x)` and `arguments(x)` should also be defined.

#### `iscall(x::T)`

Returns `true` if `x` is a function call expression. If true, `operation(x)`, `arguments(x)`
must also be defined for `x`.

If `iscall(x)` is true, then also `isexpr(x)` *must* be true. The other way around is not true.
(A function call is always an expression node, but not every expression tree represents a function call).

This means that, `head(x)` and `children(x)` must be defined. Together
with `operation(x)` and `arguments(x)`.

**Examples:**

In a functional language, all expression trees are function calls (e.g. SymbolicUtils.jl).
Let's say that you have an hybrid array and functional language. `iscall` on the expression `v[i]`
is `false`, and `iscall` on expression `f(x)` is `true`, but both of them are nested
expressions, and `isexpr` is `true` on both.

The same goes for Julia `Expr`. An `Expr(:block, ...)` is *not a function call*
and has no `operation` and `arguments`, but has a `head` and `children`.


The distinction between `head`/`children` and `operation`/`arguments` is needed
when dealing with languages that are *not representing function call operations as their head*.
The main example is `Expr(:call, :f, :x)`: it has both a `head` and an `operation`, which are
respectively `:call` and `:f`.

In other symbolic expression languages, such as SymbolicUtils.jl, the `head` of a node
can correspond to `operation` and `children` can correspond to `arguments`.

#### `head(x)`

Expand All @@ -22,6 +52,16 @@ Returns the head of the S-expression.

Returns the children (aka tail) of the S-expression.

#### `operation(x)`

Returns the function a function call expression is calling. `iscall(x)` must be
true as a precondition.

#### `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
Expand All @@ -41,19 +81,6 @@ Packages providing expression types _must_ implement this method for each expres
If your types do not support type information or metadata, you still need to accept
these arguments and may choose to not use them.

#### `iscall(x::T)`

Returns `true` if `x` is a function call expression. If true, `operation`, `arguments` must also be defined for `x::T`.

#### `operation(x)`

Returns the function a function call expression is calling. `iscall(x)` must be
true as a precondition.

#### `arguments(x)`

Returns the arguments to the function call in a function call expression.
`iscall(x)` must be true as a precondition.

### Optional

Expand All @@ -67,11 +94,34 @@ Implicitly defined if `arguments(x)` is defined.

Returns the metadata attached to `x`.

#### `symtype(expr)`
#### `metadata(expr, md)`

Returns `expr` with metadata `md` attached to it.

## Examples

### Function call Julia Expressions

```julia
ex = :(f(a, b))
@test head(ex) == :call
@test children(ex) == [:f, :a, :b]
@test operation(ex) == :f
@test arguments(ex) == [:a, :b]
@test isexpr(ex)
@test iscall(ex)
@test ex == maketerm(Expr, :call, [:f, :a, :b], nothing)
```


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.
### Non-function call Julia Expressions

<!-- TODO update examples -->
```julia
ex = :(arr[i, j])
@test head(ex) == :ref
@test_throws ErrorException operation(ex)
@test_throws ErrorException arguments(ex)
@test isexpr(ex)
@test !iscall(ex)
@test ex == maketerm(Expr, :ref, [:arr, :i, :j], nothing)
```
60 changes: 32 additions & 28 deletions src/TermInterface.jl
Original file line number Diff line number Diff line change
@@ -1,41 +1,45 @@
module TermInterface

"""
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

"""
isexpr(x)
Returns `true` if `x` is an expression tree (an S-expression). If true, `head` and `children` methods must be defined for `x`.
Returns `true` if `x` is an expression tree. If true, `head(x)` and `children(x)` methods must be defined for `x`.
Optionally, if `x` represents a function call, `iscall(x)` should be true, and `operation(x)` and `arguments(x)` should also be defined.
"""
isexpr(x) = false
export isexpr

"""
symtype(expr)
iscall(x)
Returns `true` if `x` is a function call expression. If true, `operation(x)`, `arguments(x)`
must also be defined for `x`.

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.
"""
function symtype(x)
typeof(x)
end
export symtype
If `iscall(x)` is true, then also `isexpr(x)` *must* be true. The other way around is not true.
(A function call is always an expression node, but not every expression tree represents a function call).

"""
issym(x)
This means that, `head(x)` and `children(x)` must be defined. Together
with `operation(x)` and `arguments(x)`.

## Examples

In a functional language, all expression trees are function calls (e.g. SymbolicUtils.jl).
Let's say that you have an hybrid array and functional language. `iscall` on the expression `v[i]`
is `false`, and `iscall` on expression `f(x)` is `true`, but both of them are nested
expressions, and `isexpr` is `true` on both.

The same goes for Julia `Expr`. An `Expr(:block, ...)` is *not a function call*
and has no `operation` and `arguments`, but has a `head` and `children`.

Returns `true` if `x` is a symbol. If true, `nameof` must be defined
on `x` and must return a Symbol.

The distinction between `head`/`children` and `operation`/`arguments` is needed
when dealing with languages that are *not representing function call operations as their head*.
The main example is `Expr(:call, :f, :x)`: it has both a `head` and an `operation`, which are
respectively `:call` and `:f`.

In other symbolic expression languages, such as SymbolicUtils.jl, the `head` of a node
can correspond to `operation` and `children` can correspond to `arguments`.
"""
issym(x) = false
export issym
iscall(x) = false
export iscall

"""
head(x)
Expand Down Expand Up @@ -109,10 +113,10 @@ function metadata end


"""
maketerm(T, head, children, type, metadata)
maketerm(T, head, children, 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.
the head and tail 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,
Expand All @@ -125,7 +129,7 @@ the sub-expression. `T` will be the type of the outer expression.

Packages providing expression types _must_ implement this method for each expression type.

Giving `nothing` for `type` or `metadata` results in a default being selected.
Giving `nothing` for `metadata` should result in a default being selected.
"""

function maketerm end
Expand Down
4 changes: 1 addition & 3 deletions src/expr.jl
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,4 @@ children(e::Expr) = e.args
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, type, metadata)
Expr(head, args...)
end
maketerm(::Type{Expr}, head, args, metadata) = Expr(head, args...)
4 changes: 2 additions & 2 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ using Test
@test arguments(ex) == [:a, :b]
@test isexpr(ex)
@test iscall(ex)
@test ex == maketerm(Expr, :call, [:f, :a, :b], nothing, nothing)
@test ex == maketerm(Expr, :call, [:f, :a, :b], nothing)


ex = :(arr[i, j])
Expand All @@ -18,5 +18,5 @@ using Test
@test_throws ErrorException arguments(ex)
@test isexpr(ex)
@test !iscall(ex)
@test ex == maketerm(Expr, :ref, [:arr, :i, :j], nothing, nothing)
@test ex == maketerm(Expr, :ref, [:arr, :i, :j], nothing)
end
Loading