openrat-cms

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

DslExpression.class.php (6751B)


      1 <?php
      2 
      3 namespace dsl\ast;
      4 
      5 define('LEFT', 0);
      6 define('RIGHT', 1);
      7 
      8 use dsl\context\BaseScriptableObject;
      9 use dsl\DslParserException;
     10 use dsl\DslToken;
     11 use dsl\executor\DslInterpreter;
     12 use dsl\standard\Number;
     13 use dsl\standard\StandardArray;
     14 use dsl\standard\StandardString;
     15 
     16 class DslExpression extends DslElement implements DslStatement
     17 {
     18 	private $value;
     19 
     20 	public function __construct( $valueTokens )
     21 	{
     22 		$this->parse( $valueTokens );
     23 	}
     24 
     25 	public function execute( & $context ) {
     26 
     27 		if   ( is_array( $this->value ) )
     28 			foreach( $this->value as $v)
     29 				$v->execute( $context );
     30 		else
     31 			return $this->value->execute( $context );
     32 	}
     33 
     34 	/**
     35 	 * @param DslToken[] $tokens
     36 	 * @throws DslParserException
     37 	 */
     38 	public function parse($tokens)
     39 	{
     40 		if   ( ! $tokens ) {
     41 			$this->value = new DslNull();
     42 			return;
     43 		}
     44 
     45 		$this->parseExpression( $tokens );
     46 	}
     47 
     48 
     49 	/**
     50 	 * Parsing an expression using the shunting yard algorithm.
     51 	 *
     52 	 *  Precedences see https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Operators/Operator_Precedence#assoziativit%C3%A4t
     53 	 * @param DslToken[] $tokens
     54 	 * @throws DslParserException
     55 	 */
     56 	private function parseExpression( $tokens )
     57 	{
     58 		$precedence = [
     59 			','  =>  2,
     60 			'='  =>  3,
     61 			'+=' =>  3,
     62 			'-=' =>  3,
     63 			'||' =>  5,
     64 			'&&' =>  6,
     65 			'==' => 10,
     66 			'!=' => 10,
     67 			'<'  => 11,
     68 			'<=' => 11,
     69 			'>'  => 11,
     70 			'>=' => 11,
     71 			'+'  => 13,
     72 			'-'  => 13,
     73 			'/'  => 14,
     74 			'*'  => 14,
     75 			'%'  => 14,
     76 			'**' => 15,
     77 			'!'  => 16,
     78 			'$'  => 19, // function call, provided by the lexer
     79 			'.'  => 19,
     80 		];
     81 
     82 		$assoc = [
     83 			','   => LEFT,
     84 			'='   => RIGHT,
     85 			'-='  => RIGHT,
     86 			'+='  => RIGHT,
     87 			'||'  => LEFT,
     88 			'&&'  => LEFT,
     89 			'=='  => LEFT,
     90 			'!='  => LEFT,
     91 			'<'   => LEFT,
     92 			'<='  => LEFT,
     93 			'> '  => LEFT,
     94 			'>='  => LEFT,
     95 			'+'   => LEFT,
     96 			'-'   => LEFT,
     97 			'/'   => LEFT,
     98 			'*'   => LEFT,
     99 			'%'   => LEFT,
    100 			'^'   => RIGHT,
    101 			'!'   => RIGHT,
    102 			'**'  => RIGHT,
    103 			'.'   => LEFT,
    104 			'$'   => LEFT,
    105 		];
    106 
    107 
    108 		// for the purpose of comparing only; it's forced to top priority explicitly
    109 		$precedence['('] = 0;
    110 		$precedence[')'] = 0;
    111 
    112 		$output_queue   = [];
    113 		$operator_stack = [];
    114 
    115 		if   ( $tokens instanceof DslStatement ) {
    116 
    117 			$this->value = $tokens;
    118 			return;
    119 		}
    120 
    121 		if   ( $tokens instanceof DslToken )
    122 			$tokens = [$tokens];
    123 
    124 		if   ( ! is_array($tokens))
    125 			throw new DslParserException("tokens must be an array, but it is ".get_class($tokens));
    126 
    127 		// while there are tokens to be read:
    128 		while ( $tokens ) {
    129 			// read a token.
    130 			$token = array_shift($tokens);
    131 
    132 			if ($token->isOperator() ) {
    133 
    134 				// while there is an operator at the top of the operator stack with
    135 				// greater than or equal to precedence:
    136 				while ($operator_stack &&
    137 					$precedence[end($operator_stack)->value] >= $precedence[$token->value] + $assoc[$token->value]) {
    138 					// pop operators from the operator stack, onto the output queue.
    139 
    140 					$left  = array_pop( $output_queue );
    141 					$right = array_pop( $output_queue );
    142 					$output_queue[] = $this->createNode( array_pop($operator_stack),$left,$right );
    143 				}
    144 				// push the read operator onto the operator stack.
    145 				$operator_stack[] = $token;
    146 
    147 				// if the token is a left bracket (i.e. "("), then:
    148 			} elseif ($token->value === '(') {
    149 				// push it onto the operator stack.
    150 				$operator_stack[] = $token;
    151 
    152 				// if the token is a right bracket (i.e. ")"), then:
    153 			} elseif ($token->value === ')') {
    154 				// while the operator at the top of the operator stack is not a left bracket:
    155 				while (end($operator_stack)->value !== '(') {
    156 					// pop operators from the operator stack onto the output queue.
    157 					$left  = array_pop( $output_queue );
    158 					$right = array_pop( $output_queue );
    159 					$output_queue[] = $this->createNode( array_pop($operator_stack),$left,$right );
    160 
    161 					// /* if the stack runs out without finding a left bracket, then there are
    162 					// mismatched parentheses. */
    163 					if (!$operator_stack) {
    164 						throw new DslParserException("Mismatched ')' parentheses");
    165 					}
    166 				}
    167 
    168 				// pop the left bracket from the stack.
    169 				array_pop($operator_stack);
    170 
    171 			}
    172 			else {
    173 					$output_queue[] = $this->tokenToStatement( $token );
    174 			}
    175 		} // if there are no more tokens to read:
    176 
    177 		// while there are still operator tokens on the stack:
    178 		while ($operator_stack) {
    179 			$token = array_pop($operator_stack);
    180 
    181 			// if the operator token on the top of the stack is a bracket, then
    182 			// there are mismatched parentheses.
    183 			if ($token->type == DslToken::T_OPERATOR && $token->value == '(') {
    184 				throw new DslParserException( "Mismatched '(' parentheses");
    185 			}
    186 			// pop the operator onto the output queue.
    187 			$left  = array_pop( $output_queue );
    188 			$right = array_pop( $output_queue );
    189 			$output_queue[] = $this->createNode( $token,$left,$right );
    190 		}
    191 
    192 		$this->value = $output_queue[0];
    193 	}
    194 
    195 	/**
    196 	 * @param $op DslToken
    197 	 * @param $left
    198 	 * @param $right
    199 	 * @throws DslParserException
    200 	 */
    201 	private function createNode($op, $left, $right)
    202 	{
    203 		if   ( $op->value == '=' )
    204 			return new DslAssignment( $right,$left );
    205 		if   ( $op->value == ',' )
    206 			return new DslSequence( $right,$left );
    207 		if   ( $op->value == '.' )
    208 			return new DslProperty( $right, $left );
    209 		if   ( $op->value == '$' )
    210 			return new DslFunctionCall( $right, $left );
    211 		else
    212 			return new DslOperation( $op->value,$right,$left );
    213 	}
    214 
    215 	/**
    216 	 * @param $token DslToken
    217 	 */
    218 	private function tokenToStatement($token)
    219 	{
    220 		switch( $token->type ) {
    221 			case DslToken::T_NONE:
    222 				return new DslInteger( 0 );
    223 			case DslToken::T_NULL:
    224 				return new DslNull();
    225 			case DslToken::T_TRUE:
    226 				return new DslTrue();
    227 			case DslToken::T_FALSE:
    228 				return new DslFalse();
    229 			case DslToken::T_NUMBER:
    230 				return new DslInteger( $token->value );
    231 			case DslToken::T_TEXT:
    232 				return new DslString( $token->value );
    233 			case DslToken::T_STRING:
    234 				return new DslVariable( $token->value );
    235 			case DslToken::T_DOT:
    236 				return new DslProperty( $token->value );
    237 			default:
    238 				throw new DslParserException('Unknown token '.$token->value,$token->lineNumber);
    239 		}
    240 	}
    241 
    242 
    243 	public static function convertValueToStandardObject($value ) {
    244 
    245 		if   (  is_object( $value ) ) {
    246 
    247 			if   ( $value instanceof BaseScriptableObject ) {
    248 				return $value;
    249 			}
    250 
    251 			if   ( DslInterpreter::isSecure() )
    252 				// Secured Sandbox, external objects are not evaluated.
    253 				return new StandardString( 'ProtectedObject' );
    254 			else
    255 				return $value; // Unsecured, but wanted.
    256 		}
    257 		elseif   (  is_array( $value ) ) {
    258 
    259 			return new StandardArray($value);
    260 		}
    261 		elseif   (  is_int( $value ) ) {
    262 
    263 			return new Number($value);
    264 		}
    265 		elseif   (  is_float( $value ) ) {
    266 
    267 			return new Number($value);
    268 		}
    269 		elseif   (  is_string( $value ) ) {
    270 
    271 			return new StandardString($value);
    272 		}
    273 	}
    274 }