Введение
Этот пост написан для тех, кто глубоко погружён в разработку компиляторов или DSL, часто сталкивается с задачами парсинга, строит сложные конфигурации или движки с нуля. Этот инструмент предназначен для разбора большинства контекстно-свободных языков с минимальными усилиями. Он не претендует на то, чтобы генерировать код, который невозможно превзойти по производительности, — добиться более высокой производительности в написанном вручную парсере всё же сложнее.
Немного теории (можно пропустить)
Парсеры — это инструменты, преобразующие текстовые шаблоны в более машинно-дружественное представление. Генераторы парсеров, соответственно, генерируют парсеры. Процесс часто делится на два этапа, хотя иногда они объединяются. Лексер обрабатывает исходный текст и производит токены — так называемые независимые фрагменты текста. Затем эти фрагменты обрабатываются парсером, поскольку их порядок влияет на итоговое представление. Результат может быть выведен как CST или AST. CST (Concrete Syntax Tree, конкретное синтаксическое дерево) — представление, предшествующее AST, где доступны все токены правила парсера. AST сокращает это представление, оставляя только значимые токены (например, группирующие кавычки против значения внутри группы). Другими словами, AST убирает из представления весь синтаксический контекст, поэтому теоретически два AST могут быть эквивалентны при разном синтаксисе, если совпадает семантика, — но для CST это неприменимо.
Для лексера используется NFA или DFA. Коротко говоря, это таблицы, где символ сопоставляется состоянию, у которого следующий символ сопоставляется другому состоянию — и так до конца ввода. У NFA на одном символе может быть несколько следующих состояний, в то время как DFA детерминирован и сопоставляет один символ ровно одному состоянию. NFA проще строить из регулярного выражения и часто служит промежуточным представлением при статической генерации. Классические DFA только сопоставляли текст и не имели возможности захвата (capture). При обходе DFA нельзя было выполнять произвольный семантический код — семантика выполнялась только в конце. Чтобы построить AST, приходилось повторно разбирать некоторые фрагменты и извлекать данные. Относительно недавно (около 2000 года) были изобретены TNFA и TDFA, развитие которых продолжается и по сей день (2026). Коротко — это позволяет захватывать данные прямо во время обхода таблицы, так что построение и CST, и AST может быть полностью автоматизировано. Повторный разбор тоже не требуется.
Парсеры — более развитая тема. Есть алгоритмы построения LL (нисходящий разбор) и LR (восходящий разбор). Этот генератор реализует оба, но именно нисходящий разбор реализован и хорошо протестирован. Коротко, это подход “функция на правило”, где функции могут рекурсивно вызывать друг друга — тот же подход, что применяется в написанных вручную парсерах, поскольку промышленные LR-парсеры писать вручную непрактично. Десятилетиями генераторы парсеров не умели выдавать хорошую диагностику ошибок и стратегии восстановления, не имели устоявшихся техник автоматического построения AST, не позволяли добавлять семантические действия без раздувания грамматики в языконезависимом виде — поэтому сообщество считало их игрушкой, и, например, все крупные компиляторы используют написанные вручную LL-парсеры.
Зачем ещё один генератор парсеров
Почти 3 года назад мне понадобился парсер. Результат работы большинства генераторов парсеров наполовину пригоден к использованию “из коробки”. AST, сообщения об ошибках и восстановление после них приходится строить самостоятельно (ANTLR), либо писать устаревшие грамматики прямо с кодом (Flex + Bison). Либо использовать генераторы, выдающие парсер только под одну целевую платформу, либо вообще не генерирующие парсер, а использующие возможности языка для обработки текста (на Boost::Spirit больно смотреть). Если вам действительно нужно что-то production-ready, рано или поздно вы перепишете это в написанный вручную парсер. А если ваша грамматика меняется — все изменения придётся поддерживать самостоятельно.
Недавно этот вопрос снова подняли, потому что тема, которая десятилетиями считалась решённой, на деле не решена в плане программного обеспечения. https://blog.adamant-lang.org/2019/dreaming-of-a-parser-generator/
Желаемое состояние этого генератора парсеров в основном следует требованиям из статьи по ссылке выше:
Композируемые грамматики
Инкрементальность
Сообщения об ошибках и восстановление после них
Генерация значений токенов
Автоматическое построение AST
Восстановление компилятора после ошибок
Это наиболее значимые пункты, но я бы добавил ещё:
Статическая генерация. Никакой генерации в рантайме
Отсутствие зависимостей — сгенерированный парсер должен легко настраиваться
Вывод на несколько языков
Семантические действия и обработка ошибок прямо в грамматике, без её раздувания
Гибкость использования как в тривиальных, так и в очень крупных проектах
Простой в использовании API
Архитектура и использование
Лексер генерируется как TDFA с поддержкой: шаблонов (patterns) в грамматике + вложенных токенов (без потери структуры AST) + правил уровня парсера внутри токенов + семантических действий во время обхода.
TEMPLATED_TYPE: 'template' __WS 'type' __WS @ '==' | '!=' __WS @ 'int' | 'str' | 'bool' @{op, type} ; main: TEMPLATED_TYPE;
TEMPLATED_TYPE ├── op: == └── type: str
Здесь захвачено то, что помечено как @, а итоговый токен выглядит как AST. Захват для вложенных токенов тоже реализован, но нужны небольшие доработки генератора, чтобы он заработал. Архитектура поддерживает правила уровня парсера, но здесь тоже нужны маленькие доработки. Но для большинства грамматик, текущих функций достаточно
Парсер в этой реализации — LL(k), но в будущем может быть расширен до LL(*). Его AST строится полностью автоматически, с автоматической поддержкой рекурсивных узлов, и готов к использованию сразу же.
Самоописывающая грамматика
input: "a: (INT ID '=' '0') | (ID | INT);" ID: @ ([a-zA-Z] [a-zA-Z0-9_]*) {@} ; rule: @ ID ':' @ #value+ ';' @{name, v} #capture: '@' (\s0 @ ID)? {@} ; #value: @ #pattern @ #alternative* @ #QUANTIFIER? @{v, alts, quantifier} #pattern: @ ID | #STRING | #SPACE | group {@} ; #STRING: '\'' @ [^']* '\'' {@} ; #SPACE: '\\s' {@} ; #group: '(' @ #value+ ')' {@} ; #alternative: '|' @ pattern {@} ; #QUANTIFIER: @ '?' | '+' | '*' {@} ; ; ; main: @ rule {@} ;
main └── value ├── rule ├── name ├── ID │ └── value: a └── v └── item ├── rule_value ├── v ├── rule_value_pattern │ └── value │ ├── rule_value_group │ └── value │ ├── item │ ├── rule_value │ │ ├── v │ │ ├── rule_value_pattern │ │ │ └── value │ │ │ ├── ID │ │ │ └── value: INT │ │ ├── alts │ │ └── quantifier │ │ ├── <empty> │ ├── item │ ├── rule_value │ │ ├── v │ │ ├── rule_value_pattern │ │ │ └── value │ │ │ ├── ID │ │ │ └── value: ID │ │ ├── alts │ │ └── quantifier │ │ ├── <empty> │ ├── item │ ├── rule_value │ │ ├── v │ │ ├── rule_value_pattern │ │ │ └── value │ │ │ ├── rule_value_STRING │ │ │ └── value: = │ │ ├── alts │ │ └── quantifier │ │ ├── <empty> │ └── item │ ├── rule_value │ ├── v │ ├── rule_value_pattern │ │ └── value │ │ ├── rule_value_STRING │ │ └── value: 0 │ ├── alts │ └── quantifier │ ├── <empty> ├── alts └── item │ ├── rule_value_alternative │ └── value │ ├── rule_value_pattern │ └── value │ ├── rule_value_group │ └── value │ └── item │ ├── rule_value │ ├── v │ ├── rule_value_pattern │ │ └── value │ │ ├── ID │ │ └── value: ID │ ├── alts │ └── item │ │ ├── rule_value_alternative │ │ └── value │ │ ├── rule_value_pattern │ │ └── value │ │ ├── ID │ │ └── value: INT │ └── quantifier │ ├── <empty> └── quantifier ├── <empty>
Этот фрагмент — не пример грамматики, а собственный синтаксис правил ISPA, записанный на ISPA. Генератор самоприменим (self-hosted): тот же DSL, которым описывают C или JSON, достаточно выразителен, чтобы описать самого себя.
Несколько примитивов, чтобы читать его:
@помечает точку захвата (capture point) — начинающийся там фрагмент становится доступен семантическому действию. Это механизм тегирования TDFA, проявляющийся прямо в синтаксисе грамматики, а не прикрученный сверху.{...}— семантическое действие.{@}— это сокращение для “вернуть единственный захват как есть”; используется в листовых правилах вродеID,STRINGилиQUANTIFIER, которые просто возвращают совпавший текст.@{field1, field2}строит узел AST прямо из именованных захватов — без отдельного кода построения дерева. Действие@{name, v}в правилеruleозначает: “узел, который порождает это правило, имеет полеname(из захватаID) и полеv(из повторяющихся захватов#value)”. Именно это на практике означает “автоматический AST”: форма действия — это и есть форма узла.#name:объявляет вложенное, локально-ограниченное правило — видимое только внутри содержащего его правила.#value,#pattern,#captureи подобные существуют только внутриrule;#pattern,#STRINGи аналогичные — только внутри#value. Это не даёт служебным внутриграмматическим правилам засорять глобальное пространство имён, вместо того чтобы заставлять каждое правило генератора жить на верхнем уровне.Стандартные regex-подобные операторы работают как обычно:
+(один или более),*(ноль или более),?(опционально),|(альтернатива), и строковые литералы в кавычках для точного текста.\s— встроенный токен пропуска пробелов.
Читая сверху вниз: ID совпадает с идентификатором (буква, затем буквы/цифры/подчёркивание). rule — это имя, двоеточие, одно или более #value, и терминатор — где каждый #value представляет собой #pattern (ссылку на ID, строку, пробел, или заключённую в скобки #group), за которым опционально следуют альтернативы и квантификатор. main — это просто точка входа: файл грамматики — это одно или более правил.
Проще код для взаимодействия с парсером я представить не могу:
#include <Parser.h> #include <iostream> // output to console #include <fstream> // output to file int main(int argc, const char** argv) { if (argc < 2) { std::cout << "Usage: " << argv[0] << " \"<input>\"\n"; return 1; } Parser::Lexer lexer; lexer.makeTokens(argv[1]); Parser::Parser parser; Parser::Types::main &node = parser.parse(lexer); std::ofstream of("tree.txt"); of << node << "\n\n"; }
Как выглядит сгенерированный парсер
// Parser.h #ifndef PARSER_H #define PARSER_H #include <string> #include <vector> #include <unordered_map> #include <array> #include <string> #include <variant> #include <optional> #include <memory> #include <ispastdlib.hpp> namespace Parser { enum class Tokens { // Все имена токенов. Полезно для switch NONE, AUTO_5, AUTO_4, AUTO_2, AUTO_1, AUTO_3, rule_value_STRING, __WS, rule_value_SPACE, NUMBER, AUTO_0, rule_value_QUANTIFIER, ID, }; enum class Rules { // аналогично NONE, main, rule_value_alternative, rule_value_pattern, rule_value_group, rule_value, rule_capture, rule, }; namespace FlatTypes { /* а тут уже все типы - структуры, которые создаються в грамматике через @ автоматически создает std::shared_ptr для само-вызывающихся структур shared_ptr легче использовать из-за возможности копирования. unique_ptr в текущей архитектуре не даст скомпилировать код, а фикс требует редактирования фундамента */ struct AUTO_5; struct AUTO_4; struct AUTO_2; struct AUTO_1; struct AUTO_3; struct rule_value_STRING; struct rule; struct rule_value; struct __WS; struct rule_value_SPACE; struct NUMBER; struct AUTO_0; struct rule_value_QUANTIFIER; struct ID; struct main; struct rule_value_alternative; struct rule_value_pattern; struct rule_value_group; struct rule_capture; } namespace FlatTypes { struct AUTO_5 { auto write_to_output(::ISPA_STD::ASTPrinter<std::ostream>& printer) const -> void; auto print(std::ostream& os) const -> void; auto to_string() const -> std::string; }; auto operator<<(std::ostream &os, const AUTO_5 &value) -> std::ostream&; } namespace FlatTypes { struct AUTO_4 { auto write_to_output(::ISPA_STD::ASTPrinter<std::ostream>& printer) const -> void; auto print(std::ostream& os) const -> void; auto to_string() const -> std::string; }; auto operator<<(std::ostream &os, const AUTO_4 &value) -> std::ostream&; } // ... namespace FlatTypes { struct rule_value_group { std::vector<::ISPA_STD::Node<::Parser::Rules, std::shared_ptr<::Parser::FlatTypes::rule_value>>> value; auto write_to_output(::ISPA_STD::ASTPrinter<std::ostream>& printer) const -> void; auto print(std::ostream& os) const -> void; auto to_string() const -> std::string; }; auto operator<<(std::ostream &os, const rule_value_group &value) -> std::ostream&; } // ... // вариант из всез токенов. Лексер буквально сохраняет std::vector<Token> // c этим же std::vector работает и парсер using Token = std::variant<std::monostate, ::ISPA_STD::Node<::Parser::Tokens, Types::AUTO_5>, ::ISPA_STD::Node<::Parser::Tokens, Types::AUTO_4> ...>; class Lexer : public ::ISPA_STD::Lexer_base<::Parser::Tokens, Token> { static ::ISPA_STD::DFA::API::CharToClass char_class_table; static ::ISPA_STD::DFA::API::Table<21, 129> dfa_table; // сама DFA таблица /* Тут лежат действия для сохранения значений. в DFA таблице закодированны как dfa.size() + action_table_state_id */ static ::ISPA_STD::DFA::API::Table<211, 4> action_table; /* А это и есть большая switch функция, позволяющая выполнить что угодно во время лексического анализа Пока что отвечает за создание финальных AST структур токена */ static auto semantic_action_exec(long long state, const ::ISPA_STD::DFA::API::Captures& captures, long long start_pos, const char* start, long long length, long long line) -> std::pair<long long, Token>; bool init_done; auto init() -> void override; ::ISPA_STD::DFA::API::TdfaLayout<14, 14> tdfa_registers; static std::array<::ISPA_STD::DFA::API::DFADebug, 226> debug_array; static std::unordered_map<long long, std::unordered_map<long long, long long>> debug_index; public: auto makeToken(const char*& pos) -> Token override; }; class Parser : public ::ISPA_STD::LLParser_base<::Parser::Tokens, ::Parser::Rules, Types::main, Token> { // ... /* Одно из правил сгенерированного парсера IT - итератор, позволяющий делать lazy parse. Lazy parrsing - аккамуляция токенов по мере надобности парсером. Много ненужного кода. Чтобы его убрать нужен оптимизатор, на что я решил пока что время не тратить. В целом компилируемые языки сделают больше чем весь возможный оптимизатор вместе взятый */ template<typename IT> auto rule_value_pattern(IT pos) -> ::ISPA_STD::MatchResult<Rules, Types::rule::value::pattern, IT>{ std::variant<std::monostate, ::ISPA_STD::Node<::Parser::Rules, Types::rule::value::group>, ::ISPA_STD::Node<::Parser::Tokens, Types::rule::value::SPACE>, ::ISPA_STD::Node<::Parser::Tokens, Types::rule::value::STRING>, ::ISPA_STD::Node<::Parser::Tokens, Types::ID>> _0; bool success_1; success_1 = false; // grammar `@ ( ID | rule::value::STRING | rule::value::SPACE | rule::value::group)`: choice; reset match status before selecting an alternative success_1 = false; switch (static_cast<Tokens>((*pos).index())) { case Tokens::AUTO_1: { ::ISPA_STD::MatchResult<Rules, Types::rule::value::group, IT> rule_value_group_2; bool success_3 = false; rule_value_group_2 = rule_value_group(pos); if (!(rule_value_group_2.status)) { return {}; } success_3 = true; pos = rule_value_group_2.it; if (!success_3) { return {}; } _0 = rule_value_group_2.node; success_1 = true; break; } case Tokens::ID: { ::ISPA_STD::Node<::Parser::Tokens, Types::ID> ID_4; bool success_5 = false; if (!(std::holds_alternative<::ISPA_STD::Node<::Parser::Tokens, Types::ID>>((*pos)))) { return {}; } ID_4 = std::get<::ISPA_STD::Node<::Parser::Tokens, Types::ID>>((*pos)); success_5 = true; pos++; if (!success_5) { return {}; } _0 = ID_4; success_1 = true; break; } case Tokens::rule_value_SPACE: { ::ISPA_STD::Node<::Parser::Tokens, Types::rule::value::SPACE> rule_value_SPACE_6; bool success_7 = false; if (!(std::holds_alternative<::ISPA_STD::Node<::Parser::Tokens, Types::rule::value::SPACE>>((*pos)))) { return {}; } rule_value_SPACE_6 = std::get<::ISPA_STD::Node<::Parser::Tokens, Types::rule::value::SPACE>>((*pos)); success_7 = true; pos++; if (!success_7) { return {}; } _0 = rule_value_SPACE_6; success_1 = true; break; } case Tokens::rule_value_STRING: { ::ISPA_STD::Node<::Parser::Tokens, Types::rule::value::STRING> rule_value_STRING_8; bool success_9 = false; if (!(std::holds_alternative<::ISPA_STD::Node<::Parser::Tokens, Types::rule::value::STRING>>((*pos)))) { return {}; } rule_value_STRING_8 = std::get<::ISPA_STD::Node<::Parser::Tokens, Types::rule::value::STRING>>((*pos)); success_9 = true; pos++; if (!success_9) { return {}; } _0 = rule_value_STRING_8; success_1 = true; break; } } if (!success_1) { return {}; } Types::rule::value::pattern result; ::ISPA_STD::assign_compatible(result.value, _0); ::ISPA_STD::MatchResult<Rules, Types::rule::value::pattern, IT> match_result; match_result.status = true; match_result.node.set_data(result); match_result.it = pos; return match_result; } template<typename IT> auto rule_value_group(IT pos) -> ::ISPA_STD::MatchResult<Rules, Types::rule::value::group, IT>{ ::ISPA_STD::Node<::Parser::Tokens, Types::AUTO_1> AUTO_1_0; bool success_1; ::ISPA_STD::Node<::Parser::Tokens, Types::__WS> __WS_2; bool success_3; ::ISPA_STD::MatchResult<Rules, Types::rule::value, IT> rule_value_5; bool success_6; ::ISPA_STD::Node<::Parser::Rules, Types::rule::value> element_7; std::vector<::ISPA_STD::Node<::Parser::Rules, Types::rule::value>> shadow_8; std::vector<::ISPA_STD::Node<::Parser::Rules, Types::rule::value>> uvar_4; bool success_9; ::ISPA_STD::Node<::Parser::Tokens, Types::__WS> __WS_10; bool success_11; ::ISPA_STD::Node<::Parser::Tokens, Types::AUTO_2> AUTO_2_12; bool success_13; success_1 = false; if (!(std::holds_alternative<::ISPA_STD::Node<::Parser::Tokens, Types::AUTO_1>>((*pos)))) { return {}; } AUTO_1_0 = std::get<::ISPA_STD::Node<::Parser::Tokens, Types::AUTO_1>>((*pos)); success_1 = true; pos++; success_3 = false; if (std::holds_alternative<::ISPA_STD::Node<::Parser::Tokens, Types::__WS>>((*pos))) { __WS_2 = std::get<::ISPA_STD::Node<::Parser::Tokens, Types::__WS>>((*pos)); success_3 = true; pos++; } success_6 = false; // grammar `@ rule::value+;`: repeat named rule; collect every matched value success_9 = false; while (true) { rule_value_5 = rule_value(pos); if (!(rule_value_5.status)) { break; } success_6 = true; pos = rule_value_5.it; element_7 = rule_value_5.node; shadow_8.push_back(element_7); success_9 = true; } if (!success_9) { return {}; } ::ISPA_STD::assign_compatible(uvar_4, shadow_8); success_11 = false; if (std::holds_alternative<::ISPA_STD::Node<::Parser::Tokens, Types::__WS>>((*pos))) { __WS_10 = std::get<::ISPA_STD::Node<::Parser::Tokens, Types::__WS>>((*pos)); success_11 = true; pos++; } success_13 = false; if (!(std::holds_alternative<::ISPA_STD::Node<::Parser::Tokens, Types::AUTO_2>>((*pos)))) { return {}; } AUTO_2_12 = std::get<::ISPA_STD::Node<::Parser::Tokens, Types::AUTO_2>>((*pos)); success_13 = true; pos++; Types::rule::value::group result; ::ISPA_STD::assign_compatible(result.value, uvar_4); ::ISPA_STD::MatchResult<Rules, Types::rule::value::group, IT> match_result; match_result.status = true; match_result.node.set_data(result); match_result.it = pos; return match_result; } // ... public: // функции-wrappers. Запускают main() // main() - правило, содержащее все другие возможные правила рекурсивно auto parseFromTokens() -> void override; // для парсинга с заранее акумулированных токенов auto lazyParse() -> void override; // для lazy parsing }; } #endif // PARSER_H
// Parser.cpp #include "Parser.h" // реализация вывода. AST можно посмотреть одной строчкой пользовательского кода auto ::Parser::FlatTypes::AUTO_5::write_to_output (::ISPA_STD::ASTPrinter<std::ostream>& printer) const -> void{ printer.node("AUTO_5", true); } auto ::Parser::FlatTypes::AUTO_5::print (std::ostream& os) const -> void{ ::ISPA_STD::ASTPrinter<std::ostream> printer = ::ISPA_STD::ASTPrinter<std::ostream>{os}; write_to_output(printer); } auto ::Parser::FlatTypes::AUTO_5::to_string () const -> std::string{ return ::ISPA_STD::concat("AUTO_5 {", "}"); } auto Parser::FlatTypes::operator<<(std::ostream &os, const AUTO_5 &value) -> std::ostream&{ value.print(os); return os; } // ... // Магические таблицы. // Никто не знает как их сделали, а смотря на них ... ощущение постоты ... и глубокого раздумия ::ISPA_STD::DFA::API::CharToClass Parser::Lexer::char_class_table = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16...128}; //#256 ::ISPA_STD::DFA::API::Table<21, 129> Parser::Lexer::dfa_table = {... {... ::ISPA_STD::DFA::API::null_state, ::ISPA_STD::DFA::API::null_state, ::ISPA_STD::DFA::API::null_state, 2, 3, 4, 83, 85 ...}, ...} ::ISPA_STD::DFA::API::Table<211, 4> Parser::Lexer::action_table = {{{1, 0, ::ISPA_STD::DFA::API::null_state, 12}, {1, 0, ::ISPA_STD::DFA::API::null_state, 12}, ...}}}; // Интерестная часть. auto ::Parser::Lexer::semantic_action_exec (long long state, const ::ISPA_STD::DFA::API::Captures& captures, long long start_pos, const char* start, long long length, long long line) -> std::pair<long long, Token>{ switch (state) { case 0: { return std::make_pair(::ISPA_STD::DFA::API::null_state, ::ISPA_STD::Node<::Parser::Tokens, Types::__WS>::create(start_pos, start, length, line, Tokens::__WS, Types::__WS{})); break; } case 1: { return std::make_pair(::ISPA_STD::DFA::API::null_state, ::ISPA_STD::Node<::Parser::Tokens, Types::AUTO_1>::create(start_pos, start, length, line, Tokens::AUTO_1, Types::AUTO_1{})); break; } case 2: { return std::make_pair(::ISPA_STD::DFA::API::null_state, ::ISPA_STD::Node<::Parser::Tokens, Types::AUTO_2>::create(start_pos, start, length, line, Tokens::AUTO_2, Types::AUTO_2{})); break; } case 3: { char value; if (captures.is_set(1)) { value = captures.character(1); } else { if (captures.is_set(3)) { value = captures.character(3); } else { if (captures.is_set(2)) { value = captures.character(2); } } } return std::make_pair(::ISPA_STD::DFA::API::null_state, ::ISPA_STD::Node<::Parser::Tokens, Types::rule::value::QUANTIFIER>::create(start_pos, start, length, line, Tokens::rule_value_QUANTIFIER, Types::rule::value::QUANTIFIER{value})); break; } // ... }auto ::Parser::Lexer::init () -> void{ return ; } // Таблица, которая внушает меньше пустоты // Некоторым файлам пощасливилось появиться с ней, а некоторые ... обходяться только 3-мя. std::array<::ISPA_STD::DFA::API::DFADebug, 226> Parser::Lexer::debug_array = {..., ::ISPA_STD::DFA::API::DFADebug {"[_a-zA-Z0-9]", "ID", 0, -1, 1, 1, 0, 1, '1'}, ::ISPA_STD::DFA::API::DFADebug {"[_a-zA-Z0-9]", "ID", 0, -1, 1, 1, 0, 1, '2'}, ...}; // для сопоставления клас, состояние, символ -> дебаг из таблицы std::unordered_map<long long, std::unordered_map<long long, long long>> Parser::Lexer::debug_index = {...}; auto ::Parser::Lexer::makeToken (const char*& pos) -> Token{ return lookup(dfa_table, char_class_table, action_table, tdfa_registers, semantic_action_exec, debug_array, debug_index, pos); } // имплементация wrappers auto ::Parser::Parser::parseFromTokens () -> void{ tree = main(Lexer::iterator( * lexer)).node.data(); } auto ::Parser::Parser::lazyParse () -> void{ tree = main(Lexer::lazy_iterator( * lexer, text)).node.data(); }
Текущая работа
Сейчас я занимаюсь бутстрапингом парсера грамматики. Релизы доступны на GitHub, их можно скачать и запустить. Они достаточно стабильны, чтобы сгенерировать C+±парсер, скомпилировать его и разобрать ввод.
Планы на будущее
Modules - модули (import json)
Semantic actions with advanced rules (Python subset), восстановление при ошибках и семантические действия
Templates (наследовение вряд-ли добавлю в текущем виде)
