Poodletooth-iLand/dependencies/panda/direct/ffi/FFIOverload.py

473 lines
19 KiB
Python
Raw Normal View History

2015-03-03 16:10:12 -06:00
from direct.showbase.PythonUtil import *
from types import *
import string
import FFIConstants
import FFISpecs
import FFITypes
"""
Things that are not supported:
- Overloading a function based on an enum being differentiated from an int
- Type names from C++ cannot have __enum__ in their name
- Overloading static and non-static methods with the same name
"""
AT_not_atomic = 0
AT_int = 1
AT_float = 2
AT_double = 3
AT_bool = 4
AT_char = 5
AT_void = 6
AT_string = 7
AT_longlong = 8
def cullOverloadedMethods(fullMethodDict):
"""
Find all the entries that have multiple indexes for the same method name
Get rid of all others.
"""
tmpDict = {}
# For each class
for methodName in fullMethodDict.keys():
methodList = fullMethodDict[methodName]
# See if this method has more than one function index (overloaded)
if (len(methodList) > 1):
tmpDict[methodName] = methodList
# Mark all the method specifications as overloaded
for methodSpec in methodList:
methodSpec.overloaded = 1
return tmpDict
def getTypeName(classTypeDesc, typeDesc):
"""
Map the interrogate primitive type names to python type names.
We assume that the module using this has imported the types module.
It is valid to pass in None for classTypeDesc if we are not in a class
"""
typeName = typeDesc.getFullNestedName()
# Atomic C++ types are type checked against the builtin
# Python types. This code sorts out the mapping
if typeDesc.isAtomic():
# Ints, bools, and chars are treated as ints.
# Enums are special and are not atomic, see below
if ((typeDesc.atomicType == AT_int) or
(typeDesc.atomicType == AT_bool) or
(typeDesc.atomicType == AT_char)):
return 'IntType'
# Floats and doubles are both floats in Python
elif ((typeDesc.atomicType == AT_float) or
(typeDesc.atomicType == AT_double)):
return 'FloatType'
elif ((typeDesc.atomicType == AT_longlong)):
return 'LongType'
# Strings are treated as Python strings
elif ((typeDesc.atomicType == AT_string)):
return 'StringType'
elif (typeDesc.atomicType == AT_void):
# Convert the void type to None type... I guess...
# So far we do not have any code that uses this
return 'NoneType'
else:
FFIConstants.notify.error("Unknown atomicType: %s" % (typeDesc.atomicType))
# If the type is an enum, we really want to treat it like an int
# To handle this, the type will have __enum__ in the name
# Usually it will start the typeName, but some typeNames have the
# surrounding class as part of their name
# like BoundedObject.__enum__BoundingVolumeType
elif (typeName.find('__enum__') >= 0):
return 'IntType'
# If it was not atomic or enum, it must be a class which is a
# bit trickier because we output different things depending on the
# scoping of the type.
else:
# classTypeDesc typeDesc fullNestedName Resulting TypeName
# 1 Outer Other Other Other.Other
# 2 Outer Outer Outer Outer
# 3 Outer Inner Outer.Inner Outer.Inner
# 4 Inner Other Other Other.Other
# 5 Inner Outer Outer Outer
# 6 Inner Inner Outer.Inner Outer.Inner
# 7 None Other Other Other.Other
# CASES 1, 4, and 7 are the only ones that are different from the full
# nested name, returning Other.Other
returnNestedTypeNames = string.split(typeName, '.')
returnModuleName = returnNestedTypeNames[0]
if classTypeDesc:
classTypeName = classTypeDesc.getFullNestedName()
classNestedTypeNames = string.split(classTypeName, '.')
# If there is no nesting, return typeName.typeName
if ((not (classTypeDesc.foreignTypeName in returnNestedTypeNames)) and
(not (typeDesc.foreignTypeName in classNestedTypeNames))):
return (returnModuleName + '.' + typeName)
# All other cases, we just need typeName
else:
return typeName
else:
# If you had no class, you need to specify module plus typename
return (returnModuleName + '.' + typeName)
def inheritsFrom(type1, type2):
"""
Return true if type1 inherits from type2
This works by recursively checking parentTypes for type1
"""
if type1.parentTypes:
if type2 in type1.parentTypes:
return 1
else:
result = 0
for type in type1.parentTypes:
result = (result or inheritsFrom(type, type2))
return result
else:
return 0
def getInheritanceLevel(type, checkNested = 1):
if type.__class__ == FFITypes.PyObjectTypeDescriptor:
# A special case: PyObject * is always the most general
# object. Everything is a PyObject.
return -1
# If this is a nested type, return the inheritance level of the outer type.
if type.isNested:
# Check the level of your outer class
# pass the checkNested flag as 0 to prevent an infinite loop
# between the parent and child
level = getInheritanceLevel(type.outerType, 0)
else:
level = 0
for parentType in type.parentTypes:
# Add 1 because you are one level higher than your parent
level = max(level, 1+getInheritanceLevel(parentType))
if checkNested:
for nestedType in type.nestedTypes:
# Do not add 1 to your nested types
level = max(level, getInheritanceLevel(nestedType))
return level
def inheritanceLevelSort(type1, type2):
level1 = getInheritanceLevel(type1)
level2 = getInheritanceLevel(type2)
if (level1 == level2):
# If they are equal in the inheritance,
# sort them alphabetically by their type name
return cmp(type1.foreignTypeName, type2.foreignTypeName)
elif (level1 < level2):
return -1
elif (level1 > level2):
return 1
def subclass(type1, type2):
"""
Helper funcion used in sorting classes by inheritance
"""
# If the types are the same, return 0
if type1 == type2:
return 0
# If you have no args, sort you first
elif (type1 == 0):
return 1
elif (type2 == 0):
return -1
# If class1 inherits from class2 return -1
elif inheritsFrom(type1, type2):
return -1
# If class2 inherits from class1 return 1
elif inheritsFrom(type2, type1):
return 1
else:
# This is the don't care case. We must specify a sorting
# rule just so it is not arbitrary
if (type1.foreignTypeName > type2.foreignTypeName):
return -1
else:
return 1
class FFIMethodArgumentTreeCollection:
def __init__(self, classTypeDesc, methodSpecList):
self.classTypeDesc = classTypeDesc
self.methodSpecList = methodSpecList
self.methodDict = {}
self.treeDict = {}
def outputOverloadedMethodHeader(self, file, nesting):
# If one is static, we assume they all are.
# The current system does not support overloading static and non-static
# methods with the same name
# Constructors are not treated as static. They are special because
# they are not really constructors, they are instance methods that fill
# in the this pointer.
# Global functions do not need static versions
if (self.methodSpecList[0].isStatic() and
(not self.methodSpecList[0].isConstructor())):
indent(file, nesting, 'def ' +
self.methodSpecList[0].name + '(*_args):\n')
else:
indent(file, nesting, 'def ' +
self.methodSpecList[0].name + '(self, *_args):\n')
self.methodSpecList[0].outputCFunctionComment(file, nesting+2)
indent(file, nesting+2, 'numArgs = len(_args)\n')
def outputOverloadedMethodFooter(self, file, nesting):
# If this is a static method, we need to output a static version
# If one is static, we assume they all are.
# The current system does not support overloading static and non-static
# methods with the same name
# Constructors are not treated as static. They are special because
# they are not really constructors, they are instance methods that fill
# in the this pointer.
methodName = self.methodSpecList[0].name
if (self.methodSpecList[0].isStatic() and
(not self.methodSpecList[0].isConstructor()) and
(not isinstance(self.methodSpecList[0], FFISpecs.GlobalFunctionSpecification))):
self.outputOverloadedStaticFooter(file, nesting)
else:
if self.classTypeDesc:
indent(file, nesting, "FFIExternalObject.funcToMethod("+methodName+','+ self.classTypeDesc.foreignTypeName+ ",'"+methodName+"')\n")
indent(file, nesting, 'del '+methodName+'\n')
indent(file, nesting, ' \n')
indent(file, nesting+1, '\n')
def outputOverloadedStaticFooter(self, file, nesting):
# foo = staticmethod(foo)
methodName = self.methodSpecList[0].name
indent(file, nesting, self.classTypeDesc.foreignTypeName + '.' + methodName + ' = staticmethod(' + methodName + ')\n')
indent(file, nesting,'del ' +methodName+' \n\n')
def setup(self):
for method in self.methodSpecList:
numArgs = len(method.typeDescriptor.thislessArgTypes())
numArgsList = self.methodDict.setdefault(numArgs, [])
numArgsList.append(method)
for numArgs in self.methodDict.keys():
methodList = self.methodDict[numArgs]
tree = FFIMethodArgumentTree(self.classTypeDesc, methodList)
treeList = self.treeDict.setdefault(numArgs, [])
treeList.append(tree)
def generateCode(self, file, nesting):
self.setup()
self.outputOverloadedMethodHeader(file, nesting)
numArgsKeys = self.treeDict.keys()
numArgsKeys.sort()
for i in range(len(numArgsKeys)):
numArgs = numArgsKeys[i]
trees = self.treeDict[numArgs]
for tree in trees:
# If this is the first case, output an if clause
if (i == 0):
indent(file, nesting+2, 'if (numArgs == ' + repr(numArgs) + '):\n')
# If this is a subsequent first case, output an elif clause
else:
indent(file, nesting+2, 'elif (numArgs == ' + repr(numArgs) + '):\n')
tree.setup()
tree.traverse(file, nesting+1, 0)
# If the overloaded function got all the way through the if statements
# it must have had the wrong number or type of arguments
indent(file, nesting+2, "else:\n")
indent(file, nesting+3, "raise TypeError, 'Invalid number of arguments: ' + repr(numArgs) + ', expected one of: ")
for numArgs in numArgsKeys:
indent(file, 0, (repr(numArgs) + ' '))
indent(file, 0, "'\n")
self.outputOverloadedMethodFooter(file, nesting)
class FFIMethodArgumentTree:
"""
Tree is made from nested dictionaries.
The keys are methodNamed.
The values are [tree, methodSpec]
methodSpec may be None at any level
If tree is None, it is a leaf node and methodSpec will be defined
"""
def __init__(self, classTypeDesc, methodSpecList):
self.argSpec = None
self.classTypeDesc = classTypeDesc
self.methodSpecList = methodSpecList
# The actual tree is implemented as nested dictionaries
self.tree = {}
def setup(self):
for methodSpec in self.methodSpecList:
argTypes = methodSpec.typeDescriptor.thislessArgTypes()
self.fillInArgTypes(argTypes, methodSpec)
def fillInArgTypes(self, argTypes, methodSpec):
# If the method takes no arguments, we will assign a type index of 0
if (len(argTypes) == 0):
self.tree[0] = [
FFIMethodArgumentTree(self.classTypeDesc,
self.methodSpecList),
methodSpec]
else:
self.argSpec = argTypes[0]
typeDesc = self.argSpec.typeDescriptor.recursiveTypeDescriptor()
if (len(argTypes) == 1):
# If this is the last parameter, we are a leaf node, so store the
# methodSpec in this dictionary
self.tree[typeDesc] = [None, methodSpec]
else:
if typeDesc in self.tree:
# If there already is a tree here, jump into and pass the
# cdr of the arg list
subTree = self.tree[typeDesc][0]
subTree.fillInArgTypes(argTypes[1:], methodSpec)
else:
# Add a subtree for the rest of the arg list
subTree = FFIMethodArgumentTree(self.classTypeDesc,
self.methodSpecList)
subTree.fillInArgTypes(argTypes[1:], methodSpec)
# This subtree has no method spec
self.tree[typeDesc] = [subTree, None]
def traverse(self, file, nesting, level):
oneTreeHasArgs = 0
typeNameList = []
# First see if this tree branches at all. If it does not there are
# drastic optimizations we can take because we can simply call the
# bottom-most function. We are not checking the types of all the
# arguments for the sake of type checking, we are simply trying to
# figure out which overloaded function to call. If there is only
# one overloaded function with this number of arguements at this
# level, it must be the one. No need to continue checking all the
# arguments.
branches = 0
subTree = self
prevTree = subTree
levelCopy = level
while subTree:
if (len(subTree.tree.keys()) == 0):
# Dead end branch
break
if (len(subTree.tree.keys()) > 1):
# Ok, we branch, it was worth a try though
branches = 1
break
prevTree = subTree
# Must only have one subtree, traverse it
subTree = subTree.tree.values()[0][0]
levelCopy += 1
# If there were no branches, this is easy
# Just output the function and return
# Note this operates on prevTree because subTree went one too far
if not branches:
methodSpec = prevTree.tree.values()[0][1]
indent(file, nesting+2, 'return ')
methodSpec.outputOverloadedCall(file, prevTree.classTypeDesc, levelCopy)
return
# Ok, We must have a branch down here somewhere
# Make a copy of the keys so we can sort them in place
sortedKeys = self.tree.keys()
# Sort the keys based on inheritance hierarchy, most specific classes first
sortedKeys.sort(subclass)
for i in range(len(sortedKeys)):
typeDesc = sortedKeys[i]
# See if this takes no arguments
if (typeDesc == 0):
# Output the function
methodSpec = self.tree[0][1]
indent(file, nesting+2, 'return ')
methodSpec.outputOverloadedCall(file, self.classTypeDesc, 0)
else:
# This is handled at the top of the file now (?)
# Import a file if we need to for this typeDesc
# if ((typeDesc != 0) and
# (not typeDesc.isNested) and
# # Do not put our own module in the import list
# (self.classTypeDesc != typeDesc) and
# # If this is a class (not a primitive), put it on the list
# (typeDesc.__class__ == FFITypes.ClassTypeDescriptor)):
# indent(file, nesting+2, 'import ' + typeDesc.foreignTypeName + '\n')
# Specify that at least one of these trees had arguments
# so we know to output an else clause
oneTreeHasArgs = 1
typeName = getTypeName(self.classTypeDesc, typeDesc)
typeNameList.append(typeName)
if typeDesc.__class__ == FFITypes.PyObjectTypeDescriptor:
# A special case: if one of the parameters is
# PyObject *, that means anything is accepted.
condition = '1'
else:
# Otherwise, we'll check the particular type of
# the object.
condition = '(isinstance(_args[' + repr(level) + '], ' + typeName + '))'
# Legal types for a float parameter include int and long.
if (typeName == 'FloatType'):
condition += (' or (isinstance(_args[' + repr(level) + '], IntType))')
condition += (' or (isinstance(_args[' + repr(level) + '], LongType))')
# Legal types for a long parameter include int.
elif (typeName == 'LongType'):
condition += (' or (isinstance(_args[' + repr(level) + '], IntType))')
# Legal types for an int parameter include long.
elif (typeName == 'IntType'):
condition += (' or (isinstance(_args[' + repr(level) + '], LongType))')
indent(file, nesting+2, 'if ' + condition + ':\n')
if (self.tree[typeDesc][0] is not None):
self.tree[typeDesc][0].traverse(file, nesting+1, level+1)
else:
methodSpec = self.tree[typeDesc][1]
indent(file, nesting+3, 'return ')
numArgs = level+1
methodSpec.outputOverloadedCall(file, self.classTypeDesc, numArgs)
# Output an else clause if one of the trees had arguments
if oneTreeHasArgs:
indent(file, nesting+2, "raise TypeError, 'Invalid argument " + repr(level) + ", expected one of: ")
for name in typeNameList:
indent(file, 0, ('<' + name + '> '))
indent(file, 0, "'\n")
def isSinglePath(self):
if (len(self.tree.keys()) > 1):
# More than one child, return false
return 0
else:
# Only have one child, see if he only has one child
key = self.tree.keys()[0]
tree = self.tree[key][0]
if tree:
return tree.isSinglePath()
else:
return self.tree[key][1]