mirror of
https://github.com/fenom-template/fenom.git
synced 2023-08-10 21:13:07 +03:00
Update to 1.1
Read CHANGELOG.md#1.1.0
This commit is contained in:
@ -16,7 +16,7 @@ use Fenom\Template,
|
||||
* @author Ivan Shalganov <a.cobest@gmail.com>
|
||||
*/
|
||||
class Fenom {
|
||||
const VERSION = '1.0';
|
||||
const VERSION = '1.1';
|
||||
|
||||
/* Actions */
|
||||
const INLINE_COMPILER = 1;
|
||||
@ -26,16 +26,16 @@ class Fenom {
|
||||
const MODIFIER = 5;
|
||||
|
||||
/* Options */
|
||||
const DENY_METHODS = 0x10;
|
||||
const DENY_INLINE_FUNCS = 0x20;
|
||||
const FORCE_INCLUDE = 0x40;
|
||||
const AUTO_RELOAD = 0x80;
|
||||
const FORCE_COMPILE = 0xF0;
|
||||
const DISABLE_CACHE = 0x1F0;
|
||||
const AUTO_ESCAPE = 0x200;
|
||||
const FORCE_VERIFY = 0x400;
|
||||
const AUTO_TRIM = 0x800;
|
||||
const DENY_STATIC_METHODS = 0xF00;
|
||||
const DENY_METHODS = 0x10;
|
||||
const DENY_INLINE_FUNCS = 0x20;
|
||||
const FORCE_INCLUDE = 0x40;
|
||||
const AUTO_RELOAD = 0x80;
|
||||
const FORCE_COMPILE = 0x100;
|
||||
const AUTO_ESCAPE = 0x200;
|
||||
const DISABLE_CACHE = 0x400;
|
||||
const FORCE_VERIFY = 0x800; // reserved
|
||||
const AUTO_TRIM = 0x1000; // reserved
|
||||
const DENY_STATIC_METHODS = 0x2000; // reserved
|
||||
|
||||
/* Default parsers */
|
||||
const DEFAULT_CLOSE_COMPILER = 'Fenom\Compiler::stdClose';
|
||||
@ -49,14 +49,14 @@ class Fenom {
|
||||
* @see setOptions
|
||||
*/
|
||||
private static $_option_list = array(
|
||||
"disable_methods" => self::DENY_METHODS,
|
||||
"disable_methods" => self::DENY_METHODS,
|
||||
"disable_native_funcs" => self::DENY_INLINE_FUNCS,
|
||||
"disable_cache" => self::DISABLE_CACHE,
|
||||
"force_compile" => self::FORCE_COMPILE,
|
||||
"auto_reload" => self::AUTO_RELOAD,
|
||||
"force_include" => self::FORCE_INCLUDE,
|
||||
"auto_escape" => self::AUTO_ESCAPE,
|
||||
"force_verify" => self::FORCE_VERIFY
|
||||
"disable_cache" => self::DISABLE_CACHE,
|
||||
"force_compile" => self::FORCE_COMPILE,
|
||||
"auto_reload" => self::AUTO_RELOAD,
|
||||
"force_include" => self::FORCE_INCLUDE,
|
||||
"auto_escape" => self::AUTO_ESCAPE,
|
||||
"force_verify" => self::FORCE_VERIFY
|
||||
);
|
||||
|
||||
/**
|
||||
@ -615,7 +615,8 @@ class Fenom {
|
||||
* @return Fenom\Template
|
||||
*/
|
||||
public function getTemplate($template, $options = 0) {
|
||||
$key = dechex($this->_options | $options)."@".$template;
|
||||
$options |= $this->_options;
|
||||
$key = dechex($options)."@".$template;
|
||||
if(isset($this->_storage[ $key ])) {
|
||||
/** @var Fenom\Template $tpl */
|
||||
$tpl = $this->_storage[ $key ];
|
||||
|
@ -875,7 +875,7 @@ class Compiler {
|
||||
* @return string
|
||||
*/
|
||||
public static function tagRaw(Tokenizer $tokens, Template $tpl) {
|
||||
$escape = $tpl->escape;
|
||||
$escape = (bool)$tpl->escape;
|
||||
$tpl->escape = false;
|
||||
if($tokens->is(':')) {
|
||||
$func = $tokens->getNext(Tokenizer::MACRO_STRING);
|
||||
@ -885,11 +885,12 @@ class Compiler {
|
||||
} elseif ($tag["type"] == \Fenom::BLOCK_FUNCTION) {
|
||||
$code = $tpl->parseAct($tokens);
|
||||
$tpl->getLastScope()->escape = false;
|
||||
return $code;
|
||||
} else {
|
||||
throw new InvalidUsageException("Raw mode allow for expressions or functions");
|
||||
}
|
||||
} else {
|
||||
$code = $tpl->out($tpl->parseExp($tokens, true), false);
|
||||
$code = $tpl->out($tpl->parseExp($tokens, true));
|
||||
}
|
||||
$tpl->escape = $escape;
|
||||
return $code;
|
||||
|
@ -147,13 +147,15 @@ class Modifier {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param $value
|
||||
* @param $list
|
||||
* @param mixed $value
|
||||
* @param mixed $haystack
|
||||
* @return bool
|
||||
*/
|
||||
public static function in($value, $list) {
|
||||
if(is_array($list)) {
|
||||
return in_array($value, $list);
|
||||
public static function in($value, $haystack) {
|
||||
if(is_array($haystack)) {
|
||||
return in_array($value, $haystack) || array_key_exists($value, $haystack);
|
||||
} elseif(is_string($haystack)) {
|
||||
return strpos($haystack, $value) !== false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -29,7 +29,6 @@ class Scope extends \ArrayObject {
|
||||
private $_action;
|
||||
private $_body;
|
||||
private $_offset;
|
||||
public $_global_escape = false;
|
||||
|
||||
/**
|
||||
* Creating cope
|
||||
@ -58,8 +57,7 @@ class Scope extends \ArrayObject {
|
||||
public function setFuncName($function) {
|
||||
$this["function"] = $function;
|
||||
$this->is_compiler = false;
|
||||
$this->_global_escape = $this->tpl->escape;
|
||||
$this->tpl->escape = false;
|
||||
$this->escape = $this->tpl->escape;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -108,9 +106,6 @@ class Scope extends \ArrayObject {
|
||||
* @return string
|
||||
*/
|
||||
public function close($tokenizer) {
|
||||
if(!$this->is_compiler) {
|
||||
$this->tpl->escape = $this->_global_escape;
|
||||
}
|
||||
return call_user_func($this->_action["close"], $tokenizer, $this);
|
||||
}
|
||||
|
||||
@ -145,4 +140,8 @@ class Scope extends \ArrayObject {
|
||||
$this->cutContent();
|
||||
$this->_body .= $new_content;
|
||||
}
|
||||
|
||||
public function unEscapeContent() {
|
||||
|
||||
}
|
||||
}
|
@ -59,9 +59,10 @@ class Template extends Render {
|
||||
public $parents = array();
|
||||
|
||||
/**
|
||||
* Escape output value
|
||||
* Escape outputs value
|
||||
* @var bool
|
||||
*/
|
||||
// public $escape = false;
|
||||
public $escape = false;
|
||||
public $_extends;
|
||||
public $_extended = false;
|
||||
@ -90,6 +91,34 @@ class Template extends Render {
|
||||
|
||||
private $_filter = array();
|
||||
|
||||
private static $_checkers = array(
|
||||
'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)',
|
||||
'callable' => 'is_callable(%s)',
|
||||
'callback' => 'is_callable(%s)',
|
||||
'array' => 'is_array(%s)',
|
||||
'iterable' => '\Fenom\Modifier::isIterable(%s)',
|
||||
'const' => 'defined(%s)',
|
||||
'template' => '$this->getStorage()->templateExists(%s)',
|
||||
'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)'
|
||||
);
|
||||
|
||||
/**
|
||||
* Just factory
|
||||
*
|
||||
@ -172,7 +201,7 @@ class Template extends Render {
|
||||
$this->_appendText(substr($this->_src, $pos, $start - $pos + 2));
|
||||
$end = $start + 1;
|
||||
break;
|
||||
case "*": // if comments
|
||||
case "*": // comment block
|
||||
$end = strpos($this->_src, '*}', $start); // find end of the comment block
|
||||
if($end === false) {
|
||||
throw new CompileException("Unclosed comment block in line {$this->_line}", 0, 1, $this->_name, $this->_line);
|
||||
@ -182,7 +211,6 @@ class Template extends Render {
|
||||
$comment = substr($this->_src, $start, $end - $start); // read the comment block for processing
|
||||
$this->_line += substr_count($comment, "\n"); // count lines in comments
|
||||
unset($comment); // cleanup
|
||||
// $pos = $end + 1;
|
||||
break;
|
||||
default:
|
||||
$this->_appendText(substr($this->_src, $pos, $start - $pos));
|
||||
@ -208,22 +236,21 @@ class Template extends Render {
|
||||
if($tokens->isIncomplete()) { // all strings finished?
|
||||
$need_next_close_symbol = true;
|
||||
} else {
|
||||
$this->_appendCode( $this->_tag($tokens) , $tag); // start the tag lexer
|
||||
$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);
|
||||
}
|
||||
}
|
||||
}
|
||||
} while ($need_next_close_symbol);
|
||||
// $pos = $end + 1; // move search-pointer to end of the tag
|
||||
unset($_tag, $tag); // cleanup
|
||||
break;
|
||||
}
|
||||
$pos = $end + 1; // move search-pointer to end of the tag
|
||||
$pos = $end + 1; // move search-pointer to end of the tag
|
||||
}
|
||||
|
||||
gc_collect_cycles();
|
||||
$this->_appendText(substr($this->_src, $end ? $end + 1 : 0));
|
||||
$this->_appendText(substr($this->_src, $end ? $end + 1 : 0)); // append tail of the template
|
||||
if($this->_stack) {
|
||||
$_names = array();
|
||||
$_line = 0;
|
||||
@ -241,6 +268,7 @@ class Template extends Render {
|
||||
call_user_func_array($cb, array(&$this->_body, $this));
|
||||
}
|
||||
}
|
||||
$this->addDepend($this); // for 'verify' performance
|
||||
}
|
||||
|
||||
/**
|
||||
@ -303,7 +331,6 @@ class Template extends Render {
|
||||
* @param $source
|
||||
*/
|
||||
private function _appendCode($code, $source) {
|
||||
|
||||
if(!$code) {
|
||||
return;
|
||||
} else {
|
||||
@ -384,22 +411,6 @@ class Template extends Render {
|
||||
$this->_depends[$tpl->getScm()][$tpl->getName()] = $tpl->getTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute template and return result as string
|
||||
* @param array $values for template
|
||||
* @throws CompileException
|
||||
* @return string
|
||||
*/
|
||||
public function fetch(array $values) {
|
||||
if(!$this->_code) {
|
||||
eval("\$this->_code = ".$this->_getClosureSource().";");
|
||||
if(!$this->_code) {
|
||||
throw new CompileException("Fatal error while creating the template");
|
||||
}
|
||||
}
|
||||
return parent::fetch($values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Output the value
|
||||
*
|
||||
@ -414,14 +425,14 @@ class Template extends Render {
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Internal tags router
|
||||
* Tag router
|
||||
* @param Tokenizer $tokens
|
||||
*
|
||||
* @throws SecurityException
|
||||
* @throws CompileException
|
||||
* @return string executable PHP code
|
||||
*/
|
||||
private function _tag(Tokenizer $tokens) {
|
||||
public function parseTag(Tokenizer $tokens) {
|
||||
try {
|
||||
if($tokens->is(Tokenizer::MACRO_STRING)) {
|
||||
if($tokens->current() === "ignore") {
|
||||
@ -432,11 +443,11 @@ class Template extends Render {
|
||||
return $this->parseAct($tokens);
|
||||
}
|
||||
} elseif ($tokens->is('/')) {
|
||||
return $this->_end($tokens);
|
||||
return $this->parseEndTag($tokens);
|
||||
} elseif ($tokens->is('#')) {
|
||||
return $this->out($this->parseConst($tokens), $tokens).';';
|
||||
return $this->out($this->parseConst($tokens), $tokens);
|
||||
} else {
|
||||
return $code = $this->out($this->parseExp($tokens), $tokens).";";
|
||||
return $this->out($this->parseExp($tokens), $tokens);
|
||||
}
|
||||
} catch (InvalidUsageException $e) {
|
||||
throw new CompileException($e->getMessage()." in {$this} line {$this->_line}", 0, E_ERROR, $this->_name, $this->_line, $e);
|
||||
@ -454,7 +465,7 @@ class Template extends Render {
|
||||
* @return string
|
||||
* @throws TokenizeException
|
||||
*/
|
||||
private function _end(Tokenizer $tokens) {
|
||||
public function parseEndTag(Tokenizer $tokens) {
|
||||
$name = $tokens->getNext(Tokenizer::MACRO_STRING);
|
||||
$tokens->next();
|
||||
if(!$this->_stack) {
|
||||
@ -469,7 +480,7 @@ class Template extends Render {
|
||||
return $scope->close($tokens);
|
||||
} else {
|
||||
$code = $this->out($scope->close($tokens));
|
||||
$scope->tpl->escape = $scope->escape;
|
||||
$scope->tpl->escape = $scope->escape; // restore escape option
|
||||
return $code;
|
||||
}
|
||||
}
|
||||
@ -495,13 +506,14 @@ class Template extends Render {
|
||||
if($tokens->is(Tokenizer::MACRO_STRING)) {
|
||||
$action = $tokens->getAndNext();
|
||||
} else {
|
||||
return $this->out($this->parseExp($tokens), $tokens).';'; // may be math and/or boolean expression
|
||||
return $this->out($this->parseExp($tokens)); // may be math and/or boolean expression
|
||||
}
|
||||
if($tokens->is("(", T_NAMESPACE, T_DOUBLE_COLON) && !$tokens->isWhiteSpaced()) { // just invoke function or static method
|
||||
$tokens->back();
|
||||
return $this->out($this->parseExp($tokens));
|
||||
}
|
||||
|
||||
if($tokens->is("(", T_NAMESPACE, T_DOUBLE_COLON)) { // just invoke function or static method
|
||||
$tokens->back();
|
||||
return $this->out($this->parseExp($tokens), $tokens).";";
|
||||
} elseif($tokens->is('.')) {
|
||||
if($tokens->is('.')) {
|
||||
$name = $tokens->skip()->get(Tokenizer::MACRO_STRING);
|
||||
if($action !== "macro") {
|
||||
$name = $action.".".$name;
|
||||
@ -526,7 +538,8 @@ class Template extends Render {
|
||||
$scope = new Scope($action, $this, $this->_line, $act, count($this->_stack), $this->_body);
|
||||
$scope->setFuncName($act["function"]);
|
||||
array_push($this->_stack, $scope);
|
||||
$scope->escape = $this->_options & Fenom::AUTO_ESCAPE;
|
||||
$scope->escape = $this->escape;
|
||||
$this->escape = false;
|
||||
return $scope->open($tokens);
|
||||
default:
|
||||
throw new \LogicException("Unknown function type");
|
||||
@ -557,63 +570,91 @@ class Template extends Render {
|
||||
* @return string
|
||||
*/
|
||||
public function parseExp(Tokenizer $tokens, $required = false) {
|
||||
$_exp = "";
|
||||
$brackets = 0;
|
||||
$term = false;
|
||||
$cond = false;
|
||||
$_exp = array(); // expression as PHP code
|
||||
$term = false; // last item was variable or value.
|
||||
// 0 - was operator, but trem required
|
||||
// false - was operator or no one term
|
||||
// true - was trem
|
||||
// 1 - term is strict varaible
|
||||
$cond = false; // last item was operator
|
||||
while($tokens->valid()) {
|
||||
if(!$term && $tokens->is(Tokenizer::MACRO_SCALAR, '"', '`', T_ENCAPSED_AND_WHITESPACE)) {
|
||||
$_exp .= $this->parseScalar($tokens, true);
|
||||
$term = 1;
|
||||
} elseif(!$term && $tokens->is(T_VARIABLE)) {
|
||||
|
||||
$pp = $tokens->isPrev(Tokenizer::MACRO_INCDEC);
|
||||
$_exp .= $this->parseVariable($tokens, 0, $only_var);
|
||||
if($only_var && !$pp) {
|
||||
$term = 2;
|
||||
if(!$term && $tokens->is(Tokenizer::MACRO_SCALAR, '"', '`', T_ENCAPSED_AND_WHITESPACE)) { // like quoted string
|
||||
$_exp[] = $this->parseScalar($tokens, true);
|
||||
$term = true;
|
||||
} elseif(!$term && $tokens->is(Tokenizer::MACRO_INCDEC)) { // like variable
|
||||
$_exp[] = $this->parseVariable($tokens);
|
||||
$term = true;
|
||||
} elseif(!$term && $tokens->is(T_VARIABLE)) { // like variable too
|
||||
$var = $this->parseVar($tokens);
|
||||
if($tokens->is(Tokenizer::MACRO_EQUALS)) {
|
||||
$_exp[] = $var;
|
||||
if($tokens->isLast()) {
|
||||
break;
|
||||
}
|
||||
$_exp[] = $tokens->getAndNext();
|
||||
$term = 0;
|
||||
} elseif($tokens->is(Tokenizer::MACRO_INCDEC, "|", "!", "?", '(')) {
|
||||
$_exp[] = $this->parseVariable($tokens, 0, $var);
|
||||
$term = true;
|
||||
} else {
|
||||
$_exp[] = $var;
|
||||
$term = 1;
|
||||
}
|
||||
} elseif(!$term && $tokens->is('#')) {
|
||||
} elseif(!$term && $tokens->is("(")) { // open bracket
|
||||
$tokens->next();
|
||||
$_exp[] = "(".$this->parseExp($tokens, true).")";
|
||||
$tokens->get(")");
|
||||
$tokens->next();
|
||||
$term = 1;
|
||||
$_exp .= $this->parseConst($tokens);
|
||||
} elseif(!$term && $tokens->is("(")) {
|
||||
$_exp .= $tokens->getAndNext();
|
||||
$brackets++;
|
||||
$term = false;
|
||||
} elseif($term && $tokens->is(")")) {
|
||||
if(!$brackets) {
|
||||
break;
|
||||
} elseif($tokens->is(T_STRING)) {
|
||||
if($term) { // parse 'in' or 'is' operators
|
||||
if(!$_exp) {
|
||||
break;
|
||||
}
|
||||
$operator = $tokens->current();
|
||||
if($operator == "is") {
|
||||
$item = array_pop($_exp);
|
||||
$_exp[] = $this->parseIs($tokens, $item, $term === 1);
|
||||
} elseif($operator == "in" || ($operator == "not" && $tokens->isNextToken("in"))) {
|
||||
$item = array_pop($_exp);
|
||||
$_exp[] = $this->parseIn($tokens, $item, $term === 1);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else { // function or special value
|
||||
if($tokens->isSpecialVal()) {
|
||||
$_exp[] = $tokens->getAndNext();
|
||||
} elseif($tokens->isNext("(") && !$tokens->getWhitespace()) {
|
||||
$func = $this->_fenom->getModifier($tokens->current());
|
||||
$tokens->next();
|
||||
$func = $func.$this->parseArgs($tokens);
|
||||
if($tokens->is('|')) {
|
||||
$_exp[] = $this->parseModifier($tokens, $func);
|
||||
} else {
|
||||
$_exp[] = $func;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
$term = true;
|
||||
}
|
||||
$brackets--;
|
||||
$_exp .= $tokens->getAndNext();
|
||||
$term = 1;
|
||||
} elseif(!$term && $tokens->is(T_STRING)) {
|
||||
if($tokens->isSpecialVal()) {
|
||||
$_exp .= $tokens->getAndNext();
|
||||
} elseif($tokens->isNext("(")) {
|
||||
$func = $this->_fenom->getModifier($tokens->current());
|
||||
$tokens->next();
|
||||
$_exp .= $func.$this->parseArgs($tokens);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
$term = 1;
|
||||
} elseif(!$term && $tokens->is(T_ISSET, T_EMPTY)) {
|
||||
$_exp .= $tokens->getAndNext();
|
||||
} elseif(!$term && $tokens->is(T_ISSET, T_EMPTY)) { // empty and isset operators
|
||||
$func = $tokens->getAndNext();
|
||||
if($tokens->is("(") && $tokens->isNext(T_VARIABLE)) {
|
||||
$_exp .= $this->parseArgs($tokens);
|
||||
$tokens->next();
|
||||
$_exp[] = $func."(".$this->parseVar($tokens).")";
|
||||
$tokens->need(')')->next();
|
||||
} else {
|
||||
throw new TokenizeException("Unexpected token ".$tokens->getNext().", isset() and empty() accept only variables");
|
||||
}
|
||||
$term = 1;
|
||||
} elseif(!$term && $tokens->is(Tokenizer::MACRO_UNARY)) {
|
||||
$term = true;
|
||||
} elseif(!$term && $tokens->is(Tokenizer::MACRO_UNARY)) { // like unary operator, see Tokenizer::MACRO_UNARY
|
||||
if(!$tokens->isNext(T_VARIABLE, T_DNUMBER, T_LNUMBER, T_STRING, T_ISSET, T_EMPTY)) {
|
||||
break;
|
||||
}
|
||||
$_exp .= $tokens->getAndNext();
|
||||
$term = 0;
|
||||
} elseif($tokens->is(Tokenizer::MACRO_BINARY)) {
|
||||
$_exp[] = $tokens->getAndNext();
|
||||
$term = false;
|
||||
} elseif($tokens->is(Tokenizer::MACRO_BINARY)) { // like binary operator, see Tokenizer::MACRO_BINARY
|
||||
if(!$term) {
|
||||
throw new UnexpectedTokenException($tokens);
|
||||
}
|
||||
@ -628,37 +669,19 @@ class Template extends Render {
|
||||
} elseif ($tokens->is(Tokenizer::MACRO_BOOLEAN)) {
|
||||
$cond = false;
|
||||
}
|
||||
$_exp .= " ".$tokens->getAndNext()." ";
|
||||
$_exp[] = " ".$tokens->getAndNext()." ";
|
||||
$term = 0;
|
||||
} elseif($tokens->is(Tokenizer::MACRO_INCDEC)) {
|
||||
if($term === 2) {
|
||||
$term = 1;
|
||||
} elseif(!$tokens->isNext(T_VARIABLE)) {
|
||||
break;
|
||||
}
|
||||
$_exp .= $tokens->getAndNext();
|
||||
} elseif($term && !$cond && !$tokens->isLast()) {
|
||||
if($tokens->is(Tokenizer::MACRO_EQUALS) && $term === 2) {
|
||||
$_exp .= ' '.$tokens->getAndNext().' ';
|
||||
$term = 0;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if($term === 0) {
|
||||
throw new UnexpectedTokenException($tokens);
|
||||
}
|
||||
if($brackets) {
|
||||
throw new TokenizeException("Brackets don't match");
|
||||
}
|
||||
if($required && $_exp === "") {
|
||||
if($required && !$_exp) {
|
||||
throw new UnexpectedTokenException($tokens);
|
||||
}
|
||||
return $_exp;
|
||||
return implode('',$_exp);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -678,12 +701,8 @@ class Template extends Render {
|
||||
if($tokens->is(T_VARIABLE)) {
|
||||
$key = "[ ".$this->parseVariable($tokens, self::DENY_ARRAY)." ]";
|
||||
} elseif($tokens->is(Tokenizer::MACRO_STRING)) {
|
||||
if($tokens->isNext("(")) {
|
||||
$key = "[".$this->parseExp($tokens)."]";
|
||||
} else {
|
||||
$key = '["'.$key.'"]';
|
||||
$tokens->next();
|
||||
}
|
||||
$key = '["'.$key.'"]';
|
||||
$tokens->next();
|
||||
} elseif($tokens->is(Tokenizer::MACRO_SCALAR, '"')) {
|
||||
$key = "[".$this->parseScalar($tokens, false)."]";
|
||||
} else {
|
||||
@ -707,54 +726,73 @@ class Template extends Render {
|
||||
$_var .= $key;
|
||||
} elseif($t === T_DNUMBER) {
|
||||
$_var .= '['.substr($tokens->getAndNext(), 1).']';
|
||||
} elseif($t === T_OBJECT_OPERATOR) {
|
||||
$_var .= "->".$tokens->getNext(T_STRING);
|
||||
$tokens->next();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $_var;
|
||||
if($this->_options & Fenom::FORCE_VERIFY) {
|
||||
return 'isset('.$_var.') ? '.$_var.' : null';
|
||||
} else {
|
||||
return $_var;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse variable
|
||||
* Parse complex variable
|
||||
* $var.foo[bar]["a"][1+3/$var]|mod:3:"w":$var3|mod3
|
||||
* ++$var|mod
|
||||
* $var--|mod
|
||||
*
|
||||
* @see parseModifier
|
||||
* @static
|
||||
* @param Tokenizer $tokens
|
||||
* @param int $deny set limitations
|
||||
* @param bool $pure_var will be FALSE if variable modified
|
||||
* @param int $options set parser options
|
||||
* @param string $var already parsed plain variable
|
||||
* @throws \LogicException
|
||||
* @throws UnexpectedTokenException
|
||||
* @throws InvalidUsageException
|
||||
* @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) {
|
||||
$prop = $tokens->getNext(T_STRING);
|
||||
if($tokens->isNext("(")) {
|
||||
if($this->_options & Fenom::DENY_METHODS) {
|
||||
throw new \LogicException("Forbidden to call methods");
|
||||
}
|
||||
$pure_var = false;
|
||||
$tokens->next();
|
||||
$_var .= '->'.$prop.$this->parseArgs($tokens);
|
||||
} else {
|
||||
$tokens->next();
|
||||
$_var .= '->'.$prop;
|
||||
}
|
||||
} elseif($t === "?" || $t === "!") {
|
||||
$pure_var = false;
|
||||
return $this->parseTernary($tokens, $_var, $t);
|
||||
public function parseVariable(Tokenizer $tokens, $options = 0, $var = null) {
|
||||
$stained = false;
|
||||
if(!$var) {
|
||||
if($tokens->is(Tokenizer::MACRO_INCDEC)) {
|
||||
$stained = true;
|
||||
$var = $tokens->getAndNext().$this->parseVar($tokens, $options);
|
||||
} else {
|
||||
break;
|
||||
$var = $this->parseVar($tokens, $options);
|
||||
}
|
||||
if($tokens->is(T_OBJECT_OPERATOR)) { // parse
|
||||
$var .= '->'.$tokens->getNext(T_STRING);
|
||||
$tokens->next();
|
||||
}
|
||||
}
|
||||
return $_var;
|
||||
|
||||
if($tokens->is("(") && $tokens->hasBackList(T_STRING, T_OBJECT_OPERATOR)) {
|
||||
if($stained) {
|
||||
throw new InvalidUsageException("Can not increment or decrement of the method result");
|
||||
}
|
||||
if($this->_options & Fenom::DENY_METHODS) {
|
||||
throw new \LogicException("Forbidden to call methods");
|
||||
}
|
||||
$var .= $this->parseArgs($tokens);
|
||||
$stained = true;
|
||||
}
|
||||
if($tokens->is('?', '!')) {
|
||||
return $this->parseTernary($tokens, $var, $tokens->current());
|
||||
}
|
||||
if($tokens->is(Tokenizer::MACRO_INCDEC)) {
|
||||
if($stained) {
|
||||
throw new InvalidUsageException("Can not use two increments and/or decrements for one variable");
|
||||
}
|
||||
$var .= $tokens->getAndNext();
|
||||
}
|
||||
if($tokens->is('|') && !($options & self::DENY_MODS)) {
|
||||
return $this->parseModifier($tokens, $var);
|
||||
}
|
||||
return $var;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -796,6 +834,127 @@ class Template extends Render {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse 'is' and 'is not' operator
|
||||
* @see $_checkers
|
||||
* @param Tokenizer $tokens
|
||||
* @param string $value
|
||||
* @param bool $variable
|
||||
* @throws InvalidUsageException
|
||||
* @return string
|
||||
*/
|
||||
public function parseIs(Tokenizer $tokens, $value, $variable = false) {
|
||||
$tokens->next();
|
||||
if($tokens->current() == 'not'){
|
||||
$invert = '!';
|
||||
$equal = '!=';
|
||||
$tokens->next();
|
||||
} else {
|
||||
$invert = '';
|
||||
$equal = '==';
|
||||
}
|
||||
if($tokens->is(Tokenizer::MACRO_STRING)) {
|
||||
$action = $tokens->current();
|
||||
if(!$variable && ($action == "set" || $action == "empty")) {
|
||||
$action = "_$action";
|
||||
$tokens->next();
|
||||
return $invert.sprintf(self::$_checkers[$action], $value);
|
||||
} elseif(isset(self::$_checkers[$action])) {
|
||||
$tokens->next();
|
||||
return $invert.sprintf(self::$_checkers[$action], $value);
|
||||
} elseif($tokens->isSpecialVal()) {
|
||||
$tokens->next();
|
||||
return '('.$value.' '.$equal.'= '.$action.')';
|
||||
}
|
||||
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).')';
|
||||
} else {
|
||||
throw new InvalidUsageException("Unknown argument");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse 'in' and 'not in' operators
|
||||
* @param Tokenizer $tokens
|
||||
* @param string $value
|
||||
* @throws InvalidUsageException
|
||||
* @throws UnexpectedTokenException
|
||||
* @return string
|
||||
*/
|
||||
public function parseIn(Tokenizer $tokens, $value) {
|
||||
$checkers = array(
|
||||
"string" => 'is_int(strpos(%2$s, %1$s))',
|
||||
"list" => "in_array(%s, %s)",
|
||||
"keys" => "array_key_exists(%s, %s)",
|
||||
"auto" => '\Fenom\Modifier::in(%s, %s)'
|
||||
);
|
||||
$checker = null;
|
||||
$invert = '';
|
||||
if($tokens->current() == 'not'){
|
||||
$invert = '!';
|
||||
$tokens->next();
|
||||
}
|
||||
if($tokens->current() !== "in") {
|
||||
throw new UnexpectedTokenException($tokens);
|
||||
}
|
||||
$tokens->next();
|
||||
if($tokens->is(Tokenizer::MACRO_STRING)) {
|
||||
$checker = $tokens->current();
|
||||
if(!isset($checkers[$checker])) {
|
||||
throw new UnexpectedTokenException($tokens);
|
||||
}
|
||||
$tokens->next();
|
||||
}
|
||||
if($tokens->is('[')) {
|
||||
if($checker == "string") {
|
||||
throw new InvalidUsageException("Can not use string operation for array");
|
||||
} elseif(!$checker) {
|
||||
$checker = "list";
|
||||
}
|
||||
return $invert.sprintf($checkers[$checker], $value, $this->parseArray($tokens));
|
||||
} elseif($tokens->is('"', T_ENCAPSED_AND_WHITESPACE, T_CONSTANT_ENCAPSED_STRING)) {
|
||||
if(!$checker) {
|
||||
$checker = "string";
|
||||
} elseif($checker != "string") {
|
||||
throw new InvalidUsageException("Can not use array operation for string");
|
||||
}
|
||||
return $invert.sprintf($checkers[$checker], "strval($value)", $this->parseScalar($tokens));
|
||||
} elseif($tokens->is(T_VARIABLE, Tokenizer::MACRO_INCDEC)) {
|
||||
if(!$checker) {
|
||||
$checker = "auto";
|
||||
}
|
||||
return $invert.sprintf($checkers[$checker], $value, $this->parseVariable($tokens));
|
||||
} else {
|
||||
throw new UnexpectedTokenException($tokens);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse method, class or constant name
|
||||
*
|
||||
* @param Tokenizer $tokens
|
||||
* @return string
|
||||
*/
|
||||
public function parseName(Tokenizer $tokens) {
|
||||
$tokens->skipIf(T_NS_SEPARATOR);
|
||||
$name = "";
|
||||
if($tokens->is(T_STRING)) {
|
||||
$name .= $tokens->getAndNext();
|
||||
while($tokens->is(T_NS_SEPARATOR)) {
|
||||
$name .= '\\'.$tokens->next()->get(T_STRING);
|
||||
$tokens->next();
|
||||
}
|
||||
}
|
||||
return $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse scalar values
|
||||
*
|
||||
@ -919,7 +1078,7 @@ class Template extends Render {
|
||||
} elseif($tokens->is('"', '`', T_ENCAPSED_AND_WHITESPACE)) {
|
||||
$args[] = $this->parseSubstr($tokens);
|
||||
} elseif($tokens->is('(')) {
|
||||
$args[] = $this->parseExp($tokens);
|
||||
$args[] = $this->parseExp($tokens, true);
|
||||
} elseif($tokens->is('[')) {
|
||||
$args[] = $this->parseArray($tokens);
|
||||
} elseif($tokens->is(T_STRING) && $tokens->isNext('(')) {
|
||||
@ -929,7 +1088,6 @@ class Template extends Render {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if(!is_string($mods)) { // dynamic modifier
|
||||
$mods = 'call_user_func($tpl->getStorage()->getModifier("'.$modifier_name.'"), ';
|
||||
} else {
|
||||
|
@ -284,6 +284,13 @@ class Tokenizer {
|
||||
return $this->current();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $token
|
||||
* @return bool
|
||||
*/
|
||||
public function isNextToken($token) {
|
||||
return $this->next ? $this->next[1] == $token : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return substring. This method doesn't move pointer.
|
||||
@ -374,6 +381,22 @@ class Tokenizer {
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $token1
|
||||
* @return bool
|
||||
*/
|
||||
public function hasBackList($token1 /*, $token2 ...*/) {
|
||||
$tokens = func_get_args();
|
||||
$c = $this->p;
|
||||
foreach($tokens as $token) {
|
||||
$c--;
|
||||
if($c < 0 || $this->tokens[$c][0] !== $token) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazy load properties
|
||||
*
|
||||
|
Reference in New Issue
Block a user