~jonas-drange/ubuntu-start-page/1252899-mobile-friendly

« back to all changes in this revision

Viewing changes to src/mako/codegen.py

  • Committer: Matthew Nuzum
  • Date: 2008-04-18 01:58:53 UTC
  • Revision ID: matthew.nuzum@canonical.com-20080418015853-2b8rf979z2c2exxl
adding files

Show diffs side-by-side

added added

removed removed

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