This commit is contained in:
Fabien Potencier
2025-02-14 08:43:56 +01:00
parent 561c6629e1
commit 251c0b5da9
12 changed files with 32 additions and 28 deletions
+6 -5
View File
@@ -16,9 +16,9 @@ use Twig\ExpressionParser\InfixAssociativity;
use Twig\ExpressionParser\InfixExpressionParserInterface;
use Twig\Loader\ArrayLoader;
require_once dirname(__DIR__).'/vendor/autoload.php';
require_once \dirname(__DIR__).'/vendor/autoload.php';
$output = fopen(dirname(__DIR__).'/doc/operators_precedence.rst', 'w');
$output = fopen(\dirname(__DIR__).'/doc/operators_precedence.rst', 'w');
$twig = new Environment(new ArrayLoader([]));
$expressionParsers = [];
@@ -28,7 +28,7 @@ foreach ($twig->getExpressionParsers() as $expressionParser) {
fwrite($output, "\n=========== ================ ======= ============= ===========\n");
fwrite($output, "Precedence Operator Type Associativity Description\n");
fwrite($output, "=========== ================ ======= ============= ===========");
fwrite($output, '=========== ================ ======= ============= ===========');
usort($expressionParsers, fn ($a, $b) => $b->getPrecedence() <=> $a->getPrecedence());
@@ -57,11 +57,12 @@ fwrite($output, "\nHere is the same table for Twig 4.0 with adjusted precedences
fwrite($output, "\n=========== ================ ======= ============= ===========\n");
fwrite($output, "Precedence Operator Type Associativity Description\n");
fwrite($output, "=========== ================ ======= ============= ===========");
fwrite($output, '=========== ================ ======= ============= ===========');
usort($expressionParsers, function($a, $b) {
usort($expressionParsers, function ($a, $b) {
$aPrecedence = $a->getPrecedenceChange() ? $a->getPrecedenceChange()->getNewPrecedence() : $a->getPrecedence();
$bPrecedence = $b->getPrecedenceChange() ? $b->getPrecedenceChange()->getNewPrecedence() : $b->getPrecedence();
return $bPrecedence - $aPrecedence;
});
@@ -30,7 +30,7 @@ class CacheTokenParser extends AbstractTokenParser
$tags = null;
while ($stream->test(Token::NAME_TYPE)) {
$k = $stream->getCurrent()->getValue();
if (!in_array($k, ['ttl', 'tags'])) {
if (!\in_array($k, ['ttl', 'tags'])) {
throw new SyntaxError(\sprintf('Unknown "%s" configuration.', $k), $stream->getCurrent()->getLine(), $stream->getSourceContext());
}
@@ -19,7 +19,7 @@ enum ExpressionParserType: string
case Prefix = 'prefix';
case Infix = 'infix';
static public function getType(object $object): ExpressionParserType
public static function getType(object $object): ExpressionParserType
{
if ($object instanceof PrefixExpressionParserInterface) {
return self::Prefix;
@@ -28,6 +28,6 @@ enum ExpressionParserType: string
return self::Infix;
}
throw new \InvalidArgumentException(\sprintf('Unsupported expression parser type: %s', \get_class($object)));
throw new \InvalidArgumentException(\sprintf('Unsupported expression parser type: %s', $object::class));
}
}
+4 -5
View File
@@ -36,9 +36,8 @@ final class ExpressionParsers implements \IteratorAggregate
/**
* @param array<ExpressionParserInterface> $parsers
*/
public function __construct(
array $parsers = [],
) {
public function __construct(array $parsers = [])
{
$this->add($parsers);
}
@@ -47,7 +46,7 @@ final class ExpressionParsers implements \IteratorAggregate
*
* @return $this
*/
public function add(array $parsers): self
public function add(array $parsers): static
{
foreach ($parsers as $parser) {
if ($parser->getPrecedence() > 512 || $parser->getPrecedence() < 0) {
@@ -56,7 +55,7 @@ final class ExpressionParsers implements \IteratorAggregate
}
$interface = $parser instanceof PrefixExpressionParserInterface ? PrefixExpressionParserInterface::class : InfixExpressionParserInterface::class;
$this->parsersByName[$interface][$parser->getName()] = $parser;
$this->parsersByClass[get_class($parser)] = $parser;
$this->parsersByClass[$parser::class] = $parser;
foreach ($parser->getAliases() as $alias) {
$this->parsersByName[$interface][$alias] = $parser;
}
@@ -62,7 +62,7 @@ trait ArgumentsTrait
$name = null;
if (($token = $stream->nextIf(Token::OPERATOR_TYPE, '=')) || ($token = $stream->nextIf(Token::PUNCTUATION_TYPE, ':'))) {
if (!$value instanceof ContextVariable) {
throw new SyntaxError(\sprintf('A parameter name must be a string, "%s" given.', \get_class($value)), $token->getLine(), $stream->getSourceContext());
throw new SyntaxError(\sprintf('A parameter name must be a string, "%s" given.', $value::class), $token->getLine(), $stream->getSourceContext());
}
$name = $value->getAttribute('name');
$value = $parser->parseExpression();
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Twig\ExpressionParser\Infix;
namespace Twig\ExpressionParser\Infix;
use Twig\Attribute\FirstClassTwigCallableReady;
use Twig\ExpressionParser\AbstractExpressionParser;
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Twig\ExpressionParser\Infix;
namespace Twig\ExpressionParser\Infix;
use Twig\Node\Expression\AbstractExpression;
use Twig\Node\Expression\Unary\NotUnary;
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Twig\ExpressionParser\Infix;
namespace Twig\ExpressionParser\Infix;
use Twig\ExpressionParser\AbstractExpressionParser;
use Twig\ExpressionParser\ExpressionParserDescriptionInterface;
@@ -41,11 +41,13 @@ final class LiteralExpressionParser extends AbstractExpressionParser implements
case 'true':
case 'TRUE':
$this->type = 'constant';
return new ConstantExpression(true, $token->getLine());
case 'false':
case 'FALSE':
$this->type = 'constant';
return new ConstantExpression(false, $token->getLine());
case 'none':
@@ -53,10 +55,12 @@ final class LiteralExpressionParser extends AbstractExpressionParser implements
case 'null':
case 'NULL':
$this->type = 'constant';
return new ConstantExpression(null, $token->getLine());
default:
$this->type = 'variable';
return new ContextVariable($token->getValue(), $token->getLine());
}
+2 -2
View File
@@ -509,8 +509,8 @@ final class ExtensionSet
}
}
if (count($expressionParsers)) {
trigger_deprecation('twig/twig', '3.20', \sprintf('Extension "%s" uses the old signature for "getOperators()", please implement "getExpressionParsers()" instead.', \get_class($extension)));
if (\count($expressionParsers)) {
trigger_deprecation('twig/twig', '3.20', \sprintf('Extension "%s" uses the old signature for "getOperators()", please implement "getExpressionParsers()" instead.', $extension::class));
$this->expressionParsers->add($expressionParsers);
}
+3 -3
View File
@@ -342,7 +342,7 @@ class Lexer
// operators
elseif (preg_match($this->regexes['operator'], $this->code, $match, 0, $this->cursor)) {
$operator = preg_replace('/\s+/', ' ', $match[0]);
if (in_array($operator, $this->openingBrackets)) {
if (\in_array($operator, $this->openingBrackets)) {
$this->checkBrackets($operator);
}
$this->pushToken(Token::OPERATOR_TYPE, $operator);
@@ -579,9 +579,9 @@ class Lexer
private function checkBrackets(string $code): void
{
// opening bracket
if (in_array($code, $this->openingBrackets)) {
if (\in_array($code, $this->openingBrackets)) {
$this->brackets[] = [$code, $this->lineno];
} elseif (in_array($code, $this->closingBrackets)) {
} elseif (\in_array($code, $this->closingBrackets)) {
// closing bracket
if (!$this->brackets) {
throw new SyntaxError(\sprintf('Unexpected "%s".', $code), $this->lineno, $this->source);
+6 -6
View File
@@ -650,8 +650,8 @@ class ExpressionParserTest extends TestCase
yield "$op1 vs $op2" => ["{{ $e }}", "{{ (12 $op1 6) $op2 3 }}", $php];
}
}
yield '+ vs not in' => ['{{ 1 + 2 not in [3, 4] }}', '{{ (1 + 2) not in [3, 4] }}', eval("return !in_array(1 + 2, [3, 4]);")];
yield '+ vs in' => ['{{ 1 + 2 in [3, 4] }}', '{{ (1 + 2) in [3, 4] }}', eval("return in_array(1 + 2, [3, 4]);")];
yield '+ vs not in' => ['{{ 1 + 2 not in [3, 4] }}', '{{ (1 + 2) not in [3, 4] }}', eval('return !in_array(1 + 2, [3, 4]);')];
yield '+ vs in' => ['{{ 1 + 2 in [3, 4] }}', '{{ (1 + 2) in [3, 4] }}', eval('return in_array(1 + 2, [3, 4]);')];
yield '+ vs matches' => ['{{ 1 + 2 matches "/^3$/" }}', '{{ (1 + 2) matches "/^3$/" }}', eval("return preg_match('/^3$/', 1 + 2);")];
// ~ stronger than `starts with` `ends with`
@@ -668,17 +668,17 @@ class ExpressionParserTest extends TestCase
yield '[] . vs unary -' => ['{{ -a["c"].d }}', '{{ -((a["c"]).d) }}', eval("\$a = ['c' => ['d' => 2]]; return -\$a['c']['d'];"), $context];
// () stronger than anything else
yield '() vs unary -' => ['{{ -random(1, 1) + 3 }}', '{{ -(random(1, 1)) + 3 }}', eval("return -rand(1, 1) + 3;")];
yield '() vs unary -' => ['{{ -random(1, 1) + 3 }}', '{{ -(random(1, 1)) + 3 }}', eval('return -rand(1, 1) + 3;')];
// + - stronger than |
yield '+ vs |' => ['{{ 10 + 2|length }}', '{{ 10 + (2|length) }}', eval("return 10 + strlen(2);"), $context];
yield '+ vs |' => ['{{ 10 + 2|length }}', '{{ 10 + (2|length) }}', eval('return 10 + strlen(2);'), $context];
// - unary stronger than |
// To be uncomment in Twig 4.0
//yield '- vs |' => ['{{ -1|abs }}', '{{ (-1)|abs }}', eval("return abs(-1);"), $context];
// yield '- vs |' => ['{{ -1|abs }}', '{{ (-1)|abs }}', eval("return abs(-1);"), $context];
// ?? stronger than ()
//yield '?? vs ()' => ['{{ (1 ?? "a") }}', '{{ ((1 ?? "a")) }}', eval("return 1;")];
// yield '?? vs ()' => ['{{ (1 ?? "a") }}', '{{ ((1 ?? "a")) }}', eval("return 1;")];
}
}