examples/bool_expr/bool_expr.rb in sexpr-0.3.0 vs examples/bool_expr/bool_expr.rb in sexpr-0.4.0

- old
+ new

@@ -1,6 +1,7 @@ require 'sexpr' +require 'citrus' # Let load the grammar from the .yml definition file. BoolExpr = Sexpr.load File.expand_path('../bool_expr.sexp.yml', __FILE__) # A Sexpr grammar is simply a module. Ruby allows us to re-open it later. @@ -26,19 +27,48 @@ def mod2rulename(mod) rule = super (rule.to_s =~ /^bool_(.*)$/) ? const_get($1.to_sym) : rule end -end + # This class pushes `[:not, ...]` as far as possible in boolean expressions. + # It provides an example of s-expression rewriter + class NotPushProcessor < Sexpr::Rewriter + # Let the default implementation know that we are working on the BoolExpr + # grammar. This way, all rewriting results will automatically be tagged + # with the correct modules above (And, Not, ...) + grammar BoolExpr + + # The main rewriting rule, that pushes a NOT according to the different + # cases + def on_bool_not(sexpr) + case expr = sexpr.last + when And then call [:bool_or, [:bool_not, expr[1]], [:bool_not, expr[2]] ] + when Or then call [:bool_and, [:bool_not, expr[1]], [:bool_not, expr[2]] ] + when Not then call expr.last + when Lit then [:bool_lit, !expr.last] + else + sexpr + end + end + + # By default, we simply copy the node and apply rewriting rules on children + alias :on_missing :copy_and_apply + + end # class NotPushProcessor + +end # module BoolExpr + describe BoolExpr do subject{ BoolExpr } describe "the parsing feature" do it 'parses boolean expressions without error' do subject.parse("x and y").should be_a(Citrus::Match) + subject.parse("not(y)").should be_a(Citrus::Match) + subject.parse("not(true)").should be_a(Citrus::Match) end it 'provides a shortcut to get s-expressions directly' do subject.sexpr("x and y").should eq([:bool_and, [:var_ref, "x"], [:var_ref, "y"]]) end @@ -80,7 +110,44 @@ subject[:bool_lit].match?([:bool_lit, true]).should be_true subject[:bool_and].match?([:bool_lit, true]).should be_false end end # validating + + describe BoolExpr::NotPushProcessor do + + def _(expr) + BoolExpr.sexpr(expr) + end + + def rw(expr) + BoolExpr::NotPushProcessor.new.call(expr) + end + + it 'does nothing on variable references' do + rw("not x").should eq([:bool_not, [:var_ref, "x"]]) + end + + it 'rewrites literals through negating them' do + rw("not true").should eq(_ "false") + rw("not false").should eq(_ "true") + end + + it 'rewrites not through removing them' do + rw("not not true").should eq(_ "true") + end + + it 'rewrites or through and of negated terms' do + rw("not(x or y)").should eq(_ "not(x) and not(y)") + end + + it 'rewrites and through or of negated terms' do + rw("not(x and y)").should eq(_ "not(x) or not(y)") + end + + it 'rewrites recursively' do + rw("not(x and not(y))").should eq(_ "not(x) or y") + end + + end # rewriting end if defined?(RSpec) \ No newline at end of file