Added Mako report library
[odoo/odoo.git] / bin / mako / codegen.py
1 # codegen.py
2 # Copyright (C) 2006, 2007, 2008 Michael Bayer mike_mp@zzzcomputing.com
3 #
4 # This module is part of Mako and is released under
5 # the MIT License: http://www.opensource.org/licenses/mit-license.php
6
7 """provides functionality for rendering a parsetree constructing into module source code."""
8
9 import time
10 import re
11 from mako.pygen import PythonPrinter
12 from mako import util, ast, parsetree, filters
13
14 MAGIC_NUMBER = 5
15
16
17 def compile(node, uri, filename=None, default_filters=None, buffer_filters=None, imports=None, source_encoding=None, generate_unicode=True):
18     """generate module source code given a parsetree node, uri, and optional source filename"""
19
20     buf = util.FastEncodingBuffer(unicode=generate_unicode)
21
22     printer = PythonPrinter(buf)
23     _GenerateRenderMethod(printer, _CompileContext(uri, filename, default_filters, buffer_filters, imports, source_encoding, generate_unicode), node)
24     return buf.getvalue()
25
26 class _CompileContext(object):
27     def __init__(self, uri, filename, default_filters, buffer_filters, imports, source_encoding, generate_unicode):
28         self.uri = uri
29         self.filename = filename
30         self.default_filters = default_filters
31         self.buffer_filters = buffer_filters
32         self.imports = imports
33         self.source_encoding = source_encoding
34         self.generate_unicode = generate_unicode
35         
36 class _GenerateRenderMethod(object):
37     """a template visitor object which generates the full module source for a template."""
38     def __init__(self, printer, compiler, node):
39         self.printer = printer
40         self.last_source_line = -1
41         self.compiler = compiler
42         self.node = node
43         self.identifier_stack = [None]
44         
45         self.in_def = isinstance(node, parsetree.DefTag)
46
47         if self.in_def:
48             name = "render_" + node.name
49             args = node.function_decl.get_argument_expressions()
50             filtered = len(node.filter_args.args) > 0 
51             buffered = eval(node.attributes.get('buffered', 'False'))
52             cached = eval(node.attributes.get('cached', 'False'))
53             defs = None
54             pagetag = None
55         else:
56             defs = self.write_toplevel()
57             pagetag = self.compiler.pagetag
58             name = "render_body"
59             if pagetag is not None:
60                 args = pagetag.body_decl.get_argument_expressions()
61                 if not pagetag.body_decl.kwargs:
62                     args += ['**pageargs']
63                 cached = eval(pagetag.attributes.get('cached', 'False'))
64             else:
65                 args = ['**pageargs']
66                 cached = False
67             buffered = filtered = False
68         if args is None:
69             args = ['context']
70         else:
71             args = [a for a in ['context'] + args]
72             
73         self.write_render_callable(pagetag or node, name, args, buffered, filtered, cached)
74         
75         if defs is not None:
76             for node in defs:
77                 _GenerateRenderMethod(printer, compiler, node)
78     
79     identifiers = property(lambda self:self.identifier_stack[-1])
80     
81     def write_toplevel(self):
82         """traverse a template structure for module-level directives and generate the
83         start of module-level code."""
84         inherit = []
85         namespaces = {}
86         module_code = []
87         encoding =[None]
88
89         self.compiler.pagetag = None
90         
91         class FindTopLevel(object):
92             def visitInheritTag(s, node):
93                 inherit.append(node)
94             def visitNamespaceTag(s, node):
95                 namespaces[node.name] = node
96             def visitPageTag(s, node):
97                 self.compiler.pagetag = node
98             def visitCode(s, node):
99                 if node.ismodule:
100                     module_code.append(node)
101             
102         f = FindTopLevel()
103         for n in self.node.nodes:
104             n.accept_visitor(f)
105
106         self.compiler.namespaces = namespaces
107
108         module_ident = util.Set()
109         for n in module_code:
110             module_ident = module_ident.union(n.declared_identifiers())
111
112         module_identifiers = _Identifiers()
113         module_identifiers.declared = module_ident
114         
115         # module-level names, python code
116         if not self.compiler.generate_unicode and self.compiler.source_encoding:
117             self.printer.writeline("# -*- encoding:%s -*-" % self.compiler.source_encoding)
118             
119         self.printer.writeline("from mako import runtime, filters, cache")
120         self.printer.writeline("UNDEFINED = runtime.UNDEFINED")
121         self.printer.writeline("__M_dict_builtin = dict")
122         self.printer.writeline("__M_locals_builtin = locals")
123         self.printer.writeline("_magic_number = %s" % repr(MAGIC_NUMBER))
124         self.printer.writeline("_modified_time = %s" % repr(time.time()))
125         self.printer.writeline("_template_filename=%s" % repr(self.compiler.filename))
126         self.printer.writeline("_template_uri=%s" % repr(self.compiler.uri))
127         self.printer.writeline("_template_cache=cache.Cache(__name__, _modified_time)")
128         self.printer.writeline("_source_encoding=%s" % repr(self.compiler.source_encoding))
129         if self.compiler.imports:
130             buf = ''
131             for imp in self.compiler.imports:
132                 buf += imp + "\n"
133                 self.printer.writeline(imp)
134             impcode = ast.PythonCode(buf, source='', lineno=0, pos=0, filename='template defined imports')
135         else:
136             impcode = None
137         
138         main_identifiers = module_identifiers.branch(self.node)
139         module_identifiers.topleveldefs = module_identifiers.topleveldefs.union(main_identifiers.topleveldefs)
140         [module_identifiers.declared.add(x) for x in ["UNDEFINED"]]
141         if impcode:
142             [module_identifiers.declared.add(x) for x in impcode.declared_identifiers]
143             
144         self.compiler.identifiers = module_identifiers
145         self.printer.writeline("_exports = %s" % repr([n.name for n in main_identifiers.topleveldefs.values()]))
146         self.printer.write("\n\n")
147
148         if len(module_code):
149             self.write_module_code(module_code)
150
151         if len(inherit):
152             self.write_namespaces(namespaces)
153             self.write_inherit(inherit[-1])
154         elif len(namespaces):
155             self.write_namespaces(namespaces)
156
157         return main_identifiers.topleveldefs.values()
158
159     def write_render_callable(self, node, name, args, buffered, filtered, cached):
160         """write a top-level render callable.
161         
162         this could be the main render() method or that of a top-level def."""
163         self.printer.writelines(
164             "def %s(%s):" % (name, ','.join(args)),
165                 "context.caller_stack._push_frame()",
166                 "try:"
167         )
168         if buffered or filtered or cached:
169             self.printer.writeline("context._push_buffer()")
170         
171         self.identifier_stack.append(self.compiler.identifiers.branch(self.node))
172         if not self.in_def and '**pageargs' in args:
173             self.identifier_stack[-1].argument_declared.add('pageargs')
174
175         if not self.in_def and (len(self.identifiers.locally_assigned) > 0 or len(self.identifiers.argument_declared)>0):
176             self.printer.writeline("__M_locals = __M_dict_builtin(%s)" % ','.join(["%s=%s" % (x, x) for x in self.identifiers.argument_declared]))
177
178         self.write_variable_declares(self.identifiers, toplevel=True)
179
180         for n in self.node.nodes:
181             n.accept_visitor(self)
182
183         self.write_def_finish(self.node, buffered, filtered, cached)
184         self.printer.writeline(None)
185         self.printer.write("\n\n")
186         if cached:
187             self.write_cache_decorator(node, name, args, buffered, self.identifiers, toplevel=True)
188             
189     def write_module_code(self, module_code):
190         """write module-level template code, i.e. that which is enclosed in <%! %> tags
191         in the template."""
192         for n in module_code:
193             self.write_source_comment(n)
194             self.printer.write_indented_block(n.text)
195
196     def write_inherit(self, node):
197         """write the module-level inheritance-determination callable."""
198         self.printer.writelines(
199             "def _mako_inherit(template, context):",
200                 "_mako_generate_namespaces(context)",
201                 "return runtime._inherit_from(context, %s, _template_uri)" % (node.parsed_attributes['file']),
202                 None
203             )
204
205     def write_namespaces(self, namespaces):
206         """write the module-level namespace-generating callable."""
207         self.printer.writelines(
208             "def _mako_get_namespace(context, name):",
209                 "try:",
210                     "return context.namespaces[(__name__, name)]",
211                 "except KeyError:",
212                     "_mako_generate_namespaces(context)",
213                 "return context.namespaces[(__name__, name)]",
214             None,None
215             )
216         self.printer.writeline("def _mako_generate_namespaces(context):")
217         for node in namespaces.values():
218             if node.attributes.has_key('import'):
219                 self.compiler.has_ns_imports = True
220             self.write_source_comment(node)
221             if len(node.nodes):
222                 self.printer.writeline("def make_namespace():")
223                 export = []
224                 identifiers = self.compiler.identifiers.branch(node)
225                 class NSDefVisitor(object):
226                     def visitDefTag(s, node):
227                         self.write_inline_def(node, identifiers, nested=False)
228                         export.append(node.name)
229                 vis = NSDefVisitor()
230                 for n in node.nodes:
231                     n.accept_visitor(vis)
232                 self.printer.writeline("return [%s]" % (','.join(export)))
233                 self.printer.writeline(None)
234                 callable_name = "make_namespace()"
235             else:
236                 callable_name = "None"
237             self.printer.writeline("ns = runtime.Namespace(%s, context._clean_inheritance_tokens(), templateuri=%s, callables=%s, calling_uri=_template_uri, module=%s)" % (repr(node.name), node.parsed_attributes.get('file', 'None'), callable_name, node.parsed_attributes.get('module', 'None')))
238             if eval(node.attributes.get('inheritable', "False")):
239                 self.printer.writeline("context['self'].%s = ns" % (node.name))
240             self.printer.writeline("context.namespaces[(__name__, %s)] = ns" % repr(node.name))
241             self.printer.write("\n")
242         if not len(namespaces):
243             self.printer.writeline("pass")
244         self.printer.writeline(None)
245             
246     def write_variable_declares(self, identifiers, toplevel=False, limit=None):
247         """write variable declarations at the top of a function.
248         
249         the variable declarations are in the form of callable definitions for defs and/or
250         name lookup within the function's context argument.  the names declared are based on the
251         names that are referenced in the function body, which don't otherwise have any explicit
252         assignment operation.  names that are assigned within the body are assumed to be 
253         locally-scoped variables and are not separately declared.
254         
255         for def callable definitions, if the def is a top-level callable then a 
256         'stub' callable is generated which wraps the current Context into a closure.  if the def
257         is not top-level, it is fully rendered as a local closure."""
258         
259         # collection of all defs available to us in this scope
260         comp_idents = dict([(c.name, c) for c in identifiers.defs])
261         to_write = util.Set()
262         
263         # write "context.get()" for all variables we are going to need that arent in the namespace yet
264         to_write = to_write.union(identifiers.undeclared)
265         
266         # write closure functions for closures that we define right here
267         to_write = to_write.union(util.Set([c.name for c in identifiers.closuredefs.values()]))
268
269         # remove identifiers that are declared in the argument signature of the callable
270         to_write = to_write.difference(identifiers.argument_declared)
271
272         # remove identifiers that we are going to assign to.  in this way we mimic Python's behavior,
273         # i.e. assignment to a variable within a block means that variable is now a "locally declared" var,
274         # which cannot be referenced beforehand.  
275         to_write = to_write.difference(identifiers.locally_declared)
276         
277         # if a limiting set was sent, constraint to those items in that list
278         # (this is used for the caching decorator)
279         if limit is not None:
280             to_write = to_write.intersection(limit)
281         
282         if toplevel and getattr(self.compiler, 'has_ns_imports', False):
283             self.printer.writeline("_import_ns = {}")
284             self.compiler.has_imports = True
285             for ident, ns in self.compiler.namespaces.iteritems():
286                 if ns.attributes.has_key('import'):
287                     self.printer.writeline("_mako_get_namespace(context, %s)._populate(_import_ns, %s)" % (repr(ident),  repr(re.split(r'\s*,\s*', ns.attributes['import']))))
288                         
289         for ident in to_write:
290             if ident in comp_idents:
291                 comp = comp_idents[ident]
292                 if comp.is_root():
293                     self.write_def_decl(comp, identifiers)
294                 else:
295                     self.write_inline_def(comp, identifiers, nested=True)
296             elif ident in self.compiler.namespaces:
297                 self.printer.writeline("%s = _mako_get_namespace(context, %s)" % (ident, repr(ident)))
298             else:
299                 if getattr(self.compiler, 'has_ns_imports', False):
300                     self.printer.writeline("%s = _import_ns.get(%s, context.get(%s, UNDEFINED))" % (ident, repr(ident), repr(ident)))
301                 else:
302                     self.printer.writeline("%s = context.get(%s, UNDEFINED)" % (ident, repr(ident)))
303         
304         self.printer.writeline("__M_writer = context.writer()")
305         
306     def write_source_comment(self, node):
307         """write a source comment containing the line number of the corresponding template line."""
308         if self.last_source_line != node.lineno:
309             self.printer.writeline("# SOURCE LINE %d" % node.lineno)
310             self.last_source_line = node.lineno
311
312     def write_def_decl(self, node, identifiers):
313         """write a locally-available callable referencing a top-level def"""
314         funcname = node.function_decl.funcname
315         namedecls = node.function_decl.get_argument_expressions()
316         nameargs = node.function_decl.get_argument_expressions(include_defaults=False)
317         if not self.in_def and (len(self.identifiers.locally_assigned) > 0 or len(self.identifiers.argument_declared) > 0):
318             nameargs.insert(0, 'context.locals_(__M_locals)')
319         else:
320             nameargs.insert(0, 'context')
321         self.printer.writeline("def %s(%s):" % (funcname, ",".join(namedecls)))
322         self.printer.writeline("return render_%s(%s)" % (funcname, ",".join(nameargs)))
323         self.printer.writeline(None)
324         
325     def write_inline_def(self, node, identifiers, nested):
326         """write a locally-available def callable inside an enclosing def."""
327         namedecls = node.function_decl.get_argument_expressions()
328         self.printer.writeline("def %s(%s):" % (node.name, ",".join(namedecls)))
329         filtered = len(node.filter_args.args) > 0 
330         buffered = eval(node.attributes.get('buffered', 'False'))
331         cached = eval(node.attributes.get('cached', 'False'))
332         self.printer.writelines(
333             "context.caller_stack._push_frame()",
334             "try:"
335             )
336         if buffered or filtered or cached:
337             self.printer.writelines(
338                 "context._push_buffer()",
339                 )
340
341         identifiers = identifiers.branch(node, nested=nested)
342
343         self.write_variable_declares(identifiers)
344         
345         self.identifier_stack.append(identifiers)
346         for n in node.nodes:
347             n.accept_visitor(self)
348         self.identifier_stack.pop()
349         
350         self.write_def_finish(node, buffered, filtered, cached)
351         self.printer.writeline(None)
352         if cached:
353             self.write_cache_decorator(node, node.name, namedecls, False, identifiers, inline=True, toplevel=False)
354             
355     def write_def_finish(self, node, buffered, filtered, cached, callstack=True):
356         """write the end section of a rendering function, either outermost or inline.
357         
358         this takes into account if the rendering function was filtered, buffered, etc.
359         and closes the corresponding try: block if any, and writes code to retrieve captured content, 
360         apply filters, send proper return value."""
361         if not buffered and not cached and not filtered:
362             self.printer.writeline("return ''")
363             if callstack:
364                 self.printer.writelines(
365                     "finally:",
366                         "context.caller_stack._pop_frame()",
367                     None
368                 )
369                 
370         if buffered or filtered or cached:
371             if buffered or cached:
372                 # in a caching scenario, don't try to get a writer
373                 # from the context after popping; assume the caching
374                 # implemenation might be using a context with no
375                 # extra buffers
376                 self.printer.writelines(
377                     "finally:",
378                         "__M_buf = context._pop_buffer()"
379                 )
380             else:
381                 self.printer.writelines(
382                     "finally:",
383                         "__M_buf, __M_writer = context._pop_buffer_and_writer()"
384                 )
385                 
386             if callstack:
387                 self.printer.writeline("context.caller_stack._pop_frame()")
388                 
389             s = "__M_buf.getvalue()"
390             if filtered:
391                 s = self.create_filter_callable(node.filter_args.args, s, False)
392             self.printer.writeline(None)
393             if buffered and not cached:
394                 s = self.create_filter_callable(self.compiler.buffer_filters, s, False)
395             if buffered or cached:
396                 self.printer.writeline("return %s" % s)
397             else:
398                 self.printer.writelines(
399                     "__M_writer(%s)" % s,
400                     "return ''"
401                 )
402
403     def write_cache_decorator(self, node_or_pagetag, name, args, buffered, identifiers, inline=False, toplevel=False):
404         """write a post-function decorator to replace a rendering callable with a cached version of itself."""
405         self.printer.writeline("__M_%s = %s" % (name, name))
406         cachekey = node_or_pagetag.parsed_attributes.get('cache_key', repr(name))
407         cacheargs = {}
408         for arg in (('cache_type', 'type'), ('cache_dir', 'data_dir'), ('cache_timeout', 'expiretime'), ('cache_url', 'url')):
409             val = node_or_pagetag.parsed_attributes.get(arg[0], None)
410             if val is not None:
411                 if arg[1] == 'expiretime':
412                     cacheargs[arg[1]] = int(eval(val))
413                 else:
414                     cacheargs[arg[1]] = val
415             else:
416                 if self.compiler.pagetag is not None:
417                     val = self.compiler.pagetag.parsed_attributes.get(arg[0], None)
418                     if val is not None:
419                         if arg[1] == 'expiretime':
420                             cacheargs[arg[1]] == int(eval(val))
421                         else:
422                             cacheargs[arg[1]] = val
423         
424         self.printer.writeline("def %s(%s):" % (name, ','.join(args)))
425         
426         # form "arg1, arg2, arg3=arg3, arg4=arg4", etc.
427         pass_args = [ '=' in a and "%s=%s" % ((a.split('=')[0],)*2) or a for a in args]
428
429         self.write_variable_declares(identifiers, toplevel=toplevel, limit=node_or_pagetag.undeclared_identifiers())
430         if buffered:
431             s = "context.get('local').get_cached(%s, defname=%r, %screatefunc=lambda:__M_%s(%s))" % (cachekey, name, ''.join(["%s=%s, " % (k,v) for k, v in cacheargs.iteritems()]), name, ','.join(pass_args))
432             # apply buffer_filters
433             s = self.create_filter_callable(self.compiler.buffer_filters, s, False)
434             self.printer.writelines("return " + s,None)
435         else:
436             self.printer.writelines(
437                     "__M_writer(context.get('local').get_cached(%s, defname=%r, %screatefunc=lambda:__M_%s(%s)))" % (cachekey, name, ''.join(["%s=%s, " % (k,v) for k, v in cacheargs.iteritems()]), name, ','.join(pass_args)),
438                     "return ''",
439                 None
440             )
441
442     def create_filter_callable(self, args, target, is_expression):
443         """write a filter-applying expression based on the filters present in the given 
444         filter names, adjusting for the global 'default' filter aliases as needed."""
445         def locate_encode(name):
446             if re.match(r'decode\..+', name):
447                 return "filters." + name
448             else:
449                 return filters.DEFAULT_ESCAPES.get(name, name)
450         
451         if 'n' not in args:
452             if is_expression:
453                 if self.compiler.pagetag:
454                     args = self.compiler.pagetag.filter_args.args + args
455                 if self.compiler.default_filters:
456                     args = self.compiler.default_filters + args
457         for e in args:
458             # if filter given as a function, get just the identifier portion
459             if e == 'n':
460                 continue
461             m = re.match(r'(.+?)(\(.*\))', e)
462             if m:
463                 (ident, fargs) = m.group(1,2)
464                 f = locate_encode(ident)
465                 e = f + fargs
466             else:
467                 x = e
468                 e = locate_encode(e)
469                 assert e is not None
470             target = "%s(%s)" % (e, target)
471         return target
472         
473     def visitExpression(self, node):
474         self.write_source_comment(node)
475         if len(node.escapes) or (self.compiler.pagetag is not None and len(self.compiler.pagetag.filter_args.args)) or len(self.compiler.default_filters):
476             s = self.create_filter_callable(node.escapes_code.args, "%s" % node.text, True)
477             self.printer.writeline("__M_writer(%s)" % s)
478         else:
479             self.printer.writeline("__M_writer(%s)" % node.text)
480             
481     def visitControlLine(self, node):
482         if node.isend:
483             self.printer.writeline(None)
484         else:
485             self.write_source_comment(node)
486             self.printer.writeline(node.text)
487     def visitText(self, node):
488         self.write_source_comment(node)
489         self.printer.writeline("__M_writer(%s)" % repr(node.content))
490     def visitTextTag(self, node):
491         filtered = len(node.filter_args.args) > 0
492         if filtered:
493             self.printer.writelines(
494                 "__M_writer = context._push_writer()",
495                 "try:",
496             )
497         for n in node.nodes:
498             n.accept_visitor(self)
499         if filtered:
500             self.printer.writelines(
501                 "finally:",
502                 "__M_buf, __M_writer = context._pop_buffer_and_writer()",
503                 "__M_writer(%s)" % self.create_filter_callable(node.filter_args.args, "__M_buf.getvalue()", False),
504                 None
505                 )
506         
507     def visitCode(self, node):
508         if not node.ismodule:
509             self.write_source_comment(node)
510             self.printer.write_indented_block(node.text)
511
512             if not self.in_def and len(self.identifiers.locally_assigned) > 0:
513                 # if we are the "template" def, fudge locally declared/modified variables into the "__M_locals" dictionary,
514                 # which is used for def calls within the same template, to simulate "enclosing scope"
515                 self.printer.writeline('__M_locals.update(__M_dict_builtin([(__M_key, __M_locals_builtin()[__M_key]) for __M_key in [%s] if __M_key in __M_locals_builtin()]))' % ','.join([repr(x) for x in node.declared_identifiers()]))
516                 
517     def visitIncludeTag(self, node):
518         self.write_source_comment(node)
519         args = node.attributes.get('args')
520         if args:
521             self.printer.writeline("runtime._include_file(context, %s, _template_uri, %s)" % (node.parsed_attributes['file'], args))
522         else:
523             self.printer.writeline("runtime._include_file(context, %s, _template_uri)" % (node.parsed_attributes['file']))
524             
525     def visitNamespaceTag(self, node):
526         pass
527             
528     def visitDefTag(self, node):
529         pass
530
531     def visitCallNamespaceTag(self, node):
532         # TODO: we can put namespace-specific checks here, such
533         # as ensure the given namespace will be imported,
534         # pre-import the namespace, etc.
535         self.visitCallTag(node)
536         
537     def visitCallTag(self, node):
538         self.printer.writeline("def ccall(caller):")
539         export = ['body']
540         callable_identifiers = self.identifiers.branch(node, nested=True)
541         body_identifiers = callable_identifiers.branch(node, nested=False)
542         # we want the 'caller' passed to ccall to be used for the body() function,
543         # but for other non-body() <%def>s within <%call> we want the current caller off the call stack (if any)
544         body_identifiers.add_declared('caller')
545         
546         self.identifier_stack.append(body_identifiers)
547         class DefVisitor(object):
548             def visitDefTag(s, node):
549                 self.write_inline_def(node, callable_identifiers, nested=False)
550                 export.append(node.name)
551                 # remove defs that are within the <%call> from the "closuredefs" defined
552                 # in the body, so they dont render twice
553                 if node.name in body_identifiers.closuredefs:
554                     del body_identifiers.closuredefs[node.name]
555
556         vis = DefVisitor()
557         for n in node.nodes:
558             n.accept_visitor(vis)
559         self.identifier_stack.pop()
560         
561         bodyargs = node.body_decl.get_argument_expressions()    
562         self.printer.writeline("def body(%s):" % ','.join(bodyargs))
563         # TODO: figure out best way to specify buffering/nonbuffering (at call time would be better)
564         buffered = False
565         if buffered:
566             self.printer.writelines(
567                 "context._push_buffer()",
568                 "try:"
569             )
570         self.write_variable_declares(body_identifiers)
571         self.identifier_stack.append(body_identifiers)
572         
573         for n in node.nodes:
574             n.accept_visitor(self)
575         self.identifier_stack.pop()
576         
577         self.write_def_finish(node, buffered, False, False, callstack=False)
578         self.printer.writelines(
579             None,
580             "return [%s]" % (','.join(export)),
581             None
582         )
583
584         self.printer.writelines(
585             # get local reference to current caller, if any
586             "caller = context.caller_stack._get_caller()",
587             # push on caller for nested call
588             "context.caller_stack.nextcaller = runtime.Namespace('caller', context, callables=ccall(caller))",
589             "try:")
590         self.write_source_comment(node)
591         self.printer.writelines(
592                 "__M_writer(%s)" % self.create_filter_callable([], node.expression, True),
593             "finally:",
594                 "context.caller_stack.nextcaller = None",
595             None
596         )
597
598 class _Identifiers(object):
599     """tracks the status of identifier names as template code is rendered."""
600     def __init__(self, node=None, parent=None, nested=False):
601         if parent is not None:
602             # things that have already been declared in an enclosing namespace (i.e. names we can just use)
603             self.declared = util.Set(parent.declared).union([c.name for c in parent.closuredefs.values()]).union(parent.locally_declared).union(parent.argument_declared)
604             
605             # if these identifiers correspond to a "nested" scope, it means whatever the 
606             # parent identifiers had as undeclared will have been declared by that parent, 
607             # and therefore we have them in our scope.
608             if nested:
609                 self.declared = self.declared.union(parent.undeclared)
610             
611             # top level defs that are available
612             self.topleveldefs = util.SetLikeDict(**parent.topleveldefs)
613         else:
614             self.declared = util.Set()
615             self.topleveldefs = util.SetLikeDict()
616         
617         # things within this level that are referenced before they are declared (e.g. assigned to)
618         self.undeclared = util.Set()
619         
620         # things that are declared locally.  some of these things could be in the "undeclared"
621         # list as well if they are referenced before declared
622         self.locally_declared = util.Set()
623     
624         # assignments made in explicit python blocks.  these will be propigated to 
625         # the context of local def calls.
626         self.locally_assigned = util.Set()
627         
628         # things that are declared in the argument signature of the def callable
629         self.argument_declared = util.Set()
630         
631         # closure defs that are defined in this level
632         self.closuredefs = util.SetLikeDict()
633         
634         self.node = node
635         
636         if node is not None:
637             node.accept_visitor(self)
638         
639     def branch(self, node, **kwargs):
640         """create a new Identifiers for a new Node, with this Identifiers as the parent."""
641         return _Identifiers(node, self, **kwargs)
642     
643     defs = property(lambda self:util.Set(self.topleveldefs.union(self.closuredefs).values()))
644     
645     def __repr__(self):
646         return "Identifiers(declared=%s, locally_declared=%s, undeclared=%s, topleveldefs=%s, closuredefs=%s, argumenetdeclared=%s)" % (repr(list(self.declared)), repr(list(self.locally_declared)), repr(list(self.undeclared)), repr([c.name for c in self.topleveldefs.values()]), repr([c.name for c in self.closuredefs.values()]), repr(self.argument_declared))
647         
648     def check_declared(self, node):
649         """update the state of this Identifiers with the undeclared and declared identifiers of the given node."""
650         for ident in node.undeclared_identifiers():
651             if ident != 'context' and ident not in self.declared.union(self.locally_declared):
652                 self.undeclared.add(ident)
653         for ident in node.declared_identifiers():
654             self.locally_declared.add(ident)
655     
656     def add_declared(self, ident):
657         self.declared.add(ident)
658         if ident in self.undeclared:
659             self.undeclared.remove(ident)
660                         
661     def visitExpression(self, node):
662         self.check_declared(node)
663     def visitControlLine(self, node):
664         self.check_declared(node)
665     def visitCode(self, node):
666         if not node.ismodule:
667             self.check_declared(node)
668             self.locally_assigned = self.locally_assigned.union(node.declared_identifiers())
669     def visitDefTag(self, node):
670         if node.is_root():
671             self.topleveldefs[node.name] = node
672         elif node is not self.node:
673             self.closuredefs[node.name] = node
674         for ident in node.undeclared_identifiers():
675             if ident != 'context' and ident not in self.declared.union(self.locally_declared):
676                 self.undeclared.add(ident)
677         # visit defs only one level deep
678         if node is self.node:
679             for ident in node.declared_identifiers():
680                 self.argument_declared.add(ident)
681             for n in node.nodes:
682                 n.accept_visitor(self)
683     def visitIncludeTag(self, node):
684         self.check_declared(node)
685     def visitPageTag(self, node):
686         for ident in node.declared_identifiers():
687             self.argument_declared.add(ident)
688         self.check_declared(node)
689     
690     def visitCallNamespaceTag(self, node):
691         self.visitCallTag(node)
692         
693     def visitCallTag(self, node):
694         if node is self.node:
695             for ident in node.undeclared_identifiers():
696                 if ident != 'context' and ident not in self.declared.union(self.locally_declared):
697                     self.undeclared.add(ident)
698             for ident in node.declared_identifiers():
699                 self.argument_declared.add(ident)
700             for n in node.nodes:
701                 n.accept_visitor(self)
702         else:
703             for ident in node.undeclared_identifiers():
704                 if ident != 'context' and ident not in self.declared.union(self.locally_declared):
705                     self.undeclared.add(ident)
706