Wizard Parser, v4.0
Posted on

LL(*)-парсер на C++ с поддержкой DSL для описания грамматики в EBNF непосредственно в коде программы.
Major release. Support evaluation of an expression in the example.
Change Log
- In the example:
- Simplify a grammar:
- simplify:
- implementation;
- description;
- upgrade tests:
- remove useless tests:
- of the
lexermodule; - of the
parsermodule;
- of the
- upgrade rest tests:
- of the
lexermodule; - of the
parsermodule.
- of the
- remove useless tests:
- simplify:
- Simplify a result CST:
- remove nodes with the
nothingtype; - join nodes with the
sequencetype; - fix the
walk_ast_node()function; - upgrade tests of the
parsermodule.
- remove nodes with the
- Add the
targetoption:- fix the options implementation;
- upgrade tests:
- of the
lexermodule; - of the
parsermodule.
- of the
- Evaluate a CST node:
- evaluate:
- numbers;
- constants;
- rest nodes:
- with the
atomtype; - with the
unarytype; - with the
function_calltype; - with the
producttype; - with the
sumtype;
- with the
- add runtime docs:
- add a description:
- of built-in constants;
- of built-in functions;
- add a table of contents.
- add a description:
- evaluate:
- Add tests of evaluation:
- add the
evaluatortest group; - add the
test_text_output()function; - add tests of evaluation:
- of nodes:
- with the
atomtype; - with the
unarytype; - with the
producttype; - with the
sumtype;
- with the
- of expressions in parentheses.
- of nodes:
- add the
- Simplify a grammar:
Пример использования
#define THEWIZARDPLUSPLUS_WIZARD_PARSER_PARSER_MACROSES
#include "vendor/fmt/format.hpp"
#include "vendor/better-enums/enum_strict.hpp"
#include "vendor/range/v3/view/filter.hpp"
#include "vendor/range/v3/to_container.hpp"
#include "vendor/range/v3/view/transform.hpp"
#include "vendor/range/v3/view/join.hpp"
#include "vendor/range/v3/view/drop.hpp"
#include "vendor/range/v3/view/chunk.hpp"
#include "vendor/docopt/docopt.hpp"
#include "vendor/json.hpp"
#include "vendor/range/v3/numeric/accumulate.hpp"
#include "vendor/range/v3/view/concat.hpp"
#include "vendor/range/v3/view/single.hpp"
#include <thewizardplusplus/wizard_parser/parser/ast_node.hpp>
#include <thewizardplusplus/wizard_parser/lexer/lexeme.hpp>
#include <thewizardplusplus/wizard_parser/parser/rule_parser.hpp>
#include <thewizardplusplus/wizard_parser/parser/dummy_parser.hpp>
#include <thewizardplusplus/wizard_parser/parser/typing_parser.hpp>
#include <thewizardplusplus/wizard_parser/parser/match_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/alternation_parser.hpp>
#include <thewizardplusplus/wizard_parser/utilities/utilities.hpp>
#include <thewizardplusplus/wizard_parser/lexer/tokenize.hpp>
#include <thewizardplusplus/wizard_parser/lexer/token.hpp>
#include <functional>
#include <unordered_map>
#include <string>
#include <cstddef>
#include <vector>
#include <stdexcept>
#include <cstdint>
#include <regex>
#include <iostream>
#include <cstdlib>
#include <iterator>
#include <exception>
using namespace thewizardplusplus::wizard_parser;
using namespace thewizardplusplus::wizard_parser::parser::operators;
using namespace std::literals::string_literals;
using ast_node_handler =
std::function<parser::ast_node(const parser::ast_node&)>;
using constant_group = std::unordered_map<std::string, double>;
struct function final {
const std::size_t arity;
const std::function<double(const std::vector<double>&)> handler;
};
using function_group = std::unordered_map<std::string, function>;
struct positional_exception: std::runtime_error {
const std::string description;
const std::size_t offset;
positional_exception(
const std::string& description,
const std::size_t& offset
);
};
positional_exception::positional_exception(
const std::string& description,
const std::size_t& offset
):
std::runtime_error{fmt::format("{:s} (offset: {:d})", description, offset)},
description{description},
offset{offset}
{}
BETTER_ENUM(entity_type, std::uint8_t,
symbol,
token,
eoi,
node,
constant,
function
)
template<entity_type::_integral type>
struct unexpected_entity_exception final: positional_exception {
static_assert(entity_type::_is_valid(type));
explicit unexpected_entity_exception(const std::size_t& offset);
};
template<entity_type::_integral type>
unexpected_entity_exception<type>::unexpected_entity_exception(
const std::size_t& offset
):
positional_exception{
fmt::format(
"unexpected {:s}",
entity_type::_from_integral(type)._to_string()
),
offset
}
{}
const auto usage =
R"(Usage:
./example -h | --help
./example [-t TARGET | --target TARGET] [--] <expression>
./example [-t TARGET | --target TARGET] (-s | --stdin)
Options:
-h, --help - show this message;
-t TARGET, --target TARGET - preliminary target of processing
(allowed: tokens, cst);
-s, --stdin - read an expression from stdin.)";
const auto lexemes = lexer::lexeme_group{
{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*)"}, "identifier"},
{std::regex{R"(\s+)"}, "whitespace"}
};
const auto handlers = std::vector<ast_node_handler>{
// remove CST nodes with the nothing type
[] (const auto& ast) {
const auto type = (+parser::ast_node_type::nothing)._to_string();
const auto new_children = ast.children
| ranges::view::filter([&] (const auto& ast) { return ast.type != type; })
| ranges::to_<parser::ast_node_group>();
return parser::ast_node{ast.type, ast.value, new_children, ast.offset};
},
// join CST nodes with the sequence type
[] (const auto& ast) {
const auto type = (+parser::ast_node_type::sequence)._to_string();
if (ast.type != type) {
return ast;
}
const auto new_children_set = ast.children
| ranges::view::transform([&] (const auto& ast) {
return ast.type == type ? ast.children : parser::ast_node_group{ast};
})
| ranges::to_<std::vector<parser::ast_node_group>>();
const auto new_children = new_children_set
| ranges::view::join
| ranges::to_<parser::ast_node_group>();
return parser::ast_node{ast.type, ast.value, new_children, ast.offset};
}
};
const auto constants = constant_group{
{"pi", 3.1415926535897932384626433},
{"e", 2.7182818284590452353602874}
};
const auto functions = function_group{
{"+", {2, [] (const auto& arguments) {
return arguments.front() + arguments.back();
}}},
{"-", {2, [] (const auto& arguments) {
return arguments.front() - arguments.back();
}}},
{"*", {2, [] (const auto& arguments) {
return arguments.front() * arguments.back();
}}},
{"/", {2, [] (const auto& arguments) {
return arguments.front() / arguments.back();
}}},
{"%", {2, [] (const auto& arguments) {
return static_cast<std::int64_t>(arguments.front())
% static_cast<std::int64_t>(arguments.back());
}}},
{"floor", {1, [] (const auto& arguments) {
return std::floor(arguments.front());
}}},
{"ceil", {1, [] (const auto& arguments) {
return std::ceil(arguments.front());
}}},
{"trunc", {1, [] (const auto& arguments) {
return std::trunc(arguments.front());
}}},
{"round", {1, [] (const auto& arguments) {
return std::round(arguments.front());
}}},
{"sin", {1, [] (const auto& arguments) {
return std::sin(arguments.front());
}}},
{"cos", {1, [] (const auto& arguments) {
return std::cos(arguments.front());
}}},
{"tn", {1, [] (const auto& arguments) {
return std::tan(arguments.front());
}}},
{"arcsin", {1, [] (const auto& arguments) {
return std::asin(arguments.front());
}}},
{"arccos", {1, [] (const auto& arguments) {
return std::acos(arguments.front());
}}},
{"arctn", {1, [] (const auto& arguments) {
return std::atan(arguments.front());
}}},
{"angle", {2, [] (const auto& arguments) {
return std::atan2(arguments.back(), arguments.front());
}}},
{"pow", {2, [] (const auto& arguments) {
return std::pow(arguments.front(), arguments.back());
}}},
{"sqrt", {1, [] (const auto& arguments) {
return std::sqrt(arguments.front());
}}},
{"exp", {1, [] (const auto& arguments) {
return std::exp(arguments.front());
}}},
{"ln", {1, [] (const auto& arguments) {
return std::log(arguments.front());
}}},
{"lg", {1, [] (const auto& arguments) {
return std::log10(arguments.front());
}}},
{"abs", {1, [] (const auto& arguments) {
return std::abs(arguments.front());
}}},
};
void stop(const int& code, std::ostream& stream, const std::string& message) {
stream << fmt::format("{:s}\n", message);
std::exit(code);
}
parser::rule_parser::pointer make_parser() {
const auto expression_dummy = parser::dummy();
RULE(function_call) = "identifier"_t >> &"("_v >>
-(expression_dummy >> *(&","_v >> expression_dummy))
>> &")"_v;
RULE(atom) = "number"_t
| function_call
| "identifier"_t
| (&"("_v >> expression_dummy >> &")"_v);
RULE(unary) = *("-"_v) >> atom;
RULE(product) = unary >> *(("*"_v | "/"_v | "%"_v) >> unary);
RULE(sum) = product >> *(("+"_v | "-"_v) >> product);
expression_dummy->set_parser(sum);
return sum;
}
parser::ast_node walk_ast_node(
const parser::ast_node& ast,
const ast_node_handler& handler
) {
const auto new_children = ast.children
| ranges::view::transform([&] (const auto& ast) {
return walk_ast_node(ast, handler);
});
return handler({ast.type, ast.value, new_children, ast.offset});
}
const parser::ast_node_group& inspect_sequence(const parser::ast_node& ast) {
return ast.children.front().children;
}
std::size_t get_offset(const parser::ast_node& ast) {
if (ast.offset) {
return *ast.offset;
}
if (ast.children.empty()) {
return utilities::integral_infinity;
}
return get_offset(ast.children.front());
}
double evaluate_ast_node(
const parser::ast_node& ast,
const constant_group& constants,
const function_group& functions
) {
const auto evaluate_with_context = [&] (const auto& ast) {
return evaluate_ast_node(ast, constants, functions);
};
if (ast.type == "number") {
return std::stod(ast.value, nullptr);
} else if (ast.type == "identifier") {
try {
return constants.at(ast.value);
} catch (const std::out_of_range& exception) {
throw unexpected_entity_exception<entity_type::constant>{get_offset(ast)};
}
} else if (ast.type == "atom") {
const auto type = (+parser::ast_node_type::sequence)._to_string();
const auto first_child = ast.children.front().type == type
? inspect_sequence(ast).front()
: ast.children.front();
return evaluate_with_context(first_child);
} else if (ast.type == "unary") {
const auto result = evaluate_with_context(inspect_sequence(ast).back());
const auto sign = (inspect_sequence(ast).size()-1) % 2 ? -1 : 1;
return sign * result;
} else if (ast.type == "function_call") {
try {
const auto name = inspect_sequence(ast).front().value;
const auto arguments = inspect_sequence(ast)
| ranges::view::drop(1)
| ranges::view::transform([&] (const auto& ast) {
return evaluate_with_context(ast);
});
const auto function = functions.at(name);
if (arguments.size() != function.arity) {
throw positional_exception{
fmt::format(
"function requires {:d} {:s}",
function.arity,
function.arity == 1 ? "argument" : "arguments"
),
get_offset(ast)
};
}
return function.handler(arguments);
} catch (const std::out_of_range& exception) {
throw unexpected_entity_exception<entity_type::function>{get_offset(ast)};
}
} else if (ast.type == "product" || ast.type == "sum") {
const auto first_operand =
evaluate_with_context(inspect_sequence(ast).front());
const auto children_chunks = inspect_sequence(ast)
| ranges::view::drop(1)
| ranges::view::chunk(2)
| ranges::view::transform([] (const auto& chunk) {
return chunk | ranges::to_<parser::ast_node_group>();
});
return ranges::accumulate(
children_chunks,
first_operand,
[&] (const auto& result, const auto& chunk) {
const auto name = chunk.front().value;
const auto second_operand = evaluate_with_context(chunk.back());
return functions.at(name).handler({result, second_operand});
}
);
} else {
throw unexpected_entity_exception<entity_type::node>{get_offset(ast)};
}
}
int main(int argc, char* argv[]) try {
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, rest_offset] = lexer::tokenize(lexemes, code);
if (rest_offset != code.size()) {
throw unexpected_entity_exception<entity_type::symbol>{rest_offset};
}
auto cleaned_tokens = tokens
| ranges::view::filter([] (const auto& token) {
return token.type != "whitespace";
})
| ranges::to_<lexer::token_group>();
if (options.at("--target") == "tokens"s) {
stop(EXIT_SUCCESS, std::cout, nlohmann::json(cleaned_tokens).dump());
}
const auto ast = make_parser()->parse(cleaned_tokens);
if (!ast.rest_tokens.empty()) {
throw unexpected_entity_exception<entity_type::token>{
lexer::get_offset(ast.rest_tokens)
};
}
if (!ast.node) {
throw unexpected_entity_exception<entity_type::eoi>{code.size()};
}
const auto completed_handlers = ranges::view::concat(
// replace CST nodes offsets which are equal
// to the utilities::integral_infinity constant to a code size
ranges::view::single([&] (const auto& ast) {
const auto offset = ast.offset == utilities::integral_infinity
? code.size()
: ast.offset;
return parser::ast_node{ast.type, ast.value, ast.children, offset};
}),
handlers
);
const auto transformed_ast = ranges::accumulate(
completed_handlers,
*ast.node,
walk_ast_node
);
if (options.at("--target") == "cst"s) {
stop(EXIT_SUCCESS, std::cout, nlohmann::json(transformed_ast).dump());
}
const auto result = evaluate_ast_node(transformed_ast, constants, functions);
stop(EXIT_SUCCESS, std::cout, std::to_string(result));
} catch (const std::exception& exception) {
stop(EXIT_FAILURE, std::cerr, fmt::format("error: {:s}", exception.what()));
}
Репозиторий
Ссылка: https://github.com/thewizardplusplus/wizard-parser/tree/v4.0.
Содержание: код, документация, пример использования.
Лицензия:
- кода — MIT;
- документации — CC BY 4.0.
Скриншоты
Консольная справка в примере
