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