From c6936407d1c3ba0325441d44511d2876eb4dbede Mon Sep 17 00:00:00 2001 From: Marc Slemko Date: Wed, 23 Aug 2006 02:15:31 +0000 Subject: [PATCH] Thrift php generator in python. Cleaned up parser and cpp generator git-svn-id: https://svn.apache.org/repos/asf/incubator/thrift/trunk@664765 13f79535-47bb-0310-9956-ffa450edef68 --- compiler/setup.py | 2 +- compiler/src/__init__.py | 2 +- compiler/src/cpp_generator.py | 18 +- compiler/src/generator.py | 2 +- compiler/src/parser.py | 42 +- compiler/src/php_generator.py | 1015 +++++++++++++++++++++++++++++++++ 6 files changed, 1051 insertions(+), 30 deletions(-) create mode 100644 compiler/src/php_generator.py diff --git a/compiler/setup.py b/compiler/setup.py index d5bbae86e..3901042bc 100644 --- a/compiler/setup.py +++ b/compiler/setup.py @@ -7,7 +7,7 @@ setup(name='Thrift', author_email= ['mcslee@facebook.com', 'marc@facebook.com'], url='http://code.facebook.com/thrift', package_dir={'thrift' : 'src'}, - py_modules = ['thrift.parser', 'thrift.cpp_generator', 'thrift.generator'], + py_modules = ['thrift.parser', 'thrift.cpp_generator', 'thrift.generator', 'thrift.php_generator'], scripts = ['src/thrift'] ) diff --git a/compiler/src/__init__.py b/compiler/src/__init__.py index 1ceea9c5f..440be4fc0 100644 --- a/compiler/src/__init__.py +++ b/compiler/src/__init__.py @@ -1 +1 @@ -__all__ = ['parser', 'generator', 'cpp_generator'] +__all__ = ['parser', 'generator', 'cpp_generator', 'php_generator'] diff --git a/compiler/src/cpp_generator.py b/compiler/src/cpp_generator.py index f1bf84c0d..ba3380e82 100644 --- a/compiler/src/cpp_generator.py +++ b/compiler/src/cpp_generator.py @@ -226,16 +226,16 @@ def toStructDefinition(struct): # Field declarations - result+= string.join([" "+toCTypeDeclaration(field)+";\n" for field in struct.fieldList if toCanonicalType(field.type) != VOID_TYPE], "") + 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 toCanonicalType(field.type) != VOID_TYPE], ", ") + 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 toCanonicalType(field.type) != VOID_TYPE], "") + result+= string.join([" bool "+field.name+";\n" for field in struct.fieldList if not isVoidType(field.type)], "") result+= " } __isset;\n" # bring it on home @@ -460,7 +460,7 @@ ${argsToStruct}; _iprot->readMessageEnd(_itrans); ${returnResult} - throw """+CPP_EXCEPTION+"""(\"${function} failed: unknown result"); + throw """+CPP_EXCEPTION+"""(\"${function} failed: unknown result\"); } """) @@ -493,7 +493,7 @@ def toServerFunctionDefinition(servicePrefix, function, debugp=None): resultStructWriter = toWriterCall("__result", function.resultStruct, "_oprot") - if toCanonicalType(function.returnType()) != VOID_TYPE: + if not isVoidType(function.returnType()): functionCallPrefix= "__result.success = " functionCallSuffix = "\n __result.__isset.success = true;" else: @@ -548,7 +548,7 @@ def toClientDeclaration(service, debugp=None): def toClientFunctionDefinition(servicePrefix, function, debugp=None): """Converts a thrift service method declaration to a client stub implementation""" - isVoid = toCanonicalType(function.returnType()) == VOID_TYPE + isVoid = isVoidType(function.returnType()) returnDeclaration = toCTypeDeclaration(function.returnType()) @@ -1044,7 +1044,7 @@ def toStructReaderDefinition(struct): fieldSwitch="" for field in fieldList: - if toCanonicalType(field.type) == VOID_TYPE: + if isVoidType(field.type): continue; fieldSwitch+= " case "+str(field.id)+": " fieldSwitch+= toReaderCall("value."+field.name, field.type)+"; value.__isset."+field.name+" = true; break;\n" @@ -1063,7 +1063,7 @@ def toStructWriterDefinition(struct): type=toWireType(toCanonicalType(field.type)), id=field.id, fieldWriterCall=toWriterCall("value."+field.name, field.type))+";" - for field in struct.fieldList if toCanonicalType(field.type) != VOID_TYPE + for field in struct.fieldList if not isVoidType(field.type) ] fieldWriterCalls = " "+string.join(fieldWriterCalls, "\n ") @@ -1099,7 +1099,7 @@ def toResultStructWriterDefinition(struct): type=toWireType(toCanonicalType(field.type)), id=field.id, fieldWriterCall=toWriterCall("value."+field.name, field.type))+";}" - for field in struct.fieldList if toCanonicalType(field.type) != VOID_TYPE] + for field in struct.fieldList if not isVoidType(field.type)] fieldWriterCalls = " "+string.join(fieldWriterCalls, "\n else ") diff --git a/compiler/src/generator.py b/compiler/src/generator.py index 9ac2cdaf3..7e8a84a64 100644 --- a/compiler/src/generator.py +++ b/compiler/src/generator.py @@ -1,3 +1,3 @@ class Generator(object): - def __call__(self, program, filename, gendir): + def __call__(self, program, filename, gendir, debugp=None): raise Exception, "Not implemented" diff --git a/compiler/src/parser.py b/compiler/src/parser.py index 3b0bdc174..87633f4a5 100644 --- a/compiler/src/parser.py +++ b/compiler/src/parser.py @@ -50,8 +50,8 @@ class SymanticsError(Error): return str(self.start)+": error: "+self.message class ErrorException(Exception): - def __init__(self, errors=None): + Exception.__init__(self) self.errors = errors class Definition(object): @@ -82,14 +82,18 @@ class Identifier(Definition): result+="="+str(self.id) return result - def toCanonicalType(ttype): if isinstance(ttype, TypedefType): return toCanonicalType(ttype.definitionType) else: return ttype +def isVoidType(ttype): + """Is canonnical type of the specified type void?""" + return toCanonicalType(ttype) == VOID_TYPE + def isComparableType(ttype): + """Is the type one that is implicitly comparable and thus is suitable for use in C++ maps and sets and java Sets and Maps?""" ttype = toCanonicalType(ttype) return isinstance(ttype, PrimitiveType) or isinstance(ttype, EnumType) @@ -227,14 +231,16 @@ class EnumType(Definition): def assignId(enumDef, currentId, ids): 'Finds the next available id number for an enum definition' - id= currentId + 1 + eid= currentId + 1 - while id in ids: - id += 1 + while eid in ids: + eid += 1 - enumDef.id = id + enumDef.id = eid ids[enumDef.id] = enumDef + + return eid # assign ids for all enum defs with unspecified ids @@ -302,16 +308,16 @@ def validateFieldList(fieldList): def assignId(field, currentId, ids): 'Finds the next available id number for a field' - id = currentId - 1 + fid = currentId - 1 - while id in ids: - id-= 1 + while fid in ids: + fid-= 1 - field.id = id + field.id = fid ids[field.id] = field - return id + return fid # assign ids for all fields with unspecified ids @@ -340,7 +346,7 @@ class ExceptionType(StructType): class Function(Definition): - def __init__(self, symbols, name, resultStruct, argsStruct, ): + def __init__(self, symbols, name, resultStruct, argsStruct): Definition.__init__(self, symbols, name) self.resultStruct = resultStruct self.argsStruct = argsStruct @@ -373,7 +379,7 @@ class Service(Definition): functionNames = {} for function in self.functionList: if function.name in functionNames: - oldFunction = functionName[function.name] + oldFunction = functionNames[function.name] errors.append(SymanticsError(function, "function "+function.name+" already defined at "+str(oldFunction.start))) if len(errors): @@ -384,7 +390,7 @@ class Service(Definition): class Program(object): - def __init__(self, symbols=None, name="", + def __init__(self, symbols=None, name="", namespace="", definitions=None, serviceMap=None, @@ -470,16 +476,16 @@ class Program(object): else: raise ErrorException([SymanticsError(parent, "unknown symbol \""+str(symbol)+"\"")]) - for map in (self.primitiveMap, self.collectionMap, self.typedefMap, self.enumMap, self.structMap): - if typeName in map: - return map[typeName] + for tmap in (self.primitiveMap, self.collectionMap, self.typedefMap, self.enumMap, self.structMap): + if typeName in tmap: + return tmap[typeName] raise ErrorException([SymanticsError(parent, "\""+typeName+"\" is not defined.")]) def hasType(self, parent, symbol): """ Determine if a type definition exists for the symbol""" - return self.getType(parent, symbol) == True + return self.getType(parent, symbol) != None def validate(self): diff --git a/compiler/src/php_generator.py b/compiler/src/php_generator.py new file mode 100644 index 000000000..e68a4f73e --- /dev/null +++ b/compiler/src/php_generator.py @@ -0,0 +1,1015 @@ +""" Thrift IDL PHP client stub generator + + This generated PHP class definitions and client stubs for a valid thrift IDL definition + + Author(s): Mark Slee(mclee@facebook.com), Marc Kwiatkowski (marc@facebook.com) + + $Id: +""" +import time + +import os +import os.path +from string import Template +from thrift.parser import * +from thrift.generator import * + +HEADER_COMMENT = """ +""") + +PHP_SERVICES_HEADER = Template(HEADER_COMMENT+""" + +require_once THRIFT_ROOT.'/Thrift.php'; +require_once dirname(__FILE__).\'/${source}_types.php\'; + +""") + +PHP_SERVICES_FOOTER = Template(""" +?> +""") + +PHP_IMPL_HEADER = Template(HEADER_COMMENT+""" + +require_once THRIFT_ROOT.'/Thrift.php'; +require_once dirname(__FILE__).\'/${source}_types.php\'; + +""") + +PHP_IMPL_FOOTER = Template(""" +?> +""") + +def php_debug(arg): + print(arg) + +class Indenter(object): + def __init__(self, margin=4): + self.margin = ''.join([' ' for ix in range(margin)]) + self.level = 0 + + def __getitem__(self, level): + return ''.join([self.margin for ix in range(level)]) + + def __iadd__(self, value): + self.level = self.level + value + return self + + def __isub__(self, value): + self.level = self.level - value + return self + + def __call__(self): + return self.__getitem__(self.level) + +class Variable(object): + def __init__(self, ttype, name): + self.ttype = ttype + self.name = name + + def toDeclaration(self, new=False): + if not new: + defaultValue = defaultValueForType(self.ttype) + else: + defaultValue = newInstanceForType(self.ttype) + return self.name+" = "+defaultValue + +class Scope(object): + def __init__(self, parent=None): + self.index = 0 + self.vars = {} + self.parent = parent + self.childIndex = 0 + if self.parent: + self.level = self.parent.level + 1 + self.index = self.parent.childIndex + self.parent.childIndex+= 1 + self.suffix = self.parent.suffix+str(self.index) + else: + self.level = 0 + self.index = 0 + self.suffix = "" + + def declare(self, ttype, name): + if name in self.vars: + raise Exception, "Variable \""+name+"\" already declared in this scope." + result = Variable(ttype, "$_"+name+self.suffix) + self.vars[name] = result + return result + + def get(self, var): + if var.name in self.vars: + return self.vars[var.name] + elif self.parent: + return self.parent.get(var) + else: + raise Exception, "Variable \""+var.name+"\" does not exist in any enclosing scope" + +PHP_TYPE_PROTOCOL_MAP = { + BOOL_TYPE : "Bool", + STRING_TYPE: "String", + UTF7_TYPE: "String", + UTF8_TYPE: "Wstring", + BYTE_TYPE : "Byte", + I08_TYPE: "Byte", + I16_TYPE: "I16", + I32_TYPE: "I32", + I64_TYPE: "I64", + I16_TYPE: "I16", + I32_TYPE: "I32", + I64_TYPE: "I64", + U08_TYPE: "Byte", + U16_TYPE: "U16", + U32_TYPE: "U32", + U64_TYPE: "U64", + U16_TYPE: "U16", + U32_TYPE: "U32", + U64_TYPE: "U64", + FLOAT_TYPE: "Float", + DOUBLE_TYPE: "Double" + } + +PHP_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" +} + +class CodeGenerator(object): + def __init__(self, + scope=Scope(), + indent = Indenter()): + self.scope = scope + self.indent = indent + + def newScope(self): + self.scope = Scope(self.scope) + + def oldScope(self): + if not self.scope.parent: + raise Exception, "Unbalanced scope entry/exit" + + self.scope = self.scope.parent + + def declare(self, type, name, defaultValue=None): + tvar = self.scope.declare(type, name) + return tvar + + def toDeclaration(self, tvar, new=False): + if not new: + defaultValue = defaultValueForType(tvar.ttype) + else: + defaultValue = newInstanceForType(tvar.ttype) + + return self.indent()+tvar.name+" = "+defaultValue+";\n" + +class Reader(CodeGenerator): + def __init__(self, + iprot="$this->_iprot", + itrans="$this->_itrans", + scope=Scope(), + indent = Indenter()): + CodeGenerator.__init__(self, scope, indent) + self.iprot = iprot + self.itrans = itrans + + def toReadMessageBegin(self, messageNameVar, seqidVar): + return self.indent()+self.iprot+"->readMessageBegin("+self.itrans+", "+messageNameVar.name+", "+seqidVar.name+");\n" + + def toReadMessageEnd(self): + return self.indent()+self.iprot+"->readMessageEnd("+self.itrans+");\n" + + def toReadStructBegin(self, structNameVar): + return self.indent()+self.iprot+"->readStructBegin("+self.itrans+", "+structNameVar.name+");\n" + + def toReadStructEnd(self): + return self.indent()+self.iprot+"->readStructEnd("+self.itrans+");\n" + + def toReadMapBegin(self, keyTypeVar, valueTypeVar, sizeVar): + return self.indent()+self.iprot+"->readMapBegin("+self.itrans+", "+keyTypeVar.name+", "+valueTypeVar.name+", "+sizeVar.name+");\n" + + def toReadMapEnd(self): + return self.indent()+self.iprot+"->readMapEnd("+self.itrans+");\n" + + def toReadSetBegin(self, valueTypeVar, sizeVar): + return self.indent()+self.iprot+"->readSetBegin("+self.itrans+", "+valueTypeVar.name+", "+sizeVar.name+");\n" + + def toReadSetEnd(self): + return self.indent()+self.iprot+"->readSetEnd("+self.itrans+");\n" + + def toReadListBegin(self, valueTypeVar, sizeVar): + return self.indent()+self.iprot+"->readListBegin("+self.itrans+", "+valueTypeVar.name+", "+sizeVar.name+");\n" + + def toReadListEnd(self): + return self.indent()+self.iprot+"->readListEnd("+self.itrans+");\n" + + def toReadFieldBegin(self, fieldNameVar, fieldTypeVar, fieldIdVar): + return self.indent()+self.iprot+"->readFieldBegin("+self.itrans+", "+fieldNameVar.name+", "+fieldTypeVar.name+", "+fieldIdVar.name+");\n" + + def toReadFieldEnd(self): + return self.indent()+self.iprot+"->readFieldEnd("+self.itrans+");\n" + + def toSkipField(self, fieldTypeVar): + return self.indent()+self.iprot+"->skipField("+self.itrans+", "+fieldTypeVar.name+");\n" + + def toReadPrimitive(self, value, suffix): + return self.indent()+value+" = "+self.iprot+"->read"+suffix+"("+self.itrans+");\n" + + def toRead(self, value, ttype): + + if isinstance(ttype, PrimitiveType): + return self.toReadPrimitive(value, PHP_TYPE_PROTOCOL_MAP[ttype]) + + elif isinstance(ttype, StructType): + self.newScope() + result = self.toReadStruct(value, ttype) + self.oldScope() + return result + + elif isinstance(ttype, CollectionType): + self.newScope() + result = self.toReadCollection(value, ttype) + self.oldScope() + return result + + elif isinstance(ttype, EnumType): + return self.toReadPrimitive(value, PHP_TYPE_PROTOCOL_MAP[I32_TYPE]) + + elif isinstance(ttype, TypedefType): + return self.toRead(value, ttype.definitionType) + + else: + raise Exception, "Unsupported type "+str(type(ttype))+":"+str(ttype) + + def toReadStruct(self, value, struct): + + nameVar = self.declare(STRING_TYPE, "name") + fieldIdVar = self.declare(I16_TYPE, "fieldId") + fieldTypeVar = self.declare(U32_TYPE, "fieldType") + localVars = [nameVar, fieldTypeVar, fieldIdVar] + + result= string.join([self.toDeclaration(localVar) for localVar in localVars], "") + + result+= self.toReadStructBegin(nameVar) + + result+= self.indent()+"while(true) {\n" + + self.indent += 1 + + result+= self.toReadFieldBegin(nameVar, fieldTypeVar, fieldIdVar) + + result += self.indent()+"if("+fieldTypeVar.name+" == "+PHP_PROTOCOL_TSTOP+") {\n" + + self.indent+= 1 + + result+= self.indent()+"break;\n" + + self.indent-= 1 + + result+= self.indent()+"}\n" + + result+= self.indent()+"switch("+fieldIdVar.name+") {\n" + + self.indent+= 1 + + # Sort field list in order of increasing ids + + fieldList = [] + + fieldList+= struct.fieldList + + fieldList.sort(lambda a,b: a.id - b.id) + + for field in fieldList: + if isVoidType(field.type): + continue + + result+= self.indent()+"case "+str(field.id)+":\n" + + result+= self.toRead(value+"->"+field.name, field.type) + result+= self.indent()+"break;\n" + + result+= self.indent()+"default:\n" + result+= self.toSkipField(fieldTypeVar) + result+= self.indent()+"break;\n" + + self.indent-= 1 + + result+= self.indent()+"}\n" + + self.indent-= 1 + + result+= self.indent()+"}\n" + + result+= self.toReadStructEnd() + + result+= self.indent()+"return "+value+";\n" + + return result + + def toReadCollection(self, value, collection): + + isMap = isinstance(collection, MapType) + + sizeVar = self.declare(U32_TYPE, "size") + valueTypeVar = self.declare(U32_TYPE, "valueType") + elemVar = self.declare(collection.valueType, "elem") + localVars = [sizeVar, valueTypeVar, elemVar] + + if isMap: + keyTypeVar = self.declare(U32_TYPE, "keyType") + key = self.declare(collection.keyType, "key") + localVars+= [keyTypeVar, key] + + ix = self.declare(U32_TYPE, "ix") + + result= string.join([self.toDeclaration(localVar) for localVar in localVars], "") + + if isMap: + result+= self.toReadMapBegin(keyTypeVar, valueTypeVar, sizeVar) + elif isinstance(collection, ListType): + result+= self.toReadListBegin(valueTypeVar, sizeVar) + elif isinstance(collection, SetType): + result+= self.toReadSetBegin(valueTypeVar, sizeVar) + else: + raise Exception, "Unknown collection type "+str(collection) + + result+= self.indent()+"for("+ix.name+" = 0; "+ix.name+" < "+sizeVar.name+"; ++"+ix.name+") {\n" + + self.indent+= 1 + + if isMap: + result+= self.toRead(key.name, collection.keyType) + + result+= self.toRead(elemVar.name, collection.valueType) + + if isMap: + result+= self.indent()+value+"["+key.name+"] = "+elemVar.name+";\n" + else: + result+= self.indent()+value+"[] = "+elemVar.name+";\n" + + self.indent-= 1 + + result+= self.indent()+"}\n" + + if isMap: + result+= self.toReadMapEnd() + elif isinstance(collection, ListType): + result+= self.toReadListEnd() + elif isinstance(collection, SetType): + result+= self.toReadSetEnd() + else: + raise Exception, "Unknown collection type "+str(collection) + + return result + +class Writer(CodeGenerator): + def __init__(self, + oprot="$this->_oprot", + otrans="$this->_otrans", + scope=Scope(), + indent=Indenter()): + CodeGenerator.__init__(self, scope, indent) + self.oprot = oprot + self.otrans = otrans + + def toWriteMessageBegin(self, messageName, seqid): + return self.indent()+self.oprot+"->writeMessageBegin("+self.otrans+", "+messageName+", "+seqid+");\n" + + def toWriteMessageEnd(self): + return self.indent()+self.oprot+"->writeMessageEnd("+self.otrans+");\n" + + def toWriteStructBegin(self, structName): + return self.indent()+self.oprot+"->writeStructBegin("+self.otrans+", "+structName+");\n" + + def toWriteStructEnd(self): + return self.indent()+self.oprot+"->writeStructEnd("+self.otrans+");\n" + + def toWriteMapBegin(self, keyType, valueType, size): + return self.indent()+self.oprot+"->writeMapBegin("+self.otrans+", "+keyType+", "+valueType+", "+size+");\n" + + def toWriteMapEnd(self): + return self.indent()+self.oprot+"->writeMapEnd("+self.otrans+");\n" + + def toWriteSetBegin(self, valueType, size): + return self.indent()+self.oprot+"->writeSetBegin("+self.otrans+", "+valueType+", "+size+");\n" + + def toWriteSetEnd(self): + return self.indent()+self.oprot+"->writeSetEnd("+self.otrans+");\n" + + def toWriteListBegin(self, valueType, size): + return self.indent()+self.oprot+"->writeListBegin("+self.otrans+", "+valueType+", "+size+");\n" + + def toWriteListEnd(self): + return self.indent()+self.oprot+"->writeListEnd("+self.otrans+");\n" + + def toWriteFieldBegin(self, fieldName, fieldType, fieldId): + return self.indent()+self.oprot+"->writeFieldBegin("+self.otrans+", "+fieldName+", "+fieldType+", "+str(fieldId)+");\n" + + def toWriteFieldEnd(self): + return self.indent()+self.oprot+"->writeFieldEnd("+self.otrans+");\n" + + def toWriteFieldStop(self): + return self.indent()+self.oprot+"->writeFieldStop("+self.otrans+");\n" + + def toSkipField(self, fieldType): + return self.indent()+self.oprot+"->skipField("+self.otrans+", "+fieldType+");\n" + + def toWriteFlush(self): + return self.indent()+self.otrans+"->flush();\n" + + def toWritePrimitive(self, value, suffix): + return self.indent()+self.oprot+"->write"+suffix+"("+self.otrans+", "+value+");\n" + + def toWrite(self, value, ttype): + + if isinstance(ttype, PrimitiveType): + return self.toWritePrimitive(value, PHP_TYPE_PROTOCOL_MAP[ttype]) + + elif isinstance(ttype, StructType): + self.newScope() + result = self.toWriteStruct(value, ttype) + self.oldScope() + return result + + elif isinstance(ttype, CollectionType): + self.newScope() + result = self.toWriteCollection(value, ttype) + self.oldScope() + return result + + elif isinstance(ttype, EnumType): + return self.toWritePrimitive(value, PHP_TYPE_PROTOCOL_MAP[I32_TYPE]) + + elif isinstance(ttype, TypedefType): + return self.toWrite(value, ttype.definitionType) + + else: + raise Exception, "Unsupported type "+str(type(ttype))+":"+str(ttype) + + def toWriteStruct(self, value, struct): + + result=self.indent()+"{\n" + + self.indent+= 1 + + result+= self.toWriteStructBegin("\""+struct.name+"\"") + + for field in struct.fieldList: + if isVoidType(field.type): + continue + + result+= self.toWriteFieldBegin("\""+field.name+"\"", toWireType(field.type), field.id) + result+= self.toWrite(value+"->"+field.name, field.type) + result+= self.toWriteFieldEnd() + + result+= self.toWriteFieldStop() + + result+= self.toWriteStructEnd() + + self.indent-= 1 + + result+= self.indent()+"}\n" + + return result + + def toWriteCollection(self, value, collection): + + result=self.indent()+"{\n" + + self.indent+= 1 + + size = "count("+value+")" + + isMap = isinstance(collection, MapType) + + elemVar = self.declare(VOID_TYPE, "elem") + + if isMap: + keyVar = self.declare(VOID_TYPE, "key") + result+= self.toWriteMapBegin(toWireType(collection.keyType), toWireType(collection.valueType), size) + elif isinstance(collection, ListType): + result+= self.toWriteListBegin(toWireType(collection.valueType), size) + elif isinstance(collection, SetType): + result+= self.toWriteSetBegin(toWireType(collection.valueType), size) + else: + raise Exception, "Unknown collection type "+str(collection) + + if isMap: + + result+= self.indent()+"forech("+value+" as "+keyVar.name+" => "+elemVar.name+") {\n" + + else: + + result+= self.indent()+"forech("+value+" as "+elemVar.name+") {\n" + + self.indent+= 1 + + if isMap: + result+= self.toWrite(keyVar.name, collection.keyType) + + result+= self.toWrite(elemVar.name, collection.valueType) + + self.indent-= 1 + + result+= self.indent()+"}\n" + + if isMap: + result+= self.toWriteMapEnd() + elif isinstance(collection, ListType): + result+= self.toWriteListEnd() + elif isinstance(collection, SetType): + result+= self.toWriteSetEnd() + else: + raise Exception, "Unknown collection type "+str(collection) + + self.indent-= 1 + + result+= self.indent()+"}\n" + + return result + +class ClientFunctionGenerator(CodeGenerator): + def __init__(self, + scope=Scope(), + indent=Indenter()): + CodeGenerator.__init__(self, scope, indent) + self.reader = Reader(scope=scope, indent=indent) + self.writer = Writer(scope=scope, indent=indent) + + def __call__(self, function): + + result= self.indent()+"public function "+function.name+"("+string.join(["$arg"+str(ix) for ix in range(len(function.args()))], ", ")+") {\n" + + self.newScope() + + self.indent+= 1 + + result+= self.writer.toWriteMessageBegin("\""+function.name+"\"", "$this->seqid") + result+= self.indent()+"$this->_seqid++;\n" + + # Send the args struct + + result+= self.indent()+"{\n" + + self.newScope() + + self.indent+= 1 + + result+= self.writer.toWriteStructBegin("\""+function.argsStruct.name+"\"") + + for ix in range(len(function.argsStruct.fieldList)): + field = function.argsStruct.fieldList[ix] + if isVoidType(field.type): + continue + + result+= self.writer.toWriteFieldBegin("\""+field.name+"\"", toWireType(field.type), field.id) + result+= self.writer.toWrite("$arg"+str(ix), field.type) + result+= self.writer.toWriteFieldEnd() + + result+= self.writer.toWriteFieldStop() + + result+= self.writer.toWriteStructEnd() + + self.indent-= 1 + + self.oldScope() + + result+= self.indent()+"}\n" + + result+= self.writer.toWriteMessageEnd() + + result+= self.writer.toWriteFlush(); + + resultVar = self.declare(function.resultStruct, "result") + nameVar = self.declare(function.resultStruct, "name") + seqidVar = self.declare(function.resultStruct, "seqid") + + result+= self.toDeclaration(resultVar, True) + result+= self.toDeclaration(nameVar) + result+= self.toDeclaration(seqidVar) + + result+= self.reader.toReadMessageBegin(nameVar, seqidVar) + + result+= self.indent()+"{\n" + + self.newScope() + + self.indent+= 1 + + result+= self.reader.toRead(resultVar.name, function.resultStruct) + + self.indent-= 1 + + self.oldScope() + + result+= self.indent()+"}\n" + + self.indent-= 1 + + self.oldScope() + + result+= self.reader.toReadMessageEnd() + + result+= self.indent()+"}\n" + + return result + +class ClientServiceGenerator(CodeGenerator): + def __init__(self, + scope=Scope(), + indent=Indenter()): + CodeGenerator.__init__(self, scope, indent) + self.functionGenerator = ClientFunctionGenerator(scope, indent) + + def __call__(self, service): + + result= self.indent()+"class "+service.name+"Client extends "+service.name+"If {\n" + + self.indent+= 1 + + result+= self.indent()+"private $_itrans = null;\n" + result+= self.indent()+"private $_otrans = null;\n\n" + + result+= self.indent()+"private $_iprot = null;\n" + result+= self.indent()+"private $_oprot = null;\n\n" + result+= self.indent()+"private $_seqid = 0;\n\n" + + result+= self.indent()+"public function __construct() {\n" + self.indent+= 1 + + result+= self.indent()+"$argv = func_get_args();\n" + result+= self.indent()+"$argc = count($argv);\n" + result+= self.indent()+"if ($argc == 2) {;\n" + self.indent+= 1 + result+= self.indent()+"$this->_itrans = $this->_otrans = $argv[0];\n" + result+= self.indent()+"$this->_iprot = $this->_oprot = $argv[1];\n" + self.indent-= 1 + result+= self.indent()+"} else if($argc == 4) {\n" + self.indent+= 1 + result+= self.indent()+"$this->_itrans = $argv[0];\n" + result+= self.indent()+"$this->_otrans = $argv[1];\n" + result+= self.indent()+"$this->_iprot = $argv[2];\n" + result+= self.indent()+"$this->_oprot = $argv[3];\n" + self.indent-= 1 + result+= self.indent()+"}\n" + self.indent-= 1 + result+= self.indent()+"}\n\n" + + for function in service.functionList: + + result+= self.functionGenerator(function) + + self.indent-= 1 + result+= self.indent()+"}\n" + + return result + +PHP_PRIMITIVE_DEFAULT_MAP = { + VOID_TYPE : "", + BOOL_TYPE : "False", + STRING_TYPE: "\'\'", + UTF7_TYPE: "\'\'", + UTF8_TYPE: "\'\'", + UTF16_TYPE: "\'\'", + BYTE_TYPE : "0", + I08_TYPE: "0", + I16_TYPE: "0", + I32_TYPE: "0", + I64_TYPE: "0", + U08_TYPE: "0", + U16_TYPE: "0", + U32_TYPE: "0", + U64_TYPE: "0", + FLOAT_TYPE: "0.0", + DOUBLE_TYPE: "0.0" +} + +def toPHPNamespacePrefix(namespace): + """No namespaces in PHP""" + return "" + +def toPHPNamespaceSuffix(namespace): + """No namespaces in PHP""" + return "" + +def defaultValueForType(ttype, value=None): + """Returns the default literal value for a given type""" + + if value: + return value + elif isinstance(ttype, PrimitiveType): + return PHP_PRIMITIVE_DEFAULT_MAP[ttype] + elif isinstance(ttype, CollectionType): + return "array()" + elif isinstance(ttype, StructType): + return "null" + elif isinstance(ttype, EnumType): + return "0" + elif isinstance(ttype, TypedefType): + return defaultValueForType(toCanonicalType(ttype)) + else: + raise Exception, "Unknown type "+str(ttype) + +def newInstanceForType(ttype, value=None): + """Returns the default new instance for a given type""" + + if value: + return value + elif isinstance(ttype, StructType): + return "new "+ttype.name+"()" + else: + return defaultValueForType(ttype, value) + +def toPHPTypeDeclaration(ttype): + """ Converts the thrift IDL type to the corresponding PHP type. Note that if ttype is FieldType, this function +converts to type declaration followed by field name, i.e. \"string string_thing\"""" + + if isinstance(ttype, Field): + return "$"+ttype.name + if isinstance(ttype, Function): + return ttype.name+"("+string.join([toPHPTypeDeclaration(arg) for arg in ttype.args()], ", ")+")" + else: + return "" + +def toTypedefDefinition(typedef): + """Converts a thrift typedef to a PHP typdef definition.""" + + return "" + +def toEnumDefinition(enum): + """ Converts a thrift enum to a PHP enum """ + + result = "$GLOBALS[\'E_"+enum.name+"\'] = array(\n" + + result += string.join([" \'"+ed.name+"\' => "+str(ed.id)+",\n" for ed in enum.enumDefs], "") + + result+= ");\n\n" + + result += "final class "+enum.name+" {\n" + + result += string.join([" const "+ed.name+" = "+str(ed.id)+";\n" for ed in enum.enumDefs], "") + + result += "}\n" + + return result + +def toStructDefinition(struct): + """Converts a thrift struct to a PHP class""" + + result = "class "+struct.name+" {\n" + + # Create constructor defaults for primitive types + + # Field declarations + + result+= string.join([" public $"+field.name+" = "+defaultValueForType(field.type)+";\n" for field in struct.fieldList if not isVoidType(field.type)], "") + + # bring it on home + + result+= "}\n" + + return result + +PHP_DEFINITION_MAP = { + TypedefType : toTypedefDefinition, + EnumType : toEnumDefinition, + StructType : toStructDefinition, + ExceptionType : toStructDefinition, + Service : None + } + +def toDefinitions(definitions): + """Converts an arbitrary thrift grammatical unit definition to the corresponding PHP definition""" + + result = "" + + for definition in definitions: + + writer = PHP_DEFINITION_MAP[type(definition)] + + if writer: + result+= writer(definition)+"\n" + + return result + +PHP_THRIFT_NS = "facebook::thrift" + +PHP_INTERFACE_FUNCTION_DECLARATION = Template(""" public abstract function ${functionDeclaration}; +""") + +PHP_INTERFACE_DECLARATION = Template(""" +abstract class ${service}If { +${functionDeclarations}}; +""") + +def toServiceInterfaceDeclaration(service, debugp=None): + """Converts a thrift service definition into a C++ abstract base class""" + + functionDeclarations = string.join([PHP_INTERFACE_FUNCTION_DECLARATION.substitute(service=service.name, functionDeclaration=toPHPTypeDeclaration(function)) for function in service.functionList], "") + + return PHP_INTERFACE_DECLARATION.substitute(service=service.name, functionDeclarations=functionDeclarations) + +PHP_EXCEPTION = PHP_THRIFT_NS+"::Exception" + +PHP_SP = Template("boost::shared_ptr<${klass}> ") + + +PHP_PROTOCOL_TSTOP = "TType::STOP" +PHP_PROTOCOL_TTYPE = "TType::" +PHP_PROTOCOL_CALL = "TMessageType::T_CALL" +PHP_PROTOCOL_REPLY = "TMessageType::T_REPLY" + +PHP_TTYPE_MAP = { + STOP_TYPE : PHP_PROTOCOL_TTYPE+"STOP", + VOID_TYPE : PHP_PROTOCOL_TTYPE+"VOID", + BOOL_TYPE : PHP_PROTOCOL_TTYPE+"BOOL", + UTF7_TYPE : PHP_PROTOCOL_TTYPE+"UTF7", + UTF7_TYPE : PHP_PROTOCOL_TTYPE+"UTF7", + UTF8_TYPE : PHP_PROTOCOL_TTYPE+"UTF8", + UTF16_TYPE : PHP_PROTOCOL_TTYPE+"UTF16", + U08_TYPE : PHP_PROTOCOL_TTYPE+"U08", + I08_TYPE : PHP_PROTOCOL_TTYPE+"I08", + I16_TYPE : PHP_PROTOCOL_TTYPE+"I16", + I32_TYPE : PHP_PROTOCOL_TTYPE+"I32", + I64_TYPE : PHP_PROTOCOL_TTYPE+"I64", + U08_TYPE : PHP_PROTOCOL_TTYPE+"U08", + U16_TYPE : PHP_PROTOCOL_TTYPE+"U16", + U32_TYPE : PHP_PROTOCOL_TTYPE+"U32", + U64_TYPE : PHP_PROTOCOL_TTYPE+"U64", + FLOAT_TYPE : PHP_PROTOCOL_TTYPE+"FLOAT", + DOUBLE_TYPE : PHP_PROTOCOL_TTYPE+"DOUBLE", + StructType : PHP_PROTOCOL_TTYPE+"STRUCT", + ExceptionType : PHP_PROTOCOL_TTYPE+"STRUCT", + ListType : PHP_PROTOCOL_TTYPE+"LIST", + MapType : PHP_PROTOCOL_TTYPE+"MAP", + SetType : PHP_PROTOCOL_TTYPE+"SET" +} + + +def toWireType(ttype): + """Converts a thrift type to the corresponding wire type. This differs from toPHPTypeDeclaration in that it reduces typedefs +to their canonical form and converts enums to signedf 32 bit integers""" + + if isinstance(ttype, PrimitiveType): + return PHP_TTYPE_MAP[ttype] + + elif isinstance(ttype, EnumType): + return PHP_TTYPE_MAP[I32_TYPE] + + elif isinstance(ttype, TypedefType): + return toWireType(toCanonicalType(ttype)) + + elif isinstance(ttype, StructType) or isinstance(ttype, CollectionType): + return PHP_TTYPE_MAP[type(ttype)] + + else: + raise Exception, "No wire type for thrift type: "+str(ttype) + +def toGenDir(filename, suffix="php-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.php") + + 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)) + + phpFile = file(definitionHeader, "w") + + basename = toBasename(filename) + + phpFile.write(PHP_TYPES_HEADER.substitute(source=basename, date=time.ctime(), namespacePrefix=toPHPNamespacePrefix(program.namespace))) + + phpFile.write(toDefinitions(program.definitions)) + + phpFile.write(PHP_TYPES_FOOTER.substitute(source=basename, namespaceSuffix=toPHPNamespaceSuffix(program.namespace))) + + phpFile.close() + +def toToImplementationSourceName(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+".php") + + if debugp: + debugp("toToImplementationSourceName("+str(filename)+", "+str(genDir)+") => "+str(basename)) + + return result + +def writeImplementationSource(program, filename, genDir=None, debugp=None): + """Writes public thrift service interface, client stubs, and private types defined in a thrift program into a php library 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""" + + toImplementationSource = toToImplementationSourceName(filename, genDir) + + if debugp: + debugp("toImplementationSource: "+str(toImplementationSource)) + + phpFile = file(toImplementationSource, "w") + + basename = toBasename(filename) + + phpFile.write(PHP_SERVICES_HEADER.substitute(source=basename, date=time.ctime(), namespacePrefix=toPHPNamespacePrefix(program.namespace))) + + # Generate classes for function result "structs" + + privateStructs = [] + + for service in program.serviceMap.values(): + + phpFile.write(toServiceInterfaceDeclaration(service)) + + for function in service.functionList: + privateStructs.append(function.resultStruct) + + phpFile.write(toDefinitions(privateStructs)) + + serviceGenerator = ClientServiceGenerator() + + for service in program.serviceMap.values(): + + phpFile.write(serviceGenerator(service)) + + phpFile.write(PHP_SERVICES_FOOTER.substitute(source=basename, namespaceSuffix=toPHPNamespaceSuffix(program.namespace))) + + phpFile.close() + +class PHPGenerator(Generator): + + def __call__(self, program, filename, genDir=None, debugp=None): + + writeDefinitionHeader(program, filename, genDir, debugp) + + writeImplementationSource(program, filename, genDir, debugp)