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 usesparser
parse input , consumes (and discards) whitespaces afterwards. result of parsing result ofparser
.lit_c<char>
:
lit_c matches specificchar
, result of parsing same char. in grammar result overridden use ofalways
.
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 specificmetaparse_string
(_s("true")
returnsmetaparse::string<'t','r','u','e'>
metaparse uses internally magic) , result of parsingresult_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 matchesparser
. result type result type ofparser
transformedsemanticaction
.always<parser,newresulttype>
:
matchesparser
, returnsnewresulttype
.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 ofparsers
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 matchesinitialparser
,repeatingparser
multiple times until fails. result type result ofmpl::fold<repeatingparsersequence, initialparserresult, semanticaction>
,repeatingparsersequence
sequence of result types of every application ofrepeatingparser
. ifrepeatingparser
never succeeds result typeinitialparserresult
.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 ofparsers
, 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 matchparser
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 initialparserand_expr
.and_expr
: try initialparsernot_expr
.
not_expr
: not_token fails tryvalue_expr
.
value_expr
: arg1_token succeeds. return typearg<0>
, gonot_expr
.
not_expr
: return type not modified @ step. goand_expr
.
and_expr
: try repeatingparser, fails. and_expr succeeds , return type return type of initialparser:arg<0>
. goor_expr
.or_expr
: try repeatingparser, or_token matches, tryand_expr
.
and_expr
: try initialparsernot_expr
.
not_expr
: not_token succeeds, tryvalue_expr
.
value_expr
: arg2_token succeeds. return typearg<1>
, gonot_expr
.
not_expr
: return type modified transform using build_not: build_not::apply< arg<1> >. goand_expr
.
and_expr
: try repeatingparser, fails. and_expr succeeds , returns build_not::apply< arg<1> >. goor_expr
.
or_expr
: repeatingparser has succeeded, foldlp uses build_or onbuild_not::apply< arg<1> >
,arg<0>
, obtainingbuild_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
Post a Comment