import FFIConstants import FFITypes import FFIOverload import string from direct.showbase.PythonUtil import * augmentedAssignments = ['__iadd__', '__isub__', '__imul__', '__idiv__', '__ior__', '__iand__', '__ixor__', '__ilshift__', '__irshift__'] class FunctionSpecification: def __init__(self): self.name = '' self.typeDescriptor = None self.index = 0 self.overloaded = 0 # Is this function a constructor self.constructor = 0 def isConstructor(self): return self.constructor def isStatic(self): for arg in self.typeDescriptor.argumentTypes: if arg.isThis: return 0 # No args were this pointers, must be static return 1 def outputTypeChecking(self, methodClass, args, file, nesting): """ Output an assert statement to check the type of each arg in this method This can be turned off with a command line parameter in generatePythonCode It is valid to pass in None for methodClass if you are not in any methodClass """ if FFIConstants.wantTypeChecking: for i in range(len(args)): methodArgSpec = args[i] typeDesc = methodArgSpec.typeDescriptor.recursiveTypeDescriptor() typeName = FFIOverload.getTypeName(methodClass, typeDesc) # We only do type checking on class types. C++ can do # type checking on the primitive types, and will do a # better job anyway. if typeDesc.__class__ == FFITypes.ClassTypeDescriptor: # Get the real return type (not derived) if ((not typeDesc.isNested) and # Do not put our own module in the import list (methodClass != typeDesc)): indent(file, nesting, 'import ' + typeDesc.foreignTypeName + '\n') indent(file, nesting, 'if not isinstance(' + methodArgSpec.name + ', ' + typeName + '):\n') indent(file, nesting + 1, 'raise TypeError, "Invalid argument %s, expected <%s>"\n' % (i, typeDesc.foreignTypeName)) def outputCFunctionComment(self, file, nesting): """ Output a docstring to the file describing the C++ call with type info Also output the C++ comment from interrogate. """ if FFIConstants.wantComments: indent(file, nesting, '"""\n') # Output the function prototype if self.typeDescriptor.prototype: indent(file, nesting, self.typeDescriptor.prototype + '\n') # Output the function comment if self.typeDescriptor.comment: # To insert tabs into the comment, replace all newlines with a newline+tabs comment = string.replace(self.typeDescriptor.comment, '\n', ('\n' + (' ' * nesting))) indent(file, nesting, comment) indent(file, 0, '\n') indent(file, nesting, '"""\n') def getFinalName(self): """ Return the name of the function given that it might be overloaded If it is overloaded, prepend "overloaded", then append the types of each argument to make it unique. So "getChild(int)" becomes "overloaded_getChild_int(int)" """ if self.overloaded: name = 'private__overloaded_' + self.name for methodArgSpec in self.typeDescriptor.argumentTypes: name = name + '_' + methodArgSpec.typeDescriptor.foreignTypeName return name else: return self.name def outputOverloadedCall(self, file, classTypeDesc, numArgs): """ Write the function call to call this overloaded method For example: self.overloaded_setPos_ptrNodePath_float_float_float(*_args) If it is a class (static) method, call the class method Class.overloaded_setPos_ptrNodePath_float_float_float(*_args) 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. These do not get indented because they are not the beginning of the line If classTypeDesc is None, then this is a global function and should output code as such """ if classTypeDesc: if (self.isStatic() and not self.isConstructor()): if numArgs: indent(file, 0, classTypeDesc.foreignTypeName + '.' + self.getFinalName() + '(*_args)\n') else: indent(file, 0, classTypeDesc.foreignTypeName + '.' + self.getFinalName() + '()\n') else: if numArgs: indent(file, 0, 'self.' + self.getFinalName() + '(*_args)\n') else: indent(file, 0, 'self.' + self.getFinalName() + '()\n') else: if numArgs: indent(file, 0, self.getFinalName() + '(*_args)\n') else: indent(file, 0, self.getFinalName() + '()\n') class GlobalFunctionSpecification(FunctionSpecification): def __init__(self): FunctionSpecification.__init__(self) # Use generateCode when creating a global (non-class) function def generateGlobalCode(self, file): self.outputHeader(file) self.outputBody(file) self.outputFooter(file) # Use generateCode when creating a global (non-class) function def generateGlobalDowncastCode(self, file): self.outputHeader(file) self.outputBody(file, 0, 0) # no downcast, no type checking self.outputFooter(file) # Use generateMethodCode when creating a global->class function def generateMethodCode(self, methodClass, file, nesting): self.outputMethodHeader(methodClass, file, nesting) self.outputMethodBody(methodClass, file, nesting) self.outputMethodFooter(methodClass, file, nesting) ################################################## ## Global Function Code Generation ################################################## def outputHeader(self, file): argTypes = self.typeDescriptor.argumentTypes indent(file, 0, 'def ' + self.getFinalName() + '(') for i in range(len(argTypes)): file.write(argTypes[i].name) if (i < (len(argTypes)-1)): file.write(', ') file.write('):\n') def outputBody(self, file, needsDowncast=1, typeChecking=1): # The method body will look something like # returnValue = PandaGlobal.method(arg) # returnObject = NodePath() # returnObject.this = returnValue # returnObject.userManagesMemory = 1 (optional) # return returnObject self.outputCFunctionComment(file, 1) argTypes = self.typeDescriptor.argumentTypes # The global downcast functions do not need type checking, and # in fact have a problem where they assert you are downcasting # from the immediate superclass when in fact you may be downcasting # from a class way up the chain as long as their is single # inheritance up to it if typeChecking: self.outputTypeChecking(None, argTypes, file, 1) indent(file, 1, 'returnValue = ' + self.typeDescriptor.moduleName + '.' + self.typeDescriptor.wrapperName + '(') for i in range(len(argTypes)): file.write(argTypes[i].passName()) if (i < (len(argTypes)-1)): file.write(', ') file.write(')\n') #indent(file, 1, 'if returnValue is None:\n') #indent(file, 2, 'return None\n') returnType = self.typeDescriptor.returnType.recursiveTypeDescriptor() returnType.generateReturnValueWrapper(None, file, self.typeDescriptor.userManagesMemory, needsDowncast, 1) def outputFooter(self, file): indent(file, 0, '\n') ################################################## ## Class Method Code Generation ################################################## def outputMethodHeader(self, methodClass, file, nesting): argTypes = self.typeDescriptor.argumentTypes indent(file, nesting, 'def ' + self.getFinalName() + '(') for i in range(len(argTypes)): # Instead of the first argument, put self if (i == 0): file.write('self') else: file.write(argTypes[i].name) if (i < (len(argTypes)-1)): file.write(', ') file.write('):\n') def outputMethodBody(self, methodClass, file, nesting): # The method body will look something like # returnValue = PandaGlobal.method(self.this, arg) # returnValue.userManagesMemory = 1 (optional) # return returnValue self.outputCFunctionComment(file, nesting+2) argTypes = self.typeDescriptor.argumentTypes self.outputTypeChecking(methodClass, argTypes[1:], file, nesting+2) indent(file, nesting+2, 'returnValue = ' + self.typeDescriptor.moduleName + '.' + self.typeDescriptor.wrapperName + '(') for i in range(len(argTypes)): # Instead of the first argument, put self.this if (i == 0): file.write('self.this') else: file.write(argTypes[i].passName()) if (i < (len(argTypes)-1)): file.write(', ') file.write(')\n') indent(file, 1, 'if returnValue is None:\n') indent(file, 2, 'return None\n') returnType = self.typeDescriptor.returnType.recursiveTypeDescriptor() returnType.generateReturnValueWrapper(methodClass, file, self.typeDescriptor.userManagesMemory, 1, nesting+2) def outputMethodFooter(self, methodClass, file, nesting): indent(file, nesting+1, '\n') class MethodSpecification(FunctionSpecification): def __init__(self): FunctionSpecification.__init__(self) def generateConstructorCode(self, methodClass, file, nesting): self.outputConstructorHeader(methodClass, file, nesting) self.outputConstructorBody(methodClass, file, nesting) self.outputConstructorFooter(methodClass, file, nesting) def generateDestructorCode(self, methodClass, file, nesting): self.outputDestructorHeader(methodClass, file, nesting) self.outputDestructorBody(methodClass, file, nesting) self.outputDestructorFooter(methodClass, file, nesting) def generateMethodCode(self, methodClass, file, nesting): self.outputMethodHeader(methodClass, file, nesting) self.outputMethodBody(methodClass, file, nesting) self.outputMethodFooter(methodClass, file, nesting) def generateStaticCode(self, methodClass, file, nesting): self.outputStaticHeader(methodClass, file, nesting) self.outputStaticBody(methodClass, file, nesting) self.outputStaticFooter(methodClass, file, nesting) def generateInheritedMethodCode(self, methodClass, parentList, file, nesting, needsDowncast): self.outputInheritedMethodHeader(methodClass, parentList, file, nesting, needsDowncast) self.outputInheritedMethodBody(methodClass, parentList, file, nesting, needsDowncast) self.outputInheritedMethodFooter(methodClass, parentList, file, nesting, needsDowncast) def generateUpcastMethodCode(self, methodClass, file, nesting): # The upcast method code is just like regular code, but the # return value wrapper does not have downcasting instructions self.outputMethodHeader(methodClass, file, nesting) self.outputMethodBody(methodClass, file, nesting, 0) # no downcast self.outputMethodFooter(methodClass, file, nesting) ################################################## ## Constructor Code Generation ################################################## def outputConstructorHeader(self, methodClass, file, nesting): argTypes = self.typeDescriptor.argumentTypes thislessArgTypes = self.typeDescriptor.thislessArgTypes() indent(file, nesting+1, 'def ' + self.getFinalName() + '(self') if (len(thislessArgTypes) > 0): file.write(', ') for i in range(len(thislessArgTypes)): file.write(thislessArgTypes[i].name) if (i < (len(thislessArgTypes)-1)): file.write(', ') file.write('):\n') self.outputCFunctionComment(file, nesting+2) def outputConstructorBody(self, methodClass, file, nesting): # The method body will look something like # self.this = panda.Class_constructor(arg) # self.userManagesMemory = 1 (optional) argTypes = self.typeDescriptor.argumentTypes thislessArgTypes = self.typeDescriptor.thislessArgTypes() self.outputTypeChecking(methodClass, thislessArgTypes, file, nesting+2) indent(file, nesting+2, 'self.this = ' + self.typeDescriptor.moduleName + '.' + self.typeDescriptor.wrapperName + '(') # Do not pass self into the constructor for i in range(len(thislessArgTypes)): file.write(thislessArgTypes[i].passName()) if (i < (len(thislessArgTypes)-1)): file.write(', ') file.write(')\n') indent(file, nesting+2, 'assert self.this != 0\n') if self.typeDescriptor.userManagesMemory: indent(file, nesting+2, 'self.userManagesMemory = 1\n') def outputConstructorFooter(self, methodClass, file, nesting): indent(file, nesting+1, '\n') ################################################## ## Destructor Code Generation ################################################## def outputDestructorHeader(self, methodClass, file, nesting): argTypes = self.typeDescriptor.argumentTypes thislessArgTypes = self.typeDescriptor.thislessArgTypes() indent(file, nesting+1, 'def ' + self.getFinalName() + '(self') if (len(thislessArgTypes) > 0): file.write(', ') for i in range(len(thislessArgTypes)): file.write(thislessArgTypes[i].name) if (i < (len(thislessArgTypes)-1)): file.write(', ') file.write('):\n') self.outputCFunctionComment(file, nesting+2) def outputDestructorBody(self, methodClass, file, nesting): # The method body will look something like # panda.Class_destructor(self.this) functionName = (self.typeDescriptor.moduleName + '.' + self.typeDescriptor.wrapperName) # Make sure the module and function have not been deleted first # This only happens during shutdown indent(file, nesting+2, 'if (' + self.typeDescriptor.moduleName + ' and ' + functionName + '):\n') indent(file, nesting+3, functionName + '(self.this)\n') def outputDestructorFooter(self, methodClass, file, nesting): indent(file, nesting+1, '\n') ################################################## ## Method Code Generation ################################################## def outputMethodHeader(self, methodClass, file, nesting): argTypes = self.typeDescriptor.argumentTypes thislessArgTypes = self.typeDescriptor.thislessArgTypes() indent(file, nesting, 'def ' + self.getFinalName() + '(self') if (len(thislessArgTypes) > 0): file.write(', ') for i in range(len(thislessArgTypes)): file.write(thislessArgTypes[i].name) if (i < (len(thislessArgTypes)-1)): file.write(', ') file.write('):\n') def outputMethodBody(self, methodClass, file, nesting, needsDowncast=1): # The method body will look something like # returnValue = panda.Class_method(self.this, arg) # returnValue.userManagesMemory = 1 (optional) # return returnValue self.outputCFunctionComment(file, nesting+2) argTypes = self.typeDescriptor.argumentTypes thislessArgTypes = self.typeDescriptor.thislessArgTypes() self.outputTypeChecking(methodClass, thislessArgTypes, file, nesting+2) indent(file, nesting+2, 'returnValue = ' + self.typeDescriptor.moduleName + '.' + self.typeDescriptor.wrapperName + '(') file.write('self.this') if (len(thislessArgTypes) > 0): file.write(', ') for i in range(len(thislessArgTypes)): file.write(thislessArgTypes[i].passName()) if (i < (len(thislessArgTypes)-1)): file.write(', ') file.write(')\n') # If this is an augmented assignment operator like +=, we have special rules # In this case we simply call the C++ function, make sure we got the same # return value back, then return self. Otherwise if you let it go through the # normal system, it actually deletes the old Python object causing the C++ memory # to be deleted then returns a new Python shadow object with the old C++ pointer... BAD! if self.getFinalName() in augmentedAssignments: indent(file, nesting+2, 'assert self.this == returnValue\n') indent(file, nesting+2, 'return self\n') else: returnType = self.typeDescriptor.returnType.recursiveTypeDescriptor() returnType.generateReturnValueWrapper(methodClass, file, self.typeDescriptor.userManagesMemory, needsDowncast, nesting+2) def outputMethodFooter(self, methodClass, file, nesting): indent(file, nesting, 'FFIExternalObject.funcToMethod(' +self.getFinalName()+ ',' + methodClass.foreignTypeName + ",'" +self.getFinalName() +"')\n") indent(file, nesting, 'del ' + self.getFinalName()+' \n') indent(file, nesting+1,'\n') #indent(file, nesting, methodClass.foreignTypeName +'.'+ self.getFinalName() + ' = staticmethod(' + self.getFinalName() + ')\n') #indent(file, nesting,'del ' + self.getFinalName()+' \n') #indent(file, nesting+1, '\n') indent(file, nesting+1, '\n') ################################################## ## Static Method Code Generation ################################################## def outputStaticHeader(self, methodClass, file, nesting): argTypes = self.typeDescriptor.argumentTypes indent(file, nesting, 'def ' + self.getFinalName() + '(') for i in range(len(argTypes)): file.write(argTypes[i].name) if (i < (len(argTypes)-1)): file.write(', ') file.write('):\n') def outputStaticBody(self, methodClass, file, nesting): # The method body will look something like # returnValue = panda.class_method(self.this, arg) # returnValue.userManagesMemory = 1 (optional) # return returnValue self.outputCFunctionComment(file, nesting+2) argTypes = self.typeDescriptor.argumentTypes thislessArgTypes = self.typeDescriptor.thislessArgTypes() self.outputTypeChecking(methodClass, thislessArgTypes, file, nesting+2) indent(file, nesting+2, 'returnValue = ' + self.typeDescriptor.moduleName + '.' + self.typeDescriptor.wrapperName + '(') # Static methods do not take the this parameter if (len(thislessArgTypes) > 0): for i in range(len(thislessArgTypes)): file.write(thislessArgTypes[i].passName()) if (i < (len(thislessArgTypes)-1)): file.write(', ') file.write(')\n') returnType = self.typeDescriptor.returnType.recursiveTypeDescriptor() returnType.generateReturnValueWrapper(methodClass, file, self.typeDescriptor.userManagesMemory, 1, nesting+2) def outputStaticFooter(self, methodClass, file, nesting): indent(file, nesting, methodClass.foreignTypeName +'.'+ self.getFinalName() + ' = staticmethod(' + self.getFinalName() + ')\n') indent(file, nesting,'del ' + self.getFinalName()+' \n') indent(file, nesting+1, '\n') ################################################## ## Upcast Method Code Generation ################################################## def outputInheritedMethodHeader(self, methodClass, parentList, file, nesting, needsDowncast): argTypes = self.typeDescriptor.argumentTypes thislessArgTypes = self.typeDescriptor.thislessArgTypes() indent(file, nesting, 'def ' + self.getFinalName() + '(self') if (len(thislessArgTypes) > 0): file.write(', ') for i in range(len(thislessArgTypes)): file.write(thislessArgTypes[i].name) if (i < (len(thislessArgTypes)-1)): file.write(', ') file.write('):\n') def outputInheritedMethodBody(self, methodClass, parentList, file, nesting, needsDowncast): # The method body will look something like # upcastSelf = self.upcastToParentClass() # returnValue = libpanda.method(upcastSelf.this, arg) # returnValue.userManagesMemory = 1 (optional) # return returnValue self.outputCFunctionComment(file, nesting+2) argTypes = self.typeDescriptor.argumentTypes thislessArgTypes = self.typeDescriptor.thislessArgTypes() self.outputTypeChecking(methodClass, thislessArgTypes, file, nesting+2) indent(file, nesting+2, 'upcastSelf = self\n') for i in range(len(parentList)): # Only output the upcast call if that parent class defines it parentClass = parentList[i] methodName = 'upcastTo' + parentClass.foreignTypeName if (i != 0): childClass = parentList[i-1] if childClass.hasMethodNamed(methodName): indent(file, nesting+2, 'upcastSelf = upcastSelf.' + methodName + '()\n') else: indent(file, nesting+2, '# upcastSelf = upcastSelf.' + methodName + '()\n') else: if methodClass.hasMethodNamed(methodName): indent(file, nesting+2, 'upcastSelf = upcastSelf.' + methodName + '()\n') else: indent(file, nesting+2, '# upcastSelf = upcastSelf.' + methodName + '()\n') indent(file, nesting+2, 'returnValue = ' + self.typeDescriptor.moduleName + '.' + self.typeDescriptor.wrapperName + '(upcastSelf.this') if (len(thislessArgTypes) > 0): file.write(', ') for i in range(len(thislessArgTypes)): file.write(thislessArgTypes[i].passName()) if (i < (len(thislessArgTypes)-1)): file.write(', ') file.write(')\n') returnType = self.typeDescriptor.returnType.recursiveTypeDescriptor() # Generate the return value code with no downcast instructions returnType.generateReturnValueWrapper(methodClass, file, self.typeDescriptor.userManagesMemory, needsDowncast, nesting+2) def outputInheritedMethodFooter(self, methodClass, parentList, file, nesting, needsDowncast): indent(file, nesting, 'FFIExternalObject.funcToMethod(' +self.getFinalName()+ ',' + methodClass.foreignTypeName + ",'" +self.getFinalName() +"')\n") indent(file, nesting, 'del ' + self.getFinalName()+' \n') indent(file, nesting+1,'\n') class GlobalValueSpecification: def __init__(self): self.name = '' # We really do not need the descriptor for the value, just # the getter and setter # self.typeDescriptor = None # To be filled in with a GlobalFunctionSpecification self.getter = None # To be filled in with a GlobalFunctionSpecification self.setter = None def generateGlobalCode(self, file): indent(file, 0, '# Global value: ' + self.name + '\n') if self.getter: self.getter.generateGlobalCode(file) if self.setter: self.setter.generateGlobalCode(file) indent(file, 0, '\n') # Manifest symbols class ManifestSpecification: def __init__(self): self.name = '' # We are not currently using the type descriptor self.typeDescriptor = None # To be filled in with a GlobalFunctionSpecification # if this manifest has one self.getter = None # Manifests that have int values have their int value defined # instead of having to call a getter (because there are so many of them) self.intValue = None # The string definition of this manifest self.definition = None def generateGlobalCode(self, file): # Note, if the manifest has no value and no getter we do not output anything # even though they may be defined in the C++ sense. Without any values # they are pretty useless in Python # If it has an int value, just output that instead of bothering # with a getter if (self.intValue != None): indent(file, 0, '# Manifest: ' + self.name + '\n') indent(file, 0, (self.name + ' = ' + repr(self.intValue) + '\n')) indent(file, 0, '\n') elif self.definition: indent(file, 0, ('# Manifest: ' + self.name + ' definition: ' + self.definition + '\n')) # Out put the getter if self.getter: self.getter.generateGlobalCode(file) indent(file, 0, '\n') class MethodArgumentSpecification: def __init__(self): self.name = '' self.typeDescriptor = None # By default it is not the this pointer self.isThis = 0 def passName(self): if (self.typeDescriptor.recursiveTypeDescriptor().__class__ == \ FFITypes.ClassTypeDescriptor): return self.name + '.this' else: return self.name