From 6bacfd878a539655beea0ef354202cc9b0837337 Mon Sep 17 00:00:00 2001
From: Mike Bayer <mike_mp@zzzcomputing.com>
Date: Fri, 17 Nov 2006 22:32:29 +0000
Subject: [PATCH] more logic for control lines, ternaries

---
 lib/mako/lexer.py     | 18 +++++++++++++++--
 lib/mako/parsetree.py | 15 +++++++++++---
 test/lexer.py         | 46 +++++++++++++++++++++++++++++++++++++++++--
 3 files changed, 72 insertions(+), 7 deletions(-)

diff --git a/lib/mako/lexer.py b/lib/mako/lexer.py
index 27d4fcc..64309bb 100644
--- a/lib/mako/lexer.py
+++ b/lib/mako/lexer.py
@@ -11,6 +11,7 @@ class Lexer(object):
         self.lineno = 1
         self.match_position = 0
         self.tag = []
+        self.control_line = []
         
     def match(self, regexp, flags=None):
         """match the given regular expression string and flags to the current text position.
@@ -49,7 +50,15 @@ class Lexer(object):
             self.nodes.append(node)
         if isinstance(node, parsetree.Tag):
             self.tag.append(node)
-            
+        elif isinstance(node, parsetree.ControlLine):
+            if node.isend:
+                self.control_line.pop()
+            elif node.is_primary:
+                self.control_line.append(node)
+            elif len(self.control_line) and not self.control_line[-1].is_ternary(node.keyword):
+                raise exceptions.SyntaxException("Keyword '%s' not a legal ternary for keyword '%s'" % (node.keyword, self.control_line[-1].keyword), self.matched_lineno, self.matched_charpos)
+                
+                    
     def parse(self):
         length = len(self.text)
         while (True):
@@ -134,7 +143,6 @@ class Lexer(object):
                  \Z           # end of string
                 )""", re.X | re.S)
         
-        
         if match:
             text = match.group(1)
             self.append_node(parsetree.Text, text)
@@ -175,6 +183,12 @@ class Lexer(object):
                     raise exceptions.SyntaxException("Invalid control line: '%s'" % text, self.matched_lineno, self.matched_charpos)
                 (isend, keyword) = m2.group(1, 2)
                 isend = (isend is not None)
+                
+                if isend:
+                    if not len(self.control_line):
+                        raise exceptions.SyntaxException("No starting keyword '%s' for '%s'" % (keyword, text), self.matched_lineno, self.matched_charpos)
+                    elif self.control_line[-1].keyword != keyword:
+                        raise exceptions.SyntaxException("Keyword '%s' doesn't match keyword '%s'" % (text, self.control_line[-1].keyword), self.matched_lineno, self.matched_charpos)
                 self.append_node(parsetree.ControlLine, keyword, isend, text)
             else:
                 self.append_node(parsetree.Comment, text)
diff --git a/lib/mako/parsetree.py b/lib/mako/parsetree.py
index 11dd395..9a4d5e3 100644
--- a/lib/mako/parsetree.py
+++ b/lib/mako/parsetree.py
@@ -1,6 +1,6 @@
 """object model defining a Mako template."""
 
-from mako import exceptions, ast
+from mako import exceptions, ast, util
 
 class Node(object):
     """base class for a Node in the parse tree."""
@@ -18,13 +18,22 @@ class ControlLine(Node):
         (markup)
     % endif
     """
-    def __init__(self, keyword, text, isend, **kwargs):
+    def __init__(self, keyword, isend, text, **kwargs):
         super(ControlLine, self).__init__(**kwargs)
         self.text = text
         self.keyword = keyword
         self.isend = isend
+        self.is_primary = keyword in ['for','if', 'while', 'try']
+
+    def is_ternary(self, keyword):
+        """return true if the given keyword is a ternary keyword for this ControlLine"""
+        return keyword in {
+            'if':util.Set(['else', 'elif']),
+            'try':util.Set(['except', 'finally']),
+            'for':util.Set(['else'])
+        }.get(self.keyword, [])
     def __repr__(self):
-        return "ControlLine(%s, %s, %s, %s)" % (repr(self.keyword), repr(self.isend), repr(self.text), repr((self.lineno, self.pos)))
+        return "ControlLine(%s, %s, %s, %s)" % (repr(self.keyword), repr(self.text), repr(self.isend), repr((self.lineno, self.pos)))
 
 class Text(Node):
     """defines plain text in the template."""
diff --git a/test/lexer.py b/test/lexer.py
index 0129056..9cda210 100644
--- a/test/lexer.py
+++ b/test/lexer.py
@@ -125,10 +125,52 @@ text text la la
     
 """
         nodes = Lexer(template).parse()
