【问题标题】:Boost.Spirit transform expression to ASTBoost.Spirit 将表达式转换为 AST
【发布时间】:2020-06-26 07:54:45
【问题描述】:

使用 Boost.Spirit 将某些表达式转换为 AST 的正确方法是什么?

我尝试构建它,但我认为它很乱,可以简化很多。

https://godbolt.org/z/VXHXLY

#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>

namespace ast {
  struct unary_operator;
  struct binary_operator;

  struct expression {
    typedef boost::variant<
      double,
      std::string,
      boost::recursive_wrapper<unary_operator>,
      boost::recursive_wrapper<binary_operator>,
      boost::recursive_wrapper<expression>
    > type;

    expression() {

    }

    template<typename Expr>
    expression(const Expr &expr)
      : expr(expr) {

    }

    expression &operator+=(expression rhs);
    expression &operator-=(expression rhs);

    expression &operator*=(expression rhs);
    expression &operator/=(expression rhs);

    expression &and_(expression rhs);
    expression &or_(expression rhs);

    expression &equals(expression rhs);
    expression &not_equals(expression rhs);

    expression &less_than(expression rhs);
    expression &less_equals(expression rhs);
    expression &greater_than(expression rhs);
    expression &greater_equals(expression rhs);

    expression &factor(expression rhs);

    expression &dot(expression rhs);

    type expr;
  };

  struct unary_operator {
    std::string op;
    expression rhs;

    unary_operator() {}

    unary_operator(std::string op, expression rhs)
      : op(std::move(op)), rhs(std::move(rhs)) {
    }
  };

  struct binary_operator {
    std::string op;
    expression lhs;
    expression rhs;

    binary_operator() {}

    binary_operator(std::string op, expression lhs, expression rhs)
      : op(std::move(op)), lhs(std::move(lhs)), rhs(std::move(rhs)) {
    }
  };

  expression &expression::operator+=(expression rhs) {
    expr = binary_operator("+", std::move(expr), std::move(rhs));
    return *this;
  }

  expression &expression::operator-=(expression rhs) {
    expr = binary_operator("-", std::move(expr), std::move(rhs));
    return *this;
  }

  expression &expression::operator*=(expression rhs) {
    expr = binary_operator("*", std::move(expr), std::move(rhs));
    return *this;
  }

  expression &expression::operator/=(expression rhs) {
    expr = binary_operator("/", std::move(expr), std::move(rhs));
    return *this;
  }

  expression &expression::and_(expression rhs) {
    expr = binary_operator("&&", std::move(expr), std::move(rhs));
    return *this;
  }

  expression &expression::or_(expression rhs) {
    expr = binary_operator("||", std::move(expr), std::move(rhs));
    return *this;
  }


  expression &expression::equals(expression rhs) {
    expr = binary_operator("==", std::move(expr), std::move(rhs));
    return *this;
  }

  expression &expression::not_equals(expression rhs) {
    expr = binary_operator("!=", std::move(expr), std::move(rhs));
    return *this;
  }


  expression &expression::less_than(expression rhs) {
    expr = binary_operator("<", std::move(expr), std::move(rhs));
    return *this;
  }

  expression &expression::less_equals(expression rhs) {
    expr = binary_operator("<=", std::move(expr), std::move(rhs));
    return *this;
  }

  expression &expression::greater_than(expression rhs) {
    expr = binary_operator(">", std::move(expr), std::move(rhs));
    return *this;
  }

  expression &expression::greater_equals(expression rhs) {
    expr = binary_operator(">=", std::move(expr), std::move(rhs));
    return *this;
  }


  expression &expression::factor(expression rhs) {
    expr = binary_operator("**", std::move(expr), std::move(rhs));
    return *this;
  }


  expression &expression::dot(expression rhs) {
    expr = binary_operator(".", std::move(expr), std::move(rhs));
    return *this;
  }

  struct printer {
    void operator()(const double n) const {
      std::cout << n;
    }

