问题
I'm using Boost Spirit to implement functionality in some software that allows the user to enter a mathematical equation that will be repeatedly applied to an input stream. Input stream values are represented as symbols using boost::spirit::qi::symbols
which the user can reference in their equation. (e.g. out1 = 3 * in1 + in2
)
Parsing and compiling the user's equation is not performance sensitive but calculating its output value is as it forms part of a time-critical pipeline.
The standard way Spirit is used within the documentation is to calculate the output (attribute) of an input as it is parsed. However, as between each calculation only the attribute values of the symbols (out1
, in1
, etc.) will have changed, it feels like there might be a more efficient way to achieve this, perhaps by caching the abstract syntax tree of the expression and reiterating through it.
What is the most efficient way to recalculate the value of this (fixed) equation given a new set of symbol values?
回答1:
The standard way Spirit is used is not as limited as you seem to think.
While you can use it to calulate an immediate value on the fly, it is much more usual to build an AST tree in the output attribute, that can be transformed (simplified, optimized) and interpreted (e.g. emitting virtual machine or even assembly instructions).
The compiler tutorials show this in full fledged, but the calculator samples are very close to what you seem to be looking for: http://www.boost.org/doc/libs/1_55_0/libs/spirit/example/qi/compiler_tutorial/
calc1 in example/qi/compiler_tutorial/calc1.cpp
Plain calculator example demonstrating the grammar. The parser is a syntax checker only and does not do any semantic evaluation.
calc2 in example/qi/compiler_tutorial/calc2.cpp
A Calculator example demonstrating the grammar and semantic actions using plain functions. The parser prints code suitable for a stack based virtual machine.
calc3 in example/qi/compiler_tutorial/calc3.cpp
A calculator example demonstrating the grammar and semantic actions using phoenix to do the actual expression evaluation. The parser is essentially an "interpreter" that evaluates expressions on the fly.
Here's where it gets interesting for you, since it stops doing the calculations during parsing:
calc4 in example/qi/compiler_tutorial/calc4.cpp
A Calculator example demonstrating generation of AST. The AST, once created, is traversed,
- To print its contents and
- To evaluate the result.
calc5 in example/qi/compiler_tutorial/calc5.cpp
Same as Calc4, this time, we'll incorporate debugging support, plus error handling and reporting.
calc6 in example/qi/compiler_tutorial/calc6.cpp
Yet another calculator example! This time, we will compile to a simple virtual machine. This is actually one of the very first Spirit example circa 2000. Now, it's ported to Spirit2.
calc7 in example/qi/compiler_tutorial/calc7/main.cpp
Now we'll introduce variables and assignment. This time, we'll also be renaming some of the rules -- a strategy for a grander scheme to come ;-)
This version also shows off grammar modularization. Here you will see how expressions and statements are built as modular grammars.
calc8 in example/qi/compiler_tutorial/calc8/main.cpp
Now we'll introduce boolean expressions and control structures. Is it obvious now what we are up to? ;-)
I'm sure you'll find lots of inspiration towards the end of the tutorial!
回答2:
You can build your own tree of calculator objects which mirrors the AST. So for your example out1 = 3 * in1 + in2
the AST is:
*
3
+
in1
in2
So you'd build an object hierarchy like this:
Multiplier(
Constant(3),
Adder(
Variable(&in1),
Variable(&in2)
)
)
With classes something like:
class Result {
virtual double value() = 0;
};
class Multiplier : public Result {
Multiplier(Result* lhs, Result* rhs);
double value() { return _lhs->value() * _rhs->value(); }
}
来源:https://stackoverflow.com/questions/23912607/what-is-the-most-efficient-way-to-recalculate-attributes-of-a-boost-spirit-parse