2013-01-25 18:36:16 +04:00
< ? php
2013-02-21 22:51:24 +04:00
/*
2013-04-04 10:56:44 +04:00
* This file is part of Cytro .
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-04-04 10:56:44 +04:00
namespace Cytro ;
use Cytro ;
2013-01-25 18:36:16 +04:00
/**
2013-02-21 22:51:24 +04:00
* Template compiler
*
* @ package aspect
* @ author Ivan Shalganov < owner @ bzick . net >
2013-01-25 18:36:16 +04:00
*/
class Template extends Render {
const DENY_ARRAY = 1 ;
const DENY_MODS = 2 ;
2013-03-17 14:37:23 +04:00
const EXTENDED = 0x1000 ;
2013-01-25 18:36:16 +04:00
/**
* @ var int shared counter
*/
public $i = 1 ;
/**
* Template PHP code
* @ var string
*/
2013-02-07 17:37:16 +04:00
public $_body ;
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-01-25 18:36:16 +04:00
/**
* Call stack
* @ var Scope []
*/
private $_stack = array ();
/**
* Template source
* @ var string
*/
private $_src ;
/**
* @ var int
*/
private $_pos = 0 ;
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-02-23 02:03:05 +04:00
/**
* Just factory
*
2013-04-28 18:08:57 +04:00
* @ param \Cytro $cytro
2013-04-28 11:33:36 +04:00
* @ param $options
2013-02-23 02:03:05 +04:00
* @ return Template
2013-01-25 18:36:16 +04:00
*/
2013-04-28 18:08:57 +04:00
public static function factory ( Cytro $cytro , $options ) {
return new static ( $cytro , $options );
2013-02-13 20:51:27 +04:00
}
2013-01-25 18:36:16 +04:00
/**
2013-04-28 11:33:36 +04:00
* @ param Cytro $cytro Template storage
2013-04-28 18:08:57 +04:00
* @ param int $options
* @ return \Cytro\Template
2013-02-13 20:51:27 +04:00
*/
2013-04-28 11:33:36 +04:00
public function __construct ( Cytro $cytro , $options ) {
$this -> _cytro = $cytro ;
$this -> _options = $this -> _cytro -> getOptions ();
2013-02-13 20:51:27 +04:00
}
/**
* Load source from provider
* @ param string $name
* @ param bool $compile
2013-04-28 18:08:57 +04:00
* @ return $this
2013-02-13 20:51:27 +04:00
*/
public function load ( $name , $compile = true ) {
$this -> _name = $name ;
if ( $provider = strstr ( $name , " : " , true )) {
$this -> _scm = $provider ;
$this -> _base_name = substr ( $name , strlen ( $provider ));
} else {
$this -> _base_name = $name ;
}
2013-04-28 11:33:36 +04:00
$this -> _provider = $this -> _cytro -> getProvider ( $provider );
2013-02-13 20:51:27 +04:00
$this -> _src = $this -> _provider -> getSource ( $name , $this -> _time );
if ( $compile ) {
$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-04-04 10:56:44 +04:00
* @ return \Cytro\Template
2013-01-25 18:36:16 +04:00
*/
2013-02-13 20:51:27 +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 ;
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-02-07 17:37:16 +04:00
public function compile () {
if ( ! isset ( $this -> _src )) {
return ;
}
2013-01-25 18:36:16 +04:00
$pos = 0 ;
2013-02-26 23:56:06 +04:00
$frag = " " ;
2013-02-07 17:37:16 +04:00
while (( $start = strpos ( $this -> _src , '{' , $pos )) !== false ) { // search open-char of tags
switch ( $this -> _src [ $start + 1 ]) { // check next char
2013-01-25 18:36:16 +04:00
case " \n " : case " \r " : case " \t " : case " " : case " } " : // ignore the tag
2013-03-14 21:45:00 +04:00
$pos = $start + 1 ; // try find tags after the current char
continue 2 ;
2013-01-25 18:36:16 +04:00
case " * " : // if comment block
2013-02-07 17:37:16 +04:00
$end = strpos ( $this -> _src , '*}' , $start ); // finding end of the comment block
2013-02-26 23:56:06 +04:00
$_frag = substr ( $this -> _src , $this -> _pos , $start - $end ); // read the comment block for precessing
$this -> _line += substr_count ( $_frag , " \n " ); // count skipped lines
2013-01-28 16:34:34 +04:00
$pos = $end + 1 ; // trying finding tags after the comment block
2013-01-25 18:36:16 +04:00
continue 2 ;
}
2013-04-28 11:33:36 +04:00
$end = strpos ( $this -> _src , '}' , $start ); // search close-symbol of the tag
2013-01-25 18:36:16 +04:00
if ( ! $end ) { // if unexpected end of template
2013-01-28 16:34:34 +04:00
throw new CompileException ( " Unclosed tag in line { $this -> _line } " , 0 , 1 , $this -> _name , $this -> _line );
2013-01-25 18:36:16 +04:00
}
2013-04-28 11:33:36 +04:00
$frag .= substr ( $this -> _src , $this -> _pos , $start - $this -> _pos ); // variable $frag contains chars after previous '}' and current '{'
$tag = substr ( $this -> _src , $start , $end - $start + 1 ); // variable $tag contains cytro tag '{...}'
2013-02-07 17:37:16 +04:00
$this -> _line += substr_count ( $this -> _src , " \n " , $this -> _pos , $end - $start + 1 ); // count lines in $frag and $tag (using original text $code)
$pos = $this -> _pos = $end + 1 ; // move search-pointer to end of the tag
2013-02-27 20:55:08 +04:00
if ( $tag [ strlen ( $tag ) - 2 ] === " - " ) { // check right trim flag
2013-02-26 23:56:06 +04:00
$_tag = substr ( $tag , 1 , - 2 );
$_frag = rtrim ( $frag );
} else {
$_tag = substr ( $tag , 1 , - 1 );
$_frag = $frag ;
}
2013-03-14 21:45:00 +04:00
if ( $this -> _ignore ) { // check ignore
2013-02-26 23:56:06 +04:00
if ( $_tag === '/ignore' ) {
$this -> _ignore = false ;
$this -> _appendText ( $_frag );
2013-02-27 20:55:08 +04:00
} else { // still ignore
2013-02-26 23:56:06 +04:00
$frag .= $tag ;
continue ;
2013-02-15 01:49:26 +04:00
}
2013-02-26 23:56:06 +04:00
} else {
$this -> _appendText ( $_frag );
2013-03-14 21:45:00 +04:00
$tokens = new Tokenizer ( $_tag );
$this -> _appendCode ( $this -> _tag ( $tokens ), $tag );
if ( $tokens -> key ()) { // if tokenizer still have tokens
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-02-20 18:03:53 +04:00
}
2013-02-26 23:56:06 +04:00
$frag = " " ;
2013-01-25 18:36:16 +04:00
}
2013-02-26 23:56:06 +04:00
$this -> _appendText ( substr ( $this -> _src , $this -> _pos ));
2013-01-25 18:36:16 +04:00
if ( $this -> _stack ) {
$_names = array ();
$_line = 0 ;
foreach ( $this -> _stack as $scope ) {
if ( ! $_line ) {
$_line = $scope -> line ;
}
2013-03-17 14:37:23 +04:00
$_names [] = '{' . $scope -> name . '} defined on line ' . $scope -> line ;
2013-01-25 18:36:16 +04:00
}
2013-03-17 14:37:23 +04:00
throw new CompileException ( " Unclosed tag(s): " . implode ( " , " , $_names ), 0 , 1 , $this -> _name , $_line );
2013-01-25 18:36:16 +04:00
}
unset ( $this -> _src );
if ( $this -> _post ) {
foreach ( $this -> _post as $cb ) {
call_user_func_array ( $cb , array ( & $this -> _body , $this ));
}
}
2013-01-28 16:34:34 +04:00
}
2013-01-25 18:36:16 +04:00
2013-03-17 14:37:23 +04:00
/**
* Generate temporary internal template variable
* @ return string
*/
2013-03-15 00:57:28 +04:00
public function tmpVar () {
return '$t' . ( $this -> i ++ );
}
2013-02-26 23:56:06 +04:00
/**
* Append plain text to template body
*
* @ param string $text
*/
private function _appendText ( $text ) {
2013-04-28 18:08:57 +04:00
if ( $this -> _filter ) {
if ( strpos ( $text , " <? " ) === false ) {
foreach ( explode ( " <? " , $text ) as $fragment ) {
}
} else {
$this -> _body .= $text ;
}
} else {
$this -> _body .= str_replace ( " <? " , '<?php echo "<?"; ?>' . PHP_EOL , $text );
}
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-03-14 21:45:00 +04:00
private function _escapeCode ( $code ) {
2013-02-27 20:55:08 +04:00
$c = " " ;
foreach ( token_get_all ( $code ) as $token ) {
if ( is_string ( $token )) {
$c .= $token ;
} elseif ( $token [ 0 ] == T_CLOSE_TAG ) {
$c .= $token [ 1 ] . PHP_EOL ;
} 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-03-14 21:45:00 +04:00
private function _appendCode ( $code , $source ) {
2013-02-27 20:55:08 +04:00
if ( ! $code ) {
return ;
} else {
2013-03-14 21:45:00 +04:00
if ( strpos ( $code , '?>' ) !== false ) {
$code = $this -> _escapeCode ( $code ); // paste PHP_EOL
}
$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-02-27 20:55:08 +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
*/
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
*/
public function getTemplateCode () {
return " <?php \n " .
2013-04-04 10:56:44 +04:00
" /** Cytro template ' " . $this -> _name . " ' compiled at " . date ( 'Y-m-d H:i:s' ) . " */ \n " .
2013-04-28 18:08:57 +04:00
" return new Cytro \\ Render( \$ cytro, " . $this -> _getClosureSource () . " , " . var_export ( array (
2013-03-14 21:45:00 +04:00
" options " => $this -> _options ,
2013-02-23 16:35:11 +04:00
" 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-02-13 20:51:27 +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
*/
public function display ( array $values ) {
if ( ! $this -> _code ) {
// evaluate template's code
2013-02-13 20:51:27 +04:00
eval ( " \$ this->_code = " . $this -> _getClosureSource () . " ; " );
2013-01-25 18:36:16 +04:00
if ( ! $this -> _code ) {
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
*/
public function addDepend ( Render $tpl ) {
$this -> _depends [ $tpl -> getScm ()][ $tpl -> getName ()] = $tpl -> getTime ();
}
2013-02-07 17:37:16 +04:00
2013-01-25 18:36:16 +04:00
/**
* Execute template and return result as string
* @ param array $values for template
* @ throws CompileException
* @ return string
*/
public function fetch ( array $values ) {
if ( ! $this -> _code ) {
2013-02-13 20:51:27 +04:00
eval ( " \$ this->_code = " . $this -> _getClosureSource () . " ; " );
2013-01-25 18:36:16 +04:00
if ( ! $this -> _code ) {
throw new CompileException ( " Fatal error while creating the template " );
}
}
return parent :: fetch ( $values );
}
/**
* Internal tags router
2013-03-14 21:45:00 +04:00
* @ param Tokenizer $tokens
2013-03-15 00:12:02 +04:00
* @ throws UnexpectedTokenException
2013-01-25 18:36:16 +04:00
* @ throws CompileException
* @ throws SecurityException
2013-02-26 23:56:06 +04:00
* @ return string executable PHP code
2013-01-25 18:36:16 +04:00
*/
2013-03-14 21:45:00 +04:00
private function _tag ( Tokenizer $tokens ) {
2013-01-28 16:34:34 +04:00
try {
2013-03-14 21:45:00 +04:00
if ( $tokens -> is ( Tokenizer :: MACRO_STRING )) {
if ( $tokens -> current () === " ignore " ) {
$this -> _ignore = true ;
$tokens -> next ();
return '' ;
} else {
return $this -> _parseAct ( $tokens );
}
} elseif ( $tokens -> is ( '/' )) {
return $this -> _end ( $tokens );
} elseif ( $tokens -> is ( '#' )) {
return " echo " . $this -> parseConst ( $tokens ) . ';' ;
2013-02-23 16:35:11 +04:00
} else {
2013-03-14 21:45:00 +04:00
return $code = " echo " . $this -> parseExp ( $tokens ) . " ; " ;
2013-01-25 18:36:16 +04:00
}
2013-02-07 17:37:16 +04:00
} catch ( ImproperUseException $e ) {
2013-02-23 16:35:11 +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-02-07 17:37:16 +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-02-07 17:37:16 +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
* @ return mixed
* @ throws TokenizeException
*/
private function _end ( Tokenizer $tokens ) {
2013-03-14 21:45:00 +04:00
//return "end";
2013-01-28 16:34:34 +04:00
$name = $tokens -> getNext ( Tokenizer :: MACRO_STRING );
$tokens -> next ();
if ( ! $this -> _stack ) {
throw new TokenizeException ( " Unexpected closing of the tag ' $name ', the tag hasn't been opened " );
}
/** @var Scope $scope */
$scope = array_pop ( $this -> _stack );
if ( $scope -> name !== $name ) {
throw new TokenizeException ( " Unexpected closing of the tag ' $name ' (expecting closing of the tag { $scope -> name } , opened on line { $scope -> line } ) " );
}
return $scope -> close ( $tokens );
}
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-01-28 16:34:34 +04:00
private function _parseAct ( Tokenizer $tokens ) {
2013-01-25 18:36:16 +04:00
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-03-14 21:45:00 +04:00
return 'echo ' . $this -> parseExp ( $tokens ) . ';' ; // may be math and/or boolean expression
2013-01-25 18:36:16 +04:00
}
2013-02-23 02:03:05 +04:00
if ( $tokens -> is ( " ( " , T_NAMESPACE , T_DOUBLE_COLON )) { // just invoke function or static method
$tokens -> back ();
2013-01-25 18:36:16 +04:00
return " echo " . $this -> parseExp ( $tokens ) . " ; " ;
2013-02-23 02:03:05 +04:00
} elseif ( $tokens -> is ( '.' )) {
$name = $tokens -> skip () -> get ( Tokenizer :: MACRO_STRING );
if ( $action !== " macro " ) {
$name = $action . " . " . $name ;
}
return $this -> parseMacro ( $tokens , $name );
2013-01-25 18:36:16 +04:00
}
2013-04-28 11:33:36 +04:00
if ( $act = $this -> _cytro -> getFunction ( $action )) { // call some function
2013-01-25 18:36:16 +04:00
switch ( $act [ " type " ]) {
2013-04-04 10:56:44 +04:00
case Cytro :: BLOCK_COMPILER :
2013-02-27 20:55:08 +04:00
$scope = new Scope ( $action , $this , $this -> _line , $act , count ( $this -> _stack ), $this -> _body );
2013-03-17 14:37:23 +04:00
$code = $scope -> open ( $tokens );
if ( ! $scope -> is_closed ) {
array_push ( $this -> _stack , $scope );
}
return $code ;
2013-04-04 10:56:44 +04:00
case Cytro :: INLINE_COMPILER :
2013-01-25 18:36:16 +04:00
return call_user_func ( $act [ " parser " ], $tokens , $this );
2013-04-04 10:56:44 +04:00
case Cytro :: INLINE_FUNCTION :
2013-01-25 18:36:16 +04:00
return call_user_func ( $act [ " parser " ], $act [ " function " ], $tokens , $this );
2013-04-04 10:56:44 +04:00
case Cytro :: BLOCK_FUNCTION :
2013-02-27 20:55:08 +04:00
$scope = new Scope ( $action , $this , $this -> _line , $act , count ( $this -> _stack ), $this -> _body );
2013-01-25 18:36:16 +04:00
$scope -> setFuncName ( $act [ " function " ]);
array_push ( $this -> _stack , $scope );
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-02-21 22:51:24 +04:00
for ( $j = $i = count ( $this -> _stack ) - 1 ; $i >= 0 ; $i -- ) { // call function's internal tag
2013-01-28 16:34:34 +04:00
if ( $this -> _stack [ $i ] -> hasTag ( $action , $j - $i )) {
return $this -> _stack [ $i ] -> tag ( $action , $tokens );
}
}
2013-04-28 11:33:36 +04:00
if ( $tags = $this -> _cytro -> getTagOwners ( $action )) { // unknown template tag
2013-01-25 18:36:16 +04:00
throw new TokenizeException ( " Unexpected tag ' $action ' (this tag can be used with ' " . implode ( " ', ' " , $tags ) . " ') " );
} else {
throw new TokenizeException ( " Unexpected tag $action " );
}
2013-01-28 16:34:34 +04:00
}
2013-01-25 18:36:16 +04:00
/**
* Parse expressions . The mix of math operations , boolean operations , scalars , arrays and variables .
*
* @ static
* @ param Tokenizer $tokens
* @ param bool $required
* @ throws \LogicException
2013-03-15 00:12:02 +04:00
* @ throws UnexpectedTokenException
2013-01-25 18:36:16 +04:00
* @ throws TokenizeException
* @ return string
*/
2013-01-28 16:34:34 +04:00
public function parseExp ( Tokenizer $tokens , $required = false ) {
$_exp = " " ;
2013-01-25 18:36:16 +04:00
$brackets = 0 ;
2013-01-28 16:34:34 +04:00
$term = false ;
$cond = false ;
2013-01-25 18:36:16 +04:00
while ( $tokens -> valid ()) {
if ( ! $term && $tokens -> is ( Tokenizer :: MACRO_SCALAR , '"' , '`' , T_ENCAPSED_AND_WHITESPACE )) {
$_exp .= $this -> parseScalar ( $tokens , true );
2013-01-28 16:34:34 +04:00
$term = 1 ;
2013-01-25 18:36:16 +04:00
} elseif ( ! $term && $tokens -> is ( T_VARIABLE )) {
2013-03-17 14:37:23 +04:00
2013-01-25 18:36:16 +04:00
$pp = $tokens -> isPrev ( Tokenizer :: MACRO_INCDEC );
2013-03-17 14:37:23 +04:00
$_exp .= $this -> parseVariable ( $tokens , 0 , $only_var );
2013-01-25 18:36:16 +04:00
if ( $only_var && ! $pp ) {
2013-01-28 16:34:34 +04:00
$term = 2 ;
2013-01-25 18:36:16 +04:00
} else {
$term = 1 ;
}
2013-02-23 02:03:05 +04:00
} elseif ( ! $term && $tokens -> is ( '#' )) {
$term = 1 ;
$_exp .= $this -> parseConst ( $tokens );
2013-01-25 18:36:16 +04:00
} elseif ( ! $term && $tokens -> is ( " ( " )) {
$_exp .= $tokens -> getAndNext ();
$brackets ++ ;
$term = false ;
} elseif ( $term && $tokens -> is ( " ) " )) {
2013-01-28 16:34:34 +04:00
if ( ! $brackets ) {
break ;
}
$brackets -- ;
$_exp .= $tokens -> getAndNext ();
$term = 1 ;
2013-01-25 18:36:16 +04:00
} elseif ( ! $term && $tokens -> is ( T_STRING )) {
2013-01-28 16:34:34 +04:00
if ( $tokens -> isSpecialVal ()) {
$_exp .= $tokens -> getAndNext ();
} elseif ( $tokens -> isNext ( " ( " )) {
2013-04-28 11:33:36 +04:00
$func = $this -> _cytro -> getModifier ( $tokens -> current ());
2013-01-25 18:36:16 +04:00
$tokens -> next ();
2013-01-28 16:34:34 +04:00
$_exp .= $func . $this -> parseArgs ( $tokens );
} else {
break ;
}
$term = 1 ;
2013-01-25 18:36:16 +04:00
} elseif ( ! $term && $tokens -> is ( T_ISSET , T_EMPTY )) {
$_exp .= $tokens -> getAndNext ();
if ( $tokens -> is ( " ( " ) && $tokens -> isNext ( T_VARIABLE )) {
$_exp .= $this -> parseArgs ( $tokens );
} else {
throw new TokenizeException ( " Unexpected token " . $tokens -> getNext () . " , isset() and empty() accept only variables " );
}
$term = 1 ;
} elseif ( ! $term && $tokens -> is ( Tokenizer :: MACRO_UNARY )) {
2013-01-28 16:34:34 +04:00
if ( ! $tokens -> isNext ( T_VARIABLE , T_DNUMBER , T_LNUMBER , T_STRING , T_ISSET , T_EMPTY )) {
break ;
}
$_exp .= $tokens -> getAndNext ();
$term = 0 ;
2013-01-25 18:36:16 +04:00
} elseif ( $tokens -> is ( Tokenizer :: MACRO_BINARY )) {
if ( ! $term ) {
2013-03-15 00:12:02 +04:00
throw new UnexpectedTokenException ( $tokens );
2013-01-25 18:36:16 +04:00
}
2013-01-28 16:34:34 +04:00
if ( $tokens -> isLast ()) {
break ;
}
if ( $tokens -> is ( Tokenizer :: MACRO_COND )) {
if ( $cond ) {
break ;
}
$cond = true ;
} elseif ( $tokens -> is ( Tokenizer :: MACRO_BOOLEAN )) {
$cond = false ;
}
$_exp .= " " . $tokens -> getAndNext () . " " ;
$term = 0 ;
2013-01-25 18:36:16 +04:00
} elseif ( $tokens -> is ( Tokenizer :: MACRO_INCDEC )) {
if ( $term === 2 ) {
$term = 1 ;
} elseif ( ! $tokens -> isNext ( T_VARIABLE )) {
break ;
}
$_exp .= $tokens -> getAndNext ();
} elseif ( $term && ! $cond && ! $tokens -> isLast ()) {
2013-01-28 16:34:34 +04:00
if ( $tokens -> is ( Tokenizer :: MACRO_EQUALS ) && $term === 2 ) {
$_exp .= ' ' . $tokens -> getAndNext () . ' ' ;
$term = 0 ;
} else {
break ;
}
2013-01-25 18:36:16 +04:00
} else {
2013-01-28 16:34:34 +04:00
break ;
2013-01-25 18:36:16 +04:00
}
}
if ( $term === 0 ) {
2013-03-15 00:12:02 +04:00
throw new UnexpectedTokenException ( $tokens );
2013-01-25 18:36:16 +04:00
}
if ( $brackets ) {
throw new TokenizeException ( " Brackets don't match " );
}
if ( $required && $_exp === " " ) {
2013-03-15 00:12:02 +04:00
throw new UnexpectedTokenException ( $tokens );
2013-01-25 18:36:16 +04:00
}
2013-01-28 16:34:34 +04:00
return $_exp ;
}
2013-01-25 18:36:16 +04:00
2013-03-17 14:37:23 +04:00
public function parseVar ( Tokenizer $tokens , $options = 0 ) {
2013-01-25 18:36:16 +04:00
$var = $tokens -> get ( T_VARIABLE );
2013-03-17 14:37:23 +04:00
$_var = '$tpl["' . substr ( $var , 1 ) . '"]' ;
2013-01-25 18:36:16 +04:00
$tokens -> next ();
while ( $t = $tokens -> key ()) {
2013-03-17 14:37:23 +04:00
if ( $t === " . " && ! ( $options & self :: DENY_ARRAY )) {
2013-01-25 18:36:16 +04:00
$key = $tokens -> getNext ();
if ( $tokens -> is ( T_VARIABLE )) {
2013-03-17 14:37:23 +04:00
$key = " [ " . $this -> parseVariable ( $tokens , self :: DENY_ARRAY ) . " ] " ;
2013-01-25 18:36:16 +04:00
} elseif ( $tokens -> is ( Tokenizer :: MACRO_STRING )) {
if ( $tokens -> isNext ( " ( " )) {
$key = " [ " . $this -> parseExp ( $tokens ) . " ] " ;
} else {
$key = '["' . $key . '"]' ;
$tokens -> next ();
}
} elseif ( $tokens -> is ( Tokenizer :: MACRO_SCALAR , '"' )) {
$key = " [ " . $this -> parseScalar ( $tokens , false ) . " ] " ;
} else {
break ;
}
$_var .= $key ;
2013-03-17 14:37:23 +04:00
} elseif ( $t === " [ " && ! ( $options & self :: DENY_ARRAY )) {
2013-01-25 18:36:16 +04:00
$tokens -> next ();
if ( $tokens -> is ( Tokenizer :: MACRO_STRING )) {
if ( $tokens -> isNext ( " ( " )) {
$key = " [ " . $this -> parseExp ( $tokens ) . " ] " ;
} else {
$key = '["' . $tokens -> current () . '"]' ;
$tokens -> next ();
}
} else {
$key = " [ " . $this -> parseExp ( $tokens , true ) . " ] " ;
}
$tokens -> get ( " ] " );
$tokens -> next ();
$_var .= $key ;
2013-03-17 14:37:23 +04:00
} elseif ( $t === T_DNUMBER ) {
$_var .= '[' . substr ( $tokens -> getAndNext (), 1 ) . ']' ;
} else {
break ;
}
}
return $_var ;
}
/**
* Parse variable
* $var . foo [ bar ][ " a " ][ 1 + 3 / $var ] | mod : 3 : " w " : $var3 | mod3
*
* @ see parseModifier
* @ static
* @ param Tokenizer $tokens
* @ param int $deny set limitations
* @ param bool $pure_var will be FALSE if variable modified
* @ throws \LogicException
* @ throws UnexpectedTokenException
* @ return string
*/
public function parseVariable ( Tokenizer $tokens , $deny = 0 , & $pure_var = true ) {
$_var = $this -> parseVar ( $tokens , $deny );
$pure_var = true ;
while ( $t = $tokens -> key ()) {
if ( $t === " | " && ! ( $deny & self :: DENY_MODS )) {
2013-01-25 18:36:16 +04:00
$pure_var = false ;
return $this -> parseModifier ( $tokens , $_var );
} elseif ( $t === T_OBJECT_OPERATOR ) {
$prop = $tokens -> getNext ( T_STRING );
if ( $tokens -> isNext ( " ( " )) {
2013-04-04 10:56:44 +04:00
if ( $this -> _options & Cytro :: DENY_METHODS ) {
2013-01-25 18:36:16 +04:00
throw new \LogicException ( " Forbidden to call methods " );
}
$pure_var = false ;
$tokens -> next ();
$_var .= '->' . $prop . $this -> parseArgs ( $tokens );
} else {
$tokens -> next ();
$_var .= '->' . $prop ;
}
2013-02-07 17:37:16 +04:00
} elseif ( $t === " ? " || $t === " ! " ) {
2013-01-25 18:36:16 +04:00
$pure_var = false ;
2013-03-17 14:37:23 +04:00
return $this -> parseTernary ( $tokens , $_var , $t );
2013-01-25 18:36:16 +04:00
} else {
break ;
}
}
2013-01-28 16:34:34 +04:00
return $_var ;
}
2013-01-25 18:36:16 +04:00
2013-03-17 14:37:23 +04:00
public function parseTernary ( Tokenizer $tokens , $var , $type ) {
$empty = ( $type === " ? " );
$tokens -> next ();
if ( $tokens -> is ( " : " )) {
$tokens -> next ();
if ( $empty ) {
return '(empty(' . $var . ') ? (' . $this -> parseExp ( $tokens , true ) . ') : ' . $var . ')' ;
} else {
return '(isset(' . $var . ') ? ' . $var . ' : (' . $this -> parseExp ( $tokens , true ) . '))' ;
}
} elseif ( $tokens -> is ( Tokenizer :: MACRO_BINARY , Tokenizer :: MACRO_BOOLEAN , Tokenizer :: MACRO_MATH ) || ! $tokens -> valid ()) {
if ( $empty ) {
return '!empty(' . $var . ')' ;
} else {
return 'isset(' . $var . ')' ;
}
} else {
$expr1 = $this -> parseExp ( $tokens , true );
if ( ! $tokens -> is ( " : " )) {
throw new UnexpectedTokenException ( $tokens , null , " ternary operator " );
}
$expr2 = $this -> parseExp ( $tokens , true );
if ( $empty ) {
return '(empty(' . $var . ') ? ' . $expr2 . ' : ' . $expr1 . ')' ;
} else {
return '(isset(' . $var . ') ? ' . $expr1 . ' : ' . $expr2 . ')' ;
}
}
}
2013-01-25 18:36:16 +04:00
/**
* Parse scalar values
*
* @ param Tokenizer $tokens
* @ param bool $allow_mods
* @ return string
* @ throws TokenizeException
*/
public function parseScalar ( Tokenizer $tokens , $allow_mods = true ) {
$_scalar = " " ;
if ( $token = $tokens -> key ()) {
switch ( $token ) {
case T_CONSTANT_ENCAPSED_STRING :
case T_LNUMBER :
case T_DNUMBER :
$_scalar .= $tokens -> getAndNext ();
break ;
case T_ENCAPSED_AND_WHITESPACE :
case '"' :
$_scalar .= $this -> parseSubstr ( $tokens );
break ;
default :
throw new TokenizeException ( " Unexpected scalar token ' " . $tokens -> current () . " ' " );
}
if ( $allow_mods && $tokens -> is ( " | " )) {
return $this -> parseModifier ( $tokens , $_scalar );
}
}
return $_scalar ;
}
/**
* Parse string with or without variable
*
* @ param Tokenizer $tokens
2013-03-15 00:12:02 +04:00
* @ throws UnexpectedTokenException
2013-01-25 18:36:16 +04:00
* @ return string
*/
public function parseSubstr ( Tokenizer $tokens ) {
ref : {
if ( $tokens -> is ( '"' , " ` " )) {
$p = $tokens -> p ;
$stop = $tokens -> current ();
$_str = '"' ;
$tokens -> next ();
while ( $t = $tokens -> key ()) {
if ( $t === T_ENCAPSED_AND_WHITESPACE ) {
$_str .= $tokens -> current ();
$tokens -> next ();
} elseif ( $t === T_VARIABLE ) {
2013-02-21 22:51:24 +04:00
if ( strlen ( $_str ) > 1 ) {
$_str .= '".' ;
} else {
$_str = " " ;
}
$_str .= '$tpl["' . substr ( $tokens -> current (), 1 ) . '"]' ;
2013-01-25 18:36:16 +04:00
$tokens -> next ();
2013-02-21 22:51:24 +04:00
if ( $tokens -> is ( $stop )) {
$tokens -> skip ();
return $_str ;
} else {
$_str .= '."' ;
}
2013-01-25 18:36:16 +04:00
} elseif ( $t === T_CURLY_OPEN ) {
2013-02-21 22:51:24 +04:00
if ( strlen ( $_str ) > 1 ) {
$_str .= '".' ;
} else {
$_str = " " ;
}
2013-01-25 18:36:16 +04:00
$tokens -> getNext ( T_VARIABLE );
2013-02-21 22:51:24 +04:00
$_str .= '(' . $this -> parseExp ( $tokens ) . ')' ;
/* if ( ! $tokens -> valid ()) {
$more = $this -> _getMoreSubstr ( $stop );
//var_dump($more); exit;
$tokens -> append ( " } " . $more , $p );
var_dump ( " Curly " , $more , $tokens -> getSnippetAsString ());
exit ;
} */
//$tokens->skip('}');
if ( $tokens -> is ( $stop )) {
$tokens -> next ();
return $_str ;
} else {
$_str .= '."' ;
}
2013-01-25 18:36:16 +04:00
} elseif ( $t === " } " ) {
$tokens -> next ();
} elseif ( $t === $stop ) {
$tokens -> next ();
return $_str . '"' ;
} else {
break ;
}
}
if ( $more = $this -> _getMoreSubstr ( $stop )) {
$tokens -> append ( " } " . $more , $p );
goto ref ;
}
2013-03-15 00:12:02 +04:00
throw new UnexpectedTokenException ( $tokens );
2013-01-25 18:36:16 +04:00
} elseif ( $tokens -> is ( T_CONSTANT_ENCAPSED_STRING )) {
return $tokens -> getAndNext ();
} elseif ( $tokens -> is ( T_ENCAPSED_AND_WHITESPACE )) {
$p = $tokens -> p ;
if ( $more = $this -> _getMoreSubstr ( $tokens -> curr [ 1 ][ 0 ])) {
$tokens -> append ( " } " . $more , $p );
goto ref ;
}
2013-03-15 00:12:02 +04:00
throw new UnexpectedTokenException ( $tokens );
2013-01-25 18:36:16 +04:00
} else {
return " " ;
}
}
}
2013-02-20 18:03:53 +04:00
/**
* @ param string $after
* @ return bool | string
*/
2013-01-25 18:36:16 +04:00
private function _getMoreSubstr ( $after ) {
$end = strpos ( $this -> _src , $after , $this -> _pos );
$end = strpos ( $this -> _src , " } " , $end );
if ( ! $end ) {
return false ;
}
$fragment = substr ( $this -> _src , $this -> _pos , $end - $this -> _pos );
$this -> _pos = $end + 1 ;
return $fragment ;
}
/**
* 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
*/
public function parseModifier ( Tokenizer $tokens , $value ) {
while ( $tokens -> is ( " | " )) {
2013-04-28 11:33:36 +04:00
$mods = $this -> _cytro -> getModifier ( $tokens -> getNext ( Tokenizer :: MACRO_STRING ) );
2013-01-25 18:36:16 +04:00
$tokens -> next ();
$args = array ();
while ( $tokens -> is ( " : " )) {
$token = $tokens -> getNext ( Tokenizer :: MACRO_SCALAR , T_VARIABLE , '"' , Tokenizer :: MACRO_STRING , " ( " , " [ " );
if ( $tokens -> is ( Tokenizer :: MACRO_SCALAR ) || $tokens -> isSpecialVal ()) {
$args [] = $token ;
$tokens -> next ();
} elseif ( $tokens -> is ( T_VARIABLE )) {
2013-03-17 14:37:23 +04:00
$args [] = $this -> parseVariable ( $tokens , self :: DENY_MODS );
2013-01-25 18:36:16 +04:00
} elseif ( $tokens -> is ( '"' , '`' , T_ENCAPSED_AND_WHITESPACE )) {
$args [] = $this -> parseSubstr ( $tokens );
} elseif ( $tokens -> is ( '(' )) {
$args [] = $this -> parseExp ( $tokens );
} elseif ( $tokens -> is ( '[' )) {
$args [] = $this -> parseArray ( $tokens );
} elseif ( $tokens -> is ( T_STRING ) && $tokens -> isNext ( '(' )) {
$args [] = $tokens -> getAndNext () . $this -> parseArgs ( $tokens );
} else {
break ;
}
}
if ( $args ) {
$value = $mods . '(' . $value . ', ' . implode ( " , " , $args ) . ')' ;
} else {
$value = $mods . '(' . $value . ')' ;
}
}
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
*/
public function parseArray ( Tokenizer $tokens ) {
if ( $tokens -> is ( " [ " )) {
$_arr = " array( " ;
$key = $val = false ;
$tokens -> next ();
while ( $tokens -> valid ()) {
if ( $tokens -> is ( ',' ) && $val ) {
$key = true ;
$val = false ;
$_arr .= $tokens -> getAndNext () . ' ' ;
2013-02-23 02:03:05 +04:00
} 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 ;
} elseif ( $tokens -> is ( '"' ) && ! $val ) {
$_arr .= $this -> parseSubstr ( $tokens );
$key = false ;
$val = true ;
} elseif ( $tokens -> is ( T_DOUBLE_ARROW ) && $val ) {
$_arr .= ' ' . $tokens -> getAndNext () . ' ' ;
$key = true ;
$val = false ;
} elseif ( ! $val && $tokens -> is ( '[' )) {
$_arr .= $this -> parseArray ( $tokens );
$key = false ;
$val = true ;
} elseif ( $tokens -> is ( ']' ) && ! $key ) {
$tokens -> next ();
return $_arr . ')' ;
} 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
* @ throws ImproperUseException
2013-01-25 18:36:16 +04:00
*/
2013-02-23 02:03:05 +04:00
public function parseConst ( Tokenizer $tokens ) {
$tokens -> get ( '#' );
$name = $tokens -> getNext ( T_STRING );
$tokens -> next ();
if ( $tokens -> is ( T_NAMESPACE )) {
$name .= '\\' ;
$name .= $tokens -> getNext ( T_STRING );
$tokens -> next ();
}
if ( $tokens -> is ( T_DOUBLE_COLON )) {
$name .= '::' ;
$name .= $tokens -> getNext ( T_STRING );
$tokens -> next ();
}
if ( defined ( $name )) {
return $name ;
} else {
throw new ImproperUseException ( " Use undefined constant $name " );
}
}
/**
* @ param Tokenizer $tokens
* @ param $name
* @ return string
* @ throws ImproperUseException
*/
public function parseMacro ( Tokenizer $tokens , $name ) {
if ( isset ( $this -> macros [ $name ])) {
$macro = $this -> macros [ $name ];
$p = $this -> parseParams ( $tokens );
$args = array ();
foreach ( $macro [ " args " ] as $arg ) {
if ( isset ( $p [ $arg ])) {
$args [ $arg ] = $p [ $arg ];
} elseif ( isset ( $macro [ " defaults " ][ $arg ])) {
$args [ $arg ] = $macro [ " defaults " ][ $arg ];
} else {
throw new ImproperUseException ( " Macro ' $name ' require ' $arg ' argument " );
}
}
$args = $args ? '$tpl = ' . Compiler :: toArray ( $args ) . ';' : '' ;
return '$_tpl = $tpl; ' . $args . ' ?>' . $macro [ " body " ] . '<?php $tpl = $_tpl; unset($_tpl);' ;
} else {
throw new ImproperUseException ( " Undefined macro ' $name ' " );
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-01-28 16:34:34 +04:00
public function parseArgs ( Tokenizer $tokens ) {
$_args = " ( " ;
$tokens -> next ();
$arg = $colon = false ;
while ( $tokens -> valid ()) {
if ( ! $arg && $tokens -> is ( T_VARIABLE , T_STRING , " ( " , Tokenizer :: MACRO_SCALAR , '"' , Tokenizer :: MACRO_UNARY , Tokenizer :: MACRO_INCDEC )) {
$_args .= $this -> parseExp ( $tokens , true );
$arg = true ;
$colon = false ;
} elseif ( ! $arg && $tokens -> is ( '[' )) {
$_args .= $this -> parseArray ( $tokens );
$arg = true ;
$colon = false ;
} elseif ( $arg && $tokens -> is ( ',' )) {
$_args .= $tokens -> getAndNext () . ' ' ;
$arg = false ;
$colon = true ;
} elseif ( ! $colon && $tokens -> is ( ')' )) {
$tokens -> next ();
return $_args . ')' ;
} else {
break ;
}
}
throw new TokenizeException ( " Unexpected token ' " . $tokens -> current () . " ' in argument list " );
}
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-04-28 11:33:36 +04:00
public function parsePlainArg ( Tokenizer $tokens , & $static ) {
2013-02-13 20:51:27 +04:00
if ( $tokens -> is ( T_CONSTANT_ENCAPSED_STRING )) {
2013-02-20 18:03:53 +04:00
if ( $tokens -> isNext ( '|' )) {
return $this -> parseExp ( $tokens , true );
} else {
$str = $tokens -> getAndNext ();
$static = stripslashes ( substr ( $str , 1 , - 1 ));
return $str ;
}
2013-02-13 20:51:27 +04:00
} elseif ( $tokens -> is ( Tokenizer :: MACRO_STRING )) {
2013-02-20 18:03:53 +04:00
$static = $tokens -> getAndNext ();
return '"' . addslashes ( $static ) . '"' ;
2013-02-13 20:51:27 +04:00
} else {
return $this -> parseExp ( $tokens , true );
}
}
2013-01-25 18:36:16 +04:00
/**
* Parse parameters as $key = $value
* param1 = $var param2 = 3 ...
*
* @ static
* @ param Tokenizer $tokens
* @ param array $defaults
* @ throws \Exception
* @ return array
*/
2013-01-28 16:34:34 +04:00
public function parseParams ( Tokenizer $tokens , array $defaults = null ) {
$params = array ();
2013-01-25 18:36:16 +04:00
while ( $tokens -> valid ()) {
if ( $tokens -> is ( Tokenizer :: MACRO_STRING )) {
$key = $tokens -> getAndNext ();
if ( $defaults && ! isset ( $defaults [ $key ])) {
throw new \Exception ( " Unknown parameter ' $key ' " );
}
if ( $tokens -> is ( " = " )) {
$tokens -> next ();
$params [ $key ] = $this -> parseExp ( $tokens );
} else {
2013-02-23 02:03:05 +04:00
$params [ $key ] = 'true' ;
2013-01-25 18:36:16 +04:00
}
} elseif ( $tokens -> is ( Tokenizer :: MACRO_SCALAR , '"' , '`' , T_VARIABLE , " [ " , '(' )) {
$params [] = $this -> parseExp ( $tokens );
} else {
break ;
}
}
if ( $defaults ) {
$params += $defaults ;
}
return $params ;
2013-01-28 16:34:34 +04:00
}
2013-01-25 18:36:16 +04:00
}
class CompileException extends \ErrorException {}
2013-02-07 17:37:16 +04:00
class SecurityException extends CompileException {}
class ImproperUseException extends \LogicException {}