Wizard Parser, v3.0
Posted on

LL(*)-парсер на C++ с поддержкой DSL для описания грамматики в EBNF непосредственно в коде программы.
Major release. Revision of errors handling, removing some possibilities and global refactoring.
Change Log
- Processing errors:
- Add the universal exception for an unexpected entity.
- Symplify processing an error:
- with an unexpected symbol;
- with an unexpected token;
- with an unexpected EOI.
- Remove:
- code entities:
- header including all parsers;
- functions:
- function for repeating in a range of times;
- overload of the
parser::parse()function accepting a string;
- operators:
- for repeating an exact number of times;
- for parsing separated lists;
- possibilities:
- ignoring some tokens;
- auto-selection:
- of longest token;
- of longest node;
- serialization to JSON:
- from the
lexermodule; - from the
parsermodule.
- from the
- code entities:
- Refactoring:
- of the
lexermodule:- combine in a single file:
tokenandtoken_grouptypes;
- add new types:
lexeme_grouptype alias;token_spantype alias;
- refactoring:
- of the
find_matched_token()function; - of the
tokenize()function;
- of the
- combine in a single file:
- of the
parsermodule:- simplify a code of parsers;
- combine in a single file:
ast_node_flagandast_nodetypes;parsing_resultandrule_parsertypes;match_typeandmatch_parsertypes;
- add new types:
ast_node_grouptype alias;
- replace:
- magic constants of AST node types to an enumeration;
- custom bitwise operators for the
ast_node_flagenumeration to using the grisumbras/enum-flags library; - mutable instances in parameters to constant references;
- remove:
- some C++ class specifiers;
- usages of move semantics;
- refactoring:
- of the
append_node()function; - of the
repetition_parser::parse()method;
- of the
- of the
utilitiesmodule:- rename it to
exceptions; - replace:
- mutable instances in parameters to constant references;
- remove:
- usages of move semantics;
- rename it to
- of the example:
- combine in a single function:
make_atom_parser()andmake_expression_parser()functions;
- refactoring:
- of the grammar;
- of the
main()function; - of the tests runner.
- combine in a single function:
- of the
Возможности
- лексинг ASCII-текста:
- задание лексем посредством регулярных выражений;
- парсинг ASCII-текста;
- описание грамматики на EBNF непосредственно в коде программы (посредством DSL);
- представление результата в виде AST;
- задание имени ноды в AST;
- комбинаторы:
- следование;
- упорядоченная альтернатива;
- повторение:
- 0 или 1 раз (опциональность);
- 0 или больше раз;
- 1 или больше раз;
- исключение;
- просмотр вперёд:
- позитивный;
- негативный;
- парсеры:
- пустота;
- конец текста;
- определённый текст;
- определённая лексема.
Пример использования
#include "vendor/json.hpp"
#include "vendor/docopt/docopt.hpp"
#include "vendor/fmt/format.hpp"
#include <thewizardplusplus/wizard_parser/lexer/lexeme.hpp>
#include <thewizardplusplus/wizard_parser/lexer/token.hpp>
#include <thewizardplusplus/wizard_parser/parser/ast_node.hpp>
#include <thewizardplusplus/wizard_parser/parser/rule_parser.hpp>
#include <thewizardplusplus/wizard_parser/parser/dummy_parser.hpp>
#include <thewizardplusplus/wizard_parser/parser/macroses.hpp>
#include <thewizardplusplus/wizard_parser/parser/match_parser.hpp>
#include <thewizardplusplus/wizard_parser/parser/alternation_parser.hpp>
#include <thewizardplusplus/wizard_parser/parser/exception_parser.hpp>
#include <thewizardplusplus/wizard_parser/parser/concatenation_parser.hpp>
#include <thewizardplusplus/wizard_parser/parser/lookahead_parser.hpp>
#include <thewizardplusplus/wizard_parser/parser/repetition_parser.hpp>
#include <thewizardplusplus/wizard_parser/parser/eoi_parser.hpp>
#include <thewizardplusplus/wizard_parser/lexer/tokenize.hpp>
#include <thewizardplusplus/wizard_parser/parser/parse.hpp>
#include <thewizardplusplus/wizard_parser/exceptions/unexpected_entity_exception.hpp>
#include <regex>
#include <iostream>
#include <string>
#include <iterator>
#include <algorithm>
#include <cstdlib>
#include <exception>
using namespace thewizardplusplus::wizard_parser::lexer;
using namespace thewizardplusplus::wizard_parser::parser;
using namespace thewizardplusplus::wizard_parser::parser::operators;
using namespace thewizardplusplus::wizard_parser::exceptions;
const auto usage =
R"(Usage:
./example -h | --help
./example [-t | --tokens] <expression>
./example [-t | --tokens] (-s | --stdin)
Options:
-h, --help - show this message;
-t, --tokens - show a token list instead an AST;
-s, --stdin - read an expression from stdin.)";
const auto lexemes = lexeme_group{
{std::regex{"=="}, "equal"},
{std::regex{"/="}, "not_equal"},
{std::regex{"<="}, "less_or_equal"},
{std::regex{"<"}, "less"},
{std::regex{">="}, "great_or_equal"},
{std::regex{">"}, "great"},
{std::regex{R"(\+)"}, "plus"},
{std::regex{"-"}, "minus"},
{std::regex{R"(\*)"}, "star"},
{std::regex{"/"}, "slash"},
{std::regex{"%"}, "percent"},
{std::regex{R"(\()"}, "opening_parenthesis"},
{std::regex{R"(\))"}, "closing_parenthesis"},
{std::regex{","}, "comma"},
{std::regex{R"(\d+(?:\.\d+)?(?:e-?\d+)?)"}, "number"},
{std::regex{R"([A-Za-z_]\w*)"}, "base_identifier"},
{std::regex{R"(\s+)"}, "whitespace"}
};
namespace thewizardplusplus::wizard_parser {
namespace lexer {
void to_json(nlohmann::json& json, const token& some_token) {
json = { { "type", some_token.type }, { "value", some_token.value } };
}
}
namespace parser {
void to_json(nlohmann::json& json, const ast_node& ast) {
json = { { "type", ast.type } };
if (!ast.value.empty()) {
json["value"] = ast.value;
}
if (!ast.children.empty()) {
json["children"] = ast.children;
}
}
}
}
void stop(const int& code, std::ostream& stream, const std::string& message) {
stream << fmt::format("{:s}\n", message);
std::exit(code);
}
rule_parser::pointer make_parser() {
const auto expression_dummy = dummy();
RULE(number) = "number"_t;
RULE(key_words) = "not"_v | "and"_v | "or"_v;
RULE(identifier) = "base_identifier"_t - key_words;
IMPORTANT_RULE(function_call) = identifier >> &"("_v >>
-(expression_dummy >> *(&","_v >> expression_dummy))
>> &")"_v;
RULE(atom) = number
| function_call
| identifier
| (&"("_v >> expression_dummy >> &")"_v);
RULE(unary) = *("-"_v | "not"_v) >> atom;
RULE(product) = unary >> *(("*"_v | "/"_v | "%"_v) >> unary);
RULE(sum) = product >> *(("+"_v | "-"_v) >> product);
RULE(comparison) = sum >> *(("<"_v | "<="_v | ">"_v | ">="_v) >> sum);
RULE(equality) = comparison >> *(("=="_v | "/="_v) >> comparison);
RULE(conjunction) = equality >> *(&"and"_v >> equality);
RULE(disjunction) = conjunction >> *(&"or"_v >> conjunction);
expression_dummy->set_parser(disjunction);
RULE(expression) = disjunction >> eoi();
return expression;
}
int main(int argc, char* argv[]) try {
auto cleaned_tokens = token_group{};
const auto options = docopt::docopt(usage, {argv+1, argv+argc}, true);
const auto code = options.at("--stdin").asBool()
? std::string{std::istreambuf_iterator<char>{std::cin}, {}}
: options.at("<expression>").asString();
const auto tokens = tokenize(lexemes, code);
std::copy_if(
std::cbegin(tokens),
std::cend(tokens),
std::back_inserter(cleaned_tokens),
[] (const auto& token) { return token.type != "whitespace"; }
);
if (options.at("--tokens").asBool()) {
stop(EXIT_SUCCESS, std::cout, nlohmann::json(cleaned_tokens).dump());
}
const auto parser = make_parser();
try {
const auto ast = parse(parser, cleaned_tokens);
stop(EXIT_SUCCESS, std::cout, nlohmann::json(ast).dump());
} catch (const unexpected_entity_exception<entity_type::eoi>& exception) {
throw decltype(exception){code.size()};
}
} catch (const std::exception& exception) {
stop(EXIT_FAILURE, std::cerr, fmt::format("error: {:s}", exception.what()));
}
Репозиторий
Ссылка: https://github.com/thewizardplusplus/wizard-parser/tree/v3.0.
Содержание: код, документация, пример использования.
Лицензия:
- кода — MIT;
- документации — CC BY 4.0.
Скриншоты
Количество циклов
