changeset 131:2ad83f1d337c trunk

* Support for line numbers in exceptions in expression evaluation (#22). * Fix bug in expression evaluation when item access was used inside a lambda or list comprehension. Thanks to Kevin Dangoor for reporting the problem.
author cmlenz
date Fri, 04 Aug 2006 13:07:52 +0000
parents 6edc71acb642
children dc42cb3c02dc
files markup/eval.py markup/tests/eval.py
diffstat 2 files changed, 56 insertions(+), 29 deletions(-) [+]
line wrap: on
line diff
--- a/markup/eval.py
+++ b/markup/eval.py
@@ -16,6 +16,7 @@
 import __builtin__
 from compiler import ast, parse
 from compiler.pycodegen import ExpressionCodeGenerator
+import new
 
 from markup.core import Stream
 
@@ -99,15 +100,24 @@
     tree = xform.visit(tree)
 
     if isinstance(filename, unicode):
-        # pycodegen doesn't like unicode in the filename
+        # unicode file names not allowed for code objects
         filename = filename.encode('utf-8', 'replace')
-    tree.filename = filename or '<string>'
+    elif not filename:
+        filename = '<string>'
+    tree.filename = '<string>'
+    if lineno <= 0:
+        lineno = 1
 
     gen = ExpressionCodeGenerator(tree)
-    if lineno >= 0:
-        gen.emit('SET_LINENO', lineno)
+    gen.optimized = True
+    code = gen.getCode()
 
-    return gen.getCode()
+    # We'd like to just set co_firstlineno, but it's readonly. So we need to
+    # clone the code object while adjusting the line number
+    return new.code(0, code.co_nlocals, code.co_stacksize,
+                    code.co_flags | 0x0040, code.co_code, code.co_consts,
+                    code.co_names, code.co_varnames, filename, code.co_name,
+                    lineno, code.co_lnotab, (), ())
 
 def _lookup_name(data, name, locals=None):
     val = data.get(name)
@@ -257,37 +267,30 @@
     for template expressions.
     """
 
-    def visitGetattr(self, node, *args, **kwargs):
-        return ast.CallFunc(ast.Name('_lookup_attr'),
-            [ast.Name('data'), self.visit(node.expr, *args, **kwargs),
-             ast.Const(node.attrname)]
-        )
+    def visitGetattr(self, node, locals_=False):
+        return ast.CallFunc(ast.Name('_lookup_attr'), [
+            ast.Name('data'), self.visit(node.expr, locals_=locals_),
+            ast.Const(node.attrname)
+        ])
 
-    def visitLambda(self, node, *args, **kwargs):
-        old_lookup_locals = kwargs.get('lookup_locals', False)
-        kwargs['lookup_locals'] = True
-        node.code = self.visit(node.code, *args, **kwargs)
+    def visitLambda(self, node, locals_=False):
+        node.code = self.visit(node.code, locals_=True)
         node.filename = '<string>' # workaround for bug in pycodegen
-        kwargs['lookup_locals'] = old_lookup_locals
         return node
 
-    def visitListComp(self, node, *args, **kwargs):
-        old_lookup_locals = kwargs.get('lookup_locals', False)
-        kwargs['lookup_locals'] = True
-        node.expr = self.visit(node.expr, *args, **kwargs)
-        node.quals = map(lambda x: self.visit(x, *args, **kwargs), node.quals)
-        kwargs['lookup_locals'] = old_lookup_locals
+    def visitListComp(self, node, locals_=False):
+        node.expr = self.visit(node.expr, locals_=True)
+        node.quals = map(lambda x: self.visit(x, locals_=True), node.quals)
         return node
 
-    def visitName(self, node, *args, **kwargs):
+    def visitName(self, node, locals_=False):
         func_args = [ast.Name('data'), ast.Const(node.name)]
-        if kwargs.get('lookup_locals'):
+        if locals_:
             func_args.append(ast.CallFunc(ast.Name('locals'), []))
         return ast.CallFunc(ast.Name('_lookup_name'), func_args)
-        return node
 
-    def visitSubscript(self, node, *args, **kwargs):
-        return ast.CallFunc(ast.Name('_lookup_item'),
-            [ast.Name('data'), self.visit(node.expr, *args, **kwargs),
-             ast.Tuple(map(self.visit, node.subs, *args, **kwargs))]
-        )
+    def visitSubscript(self, node, locals_=False):
+        return ast.CallFunc(ast.Name('_lookup_item'), [
+            ast.Name('data'), self.visit(node.expr, locals_=locals_),
+            ast.Tuple(map(lambda x: self.visit(x, locals_=locals_), node.subs))
+        ])
--- a/markup/tests/eval.py
+++ b/markup/tests/eval.py
@@ -12,6 +12,7 @@
 # history and logs, available at hhttp://markup.edgewall.org/log/.
 
 import doctest
+import sys
 import unittest
 
 from markup.eval import Expression
@@ -214,6 +215,29 @@
         self.assertEqual([2, 3, 4, 5, 6],
                          expr.evaluate({'numbers': range(5), 'offset': 2}))
 
+    def test_list_comprehension_with_getattr(self):
+        items = [{'name': 'a', 'value': 1}, {'name': 'b', 'value': 2}]
+        expr = Expression("[i.name for i in items if i.value > 1]")
+        self.assertEqual(['b'], expr.evaluate({'items': items}))
+
+    def test_list_comprehension_with_getitem(self):
+        items = [{'name': 'a', 'value': 1}, {'name': 'b', 'value': 2}]
+        expr = Expression("[i['name'] for i in items if i['value'] > 1]")
+        self.assertEqual(['b'], expr.evaluate({'items': items}))
+
+    def test_error_position(self):
+        expr = Expression("nothing()", filename='index.html', lineno=50)
+        try:
+            expr.evaluate({})
+            self.fail('Expected TypeError')
+        except TypeError, e:
+            exc_type, exc_value, exc_traceback = sys.exc_info()
+            frame = exc_traceback.tb_next
+            while frame.tb_next:
+                frame = frame.tb_next
+            self.assertEqual('index.html', frame.tb_frame.f_code.co_filename)
+            self.assertEqual(50, frame.tb_lineno)
+
 
 def suite():
     suite = unittest.TestSuite()
Copyright (C) 2012-2017 Edgewall Software