Small refactoring

This commit is contained in:
bzick 2013-03-17 14:37:23 +04:00
parent 454fff1a08
commit bd056bf75b
9 changed files with 128 additions and 125 deletions

View File

@ -53,7 +53,7 @@ $template = $twig->loadTemplate('echo/twig.tpl');
$template->render($data); $template->render($data);
var_dump("Twig cached: ".(microtime(true)-$start)); var_dump("Twig cached: ".(microtime(true)-$start));
$aspect = Aspect::factory(__DIR__, __DIR__."/../compile/", Aspect::CHECK_MTIME); $aspect = Aspect::factory(__DIR__, __DIR__."/../compile/", Aspect::AUTO_RELOAD);
$start = microtime(true); $start = microtime(true);
$template = $aspect->fetch('echo/smarty.tpl', $data); $template = $aspect->fetch('echo/smarty.tpl', $data);

View File

@ -38,7 +38,7 @@ $template = $twig->loadTemplate('foreach/twig.tpl');
$template->render($data); $template->render($data);
var_dump("Twig cached: ".(microtime(true)-$start)); var_dump("Twig cached: ".(microtime(true)-$start));
$aspect = Aspect::factory(__DIR__, __DIR__."/../compile/", Aspect::CHECK_MTIME | Aspect::INCLUDE_SOURCES); $aspect = Aspect::factory(__DIR__, __DIR__."/../compile/", Aspect::AUTO_RELOAD | Aspect::INCLUDE_SOURCES);
$start = microtime(true); $start = microtime(true);
$template = $aspect->fetch('foreach/smarty.tpl', $data); $template = $aspect->fetch('foreach/smarty.tpl', $data);

View File

@ -49,7 +49,7 @@ $template = $twig->loadTemplate('inheritance/twig/b100.tpl');
$template->render($data); $template->render($data);
var_dump("Twig cached: ".(microtime(true)-$start)); var_dump("Twig cached: ".(microtime(true)-$start));
$aspect = Aspect::factory(__DIR__, __DIR__."/../compile/", Aspect::CHECK_MTIME); $aspect = Aspect::factory(__DIR__, __DIR__."/../compile/", Aspect::AUTO_RELOAD);
$start = microtime(true); $start = microtime(true);
$template = $aspect->fetch('inheritance/smarty/b100.tpl', $data); $template = $aspect->fetch('inheritance/smarty/b100.tpl', $data);

View File

@ -20,6 +20,8 @@ Tag {macro} [RU]
{macro.plus x=$num y=100} {macro.plus x=$num y=100}
``` ```
На данный момент рекурсивный вызов макроса не поддерживается.
### {import} ### {import}
Для использования маросов в другом шаблоне необходимо их импортировать при помощи тега `{import}` Для использования маросов в другом шаблоне необходимо их импортировать при помощи тега `{import}`

View File

