Skip to content

Commit

Permalink
Merge branch '3.2'
Browse files Browse the repository at this point in the history
* 3.2:
  [DI] Autowiring and factories are incompatible with each others
  [DI] Don't use auto-registered services to populate type-candidates
  Lighten tests output by removing composer suggestions
  support nullable array or collection
  Complete the injection of the expression in all syntax errors
  CS: Remove invisible chars
  Disable resource tracking if the config component is missing
  [EventDispatcher] Remove unneded count()
  Fix tests expecting a valid date
  Avoid forcing to define the choices_as_values option when using choice_loader
  add expression text to SyntaxError
  [Console] Fix table cell styling
  [Console] Revised exception rendering
  Fix @param in PHPDoc
  [WebProfilerBundle] Normalize whitespace in exceptions passed in headers
  Disable color support detection for tests
  [Form] Improve the exceptions when trying to get the data in a PRE_SET_DATA listener and the data has not already been set
  • Loading branch information
nicolas-grekas committed Apr 4, 2017
2 parents c29121f + 01655cd commit 0a615f9
Show file tree
Hide file tree
Showing 6 changed files with 69 additions and 21 deletions.
10 changes: 5 additions & 5 deletions Lexer.php
Expand Up @@ -59,12 +59,12 @@ public function tokenize($expression)
} elseif (false !== strpos(')]}', $expression[$cursor])) {
// closing bracket
if (empty($brackets)) {
throw new SyntaxError(sprintf('Unexpected "%s"', $expression[$cursor]), $cursor);
throw new SyntaxError(sprintf('Unexpected "%s"', $expression[$cursor]), $cursor, $expression);
}

list($expect, $cur) = array_pop($brackets);
if ($expression[$cursor] != strtr($expect, '([{', ')]}')) {
throw new SyntaxError(sprintf('Unclosed "%s"', $expect), $cur);
throw new SyntaxError(sprintf('Unclosed "%s"', $expect), $cur, $expression);
}

$tokens[] = new Token(Token::PUNCTUATION_TYPE, $expression[$cursor], $cursor + 1);
Expand All @@ -87,17 +87,17 @@ public function tokenize($expression)
$cursor += strlen($match[0]);
} else {
// unlexable
throw new SyntaxError(sprintf('Unexpected character "%s"', $expression[$cursor]), $cursor);
throw new SyntaxError(sprintf('Unexpected character "%s"', $expression[$cursor]), $cursor, $expression);
}
}

$tokens[] = new Token(Token::EOF_TYPE, null, $cursor + 1);

if (!empty($brackets)) {
list($expect, $cur) = array_pop($brackets);
throw new SyntaxError(sprintf('Unclosed "%s"', $expect), $cur);
throw new SyntaxError(sprintf('Unclosed "%s"', $expect), $cur, $expression);
}

return new TokenStream($tokens);
return new TokenStream($tokens, $expression);
}
}
12 changes: 6 additions & 6 deletions Parser.php
Expand Up @@ -99,7 +99,7 @@ public function parse(TokenStream $stream, $names = array())

$node = $this->parseExpression();
if (!$stream->isEOF()) {
throw new SyntaxError(sprintf('Unexpected token "%s" of value "%s"', $stream->current->type, $stream->current->value), $stream->current->cursor);
throw new SyntaxError(sprintf('Unexpected token "%s" of value "%s"', $stream->current->type, $stream->current->value), $stream->current->cursor, $stream->getExpression());
}

return $node;
Expand Down Expand Up @@ -195,13 +195,13 @@ public function parsePrimaryExpression()
default:
if ('(' === $this->stream->current->value) {
if (false === isset($this->functions[$token->value])) {
throw new SyntaxError(sprintf('The function "%s" does not exist', $token->value), $token->cursor);
throw new SyntaxError(sprintf('The function "%s" does not exist', $token->value), $token->cursor, $this->stream->getExpression());
}

$node = new Node\FunctionNode($token->value, $this->parseArguments());
} else {
if (!in_array($token->value, $this->names, true)) {
throw new SyntaxError(sprintf('Variable "%s" is not valid', $token->value), $token->cursor);
throw new SyntaxError(sprintf('Variable "%s" is not valid', $token->value), $token->cursor, $this->stream->getExpression());
}

// is the name used in the compiled code different
Expand All @@ -227,7 +227,7 @@ public function parsePrimaryExpression()
} elseif ($token->test(Token::PUNCTUATION_TYPE, '{')) {
$node = $this->parseHashExpression();
} else {
throw new SyntaxError(sprintf('Unexpected token "%s" of value "%s"', $token->type, $token->value), $token->cursor);
throw new SyntaxError(sprintf('Unexpected token "%s" of value "%s"', $token->type, $token->value), $token->cursor, $this->stream->getExpression());
}
}

Expand Down Expand Up @@ -289,7 +289,7 @@ public function parseHashExpression()
} else {
$current = $this->stream->current;

throw new SyntaxError(sprintf('A hash key must be a quoted string, a number, a name, or an expression enclosed in parentheses (unexpected token "%s" of value "%s"', $current->type, $current->value), $current->cursor);
throw new SyntaxError(sprintf('A hash key must be a quoted string, a number, a name, or an expression enclosed in parentheses (unexpected token "%s" of value "%s"', $current->type, $current->value), $current->cursor, $this->stream->getExpression());
}

