2013-01-25 18:36:16 +04:00
< ? php
2013-02-21 22:51:24 +04:00
/*
2013-06-28 11:53:53 +04:00
* This file is part of Fenom .
2013-02-21 22:51:24 +04:00
*
* ( c ) 2013 Ivan Shalganov
*
2013-04-28 18:08:57 +04:00
* For the full copyright and license information , please view the license . md
2013-02-21 22:51:24 +04:00
* file that was distributed with this source code .
*/
2013-06-28 11:53:53 +04:00
namespace Fenom ;
2013-08-01 01:05:19 +04:00
use Fenom ;
use Fenom\Error\UnexpectedTokenException ;
2013-07-29 16:15:52 +04:00
use Fenom\Error\CompileException ;
use Fenom\Error\InvalidUsageException ;
use Fenom\Error\SecurityException ;
use Fenom\Error\TokenizeException ;
2013-01-25 18:36:16 +04:00
/**
2013-02-21 22:51:24 +04:00
* Template compiler
*
2013-06-28 11:53:53 +04:00
* @ package Fenom
2013-07-04 01:28:10 +04:00
* @ author Ivan Shalganov < a . cobest @ gmail . com >
2013-01-25 18:36:16 +04:00
*/
2013-07-29 14:58:14 +04:00
class Template extends Render
{
2013-01-25 18:36:16 +04:00
2013-07-03 12:10:50 +04:00
/**
* Disable array parser .
*/
const DENY_ARRAY = 1 ;
/**
* Disable modifier parser .
*/
const DENY_MODS = 2 ;
/**
* Template was extended
*/
const DYNAMIC_EXTEND = 0x1000 ;
const EXTENDED = 0x2000 ;
const DYNAMIC_BLOCK = 0x4000 ;
2013-03-17 14:37:23 +04:00
2013-01-25 18:36:16 +04:00
/**
* @ var int shared counter
*/
public $i = 1 ;
2013-02-23 02:03:05 +04:00
/**
* @ var array of macros
*/
public $macros = array ();
2013-02-23 13:29:20 +04:00
/**
* @ var array of blocks
*/
public $blocks = array ();
2013-06-08 00:08:00 +04:00
2013-07-03 12:10:50 +04:00
public $uses = array ();
2013-06-08 00:08:00 +04:00
2013-07-03 12:10:50 +04:00
public $parents = array ();
2013-06-08 00:08:00 +04:00
2013-07-07 01:29:33 +04:00
/**
2013-07-22 18:03:43 +04:00
* Escape outputs value
2013-07-07 01:29:33 +04:00
* @ var bool
*/
public $escape = false ;
2013-07-29 14:53:21 +04:00
2013-07-03 12:10:50 +04:00
public $_extends ;
public $_extended = false ;
public $_compatible ;
2013-06-08 00:08:00 +04:00
2013-07-29 14:53:21 +04:00
/**
* Template PHP code
* @ var string
*/
private $_body ;
2013-01-25 18:36:16 +04:00
/**
* Call stack
* @ var Scope []
*/
private $_stack = array ();
/**
* Template source
* @ var string
*/
private $_src ;
/**
* @ var int
*/
private $_line = 1 ;
private $_post = array ();
/**
* @ var bool
*/
2013-02-07 17:37:16 +04:00
private $_ignore = false ;
2013-01-25 18:36:16 +04:00
2013-07-26 10:40:07 +04:00
private $_before ;
2013-08-01 01:05:19 +04:00
private $_filters = array ();
2013-05-17 22:20:29 +04:00
2013-07-22 18:03:43 +04:00
private static $_checkers = array (
2013-07-29 14:58:14 +04:00
'integer' => 'is_int(%s)' ,
'int' => 'is_int(%s)' ,
'float' => 'is_float(%s)' ,
'double' => 'is_float(%s)' ,
'decimal' => 'is_float(%s)' ,
'string' => 'is_string(%s)' ,
'bool' => 'is_bool(%s)' ,
'boolean' => 'is_bool(%s)' ,
'number' => 'is_numeric(%s)' ,
'numeric' => 'is_numeric(%s)' ,
'scalar' => 'is_scalar(%s)' ,
'object' => 'is_object(%s)' ,
2013-07-22 18:03:43 +04:00
'callable' => 'is_callable(%s)' ,
'callback' => 'is_callable(%s)' ,
2013-07-29 14:58:14 +04:00
'array' => 'is_array(%s)' ,
2013-07-22 18:03:43 +04:00
'iterable' => '\Fenom\Modifier::isIterable(%s)' ,
2013-07-29 14:58:14 +04:00
'const' => 'defined(%s)' ,
2013-07-22 18:36:48 +04:00
'template' => '$tpl->getStorage()->templateExists(%s)' ,
2013-07-29 14:58:14 +04:00
'empty' => 'empty(%s)' ,
'set' => 'isset(%s)' ,
'_empty' => '!%s' , // for none variable
'_set' => '(%s !== null)' , // for none variable
'odd' => '(%s & 1)' ,
'even' => '!(%s %% 2)' ,
'third' => '!(%s %% 3)'
2013-07-22 18:03:43 +04:00
);
2013-01-25 18:36:16 +04:00
/**
2013-06-28 11:53:53 +04:00
* @ param Fenom $fenom Template storage
2013-04-28 18:08:57 +04:00
* @ param int $options
2013-06-28 11:53:53 +04:00
* @ return \Fenom\Template
2013-02-13 20:51:27 +04:00
*/
2013-07-29 14:58:14 +04:00
public function __construct ( Fenom $fenom , $options )
{
2013-06-28 11:53:53 +04:00
$this -> _fenom = $fenom ;
2013-05-19 02:04:52 +04:00
$this -> _options = $options ;
2013-08-01 01:05:19 +04:00
$this -> _filters = $this -> _fenom -> getFilters ();
2013-02-13 20:51:27 +04:00
}
2013-07-02 11:07:33 +04:00
/**
* Get tag stack size
* @ return int
*/
2013-07-29 14:58:14 +04:00
public function getStackSize ()
{
2013-07-02 11:07:33 +04:00
return count ( $this -> _stack );
}
2013-02-13 20:51:27 +04:00
/**
* Load source from provider
* @ param string $name
* @ param bool $compile
2013-07-29 16:15:52 +04:00
* @ return self
2013-02-13 20:51:27 +04:00
*/
2013-07-29 14:58:14 +04:00
public function load ( $name , $compile = true )
{
2013-02-13 20:51:27 +04:00
$this -> _name = $name ;
2013-07-29 14:58:14 +04:00
if ( $provider = strstr ( $name , " : " , true )) {
2013-02-13 20:51:27 +04:00
$this -> _scm = $provider ;
2013-05-30 19:00:00 +04:00
$this -> _base_name = substr ( $name , strlen ( $provider ) + 1 );
2013-02-13 20:51:27 +04:00
} else {
2013-07-03 12:10:50 +04:00
$this -> _base_name = $name ;
2013-02-13 20:51:27 +04:00
}
2013-07-03 12:10:50 +04:00
$this -> _provider = $this -> _fenom -> getProvider ( $provider );
2013-05-30 19:00:00 +04:00
$this -> _src = $this -> _provider -> getSource ( $this -> _base_name , $this -> _time );
2013-07-29 14:58:14 +04:00
if ( $compile ) {
2013-02-13 20:51:27 +04:00
$this -> compile ();
}
return $this ;
}
/**
* Load custom source
2013-01-25 18:36:16 +04:00
* @ param string $name template name
2013-02-13 20:51:27 +04:00
* @ param string $src template source
* @ param bool $compile
2013-06-28 11:53:53 +04:00
* @ return \Fenom\Template
2013-01-25 18:36:16 +04:00
*/
2013-07-29 14:58:14 +04:00
public function source ( $name , $src , $compile = true )
{
2013-01-28 16:34:34 +04:00
$this -> _name = $name ;
2013-02-13 20:51:27 +04:00
$this -> _src = $src ;
2013-07-29 14:58:14 +04:00
if ( $compile ) {
2013-02-07 17:37:16 +04:00
$this -> compile ();
}
2013-02-13 20:51:27 +04:00
return $this ;
2013-02-07 17:37:16 +04:00
}
2013-02-13 20:51:27 +04:00
/**
* Convert template to PHP code
*
* @ throws CompileException
*/
2013-07-29 14:58:14 +04:00
public function compile ()
{
2013-07-03 12:10:50 +04:00
$end = $pos = 0 ;
2013-07-07 01:29:33 +04:00
$this -> escape = $this -> _options & Fenom :: AUTO_ESCAPE ;
2013-08-02 21:50:04 +04:00
foreach ( $this -> _fenom -> getPreFilters () as $filter ) {
2013-08-01 01:05:19 +04:00
$this -> _src = call_user_func ( $filter , $this -> _src , $this );
}
2013-07-08 07:43:20 +04:00
2013-07-29 14:58:14 +04:00
while (( $start = strpos ( $this -> _src , '{' , $pos )) !== false ) { // search open-symbol of tags
switch ( $this -> _src [ $start + 1 ]) { // check next character
case " \n " :
case " \r " :
case " \t " :
case " " :
case " } " : // ignore the tag
2013-07-08 07:43:20 +04:00
$this -> _appendText ( substr ( $this -> _src , $pos , $start - $pos + 2 ));
$end = $start + 1 ;
break ;
2013-07-22 18:03:43 +04:00
case " * " : // comment block
2013-07-08 07:43:20 +04:00
$end = strpos ( $this -> _src , '*}' , $start ); // find end of the comment block
2013-07-29 14:58:14 +04:00
if ( $end === false ) {
2013-07-03 12:10:50 +04:00
throw new CompileException ( " Unclosed comment block in line { $this -> _line } " , 0 , 1 , $this -> _name , $this -> _line );
}
2013-07-08 07:43:20 +04:00
$end ++ ;
2013-07-03 12:10:50 +04:00
$this -> _appendText ( substr ( $this -> _src , $pos , $start - $pos ));
2013-05-30 22:41:58 +04:00
$comment = substr ( $this -> _src , $start , $end - $start ); // read the comment block for processing
2013-06-01 02:00:43 +04:00
$this -> _line += substr_count ( $comment , " \n " ); // count lines in comments
2013-07-03 12:10:50 +04:00
unset ( $comment ); // cleanup
2013-07-08 07:43:20 +04:00
break ;
default :
$this -> _appendText ( substr ( $this -> _src , $pos , $start - $pos ));
$end = $start + 1 ;
do {
2013-07-25 02:05:44 +04:00
$need_more = false ;
2013-07-08 07:43:20 +04:00
$end = strpos ( $this -> _src , '}' , $end + 1 ); // search close-symbol of the tag
2013-07-29 14:58:14 +04:00
if ( $end === false ) { // if unexpected end of template
2013-07-08 07:43:20 +04:00
throw new CompileException ( " Unclosed tag in line { $this -> _line } " , 0 , 1 , $this -> _name , $this -> _line );
}
$tag = substr ( $this -> _src , $start , $end - $start + 1 ); // variable $tag contains fenom tag '{...}'
$_tag = substr ( $tag , 1 , - 1 ); // strip delimiters '{' and '}'
2013-07-29 14:58:14 +04:00
if ( $this -> _ignore ) { // check ignore
if ( $_tag === '/ignore' ) { // turn off ignore
2013-07-08 07:43:20 +04:00
$this -> _ignore = false ;
} else { // still ignore
$this -> _appendText ( $tag );
}
} else {
$tokens = new Tokenizer ( $_tag ); // tokenize the tag
2013-07-29 14:58:14 +04:00
if ( $tokens -> isIncomplete ()) { // all strings finished?
2013-07-25 02:05:44 +04:00
$need_more = true ;
2013-07-08 07:43:20 +04:00
} else {
2013-07-29 14:58:14 +04:00
$this -> _appendCode ( $this -> parseTag ( $tokens ), $tag ); // start the tag lexer
if ( $tokens -> key ()) { // if tokenizer have tokens - throws exceptions
throw new CompileException ( " Unexpected token ' " . $tokens -> current () . " ' in { $this } line { $this -> _line } , near ' { " . $tokens -> getSnippetAsString ( 0 , 0 ) . " ' <- there " , 0 , E_ERROR , $this -> _name , $this -> _line );
2013-07-08 07:43:20 +04:00
}
}
}
2013-07-25 02:05:44 +04:00
} while ( $need_more );
2013-07-08 07:43:20 +04:00
unset ( $_tag , $tag ); // cleanup
break ;
2013-07-03 12:10:50 +04:00
}
2013-07-22 18:03:43 +04:00
$pos = $end + 1 ; // move search-pointer to end of the tag
2013-01-25 18:36:16 +04:00
}
2013-07-08 07:43:20 +04:00
2013-05-30 19:00:00 +04:00
gc_collect_cycles ();
2013-07-22 18:03:43 +04:00
$this -> _appendText ( substr ( $this -> _src , $end ? $end + 1 : 0 )); // append tail of the template
2013-07-29 14:58:14 +04:00
if ( $this -> _stack ) {
2013-01-25 18:36:16 +04:00
$_names = array ();
$_line = 0 ;
2013-07-29 14:58:14 +04:00
foreach ( $this -> _stack as $scope ) {
if ( ! $_line ) {
2013-01-25 18:36:16 +04:00
$_line = $scope -> line ;
}
2013-07-29 14:58:14 +04:00
$_names [] = '{' . $scope -> name . '} opened on line ' . $scope -> line ;
2013-01-25 18:36:16 +04:00
}
2013-07-29 14:58:14 +04:00
throw new CompileException ( " Unclosed tag " . ( count ( $_names ) == 1 ? " " : " s " ) . " : " . implode ( " , " , $_names ), 0 , 1 , $this -> _name , $_line );
2013-01-25 18:36:16 +04:00
}
2013-06-01 02:00:43 +04:00
$this -> _src = " " ; // cleanup
2013-07-29 14:58:14 +04:00
if ( $this -> _post ) {
foreach ( $this -> _post as $cb ) {
2013-01-25 18:36:16 +04:00
call_user_func_array ( $cb , array ( & $this -> _body , $this ));
}
}
2013-07-22 18:03:43 +04:00
$this -> addDepend ( $this ); // for 'verify' performance
2013-08-02 21:50:04 +04:00
foreach ( $this -> _fenom -> getPostFilters () as $filter ) {
2013-08-01 01:05:19 +04:00
$this -> _body = call_user_func ( $filter , $this -> _body , $this );
}
2013-01-28 16:34:34 +04:00
}
2013-01-25 18:36:16 +04:00
2013-07-26 10:40:07 +04:00
/**
2013-08-01 01:05:19 +04:00
* Execute some code at loading cache
2013-07-26 10:40:07 +04:00
* @ param $code
2013-08-01 01:05:19 +04:00
* @ return void
2013-07-26 10:40:07 +04:00
*/
2013-07-29 14:58:14 +04:00
public function before ( $code )
{
2013-07-26 10:40:07 +04:00
$this -> _before .= $code ;
}
2013-03-17 14:37:23 +04:00
/**
* Generate temporary internal template variable
* @ return string
*/
2013-07-29 14:58:14 +04:00
public function tmpVar ()
{
return '$t' . ( $this -> i ++ );
2013-03-15 00:57:28 +04:00
}
2013-02-26 23:56:06 +04:00
/**
* Append plain text to template body
*
* @ param string $text
*/
2013-07-29 14:58:14 +04:00
private function _appendText ( $text )
{
2013-07-03 12:10:50 +04:00
$this -> _line += substr_count ( $text , " \n " );
2013-08-01 01:05:19 +04:00
if ( $this -> _filters ) {
2013-07-29 14:58:14 +04:00
if ( strpos ( $text , " <? " ) === false ) {
2013-08-01 01:05:19 +04:00
foreach ( $this -> _filters as $filter ) {
$text = call_user_func ( $filter , $text , $this );
}
2013-04-28 18:08:57 +04:00
$this -> _body .= $text ;
2013-05-17 22:20:29 +04:00
} else {
$fragments = explode ( " <? " , $text );
2013-07-29 14:58:14 +04:00
foreach ( $fragments as & $fragment ) {
if ( $fragment ) {
2013-08-01 01:05:19 +04:00
foreach ( $this -> _filters as $filter ) {
$fragment = call_user_func ( $filter , $fragment , $this );
2013-05-17 22:20:29 +04:00
}
}
}
$this -> _body .= implode ( '<?php echo "<?"; ?>' , $fragments );
2013-04-28 18:08:57 +04:00
}
} else {
2013-07-29 14:58:14 +04:00
$this -> _body .= str_replace ( " <? " , '<?php echo "<?"; ?>' . PHP_EOL , $text );
2013-04-28 18:08:57 +04:00
}
2013-02-26 23:56:06 +04:00
}
2013-04-28 11:33:36 +04:00
/**
* Append PHP_EOL after each '?>'
* @ param int $code
* @ return string
*/
2013-07-29 14:58:14 +04:00
private function _escapeCode ( $code )
{
2013-02-27 20:55:08 +04:00
$c = " " ;
2013-07-29 14:58:14 +04:00
foreach ( token_get_all ( $code ) as $token ) {
if ( is_string ( $token )) {
2013-02-27 20:55:08 +04:00
$c .= $token ;
2013-07-29 14:58:14 +04:00
} elseif ( $token [ 0 ] == T_CLOSE_TAG ) {
$c .= $token [ 1 ] . PHP_EOL ;
2013-02-27 20:55:08 +04:00
} else {
$c .= $token [ 1 ];
}
}
return $c ;
}
2013-02-26 23:56:06 +04:00
/**
* Append PHP code to template body
*
* @ param string $code
2013-03-14 21:45:00 +04:00
* @ param $source
2013-02-26 23:56:06 +04:00
*/
2013-07-29 14:58:14 +04:00
private function _appendCode ( $code , $source )
{
if ( ! $code ) {
2013-02-27 20:55:08 +04:00
return ;
} else {
2013-07-03 12:10:50 +04:00
$this -> _line += substr_count ( $source , " \n " );
2013-07-29 14:58:14 +04:00
if ( strpos ( $code , '?>' ) !== false ) {
2013-03-14 21:45:00 +04:00
$code = $this -> _escapeCode ( $code ); // paste PHP_EOL
}
2013-07-29 14:58:14 +04:00
$this -> _body .= " <?php \n /* { $this -> _name } : { $this -> _line } : { $source } */ \n $code ?> " . PHP_EOL ;
2013-02-27 20:55:08 +04:00
}
2013-02-26 23:56:06 +04:00
}
/**
* @ param callable [] $cb
*/
2013-07-29 14:58:14 +04:00
public function addPostCompile ( $cb )
{
2013-01-25 18:36:16 +04:00
$this -> _post [] = $cb ;
}
/**
* Return PHP code of template
2013-02-27 20:55:08 +04:00
*
2013-01-25 18:36:16 +04:00
* @ return string
*/
2013-07-29 14:58:14 +04:00
public function getBody ()
{
2013-01-28 16:34:34 +04:00
return $this -> _body ;
}
2013-01-25 18:36:16 +04:00
/**
2013-02-07 17:37:16 +04:00
* Return PHP code for saving to file
2013-02-27 20:55:08 +04:00
*
2013-01-25 18:36:16 +04:00
* @ return string
*/
2013-07-29 14:58:14 +04:00
public function getTemplateCode ()
{
$before = $this -> _before ? $this -> _before . " \n " : " " ;
return " <?php \n " .
" /** Fenom template ' " . $this -> _name . " ' compiled at " . date ( 'Y-m-d H:i:s' ) . " */ \n " .
$before . // some code 'before' template
" return new Fenom \\ Render( \$ fenom, " . $this -> _getClosureSource () . " , " . var_export ( array (
" options " => $this -> _options ,
" provider " => $this -> _scm ,
" name " => $this -> _name ,
" base_name " => $this -> _base_name ,
" time " => $this -> _time ,
" depends " => $this -> _depends
), true ) . " ); \n " ;
2013-01-25 18:36:16 +04:00
}
/**
* Return closure code
* @ return string
*/
2013-07-29 14:58:14 +04:00
private function _getClosureSource ()
{
2013-01-25 18:36:16 +04:00
return " function ( \$ tpl) { \n ?> { $this -> _body } <?php \n } " ;
}
/**
* Runtime execute template .
*
* @ param array $values input values
* @ throws CompileException
* @ return Render
*/
2013-07-29 14:58:14 +04:00
public function display ( array $values )
{
if ( ! $this -> _code ) {
2013-01-25 18:36:16 +04:00
// evaluate template's code
2013-07-29 14:58:14 +04:00
eval ( " \$ this->_code = " . $this -> _getClosureSource () . " ; " );
if ( ! $this -> _code ) {
2013-01-25 18:36:16 +04:00
throw new CompileException ( " Fatal error while creating the template " );
}
}
return parent :: display ( $values );
}
2013-02-23 16:35:11 +04:00
/**
* Add depends from template
* @ param Render $tpl
*/
2013-07-29 14:58:14 +04:00
public function addDepend ( Render $tpl )
{
2013-02-23 16:35:11 +04:00
$this -> _depends [ $tpl -> getScm ()][ $tpl -> getName ()] = $tpl -> getTime ();
}
2013-02-07 17:37:16 +04:00
2013-07-06 14:03:05 +04:00
/**
* Output the value
*
* @ param $data
* @ return string
*/
2013-07-29 14:58:14 +04:00
public function out ( $data )
{
if ( $this -> escape ) {
2013-07-07 01:29:33 +04:00
return " echo htmlspecialchars( $data , ENT_COMPAT, 'UTF-8'); " ;
2013-07-04 01:28:10 +04:00
} else {
2013-07-07 01:29:33 +04:00
return " echo $data ; " ;
2013-07-04 01:28:10 +04:00
}
}
2013-07-29 14:58:14 +04:00
2013-07-03 12:10:50 +04:00
/**
2013-07-22 18:03:43 +04:00
* Tag router
2013-07-03 12:10:50 +04:00
* @ param Tokenizer $tokens
*
* @ throws SecurityException
* @ throws CompileException
* @ return string executable PHP code
*/
2013-07-29 14:58:14 +04:00
public function parseTag ( Tokenizer $tokens )
{
2013-01-28 16:34:34 +04:00
try {
2013-07-29 14:58:14 +04:00
if ( $tokens -> is ( Tokenizer :: MACRO_STRING )) {
if ( $tokens -> current () === " ignore " ) {
2013-03-14 21:45:00 +04:00
$this -> _ignore = true ;
$tokens -> next ();
return '' ;
} else {
2013-07-07 01:29:33 +04:00
return $this -> parseAct ( $tokens );
2013-03-14 21:45:00 +04:00
}
} elseif ( $tokens -> is ( '/' )) {
2013-07-22 18:03:43 +04:00
return $this -> parseEndTag ( $tokens );
2013-03-14 21:45:00 +04:00
} elseif ( $tokens -> is ( '#' )) {
2013-07-22 18:03:43 +04:00
return $this -> out ( $this -> parseConst ( $tokens ), $tokens );
2013-02-23 16:35:11 +04:00
} else {
2013-07-22 18:03:43 +04:00
return $this -> out ( $this -> parseExp ( $tokens ), $tokens );
2013-01-25 18:36:16 +04:00
}
2013-07-02 11:07:33 +04:00
} catch ( InvalidUsageException $e ) {
2013-07-29 14:58:14 +04:00
throw new CompileException ( $e -> getMessage () . " in { $this } line { $this -> _line } " , 0 , E_ERROR , $this -> _name , $this -> _line , $e );
2013-01-25 18:36:16 +04:00
} catch ( \LogicException $e ) {
2013-07-29 14:58:14 +04:00
throw new SecurityException ( $e -> getMessage () . " in { $this } line { $this -> _line } , near ' { " . $tokens -> getSnippetAsString ( 0 , 0 ) . " ' <- there " , 0 , E_ERROR , $this -> _name , $this -> _line , $e );
2013-01-28 16:34:34 +04:00
} catch ( \Exception $e ) {
2013-07-29 14:58:14 +04:00
throw new CompileException ( $e -> getMessage () . " in { $this } line { $this -> _line } , near ' { " . $tokens -> getSnippetAsString ( 0 , 0 ) . " ' <- there " , 0 , E_ERROR , $this -> _name , $this -> _line , $e );
2013-01-28 16:34:34 +04:00
}
}
2013-01-25 18:36:16 +04:00
/**
* Close tag handler
2013-02-21 22:51:24 +04:00
*
2013-01-25 18:36:16 +04:00
* @ param Tokenizer $tokens
2013-07-07 01:29:33 +04:00
* @ return string
2013-01-25 18:36:16 +04:00
* @ throws TokenizeException
*/
2013-07-29 14:58:14 +04:00
public function parseEndTag ( Tokenizer $tokens )
{
2013-01-28 16:34:34 +04:00
$name = $tokens -> getNext ( Tokenizer :: MACRO_STRING );
$tokens -> next ();
2013-07-29 14:58:14 +04:00
if ( ! $this -> _stack ) {
2013-01-28 16:34:34 +04:00
throw new TokenizeException ( " Unexpected closing of the tag ' $name ', the tag hasn't been opened " );
}
/** @var Scope $scope */
$scope = array_pop ( $this -> _stack );
2013-07-29 14:58:14 +04:00
if ( $scope -> name !== $name ) {
2013-01-28 16:34:34 +04:00
throw new TokenizeException ( " Unexpected closing of the tag ' $name ' (expecting closing of the tag { $scope -> name } , opened on line { $scope -> line } ) " );
}
2013-07-29 14:58:14 +04:00
if ( $scope -> is_compiler ) {
2013-07-07 01:29:33 +04:00
return $scope -> close ( $tokens );
} else {
2013-07-07 11:29:26 +04:00
$code = $this -> out ( $scope -> close ( $tokens ));
2013-07-22 18:03:43 +04:00
$scope -> tpl -> escape = $scope -> escape ; // restore escape option
2013-07-07 11:29:26 +04:00
return $code ;
2013-07-07 01:29:33 +04:00
}
}
/**
* Get current scope
* @ return Scope
*/
2013-07-29 14:58:14 +04:00
public function getLastScope ()
{
2013-07-07 01:29:33 +04:00
return end ( $this -> _stack );
2013-01-28 16:34:34 +04:00
}
2013-01-25 18:36:16 +04:00
/**
* Parse action { action ... } or { action ( ... ) ... }
*
* @ static
* @ param Tokenizer $tokens
2013-02-20 18:03:53 +04:00
* @ throws \LogicException
2013-01-25 18:36:16 +04:00
* @ throws TokenizeException
* @ return string
*/
2013-07-29 14:58:14 +04:00
public function parseAct ( Tokenizer $tokens )
{
if ( $tokens -> is ( Tokenizer :: MACRO_STRING )) {
2013-02-23 02:03:05 +04:00
$action = $tokens -> getAndNext ();
2013-01-25 18:36:16 +04:00
} else {
2013-07-22 18:03:43 +04:00
return $this -> out ( $this -> parseExp ( $tokens )); // may be math and/or boolean expression
2013-01-25 18:36:16 +04:00
}
2013-07-29 14:58:14 +04:00
if ( $tokens -> is ( " ( " , T_NAMESPACE , T_DOUBLE_COLON ) && ! $tokens -> isWhiteSpaced ()) { // just invoke function or static method
2013-02-23 02:03:05 +04:00
$tokens -> back ();
2013-07-22 18:03:43 +04:00
return $this -> out ( $this -> parseExp ( $tokens ));
}
2013-07-29 14:58:14 +04:00
if ( $tokens -> is ( '.' )) {
2013-02-23 02:03:05 +04:00
$name = $tokens -> skip () -> get ( Tokenizer :: MACRO_STRING );
2013-07-29 14:58:14 +04:00
if ( $action !== " macro " ) {
$name = $action . " . " . $name ;
2013-02-23 02:03:05 +04:00
}
2013-07-29 14:53:21 +04:00
return $this -> parseMacroCall ( $tokens , $name );
2013-01-25 18:36:16 +04:00
}
2013-07-29 14:58:14 +04:00
if ( $tag = $this -> _fenom -> getTag ( $action , $this )) { // call some function
switch ( $tag [ " type " ]) {
2013-06-28 11:53:53 +04:00
case Fenom :: BLOCK_COMPILER :
2013-07-24 19:37:07 +04:00
$scope = new Scope ( $action , $this , $this -> _line , $tag , count ( $this -> _stack ), $this -> _body );
2013-03-17 14:37:23 +04:00
$code = $scope -> open ( $tokens );
2013-07-29 14:58:14 +04:00
if ( ! $scope -> is_closed ) {
2013-03-17 14:37:23 +04:00
array_push ( $this -> _stack , $scope );
}
return $code ;
2013-06-28 11:53:53 +04:00
case Fenom :: INLINE_COMPILER :
2013-07-24 19:37:07 +04:00
return call_user_func ( $tag [ " parser " ], $tokens , $this );
2013-06-28 11:53:53 +04:00
case Fenom :: INLINE_FUNCTION :
2013-07-24 19:37:07 +04:00
return $this -> out ( call_user_func ( $tag [ " parser " ], $tag [ " function " ], $tokens , $this ));
2013-06-28 11:53:53 +04:00
case Fenom :: BLOCK_FUNCTION :
2013-07-24 19:37:07 +04:00
$scope = new Scope ( $action , $this , $this -> _line , $tag , count ( $this -> _stack ), $this -> _body );
$scope -> setFuncName ( $tag [ " function " ]);
2013-01-25 18:36:16 +04:00
array_push ( $this -> _stack , $scope );
2013-07-22 18:03:43 +04:00
$scope -> escape = $this -> escape ;
$this -> escape = false ;
2013-01-25 18:36:16 +04:00
return $scope -> open ( $tokens );
2013-02-20 18:03:53 +04:00
default :
throw new \LogicException ( " Unknown function type " );
2013-01-25 18:36:16 +04:00
}
2013-01-28 16:34:34 +04:00
}
2013-01-25 18:36:16 +04:00
2013-07-29 14:58:14 +04:00
for ( $j = $i = count ( $this -> _stack ) - 1 ; $i >= 0 ; $i -- ) { // call function's internal tag
if ( $this -> _stack [ $i ] -> hasTag ( $action , $j - $i )) {
2013-01-28 16:34:34 +04:00
return $this -> _stack [ $i ] -> tag ( $action , $tokens );
}
}
2013-07-29 14:58:14 +04:00
if ( $tags = $this -> _fenom -> getTagOwners ( $action )) { // unknown template tag
throw new TokenizeException ( " Unexpected tag ' $action ' (this tag can be used with ' " . implode ( " ', ' " , $tags ) . " ') " );
2013-01-25 18:36:16 +04:00
} else {
throw new TokenizeException ( " Unexpected tag $action " );
}
2013-01-28 16:34:34 +04:00
}
2013-01-25 18:36:16 +04:00
/**
2013-08-02 20:29:18 +04:00
* Parse expressions . The mix of operations and terms .
2013-01-25 18:36:16 +04:00
*
* @ param Tokenizer $tokens
2013-07-24 19:37:07 +04:00
* @ param bool $required
2013-01-25 18:36:16 +04:00
* @ return string
2013-08-02 20:29:18 +04:00
* @ throws Error\UnexpectedTokenException
2013-01-25 18:36:16 +04:00
*/
2013-08-02 21:50:04 +04:00
public function parseExp ( Tokenizer $tokens , $required = false )
{
$exp = array ();
$var = false ; // last term was: true - variable, false - mixed
$op = false ; // last exp was operator
2013-08-02 20:29:18 +04:00
$cond = false ; // was conditional operator
2013-08-02 21:50:04 +04:00
while ( $tokens -> valid ()) {
2013-08-02 20:29:18 +04:00
// parse term
$term = $this -> parseTerm ( $tokens , $var );
2013-08-02 21:50:04 +04:00
if ( $term !== false ) {
2013-08-02 20:29:18 +04:00
$exp [] = $term ;
$op = false ;
} else {
break ;
}
2013-08-02 21:50:04 +04:00
if ( ! $tokens -> valid ()) {
2013-08-02 20:29:18 +04:00
break ;
}
// parse operator
2013-08-02 21:50:04 +04:00
if ( $tokens -> is ( Tokenizer :: MACRO_BINARY )) {
2013-08-02 20:29:18 +04:00
if ( $tokens -> is ( Tokenizer :: MACRO_COND )) {
if ( $cond ) {
2013-07-22 18:03:43 +04:00
break ;
}
2013-08-02 20:29:18 +04:00
$cond = true ;
}
$op = $tokens -> getAndNext ();
2013-08-02 21:50:04 +04:00
} elseif ( $tokens -> is ( Tokenizer :: MACRO_EQUALS )) {
if ( ! $var ) {
2013-08-02 20:29:18 +04:00
break ;
}
$op = $tokens -> getAndNext ();
2013-08-02 21:50:04 +04:00
} elseif ( $tokens -> is ( T_STRING )) {
2013-08-02 20:29:18 +04:00
if ( ! $exp ) {
break ;
}
$operator = $tokens -> current ();
if ( $operator == " is " ) {
$item = array_pop ( $exp );
$exp [] = $this -> parseIs ( $tokens , $item , $var );
} elseif ( $operator == " in " || ( $operator == " not " && $tokens -> isNextToken ( " in " ))) {
$item = array_pop ( $exp );
$exp [] = $this -> parseIn ( $tokens , $item , $var );
2013-01-25 18:36:16 +04:00
} else {
2013-08-02 20:29:18 +04:00
break ;
2013-01-25 18:36:16 +04:00
}
2013-08-02 21:50:04 +04:00
} elseif ( $tokens -> is ( '~' )) {
2013-08-02 20:29:18 +04:00
// string concat coming soon
} else {
break ;
}
2013-08-02 21:50:04 +04:00
if ( $op ) {
2013-08-02 20:29:18 +04:00
$exp [] = $op ;
}
}
if ( $op ) {
throw new UnexpectedTokenException ( $tokens );
}
if ( $required && ! $exp ) {
throw new UnexpectedTokenException ( $tokens );
}
return implode ( ' ' , $exp );
}
/**
* Parse any term : - 2 , ++ $var , 'adf' | mod : 4
*
* @ param Tokenizer $tokens
* @ param bool $is_var
* @ return bool | string
* @ throws Error\UnexpectedTokenException
* @ throws Error\TokenizeException
* @ throws \Exception
*/
2013-08-02 21:50:04 +04:00
public function parseTerm ( Tokenizer $tokens , & $is_var = false )
{
2013-08-02 20:29:18 +04:00
$is_var = false ;
2013-08-02 21:50:04 +04:00
$unary = " " ;
2013-08-02 20:29:18 +04:00
term : {
2013-08-02 21:50:04 +04:00
if ( $tokens -> is ( T_LNUMBER , T_DNUMBER )) {
return $unary . $this -> parseScalar ( $tokens , true );
} elseif ( $tokens -> is ( T_CONSTANT_ENCAPSED_STRING , '"' , T_ENCAPSED_AND_WHITESPACE )) {
if ( $unary ) {
throw new UnexpectedTokenException ( $tokens -> back ());
}
return $this -> parseScalar ( $tokens , true );
} elseif ( $tokens -> is ( T_VARIABLE )) {
$var = $this -> parseVar ( $tokens );
if ( $tokens -> is ( Tokenizer :: MACRO_INCDEC , " | " , " ! " , " ? " )) {
return $unary . $this -> parseVariable ( $tokens , 0 , $var );
} elseif ( $tokens -> is ( " ( " ) && $tokens -> hasBackList ( T_STRING )) { // method call
return $unary . $this -> parseVariable ( $tokens , 0 , $var );
} elseif ( $unary ) {
return $unary . $var ;
} else {
$is_var = true ;
return $var ;
}
} elseif ( $tokens -> is ( Tokenizer :: MACRO_INCDEC )) {
return $unary . $this -> parseVariable ( $tokens );
} elseif ( $tokens -> is ( " ( " )) {
$tokens -> next ();
$exp = $unary . " ( " . $this -> parseExp ( $tokens , true ) . " ) " ;
$tokens -> need ( " ) " ) -> next ();
return $exp ;
} elseif ( $tokens -> is ( Tokenizer :: MACRO_UNARY )) {
if ( $unary ) {
throw new UnexpectedTokenException ( $tokens );
}
$unary = $tokens -> getAndNext ();
goto term ;
} elseif ( $tokens -> is ( T_STRING )) {
if ( $tokens -> isSpecialVal ()) {
return $unary . $tokens -> getAndNext ();
} elseif ( $tokens -> isNext ( " ( " ) && ! $tokens -> getWhitespace ()) {
$func = $this -> _fenom -> getModifier ( $tokens -> current (), $this );
if ( ! $func ) {
throw new \Exception ( " Function " . $tokens -> getAndNext () . " not found " );
2013-08-02 20:29:18 +04:00
}
2013-07-22 18:03:43 +04:00
$tokens -> next ();
2013-08-02 21:50:04 +04:00
$func = $func . $this -> parseArgs ( $tokens );
if ( $tokens -> is ( '|' )) {
return $unary . $this -> parseModifier ( $tokens , $func );
2013-08-02 20:29:18 +04:00
} else {
2013-08-02 21:50:04 +04:00
return $unary . $func ;
2013-01-28 16:34:34 +04:00
}
2013-01-25 18:36:16 +04:00
} else {
2013-08-02 20:29:18 +04:00
return false ;
2013-01-25 18:36:16 +04:00
}
2013-08-02 21:50:04 +04:00
} elseif ( $tokens -> is ( T_ISSET , T_EMPTY )) {
$func = $tokens -> getAndNext ();
if ( $tokens -> is ( " ( " ) && $tokens -> isNext ( T_VARIABLE )) {
$tokens -> next ();
$exp = $func . " ( " . $this -> parseVar ( $tokens ) . " ) " ;
$tokens -> need ( ')' ) -> next ();
return $unary . $exp ;
} else {
throw new TokenizeException ( " Unexpected token " . $tokens -> getNext () . " , isset() and empty() accept only variables " );
}
} elseif ( $tokens -> is ( '[' )) {
if ( $unary ) {
throw new UnexpectedTokenException ( $tokens -> back ());
}
return $this -> parseArray ( $tokens );
} elseif ( $unary ) {
$tokens -> back ();
throw new UnexpectedTokenException ( $tokens );
} else {
return false ;
2013-01-25 18:36:16 +04:00
}
2013-01-28 16:34:34 +04:00
}
2013-08-02 21:50:04 +04:00
}
2013-01-25 18:36:16 +04:00
2013-07-03 12:22:35 +04:00
/**
* Parse simple variable ( without modifier etc )
*
* @ param Tokenizer $tokens
* @ param int $options
* @ return string
*/
2013-07-29 14:58:14 +04:00
public function parseVar ( Tokenizer $tokens , $options = 0 )
{
2013-01-25 18:36:16 +04:00
$var = $tokens -> get ( T_VARIABLE );
2013-07-29 14:58:14 +04:00
$_var = '$tpl["' . substr ( $var , 1 ) . '"]' ;
2013-01-25 18:36:16 +04:00
$tokens -> next ();
2013-07-29 14:58:14 +04:00
while ( $t = $tokens -> key ()) {
if ( $t === " . " && ! ( $options & self :: DENY_ARRAY )) {
2013-01-25 18:36:16 +04:00
$key = $tokens -> getNext ();
2013-07-29 14:58:14 +04:00
if ( $tokens -> is ( T_VARIABLE )) {
$key = " [ " . $this -> parseVariable ( $tokens , self :: DENY_ARRAY ) . " ] " ;
} elseif ( $tokens -> is ( Tokenizer :: MACRO_STRING )) {
$key = '["' . $key . '"]' ;
2013-07-22 18:03:43 +04:00
$tokens -> next ();
2013-07-29 14:58:14 +04:00
} elseif ( $tokens -> is ( Tokenizer :: MACRO_SCALAR , '"' )) {
$key = " [ " . $this -> parseScalar ( $tokens , false ) . " ] " ;
2013-01-25 18:36:16 +04:00
} else {
break ;
}
$_var .= $key ;
2013-07-29 14:58:14 +04:00
} elseif ( $t === " [ " && ! ( $options & self :: DENY_ARRAY )) {
2013-01-25 18:36:16 +04:00
$tokens -> next ();
2013-07-29 14:58:14 +04:00
if ( $tokens -> is ( Tokenizer :: MACRO_STRING )) {
if ( $tokens -> isNext ( " ( " )) {
$key = " [ " . $this -> parseExp ( $tokens ) . " ] " ;
2013-01-25 18:36:16 +04:00
} else {
2013-07-29 14:58:14 +04:00
$key = '["' . $tokens -> current () . '"]' ;
2013-01-25 18:36:16 +04:00
$tokens -> next ();
}
} else {
2013-07-29 14:58:14 +04:00
$key = " [ " . $this -> parseExp ( $tokens , true ) . " ] " ;
2013-01-25 18:36:16 +04:00
}
$tokens -> get ( " ] " );
$tokens -> next ();
$_var .= $key ;
2013-07-29 14:58:14 +04:00
} elseif ( $t === T_DNUMBER ) {
$_var .= '[' . substr ( $tokens -> getAndNext (), 1 ) . ']' ;
} elseif ( $t === T_OBJECT_OPERATOR ) {
$_var .= " -> " . $tokens -> getNext ( T_STRING );
2013-07-22 18:03:43 +04:00
$tokens -> next ();
2013-03-17 14:37:23 +04:00
} else {
break ;
}
}
2013-07-29 14:58:14 +04:00
if ( $this -> _options & Fenom :: FORCE_VERIFY ) {
return 'isset(' . $_var . ') ? ' . $_var . ' : null' ;
2013-07-22 18:03:43 +04:00
} else {
return $_var ;
}
2013-03-17 14:37:23 +04:00
}
/**
2013-07-22 18:03:43 +04:00
* Parse complex variable
2013-03-17 14:37:23 +04:00
* $var . foo [ bar ][ " a " ][ 1 + 3 / $var ] | mod : 3 : " w " : $var3 | mod3
2013-07-22 18:03:43 +04:00
* ++ $var | mod
* $var --| mod
2013-03-17 14:37:23 +04:00
*
* @ see parseModifier
* @ static
* @ param Tokenizer $tokens
2013-07-22 18:03:43 +04:00
* @ param int $options set parser options
* @ param string $var already parsed plain variable
2013-03-17 14:37:23 +04:00
* @ throws \LogicException
2013-07-22 18:03:43 +04:00
* @ throws InvalidUsageException
2013-03-17 14:37:23 +04:00
* @ return string
*/
2013-07-29 14:58:14 +04:00
public function parseVariable ( Tokenizer $tokens , $options = 0 , $var = null )
{
2013-07-22 18:03:43 +04:00
$stained = false ;
2013-07-29 14:58:14 +04:00
if ( ! $var ) {
if ( $tokens -> is ( Tokenizer :: MACRO_INCDEC )) {
2013-07-22 18:03:43 +04:00
$stained = true ;
2013-07-29 14:58:14 +04:00
$var = $tokens -> getAndNext () . $this -> parseVar ( $tokens , $options );
2013-01-25 18:36:16 +04:00
} else {
2013-07-22 18:03:43 +04:00
$var = $this -> parseVar ( $tokens , $options );
}
2013-07-29 14:58:14 +04:00
if ( $tokens -> is ( T_OBJECT_OPERATOR )) { // parse
$var .= '->' . $tokens -> getNext ( T_STRING );
2013-07-22 18:03:43 +04:00
$tokens -> next ();
2013-01-25 18:36:16 +04:00
}
}
2013-07-22 18:03:43 +04:00
2013-07-29 14:58:14 +04:00
if ( $tokens -> is ( " ( " ) && $tokens -> hasBackList ( T_STRING , T_OBJECT_OPERATOR )) {
if ( $stained ) {
2013-07-22 18:03:43 +04:00
throw new InvalidUsageException ( " Can not increment or decrement of the method result " );
}
2013-07-29 14:58:14 +04:00
if ( $this -> _options & Fenom :: DENY_METHODS ) {
2013-07-22 18:03:43 +04:00
throw new \LogicException ( " Forbidden to call methods " );
}
$var .= $this -> parseArgs ( $tokens );
$stained = true ;
}
2013-07-29 14:58:14 +04:00
if ( $tokens -> is ( '?' , '!' )) {
2013-07-22 18:03:43 +04:00
return $this -> parseTernary ( $tokens , $var , $tokens -> current ());
}
2013-07-29 14:58:14 +04:00
if ( $tokens -> is ( Tokenizer :: MACRO_INCDEC )) {
if ( $stained ) {
2013-07-22 18:03:43 +04:00
throw new InvalidUsageException ( " Can not use two increments and/or decrements for one variable " );
}
$var .= $tokens -> getAndNext ();
}
2013-07-29 14:58:14 +04:00
if ( $tokens -> is ( '|' ) && ! ( $options & self :: DENY_MODS )) {
2013-07-22 18:03:43 +04:00
return $this -> parseModifier ( $tokens , $var );
}
return $var ;
2013-01-28 16:34:34 +04:00
}
2013-01-25 18:36:16 +04:00
2013-07-03 12:22:35 +04:00
/**
* Parse ternary operator
*
* @ param Tokenizer $tokens
* @ param $var
* @ param $type
* @ return string
* @ throws UnexpectedTokenException
*/
2013-07-29 14:58:14 +04:00
public function parseTernary ( Tokenizer $tokens , $var , $type )
{
2013-03-17 14:37:23 +04:00
$empty = ( $type === " ? " );
$tokens -> next ();
2013-07-29 14:58:14 +04:00
if ( $tokens -> is ( " : " )) {
2013-03-17 14:37:23 +04:00
$tokens -> next ();
2013-07-29 14:58:14 +04:00
if ( $empty ) {
return '(empty(' . $var . ') ? (' . $this -> parseExp ( $tokens , true ) . ') : ' . $var . ')' ;
2013-03-17 14:37:23 +04:00
} else {
2013-07-29 14:58:14 +04:00
return '(isset(' . $var . ') ? ' . $var . ' : (' . $this -> parseExp ( $tokens , true ) . '))' ;
2013-03-17 14:37:23 +04:00
}
2013-07-29 14:58:14 +04:00
} elseif ( $tokens -> is ( Tokenizer :: MACRO_BINARY , Tokenizer :: MACRO_BOOLEAN , Tokenizer :: MACRO_MATH ) || ! $tokens -> valid ()) {
if ( $empty ) {
return '!empty(' . $var . ')' ;
2013-03-17 14:37:23 +04:00
} else {
2013-07-29 14:58:14 +04:00
return 'isset(' . $var . ')' ;
2013-03-17 14:37:23 +04:00
}
} else {
$expr1 = $this -> parseExp ( $tokens , true );
2013-07-29 14:58:14 +04:00
if ( ! $tokens -> is ( " : " )) {
2013-03-17 14:37:23 +04:00
throw new UnexpectedTokenException ( $tokens , null , " ternary operator " );
}
$expr2 = $this -> parseExp ( $tokens , true );
2013-07-29 14:58:14 +04:00
if ( $empty ) {
return '(empty(' . $var . ') ? ' . $expr2 . ' : ' . $expr1 . ')' ;
2013-03-17 14:37:23 +04:00
} else {
2013-07-29 14:58:14 +04:00
return '(isset(' . $var . ') ? ' . $expr1 . ' : ' . $expr2 . ')' ;
2013-03-17 14:37:23 +04:00
}
}
}
2013-07-22 18:03:43 +04:00
/**
2013-07-29 14:53:21 +04:00
* Parse 'is' and 'is not' operators
2013-07-22 18:03:43 +04:00
* @ see $_checkers
* @ param Tokenizer $tokens
* @ param string $value
* @ param bool $variable
* @ throws InvalidUsageException
* @ return string
*/
2013-07-29 14:58:14 +04:00
public function parseIs ( Tokenizer $tokens , $value , $variable = false )
{
2013-07-22 18:03:43 +04:00
$tokens -> next ();
2013-07-29 14:58:14 +04:00
if ( $tokens -> current () == 'not' ) {
2013-07-22 18:03:43 +04:00
$invert = '!' ;
$equal = '!=' ;
$tokens -> next ();
} else {
$invert = '' ;
$equal = '==' ;
}
2013-07-29 14:58:14 +04:00
if ( $tokens -> is ( Tokenizer :: MACRO_STRING )) {
2013-07-22 18:03:43 +04:00
$action = $tokens -> current ();
2013-07-29 14:58:14 +04:00
if ( ! $variable && ( $action == " set " || $action == " empty " )) {
2013-07-22 18:03:43 +04:00
$action = " _ $action " ;
$tokens -> next ();
2013-07-29 14:58:14 +04:00
return $invert . sprintf ( self :: $_checkers [ $action ], $value );
} elseif ( isset ( self :: $_checkers [ $action ])) {
2013-07-22 18:03:43 +04:00
$tokens -> next ();
2013-07-29 14:58:14 +04:00
return $invert . sprintf ( self :: $_checkers [ $action ], $value );
} elseif ( $tokens -> isSpecialVal ()) {
2013-07-22 18:03:43 +04:00
$tokens -> next ();
2013-07-29 14:58:14 +04:00
return '(' . $value . ' ' . $equal . '= ' . $action . ')' ;
2013-07-22 18:03:43 +04:00
}
2013-07-29 14:58:14 +04:00
return $invert . '(' . $value . ' instanceof \\' . $this -> parseName ( $tokens ) . ')' ;
} elseif ( $tokens -> is ( T_VARIABLE )) {
return '(' . $value . ' ' . $equal . '= ' . $this -> parseVariable ( $tokens ) . ')' ;
} elseif ( $tokens -> is ( Tokenizer :: MACRO_SCALAR )) {
return '(' . $value . ' ' . $equal . '= ' . $this -> parseScalar ( $tokens ) . ')' ;
} elseif ( $tokens -> is ( '[' )) {
return '(' . $value . ' ' . $equal . '= ' . $this -> parseArray ( $tokens ) . ')' ;
} elseif ( $tokens -> is ( T_NS_SEPARATOR )) { //
return $invert . '(' . $value . ' instanceof \\' . $this -> parseName ( $tokens ) . ')' ;
2013-07-22 18:03:43 +04:00
} else {
throw new InvalidUsageException ( " Unknown argument " );
}
}
/**
* Parse 'in' and 'not in' operators
* @ param Tokenizer $tokens
* @ param string $value
* @ throws InvalidUsageException
* @ throws UnexpectedTokenException
* @ return string
*/
2013-07-29 14:58:14 +04:00
public function parseIn ( Tokenizer $tokens , $value )
{
2013-07-22 18:03:43 +04:00
$checkers = array (
" string " => 'is_int(strpos(%2$s, %1$s))' ,
2013-07-29 14:58:14 +04:00
" list " => " in_array(%s, %s) " ,
" keys " => " array_key_exists(%s, %s) " ,
" auto " => '\Fenom\Modifier::in(%s, %s)'
2013-07-22 18:03:43 +04:00
);
$checker = null ;
$invert = '' ;
2013-07-29 14:58:14 +04:00
if ( $tokens -> current () == 'not' ) {
2013-07-22 18:03:43 +04:00
$invert = '!' ;
$tokens -> next ();
}
2013-07-29 14:58:14 +04:00
if ( $tokens -> current () !== " in " ) {
2013-07-22 18:03:43 +04:00
throw new UnexpectedTokenException ( $tokens );
}
$tokens -> next ();
2013-07-29 14:58:14 +04:00
if ( $tokens -> is ( Tokenizer :: MACRO_STRING )) {
2013-07-22 18:03:43 +04:00
$checker = $tokens -> current ();
2013-07-29 14:58:14 +04:00
if ( ! isset ( $checkers [ $checker ])) {
2013-07-22 18:03:43 +04:00
throw new UnexpectedTokenException ( $tokens );
}
$tokens -> next ();
}
2013-07-29 14:58:14 +04:00
if ( $tokens -> is ( '[' )) {
if ( $checker == " string " ) {
2013-07-22 18:03:43 +04:00
throw new InvalidUsageException ( " Can not use string operation for array " );
2013-07-29 14:58:14 +04:00
} elseif ( ! $checker ) {
2013-07-22 18:03:43 +04:00
$checker = " list " ;
}
2013-07-29 14:58:14 +04:00
return $invert . sprintf ( $checkers [ $checker ], $value , $this -> parseArray ( $tokens ));
} elseif ( $tokens -> is ( '"' , T_ENCAPSED_AND_WHITESPACE , T_CONSTANT_ENCAPSED_STRING )) {
if ( ! $checker ) {
2013-07-22 18:03:43 +04:00
$checker = " string " ;
2013-07-29 14:58:14 +04:00
} elseif ( $checker != " string " ) {
2013-07-22 18:03:43 +04:00
throw new InvalidUsageException ( " Can not use array operation for string " );
}
2013-07-29 14:58:14 +04:00
return $invert . sprintf ( $checkers [ $checker ], " strval( $value ) " , $this -> parseScalar ( $tokens ));
} elseif ( $tokens -> is ( T_VARIABLE , Tokenizer :: MACRO_INCDEC )) {
if ( ! $checker ) {
2013-07-22 18:03:43 +04:00
$checker = " auto " ;
}
2013-07-29 14:58:14 +04:00
return $invert . sprintf ( $checkers [ $checker ], $value , $this -> parseVariable ( $tokens ));
2013-07-22 18:03:43 +04:00
} else {
throw new UnexpectedTokenException ( $tokens );
}
}
/**
* Parse method , class or constant name
*
* @ param Tokenizer $tokens
* @ return string
*/
2013-07-29 14:58:14 +04:00
public function parseName ( Tokenizer $tokens )
{
2013-07-22 18:03:43 +04:00
$tokens -> skipIf ( T_NS_SEPARATOR );
$name = " " ;
2013-07-29 14:58:14 +04:00
if ( $tokens -> is ( T_STRING )) {
2013-07-22 18:03:43 +04:00
$name .= $tokens -> getAndNext ();
2013-07-29 14:58:14 +04:00
while ( $tokens -> is ( T_NS_SEPARATOR )) {
$name .= '\\' . $tokens -> next () -> get ( T_STRING );
2013-07-22 18:03:43 +04:00
$tokens -> next ();
}
}
return $name ;
}
2013-01-25 18:36:16 +04:00
/**
* Parse scalar values
*
* @ param Tokenizer $tokens
* @ param bool $allow_mods
* @ return string
* @ throws TokenizeException
*/
2013-07-29 14:58:14 +04:00
public function parseScalar ( Tokenizer $tokens , $allow_mods = true )
{
2013-01-25 18:36:16 +04:00
$_scalar = " " ;
2013-07-29 14:58:14 +04:00
if ( $token = $tokens -> key ()) {
switch ( $token ) {
2013-01-25 18:36:16 +04:00
case T_CONSTANT_ENCAPSED_STRING :
case T_LNUMBER :
case T_DNUMBER :
$_scalar .= $tokens -> getAndNext ();
break ;
case T_ENCAPSED_AND_WHITESPACE :
case '"' :
2013-08-02 20:29:18 +04:00
$_scalar .= $this -> parseQuote ( $tokens );
2013-01-25 18:36:16 +04:00
break ;
default :
2013-07-29 14:58:14 +04:00
throw new TokenizeException ( " Unexpected scalar token ' " . $tokens -> current () . " ' " );
2013-01-25 18:36:16 +04:00
}
2013-07-29 14:58:14 +04:00
if ( $allow_mods && $tokens -> is ( " | " )) {
2013-01-25 18:36:16 +04:00
return $this -> parseModifier ( $tokens , $_scalar );
}
}
return $_scalar ;
}
2013-07-03 12:10:50 +04:00
/**
* Parse string with or without variable
*
* @ param Tokenizer $tokens
* @ throws UnexpectedTokenException
* @ return string
*/
2013-08-02 20:29:18 +04:00
public function parseQuote ( Tokenizer $tokens )
2013-07-29 14:58:14 +04:00
{
if ( $tokens -> is ( '"' , " ` " )) {
2013-05-30 19:00:00 +04:00
$stop = $tokens -> current ();
$_str = '"' ;
$tokens -> next ();
2013-07-29 14:58:14 +04:00
while ( $t = $tokens -> key ()) {
if ( $t === T_ENCAPSED_AND_WHITESPACE ) {
2013-05-30 19:00:00 +04:00
$_str .= $tokens -> current ();
$tokens -> next ();
2013-07-29 14:58:14 +04:00
} elseif ( $t === T_VARIABLE ) {
if ( strlen ( $_str ) > 1 ) {
2013-05-30 19:00:00 +04:00
$_str .= '".' ;
} else {
$_str = " " ;
}
2013-07-29 14:58:14 +04:00
$_str .= '$tpl["' . substr ( $tokens -> current (), 1 ) . '"]' ;
2013-05-30 19:00:00 +04:00
$tokens -> next ();
2013-07-29 14:58:14 +04:00
if ( $tokens -> is ( $stop )) {
2013-05-30 19:00:00 +04:00
$tokens -> skip ();
return $_str ;
} else {
$_str .= '."' ;
}
2013-07-29 14:58:14 +04:00
} elseif ( $t === T_CURLY_OPEN ) {
if ( strlen ( $_str ) > 1 ) {
2013-05-30 19:00:00 +04:00
$_str .= '".' ;
} else {
$_str = " " ;
}
$tokens -> getNext ( T_VARIABLE );
2013-07-29 14:58:14 +04:00
$_str .= '(' . $this -> parseExp ( $tokens ) . ')' ;
if ( $tokens -> is ( $stop )) {
2013-01-25 18:36:16 +04:00
$tokens -> next ();
2013-05-30 19:00:00 +04:00
return $_str ;
2013-01-25 18:36:16 +04:00
} else {
2013-05-30 19:00:00 +04:00
$_str .= '."' ;
2013-01-25 18:36:16 +04:00
}
2013-07-29 14:58:14 +04:00
} elseif ( $t === " } " ) {
2013-05-30 19:00:00 +04:00
$tokens -> next ();
2013-07-29 14:58:14 +04:00
} elseif ( $t === $stop ) {
2013-05-30 19:00:00 +04:00
$tokens -> next ();
2013-07-29 14:58:14 +04:00
return $_str . '"' ;
2013-05-30 19:00:00 +04:00
} else {
break ;
2013-01-25 18:36:16 +04:00
}
}
2013-05-30 19:00:00 +04:00
throw new UnexpectedTokenException ( $tokens );
2013-07-29 14:58:14 +04:00
} elseif ( $tokens -> is ( T_CONSTANT_ENCAPSED_STRING )) {
2013-05-30 19:00:00 +04:00
return $tokens -> getAndNext ();
2013-07-29 14:58:14 +04:00
} elseif ( $tokens -> is ( T_ENCAPSED_AND_WHITESPACE )) {
2013-05-30 19:00:00 +04:00
throw new UnexpectedTokenException ( $tokens );
} else {
return " " ;
2013-01-25 18:36:16 +04:00
}
}
2013-08-02 20:29:18 +04:00
/**
* @ param Tokenizer $tokens
* @ param null $first_member
*/
2013-08-02 21:50:04 +04:00
public function parseConcat ( Tokenizer $tokens , $first_member = null )
{
2013-08-02 20:29:18 +04:00
$concat = array ();
2013-08-02 21:50:04 +04:00
if ( $first_member ) {
2013-08-02 20:29:18 +04:00
}
}
2013-01-25 18:36:16 +04:00
/**
* Parse modifiers
* | modifier : 1 : 2.3 : 'string' : false : $var : ( 4 + 5 * $var3 ) | modifier2 : " str { $var + 3 } ing " : $arr . item
*
* @ param Tokenizer $tokens
* @ param $value
* @ throws \LogicException
* @ throws \Exception
* @ return string
*/
2013-07-29 14:58:14 +04:00
public function parseModifier ( Tokenizer $tokens , $value )
{
while ( $tokens -> is ( " | " )) {
2013-07-24 20:56:28 +04:00
$mods = $this -> _fenom -> getModifier ( $tokens -> getNext ( Tokenizer :: MACRO_STRING ), $this );
2013-07-29 14:58:14 +04:00
if ( ! $mods ) {
throw new \Exception ( " Modifier " . $tokens -> current () . " not found " );
2013-07-24 19:37:07 +04:00
}
2013-01-25 18:36:16 +04:00
$tokens -> next ();
$args = array ();
2013-07-29 14:58:14 +04:00
while ( $tokens -> is ( " : " )) {
2013-01-25 18:36:16 +04:00
$token = $tokens -> getNext ( Tokenizer :: MACRO_SCALAR , T_VARIABLE , '"' , Tokenizer :: MACRO_STRING , " ( " , " [ " );
2013-07-29 14:58:14 +04:00
if ( $tokens -> is ( Tokenizer :: MACRO_SCALAR ) || $tokens -> isSpecialVal ()) {
2013-01-25 18:36:16 +04:00
$args [] = $token ;
$tokens -> next ();
2013-07-29 14:58:14 +04:00
} elseif ( $tokens -> is ( T_VARIABLE )) {
2013-03-17 14:37:23 +04:00
$args [] = $this -> parseVariable ( $tokens , self :: DENY_MODS );
2013-07-29 14:58:14 +04:00
} elseif ( $tokens -> is ( '"' , '`' , T_ENCAPSED_AND_WHITESPACE )) {
2013-08-02 20:29:18 +04:00
$args [] = $this -> parseQuote ( $tokens );
2013-07-29 14:58:14 +04:00
} elseif ( $tokens -> is ( '(' )) {
2013-07-22 18:03:43 +04:00
$args [] = $this -> parseExp ( $tokens , true );
2013-07-29 14:58:14 +04:00
} elseif ( $tokens -> is ( '[' )) {
2013-01-25 18:36:16 +04:00
$args [] = $this -> parseArray ( $tokens );
2013-07-29 14:58:14 +04:00
} elseif ( $tokens -> is ( T_STRING ) && $tokens -> isNext ( '(' )) {
$args [] = $tokens -> getAndNext () . $this -> parseArgs ( $tokens );
2013-01-25 18:36:16 +04:00
} else {
break ;
}
}
2013-07-29 14:58:14 +04:00
if ( ! is_string ( $mods )) { // dynamic modifier
$mods = 'call_user_func($tpl->getStorage()->getModifier("' . $mods . '"), ' ;
2013-06-20 10:36:35 +04:00
} else {
$mods .= " ( " ;
}
2013-07-29 14:58:14 +04:00
if ( $args ) {
$value = $mods . $value . ', ' . implode ( " , " , $args ) . ')' ;
2013-01-25 18:36:16 +04:00
} else {
2013-07-29 14:58:14 +04:00
$value = $mods . $value . ')' ;
2013-01-25 18:36:16 +04:00
}
}
return $value ;
}
/**
* Parse array
* [ 1 , 2.3 , 5 + 7 / $var , 'string' , " str { $var + 3 } ing " , $var2 , []]
*
* @ param Tokenizer $tokens
2013-03-15 00:12:02 +04:00
* @ throws UnexpectedTokenException
2013-01-25 18:36:16 +04:00
* @ return string
*/
2013-07-29 14:58:14 +04:00
public function parseArray ( Tokenizer $tokens )
{
if ( $tokens -> is ( " [ " )) {
2013-01-25 18:36:16 +04:00
$_arr = " array( " ;
$key = $val = false ;
$tokens -> next ();
2013-07-29 14:58:14 +04:00
while ( $tokens -> valid ()) {
if ( $tokens -> is ( ',' ) && $val ) {
2013-01-25 18:36:16 +04:00
$key = true ;
$val = false ;
2013-07-29 14:58:14 +04:00
$_arr .= $tokens -> getAndNext () . ' ' ;
} elseif ( $tokens -> is ( Tokenizer :: MACRO_SCALAR , T_VARIABLE , T_STRING , T_EMPTY , T_ISSET , " ( " , " # " ) && ! $val ) {
2013-01-25 18:36:16 +04:00
$_arr .= $this -> parseExp ( $tokens , true );
$key = false ;
$val = true ;
2013-07-29 14:58:14 +04:00
} elseif ( $tokens -> is ( '"' ) && ! $val ) {
2013-08-02 20:29:18 +04:00
$_arr .= $this -> parseQuote ( $tokens );
2013-01-25 18:36:16 +04:00
$key = false ;
$val = true ;
2013-07-29 14:58:14 +04:00
} elseif ( $tokens -> is ( T_DOUBLE_ARROW ) && $val ) {
$_arr .= ' ' . $tokens -> getAndNext () . ' ' ;
2013-01-25 18:36:16 +04:00
$key = true ;
$val = false ;
2013-07-29 14:58:14 +04:00
} elseif ( ! $val && $tokens -> is ( '[' )) {
2013-01-25 18:36:16 +04:00
$_arr .= $this -> parseArray ( $tokens );
$key = false ;
$val = true ;
2013-07-29 14:58:14 +04:00
} elseif ( $tokens -> is ( ']' ) && ! $key ) {
2013-01-25 18:36:16 +04:00
$tokens -> next ();
2013-07-29 14:58:14 +04:00
return $_arr . ')' ;
2013-01-25 18:36:16 +04:00
} else {
break ;
}
}
}
2013-03-15 00:12:02 +04:00
throw new UnexpectedTokenException ( $tokens );
2013-01-25 18:36:16 +04:00
}
/**
2013-02-23 02:03:05 +04:00
* Parse constant
* #Ns\MyClass::CONST1, #CONST1, #MyClass::CONST1
2013-01-25 18:36:16 +04:00
*
* @ param Tokenizer $tokens
2013-02-23 02:03:05 +04:00
* @ return string
2013-07-02 11:07:33 +04:00
* @ throws InvalidUsageException
2013-01-25 18:36:16 +04:00
*/
2013-07-29 14:58:14 +04:00
public function parseConst ( Tokenizer $tokens )
{
2013-02-23 02:03:05 +04:00
$tokens -> get ( '#' );
$name = $tokens -> getNext ( T_STRING );
$tokens -> next ();
2013-07-29 14:58:14 +04:00
if ( $tokens -> is ( T_NAMESPACE )) {
2013-02-23 02:03:05 +04:00
$name .= '\\' ;
$name .= $tokens -> getNext ( T_STRING );
$tokens -> next ();
}
2013-07-29 14:58:14 +04:00
if ( $tokens -> is ( T_DOUBLE_COLON )) {
2013-02-23 02:03:05 +04:00
$name .= '::' ;
$name .= $tokens -> getNext ( T_STRING );
$tokens -> next ();
}
2013-07-29 14:58:14 +04:00
if ( defined ( $name )) {
2013-02-23 02:03:05 +04:00
return $name ;
} else {
2013-07-02 11:07:33 +04:00
throw new InvalidUsageException ( " Use undefined constant $name " );
2013-02-23 02:03:05 +04:00
}
}
/**
* @ param Tokenizer $tokens
* @ param $name
* @ return string
2013-07-02 11:07:33 +04:00
* @ throws InvalidUsageException
2013-02-23 02:03:05 +04:00
*/
2013-07-29 14:58:14 +04:00
public function parseMacroCall ( Tokenizer $tokens , $name )
{
2013-07-29 14:53:21 +04:00
$recursive = false ;
$macro = false ;
2013-07-29 14:58:14 +04:00
if ( isset ( $this -> macros [ $name ])) {
$macro = $this -> macros [ $name ];
2013-07-29 14:53:21 +04:00
} else {
2013-07-29 14:58:14 +04:00
foreach ( $this -> _stack as $scope ) {
if ( $scope -> name == 'macro' && $scope [ 'name' ] == $name ) { // invoke recursive
2013-07-29 14:53:21 +04:00
$recursive = $scope ;
$macro = $scope [ 'macro' ];
break ;
2013-02-23 02:03:05 +04:00
}
}
2013-07-29 14:58:14 +04:00
if ( ! $macro ) {
2013-07-29 14:53:21 +04:00
throw new InvalidUsageException ( " Undefined macro ' $name ' " );
}
}
$tokens -> next ();
$p = $this -> parseParams ( $tokens );
$args = array ();
2013-07-29 14:58:14 +04:00
foreach ( $macro [ 'args' ] as $arg ) {
if ( isset ( $p [ $arg ])) {
$args [ $arg ] = $p [ $arg ];
} elseif ( isset ( $macro [ 'defaults' ][ $arg ])) {
$args [ $arg ] = $macro [ 'defaults' ][ $arg ];
2013-07-29 14:53:21 +04:00
} else {
throw new InvalidUsageException ( " Macro ' $name ' require ' $arg ' argument " );
}
}
2013-07-29 14:58:14 +04:00
$args = $args ? '$tpl = ' . Compiler :: toArray ( $args ) . ';' : '' ;
if ( $recursive ) {
2013-07-29 14:53:21 +04:00
$n = $this -> i ++ ;
$recursive [ 'recursive' ][] = $n ;
2013-07-29 14:58:14 +04:00
return '$stack_' . $macro [ 'id' ] . '[] = array("tpl" => $tpl, "mark" => ' . $n . '); ' . $args . ' goto macro_' . $macro [ 'id' ] . '; macro_' . $n . ':' ;
2013-02-23 02:03:05 +04:00
} else {
2013-07-29 14:58:14 +04:00
return '$_tpl = $tpl; ' . $args . ' ?>' . $macro [ " body " ] . '<?php $tpl = $_tpl; unset($_tpl);' ;
2013-01-25 18:36:16 +04:00
}
}
/**
* Parse argument list
* ( 1 + 2.3 , 'string' , $var , [ 2 , 4 ])
*
* @ static
* @ param Tokenizer $tokens
* @ throws TokenizeException
* @ return string
*/
2013-07-29 14:58:14 +04:00
public function parseArgs ( Tokenizer $tokens )
{
2013-01-28 16:34:34 +04:00
$_args = " ( " ;
$tokens -> next ();
$arg = $colon = false ;
2013-07-29 14:58:14 +04:00
while ( $tokens -> valid ()) {
if ( ! $arg && $tokens -> is ( T_VARIABLE , T_STRING , " ( " , Tokenizer :: MACRO_SCALAR , '"' , Tokenizer :: MACRO_UNARY , Tokenizer :: MACRO_INCDEC )) {
2013-01-28 16:34:34 +04:00
$_args .= $this -> parseExp ( $tokens , true );
$arg = true ;
$colon = false ;
2013-07-29 14:58:14 +04:00
} elseif ( ! $arg && $tokens -> is ( '[' )) {
2013-01-28 16:34:34 +04:00
$_args .= $this -> parseArray ( $tokens );
$arg = true ;
$colon = false ;
2013-07-29 14:58:14 +04:00
} elseif ( $arg && $tokens -> is ( ',' )) {
$_args .= $tokens -> getAndNext () . ' ' ;
2013-01-28 16:34:34 +04:00
$arg = false ;
$colon = true ;
2013-07-29 14:58:14 +04:00
} elseif ( ! $colon && $tokens -> is ( ')' )) {
2013-01-28 16:34:34 +04:00
$tokens -> next ();
2013-07-29 14:58:14 +04:00
return $_args . ')' ;
2013-01-28 16:34:34 +04:00
} else {
break ;
}
}
2013-07-29 14:58:14 +04:00
throw new TokenizeException ( " Unexpected token ' " . $tokens -> current () . " ' in argument list " );
2013-01-28 16:34:34 +04:00
}
2013-01-25 18:36:16 +04:00
2013-02-20 18:03:53 +04:00
/**
* Parse first unnamed argument
*
* @ param Tokenizer $tokens
* @ param string $static
* @ return mixed | string
*/
2013-07-29 14:58:14 +04:00
public function parsePlainArg ( Tokenizer $tokens , & $static )
{
if ( $tokens -> is ( T_CONSTANT_ENCAPSED_STRING )) {
if ( $tokens -> isNext ( '|' )) {
2013-02-20 18:03:53 +04:00
return $this -> parseExp ( $tokens , true );
} else {
$str = $tokens -> getAndNext ();
$static = stripslashes ( substr ( $str , 1 , - 1 ));
return $str ;
}
2013-07-29 14:58:14 +04:00
} elseif ( $tokens -> is ( Tokenizer :: MACRO_STRING )) {
2013-02-20 18:03:53 +04:00
$static = $tokens -> getAndNext ();
2013-07-29 14:58:14 +04:00
return '"' . addslashes ( $static ) . '"' ;
2013-02-13 20:51:27 +04:00
} else {
return $this -> parseExp ( $tokens , true );
}
}
2013-07-29 14:53:21 +04:00
2013-01-25 18:36:16 +04:00
/**
* Parse parameters as $key = $value
* param1 = $var param2 = 3 ...
*
* @ static
* @ param Tokenizer $tokens
2013-07-29 14:58:14 +04:00
* @ param array $defaults
2013-01-25 18:36:16 +04:00
* @ throws \Exception
* @ return array
*/
2013-07-29 14:58:14 +04:00
public function parseParams ( Tokenizer $tokens , array $defaults = null )
{
2013-01-28 16:34:34 +04:00
$params = array ();
2013-07-29 14:58:14 +04:00
while ( $tokens -> valid ()) {
if ( $tokens -> is ( Tokenizer :: MACRO_STRING )) {
2013-01-25 18:36:16 +04:00
$key = $tokens -> getAndNext ();
2013-07-29 14:58:14 +04:00
if ( $defaults && ! isset ( $defaults [ $key ])) {
2013-01-25 18:36:16 +04:00
throw new \Exception ( " Unknown parameter ' $key ' " );
}
2013-07-29 14:58:14 +04:00
if ( $tokens -> is ( " = " )) {
2013-01-25 18:36:16 +04:00
$tokens -> next ();
2013-07-29 14:58:14 +04:00
$params [ $key ] = $this -> parseExp ( $tokens );
2013-01-25 18:36:16 +04:00
} else {
2013-07-29 14:58:14 +04:00
$params [ $key ] = 'true' ;
2013-01-25 18:36:16 +04:00
}
2013-07-29 14:58:14 +04:00
} elseif ( $tokens -> is ( Tokenizer :: MACRO_SCALAR , '"' , '`' , T_VARIABLE , " [ " , '(' )) {
2013-01-25 18:36:16 +04:00
$params [] = $this -> parseExp ( $tokens );
} else {
break ;
}
}
2013-07-29 14:58:14 +04:00
if ( $defaults ) {
2013-01-25 18:36:16 +04:00
$params += $defaults ;
}
return $params ;
2013-01-28 16:34:34 +04:00
}
2013-07-29 14:58:14 +04:00
}