Вход Регистрация
Файл: libs/fenom/Fenom/Template.php
Строк: 1572
<?php
/*
 * This file is part of Fenom.
 *
 * (c) 2013 Ivan Shalganov
 *
 * For the full copyright and license information, please view the license.md
 * file that was distributed with this source code.
 */
namespace Fenom;

use 
Fenom;
use 
FenomErrorUnexpectedTokenException;
use 
FenomErrorCompileException;
use 
FenomErrorInvalidUsageException;
use 
FenomErrorSecurityException;
use 
FenomErrorTokenizeException;

/**
 * Template compiler
 *
 * @package    Fenom
 * @author     Ivan Shalganov <a.cobest@gmail.com>
 */
class Template extends Render
{
    const 
VAR_NAME '$var';
    const 
TPL_NAME '$tpl';

    
/**
     * Disable array parser.
     */
    
const DENY_ARRAY 1;
    
/**
     * Disable modifier parser.
     */
    
const DENY_MODS 2;
    
/**
     * @var int shared counter
     */
    
public $i 1;
    
/**
     * @var array of macros
     */
    
public $macros = array();

    
/**
     * @var array of blocks
     */
    
public $blocks = array();

    
/**
     * @var string|null
     */
    
public $extends;

    
/**
     * @var string|null
     */
    
public $extended;

    
/**
     * Stack of extended templates
     * @var array
     */
    
public $ext_stack = array();

    public 
$extend_body false;

    
/**
     * Template PHP code
     * @var string
     */
    
private $_body;

    
/**
     * Call stack
     * @var Tag[]
     */
    
private $_stack = array();

    
/**
     * Template source
     * @var string
     */
    
private $_src;
    
/**
     * @var int
     */
    
private $_line 1;
    private 
$_post = array();
    
/**
     * @var bool|string
     */
    
private $_ignore false;

    private 
$_before;

    private 
$_filters = array();

    
/**
     * @var int crc32 of the template name
     */
    
private $_crc 0;

    
/**
     * @param Fenom $fenom Template storage
     * @param int $options
     * @return FenomTemplate
     */
    
public function __construct(Fenom $fenom$options)
    {
        
$this->_fenom       $fenom;
        
$this->_options     $options;
        
$this->_filters     $this->_fenom->getFilters();
        
$this->_tag_filters $this->_fenom->getTagFilters();
    }

    
/**
     * Get tag stack size
     * @return int
     */
    
public function getStackSize()
    {
        return 
count($this->_stack);
    }

    
/**
     * @param string $tag
     * @return bool|FenomTag
     */
    
public function getParentScope($tag)
    {
        for (
$i count($this->_stack) - 1$i >= 0$i--) {
            if (
$this->_stack[$i]->name == $tag) {
                return 
$this->_stack[$i];
            }
        }

        return 
false;
    }

    
/**
     * Load source from provider
     * @param string $name
     * @param bool $compile
     * @return self
     */
    
public function load($name$compile true)
    {
        
$this->_name $name;
        
$this->_crc  crc32($this->_name);
        if (
$provider strstr($name':'true)) {
            
$this->_scm       $provider;
            
$this->_base_name substr($namestrlen($provider) + 1);
        } else {
            
$this->_base_name $name;
        }
        
$this->_provider $this->_fenom->getProvider($provider);
        
$this->_src      $this->_provider->getSource($this->_base_name$this->_time);
        if (
$compile) {
            
$this->compile();
        }
        return 
$this;
    }

    
/**
     * Load custom source
     * @param string $name template name
     * @param string $src template source
     * @param bool $compile
     * @return FenomTemplate
     */
    
public function source($name$src$compile true)
    {
        
$this->_name $name;
        
$this->_src  $src;
        if (
$compile) {
            
$this->compile();
        }
        return 
$this;
    }

    
/**
     * Convert template to PHP code
     *
     * @throws CompileException
     */
    
public function compile()
    {
        
$end          $pos 0;
        foreach (
$this->_fenom->getPreFilters() as $filter) {
            
$this->_src call_user_func($filter$this$this->_src);
        }
        while ((
$start strpos($this->_src'{'$pos)) !== false) { // search open-symbol of tags
            
switch (substr($this->_src$start 11)) { // check next character
                
case "n":
                case 
"r":
                case 
"t":
                case 
" ":
                case 
"}"// ignore the tag
                    
$this->_appendText(substr($this->_src$pos$start $pos 2));
                    
$end $start 1;
                    break;
                case 
"*"// comment block
                    
$end strpos($this->_src'*}'$start); // find end of the comment block
                    
if ($end === false) {
                        throw new 
CompileException("Unclosed comment block in line {$this->_line}"01$this->_name$this->_line);
                    }
                    
$end++;
                    
$this->_appendText(substr($this->_src$pos$start $pos));
                    
$comment substr($this->_src$start$end $start); // read the comment block for processing
                    
$this->_line += substr_count($comment"n"); // count lines in comments
                    
unset($comment); // cleanup
                    
break;
                default:
                    
$this->_appendText(substr($this->_src$pos$start $pos));
                    
$end $start 1;
                    do {
                        
$need_more false;
                        
$end       strpos($this->_src'}'$end 1); // search close-symbol of the tag
                        
if ($end === false) { // if unexpected end of template
                            
throw new CompileException("Unclosed tag in line {$this->_line}"01$this->_name$this->_line);
                        }
                        
$tag substr(
                            
$this->_src,
                            
$start 1// skip '{'
                            
$end $start // skip '}'
                        
);

                        if (
$this->_ignore) { // check ignore
                            
if ($tag === '/' $this->_ignore) { // turn off ignore
                                
$this->_ignore false;
                            } else { 
// still ignore
                                
$this->_appendText('{' $tag '}');
                                continue;
                            }
                        }

                        if (
$this->_tag_filters) {
                            foreach (
$this->_tag_filters as $filter) {
                                
$tag call_user_func($filter$tag$this);
                            }
                        }
                        
$tokens = new Tokenizer($tag); // tokenize the tag
                        
if ($tokens->isIncomplete()) { // all strings finished?
                            
$need_more true;
                        } else {
                            
$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(00) . "' <- there"0E_ERROR$this->_name$this->_line);
                            }
                        }
                    } while (
$need_more);
                    unset(
$tag); // cleanup
                    
break;
            }
            
$pos $end 1// move search-pointer to end of the tag
        
}

        
gc_collect_cycles();
        
