c++ - How to parse text for a DSL at compile time? -


yes. that's right. want able paste expression like:

"a && b || c" 

directly source code string:

const std::string expression_text("a && b || c"); 

create lazily evaluated structure it:

expr expr(magical_function(expression_text)); 

then later on evaluate substituting in known values:

evaluate(expr, a, b, c); 

i'd want expand little dsl later little more complicated using non-c++ syntax can't hardcode expression simple way. use case i'll able copy , paste same logic module used in different development area language rather have adapt each time follow c++ syntax.

if can me started on @ least how above simple concept of 1 expression , 2 boolean operators appreciated.

note: posted question due feedback question posted: how parse dsl input high performance expression template. here wanted answer different problem, comments provoked specific question thought worth posting potential answers worth documenting.

disclaimer: know nothing metaparse, , little proto. following code attempt (mostly via trial , error) modify this example similar want.

the code can divided in several parts:

1. grammar


1.1 token definitions

typedef token < lit_c < 'a' > > arg1_token; typedef token < lit_c < 'b' > > arg2_token; typedef token < lit_c < 'c' > > arg3_token; 
  • token<parser>:
    token parser combinator uses parser parse input , consumes (and discards) whitespaces afterwards. result of parsing result of parser.
  • lit_c<char>:
    lit_c matches specific char , result of parsing same char. in grammar result overridden use of always.
typedef token < keyword < _s ( "true" ), bool_<true> > > true_token; typedef token < keyword < _s ( "false" ), bool_<false> > > false_token; 
  • keyword<metaparse_string,result_type=undefined>:
    keyword matches specific metaparse_string (_s("true") returns metaparse::string<'t','r','u','e'> metaparse uses internally magic) , result of parsing result_type.
typedef token < keyword < _s ( "&&" ) > > and_token; typedef token < keyword < _s ( "||" ) > > or_token; typedef token < lit_c < '!' > > not_token; 

in case of and_token , or_token result undefined , in grammar below ignored.


1.2 "rules" of grammar

struct paren_exp; 

first paren_exp forward-declared.

typedef one_of<          paren_exp,          transform<true_token, build_value>,         transform<false_token, build_value>,          always<arg1_token, arg<0> >,         always<arg2_token, arg<1> >,          always<arg3_token, arg<2> >      >     value_exp; 
  • one_of<parsers...>:
    one_of parser combinator tries match input 1 of parameters. result first parser matches returns.
  • transform<parser,semanticaction>:
    transform parser combinator matches parser. result type result type of parser transformed semanticaction.
  • always<parser,newresulttype>:
    matches parser, returns newresulttype.

    the equivalent spirit rule be:

    value_exp = paren_exp [ _val=_1 ]     | true_token      [ _val=build_value(_1) ]     | false_token     [ _val=build_value(_1) ]     | argn_token      [ _val=phx::construct<arg<n>>() ]; 
typedef one_of<          transform<last_of<not_token, value_exp>, build_not>,          value_exp     >     not_exp; 
  • last_of<parsers...>:
    last_of matches every 1 of parsers in sequence , result type result type of last parser.

    the equivalent spirit rule be:

    not_exp = (omit[not_token] >> value_exp) [ _val=build_not(_1) ]      | value_exp                          [ _val=_1 ]; 
typedef foldl_start_with_parser<         last_of<and_token, not_exp>,         not_exp,         build_and     > and_exp; // and_exp = not_exp >> *(omit[and_token] >> not_exp);  typedef foldl_start_with_parser<     last_of<or_token, and_exp>,     and_exp,     build_or > or_exp;     // or_exp = and_exp >> *(omit[or_token] >> and_exp); 
  • foldl_start_with_parser<repeatingparser,initialparser,semanticaction>:
    this parser combinator matches initialparser , repeatingparser multiple times until fails. result type result of mpl::fold<repeatingparsersequence, initialparserresult, semanticaction>, repeatingparsersequence sequence of result types of every application of repeatingparser. if repeatingparser never succeeds result type initialparserresult.

    i believe (xd) equivalent spirit rule be:

    or_exp = and_exp[_a=_1]      >> *( omit[or_token] >> and_exp [ _val = build_or(_1,_a), _a = _val ]);   
struct paren_exp: middle_of < lit_c < '(' > , or_exp, lit_c < ')' > > {};     // paren_exp = '(' >> or_exp >> ')'; 
  • middle_of<parsers...>:
    this matches sequence of parsers , result type result of parser in middle.