    void operator()(const std::string &s) const {
      std::cout << s;
    }

    void operator()(const expression &ast) const {
      boost::apply_visitor(*this, ast.expr);
    }

    void operator()(const binary_operator &expr) const {
      std::cout << "op:" << expr.op << "(";
      boost::apply_visitor(*this, expr.lhs.expr);
      std::cout << ", ";
      boost::apply_visitor(*this, expr.rhs.expr);
      std::cout << ')';
    }

    void operator()(const unary_operator &expr) const {
      std::cout << "op:" << expr.op << "(";
      boost::apply_visitor(*this, expr.rhs.expr);
      std::cout << ')';
    }
  };

  struct operators {
    struct and_ {
    };
    struct or_ {
    };

    struct equals {
    };
    struct not_equals {
    };

    struct less_than {
    };
    struct less_equals {
    };
    struct greater_than {
    };
    struct greater_equals {
    };

    struct factor {
    };

    struct dot {
    };

    expression &operator()(expression &lhs, expression rhs, and_) const {
      return lhs.and_(std::move(rhs));
    }

    expression &operator()(expression &lhs, expression rhs, or_) const {
      return lhs.or_(std::move(rhs));
    }


    expression &operator()(expression &lhs, expression rhs, equals) const {
      return lhs.equals(std::move(rhs));
    }

    expression &operator()(expression &lhs, expression rhs, not_equals) const {
      return lhs.not_equals(std::move(rhs));
    }


    expression &operator()(expression &lhs, expression rhs, less_than) const {
      return lhs.less_than(std::move(rhs));
    }

    expression &operator()(expression &lhs, expression rhs, less_equals) const {
      return lhs.less_equals(std::move(rhs));
    }

    expression &operator()(expression &lhs, expression rhs, greater_than) const {
      return lhs.greater_than(std::move(rhs));
    }

    expression &operator()(expression &lhs, expression rhs, greater_equals) const {
      return lhs.greater_equals(std::move(rhs));
    }

    expression &operator()(expression &lhs, expression rhs, factor) const {
      return lhs.factor(std::move(rhs));
    }


    expression &operator()(expression &lhs, expression rhs, dot) const {
      return lhs.dot(std::move(rhs));
    }
  };
}

namespace qi = boost::spirit::qi;

struct expectation_handler {
  template<typename Iterator>
  void operator()(Iterator first, Iterator last, const boost::spirit::info &info) const {
    std::stringstream msg;
    msg << "Expected " << info << " at \"" << std::string(first, last) << "\"";
    throw std::runtime_error(msg.str());
  }
};

