微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

Boost Spirit X3 的最小计算器

如何解决Boost Spirit X3 的最小计算器

我正在尝试使用 Boost Spirit X3 编写一个简约的计算器示例。我发现官方示例确实如此令人困惑,因为它们似乎使用了大量不必要的样板代码来定义和评估 AST,并且 X3 的各个版本之间存在细微的语法变化。

这里有一些伪代码概述了我想要做什么:

  using namespace boost::spirit::x3;

  std::string Test("1 + (3 - 2)");

  auto first = Test.begin();
  auto last = Test.end();

  rule<class dash_expr> const dash_expr("dash_expr");
  rule<class factor_expr> const factor_expr("factor_expr");

  auto const dash_expr_def = factor_expr
                           >> *( (char_('+') >> dash_expr)
                               | (char_('-') >> dash_expr)
                               );

  auto const factor_expr_def = uint_
                             | '(' >> dash_expr >> ')'
                             ;

  BOOST_SPIRIT_DEFINE(dash_expr,factor_expr);

  ascii::space_type space;

  bool r = phrase_parse(
      first,last,dash_expr,space
  );

首先,这不能使用当前版本的 boost 编译,因为 BOOST_SPIRIT_DEFINE 会导致“预期表达式”错误。此外,如何在不定义大量其他类型和操作的情况下评估生成的 AST?较早的 Boost Spirit Qi 示例表明,可以简单地使用“内联”表达式,例如 (char_('+') >> dash_expr) [_val += _1]。此功能是否已弃用?我如何将我的伪代码转换一个工作示例,使用最少的额外代码输出正确的结果 2

解决方法

问题。首先,这不能使用当前版本的 boost 进行编译,因为 BOOST_SPIRIT_DEFINE 会导致“预期表达式”错误。

那是因为注册规则定义专门用于函数模板。它需要在命名空间范围内。

修正:https://godbolt.org/z/e3Essd8e8

问。此外,我如何在不定义大量其他类型和操作的情况下评估生成的 AST?较早的 Boost Spirit Qi 示例表明,可以简单地使用“内联”表达式,例如 (char_('+') >> dash_expr) [_val += _1]。此功能是否已弃用?

没有。它只是没有在 X3 中实现。语义动作已经被简化了很多次,现在是常规的 lambdas。因此,用于语义动作的基于 Proto 的 DSL 的编译开销似乎是多余的。

问。如何将我的伪代码转换成一个工作示例,使用最少的额外代码行输出正确的结果 2?

基本上,通过添加一些代码。我看到了更多可以简化的东西。我可以向您展示一些变体。

您可能会感兴趣的是一个简单的头文件(类似于新的 Boost Lambda2 提案),它实现了 X3 的 Boost Phoenix 的核心。

先做简单的蛋糕

直接的方法是“只做工作”。为了让它不那么麻烦,我将使用一个宏。我还将实现更多的二元运算符:

Live On Compiler Explorer

find_dependency

印刷品

#include <boost/spirit/home/x3.hpp>
#include <iostream>
#include <iomanip>
namespace x3 = boost::spirit::x3;

using V = int32_t;

namespace Parser {
    x3::rule<struct expr,V> const   expr{"expr"};
    x3::rule<struct simple,V> const simple{"simple"};
    x3::rule<struct factor,V> const factor{"factor"};

    auto assign = [](auto& ctx) { _val(ctx) = _attr(ctx); };
    #define BINOP(op,rhs) (x3::lit(#op) >> x3::as_parser(rhs) \
        [([](auto& ctx) { _val(ctx) = _val(ctx) op _attr(ctx); })])

    auto simple_def = x3::double_ | '(' >> expr >> ')';

    auto factor_def = simple[assign] >>
        *(BINOP(*,factor) 
        | BINOP(/,factor) 
        | BINOP(%,factor));

    auto expr_def = factor[assign] >>
        *(BINOP(+,expr) 
        | BINOP(-,expr));

    BOOST_SPIRIT_DEFINE(expr,factor,simple)
} // namespace Parser

V evaluate(std::string_view text) {
    V value{};
    if (!phrase_parse(text.begin(),text.end(),Parser::expr >> x3::eoi,x3::space,value))
        throw std::runtime_error("error in expression");
    return value;
}

