1

I am a bit of a newbee using Spirit.

I am trying to construct an AST tree from a simple "excel" formula using spirit x3. The grammar supports typical operators (+,-,*,/), functions (myfunc(myparam1, myparam2)) and cell references (eg A1, AA234).

So an example expression to be parsed might be A1 + sin(A2+3).

The problem is that the xlreference rule below never gets matched as the xlfunction rule takes precedence and the rule does not backtrack. I have experimented with expect, but I am lacking a few good examples to get it going.

I guess this leads onto another question relating to what is the best way to debug x3. I have seen the BOOST_SPIRIT_X3_DEBUG define, but I could not find any examples that demonstrated it's usage. I am also wrote an on_error method on the expression_class but this does not provide a good trace. I have tried to use the position tagging and with statement but this also does not provide enough information.

Any help would be appreciated!

x3::rule<class xlreference, ast::xlreference> const   xlreference{"xlreference"};
auto const xlreference_def = +alpha  > x3::uint_ > !x3::expect[char('(')];
BOOST_SPIRIT_DEFINE(xlreference);

struct identifier_class;
typedef x3::rule<identifier_class, std::string> identifier_type;
identifier_type const identifier = "identifier";
auto const identifier_def = x3::char_("a-zA-Z") > *(x3::char_("a-zA-Z") | x3::char_('_')) > !x3::expect[char('0-9')];
BOOST_SPIRIT_DEFINE(identifier);


auto const expression_def = // constadditive_expr_def 
    term [print_action()]
    >> *(   (char_('+') > term)
        |   (char_('-') > term)
        )
    ;

x3::rule<xlfunction_class, ast::xlfunction> const xlfunction("xlfunction");
auto const xlfunction_def = identifier > '(' > *(expression > *(',' > expression)) > ')';
BOOST_SPIRIT_DEFINE(xlfunction);

    

auto const term_def = //constmultiplicative_expr_def 
    factor 
    >> *(   (char_('*') > factor) 
        |   (char_('/') > factor) 
        )
    ;



auto const factor_def = // constunary_expr_def 
    xlfunction [print_action()]
    |   '(' > expression > ')' 
    |   (char_('-') > factor) 
    |   (char_('+') > factor) 
    | x3::double_  [print_action()] | xlreference [print_action()]
    ;

The error handler:

    struct expression_class //: x3::annotate_on_success
{
    //  Our error handler
    template <typename Iterator, typename Exception, typename Context>
    x3::error_handler_result
    on_error(Iterator& q, Iterator const& last, Exception const& x, Context const& context)
    {
        std::cout
            << "Error! Expecting: "
            << x.which()
            << " here: \""
            << std::string(x.where(), last)
            << "\""
            << std::endl
            ;
        return x3::error_handler_result::fail;
    }
};

Position tag:

    with<position_cache_tag>(std::ref(positions))
[
    client::calculator_grammar::expression
];
client::ast::program ast;
bool r = phrase_parse(iter, (iterator_type const ) str.end(), parser, x3::space, ast);
if (!r) {
    std::cout << "failed:" << str << "\n";
}
Michael
  • 13
  • 2
  • I haven't tried compiling it, but one item is that `*(expression > *(',' > expression))` will try to be parsed into a vector of vectors. I'm guessing you probably want instead `-(expression % ',')`. – Matt Oct 21 '21 at 18:21
  • There's code missing (specifically before `with`). Can you make the sample self-contained? – sehe Oct 21 '21 at 18:36

1 Answers1

0

