Split parseExp (for #3)

This commit is contained in:
bzick 2013-08-02 20:29:18 +04:00
parent f1d252a3cc
commit c27df81545
4 changed files with 168 additions and 118 deletions

View File

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

View File

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

View File

@ -328,7 +328,7 @@ class Tokenizer
* @return mixed
* @throws UnexpectedTokenException
*/
public function getAndNext()
public function getAndNext(/* $token1, ... */)
{
if ($this->curr) {
$cur = $this->curr[1];

View File

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