Семантика операторов в boost::spirit

В boost::spirit переопределяется множество операторов(*&! etc).

Как пример:
При реализации custom lexer правила можно добавить несколькими способами и непонятно, в чем между ними разница

// self  = word | number | any
// self %= word | number | any
self.add(word)(number)(any);
// self += word; self += number; self += any;

Или, допустим, дано правило:

rule = -(char_("*") >> char_("*")); // %$#?

Есть ли какой справочник("cheat sheet") для этой библиотеки?

Гуглить "закорючки" вида -(!a >> *b) довольно проблематично...

UPD[0x00]: Нашел сводную таблицу для одной из старых версий библиотеки.
Для актуальной версии есть quick reference (spirit::qi, spirit::lex), но там описания довольно размытые(ИМХО)

UPD[0x01]: пытаюсь побороть компилятор и распечатать совпадения с правилами(для начала)

#include "stdafx.hxx"
#include <iostream>

#include <functional>

namespace some_lang
{
using namespace boost::spirit;
namespace phx = boost::phoenix;


enum LexemeId
{
    LEX_ANY,
    LEX_WORD,
    LEX_NUMBER,
};
using char_type = char;
using string_copy = std::basic_string<char_type>;
using string_view = std::basic_string_view<char_type>;
using copy_iter = string_copy::const_iterator;
using view_iter = string_view::const_iterator;
using string_type = string_view;
using string_iter = string_type::iterator;
using string_pointer = string_type::const_pointer;

using token_type = lex::lexertl::token<
        string_pointer // should(?) be 'char_type *'
        , boost::mpl::vector<string_copy, string_view> // list of attr types, type should(?) have 'insert' method
        , boost::mpl::true_ // HaveState ??
        , LexemeId // tokenId type
>;
using lexer_type = lex::lexertl::lexer<token_type>;
using token_def = lex::token_def<
        string_view
        , char_type
        , LexemeId // tokenId type
>;

struct lang_lexer: public lex::lexer<lexer_type>
{
    lang_lexer()
        : lang_lexer::base_type{lex::match_flags::match_icase } // not case-sensitive
        , any(".", LEX_ANY)
        , word("[a-z_][\\w]+", LEX_WORD)
        , number("[\\d]+", LEX_NUMBER)
    {
        // try to print matched part
        //auto print = std::cout << phx::arg_names::arg1 << std::endl;
        //self = word | number | any;
        self.add
            (word)
            (number)
            (any)
        ;
        //auto s = self("code");//[print];
    }
    token_def any;
    token_def word;
    token_def number;
};

using lexer_iter = lang_lexer::iterator_type;
using lang_rule = qi::rule<lexer_iter>;

struct lang_grammar: qi::grammar<lexer_iter>
{
    using lexer = lang_lexer;
    using iterator = lexer_iter;
    using iter_range = boost::iterator_range<string_pointer>;

    explicit
    lang_grammar(const lexer& tok)
        : lang_grammar::base_type{start}
    {
        start = *(
                tok.any//[&print] // << error: no viable overloaded operator[] for type 'const token_def'
              | tok.word//[&print]
              | tok.number//[&print]
                );
    }

    static void print(const string_copy& s)
    {
        std::cout << s << std::endl;
    }
    static void print(string_view s)
    {
        std::cout << s << std::endl;
    }
    static void print(iter_range pair)
    {
        string_view s{pair.begin(), pair.end()};
        std::cout << s << std::endl;
    }

    lang_rule start;
};
}

namespace playground
{
using namespace std::literals;
using namespace some_lang;

auto s_begin(const string_type& s)
{
    return s.data();
}

auto s_end(const string_type& s)
{
    return s.data() + s.size();
}
auto s_begin(string_type& s)
{
    return s.data();
}

auto s_end(string_type& s)
{
    return s.data() + s.size();
}

auto tokenize_parse(const string_type& input)
{
    lang_lexer tok;
    lang_grammar g{tok};

    auto first = s_begin(input);
    auto last = s_end(input);

    auto r = lex::tokenize_and_parse(first, last, tok, g);

    std::cout << std::boolalpha << "r = " << r << std::endl;
}

}

int main()
{
    using namespace std::literals;
    auto input = "(*abcdef,ABCDEF,*)//{9876543210,}"s;

    std::cout << "grammar:" << std::endl;
    playground::tokenize_parse(input);

    return 0;
}

// precompiled header: stdafx.hxx
#pragma once

