From 82177f44bd1c5b823ea9582d5455836916774868 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 15 Jun 2016 11:25:34 +0200 Subject: [PATCH] [ExpressionLanguage] Add a way to hook on each node when dumping the AST --- .../ExpressionLanguage/Node/ArgumentsNode.php | 8 +- .../ExpressionLanguage/Node/ArrayNode.php | 36 ++++----- .../ExpressionLanguage/Node/BinaryNode.php | 2 +- .../Node/ConditionalNode.php | 2 +- .../ExpressionLanguage/Node/ConstantNode.php | 74 ++++++++----------- .../ExpressionLanguage/Node/FunctionNode.php | 12 +-- .../ExpressionLanguage/Node/GetAttrNode.php | 14 ++-- .../ExpressionLanguage/Node/NameNode.php | 2 +- .../ExpressionLanguage/Node/Node.php | 4 +- .../ExpressionLanguage/Node/UnaryNode.php | 2 +- .../ExpressionLanguage/ParsedExpression.php | 13 +++- .../Component/ExpressionLanguage/Parser.php | 2 +- .../Tests/Node/AbstractNodeTest.php | 4 +- .../Tests/Node/ConstantNodeTest.php | 4 +- .../Tests/Node/GetAttrNodeTest.php | 12 +-- .../ExpressionLanguage/Tests/ParserTest.php | 12 +-- 16 files changed, 105 insertions(+), 98 deletions(-) diff --git a/src/Symfony/Component/ExpressionLanguage/Node/ArgumentsNode.php b/src/Symfony/Component/ExpressionLanguage/Node/ArgumentsNode.php index 9c7b67a2a6ba2..f39c0e4b0e4b6 100644 --- a/src/Symfony/Component/ExpressionLanguage/Node/ArgumentsNode.php +++ b/src/Symfony/Component/ExpressionLanguage/Node/ArgumentsNode.php @@ -27,12 +27,14 @@ public function compile(Compiler $compiler) public function dump() { - $str = ''; + $tokens = array(); foreach ($this->getKeyValuePairs() as $pair) { - $str .= sprintf('%s, ', $pair['value']->dump()); + $tokens[] = $pair['value']; + $tokens[] = ', '; } + array_pop($tokens); - return rtrim($str, ', '); + return $tokens; } } diff --git a/src/Symfony/Component/ExpressionLanguage/Node/ArrayNode.php b/src/Symfony/Component/ExpressionLanguage/Node/ArrayNode.php index 15cc817dac7ed..36244cacc518d 100644 --- a/src/Symfony/Component/ExpressionLanguage/Node/ArrayNode.php +++ b/src/Symfony/Component/ExpressionLanguage/Node/ArrayNode.php @@ -62,30 +62,30 @@ public function dump() { $array = array(); foreach ($this->getKeyValuePairs() as $pair) { - $array[$pair['key']->attributes['value']] = $pair['value']->dump(); + $array[$pair['key']->attributes['value']] = $pair['value']; } + $tokens = array(); + if ($this->isHash($array)) { - $str = '{'; - - foreach ($array as $key => $value) { - if (is_int($key)) { - $str .= sprintf('%s: %s, ', $key, $value); - } else { - $str .= sprintf('"%s": %s, ', $this->dumpEscaped($key), $value); - } + foreach ($array as $k => $v) { + $tokens[] = ', '; + $tokens[] = new ConstantNode($k); + $tokens[] = ': '; + $tokens[] = $v; } - - return rtrim($str, ', ').'}'; - } - - $str = '['; - - foreach ($array as $key => $value) { - $str .= sprintf('%s, ', $value); + $tokens[0] = '{'; + $tokens[] = '}'; + } else { + foreach ($array as $v) { + $tokens[] = ', '; + $tokens[] = $v; + } + $tokens[0] = '['; + $tokens[] = ']'; } - return rtrim($str, ', ').']'; + return $tokens; } protected function getKeyValuePairs() diff --git a/src/Symfony/Component/ExpressionLanguage/Node/BinaryNode.php b/src/Symfony/Component/ExpressionLanguage/Node/BinaryNode.php index f28562b01ac50..bd1bb897265a5 100644 --- a/src/Symfony/Component/ExpressionLanguage/Node/BinaryNode.php +++ b/src/Symfony/Component/ExpressionLanguage/Node/BinaryNode.php @@ -157,6 +157,6 @@ public function evaluate($functions, $values) public function dump() { - return sprintf('(%s %s %s)', $this->nodes['left']->dump(), $this->attributes['operator'], $this->nodes['right']->dump()); + return array('(', $this->nodes['left'], ' '.$this->attributes['operator'].' ', $this->nodes['right'], ')'); } } diff --git a/src/Symfony/Component/ExpressionLanguage/Node/ConditionalNode.php b/src/Symfony/Component/ExpressionLanguage/Node/ConditionalNode.php index cbe235f2503f2..4d31adb08e14f 100644 --- a/src/Symfony/Component/ExpressionLanguage/Node/ConditionalNode.php +++ b/src/Symfony/Component/ExpressionLanguage/Node/ConditionalNode.php @@ -51,6 +51,6 @@ public function evaluate($functions, $values) public function dump() { - return sprintf('(%s ? %s : %s)', $this->nodes['expr1']->dump(), $this->nodes['expr2']->dump(), $this->nodes['expr3']->dump()); + return array('(', $this->nodes['expr1'], ' ? ', $this->nodes['expr2'], ' : ', $this->nodes['expr3'], ')'); } } diff --git a/src/Symfony/Component/ExpressionLanguage/Node/ConstantNode.php b/src/Symfony/Component/ExpressionLanguage/Node/ConstantNode.php index 2017f6f31dff3..5a1c831c3d998 100644 --- a/src/Symfony/Component/ExpressionLanguage/Node/ConstantNode.php +++ b/src/Symfony/Component/ExpressionLanguage/Node/ConstantNode.php @@ -40,49 +40,37 @@ public function evaluate($functions, $values) public function dump() { - return $this->dumpValue($this->attributes['value']); - } - - private function dumpValue($value) - { - switch (true) { - case true === $value: - return 'true'; - - case false === $value: - return 'false'; - - case null === $value: - return 'null'; - - case is_numeric($value): - return $value; - - case is_array($value): - if ($this->isHash($value)) { - $str = '{'; - - foreach ($value as $key => $v) { - if (is_int($key)) { - $str .= sprintf('%s: %s, ', $key, $this->dumpValue($v)); - } else { - $str .= sprintf('"%s": %s, ', $this->dumpEscaped($key), $this->dumpValue($v)); - } - } - - return rtrim($str, ', ').'}'; - } - - $str = '['; - - foreach ($value as $key => $v) { - $str .= sprintf('%s, ', $this->dumpValue($v)); - } - - return rtrim($str, ', ').']'; - - default: - return sprintf('"%s"', $this->dumpEscaped($value)); + $tokens = array(); + $value = $this->attributes['value']; + + if (true === $value) { + $tokens[] = 'true'; + } elseif (false === $value) { + $tokens[] = 'false'; + } elseif (null === $value) { + $tokens[] = 'null'; + } elseif (is_numeric($value)) { + $tokens[] = $value; + } elseif (!is_array($value)) { + $tokens[] = $this->dumpString($value); + } elseif ($this->isHash($value)) { + foreach ($value as $k => $v) { + $tokens[] = ', '; + $tokens[] = new self($k); + $tokens[] = ': '; + $tokens[] = new self($v); + } + $tokens[0] = '{'; + $tokens[] = '}'; + } else { + foreach ($value as $v) { + $tokens[] = ', '; + $tokens[] = new self($v); + } + $tokens[0] = '['; + $tokens[] = ']'; } + + return $tokens; } } diff --git a/src/Symfony/Component/ExpressionLanguage/Node/FunctionNode.php b/src/Symfony/Component/ExpressionLanguage/Node/FunctionNode.php index 9cb0b01f5682d..6edcd59127feb 100644 --- a/src/Symfony/Component/ExpressionLanguage/Node/FunctionNode.php +++ b/src/Symfony/Component/ExpressionLanguage/Node/FunctionNode.php @@ -52,14 +52,16 @@ public function evaluate($functions, $values) public function dump() { - $str = $this->attributes['name']; - - $str .= '('; + $tokens = array(); + $tokens[] = $this->attributes['name']; foreach ($this->nodes['arguments']->nodes as $node) { - $str .= $node->dump().', '; + $tokens[] = ', '; + $tokens[] = $node; } + $tokens[1] = '('; + $tokens[] = ')'; - return rtrim($str, ', ').')'; + return $tokens; } } diff --git a/src/Symfony/Component/ExpressionLanguage/Node/GetAttrNode.php b/src/Symfony/Component/ExpressionLanguage/Node/GetAttrNode.php index 1cf88c0ee18d6..d81b0b364072a 100644 --- a/src/Symfony/Component/ExpressionLanguage/Node/GetAttrNode.php +++ b/src/Symfony/Component/ExpressionLanguage/Node/GetAttrNode.php @@ -39,7 +39,7 @@ public function compile(Compiler $compiler) $compiler ->compile($this->nodes['node']) ->raw('->') - ->raw($this->nodes['attribute']->attributes['value']) + ->raw($this->nodes['attribute']->attributes['name']) ; break; @@ -47,7 +47,7 @@ public function compile(Compiler $compiler) $compiler ->compile($this->nodes['node']) ->raw('->') - ->raw($this->nodes['attribute']->attributes['value']) + ->raw($this->nodes['attribute']->attributes['name']) ->raw('(') ->compile($this->nodes['arguments']) ->raw(')') @@ -73,7 +73,7 @@ public function evaluate($functions, $values) throw new \RuntimeException('Unable to get a property on a non-object.'); } - $property = $this->nodes['attribute']->attributes['value']; + $property = $this->nodes['attribute']->attributes['name']; return $obj->$property; @@ -83,7 +83,7 @@ public function evaluate($functions, $values) throw new \RuntimeException('Unable to get a property on a non-object.'); } - return call_user_func_array(array($obj, $this->nodes['attribute']->attributes['value']), $this->nodes['arguments']->evaluate($functions, $values)); + return call_user_func_array(array($obj, $this->nodes['attribute']->attributes['name']), $this->nodes['arguments']->evaluate($functions, $values)); case self::ARRAY_CALL: $array = $this->nodes['node']->evaluate($functions, $values); @@ -99,13 +99,13 @@ public function dump() { switch ($this->attributes['type']) { case self::PROPERTY_CALL: - return sprintf('%s.%s', $this->nodes['node']->dump(), trim($this->nodes['attribute']->dump(), '"')); + return array($this->nodes['node'], '.', $this->nodes['attribute']); case self::METHOD_CALL: - return sprintf('%s.%s(%s)', $this->nodes['node']->dump(), trim($this->nodes['attribute']->dump(), '"'), $this->nodes['arguments']->dump()); + return array($this->nodes['node'], '.', $this->nodes['attribute'], '(', $this->nodes['arguments'], ')'); case self::ARRAY_CALL: - return sprintf('%s[%s]', $this->nodes['node']->dump(), $this->nodes['attribute']->dump()); + return array($this->nodes['node'], '[', $this->nodes['attribute'], ']'); } } } diff --git a/src/Symfony/Component/ExpressionLanguage/Node/NameNode.php b/src/Symfony/Component/ExpressionLanguage/Node/NameNode.php index 4e03df931a485..5fcccebd2d97f 100644 --- a/src/Symfony/Component/ExpressionLanguage/Node/NameNode.php +++ b/src/Symfony/Component/ExpressionLanguage/Node/NameNode.php @@ -40,6 +40,6 @@ public function evaluate($functions, $values) public function dump() { - return $this->attributes['name']; + return array($this->attributes['name']); } } diff --git a/src/Symfony/Component/ExpressionLanguage/Node/Node.php b/src/Symfony/Component/ExpressionLanguage/Node/Node.php index 6bce170bab034..a1607a9817ec2 100644 --- a/src/Symfony/Component/ExpressionLanguage/Node/Node.php +++ b/src/Symfony/Component/ExpressionLanguage/Node/Node.php @@ -81,9 +81,9 @@ public function dump() throw new \BadMethodCallException(sprintf('Dumping a "%s" instance is not supported yet.', get_class($this))); } - protected function dumpEscaped($value) + protected function dumpString($value) { - return str_replace(array('\\', '"'), array('\\\\', '\"'), $value); + return sprintf('"%s"', addcslashes($value, "\0\t\"\\")); } protected function isHash(array $value) diff --git a/src/Symfony/Component/ExpressionLanguage/Node/UnaryNode.php b/src/Symfony/Component/ExpressionLanguage/Node/UnaryNode.php index acafb33582553..0af0ce3ddd41b 100644 --- a/src/Symfony/Component/ExpressionLanguage/Node/UnaryNode.php +++ b/src/Symfony/Component/ExpressionLanguage/Node/UnaryNode.php @@ -61,6 +61,6 @@ public function evaluate($functions, $values) public function dump() { - return sprintf('(%s %s)', $this->attributes['operator'], $this->nodes['node']->dump()); + return array('(', $this->attributes['operator'].' ', $this->nodes['node'], ')'); } } diff --git a/src/Symfony/Component/ExpressionLanguage/ParsedExpression.php b/src/Symfony/Component/ExpressionLanguage/ParsedExpression.php index 7aaeb246baf17..4ec91eff21f0c 100644 --- a/src/Symfony/Component/ExpressionLanguage/ParsedExpression.php +++ b/src/Symfony/Component/ExpressionLanguage/ParsedExpression.php @@ -42,6 +42,17 @@ public function getNodes() public function dump() { - return $this->nodes->dump(); + return $this->dumpNode($this->nodes); + } + + private function dumpNode(Node $node) + { + $dump = ''; + + foreach($node->dump() as $token) { + $dump .= is_scalar($token) ? $token : $this->dumpNode($token); + } + + return $dump; } } diff --git a/src/Symfony/Component/ExpressionLanguage/Parser.php b/src/Symfony/Component/ExpressionLanguage/Parser.php index c94ba14db405d..e4ebee1dd27eb 100644 --- a/src/Symfony/Component/ExpressionLanguage/Parser.php +++ b/src/Symfony/Component/ExpressionLanguage/Parser.php @@ -330,7 +330,7 @@ public function parsePostfixExpression($node) throw new SyntaxError('Expected name', $token->cursor); } - $arg = new Node\ConstantNode($token->value); + $arg = new Node\NameNode($token->value); $arguments = new Node\ArgumentsNode(); if ($this->stream->current->test(Token::PUNCTUATION_TYPE, '(')) { diff --git a/src/Symfony/Component/ExpressionLanguage/Tests/Node/AbstractNodeTest.php b/src/Symfony/Component/ExpressionLanguage/Tests/Node/AbstractNodeTest.php index 4ddd10a159190..68de73dc361ef 100644 --- a/src/Symfony/Component/ExpressionLanguage/Tests/Node/AbstractNodeTest.php +++ b/src/Symfony/Component/ExpressionLanguage/Tests/Node/AbstractNodeTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\ExpressionLanguage\Tests\Node; use Symfony\Component\ExpressionLanguage\Compiler; +use Symfony\Component\ExpressionLanguage\ParsedExpression; abstract class AbstractNodeTest extends \PHPUnit_Framework_TestCase { @@ -42,7 +43,8 @@ abstract public function getCompileData(); */ public function testDump($expected, $node) { - $this->assertSame($expected, $node->dump()); + $expr = new ParsedExpression($expected, $node); + $this->assertSame($expected, $expr->dump()); } abstract public function getDumpData(); diff --git a/src/Symfony/Component/ExpressionLanguage/Tests/Node/ConstantNodeTest.php b/src/Symfony/Component/ExpressionLanguage/Tests/Node/ConstantNodeTest.php index d5f5f5ca74a63..1ba8ea96c6b95 100644 --- a/src/Symfony/Component/ExpressionLanguage/Tests/Node/ConstantNodeTest.php +++ b/src/Symfony/Component/ExpressionLanguage/Tests/Node/ConstantNodeTest.php @@ -47,8 +47,8 @@ public function getDumpData() array('false', new ConstantNode(false)), array('true', new ConstantNode(true)), array('null', new ConstantNode(null)), - array(3, new ConstantNode(3)), - array(3.3, new ConstantNode(3.3)), + array('3', new ConstantNode(3)), + array('3.3', new ConstantNode(3.3)), array('"foo"', new ConstantNode('foo')), array('{0: 1, "b": "a", 1: true}', new ConstantNode(array(1, 'b' => 'a', true))), array('{"a\\"b": "c", "a\\\\b": "d"}', new ConstantNode(array('a"b' => 'c', 'a\\b' => 'd'))), diff --git a/src/Symfony/Component/ExpressionLanguage/Tests/Node/GetAttrNodeTest.php b/src/Symfony/Component/ExpressionLanguage/Tests/Node/GetAttrNodeTest.php index f5cad1885708f..de7177a805d0b 100644 --- a/src/Symfony/Component/ExpressionLanguage/Tests/Node/GetAttrNodeTest.php +++ b/src/Symfony/Component/ExpressionLanguage/Tests/Node/GetAttrNodeTest.php @@ -24,9 +24,9 @@ public function getEvaluateData() array('b', new GetAttrNode(new NameNode('foo'), new ConstantNode(0), $this->getArrayNode(), GetAttrNode::ARRAY_CALL), array('foo' => array('b' => 'a', 'b'))), array('a', new GetAttrNode(new NameNode('foo'), new ConstantNode('b'), $this->getArrayNode(), GetAttrNode::ARRAY_CALL), array('foo' => array('b' => 'a', 'b'))), - array('bar', new GetAttrNode(new NameNode('foo'), new ConstantNode('foo'), $this->getArrayNode(), GetAttrNode::PROPERTY_CALL), array('foo' => new Obj())), + array('bar', new GetAttrNode(new NameNode('foo'), new NameNode('foo'), $this->getArrayNode(), GetAttrNode::PROPERTY_CALL), array('foo' => new Obj())), - array('baz', new GetAttrNode(new NameNode('foo'), new ConstantNode('foo'), $this->getArrayNode(), GetAttrNode::METHOD_CALL), array('foo' => new Obj())), + array('baz', new GetAttrNode(new NameNode('foo'), new NameNode('foo'), $this->getArrayNode(), GetAttrNode::METHOD_CALL), array('foo' => new Obj())), array('a', new GetAttrNode(new NameNode('foo'), new NameNode('index'), $this->getArrayNode(), GetAttrNode::ARRAY_CALL), array('foo' => array('b' => 'a', 'b'), 'index' => 'b')), ); } @@ -37,9 +37,9 @@ public function getCompileData() array('$foo[0]', new GetAttrNode(new NameNode('foo'), new ConstantNode(0), $this->getArrayNode(), GetAttrNode::ARRAY_CALL)), array('$foo["b"]', new GetAttrNode(new NameNode('foo'), new ConstantNode('b'), $this->getArrayNode(), GetAttrNode::ARRAY_CALL)), - array('$foo->foo', new GetAttrNode(new NameNode('foo'), new ConstantNode('foo'), $this->getArrayNode(), GetAttrNode::PROPERTY_CALL), array('foo' => new Obj())), + array('$foo->foo', new GetAttrNode(new NameNode('foo'), new NameNode('foo'), $this->getArrayNode(), GetAttrNode::PROPERTY_CALL), array('foo' => new Obj())), - array('$foo->foo(array("b" => "a", 0 => "b"))', new GetAttrNode(new NameNode('foo'), new ConstantNode('foo'), $this->getArrayNode(), GetAttrNode::METHOD_CALL), array('foo' => new Obj())), + array('$foo->foo(array("b" => "a", 0 => "b"))', new GetAttrNode(new NameNode('foo'), new NameNode('foo'), $this->getArrayNode(), GetAttrNode::METHOD_CALL), array('foo' => new Obj())), array('$foo[$index]', new GetAttrNode(new NameNode('foo'), new NameNode('index'), $this->getArrayNode(), GetAttrNode::ARRAY_CALL)), ); } @@ -50,9 +50,9 @@ public function getDumpData() array('foo[0]', new GetAttrNode(new NameNode('foo'), new ConstantNode(0), $this->getArrayNode(), GetAttrNode::ARRAY_CALL)), array('foo["b"]', new GetAttrNode(new NameNode('foo'), new ConstantNode('b'), $this->getArrayNode(), GetAttrNode::ARRAY_CALL)), - array('foo.foo', new GetAttrNode(new NameNode('foo'), new ConstantNode('foo'), $this->getArrayNode(), GetAttrNode::PROPERTY_CALL), array('foo' => new Obj())), + array('foo.foo', new GetAttrNode(new NameNode('foo'), new NameNode('foo'), $this->getArrayNode(), GetAttrNode::PROPERTY_CALL), array('foo' => new Obj())), - array('foo.foo({"b": "a", 0: "b"})', new GetAttrNode(new NameNode('foo'), new ConstantNode('foo'), $this->getArrayNode(), GetAttrNode::METHOD_CALL), array('foo' => new Obj())), + array('foo.foo({"b": "a", 0: "b"})', new GetAttrNode(new NameNode('foo'), new NameNode('foo'), $this->getArrayNode(), GetAttrNode::METHOD_CALL), array('foo' => new Obj())), array('foo[index]', new GetAttrNode(new NameNode('foo'), new NameNode('index'), $this->getArrayNode(), GetAttrNode::ARRAY_CALL)), ); } diff --git a/src/Symfony/Component/ExpressionLanguage/Tests/ParserTest.php b/src/Symfony/Component/ExpressionLanguage/Tests/ParserTest.php index dd850dd360033..8f5a3ce11fb41 100644 --- a/src/Symfony/Component/ExpressionLanguage/Tests/ParserTest.php +++ b/src/Symfony/Component/ExpressionLanguage/Tests/ParserTest.php @@ -98,24 +98,24 @@ public function getParseData() '(3 - 3) * 2', ), array( - new Node\GetAttrNode(new Node\NameNode('foo'), new Node\ConstantNode('bar'), new Node\ArgumentsNode(), Node\GetAttrNode::PROPERTY_CALL), + new Node\GetAttrNode(new Node\NameNode('foo'), new Node\NameNode('bar'), new Node\ArgumentsNode(), Node\GetAttrNode::PROPERTY_CALL), 'foo.bar', array('foo'), ), array( - new Node\GetAttrNode(new Node\NameNode('foo'), new Node\ConstantNode('bar'), new Node\ArgumentsNode(), Node\GetAttrNode::METHOD_CALL), + new Node\GetAttrNode(new Node\NameNode('foo'), new Node\NameNode('bar'), new Node\ArgumentsNode(), Node\GetAttrNode::METHOD_CALL), 'foo.bar()', array('foo'), ), array( - new Node\GetAttrNode(new Node\NameNode('foo'), new Node\ConstantNode('not'), new Node\ArgumentsNode(), Node\GetAttrNode::METHOD_CALL), + new Node\GetAttrNode(new Node\NameNode('foo'), new Node\NameNode('not'), new Node\ArgumentsNode(), Node\GetAttrNode::METHOD_CALL), 'foo.not()', array('foo'), ), array( new Node\GetAttrNode( new Node\NameNode('foo'), - new Node\ConstantNode('bar'), + new Node\NameNode('bar'), $arguments, Node\GetAttrNode::METHOD_CALL ), @@ -159,7 +159,9 @@ public function getParseData() private function createGetAttrNode($node, $item, $type) { - return new Node\GetAttrNode($node, new Node\ConstantNode($item), new Node\ArgumentsNode(), $type); + $attr = Node\GetAttrNode::ARRAY_CALL === $type ? new Node\ConstantNode($item) : new Node\NameNode($item); + + return new Node\GetAttrNode($node, $attr, new Node\ArgumentsNode(), $type); } /**