1 /************************************************************************* 2 * 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * Copyright 2000, 2010 Oracle and/or its affiliates. 6 * 7 * OpenOffice.org - a multi-platform office productivity suite 8 * 9 * This file is part of OpenOffice.org. 10 * 11 * OpenOffice.org is free software: you can redistribute it and/or modify 12 * it under the terms of the GNU Lesser General Public License version 3 13 * only, as published by the Free Software Foundation. 14 * 15 * OpenOffice.org is distributed in the hope that it will be useful, 16 * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 * GNU Lesser General Public License version 3 for more details 19 * (a copy is included in the LICENSE file that accompanied this code). 20 * 21 * You should have received a copy of the GNU Lesser General Public License 22 * version 3 along with OpenOffice.org. If not, see 23 * <http://www.openoffice.org/license.html> 24 * for a copy of the LGPLv3 License. 25 * 26 ************************************************************************/ 27 28 // MARKER(update_precomp.py): autogen include statement, do not remove 29 #include "precompiled_slideshow.hxx" 30 31 // must be first 32 #include <canvas/debug.hxx> 33 #include <tools/diagnose_ex.h> 34 35 #include <rtl/math.hxx> 36 37 #include <smilfunctionparser.hxx> 38 #include <expressionnodefactory.hxx> 39 40 #include <rtl/ustring.hxx> 41 #include <canvas/verbosetrace.hxx> 42 43 #include <basegfx/matrix/b2dhommatrix.hxx> 44 #include <basegfx/point/b2dpoint.hxx> 45 46 // Makes parser a static resource, 47 // we're synchronized externally. 48 // But watch out, the parser might have 49 // state not visible to this code! 50 #define BOOST_SPIRIT_SINGLE_GRAMMAR_INSTANCE 51 #if defined(VERBOSE) && defined(DBG_UTIL) 52 #include <typeinfo> 53 #define BOOST_SPIRIT_DEBUG 54 #endif 55 #include <boost/spirit/include/classic_core.hpp> 56 57 #if OSL_DEBUG_LEVEL > 0 58 #include <iostream> 59 #endif 60 #include <functional> 61 #include <algorithm> 62 #include <stack> 63 64 65 66 /* Implementation of SmilFunctionParser class */ 67 68 namespace slideshow 69 { 70 namespace internal 71 { 72 namespace 73 { 74 typedef const sal_Char* StringIteratorT; 75 76 struct ParserContext 77 { 78 typedef ::std::stack< ExpressionNodeSharedPtr > OperandStack; 79 80 // stores a stack of not-yet-evaluated operands. This is used 81 // by the operators (i.e. '+', '*', 'sin' etc.) to pop their 82 // arguments from. If all arguments to an operator are constant, 83 // the operator pushes a precalculated result on the stack, and 84 // a composite ExpressionNode otherwise. 85 OperandStack maOperandStack; 86 87 // bounds of the shape this expression is associated with 88 ::basegfx::B2DRectangle maShapeBounds; 89 90 // when true, enable usage of time-dependent variable '$' 91 // in expressions 92 bool mbParseAnimationFunction; 93 }; 94 95 typedef ::boost::shared_ptr< ParserContext > ParserContextSharedPtr; 96 97 98 template< typename Generator > class ShapeBoundsFunctor 99 { 100 public: 101 ShapeBoundsFunctor( Generator aGenerator, 102 const ParserContextSharedPtr& rContext ) : 103 maGenerator( aGenerator ), 104 mpContext( rContext ) 105 { 106 ENSURE_OR_THROW( mpContext, 107 "ShapeBoundsFunctor::ShapeBoundsFunctor(): Invalid context" ); 108 } 109 110 void operator()( StringIteratorT, StringIteratorT ) const 111 { 112 mpContext->maOperandStack.push( 113 ExpressionNodeFactory::createConstantValueExpression( 114 maGenerator( mpContext->maShapeBounds ) ) ); 115 } 116 117 private: 118 Generator maGenerator; 119 ParserContextSharedPtr mpContext; 120 }; 121 122 template< typename Generator > ShapeBoundsFunctor< Generator > 123 makeShapeBoundsFunctor( const Generator& rGenerator, 124 const ParserContextSharedPtr& rContext ) 125 { 126 return ShapeBoundsFunctor<Generator>(rGenerator, rContext); 127 } 128 129 /** Generate apriori constant value 130 */ 131 class ConstantFunctor 132 { 133 public: 134 ConstantFunctor( double rValue, 135 const ParserContextSharedPtr& rContext ) : 136 mnValue( rValue ), 137 mpContext( rContext ) 138 { 139 ENSURE_OR_THROW( mpContext, 140 "ConstantFunctor::ConstantFunctor(): Invalid context" ); 141 } 142 143 void operator()( StringIteratorT, StringIteratorT ) const 144 { 145 mpContext->maOperandStack.push( 146 ExpressionNodeFactory::createConstantValueExpression( mnValue ) ); 147 } 148 149 private: 150 const double mnValue; 151 ParserContextSharedPtr mpContext; 152 }; 153 154 /** Generate parse-dependent-but-then-constant value 155 */ 156 class DoubleConstantFunctor 157 { 158 public: 159 DoubleConstantFunctor( const ParserContextSharedPtr& rContext ) : 160 mpContext( rContext ) 161 { 162 ENSURE_OR_THROW( mpContext, 163 "DoubleConstantFunctor::DoubleConstantFunctor(): Invalid context" ); 164 } 165 166 void operator()( double n ) const 167 { 168 // push constant value expression to the stack 169 mpContext->maOperandStack.push( 170 ExpressionNodeFactory::createConstantValueExpression( n ) ); 171 } 172 173 private: 174 ParserContextSharedPtr mpContext; 175 }; 176 177 /** Generate special t value expression node 178 */ 179 class ValueTFunctor 180 { 181 public: 182 ValueTFunctor( const ParserContextSharedPtr& rContext ) : 183 mpContext( rContext ) 184 { 185 ENSURE_OR_THROW( mpContext, 186 "ValueTFunctor::ValueTFunctor(): Invalid context" ); 187 } 188 189 void operator()( StringIteratorT, StringIteratorT ) const 190 { 191 if( !mpContext->mbParseAnimationFunction ) 192 { 193 OSL_ENSURE( false, 194 "ValueTFunctor::operator(): variable encountered, but we're not parsing a function here" ); 195 throw ParseError(); 196 } 197 198 // push special t value expression to the stack 199 mpContext->maOperandStack.push( 200 ExpressionNodeFactory::createValueTExpression() ); 201 } 202 203 private: 204 ParserContextSharedPtr mpContext; 205 }; 206 207 template< typename Functor > class UnaryFunctionFunctor 208 { 209 private: 210 /** ExpressionNode implementation for unary 211 function over one ExpressionNode 212 */ 213 class UnaryFunctionExpression : public ExpressionNode 214 { 215 public: 216 UnaryFunctionExpression( const Functor& rFunctor, 217 const ExpressionNodeSharedPtr& rArg ) : 218 maFunctor( rFunctor ), 219 mpArg( rArg ) 220 { 221 } 222 223 virtual double operator()( double t ) const 224 { 225 return maFunctor( (*mpArg)(t) ); 226 } 227 228 virtual bool isConstant() const 229 { 230 return mpArg->isConstant(); 231 } 232 233 private: 234 Functor maFunctor; 235 ExpressionNodeSharedPtr mpArg; 236 }; 237 238 public: 239 UnaryFunctionFunctor( const Functor& rFunctor, 240 const ParserContextSharedPtr& rContext ) : 241 maFunctor( rFunctor ), 242 mpContext( rContext ) 243 { 244 ENSURE_OR_THROW( mpContext, 245 "UnaryFunctionFunctor::UnaryFunctionFunctor(): Invalid context" ); 246 } 247 248 void operator()( StringIteratorT, StringIteratorT ) const 249 { 250 ParserContext::OperandStack& rNodeStack( mpContext->maOperandStack ); 251 252 if( rNodeStack.size() < 1 ) 253 throw ParseError( "Not enough arguments for unary operator" ); 254 255 // retrieve arguments 256 ExpressionNodeSharedPtr pArg( rNodeStack.top() ); 257 rNodeStack.pop(); 258 259 // check for constness 260 if( pArg->isConstant() ) 261 { 262 rNodeStack.push( 263 ExpressionNodeFactory::createConstantValueExpression( 264 maFunctor( (*pArg)(0.0) ) ) ); 265 } 266 else 267 { 268 // push complex node, that calcs the value on demand 269 rNodeStack.push( 270 ExpressionNodeSharedPtr( 271 new UnaryFunctionExpression( 272 maFunctor, 273 pArg ) ) ); 274 } 275 } 276 277 private: 278 Functor maFunctor; 279 ParserContextSharedPtr mpContext; 280 }; 281 282 // TODO(Q2): Refactor makeUnaryFunctionFunctor, 283 // makeBinaryFunctionFunctor and the whole 284 // ExpressionNodeFactory, to use a generic 285 // makeFunctionFunctor template, which is overloaded for 286 // unary, binary, ternary, etc. function pointers. 287 template< typename Functor > UnaryFunctionFunctor<Functor> 288 makeUnaryFunctionFunctor( const Functor& rFunctor, 289 const ParserContextSharedPtr& rContext ) 290 { 291 return UnaryFunctionFunctor<Functor>( rFunctor, rContext ); 292 } 293 294 // MSVC has problems instantiating above template function with plain function 295 // pointers (doesn't like the const reference there). Thus, provide it with 296 // a dedicated overload here. 297 UnaryFunctionFunctor< double (*)(double) > 298 makeUnaryFunctionFunctor( double (*pFunc)(double), 299 const ParserContextSharedPtr& rContext ) 300 { 301 return UnaryFunctionFunctor< double (*)(double) >( pFunc, rContext ); 302 } 303 304 /** Implements a binary function over two ExpressionNodes 305 306 @tpl Generator 307 Generator functor, to generate an ExpressionNode of 308 appropriate type 309 310 */ 311 template< class Generator > class BinaryFunctionFunctor 312 { 313 public: 314 BinaryFunctionFunctor( const Generator& rGenerator, 315 const ParserContextSharedPtr& rContext ) : 316 maGenerator( rGenerator ), 317 mpContext( rContext ) 318 { 319 ENSURE_OR_THROW( mpContext, 320 "BinaryFunctionFunctor::BinaryFunctionFunctor(): Invalid context" ); 321 } 322 323 void operator()( StringIteratorT, StringIteratorT ) const 324 { 325 ParserContext::OperandStack& rNodeStack( mpContext->maOperandStack ); 326 327 if( rNodeStack.size() < 2 ) 328 throw ParseError( "Not enough arguments for binary operator" ); 329 330 // retrieve arguments 331 ExpressionNodeSharedPtr pSecondArg( rNodeStack.top() ); 332 rNodeStack.pop(); 333 ExpressionNodeSharedPtr pFirstArg( rNodeStack.top() ); 334 rNodeStack.pop(); 335 336 // create combined ExpressionNode 337 ExpressionNodeSharedPtr pNode( maGenerator( pFirstArg, 338 pSecondArg ) ); 339 // check for constness 340 if( pFirstArg->isConstant() && 341 pSecondArg->isConstant() ) 342 { 343 // call the operator() at pNode, store result 344 // in constant value ExpressionNode. 345 rNodeStack.push( 346 ExpressionNodeFactory::createConstantValueExpression( 347 (*pNode)( 0.0 ) ) ); 348 } 349 else 350 { 351 // push complex node, that calcs the value on demand 352 rNodeStack.push( pNode ); 353 } 354 } 355 356 private: 357 Generator maGenerator; 358 ParserContextSharedPtr mpContext; 359 }; 360 361 template< typename Generator > BinaryFunctionFunctor<Generator> 362 makeBinaryFunctionFunctor( const Generator& rGenerator, 363 const ParserContextSharedPtr& rContext ) 364 { 365 return BinaryFunctionFunctor<Generator>( rGenerator, rContext ); 366 } 367 368 369 // Workaround for MSVC compiler anomaly (stack trashing) 370 // 371 // The default ureal_parser_policies implementation of parse_exp 372 // triggers a really weird error in MSVC7 (Version 13.00.9466), in 373 // that the real_parser_impl::parse_main() call of parse_exp() 374 // overwrites the frame pointer _on the stack_ (EBP of the calling 375 // function gets overwritten while lying on the stack). 376 // 377 // For the time being, our parser thus can only read the 1.0E10 378 // notation, not the 1.0e10 one. 379 // 380 // TODO(F1): Also handle the 1.0e10 case here. 381 template< typename T > struct custom_real_parser_policies : public ::boost::spirit::ureal_parser_policies<T> 382 { 383 template< typename ScannerT > 384 static typename ::boost::spirit::parser_result< ::boost::spirit::chlit<>, ScannerT >::type 385 parse_exp(ScannerT& scan) 386 { 387 // as_lower_d somehow breaks MSVC7 388 return ::boost::spirit::ch_p('E').parse(scan); 389 } 390 }; 391 392 /* This class implements the following grammar (more or 393 less literally written down below, only slightly 394 obfuscated by the parser actions): 395 396 identifier = '$'|'pi'|'e'|'X'|'Y'|'Width'|'Height' 397 398 function = 'abs'|'sqrt'|'sin'|'cos'|'tan'|'atan'|'acos'|'asin'|'exp'|'log' 399 400 basic_expression = 401 number | 402 identifier | 403 function '(' additive_expression ')' | 404 '(' additive_expression ')' 405 406 unary_expression = 407 '-' basic_expression | 408 basic_expression 409 410 multiplicative_expression = 411 unary_expression ( ( '*' unary_expression )* | 412 ( '/' unary_expression )* ) 413 414 additive_expression = 415 multiplicative_expression ( ( '+' multiplicative_expression )* | 416 ( '-' multiplicative_expression )* ) 417 418 */ 419 class ExpressionGrammar : public ::boost::spirit::grammar< ExpressionGrammar > 420 { 421 public: 422 /** Create an arithmetic expression grammar 423 424 @param rParserContext 425 Contains context info for the parser 426 */ 427 ExpressionGrammar( const ParserContextSharedPtr& rParserContext ) : 428 mpParserContext( rParserContext ) 429 { 430 } 431 432 template< typename ScannerT > class definition 433 { 434 public: 435 // grammar definition 436 definition( const ExpressionGrammar& self ) 437 { 438 using ::boost::spirit::str_p; 439 using ::boost::spirit::real_parser; 440 441 identifier = 442 str_p( "$" )[ ValueTFunctor( self.getContext()) ] 443 | str_p( "pi" )[ ConstantFunctor(M_PI, self.getContext()) ] 444 | str_p( "e" )[ ConstantFunctor(M_E, self.getContext()) ] 445 | str_p( "x" )[ makeShapeBoundsFunctor(::std::mem_fun_ref(&::basegfx::B2DRange::getCenterX),self.getContext()) ] 446 | str_p( "y" )[ makeShapeBoundsFunctor(::std::mem_fun_ref(&::basegfx::B2DRange::getCenterY),self.getContext()) ] 447 | str_p( "width" )[ makeShapeBoundsFunctor(::std::mem_fun_ref(&::basegfx::B2DRange::getWidth), self.getContext()) ] 448 | str_p( "height" )[ makeShapeBoundsFunctor(::std::mem_fun_ref(&::basegfx::B2DRange::getHeight), self.getContext()) ] 449 ; 450 451 unaryFunction = 452 (str_p( "abs" ) >> '(' >> additiveExpression >> ')' )[ makeUnaryFunctionFunctor(&fabs, self.getContext()) ] 453 | (str_p( "sqrt" ) >> '(' >> additiveExpression >> ')' )[ makeUnaryFunctionFunctor(&sqrt, self.getContext()) ] 454 | (str_p( "sin" ) >> '(' >> additiveExpression >> ')' )[ makeUnaryFunctionFunctor(&sin, self.getContext()) ] 455 | (str_p( "cos" ) >> '(' >> additiveExpression >> ')' )[ makeUnaryFunctionFunctor(&cos, self.getContext()) ] 456 | (str_p( "tan" ) >> '(' >> additiveExpression >> ')' )[ makeUnaryFunctionFunctor(&tan, self.getContext()) ] 457 | (str_p( "atan" ) >> '(' >> additiveExpression >> ')' )[ makeUnaryFunctionFunctor(&atan, self.getContext()) ] 458 | (str_p( "acos" ) >> '(' >> additiveExpression >> ')' )[ makeUnaryFunctionFunctor(&acos, self.getContext()) ] 459 | (str_p( "asin" ) >> '(' >> additiveExpression >> ')' )[ makeUnaryFunctionFunctor(&asin, self.getContext()) ] 460 | (str_p( "exp" ) >> '(' >> additiveExpression >> ')' )[ makeUnaryFunctionFunctor(&exp, self.getContext()) ] 461 | (str_p( "log" ) >> '(' >> additiveExpression >> ')' )[ makeUnaryFunctionFunctor(&log, self.getContext()) ] 462 ; 463 464 binaryFunction = 465 (str_p( "min" ) >> '(' >> additiveExpression >> ',' >> additiveExpression >> ')' )[ makeBinaryFunctionFunctor(&ExpressionNodeFactory::createMinExpression, self.getContext()) ] 466 | (str_p( "max" ) >> '(' >> additiveExpression >> ',' >> additiveExpression >> ')' )[ makeBinaryFunctionFunctor(&ExpressionNodeFactory::createMaxExpression, self.getContext()) ] 467 ; 468 469 basicExpression = 470 real_parser<double, custom_real_parser_policies<double> >()[ DoubleConstantFunctor(self.getContext()) ] 471 | identifier 472 | unaryFunction 473 | binaryFunction 474 | '(' >> additiveExpression >> ')' 475 ; 476 477 unaryExpression = 478 ('-' >> basicExpression)[ makeUnaryFunctionFunctor(::std::negate<double>(), self.getContext()) ] 479 | basicExpression 480 ; 481 482 multiplicativeExpression = 483 unaryExpression 484 >> *( ('*' >> unaryExpression)[ makeBinaryFunctionFunctor(&ExpressionNodeFactory::createMultipliesExpression, self.getContext()) ] 485 | ('/' >> unaryExpression)[ makeBinaryFunctionFunctor(&ExpressionNodeFactory::createDividesExpression, self.getContext()) ] 486 ) 487 ; 488 489 additiveExpression = 490 multiplicativeExpression 491 >> *( ('+' >> multiplicativeExpression)[ makeBinaryFunctionFunctor(&ExpressionNodeFactory::createPlusExpression, self.getContext()) ] 492 | ('-' >> multiplicativeExpression)[ makeBinaryFunctionFunctor(&ExpressionNodeFactory::createMinusExpression, self.getContext()) ] 493 ) 494 ; 495 496 BOOST_SPIRIT_DEBUG_RULE(additiveExpression); 497 BOOST_SPIRIT_DEBUG_RULE(multiplicativeExpression); 498 BOOST_SPIRIT_DEBUG_RULE(unaryExpression); 499 BOOST_SPIRIT_DEBUG_RULE(basicExpression); 500 BOOST_SPIRIT_DEBUG_RULE(unaryFunction); 501 BOOST_SPIRIT_DEBUG_RULE(binaryFunction); 502 BOOST_SPIRIT_DEBUG_RULE(identifier); 503 } 504 505 const ::boost::spirit::rule< ScannerT >& start() const 506 { 507 return additiveExpression; 508 } 509 510 private: 511 // the constituents of the Spirit arithmetic expression grammar. 512 // For the sake of readability, without 'ma' prefix. 513 ::boost::spirit::rule< ScannerT > additiveExpression; 514 ::boost::spirit::rule< ScannerT > multiplicativeExpression; 515 ::boost::spirit::rule< ScannerT > unaryExpression; 516 ::boost::spirit::rule< ScannerT > basicExpression; 517 ::boost::spirit::rule< ScannerT > unaryFunction; 518 ::boost::spirit::rule< ScannerT > binaryFunction; 519 ::boost::spirit::rule< ScannerT > identifier; 520 }; 521 522 const ParserContextSharedPtr& getContext() const 523 { 524 return mpParserContext; 525 } 526 527 private: 528 ParserContextSharedPtr mpParserContext; // might get modified during parsing 529 }; 530 531 #ifdef BOOST_SPIRIT_SINGLE_GRAMMAR_INSTANCE 532 const ParserContextSharedPtr& getParserContext() 533 { 534 static ParserContextSharedPtr lcl_parserContext( new ParserContext() ); 535 536 // clear node stack (since we reuse the static object, that's 537 // the whole point here) 538 while( !lcl_parserContext->maOperandStack.empty() ) 539 lcl_parserContext->maOperandStack.pop(); 540 541 return lcl_parserContext; 542 } 543 #endif 544 } 545 546 ExpressionNodeSharedPtr SmilFunctionParser::parseSmilValue( const ::rtl::OUString& rSmilValue, 547 const ::basegfx::B2DRectangle& rRelativeShapeBounds ) 548 { 549 // TODO(Q1): Check if a combination of the RTL_UNICODETOTEXT_FLAGS_* 550 // gives better conversion robustness here (we might want to map space 551 // etc. to ASCII space here) 552 const ::rtl::OString& rAsciiSmilValue( 553 rtl::OUStringToOString( rSmilValue, RTL_TEXTENCODING_ASCII_US ) ); 554 555 StringIteratorT aStart( rAsciiSmilValue.getStr() ); 556 StringIteratorT aEnd( rAsciiSmilValue.getStr()+rAsciiSmilValue.getLength() ); 557 558 ParserContextSharedPtr pContext; 559 560 #ifdef BOOST_SPIRIT_SINGLE_GRAMMAR_INSTANCE 561 // static parser context, because the actual 562 // Spirit parser is also a static object 563 pContext = getParserContext(); 564 #else 565 pContext.reset( new ParserContext() ); 566 #endif 567 568 pContext->maShapeBounds = rRelativeShapeBounds; 569 pContext->mbParseAnimationFunction = false; // parse with '$' disabled 570 571 572 ExpressionGrammar aExpressionGrammer( pContext ); 573 const ::boost::spirit::parse_info<StringIteratorT> aParseInfo( 574 ::boost::spirit::parse( aStart, 575 aEnd, 576 aExpressionGrammer, 577 ::boost::spirit::space_p ) ); 578 OSL_DEBUG_ONLY(::std::cout.flush()); // needed to keep stdout and cout in sync 579 580 // input fully congested by the parser? 581 if( !aParseInfo.full ) 582 throw ParseError( "SmilFunctionParser::parseSmilValue(): string not fully parseable" ); 583 584 // parser's state stack now must contain exactly _one_ ExpressionNode, 585 // which represents our formula. 586 if( pContext->maOperandStack.size() != 1 ) 587 throw ParseError( "SmilFunctionParser::parseSmilValue(): incomplete or empty expression" ); 588 589 return pContext->maOperandStack.top(); 590 } 591 592 ExpressionNodeSharedPtr SmilFunctionParser::parseSmilFunction( const ::rtl::OUString& rSmilFunction, 593 const ::basegfx::B2DRectangle& rRelativeShapeBounds ) 594 { 595 // TODO(Q1): Check if a combination of the RTL_UNICODETOTEXT_FLAGS_* 596 // gives better conversion robustness here (we might want to map space 597 // etc. to ASCII space here) 598 const ::rtl::OString& rAsciiSmilFunction( 599 rtl::OUStringToOString( rSmilFunction, RTL_TEXTENCODING_ASCII_US ) ); 600 601 StringIteratorT aStart( rAsciiSmilFunction.getStr() ); 602 StringIteratorT aEnd( rAsciiSmilFunction.getStr()+rAsciiSmilFunction.getLength() ); 603 604 ParserContextSharedPtr pContext; 605 606 #ifdef BOOST_SPIRIT_SINGLE_GRAMMAR_INSTANCE 607 // static parser context, because the actual 608 // Spirit parser is also a static object 609 pContext = getParserContext(); 610 #else 611 pContext.reset( new ParserContext() ); 612 #endif 613 614 pContext->maShapeBounds = rRelativeShapeBounds; 615 pContext->mbParseAnimationFunction = true; // parse with '$' enabled 616 617 618 ExpressionGrammar aExpressionGrammer( pContext ); 619 const ::boost::spirit::parse_info<StringIteratorT> aParseInfo( 620 ::boost::spirit::parse( aStart, 621 aEnd, 622 aExpressionGrammer >> ::boost::spirit::end_p, 623 ::boost::spirit::space_p ) ); 624 OSL_DEBUG_ONLY(::std::cout.flush()); // needed to keep stdout and cout in sync 625 626 // input fully congested by the parser? 627 if( !aParseInfo.full ) 628 throw ParseError( "SmilFunctionParser::parseSmilFunction(): string not fully parseable" ); 629 630 // parser's state stack now must contain exactly _one_ ExpressionNode, 631 // which represents our formula. 632 if( pContext->maOperandStack.size() != 1 ) 633 throw ParseError( "SmilFunctionParser::parseSmilFunction(): incomplete or empty expression" ); 634 635 return pContext->maOperandStack.top(); 636 } 637 } 638 } 639