707 lines
32 KiB
Python
Executable File
707 lines
32 KiB
Python
Executable File
# codegen.py
|
|
# Copyright (C) 2006, 2007, 2008 Michael Bayer mike_mp@zzzcomputing.com
|
|
#
|
|
# This module is part of Mako and is released under
|
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
|
|
|
"""provides functionality for rendering a parsetree constructing into module source code."""
|
|
|
|
import time
|
|
import re
|
|
from mako.pygen import PythonPrinter
|
|
from mako import util, ast, parsetree, filters
|
|
|
|
MAGIC_NUMBER = 5
|
|
|
|
|
|
def compile(node, uri, filename=None, default_filters=None, buffer_filters=None, imports=None, source_encoding=None, generate_unicode=True):
|
|
"""generate module source code given a parsetree node, uri, and optional source filename"""
|
|
|
|
buf = util.FastEncodingBuffer(unicode=generate_unicode)
|
|
|
|
printer = PythonPrinter(buf)
|
|
_GenerateRenderMethod(printer, _CompileContext(uri, filename, default_filters, buffer_filters, imports, source_encoding, generate_unicode), node)
|
|
return buf.getvalue()
|
|
|
|
class _CompileContext(object):
|
|
def __init__(self, uri, filename, default_filters, buffer_filters, imports, source_encoding, generate_unicode):
|
|
self.uri = uri
|
|
self.filename = filename
|
|
self.default_filters = default_filters
|
|
self.buffer_filters = buffer_filters
|
|
self.imports = imports
|
|
self.source_encoding = source_encoding
|
|
self.generate_unicode = generate_unicode
|
|
|
|
class _GenerateRenderMethod(object):
|
|
"""a template visitor object which generates the full module source for a template."""
|
|
def __init__(self, printer, compiler, node):
|
|
self.printer = printer
|
|
self.last_source_line = -1
|
|
self.compiler = compiler
|
|
self.node = node
|
|
self.identifier_stack = [None]
|
|
|
|
self.in_def = isinstance(node, parsetree.DefTag)
|
|
|
|
if self.in_def:
|
|
name = "render_" + node.name
|
|
args = node.function_decl.get_argument_expressions()
|
|
filtered = len(node.filter_args.args) > 0
|
|
buffered = eval(node.attributes.get('buffered', 'False'))
|
|
cached = eval(node.attributes.get('cached', 'False'))
|
|
defs = None
|
|
pagetag = None
|
|
else:
|
|
defs = self.write_toplevel()
|
|
pagetag = self.compiler.pagetag
|
|
name = "render_body"
|
|
if pagetag is not None:
|
|
args = pagetag.body_decl.get_argument_expressions()
|
|
if not pagetag.body_decl.kwargs:
|
|
args += ['**pageargs']
|
|
cached = eval(pagetag.attributes.get('cached', 'False'))
|
|
else:
|
|
args = ['**pageargs']
|
|
cached = False
|
|
buffered = filtered = False
|
|
if args is None:
|
|
args = ['context']
|
|
else:
|
|
args = [a for a in ['context'] + args]
|
|
|
|
self.write_render_callable(pagetag or node, name, args, buffered, filtered, cached)
|
|
|
|
if defs is not None:
|
|
for node in defs:
|
|
_GenerateRenderMethod(printer, compiler, node)
|
|
|
|
identifiers = property(lambda self:self.identifier_stack[-1])
|
|
|
|
def write_toplevel(self):
|
|
"""traverse a template structure for module-level directives and generate the
|
|
start of module-level code."""
|
|
inherit = []
|
|
namespaces = {}
|
|
module_code = []
|
|
encoding =[None]
|
|
|
|
self.compiler.pagetag = None
|
|
|
|
class FindTopLevel(object):
|
|
def visitInheritTag(s, node):
|
|
inherit.append(node)
|
|
def visitNamespaceTag(s, node):
|
|
namespaces[node.name] = node
|
|
def visitPageTag(s, node):
|
|
self.compiler.pagetag = node
|
|
def visitCode(s, node):
|
|
if node.ismodule:
|
|
module_code.append(node)
|
|
|
|
f = FindTopLevel()
|
|
for n in self.node.nodes:
|
|
n.accept_visitor(f)
|
|
|
|
self.compiler.namespaces = namespaces
|
|
|
|
module_ident = util.Set()
|
|
for n in module_code:
|
|
module_ident = module_ident.union(n.declared_identifiers())
|
|
|
|
module_identifiers = _Identifiers()
|
|
module_identifiers.declared = module_ident
|
|
|
|
# module-level names, python code
|
|
if not self.compiler.generate_unicode and self.compiler.source_encoding:
|
|
self.printer.writeline("# -*- encoding:%s -*-" % self.compiler.source_encoding)
|
|
|
|
self.printer.writeline("from mako import runtime, filters, cache")
|
|
self.printer.writeline("UNDEFINED = runtime.UNDEFINED")
|
|
self.printer.writeline("__M_dict_builtin = dict")
|
|
self.printer.writeline("__M_locals_builtin = locals")
|
|
self.printer.writeline("_magic_number = %s" % repr(MAGIC_NUMBER))
|
|
self.printer.writeline("_modified_time = %s" % repr(time.time()))
|
|
self.printer.writeline("_template_filename=%s" % repr(self.compiler.filename))
|
|
self.printer.writeline("_template_uri=%s" % repr(self.compiler.uri))
|
|
self.printer.writeline("_template_cache=cache.Cache(__name__, _modified_time)")
|
|
self.printer.writeline("_source_encoding=%s" % repr(self.compiler.source_encoding))
|
|
if self.compiler.imports:
|
|
buf = ''
|
|
for imp in self.compiler.imports:
|
|
buf += imp + "\n"
|
|
self.printer.writeline(imp)
|
|
impcode = ast.PythonCode(buf, source='', lineno=0, pos=0, filename='template defined imports')
|
|
else:
|
|
impcode = None
|
|
|
|
main_identifiers = module_identifiers.branch(self.node)
|
|
module_identifiers.topleveldefs = module_identifiers.topleveldefs.union(main_identifiers.topleveldefs)
|
|
[module_identifiers.declared.add(x) for x in ["UNDEFINED"]]
|
|
if impcode:
|
|
[module_identifiers.declared.add(x) for x in impcode.declared_identifiers]
|
|
|
|
self.compiler.identifiers = module_identifiers
|
|
self.printer.writeline("_exports = %s" % repr([n.name for n in main_identifiers.topleveldefs.values()]))
|
|
self.printer.write("\n\n")
|
|
|
|
if len(module_code):
|
|
self.write_module_code(module_code)
|
|
|
|
if len(inherit):
|
|
self.write_namespaces(namespaces)
|
|
self.write_inherit(inherit[-1])
|
|
elif len(namespaces):
|
|
self.write_namespaces(namespaces)
|
|
|
|
return main_identifiers.topleveldefs.values()
|
|
|
|
def write_render_callable(self, node, name, args, buffered, filtered, cached):
|
|
"""write a top-level render callable.
|
|
|
|
this could be the main render() method or that of a top-level def."""
|
|
self.printer.writelines(
|
|
"def %s(%s):" % (name, ','.join(args)),
|
|
"context.caller_stack._push_frame()",
|
|
"try:"
|
|
)
|
|
if buffered or filtered or cached:
|
|
self.printer.writeline("context._push_buffer()")
|
|
|
|
self.identifier_stack.append(self.compiler.identifiers.branch(self.node))
|
|
if not self.in_def and '**pageargs' in args:
|
|
self.identifier_stack[-1].argument_declared.add('pageargs')
|
|
|
|
if not self.in_def and (len(self.identifiers.locally_assigned) > 0 or len(self.identifiers.argument_declared)>0):
|
|
self.printer.writeline("__M_locals = __M_dict_builtin(%s)" % ','.join(["%s=%s" % (x, x) for x in self.identifiers.argument_declared]))
|
|
|
|
self.write_variable_declares(self.identifiers, toplevel=True)
|
|
|
|
for n in self.node.nodes:
|
|
n.accept_visitor(self)
|
|
|
|
self.write_def_finish(self.node, buffered, filtered, cached)
|
|
self.printer.writeline(None)
|
|
self.printer.write("\n\n")
|
|
if cached:
|
|
self.write_cache_decorator(node, name, args, buffered, self.identifiers, toplevel=True)
|
|
|
|
def write_module_code(self, module_code):
|
|
"""write module-level template code, i.e. that which is enclosed in <%! %> tags
|
|
in the template."""
|
|
for n in module_code:
|
|
self.write_source_comment(n)
|
|
self.printer.write_indented_block(n.text)
|
|
|
|
def write_inherit(self, node):
|
|
"""write the module-level inheritance-determination callable."""
|
|
self.printer.writelines(
|
|
"def _mako_inherit(template, context):",
|
|
"_mako_generate_namespaces(context)",
|
|
"return runtime._inherit_from(context, %s, _template_uri)" % (node.parsed_attributes['file']),
|
|
None
|
|
)
|
|
|
|
def write_namespaces(self, namespaces):
|
|
"""write the module-level namespace-generating callable."""
|
|
self.printer.writelines(
|
|
"def _mako_get_namespace(context, name):",
|
|
"try:",
|
|
"return context.namespaces[(__name__, name)]",
|
|
"except KeyError:",
|
|
"_mako_generate_namespaces(context)",
|
|
"return context.namespaces[(__name__, name)]",
|
|
None,None
|
|
)
|
|
self.printer.writeline("def _mako_generate_namespaces(context):")
|
|
for node in namespaces.values():
|
|
if node.attributes.has_key('import'):
|
|
self.compiler.has_ns_imports = True
|
|
self.write_source_comment(node)
|
|
if len(node.nodes):
|
|
self.printer.writeline("def make_namespace():")
|
|
export = []
|
|
identifiers = self.compiler.identifiers.branch(node)
|
|
class NSDefVisitor(object):
|
|
def visitDefTag(s, node):
|
|
self.write_inline_def(node, identifiers, nested=False)
|
|
export.append(node.name)
|
|
vis = NSDefVisitor()
|
|
for n in node.nodes:
|
|
n.accept_visitor(vis)
|
|
self.printer.writeline("return [%s]" % (','.join(export)))
|
|
self.printer.writeline(None)
|
|
callable_name = "make_namespace()"
|
|
else:
|
|
callable_name = "None"
|
|
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')))
|
|
if eval(node.attributes.get('inheritable', "False")):
|
|
self.printer.writeline("context['self'].%s = ns" % (node.name))
|
|
self.printer.writeline("context.namespaces[(__name__, %s)] = ns" % repr(node.name))
|
|
self.printer.write("\n")
|
|
if not len(namespaces):
|
|
self.printer.writeline("pass")
|
|
self.printer.writeline(None)
|
|
|
|
def write_variable_declares(self, identifiers, toplevel=False, limit=None):
|
|
"""write variable declarations at the top of a function.
|
|
|
|
the variable declarations are in the form of callable definitions for defs and/or
|
|
name lookup within the function's context argument. the names declared are based on the
|
|
names that are referenced in the function body, which don't otherwise have any explicit
|
|
assignment operation. names that are assigned within the body are assumed to be
|
|
locally-scoped variables and are not separately declared.
|
|
|
|
for def callable definitions, if the def is a top-level callable then a
|
|
'stub' callable is generated which wraps the current Context into a closure. if the def
|
|
is not top-level, it is fully rendered as a local closure."""
|
|
|
|
# collection of all defs available to us in this scope
|
|
comp_idents = dict([(c.name, c) for c in identifiers.defs])
|
|
to_write = util.Set()
|
|
|
|
# write "context.get()" for all variables we are going to need that arent in the namespace yet
|
|
to_write = to_write.union(identifiers.undeclared)
|
|
|
|
# write closure functions for closures that we define right here
|
|
to_write = to_write.union(util.Set([c.name for c in identifiers.closuredefs.values()]))
|
|
|
|
# remove identifiers that are declared in the argument signature of the callable
|
|
to_write = to_write.difference(identifiers.argument_declared)
|
|
|
|
# remove identifiers that we are going to assign to. in this way we mimic Python's behavior,
|
|
# i.e. assignment to a variable within a block means that variable is now a "locally declared" var,
|
|
# which cannot be referenced beforehand.
|
|
to_write = to_write.difference(identifiers.locally_declared)
|
|
|
|
# if a limiting set was sent, constraint to those items in that list
|
|
# (this is used for the caching decorator)
|
|
if limit is not None:
|
|
to_write = to_write.intersection(limit)
|
|
|
|
if toplevel and getattr(self.compiler, 'has_ns_imports', False):
|
|
self.printer.writeline("_import_ns = {}")
|
|
self.compiler.has_imports = True
|
|
for ident, ns in self.compiler.namespaces.iteritems():
|
|
if ns.attributes.has_key('import'):
|
|
self.printer.writeline("_mako_get_namespace(context, %s)._populate(_import_ns, %s)" % (repr(ident), repr(re.split(r'\s*,\s*', ns.attributes['import']))))
|
|
|
|
for ident in to_write:
|
|
if ident in comp_idents:
|
|
comp = comp_idents[ident]
|
|
if comp.is_root():
|
|
self.write_def_decl(comp, identifiers)
|
|
else:
|
|
self.write_inline_def(comp, identifiers, nested=True)
|
|
elif ident in self.compiler.namespaces:
|
|
self.printer.writeline("%s = _mako_get_namespace(context, %s)" % (ident, repr(ident)))
|
|
else:
|
|
if getattr(self.compiler, 'has_ns_imports', False):
|
|
self.printer.writeline("%s = _import_ns.get(%s, context.get(%s, UNDEFINED))" % (ident, repr(ident), repr(ident)))
|
|
else:
|
|
self.printer.writeline("%s = context.get(%s, UNDEFINED)" % (ident, repr(ident)))
|
|
|
|
self.printer.writeline("__M_writer = context.writer()")
|
|
|
|
def write_source_comment(self, node):
|
|
"""write a source comment containing the line number of the corresponding template line."""
|
|
if self.last_source_line != node.lineno:
|
|
self.printer.writeline("# SOURCE LINE %d" % node.lineno)
|
|
self.last_source_line = node.lineno
|
|
|
|
def write_def_decl(self, node, identifiers):
|
|
"""write a locally-available callable referencing a top-level def"""
|
|
funcname = node.function_decl.funcname
|
|
namedecls = node.function_decl.get_argument_expressions()
|
|
nameargs = node.function_decl.get_argument_expressions(include_defaults=False)
|
|
if not self.in_def and (len(self.identifiers.locally_assigned) > 0 or len(self.identifiers.argument_declared) > 0):
|
|
nameargs.insert(0, 'context.locals_(__M_locals)')
|
|
else:
|
|
nameargs.insert(0, 'context')
|
|
self.printer.writeline("def %s(%s):" % (funcname, ",".join(namedecls)))
|
|
self.printer.writeline("return render_%s(%s)" % (funcname, ",".join(nameargs)))
|
|
self.printer.writeline(None)
|
|
|
|
def write_inline_def(self, node, identifiers, nested):
|
|
"""write a locally-available def callable inside an enclosing def."""
|
|
namedecls = node.function_decl.get_argument_expressions()
|
|
self.printer.writeline("def %s(%s):" % (node.name, ",".join(namedecls)))
|
|
filtered = len(node.filter_args.args) > 0
|
|
buffered = eval(node.attributes.get('buffered', 'False'))
|
|
cached = eval(node.attributes.get('cached', 'False'))
|
|
self.printer.writelines(
|
|
"context.caller_stack._push_frame()",
|
|
"try:"
|
|
)
|
|
if buffered or filtered or cached:
|
|
self.printer.writelines(
|
|
"context._push_buffer()",
|
|
)
|
|
|
|
identifiers = identifiers.branch(node, nested=nested)
|
|
|
|
self.write_variable_declares(identifiers)
|
|
|
|
self.identifier_stack.append(identifiers)
|
|
for n in node.nodes:
|
|
n.accept_visitor(self)
|
|
self.identifier_stack.pop()
|
|
|
|
self.write_def_finish(node, buffered, filtered, cached)
|
|
self.printer.writeline(None)
|
|
if cached:
|
|
self.write_cache_decorator(node, node.name, namedecls, False, identifiers, inline=True, toplevel=False)
|
|
|
|
def write_def_finish(self, node, buffered, filtered, cached, callstack=True):
|
|
"""write the end section of a rendering function, either outermost or inline.
|
|
|
|
this takes into account if the rendering function was filtered, buffered, etc.
|
|
and closes the corresponding try: block if any, and writes code to retrieve captured content,
|
|
apply filters, send proper return value."""
|
|
if not buffered and not cached and not filtered:
|
|
self.printer.writeline("return ''")
|
|
if callstack:
|
|
self.printer.writelines(
|
|
"finally:",
|
|
"context.caller_stack._pop_frame()",
|
|
None
|
|
)
|
|
|
|
if buffered or filtered or cached:
|
|
if buffered or cached:
|
|
# in a caching scenario, don't try to get a writer
|
|
# from the context after popping; assume the caching
|
|
# implemenation might be using a context with no
|
|
# extra buffers
|
|
self.printer.writelines(
|
|
"finally:",
|
|
"__M_buf = context._pop_buffer()"
|
|
)
|
|
else:
|
|
self.printer.writelines(
|
|
"finally:",
|
|
"__M_buf, __M_writer = context._pop_buffer_and_writer()"
|
|
)
|
|
|
|
if callstack:
|
|
self.printer.writeline("context.caller_stack._pop_frame()")
|
|
|
|
s = "__M_buf.getvalue()"
|
|
if filtered:
|
|
s = self.create_filter_callable(node.filter_args.args, s, False)
|
|
self.printer.writeline(None)
|
|
if buffered and not cached:
|
|
s = self.create_filter_callable(self.compiler.buffer_filters, s, False)
|
|
if buffered or cached:
|
|
self.printer.writeline("return %s" % s)
|
|
else:
|
|
self.printer.writelines(
|
|
"__M_writer(%s)" % s,
|
|
"return ''"
|
|
)
|
|
|
|
def write_cache_decorator(self, node_or_pagetag, name, args, buffered, identifiers, inline=False, toplevel=False):
|
|
"""write a post-function decorator to replace a rendering callable with a cached version of itself."""
|
|
self.printer.writeline("__M_%s = %s" % (name, name))
|
|
cachekey = node_or_pagetag.parsed_attributes.get('cache_key', repr(name))
|
|
cacheargs = {}
|
|
for arg in (('cache_type', 'type'), ('cache_dir', 'data_dir'), ('cache_timeout', 'expiretime'), ('cache_url', 'url')):
|
|
val = node_or_pagetag.parsed_attributes.get(arg[0], None)
|
|
if val is not None:
|
|
if arg[1] == 'expiretime':
|
|
cacheargs[arg[1]] = int(eval(val))
|
|
else:
|
|
cacheargs[arg[1]] = val
|
|
else:
|
|
if self.compiler.pagetag is not None:
|
|
val = self.compiler.pagetag.parsed_attributes.get(arg[0], None)
|
|
if val is not None:
|
|
if arg[1] == 'expiretime':
|
|
cacheargs[arg[1]] == int(eval(val))
|
|
else:
|
|
cacheargs[arg[1]] = val
|
|
|
|
self.printer.writeline("def %s(%s):" % (name, ','.join(args)))
|
|
|
|
# form "arg1, arg2, arg3=arg3, arg4=arg4", etc.
|
|
pass_args = [ '=' in a and "%s=%s" % ((a.split('=')[0],)*2) or a for a in args]
|
|
|
|
self.write_variable_declares(identifiers, toplevel=toplevel, limit=node_or_pagetag.undeclared_identifiers())
|
|
if buffered:
|
|
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))
|
|
# apply buffer_filters
|
|
s = self.create_filter_callable(self.compiler.buffer_filters, s, False)
|
|
self.printer.writelines("return " + s,None)
|
|
else:
|
|
self.printer.writelines(
|
|
"__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)),
|
|
"return ''",
|
|
None
|
|
)
|
|
|
|
def create_filter_callable(self, args, target, is_expression):
|
|
"""write a filter-applying expression based on the filters present in the given
|
|
filter names, adjusting for the global 'default' filter aliases as needed."""
|
|
def locate_encode(name):
|
|
if re.match(r'decode\..+', name):
|
|
return "filters." + name
|
|
else:
|
|
return filters.DEFAULT_ESCAPES.get(name, name)
|
|
|
|
if 'n' not in args:
|
|
if is_expression:
|
|
if self.compiler.pagetag:
|
|
args = self.compiler.pagetag.filter_args.args + args
|
|
if self.compiler.default_filters:
|
|
args = self.compiler.default_filters + args
|
|
for e in args:
|
|
# if filter given as a function, get just the identifier portion
|
|
if e == 'n':
|
|
continue
|
|
m = re.match(r'(.+?)(\(.*\))', e)
|
|
if m:
|
|
(ident, fargs) = m.group(1,2)
|
|
f = locate_encode(ident)
|
|
e = f + fargs
|
|
else:
|
|
x = e
|
|
e = locate_encode(e)
|
|
assert e is not None
|
|
target = "%s(%s)" % (e, target)
|
|
return target
|
|
|
|
def visitExpression(self, node):
|
|
self.write_source_comment(node)
|
|
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):
|
|
s = self.create_filter_callable(node.escapes_code.args, "%s" % node.text, True)
|
|
self.printer.writeline("__M_writer(%s)" % s)
|
|
else:
|
|
self.printer.writeline("__M_writer(%s)" % node.text)
|
|
|
|
def visitControlLine(self, node):
|
|
if node.isend:
|
|
self.printer.writeline(None)
|
|
else:
|
|
self.write_source_comment(node)
|
|
self.printer.writeline(node.text)
|
|
def visitText(self, node):
|
|
self.write_source_comment(node)
|
|
self.printer.writeline("__M_writer(%s)" % repr(node.content))
|
|
def visitTextTag(self, node):
|
|
filtered = len(node.filter_args.args) > 0
|
|
if filtered:
|
|
self.printer.writelines(
|
|
"__M_writer = context._push_writer()",
|
|
"try:",
|
|
)
|
|
for n in node.nodes:
|
|
n.accept_visitor(self)
|
|
if filtered:
|
|
self.printer.writelines(
|
|
"finally:",
|
|
"__M_buf, __M_writer = context._pop_buffer_and_writer()",
|
|
"__M_writer(%s)" % self.create_filter_callable(node.filter_args.args, "__M_buf.getvalue()", False),
|
|
None
|
|
)
|
|
|
|
def visitCode(self, node):
|
|
if not node.ismodule:
|
|
self.write_source_comment(node)
|
|
self.printer.write_indented_block(node.text)
|
|
|
|
if not self.in_def and len(self.identifiers.locally_assigned) > 0:
|
|
# if we are the "template" def, fudge locally declared/modified variables into the "__M_locals" dictionary,
|
|
# which is used for def calls within the same template, to simulate "enclosing scope"
|
|
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()]))
|
|
|
|
def visitIncludeTag(self, node):
|
|
self.write_source_comment(node)
|
|
args = node.attributes.get('args')
|
|
if args:
|
|
self.printer.writeline("runtime._include_file(context, %s, _template_uri, %s)" % (node.parsed_attributes['file'], args))
|
|
else:
|
|
self.printer.writeline("runtime._include_file(context, %s, _template_uri)" % (node.parsed_attributes['file']))
|
|
|
|
def visitNamespaceTag(self, node):
|
|
pass
|
|
|
|
def visitDefTag(self, node):
|
|
pass
|
|
|
|
def visitCallNamespaceTag(self, node):
|
|
# TODO: we can put namespace-specific checks here, such
|
|
# as ensure the given namespace will be imported,
|
|
# pre-import the namespace, etc.
|
|
self.visitCallTag(node)
|
|
|
|
def visitCallTag(self, node):
|
|
self.printer.writeline("def ccall(caller):")
|
|
export = ['body']
|
|
callable_identifiers = self.identifiers.branch(node, nested=True)
|
|
body_identifiers = callable_identifiers.branch(node, nested=False)
|
|
# we want the 'caller' passed to ccall to be used for the body() function,
|
|
# but for other non-body() <%def>s within <%call> we want the current caller off the call stack (if any)
|
|
body_identifiers.add_declared('caller')
|
|
|
|
self.identifier_stack.append(body_identifiers)
|
|
class DefVisitor(object):
|
|
def visitDefTag(s, node):
|
|
self.write_inline_def(node, callable_identifiers, nested=False)
|
|
export.append(node.name)
|
|
# remove defs that are within the <%call> from the "closuredefs" defined
|
|
# in the body, so they dont render twice
|
|
if node.name in body_identifiers.closuredefs:
|
|
del body_identifiers.closuredefs[node.name]
|
|
|
|
vis = DefVisitor()
|
|
for n in node.nodes:
|
|
n.accept_visitor(vis)
|
|
self.identifier_stack.pop()
|
|
|
|
bodyargs = node.body_decl.get_argument_expressions()
|
|
self.printer.writeline("def body(%s):" % ','.join(bodyargs))
|
|
# TODO: figure out best way to specify buffering/nonbuffering (at call time would be better)
|
|
buffered = False
|
|
if buffered:
|
|
self.printer.writelines(
|
|
"context._push_buffer()",
|
|
"try:"
|
|
)
|
|
self.write_variable_declares(body_identifiers)
|
|
self.identifier_stack.append(body_identifiers)
|
|
|
|
for n in node.nodes:
|
|
n.accept_visitor(self)
|
|
self.identifier_stack.pop()
|
|
|
|
self.write_def_finish(node, buffered, False, False, callstack=False)
|
|
self.printer.writelines(
|
|
None,
|
|
"return [%s]" % (','.join(export)),
|
|
None
|
|
)
|
|
|
|
self.printer.writelines(
|
|
# get local reference to current caller, if any
|
|
"caller = context.caller_stack._get_caller()",
|
|
# push on caller for nested call
|
|
"context.caller_stack.nextcaller = runtime.Namespace('caller', context, callables=ccall(caller))",
|
|
"try:")
|
|
self.write_source_comment(node)
|
|
self.printer.writelines(
|
|
"__M_writer(%s)" % self.create_filter_callable([], node.expression, True),
|
|
"finally:",
|
|
"context.caller_stack.nextcaller = None",
|
|
None
|
|
)
|
|
|
|
class _Identifiers(object):
|
|
"""tracks the status of identifier names as template code is rendered."""
|
|
def __init__(self, node=None, parent=None, nested=False):
|
|
if parent is not None:
|
|
# things that have already been declared in an enclosing namespace (i.e. names we can just use)
|
|
self.declared = util.Set(parent.declared).union([c.name for c in parent.closuredefs.values()]).union(parent.locally_declared).union(parent.argument_declared)
|
|
|
|
# if these identifiers correspond to a "nested" scope, it means whatever the
|
|
# parent identifiers had as undeclared will have been declared by that parent,
|
|
# and therefore we have them in our scope.
|
|
if nested:
|
|
self.declared = self.declared.union(parent.undeclared)
|
|
|
|
# top level defs that are available
|
|
self.topleveldefs = util.SetLikeDict(**parent.topleveldefs)
|
|
else:
|
|
self.declared = util.Set()
|
|
self.topleveldefs = util.SetLikeDict()
|
|
|
|
# things within this level that are referenced before they are declared (e.g. assigned to)
|
|
self.undeclared = util.Set()
|
|
|
|
# things that are declared locally. some of these things could be in the "undeclared"
|
|
# list as well if they are referenced before declared
|
|
self.locally_declared = util.Set()
|
|
|
|
# assignments made in explicit python blocks. these will be propigated to
|
|
# the context of local def calls.
|
|
self.locally_assigned = util.Set()
|
|
|
|
# things that are declared in the argument signature of the def callable
|
|
self.argument_declared = util.Set()
|
|
|
|
# closure defs that are defined in this level
|
|
self.closuredefs = util.SetLikeDict()
|
|
|
|
self.node = node
|
|
|
|
if node is not None:
|
|
node.accept_visitor(self)
|
|
|
|
def branch(self, node, **kwargs):
|
|
"""create a new Identifiers for a new Node, with this Identifiers as the parent."""
|
|
return _Identifiers(node, self, **kwargs)
|
|
|
|
defs = property(lambda self:util.Set(self.topleveldefs.union(self.closuredefs).values()))
|
|
|
|
def __repr__(self):
|
|
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))
|
|
|
|
def check_declared(self, node):
|
|
"""update the state of this Identifiers with the undeclared and declared identifiers of the given node."""
|
|
for ident in node.undeclared_identifiers():
|
|
if ident != 'context' and ident not in self.declared.union(self.locally_declared):
|
|
self.undeclared.add(ident)
|
|
for ident in node.declared_identifiers():
|
|
self.locally_declared.add(ident)
|
|
|
|
def add_declared(self, ident):
|
|
self.declared.add(ident)
|
|
if ident in self.undeclared:
|
|
self.undeclared.remove(ident)
|
|
|
|
def visitExpression(self, node):
|
|
self.check_declared(node)
|
|
def visitControlLine(self, node):
|
|
self.check_declared(node)
|
|
def visitCode(self, node):
|
|
if not node.ismodule:
|
|
self.check_declared(node)
|
|
self.locally_assigned = self.locally_assigned.union(node.declared_identifiers())
|
|
def visitDefTag(self, node):
|
|
if node.is_root():
|
|
self.topleveldefs[node.name] = node
|
|
elif node is not self.node:
|
|
self.closuredefs[node.name] = node
|
|
for ident in node.undeclared_identifiers():
|
|
if ident != 'context' and ident not in self.declared.union(self.locally_declared):
|
|
self.undeclared.add(ident)
|
|
# visit defs only one level deep
|
|
if node is self.node:
|
|
for ident in node.declared_identifiers():
|
|
self.argument_declared.add(ident)
|
|
for n in node.nodes:
|
|
n.accept_visitor(self)
|
|
def visitIncludeTag(self, node):
|
|
self.check_declared(node)
|
|
def visitPageTag(self, node):
|
|
for ident in node.declared_identifiers():
|
|
self.argument_declared.add(ident)
|
|
self.check_declared(node)
|
|
|
|
def visitCallNamespaceTag(self, node):
|
|
self.visitCallTag(node)
|
|
|
|
def visitCallTag(self, node):
|
|
if node is self.node:
|
|
for ident in node.undeclared_identifiers():
|
|
if ident != 'context' and ident not in self.declared.union(self.locally_declared):
|
|
self.undeclared.add(ident)
|
|
for ident in node.declared_identifiers():
|
|
self.argument_declared.add(ident)
|
|
for n in node.nodes:
|
|
n.accept_visitor(self)
|
|
else:
|
|
for ident in node.undeclared_identifiers():
|
|
if ident != 'context' and ident not in self.declared.union(self.locally_declared):
|
|
self.undeclared.add(ident)
|
|
|