track brackets nesting in lexer
authornikic <+@ni-po.com>
Fri, 31 Dec 2010 16:26:55 +0000 (17:26 +0100)
committerFabien Potencier <fabien.potencier@gmail.com>
Mon, 3 Jan 2011 06:48:09 +0000 (07:48 +0100)
lib/Twig/Lexer.php

index f0a1ab5..3dfeba6 100644 (file)
@@ -24,6 +24,7 @@ class Twig_Lexer implements Twig_LexerInterface
     protected $lineno;
     protected $end;
     protected $state;
+    protected $brackets;
 
     protected $env;
     protected $filename;
@@ -34,10 +35,10 @@ class Twig_Lexer implements Twig_LexerInterface
     const STATE_BLOCK = 1;
     const STATE_VAR   = 2;
 
-    const REGEX_NAME        = '/[A-Za-z_][A-Za-z0-9_]*/A';
-    const REGEX_NUMBER      = '/[0-9]+(?:\.[0-9]+)?/A';
-    const REGEX_STRING      = '/"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\'/As';
-    const REGEX_PUNCTUATION = '/[\[\](){}?:.,|]/A';
+    const REGEX_NAME   = '/[A-Za-z_][A-Za-z0-9_]*/A';
+    const REGEX_NUMBER = '/[0-9]+(?:\.[0-9]+)?/A';
+    const REGEX_STRING = '/"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\'/As';
+    const PUNCTUATION  = '()[]{}?:.,|';
 
     public function __construct(Twig_Environment $env, array $options = array())
     {
@@ -72,6 +73,7 @@ class Twig_Lexer implements Twig_LexerInterface
         $this->end = strlen($this->code);
         $this->tokens = array();
         $this->state = self::STATE_DATA;
+        $this->brackets = array();
 
         while ($this->cursor < $this->end) {
             // dispatch to the lexing functions depending
@@ -93,6 +95,11 @@ class Twig_Lexer implements Twig_LexerInterface
 
         $this->pushToken(Twig_Token::EOF_TYPE);
 
+        if (!empty($this->brackets)) {
+            list($expect, $lineno) = array_pop($this->brackets);
+            throw new Twig_Error_Syntax(sprintf('Unclosed "%s"', $expect), $lineno, $this->filename);
+        }
+
         if (isset($mbEncoding)) {
             mb_internal_encoding($mbEncoding);
         }
@@ -167,7 +174,7 @@ class Twig_Lexer implements Twig_LexerInterface
 
     protected function lexBlock()
     {
-        if (preg_match('/\s*'.preg_quote($this->options['tag_block'][1], '/').'/A', $this->code, $match, null, $this->cursor)) {
+        if (empty($this->brackets) && preg_match('/\s*'.preg_quote($this->options['tag_block'][1], '/').'/A', $this->code, $match, null, $this->cursor)) {
             $this->pushToken(Twig_Token::BLOCK_END_TYPE);
             $this->moveCursor($match[0]);
             $this->state = self::STATE_DATA;
@@ -185,7 +192,7 @@ class Twig_Lexer implements Twig_LexerInterface
 
     protected function lexVar()
     {
-        if (preg_match('/\s*'.preg_quote($this->options['tag_variable'][1], '/').'/A', $this->code, $match, null, $this->cursor)) {
+        if (empty($this->brackets) && preg_match('/\s*'.preg_quote($this->options['tag_variable'][1], '/').'/A', $this->code, $match, null, $this->cursor)) {
             $this->pushToken(Twig_Token::VAR_END_TYPE);
             $this->moveCursor($match[0]);
             $this->state = self::STATE_DATA;
@@ -222,9 +229,25 @@ class Twig_Lexer implements Twig_LexerInterface
             $this->moveCursor($match[0]);
         }
         // punctuation
-        elseif (preg_match(self::REGEX_PUNCTUATION, $this->code, $match, null, $this->cursor)) {
-            $this->pushToken(Twig_Token::PUNCTUATION_TYPE, $match[0]);
-            $this->moveCursor($match[0]);
+        elseif (false !== strpos(self::PUNCTUATION, $this->code[$this->cursor])) {
+            // opening bracket
+            if (false !== strpos('([{', $this->code[$this->cursor])) {
+                $this->brackets[] = array($this->code[$this->cursor], $this->lineno);
+            }
+            // closing bracket
+            elseif (false !== strpos(')]}', $this->code[$this->cursor])) {
+                if (empty($this->brackets)) {
+                    throw new Twig_Error_Syntax(sprintf('Unexpected "%s"', $this->code[$this->cursor]), $this->lineno, $this->filename);
+                }
+
+                list($expect, $lineno) = array_pop($this->brackets);
+                if ($this->code[$this->cursor] != strtr($expect, '([{', ')]}')) {
+                    throw new Twig_Error_Syntax(sprintf('Unclosed "%s"', $expect), $lineno, $this->filename);
+                }
+            }
+
+            $this->pushToken(Twig_Token::PUNCTUATION_TYPE, $this->code[$this->cursor]);
+            ++$this->cursor;
         }
         // strings
         elseif (preg_match(self::REGEX_STRING, $this->code, $match, null, $this->cursor)) {
@@ -270,7 +293,7 @@ class Twig_Lexer implements Twig_LexerInterface
         $regex = array();
         foreach ($operators as $operator => $length) {
             // an operator that ends with a character must be followed by
-            // a whitespace or a parenthese
+            // a whitespace or a parenthesis
             if (ctype_alpha($operator[$length - 1])) {
                 $regex[] = preg_quote($operator, '/').'(?=[ ()])';
             } else {