mirror of
https://github.com/fenom-template/fenom.git
synced 2023-08-10 21:13:07 +03:00
Small refactoring
This commit is contained in:
parent
454fff1a08
commit
bd056bf75b
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -20,6 +20,8 @@ Tag {macro} [RU]
|
||||
{macro.plus x=$num y=100}
|
||||
```
|
||||
|
||||
На данный момент рекурсивный вызов макроса не поддерживается.
|
||||
|
||||
### {import}
|
||||
|
||||
Для использования маросов в другом шаблоне необходимо их импортировать при помощи тега `{import}`
|
||||
|
@ -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 ];
|
||||
|
@ -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();";
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ class Scope extends \ArrayObject {
|
||||
*/
|
||||
public $tpl;
|
||||
public $is_compiler = true;
|
||||
public $is_closed = false;
|
||||
private $_action;
|
||||
private $_body;
|
||||
private $_offset;
|
||||
|
@ -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('(')) {
|
||||
|
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user