scriptbox

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

DslExpression.class.php (6055B)


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