def _hoist_variables_to_piglet_context(astnode, exclude_functions=frozenset()): """ Template functions extract all local variables from the :var:`piglet.runtime.data` thread local """ # Names we never hoist. # "None" would raise a SyntaxError if we try assigning to it. The others # are used internally by piglet and need to be reserved. restricted_names = {'None', 'True', 'False', 'value_of', 'defined', 'Markup', 'iter', 'AttributeError', 'Exception', 'print', 'getattr'} # Mapping of function -> names used within function func_names = {} # Mapping of {function name: {<ancestor nodes>, ...}} func_ancestors = {} # All names discovered together with their ast location names = [] for fn, ancestors in _get_function_defs(astnode): func_ancestors.setdefault(fn.name, set()).add(ancestors) func_names[fn] = set() is_reserved = lambda n: n.id in restricted_names is_piglet = lambda n: n.id.startswith('__piglet') is_function = lambda n, a: any( a[:len(fancestors)] == fancestors for fancestors in func_ancestors.get(n.id, set())) names = ((ancestors, node) for node, ancestors in _get_out_of_scope_names(astnode) if not (is_reserved(node) or is_piglet(node) or is_function(node, ancestors))) for ancestors, node in names: container_func = next((a for a in reversed(ancestors) if isinstance(a, FunctionDef)), None) if container_func in exclude_functions: continue if container_func is None: lineno = getattr(node, 'lineno', '(unknown line number)') raise AssertionError("Unexpected variable found at {}: {}" .format(lineno, node.id)) func_names[container_func].add(node.id) for f, names in func_names.items(): assignments = [ Assign(targets=[StoreName('__piglet_ctx')], value=Subscript( value=LoadAttribute('__piglet_rtdata.context'), slice=Index(value=Num(n=-1)), ctx=Load() )) ] for n in sorted(names): is_builtin = n in builtin_names if is_builtin: default = LoadAttribute( LoadAttribute('__piglet_rt', 'builtins'), n) else: default = Call(func=Attribute(value=Name(id='__piglet_rt', ctx=Load()), attr='Undefined', ctx=Load()), args=[Str(s=n)], starargs=None, kwargs=None, keywords=[]) value = Call(func=Attribute(value=Name(id='__piglet_ctx', ctx=Load()), attr='get', ctx=Load()), args=[Str(s=n), default], starargs=None, kwargs=None, keywords=[]) a = Assign(targets=[Name(id=n, ctx=Store())], value=value) assignments.append(a) f.body[0:0] = assignments return astnode