| # 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) |
| |