diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 53f53a8..04d73b9 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -19,6 +19,7 @@ jobs: matrix: version: - '1.8' + - '1' - 'nightly' os: - ubuntu-latest @@ -33,3 +34,10 @@ jobs: - uses: julia-actions/cache@v1 - uses: julia-actions/julia-buildpkg@v1 - uses: julia-actions/julia-runtest@v1 + - uses: julia-actions/julia-processcoverage@v1 + - uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: lcov.info + fail_ci_if_error: false + diff --git a/Project.toml b/Project.toml index 55aaedb..c715f9b 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "HerbGrammar" uuid = "4ef9e186-2fe5-4b24-8de7-9f7291f24af7" authors = ["Sebastijan Dumancic ", "Jaap de Jong ", "Nicolae Filat ", "Piotr Cichoń "] -version = "0.2.2" +version = "0.3.0" [deps] AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" diff --git a/README.md b/README.md index 637c3cd..b75d048 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,9 @@ +# HerbGrammar.jl + +[![codecov](https://codecov.io/github/Herb-AI/HerbGrammar.jl/graph/badge.svg?token=IKDBJC5IBK)](https://codecov.io/github/Herb-AI/HerbGrammar.jl) [![Build Status](https://github.com/Herb-AI/HerbGrammar.jl/actions/workflows/CI.yml/badge.svg?branch=master)](https://github.com/Herb-AI/HerbGrammar.jl/actions/workflows/CI.yml?query=branch%3Amaster) [![Dev-Docs](https://img.shields.io/badge/docs-latest-blue.svg)](https://Herb-AI.github.io/Herb.jl/dev) -# HerbGrammar.jl - This package contains functionality for declaring grammars for the Herb Program Synthesis framework. For full documentation please see the [`Herb.jl` documentation](https://herb-ai.github.io/Herb.jl/dev/). diff --git a/src/HerbGrammar.jl b/src/HerbGrammar.jl index a4814da..2e3082c 100644 --- a/src/HerbGrammar.jl +++ b/src/HerbGrammar.jl @@ -66,7 +66,6 @@ export mindepth, containedin, subsequenceof, - has_children, store_csg, read_csg, read_pcsg, diff --git a/src/csg/csg.jl b/src/csg/csg.jl index 0e4c7af..e44ca18 100644 --- a/src/csg/csg.jl +++ b/src/csg/csg.jl @@ -48,6 +48,8 @@ ContextSensitiveGrammar( log_probabilities::Union{Vector{<:Real}, Nothing} ) = ContextSensitiveGrammar(rules, types, isterminal, iseval, bytype, domains, childtypes, bychildtypes, log_probabilities, AbstractConstraint[]) +ContextSensitiveGrammar() = ContextSensitiveGrammar([], [], BitVector[], BitVector[], Dict{Symbol, Vector{Int}}(), Dict{Symbol, BitVector}(), Vector{Vector{Symbol}}(), Vector{BitVector}(), nothing, AbstractConstraint[]) + """ expr2csgrammar(ex::Expr)::ContextSensitiveGrammar @@ -68,31 +70,15 @@ grammar = expr2csgrammar( ``` """ function expr2csgrammar(ex::Expr)::ContextSensitiveGrammar - rules = Any[] - types = Symbol[] - bytype = Dict{Symbol,Vector{Int}}() + grammar = ContextSensitiveGrammar() + for e ∈ ex.args if isa(e, Expr) - if e.head == :(=) - s = e.args[1] # name of return type - rule = e.args[2] # expression? - rvec = Any[] - parse_rule!(rvec, rule) - for r ∈ rvec - push!(rules, r) - push!(types, s) - bytype[s] = push!(get(bytype, s, Int[]), length(rules)) - end - end + add_rule!(grammar, e) end end - alltypes = collect(keys(bytype)) - is_terminal::Vector{Bool} = [isterminal(rule, alltypes) for rule ∈ rules] - is_eval::Vector{Bool} = [iseval(rule) for rule ∈ rules] - childtypes::Vector{Vector{Symbol}} = [get_childtypes(rule, alltypes) for rule ∈ rules] - domains = Dict(type => BitArray(r ∈ bytype[type] for r ∈ 1:length(rules)) for type ∈ alltypes) - bychildtypes = [BitVector([childtypes[i1] == childtypes[i2] for i2 ∈ 1:length(rules)]) for i1 ∈ 1:length(rules)] - return ContextSensitiveGrammar(rules, types, is_terminal, is_eval, bytype, domains, childtypes, bychildtypes, nothing) + + return grammar end @@ -133,6 +119,12 @@ macro csgrammar(ex) return expr2csgrammar(ex) end + +""" + @cfgrammar + +This macro is deprecated and will be removed in future versions. Use [`@csgrammar`](@ref) instead. +""" macro cfgrammar(ex) return expr2csgrammar(ex) end diff --git a/src/grammar_base.jl b/src/grammar_base.jl index 14e140c..ef99883 100644 --- a/src/grammar_base.jl +++ b/src/grammar_base.jl @@ -214,7 +214,7 @@ function add_rule!(g::AbstractGrammar, e::Expr) # Only add a rule if it does not exist yet. Check for existance # with strict equality so that true and 1 are not considered # equal. that means we can't use `in` or `∈` for equality checking. - if !any(r === rule for rule ∈ g.rules) + if !any(r === rule || typeof(r)==Expr && r == rule for rule ∈ g.rules) push!(g.rules, r) push!(g.iseval, iseval(rule)) push!(g.types, s) diff --git a/src/rulenode_operators.jl b/src/rulenode_operators.jl index fc38261..ab5b93d 100644 --- a/src/rulenode_operators.jl +++ b/src/rulenode_operators.jl @@ -152,28 +152,6 @@ end rulesonleft(::Hole, ::Vector{Int}) = Set{Int}() -#TODO: it seems like this function is exactly redefining what is already in HerbCore/rulenode.jl. It can be safely deleted -# """ -# get_node_at_location(root::RuleNode, location::Vector{Int}) - -# Retrieves a [`RuleNode`](@ref) at the given location by reference. -# """ -# function get_node_at_location(root::AbstractRuleNode, location::Vector{Int}) -# if location == [] -# return root -# else -# return get_node_at_location(root.children[location[1]], location[2:end]) -# end -# end - -# function get_node_at_location(root::VariableShapedHole, location::Vector{Int}) -# if location == [] -# return root -# end -# return nothing -# end - - """ rulenode2expr(rulenode::AbstractRuleNode, grammar::AbstractGrammar) @@ -191,10 +169,9 @@ function rulenode2expr(rulenode::AbstractRuleNode, grammar::AbstractGrammar) return root end -function _get_hole_type(hole::Hole, grammar::AbstractGrammar) - #TODO: convert the children of UniformHoles to subexpressions +function _get_hole_type(hole::AbstractHole, grammar::AbstractGrammar) @assert !isfilled(hole) "Hole $(hole) is convertable to an expression. There is no need to represent it using a symbol." - index = findfirst(rulenode.domain) + index = findfirst(hole.domain) return isnothing(index) ? :Nothing : grammar.types[index] end @@ -205,27 +182,26 @@ function _rulenode2expr(expr::Expr, rulenode::AbstractRuleNode, grammar::Abstrac expr.args[k],j = _rulenode2expr(arg, rulenode, grammar, j) elseif haskey(grammar.bytype, arg) child = rulenode.children[j+=1] - if !isfilled(rulenode) - return _get_hole_type(rulenode, grammar) - end - expr.args[k] = hasdynamicvalue(rulenode) ? child._val : deepcopy(grammar.rules[get_rule(child)]) - if !isterminal(grammar, child) - expr.args[k],_ = _rulenode2expr(expr.args[k], child, grammar, 0) + if isfilled(child) + expr.args[k] = hasdynamicvalue(child) ? child._val : deepcopy(grammar.rules[get_rule(child)]) + if !isterminal(grammar, child) + expr.args[k],_ = _rulenode2expr(expr.args[k], child, grammar, 0) + end + else + expr.args[k] = _get_hole_type(child, grammar) end end end - return expr, j end + return expr, j end -function _rulenode2expr(typ::Symbol, rulenode::RuleNode, grammar::AbstractGrammar, j=0) +function _rulenode2expr(typ::Symbol, rulenode::AbstractRuleNode, grammar::AbstractGrammar, j=0) + @assert isfilled(rulenode) "grammar contains a duplicate rule" retval = typ if haskey(grammar.bytype, typ) child = rulenode.children[1] - if isa(child, Hole) - return retval, j - end retval = hasdynamicvalue(rulenode) ? child._val : deepcopy(grammar.rules[get_rule(child)]) if !grammar.isterminal[get_rule(child)] retval,_ = _rulenode2expr(retval, child, grammar, 0) diff --git a/src/utils.jl b/src/utils.jl index b94f439..3eaf4b2 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -150,13 +150,3 @@ function subsequenceof(vec1::Vector{Int}, vec2::Vector{Int}) return get(vec1, vec1_index, nothing) === nothing end - - -""" - has_children(node::RuleNode) - -Returns true if `node` has children -""" -has_children(node::RuleNode) = !isempty(node.children) -has_children(::Hole) = false - diff --git a/test/runtests.jl b/test/runtests.jl index f4a7f0a..b26dd0c 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,7 +1,9 @@ +using HerbCore using HerbGrammar using Test @testset "HerbGrammar.jl" verbose=true begin include("test_csg.jl") include("test_rulenode_operators.jl") + include("test_rulenode2expr.jl") end diff --git a/test/test_csg.jl b/test/test_csg.jl index 1d6fbae..69f7902 100644 --- a/test/test_csg.jl +++ b/test/test_csg.jl @@ -27,6 +27,11 @@ Real = 1 | 2 | 3 end @test g₃.rules == [1,2,3] + + g₄ = @cfgrammar begin + Real = 1 | 1 + end + @test g₄.rules == [1] end @@ -38,6 +43,12 @@ # Basic adding add_rule!(g₁, :(Real = 3)) @test g₁.rules == [1, 2, 3] + @test g₁.bytype[:Real] == [1, 2, 3] + @test g₁.types == [:Real, :Real, :Real] + @test g₁.isterminal == [true, true, true] + @test g₁.iseval == [false, false, false] + @test g₁.childtypes == [[], [], []] + @test g₁.bychildtypes == [BitVector((1, 1, 1)) for _ in 1:3] # Adding multiple rules in one line add_rule!(g₁, :(Real = 4 | 5)) diff --git a/test/test_rulenode2expr.jl b/test/test_rulenode2expr.jl new file mode 100644 index 0000000..54db1b2 --- /dev/null +++ b/test/test_rulenode2expr.jl @@ -0,0 +1,50 @@ +@testset verbose=true "rulenode2expr" begin + grammar = @csgrammar begin + S = 1 + S = x + S = S + S + S = S * S + end + + @testset "RuleNode" begin + node = RuleNode(3, [ + RuleNode(3,[ + RuleNode(1), + RuleNode(1) + ]), + RuleNode(3,[ + RuleNode(2), + RuleNode(1) + ]), + ]) + @test string(rulenode2expr(node, grammar)) == "(1 + 1) + (x + 1)" + end + + @testset "Hole" begin + node = RuleNode(3, [ + RuleNode(3,[ + RuleNode(1), + RuleNode(1) + ]), + RuleNode(3,[ + RuleNode(2), + Hole(get_domain(grammar, :S)) + ]), + ]) + @test string(rulenode2expr(node, grammar)) == "(1 + 1) + (x + S)" + end + + @testset "UniformHole" begin + node = RuleNode(3, [ + RuleNode(3,[ + RuleNode(1), + RuleNode(1) + ]), + UniformHole(BitVector((0, 0, 1, 1)),[ + RuleNode(2), + RuleNode(1) + ]), + ]) + @test string(rulenode2expr(node, grammar)) == "(1 + 1) + S" + end +end