back_end.qbk

来自「Boost provides free peer-reviewed portab」· QBK 代码 · 共 1,356 行 · 第 1/5 页

QBK
1,356
字号
    {};    terminal<placeholder<0> >::type const _1 = {{}};    terminal<placeholder<1> >::type const _2 = {{}};    // ...    calculator_context ctx;    ctx.args.push_back(4);    ctx.args.push_back(5);    double j = proto::eval( (_2 - _1) / _2 * 100, ctx );    std::cout << "j = " << j << std::endl;The above code displays the following:[prej = 20][endsect][endsect][endsect][import ../test/examples.cpp][/============================================================================][section:expression_transformation Expression Transformation: Semantic Actions][/============================================================================]If you have ever built a parser with the help of a tool like Antlr, yacc or Boost.Spirit, you might be familiar with /semantic actions/. In addition to allowing you to define the grammar of the language recognized by the parser, these tools let you embed code within your grammar that executes when parts of the grammar participate in a parse. Proto has the equivalent of semantic actions. They are called /transforms/. This section describes how to embed transforms within your Proto grammars, turning your grammars into function objects that can manipulate or evaluate expressions in powerful ways.Proto transforms are an advanced topic. We'll take it slow, using examples to illustrate the key concepts, starting simple.[/==================================][section ["Activating] Your Grammars][/==================================]The Proto grammars we've seen so far are static. You can check at compile-time to see if an expression type matches a grammar, but that's it. Things get more interesting when you give them runtime behaviors. A grammar with embedded transforms is more than just a static grammar. It is a function object that accepts expressions that match the grammar and does /something/ with them.Below is a very simple grammar. It matches terminal expressions.    // A simple Proto grammar that matches all terminals    proto::terminal< _ >Here is the same grammar with a transform that extracts the value from the terminal:    // A simple Proto grammar that matches all terminals    // *and* a function object that extracts the value from    // the terminal    proto::when<        proto::terminal< _ >      , proto::_value          // <-- Look, a transform!    >You can read this as follows: when you match a terminal expression, extract the value. The type `proto::_value` is a so-called transform. Later we'll see what makes it a transform, but for now just think of it as a kind of function object. Note the use of _when_: the first template parameter is the grammar to match and the second is the transform to execute. The result is both a grammar that matches terminal expressions and a function object that accepts terminal expressions and extracts their values. As with ordinary grammars, we can define an empty struct that inherits from a grammar+transform to give us an easy way to refer back to the thing we're defining, as follows:    // A grammar and a function object, as before    struct Value      : proto::when<            proto::terminal< _ >          , proto::_value        >    {};    // "Value" is a grammar that matches terminal expressions    BOOST_MPL_ASSERT(( proto::matches< proto::terminal<int>::type, Value > ));    // "Value" also defines a function object that accepts terminals    // and extracts their value.    proto::terminal<int>::type answer = {42};    Value get_value;    int i = get_value( answer );As already mentioned, `Value` is a grammar that matches terminal expressions and a function object that operates on terminal expressions. It would be an error to pass a non-terminal expression to the `Value` function object. This is a general property of grammars with transforms; when using them as function objects, expressions passed to them must match the grammar.Proto grammars are valid TR1-style function objects. That means you can use `boost::result_of<>` to ask a grammar what its return type will be, given a particular expression type. For instance, we can access the `Value` grammar's return type as follows:    // We can use boost::result_of<> to get the return type    // of a Proto grammar.    typedef        typename boost::result_of<Value(proto::terminal<int>::type)>::type    result_type;    // Check that we got the type we expected    BOOST_MPL_ASSERT(( boost::is_same<result_type, int> ));[note A grammar with embedded transforms is both a grammar and a function object. Calling these things "grammars with transforms" would get tedious. We could call them something like "active grammars", but as we'll see /every/ grammar that you can define with Proto is "active"; that is, every grammar has some behavior when used as a function object. So we'll continue calling these things plain "grammars". The term "transform" is reserved for the thing that is used as the second parameter to the _when_ template.][endsect][/=========================================][section Handling Alternation and Recursion][/=========================================]Most grammars are a little more complicated than the one in the preceding section. For the sake of illustration, let's define a rather nonsensical grammar that matches any expression and recurses to the leftmost terminal and returns its value. It will demonstrate how two key concepts of Proto grammars -- alternation and recursion -- interact with transforms. The grammar is described below.    // A grammar that matches any expression, and a function object    // that returns the value of the leftmost terminal.    struct LeftmostLeaf      : proto::or_<            // If the expression is a terminal, return its value            proto::when<                proto::terminal< _ >              , proto::_value            >            // Otherwise, it is a non-terminal. Return the result            // of invoking LeftmostLeaf on the 0th (leftmost) child.          , proto::when<                _              , LeftmostLeaf( proto::_child0 )            >        >    {};    // A Proto terminal wrapping std::cout    proto::terminal< std::ostream & >::type cout_ = { std::cout };        // Create an expression and use LeftmostLeaf to extract the    // value of the leftmost terminal, which will be std::cout.    std::ostream & sout = LeftmostLeaf()( cout_ << "the answer: " << 42 << '\n' );We've seen `proto::or_<>` before. Here it is serving two roles. First, it is a grammar that matches any of its alternate sub-grammars; in this case, either a terminal or a non-terminal. Second, it is also a function object that accepts an expression, finds the alternate sub-grammar that matches the expression, and applies its transform. And since `LeftmostLeaf` inherits from `proto::or_<>`, `LeftmostLeaf` is also both a grammar and a function object.[def _some_transform_ [~some-transform]][note The second alternate uses `proto::_` as its grammar. Recall that `proto::_` is the wildcard grammar that matches any expression. Since alternates in `proto::or_<>` are tried in order, and since the first alternate handles all terminals, the second alternate handles all (and only) non-terminals. Often enough, `proto::when< _, _some_transform_ >` is the last alternate in a grammar, so for improved readability, you could use the equivalent `proto::otherwise< _some_transform_ >`.]The next section describes this grammar further.[endsect][/==========================][section Callable Transforms][/==========================][def __bold_transform__ [*LeftmostLeaf( proto::_child0 )]]In the grammar defined in the preceding section, the transform associated with non-terminals is a little strange-looking:    proto::when<        _      , __bold_transform__   // <-- a "callable" transform    >It has the effect of accepting non-terminal expressions, taking the 0th (leftmost) child and recursively invoking the `LeftmostLeaf` function on it. But `LeftmostLeaf( proto::_child0 )` is actually a /function type/. Literally, it is the type of a function that accepts an object of type `proto::_child0` and returns an object of type `LeftmostLeaf`. So how do we make sense of this transform? Clearly, there is no function that actually has this signature, nor would such a function be useful. The key is in understanding how `proto::when<>` /interprets/ its second template parameter.When the second template parameter to _when_ is a function type, _when_ interprets the function type as a transform. In this case, `LeftmostLeaf` is treated as the type of a function object to invoke, and `proto::_child0` is treated as a transform. First, `proto::_child0` is applied to the current expression (the non-terminal that matched this alternate sub-grammar), and the result (the 0th child) is passed as an argument to `LeftmostLeaf`.[note *Transforms are a Domain-Specific Language*`LeftmostLeaf( proto::_child0 )` /looks/ like an invocation of the `LeftmostLeaf` function object, but it's not, but then it actually is! Why this confusing subterfuge? Function types give us a natural and concise syntax for composing more complicated transforms from simpler ones. The fact that the syntax is suggestive of a function invocation is on purpose. It is a domain-specific embedded language for defining expression transformations. If the subterfuge worked, it may have fooled you into thinking the transform is doing exactly what it actually does! And that's the point.]The type `LeftmostLeaf( proto::_child0 )` is an example of a /callable transform/. It is a function type that represents a function object to call and its arguments. The types `proto::_child0` and `proto::_value` are /primitive transforms/. They are plain structs, not unlike function objects, from which callable transforms can be composed. There is one other type of transform, /object transforms/, that we'll encounter next.[endsect][/========================][section Object Transforms][/========================]The very first transform we looked at simply extracted the value of terminals. Let's do the same thing, but this time we'll promote all ints to longs first. (Please forgive the contrived-ness of the examples so far; they get more interesting later.) Here's the grammar:    // A simple Proto grammar that matches all terminals,    // and a function object that extracts the value from    // the terminal, promoting ints to longs:    struct ValueWithPomote      : proto::or_<            proto::when<                proto::terminal< int >              , long(proto::_value)     // <-- an "object" transform            >          , proto::when<                proto::terminal< _ >              , proto::_value            >        >    {};You can read the above grammar as follows: when you match an int terminal, extract the value from the terminal and use it to initialize a long; otherwise, when you match another kind of terminal, just extract the value. The type `long(proto::_value)` is a so-called /object/ transform. It looks like the creation of a temporary long, but it's really a function type. Just as a callable transform is a function type that represents a function to call and its arguments, an object transforms is a function type that represents an object to construct and the arguments to its constructor.[/================================================][note *Object Transforms vs. Callable Transforms*When using function types as Proto transforms, they can either represent an object to construct or a function to call. It is similar to "normal" C++ where the syntax `foo("arg")` can either be interpreted as an object to construct or a function to call, depending on whether `foo` is a type or a function. But consider two of the transforms we've seen so far:``    LeftmostLeaf(proto::_child0)  // <-- a callable transform    long(proto::_value)           // <-- an object transform``Proto can't know in general which is which, so it uses a trait, `proto::is_callable<>`, to differentiate. `is_callable< long >::value` is false so `long(proto::_value)` is an object to construct, but `is_callable< LeftmostLeaf >::value` is true so `LeftmostLeaf(proto::_child0)` is a function to call. Later on, we'll see how Proto recognizes a type as "callable".][/================================================][endsect][/================================][section Example: Calculator Arity][/================================]Now that we have the basics of Proto transforms down, let's consider a slightly more realistic example. We can use transforms to improve the type-safety of the [link boost_proto.users_guide.getting_started.hello_calculator calculator DSEL]. If you recall, it lets you write infix arithmetic expressions involving argument placeholders like `_1` and `_2` and pass them to STL algorithms as function objects, as follows:    double a1[4] = { 56, 84, 37, 69 };    double a2[4] = { 65, 120, 60, 70 };    double a3[4] = { 0 };    // Use std::transform() and a calculator expression    // to calculate percentages given two input sequences:    std::transform(a1, a1+4, a2, a3, (_2 - _1) / _2 * 100);This works because we gave calculator expressions an `operator()` that evaluates the expression, replacing the placeholders with the arguments to `operator()`. The overloaded `calculator<>::operator()` looked like this:    // Overload operator() to invoke proto::eval() with    // our calculator_context.    template<typename Expr>    double    calculator<Expr>::operator()(double a1 = 0, double a2 = 0) const    {        calculator_context ctx;        ctx.args.push_back(a1);        ctx.args.push_back(a2);                return proto::eval(*this, ctx);    }Although this works, it's not ideal because it doesn't warn users if they supply too many or too few arguments to a calculator expression. Consider the following mistakes:    (_1 * _1)(4, 2);  // Oops, too many arguments!    (_2 * _2)(42);    // Oops, too few arguments!The expression `_1 * _1` defines a unary calculator expression; it takes one argument and squares it. If we pass more than one argument, the extra arguments will be silently ignored, which might be surprising to users. The next expression, `_2 * _2` defines a binary calculator expression; it takes two arguments, ignores the first and squares the second. If we only pass one argument, the code silently fills in `0.0` for the second argument, which is also probably not what users expect. What can be done?We can say that the /arity/ of a calculator expression is the number of arguments it expects, and it is equal to the largest placeholder in the expression. So, the arity of `_1 * _1` is one, and the arity of `_2 * _2` is two. We can increase the type-safety of our calculator DSEL by making sure the arity of an expression equals the actual number of arguments supplied. Computing the arity of an expression is simple with the help of Proto transforms.It's straightforward to describe in words how the arity of an expression shouldbe calculated. Consider that calculator expressions can be made of `_1`, `_2`, literals, unary expressions and binary expressions. The following table shows the arities for each of these 5 constituents.[table Calculator Sub-Expression Arities    [[Sub-Expression]       [Arity]]    [[Placeholder 1]        [`1`]]    [[Placeholder 2]        [`2`]]    [[Literal]              [`0`]]    [[Unary Expression]     [ /arity of the operand/ ]]    [[Binary Expression]    [ /max arity of the two operands/ ]]]Using this information, we can write the grammar for calculator expressions and attach transforms for computing the arity of each constituent. The code below computes the expression arity as a compile-time integer, using integral wrappers and metafunctions from the Boost MPL Library. The grammar is described below.[CalcArity]When we find a placeholder terminal or a literal, we use an /object transform/ such as `mpl::int_<1>()` to create a (default-constructed) compile-time integer representing the arity of that terminal.For unary expressions, we use `CalcArity(proto::_child)` which is a /callable transform/ that computes the arity of the expression's child.The transform for binary expressions has a few new tricks. Let's look more closely:    // Compute the left and right arities and    // take the larger of the two.    mpl::max<CalcArity(proto::_left),             CalcArity(proto::_right)>()This is an object transform; it default-constructs ... what exactly? The `mpl::max<>` template is an MPL metafunction that accepts two compile-time integers. It has a nested `::type` typedef (not shown) that is the maximum of the two. But here, we appear to be passing it two things that are /not/ compile-time integers; they're Proto callable transforms. Proto is smart enough to recognize that fact. It first evaluates the two nested callable transforms, computing the arities of the left and right child expressions. Then it puts the resulting integers into `mpl::max<>` and evaluates the metafunction by asking for the nested `::type`. That is the type of the object that gets default-constructed and returned.More generally, when evaluating object transforms, Proto looks at the object type and checks whether it is a template specialization, like `mpl::max<>`. If it is, Proto looks for nested transforms that it can evaluate. After any nested transforms have been evaluated and substituted back into the template, the new template specialization is the result type, unless that type has a nested `::type`, in which case that becomes the result.Now that we can calculate the arity of a calculator expression, let's redefine the `calculator<>` expression wrapper we wrote in the Getting Started guide to use the `CalcArity` grammar and some macros from Boost.MPL to issue compile-time errors when users specify too many or too few arguments.    // The calculator expression wrapper, as defined in the Hello    // Calculator example in the Getting Started guide. It behaves

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?