changed array/hashes notation
authorFabien Potencier <fabien.potencier@gmail.com>
Tue, 14 Dec 2010 07:53:48 +0000 (08:53 +0100)
committerFabien Potencier <fabien.potencier@gmail.com>
Tue, 14 Dec 2010 08:33:07 +0000 (09:33 +0100)
CHANGELOG
doc/02-Twig-for-Template-Designers.markdown
lib/Twig/ExpressionParser.php
lib/Twig/Grammar/Hash.php [new file with mode: 0644]
test/Twig/Tests/ExpressionParserTest.php [new file with mode: 0644]
test/Twig/Tests/Fixtures/expressions/array.test
test/Twig/Tests/Fixtures/tags/include/only.test
test/Twig/Tests/Fixtures/tags/include/with_variables.test

index d7efa1e..87d087f 100644 (file)
--- 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:
 
index f9e0236..68a096c 100644 (file)
@@ -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`
index 24ff10a..51a87c9 100644 (file)
@@ -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 (file)
index 0000000..4bb181f
--- /dev/null
@@ -0,0 +1,22 @@
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2010 Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+class Twig_Grammar_Hash extends Twig_Grammar
+{
+    public function __toString()
+    {
+        return sprintf('<%s:array>', $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 (file)
index 0000000..eb81477
--- /dev/null
@@ -0,0 +1,99 @@
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+class Twig_Tests_ExpressionParserTest extends PHPUnit_Framework_TestCase
+{
+    /**
+     * @dataProvider getTestsForArray
+     */
+    public function testArrayExpression($template, $expected)
+    {
+        $env = new Twig_Environment(new Twig_Loader_String(), array('cache' => 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),
+            ),
+        );
+    }
+}
index 24167b6..e505ca4 100644 (file)
@@ -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(',') }}
index 474adcc..22e3d0f 100644 (file)
@@ -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--
index 96e393c..41384ac 100644 (file)
@@ -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 }}