Files
Phoenix/etgtools/sip_generator.py
Robin Dunn 2cce365c83 "c++" --> "C++"
git-svn-id: https://svn.wxwidgets.org/svn/wx/sandbox/trunk/Phoenix@66134 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
2010-11-13 08:07:02 +00:00

485 lines
18 KiB
Python

#---------------------------------------------------------------------------
# Name: etgtools/sip_generator.py
# Author: Robin Dunn
#
# Created: 3-Nov-2010
# Copyright: (c) 2010 by Total Control Software
# Licence: wxWindows license
#---------------------------------------------------------------------------
"""
The generator class for creating SIP definition files from the data
objects produced by the ETG scripts.
"""
import sys, os
import extractors
import generators
from cStringIO import StringIO
divider = '//' + '-'*75 + '\n'
phoenixRoot = os.path.abspath(os.path.split(__file__)[0]+'/..')
#---------------------------------------------------------------------------
class SipWrapperGenerator(generators.WrapperGeneratorBase):
def generate(self, module, destFile=None):
stream = StringIO()
# generate SIP code from the module and its objects
self.generateModule(module, stream)
# Write the contents of the stream to the destination file
if not destFile:
destFile = os.path.join(phoenixRoot, 'sip/gen', module.name + '.sip')
file(destFile, 'wt').write(stream.getvalue())
#-----------------------------------------------------------------------
def generateModule(self, module, stream):
assert isinstance(module, extractors.ModuleDef)
# write the file header
stream.write(divider + """\
// This file is generated by wxPython's SIP generator. Do not edit by hand.
//
// Copyright: (c) 2010 by Total Control Software
// Licence: wxWindows license
""")
if module.name == module.module:
stream.write("""
%%Module(name=%s.%s, use_argument_names=True, language="C++")
%%Copying
Copyright: (c) 2010 by Total Control Software
Licence: wxWindows license
%%End
""" % (module.package, module.name))
if module.name.startswith('_'):
doc = ''
if module.docstring:
doc = '\n"""\n%s\n"""\n' % module.docstring
stream.write("""\
%%Extract pycode
# This file is generated by wxPython's SIP generator. Do not edit by hand.
#
# Copyright: (c) 2010 by Total Control Software
# Licence: wxWindows license
%s
from %s import *
%%End
""" % (doc, module.name))
else:
stream.write("//\n// This file will be included by %s.sip\n//\n" % module.module)
stream.write(divider)
# %Imports and %Includes
for i in module.imports:
stream.write("%%Import %s.sip\n" % i)
stream.write("\n")
for i in module.includes:
stream.write("%%Include %s.sip\n" % i)
# C++ code to be written out to the generated module
if module.headerCode:
stream.write("\n%ModuleHeaderCode\n")
for c in module.headerCode:
stream.write('%s\n' % c)
stream.write("%End\n\n")
if module.cppCode:
stream.write("%ModuleCode\n")
for c in module.cppCode:
stream.write('%s\n' % c)
stream.write("%End\n")
stream.write('\n%s\n' % divider)
# Now generate each of the items in the module
self.generateModuleItems(module, stream)
# Add code for the module initialization sections.
if module.preInitializerCode:
stream.write('\n%s\n\n%%PreInitialisationCode\n' % divider)
for i in module.preInitializerCode:
stream.write('%s\n' % i)
stream.write('%End\n')
if module.initializerCode:
stream.write('\n%s\n\n%%InitialisationCode\n' % divider)
for i in module.initializerCode:
stream.write('%s\n' % i)
stream.write('%End\n')
if module.postInitializerCode:
stream.write('\n%s\n\n%%PostInitialisationCode\n' % divider)
for i in module.postInitializerCode:
stream.write('%s\n' % i)
stream.write('%End\n')
stream.write('\n%s\n' % divider)
def generateModuleItems(self, module, stream):
methodMap = {
extractors.ClassDef : self.generateClass,
extractors.FunctionDef : self.generateFunction,
extractors.EnumDef : self.generateEnum,
extractors.GlobalVarDef : self.generateGlobalVar,
extractors.TypedefDef : self.generateTypedef,
extractors.WigCode : self.generateWigCode,
extractors.PyCodeDef : self.generatePyCode,
}
for item in module:
if item.ignored:
continue
function = methodMap[item.__class__]
function(item, stream)
#-----------------------------------------------------------------------
def generateFunction(self, function, stream):
assert isinstance(function, extractors.FunctionDef)
if not function.ignored:
stream.write('%s %s(' % (function.type, function.name))
if function.items:
stream.write('\n')
self.generateParameters(function.items, stream, ' '*4)
stream.write(')%s;\n' % self.annotate(function))
for f in function.overloads:
self.generateFunction(f, stream)
stream.write('\n')
def generateParameters(self, parameters, stream, indent):
for idx, param in enumerate(parameters):
if param.ignored:
continue
stream.write(indent)
stream.write('%s %s' % (param.type, param.name))
if param.default:
stream.write(' = %s' % param.default)
stream.write(self.annotate(param))
if not idx == len(parameters)-1:
stream.write(',')
stream.write('\n')
#-----------------------------------------------------------------------
def generateEnum(self, enum, stream, indent=''):
assert isinstance(enum, extractors.EnumDef)
if enum.ignored:
return
name = enum.name
if name.startswith('@'):
name = ''
stream.write('%senum %s%s\n{\n' % (indent, name, self.annotate(enum)))
values = []
for v in enum.items:
if v.ignored:
continue
values.append("%s %s%s" % (indent, v.name, self.annotate(v)))
stream.write(',\n'.join(values))
stream.write('%s\n};\n\n' % (indent, ))
#-----------------------------------------------------------------------
def generateGlobalVar(self, globalVar, stream):
assert isinstance(globalVar, extractors.GlobalVarDef)
if globalVar.ignored:
return
stream.write('%s %s' % (globalVar.type, globalVar.name))
stream.write('%s;\n\n' % self.annotate(globalVar))
#-----------------------------------------------------------------------
def generateTypedef(self, typedef, stream):
assert isinstance(typedef, extractors.TypedefDef)
if typedef.ignored:
return
stream.write('typedef %s %s' % (typedef.type, typedef.name))
stream.write('%s;\n\n' % self.annotate(typedef))
#-----------------------------------------------------------------------
def generateWigCode(self, wig, stream, indent=''):
assert isinstance(wig, extractors.WigCode)
stream.write(nci(wig.code, len(indent), False))
stream.write('\n\n')
#-----------------------------------------------------------------------
def generatePyCode(self, pc, stream, indent=''):
assert isinstance(pc, extractors.PyCodeDef)
if hasattr(pc, 'klass') and pc.klass.generatingInClass:
pc.klass.generateAfterClass.append(pc)
else:
stream.write('%Extract pycode\n')
stream.write(nci(pc.code))
stream.write('\n%End\n\n')
#-----------------------------------------------------------------------
def generateClass(self, klass, stream, indent=''):
assert isinstance(klass, extractors.ClassDef)
if klass.ignored:
return
# write the class header
stream.write('%sclass %s' % (indent, klass.name))
if klass.bases:
stream.write(' : ')
stream.write(', '.join(klass.bases))
stream.write(self.annotate(klass))
stream.write('\n%s{\n' % indent)
if klass.includes:
stream.write('%s%%TypeHeaderCode\n' % indent)
for inc in klass.includes:
stream.write('%s #include <%s>\n' % (indent, inc))
stream.write('%s%%End\n' % indent)
stream.write('\n%spublic:\n' % indent)
# is the generator currently inside the class or after it?
klass.generatingInClass = True
# Split the items into public and protected groups
ctors = [i for i in klass if
isinstance(i, extractors.MethodDef) and
i.protection == 'public' and (i.isCtor or i.isDtor)]
public = [i for i in klass if i.protection == 'public' and i not in ctors]
protected = [i for i in klass if i.protection == 'protected']
dispatch = {
extractors.MemberVarDef : self.generateMemberVar,
extractors.PropertyDef : self.generateProperty,
extractors.MethodDef : self.generateMethod,
extractors.EnumDef : self.generateEnum,
extractors.CppMethodDef : self.generateCppMethod,
extractors.PyMethodDef : self.generatePyMethod,
extractors.PyCodeDef : self.generatePyCode,
extractors.WigCode : self.generateWigCode,
# TODO: nested classes too?
}
for item in ctors:
f = dispatch[item.__class__]
f(item, stream, indent + ' '*4)
for item in public:
f = dispatch[item.__class__]
f(item, stream, indent + ' '*4)
if protected and [i for i in protected if not i.ignored]:
stream.write('\nprotected:\n')
for item in protected:
f = dispatch[item.__class__]
f(item, stream, indent + ' '*4)
if klass.convertFromPyObject:
self.generateConvertCode('%ConvertToTypeCode',
klass.convertFromPyObject,
stream, indent + ' '*4)
if klass.convertToPyObject:
self.generateConvertCode('%ConvertFromTypeCode',
klass.convertToPyObject,
stream, indent + ' '*4)
stream.write('%s}; // end of class %s\n\n\n' % (indent, klass.name))
# Now generate anything that was deferred until after the class is finished
klass.generatingInClass = False
for item in klass.generateAfterClass:
f = dispatch[item.__class__]
f(item, stream, indent)
def generateConvertCode(self, kind, code, stream, indent):
stream.write('%s%s\n' % (indent, kind))
stream.write(nci(code, len(indent)+4))
stream.write('%s%%End\n' % indent)
def generateMemberVar(self, memberVar, stream, indent):
assert isinstance(memberVar, extractors.MemberVarDef)
if memberVar.ignored:
return
stream.write('%s%s %s' % (indent, memberVar.type, memberVar.name))
stream.write('%s;\n\n' % self.annotate(memberVar))
def generateProperty(self, prop, stream, indent):
assert isinstance(prop, extractors.PropertyDef)
if prop.ignored:
return
stream.write('%s%%Property(name=%s, get=%s' % (indent, prop.name, prop.getter))
if prop.setter:
stream.write(', set=%s' % prop.setter)
stream.write(')')
if prop.briefDoc:
stream.write(' // %s' % prop.briefDoc)
stream.write('\n')
def generateMethod(self, method, stream, indent):
assert isinstance(method, extractors.MethodDef)
if not method.ignored:
if method.isVirtual:
stream.write("%svirtual\n" % indent)
if method.isStatic:
stream.write("%sstatic\n" % indent)
if method.isCtor or method.isDtor:
stream.write('%s%s(' % (indent, method.name))
else:
stream.write('%s%s %s(' % (indent, method.type, method.name))
if method.items:
stream.write('\n')
self.generateParameters(method.items, stream, indent+' '*4)
stream.write(indent)
stream.write(')%s;\n\n' % self.annotate(method))
if method.overloads:
for m in method.overloads:
self.generateMethod(m, stream, indent)
def generateCppMethod(self, method, stream, indent):
assert isinstance(method, extractors.CppMethodDef)
if method.ignored:
return
if method.isCtor:
stream.write('%s%s%s%s;\n' %
(indent, method.name, method.argsString, self.annotate(method)))
else:
stream.write('%s%s %s%s%s;\n' %
(indent, method.type, method.name, method.argsString,
self.annotate(method)))
stream.write('%s%%MethodCode\n' % indent)
stream.write(nci(method.body, len(indent)+4))
stream.write('%s%%End\n\n' % indent)
def generatePyMethod(self, pm, stream, indent):
assert isinstance(pm, extractors.PyMethodDef)
if pm.klass.generatingInClass:
pm.klass.generateAfterClass.append(pm)
else:
klassName = pm.klass.pyName or pm.klass.name
stream.write("%Extract pycode\n")
stream.write("def _%s_%s%s:\n" % (klassName, pm.name, pm.argsString))
if pm.briefDoc:
stream.write(nci('"""\n%s\n"""\n' % pm.briefDoc, 4))
stream.write(nci(pm.body, 4))
stream.write('%s.%s = _%s_%s\n' % (klassName, pm.name, klassName, pm.name))
stream.write('\n%End\n\n')
#-----------------------------------------------------------------------
def annotate(self, item):
annotations = []
if item.pyName:
annotations.append('PyName=%s' % item.pyName)
if isinstance(item, extractors.ParamDef):
if item.out:
annotations.append('Out')
if item.inOut:
annotations.extend(['In', 'Out'])
if item.array:
annotations.append('Array')
if item.arraySize:
annotations.append('ArraySize')
if isinstance(item, (extractors.ParamDef, extractors.FunctionDef)):
if item.transfer:
annotations.append('Transfer')
if item.transferBack:
annotations.append('TransferBack')
if item.transferThis:
annotations.append('TranserThis')
if isinstance(item, extractors.FunctionDef):
if item.deprecated:
annotations.append('Deprecated')
if item.factory:
annotations.append('Factory')
if item.pyReleaseGIL: # else HoldGIL??
annotations.append('ReleaseGIL')
if item.noCopy:
annotations.append('NoCopy')
if isinstance(item, extractors.MethodDef):
if item.defaultCtor:
annotations.append('Default')
if isinstance(item, extractors.ClassDef):
if item.abstract:
annotations.append('Abstract')
if item.deprecated:
annotations.append('Deprecated')
if item.external:
annotations.append('External')
if item.noDefCtor:
annotations.append('NoDefaultCtors')
if item.singlton:
annotations.append('DelayDtor')
if annotations:
return ' /%s/' % ', '.join(annotations)
else:
return ''
#---------------------------------------------------------------------------
# helpers and utilities
def nci(text, numSpaces=0, stripLeading=True):
"""
Normalize Code Indents
First use the count of leading spaces on the first line and remove that
many spaces from the front of all lines, and then indent each line by
adding numSpaces spaces. This is used so we can convert the arbitrary
indents that might be used by the treaker code into what is expected for
the context we are generating for.
"""
def _getLeadingSpaceCount(line):
count = 0
for c in line:
assert c != '\t', "Use spaces for indent, not tabs"
if c != ' ':
break
count += 1
else:
assert False, "First line should not be empty."
return count
def _allSpaces(text):
for c in text:
if c != ' ':
return False
return True
lines = text.rstrip().split('\n')
if stripLeading:
numStrip = _getLeadingSpaceCount(lines[0])
else:
numStrip = 0
for idx, line in enumerate(lines):
assert _allSpaces(line[:numStrip]), "Indentation inconsistent with first line"
lines[idx] = ' '*numSpaces + line[numStrip:]
newText = '\n'.join(lines) + '\n'
return newText
#---------------------------------------------------------------------------