From 87af6e5ae357f14c2735f2e021d2e48adb1a02a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Thu, 9 Jun 2016 19:18:52 +0200 Subject: [PATCH] [ExpressionLanguage] Added a way to dump AST --- .../ExpressionLanguage/Node/ArgumentsNode.php | 11 +++++ .../ExpressionLanguage/Node/ArrayNode.php | 30 ++++++++++++ .../ExpressionLanguage/Node/BinaryNode.php | 5 ++ .../Node/ConditionalNode.php | 5 ++ .../ExpressionLanguage/Node/ConstantNode.php | 48 ++++++++++++++++++ .../ExpressionLanguage/Node/FunctionNode.php | 13 +++++ .../ExpressionLanguage/Node/GetAttrNode.php | 14 ++++++ .../ExpressionLanguage/Node/NameNode.php | 5 ++ .../ExpressionLanguage/Node/Node.php | 23 +++++++++ .../ExpressionLanguage/Node/UnaryNode.php | 5 ++ .../ExpressionLanguage/ParsedExpression.php | 5 ++ .../Tests/Node/AbstractNodeTest.php | 10 ++++ .../Tests/Node/ArgumentsNodeTest.php | 7 +++ .../Tests/Node/ArrayNodeTest.php | 15 ++++++ .../Tests/Node/BinaryNodeTest.php | 49 +++++++++++++++++++ .../Tests/Node/ConditionalNodeTest.php | 8 +++ .../Tests/Node/ConstantNodeTest.php | 16 ++++++ .../Tests/Node/FunctionNodeTest.php | 7 +++ .../Tests/Node/GetAttrNodeTest.php | 13 +++++ .../Tests/Node/NameNodeTest.php | 7 +++ .../Tests/Node/UnaryNodeTest.php | 10 ++++ 21 files changed, 306 insertions(+) diff --git a/src/Symfony/Component/ExpressionLanguage/Node/ArgumentsNode.php b/src/Symfony/Component/ExpressionLanguage/Node/ArgumentsNode.php index d97057934b..9c7b67a2a6 100644 --- a/src/Symfony/Component/ExpressionLanguage/Node/ArgumentsNode.php +++ b/src/Symfony/Component/ExpressionLanguage/Node/ArgumentsNode.php @@ -24,4 +24,15 @@ class ArgumentsNode extends ArrayNode { $this->compileArguments($compiler, false); } + + public function dump() + { + $str = ''; + + foreach ($this->getKeyValuePairs() as $pair) { + $str .= sprintf('%s, ', $pair['value']->dump()); + } + + return rtrim($str, ', '); + } } diff --git a/src/Symfony/Component/ExpressionLanguage/Node/ArrayNode.php b/src/Symfony/Component/ExpressionLanguage/Node/ArrayNode.php index 465527e5b7..15cc817dac 100644 --- a/src/Symfony/Component/ExpressionLanguage/Node/ArrayNode.php +++ b/src/Symfony/Component/ExpressionLanguage/Node/ArrayNode.php @@ -58,6 +58,36 @@ class ArrayNode extends Node return $result; } + public function dump() + { + $array = array(); + foreach ($this->getKeyValuePairs() as $pair) { + $array[$pair['key']->attributes['value']] = $pair['value']->dump(); + } + + 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); + } + } + + return rtrim($str, ', ').'}'; + } + + $str = '['; + + foreach ($array as $key => $value) { + $str .= sprintf('%s, ', $value); + } + + return rtrim($str, ', ').']'; + } + protected function getKeyValuePairs() { $pairs = array(); diff --git a/src/Symfony/Component/ExpressionLanguage/Node/BinaryNode.php b/src/Symfony/Component/ExpressionLanguage/Node/BinaryNode.php index 8cf1bc2e43..f28562b01a 100644 --- a/src/Symfony/Component/ExpressionLanguage/Node/BinaryNode.php +++ b/src/Symfony/Component/ExpressionLanguage/Node/BinaryNode.php @@ -154,4 +154,9 @@ class BinaryNode extends Node return preg_match($right, $left); } } + + public function dump() + { + return sprintf('(%s %s %s)', $this->nodes['left']->dump(), $this->attributes['operator'], $this->nodes['right']->dump()); + } } diff --git a/src/Symfony/Component/ExpressionLanguage/Node/ConditionalNode.php b/src/Symfony/Component/ExpressionLanguage/Node/ConditionalNode.php index 5d326faeb1..cbe235f250 100644 --- a/src/Symfony/Component/ExpressionLanguage/Node/ConditionalNode.php +++ b/src/Symfony/Component/ExpressionLanguage/Node/ConditionalNode.php @@ -48,4 +48,9 @@ class ConditionalNode extends Node return $this->nodes['expr3']->evaluate($functions, $values); } + + public function dump() + { + return sprintf('(%s ? %s : %s)', $this->nodes['expr1']->dump(), $this->nodes['expr2']->dump(), $this->nodes['expr3']->dump()); + } } diff --git a/src/Symfony/Component/ExpressionLanguage/Node/ConstantNode.php b/src/Symfony/Component/ExpressionLanguage/Node/ConstantNode.php index 9369d859fc..2017f6f31d 100644 --- a/src/Symfony/Component/ExpressionLanguage/Node/ConstantNode.php +++ b/src/Symfony/Component/ExpressionLanguage/Node/ConstantNode.php @@ -37,4 +37,52 @@ class ConstantNode extends Node { return $this->attributes['value']; } + + 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)); + } + } } diff --git a/src/Symfony/Component/ExpressionLanguage/Node/FunctionNode.php b/src/Symfony/Component/ExpressionLanguage/Node/FunctionNode.php index ab23acb94c..9cb0b01f56 100644 --- a/src/Symfony/Component/ExpressionLanguage/Node/FunctionNode.php +++ b/src/Symfony/Component/ExpressionLanguage/Node/FunctionNode.php @@ -49,4 +49,17 @@ class FunctionNode extends Node return call_user_func_array($functions[$this->attributes['name']]['evaluator'], $arguments); } + + public function dump() + { + $str = $this->attributes['name']; + + $str .= '('; + + foreach ($this->nodes['arguments']->nodes as $node) { + $str .= $node->dump().', '; + } + + return rtrim($str, ', ').')'; + } } diff --git a/src/Symfony/Component/ExpressionLanguage/Node/GetAttrNode.php b/src/Symfony/Component/ExpressionLanguage/Node/GetAttrNode.php index ae22deb8a0..1cf88c0ee1 100644 --- a/src/Symfony/Component/ExpressionLanguage/Node/GetAttrNode.php +++ b/src/Symfony/Component/ExpressionLanguage/Node/GetAttrNode.php @@ -94,4 +94,18 @@ class GetAttrNode extends Node return $array[$this->nodes['attribute']->evaluate($functions, $values)]; } } + + public function dump() + { + switch ($this->attributes['type']) { + case self::PROPERTY_CALL: + return sprintf('%s.%s', $this->nodes['node']->dump(), trim($this->nodes['attribute']->dump(), '"')); + + case self::METHOD_CALL: + return sprintf('%s.%s(%s)', $this->nodes['node']->dump(), trim($this->nodes['attribute']->dump(), '"'), $this->nodes['arguments']->dump()); + + case self::ARRAY_CALL: + return sprintf('%s[%s]', $this->nodes['node']->dump(), $this->nodes['attribute']->dump()); + } + } } diff --git a/src/Symfony/Component/ExpressionLanguage/Node/NameNode.php b/src/Symfony/Component/ExpressionLanguage/Node/NameNode.php index 30336ba75a..4e03df931a 100644 --- a/src/Symfony/Component/ExpressionLanguage/Node/NameNode.php +++ b/src/Symfony/Component/ExpressionLanguage/Node/NameNode.php @@ -37,4 +37,9 @@ class NameNode extends Node { return $values[$this->attributes['name']]; } + + public function dump() + { + return $this->attributes['name']; + } } diff --git a/src/Symfony/Component/ExpressionLanguage/Node/Node.php b/src/Symfony/Component/ExpressionLanguage/Node/Node.php index da49d6b4b2..6bce170bab 100644 --- a/src/Symfony/Component/ExpressionLanguage/Node/Node.php +++ b/src/Symfony/Component/ExpressionLanguage/Node/Node.php @@ -75,4 +75,27 @@ class Node return $results; } + + public function dump() + { + throw new \BadMethodCallException(sprintf('Dumping a "%s" instance is not supported yet.', get_class($this))); + } + + protected function dumpEscaped($value) + { + return str_replace(array('\\', '"'), array('\\\\', '\"'), $value); + } + + protected function isHash(array $value) + { + $expectedKey = 0; + + foreach ($value as $key => $val) { + if ($key !== $expectedKey++) { + return true; + } + } + + return false; + } } diff --git a/src/Symfony/Component/ExpressionLanguage/Node/UnaryNode.php b/src/Symfony/Component/ExpressionLanguage/Node/UnaryNode.php index 68fec552a0..acafb33582 100644 --- a/src/Symfony/Component/ExpressionLanguage/Node/UnaryNode.php +++ b/src/Symfony/Component/ExpressionLanguage/Node/UnaryNode.php @@ -58,4 +58,9 @@ class UnaryNode extends Node return $value; } + + public function dump() + { + return sprintf('(%s %s)', $this->attributes['operator'], $this->nodes['node']->dump()); + } } diff --git a/src/Symfony/Component/ExpressionLanguage/ParsedExpression.php b/src/Symfony/Component/ExpressionLanguage/ParsedExpression.php index 61bf5807c4..7aaeb246ba 100644 --- a/src/Symfony/Component/ExpressionLanguage/ParsedExpression.php +++ b/src/Symfony/Component/ExpressionLanguage/ParsedExpression.php @@ -39,4 +39,9 @@ class ParsedExpression extends Expression { return $this->nodes; } + + public function dump() + { + return $this->nodes->dump(); + } } diff --git a/src/Symfony/Component/ExpressionLanguage/Tests/Node/AbstractNodeTest.php b/src/Symfony/Component/ExpressionLanguage/Tests/Node/AbstractNodeTest.php index 58b0e177e8..4ddd10a159 100644 --- a/src/Symfony/Component/ExpressionLanguage/Tests/Node/AbstractNodeTest.php +++ b/src/Symfony/Component/ExpressionLanguage/Tests/Node/AbstractNodeTest.php @@ -36,4 +36,14 @@ abstract class AbstractNodeTest extends \PHPUnit_Framework_TestCase } abstract public function getCompileData(); + + /** + * @dataProvider getDumpData + */ + public function testDump($expected, $node) + { + $this->assertSame($expected, $node->dump()); + } + + abstract public function getDumpData(); } diff --git a/src/Symfony/Component/ExpressionLanguage/Tests/Node/ArgumentsNodeTest.php b/src/Symfony/Component/ExpressionLanguage/Tests/Node/ArgumentsNodeTest.php index 27e72dfc41..60a6d1ca2a 100644 --- a/src/Symfony/Component/ExpressionLanguage/Tests/Node/ArgumentsNodeTest.php +++ b/src/Symfony/Component/ExpressionLanguage/Tests/Node/ArgumentsNodeTest.php @@ -22,6 +22,13 @@ class ArgumentsNodeTest extends ArrayNodeTest ); } + public function getDumpData() + { + return array( + array('"a", "b"', $this->getArrayNode()), + ); + } + protected function createArrayNode() { return new ArgumentsNode(); diff --git a/src/Symfony/Component/ExpressionLanguage/Tests/Node/ArrayNodeTest.php b/src/Symfony/Component/ExpressionLanguage/Tests/Node/ArrayNodeTest.php index f2342f2850..11a35d461c 100644 --- a/src/Symfony/Component/ExpressionLanguage/Tests/Node/ArrayNodeTest.php +++ b/src/Symfony/Component/ExpressionLanguage/Tests/Node/ArrayNodeTest.php @@ -42,6 +42,21 @@ class ArrayNodeTest extends AbstractNodeTest ); } + public function getDumpData() + { + yield array('{"b": "a", 0: "b"}', $this->getArrayNode()); + + $array = $this->createArrayNode(); + $array->addElement(new ConstantNode('c'), new ConstantNode('a"b')); + $array->addElement(new ConstantNode('d'), new ConstantNode('a\b')); + yield array('{"a\\"b": "c", "a\\\\b": "d"}', $array); + + $array = $this->createArrayNode(); + $array->addElement(new ConstantNode('c')); + $array->addElement(new ConstantNode('d')); + yield array('["c", "d"]', $array); + } + protected function getArrayNode() { $array = $this->createArrayNode(); diff --git a/src/Symfony/Component/ExpressionLanguage/Tests/Node/BinaryNodeTest.php b/src/Symfony/Component/ExpressionLanguage/Tests/Node/BinaryNodeTest.php index 97ac480244..258d276b53 100644 --- a/src/Symfony/Component/ExpressionLanguage/Tests/Node/BinaryNodeTest.php +++ b/src/Symfony/Component/ExpressionLanguage/Tests/Node/BinaryNodeTest.php @@ -114,4 +114,53 @@ class BinaryNodeTest extends AbstractNodeTest array('preg_match("/^[a-z]+/i\$/", "abc")', new BinaryNode('matches', new ConstantNode('abc'), new ConstantNode('/^[a-z]+/i$/'))), ); } + + public function getDumpData() + { + $array = new ArrayNode(); + $array->addElement(new ConstantNode('a')); + $array->addElement(new ConstantNode('b')); + + return array( + array('(true or false)', new BinaryNode('or', new ConstantNode(true), new ConstantNode(false))), + array('(true || false)', new BinaryNode('||', new ConstantNode(true), new ConstantNode(false))), + array('(true and false)', new BinaryNode('and', new ConstantNode(true), new ConstantNode(false))), + array('(true && false)', new BinaryNode('&&', new ConstantNode(true), new ConstantNode(false))), + + array('(2 & 4)', new BinaryNode('&', new ConstantNode(2), new ConstantNode(4))), + array('(2 | 4)', new BinaryNode('|', new ConstantNode(2), new ConstantNode(4))), + array('(2 ^ 4)', new BinaryNode('^', new ConstantNode(2), new ConstantNode(4))), + + array('(1 < 2)', new BinaryNode('<', new ConstantNode(1), new ConstantNode(2))), + array('(1 <= 2)', new BinaryNode('<=', new ConstantNode(1), new ConstantNode(2))), + array('(1 <= 1)', new BinaryNode('<=', new ConstantNode(1), new ConstantNode(1))), + + array('(1 > 2)', new BinaryNode('>', new ConstantNode(1), new ConstantNode(2))), + array('(1 >= 2)', new BinaryNode('>=', new ConstantNode(1), new ConstantNode(2))), + array('(1 >= 1)', new BinaryNode('>=', new ConstantNode(1), new ConstantNode(1))), + + array('(true === true)', new BinaryNode('===', new ConstantNode(true), new ConstantNode(true))), + array('(true !== true)', new BinaryNode('!==', new ConstantNode(true), new ConstantNode(true))), + + array('(2 == 1)', new BinaryNode('==', new ConstantNode(2), new ConstantNode(1))), + array('(2 != 1)', new BinaryNode('!=', new ConstantNode(2), new ConstantNode(1))), + + array('(1 - 2)', new BinaryNode('-', new ConstantNode(1), new ConstantNode(2))), + array('(1 + 2)', new BinaryNode('+', new ConstantNode(1), new ConstantNode(2))), + array('(2 * 2)', new BinaryNode('*', new ConstantNode(2), new ConstantNode(2))), + array('(2 / 2)', new BinaryNode('/', new ConstantNode(2), new ConstantNode(2))), + array('(5 % 2)', new BinaryNode('%', new ConstantNode(5), new ConstantNode(2))), + array('(5 ** 2)', new BinaryNode('**', new ConstantNode(5), new ConstantNode(2))), + array('("a" ~ "b")', new BinaryNode('~', new ConstantNode('a'), new ConstantNode('b'))), + + array('("a" in ["a", "b"])', new BinaryNode('in', new ConstantNode('a'), $array)), + array('("c" in ["a", "b"])', new BinaryNode('in', new ConstantNode('c'), $array)), + array('("c" not in ["a", "b"])', new BinaryNode('not in', new ConstantNode('c'), $array)), + array('("a" not in ["a", "b"])', new BinaryNode('not in', new ConstantNode('a'), $array)), + + array('(1 .. 3)', new BinaryNode('..', new ConstantNode(1), new ConstantNode(3))), + + array('("abc" matches "/^[a-z]+/i$/")', new BinaryNode('matches', new ConstantNode('abc'), new ConstantNode('/^[a-z]+/i$/'))), + ); + } } diff --git a/src/Symfony/Component/ExpressionLanguage/Tests/Node/ConditionalNodeTest.php b/src/Symfony/Component/ExpressionLanguage/Tests/Node/ConditionalNodeTest.php index 9b9f7a2724..cbf9e8d43c 100644 --- a/src/Symfony/Component/ExpressionLanguage/Tests/Node/ConditionalNodeTest.php +++ b/src/Symfony/Component/ExpressionLanguage/Tests/Node/ConditionalNodeTest.php @@ -31,4 +31,12 @@ class ConditionalNodeTest extends AbstractNodeTest array('((false) ? (1) : (2))', new ConditionalNode(new ConstantNode(false), new ConstantNode(1), new ConstantNode(2))), ); } + + public function getDumpData() + { + return array( + array('(true ? 1 : 2)', new ConditionalNode(new ConstantNode(true), new ConstantNode(1), new ConstantNode(2))), + array('(false ? 1 : 2)', new ConditionalNode(new ConstantNode(false), new ConstantNode(1), new ConstantNode(2))), + ); + } } diff --git a/src/Symfony/Component/ExpressionLanguage/Tests/Node/ConstantNodeTest.php b/src/Symfony/Component/ExpressionLanguage/Tests/Node/ConstantNodeTest.php index c1a67a8603..d5f5f5ca74 100644 --- a/src/Symfony/Component/ExpressionLanguage/Tests/Node/ConstantNodeTest.php +++ b/src/Symfony/Component/ExpressionLanguage/Tests/Node/ConstantNodeTest.php @@ -40,4 +40,20 @@ class ConstantNodeTest extends AbstractNodeTest array('array(0 => 1, "b" => "a")', new ConstantNode(array(1, 'b' => 'a'))), ); } + + public function getDumpData() + { + return array( + 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('"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'))), + array('["c", "d"]', new ConstantNode(array('c', 'd'))), + array('{"a": ["b"]}', new ConstantNode(array('a' => array('b')))), + ); + } } diff --git a/src/Symfony/Component/ExpressionLanguage/Tests/Node/FunctionNodeTest.php b/src/Symfony/Component/ExpressionLanguage/Tests/Node/FunctionNodeTest.php index ecdc3d6371..8d6f92a9c7 100644 --- a/src/Symfony/Component/ExpressionLanguage/Tests/Node/FunctionNodeTest.php +++ b/src/Symfony/Component/ExpressionLanguage/Tests/Node/FunctionNodeTest.php @@ -31,6 +31,13 @@ class FunctionNodeTest extends AbstractNodeTest ); } + public function getDumpData() + { + return array( + array('foo("bar")', new FunctionNode('foo', new Node(array(new ConstantNode('bar')))), array('foo' => $this->getCallables())), + ); + } + protected function getCallables() { return array( diff --git a/src/Symfony/Component/ExpressionLanguage/Tests/Node/GetAttrNodeTest.php b/src/Symfony/Component/ExpressionLanguage/Tests/Node/GetAttrNodeTest.php index 57bd165075..f5cad18857 100644 --- a/src/Symfony/Component/ExpressionLanguage/Tests/Node/GetAttrNodeTest.php +++ b/src/Symfony/Component/ExpressionLanguage/Tests/Node/GetAttrNodeTest.php @@ -44,6 +44,19 @@ class GetAttrNodeTest extends AbstractNodeTest ); } + public function getDumpData() + { + return array( + 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({"b": "a", 0: "b"})', new GetAttrNode(new NameNode('foo'), new ConstantNode('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)), + ); + } + protected function getArrayNode() { $array = new ArrayNode(); diff --git a/src/Symfony/Component/ExpressionLanguage/Tests/Node/NameNodeTest.php b/src/Symfony/Component/ExpressionLanguage/Tests/Node/NameNodeTest.php index b645a6bfff..5fa2c37f29 100644 --- a/src/Symfony/Component/ExpressionLanguage/Tests/Node/NameNodeTest.php +++ b/src/Symfony/Component/ExpressionLanguage/Tests/Node/NameNodeTest.php @@ -28,4 +28,11 @@ class NameNodeTest extends AbstractNodeTest array('$foo', new NameNode('foo')), ); } + + public function getDumpData() + { + return array( + array('foo', new NameNode('foo')), + ); + } } diff --git a/src/Symfony/Component/ExpressionLanguage/Tests/Node/UnaryNodeTest.php b/src/Symfony/Component/ExpressionLanguage/Tests/Node/UnaryNodeTest.php index 6e6f117fda..ae2e3eee76 100644 --- a/src/Symfony/Component/ExpressionLanguage/Tests/Node/UnaryNodeTest.php +++ b/src/Symfony/Component/ExpressionLanguage/Tests/Node/UnaryNodeTest.php @@ -35,4 +35,14 @@ class UnaryNodeTest extends AbstractNodeTest array('(!true)', new UnaryNode('not', new ConstantNode(true))), ); } + + public function getDumpData() + { + return array( + array('(- 1)', new UnaryNode('-', new ConstantNode(1))), + array('(+ 3)', new UnaryNode('+', new ConstantNode(3))), + array('(! true)', new UnaryNode('!', new ConstantNode(true))), + array('(not true)', new UnaryNode('not', new ConstantNode(true))), + ); + } }