boost.spirit用户手册翻译(22):Phoenix

Phoenix

The preceding chapter introduced Phoenix as a means to implementing your semantic actions. We shall look a little bit more into this important library with focus on how you can use it handily with Spirit. This chapter is by no means a thorough discourse of the library. For more information on Phoenix, please take some time to read the Phoenix User's Guide. If you just want to use it quickly, this chapter will probably suffice. Rather than taking you to the theories and details of the library, we shall try to provide you with annotated exemplars instead. Hopefully, this will get you into high gear quickly.


前一章节把Phoenix作为实现语义动作的一种方法来介绍。下面我们将稍微深入了解这个重要的库,并把焦点集中于如何在Spirit中便利地使用它。但这一章节绝非对这个库的完整讨论。想了解Phoenix的更多信息,需要拿出点时间来阅读Phoenix用户指南。如果只是想尽快学会使用它,这一章将可以满足。比起告诉你这个库的理论和细节,我们倾向于把带有评注的例子提供给你。希望这样能使你的水平迅速提高。

Semantic actions in Spirit can be just about any function or function object (functor) as long as it can satisfy the required signature. For example, uint_p requires a signature of void F(T), where T is the type of the integer (typically unsigned int). Plain vanilla actions are of the void F(IterT, IterT) variety. You can code your actions in plain C++. Calls to C++ functions or functors will thus be of the form P[&F] or P[F()] etc. (see Semantic Actions). Phoenix on the other hand, attempts to mimic C++ such that you can define the function body inlined in the code.


Spirit的语义动作可以是任意的函数和函数对象(仿函数),只要他们符合所需的签名。比如,uint_p需要void F(T)这样的签名,这里T是整数的类型(一般为unsigned int)。一般的动作为void F(IterT,IterT)的变形。你可以只用C++来编写你的动作。对这些函数和仿函数的调用将有诸如P[&F] or P[F()]这样的形式(见语义动作)。Phoenix,在另一方面,试图模仿C++以使你能在代码中直接内联函数的内容。

C++ in C++?

C++里的C++?

In as much as Spirit attempts to mimic EBNF in C++, Phoenix attempts to mimic C++ in C++!!!

就像Spirit试图在C++里模仿EBNF,Phoenix试图在C++里模仿C++!!!

var

Remember the boost::ref? We discussed that in the Parametric Parsers chapter. Phoenix has a similar, but more flexible, counterpart. It's called var. The usage is similar to boost::ref and you can use it as a direct replacement. However, unlike boost::ref, you can use it to form more complex expressions. Here are some examples:

记得boost::ref么?我们在参数化分析器一章中讨论过它。Phoenix有一个类似的,但更有弹性的摹本。他叫var。使用起来类似于boost::ref,你可以直接把他当成占位符。然而,与boost::ref不同,你可以用它来组成更复杂的表达式。下面是一些例子:

    var(x) += 3
    var(x) = var(y) + var(z)
    var(x) = var(y) + (3 * var(z))
    var(x) = var(y)[var(i)] // assuming y is indexable and i is an index

Let's start with a simple example. We'll want to parse a comma separated list of numbers and report the sum of all the numbers. Using phoenix's var, we do not have to write external semantic actions. We simply inline the code inside the semantic action slots. Here's the complete grammar with our phoenix actions (see sum.cpp in the examples):

我们以简单的例子开始。比方我们想分析一个由逗号分隔的数字列表并报告所有数字之和。使用phoenix的var,我们不需要写额外的语义动作。而只需要简单地把代码内联在语义动作槽内。下面是完整的使用phoenix语义动作的语法(例子见sum.cpp):

    real_p[var(n) = arg1] >> *(',' >> real_p[var(n) += arg1]) 

The full source code can be viewed here. This is part of the Spirit distribution.

完整的源码在这里。这是Spirit发布包的组成部分。

argN

