vendor/twig/twig/src/ExpressionParser.php line 56

Open in your IDE?
  1. <?php
  2. /*
  3. * This file is part of Twig.
  4. *
  5. * (c) Fabien Potencier
  6. * (c) Armin Ronacher
  7. *
  8. * For the full copyright and license information, please view the LICENSE
  9. * file that was distributed with this source code.
  10. */
  11. namespace Twig;
  12. use Twig\Error\SyntaxError;
  13. use Twig\ExpressionParser\Infix\DotExpressionParser;
  14. use Twig\ExpressionParser\Infix\FilterExpressionParser;
  15. use Twig\ExpressionParser\Infix\SquareBracketExpressionParser;
  16. use Twig\Node\Expression\ArrayExpression;
  17. use Twig\Node\Expression\ConstantExpression;
  18. use Twig\Node\Expression\Unary\NegUnary;
  19. use Twig\Node\Expression\Unary\PosUnary;
  20. use Twig\Node\Expression\Unary\SpreadUnary;
  21. use Twig\Node\Expression\Variable\AssignContextVariable;
  22. use Twig\Node\Expression\Variable\ContextVariable;
  23. use Twig\Node\Node;
  24. use Twig\Node\Nodes;
  25. /**
  26. * Parses expressions.
  27. *
  28. * This parser implements a "Precedence climbing" algorithm.
  29. *
  30. * @see https://www.engr.mun.ca/~theo/Misc/exp_parsing.htm
  31. * @see https://en.wikipedia.org/wiki/Operator-precedence_parser
  32. *
  33. * @author Fabien Potencier <fabien@symfony.com>
  34. *
  35. * @deprecated since Twig 3.21
  36. */
  37. class ExpressionParser
  38. {
  39. /**
  40. * @deprecated since Twig 3.21
  41. */
  42. public const OPERATOR_LEFT = 1;
  43. /**
  44. * @deprecated since Twig 3.21
  45. */
  46. public const OPERATOR_RIGHT = 2;
  47. public function __construct(
  48. private Parser $parser,
  49. private Environment $env,
  50. ) {
  51. trigger_deprecation('twig/twig', '3.21', 'Class "%s" is deprecated, use "Parser::parseExpression()" instead.', __CLASS__);
  52. }
  53. public function parseExpression($precedence = 0)
  54. {
  55. if (\func_num_args() > 1) {
  56. trigger_deprecation('twig/twig', '3.15', 'Passing a second argument ($allowArrow) to "%s()" is deprecated.', __METHOD__);
  57. }
  58. trigger_deprecation('twig/twig', '3.21', 'The "%s()" method is deprecated, use "Parser::parseExpression()" instead.', __METHOD__);
  59. return $this->parser->parseExpression((int) $precedence);
  60. }
  61. /**
  62. * @deprecated since Twig 3.21
  63. */
  64. public function parsePrimaryExpression()
  65. {
  66. trigger_deprecation('twig/twig', '3.21', 'The "%s()" method is deprecated.', __METHOD__);
  67. return $this->parseExpression();
  68. }
  69. /**
  70. * @deprecated since Twig 3.21
  71. */
  72. public function parseStringExpression()
  73. {
  74. trigger_deprecation('twig/twig', '3.21', 'The "%s()" method is deprecated.', __METHOD__);
  75. return $this->parseExpression();
  76. }
  77. /**
  78. * @deprecated since Twig 3.11, use parseExpression() instead
  79. */
  80. public function parseArrayExpression()
  81. {
  82. trigger_deprecation('twig/twig', '3.11', 'Calling "%s()" is deprecated, use "parseExpression()" instead.', __METHOD__);
  83. return $this->parseExpression();
  84. }
  85. /**
  86. * @deprecated since Twig 3.21
  87. */
  88. public function parseSequenceExpression()
  89. {
  90. trigger_deprecation('twig/twig', '3.21', 'The "%s()" method is deprecated.', __METHOD__);
  91. return $this->parseExpression();
  92. }
  93. /**
  94. * @deprecated since Twig 3.11, use parseExpression() instead
  95. */
  96. public function parseHashExpression()
  97. {
  98. trigger_deprecation('twig/twig', '3.11', 'Calling "%s()" is deprecated, use "parseExpression()" instead.', __METHOD__);
  99. return $this->parseExpression();
  100. }
  101. /**
  102. * @deprecated since Twig 3.21
  103. */
  104. public function parseMappingExpression()
  105. {
  106. trigger_deprecation('twig/twig', '3.21', 'The "%s()" method is deprecated.', __METHOD__);
  107. return $this->parseExpression();
  108. }
  109. /**
  110. * @deprecated since Twig 3.21
  111. */
  112. public function parsePostfixExpression($node)
  113. {
  114. trigger_deprecation('twig/twig', '3.21', 'The "%s()" method is deprecated.', __METHOD__);
  115. while (true) {
  116. $token = $this->parser->getCurrentToken();
  117. if ($token->test(Token::PUNCTUATION_TYPE)) {
  118. if ('.' == $token->getValue() || '[' == $token->getValue()) {
  119. $node = $this->parseSubscriptExpression($node);
  120. } elseif ('|' == $token->getValue()) {
  121. $node = $this->parseFilterExpression($node);
  122. } else {
  123. break;
  124. }
  125. } else {
  126. break;
  127. }
  128. }
  129. return $node;
  130. }
  131. /**
  132. * @deprecated since Twig 3.21
  133. */
  134. public function parseSubscriptExpression($node)
  135. {
  136. trigger_deprecation('twig/twig', '3.21', 'The "%s()" method is deprecated.', __METHOD__);
  137. $parsers = new \ReflectionProperty($this->parser, 'parsers');
  138. if ('.' === $this->parser->getStream()->next()->getValue()) {
  139. return $parsers->getValue($this->parser)->getByClass(DotExpressionParser::class)->parse($this->parser, $node, $this->parser->getCurrentToken());
  140. }
  141. return $parsers->getValue($this->parser)->getByClass(SquareBracketExpressionParser::class)->parse($this->parser, $node, $this->parser->getCurrentToken());
  142. }
  143. /**
  144. * @deprecated since Twig 3.21
  145. */
  146. public function parseFilterExpression($node)
  147. {
  148. trigger_deprecation('twig/twig', '3.21', 'The "%s()" method is deprecated.', __METHOD__);
  149. $this->parser->getStream()->next();
  150. return $this->parseFilterExpressionRaw($node);
  151. }
  152. /**
  153. * @deprecated since Twig 3.21
  154. */
  155. public function parseFilterExpressionRaw($node)
  156. {
  157. trigger_deprecation('twig/twig', '3.21', 'The "%s()" method is deprecated.', __METHOD__);
  158. $parsers = new \ReflectionProperty($this->parser, 'parsers');
  159. $op = $parsers->getValue($this->parser)->getByClass(FilterExpressionParser::class);
  160. while (true) {
  161. $node = $op->parse($this->parser, $node, $this->parser->getCurrentToken());
  162. if (!$this->parser->getStream()->test(Token::OPERATOR_TYPE, '|')) {
  163. break;
  164. }
  165. $this->parser->getStream()->next();
  166. }
  167. return $node;
  168. }
  169. /**
  170. * Parses arguments.
  171. *
  172. * @return Node
  173. *
  174. * @throws SyntaxError
  175. *
  176. * @deprecated since Twig 3.19 Use Twig\ExpressionParser\Infix\ArgumentsTrait::parseNamedArguments() instead
  177. */
  178. public function parseArguments()
  179. {
  180. trigger_deprecation('twig/twig', '3.19', \sprintf('The "%s()" method is deprecated, use "Twig\ExpressionParser\Infix\ArgumentsTrait::parseNamedArguments()" instead.', __METHOD__));
  181. $parsePrimary = new \ReflectionMethod($this->parser, 'parsePrimary');
  182. $namedArguments = false;
  183. $definition = false;
  184. if (\func_num_args() > 1) {
  185. $definition = func_get_arg(1);
  186. }
  187. if (\func_num_args() > 0) {
  188. trigger_deprecation('twig/twig', '3.15', 'Passing arguments to "%s()" is deprecated.', __METHOD__);
  189. $namedArguments = func_get_arg(0);
  190. }
  191. $args = [];
  192. $stream = $this->parser->getStream();
  193. $stream->expect(Token::OPERATOR_TYPE, '(', 'A list of arguments must begin with an opening parenthesis');
  194. $hasSpread = false;
  195. while (!$stream->test(Token::PUNCTUATION_TYPE, ')')) {
  196. if ($args) {
  197. $stream->expect(Token::PUNCTUATION_TYPE, ',', 'Arguments must be separated by a comma');
  198. // if the comma above was a trailing comma, early exit the argument parse loop
  199. if ($stream->test(Token::PUNCTUATION_TYPE, ')')) {
  200. break;
  201. }
  202. }
  203. if ($definition) {
  204. $token = $stream->expect(Token::NAME_TYPE, null, 'An argument must be a name');
  205. $value = new ContextVariable($token->getValue(), $this->parser->getCurrentToken()->getLine());
  206. } else {
  207. if ($stream->nextIf(Token::SPREAD_TYPE)) {
  208. $hasSpread = true;
  209. $value = new SpreadUnary($this->parseExpression(), $stream->getCurrent()->getLine());
  210. } elseif ($hasSpread) {
  211. throw new SyntaxError('Normal arguments must be placed before argument unpacking.', $stream->getCurrent()->getLine(), $stream->getSourceContext());
  212. } else {
  213. $value = $this->parseExpression();
  214. }
  215. }
  216. $name = null;
  217. if ($namedArguments && (($token = $stream->nextIf(Token::OPERATOR_TYPE, '=')) || (!$definition && $token = $stream->nextIf(Token::PUNCTUATION_TYPE, ':')))) {
  218. if (!$value instanceof ContextVariable) {
  219. throw new SyntaxError(\sprintf('A parameter name must be a string, "%s" given.', $value::class), $token->getLine(), $stream->getSourceContext());
  220. }
  221. $name = $value->getAttribute('name');
  222. if ($definition) {
  223. $value = $parsePrimary->invoke($this->parser);
  224. if (!$this->checkConstantExpression($value)) {
  225. throw new SyntaxError('A default value for an argument must be a constant (a boolean, a string, a number, a sequence, or a mapping).', $token->getLine(), $stream->getSourceContext());
  226. }
  227. } else {
  228. $value = $this->parseExpression();
  229. }
  230. }
  231. if ($definition) {
  232. if (null === $name) {
  233. $name = $value->getAttribute('name');
  234. $value = new ConstantExpression(null, $this->parser->getCurrentToken()->getLine());
  235. $value->setAttribute('is_implicit', true);
  236. }
  237. $args[$name] = $value;
  238. } else {
  239. if (null === $name) {
  240. $args[] = $value;
  241. } else {
  242. $args[$name] = $value;
  243. }
  244. }
  245. }
  246. $stream->expect(Token::PUNCTUATION_TYPE, ')', 'A list of arguments must be closed by a parenthesis');
  247. return new Nodes($args);
  248. }
  249. /**
  250. * @deprecated since Twig 3.21, use "AbstractTokenParser::parseAssignmentExpression()" instead
  251. */
  252. public function parseAssignmentExpression()
  253. {
  254. trigger_deprecation('twig/twig', '3.21', 'The "%s()" method is deprecated, use "AbstractTokenParser::parseAssignmentExpression()" instead.', __METHOD__);
  255. $stream = $this->parser->getStream();
  256. $targets = [];
  257. while (true) {
  258. $token = $this->parser->getCurrentToken();
  259. if ($stream->test(Token::OPERATOR_TYPE) && preg_match(Lexer::REGEX_NAME, $token->getValue())) {
  260. // in this context, string operators are variable names
  261. $this->parser->getStream()->next();
  262. } else {
  263. $stream->expect(Token::NAME_TYPE, null, 'Only variables can be assigned to');
  264. }
  265. $targets[] = new AssignContextVariable($token->getValue(), $token->getLine());
  266. if (!$stream->nextIf(Token::PUNCTUATION_TYPE, ',')) {
  267. break;
  268. }
  269. }
  270. return new Nodes($targets);
  271. }
  272. /**
  273. * @deprecated since Twig 3.21
  274. */
  275. public function parseMultitargetExpression()
  276. {
  277. trigger_deprecation('twig/twig', '3.21', 'The "%s()" method is deprecated.', __METHOD__);
  278. $targets = [];
  279. while (true) {
  280. $targets[] = $this->parseExpression();
  281. if (!$this->parser->getStream()->nextIf(Token::PUNCTUATION_TYPE, ',')) {
  282. break;
  283. }
  284. }
  285. return new Nodes($targets);
  286. }
  287. // checks that the node only contains "constant" elements
  288. // to be removed in 4.0
  289. private function checkConstantExpression(Node $node): bool
  290. {
  291. if (!($node instanceof ConstantExpression || $node instanceof ArrayExpression
  292. || $node instanceof NegUnary || $node instanceof PosUnary
  293. )) {
  294. return false;
  295. }
  296. foreach ($node as $n) {
  297. if (!$this->checkConstantExpression($n)) {
  298. return false;
  299. }
  300. }
  301. return true;
  302. }
  303. /**
  304. * @deprecated since Twig 3.19 Use Twig\ExpressionParser\Infix\ArgumentsTrait::parseNamedArguments() instead
  305. */
  306. public function parseOnlyArguments()
  307. {
  308. trigger_deprecation('twig/twig', '3.19', \sprintf('The "%s()" method is deprecated, use "Twig\ExpressionParser\Infix\ArgumentsTrait::parseNamedArguments()" instead.', __METHOD__));
  309. return $this->parseArguments();
  310. }
  311. }