Mercurial > hg > orthanc-stone
changeset 489:f6b7f113cf27 bgo-commands-codegen
Ongoing work on code generation
author | bgo-osimis |
---|---|
date | Mon, 18 Feb 2019 07:46:59 +0100 |
parents | 8e40355a172b |
children | 6470248790db |
files | Resources/CodeGeneration/README.md Resources/CodeGeneration/stonegentool.py Resources/CodeGeneration/stonegentool_test.py |
diffstat | 3 files changed, 230 insertions(+), 162 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CodeGeneration/README.md Mon Feb 18 07:46:59 2019 +0100 @@ -0,0 +1,24 @@ +class TestMessage { + s1: string; + s2: Array<string>; + s3: Array<Array<string>>; + s4: Map<string, number>; + s5: Map<number, Array<string>>; + s6: Color; + s7: boolean; +} + +--> + +{"s2":["toto","toto2","toto3"],"s4":{"toto":1999,"tatata":1999},"s6":0,"s7":true} + +(absent fields weren't specified) + + +type:B +value:{"someAs":[{...},{},{}]}......................... +Deserialize + +jsonValue + +
--- a/Resources/CodeGeneration/stonegentool.py Fri Feb 15 14:30:26 2019 +0100 +++ b/Resources/CodeGeneration/stonegentool.py Mon Feb 18 07:46:59 2019 +0100 @@ -2,18 +2,18 @@ import re import sys from typing import ( - Any, - Dict, - Generator, - Iterable, - Iterator, - List, - Match, - Optional, - Tuple, - Union, - cast, - ) + Any, + Dict, + Generator, + Iterable, + Iterator, + List, + Match, + Optional, + Tuple, + Union, + cast, +) from io import StringIO import time @@ -22,22 +22,33 @@ 12345678901234567890123456789012345678901234567890123456789012345678901234567890 """ + class GeneratedCode: - def __init__(self): - self.cppPreamble = StringIO() # file-wide preamble (#include directives, comment...) - self.cppEnums = StringIO() - self.cppStructs = StringIO() - self.cppDispatcher = StringIO() - self.cppHandler = StringIO() + def __init__(self): + + # file-wide preamble (#include directives, comment...) + self.cppPreamble = StringIO() + + self.cppEnums = StringIO() + self.cppStructs = StringIO() + self.cppDispatcher = StringIO() + self.cppHandler = StringIO() - self.tsPreamble = StringIO() # file-wide preamble (module directives, comment...) - self.tsEnums = StringIO() - self.tsStructs = StringIO() - self.tsDispatcher = StringIO() - self.tsHandler = StringIO() + # file-wide preamble (module directives, comment...) + self.tsPreamble = StringIO() + + self.tsEnums = StringIO() + self.tsStructs = StringIO() + self.tsDispatcher = StringIO() + self.tsHandler = StringIO() - def FlattenToFiles(self,outputDir: str): - raise NotImplementedError() + def FlattenToFiles(self, outputDir: str): + raise NotImplementedError() + + +raise Exception(""" +$$$$TODO check field names are unique +""") class JsonHelpers: """A set of utilities to perform JSON operations""" @@ -105,6 +116,7 @@ retVal = retVal.replace("float64", "double") return retVal + def GetTypeScriptTypeNameFromCanonical(canonicalTypeName: str) -> str: # TS: replace vector with Array and map with Map # string remains string @@ -170,16 +182,6 @@ CheckTypeSchema(definedType) -# def CreateAndCacheTypeObject(allTypes : Dict[str,Type], typeDict : Dict) -> None: -# """This does not set the dependentTypes field""" -# typeName : str = typeDict['name'] -# if typeName in allTypes: -# raise Exception(f'Type {typeName} is defined more than once!') -# else: -# typeObject = Type(typeName, typeDict['kind']) -# allTypes[typeName] = typeObject - - def EatToken(sentence: str) -> Tuple[str, str]: """splits "A,B,C" into "A" and "B,C" where A, B and C are type names (including templates) like "int32", "TotoTutu", or @@ -241,9 +243,9 @@ return (False, "", []) else: m = cast(Match[str], matches) - assert(len(m.groups()) == 2) - # we need to split with the commas that are outside of the defined types - # simply splitting at commas won't work + assert len(m.groups()) == 2 + # we need to split with the commas that are outside of the + # defined types. Simply splitting at commas won't work listOfDependentTypes = SplitListOfTypes(m.group(2)) return (True, m.group(1), listOfDependentTypes) @@ -268,156 +270,207 @@ structTypes: Dict[str, Dict], typeName: str, ) -> None: - if typeName in ancestors: - raise Exception( - f"Cyclic dependency chain found: the last of {ancestors} " - + f"depends on {typeName} that is already in the list." + if typeName in ancestors: + raise Exception( + f"Cyclic dependency chain found: the last of {ancestors} " + + f"depends on {typeName} that is already in the list." + ) + + if not (typeName in genOrderQueue): + # 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! + (isTemplate, _, dependentTypeNames) = ParseTemplateType(typeName) + if isTemplate: + for dependentTypeName in dependentTypeNames: + # childAncestors = ancestors.copy() NO TEMPLATE ANCESTOR!!! + # childAncestors.append(typeName) + ProcessTypeTree( + ancestors, genOrderQueue, structTypes, dependentTypeName + ) + else: + if typeName in structTypes: + ProcessStructType_DepthFirstRecursive( + genOrderQueue, structTypes, structTypes[typeName] ) - if not (typeName in genOrderQueue): - # 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! - (isTemplate, _, dependentTypeNames) = ParseTemplateType(typeName) - if isTemplate: - for dependentTypeName in dependentTypeNames: - # childAncestors = ancestors.copy() NO TEMPLATE ANCESTOR!!! - # childAncestors.append(typeName) - ProcessTypeTree( - ancestors, genOrderQueue, structTypes, dependentTypeName - ) - else: - if typeName in structTypes: - ProcessStructType_DepthFirstRecursive( - genOrderQueue, structTypes, structTypes[typeName] - ) +def ProcessStructType_DepthFirstRecursive(genOrderQueue: List[str], \ + structTypes: Dict[str, Dict], typeDict: Dict) -> None: + # let's generate the code according to the + typeName: str = typeDict["name"] + if typeDict["kind"] != "struct": + raise Exception( + f"Unexpected kind '{typeDict['kind']}' for " + "type '{typeName}'" + ) + typeFields: List[Dict] = typeDict["fields"] + for typeField in typeFields: + ancestors = [typeName] + ProcessTypeTree(ancestors, genOrderQueue, structTypes, typeField["type"]) + # now we're pretty sure our dependencies have been processed, + # we can start marking our code for generation (it might already have + # been done if someone referenced us earlier) + if not typeName in genOrderQueue: + genOrderQueue.append(typeName) -def ProcessStructType_DepthFirstRecursive( - genOrderQueue: List[str], structTypes: Dict[str, Dict], typeDict: Dict) -> None: - # let's generate the code according to the - typeName: str = typeDict["name"] - if typeDict["kind"] != "struct": - raise Exception( - f"Unexpected kind '{typeDict['kind']}' for " + "type '{typeName}'" - ) - typeFields: List[Dict] = typeDict["fields"] - for typeField in typeFields: - ancestors = [typeName] - ProcessTypeTree(ancestors, genOrderQueue, structTypes, typeField["type"]) - # now we're pretty sure our dependencies have been processed, - # we can start marking our code for generation (it might already have - # been done if someone referenced us earlier) - if not typeName in genOrderQueue: - genOrderQueue.append(typeName) +def ProcessEnumerationType(outputStreams: GeneratedCode, typeDict: Dict) -> None: + tsText: StringIO = StringIO() + cppText: StringIO = StringIO() -def ProcessEnumerationType( - outputStreams: GeneratedCode, typeDict: Dict) -> None: - tsText : StringIO = StringIO() - cppText : StringIO = StringIO() - - tsText.write("enum %s\n" % typeDict['name']) + tsText.write("enum %s\n" % typeDict["name"]) tsText.write("{\n") - cppText.write("enum %s\n" % typeDict['name']) + cppText.write("enum %s\n" % typeDict["name"]) cppText.write("{\n") - for i in range(len(typeDict['fields'])): - field = typeDict['fields'][i] - name = field['name'] + for i in range(len(typeDict["fields"])): + field = typeDict["fields"][i] + name = field["name"] tsText.write(" %s" % name) - if i < len(typeDict['fields'])-1: - tsText.write(",") + if i < len(typeDict["fields"]) - 1: + tsText.write(",") tsText.write("\n") cppText.write(" %s" % name) - if i < len(typeDict['fields'])-1: - cppText.write(",") + if i < len(typeDict["fields"]) - 1: + cppText.write(",") cppText.write("\n") - - tsText.write("};\n\n") - cppText.write("};\n\n") + + tsText.write("};\n\n") + cppText.write("};\n\n") outputStreams.tsEnums.write(tsText.getvalue()) outputStreams.cppEnums.write(cppText.getvalue()) -def ProcessStructType( - outputStreams: GeneratedCode, typeDict) -> None: - tsText : StringIO = StringIO() - cppText : StringIO = StringIO() - - tsText.write("class %s\n" % typeDict['name']) +def GetSerializationCode(typeName: str,valueName: str, tempName: str) + if IsPrimitiveType(typeName): + """ + json::Value val(objectTypeInt...) + val.setValue(valueName) <--- val + """ + elif IsArray(typeName) + """ + { + json::Value val(objectTypeArray...) + for(size_t i = 0; i < {fieldName}.size(); ++i) + { + json::Value val(objectTypeArray...) + } + val.setValue(valueName) + // <--- the calling code will insert collection/field writing here, + // like "parent.set("{fieldName}",val) or parent.append(val) + $collectValue + } + """ + + + + +def ProcessStructType(outputStreams: GeneratedCode, typeDict) -> None: + tsText: StringIO = StringIO() + cppText: StringIO = StringIO() + + tsText.write("class %s\n" % typeDict["name"]) tsText.write("{\n") - cppText.write("struct %s\n" % typeDict['name']) + cppText.write("struct %s\n" % typeDict["name"]) cppText.write("{\n") - for i in range(len(typeDict['fields'])): - field = typeDict['fields'][i] - name = field['name'] - tsType = GetTypeScriptTypeNameFromCanonical(field['type']) + """ + + GenerateSerializationCode(typeName,valueName) + + primitives: + ----------- + int + jsonValue val(objectInt); + val.setValue("$name") + parent.add(("$name",$name) + double + ... + string + ... + + collections: + ----------- + dict { } + + + + + + serializeValue() + """ + + for i in range(len(typeDict["fields"])): + field = typeDict["fields"][i] + name = field["name"] + tsType = GetTypeScriptTypeNameFromCanonical(field["type"]) tsText.write(" public %s %s;\n" % (tsType, name)) - cppType = GetCppTypeNameFromCanonical(field['type']) + cppType = GetCppTypeNameFromCanonical(field["type"]) cppText.write(" %s %s;\n" % (cppType, name)) - + tsText.write("};\n\n") cppText.write("};\n\n") outputStreams.tsStructs.write(tsText.getvalue()) outputStreams.cppStructs.write(cppText.getvalue()) + def WritePreambles(rootName: str, outputStreams: GeneratedCode) -> None: - outputStreams.cppPreamble.write("""// autogenerated by stonegentool on %s for module %s + outputStreams.cppPreamble.write( +"""// autogenerated by stonegentool on %s for module %s #include <cstdint> #include <string> #include <vector> #include <map> -""" % (time.ctime(),rootName)) +""" % (time.ctime(), rootName)) - outputStreams.tsPreamble.write("""// autogenerated by stonegentool on %s for module %s -""" % (time.ctime(),rootName)) + outputStreams.tsPreamble.write( + """// autogenerated by stonegentool on %s for module %s +""" % (time.ctime(), rootName)) + def ProcessSchema(schema: dict) -> Tuple[str, GeneratedCode, List[str]]: - CheckSchemaSchema(schema) - rootName: str = schema["root_name"] - definedTypes: list = schema["types"] + CheckSchemaSchema(schema) + rootName: str = schema["root_name"] + definedTypes: list = schema["types"] - # this will be filled with the generation queue. That is, the type - # names in the order where they must be defined. - genOrderQueue: List = [] + # this will be filled with the generation queue. That is, the type + # names in the order where they must be defined. + genOrderQueue: List = [] - # the struct names are mapped to their JSON dictionary - structTypes: Dict[str, Dict] = {} + # the struct names are mapped to their JSON dictionary + structTypes: Dict[str, Dict] = {} - outputStreams : GeneratedCode = GeneratedCode() + outputStreams: GeneratedCode = GeneratedCode() - WritePreambles(rootName, outputStreams) + WritePreambles(rootName, outputStreams) - # the order here is the generation order - for definedType in definedTypes: - if definedType["kind"] == "enum": - ProcessEnumerationType(outputStreams, definedType) + # the order here is the generation order + for definedType in definedTypes: + if definedType["kind"] == "enum": + ProcessEnumerationType(outputStreams, definedType) - for definedType in definedTypes: - if definedType["kind"] == "struct": - structTypes[definedType["name"]] = definedType + for definedType in definedTypes: + if definedType["kind"] == "struct": + structTypes[definedType["name"]] = definedType # the order here is NOT the generation order: the types # will be processed according to their dependency graph for definedType in definedTypes: - if definedType["kind"] == "struct": - ProcessStructType_DepthFirstRecursive( - genOrderQueue, structTypes, definedType - ) + if definedType["kind"] == "struct": + ProcessStructType_DepthFirstRecursive( + genOrderQueue, structTypes, definedType + ) for i in range(len(genOrderQueue)): - typeName = genOrderQueue[i] - typeDict = structTypes[typeName] - ProcessStructType(outputStreams, typeDict) + typeName = genOrderQueue[i] + typeDict = structTypes[typeName] + ProcessStructType(outputStreams, typeDict) - return (rootName, outputStreams, genOrderQueue) - + return (rootName, outputStreams, genOrderQueue) def WriteStreamsToFiles(rootName: str, outputStreams: Dict[str, StringIO]) -> None: @@ -427,28 +480,28 @@ import argparse parser = argparse.ArgumentParser( - usage="""stonegentool.py [-h] [-o OUT_DIR] [-v] input_schemas - EXAMPLE: python command_gen.py -o "generated_files/" """ + 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_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 - working folder""", + "-o", + "--out_dir", + type=str, + default=".", + help="""path of the directory where the files + will be generated. Default is current + working folder""", ) parser.add_argument( - "-v", - "--verbosity", - action="count", - default=0, - help="""increase output verbosity (0 == errors - only, 1 == some verbosity, 2 == nerd - mode""", + "-v", + "--verbosity", + action="count", + default=0, + help="""increase output verbosity (0 == errors + only, 1 == some verbosity, 2 == nerd + mode""", ) args = parser.parse_args()
--- a/Resources/CodeGeneration/stonegentool_test.py Fri Feb 15 14:30:26 2019 +0100 +++ b/Resources/CodeGeneration/stonegentool_test.py Mon Feb 18 07:46:59 2019 +0100 @@ -102,7 +102,7 @@ self.assertEqual("B",genOrderQueue[1]) self.assertEqual("C",genOrderQueue[2]) - def test_GenerateTypeScriptEnumeration(self): + def test_GeneratePreambleEnumerationAndStructs(self): fn = os.path.join(os.path.dirname(__file__), 'test', 'test1.jsonc') obj = LoadSchema(fn) (_,outputStreams,_) = ProcessSchema(obj) @@ -202,15 +202,6 @@ self.assertEqual(cppEnumsRef,outputStreams.cppEnums.getvalue()) self.assertEqual(cppStructsRef,outputStreams.cppStructs.getvalue()) - def test_GenerateCppEnumeration(self): - pass - - def test_GenerateTypeScriptClasses(self): - pass - - def test_GenerateCppClasses(self): - pass - def test_GenerateTypeScriptHandlerInterface(self): pass