Add string concatenation (#3)

This commit is contained in:
bzick
2013-08-03 18:56:17 +04:00
parent 1e7cf29290
commit 09f952686b
4 changed files with 113 additions and 78 deletions

View File

@ -81,6 +81,10 @@ Operators
* `--$a` - decrement the variable and use it * `--$a` - decrement the variable and use it
* `$a--` - use the variable and decrement it * `$a--` - use the variable and decrement it
### String operator
* `$a ~ $b` - return concatenation of variables `$a` and `$b`
### Ternary operator ### Ternary operator
* `$a ? $b : $c` - returns `$b` if `$a` is not empty, and `$c` otherwise * `$a ? $b : $c` - returns `$b` if `$a` is not empty, and `$c` otherwise

View File

@ -615,36 +615,34 @@ class Template extends Render
$exp = array(); $exp = array();
$var = false; // last term was: true - variable, false - mixed $var = false; // last term was: true - variable, false - mixed
$op = false; // last exp was operator $op = false; // last exp was operator
$cond = false; // was conditional operator $cond = false; // was comparison operator
while ($tokens->valid()) { while ($tokens->valid()) {
// parse term // parse term
$term = $this->parseTerm($tokens, $var); $term = $this->parseTerm($tokens, $var); // term of the expression
if ($term !== false) { if ($term !== false) {
$exp[] = $term; $exp[] = $term;
$op = false; $op = false;
} else { } else {
break; break;
} }
if (!$tokens->valid()) { if (!$tokens->valid()) {
break; break;
} }
// parse operator // parse operator
if ($tokens->is(Tokenizer::MACRO_BINARY)) { if ($tokens->is(Tokenizer::MACRO_BINARY)) { // binary operator: $a + $b, $a <= $b, ...
if ($tokens->is(Tokenizer::MACRO_COND)) { if ($tokens->is(Tokenizer::MACRO_COND)) { // comparison operator
if ($cond) { if ($cond) {
break; break;
} }
$cond = true; $cond = true;
} }
$op = $tokens->getAndNext(); $op = $tokens->getAndNext();
} elseif ($tokens->is(Tokenizer::MACRO_EQUALS)) { } elseif ($tokens->is(Tokenizer::MACRO_EQUALS)) { // assignment operator: $a = 4, $a += 3, ...
if (!$var) { if (!$var) {
break; break;
} }
$op = $tokens->getAndNext(); $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) { if (!$exp) {
break; break;
} }
@ -658,8 +656,17 @@ class Template extends Render
} else { } else {
break; break;
} }
} elseif ($tokens->is('~')) { } elseif ($tokens->is('~')) { // string concatenation operator: 'asd' ~ $var
// string concat coming soon $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 { } else {
break; break;
} }
@ -692,79 +699,79 @@ class Template extends Render
$is_var = false; $is_var = false;
$unary = ""; $unary = "";
term: { term: {
if ($tokens->is(T_LNUMBER, T_DNUMBER)) { if ($tokens->is(T_LNUMBER, T_DNUMBER)) {
return $unary . $this->parseScalar($tokens, true); return $unary . $this->parseScalar($tokens, true);
} elseif ($tokens->is(T_CONSTANT_ENCAPSED_STRING, '"', T_ENCAPSED_AND_WHITESPACE)) { } elseif ($tokens->is(T_CONSTANT_ENCAPSED_STRING, '"', T_ENCAPSED_AND_WHITESPACE)) {
if ($unary) { if ($unary) {
throw new UnexpectedTokenException($tokens->back()); 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(); return $this->parseScalar($tokens, true);
$func = $func . $this->parseArgs($tokens); } elseif ($tokens->is(T_VARIABLE)) {
if ($tokens->is('|')) { $var = $this->parseVar($tokens);
return $unary . $this->parseModifier($tokens, $func); 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 { } 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 { } else {
return false; 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) * Parse simple variable (without modifier etc)

View File

@ -34,7 +34,9 @@ class TestCase extends \PHPUnit_Framework_TestCase
"obj" => new \StdClass, "obj" => new \StdClass,
"list" => array( "list" => array(
"a" => 1, "a" => 1,
"b" => 2 "one" => 1,
"b" => 2,
"two" => 2
), ),
0 => "empty value", 0 => "empty value",
1 => "one value", 1 => "one value",
@ -204,7 +206,11 @@ class TestCase extends \PHPUnit_Framework_TestCase
public function providerVariables() public function providerVariables()
{ {
return array(); return array(
array('$one', 1),
array('$list.one', 1),
array('$list[$$.DEVELOP]', 1),
);
} }
public static function providerObjects() public static function providerObjects()

View File

@ -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() public function _testSandbox()
{ {
try { 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) { } catch (\Exception $e) {
print_r($e->getMessage() . "\n" . $e->getTraceAsString()); print_r($e->getMessage() . "\n" . $e->getTraceAsString());
} }
@ -891,5 +901,13 @@ class TemplateTest extends TestCase
{ {
$this->exec($code, self::getVars(), $result); $this->exec($code, self::getVars(), $result);
} }
/**
* @dataProvider providerConcat
*/
public function testConcat($code, $result)
{
$this->exec($code, self::getVars(), $result);
}
} }