template<typename Iterator>
struct grammar : qi::grammar<Iterator, ast::expression(), qi::ascii::space_type> {
  grammar()
    : grammar::base_type(expression) {

    variable = qi::lexeme[qi::alpha >> *(qi::alnum | '_')];

    expression = logical.alias() > qi::eoi;

    logical = equality[qi::_val = qi::_1]
      >> *(
        ((qi::lit("&&") > equality[op(qi::_val, qi::_1, ast::operators::and_{})]) |
         (qi::lit("||") > equality[op(qi::_val, qi::_1, ast::operators::or_{})]))
      );

    equality = relational[qi::_val = qi::_1]
      >> *(
        ((qi::lit("==") > relational[op(qi::_val, qi::_1, ast::operators::equals{})]) |
         (qi::lit("!=") > relational[op(qi::_val, qi::_1, ast::operators::not_equals{})]))
      );

    relational = additive[qi::_val = qi::_1]
      >> *(
        ((qi::lit("<") > relational[op(qi::_val, qi::_1, ast::operators::less_than{})]) |
         (qi::lit("<=") > relational[op(qi::_val, qi::_1, ast::operators::less_equals{})]) |
         (qi::lit(">") > relational[op(qi::_val, qi::_1, ast::operators::greater_than{})]) |
         (qi::lit(">=") > relational[op(qi::_val, qi::_1, ast::operators::greater_equals{})]))
      );

    additive = multiplicative[qi::_val = qi::_1]
      >> *(
        ((qi::lit("+") > multiplicative[qi::_val += qi::_1]) |
         (qi::lit("-") > multiplicative[qi::_val -= qi::_1]))
      );

    multiplicative = factor[qi::_val = qi::_1]
      >> *(
        ((qi::lit("*") > factor[qi::_val *= qi::_1]) |
         (qi::lit("/") > factor[qi::_val /= qi::_1]))
      );

    factor = primary[qi::_val = qi::_1]
      >> *((qi::lit("**")) > primary[op(qi::_val, qi::_1, ast::operators::factor{})]);

    primary =
      qi::double_[qi::_val = qi::_1]
      | ('(' > expression[qi::_val = qi::_1] > ')')
        >> *(qi::char_('.') > variable[qi::_val = op(qi::_val, qi::_1, ast::operators::dot{})])
      | variable[qi::_val = qi::_1]
        >> *(qi::char_('.') > variable[qi::_val = op(qi::_val, qi::_1, ast::operators::dot{})]);

    qi::on_error<qi::fail>(
      expression,
      boost::phoenix::bind(boost::phoenix::ref(err_handler), qi::_3, qi::_2, qi::_4));
  }

  qi::rule<Iterator, ast::expression(), qi::ascii::space_type> expression, logical, equality, relational, additive, multiplicative, factor, unary, binary, primary;
  qi::rule<Iterator, std::string()> variable;
  boost::phoenix::function<ast::operators> op;
  expectation_handler err_handler;
};

int main(int argc, const char *argv[]) {
  std::string input("2 + 5 + t.a");
  auto it_begin(input.begin()), it_end(input.end());

  grammar<decltype(it_begin)> parser;

  ast::expression expression;
  qi::phrase_parse(it_begin, it_end, parser, qi::ascii::space, expression);

  ast::printer printer;
  printer(expression);

  return 0;
}

打印

op:+(op:+(2, 5), op:.(t, a))

【问题讨论】:

  • Boost.Spirit 的文档是here。如果您有具体问题,请在问题本身中说明该问题。
  • 如果您正在寻找代码审查,这里有codereview.stackexchange.com。 (到哪里,我才意识到,你有cross-posted这个问题......也许删除这个?)
  • @DevSolar 我承认它更合适。但是,这种主题确实需要特定的图书馆知识,而且我肯定永远不会看到 [CR] 上的问题。所以,我会在那里回答,但只是因为我在这里找到了它...... [难题]

标签: c++ boost abstract-syntax-tree boost-spirit


【解决方案1】:

我将按照我“发现”您的代码的顺序来叙述这一点。然后我将介绍一些我认为最终最重要的调整。

