Skip to content
Open
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
131 changes: 77 additions & 54 deletions src/rulenode.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ An `AbstractRuleNode` is expected to implement the following functions:

- `isfilled(::AbstractRuleNode)::Bool`. True iff the grammar rule this node holds is not ambiguous, i.e. has domain size 1.
- `isuniform(::AbstractRuleNode)::Bool`. True iff the children of this node are known.
- `get_rule(::AbstractRuleNode)::Int`. Returns the index of the grammar rule it represents.
- `get_rule(::AbstractRuleNode)::Integer`. Returns the index of the grammar rule it represents.
- `get_children(::AbstractRuleNode)::Vector{AbstractRuleNode}`. Returns the children of this node.

Expression trees consist of [`RuleNode`](@ref)s and [`AbstractHole`](@ref)s.
Expand All @@ -16,7 +16,7 @@ Expression trees consist of [`RuleNode`](@ref)s and [`AbstractHole`](@ref)s.
"""
abstract type AbstractRuleNode end

# Interface to AbstractTrees.jl
# Integererface to AbstractTrees.jl
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Find and replace error? 😄

AbstractTrees.children(node::AbstractRuleNode) = get_children(node)
AbstractTrees.nodevalue(node::AbstractRuleNode) = get_rule(node)

Expand All @@ -34,12 +34,19 @@ A [`RuleNode`](@ref) consists of:
!!! compat
Evaluate immediately functionality is not yet supported by most of Herb.jl.
"""
mutable struct RuleNode <: AbstractRuleNode
ind::Int # index in grammar
_val::Any #value of _() evals
mutable struct RuleNode{T<:Integer} <: AbstractRuleNode
ind::T
_val::Any
children::Vector{AbstractRuleNode}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering if we should switch this to AbstractVector while we're at it. This opens us up to using StaticArrays.jl' SVector or MVector. Probably some performance to squeeze out there.


# Default constructor
function RuleNode(ind::Integer, _val::Any, children::Vector{<:AbstractRuleNode}=AbstractRuleNode[])
new{UInt8}(ind, _val, children)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd also be fine with using smallest_Int_type now that it's fast.

end
end



"""
AbstractHole <: AbstractRuleNode

Expand Down Expand Up @@ -84,16 +91,16 @@ Contains a hole and the path to the hole from the root of the tree.
"""
struct HoleReference
hole::AbstractHole
path::Vector{Int}
path::Vector{Integer}
end

RuleNode(ind::Int) = RuleNode(ind, nothing, AbstractRuleNode[])
RuleNode(ind::Integer) = RuleNode(ind, nothing, AbstractRuleNode[])
"""
RuleNode(ind::Int, children::Vector{AbstractRuleNode})
RuleNode(ind::Integer, children::Vector{AbstractRuleNode})

Create a [`RuleNode`](@ref) for the [`AbstractGrammar`](@ref) rule with index `ind` and `children` as subtrees.
"""
RuleNode(ind::Int, children::Vector{<:AbstractRuleNode}) = RuleNode(ind, nothing, children)
RuleNode(ind::Integer, children::Vector{<:AbstractRuleNode}) = RuleNode(ind, nothing, children)

