Семантика операторов в 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 шт):
self = word | number | any
- по сути это базовое присваивание по правилам: создает правилоself
, которое соответствует либоword
, либоnumber
, либоany
self.add(word)(number)(any)
- это уже более явный способ добавления правил, где вызывается методadd
для добавления каждого элемента, полезно тем, что можно явно указать порядок добавленияself%=word|number|any
- тут%=
используется для добавления правил к уже существующему правилу(да тавтология) - значит что можно добавлять новые элементы в правило, ПРИ ЭТОМ сохраняя уже определенные элементыself+=word;self+=number;self+=any
- оператор+=
тоже используется для добавления как%=
, но разница только в восприятии
(если нужно пояснить и последний пример - пишите, в контексте не понял к чему он)
ПРо справочник можно в главной документации посмотреть, но читать его не очень приятно. Говорю только за себя - мне удобно было изучать с помощью гитхаба, особенно примеры или та же Boost Spirit Wiki
Документация:
Основные операторы для парсеров:
spirit::lex:
primitives
'c' | lex::char_(c)
- совпадение с символом"str" | lex::string(str)
- совпадение с regexptoken_def<attr>
- совпадение с токеном, сохраняет attra | 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 проще написан.