# HG changeset patch # User Paul Boddie # Date 1275830870 -7200 # Node ID 5dadd4e4a171ee1a0a981337d10f339853f6c871 # Parent 8aba4d89a07805c98dc5823522403d7f6171b739 Introduced the notion of dynamic definitions, to be used in conjunction with the assessment of default parameter values, in order to determine whether functions (and lambdas) are dynamic. Fixed default layout for static and dynamic functions, removing lambda-specific tests. Changed the result of function/lambda definitions during inspection to be a genuine function which can then be tested for dynamic properties. Fixed loop tracking within functions. diff -r 8aba4d89a078 -r 5dadd4e4a171 docs/invocation.txt --- a/docs/invocation.txt Sun Jun 06 03:03:09 2010 +0200 +++ b/docs/invocation.txt Sun Jun 06 15:27:50 2010 +0200 @@ -163,13 +163,31 @@ Approach #2 - add arguments, add defaults while checking frame -Defaults for lambda functions: +Dynamic functions: - f = lambda x, y=default: ... + def f(x): + def g(y=x): # dynamic: y depends on non-constant value + ... + def h(y=2): # static: y depends on constant value + ... + + def f(x): + g = lambda y=x: ... # dynamic: y depends on non-constant value + h = lambda y=2: ... # static: y depends on constant value - Defines instance of f with method: +Representation of dynamic functions: + + f = lambda x, y=nonconst: ... + + def f(x, y=nonconst): + ... - def (, x, y=default): + Defines instance with method: + + def (, x, y=nonconst): + ... + + def f(, x, y=nonconst): ... Where default is attribute #1. diff -r 8aba4d89a078 -r 5dadd4e4a171 micropython/__init__.py --- a/micropython/__init__.py Sun Jun 06 03:03:09 2010 +0200 +++ b/micropython/__init__.py Sun Jun 06 15:27:50 2010 +0200 @@ -167,9 +167,9 @@ self.code.append(obj) # Append any default values to the image. - # Only do this for named functions (not lambdas). + # Only do this for functions which are not dynamic. - if obj.name is not None: + if not obj.is_dynamic(): self.code += obj.default_attrs # Omit built-in function code where requested. diff -r 8aba4d89a078 -r 5dadd4e4a171 micropython/data.py --- a/micropython/data.py Sun Jun 06 03:03:09 2010 +0200 +++ b/micropython/data.py Sun Jun 06 15:27:50 2010 +0200 @@ -560,6 +560,15 @@ self.context_values.update(context_values) + def is_constant(self): + + """ + Return whether this attribute references something that can be regarded + as being constant within a particular scope. + """ + + return self.assignments == 1 + def is_strict_constant(self): """ @@ -656,7 +665,7 @@ return 0 def __repr__(self): - return "Attr(%r, %s, %r) # [%s], %r" % ( + return "Attr(%r, %s, %r) # {[%s] (%r)}" % ( self.position, shortrepr(self.parent), self.name, self._context_values_str(), self.assignments ) @@ -1093,7 +1102,8 @@ "An inspected function." - def __init__(self, name, parent, argnames, defaults, has_star, has_dstar, module=None, node=None): + def __init__(self, name, parent, argnames, defaults, has_star, has_dstar, + dynamic_def=0, module=None, node=None): """ Initialise the function with the given 'name', 'parent', list of @@ -1109,6 +1119,7 @@ self.defaults = defaults self.has_star = has_star self.has_dstar = has_dstar + self.dynamic_def = dynamic_def self.astnode = node node._def = self @@ -1210,7 +1221,7 @@ if self.dynamic is None: for attr in self.default_attrs: - if not attr.is_strict_constant(): + if not attr.is_strict_constant() and self.dynamic_def: self.dynamic = 1 self._make_dynamic() break @@ -1333,7 +1344,7 @@ "Make an instantiator function from a method, keeping all arguments." function = Function(self.parent.name, self.parent.parent, self.argnames, self.defaults, - self.has_star, self.has_dstar, self.module, self.astnode) + self.has_star, self.has_dstar, self.dynamic_def, self.module, self.astnode) function.default_attrs = self.default_attrs return function diff -r 8aba4d89a078 -r 5dadd4e4a171 micropython/inspect.py --- a/micropython/inspect.py Sun Jun 06 03:03:09 2010 +0200 +++ b/micropython/inspect.py Sun Jun 06 15:27:50 2010 +0200 @@ -67,8 +67,9 @@ Assignments to names within functions are not generally considered to cause the targets of such assignments to provide constant values since functions can be -invoked many times with different inputs. However, there may be benefits in -considering a local to be constant within a single invocation. +invoked many times with different inputs. This affects particularly the +definition of functions or lambdas within functions. However, there may be +benefits in considering a local to be constant within a single invocation. """ from micropython.common import * @@ -111,6 +112,7 @@ self.in_init = 0 # Find instance attributes in __init__ methods. self.in_method = 0 # Find instance attributes in all methods. + self.in_function = 0 # Note function presence, affecting definitions. self.in_loop = 0 # Note loop "membership", affecting assignments. self.namespaces = [] self.module = None @@ -440,6 +442,7 @@ node.defaults, (node.flags & 4 != 0), (node.flags & 8 != 0), + self.in_loop or self.in_function, self, node ) @@ -467,19 +470,8 @@ # 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 self.in_class(): - raise InspectError(self.full_name(), node, "Methods cannot define dynamic defaults.") - - result = Instance() # indicates no known target - else: - result = function - - return result + function.make_dynamic() + return function def _visitFunctionBody(self, node, namespaces): @@ -493,9 +485,16 @@ self.in_init = 1 self.in_method = 1 + in_function = self.in_function + in_loop = self.in_loop + self.in_function = 1 + self.in_loop = 0 + self.namespaces = namespaces self.dispatch(node.code) + self.in_loop = in_loop + self.in_function = in_function self.in_init = 0 self.in_method = 0 @@ -610,6 +609,9 @@ print "Class %r in %r is not global: ignored." % (node.name, self.namespaces[-1].full_name()) return None else: + if self.in_loop: + print "Warning: class %r in %r defined in a loop." % (node.name, self.full_name()) + cls = Class(node.name, self.get_namespace(), self, node) # Visit the base class expressions, attempting to find concrete @@ -711,6 +713,7 @@ self.use_name("__iter__", node.list) self.use_name("next") + in_loop = self.in_loop self.in_loop = 1 self.dispatch(node.assign) self.dispatch(node.list) @@ -721,7 +724,7 @@ self.new_branch() self.dispatch(node.body) self.shelve_branch() - self.in_loop = 0 + self.in_loop = in_loop # Maintain a branch for the else clause or the current retained usage # where execution avoids the conditional clauses. @@ -1000,12 +1003,13 @@ # Propagate attribute usage to branches. + in_loop = self.in_loop self.in_loop = 1 self.dispatch(node.test) self.new_branch(node) self.dispatch(node.body) self.shelve_branch() - self.in_loop = 0 + self.in_loop = in_loop # Maintain a branch for the else clause or the current retained usage # where execution avoids the conditional clauses. diff -r 8aba4d89a078 -r 5dadd4e4a171 micropython/rsvp.py --- a/micropython/rsvp.py Sun Jun 06 03:03:09 2010 +0200 +++ b/micropython/rsvp.py Sun Jun 06 15:27:50 2010 +0200 @@ -177,12 +177,12 @@ if not self.is_generated(with_builtins): item.code_location = item.full_name() - # Skip any defaults for named functions. + # Skip any defaults for static functions. - elif item.name is not None: + elif not item.is_dynamic(): item.code_location = location + len(item.defaults) - # Skip any defaults for lambda functions. + # Skip any defaults for dynamic functions. else: item.code_location = location diff -r 8aba4d89a078 -r 5dadd4e4a171 micropython/trans.py --- a/micropython/trans.py Sun Jun 06 03:03:09 2010 +0200 +++ b/micropython/trans.py Sun Jun 06 15:27:50 2010 +0200 @@ -619,7 +619,10 @@ t = self.optimiser.optimise_known_target() if t: target, context = t - if isinstance(target, Instance): # lambda object + + # Detect dynamic functions acting like instances. + + if isinstance(target, Function) and target.is_dynamic(): target, context = None, None else: target, context = None, None @@ -904,10 +907,9 @@ 'employed_positions' collection. """ - # Where a lambda is involved, construct a dynamic object to hold the - # defaults. + # Where appropriate, construct a dynamic object to hold the defaults. - dynamic = target.name is None + dynamic = target.is_dynamic() # Here, we use negative index values to visit the right hand end of # the defaults list. @@ -1031,20 +1033,18 @@ 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(LoadConst(fn)) self.new_op(LoadCallable()) self.new_op(temp) self.new_op(StoreCallable()) self.new_op(temp) #self.discard_temp(temp) + else: + self.new_op(LoadFunction(fn)) def _visitFunctionDefinition(self, node): @@ -1065,11 +1065,13 @@ # Check the number of parameters and defaults. self.new_op(CheckFrame((nparams, ndefaults))) + + if fn.is_dynamic(): + self.new_op(LoadTemp(0)) # context provides storage + else: + self.new_op(LoadFunction(fn)) + 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. diff -r 8aba4d89a078 -r 5dadd4e4a171 tests/call_func_default_global.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/call_func_default_global.py Sun Jun 06 15:27:50 2010 +0200 @@ -0,0 +1,11 @@ +#!/usr/bin/env python + +d = 4 + +def f(a, b, c=d): + return c + +result_3 = f(1, 2, 3) +result_4 = f(1, 2) + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r 8aba4d89a078 -r 5dadd4e4a171 tests/call_func_default_global_multiple.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/call_func_default_global_multiple.py Sun Jun 06 15:27:50 2010 +0200 @@ -0,0 +1,25 @@ +#!/usr/bin/env python + +d = 4 + +i = 0 +l = [] + +while i < 3: + def f(a, b, c=d): + return c + l.append(f) + i += 1 + +f0 = l[0] +f1 = l[1] +f2 = l[2] + +result0_3 = f0(1, 2, 3) +result1_3 = f1(1, 2, 3) +result2_3 = f2(1, 2, 3) +result0_4 = f0(1, 2) +result1_4 = f1(1, 2) +result2_4 = f2(1, 2) + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r 8aba4d89a078 -r 5dadd4e4a171 tests/call_func_default_global_non_constant.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/call_func_default_global_non_constant.py Sun Jun 06 15:27:50 2010 +0200 @@ -0,0 +1,12 @@ +#!/usr/bin/env python + +d = 2 +d = 4 # make non-constant + +def f(a, b, c=d): + return c + +result_3 = f(1, 2, 3) +result_4 = f(1, 2) + +# vim: tabstop=4 expandtab shiftwidth=4