feature #19060 [ExpressionLanguage] Add a way to hook on each node when dumping the AST (nicolas-grekas)

This PR was merged into the 3.2-dev branch.

Discussion
----------

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

| Q             | A
| ------------- | ---
| Branch?       | "master
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #19013
| License       | MIT
| Doc PR        | -

This is an iteration over #19013 to allow writing dumpers that can decorate dumps (e.g. add HTML tags based on each node type to do syntax highlighting).

Commits
-------

66d23db [ExpressionLanguage] Add a way to hook on each node when dumping the AST
This commit is contained in:
Fabien Potencier 2016-06-15 18:22:57 +02:00
commit e9e0975f22
16 changed files with 115 additions and 108 deletions

View File

@ -25,14 +25,16 @@ class ArgumentsNode extends ArrayNode
$this->compileArguments($compiler, false);
}
public function dump()
public function toArray()
{
$str = '';
$array = array();
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;
}
public function dump()
public function toArray()
{
$array = array();
$value = array();
foreach ($this->getKeyValuePairs() as $pair) {
$array[$pair['key']->attributes['value']] = $pair['value']->dump();
$value[$pair['key']->attributes['value']] = $pair['value'];
}
if ($this->isHash($array)) {
$str = '{';
$array = array();
foreach ($array as $key => $value) {
if (is_int($key)) {
$str .= sprintf('%s: %s, ', $key, $value);
} else {
$str .= sprintf('"%s": %s, ', $this->dumpEscaped($key), $value);
}
if ($this->isHash($value)) {
foreach ($value as $k => $v) {
$array[] = ', ';
$array[] = new ConstantNode($k);
$array[] = ': ';
$array[] = $v;
}
return rtrim($str, ', ').'}';
$array[0] = '{';
$array[] = '}';
} else {
foreach ($value as $v) {
$array[] = ', ';
$array[] = $v;
}
$array[0] = '[';
$array[] = ']';
}
$str = '[';
foreach ($array as $key => $value) {
$str .= sprintf('%s, ', $value);
}
return rtrim($str, ', ').']';
return $array;
}
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);
}
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'];
}
public function dump()
public function toArray()
{
return $this->dumpValue($this->attributes['value']);
}
$array = array();
$value = $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));
if (true === $value) {
$array[] = 'true';
} elseif (false === $value) {
$array[] = 'false';
} elseif (null === $value) {
$array[] = 'null';
} elseif (is_numeric($value)) {
$array[] = $value;
} elseif (!is_array($value)) {
$array[] = $this->dumpString($value);
} elseif ($this->isHash($value)) {
foreach ($value as $k => $v) {
$array[] = ', ';
$array[] = new self($k);
$array[] = ': ';
$array[] = new self($v);
}
$array[0] = '{';
$array[] = '}';
} else {
foreach ($value as $v) {
$array[] = ', ';
$array[] = new self($v);
}
$array[0] = '[';
$array[] = ']';
}
return $array;
}
}

View File

@ -50,16 +50,18 @@ class FunctionNode extends Node
return call_user_func_array($functions[$this->attributes['name']]['evaluator'], $arguments);
}
public function dump()
public function toArray()
{
$str = $this->attributes['name'];
$str .= '(';
$array = array();
$array[] = $this->attributes['name'];
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
->compile($this->nodes['node'])
->raw('->')
->raw($this->nodes['attribute']->attributes['value'])
->raw($this->nodes['attribute']->attributes['name'])
;
break;
@ -47,7 +47,7 @@ class GetAttrNode extends Node
$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 @@ class GetAttrNode extends Node
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 @@ class GetAttrNode extends Node
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);
@ -95,17 +95,17 @@ class GetAttrNode extends Node
}
}
public function dump()
public function toArray()
{
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'], ']');
}
}
}

View File

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

View File

@ -59,8 +59,8 @@ class UnaryNode extends Node
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()
{
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);
}
$arg = new Node\ConstantNode($token->value);
$arg = new Node\NameNode($token->value);
$arguments = new Node\ArgumentsNode();
if ($this->stream->current->test(Token::PUNCTUATION_TYPE, '(')) {

View File

@ -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 class AbstractNodeTest extends \PHPUnit_Framework_TestCase
*/
public function testDump($expected, $node)
{
$this->assertSame($expected, $node->dump());
$expr = new ParsedExpression($expected, $node);
$this->assertSame($expected, $expr->dump());
}
abstract public function getDumpData();

View File

@ -47,8 +47,8 @@ class ConstantNodeTest extends AbstractNodeTest
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'))),

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('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 @@ class GetAttrNodeTest extends AbstractNodeTest
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 @@ class GetAttrNodeTest extends AbstractNodeTest
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)),
);
}

View File

@ -98,24 +98,24 @@ class ParserTest extends \PHPUnit_Framework_TestCase
'(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 @@ class ParserTest extends \PHPUnit_Framework_TestCase
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);
}
/**