$this->_appendText(substr($this->_src$end $end 0)); // append tail of the template
        
if ($this->_stack) {
            
$_names = array();
            foreach (
$this->_stack as $scope) {
                
$_names[] = '{' $scope->name '} opened on line ' $scope->line;
            }
            throw new 
CompileException("Unclosed tag" . (count($_names) > "s" "") . ": " implode(
                
", ",
                
$_names
            
), 01$this->_name$scope->line); // $scope already defined there!
        
}
        
$this->_src ""// cleanup
        
if ($this->_post) {
            foreach (
$this->_post as $cb) {
                
call_user_func_array($cb, array($this, &$this->_body));
            }
        }
        
$this->addDepend($this); // for 'verify' performance
        
foreach ($this->_fenom->getPostFilters() as $filter) {
            
$this->_body call_user_func($filter$this$this->_body);
        }
    }

    
/**
     * Set or unset the option
     * @param int $option
     * @param bool $value
     */
    
public function setOption($option$value)
    {
        if (
$value) {
            
$this->_options |= $option;
        } else {
            
$this->_options &= ~$option;
        }
    }

    
/**
     * Execute some code at loading cache
     * @param $code
     * @return void
     */
    
public function before($code)
    {
        
$this->_before .= $code;
    }

    
/**
     * Generate temporary internal template variable
     * @return string
     */
    
public function tmpVar()
    {
        return 
sprintf('$t%x_%x'$this->_crc$this->i++);
    }

    
/**
     * Append plain text to template body
     *
     * @param string $text
     */
    
private function _appendText($text)
    {
        
$this->_line += substr_count($text"n");
        if (
$this->_filters) {
            if (
strpos($text"<?") === false) {
                foreach (
$this->_filters as $filter) {
                    
$text call_user_func($filter$this$text);
                }
            } else {
                
$fragments explode("<?"$text);
                foreach (
$fragments as &$fragment) {
                    if (
$fragment) {
                        foreach (
$this->_filters as $filter) {
                            
$fragment call_user_func($filter$this$fragment);
                        }
                    }
                }
                
$text implode('<?php echo "<?"; ?>'$fragments);
            }
        } else {
            
$text str_replace("<?"'<?php echo "<?"; ?>' PHP_EOL$text);
        }
        if(
$this->_options Fenom::AUTO_STRIP) {
            
$text preg_replace('/s+/uS'' '$text);
            
$text preg_replace('/s*([pPpS]+)s*/uS''$1'$text);
        }
        
$this->_body .= $text;
    }

    
/**
     * Append PHP code to template body
     *
     * @param string $code
     * @param $source
     */
    
private function _appendCode($code$source)
    {
        if (!
$code) {
            return;
        } else {
            
$this->_line += substr_count($source"n");
            
$this->_body .= "<?phpn/* {$this->_name}:{$this->_line}{$source} */n $code ?>";
        }
    }

    
/**
     * @param $tag_name
     */
    
public function ignore($tag_name) {
        
$this->_ignore $tag_name;
    }

    
/**
     * @param callable[] $cb
     */
    
public function addPostCompile($cb)
    {
        
$this->_post[] = $cb;
    }

    
/**
     * Return PHP code of template
     *
     * @return string
     */
    
public function getBody()
    {
        return 
$this->_body;
    }

    
/**
     * Return PHP code for saving to file
     *
     * @return string
     */
    
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() . ", array(n" .
        
"t'options' => {$this->_options},n" .
        
"t'provider' => " var_export($this->_scmtrue) . ",n" .
        
"t'name' => " var_export($this->_nametrue) . ",n" .
        
"t'base_name' => " var_export($this->_base_nametrue) . ",n" .
        
"t'time' => {$this->_time},n" .
        
"t'depends' => " var_export($this->_dependstrue) . ",n" .
        
"t'macros' => " $this->_getMacrosArray() . ",n
        ));n"
;
    }

    
/**
     * Make array with macros code
     * @return string
     */
    
