[pypy-svn] r8626 - pypy/dist/pypy/tool
tismer at codespeak.net
tismer at codespeak.net
Thu Jan 27 11:02:39 CET 2005
Author: tismer
Date: Thu Jan 27 11:02:39 2005
New Revision: 8626
Added:
pypy/dist/pypy/tool/_enum_exceptions.py
Log:
This is the crazy module that introspects and regenerates
the exceptions module.
It works very nice for the __init__ methods.
Automatic recreation of the __str__ methods appears to
be almost impossible with reasonable effort.
Not that this source file is already as large as the output it produces :-)
Some of the ideas might be applicable in other fields as well.
I learned quite a bit from this.
Added: pypy/dist/pypy/tool/_enum_exceptions.py
==============================================================================
--- (empty file)
+++ pypy/dist/pypy/tool/_enum_exceptions.py Thu Jan 27 11:02:39 2005
@@ -0,0 +1,327 @@
+# this script is used for extracting
+# the information available for exceptions
+# via introspection.
+# The idea is to use it once to create
+# a template for a re-birth of exceptions.py
+
+import types
+
+def classOfAttribute(klass, attname):
+ if attname in klass.__dict__:
+ return klass
+ for base in klass.__bases__:
+ ret = classOfAttribute(base, attname)
+ if ret:
+ return ret
+
+def getAttributes(klass, ignorelist = []):
+ return [name for name in dir(klass) if name not in ignorelist]
+
+def makeExceptionsTemplate(f=None):
+
+ def enumExceptionsInOrder():
+ import exceptions
+ seen = {}
+ ordered = []
+
+ def enumerateOne(exc):
+ seen[exc] = 1
+ for each in exc.__bases__:
+ if each not in seen:
+ enumerateOne(each)
+ ordered.append(exc)
+
+ for each in exceptions.__dict__.values():
+ if isinstance(each, (types.ClassType, type)) and \
+ each not in seen:
+ enumerateOne(each)
+
+ return ordered
+
+ if not f:
+ f = sys.stdout
+
+ for exc in enumExceptionsInOrder():
+ name = exc.__name__
+ bases = exc.__bases__
+ doc = exc.__doc__
+ bases = [this.__name__ for this in bases]
+ bases = ", ".join(bases)
+ if bases: bases = "(%s)" % bases
+
+ ignorelist = "__doc__ __module__".split()
+ # find out class variables and methods
+ simple = []
+ difficult = []
+ for attname in getAttributes(exc, ignorelist):
+ if classOfAttribute(exc, attname) is exc:
+ obj = getattr(exc, attname)
+ (simple, difficult)[callable(obj)].append( (attname, obj) )
+ print >> f, "class %s%s:" % (name, bases)
+ if doc:
+ print >> f, ' """%s"""' % doc
+ if not (simple or difficult or doc):
+ print >> f, " pass"
+ for tup in simple:
+ print >> f, " %s = %r" % tup
+
+ for attname, meth in difficult:
+ print >> f
+ func = globals().get("tryGenerate" + attname, None)
+ if not func:
+ print >> f, " # please implement %s.%s (%r)" % (name, attname, meth)
+ else:
+ try:
+ print >> f, " # auto-generated code, please check carefully!"
+ for line in func(exc):
+ print >> f, " " + line
+ except ValueError, e:
+ print >> f, " # %s" % e
+ print >> f, " # please implement %s.%s (%r)" % (name, attname, meth)
+ print >> f
+
+def tryGenerate__getitem__(exc):
+ for args in (), (1, 2, 3):
+ try:
+ sample = exc(*args)
+ except:
+ raise ValueError, "cannot create instance"
+ if "args" not in sample.__dict__:
+ raise ValueError, "args attribute not found in __dict__"
+ if args != sample.args:
+ raise ValueError, "instance has modified args"
+ for i in range(5):
+ try: x = sample[i]
+ except IndexError: x = 42
+ try: y = args[i]
+ except IndexError: y = 42
+ if x != y:
+ raise ValueError, "args does not behave like a sequence"
+ del sample.args
+ try: x = sample[0]
+ except: x = 42
+ use_default = x is None
+ # looks fine so far.
+ yield "def __getitem__(self, idx):"
+ if use_default:
+ yield " if not hasattr(self, 'args'):"
+ yield " return None"
+ yield " return self.args[idx]"
+
+
+class ProbeObject(object):
+ """ this class creates general "any" objects, and
+ for the special case of SyntaxError, it can behave
+ like a subscriptable object
+ """
+ def __init__(self, argpos, maxprobe=None):
+ self.argpos = argpos
+ self.maxprobe = maxprobe
+ self.probed = []
+ def __getitem__(self, idx):
+ if idx not in self.probed:
+ self.probed.append(idx)
+ if self.maxprobe is not None and idx > self.maxprobe:
+ raise IndexError, "cheat cheat %d" % idx
+ return "arg%d_%s" % (self.argpos, idx)
+ def __repr__(self):
+ if self.probed:
+ return "<arg%d:%r>" % (self.argpos, self.probed)
+ else:
+ return "<arg%d>" % self.argpos
+ def __str__(self):
+ # make this different from repr!
+ return repr(self)[1:-1]
+ def __cmp__(self, other):
+ return cmp( (self.argpos, self.probed), other)
+
+def genArgsToTry(argpos):
+ args = [ProbeObject(argpos),
+ "arg%d" % argpos, u"arg%d" %argpos, 1000+argpos*10]
+ return args
+
+def cartesian(*args):
+ if len(args)== 0:
+ yield args
+ elif len(args) == 1:
+ for item in args[0]:
+ yield (item,)
+ else:
+ for item in args[0]:
+ for other in cartesian(*args[1:]):
+ yield (item,) + other
+
+def probeArgCount(exc, maxprobe=20):
+ worksmaybe = []
+ for i in range(maxprobe):
+ try:
+ probe = exc(*(i,)*i) # test i-tuple
+ worksmaybe.append(i)
+ except TypeError, e:
+ if not str(e).startswith("function takes "):
+ worksmaybe.append(i)
+ except:
+ pass
+ return min(worksmaybe), max(worksmaybe)
+
+def refreshArgs(tup):
+ res = []
+ for arg in tup:
+ if type(arg) is ProbeObject:
+ arg = ProbeObject(arg.argpos) # cleanup probing
+ res.append(arg)
+ return tuple(res)
+
+def findAllArgs(exc, maxprobe):
+ minargs, maxargs = probeArgCount(exc, maxprobe=20)
+ res = []
+ # for minargs args, we need to try combinations
+ arglist = tuple([genArgsToTry(i) for i in range(minargs)])
+ for args in cartesian(*arglist):
+ try:
+ probe = exc(*args)
+ res.append(args)
+ works = refreshArgs(args)
+ break
+ except Exception, e:
+ continue
+ else:
+ raise TypeError, "cannot analyse arguments of %s" % exc.__name__
+ # for the variable part, don't try combinations
+ for i in range(minargs, maxargs):
+ for arg in genArgsToTry(i):
+ args = works + (arg,)
+ try:
+ probe = exc(*args)
+ res.append(args)
+ works = refreshArgs(args)
+ break
+ except:
+ continue
+ else:
+ raise TypeError, "cannot analyse arguments of %s" % exc.__name__
+ return minargs, maxargs, res
+
+def captureAssignments(exc, args):
+ """ we wrap a class around the exc class and record attribute access """
+ assigned = []
+ class WrapExc(exc):
+ def __setattr__(self, name, obj):
+ assigned.append( (name, obj) )
+ self.__dict__[name] = obj
+ probe = WrapExc(*args)
+ names = {}
+ names[args] = "args"
+ for i, arg in enumerate(args):
+ names[arg] = "args[%d]" % i
+ if not isinstance(arg, ProbeObject):
+ continue
+ for subidx in arg.probed:
+ names[arg[subidx]] = "args[%d][%d]" % (i, subidx)
+ def nameof(obj):
+ if obj in names:
+ return names[obj]
+ elif isinstance(obj, (tuple, list)):
+ stuff = [nameof(x) for x in obj]
+ br = str(type(obj)())
+ txt = br[0] + ", ".join(stuff) + br[-1]
+ names[obj] = txt
+ else:
+ names[obj] = "%r # default, hopefully" % obj
+ return names[obj]
+ res = []
+ for name, obj in assigned:
+ res.append("self.%s = %s" % (name, nameof(obj)))
+ return tuple(res)
+
+def tryGenerate__init__(exc, maxprobe=20):
+ minargs, maxargs, working = findAllArgs(exc, maxprobe)
+ # merge assignments in order, record set of arg counts
+ foldcases = {}
+ for args in working:
+ singleprog = captureAssignments(exc, args)
+ for tup in enumerate(singleprog):
+ foldcases.setdefault(tup, [])
+ foldcases[tup].append(len(args))
+ # group assignments by set of arg counts and order
+ groupassign = {}
+ for (order, assignment), argcounts in foldcases.items():
+ key = tuple(argcounts)
+ # special case: we don't raise errors
+ # and always assign to self.args
+ if assignment == "self.args = args" and len(key) != maxprobe:
+ assignment += " # modified: always assign args, no error check"
+ key = tuple(range(maxprobe))
+ groupassign.setdefault(key, [])
+ groupassign[key].append( (order, assignment) )
+ cases = groupassign.items()
+ cases.sort()
+ yield "def __init__(self, *args):"
+ if len(cases) > 1 or len(cases[0][0]) != maxprobe:
+ yield " argc = len(args)"
+ for argcounts, ordered_statements in cases:
+ ordered_statements.sort()
+ if len(argcounts) == maxprobe:
+ # all counts, no condition
+ indent = 1
+ else:
+ indent = 2
+ dense = tuple(range(argcounts[0], argcounts[-1]+1)) == argcounts
+ if len(argcounts) == 1:
+ yield " if argc == %d:" % argcounts
+ elif dense and argcounts[0] == 0:
+ yield " if argc <= %d:" % argcounts[-1]
+ elif dense and argcounts[-1] == maxprobe-1:
+ yield " if argc >= %d:" % argcounts[0]
+ elif dense:
+ yield " if %d <= argc <= %d:" % (argcounts[0], argcounts[-1])
+ else:
+ yield " if argc in %r:" % (argcounts, )
+ for order, line in ordered_statements:
+ yield indent * " " + line
+
+def tryGenerate__str__(exc, maxprobe=20):
+ minargs, maxargs, working = findAllArgs(exc, maxprobe)
+ # checking the default case (well, there are two)
+ simple = False
+ arg1_methods = []
+ for args in working:
+ test = str(exc(*args))
+ if len(args) == 0 and test != "":
+ break
+ if len(args) == 1:
+ if test == repr(args[0]):
+ arg1_methods.append("repr")
+ elif test == str(args[0]):
+ arg1_methods.append("str")
+ else:
+ break
+ if len(args) >= 2 and test != repr(args):
+ break
+ else:
+ simple = arg1_methods and min(arg1_methods) == max(arg1_methods)
+ if simple:
+ yield "def __str__(self):"
+ yield " argc = len(self.args)"
+ yield " if argc == 0:"
+ yield " return ''"
+ yield " elif argc == 1:"
+ yield " return %s(self.args[0])" % arg1_methods.pop()
+ yield " else:"
+ yield " return str(self.args)"
+ return
+ # no idea how I should do this
+ probe = exc(*working[0])
+ dic = probe.__dict__
+ for key in dic.keys():
+ if key.startswith("__") and key.endswith("__"):
+ del dic[key]
+ yield "def __str__(self):"
+ yield " # this is a bad hack, please supply an implementation"
+ yield " res = ' '.join(["
+ for key in dic.keys():
+ yield " '%s=' + str(self.%s)," % (key, key)
+ yield " ])"
+ yield " return res"
+
+makeExceptionsTemplate(file("d:/tmp/look.py", "w"))
More information about the Pypy-commit
mailing list