Fix: cycle tag. Update comments. Add tests.

This commit is contained in:
bzick 2013-06-01 02:00:43 +04:00
parent 7453dbcf2b
commit f42d0cff5d
4 changed files with 66 additions and 37 deletions

View File

@ -14,22 +14,24 @@ use Cytro\Template,
* Cytro Template Engine
*/
class Cytro {
const VERSION = '1.0.1';
const VERSION = '1.0';
/* Compiler types */
const INLINE_COMPILER = 1;
const BLOCK_COMPILER = 2;
const INLINE_FUNCTION = 3;
const BLOCK_FUNCTION = 4;
const MODIFIER = 5;
/* Options */
const DENY_METHODS = 0x10;
const DENY_INLINE_FUNCS = 0x20;
const FORCE_INCLUDE = 0x40;
const AUTO_RELOAD = 0x80;
const FORCE_COMPILE = 0xF0;
const DISABLE_CACHE = 0x1F0;
/* Default parsers */
const DEFAULT_CLOSE_COMPILER = 'Cytro\Compiler::stdClose';
const DEFAULT_FUNC_PARSER = 'Cytro\Compiler::stdFuncParser';
const DEFAULT_FUNC_OPEN = 'Cytro\Compiler::stdFuncOpen';
@ -204,7 +206,11 @@ class Cytro {
'import' => array(
'type' => self::INLINE_COMPILER,
'parser' => 'Cytro\Compiler::tagImport'
)
),
'cycle' => array(
'type' => self::INLINE_COMPILER,
'parser' => 'Cytro\Compiler::tagCycle'
)
);
/**

View File

@ -696,21 +696,31 @@ class Compiler {
* @throws ImproperUseException
*/
public static function tagCycle(Tokenizer $tokens, Template $tpl) {
$exp = $tpl->parseExp($tokens, true);
if($tokens->is("[")) {
$exp = $tpl->parseArray($tokens);
} else {
$exp = $tpl->parseExp($tokens, true);
}
if($tokens->valid()) {
$p = $tpl->parseParams($tokens);
if(empty($p["index"])) {
throw new ImproperUseException("Cycle may contain only index attribute");
} else {
return __CLASS__.'::cycle((array)'.$exp.', '.$p["index"].');';
return 'echo '.__CLASS__.'::cycle('.$exp.', '.$p["index"].')';
}
} else {
$var = $tpl->tmpVar();
return "is_array($exp) ? ".__CLASS__.'::cycle('.$exp.", isset($var) ? $var++ : ($var = 0) ) : $exp";
return 'echo '.__CLASS__.'::cycle('.$exp.", isset($var) ? ++$var : ($var = 0) )";
}
}
public static function cycle($vals, $index) {
/**
* Runtime cycle callback
* @param mixed $vals
* @param $index
* @return mixed
*/
public static function cycle($vals, $index) {
return $vals[$index % count($vals)];
}

View File

@ -18,10 +18,19 @@ use Cytro;
*/
class Template extends Render {
const DENY_ARRAY = 1;
const DENY_MODS = 2;
/**
* Disable array parser.
*/
const DENY_ARRAY = 1;
/**
* Disable modifier parser.
*/
const DENY_MODS = 2;
const EXTENDED = 0x1000;
/**
* Template was extended
*/
const EXTENDED = 0x1000;
/**
* @var int shared counter
@ -56,7 +65,6 @@ class Template extends Render {
/**
* @var int
*/
private $_pos = 0;
private $_line = 1;
private $_post = array();
/**
@ -131,25 +139,22 @@ class Template extends Render {
* @throws CompileException
*/
public function compile() {
if(!isset($this->_src)) { // already compiled
return;
}
$end = $pos = 0;
while(($start = strpos($this->_src, '{', $pos)) !== false) { // search open-symbol of tags
switch($this->_src[$start + 1]) { // check next character
case "\n": case "\r": case "\t": case " ": case "}": // ignore the tag
$pos = $start + 1; // try find tags after the current char
$pos = $start + 1;
continue 2;
case "*": // if comment block
$end = strpos($this->_src, '*}', $start) + 1; // finding end of the comment block
case "*": // if comments
$end = strpos($this->_src, '*}', $start) + 1; // find end of the comment block
if($end === false) {
throw new CompileException("Unclosed comment block in line {$this->_line}", 0, 1, $this->_name, $this->_line);
}
$this->_appendText(substr($this->_src, $pos, $start - $pos));
$comment = substr($this->_src, $start, $end - $start); // read the comment block for processing
$this->_line += substr_count($comment, "\n"); // count skipped lines in comment block
unset($comment);
$pos = $end + 1; // seek pointer
$this->_line += substr_count($comment, "\n"); // count lines in comments
unset($comment); // cleanup
$pos = $end + 1;
continue 2;
}
$frag = substr($this->_src, $pos, $start - $pos); // variable $frag contains chars after previous '}' and current '{'
@ -177,7 +182,7 @@ class Template extends Render {
$tokens = new Tokenizer($_tag); // tokenize the tag
if($tokens->isIncomplete()) { // all strings finished?
$from = $end + 1;
goto reparse; // find another close-symbol
goto reparse; // need next close-symbol
}
$this->_appendCode( $this->_tag($tokens) , $tag); // start the tag lexer
$pos = $end + 1; // move search-pointer to end of the tag
@ -187,7 +192,7 @@ class Template extends Render {
}
}
unset($frag);
unset($frag, $_tag, $tag); // cleanup
}
gc_collect_cycles();
$this->_appendText(substr($this->_src, $end ? $end + 1 : 0));
@ -202,7 +207,7 @@ class Template extends Render {
}
throw new CompileException("Unclosed tag".(count($_names) == 1 ? "" : "s").": ".implode(", ", $_names), 0, 1, $this->_name, $_line);
}
unset($this->_src);
$this->_src = ""; // cleanup
if($this->_post) {
foreach($this->_post as $cb) {
call_user_func_array($cb, array(&$this->_body, $this));
@ -1065,5 +1070,4 @@ class Template extends Render {
class CompileException extends \ErrorException {}
class SecurityException extends CompileException {}
class ImproperUseException extends \LogicException {}
class ReparseTagException extends \Exception {}
class ImproperUseException extends \LogicException {}

View File

@ -5,14 +5,14 @@ namespace Cytro;
class TagsTest extends TestCase {
// public function _testSandbox() {
// try {
// var_dump($this->cytro->compileCode(" literal: { \$a} end")->getBody());
// } catch(\Exception $e) {
// echo "$e";
// }
// exit;
// }
public function _testSandbox() {
try {
var_dump($this->cytro->compileCode('{for $i=0 to=5}{cycle ["one", "two"]}, {/for}')->getBody());
} catch(\Exception $e) {
echo "$e";
}
exit;
}
/**
* @dataProvider providerScalars
@ -35,13 +35,22 @@ class TagsTest extends TestCase {
$this->assertRender("{var \$a|low|dots}before {{$tpl_val}} after{/var}\nVar: {\$a}", "\nVar: ".strtolower("before ".$val." after")."...");
}
public function testCycle() {
public function testCycle() {
$this->assertRender('{for $i=0 to=4}{cycle ["one", "two"]}, {/for}', "one, two, one, two, one, ");
}
/**
*
*/
public function testCycleIndex() {
$this->assertRender('{var $a=["one", "two"]}{for $i=1 to=5}{cycle $a index=$i}, {/for}', "two, one, two, one, two, ");
}
public function testFilter() {
/**
* @dataProvider providerScalars
*/
public function testFilter($tpl_val, $val) {
$this->assertRender("{filter|up} before {{$tpl_val}} after {/filter}", strtoupper(" before {$val} after "));
}
}