Проблема балансировки грамматики

Можно ли заставить Boost.Spirit Qi вести себя таким образом, чтобы сгенерированная грамматика была настраиваемой в соответствии с некоторыми вычисляемыми во время выполнения условиями / правилами / нормами? Например, входные данные состоят из языковых конструкций, которые вызывают различные альтернативы во время синтаксического анализа, некоторые чаще, другие — меньше. Но порядок альтернатив влияет на эффективность, т. Е. Оптимальность грамматики во время выполнения. В некоторых случаях невозможно заранее определить, какая альтернатива будет выбираться чаще в случае произвольного ввода (который может быть сильно сгруппирован).

Я знаю, что можно добавлять символы к qi::symbols во время выполнения, но подобное поведение было бы желательно для некоторых других парсеров.

3

Решение

Вы, к сожалению, забыли (?) Включить образец грамматики. Так что я придумал свое. Он разбирает язык как этот:

begin
declare x : int;
declare y : string;

let x = 42;
let y = "Life the universe and everything";

for(ch : y)
begin
if (call is_alpha(ch))
begin
declare z : string;
let z = call to_upper(ch);
call print(z);
end;
else call print("?");
end;
end;

Сейчас. Вы можете заметить, что каждая «языковая конструкция» (как вы упоминаете ее в OP) имеет вводное ключевое слово. Это специально.

Потому что теперь мы можем использовать qi::symbols с этими ключевыми словами вводного для отправки правил (это известно как трюк Nabialek):

// let's have some language constructs
feature_vardecl    = identifier >> ':' >> type >> ';';
feature_assignment = identifier >> "=" >> expression >> ';';
feature_block      = *statement >> kw["end"] >> ';' | statement;
feature_forloop    = '(' >> identifier >> ':' >> identifier > ')' >> statement;
feature_func_call  = invocation > ';';
feature_if         = ('(' > expression > ')' > statement) >> (kw["else"] > statement);

language_constructs.add
("declare", &feature_vardecl)
("let",     &feature_assignment)
("begin",   &feature_block)
("if",      &feature_if)
("for",     &feature_forloop)
("call",    &feature_func_call);

Как видите, мы храним адрес соответствующего грамматического правила как значение в словаре. Теперь мы используем трюк Набиалек (использует qi::_a локальный для вызова subrule):

statement  =
(kw[language_constructs] [ qi::_a = qi::_1 ] > qi::lazy(*qi::_a))
| (expression > ';');

Если вам нужна более легкая грамматика, вы просто удалите некоторые функции:

language_constructs.add
("let",     &feature_assignment)
("begin",   &feature_block)
("call",    &feature_func_call);

Вы мог даже динамически добавлять функции к language_constructs в ответ на ввод (например, идентификатор версии на входе или просто при сбое анализа). Я не уверен, будет ли это хорошей идеей, но … это все возможно.


Полнофункциональный пример, который анализирует вышеупомянутую программу (если поставляется в input.txt) в комплекте с adhoc «модульными тестами», поддержкой проверки ключевых слов, отладкой и т. д .:

Видеть это Жить на Колиру

#define BOOST_SPIRIT_DEBUG
#define BOOST_SPIRIT_USE_PHOENIX_V3
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <boost/spirit/repository/include/qi_distinct.hpp>
#include <fstream>

namespace qi = boost::spirit::qi;
namespace phx= boost::phoenix;

using It      = std::string::const_iterator;
using Skipper = qi::space_type;
using Rule    = qi::rule<It, Skipper>;

template <typename G, size_t N>
bool test(const char (&raw)[N], const G& grammar)
{
std::string const input(raw, raw+N-1);
auto f(std::begin(input)), l(std::end(input));
try
{
bool ok = qi::phrase_parse(f, l, grammar, qi::space);
// if (f!=l) std::cerr << "remaining unparsed: '" << std::string(f,l) << "'\n";
return ok && (f == l);
} catch (qi::expectation_failure<It> const& e)
{
// std::cout << "Expectation failure '" << e.what() << "' at '" << std::string(e.first, e.last) << "'\n";
return false;
}
}

