带有 Boost Spirit X3 的最小计算器

Minimal calculator with 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

Q. First of all, this does not compile with a current version of boost because BOOST_SPIRIT_DEFINE causes a "expected expression" error.

那是因为注册规则定义特化了函数模板。它需要在命名空间范围内。

固定:https://godbolt.org/z/e3Essd8e8

Q. Additionally, how do I evaluate the resulting AST without defining a plethora of additional types and operations? Older Boost Spirit Qi examples suggest that it is possible to simply use "inline" expressions like (char_('+') >> dash_expr) [_val += _1]. Is this feature deprecated?

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

Q. How do I turn my pseudo code into a working example that outputs the correct result 2 using the least amount of additional lines of code?

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

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

先做简单的蛋糕

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

Live On Compiler Explorer

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

版画

"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

Phoeni_X3.hpp

没有任何形式的保证,以下是灵感来源:https://github.com/sehe/expression-parsers/tree/x3-c++17

Wandbox 现场演示:

----------------------------------------------
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, factor, 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(), text.end(), 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, R, tag::_exp_assign> operator^=(L&&, R&&) { return {}; }
         template <typename L, typename R> BinExpr<L, R, tag::_add_assign> operator+=(L&&, R&&) { return {}; }
         template <typename L, typename R> BinExpr<L, R, tag::_sub_assign> operator-=(L&&, R&&) { return {}; }
         template <typename L, typename R> BinExpr<L, R, tag::_mul_assign> operator*=(L&&, R&&) { return {}; }
         template <typename L, typename R> BinExpr<L, R, tag::_div_assign> operator/=(L&&, R&&) { return {}; }
         template <typename L, typename R> BinExpr<L, R, tag::_exp> operator&(L&&, R&&) { return {}; }
         template <typename L, typename R> BinExpr<L, R, tag::_add> operator+(L&&, R&&) { return {}; }
         template <typename L, typename R> BinExpr<L, R, tag::_sub> operator-(L&&, R&&) { return {}; }
         template <typename L, typename R> BinExpr<L, R, tag::_mul> operator*(L&&, R&&) { return {}; }
         template <typename L, typename R> BinExpr<L, R, tag::_div> operator/(L&&, R&&) { return {}; }
         template <typename L> template <typename R>
             auto _base_type<L>::operator=(R&&) const { return BinExpr<L, R, 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 R, typename Ctx> auto eval(Ctx& ctx, BinExpr<L, R, tag::_add>)    { return eval(ctx, L{}) + eval(ctx, R{}); }
         template <typename L, typename R, typename Ctx> auto eval(Ctx& ctx, BinExpr<L, R, tag::_sub>)    { return eval(ctx, L{}) - eval(ctx, R{}); }
         template <typename L, typename R, typename Ctx> auto eval(Ctx& ctx, BinExpr<L, R, tag::_mul>)    { return eval(ctx, L{}) * eval(ctx, R{}); }
         template <typename L, typename R, typename Ctx> auto eval(Ctx& ctx, BinExpr<L, R, tag::_div>)    { return eval(ctx, L{}) / eval(ctx, R{}); }
         template <typename L, typename R, typename Ctx> auto eval(Ctx& ctx, BinExpr<L, R, tag::_exp>)    { return pow(eval(ctx, L{}), eval(ctx, R{})); }
         template <typename L, typename R, typename Ctx> auto eval(Ctx& ctx, BinExpr<L, R, tag::_assign>) { return eval(ctx, L{}) = eval(ctx, R{}); }
         template <typename L, typename R, typename Ctx> auto&& eval(Ctx& ctx, BinExpr<L, R, tag::_add_assign>) { return eval(ctx, L{}) += eval(ctx, R{}); }
         template <typename L, typename R, typename Ctx> auto&& eval(Ctx& ctx, BinExpr<L, R, tag::_sub_assign>) { return eval(ctx, L{}) -= eval(ctx, R{}); }
         template <typename L, typename R, typename Ctx> auto&& eval(Ctx& ctx, BinExpr<L, R, tag::_mul_assign>) { return eval(ctx, L{}) *= eval(ctx, R{}); }
         template <typename L, typename R, typename Ctx> auto&& eval(Ctx& ctx, BinExpr<L, R, tag::_div_assign>) { return eval(ctx, L{}) /= eval(ctx, R{}); }
         template <typename L, typename R, typename Ctx> auto&& eval(Ctx& ctx, BinExpr<L, R, tag::_exp_assign>) { return eval(ctx, L{}) = pow(eval(ctx, L{}), eval(ctx, R{})); }
     }
    
  • 文件quote_esc.hpp

     #pragma once
     #include <ostream>
     #include <iomanip>
    
     template <typename T>
     struct quote_esc {
         T sv;
         quote_esc(T sv):sv(std::move(sv)) {}
    
         friend std::ostream& operator<<(std::ostream& os, quote_esc const& esc) {
             os << '"';
             for(uint8_t ch : std::string_view(esc.sv))
                 switch(ch) {
                     case '"' : os << "\\""; break;
                     case '\r': os << "\r";  break;
                     case '\n': os << "\n";  break;
                     case '\b': os << "\b";  break;
                     case '[=15=]': os << "\0";  break;
                     case '\t': os << "\t";  break;
                     case '\f': os << "\f";  break;
                     default: os << ch;
                 }
    
             return os << '"';
         }
     };