From: Fabien Potencier Date: Tue, 14 Dec 2010 07:53:48 +0000 (+0100) Subject: changed array/hashes notation X-Git-Url: http://git.silmor.de/gitweb/?a=commitdiff_plain;h=4505200d9f5481839738421eed0bde3ae546e0a7;p=web%2Fkonrad%2Ftwig.git changed array/hashes notation --- diff --git a/CHANGELOG b/CHANGELOG index d7efa1e..87d087f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -8,6 +8,10 @@ Backward incompatibilities: or just set the 'autoescape' option to 'false'. * removed the "without loop" attribute for the "for" tag (not needed anymore as the Optimizer take care of that for most cases) + * arrays and hashes have now a different syntax + * arrays keep the same syntax with square brackets: [1, 2] + * hashes now use curly braces (["a": "b"] should now be written as {"a": "b"}) + * support for "arrays with keys" and "hashes without keys" is not supported anymore ([1, "foo": "bar"] or {"foo": "bar", 1}) Changes: diff --git a/doc/02-Twig-for-Template-Designers.markdown b/doc/02-Twig-for-Template-Designers.markdown index f9e0236..68a096c 100644 --- a/doc/02-Twig-for-Template-Designers.markdown +++ b/doc/02-Twig-for-Template-Designers.markdown @@ -695,7 +695,7 @@ the `set` tag and can have multiple targets: {% set foo = [1, 2] %} - {% set foo = ['foo': 'bar'] %} + {% set foo = {'foo': 'bar'} %} {% set foo = 'foo' ~ 'bar' %} @@ -742,7 +742,7 @@ You can add additional variables by passing them after the `with` keyword: {# the foo template will have access to the variables from the current context and the foo one #} {% include 'foo' with ['foo': 'bar'] %} - {% set vars = ['foo': 'bar'] %} + {% set vars = {'foo': 'bar'} %} {% include 'foo' with vars %} You can disable access to the context by appending the `only` keyword: @@ -923,12 +923,13 @@ exist: writing the number down. If a dot is present the number is a float, otherwise an integer. - * `[foo, bar]` (new in Twig 0.9.5): Arrays are defined by a sequence of + * `["foo", "bar"]` (new in Twig 0.9.5): Arrays are defined by a sequence of expressions separated by a comma (`,`) and wrapped with squared brackets - (`[]`). As an array element can be any valid expression, arrays can be - nested. Like PHP, arrays can also have named items (hashes) like `['foo': - 'foo', 'bar': 'bar']`. You can even mix and match both syntaxes: `['foo': - 'foo', 'bar']`. + (`[]`). + + * `{"foo": "bar"}` (new in Twig 0.9.10): Hashes are defined by a list of keys + and values separated by a comma (`,`) and wrapped with curly braces (`{}`). + A value can be any valid expression. * `true` / `false`: `true` represents the true value, `false` represents the false value. @@ -936,6 +937,11 @@ exist: * `none`: `none` represents no specific value (the equivalent of `null` in PHP). This is the value returned when a variable does not exist. +Arrays and hashes can be nested: + + [Twig] + {% set foo = [1, {"foo": "bar"}] %} + ### Math Twig allows you to calculate with values. This is rarely useful in templates @@ -1073,7 +1079,7 @@ The `format` filter formats a given string by replacing the placeholders (placeholders follows the `printf` notation): [twig] - {# string is a format string like: I like %s and %s. #} + {# string is a format string like: #} {{ string|format(foo, "bar") }} {# returns I like foo and bar. (if the foo parameter equals to the foo string) #} @@ -1084,7 +1090,7 @@ The `replace` filter formats a given string by replacing the placeholders [twig] {# string is a format string like: I like %this% and %that%. #} - {{ string|replace(['%this%': foo, '%that%': "bar"]) }} + {{ string|replace({'%this%': foo, '%that%': "bar"}) }} {# returns I like foo and bar. (if the foo parameter equals to the foo string) #} ### `cycle` diff --git a/lib/Twig/ExpressionParser.php b/lib/Twig/ExpressionParser.php index 24ff10a..51a87c9 100644 --- a/lib/Twig/ExpressionParser.php +++ b/lib/Twig/ExpressionParser.php @@ -143,6 +143,8 @@ class Twig_ExpressionParser default: if ($token->test(Twig_Token::PUNCTUATION_TYPE, '[')) { $node = $this->parseArrayExpression(); + } elseif ($token->test(Twig_Token::PUNCTUATION_TYPE, '{')) { + $node = $this->parseHashExpression(); } else { throw new Twig_Error_Syntax(sprintf('Unexpected token "%s" of value "%s"', Twig_Token::getTypeAsString($token->getType()), $token->getValue()), $token->getLine()); } @@ -166,36 +168,43 @@ class Twig_ExpressionParser // trailing ,? if ($stream->test(Twig_Token::PUNCTUATION_TYPE, ']')) { - $stream->expect(Twig_Token::PUNCTUATION_TYPE, ']'); - - return new Twig_Node_Expression_Array($elements, $this->parser->getCurrentToken()->getLine()); + break; } } - // hash or array element? - if ( - $stream->test(Twig_Token::STRING_TYPE) - || - $stream->test(Twig_Token::NUMBER_TYPE) - ) - { - if ($stream->look()->test(Twig_Token::PUNCTUATION_TYPE, ':')) { - // hash - $key = $stream->next()->getValue(); - $stream->next(); + $elements[] = $this->parseExpression(); + } + $stream->expect(Twig_Token::PUNCTUATION_TYPE, ']'); - $elements[$key] = $this->parseExpression(); + return new Twig_Node_Expression_Array($elements, $stream->getCurrent()->getLine()); + } - continue; + public function parseHashExpression() + { + $stream = $this->parser->getStream(); + $stream->expect(Twig_Token::PUNCTUATION_TYPE, '{'); + $elements = array(); + while (!$stream->test(Twig_Token::PUNCTUATION_TYPE, '}')) { + if (!empty($elements)) { + $stream->expect(Twig_Token::PUNCTUATION_TYPE, ','); + + // trailing ,? + if ($stream->test(Twig_Token::PUNCTUATION_TYPE, '}')) { + break; } - $stream->rewind(); } - $elements[] = $this->parseExpression(); + if (!$stream->test(Twig_Token::STRING_TYPE) && !$stream->test(Twig_Token::NUMBER_TYPE)) { + throw new Twig_Error_Syntax(sprintf('A hash key must be a quoted string or a number (unexpected token "%s" of value "%s"', Twig_Token::getTypeAsString($stream->getCurrent()->getType()), $stream->getCurrent()->getValue()), $stream->getCurrent()->getLine()); + } + + $key = $stream->next()->getValue(); + $stream->expect(Twig_Token::PUNCTUATION_TYPE, ':'); + $elements[$key] = $this->parseExpression(); } - $stream->expect(Twig_Token::PUNCTUATION_TYPE, ']'); + $stream->expect(Twig_Token::PUNCTUATION_TYPE, '}'); - return new Twig_Node_Expression_Array($elements, $this->parser->getCurrentToken()->getLine()); + return new Twig_Node_Expression_Array($elements, $stream->getCurrent()->getLine()); } public function parsePostfixExpression($node) diff --git a/lib/Twig/Grammar/Hash.php b/lib/Twig/Grammar/Hash.php new file mode 100644 index 0000000..4bb181f --- /dev/null +++ b/lib/Twig/Grammar/Hash.php @@ -0,0 +1,22 @@ +', $this->name); + } + + public function parse(Twig_Token $token) + { + return $this->parser->getExpressionParser()->parseHashExpression(); + } +} diff --git a/test/Twig/Tests/ExpressionParserTest.php b/test/Twig/Tests/ExpressionParserTest.php new file mode 100644 index 0000000..eb81477 --- /dev/null +++ b/test/Twig/Tests/ExpressionParserTest.php @@ -0,0 +1,99 @@ + false, 'autoescape' => false)); + $stream = $env->tokenize($template, 'index'); + $parser = new Twig_Parser($env); + + $this->assertEquals($expected, $parser->parse($stream)->getNode('body')->getNode(0)->getNode('expr')); + } + + /** + * @expectedException Twig_Error_Syntax + * @dataProvider getFailingTestsForArray + */ + public function testArraySyntaxError($template) + { + $env = new Twig_Environment(new Twig_Loader_String(), array('cache' => false, 'autoescape' => false)); + $parser = new Twig_Parser($env); + + $parser->parse($env->tokenize($template, 'index')); + } + + public function getFailingTestsForArray() + { + return array( + array('{{ [1, "a": "b"] }}'), + array('{{ {a: "b"} }}'), + array('{{ {"a": "b", 2} }}'), + ); + } + + public function getTestsForArray() + { + return array( + // simple array + array('{{ [1, 2] }}', new Twig_Node_Expression_Array(array( + new Twig_Node_Expression_Constant(1, 1), + new Twig_Node_Expression_Constant(2, 1), + ), 1), + ), + + // array with trailing , + array('{{ [1, 2, ] }}', new Twig_Node_Expression_Array(array( + new Twig_Node_Expression_Constant(1, 1), + new Twig_Node_Expression_Constant(2, 1), + ), 1), + ), + + // simple hash + array('{{ {"a": "b", "b": "c"} }}', new Twig_Node_Expression_Array(array( + 'a' => new Twig_Node_Expression_Constant('b', 1), + 'b' => new Twig_Node_Expression_Constant('c', 1), + ), 1), + ), + + // hash with trailing , + array('{{ {"a": "b", "b": "c", } }}', new Twig_Node_Expression_Array(array( + 'a' => new Twig_Node_Expression_Constant('b', 1), + 'b' => new Twig_Node_Expression_Constant('c', 1), + ), 1), + ), + + // hash in an array + array('{{ [1, {"a": "b", "b": "c"}] }}', new Twig_Node_Expression_Array(array( + new Twig_Node_Expression_Constant(1, 1), + new Twig_Node_Expression_Array(array( + 'a' => new Twig_Node_Expression_Constant('b', 1), + 'b' => new Twig_Node_Expression_Constant('c', 1), + ), 1), + ), 1), + ), + + // array in a hash + array('{{ {"a": [1, 2], "b": "c"} }}', new Twig_Node_Expression_Array(array( + 'a' => new Twig_Node_Expression_Array(array( + new Twig_Node_Expression_Constant(1, 1), + new Twig_Node_Expression_Constant(2, 1), + ), 1), + 'b' => new Twig_Node_Expression_Constant('c', 1), + ), 1), + ), + ); + } +} diff --git a/test/Twig/Tests/Fixtures/expressions/array.test b/test/Twig/Tests/Fixtures/expressions/array.test index 24167b6..e505ca4 100644 --- a/test/Twig/Tests/Fixtures/expressions/array.test +++ b/test/Twig/Tests/Fixtures/expressions/array.test @@ -6,13 +6,13 @@ Twig supports array notation {{ [1, 2]|join(',') }} {{ ['foo', "bar"]|join(',') }} -{{ [1, 'foo': 'bar']|join(',') }} -{{ [1, 'foo': 'bar']|keys|join(',') }} +{{ {0: 1, 'foo': 'bar'}|join(',') }} +{{ {0: 1, 'foo': 'bar'}|keys|join(',') }} {# nested arrays #} -{% set a = [1, 2, [1, 2], 'foo': ['foo': 'bar']] %} +{% set a = [1, 2, [1, 2], {'foo': {'foo': 'bar'}}] %} {{ a[2]|join(',') }} -{{ a["foo"]|join(',') }} +{{ a[3]["foo"]|join(',') }} {# works even if [] is used inside the array #} {{ [foo[bar]]|join(',') }} diff --git a/test/Twig/Tests/Fixtures/tags/include/only.test b/test/Twig/Tests/Fixtures/tags/include/only.test index 474adcc..22e3d0f 100644 --- a/test/Twig/Tests/Fixtures/tags/include/only.test +++ b/test/Twig/Tests/Fixtures/tags/include/only.test @@ -3,8 +3,8 @@ --TEMPLATE-- {% include "foo.twig" %} {% include "foo.twig" only %} -{% include "foo.twig" with ['foo1': 'bar'] %} -{% include "foo.twig" with ['foo1': 'bar'] only %} +{% include "foo.twig" with {'foo1': 'bar'} %} +{% include "foo.twig" with {'foo1': 'bar'} only %} --TEMPLATE(foo.twig)-- {% for k, v in _context %}{{ k }},{% endfor %} --DATA-- diff --git a/test/Twig/Tests/Fixtures/tags/include/with_variables.test b/test/Twig/Tests/Fixtures/tags/include/with_variables.test index 96e393c..41384ac 100644 --- a/test/Twig/Tests/Fixtures/tags/include/with_variables.test +++ b/test/Twig/Tests/Fixtures/tags/include/with_variables.test @@ -1,7 +1,7 @@ --TEST-- "include" tag accept variables --TEMPLATE-- -{% include "foo.twig" with ['foo': 'bar'] %} +{% include "foo.twig" with {'foo': 'bar'} %} {% include "foo.twig" with vars %} --TEMPLATE(foo.twig)-- {{ foo }}