diff --git a/doc/filters/filter.rst b/doc/filters/filter.rst index 7e6a132b1..5c236eff0 100644 --- a/doc/filters/filter.rst +++ b/doc/filters/filter.rst @@ -11,7 +11,7 @@ function. The arrow function receives the value of the sequence or mapping: {% set sizes = [34, 36, 38, 40, 42] %} - {% for v in sizes|filter(|v| => v > 38) -%} + {% for v in sizes|filter(v => v > 38) -%} {{ v }} {% endfor %} {# output 40 42 #} @@ -24,7 +24,7 @@ function. The arrow function receives the value of the sequence or mapping: xl: 42, } %} - {% for k, v in sizes|filter(|v| => v > 38) -%} + {% for k, v in sizes|filter(v => v > 38) -%} {{ k }} = {{ v }} {% endfor %} {# output l = 40 xl = 42 #} diff --git a/doc/filters/map.rst b/doc/filters/map.rst index 0800e7c30..106b87463 100644 --- a/doc/filters/map.rst +++ b/doc/filters/map.rst @@ -14,7 +14,7 @@ mapping. The arrow function receives the value of the sequence or mapping: {first: "Alice", last: "Dupond"}, ] %} - {{ people|map(|p| => "#{p.first} #{p.last}")|join(', ') }} + {{ people|map(p => "#{p.first} #{p.last}")|join(', ') }} {# outputs Bob Smith, Alice Dupond #} Note that the arrow function has access to the current context. diff --git a/doc/filters/reduce.rst b/doc/filters/reduce.rst index 7f0108efd..10a0d5acb 100644 --- a/doc/filters/reduce.rst +++ b/doc/filters/reduce.rst @@ -13,14 +13,14 @@ value of the sequence or mapping: {% set numbers = [1, 2, 3] %} - {{ numbers|reduce(|carry, v| => carry + v) }} + {{ numbers|reduce((carry, v) => carry + v) }} {# output 6 #} The ``reduce`` filter takes an ``initial`` value as a second argument: .. code-block:: twig - {{ numbers|reduce(|carry, v| => carry + v, 10) }} + {{ numbers|reduce((carry, v) => carry + v, 10) }} {# output 16 #} Note that the arrow function has access to the current context. diff --git a/src/ExpressionParser.php b/src/ExpressionParser.php index 958b4b16e..d19cb58a5 100644 --- a/src/ExpressionParser.php +++ b/src/ExpressionParser.php @@ -109,22 +109,47 @@ class ExpressionParser private function parseArrow() { $stream = $this->parser->getStream(); - $token = $this->parser->getCurrentToken(); - $line = $token->getLine(); - if (!$token->test(Token::NAME_TYPE, 'fn')) { + + // short array syntax (one argument, no parentheses)? + if ($stream->look(1)->test(Token::ARROW_TYPE)) { + $line = $stream->getCurrent()->getLine(); + $token = $stream->expect(Token::NAME_TYPE); + $names = [new AssignNameExpression($token->getValue(), $token->getLine())]; + $stream->expect(Token::ARROW_TYPE); + + return new ArrowFunctionExpression($this->parseExpression(0), new Node($names), $line); + } + + // first, determine if we are parsing an arrow function by finding => (long form) + $i = 0; + if (!$stream->look($i)->test(Token::PUNCTUATION_TYPE, '(')) { return null; } - if ($stream->look(2)->test(Token::PUNCTUATION_TYPE, '(')) { + ++$i; + while (true) { + // variable name + ++$i; + if (!$stream->look($i)->test(Token::PUNCTUATION_TYPE, ',')) { + break; + } + ++$i; + } + if (!$stream->look($i)->test(Token::PUNCTUATION_TYPE, ')')) { + return null; + } + ++$i; + if (!$stream->look($i)->test(Token::ARROW_TYPE)) { return null; } - $stream->next(); // fn - $stream->next(); // ( + // yes, let's parse it properly + $token = $stream->expect(Token::PUNCTUATION_TYPE, '('); + $line = $token->getLine(); + $names = []; while (true) { $token = $stream->expect(Token::NAME_TYPE); - $value = $token->getValue(); - $names[] = new AssignNameExpression($value, $token->getLine()); + $names[] = new AssignNameExpression($token->getValue(), $token->getLine()); if (!$stream->nextIf(Token::PUNCTUATION_TYPE, ',')) { break; diff --git a/test/Twig/Tests/Fixtures/expressions/not_arrow_fn.test b/test/Twig/Tests/Fixtures/expressions/not_arrow_fn.test new file mode 100644 index 000000000..af82c47cf --- /dev/null +++ b/test/Twig/Tests/Fixtures/expressions/not_arrow_fn.test @@ -0,0 +1,8 @@ +--TEST-- +A string in parentheses cannot be confused with an arrow function +--TEMPLATE-- +{{ ["foo", "bar"]|join(("f")) }} +--DATA-- +return [] +--EXPECT-- +foofbar diff --git a/test/Twig/Tests/Fixtures/filters/filter.test b/test/Twig/Tests/Fixtures/filters/filter.test index 38f4e8020..ce2e45c55 100644 --- a/test/Twig/Tests/Fixtures/filters/filter.test +++ b/test/Twig/Tests/Fixtures/filters/filter.test @@ -3,10 +3,15 @@ --TEMPLATE-- {% set offset = 3 %} -{% for k, v in [1, 5, 3, 4, 5]|filter(fn(v) => v > offset) -%} +{% for k, v in [1, 5, 3, 4, 5]|filter((v) => v > offset) -%} {{ k }} = {{ v }} {% endfor %} -{% for k, v in {a: 1, b: 2, c: 5, d: 2}|filter(fn(v) => v > offset) -%} + +{% for k, v in {a: 1, b: 2, c: 5, d: 2}|filter((v) => v > offset) -%} + {{ k }} = {{ v }} +{% endfor %} + +{% for k, v in [1, 5, 3, 4, 5]|filter(v => v > offset) -%} {{ k }} = {{ v }} {% endfor %} --DATA-- @@ -15,4 +20,9 @@ return [] 1 = 5 3 = 4 4 = 5 + c = 5 + +1 = 5 +3 = 4 +4 = 5 diff --git a/test/Twig/Tests/Fixtures/filters/map.test b/test/Twig/Tests/Fixtures/filters/map.test index 4955300a1..4a630b8b9 100644 --- a/test/Twig/Tests/Fixtures/filters/map.test +++ b/test/Twig/Tests/Fixtures/filters/map.test @@ -3,10 +3,15 @@ --TEMPLATE-- {% set offset = 3 %} -{% for k, v in [1, 2]|map(fn(item) => item + 2 ) -%} +{% for k, v in [1, 2]|map((item) => item + 2 ) -%} {{ k }} = {{ v }} {% endfor %} -{% for k, v in {a: 1, b: 2}|map(fn(item) => item ~ "*" ) -%} + +{% for k, v in {a: 1, b: 2}|map((item) => item ~ "*" ) -%} + {{ k }} = {{ v }} +{% endfor %} + +{% for k, v in [1, 2]|map(item => item + 2 ) -%} {{ k }} = {{ v }} {% endfor %} --DATA-- @@ -14,5 +19,9 @@ return [] --EXPECT-- 0 = 3 1 = 4 + a = 1* b = 2* + +0 = 3 +1 = 4 diff --git a/test/Twig/Tests/Fixtures/filters/reduce.test b/test/Twig/Tests/Fixtures/filters/reduce.test index 40ce665b2..195680ff7 100644 --- a/test/Twig/Tests/Fixtures/filters/reduce.test +++ b/test/Twig/Tests/Fixtures/filters/reduce.test @@ -3,7 +3,7 @@ --TEMPLATE-- {% set offset = 3 %} -{{ [1, -1, 4]|reduce(fn(carry, item) => carry + item + offset, 10) }} +{{ [1, -1, 4]|reduce((carry, item) => carry + item + offset, 10) }} --DATA-- return [] --EXPECT--