annotate vendor/twig/twig/lib/Twig/Parser.php @ 12:7a779792577d

Update Drupal core to v8.4.5 (via Composer)
author Chris Cannam
date Fri, 23 Feb 2018 15:52:07 +0000
parents 4c8ae668cc8c
children 5fb285c0d0e3
rev   line source
Chris@0 1 <?php
Chris@0 2
Chris@0 3 /*
Chris@0 4 * This file is part of Twig.
Chris@0 5 *
Chris@0 6 * (c) Fabien Potencier
Chris@0 7 * (c) Armin Ronacher
Chris@0 8 *
Chris@0 9 * For the full copyright and license information, please view the LICENSE
Chris@0 10 * file that was distributed with this source code.
Chris@0 11 */
Chris@0 12
Chris@0 13 /**
Chris@0 14 * Default parser implementation.
Chris@0 15 *
Chris@0 16 * @author Fabien Potencier <fabien@symfony.com>
Chris@0 17 */
Chris@0 18 class Twig_Parser implements Twig_ParserInterface
Chris@0 19 {
Chris@0 20 protected $stack = array();
Chris@0 21 protected $stream;
Chris@0 22 protected $parent;
Chris@0 23 protected $handlers;
Chris@0 24 protected $visitors;
Chris@0 25 protected $expressionParser;
Chris@0 26 protected $blocks;
Chris@0 27 protected $blockStack;
Chris@0 28 protected $macros;
Chris@0 29 protected $env;
Chris@0 30 protected $reservedMacroNames;
Chris@0 31 protected $importedSymbols;
Chris@0 32 protected $traits;
Chris@0 33 protected $embeddedTemplates = array();
Chris@0 34
Chris@0 35 public function __construct(Twig_Environment $env)
Chris@0 36 {
Chris@0 37 $this->env = $env;
Chris@0 38 }
Chris@0 39
Chris@0 40 /**
Chris@0 41 * @deprecated since 1.27 (to be removed in 2.0)
Chris@0 42 */
Chris@0 43 public function getEnvironment()
Chris@0 44 {
Chris@0 45 @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0.', E_USER_DEPRECATED);
Chris@0 46
Chris@0 47 return $this->env;
Chris@0 48 }
Chris@0 49
Chris@0 50 public function getVarName()
Chris@0 51 {
Chris@0 52 return sprintf('__internal_%s', hash('sha256', uniqid(mt_rand(), true), false));
Chris@0 53 }
Chris@0 54
Chris@0 55 /**
Chris@0 56 * @deprecated since 1.27 (to be removed in 2.0). Use $parser->getStream()->getSourceContext()->getPath() instead.
Chris@0 57 */
Chris@0 58 public function getFilename()
Chris@0 59 {
Chris@0 60 @trigger_error(sprintf('The "%s" method is deprecated since version 1.27 and will be removed in 2.0. Use $parser->getStream()->getSourceContext()->getPath() instead.', __METHOD__), E_USER_DEPRECATED);
Chris@0 61
Chris@0 62 return $this->stream->getSourceContext()->getName();
Chris@0 63 }
Chris@0 64
Chris@0 65 public function parse(Twig_TokenStream $stream, $test = null, $dropNeedle = false)
Chris@0 66 {
Chris@0 67 // push all variables into the stack to keep the current state of the parser
Chris@0 68 // using get_object_vars() instead of foreach would lead to https://bugs.php.net/71336
Chris@0 69 // This hack can be removed when min version if PHP 7.0
Chris@0 70 $vars = array();
Chris@0 71 foreach ($this as $k => $v) {
Chris@0 72 $vars[$k] = $v;
Chris@0 73 }
Chris@0 74
Chris@0 75 unset($vars['stack'], $vars['env'], $vars['handlers'], $vars['visitors'], $vars['expressionParser'], $vars['reservedMacroNames']);
Chris@0 76 $this->stack[] = $vars;
Chris@0 77
Chris@0 78 // tag handlers
Chris@0 79 if (null === $this->handlers) {
Chris@0 80 $this->handlers = $this->env->getTokenParsers();
Chris@0 81 $this->handlers->setParser($this);
Chris@0 82 }
Chris@0 83
Chris@0 84 // node visitors
Chris@0 85 if (null === $this->visitors) {
Chris@0 86 $this->visitors = $this->env->getNodeVisitors();
Chris@0 87 }
Chris@0 88
Chris@0 89 if (null === $this->expressionParser) {
Chris@0 90 $this->expressionParser = new Twig_ExpressionParser($this, $this->env);
Chris@0 91 }
Chris@0 92
Chris@0 93 $this->stream = $stream;
Chris@0 94 $this->parent = null;
Chris@0 95 $this->blocks = array();
Chris@0 96 $this->macros = array();
Chris@0 97 $this->traits = array();
Chris@0 98 $this->blockStack = array();
Chris@0 99 $this->importedSymbols = array(array());
Chris@0 100 $this->embeddedTemplates = array();
Chris@0 101
Chris@0 102 try {
Chris@0 103 $body = $this->subparse($test, $dropNeedle);
Chris@0 104
Chris@0 105 if (null !== $this->parent && null === $body = $this->filterBodyNodes($body)) {
Chris@0 106 $body = new Twig_Node();
Chris@0 107 }
Chris@0 108 } catch (Twig_Error_Syntax $e) {
Chris@0 109 if (!$e->getSourceContext()) {
Chris@0 110 $e->setSourceContext($this->stream->getSourceContext());
Chris@0 111 }
Chris@0 112
Chris@0 113 if (!$e->getTemplateLine()) {
Chris@0 114 $e->setTemplateLine($this->stream->getCurrent()->getLine());
Chris@0 115 }
Chris@0 116
Chris@0 117 throw $e;
Chris@0 118 }
Chris@0 119
Chris@0 120 $node = new Twig_Node_Module(new Twig_Node_Body(array($body)), $this->parent, new Twig_Node($this->blocks), new Twig_Node($this->macros), new Twig_Node($this->traits), $this->embeddedTemplates, $stream->getSourceContext());
Chris@0 121
Chris@0 122 $traverser = new Twig_NodeTraverser($this->env, $this->visitors);
Chris@0 123
Chris@0 124 $node = $traverser->traverse($node);
Chris@0 125
Chris@0 126 // restore previous stack so previous parse() call can resume working
Chris@0 127 foreach (array_pop($this->stack) as $key => $val) {
Chris@0 128 $this->$key = $val;
Chris@0 129 }
Chris@0 130
Chris@0 131 return $node;
Chris@0 132 }
Chris@0 133
Chris@0 134 public function subparse($test, $dropNeedle = false)
Chris@0 135 {
Chris@0 136 $lineno = $this->getCurrentToken()->getLine();
Chris@0 137 $rv = array();
Chris@0 138 while (!$this->stream->isEOF()) {
Chris@0 139 switch ($this->getCurrentToken()->getType()) {
Chris@0 140 case Twig_Token::TEXT_TYPE:
Chris@0 141 $token = $this->stream->next();
Chris@0 142 $rv[] = new Twig_Node_Text($token->getValue(), $token->getLine());
Chris@0 143 break;
Chris@0 144
Chris@0 145 case Twig_Token::VAR_START_TYPE:
Chris@0 146 $token = $this->stream->next();
Chris@0 147 $expr = $this->expressionParser->parseExpression();
Chris@0 148 $this->stream->expect(Twig_Token::VAR_END_TYPE);
Chris@0 149 $rv[] = new Twig_Node_Print($expr, $token->getLine());
Chris@0 150 break;
Chris@0 151
Chris@0 152 case Twig_Token::BLOCK_START_TYPE:
Chris@0 153 $this->stream->next();
Chris@0 154 $token = $this->getCurrentToken();
Chris@0 155
Chris@0 156 if ($token->getType() !== Twig_Token::NAME_TYPE) {
Chris@0 157 throw new Twig_Error_Syntax('A block must start with a tag name.', $token->getLine(), $this->stream->getSourceContext());
Chris@0 158 }
Chris@0 159
Chris@0 160 if (null !== $test && call_user_func($test, $token)) {
Chris@0 161 if ($dropNeedle) {
Chris@0 162 $this->stream->next();
Chris@0 163 }
Chris@0 164
Chris@0 165 if (1 === count($rv)) {
Chris@0 166 return $rv[0];
Chris@0 167 }
Chris@0 168
Chris@0 169 return new Twig_Node($rv, array(), $lineno);
Chris@0 170 }
Chris@0 171
Chris@0 172 $subparser = $this->handlers->getTokenParser($token->getValue());
Chris@0 173 if (null === $subparser) {
Chris@0 174 if (null !== $test) {
Chris@0 175 $e = new Twig_Error_Syntax(sprintf('Unexpected "%s" tag', $token->getValue()), $token->getLine(), $this->stream->getSourceContext());
Chris@0 176
Chris@0 177 if (is_array($test) && isset($test[0]) && $test[0] instanceof Twig_TokenParserInterface) {
Chris@0 178 $e->appendMessage(sprintf(' (expecting closing tag for the "%s" tag defined near line %s).', $test[0]->getTag(), $lineno));
Chris@0 179 }
Chris@0 180 } else {
Chris@0 181 $e = new Twig_Error_Syntax(sprintf('Unknown "%s" tag.', $token->getValue()), $token->getLine(), $this->stream->getSourceContext());
Chris@0 182 $e->addSuggestions($token->getValue(), array_keys($this->env->getTags()));
Chris@0 183 }
Chris@0 184
Chris@0 185 throw $e;
Chris@0 186 }
Chris@0 187
Chris@0 188 $this->stream->next();
Chris@0 189
Chris@0 190 $node = $subparser->parse($token);
Chris@0 191 if (null !== $node) {
Chris@0 192 $rv[] = $node;
Chris@0 193 }
Chris@0 194 break;
Chris@0 195
Chris@0 196 default:
Chris@0 197 throw new Twig_Error_Syntax('Lexer or parser ended up in unsupported state.', $this->getCurrentToken()->getLine(), $this->stream->getSourceContext());
Chris@0 198 }
Chris@0 199 }
Chris@0 200
Chris@0 201 if (1 === count($rv)) {
Chris@0 202 return $rv[0];
Chris@0 203 }
Chris@0 204
Chris@0 205 return new Twig_Node($rv, array(), $lineno);
Chris@0 206 }
Chris@0 207
Chris@0 208 /**
Chris@0 209 * @deprecated since 1.27 (to be removed in 2.0)
Chris@0 210 */
Chris@0 211 public function addHandler($name, $class)
Chris@0 212 {
Chris@0 213 @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0.', E_USER_DEPRECATED);
Chris@0 214
Chris@0 215 $this->handlers[$name] = $class;
Chris@0 216 }
Chris@0 217
Chris@0 218 /**
Chris@0 219 * @deprecated since 1.27 (to be removed in 2.0)
Chris@0 220 */
Chris@0 221 public function addNodeVisitor(Twig_NodeVisitorInterface $visitor)
Chris@0 222 {
Chris@0 223 @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0.', E_USER_DEPRECATED);
Chris@0 224
Chris@0 225 $this->visitors[] = $visitor;
Chris@0 226 }
Chris@0 227
Chris@0 228 public function getBlockStack()
Chris@0 229 {
Chris@0 230 return $this->blockStack;
Chris@0 231 }
Chris@0 232
Chris@0 233 public function peekBlockStack()
Chris@0 234 {
Chris@0 235 return $this->blockStack[count($this->blockStack) - 1];
Chris@0 236 }
Chris@0 237
Chris@0 238 public function popBlockStack()
Chris@0 239 {
Chris@0 240 array_pop($this->blockStack);
Chris@0 241 }
Chris@0 242
Chris@0 243 public function pushBlockStack($name)
Chris@0 244 {
Chris@0 245 $this->blockStack[] = $name;
Chris@0 246 }
Chris@0 247
Chris@0 248 public function hasBlock($name)
Chris@0 249 {
Chris@0 250 return isset($this->blocks[$name]);
Chris@0 251 }
Chris@0 252
Chris@0 253 public function getBlock($name)
Chris@0 254 {
Chris@0 255 return $this->blocks[$name];
Chris@0 256 }
Chris@0 257
Chris@0 258 public function setBlock($name, Twig_Node_Block $value)
Chris@0 259 {
Chris@0 260 $this->blocks[$name] = new Twig_Node_Body(array($value), array(), $value->getTemplateLine());
Chris@0 261 }
Chris@0 262
Chris@0 263 public function hasMacro($name)
Chris@0 264 {
Chris@0 265 return isset($this->macros[$name]);
Chris@0 266 }
Chris@0 267
Chris@0 268 public function setMacro($name, Twig_Node_Macro $node)
Chris@0 269 {
Chris@0 270 if ($this->isReservedMacroName($name)) {
Chris@0 271 throw new Twig_Error_Syntax(sprintf('"%s" cannot be used as a macro name as it is a reserved keyword.', $name), $node->getTemplateLine(), $this->stream->getSourceContext());
Chris@0 272 }
Chris@0 273
Chris@0 274 $this->macros[$name] = $node;
Chris@0 275 }
Chris@0 276
Chris@0 277 public function isReservedMacroName($name)
Chris@0 278 {
Chris@0 279 if (null === $this->reservedMacroNames) {
Chris@0 280 $this->reservedMacroNames = array();
Chris@0 281 $r = new ReflectionClass($this->env->getBaseTemplateClass());
Chris@0 282 foreach ($r->getMethods() as $method) {
Chris@0 283 $methodName = strtolower($method->getName());
Chris@0 284
Chris@0 285 if ('get' === substr($methodName, 0, 3) && isset($methodName[3])) {
Chris@0 286 $this->reservedMacroNames[] = substr($methodName, 3);
Chris@0 287 }
Chris@0 288 }
Chris@0 289 }
Chris@0 290
Chris@0 291 return in_array(strtolower($name), $this->reservedMacroNames);
Chris@0 292 }
Chris@0 293
Chris@0 294 public function addTrait($trait)
Chris@0 295 {
Chris@0 296 $this->traits[] = $trait;
Chris@0 297 }
Chris@0 298
Chris@0 299 public function hasTraits()
Chris@0 300 {
Chris@0 301 return count($this->traits) > 0;
Chris@0 302 }
Chris@0 303
Chris@0 304 public function embedTemplate(Twig_Node_Module $template)
Chris@0 305 {
Chris@0 306 $template->setIndex(mt_rand());
Chris@0 307
Chris@0 308 $this->embeddedTemplates[] = $template;
Chris@0 309 }
Chris@0 310
Chris@0 311 public function addImportedSymbol($type, $alias, $name = null, Twig_Node_Expression $node = null)
Chris@0 312 {
Chris@0 313 $this->importedSymbols[0][$type][$alias] = array('name' => $name, 'node' => $node);
Chris@0 314 }
Chris@0 315
Chris@0 316 public function getImportedSymbol($type, $alias)
Chris@0 317 {
Chris@0 318 foreach ($this->importedSymbols as $functions) {
Chris@0 319 if (isset($functions[$type][$alias])) {
Chris@0 320 return $functions[$type][$alias];
Chris@0 321 }
Chris@0 322 }
Chris@0 323 }
Chris@0 324
Chris@0 325 public function isMainScope()
Chris@0 326 {
Chris@0 327 return 1 === count($this->importedSymbols);
Chris@0 328 }
Chris@0 329
Chris@0 330 public function pushLocalScope()
Chris@0 331 {
Chris@0 332 array_unshift($this->importedSymbols, array());
Chris@0 333 }
Chris@0 334
Chris@0 335 public function popLocalScope()
Chris@0 336 {
Chris@0 337 array_shift($this->importedSymbols);
Chris@0 338 }
Chris@0 339
Chris@0 340 /**
Chris@0 341 * @return Twig_ExpressionParser
Chris@0 342 */
Chris@0 343 public function getExpressionParser()
Chris@0 344 {
Chris@0 345 return $this->expressionParser;
Chris@0 346 }
Chris@0 347
Chris@0 348 public function getParent()
Chris@0 349 {
Chris@0 350 return $this->parent;
Chris@0 351 }
Chris@0 352
Chris@0 353 public function setParent($parent)
Chris@0 354 {
Chris@0 355 $this->parent = $parent;
Chris@0 356 }
Chris@0 357
Chris@0 358 /**
Chris@0 359 * @return Twig_TokenStream
Chris@0 360 */
Chris@0 361 public function getStream()
Chris@0 362 {
Chris@0 363 return $this->stream;
Chris@0 364 }
Chris@0 365
Chris@0 366 /**
Chris@0 367 * @return Twig_Token
Chris@0 368 */
Chris@0 369 public function getCurrentToken()
Chris@0 370 {
Chris@0 371 return $this->stream->getCurrent();
Chris@0 372 }
Chris@0 373
Chris@0 374 protected function filterBodyNodes(Twig_NodeInterface $node)
Chris@0 375 {
Chris@0 376 // check that the body does not contain non-empty output nodes
Chris@0 377 if (
Chris@0 378 ($node instanceof Twig_Node_Text && !ctype_space($node->getAttribute('data')))
Chris@0 379 ||
Chris@0 380 (!$node instanceof Twig_Node_Text && !$node instanceof Twig_Node_BlockReference && $node instanceof Twig_NodeOutputInterface)
Chris@0 381 ) {
Chris@0 382 if (false !== strpos((string) $node, chr(0xEF).chr(0xBB).chr(0xBF))) {
Chris@0 383 throw new Twig_Error_Syntax('A template that extends another one cannot start with a byte order mark (BOM); it must be removed.', $node->getTemplateLine(), $this->stream->getSourceContext());
Chris@0 384 }
Chris@0 385
Chris@0 386 throw new Twig_Error_Syntax('A template that extends another one cannot include contents outside Twig blocks. Did you forget to put the contents inside a {% block %} tag?', $node->getTemplateLine(), $this->stream->getSourceContext());
Chris@0 387 }
Chris@0 388
Chris@0 389 // bypass nodes that will "capture" the output
Chris@0 390 if ($node instanceof Twig_NodeCaptureInterface) {
Chris@0 391 return $node;
Chris@0 392 }
Chris@0 393
Chris@0 394 if ($node instanceof Twig_NodeOutputInterface) {
Chris@0 395 return;
Chris@0 396 }
Chris@0 397
Chris@0 398 foreach ($node as $k => $n) {
Chris@0 399 if (null !== $n && null === $this->filterBodyNodes($n)) {
Chris@0 400 $node->removeNode($k);
Chris@0 401 }
Chris@0 402 }
Chris@0 403
Chris@0 404 return $node;
Chris@0 405 }
Chris@0 406 }
Chris@12 407
Chris@12 408 class_alias('Twig_Parser', 'Twig\Parser', false);
Chris@12 409 class_exists('Twig_Node');
Chris@12 410 class_exists('Twig_TokenStream');