From 09f952686b146f5a86ff3bb301b9397e04780182 Mon Sep 17 00:00:00 2001 From: bzick Date: Sat, 3 Aug 2013 18:56:17 +0400 Subject: [PATCH] Add string concatenation (#3) --- docs/operators.md | 4 + src/Fenom/Template.php | 157 +++++++++++++++-------------- tests/TestCase.php | 10 +- tests/cases/Fenom/TemplateTest.php | 20 +++- 4 files changed, 113 insertions(+), 78 deletions(-) diff --git a/docs/operators.md b/docs/operators.md index ad264a4..c0dd526 100644 --- a/docs/operators.md +++ b/docs/operators.md @@ -81,6 +81,10 @@ Operators * `--$a` - decrement the variable and use it * `$a--` - use the variable and decrement it +### String operator + +* `$a ~ $b` - return concatenation of variables `$a` and `$b` + ### Ternary operator * `$a ? $b : $c` - returns `$b` if `$a` is not empty, and `$c` otherwise diff --git a/src/Fenom/Template.php b/src/Fenom/Template.php index 924dc48..8d6c16e 100644 --- a/src/Fenom/Template.php +++ b/src/Fenom/Template.php @@ -615,36 +615,34 @@ class Template extends Render $exp = array(); $var = false; // last term was: true - variable, false - mixed $op = false; // last exp was operator - $cond = false; // was conditional operator + $cond = false; // was comparison operator while ($tokens->valid()) { // parse term - $term = $this->parseTerm($tokens, $var); + $term = $this->parseTerm($tokens, $var); // term of the expression 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 ($tokens->is(Tokenizer::MACRO_BINARY)) { // binary operator: $a + $b, $a <= $b, ... + if ($tokens->is(Tokenizer::MACRO_COND)) { // comparison operator if ($cond) { break; } $cond = true; } $op = $tokens->getAndNext(); - } elseif ($tokens->is(Tokenizer::MACRO_EQUALS)) { + } elseif ($tokens->is(Tokenizer::MACRO_EQUALS)) { // assignment operator: $a = 4, $a += 3, ... if (!$var) { break; } $op = $tokens->getAndNext(); - } elseif ($tokens->is(T_STRING)) { + } elseif ($tokens->is(T_STRING)) { // test or containment operator: $a in $b, $a is set, ... if (!$exp) { break; } @@ -658,8 +656,17 @@ class Template extends Render } else { break; } - } elseif ($tokens->is('~')) { - // string concat coming soon + } elseif ($tokens->is('~')) { // string concatenation operator: 'asd' ~ $var + $concat = array(array_pop($exp)); + while($tokens->is('~')) { + $tokens->next(); + if($tokens->is(T_LNUMBER, T_DNUMBER)) { + $concat[] = "strval(".$this->parseTerm($tokens).")"; + } else { + $concat[] = $this->parseTerm($tokens); + } + } + $exp[] = "(".implode(".", $concat).")"; } else { break; } @@ -692,79 +699,79 @@ class Template extends Render $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"); + 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()); } - $tokens->next(); - $func = $func . $this->parseArgs($tokens); - if ($tokens->is('|')) { - return $unary . $this->parseModifier($tokens, $func); + 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 { - return $unary . $func; + $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; } - } 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; } } - } /** * Parse simple variable (without modifier etc) diff --git a/tests/TestCase.php b/tests/TestCase.php index 4883d3e..8d4b56f 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -34,7 +34,9 @@ class TestCase extends \PHPUnit_Framework_TestCase "obj" => new \StdClass, "list" => array( "a" => 1, - "b" => 2 + "one" => 1, + "b" => 2, + "two" => 2 ), 0 => "empty value", 1 => "one value", @@ -204,7 +206,11 @@ class TestCase extends \PHPUnit_Framework_TestCase public function providerVariables() { - return array(); + return array( + array('$one', 1), + array('$list.one', 1), + array('$list[$$.DEVELOP]', 1), + ); } public static function providerObjects() diff --git a/tests/cases/Fenom/TemplateTest.php b/tests/cases/Fenom/TemplateTest.php index 4df1a58..c439331 100644 --- a/tests/cases/Fenom/TemplateTest.php +++ b/tests/cases/Fenom/TemplateTest.php @@ -676,10 +676,20 @@ class TemplateTest extends TestCase ); } + public static function providerConcat() { + return array( + array('{"string" ~ $one ~ up("end")}', "string1END"), + array('{"string" ~ $one++ ~ "end"}', "string1end"), + array('{"string" ~ ++$one ~ "end"}', "string2end"), + array('{"string" ~ "one" ~ "end"}', "stringoneend"), + array('{"string" ~ 1 ~ "end"}', "string1end"), + ); + } + public function _testSandbox() { try { - var_dump($this->fenom->compileCode('{$a!"no way":"right"}')->getBody()); + var_dump($this->fenom->compileCode('{$a++~"hi"~time("Y:m:d")}')->getBody()); } catch (\Exception $e) { print_r($e->getMessage() . "\n" . $e->getTraceAsString()); } @@ -891,5 +901,13 @@ class TemplateTest extends TestCase { $this->exec($code, self::getVars(), $result); } + + /** + * @dataProvider providerConcat + */ + public function testConcat($code, $result) + { + $this->exec($code, self::getVars(), $result); + } }