diff --git a/src/Mustache/Compiler.php b/src/Mustache/Compiler.php index 74aeb7b..3c5b7ee 100644 --- a/src/Mustache/Compiler.php +++ b/src/Mustache/Compiler.php @@ -47,8 +47,8 @@ class Compiler { $code = ''; $level++; foreach ($tree as $node) { - switch (is_string($node) ? 'text' : $node[Tokenizer::TAG]) { - case '#': + switch (is_string($node) ? 'text' : $node[Tokenizer::TYPE]) { + case Tokenizer::T_SECTION: $code .= $this->section( $node[Tokenizer::NODES], $node[Tokenizer::NAME], @@ -60,7 +60,7 @@ class Compiler { ); break; - case '^': + case Tokenizer::T_INVERTED: $code .= $this->invertedSection( $node[Tokenizer::NODES], $node[Tokenizer::NAME], @@ -68,8 +68,8 @@ class Compiler { ); break; - case '<': - case '>': + case Tokenizer::T_PARTIAL: + case Tokenizer::T_PARTIAL_2: $code .= $this->partial( $node[Tokenizer::NAME], isset($node[Tokenizer::INDENT]) ? $node[Tokenizer::INDENT] : '', @@ -77,15 +77,15 @@ class Compiler { ); break; - case '{': - case '&': + case Tokenizer::T_UNESCAPED: + case Tokenizer::T_UNESCAPED_2: $code .= $this->variable($node[Tokenizer::NAME], false, $level); break; - case '!': + case Tokenizer::T_COMMENT: break; - case '_v': + case Tokenizer::T_ESCAPED: $code .= $this->variable($node[Tokenizer::NAME], true, $level); break; diff --git a/src/Mustache/Parser.php b/src/Mustache/Parser.php index 270877b..d115ce9 100644 --- a/src/Mustache/Parser.php +++ b/src/Mustache/Parser.php @@ -49,13 +49,13 @@ class Parser { if ($token === null) { continue; } elseif (is_array($token)) { - switch ($token[Tokenizer::TAG]) { - case '#': - case '^': + switch ($token[Tokenizer::TYPE]) { + case Tokenizer::T_SECTION: + case Tokenizer::T_INVERTED: $nodes[] = $this->buildTree($tokens, $token); break; - case '/': + case Tokenizer::T_END_SECTION: if (!isset($parent)) { throw new \LogicException('Unexpected closing tag: /'. $token[Tokenizer::NAME]); } diff --git a/src/Mustache/Tokenizer.php b/src/Mustache/Tokenizer.php index 16a6c8f..226f091 100644 --- a/src/Mustache/Tokenizer.php +++ b/src/Mustache/Tokenizer.php @@ -24,40 +24,47 @@ class Tokenizer { const IN_TAG = 2; // Token types - const T_SECTION = 1; - const T_INVERTED = 2; - const T_END_SECTION = 3; - const T_COMMENT = 4; - const T_PARTIAL = 5; - const T_PARTIAL_2 = 6; - const T_DELIM_CHANGE = 7; - const T_ESCAPED = 8; - const T_UNESCAPED = 9; - const T_UNESCAPED_2 = 10; + const T_SECTION = '#'; + const T_INVERTED = '^'; + const T_END_SECTION = '/'; + const T_COMMENT = '!'; + const T_PARTIAL = '>'; + const T_PARTIAL_2 = '<'; + const T_DELIM_CHANGE = '='; + const T_ESCAPED = '_v'; + const T_UNESCAPED = '{'; + const T_UNESCAPED_2 = '&'; - // Token types map + // Valid token types private static $tagTypes = array( - '#' => self::T_SECTION, - '^' => self::T_INVERTED, - '/' => self::T_END_SECTION, - '!' => self::T_COMMENT, - '>' => self::T_PARTIAL, - '<' => self::T_PARTIAL_2, - '=' => self::T_DELIM_CHANGE, - '_v' => self::T_ESCAPED, - '{' => self::T_UNESCAPED, - '&' => self::T_UNESCAPED_2, + self::T_SECTION => true, + self::T_INVERTED => true, + self::T_END_SECTION => true, + self::T_COMMENT => true, + self::T_PARTIAL => true, + self::T_PARTIAL_2 => true, + self::T_DELIM_CHANGE => true, + self::T_ESCAPED => true, + self::T_UNESCAPED => true, + self::T_UNESCAPED_2 => true, + ); + + // Interpolated tags + private static $interpolatedTags = array( + self::T_ESCAPED => true, + self::T_UNESCAPED => true, + self::T_UNESCAPED_2 => true, ); // Token properties - const NODES = 'nodes'; - const TAG = 'tag'; + const TYPE = 'type'; const NAME = 'name'; const OTAG = 'otag'; const CTAG = 'ctag'; const INDEX = 'index'; const END = 'end'; const INDENT = 'indent'; + const NODES = 'nodes'; private $state; private $tagType; @@ -104,14 +111,21 @@ class Tokenizer { break; case self::IN_TAG_TYPE: + $i += strlen($this->otag) - 1; - $tag = isset(self::$tagTypes[$text[$i + 1]]) ? self::$tagTypes[$text[$i + 1]] : null; - $this->tagType = $tag ? $text[$i + 1] : '_v'; - if ($this->tagType === '=') { + if (isset(self::$tagTypes[$text[$i + 1]])) { + $tag = $text[$i + 1]; + $this->tagType = $tag; + } else { + $tag = null; + $this->tagType = self::T_ESCAPED; + } + + if ($this->tagType === self::T_DELIM_CHANGE) { $i = $this->changeDelimiters($text, $i); $this->state = self::IN_TEXT; } else { - if ($tag) { + if ($tag !== null) { $i++; } $this->state = self::IN_TAG; @@ -122,21 +136,25 @@ class Tokenizer { default: if ($this->tagChange($this->ctag, $text, $i)) { $this->tokens[] = array( - self::TAG => $this->tagType, + self::TYPE => $this->tagType, self::NAME => trim($this->buffer), self::OTAG => $this->otag, self::CTAG => $this->ctag, - self::INDEX => ($this->tagType == '/') ? $this->seenTag - strlen($this->otag) : $i + strlen($this->ctag) + self::INDEX => ($this->tagType == self::T_END_SECTION) ? $this->seenTag - strlen($this->otag) : $i + strlen($this->ctag) ); $this->buffer = ''; $i += strlen($this->ctag) - 1; $this->state = self::IN_TEXT; - if ($this->tagType == '{') { + if ($this->tagType == self::T_UNESCAPED) { if ($this->ctag == '}}') { $i++; } else { - $this->cleanTripleStache($this->tokens[count($this->tokens) - 1]); + // Clean up `{{{ tripleStache }}}` style tokens. + $lastName = $this->tokens[count($this->tokens) - 1][self::NAME]; + if (substr($lastName, -1) === '}') { + $this->tokens[count($this->tokens) - 1][self::NAME] = trim(substr($lastName, 0, -1)); + } } } } else { @@ -185,8 +203,8 @@ class Tokenizer { $tokensCount = count($this->tokens); for ($j = $this->lineStart; $j < $tokensCount; $j++) { $token = $this->tokens[$j]; - if (is_array($token) && isset(self::$tagTypes[$token[self::TAG]])) { - if (self::$tagTypes[$token[self::TAG]] >= self::T_ESCAPED) { + if (is_array($token) && isset(self::$tagTypes[$token[self::TYPE]])) { + if (isset(self::$interpolatedTags[$token[self::TYPE]])) { return false; } } elseif (is_string($token)) { @@ -210,7 +228,7 @@ class Tokenizer { $tokensCount = count($this->tokens); for ($j = $this->lineStart; $j < $tokensCount; $j++) { if (!is_array($this->tokens[$j])) { - if (isset($this->tokens[$j+1]) && is_array($this->tokens[$j+1]) && $this->tokens[$j+1][self::TAG] == '>') { + if (isset($this->tokens[$j+1]) && is_array($this->tokens[$j+1]) && $this->tokens[$j+1][self::TYPE] == self::T_PARTIAL) { $this->tokens[$j+1][self::INDENT] = (string) $this->tokens[$j]; } @@ -245,17 +263,6 @@ class Tokenizer { return $closeIndex + strlen($close) - 1; } - /** - * Clean up `{{{ tripleStache }}}` style tokens. - * - * @param array &$token - */ - private function cleanTripleStache(&$token) { - if (substr($token[self::NAME], -1) === '}') { - $token[self::NAME] = trim(substr($token[self::NAME], 0, -1)); - } - } - /** * Test whether it's time to change tags. * diff --git a/test/Mustache/Test/CompilerTest.php b/test/Mustache/Test/CompilerTest.php index 0a34a00..4afe7e4 100644 --- a/test/Mustache/Test/CompilerTest.php +++ b/test/Mustache/Test/CompilerTest.php @@ -50,11 +50,11 @@ class CompilerTest extends \PHPUnit_Framework_TestCase { 'foo', "\n", array( - Tokenizer::TAG => '_v', + Tokenizer::TYPE => Tokenizer::T_ESCAPED, Tokenizer::NAME => 'name', ), array( - Tokenizer::TAG => '_v', + Tokenizer::TYPE => Tokenizer::T_ESCAPED, Tokenizer::NAME => '.', ), "'bar'", @@ -79,6 +79,6 @@ class CompilerTest extends \PHPUnit_Framework_TestCase { */ public function testCompilerThrowsUnknownNodeTypeException() { $compiler = new Compiler; - $compiler->compile('', array(array(Tokenizer::TAG => 'invalid')), 'SomeClass'); + $compiler->compile('', array(array(Tokenizer::TYPE => 'invalid')), 'SomeClass'); } } diff --git a/test/Mustache/Test/ParserTest.php b/test/Mustache/Test/ParserTest.php index 17c2fb8..473a16a 100644 --- a/test/Mustache/Test/ParserTest.php +++ b/test/Mustache/Test/ParserTest.php @@ -43,11 +43,11 @@ class ParserTest extends \PHPUnit_Framework_TestCase { array( array(array( - Tokenizer::TAG => '_v', + Tokenizer::TYPE => Tokenizer::T_ESCAPED, Tokenizer::NAME => 'name' )), array(array( - Tokenizer::TAG => '_v', + Tokenizer::TYPE => Tokenizer::T_ESCAPED, Tokenizer::NAME => 'name' )), ), @@ -56,31 +56,31 @@ class ParserTest extends \PHPUnit_Framework_TestCase { array( 'foo', array( - Tokenizer::TAG => '^', + Tokenizer::TYPE => Tokenizer::T_INVERTED, Tokenizer::INDEX => 123, - Tokenizer::NAME => 'parent' + Tokenizer::NAME => 'parent' ), array( - Tokenizer::TAG => '_v', - Tokenizer::NAME => 'name' + Tokenizer::TYPE => Tokenizer::T_ESCAPED, + Tokenizer::NAME => 'name' ), array( - Tokenizer::TAG => '/', + Tokenizer::TYPE => Tokenizer::T_END_SECTION, Tokenizer::INDEX => 456, - Tokenizer::NAME => 'parent' + Tokenizer::NAME => 'parent' ), 'bar', ), array( 'foo', array( - Tokenizer::TAG => '^', - Tokenizer::NAME => 'parent', + Tokenizer::TYPE => Tokenizer::T_INVERTED, + Tokenizer::NAME => 'parent', Tokenizer::INDEX => 123, - Tokenizer::END => 456, + Tokenizer::END => 456, Tokenizer::NODES => array( array( - Tokenizer::TAG => '_v', + Tokenizer::TYPE => Tokenizer::T_ESCAPED, Tokenizer::NAME => 'name' ), ), @@ -107,9 +107,9 @@ class ParserTest extends \PHPUnit_Framework_TestCase { array( array( array( - Tokenizer::TAG => '#', + Tokenizer::TYPE => Tokenizer::T_SECTION, + Tokenizer::NAME => 'parent', Tokenizer::INDEX => 123, - Tokenizer::NAME => 'parent' ), ), ), @@ -118,9 +118,9 @@ class ParserTest extends \PHPUnit_Framework_TestCase { array( array( array( - Tokenizer::TAG => '^', + Tokenizer::TYPE => Tokenizer::T_INVERTED, + Tokenizer::NAME => 'parent', Tokenizer::INDEX => 123, - Tokenizer::NAME => 'parent' ), ), ), @@ -129,9 +129,9 @@ class ParserTest extends \PHPUnit_Framework_TestCase { array( array( array( - Tokenizer::TAG => '/', + Tokenizer::TYPE => Tokenizer::T_END_SECTION, + Tokenizer::NAME => 'parent', Tokenizer::INDEX => 123, - Tokenizer::NAME => 'parent' ), ), ), @@ -140,24 +140,24 @@ class ParserTest extends \PHPUnit_Framework_TestCase { array( array( array( - Tokenizer::TAG => '#', + Tokenizer::TYPE => Tokenizer::T_SECTION, + Tokenizer::NAME => 'parent', Tokenizer::INDEX => 123, - Tokenizer::NAME => 'parent' ), array( - Tokenizer::TAG => '#', + Tokenizer::TYPE => Tokenizer::T_SECTION, + Tokenizer::NAME => 'child', Tokenizer::INDEX => 123, - Tokenizer::NAME => 'child' ), array( - Tokenizer::TAG => '/', + Tokenizer::TYPE => Tokenizer::T_END_SECTION, + Tokenizer::NAME => 'parent', Tokenizer::INDEX => 123, - Tokenizer::NAME => 'parent' ), array( - Tokenizer::TAG => '/', + Tokenizer::TYPE => Tokenizer::T_END_SECTION, + Tokenizer::NAME => 'child', Tokenizer::INDEX => 123, - Tokenizer::NAME => 'child' ), ), ), diff --git a/test/Mustache/Test/TokenizerTest.php b/test/Mustache/Test/TokenizerTest.php index 64c0250..6782a87 100644 --- a/test/Mustache/Test/TokenizerTest.php +++ b/test/Mustache/Test/TokenizerTest.php @@ -45,7 +45,7 @@ class TokenizerTest extends \PHPUnit_Framework_TestCase { null, array( array( - Tokenizer::TAG => '_v', + Tokenizer::TYPE => Tokenizer::T_ESCAPED, Tokenizer::NAME => 'name', Tokenizer::OTAG => '{{', Tokenizer::CTAG => '}}', @@ -65,7 +65,7 @@ class TokenizerTest extends \PHPUnit_Framework_TestCase { '<<< >>>', array( array( - Tokenizer::TAG => '_v', + Tokenizer::TYPE => Tokenizer::T_ESCAPED, Tokenizer::NAME => 'name', Tokenizer::OTAG => '<<<', Tokenizer::CTAG => '>>>', @@ -79,7 +79,7 @@ class TokenizerTest extends \PHPUnit_Framework_TestCase { null, array( array( - Tokenizer::TAG => '{', + Tokenizer::TYPE => Tokenizer::T_UNESCAPED, Tokenizer::NAME => 'a', Tokenizer::OTAG => '{{', Tokenizer::CTAG => '}}', @@ -87,7 +87,7 @@ class TokenizerTest extends \PHPUnit_Framework_TestCase { ), "\n", array( - Tokenizer::TAG => '#', + Tokenizer::TYPE => Tokenizer::T_SECTION, Tokenizer::NAME => 'b', Tokenizer::OTAG => '{{', Tokenizer::CTAG => '}}', @@ -95,14 +95,14 @@ class TokenizerTest extends \PHPUnit_Framework_TestCase { ), null, array( - Tokenizer::TAG => '_v', + Tokenizer::TYPE => Tokenizer::T_ESCAPED, Tokenizer::NAME => 'c', Tokenizer::OTAG => '|', Tokenizer::CTAG => '|', Tokenizer::INDEX => 37, ), array( - Tokenizer::TAG => '/', + Tokenizer::TYPE => Tokenizer::T_END_SECTION, Tokenizer::NAME => 'b', Tokenizer::OTAG => '|', Tokenizer::CTAG => '|', @@ -110,7 +110,7 @@ class TokenizerTest extends \PHPUnit_Framework_TestCase { ), "\n", array( - Tokenizer::TAG => '{', + Tokenizer::TYPE => Tokenizer::T_UNESCAPED, Tokenizer::NAME => 'd', Tokenizer::OTAG => '|', Tokenizer::CTAG => '|', @@ -119,8 +119,6 @@ class TokenizerTest extends \PHPUnit_Framework_TestCase { ) ), - - ); } }