From c27df815457011cd524c050c5424ef53429a0786 Mon Sep 17 00:00:00 2001 From: bzick Date: Fri, 2 Aug 2013 20:29:18 +0400 Subject: [PATCH] Split parseExp (for #3) --- src/Fenom/Compiler.php | 9 + src/Fenom/Template.php | 268 +++++++++++++++++------------ src/Fenom/Tokenizer.php | 2 +- tests/cases/Fenom/TemplateTest.php | 7 +- 4 files changed, 168 insertions(+), 118 deletions(-) diff --git a/src/Fenom/Compiler.php b/src/Fenom/Compiler.php index f20f5dc..1ebb623 100644 --- a/src/Fenom/Compiler.php +++ b/src/Fenom/Compiler.php @@ -32,6 +32,15 @@ class Compiler */ public static function tagInclude(Tokenizer $tokens, Template $tpl) { + $name = false; +// if($tokens->is('[')) { +// $tokens->next(); +// if(!$name && $tokens->is(T_CONSTANT_ENCAPSED_STRING)) { +// if($tpl->getStorage()->templateExists($_name = substr($tokens->getAndNext(), 1, -1))) { +// $name = $_name; +// } +// } +// } $cname = $tpl->parsePlainArg($tokens, $name); $p = $tpl->parseParams($tokens); if ($p) { // if we have additionally variables diff --git a/src/Fenom/Template.php b/src/Fenom/Template.php index 3dc6293..ce8248d 100644 --- a/src/Fenom/Template.php +++ b/src/Fenom/Template.php @@ -603,135 +603,165 @@ class Template extends Render } /** - * Parse expressions. The mix of math operations, boolean operations, scalars, arrays and variables. + * Parse expressions. The mix of operations and terms. * - * @static * @param Tokenizer $tokens * @param bool $required - * @throws TokenizeException - * @throws UnexpectedTokenException - * @throws \Exception * @return string + * @throws Error\UnexpectedTokenException */ - public function parseExp(Tokenizer $tokens, $required = false) - { - $_exp = array(); // expression as PHP code - $term = false; // last item was variable or value. - // 0 - was operator, but trem required - // false - was operator or no one term - // true - was trem - // 1 - term is strict varaible - $cond = false; // last item was operator - while ($tokens->valid()) { - if (!$term && $tokens->is(Tokenizer::MACRO_SCALAR, '"', '`', T_ENCAPSED_AND_WHITESPACE)) { // like quoted string - $_exp[] = $this->parseScalar($tokens, true); - $term = true; - } elseif (!$term && $tokens->is(Tokenizer::MACRO_INCDEC)) { // like variable - $_exp[] = $this->parseVariable($tokens); - $term = true; - } elseif (!$term && $tokens->is(T_VARIABLE)) { // like variable too - $var = $this->parseVar($tokens); - if ($tokens->is(Tokenizer::MACRO_EQUALS)) { - $_exp[] = $var; - if ($tokens->isLast()) { - break; - } - $_exp[] = $tokens->getAndNext(); - $term = 0; - } elseif ($tokens->is(Tokenizer::MACRO_INCDEC, "|", "!", "?", '(')) { - $_exp[] = $this->parseVariable($tokens, 0, $var); - $term = true; - } else { - $_exp[] = $var; - $term = 1; - } - } elseif (!$term && $tokens->is("(")) { // open bracket - $tokens->next(); - $_exp[] = "(" . $this->parseExp($tokens, true) . ")"; - $tokens->get(")"); - $tokens->next(); - $term = 1; - } elseif ($tokens->is(T_STRING)) { - if ($term) { // parse 'in' or 'is' operators - if (!$_exp) { - break; - } - $operator = $tokens->current(); - if ($operator == "is") { - $item = array_pop($_exp); - $_exp[] = $this->parseIs($tokens, $item, $term === 1); - } elseif ($operator == "in" || ($operator == "not" && $tokens->isNextToken("in"))) { - $item = array_pop($_exp); - $_exp[] = $this->parseIn($tokens, $item, $term === 1); - } else { - break; - } - } else { // function or special value - if ($tokens->isSpecialVal()) { - $_exp[] = $tokens->getAndNext(); - } elseif ($tokens->isNext("(") && !$tokens->getWhitespace()) { - $func = $this->_fenom->getModifier($tokens->current(), $this); - if (!$func) { - throw new \Exception("Function " . $tokens->getAndNext() . " not found"); - } - $tokens->next(); - $func = $func . $this->parseArgs($tokens); - if ($tokens->is('|')) { - $_exp[] = $this->parseModifier($tokens, $func); - } else { - $_exp[] = $func; - } - } else { - break; - } - $term = true; - } - } elseif (!$term && $tokens->is(T_ISSET, T_EMPTY)) { // empty and isset operators - $func = $tokens->getAndNext(); - if ($tokens->is("(") && $tokens->isNext(T_VARIABLE)) { - $tokens->next(); - $_exp[] = $func . "(" . $this->parseVar($tokens) . ")"; - $tokens->need(')')->next(); - } else { - throw new TokenizeException("Unexpected token " . $tokens->getNext() . ", isset() and empty() accept only variables"); - } - $term = true; - } elseif (!$term && $tokens->is(Tokenizer::MACRO_UNARY)) { - if (!$tokens->isNext(T_VARIABLE, T_DNUMBER, T_LNUMBER, T_STRING, T_ISSET, T_EMPTY, '(')) { - break; - } - $_exp[] = $tokens->getAndNext(); - $term = 0; - } elseif ($tokens->is(Tokenizer::MACRO_BINARY)) { // like binary operator, see Tokenizer::MACRO_BINARY - if (!$term) { - throw new UnexpectedTokenException($tokens); - } - if ($tokens->isLast()) { - break; - } + public function parseExp(Tokenizer $tokens, $required = false) { + $exp = array(); + $var = false; // last term was: true - variable, false - mixed + $op = false; // last exp was operator + $cond = false; // was conditional operator + while($tokens->valid()) { + // parse term + $term = $this->parseTerm($tokens, $var); + if($term !== false) { + $exp[] = $term; + $op = false; + } else { + break; + } + + if(!$tokens->valid()) { + break; + } + + // parse operator + if($tokens->is(Tokenizer::MACRO_BINARY)) { if ($tokens->is(Tokenizer::MACRO_COND)) { if ($cond) { break; } $cond = true; - } elseif ($tokens->is(Tokenizer::MACRO_BOOLEAN)) { - $cond = false; } - $_exp[] = " " . $tokens->getAndNext() . " "; - $term = 0; - } elseif ($tokens->is('[')) { - $_exp[] = $this->parseArray($tokens); + $op = $tokens->getAndNext(); + } elseif($tokens->is(Tokenizer::MACRO_EQUALS)) { + if(!$var) { + break; + } + $op = $tokens->getAndNext(); + } elseif($tokens->is(T_STRING)) { + if (!$exp) { + break; + } + $operator = $tokens->current(); + if ($operator == "is") { + $item = array_pop($exp); + $exp[] = $this->parseIs($tokens, $item, $var); + } elseif ($operator == "in" || ($operator == "not" && $tokens->isNextToken("in"))) { + $item = array_pop($exp); + $exp[] = $this->parseIn($tokens, $item, $var); + } else { + break; + } + } elseif($tokens->is('~')) { + // string concat coming soon } else { break; } + if($op) { + $exp[] = $op; + } } - if ($term === 0) { + + if ($op) { throw new UnexpectedTokenException($tokens); } - if ($required && !$_exp) { + if ($required && !$exp) { throw new UnexpectedTokenException($tokens); } - return implode('', $_exp); + return implode(' ', $exp); + } + + /** + * Parse any term: -2, ++$var, 'adf'|mod:4 + * + * @param Tokenizer $tokens + * @param bool $is_var + * @return bool|string + * @throws Error\UnexpectedTokenException + * @throws Error\TokenizeException + * @throws \Exception + */ + public function parseTerm(Tokenizer $tokens, &$is_var = false) { + $is_var = false; + $unary = ""; + term: { + if($tokens->is(T_LNUMBER, T_DNUMBER)) { + return $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); + } 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; + } else { + $is_var = true; + return $var; + } + } elseif($tokens->is(Tokenizer::MACRO_INCDEC)) { + return $unary.$this->parseVariable($tokens); + } elseif($tokens->is("(")) { + $tokens->next(); + $exp = $unary."(" . $this->parseExp($tokens, true).")"; + $tokens->need(")")->next(); + return $exp; + } elseif($tokens->is(Tokenizer::MACRO_UNARY)) { + if($unary) { + throw new UnexpectedTokenException($tokens); + } + $unary = $tokens->getAndNext(); + goto term; + } elseif($tokens->is(T_STRING)) { + if ($tokens->isSpecialVal()) { + return $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; + } + } 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) . ")"; + $tokens->need(')')->next(); + return $unary.$exp; + } else { + throw new TokenizeException("Unexpected token " . $tokens->getNext() . ", isset() and empty() accept only variables"); + } + } elseif($tokens->is('[')) { + if($unary) { + throw new UnexpectedTokenException($tokens->back()); + } + return $this->parseArray($tokens); + } elseif($unary) { + $tokens->back(); + throw new UnexpectedTokenException($tokens); + } else { + return false; + } + } } /** @@ -1031,7 +1061,7 @@ class Template extends Render break; case T_ENCAPSED_AND_WHITESPACE: case '"': - $_scalar .= $this->parseSubstr($tokens); + $_scalar .= $this->parseQuote($tokens); break; default: throw new TokenizeException("Unexpected scalar token '" . $tokens->current() . "'"); @@ -1050,7 +1080,7 @@ class Template extends Render * @throws UnexpectedTokenException * @return string */ - public function parseSubstr(Tokenizer $tokens) + public function parseQuote(Tokenizer $tokens) { if ($tokens->is('"', "`")) { $stop = $tokens->current(); @@ -1108,6 +1138,16 @@ 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 @@ -1137,7 +1177,7 @@ class Template extends Render } elseif ($tokens->is(T_VARIABLE)) { $args[] = $this->parseVariable($tokens, self::DENY_MODS); } elseif ($tokens->is('"', '`', T_ENCAPSED_AND_WHITESPACE)) { - $args[] = $this->parseSubstr($tokens); + $args[] = $this->parseQuote($tokens); } elseif ($tokens->is('(')) { $args[] = $this->parseExp($tokens, true); } elseif ($tokens->is('[')) { @@ -1187,7 +1227,7 @@ class Template extends Render $key = false; $val = true; } elseif ($tokens->is('"') && !$val) { - $_arr .= $this->parseSubstr($tokens); + $_arr .= $this->parseQuote($tokens); $key = false; $val = true; } elseif ($tokens->is(T_DOUBLE_ARROW) && $val) { diff --git a/src/Fenom/Tokenizer.php b/src/Fenom/Tokenizer.php index 210b18b..abea858 100644 --- a/src/Fenom/Tokenizer.php +++ b/src/Fenom/Tokenizer.php @@ -328,7 +328,7 @@ class Tokenizer * @return mixed * @throws UnexpectedTokenException */ - public function getAndNext() + public function getAndNext(/* $token1, ... */) { if ($this->curr) { $cur = $this->curr[1]; diff --git a/tests/cases/Fenom/TemplateTest.php b/tests/cases/Fenom/TemplateTest.php index 64f8e39..8ea7e12 100644 --- a/tests/cases/Fenom/TemplateTest.php +++ b/tests/cases/Fenom/TemplateTest.php @@ -186,9 +186,10 @@ class TemplateTest extends TestCase { return array( array('If: {-"hi"} end', 'Fenom\Error\CompileException', "Unexpected token '-'"), + array('If: {-[1,2]} end', 'Fenom\Error\CompileException', "Unexpected token '-'"), array('If: {($a++)++} end', 'Fenom\Error\CompileException', "Unexpected token '++'"), array('If: {$a + * $c} end', 'Fenom\Error\CompileException', "Unexpected token '*'"), - array('If: {$a + } end', 'Fenom\Error\CompileException', "Unexpected token '+'"), + array('If: {$a + } end', 'Fenom\Error\CompileException', "Unexpected end of expression"), array('If: {$a + =} end', 'Fenom\Error\CompileException', "Unexpected token '='"), array('If: {$a + 1 =} end', 'Fenom\Error\CompileException', "Unexpected token '='"), array('If: {$a + 1 = 6} end', 'Fenom\Error\CompileException', "Unexpected token '='"), @@ -334,7 +335,7 @@ class TemplateTest extends TestCase 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), - array('Create: {var $v = 4*} Result: {$v} end', 'Fenom\Error\CompileException', "Unexpected token '*'"), + array('Create: {var $v = 4*} Result: {$v} end', 'Fenom\Error\CompileException', "Unexpected end of expression"), array('Create: {var $v = ""$a} Result: {$v} end', 'Fenom\Error\CompileException', "Unexpected token '\$a'"), array('Create: {var $v = [1,2} Result: {$v} end', 'Fenom\Error\CompileException', "Unexpected end of expression"), array('Create: {var $v = empty(2)} Result: {$v} end', 'Fenom\Error\CompileException', "Unexpected token 2, isset() and empty() accept only variables"), @@ -672,7 +673,7 @@ class TemplateTest extends TestCase public function _testSandbox() { try { - var_dump($this->fenom->compileCode('{$one.two->three[e]()}')->getBody()); + var_dump($this->fenom->compileCode('{if 0 is empty} block1 {else} block2 {/if}')->getBody()); } catch (\Exception $e) { print_r($e->getMessage() . "\n" . $e->getTraceAsString()); }