Notice the expression: var(n) = arg1 . What is arg1 and what is it doing there? arg1 is an argument placeholder. Remember that real_p (see Numerics) reports the parsed number to its attached semantic action. arg1 is a placeholder for the first argument passed to the semantic action by the parser. If there are more than one arguments passed in, these arguments can be referred to using arg1..argN. For instance, generic semantic actions (transduction interface; see Semantic Actions) are passed 2 arguments: the iterators (first/last) to the matching portion of the input stream. You can refer to first and last through arg1 and arg2, respectively.

注意表达式var(n) = arg1arg1是什么,干什么?arg1是一个参数的占位符。记得real_p(见数值)要把分析出的数值报告给挂接着它的语义动作。arg1就是对应由分析器传给语义动作的第一个参数的占位符。举例来说,一般的语义动作(转换接口;见语义动作)接受两个参数:对应输入流中匹配位置的一对迭代器(first/last)。你可以分别通过arg1arg2 访问first和last。

Like var, argN is also composable. Here are some examples:

类似于var,argN也是可复合的。这里是一些例子:

    var(x) += arg1
    var(x) = arg1 + var(z)
    var(x) = arg1 + (3 * arg2)
    var(x) = arg1[arg2] // assuming arg1 is indexable and arg2 is an index

val

Note the expression: 3 * arg2. This expression is actually a short-hand equivalent to: val(3) * arg2. We shall see later why, in some cases, we need to explicitly wrap constants and literals inside the val. Again, like var and argN, val is also composable.

注意表达式3 * arg2。这个表达式实际上是val(3) * arg2的简写。后面我们将见到为什么在某些情况下,我们需要显式地封装常量和变量于val中。再次,类似于var和argN,val也是可复合的。

Functions

函数

Remember our very first example? In the Quick Start chapter, we presented a parser that parses a comma separated list and stuffs the parsed numbers in a vector (see number_list.cpp) . For simplicity, we used Spirit's pre-defined actors (see Predefined Actors). In the example, we used push_back_a:

记得我们很早的例子么?在快速开始一章中,我们展示了一个分析逗号分隔的数值表并把数值推入一个vector的分析器(见 number_list.cpp)。简单起见,我们用了Spirit预定义的动作器。(见预定义动作器)。在那个例子中,我们使用了push_back_a:

real_p[push_back_a(v)] >> *(',' >> real_p[push_back_a(v)])

Phoenix allows you to write more powerful polymorphic functions, similar to push_back_a, easily. See stuff_vector.cpp. The example is similar to number_list.cpp in functionality, but this time, using phoenix a function to actually implement the push_back function:

Phoenix允许你写更强大的多态函数,类似于push_back_a,但更简单。见stuff_vector.cpp。下面的例子功能类似于number_list.cpp ,但这次,用phoenix写一个函数来实现push_back函数:

    struct push_back_impl
    {
        template <typename Container, typename Item>
        struct result
        {
            typedef void type;
        };

        template <typename Container, typename Item>
        void operator()(Container& c, Item const& item) const
        {
            c.push_back(item);
        }
    };

    function<push_back_impl> const push_back = push_back_impl();

The full source code can be viewed here. This is part of the Spirit distribution.

完整的源码参见这里。这是Spirit分发包的组成部分。

Predefined Phoenix Functions

预定义的Phoenix函数

A future version of Phoenix will include an extensive set of predefined functions covering the whole of STL containers, iterators and algorithms. push_back, will be part of this suite.

Phoenix将来的版本将包含一个巨大的预定义函数集,涵盖整个STL容器、迭代器和算法。push_back,将是这个套装的一部分。

push_back_impl is a simple wrapper over the push_back member function of STL containers. The extra scaffolding is there to provide phoenix with additional information that otherwise cannot be directly deduced. result relays to phoenix the return type of the functor (operator()) given its argument types (Container and Item) . In this case, the return type is always, simply void.

push_back_impl是一个针对STL容器的push_back成员函数的简单封装。其他的多出来的代码用来给Phoenix提供附加的信息,否则它将无法直接推演。result 把给定了参数(Container和Item)的仿函数(operator())的返回类型传递给Phoenix。在这个例子里,返回类型总是单纯的void。

push_back is a phoenix function object. This is the actual function object that we shall use. The beauty behind phoenix function objects is that the actual use is strikingly similar to a normal C++ function call. Here's the number list parser rewritten using our phoenix function object:

