split out nmutil library based on ieee754fpu code
[nmutil.git] / src / nmutil / noconflict.py
1 import inspect, types
2
3 ############## preliminary: two utility functions #####################
4
5 def skip_redundant(iterable, skipset=None):
6 "Redundant items are repeated items or items in the original skipset."
7 if skipset is None: skipset = set()
8 for item in iterable:
9 if item not in skipset:
10 skipset.add(item)
11 yield item
12
13
14 def remove_redundant(metaclasses):
15 skipset = set([type])
16 for meta in metaclasses: # determines the metaclasses to be skipped
17 skipset.update(inspect.getmro(meta)[1:])
18 return tuple(skip_redundant(metaclasses, skipset))
19
20 ##################################################################
21 ## now the core of the module: two mutually recursive functions ##
22 ##################################################################
23
24 memoized_metaclasses_map = {}
25
26 def get_noconflict_metaclass(bases, left_metas, right_metas):
27 """Not intended to be used outside of this module, unless you know
28 what you are doing."""
29 # make tuple of needed metaclasses in specified priority order
30 metas = left_metas + tuple(map(type, bases)) + right_metas
31 needed_metas = remove_redundant(metas)
32
33 # return existing confict-solving meta, if any
34 if needed_metas in memoized_metaclasses_map:
35 return memoized_metaclasses_map[needed_metas]
36 # nope: compute, memoize and return needed conflict-solving meta
37 elif not needed_metas: # wee, a trivial case, happy us
38 meta = type
39 elif len(needed_metas) == 1: # another trivial case
40 meta = needed_metas[0]
41 # check for recursion, can happen i.e. for Zope ExtensionClasses
42 elif needed_metas == bases:
43 raise TypeError("Incompatible root metatypes", needed_metas)
44 else: # gotta work ...
45 metaname = '_' + ''.join([m.__name__ for m in needed_metas])
46 meta = classmaker()(metaname, needed_metas, {})
47 memoized_metaclasses_map[needed_metas] = meta
48 return meta
49
50 def classmaker(left_metas=(), right_metas=()):
51 def make_class(name, bases, adict):
52 print ("make_class", name)
53 metaclass = get_noconflict_metaclass(bases, left_metas, right_metas)
54 return metaclass(name, bases, adict)
55 return make_class