blob: 05e6a04e7a5b7da1f1692cab96c854f620c58cb9 [file] [log] [blame]
/**************************************************************
*
* 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();
}
}
}