push_back是一个phoenix函数对象。这是我们将实际使用的函数对象。phoenix函数对象幕后的美丽在于它的实际调用非常类似于一个C++的普通函数。下面是用我们的phoenix函数对象重写的数值列表分析器:

    real_p[push_back(var(v), arg1)] >> *(',' >> real_p[push_back(var(v), arg1)])

And, unlike predefined actors, they can be composed. See the pattern? Here are some examples:

并且,与预定义动作器不同,他们可以复合。看到这个模式了么?这里是一些例子:

    push_back(var(v), arg1 + 2)
    push_back(var(v), var(x) + arg1)
    push_back(var(v)[arg1], arg2) // assuming v is a vector of vectors and arg1 is an index

push_back does not have a return type. Say, for example, we wrote another phoenix function sin, we can use it in expressions as well:

push_back并没有返回类型。假如,举例来说,我们写了另一个phoenix函数sin,我们也可以把它用于表达式中:

    push_back(var(v), sin(arg1) * 2)

Construct

构造

Sometimes, we wish to construct an object. For instance, we might want to create a std::string given the first/last iterators. For instance, say we want to parse a list of identifiers instead. Our grammar, without the actions, is:

有时,我们希望构造一个对象。例如,我们可能像用给定的first/last迭代器创建一个std::string对象。比如,假设我们想分析一个标识符列表。我们的语法,没有语义动作,是这样的:

    (+alpha_p) >> *(',' >> (+alpha_p))

construct_ is a predefined phoenix function that, you guessed it, constructs an object, from the arguments passed in. The usage is:

construct_是一个预定义的phoenix函数,正如你猜到的,构造一个对象,使用给定的参数。使用方法是:

    construct_<T>(arg1, arg2,... argN)

where T is the desired type and arg1..argN are the constructor arguments. For example, we can construct a std::string from the first/last iterator pair this way:

这里T是想构造的类型而arg1...argN是构造函数的参数。例如,我们可以这样从first/last迭代器对创建std::string:

    construct_<std::string>(arg1, arg2)

Now, we attach the actions to our grammar:

现在,我们把语义动作挂接到语法上:

    (+alpha_p)
    [
        push_back(var(v), construct_<std::string>(arg1, arg2))
    ]
    >>
    *(',' >>
        (+alpha_p)
        [
            push_back(var(v), construct_<std::string>(arg1, arg2))
        ]
    )

The full source code can be viewed here. This is part of the Spirit distribution.

完整的代码参见这里。这是Spirit分发包的组成部分。

Lambda expressions

Lambda表达式

All these phoenix expressions we see above are lambda expressions. The important thing to note is that these expressions are not evaluated immediately. At grammar construction time, when the actions are attached to the productions, a lambda expression actually generates an unnamed function object that is evaluated later, at parse time. In other words, lambda expressions are lazily evaluated.

所有这些我们在上面见到的Phoenix表达式都是所谓的lambda表达式。值得注意的是这些表达式并不是立即推算的。在grammar创建的时候,当语义动作挂接到所生成的对象上时,一个lambda表达式是推迟计算的的,只是在分析时才实际产生一个匿名函数对象。换言之,lambda表达式是懒惰推算的。

Lambda Expressions?

Lambda表达式?

Lambda expressions are actually unnamed partially applied functions where placeholders (e.g. arg1, arg2) are provided in place of some of the arguments. The reason this is called a lambda expression is that traditionally, such placeholders are written using the Greek letter lambda .

Lambda表达式实际上是匿名的部分特化函数,这里占位符(比如arg1,arg2)被提供来填补某些参数的位置。把这个叫做lambda表达式的原因是由于传统上,这样的占位符写成希腊文字母

Phoenix uses tricks not unlike those used by Spirit to mimic C++ such that you can define the function body inlined in the code. It's weird, but as mentioned, Phoenix actually mimicks C++ in C++ using expression templates. Surely, there are limitations...

Phoenix使用类似于Spirit里用的技巧来模仿C++使得你可以在代码中内联地定义函数体。这很奇怪,但正如所提到的,Phoenix实际上是用表达式模板来在C++里模仿C++。当然,这也是有限制的……

All components in a Phoenix expression must be an actor (in phoenix parlance) in the same way that components in Spirit should be a parser. In Spirit, you can write:

Phoenix表达式里所有的元件都必须是动作器(以phoenix的行话)而Spirit里这些元件必须是Spirit的分析器。在Spirit中,我们可以写:

    r = ch_p('x') >> 'y';

But not:

但不能:

    r = 'x' >> 'y';

In essence, parser >> char is a parser, but char >> char is a char (the char shift-right by another char).

本质上,parser>>char是一个分析器,但char>>char是一个char(由一个char右移位而来的另一个char)。

The same restrictions apply to Phoenix. For instance:

Phoenix中同样存在这样的限制。例如:

    int x = 1;
    cout << var(x) << "pizza"

is a well formed Phoenix expression that's lazily evaluated. But:

是一个形态良好、懒惰推算的Phoenix表达式。但:

    cout << x << "pizza"

is not. Such expressions are immediately executed. C++ syntax dictates that at least one of the operands must be a Phoenix actor type. This also applies to compound expressions. For example:

不是。这样的表达式是立即计算的。C++句法要求只要有一个算子是Phoenix动作器类。这对复合表达式同样成立。比如:

    cout << var(x) << "pizza" << "man"

This is evaluated as:

这个被推算为:

    (((cout << var(x)) << "pizza") << "man")

Since (cout << var(x)) is an actor, at least one of the operands is a phoenix actor, ((cout << var(x)) << "pizza") is also a Phoenix actor, and the whole expression is thus also an actor.

由于(cout << var(x))是一个动作器,至少一个算子是phoenix动作器, ((cout << var(x)) << "pizza") 也是一个Phoenix动作器,而整个表达式也因此是一个动作器。

Sometimes, it is safe to write:

有时,这么写是安全的:

    cout << var(x) << val("pizza") << val("man")

just to make it explicitly clear what we are dealing with, especially with complex expressions, in the same way as we explicitly wrap literal strings in str_p("lit") in Spirit.

只要用与Spirit里显式地用str_p("lit")封装字符串相同的方法显式地表明我们在和什么打交道,在复杂表达式中这点尤为重要。

Phoenix (and Spirit) also deals with unary operators. In such cases, we have no choice. The operand must be a Phoenix actor (or Spirit parser). Examples:

Phoenix(以及Spirit)也处理一元操作符。在这样的情况下,我们没选择。算子必须是Phoenix动作器(或者Spirit分析器)。例子:

Spirit:

    *ch_p('z')  // good
    *('z') // bad

Phoenix:

    *var(x) // good (lazy)
    *x // bad (immediate)

Also, in Phoenix, for assignments and indexing to be lazily evaluated, the object acted upon should be a Phoenix actor. Examples:

同样,在Phoenix里,要使等号和下标被懒惰推算,其上的动作对象必须是Phoenix动作器。例子:

    var(x) = 123 // good (lazy)
    x = 123 // bad (immediate)
    var(x)[0] // good (lazy)
    x[0] // bad, immediate
    var(x)[var(i)] // good (lazy)
    x[var(i)] // bad and illegal (x is not an actor)
    var(x[var(i)]) // bad and illegal (x is not an actor)

Wrapping up

封装

Well, there you have it. I hope with this jump-start chapter, you may be able to harness the power of lambda expressions. By all means, please read the phoenix manual to learn more about the nitty gritty details. Surely, you'll get to know a lot more than just by reading this chapter. There are a lot of things still to be touched. There won't be enough space here to cover all the features of Phoenix even in brief.

好了,就到这儿了。我希望这个助动章节能够让你驾驭lambda表达式的力量。请一定要阅读phoneix手册以了解更多的实质细节。当然,你将获得比阅读这章更多的知识。还有很多事情要了解。这里没有足够的空间来涵盖即使是简明的Phoenix功能介绍。

The next chapter, Closures, we'll see more of phoenix. Stay tuned.

下一章,闭包,我们将看到更多的phoenix,请不要走开,我们马上回来。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值