2

I have a Qi grammar definition that I use to parse an input. Later I have a Karma generator to output in a way that should be similar to the input.

Is this possible at all? It seem that a parser grammar can be transformed into a generator grammar automatically (??).

#include <boost/config/warning_disable.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/karma.hpp>
#include <iostream>
int main(){

    //test input
    std::string s = "Xx     1.233    pseudo";

    //input variables
    std::string element;
    double mass;
    std::string pseudo;

    auto GRAMMAR = 
            boost::spirit::qi::lexeme[+(boost::spirit::qi::char_ - ' ' - '\n')] 
            >> boost::spirit::qi::double_
            >> boost::spirit::qi::lexeme[+(boost::spirit::qi::char_ - ' ' - '\n')];

    bool r = boost::spirit::qi::phrase_parse(
            s.begin(), s.end(), 
            GRAMMAR, 
            boost::spirit::qi::space, element, mass, pseudo
    );

    std::cout << boost::spirit::karma::format(
            GRAMMAR ??? is it possible? 
            , 
            element,
            mass,
            pseudo
    );
}
alfC
  • 14,261
  • 4
  • 67
  • 118
  • 1
    Short answer, no. (Yes it's possible, no it's not possible automatically) – sehe Sep 16 '16 at 01:15
  • @sehe, ok, thanks. I was so frustrated that inverting the direction of '>>' and changing `qi::` by `karma::` didn't work that I asked this question. – alfC Sep 16 '16 at 06:30
  • 2
    I don't know what I'm doing, but [this](http://melpon.org/wandbox/permlink/s80FiLz49KWNsWsc) seems to work. The problems you have found are caused by the fact that Karma does not have neither the `lexeme` directive (`verbatim` seems to be the analogous directive) nor the `-` (difference) operator. In my approach I have substituted `lexeme` by `verbatim` and transformed `foo-bar` into simply `foo`. I'm sure that in a more complex example something else will break (with "error_invalid_expression") when you use something that exists in Qi but not in Karma. (@sehe in case he's interested). – llonesmiz Sep 16 '16 at 09:03
  • @jv_, I was about to say yesterday *as a joke* that one could write a transformation function from qi to karma and today you did it ! I am not an expert in parser so I wasn't sure if a complete transformation is always possible between a parser and a generator . This is very impressive! Spirit authors should notice your code. – alfC Sep 16 '16 at 11:56
  • @jv_ , by the way something like this will make spirit much more powerful because one could go back and forth in any specific grammar. Also, if you put this as an answer I will accept it. – alfC Sep 16 '16 at 12:16
  • You probably noticed, but in case you didn't,this approach does not work with `qi::grammar`s and `qi::rule`s, only with "raw" qi expressions. With that in mind, are you still interested in an answer? – llonesmiz Sep 16 '16 at 13:57
  • @jv_, I am afraid I don't know enough computer science to tell the difference between what I have in my code and a grammar, or between the constructed grammar and a `qi::grammar` (is it a type erasure of a grammar? What is the difference between the `GRAMMAR` and `qi::grammar`?) . I don't know, it looks like your code does exactly what I want in my example, perhaps if I build a more complicated case then I will appreciate the limitation. I don't understand what is a `qi::rule` even. All I know about what I am constructing is that the grammar will not be recursive (does it help?). – alfC Sep 16 '16 at 14:03
  • A recursive one would not work with this approach (since it would require a rule or grammar). In order to make the Qi->Karma transformation you need a lot of information about the used parsers that is lost when you use `qi::rule`/`qi::grammar`. – llonesmiz Sep 16 '16 at 14:08
  • 2
    @sehe and alfC: In an even [sillier approach](http://melpon.org/wandbox/permlink/Akrk9k9u9F4Tf1Vb), I have tried to implement a macro that takes something very similar to the "transformation table" in my answer and produces the corresponding Proto grammar (so we now have Boost.Preprocessor->Boost.Metaparse->Boost.Proto->Boost.Spirit, I haven't tested it extensively but the compile times must be really awful). The use of the `QI_TO_KARMA` macro defines a struct `grammar` inside namespace `qi_to_karma`. Right now it only works with binary expressions (so you can't match optional for example). – llonesmiz Sep 24 '16 at 09:01

1 Answers1

5

Sadly it's not possible to achieve what you want in a general way (or at least I don't know how), but if you are willing to just use a limited subset of Spirit.Qi the approach below could work.

The first thing to know is that when you use something like:

int_ >> double_

You just have a Boost.Proto expression that describes several terminals and how they are related. That expression by itself doesn't "know" anything about how to parse one int and then one double. Whenever you use parse/phrase_parse or assign one of these Proto expressions to a rule Spirit "compiles" that expression for a domain (Qi or Karma) and creates the parsers/generators that do the actual work.

Here you can see a small example that shows the exact types of the Proto and compiled Qi expressions:

Raw proto type:
boost::proto::exprns_::expr<boost::proto::tagns_::tag::shift_right, boost::proto::argsns_::list2<boost::spirit::terminal<boost::spirit::tag::int_> const&, boost::spirit::terminal<boost::spirit::tag::double_> const&>, 2l>

"Pretty" proto type:
shift_right(
    terminal(boost::spirit::tag::int_)
  , terminal(boost::spirit::tag::double_)
)

Compiled Qi type:
boost::spirit::qi::sequence<boost::fusion::cons<boost::spirit::qi::any_int_parser<int, 10u, 1u, -1>, boost::fusion::cons<boost::spirit::qi::any_real_parser<double, boost::spirit::qi::real_policies<double> >, boost::fusion::nil_> > >

As long as you have access to the original expression you can use Proto transforms/grammars to convert it to a suitable Karma expression.

In the example below I have used the following transformations:

Qi          |Karma          |Reason  
------------|---------------|------  
lexeme[expr]|verbatim[expr] | lexeme does not exist in Karma
omit[expr]  |no_delimit[eps]| omit consumes an attribute in Karma
a >> b      |a << b         |
a > b       |a << b         | < does not exist in Karma
a - b       |a              | - does not exist in Karma

In order to achieve this transformations you can use boost::proto::or_ getting something similar to:

struct Grammar : proto::or_<
                     proto::when<Matcher1,Transform1>,
                     proto::when<Matcher2,Transform2>,
                     Matcher3,
                     Matcher4
>{};

I'll try to explain how this works.
MatcherN in the example below can be:

  • proto::terminal<boost::spirit::tag::omit>: matches only that specific terminal.
  • proto::terminal<proto::_>: matches any terminal not specifically matched before.
  • proto::subscript<proto::terminal<boost::spirit::tag::omit>,proto::_>: matches omit[expr] where expr can be anything.
  • proto::shift_right<ToKarma,ToKarma>: matches expr1 >> expr2 where expr1 and expr2 must recursively conform to the ToKarma grammar.
  • proto::nary_expr<proto::_,proto::vararg<ToKarma> >: matches any n-ary (unary, binary or actually n-ary like a function call a(b,c,d,e)) where each one of the elements of the expression conforms to the ToKarma grammar.

All the TransformN in this example are expression builders, here are some explanations:

  • _make_terminal(boost::spirit::tag::lexeme()): builds a proto::terminal<boost::spirit::tag::lexeme> (note that it is necessary to add () after the tag, you'll get an awful error if you forget them).
  • _make_subscript(_make_terminal(tag::no_delimit()), _make_terminal(tag::eps())): builds a proto::subscript<proto::terminal<tag::no_delimit>, proto::terminal<tag::eps> >, or the equivalent to no_delimit[eps].
  • _make_shift_left(ToKarma(proto::_left), ToKarma(proto::_right)): proto::_left means take the lhs of the original expression. ToKarma(proto::_left) means recursively apply the ToKarma grammar/transform to the lhs of the original expression. The whole _make_shift_left basically builds transformed_lhs << transformed_rhs.

A MatcherN by itself (not inside proto::when) is a shorthand for build an expression of the same type using as elements the result of recursively applying the transform to the original elements.


Full Sample (Running on WandBox)

#include <iostream>
#include <string>
#include <tuple>
#include <boost/config/warning_disable.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/karma.hpp>
#include <boost/fusion/include/std_tuple.hpp>

namespace proto= boost::proto;


struct ToKarma: proto::or_<
    //translation of directives
    proto::when<proto::terminal<boost::spirit::tag::lexeme>, proto::_make_terminal(boost::spirit::tag::verbatim())>, //lexeme -> verbatim
    proto::when<
        proto::subscript<proto::terminal<boost::spirit::tag::omit>,proto::_>, //omit[expr] -> no_delimit[eps]
        proto::_make_subscript(proto::_make_terminal(boost::spirit::tag::no_delimit()),proto::_make_terminal(boost::spirit::tag::eps()))
    >,

    proto::terminal<proto::_>, //if the expression is any other terminal leave it as is

    //translation of operators
    proto::when<proto::shift_right<ToKarma,ToKarma>, proto::_make_shift_left(ToKarma(proto::_left),ToKarma(proto::_right)) >, //changes '>>' into '<<'
    proto::when<proto::greater<ToKarma,ToKarma>, proto::_make_shift_left(ToKarma(proto::_left),ToKarma(proto::_right)) >, //changes '>' into '<<'
    proto::when<proto::minus<ToKarma,ToKarma>, ToKarma(proto::_left)>, //changes 'expr-whatever' into 'expr'

    proto::nary_expr<proto::_,proto::vararg<ToKarma> > //if it's anything else leave it unchanged and recurse into the expression tree
>{};


template <typename ... Attr, typename Parser>
void test(const std::string& input, const Parser& parser)
{
    std::cout << "Original: \"" << input << "\"\n";

    std::tuple<Attr...> attr;

    std::string::const_iterator iter = input.begin(), end = input.end();

    bool result = boost::spirit::qi::phrase_parse(iter,end,parser,boost::spirit::qi::space,attr);

    if(result && iter==end)
    {
        ToKarma to_karma;
        std::cout << "Generated: \"" << boost::spirit::karma::format_delimited(to_karma(parser), boost::spirit::karma::space, attr) << '"' << std::endl;
    }
    else
    {
        std::cout << "Parsing failed. Unparsed: ->" << std::string(iter,end) << "<-" << std::endl;
    }
}



int main(){
    using namespace boost::spirit::qi;

    test<std::string,double,std::string >("Xx     1.233    pseudo", lexeme[+(char_-' '-'\n')] >> double_ >> lexeme[+(char_-' '-'\n')]);
    test<int,double>("foo 1 2.5", omit[lexeme[+alpha]] > int_ > double_);
}

PS:
Things that definitely won't work:

  • qi::rule
  • qi::grammar
  • qi::symbols

Things that don't exist in Karma:

  • qi::attr
  • qi::matches
  • qi::hold
  • Permutation parser ^
  • Sequential Or parser ||

Things that have different semantics in Karma:

  • qi::skip
  • And-predicate parser &
  • Not-predicate parser !
llonesmiz
  • 155
  • 2
  • 11
  • 20