typedef last_of<repeated<space>, or_exp> expression;     //expression = omit[*space] >> or_exp; 
  • repeated<parser>:
    this parser combinator tries match parser multiple times. result sequence of result types of every application of parser, if parser fails on first try result empty sequence. rule removes leading whitespace.
typedef build_parser<entire_input<expression> > function_parser; 

this line creates metafunction accepts input string , returns result of parsing.


2. construction of expression

let's @ example walkthrough of building of expression. done in 2 steps: first grammar constructs tree depends on build_or, build_and, build_value, build_not , arg<n>. once type, can proto expression using proto_type typedef.

"a || !b"

we start on or_expr:

  • or_expr: try initialparser and_expr.
    • and_expr: try initialparser not_expr.
      • not_expr: not_token fails try value_expr.
        • value_expr: arg1_token succeeds. return type arg<0> , go not_expr.
      • not_expr: return type not modified @ step. go and_expr.
    • and_expr: try repeatingparser, fails. and_expr succeeds , return type return type of initialparser: arg<0>. go or_expr.
    • or_expr: try repeatingparser, or_token matches, try and_expr.
    • and_expr: try initialparser not_expr.
      • not_expr: not_token succeeds, try value_expr.
        • value_expr: arg2_token succeeds. return type arg<1> , go not_expr.
      • not_expr: return type modified transform using build_not: build_not::apply< arg<1> >. go and_expr.
    • and_expr: try repeatingparser, fails. and_expr succeeds , returns build_not::apply< arg<1> >. go or_expr.
  • or_expr: repeatingparser has succeeded, foldlp uses build_or on build_not::apply< arg<1> > , arg<0>, obtaining build_or::apply< build_not::apply< arg<1> >, arg<0> >.

once have tree constructed proto_type:

build_or::apply< build_not::apply< arg<1> >, arg<0> >::proto_type; proto::logical_or< arg<0>::proto_type, build_not::apply< arg<1> >::proto_type >::type; proto::logical_or< proto::terminal< placeholder<0> >::type, build_not::apply< arg<1> >::proto_type >::type; proto::logical_or< proto::terminal< placeholder<0> >::type, proto::logical_not< arg<1>::proto_type >::type >::type; proto::logical_or< proto::terminal< placeholder<0> >::type, proto::logical_not< proto::terminal< placeholder<1> >::type >::type >::type; 

full sample code (running on wandbox)