private function _getMacrosArray()
    {
        if (
$this->macros) {
            
$macros = array();
            foreach (
$this->macros as $m) {
                if (
$m["recursive"]) {
                    
$macros[] = "tt'" $m["name"] . "' => function ($var$tpl) {n?>" $m["body"] . "<?phpn}";
                }
            }
            return 
"array(n" implode(",n"$macros) . ")";
        } else {
            return 
'array()';
        }
    }

    
/**
     * Return closure code
     * @return string
     */
    
private function _getClosureSource()
    {
        return 
"function ($var$tpl) {n?>{$this->_body}<?phpn}";
    }

    
/**
     * Runtime execute template.
     *
     * @param array $values input values
     * @throws CompileException
     * @return Render
     */
    
public function display(array $values)
    {
        if (!
$this->_code) {
            
// evaluate template's code
            
eval("$this->_code = " $this->_getClosureSource() . ";n$this->_macros = " $this->_getMacrosArray() . ';');
            if (!
$this->_code) {
                throw new 
CompileException("Fatal error while creating the template");
            }
        }
        return 
parent::display($values);

    }

    
/**
     * Add depends
     * @param Render $tpl
     */
    
public function addDepend(Render $tpl)
    {
        
$this->_depends[$tpl->getScm()][$tpl->getName()] = $tpl->getTime();
    }

    
/**
     * Output the value
     *
     * @param string $data
     * @param null|bool $escape
     * @return string
     */
    
public function out($data$escape null)
    {
        if (
$escape === null) {
            
$escape $this->_options Fenom::AUTO_ESCAPE;
        }
        if (
$escape) {
            return 
"echo htmlspecialchars($data, ENT_COMPAT, 'UTF-8');";
        } else {
            return 
"echo $data;";
        }
    }

    
/**
     * Import block from another template
     * @param string $tpl
     */
    
public function importBlocks($tpl)
    {
        
$donor $this->_fenom->compile($tplfalse);
        foreach (
$donor->blocks as $name => $block) {
            if (!isset(
$this->blocks[$name])) {
                
$block['import']     = $this->getName();
                
$this->blocks[$name] = $block;
            }
        }
        
$this->addDepend($donor);
    }

    
/**
     * Extends the template
     * @param string $tpl
     * @return FenomTemplate parent
     */
    
public function extend($tpl)
    {
        if (!
$this->_body) {
            
$this->compile();
        }
        
$parent           $this->_fenom->getRawTemplate()->load($tplfalse);
        
$parent->blocks   = & $this->blocks;
        
$parent->macros   = & $this->macros;
        
$parent->extended $this->getName();
        if (!
$this->ext_stack) {
            
$this->ext_stack[] = $this->getName();
        }
        
$this->ext_stack[] = $parent->getName();
        
$parent->_options  $this->_options;
        
$parent->ext_stack $this->ext_stack;
        
$parent->compile();
        
$this->_body $parent->_body;
        
$this->_src  $parent->_src;
        
$this->addDepend($parent);
        return 
$parent;
    }

    
/**
     * Tag router
     * @param Tokenizer $tokens
     *
     * @throws SecurityException
     * @throws CompileException
     * @return string executable PHP code
     */
    
public function parseTag(Tokenizer $tokens)
    {
        try {
            if (
$tokens->is(Tokenizer::MACRO_STRING)) {
                return 
$this->parseAct($tokens);
            } elseif (
$tokens->is('/')) {
                return 
$this->parseEndTag($tokens);
            } else {
                return 
$this->out($this->parseExpr($tokens));
            }
        } catch (
InvalidUsageException $e) {
            throw new 
CompileException($e->getMessage() . " in {$this->_name} line {$this->_line}"0E_ERROR$this->_name$this->_line$e);
        } catch (
LogicException $e) {
            throw new 
SecurityException($e->getMessage() . " in {$this->_name} line {$this->_line}, near '{" $tokens->getSnippetAsString(00) . "' <- there"0E_ERROR$this->_name$this->_line$e);
        } catch (
Exception $e) {
            throw new 
CompileException($e->getMessage() . " in {$this->_name} line {$this->_line}, near '{" $tokens->getSnippetAsString(00) . "' <- there"0E_ERROR$this->_name$this->_line$e);
        }
    }

    
/**
     * Close tag handler
     *
     * @param Tokenizer $tokens
     * @return string
     * @throws TokenizeException
     */
    
public function parseEndTag(Tokenizer $tokens)
    {
        
$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 Tag $tag */
        
$tag array_pop($this->_stack);
        if (
$tag->name !== $name) {
            throw new 
TokenizeException("Unexpected closing of the tag '$name' (expecting closing of the tag {$tag->name}, opened in line {$tag->line})");
        }
        return 
$tag->end($tokens);
    }

    
/**
     * Parse action {action ...} or {action(...) ...}
     *
     * @static
     * @param Tokenizer $tokens
     * @throws LogicException
     * @throws RuntimeException
     * @throws ErrorTokenizeException
     * @return string
     */
    
