Skip to content

Commit 269a0a7

Browse files
committed
Merge branch '2.7' into 2.8
* 2.7: Complete the injection of the expression in all syntax errors add expression text to SyntaxError
2 parents 3d63ca2 + 853fc65 commit 269a0a7

File tree

6 files changed

+70
-22
lines changed

6 files changed

+70
-22
lines changed

Lexer.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,12 @@ public function tokenize($expression)
5959
} elseif (false !== strpos(')]}', $expression[$cursor])) {
6060
// closing bracket
6161
if (empty($brackets)) {
62-
throw new SyntaxError(sprintf('Unexpected "%s"', $expression[$cursor]), $cursor);
62+
throw new SyntaxError(sprintf('Unexpected "%s"', $expression[$cursor]), $cursor, $expression);
6363
}
6464

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

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

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

9696
if (!empty($brackets)) {
9797
list($expect, $cur) = array_pop($brackets);
98-
throw new SyntaxError(sprintf('Unclosed "%s"', $expect), $cur);
98+
throw new SyntaxError(sprintf('Unclosed "%s"', $expect), $cur, $expression);
9999
}
100100

101-
return new TokenStream($tokens);
101+
return new TokenStream($tokens, $expression);
102102
}
103103
}

Parser.php

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ public function parse(TokenStream $stream, $names = array())
9999

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

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

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

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

@@ -289,7 +289,7 @@ public function parseHashExpression()
289289
} else {
290290
$current = $this->stream->current;
291291

292-
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);
292+
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());
293293
}
294294

295295
$this->stream->expect(Token::PUNCTUATION_TYPE, ':', 'A hash key must be followed by a colon (:)');
@@ -327,7 +327,7 @@ public function parsePostfixExpression($node)
327327
// 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.
328328
($token->type !== Token::OPERATOR_TYPE || !preg_match('/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/A', $token->value))
329329
) {
330-
throw new SyntaxError('Expected name', $token->cursor);
330+
throw new SyntaxError('Expected name', $token->cursor, $this->stream->getExpression());
331331
}
332332

333333
$arg = new Node\ConstantNode($token->value);
@@ -345,7 +345,7 @@ public function parsePostfixExpression($node)
345345
$node = new Node\GetAttrNode($node, $arg, $arguments, $type);
346346
} elseif ('[' === $token->value) {
347347
if ($node instanceof Node\GetAttrNode && Node\GetAttrNode::METHOD_CALL === $node->attributes['type'] && PHP_VERSION_ID < 50400) {
348-
throw new SyntaxError('Array calls on a method call is only supported on PHP 5.4+', $token->cursor);
348+
throw new SyntaxError('Array calls on a method call is only supported on PHP 5.4+', $token->cursor, $this->stream->getExpression());
349349
}
350350

351351
$this->stream->next();

SyntaxError.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,14 @@
1313

1414
class SyntaxError extends \LogicException
1515
{
16-
public function __construct($message, $cursor = 0)
16+
public function __construct($message, $cursor = 0, $expression = '')
1717
{
18-
parent::__construct(sprintf('%s around position %d.', $message, $cursor));
18+
$message = sprintf('%s around position %d', $message, $cursor);
19+
if ($expression) {
20+
$message = sprintf('%s for expression `%s`', $message, $expression);
21+
}
22+
$message .= '.';
23+
24+
parent::__construct($message);
1925
}
2026
}

Tests/LexerTest.php

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,43 @@
1818

1919
class LexerTest extends TestCase
2020
{
21+
/**
22+
* @var Lexer
23+
*/
24+
private $lexer;
25+
26+
protected function setUp()
27+
{
28+
$this->lexer = new Lexer();
29+
}
30+
2131
/**
2232
* @dataProvider getTokenizeData
2333
*/
2434
public function testTokenize($tokens, $expression)
2535
{
2636
$tokens[] = new Token('end of expression', null, strlen($expression) + 1);
27-
$lexer = new Lexer();
28-
$this->assertEquals(new TokenStream($tokens), $lexer->tokenize($expression));
37+
$this->assertEquals(new TokenStream($tokens, $expression), $this->lexer->tokenize($expression));
38+
}
39+
40+
/**
41+
* @expectedException \Symfony\Component\ExpressionLanguage\SyntaxError
42+
* @expectedExceptionMessage Unexpected character "'" around position 33 for expression `service(faulty.expression.example').dummyMethod()`.
43+
*/
44+
public function testTokenizeThrowsErrorWithMessage()
45+
{
46+
$expression = "service(faulty.expression.example').dummyMethod()";
47+
$this->lexer->tokenize($expression);
48+
}
49+
50+
/**
51+
* @expectedException \Symfony\Component\ExpressionLanguage\SyntaxError
52+
* @expectedExceptionMessage Unclosed "(" around position 7 for expression `service(unclosed.expression.dummyMethod()`.
53+
*/
54+
public function testTokenizeThrowsErrorOnUnclosedBrace()
55+
{
56+
$expression = 'service(unclosed.expression.dummyMethod()';
57+
$this->lexer->tokenize($expression);
2958
}
3059

3160
public function getTokenizeData()

Tests/ParserTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ class ParserTest extends TestCase
2020
{
2121
/**
2222
* @expectedException \Symfony\Component\ExpressionLanguage\SyntaxError
23-
* @expectedExceptionMessage Variable "foo" is not valid around position 1.
23+
* @expectedExceptionMessage Variable "foo" is not valid around position 1 for expression `foo`.
2424
*/
2525
public function testParseWithInvalidName()
2626
{
@@ -31,7 +31,7 @@ public function testParseWithInvalidName()
3131

3232
/**
3333
* @expectedException \Symfony\Component\ExpressionLanguage\SyntaxError
34-
* @expectedExceptionMessage Variable "foo" is not valid around position 1.
34+
* @expectedExceptionMessage Variable "foo" is not valid around position 1 for expression `foo`.
3535
*/
3636
public function testParseWithZeroInNames()
3737
{

TokenStream.php

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,19 @@ class TokenStream
2222

2323
private $tokens;
2424
private $position = 0;
25+
private $expression;
2526

2627
/**
2728
* Constructor.
2829
*
29-
* @param array $tokens An array of tokens
30+
* @param array $tokens An array of tokens
31+
* @param string $expression
3032
*/
31-
public function __construct(array $tokens)
33+
public function __construct(array $tokens, $expression = '')
3234
{
3335
$this->tokens = $tokens;
3436
$this->current = $tokens[0];
37+
$this->expression = $expression;
3538
}
3639

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

5659
++$this->position;
@@ -69,7 +72,7 @@ public function expect($type, $value = null, $message = null)
6972
{
7073
$token = $this->current;
7174
if (!$token->test($type, $value)) {
72-
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);
75+
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);
7376
}
7477
$this->next();
7578
}
@@ -83,4 +86,14 @@ public function isEOF()
8386
{
8487
return $this->current->type === Token::EOF_TYPE;
8588
}
89+
90+
/**
91+
* @internal
92+
*
93+
* @return string
94+
*/
95+
public function getExpression()
96+
{
97+
return $this->expression;
98+
}
8699
}

0 commit comments

Comments
 (0)