#include <iostream> #include <vector>  #include <boost/metaparse/repeated.hpp> #include <boost/metaparse/sequence.hpp> #include <boost/metaparse/lit_c.hpp> #include <boost/metaparse/last_of.hpp> #include <boost/metaparse/middle_of.hpp> #include <boost/metaparse/space.hpp> #include <boost/metaparse/foldl_start_with_parser.hpp> #include <boost/metaparse/one_of.hpp> #include <boost/metaparse/token.hpp> #include <boost/metaparse/entire_input.hpp> #include <boost/metaparse/string.hpp> #include <boost/metaparse/transform.hpp> #include <boost/metaparse/always.hpp> #include <boost/metaparse/build_parser.hpp> #include <boost/metaparse/keyword.hpp>  #include <boost/mpl/apply_wrap.hpp> #include <boost/mpl/front.hpp> #include <boost/mpl/back.hpp> #include <boost/mpl/bool.hpp>  #include <boost/proto/proto.hpp> #include <boost/fusion/include/at.hpp> #include <boost/fusion/include/make_vector.hpp>  using boost::metaparse::sequence; using boost::metaparse::lit_c; using boost::metaparse::last_of; using boost::metaparse::middle_of; using boost::metaparse::space; using boost::metaparse::repeated; using boost::metaparse::build_parser; using boost::metaparse::foldl_start_with_parser; using boost::metaparse::one_of; using boost::metaparse::token; using boost::metaparse::entire_input; using boost::metaparse::transform; using boost::metaparse::always; using boost::metaparse::keyword;  using boost::mpl::apply_wrap1; using boost::mpl::front; using boost::mpl::back; using boost::mpl::bool_;   struct build_or {     typedef build_or type;      template <class c, class state>     struct apply     {         typedef apply type;         typedef typename boost::proto::logical_or<typename state::proto_type, typename c::proto_type >::type proto_type;     }; };  struct build_and {     typedef build_and type;      template <class c, class state>     struct apply     {         typedef apply type;         typedef typename boost::proto::logical_and<typename state::proto_type, typename c::proto_type >::type proto_type;     }; };    template<bool i> struct value //helper struct used during evaluation in proto context {};  struct build_value {     typedef build_value type;      template <class v>     struct apply     {         typedef apply type;         typedef typename boost::proto::terminal<value<v::type::value> >::type proto_type;     }; };  struct build_not {     typedef build_not type;      template <class v>     struct apply     {         typedef apply type;         typedef typename boost::proto::logical_not<typename v::proto_type >::type proto_type;     }; };  template<int i> struct placeholder //helper struct used during evaluation in proto context {};  template<int i> struct arg {     typedef arg type;     typedef typename boost::proto::terminal<placeholder<i> >::type proto_type; };  #ifdef _s #error _s defined #endif #define _s boost_metaparse_string  typedef token < keyword < _s ( "&&" ) > > and_token; typedef token < keyword < _s ( "||" ) > > or_token; typedef token < lit_c < '!' > > not_token;  typedef token < keyword < _s ( "true" ), bool_<true> > > true_token; typedef token < keyword < _s ( "false" ), bool_<false> > > false_token;  typedef token < lit_c < 'a' > > arg1_token; typedef token < lit_c < 'b' > > arg2_token; typedef token < lit_c < 'c' > > arg3_token;   struct paren_exp;  typedef one_of< paren_exp, transform<true_token, build_value>, transform<false_token, build_value>, always<arg1_token, arg<0> >, always<arg2_token, arg<1> >, always<arg3_token, arg<2> > > value_exp; //value_exp = paren_exp | true_token | false_token | arg1_token | arg2_token | arg3_token;  typedef one_of< transform<last_of<not_token, value_exp>, build_not>, value_exp> not_exp; //not_exp = (omit[not_token] >> value_exp) | value_exp;  typedef foldl_start_with_parser < last_of<and_token, not_exp>,          not_exp,          build_and          >          and_exp; // and_exp = not_exp >> *(and_token >> not_exp);  typedef foldl_start_with_parser < last_of<or_token, and_exp>,          and_exp,          build_or          >          or_exp; // or_exp = and_exp >> *(or_token >> and_exp);  struct paren_exp: middle_of < lit_c < '(' > , or_exp, lit_c < ')' > > {}; //paren_exp = lit('(') >> or_exp >> lit('(');  typedef last_of<repeated<space>, or_exp> expression; //expression = omit[*space] >> or_exp;  typedef build_parser<entire_input<expression> > function_parser;   template <typename args> struct calculator_context         : boost::proto::callable_context< calculator_context<args> const > {     calculator_context ( const args& args ) : args_ ( args ) {}     // values replace placeholders     const args& args_;      // define result type of calculator.     // (this makes calculator_context "callable".)     typedef bool result_type;      // handle placeholders:     template<int i>     bool operator() ( boost::proto::tag::terminal, placeholder<i> ) const     {         return boost::fusion::at_c<i> ( args_ );     }      template<bool i>     bool operator() ( boost::proto::tag::terminal, value<i> ) const     {         return i;     } };  template <typename args> calculator_context<args> make_context ( const args& args ) {     return calculator_context<args> ( args ); }  template <typename expr, typename ... args> int evaluate ( const expr& expr, const args& ... args ) {     return boost::proto::eval ( expr, make_context ( boost::fusion::make_vector ( args... ) ) ); }  #ifdef lambda #error lambda defined #endif #define lambda(exp) apply_wrap1<function_parser, _s(exp)>::type::proto_type{}  int main() {     using std::cout;     using std::endl;      cout << evaluate ( lambda ( "true&&false" ) ) << endl;     cout << evaluate ( lambda ( "true&&a" ), false ) << endl;     cout << evaluate ( lambda ( "true&&a" ), true ) << endl;     cout << evaluate ( lambda ( "a&&b" ), true, false ) << endl;     cout << evaluate ( lambda ( "a&&(b||c)" ), true, false, true ) << endl;     cout << evaluate ( lambda ( "!a&&(false||(b&&!c||false))" ), false, true, false ) << endl; }  /*int main(int argc , char** argv) {     using std::cout;     using std::endl;      bool a=false, b=false, c=false;      if(argc==4)     {         a=(argv[1][0]=='1');         b=(argv[2][0]=='1');         c=(argv[3][0]=='1');     }      lambda("a && b || c") expr;      cout << evaluate(expr, true, true, false) << endl;     cout << evaluate(expr, a, b, c) << endl;      return 0; }*/ 

Comments

Popular posts from this blog

html5 - What is breaking my page when printing? -

c# - must be a non-abstract type with a public parameterless constructor in redis -

ajax - PHP/JSON Login script (Twitter style) not setting sessions -