public function parseAct(Tokenizer $tokens)
    {
        
$action $tokens->get(Tokenizer::MACRO_STRING);
        
$tokens->next();
        if (
$tokens->is("("T_DOUBLE_COLONT_NS_SEPARATOR) && !$tokens->isWhiteSpaced()
        ) { 
// just invoke function or static method
            
$tokens->back();
            return 
$this->out($this->parseExpr($tokens));
        } elseif (
$tokens->is('.')) {
            
$name $tokens->skip()->get(Tokenizer::MACRO_STRING);
            if (
$action !== "macro") {
                
$name $action "." $name;
            }
            return 
$this->parseMacroCall($tokens$name);
        }
        if (
$info $this->_fenom->getTag($action$this)) {
            
$tag = new Tag($action$this$info$this->_body);
            if (
$tokens->is(':')) { // parse tag options
                
do {
                    
$tag->tagOption($tokens->next()->need(T_STRING)->getAndNext());
                } while (
$tokens->is(':'));
            }
            
$code $tag->start($tokens);
            if (
$tag->isClosed()) {
                
$tag->restoreAll();
            } else {
                
array_push($this->_stack$tag);
            }
            return 
$code;
        }

        for (
$j $i count($this->_stack) - 1$i >= 0$i--) { // call function's internal tag
            
if ($this->_stack[$i]->hasTag($action$j $i)) {
                return 
$this->_stack[$i]->tag($action$tokens);
            }
        }
        if (
$tags $this->_fenom->getTagOwners($action)) { // unknown template tag
            
throw new TokenizeException("Unexpected tag '$action' (this tag can be used with '" implode(
                    
"', '",
                    
$tags
                
) . "')");
        } else {
            throw new 
TokenizeException("Unexpected tag '$action'");
        }
    }

    
/**
     * Get current template line
     * @return int
     */
    
public function getLine()
    {
        return 
$this->_line;
    }

    
/**
     * Parse expressions. The mix of operators and terms.
     *
     * @param Tokenizer $tokens
     * @return string
     * @throws ErrorUnexpectedTokenException
     */
    
public function parseExpr(Tokenizer $tokens)
    {
        
$exp  = array();
        
$var  false// last term was: true - variable, false - mixed
        
$op   false// last exp was operator
        
$cond false// was comparison operator
        
while ($tokens->valid()) {
            
// parse term
            
$term $this->parseTerm($tokens$var); // term of the expression
            
if ($term !== false) {
                if (
$this->_options Fenom::FORCE_VERIFY) {
                    
$term '(isset(' $term ') ? ' $term ' : null)';
                    
$var  false;
                }
                if (
$tokens->is('|')) {
                    
$term $this->parseModifier($tokens$term);
                    
$var  false;
                }
                if (
$tokens->is('?''!')) {
                    
$term $this->parseTernary($tokens$term$var);
                    
$var  false;
                }
                
$exp[] = $term;
                
$op    false;
            } else {
                break;
            }
            if (!
$tokens->valid()) {
                break;
            }
            
// parse operator
            
if ($tokens->is(Tokenizer::MACRO_BINARY)) { // binary operator: $a + $b, $a <= $b, ...
                
if ($tokens->is(Tokenizer::MACRO_COND)) { // comparison operator
                    
if ($cond) {
                        break;
                    }
                    
$cond true;
                } elseif (
$tokens->is(Tokenizer::MACRO_BOOLEAN)) {
                    
$cond false;
                }
                
$op $tokens->getAndNext();
            } elseif (
$tokens->is(Tokenizer::MACRO_EQUALS'[')) { // assignment operator: $a = 4, $a += 3, ...
                
if (!$var) {
                    break;
                }
                
$op $tokens->getAndNext();
                if(
$op == '[') {
                    
$tokens->need(']')->next()->need('=')->next();
                    
$op '[]=';
                }
            } elseif (
$tokens->is(T_STRING)) { // test or containment operator: $a in $b, $a is set, ...
                
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);
                } else {
                    break;
                }
            } elseif (
$tokens->is('~')) { // string concatenation operator: 'asd' ~ $var
                
if($tokens->isNext('=')) { // ~=
                    
$exp[] = ".=";
                    
$tokens->next()->next();
                } else {
                    
$concat = array(array_pop($exp));

                    while (
$tokens->is('~')) {
                        
$tokens->next();
                        if (
$tokens->is(T_LNUMBERT_DNUMBER)) {
                            
$concat[] = "strval(" $this->parseTerm($tokens) . ")";
                        } else {
                            if(!
$concat[] = $this->parseTerm($tokens)) {
                                throw new 
UnexpectedTokenException($tokens);
                            }
                        }
                    }
                    
$exp[] = "(" implode("."$concat) . ")";
                }
            } else {
                break;
            }
            if (
$op) {
                
$exp[] = $op;
            }
        }

        if (
$op || !$exp) {
            throw new 
UnexpectedTokenException($tokens);
        }
        return 
implode(' '$exp);
    }

    
/**
     * Parse any term of expression: -2, ++$var, 'adf'
     *
     * @param Tokenizer $tokens
     * @param bool $is_var is parsed term - plain variable
     * @throws ErrorUnexpectedTokenException
     * @throws ErrorTokenizeException
     * @throws Exception
     * @return bool|string
     */
    
