Merge remote-tracking branch 'refs/remotes/origin/develop'

This commit is contained in:
bzick 2013-09-02 17:41:50 +04:00
commit 7a84cf4ceb
14 changed files with 314 additions and 283 deletions

View File

@ -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

View File

@ -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'
));

View File

@ -1,8 +1,5 @@
Greeting,
{if $user}
{$user.name}!
{else}
anonymous?
{/if}
{import 'macros.tpl' as mc}
3
A1
{mc.factorial num=10}
A2

View File

@ -0,0 +1,5 @@
{macro factorial(num)}
{if $num}
{$num} {macro.factorial num=$num-1} {$num}
{/if}
{/macro}

View File

@ -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]}
```

View File

@ -42,9 +42,9 @@ Documentation
* [if](./tags/if.md), `elseif` and `else`
* [foreach](./tags/foreach.md), `foreaelse`, `break` and `continue`
* [for](./tags/for.md), `forelse`, `break` and `continue`
* [switch](./tags/switch.md), `case`, `default` and `break`
* [switch](./tags/switch.md), `case`, `default`
* [cycle](./tags/cycle.md)
* [include](./tags/include.md)
* [include](./tags/include.md), `insert`
* [extends](./tags/extends.md), `use`, `block` and `parent`
* [filter](./tags/filter.md)
* [ignore](./tags/ignore.md)

View File

@ -13,4 +13,33 @@ Tag {include} [RU]
{include "about.tpl" page=$item limit=50}
```
Все изменения переменных в подключаемом шаблоне не будут воздействовать на родительский шаблон.
Все изменения переменных в подключаемом шаблоне не будут воздействовать на родительский шаблон.
### {insert}
The tag insert template code instead self.
* No dynamic name allowed
* No variables as attribute allowed
For example, main.tpl:
```smarty
a: {$a}
{insert 'b.tpl'}
c: {$c}
```
b.tpl:
```
b: {$b}
```
Во время разбора шаблона код шаблона `b.tpl` будет вставлен в код шаблона `main.tpl` как есть:
```smarty
a: {$a}
b: {$b}
c: {$c}
```

View File

@ -5,9 +5,36 @@ Tag {switch}
{switch <condition>}
{case <value1>}
...
{case <value2>}
{case <value2>, <value3>, ...}
...
{case <value3>}
...
{default}
...
{/switch}
```
For example,
```smarty
{switch $type}
{case 'new'}
It is new item
{case 'current', 'new'}
It is new or current item
{case 'current'}
It is current item
{case 'new'}
It is new item, again
{default}
I don't know the type {$type}
{/switch}
```
set `$type = 'new'`, then template output
```
It is new item
It is new or current item
It is new item, again
```

View File

@ -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;
}
}

View File

@ -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() . '<?php $tpl->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() . '<?php $tpl->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() . '<?php';
}
/**
* Open tag {if ...}
@ -117,7 +135,7 @@ class Compiler
$key = null;
$before = $body = array();
if ($tokens->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<?php\n} else";
}
$code .= " {\n?>$default<?php\n}";
return $code;
}
@ -353,25 +437,6 @@ class Compiler
}
}
/**
* Tag {default}
*
* @static
* @param Tokenizer $tokens
* @param Scope $scope
* @return string
*/
public static function tagDefault($tokens, Scope $scope)
{
$code = 'default: ';
if ($scope["switch"]) {
unset($scope["no-break"], $scope["no-continue"]);
$code = $scope["switch"] . "\n" . $code;
$scope["switch"] = "";
}
return $code;
}
/**
* Tag {break}
*
@ -698,7 +763,7 @@ class Compiler
*/
public static function varOpen(Tokenizer $tokens, Scope $scope)
{
$var = $scope->tpl->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)");

View File

@ -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,

View File

@ -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"] . "<?php\n}";
}
}
return "array(\n" . implode(",\n", $macros).")";
return "array(\n" . implode(",\n", $macros) . ")";
} else {
return 'array()';
}
@ -432,7 +428,7 @@ class Template extends Render
{
if (!$this->_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"] . '<?php';
}
return '$_tpl' . $n . ' = $tpl->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;

View File

@ -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

View File

@ -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)