ExpressionParser.php 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648
  1. <?php
  2. /*
  3. * This file is part of Twig.
  4. *
  5. * (c) 2009 Fabien Potencier
  6. * (c) 2009 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. /**
  12. * Parses expressions.
  13. *
  14. * This parser implements a "Precedence climbing" algorithm.
  15. *
  16. * @see http://www.engr.mun.ca/~theo/Misc/exp_parsing.htm
  17. * @see http://en.wikipedia.org/wiki/Operator-precedence_parser
  18. *
  19. * @author Fabien Potencier <fabien@symfony.com>
  20. */
  21. class Twig_ExpressionParser
  22. {
  23. const OPERATOR_LEFT = 1;
  24. const OPERATOR_RIGHT = 2;
  25. protected $parser;
  26. protected $unaryOperators;
  27. protected $binaryOperators;
  28. public function __construct(Twig_Parser $parser, array $unaryOperators, array $binaryOperators)
  29. {
  30. $this->parser = $parser;
  31. $this->unaryOperators = $unaryOperators;
  32. $this->binaryOperators = $binaryOperators;
  33. }
  34. public function parseExpression($precedence = 0)
  35. {
  36. $expr = $this->getPrimary();
  37. $token = $this->parser->getCurrentToken();
  38. while ($this->isBinary($token) && $this->binaryOperators[$token->getValue()]['precedence'] >= $precedence) {
  39. $op = $this->binaryOperators[$token->getValue()];
  40. $this->parser->getStream()->next();
  41. if (isset($op['callable'])) {
  42. $expr = call_user_func($op['callable'], $this->parser, $expr);
  43. } else {
  44. $expr1 = $this->parseExpression(self::OPERATOR_LEFT === $op['associativity'] ? $op['precedence'] + 1 : $op['precedence']);
  45. $class = $op['class'];
  46. $expr = new $class($expr, $expr1, $token->getLine());
  47. }
  48. $token = $this->parser->getCurrentToken();
  49. }
  50. if (0 === $precedence) {
  51. return $this->parseConditionalExpression($expr);
  52. }
  53. return $expr;
  54. }
  55. protected function getPrimary()
  56. {
  57. $token = $this->parser->getCurrentToken();
  58. if ($this->isUnary($token)) {
  59. $operator = $this->unaryOperators[$token->getValue()];
  60. $this->parser->getStream()->next();
  61. $expr = $this->parseExpression($operator['precedence']);
  62. $class = $operator['class'];
  63. return $this->parsePostfixExpression(new $class($expr, $token->getLine()));
  64. } elseif ($token->test(Twig_Token::PUNCTUATION_TYPE, '(')) {
  65. $this->parser->getStream()->next();
  66. $expr = $this->parseExpression();
  67. $this->parser->getStream()->expect(Twig_Token::PUNCTUATION_TYPE, ')', 'An opened parenthesis is not properly closed');
  68. return $this->parsePostfixExpression($expr);
  69. }
  70. return $this->parsePrimaryExpression();
  71. }
  72. protected function parseConditionalExpression($expr)
  73. {
  74. while ($this->parser->getStream()->nextIf(Twig_Token::PUNCTUATION_TYPE, '?')) {
  75. if (!$this->parser->getStream()->nextIf(Twig_Token::PUNCTUATION_TYPE, ':')) {
  76. $expr2 = $this->parseExpression();
  77. if ($this->parser->getStream()->nextIf(Twig_Token::PUNCTUATION_TYPE, ':')) {
  78. $expr3 = $this->parseExpression();
  79. } else {
  80. $expr3 = new Twig_Node_Expression_Constant('', $this->parser->getCurrentToken()->getLine());
  81. }
  82. } else {
  83. $expr2 = $expr;
  84. $expr3 = $this->parseExpression();
  85. }
  86. $expr = new Twig_Node_Expression_Conditional($expr, $expr2, $expr3, $this->parser->getCurrentToken()->getLine());
  87. }
  88. return $expr;
  89. }
  90. protected function isUnary(Twig_Token $token)
  91. {
  92. return $token->test(Twig_Token::OPERATOR_TYPE) && isset($this->unaryOperators[$token->getValue()]);
  93. }
  94. protected function isBinary(Twig_Token $token)
  95. {
  96. return $token->test(Twig_Token::OPERATOR_TYPE) && isset($this->binaryOperators[$token->getValue()]);
  97. }
  98. public function parsePrimaryExpression()
  99. {
  100. $token = $this->parser->getCurrentToken();
  101. switch ($token->getType()) {
  102. case Twig_Token::NAME_TYPE:
  103. $this->parser->getStream()->next();
  104. switch ($token->getValue()) {
  105. case 'true':
  106. case 'TRUE':
  107. $node = new Twig_Node_Expression_Constant(true, $token->getLine());
  108. break;
  109. case 'false':
  110. case 'FALSE':
  111. $node = new Twig_Node_Expression_Constant(false, $token->getLine());
  112. break;
  113. case 'none':
  114. case 'NONE':
  115. case 'null':
  116. case 'NULL':
  117. $node = new Twig_Node_Expression_Constant(null, $token->getLine());
  118. break;
  119. default:
  120. if ('(' === $this->parser->getCurrentToken()->getValue()) {
  121. $node = $this->getFunctionNode($token->getValue(), $token->getLine());
  122. } else {
  123. $node = new Twig_Node_Expression_Name($token->getValue(), $token->getLine());
  124. }
  125. }
  126. break;
  127. case Twig_Token::NUMBER_TYPE:
  128. $this->parser->getStream()->next();
  129. $node = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine());
  130. break;
  131. case Twig_Token::STRING_TYPE:
  132. case Twig_Token::INTERPOLATION_START_TYPE:
  133. $node = $this->parseStringExpression();
  134. break;
  135. case Twig_Token::OPERATOR_TYPE:
  136. if (preg_match(Twig_Lexer::REGEX_NAME, $token->getValue(), $matches) && $matches[0] == $token->getValue()) {
  137. // in this context, string operators are variable names
  138. $this->parser->getStream()->next();
  139. $node = new Twig_Node_Expression_Name($token->getValue(), $token->getLine());
  140. break;
  141. } elseif (isset($this->unaryOperators[$token->getValue()])) {
  142. $class = $this->unaryOperators[$token->getValue()]['class'];
  143. $ref = new ReflectionClass($class);
  144. $negClass = 'Twig_Node_Expression_Unary_Neg';
  145. $posClass = 'Twig_Node_Expression_Unary_Pos';
  146. if (!(in_array($ref->getName(), array($negClass, $posClass)) || $ref->isSubclassOf($negClass) || $ref->isSubclassOf($posClass))) {
  147. throw new Twig_Error_Syntax(sprintf('Unexpected unary operator "%s".', $token->getValue()), $token->getLine(), $this->parser->getFilename());
  148. }
  149. $this->parser->getStream()->next();
  150. $expr = $this->parsePrimaryExpression();
  151. $node = new $class($expr, $token->getLine());
  152. break;
  153. }
  154. default:
  155. if ($token->test(Twig_Token::PUNCTUATION_TYPE, '[')) {
  156. $node = $this->parseArrayExpression();
  157. } elseif ($token->test(Twig_Token::PUNCTUATION_TYPE, '{')) {
  158. $node = $this->parseHashExpression();
  159. } else {
  160. throw new Twig_Error_Syntax(sprintf('Unexpected token "%s" of value "%s".', Twig_Token::typeToEnglish($token->getType()), $token->getValue()), $token->getLine(), $this->parser->getFilename());
  161. }
  162. }
  163. return $this->parsePostfixExpression($node);
  164. }
  165. public function parseStringExpression()
  166. {
  167. $stream = $this->parser->getStream();
  168. $nodes = array();
  169. // a string cannot be followed by another string in a single expression
  170. $nextCanBeString = true;
  171. while (true) {
  172. if ($nextCanBeString && $token = $stream->nextIf(Twig_Token::STRING_TYPE)) {
  173. $nodes[] = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine());
  174. $nextCanBeString = false;
  175. } elseif ($stream->nextIf(Twig_Token::INTERPOLATION_START_TYPE)) {
  176. $nodes[] = $this->parseExpression();
  177. $stream->expect(Twig_Token::INTERPOLATION_END_TYPE);
  178. $nextCanBeString = true;
  179. } else {
  180. break;
  181. }
  182. }
  183. $expr = array_shift($nodes);
  184. foreach ($nodes as $node) {
  185. $expr = new Twig_Node_Expression_Binary_Concat($expr, $node, $node->getLine());
  186. }
  187. return $expr;
  188. }
  189. public function parseArrayExpression()
  190. {
  191. $stream = $this->parser->getStream();
  192. $stream->expect(Twig_Token::PUNCTUATION_TYPE, '[', 'An array element was expected');
  193. $node = new Twig_Node_Expression_Array(array(), $stream->getCurrent()->getLine());
  194. $first = true;
  195. while (!$stream->test(Twig_Token::PUNCTUATION_TYPE, ']')) {
  196. if (!$first) {
  197. $stream->expect(Twig_Token::PUNCTUATION_TYPE, ',', 'An array element must be followed by a comma');
  198. // trailing ,?
  199. if ($stream->test(Twig_Token::PUNCTUATION_TYPE, ']')) {
  200. break;
  201. }
  202. }
  203. $first = false;
  204. $node->addElement($this->parseExpression());
  205. }
  206. $stream->expect(Twig_Token::PUNCTUATION_TYPE, ']', 'An opened array is not properly closed');
  207. return $node;
  208. }
  209. public function parseHashExpression()
  210. {
  211. $stream = $this->parser->getStream();
  212. $stream->expect(Twig_Token::PUNCTUATION_TYPE, '{', 'A hash element was expected');
  213. $node = new Twig_Node_Expression_Array(array(), $stream->getCurrent()->getLine());
  214. $first = true;
  215. while (!$stream->test(Twig_Token::PUNCTUATION_TYPE, '}')) {
  216. if (!$first) {
  217. $stream->expect(Twig_Token::PUNCTUATION_TYPE, ',', 'A hash value must be followed by a comma');
  218. // trailing ,?
  219. if ($stream->test(Twig_Token::PUNCTUATION_TYPE, '}')) {
  220. break;
  221. }
  222. }
  223. $first = false;
  224. // a hash key can be:
  225. //
  226. // * a number -- 12
  227. // * a string -- 'a'
  228. // * a name, which is equivalent to a string -- a
  229. // * an expression, which must be enclosed in parentheses -- (1 + 2)
  230. if (($token = $stream->nextIf(Twig_Token::STRING_TYPE)) || ($token = $stream->nextIf(Twig_Token::NAME_TYPE)) || $token = $stream->nextIf(Twig_Token::NUMBER_TYPE)) {
  231. $key = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine());
  232. } elseif ($stream->test(Twig_Token::PUNCTUATION_TYPE, '(')) {
  233. $key = $this->parseExpression();
  234. } else {
  235. $current = $stream->getCurrent();
  236. throw new Twig_Error_Syntax(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".', Twig_Token::typeToEnglish($current->getType()), $current->getValue()), $current->getLine(), $this->parser->getFilename());
  237. }
  238. $stream->expect(Twig_Token::PUNCTUATION_TYPE, ':', 'A hash key must be followed by a colon (:)');
  239. $value = $this->parseExpression();
  240. $node->addElement($value, $key);
  241. }
  242. $stream->expect(Twig_Token::PUNCTUATION_TYPE, '}', 'An opened hash is not properly closed');
  243. return $node;
  244. }
  245. public function parsePostfixExpression($node)
  246. {
  247. while (true) {
  248. $token = $this->parser->getCurrentToken();
  249. if ($token->getType() == Twig_Token::PUNCTUATION_TYPE) {
  250. if ('.' == $token->getValue() || '[' == $token->getValue()) {
  251. $node = $this->parseSubscriptExpression($node);
  252. } elseif ('|' == $token->getValue()) {
  253. $node = $this->parseFilterExpression($node);
  254. } else {
  255. break;
  256. }
  257. } else {
  258. break;
  259. }
  260. }
  261. return $node;
  262. }
  263. public function getFunctionNode($name, $line)
  264. {
  265. switch ($name) {
  266. case 'parent':
  267. $this->parseArguments();
  268. if (!count($this->parser->getBlockStack())) {
  269. throw new Twig_Error_Syntax('Calling "parent" outside a block is forbidden.', $line, $this->parser->getFilename());
  270. }
  271. if (!$this->parser->getParent() && !$this->parser->hasTraits()) {
  272. throw new Twig_Error_Syntax('Calling "parent" on a template that does not extend nor "use" another template is forbidden.', $line, $this->parser->getFilename());
  273. }
  274. return new Twig_Node_Expression_Parent($this->parser->peekBlockStack(), $line);
  275. case 'block':
  276. return new Twig_Node_Expression_BlockReference($this->parseArguments()->getNode(0), false, $line);
  277. case 'attribute':
  278. $args = $this->parseArguments();
  279. if (count($args) < 2) {
  280. throw new Twig_Error_Syntax('The "attribute" function takes at least two arguments (the variable and the attributes).', $line, $this->parser->getFilename());
  281. }
  282. return new Twig_Node_Expression_GetAttr($args->getNode(0), $args->getNode(1), count($args) > 2 ? $args->getNode(2) : null, Twig_Template::ANY_CALL, $line);
  283. default:
  284. if (null !== $alias = $this->parser->getImportedSymbol('function', $name)) {
  285. $arguments = new Twig_Node_Expression_Array(array(), $line);
  286. foreach ($this->parseArguments() as $n) {
  287. $arguments->addElement($n);
  288. }
  289. $node = new Twig_Node_Expression_MethodCall($alias['node'], $alias['name'], $arguments, $line);
  290. $node->setAttribute('safe', true);
  291. return $node;
  292. }
  293. $args = $this->parseArguments(true);
  294. $class = $this->getFunctionNodeClass($name, $line);
  295. return new $class($name, $args, $line);
  296. }
  297. }
  298. public function parseSubscriptExpression($node)
  299. {
  300. $stream = $this->parser->getStream();
  301. $token = $stream->next();
  302. $lineno = $token->getLine();
  303. $arguments = new Twig_Node_Expression_Array(array(), $lineno);
  304. $type = Twig_Template::ANY_CALL;
  305. if ($token->getValue() == '.') {
  306. $token = $stream->next();
  307. if (
  308. $token->getType() == Twig_Token::NAME_TYPE
  309. ||
  310. $token->getType() == Twig_Token::NUMBER_TYPE
  311. ||
  312. ($token->getType() == Twig_Token::OPERATOR_TYPE && preg_match(Twig_Lexer::REGEX_NAME, $token->getValue()))
  313. ) {
  314. $arg = new Twig_Node_Expression_Constant($token->getValue(), $lineno);
  315. if ($stream->test(Twig_Token::PUNCTUATION_TYPE, '(')) {
  316. $type = Twig_Template::METHOD_CALL;
  317. foreach ($this->parseArguments() as $n) {
  318. $arguments->addElement($n);
  319. }
  320. }
  321. } else {
  322. throw new Twig_Error_Syntax('Expected name or number', $lineno, $this->parser->getFilename());
  323. }
  324. if ($node instanceof Twig_Node_Expression_Name && null !== $this->parser->getImportedSymbol('template', $node->getAttribute('name'))) {
  325. if (!$arg instanceof Twig_Node_Expression_Constant) {
  326. throw new Twig_Error_Syntax(sprintf('Dynamic macro names are not supported (called on "%s").', $node->getAttribute('name')), $token->getLine(), $this->parser->getFilename());
  327. }
  328. $name = $arg->getAttribute('value');
  329. if ($this->parser->isReservedMacroName($name)) {
  330. throw new Twig_Error_Syntax(sprintf('"%s" cannot be called as macro as it is a reserved keyword.', $name), $token->getLine(), $this->parser->getFilename());
  331. }
  332. $node = new Twig_Node_Expression_MethodCall($node, 'get'.$name, $arguments, $lineno);
  333. $node->setAttribute('safe', true);
  334. return $node;
  335. }
  336. } else {
  337. $type = Twig_Template::ARRAY_CALL;
  338. // slice?
  339. $slice = false;
  340. if ($stream->test(Twig_Token::PUNCTUATION_TYPE, ':')) {
  341. $slice = true;
  342. $arg = new Twig_Node_Expression_Constant(0, $token->getLine());
  343. } else {
  344. $arg = $this->parseExpression();
  345. }
  346. if ($stream->nextIf(Twig_Token::PUNCTUATION_TYPE, ':')) {
  347. $slice = true;
  348. }
  349. if ($slice) {
  350. if ($stream->test(Twig_Token::PUNCTUATION_TYPE, ']')) {
  351. $length = new Twig_Node_Expression_Constant(null, $token->getLine());
  352. } else {
  353. $length = $this->parseExpression();
  354. }
  355. $class = $this->getFilterNodeClass('slice', $token->getLine());
  356. $arguments = new Twig_Node(array($arg, $length));
  357. $filter = new $class($node, new Twig_Node_Expression_Constant('slice', $token->getLine()), $arguments, $token->getLine());
  358. $stream->expect(Twig_Token::PUNCTUATION_TYPE, ']');
  359. return $filter;
  360. }
  361. $stream->expect(Twig_Token::PUNCTUATION_TYPE, ']');
  362. }
  363. return new Twig_Node_Expression_GetAttr($node, $arg, $arguments, $type, $lineno);
  364. }
  365. public function parseFilterExpression($node)
  366. {
  367. $this->parser->getStream()->next();
  368. return $this->parseFilterExpressionRaw($node);
  369. }
  370. public function parseFilterExpressionRaw($node, $tag = null)
  371. {
  372. while (true) {
  373. $token = $this->parser->getStream()->expect(Twig_Token::NAME_TYPE);
  374. $name = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine());
  375. if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, '(')) {
  376. $arguments = new Twig_Node();
  377. } else {
  378. $arguments = $this->parseArguments(true);
  379. }
  380. $class = $this->getFilterNodeClass($name->getAttribute('value'), $token->getLine());
  381. $node = new $class($node, $name, $arguments, $token->getLine(), $tag);
  382. if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, '|')) {
  383. break;
  384. }
  385. $this->parser->getStream()->next();
  386. }
  387. return $node;
  388. }
  389. /**
  390. * Parses arguments.
  391. *
  392. * @param bool $namedArguments Whether to allow named arguments or not
  393. * @param bool $definition Whether we are parsing arguments for a function definition
  394. *
  395. * @return Twig_Node
  396. *
  397. * @throws Twig_Error_Syntax
  398. */
  399. public function parseArguments($namedArguments = false, $definition = false)
  400. {
  401. $args = array();
  402. $stream = $this->parser->getStream();
  403. $stream->expect(Twig_Token::PUNCTUATION_TYPE, '(', 'A list of arguments must begin with an opening parenthesis');
  404. while (!$stream->test(Twig_Token::PUNCTUATION_TYPE, ')')) {
  405. if (!empty($args)) {
  406. $stream->expect(Twig_Token::PUNCTUATION_TYPE, ',', 'Arguments must be separated by a comma');
  407. }
  408. if ($definition) {
  409. $token = $stream->expect(Twig_Token::NAME_TYPE, null, 'An argument must be a name');
  410. $value = new Twig_Node_Expression_Name($token->getValue(), $this->parser->getCurrentToken()->getLine());
  411. } else {
  412. $value = $this->parseExpression();
  413. }
  414. $name = null;
  415. if ($namedArguments && $token = $stream->nextIf(Twig_Token::OPERATOR_TYPE, '=')) {
  416. if (!$value instanceof Twig_Node_Expression_Name) {
  417. throw new Twig_Error_Syntax(sprintf('A parameter name must be a string, "%s" given.', get_class($value)), $token->getLine(), $this->parser->getFilename());
  418. }
  419. $name = $value->getAttribute('name');
  420. if ($definition) {
  421. $value = $this->parsePrimaryExpression();
  422. if (!$this->checkConstantExpression($value)) {
  423. throw new Twig_Error_Syntax(sprintf('A default value for an argument must be a constant (a boolean, a string, a number, or an array).'), $token->getLine(), $this->parser->getFilename());
  424. }
  425. } else {
  426. $value = $this->parseExpression();
  427. }
  428. }
  429. if ($definition) {
  430. if (null === $name) {
  431. $name = $value->getAttribute('name');
  432. $value = new Twig_Node_Expression_Constant(null, $this->parser->getCurrentToken()->getLine());
  433. }
  434. $args[$name] = $value;
  435. } else {
  436. if (null === $name) {
  437. $args[] = $value;
  438. } else {
  439. $args[$name] = $value;
  440. }
  441. }
  442. }
  443. $stream->expect(Twig_Token::PUNCTUATION_TYPE, ')', 'A list of arguments must be closed by a parenthesis');
  444. return new Twig_Node($args);
  445. }
  446. public function parseAssignmentExpression()
  447. {
  448. $targets = array();
  449. while (true) {
  450. $token = $this->parser->getStream()->expect(Twig_Token::NAME_TYPE, null, 'Only variables can be assigned to');
  451. if (in_array($token->getValue(), array('true', 'false', 'none'))) {
  452. throw new Twig_Error_Syntax(sprintf('You cannot assign a value to "%s".', $token->getValue()), $token->getLine(), $this->parser->getFilename());
  453. }
  454. $targets[] = new Twig_Node_Expression_AssignName($token->getValue(), $token->getLine());
  455. if (!$this->parser->getStream()->nextIf(Twig_Token::PUNCTUATION_TYPE, ',')) {
  456. break;
  457. }
  458. }
  459. return new Twig_Node($targets);
  460. }
  461. public function parseMultitargetExpression()
  462. {
  463. $targets = array();
  464. while (true) {
  465. $targets[] = $this->parseExpression();
  466. if (!$this->parser->getStream()->nextIf(Twig_Token::PUNCTUATION_TYPE, ',')) {
  467. break;
  468. }
  469. }
  470. return new Twig_Node($targets);
  471. }
  472. protected function getFunctionNodeClass($name, $line)
  473. {
  474. $env = $this->parser->getEnvironment();
  475. if (false === $function = $env->getFunction($name)) {
  476. $e = new Twig_Error_Syntax(sprintf('Unknown "%s" function.', $name), $line, $this->parser->getFilename());
  477. $e->addSuggestions($name, array_keys($env->getFunctions()));
  478. throw $e;
  479. }
  480. if ($function instanceof Twig_SimpleFunction && $function->isDeprecated()) {
  481. $message = sprintf('Twig Function "%s" is deprecated', $function->getName());
  482. if (!is_bool($function->getDeprecatedVersion())) {
  483. $message .= sprintf(' since version %s', $function->getDeprecatedVersion());
  484. }
  485. if ($function->getAlternative()) {
  486. $message .= sprintf('. Use "%s" instead', $function->getAlternative());
  487. }
  488. $message .= sprintf(' in %s at line %d.', $this->parser->getFilename(), $line);
  489. @trigger_error($message, E_USER_DEPRECATED);
  490. }
  491. if ($function instanceof Twig_SimpleFunction) {
  492. return $function->getNodeClass();
  493. }
  494. return $function instanceof Twig_Function_Node ? $function->getClass() : 'Twig_Node_Expression_Function';
  495. }
  496. protected function getFilterNodeClass($name, $line)
  497. {
  498. $env = $this->parser->getEnvironment();
  499. if (false === $filter = $env->getFilter($name)) {
  500. $e = new Twig_Error_Syntax(sprintf('Unknown "%s" filter.', $name), $line, $this->parser->getFilename());
  501. $e->addSuggestions($name, array_keys($env->getFilters()));
  502. throw $e;
  503. }
  504. if ($filter instanceof Twig_SimpleFilter && $filter->isDeprecated()) {
  505. $message = sprintf('Twig Filter "%s" is deprecated', $filter->getName());
  506. if (!is_bool($filter->getDeprecatedVersion())) {
  507. $message .= sprintf(' since version %s', $filter->getDeprecatedVersion());
  508. }
  509. if ($filter->getAlternative()) {
  510. $message .= sprintf('. Use "%s" instead', $filter->getAlternative());
  511. }
  512. $message .= sprintf(' in %s at line %d.', $this->parser->getFilename(), $line);
  513. @trigger_error($message, E_USER_DEPRECATED);
  514. }
  515. if ($filter instanceof Twig_SimpleFilter) {
  516. return $filter->getNodeClass();
  517. }
  518. return $filter instanceof Twig_Filter_Node ? $filter->getClass() : 'Twig_Node_Expression_Filter';
  519. }
  520. // checks that the node only contains "constant" elements
  521. protected function checkConstantExpression(Twig_NodeInterface $node)
  522. {
  523. if (!($node instanceof Twig_Node_Expression_Constant || $node instanceof Twig_Node_Expression_Array
  524. || $node instanceof Twig_Node_Expression_Unary_Neg || $node instanceof Twig_Node_Expression_Unary_Pos
  525. )) {
  526. return false;
  527. }
  528. foreach ($node as $n) {
  529. if (!$this->checkConstantExpression($n)) {
  530. return false;
  531. }
  532. }
  533. return true;
  534. }
  535. }