diff options
Diffstat (limited to 'inc/lib/Twig/Parser.php')
| -rw-r--r-- | inc/lib/Twig/Parser.php | 126 |
1 files changed, 93 insertions, 33 deletions
diff --git a/inc/lib/Twig/Parser.php b/inc/lib/Twig/Parser.php index 8c982887..958e46b3 100644 --- a/inc/lib/Twig/Parser.php +++ b/inc/lib/Twig/Parser.php @@ -13,11 +13,11 @@ /** * Default parser implementation. * - * @package twig - * @author Fabien Potencier <fabien@symfony.com> + * @author Fabien Potencier <fabien@symfony.com> */ class Twig_Parser implements Twig_ParserInterface { + protected $stack = array(); protected $stream; protected $parent; protected $handlers; @@ -28,9 +28,9 @@ class Twig_Parser implements Twig_ParserInterface protected $macros; protected $env; protected $reservedMacroNames; - protected $importedFunctions; - protected $tmpVarCount; + protected $importedSymbols; protected $traits; + protected $embeddedTemplates = array(); /** * Constructor. @@ -42,28 +42,45 @@ class Twig_Parser implements Twig_ParserInterface $this->env = $env; } + public function getEnvironment() + { + return $this->env; + } + public function getVarName() { - return sprintf('__internal_%s_%d', substr($this->env->getTemplateClass($this->stream->getFilename()), strlen($this->env->getTemplateClassPrefix())), ++$this->tmpVarCount); + return sprintf('__internal_%s', hash('sha1', uniqid(mt_rand(), true), false)); + } + + public function getFilename() + { + return $this->stream->getFilename(); } /** * Converts a token stream to a node tree. * - * @param Twig_TokenStream $stream A token stream instance + * @param Twig_TokenStream $stream A token stream instance * * @return Twig_Node_Module A node tree */ - public function parse(Twig_TokenStream $stream) + public function parse(Twig_TokenStream $stream, $test = null, $dropNeedle = false) { - $this->tmpVarCount = 0; + // push all variables into the stack to keep the current state of the parser + $vars = get_object_vars($this); + unset($vars['stack'], $vars['env'], $vars['handlers'], $vars['visitors'], $vars['expressionParser']); + $this->stack[] = $vars; // tag handlers - $this->handlers = $this->env->getTokenParsers(); - $this->handlers->setParser($this); + if (null === $this->handlers) { + $this->handlers = $this->env->getTokenParsers(); + $this->handlers->setParser($this); + } // node visitors - $this->visitors = $this->env->getNodeVisitors(); + if (null === $this->visitors) { + $this->visitors = $this->env->getNodeVisitors(); + } if (null === $this->expressionParser) { $this->expressionParser = new Twig_ExpressionParser($this, $this->env->getUnaryOperators(), $this->env->getBinaryOperators()); @@ -75,10 +92,11 @@ class Twig_Parser implements Twig_ParserInterface $this->macros = array(); $this->traits = array(); $this->blockStack = array(); - $this->importedFunctions = array(array()); + $this->importedSymbols = array(array()); + $this->embeddedTemplates = array(); try { - $body = $this->subparse(null); + $body = $this->subparse($test, $dropNeedle); if (null !== $this->parent) { if (null === $body = $this->filterBodyNodes($body)) { @@ -86,18 +104,29 @@ class Twig_Parser implements Twig_ParserInterface } } } catch (Twig_Error_Syntax $e) { - if (null === $e->getTemplateFile()) { - $e->setTemplateFile($this->stream->getFilename()); + if (!$e->getTemplateFile()) { + $e->setTemplateFile($this->getFilename()); + } + + if (!$e->getTemplateLine()) { + $e->setTemplateLine($this->stream->getCurrent()->getLine()); } throw $e; } - $node = new Twig_Node_Module($body, $this->parent, new Twig_Node($this->blocks), new Twig_Node($this->macros), new Twig_Node($this->traits), $this->stream->getFilename()); + $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, $this->getFilename()); $traverser = new Twig_NodeTraverser($this->env, $this->visitors); - return $traverser->traverse($node); + $node = $traverser->traverse($node); + + // restore previous stack so previous parse() call can resume working + foreach (array_pop($this->stack) as $key => $val) { + $this->$key = $val; + } + + return $node; } public function subparse($test, $dropNeedle = false) @@ -123,7 +152,7 @@ class Twig_Parser implements Twig_ParserInterface $token = $this->getCurrentToken(); if ($token->getType() !== Twig_Token::NAME_TYPE) { - throw new Twig_Error_Syntax('A block must start with a tag name', $token->getLine(), $this->stream->getFilename()); + throw new Twig_Error_Syntax('A block must start with a tag name', $token->getLine(), $this->getFilename()); } if (null !== $test && call_user_func($test, $token)) { @@ -141,10 +170,20 @@ class Twig_Parser implements Twig_ParserInterface $subparser = $this->handlers->getTokenParser($token->getValue()); if (null === $subparser) { if (null !== $test) { - throw new Twig_Error_Syntax(sprintf('Unexpected tag name "%s" (expecting closing tag for the "%s" tag defined near line %s)', $token->getValue(), $test[0]->getTag(), $lineno), $token->getLine(), $this->stream->getFilename()); + $error = sprintf('Unexpected tag name "%s"', $token->getValue()); + if (is_array($test) && isset($test[0]) && $test[0] instanceof Twig_TokenParserInterface) { + $error .= sprintf(' (expecting closing tag for the "%s" tag defined near line %s)', $test[0]->getTag(), $lineno); + } + + throw new Twig_Error_Syntax($error, $token->getLine(), $this->getFilename()); } - throw new Twig_Error_Syntax(sprintf('Unknown tag name "%s"', $token->getValue()), $token->getLine(), $this->stream->getFilename()); + $message = sprintf('Unknown tag name "%s"', $token->getValue()); + if ($alternatives = $this->env->computeAlternatives($token->getValue(), array_keys($this->env->getTags()))) { + $message = sprintf('%s. Did you mean "%s"', $message, implode('", "', $alternatives)); + } + + throw new Twig_Error_Syntax($message, $token->getLine(), $this->getFilename()); } $this->stream->next(); @@ -156,7 +195,7 @@ class Twig_Parser implements Twig_ParserInterface break; default: - throw new Twig_Error_Syntax('Lexer or parser ended up in unsupported state.', -1, $this->stream->getFilename()); + throw new Twig_Error_Syntax('Lexer or parser ended up in unsupported state.', 0, $this->getFilename()); } } @@ -202,9 +241,14 @@ class Twig_Parser implements Twig_ParserInterface return isset($this->blocks[$name]); } + public function getBlock($name) + { + return $this->blocks[$name]; + } + public function setBlock($name, $value) { - $this->blocks[$name] = $value; + $this->blocks[$name] = new Twig_Node_Body(array($value), array(), $value->getLine()); } public function hasMacro($name) @@ -223,7 +267,7 @@ class Twig_Parser implements Twig_ParserInterface } if (in_array($name, $this->reservedMacroNames)) { - throw new Twig_Error_Syntax(sprintf('"%s" cannot be used as a macro name as it is a reserved keyword', $name), $node->getLine()); + throw new Twig_Error_Syntax(sprintf('"%s" cannot be used as a macro name as it is a reserved keyword', $name), $node->getLine(), $this->getFilename()); } $this->macros[$name] = $node; @@ -234,33 +278,45 @@ class Twig_Parser implements Twig_ParserInterface $this->traits[] = $trait; } - public function addImportedFunction($alias, $name, Twig_Node_Expression $node) + public function hasTraits() + { + return count($this->traits) > 0; + } + + public function embedTemplate(Twig_Node_Module $template) { - $this->importedFunctions[0][$alias] = array('name' => $name, 'node' => $node); + $template->setIndex(mt_rand()); + + $this->embeddedTemplates[] = $template; } - public function getImportedFunction($alias) + public function addImportedSymbol($type, $alias, $name = null, Twig_Node_Expression $node = null) { - foreach ($this->importedFunctions as $functions) { - if (isset($functions[$alias])) { - return $functions[$alias]; + $this->importedSymbols[0][$type][$alias] = array('name' => $name, 'node' => $node); + } + + public function getImportedSymbol($type, $alias) + { + foreach ($this->importedSymbols as $functions) { + if (isset($functions[$type][$alias])) { + return $functions[$type][$alias]; } } } public function isMainScope() { - return 1 === count($this->importedFunctions); + return 1 === count($this->importedSymbols); } public function pushLocalScope() { - array_unshift($this->importedFunctions, array()); + array_unshift($this->importedSymbols, array()); } public function popLocalScope() { - array_shift($this->importedFunctions); + array_shift($this->importedSymbols); } /** @@ -311,7 +367,11 @@ class Twig_Parser implements Twig_ParserInterface || (!$node instanceof Twig_Node_Text && !$node instanceof Twig_Node_BlockReference && $node instanceof Twig_NodeOutputInterface) ) { - throw new Twig_Error_Syntax('A template that extends another one cannot have a body.', $node->getLine(), $this->stream->getFilename()); + if (false !== strpos((string) $node, chr(0xEF).chr(0xBB).chr(0xBF))) { + throw new Twig_Error_Syntax('A template that extends another one cannot have a body but a byte order mark (BOM) has been detected; it must be removed.', $node->getLine(), $this->getFilename()); + } + + throw new Twig_Error_Syntax('A template that extends another one cannot have a body.', $node->getLine(), $this->getFilename()); } // bypass "set" nodes as they "capture" the output |
