grammars.qbk
来自「Boost provides free peer-reviewed portab」· QBK 代码 · 共 482 行 · 第 1/2 页
QBK
482 行
There is one more way you can perform a fuzzy match on terminals. Consider theproblem of trying to match a `std::complex<>` terminal. You can easily matcha `std::complex<float>` or a `std::complex<double>`, but how would you matchany instantiation of `std::complex<>`? You can use `proto::_` here to solvethis problem. Here is the grammar to match any `std::complex<>` instantiation: struct StdComplex : terminal< std::complex< _ > > {};When given a grammar like this, Proto will deconstruct the grammar and theterminal it is being matched against and see if it can match all theconstituents.[endsect][/====================================================][section:if_and_not [^if_<>], [^and_<>], and [^not_<>]][/====================================================]We've already seen how to use expression generators like `terminal<>` and`shift_right<>` as grammars. We've also seen _or_, which we can use toexpress a set of alternate grammars. There are a few others of interest; inparticular, _if_, _and_ and _not_.The _not_ template is the simplest. It takes a grammar as a template parameterand logically negates it; `not_<Grammar>` will match any expression that`Grammar` does /not/ match.The _if_ template is used together with a Proto transform that is evaluatedagainst expression types to find matches. (Proto transforms will be describedlater.)The _and_ template is like _or_, except that each argument of the _and_ mustmatch in order for the _and_ to match. As an example, consider the definitionof `CharString` above that uses _exact_. It could have been written without_exact_ as follows: struct CharString : and_< terminal< _ > , if_< is_same< _arg, char const * >() > > {};This says that a `CharString` must be a terminal, /and/ its argument must bethe same as `char const *`. Notice the template argument of _if_:`is_same< _arg, char const * >()`. This is Proto transform that compares theargument of a terminal to `char const *`.The _if_ template has a couple of variants. In additon to `if_<Condition>` youcan also say `if_<Condition, ThenGrammar>` and`if_<Condition, ThenGrammar, ElseGrammar>`. These let you select one sub-grammaror another based on the `Condition`.[endsect][/==================================][section Matching Vararg Expressions][/==================================]Not all of C++'s overloadable operators are unary or binary. There is theoddball `operator()` -- the function call operator -- which can have any numberof arguments. Likewise, with Proto you may define your own "operators" thatcould also take more that two arguments. As a result, there may be nodes inyour Proto expression tree that have an arbitrary number of children (up tosome predefined maximum). How do you write a grammar to match such a node?For such cases, Proto provides the _vararg_ class template. Its templateargument is a grammar, and the _vararg_ will match the grammar zero or moretimes. Consider a Proto lazy function called `fun()` that can take zero ormore characters as arguments, as follows: struct fun_tag {}; struct FunTag : terminal< fun_tag > {}; FunTag::type const fun = {{}}; // example usage: fun(); fun('a'); fun('a', 'b'); ...Below is the grammar that matches all the allowable invocations of `fun()`: struct FunCall : function< FunTag, vararg< terminal< char > > > {};The `FunCall` grammar uses _vararg_ to match zero or more character literalsas arguments of the `fun()` function.As another example, can you guess what the following grammar matches? struct Foo : or_< terminal< _ > , nary_expr< _, vararg< Foo > > > {};Here's a hint: the first template parameter to `nary_expr<>` represents thenode type, and any additional template parameters represent children nodes. Theanswer is that this is a degenerate grammar that matches every possibleexpression tree, from root to leaves.[endsect][/=============================][section Defining DSEL Grammars][/=============================]We've already seen how to use small grammars to answer simple questions aboutexpression trees. Here's a harder question: ["Does this expression conform to thegrammar of my domain-specific embedded language?] In this section we'll see howto use Proto to define a grammar for your DSEL and use it to validateexpression templates, giving short, readable compile-time errors for invalidexpressions.[tip You might be thinking that this is a backwards way of doing things.["If Proto let me select which operators to overload, my users wouldn't be ableto create invalid expressions in the first place, and I wouldn't need a grammarat all!] That may be true, but there are reasons for preferring to do thingsthis way.First, it lets you develop your DSEL rapidly -- all the operators arethere for you already! -- and worry about invalid syntax later.Second, itmight be the case that some operators are only allowed in certain contextswithin your DSEL. This is easy to express with a grammar, and hard to do withstraight operator overloading. Third, using a DSEL grammar to flag invalidexpressions can often yield better errors than manually selecting theoverloaded operators. Fourth, the grammar can be used for more than justvalidation. As we'll see later, you can use your grammar to define ['treetransformations] that convert expression templates into other more usefulobjects.If none of the above convinces you, you actually /can/ use Proto to controlwhich operators are overloaded within your domain. And to do it, you need to define a grammar! We'll see how later.]In a previous section, we used Proto to define a DSEL for a lazily evaluatedcalculator that allowed any combination of placeholders, floating-pointliterals, addition, subtraction, multiplaction, division and grouping. Ifwe were to write the grammar for this DSEL in[@http://en.wikipedia.org/wiki/Extended_Backus_Naur_Form EBNF], it might looklike this:[pregroup ::= '(' expression ')'factor ::= double | placeholder1 | placeholder2 | groupterm ::= factor (('*' factor) | ('/' factor))*expression ::= term (('+' term) | ('-' term))*]This captures the syntax, associativity and precedence rules of a calculator.Writing the grammar for our calculator DSEL using Proto is /even simpler/.Since we are using C++ as the host language, we are bound to the associativityand precedence rules for the C++ operators. Our grammar can assume them. Also,in C++ grouping is already handled for us with the use of parenthesis, so wedon't have to code that into our grammar.Let's begin our grammar for forward-declaring it: struct CalculatorGrammar;It's an incomplete type at this point, but we'll still be able to use it todefine the rules of our grammar. Let's define grammar rules for the terminals: struct Double : terminal< convertible_to< double > > {}; struct Placeholder1 : terminal< placeholder1 > {}; struct Placeholder2 : terminal< placeholder2 > {}; struct Terminal : or_< Double, Placeholder1, Placeholder2 > {};Now let's define the rules for addition, subtraction, multiplication anddivision. Here, we can ignore issues of associativity and precedence -- the C++compiler will enforce that for us. We only must enforce that the arguments tothe operators must themselves conform to the `CalculatorGrammar` that weforward-declared above. struct Plus : plus< CalculatorGrammar, CalculatorGrammar > {}; struct Minus : minus< CalculatorGrammar, CalculatorGrammar > {}; struct Multiplies : multiplies< CalculatorGrammar, CalculatorGrammar > {}; struct Divides : divides< CalculatorGrammar, CalculatorGrammar > {};Now that we've defined all the parts of the grammar, we can define`CalculatorGrammar`: struct CalculatorGrammar : or_< Terminal , Plus , Minus , Multiplies , Divides > {};That's it! Now we can use `CalculatorGrammar` to enforce that an expressiontemplate conforms to our grammar. We can use _matches_ and `BOOST_MPL_ASSERT()`to issue readable compile-time errors for invalid expressions, as below: template< typename Expr > void evaluate( Expr const & expr ) { BOOST_MPL_ASSERT(( matches< Expr, CalculatorGrammar > )); // ... }[endsect][endsect]
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?