1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
21 #include <comphelper/diagnose_ex.hxx>
23 #include <slideshowexceptions.hxx>
24 #include <smilfunctionparser.hxx>
25 #include <expressionnodefactory.hxx>
27 #include <rtl/ustring.hxx>
28 #include <sal/log.hxx>
30 // Makes parser a static resource,
31 // we're synchronized externally.
32 // But watch out, the parser might have
33 // state not visible to this code!
34 #define BOOST_SPIRIT_SINGLE_GRAMMAR_INSTANCE
37 #define BOOST_SPIRIT_DEBUG
39 #include <boost/spirit/include/classic_core.hpp>
48 /* Implementation of SmilFunctionParser class */
50 namespace slideshow::internal
54 typedef const char* StringIteratorT
;
58 typedef ::std::stack
< std::shared_ptr
<ExpressionNode
> > OperandStack
;
60 // stores a stack of not-yet-evaluated operands. This is used
61 // by the operators (i.e. '+', '*', 'sin' etc.) to pop their
62 // arguments from. If all arguments to an operator are constant,
63 // the operator pushes a precalculated result on the stack, and
64 // a composite ExpressionNode otherwise.
65 OperandStack maOperandStack
;
67 // bounds of the shape this expression is associated with
68 ::basegfx::B2DRectangle maShapeBounds
;
70 // when true, enable usage of time-dependent variable '$'
72 bool mbParseAnimationFunction
;
75 typedef ::std::shared_ptr
< ParserContext
> ParserContextSharedPtr
;
78 template< typename Generator
> class ShapeBoundsFunctor
81 ShapeBoundsFunctor( Generator aGenerator
,
82 ParserContextSharedPtr xContext
) :
83 maGenerator( aGenerator
),
84 mpContext(std::move( xContext
))
86 ENSURE_OR_THROW( mpContext
,
87 "ShapeBoundsFunctor::ShapeBoundsFunctor(): Invalid context" );
90 void operator()( StringIteratorT
, StringIteratorT
) const
92 mpContext
->maOperandStack
.push(
93 ExpressionNodeFactory::createConstantValueExpression(
94 maGenerator( mpContext
->maShapeBounds
) ) );
98 Generator maGenerator
;
99 ParserContextSharedPtr mpContext
;
102 template< typename Generator
> ShapeBoundsFunctor
< Generator
>
103 makeShapeBoundsFunctor( const Generator
& rGenerator
,
104 const ParserContextSharedPtr
& rContext
)
106 return ShapeBoundsFunctor
<Generator
>(rGenerator
, rContext
);
109 /** Generate apriori constant value
111 class ConstantFunctor
114 ConstantFunctor( double rValue
,
115 ParserContextSharedPtr xContext
) :
117 mpContext(std::move( xContext
))
119 ENSURE_OR_THROW( mpContext
,
120 "ConstantFunctor::ConstantFunctor(): Invalid context" );
123 void operator()( StringIteratorT
, StringIteratorT
) const
125 mpContext
->maOperandStack
.push(
126 ExpressionNodeFactory::createConstantValueExpression( mnValue
) );
130 const double mnValue
;
131 ParserContextSharedPtr mpContext
;
134 /** Generate parse-dependent-but-then-constant value
136 class DoubleConstantFunctor
139 explicit DoubleConstantFunctor( ParserContextSharedPtr xContext
) :
140 mpContext(std::move( xContext
))
142 ENSURE_OR_THROW( mpContext
,
143 "DoubleConstantFunctor::DoubleConstantFunctor(): Invalid context" );
146 void operator()( double n
) const
148 // push constant value expression to the stack
149 mpContext
->maOperandStack
.push(
150 ExpressionNodeFactory::createConstantValueExpression( n
) );
154 ParserContextSharedPtr mpContext
;
157 /** Generate special t value expression node
162 explicit ValueTFunctor( ParserContextSharedPtr xContext
) :
163 mpContext(std::move( xContext
))
165 ENSURE_OR_THROW( mpContext
,
166 "ValueTFunctor::ValueTFunctor(): Invalid context" );
169 void operator()( StringIteratorT
, StringIteratorT
) const
171 if( !mpContext
->mbParseAnimationFunction
)
173 SAL_WARN("slideshow", "ValueTFunctor::operator(): variable encountered, but we're not parsing a function here" );
177 // push special t value expression to the stack
178 mpContext
->maOperandStack
.push(
179 ExpressionNodeFactory::createValueTExpression() );
183 ParserContextSharedPtr mpContext
;
186 template< typename Functor
> class UnaryFunctionFunctor
189 /** ExpressionNode implementation for unary
190 function over one ExpressionNode
192 class UnaryFunctionExpression
: public ExpressionNode
195 UnaryFunctionExpression( const Functor
& rFunctor
,
196 std::shared_ptr
<ExpressionNode
> xArg
) :
197 maFunctor( rFunctor
),
198 mpArg(std::move( xArg
))
202 virtual double operator()( double t
) const override
204 return maFunctor( (*mpArg
)(t
) );
207 virtual bool isConstant() const override
209 return mpArg
->isConstant();
214 std::shared_ptr
<ExpressionNode
> mpArg
;
218 UnaryFunctionFunctor( const Functor
& rFunctor
,
219 ParserContextSharedPtr xContext
) :
220 maFunctor( rFunctor
),
221 mpContext(std::move( xContext
))
223 ENSURE_OR_THROW( mpContext
,
224 "UnaryFunctionFunctor::UnaryFunctionFunctor(): Invalid context" );
227 void operator()( StringIteratorT
, StringIteratorT
) const
229 ParserContext::OperandStack
& rNodeStack( mpContext
->maOperandStack
);
231 if( rNodeStack
.empty() )
232 throw ParseError( "Not enough arguments for unary operator" );
234 // retrieve arguments
235 std::shared_ptr
<ExpressionNode
> pArg( std::move(rNodeStack
.top()) );
238 // check for constness
239 if( pArg
->isConstant() )
242 ExpressionNodeFactory::createConstantValueExpression(
243 maFunctor( (*pArg
)(0.0) ) ) );
247 // push complex node, that calcs the value on demand
249 std::make_shared
<UnaryFunctionExpression
>(
257 ParserContextSharedPtr mpContext
;
260 // TODO(Q2): Refactor makeUnaryFunctionFunctor,
261 // makeBinaryFunctionFunctor and the whole
262 // ExpressionNodeFactory, to use a generic
263 // makeFunctionFunctor template, which is overloaded for
264 // unary, binary, ternary, etc. function pointers.
265 template< typename Functor
> UnaryFunctionFunctor
<Functor
>
266 makeUnaryFunctionFunctor( const Functor
& rFunctor
,
267 const ParserContextSharedPtr
& rContext
)
269 return UnaryFunctionFunctor
<Functor
>( rFunctor
, rContext
);
272 // MSVC has problems instantiating above template function with plain function
273 // pointers (doesn't like the const reference there). Thus, provide it with
274 // a dedicated overload here.
275 UnaryFunctionFunctor
< double (*)(double) >
276 makeUnaryFunctionFunctor( double (*pFunc
)(double),
277 const ParserContextSharedPtr
& rContext
)
279 return UnaryFunctionFunctor
< double (*)(double) >( pFunc
, rContext
);
282 /** Implements a binary function over two ExpressionNodes
285 Generator functor, to generate an ExpressionNode of
289 template< class Generator
> class BinaryFunctionFunctor
292 BinaryFunctionFunctor( const Generator
& rGenerator
,
293 ParserContextSharedPtr xContext
) :
294 maGenerator( rGenerator
),
295 mpContext(std::move( xContext
))
297 ENSURE_OR_THROW( mpContext
,
298 "BinaryFunctionFunctor::BinaryFunctionFunctor(): Invalid context" );
301 void operator()( StringIteratorT
, StringIteratorT
) const
303 ParserContext::OperandStack
& rNodeStack( mpContext
->maOperandStack
);
305 if( rNodeStack
.size() < 2 )
306 throw ParseError( "Not enough arguments for binary operator" );
308 // retrieve arguments
309 std::shared_ptr
<ExpressionNode
> pSecondArg( std::move(rNodeStack
.top()) );
311 std::shared_ptr
<ExpressionNode
> pFirstArg( std::move(rNodeStack
.top()) );
314 // create combined ExpressionNode
315 std::shared_ptr
<ExpressionNode
> pNode( maGenerator( pFirstArg
,
318 assert(pSecondArg
&& pFirstArg
);
320 // check for constness
321 if (pFirstArg
->isConstant() && pSecondArg
->isConstant())
323 // call the operator() at pNode, store result
324 // in constant value ExpressionNode.
326 ExpressionNodeFactory::createConstantValueExpression(
331 // push complex node, that calcs the value on demand
332 rNodeStack
.push( pNode
);
337 Generator maGenerator
;
338 ParserContextSharedPtr mpContext
;
341 template< typename Generator
> BinaryFunctionFunctor
<Generator
>
342 makeBinaryFunctionFunctor( const Generator
& rGenerator
,
343 const ParserContextSharedPtr
& rContext
)
345 return BinaryFunctionFunctor
<Generator
>( rGenerator
, rContext
);
349 // Workaround for MSVC compiler anomaly (stack trashing)
351 // The default ureal_parser_policies implementation of parse_exp
352 // triggers a really weird error in MSVC7 (Version 13.00.9466), in
353 // that the real_parser_impl::parse_main() call of parse_exp()
354 // overwrites the frame pointer _on the stack_ (EBP of the calling
355 // function gets overwritten while lying on the stack).
357 // For the time being, our parser thus can only read the 1.0E10
358 // notation, not the 1.0e10 one.
360 // TODO(F1): Also handle the 1.0e10 case here.
361 template< typename T
> struct custom_real_parser_policies
: public ::boost::spirit::classic::ureal_parser_policies
<T
>
363 template< typename ScannerT
>
364 static typename ::boost::spirit::classic::parser_result
< ::boost::spirit::classic::chlit
<>, ScannerT
>::type
365 parse_exp(ScannerT
& scan
)
367 // as_lower_d somehow breaks MSVC7
368 return ::boost::spirit::classic::ch_p('E').parse(scan
);
372 /* This class implements the following grammar (more or
373 less literally written down below, only slightly
374 obfuscated by the parser actions):
376 identifier = '$'|'pi'|'e'|'X'|'Y'|'Width'|'Height'
378 function = 'abs'|'sqrt'|'sin'|'cos'|'tan'|'atan'|'acos'|'asin'|'exp'|'log'
383 function '(' additive_expression ')' |
384 '(' additive_expression ')'
387 '-' basic_expression |
390 multiplicative_expression =
391 unary_expression ( ( '*' unary_expression )* |
392 ( '/' unary_expression )* )
394 additive_expression =
395 multiplicative_expression ( ( '+' multiplicative_expression )* |
396 ( '-' multiplicative_expression )* )
399 class ExpressionGrammar
: public ::boost::spirit::classic::grammar
< ExpressionGrammar
>
402 /** Create an arithmetic expression grammar
404 @param rParserContext
405 Contains context info for the parser
407 explicit ExpressionGrammar( ParserContextSharedPtr xParserContext
) :
408 mpParserContext(std::move( xParserContext
))
412 template< typename ScannerT
> class definition
415 // grammar definition
416 explicit definition( const ExpressionGrammar
& self
)
418 using ::boost::spirit::classic::str_p
;
419 using ::boost::spirit::classic::real_parser
;
422 str_p( "$" )[ ValueTFunctor( self
.getContext()) ]
423 | str_p( "pi" )[ ConstantFunctor(M_PI
, self
.getContext()) ]
424 | str_p( "e" )[ ConstantFunctor(M_E
, self
.getContext()) ]
425 | str_p( "x" )[ makeShapeBoundsFunctor(::std::mem_fn(&::basegfx::B2DRange::getCenterX
),self
.getContext()) ]
426 | str_p( "y" )[ makeShapeBoundsFunctor(::std::mem_fn(&::basegfx::B2DRange::getCenterY
),self
.getContext()) ]
427 | str_p( "width" )[ makeShapeBoundsFunctor(::std::mem_fn(&::basegfx::B2DRange::getWidth
), self
.getContext()) ]
428 | str_p( "height" )[ makeShapeBoundsFunctor(::std::mem_fn(&::basegfx::B2DRange::getHeight
), self
.getContext()) ]
432 (str_p( "abs" ) >> '(' >> additiveExpression
>> ')' )[ makeUnaryFunctionFunctor(&fabs
, self
.getContext()) ]
433 | (str_p( "sqrt" ) >> '(' >> additiveExpression
>> ')' )[ makeUnaryFunctionFunctor(&sqrt
, self
.getContext()) ]
434 | (str_p( "sin" ) >> '(' >> additiveExpression
>> ')' )[ makeUnaryFunctionFunctor(&sin
, self
.getContext()) ]
435 | (str_p( "cos" ) >> '(' >> additiveExpression
>> ')' )[ makeUnaryFunctionFunctor(&cos
, self
.getContext()) ]
436 | (str_p( "tan" ) >> '(' >> additiveExpression
>> ')' )[ makeUnaryFunctionFunctor(&tan
, self
.getContext()) ]
437 | (str_p( "atan" ) >> '(' >> additiveExpression
>> ')' )[ makeUnaryFunctionFunctor(&atan
, self
.getContext()) ]
438 | (str_p( "acos" ) >> '(' >> additiveExpression
>> ')' )[ makeUnaryFunctionFunctor(&acos
, self
.getContext()) ]
439 | (str_p( "asin" ) >> '(' >> additiveExpression
>> ')' )[ makeUnaryFunctionFunctor(&asin
, self
.getContext()) ]
440 | (str_p( "exp" ) >> '(' >> additiveExpression
>> ')' )[ makeUnaryFunctionFunctor(&exp
, self
.getContext()) ]
441 | (str_p( "log" ) >> '(' >> additiveExpression
>> ')' )[ makeUnaryFunctionFunctor(&log
, self
.getContext()) ]
445 (str_p( "min" ) >> '(' >> additiveExpression
>> ',' >> additiveExpression
>> ')' )[ makeBinaryFunctionFunctor(&ExpressionNodeFactory::createMinExpression
, self
.getContext()) ]
446 | (str_p( "max" ) >> '(' >> additiveExpression
>> ',' >> additiveExpression
>> ')' )[ makeBinaryFunctionFunctor(&ExpressionNodeFactory::createMaxExpression
, self
.getContext()) ]
450 real_parser
<double, custom_real_parser_policies
<double> >()[ DoubleConstantFunctor(self
.getContext()) ]
454 | '(' >> additiveExpression
>> ')'
458 ('-' >> basicExpression
)[ makeUnaryFunctionFunctor(::std::negate
<double>(), self
.getContext()) ]
462 multiplicativeExpression
=
464 >> *( ('*' >> unaryExpression
)[ makeBinaryFunctionFunctor(&ExpressionNodeFactory::createMultipliesExpression
, self
.getContext()) ]
465 | ('/' >> unaryExpression
)[ makeBinaryFunctionFunctor(&ExpressionNodeFactory::createDividesExpression
, self
.getContext()) ]
470 multiplicativeExpression
471 >> *( ('+' >> multiplicativeExpression
)[ makeBinaryFunctionFunctor(&ExpressionNodeFactory::createPlusExpression
, self
.getContext()) ]
472 | ('-' >> multiplicativeExpression
)[ makeBinaryFunctionFunctor(&ExpressionNodeFactory::createMinusExpression
, self
.getContext()) ]
476 BOOST_SPIRIT_DEBUG_RULE(additiveExpression
);
477 BOOST_SPIRIT_DEBUG_RULE(multiplicativeExpression
);
478 BOOST_SPIRIT_DEBUG_RULE(unaryExpression
);
479 BOOST_SPIRIT_DEBUG_RULE(basicExpression
);
480 BOOST_SPIRIT_DEBUG_RULE(unaryFunction
);
481 BOOST_SPIRIT_DEBUG_RULE(binaryFunction
);
482 BOOST_SPIRIT_DEBUG_RULE(identifier
);
485 const ::boost::spirit::classic::rule
< ScannerT
>& start() const
487 return additiveExpression
;
491 // the constituents of the Spirit arithmetic expression grammar.
492 // For the sake of readability, without 'ma' prefix.
493 ::boost::spirit::classic::rule
< ScannerT
> additiveExpression
;
494 ::boost::spirit::classic::rule
< ScannerT
> multiplicativeExpression
;
495 ::boost::spirit::classic::rule
< ScannerT
> unaryExpression
;
496 ::boost::spirit::classic::rule
< ScannerT
> basicExpression
;
497 ::boost::spirit::classic::rule
< ScannerT
> unaryFunction
;
498 ::boost::spirit::classic::rule
< ScannerT
> binaryFunction
;
499 ::boost::spirit::classic::rule
< ScannerT
> identifier
;
502 const ParserContextSharedPtr
& getContext() const
504 return mpParserContext
;
508 ParserContextSharedPtr mpParserContext
; // might get modified during parsing
511 const ParserContextSharedPtr
& getParserContext()
513 static ParserContextSharedPtr lcl_parserContext
= std::make_shared
<ParserContext
>();
515 // clear node stack (since we reuse the static object, that's
516 // the whole point here)
517 while( !lcl_parserContext
->maOperandStack
.empty() )
518 lcl_parserContext
->maOperandStack
.pop();
520 return lcl_parserContext
;
524 std::shared_ptr
<ExpressionNode
> const & SmilFunctionParser::parseSmilValue( const OUString
& rSmilValue
,
525 const ::basegfx::B2DRectangle
& rRelativeShapeBounds
)
527 // TODO(Q1): Check if a combination of the RTL_UNICODETOTEXT_FLAGS_*
528 // gives better conversion robustness here (we might want to map space
529 // etc. to ASCII space here)
530 const OString
aAsciiSmilValue(
531 OUStringToOString( rSmilValue
, RTL_TEXTENCODING_ASCII_US
) );
533 StringIteratorT
aStart( aAsciiSmilValue
.getStr() );
534 StringIteratorT
aEnd( aAsciiSmilValue
.getStr()+aAsciiSmilValue
.getLength() );
536 // static parser context, because the actual
537 // Spirit parser is also a static object
538 const ParserContextSharedPtr
& pContext
= getParserContext();
540 pContext
->maShapeBounds
= rRelativeShapeBounds
;
541 pContext
->mbParseAnimationFunction
= false; // parse with '$' disabled
544 ExpressionGrammar
aExpressionGrammer( pContext
);
545 const ::boost::spirit::classic::parse_info
<StringIteratorT
> aParseInfo(
546 ::boost::spirit::classic::parse( aStart
,
549 ::boost::spirit::classic::space_p
) );
551 #if OSL_DEBUG_LEVEL > 0
552 ::std::cout
.flush(); // needed to keep stdout and cout in sync
555 // input fully congested by the parser?
556 if( !aParseInfo
.full
)
557 throw ParseError( "SmilFunctionParser::parseSmilValue(): string not fully parseable" );
559 // parser's state stack now must contain exactly _one_ ExpressionNode,
560 // which represents our formula.
561 if( pContext
->maOperandStack
.size() != 1 )
562 throw ParseError( "SmilFunctionParser::parseSmilValue(): incomplete or empty expression" );
564 return pContext
->maOperandStack
.top();
567 std::shared_ptr
<ExpressionNode
> const & SmilFunctionParser::parseSmilFunction( const OUString
& rSmilFunction
,
568 const ::basegfx::B2DRectangle
& rRelativeShapeBounds
)
570 // TODO(Q1): Check if a combination of the RTL_UNICODETOTEXT_FLAGS_*
571 // gives better conversion robustness here (we might want to map space
572 // etc. to ASCII space here)
573 const OString
aAsciiSmilFunction(
574 OUStringToOString( rSmilFunction
, RTL_TEXTENCODING_ASCII_US
) );
576 StringIteratorT
aStart( aAsciiSmilFunction
.getStr() );
577 StringIteratorT
aEnd( aAsciiSmilFunction
.getStr()+aAsciiSmilFunction
.getLength() );
579 // static parser context, because the actual
580 // Spirit parser is also a static object
581 const ParserContextSharedPtr
& pContext
= getParserContext();
583 pContext
->maShapeBounds
= rRelativeShapeBounds
;
584 pContext
->mbParseAnimationFunction
= true; // parse with '$' enabled
587 ExpressionGrammar
aExpressionGrammer( pContext
);
588 const ::boost::spirit::classic::parse_info
<StringIteratorT
> aParseInfo(
589 ::boost::spirit::classic::parse( aStart
,
591 aExpressionGrammer
>> ::boost::spirit::classic::end_p
,
592 ::boost::spirit::classic::space_p
) );
594 #if OSL_DEBUG_LEVEL > 0
595 ::std::cout
.flush(); // needed to keep stdout and cout in sync
597 // input fully congested by the parser?
598 if( !aParseInfo
.full
)
599 throw ParseError( "SmilFunctionParser::parseSmilFunction(): string not fully parseable" );
601 // parser's state stack now must contain exactly _one_ ExpressionNode,
602 // which represents our formula.
603 if( pContext
->maOperandStack
.size() != 1 )
604 throw ParseError( "SmilFunctionParser::parseSmilFunction(): incomplete or empty expression" );
606 return pContext
->maOperandStack
.top();
610 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */