front_end.qbk
来自「Boost provides free peer-reviewed portab」· QBK 代码 · 共 554 行 · 第 1/3 页
QBK
554 行
[/ / Copyright (c) 2007 Eric Niebler / / Distributed under the Boost Software License, Version 1.0. (See accompanying / file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) /][/================================================================================][section:front_end Fronts Ends: Defining Terminals and Non-Terminals of Your DSEL][/================================================================================]Here is the fun part: designing your own mini-programming language. In this section we'll talk about the nuts and bolts of designing a DSEL interface using Proto. We'll cover the definition of terminals and lazy functions that the users of your DSEL will get to program with. We'll also talk about Proto's expression template-building operator overloads, and about ways to add additional members to expressions within your domain.[/=======================][section Making Terminals][/=======================]As we saw with the Calculator example from the Introduction, the simplest way to get a DSEL up and running is simply to define some terminals, as follows. // Define a literal integer Proto expression. proto::terminal<int>::type i = {0}; // This creates an expression template. i + 1;With some terminals and Proto's operator overloads, you can immediately start creating expression templates. Defining terminals -- with aggregate initialization -- can be a little awkward at times. Proto provides an easier-to-use wrapper for literals that can be used to construct Protofied terminal expressions. It's called _literal_. // Define a literal integer Proto expression. proto::literal<int> i = 0; // Proto literals are really just Proto terminal expressions. // For example, this builds a Proto expression template: i + 1;There is also a _lit_ function for constructing a _literal_ in-place. The above expression can simply be written as: // proto::lit(0) creates an integer terminal expression proto::lit(0) + 1;[endsect][/=================================][section Proto's Operator Overloads][/=================================]Once we have some Proto terminals, expressions involving those terminals build expression trees for us. Proto defines overloads for each of C++'s overloadable operators in the `boost::proto` namespace. As long as one operand is a Proto expression, the result of the operation is a tree node representing that operation. [note Proto's operator overloads live in the `boost::proto` namespace and are found via ADL (argument-dependent lookup). That is why expressions must be "tainted" with Proto-ness for Proto to be able to build trees out of expressions.]As a result of Proto's operator overloads, we can say: -_1; // OK, build a unary-negate tree node _1 + 42; // OK, build a binary-plus tree nodeFor the most part, this Just Works and you don't need to think about it, but a few operators are special and it can be helpful to know how Proto handles them.[/=========================================================][heading Assignment, Subscript, and Function Call Operators][/=========================================================]Proto also overloads `operator=`, `operator[]`, and `operator()`, but these operators are member functions of the expression template rather than free functions in Proto's namespace. The following are valid Proto expressions: _1 = 5; // OK, builds a binary assign tree node _1[6]; // OK, builds a binary subscript tree node _1(); // OK, builds a unary function tree node _1(7); // OK, builds a binary function tree node _1(8,9); // OK, builds a ternary function tree node // ... etc.For the first two lines, assignment and subscript, it should be fairly unsurprisingthat the resulting expression node should be binary. After all, there aretwo operands in each expression. It may be surprising at first that what appearsto be a function call with no arguments, `_1()`, actually creates an expressionnode with one child. The child is `_1` itself. Likewise, the expression `_1(7)`has two children: `_1` and `7`.Because these operators can only be defined as member functions, the following expressions are invalid: int i; i = _1; // ERROR: cannot assign _1 to an int int *p; p[_1]; // ERROR: cannot use _1 as an index std::sin(_1); // ERROR: cannot call std::sin() with _1Also, C++ has special rules for overloads of `operator->` that make it uselessfor building expression templates, so Proto does not overload it.[/==============================][heading The Address-Of Operator][/==============================]Proto overloads the address-of operator for expression types, so that thefollowing code creates a new unary address-of tree node: &_1; // OK, creates a unary address-of tree nodeIt does /not/ return the address of the `_1` object. However, there isspecial code in Proto such that a unary address-of node is implicitlyconvertible to a pointer to its child. In other words, the followingcode works and does what you might expect, but not in the obvious way: typedef proto::terminal< placeholder<0> >::type _1_type; _1_type const _1 = {{}}; _1_type const * p = &_1; // OK, &_1 implicitly converted[endsect][/============================][section Making Lazy Functions][/============================]If we limited ourselves to nothing but terminals and operator overloads, our domain-specific embedded languages wouldn't be very expressive. Imagine that we wanted to extend our calculator DSEL with a full suite of math functions like `sin()` and `pow()` that we could invoke lazily as follows. // A calculator expression that takes one argument // and takes the sine of it. sin(_1);We would like the above to create an expression template representing a function invocation. When that expression is evaluated, it should cause the function to be invoked. (At least, that's the meaning of function invocation we'd like the calculator DSEL to have.) You can define `sin` quite simply as follows. // "sin" is a Proto terminal containing a function pointer proto::terminal< double(*)(double) >::type const sin = {&std::sin};In the above, we define `sin` as a Proto terminal containing a pointer to the `std::sin()` function. Now we can use `sin` as a lazy function. The `default_context` that we saw in the Introduction knows how to evaluate lazy functions. Consider the following: double pi = 3.1415926535; proto::default_context ctx; // Create a lazy "sin" invocation and immediately evaluate it std::cout << proto::eval( sin(pi/2), ctx ) << std::endl;The above code prints out:[pre 1]It is important to note that there is nothing special about terminals that contain function pointers. /Any/ Proto expression has an overloaded function call operator. Consider: // This compiles! proto::lit(1)(2)(3,4)(5,6,7,8);That may look strange at first. It creates an integer terminal with _lit_, and then invokes it like a function again and again. What does it mean? To be sure, the `default_context` wouldn't know what to do with it. The `default_context` only knows how to evaluate expressions that are sufficiently C++-like. In the case of function call expressions, the left hand side must evaluate to something that can be invoked: a pointer to a function, a reference to a function, or a TR1-style function object. That doesn't stop you from defining your own evaluation context that gives that expression a meaning. But more on that later.[/=======================================][heading Making Lazy Functions, Continued][/=======================================]Now, what if we wanted to add a `pow()` function to our calculator DSEL that users could invoke as follows? // A calculator expression that takes one argument // and raises it to the 2nd power pow< 2 >(_1);The simple technique described above of making `pow` a terminal containing a function pointer doesn't work here. If `pow` is an object, then the expression `pow< 2 >(_1)` is not valid C++. `pow` needs to be a real function template. But it must be an unusual function; it must return an expression template.Before we can write the `pow()` function, we need a function object that wraps an invocation of `std::pow()`. // Define a pow_fun function object template<int Exp> struct pow_fun { typedef double result_type; double operator()(double d) const { return std::pow(d, Exp); } };Now, let's try to define a function template that returns an expression template. We'll use the `proto::function<>` metafunction to calculate the type of a Proto expression that represents a function call. It is analogous to _terminal_. (We'll see a couple of different ways to solve this problem, and each will demonstrate another utility for defining Proto front-ends.) // Define a lazy pow() function for the calculator DSEL. // Can be used as: pow< 2 >(_1) template<int Exp, typename Arg> typename proto::function< typename proto::terminal<pow_fun<Exp> >::type , Arg const & >::type pow(Arg const &arg) { typedef
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?