Okay, taking the review one step at a time. Making up AST types along the way (because you didn't care to show).

  1. This is invalid code: char('0-9'). Is that a wide-character literal? Enable your compiler warnings! You probably meant x3::char_("0-9") (two important differences!).

  2. !x3::expect[] is a contradiction. You can never pass that condition, because ! asserts the lookahead is NOT matched, while expect[] REQUIRES a match. So, best case ! fails because the expect[]-ation is matched. Worst case expect[] throws an exception because you asked it to.

  3. operator > is already an expectation point. For the same reason as before > !p is a contradiction. Make it >> !p

  4. Replace char_("0-9") with x3::digit

  5. Replace char_("a-zA-Z") with x3::alpha

  6. Some (many) rules NEED to be lexemes. That's because you invoke the grammar in a skipper context (phrase_parse with x3::space). You identifier will silently eat whitespace, because you didn't make them lexemes. See Boost spirit skipper issues

  7. Negative lookahead assertions don't expose attributes so ! char_('(') could (should?) be ! lit('(')

  8. presence of semantic actions (by default) suppresses attribute propagation - so print_action() will cause attribute propagation to stop

  9. Expectation points (operator>) by defintion cannot backtrack. That's what makes them expecation points.

  10. use the List operator of composing with kleene-star: p >> *(',' >> p) -> p % ','

  11. That extra kleene-star was bogus. Did you mean to make the argument list optional? That's -(expression % ',')

  12. the chaining operator rules make it slightly cumbersome to get the ast right

  13. Simplify

    factor >> *((x3::char_('*') > factor) //
               | (x3::char_('/') > factor));
    

    To just factor >> *(x3::char_("*/") >> factor);

  14. factor_def logically matches what expression would be?

A first review pass yields:

auto const xlreference_def =
    x3::lexeme[+x3::alpha >> x3::uint_] >> !x3::char_('(');

auto const identifier_def =
    x3::raw[x3::lexeme[x3::alpha >> *(x3::alpha | '_') >> !x3::digit]];

auto const xlfunction_def = identifier >> '(' >> -(expression % ',') >> ')';

auto const term_def = factor >> *(x3::char_("*/") >> factor);

auto const factor_def = xlfunction //
    | '(' >> expression >> ')'     //
    | x3::double_                  //
    | xlreference;

auto const expression_def = term >> *(x3::char_("-+") >> term);

More observations:

  1. (iterator_type const)str.end()?? Never use C-style casts. In fact, just use str.cend() or indeed str.end() if str is suitably const anyways.

  2. phrase_parse - consider NOT making the skipper a caller's decision, since it logically is part of your grammar

  3. many kinds of excel expressions are not parsed: A:A, $A4, B$4, all cell ranges; I think often R1C1 is also supported

TIME FOR MAGIC FAIRY DUST

So, leaning on a lot of experience, I'm going to Crystall Ball™ some AST®:

namespace client::ast {
    using identifier = std::string;
    //using identifier = boost::iterator_range<std::string::const_iterator>;
    
    struct string_literal  : std::string {
        using std::string::string;
        using std::string::operator=;

        friend std::ostream& operator<<(std::ostream& os, string_literal const& sl) {
            return os << std::quoted(sl) ;
        }
    };

    struct xlreference {
        std::string colname;
        size_t      rownum;
    };

    struct xlfunction; // fwd
    struct binary_op;  // fwd

    using expression = boost::variant<        //
        double,                               //
        string_literal,                       //
        identifier,                           //
        xlreference,                          //
        boost::recursive_wrapper<xlfunction>, //
        boost::recursive_wrapper<binary_op>   //
        >;

    struct xlfunction{
        identifier              name;
        std::vector<expression> args;

        friend std::ostream& operator<<(std::ostream& os, xlfunction const& xlf)
        {
            os << xlf.name << "(";
            char const* sep = "";
            for (auto& arg : xlf.args)
                os << std::exchange(sep, ", ") << arg;
            return os;
        }
    };

    struct binary_op {
        struct chained_t {
            char       op;
            expression e;
        };
        expression             lhs;
        std::vector<chained_t> chained;

        friend std::ostream& operator<<(std::ostream& os, binary_op const& bop)
        {
            os << "(" << bop.lhs;
            for (auto& rhs : bop.chained)
            os << rhs.op << rhs.e;
            return os << ")";
        }
    };

    using program = expression;
    using boost::fusion::operator<<;
} // namespace client::ast

Which we promptly adapt:

BOOST_FUSION_ADAPT_STRUCT(client::ast::xlfunction, name, args)
BOOST_FUSION_ADAPT_STRUCT(client::ast::xlreference, colname, rownum)
BOOST_FUSION_ADAPT_STRUCT(client::ast::binary_op, lhs, chained)
BOOST_FUSION_ADAPT_STRUCT(client::ast::binary_op::chained_t, op, e)

Next, let's declare suitable rules:

x3::rule<struct identifier_class, ast::identifier>  const identifier{"identifier"};
x3::rule<struct xlreference,      ast::xlreference> const xlreference{"xlreference"};
x3::rule<struct xlfunction_class, ast::xlfunction>  const xlfunction{"xlfunction"};
x3::rule<struct factor_class,     ast::expression>  const factor{"factor"};
x3::rule<struct expression_class, ast::binary_op>   const expression{"expression"};
x3::rule<struct term_class,       ast::binary_op>   const term{"term"};

Which need definitions:

auto const xlreference_def =
    x3::lexeme[+x3::alpha >> x3::uint_] /*>> !x3::char_('(')*/;

Note how the look-ahead assertion (!) doesn't actually change the parse result, as any cell reference isn't a valid identifier anyways, so the () would remain unparsed.

auto const identifier_def =
    x3::raw[x3::lexeme[x3::alpha >> *(x3::alpha | '_') /*>> !x3::digit*/]];

Same here. Remaining input will be checked against with x3::eoi later.

I threw in a string literal because any Excel clone would have one:

auto const string_literal =
    x3::rule<struct _, ast::string_literal>{"string_literal"} //
= x3::lexeme['"' > *('\\' >> x3::char_ | ~x3::char_('"')) > '"'];

Note that this demonstrates that non-recursive, locally-defined rules don't need separate definitions.

Then come the expression rules

auto const factor_def =        //
    xlfunction                 //
    | '(' >> expression >> ')' //
    | x3::double_              //
    | string_literal           //
    | xlreference              //
    | identifier               //
    ;

I'd usually call this "simple expression" instead of factor.

auto const term_def       = factor >> *(x3::char_("*/") >>  factor);
auto const expression_def = term >> *(x3::char_("-+") >> term);
auto const xlfunction_def = identifier >> '(' >> -(expression % ',') >> ')';

Straight-forward mappings to the AST.

BOOST_SPIRIT_DEFINE(xlreference)
BOOST_SPIRIT_DEFINE(identifier)
BOOST_SPIRIT_DEFINE(xlfunction)
BOOST_SPIRIT_DEFINE(term)
BOOST_SPIRIT_DEFINE(factor)
BOOST_SPIRIT_DEFINE(expression)

'Nuff said. Now comes a bit of cargo cult - remnants of code both unshown and unused, which I'll mostly just accept and ignore here:

int main() {
    std::vector<int> positions; // TODO

    auto parser = x3::with<struct position_cache_tag /*TODO*/>        //
        (std::ref(positions))                                         //
            [                                                         //
                x3::skip(x3::space)[                                  //
                    client::calculator_grammar::expression >> x3::eoi //
    ]                                                                 //
    ];

DO NOTE though that x3::eoi makes it so the rule doesn't match if end of input (modulo skipper) isn't reached.

Now, let's add some test cases!

struct {
    std::string              category;
    std::vector<std::string> cases;
} test_table[] = {
    {
        "xlreference",
        {"A1", "A1111", "AbCdZ9876543", "i9", "i0"},
    },
    {
        "identifier",
        {"i", "id", "id_entifier"},
    },
    {
        "number",
        {"123", "inf", "-inf", "NaN", ".99e34", "1e-8", "1.e-8", "+9"},
    },
    {
        "binaries",
        {                                                       //
         "3+4", "3*4",                                          //
         "3+4+5", "3*4*5", "3+4*5", "3*4+5", "3*4+5",           //
         "3+(4+5)", "3*(4*5)", "3+(4*5)", "3*(4+5)", "3*(4+5)", //
         "(3+4)+5", "(3*4)*5", "(3+4)*5", "(3*4)+5", "(3*4)+5"},
    },
    {
        "xlfunction",
        {
            "pi()",
            "sin(4)",
            R"--(IIF(A1, "Red", "Green"))--",
        },
    },
    {
        "invalid",
        {
            "A9()", // an xlreference may not be followed by ()
            "",     // you didn't specify
        },
    },
    {
        "other",
        {
            "A-9",    // 1-letter identifier and binary operation
            "1 + +9", // unary plus accepted in number rule
        },
    },
    {
        "question",
        {
            "myfunc(myparam1, myparam2)",
            "A1",
            "AA234",
            "A1 + sin(A2+3)",
        },
    },
};

And run them:

for (auto& [cat, cases] : test_table) {
    for (std::string const& str : cases) {
        auto iter = begin(str), last(end(str));
        std::cout << std::setw(12) << cat << ": ";

        client::ast::program ast;
        if (parse(iter, last, parser, ast)) {
            std::cout << "parsed: " << ast;
        } else {
            std::cout << "failed: " << std::quoted(str);
        }

        if (iter == last) {
            std::cout << "\n";
        } else {
            std::cout << " unparsed: "
                      << std::quoted(std::string_view(iter, last)) << "\n";
        }
    }
}

Live Demo

Live On Coliru

//#define BOOST_SPIRIT_X3_DEBUG
#include <boost/fusion/adapted.hpp>
#include <boost/fusion/include/io.hpp>
#include <boost/spirit/home/x3.hpp>
#include <boost/spirit/home/x3/support/ast/position_tagged.hpp>
#include <boost/spirit/home/x3/support/utility/annotate_on_success.hpp>
#include <iostream>
#include <iomanip>
#include <map>
namespace x3 = boost::spirit::x3;

namespace client::ast {
    using identifier = std::string;
    //using identifier = boost::iterator_range<std::string::const_iterator>;
    
    struct string_literal  : std::string {
        using std::string::string;
        using std::string::operator=;

        friend std::ostream& operator<<(std::ostream& os, string_literal const& sl) {
            return os << std::quoted(sl) ;
        }
    };

    struct xlreference {
        std::string colname;
        size_t      rownum;
    };

    struct xlfunction; // fwd
    struct binary_op;  // fwd

    using expression = boost::variant<        //
        double,                               //
        string_literal,                       //
        identifier,                           //
        xlreference,                          //
        boost::recursive_wrapper<xlfunction>, //
        boost::recursive_wrapper<binary_op>   //
        >;

    struct xlfunction{
        identifier              name;
        std::vector<expression> args;

        friend std::ostream& operator<<(std::ostream& os, xlfunction const& xlf)
        {
            os << xlf.name << "(";
            char const* sep = "";
            for (auto& arg : xlf.args)
                os << std::exchange(sep, ", ") << arg;
            return os;
        }
    };

    struct binary_op {
        struct chained_t {
            char       op;
            expression e;
        };
        expression             lhs;
        std::vector<chained_t> chained;

        friend std::ostream& operator<<(std::ostream& os, binary_op const& bop)
        {
            os << "(" << bop.lhs;
            for (auto& rhs : bop.chained)
            os << rhs.op << rhs.e;
            return os << ")";
        }
    };

    using program = expression;
    using boost::fusion::operator<<;
} // namespace client::ast

BOOST_FUSION_ADAPT_STRUCT(client::ast::xlfunction, name, args)
BOOST_FUSION_ADAPT_STRUCT(client::ast::xlreference, colname, rownum)
BOOST_FUSION_ADAPT_STRUCT(client::ast::binary_op, lhs, chained)
BOOST_FUSION_ADAPT_STRUCT(client::ast::binary_op::chained_t, op, e)

namespace client::calculator_grammar {
    struct expression_class //: x3::annotate_on_success
    {
        //  Our error handler
        template <typename Iterator, typename Exception, typename Context>
        x3::error_handler_result on_error(Iterator& q, Iterator const& last,
                                          Exception const& x,
                                          Context const&   context)
        {
            std::cout                                          //
                << "Error! Expecting: " << x.which()           //
                << " here: \"" << std::string(x.where(), last) //
                << "\"" << std::endl;

            return x3::error_handler_result::fail;
        }
    };

    x3::rule<struct identifier_class, ast::identifier>  const identifier{"identifier"};
    x3::rule<struct xlreference,      ast::xlreference> const xlreference{"xlreference"};
    x3::rule<struct xlfunction_class, ast::xlfunction>  const xlfunction{"xlfunction"};
    x3::rule<struct factor_class,     ast::expression>  const factor{"factor"};
    x3::rule<struct expression_class, ast::binary_op>   const expression{"expression"};
    x3::rule<struct term_class,       ast::binary_op>   const term{"term"};

    auto const xlreference_def =
        x3::lexeme[+x3::alpha >> x3::uint_] /*>> !x3::char_('(')*/;

    auto const identifier_def =
        x3::raw[x3::lexeme[x3::alpha >> *(x3::alpha | '_') /*>> !x3::digit*/]];

    auto const string_literal =
        x3::rule<struct _, ast::string_literal>{"string_literal"} //
    = x3::lexeme['"' > *('\\' >> x3::char_ | ~x3::char_('"')) > '"'];

    auto const factor_def =        //
        xlfunction                 //
        | '(' >> expression >> ')' //
        | x3::double_              //
        | string_literal           //
        | xlreference              //
        | identifier               //
        ;

    auto const term_def       = factor >> *(x3::char_("*/") >>  factor);
    auto const expression_def = term >> *(x3::char_("-+") >> term);
    auto const xlfunction_def = identifier >> '(' >> -(expression % ',') >> ')';

    BOOST_SPIRIT_DEFINE(xlreference)
    BOOST_SPIRIT_DEFINE(identifier)
    BOOST_SPIRIT_DEFINE(xlfunction)
    BOOST_SPIRIT_DEFINE(term)
    BOOST_SPIRIT_DEFINE(factor)
    BOOST_SPIRIT_DEFINE(expression)

} // namespace client::calculator_grammar

int main() {
    std::vector<int> positions; // TODO

    auto parser = x3::with<struct position_cache_tag /*TODO*/>        //
        (std::ref(positions))                                         //
            [                                                         //
                x3::skip(x3::space)[                                  //
                    client::calculator_grammar::expression >> x3::eoi //
    ]                                                                 //
    ];

    struct {
        std::string              category;
        std::vector<std::string> cases;
    } test_table[] = {
        {
            "xlreference",
            {"A1", "A1111", "AbCdZ9876543", "i9", "i0"},
        },
        {
            "identifier",
            {"i", "id", "id_entifier"},
        },
        {
            "number",
            {"123", "inf", "-inf", "NaN", ".99e34", "1e-8", "1.e-8", "+9"},
        },
        {
            "binaries",
            {                                                       //
             "3+4", "3*4",                                          //
             "3+4+5", "3*4*5", "3+4*5", "3*4+5", "3*4+5",           //
             "3+(4+5)", "3*(4*5)", "3+(4*5)", "3*(4+5)", "3*(4+5)", //
             "(3+4)+5", "(3*4)*5", "(3+4)*5", "(3*4)+5", "(3*4)+5"},
        },
        {
            "xlfunction",
            {
                "pi()",
                "sin(4)",
                R"--(IIF(A1, "Red", "Green"))--",
            },
        },
        {
            "invalid",
            {
                "A9()", // an xlreference may not be followed by ()
                "",     // you didn't specify
            },
        },
        {
            "other",
            {
                "A-9",    // 1-letter identifier and binary operation
                "1 + +9", // unary plus accepted in number rule
            },
        },
        {
            "question",
            {
                "myfunc(myparam1, myparam2)",
                "A1",
                "AA234",
                "A1 + sin(A2+3)",
            },
        },
    };

    for (auto& [cat, cases] : test_table) {
        for (std::string const& str : cases) {
            auto iter = begin(str), last(end(str));
            std::cout << std::setw(12) << cat << ": ";

            client::ast::program ast;
            if (parse(iter, last, parser, ast)) {
                std::cout << "parsed: " << ast;
            } else {
                std::cout << "failed: " << std::quoted(str);
            }

            if (iter == last) {
                std::cout << "\n";
            } else {
                std::cout << " unparsed: "
                          << std::quoted(std::string_view(iter, last)) << "\n";
            }
        }
    }
}

Prints

 xlreference: parsed: (((A 1)))
 xlreference: parsed: (((A 1111)))
 xlreference: parsed: (((AbCdZ 9876543)))
 xlreference: parsed: (((i 9)))
 xlreference: parsed: (((i 0)))
  identifier: parsed: ((i))
  identifier: parsed: ((id))
  identifier: parsed: ((id_entifier))
      number: parsed: ((123))
      number: parsed: ((inf))
      number: parsed: ((-inf))
      number: parsed: ((nan))
      number: parsed: ((9.9e+33))
      number: parsed: ((1e-08))
      number: parsed: ((1e-08))
      number: parsed: ((9))
    binaries: parsed: ((3)+(4))
    binaries: parsed: ((3*4))
    binaries: parsed: ((3)+(4)+(5))
    binaries: parsed: ((3*4*5))
    binaries: parsed: ((3)+(4*5))
    binaries: parsed: ((3*4)+(5))
    binaries: parsed: ((3*4)+(5))
    binaries: parsed: ((3)+(((4)+(5))))
    binaries: parsed: ((3*((4*5))))
    binaries: parsed: ((3)+(((4*5))))
    binaries: parsed: ((3*((4)+(5))))
    binaries: parsed: ((3*((4)+(5))))
    binaries: parsed: ((((3)+(4)))+(5))
    binaries: parsed: ((((3*4))*5))
    binaries: parsed: ((((3)+(4))*5))
    binaries: parsed: ((((3*4)))+(5))
    binaries: parsed: ((((3*4)))+(5))
  xlfunction: parsed: ((pi())
  xlfunction: parsed: ((sin(((4))))
  xlfunction: parsed: ((IIF((((A 1))), (("Red")), (("Green"))))
     invalid: failed: "A9()" unparsed: "A9()"
     invalid: failed: ""
       other: parsed: ((A)-(9))
       other: parsed: ((1)+(9))
    question: parsed: ((myfunc((((myparam 1))), (((myparam 2)))))
    question: parsed: (((A 1)))
    question: parsed: (((AA 234)))
    question: parsed: (((A 1))+(sin((((A 2))+(3))))

The only two failed lines are as expected

DEBUG?

Simply uncomment

#define BOOST_SPIRIT_X3_DEBUG

And be slammed with additional noise:

    question: <expression>
  <try>A1 + sin(A2+3)</try>
  <term>
    <try>A1 + sin(A2+3)</try>
    <factor>
      <try>A1 + sin(A2+3)</try>
      <xlfunction>
        <try>A1 + sin(A2+3)</try>
        <identifier>
          <try>A1 + sin(A2+3)</try>
          <success>1 + sin(A2+3)</success>
          <attributes>[A]</attributes>
        </identifier>
        <fail/>
      </xlfunction>
      <string_literal>
        <try>A1 + sin(A2+3)</try>
        <fail/>
      </string_literal>
      <xlreference>
        <try>A1 + sin(A2+3)</try>
        <success> + sin(A2+3)</success>
        <attributes>[[A], 1]</attributes>
      </xlreference>
      <success> + sin(A2+3)</success>
      <attributes>[[A], 1]</attributes>
    </factor>
    <success> + sin(A2+3)</success>
    <attributes>[[[A], 1], []]</attributes>
  </term>
  <term>
    <try> sin(A2+3)</try>
    <factor>
      <try> sin(A2+3)</try>
      <xlfunction>
        <try> sin(A2+3)</try>
        <identifier>
          <try> sin(A2+3)</try>
          <success>(A2+3)</success>
          <attributes>[s, i, n]</attributes>
        </identifier>
        <expression>
          <try>A2+3)</try>
          <term>
            <try>A2+3)</try>
            <factor>
              <try>A2+3)</try>
              <xlfunction>
                <try>A2+3)</try>
                <identifier>
                  <try>A2+3)</try>
                  <success>2+3)</success>
                  <attributes>[A]</attributes>
                </identifier>
                <fail/>
              </xlfunction>
              <string_literal>
                <try>A2+3)</try>
                <fail/>
              </string_literal>
              <xlreference>
                <try>A2+3)</try>
                <success>+3)</success>
                <attributes>[[A], 2]</attributes>
              </xlreference>
              <success>+3)</success>
              <attributes>[[A], 2]</attributes>
            </factor>
            <success>+3)</success>
            <attributes>[[[A], 2], []]</attributes>
          </term>
          <term>
            <try>3)</try>
            <factor>
              <try>3)</try>
              <xlfunction>
                <try>3)</try>
                <identifier>
                  <try>3)</try>
                  <fail/>
                </identifier>
                <fail/>
              </xlfunction>
              <success>)</success>
              <attributes>3</attributes>
            </factor>
            <success>)</success>
            <attributes>[3, []]</attributes>
          </term>
          <success>)</success>
          <attributes>[[[[A], 2], []], [[+, [3, []]]]]</attributes>
        </expression>
        <success></success>
        <attributes>[[s, i, n], [[[[[A], 2], []], [[+, [3, []]]]]]]</attributes>
      </xlfunction>
      <success></success>
      <attributes>[[s, i, n], [[[[[A], 2], []], [[+, [3, []]]]]]]</attributes>
    </factor>
    <success></success>
    <attributes>[[[s, i, n], [[[[[A], 2], []], [[+, [3, []]]]]]], []]</attributes>
  </term>
  <success></success>
  <attributes>[[[[A], 1], []], [[+, [[[s, i, n], [[[[[A], 2], []], [[+, [3, []]]]]]], []]]]]</attributes>
