[ExpressionLanguage] Add a way to hook on each node when dumping the AST

This commit is contained in:
Nicolas Grekas 2016-06-15 11:25:34 +02:00
parent f400f01ecc
commit 66d23dbef6
16 changed files with 115 additions and 108 deletions

View File

@ -25,14 +25,16 @@ class ArgumentsNode extends ArrayNode
$this->compileArguments($compiler, false); $this->compileArguments($compiler, false);
} }
public function dump() public function toArray()
{ {
$str = ''; $array = array();
foreach ($this->getKeyValuePairs() as $pair) { foreach ($this->getKeyValuePairs() as $pair) {
$str .= sprintf('%s, ', $pair['value']->dump()); $array[] = $pair['value'];
$array[] = ', ';
} }
array_pop($array);
return rtrim($str, ', '); return $array;
} }
} }

View File

@ -58,34 +58,34 @@ class ArrayNode extends Node
return $result; return $result;
} }
public function dump() public function toArray()
{ {
$array = array(); $value = array();
foreach ($this->getKeyValuePairs() as $pair) { foreach ($this->getKeyValuePairs() as $pair) {
$array[$pair['key']->attributes['value']] = $pair['value']->dump(); $value[$pair['key']->attributes['value']] = $pair['value'];
} }
if ($this->isHash($array)) { $array = array();
$str = '{';
foreach ($array as $key => $value) { if ($this->isHash($value)) {
if (is_int($key)) { foreach ($value as $k => $v) {
$str .= sprintf('%s: %s, ', $key, $value); $array[] = ', ';
} else { $array[] = new ConstantNode($k);
$str .= sprintf('"%s": %s, ', $this->dumpEscaped($key), $value); $array[] = ': ';
} $array[] = $v;
} }
$array[0] = '{';
return rtrim($str, ', ').'}'; $array[] = '}';
} else {
foreach ($value as $v) {
$array[] = ', ';
$array[] = $v;
}
$array[0] = '[';
$array[] = ']';
} }
$str = '['; return $array;
foreach ($array as $key => $value) {
$str .= sprintf('%s, ', $value);
}
return rtrim($str, ', ').']';
} }
protected function getKeyValuePairs() protected function getKeyValuePairs()

View File

@ -155,8 +155,8 @@ class BinaryNode extends Node
} }
} }
public function dump() public function toArray()
{ {
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'], ')');
} }
} }

View File

@ -49,8 +49,8 @@ class ConditionalNode extends Node
return $this->nodes['expr3']->evaluate($functions, $values); return $this->nodes['expr3']->evaluate($functions, $values);
} }
public function dump() public function toArray()
{ {
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'], ')');
} }
} }

View File

@ -38,51 +38,39 @@ class ConstantNode extends Node
return $this->attributes['value']; return $this->attributes['value'];
} }
public function dump() public function toArray()
{ {
return $this->dumpValue($this->attributes['value']); $array = array();
} $value = $this->attributes['value'];
private function dumpValue($value) if (true === $value) {
{ $array[] = 'true';
switch (true) { } elseif (false === $value) {
case true === $value: $array[] = 'false';
return 'true'; } elseif (null === $value) {
$array[] = 'null';
case false === $value: } elseif (is_numeric($value)) {
return 'false'; $array[] = $value;
} elseif (!is_array($value)) {
case null === $value: $array[] = $this->dumpString($value);
return 'null'; } elseif ($this->isHash($value)) {
foreach ($value as $k => $v) {
case is_numeric($value): $array[] = ', ';
return $value; $array[] = new self($k);
$array[] = ': ';
case is_array($value): $array[] = new self($v);
if ($this->isHash($value)) { }
$str = '{'; $array[0] = '{';
$array[] = '}';
foreach ($value as $key => $v) { } else {
if (is_int($key)) { foreach ($value as $v) {
$str .= sprintf('%s: %s, ', $key, $this->dumpValue($v)); $array[] = ', ';
} else { $array[] = new self($v);
$str .= sprintf('"%s": %s, ', $this->dumpEscaped($key), $this->dumpValue($v)); }
} $array[0] = '[';
} $array[] = ']';
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));
} }
return $array;
} }
} }

View File

