blob: 97a5f36011c55c3746ac3ae86f8d5283cf03b82d [file] [log] [blame]
import time
import os
import os.path
from string import Template
from thrift.parser import *
from thrift.generator import *
HEADER_COMMENT = """/**
* Autogenerated by Thrift
* ${date}
*
* DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
*/
"""
CPP_TYPES_HEADER = Template(HEADER_COMMENT+"""
#if !defined(${source}_types_h_)
#define ${source}_types_h_ 1
#include <Thrift.h>
${namespacePrefix}
""")
CPP_TYPES_FOOTER = Template("""
${namespaceSuffix}
#endif // !defined(${source}_types_h_)
""")
CPP_SERVICES_HEADER = Template(HEADER_COMMENT+"""
#if !defined(${source}_h_)
#define ${source}_h_ 1
#include <Thrift.h>
#include <TProcessor.h>
#include <protocol/TProtocol.h>
#include <transport/TTransport.h>
#include \"${source}_types.h\"
${namespacePrefix}
""")
CPP_SERVICES_FOOTER = Template("""
${namespaceSuffix}
#endif // !defined(${source}_h_)""")
CPP_IMPL_HEADER = Template(HEADER_COMMENT+"""
#include \"${source}.h\"
${namespacePrefix}
""")
CPP_IMPL_FOOTER = Template("""
${namespaceSuffix}
""")
def cpp_debug(arg):
print(arg)
class Indenter(object):
def __init__(self, level=0, step=4):
self.level = level
self.step = step
self.chunk = ""
for i in range(step):
self.chunk+= " "
self.prefix=""
def inc(self):
self.level+= self.step
self.prefix += self.chunk
def dec(self):
self.level-= self.step
if(self.level < 0):
raise Exception, "Illegal indent level"
self.prefix = self.prefix[:self.level]
def __call__(self):
return self.prefix
class CFile(file):
def __init__(self, name, flags):
file.__init__(self, name, flags)
self.indent = Indenter()
self.newline = True
def rwrite(self, value):
file.write(self, value)
def write(self, value=""):
if self.newline:
self.rwrite(self.indent())
self.newline = False
self.rwrite(value)
def writeln(self, value=""):
self.write(value+"\n")
self.newline = True
def beginBlock(self):
self.writeln("{")
self.indent.inc();
def endBlock(self, suffix=""):
self.indent.dec();
self.writeln("}"+suffix)
CPP_PRIMITIVE_MAP = {
"void" : "void",
"bool" : "bool",
"string": "std::string",
"utf7": "std::string",
"utf8": "std::wstring",
"utf16": "std::utf16",
"byte" : "uint8_t",
"i08": "int8_t",
"i16": "int16_t",
"i32": "int32_t",
"i64": "int64_t",
"u08": "uint8_t",
"u16": "uint16_t",
"u32": "uint32_t",
"u64": "uint64_t",
"float": "float",
"double": "double"
}
CPP_CONTAINER_MAP = {
MapType : "std::map",
ListType: "std::list",
SetType : "std::set",
}
def toCNamespacePrefix(namespace):
if not namespace:
return ""
else:
return string.join(["namespace "+token + " {" for token in string.split(namespace, ".")], " ")
def toCNamespaceSuffix(namespace):
if not namespace:
return ""
else:
return string.join(["}" for token in string.split(namespace, ".")], "")
def toCTypeDeclaration(ttype):
""" Converts the thrift IDL type to the corresponding C/C++ type. Note that if ttype is FieldType, this function c
converts to type declaration followed by field name, i.e. \"string string_thing\""""
if isinstance(ttype, PrimitiveType):
return CPP_PRIMITIVE_MAP[ttype.name]
elif isinstance(ttype, CollectionType):
result = CPP_CONTAINER_MAP[type(ttype)]+"<"
if isinstance(ttype, MapType):
result+= toCTypeDeclaration(ttype.keyType)+", "+ toCTypeDeclaration(ttype.valueType)
elif isinstance(ttype, SetType) or isinstance(ttype, ListType):
result+= toCTypeDeclaration(ttype.valueType)
else:
raise Exception, "Unknown Collection Type "+str(ttype)
result+= "> "
return result
elif isinstance(ttype, StructType):
return "struct "+ttype.name
elif isinstance(ttype, TypedefType):
return ttype.name;
elif isinstance(ttype, EnumType):
return ttype.name;
elif isinstance(ttype, Function):
result = toCTypeDeclaration(ttype.returnType())+ " "+ttype.name+"("+string.join([toCTypeDeclaration(arg) for arg in ttype.args()], ", ")+")"
if len(ttype.exceptions()):
result+= " throw("+string.join([toCTypeDeclaration(exceptions.type) for exceptions in ttype.exceptions()], ", ")+")"
return result
elif isinstance(ttype, Field):
return toCTypeDeclaration(ttype.type)+ " "+ttype.name
else:
raise Exception, "Unknown type "+str(ttype)
def toTypeDefDefinition(typedef):
""" Converts a thrift typedef to a C/C++ typedef """
return "typedef "+toCTypeDeclaration(typedef.definitionType)+" "+typedef.name+";"
def toEnumDefinition(enum):
""" Converts a thrift enum to a C/C++ enum """
result = "enum "+enum.name+" {\n"
first = True
for ed in enum.enumDefs:
if first:
first = False
else:
result+= ",\n"
result+= " "+ed.name+" = "+str(ed.id)
result+= "\n};\n"
return result
def toStructDefinition(struct):
"""Converts a thrift struct to a C/C++ struct"""
result = "struct "+struct.name+" {\n"
# Create constructor defaults for primitive types
ctorValues = string.join([field.name+"(0)" for field in struct.fieldList if isinstance(toCanonicalType(field.type), PrimitiveType) and toCanonicalType(field.type) not in [STRING_TYPE, UTF8_TYPE, UTF16_TYPE, VOID_TYPE]], ", ")
if len(ctorValues) > 0:
result+= " "+struct.name+"() : "+ctorValues+ " {}\n"
# Field declarations
result+= string.join([" "+toCTypeDeclaration(field)+";\n" for field in struct.fieldList if not isVoidType(field.type)], "")
# is-field-set struct and ctor
ctorValues = string.join([field.name+"(false)" for field in struct.fieldList if not isVoidType(field.type)], ", ")
if len(ctorValues) > 0:
result+= " struct __isset {\n"
result+= " __isset() : "+ctorValues+" {}\n"
result+= string.join([" bool "+field.name+";\n" for field in struct.fieldList if not isVoidType(field.type)], "")
result+= " } __isset;\n"
# bring it on home
result+= "};\n"
return result
CPP_DEFINITION_MAP = {
TypedefType : toTypeDefDefinition,
EnumType : toEnumDefinition,
StructType : toStructDefinition,
ExceptionType : toStructDefinition,
Service : None
}
def toDefinitions(definitions):
"""Converts an arbitrafy thrift grammatical unit definition to the corresponding C/C++ definition"""
result = ""
for definition in definitions:
writer = CPP_DEFINITION_MAP[type(definition)]
if writer:
result+= writer(definition)+"\n"
return result
CPP_THRIFT_NS = "facebook::thrift"
CPP_INTERFACE_FUNCTION_DECLARATION = Template(""" virtual ${functionDeclaration} = 0;
""")
CPP_INTERFACE_DECLARATION = Template("""
class ${service}If {
public:
virtual ~${service}If() {}
${functionDeclarations}};
""")
def toServiceInterfaceDeclaration(service, debugp=None):
"""Converts a thrift service definition into a C++ abstract base class"""
functionDeclarations = string.join([CPP_INTERFACE_FUNCTION_DECLARATION.substitute(service=service.name, functionDeclaration=toCTypeDeclaration(function)) for function in service.functionList], "")
return CPP_INTERFACE_DECLARATION.substitute(service=service.name, functionDeclarations=functionDeclarations)
CPP_EXCEPTION = CPP_THRIFT_NS+"::Exception"
CPP_SP = Template("boost::shared_ptr<${klass}> ")
CPP_PROCESSOR = CPP_THRIFT_NS+"::TProcessor"
CPP_PROCESSORP = CPP_SP.substitute(klass=CPP_PROCESSOR)
CPP_PROTOCOL_NS = CPP_THRIFT_NS+"::protocol"
CPP_PROTOCOL = CPP_PROTOCOL_NS+"::TProtocol"
CPP_PROTOCOLP = CPP_SP.substitute(klass="const "+CPP_PROTOCOL)
CPP_TRANSPORT_NS = CPP_THRIFT_NS+"::transport"
CPP_TRANSPORT = CPP_TRANSPORT_NS+"::TTransport"
CPP_TRANSPORTP = CPP_SP.substitute(klass=CPP_TRANSPORT)
CPP_PROTOCOL_TSTOP = CPP_PROTOCOL_NS+"::T_STOP"
CPP_PROTOCOL_TTYPE = CPP_PROTOCOL_NS+"::TType"
CPP_PROTOCOL_MESSAGE_TYPE = CPP_PROTOCOL_NS+"::TMessageType"
CPP_PROTOCOL_CALL = CPP_PROTOCOL_NS+"::T_CALL"
CPP_PROTOCOL_REPLY = CPP_PROTOCOL_NS+"::T_REPLY"
CPP_TTYPE_MAP = {
STOP_TYPE : CPP_PROTOCOL_NS+"::T_STOP",
VOID_TYPE : CPP_PROTOCOL_NS+"::T_VOID",
BOOL_TYPE : CPP_PROTOCOL_NS+"::T_BOOL",
UTF7_TYPE : CPP_PROTOCOL_NS+"::T_UTF7",
UTF7_TYPE : CPP_PROTOCOL_NS+"::T_UTF7",
UTF8_TYPE : CPP_PROTOCOL_NS+"::T_UTF8",
UTF16_TYPE : CPP_PROTOCOL_NS+"::T_UTF16",
U08_TYPE : CPP_PROTOCOL_NS+"::T_U08",
I08_TYPE : CPP_PROTOCOL_NS+"::T_I08",
I16_TYPE : CPP_PROTOCOL_NS+"::T_I16",
I32_TYPE : CPP_PROTOCOL_NS+"::T_I32",
I64_TYPE : CPP_PROTOCOL_NS+"::T_I64",
U08_TYPE : CPP_PROTOCOL_NS+"::T_U08",
U16_TYPE : CPP_PROTOCOL_NS+"::T_U16",
U32_TYPE : CPP_PROTOCOL_NS+"::T_U32",
U64_TYPE : CPP_PROTOCOL_NS+"::T_U64",
FLOAT_TYPE : CPP_PROTOCOL_NS+"::T_FLOAT",
DOUBLE_TYPE : CPP_PROTOCOL_NS+"::T_DOUBLE",
StructType : CPP_PROTOCOL_NS+"::T_STRUCT",
ExceptionType : CPP_PROTOCOL_NS+"::T_STRUCT",
ListType : CPP_PROTOCOL_NS+"::T_LIST",
MapType : CPP_PROTOCOL_NS+"::T_MAP",
SetType : CPP_PROTOCOL_NS+"::T_SET"
}
CPP_SERVER_FUNCTION_DECLARATION = Template(""" void process_${function}(uint32_t seqid, """+CPP_TRANSPORTP+""" itrans, """+CPP_TRANSPORTP+""" otrans);
""")
CPP_SERVER_FUNCTION_DEFINITION = Template("""
void ${service}ServerIf::process_${function}(uint32_t seqid, """+CPP_TRANSPORTP+""" itrans, """+CPP_TRANSPORTP+""" otrans) {
uint32_t xfer = 0;
${argsStructDeclaration};
${argsStructReader};
_iprot->readMessageEnd(itrans);
${resultStructDeclaration};
${functionCall}
_oprot->writeMessageBegin(otrans, \"${function}\", """+CPP_PROTOCOL_REPLY+""", seqid);
${resultStructWriter};
_oprot->writeMessageEnd(otrans);
otrans->flush();
}
""")
CPP_SERVER_PROCESS_DEFINITION = Template("""
bool ${service}ServerIf::process("""+CPP_TRANSPORTP+""" itrans, """+CPP_TRANSPORTP+""" otrans) {
std::string name;
"""+CPP_PROTOCOL_MESSAGE_TYPE+""" messageType;
uint32_t seqid;
_iprot->readMessageBegin(itrans, name, messageType, seqid);
if(messageType == """+CPP_PROTOCOL_CALL+""") {
${callProcessSwitch}
} else {
throw """+CPP_EXCEPTION+"""(\"Unexpected message type\");
}
return true;
}
""")
def toWireType(ttype):
"""Converts a thrift type to the corresponding wire type. This differs from toCTypeDeclaration in that it reduces typedefs
to their canonical form and converts enums to signedf 32 bit integers"""
if isinstance(ttype, PrimitiveType):
return CPP_TTYPE_MAP[ttype]
elif isinstance(ttype, EnumType):
return CPP_TTYPE_MAP[I32_TYPE]
elif isinstance(ttype, TypedefType):
return toWireType(toCanonicalType(ttype))
elif isinstance(ttype, StructType) or isinstance(ttype, CollectionType):
return CPP_TTYPE_MAP[type(ttype)]
else:
raise Exception, "No wire type for thrift type: "+str(ttype)
CPP_SERVER_DECLARATION = Template("""
class ${service}ServerIf : public ${service}If, public """+CPP_PROCESSOR+""" {
public:
${service}ServerIf("""+CPP_PROTOCOLP+""" protocol): _iprot(protocol), _oprot(protocol) {}
${service}ServerIf("""+CPP_PROTOCOLP+""" iprot, """+CPP_PROTOCOLP+""" oprot) : _iprot(iprot), _oprot(oprot) {}
virtual ~${service}ServerIf() {}
bool process("""+CPP_TRANSPORTP+""" _itrans,"""+CPP_TRANSPORTP+""" _otrans);
protected:
"""+CPP_PROTOCOLP+""" _iprot;
"""+CPP_PROTOCOLP+""" _oprot;
private:
${functionDeclarations}};
""")
def toServerDeclaration(service, debugp=None):
"""Converts a thrift service definition to the Server skeleton class declaration."""
functionDeclarations = string.join([CPP_SERVER_FUNCTION_DECLARATION.substitute(function=function.name) for function in service.functionList], "")
return CPP_SERVER_DECLARATION.substitute(service=service.name, functionDeclarations=functionDeclarations)
CPP_CLIENT_FUNCTION_DECLARATION = Template(""" ${functionDeclaration};
""")
CPP_CLIENT_FUNCTION_DEFINITION = Template("""
${returnDeclaration} ${service}Client::${function}(${argsDeclaration}) ${exceptionDeclaration} {
uint32_t xfer = 0;
std::string name;
"""+CPP_PROTOCOL_MESSAGE_TYPE+""" messageType;
uint32_t cseqid = 0;
uint32_t rseqid = 0;
_oprot->writeMessageBegin(_otrans, \"${function}\", """+CPP_PROTOCOL_CALL+""", cseqid);
${argsStructDeclaration};
${argsToStruct};
${argsStructWriter};
_otrans->flush();
_iprot->readMessageBegin(_itrans, name, messageType, rseqid);
if(messageType != """+CPP_PROTOCOL_REPLY+""" ||
rseqid != cseqid) {
throw """+CPP_EXCEPTION+"""(\"unexpected message type or id\");
}
${resultStructDeclaration};
${resultStructReader};
_iprot->readMessageEnd(_itrans);
${returnResult}
throw """+CPP_EXCEPTION+"""(\"${function} failed: unknown result\");
}
""")
CPP_CLIENT_DECLARATION = Template("""
class ${service}Client : public ${service}If {
public:
${service}Client("""+CPP_TRANSPORTP+""" transport, """+CPP_PROTOCOLP+""" protocol): _itrans(transport), _otrans(transport), _iprot(protocol), _oprot(protocol) {}
${service}Client("""+CPP_TRANSPORTP+""" itrans, """+CPP_TRANSPORTP+""" otrans, """+CPP_PROTOCOLP+""" iprot, """+CPP_PROTOCOLP+""" oprot) : _itrans(itrans), _otrans(otrans), _iprot(iprot), _oprot(oprot) {}
${functionDeclarations}
private:
"""+CPP_TRANSPORTP+""" _itrans;
"""+CPP_TRANSPORTP+""" _otrans;
"""+CPP_PROTOCOLP+""" _iprot;
"""+CPP_PROTOCOLP+""" _oprot;
};""")
def toServerFunctionDefinition(servicePrefix, function, debugp=None):
"""Converts a thrift service method declaration into a server method-call processoror function"""
result = ""
argsStructDeclaration = toCTypeDeclaration(function.argsStruct)+" __args"
argsStructReader = toReaderCall("__args", function.argsStruct, "_iprot")
resultStructDeclaration = toCTypeDeclaration(function.resultStruct)+" __result"
resultStructWriter = toWriterCall("__result", function.resultStruct, "_oprot")
if not isVoidType(function.returnType()):
functionCallPrefix= "__result.success = "
functionCallSuffix = "\n __result.__isset.success = true;"
else:
functionCallPrefix = ""
functionCallSuffix = ""
functionCall= function.name+"("+string.join(["__args."+arg.name for arg in function.args()], ", ")+");"
exceptions = function.exceptions()
if len(exceptions) > 0:
functionCallPrefix= "try {\n "+functionCallPrefix
functionCallSuffix+= "\n }"+string.join([" catch("+toCTypeDeclaration(exceptions[ix].type)+"& e"+str(ix)+") {\n __result."+exceptions[ix].name+" = e"+str(ix)+";\n __result.__isset."+exceptions[ix].name+" = true;\n }"
for ix in range(len(exceptions))], "")
functionCall = functionCallPrefix+functionCall+functionCallSuffix
result+= CPP_SERVER_FUNCTION_DEFINITION.substitute(service=servicePrefix, function=function.name,
argsStructDeclaration=argsStructDeclaration,
argsStructReader=argsStructReader,
functionCall=functionCall,
resultStructDeclaration=resultStructDeclaration,
resultStructWriter=resultStructWriter)
return result
def toServerServiceDefinition(service, debugp=None):
"""Converts a thrift service definiton to a server skeleton implementation"""
result = ""
for function in service.functionList:
result+= toServerFunctionDefinition(service.name, function, debugp)
callProcessSwitch = " if"+string.join(["(name.compare(\""+function.name+"\") == 0) {\n process_"+function.name+"(seqid, itrans, otrans);\n }" for function in service.functionList], " else if")+" else {\n throw "+CPP_EXCEPTION+"(\"Unknown function name \\\"\"+name+\"\\\"\");\n }"
result+= CPP_SERVER_PROCESS_DEFINITION.substitute(service=service.name, callProcessSwitch=callProcessSwitch)
return result
def toServerDefinition(program, debugp=None):
return string.join([toServerServiceDefinition(service) for service in program.serviceMap.values()], "\n")
def toClientDeclaration(service, debugp=None):
functionDeclarations = string.join([CPP_CLIENT_FUNCTION_DECLARATION.substitute(functionDeclaration=toCTypeDeclaration(function)) for function in service.functionList], "")
return CPP_CLIENT_DECLARATION.substitute(service=service.name, functionDeclarations=functionDeclarations)+"\n"
def toClientFunctionDefinition(servicePrefix, function, debugp=None):
"""Converts a thrift service method declaration to a client stub implementation"""
isVoid = isVoidType(function.returnType())
returnDeclaration = toCTypeDeclaration(function.returnType())
argsDeclaration = string.join([toCTypeDeclaration(function.args()[ix].type)+" __arg"+str(ix) for ix in range(len(function.args()))], ", ")
exceptionDeclaration = string.join([toCTypeDeclaration(exception.type) for exception in function.exceptions()], ", ")
if len(exceptionDeclaration)> 0:
exceptionDeclaration = "throw("+exceptionDeclaration+")"
argsStructDeclaration = toCTypeDeclaration(function.argsStruct)+" __args"
argsStructWriter = toWriterCall("__args", function.argsStruct, "_oprot", "_otrans")
argsToStruct= string.join([" __args."+function.args()[ix].name+" = __arg"+str(ix) for ix in range(len(function.args()))], ";\n")
resultStructDeclaration = toCTypeDeclaration(function.resultStruct)+" __result"
resultStructReader = toReaderCall("__result", function.resultStruct, "_iprot", "_itrans")
exceptions = function.exceptions()
""" void return and non-void returns require very different arrangments. For void returns, we don't actually expect
anything to have been sent from the remote side, therefore we need to check for explicit exception returns first,
then, if none were encountered, return. For void we test for success first - since this is the most likey result -
and then check for exceptions. In both cases, we need to handle the case where there are no specified exceptions. """
if len(exceptions) > 0:
errors= ["if(__result.__isset."+exception.name+") {\n throw __result."+exception.name+";\n }" for exception in exceptions]
else:
errors = []
if not isVoid:
returnResult = " if(__result.__isset.success) {\n return __result.success;\n}"
if len(errors) > 0:
returnResult+= " else "+string.join(errors, " else ")
else:
if len(errors) > 0:
returnResult= " "+string.join(errors, " else ")+" else {\n return;\n }\n"
else:
returnResult=" return;\n"
return CPP_CLIENT_FUNCTION_DEFINITION.substitute(service=servicePrefix,
function=function.name,
returnDeclaration=returnDeclaration,
argsDeclaration=argsDeclaration,
exceptionDeclaration=exceptionDeclaration,
argsStructDeclaration=argsStructDeclaration,
argsStructWriter=argsStructWriter,
argsToStruct=argsToStruct,
resultStructDeclaration=resultStructDeclaration,
resultStructReader=resultStructReader,
returnResult=returnResult)
def toClientServiceDefinition(service, debugp=None):
"""Converts a thrift service definition to a client stub implementation"""
result = ""
for function in service.functionList:
result+= toClientFunctionDefinition(service.name, function)
return result
def toClientDefinition(program, debugp=None):
"""Converts all services in a thrift program to client stub implementations"""
return string.join([toClientServiceDefinition(service) for service in program.serviceMap.values()], "\n")
def toServiceDeclaration(service, debugp=None):
"""Converts all services in a thrift program to service interface or abstract base class declarations"""
return toServiceInterfaceDeclaration(service, debugp) + toServerDeclaration(service, debugp) + toClientDeclaration(service, debugp)
def toGenDir(filename, suffix="cpp-gen", debugp=None):
"""creates a generated-code subdirectory for C++ code based on filename of thrift source file and optional suffix"""
result = os.path.join(os.path.split(filename)[0], suffix)
if not os.path.exists(result):
os.mkdir(result)
return result
def toBasename(filename, debugp=None):
""" Take the filename minus the path and\".thrift\" extension if present """
basename = os.path.split(filename)[1]
tokens = os.path.splitext(basename)
if tokens[1].lower() == ".thrift":
basename = tokens[0]
if debugp:
debugp("toBasename("+str(filename)+") => "+str(basename))
return basename
def toDefinitionHeaderName(filename, genDir=None, debugp=None):
"""Creates a file name for the public thrift data types based on filename of thrift source file and optional suffix"""
if not genDir:
genDir = toGenDir(filename)
basename = toBasename(filename)
result = os.path.join(genDir, basename+"_types.h")
if debugp:
debugp("toDefinitionHeaderName("+str(filename)+", "+str(genDir)+") => "+str(basename))
return result
def writeDefinitionHeader(program, filename, genDir=None, debugp=None):
"""Writes public thrift data types defined in program into a public data types header file. Uses the name of the original
thrift source, filename, and the optional generated C++ code directory, genDir, to determine the name and location of header file"""
definitionHeader = toDefinitionHeaderName(filename, genDir)
if debugp:
debugp("definitionHeader: "+str(definitionHeader))
cfile = CFile(definitionHeader, "w")
basename = toBasename(filename)
cfile.writeln(CPP_TYPES_HEADER.substitute(source=basename, date=time.ctime(), namespacePrefix=toCNamespacePrefix(program.namespace)))
cfile.write(toDefinitions(program.definitions))
cfile.writeln(CPP_TYPES_FOOTER.substitute(source=basename, namespaceSuffix=toCNamespaceSuffix(program.namespace)))
cfile.close()
def toServicesHeaderName(filename, genDir=None, debugp=None):
"""Creates a file name for the public thrift services based on filename of thrift source file and optional suffix"""
if not genDir:
genDir = toGenDir(filename)
basename = toBasename(filename)
result = os.path.join(genDir, basename+".h")
if debugp:
debugp("toDefinitionHeaderName("+str(filename)+", "+str(genDir)+") => "+str(basename))
return result
def writeServicesHeader(program, filename, genDir=None, debugp=None):
"""Writes public thrift service abstract base class, client class, and server class for all services defined in program into a public services header file. Uses the name of the original thrift source, filename, and the optional generated C++ code directory, genDir, to determine the name and location of header file"""
servicesHeader = toServicesHeaderName(filename, genDir)
if debugp:
debugp("servicesHeader: "+str(servicesHeader))
cfile = CFile(servicesHeader, "w")
basename = toBasename(filename)
cfile.writeln(CPP_SERVICES_HEADER.substitute(source=basename, date=time.ctime(), namespacePrefix=toCNamespacePrefix(program.namespace)))
services = []
# Build orderered list of service definitions by scanning definitions list for services
for definition in program.definitions:
if isinstance(definition, Service) and definition.name in program.serviceMap:
services.append(definition)
for service in services:
cfile.write(toServiceDeclaration(service))
cfile.writeln(CPP_SERVICES_FOOTER.substitute(source=basename, namespaceSuffix=toCNamespaceSuffix(program.namespace)))
cfile.close()
CPP_STRUCT_READ = Template("""
uint32_t read${name}Struct("""+CPP_PROTOCOLP+""" _iprot, """+CPP_TRANSPORTP+""" itrans, ${declaration}& value) {
std::string name;
uint32_t id;
uint32_t type;
uint32_t xfer = 0;
while(true) {
xfer+= _iprot->readFieldBegin(_itrans, name, type, id);
if(type == """+CPP_PROTOCOL_TSTOP+""") {
break;
}
switch(id) {
${readFieldListSwitch}
}
}
xfer+= _iprot->readStructEnd(_itrans);
return xfer;
}
""")
CPP_PRIMITIVE_TYPE_IO_METHOD_SUFFIX_MAP = {
"void" :"Void",
"bool" : "Bool",
"string": "String",
"utf7": "String",
"utf8": "String",
"utf16": "String",
"i08": "Byte",
"i16": "I16",
"i32": "I32",
"i64": "I64",
"u08": "Byte",
"u16": "U16",
"u32": "U32",
"u64": "U64",
"float": "Float",
"double": "Double"
}
CPP_COLLECTION_TYPE_IO_METHOD_SUFFIX_MAP = {
MapType : "map",
ListType : "list",
SetType : "set"
}
def typeToIOMethodSuffix(ttype):
"""Converts type to a name suitable as a suffix with TProtocol primitive read and write methods or with a generated read or write
method for a complex type"""
if isinstance(ttype, PrimitiveType):
return CPP_PRIMITIVE_TYPE_IO_METHOD_SUFFIX_MAP[ttype.name]
elif isinstance(ttype, CollectionType):
result = CPP_COLLECTION_TYPE_IO_METHOD_SUFFIX_MAP[type(ttype)]+"_"
if isinstance(ttype, MapType):
result+= "k_"+typeToIOMethodSuffix(ttype.keyType)+"_"
result += "v_"+typeToIOMethodSuffix(ttype.valueType)
return result
elif isinstance(ttype, StructType):
return "struct_"+ttype.name
elif isinstance(ttype, TypedefType):
return ttype.name
elif isinstance(ttype, EnumType):
return ttype.name
else:
raise Exception, "Unknown type "+str(ttype)
def toReaderCall(value, ttype, reader="iprot", transport="itrans"):
"""Converts type to a name suitable as a suffix with TProtocol primitive read methods or with a generated read
method for a complex type"""
suffix = typeToIOMethodSuffix(ttype)
if isinstance(ttype, PrimitiveType):
if ttype != VOID_TYPE:
return "xfer += "+reader+"->read"+suffix+"("+transport+", "+value+")"
else:
return ""
elif isinstance(ttype, CollectionType):
return "xfer+= read_"+suffix+"("+reader+", "+transport+", "+value+")"
elif isinstance(ttype, StructType):
return "xfer+= read_"+suffix+"("+reader+", "+transport+", "+value+")"
elif isinstance(ttype, TypedefType):
return toReaderCall("reinterpret_cast<"+toCTypeDeclaration(ttype.definitionType)+"&>("+value+")", ttype.definitionType, reader)
elif isinstance(ttype, EnumType):
return toReaderCall("reinterpret_cast<"+toCTypeDeclaration(I32_TYPE)+"&>("+value+")", I32_TYPE, reader)
else:
raise Exception, "Unknown type "+str(ttype)
def toWriterCall(value, ttype, writer="oprot", transport="otrans"):
"""Converts type to a name suitable as a suffix with TProtocol primitive write methods or with a generated write
method for a complex type"""
suffix = typeToIOMethodSuffix(ttype)
if isinstance(ttype, PrimitiveType):
if ttype != VOID_TYPE:
return "xfer+= "+writer+"->write"+suffix+"("+transport+", "+value+")"
else:
return ""
elif isinstance(ttype, CollectionType):
return "xfer+= write_"+suffix+"("+writer+", "+transport+", "+value+")"
elif isinstance(ttype, StructType):
return "xfer+= write_"+suffix+"("+writer+", "+transport+", "+value+")"
elif isinstance(ttype, TypedefType):
return toWriterCall("reinterpret_cast<const "+toCTypeDeclaration(ttype.definitionType)+"&>("+value+")", ttype.definitionType, writer)
elif isinstance(ttype, EnumType):
return toWriterCall("reinterpret_cast<const "+toCTypeDeclaration(I32_TYPE)+"&>("+value+")", I32_TYPE, writer)
else:
raise Exception, "Unknown type "+str(ttype)
CPP_READ_MAP_DEFINITION = Template("""
uint32_t read_${suffix}("""+CPP_PROTOCOLP+""" iprot, """+CPP_TRANSPORTP+""" itrans, ${declaration}& value) {
uint32_t count;
${keyType} key;
${valueType} elem;
uint32_t xfer = 0;
"""+CPP_PROTOCOL_TTYPE+""" keyType;
"""+CPP_PROTOCOL_TTYPE+""" valueType;
xfer += iprot->readMapBegin(itrans, keyType, valueType, count);
/* XXX
Should we assert that read key and value type are correct? */
for(uint32_t ix = 0; ix < count; ix++) {
${keyReaderCall};
${valueReaderCall};
value.insert(std::make_pair(key, elem));
}
xfer += iprot->readMapEnd(itrans);
return xfer;
}
""")
CPP_WRITE_MAP_DEFINITION = Template("""
uint32_t write_${suffix}("""+CPP_PROTOCOLP+""" oprot, """+CPP_TRANSPORTP+""" otrans, const ${declaration}& value) {
uint32_t xfer = 0;
xfer += oprot->writeMapBegin(otrans, ${keyWireType}, ${valueWireType}, value.size());
for(${declaration}::const_iterator ix = value.begin(); ix != value.end(); ++ix) {
${keyWriterCall};
${valueWriterCall};
}
xfer += oprot->writeMapEnd(otrans);
return xfer;
}
""")
CPP_READ_LIST_DEFINITION = Template("""
uint32_t read_${suffix}("""+CPP_PROTOCOLP+""" iprot, """+CPP_TRANSPORTP+""" itrans, ${declaration}& value) {
uint32_t count;
${valueType} elem;
uint32_t xfer = 0;
"""+CPP_PROTOCOL_TTYPE+""" valueType;
xfer += iprot->read${protocolSuffix}Begin(itrans, valueType, count);
/* XXX
Should we assert that read value type is correct? */
xfer+= iprot->readU32(itrans, count);
for(uint32_t ix = 0; ix < count; ix++) {
${valueReaderCall};
value.${insert}(elem);
}
xfer += iprot->read${protocolSuffix}End(itrans);
return xfer;
}
""")
CPP_WRITE_LIST_DEFINITION = Template("""
uint32_t write_${suffix}("""+CPP_PROTOCOLP+""" oprot, """+CPP_TRANSPORTP+""" otrans, const ${declaration}& value) {
uint32_t xfer = 0;
xfer+= oprot->write${protocolSuffix}Begin(otrans, ${valueWireType}, value.size());
for(${declaration}::const_iterator ix = value.begin(); ix != value.end(); ++ix) {
${valueWriterCall};
}
xfer+= oprot->write${protocolSuffix}End(otrans);
return xfer;
}
""")
def toCollectionReaderDefinition(collection):
"""Converts collection type to reader function definition"""
suffix = typeToIOMethodSuffix(collection)
if isinstance(collection, MapType):
keyReaderCall = toReaderCall("key", collection.keyType)
valueReaderCall= toReaderCall("elem", collection.valueType)
if isinstance(collection, MapType):
return CPP_READ_MAP_DEFINITION.substitute(suffix=suffix, declaration=toCTypeDeclaration(collection),
keyType=toCTypeDeclaration(collection.keyType),
keyReaderCall=keyReaderCall,
valueType=toCTypeDeclaration(collection.valueType),
valueReaderCall=valueReaderCall)
else:
if isinstance(collection, ListType):
protocolSuffix="List"
insert="push_back"
else:
protocolSuffix="Set"
insert="insert"
return CPP_READ_LIST_DEFINITION.substitute(suffix=suffix, declaration=toCTypeDeclaration(collection),
protocolSuffix=protocolSuffix,
valueReaderCall=valueReaderCall,
valueType=toCTypeDeclaration(collection.valueType),
insert=insert)
def toCollectionWriterDefinition(collection):
"""Converts collection type to writer function definition"""
suffix = typeToIOMethodSuffix(collection)
if isinstance(collection, MapType):
keyWriterCall = toWriterCall("ix->first", collection.keyType)
keyWireType = toWireType(collection.keyType)
valueWriterCall = toWriterCall("ix->second", collection.valueType)
else:
valueWriterCall= toWriterCall("*ix", collection.valueType)
valueWireType = toWireType(collection.valueType)
if isinstance(collection, MapType):
return CPP_WRITE_MAP_DEFINITION.substitute(suffix=suffix, declaration=toCTypeDeclaration(collection),
keyType=toCTypeDeclaration(collection.keyType),
keyWireType=keyWireType,
keyWriterCall=keyWriterCall,
valueType=toCTypeDeclaration(collection.valueType),
valueWireType=valueWireType,
valueWriterCall=valueWriterCall)
else:
if isinstance(collection, ListType):
protocolSuffix = "List"
elif isinstance(collection, SetType):
protocolSuffix = "Set"
else:
raise Exception, "Unknown collection type "+str(type(collection))+":"+str(collection)
return CPP_WRITE_LIST_DEFINITION.substitute(suffix=suffix, declaration=toCTypeDeclaration(collection),
protocolSuffix=protocolSuffix,
valueWireType=valueWireType,
valueWriterCall=valueWriterCall,
valueType=toCTypeDeclaration(collection.valueType))
CPP_READ_STRUCT_DEFINITION = Template("""
uint32_t read_${suffix}("""+CPP_PROTOCOLP+""" iprot, """+CPP_TRANSPORTP+""" itrans, ${declaration}& value) {
std::string name;
"""+CPP_PROTOCOL_TTYPE+""" type;
int16_t id;
uint32_t xfer = 0;
xfer+= iprot->readStructBegin(itrans, name);
while(true) {
xfer+= iprot->readFieldBegin(itrans, name, type, id);
if(type == """+CPP_PROTOCOL_TSTOP+""") {break;}
switch(id) {
${fieldSwitch}
default: xfer += iprot->skip(itrans, type); break;
}
xfer+= iprot->readFieldEnd(itrans);
}
xfer+= iprot->readStructEnd(itrans);
return xfer;
}
""")
CPP_WRITE_FIELD_DEFINITION = Template("""oprot->writeFieldBegin(otrans, \"${name}\", ${type}, ${id}); ${fieldWriterCall}; oprot->writeFieldEnd(otrans)""")
CPP_WRITE_STRUCT_DEFINITION = Template("""
uint32_t write_${suffix}("""+CPP_PROTOCOLP+""" oprot, """+CPP_TRANSPORTP+""" otrans, const ${declaration}& value) {
uint32_t xfer = 0;
xfer+= oprot->writeStructBegin(otrans, \"${name}\");
${fieldWriterCalls}
xfer+= oprot->writeFieldStop(otrans);
xfer += oprot->writeStructEnd(otrans);
return xfer;
}
""")
def toStructReaderDefinition(struct):
"""Converts struct type to reader function definition"""
suffix = typeToIOMethodSuffix(struct)
# Sort field list in order of increasing ids
fieldList = []
fieldList+= struct.fieldList
fieldList.sort(lambda a,b: a.id - b.id)
fieldSwitch=""
for field in fieldList:
if isVoidType(field.type):
continue;
fieldSwitch+= " case "+str(field.id)+": "
fieldSwitch+= toReaderCall("value."+field.name, field.type)+"; value.__isset."+field.name+" = true; break;\n"
return CPP_READ_STRUCT_DEFINITION.substitute(suffix=suffix, declaration=toCTypeDeclaration(struct), fieldSwitch=fieldSwitch)
def toStructWriterDefinition(struct):
"""Converts struct type to writer function definition"""
suffix = typeToIOMethodSuffix(struct)
fieldWriterCalls = []
fieldWriterCalls = [CPP_WRITE_FIELD_DEFINITION.substitute(name=field.name,
type=toWireType(toCanonicalType(field.type)),
id=field.id,
fieldWriterCall=toWriterCall("value."+field.name, field.type))+";"
for field in struct.fieldList if not isVoidType(field.type)
]
fieldWriterCalls = " "+string.join(fieldWriterCalls, "\n ")
return CPP_WRITE_STRUCT_DEFINITION.substitute(name=struct.name, suffix=suffix, declaration=toCTypeDeclaration(struct), fieldWriterCalls=fieldWriterCalls)
CPP_WRITE_RESULT_STRUCT_DEFINITION = Template("""
uint32_t write_${suffix}("""+CPP_PROTOCOLP+""" oprot, """+CPP_TRANSPORTP+""" otrans, const ${declaration}& value) {
uint32_t xfer = 0;
xfer+= oprot->writeStructBegin(otrans, \"${name}\");
if(${value}.__isset.x)
${fieldWriterCalls}
xfer+= oprot->writeFieldStop(otrans);
xfer += oprot->writeStructEnd(otrans);
return xfer;
}
""")
def toResultStructReaderDefinition(struct):
"""Converts internal results struct to a reader function definition"""
return toStructReaderDefinition(struct)
def toResultStructWriterDefinition(struct):
"""Converts internal results struct to a reader function definition. The difference between this function and toStructWriterDefinition is that this only sends one field, either success or an exception field, depending on which field is set"""
suffix = typeToIOMethodSuffix(struct)
fieldWriterCalls = ["if(value.__isset."+field.name+") { "+
CPP_WRITE_FIELD_DEFINITION.substitute(name=field.name,
type=toWireType(toCanonicalType(field.type)),
id=field.id,
fieldWriterCall=toWriterCall("value."+field.name, field.type))+";}"
for field in struct.fieldList if not isVoidType(field.type)]
fieldWriterCalls = " "+string.join(fieldWriterCalls, "\n else ")
return CPP_WRITE_STRUCT_DEFINITION.substitute(name=struct.name, suffix=suffix, declaration=toCTypeDeclaration(struct), fieldWriterCalls=fieldWriterCalls)
def toReaderDefinition(ttype):
"""Converts thrift type to a reader function definition"""
if isinstance(ttype, CollectionType):
return toCollectionReaderDefinition(ttype)
elif isinstance(ttype, StructType):
return toStructReaderDefinition(ttype)
elif isinstance(ttype, TypedefType):
return ""
elif isinstance(ttype, EnumType):
return ""
else:
raise Exception, "Unsupported type: "+str(ttype)
def toWriterDefinition(ttype):
"""Converts thrift type to a writer function definition"""
if isinstance(ttype, CollectionType):
return toCollectionWriterDefinition(ttype)
elif isinstance(ttype, StructType):
return toStructWriterDefinition(ttype)
elif isinstance(ttype, TypedefType):
return ""
elif isinstance(ttype, EnumType):
return ""
else:
raise Exception, "Unsupported type: "+str(ttype)
def toOrderedIOList(ttype, result=None):
"""Builds a list of types ordered by doing a depth first traverse of all thrift type definitions. This gives us a list from which we can
generate read/write methods without making forward references."""
if not result:
result = []
if ttype in result:
return result
elif isinstance(ttype, PrimitiveType):
return result
elif isinstance(ttype, CollectionType):
if isinstance(ttype, MapType):
result = toOrderedIOList(ttype.keyType, result)
result = toOrderedIOList(ttype.valueType, result)
result.append(ttype)
elif isinstance(ttype, StructType):
for field in ttype.fieldList:
result = toOrderedIOList(field.type, result)
result.append(ttype)
elif isinstance(ttype, TypedefType):
result.append(ttype)
return result
elif isinstance(ttype, EnumType):
result.append(ttype)
return result
elif isinstance(ttype, Program):
for struct in ttype.structMap.values():
result = toOrderedIOList(struct, result)
for service in ttype.serviceMap.values():
result = toOrderedIOList(service, result)
elif isinstance(ttype, Service):
for function in ttype.functionList:
result = toOrderedIOList(function, result)
elif isinstance(ttype, Function):
result = toOrderedIOList(ttype.returnType(), result)
# skip the args struct itself and just order the arguments themselves
# we don't want the arg struct to be referred to until later, since we need to
# inline those struct definitions with the implementation, not in the types header
for field in ttype.args():
result = toOrderedIOList(field.type, result)
else:
raise Exception, "Unsupported thrift type: "+str(ttype)
return result
def toIOMethodImplementations(program):
"""Generates read and write methods for all non-primitive types in a thrift program as well as for the internal argStruct and resultStruct types for
all service functions"""
# get ordered list of all types that need marshallers:
iolist = toOrderedIOList(program)
result = ""
for ttype in iolist:
result+= toReaderDefinition(ttype)
result+= toWriterDefinition(ttype)
# For all function argument lists, we need to create both struct definitions
# and io methods. We keep the struct definitions local, since they aren't part of the service API
#
# Note that we don't need to do a depth-first traverse of arg structs since they can only include fields
# we've already seen
for service in program.serviceMap.values():
for function in service.functionList:
result+= toStructDefinition(function.argsStruct)
result+= toReaderDefinition(function.argsStruct)
result+= toWriterDefinition(function.argsStruct)
result+= toStructDefinition(function.resultStruct)
result+= toResultStructReaderDefinition(function.resultStruct)
result+= toResultStructWriterDefinition(function.resultStruct)
return result;
def toImplementationSourceName(filename, genDir=None, debugp=None):
"""Creates a file name for the implementation of client stubs, server skeletons, and non-primitive read/write methods."""
if not genDir:
genDir = toGenDir(filename)
basename = toBasename(filename)
result = os.path.join(genDir, basename+".cc")
if debugp:
debugp("toDefinitionHeaderName("+str(filename)+", "+str(genDir)+") => "+str(basename))
return result
def writeImplementationSource(program, filename, genDir=None, debugp=None):
"""Writes client stub, server skeleton, and non-primitive type I/O functions for all servciesf defined in program into a C/C++ source file. Uses the name of the original
thrift source, filename, and the optional generated C++ code directory, genDir, to determine the name and location of header file"""
implementationSource = toImplementationSourceName(filename, genDir)
if debugp:
debugp("implementationSource: "+str(implementationSource))
cfile = CFile(implementationSource, "w")
basename = toBasename(filename)
cfile.writeln(CPP_IMPL_HEADER.substitute(source=basename, date=time.ctime(), namespacePrefix=toCNamespacePrefix(program.namespace)))
cfile.write(toIOMethodImplementations(program))
cfile.write(toServerDefinition(program))
cfile.write(toClientDefinition(program))
cfile.writeln(CPP_IMPL_FOOTER.substitute(source=basename, namespaceSuffix=toCNamespaceSuffix(program.namespace)))
cfile.close()
class CPPGenerator(Generator):
def __call__(self, program, filename, genDir=None, debugp=None):
writeDefinitionHeader(program, filename, genDir, debugp)
writeServicesHeader(program, filename, genDir, debugp)
writeImplementationSource(program, filename, genDir, debugp)