Inspired by what little I know about Boost.Proto I've tried to modify your code to allow for independent ast transformations. This approach uses 4 passes (eliminate_iff, eliminate_imp, distribute_nots and distribute_ors) and in each one it rebuilds the ast. There may be a way to do the same in a single pass, probably with better performance, but I think that approach would be (even) harder to understand.
Explanation of the changes:
The first change is a little gratuitous but I really think that all the phx::construct...
s make the grammar harder to read. The grammar I use is:
iff_ = as_iff[imp_ >> "iff" >> iff_] | imp_;
imp_ = as_imp[or_ >> "imp" >> imp_] | or_;
or_ = as_or[and_ >> "or" >> or_] | and_;
and_ = as_and[not_ >> "and" >> and_] | not_;
not_ = as_not["not" > simple] | simple;
In order to be able to use this you need to adapt unop
and binop
using BOOST_FUSION_ADAPT_TPL_STRUCT
and declare as_xxx
as:
const as<binop<op_xxx>> as_xxx={};
If you don't like this change your original grammar should also work (if you add a using namespace ast;
).
I've put everything related to the AST inside namespace ast
and made a few additions:
enum class expr_type
: the order of its enumerators needs to be kept in synch with the parameters in the variant. It is used to check whether one of a node's children has a particular type.
get_expr_type
: simply returns what is the type of the expression.
printer
: now it just prints the expression passed, without making any transformation. Maybe it could be changed to be smarter about the placing of parentheses.
- operators
!
, &&
and ||
: they are used to make the rebuilding of the AST easier.
And finally the transformations. Every transformation uses ast_helper<Transformation>
as its base. This struct has several reused member functions:
pass_through
: creates a node of the same type that has as members, the result of transforming the original members.
recurse
: applies the transformation to the current node.
left
: gets the first member of a node independently of the type of the node. Gets used in the more complex transformations to slightly help with readability.
child0
: exactly the same as left
, but the name makes more sense in unary nodes.
right
: gets the second member of a node.
eliminate_imp :
This one is really easy:
- If you get a
binop<op_imp>
return !p || q
. Where p
and q
are the result of applying the transformation to the first and second operands respectively.
- If you get anything else return a node of the same kind applying the transformation to its operands(pass_through).
eliminate_iff :
It's basically the same, changing binop<op_iff>
with (p || !q)&&(!p || q)
.
distribute_nots :
distribute_ors :
- If it's anything but an or, pass_through.
- If it's an or:
- Check whether its first operand is an
and
. If it is distribute the or
s and apply the transformation again in case another or->and
is there.
- Check whether its second operand is an
and
. Do the analogous work.
- If neither direct child is an
and
, check recursively if there is any and
in the subtree starting with this node. If there is it'll end up floating to the top so we'll need to recurse on the pass_through.
- If there isn't any
and
in the subtree, it is already in CNF and simply pass_through.
Running on Ideone
Full Code:
#include <boost/spirit/include/qi.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/variant/recursive_wrapper.hpp>
namespace qi = boost::spirit::qi;
// Abstract data type
struct op_or {};
struct op_and {};
struct op_imp {};
struct op_iff {};
struct op_not {};
namespace ast
{
typedef std::string var;
template <typename tag> struct binop;
template <typename tag> struct unop;
enum class expr_type { var = 0, not_, and_, or_, imp, iff };
typedef boost::variant<var,
boost::recursive_wrapper<unop <op_not> >,
boost::recursive_wrapper<binop<op_and> >,
boost::recursive_wrapper<binop<op_or> >,
boost::recursive_wrapper<binop<op_imp> >,
boost::recursive_wrapper<binop<op_iff> >
> expr;
expr_type get_expr_type(const expr& expression)
{
return static_cast<expr_type>(expression.which());
}
template <typename tag> struct binop
{
expr oper1, oper2;
};
template <typename tag> struct unop
{
expr oper1;
};
struct printer : boost::static_visitor<void>
{
printer(std::ostream& os) : _os(os) {}
std::ostream& _os;
mutable bool first{ true };
//
void operator()(const ast::var& v) const { _os << v; }
void operator()(const ast::binop<op_and>& b) const { print(" and ", b.oper1, b.oper2); }
void operator()(const ast::binop<op_or>& b) const { print(" or ", b.oper1, b.oper2); }
void operator()(const ast::binop<op_iff>& b) const { print(" iff ", b.oper1, b.oper2); }
void operator()(const ast::binop<op_imp>& b) const { print(" imp ", b.oper1, b.oper2); }
void print(const std::string& op, const ast::expr& l, const ast::expr& r) const
{
_os << "(";
boost::apply_visitor(*this, l);
_os << op;
boost::apply_visitor(*this, r);
_os << ")";
}
void operator()(const ast::unop<op_not>& u) const
{
_os << "not(";
boost::apply_visitor(*this, u.oper1);
_os << ")";
}
};
std::ostream& operator<<(std::ostream& os, const expr& e)
{
boost::apply_visitor(printer(os), e); return os;
}
expr operator!(const expr& e)
{
return unop<op_not>{e};
}
expr operator||(const expr& l, const expr& r)
{
return binop<op_or>{l, r};
}
expr operator&&(const expr& l, const expr& r)
{
return binop<op_and>{l, r};
}
}
BOOST_FUSION_ADAPT_TPL_STRUCT(
(Tag),
(ast::binop) (Tag),
(ast::expr, oper1)
(ast::expr, oper2)
)
BOOST_FUSION_ADAPT_TPL_STRUCT(
(Tag),
(ast::unop) (Tag),
(ast::expr, oper1)
)
// Grammar rules
template <typename It, typename Skipper = qi::space_type>
struct parser : qi::grammar<It, ast::expr(), Skipper>
{
parser() : parser::base_type(expr_)
{
using namespace qi;
const as<ast::binop<op_iff> > as_iff = {};
const as<ast::binop<op_imp> > as_imp = {};
const as<ast::binop<op_or> > as_or = {};
const as<ast::binop<op_and> > as_and = {};
const as<ast::unop<op_not> > as_not = {};
expr_ = iff_.alias();
iff_ = as_iff[imp_ >> "iff" >> iff_] | imp_;
imp_ = as_imp[or_ >> "imp" >> imp_] | or_;
or_ = as_or[and_ >> "or" >> or_] | and_;
and_ = as_and[not_ >> "and" >> and_] | not_;
not_ = as_not["not" > simple] | simple;
simple = (('(' > expr_ > ')') | var_);
var_ = qi::lexeme[+alpha];
BOOST_SPIRIT_DEBUG_NODE(expr_);
BOOST_SPIRIT_DEBUG_NODE(iff_);
BOOST_SPIRIT_DEBUG_NODE(imp_);
BOOST_SPIRIT_DEBUG_NODE(or_);
BOOST_SPIRIT_DEBUG_NODE(and_);
BOOST_SPIRIT_DEBUG_NODE(not_);
BOOST_SPIRIT_DEBUG_NODE(simple);
BOOST_SPIRIT_DEBUG_NODE(var_);
}
private:
qi::rule<It, ast::var(), Skipper> var_;
qi::rule<It, ast::expr(), Skipper> not_, and_, or_, imp_, iff_, simple, expr_;
};
template <typename Transform>
struct ast_helper : boost::static_visitor<ast::expr>
{
template <typename Tag>
ast::expr pass_through(const ast::binop<Tag>& op) const
{
return ast::binop<Tag>{recurse(op.oper1), recurse(op.oper2)};
}
template <typename Tag>
ast::expr pass_through(const ast::unop<Tag>& op) const
{
return ast::unop<Tag>{recurse(op.oper1)};
}
ast::expr pass_through(const ast::var& variable) const
{
return variable;
}
ast::expr recurse(const ast::expr& expression) const
{
return boost::apply_visitor(Transform{}, expression);
}
struct left_getter:boost::static_visitor<ast::expr>
{
template< template<class> class Op,typename Tag>
ast::expr operator()(const Op<Tag>& op) const
{
return op.oper1;
}
ast::expr operator()(const ast::var&) const
{
return{};//throw something?
}
};
ast::expr left(const ast::expr& expression) const
{
return boost::apply_visitor(left_getter{}, expression);
}
ast::expr child0(const ast::expr& expression) const
{
return left(expression);
}
struct right_getter :boost::static_visitor<ast::expr>
{
template<typename Tag>
ast::expr operator()(const ast::binop<Tag>& op) const
{
return op.oper2;
}
template<typename Expr>
ast::expr operator()(const Expr&) const
{
return{};//throw something?
}
};
ast::expr right(const ast::expr& expression) const
{
return boost::apply_visitor(right_getter{}, expression);
}
};
struct eliminate_imp : ast_helper<eliminate_imp>
{
template <typename Op>
ast::expr operator()(const Op& op) const
{
return pass_through(op);
}
ast::expr operator()(const ast::binop<op_imp>& imp) const
{
return !recurse(imp.oper1) || recurse(imp.oper2);
}
ast::expr operator()(const ast::expr& expression) const
{
return recurse(expression);
}
};
struct eliminate_iff : ast_helper<eliminate_iff>
{
template <typename Op>
ast::expr operator()(const Op& op) const
{
return pass_through(op);
}
ast::expr operator()(const ast::binop<op_iff>& imp) const
{
return (recurse(imp.oper1) || !recurse(imp.oper2)) && (!recurse(imp.oper1) || recurse(imp.oper2));
}
ast::expr operator()(const ast::expr& expression) const
{
return recurse(expression);
}
};
struct distribute_nots : ast_helper<distribute_nots>
{
template <typename Op>
ast::expr operator()(const Op& op) const
{
return pass_through(op);
}
ast::expr operator()(const ast::unop<op_not>& not_) const
{
switch (ast::get_expr_type(not_.oper1)) //There is probably a better solution
{
case ast::expr_type::and_:
return recurse(!recurse(left(not_.oper1))) || recurse(!recurse(right(not_.oper1)));
case ast::expr_type::or_:
return recurse(!recurse(left(not_.oper1))) && recurse(!recurse(right(not_.oper1)));
case ast::expr_type::not_:
return recurse(child0(not_.oper1));
default:
return pass_through(not_);
}
}
ast::expr operator()(const ast::expr& expression) const
{
return recurse(expression);
}
};
struct any_and_inside : boost::static_visitor<bool>
{
any_and_inside(const ast::expr& expression) :expression(expression) {}
const ast::expr& expression;
bool operator()(const ast::var&) const
{
return false;
}
template <typename Tag>
bool operator()(const ast::binop<Tag>& op) const
{
return boost::apply_visitor(*this, op.oper1) || boost::apply_visitor(*this, op.oper2);
}
bool operator()(const ast::binop<op_and>&) const
{
return true;
}
template<typename Tag>
bool operator()(const ast::unop<Tag>& op) const
{
return boost::apply_visitor(*this, op.oper1);
}
explicit operator bool() const
{
return boost::apply_visitor(*this, expression);
}
};
struct distribute_ors : ast_helper<distribute_ors>
{
template <typename Op>
ast::expr operator()(const Op& op) const
{
return pass_through(op);
}
ast::expr operator()(const ast::binop<op_or>& or_) const
{
if (ast::get_expr_type(or_.oper1) == ast::expr_type::and_)
{
return recurse(recurse(left(or_.oper1)) || recurse(or_.oper2))
&& recurse(recurse(right(or_.oper1)) || recurse(or_.oper2));
}
else if (ast::get_expr_type(or_.oper2) == ast::expr_type::and_)
{
return recurse(recurse(or_.oper1) || recurse(left(or_.oper2)))
&& recurse(recurse(or_.oper1) || recurse(right(or_.oper2)));
}
else if (any_and_inside( or_ ))
{
return recurse(recurse(or_.oper1) || recurse(or_.oper2));
}
else
{
return pass_through(or_);
}
}
ast::expr operator()(const ast::expr& expression) const
{
return recurse(expression);
}
};
ast::expr to_CNF(const ast::expr& expression)
{
return distribute_ors()(distribute_nots()(eliminate_iff()(eliminate_imp()(expression))));
}
// Test some examples in main and check the order of precedence
int main()
{
for (auto& input : std::list<std::string>{
// Test the order of precedence
"(a and b) imp ((c and d) or (a and b));",
"a and b iff (c and d or a and b);",
"a and b imp (c and d or a and b);",
"not a or not b;",
"a or b;",
"not a and b;",
"not (a and b);",
"a or b or c;",
"aaa imp bbb iff ccc;",
"aaa iff bbb imp ccc;",
// Test elimination of equivalences
"a iff b;",
"a iff b or c;",
"a or b iff b;",
"a iff b iff c;",
// Test elimination of implications
"p imp q;",
"p imp not q;",
"not p imp not q;",
"p imp q and r;",
"p imp q imp r;"
})
{
auto f(std::begin(input)), l(std::end(input));
parser<decltype(f)> p;
try
{
ast::expr result;
bool ok = qi::phrase_parse(f, l, p > ';', qi::space, result);
if (!ok)
std::cerr << "invalid input\n";
else
{
std::cout << "original: " << result << "\n";
std::cout << "CNF: " << to_CNF(result) << "\n";
}
}
catch (const qi::expectation_failure<decltype(f)>& e)
{
std::cerr << "expectation_failure at '" << std::string(e.first, e.last) << "'\n";
}
if (f != l) std::cerr << "unparsed: '" << std::string(f, l) << "'\n";
}
return 0;
}