| /************************************************************** |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * |
| *************************************************************/ |
| |
| |
| |
| // MARKER(update_precomp.py): autogen include statement, do not remove |
| #include "precompiled_slideshow.hxx" |
| |
| // must be first |
| #include <canvas/debug.hxx> |
| #include <tools/diagnose_ex.h> |
| |
| #include <rtl/math.hxx> |
| |
| #include <smilfunctionparser.hxx> |
| #include <expressionnodefactory.hxx> |
| |
| #include <rtl/ustring.hxx> |
| #include <canvas/verbosetrace.hxx> |
| |
| #include <basegfx/matrix/b2dhommatrix.hxx> |
| #include <basegfx/point/b2dpoint.hxx> |
| |
| // Makes parser a static resource, |
| // we're synchronized externally. |
| // But watch out, the parser might have |
| // state not visible to this code! |
| #define BOOST_SPIRIT_SINGLE_GRAMMAR_INSTANCE |
| #if defined(VERBOSE) && defined(DBG_UTIL) |
| #include <typeinfo> |
| #define BOOST_SPIRIT_DEBUG |
| #endif |
| #include <boost/spirit/include/classic_core.hpp> |
| |
| #if OSL_DEBUG_LEVEL > 0 |
| #include <iostream> |
| #endif |
| #include <functional> |
| #include <algorithm> |
| #include <stack> |
| |
| |
| |
| /* Implementation of SmilFunctionParser class */ |
| |
| namespace slideshow |
| { |
| namespace internal |
| { |
| namespace |
| { |
| typedef const sal_Char* StringIteratorT; |
| |
| struct ParserContext |
| { |
| typedef ::std::stack< ExpressionNodeSharedPtr > OperandStack; |
| |
| // stores a stack of not-yet-evaluated operands. This is used |
| // by the operators (i.e. '+', '*', 'sin' etc.) to pop their |
| // arguments from. If all arguments to an operator are constant, |
| // the operator pushes a precalculated result on the stack, and |
| // a composite ExpressionNode otherwise. |
| OperandStack maOperandStack; |
| |
| // bounds of the shape this expression is associated with |
| ::basegfx::B2DRectangle maShapeBounds; |
| |
| // when true, enable usage of time-dependent variable '$' |
| // in expressions |
| bool mbParseAnimationFunction; |
| }; |
| |
| typedef ::boost::shared_ptr< ParserContext > ParserContextSharedPtr; |
| |
| |
| template< typename Generator > class ShapeBoundsFunctor |
| { |
| public: |
| ShapeBoundsFunctor( Generator aGenerator, |
| const ParserContextSharedPtr& rContext ) : |
| maGenerator( aGenerator ), |
| mpContext( rContext ) |
| { |
| ENSURE_OR_THROW( mpContext, |
| "ShapeBoundsFunctor::ShapeBoundsFunctor(): Invalid context" ); |
| } |
| |
| void operator()( StringIteratorT, StringIteratorT ) const |
| { |
| mpContext->maOperandStack.push( |
| ExpressionNodeFactory::createConstantValueExpression( |
| maGenerator( mpContext->maShapeBounds ) ) ); |
| } |
| |
| private: |
| Generator maGenerator; |
| ParserContextSharedPtr mpContext; |
| }; |
| |
| template< typename Generator > ShapeBoundsFunctor< Generator > |
| makeShapeBoundsFunctor( const Generator& rGenerator, |
| const ParserContextSharedPtr& rContext ) |
| { |
| return ShapeBoundsFunctor<Generator>(rGenerator, rContext); |
| } |
| |
| /** Generate apriori constant value |
| */ |
| class ConstantFunctor |
| { |
| public: |
| ConstantFunctor( double rValue, |
| const ParserContextSharedPtr& rContext ) : |
| mnValue( rValue ), |
| mpContext( rContext ) |
| { |
| ENSURE_OR_THROW( mpContext, |
| "ConstantFunctor::ConstantFunctor(): Invalid context" ); |
| } |
| |
| void operator()( StringIteratorT, StringIteratorT ) const |
| { |
| mpContext->maOperandStack.push( |
| ExpressionNodeFactory::createConstantValueExpression( mnValue ) ); |
| } |
| |
| private: |
| const double mnValue; |
| ParserContextSharedPtr mpContext; |
| }; |
| |
| /** Generate parse-dependent-but-then-constant value |
| */ |
| class DoubleConstantFunctor |
| { |
| public: |
| DoubleConstantFunctor( const ParserContextSharedPtr& rContext ) : |
| mpContext( rContext ) |
| { |
| ENSURE_OR_THROW( mpContext, |
| "DoubleConstantFunctor::DoubleConstantFunctor(): Invalid context" ); |
| } |
| |
| void operator()( double n ) const |
| { |
| // push constant value expression to the stack |
| mpContext->maOperandStack.push( |
| ExpressionNodeFactory::createConstantValueExpression( n ) ); |
| } |
| |
| private: |
| ParserContextSharedPtr mpContext; |
| }; |
| |
| /** Generate special t value expression node |
| */ |
| class ValueTFunctor |
| { |
| public: |
| ValueTFunctor( const ParserContextSharedPtr& rContext ) : |
| mpContext( rContext ) |
| { |
| ENSURE_OR_THROW( mpContext, |
| "ValueTFunctor::ValueTFunctor(): Invalid context" ); |
| } |
| |
| void operator()( StringIteratorT, StringIteratorT ) const |
| { |
| if( !mpContext->mbParseAnimationFunction ) |
| { |
| OSL_ENSURE( false, |
| "ValueTFunctor::operator(): variable encountered, but we're not parsing a function here" ); |
| throw ParseError(); |
| } |
| |
| // push special t value expression to the stack |
| mpContext->maOperandStack.push( |
| ExpressionNodeFactory::createValueTExpression() ); |
| } |
| |
| private: |
| ParserContextSharedPtr mpContext; |
| }; |
| |
| template< typename Functor > class UnaryFunctionFunctor |
| { |
| private: |
| /** ExpressionNode implementation for unary |
| function over one ExpressionNode |
| */ |
| class UnaryFunctionExpression : public ExpressionNode |
| { |
| public: |
| UnaryFunctionExpression( const Functor& rFunctor, |
| const ExpressionNodeSharedPtr& rArg ) : |
| maFunctor( rFunctor ), |
| mpArg( rArg ) |
| { |
| } |
| |
| virtual double operator()( double t ) const |
| { |
| return maFunctor( (*mpArg)(t) ); |
| } |
| |
| virtual bool isConstant() const |
| { |
| return mpArg->isConstant(); |
| } |
| |
| private: |
| Functor maFunctor; |
| ExpressionNodeSharedPtr mpArg; |
| }; |
| |
| public: |
| UnaryFunctionFunctor( const Functor& rFunctor, |
| const ParserContextSharedPtr& rContext ) : |
| maFunctor( rFunctor ), |
| mpContext( rContext ) |
| { |
| ENSURE_OR_THROW( mpContext, |
| "UnaryFunctionFunctor::UnaryFunctionFunctor(): Invalid context" ); |
| } |
| |
| void operator()( StringIteratorT, StringIteratorT ) const |
| { |
| ParserContext::OperandStack& rNodeStack( mpContext->maOperandStack ); |
| |
| if( rNodeStack.size() < 1 ) |
| throw ParseError( "Not enough arguments for unary operator" ); |
| |
| // retrieve arguments |
| ExpressionNodeSharedPtr pArg( rNodeStack.top() ); |
| rNodeStack.pop(); |
| |
| // check for constness |
| if( pArg->isConstant() ) |
| { |
| rNodeStack.push( |
| ExpressionNodeFactory::createConstantValueExpression( |
| maFunctor( (*pArg)(0.0) ) ) ); |
| } |
| else |
| { |
| // push complex node, that calcs the value on demand |
| rNodeStack.push( |
| ExpressionNodeSharedPtr( |
| new UnaryFunctionExpression( |
| maFunctor, |
| pArg ) ) ); |
| } |
| } |
| |
| private: |
| Functor maFunctor; |
| ParserContextSharedPtr mpContext; |
| }; |
| |
| // TODO(Q2): Refactor makeUnaryFunctionFunctor, |
| // makeBinaryFunctionFunctor and the whole |
| // ExpressionNodeFactory, to use a generic |
| // makeFunctionFunctor template, which is overloaded for |
| // unary, binary, ternary, etc. function pointers. |
| template< typename Functor > UnaryFunctionFunctor<Functor> |
| makeUnaryFunctionFunctor( const Functor& rFunctor, |
| const ParserContextSharedPtr& rContext ) |
| { |
| return UnaryFunctionFunctor<Functor>( rFunctor, rContext ); |
| } |
| |
| // MSVC has problems instantiating above template function with plain function |
| // pointers (doesn't like the const reference there). Thus, provide it with |
| // a dedicated overload here. |
| UnaryFunctionFunctor< double (*)(double) > |
| makeUnaryFunctionFunctor( double (*pFunc)(double), |
| const ParserContextSharedPtr& rContext ) |
| { |
| return UnaryFunctionFunctor< double (*)(double) >( pFunc, rContext ); |
| } |
| |
| /** Implements a binary function over two ExpressionNodes |
| |
| @tpl Generator |
| Generator functor, to generate an ExpressionNode of |
| appropriate type |
| |
| */ |
| template< class Generator > class BinaryFunctionFunctor |
| { |
| public: |
| BinaryFunctionFunctor( const Generator& rGenerator, |
| const ParserContextSharedPtr& rContext ) : |
| maGenerator( rGenerator ), |
| mpContext( rContext ) |
| { |
| ENSURE_OR_THROW( mpContext, |
| "BinaryFunctionFunctor::BinaryFunctionFunctor(): Invalid context" ); |
| } |
| |
| void operator()( StringIteratorT, StringIteratorT ) const |
| { |
| ParserContext::OperandStack& rNodeStack( mpContext->maOperandStack ); |
| |
| if( rNodeStack.size() < 2 ) |
| throw ParseError( "Not enough arguments for binary operator" ); |
| |
| // retrieve arguments |
| ExpressionNodeSharedPtr pSecondArg( rNodeStack.top() ); |
| rNodeStack.pop(); |
| ExpressionNodeSharedPtr pFirstArg( rNodeStack.top() ); |
| rNodeStack.pop(); |
| |
| // create combined ExpressionNode |
| ExpressionNodeSharedPtr pNode( maGenerator( pFirstArg, |
| pSecondArg ) ); |
| // check for constness |
| if( pFirstArg->isConstant() && |
| pSecondArg->isConstant() ) |
| { |
| // call the operator() at pNode, store result |
| // in constant value ExpressionNode. |
| rNodeStack.push( |
| ExpressionNodeFactory::createConstantValueExpression( |
| (*pNode)( 0.0 ) ) ); |
| } |
| else |
| { |
| // push complex node, that calcs the value on demand |
| rNodeStack.push( pNode ); |
| } |
| } |
| |
| private: |
| Generator maGenerator; |
| ParserContextSharedPtr mpContext; |
| }; |
| |
| template< typename Generator > BinaryFunctionFunctor<Generator> |
| makeBinaryFunctionFunctor( const Generator& rGenerator, |
| const ParserContextSharedPtr& rContext ) |
| { |
| return BinaryFunctionFunctor<Generator>( rGenerator, rContext ); |
| } |
| |
| |
| // Workaround for MSVC compiler anomaly (stack trashing) |
| // |
| // The default ureal_parser_policies implementation of parse_exp |
| // triggers a really weird error in MSVC7 (Version 13.00.9466), in |
| // that the real_parser_impl::parse_main() call of parse_exp() |
| // overwrites the frame pointer _on the stack_ (EBP of the calling |
| // function gets overwritten while lying on the stack). |
| // |
| // For the time being, our parser thus can only read the 1.0E10 |
| // notation, not the 1.0e10 one. |
| // |
| // TODO(F1): Also handle the 1.0e10 case here. |
| template< typename T > struct custom_real_parser_policies : public ::boost::spirit::ureal_parser_policies<T> |
| { |
| template< typename ScannerT > |
| static typename ::boost::spirit::parser_result< ::boost::spirit::chlit<>, ScannerT >::type |
| parse_exp(ScannerT& scan) |
| { |
| // as_lower_d somehow breaks MSVC7 |
| return ::boost::spirit::ch_p('E').parse(scan); |
| } |
| }; |
| |
| /* This class implements the following grammar (more or |
| less literally written down below, only slightly |
| obfuscated by the parser actions): |
| |
| identifier = '$'|'pi'|'e'|'X'|'Y'|'Width'|'Height' |
| |
| function = 'abs'|'sqrt'|'sin'|'cos'|'tan'|'atan'|'acos'|'asin'|'exp'|'log' |
| |
| basic_expression = |
| number | |
| identifier | |
| function '(' additive_expression ')' | |
| '(' additive_expression ')' |
| |
| unary_expression = |
| '-' basic_expression | |
| basic_expression |
| |
| multiplicative_expression = |
| unary_expression ( ( '*' unary_expression )* | |
| ( '/' unary_expression )* ) |
| |
| additive_expression = |
| multiplicative_expression ( ( '+' multiplicative_expression )* | |
| ( '-' multiplicative_expression )* ) |
| |
| */ |
| class ExpressionGrammar : public ::boost::spirit::grammar< ExpressionGrammar > |
| { |
| public: |
| /** Create an arithmetic expression grammar |
| |
| @param rParserContext |
| Contains context info for the parser |
| */ |
| ExpressionGrammar( const ParserContextSharedPtr& rParserContext ) : |
| mpParserContext( rParserContext ) |
| { |
| } |
| |
| template< typename ScannerT > class definition |
| { |
| public: |
| // grammar definition |
| definition( const ExpressionGrammar& self ) |
| { |
| using ::boost::spirit::str_p; |
| using ::boost::spirit::real_parser; |
| |
| identifier = |
| str_p( "$" )[ ValueTFunctor( self.getContext()) ] |
| | str_p( "pi" )[ ConstantFunctor(M_PI, self.getContext()) ] |
| | str_p( "e" )[ ConstantFunctor(M_E, self.getContext()) ] |
| | str_p( "x" )[ makeShapeBoundsFunctor(::std::mem_fun_ref(&::basegfx::B2DRange::getCenterX),self.getContext()) ] |
| | str_p( "y" )[ makeShapeBoundsFunctor(::std::mem_fun_ref(&::basegfx::B2DRange::getCenterY),self.getContext()) ] |
| | str_p( "width" )[ makeShapeBoundsFunctor(::std::mem_fun_ref(&::basegfx::B2DRange::getWidth), self.getContext()) ] |
| | str_p( "height" )[ makeShapeBoundsFunctor(::std::mem_fun_ref(&::basegfx::B2DRange::getHeight), self.getContext()) ] |
| ; |
| |
| unaryFunction = |
| (str_p( "abs" ) >> '(' >> additiveExpression >> ')' )[ makeUnaryFunctionFunctor(&fabs, self.getContext()) ] |
| | (str_p( "sqrt" ) >> '(' >> additiveExpression >> ')' )[ makeUnaryFunctionFunctor(&sqrt, self.getContext()) ] |
| | (str_p( "sin" ) >> '(' >> additiveExpression >> ')' )[ makeUnaryFunctionFunctor(&sin, self.getContext()) ] |
| | (str_p( "cos" ) >> '(' >> additiveExpression >> ')' )[ makeUnaryFunctionFunctor(&cos, self.getContext()) ] |
| | (str_p( "tan" ) >> '(' >> additiveExpression >> ')' )[ makeUnaryFunctionFunctor(&tan, self.getContext()) ] |
| | (str_p( "atan" ) >> '(' >> additiveExpression >> ')' )[ makeUnaryFunctionFunctor(&atan, self.getContext()) ] |
| | (str_p( "acos" ) >> '(' >> additiveExpression >> ')' )[ makeUnaryFunctionFunctor(&acos, self.getContext()) ] |
| | (str_p( "asin" ) >> '(' >> additiveExpression >> ')' )[ makeUnaryFunctionFunctor(&asin, self.getContext()) ] |
| | (str_p( "exp" ) >> '(' >> additiveExpression >> ')' )[ makeUnaryFunctionFunctor(&exp, self.getContext()) ] |
| | (str_p( "log" ) >> '(' >> additiveExpression >> ')' )[ makeUnaryFunctionFunctor(&log, self.getContext()) ] |
| ; |
| |
| binaryFunction = |
| (str_p( "min" ) >> '(' >> additiveExpression >> ',' >> additiveExpression >> ')' )[ makeBinaryFunctionFunctor(&ExpressionNodeFactory::createMinExpression, self.getContext()) ] |
| | (str_p( "max" ) >> '(' >> additiveExpression >> ',' >> additiveExpression >> ')' )[ makeBinaryFunctionFunctor(&ExpressionNodeFactory::createMaxExpression, self.getContext()) ] |
| ; |
| |
| basicExpression = |
| real_parser<double, custom_real_parser_policies<double> >()[ DoubleConstantFunctor(self.getContext()) ] |
| | identifier |
| | unaryFunction |
| | binaryFunction |
| | '(' >> additiveExpression >> ')' |
| ; |
| |
| unaryExpression = |
| ('-' >> basicExpression)[ makeUnaryFunctionFunctor(::std::negate<double>(), self.getContext()) ] |
| | basicExpression |
| ; |
| |
| multiplicativeExpression = |
| unaryExpression |
| >> *( ('*' >> unaryExpression)[ makeBinaryFunctionFunctor(&ExpressionNodeFactory::createMultipliesExpression, self.getContext()) ] |
| | ('/' >> unaryExpression)[ makeBinaryFunctionFunctor(&ExpressionNodeFactory::createDividesExpression, self.getContext()) ] |
| ) |
| ; |
| |
| additiveExpression = |
| multiplicativeExpression |
| >> *( ('+' >> multiplicativeExpression)[ makeBinaryFunctionFunctor(&ExpressionNodeFactory::createPlusExpression, self.getContext()) ] |
| | ('-' >> multiplicativeExpression)[ makeBinaryFunctionFunctor(&ExpressionNodeFactory::createMinusExpression, self.getContext()) ] |
| ) |
| ; |
| |
| BOOST_SPIRIT_DEBUG_RULE(additiveExpression); |
| BOOST_SPIRIT_DEBUG_RULE(multiplicativeExpression); |
| BOOST_SPIRIT_DEBUG_RULE(unaryExpression); |
| BOOST_SPIRIT_DEBUG_RULE(basicExpression); |
| BOOST_SPIRIT_DEBUG_RULE(unaryFunction); |
| BOOST_SPIRIT_DEBUG_RULE(binaryFunction); |
| BOOST_SPIRIT_DEBUG_RULE(identifier); |
| } |
| |
| const ::boost::spirit::rule< ScannerT >& start() const |
| { |
| return additiveExpression; |
| } |
| |
| private: |
| // the constituents of the Spirit arithmetic expression grammar. |
| // For the sake of readability, without 'ma' prefix. |
| ::boost::spirit::rule< ScannerT > additiveExpression; |
| ::boost::spirit::rule< ScannerT > multiplicativeExpression; |
| ::boost::spirit::rule< ScannerT > unaryExpression; |
| ::boost::spirit::rule< ScannerT > basicExpression; |
| ::boost::spirit::rule< ScannerT > unaryFunction; |
| ::boost::spirit::rule< ScannerT > binaryFunction; |
| ::boost::spirit::rule< ScannerT > identifier; |
| }; |
| |
| const ParserContextSharedPtr& getContext() const |
| { |
| return mpParserContext; |
| } |
| |
| private: |
| ParserContextSharedPtr mpParserContext; // might get modified during parsing |
| }; |
| |
| #ifdef BOOST_SPIRIT_SINGLE_GRAMMAR_INSTANCE |
| const ParserContextSharedPtr& getParserContext() |
| { |
| static ParserContextSharedPtr lcl_parserContext( new ParserContext() ); |
| |
| // clear node stack (since we reuse the static object, that's |
| // the whole point here) |
| while( !lcl_parserContext->maOperandStack.empty() ) |
| lcl_parserContext->maOperandStack.pop(); |
| |
| return lcl_parserContext; |
| } |
| #endif |
| } |
| |
| ExpressionNodeSharedPtr SmilFunctionParser::parseSmilValue( const ::rtl::OUString& rSmilValue, |
| const ::basegfx::B2DRectangle& rRelativeShapeBounds ) |
| { |
| // TODO(Q1): Check if a combination of the RTL_UNICODETOTEXT_FLAGS_* |
| // gives better conversion robustness here (we might want to map space |
| // etc. to ASCII space here) |
| const ::rtl::OString& rAsciiSmilValue( |
| rtl::OUStringToOString( rSmilValue, RTL_TEXTENCODING_ASCII_US ) ); |
| |
| StringIteratorT aStart( rAsciiSmilValue.getStr() ); |
| StringIteratorT aEnd( rAsciiSmilValue.getStr()+rAsciiSmilValue.getLength() ); |
| |
| ParserContextSharedPtr pContext; |
| |
| #ifdef BOOST_SPIRIT_SINGLE_GRAMMAR_INSTANCE |
| // static parser context, because the actual |
| // Spirit parser is also a static object |
| pContext = getParserContext(); |
| #else |
| pContext.reset( new ParserContext() ); |
| #endif |
| |
| pContext->maShapeBounds = rRelativeShapeBounds; |
| pContext->mbParseAnimationFunction = false; // parse with '$' disabled |
| |
| |
| ExpressionGrammar aExpressionGrammer( pContext ); |
| const ::boost::spirit::parse_info<StringIteratorT> aParseInfo( |
| ::boost::spirit::parse( aStart, |
| aEnd, |
| aExpressionGrammer, |
| ::boost::spirit::space_p ) ); |
| OSL_DEBUG_ONLY(::std::cout.flush()); // needed to keep stdout and cout in sync |
| |
| // input fully congested by the parser? |
| if( !aParseInfo.full ) |
| throw ParseError( "SmilFunctionParser::parseSmilValue(): string not fully parseable" ); |
| |
| // parser's state stack now must contain exactly _one_ ExpressionNode, |
| // which represents our formula. |
| if( pContext->maOperandStack.size() != 1 ) |
| throw ParseError( "SmilFunctionParser::parseSmilValue(): incomplete or empty expression" ); |
| |
| return pContext->maOperandStack.top(); |
| } |
| |
| ExpressionNodeSharedPtr SmilFunctionParser::parseSmilFunction( const ::rtl::OUString& rSmilFunction, |
| const ::basegfx::B2DRectangle& rRelativeShapeBounds ) |
| { |
| // TODO(Q1): Check if a combination of the RTL_UNICODETOTEXT_FLAGS_* |
| // gives better conversion robustness here (we might want to map space |
| // etc. to ASCII space here) |
| const ::rtl::OString& rAsciiSmilFunction( |
| rtl::OUStringToOString( rSmilFunction, RTL_TEXTENCODING_ASCII_US ) ); |
| |
| StringIteratorT aStart( rAsciiSmilFunction.getStr() ); |
| StringIteratorT aEnd( rAsciiSmilFunction.getStr()+rAsciiSmilFunction.getLength() ); |
| |
| ParserContextSharedPtr pContext; |
| |
| #ifdef BOOST_SPIRIT_SINGLE_GRAMMAR_INSTANCE |
| // static parser context, because the actual |
| // Spirit parser is also a static object |
| pContext = getParserContext(); |
| #else |
| pContext.reset( new ParserContext() ); |
| #endif |
| |
| pContext->maShapeBounds = rRelativeShapeBounds; |
| pContext->mbParseAnimationFunction = true; // parse with '$' enabled |
| |
| |
| ExpressionGrammar aExpressionGrammer( pContext ); |
| const ::boost::spirit::parse_info<StringIteratorT> aParseInfo( |
| ::boost::spirit::parse( aStart, |
| aEnd, |
| aExpressionGrammer >> ::boost::spirit::end_p, |
| ::boost::spirit::space_p ) ); |
| OSL_DEBUG_ONLY(::std::cout.flush()); // needed to keep stdout and cout in sync |
| |
| // input fully congested by the parser? |
| if( !aParseInfo.full ) |
| throw ParseError( "SmilFunctionParser::parseSmilFunction(): string not fully parseable" ); |
| |
| // parser's state stack now must contain exactly _one_ ExpressionNode, |
| // which represents our formula. |
| if( pContext->maOperandStack.size() != 1 ) |
| throw ParseError( "SmilFunctionParser::parseSmilFunction(): incomplete or empty expression" ); |
| |
| return pContext->maOperandStack.top(); |
| } |
| } |
| } |