function _shorthand2rulenode(i::Integer)
:(RuleNode($i))
Expand All @@ -102,7 +109,7 @@ end
function _shorthand2rulenode(ex::Expr)
@assert ex.head==:curly "Input to the @rulenode macro should be in the form of: 1{2,3}"
:(RuleNode(
# Interpolate the _value_ of the first rule (1 from 1{2,3})
# Integererpolate the _value_ of the first rule (1 from 1{2,3})
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# Integererpolate the _value_ of the first rule (1 from 1{2,3})
# Interpolate the _value_ of the first rule (1 from 1{2,3})

# into the first argument of the RuleNode constructor
$(ex.args[1]),
[
Expand Down Expand Up @@ -137,20 +144,20 @@ macro rulenode(ex::Union{Integer, Expr})
end

"""
RuleNode(ind::Int, _val::Any)
RuleNode(ind::Integer, _val::Any)

Create a [`RuleNode`](@ref) for the [`AbstractGrammar`](@ref) rule with index `ind`,
`_val` as immediately evaluated value and no children

!!! warning
Only use this constructor if you are absolutely certain that a rule is terminal and cannot have children.
Use [`RuleNode(ind::Int, grammar::AbstractGrammar)`] for rules that might have children.
Use [`RuleNode(ind::Integer, grammar::AbstractGrammar)`] for rules that might have children.
In general, [`AbstractHole`](@ref)s should be used as a placeholder when the children of a node are not yet known.

!!! compat
Evaluate immediately functionality is not yet supported by most of Herb.jl.
"""
RuleNode(ind::Int, _val::Any) = RuleNode(ind, _val, AbstractRuleNode[])
# RuleNode(ind::Integer, _val::Any) = RuleNode(ind, _val, AbstractRuleNode[])
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why comment this?


Base.:(==)(::RuleNode, ::AbstractHole) = false
Base.:(==)(::AbstractHole, ::RuleNode) = false
Expand Down Expand Up @@ -236,7 +243,7 @@ are found.
Base.isless(rn₁::AbstractRuleNode, rn₂::AbstractRuleNode)::Bool = _rulenode_compare(
rn₁, rn₂) == -1

function _rulenode_compare(rn₁::AbstractRuleNode, rn₂::AbstractRuleNode)::Int
function _rulenode_compare(rn₁::AbstractRuleNode, rn₂::AbstractRuleNode)::Integer
# Helper function for Base.isless
if !isfilled(rn₁) || !isfilled(rn₂)
throw(ArgumentError("Unable to compare nodes of types ($(typeof(rn₁)), $(typeof(rn₂)))"))
Expand All @@ -255,12 +262,12 @@ function _rulenode_compare(rn₁::AbstractRuleNode, rn₂::AbstractRuleNode)::In
end

"""
depth(root::RuleNode)::Int
depth(root::RuleNode)::Integer

Return the depth of the [`AbstractRuleNode`](@ref) tree rooted at root.
Holes do count towards the depth.
"""
function depth(root::AbstractRuleNode)::Int
function depth(root::AbstractRuleNode)::Integer
retval = 1
for c in root.children
retval = max(retval, depth(c) + 1)
Expand All @@ -271,15 +278,15 @@ end
depth(::Hole) = 1

"""
node_depth(root::AbstractRuleNode, node::AbstractRuleNode)::Int
node_depth(root::AbstractRuleNode, node::AbstractRuleNode)::Integer

Return the depth of `node` for an [`AbstractRuleNode`](@ref) tree rooted at `root`.
Depth is `1` when `root == node`.

!!! warning
`node` must be a subtree of `root` in order for this function to work.
"""
function node_depth(root::AbstractRuleNode, node::AbstractRuleNode)::Int
function node_depth(root::AbstractRuleNode, node::AbstractRuleNode)::Integer
root ≡ node && return 1
root isa Hole && return 1
for c in root.children
Expand All @@ -290,17 +297,17 @@ function node_depth(root::AbstractRuleNode, node::AbstractRuleNode)::Int
end

"""
rulesoftype(node::RuleNode, ruleset::Set{Int}[, ignoreNode::AbstractRuleNode])
rulesoftype(node::RuleNode, rule_index::Int[, ignoreNode::AbstractRuleNode])
rulesoftype(node::RuleNode, ruleset::Set{<:Integer}[, ignoreNode::AbstractRuleNode])
rulesoftype(node::RuleNode, rule_index::Integer[, ignoreNode::AbstractRuleNode])

Returns every rule in the ruleset that is also used in the [`AbstractRuleNode`](@ref) tree, but not in the `ignoreNode` subtree.

!!! warning
The `ignoreNode` must be a subtree of `node` for it to have an effect.
"""
function rulesoftype(node::RuleNode, ruleset::Set{Int},
ignoreNode::Union{Nothing, AbstractRuleNode} = nothing)::Set{Int}
retval = Set{Int}()
function rulesoftype(node::RuleNode, ruleset::Set{<:Integer},
ignoreNode::Union{Nothing, AbstractRuleNode} = nothing)::Set{Integer}
retval = Set{Integer}()

if !isnothing(ignoreNode) && node == ignoreNode
return retval
Expand All @@ -320,12 +327,12 @@ function rulesoftype(node::RuleNode, ruleset::Set{Int},
return retval
end
end
function rulesoftype(node::RuleNode, rule_index::Int,
function rulesoftype(node::RuleNode, rule_index::Integer,
ignoreNode::Union{Nothing, AbstractRuleNode} = nothing)
rulesoftype(node, Set{Int}(rule_index), ignoreNode)
rulesoftype(node, Set{Integer}(rule_index), ignoreNode)
end

rulesoftype(::Hole, ::Vararg{Any}) = Set{Int}()
rulesoftype(::Hole, ::Vararg{Any}) = Set{Integer}()

"""
rulesoftype(node::RuleNode, grammar::AbstractGrammar, ruletype::Symbol[, ignoreNode::AbstractRuleNode])
Expand All @@ -339,20 +346,20 @@ rulesoftype(node::RuleNode, grammar::AbstractGrammar, ruletype::Symbol, ignoreNo
node, Set(grammar[ruletype]), ignoreNode)

"""
contains_index(rulenode::RuleNode, index::Int)
contains_index(rulenode::RuleNode, index::Integer)

Returns true if the rulenode contains the index.
"""
contains_index(rulenode::AbstractRuleNode, index::Int) = !isempty(rulesoftype(
contains_index(rulenode::AbstractRuleNode, index::Integer) = !isempty(rulesoftype(
rulenode, index))

"""
swap_node(expr::AbstractRuleNode, new_expr::AbstractRuleNode, path::Vector{Int})
swap_node(expr::AbstractRuleNode, new_expr::AbstractRuleNode, path::Vector{<:Integer})

Replace a node in `expr`, specified by `path`, with `new_expr`.
Path is a sequence of child indices, starting from the root node.
"""
function swap_node(expr::AbstractRuleNode, new_expr::AbstractRuleNode, path::Vector{Int})
function swap_node(expr::AbstractRuleNode, new_expr::AbstractRuleNode, path::Vector{<:Integer})
if length(path) == 1
expr.children[path[begin]] = new_expr
else
Expand All @@ -361,11 +368,11 @@ function swap_node(expr::AbstractRuleNode, new_expr::AbstractRuleNode, path::Vec
end

"""
swap_node(expr::RuleNode, node::RuleNode, child_index::Int, new_expr::RuleNode)
swap_node(expr::RuleNode, node::RuleNode, child_index::Integer, new_expr::RuleNode)

Replace child `i` of a node, a part of larger `expr`, with `new_expr`.
"""
function swap_node(expr::RuleNode, node::RuleNode, child_index::Int, new_expr::RuleNode)
function swap_node(expr::RuleNode, node::RuleNode, child_index::Integer, new_expr::RuleNode)
if expr == node
node.children[child_index] = new_expr
else
Expand All @@ -376,14 +383,14 @@ function swap_node(expr::RuleNode, node::RuleNode, child_index::Int, new_expr::R
end

"""
get_rulesequence(node::RuleNode, path::Vector{Int})
get_rulesequence(node::RuleNode, path::Vector{Integer})

Extract the derivation sequence from a path (sequence of child indices) and an [`AbstractRuleNode`](@ref).
If the path is deeper than the deepest node, it returns what it has.
"""
function get_rulesequence(node::RuleNode, path::Vector{Int})
function get_rulesequence(node::RuleNode, path::Vector{<:Integer})
if node.ind == 0 # sign for empty node
return Vector{Int}()
return Vector{Integer}()
elseif isempty(node.children) # no childnen, nowehere to follow the path; still return the index
return [node.ind]
elseif isempty(path)
Expand All @@ -397,54 +404,54 @@ function get_rulesequence(node::RuleNode, path::Vector{Int})
# if only one item left in the path
# need to access the child with get because it can happen that the child is not yet built
return append!([node.ind],
get_rulesequence(get(node.children, path[begin], RuleNode(0)), Vector{Int}()))
get_rulesequence(get(node.children, path[begin], RuleNode(0)), Vector{Integer}()))
end
end

get_rulesequence(::AbstractHole, ::Vector{Int}) = Vector{Int}()
get_rulesequence(::AbstractHole, ::Vector{Integer}) = Vector{Integer}()

"""
rulesonleft(expr::RuleNode, path::Vector{Int})::Set{Int}
rulesonleft(expr::RuleNode, path::Vector{Integer})::Set{Integer}

Finds all rules that are used in the left subtree defined by the path.
"""
function rulesonleft(expr::RuleNode, path::Vector{Int})
function rulesonleft(expr::RuleNode, path::Vector{<:Integer})
if isempty(expr.children)
# if the encoutered node is terminal or non-expanded non-terminal, return node id
Set{Int}(expr.ind)
Set{Integer}(expr.ind)
elseif isempty(path)
# if path is empty, collect the entire subtree
ruleset = Set{Int}(expr.ind)
ruleset = Set{Integer}(expr.ind)
for ch in expr.children
union!(ruleset, rulesonleft(ch, Vector{Int}()))
union!(ruleset, rulesonleft(ch, Vector{Integer}()))
end
return ruleset
elseif length(path) == 1
# if there is only one element left in the path, collect all children except the one indicated in the path
ruleset = Set{Int}(expr.ind)
ruleset = Set{Integer}(expr.ind)
for i in 1:(path[begin] - 1)
union!(ruleset, rulesonleft(expr.children[i], Vector{Int}()))
union!(ruleset, rulesonleft(expr.children[i], Vector{Integer}()))
end
return ruleset
else
# collect all subtrees up to the child indexed in the path
ruleset = Set{Int}(expr.ind)
ruleset = Set{Integer}(expr.ind)
for i in 1:(path[begin] - 1)
union!(ruleset, rulesonleft(expr.children[i], Vector{Int}()))
union!(ruleset, rulesonleft(expr.children[i], Vector{Integer}()))
end
union!(ruleset, rulesonleft(expr.children[path[begin]], path[2:end]))
return ruleset
end
end

rulesonleft(h::AbstractHole, loc::Vector{Int}) = Set{Int}(findall(h.domain))
rulesonleft(h::AbstractHole, loc::Vector{<:Integer}) = Set{Integer}(findall(h.domain))

"""
get_node_at_location(root::AbstractRuleNode, location::Vector{Int})
get_node_at_location(root::AbstractRuleNode, location::Vector{Integer})

Retrieves a [`RuleNode`](@ref) at the given location by reference.
"""
function get_node_at_location(root::AbstractRuleNode, location::Vector{Int})
function get_node_at_location(root::AbstractRuleNode, location::Vector{<:Integer})
if location == []
return root
else
Expand All @@ -453,11 +460,11 @@ function get_node_at_location(root::AbstractRuleNode, location::Vector{Int})
end

"""
get_node_at_location(root::Hole, location::Vector{Int})
get_node_at_location(root::Hole, location::Vector{Integer})

Retrieves the current hole, if location is this very hole. Throws error otherwise.
"""
function get_node_at_location(root::Hole, location::Vector{Int})
function get_node_at_location(root::Hole, location::Vector{<:Integer})
if location == []
return root
end
Expand All @@ -470,9 +477,9 @@ end
Returns the path from the `root` to the `targetnode`. Returns nothing if no path exists.
"""
function get_path(
root::AbstractRuleNode, targetnode::AbstractRuleNode)::Union{Vector{Int}, Nothing}
root::AbstractRuleNode, targetnode::AbstractRuleNode)::Union{Vector{<:Integer}, Nothing}
if root === targetnode
return Vector{Int}()
return Vector{Integer}()
end
for (i, child) in enumerate(get_children(root))
path = get_path(child, targetnode)
Expand All @@ -484,7 +491,7 @@ function get_path(
end

"""
number_of_holes(rn::AbstractRuleNode)::Int
number_of_holes(rn::AbstractRuleNode)::Integer

Recursively counts the number of holes in an [`AbstractRuleNode`](@ref)
"""
Expand Down Expand Up @@ -592,3 +599,19 @@ function have_same_shape(node1, node2)
end
return true
end


"""
smallest_Int_type(n::Integer)

Finds the smallest type for the given integer and returns it.
"""
function smallest_Int_type(n::Integer)
if typemin(UInt8) ≤ n ≤ typemax(UInt8)
return UInt8
elseif typemin(UInt16) ≤ n ≤ typemax(UInt16)
return UInt16
else
return Int
end
end
9 changes: 9 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -348,4 +348,13 @@ using Test
@test g[:A] == [1]
end
end

@testset "Utils" begin
@testset "smallest_Int_type" begin
@test HerbCore.smallest_Int_type(255) == UInt8
@test HerbCore.smallest_Int_type(256) == UInt16
@test HerbCore.smallest_Int_type(65536) == Int
@test HerbCore.smallest_Int_type(-1) == Int
end
end
end