/** * @dataProvider getTokenizeData */ public function testTokenize($tokens, $expression) { $tokens[] = new Token('end of expression', null, strlen($expression) + 1); $lexer = new Lexer(); $this->assertEquals(new TokenStream($tokens), $lexer->tokenize($expression)); }
/** * @dataProvider getInvalidPostfixData * @expectedException \Symfony\Component\ExpressionLanguage\SyntaxError */ public function testParseWithInvalidPostfixData($expr, $names = array()) { $lexer = new Lexer(); $parser = new Parser(array()); $parser->parse($lexer->tokenize($expr), $names); }
/** * Actually tokenize an expression - at this point object and property access is * transformed, so that "this.property" will be "get('this.propery')" * * Also all function calls (but isset and empty) will be rewritten from * function(abc) to call("function", abc) to make dynamic functions possible * * @param string $expression The expression * * @return array */ protected function tokenizeExpression($expression) { $stream = parent::tokenize($expression); $tokens = array(); $previousWasDot = false; $ignorePrimaryExpressions = array_flip(['null', 'NULL', 'false', 'FALSE', 'true', 'TRUE']); while (!$stream->isEOF()) { /* @var \Symfony\Component\ExpressionLanguage\Token $token */ $token = $stream->current; $stream->next(); if ($token->type === Token::NAME_TYPE && !$previousWasDot) { if (array_key_exists($token->value, $ignorePrimaryExpressions)) { $tokens[] = $token; continue; } $isTest = false; if ($stream->current->test(Token::PUNCTUATION_TYPE, '(')) { $tokens[] = $token; $tokens[] = $stream->current; $stream->next(); if ($token->value === 'isset' || $token->value === 'empty') { $isTest = true; $token = $stream->current; if ($token->type !== Token::NAME_TYPE) { throw new SyntaxError('Expected name', $token->cursor); } $stream->next(); } else { $tokens[] = new Token(Token::STRING_TYPE, $token->value, $token->cursor); $token->value = 'call'; if (!$stream->current->test(Token::PUNCTUATION_TYPE, ')')) { $tokens[] = new Token(Token::PUNCTUATION_TYPE, ',', $token->cursor); } continue; } } $names = array($token->value); $isFunctionCall = false; while (!$stream->isEOF() && $stream->current->type === Token::PUNCTUATION_TYPE && $stream->current->value === '.') { $stream->next(); $nameToken = $stream->current; $stream->next(); // Operators like "not" and "matches" are valid method or property names - others not if ($nameToken->type !== Token::NAME_TYPE && ($nameToken->type !== Token::OPERATOR_TYPE || !preg_match('/[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*/A', $nameToken->value))) { throw new SyntaxError('Expected name', $nameToken->cursor); } if ($stream->current->test(Token::PUNCTUATION_TYPE, '(')) { $isFunctionCall = true; } else { $names[] = $nameToken->value; } } if ($isTest) { if ($isFunctionCall) { throw new SyntaxError('Can\'t use function return value in write context', $stream->current->cursor); } if (!$stream->current->test(Token::PUNCTUATION_TYPE, ')')) { throw new SyntaxError('Expected )', $stream->current->cursor); } $tokens[] = new Token(Token::STRING_TYPE, implode('.', $names), $token->cursor); } else { $tokens[] = new Token(Token::NAME_TYPE, 'get', $token->cursor); $tokens[] = new Token(Token::PUNCTUATION_TYPE, '(', $token->cursor); $tokens[] = new Token(Token::STRING_TYPE, implode('.', $names), $token->cursor); $tokens[] = new Token(Token::PUNCTUATION_TYPE, ')', $token->cursor); if ($isFunctionCall) { $tokens[] = new Token(Token::PUNCTUATION_TYPE, '.', $nameToken->cursor - strlen($nameToken->value)); $tokens[] = $nameToken; } } } else { $tokens[] = $token; $previousWasDot = $token->test(Token::PUNCTUATION_TYPE, '.'); } } return $tokens; }
/** * @dataProvider getParseData */ public function testParse($node, $expression, $names = array()) { $lexer = new Lexer(); $parser = new Parser(array()); $this->assertEquals($node, $parser->parse($lexer->tokenize($expression), $names)); }