template <typename It, typename Skipper>
struct toy_grammar : qi::grammar<It, Skipper>
{
toy_grammar() : toy_grammar::base_type(start)
{
using boost::spirit::repository::distinct;
static const auto kw = distinct(qi::char_("a-zA-Z_0-9"));

keywords.add("let")("declare")("begin")("end")("for")("call")("if")("else");

identifier = !kw[keywords] >> qi::lexeme [ qi::alpha >> *qi::char_("a-zA-Z_0-9") ];

assert( test("z", identifier));
assert( test("Afgjkj_123123", identifier));
assert(!test("1", identifier));

type       = qi::lexeme [ kw["int"] | kw["double"]| kw["string"] | kw["boolean"]];

assert( test("int",     type));
assert( test("double",  type));
assert( test("string",  type));
assert( test("boolean", type));
assert(!test("intzies", type));
assert(!test("Int",     type));

literal    = qi::lexeme [
qi::real_parser<double, qi::strict_real_policies<double>>()
| qi::int_
| qi::as_string ['"' >> *~qi::char_('"') >> '"']
| kw [ qi::bool_ ]
];

assert( test("42",     literal));
assert( test("42.",    literal));
assert( test(".0",     literal));
assert( test("-3e+7",  literal));
assert( test("-inf",   literal));
assert( test("-99",    literal));
assert( test("\"\"",   literal));
assert( test("\"\0\"", literal));
assert( test("true",   literal));
assert( test("false",  literal));
assert(!test("trueish",literal));
assert(!test("yes",    literal));

invocation = identifier > '(' > -(expression % ',') > ')';

// arhem, this part left as an exercise for the reader :)
expression = literal | identifier | (kw["call"] > invocation);

assert( test("-99",       expression));
assert( test("\"santa\"", expression));
assert( test("clause",    expression));
assert( test("true",      expression));
assert( test("call foo()",    expression));
assert( test("call foo(bar, inf, false)", expression));
assert(!test("call 42()",     expression));

// let's have some language constructs
feature_vardecl    = identifier >> ':' >> type >> ';';
feature_assignment = identifier >> "=" >> expression >> ';';
feature_block      = *statement >> kw["end"] >> ';' | statement;
feature_forloop    = '(' >> identifier >> ':' >> identifier > ')' >> statement;
feature_func_call  = invocation > ';';
feature_if_else    = ('(' > expression > ')' > statement) >> (kw["else"] > statement);

language_constructs.add
("declare", &feature_vardecl)
("let",     &feature_assignment)
("begin",   &feature_block)
("if",      &feature_if_else)
("for",     &feature_forloop)
("call",    &feature_func_call);

statement  =
(kw[language_constructs] [ qi::_a = qi::_1 ] > qi::lazy(*qi::_a))
| (expression > ';');

assert( test("declare x : int;"                                       , statement));
assert( test("let y = true;"                                          , statement));
assert( test("call foo();",                                             statement));
assert( test("call foo(bar, inf, false);",                              statement));

assert( test("begin end;",                                              statement));
assert( test("begin let y = x; end;",                                   statement));
assert( test("begin let y = x; call foo(y); end;",                      statement));
assert( test("for (x : collection) begin let y = x; call foo(y); end;", statement));

BOOST_SPIRIT_DEBUG_NODES((identifier)(type)(literal)(expression)(invocation)(statement)
(feature_vardecl)(feature_assignment)(feature_block)(feature_forloop)(feature_func_call)(feature_if_else)
);

start = statement;
}
private:
qi::symbols<char, Rule const*> language_constructs;
qi::symbols<char, qi::unused_type> keywords;

Rule start,
identifier, type, literal, expression, invocation,
feature_assignment, feature_vardecl, feature_block, feature_forloop, feature_func_call, feature_if_else;

qi::rule<It, Skipper, qi::locals<Rule const*> > statement;
};

int main()
{
using namespace std;
ifstream ifs("input.txt", ios::binary);
string const input(istreambuf_iterator<char>(ifs), {});

auto f(begin(input)), l(end(input));
try
{
static const toy_grammar<It, Skipper> p;
bool ok = qi::phrase_parse(
f, l,
p,
qi::space);

assert(ok);

if (f!=l)
cout << "Program remaining unparsed: '" << string(f,l) << "'\n";
} catch (qi::expectation_failure<It> const& e)
{
cout << "Expectation failure '" << e.what() << "' at '" << string(e.first, e.last) << "'\n";
}
}
5

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]