# HG changeset patch # User bgo-osimis # Date 1549986568 -3600 # Node ID db093eb6b29d524724982cddb20e598c54bbcd82 # Parent 52549faf47ba2aa9eb5e421173b71734ccbfe00b Ongoing code generation tool diff -r 52549faf47ba -r db093eb6b29d Resources/CodeGeneration/stonegentool.py --- a/Resources/CodeGeneration/stonegentool.py Tue Feb 12 13:06:24 2019 +0100 +++ b/Resources/CodeGeneration/stonegentool.py Tue Feb 12 16:49:28 2019 +0100 @@ -1,4 +1,4 @@ -from __future__ import print_function +from typing import List,Dict import sys, json def LoadSchema(file_path : str): @@ -7,16 +7,22 @@ return obj class Type: - def __init__(self, canonicalTypeName:str, dependentTypes:str): + def __init__(self, canonicalTypeName:str, kind:str): + allowedTypeKinds = ["primitive","enum","struct","collection"] """dependent type is the list of canonical types this type depends on. For instance, vector> depends on map that, in turn, depends on string and int32 that, in turn, depend on nothing""" self.canonicalTypeName = canonicalTypeName + assert(kind in allowedTypeKinds) + + def setDependentTypes(self, dependentTypes:List[Type]) -> None: self.dependentTypes = dependentTypes - def getDependentTypes(self): + + def getDependentTypes(self) -> List[Type]: return self.dependentTypes - def getCppTypeName(self): + + def getCppTypeName(self) -> str: # C++: prefix map vector and string with std::map, std::vector and std::string # replace int32 by int32_t # replace float32 by float @@ -27,7 +33,8 @@ retVal : str = self.canonicalTypeName.replace("float32","float") retVal : str = self.canonicalTypeName.replace("float64","double") return retVal - def getTypeScriptTypeName(self): + + def getTypeScriptTypeName(self) -> str: # TS: replace vector with Array and map with Map # string remains string # replace int32 by number @@ -43,24 +50,121 @@ class Schema: def __init__(self, root_prefix : str, defined_types : List[Type]): - self.root_prefix : str = root_prefix - self.defined_types : str = defined_types + self.rootName : str = root_prefix + self.definedTypes : str = defined_types + +def CheckTypeSchema(definedType : Dict) -> None: + allowedDefinedTypeKinds = ["enum","struct"] + if not definedType.has_key('name'): + raise Exception("type lacks the 'name' key") + name = definedType['name'] + if not definedType.has_key('kind'): + raise Exception(f"type {name} lacks the 'kind' key") + kind = definedType['kind'] + if not (kind in allowedDefinedTypeKinds): + raise Exception(f"type {name} : kind {kind} is not allowed. It must be one of {allowedDefinedTypeKinds}") + + if not definedType.has_key('fields'): + raise Exception("type {name} lacks the 'fields' key") + + # generic check on all kinds of types + fields = definedType['fields'] + for field in fields: + fieldName = field['name'] + if not field.has_key('name'): + raise Exception("field in type {name} lacks the 'name' key") -def ProcessSchemaPaths(inputSchemas : list[str]) + # fields in struct must have types + if kind == 'struct': + for field in fields: + fieldName = field['name'] + if not field.has_key('type'): + raise Exception(f"field {fieldName} in type {name} lacks the 'type' key") + +def CheckSchemaSchema(schema : Dict) -> None: + if not schema.has_key('root_name'): + raise Exception("schema lacks the 'root_name' key") + if not schema.has_key('types'): + raise Exception("schema lacks the 'types' key") + for definedType in schema['types']: + CheckTypeSchema(definedType) + +def CreateAndCacheTypeObject(allTypes : Dict[str,Type], typeDict : Dict) -> None: + """This does not set the dependentTypes field""" + typeName : str = typeDict['name'] + if allTypes.has_key(typeName): + raise Exception(f'Type {name} is defined more than once!') + else: + typeObject = Type(typeName, typeDict['kind']) + allTypes[typeName] = typeObject -def ProcessSchemas(schemaPaths): - schemas = [] - for schemaPath in schemaPaths: - schemas.append(LoadSchema(schemaPath)) - return schemas +def ParseTemplateType(typeName) -> (bool,str,str): + """ If the type is a template like "SOMETHING>>", then + it returns (true,"SOMETHING","SOME>") + otherwise it returns (false,"","")""" + + + + +def GetCachedTypeObject(allTypes : Dict[str,Type], typeName : str) -> Type: + if allTypes.has_key('typeName') + return allTypes['typeName'] + # if we reach this point, it means the type is NOT a struct or an enum. + # it is another (non directly user-defined) type that we must parse and create + # let's do it + + +def ComputeTopTypeTree(allTypes : Dict[str,Type], typeDict : Dict) -> None: + """Now that all the types we might be referring to are known (either structs + and enums already created OR primitive types OR collections), we can create + the whole dependency tree""" + + if typeDict['kind'] == 'struct': + typeName : str = typeDict['name'] + typeObject : Type = allTypes[typeName] + typeFields : List[Dict] = typeDict['fields'] + dependentTypes = [] + for typeField : Dict in typeFields: + typeFieldObject = CreateTypeObject(allTypes, typeField['name']) + dependentTypes.append(typeFieldObject) + elif typeDict['kind'] == 'enum': + # the enum objects are already created and have no dependencies + else: + raise Exception("Internal error: ComputeTopTypeTree() can only be called on the defined types (enums and structs)") + + + + +def ProcessSchema(schema : dict) -> None: + CheckSchemaSchema(schema) + rootName : str = schema['root_name'] + definedTypes : list = schema['types'] + + # gather defined types + allTypes : Dict[str,Type] = {} + + # gather all defined types (first pass) : structs and enums + # we do not parse the subtypes now as to not mandate + # a particular declaration order + for definedType in definedTypes: + CreateAndCacheTypeObject(allTypes,definedType) + + # now we have objects for all the defined types + # let's build the whole type dependency tree + for definedType in definedTypes: + ComputeTypeTree(allTypes,definedType) + + + + if __name__ == '__main__': import argparse parser = argparse.ArgumentParser(usage = """stonegentool.py [-h] [-o OUT_DIR] [-v] input_schemas EXAMPLE: python command_gen.py -o "generated_files/" "mainSchema.json,App Specific Commands.json" """) - parser.add_argument("input_schemas", type=str, - help = "one or more schema files, as a comma-separated list of paths") + parser.add_argument("input_schema", type=str, + help = "path to the schema file") parser.add_argument("-o", "--out_dir", type=str, default=".", help = """path of the directory where the files will be generated. Default is current @@ -71,15 +175,14 @@ mode""") args = parser.parse_args() - inputSchemas = args.input_schemas.split(",") + inputSchemaFilename = args.input_schema outDir = args.out_dir - print("input schemas = " + str(inputSchemas)) + print("input schema = " + str(inputSchemaFilename)) print("out dir = " + str(outDir)) - ProcessSchemaPaths(inputSchemas) - - + ProcessSchema(LoadSchema(inputSchemaFilename)) + ################### ## ATTIC ##