From 0a9facc493fdfcccd7202521516e46554dfd470e Mon Sep 17 00:00:00 2001 From: bzick Date: Mon, 2 Sep 2013 17:40:58 +0400 Subject: [PATCH] Done 1.4.0 --- CHANGELOG.md | 22 +- benchmark/sandbox/fenom.php | 8 +- benchmark/sandbox/templates/greeting.tpl | 11 +- benchmark/sandbox/templates/macros.tpl | 5 + docs/operators.md | 4 +- docs/tags/include.md | 4 +- docs/tags/switch.md | 2 +- src/Fenom.php | 27 ++- src/Fenom/Compiler.php | 137 +++++++++--- src/Fenom/Render.php | 6 +- src/Fenom/Template.php | 270 +++++++---------------- src/Fenom/Tokenizer.php | 9 +- tests/cases/Fenom/TemplateTest.php | 34 +-- 13 files changed, 257 insertions(+), 282 deletions(-) create mode 100644 benchmark/sandbox/templates/macros.tpl diff --git a/CHANGELOG.md b/CHANGELOG.md index f8c4801..e1ebc47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,21 @@ -CHANGELOG +Changelog ========= -## 1.3.1 (2013-08-29) +## 1.4.0 -- Bug: accessor don't work in modifier -- Removed too many EOL in template code +- Redesign tag {switch} +- Add tag {insert} +- Add variable verification before using (option `Fenom::FORCE_VERIFY`) +- Improve internal parsers +- Fix #45: intersection of names of tmp vars +- Fix #44: invalid `_depend` format in template +- Docs++ +- Tests++ + +### 1.3.1 (2013-08-29) + +- Fix: accessor don't work in modifier +- Removed too many EOLs in template code - Tests++ ## 1.3.0 (2013-08-23) @@ -15,7 +26,7 @@ CHANGELOG - Recognize macros parser - Fix `auto_reload` option - Tests++ -- Docs-- +- Docs++ ### 1.2.2 (2013-08-07) @@ -30,7 +41,6 @@ CHANGELOG - Feature #28: macros may be called recursively - Feature #29: add {unset} tag - Add hook for loading modifiers and tags -- Add hook for loading modifiers and tags - Feature #3: Add string operator '~' - Improve parsers: parserExp, parserVar, parserVariable, parserMacro - Fix ternary bug diff --git a/benchmark/sandbox/fenom.php b/benchmark/sandbox/fenom.php index ef220e5..9a4e780 100644 --- a/benchmark/sandbox/fenom.php +++ b/benchmark/sandbox/fenom.php @@ -2,10 +2,12 @@ require_once __DIR__.'/../../vendor/autoload.php'; -$fenom = Fenom::factory(__DIR__.'/templates', __DIR__.'/compiled', Fenom::FORCE_COMPILE); +$fenom = Fenom::factory(__DIR__.'/templates', __DIR__.'/compiled', 0); $fenom->display("greeting.tpl", array( "user" => array( - "name" => "Ivka" - ) + "name" => "Ivka", + 'type' => 'new' + ), + 'type' => 'new' )); \ No newline at end of file diff --git a/benchmark/sandbox/templates/greeting.tpl b/benchmark/sandbox/templates/greeting.tpl index 0b97ab3..452daf9 100644 --- a/benchmark/sandbox/templates/greeting.tpl +++ b/benchmark/sandbox/templates/greeting.tpl @@ -1,8 +1,5 @@ -Greeting, -{if $user} - {$user.name}! -{else} - anonymous? -{/if} +{import 'macros.tpl' as mc} -3 \ No newline at end of file +A1 +{mc.factorial num=10} +A2 \ No newline at end of file diff --git a/benchmark/sandbox/templates/macros.tpl b/benchmark/sandbox/templates/macros.tpl new file mode 100644 index 0000000..b7b91ba --- /dev/null +++ b/benchmark/sandbox/templates/macros.tpl @@ -0,0 +1,5 @@ +{macro factorial(num)} +{if $num} + {$num} {macro.factorial num=$num-1} {$num} +{/if} +{/macro} \ No newline at end of file diff --git a/docs/operators.md b/docs/operators.md index c0dd526..579d772 100644 --- a/docs/operators.md +++ b/docs/operators.md @@ -131,11 +131,11 @@ Tests can be negated by using the `not in` operator. * `$a in $b` - variable `$a` contains in `$b`, $b may be string, plain or assoc array. * `$a in list $b` - variable `$a` contains in array `$b` as value -* `$a in keys $b` - variable `$a` contains in array `$b` as key +* `$a in keys $b` - array `$b` contain key `$a` * `$a in string $b` - variable `$a` contains in string `$b` as substring ```smarty {'df' in 'abcdefg'} {5 in [1, 5, 25, 125]} -{2 in keys [1, 5, 25, 125]} +{99 in keys [1, 5, 25, 99 => 125]} ``` \ No newline at end of file diff --git a/docs/tags/include.md b/docs/tags/include.md index 08b59c2..2c7d678 100644 --- a/docs/tags/include.md +++ b/docs/tags/include.md @@ -17,12 +17,12 @@ Tag {include} [RU] ### {insert} -The tag insert template template code instead self. +The tag insert template code instead self. * No dynamic name allowed * No variables as attribute allowed -For example, +For example, main.tpl: ```smarty a: {$a} diff --git a/docs/tags/switch.md b/docs/tags/switch.md index 0bb66eb..3df5f9b 100644 --- a/docs/tags/switch.md +++ b/docs/tags/switch.md @@ -31,7 +31,7 @@ For example, {/switch} ``` -If `$type = 'new'` template outputs +set `$type = 'new'`, then template output ``` It is new item diff --git a/src/Fenom.php b/src/Fenom.php index 02a10a7..50173cb 100644 --- a/src/Fenom.php +++ b/src/Fenom.php @@ -157,17 +157,16 @@ class Fenom 'close' => 'Fenom\Compiler::stdClose', 'tags' => array( 'elseif' => 'Fenom\Compiler::tagElseIf', - 'else' => 'Fenom\Compiler::tagElse', + 'else' => 'Fenom\Compiler::tagElse' ) ), - 'switch' => array( // {switch ...} {case ...} {break} {default} {/switch} + 'switch' => array( // {switch ...} {case ..., ...} {default} {/switch} 'type' => self::BLOCK_COMPILER, 'open' => 'Fenom\Compiler::switchOpen', - 'close' => 'Fenom\Compiler::stdClose', + 'close' => 'Fenom\Compiler::switchClose', 'tags' => array( 'case' => 'Fenom\Compiler::tagCase', - 'default' => 'Fenom\Compiler::tagDefault', - 'break' => 'Fenom\Compiler::tagBreak', + 'default' => 'Fenom\Compiler::tagDefault' ), 'float_tags' => array('break' => 1) ), @@ -196,6 +195,10 @@ class Fenom 'type' => self::INLINE_COMPILER, 'parser' => 'Fenom\Compiler::tagInclude' ), + 'insert' => array( // {include ...} + 'type' => self::INLINE_COMPILER, + 'parser' => 'Fenom\Compiler::tagInsert' + ), 'var' => array( // {var ...} 'type' => self::BLOCK_COMPILER, 'open' => 'Fenom\Compiler::varOpen', @@ -205,8 +208,7 @@ class Fenom 'type' => self::BLOCK_COMPILER, 'open' => 'Fenom\Compiler::tagBlockOpen', 'close' => 'Fenom\Compiler::tagBlockClose', - 'tags' => array( - 'parent' => 'Fenom\Compiler::tagParent' + 'tags' => array(// 'parent' => 'Fenom\Compiler::tagParent' // not implemented yet ), 'float_tags' => array('parent' => 1) ), @@ -608,15 +610,18 @@ class Fenom * * @param string $scm scheme name * @param Fenom\ProviderInterface $provider provider object + * @return $this */ public function addProvider($scm, \Fenom\ProviderInterface $provider) { $this->_providers[$scm] = $provider; + return $this; } /** * Set options * @param int|array $options + * @return $this */ public function setOptions($options) { @@ -625,6 +630,7 @@ class Fenom } $this->_storage = array(); $this->_options = $options; + return $this; } /** @@ -758,10 +764,11 @@ class Fenom { $file_name = $this->_getCacheName($tpl, $opts); if (is_file($this->_compile_dir . "/" . $file_name)) { - $fenom = $this; + $fenom = $this; // used in template $_tpl = include($this->_compile_dir . "/" . $file_name); - /* @var Fenom\Render $tpl */ - if($_tpl->isValid()) { + /* @var Fenom\Render $_tpl */ +// var_dump($tpl, $_tpl->isValid()); exit; + if (!($this->_options & self::AUTO_RELOAD) || ($this->_options & self::AUTO_RELOAD) && $_tpl->isValid()) { return $_tpl; } } diff --git a/src/Fenom/Compiler.php b/src/Fenom/Compiler.php index 146bf66..b54d203 100644 --- a/src/Fenom/Compiler.php +++ b/src/Fenom/Compiler.php @@ -36,7 +36,7 @@ class Compiler $cname = $tpl->parsePlainArg($tokens, $name); $p = $tpl->parseParams($tokens); if ($p) { // if we have additionally variables - if ($name && ($tpl->getStorage()->getOptions() & \Fenom::FORCE_INCLUDE)) { // if FORCE_INCLUDE enabled and template name known + if ($name && ($tpl->getStorage()->getOptions() & \Fenom::FORCE_INCLUDE)) { $inc = $tpl->getStorage()->compile($name, false); $tpl->addDepend($inc); return '$_tpl = (array)$tpl; $tpl->exchangeArray(' . self::toArray($p) . '+$_tpl); ?>' . $inc->getBody() . 'exchangeArray($_tpl); unset($_tpl);'; @@ -44,7 +44,7 @@ class Compiler return '$tpl->getStorage()->getTemplate(' . $cname . ')->display(' . self::toArray($p) . '+(array)$tpl);'; } } else { - if ($name && ($tpl->getStorage()->getOptions() & \Fenom::FORCE_INCLUDE)) { // if FORCE_INCLUDE enabled and template name known + if ($name && ($tpl->getStorage()->getOptions() & \Fenom::FORCE_INCLUDE)) { $inc = $tpl->getStorage()->compile($name, false); $tpl->addDepend($inc); return '$_tpl = (array)$tpl; ?>' . $inc->getBody() . 'exchangeArray($_tpl); unset($_tpl);'; @@ -54,6 +54,24 @@ class Compiler } } + /** + * Tag {insert ...} + * @param Tokenizer $tokens + * @param Template $tpl + * @return string + * @throws Error\InvalidUsageException + */ + public static function tagInsert(Tokenizer $tokens, Template $tpl) + { + $tpl->parsePlainArg($tokens, $name); + if (!$name) { + throw new InvalidUsageException("Tag {insert} accept only static template name"); + } + $inc = $tpl->getStorage()->compile($name, false); + $tpl->addDepend($inc); + return '?>' . $tpl->getBody() . 'is(T_VARIABLE)) { - $from = $scope->tpl->parseVariable($tokens, Template::DENY_MODS); + $from = $scope->tpl->parseTerm($tokens); $prepend = ""; } elseif ($tokens->is('[')) { $from = $scope->tpl->parseArray($tokens); @@ -129,11 +147,11 @@ class Compiler } $tokens->get(T_AS); $tokens->next(); - $value = $scope->tpl->parseVar($tokens); + $value = $scope->tpl->parseVariable($tokens); if ($tokens->is(T_DOUBLE_ARROW)) { $tokens->next(); $key = $value; - $value = $scope->tpl->parseVar($tokens); + $value = $scope->tpl->parseVariable($tokens); } $scope["after"] = array(); @@ -146,7 +164,7 @@ class Compiler } $tokens->getNext("="); $tokens->next(); - $p[$param] = $scope->tpl->parseVar($tokens); + $p[$param] = $scope->tpl->parseVariable($tokens); } if ($p["index"]) { @@ -208,8 +226,9 @@ class Compiler * @static * @param Tokenizer $tokens * @param Scope $scope + * @throws Error\UnexpectedTokenException + * @throws Error\InvalidUsageException * @return string - * @throws InvalidUsageException */ public static function forOpen(Tokenizer $tokens, Scope $scope) { @@ -217,7 +236,10 @@ class Compiler $scope["after"] = $before = $body = array(); $i = array('', ''); $c = ""; - $var = $scope->tpl->parseVariable($tokens, Template::DENY_MODS); + $var = $scope->tpl->parseTerm($tokens, $is_var); + if (!$is_var) { + throw new UnexpectedTokenException($tokens); + } $tokens->get("="); $tokens->next(); $val = $scope->tpl->parseExpr($tokens); @@ -310,12 +332,34 @@ class Compiler */ public static function switchOpen(Tokenizer $tokens, Scope $scope) { - $scope["no-break"] = $scope["no-continue"] = true; - $scope["switch"] = 'switch(' . $scope->tpl->parseExpr($tokens) . ') {'; + $scope["expr"] = $scope->tpl->parseExpr($tokens); + $scope["case"] = array(); + $scope["last"] = array(); + $scope["default"] = ''; // lazy init return ''; } + /** + * Resort cases for {switch} + * @param Scope $scope + */ + private static function _caseResort(Scope $scope) + { + $content = $scope->cutContent(); + if ($scope["last"] === false) { + $scope["default"] .= $content; + } else { + foreach ($scope["last"] as $case) { + if (!isset($scope["case"][$case])) { + $scope["case"][$case] = ""; + } + $scope["case"][$case] .= $content; + } + } + $scope["last"] = array(); + } + /** * Tag {case ...} * @@ -326,12 +370,52 @@ class Compiler */ public static function tagCase(Tokenizer $tokens, Scope $scope) { - $code = 'case ' . $scope->tpl->parseExpr($tokens) . ': '; - if ($scope["switch"]) { - unset($scope["no-break"], $scope["no-continue"]); - $code = $scope["switch"] . "\n" . $code; - $scope["switch"] = ""; + self::_caseResort($scope); + do { + $scope["last"][] = $scope->tpl->parseScalar($tokens, false); + if ($tokens->is(',')) { + $tokens->next(); + } else { + break; + } + } while (true); + return ''; + } + + + /** + * Tag {default} + * + * @static + * @param Tokenizer $tokens + * @param Scope $scope + * @return string + */ + public static function tagDefault($tokens, Scope $scope) + { + self::_caseResort($scope); + $scope["last"] = false; + return ''; + } + + /** + * Close tag {switch} + * + * @static + * @param Tokenizer $tokens + * @param Scope $scope + * @return string + */ + public static function switchClose(Tokenizer $tokens, Scope $scope) + { + self::_caseResort($scope); + $expr = $scope["expr"]; + $code = ""; + $default = $scope["default"]; + foreach ($scope["case"] as $case => $content) { + $code .= "if($expr == $case) {\n?>$content$defaulttpl->parseVar($tokens); + $var = $scope->tpl->parseVariable($tokens); if ($tokens->is('=')) { // inline tag {var ...} $scope->is_closed = true; $tokens->next(); @@ -975,7 +1040,7 @@ class Compiler { $vars = array(); while ($tokens->valid()) { - $vars[] = $tpl->parseVar($tokens); + $vars[] = $tpl->parseVariable($tokens); } if (!$vars) { throw new InvalidUsageException("Unset must accept variable(s)"); diff --git a/src/Fenom/Render.php b/src/Fenom/Render.php index 7cf7b31..468d08a 100644 --- a/src/Fenom/Render.php +++ b/src/Fenom/Render.php @@ -175,7 +175,7 @@ class Render extends \ArrayObject */ public function isValid() { - if (count($this->_depends) === 1) { // if no external dependencies, only self + if (count($this->_depends[0]) === 1) { // if no external dependencies, only self $provider = $this->_fenom->getProvider($this->_scm); if ($provider->getLastModified($this->_name) !== $this->_time) { return false; @@ -198,7 +198,7 @@ class Render extends \ArrayObject */ public function getMacro($name) { - if(empty($this->_macros[$name])) { + if (empty($this->_macros[$name])) { throw new RuntimeException('macro not found'); } return $this->_macros[$name]; @@ -247,7 +247,7 @@ class Render extends \ArrayObject public function __get($name) { - if($name == 'info') { + if ($name == 'info') { return array( 'name' => $this->_name, 'schema' => $this->_scm, diff --git a/src/Fenom/Template.php b/src/Fenom/Template.php index 128e530..f5cb24a 100644 --- a/src/Fenom/Template.php +++ b/src/Fenom/Template.php @@ -223,10 +223,6 @@ class Template extends Render unset($comment); // cleanup break; default: -// var_dump($this->_src[$pos]); -// if($this->_src[$pos] === "\n") { -// $pos++; -// } $this->_appendText(substr($this->_src, $pos, $start - $pos)); $end = $start + 1; do { @@ -388,7 +384,7 @@ class Template extends Render "\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" . + "\t'depends' => " . var_export($this->_depends, true) . ",\n" . "\t'macros' => " . $this->_getMacrosArray() . ",\n ));\n"; } @@ -406,7 +402,7 @@ class Template extends Render $macros[] = "\t\t'" . $m["name"] . "' => function (\$tpl) {\n?>" . $m["body"] . "_code) { // evaluate template's code - eval("\$this->_code = " . $this->_getClosureSource() . ";\n\$this->_macros = ".$this->_getMacrosArray() .';'); + eval("\$this->_code = " . $this->_getClosureSource() . ";\n\$this->_macros = " . $this->_getMacrosArray() . ';'); if (!$this->_code) { throw new CompileException("Fatal error while creating the template"); } @@ -447,7 +443,6 @@ class Template extends Render */ public function addDepend(Render $tpl) { -// var_dump($tpl->getScm(),"$tpl", (new \Exception())->getTraceAsString() ); $this->_depends[$tpl->getScm()][$tpl->getName()] = $tpl->getTime(); } @@ -606,7 +601,6 @@ class Template extends Render * Parse expressions. The mix of operators and terms. * * @param Tokenizer $tokens - * @return string * @throws Error\UnexpectedTokenException */ public function parseExpr(Tokenizer $tokens) @@ -619,6 +613,18 @@ class Template extends Render // parse term $term = $this->parseTerm($tokens, $var); // term of the expression if ($term !== false) { + if($this->_options & Fenom::FORCE_VERIFY) { + $term = '(isset('.$term.') ? '.$term.' : null)'; + $var = false; + } + if ($tokens->is('|')) { + $term = $this->parseModifier($tokens, $term); + $var = false; + } + if ($tokens->is('?', '!')) { + $term = $this->parseTernary($tokens, $term, $tokens->current()); + $var = false; + } $exp[] = $term; $op = false; } else { @@ -683,14 +689,14 @@ class Template extends Render } /** - * Parse any term of expression: -2, ++$var, 'adf'|mod:4 + * Parse any term of expression: -2, ++$var, 'adf' * * @param Tokenizer $tokens - * @param bool $is_var - * @return bool|string + * @param bool $is_var is parsed term - plain variable * @throws Error\UnexpectedTokenException * @throws Error\TokenizeException * @throws \Exception + * @return bool|string */ public function parseTerm(Tokenizer $tokens, &$is_var = false) { @@ -701,63 +707,50 @@ class Template extends Render $unary = ""; } if ($tokens->is(T_LNUMBER, T_DNUMBER)) { - return $unary . $this->parseScalar($tokens, true); + $code = $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); + $code = $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; + $code = $unary . $this->parseVariable($tokens); + if ($tokens->is("(") && $tokens->hasBackList(T_STRING, T_OBJECT_OPERATOR)) { + if ($this->_options & Fenom::DENY_METHODS) { + throw new \LogicException("Forbidden to call methods"); + } + $code .= $this->parseArgs($tokens); + } elseif ($tokens->is(Tokenizer::MACRO_INCDEC)) { + $code .= $tokens->getAndNext(); } 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; - } + $code = $unary . $var; } elseif ($tokens->is(Tokenizer::MACRO_INCDEC)) { - return $unary . $this->parseVariable($tokens); + $code = $unary . $tokens->getAndNext() . $this->parseVariable($tokens); } elseif ($tokens->is("(")) { $tokens->next(); - $exp = $unary . "(" . $this->parseExpr($tokens) . ")"; + $code = $unary . "(" . $this->parseExpr($tokens) . ")"; $tokens->need(")")->next(); - return $exp; } elseif ($tokens->is(T_STRING)) { if ($tokens->isSpecialVal()) { - return $unary . $tokens->getAndNext(); + $code = $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"); } - $tokens->next(); - $func = $func . $this->parseArgs($tokens); - if ($tokens->is('|')) { - return $unary . $this->parseModifier($tokens, $func); - } else { - return $unary . $func; - } + $code = $unary . $func . $this->parseArgs($tokens->next()); } else { return false; } } elseif ($tokens->is(T_ISSET, T_EMPTY)) { $func = $tokens->getAndNext(); if ($tokens->is("(") && $tokens->isNext(T_VARIABLE)) { - $tokens->next(); - $exp = $func . "(" . $this->parseVar($tokens) . ")"; + $code = $unary . $func . "(" . $this->parseVariable($tokens->next()) . ")"; $tokens->need(')')->next(); - return $unary . $exp; } else { throw new TokenizeException("Unexpected token " . $tokens->getNext() . ", isset() and empty() accept only variables"); } @@ -765,43 +758,29 @@ class Template extends Render if ($unary) { throw new UnexpectedTokenException($tokens->back()); } - return $this->parseArray($tokens); + $code = $this->parseArray($tokens); } elseif ($unary) { - $tokens->back(); - throw new UnexpectedTokenException($tokens); + throw new UnexpectedTokenException($tokens->back()); } else { return false; } + return $code; } /** - * Parse simple variable (without modifier etc) - * - * @param Tokenizer $tokens - * @return string - */ - public function parseVar(Tokenizer $tokens) - { - $var = $tokens->get(T_VARIABLE); - $_var = '$tpl["' . substr($var, 1) . '"]'; - $tokens->next(); - $_var = $this->_var($tokens, $_var); - if ($this->_options & Fenom::FORCE_VERIFY) { - return 'isset(' . $_var . ') ? ' . $_var . ' : null'; - } else { - return $_var; - } - } - - /** + * Parse variable name: $a, $a.b, $a.b[c] * @param Tokenizer $tokens * @param $var * @return string * @throws Error\UnexpectedTokenException */ - protected function _var(Tokenizer $tokens, $var) + public function parseVariable(Tokenizer $tokens, $var = null) { + if(!$var) { + $var = '$tpl["' . substr( $tokens->get(T_VARIABLE), 1) . '"]'; + $tokens->next(); + } while ($t = $tokens->key()) { if ($t === ".") { $tokens->next(); @@ -844,62 +823,6 @@ class Template extends Render return $var; } - /** - * 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 $options set parser options - * @param string $var already parsed plain variable - * @throws \LogicException - * @throws InvalidUsageException - * @return string - */ - 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 { - $var = $this->parseVar($tokens, $options); - } - if ($tokens->is(T_OBJECT_OPERATOR)) { // parse - $var .= '->' . $tokens->getNext(T_STRING); - $tokens->next(); - } - } - - 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; - } - /** * Parse accessor */ @@ -907,16 +830,16 @@ class Template extends Render { $is_var = false; $vars = array( - 'get' => '$_GET', - 'post' => '$_POST', + 'get' => '$_GET', + 'post' => '$_POST', 'session' => '$_SESSION', - 'cookie' => '$_COOKIE', + 'cookie' => '$_COOKIE', 'request' => '$_REQUEST', - 'files' => '$_FILES', + 'files' => '$_FILES', 'globals' => '$GLOBALS', - 'server' => '$_SERVER', - 'env' => '$_ENV', - 'tpl' => '$tpl->info' + 'server' => '$_SERVER', + 'env' => '$_ENV', + 'tpl' => '$tpl->info' ); if ($this->_options & Fenom::DENY_ACCESSOR) { throw new \LogicException("Accessor are disabled"); @@ -925,7 +848,7 @@ class Template extends Render $tokens->next(); if (isset($vars[$key])) { $is_var = true; - return $this->_var($tokens, $vars[$key]); + return $this->parseVariable($tokens, $vars[$key]); } switch ($key) { case 'const': @@ -942,11 +865,7 @@ class Template extends Render throw new UnexpectedTokenException($tokens); } - if ($tokens->is('|')) { - return $this->parseModifier($tokens, $var); - } else { - return $var; - } + return $var; } /** @@ -1021,12 +940,8 @@ class Template extends Render 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_VARIABLE, '[', Tokenizer::MACRO_SCALAR, '"')) { + return '(' . $value . ' ' . $equal . '= ' . $this->parseTerm($tokens) . ')'; } elseif ($tokens->is(T_NS_SEPARATOR)) { // return $invert . '(' . $value . ' instanceof \\' . $this->parseName($tokens) . ')'; } else { @@ -1085,7 +1000,7 @@ class Template extends Render if (!$checker) { $checker = "auto"; } - return $invert . sprintf($checkers[$checker], $value, $this->parseVariable($tokens)); + return $invert . sprintf($checkers[$checker], $value, $this->parseTerm($tokens)); } else { throw new UnexpectedTokenException($tokens); } @@ -1115,30 +1030,25 @@ class Template extends Render * Parse scalar values * * @param Tokenizer $tokens - * @param bool $allow_mods + * @throws Error\UnexpectedTokenException * @return string - * @throws TokenizeException */ - public function parseScalar(Tokenizer $tokens, $allow_mods = true) + public function parseScalar(Tokenizer $tokens) { $_scalar = ""; - if ($token = $tokens->key()) { - switch ($token) { - case T_CONSTANT_ENCAPSED_STRING: - case T_LNUMBER: - case T_DNUMBER: - $_scalar .= $tokens->getAndNext(); - break; - case T_ENCAPSED_AND_WHITESPACE: - case '"': - $_scalar .= $this->parseQuote($tokens); - break; - default: - throw new TokenizeException("Unexpected scalar token '" . $tokens->current() . "'"); - } - if ($allow_mods && $tokens->is("|")) { - return $this->parseModifier($tokens, $_scalar); - } + $token = $tokens->key(); + switch ($token) { + case T_CONSTANT_ENCAPSED_STRING: + case T_LNUMBER: + case T_DNUMBER: + $_scalar .= $tokens->getAndNext(); + break; + case T_ENCAPSED_AND_WHITESPACE: + case '"': + $_scalar .= $this->parseQuote($tokens); + break; + default: + throw new UnexpectedTokenException($tokens); } return $_scalar; } @@ -1152,7 +1062,7 @@ class Template extends Render */ public function parseQuote(Tokenizer $tokens) { - if ($tokens->is('"', "`")) { + if ($tokens->is('"')) { $stop = $tokens->current(); $_str = '"'; $tokens->next(); @@ -1208,17 +1118,6 @@ class Template extends Render } } - /** - * @param Tokenizer $tokens - * @param null $first_member - */ - public function parseConcat(Tokenizer $tokens, $first_member = null) - { - $concat = array(); - if ($first_member) { - } - } - /** * Parse modifiers * |modifier:1:2.3:'string':false:$var:(4+5*$var3)|modifier2:"str {$var+3} ing":$arr.item @@ -1240,25 +1139,8 @@ class Template extends Render $args = array(); while ($tokens->is(":")) { - $token = $tokens->getNext(Tokenizer::MACRO_SCALAR, T_VARIABLE, '"', Tokenizer::MACRO_STRING, "(", "[", '$'); - - if ($tokens->is(Tokenizer::MACRO_SCALAR) || $tokens->isSpecialVal()) { - $args[] = $token; - $tokens->next(); - } elseif ($tokens->is(T_VARIABLE)) { - $args[] = $this->parseVariable($tokens, self::DENY_MODS); - } elseif ($tokens->is('$')) { - $args[] = $this->parseAccessor($tokens, $is_var); - } elseif ($tokens->is('"', '`', T_ENCAPSED_AND_WHITESPACE)) { - $args[] = $this->parseQuote($tokens); - } elseif ($tokens->is('(')) { - $args[] = $this->parseExpr($tokens); - } elseif ($tokens->is('[')) { - $args[] = $this->parseArray($tokens); - } elseif ($tokens->is(T_STRING) && $tokens->isNext('(')) { - $args[] = $tokens->getAndNext() . $this->parseArgs($tokens); - } else { - break; + if (!$args[] = $this->parseTerm($tokens->next())) { + throw new UnexpectedTokenException($tokens); } } @@ -1358,14 +1240,14 @@ class Template extends Render throw new InvalidUsageException("Macro '$name' require '$arg' argument"); } } - $n = $this->i++; + $n = sprintf('%u_%d', crc32($this->_name), $this->i++); if ($recursive) { $recursive['recursive'] = true; $body = '$tpl->getMacro("' . $name . '")->__invoke($tpl);'; } else { $body = '?>' . $macro["body"] . 'exchangeArray(' . Compiler::toArray($args) . ');' . PHP_EOL . $body . PHP_EOL . '$tpl->exchangeArray($_tpl' . $n . '); unset($_tpl' . $n . ');'; + return '$_tpl' . $n . ' = $tpl->exchangeArray(' . Compiler::toArray($args) . ');' . PHP_EOL . $body . PHP_EOL . '$tpl->exchangeArray($_tpl' . $n . '); /* X */ unset($_tpl' . $n . ');'; } /** @@ -1457,7 +1339,7 @@ class Template extends Render } else { $params[$key] = 'true'; } - } elseif ($tokens->is(Tokenizer::MACRO_SCALAR, '"', '`', T_VARIABLE, "[", '(')) { + } elseif ($tokens->is(Tokenizer::MACRO_SCALAR, '"', T_VARIABLE, "[", '(')) { $params[] = $this->parseExpr($tokens); } else { break; diff --git a/src/Fenom/Tokenizer.php b/src/Fenom/Tokenizer.php index d07c758..74765cf 100644 --- a/src/Fenom/Tokenizer.php +++ b/src/Fenom/Tokenizer.php @@ -104,8 +104,8 @@ class Tokenizer \T_LOGICAL_AND => 1, \T_LOGICAL_OR => 1, \T_LOGICAL_XOR => 1, \T_METHOD_C => 1, \T_NAMESPACE => 1, \T_NS_C => 1, \T_NEW => 1, \T_PRINT => 1, \T_PRIVATE => 1, \T_PUBLIC => 1, \T_PROTECTED => 1, \T_REQUIRE => 1, \T_REQUIRE_ONCE => 1, \T_RETURN => 1, \T_RETURN => 1, \T_STRING => 1, \T_SWITCH => 1, \T_THROW => 1, - \T_TRAIT => 1, \T_TRAIT_C => 1, \T_TRY => 1, \T_UNSET => 1, \T_VAR => 1, - \T_WHILE => 1, \T_YIELD => 1, \T_USE => 1 + \T_TRAIT => 1, \T_TRAIT_C => 1, \T_TRY => 1, \T_UNSET => 1, \T_USE => 1, \T_VAR => 1, + \T_WHILE => 1, \T_YIELD => 1 ), self::MACRO_INCDEC => array( \T_INC => 1, \T_DEC => 1 @@ -130,9 +130,10 @@ class Tokenizer \T_IS_NOT_EQUAL => 1, \T_IS_NOT_IDENTICAL => 1, \T_IS_SMALLER_OR_EQUAL => 1, ), self::MACRO_EQUALS => array( - \T_AND_EQUAL => 1, \T_CONCAT_EQUAL => 1, \T_DIV_EQUAL => 1, \T_MINUS_EQUAL => 1, \T_MOD_EQUAL => 1, + \T_AND_EQUAL => 1, \T_DIV_EQUAL => 1, \T_MINUS_EQUAL => 1, \T_MOD_EQUAL => 1, \T_MUL_EQUAL => 1, \T_OR_EQUAL => 1, \T_PLUS_EQUAL => 1, \T_SL_EQUAL => 1, \T_SR_EQUAL => 1, - \T_XOR_EQUAL => 1, '=' => 1 + \T_XOR_EQUAL => 1, '=' => 1, +// \T_CONCAT_EQUAL => 1, ), self::MACRO_SCALAR => array( \T_LNUMBER => 1, \T_DNUMBER => 1, \T_CONSTANT_ENCAPSED_STRING => 1 diff --git a/tests/cases/Fenom/TemplateTest.php b/tests/cases/Fenom/TemplateTest.php index ec2a970..be1b236 100644 --- a/tests/cases/Fenom/TemplateTest.php +++ b/tests/cases/Fenom/TemplateTest.php @@ -137,6 +137,7 @@ class TemplateTest extends TestCase array('Mod: {$date|date:"Y m d"}!', $b, 'Mod: 2012 07 26!'), array('Mod: {$tags|strip_tags}!', $b, 'Mod: my name is Legion!'), array('Mod: {$b.c|json_encode}!', $b, 'Mod: "Username"!'), + array('Mod: {($time/1024/1024)|round:2}!', $b, 'Mod: 1281.09!'), array('Mod: {time()|date:"Y m d"}!', $b, 'Mod: ' . date("Y m d") . '!'), ); } @@ -341,7 +342,7 @@ class TemplateTest extends TestCase array('Create: {var $v = 1++} Result: {$v} end', 'Fenom\Error\CompileException', "Unexpected token '++'"), array('Create: {var $v = c} Result: {$v} end', 'Fenom\Error\CompileException', "Unexpected token 'c'"), array('Create: {var $v = ($a)++} Result: {$v} end', 'Fenom\Error\CompileException', "Unexpected token '++'"), - array('Create: {var $v = --$a++} Result: {$v} end', 'Fenom\Error\CompileException', "Can not use two increments and/or decrements for one variable"), + array('Create: {var $v = --$a++} Result: {$v} end', 'Fenom\Error\CompileException', "Unexpected token '++'"), array('Create: {var $v = $a|upper++} Result: {$v} end', 'Fenom\Error\CompileException', "Unexpected token '++'"), array('Create: {var $v = max($a,2)++} Result: {$v} end', 'Fenom\Error\CompileException', "Unexpected token '++'"), array('Create: {var $v = max($a,2)} Result: {$v} end', 'Fenom\Error\CompileException', "Function max not found", Fenom::DENY_NATIVE_FUNCS), @@ -494,37 +495,40 @@ class TemplateTest extends TestCase public static function providerSwitch() { $code1 = 'Switch: {switch $a} - {case 1} one {break} - {case 2} two {break} - {case "string"} str {break} + {case 1, "one"} one + {case 2, "two"} two + {case "string"} str {default} def {/switch} end'; $code2 = 'Switch: {switch $a} - {case 1} one {break} - {case 2} two {break} - {case "string"} str {break} + {case 1, "one"} one + {case 2, "two"} two + {case "string"} str {/switch} end'; $code3 = 'Switch: {switch $a} invalid - {case 1} one {break} + {case 1, "one"} one {/switch} end'; return array( array($code1, array("a" => 1), 'Switch: one end'), + array($code1, array("a" => 'one'), 'Switch: one end'), array($code1, array("a" => 2), 'Switch: two end'), + array($code1, array("a" => 'two'), 'Switch: two end'), array($code1, array("a" => "string"), 'Switch: str end'), array($code2, array("a" => "unk"), 'Switch: end'), - array($code3, array("a" => 1), 'Switch: invalid one end'), + array($code3, array("a" => 1), 'Switch: one end'), + array($code3, array("a" => 'one'), 'Switch: one end'), ); } public static function providerSwitchInvalid() { return array( - array('Switch: {switch}{case 1} one {break}{/switch} end', 'Fenom\Error\CompileException', "Unexpected end of expression"), - array('Switch: {switch 1}{case} one {break}{/switch} end', 'Fenom\Error\CompileException', "Unexpected end of expression"), - array('Switch: {switch 1}{break}{case} one {/switch} end', 'Fenom\Error\CompileException', "Improper usage of the tag {break}"), + array('Switch: {switch}{case 1} one {/switch} end', 'Fenom\Error\CompileException', "Unexpected end of expression"), + array('Switch: {switch 1}{case} one{/switch} end', 'Fenom\Error\CompileException', "Unexpected end of expression"), + array('Switch: {switch 1}{case $var} one {/switch} end', 'Fenom\Error\CompileException', "Unexpected token '\$var' in expression"), ); } @@ -575,7 +579,7 @@ class TemplateTest extends TestCase array('For: {for} block1 {/for} end', 'Fenom\Error\CompileException', "Unexpected end of expression"), array('For: {for $a=} block1 {/for} end', 'Fenom\Error\CompileException', "Unexpected end of expression"), array('For: {for $a+1=3 to=6} block1 {/for} end', 'Fenom\Error\CompileException', "Unexpected token '+'"), - array('For: {for max($a,$b)=3 to=6} block1 {/for} end', 'Fenom\Error\CompileException', "Unexpected token 'max'"), + array('For: {for max($a,$b)=3 to=6} block1 {/for} end', 'Fenom\Error\CompileException', "Unexpected token '='"), array('For: {for to=6 $a=3} block1 {/for} end', 'Fenom\Error\CompileException', "Unexpected token 'to'"), array('For: {for index=$i $a=3 to=6} block1 {/for} end', 'Fenom\Error\CompileException', "Unexpected token 'index'"), array('For: {for first=$i $a=3 to=6} block1 {/for} end', 'Fenom\Error\CompileException', "Unexpected token 'first'"), @@ -719,7 +723,7 @@ class TemplateTest extends TestCase public function _testSandbox() { try { - var_dump($this->fenom->compileCode('{"string"|append:$.get.one}')->getBody()); + var_dump($this->fenom->setOptions(Fenom::FORCE_VERIFY)->compileCode('{if $unexist} block1 {else} block2 {/if}')->getBody()); } catch (\Exception $e) { print_r($e->getMessage() . "\n" . $e->getTraceAsString()); } @@ -875,6 +879,7 @@ class TemplateTest extends TestCase } /** + * @group switch * @dataProvider providerSwitch */ public function testSwitch($code, $vars, $result) @@ -883,6 +888,7 @@ class TemplateTest extends TestCase } /** + * @group switch-bad * @dataProvider providerSwitchInvalid */ public function testSwitchInvalid($code, $exception, $message, $options = 0)