[ExpressionLanguage] Added a way to dump AST

This commit is contained in:
Grégoire Pineau 2016-06-09 19:18:52 +02:00
parent e7077605bb
commit 87af6e5ae3
21 changed files with 306 additions and 0 deletions

View File

@ -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, ', ');
}
}

View File

@ -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();

View File

@ -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());
}
}

View File

@ -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());
}
}

View File

@ -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));
}
}
}

View File

@ -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, ', ').')';
}
}

View File

@ -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());
}
}
}

View File

@ -37,4 +37,9 @@ class NameNode extends Node
{
return $values[$this->attributes['name']];
}
public function dump()
{
return $this->attributes['name'];
}
}

View File

@ -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;
}
}

View File

@ -58,4 +58,9 @@ class UnaryNode extends Node
return $value;
}
public function dump()
{
return sprintf('(%s %s)', $this->attributes['operator'], $this->nodes['node']->dump());
}
}

View File

@ -39,4 +39,9 @@ class ParsedExpression extends Expression
{
return $this->nodes;
}
public function dump()
{
return $this->nodes->dump();
}
}

View File

@ -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();
}

View File

@ -22,6 +22,13 @@ class ArgumentsNodeTest extends ArrayNodeTest
);
}
public function getDumpData()
{
return array(
array('"a", "b"', $this->getArrayNode()),
);
}
protected function createArrayNode()
{
return new ArgumentsNode();

View File

@ -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();

View File

@ -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$/'))),
);
}
}

View File

@ -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))),
);
}
}

View File

@ -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')))),
);
}
}

View File

@ -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(

View File

@ -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();

View File

@ -28,4 +28,11 @@ class NameNodeTest extends AbstractNodeTest
array('$foo', new NameNode('foo')),
);
}
public function getDumpData()
{
return array(
array('foo', new NameNode('foo')),
);
}
}

View File

@ -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))),
);
}
}