在C ++中将逻辑公式转换为连接法线形式

时间:2015-12-23 14:23:38

标签: c++ recursion logic boost-spirit conjunctive-normal-form

我将使用Boots / Spirit在C ++中实现CNF生成器。但在完成后#34;优先顺序"和"消除等价&影响"这两部分,我无法弄清楚如何实现"向内移动NOTs"和"通过ANDs"向内发布OR。

此处记录了所需的输出: https://en.wikipedia.org/wiki/Conjunctive_normal_form

以下是更详细的说明:

优先顺序:

NOT > AND > OR > IMP > IFF

输入示例:

A iff B imp C

现在输出是:

(A or not ( not B or C)) and ( not A or ( not B or C))

代码(我在打印机部分实现输出):

#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>
#include <boost/variant/recursive_wrapper.hpp>

namespace qi    = boost::spirit::qi;
namespace phx   = boost::phoenix;



// Abstract data type

struct op_or  {};
struct op_and {};
struct op_imp {};
struct op_iff {};
struct op_not {};

typedef std::string var;
template <typename tag> struct binop;
template <typename tag> struct unop;

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;

template <typename tag> struct binop
{
  explicit binop(const expr& l, const expr& r) : oper1(l), oper2(r) { }
  expr oper1, oper2;
};

template <typename tag> struct unop
{
  explicit unop(const expr& o) : oper1(o) { }
  expr oper1;
};



// Operating on the syntax tree

struct printer : boost::static_visitor<void>
{
  printer(std::ostream& os) : _os(os) {}
  std::ostream& _os;

  //
  void operator()(const var& v) const { _os << v; }

  void operator()(const binop<op_and>& b) const { print(" and ", b.oper1, b.oper2); }
  void operator()(const binop<op_or >& b) const { print(" or ", b.oper1, b.oper2); }
  void operator()(const binop<op_iff>& b) const { eliminate_iff(b.oper1, b.oper2); }
  void operator()(const binop<op_imp>& b) const { eliminate_imp(b.oper1, b.oper2); }

  void print(const std::string& op, const expr& l, const expr& r) const
  {
    _os << "(";
    boost::apply_visitor(*this, l);
    _os << op;
    boost::apply_visitor(*this, r);
    _os << ")";
  }

  void operator()(const unop<op_not>& u) const
  {
    _os << "( not ";
    boost::apply_visitor(*this, u.oper1);
    _os << ")";
  }

  void eliminate_iff(const expr& l, const expr& r) const
  {
    _os << "(";
    boost::apply_visitor(*this, l);
    _os << " or not ";
    boost::apply_visitor(*this, r);
    _os << ") and ( not ";
    boost::apply_visitor(*this, l);
    _os << " or ";
    boost::apply_visitor(*this, r);
    _os << ")";
  }

  void eliminate_imp(const expr& l, const expr& r) const
  {
    _os << "( not ";
    boost::apply_visitor(*this, l);
    _os << " or ";
    boost::apply_visitor(*this, r);
    _os << ")";
  }
};

std::ostream& operator<<(std::ostream& os, const expr& e)
{ boost::apply_visitor(printer(os), e); return os; }



// Grammar rules

