diff --git a/Project.toml b/Project.toml index 18de09b..829f113 100644 --- a/Project.toml +++ b/Project.toml @@ -1,16 +1,16 @@ name = "HerbConstraints" uuid = "1fa96474-3206-4513-b4fa-23913f296dfc" authors = ["Jaap de Jong "] -version = "0.1.0" +version = "0.1.1" [deps] HerbGrammar = "4ef9e186-2fe5-4b24-8de7-9f7291f24af7" HerbCore = "2b23ba43-8213-43cb-b5ea-38c12b45bd45" [compat] -julia = "1.8" -HerbCore = "0.1.0" -HerbGrammar = "0.1.0" +HerbCore = "^0.2.0" +HerbGrammar = "^0.2.0" +julia = "^1.8" [extras] Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" diff --git a/README.md b/README.md index 00a87e2..50f8a55 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ # HerbConstraints.jl + +[![Build Status](https://github.com/Herb-AI/HerbConstraints.jl/actions/workflows/CI.yml/badge.svg?branch=master)](https://github.com/Herb-AI/HerbConstraints.jl/actions/workflows/CI.yml?query=branch%3Amaster) + This package contains the functionality to formulate, represent and use constraints within `Herb`. Constraints are formulated as context-sensitive grammars. Further, they are divided into global and local constraints, that may be partially mapped to each other. `HerbConstraints.jl` provides functionality to propagate constraints and match constraint patterns efficiently. Further, provides error handling through `matchfail` and `matchnode`. - -[![Build Status](https://github.com/Herb-AI/HerbConstraints.jl/actions/workflows/CI.yml/badge.svg?branch=master)](https://github.com/Herb-AI/HerbConstraints.jl/actions/workflows/CI.yml?query=branch%3Amaster) - diff --git a/src/HerbConstraints.jl b/src/HerbConstraints.jl index 06827c9..38fc29d 100644 --- a/src/HerbConstraints.jl +++ b/src/HerbConstraints.jl @@ -10,7 +10,7 @@ Abstract type representing all propagator constraints. Each propagator constraint has an implementation of a [`propagate`](@ref)-function that takes - the [`PropagatorConstraint`](@ref) -- a [`Grammar`](@ref) +- a [`AbstractGrammar`](@ref) - a [`GrammarContext`](@ref), which most importantly contains the tree and the location in the tree where propagation should take place. - The `domain` which the [`propagate`](@ref)-function prunes. @@ -31,7 +31,7 @@ Each local constraint contains a `path` to a specific location in the tree. Each local constraint has an implementation of a [`propagate`](@ref)-function that takes - the [`LocalConstraint`](@ref) -- a [`Grammar`](@ref) +- a [`AbstractGrammar`](@ref) - a [`GrammarContext`](@ref), which most importantly contains the tree and the location in the tree where propagation should take place. - The `domain` which the [`propagate`](@ref)-function prunes. diff --git a/src/localconstraints/local_condition.jl b/src/localconstraints/local_condition.jl index 99af0a0..26faf4a 100644 --- a/src/localconstraints/local_condition.jl +++ b/src/localconstraints/local_condition.jl @@ -1,12 +1,29 @@ +""" + LocalCondition <: LocalConstraint + +Forbids any subtree that matches the pattern defined by `tree` and where the +[`RuleNode`](@ref) that is matched to the variable in the pattern violates the +predicate given by the `condition` function. + +The `condition` function takes a `RuleNode` tree and should return a `Bool`. + +This constraint is only enforced at the location defined by `path`. +Use a `Condition` constraint for enforcing this throughout the entire search space. +""" mutable struct LocalCondition <: LocalConstraint path::Vector{Int} tree::AbstractMatchNode condition::Function end +""" + propagate(c::LocalCondition, ::AbstractGrammar, context::GrammarContext, domain::Vector{Int}, filled_hole::Union{HoleReference, Nothing}) + +Propagates the [`LocalCondition`](@ref) constraint. +""" function propagate( c::LocalCondition, - ::Grammar, + ::AbstractGrammar, context::GrammarContext, domain::Vector{Int}, filled_hole::Union{HoleReference, Nothing} diff --git a/src/localconstraints/local_forbidden.jl b/src/localconstraints/local_forbidden.jl index 821cb68..2d78909 100644 --- a/src/localconstraints/local_forbidden.jl +++ b/src/localconstraints/local_forbidden.jl @@ -18,7 +18,7 @@ the RuleNode at the given path match the pattern defined by the MatchNode. """ function propagate( c::LocalForbidden, - ::Grammar, + ::AbstractGrammar, context::GrammarContext, domain::Vector{Int}, filled_hole::Union{HoleReference, Nothing} diff --git a/src/localconstraints/local_one_of.jl b/src/localconstraints/local_one_of.jl index cb6624f..89c8564 100644 --- a/src/localconstraints/local_one_of.jl +++ b/src/localconstraints/local_one_of.jl @@ -13,7 +13,7 @@ It enforces that at least one of its given constraints hold. """ function propagate( c::LocalOneOf, - g::Grammar, + g::AbstractGrammar, context::GrammarContext, domain::Vector{Int}, filled_hole::Union{HoleReference, Nothing} diff --git a/src/localconstraints/local_ordered.jl b/src/localconstraints/local_ordered.jl index 39d1aac..ff2d985 100644 --- a/src/localconstraints/local_ordered.jl +++ b/src/localconstraints/local_ordered.jl @@ -16,7 +16,7 @@ constraint. """ function propagate( c::LocalOrdered, - ::Grammar, + ::AbstractGrammar, context::GrammarContext, domain::Vector{Int}, filled_hole::Union{HoleReference, Nothing} diff --git a/src/matchnode.jl b/src/matchnode.jl index e6551ab..c3a693a 100644 --- a/src/matchnode.jl +++ b/src/matchnode.jl @@ -78,12 +78,12 @@ contains_var(mn::MatchNode, var::Symbol) = any(contains_var(c, var) for c ∈ mn """ - matchnode2expr(pattern::MatchNode, grammar::Grammar) + matchnode2expr(pattern::MatchNode, grammar::AbstractGrammar) Converts a MatchNode tree into a Julia expression. This is primarily useful for pretty-printing a pattern. Returns the corresponding expression. """ -function matchnode2expr(pattern::MatchNode, grammar::Grammar) +function matchnode2expr(pattern::MatchNode, grammar::AbstractGrammar) root = deepcopy(grammar.rules[pattern.rule_ind]) if !grammar.isterminal[pattern.rule_ind] # not terminal root,_ = _matchnode2expr(root, pattern, grammar) @@ -92,21 +92,21 @@ function matchnode2expr(pattern::MatchNode, grammar::Grammar) end """ - matchnode2expr(pattern::MatchVar, grammar::Grammar) + matchnode2expr(pattern::MatchVar, grammar::AbstractGrammar) Converts a MatchVar into an expression by returning the variable directly. This is primarily useful for pretty-printing a pattern. """ -function matchnode2expr(pattern::MatchVar, ::Grammar) +function matchnode2expr(pattern::MatchVar, ::AbstractGrammar) return pattern.var_name end """ - _matchnode2expr(expr::Expr, pattern::MatchNode, grammar::Grammar, j=0) + _matchnode2expr(expr::Expr, pattern::MatchNode, grammar::AbstractGrammar, j=0) Internal function for [`matchnode2expr`](@ref), recursively iterating over a matched pattern and converting it to an expression. This is primarily useful for pretty-printing a pattern. Returns the corresponding expression and the current child index. """ -function _matchnode2expr(expr::Expr, pattern::MatchNode, grammar::Grammar, j=0) +function _matchnode2expr(expr::Expr, pattern::MatchNode, grammar::AbstractGrammar, j=0) for (k,arg) ∈ enumerate(expr.args) if isa(arg, Expr) expr.args[k],j = _matchnode2expr(arg, pattern, grammar, j) @@ -127,11 +127,11 @@ end """ - _matchnode2expr(expr::Expr, pattern::MatchVar, grammar::Grammar, j=0) + _matchnode2expr(expr::Expr, pattern::MatchVar, grammar::AbstractGrammar, j=0) Internal function for [`matchnode2expr`](@ref), recursively iterating over a matched variable and converting it to an expression. This is primarily useful for pretty-printing a pattern. Returns the corresponding expression and the current child index. """ -function _matchnode2expr(expr::Expr, pattern::MatchVar, grammar::Grammar, j=0) +function _matchnode2expr(expr::Expr, pattern::MatchVar, grammar::AbstractGrammar, j=0) for (k,arg) ∈ enumerate(expr.args) if isa(arg, Expr) expr.args[k],j = _matchnode2expr(arg, pattern, grammar, j) @@ -148,11 +148,11 @@ end """ - _matchnode2expr(typ::Symbol, pattern::MatchNode, grammar::Grammar, j=0) + _matchnode2expr(typ::Symbol, pattern::MatchNode, grammar::AbstractGrammar, j=0) Internal function for [`matchnode2expr`](@ref), returning the matched translated symbol. This is primarily useful for pretty-printing a pattern. Returns the corresponding expression, i.e. the variable name and the current child index. """ -function _matchnode2expr(typ::Symbol, pattern::MatchNode, grammar::Grammar, j=0) +function _matchnode2expr(typ::Symbol, pattern::MatchNode, grammar::AbstractGrammar, j=0) retval = typ if haskey(grammar.bytype, typ) child = pattern.children[1] @@ -170,11 +170,11 @@ end """ - _matchnode2expr(typ::Symbol, pattern::MatchVar, grammar::Grammar, j=0) + _matchnode2expr(typ::Symbol, pattern::MatchVar, grammar::AbstractGrammar, j=0) Internal function for [`matchnode2expr`](@ref). This is primarily useful for pretty-printing a pattern. Returns the corresponding expression, i.e. the variable name and the current child index. """ -function _matchnode2expr(typ::Symbol, pattern::MatchVar, grammar::Grammar, j=0) +function _matchnode2expr(typ::Symbol, pattern::MatchVar, grammar::AbstractGrammar, j=0) return pattern.var_name, j end diff --git a/src/propagatorconstraints/comesafter.jl b/src/propagatorconstraints/comesafter.jl index 0908a16..7762169 100644 --- a/src/propagatorconstraints/comesafter.jl +++ b/src/propagatorconstraints/comesafter.jl @@ -34,7 +34,7 @@ Creates a [`ComesAfter`](@ref) constraint with only a single `predecessor`. ComesAfter(rule::Int, predecessor::Int) = ComesAfter(rule, [predecessor]) """ - propagate(c::ComesAfter, ::Grammar, context::GrammarContext, domain::Vector{Int})::Tuple{Vector{Int}, Vector{LocalConstraint}} + propagate(c::ComesAfter, ::AbstractGrammar, context::GrammarContext, domain::Vector{Int})::Tuple{Vector{Int}, Vector{LocalConstraint}} Propagates the [`ComesAfter`](@ref) [`PropagatorConstraint`](@ref). Rules in the domain that would violate the [`ComesAfter`](@ref) constraint are removed. @@ -42,7 +42,7 @@ Rules in the domain that would violate the [`ComesAfter`](@ref) constraint are r function propagate( c::ComesAfter, - ::Grammar, + ::AbstractGrammar, context::GrammarContext, domain::Vector{Int}, filled_hole::Union{HoleReference, Nothing} @@ -69,7 +69,7 @@ end """ Checks if the given tree abides the constraint. """ -function check_tree(c::ComesAfter, g::Grammar, tree::AbstractRuleNode)::Bool +function check_tree(c::ComesAfter, g::AbstractGrammar, tree::AbstractRuleNode)::Bool @warn "ComesAfter.check_tree not implemented!" return true diff --git a/src/propagatorconstraints/condition.jl b/src/propagatorconstraints/condition.jl index 0c314ed..d30cda2 100644 --- a/src/propagatorconstraints/condition.jl +++ b/src/propagatorconstraints/condition.jl @@ -1,12 +1,40 @@ +""" + Condition <: PropagatorConstraint + +This [`PropagatorConstraint`](@ref) forbids any subtree that matches the pattern defined by `tree` +and where the [`RuleNode`](@ref) that is matched to the variable in the pattern violates the predicate given by +the `condition` function. + +The `condition` function takes a `RuleNode` tree and should return a `Bool`. + +!!! warning + The [`Condition`](@ref) constraint makes use of [`LocalConstraint`](@ref)s to make sure that constraints + are also enforced in the future when the context of a [`Hole`](@ref) changes. + Therefore, [`Condition`](@ref) can only be used in implementations that keep track of the + [`LocalConstraint`](@ref)s and propagate them at the right moments. +""" struct Condition <: PropagatorConstraint tree::AbstractMatchNode condition::Function end +""" + propagate(c::Condition, g::AbstractGrammar, context::GrammarContext, domain::Vector{Int})::Tuple{Vector{Int}, Vector{LocalConstraint}} + +Propagates the [`Condition`](@ref) constraint. +Rules that violate the [`Condition`](@ref) constraint are removed from the domain. + +!!! warning + The [`Condition`](@ref) constraint makes use of [`LocalConstraint`](@ref)s to make sure that constraints + are also enforced in the future when the context of a [`Hole`](@ref) changes. + Therefore, [`Condition`](@ref) can only be used in implementations that keep track of the + [`LocalConstraint`](@ref)s and propagate them at the right moments. +""" + function propagate( c::Condition, - g::Grammar, + g::AbstractGrammar, context::GrammarContext, domain::Vector{Int}, filled_hole::Union{HoleReference, Nothing} @@ -27,7 +55,7 @@ end """ Checks if the given tree abides the constraint. """ -function check_tree(c::Condition, g::Grammar, tree::RuleNode)::Bool +function check_tree(c::Condition, g::AbstractGrammar, tree::RuleNode)::Bool vars = Dict{Symbol, AbstractRuleNode}() # Return false if the node fits the pattern, but not the condition @@ -38,6 +66,6 @@ function check_tree(c::Condition, g::Grammar, tree::RuleNode)::Bool return all(check_tree(c, g, child) for child ∈ tree.children) end -function check_tree(::Condition, ::Grammar, ::Hole)::Bool +function check_tree(::Condition, ::AbstractGrammar, ::Hole)::Bool return false end diff --git a/src/propagatorconstraints/forbidden.jl b/src/propagatorconstraints/forbidden.jl index d840126..aababcf 100644 --- a/src/propagatorconstraints/forbidden.jl +++ b/src/propagatorconstraints/forbidden.jl @@ -4,7 +4,7 @@ This [`PropagatorConstraint`] forbids any subtree that matches the pattern given by `tree` to be generated. A pattern is a tree of [`AbstractMatchNode`](@ref)s. Such a node can either be a [`MatchNode`](@ref), which contains a rule index corresponding to the -rule index in the [`Grammar`](@ref) and the appropriate number of children, similar to [`RuleNode`](@ref)s. +rule index in the [`AbstractGrammar`](@ref) and the appropriate number of children, similar to [`RuleNode`](@ref)s. It can also contain a [`MatchVar`](@ref), which contains a single identifier symbol. A [`MatchVar`](@ref) can match any subtree, but if there are multiple instances of the same variable in the pattern, the matched subtrees must be identical. @@ -31,7 +31,7 @@ end """ - propagate(c::Forbidden, g::Grammar, context::GrammarContext, domain::Vector{Int})::Tuple{Vector{Int}, Vector{LocalConstraint}} + propagate(c::Forbidden, g::AbstractGrammar, context::GrammarContext, domain::Vector{Int})::Tuple{Vector{Int}, Vector{LocalConstraint}} Propagates the [`Forbidden`](@ref) constraint. It removes the rules from the `domain` that would complete the forbidden tree. @@ -44,7 +44,7 @@ It removes the rules from the `domain` that would complete the forbidden tree. """ function propagate( c::Forbidden, - g::Grammar, + g::AbstractGrammar, context::GrammarContext, domain::Vector{Int}, filled_hole::Union{HoleReference, Nothing} @@ -62,11 +62,11 @@ function propagate( end """ - check_tree(c::Forbidden, g::Grammar, tree::RuleNode)::Bool + check_tree(c::Forbidden, g::AbstractGrammar, tree::RuleNode)::Bool Checks if the given [`AbstractRuleNode`](@ref) tree abides the [`Forbidden`](@ref) constraint. """ -function check_tree(c::Forbidden, g::Grammar, tree::RuleNode)::Bool +function check_tree(c::Forbidden, g::AbstractGrammar, tree::RuleNode)::Bool vars = Dict{Symbol, AbstractRuleNode}() if _pattern_match(tree, c.tree, vars) ≡ nothing return false @@ -74,7 +74,7 @@ function check_tree(c::Forbidden, g::Grammar, tree::RuleNode)::Bool return all(check_tree(c, g, child) for child ∈ tree.children) end -function check_tree(c::Forbidden, ::Grammar, tree::Hole)::Bool +function check_tree(c::Forbidden, ::AbstractGrammar, tree::Hole)::Bool vars = Dict{Symbol, AbstractRuleNode}() return _pattern_match(tree, c.tree, vars) !== nothing end diff --git a/src/propagatorconstraints/forbidden_path.jl b/src/propagatorconstraints/forbidden_path.jl index b842c20..76e3f2d 100644 --- a/src/propagatorconstraints/forbidden_path.jl +++ b/src/propagatorconstraints/forbidden_path.jl @@ -24,7 +24,7 @@ It removes the elements from the domain that would complete the forbidden sequen """ function propagate( c::ForbiddenPath, - ::Grammar, + ::AbstractGrammar, context::GrammarContext, domain::Vector{Int}, filled_hole::Union{HoleReference, Nothing} @@ -48,7 +48,7 @@ end """ Checks if the given tree abides the constraint. """ -function check_tree(c::ForbiddenPath, g::Grammar, tree::AbstractRuleNode)::Bool +function check_tree(c::ForbiddenPath, g::AbstractGrammar, tree::AbstractRuleNode)::Bool @warn "ForbiddenPath.check_tree not implemented!" return true diff --git a/src/propagatorconstraints/one_of.jl b/src/propagatorconstraints/one_of.jl index 0fb6c5a..375b6e8 100644 --- a/src/propagatorconstraints/one_of.jl +++ b/src/propagatorconstraints/one_of.jl @@ -15,7 +15,7 @@ It enforces that at least one of its given constraints hold. """ function propagate( c::OneOf, - g::Grammar, + g::AbstractGrammar, context::GrammarContext, domain::Vector{Int}, filled_hole::Union{HoleReference, Nothing} @@ -32,6 +32,6 @@ end """ Checks if the given tree abides the constraint. """ -function check_tree(c::OneOf, g::Grammar, tree::AbstractRuleNode)::Bool +function check_tree(c::OneOf, g::AbstractGrammar, tree::AbstractRuleNode)::Bool return any(check_tree(cons, g, tree) for cons in c.constraints) end diff --git a/src/propagatorconstraints/ordered.jl b/src/propagatorconstraints/ordered.jl index f1e02bf..58fdf86 100644 --- a/src/propagatorconstraints/ordered.jl +++ b/src/propagatorconstraints/ordered.jl @@ -5,7 +5,7 @@ A [`PropagatorConstraint`](@ref) that enforces a specific order in [`MatchVar`]( assignments in the pattern defined by `tree`. A pattern is a tree of [`AbstractMatchNode`](@ref)s. Such a node can either be a [`MatchNode`](@ref), which contains a rule index corresponding to the -rule index in the [`Grammar`](@ref) and the appropriate number of children, similar to [`RuleNode`](@ref)s. +rule index in the [`AbstractGrammar`](@ref) and the appropriate number of children, similar to [`RuleNode`](@ref)s. It can also contain a [`MatchVar`](@ref), which contains a single identifier symbol. A [`MatchVar`](@ref) can match any subtree, but if there are multiple instances of the same variable in the pattern, the matched subtrees must be identical. @@ -38,7 +38,7 @@ end """ - propagate(c::Ordered, g::Grammar, context::GrammarContext, domain::Vector{Int})::Tuple{Vector{Int}, Vector{LocalConstraint}} + propagate(c::Ordered, g::AbstractGrammar, context::GrammarContext, domain::Vector{Int})::Tuple{Vector{Int}, Vector{LocalConstraint}} Propagates the [`Ordered`](@ref) constraint. Any rule that violates the order as defined by the contraint is removed from the `domain`. @@ -51,7 +51,7 @@ Any rule that violates the order as defined by the contraint is removed from the """ function propagate( c::Ordered, - g::Grammar, + g::AbstractGrammar, context::GrammarContext, domain::Vector{Int}, filled_hole::Union{HoleReference, Nothing} @@ -69,11 +69,11 @@ function propagate( end """ - check_tree(c::Ordered, g::Grammar, tree::RuleNode)::Bool + check_tree(c::Ordered, g::AbstractGrammar, tree::RuleNode)::Bool Checks if the given [`AbstractRuleNode`](@ref) tree abides the [`Ordered`](@ref) constraint. """ -function check_tree(c::Ordered, g::Grammar, tree::RuleNode)::Bool +function check_tree(c::Ordered, g::AbstractGrammar, tree::RuleNode)::Bool vars = Dict{Symbol, AbstractRuleNode}() if _pattern_match(tree, c.tree, vars) ≡ nothing # Check variable ordering @@ -84,4 +84,4 @@ function check_tree(c::Ordered, g::Grammar, tree::RuleNode)::Bool return all(check_tree(c, g, child) for child ∈ tree.children) end -check_tree(c::Ordered, g::Grammar, tree::Hole)::Bool = true +check_tree(c::Ordered, g::AbstractGrammar, tree::Hole)::Bool = true diff --git a/src/propagatorconstraints/require_on_left.jl b/src/propagatorconstraints/require_on_left.jl index 8875d3a..cec70c6 100644 --- a/src/propagatorconstraints/require_on_left.jl +++ b/src/propagatorconstraints/require_on_left.jl @@ -14,7 +14,7 @@ predecessor in the left subtree. """ function propagate( c::RequireOnLeft, - ::Grammar, + ::AbstractGrammar, context::GrammarContext, domain::Vector{Int}, filled_hole::Union{HoleReference, Nothing} @@ -43,7 +43,7 @@ end """ Checks if the given tree abides the constraint. """ -function check_tree(c::RequireOnLeft, g::Grammar, tree::AbstractRuleNode)::Bool +function check_tree(c::RequireOnLeft, g::AbstractGrammar, tree::AbstractRuleNode)::Bool @warn "RequireOnLeft.check_tree not implemented!" return true