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 }