2011-10-05 06:22:53 +02:00
< ? 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 .
*/
/**
* Default parser implementation .
*
2013-08-01 21:20:12 +02:00
* @ author Fabien Potencier < fabien @ symfony . com >
2011-10-05 06:22:53 +02:00
*/
class Twig_Parser implements Twig_ParserInterface
{
2013-08-01 21:20:12 +02:00
protected $stack = array ();
2011-10-05 06:22:53 +02:00
protected $stream ;
protected $parent ;
protected $handlers ;
protected $visitors ;
protected $expressionParser ;
protected $blocks ;
protected $blockStack ;
protected $macros ;
protected $env ;
protected $reservedMacroNames ;
2013-08-01 21:20:12 +02:00
protected $importedSymbols ;
2011-10-05 06:22:53 +02:00
protected $traits ;
2013-08-01 21:20:12 +02:00
protected $embeddedTemplates = array ();
2011-10-05 06:22:53 +02:00
/**
* Constructor .
*
* @ param Twig_Environment $env A Twig_Environment instance
*/
public function __construct ( Twig_Environment $env )
{
$this -> env = $env ;
}
2013-08-01 21:20:12 +02:00
public function getEnvironment ()
{
return $this -> env ;
}
2011-10-05 06:22:53 +02:00
public function getVarName ()
{
2013-09-19 08:08:25 +02:00
return sprintf ( '__internal_%s' , hash ( 'sha256' , uniqid ( mt_rand (), true ), false ));
2013-08-01 21:20:12 +02:00
}
public function getFilename ()
{
return $this -> stream -> getFilename ();
2011-10-05 06:22:53 +02:00
}
/**
* Converts a token stream to a node tree .
*
2013-08-01 21:20:12 +02:00
* @ param Twig_TokenStream $stream A token stream instance
2011-10-05 06:22:53 +02:00
*
* @ return Twig_Node_Module A node tree
*/
2013-08-01 21:20:12 +02:00
public function parse ( Twig_TokenStream $stream , $test = null , $dropNeedle = false )
2011-10-05 06:22:53 +02:00
{
2013-08-01 21:20:12 +02:00
// push all variables into the stack to keep the current state of the parser
$vars = get_object_vars ( $this );
unset ( $vars [ 'stack' ], $vars [ 'env' ], $vars [ 'handlers' ], $vars [ 'visitors' ], $vars [ 'expressionParser' ]);
$this -> stack [] = $vars ;
2011-10-05 06:22:53 +02:00
// tag handlers
2013-08-01 21:20:12 +02:00
if ( null === $this -> handlers ) {
$this -> handlers = $this -> env -> getTokenParsers ();
$this -> handlers -> setParser ( $this );
}
2011-10-05 06:22:53 +02:00
// node visitors
2013-08-01 21:20:12 +02:00
if ( null === $this -> visitors ) {
$this -> visitors = $this -> env -> getNodeVisitors ();
}
2011-10-05 06:22:53 +02:00
if ( null === $this -> expressionParser ) {
$this -> expressionParser = new Twig_ExpressionParser ( $this , $this -> env -> getUnaryOperators (), $this -> env -> getBinaryOperators ());
}
$this -> stream = $stream ;
$this -> parent = null ;
$this -> blocks = array ();
$this -> macros = array ();
$this -> traits = array ();
$this -> blockStack = array ();
2013-08-01 21:20:12 +02:00
$this -> importedSymbols = array ( array ());
$this -> embeddedTemplates = array ();
2011-10-05 06:22:53 +02:00
try {
2013-08-01 21:20:12 +02:00
$body = $this -> subparse ( $test , $dropNeedle );
2011-10-05 06:22:53 +02:00
if ( null !== $this -> parent ) {
if ( null === $body = $this -> filterBodyNodes ( $body )) {
$body = new Twig_Node ();
}
}
} catch ( Twig_Error_Syntax $e ) {
2013-08-01 21:20:12 +02:00
if ( ! $e -> getTemplateFile ()) {
$e -> setTemplateFile ( $this -> getFilename ());
}
if ( ! $e -> getTemplateLine ()) {
$e -> setTemplateLine ( $this -> stream -> getCurrent () -> getLine ());
2011-10-05 06:22:53 +02:00
}
throw $e ;
}
2013-08-01 21:20:12 +02:00
$node = new Twig_Node_Module ( new Twig_Node_Body ( array ( $body )), $this -> parent , new Twig_Node ( $this -> blocks ), new Twig_Node ( $this -> macros ), new Twig_Node ( $this -> traits ), $this -> embeddedTemplates , $this -> getFilename ());
2011-10-05 06:22:53 +02:00
$traverser = new Twig_NodeTraverser ( $this -> env , $this -> visitors );
2013-08-01 21:20:12 +02:00
$node = $traverser -> traverse ( $node );
// restore previous stack so previous parse() call can resume working
foreach ( array_pop ( $this -> stack ) as $key => $val ) {
$this -> $key = $val ;
}
return $node ;
2011-10-05 06:22:53 +02:00
}
public function subparse ( $test , $dropNeedle = false )
{
$lineno = $this -> getCurrentToken () -> getLine ();
$rv = array ();
while ( ! $this -> stream -> isEOF ()) {
switch ( $this -> getCurrentToken () -> getType ()) {
case Twig_Token :: TEXT_TYPE :
$token = $this -> stream -> next ();
$rv [] = new Twig_Node_Text ( $token -> getValue (), $token -> getLine ());
break ;
case Twig_Token :: VAR_START_TYPE :
$token = $this -> stream -> next ();
$expr = $this -> expressionParser -> parseExpression ();
$this -> stream -> expect ( Twig_Token :: VAR_END_TYPE );
$rv [] = new Twig_Node_Print ( $expr , $token -> getLine ());
break ;
case Twig_Token :: BLOCK_START_TYPE :
$this -> stream -> next ();
$token = $this -> getCurrentToken ();
if ( $token -> getType () !== Twig_Token :: NAME_TYPE ) {
2013-08-01 21:20:12 +02:00
throw new Twig_Error_Syntax ( 'A block must start with a tag name' , $token -> getLine (), $this -> getFilename ());
2011-10-05 06:22:53 +02:00
}
if ( null !== $test && call_user_func ( $test , $token )) {
if ( $dropNeedle ) {
$this -> stream -> next ();
}
if ( 1 === count ( $rv )) {
return $rv [ 0 ];
}
return new Twig_Node ( $rv , array (), $lineno );
}
$subparser = $this -> handlers -> getTokenParser ( $token -> getValue ());
if ( null === $subparser ) {
if ( null !== $test ) {
2013-08-01 21:20:12 +02:00
$error = sprintf ( 'Unexpected tag name "%s"' , $token -> getValue ());
if ( is_array ( $test ) && isset ( $test [ 0 ]) && $test [ 0 ] instanceof Twig_TokenParserInterface ) {
$error .= sprintf ( ' (expecting closing tag for the "%s" tag defined near line %s)' , $test [ 0 ] -> getTag (), $lineno );
}
throw new Twig_Error_Syntax ( $error , $token -> getLine (), $this -> getFilename ());
2011-10-05 06:22:53 +02:00
}
2013-08-01 21:20:12 +02:00
$message = sprintf ( 'Unknown tag name "%s"' , $token -> getValue ());
if ( $alternatives = $this -> env -> computeAlternatives ( $token -> getValue (), array_keys ( $this -> env -> getTags ()))) {
$message = sprintf ( '%s. Did you mean "%s"' , $message , implode ( '", "' , $alternatives ));
}
throw new Twig_Error_Syntax ( $message , $token -> getLine (), $this -> getFilename ());
2011-10-05 06:22:53 +02:00
}
$this -> stream -> next ();
$node = $subparser -> parse ( $token );
if ( null !== $node ) {
$rv [] = $node ;
}
break ;
default :
2013-08-01 21:20:12 +02:00
throw new Twig_Error_Syntax ( 'Lexer or parser ended up in unsupported state.' , 0 , $this -> getFilename ());
2011-10-05 06:22:53 +02:00
}
}
if ( 1 === count ( $rv )) {
return $rv [ 0 ];
}
return new Twig_Node ( $rv , array (), $lineno );
}
public function addHandler ( $name , $class )
{
$this -> handlers [ $name ] = $class ;
}
public function addNodeVisitor ( Twig_NodeVisitorInterface $visitor )
{
$this -> visitors [] = $visitor ;
}
public function getBlockStack ()
{
return $this -> blockStack ;
}
public function peekBlockStack ()
{
return $this -> blockStack [ count ( $this -> blockStack ) - 1 ];
}
public function popBlockStack ()
{
array_pop ( $this -> blockStack );
}
public function pushBlockStack ( $name )
{
$this -> blockStack [] = $name ;
}
public function hasBlock ( $name )
{
return isset ( $this -> blocks [ $name ]);
}
2013-08-01 21:20:12 +02:00
public function getBlock ( $name )
{
return $this -> blocks [ $name ];
}
2011-10-05 06:22:53 +02:00
public function setBlock ( $name , $value )
{
2013-08-01 21:20:12 +02:00
$this -> blocks [ $name ] = new Twig_Node_Body ( array ( $value ), array (), $value -> getLine ());
2011-10-05 06:22:53 +02:00
}
public function hasMacro ( $name )
{
return isset ( $this -> macros [ $name ]);
}
public function setMacro ( $name , Twig_Node_Macro $node )
{
if ( null === $this -> reservedMacroNames ) {
$this -> reservedMacroNames = array ();
$r = new ReflectionClass ( $this -> env -> getBaseTemplateClass ());
foreach ( $r -> getMethods () as $method ) {
$this -> reservedMacroNames [] = $method -> getName ();
}
}
if ( in_array ( $name , $this -> reservedMacroNames )) {
2013-08-01 21:20:12 +02:00
throw new Twig_Error_Syntax ( sprintf ( '"%s" cannot be used as a macro name as it is a reserved keyword' , $name ), $node -> getLine (), $this -> getFilename ());
2011-10-05 06:22:53 +02:00
}
$this -> macros [ $name ] = $node ;
}
public function addTrait ( $trait )
{
$this -> traits [] = $trait ;
}
2013-08-01 21:20:12 +02:00
public function hasTraits ()
{
return count ( $this -> traits ) > 0 ;
}
public function embedTemplate ( Twig_Node_Module $template )
2011-10-05 06:22:53 +02:00
{
2013-08-01 21:20:12 +02:00
$template -> setIndex ( mt_rand ());
$this -> embeddedTemplates [] = $template ;
2011-10-05 06:22:53 +02:00
}
2013-08-01 21:20:12 +02:00
public function addImportedSymbol ( $type , $alias , $name = null , Twig_Node_Expression $node = null )
2011-10-05 06:22:53 +02:00
{
2013-08-01 21:20:12 +02:00
$this -> importedSymbols [ 0 ][ $type ][ $alias ] = array ( 'name' => $name , 'node' => $node );
}
public function getImportedSymbol ( $type , $alias )
{
foreach ( $this -> importedSymbols as $functions ) {
if ( isset ( $functions [ $type ][ $alias ])) {
return $functions [ $type ][ $alias ];
2011-10-05 06:22:53 +02:00
}
}
}
public function isMainScope ()
{
2013-08-01 21:20:12 +02:00
return 1 === count ( $this -> importedSymbols );
2011-10-05 06:22:53 +02:00
}
public function pushLocalScope ()
{
2013-08-01 21:20:12 +02:00
array_unshift ( $this -> importedSymbols , array ());
2011-10-05 06:22:53 +02:00
}
public function popLocalScope ()
{
2013-08-01 21:20:12 +02:00
array_shift ( $this -> importedSymbols );
2011-10-05 06:22:53 +02:00
}
/**
* Gets the expression parser .
*
* @ return Twig_ExpressionParser The expression parser
*/
public function getExpressionParser ()
{
return $this -> expressionParser ;
}
public function getParent ()
{
return $this -> parent ;
}
public function setParent ( $parent )
{
$this -> parent = $parent ;
}
/**
* Gets the token stream .
*
* @ return Twig_TokenStream The token stream
*/
public function getStream ()
{
return $this -> stream ;
}
/**
* Gets the current token .
*
* @ return Twig_Token The current token
*/
public function getCurrentToken ()
{
return $this -> stream -> getCurrent ();
}
protected function filterBodyNodes ( Twig_NodeInterface $node )
{
// check that the body does not contain non-empty output nodes
if (
( $node instanceof Twig_Node_Text && ! ctype_space ( $node -> getAttribute ( 'data' )))
||
( ! $node instanceof Twig_Node_Text && ! $node instanceof Twig_Node_BlockReference && $node instanceof Twig_NodeOutputInterface )
) {
2013-08-01 21:20:12 +02:00
if ( false !== strpos (( string ) $node , chr ( 0xEF ) . chr ( 0xBB ) . chr ( 0xBF ))) {
throw new Twig_Error_Syntax ( 'A template that extends another one cannot have a body but a byte order mark (BOM) has been detected; it must be removed.' , $node -> getLine (), $this -> getFilename ());
}
throw new Twig_Error_Syntax ( 'A template that extends another one cannot have a body.' , $node -> getLine (), $this -> getFilename ());
2011-10-05 06:22:53 +02:00
}
// bypass "set" nodes as they "capture" the output
if ( $node instanceof Twig_Node_Set ) {
return $node ;
}
if ( $node instanceof Twig_NodeOutputInterface ) {
return ;
}
foreach ( $node as $k => $n ) {
if ( null !== $n && null === $n = $this -> filterBodyNodes ( $n )) {
$node -> removeNode ( $k );
}
}
return $node ;
}
}