evaluate($dataContext); $expression = array_pop($scopes); $expression->append($value); } private static function scopePush(&$expression, &$scopes, $token) { array_push($scopes, $expression); $expression = new PrimitiveExp($token); } public static function parse($tokenStream, $dataContext) { $scopes = array(); $expression = new PrimitiveExp(new Token('final')); while ($token = array_shift($tokenStream)) { // split non-primitive expression into primitive ones switch ($token->type) { case ExpType::$PAREN: switch ($token->value) { case '[': $expression->append(new Token(ExpType::$DOT, '.')); // drop through case '(': ExpParser::scopePush($expression, $scopes, $token); break; case ']': if ($expression->reason->value != '[') throw new ExpParserException("Unbalanced [], should be detected in Lexer"); ExpParser::scopePop($expression, $scopes, $dataContext); break; case ')': if ($expression->reason->value != '(') throw new ExpParserException("Unbalanced (), should be detected in Lexer"); ExpParser::scopePop($expression, $scopes, $dataContext); // close if it's a function if ($expression->reason->type == ExpType::$FUNCTION) ExpParser::scopePop($expression, $scopes, $dataContext); break; default: throw new ExpParserException("Token error: " . print_r($token, true)); } break; case ExpType::$FUNCTION: ExpParser::scopePush($expression, $scopes, $token); break; case ExpType::$TERNARY: if ($token->value != '?') throw new ExpParserException("Ternary token error"); $nextTernary = ExpParser::findNextTernary($tokenStream, 1); $indicator = ExpType::coerceToBool($expression->evaluate($dataContext)); if ($indicator->value) { // parsed?todo:skip $nextCloseSymbol = ExpParser::findNextCloseSymbol($tokenStream, $nextTernary + 1); array_splice($tokenStream, $nextTernary, $nextCloseSymbol - $nextTernary); } else { // parsed?skip:todo $tokenStream = array_slice($tokenStream, $nextTernary + 1); } $expression = new PrimitiveExp($expression->reason); break; case ExpType::$COMMA: if ($expression->reason->value != '(') throw new ExpParserException("Unbalanced (), should be detected in Lexer"); ExpParser::scopePop($expression, $scopes, $dataContext); ExpParser::scopePush($expression, $scopes, new Token(ExpType::$PAREN, '(')); break; default: $expression->append($token); } } if ($expression->reason->type != 'final') throw new ExpParserException("Gramma error on the non-primitive expression"); return $expression->evaluate($dataContext); } private static function findNextTernary($tokenStream, $startPos) { $stackDepth = 0; for ($i = $startPos; $i < count($tokenStream); $i ++) { $token = $tokenStream[$i]; if ($token->type == ExpType::$TERNARY && $token->value == '?') $stackDepth ++; if ($token->type == ExpType::$TERNARY && $token->value == ':') $stackDepth --; if ($stackDepth < 0) break; } return $i; } private static function findNextCloseSymbol($tokenStream, $startPos) { $stackDepth = 0; for ($i = $startPos; $i < count($tokenStream); $i ++) { $token = $tokenStream[$i]; if ($stackDepth == 0 && ExpParser::isCloseSymbol($token, false)) break; if (ExpParser::isOpenSymbol($token)) $stackDepth ++; if (ExpParser::isCloseSymbol($token)) $stackDepth --; } return $i; } private static function isOpenSymbol($token) { if ($token->type == ExpType::$PAREN && in_array($token->value, array('[', '('))) return true; return false; } private static function isCloseSymbol($token, $rigid = true) { if ($token->type == ExpType::$PAREN && in_array($token->value, array(']', ')'))) return true; if (! $rigid && ($token->type == ExpType::$COMMA || $token->type == ExpType::$TERNARY)) return true; return false; } }