template <typename It, typename Skipper = qi::space_type>
struct parser : qi::grammar<It, expr(), Skipper>
{
  parser() : parser::base_type(expr_)
  {
    using namespace qi;

    expr_  = iff_.alias();

    iff_ = (imp_ >> "iff" >> iff_) [ _val = phx::construct<binop<op_iff>>(_1, _2) ] | imp_   [ _val = _1 ];
    imp_ = (or_  >> "imp" >> imp_) [ _val = phx::construct<binop<op_imp>>(_1, _2) ] | or_    [ _val = _1 ];
    or_  = (and_ >> "or"  >> or_ ) [ _val = phx::construct<binop<op_or >>(_1, _2) ] | and_   [ _val = _1 ];
    and_ = (not_ >> "and" >> and_) [ _val = phx::construct<binop<op_and>>(_1, _2) ] | not_   [ _val = _1 ];
    not_ = ("not" > simple       ) [ _val = phx::construct<unop <op_not>>(_1)     ] | simple [ _val = _1 ];

    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, var() , Skipper> var_;
  qi::rule<It, expr(), Skipper> not_, and_, or_, imp_, iff_, simple, expr_;
};




// 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
    {
      expr result;
      bool ok = qi::phrase_parse(f,l,p > ';',qi::space,result);

      if (!ok)
        std::cerr << "invalid input\n";
      else
        std::cout << "result: " << 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;
}

编译命令:

clang++ -std=c++11 -stdlib=libc++ -Weverything CNF_generator.cpp

2 个答案:

答案 0 :(得分:4)

在向AND分配OR之前,应该向内移动:

!(A AND B) ==> (!A OR !B)
!(A OR B) ==> (!A AND !B)

请记住取消执行此操作时发生的任何!!X

同时删除多余的( )

OR分布在AND:

A OR (B AND C) ==> (A OR B) AND (A OR C)

你可能需要减少一些其他的冗余,这些冗余会随着你所做的一切而蔓延,例如(X OR X)

(A or not ( not B or C)) and ( not A or ( not B or C)) ==>
(A or ( not not B and not C)) and ( not A or ( not B or C ) ) ==>
(A or ( B and not C)) and ( not A or not B or C) ==>
( A or B) and (A or {{ 1}}的 not C) )
and ( not A or not B or C) ==>

也许我误解了你的问题并且你已经理解了上述所有的转换,并且你在你创建的结构中执行这些操作时遇到了麻烦。

通过尝试完成打印例程中的所有转换,你当然已经为自己(可能是不可能的)做了些事情。我会解析,然后转换,然后打印。

如果你坚持转换打印例程,那么你可能会错过一些简化,你需要打印才能更加了解CNF的规则。 AND节点可以简单地在其间以AND递归地打印其两侧。但是任何其他节点最先检查它的子节点并有条件地转换到足以在递归调用之前将AND拉到顶部。

你有:

(A or B) and (A or not C) and ( not A or not B or C)

但是您无法从iff一直进入void eliminate_iff(const expr& l, const expr& r) const { _os << "("; boost::apply_visitor(*this, l); _os << " or not "; boost::apply_visitor(*this, r); _os << ") and ( not "; boost::apply_visitor(*this, l); _os << " or "; boost::apply_visitor(*this, r); _os << ")"; } l,但您无法直接生成任何r"not"文字直到你递归到达底部。因此,在打印时转换设计错误时,iff例程需要生成表示("or"或不l)的临时对象,然后调用r处理例程来处理它,然后输出or然后创建一个表示(不是"AND"l)的临时对象,并调用r处理例程来处理它。

同样,or处理例程需要查看每个操作数。如果每个只是最终变量的最终变量或ornot可以简单地将自己发送到流。但是如果任何操作数更复杂,or必须做更复杂的事情。

除了在开始打印之前进行转换之外,还有一些其他方面可能会更改以使代码更简单:

首先,通过让oror个对象各自拥有任意数量的操作数and,而不是一对操作数,可以避免很多麻烦。这个代价很高,你需要一个适合对象的比较函数。但是,回报是值得拥有比较功能的麻烦。 接下来,您可能考虑为所有子表达式设置单一类型,而不是为每个运算符设置类型。因此每个对象必须存储运算符和std::set个操作数。这个设计选择存在一些非常大而明显的缺点,但有一个很大的优势:子表达式可以将自身转换为另一种类型。

更常见的子表达式转换方案(可能仍然是最好的,只考虑替代方案)是子表达式的所有者要求子表达式有条件地生成自身的转换克隆。这比使对象能够直接转换自身更有效。但是正确地获取编码细节需要更多的思考。

这个语法的另一个好选择是在解析时进行所有转换。更复杂的问题确实值得完全拆分解析,转换,打印。但是在这种情况下,如果你通过工厂函数思考,变换可以很好地融入解析:

工厂需要一个操作员和一个(std::set)或两个已经CNF 的子表达式。它产生一个新的CNF表达式:

  • NOT

    • a)两个输入都是AND,形成他们的集合的联合。
    • b)一个输入是AND,将另一个输入插入到该组中。
    • c)两个输入都不是AND,请使用这两个输入创建新的AND
  • AND

    • a)两个输入都是OR,形成他们的集合的联合。
    • b)一个输入是OR,另一个输入是原始输入或OR,将另一个输入插入NOT组。
    • c)至少有一个输入是OR,在AND之间分配另一个输入(分配函数必须处理丑陋的子情况)。
  • AND

    • 原语的反转是微不足道的。 NOT的反转是微不足道的。 NOT的反转非常简单。 OR的反转是整个设计中最丑陋的事情(你需要把整个事情都翻过来)但是可行。为了保持理智,你可能会忘记效率并递归地使用工厂进行ANDNOT操作OR平凡转换(但需要进一步转换才能返回CNF) )。
  • NOT ANDIFF:只需对基本工厂进行适当的多次调用。

答案 1 :(得分:3)

受到我对Boost.Proto的了解很少的启发我试图修改你的代码以允许独立的ast转换。这种方法使用4次传递(eliminate_iff,eliminate_imp,distribute_nots和distribute_ors),并在每次传递中重建ast。可能有一种方法可以在一次通过中做同样的事情,可能具有更好的性能,但我认为这种方法(甚至)更难理解。

更改说明:

第一个变化是有点无偿的,但我真的认为所有phx::construct...都会使语法难以阅读。我使用的语法是:

    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;

为了能够使用此功能,您需要使用unop调整binopBOOST_FUSION_ADAPT_TPL_STRUCT,并将as_xxx声明为:

const as<binop<op_xxx>> as_xxx={};

如果你不喜欢这个改变你的原始语法也应该有效(如果你添加using namespace ast;)。

我已将与AST相关的所有内容放在namespace ast中并添加了一些内容:

  • enum class expr_type:其枚举器的顺序需要与变体中的参数保持同步。它用于检查节点的某个子节点是否具有特定类型。
  • get_expr_type:只返回表达式的类型。
  • printer:现在只打印传递的表达式,而不进行任何转换。也许它可以改为更聪明地放置括号。
  • 运算符!&&||:它们用于简化AST的重建。

最后是转型。每次转换都使用ast_helper<Transformation>作为基础。这个结构有几个重用的成员函数:

  • pass_through:创建一个与成员相同类型的节点,即转换原始成员的结果。
  • recurse:将转换应用于当前节点。
  • left:获取节点的第一个成员,与节点类型无关。在更复杂的转换中使用,以略微提高可读性。
  • child0:与left完全相同,但名称在一元节点中更有意义。
  • right:获取节点的第二个成员。

elimination_imp
这个很简单:

  • 如果您获得binop<op_imp>返回!p || q。其中pq分别是对第一个和第二个操作数应用转换的结果。
  • 如果你得到任何其他东西,则返回一个相同类型的节点,将转换应用于其操作数(pass_through)。

eliminate_iff
它基本相同,用binop<op_iff>更改(p || !q)&&(!p || q)

distribute_nots

  • 如果您收到的任何内容都不是unop<op_not>,只需通过。​​
  • 如果您得到unop<op_not>,请先检查其操作数的类型:

    • 如果是,则用!p || !q替换。
    • 如果是,或者用!p && !q替换。
    • 如果不是,请用p替换。

distribute_ors

  • 如果它只是一个or,pass_through。
  • 如果是或者:
    • 检查其第一个操作数是否为and。如果它分发了or并再次应用转换,以防其他or->and存在。
    • 检查其第二个操作数是否为and。做类似的工作。
    • 如果直接子节点都不是and,则在从该节点开始的子树中有任何and时,递归检查。如果它最终会浮动到顶部,那么我们需要在pass_through上进行递归。
    • 如果子树中没有and,则它已经在CNF中并且只是直通。

Running on Ideone

完整代码:

#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;
}