#include <vector>
#include <array>
#include <span>
#include <map>
#include <set>
#include <unordered_map>
#include <unordered_set>
#include <string>
#include <string_view>
#include <algorithm>

#include <boost/spirit/include/classic.hpp>
#include <boost/spirit/include/karma.hpp>
#include <boost/spirit/include/lex.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/lex_lexertl.hpp>

#include <boost/phoenix.hpp>
#include <boost/phoenix/operator.hpp>
#include <boost/phoenix/statement.hpp>
#include <boost/phoenix/stl/container.hpp>


Ответы (2 шт):

Автор решения: citn
  1. self = word | number | any - по сути это базовое присваивание по правилам: создает правило self, которое соответствует либо word, либо number, либо any
  2. self.add(word)(number)(any) - это уже более явный способ добавления правил, где вызывается метод add для добавления каждого элемента, полезно тем, что можно явно указать порядок добавления
  3. self%=word|number|any - тут %= используется для добавления правил к уже существующему правилу(да тавтология) - значит что можно добавлять новые элементы в правило, ПРИ ЭТОМ сохраняя уже определенные элементы
  4. self+=word;self+=number;self+=any - оператор += тоже используется для добавления как %=, но разница только в восприятии

(если нужно пояснить и последний пример - пишите, в контексте не понял к чему он)

ПРо справочник можно в главной документации посмотреть, но читать его не очень приятно. Говорю только за себя - мне удобно было изучать с помощью гитхаба, особенно примеры или та же Boost Spirit Wiki

→ Ссылка
Автор решения: Maxim Timakov

Документация:

Основные операторы для парсеров:

spirit::lex:

primitives

  • 'c' | lex::char_(c) - совпадение с символом
  • "str" | lex::string(str) - совпадение с regexp
  • token_def<attr> - совпадение с токеном, сохраняет attr
  • a | b | ... - совпадение с любым из выражений
  • l[f] - вызов f(semantic action) при совпадении с l(базовым типом лексера должен быть lex::lexertl::actor_lexer)

using lexer_base_type = lex::lexertl::actor_lexer
<
    token_type
>;

template<typename LexerBase = lexer_base_type>
struct lexer_type: lex::lexer<LexerBase>
{
    static constexpr unsigned int default_flags
        = lex::match_flags::match_default
        | lex::match_flags::match_icase
        | lex::match_flags::match_not_dot_newline
        ;
    using lexer_def = lexer_type::base_type::lexer_def;
    lexer_type()
        : lexer_type::base_type{default_flags}
    {

        declare(this->self);
    }

    void declare(lexer_def& self)
    {
        eol = "$"; // note: regexp
        point = '.'; // note: single char
        comment = R"(\/\/.*$)";
        auto regexp_line = lex::string(R"(\/\/.*$)");
        auto chars_list = lex::char_('a') | 'b';

        self = (  comment
                | regexp_line
                | chars_list
                | eol
                | point
        );
    }

    token_def<> point; // без сохранения совпадений
    token_def<lex::unused_type> eol; // без сохранения совпадений
    token_def<std::string> comment; // совпадения сохраняются в виде std::string
};

spirit::qi:

primitives

  • attr(Attrib a) - set attribute to value
  • eoi - end of input
  • eol - end of line
  • eps - empty string
    • eps(f) - ...
  • symbols<> - symbol table

unary parsers

  • & p - and predicate - look forward
  • * p - Kleene star - 0 or more matches of p
  • lexeme[p] - disable spacer for p
  • ! p - not predicate - match if p fails
  • omit[p] - ignore attr of p
  • + p - 1 or more matches
  • raw[p] - produces iterator pair [first, last) instead attribute
  • repeat - matches p n times
    • repeat[p] - same as Kleene
    • repeat(n)[p] - exactly n times
    • repeat(min, max)[p] - [min, max] times
    • repeat(min, inf)[p] - minimum min times
  • skip[a]/skip(p)[a] - restore skipper for a / use p as skipper

binary parsers

  • a - b - difference - works like (!b) && (a) - b should fail
  • a % b - list parser, same as a >> *(omit[b] >> a)

nary parsers

  • a | b | ... - alternative
  • a > b - expectation assert(a && b). throws exception if fail
  • a ^ b ^ ... - permutation - match a or b in any order
  • a >> b >> ... - sequence
  • a || b || ... - sequence Or - match a or b in sequence. equivalent to ((a >> -b) | b)

Пытался изучать код spirit v2, но там довольно сложно разобраться.
ИМХО, SpiritX3 проще написан.

→ Ссылка