You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by ih...@apache.org on 2012/05/22 16:42:31 UTC

svn commit: r1341499 [2/7] - in /logging/site/branches/experimental-twig-textile: ./ libs/Twig/ libs/Twig/lib/ libs/Twig/lib/Twig/ libs/Twig/lib/Twig/Error/ libs/Twig/lib/Twig/Extension/ libs/Twig/lib/Twig/Filter/ libs/Twig/lib/Twig/Function/ libs/Twig...

Added: logging/site/branches/experimental-twig-textile/libs/Twig/lib/Twig/Environment.php
URL: http://svn.apache.org/viewvc/logging/site/branches/experimental-twig-textile/libs/Twig/lib/Twig/Environment.php?rev=1341499&view=auto
==============================================================================
--- logging/site/branches/experimental-twig-textile/libs/Twig/lib/Twig/Environment.php (added)
+++ logging/site/branches/experimental-twig-textile/libs/Twig/lib/Twig/Environment.php Tue May 22 14:42:25 2012
@@ -0,0 +1,1106 @@
+<?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 <fa...@symfony.com>
+ */
+class Twig_Environment
+{
+    const VERSION = '1.8.1';
+
+    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;
+    protected $staging;
+
+    /**
+     * 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 html):
+     *                  * false: disable auto-escaping
+     *                  * true: equivalent to html
+     *                  * html, js: set the autoescaping to one of the supported strategies
+     *                  * PHP callback: a PHP callback that returns an escaping strategy based on the template "filename"
+     *
+     *  * 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'          => 'html',
+            '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($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();
+        $this->staging = array(
+            'functions'     => array(),
+            'filters'       => array(),
+            'tests'         => array(),
+            'token_parsers' => array(),
+            'visitors'      => array(),
+            'globals'       => 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
+     * @param integer $index The index if it is an embedded template
+     *
+     * @return string The template class name
+     */
+    public function getTemplateClass($name, $index = null)
+    {
+        return $this->templateClassPrefix.md5($this->loader->getCacheKey($name)).(null === $index ? '' : '_'.$index);
+    }
+
+    /**
+     * 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);
+    }
+
+    /**
+     * Displays a template.
+     *
+     * @param string $name    The template name
+     * @param array  $context An array of parameters to pass to the template
+     */
+    public function display($name, array $context = array())
+    {
+        $this->loadTemplate($name)->display($context);
+    }
+
+    /**
+     * Loads a template by name.
+     *
+     * @param string  $name  The template name
+     * @param integer $index The index if it is an embedded template
+     *
+     * @return Twig_TemplateInterface A template instance representing the given template name
+     */
+    public function loadTemplate($name, $index = null)
+    {
+        $cls = $this->getTemplateClass($name, $index);
+
+        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->isTemplateFresh($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);
+    }
+
+    /**
+     * Returns true if the template is still fresh.
+     *
+     * Besides checking the loader for freshness information,
+     * this method also checks if the enabled extensions have
+     * not changed.
+     *
+     * @param string    $name The template name
+     * @param timestamp $time The last modification time of the cached template
+     *
+     * @return Boolean true if the template is fresh, false otherwise
+     */
+    public function isTemplateFresh($name, $time)
+    {
+        foreach ($this->extensions as $extension) {
+            $r = new ReflectionObject($extension);
+            if (filemtime($r->getFileName()) > $time) {
+                return false;
+            }
+        }
+
+        return $this->loader->isFresh($name, $time);
+    }
+
+    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;
+        $this->parsers = null;
+        $this->visitors = null;
+        $this->filters = null;
+        $this->tests = null;
+        $this->functions = null;
+        $this->globals = null;
+    }
+
+    /**
+     * Removes an extension by name.
+     *
+     * @param string $name The extension name
+     */
+    public function removeExtension($name)
+    {
+        unset($this->extensions[$name]);
+        $this->parsers = null;
+        $this->visitors = null;
+        $this->filters = null;
+        $this->tests = null;
+        $this->functions = null;
+        $this->globals = null;
+    }
+
+    /**
+     * 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)
+    {
+        $this->staging['token_parsers'][] = $parser;
+        $this->parsers = null;
+    }
+
+    /**
+     * Gets the registered Token Parsers.
+     *
+     * @return Twig_TokenParserBrokerInterface A broker containing token parsers
+     */
+    public function getTokenParsers()
+    {
+        if (null === $this->parsers) {
+            $this->parsers = new Twig_TokenParserBroker();
+
+            if (isset($this->staging['token_parsers'])) {
+                foreach ($this->staging['token_parsers'] as $parser) {
+                    $this->parsers->addTokenParser($parser);
+                }
+            }
+
+            foreach ($this->getExtensions() as $extension) {
+                $parsers = $extension->getTokenParsers();
+                foreach($parsers as $parser) {
+                    if ($parser instanceof Twig_TokenParserInterface) {
+                        $this->parsers->addTokenParser($parser);
+                    } elseif ($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;
+    }
+
+    /**
+     * Gets registered tags.
+     *
+     * Be warned that this method cannot return tags defined by Twig_TokenParserBrokerInterface classes.
+     *
+     * @return Twig_TokenParserInterface[] An array of Twig_TokenParserInterface instances
+     */
+    public function getTags()
+    {
+        $tags = array();
+        foreach ($this->getTokenParsers()->getParsers() as $parser) {
+            if ($parser instanceof Twig_TokenParserInterface) {
+                $tags[$parser->getTag()] = $parser;
+            }
+        }
+
+        return $tags;
+    }
+
+    /**
+     * Registers a Node Visitor.
+     *
+     * @param Twig_NodeVisitorInterface $visitor A Twig_NodeVisitorInterface instance
+     */
+    public function addNodeVisitor(Twig_NodeVisitorInterface $visitor)
+    {
+        $this->staging['visitors'][] = $visitor;
+        $this->visitors = null;
+    }
+
+    /**
+     * Gets the registered Node Visitors.
+     *
+     * @return Twig_NodeVisitorInterface[] An array of Twig_NodeVisitorInterface instances
+     */
+    public function getNodeVisitors()
+    {
+        if (null === $this->visitors) {
+            foreach ($this->getExtensions() as $extension) {
+                foreach ($extension->getNodeVisitors() as $visitor) {
+                    $this->addNodeVisitor($visitor);
+                }
+            }
+
+            $this->visitors = $this->staging['visitors'];
+        }
+
+        return $this->visitors;
+    }
+
+    /**
+     * Registers a Filter.
+     *
+     * @param string               $name   The filter name
+     * @param Twig_FilterInterface $filter A Twig_FilterInterface instance
+     */
+    public function addFilter($name, Twig_FilterInterface $filter)
+    {
+        $this->staging['filters'][$name] = $filter;
+        $this->filters = null;
+    }
+
+    /**
+     * 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->getFilters();
+        }
+
+        if (isset($this->filters[$name])) {
+            return $this->filters[$name];
+        }
+
+        foreach ($this->filters as $pattern => $filter) {
+            $pattern = str_replace('\\*', '(.*?)', preg_quote($pattern, '#'), $count);
+
+            if ($count) {
+                if (preg_match('#^'.$pattern.'$#', $name, $matches)) {
+                    array_shift($matches);
+                    $filter->setArguments($matches);
+
+                    return $filter;
+                }
+            }
+        }
+
+        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.
+     *
+     * Be warned that this method cannot return filters defined with registerUndefinedFunctionCallback.
+     *
+     * @return Twig_FilterInterface[] An array of Twig_FilterInterface instances
+     *
+     * @see registerUndefinedFilterCallback
+     */
+    public function getFilters()
+    {
+        if (null === $this->filters) {
+            foreach ($this->getExtensions() as $extension) {
+                foreach ($extension->getFilters() as $name => $filter) {
+                    $this->addFilter($name, $filter);
+                }
+            }
+
+            $this->filters = $this->staging['filters'];
+        }
+
+        return $this->filters;
+    }
+
+    /**
+     * Registers a Test.
+     *
+     * @param string             $name The test name
+     * @param Twig_TestInterface $test A Twig_TestInterface instance
+     */
+    public function addTest($name, Twig_TestInterface $test)
+    {
+        $this->staging['tests'][$name] = $test;
+        $this->tests = null;
+    }
+
+    /**
+     * Gets the registered Tests.
+     *
+     * @return Twig_TestInterface[] An array of Twig_TestInterface instances
+     */
+    public function getTests()
+    {
+        if (null === $this->tests) {
+            foreach ($this->getExtensions() as $extension) {
+                foreach ($extension->getTests() as $name => $test) {
+                    $this->addTest($name, $test);
+                }
+            }
+
+            $this->tests = $this->staging['tests'];
+        }
+
+        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)
+    {
+        $this->staging['functions'][$name] = $function;
+        $this->functions = null;
+    }
+
+    /**
+     * 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->getFunctions();
+        }
+
+        if (isset($this->functions[$name])) {
+            return $this->functions[$name];
+        }
+
+        foreach ($this->functions as $pattern => $function) {
+            $pattern = str_replace('\\*', '(.*?)', preg_quote($pattern, '#'), $count);
+
+            if ($count) {
+                if (preg_match('#^'.$pattern.'$#', $name, $matches)) {
+                    array_shift($matches);
+                    $function->setArguments($matches);
+
+                    return $function;
+                }
+            }
+        }
+
+        foreach ($this->functionCallbacks as $callback) {
+            if (false !== $function = call_user_func($callback, $name)) {
+                return $function;
+            }
+        }
+
+        return false;
+    }
+
+    public function registerUndefinedFunctionCallback($callable)
+    {
+        $this->functionCallbacks[] = $callable;
+    }
+
+    /**
+     * Gets registered functions.
+     *
+     * Be warned that this method cannot return functions defined with registerUndefinedFunctionCallback.
+     *
+     * @return Twig_FunctionInterface[] An array of Twig_FunctionInterface instances
+     *
+     * @see registerUndefinedFunctionCallback
+     */
+    public function getFunctions()
+    {
+        if (null === $this->functions) {
+            foreach ($this->getExtensions() as $extension) {
+                foreach ($extension->getFunctions() as $name => $function) {
+                    $this->addFunction($name, $function);
+                }
+            }
+
+            $this->functions = $this->staging['functions'];
+        }
+
+        return $this->functions;
+    }
+
+    /**
+     * Registers a Global.
+     *
+     * @param string $name  The global name
+     * @param mixed  $value The global value
+     */
+    public function addGlobal($name, $value)
+    {
+        $this->staging['globals'][$name] = $value;
+        $this->globals = null;
+    }
+
+    /**
+     * Gets the registered Globals.
+     *
+     * @return array An array of globals
+     */
+    public function getGlobals()
+    {
+        if (null === $this->globals) {
+            $this->globals = isset($this->staging['globals']) ? $this->staging['globals'] : array();
+            foreach ($this->getExtensions() as $extension) {
+                $this->globals = array_merge($this->globals, $extension->getGlobals());
+            }
+        }
+
+        return $this->globals;
+    }
+
+    /**
+     * Merges a context with the defined globals.
+     *
+     * @param array $context An array representing the context
+     *
+     * @return array The context merged with the globals
+     */
+    public function mergeGlobals(array $context)
+    {
+        // we don't use array_merge as the context being generally
+        // bigger than globals, this code is faster.
+        foreach ($this->getGlobals() as $key => $value) {
+            if (!array_key_exists($key, $context)) {
+                $context[$key] = $value;
+            }
+        }
+
+        return $context;
+    }
+
+    /**
+     * 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;
+    }
+
+    public function computeAlternatives($name, $items)
+    {
+        $alternatives = array();
+        foreach ($items as $item) {
+            $lev = levenshtein($name, $item);
+            if ($lev <= strlen($name) / 3 || false !== strpos($item, $name)) {
+                $alternatives[$item] = $lev;
+            }
+        }
+        asort($alternatives);
+
+        return array_keys($alternatives);
+    }
+
+    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)
+    {
+        $dir = dirname($file);
+        if (!is_dir($dir)) {
+            if (false === @mkdir($dir, 0777, true) && !is_dir($dir)) {
+                throw new RuntimeException(sprintf("Unable to create the cache directory (%s).", $dir));
+            }
+        } elseif (!is_writable($dir)) {
+            throw new RuntimeException(sprintf("Unable to write in the cache directory (%s).", $dir));
+        }
+
+        $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));
+    }
+}

Added: logging/site/branches/experimental-twig-textile/libs/Twig/lib/Twig/Error.php
URL: http://svn.apache.org/viewvc/logging/site/branches/experimental-twig-textile/libs/Twig/lib/Twig/Error.php?rev=1341499&view=auto
==============================================================================
--- logging/site/branches/experimental-twig-textile/libs/Twig/lib/Twig/Error.php (added)
+++ logging/site/branches/experimental-twig-textile/libs/Twig/lib/Twig/Error.php Tue May 22 14:42:25 2012
@@ -0,0 +1,199 @@
+<?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 <fa...@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 (version_compare(PHP_VERSION, '5.3.0', '<')) {
+            $this->previous = $previous;
+            parent::__construct('');
+        } else {
+            parent::__construct('', 0, $previous);
+        }
+
+        $this->lineno = $lineno;
+        $this->filename = $filename;
+
+        if (-1 === $this->lineno || null === $this->filename) {
+            $this->guessTemplateInfo();
+        }
+
+        $this->rawMessage = $message;
+
+        $this->updateRepr();
+    }
+
+    /**
+     * 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) {
+            if (is_string($this->filename) || (is_object($this->filename) && method_exists($this->filename, '__toString'))) {
+                $filename = sprintf('"%s"', $this->filename);
+            } else {
+                $filename = json_encode($this->filename);
+            }
+            $this->message .= sprintf(' in %s', $filename);
+        }
+
+        if ($this->lineno >= 0) {
+            $this->message .= sprintf(' at line %d', $this->lineno);
+        }
+
+        if ($dot) {
+            $this->message .= '.';
+        }
+    }
+
+    protected function guessTemplateInfo()
+    {
+        $template = null;
+        foreach (debug_backtrace() as $trace) {
+            if (isset($trace['object']) && $trace['object'] instanceof Twig_Template && 'Twig_Template' !== get_class($trace['object'])) {
+                $template = $trace['object'];
+
+                // update template filename
+                if (null === $this->filename) {
+                    $this->filename = $template->getTemplateName();
+                }
+
+                break;
+            }
+        }
+
+        if (null === $template || $this->lineno > -1) {
+            return;
+        }
+
+        $r = new ReflectionObject($template);
+        $file = $r->getFileName();
+
+        $exceptions = array($e = $this);
+        while (($e instanceof self || method_exists($e, 'getPrevious')) && $e = $e->getPrevious()) {
+            $exceptions[] = $e;
+        }
+
+        while ($e = array_pop($exceptions)) {
+            $traces = $e->getTrace();
+            while ($trace = array_shift($traces)) {
+                if (!isset($trace['file']) || !isset($trace['line']) || $file != $trace['file']) {
+                    continue;
+                }
+
+                foreach ($template->getDebugInfo() as $codeLine => $templateLine) {
+                    if ($codeLine <= $trace['line']) {
+                        // update template line
+                        $this->lineno = $templateLine;
+
+                        return;
+                    }
+                }
+            }
+        }
+    }
+}

Added: logging/site/branches/experimental-twig-textile/libs/Twig/lib/Twig/Error/Loader.php
URL: http://svn.apache.org/viewvc/logging/site/branches/experimental-twig-textile/libs/Twig/lib/Twig/Error/Loader.php?rev=1341499&view=auto
==============================================================================
--- logging/site/branches/experimental-twig-textile/libs/Twig/lib/Twig/Error/Loader.php (added)
+++ logging/site/branches/experimental-twig-textile/libs/Twig/lib/Twig/Error/Loader.php Tue May 22 14:42:25 2012
@@ -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 <fa...@symfony.com>
+ */
+class Twig_Error_Loader extends Twig_Error
+{
+}

Added: logging/site/branches/experimental-twig-textile/libs/Twig/lib/Twig/Error/Runtime.php
URL: http://svn.apache.org/viewvc/logging/site/branches/experimental-twig-textile/libs/Twig/lib/Twig/Error/Runtime.php?rev=1341499&view=auto
==============================================================================
--- logging/site/branches/experimental-twig-textile/libs/Twig/lib/Twig/Error/Runtime.php (added)
+++ logging/site/branches/experimental-twig-textile/libs/Twig/lib/Twig/Error/Runtime.php Tue May 22 14:42:25 2012
@@ -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 <fa...@symfony.com>
+ */
+class Twig_Error_Runtime extends Twig_Error
+{
+}

Added: logging/site/branches/experimental-twig-textile/libs/Twig/lib/Twig/Error/Syntax.php
URL: http://svn.apache.org/viewvc/logging/site/branches/experimental-twig-textile/libs/Twig/lib/Twig/Error/Syntax.php?rev=1341499&view=auto
==============================================================================
--- logging/site/branches/experimental-twig-textile/libs/Twig/lib/Twig/Error/Syntax.php (added)
+++ logging/site/branches/experimental-twig-textile/libs/Twig/lib/Twig/Error/Syntax.php Tue May 22 14:42:25 2012
@@ -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 <fa...@symfony.com>
+ */
+class Twig_Error_Syntax extends Twig_Error
+{
+}

Added: logging/site/branches/experimental-twig-textile/libs/Twig/lib/Twig/ExpressionParser.php
URL: http://svn.apache.org/viewvc/logging/site/branches/experimental-twig-textile/libs/Twig/lib/Twig/ExpressionParser.php?rev=1341499&view=auto
==============================================================================
--- logging/site/branches/experimental-twig-textile/libs/Twig/lib/Twig/ExpressionParser.php (added)
+++ logging/site/branches/experimental-twig-textile/libs/Twig/lib/Twig/ExpressionParser.php Tue May 22 14:42:25 2012
@@ -0,0 +1,488 @@
+<?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 <fa...@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:
+                $this->parser->getStream()->next();
+                $node = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine());
+                break;
+
+            case Twig_Token::STRING_TYPE:
+            case Twig_Token::INTERPOLATION_START_TYPE:
+                $node = $this->parseStringExpression();
+                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 parseStringExpression()
+    {
+        $stream = $this->parser->getStream();
+
+        $nodes = array();
+        // a string cannot be followed by another string in a single expression
+        $nextCanBeString = true;
+        while (true) {
+            if ($stream->test(Twig_Token::STRING_TYPE) && $nextCanBeString) {
+                $token = $stream->next();
+                $nodes[] = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine());
+                $nextCanBeString = false;
+            } elseif ($stream->test(Twig_Token::INTERPOLATION_START_TYPE)) {
+                $stream->next();
+                $nodes[] = $this->parseExpression();
+                $stream->expect(Twig_Token::INTERPOLATION_END_TYPE);
+                $nextCanBeString = true;
+            } else {
+                break;
+            }
+        }
+
+        $expr = array_shift($nodes);
+        foreach ($nodes as $node) {
+            $expr = new Twig_Node_Expression_Binary_Concat($expr, $node, $node->getLine());
+        }
+
+        return $expr;
+    }
+
+    public function parseArrayExpression()
+    {
+        $stream = $this->parser->getStream();
+        $stream->expect(Twig_Token::PUNCTUATION_TYPE, '[', 'An array element was expected');
+
+        $node = new Twig_Node_Expression_Array(array(), $stream->getCurrent()->getLine());
+        $first = true;
+        while (!$stream->test(Twig_Token::PUNCTUATION_TYPE, ']')) {
+            if (!$first) {
+                $stream->expect(Twig_Token::PUNCTUATION_TYPE, ',', 'An array element must be followed by a comma');
+
+                // trailing ,?
+                if ($stream->test(Twig_Token::PUNCTUATION_TYPE, ']')) {
+                    break;
+                }
+            }
+            $first = false;
+
+            $node->addElement($this->parseExpression());
+        }
+        $stream->expect(Twig_Token::PUNCTUATION_TYPE, ']', 'An opened array is not properly closed');
+
+        return $node;
+    }
+
+    public function parseHashExpression()
+    {
+        $stream = $this->parser->getStream();
+        $stream->expect(Twig_Token::PUNCTUATION_TYPE, '{', 'A hash element was expected');
+
+        $node = new Twig_Node_Expression_Array(array(), $stream->getCurrent()->getLine());
+        $first = true;
+        while (!$stream->test(Twig_Token::PUNCTUATION_TYPE, '}')) {
+            if (!$first) {
+                $stream->expect(Twig_Token::PUNCTUATION_TYPE, ',', 'A hash value must be followed by a comma');
+
+                // trailing ,?
+                if ($stream->test(Twig_Token::PUNCTUATION_TYPE, '}')) {
+                    break;
+                }
+            }
+            $first = false;
+
+            // a hash key can be:
+            //
+            //  * a number -- 12
+            //  * a string -- 'a'
+            //  * a name, which is equivalent to a string -- a
+            //  * an expression, which must be enclosed in parentheses -- (1 + 2)
+            if ($stream->test(Twig_Token::STRING_TYPE) || $stream->test(Twig_Token::NAME_TYPE) || $stream->test(Twig_Token::NUMBER_TYPE)) {
+                $token = $stream->next();
+                $key = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine());
+            } elseif ($stream->test(Twig_Token::PUNCTUATION_TYPE, '(')) {
+                $key = $this->parseExpression();
+            } else {
+                $current = $stream->getCurrent();
+
+                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->getLine()), $current->getValue()), $current->getLine());
+            }
+
+            $stream->expect(Twig_Token::PUNCTUATION_TYPE, ':', 'A hash key must be followed by a colon (:)');
+            $value = $this->parseExpression();
+
+            $node->addElement($value, $key);
+        }
+        $stream->expect(Twig_Token::PUNCTUATION_TYPE, '}', 'An opened hash is not properly closed');
+
+        return $node;
+    }
+
+    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() && !$this->parser->hasTraits()) {
+                    throw new Twig_Error_Syntax('Calling "parent" on a template that does not extend nor "use" another template 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 attributes)', $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)) {
+                    $arguments = new Twig_Node_Expression_Array(array(), $line);
+                    foreach ($args as $n) {
+                        $arguments->addElement($n);
+                    }
+
+                    $node = new Twig_Node_Expression_MethodCall($alias['node'], $alias['name'], $arguments, $line);
+                    $node->setAttribute('safe', true);
+
+                    return $node;
+                }
+
+                $class = $this->getFunctionNodeClass($name);
+
+                return new $class($name, $args, $line);
+        }
+    }
+
+    public function parseSubscriptExpression($node)
+    {
+        $stream = $this->parser->getStream();
+        $token = $stream->next();
+        $lineno = $token->getLine();
+        $arguments = new Twig_Node_Expression_Array(array(), $lineno);
+        $type = Twig_TemplateInterface::ANY_CALL;
+        if ($token->getValue() == '.') {
+            $token = $stream->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 ($stream->test(Twig_Token::PUNCTUATION_TYPE, '(')) {
+                    $type = Twig_TemplateInterface::METHOD_CALL;
+                    foreach ($this->parseArguments() as $n) {
+                        $arguments->addElement($n);
+                    }
+                }
+            } else {
+                throw new Twig_Error_Syntax('Expected name or number', $lineno);
+            }
+        } else {
+            $type = Twig_TemplateInterface::ARRAY_CALL;
+
+            $arg = $this->parseExpression();
+
+            // slice?
+            if ($stream->test(Twig_Token::PUNCTUATION_TYPE, ':')) {
+                $stream->next();
+
+                if ($stream->test(Twig_Token::PUNCTUATION_TYPE, ']')) {
+                    $length = new Twig_Node_Expression_Constant(null, $token->getLine());
+                } else {
+                    $length = $this->parseExpression();
+                }
+
+                $class = $this->getFilterNodeClass('slice');
+                $arguments = new Twig_Node(array($arg, $length));
+                $filter = new $class($node, new Twig_Node_Expression_Constant('slice', $token->getLine()), $arguments, $token->getLine());
+
+                $stream->expect(Twig_Token::PUNCTUATION_TYPE, ']');
+
+                return $filter;
+            }
+
+            $stream->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();
+            }
+
+            $class = $this->getFilterNodeClass($name->getAttribute('value'));
+
+            $node = new $class($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, ',')) {
+                break;
+            }
+            $this->parser->getStream()->next();
+        }
+
+        return new Twig_Node($targets);
+    }
+
+    protected function getFunctionNodeClass($name)
+    {
+        $functionMap = $this->parser->getEnvironment()->getFunctions();
+        if (isset($functionMap[$name]) && $functionMap[$name] instanceof Twig_Function_Node) {
+            return $functionMap[$name]->getClass();
+        }
+
+        return 'Twig_Node_Expression_Function';
+    }
+
+    protected function getFilterNodeClass($name)
+    {
+        $filterMap = $this->parser->getEnvironment()->getFilters();
+        if (isset($filterMap[$name]) && $filterMap[$name] instanceof Twig_Filter_Node) {
+            return $filterMap[$name]->getClass();
+        }
+
+        return 'Twig_Node_Expression_Filter';
+    }
+}

