Spirit-Qi: Como posso escrever um analisador não-terminal?
Quero escrever um analisador (como uma extensão qi) que pode ser usado viamy_parser(p1, p2, ...)
Ondep1, p2, ...
são expressões do analisador qi.
Na verdade, eu quero implementar umbest_match
analisador que funciona como a alternativa qi, mas seleciona não a primeira regra correspondente, mas a regra que 'explica' a maior parte da entrada.
Dadas duas regrassimple_id = +(qi::alpha)
ecomplex_id = simple_id >> *(qi::string("::") > simple_id)
selecionariacomplex_id
na entradawilly::anton
. E não produziria atributos intermediários ao fazê-lo. Esses benefícios são pagos com o tempo de execução porque a análise de lookahead é necessária.
Na minha opinião, existem vários casos de uso para essa construção de analisador.benchmark(p1, ...)
como um analisador de suporte para otimização, apenas um exemplo. Eu fornecerei isso, uma vez que eu saiba como fazer isso.
Este analisador seria um não terminal. Eu tentei (difícil), mas não consigo encontrar respostas para o meu problema no qi. Meu palpite é que os mecanismos C ++ são tão fortemente integrados ao c qi, que não há um ponto de entrada compreensível, pelo menos para mim.
É muito fácil implementar o que eu quero introduzir um operador. Anexo a solução atual abaixo. Compila conforme o esperado, mas está incompleto.
Um operador de qi obtém uma fusão :: vetor / sequência (o que for) pronta e age sobre ela. Parece não haver ofertas de bibliotecas para resolver meu problema. Atémake_nary_composite
já espera que os argumentos sejam compilados paraElements
.
Eu tentei muito, tudo falhou, então não quero aborrecê-lo com isso.
Uma solução alternativa que posso imaginar poderia ser fornecer um operador com estado,
, o que tornariap1, ...
a uma expressão legal de qi. Em seguida, implemente um unary_parserbest_match
ou diretiva para processar essa expressão. o,
obteria dois modos: obter o comprimento da entrada que o analisador atual (bem-sucedido) consome e, na verdade, analisar o selecionado da fase 1. O wrapper que executa esse analisador de vírgula primeiro no modo 1 e depois no modo 2. Pode ser feio, mas poderia trabalhar.
A implementação do operadorp1 |= p2 |= ...
é o mais caro em termos de tempo de execução para n> 2. Eu ficaria feliz em contornar isso.
As despesas gerais mínimas (mas mesmo assim razoáveis) teriambest_match(p1, ...)
. Isso é sequer possível?
Por não ter muita experiência com boost :: fusion, adicionei algumas perguntas ao código (como fazer em relação à fusão).
Parece errado pressionar algo que na verdade é um analisador não terminal não obrigatório no esquema de análise unária. Mas, com meu entendimento ruim, parece ser a única maneira de fazê-lo. Eu apreciaria muito qualquer ajuda para me tirar disso.
Meu ambiente: Boost 1.61, MSVC 2015 Upd 2, alvo win32console.
PROGRESSO:
Eu acho que estou conseguindo lenta mas seguramente. Fiquei muito feliz em ver umerror_invalid_expression
. Falha na compilação devido à seguinte expressão proto:
boost::proto::exprns_::expr<
boost::proto::tagns_::tag::function,boost::proto::argsns_::list5<
const boost::proto::exprns_::expr<
boost::proto::tagns_::tag::terminal,boost::proto::argsns_::term<
mxc::qitoo::tag::best_match
>
,0> &
,const boost::spirit::qi::rule<
iterator_type,std::string (void),
boost::spirit::standard::space_type,
boost::spirit::unused_type,boost::spirit::unused_type> &
,const boost::spirit::qi::rule<
iterator_type,std::string (void),
boost::spirit::standard::space_type,
boost::spirit::unused_type,
boost::spirit::unused_type> &
, const boost::spirit::qi::rule<
iterator_type,std::string (void),
boost::spirit::standard::space_type,
boost::spirit::unused_type,
boost::spirit::unused_type> &
,const boost::spirit::qi::rule<
iterator_type,std::string (void),
boost::spirit::standard::space_type,
boost::spirit::unused_type,
boost::spirit::unused_type> &
>
,5>
Na verdade, essa é a expressão que descreve meu uso no estilo de função do meu analisador best_match. Minha regra de teste parece
qitoo::best_match(id, qualified_id, id, qualified_id)
Ondeid
equalified_id
são as regras mencionadas acima. Tudo o que eu quero. O erro ocorre porque esta expressão não tem um membroparse
. OK. Indo mais fundo, descobri que o meta-compilador do qi suporta analisadores unários, binários e nários (o que significa variável). Mas ele não suporta sintaxe de estilo de função. O Qi parece utilizar unário, binário e nário apenas para operadores. E embora o tipo nary seja suportado, é mais ou menos obsoleto, porque os operadores de qi são binários máx.
EDITAR PROGRESSO EDIT
#include <boost/spirit/home/qi.hpp>
namespace boost {
namespace spirit
{
///////////////////////////////////////////////////////////////////////////
// Enablers
///////////////////////////////////////////////////////////////////////////
template <>
struct use_operator<qi::domain, proto::tag::bitwise_or_assign> // enables |=
: mpl::true_ {};
template <>
struct flatten_tree<qi::domain, proto::tag::bitwise_or_assign> // flattens |=
: mpl::true_ {};
}
}
namespace mxc {
namespace qitoo {
namespace spirit = boost::spirit;
namespace qi = spirit::qi;
namespace fusion = boost::fusion;
template <typename Elements>
struct best_match_parser : qi::nary_parser<mxc::qitoo::best_match_parser<Elements>>
{
// This one does a lookahead parse of each qi expression to find out which rule matches
// most of the input
template <typename Iterator, typename Context, typename Skipper>
struct best_function
{
best_function(Iterator& first, Iterator const& last, Context& context,
Skipper const& skipper, int& best, int& idx, int& size)
: first(first), last(last), context(context), skipper(skipper), best(best),
idx(idx), size(size) {};
template <typename Component>
void operator()(Component& component) const
{
Iterator f = first;
if (component.parse(f, last, context, skipper, qi::unused)) {
// please have a look on how I transport best, idx and size
// into the parser context
//
// I guess, this is a nasty hack and could be done better
// Any advice would be highliy appreciated
int l = f - first;
if (l > size) {
size = l;
best = idx++;
}
idx++;
}
}
private:
int& best;
int& idx;
int& size;
Iterator& first;
Iterator const& last;
Context& context;
Skipper const& skipper;
};
template <typename Context, typename Iterator>
struct attribute
{
// Put all the element attributes in a tuple
typedef typename spirit::traits::build_attribute_sequence <
Elements, Context, spirit::traits::alternative_attribute_transform
, Iterator, qi::domain
>::type all_attributes;
// Ok, now make a variant over the attribute sequence. Note that
// build_variant makes sure that 1) all attributes in the variant
// are unique 2) puts the unused attribute, if there is any, to
// the front and 3) collapses single element variants, variant<T>
// to T.
typedef typename
spirit::traits::build_variant<all_attributes>::type
type;
};
best_match_parser(Elements const& elements_) : elements(elements_), size(0), idx(0), best(-1) {}
template <typename Iterator, typename Context, typename Skipper, typename Attribute>
bool parse(Iterator& first, Iterator const& last
, Context& context, Skipper const& skipper
, Attribute& attr_) const
{
best_function<Iterator, Context, Skipper> f(first, last, context, skipper, best, idx, size);
// find out which parser matches most of the input
fusion::for_each(elements, f);
// best >= 0 if at least one parser was successful
if (best >= 0) {
// now that I have the best parser identified, how do I access it?
// I understand that the next line can not work, but I'm looking for something like that
// --> auto v = fusion::at<boost::mpl::int_<best>>(elements);
};
return false;
}
template <typename Context>
qi::info what(Context& context) const
{
qi::info result("best_match");
fusion::for_each(elements,
spirit::detail::what_function<Context>(result, context));
return result;
}
Elements elements;
mutable int best;
mutable int idx;
mutable int size;
};
}
}
namespace boost {
namespace spirit {
namespace qi {
///////////////////////////////////////////////////////////////////////////
// Parser generators: make_xxx function (objects)
///////////////////////////////////////////////////////////////////////////
template <typename Elements, typename Modifiers>
struct make_composite<proto::tag::bitwise_or_assign, Elements, Modifiers>
: make_nary_composite < Elements, mxc::qitoo::best_match_parser >
{};
}
namespace traits {
///////////////////////////////////////////////////////////////////////////
template <typename Elements>
struct has_semantic_action<mxc::qitoo::best_match_parser<Elements> >
: nary_has_semantic_action<Elements> {};
///////////////////////////////////////////////////////////////////////////
template <typename Elements, typename Attribute, typename Context
, typename Iterator>
struct handles_container<mxc::qitoo::best_match_parser<Elements>, Attribute, Context
, Iterator>
: nary_handles_container<Elements, Attribute, Context, Iterator> {};
}
}
}
namespace qi = boost::spirit::qi;
namespace qitoo = mxc::qitoo;
using iterator_type = std::string::const_iterator;
using result_type = std::string;
template<typename Parser>
void parse(const std::string message, const std::string& input, const std::string& rule, const Parser& parser) {
iterator_type iter = input.begin(), end = input.end();
std::vector<result_type> parsed_result;
std::cout << "-------------------------\n";
std::cout << message << "\n";
std::cout << "Rule: " << rule << std::endl;
std::cout << "Parsing: \"" << input << "\"\n";
bool result = qi::phrase_parse(iter, end, parser, qi::space, parsed_result);
if (result)
{
std::cout << "Parser succeeded.\n";
std::cout << "Parsed " << parsed_result.size() << " elements:";
for (const auto& str : parsed_result)
std::cout << "[" << str << "]";
std::cout << std::endl;
}
else
{
std::cout << "Parser failed" << std::endl;
}
if (iter == end) {
std::cout << "EOI reached." << std::endl;
}
else {
std::cout << "EOI not reached. Unparsed: \"" << std::string(iter, end) << "\"" << std::endl;
}
std::cout << "-------------------------\n";
}
int main()
{
namespace qi = boost::spirit::qi;
qi::rule < iterator_type, std::string(), qi::space_type>
id = (qi::alpha | qi::char_('_')) >> *(qi::alnum | qi::char_('_'));
qi::rule < iterator_type, std::string(), qi::space_type>
qualified_id = id >> *(qi::string("::") > id);
namespace qitoo = mxc::qitoo;
namespace qi = boost::spirit::qi;
parse("best match operator, select second rule"
, "willy::anton::lutz"
, "id |= qualified_id"
, id |= qualified_id);
}