@ -50,16 +50,18 @@ class FunctionNode extends Node
return call_user_func_array($functions[$this->attributes['name']]['evaluator'], $arguments); return call_user_func_array($functions[$this->attributes['name']]['evaluator'], $arguments);
} }
public function dump() public function toArray()
{ {
$str = $this->attributes['name']; $array = array();
$array[] = $this->attributes['name'];
$str .= '(';
foreach ($this->nodes['arguments']->nodes as $node) { foreach ($this->nodes['arguments']->nodes as $node) {
$str .= $node->dump().', '; $array[] = ', ';
$array[] = $node;
} }
$array[1] = '(';
$array[] = ')';
return rtrim($str, ', ').')'; return $array;
} }
} }

View File

@ -39,7 +39,7 @@ class GetAttrNode extends Node
$compiler $compiler
->compile($this->nodes['node']) ->compile($this->nodes['node'])
->raw('->') ->raw('->')
->raw($this->nodes['attribute']->attributes['value']) ->raw($this->nodes['attribute']->attributes['name'])
; ;
break; break;
@ -47,7 +47,7 @@ class GetAttrNode extends Node
$compiler $compiler
->compile($this->nodes['node']) ->compile($this->nodes['node'])
->raw('->') ->raw('->')
->raw($this->nodes['attribute']->attributes['value']) ->raw($this->nodes['attribute']->attributes['name'])
->raw('(') ->raw('(')
->compile($this->nodes['arguments']) ->compile($this->nodes['arguments'])
->raw(')') ->raw(')')
@ -73,7 +73,7 @@ class GetAttrNode extends Node
throw new \RuntimeException('Unable to get a property on a non-object.'); 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; return $obj->$property;
@ -83,7 +83,7 @@ class GetAttrNode extends Node
throw new \RuntimeException('Unable to get a property on a non-object.'); 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: case self::ARRAY_CALL:
$array = $this->nodes['node']->evaluate($functions, $values); $array = $this->nodes['node']->evaluate($functions, $values);
@ -95,17 +95,17 @@ class GetAttrNode extends Node
} }
} }
public function dump() public function toArray()
{ {
switch ($this->attributes['type']) { switch ($this->attributes['type']) {
case self::PROPERTY_CALL: 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: 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: case self::ARRAY_CALL:
return sprintf('%s[%s]', $this->nodes['node']->dump(), $this->nodes['attribute']->dump()); return array($this->nodes['node'], '[', $this->nodes['attribute'], ']');
} }
} }
} }

View File

@ -38,8 +38,8 @@ class NameNode extends Node
return $values[$this->attributes['name']]; return $values[$this->attributes['name']];
} }
public function dump() public function toArray()
{ {
return $this->attributes['name']; return array($this->attributes['name']);
} }
} }

View File

@ -76,14 +76,14 @@ class Node
return $results; return $results;
} }
public function dump() public function toArray()
{ {
throw new \BadMethodCallException(sprintf('Dumping a "%s" instance is not supported yet.', get_class($this))); 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) protected function isHash(array $value)

View File

@ -59,8 +59,8 @@ class UnaryNode extends Node
return $value; return $value;
} }
public function dump() public function toArray()
{ {
return sprintf('(%s %s)', $this->attributes['operator'], $this->nodes['node']->dump()); return array('(', $this->attributes['operator'].' ', $this->nodes['node'], ')');
} }
} }

View File

@ -42,6 +42,17 @@ class ParsedExpression extends Expression
public function dump() public function dump()
{ {
return $this->nodes->dump(); return $this->dumpNode($this->nodes);
}
private function dumpNode(Node $node)
{
$dump = '';
foreach($node->toArray() as $v) {
$dump .= is_scalar($v) ? $v : $this->dumpNode($v);
}
return $dump;
} }
} }

View File

