diff --git a/lib/mako/lexer.py b/lib/mako/lexer.py index 27d4fccb62771bce5d26146ae3541679a532890e..64309bb1904a8d618af621715d7d8113d54bfe5b 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 11dd3955ab64d3cd6aec933815192752763f0705..9a4d5e368c1747c3a12a8b5583d3908213a3c43d 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 01290565ddc3f897e1e26d51458c979450135c38..9cda210ea24256e9ada9f84b299c63245ae784f4 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__':