public function parseTerm(Tokenizer $tokens, &$is_var false)
    {
        
$is_var false;
        if (
$tokens->is(Tokenizer::MACRO_UNARY)) {
            
$unary $tokens->getAndNext();
        } else {
            
$unary "";
        }
        if (
$tokens->is(T_LNUMBERT_DNUMBER)) {
            return 
$unary $this->parseScalar($tokenstrue);
        } elseif (
$tokens->is(T_CONSTANT_ENCAPSED_STRING'"'T_ENCAPSED_AND_WHITESPACE)) {
            if (
$unary) {
                throw new 
UnexpectedTokenException($tokens->back());
            }
            return 
$this->parseScalar($tokenstrue);
        } elseif (
$tokens->is(T_VARIABLE)) {
            
$code $unary $this->parseVariable($tokens);
            if (
$tokens->is("(") && $tokens->hasBackList(T_STRINGT_OBJECT_OPERATOR)) {
                if (
$this->_options Fenom::DENY_METHODS) {
                    throw new 
LogicException("Forbidden to call methods");
                }
                
$code $this->parseChain($tokens$code);
            } elseif (
$tokens->is(Tokenizer::MACRO_INCDEC)) {
                
$code .= $tokens->getAndNext();
            } else {
                
$is_var true;
            }
            return 
$code;
        } elseif (
$tokens->is('$')) {
            
$var  $this->parseAccessor($tokens$is_var);
            return 
$unary $var;
        } elseif (
$tokens->is(Tokenizer::MACRO_INCDEC)) {
            return 
$unary $tokens->getAndNext() . $this->parseVariable($tokens);
        } elseif (
$tokens->is("(")) {
            
$tokens->next();
            
$code $unary "(" $this->parseExpr($tokens) . ")";
            
$tokens->need(")")->next();
            return 
$code;
        } 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");
                }
                return 
$unary $this->parseChain($tokens$func $this->parseArgs($tokens->next()));
            } elseif (
$tokens->isNext(T_NS_SEPARATORT_DOUBLE_COLON)) {
                
$method $this->parseStatic($tokens);
                
$args   $this->parseArgs($tokens);
                return 
$unary $this->parseChain($tokens$method $args);
            } else {
                return 
false;
            }
        } elseif (
$tokens->is(T_ISSETT_EMPTY)) {
            
$func $tokens->getAndNext();
            if (
$tokens->is("(") && $tokens->isNext(T_VARIABLE)) {
                
$code $unary $func "(" $this->parseVariable($tokens->next()) . ")";
                
$tokens->need(')')->next();
                return 
$code;
            } 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) {
            throw new 
UnexpectedTokenException($tokens->back());
        } else {
            return 
false;
        }
    }

    
/**
     * Parse call-chunks: $var->func()->func()->prop->func()->...
     * @param Tokenizer $tokens
     * @param string $code start point (it is $var)
     * @return string
     */
    
public function parseChain(Tokenizer $tokens$code) {
        do {
            if (
$tokens->is('(')) {
                
$code .= $this->parseArgs($tokens);
            }
            if (
$tokens->is(T_OBJECT_OPERATOR) && $tokens->isNext(T_STRING)) {
                
$code .= '->' $tokens->next()->getAndNext();
            }
        } while (
$tokens->is('('T_OBJECT_OPERATOR));

        return 
$code;
    }

    
/**
     * Parse variable name: $a, $a.b, $a.b['c']
     * @param Tokenizer $tokens
     * @param $var
     * @return string
     * @throws ErrorUnexpectedTokenException
     */
    
public function parseVariable(Tokenizer $tokens$var null)
    {
        if (!
$var) {
            
$var '$var["' substr($tokens->get(T_VARIABLE), 1) . '"]';
            
$tokens->next();
        }
        while (
$t $tokens->key()) {
            if (
$t === ".") {
                
$tokens->next();
                if (
$tokens->is(T_VARIABLE)) {
                    
$key '[ $var["' substr($tokens->getAndNext(), 1) . '"] ]';
                } elseif (
$tokens->is(Tokenizer::MACRO_STRING)) {
                    
$key '["' $tokens->getAndNext() . '"]';
                } elseif (
$tokens->is(Tokenizer::MACRO_SCALAR)) {
                    
$key "[" $tokens->getAndNext() . "]";
                } elseif (
$tokens->is('"')) {
                    
$key "[" $this->parseQuote($tokens) . "]";
                } else {
                    throw new 
UnexpectedTokenException($tokens);
                }
                
$var .= $key;
            } elseif (
$t === "[") {
                if(
$tokens->isNext(']')) {
                    break;
                }
                
$tokens->next();
                if (
$tokens->is(Tokenizer::MACRO_STRING)) {
                    if (
$tokens->isNext("(")) {
                        
$key "[" $this->parseExpr($tokens) . "]";
                    } else {
                        
$key '["' $tokens->current() . '"]';
                        
$tokens->next();
                    }
                } else {
                    
$key "[" $this->parseExpr($tokens) . "]";
                }
                
$tokens->get("]");
                
$tokens->next();
                
$var .= $key;
            } elseif (
$t === T_DNUMBER) {
                
$var .= '[' substr($tokens->getAndNext(), 1) . ']';
            } elseif (
$t === T_OBJECT_OPERATOR) {
                
$var .= "->" $tokens->getNext(T_STRING);
                
$tokens->next();
            } else {
                break;
            }
        }
        return 
$var;
    }

    
/**
     * Parse accessor
     */
    
