# HG changeset patch # User Paul Boddie # Date 1275786189 -7200 # Node ID 8aba4d89a07805c98dc5823522403d7f6171b739 # Parent ac8d3b0cf626b4b7aa2ad35324247f9d0d9356d1 Changed the function/lambda support to generate special function instances for both kinds of function where the defaults are dynamic. Added support for testing dynamic defaults and for the strict constant nature of non-dynamic attributes. Consolidated the function declaration and definition code generation routines. diff -r ac8d3b0cf626 -r 8aba4d89a078 micropython/ast.py --- a/micropython/ast.py Sat Jun 05 15:11:26 2010 +0200 +++ b/micropython/ast.py Sun Jun 06 03:03:09 2010 +0200 @@ -586,70 +586,20 @@ # Only store the name when visiting this node from outside. if self.unit is not node.unit: - self.new_op(LoadFunction(node.unit)) + self._visitFunctionDeclaration(node) + + # Record the declared function. + self.record_value() self._visitName(node, self.name_store_instructions) # AssName equivalent self.set_source() self.discard_value() - self._generateFunctionDefaults(node.unit) - # Visiting of the code occurs when get_code is invoked on this node. else: - # Check frames using the function's details. - - fn = node.unit - nparams = len(fn.positional_names) - ndefaults = len(fn.defaults) - - fn.body_block = self.new_block() - - # Check the number of parameters and defaults. - - self.new_op(CheckFrame((nparams, ndefaults))) - if ndefaults > 0: - self.new_op(LoadFunction(fn)) - self.new_op(FillDefaults((nparams, ndefaults))) - - # Produce the body. - - self.set_block(fn.body_block) - - extend = ExtendFrame() - self.new_op(extend) - - # For functions with star parameters, make a special list for the - # extra arguments and re-map the parameter. - - if fn.has_star: - self.new_op(CopyExtra(nparams)) - - # Ensure that the star parameter has a slot in the frame. - - self.new_op(CheckExtra(nparams)) - self.new_op(StoreTemp(nparams)) - - # Add any attribute usage guards. - - if self.optimiser.should_optimise_accesses_by_attribute_usage() and hasattr(node, "_attrnames"): - self._generateGuards(node) - - # Visit the actual code. - - self.dispatch(node.code) - - # Add a return statement where one is not already produced. - - if not isinstance(self.last_op(), Return): - self.dispatch(compiler.ast.Name("None")) - self.new_op(StoreResult()) - self.new_op(Return()) - - # Make sure that enough frame space is reserved from the start. - - self.set_frame_usage(node, extend) + self._visitFunctionDefinition(node) def visitGlobal(self, node): pass @@ -675,58 +625,15 @@ # outside. if self.unit is not node.unit: - fn = node.unit - ndefaults = len(fn.defaults) - temp = self._generateFunctionDefaults(fn) - - if ndefaults > 0: - self.new_op(LoadConst(fn)) - else: - self.new_op(LoadFunction(fn)) - # Populate the new object required for the function. + # Provide the declared function. - if temp is not None: - self.new_op(LoadCallable()) - self.new_op(temp) - self.new_op(StoreCallable()) - - self.new_op(temp) - #self.discard_temp(temp) + self._visitFunctionDeclaration(node) # Visiting of the code occurs when get_code is invoked on this node. else: - # Check frames using the function's details. - - fn = node.unit - nparams = len(fn.positional_names) - ndefaults = len(fn.defaults) - - fn.body_block = self.new_block() - - # Check the number of parameters and defaults. - - self.new_op(CheckFrame((nparams, ndefaults))) - if ndefaults > 0: - self.new_op(LoadTemp(0)) # context provides storage - self.new_op(FillDefaults((nparams, ndefaults))) - - # Produce the body. - - self.set_block(fn.body_block) - - extend = ExtendFrame() - self.new_op(extend) - - self.dispatch(node.code) - - self.new_op(StoreResult()) - self.new_op(Return()) - - # Make sure that enough frame space is reserved from the start. - - self.set_frame_usage(node, extend) + self._visitFunctionDefinition(node) def visitModule(self, node): extend = ExtendFrame() diff -r ac8d3b0cf626 -r 8aba4d89a078 micropython/data.py --- a/micropython/data.py Sat Jun 05 15:11:26 2010 +0200 +++ b/micropython/data.py Sun Jun 06 03:03:09 2010 +0200 @@ -560,6 +560,16 @@ self.context_values.update(context_values) + def is_strict_constant(self): + + """ + Return whether this attribute references something that can be regarded + as being constant. + """ + + value = self.get_value() + return not (value is None or isinstance(value, Instance)) + def is_static_attribute(self): """ @@ -1102,11 +1112,6 @@ self.astnode = node node._def = self - # For lambda functions with defaults, add a context argument. - - if name is None and defaults: - self.argnames.insert(0, "") - # Initialise the positional names. self.positional_names = self.argnames[:] @@ -1147,6 +1152,7 @@ # Image generation details. + self.dynamic = None self.location = None self.code_location = None self.code_body_location = None @@ -1185,7 +1191,7 @@ def get_body_block(self): return self.body_block - # Namespace-related methods. + # Defaults-related methods. def store_default(self, attr_or_value): @@ -1198,6 +1204,34 @@ self._set_using_attr(attr, attr_or_value) self.default_attrs.append(attr) + def make_dynamic(self): + + "Return whether this function must be handled using a dynamic object." + + if self.dynamic is None: + for attr in self.default_attrs: + if not attr.is_strict_constant(): + self.dynamic = 1 + self._make_dynamic() + break + else: + self.dynamic = 0 + + return self.dynamic + + is_dynamic = make_dynamic + + def _make_dynamic(self): + + "Where functions have dynamic defaults, add a context argument." + + name = "" + self.argnames.insert(0, name) + self.positional_names.insert(0, name) + self.set(name, Instance()) + + # Namespace-related methods. + def make_global(self, name): if name not in self.argnames and not self.has_key(name): self.globals.add(name) diff -r ac8d3b0cf626 -r 8aba4d89a078 micropython/inspect.py --- a/micropython/inspect.py Sat Jun 05 15:11:26 2010 +0200 +++ b/micropython/inspect.py Sun Jun 06 03:03:09 2010 +0200 @@ -261,6 +261,10 @@ # Namespace methods. + def in_class(self, namespaces=None): + namespaces = namespaces or self.namespaces + return len(namespaces) > 1 and isinstance(namespaces[-2], Class) + def store(self, name, obj): "Record attribute or local 'name', storing 'obj'." @@ -454,19 +458,28 @@ self.functions.append((node, self.namespaces + [function])) + # Store the function. + if name is not None: self.store(name, function) else: self.store_lambda(function) - # Where defaults exist, an instance needs creating. Thus, it makes - # no sense to return a reference to the function here, since the - # recipient will not be referencing the function itself. + # Test the defaults and assess whether an dynamic object will result. + + if function.make_dynamic(): + + # Forbid dynamic methods, since the context of methods is the + # instance itself. - if node.defaults: - return Instance() # indicates no known target + if self.in_class(): + raise InspectError(self.full_name(), node, "Methods cannot define dynamic defaults.") - return function + result = Instance() # indicates no known target + else: + result = function + + return result def _visitFunctionBody(self, node, namespaces): @@ -475,7 +488,7 @@ # Current namespace is the function. # Previous namespace is the class. - if len(namespaces) > 1 and isinstance(namespaces[-2], Class): + if self.in_class(namespaces): if namespaces[-1].name == "__init__": self.in_init = 1 self.in_method = 1 diff -r ac8d3b0cf626 -r 8aba4d89a078 micropython/trans.py --- a/micropython/trans.py Sat Jun 05 15:11:26 2010 +0200 +++ b/micropython/trans.py Sun Jun 06 03:03:09 2010 +0200 @@ -1019,6 +1019,103 @@ if temp_context is not None: self.discard_temp(temp_context) + def _visitFunctionDeclaration(self, node): + + """ + Visit the function declaration at 'node', which can be a lambda or a + named function. As a consequence an instruction will be generated which + provides a reference to the function. + """ + + fn = node.unit + ndefaults = len(fn.defaults) + temp = self._generateFunctionDefaults(fn) + + if ndefaults > 0: + self.new_op(LoadConst(fn)) + else: + self.new_op(LoadFunction(fn)) + + # Populate the new object required for the function. + + if temp is not None: + self.new_op(LoadCallable()) + self.new_op(temp) + self.new_op(StoreCallable()) + + self.new_op(temp) + #self.discard_temp(temp) + + def _visitFunctionDefinition(self, node): + + """ + Visit the function definition at 'node', which can be a lambda or a + named function, generating the prelude with argument and default + checking, plus the body of the function itself. + """ + + # Check frames using the function's details. + + fn = node.unit + nparams = len(fn.positional_names) + ndefaults = len(fn.defaults) + + fn.body_block = self.new_block() + + # Check the number of parameters and defaults. + + self.new_op(CheckFrame((nparams, ndefaults))) + if ndefaults > 0: + if fn.is_dynamic(): + self.new_op(LoadTemp(0)) # context provides storage + else: + self.new_op(LoadFunction(fn)) + self.new_op(FillDefaults((nparams, ndefaults))) + + # Produce the body. + + self.set_block(fn.body_block) + + extend = ExtendFrame() + self.new_op(extend) + + # For functions with star parameters, make a special list for the + # extra arguments and re-map the parameter. + + if fn.has_star: + self.new_op(CopyExtra(nparams)) + + # Ensure that the star parameter has a slot in the frame. + + self.new_op(CheckExtra(nparams)) + self.new_op(StoreTemp(nparams)) + + # Add any attribute usage guards. + + if self.optimiser.should_optimise_accesses_by_attribute_usage() and hasattr(node, "_attrnames"): + self._generateGuards(node) + + # Visit the actual code. + + self.dispatch(node.code) + + # Add a return statement where one is not already produced. + + if not isinstance(self.last_op(), Return): + + # Return None for normal functions without explicit return + # statements. + + if fn.name is not None: + self.dispatch(compiler.ast.Name("None")) + + self.new_op(StoreResult()) + self.new_op(Return()) + + # Make sure that enough frame space is reserved from the start. + + self.set_frame_usage(node, extend) + def _generateFunctionDefaults(self, function): """ @@ -1031,10 +1128,10 @@ if not attr_to_default: return None - # Where a lambda is involved, construct a dynamic object to hold the - # defaults. + # Where non-constant defaults are involved, construct a dynamic object + # to hold the defaults. - dynamic = function.name is None + dynamic = function.is_dynamic() if dynamic: self.make_instance(self.get_builtin_class("function", function), len(attr_to_default)) diff -r ac8d3b0cf626 -r 8aba4d89a078 tests/lambda_defaults_local_non_constant.py --- a/tests/lambda_defaults_local_non_constant.py Sat Jun 05 15:11:26 2010 +0200 +++ b/tests/lambda_defaults_local_non_constant.py Sun Jun 06 03:03:09 2010 +0200 @@ -7,7 +7,12 @@ return f(x) add_2 = make_add(2) +add_3 = make_add(3) + result_3 = add_2(1) -result2_3 = g(add_2, 1) +result_4 = g(add_2, 2) + +result_5 = add_3(2) +result_6 = g(add_3, 3) # vim: tabstop=4 expandtab shiftwidth=4 diff -r ac8d3b0cf626 -r 8aba4d89a078 tests/nested_functions_using_defaults.py --- a/tests/nested_functions_using_defaults.py Sat Jun 05 15:11:26 2010 +0200 +++ b/tests/nested_functions_using_defaults.py Sun Jun 06 03:03:09 2010 +0200 @@ -5,7 +5,10 @@ return p return b -f = a(2) -result_2 = f() +f2 = a(2) +f3 = a(3) + +result_2 = f2() +result_3 = f3() # vim: tabstop=4 expandtab shiftwidth=4