2011-10-05 06:22:53 +02:00
< ? php
/*
* This file is part of Twig .
*
2018-05-10 12:24:53 +02:00
* ( c ) Fabien Potencier
* ( c ) Armin Ronacher
2011-10-05 06:22:53 +02:00
*
* 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 ();
2018-05-10 12:24:53 +02:00
private $varNameSalt = 0 ;
2011-10-05 06:22:53 +02:00
public function __construct ( Twig_Environment $env )
{
$this -> env = $env ;
}
2018-05-10 12:24:53 +02:00
/**
* @ deprecated since 1.27 ( to be removed in 2.0 )
*/
2013-08-01 21:20:12 +02:00
public function getEnvironment ()
{
2018-05-10 12:24:53 +02:00
@ trigger_error ( 'The ' . __METHOD__ . ' method is deprecated since version 1.27 and will be removed in 2.0.' , E_USER_DEPRECATED );
2013-08-01 21:20:12 +02:00
return $this -> env ;
}
2011-10-05 06:22:53 +02:00
public function getVarName ()
{
2018-05-10 12:24:53 +02:00
return sprintf ( '__internal_%s' , hash ( 'sha256' , __METHOD__ . $this -> stream -> getSourceContext () -> getCode () . $this -> varNameSalt ++ ));
2013-08-01 21:20:12 +02:00
}
2018-05-10 12:24:53 +02:00
/**
* @ deprecated since 1.27 ( to be removed in 2.0 ) . Use $parser -> getStream () -> getSourceContext () -> getPath () instead .
*/
2013-08-01 21:20:12 +02:00
public function getFilename ()
{
2018-05-10 12:24:53 +02:00
@ trigger_error ( sprintf ( 'The "%s" method is deprecated since version 1.27 and will be removed in 2.0. Use $parser->getStream()->getSourceContext()->getPath() instead.' , __METHOD__ ), E_USER_DEPRECATED );
return $this -> stream -> getSourceContext () -> getName ();
2011-10-05 06:22:53 +02:00
}
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
2018-05-10 12:24:53 +02:00
// using get_object_vars() instead of foreach would lead to https://bugs.php.net/71336
// This hack can be removed when min version if PHP 7.0
$vars = array ();
foreach ( $this as $k => $v ) {
$vars [ $k ] = $v ;
}
unset ( $vars [ 'stack' ], $vars [ 'env' ], $vars [ 'handlers' ], $vars [ 'visitors' ], $vars [ 'expressionParser' ], $vars [ 'reservedMacroNames' ]);
2013-08-01 21:20:12 +02:00
$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 ) {
2018-05-10 12:24:53 +02:00
$this -> expressionParser = new Twig_ExpressionParser ( $this , $this -> env );
2011-10-05 06:22:53 +02:00
}
$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 ();
2018-05-10 12:24:53 +02:00
$this -> varNameSalt = 0 ;
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
2018-05-10 12:24:53 +02:00
if ( null !== $this -> parent && null === $body = $this -> filterBodyNodes ( $body )) {
$body = new Twig_Node ();
2011-10-05 06:22:53 +02:00
}
} catch ( Twig_Error_Syntax $e ) {
2018-05-10 12:24:53 +02:00
if ( ! $e -> getSourceContext ()) {
$e -> setSourceContext ( $this -> stream -> getSourceContext ());
2013-08-01 21:20:12 +02:00
}
if ( ! $e -> getTemplateLine ()) {
$e -> setTemplateLine ( $this -> stream -> getCurrent () -> getLine ());
2011-10-05 06:22:53 +02:00
}
throw $e ;
}
2018-05-10 12:24:53 +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 , $stream -> getSourceContext ());
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 ();
2018-05-10 12:24:53 +02:00
if ( Twig_Token :: NAME_TYPE !== $token -> getType ()) {
throw new Twig_Error_Syntax ( 'A block must start with a tag name.' , $token -> getLine (), $this -> stream -> getSourceContext ());
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 ) {
2018-05-10 12:24:53 +02:00
$e = new Twig_Error_Syntax ( sprintf ( 'Unexpected "%s" tag' , $token -> getValue ()), $token -> getLine (), $this -> stream -> getSourceContext ());
2013-08-01 21:20:12 +02:00
if ( is_array ( $test ) && isset ( $test [ 0 ]) && $test [ 0 ] instanceof Twig_TokenParserInterface ) {
2018-05-10 12:24:53 +02:00
$e -> appendMessage ( sprintf ( ' (expecting closing tag for the "%s" tag defined near line %s).' , $test [ 0 ] -> getTag (), $lineno ));
2013-08-01 21:20:12 +02:00
}
2018-05-10 12:24:53 +02:00
} else {
$e = new Twig_Error_Syntax ( sprintf ( 'Unknown "%s" tag.' , $token -> getValue ()), $token -> getLine (), $this -> stream -> getSourceContext ());
$e -> addSuggestions ( $token -> getValue (), array_keys ( $this -> env -> getTags ()));
2013-08-01 21:20:12 +02:00
}
2018-05-10 12:24:53 +02:00
throw $e ;
2011-10-05 06:22:53 +02:00
}
$this -> stream -> next ();
$node = $subparser -> parse ( $token );
if ( null !== $node ) {
$rv [] = $node ;
}
break ;
default :
2018-05-10 12:24:53 +02:00
throw new Twig_Error_Syntax ( 'Lexer or parser ended up in unsupported state.' , $this -> getCurrentToken () -> getLine (), $this -> stream -> getSourceContext ());
2011-10-05 06:22:53 +02:00
}
}
if ( 1 === count ( $rv )) {
return $rv [ 0 ];
}
return new Twig_Node ( $rv , array (), $lineno );
}
2018-05-10 12:24:53 +02:00
/**
* @ deprecated since 1.27 ( to be removed in 2.0 )
*/
2011-10-05 06:22:53 +02:00
public function addHandler ( $name , $class )
{
2018-05-10 12:24:53 +02:00
@ trigger_error ( 'The ' . __METHOD__ . ' method is deprecated since version 1.27 and will be removed in 2.0.' , E_USER_DEPRECATED );
2011-10-05 06:22:53 +02:00
$this -> handlers [ $name ] = $class ;
}
2018-05-10 12:24:53 +02:00
/**
* @ deprecated since 1.27 ( to be removed in 2.0 )
*/
2011-10-05 06:22:53 +02:00
public function addNodeVisitor ( Twig_NodeVisitorInterface $visitor )
{
2018-05-10 12:24:53 +02:00
@ trigger_error ( 'The ' . __METHOD__ . ' method is deprecated since version 1.27 and will be removed in 2.0.' , E_USER_DEPRECATED );
2011-10-05 06:22:53 +02:00
$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 ];
}
2018-05-10 12:24:53 +02:00
public function setBlock ( $name , Twig_Node_Block $value )
2011-10-05 06:22:53 +02:00
{
2018-05-10 12:24:53 +02:00
$this -> blocks [ $name ] = new Twig_Node_Body ( array ( $value ), array (), $value -> getTemplateLine ());
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 )
2018-05-10 12:24:53 +02:00
{
if ( $this -> isReservedMacroName ( $name )) {
throw new Twig_Error_Syntax ( sprintf ( '"%s" cannot be used as a macro name as it is a reserved keyword.' , $name ), $node -> getTemplateLine (), $this -> stream -> getSourceContext ());
}
$this -> macros [ $name ] = $node ;
}
public function isReservedMacroName ( $name )
2011-10-05 06:22:53 +02:00
{
if ( null === $this -> reservedMacroNames ) {
$this -> reservedMacroNames = array ();
$r = new ReflectionClass ( $this -> env -> getBaseTemplateClass ());
foreach ( $r -> getMethods () as $method ) {
2018-05-10 12:24:53 +02:00
$methodName = strtolower ( $method -> getName ());
2011-10-05 06:22:53 +02:00
2018-05-10 12:24:53 +02:00
if ( 'get' === substr ( $methodName , 0 , 3 ) && isset ( $methodName [ 3 ])) {
$this -> reservedMacroNames [] = substr ( $methodName , 3 );
}
}
2011-10-05 06:22:53 +02:00
}
2018-05-10 12:24:53 +02:00
return in_array ( strtolower ( $name ), $this -> reservedMacroNames );
2011-10-05 06:22:53 +02:00
}
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
}
/**
2018-05-10 12:24:53 +02:00
* @ return Twig_ExpressionParser
2011-10-05 06:22:53 +02:00
*/
public function getExpressionParser ()
{
return $this -> expressionParser ;
}
public function getParent ()
{
return $this -> parent ;
}
public function setParent ( $parent )
{
$this -> parent = $parent ;
}
/**
2018-05-10 12:24:53 +02:00
* @ return Twig_TokenStream
2011-10-05 06:22:53 +02:00
*/
public function getStream ()
{
return $this -> stream ;
}
/**
2018-05-10 12:24:53 +02:00
* @ return Twig_Token
2011-10-05 06:22:53 +02:00
*/
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 ))) {
2018-05-10 12:24:53 +02:00
throw new Twig_Error_Syntax ( 'A template that extends another one cannot start with a byte order mark (BOM); it must be removed.' , $node -> getTemplateLine (), $this -> stream -> getSourceContext ());
2013-08-01 21:20:12 +02:00
}
2018-05-10 12:24:53 +02:00
throw new Twig_Error_Syntax ( 'A template that extends another one cannot include content outside Twig blocks. Did you forget to put the content inside a {% block %} tag?' , $node -> getTemplateLine (), $this -> stream -> getSourceContext ());
2011-10-05 06:22:53 +02:00
}
2018-05-10 12:24:53 +02:00
// bypass nodes that will "capture" the output
if ( $node instanceof Twig_NodeCaptureInterface ) {
2011-10-05 06:22:53 +02:00
return $node ;
}
if ( $node instanceof Twig_NodeOutputInterface ) {
return ;
}
foreach ( $node as $k => $n ) {
2018-05-10 12:24:53 +02:00
if ( null !== $n && null === $this -> filterBodyNodes ( $n )) {
2011-10-05 06:22:53 +02:00
$node -> removeNode ( $k );
}
}
return $node ;
}
}
2018-05-10 12:24:53 +02:00
class_alias ( 'Twig_Parser' , 'Twig\Parser' , false );
class_exists ( 'Twig_Node' );
class_exists ( 'Twig_TokenStream' );