public function parseAccessor(Tokenizer $tokens, &$is_var)
    {
        
$is_var false;
        
$vars   = array(
            
'get'     => '$_GET',
            
'post'    => '$_POST',
            
'session' => '$_SESSION',
            
'cookie'  => '$_COOKIE',
            
'request' => '$_REQUEST',
            
'files'   => '$_FILES',
            
'globals' => '$GLOBALS',
            
'server'  => '$_SERVER',
            
'env'     => '$_ENV',
            
'tpl'     => '$tpl->info'
        
);
        if (
$this->_options Fenom::DENY_ACCESSOR) {
            throw new 
LogicException("Accessor are disabled");
        }
        
$key $tokens->need('$')->next()->need('.')->next()->current();
        
$tokens->next();
        if (isset(
$vars[$key])) {
            
$is_var true;
            return 
$this->parseVariable($tokens$vars[$key]);
        }
        switch (
$key) {
            case 
'const':
                
$tokens->need('.')->next();
                
$var '@constant(' var_export($this->parseName($tokens), true) . ')';
                break;
            case 
'version':
                
$var 'Fenom::VERSION';
                break;
            default:
                throw new 
UnexpectedTokenException($tokens->back());
        }

        return 
$var;
    }

    
/**
     * Parse ternary operator
     *
     * @param Tokenizer $tokens
     * @param $var
     * @param $is_var
     * @return string
     * @throws UnexpectedTokenException
     */
    
public function parseTernary(Tokenizer $tokens$var$is_var)
    {
        
$empty $tokens->is('?');
        
$tokens->next();
        if (
$tokens->is(":")) {
            
$tokens->next();
            if (
$empty) {
                if (
$is_var) {
                    return 
'(empty(' $var ') ? (' $this->parseExpr($tokens) . ') : ' $var ')';
                } else {
                    return 
'(' $var ' ?: (' $this->parseExpr($tokens) . '))';
                }
            } else {
                if (
$is_var) {
                    return 
'(isset(' $var ') ? ' $var ' : (' $this->parseExpr($tokens) . '))';
                } else {
                    return 
'((' $var ' !== null) ? ' $var ' : (' $this->parseExpr($tokens) . '))';
                }
            }
        } elseif (
$tokens->is(
                
Tokenizer::MACRO_BINARY,
                
Tokenizer::MACRO_BOOLEAN,
                
Tokenizer::MACRO_MATH
            
) || !$tokens->valid()
        ) {
            if (
$empty) {
                if (
$is_var) {
                    return 
'!empty(' $var ')';
                } else {
                    return 
'(' $var ')';
                }
            } else {
                if (
$is_var) {
                    return 
'isset(' $var ')';
                } else {
                    return 
'(' $var ' !== null)';
                }
            }
        } else {
            
$expr1 $this->parseExpr($tokens);
            
$tokens->need(':')->skip();
            
$expr2 $this->parseExpr($tokens);
            if (
$empty) {
                if (
$is_var) {
                    return 
'(empty(' $var ') ? ' $expr2 ' : ' $expr1 ')';
                } else {
                    return 
'(' $var ' ? ' $expr1 ' : ' $expr2 ')';
                }
            } else {
                if (
$is_var) {
                    return 
'(isset(' $var ') ? ' $expr1 ' : ' $expr2 ')';
                } else {
                    return 
'((' $var ' !== null) ? ' $expr1 ' : ' $expr2 ')';
                }
            }
        }
    }

    
/**
     * Parse 'is' and 'is not' operators
     * @see _tests
     * @param Tokenizer $tokens
     * @param string $value
     * @param bool $variable
     * @throws InvalidUsageException
     * @return string
     */
    