@ -330,7 +330,7 @@ class Parser
throw new SyntaxError('Expected name', $token->cursor); throw new SyntaxError('Expected name', $token->cursor);
} }
$arg = new Node\ConstantNode($token->value); $arg = new Node\NameNode($token->value);
$arguments = new Node\ArgumentsNode(); $arguments = new Node\ArgumentsNode();
if ($this->stream->current->test(Token::PUNCTUATION_TYPE, '(')) { if ($this->stream->current->test(Token::PUNCTUATION_TYPE, '(')) {

View File

@ -12,6 +12,7 @@
namespace Symfony\Component\ExpressionLanguage\Tests\Node; namespace Symfony\Component\ExpressionLanguage\Tests\Node;
use Symfony\Component\ExpressionLanguage\Compiler; use Symfony\Component\ExpressionLanguage\Compiler;
use Symfony\Component\ExpressionLanguage\ParsedExpression;
abstract class AbstractNodeTest extends \PHPUnit_Framework_TestCase abstract class AbstractNodeTest extends \PHPUnit_Framework_TestCase
{ {
@ -42,7 +43,8 @@ abstract class AbstractNodeTest extends \PHPUnit_Framework_TestCase
*/ */
public function testDump($expected, $node) public function testDump($expected, $node)
{ {
$this->assertSame($expected, $node->dump()); $expr = new ParsedExpression($expected, $node);
$this->assertSame($expected, $expr->dump());
} }
abstract public function getDumpData(); abstract public function getDumpData();

View File

@ -47,8 +47,8 @@ class ConstantNodeTest extends AbstractNodeTest
array('false', new ConstantNode(false)), array('false', new ConstantNode(false)),
array('true', new ConstantNode(true)), array('true', new ConstantNode(true)),
array('null', new ConstantNode(null)), array('null', new ConstantNode(null)),
array(3, new ConstantNode(3)), array('3', new ConstantNode(3)),
array(3.3, new ConstantNode(3.3)), array('3.3', new ConstantNode(3.3)),
array('"foo"', new ConstantNode('foo')), array('"foo"', new ConstantNode('foo')),
array('{0: 1, "b": "a", 1: true}', new ConstantNode(array(1, 'b' => 'a', true))), 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('{"a\\"b": "c", "a\\\\b": "d"}', new ConstantNode(array('a"b' => 'c', 'a\\b' => 'd'))),

View File

@ -24,9 +24,9 @@ class GetAttrNodeTest extends AbstractNodeTest
array('b', new GetAttrNode(new NameNode('foo'), new ConstantNode(0), $this->getArrayNode(), GetAttrNode::ARRAY_CALL), array('foo' => array('b' => 'a', 'b'))), 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('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')), 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 @@ class GetAttrNodeTest extends AbstractNodeTest
array('$foo[0]', new GetAttrNode(new NameNode('foo'), new ConstantNode(0), $this->getArrayNode(), GetAttrNode::ARRAY_CALL)), 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["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)), array('$foo[$index]', new GetAttrNode(new NameNode('foo'), new NameNode('index'), $this->getArrayNode(), GetAttrNode::ARRAY_CALL)),
); );
} }
@ -50,9 +50,9 @@ class GetAttrNodeTest extends AbstractNodeTest
array('foo[0]', new GetAttrNode(new NameNode('foo'), new ConstantNode(0), $this->getArrayNode(), GetAttrNode::ARRAY_CALL)), 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["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)), array('foo[index]', new GetAttrNode(new NameNode('foo'), new NameNode('index'), $this->getArrayNode(), GetAttrNode::ARRAY_CALL)),
); );
} }

View File

@ -98,24 +98,24 @@ class ParserTest extends \PHPUnit_Framework_TestCase
'(3 - 3) * 2', '(3 - 3) * 2',
), ),
array( 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', 'foo.bar',
array('foo'), array('foo'),
), ),
array( 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()', 'foo.bar()',
array('foo'), array('foo'),
), ),
array( 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()', 'foo.not()',
array('foo'), array('foo'),
), ),
array( array(
new Node\GetAttrNode( new Node\GetAttrNode(
new Node\NameNode('foo'), new Node\NameNode('foo'),
new Node\ConstantNode('bar'), new Node\NameNode('bar'),
$arguments, $arguments,
Node\GetAttrNode::METHOD_CALL Node\GetAttrNode::METHOD_CALL
), ),
@ -159,7 +159,9 @@ class ParserTest extends \PHPUnit_Framework_TestCase
private function createGetAttrNode($node, $item, $type) 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);
} }
/** /**