</expression>
parsed: (((A 1))+(sin((((A 2))+(3))))
sehe
  • 374,641
  • 47
  • 450
  • 633
  • very detailed - thanks @sehe * args were optional for the function input - I originally used -(%) * char vs x3:char_ - those were typo's * #define BOOST_SPIRIT_X3_DEBUG places template requirements requiring << support (ie cout << val;) - assume that is why you declared operator< * +1 x3::eoi * FYI - for identifiers I saw a great example using ... !keywords >> +x3::alpha to prevent special words. * +1 skipper part of grammar * ok so x3::lexeme forces parsing space * +1 on recursive_wrapper * I think >> !x3::digit would be incorrect as it would still capture a char – Michael Oct 21 '21 at 22:46
  • cheers * ok * huh, ok :) * yes and yes * cheers * yes negative assertions are powerful (beware of excessive backtracking when done wrong, DEBUG output is handy to spot this) * cheers * it inhibits the skipper, [almost like `no_skip[]`](https://stackoverflow.com/questions/17072987/boost-spirit-skipper-issues/17073965#17073965) * cheers, I assumed you had seen this from the tutorials that you borrowed code from * `!p` assertion never capture, they merely affect match success. – sehe Oct 21 '21 at 22:52
  • Ok, so >> !x3::digit is different from ~x3::digit. That will be a useful distinction. – Michael Oct 21 '21 at 23:58
  • Indeed! `&p` and `!p` are assertions that don't consume input. `~charset` is just a charset (inverted, but still) – sehe Oct 22 '21 at 11:45