Use generics in ExpressionParsers

This commit is contained in:
Fabien Potencier
2025-02-06 08:26:34 +01:00
parent 8ff19090f3
commit 4b57f48a65
5 changed files with 34 additions and 46 deletions
+3 -3
View File
@@ -162,10 +162,10 @@ class ExpressionParser
$parsers = new \ReflectionProperty($this->parser, 'parsers');
if ('.' === $this->parser->getStream()->next()->getValue()) {
return $parsers->getValue($this->parser)->getInfixByClass(DotExpressionParser::class)->parse($this->parser, $node, $this->parser->getCurrentToken());
return $parsers->getValue($this->parser)->getByClass(DotExpressionParser::class)->parse($this->parser, $node, $this->parser->getCurrentToken());
}
return $parsers->getValue($this->parser)->getInfixByClass(SquareBracketExpressionParser::class)->parse($this->parser, $node, $this->parser->getCurrentToken());
return $parsers->getValue($this->parser)->getByClass(SquareBracketExpressionParser::class)->parse($this->parser, $node, $this->parser->getCurrentToken());
}
/**
@@ -189,7 +189,7 @@ class ExpressionParser
$parsers = new \ReflectionProperty($this->parser, 'parsers');
$op = $parsers->getValue($this->parser)->getInfixByClass(FilterExpressionParser::class);
$op = $parsers->getValue($this->parser)->getByClass(FilterExpressionParser::class);
while (true) {
$node = $op->parse($this->parser, $node, $this->parser->getCurrentToken());
if (!$this->parser->getStream()->test(Token::OPERATOR_TYPE, '|')) {
+22 -37
View File
@@ -19,20 +19,15 @@ namespace Twig\ExpressionParser;
final class ExpressionParsers implements \IteratorAggregate
{
/**
* @var array<value-of<ExpressionParserType>, array<string, ExpressionParserInterface>>
* @var array<class-string<ExpressionParserInterface>, array<string, ExpressionParserInterface>>
*/
private array $parsers = [];
private array $parsersByName = [];
/**
* @var array<value-of<ExpressionParserType>, array<class-string<ExpressionParserInterface>, ExpressionParserInterface>>
* @var array<class-string<ExpressionParserInterface>, ExpressionParserInterface>
*/
private array $parsersByClass = [];
/**
* @var array<value-of<ExpressionParserType>, array<string, ExpressionParserInterface>>
*/
private array $aliases = [];
/**
* @var \WeakMap<ExpressionParserInterface, array<ExpressionParserInterface>>|null
*/
@@ -59,11 +54,11 @@ final class ExpressionParsers implements \IteratorAggregate
trigger_deprecation('twig/twig', '3.20', 'Precedence for "%s" must be between 0 and 512, got %d.', $parser->getName(), $parser->getPrecedence());
// throw new \InvalidArgumentException(\sprintf('Precedence for "%s" must be between 0 and 512, got %d.', $parser->getName(), $parser->getPrecedence()));
}
$type = ExpressionParserType::getType($parser);
$this->parsers[$type->value][$parser->getName()] = $parser;
$this->parsersByClass[$type->value][get_class($parser)] = $parser;
$interface = $parser instanceof PrefixExpressionParserInterface ? PrefixExpressionParserInterface::class : InfixExpressionParserInterface::class;
$this->parsersByName[$interface][$parser->getName()] = $parser;
$this->parsersByClass[get_class($parser)] = $parser;
foreach ($parser->getAliases() as $alias) {
$this->aliases[$type->value][$alias] = $parser;
$this->parsersByName[$interface][$alias] = $parser;
}
}
@@ -71,42 +66,32 @@ final class ExpressionParsers implements \IteratorAggregate
}
/**
* @param class-string<PrefixExpressionParserInterface> $name
* @template T of ExpressionParserInterface
*
* @param class-string<T> $class
*
* @return T|null
*/
public function getPrefixByClass(string $name): ?PrefixExpressionParserInterface
public function getByClass(string $class): ?ExpressionParserInterface
{
return $this->parsersByClass[ExpressionParserType::Prefix->value][$name] ?? null;
}
public function getPrefix(string $name): ?PrefixExpressionParserInterface
{
return
$this->parsers[ExpressionParserType::Prefix->value][$name]
?? $this->aliases[ExpressionParserType::Prefix->value][$name]
?? null
;
return $this->parsersByClass[$class] ?? null;
}
/**
* @param class-string<InfixExpressionParserInterface> $name
* @template T of ExpressionParserInterface
*
* @param class-string<T> $interface
*
* @return T|null
*/
public function getInfixByClass(string $name): ?InfixExpressionParserInterface
public function getByName(string $interface, string $name): ?ExpressionParserInterface
{
return $this->parsersByClass[ExpressionParserType::Infix->value][$name] ?? null;
}
public function getInfix(string $name): ?InfixExpressionParserInterface
{
return
$this->parsers[ExpressionParserType::Infix->value][$name]
?? $this->aliases[ExpressionParserType::Infix->value][$name]
?? null
;
return $this->parsersByName[$interface][$name] ?? null;
}
public function getIterator(): \Traversable
{
foreach ($this->parsers as $parsers) {
foreach ($this->parsersByName as $parsers) {
// we don't yield the keys
yield from $parsers;
}
+4 -3
View File
@@ -16,6 +16,7 @@ use Twig\Error\SyntaxError;
use Twig\ExpressionParser\ExpressionParserInterface;
use Twig\ExpressionParser\ExpressionParsers;
use Twig\ExpressionParser\ExpressionParserType;
use Twig\ExpressionParser\InfixExpressionParserInterface;
use Twig\ExpressionParser\Prefix\LiteralExpressionParser;
use Twig\ExpressionParser\PrefixExpressionParserInterface;
use Twig\Node\BlockNode;
@@ -357,16 +358,16 @@ class Parser
public function parseExpression(int $precedence = 0): AbstractExpression
{
$token = $this->getCurrentToken();
if ($token->test(Token::OPERATOR_TYPE) && $ep = $this->parsers->getPrefix($token->getValue())) {
if ($token->test(Token::OPERATOR_TYPE) && $ep = $this->parsers->getByName(PrefixExpressionParserInterface::class, $token->getValue())) {
$this->getStream()->next();
$expr = $ep->parse($this, $token);
$this->checkPrecedenceDeprecations($ep, $expr);
} else {
$expr = $this->parsers->getPrefixByClass(LiteralExpressionParser::class)->parse($this, $token);
$expr = $this->parsers->getByClass(LiteralExpressionParser::class)->parse($this, $token);
}
$token = $this->getCurrentToken();
while ($token->test(Token::OPERATOR_TYPE) && ($ep = $this->parsers->getInfix($token->getValue())) && $ep->getPrecedence() >= $precedence) {
while ($token->test(Token::OPERATOR_TYPE) && ($ep = $this->parsers->getByName(InfixExpressionParserInterface::class, $token->getValue())) && $ep->getPrecedence() >= $precedence) {
$this->getStream()->next();
$expr = $ep->parse($this, $expr, $token);
$this->checkPrecedenceDeprecations($ep, $expr);
+1 -1
View File
@@ -35,7 +35,7 @@ final class ApplyTokenParser extends AbstractTokenParser
$lineno = $token->getLine();
$ref = new LocalVariable(null, $lineno);
$filter = $ref;
$op = $this->parser->getEnvironment()->getExpressionParsers()->getInfixByClass(FilterExpressionParser::class);
$op = $this->parser->getEnvironment()->getExpressionParsers()->getByClass(FilterExpressionParser::class);
while (true) {
$filter = $op->parse($this->parser, $filter, $this->parser->getCurrentToken());
if (!$this->parser->getStream()->test(Token::OPERATOR_TYPE, '|')) {
+4 -2
View File
@@ -19,7 +19,9 @@ use Twig\Environment;
use Twig\Error\RuntimeError;
use Twig\Error\SyntaxError;
use Twig\ExpressionParser\Infix\BinaryOperatorExpressionParser;
use Twig\ExpressionParser\InfixExpressionParserInterface;
use Twig\ExpressionParser\Prefix\UnaryOperatorExpressionParser;
use Twig\ExpressionParser\PrefixExpressionParserInterface;
use Twig\Extension\AbstractExtension;
use Twig\Extension\ExtensionInterface;
use Twig\Extension\GlobalsInterface;
@@ -309,8 +311,8 @@ class EnvironmentTest extends TestCase
$this->assertArrayHasKey('foo_filter', $twig->getFilters());
$this->assertArrayHasKey('foo_function', $twig->getFunctions());
$this->assertArrayHasKey('foo_test', $twig->getTests());
$this->assertNotNull($twig->getExpressionParsers()->getPrefix('foo_unary'));
$this->assertNotNull($twig->getExpressionParsers()->getInfix('foo_binary'));
$this->assertNotNull($twig->getExpressionParsers()->getByName(PrefixExpressionParserInterface::class, 'foo_unary'));
$this->assertNotNull($twig->getExpressionParsers()->getByName(InfixExpressionParserInterface::class, 'foo_binary'));
$this->assertArrayHasKey('foo_global', $twig->getGlobals());
$visitors = $twig->getNodeVisitors();
$found = false;