fenom/src/Fenom/Template.php

1472 lines
49 KiB
PHP
Raw Normal View History

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-22 18:03:43 +04:00
* Escape outputs value
* @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
*/
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-08-23 00:55:53 +04:00
protected static $_tests = 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
}
/**
* Get tag stack size
* @return int
*/
2013-07-29 14:58:14 +04:00
public function getStackSize()
{
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;
$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);
$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) {
$this->compile();
}
2013-02-13 20:51:27 +04:00
return $this;
}
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;
$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-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
$this->_appendText(substr($this->_src, $pos, $start - $pos + 2));
$end = $start + 1;
break;
2013-07-22 18:03:43 +04:00
case "*": // comment block
$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);
}
$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
$this->_line += substr_count($comment, "\n"); // count lines in comments
2013-07-03 12:10:50 +04:00
unset($comment); // cleanup
break;
default:
2013-08-23 00:55:53 +04:00
// var_dump($this->_src[$pos]);
2013-08-23 01:06:47 +04:00
// if($this->_src[$pos] === "\n") {
// $pos++;
// }
$this->_appendText(substr($this->_src, $pos, $start - $pos));
$end = $start + 1;
do {
2013-07-25 02:05:44 +04:00
$need_more = false;
$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
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
$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;
} 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-25 02:05:44 +04:00
} while ($need_more);
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
}
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
}
$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
}
/**
* Append PHP code to template body
*
* @param string $code
* @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-08-29 11:29:34 +04:00
$this->_body .= "<?php\n/* {$this->_name}:{$this->_line}: {$source} */\n $code ?>";
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
/**
* 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
2013-08-11 19:55:30 +04:00
"return new Fenom\\Render(\$fenom, " . $this->_getClosureSource() . ", array(\n" .
"\t'options' => {$this->_options},\n" .
"\t'provider' => " . var_export($this->_scm, true) . ",\n" .
"\t'name' => " . var_export($this->_name, true) . ",\n" .
"\t'base_name' => " . var_export($this->_base_name, true) . ",\n" .
"\t'time' => {$this->_time},\n" .
"\t'depends' => " . var_export($this->_base_name, true) . ",\n" .
2013-08-22 00:03:20 +04:00
"\t'macros' => " . $this->_getMacrosArray() . ",\n
2013-08-05 13:07:16 +04:00
));\n";
2013-01-25 18:36:16 +04:00
}
2013-08-22 00:03:20 +04:00
/**
* Make array with macros code
* @return string
*/
private function _getMacrosArray()
{
if ($this->macros) {
$macros = array();
foreach ($this->macros as $m) {
if ($m["recursive"]) {
$macros[] = "\t\t'" . $m["name"] . "' => function (\$tpl) {\n?>" . $m["body"] . "<?php\n}";
}
}
return "array(\n" . implode(",\n", $macros).")";
} else {
return 'array()';
}
}
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-08-22 00:03:20 +04:00
eval("\$this->_code = " . $this->_getClosureSource() . ";\n\$this->_macros = ".$this->_getMacrosArray() .';');
2013-07-29 14:58:14 +04:00
if (!$this->_code) {
2013-01-25 18:36:16 +04:00
throw new CompileException("Fatal error while creating the template");
}
}
return parent::display($values);
}
/**
* Add depends from template
* @param Render $tpl
*/
2013-07-29 14:58:14 +04:00
public function addDepend(Render $tpl)
{
2013-08-23 00:55:53 +04:00
// var_dump($tpl->getScm(),"$tpl", (new \Exception())->getTraceAsString() );
$this->_depends[$tpl->getScm()][$tpl->getName()] = $tpl->getTime();
}
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) {
return "echo htmlspecialchars($data, ENT_COMPAT, 'UTF-8');";
2013-07-04 01:28:10 +04:00
} else {
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") {
$this->_ignore = true;
$tokens->next();
return '';
} else {
return $this->parseAct($tokens);
}
} elseif ($tokens->is('/')) {
2013-07-22 18:03:43 +04:00
return $this->parseEndTag($tokens);
} else {
2013-08-11 19:55:30 +04:00
return $this->out($this->parseExpr($tokens), $tokens);
2013-01-25 18:36:16 +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
* @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) {
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;
}
}
/**
* Get current scope
* @return Scope
*/
2013-07-29 14:58:14 +04:00
public function getLastScope()
{
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-08-11 19:55:30 +04:00
return $this->out($this->parseExpr($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-08-11 19:55:30 +04:00
return $this->out($this->parseExpr($tokens));
2013-07-22 18:03:43 +04:00
}
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-11 19:55:30 +04:00
* Parse expressions. The mix of operators and terms.
2013-01-25 18:36:16 +04:00
*
* @param Tokenizer $tokens
* @return string
2013-08-02 20:29:18 +04:00
* @throws Error\UnexpectedTokenException
2013-01-25 18:36:16 +04:00
*/
2013-08-11 19:55:30 +04:00
public function parseExpr(Tokenizer $tokens)
2013-08-02 21:50:04 +04:00
{
$exp = array();
$var = false; // last term was: true - variable, false - mixed
$op = false; // last exp was operator
2013-08-03 18:56:17 +04:00
$cond = false; // was comparison operator
2013-08-02 21:50:04 +04:00
while ($tokens->valid()) {
2013-08-02 20:29:18 +04:00
// parse term
2013-08-03 18:56:17 +04:00
$term = $this->parseTerm($tokens, $var); // term of the expression
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-03 18:56:17 +04:00
if ($tokens->is(Tokenizer::MACRO_BINARY)) { // binary operator: $a + $b, $a <= $b, ...
if ($tokens->is(Tokenizer::MACRO_COND)) { // comparison operator
2013-08-02 20:29:18 +04:00
if ($cond) {
2013-07-22 18:03:43 +04:00
break;
}
2013-08-02 20:29:18 +04:00
$cond = true;
2013-08-06 12:04:23 +04:00
} elseif ($tokens->is(Tokenizer::MACRO_BOOLEAN)) {
$cond = false;
2013-08-02 20:29:18 +04:00
}
$op = $tokens->getAndNext();
2013-08-03 18:56:17 +04:00
} elseif ($tokens->is(Tokenizer::MACRO_EQUALS)) { // assignment operator: $a = 4, $a += 3, ...
2013-08-02 21:50:04 +04:00
if (!$var) {
2013-08-02 20:29:18 +04:00
break;
}
$op = $tokens->getAndNext();
2013-08-03 18:56:17 +04:00
} elseif ($tokens->is(T_STRING)) { // test or containment operator: $a in $b, $a is set, ...
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-03 18:56:17 +04:00
} elseif ($tokens->is('~')) { // string concatenation operator: 'asd' ~ $var
$concat = array(array_pop($exp));
2013-08-11 19:55:30 +04:00
while ($tokens->is('~')) {
2013-08-03 18:56:17 +04:00
$tokens->next();
2013-08-11 19:55:30 +04:00
if ($tokens->is(T_LNUMBER, T_DNUMBER)) {
$concat[] = "strval(" . $this->parseTerm($tokens) . ")";
2013-08-03 18:56:17 +04:00
} else {
2013-08-11 19:55:30 +04:00
$concat[] = $this->parseTerm($tokens);
2013-08-03 18:56:17 +04:00
}
}
2013-08-11 19:55:30 +04:00
$exp[] = "(" . implode(".", $concat) . ")";
2013-08-02 20:29:18 +04:00
} else {
break;
}
2013-08-02 21:50:04 +04:00
if ($op) {
2013-08-02 20:29:18 +04:00
$exp[] = $op;
}
}
2013-08-11 19:55:30 +04:00
if ($op || !$exp) {
2013-08-02 20:29:18 +04:00
throw new UnexpectedTokenException($tokens);
}
return implode(' ', $exp);
}
/**
2013-08-11 19:55:30 +04:00
* Parse any term of expression: -2, ++$var, 'adf'|mod:4
2013-08-02 20:29:18 +04:00
*
* @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-11 19:55:30 +04:00
if ($tokens->is(Tokenizer::MACRO_UNARY)) {
$unary = $tokens->getAndNext();
} else {
$unary = "";
}
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('$')) {
$var = $this->parseAccessor($tokens, $is_var);
if ($tokens->is(Tokenizer::MACRO_INCDEC, "|", "!", "?")) {
return $unary . $this->parseVariable($tokens, 0, $var);
} else {
return $unary . $var;
}
} elseif ($tokens->is(Tokenizer::MACRO_INCDEC)) {
return $unary . $this->parseVariable($tokens);
} elseif ($tokens->is("(")) {
$tokens->next();
$exp = $unary . "(" . $this->parseExpr($tokens) . ")";
$tokens->need(")")->next();
return $exp;
} 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-11 19:55:30 +04:00
$func = $func . $this->parseArgs($tokens);
if ($tokens->is('|')) {
return $unary . $this->parseModifier($tokens, $func);
2013-08-03 18:56:17 +04:00
} else {
2013-08-11 19:55:30 +04:00
return $unary . $func;
2013-08-03 18:56:17 +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-11 19:55:30 +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-08-11 19:55:30 +04:00
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 simple variable (without modifier etc)
*
* @param Tokenizer $tokens
* @return string
*/
2013-08-11 19:55:30 +04:00
public function parseVar(Tokenizer $tokens)
2013-07-29 14:58:14 +04:00
{
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-08-11 19:55:30 +04:00
$_var = $this->_var($tokens, $_var);
if ($this->_options & Fenom::FORCE_VERIFY) {
return 'isset(' . $_var . ') ? ' . $_var . ' : null';
} else {
return $_var;
}
}
/**
* @param Tokenizer $tokens
* @param $var
* @return string
* @throws Error\UnexpectedTokenException
*/
protected function _var(Tokenizer $tokens, $var)
{
2013-07-29 14:58:14 +04:00
while ($t = $tokens->key()) {
2013-08-11 19:55:30 +04:00
if ($t === ".") {
$tokens->next();
2013-07-29 14:58:14 +04:00
if ($tokens->is(T_VARIABLE)) {
2013-08-11 19:55:30 +04:00
$key = '[ $tpl["' . substr($tokens->getAndNext(), 1) . '"] ]';
2013-07-29 14:58:14 +04:00
} elseif ($tokens->is(Tokenizer::MACRO_STRING)) {
2013-08-11 19:55:30 +04:00
$key = '["' . $tokens->getAndNext() . '"]';
} elseif ($tokens->is(Tokenizer::MACRO_SCALAR)) {
$key = "[" . $tokens->getAndNext() . "]";
} elseif ($tokens->is('"')) {
$key = "[" . $this->parseQuote($tokens) . "]";
2013-01-25 18:36:16 +04:00
} else {
2013-08-11 19:55:30 +04:00
throw new UnexpectedTokenException($tokens);
2013-01-25 18:36:16 +04:00
}
2013-08-11 19:55:30 +04:00
$var .= $key;
} elseif ($t === "[") {
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("(")) {
2013-08-11 19:55:30 +04:00
$key = "[" . $this->parseExpr($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-08-11 19:55:30 +04:00
$key = "[" . $this->parseExpr($tokens) . "]";
2013-01-25 18:36:16 +04:00
}
$tokens->get("]");
$tokens->next();
2013-08-11 19:55:30 +04:00
$var .= $key;
2013-07-29 14:58:14 +04:00
} elseif ($t === T_DNUMBER) {
2013-08-11 19:55:30 +04:00
$var .= '[' . substr($tokens->getAndNext(), 1) . ']';
2013-07-29 14:58:14 +04:00
} elseif ($t === T_OBJECT_OPERATOR) {
2013-08-11 19:55:30 +04:00
$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-08-11 19:55:30 +04:00
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-08-11 19:55:30 +04:00
/**
* Parse accessor
*/
public function parseAccessor(Tokenizer $tokens, &$is_var)
{
$is_var = false;
$vars = array(
2013-08-12 20:14:35 +04:00
'get' => '$_GET',
'post' => '$_POST',
2013-08-11 19:55:30 +04:00
'session' => '$_SESSION',
2013-08-12 20:14:35 +04:00
'cookie' => '$_COOKIE',
2013-08-11 19:55:30 +04:00
'request' => '$_REQUEST',
2013-08-12 20:14:35 +04:00
'files' => '$_FILES',
2013-08-11 19:55:30 +04:00
'globals' => '$GLOBALS',
2013-08-12 20:14:35 +04:00
'server' => '$_SERVER',
'env' => '$_ENV',
'tpl' => '$tpl->info'
2013-08-11 19:55:30 +04:00
);
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->_var($tokens, $vars[$key]);
}
switch ($key) {
case 'const':
$tokens->need('.')->next();
$var = $this->parseName($tokens);
if (!defined($var)) {
$var = 'constant(' . var_export($var, true) . ')';
}
break;
case 'version':
$var = '\Fenom::VERSION';
break;
default:
throw new UnexpectedTokenException($tokens);
}
if ($tokens->is('|')) {
return $this->parseModifier($tokens, $var);
} else {
return $var;
}
}
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) {
2013-08-11 19:55:30 +04:00
return '(empty(' . $var . ') ? (' . $this->parseExpr($tokens) . ') : ' . $var . ')';
2013-03-17 14:37:23 +04:00
} else {
2013-08-11 19:55:30 +04:00
return '(isset(' . $var . ') ? ' . $var . ' : (' . $this->parseExpr($tokens) . '))';
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 {
2013-08-11 19:55:30 +04:00
$expr1 = $this->parseExpr($tokens);
2013-08-02 23:01:06 +04:00
$tokens->need(':')->skip();
2013-08-11 19:55:30 +04:00
$expr2 = $this->parseExpr($tokens);
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-08-23 00:55:53 +04:00
* @see _tests
2013-07-22 18:03:43 +04:00
* @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-08-23 00:55:53 +04:00
return $invert . sprintf(self::$_tests[$action], $value);
} elseif (isset(self::$_tests[$action])) {
2013-07-22 18:03:43 +04:00
$tokens->next();
2013-08-23 00:55:53 +04:00
return $invert . sprintf(self::$_tests[$action], $value);
2013-07-29 14:58:14 +04:00
} 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('"', "`")) {
$stop = $tokens->current();
$_str = '"';
$tokens->next();
2013-07-29 14:58:14 +04:00
while ($t = $tokens->key()) {
if ($t === T_ENCAPSED_AND_WHITESPACE) {
$_str .= $tokens->current();
$tokens->next();
2013-07-29 14:58:14 +04:00
} elseif ($t === T_VARIABLE) {
if (strlen($_str) > 1) {
$_str .= '".';
} else {
$_str = "";
}
2013-07-29 14:58:14 +04:00
$_str .= '$tpl["' . substr($tokens->current(), 1) . '"]';
$tokens->next();
2013-07-29 14:58:14 +04:00
if ($tokens->is($stop)) {
$tokens->skip();
return $_str;
} else {
$_str .= '."';
}
2013-07-29 14:58:14 +04:00
} elseif ($t === T_CURLY_OPEN) {
if (strlen($_str) > 1) {
$_str .= '".';
} else {
$_str = "";
}
$tokens->getNext(T_VARIABLE);
2013-08-11 19:55:30 +04:00
$_str .= '(' . $this->parseExpr($tokens) . ')';
2013-07-29 14:58:14 +04:00
if ($tokens->is($stop)) {
2013-01-25 18:36:16 +04:00
$tokens->next();
return $_str;
2013-01-25 18:36:16 +04:00
} else {
$_str .= '."';
2013-01-25 18:36:16 +04:00
}
2013-07-29 14:58:14 +04:00
} elseif ($t === "}") {
$tokens->next();
2013-07-29 14:58:14 +04:00
} elseif ($t === $stop) {
$tokens->next();
2013-07-29 14:58:14 +04:00
return $_str . '"';
} else {
break;
2013-01-25 18:36:16 +04:00
}
}
throw new UnexpectedTokenException($tokens);
2013-07-29 14:58:14 +04:00
} elseif ($tokens->is(T_CONSTANT_ENCAPSED_STRING)) {
return $tokens->getAndNext();
2013-07-29 14:58:14 +04:00
} elseif ($tokens->is(T_ENCAPSED_AND_WHITESPACE)) {
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-08-29 11:29:34 +04:00
$token = $tokens->getNext(Tokenizer::MACRO_SCALAR, T_VARIABLE, '"', Tokenizer::MACRO_STRING, "(", "[", '$');
2013-01-25 18:36:16 +04:00
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-08-29 11:29:34 +04:00
} elseif ($tokens->is('$')) {
$args[] = $this->parseAccessor($tokens, $is_var);
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-08-11 19:55:30 +04:00
$args[] = $this->parseExpr($tokens);
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-08-11 19:55:30 +04:00
$_arr .= $this->parseExpr($tokens);
2013-01-25 18:36:16 +04:00
$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
/**
* @param Tokenizer $tokens
* @param $name
* @return string
* @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-08-05 13:07:16 +04:00
$n = $this->i++;
2013-07-29 14:58:14 +04:00
if ($recursive) {
2013-08-05 13:07:16 +04:00
$recursive['recursive'] = true;
2013-08-11 19:55:30 +04:00
$body = '$tpl->getMacro("' . $name . '")->__invoke($tpl);';
2013-02-23 02:03:05 +04:00
} else {
2013-08-11 19:55:30 +04:00
$body = '?>' . $macro["body"] . '<?php';
2013-01-25 18:36:16 +04:00
}
2013-08-11 19:55:30 +04:00
return '$_tpl' . $n . ' = $tpl->exchangeArray(' . Compiler::toArray($args) . ');' . PHP_EOL . $body . PHP_EOL . '$tpl->exchangeArray($_tpl' . $n . '); unset($_tpl' . $n . ');';
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-08-11 19:55:30 +04:00
$_args .= $this->parseExpr($tokens);
2013-01-28 16:34:34 +04:00
$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-08-11 19:55:30 +04:00
return $this->parseExpr($tokens);
2013-02-20 18:03:53 +04:00
} 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 {
2013-08-11 19:55:30 +04:00
return $this->parseExpr($tokens);
2013-02-13 20:51:27 +04:00
}
}
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-08-11 19:55:30 +04:00
$params[$key] = $this->parseExpr($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-08-11 19:55:30 +04:00
$params[] = $this->parseExpr($tokens);
2013-01-25 18:36:16 +04:00
} 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
}