Mercurial > genshi > mirror
comparison markup/eval.py @ 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 | c9f0a26e28a2 |
children | dc42cb3c02dc |
comparison
equal
deleted
inserted
replaced
130:6edc71acb642 | 131:2ad83f1d337c |
---|---|
14 """Support for "safe" evaluation of Python expressions.""" | 14 """Support for "safe" evaluation of Python expressions.""" |
15 | 15 |
16 import __builtin__ | 16 import __builtin__ |
17 from compiler import ast, parse | 17 from compiler import ast, parse |
18 from compiler.pycodegen import ExpressionCodeGenerator | 18 from compiler.pycodegen import ExpressionCodeGenerator |
19 import new | |
19 | 20 |
20 from markup.core import Stream | 21 from markup.core import Stream |
21 | 22 |
22 __all__ = ['Expression'] | 23 __all__ = ['Expression'] |
23 | 24 |
97 tree = parse(source, 'eval') | 98 tree = parse(source, 'eval') |
98 xform = ExpressionASTTransformer() | 99 xform = ExpressionASTTransformer() |
99 tree = xform.visit(tree) | 100 tree = xform.visit(tree) |
100 | 101 |
101 if isinstance(filename, unicode): | 102 if isinstance(filename, unicode): |
102 # pycodegen doesn't like unicode in the filename | 103 # unicode file names not allowed for code objects |
103 filename = filename.encode('utf-8', 'replace') | 104 filename = filename.encode('utf-8', 'replace') |
104 tree.filename = filename or '<string>' | 105 elif not filename: |
106 filename = '<string>' | |
107 tree.filename = '<string>' | |
108 if lineno <= 0: | |
109 lineno = 1 | |
105 | 110 |
106 gen = ExpressionCodeGenerator(tree) | 111 gen = ExpressionCodeGenerator(tree) |
107 if lineno >= 0: | 112 gen.optimized = True |
108 gen.emit('SET_LINENO', lineno) | 113 code = gen.getCode() |
109 | 114 |
110 return gen.getCode() | 115 # We'd like to just set co_firstlineno, but it's readonly. So we need to |
116 # clone the code object while adjusting the line number | |
117 return new.code(0, code.co_nlocals, code.co_stacksize, | |
118 code.co_flags | 0x0040, code.co_code, code.co_consts, | |
119 code.co_names, code.co_varnames, filename, code.co_name, | |
120 lineno, code.co_lnotab, (), ()) | |
111 | 121 |
112 def _lookup_name(data, name, locals=None): | 122 def _lookup_name(data, name, locals=None): |
113 val = data.get(name) | 123 val = data.get(name) |
114 if val is None and locals: | 124 if val is None and locals: |
115 val = locals.get(name) | 125 val = locals.get(name) |
255 class ExpressionASTTransformer(ASTTransformer): | 265 class ExpressionASTTransformer(ASTTransformer): |
256 """Concrete AST transformer that implements the AST transformations needed | 266 """Concrete AST transformer that implements the AST transformations needed |
257 for template expressions. | 267 for template expressions. |
258 """ | 268 """ |
259 | 269 |
260 def visitGetattr(self, node, *args, **kwargs): | 270 def visitGetattr(self, node, locals_=False): |
261 return ast.CallFunc(ast.Name('_lookup_attr'), | 271 return ast.CallFunc(ast.Name('_lookup_attr'), [ |
262 [ast.Name('data'), self.visit(node.expr, *args, **kwargs), | 272 ast.Name('data'), self.visit(node.expr, locals_=locals_), |
263 ast.Const(node.attrname)] | 273 ast.Const(node.attrname) |
264 ) | 274 ]) |
265 | 275 |
266 def visitLambda(self, node, *args, **kwargs): | 276 def visitLambda(self, node, locals_=False): |
267 old_lookup_locals = kwargs.get('lookup_locals', False) | 277 node.code = self.visit(node.code, locals_=True) |
268 kwargs['lookup_locals'] = True | |
269 node.code = self.visit(node.code, *args, **kwargs) | |
270 node.filename = '<string>' # workaround for bug in pycodegen | 278 node.filename = '<string>' # workaround for bug in pycodegen |
271 kwargs['lookup_locals'] = old_lookup_locals | 279 return node |
272 return node | 280 |
273 | 281 def visitListComp(self, node, locals_=False): |
274 def visitListComp(self, node, *args, **kwargs): | 282 node.expr = self.visit(node.expr, locals_=True) |
275 old_lookup_locals = kwargs.get('lookup_locals', False) | 283 node.quals = map(lambda x: self.visit(x, locals_=True), node.quals) |
276 kwargs['lookup_locals'] = True | 284 return node |
277 node.expr = self.visit(node.expr, *args, **kwargs) | 285 |
278 node.quals = map(lambda x: self.visit(x, *args, **kwargs), node.quals) | 286 def visitName(self, node, locals_=False): |
279 kwargs['lookup_locals'] = old_lookup_locals | |
280 return node | |
281 | |
282 def visitName(self, node, *args, **kwargs): | |
283 func_args = [ast.Name('data'), ast.Const(node.name)] | 287 func_args = [ast.Name('data'), ast.Const(node.name)] |
284 if kwargs.get('lookup_locals'): | 288 if locals_: |
285 func_args.append(ast.CallFunc(ast.Name('locals'), [])) | 289 func_args.append(ast.CallFunc(ast.Name('locals'), [])) |
286 return ast.CallFunc(ast.Name('_lookup_name'), func_args) | 290 return ast.CallFunc(ast.Name('_lookup_name'), func_args) |
287 return node | 291 |
288 | 292 def visitSubscript(self, node, locals_=False): |
289 def visitSubscript(self, node, *args, **kwargs): | 293 return ast.CallFunc(ast.Name('_lookup_item'), [ |
290 return ast.CallFunc(ast.Name('_lookup_item'), | 294 ast.Name('data'), self.visit(node.expr, locals_=locals_), |
291 [ast.Name('data'), self.visit(node.expr, *args, **kwargs), | 295 ast.Tuple(map(lambda x: self.visit(x, locals_=locals_), node.subs)) |
292 ast.Tuple(map(self.visit, node.subs, *args, **kwargs))] | 296 ]) |
293 ) |