145 changed files with 11031 additions and 320 deletions
@ -0,0 +1,46 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2009 Fabien Potencier |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
|
|||
/** |
|||
* Autoloads Twig classes. |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
class Twig_Autoloader |
|||
{ |
|||
/** |
|||
* Registers Twig_Autoloader as an SPL autoloader. |
|||
*/ |
|||
static public function register() |
|||
{ |
|||
ini_set('unserialize_callback_func', 'spl_autoload_call'); |
|||
spl_autoload_register(array(new self, 'autoload')); |
|||
} |
|||
|
|||
/** |
|||
* Handles autoloading of classes. |
|||
* |
|||
* @param string $class A class name. |
|||
* |
|||
* @return boolean Returns true if the class has been loaded |
|||
*/ |
|||
static public function autoload($class) |
|||
{ |
|||
if (0 !== strpos($class, 'Twig')) { |
|||
return; |
|||
} |
|||
|
|||
if (is_file($file = dirname(__FILE__).'/../'.str_replace(array('_', "\0"), array('/', ''), $class).'.php')) { |
|||
require $file; |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,219 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2009 Fabien Potencier |
|||
* (c) 2009 Armin Ronacher |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
|
|||
/** |
|||
* Compiles a node to PHP code. |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
class Twig_Compiler implements Twig_CompilerInterface |
|||
{ |
|||
protected $lastLine; |
|||
protected $source; |
|||
protected $indentation; |
|||
protected $env; |
|||
|
|||
/** |
|||
* Constructor. |
|||
* |
|||
* @param Twig_Environment $env The twig environment instance |
|||
*/ |
|||
public function __construct(Twig_Environment $env) |
|||
{ |
|||
$this->env = $env; |
|||
} |
|||
|
|||
/** |
|||
* Returns the environment instance related to this compiler. |
|||
* |
|||
* @return Twig_Environment The environment instance |
|||
*/ |
|||
public function getEnvironment() |
|||
{ |
|||
return $this->env; |
|||
} |
|||
|
|||
/** |
|||
* Gets the current PHP code after compilation. |
|||
* |
|||
* @return string The PHP code |
|||
*/ |
|||
public function getSource() |
|||
{ |
|||
return $this->source; |
|||
} |
|||
|
|||
/** |
|||
* Compiles a node. |
|||
* |
|||
* @param Twig_NodeInterface $node The node to compile |
|||
* @param integer $indent The current indentation |
|||
* |
|||
* @return Twig_Compiler The current compiler instance |
|||
*/ |
|||
public function compile(Twig_NodeInterface $node, $indentation = 0) |
|||
{ |
|||
$this->lastLine = null; |
|||
$this->source = ''; |
|||
$this->indentation = $indentation; |
|||
|
|||
$node->compile($this); |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
public function subcompile(Twig_NodeInterface $node, $raw = true) |
|||
{ |
|||
if (false === $raw) { |
|||
$this->addIndentation(); |
|||
} |
|||
|
|||
$node->compile($this); |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* Adds a raw string to the compiled code. |
|||
* |
|||
* @param string $string The string |
|||
* |
|||
* @return Twig_Compiler The current compiler instance |
|||
*/ |
|||
public function raw($string) |
|||
{ |
|||
$this->source .= $string; |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* Writes a string to the compiled code by adding indentation. |
|||
* |
|||
* @return Twig_Compiler The current compiler instance |
|||
*/ |
|||
public function write() |
|||
{ |
|||
$strings = func_get_args(); |
|||
foreach ($strings as $string) { |
|||
$this->addIndentation(); |
|||
$this->source .= $string; |
|||
} |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
public function addIndentation() |
|||
{ |
|||
$this->source .= str_repeat(' ', $this->indentation * 4); |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* Adds a quoted string to the compiled code. |
|||
* |
|||
* @param string $string The string |
|||
* |
|||
* @return Twig_Compiler The current compiler instance |
|||
*/ |
|||
public function string($value) |
|||
{ |
|||
$this->source .= sprintf('"%s"', addcslashes($value, "\0\t\"\$\\")); |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* Returns a PHP representation of a given value. |
|||
* |
|||
* @param mixed $value The value to convert |
|||
* |
|||
* @return Twig_Compiler The current compiler instance |
|||
*/ |
|||
public function repr($value) |
|||
{ |
|||
if (is_int($value) || is_float($value)) { |
|||
$this->raw($value); |
|||
} else if (null === $value) { |
|||
$this->raw('null'); |
|||
} else if (is_bool($value)) { |
|||
$this->raw($value ? 'true' : 'false'); |
|||
} else if (is_array($value)) { |
|||
$this->raw('array('); |
|||
$i = 0; |
|||
foreach ($value as $key => $value) { |
|||
if ($i++) { |
|||
$this->raw(', '); |
|||
} |
|||
$this->repr($key); |
|||
$this->raw(' => '); |
|||
$this->repr($value); |
|||
} |
|||
$this->raw(')'); |
|||
} else { |
|||
$this->string($value); |
|||
} |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* Adds debugging information. |
|||
* |
|||
* @param Twig_NodeInterface $node The related twig node |
|||
* |
|||
* @return Twig_Compiler The current compiler instance |
|||
*/ |
|||
public function addDebugInfo(Twig_NodeInterface $node) |
|||
{ |
|||
if ($node->getLine() != $this->lastLine) { |
|||
$this->lastLine = $node->getLine(); |
|||
$this->write("// line {$node->getLine()}\n"); |
|||
} |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* Indents the generated code. |
|||
* |
|||
* @param integer $indent The number of indentation to add |
|||
* |
|||
* @return Twig_Compiler The current compiler instance |
|||
*/ |
|||
public function indent($step = 1) |
|||
{ |
|||
$this->indentation += $step; |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* Outdents the generated code. |
|||
* |
|||
* @param integer $indent The number of indentation to remove |
|||
* |
|||
* @return Twig_Compiler The current compiler instance |
|||
*/ |
|||
public function outdent($step = 1) |
|||
{ |
|||
$this->indentation -= $step; |
|||
|
|||
if ($this->indentation < 0) { |
|||
throw new Twig_Error('Unable to call outdent() as the indentation would become negative'); |
|||
} |
|||
|
|||
return $this; |
|||
} |
|||
} |
@ -0,0 +1,35 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2009 Fabien Potencier |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
|
|||
/** |
|||
* Interface implemented by compiler classes. |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
interface Twig_CompilerInterface |
|||
{ |
|||
/** |
|||
* Compiles a node. |
|||
* |
|||
* @param Twig_NodeInterface $node The node to compile |
|||
* |
|||
* @return Twig_CompilerInterface The current compiler instance |
|||
*/ |
|||
function compile(Twig_NodeInterface $node); |
|||
|
|||
/** |
|||
* Gets the current PHP code after compilation. |
|||
* |
|||
* @return string The PHP code |
|||
*/ |
|||
function getSource(); |
|||
} |
@ -0,0 +1,938 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2009 Fabien Potencier |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
|
|||
/** |
|||
* Stores the Twig configuration. |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
class Twig_Environment |
|||
{ |
|||
const VERSION = '1.2.0'; |
|||
|
|||
protected $charset; |
|||
protected $loader; |
|||
protected $debug; |
|||
protected $autoReload; |
|||
protected $cache; |
|||
protected $lexer; |
|||
protected $parser; |
|||
protected $compiler; |
|||
protected $baseTemplateClass; |
|||
protected $extensions; |
|||
protected $parsers; |
|||
protected $visitors; |
|||
protected $filters; |
|||
protected $tests; |
|||
protected $functions; |
|||
protected $globals; |
|||
protected $runtimeInitialized; |
|||
protected $loadedTemplates; |
|||
protected $strictVariables; |
|||
protected $unaryOperators; |
|||
protected $binaryOperators; |
|||
protected $templateClassPrefix = '__TwigTemplate_'; |
|||
protected $functionCallbacks; |
|||
protected $filterCallbacks; |
|||
|
|||
/** |
|||
* Constructor. |
|||
* |
|||
* Available options: |
|||
* |
|||
* * debug: When set to `true`, the generated templates have a __toString() |
|||
* method that you can use to display the generated nodes (default to |
|||
* false). |
|||
* |
|||
* * charset: The charset used by the templates (default to utf-8). |
|||
* |
|||
* * base_template_class: The base template class to use for generated |
|||
* templates (default to Twig_Template). |
|||
* |
|||
* * cache: An absolute path where to store the compiled templates, or |
|||
* false to disable compilation cache (default) |
|||
* |
|||
* * auto_reload: Whether to reload the template is the original source changed. |
|||
* If you don't provide the auto_reload option, it will be |
|||
* determined automatically base on the debug value. |
|||
* |
|||
* * strict_variables: Whether to ignore invalid variables in templates |
|||
* (default to false). |
|||
* |
|||
* * autoescape: Whether to enable auto-escaping (default to true); |
|||
* |
|||
* * optimizations: A flag that indicates which optimizations to apply |
|||
* (default to -1 which means that all optimizations are enabled; |
|||
* set it to 0 to disable) |
|||
* |
|||
* @param Twig_LoaderInterface $loader A Twig_LoaderInterface instance |
|||
* @param array $options An array of options |
|||
*/ |
|||
public function __construct(Twig_LoaderInterface $loader = null, $options = array()) |
|||
{ |
|||
if (null !== $loader) { |
|||
$this->setLoader($loader); |
|||
} |
|||
|
|||
$options = array_merge(array( |
|||
'debug' => false, |
|||
'charset' => 'UTF-8', |
|||
'base_template_class' => 'Twig_Template', |
|||
'strict_variables' => false, |
|||
'autoescape' => true, |
|||
'cache' => false, |
|||
'auto_reload' => null, |
|||
'optimizations' => -1, |
|||
), $options); |
|||
|
|||
$this->debug = (bool) $options['debug']; |
|||
$this->charset = $options['charset']; |
|||
$this->baseTemplateClass = $options['base_template_class']; |
|||
$this->autoReload = null === $options['auto_reload'] ? $this->debug : (bool) $options['auto_reload']; |
|||
$this->extensions = array( |
|||
'core' => new Twig_Extension_Core(), |
|||
'escaper' => new Twig_Extension_Escaper((bool) $options['autoescape']), |
|||
'optimizer' => new Twig_Extension_Optimizer($options['optimizations']), |
|||
); |
|||
$this->strictVariables = (bool) $options['strict_variables']; |
|||
$this->runtimeInitialized = false; |
|||
$this->setCache($options['cache']); |
|||
$this->functionCallbacks = array(); |
|||
$this->filterCallbacks = array(); |
|||
} |
|||
|
|||
/** |
|||
* Gets the base template class for compiled templates. |
|||
* |
|||
* @return string The base template class name |
|||
*/ |
|||
public function getBaseTemplateClass() |
|||
{ |
|||
return $this->baseTemplateClass; |
|||
} |
|||
|
|||
/** |
|||
* Sets the base template class for compiled templates. |
|||
* |
|||
* @param string $class The base template class name |
|||
*/ |
|||
public function setBaseTemplateClass($class) |
|||
{ |
|||
$this->baseTemplateClass = $class; |
|||
} |
|||
|
|||
/** |
|||
* Enables debugging mode. |
|||
*/ |
|||
public function enableDebug() |
|||
{ |
|||
$this->debug = true; |
|||
} |
|||
|
|||
/** |
|||
* Disables debugging mode. |
|||
*/ |
|||
public function disableDebug() |
|||
{ |
|||
$this->debug = false; |
|||
} |
|||
|
|||
/** |
|||
* Checks if debug mode is enabled. |
|||
* |
|||
* @return Boolean true if debug mode is enabled, false otherwise |
|||
*/ |
|||
public function isDebug() |
|||
{ |
|||
return $this->debug; |
|||
} |
|||
|
|||
/** |
|||
* Enables the auto_reload option. |
|||
*/ |
|||
public function enableAutoReload() |
|||
{ |
|||
$this->autoReload = true; |
|||
} |
|||
|
|||
/** |
|||
* Disables the auto_reload option. |
|||
*/ |
|||
public function disableAutoReload() |
|||
{ |
|||
$this->autoReload = false; |
|||
} |
|||
|
|||
/** |
|||
* Checks if the auto_reload option is enabled. |
|||
* |
|||
* @return Boolean true if auto_reload is enabled, false otherwise |
|||
*/ |
|||
public function isAutoReload() |
|||
{ |
|||
return $this->autoReload; |
|||
} |
|||
|
|||
/** |
|||
* Enables the strict_variables option. |
|||
*/ |
|||
public function enableStrictVariables() |
|||
{ |
|||
$this->strictVariables = true; |
|||
} |
|||
|
|||
/** |
|||
* Disables the strict_variables option. |
|||
*/ |
|||
public function disableStrictVariables() |
|||
{ |
|||
$this->strictVariables = false; |
|||
} |
|||
|
|||
/** |
|||
* Checks if the strict_variables option is enabled. |
|||
* |
|||
* @return Boolean true if strict_variables is enabled, false otherwise |
|||
*/ |
|||
public function isStrictVariables() |
|||
{ |
|||
return $this->strictVariables; |
|||
} |
|||
|
|||
/** |
|||
* Gets the cache directory or false if cache is disabled. |
|||
* |
|||
* @return string|false |
|||
*/ |
|||
public function getCache() |
|||
{ |
|||
return $this->cache; |
|||
} |
|||
|
|||
/** |
|||
* Sets the cache directory or false if cache is disabled. |
|||
* |
|||
* @param string|false $cache The absolute path to the compiled templates, |
|||
* or false to disable cache |
|||
*/ |
|||
public function setCache($cache) |
|||
{ |
|||
$this->cache = $cache ? $cache : false; |
|||
} |
|||
|
|||
/** |
|||
* Gets the cache filename for a given template. |
|||
* |
|||
* @param string $name The template name |
|||
* |
|||
* @return string The cache file name |
|||
*/ |
|||
public function getCacheFilename($name) |
|||
{ |
|||
if (false === $this->cache) { |
|||
return false; |
|||
} |
|||
|
|||
$class = substr($this->getTemplateClass($name), strlen($this->templateClassPrefix)); |
|||
|
|||
return $this->getCache().'/'.substr($class, 0, 2).'/'.substr($class, 2, 2).'/'.substr($class, 4).'.php'; |
|||
} |
|||
|
|||
/** |
|||
* Gets the template class associated with the given string. |
|||
* |
|||
* @param string $name The name for which to calculate the template class name |
|||
* |
|||
* @return string The template class name |
|||
*/ |
|||
public function getTemplateClass($name) |
|||
{ |
|||
return $this->templateClassPrefix.md5($this->loader->getCacheKey($name)); |
|||
} |
|||
|
|||
/** |
|||
* Gets the template class prefix. |
|||
* |
|||
* @return string The template class prefix |
|||
*/ |
|||
public function getTemplateClassPrefix() |
|||
{ |
|||
return $this->templateClassPrefix; |
|||
} |
|||
|
|||
/** |
|||
* Renders a template. |
|||
* |
|||
* @param string $name The template name |
|||
* @param array $context An array of parameters to pass to the template |
|||
* |
|||
* @return string The rendered template |
|||
*/ |
|||
public function render($name, array $context = array()) |
|||
{ |
|||
return $this->loadTemplate($name)->render($context); |
|||
} |
|||
|
|||
/** |
|||
* Loads a template by name. |
|||
* |
|||
* @param string $name The template name |
|||
* |
|||
* @return Twig_TemplateInterface A template instance representing the given template name |
|||
*/ |
|||
public function loadTemplate($name) |
|||
{ |
|||
$cls = $this->getTemplateClass($name); |
|||
|
|||
if (isset($this->loadedTemplates[$cls])) { |
|||
return $this->loadedTemplates[$cls]; |
|||
} |
|||
|
|||
if (!class_exists($cls, false)) { |
|||
if (false === $cache = $this->getCacheFilename($name)) { |
|||
eval('?>'.$this->compileSource($this->loader->getSource($name), $name)); |
|||
} else { |
|||
if (!is_file($cache) || ($this->isAutoReload() && !$this->loader->isFresh($name, filemtime($cache)))) { |
|||
$this->writeCacheFile($cache, $this->compileSource($this->loader->getSource($name), $name)); |
|||
} |
|||
|
|||
require_once $cache; |
|||
} |
|||
} |
|||
|
|||
if (!$this->runtimeInitialized) { |
|||
$this->initRuntime(); |
|||
} |
|||
|
|||
return $this->loadedTemplates[$cls] = new $cls($this); |
|||
} |
|||
|
|||
public function resolveTemplate($names) |
|||
{ |
|||
if (!is_array($names)) { |
|||
$names = array($names); |
|||
} |
|||
|
|||
foreach ($names as $name) { |
|||
if ($name instanceof Twig_Template) { |
|||
return $name; |
|||
} |
|||
|
|||
try { |
|||
return $this->loadTemplate($name); |
|||
} catch (Twig_Error_Loader $e) { |
|||
} |
|||
} |
|||
|
|||
if (1 === count($names)) { |
|||
throw $e; |
|||
} |
|||
|
|||
throw new Twig_Error_Loader(sprintf('Unable to find one of the following templates: "%s".', implode('", "', $names))); |
|||
} |
|||
|
|||
/** |
|||
* Clears the internal template cache. |
|||
*/ |
|||
public function clearTemplateCache() |
|||
{ |
|||
$this->loadedTemplates = array(); |
|||
} |
|||
|
|||
/** |
|||
* Clears the template cache files on the filesystem. |
|||
*/ |
|||
public function clearCacheFiles() |
|||
{ |
|||
if (false === $this->cache) { |
|||
return; |
|||
} |
|||
|
|||
foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($this->cache), RecursiveIteratorIterator::LEAVES_ONLY) as $file) { |
|||
if ($file->isFile()) { |
|||
@unlink($file->getPathname()); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Gets the Lexer instance. |
|||
* |
|||
* @return Twig_LexerInterface A Twig_LexerInterface instance |
|||
*/ |
|||
public function getLexer() |
|||
{ |
|||
if (null === $this->lexer) { |
|||
$this->lexer = new Twig_Lexer($this); |
|||
} |
|||
|
|||
return $this->lexer; |
|||
} |
|||
|
|||
/** |
|||
* Sets the Lexer instance. |
|||
* |
|||
* @param Twig_LexerInterface A Twig_LexerInterface instance |
|||
*/ |
|||
public function setLexer(Twig_LexerInterface $lexer) |
|||
{ |
|||
$this->lexer = $lexer; |
|||
} |
|||
|
|||
/** |
|||
* Tokenizes a source code. |
|||
* |
|||
* @param string $source The template source code |
|||
* @param string $name The template name |
|||
* |
|||
* @return Twig_TokenStream A Twig_TokenStream instance |
|||
*/ |
|||
public function tokenize($source, $name = null) |
|||
{ |
|||
return $this->getLexer()->tokenize($source, $name); |
|||
} |
|||
|
|||
/** |
|||
* Gets the Parser instance. |
|||
* |
|||
* @return Twig_ParserInterface A Twig_ParserInterface instance |
|||
*/ |
|||
public function getParser() |
|||
{ |
|||
if (null === $this->parser) { |
|||
$this->parser = new Twig_Parser($this); |
|||
} |
|||
|
|||
return $this->parser; |
|||
} |
|||
|
|||
/** |
|||
* Sets the Parser instance. |
|||
* |
|||
* @param Twig_ParserInterface A Twig_ParserInterface instance |
|||
*/ |
|||
public function setParser(Twig_ParserInterface $parser) |
|||
{ |
|||
$this->parser = $parser; |
|||
} |
|||
|
|||
/** |
|||
* Parses a token stream. |
|||
* |
|||
* @param Twig_TokenStream $tokens A Twig_TokenStream instance |
|||
* |
|||
* @return Twig_Node_Module A Node tree |
|||
*/ |
|||
public function parse(Twig_TokenStream $tokens) |
|||
{ |
|||
return $this->getParser()->parse($tokens); |
|||
} |
|||
|
|||
/** |
|||
* Gets the Compiler instance. |
|||
* |
|||
* @return Twig_CompilerInterface A Twig_CompilerInterface instance |
|||
*/ |
|||
public function getCompiler() |
|||
{ |
|||
if (null === $this->compiler) { |
|||
$this->compiler = new Twig_Compiler($this); |
|||
} |
|||
|
|||
return $this->compiler; |
|||
} |
|||
|
|||
/** |
|||
* Sets the Compiler instance. |
|||
* |
|||
* @param Twig_CompilerInterface $compiler A Twig_CompilerInterface instance |
|||
*/ |
|||
public function setCompiler(Twig_CompilerInterface $compiler) |
|||
{ |
|||
$this->compiler = $compiler; |
|||
} |
|||
|
|||
/** |
|||
* Compiles a Node. |
|||
* |
|||
* @param Twig_NodeInterface $node A Twig_NodeInterface instance |
|||
* |
|||
* @return string The compiled PHP source code |
|||
*/ |
|||
public function compile(Twig_NodeInterface $node) |
|||
{ |
|||
return $this->getCompiler()->compile($node)->getSource(); |
|||
} |
|||
|
|||
/** |
|||
* Compiles a template source code. |
|||
* |
|||
* @param string $source The template source code |
|||
* @param string $name The template name |
|||
* |
|||
* @return string The compiled PHP source code |
|||
*/ |
|||
public function compileSource($source, $name = null) |
|||
{ |
|||
try { |
|||
return $this->compile($this->parse($this->tokenize($source, $name))); |
|||
} catch (Twig_Error $e) { |
|||
$e->setTemplateFile($name); |
|||
throw $e; |
|||
} catch (Exception $e) { |
|||
throw new Twig_Error_Runtime(sprintf('An exception has been thrown during the compilation of a template ("%s").', $e->getMessage()), -1, $name, $e); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Sets the Loader instance. |
|||
* |
|||
* @param Twig_LoaderInterface $loader A Twig_LoaderInterface instance |
|||
*/ |
|||
public function setLoader(Twig_LoaderInterface $loader) |
|||
{ |
|||
$this->loader = $loader; |
|||
} |
|||
|
|||
/** |
|||
* Gets the Loader instance. |
|||
* |
|||
* @return Twig_LoaderInterface A Twig_LoaderInterface instance |
|||
*/ |
|||
public function getLoader() |
|||
{ |
|||
return $this->loader; |
|||
} |
|||
|
|||
/** |
|||
* Sets the default template charset. |
|||
* |
|||
* @param string $charset The default charset |
|||
*/ |
|||
public function setCharset($charset) |
|||
{ |
|||
$this->charset = $charset; |
|||
} |
|||
|
|||
/** |
|||
* Gets the default template charset. |
|||
* |
|||
* @return string The default charset |
|||
*/ |
|||
public function getCharset() |
|||
{ |
|||
return $this->charset; |
|||
} |
|||
|
|||
/** |
|||
* Initializes the runtime environment. |
|||
*/ |
|||
public function initRuntime() |
|||
{ |
|||
$this->runtimeInitialized = true; |
|||
|
|||
foreach ($this->getExtensions() as $extension) { |
|||
$extension->initRuntime($this); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Returns true if the given extension is registered. |
|||
* |
|||
* @param string $name The extension name |
|||
* |
|||
* @return Boolean Whether the extension is registered or not |
|||
*/ |
|||
public function hasExtension($name) |
|||
{ |
|||
return isset($this->extensions[$name]); |
|||
} |
|||
|
|||
/** |
|||
* Gets an extension by name. |
|||
* |
|||
* @param string $name The extension name |
|||
* |
|||
* @return Twig_ExtensionInterface A Twig_ExtensionInterface instance |
|||
*/ |
|||
public function getExtension($name) |
|||
{ |
|||
if (!isset($this->extensions[$name])) { |
|||
throw new Twig_Error_Runtime(sprintf('The "%s" extension is not enabled.', $name)); |
|||
} |
|||
|
|||
return $this->extensions[$name]; |
|||
} |
|||
|
|||
/** |
|||
* Registers an extension. |
|||
* |
|||
* @param Twig_ExtensionInterface $extension A Twig_ExtensionInterface instance |
|||
*/ |
|||
public function addExtension(Twig_ExtensionInterface $extension) |
|||
{ |
|||
$this->extensions[$extension->getName()] = $extension; |
|||
} |
|||
|
|||
/** |
|||
* Removes an extension by name. |
|||
* |
|||
* @param string $name The extension name |
|||
*/ |
|||
public function removeExtension($name) |
|||
{ |
|||
unset($this->extensions[$name]); |
|||
} |
|||
|
|||
/** |
|||
* Registers an array of extensions. |
|||
* |
|||
* @param array $extensions An array of extensions |
|||
*/ |
|||
public function setExtensions(array $extensions) |
|||
{ |
|||
foreach ($extensions as $extension) { |
|||
$this->addExtension($extension); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Returns all registered extensions. |
|||
* |
|||
* @return array An array of extensions |
|||
*/ |
|||
public function getExtensions() |
|||
{ |
|||
return $this->extensions; |
|||
} |
|||
|
|||
/** |
|||
* Registers a Token Parser. |
|||
* |
|||
* @param Twig_TokenParserInterface $parser A Twig_TokenParserInterface instance |
|||
*/ |
|||
public function addTokenParser(Twig_TokenParserInterface $parser) |
|||
{ |
|||
if (null === $this->parsers) { |
|||
$this->getTokenParsers(); |
|||
} |
|||
|
|||
$this->parsers->addTokenParser($parser); |
|||
} |
|||
|
|||
/** |
|||
* Gets the registered Token Parsers. |
|||
* |
|||
* @return Twig_TokenParserInterface[] An array of Twig_TokenParserInterface instances |
|||
*/ |
|||
public function getTokenParsers() |
|||
{ |
|||
if (null === $this->parsers) { |
|||
$this->parsers = new Twig_TokenParserBroker; |
|||
foreach ($this->getExtensions() as $extension) { |
|||
$parsers = $extension->getTokenParsers(); |
|||
foreach($parsers as $parser) { |
|||
if ($parser instanceof Twig_TokenParserInterface) { |
|||
$this->parsers->addTokenParser($parser); |
|||
} else if ($parser instanceof Twig_TokenParserBrokerInterface) { |
|||
$this->parsers->addTokenParserBroker($parser); |
|||
} else { |
|||
throw new Twig_Error_Runtime('getTokenParsers() must return an array of Twig_TokenParserInterface or Twig_TokenParserBrokerInterface instances'); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
return $this->parsers; |
|||
} |
|||
|
|||
/** |
|||
* Registers a Node Visitor. |
|||
* |
|||
* @param Twig_NodeVisitorInterface $visitor A Twig_NodeVisitorInterface instance |
|||
*/ |
|||
public function addNodeVisitor(Twig_NodeVisitorInterface $visitor) |
|||
{ |
|||
if (null === $this->visitors) { |
|||
$this->getNodeVisitors(); |
|||
} |
|||
|
|||
$this->visitors[] = $visitor; |
|||
} |
|||
|
|||
/** |
|||
* Gets the registered Node Visitors. |
|||
* |
|||
* @return Twig_NodeVisitorInterface[] An array of Twig_NodeVisitorInterface instances |
|||
*/ |
|||
public function getNodeVisitors() |
|||
{ |
|||
if (null === $this->visitors) { |
|||
$this->visitors = array(); |
|||
foreach ($this->getExtensions() as $extension) { |
|||
$this->visitors = array_merge($this->visitors, $extension->getNodeVisitors()); |
|||
} |
|||
} |
|||
|
|||
return $this->visitors; |
|||
} |
|||
|
|||
/** |
|||
* Registers a Filter. |
|||
* |
|||
* @param string $name The filter name |
|||
* @param Twig_FilterInterface $visitor A Twig_FilterInterface instance |
|||
*/ |
|||
public function addFilter($name, Twig_FilterInterface $filter) |
|||
{ |
|||
if (null === $this->filters) { |
|||
$this->loadFilters(); |
|||
} |
|||
|
|||
$this->filters[$name] = $filter; |
|||
} |
|||
|
|||
/** |
|||
* Get a filter by name. |
|||
* |
|||
* Subclasses may override this method and load filters differently; |
|||
* so no list of filters is available. |
|||
* |
|||
* @param string $name The filter name |
|||
* |
|||
* @return Twig_Filter|false A Twig_Filter instance or false if the filter does not exists |
|||
*/ |
|||
public function getFilter($name) |
|||
{ |
|||
if (null === $this->filters) { |
|||
$this->loadFilters(); |
|||
} |
|||
|
|||
if (isset($this->filters[$name])) { |
|||
return $this->filters[$name]; |
|||
} |
|||
|
|||
foreach ($this->filterCallbacks as $callback) { |
|||
if (false !== $filter = call_user_func($callback, $name)) { |
|||
return $filter; |
|||
} |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
public function registerUndefinedFilterCallback($callable) |
|||
{ |
|||
$this->filterCallbacks[] = $callable; |
|||
} |
|||
|
|||
/** |
|||
* Gets the registered Filters. |
|||
* |
|||
* @return Twig_FilterInterface[] An array of Twig_FilterInterface instances |
|||
*/ |
|||
protected function loadFilters() |
|||
{ |
|||
$this->filters = array(); |
|||
foreach ($this->getExtensions() as $extension) { |
|||
$this->filters = array_merge($this->filters, $extension->getFilters()); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Registers a Test. |
|||
* |
|||
* @param string $name The test name |
|||
* @param Twig_TestInterface $visitor A Twig_TestInterface instance |
|||
*/ |
|||
public function addTest($name, Twig_TestInterface $test) |
|||
{ |
|||
if (null === $this->tests) { |
|||
$this->getTests(); |
|||
} |
|||
|
|||
$this->tests[$name] = $test; |
|||
} |
|||
|
|||
/** |
|||
* Gets the registered Tests. |
|||
* |
|||
* @return Twig_TestInterface[] An array of Twig_TestInterface instances |
|||
*/ |
|||
public function getTests() |
|||
{ |
|||
if (null === $this->tests) { |
|||
$this->tests = array(); |
|||
foreach ($this->getExtensions() as $extension) { |
|||
$this->tests = array_merge($this->tests, $extension->getTests()); |
|||
} |
|||
} |
|||
|
|||
return $this->tests; |
|||
} |
|||
|
|||
/** |
|||
* Registers a Function. |
|||
* |
|||
* @param string $name The function name |
|||
* @param Twig_FunctionInterface $function A Twig_FunctionInterface instance |
|||
*/ |
|||
public function addFunction($name, Twig_FunctionInterface $function) |
|||
{ |
|||
if (null === $this->functions) { |
|||
$this->loadFunctions(); |
|||
} |
|||
|
|||
$this->functions[$name] = $function; |
|||
} |
|||
|
|||
/** |
|||
* Get a function by name. |
|||
* |
|||
* Subclasses may override this method and load functions differently; |
|||
* so no list of functions is available. |
|||
* |
|||
* @param string $name function name |
|||
* |
|||
* @return Twig_Function|false A Twig_Function instance or false if the function does not exists |
|||
*/ |
|||
public function getFunction($name) |
|||
{ |
|||
if (null === $this->functions) { |
|||
$this->loadFunctions(); |
|||
} |
|||
|
|||
if (isset($this->functions[$name])) { |
|||
return $this->functions[$name]; |
|||
} |
|||
|
|||
foreach ($this->functionCallbacks as $callback) { |
|||
if (false !== $function = call_user_func($callback, $name)) { |
|||
return $function; |
|||
} |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
public function registerUndefinedFunctionCallback($callable) |
|||
{ |
|||
$this->functionCallbacks[] = $callable; |
|||
} |
|||
|
|||
protected function loadFunctions() |
|||
{ |
|||
$this->functions = array(); |
|||
foreach ($this->getExtensions() as $extension) { |
|||
$this->functions = array_merge($this->functions, $extension->getFunctions()); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Registers a Global. |
|||
* |
|||
* @param string $name The global name |
|||
* @param mixed $value The global value |
|||
*/ |
|||
public function addGlobal($name, $value) |
|||
{ |
|||
if (null === $this->globals) { |
|||
$this->getGlobals(); |
|||
} |
|||
|
|||
$this->globals[$name] = $value; |
|||
} |
|||
|
|||
/** |
|||
* Gets the registered Globals. |
|||
* |
|||
* @return array An array of globals |
|||
*/ |
|||
public function getGlobals() |
|||
{ |
|||
if (null === $this->globals) { |
|||
$this->globals = array(); |
|||
foreach ($this->getExtensions() as $extension) { |
|||
$this->globals = array_merge($this->globals, $extension->getGlobals()); |
|||
} |
|||
} |
|||
|
|||
return $this->globals; |
|||
} |
|||
|
|||
/** |
|||
* Gets the registered unary Operators. |
|||
* |
|||
* @return array An array of unary operators |
|||
*/ |
|||
public function getUnaryOperators() |
|||
{ |
|||
if (null === $this->unaryOperators) { |
|||
$this->initOperators(); |
|||
} |
|||
|
|||
return $this->unaryOperators; |
|||
} |
|||
|
|||
/** |
|||
* Gets the registered binary Operators. |
|||
* |
|||
* @return array An array of binary operators |
|||
*/ |
|||
public function getBinaryOperators() |
|||
{ |
|||
if (null === $this->binaryOperators) { |
|||
$this->initOperators(); |
|||
} |
|||
|
|||
return $this->binaryOperators; |
|||
} |
|||
|
|||
protected function initOperators() |
|||
{ |
|||
$this->unaryOperators = array(); |
|||
$this->binaryOperators = array(); |
|||
foreach ($this->getExtensions() as $extension) { |
|||
$operators = $extension->getOperators(); |
|||
|
|||
if (!$operators) { |
|||
continue; |
|||
} |
|||
|
|||
if (2 !== count($operators)) { |
|||
throw new InvalidArgumentException(sprintf('"%s::getOperators()" does not return a valid operators array.', get_class($extension))); |
|||
} |
|||
|
|||
$this->unaryOperators = array_merge($this->unaryOperators, $operators[0]); |
|||
$this->binaryOperators = array_merge($this->binaryOperators, $operators[1]); |
|||
} |
|||
} |
|||
|
|||
protected function writeCacheFile($file, $content) |
|||
{ |
|||
if (!is_dir(dirname($file))) { |
|||
mkdir(dirname($file), 0777, true); |
|||
} |
|||
|
|||
$tmpFile = tempnam(dirname($file), basename($file)); |
|||
if (false !== @file_put_contents($tmpFile, $content)) { |
|||
// rename does not work on Win32 before 5.2.6 |
|||
if (@rename($tmpFile, $file) || (@copy($tmpFile, $file) && unlink($tmpFile))) { |
|||
chmod($file, 0644); |
|||
|
|||
return; |
|||
} |
|||
} |
|||
|
|||
throw new Twig_Error_Runtime(sprintf('Failed to write cache file "%s".', $file)); |
|||
} |
|||
} |
@ -0,0 +1,195 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2009 Fabien Potencier |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
|
|||
/** |
|||
* Twig base exception. |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
class Twig_Error extends Exception |
|||
{ |
|||
protected $lineno; |
|||
protected $filename; |
|||
protected $rawMessage; |
|||
protected $previous; |
|||
|
|||
/** |
|||
* Constructor. |
|||
* |
|||
* @param string $message The error message |
|||
* @param integer $lineno The template line where the error occurred |
|||
* @param string $filename The template file name where the error occurred |
|||
* @param Exception $previous The previous exception |
|||
*/ |
|||
public function __construct($message, $lineno = -1, $filename = null, Exception $previous = null) |
|||
{ |
|||
if (-1 === $lineno || null === $filename) { |
|||
list($lineno, $filename) = $this->findTemplateInfo(null !== $previous ? $previous : $this, $lineno, $filename); |
|||
} |
|||
|
|||
$this->lineno = $lineno; |
|||
$this->filename = $filename; |
|||
$this->rawMessage = $message; |
|||
|
|||
$this->updateRepr(); |
|||
|
|||
if (version_compare(PHP_VERSION, '5.3.0', '<')) { |
|||
$this->previous = $previous; |
|||
parent::__construct($this->message); |
|||
} else { |
|||
parent::__construct($this->message, 0, $previous); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Gets the raw message. |
|||
* |
|||
* @return string The raw message |
|||
*/ |
|||
public function getRawMessage() |
|||
{ |
|||
return $this->rawMessage; |
|||
} |
|||
|
|||
/** |
|||
* Gets the filename where the error occurred. |
|||
* |
|||
* @return string The filename |
|||
*/ |
|||
public function getTemplateFile() |
|||
{ |
|||
return $this->filename; |
|||
} |
|||
|
|||
/** |
|||
* Sets the filename where the error occurred. |
|||
* |
|||
* @param string $filename The filename |
|||
*/ |
|||
public function setTemplateFile($filename) |
|||
{ |
|||
$this->filename = $filename; |
|||
|
|||
$this->updateRepr(); |
|||
} |
|||
|
|||
/** |
|||
* Gets the template line where the error occurred. |
|||
* |
|||
* @return integer The template line |
|||
*/ |
|||
public function getTemplateLine() |
|||
{ |
|||
return $this->lineno; |
|||
} |
|||
|
|||
/** |
|||
* Sets the template line where the error occurred. |
|||
* |
|||
* @param integer $lineno The template line |
|||
*/ |
|||
public function setTemplateLine($lineno) |
|||
{ |
|||
$this->lineno = $lineno; |
|||
|
|||
$this->updateRepr(); |
|||
} |
|||
|
|||
/** |
|||
* For PHP < 5.3.0, provides access to the getPrevious() method. |
|||
* |
|||
* @param string $method The method name |
|||
* @param array $arguments The parameters to be passed to the method |
|||
* |
|||
* @return Exception The previous exception or null |
|||
*/ |
|||
public function __call($method, $arguments) |
|||
{ |
|||
if ('getprevious' == strtolower($method)) { |
|||
return $this->previous; |
|||
} |
|||
|
|||
throw new BadMethodCallException(sprintf('Method "Twig_Error::%s()" does not exist.', $method)); |
|||
} |
|||
|
|||
protected function updateRepr() |
|||
{ |
|||
$this->message = $this->rawMessage; |
|||
|
|||
$dot = false; |
|||
if ('.' === substr($this->message, -1)) { |
|||
$this->message = substr($this->message, 0, -1); |
|||
$dot = true; |
|||
} |
|||
|
|||
if (null !== $this->filename) { |
|||
$this->message .= sprintf(' in %s', json_encode($this->filename)); |
|||
} |
|||
|
|||
if ($this->lineno >= 0) { |
|||
$this->message .= sprintf(' at line %d', $this->lineno); |
|||
} |
|||
|
|||
if ($dot) { |
|||
$this->message .= '.'; |
|||
} |
|||
} |
|||
|
|||
protected function findTemplateInfo(Exception $e, $currentLine, $currentFile) |
|||
{ |
|||
if (!function_exists('token_get_all')) { |
|||
return array($currentLine, $currentFile); |
|||
} |
|||
|
|||
$traces = $e->getTrace(); |
|||
foreach ($traces as $i => $trace) { |
|||
if (!isset($trace['class']) || 'Twig_Template' === $trace['class']) { |
|||
continue; |
|||
} |
|||
|
|||
$r = new ReflectionClass($trace['class']); |
|||
if (!$r->implementsInterface('Twig_TemplateInterface')) { |
|||
continue; |
|||
} |
|||
|
|||
if (!is_file($r->getFilename())) { |
|||
// probably an eval()'d code |
|||
return array($currentLine, $currentFile); |
|||
} |
|||
|
|||
if (0 === $i) { |
|||
$line = $e->getLine(); |
|||
} else { |
|||
$line = isset($traces[$i - 1]['line']) ? $traces[$i - 1]['line'] : -log(0); |
|||
} |
|||
|
|||
$tokens = token_get_all(file_get_contents($r->getFilename())); |
|||
$templateline = -1; |
|||
$template = null; |
|||
foreach ($tokens as $token) { |
|||
if (isset($token[2]) && $token[2] >= $line) { |
|||
return array($templateline, $template); |
|||
} |
|||
|
|||
if (T_COMMENT === $token[0] && null === $template && preg_match('#/\* +(.+) +\*/#', $token[1], $match)) { |
|||
$template = $match[1]; |
|||
} elseif (T_COMMENT === $token[0] && preg_match('#^//\s*line (\d+)\s*$#', $token[1], $match)) { |
|||
$templateline = $match[1]; |
|||
} |
|||
} |
|||
|
|||
return array($currentLine, $template); |
|||
} |
|||
|
|||
return array($currentLine, $currentFile); |
|||
} |
|||
} |
@ -0,0 +1,20 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2010 Fabien Potencier |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
|
|||
/** |
|||
* Exception thrown when an error occurs during template loading. |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
class Twig_Error_Loader extends Twig_Error |
|||
{ |
|||
} |
@ -0,0 +1,21 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2009 Fabien Potencier |
|||
* (c) 2009 Armin Ronacher |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
|
|||
/** |
|||
* Exception thrown when an error occurs at runtime. |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
class Twig_Error_Runtime extends Twig_Error |
|||
{ |
|||
} |
@ -0,0 +1,21 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2009 Fabien Potencier |
|||
* (c) 2009 Armin Ronacher |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
|
|||
/** |
|||
* Exception thrown when a syntax error occurs during lexing or parsing of a template. |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
class Twig_Error_Syntax extends Twig_Error |
|||
{ |
|||
} |
@ -0,0 +1,382 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2009 Fabien Potencier |
|||
* (c) 2009 Armin Ronacher |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
|
|||
/** |
|||
* Parses expressions. |
|||
* |
|||
* This parser implements a "Precedence climbing" algorithm. |
|||
* |
|||
* @see http://www.engr.mun.ca/~theo/Misc/exp_parsing.htm |
|||
* @see http://en.wikipedia.org/wiki/Operator-precedence_parser |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
class Twig_ExpressionParser |
|||
{ |
|||
const OPERATOR_LEFT = 1; |
|||
const OPERATOR_RIGHT = 2; |
|||
|
|||
protected $parser; |
|||
protected $unaryOperators; |
|||
protected $binaryOperators; |
|||
|
|||
public function __construct(Twig_Parser $parser, array $unaryOperators, array $binaryOperators) |
|||
{ |
|||
$this->parser = $parser; |
|||
$this->unaryOperators = $unaryOperators; |
|||
$this->binaryOperators = $binaryOperators; |
|||
} |
|||
|
|||
public function parseExpression($precedence = 0) |
|||
{ |
|||
$expr = $this->getPrimary(); |
|||
$token = $this->parser->getCurrentToken(); |
|||
while ($this->isBinary($token) && $this->binaryOperators[$token->getValue()]['precedence'] >= $precedence) { |
|||
$op = $this->binaryOperators[$token->getValue()]; |
|||
$this->parser->getStream()->next(); |
|||
|
|||
if (isset($op['callable'])) { |
|||
$expr = call_user_func($op['callable'], $this->parser, $expr); |
|||
} else { |
|||
$expr1 = $this->parseExpression(self::OPERATOR_LEFT === $op['associativity'] ? $op['precedence'] + 1 : $op['precedence']); |
|||
$class = $op['class']; |
|||
$expr = new $class($expr, $expr1, $token->getLine()); |
|||
} |
|||
|
|||
$token = $this->parser->getCurrentToken(); |
|||
} |
|||
|
|||
if (0 === $precedence) { |
|||
return $this->parseConditionalExpression($expr); |
|||
} |
|||
|
|||
return $expr; |
|||
} |
|||
|
|||
protected function getPrimary() |
|||
{ |
|||
$token = $this->parser->getCurrentToken(); |
|||
|
|||
if ($this->isUnary($token)) { |
|||
$operator = $this->unaryOperators[$token->getValue()]; |
|||
$this->parser->getStream()->next(); |
|||
$expr = $this->parseExpression($operator['precedence']); |
|||
$class = $operator['class']; |
|||
|
|||
return $this->parsePostfixExpression(new $class($expr, $token->getLine())); |
|||
} elseif ($token->test(Twig_Token::PUNCTUATION_TYPE, '(')) { |
|||
$this->parser->getStream()->next(); |
|||
$expr = $this->parseExpression(); |
|||
$this->parser->getStream()->expect(Twig_Token::PUNCTUATION_TYPE, ')', 'An opened parenthesis is not properly closed'); |
|||
|
|||
return $this->parsePostfixExpression($expr); |
|||
} |
|||
|
|||
return $this->parsePrimaryExpression(); |
|||
} |
|||
|
|||
protected function parseConditionalExpression($expr) |
|||
{ |
|||
while ($this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, '?')) { |
|||
$this->parser->getStream()->next(); |
|||
$expr2 = $this->parseExpression(); |
|||
$this->parser->getStream()->expect(Twig_Token::PUNCTUATION_TYPE, ':', 'The ternary operator must have a default value'); |
|||
$expr3 = $this->parseExpression(); |
|||
|
|||
$expr = new Twig_Node_Expression_Conditional($expr, $expr2, $expr3, $this->parser->getCurrentToken()->getLine()); |
|||
} |
|||
|
|||
return $expr; |
|||
} |
|||
|
|||
protected function isUnary(Twig_Token $token) |
|||
{ |
|||
return $token->test(Twig_Token::OPERATOR_TYPE) && isset($this->unaryOperators[$token->getValue()]); |
|||
} |
|||
|
|||
protected function isBinary(Twig_Token $token) |
|||
{ |
|||
return $token->test(Twig_Token::OPERATOR_TYPE) && isset($this->binaryOperators[$token->getValue()]); |
|||
} |
|||
|
|||
public function parsePrimaryExpression() |
|||
{ |
|||
$token = $this->parser->getCurrentToken(); |
|||
switch ($token->getType()) { |
|||
case Twig_Token::NAME_TYPE: |
|||
$this->parser->getStream()->next(); |
|||
switch ($token->getValue()) { |
|||
case 'true': |
|||
case 'TRUE': |
|||
$node = new Twig_Node_Expression_Constant(true, $token->getLine()); |
|||
break; |
|||
|
|||
case 'false': |
|||
case 'FALSE': |
|||
$node = new Twig_Node_Expression_Constant(false, $token->getLine()); |
|||
break; |
|||
|
|||
case 'none': |
|||
case 'NONE': |
|||
case 'null': |
|||
case 'NULL': |
|||
$node = new Twig_Node_Expression_Constant(null, $token->getLine()); |
|||
break; |
|||
|
|||
default: |
|||
if ('(' === $this->parser->getCurrentToken()->getValue()) { |
|||
$node = $this->getFunctionNode($token->getValue(), $token->getLine()); |
|||
} else { |
|||
$node = new Twig_Node_Expression_Name($token->getValue(), $token->getLine()); |
|||
} |
|||
} |
|||
break; |
|||
|
|||
case Twig_Token::NUMBER_TYPE: |
|||
case Twig_Token::STRING_TYPE: |
|||
$this->parser->getStream()->next(); |
|||
$node = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine()); |
|||
break; |
|||
|
|||
default: |
|||
if ($token->test(Twig_Token::PUNCTUATION_TYPE, '[')) { |
|||
$node = $this->parseArrayExpression(); |
|||
} elseif ($token->test(Twig_Token::PUNCTUATION_TYPE, '{')) { |
|||
$node = $this->parseHashExpression(); |
|||
} else { |
|||
throw new Twig_Error_Syntax(sprintf('Unexpected token "%s" of value "%s"', Twig_Token::typeToEnglish($token->getType(), $token->getLine()), $token->getValue()), $token->getLine()); |
|||
} |
|||
} |
|||
|
|||
return $this->parsePostfixExpression($node); |
|||
} |
|||
|
|||
public function parseArrayExpression() |
|||
{ |
|||
$stream = $this->parser->getStream(); |
|||
$stream->expect(Twig_Token::PUNCTUATION_TYPE, '[', 'An array element was expected'); |
|||
$elements = array(); |
|||
while (!$stream->test(Twig_Token::PUNCTUATION_TYPE, ']')) { |
|||
if (!empty($elements)) { |
|||
$stream->expect(Twig_Token::PUNCTUATION_TYPE, ',', 'An array element must be followed by a comma'); |
|||
|
|||
// trailing ,? |
|||
if ($stream->test(Twig_Token::PUNCTUATION_TYPE, ']')) { |
|||
break; |
|||
} |
|||
} |
|||
|
|||
$elements[] = $this->parseExpression(); |
|||
} |
|||
$stream->expect(Twig_Token::PUNCTUATION_TYPE, ']', 'An opened array is not properly closed'); |
|||
|
|||
return new Twig_Node_Expression_Array($elements, $stream->getCurrent()->getLine()); |
|||
} |
|||
|
|||
public function parseHashExpression() |
|||
{ |
|||
$stream = $this->parser->getStream(); |
|||
$stream->expect(Twig_Token::PUNCTUATION_TYPE, '{', 'A hash element was expected'); |
|||
$elements = array(); |
|||
while (!$stream->test(Twig_Token::PUNCTUATION_TYPE, '}')) { |
|||
if (!empty($elements)) { |
|||
$stream->expect(Twig_Token::PUNCTUATION_TYPE, ',', 'A hash value must be followed by a comma'); |
|||
|
|||
// trailing ,? |
|||
if ($stream->test(Twig_Token::PUNCTUATION_TYPE, '}')) { |
|||
break; |
|||
} |
|||
} |
|||
|
|||
if (!$stream->test(Twig_Token::STRING_TYPE) && !$stream->test(Twig_Token::NUMBER_TYPE)) { |
|||
$current = $stream->getCurrent(); |
|||
throw new Twig_Error_Syntax(sprintf('A hash key must be a quoted string or a number (unexpected token "%s" of value "%s"', Twig_Token::typeToEnglish($current->getType(), $current->getLine()), $current->getValue()), $current->getLine()); |
|||
} |
|||
|
|||
$key = $stream->next()->getValue(); |
|||
$stream->expect(Twig_Token::PUNCTUATION_TYPE, ':', 'A hash key must be followed by a colon (:)'); |
|||
$elements[$key] = $this->parseExpression(); |
|||
} |
|||
$stream->expect(Twig_Token::PUNCTUATION_TYPE, '}', 'An opened hash is not properly closed'); |
|||
|
|||
return new Twig_Node_Expression_Array($elements, $stream->getCurrent()->getLine()); |
|||
} |
|||
|
|||
public function parsePostfixExpression($node) |
|||
{ |
|||
while (true) { |
|||
$token = $this->parser->getCurrentToken(); |
|||
if ($token->getType() == Twig_Token::PUNCTUATION_TYPE) { |
|||
if ('.' == $token->getValue() || '[' == $token->getValue()) { |
|||
$node = $this->parseSubscriptExpression($node); |
|||
} elseif ('|' == $token->getValue()) { |
|||
$node = $this->parseFilterExpression($node); |
|||
} else { |
|||
break; |
|||
} |
|||
} else { |
|||
break; |
|||
} |
|||
} |
|||
|
|||
return $node; |
|||
} |
|||
|
|||
public function getFunctionNode($name, $line) |
|||
{ |
|||
$args = $this->parseArguments(); |
|||
switch ($name) { |
|||
case 'parent': |
|||
if (!count($this->parser->getBlockStack())) { |
|||
throw new Twig_Error_Syntax('Calling "parent" outside a block is forbidden', $line); |
|||
} |
|||
|
|||
if (!$this->parser->getParent()) { |
|||
throw new Twig_Error_Syntax('Calling "parent" on a template that does not extend another one is forbidden', $line); |
|||
} |
|||
|
|||
return new Twig_Node_Expression_Parent($this->parser->peekBlockStack(), $line); |
|||
case 'block': |
|||
return new Twig_Node_Expression_BlockReference($args->getNode(0), false, $line); |
|||
case 'attribute': |
|||
if (count($args) < 2) { |
|||
throw new Twig_Error_Syntax('The "attribute" function takes at least two arguments (the variable and the attribute)', $line); |
|||
} |
|||
|
|||
return new Twig_Node_Expression_GetAttr($args->getNode(0), $args->getNode(1), count($args) > 2 ? $args->getNode(2) : new Twig_Node_Expression_Array(array(), $line), Twig_TemplateInterface::ANY_CALL, $line); |
|||
default: |
|||
if (null !== $alias = $this->parser->getImportedFunction($name)) { |
|||
return new Twig_Node_Expression_GetAttr($alias['node'], new Twig_Node_Expression_Constant($alias['name'], $line), $args, Twig_TemplateInterface::METHOD_CALL, $line); |
|||
} |
|||
|
|||
return new Twig_Node_Expression_Function($name, $args, $line); |
|||
} |
|||
} |
|||
|
|||
public function parseSubscriptExpression($node) |
|||
{ |
|||
$token = $this->parser->getStream()->next(); |
|||
$lineno = $token->getLine(); |
|||
$arguments = new Twig_Node(); |
|||
$type = Twig_TemplateInterface::ANY_CALL; |
|||
if ($token->getValue() == '.') { |
|||
$token = $this->parser->getStream()->next(); |
|||
if ( |
|||
$token->getType() == Twig_Token::NAME_TYPE |
|||
|| |
|||
$token->getType() == Twig_Token::NUMBER_TYPE |
|||
|| |
|||
($token->getType() == Twig_Token::OPERATOR_TYPE && preg_match(Twig_Lexer::REGEX_NAME, $token->getValue())) |
|||
) { |
|||
$arg = new Twig_Node_Expression_Constant($token->getValue(), $lineno); |
|||
|
|||
if ($this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, '(')) { |
|||
$type = Twig_TemplateInterface::METHOD_CALL; |
|||
$arguments = $this->parseArguments(); |
|||
} else { |
|||
$arguments = new Twig_Node(); |
|||
} |
|||
} else { |
|||
throw new Twig_Error_Syntax('Expected name or number', $lineno); |
|||
} |
|||
} else { |
|||
$type = Twig_TemplateInterface::ARRAY_CALL; |
|||
|
|||
$arg = $this->parseExpression(); |
|||
$this->parser->getStream()->expect(Twig_Token::PUNCTUATION_TYPE, ']'); |
|||
} |
|||
|
|||
return new Twig_Node_Expression_GetAttr($node, $arg, $arguments, $type, $lineno); |
|||
} |
|||
|
|||
public function parseFilterExpression($node) |
|||
{ |
|||
$this->parser->getStream()->next(); |
|||
|
|||
return $this->parseFilterExpressionRaw($node); |
|||
} |
|||
|
|||
public function parseFilterExpressionRaw($node, $tag = null) |
|||
{ |
|||
while (true) { |
|||
$token = $this->parser->getStream()->expect(Twig_Token::NAME_TYPE); |
|||
|
|||
$name = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine()); |
|||
if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, '(')) { |
|||
$arguments = new Twig_Node(); |
|||
} else { |
|||
$arguments = $this->parseArguments(); |
|||
} |
|||
|
|||
$node = new Twig_Node_Expression_Filter($node, $name, $arguments, $token->getLine(), $tag); |
|||
|
|||
if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, '|')) { |
|||
break; |
|||
} |
|||
|
|||
$this->parser->getStream()->next(); |
|||
} |
|||
|
|||
return $node; |
|||
} |
|||
|
|||
public function parseArguments() |
|||
{ |
|||
$args = array(); |
|||
$stream = $this->parser->getStream(); |
|||
|
|||
$stream->expect(Twig_Token::PUNCTUATION_TYPE, '(', 'A list of arguments must be opened by a parenthesis'); |
|||
while (!$stream->test(Twig_Token::PUNCTUATION_TYPE, ')')) { |
|||
if (!empty($args)) { |
|||
$stream->expect(Twig_Token::PUNCTUATION_TYPE, ',', 'Arguments must be separated by a comma'); |
|||
} |
|||
$args[] = $this->parseExpression(); |
|||
} |
|||
$stream->expect(Twig_Token::PUNCTUATION_TYPE, ')', 'A list of arguments must be closed by a parenthesis'); |
|||
|
|||
return new Twig_Node($args); |
|||
} |
|||
|
|||
public function parseAssignmentExpression() |
|||
{ |
|||
$targets = array(); |
|||
while (true) { |
|||
$token = $this->parser->getStream()->expect(Twig_Token::NAME_TYPE, null, 'Only variables can be assigned to'); |
|||
if (in_array($token->getValue(), array('true', 'false', 'none'))) { |
|||
throw new Twig_Error_Syntax(sprintf('You cannot assign a value to "%s"', $token->getValue()), $token->getLine()); |
|||
} |
|||
$targets[] = new Twig_Node_Expression_AssignName($token->getValue(), $token->getLine()); |
|||
|
|||
if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, ',')) { |
|||
break; |
|||
} |
|||
$this->parser->getStream()->next(); |
|||
} |
|||
|
|||
return new Twig_Node($targets); |
|||
} |
|||
|
|||
public function parseMultitargetExpression() |
|||
{ |
|||
$targets = array(); |
|||
while (true) { |
|||
$targets[] = $this->parseExpression(); |
|||
if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, ',')) { |
|||