int main() {
    for (auto expr : {
            "1 + (3 - 2)","1 * 3 - 2","17 % 5 - 2","17 % (5 - 2)","1 - 5 * 7","(1 - 5) * 7","((1 - 5) * 7)",})
    {
        std::cout << std::quoted(expr) << "\t-> " << evaluate(expr) << "\n";
    }
}

Phoeni_X3.hpp

不提供任何形式的保证,这里的灵感来自:https://github.com/sehe/expression-parsers/tree/x3-c++17

Wandbox 上的现场演示:

"1 + (3 - 2)"   -> 2
"1 * 3 - 2"     -> 1
"17 % 5 - 2"    -> 0
"17 % (5 - 2)"  -> 2
"1 - 5 * 7"     -> -34
"(1 - 5) * 7"   -> -28
"((1 - 5) * 7)" -> -28
  • 文件 ---------------------------------------------- OUTPUT: 0 ← "1 + ( 2 - 3 )" ---------------------------------------------- OUTPUT: 6.25 ← "( ( 1 + ( 2 - 3 ) + 5 ) / 2 ) ^ 2" ---------------------------------------------- OUTPUT: 14 ← "5 + ( ( 1 + 2 ) * 4 ) - 3" ---------------------------------------------- OUTPUT: 7 ← "3+4" ---------------------------------------------- OUTPUT: 11 ← "3+4*2" ---------------------------------------------- OUTPUT: 11 ← "3+(4*2)" ---------------------------------------------- OUTPUT: 14 ← "(3+4)*2" ---------------------------------------------- OUTPUT: 128 ← "2*2* 2* 2 * 2\t*2\n*2" ---------------------------------------------- Expecting ')' in "(2" (2 ^--- here ---------------------------------------------- Expecting term in "(2+/" (2+/ ^--- here ---------------------------------------------- Expecting term in "42*()" 42*() ^--- here ---------------------------------------------- Expecting ')' in "(2*2* 2* 2 * 2\t*2\n*2" *2 ^--- here

    test.cpp
  • 文件 //#define BOOST_SPIRIT_X3_DEBUG #include <iostream> #include <iomanip> #include <string_view> #include "quote_esc.hpp" #include "phoeni_x3.hpp" namespace x3 = boost::spirit::x3; namespace parsing { #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wparentheses" using namespace PhoeniX3::placeholders; using Value = double; using x3::expect; x3::rule<struct _e,Value> expr { "expr" }; x3::rule<struct _x,Value> expo { "exponentation" }; x3::rule<struct _t,Value> term { "term" }; x3::rule<struct _f,Value> factor { "factor" }; auto simple = x3::rule<struct _s,Value> {"simple"} = x3::double_ [_val = _attr] | '(' >> expect[term][_val = _attr] > ')' ; auto expo_def = simple [_val = _attr] >> *( '^' >> expect[expo] [ _val ^= _attr ] ) ; auto factor_def = expo [_val = _attr] >> *( '*' >> expect[factor] [_val *= _attr] | '/' >> expect[factor] [_val /= _attr]) ; auto term_def = factor [_val = _attr] >> *( '+' >> expect[term] [_val += _attr] | '-' >> expect[term] [_val -= _attr]) ; auto expr_def = x3::skip(x3::space)[ x3::eps > term > x3::eoi ]; BOOST_SPIRIT_DEFINE(expr,term,expo) using expectation_failure = x3::expectation_failure<std::string_view::const_iterator>; #pragma GCC diagnostic pop } int main() { for (std::string_view text : { "1 + ( 2 - 3 )",//OUTPUT: 0.0 "( ( 1 + ( 2 - 3 ) + 5 ) / 2 ) ^ 2",//OUTPUT: 6.25 "5 + ( ( 1 + 2 ) * 4 ) - 3",//OUTPUT: 14.0 "3+4","3+4*2","3+(4*2)","(3+4)*2","2*2* 2* 2 * 2\t*2\n*2","(2","(2+/","42*()","(2*2* 2* 2 * 2\t*2\n*2",}) try { std::cout << "----------------------------------------------\n"; double result; if (parse(text.begin(),parsing::expr,result)) { std::cout << "OUTPUT: " << result << " ← " << quote_esc(text) << "\n"; } else { std::cout << "Unparsed expression " << quote_esc(text) << "\n"; } } catch(parsing::expectation_failure const& ef) { auto pos = ef.where() - text.begin(); // isolate a line auto sol = text.find_last_of("\r\n",pos) + 1; auto eol = text.find_first_of("\r\n",pos); std::cout << "Expecting " << ef.which() << " in " << quote_esc(text) << "\n " << text.substr(sol,eol) << "\n " << std::string(pos - sol,' ') << "^--- here\n"; } }

    phoeni_x3.hpp
  • 文件 #pragma once #include <boost/spirit/home/x3.hpp> namespace PhoeniX3 { namespace x3 = boost::spirit::x3; // base facilities void eval(void); // ADL enable // for instance-only operator overloads template <typename T> struct _base_type { using self = T; using base = _base_type; template <typename U> auto operator=(U&&) const; template <typename Ctx> decltype(auto) operator()(Ctx& ctx)/*&&*/ { return eval(ctx,self{}); } }; // placeholders struct _val_type : _base_type<_val_type> {using base::operator(); using base::operator=;}; struct _attr_type : _base_type<_attr_type> {using base::operator(); using base::operator=;}; template <int N> struct _atc_type : _base_type<_atc_type<N>> { using base = _base_type<_atc_type>; using base::operator(); using base::operator=; }; namespace placeholders { _val_type static const _val {}; _attr_type static const _attr {}; _atc_type<0> static const _1 {}; _atc_type<1> static const _2 {}; _atc_type<2> static const _3 {}; _atc_type<3> static const _4 {}; _atc_type<4> static const _5 {}; _atc_type<5> static const _6 {}; _atc_type<6> static const _7 {}; _atc_type<7> static const _8 {}; _atc_type<8> static const _9 {}; } // expression types template <typename L,typename R,typename Op> struct BinExpr : _base_type<BinExpr<L,R,Op> > { using BinExpr::base::operator(); using BinExpr::base::operator=; }; namespace tag { struct _add; struct _add_assign; struct _sub; struct _sub_assign; struct _mul; struct _mul_assign; struct _div; struct _div_assign; struct _exp; struct _exp_assign; struct _assign; } template <typename L,typename R> BinExpr<L,tag::_exp_assign> operator^=(L&&,R&&) { return {}; } template <typename L,tag::_add_assign> operator+=(L&&,tag::_sub_assign> operator-=(L&&,tag::_mul_assign> operator*=(L&&,tag::_div_assign> operator/=(L&&,tag::_exp> operator&(L&&,tag::_add> operator+(L&&,tag::_sub> operator-(L&&,tag::_mul> operator*(L&&,tag::_div> operator/(L&&,R&&) { return {}; } template <typename L> template <typename R> auto _base_type<L>::operator=(R&&) const { return BinExpr<L,tag::_assign>{}; } template <typename Ctx> auto&& eval(Ctx& ctx,_val_type) { return x3::_val(ctx); } template <typename Ctx> auto&& eval(Ctx& ctx,_attr_type) { return x3::_attr(ctx); } template <typename Ctx,int N> auto&& eval(Ctx& ctx,_atc_type<N>) { return boost::fusion::at_c<N>(x3::_attr(ctx)); } template <typename L,typename Ctx> auto eval(Ctx& ctx,BinExpr<L,tag::_add>) { return eval(ctx,L{}) + eval(ctx,R{}); } template <typename L,tag::_sub>) { return eval(ctx,L{}) - eval(ctx,tag::_mul>) { return eval(ctx,L{}) * eval(ctx,tag::_div>) { return eval(ctx,L{}) / eval(ctx,tag::_exp>) { return pow(eval(ctx,L{}),eval(ctx,R{})); } template <typename L,tag::_assign>) { return eval(ctx,L{}) = eval(ctx,typename Ctx> auto&& eval(Ctx& ctx,tag::_add_assign>) { return eval(ctx,L{}) += eval(ctx,tag::_sub_assign>) { return eval(ctx,L{}) -= eval(ctx,tag::_mul_assign>) { return eval(ctx,L{}) *= eval(ctx,tag::_div_assign>) { return eval(ctx,L{}) /= eval(ctx,tag::_exp_assign>) { return eval(ctx,L{}) = pow(eval(ctx,R{})); } }

    quote_esc.hpp

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。