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);
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);
$template = $aspect->fetch('echo/smarty.tpl', $data);

View File

@ -38,7 +38,7 @@ $template = $twig->loadTemplate('foreach/twig.tpl');
$template->render($data);
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);
$template = $aspect->fetch('foreach/smarty.tpl', $data);

View File

@ -49,7 +49,7 @@ $template = $twig->loadTemplate('inheritance/twig/b100.tpl');
$template->render($data);
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);
$template = $aspect->fetch('inheritance/smarty/b100.tpl', $data);

View File

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

View File

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

View File

@ -98,7 +98,7 @@ class Compiler {
$key = null;
$before = $body = array();
if($tokens->is(T_VARIABLE)) {
$from = $scope->tpl->parseVar($tokens, Template::DENY_MODS);
$from = $scope->tpl->parseVariable($tokens, Template::DENY_MODS);
$prepend = "";
} elseif($tokens->is('[')) {
$from = $scope->tpl->parseArray($tokens);
@ -110,11 +110,11 @@ class Compiler {
}
$tokens->get(T_AS);
$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)) {
$tokens->next();
$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();
@ -127,7 +127,7 @@ class Compiler {
}
$tokens->getNext("=");
$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"]) {
@ -195,7 +195,7 @@ class Compiler {
$scope["after"] = $before = $body = array();
$i = array('', '');
$c = "";
$var = $scope->tpl->parseVar($tokens, Template::DENY_MODS);
$var = $scope->tpl->parseVariable($tokens, Template::DENY_MODS);
$tokens->get("=");
$tokens->next();
$val = $scope->tpl->parseExp($tokens, true);
@ -382,7 +382,7 @@ class Compiler {
return "";
} else { // dynamic extends
$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).')';
}
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 ...}
*
@ -624,9 +633,9 @@ class Compiler {
* @param Template $tpl
* @return string
*/
public static function assign(Tokenizer $tokens, Template $tpl) {
return self::setVar($tokens, $tpl).';';
}
//public static function assign(Tokenizer $tokens, Template $tpl) {
// return self::setVar($tokens, $tpl).';';
//}
/**
* Set variable expression
@ -636,7 +645,7 @@ class Compiler {
* @return string
*/
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->next();
@ -668,7 +677,7 @@ class Compiler {
$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();";
}

View File

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

View File

@ -21,6 +21,8 @@ class Template extends Render {
const DENY_ARRAY = 1;
const DENY_MODS = 2;
const EXTENDED = 0x1000;
/**
* @var int shared counter
*/
@ -190,9 +192,9 @@ class Template extends Render {
if(!$_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);
if($this->_post) {
@ -202,6 +204,10 @@ class Template extends Render {
}
}
/**
* Generate temporary internal template variable
* @return string
*/
public function tmpVar() {
return '$t'.($this->i++);
}
@ -418,8 +424,11 @@ class Template extends Render {
switch($act["type"]) {
case Aspect::BLOCK_COMPILER:
$scope = new Scope($action, $this, $this->_line, $act, count($this->_stack), $this->_body);
array_push($this->_stack, $scope);
return $scope->open($tokens);
$code = $scope->open($tokens);
if(!$scope->is_closed) {
array_push($this->_stack, $scope);
}
return $code;
case Aspect::INLINE_COMPILER:
return call_user_func($act["parser"], $tokens, $this);
case Aspect::INLINE_FUNCTION:
@ -467,8 +476,9 @@ class Template extends Render {
$_exp .= $this->parseScalar($tokens, true);
$term = 1;
} elseif(!$term && $tokens->is(T_VARIABLE)) {
$pp = $tokens->isPrev(Tokenizer::MACRO_INCDEC);
$_exp .= $this->parseVar($tokens, 0, $only_var);
$_exp .= $this->parseVariable($tokens, 0, $only_var);
if($only_var && !$pp) {
$term = 2;
} else {
@ -562,29 +572,15 @@ class Template extends Render {
}
/**
* 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) {
public function parseVar(Tokenizer $tokens, $options = 0) {
$var = $tokens->get(T_VARIABLE);
$pure_var = true;
$_var = '$tpl["'.ltrim($var,'$').'"]';
$_var = '$tpl["'.substr($var, 1).'"]';
$tokens->next();
while($t = $tokens->key()) {
if($t === "." && !($deny & self::DENY_ARRAY)) {
if($t === "." && !($options & self::DENY_ARRAY)) {
$key = $tokens->getNext();
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)) {
if($tokens->isNext("(")) {
$key = "[".$this->parseExp($tokens)."]";
@ -598,7 +594,7 @@ class Template extends Render {
break;
}
$_var .= $key;
} elseif($t === "[" && !($deny & self::DENY_ARRAY)) {
} elseif($t === "[" && !($options & self::DENY_ARRAY)) {
$tokens->next();
if($tokens->is(Tokenizer::MACRO_STRING)) {
if($tokens->isNext("(")) {
@ -613,7 +609,33 @@ class Template extends Render {
$tokens->get("]");
$tokens->next();
$_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;
return $this->parseModifier($tokens, $_var);
} elseif($t === T_OBJECT_OPERATOR) {
@ -629,41 +651,9 @@ class Template extends Render {
$tokens->next();
$_var .= '->'.$prop;
}
} elseif($t === T_DNUMBER) {
$_var .= '['.substr($tokens->getAndNext(), 1).']';
} elseif($t === "?" || $t === "!") {
$pure_var = false;
$empty = ($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.')';
return $this->parseTernary($tokens, $_var, $t);
} else {
break;
}
@ -671,6 +661,36 @@ class Template extends Render {
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
*
@ -825,7 +845,7 @@ class Template extends Render {
$args[] = $token;
$tokens->next();
} 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)) {
$args[] = $this->parseSubstr($tokens);
} 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: {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: {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
*/
public function testExpressions($code, $vars, $result) {