我很喜欢你所做的很多事情。

  1. 可以(应该?)改进一些名称。例如。 ast::operators 没有表明它的目的。这是二元运算符表达式的惰性工厂。

    所以,将其命名为 make_binary 或类似名称。

    与包装它的 phoenix::function&lt;&gt; 包装器相同。语义动作中的op 并不能很好地表达那里发生的事情。

  2. 不要让op(别名make_binary)actor 对_val 参数产生副作用,而是考虑让它返回一个不同的值。然后一切都可以变得不可变,语义动作更好地表达意图:

    rule = expr [ _val = foo(_val, _1, _2, _3) ];
    

    表示 _val 已更新为根据给定参数创建的内容。

  3. 在语法层面,事情看起来并不“整洁”。很多东西可以通过简单的using namespace qi::labels 来改进,并去掉多余的qi::lit() 包装器,这会改变例如

    logical = equality[qi::_val = qi::_1]
      >> *(
        ((qi::lit("&&") > equality[op(qi::_val, qi::_1, ast::operators::and_{})]) |
         (qi::lit("||") > equality[op(qi::_val, qi::_1, ast::operators::or_{})]))
      );
    

    进入

    using ast::operators;
    using namespace qi::labels;
    
    logical = equality[_val = _1]
      >> *(
        (("&&" > equality[op(_val, _1, operators::and_{})]) |
         ("||" > equality[op(_val, _1, operators::or_{})]))
      );
    
  4. 您在语法中检查eoi(对您有好处!)。但是,它被放在一个递归规则中:

    expression = logical.alias() > qi::eoi;
    

    这意味着(a+b)*3 永远不会解析,因为) 是在需要eoi 的地方找到的。通过将eoi 放在顶层来修复它。

  5. 你有一个语法级别的船长,这意味着人们必须通过正确的船长。如果他们不这样做,他们可能会破坏语法。相反,将船长设为内部以便您控制它,并且界面更易于使用(正确):

    start = qi::skip(qi::ascii::space) [ expression ];
    

    用法:

    if (qi::parse(it_begin, it_end, parser, expression)) {
    

    或许:

    if (qi::parse(it_begin, it_end, parser > qi::eoi, expression)) {
    
  6. 我知道驱动程序代码 (main) 可能超出您的审查范围,但我会向您展示缺少的错误处理,因为它可能非常微妙 w.r.t。部分解析:

    int main() {
        ast::printer printer;
        grammar<std::string::const_iterator> parser;
    
        for (std::string const input : {
                "2 + 5 + t.a",
                "(2 + 5) + t.a", // note the removed eoi constraint
                "2 + 5 * t.a",
                "2 * 5 - t.a",
                "partial match",
                "uhoh *",
            })
        try {
            std::cout << "----- " << std::quoted(input) << " ---- \n";
            auto it_begin(input.begin()), it_end(input.end());
    
            ast::expression expression;
            if (qi::parse(it_begin, it_end, parser, expression)) {
                printer(expression);
                std::cout << std::endl;
            } else {
                std::cout << "Not matched\n";
            }
    
            if (it_begin != it_end) {
                std::string tail(it_begin, it_end);
                std::cout << "Remaining unparsed input: " << std::quoted(tail) << "\n";
            }
        } catch(std::exception const& e) {
            std::cout << "Exception: " << std::quoted(e.what()) << "\n";
        }
    }
    
  7. 请注意,除非您为规则命名,否则期望不会提供有用的信息。

    Exception: Expected <unnamed-rule> at ""
    

    命名它们的惯用方式是使用 DEBUG 宏:

    BOOST_SPIRIT_DEBUG_NODES(
            (start)
            (expression)(logical)(equality)
            (relational)(additive)(multiplicative)
            (factor)(unary)(binary)(primary)
            (variable)
        )
    

    现在:

    Exception: Expected <factor> at ""
    

    中场休息:这里的表面变化:Live On Coliru

  8. 在打印机中有很多重复(apply_visitor(*this...),由于operator(),它的可读性略差。我的偏好是中继到 callapply 函数

  9. 同样在打印机中,不要对输出流进行硬编码。有一天(TM)你会想要格式化为一个字符串。或者std::cerr,或者一个文件

    在打印机上结合这些注释:Live On Coliru

    struct printer {
        std::ostream& _os;
    
        template <typename T> std::ostream& operator()(T const& v) const
            { return call(v); }
    
      private:
        std::ostream& call(expression const& ast) const {
            return boost::apply_visitor(*this, ast.expr);
        }
    
        std::ostream& call(binary_operator const& expr) const {
            _os << "op:" << expr.op << "(";
            call(expr.lhs) << ", ";
            return call(expr.rhs) << ')';
        }
    
        std::ostream& call(unary_operator const& expr) const {
            _os << "op:" << expr.op << "(";
            return call(expr.rhs) << ')';
        }
    
        template <typename Lit>
        std::ostream& call(Lit const& v) const { return _os << v; }
    };
    
  10. 这样做的逻辑扩展是使其成为实际的output manipulator

        std::cout << "Parsed: " << fmt_expr{expression} << std::endl;
    

    再次,Live On Coliru,也再次简化了printer 访问者:

    std::ostream& call(binary_operator const& expr) const {
        return _os
            << "op:" << expr.op
            << "("   << fmt_expr{expr.lhs}
            << ", "  << fmt_expr{expr.rhs} << ')';
    }
    
  11. 在 AST 中,您将实际的运算符动态地存储为字符串。在我看来,仅针对所有 ast 构建重载(ast::operator::operator() 以及ast::expr 的所有委派成员)对运算符进行静态编码并没有太多价值。而是每次只传递一个字符串?

    现在builder命名空间可以消失,工厂成员不对称,整个phoenix函数是语法本地化的:

    struct make_binary_f {
        ast::binary_operator operator()(ast::expression lhs, ast::expression rhs, std::string op) const {
            return { op, lhs, rhs };
        }
    };
    boost::phoenix::function<make_binary_f> make;
    

    另一个中间站Live On Coliru

    成就解锁

    减少 113 行代码(现在是 218 行代码而不是 331 行代码)

  12. 随机点:

    variable = qi::lexeme[qi::alpha >> *(qi::alnum | '_')];
    

    '_' 等价于qi::lit('_'),而不是qi::char_('_'),因此这将删除所有下划线。要么使用 char_,要么使用 raw[] 直接从源迭代器构造参数。

  13. 现在我们进入细节:我们可以使用自动属性传播来代替[_val=_1](参见Boost Spirit: "Semantic actions are evil"?operator %= rule init)。

  14. 分解出更常见的子表达式。连同上一个项目符号:

    primary
        = qi::double_[_val = _1]
        | ('(' > expression[_val = _1] > ')')
          >> *("." > variable[_val = make(_val, _1, ".")])
        | variable[_val = _1]
          >> *("." > variable[_val = make(_val, _1, ".")]);
    

    变成:

    primary %= qi::double_
        | (('(' > expression > ')') | variable)
            >> *("." > variable[_val = make(_val, _1, ".")])
        ;
    
  15. expression 之外提升变体类型,以便您可以在expression 之前实现递归类型。此外,考虑派生自变体 (LSK) 的 expression。在您的情况下,实际上不需要嵌套表达式,因为一元/二元节点已经强加了顺序。所以你的整个 AST 可以是:

    struct unary_operator;
    struct binary_operator;
    
    typedef boost::variant<
        double,
        std::string,
        boost::recursive_wrapper<unary_operator>,
        boost::recursive_wrapper<binary_operator>
    > expr_variant;
    
    struct expression : expr_variant {
        using expr_variant::expr_variant;
        using expr_variant::operator=;
    };
    
    struct unary_operator  { expression rhs;                 std::string op; } ;
    struct binary_operator { expression lhs; expression rhs; std::string op; } ;
    
  16. expectation_handler 移动到语法类中(它对其他任何东西都没用),并且可以选择用phoenix::function 对其进行现代化改造?无论如何,由于函子是无状态的,所以不需要ref(当然也不需要ref 而不是cref):

    qi::on_error<qi::fail>(
        expression,
        boost::phoenix::bind(expectation_handler{}, _3, _2, _4));
    

    其实就是这样

    auto handler = [](Iterator first, Iterator last, const boost::spirit::info &info) {
        std::stringstream msg;
        msg << "Expected " << info << " at \"" << std::string(first, last) << "\"";
        throw std::runtime_error(msg.str());
    };
    
    qi::on_error<qi::fail>(
        expression,
        boost::phoenix::bind(handler, _3, _2, _4));
    
  17. 小问题:使用std::quoted 而不是“假”引用:)

  18. 晚期脑电波,您可以提取大部分语义动作:

    auto make_bin =
        _val = px::bind(make_<ast::binary_expr>{}, _val, _2, _1);
    

    只要所有肢体都是无状态的/按值计算,这不是问题(尽管与 Assigning parsers to auto variables 形成对比!)。现在只需让操作符公开属性:

    expression %= equality
      >> *(
          (qi::string("&&") > equality)[make_bin] |
          (qi::string("||") > equality)[make_bin]
      );
    
    equality %= relational
      >> *(
          (qi::string("==") > relational)[make_bin] |
          (qi::string("!=") > relational)[make_bin]
      );
    
    relational %= additive
      >> *(
          (qi::string("<")  > relational)[make_bin]  |
          (qi::string("<=") > relational)[make_bin] |
          (qi::string(">")  > relational)[make_bin]  |
          (qi::string(">=") > relational)[make_bin]
      );
    
    additive %= multiplicative
      >> *(
          (qi::string("+") > multiplicative)[make_bin] |
          (qi::string("-") > multiplicative)[make_bin]
      );
    
    multiplicative %= factor
      >> *(
          (qi::string("*") > factor)[make_bin] |
          (qi::string("/") > factor)[make_bin]
      );
    
    factor %= primary
      >> *(
          (qi::string("**") > primary)[make_bin]
      );
    
    primary %= qi::double_
        | (('(' > expression > ')') | variable)
            >> *(qi::string(".") > variable)[make_bin]
        ;
    
  19. 其实刚刚查了一下,phoenix::construct可以做聚合:

    auto make_bin =
        _val = boost::phoenix::construct<ast::binary_expr>(_1, _val, _2);
    
  20. 还删除了未使用的 unary_* 机器,将 IO 操纵器移至 io 命名空间(而不是 ast)并重新引入 eoi 签入 main 驱动程序...

  21. 哎呀,使用一些 c++17,您可以组合每个生产的分支:

    auto op = [](auto... sym) { return qi::copy((qi::string(sym) | ...)); };
    
    expression     %= equality       >> *(op("&&","||") > equality)[make_bin];
    equality       %= relational     >> *(op("==","!=") > relational)[make_bin];
    relational     %= additive       >> *(op("<","<=",">",">=") > relational)[make_bin];
    additive       %= multiplicative >> *(op("+","-")   > multiplicative)[make_bin];
    multiplicative %= factor         >> *(op("*","/")   > factor)[make_bin];
    factor         %= primary        >> *(op("**")      > primary)[make_bin];
    

完整演示,103 行代码

只是没能将其控制在 100 LoC 以下,但我在此过程中添加了更多测试用例。

#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <iostream>
#include <iomanip>
namespace qi = boost::spirit::qi;

namespace ast {
    struct binary_expr;

    typedef boost::variant<
        double,
        std::string,
        boost::recursive_wrapper<binary_expr>
    > expr_variant;

    struct expression : expr_variant {
        using expr_variant::expr_variant;
        using expr_variant::operator=;
    };

    struct binary_expr {
        binary_expr(std::string op, expression lhs, expression rhs)
            : op(std::move(op)), lhs(std::move(lhs)), rhs(std::move(rhs)) {}
        std::string op; expression lhs; expression rhs;
    };
}

namespace io {
    struct fmt_expr { // io manipulator
        ast::expression const& _ref;
        friend std::ostream& operator<<(std::ostream& os, fmt_expr manip);
    };

    struct formatter_visitor {
        std::ostream& _os;
        template <typename T> std::ostream& operator()(T const& v) const
            { return call(v); }

      private:
        std::ostream& call(ast::expression const& v) const {
            return boost::apply_visitor(*this, v);
        }

        std::ostream& call(ast::binary_expr const& expr) const {
            return _os << "op:" << expr.op
                << "("   << fmt_expr{expr.lhs} << ", "  << fmt_expr{expr.rhs} << ')';
        }

        template <typename Lit>
        std::ostream& call(Lit const& v) const { return _os << v; }
    };

    std::ostream& operator<<(std::ostream& os, fmt_expr manip) {
        return formatter_visitor{os}(manip._ref);
    }
}

template<typename Iterator>
struct grammar : qi::grammar<Iterator, ast::expression()> {
    grammar() : grammar::base_type(start) {
        using namespace qi::labels;

        auto make_bin = _val = boost::phoenix::construct<ast::binary_expr>(_1, _val, _2);
        auto op = [](auto... sym) { return qi::copy((qi::string(sym) | ...)); };

        expression     %= equality       >> *(op("&&","||") > equality)[make_bin];
        equality       %= relational     >> *(op("==","!=") > relational)[make_bin];
        relational     %= additive       >> *(op("<","<=",">",">=") > relational)[make_bin];
        additive       %= multiplicative >> *(op("+","-")   > multiplicative)[make_bin];
        multiplicative %= factor         >> *(op("*","/")   > factor)[make_bin];
        factor         %= primary        >> *(op("**")      > primary)[make_bin];
    
        variable = qi::lexeme[qi::alpha >> *(qi::alnum | qi::char_('_'))];
        primary %= qi::double_ | (('(' > expression > ')') | variable)
                >> *(op(".") > variable)[make_bin];
    
        start = qi::skip(qi::ascii::space) [ qi::eps > expression ] > qi::eoi;

        qi::on_error<qi::fail>(start, boost::phoenix::bind([](auto first, auto last, auto const& info) {
            std::stringstream msg;
            msg << "Expected " << info << " at " << std::quoted(std::string(first, last));
            throw std::runtime_error(msg.str());
        }, _3, _2, _4));

        BOOST_SPIRIT_DEBUG_NODES((expression)(equality)(relational)(additive)
                (multiplicative)(factor)(unary)(binary)(primary)(variable))
    }
  private:
    qi::rule<Iterator, ast::expression()> start;
    qi::rule<Iterator, ast::expression(), qi::ascii::space_type> expression, equality, relational, additive, multiplicative, factor, unary, binary, primary;
    qi::rule<Iterator, std::string()> variable; // lexeme
};

int main() {
    using io::fmt_expr;
    grammar<std::string::const_iterator> parser;

    for (std::string const s : { "2 + 5 + t.a", "(2 + 5) + t.a", "2 + 5 * t.a",
            "2 * 5 - t.a", "partial match", "uhoh *", "under_scores", "" })
    try {
        ast::expression expression;
        qi::parse(s.begin(), s.end(), parser, expression);
        std::cout << std::quoted(s) << " -> " << fmt_expr{expression} << "\n";
    } catch(std::exception const& e) {
        std::cout << "Exception: " << e.what() << "\n";
    }
}

打印

"2 + 5 + t.a" -> op:+(op:+(2, 5), op:.(t, a))
"(2 + 5) + t.a" -> op:+(op:+(2, 5), op:.(t, a))
"2 + 5 * t.a" -> op:+(2, op:*(5, op:.(t, a)))
"2 * 5 - t.a" -> op:-(op:*(2, 5), op:.(t, a))
Exception: Expected <eoi> at " match"
Exception: Expected <factor> at ""
"under_scores" -> under_scores

超出范围

我将考虑超出范围的事情与您的语法/ast语义有关。

  1. 运算符优先级有点吵。您想要的是一些元数据,允许您“组合”二进制操作数并出现正确的优先级,如下所示:

    expression %= primary
      >> *(
          (binop > expression) [_val = make_bin(_1, _val, _2)]
      );
    

    我已在this answerextended chat 上应用此策略,生成的代码在github 上:https://github.com/sehe/qi-extended-parser-evaluator

  2. 如果您支持 C++14,请考虑使用 X3。编译时间会少很多。

【讨论】:

    猜你喜欢
    • 2012-01-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-27
    • 1970-01-01
    • 1970-01-01
    • 2021-12-27
    相关资源
    最近更新 更多