diff --git a/clangify.sh b/clangify.sh index 9113952e..671c69b9 100755 --- a/clangify.sh +++ b/clangify.sh @@ -14,6 +14,7 @@ include=("common" \ "servatrice/src") exclude=("servatrice/src/smtp" \ "common/sfmt" \ +"common/lib" \ "oracle/src/zip" \ "oracle/src/lzma" \ "oracle/src/qt-json") diff --git a/cockatrice/cockatrice.qrc b/cockatrice/cockatrice.qrc index 3619cc0d..aff2f002 100644 --- a/cockatrice/cockatrice.qrc +++ b/cockatrice/cockatrice.qrc @@ -351,6 +351,7 @@ resources/tips/images/cockatrice_register.png resources/tips/images/cockatrice_wiki.png resources/tips/images/coin_flip.png + resources/tips/images/counter_expression.png resources/tips/images/face_down.png resources/tips/images/filter_games.png resources/tips/images/github_logo.png diff --git a/cockatrice/resources/tips/images/counter_expression.png b/cockatrice/resources/tips/images/counter_expression.png new file mode 100644 index 00000000..0e6bbcef Binary files /dev/null and b/cockatrice/resources/tips/images/counter_expression.png differ diff --git a/cockatrice/resources/tips/tips_of_the_day.xml b/cockatrice/resources/tips/tips_of_the_day.xml index 5625fbe0..aa2cb463 100644 --- a/cockatrice/resources/tips/tips_of_the_day.xml +++ b/cockatrice/resources/tips/tips_of_the_day.xml @@ -83,4 +83,10 @@ face_down.png 2018-03-01 + + Counter expressions + When setting a counter value, you can type a math expression in the box and the counter will be set to the result.<br>The "x" variable contains the current counter value. + counter_expression.png + 2019-02-02 + \ No newline at end of file diff --git a/cockatrice/src/abstractcounter.cpp b/cockatrice/src/abstractcounter.cpp index 42cf6ad8..d9095138 100644 --- a/cockatrice/src/abstractcounter.cpp +++ b/cockatrice/src/abstractcounter.cpp @@ -1,9 +1,11 @@ #include "abstractcounter.h" +#include "expression.h" #include "pb/command_inc_counter.pb.h" #include "pb/command_set_counter.pb.h" #include "player.h" #include "settingscache.h" #include +#include #include #include #include @@ -17,7 +19,7 @@ AbstractCounter::AbstractCounter(Player *_player, bool _useNameForShortcut, QGraphicsItem *parent) : QGraphicsItem(parent), player(_player), id(_id), name(_name), value(_value), - useNameForShortcut(_useNameForShortcut), hovered(false), aDec(0), aInc(0), dialogSemaphore(false), + useNameForShortcut(_useNameForShortcut), hovered(false), aDec(nullptr), aInc(nullptr), dialogSemaphore(false), deleteAfterDialog(false), shownInCounterArea(_shownInCounterArea) { setAcceptHoverEvents(true); @@ -114,7 +116,11 @@ void AbstractCounter::setValue(int _value) void AbstractCounter::mousePressEvent(QGraphicsSceneMouseEvent *event) { if (isUnderMouse() && player->getLocal()) { - if (event->button() == Qt::LeftButton) { + if (event->button() == Qt::MidButton || (QApplication::keyboardModifiers() & Qt::ShiftModifier)) { + if (menu) + menu->exec(event->screenPos()); + event->accept(); + } else if (event->button() == Qt::LeftButton) { Command_IncCounter cmd; cmd.set_counter_id(id); cmd.set_delta(1); @@ -126,10 +132,6 @@ void AbstractCounter::mousePressEvent(QGraphicsSceneMouseEvent *event) cmd.set_delta(-1); player->sendGameCommand(cmd); event->accept(); - } else if (event->button() == Qt::MidButton) { - if (menu) - menu->exec(event->screenPos()); - event->accept(); } } else event->ignore(); @@ -160,8 +162,12 @@ void AbstractCounter::setCounter() { bool ok; dialogSemaphore = true; - int newValue = QInputDialog::getInt(0, tr("Set counter"), tr("New value for counter '%1':").arg(name), value, - -2000000000, 2000000000, 1, &ok); + QString expression = QInputDialog::getText(nullptr, tr("Set counter"), tr("New value for counter '%1':").arg(name), + QLineEdit::Normal, QString::number(value), &ok); + + Expression exp(value); + int newValue = static_cast(exp.parse(expression)); + if (deleteAfterDialog) { deleteLater(); return; diff --git a/cockatrice/src/abstractcounter.h b/cockatrice/src/abstractcounter.h index 99bcd7ca..2e592348 100644 --- a/cockatrice/src/abstractcounter.h +++ b/cockatrice/src/abstractcounter.h @@ -11,6 +11,7 @@ class AbstractCounter : public QObject, public QGraphicsItem { Q_OBJECT Q_INTERFACES(QGraphicsItem) + protected: Player *player; int id; @@ -18,15 +19,17 @@ protected: int value; bool useNameForShortcut, hovered; - void mousePressEvent(QGraphicsSceneMouseEvent *event); - void hoverEnterEvent(QGraphicsSceneHoverEvent *event); - void hoverLeaveEvent(QGraphicsSceneHoverEvent *event); + void mousePressEvent(QGraphicsSceneMouseEvent *event) override; + void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override; + void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override; private: QAction *aSet, *aDec, *aInc; QMenu *menu; bool dialogSemaphore, deleteAfterDialog; bool shownInCounterArea; + bool shortcutActive; + private slots: void refreshShortcuts(); void incrementCounter(); @@ -39,14 +42,19 @@ public: bool _shownInCounterArea, int _value, bool _useNameForShortcut = false, - QGraphicsItem *parent = 0); - ~AbstractCounter(); + QGraphicsItem *parent = nullptr); + ~AbstractCounter() override; + + void retranslateUi(); + void setValue(int _value); + void setShortcutsActive(); + void setShortcutsInactive(); + void delCounter(); QMenu *getMenu() const { return menu; } - void retranslateUi(); int getId() const { @@ -64,12 +72,6 @@ public: { return value; } - void setValue(int _value); - void delCounter(); - - void setShortcutsActive(); - void setShortcutsInactive(); - bool shortcutActive; }; #endif diff --git a/cockatrice/src/dlg_tip_of_the_day.cpp b/cockatrice/src/dlg_tip_of_the_day.cpp index 67ecc400..37d6465f 100644 --- a/cockatrice/src/dlg_tip_of_the_day.cpp +++ b/cockatrice/src/dlg_tip_of_the_day.cpp @@ -13,7 +13,7 @@ #define MIN_TIP_IMAGE_HEIGHT 200 #define MIN_TIP_IMAGE_WIDTH 200 #define MAX_TIP_IMAGE_HEIGHT 300 -#define MAX_TIP_IMAGE_WIDTH 300 +#define MAX_TIP_IMAGE_WIDTH 500 DlgTipOfTheDay::DlgTipOfTheDay(QWidget *parent) : QDialog(parent) { @@ -149,9 +149,9 @@ void DlgTipOfTheDay::updateTip(int tipId) qDebug() << "Image failed to load from" << imagePath; imageLabel->clear(); } else { - int h = std::min(std::max(image->height(), MIN_TIP_IMAGE_HEIGHT), MAX_TIP_IMAGE_HEIGHT); + int h = std::min(std::max(imageLabel->height(), MIN_TIP_IMAGE_HEIGHT), MAX_TIP_IMAGE_HEIGHT); int w = std::min(std::max(imageLabel->width(), MIN_TIP_IMAGE_WIDTH), MAX_TIP_IMAGE_WIDTH); - imageLabel->setPixmap(image->scaled(h, w, Qt::KeepAspectRatio, Qt::SmoothTransformation)); + imageLabel->setPixmap(image->scaled(w, h, Qt::KeepAspectRatio, Qt::SmoothTransformation)); } date->setText("Tip added on: " + tip.getDate().toString("yyyy.MM.dd") + ""); @@ -163,9 +163,7 @@ void DlgTipOfTheDay::updateTip(int tipId) void DlgTipOfTheDay::resizeEvent(QResizeEvent *event) { - int h = imageLabel->height(); - int w = imageLabel->width(); - imageLabel->setPixmap(image->scaled(w, h, Qt::KeepAspectRatio, Qt::SmoothTransformation)); + imageLabel->setPixmap(image->scaled(imageLabel->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); QWidget::resizeEvent(event); } diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index f32d59a2..cf04f195 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -26,6 +26,7 @@ SET(common_SOURCES server_room.cpp serverinfo_user_container.cpp sfmt/SFMT.c + expression.cpp ) set(ORACLE_LIBS) diff --git a/common/expression.cpp b/common/expression.cpp new file mode 100644 index 00000000..1671d04b --- /dev/null +++ b/common/expression.cpp @@ -0,0 +1,108 @@ +#include "expression.h" +#include "./lib/peglib.h" + +#include +#include +#include +#include + +peg::parser math(R"( + EXPRESSION <- P0 + P0 <- P1 (P1_OPERATOR P1)* + P1 <- P2 (P2_OPERATOR P2)* + P2 <- P3 (P3_OPERATOR P3)* + P3 <- NUMBER / FUNCTION / VARIABLE / '(' P0 ')' + + P1_OPERATOR <- < [-+] > + P2_OPERATOR <- < [/*] > + P3_OPERATOR <- < '^' > + + NUMBER <- < '-'? [0-9]+ > + NAME <- < [a-z][a-z0-9]* > + VARIABLE <- < [x] > + FUNCTION <- NAME '(' EXPRESSION ( [,\n] EXPRESSION )* ')' + + %whitespace <- [ \t\r]* + )"); + +QMap> *default_functions = nullptr; + +Expression::Expression(double initial) : value(initial) +{ + if (default_functions == nullptr) { + default_functions = new QMap>(); + default_functions->insert("sin", [](double a) { return sin(a); }); + default_functions->insert("cos", [](double a) { return cos(a); }); + default_functions->insert("tan", [](double a) { return tan(a); }); + default_functions->insert("sqrt", [](double a) { return sqrt(a); }); + default_functions->insert("log", [](double a) { return log(a); }); + default_functions->insert("log10", [](double a) { return log(a); }); + default_functions->insert("trunc", [](double a) { return trunc(a); }); + default_functions->insert("abs", [](double a) { return abs(a); }); + + default_functions->insert("floor", [](double a) { return floor(a); }); + default_functions->insert("ceil", [](double a) { return ceil(a); }); + default_functions->insert("round", [](double a) { return round(a); }); + default_functions->insert("trunc", [](double a) { return trunc(a); }); + } + fns = QMap>(*default_functions); +} + +double Expression::eval(const peg::Ast &ast) +{ + const auto &nodes = ast.nodes; + if (ast.name == "NUMBER") { + return stod(ast.token); + } else if (ast.name == "FUNCTION") { + QString name = QString::fromStdString(nodes[0]->token); + if (!fns.contains(name)) + return 0; + return fns[name](eval(*nodes[1])); + } else if (ast.name == "VARIABLE") { + return value; + } else if (ast.name[0] == 'P') { + double result = eval(*nodes[0]); + for (int i = 1; i < nodes.size(); i += 2) { + double arg = eval(*nodes[i + 1]); + char operation = nodes[i]->token[0]; + switch (operation) { + case '+': + result += arg; + break; + case '-': + result -= arg; + break; + case '*': + result *= arg; + break; + case '/': + result /= arg; + break; + case '^': + result = pow(result, arg); + break; + default: + result = 0; + break; + } + } + return result; + } else { + return -1; + } +} + +double Expression::parse(const QString &expr) +{ + QByteArray ba = expr.toLocal8Bit(); + + math.enable_ast(); + + std::shared_ptr ast; + if (math.parse(ba.data(), ast)) { + ast = peg::AstOptimizer(true).optimize(ast); + return eval(*ast); + } + + return 0; +} diff --git a/common/expression.h b/common/expression.h new file mode 100644 index 00000000..8c6a9493 --- /dev/null +++ b/common/expression.h @@ -0,0 +1,28 @@ +#ifndef EXPRESSION_H +#define EXPRESSION_H + +#include +#include +#include + +namespace peg +{ +template struct AstBase; +struct EmptyType; +typedef AstBase Ast; +} // namespace peg + +class Expression +{ +public: + double value; + + explicit Expression(double initial = 0); + double parse(const QString &expr); + +private: + double eval(const peg::Ast &ast); + QMap> fns; +}; + +#endif diff --git a/common/lib/peglib.h b/common/lib/peglib.h new file mode 100644 index 00000000..5c5e4c01 --- /dev/null +++ b/common/lib/peglib.h @@ -0,0 +1,3293 @@ +// +// peglib.h +// +// Copyright (c) 2015-18 Yuji Hirose. All rights reserved. +// MIT License +// + +#ifndef CPPPEGLIB_PEGLIB_H +#define CPPPEGLIB_PEGLIB_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// guard for older versions of VC++ +#ifdef _MSC_VER +// VS2013 has no constexpr +#if (_MSC_VER == 1800) +#define PEGLIB_NO_CONSTEXPR_SUPPORT +#elif (_MSC_VER >= 1800) +// good to go +#else (_MSC_VER < 1800) +#error "Requires C+11 support" +#endif +#endif + +// define if the compiler doesn't support unicode characters reliably in the +// source code +//#define PEGLIB_NO_UNICODE_CHARS + +namespace peg { + +/*----------------------------------------------------------------------------- + * any + *---------------------------------------------------------------------------*/ + +class any +{ +public: + any() : content_(nullptr) {} + + any(const any& rhs) : content_(rhs.clone()) {} + + any(any&& rhs) : content_(rhs.content_) { + rhs.content_ = nullptr; + } + + template + any(const T& value) : content_(new holder(value)) {} + + any& operator=(const any& rhs) { + if (this != &rhs) { + if (content_) { + delete content_; + } + content_ = rhs.clone(); + } + return *this; + } + + any& operator=(any&& rhs) { + if (this != &rhs) { + if (content_) { + delete content_; + } + content_ = rhs.content_; + rhs.content_ = nullptr; + } + return *this; + } + + ~any() { + delete content_; + } + + bool is_undefined() const { + return content_ == nullptr; + } + + template < + typename T, + typename std::enable_if::value, std::nullptr_t>::type = nullptr + > + T& get() { + if (!content_) { + throw std::bad_cast(); + } + auto p = dynamic_cast*>(content_); + assert(p); + if (!p) { + throw std::bad_cast(); + } + return p->value_; + } + + template < + typename T, + typename std::enable_if::value, std::nullptr_t>::type = nullptr + > + T& get() { + return *this; + } + + template < + typename T, + typename std::enable_if::value, std::nullptr_t>::type = nullptr + > + const T& get() const { + assert(content_); + auto p = dynamic_cast*>(content_); + assert(p); + if (!p) { + throw std::bad_cast(); + } + return p->value_; + } + + template < + typename T, + typename std::enable_if::value, std::nullptr_t>::type = nullptr + > + const any& get() const { + return *this; + } + +private: + struct placeholder { + virtual ~placeholder() {} + virtual placeholder* clone() const = 0; + }; + + template + struct holder : placeholder { + holder(const T& value) : value_(value) {} + placeholder* clone() const override { + return new holder(value_); + } + T value_; + }; + + placeholder* clone() const { + return content_ ? content_->clone() : nullptr; + } + + placeholder* content_; +}; + +/*----------------------------------------------------------------------------- + * scope_exit + *---------------------------------------------------------------------------*/ + +// This is based on "http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4189". + +template +struct scope_exit +{ + explicit scope_exit(EF&& f) + : exit_function(std::move(f)) + , execute_on_destruction{true} {} + + scope_exit(scope_exit&& rhs) + : exit_function(std::move(rhs.exit_function)) + , execute_on_destruction{rhs.execute_on_destruction} { + rhs.release(); + } + + ~scope_exit() { + if (execute_on_destruction) { + this->exit_function(); + } + } + + void release() { + this->execute_on_destruction = false; + } + +private: + scope_exit(const scope_exit&) = delete; + void operator=(const scope_exit&) = delete; + scope_exit& operator=(scope_exit&&) = delete; + + EF exit_function; + bool execute_on_destruction; +}; + +template +auto make_scope_exit(EF&& exit_function) -> scope_exit { + return scope_exit::type>(std::forward(exit_function)); +} + +/*----------------------------------------------------------------------------- + * UTF8 functions + *---------------------------------------------------------------------------*/ + +inline size_t codepoint_length(const char *s8, size_t l) { + if (l) { + auto b = static_cast(s8[0]); + if ((b & 0x80) == 0) { + return 1; + } else if ((b & 0xE0) == 0xC0) { + return 2; + } else if ((b & 0xF0) == 0xE0) { + return 3; + } else if ((b & 0xF8) == 0xF0) { + return 4; + } + } + return 0; +} + +inline size_t encode_codepoint(char32_t cp, char *buff) { + if (cp < 0x0080) { + buff[0] = static_cast(cp & 0x7F); + return 1; + } else if (cp < 0x0800) { + buff[0] = static_cast(0xC0 | ((cp >> 6) & 0x1F)); + buff[1] = static_cast(0x80 | (cp & 0x3F)); + return 2; + } else if (cp < 0xD800) { + buff[0] = static_cast(0xE0 | ((cp >> 12) & 0xF)); + buff[1] = static_cast(0x80 | ((cp >> 6) & 0x3F)); + buff[2] = static_cast(0x80 | (cp & 0x3F)); + return 3; + } else if (cp < 0xE000) { + // D800 - DFFF is invalid... + return 0; + } else if (cp < 0x10000) { + buff[0] = static_cast(0xE0 | ((cp >> 12) & 0xF)); + buff[1] = static_cast(0x80 | ((cp >> 6) & 0x3F)); + buff[2] = static_cast(0x80 | (cp & 0x3F)); + return 3; + } else if (cp < 0x110000) { + buff[0] = static_cast(0xF0 | ((cp >> 18) & 0x7)); + buff[1] = static_cast(0x80 | ((cp >> 12) & 0x3F)); + buff[2] = static_cast(0x80 | ((cp >> 6) & 0x3F)); + buff[3] = static_cast(0x80 | (cp & 0x3F)); + return 4; + } + return 0; +} + +inline std::string encode_codepoint(char32_t cp) { + char buff[4]; + auto l = encode_codepoint(cp, buff); + return std::string(buff, l); +} + +inline bool decode_codepoint(const char *s8, size_t l, size_t &bytes, + char32_t &cp) { + if (l) { + auto b = static_cast(s8[0]); + if ((b & 0x80) == 0) { + bytes = 1; + cp = b; + return true; + } else if ((b & 0xE0) == 0xC0) { + if (l >= 2) { + bytes = 2; + cp = ((static_cast(s8[0] & 0x1F)) << 6) | + (static_cast(s8[1] & 0x3F)); + return true; + } + } else if ((b & 0xF0) == 0xE0) { + if (l >= 3) { + bytes = 3; + cp = ((static_cast(s8[0] & 0x0F)) << 12) | + ((static_cast(s8[1] & 0x3F)) << 6) | + (static_cast(s8[2] & 0x3F)); + return true; + } + } else if ((b & 0xF8) == 0xF0) { + if (l >= 4) { + bytes = 4; + cp = ((static_cast(s8[0] & 0x07)) << 18) | + ((static_cast(s8[1] & 0x3F)) << 12) | + ((static_cast(s8[2] & 0x3F)) << 6) | + (static_cast(s8[3] & 0x3F)); + return true; + } + } + } + return false; +} + +inline size_t decode_codepoint(const char *s8, size_t l, char32_t &out) { + size_t bytes; + if (decode_codepoint(s8, l, bytes, out)) { + return bytes; + } + return 0; +} + +inline char32_t decode_codepoint(const char *s8, size_t l) { + char32_t out = 0; + decode_codepoint(s8, l, out); + return out; +} + +inline std::u32string decode(const char *s8, size_t l) { + std::u32string out; + size_t i = 0; + while (i < l) { + auto beg = i++; + while (i < l && (s8[i] & 0xc0) == 0x80) { + i++; + } + out += decode_codepoint(&s8[beg], (i - beg)); + } + return out; +} + +/*----------------------------------------------------------------------------- + * resolve_escape_sequence + *---------------------------------------------------------------------------*/ + +inline bool is_hex(char c, int& v) { + if ('0' <= c && c <= '9') { + v = c - '0'; + return true; + } else if ('a' <= c && c <= 'f') { + v = c - 'a' + 10; + return true; + } else if ('A' <= c && c <= 'F') { + v = c - 'A' + 10; + return true; + } + return false; +} + +inline bool is_digit(char c, int& v) { + if ('0' <= c && c <= '9') { + v = c - '0'; + return true; + } + return false; +} + +inline std::pair parse_hex_number(const char* s, size_t n, size_t i) { + int ret = 0; + int val; + while (i < n && is_hex(s[i], val)) { + ret = static_cast(ret * 16 + val); + i++; + } + return std::make_pair(ret, i); +} + +inline std::pair parse_octal_number(const char* s, size_t n, size_t i) { + int ret = 0; + int val; + while (i < n && is_digit(s[i], val)) { + ret = static_cast(ret * 8 + val); + i++; + } + return std::make_pair(ret, i); +} + +inline std::string resolve_escape_sequence(const char* s, size_t n) { + std::string r; + r.reserve(n); + + size_t i = 0; + while (i < n) { + auto ch = s[i]; + if (ch == '\\') { + i++; + switch (s[i]) { + case 'n': r += '\n'; i++; break; + case 'r': r += '\r'; i++; break; + case 't': r += '\t'; i++; break; + case '\'': r += '\''; i++; break; + case '"': r += '"'; i++; break; + case '[': r += '['; i++; break; + case ']': r += ']'; i++; break; + case '\\': r += '\\'; i++; break; + case 'x': + case 'u': { + char32_t cp; + std::tie(cp, i) = parse_hex_number(s, n, i + 1); + r += encode_codepoint(cp); + break; + } + default: { + char32_t cp; + std::tie(cp, i) = parse_octal_number(s, n, i); + r += encode_codepoint(cp); + break; + } + } + } else { + r += ch; + i++; + } + } + return r; +} + +/*----------------------------------------------------------------------------- + * PEG + *---------------------------------------------------------------------------*/ + +/* +* Line information utility function +*/ +inline std::pair line_info(const char* start, const char* cur) { + auto p = start; + auto col_ptr = p; + auto no = 1; + + while (p < cur) { + if (*p == '\n') { + no++; + col_ptr = p + 1; + } + p++; + } + + auto col = p - col_ptr + 1; + + return std::make_pair(no, col); +} + +/* +* Semantic values +*/ +struct SemanticValues : protected std::vector +{ + // Input text + const char* path; + const char* ss; + + // Matched string + const char* c_str() const { return s_; } + size_t length() const { return n_; } + + std::string str() const { + return std::string(s_, n_); + } + + // Line number and column at which the matched string is + std::pair line_info() const { + return peg::line_info(ss, s_); + } + + // Choice count + size_t choice_count() const { return choice_count_; } + + // Choice number (0 based index) + size_t choice() const { return choice_; } + + // Tokens + std::vector> tokens; + + std::string token(size_t id = 0) const { + if (!tokens.empty()) { + assert(id < tokens.size()); + const auto& tok = tokens[id]; + return std::string(tok.first, tok.second); + } + return std::string(s_, n_); + } + + // Transform the semantic value vector to another vector + template + auto transform(size_t beg = 0, size_t end = static_cast(-1)) const -> vector { + return this->transform(beg, end, [](const any& v) { return v.get(); }); + } + + SemanticValues() : s_(nullptr), n_(0), choice_count_(0), choice_(0) {} + + using std::vector::iterator; + using std::vector::const_iterator; + using std::vector::size; + using std::vector::empty; + using std::vector::assign; + using std::vector::begin; + using std::vector::end; + using std::vector::rbegin; + using std::vector::rend; + using std::vector::operator[]; + using std::vector::at; + using std::vector::resize; + using std::vector::front; + using std::vector::back; + using std::vector::push_back; + using std::vector::pop_back; + using std::vector::insert; + using std::vector::erase; + using std::vector::clear; + using std::vector::swap; + using std::vector::emplace; + using std::vector::emplace_back; + +private: + friend class Context; + friend class Sequence; + friend class PrioritizedChoice; + friend class Holder; + + const char* s_; + size_t n_; + size_t choice_count_; + size_t choice_; + + template + auto transform(F f) const -> vector::type> { + vector::type> r; + for (const auto& v: *this) { + r.emplace_back(f(v)); + } + return r; + } + + template + auto transform(size_t beg, size_t end, F f) const -> vector::type> { + vector::type> r; + end = (std::min)(end, size()); + for (size_t i = beg; i < end; i++) { + r.emplace_back(f((*this)[i])); + } + return r; + } + + void reset() { + path = nullptr; + ss = nullptr; + tokens.clear(); + + s_ = nullptr; + n_ = 0; + choice_count_ = 0; + choice_ = 0; + } +}; + +/* + * Semantic action + */ +template < + typename R, typename F, + typename std::enable_if::value, std::nullptr_t>::type = nullptr, + typename... Args> +any call(F fn, Args&&... args) { + fn(std::forward(args)...); + return any(); +} + +template < + typename R, typename F, + typename std::enable_if::type, any>::value, std::nullptr_t>::type = nullptr, + typename... Args> +any call(F fn, Args&&... args) { + return fn(std::forward(args)...); +} + +template < + typename R, typename F, + typename std::enable_if< + !std::is_void::value && + !std::is_same::type, any>::value, std::nullptr_t>::type = nullptr, + typename... Args> +any call(F fn, Args&&... args) { + return any(fn(std::forward(args)...)); +} + +class Action +{ +public: + Action() = default; + + Action(const Action& rhs) : fn_(rhs.fn_) {} + + template ::value && !std::is_same::value, std::nullptr_t>::type = nullptr> + Action(F fn) : fn_(make_adaptor(fn, &F::operator())) {} + + template ::value, std::nullptr_t>::type = nullptr> + Action(F fn) : fn_(make_adaptor(fn, fn)) {} + + template ::value, std::nullptr_t>::type = nullptr> + Action(F /*fn*/) {} + + template ::value && !std::is_same::value, std::nullptr_t>::type = nullptr> + void operator=(F fn) { + fn_ = make_adaptor(fn, &F::operator()); + } + + template ::value, std::nullptr_t>::type = nullptr> + void operator=(F fn) { + fn_ = make_adaptor(fn, fn); + } + + template ::value, std::nullptr_t>::type = nullptr> + void operator=(F /*fn*/) {} + + Action& operator=(const Action& rhs) = default; + + operator bool() const { + return bool(fn_); + } + + any operator()(SemanticValues& sv, any& dt) const { + return fn_(sv, dt); + } + +private: + template + struct TypeAdaptor_sv { + TypeAdaptor_sv(std::function fn) + : fn_(fn) {} + any operator()(SemanticValues& sv, any& /*dt*/) { + return call(fn_, sv); + } + std::function fn_; + }; + + template + struct TypeAdaptor_csv { + TypeAdaptor_csv(std::function fn) + : fn_(fn) {} + any operator()(SemanticValues& sv, any& /*dt*/) { + return call(fn_, sv); + } + std::function fn_; + }; + + template + struct TypeAdaptor_sv_dt { + TypeAdaptor_sv_dt(std::function fn) + : fn_(fn) {} + any operator()(SemanticValues& sv, any& dt) { + return call(fn_, sv, dt); + } + std::function fn_; + }; + + template + struct TypeAdaptor_csv_dt { + TypeAdaptor_csv_dt(std::function fn) + : fn_(fn) {} + any operator()(SemanticValues& sv, any& dt) { + return call(fn_, sv, dt); + } + std::function fn_; + }; + + typedef std::function Fty; + + template + Fty make_adaptor(F fn, R (F::* /*mf*/)(SemanticValues& sv) const) { + return TypeAdaptor_sv(fn); + } + + template + Fty make_adaptor(F fn, R (F::* /*mf*/)(const SemanticValues& sv) const) { + return TypeAdaptor_csv(fn); + } + + template + Fty make_adaptor(F fn, R (F::* /*mf*/)(SemanticValues& sv)) { + return TypeAdaptor_sv(fn); + } + + template + Fty make_adaptor(F fn, R (F::* /*mf*/)(const SemanticValues& sv)) { + return TypeAdaptor_csv(fn); + } + + template + Fty make_adaptor(F fn, R (* /*mf*/)(SemanticValues& sv)) { + return TypeAdaptor_sv(fn); + } + + template + Fty make_adaptor(F fn, R (* /*mf*/)(const SemanticValues& sv)) { + return TypeAdaptor_csv(fn); + } + + template + Fty make_adaptor(F fn, R (F::* /*mf*/)(SemanticValues& sv, any& dt) const) { + return TypeAdaptor_sv_dt(fn); + } + + template + Fty make_adaptor(F fn, R (F::* /*mf*/)(const SemanticValues& sv, any& dt) const) { + return TypeAdaptor_csv_dt(fn); + } + + template + Fty make_adaptor(F fn, R (F::* /*mf*/)(SemanticValues& sv, any& dt)) { + return TypeAdaptor_sv_dt(fn); + } + + template + Fty make_adaptor(F fn, R (F::* /*mf*/)(const SemanticValues& sv, any& dt)) { + return TypeAdaptor_csv_dt(fn); + } + + template + Fty make_adaptor(F fn, R(* /*mf*/)(SemanticValues& sv, any& dt)) { + return TypeAdaptor_sv_dt(fn); + } + + template + Fty make_adaptor(F fn, R(* /*mf*/)(const SemanticValues& sv, any& dt)) { + return TypeAdaptor_csv_dt(fn); + } + + Fty fn_; +}; + +/* + * Semantic predicate + */ +// Note: 'parse_error' exception class should be be used in sematic action handlers to reject the rule. +struct parse_error { + parse_error() = default; + parse_error(const char* s) : s_(s) {} + const char* what() const { return s_.empty() ? nullptr : s_.c_str(); } +private: + std::string s_; +}; + +/* + * Result + */ +inline bool success(size_t len) { + return len != static_cast(-1); +} + +inline bool fail(size_t len) { + return len == static_cast(-1); +} + +/* + * Context + */ +class Context; +class Ope; +class Definition; + +typedef std::function Tracer; + +class Context +{ +public: + const char* path; + const char* s; + const size_t l; + + const char* error_pos; + const char* message_pos; + std::string message; // TODO: should be `int`. + + std::vector> value_stack; + size_t value_stack_size; + std::vector>> args_stack; + + size_t nest_level; + + bool in_token; + + std::shared_ptr whitespaceOpe; + bool in_whitespace; + + std::shared_ptr wordOpe; + + std::vector> capture_scope_stack; + + const size_t def_count; + const bool enablePackratParsing; + std::vector cache_registered; + std::vector cache_success; + + std::map, std::tuple> cache_values; + + std::function tracer; + + Context( + const char* a_path, + const char* a_s, + size_t a_l, + size_t a_def_count, + std::shared_ptr a_whitespaceOpe, + std::shared_ptr a_wordOpe, + bool a_enablePackratParsing, + Tracer a_tracer) + : path(a_path) + , s(a_s) + , l(a_l) + , error_pos(nullptr) + , message_pos(nullptr) + , value_stack_size(0) + , nest_level(0) + , in_token(false) + , whitespaceOpe(a_whitespaceOpe) + , in_whitespace(false) + , wordOpe(a_wordOpe) + , def_count(a_def_count) + , enablePackratParsing(a_enablePackratParsing) + , cache_registered(enablePackratParsing ? def_count * (l + 1) : 0) + , cache_success(enablePackratParsing ? def_count * (l + 1) : 0) + , tracer(a_tracer) + { + args_stack.resize(1); + capture_scope_stack.resize(1); + } + + template + void packrat(const char* a_s, size_t def_id, size_t& len, any& val, T fn) { + if (!enablePackratParsing) { + fn(val); + return; + } + + auto col = a_s - s; + auto idx = def_count * static_cast(col) + def_id; + + if (cache_registered[idx]) { + if (cache_success[idx]) { + auto key = std::make_pair(col, def_id); + std::tie(len, val) = cache_values[key]; + return; + } else { + len = static_cast(-1); + return; + } + } else { + fn(val); + cache_registered[idx] = true; + cache_success[idx] = success(len); + if (success(len)) { + auto key = std::make_pair(col, def_id); + cache_values[key] = std::make_pair(len, val); + } + return; + } + } + + SemanticValues& push() { + assert(value_stack_size <= value_stack.size()); + if (value_stack_size == value_stack.size()) { + value_stack.emplace_back(std::make_shared()); + } + auto& sv = *value_stack[value_stack_size++]; + if (!sv.empty()) { + sv.clear(); + } + sv.reset(); + sv.path = path; + sv.ss = s; + return sv; + } + + void pop() { + value_stack_size--; + } + + void push_args(const std::vector>& args) { + args_stack.push_back(args); + } + + void pop_args() { + args_stack.pop_back(); + } + + const std::vector>& top_args() const { + return args_stack[args_stack.size() - 1]; + } + + void push_capture_scope() { + capture_scope_stack.resize(capture_scope_stack.size() + 1); + } + + void pop_capture_scope() { + capture_scope_stack.pop_back(); + } + + void shift_capture_values() { + assert(capture_scope_stack.size() >= 2); + auto it = capture_scope_stack.rbegin(); + auto it_prev = it + 1; + for (const auto& kv: *it) { + (*it_prev)[kv.first] = kv.second; + } + } + + void set_error_pos(const char* a_s) { + if (error_pos < a_s) error_pos = a_s; + } + + void trace(const char* name, const char* a_s, size_t n, SemanticValues& sv, any& dt) const { + if (tracer) tracer(name, a_s, n, sv, *this, dt); + } +}; + +/* + * Parser operators + */ +class Ope +{ +public: + struct Visitor; + + virtual ~Ope() {} + virtual size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const = 0; + virtual void accept(Visitor& v) = 0; +}; + +class Sequence : public Ope +{ +public: + Sequence(const Sequence& rhs) : opes_(rhs.opes_) {} + +#if defined(_MSC_VER) && _MSC_VER < 1900 // Less than Visual Studio 2015 + // NOTE: Compiler Error C2797 on Visual Studio 2013 + // "The C++ compiler in Visual Studio does not implement list + // initialization inside either a member initializer list or a non-static + // data member initializer. Before Visual Studio 2013 Update 3, this was + // silently converted to a function call, which could lead to bad code + // generation. Visual Studio 2013 Update 3 reports this as an error." + template + Sequence(const Args& ...args) { + opes_ = std::vector>{ static_cast>(args)... }; + } +#else + template + Sequence(const Args& ...args) : opes_{ static_cast>(args)... } {} +#endif + + Sequence(const std::vector>& opes) : opes_(opes) {} + Sequence(std::vector>&& opes) : opes_(opes) {} + + size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override { + c.trace("Sequence", s, n, sv, dt); + auto& chldsv = c.push(); + size_t i = 0; + for (const auto& ope : opes_) { + c.nest_level++; + auto se = make_scope_exit([&]() { c.nest_level--; }); + const auto& rule = *ope; + auto len = rule.parse(s + i, n - i, chldsv, c, dt); + if (fail(len)) { + return static_cast(-1); + } + i += len; + } + sv.insert(sv.end(), chldsv.begin(), chldsv.end()); + sv.s_ = chldsv.c_str(); + sv.n_ = chldsv.length(); + sv.tokens.insert(sv.tokens.end(), chldsv.tokens.begin(), chldsv.tokens.end()); + return i; + } + + void accept(Visitor& v) override; + + std::vector> opes_; +}; + +class PrioritizedChoice : public Ope +{ +public: +#if defined(_MSC_VER) && _MSC_VER < 1900 // Less than Visual Studio 2015 + // NOTE: Compiler Error C2797 on Visual Studio 2013 + // "The C++ compiler in Visual Studio does not implement list + // initialization inside either a member initializer list or a non-static + // data member initializer. Before Visual Studio 2013 Update 3, this was + // silently converted to a function call, which could lead to bad code + // generation. Visual Studio 2013 Update 3 reports this as an error." + template + PrioritizedChoice(const Args& ...args) { + opes_ = std::vector>{ static_cast>(args)... }; + } +#else + template + PrioritizedChoice(const Args& ...args) : opes_{ static_cast>(args)... } {} +#endif + + PrioritizedChoice(const std::vector>& opes) : opes_(opes) {} + PrioritizedChoice(std::vector>&& opes) : opes_(opes) {} + + size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override { + c.trace("PrioritizedChoice", s, n, sv, dt); + size_t id = 0; + for (const auto& ope : opes_) { + c.nest_level++; + auto& chldsv = c.push(); + c.push_capture_scope(); + auto se = make_scope_exit([&]() { + c.nest_level--; + c.pop(); + c.pop_capture_scope(); + }); + const auto& rule = *ope; + auto len = rule.parse(s, n, chldsv, c, dt); + if (success(len)) { + sv.insert(sv.end(), chldsv.begin(), chldsv.end()); + sv.s_ = chldsv.c_str(); + sv.n_ = chldsv.length(); + sv.choice_count_ = opes_.size(); + sv.choice_ = id; + sv.tokens.insert(sv.tokens.end(), chldsv.tokens.begin(), chldsv.tokens.end()); + + c.shift_capture_values(); + return len; + } + id++; + } + return static_cast(-1); + } + + void accept(Visitor& v) override; + + size_t size() const { return opes_.size(); } + + std::vector> opes_; +}; + +class ZeroOrMore : public Ope +{ +public: + ZeroOrMore(const std::shared_ptr& ope) : ope_(ope) {} + + size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override { + c.trace("ZeroOrMore", s, n, sv, dt); + auto save_error_pos = c.error_pos; + size_t i = 0; + while (n - i > 0) { + c.nest_level++; + c.push_capture_scope(); + auto se = make_scope_exit([&]() { + c.nest_level--; + c.pop_capture_scope(); + }); + auto save_sv_size = sv.size(); + auto save_tok_size = sv.tokens.size(); + const auto& rule = *ope_; + auto len = rule.parse(s + i, n - i, sv, c, dt); + if (success(len)) { + c.shift_capture_values(); + } else { + if (sv.size() != save_sv_size) { + sv.erase(sv.begin() + static_cast(save_sv_size)); + } + if (sv.tokens.size() != save_tok_size) { + sv.tokens.erase(sv.tokens.begin() + static_cast(save_tok_size)); + } + c.error_pos = save_error_pos; + break; + } + i += len; + } + return i; + } + + void accept(Visitor& v) override; + + std::shared_ptr ope_; +}; + +class OneOrMore : public Ope +{ +public: + OneOrMore(const std::shared_ptr& ope) : ope_(ope) {} + + size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override { + c.trace("OneOrMore", s, n, sv, dt); + size_t len = 0; + { + c.nest_level++; + c.push_capture_scope(); + auto se = make_scope_exit([&]() { + c.nest_level--; + c.pop_capture_scope(); + }); + const auto& rule = *ope_; + len = rule.parse(s, n, sv, c, dt); + if (success(len)) { + c.shift_capture_values(); + } else { + return static_cast(-1); + } + } + auto save_error_pos = c.error_pos; + auto i = len; + while (n - i > 0) { + c.nest_level++; + c.push_capture_scope(); + auto se = make_scope_exit([&]() { + c.nest_level--; + c.pop_capture_scope(); + }); + auto save_sv_size = sv.size(); + auto save_tok_size = sv.tokens.size(); + const auto& rule = *ope_; + len = rule.parse(s + i, n - i, sv, c, dt); + if (success(len)) { + c.shift_capture_values(); + } else { + if (sv.size() != save_sv_size) { + sv.erase(sv.begin() + static_cast(save_sv_size)); + } + if (sv.tokens.size() != save_tok_size) { + sv.tokens.erase(sv.tokens.begin() + static_cast(save_tok_size)); + } + c.error_pos = save_error_pos; + break; + } + i += len; + } + return i; + } + + void accept(Visitor& v) override; + + std::shared_ptr ope_; +}; + +class Option : public Ope +{ +public: + Option(const std::shared_ptr& ope) : ope_(ope) {} + + size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override { + c.trace("Option", s, n, sv, dt); + auto save_error_pos = c.error_pos; + c.nest_level++; + auto save_sv_size = sv.size(); + auto save_tok_size = sv.tokens.size(); + c.push_capture_scope(); + auto se = make_scope_exit([&]() { + c.nest_level--; + c.pop_capture_scope(); + }); + const auto& rule = *ope_; + auto len = rule.parse(s, n, sv, c, dt); + if (success(len)) { + c.shift_capture_values(); + return len; + } else { + if (sv.size() != save_sv_size) { + sv.erase(sv.begin() + static_cast(save_sv_size)); + } + if (sv.tokens.size() != save_tok_size) { + sv.tokens.erase(sv.tokens.begin() + static_cast(save_tok_size)); + } + c.error_pos = save_error_pos; + return 0; + } + } + + void accept(Visitor& v) override; + + std::shared_ptr ope_; +}; + +class AndPredicate : public Ope +{ +public: + AndPredicate(const std::shared_ptr& ope) : ope_(ope) {} + + size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override { + c.trace("AndPredicate", s, n, sv, dt); + c.nest_level++; + auto& chldsv = c.push(); + c.push_capture_scope(); + auto se = make_scope_exit([&]() { + c.nest_level--; + c.pop(); + c.pop_capture_scope(); + }); + const auto& rule = *ope_; + auto len = rule.parse(s, n, chldsv, c, dt); + if (success(len)) { + return 0; + } else { + return static_cast(-1); + } + } + + void accept(Visitor& v) override; + + std::shared_ptr ope_; +}; + +class NotPredicate : public Ope +{ +public: + NotPredicate(const std::shared_ptr& ope) : ope_(ope) {} + + size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override { + c.trace("NotPredicate", s, n, sv, dt); + auto save_error_pos = c.error_pos; + c.nest_level++; + auto& chldsv = c.push(); + c.push_capture_scope(); + auto se = make_scope_exit([&]() { + c.nest_level--; + c.pop(); + c.pop_capture_scope(); + }); + const auto& rule = *ope_; + auto len = rule.parse(s, n, chldsv, c, dt); + if (success(len)) { + c.set_error_pos(s); + return static_cast(-1); + } else { + c.error_pos = save_error_pos; + return 0; + } + } + + void accept(Visitor& v) override; + + std::shared_ptr ope_; +}; + +class LiteralString : public Ope + , public std::enable_shared_from_this +{ +public: + LiteralString(const std::string& s) + : lit_(s) + , init_is_word_(false) + , is_word_(false) + {} + + size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override; + + void accept(Visitor& v) override; + + std::string lit_; + mutable bool init_is_word_; + mutable bool is_word_; +}; + +class CharacterClass : public Ope + , public std::enable_shared_from_this +{ +public: + CharacterClass(const std::string& s) { + auto chars = decode(s.c_str(), s.length()); + auto i = 0u; + while (i < chars.size()) { + if (i + 2 < chars.size() && chars[i + 1] == '-') { + auto cp1 = chars[i]; + auto cp2 = chars[i + 2]; + ranges_.emplace_back(std::make_pair(cp1, cp2)); + i += 3; + } else { + auto cp = chars[i]; + ranges_.emplace_back(std::make_pair(cp, cp)); + i += 1; + } + } + } + + CharacterClass(const std::vector>& ranges) : ranges_(ranges) {} + + size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override { + c.trace("CharacterClass", s, n, sv, dt); + + if (n < 1) { + c.set_error_pos(s); + return static_cast(-1); + } + + char32_t cp; + auto len = decode_codepoint(s, n, cp); + + if (!ranges_.empty()) { + for (const auto& range: ranges_) { + if (range.first <= cp && cp <= range.second) { + return len; + } + } + } + + c.set_error_pos(s); + return static_cast(-1); + } + + void accept(Visitor& v) override; + + std::vector> ranges_; +}; + +class Character : public Ope + , public std::enable_shared_from_this +{ +public: + Character(char ch) : ch_(ch) {} + + size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override { + c.trace("Character", s, n, sv, dt); + if (n < 1 || s[0] != ch_) { + c.set_error_pos(s); + return static_cast(-1); + } + return 1; + } + + void accept(Visitor& v) override; + + char ch_; +}; + +class AnyCharacter : public Ope + , public std::enable_shared_from_this +{ +public: + size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override { + c.trace("AnyCharacter", s, n, sv, dt); + auto len = codepoint_length(s, n); + if (len < 1) { + c.set_error_pos(s); + return static_cast(-1); + } + return len; + } + + void accept(Visitor& v) override; +}; + +class CaptureScope : public Ope +{ +public: + CaptureScope(const std::shared_ptr& ope) + : ope_(ope) {} + + size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override { + c.push_capture_scope(); + auto se = make_scope_exit([&]() { + c.pop_capture_scope(); + }); + const auto& rule = *ope_; + auto len = rule.parse(s, n, sv, c, dt); + return len; + } + + void accept(Visitor& v) override; + + std::shared_ptr ope_; +}; + +class Capture : public Ope +{ +public: + typedef std::function MatchAction; + + Capture(const std::shared_ptr& ope, MatchAction ma) + : ope_(ope), match_action_(ma) {} + + size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override { + const auto& rule = *ope_; + auto len = rule.parse(s, n, sv, c, dt); + if (success(len) && match_action_) { + match_action_(s, len, c); + } + return len; + } + + void accept(Visitor& v) override; + + std::shared_ptr ope_; + MatchAction match_action_; +}; + +class TokenBoundary : public Ope +{ +public: + TokenBoundary(const std::shared_ptr& ope) : ope_(ope) {} + + size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override; + + void accept(Visitor& v) override; + + std::shared_ptr ope_; +}; + +class Ignore : public Ope +{ +public: + Ignore(const std::shared_ptr& ope) : ope_(ope) {} + + size_t parse(const char* s, size_t n, SemanticValues& /*sv*/, Context& c, any& dt) const override { + const auto& rule = *ope_; + auto& chldsv = c.push(); + auto se = make_scope_exit([&]() { + c.pop(); + }); + return rule.parse(s, n, chldsv, c, dt); + } + + void accept(Visitor& v) override; + + std::shared_ptr ope_; +}; + +typedef std::function Parser; + +class User : public Ope +{ +public: + User(Parser fn) : fn_(fn) {} + size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override { + c.trace("User", s, n, sv, dt); + assert(fn_); + return fn_(s, n, sv, dt); + } + void accept(Visitor& v) override; + std::function fn_; +}; + +class WeakHolder : public Ope +{ +public: + WeakHolder(const std::shared_ptr& ope) : weak_(ope) {} + + size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override { + auto ope = weak_.lock(); + assert(ope); + const auto& rule = *ope; + return rule.parse(s, n, sv, c, dt); + } + + void accept(Visitor& v) override; + + std::weak_ptr weak_; +}; + +class Holder : public Ope +{ +public: + Holder(Definition* outer) + : outer_(outer) {} + + size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override; + + void accept(Visitor& v) override; + + any reduce(SemanticValues& sv, any& dt) const; + + std::shared_ptr ope_; + Definition* outer_; + + friend class Definition; +}; + +typedef std::unordered_map Grammar; + +class Reference : public Ope + , public std::enable_shared_from_this +{ +public: + Reference( + const Grammar& grammar, + const std::string& name, + const char* s, + bool is_macro, + const std::vector>& args) + : grammar_(grammar) + , name_(name) + , s_(s) + , is_macro_(is_macro) + , args_(args) + , rule_(nullptr) + , iarg_(0) + {} + + size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override; + + void accept(Visitor& v) override; + + std::shared_ptr get_core_operator() const; + + const Grammar& grammar_; + const std::string name_; + const char* s_; + + const bool is_macro_; + const std::vector> args_; + + Definition* rule_; + size_t iarg_; +}; + +class Whitespace : public Ope +{ +public: + Whitespace(const std::shared_ptr& ope) : ope_(ope) {} + + size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override { + if (c.in_whitespace) { + return 0; + } + c.in_whitespace = true; + auto se = make_scope_exit([&]() { c.in_whitespace = false; }); + const auto& rule = *ope_; + return rule.parse(s, n, sv, c, dt); + } + + void accept(Visitor& v) override; + + std::shared_ptr ope_; +}; + +class BackReference : public Ope +{ +public: + BackReference(const std::string& name) : name_(name) {} + + size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override; + + void accept(Visitor& v) override; + + std::string name_; +}; + +/* + * Factories + */ +template +std::shared_ptr seq(Args&& ...args) { + return std::make_shared(static_cast>(args)...); +} + +template +std::shared_ptr cho(Args&& ...args) { + return std::make_shared(static_cast>(args)...); +} + +inline std::shared_ptr zom(const std::shared_ptr& ope) { + return std::make_shared(ope); +} + +inline std::shared_ptr oom(const std::shared_ptr& ope) { + return std::make_shared(ope); +} + +inline std::shared_ptr opt(const std::shared_ptr& ope) { + return std::make_shared