blob: 51fb4e5871f47405aa2ff9e3f2a59cef208bf8fa [file] [log] [blame]
<?php
/**
* File containing the ezcTemplateExpressionExpressionParser class
*
* 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.
*
* @package Template
* @version //autogen//
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
* @access private
*/
/**
* Parser for expressions.
*
* Parses as until it reaches the end of the expression. The expression is
* parsed using type parsers and operator parsers. The end of the expression
* is determined by calling atEnd() on the parent element parser.
*
* @package Template
* @version //autogen//
* @access private
*/
class ezcTemplateExpressionSourceToTstParser extends ezcTemplateSourceToTstParser
{
/**
* The current operator element if any.
*
* If you are interested in the result of the expression see $rootOperator
* instead.
*
* @var ezcTemplateOperatorTstNode
*/
public $currentOperator;
/**
* The root of the operator/operand tree which is the result of the expression parsing.
* This will only be set after the parsing is succesful.
*
* @var ezcTemplateOperatorTstNode
*/
public $rootOperator;
/**
* @var bool
*/
public $foundArrayAppend = false;
/**
* Passes control to parent.
*
* @param ezcTemplateParser $parser
* @param ezcTemplateSourceToTstParser $parentParser
* @param ezcTemplateCursor $startCursor
*/
function __construct( ezcTemplateParser $parser, /*ezcTemplateSourceToTstParser*/ $parentParser, /*ezcTemplateCursor*/ $startCursor )
{
parent::__construct( $parser, $parentParser, $startCursor );
$this->currentOperator = null;
$this->rootOperator = null;
$this->minPrecedence = false;
}
/**
* Parses the expression by using the various type and operator parsers.
* The parsers has two states 'operand' and 'operator' which is switched
* (often alternating) to correctly parse the next element. Each operation
* also has a call to atEnd() on the parent element parser to figure out
* if the end has been reached.
*
* When an operator is found it will call ezcTemplateParser::handleOperatorPrecedence()
* to get proper order of operators in the tree.
*
* Look ahead: Operand
*
* @param ezcTemplateCursor $cursor
* @return bool
*/
protected function parseCurrent( ezcTemplateCursor $cursor )
{
$this->expressionStartCursor = clone $this->currentCursor;
$this->findNextElement();
$canDoAssignment = true;
$this->foundArrayAppend = false;
// If it has a preOperator, it must have an operand.
$match = "";
$type = "";
if ( $this->parsePreModifyingOperator( $cursor, $match ) ) // Parse: --, ++
{
$canDoAssignment = false;
if ( !$this->parseOperand( $cursor, array( "Variable" ), false ) )
{
throw new ezcTemplateParserException( $this->parser->source, $cursor, $cursor, ezcTemplateSourceToTstErrorMessages::MSG_EXPECT_VARIABLE );
}
return true;
}
elseif ( $this->parsePreOperator( $cursor, $match ) ) // Parse: -, +, !
{
$canDoAssignment = false;
if ( !$this->parseOperand( $cursor, array(), false ) )
{
throw new ezcTemplateParserException( $this->parser->source, $cursor, $cursor, ezcTemplateSourceToTstErrorMessages::MSG_EXPECT_OPERAND );
}
}
elseif ( $type = $this->parseOperand( $cursor, array(), true, true) ) // Only an operand?
{
if ( $type == "Variable" )
{
// The expression stops after a post operator.
if ( $this->parsePostOperator( $cursor ) ) return true;
}
}
else
{
// No, then it's not an expression.
return false;
}
while ( true )
{
// Operator check.
$this->findNextElement();
if ( $this->foundArrayAppend )
{
// After an array append follows an assignment.
if ( !$this->parseAssignmentOperator( $cursor ) )
{
throw new ezcTemplateParserException( $this->parser->source, $cursor, $cursor,
ezcTemplateSourceToTstErrorMessages::MSG_EXPECT_ARRAY_APPEND_ASSIGNMENT );
}
}
else
{
$operator = $this->parseOperator( $cursor, true /*$canDoAssignment*/ );
if ( !$operator )
{
return true;
}
if ( $type != "Variable" || $operator != "AssignmentOperator" )
{
$canDoAssignment = false;
}
}
// An operand is mandantory.
$failedCursor = clone $cursor;
$this->findNextElement();
$this->foundArrayAppend = false;
// Modifying operators are not allowed anymore.
$this->parsePreOperator( $cursor, $match );
$type = $this->parseOperand( $cursor, array(), false );
if ( !$type )
{
throw new ezcTemplateParserException( $this->parser->source, $cursor, $cursor, ezcTemplateSourceToTstErrorMessages::MSG_EXPECT_NON_MODIFYING_OPERAND );
}
}
}
/**
* Makes sure the element list contains the root operator/operand from the
* expression.
*
* @param ezcTemplateCursor $lastCursor
* @param ezcTemplateCursor $cursor
* @return void
*/
protected function handleSuccessfulResult( ezcTemplateCursor $lastCursor, ezcTemplateCursor $cursor )
{
$rootOperator = $this->currentOperator;
if ( $rootOperator instanceof ezcTemplateOperatorTstNode )
{
$rootOperator = $rootOperator->getRoot();
}
$this->rootOperator = $rootOperator;
// Make sure element list contains the root
$this->children = array( $this->rootOperator );
}
/**
* Returns true if the type $name can be parsed. If the $allowedTypes array is not empty, the array should also have the element $name.
*
* @param string $name
* @param array(string) $allowedTypes
* @return bool
*/
protected function canParseType( $name, $allowedTypes )
{
return $this->parseOptionalType( $name ) && ( sizeof( $allowedTypes ) == 0 || in_array( $name, $allowedTypes ) );
}
/**
* Parse an operand.
*
* @param ezcTemplateCursor $cursor
* @param array(string) $allowedTypes
* @param bool $allowPostModification
* @param bool $allowArrayAppend
* @return string The type that is parsed.
*/
protected function parseOperand( $cursor, $allowedTypes = array(), $allowPostModification = true, $allowArrayAppend = false )
{
$parsedType = false;
if ( $this->canParseType( 'Literal', $allowedTypes ) )
{
$this->operatorRhsCheck( $this->currentOperator, $this->lastParser->element, $cursor );
$this->currentOperator = $this->parser->handleOperand( $this->currentOperator, $this->lastParser->element );
$parsedType = "Literal";
}
elseif ( $this->canParseType( 'Variable', $allowedTypes ) )
{
$type = $this->parser->symbolTable->retrieve( $this->lastParser->element->name );
if ( $type === false )
{
throw new ezcTemplateParserException( $this->parser->source, $this->startCursor, $this->startCursor, $this->parser->symbolTable->getErrorMessage() );
}
$this->operatorRhsCheck( $this->currentOperator, $this->lastParser->element, $cursor );
$this->currentOperator = $this->parser->handleOperand( $this->currentOperator, $this->lastParser->element );
$this->findNextElement();
while ( (($res = $this->parseArrayFetch( $cursor, $allowArrayAppend )) && $res[0]) || $this->parsePropertyFetch( $cursor) )
{
$this->findNextElement();
if ( !$res[1] ) break;
}
$parsedType = "Variable";
}
elseif ( $this->canParseType( 'FunctionCall', $allowedTypes ) )
{
$this->operatorRhsCheck( $this->currentOperator, $this->lastParser->functionCall, $cursor );
$this->currentOperator = $this->parser->handleOperand( $this->currentOperator, $this->lastParser->functionCall );
$parsedType = "FunctionCall";
}
elseif ( $cursor->match( '(' ) && sizeof( $allowedTypes ) == 0 )
{
$expressionCursor = clone $cursor;
$expressionParser = new ezcTemplateExpressionBlockSourceToTstParser( $this->parser, $this, null );
$expressionParser->setAllCursors( $expressionCursor );
$expressionParser->startCursor = clone $cursor;
$expressionParser->startBracket = '(';
$expressionParser->endBracket = ')';
if ( !$this->parseRequiredType( $expressionParser ) )
{
return false;
}
if ( !$cursor->match( ')' ) )
{
throw new ezcTemplateParserException( $this->parser->source, $cursor, $cursor, ezcTemplateSourceToTstErrorMessages::MSG_EXPECT_ROUND_BRACKET_CLOSE );
}
$this->operatorRhsCheck( $this->currentOperator, $this->lastParser->block, $cursor );
$this->currentOperator = $this->parser->handleOperand( $this->currentOperator, $this->lastParser->block );
$parsedType = "Parenthesis";
}
return $parsedType;
}
/**
* Parse a pre operator
*
* @param ezcTemplateCursor $cursor
* @return bool
*/
protected function parsePreOperator( $cursor )
{
// Check if we have -, ! operators
$operator = null;
// This will contain the name of the operator if it is found.
$operatorName = false;
$operatorSymbols = array( array( 1,
array( '-', '!', '+' ) ) );
foreach ( $operatorSymbols as $symbolEntry )
{
$chars = $cursor->current( $symbolEntry[0] );
if ( in_array( $chars, $symbolEntry[1] ) )
{
$operatorName = $chars;
break;
}
}
if ( $operatorName !== false )
{
// Ignore the unary + operator.
if ( $operatorName == "+" )
{
$this->findNextElement();
$cursor->advance();
return true;
}
$operatorStartCursor = clone $cursor;
$cursor->advance( strlen( $operatorName ) );
$this->findNextElement();
$operatorMap = array( '-' => 'NegateOperator',
'!' => 'LogicalNegateOperator' );
$operatorName = $operatorMap[$operatorName];
$function = "ezcTemplate". $operatorName . "TstNode";
$operator = new $function( $this->parser->source, clone $this->lastCursor, $cursor );
// If the min precedence has been reached we immediately stop parsing
// and return a successful parse result
if ( $this->minPrecedence !== false &&
$operator->precedence < $this->minPrecedence )
{
$cursor->copy( $operatorStartCursor );
return true;
}
if ( $this->currentOperator === null )
{
$this->currentOperator = $operator;
}
else
{
// All pre operators should not sort precedence at this
// moment so just append it to the current operator.
$this->currentOperator->appendParameter( $operator );
$operator->parentOperator = $this->currentOperator;
$this->currentOperator = $operator;
}
$this->lastCursor->copy( $cursor );
return true;
}
return false;
}
/**
* Parse a pre operator
*
* @param ezcTemplateCursor $cursor
* @return bool
*/
protected function parsePostOperator( $cursor )
{
if ( $cursor->match( '++' ) )
{
$operatorStartCursor = clone $cursor;
$operator = new ezcTemplatePostIncrementOperatorTstNode( $this->parser->source, clone $this->lastCursor, $cursor );
}
elseif ( $cursor->match( '--' ) )
{
$operatorStartCursor = clone $cursor;
$operator = new ezcTemplatePostDecrementOperatorTstNode( $this->parser->source, clone $this->lastCursor, $cursor );
}
else
{
return false;
}
$this->currentOperator = $this->parser->handleOperatorPrecedence( $this->currentOperator, $operator );
$this->lastCursor->copy( $cursor );
return true;
}
/**
* Parse a pre modifying operator
*
* @param ezcTemplateCursor $cursor
* @return bool
*/
protected function parsePreModifyingOperator( $cursor )
{
if ( $cursor->match( '++' ) )
{
$operatorStartCursor = clone $cursor;
$operator = new ezcTemplatePreIncrementOperatorTstNode( $this->parser->source, clone $this->lastCursor, $cursor );
}
elseif ( $cursor->match( '--' ) )
{
$operatorStartCursor = clone $cursor;
$operator = new ezcTemplatePreDecrementOperatorTstNode( $this->parser->source, clone $this->lastCursor, $cursor );
}
else
{
return false;
}
if ( $this->currentOperator === null )
{
$this->currentOperator = $operator;
}
else
{
// All pre operators should not sort precedence at this
// moment so just append it to the current operator.
$this->currentOperator->appendParameter( $operator );
$operator->parentOperator = $this->currentOperator;
$this->currentOperator = $operator;
}
$this->lastCursor->copy( $cursor );
return true;
}
/**
* Parse the array fetch
*
* @param ezcTemplateCursor $cursor
* @param bool $allowArrayAppend
* @return array(bool,bool) = Continue, repeat.
*/
protected function parseArrayFetch( $cursor, $allowArrayAppend = false )
{
// Try the special array fetch operator
$operator = null;
while ( $cursor->match( '[' ) )
{
$this->findNextElement();
if ( $allowArrayAppend && $cursor->match( "]" ) )
{
$operator = new ezcTemplateArrayAppendOperatorTstNode( $this->parser->source, $this->startCursor, $cursor );
$this->foundArrayAppend = true;
$this->currentOperator = $this->parser->handleOperatorPrecedence( $this->currentOperator, $operator );
$this->lastCursor = clone $cursor;
return array(true, false);
}
else
{
$operatorStartCursor = clone $cursor;
if ( !$this->parseRequiredType( 'ArrayFetch' ) )
{
return array(false, true);
}
$this->findNextElement();
if ( !$cursor->match( "]" ) )
{
throw new ezcTemplateParserException( $this->parser->source, $cursor, $cursor, ezcTemplateSourceToTstErrorMessages::MSG_EXPECT_SQUARE_BRACKET_CLOSE );
}
$operator = $this->lastParser->fetch;
}
// If the min precedence has been reached we immediately stop parsing
// and return a successful parse result
if ( $this->minPrecedence !== false &&
$operator->precedence < $this->minPrecedence )
{
$cursor->copy( $operatorStartCursor );
return array(true, true);
}
$this->currentOperator = $this->parser->handleOperatorPrecedence( $this->currentOperator, $operator );
$this->lastCursor = clone $cursor;
$this->findNextElement();
}
return array(($operator !== null), true);
}
protected function parsePropertyFetch($cursor)
{
$operator = null;
while ( $cursor->match( '->' ) )
{
$this->findNextElement();
$operator = new ezcTemplatePropertyFetchOperatorTstNode( $this->parser->source, $this->startCursor, $cursor );
if ( $this->canParseType( 'Variable' , array() ) )
{
$type = $this->parser->symbolTable->retrieve( $this->lastParser->element->name );
if ( $type === false )
{
throw new ezcTemplateParserException( $this->parser->source, $this->lastParser->startCursor, $this->lastParser->startCursor, $this->parser->symbolTable->getErrorMessage() );
}
}
else if ( !$this->parseRequiredType( 'Identifier' ) )
{
throw new ezcTemplateParserException( $this->parser->source, $cursor, $cursor,
ezcTemplateSourceToTstErrorMessages::MSG_EXPECT_IDENTIFIER_OR_VARIABLE );
}
$this->currentOperator = $this->parser->handleOperatorPrecedence( $this->currentOperator, $operator );
$this->currentOperator = $this->parser->handleOperand( $this->currentOperator, $this->lastParser->element );
$this->findNextElement();
// $operator = $this->lastParser->value;
}
return $operator !== null;
}
/**
* Parse assignment operator
*
* @param ezcTemplateCursor $cursor
* @return bool
*/
protected function parseAssignmentOperator( $cursor )
{
if ( $cursor->match( "=" ) )
{
$function = "ezcTemplateAssignmentOperatorTstNode";
$operator = new $function( $this->parser->source, clone $this->lastCursor, $cursor );
$this->checkForValidOperator( $this->currentOperator, $operator, $cursor );
$this->currentOperator = $this->parser->handleOperatorPrecedence( $this->currentOperator, $operator );
return true;
}
return false;
}
/**
* Parse operator
*
* @param ezcTemplateCursor $cursor
* @param bool $canDoAssignment
* @return bool
*/
protected function parseOperator( $cursor, $canDoAssignment = true )
{
// Try som generic operators
$operator = null;
// This will contain the name of the operator if it is found.
$operatorName = false;
$operatorSymbols = array(
array( 3,
array( '===', '!==' ) ),
array( 2,
array(
'==', '!=',
'<=', '>=',
'&&', '||',
'+=', '-=', '*=', '/=', '.=', '%=',
'..',
'=>' /* To make sure that the expression ends when this token is found */,) ),
array( 1,
array( '+', '-', '.',
'*', '/', '%',
'<', '>', '=' ) ) );
foreach ( $operatorSymbols as $symbolEntry )
{
$chars = $cursor->current( $symbolEntry[0] );
if ( in_array( $chars, $symbolEntry[1] ) )
{
$operatorName = $chars;
break;
}
}
if ( $operatorName !== false && $operatorName == "=>" )
{
return false;
}
// Cannot do an assignment right now.
if ( $operatorName == "=" && !$canDoAssignment)
{
return false;
}
if ( $operatorName !== false )
{
$operatorStartCursor = clone $cursor;
$cursor->advance( strlen( $operatorName ) );
$operatorMap = array( '+' => 'PlusOperator',
'-' => 'MinusOperator',
'.' => 'ConcatOperator',
'*' => 'MultiplicationOperator',
'/' => 'DivisionOperator',
'%' => 'ModuloOperator',
'==' => 'EqualOperator',
'!=' => 'NotEqualOperator',
'===' => 'IdenticalOperator',
'!==' => 'NotIdenticalOperator',
'<' => 'LessThanOperator',
'>' => 'GreaterThanOperator',
'<=' => 'LessEqualOperator',
'>=' => 'GreaterEqualOperator',
'&&' => 'LogicalAndOperator',
'||' => 'LogicalOrOperator',
'=' => 'AssignmentOperator',
'+=' => 'PlusAssignmentOperator',
'-=' => 'MinusAssignmentOperator',
'*=' => 'MultiplicationAssignmentOperator',
'/=' => 'DivisionAssignmentOperator',
'.=' => 'ConcatAssignmentOperator',
'%=' => 'ModuloAssignmentOperator',
'..' => 'ArrayRangeOperator', );
$requestedName = $operatorName;
$operatorName = $operatorMap[$operatorName];
$function = "ezcTemplate". $operatorName . "TstNode";
$operator = new $function( $this->parser->source, clone $this->lastCursor, $cursor );
// If the min precedence has been reached we immediately stop parsing
// and return a successful parse result
if ( $this->minPrecedence !== false &&
$operator->precedence < $this->minPrecedence )
{
$cursor->copy( $operatorStartCursor );
return $operatorName;
}
$this->checkForValidOperator( $this->currentOperator, $operator, $operatorStartCursor );
$this->currentOperator = $this->parser->handleOperatorPrecedence( $this->currentOperator, $operator );
$this->lastCursor->copy( $cursor );
return $operatorName;
}
return false;
}
/**
* Check whether the given operand can be the left hand side of the $operator.
* For example: 4 = 5; is not allowed. But $a = 5 , and $a[0][0]->bla is.
*
* @param ezcTemplateTstNode $lhs
* @param ezcTemplateTstNode $op
* @param ezcTemplateCursor $cursor
* @throws ezcTemplateParserException if the check fails.
* @return void
*/
private function checkForValidOperator( $lhs, $op, $cursor )
{
if ( $op instanceof ezcTemplateModifyingOperatorTstNode)
{
if ( !( $lhs instanceof ezcTemplateVariableTstNode ||
$lhs instanceof ezcTemplateArrayAppendOperatorTstNode ||
$lhs instanceof ezcTemplateArrayFetchOperatorTstNode ||
$lhs instanceof ezcTemplatePropertyFetchOperatorTstNode ||
$lhs instanceof ezcTemplateModifyingOperatorTstNode ) )
{
if ( $op instanceof ezcTemplateOperatorTstNode )
{
throw new ezcTemplateParserException( $this->parser->source, $cursor, $cursor,
sprintf( ezcTemplateSourceToTstErrorMessages::MSG_LHS_IS_NOT_VARIABLE, $op->symbol ));
}
else
{
throw new ezcTemplateParserException( $this->parser->source, $cursor, $cursor,
ezcTemplateSourceToTstErrorMessages::MSG_LHS_IS_NOT_VARIABLE_NO_SYMBOL );
}
}
if ( $lhs instanceof ezcTemplateModifyingOperatorTstNode )
{
$this->checkForValidOperator( $lhs->parameters[1], $op, $cursor);
}
return;
}
if ( $lhs instanceof ezcTemplateModifyingBlockTstNode )
{
throw new ezcTemplateParserException( $this->parser->source, $cursor, $cursor,
ezcTemplateSourceToTstErrorMessages::MSG_OPERATOR_LHS_IS_MODIFYING_BLOCK );
}
return;
}
/**
* @param ezcTemplateTstNode $op
* @param ezcTemplateTstNode $rhs
* @param ezcTemplateCursor $cursor
*
* @throws ezcTemplateParserException if the check fails.
* @return void
*/
private function operatorRhsCheck( $op, $rhs, $cursor )
{
if ( $rhs instanceof ezcTemplateModifyingBlockTstNode )
{
if ( $op instanceof ezcTemplateOperatorTstNode )
{
throw new ezcTemplateParserException( $this->parser->source, $cursor, $cursor,
sprintf( ezcTemplateSourceToTstErrorMessages::MSG_OPERATOR_RHS_IS_MODIFYING_BLOCK, $op->symbol ));
}
else
{
throw new ezcTemplateParserException( $this->parser->source, $cursor, $cursor,
ezcTemplateSourceToTstErrorMessages::MSG_OPERATOR_IS_MODIFYING_BLOCK );
}
}
}
}
?>