[pypy-svn] rev 1131 - in pypy/trunk/src/pypy/objspace/std: . test
arigo at codespeak.net
arigo at codespeak.net
Sun Jul 13 23:28:35 CEST 2003
Author: arigo
Date: Sun Jul 13 23:28:35 2003
New Revision: 1131
Modified:
pypy/trunk/src/pypy/objspace/std/boolobject.py
pypy/trunk/src/pypy/objspace/std/floatobject.py
pypy/trunk/src/pypy/objspace/std/multimethod.py
pypy/trunk/src/pypy/objspace/std/objectobject.py
pypy/trunk/src/pypy/objspace/std/objspace.py
pypy/trunk/src/pypy/objspace/std/test/test_multimethod.py
pypy/trunk/src/pypy/objspace/std/userobject.py
Log:
Still some light refactoring of the multimethod mecanisms.
Now we give enough static clues about the types to allow for
a complete list of what-to-try-in-which-order to be built
for each input argument types. This cleanly separates method
selection from method call, as wished by mwh and Samuele.
It should also speed up pypy a bit but not dramatically
enough to notice I'm afraid.
Modified: pypy/trunk/src/pypy/objspace/std/boolobject.py
==============================================================================
--- pypy/trunk/src/pypy/objspace/std/boolobject.py (original)
+++ pypy/trunk/src/pypy/objspace/std/boolobject.py Sun Jul 13 23:28:35 2003
@@ -27,6 +27,7 @@
# to an .intval one
def delegate__Bool(space, w_bool):
return intobject.W_IntObject(space, int(w_bool.boolval))
+delegate__Bool.result_class = intobject.W_IntObject
delegate__Bool.priority = PRIORITY_PARENT_TYPE
Modified: pypy/trunk/src/pypy/objspace/std/floatobject.py
==============================================================================
--- pypy/trunk/src/pypy/objspace/std/floatobject.py (original)
+++ pypy/trunk/src/pypy/objspace/std/floatobject.py Sun Jul 13 23:28:35 2003
@@ -28,6 +28,7 @@
# int-to-float delegation
def delegate__Int(space, w_intobj):
return W_FloatObject(space, float(w_intobj.intval))
+delegate__Int.result_class = W_FloatObject
delegate__Int.priority = PRIORITY_CHANGE_TYPE
Modified: pypy/trunk/src/pypy/objspace/std/multimethod.py
==============================================================================
--- pypy/trunk/src/pypy/objspace/std/multimethod.py (original)
+++ pypy/trunk/src/pypy/objspace/std/multimethod.py Sun Jul 13 23:28:35 2003
@@ -38,31 +38,184 @@
self.operatorsymbol = operatorsymbol
self.dispatch_table = {}
self.cache_table = {}
+ self.cache_delegator_key = None
def register(self, function, *types):
- for type in types:
- if (hasattr(type, 'dispatchclass') and
- 'dispatchclass' not in type.__dict__):
- raise error, ('looks like you forgot to call\n'
- 'registerimplementation(%r)' % type)
- # W_ANY can be used as a placeholder to dispatch on any value.
functions = self.dispatch_table.setdefault(types, [])
if function not in functions:
functions.append(function)
self.cache_table.clear()
+ def compile_calllist(self, argclasses, delegate):
+ """Compile a list of calls to try for the given classes of the
+ arguments. Return a function that should be called with the
+ actual arguments."""
+ if delegate.key is not self.cache_delegator_key:
+ self.cache_table.clear()
+ self.cache_delegator_key = delegate.key
+ try:
+ return self.cache_table[argclasses]
+ except KeyError:
+ calllist = []
+ self.internal_buildcalllist(argclasses, delegate, calllist)
+ result = self.internal_compilecalllist(argclasses, calllist)
+ self.cache_table[argclasses] = result
+ return result
+
+ def internal_compilecalllist(self, argclasses, calllist):
+ """Translate a call list into the source of a Python function
+ which is optimized and doesn't do repeated conversions on the
+ same arguments."""
+ if len(calllist) == 1:
+ fn, conversions = calllist[0]
+ if conversions.count([]) == len(conversions):
+ # no conversion, just calling a single function: return
+ # that function directly
+ return fn
+
+ #print '**** compile **** ', self.operatorsymbol, [
+ # t.__name__ for t in argclasses]
+ arglist = ['a%d'%i for i in range(len(argclasses))] + ['*extraargs']
+ source = ['def do(space,%s):' % ','.join(arglist)]
+ converted = [{(): 'a%d'%i} for i in range(len(argclasses))]
+
+ def make_conversion(argi, convlist):
+ if tuple(convlist) in converted[argi]:
+ return converted[argi][tuple(convlist)]
+ else:
+ prev = make_conversion(argi, convlist[:-1])
+ new = '%s_%d' % (prev, len(converted[argi]))
+ fname = all_functions.setdefault(convlist[-1],
+ 'd%d' % len(all_functions))
+ source.append(' %s = %s(space,%s)' % (new, fname, prev))
+ converted[argi][tuple(convlist)] = new
+ return new
+
+ all_functions = {}
+ has_firstfailure = False
+ for fn, conversions in calllist:
+ # make the required conversions
+ fname = all_functions.setdefault(fn, 'f%d' % len(all_functions))
+ arglist = [make_conversion(i, conversions[i])
+ for i in range(len(argclasses))] + ['*extraargs']
+ source.append( ' try:')
+ source.append( ' return %s(space,%s)' % (
+ fname, ','.join(arglist)))
+ if has_firstfailure:
+ source.append(' except FailedToImplement:')
+ else:
+ source.append(' except FailedToImplement, firstfailure:')
+ has_firstfailure = True
+ source.append( ' pass')
+
+ # complete exhaustion
+ if has_firstfailure:
+ source.append(' raise firstfailure')
+ else:
+ source.append(' raise FailedToImplement()')
+ source.append('')
+
+ # compile the function
+ glob = {'FailedToImplement': FailedToImplement}
+ for fn, fname in all_functions.items():
+ glob[fname] = fn
+ #for key, value in glob.items():
+ # print key, '=', value
+ #for line in source:
+ # print line
+ exec '\n'.join(source) in glob
+ return glob['do']
+
+ def internal_buildcalllist(self, argclasses, delegate, calllist):
+ """Build a list of calls to try for the given classes of the
+ arguments. The list contains 'calls' of the following form:
+ (function-to-call, list-of-list-of-converters)
+ The list of converters contains a list of converter functions per
+ argument, with [] meaning that no conversion is needed for
+ that argument."""
+ # look for an exact match first
+ arity = self.arity
+ assert arity == len(argclasses)
+ dispatchclasses = tuple([(c,) for c in argclasses])
+ choicelist = self.buildchoices(dispatchclasses)
+ seen_functions = {}
+ no_conversion = [[]] * arity
+ for signature, function in choicelist:
+ calllist.append((function, no_conversion))
+ seen_functions[function] = 1
+
+ # proceed by expanding the last argument by delegation, step by step
+ # until no longer possible, and then the previous argument, and so on.
+ expanded_args = ()
+ expanded_dispcls = ()
+
+ for argi in range(arity-1, -1, -1):
+ # growing tuple of dispatch classes we can delegate to
+ curdispcls = dispatchclasses[argi]
+ assert len(curdispcls) == 1
+ # maps each dispatch class to a list of converters
+ curargs = {curdispcls[0]: []}
+ # reduce dispatchclasses to the arguments before this one
+ # (on which no delegation has been tried yet)
+ dispatchclasses = dispatchclasses[:argi]
+ no_conversion = no_conversion[:argi]
+
+ while 1:
+ choicelist = delegate.buildchoices((curdispcls,))
+ # the list is sorted by priority
+ progress = False
+ for (t,), function in choicelist:
+ if function is None:
+ # this marks a decrease in the priority.
+ # Don't try delegators with lower priority if
+ # we have already progressed.
+ if progress:
+ break
+ else:
+ assert hasattr(function, 'result_class'), (
+ "delegator %r must have a result_class" % function)
+ nt = function.result_class
+ if nt not in curargs:
+ curdispcls += (nt,)
+ srcconvs = curargs[t]
+ if not getattr(function, 'trivial_delegation',False):
+ srcconvs = srcconvs + [function]
+ curargs[nt] = srcconvs
+ progress = True
+ else:
+ if not progress:
+ break # no progress, and delegators list exhausted
+
+ # progress: try again to dispatch with this new set of types
+ choicelist = self.buildchoices(
+ dispatchclasses + (curdispcls,) + expanded_dispcls)
+ for signature, function in choicelist:
+ if function not in seen_functions:
+ seen_functions[function] = 1
+ # collect arguments: arguments after position argi...
+ after_argi = [expanded_args[j][signature[j]]
+ for j in range(argi+1-arity, 0)] # nb. j<0
+ # collect arguments: argument argi...
+ arg_argi = curargs[signature[argi]]
+ # collect all arguments
+ newargs = no_conversion + [arg_argi] + after_argi
+ # record the call
+ calllist.append((function, newargs))
+ # end of while 1: try on delegating the same argument i
+
+ # proceed to the next argument
+ expanded_args = (curargs,) + expanded_args
+ expanded_dispcls = (curdispcls,) + expanded_dispcls
+
def buildchoices(self, allowedtypes):
"""Build a list of all possible implementations we can dispatch to,
sorted best-first, ignoring delegation."""
# 'types' is a tuple of tuples of classes, one tuple of classes per
- # argument. (Delegation needs to call buildchoice() with than one
- # class for a single argument.)
- try:
- result = self.cache_table[allowedtypes] # try from the cache first
- except KeyError:
- result = self.cache_table[allowedtypes] = []
- self.internal_buildchoices(allowedtypes, (), result)
- self.postprocessresult(allowedtypes, result)
+ # argument. (After delegation, we need to call buildchoice() with
+ # more than one possible class for a single argument.)
+ result = []
+ self.internal_buildchoices(allowedtypes, (), result)
+ self.postprocessresult(allowedtypes, result)
#print self.operatorsymbol, allowedtypes, result
# the result is a list a tuples (function, signature).
return result
@@ -129,29 +282,33 @@
def __init__(self):
MultiMethod.__init__(self, 'delegate', 1, [])
-
+ self.key = object()
+
+ def register(self, function, *types):
+ AbstractMultiMethod.register(self, function, *types)
+ self.key = object() # change the key to force recomputation
+
def postprocessresult(self, allowedtypes, result):
- by_priority = {} # classify delegators by priority
-
- # add delegation from a class to its parent classes
+ # add delegation from a class to the *first* immediate parent class
arg1types, = allowedtypes
- parenttypes = []
for t in arg1types:
- parenttypes += list(t.__bases__)
- if parenttypes:
- def delegate_to_parent_classes(space, a, parenttypes=parenttypes):
- return [(t, a) for t in parenttypes
- if issubclass(a.dispatchclass, t)]
- # hard-wire it at priority 0
- by_priority[0] = [((t,), delegate_to_parent_classes)
- for t in arg1types]
+ if t.__bases__:
+ base = t.__bases__[0]
+ def delegate_to_parent_class(space, a):
+ return a
+ delegate_to_parent_class.trivial_delegation = True
+ delegate_to_parent_class.result_class = base
+ delegate_to_parent_class.priority = 0
+ # hard-wire it at priority 0
+ result.append(((t,), delegate_to_parent_class))
# sort the results in priority order, and insert None marks
# between jumps in the priority values. Higher priority values
# first.
+ by_priority = {} # classify delegators by priority
for signature, function in result:
assert hasattr(function, 'priority'), (
- "delegator function must have a priority")
+ "delegator %r must have a priority" % function)
sublist = by_priority.setdefault(function.priority, [])
sublist.append((signature, function))
delegators = by_priority.items()
@@ -220,7 +377,7 @@
raise OperationError(*e.args)
else:
# raise a TypeError for a FailedToImplement
- initialtypes = [a.dispatchclass
+ initialtypes = [a.__class__
for a in args[:self.multimethod.arity]]
if len(initialtypes) <= 1:
plural = ""
@@ -235,90 +392,10 @@
def perform_call(self, args):
arity = self.multimethod.arity
- extraargs = args[arity:]
-
-## if self.ASSERT_BASE_TYPE:
-## for a in args[:arity]:
-## assert issubclass(a.dispatchclass, self.ASSERT_BASE_TYPE), (
-## "multimethod '%s' call with non wrapped argument: %r" %
-## (self.multimethod.operatorsymbol, a))
-
- # look for an exact match first
- firstfailure = None
- types = tuple([(a.dispatchclass,) for a in args])
- choicelist = self.multimethod.buildchoices(types)
- for signature, function in choicelist:
- try:
- return function(self.space, *args)
- except FailedToImplement, e:
- # we got FailedToImplement, record the first such error
- firstfailure = firstfailure or e
-
- seen_functions = {}
- for signature, function in choicelist:
- seen_functions[function] = 1
-
- # proceed by expanding the last argument by delegation, step by step
- # until no longer possible, and then the previous argument, and so on.
- expanded_args = ()
- expanded_types = ()
- delegate = self.space.delegate.multimethod
-
- for argi in range(arity-1, -1, -1):
- curtypes = types[argi] # growing tuple of types we can delegate to
- assert len(curtypes) == 1
- curobjs = {curtypes[0]: args[argi]} # maps them to actual objects
- args = args[:argi] # initial segments of arguments before this one
- types = types[:argi] # same with types (no deleg tried on them yet)
- while 1:
- choicelist = delegate.buildchoices((curtypes,))
- # the list is sorted by priority
- progress = False
- for (t,), function in choicelist:
- if function is None:
- # this marks a decrease in the priority.
- # Don't try delegators with lower priority if
- # we have already progressed.
- if progress:
- break
- else:
- converted = function(self.space, curobjs[t])
- if not isinstance(converted, list):
- converted = [(converted.dispatchclass,
- converted)]
- for t, a in converted:
- if t not in curobjs:
- curtypes += (t,)
- curobjs[t] = a
- progress = True
- else:
- if not progress:
- break # no progress, and delegators list exhausted
-
- # progress: try again to dispatch with this new set of types
- choicelist = self.multimethod.buildchoices(
- types + (curtypes,) + expanded_types)
- for signature, function in choicelist:
- if function not in seen_functions:
- seen_functions[function] = 1
- # collect arguments: arguments after position i...
- tail = [expanded_args[j][signature[j]]
- for j in range(argi+1-arity, 0)] # nb. j<0
- # argments before and up to position i...
- newargs= args + (curobjs[signature[argi]],) + tuple(tail)
- try:
- return function(self.space, *newargs+extraargs)
- except FailedToImplement, e:
- # record the first FailedToImplement
- firstfailure = firstfailure or e
- # end of while 1: try on delegating the same argument i
-
- # proceed to the next argument
- expanded_args = (curobjs,) + expanded_args
- expanded_types = (curtypes,) + expanded_types
-
- # complete exhaustion
- raise firstfailure or FailedToImplement()
+ argclasses = tuple([a.__class__ for a in args[:arity]])
+ delegate = self.space.delegate.multimethod
+ fn = self.multimethod.compile_calllist(argclasses, delegate)
+ return fn(self.space, *args)
def is_empty(self):
return self.multimethod.is_empty()
Modified: pypy/trunk/src/pypy/objspace/std/objectobject.py
==============================================================================
--- pypy/trunk/src/pypy/objspace/std/objectobject.py (original)
+++ pypy/trunk/src/pypy/objspace/std/objectobject.py Sun Jul 13 23:28:35 2003
@@ -15,6 +15,7 @@
# any-to-object delegation is quite trivial, because W_ObjectObject is.
def delegate__ANY(space, w_obj):
return W_ObjectObject(space)
+delegate__ANY.result_class = W_ObjectObject
delegate__ANY.priority = PRIORITY_PARENT_TYPE
def object_init__Object_ANY_ANY(space, w_object, w_args, w_kwds):
Modified: pypy/trunk/src/pypy/objspace/std/objspace.py
==============================================================================
--- pypy/trunk/src/pypy/objspace/std/objspace.py (original)
+++ pypy/trunk/src/pypy/objspace/std/objspace.py Sun Jul 13 23:28:35 2003
@@ -37,7 +37,6 @@
# it may be modified to take 'statictype' instead of requiring it to be
# stored in 'implcls' itself
assert issubclass(implcls, W_Object)
- implcls.__dict__.setdefault('dispatchclass', implcls)
##################################################################
Modified: pypy/trunk/src/pypy/objspace/std/test/test_multimethod.py
==============================================================================
--- pypy/trunk/src/pypy/objspace/std/test/test_multimethod.py (original)
+++ pypy/trunk/src/pypy/objspace/std/test/test_multimethod.py Sun Jul 13 23:28:35 2003
@@ -15,15 +15,17 @@
def from_y_to_x(space, yinstance):
return X(yinstance)
+from_y_to_x.result_class = X
from_y_to_x.priority = 2
-def from_x_to_str_sometimes(space, xinstance):
- if xinstance.value:
- return w('!' + repr(xinstance.value))
- else:
- return []
+def from_x_to_str(space, xinstance):
+ #if xinstance.value:
+ return w('!' + repr(xinstance.value))
+ #else:
+ # return []
-from_x_to_str_sometimes.priority = 2
+from_x_to_str.result_class = str
+from_x_to_str.priority = 2
class Y:
@@ -65,24 +67,27 @@
add.register(add_int_any, int, object)
delegate = DelegateMultiMethod()
- delegate.register(from_y_to_x, Y)
- delegate.register(from_x_to_str_sometimes, X)
+ delegate.register(from_y_to_x, Y)
+ delegate.register(from_x_to_str, X)
def wrap(self, x):
return '<wrapped %r>' % (x,)
w_TypeError = 'w_TypeError'
-def w(x, cache={}):
- if type(x) in cache:
- Stub = cache[type(x)]
- else:
- Stub = type(type(x))('%s_stub' % type(x).__name__, (type(x),), {})
- Stub.dispatchclass = Stub
- cache[type(x)] = Stub
- return Stub(x)
+##def w(x, cache={}):
+## if type(x) in cache:
+## Stub = cache[type(x)]
+## else:
+## Stub = type(type(x))('%s_stub' % type(x).__name__, (type(x),), {})
+## Stub.dispatchclass = Stub
+## cache[type(x)] = Stub
+## return Stub(x)
-X.dispatchclass = X
-Y.dispatchclass = Y
+##X.dispatchclass = X
+##Y.dispatchclass = Y
+
+def w(x):
+ return x
class TestMultiMethod(test.TestCase):
@@ -127,10 +132,10 @@
space = self.space
self.assertRaises(OperationError, space.add, w([3]), w(4))
self.assertRaises(OperationError, space.add, w(3.0), w('bla'))
- self.assertRaises(OperationError, space.add, X(0), w("spam"))
- self.assertRaises(OperationError, space.add, Y(666), w("egg"))
+ #self.assertRaises(OperationError, space.add, X(0), w("spam"))
+ #self.assertRaises(OperationError, space.add, Y(666), w("egg"))
- def test_delegate_x_to_str_sometimes(self):
+ def test_delegate_x_to_str(self):
space = self.space
r = space.add(X(42), w("spam"))
self.assertEquals(repr(r), "('add_string_string', '!42', 'spam')")
Modified: pypy/trunk/src/pypy/objspace/std/userobject.py
==============================================================================
--- pypy/trunk/src/pypy/objspace/std/userobject.py (original)
+++ pypy/trunk/src/pypy/objspace/std/userobject.py Sun Jul 13 23:28:35 2003
@@ -42,10 +42,17 @@
try:
return _bltin_subclass_cache[cls]
except:
- subcls = type(W_Object)("%s_sub" % cls.__name__, (cls,),
- {'statictype' : W_UserType,
- 'bltbase' : cls,
- 'dispatchclass': W_UserObject})
+ subcls = type(W_Object)("%s_sub" % cls.__name__, (W_UserObject, cls),
+ {'statictype': W_UserType})
+
+ # W_UserObject-to-the-parent-builtin-type delegation
+ def delegate_to_parent_builtin(space, w_userobj):
+ return w_userobj
+ delegate_to_parent_builtin.trivial_delegation = True
+ delegate_to_parent_builtin.result_class = cls
+ delegate_to_parent_builtin.priority = PRIORITY_PARENT_TYPE
+ StdObjSpace.delegate.register(delegate_to_parent_builtin, subcls)
+
_bltin_subclass_cache[cls] = subcls
return subcls
@@ -73,14 +80,6 @@
return mostspecialized
-# W_UserObject-to-the-parent-builtin-type delegation
-# So far this is the only delegation that produces a result
-# of a variable type.
-def delegate__User(space, w_userobj):
- return [(w_userobj.bltbase,w_userobj)]
-delegate__User.priority = PRIORITY_PARENT_TYPE
-
-
def type__User(space, w_userobj):
return w_userobj.w_type
More information about the Pypy-commit
mailing list