diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a82ed6..01fe4ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,60 @@ Changelog ========= +## 2.6.0 (2015-02-22) + +- Add range operator (`1..3`) +- Tag `for` now is deprecated, use tag `foreach` with range +- Internal improves + +### 2.5.4 (2015-02-19) + +- Fix bug #152 +- Add composer.lock to git + +### 2.5.3 (2015-02-19) + +- Fix bug #147 + +### 2.5.2 (2015-02-10) + +- Fix bug: unexpected array conversion when object given to {foreach} with force verify option (pull #148) + +### 2.5.1 (2015-02-10) + +- Fix bugs #144, #135 + + +## 2.5.0 (2015-02-01) + +- Internal improvement: functions accept array of template variables +- Improve `in` operator +- Fix bug #142 + +### 2.4.6 (2015-01-30) + +- Fix bug #138 + +### 2.4.5 (2015-01-30) + +Move project to organization `fenom-template` + +### 2.4.4 (2015-01-22) + +- Fix: parse error then modifier's argument converts to false + +### 2.4.3 (2015-01-08) + +- Fix #132 + +### 2.4.2 (2015-01-07) + +- Internal improvements and code cleaning + +### 2.4.2 (2015-01-07) + +- Fix bug #128 + ## 2.4.0 (2015-01-02) - Fix bugs #120, #104, #119 diff --git a/docs/ru/operators.md b/docs/ru/operators.md index fc859a4..1afd933 100644 --- a/docs/ru/operators.md +++ b/docs/ru/operators.md @@ -112,7 +112,7 @@ ```smarty -{var $b |= $flags} +{set $b |= $flags} ``` ### Операторы инкремента и декремента @@ -135,6 +135,39 @@ Fenom поддерживает префиксные и постфиксные о * `$a ~~ $b` - возвращает результат объединения сток `$a` и `$b` через пробел * `$a ~= $b` - присвоение с объединением +### Оператор интервала + +Оператор `..` позволяет создать массив данных, не выходящие за указанные ограничения. + +```smarty +{set $a = 1..4} +``` + +создаст массив `[1,2,3,4]` + +```smarty +{set $a = 'a'..'f'} +``` + +создаст массив `['a','b','c','d','e','f']` + +```smarty +{set $a = 'a'|up..'f'|up} +``` + +создаст массив `['A','B','C','D','E','F']` + +```smarty +{set $a = $min..$max} +``` + +создаст массив из значений где первый и минимальный элемент будет значение `$min`, +а максимальный и последний элемент будет занчение `$max` + +**Замечание:** +оганичения должны быть одного типа, интервал `1..'f'` преобразует `f` в `0` и будет сгенерировано `[1,0]` + + ### Тернарные операторы Еще одним условным оператором являются тернарные операторы `?:` и `!:`. diff --git a/docs/ru/tags/foreach.md b/docs/ru/tags/foreach.md index 5a31b2a..2e3d3a3 100644 --- a/docs/ru/tags/foreach.md +++ b/docs/ru/tags/foreach.md @@ -1,7 +1,8 @@ Тег {foreach} ============= -Тег `foreach` предоставляет простой способ перебора массивов. +Тег `foreach` предоставляет простой способ перебора массивов. +`Foreach` работает только с массивами, объектами и интервалами. ```smarty {foreach $list as [$key =>] $value [index=$index] [first=$first] [last=$last]} @@ -17,15 +18,19 @@ ### {foreach} -Перебор значений массива $list +Перебор значений массива $list: ```smarty {foreach $list as $value}
{$value}
{/foreach} + +{foreach 1..7 as $value} {* так же хорошо работает и с интрвелами *} +
№{$value}
+{/foreach} ``` -Перебор ключей и значений массива $list +Перебор ключей и значений массива $list: ```smarty {foreach $list as $key => $value} @@ -60,13 +65,19 @@ Переменная `$last` будет иметь значение **TRUE**, если текущая итерация является последней. +**Замечание:** +Использование `last` требует от `$list` быть **countable**. + ### {break} -Тег `{break}` используется для выхода из цикла до достижения последней итерации. Если в цикле встречается тег `{break}`, цикл завершает свою работу, и далее, выполняется код, следующий сразу за блоком цикла +Тег `{break}` используется для выхода из цикла до достижения последней итерации. +Если в цикле встречается тег `{break}`, цикл завершает свою работу, и далее, выполняется код, следующий сразу за блоком цикла ### {continue} -Тег `{continue}` используется для прерывания текущей итерации. Если в цикле встречается тег `{continue}`, часть цикла, следующая после тега, не выполняется, и начинается следующая итерация. Если текущая итерация была последней, цикл завершается. +Тег `{continue}` используется для прерывания текущей итерации. +Если в цикле встречается тег `{continue}`, часть цикла, следующая после тега, не выполняется, и начинается следующая итерация. +Если текущая итерация была последней, цикл завершается. ### {foreachelse} @@ -81,7 +92,4 @@ {/foreach} ``` -В блоке `{foreachelse}...{/foreach}` использование `{break}`, `{continue}` выбросит исключение `Fenom\CompileException` при компиляции - -**Замечание:** -Использование last требует от `$list` быть **countable**. \ No newline at end of file +В блоке `{foreachelse}...{/foreach}` использование `{break}`, `{continue}` выбросит исключение `Fenom\CompileException` при компиляции \ No newline at end of file diff --git a/sandbox/fenom.php b/sandbox/fenom.php index 2ac9aef..5c954d9 100644 --- a/sandbox/fenom.php +++ b/sandbox/fenom.php @@ -7,6 +7,6 @@ require_once __DIR__.'/../tests/tools.php'; $fenom = Fenom::factory(__DIR__.'/templates', __DIR__.'/compiled'); $fenom->setOptions(Fenom::AUTO_RELOAD); -var_dump($fenom->compile("blocks/second.tpl", false)->getBody()); +var_dump($fenom->compileCode('{$b == 5 ? "5" : "?"}')->getBody()); //$fenom->display("blocks/second.tpl", []); // $fenom->getTemplate("problem.tpl"); \ No newline at end of file diff --git a/src/Fenom/Compiler.php b/src/Fenom/Compiler.php index 235dfb6..9daf1a1 100644 --- a/src/Fenom/Compiler.php +++ b/src/Fenom/Compiler.php @@ -136,21 +136,20 @@ class Compiler $key = null; $before = $body = array(); $prepend = ""; - if ($tokens->is(T_VARIABLE)) { - $from = $scope->tpl->parseTerm($tokens, $is_var); - if($is_var) { - $check = '!empty('.$from.')'; - } else { - $scope["var"] = $scope->tpl->tmpVar(); - $prepend = $scope["var"].' = '.$from.';'; - $from = $check = $scope["var"]; - } - } elseif ($tokens->is('[')) { + if ($tokens->is('[')) { $count = 0; $from = $scope->tpl->parseArray($tokens, $count); $check = $count; } else { - throw new UnexpectedTokenException($tokens, null, "tag {foreach}"); + $from = $scope->tpl->parseExpr($tokens, $is_var); + if($is_var) { + $check = '!empty('.$from.') && (is_array('.$from.') || '.$from.' instanceof \Traversable)'; + } else { + $scope["var"] = $scope->tpl->tmpVar(); + $prepend = $scope["var"].' = '.$from.';'; + $from = $scope["var"]; + $check = 'is_array('.$from.') && count('.$from.') || ('.$from.' instanceof \Traversable)'; + } } $tokens->get(T_AS); $tokens->next(); @@ -193,9 +192,9 @@ class Compiler $body = $body ? implode("; ", $body) . ";" : ""; $scope["after"] = $scope["after"] ? implode("; ", $scope["after"]) . ";" : ""; if ($key) { - return "$prepend if($check) { $before foreach($from as $key => $value) { $body"; + return "$prepend if($check) {\n $before foreach($from as $key => $value) { $body"; } else { - return "$prepend if($check) { $before foreach($from as $value) { $body"; + return "$prepend if($check) {\n $before foreach($from as $value) { $body"; } } @@ -236,9 +235,11 @@ class Compiler * @throws Error\UnexpectedTokenException * @throws Error\InvalidUsageException * @return string + * @codeCoverageIgnore */ public static function forOpen(Tokenizer $tokens, Tag $scope) { + trigger_error("Fenom: tag {for} deprecated, use {foreach 1..4 as \$value} (in {$scope->tpl->getName()}:{$scope->line})", E_USER_DEPRECATED); $p = array( "index" => false, "first" => false, @@ -309,6 +310,7 @@ class Compiler * @param Tokenizer $tokens * @param Tag $scope * @return string + * @codeCoverageIgnore */ public static function forElse($tokens, Tag $scope) { @@ -322,6 +324,7 @@ class Compiler * @param Tokenizer $tokens * @param Tag $scope * @return string + * @codeCoverageIgnore */ public static function forClose($tokens, Tag $scope) { diff --git a/src/Fenom/Modifier.php b/src/Fenom/Modifier.php index cde94f3..04cfbbf 100644 --- a/src/Fenom/Modifier.php +++ b/src/Fenom/Modifier.php @@ -279,4 +279,15 @@ class Modifier return ""; } } + + /** + * @param string|int $from + * @param string|int $to + * @param int $step + * @return array + */ + public static function range($from, $to, $step = 1) { + $v = range($from, $to, $step); + return $v ? $v : array(); + } } diff --git a/src/Fenom/Template.php b/src/Fenom/Template.php index 5e0f631..d547891 100644 --- a/src/Fenom/Template.php +++ b/src/Fenom/Template.php @@ -35,6 +35,14 @@ class Template extends Render * Disable modifier parser. */ const DENY_MODS = 2; + /** + * Allow parse modifiers with term + */ + const TERM_MODS = 1; + /** + * Allow parse range with term + */ + const TERM_RANGE = 1; /** * @var int shared counter */ @@ -649,10 +657,11 @@ class Template extends Render * Parse expressions. The mix of operators and terms. * * @param Tokenizer $tokens + * @param bool $is_var + * @throws \Exception * @return string - * @throws Error\UnexpectedTokenException */ - public function parseExpr(Tokenizer $tokens) + public function parseExpr(Tokenizer $tokens, &$is_var = false) { $exp = array(); $var = false; // last term was: true - variable, false - mixed @@ -660,12 +669,8 @@ class Template extends Render $cond = false; // was comparison operator while ($tokens->valid()) { // parse term - $term = $this->parseTerm($tokens, $var); // term of the expression + $term = $this->parseTerm($tokens, $var, -1); // term of the expression if ($term !== false) { - if ($tokens->is('|')) { - $term = $this->parseModifier($tokens, $term); - $var = false; - } if ($tokens->is('?', '!')) { if($cond) { $term = array_pop($exp) . ' ' . $term; @@ -718,7 +723,7 @@ class Template extends Render break; } } elseif ($tokens->is('~')) { // string concatenation operator: 'asd' ~ $var - if($tokens->isNext('=')) { // ~= + if ($tokens->isNext('=')) { // ~= $exp[] = ".="; $tokens->next()->next(); } else { @@ -729,11 +734,11 @@ class Template extends Render if ($tokens->is(T_LNUMBER, T_DNUMBER)) { $concat[] = "strval(" . $this->parseTerm($tokens) . ")"; } else { - if($tokens->is('~')) { + if ($tokens->is('~')) { $tokens->next(); $concat[] = " "; } - if(!$concat[] = $this->parseTerm($tokens)) { + if (!$concat[] = $this->parseTerm($tokens)) { throw new UnexpectedTokenException($tokens); } } @@ -751,6 +756,10 @@ class Template extends Render if ($op || !$exp) { throw new UnexpectedTokenException($tokens); } + + if(count($exp) == 1 && $var) { + $is_var = true; + } return implode(' ', $exp); } @@ -759,12 +768,11 @@ class Template extends Render * * @param Tokenizer $tokens * @param bool $is_var is parsed term - plain variable - * @throws Error\UnexpectedTokenException - * @throws Error\TokenizeException + * @param int $allows * @throws \Exception * @return bool|string */ - public function parseTerm(Tokenizer $tokens, &$is_var = false) + public function parseTerm(Tokenizer $tokens, &$is_var = false, $allows = -1) { $is_var = false; if ($tokens->is(Tokenizer::MACRO_UNARY)) { @@ -773,52 +781,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)) { $code = $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"); } - return $unary . $this->parseChain($tokens, $code); + $code = $unary . $this->parseChain($tokens, $code); } elseif ($tokens->is(Tokenizer::MACRO_INCDEC)) { if($this->_options & Fenom::FORCE_VERIFY) { - return $unary . '(isset(' . $code . ') ? ' . $code . $tokens->getAndNext() . ' : null)'; + $code = $unary . '(isset(' . $code . ') ? ' . $code . $tokens->getAndNext() . ' : null)'; } else { - return $unary . $code . $tokens->getAndNext(); + $code = $unary . $code . $tokens->getAndNext(); } } else { if($this->_options & Fenom::FORCE_VERIFY) { - return $unary . '(isset(' . $code . ') ? ' . $code . ' : null)'; + $code = $unary . '(isset(' . $code . ') ? ' . $code . ' : null)'; } else { $is_var = true; - return $unary . $code; + $code = $unary . $code; } } } elseif ($tokens->is('$')) { $is_var = false; - $var = $this->parseAccessor($tokens); - return $unary . $var; + $code = $unary . $this->parseAccessor($tokens); } elseif ($tokens->is(Tokenizer::MACRO_INCDEC)) { if($this->_options & Fenom::FORCE_VERIFY) { $var = $this->parseVariable($tokens); - return $unary . '(isset(' . $var . ') ? ' . $tokens->getAndNext() . $this->parseVariable($tokens).' : null)'; + $code = $unary . '(isset(' . $var . ') ? ' . $tokens->getAndNext() . $this->parseVariable($tokens).' : null)'; } else { - return $unary . $tokens->getAndNext() . $this->parseVariable($tokens); + $code = $unary . $tokens->getAndNext() . $this->parseVariable($tokens); } } elseif ($tokens->is("(")) { $tokens->next(); $code = $unary . "(" . $this->parseExpr($tokens) . ")"; $tokens->need(")")->next(); - return $code; } 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($modifier = $tokens->current(), $this); if (!$func) { @@ -829,11 +835,11 @@ class Template extends Render } else { $call = $func . $this->parseArgs($tokens->next()); } - return $unary . $this->parseChain($tokens, $call); + $code = $unary . $this->parseChain($tokens, $call); } elseif ($tokens->isNext(T_NS_SEPARATOR, T_DOUBLE_COLON)) { $method = $this->parseStatic($tokens); $args = $this->parseArgs($tokens); - return $unary . $this->parseChain($tokens, $method . $args); + $code = $unary . $this->parseChain($tokens, $method . $args); } else { return false; } @@ -842,7 +848,6 @@ class Template extends Render if ($tokens->is("(") && $tokens->isNext(T_VARIABLE)) { $code = $unary . $func . "(" . $this->parseVariable($tokens->next()) . ")"; $tokens->need(')')->next(); - return $code; } else { throw new TokenizeException("Unexpected token " . $tokens->getNext() . ", isset() and empty() accept only variables"); } @@ -850,12 +855,22 @@ class Template extends Render if ($unary) { throw new UnexpectedTokenException($tokens->back()); } - return $this->parseArray($tokens); + $code = $this->parseArray($tokens); } elseif ($unary) { throw new UnexpectedTokenException($tokens->back()); } else { return false; } + if (($allows & self::TERM_MODS) && $tokens->is('|')) { + $code = $this->parseModifier($tokens, $code); + $is_var = false; + } + if(($allows & self::TERM_RANGE) && $tokens->is('.') && $tokens->isNext('.')) { + $tokens->next()->next(); + $code = 'range('.$code.', '.$this->parseTerm($tokens, $var, self::TERM_MODS).')'; + $is_var = false; + } + return $code; } /** @@ -901,6 +916,9 @@ class Template extends Render $key = "[" . $tokens->getAndNext() . "]"; } elseif ($tokens->is('"')) { $key = "[" . $this->parseQuote($tokens) . "]"; + } elseif($tokens->is('.')) { + $tokens->back(); + break; } else { throw new UnexpectedTokenException($tokens); } @@ -1153,16 +1171,15 @@ class Template extends Render case T_LNUMBER: case T_DNUMBER: return $tokens->getAndNext(); - break; case T_ENCAPSED_AND_WHITESPACE: case '"': return $this->parseQuote($tokens); - break; default: throw new UnexpectedTokenException($tokens); } } + /** * Parse string with or without variable * @@ -1254,7 +1271,7 @@ class Template extends Render $args = array(); while ($tokens->is(":")) { - if (($args[] = $this->parseTerm($tokens->next())) === false) { + if (($args[] = $this->parseTerm($tokens->next(), $is_var, 0)) === false) { throw new UnexpectedTokenException($tokens); } } diff --git a/src/Fenom/Tokenizer.php b/src/Fenom/Tokenizer.php index 217e151..acd8367 100644 --- a/src/Fenom/Tokenizer.php +++ b/src/Fenom/Tokenizer.php @@ -186,25 +186,48 @@ class Tokenizer if ($token === '"' || $token === "'" || $token === "`") { $this->quotes++; } - $tokens[] = array( + $token = array( $token, $token, - "", $line, ); - $i++; } elseif ($token[0] === \T_WHITESPACE) { $tokens[$i - 1][2] = $token[1]; - } else { - $tokens[] = array( - $token[0], - $token[1], - "", - $line = $token[2], - token_name($token[0]) // debug - ); - $i++; + continue; + } elseif ($token[0] === \T_DNUMBER) { // fix .1 and 1. + if(strpos($token[1], '.') === 0) { + $tokens[] = array( + '.', + '.', + "", + $line = $token[2] + ); + $token = array( + T_LNUMBER, + ltrim($token[1], '.'), + $line = $token[2] + ); + } elseif(strpos($token[1], '.') === strlen($token[1]) - 1) { + $tokens[] = array( + T_LNUMBER, + rtrim($token[1], '.'), + "", + $line = $token[2] + ); + $token = array( + '.', + '.', + $line = $token[2] + ); + } } + $tokens[] = array( + $token[0], + $token[1], + "", + $line = $token[2] + ); + $i++; } unset($tokens[-1]); diff --git a/tests/cases/Fenom/SandboxTest.php b/tests/cases/Fenom/SandboxTest.php index a5ea86b..b2f3172 100644 --- a/tests/cases/Fenom/SandboxTest.php +++ b/tests/cases/Fenom/SandboxTest.php @@ -5,6 +5,9 @@ namespace Fenom; class SandboxTest extends TestCase { + /** + * @group sb + */ public function test() { @@ -13,9 +16,9 @@ class SandboxTest extends TestCase { // return 'cutContent(); // }); // $this->tpl('welcome.tpl', '{$a}'); -// $this->fenom->addModifier('min', function () {}); +// var_dump($this->fenom->compileCode('{set $a=$one|min:0..$three|max:4}')->getBody()); // try { -// var_dump($this->fenom->compileCode('{time() + min(1, 10)}')->getBody()); +// var_dump($this->fenom->compileCode('{foreach $a as $k => $v} {/foreach}')->getBody()); // } catch (\Exception $e) { // print_r($e->getMessage() . "\n" . $e->getTraceAsString()); // while ($e->getPrevious()) { diff --git a/tests/cases/Fenom/TagsTest.php b/tests/cases/Fenom/TagsTest.php index 780b429..861520c 100644 --- a/tests/cases/Fenom/TagsTest.php +++ b/tests/cases/Fenom/TagsTest.php @@ -8,17 +8,17 @@ class TagsTest extends TestCase /** * @group test-for */ - public function testFor() - { - $this->assertRender('{for $i=0 to=3}{$i},{/for}', "0,1,2,3,"); - } +// public function testFor() +// { +// $this->assertRender('{for $i=0 to=3}{$i},{/for}', "0,1,2,3,"); +// } /** * @dataProvider providerScalars */ public function testVar($tpl_val, $val) { - $this->assertRender("{var \$a=$tpl_val}\nVar: {\$a}", "Var: " . $val); + $this->assertRender("{set \$a=$tpl_val}\nVar: {\$a}", "Var: " . $val); } /** @@ -26,7 +26,7 @@ class TagsTest extends TestCase */ public function testVarBlock($tpl_val, $val) { - $this->assertRender("{var \$a}before {{$tpl_val}} after{/var}\nVar: {\$a}", "Var: before " . $val . " after"); + $this->assertRender("{set \$a}before {{$tpl_val}} after{/set}\nVar: {\$a}", "Var: before " . $val . " after"); } /** @@ -35,14 +35,14 @@ class TagsTest extends TestCase public function testVarBlockModified($tpl_val, $val) { $this->assertRender( - "{var \$a|low|dots}before {{$tpl_val}} after{/var}\nVar: {\$a}", + "{set \$a|low|dots}before {{$tpl_val}} after{/set}\nVar: {\$a}", "Var: " . strtolower("before " . $val . " after") . "..." ); } public function testCycle() { - $this->assertRender('{for $i=0 to=4}{cycle ["one", "two"]}, {/for}', "one, two, one, two, one, "); + $this->assertRender('{foreach 0..4 as $i}{cycle ["one", "two"]}, {/foreach}', "one, two, one, two, one, "); } /** @@ -51,7 +51,7 @@ class TagsTest extends TestCase public function testCycleIndex() { $this->assertRender( - '{var $a=["one", "two"]}{for $i=1 to=5}{cycle $a index=$i}, {/for}', + '{set $a=["one", "two"]}{foreach 1..5 as $i}{cycle $a index=$i}, {/foreach}', "two, one, two, one, two, " ); } diff --git a/tests/cases/Fenom/TemplateTest.php b/tests/cases/Fenom/TemplateTest.php index 0107315..d663587 100644 --- a/tests/cases/Fenom/TemplateTest.php +++ b/tests/cases/Fenom/TemplateTest.php @@ -688,105 +688,28 @@ class TemplateTest extends TestCase ); } - public static function providerForeach() - { - $a = array( - "list" => array(1 => "one", 2 => "two", 3 => "three"), - "empty" => array(), - "obj" => new Helper("testing") - ); - return array( - array('Foreach: {foreach $list as $e} {$e}, {/foreach} end', $a, 'Foreach: one, two, three, end'), - array('Foreach: {foreach $list as $e} {$e},{break} break {/foreach} end', $a, 'Foreach: one, end'), - array( - 'Foreach: {foreach $list as $e} {$e},{continue} continue {/foreach} end', - $a, - 'Foreach: one, two, three, end' - ), - array( - 'Foreach: {foreach ["one", "two", "three"] as $e} {$e}, {/foreach} end', - $a, - 'Foreach: one, two, three, end' - ), - array( - 'Foreach: {foreach $list as $k => $e} {$k} => {$e}, {/foreach} end', - $a, - 'Foreach: 1 => one, 2 => two, 3 => three, end' - ), - array( - 'Foreach: {foreach [1 => "one", 2 => "two", 3 => "three"] as $k => $e} {$k} => {$e}, {/foreach} end', - $a, - 'Foreach: 1 => one, 2 => two, 3 => three, end' - ), - array('Foreach: {foreach $empty as $k => $e} {$k} => {$e}, {/foreach} end', $a, 'Foreach: end'), - array('Foreach: {foreach [] as $k => $e} {$k} => {$e}, {/foreach} end', $a, 'Foreach: end'), - array('Foreach: {foreach $obj->getArray() as $k => $e} {$k} => {$e}, {/foreach} end', $a, 'Foreach: 0 => 1, 1 => 2, 2 => 3, end'), - array('Foreach: {foreach $unexists as $k => $e} {$k} => {$e}, {/foreach} end', $a, 'Foreach: end'), - array( - 'Foreach: {foreach $empty as $k => $e} {$k} => {$e}, {foreachelse} empty {/foreach} end', - $a, - 'Foreach: empty end' - ), - array( - 'Foreach: {foreach $list as $e index=$i} {$i}: {$e}, {/foreach} end', - $a, - 'Foreach: 0: one, 1: two, 2: three, end' - ), - array( - 'Foreach: {foreach $list as $k => $e index=$i} {$i}: {$k} => {$e}, {/foreach} end', - $a, - 'Foreach: 0: 1 => one, 1: 2 => two, 2: 3 => three, end' - ), - array( - 'Foreach: {foreach $empty as $k => $e index=$i} {$i}: {$k} => {$e}, {foreachelse} empty {/foreach} end', - $a, - 'Foreach: empty end' - ), - array( - 'Foreach: {foreach $list as $k => $e first=$f index=$i} {if $f}first{/if} {$i}: {$k} => {$e}, {/foreach} end', - $a, - 'Foreach: first 0: 1 => one, 1: 2 => two, 2: 3 => three, end' - ), - array( - 'Foreach: {foreach $list as $k => $e last=$l first=$f index=$i} {if $f}first{/if} {$i}: {$k} => {$e}, {if $l}last{/if} {/foreach} end', - $a, - 'Foreach: first 0: 1 => one, 1: 2 => two, 2: 3 => three, last end' - ), - array( - 'Foreach: {foreach $empty as $k => $e last=$l first=$f index=$i} {if $f}first{/if} {$i}: {$k} => {$e}, {if $l}last{/if} {foreachelse} empty {/foreach} end', - $a, - 'Foreach: empty end' - ), - array( - 'Foreach: {foreach [1 => "one", 2 => "two", 3 => "three"] as $k => $e last=$l first=$f index=$i} {if $f}first{/if} {$i}: {$k} => {$e}, {if $l}last{/if} {/foreach} end', - $a, - 'Foreach: first 0: 1 => one, 1: 2 => two, 2: 3 => three, last end' - ), - ); - } - public static function providerForeachInvalid() { return array( array( 'Foreach: {foreach} {$e}, {/foreach} end', 'Fenom\Error\CompileException', - "Unexpected end of tag {foreach}" + "Unexpected end of expression" ), array( 'Foreach: {foreach $list} {$e}, {/foreach} end', 'Fenom\Error\CompileException', "Unexpected end of expression" ), - array( - 'Foreach: {foreach $list+1 as $e} {$e}, {/foreach} end', - 'Fenom\Error\CompileException', - "Unexpected token '+'" - ), +// array( +// 'Foreach: {foreach $list+1 as $e} {$e}, {/foreach} end', +// 'Fenom\Error\CompileException', +// "Unexpected token '+'" +// ), array( 'Foreach: {foreach array_random() as $e} {$e}, {/foreach} end', 'Fenom\Error\CompileException', - "Unexpected token 'array_random'" + "Function array_random not found" ), array( 'Foreach: {foreach $list as $e+1} {$e}, {/foreach} end', @@ -826,7 +749,7 @@ class TemplateTest extends TestCase array( 'Foreach: {foreach last=$l $list as $e } {$e}, {/foreach} end', 'Fenom\Error\CompileException', - "Unexpected token 'last' in tag {foreach}" + "Unexpected token 'last' in expression" ), array( 'Foreach: {foreach $list as $e unknown=1} {$e}, {/foreach} end', @@ -966,95 +889,6 @@ class TemplateTest extends TestCase ); } - public static function providerFor() - { - $a = array("c" => 1, "s" => 1, "m" => 3); - return array( - array('For: {for $a=4 to=6} $a: {$a}, {/for} end', $a, 'For: $a: 4, $a: 5, $a: 6, end'), - array('For: {for $a=4 step=2 to=10} $a: {$a}, {/for} end', $a, 'For: $a: 4, $a: 6, $a: 8, $a: 10, end'), - array('For: {for $a=4 step=-2 to=0} $a: {$a}, {/for} end', $a, 'For: $a: 4, $a: 2, $a: 0, end'), - array('For: {for $a=$c step=$s to=$m} $a: {$a}, {/for} end', $a, 'For: $a: 1, $a: 2, $a: 3, end'), - array('For: {for $a=-1 step=-max(1,2) to=-5} $a: {$a}, {/for} end', $a, 'For: $a: -1, $a: -3, $a: -5, end'), - array('For: {for $a=4 step=2 to=10} $a: {$a}, {break} break {/for} end', $a, 'For: $a: 4, end'), - array( - 'For: {for $a=4 step=2 to=8} $a: {$a}, {continue} continue {/for} end', - $a, - 'For: $a: 4, $a: 6, $a: 8, end' - ), - array( - 'For: {for $a=4 step=2 to=8 index=$i} $a{$i}: {$a}, {/for} end', - $a, - 'For: $a0: 4, $a1: 6, $a2: 8, end' - ), - array( - 'For: {for $a=4 step=2 to=8 index=$i first=$f} {if $f}first{/if} $a{$i}: {$a}, {/for} end', - $a, - 'For: first $a0: 4, $a1: 6, $a2: 8, end' - ), - array( - 'For: {for $a=4 step=2 to=8 index=$i first=$f last=$l} {if $f} first {/if} $a{$i}: {$a}, {if $l} last {/if} {/for} end', - $a, - 'For: first $a0: 4, $a1: 6, $a2: 8, last end' - ), - array('For: {for $a=1 to=-1 } $a: {$a}, {forelse} empty {/for} end', $a, 'For: empty end'), - array( - 'For: {for $a=1 to=-1 index=$i first=$f last=$l} {if $f} first {/if} $a{$i}: {$a}, {if $l} last {/if} {forelse} empty {/for} end', - $a, - 'For: empty end' - ), - ); - } - - public static function providerForInvalid() - { - return array( - 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 '='" - ), - 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'" - ), - array( - 'For: {for last=$i $a=3 to=6} block1 {/for} end', - 'Fenom\Error\CompileException', - "Unexpected token 'last'" - ), - array( - 'For: {for $a=4 to=6 unk=4} block1 {/for} end', - 'Fenom\Error\CompileException', - "Unknown parameter 'unk'" - ), - array( - 'For: {for $a=4 to=6 step=0} block1 {/for} end', - 'Fenom\Error\CompileException', - "Invalid step value" - ), - array( - 'For: {for $a=4 to=6} $a: {$a}, {forelse} {break} {/for} end', - 'Fenom\Error\CompileException', - "Improper usage of the tag {break}" - ), - array( - 'For: {for $a=4 to=6} $a: {$a}, {forelse} {continue} {/for} end', - 'Fenom\Error\CompileException', - "Improper usage of the tag {continue}" - ), - ); - } - public static function providerLayersInvalid() { return array( @@ -1070,14 +904,14 @@ class TemplateTest extends TestCase ), array('Layers: {blah} end', 'Fenom\Error\CompileException', "Unexpected tag 'blah'"), array( - 'Layers: {for $a=4 to=6} block1 {if 1} {forelse} {/if} {/for} end', + 'Layers: {foreach 4..6 as $a} block1 {if 1} {foreachelse} {/if} {/foreach} end', 'Fenom\Error\CompileException', - "Unexpected tag 'forelse' (this tag can be used with 'for')" + "Unexpected tag 'foreachelse' (this tag can be used with 'foreach')" ), array( - 'Layers: {for $a=4 to=6} block1 {if 1} {/for} {/if} end', + 'Layers: {foreach 4..6 as $a} block1 {if 1} {/foreach} {/if} end', 'Fenom\Error\CompileException', - "Unexpected closing of the tag 'for'" + "Unexpected closing of the tag 'foreach'" ), array( 'Layers: {switch 1} {if 1} {case 1} {/if} {/switch} end', @@ -1464,11 +1298,101 @@ class TemplateTest extends TestCase $this->exec(__FUNCTION__ . ": $code end", $vars, __FUNCTION__ . ": $result end"); } + + public static function providerForeach() + { + $a = array( + "list" => array(1 => "one", 2 => "two", 3 => "three"), + "empty" => array(), + "obj" => new Helper("testing") + ); + return array( + array('Foreach: {foreach $list as $e} {$e}, {/foreach} end', $a, 'Foreach: one, two, three, end'), + array('Foreach: {foreach $list as $e} {$e},{break} break {/foreach} end', $a, 'Foreach: one, end'), + array( + 'Foreach: {foreach $list as $e} {$e},{continue} continue {/foreach} end', + $a, + 'Foreach: one, two, three, end' + ), + array( + 'Foreach: {foreach ["one", "two", "three"] as $e} {$e}, {/foreach} end', + $a, + 'Foreach: one, two, three, end' + ), + array( + 'Foreach: {foreach $list as $k => $e} {$k} => {$e}, {/foreach} end', + $a, + 'Foreach: 1 => one, 2 => two, 3 => three, end' + ), + array( + 'Foreach: {foreach [1 => "one", 2 => "two", 3 => "three"] as $k => $e} {$k} => {$e}, {/foreach} end', + $a, + 'Foreach: 1 => one, 2 => two, 3 => three, end' + ), + array('Foreach: {foreach $empty as $k => $e} {$k} => {$e}, {/foreach} end', $a, 'Foreach: end'), + array('Foreach: {foreach [] as $k => $e} {$k} => {$e}, {/foreach} end', $a, 'Foreach: end'), + array('Foreach: {foreach $obj->getArray() as $k => $e} {$k} => {$e}, {/foreach} end', $a, 'Foreach: 0 => 1, 1 => 2, 2 => 3, end'), + array('Foreach: {foreach $unexists as $k => $e} {$k} => {$e}, {/foreach} end', $a, 'Foreach: end'), + array( + 'Foreach: {foreach $empty as $k => $e} {$k} => {$e}, {foreachelse} empty {/foreach} end', + $a, + 'Foreach: empty end' + ), + array( + 'Foreach: {foreach $list as $e index=$i} {$i}: {$e}, {/foreach} end', + $a, + 'Foreach: 0: one, 1: two, 2: three, end' + ), + array( + 'Foreach: {foreach $list as $k => $e index=$i} {$i}: {$k} => {$e}, {/foreach} end', + $a, + 'Foreach: 0: 1 => one, 1: 2 => two, 2: 3 => three, end' + ), + array( + 'Foreach: {foreach $empty as $k => $e index=$i} {$i}: {$k} => {$e}, {foreachelse} empty {/foreach} end', + $a, + 'Foreach: empty end' + ), + array( + 'Foreach: {foreach $list as $k => $e first=$f index=$i} {if $f}first{/if} {$i}: {$k} => {$e}, {/foreach} end', + $a, + 'Foreach: first 0: 1 => one, 1: 2 => two, 2: 3 => three, end' + ), + array( + 'Foreach: {foreach $list as $k => $e last=$l first=$f index=$i} {if $f}first{/if} {$i}: {$k} => {$e}, {if $l}last{/if} {/foreach} end', + $a, + 'Foreach: first 0: 1 => one, 1: 2 => two, 2: 3 => three, last end' + ), + array( + 'Foreach: {foreach $empty as $k => $e last=$l first=$f index=$i} {if $f}first{/if} {$i}: {$k} => {$e}, {if $l}last{/if} {foreachelse} empty {/foreach} end', + $a, + 'Foreach: empty end' + ), + array( + 'Foreach: {foreach [1 => "one", 2 => "two", 3 => "three"] as $k => $e last=$l first=$f index=$i} {if $f}first{/if} {$i}: {$k} => {$e}, {if $l}last{/if} {/foreach} end', + $a, + 'Foreach: first 0: 1 => one, 1: 2 => two, 2: 3 => three, last end' + ), + array( + 'Foreach: {foreach 1..3 as $k => $e} {$k} => {$e}, {/foreach} end', + $a, + 'Foreach: 0 => 1, 1 => 2, 2 => 3, end' + ), + array( + 'Foreach: {foreach $.get.items as $e} {$e}, {/foreach} end', + $a, + 'Foreach: one, two, three, end' + ), + ); + } + /** * @dataProvider providerForeach + * @backupGlobals */ public function testForeach($code, $vars, $result) { + $_GET['items'] = array('one', 'two', 'three'); $this->exec($code, $vars, $result); } @@ -1483,18 +1407,18 @@ class TemplateTest extends TestCase /** * @dataProvider providerFor */ - public function testFor($code, $vars, $result) - { - $this->exec($code, $vars, $result); - } +// public function testFor($code, $vars, $result) +// { +// $this->exec($code, $vars, $result); +// } /** * @dataProvider providerForInvalid */ - public function testForInvalid($code, $exception, $message, $options = 0) - { - $this->execError($code, $exception, $message, $options); - } +// public function testForInvalid($code, $exception, $message, $options = 0) +// { +// $this->execError($code, $exception, $message, $options); +// } /** * @group testIgnores @@ -1629,5 +1553,39 @@ class TemplateTest extends TestCase { $this->execError($code, $exception, $message, $options); } + + + public static function providerRange() + { + return array( + array('{set $a=1..3}', "1,2,3,"), + array('{set $a="a".."f"}', "a,b,c,d,e,f,"), + array('{set $a=1.."f"}', "1,0,"), + array('{set $a="a"..2}', "0,1,2,"), + array('{set $a=$one..$three}', "1,2,3,"), + array('{set $a=$one..3}', "1,2,3,"), + array('{set $a=1..$three}', "1,2,3,"), + array('{set $a=$one..$three++}', "1,2,3,"), + array('{set $a=$one..++$three}', "1,2,3,4,"), + array('{set $a=$one--..$three++}', "1,2,3,"), + array('{set $a=--$one..++$three}', "0,1,2,3,4,"), + array('{set $a="a"|up.."f"|up}', "A,B,C,D,E,F,"), + array('{set $a=$one|min:0..$three|max:4}', "0,1,2,3,4,"), + array('{set $a=$one|min:0..4}', "0,1,2,3,4,"), + array('{set $a=0..$three|max:4}', "0,1,2,3,4,"), + ); + } + + /** + * @dataProvider providerRange + * @group testRange + * @param string $code + * @param string $result + */ + public function testRange($code, $result) + { + $this->exec($code.'{foreach $a as $v}{$v},{/foreach}', self::getVars(), $result); + } + } diff --git a/tests/cases/Fenom/TokenizerTest.php b/tests/cases/Fenom/TokenizerTest.php index 4f47898..02dca99 100644 --- a/tests/cases/Fenom/TokenizerTest.php +++ b/tests/cases/Fenom/TokenizerTest.php @@ -43,8 +43,7 @@ class TokenizerTest extends \PHPUnit_Framework_TestCase T_STRING, 'please', ' ', - 1, - 'T_STRING' + 1 ), $tokens->curr ); @@ -110,4 +109,13 @@ class TokenizerTest extends \PHPUnit_Framework_TestCase $this->assertNull($tokens->undef); } + public function testFixFloats() { + $text = "1..3"; + $tokens = new Tokenizer($text); + $this->assertTrue($tokens->is(T_LNUMBER)); + $this->assertTrue($tokens->next()->is('.')); + $this->assertTrue($tokens->next()->is('.')); + $this->assertTrue($tokens->next()->is(T_LNUMBER)); + } + }