@ -6,27 +6,27 @@ use Aspect\Template,
* Aspect Template Engine * Aspect Template Engine
*/ */
class Aspect { class Aspect {
const VERSION = 1.0; const VERSION = '1.0.1';
const INLINE_COMPILER = 1; const INLINE_COMPILER = 1;
const BLOCK_COMPILER = 2; const BLOCK_COMPILER = 2;
const INLINE_FUNCTION = 3; const INLINE_FUNCTION = 3;
const BLOCK_FUNCTION = 4; const BLOCK_FUNCTION = 4;
const MODIFIER = 5; const MODIFIER = 5;
const DENY_METHODS = 0x10; const DENY_METHODS = 0x10;
const DENY_INLINE_FUNCS = 0x20; const DENY_INLINE_FUNCS = 0x20;
const FORCE_INCLUDE = 0x40; const FORCE_INCLUDE = 0x40;
const CHECK_MTIME = 0x80; const AUTO_RELOAD = 0x80;
const FORCE_COMPILE = 0xF0; const FORCE_COMPILE = 0xF0;
const DISABLE_CACHE = 0x1F0; const DISABLE_CACHE = 0x1F0;
const DEFAULT_CLOSE_COMPILER = 'Aspect\Compiler::stdClose'; const DEFAULT_CLOSE_COMPILER = 'Aspect\Compiler::stdClose';
const DEFAULT_FUNC_PARSER = 'Aspect\Compiler::stdFuncParser'; const DEFAULT_FUNC_PARSER = 'Aspect\Compiler::stdFuncParser';
const DEFAULT_FUNC_OPEN = 'Aspect\Compiler::stdFuncOpen'; const DEFAULT_FUNC_OPEN = 'Aspect\Compiler::stdFuncOpen';
const DEFAULT_FUNC_CLOSE = 'Aspect\Compiler::stdFuncClose'; const DEFAULT_FUNC_CLOSE = 'Aspect\Compiler::stdFuncClose';
const SMART_FUNC_PARSER = 'Aspect\Compiler::smartFuncParser'; const SMART_FUNC_PARSER = 'Aspect\Compiler::smartFuncParser';
/** /**
* @var array of possible options, as associative array * @var array of possible options, as associative array
@ -35,43 +35,12 @@ class Aspect {
private static $_option_list = array( private static $_option_list = array(
"disable_methods" => self::DENY_METHODS, "disable_methods" => self::DENY_METHODS,
"disable_native_funcs" => self::DENY_INLINE_FUNCS, "disable_native_funcs" => self::DENY_INLINE_FUNCS,
"disable_cache" => self::DISABLE_CACHE,
"force_compile" => self::FORCE_COMPILE, "force_compile" => self::FORCE_COMPILE,
"compile_check" => self::CHECK_MTIME, "auto_reload" => self::AUTO_RELOAD,
"force_include" => self::FORCE_INCLUDE, "force_include" => self::FORCE_INCLUDE,
); );
/**
* Default options for functions
* @var array
*/
private static $_actions_defaults = array(
self::BLOCK_FUNCTION => array(
'type' => self::BLOCK_FUNCTION,
'open' => self::DEFAULT_FUNC_OPEN,
'close' => self::DEFAULT_FUNC_CLOSE,
'function' => null,
),
self::INLINE_FUNCTION => array(
'type' => self::INLINE_FUNCTION,
'parser' => self::DEFAULT_FUNC_PARSER,
'function' => null,
),
self::INLINE_COMPILER => array(
'type' => self::INLINE_COMPILER,
'open' => null,
'close' => self::DEFAULT_CLOSE_COMPILER,
'tags' => array(),
'float_tags' => array()
),
self::BLOCK_COMPILER => array(
'type' => self::BLOCK_COMPILER,
'open' => null,
'close' => null,
'tags' => array(),
'float_tags' => array()
)
);
/** /**
* @var array Templates storage * @var array Templates storage
*/ */
@ -187,8 +156,9 @@ class Aspect {
'parser' => 'Aspect\Compiler::tagInclude' 'parser' => 'Aspect\Compiler::tagInclude'
), ),
'var' => array( // {var ...} 'var' => array( // {var ...}
'type' => self::INLINE_COMPILER, 'type' => self::BLOCK_COMPILER,
'parser' => 'Aspect\Compiler::assign' 'open' => 'Aspect\Compiler::varOpen',
'close' => 'Aspect\Compiler::varClose'
), ),
'block' => array( // {block ...} {parent} {/block} 'block' => array( // {block ...} {parent} {/block}
'type' => self::BLOCK_COMPILER, 'type' => self::BLOCK_COMPILER,
@ -597,7 +567,7 @@ class Aspect {
if(isset($this->_storage[ $template ])) { if(isset($this->_storage[ $template ])) {
/** @var Aspect\Template $tpl */ /** @var Aspect\Template $tpl */
$tpl = $this->_storage[ $template ]; $tpl = $this->_storage[ $template ];
if(($this->_options & self::CHECK_MTIME) && !$tpl->isValid()) { if(($this->_options & self::AUTO_RELOAD) && !$tpl->isValid()) {
return $this->_storage[ $template ] = $this->compile($template); return $this->_storage[ $template ] = $this->compile($template);
} else { } else {
return $this->_storage[ $template ]; return $this->_storage[ $template ];

View File

@ -98,7 +98,7 @@ class Compiler {
$key = null; $key = null;
$before = $body = array(); $before = $body = array();
if($tokens->is(T_VARIABLE)) { if($tokens->is(T_VARIABLE)) {
$from = $scope->tpl->parseVar($tokens, Template::DENY_MODS); $from = $scope->tpl->parseVariable($tokens, Template::DENY_MODS);
$prepend = ""; $prepend = "";
} elseif($tokens->is('[')) { } elseif($tokens->is('[')) {
$from = $scope->tpl->parseArray($tokens); $from = $scope->tpl->parseArray($tokens);
@ -110,11 +110,11 @@ class Compiler {
} }
$tokens->get(T_AS); $tokens->get(T_AS);
$tokens->next(); $tokens->next();
$value = $scope->tpl->parseVar($tokens, Template::DENY_MODS | Template::DENY_ARRAY); $value = $scope->tpl->parseVariable($tokens, Template::DENY_MODS | Template::DENY_ARRAY);
if($tokens->is(T_DOUBLE_ARROW)) { if($tokens->is(T_DOUBLE_ARROW)) {
$tokens->next(); $tokens->next();
$key = $value; $key = $value;
$value = $scope->tpl->parseVar($tokens, Template::DENY_MODS | Template::DENY_ARRAY); $value = $scope->tpl->parseVariable($tokens, Template::DENY_MODS | Template::DENY_ARRAY);
} }
$scope["after"] = array(); $scope["after"] = array();
@ -127,7 +127,7 @@ class Compiler {
} }
$tokens->getNext("="); $tokens->getNext("=");
$tokens->next(); $tokens->next();
$p[ $param ] = $scope->tpl->parseVar($tokens, Template::DENY_MODS | Template::DENY_ARRAY); $p[ $param ] = $scope->tpl->parseVariable($tokens, Template::DENY_MODS | Template::DENY_ARRAY);
} }
if($p["index"]) { if($p["index"]) {
@ -195,7 +195,7 @@ class Compiler {
$scope["after"] = $before = $body = array(); $scope["after"] = $before = $body = array();
$i = array('', ''); $i = array('', '');
$c = ""; $c = "";
$var = $scope->tpl->parseVar($tokens, Template::DENY_MODS); $var = $scope->tpl->parseVariable($tokens, Template::DENY_MODS);
$tokens->get("="); $tokens->get("=");
$tokens->next(); $tokens->next();
$val = $scope->tpl->parseExp($tokens, true); $val = $scope->tpl->parseExp($tokens, true);
@ -382,7 +382,7 @@ class Compiler {
return ""; return "";
} else { // dynamic extends } else { // dynamic extends
$tpl->_extends = $tpl_name; $tpl->_extends = $tpl_name;
return '$parent = $tpl->getStorage()->getTemplate("extend:".'.$tpl_name.');'; return '$parent = $tpl->getStorage()->getTemplate('.$tpl_name.', \Aspect\Template::EXTENDED);';
} }
} }
@ -616,6 +616,15 @@ class Compiler {
return 'array('.implode(",", $_code).')'; return 'array('.implode(",", $_code).')';
} }
public static function varOpen(Tokenizer $tokens, Scope $scope) {
$scope->is_closed = true;
return self::setVar($tokens, $scope->tpl).';';
}
public static function varClose() {
return '';
}
/** /**
* Tag {var ...} * Tag {var ...}
* *
@ -624,9 +633,9 @@ class Compiler {
* @param Template $tpl * @param Template $tpl
* @return string * @return string
*/ */
public static function assign(Tokenizer $tokens, Template $tpl) { //public static function assign(Tokenizer $tokens, Template $tpl) {
return self::setVar($tokens, $tpl).';'; // return self::setVar($tokens, $tpl).';';
} //}
/** /**
* Set variable expression * Set variable expression
@ -636,7 +645,7 @@ class Compiler {
* @return string * @return string
*/ */
public static function setVar(Tokenizer $tokens, Template $tpl, $allow_array = true) { public static function setVar(Tokenizer $tokens, Template $tpl, $allow_array = true) {
$var = $tpl->parseVar($tokens, $tpl::DENY_MODS); $var = $tpl->parseVariable($tokens, $tpl::DENY_MODS);
$tokens->get('='); $tokens->get('=');
$tokens->next(); $tokens->next();
@ -668,7 +677,7 @@ class Compiler {
$scope["value"] = "ob_get_clean()"; $scope["value"] = "ob_get_clean()";
} }
$scope["var"] = $scope->tpl->parseVar($tokens, Template::DENY_MODS); $scope["var"] = $scope->tpl->parseVariable($tokens, Template::DENY_MODS);
return "ob_start();"; return "ob_start();";
} }

View File

@ -14,6 +14,7 @@ class Scope extends \ArrayObject {
*/ */
public $tpl; public $tpl;
public $is_compiler = true; public $is_compiler = true;
public $is_closed = false;
private $_action; private $_action;
private $_body; private $_body;
private $_offset; private $_offset;

View File

@ -21,6 +21,8 @@ class Template extends Render {
const DENY_ARRAY = 1; const DENY_ARRAY = 1;
const DENY_MODS = 2; const DENY_MODS = 2;
const EXTENDED = 0x1000;
/** /**
* @var int shared counter * @var int shared counter
*/ */
@ -190,9 +192,9 @@ class Template extends Render {
if(!$_line) { if(!$_line) {
$_line = $scope->line; $_line = $scope->line;
} }
$_names[] = $scope->name.' defined on line '.$scope->line; $_names[] = '{'.$scope->name.'} defined on line '.$scope->line;
} }
throw new CompileException("Unclosed block tags: ".implode(", ", $_names), 0, 1, $this->_name, $_line); throw new CompileException("Unclosed tag(s): ".implode(", ", $_names), 0, 1, $this->_name, $_line);
} }
unset($this->_src); unset($this->_src);
if($this->_post) { if($this->_post) {
@ -202,6 +204,10 @@ class Template extends Render {
} }
} }
/**
* Generate temporary internal template variable
* @return string
*/
public function tmpVar() { public function tmpVar() {
return '$t'.($this->i++); return '$t'.($this->i++);
} }
@ -418,8 +424,11 @@ class Template extends Render {
switch($act["type"]) { switch($act["type"]) {
case Aspect::BLOCK_COMPILER: case Aspect::BLOCK_COMPILER:
$scope = new Scope($action, $this, $this->_line, $act, count($this->_stack), $this->_body); $scope = new Scope($action, $this, $this->_line, $act, count($this->_stack), $this->_body);
array_push($this->_stack, $scope); $code = $scope->open($tokens);
return $scope->open($tokens); if(!$scope->is_closed) {
array_push($this->_stack, $scope);
}
return $code;
case Aspect::INLINE_COMPILER: case Aspect::INLINE_COMPILER:
return call_user_func($act["parser"], $tokens, $this); return call_user_func($act["parser"], $tokens, $this);
case Aspect::INLINE_FUNCTION: case Aspect::INLINE_FUNCTION:
@ -467,8 +476,9 @@ class Template extends Render {
$_exp .= $this->parseScalar($tokens, true); $_exp .= $this->parseScalar($tokens, true);
$term = 1; $term = 1;
} elseif(!$term && $tokens->is(T_VARIABLE)) { } elseif(!$term && $tokens->is(T_VARIABLE)) {
$pp = $tokens->isPrev(Tokenizer::MACRO_INCDEC); $pp = $tokens->isPrev(Tokenizer::MACRO_INCDEC);
$_exp .= $this->parseVar($tokens, 0, $only_var); $_exp .= $this->parseVariable($tokens, 0, $only_var);
if($only_var && !$pp) { if($only_var && !$pp) {
$term = 2; $term = 2;
} else { } else {
@ -562,29 +572,15 @@ class Template extends Render {
} }
/** public function parseVar(Tokenizer $tokens, $options = 0) {
* Parse variable
* $var.foo[bar]["a"][1+3/$var]|mod:3:"w":$var3|mod3
*
* @see parseModifier
* @static
* @param Tokenizer $tokens
* @param int $deny
* @param bool $pure_var
* @throws \LogicException
* @throws UnexpectedTokenException
* @return string
*/
public function parseVar(Tokenizer $tokens, $deny = 0, &$pure_var = true) {
$var = $tokens->get(T_VARIABLE); $var = $tokens->get(T_VARIABLE);
$pure_var = true; $_var = '$tpl["'.substr($var, 1).'"]';
$_var = '$tpl["'.ltrim($var,'$').'"]';
$tokens->next(); $tokens->next();
while($t = $tokens->key()) { while($t = $tokens->key()) {
if($t === "." && !($deny & self::DENY_ARRAY)) { if($t === "." && !($options & self::DENY_ARRAY)) {
$key = $tokens->getNext(); $key = $tokens->getNext();
if($tokens->is(T_VARIABLE)) { if($tokens->is(T_VARIABLE)) {
$key = "[ ".$this->parseVar($tokens, self::DENY_ARRAY)." ]"; $key = "[ ".$this->parseVariable($tokens, self::DENY_ARRAY)." ]";
} elseif($tokens->is(Tokenizer::MACRO_STRING)) { } elseif($tokens->is(Tokenizer::MACRO_STRING)) {
if($tokens->isNext("(")) { if($tokens->isNext("(")) {
$key = "[".$this->parseExp($tokens)."]"; $key = "[".$this->parseExp($tokens)."]";
@ -598,7 +594,7 @@ class Template extends Render {
break; break;
} }
$_var .= $key; $_var .= $key;
} elseif($t === "[" && !($deny & self::DENY_ARRAY)) { } elseif($t === "[" && !($options & self::DENY_ARRAY)) {
$tokens->next(); $tokens->next();
if($tokens->is(Tokenizer::MACRO_STRING)) { if($tokens->is(Tokenizer::MACRO_STRING)) {
if($tokens->isNext("(")) { if($tokens->isNext("(")) {
@ -613,7 +609,33 @@ class Template extends Render {
$tokens->get("]"); $tokens->get("]");
$tokens->next(); $tokens->next();
$_var .= $key; $_var .= $key;
} elseif($t === "|" && !($deny & self::DENY_MODS)) { } elseif($t === T_DNUMBER) {
$_var .= '['.substr($tokens->getAndNext(), 1).']';
} else {
break;
}
}
return $_var;
}
/**
* Parse variable
* $var.foo[bar]["a"][1+3/$var]|mod:3:"w":$var3|mod3
*
* @see parseModifier
* @static
* @param Tokenizer $tokens
* @param int $deny set limitations
* @param bool $pure_var will be FALSE if variable modified
* @throws \LogicException
* @throws UnexpectedTokenException
* @return string
*/
public function parseVariable(Tokenizer $tokens, $deny = 0, &$pure_var = true) {
$_var = $this->parseVar($tokens, $deny);
$pure_var = true;
while($t = $tokens->key()) {
if($t === "|" && !($deny & self::DENY_MODS)) {
$pure_var = false; $pure_var = false;
return $this->parseModifier($tokens, $_var); return $this->parseModifier($tokens, $_var);
} elseif($t === T_OBJECT_OPERATOR) { } elseif($t === T_OBJECT_OPERATOR) {
@ -629,41 +651,9 @@ class Template extends Render {
$tokens->next(); $tokens->next();
$_var .= '->'.$prop; $_var .= '->'.$prop;
} }
} elseif($t === T_DNUMBER) {
$_var .= '['.substr($tokens->getAndNext(), 1).']';
} elseif($t === "?" || $t === "!") { } elseif($t === "?" || $t === "!") {
$pure_var = false; $pure_var = false;
$empty = ($t === "?"); return $this->parseTernary($tokens, $_var, $t);
$tokens->next();
if($tokens->is(":")) {
$tokens->next();
if($empty) {
return '(empty('.$_var.') ? ('.$this->parseExp($tokens, true).') : '.$_var.')';
} else {
return '(isset('.$_var.') ? '.$_var.' : ('.$this->parseExp($tokens, true).'))';
}
} elseif($tokens->is(Tokenizer::MACRO_BINARY, Tokenizer::MACRO_BOOLEAN, Tokenizer::MACRO_MATH) || !$tokens->valid()) {
if($empty) {
return '!empty('.$_var.')';
} else {
return 'isset('.$_var.')';
}
} else {
$expr1 = $this->parseExp($tokens, true);
if(!$tokens->is(":")) {
throw new UnexpectedTokenException($tokens, null, "ternary operator");
}
$expr2 = $this->parseExp($tokens, true);
if($empty) {
return '(empty('.$_var.') ? '.$expr2.' : '.$expr1.')';
} else {
return '(isset('.$_var.') ? '.$expr1.' : '.$expr2.')';
}
}
} elseif($t === "!") {
$pure_var = false;
$tokens->next();
return 'isset('.$_var.')';
} else { } else {
break; break;
} }
@ -671,6 +661,36 @@ class Template extends Render {
return $_var; return $_var;
} }
public function parseTernary(Tokenizer $tokens, $var, $type) {
$empty = ($type === "?");
$tokens->next();
if($tokens->is(":")) {
$tokens->next();
if($empty) {
return '(empty('.$var.') ? ('.$this->parseExp($tokens, true).') : '.$var.')';
} else {
return '(isset('.$var.') ? '.$var.' : ('.$this->parseExp($tokens, true).'))';
}
} elseif($tokens->is(Tokenizer::MACRO_BINARY, Tokenizer::MACRO_BOOLEAN, Tokenizer::MACRO_MATH) || !$tokens->valid()) {
if($empty) {
return '!empty('.$var.')';
} else {
return 'isset('.$var.')';
}
} else {
$expr1 = $this->parseExp($tokens, true);
if(!$tokens->is(":")) {
throw new UnexpectedTokenException($tokens, null, "ternary operator");
}
$expr2 = $this->parseExp($tokens, true);
if($empty) {
return '(empty('.$var.') ? '.$expr2.' : '.$expr1.')';
} else {
return '(isset('.$var.') ? '.$expr1.' : '.$expr2.')';
}
}
}
/** /**
* Parse scalar values * Parse scalar values
* *
@ -825,7 +845,7 @@ class Template extends Render {
$args[] = $token; $args[] = $token;
$tokens->next(); $tokens->next();
} elseif($tokens->is(T_VARIABLE)) { } elseif($tokens->is(T_VARIABLE)) {
$args[] = $this->parseVar($tokens, self::DENY_MODS); $args[] = $this->parseVariable($tokens, self::DENY_MODS);
} elseif($tokens->is('"', '`', T_ENCAPSED_AND_WHITESPACE)) { } elseif($tokens->is('"', '`', T_ENCAPSED_AND_WHITESPACE)) {
$args[] = $this->parseSubstr($tokens); $args[] = $this->parseSubstr($tokens);
} elseif($tokens->is('(')) { } elseif($tokens->is('(')) {

View File

@ -586,7 +586,7 @@ class TemplateTest extends TestCase {
array('Layers: {for $a=4 to=6} block1 {if 1} {/for} {/if} end', 'Aspect\CompileException', "Unexpected closing of the tag 'for'"), array('Layers: {for $a=4 to=6} block1 {if 1} {/for} {/if} end', 'Aspect\CompileException', "Unexpected closing of the tag 'for'"),
array('Layers: {switch 1} {if 1} {case 1} {/if} {/switch} end', 'Aspect\CompileException', "Unexpected tag 'case' (this tag can be used with 'switch')"), array('Layers: {switch 1} {if 1} {case 1} {/if} {/switch} end', 'Aspect\CompileException', "Unexpected tag 'case' (this tag can be used with 'switch')"),
array('Layers: {/switch} end', 'Aspect\CompileException', "Unexpected closing of the tag 'switch'"), array('Layers: {/switch} end', 'Aspect\CompileException', "Unexpected closing of the tag 'switch'"),
array('Layers: {if 1} end', 'Aspect\CompileException', "Unclosed block tags: if"), array('Layers: {if 1} end', 'Aspect\CompileException', "Unclosed tag(s): {if}"),
); );
} }
@ -630,6 +630,7 @@ class TemplateTest extends TestCase {
} }
/** /**
* @group expression
* @dataProvider providerExpressions * @dataProvider providerExpressions
*/ */
public function testExpressions($code, $vars, $result) { public function testExpressions($code, $vars, $result) {