$this->stream->expect(Token::PUNCTUATION_TYPE, ':', 'A hash key must be followed by a colon (:)');
Expand Down Expand Up @@ -327,7 +327,7 @@ public function parsePostfixExpression($node)
// As a result, if $token is NOT an operator OR $token->value is NOT a valid property or method name, an exception shall be thrown.
($token->type !== Token::OPERATOR_TYPE || !preg_match('/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/A', $token->value))
) {
throw new SyntaxError('Expected name', $token->cursor);
throw new SyntaxError('Expected name', $token->cursor, $this->stream->getExpression());
}

$arg = new Node\ConstantNode($token->value, true);
Expand Down
10 changes: 8 additions & 2 deletions SyntaxError.php
Expand Up @@ -13,8 +13,14 @@

class SyntaxError extends \LogicException
{
public function __construct($message, $cursor = 0)
public function __construct($message, $cursor = 0, $expression = '')
{
parent::__construct(sprintf('%s around position %d.', $message, $cursor));
$message = sprintf('%s around position %d', $message, $cursor);
if ($expression) {
$message = sprintf('%s for expression `%s`', $message, $expression);
}
$message .= '.';

parent::__construct($message);
}
}
33 changes: 31 additions & 2 deletions Tests/LexerTest.php
Expand Up @@ -18,14 +18,43 @@

class LexerTest extends TestCase
{
/**
* @var Lexer
*/
private $lexer;

protected function setUp()
{
$this->lexer = new Lexer();
}

/**
* @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));
$this->assertEquals(new TokenStream($tokens, $expression), $this->lexer->tokenize($expression));
}

/**
* @expectedException \Symfony\Component\ExpressionLanguage\SyntaxError
* @expectedExceptionMessage Unexpected character "'" around position 33 for expression `service(faulty.expression.example').dummyMethod()`.
*/
public function testTokenizeThrowsErrorWithMessage()
{
$expression = "service(faulty.expression.example').dummyMethod()";
$this->lexer->tokenize($expression);
}

/**
* @expectedException \Symfony\Component\ExpressionLanguage\SyntaxError
* @expectedExceptionMessage Unclosed "(" around position 7 for expression `service(unclosed.expression.dummyMethod()`.
*/
public function testTokenizeThrowsErrorOnUnclosedBrace()
{
$expression = 'service(unclosed.expression.dummyMethod()';
$this->lexer->tokenize($expression);
}

public function getTokenizeData()
Expand Down
4 changes: 2 additions & 2 deletions Tests/ParserTest.php
Expand Up @@ -20,7 +20,7 @@ class ParserTest extends TestCase
{
/**
* @expectedException \Symfony\Component\ExpressionLanguage\SyntaxError
* @expectedExceptionMessage Variable "foo" is not valid around position 1.
* @expectedExceptionMessage Variable "foo" is not valid around position 1 for expression `foo`.
*/
public function testParseWithInvalidName()
{
Expand All @@ -31,7 +31,7 @@ public function testParseWithInvalidName()

/**
* @expectedException \Symfony\Component\ExpressionLanguage\SyntaxError
* @expectedExceptionMessage Variable "foo" is not valid around position 1.
* @expectedExceptionMessage Variable "foo" is not valid around position 1 for expression `foo`.
*/
public function testParseWithZeroInNames()
{
Expand Down
21 changes: 17 additions & 4 deletions TokenStream.php
Expand Up @@ -22,16 +22,19 @@ class TokenStream

private $tokens;
private $position = 0;
private $expression;

/**
* Constructor.
*
* @param array $tokens An array of tokens
* @param array $tokens An array of tokens
* @param string $expression
*/
public function __construct(array $tokens)
public function __construct(array $tokens, $expression = '')
{
$this->tokens = $tokens;
$this->current = $tokens[0];
$this->expression = $expression;
}

/**
Expand All @@ -50,7 +53,7 @@ public function __toString()
public function next()
{
if (!isset($this->tokens[$this->position])) {
throw new SyntaxError('Unexpected end of expression', $this->current->cursor);
throw new SyntaxError('Unexpected end of expression', $this->current->cursor, $this->expression);
}

++$this->position;
Expand All @@ -69,7 +72,7 @@ public function expect($type, $value = null, $message = null)
{
$token = $this->current;
if (!$token->test($type, $value)) {
throw new SyntaxError(sprintf('%sUnexpected token "%s" of value "%s" ("%s" expected%s)', $message ? $message.'. ' : '', $token->type, $token->value, $type, $value ? sprintf(' with value "%s"', $value) : ''), $token->cursor);
throw new SyntaxError(sprintf('%sUnexpected token "%s" of value "%s" ("%s" expected%s)', $message ? $message.'. ' : '', $token->type, $token->value, $type, $value ? sprintf(' with value "%s"', $value) : ''), $token->cursor, $this->expression);
}
$this->next();
}
Expand All @@ -83,4 +86,14 @@ public function isEOF()
{
return $this->current->type === Token::EOF_TYPE;
}

/**
* @internal
*
* @return string
*/
public function getExpression()
{
return $this->expression;
}
}

0 comments on commit 0a615f9

Please sign in to comment.