Added: logging/site/branches/experimental-twig-textile/libs/Twig/lib/Twig/Extension.php
URL: http://svn.apache.org/viewvc/logging/site/branches/experimental-twig-textile/libs/Twig/lib/Twig/Extension.php?rev=1341499&view=auto
==============================================================================
--- logging/site/branches/experimental-twig-textile/libs/Twig/lib/Twig/Extension.php (added)
+++ logging/site/branches/experimental-twig-textile/libs/Twig/lib/Twig/Extension.php Tue May 22 14:42:25 2012
@@ -0,0 +1,93 @@
+<?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.
+ */
+abstract class Twig_Extension implements Twig_ExtensionInterface
+{
+    /**
+     * Initializes the runtime environment.
+     *
+     * This is where you can load some file that contains filter functions for instance.
+     *
+     * @param Twig_Environment $environment The current Twig_Environment instance
+     */
+    public function initRuntime(Twig_Environment $environment)
+    {
+    }
+
+    /**
+     * Returns the token parser instances to add to the existing list.
+     *
+     * @return array An array of Twig_TokenParserInterface or Twig_TokenParserBrokerInterface instances
+     */
+    public function getTokenParsers()
+    {
+        return array();
+    }
+
+    /**
+     * Returns the node visitor instances to add to the existing list.
+     *
+     * @return array An array of Twig_NodeVisitorInterface instances
+     */
+    public function getNodeVisitors()
+    {
+        return array();
+    }
+
+    /**
+     * Returns a list of filters to add to the existing list.
+     *
+     * @return array An array of filters
+     */
+    public function getFilters()
+    {
+        return array();
+    }
+
+    /**
+     * Returns a list of tests to add to the existing list.
+     *
+     * @return array An array of tests
+     */
+    public function getTests()
+    {
+        return array();
+    }
+
+    /**
+     * Returns a list of functions to add to the existing list.
+     *
+     * @return array An array of functions
+     */
+    public function getFunctions()
+    {
+        return array();
+    }
+
+    /**
+     * Returns a list of operators to add to the existing list.
+     *
+     * @return array An array of operators
+     */
+    public function getOperators()
+    {
+        return array();
+    }
+
+    /**
+     * Returns a list of global variables to add to the existing list.
+     *
+     * @return array An array of global variables
+     */
+    public function getGlobals()
+    {
+        return array();
+    }
+}