public function parseIs(Tokenizer $tokens$value$variable false)
    {
        
$tokens->next();
        if (
$tokens->current() == 'not') {
            
$invert '!';
            
$equal  '!=';
            
$tokens->next();
        } else {
            
$invert '';
            
$equal  '==';
        }
        if (
$tokens->is(Tokenizer::MACRO_STRING)) {
            
$action $tokens->current();
            if (!
$variable && ($action == "set" || $action == "empty")) {
                
$action "_$action";
                
$tokens->next();
                return 
$invert sprintf($this->_fenom->getTest($action), $value);
            } elseif (
$test $this->_fenom->getTest($action)) {
                
$tokens->next();
                return 
$invert sprintf($test$value);
            } elseif (
$tokens->isSpecialVal()) {
                
$tokens->next();
                return 
'(' $value ' ' $equal '= ' $action ')';
            }
            return 
$invert '(' $value ' instanceof \' . $this->parseName($tokens) . ')';
        } elseif ($tokens->is(T_VARIABLE, '
[', Tokenizer::MACRO_SCALAR, '"')) {
            return '(' . 
$value . ' ' . $equal . '= ' . $this->parseTerm($tokens) . ')';
        } elseif (
$tokens->is(T_NS_SEPARATOR)) { //
            return 
$invert . '(' . $value . ' instanceof \' . $this->parseName($tokens) . ')';
        } else {
            throw new InvalidUsageException("
Unknown argument");
        }
    }

    /**
     * Parse 'in' and 'not in' operators
     * @param Tokenizer 
$tokens
     * @param string 
$value
     * @throws InvalidUsageException
     * @throws UnexpectedTokenException
     * @return string
     */
    public function parseIn(Tokenizer 
$tokens$value)
    {
        
$checkers = array(
            "
string" => 'is_int(strpos(%2$s, %1$s))',
            "
list"   => "in_array(%s, %s)",
            "
keys"   => "array_key_exists(%s, %s)",
            "
auto"   => 'FenomModifier::in(%s, %s)'
        );
        
$checker  = null;
        
$invert   = '';
        if (
$tokens->current() == 'not') {
            
$invert = '!';
            
$tokens->next();
        }
        if (
$tokens->current() !== "in") {
            throw new UnexpectedTokenException(
$tokens);
        }
        
$tokens->next();
        if (
$tokens->is(Tokenizer::MACRO_STRING)) {
            
$checker = $tokens->current();
            if (!isset(
$checkers[$checker])) {
                throw new UnexpectedTokenException(
$tokens);
            }
            
$tokens->next();
        }
        if (
$tokens->is('[')) {
            if (
$checker == "string") {
                throw new InvalidUsageException("
Can not use string operation for array");
            } elseif (!
$checker) {
                
$checker = "list";
            }
            return 
$invert . sprintf($checkers[$checker]$value$this->parseArray($tokens));
        } elseif (
$tokens->is('"', T_ENCAPSED_AND_WHITESPACE, T_CONSTANT_ENCAPSED_STRING)) {
            if (!$checker) {
                $checker = "string";
            } elseif ($checker != "string") {
                throw new InvalidUsageException("Can not use array operation for string");
            }
            return $invert . sprintf($checkers[$checker], "strval($value)", $this->parseScalar($tokens));
        } elseif ($tokens->is(T_VARIABLE, Tokenizer::MACRO_INCDEC)) {
            if (!$checker) {
                $checker = "auto";
            }
            return $invert . sprintf($checkers[$checker], $value, $this->parseTerm($tokens));
        } else {
            throw new UnexpectedTokenException($tokens);
        }
    }

    /**
     * Parse method, class or constant name
     *
     * @param Tokenizer $tokens
     * @return string
     */
    public function parseName(Tokenizer $tokens)
    {
        $tokens->skipIf(T_NS_SEPARATOR);
        $name = "";
        if ($tokens->is(T_STRING)) {
            $name .= $tokens->getAndNext();
            while ($tokens->is(T_NS_SEPARATOR)) {
                $name .= '
\' . $tokens->next()->get(T_STRING);
                $tokens->next();
            }
        }
        return $name;
    }

    /**
     * Parse scalar values
     *
     * @param Tokenizer $tokens
     * @throws ErrorUnexpectedTokenException
     * @return string
     */
    public function parseScalar(Tokenizer $tokens)
    {
        $token = $tokens->key();
        switch ($token) {
            case T_CONSTANT_ENCAPSED_STRING:
            case T_LNUMBER:
            case T_DNUMBER:
                return $tokens->getAndNext();
                break;
            case T_ENCAPSED_AND_WHITESPACE:
            case '"':
                return 
$this->parseQuote($tokens);
                break;
            default:
                throw new UnexpectedTokenException(
$tokens);
        }
    }

    /**
     * Parse string with or without variable
     *
     * @param Tokenizer 
$tokens
     * @throws UnexpectedTokenException
     * @return string
     */
    public function parseQuote(Tokenizer 
$tokens)
    {
        if (
$tokens->is('"')) {
            $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) {
                    if (strlen(
$_str) > 1) {
                        
$_str .= '".';
                    } else {
                        $_str = "";
                    }
                    $_str .= '
$var["' . substr($tokens->current(), 1) . '"]';
                    $tokens->next();
                    if ($tokens->is($stop)) {
                        $tokens->skip();
                        return $_str;
                    } else {
                        $_str .= '
."';
                    }
                } elseif (
$t === T_CURLY_OPEN) {
                    if (strlen(
$_str) > 1) {
                        
$_str .= '".';
                    } else {
                        $_str = "";
                    }
                    $tokens->getNext(T_VARIABLE);
                    $_str .= '
(' . $this->parseExpr($tokens) . ')';
                    if ($tokens->is($stop)) {
                        $tokens->next();
                        return $_str;
                    } else {
                        $_str .= '
."';
                    }
                } elseif (
$t === "}") {
                    
$tokens->next();
                } elseif (
$t === $stop) {
                    
$tokens->next();
                    return 
$_str . '"';
                } else {

                    break;
                }
            }
            throw new UnexpectedTokenException($tokens);
        } elseif ($tokens->is(T_CONSTANT_ENCAPSED_STRING)) {
            return $tokens->getAndNext();
        } elseif ($tokens->is(T_ENCAPSED_AND_WHITESPACE)) {
            throw new UnexpectedTokenException($tokens);
        } else {
            return "";
        }
    }

    /**
     * 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("|")) {
            $modifier = $tokens->getNext(Tokenizer::MACRO_STRING);
            if ($tokens->isNext(T_DOUBLE_COLON, T_NS_SEPARATOR)) {
                $mods = $this->parseStatic($tokens);
            } else {
                $mods = $this->_fenom->getModifier($modifier, $this);
                if (!$mods) {
                    throw new Exception("Modifier " . $tokens->current() . " not found");
                }
                $tokens->next();
            }

            $args = array();
            while ($tokens->is(":")) {
                if (!$args[] = $this->parseTerm($tokens->next())) {
                    throw new UnexpectedTokenException($tokens);
                }
            }

            if (!is_string($mods)) { // dynamic modifier
                $mods = '
call_user_func($tpl->getStorage()->getModifier("' . $modifier . '"), ';
            } else {
                $mods .= "(";
            }
            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
     * @param int $count amount of elements
     * @throws ErrorUnexpectedTokenException
     * @return string
     */
    public function parseArray(Tokenizer $tokens, &$count = 0)
    {
        if ($tokens->is("[")) {
            $arr = array();
            $tokens->next();
            while ($tokens->valid()) {
                if ($tokens->is('
]')) {
                    $tokens->next();
                    return '
array(' . implode('', $arr) . ')';
                }
                if ($tokens->is('
[')) {
                    $arr[] = $this->parseArray($tokens);
                    $count++;
                } else {
                    $expr = $this->parseExpr($tokens);
                    if($tokens->is(T_DOUBLE_ARROW)) {
                        $tokens->next();
                        $arr[] = $expr.' 
=> '.$this->parseExpr($tokens);
                    } else {
                        $arr[] = $expr;
                    }
                    $count++;
                }
                if($tokens->is('
,')) {
                    $tokens->next();
                }
            }
        }
        throw new UnexpectedTokenException($tokens);
    }

    /**
     * @param Tokenizer $tokens
     * @param $name
     * @return string
     * @throws InvalidUsageException
     */
    public function parseMacroCall(Tokenizer $tokens, $name)
    {
        $recursive = false;
        $macro     = false;
        if (isset($this->macros[$name])) {
            $macro     = $this->macros[$name];
            $recursive = $macro['
recursive'];
        } else {
            foreach ($this->_stack as $scope) {
                if ($scope->name == '
macro' && $scope['name'] == $name) { // invoke recursive
                    $recursive = $scope;
                    $macro     = $scope['
macro'];
                    break;
                }
            }
            if (!$macro) {
                throw new InvalidUsageException("Undefined macro '
$name'");
            }
        }
        $tokens->next();
        $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 InvalidUsageException("Macro '
$name' require '$arg' argument");
            }
        }
        if ($recursive) {
            if ($recursive instanceof Tag) {
                $recursive['
recursive'] = true;
            }
            return '
$tpl->getMacro("' . $name . '")->__invoke(' . Compiler::toArray($args) . '$tpl);';
        } else {
            $vars = $this->tmpVar();
            return $vars . ' 
$var$var ' . Compiler::toArray($args) . ';' . PHP_EOL . '?>' .
            $macro["body"] . '<?php' . PHP_EOL . '$var ' . $vars . '; unset(' . $vars . ');';
        }
    }

    /**
     * @param Tokenizer $tokens
     * @throws LogicException
     * @throws RuntimeException
     * @return string
     */
    public function parseStatic(Tokenizer $tokens)
    {
        if ($this->_options & Fenom::DENY_STATICS) {
            throw new LogicException("Static methods are disabled");
        }
        $tokens->skipIf(T_NS_SEPARATOR);
        $name = "";
        if ($tokens->is(T_STRING)) {
            $name .= $tokens->getAndNext();
            while ($tokens->is(T_NS_SEPARATOR)) {
                $name .= '
\' . $tokens->next()->get(T_STRING);
                $tokens->next();
            }
        }
        $tokens->need(T_DOUBLE_COLON)->next()->need(T_STRING);
        $static = $name . "::" . $tokens->getAndNext();
        if (!is_callable($static)) {
            throw new RuntimeException("Method $static doesn'
t exist");
        }
        return 
$static;
    }

    /**
     * Parse argument list
     * (1 + 2.3, 'string', 
$var, [2,4])
     *
     * @param Tokenizer 
$tokens
     * @throws TokenizeException
     * @return string
     */
    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->parseExpr($tokens);
                $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");
    }

    /**
     * Parse first unnamed argument
     *
     * @param Tokenizer $tokens
     * @param string $static
     * @return mixed|string
     */
    public function parsePlainArg(Tokenizer $tokens, &$static)
    {
        if ($tokens->is(T_CONSTANT_ENCAPSED_STRING)) {
            if ($tokens->isNext('
|')) {
                return $this->parseExpr($tokens);
            } else {
                $str    = $tokens->getAndNext();
                $static = stripslashes(substr($str, 1, -1));
                return $str;
            }
        } else {
            return $this->parseExpr($tokens);
        }
    }


    /**
     * Parse parameters as $key=$value
     * param1=$var param2=3 ...
     *
     * @static
     * @param Tokenizer $tokens
     * @param array $defaults
     * @throws Exception
     * @return array
     */
    public function parseParams(Tokenizer $tokens, array $defaults = null)
    {
        $params = array();
        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->parseExpr($tokens);
                } else {
                    $params[$key] = '
true';
                }
            } elseif ($tokens->is(Tokenizer::MACRO_SCALAR, '"', T_VARIABLE, "
[", '(')) {
                
$params[] = $this->parseExpr($tokens);
            } else {
                break;
            }
        }
        if (
$defaults) {
            
$params += $defaults;
        }

        return 
$params;
    }
}
Онлайн: 1
Реклама