-        print nodes
+        #print nodes
         assert repr(nodes) == r"""[Text('\ntext text la la\n', (1, 1)), ControlLine('if', 'if foo():', False, (3, 1)), Text(' mroe text la la blah blah\n', (4, 1)), ControlLine('if', 'endif', True, (5, 1)), Text('\n        and osme more stuff\n', (6, 1)), ControlLine('for', 'for l in range(1,5):', False, (8, 1)), Text('    tex tesl asdl l is ', (9, 1)), Expression('l', [], (9, 24)), Text(' kfmas d\n', (9, 28)), ControlLine('for', 'endfor', True, (10, 1)), Text('    tetx text\n    \n', (11, 1))]"""
         
+    def test_unmatched_control(self):
+        template = """
 
+        % if foo:
+            % for x in range(1,5):
+        % endif
+"""
+        try:
+            nodes = Lexer(template).parse()
+            assert False
+        except exceptions.SyntaxException, e:
+            assert str(e) == "Keyword 'endif' doesn't match keyword 'for' at line: 5 char: 1"
+
+    def test_unmatched_control_2(self):
+        template = """
+
+        % if foo:
+            % for x in range(1,5):
+            % endlala
+        % endif
+"""
+        try:
+            nodes = Lexer(template).parse()
+            assert False
+        except exceptions.SyntaxException, e:
+            assert str(e) == "Keyword 'endlala' doesn't match keyword 'for' at line: 5 char: 1"
+    
+    def test_ternary_control(self):
+        template = """
+        % if x:
+            hi
+        % elif y+7==10:
+            there
+        % elif lala:
+            lala
+        % else:
+            hi
+        % endif
+"""    
+        nodes = Lexer(template).parse()
+        #print nodes
+        assert repr(nodes) == r"""[ControlLine('if', 'if x:', False, (1, 1)), Text('            hi\n', (3, 1)), ControlLine('elif', 'elif y+7==10:', False, (4, 1)), Text('            there\n', (5, 1)), ControlLine('elif', 'elif lala:', False, (6, 1)), Text('            lala\n', (7, 1)), ControlLine('else', 'else:', False, (8, 1)), Text('            hi\n', (9, 1)), ControlLine('if', 'endif', True, (10, 1))]"""
+        
     def test_integration(self):
         template = """<%namespace name="foo" file="somefile.html"/>
  # inherit from foobar.html
@@ -152,7 +194,7 @@ text text la la
 </table>
 """
         nodes = Lexer(template).parse()
-        print nodes
+        #print nodes
         assert repr(nodes) == r"""[NamespaceTag('namespace', {'name': '"foo"', 'file': '"somefile.html"'}, (1, 1), []), Text('\n', (1, 46)), Comment('inherit from foobar.html', (2, 1)), InheritTag('inherit', {'file': '"foobar.html"'}, (3, 1), []), Text('\n\n', (3, 31)), ComponentTag('component', {'name': '"header"'}, (5, 1), ["Text('\\n     <div>header</div>\\n', (5, 27))"]), Text('\n', (7, 14)), ComponentTag('component', {'name': '"footer"'}, (8, 1), ["Text('\\n    <div> footer</div>\\n', (8, 27))"]), Text('\n\n<table>\n', (10, 14)), ControlLine('for', 'for j in data():', False, (13, 1)), Text('    <tr>\n', (14, 1)), ControlLine('for', 'for x in j:', False, (15, 1)), Text('            <td>Hello ', (16, 1)), Expression('x', ['h'], (16, 23)), Text('</td>\n', (16, 30)), ControlLine('for', 'endfor', True, (17, 1)), Text('    </tr>\n', (18, 1)), ControlLine('for', 'endfor', True, (19, 1)), Text('</table>\n', (20, 1))]"""
 
 if __name__ == '__main__':
-- 
GitLab