[ExpressionLanguage] added the component
This commit is contained in:
parent
091a96ca3d
commit
9d98fa25ec
|
@ -33,6 +33,7 @@
|
||||||
"symfony/doctrine-bridge": "self.version",
|
"symfony/doctrine-bridge": "self.version",
|
||||||
"symfony/dom-crawler": "self.version",
|
"symfony/dom-crawler": "self.version",
|
||||||
"symfony/event-dispatcher": "self.version",
|
"symfony/event-dispatcher": "self.version",
|
||||||
|
"symfony/expression-language": "self.version",
|
||||||
"symfony/filesystem": "self.version",
|
"symfony/filesystem": "self.version",
|
||||||
"symfony/finder": "self.version",
|
"symfony/finder": "self.version",
|
||||||
"symfony/form": "self.version",
|
"symfony/form": "self.version",
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
vendor/
|
||||||
|
composer.lock
|
||||||
|
phpunit.xml
|
|
@ -0,0 +1,7 @@
|
||||||
|
CHANGELOG
|
||||||
|
=========
|
||||||
|
|
||||||
|
2.4.0
|
||||||
|
-----
|
||||||
|
|
||||||
|
* added the component
|
|
@ -0,0 +1,148 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\ExpressionLanguage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compiles a node to PHP code.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
class Compiler
|
||||||
|
{
|
||||||
|
private $source;
|
||||||
|
private $functions;
|
||||||
|
|
||||||
|
public function __construct(array $functions)
|
||||||
|
{
|
||||||
|
$this->functions = $functions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFunction($name)
|
||||||
|
{
|
||||||
|
return $this->functions[$name];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current PHP code after compilation.
|
||||||
|
*
|
||||||
|
* @return string The PHP code
|
||||||
|
*/
|
||||||
|
public function getSource()
|
||||||
|
{
|
||||||
|
return $this->source;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function reset()
|
||||||
|
{
|
||||||
|
$this->source = '';
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compiles a node.
|
||||||
|
*
|
||||||
|
* @param Node\Node $node The node to compile
|
||||||
|
*
|
||||||
|
* @return Compiler The current compiler instance
|
||||||
|
*/
|
||||||
|
public function compile(Node\Node $node)
|
||||||
|
{
|
||||||
|
$node->compile($this);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function subcompile(Node\Node $node)
|
||||||
|
{
|
||||||
|
$current = $this->source;
|
||||||
|
$this->source = '';
|
||||||
|
|
||||||
|
$node->compile($this);
|
||||||
|
|
||||||
|
$source = $this->source;
|
||||||
|
$this->source = $current;
|
||||||
|
|
||||||
|
return $source;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a raw string to the compiled code.
|
||||||
|
*
|
||||||
|
* @param string $string The string
|
||||||
|
*
|
||||||
|
* @return Compiler The current compiler instance
|
||||||
|
*/
|
||||||
|
public function raw($string)
|
||||||
|
{
|
||||||
|
$this->source .= $string;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a quoted string to the compiled code.
|
||||||
|
*
|
||||||
|
* @param string $value The string
|
||||||
|
*
|
||||||
|
* @return Compiler The current compiler instance
|
||||||
|
*/
|
||||||
|
public function string($value)
|
||||||
|
{
|
||||||
|
$this->source .= sprintf('"%s"', addcslashes($value, "\0\t\"\$\\"));
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a PHP representation of a given value.
|
||||||
|
*
|
||||||
|
* @param mixed $value The value to convert
|
||||||
|
*
|
||||||
|
* @return Compiler The current compiler instance
|
||||||
|
*/
|
||||||
|
public function repr($value)
|
||||||
|
{
|
||||||
|
if (is_int($value) || is_float($value)) {
|
||||||
|
if (false !== $locale = setlocale(LC_NUMERIC, 0)) {
|
||||||
|
setlocale(LC_NUMERIC, 'C');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->raw($value);
|
||||||
|
|
||||||
|
if (false !== $locale) {
|
||||||
|
setlocale(LC_NUMERIC, $locale);
|
||||||
|
}
|
||||||
|
} elseif (null === $value) {
|
||||||
|
$this->raw('null');
|
||||||
|
} elseif (is_bool($value)) {
|
||||||
|
$this->raw($value ? 'true' : 'false');
|
||||||
|
} elseif (is_array($value)) {
|
||||||
|
$this->raw('array(');
|
||||||
|
$first = true;
|
||||||
|
foreach ($value as $key => $value) {
|
||||||
|
if (!$first) {
|
||||||
|
$this->raw(', ');
|
||||||
|
}
|
||||||
|
$first = false;
|
||||||
|
$this->repr($key);
|
||||||
|
$this->raw(' => ');
|
||||||
|
$this->repr($value);
|
||||||
|
}
|
||||||
|
$this->raw(')');
|
||||||
|
} else {
|
||||||
|
$this->string($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\ExpressionLanguage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents an expression.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
class Expression
|
||||||
|
{
|
||||||
|
private $expression;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param string $expression An expression
|
||||||
|
*/
|
||||||
|
public function __construct($expression)
|
||||||
|
{
|
||||||
|
$this->expression = (string) $expression;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the expression.
|
||||||
|
*
|
||||||
|
* @return string The expression
|
||||||
|
*/
|
||||||
|
public function __toString()
|
||||||
|
{
|
||||||
|
return $this->expression;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,103 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\ExpressionLanguage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows to compile and evaluate expressions written in your own DSL.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
class ExpressionLanguage
|
||||||
|
{
|
||||||
|
private $lexer;
|
||||||
|
private $parser;
|
||||||
|
private $compiler;
|
||||||
|
private $cache;
|
||||||
|
|
||||||
|
protected $functions;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->functions = array();
|
||||||
|
$this->registerFunctions();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compiles an expression source code.
|
||||||
|
*
|
||||||
|
* @param string $expression The expression to compile
|
||||||
|
* @param array $names An array of valid names
|
||||||
|
*
|
||||||
|
* @return string The compiled PHP source code
|
||||||
|
*/
|
||||||
|
public function compile($expression, $names = array())
|
||||||
|
{
|
||||||
|
return $this->getCompiler()->compile($this->parse($expression, $names))->getSource();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function evaluate($expression, $values = array())
|
||||||
|
{
|
||||||
|
return $this->parse($expression, array_keys($values))->evaluate($this->functions, $values);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addFunction($name, $compiler, $evaluator)
|
||||||
|
{
|
||||||
|
$this->functions[$name] = array('compiler' => $compiler, 'evaluator' => $evaluator);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function registerFunctions()
|
||||||
|
{
|
||||||
|
$this->addFunction('constant', function ($constant) {
|
||||||
|
return sprintf('constant(%s)', $constant);
|
||||||
|
}, function (array $values, $constant) {
|
||||||
|
return constant($constant);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getLexer()
|
||||||
|
{
|
||||||
|
if (null === $this->lexer) {
|
||||||
|
$this->lexer = new Lexer();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->lexer;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getParser()
|
||||||
|
{
|
||||||
|
if (null === $this->parser) {
|
||||||
|
$this->parser = new Parser($this->functions);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->parser;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getCompiler()
|
||||||
|
{
|
||||||
|
if (null === $this->compiler) {
|
||||||
|
$this->compiler = new Compiler($this->functions);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->compiler->reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function parse($expression, $names)
|
||||||
|
{
|
||||||
|
$key = $expression.'//'.implode('-', $names);
|
||||||
|
|
||||||
|
if (!isset($this->cache[$key])) {
|
||||||
|
$this->cache[$key] = $this->getParser()->parse($this->getLexer()->tokenize((string) $expression), $names);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->cache[$key];
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
Copyright (c) 2004-2013 Fabien Potencier
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is furnished
|
||||||
|
to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
|
@ -0,0 +1,119 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\ExpressionLanguage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lexes an expression.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
class Lexer
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Tokenizes an expression.
|
||||||
|
*
|
||||||
|
* @param string $expression The expression to tokenize
|
||||||
|
*
|
||||||
|
* @return TokenStream A token stream instance
|
||||||
|
*/
|
||||||
|
public function tokenize($expression)
|
||||||
|
{
|
||||||
|
$expression = str_replace(array("\r\n", "\r"), "\n", $expression);
|
||||||
|
$cursor = 0;
|
||||||
|
$tokens = array();
|
||||||
|
$brackets = array();
|
||||||
|
$operatorRegex = $this->getOperatorRegex();
|
||||||
|
$end = strlen($expression);
|
||||||
|
|
||||||
|
while ($cursor < $end) {
|
||||||
|
if (preg_match('/\s+/A', $expression, $match, null, $cursor)) {
|
||||||
|
// whitespace
|
||||||
|
$cursor += strlen($match[0]);
|
||||||
|
} elseif (preg_match($operatorRegex, $expression, $match, null, $cursor)) {
|
||||||
|
// operators
|
||||||
|
$tokens[] = new Token(Token::OPERATOR_TYPE, $match[0], $cursor + 1);
|
||||||
|
$cursor += strlen($match[0]);
|
||||||
|
} elseif (preg_match('/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/A', $expression, $match, null, $cursor)) {
|
||||||
|
// names
|
||||||
|
$tokens[] = new Token(Token::NAME_TYPE, $match[0], $cursor + 1);
|
||||||
|
$cursor += strlen($match[0]);
|
||||||
|
} elseif (preg_match('/[0-9]+(?:\.[0-9]+)?/A', $expression, $match, null, $cursor)) {
|
||||||
|
// numbers
|
||||||
|
$number = (float) $match[0]; // floats
|
||||||
|
if (ctype_digit($match[0]) && $number <= PHP_INT_MAX) {
|
||||||
|
$number = (int) $match[0]; // integers lower than the maximum
|
||||||
|
}
|
||||||
|
$tokens[] = new Token(Token::NUMBER_TYPE, $number, $cursor + 1);
|
||||||
|
$cursor += strlen($match[0]);
|
||||||
|
} elseif (false !== strpos('([{', $expression[$cursor])) {
|
||||||
|
// opening bracket
|
||||||
|
$brackets[] = array($expression[$cursor], $cursor);
|
||||||
|
|
||||||
|
$tokens[] = new Token(Token::PUNCTUATION_TYPE, $expression[$cursor], $cursor + 1);
|
||||||
|
++$cursor;
|
||||||
|
} elseif (false !== strpos(')]}', $expression[$cursor])) {
|
||||||
|
// closing bracket
|
||||||
|
if (empty($brackets)) {
|
||||||
|
throw new SyntaxError(sprintf('Unexpected "%s"', $expression[$cursor]), $cursor);
|
||||||
|
}
|
||||||
|
|
||||||
|
list($expect, $cur) = array_pop($brackets);
|
||||||
|
if ($expression[$cursor] != strtr($expect, '([{', ')]}')) {
|
||||||
|
throw new SyntaxError(sprintf('Unclosed "%s"', $expect), $cur);
|
||||||
|
}
|
||||||
|
|
||||||
|
$tokens[] = new Token(Token::PUNCTUATION_TYPE, $expression[$cursor], $cursor + 1);
|
||||||
|
++$cursor;
|
||||||
|
} elseif (false !== strpos('.,?:', $expression[$cursor])) {
|
||||||
|
// punctuation
|
||||||
|
$tokens[] = new Token(Token::PUNCTUATION_TYPE, $expression[$cursor], $cursor + 1);
|
||||||
|
++$cursor;
|
||||||
|
} elseif (preg_match('/"([^#"\\\\]*(?:\\\\.[^#"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\'/As', $expression, $match, null, $cursor)) {
|
||||||
|
// strings
|
||||||
|
$tokens[] = new Token(Token::STRING_TYPE, stripcslashes(substr($match[0], 1, -1)), $cursor + 1);
|
||||||
|
$cursor += strlen($match[0]);
|
||||||
|
} else {
|
||||||
|
// unlexable
|
||||||
|
throw new SyntaxError(sprintf('Unexpected character "%s"', $expression[$cursor]), $cursor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$tokens[] = new Token(Token::EOF_TYPE, null, $cursor + 1);
|
||||||
|
|
||||||
|
if (!empty($brackets)) {
|
||||||
|
list($expect, $cur) = array_pop($brackets);
|
||||||
|
throw new SyntaxError(sprintf('Unclosed "%s"', $expect), $cur);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new TokenStream($tokens);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getOperatorRegex()
|
||||||
|
{
|
||||||
|
$operators = array(
|
||||||
|
'not', '!', '-', '+',
|
||||||
|
'or', '||', '&&', 'and', '|', '^', '&', '==', '===', '!=', '!==', '<', '>', '>=', '<=', 'not in', 'in', '..', '+', '-', '~', '*', '/', '%', '**',
|
||||||
|
);
|
||||||
|
|
||||||
|
$operators = array_combine($operators, array_map('strlen', $operators));
|
||||||
|
arsort($operators);
|
||||||
|
|
||||||
|
$regex = array();
|
||||||
|
foreach ($operators as $operator => $length) {
|
||||||
|
// an operator that ends with a character must be followed by
|
||||||
|
// a whitespace or a parenthesis
|
||||||
|
$regex[] = preg_quote($operator, '/').(ctype_alpha($operator[$length - 1]) ? '(?=[\s()])' : '');
|
||||||
|
}
|
||||||
|
|
||||||
|
return '/'.implode('|', $regex).'/A';
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\ExpressionLanguage\Node;
|
||||||
|
|
||||||
|
use Symfony\Component\ExpressionLanguage\Compiler;
|
||||||
|
|
||||||
|
class ArgumentsNode extends ArrayNode
|
||||||
|
{
|
||||||
|
public function compile(Compiler $compiler)
|
||||||
|
{
|
||||||
|
$this->compileArguments($compiler, false);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\ExpressionLanguage\Node;
|
||||||
|
|
||||||
|
use Symfony\Component\ExpressionLanguage\Compiler;
|
||||||
|
|
||||||
|
class ArrayNode extends Node
|
||||||
|
{
|
||||||
|
protected $index;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->index = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addElement(Node $value, Node $key = null)
|
||||||
|
{
|
||||||
|
if (null === $key) {
|
||||||
|
$key = new ConstantNode(++$this->index);
|
||||||
|
}
|
||||||
|
|
||||||
|
array_push($this->nodes, $key, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compiles the node to PHP.
|
||||||
|
*
|
||||||
|
* @param Compiler A Compiler instance
|
||||||
|
*/
|
||||||
|
public function compile(Compiler $compiler)
|
||||||
|
{
|
||||||
|
$compiler->raw('array(');
|
||||||
|
$this->compileArguments($compiler);
|
||||||
|
$compiler->raw(')');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function evaluate($functions, $values)
|
||||||
|
{
|
||||||
|
$result = array();
|
||||||
|
foreach ($this->getKeyValuePairs() as $pair) {
|
||||||
|
$result[$pair['key']->evaluate($functions, $values)] = $pair['value']->evaluate($functions, $values);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getKeyValuePairs()
|
||||||
|
{
|
||||||
|
$pairs = array();
|
||||||
|
foreach (array_chunk($this->nodes, 2) as $pair) {
|
||||||
|
$pairs[] = array('key' => $pair[0], 'value' => $pair[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $pairs;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function compileArguments(Compiler $compiler, $withKeys = true)
|
||||||
|
{
|
||||||
|
$first = true;
|
||||||
|
foreach ($this->getKeyValuePairs() as $pair) {
|
||||||
|
if (!$first) {
|
||||||
|
$compiler->raw(', ');
|
||||||
|
}
|
||||||
|
$first = false;
|
||||||
|
|
||||||
|
if ($withKeys) {
|
||||||
|
$compiler
|
||||||
|
->compile($pair['key'])
|
||||||
|
->raw(' => ')
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
$compiler->compile($pair['value']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,129 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\ExpressionLanguage\Node;
|
||||||
|
|
||||||
|
use Symfony\Component\ExpressionLanguage\Compiler;
|
||||||
|
|
||||||
|
class BinaryNode extends Node
|
||||||
|
{
|
||||||
|
private $operators = array(
|
||||||
|
'~' => '.',
|
||||||
|
'and' => '&&',
|
||||||
|
'or' => '||',
|
||||||
|
);
|
||||||
|
|
||||||
|
private $functions = array(
|
||||||
|
'**' => 'pow',
|
||||||
|
'..' => 'range',
|
||||||
|
'in' => 'in_array',
|
||||||
|
'not in' => '!in_array',
|
||||||
|
);
|
||||||
|
|
||||||
|
public function __construct($operator, Node $left, Node $right)
|
||||||
|
{
|
||||||
|
$this->nodes = array('left' => $left, 'right' => $right);
|
||||||
|
$this->attributes = array('operator' => $operator);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function compile(Compiler $compiler)
|
||||||
|
{
|
||||||
|
$operator = $this->attributes['operator'];
|
||||||
|
|
||||||
|
if (isset($this->functions[$operator])) {
|
||||||
|
$compiler
|
||||||
|
->raw(sprintf('%s(', $this->functions[$operator]))
|
||||||
|
->compile($this->nodes['left'])
|
||||||
|
->raw(', ')
|
||||||
|
->compile($this->nodes['right'])
|
||||||
|
->raw(')')
|
||||||
|
;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($this->operators[$operator])) {
|
||||||
|
$operator = $this->operators[$operator];
|
||||||
|
}
|
||||||
|
|
||||||
|
$compiler
|
||||||
|
->raw('(')
|
||||||
|
->compile($this->nodes['left'])
|
||||||
|
->raw(' ')
|
||||||
|
->raw($operator)
|
||||||
|
->raw(' ')
|
||||||
|
->compile($this->nodes['right'])
|
||||||
|
->raw(')')
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function evaluate($functions, $values)
|
||||||
|
{
|
||||||
|
$operator = $this->attributes['operator'];
|
||||||
|
$left = $this->nodes['left']->evaluate($functions, $values);
|
||||||
|
$right = $this->nodes['right']->evaluate($functions, $values);
|
||||||
|
|
||||||
|
if (isset($this->functions[$operator])) {
|
||||||
|
if ('not in' == $operator) {
|
||||||
|
return !call_user_func('in_array', $left, $right);
|
||||||
|
}
|
||||||
|
|
||||||
|
return call_user_func($this->functions[$operator], $left, $right);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($operator) {
|
||||||
|
case 'or':
|
||||||
|
case '||':
|
||||||
|
return $left || $right;
|
||||||
|
case 'and':
|
||||||
|
case '&&':
|
||||||
|
return $left && $right;
|
||||||
|
case '|':
|
||||||
|
return $left | $right;
|
||||||
|
case '^':
|
||||||
|
return $left ^ $right;
|
||||||
|
case '&':
|
||||||
|
return $left & $right;
|
||||||
|
case '==':
|
||||||
|
return $left == $right;
|
||||||
|
case '===':
|
||||||
|
return $left === $right;
|
||||||
|
case '!=':
|
||||||
|
return $left != $right;
|
||||||
|
case '!==':
|
||||||
|
return $left !== $right;
|
||||||
|
case '<':
|
||||||
|
return $left < $right;
|
||||||
|
case '>':
|
||||||
|
return $left > $right;
|
||||||
|
case '>=':
|
||||||
|
return $left >= $right;
|
||||||
|
case '<=':
|
||||||
|
return $left <= $right;
|
||||||
|
case 'not in':
|
||||||
|
return !in_array($left, $right);
|
||||||
|
case 'in':
|
||||||
|
return in_array($left, $right);
|
||||||
|
case '+':
|
||||||
|
return $left + $right;
|
||||||
|
case '-':
|
||||||
|
return $left - $right;
|
||||||
|
case '~':
|
||||||
|
return $left.$right;
|
||||||
|
case '*':
|
||||||
|
return $left * $right;
|
||||||
|
case '/':
|
||||||
|
return $left / $right;
|
||||||
|
case '%':
|
||||||
|
return $left % $right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\ExpressionLanguage\Node;
|
||||||
|
|
||||||
|
use Symfony\Component\ExpressionLanguage\Compiler;
|
||||||
|
|
||||||
|
class ConditionalNode extends Node
|
||||||
|
{
|
||||||
|
public function __construct(Node $expr1, Node $expr2, Node $expr3)
|
||||||
|
{
|
||||||
|
$this->nodes = array('expr1' => $expr1, 'expr2' => $expr2, 'expr3' => $expr3);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function compile(Compiler $compiler)
|
||||||
|
{
|
||||||
|
$compiler
|
||||||
|
->raw('((')
|
||||||
|
->compile($this->nodes['expr1'])
|
||||||
|
->raw(') ? (')
|
||||||
|
->compile($this->nodes['expr2'])
|
||||||
|
->raw(') : (')
|
||||||
|
->compile($this->nodes['expr3'])
|
||||||
|
->raw('))')
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function evaluate($functions, $values)
|
||||||
|
{
|
||||||
|
if ($this->nodes['expr1']->evaluate($functions, $values)) {
|
||||||
|
return $this->nodes['expr2']->evaluate($functions, $values);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->nodes['expr3']->evaluate($functions, $values);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\ExpressionLanguage\Node;
|
||||||
|
|
||||||
|
use Symfony\Component\ExpressionLanguage\Compiler;
|
||||||
|
|
||||||
|
class ConstantNode extends Node
|
||||||
|
{
|
||||||
|
public function __construct($value)
|
||||||
|
{
|
||||||
|
$this->attributes = array('value' => $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function compile(Compiler $compiler)
|
||||||
|
{
|
||||||
|
$compiler->repr($this->attributes['value']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function evaluate($functions, $values)
|
||||||
|
{
|
||||||
|
return $this->attributes['value'];
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\ExpressionLanguage\Node;
|
||||||
|
|
||||||
|
use Symfony\Component\ExpressionLanguage\Compiler;
|
||||||
|
|
||||||
|
class FunctionNode extends Node
|
||||||
|
{
|
||||||
|
public function __construct($name, Node $arguments)
|
||||||
|
{
|
||||||
|
$this->nodes = array('arguments' => $arguments);
|
||||||
|
$this->attributes = array('name' => $name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function compile(Compiler $compiler)
|
||||||
|
{
|
||||||
|
$arguments = array();
|
||||||
|
foreach ($this->nodes['arguments']->nodes as $node) {
|
||||||
|
$arguments[] = $compiler->subcompile($node);
|
||||||
|
}
|
||||||
|
|
||||||
|
$function = $compiler->getFunction($this->attributes['name']);
|
||||||
|
|
||||||
|
$compiler->raw(call_user_func_array($function['compiler'], $arguments));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function evaluate($functions, $values)
|
||||||
|
{
|
||||||
|
$arguments = array($values);
|
||||||
|
foreach ($this->nodes['arguments']->nodes as $node) {
|
||||||
|
$arguments[] = $node->evaluate($functions, $values);
|
||||||
|
}
|
||||||
|
|
||||||
|
return call_user_func_array($functions[$this->attributes['name']]['evaluator'], $arguments);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,90 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\ExpressionLanguage\Node;
|
||||||
|
|
||||||
|
use Symfony\Component\ExpressionLanguage\Compiler;
|
||||||
|
|
||||||
|
class GetAttrNode extends Node
|
||||||
|
{
|
||||||
|
const PROPERTY_CALL = 1;
|
||||||
|
const METHOD_CALL = 2;
|
||||||
|
const ARRAY_CALL = 3;
|
||||||
|
|
||||||
|
public function __construct(Node $node, Node $attribute, ArrayNode $arguments, $type)
|
||||||
|
{
|
||||||
|
$this->nodes = array('node' => $node, 'attribute' => $attribute, 'arguments' => $arguments);
|
||||||
|
$this->attributes = array('type' => $type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function compile(Compiler $compiler)
|
||||||
|
{
|
||||||
|
switch ($this->attributes['type']) {
|
||||||
|
case self::PROPERTY_CALL:
|
||||||
|
$compiler
|
||||||
|
->compile($this->nodes['node'])
|
||||||
|
->raw('->')
|
||||||
|
->raw($this->nodes['attribute']->attributes['value'])
|
||||||
|
;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case self::METHOD_CALL:
|
||||||
|
$compiler
|
||||||
|
->compile($this->nodes['node'])
|
||||||
|
->raw('->')
|
||||||
|
->raw($this->nodes['attribute']->attributes['value'])
|
||||||
|
->raw('(')
|
||||||
|
->compile($this->nodes['arguments'])
|
||||||
|
->raw(')')
|
||||||
|
;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case self::ARRAY_CALL:
|
||||||
|
$compiler
|
||||||
|
->compile($this->nodes['node'])
|
||||||
|
->raw('[')
|
||||||
|
->compile($this->nodes['attribute'])->raw(']')
|
||||||
|
;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function evaluate($functions, $values)
|
||||||
|
{
|
||||||
|
switch ($this->attributes['type']) {
|
||||||
|
case self::PROPERTY_CALL:
|
||||||
|
$obj = $this->nodes['node']->evaluate($functions, $values);
|
||||||
|
if (!is_object($obj)) {
|
||||||
|
throw new \RuntimeException('Unable to get a property on a non-object.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$property = $this->nodes['attribute']->attributes['value'];
|
||||||
|
|
||||||
|
return $obj->$property;
|
||||||
|
|
||||||
|
case self::METHOD_CALL:
|
||||||
|
$obj = $this->nodes['node']->evaluate($functions, $values);
|
||||||
|
if (!is_object($obj)) {
|
||||||
|
throw new \RuntimeException('Unable to get a property on a non-object.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return call_user_func_array(array($obj, $this->nodes['attribute']->evaluate($functions, $values)), $this->nodes['arguments']->evaluate($functions, $values));
|
||||||
|
|
||||||
|
case self::ARRAY_CALL:
|
||||||
|
$values = $this->nodes['node']->evaluate($functions, $values);
|
||||||
|
if (!is_array($values) && !$values instanceof \ArrayAccess) {
|
||||||
|
throw new \RuntimeException('Unable to get an item on a non-array.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $values[$this->nodes['attribute']->evaluate($functions, $values)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\ExpressionLanguage\Node;
|
||||||
|
|
||||||
|
use Symfony\Component\ExpressionLanguage\Compiler;
|
||||||
|
|
||||||
|
class NameNode extends Node
|
||||||
|
{
|
||||||
|
public function __construct($name)
|
||||||
|
{
|
||||||
|
$this->attributes = array('name' => $name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function compile(Compiler $compiler)
|
||||||
|
{
|
||||||
|
$compiler->raw('$'.$this->attributes['name']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function evaluate($functions, $values)
|
||||||
|
{
|
||||||
|
return $values[$this->attributes['name']];
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\ExpressionLanguage\Node;
|
||||||
|
|
||||||
|
use Symfony\Component\ExpressionLanguage\Compiler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a node in the AST.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
class Node
|
||||||
|
{
|
||||||
|
public $nodes = array();
|
||||||
|
public $attributes = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param array $nodes An array of nodes
|
||||||
|
* @param array $attributes An array of attributes
|
||||||
|
*/
|
||||||
|
public function __construct(array $nodes = array(), array $attributes = array())
|
||||||
|
{
|
||||||
|
$this->nodes = $nodes;
|
||||||
|
$this->attributes = $attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __toString()
|
||||||
|
{
|
||||||
|
$attributes = array();
|
||||||
|
foreach ($this->attributes as $name => $value) {
|
||||||
|
$attributes[] = sprintf('%s: %s', $name, str_replace("\n", '', var_export($value, true)));
|
||||||
|
}
|
||||||
|
|
||||||
|
$repr = array(str_replace('Symfony\Component\ExpressionLanguage\Node\\', '', get_class($this)).'('.implode(', ', $attributes));
|
||||||
|
|
||||||
|
if (count($this->nodes)) {
|
||||||
|
foreach ($this->nodes as $node) {
|
||||||
|
foreach (explode("\n", (string) $node) as $line) {
|
||||||
|
$repr[] = ' '.$line;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$repr[] = ')';
|
||||||
|
} else {
|
||||||
|
$repr[0] .= ')';
|
||||||
|
}
|
||||||
|
|
||||||
|
return implode("\n", $repr);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function compile(Compiler $compiler)
|
||||||
|
{
|
||||||
|
foreach ($this->nodes as $node) {
|
||||||
|
$node->compile($compiler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function evaluate($functions, $values)
|
||||||
|
{
|
||||||
|
$results = array();
|
||||||
|
foreach ($this->nodes as $node) {
|
||||||
|
$results[] = $node->evaluate($functions, $values);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $results;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\ExpressionLanguage\Node;
|
||||||
|
|
||||||
|
use Symfony\Component\ExpressionLanguage\Compiler;
|
||||||
|
|
||||||
|
class UnaryNode extends Node
|
||||||
|
{
|
||||||
|
private $operators = array(
|
||||||
|
'!' => '!',
|
||||||
|
'not' => '!',
|
||||||
|
'+' => '+',
|
||||||
|
'-' => '-',
|
||||||
|
);
|
||||||
|
|
||||||
|
public function __construct($operator, Node $node)
|
||||||
|
{
|
||||||
|
$this->nodes = array('node' => $node);
|
||||||
|
$this->attributes = array('operator' => $operator);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function compile(Compiler $compiler)
|
||||||
|
{
|
||||||
|
$compiler
|
||||||
|
->raw('(')
|
||||||
|
->raw($this->operators[$this->attributes['operator']])
|
||||||
|
->compile($this->nodes['node'])
|
||||||
|
->raw(')')
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function evaluate($functions, $values)
|
||||||
|
{
|
||||||
|
$value = $this->nodes['node']->evaluate($functions, $values);
|
||||||
|
switch ($this->attributes['operator']) {
|
||||||
|
case 'not':
|
||||||
|
case '!':
|
||||||
|
return !$value;
|
||||||
|
case '-':
|
||||||
|
return -$value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,356 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\ExpressionLanguage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parsers a token stream.
|
||||||
|
*
|
||||||
|
* This parser implements a "Precedence climbing" algorithm.
|
||||||
|
*
|
||||||
|
* @see http://www.engr.mun.ca/~theo/Misc/exp_parsing.htm
|
||||||
|
* @see http://en.wikipedia.org/wiki/Operator-precedence_parser
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
class Parser
|
||||||
|
{
|
||||||
|
const OPERATOR_LEFT = 1;
|
||||||
|
const OPERATOR_RIGHT = 2;
|
||||||
|
|
||||||
|
private $stream;
|
||||||
|
private $unaryOperators;
|
||||||
|
private $binaryOperators;
|
||||||
|
private $functions;
|
||||||
|
private $names;
|
||||||
|
|
||||||
|
public function __construct(array $functions)
|
||||||
|
{
|
||||||
|
$this->functions = $functions;
|
||||||
|
|
||||||
|
$this->unaryOperators = array(
|
||||||
|
'not' => array('precedence' => 50),
|
||||||
|
'!' => array('precedence' => 50),
|
||||||
|
'-' => array('precedence' => 500),
|
||||||
|
'+' => array('precedence' => 500),
|
||||||
|
);
|
||||||
|
$this->binaryOperators = array(
|
||||||
|
'or' => array('precedence' => 10, 'associativity' => Parser::OPERATOR_LEFT),
|
||||||
|
'||' => array('precedence' => 10, 'associativity' => Parser::OPERATOR_LEFT),
|
||||||
|
'and' => array('precedence' => 15, 'associativity' => Parser::OPERATOR_LEFT),
|
||||||
|
'&&' => array('precedence' => 15, 'associativity' => Parser::OPERATOR_LEFT),
|
||||||
|
'|' => array('precedence' => 16, 'associativity' => Parser::OPERATOR_LEFT),
|
||||||
|
'^' => array('precedence' => 17, 'associativity' => Parser::OPERATOR_LEFT),
|
||||||
|
'&' => array('precedence' => 18, 'associativity' => Parser::OPERATOR_LEFT),
|
||||||
|
'==' => array('precedence' => 20, 'associativity' => Parser::OPERATOR_LEFT),
|
||||||
|
'===' => array('precedence' => 20, 'associativity' => Parser::OPERATOR_LEFT),
|
||||||
|
'!=' => array('precedence' => 20, 'associativity' => Parser::OPERATOR_LEFT),
|
||||||
|
'!==' => array('precedence' => 20, 'associativity' => Parser::OPERATOR_LEFT),
|
||||||
|
'<' => array('precedence' => 20, 'associativity' => Parser::OPERATOR_LEFT),
|
||||||
|
'>' => array('precedence' => 20, 'associativity' => Parser::OPERATOR_LEFT),
|
||||||
|
'>=' => array('precedence' => 20, 'associativity' => Parser::OPERATOR_LEFT),
|
||||||
|
'<=' => array('precedence' => 20, 'associativity' => Parser::OPERATOR_LEFT),
|
||||||
|
'not in' => array('precedence' => 20, 'associativity' => Parser::OPERATOR_LEFT),
|
||||||
|
'in' => array('precedence' => 20, 'associativity' => Parser::OPERATOR_LEFT),
|
||||||
|
'..' => array('precedence' => 25, 'associativity' => Parser::OPERATOR_LEFT),
|
||||||
|
'+' => array('precedence' => 30, 'associativity' => Parser::OPERATOR_LEFT),
|
||||||
|
'-' => array('precedence' => 30, 'associativity' => Parser::OPERATOR_LEFT),
|
||||||
|
'~' => array('precedence' => 40, 'associativity' => Parser::OPERATOR_LEFT),
|
||||||
|
'*' => array('precedence' => 60, 'associativity' => Parser::OPERATOR_LEFT),
|
||||||
|
'/' => array('precedence' => 60, 'associativity' => Parser::OPERATOR_LEFT),
|
||||||
|
'%' => array('precedence' => 60, 'associativity' => Parser::OPERATOR_LEFT),
|
||||||
|
'**' => array('precedence' => 200, 'associativity' => Parser::OPERATOR_RIGHT),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a token stream to a node tree.
|
||||||
|
*
|
||||||
|
* @param TokenStream $stream A token stream instance
|
||||||
|
* @param array $names An array of valid names
|
||||||
|
*
|
||||||
|
* @return Node A node tree
|
||||||
|
*/
|
||||||
|
public function parse(TokenStream $stream, $names = array())
|
||||||
|
{
|
||||||
|
$this->stream = $stream;
|
||||||
|
$this->names = $names;
|
||||||
|
|
||||||
|
$node = $this->parseExpression();
|
||||||
|
if (!$stream->isEOF()) {
|
||||||
|
throw new SyntaxError(sprintf('Unexpected token "%s" of value "%s"', $stream->current->type, $stream->current->value), $stream->current->cursor);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $node;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function parseExpression($precedence = 0)
|
||||||
|
{
|
||||||
|
$expr = $this->getPrimary();
|
||||||
|
$token = $this->stream->current;
|
||||||
|
while ($token->test(Token::OPERATOR_TYPE) && isset($this->binaryOperators[$token->value]) && $this->binaryOperators[$token->value]['precedence'] >= $precedence) {
|
||||||
|
$op = $this->binaryOperators[$token->value];
|
||||||
|
$this->stream->next();
|
||||||
|
|
||||||
|
$expr1 = $this->parseExpression(self::OPERATOR_LEFT === $op['associativity'] ? $op['precedence'] + 1 : $op['precedence']);
|
||||||
|
$expr = new Node\BinaryNode($token->value, $expr, $expr1);
|
||||||
|
|
||||||
|
$token = $this->stream->current;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (0 === $precedence) {
|
||||||
|
return $this->parseConditionalExpression($expr);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getPrimary()
|
||||||
|
{
|
||||||
|
$token = $this->stream->current;
|
||||||
|
|
||||||
|
if ($token->test(Token::OPERATOR_TYPE) && isset($this->unaryOperators[$token->value])) {
|
||||||
|
$operator = $this->unaryOperators[$token->value];
|
||||||
|
$this->stream->next();
|
||||||
|
$expr = $this->parseExpression($operator['precedence']);
|
||||||
|
|
||||||
|
return $this->parsePostfixExpression(new Node\UnaryNode($token->value, $expr));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($token->test(Token::PUNCTUATION_TYPE, '(')) {
|
||||||
|
$this->stream->next();
|
||||||
|
$expr = $this->parseExpression();
|
||||||
|
$this->stream->expect(Token::PUNCTUATION_TYPE, ')', 'An opened parenthesis is not properly closed');
|
||||||
|
|
||||||
|
return $this->parsePostfixExpression($expr);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->parsePrimaryExpression();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function parseConditionalExpression($expr)
|
||||||
|
{
|
||||||
|
while ($this->stream->current->test(Token::PUNCTUATION_TYPE, '?')) {
|
||||||
|
$this->stream->next();
|
||||||
|
if (!$this->stream->current->test(Token::PUNCTUATION_TYPE, ':')) {
|
||||||
|
$expr2 = $this->parseExpression();
|
||||||
|
if ($this->stream->current->test(Token::PUNCTUATION_TYPE, ':')) {
|
||||||
|
$this->stream->next();
|
||||||
|
$expr3 = $this->parseExpression();
|
||||||
|
} else {
|
||||||
|
$expr3 = new Node\ConstantNode(null);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->stream->next();
|
||||||
|
$expr2 = $expr;
|
||||||
|
$expr3 = $this->parseExpression();
|
||||||
|
}
|
||||||
|
|
||||||
|
$expr = new Node\ConditionalNode($expr, $expr2, $expr3);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function parsePrimaryExpression()
|
||||||
|
{
|
||||||
|
$token = $this->stream->current;
|
||||||
|
switch ($token->type) {
|
||||||
|
case Token::NAME_TYPE:
|
||||||
|
$this->stream->next();
|
||||||
|
switch ($token->value) {
|
||||||
|
case 'true':
|
||||||
|
case 'TRUE':
|
||||||
|
return new Node\ConstantNode(true);
|
||||||
|
|
||||||
|
case 'false':
|
||||||
|
case 'FALSE':
|
||||||
|
return new Node\ConstantNode(false);
|
||||||
|
|
||||||
|
case 'null':
|
||||||
|
case 'NULL':
|
||||||
|
return new Node\ConstantNode(null);
|
||||||
|
|
||||||
|
default:
|
||||||
|
if ('(' === $this->stream->current->value) {
|
||||||
|
if (false === isset($this->functions[$token->value])) {
|
||||||
|
throw new SyntaxError(sprintf('The function "%s" does not exist', $token->value), $token->cursor);
|
||||||
|
}
|
||||||
|
|
||||||
|
$node = new Node\FunctionNode($token->value, $this->parseArguments());
|
||||||
|
} else {
|
||||||
|
if (!in_array($token->value, $this->names)) {
|
||||||
|
throw new SyntaxError(sprintf('Variable "%s" is not valid', $token->value), $token->cursor);
|
||||||
|
}
|
||||||
|
|
||||||
|
$node = new Node\NameNode($token->value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Token::NUMBER_TYPE:
|
||||||
|
case Token::STRING_TYPE:
|
||||||
|
$this->stream->next();
|
||||||
|
return new Node\ConstantNode($token->value);
|
||||||
|
|
||||||
|
default:
|
||||||
|
if ($token->test(Token::PUNCTUATION_TYPE, '[')) {
|
||||||
|
$node = $this->parseArrayExpression();
|
||||||
|
} elseif ($token->test(Token::PUNCTUATION_TYPE, '{')) {
|
||||||
|
$node = $this->parseHashExpression();
|
||||||
|
} else {
|
||||||
|
throw new SyntaxError(sprintf('Unexpected token "%s" of value "%s"', $token->type, $token->value), $token->cursor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->parsePostfixExpression($node);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function parseArrayExpression()
|
||||||
|
{
|
||||||
|
$this->stream->expect(Token::PUNCTUATION_TYPE, '[', 'An array element was expected');
|
||||||
|
|
||||||
|
$node = new Node\ArrayNode();
|
||||||
|
$first = true;
|
||||||
|
while (!$this->stream->current->test(Token::PUNCTUATION_TYPE, ']')) {
|
||||||
|
if (!$first) {
|
||||||
|
$this->stream->expect(Token::PUNCTUATION_TYPE, ',', 'An array element must be followed by a comma');
|
||||||
|
|
||||||
|
// trailing ,?
|
||||||
|
if ($this->stream->current->test(Token::PUNCTUATION_TYPE, ']')) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$first = false;
|
||||||
|
|
||||||
|
$node->addElement($this->parseExpression());
|
||||||
|
}
|
||||||
|
$this->stream->expect(Token::PUNCTUATION_TYPE, ']', 'An opened array is not properly closed');
|
||||||
|
|
||||||
|
return $node;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function parseHashExpression()
|
||||||
|
{
|
||||||
|
$this->stream->expect(Token::PUNCTUATION_TYPE, '{', 'A hash element was expected');
|
||||||
|
|
||||||
|
$node = new Node\ArrayNode();
|
||||||
|
$first = true;
|
||||||
|
while (!$this->stream->current->test(Token::PUNCTUATION_TYPE, '}')) {
|
||||||
|
if (!$first) {
|
||||||
|
$this->stream->expect(Token::PUNCTUATION_TYPE, ',', 'A hash value must be followed by a comma');
|
||||||
|
|
||||||
|
// trailing ,?
|
||||||
|
if ($this->stream->current->test(Token::PUNCTUATION_TYPE, '}')) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$first = false;
|
||||||
|
|
||||||
|
// a hash key can be:
|
||||||
|
//
|
||||||
|
// * a number -- 12
|
||||||
|
// * a string -- 'a'
|
||||||
|
// * a name, which is equivalent to a string -- a
|
||||||
|
// * an expression, which must be enclosed in parentheses -- (1 + 2)
|
||||||
|
if ($this->stream->current->test(Token::STRING_TYPE) || $this->stream->current->test(Token::NAME_TYPE) || $this->stream->current->test(Token::NUMBER_TYPE)) {
|
||||||
|
$key = new Node\ConstantNode($this->stream->current->value);
|
||||||
|
$this->stream->next();
|
||||||
|
} elseif ($this->stream->current->test(Token::PUNCTUATION_TYPE, '(')) {
|
||||||
|
$key = $this->parseExpression();
|
||||||
|
} else {
|
||||||
|
$current = $this->stream->current;
|
||||||
|
|
||||||
|
throw new SyntaxError(sprintf('A hash key must be a quoted string, a number, a name, or an expression enclosed in parentheses (unexpected token "%s" of value "%s"', $current->type, $current->value), $current->cursor);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->stream->expect(Token::PUNCTUATION_TYPE, ':', 'A hash key must be followed by a colon (:)');
|
||||||
|
$value = $this->parseExpression();
|
||||||
|
|
||||||
|
$node->addElement($value, $key);
|
||||||
|
}
|
||||||
|
$this->stream->expect(Token::PUNCTUATION_TYPE, '}', 'An opened hash is not properly closed');
|
||||||
|
|
||||||
|
return $node;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function parsePostfixExpression($node)
|
||||||
|
{
|
||||||
|
$token = $this->stream->current;
|
||||||
|
while ($token->type == Token::PUNCTUATION_TYPE) {
|
||||||
|
if ('.' === $token->value) {
|
||||||
|
$this->stream->next();
|
||||||
|
$token = $this->stream->current;
|
||||||
|
$this->stream->next();
|
||||||
|
|
||||||
|
if (
|
||||||
|
$token->type !== Token::NAME_TYPE
|
||||||
|
&&
|
||||||
|
$token->type !== Token::NUMBER_TYPE
|
||||||
|
&&
|
||||||
|
// operators line "not" are valid method or property names
|
||||||
|
($token->type !== Token::OPERATOR_TYPE && preg_match('/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/A', $token->value))
|
||||||
|
) {
|
||||||
|
throw new SyntaxError('Expected name or number', $token->cursor);
|
||||||
|
}
|
||||||
|
|
||||||
|
$arg = new Node\ConstantNode($token->value);
|
||||||
|
|
||||||
|
$arguments = new Node\ArgumentsNode();
|
||||||
|
if ($this->stream->current->test(Token::PUNCTUATION_TYPE, '(')) {
|
||||||
|
$type = Node\GetAttrNode::METHOD_CALL;
|
||||||
|
foreach ($this->parseArguments()->nodes as $n) {
|
||||||
|
$arguments->addElement($n);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$type = Node\GetAttrNode::PROPERTY_CALL;
|
||||||
|
}
|
||||||
|
|
||||||
|
$node = new Node\GetAttrNode($node, $arg, $arguments, $type);
|
||||||
|
} elseif ('[' === $token->value) {
|
||||||
|
if ($node instanceof Node\GetAttrNode && Node\GetAttrNode::METHOD_CALL === $node->attributes['type'] && version_compare(PHP_VERSION, '5.4.0', '<')) {
|
||||||
|
throw new SyntaxError('Array calls on a method call is only supported on PHP 5.4+', $token->cursor);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->stream->next();
|
||||||
|
$arg = $this->parseExpression();
|
||||||
|
$this->stream->expect(Token::PUNCTUATION_TYPE, ']');
|
||||||
|
|
||||||
|
$node = new Node\GetAttrNode($node, $arg, new Node\ArgumentsNode(), Node\GetAttrNode::ARRAY_CALL);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$token = $this->stream->current;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $node;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses arguments.
|
||||||
|
*/
|
||||||
|
public function parseArguments()
|
||||||
|
{
|
||||||
|
$args = array();
|
||||||
|
$this->stream->expect(Token::PUNCTUATION_TYPE, '(', 'A list of arguments must begin with an opening parenthesis');
|
||||||
|
while (!$this->stream->current->test(Token::PUNCTUATION_TYPE, ')')) {
|
||||||
|
if (!empty($args)) {
|
||||||
|
$this->stream->expect(Token::PUNCTUATION_TYPE, ',', 'Arguments must be separated by a comma');
|
||||||
|
}
|
||||||
|
|
||||||
|
$args[] = $this->parseExpression();
|
||||||
|
}
|
||||||
|
$this->stream->expect(Token::PUNCTUATION_TYPE, ')', 'A list of arguments must be closed by a parenthesis');
|
||||||
|
|
||||||
|
return new Node\Node($args);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
ExpressionLanguage Component
|
||||||
|
============================
|
||||||
|
|
||||||
|
The ExpressionLanguage component provides an engine that can compile and
|
||||||
|
evaluate expressions:
|
||||||
|
|
||||||
|
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
|
||||||
|
|
||||||
|
$language = new ExpressionLanguage();
|
||||||
|
|
||||||
|
echo $language->evaluate('1 + foo', array('foo' => 2));
|
||||||
|
// would output 3
|
||||||
|
|
||||||
|
echo $language->compile('1 + foo');
|
||||||
|
// would output (1 + $foo)
|
||||||
|
|
||||||
|
By default, the engine implements simple math and logic functions, method
|
||||||
|
calls, property accesses, and array accesses.
|
||||||
|
|
||||||
|
You can extend your DSL with functions:
|
||||||
|
|
||||||
|
$compiler = function ($arg) {
|
||||||
|
return sprintf('strtoupper(%s)', $arg);
|
||||||
|
};
|
||||||
|
$evaluator = function (array $variables, $value) {
|
||||||
|
return strtoupper($value);
|
||||||
|
};
|
||||||
|
$language->addFunction('upper', $compiler, $evaluator);
|
||||||
|
|
||||||
|
echo $language->evaluate('"foo" ~ upper(foo)', array('foo' => 'bar'));
|
||||||
|
// would output fooBAR
|
||||||
|
|
||||||
|
echo $language->compile('"foo" ~ upper(foo)');
|
||||||
|
// would output ("foo" . strtoupper($foo))
|
||||||
|
|
||||||
|
Resources
|
||||||
|
---------
|
||||||
|
|
||||||
|
You can run the unit tests with the following command:
|
||||||
|
|
||||||
|
$ cd path/to/Symfony/Component/ExpressionLanguage/
|
||||||
|
$ composer.phar install --dev
|
||||||
|
$ phpunit
|
|
@ -0,0 +1,20 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\ExpressionLanguage;
|
||||||
|
|
||||||
|
class SyntaxError extends \LogicException
|
||||||
|
{
|
||||||
|
public function __construct($message, $cursor = 0)
|
||||||
|
{
|
||||||
|
parent::__construct(sprintf('%s around position %d.', $message, $cursor));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\ExpressionLanguage\Tests\Node;
|
||||||
|
|
||||||
|
use Symfony\Component\ExpressionLanguage\Lexer;
|
||||||
|
use Symfony\Component\ExpressionLanguage\Token;
|
||||||
|
use Symfony\Component\ExpressionLanguage\TokenStream;
|
||||||
|
|
||||||
|
class LexerTest extends \PHPUnit_Framework_TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @dataProvider getTokenizeData
|
||||||
|
*/
|
||||||
|
public function testTokenize($tokens, $expression)
|
||||||
|
{
|
||||||
|
$tokens[] = new Token('end of expression', null, strlen($expression) + 1);
|
||||||
|
$lexer = new Lexer();
|
||||||
|
$this->assertEquals(new TokenStream($tokens), $lexer->tokenize($expression));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTokenizeData()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
array(
|
||||||
|
array(new Token('name', 'a', 1)),
|
||||||
|
'a',
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
array(new Token('string', 'foo', 1)),
|
||||||
|
'"foo"',
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
array(new Token('number', '3', 1)),
|
||||||
|
'3',
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
array(new Token('operator', '+', 1)),
|
||||||
|
'+',
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
array(new Token('punctuation', '.', 1)),
|
||||||
|
'.',
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
array(
|
||||||
|
new Token('punctuation', '(', 1),
|
||||||
|
new Token('number', '3', 2),
|
||||||
|
new Token('operator', '+', 4),
|
||||||
|
new Token('number', '5', 6),
|
||||||
|
new Token('punctuation', ')', 7),
|
||||||
|
new Token('operator', '~', 9),
|
||||||
|
new Token('name', 'foo', 11),
|
||||||
|
new Token('punctuation', '(', 14),
|
||||||
|
new Token('string', 'bar', 15),
|
||||||
|
new Token('punctuation', ')', 20),
|
||||||
|
new Token('punctuation', '.', 21),
|
||||||
|
new Token('name', 'baz', 22),
|
||||||
|
new Token('punctuation', '[', 25),
|
||||||
|
new Token('number', '4', 26),
|
||||||
|
new Token('punctuation', ']', 27),
|
||||||
|
),
|
||||||
|
'(3 + 5) ~ foo("bar").baz[4]',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\ExpressionLanguage\Tests\Node;
|
||||||
|
|
||||||
|
use Symfony\Component\ExpressionLanguage\Compiler;
|
||||||
|
|
||||||
|
abstract class AbstractNodeTest extends \PHPUnit_Framework_TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @dataProvider getEvaluateData
|
||||||
|
*/
|
||||||
|
public function testEvaluate($expected, $node, $variables = array(), $functions = array())
|
||||||
|
{
|
||||||
|
$this->assertSame($expected, $node->evaluate($functions, $variables));
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract public function getEvaluateData();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider getCompileData
|
||||||
|
*/
|
||||||
|
public function testCompile($expected, $node, $functions = array())
|
||||||
|
{
|
||||||
|
$compiler = new Compiler($functions);
|
||||||
|
$node->compile($compiler);
|
||||||
|
$this->assertSame($expected, $compiler->getSource());
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract public function getCompileData();
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\ExpressionLanguage\Tests\Node;
|
||||||
|
|
||||||
|
use Symfony\Component\ExpressionLanguage\Node\ArgumentsNode;
|
||||||
|
use Symfony\Component\ExpressionLanguage\Node\ConstantNode;
|
||||||
|
|
||||||
|
class ArgumentsNodeTest extends ArrayNodeTest
|
||||||
|
{
|
||||||
|
public function getCompileData()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
array('"a", "b"', $this->getArrayNode()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function createArrayNode()
|
||||||
|
{
|
||||||
|
return new ArgumentsNode();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\ExpressionLanguage\Tests\Node;
|
||||||
|
|
||||||
|
use Symfony\Component\ExpressionLanguage\Node\ArrayNode;
|
||||||
|
use Symfony\Component\ExpressionLanguage\Node\ConstantNode;
|
||||||
|
|
||||||
|
class ArrayNodeTest extends AbstractNodeTest
|
||||||
|
{
|
||||||
|
public function getEvaluateData()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
array(array('b' => 'a', 'b'), $this->getArrayNode()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCompileData()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
array('array("b" => "a", 0 => "b")', $this->getArrayNode()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getArrayNode()
|
||||||
|
{
|
||||||
|
$array = $this->createArrayNode();
|
||||||
|
$array->addElement(new ConstantNode('a'), new ConstantNode('b'));
|
||||||
|
$array->addElement(new ConstantNode('b'));
|
||||||
|
|
||||||
|
return $array;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function createArrayNode()
|
||||||
|
{
|
||||||
|
return new ArrayNode();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,113 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\ExpressionLanguage\Tests\Node;
|
||||||
|
|
||||||
|
use Symfony\Component\ExpressionLanguage\Node\BinaryNode;
|
||||||
|
use Symfony\Component\ExpressionLanguage\Node\ArrayNode;
|
||||||
|
use Symfony\Component\ExpressionLanguage\Node\ConstantNode;
|
||||||
|
|
||||||
|
class BinaryNodeTest extends AbstractNodeTest
|
||||||
|
{
|
||||||
|
public function getEvaluateData()
|
||||||
|
{
|
||||||
|
$array = new ArrayNode();
|
||||||
|
$array->addElement(new ConstantNode('a'));
|
||||||
|
$array->addElement(new ConstantNode('b'));
|
||||||
|
|
||||||
|
return array(
|
||||||
|
array(true, new BinaryNode('or', new ConstantNode(true), new ConstantNode(false))),
|
||||||
|
array(true, new BinaryNode('||', new ConstantNode(true), new ConstantNode(false))),
|
||||||
|
array(false, new BinaryNode('and', new ConstantNode(true), new ConstantNode(false))),
|
||||||
|
array(false, new BinaryNode('&&', new ConstantNode(true), new ConstantNode(false))),
|
||||||
|
|
||||||
|
array(0, new BinaryNode('&', new ConstantNode(2), new ConstantNode(4))),
|
||||||
|
array(6, new BinaryNode('|', new ConstantNode(2), new ConstantNode(4))),
|
||||||
|
array(6, new BinaryNode('^', new ConstantNode(2), new ConstantNode(4))),
|
||||||
|
|
||||||
|
array(true, new BinaryNode('<', new ConstantNode(1), new ConstantNode(2))),
|
||||||
|
array(true, new BinaryNode('<=', new ConstantNode(1), new ConstantNode(2))),
|
||||||
|
array(true, new BinaryNode('<=', new ConstantNode(1), new ConstantNode(1))),
|
||||||
|
|
||||||
|
array(false, new BinaryNode('>', new ConstantNode(1), new ConstantNode(2))),
|
||||||
|
array(false, new BinaryNode('>=', new ConstantNode(1), new ConstantNode(2))),
|
||||||
|
array(true, new BinaryNode('>=', new ConstantNode(1), new ConstantNode(1))),
|
||||||
|
|
||||||
|
array(true, new BinaryNode('===', new ConstantNode(true), new ConstantNode(true))),
|
||||||
|
array(false, new BinaryNode('!==', new ConstantNode(true), new ConstantNode(true))),
|
||||||
|
|
||||||
|
array(false, new BinaryNode('==', new ConstantNode(2), new ConstantNode(1))),
|
||||||
|
array(true, new BinaryNode('!=', new ConstantNode(2), new ConstantNode(1))),
|
||||||
|
|
||||||
|
array(-1, new BinaryNode('-', new ConstantNode(1), new ConstantNode(2))),
|
||||||
|
array(3, new BinaryNode('+', new ConstantNode(1), new ConstantNode(2))),
|
||||||
|
array(4, new BinaryNode('*', new ConstantNode(2), new ConstantNode(2))),
|
||||||
|
array(1, new BinaryNode('/', new ConstantNode(2), new ConstantNode(2))),
|
||||||
|
array(1, new BinaryNode('%', new ConstantNode(5), new ConstantNode(2))),
|
||||||
|
array(25, new BinaryNode('**', new ConstantNode(5), new ConstantNode(2))),
|
||||||
|
array('ab', new BinaryNode('~', new ConstantNode('a'), new ConstantNode('b'))),
|
||||||
|
|
||||||
|
array(true, new BinaryNode('in', new ConstantNode('a'), $array)),
|
||||||
|
array(false, new BinaryNode('in', new ConstantNode('c'), $array)),
|
||||||
|
array(true, new BinaryNode('not in', new ConstantNode('c'), $array)),
|
||||||
|
array(false, new BinaryNode('not in', new ConstantNode('a'), $array)),
|
||||||
|
|
||||||
|
array(array(1, 2, 3), new BinaryNode('..', new ConstantNode(1), new ConstantNode(3))),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCompileData()
|
||||||
|
{
|
||||||
|
$array = new ArrayNode();
|
||||||
|
$array->addElement(new ConstantNode('a'));
|
||||||
|
$array->addElement(new ConstantNode('b'));
|
||||||
|
|
||||||
|
return array(
|
||||||
|
array('(true || false)', new BinaryNode('or', new ConstantNode(true), new ConstantNode(false))),
|
||||||
|
array('(true || false)', new BinaryNode('||', new ConstantNode(true), new ConstantNode(false))),
|
||||||
|
array('(true && 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('pow(5, 2)', new BinaryNode('**', new ConstantNode(5), new ConstantNode(2))),
|
||||||
|
array('("a" . "b")', new BinaryNode('~', new ConstantNode('a'), new ConstantNode('b'))),
|
||||||
|
|
||||||
|
array('in_array("a", array(0 => "a", 1 => "b"))', new BinaryNode('in', new ConstantNode('a'), $array)),
|
||||||
|
array('in_array("c", array(0 => "a", 1 => "b"))', new BinaryNode('in', new ConstantNode('c'), $array)),
|
||||||
|
array('!in_array("c", array(0 => "a", 1 => "b"))', new BinaryNode('not in', new ConstantNode('c'), $array)),
|
||||||
|
array('!in_array("a", array(0 => "a", 1 => "b"))', new BinaryNode('not in', new ConstantNode('a'), $array)),
|
||||||
|
|
||||||
|
array('range(1, 3)', new BinaryNode('..', new ConstantNode(1), new ConstantNode(3))),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\ExpressionLanguage\Tests\Node;
|
||||||
|
|
||||||
|
use Symfony\Component\ExpressionLanguage\Node\ConditionalNode;
|
||||||
|
use Symfony\Component\ExpressionLanguage\Node\ConstantNode;
|
||||||
|
|
||||||
|
class ConditionalNodeTest extends AbstractNodeTest
|
||||||
|
{
|
||||||
|
public function getEvaluateData()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
array(1, new ConditionalNode(new ConstantNode(true), new ConstantNode(1), new ConstantNode(2))),
|
||||||
|
array(2, new ConditionalNode(new ConstantNode(false), new ConstantNode(1), new ConstantNode(2))),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCompileData()
|
||||||
|
{
|
||||||
|
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))),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\ExpressionLanguage\Tests\Node;
|
||||||
|
|
||||||
|
use Symfony\Component\ExpressionLanguage\Node\ConstantNode;
|
||||||
|
|
||||||
|
class ConstantNodeTest extends AbstractNodeTest
|
||||||
|
{
|
||||||
|
public function getEvaluateData()
|
||||||
|
{
|
||||||
|
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(array(1, 'b' => 'a'), new ConstantNode(array(1, 'b' => 'a'))),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCompileData()
|
||||||
|
{
|
||||||
|
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('array(0 => 1, "b" => "a")', new ConstantNode(array(1, 'b' => 'a'))),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\ExpressionLanguage\Tests\Node;
|
||||||
|
|
||||||
|
use Symfony\Component\ExpressionLanguage\Node\FunctionNode;
|
||||||
|
use Symfony\Component\ExpressionLanguage\Node\ConstantNode;
|
||||||
|
use Symfony\Component\ExpressionLanguage\Node\Node;
|
||||||
|
|
||||||
|
class FunctionNodeTest extends AbstractNodeTest
|
||||||
|
{
|
||||||
|
public function getEvaluateData()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
array('bar', new FunctionNode('foo', new Node(array(new ConstantNode('bar')))), array(), array('foo' => $this->getCallables())),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCompileData()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
array('foo("bar")', new FunctionNode('foo', new Node(array(new ConstantNode('bar')))), array('foo' => $this->getCallables())),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getCallables()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
'compiler' => function ($arg) {
|
||||||
|
return sprintf('foo(%s)', $arg);
|
||||||
|
},
|
||||||
|
'evaluator' => function ($variables, $arg) {
|
||||||
|
return $arg;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\ExpressionLanguage\Tests\Node;
|
||||||
|
|
||||||
|
use Symfony\Component\ExpressionLanguage\Node\ArrayNode;
|
||||||
|
use Symfony\Component\ExpressionLanguage\Node\NameNode;
|
||||||
|
use Symfony\Component\ExpressionLanguage\Node\GetAttrNode;
|
||||||
|
use Symfony\Component\ExpressionLanguage\Node\ConstantNode;
|
||||||
|
|
||||||
|
class GetAttrNodeTest extends AbstractNodeTest
|
||||||
|
{
|
||||||
|
public function getEvaluateData()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
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('baz', new GetAttrNode(new NameNode('foo'), new ConstantNode('foo'), $this->getArrayNode(), GetAttrNode::METHOD_CALL), array('foo' => new Obj())),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCompileData()
|
||||||
|
{
|
||||||
|
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(array("b" => "a", 0 => "b"))', new GetAttrNode(new NameNode('foo'), new ConstantNode('foo'), $this->getArrayNode(), GetAttrNode::METHOD_CALL), array('foo' => new Obj())),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getArrayNode()
|
||||||
|
{
|
||||||
|
$array = new ArrayNode();
|
||||||
|
$array->addElement(new ConstantNode('a'), new ConstantNode('b'));
|
||||||
|
$array->addElement(new ConstantNode('b'));
|
||||||
|
|
||||||
|
return $array;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Obj
|
||||||
|
{
|
||||||
|
public $foo = 'bar';
|
||||||
|
|
||||||
|
public function foo()
|
||||||
|
{
|
||||||
|
return 'baz';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\ExpressionLanguage\Tests\Node;
|
||||||
|
|
||||||
|
use Symfony\Component\ExpressionLanguage\Node\NameNode;
|
||||||
|
|
||||||
|
class NameNodeTest extends AbstractNodeTest
|
||||||
|
{
|
||||||
|
public function getEvaluateData()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
array('bar', new NameNode('foo'), array('foo' => 'bar')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCompileData()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
array('$foo', new NameNode('foo')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\ExpressionLanguage\Tests\Node;
|
||||||
|
|
||||||
|
use Symfony\Component\ExpressionLanguage\Node\Node;
|
||||||
|
use Symfony\Component\ExpressionLanguage\Node\ConstantNode;
|
||||||
|
|
||||||
|
class NodeTest extends \PHPUnit_Framework_TestCase
|
||||||
|
{
|
||||||
|
public function testToString()
|
||||||
|
{
|
||||||
|
$node = new Node(array(new ConstantNode('foo')));
|
||||||
|
|
||||||
|
$this->assertEquals(<<<EOF
|
||||||
|
Node(
|
||||||
|
ConstantNode(value: 'foo')
|
||||||
|
)
|
||||||
|
EOF
|
||||||
|
, (string) $node);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\ExpressionLanguage\Tests\Node;
|
||||||
|
|
||||||
|
use Symfony\Component\ExpressionLanguage\Node\UnaryNode;
|
||||||
|
use Symfony\Component\ExpressionLanguage\Node\ConstantNode;
|
||||||
|
|
||||||
|
class UnaryNodeTest extends AbstractNodeTest
|
||||||
|
{
|
||||||
|
public function getEvaluateData()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
array(-1, new UnaryNode('-', new ConstantNode(1))),
|
||||||
|
array(3, new UnaryNode('+', new ConstantNode(3))),
|
||||||
|
array(false, new UnaryNode('!', new ConstantNode(true))),
|
||||||
|
array(false, new UnaryNode('not', new ConstantNode(true))),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCompileData()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
array('(-1)', new UnaryNode('-', new ConstantNode(1))),
|
||||||
|
array('(+3)', new UnaryNode('+', new ConstantNode(3))),
|
||||||
|
array('(!true)', new UnaryNode('!', new ConstantNode(true))),
|
||||||
|
array('(!true)', new UnaryNode('not', new ConstantNode(true))),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,143 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\ExpressionLanguage\Tests\Node;
|
||||||
|
|
||||||
|
use Symfony\Component\ExpressionLanguage\Parser;
|
||||||
|
use Symfony\Component\ExpressionLanguage\Lexer;
|
||||||
|
use Symfony\Component\ExpressionLanguage\Node;
|
||||||
|
|
||||||
|
class ParserTest extends \PHPUnit_Framework_TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @expectedException \Symfony\Component\ExpressionLanguage\SyntaxError
|
||||||
|
* @expectedExceptionMessage Variable "foo" is not valid around position 1.
|
||||||
|
*/
|
||||||
|
public function testParseWithInvalidName()
|
||||||
|
{
|
||||||
|
$lexer = new Lexer();
|
||||||
|
$parser = new Parser(array());
|
||||||
|
$parser->parse($lexer->tokenize('foo'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider getParseData
|
||||||
|
*/
|
||||||
|
public function testParse($node, $expression, $names = array())
|
||||||
|
{
|
||||||
|
$lexer = new Lexer();
|
||||||
|
$parser = new Parser(array());
|
||||||
|
$this->assertEquals($node, $parser->parse($lexer->tokenize($expression), $names));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getParseData()
|
||||||
|
{
|
||||||
|
$arguments = new Node\ArgumentsNode();
|
||||||
|
$arguments->addElement(new Node\ConstantNode('arg1'));
|
||||||
|
$arguments->addElement(new Node\ConstantNode(2));
|
||||||
|
$arguments->addElement(new Node\ConstantNode(true));
|
||||||
|
|
||||||
|
return array(
|
||||||
|
array(
|
||||||
|
new Node\NameNode('a'),
|
||||||
|
'a',
|
||||||
|
array('a'),
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
new Node\ConstantNode('a'),
|
||||||
|
'"a"',
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
new Node\ConstantNode(3),
|
||||||
|
'3',
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
new Node\ConstantNode(false),
|
||||||
|
'false',
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
new Node\ConstantNode(true),
|
||||||
|
'true',
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
new Node\ConstantNode(null),
|
||||||
|
'null',
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
new Node\UnaryNode('-', new Node\ConstantNode(3)),
|
||||||
|
'-3',
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
new Node\BinaryNode('-', new Node\ConstantNode(3), new Node\ConstantNode(3)),
|
||||||
|
'3 - 3',
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
new Node\BinaryNode('*',
|
||||||
|
new Node\BinaryNode('-', new Node\ConstantNode(3), new Node\ConstantNode(3)),
|
||||||
|
new Node\ConstantNode(2)
|
||||||
|
),
|
||||||
|
'(3 - 3) * 2',
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
new Node\GetAttrNode(new Node\NameNode('foo'), new Node\ConstantNode('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),
|
||||||
|
'foo.bar()',
|
||||||
|
array('foo'),
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
new Node\GetAttrNode(new Node\NameNode('foo'), new Node\ConstantNode('not'), new Node\ArgumentsNode(), Node\GetAttrNode::METHOD_CALL),
|
||||||
|
'foo.not()',
|
||||||
|
array('foo'),
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
new Node\GetAttrNode(
|
||||||
|
new Node\NameNode('foo'),
|
||||||
|
new Node\ConstantNode('bar'),
|
||||||
|
$arguments,
|
||||||
|
Node\GetAttrNode::METHOD_CALL
|
||||||
|
),
|
||||||
|
'foo.bar("arg1", 2, true)',
|
||||||
|
array('foo'),
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
new Node\GetAttrNode(new Node\NameNode('foo'), new Node\ConstantNode(3), new Node\ArgumentsNode(), Node\GetAttrNode::ARRAY_CALL),
|
||||||
|
'foo[3]',
|
||||||
|
array('foo'),
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
new Node\ConditionalNode(new Node\ConstantNode(true), new Node\ConstantNode(true), new Node\ConstantNode(false)),
|
||||||
|
'true ? true : false',
|
||||||
|
),
|
||||||
|
|
||||||
|
// chained calls
|
||||||
|
array(
|
||||||
|
$this->createGetAttrNode(
|
||||||
|
$this->createGetAttrNode(
|
||||||
|
$this->createGetAttrNode(
|
||||||
|
$this->createGetAttrNode(new Node\NameNode('foo'), 'bar', Node\GetAttrNode::METHOD_CALL),
|
||||||
|
'foo', Node\GetAttrNode::METHOD_CALL),
|
||||||
|
'baz', Node\GetAttrNode::PROPERTY_CALL),
|
||||||
|
'3', Node\GetAttrNode::ARRAY_CALL),
|
||||||
|
'foo.bar().foo().baz[3]',
|
||||||
|
array('foo'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function createGetAttrNode($node, $item, $type)
|
||||||
|
{
|
||||||
|
return new Node\GetAttrNode($node, new Node\ConstantNode($item), new Node\ArgumentsNode(), $type);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\ExpressionLanguage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a Token.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
class Token
|
||||||
|
{
|
||||||
|
public $value;
|
||||||
|
public $type;
|
||||||
|
public $cursor;
|
||||||
|
|
||||||
|
const EOF_TYPE = 'end of expression';
|
||||||
|
const NAME_TYPE = 'name';
|
||||||
|
const NUMBER_TYPE = 'number';
|
||||||
|
const STRING_TYPE = 'string';
|
||||||
|
const OPERATOR_TYPE = 'operator';
|
||||||
|
const PUNCTUATION_TYPE = 'punctuation';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param integer $type The type of the token
|
||||||
|
* @param string $value The token value
|
||||||
|
* @param integer $cursor The cursor position in the source
|
||||||
|
*/
|
||||||
|
public function __construct($type, $value, $cursor)
|
||||||
|
{
|
||||||
|
$this->type = $type;
|
||||||
|
$this->value = $value;
|
||||||
|
$this->cursor = $cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a string representation of the token.
|
||||||
|
*
|
||||||
|
* @return string A string representation of the token
|
||||||
|
*/
|
||||||
|
public function __toString()
|
||||||
|
{
|
||||||
|
return sprintf('%3d %-11s %s', $this->cursor, strtoupper($this->type), $this->value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the current token for a type and/or a value.
|
||||||
|
*
|
||||||
|
* @param array|integer $type The type to test
|
||||||
|
* @param string|null $value The token value
|
||||||
|
*
|
||||||
|
* @return Boolean
|
||||||
|
*/
|
||||||
|
public function test($type, $value = null)
|
||||||
|
{
|
||||||
|
return $this->type === $type && (null === $value || $this->value == $value);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\ExpressionLanguage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a token stream.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
class TokenStream
|
||||||
|
{
|
||||||
|
public $current;
|
||||||
|
|
||||||
|
private $tokens;
|
||||||
|
private $position;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param array $tokens An array of tokens
|
||||||
|
*/
|
||||||
|
public function __construct(array $tokens)
|
||||||
|
{
|
||||||
|
$this->tokens = $tokens;
|
||||||
|
$this->position = 0;
|
||||||
|
$this->current = $tokens[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a string representation of the token stream.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function __toString()
|
||||||
|
{
|
||||||
|
return implode("\n", $this->tokens);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the pointer to the next token and returns the old one.
|
||||||
|
*/
|
||||||
|
public function next()
|
||||||
|
{
|
||||||
|
if (!isset($this->tokens[$this->position])) {
|
||||||
|
throw new SyntaxError('Unexpected end of expression', $this->current->cursor);
|
||||||
|
}
|
||||||
|
|
||||||
|
++$this->position;
|
||||||
|
|
||||||
|
$this->current = $this->tokens[$this->position];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests a token.
|
||||||
|
*/
|
||||||
|
public function expect($type, $value = null, $message = null)
|
||||||
|
{
|
||||||
|
$token = $this->current;
|
||||||
|
if (!$token->test($type, $value)) {
|
||||||
|
throw new SyntaxError(sprintf('%sUnexpected token "%s" of value "%s" ("%s" expected%s)', $message ? $message.'. ' : '', $token->type, $token->value, $type, $value ? sprintf(' with value "%s"', $value) : ''), $token->cursor);
|
||||||
|
}
|
||||||
|
$this->next();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if end of stream was reached
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isEOF()
|
||||||
|
{
|
||||||
|
return $this->current->type === Token::EOF_TYPE;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
{
|
||||||
|
"name": "symfony/expression-language",
|
||||||
|
"type": "library",
|
||||||
|
"description": "Symfony ExpressionLanguage Component",
|
||||||
|
"keywords": [],
|
||||||
|
"homepage": "http://symfony.com",
|
||||||
|
"license": "MIT",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Fabien Potencier",
|
||||||
|
"email": "fabien@symfony.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Symfony Community",
|
||||||
|
"homepage": "http://symfony.com/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"require": {
|
||||||
|
"php": ">=5.3.3"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-0": { "Symfony\\Component\\ExpressionLanguage\\": "" }
|
||||||
|
},
|
||||||
|
"target-dir": "Symfony/Component/ExpressionLanguage",
|
||||||
|
"minimum-stability": "dev",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "2.4-dev"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<phpunit backupGlobals="false"
|
||||||
|
backupStaticAttributes="false"
|
||||||
|
colors="true"
|
||||||
|
convertErrorsToExceptions="true"
|
||||||
|
convertNoticesToExceptions="true"
|
||||||
|
convertWarningsToExceptions="true"
|
||||||
|
processIsolation="false"
|
||||||
|
stopOnFailure="false"
|
||||||
|
syntaxCheck="false"
|
||||||
|
bootstrap="vendor/autoload.php"
|
||||||
|
>
|
||||||
|
<testsuites>
|
||||||
|
<testsuite name="Symfony ExpressionLanguage Component Test Suite">
|
||||||
|
<directory>./Tests/</directory>
|
||||||
|
</testsuite>
|
||||||
|
</testsuites>
|
||||||
|
|
||||||
|
<filter>
|
||||||
|
<whitelist>
|
||||||
|
<directory>./</directory>
|
||||||
|
<exclude>
|
||||||
|
<directory>./Tests</directory>
|
||||||
|
<directory>./vendor</directory>
|
||||||
|
</exclude>
|
||||||
|
</whitelist>
|
||||||
|
</filter>
|
||||||
|
</phpunit>
|
Reference in New Issue