diff Resources/CodeGeneration/stonegentool.py @ 491:8e7e151ef472 bgo-commands-codegen

Unit tests pass for enum generation
author bgo-osimis
date Wed, 20 Feb 2019 20:51:30 +0100
parents 6470248790db
children 6fbf2eae7c88
line wrap: on
line diff
--- a/Resources/CodeGeneration/stonegentool.py	Mon Feb 18 15:38:05 2019 +0100
+++ b/Resources/CodeGeneration/stonegentool.py	Wed Feb 20 20:51:30 2019 +0100
@@ -1,6 +1,8 @@
 import json
+import yaml
 import re
 import sys
+from jinja2 import Template
 from typing import (
     Any,
     Dict,
@@ -22,7 +24,34 @@
 12345678901234567890123456789012345678901234567890123456789012345678901234567890
 """
 
-class GeneratedCode:
+# see https://stackoverflow.com/a/2504457/2927708
+def trim(docstring):
+    if not docstring:
+        return ''
+    # Convert tabs to spaces (following the normal Python rules)
+    # and split into a list of lines:
+    lines = docstring.expandtabs().splitlines()
+    # Determine minimum indentation (first line doesn't count):
+    indent = sys.maxsize
+    for line in lines[1:]:
+        stripped = line.lstrip()
+        if stripped:
+            indent = min(indent, len(line) - len(stripped))
+    # Remove indentation (first line is special):
+    trimmed = [lines[0].strip()]
+    if indent < sys.maxsize:
+        for line in lines[1:]:
+            trimmed.append(line[indent:].rstrip())
+    # Strip off trailing and leading blank lines:
+    while trimmed and not trimmed[-1]:
+        trimmed.pop()
+    while trimmed and not trimmed[0]:
+        trimmed.pop(0)
+    # Return a single string:
+    return '\n'.join(trimmed)
+
+
+class GenCode:
     def __init__(self):
 
         # file-wide preamble (#include directives, comment...)
@@ -45,10 +74,6 @@
         raise NotImplementedError()
 
 
-raise Exception("""
-$$$$TODO check field names are unique
-""")
-
 class JsonHelpers:
     """A set of utilities to perform JSON operations"""
 
@@ -83,13 +108,13 @@
 def LoadSchemaFromJson(filePath: str):
     return JsonHelpers.loadJsonWithComments(filePath)
 
-def GetCppTypeNameFromCanonical(canonicalTypeName: str) -> str:
+def GetCppTypenameFromCanonical(canonicalTypename: str) -> str:
     # C++: prefix map vector and string with std::map, std::vector and
     # std::string
     # replace int32 by int32_t
     # replace float32 by float
     # replace float64 by double
-    retVal: str = canonicalTypeName
+    retVal: str = canonicalTypename
     retVal = retVal.replace("map", "std::map")
     retVal = retVal.replace("vector", "std::vector")
     retVal = retVal.replace("int32", "int32_t")
@@ -97,13 +122,13 @@
     retVal = retVal.replace("float64", "double")
     return retVal
 
-def GetTypeScriptTypeNameFromCanonical(canonicalTypeName: str) -> str:
+def GetTypeScriptTypenameFromCanonical(canonicalTypename: str) -> str:
     # TS: replace vector with Array and map with Map
     # string remains string
     # replace int32 by number
     # replace float32 by number
     # replace float64 by number
-    retVal: str = canonicalTypeName
+    retVal: str = canonicalTypename
     retVal = retVal.replace("map", "Map")
     retVal = retVal.replace("vector", "Array")
     retVal = retVal.replace("int32", "number")
@@ -112,54 +137,6 @@
     retVal = retVal.replace("bool", "boolean")
     return retVal
 
-# class Schema:
-#   def __init__(self, root_prefix : str, defined_types : List[Type]):
-#     self.rootName : str = root_prefix
-#     self.definedTypes : str = defined_types
-
-def CheckTypeSchema(definedType: Dict) -> None:
-    allowedDefinedTypeKinds = ["enum", "struct"]
-    if not "name" in definedType:
-        raise Exception("type lacks the 'name' key")
-    name = definedType["name"]
-    if not "kind" in definedType:
-        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. "
-            + f"It must be one of {allowedDefinedTypeKinds}"
-        )
-
-    if not "fields" in definedType:
-        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 "name" in field:
-            raise Exception("field in type {name} lacks the 'name' key")
-
-    # fields in struct must have types
-    if kind == "struct":
-        for field in fields:
-            fieldName = field["name"]
-            if not "type" in field:
-                raise Exception(
-                    f"field {fieldName} in type {name} " + "has no 'type' key"
-                )
-
-
-def CheckSchemaSchema(schema: Dict) -> None:
-    if not "root_name" in schema:
-        raise Exception("schema lacks the 'root_name' key")
-    if not "types" in schema:
-        raise Exception("schema lacks the 'types' key")
-    for definedType in schema["types"]:
-        CheckTypeSchema(definedType)
-
-
 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 
@@ -183,7 +160,7 @@
     return (sentence, "")
 
 
-def SplitListOfTypes(typeName: str) -> List[str]:
+def SplitListOfTypes(typename: str) -> List[str]:
     """Splits something like
   vector<string>,int32,map<string,map<string,int32>> 
   in:
@@ -195,7 +172,7 @@
   """
     stillStuffToEat: bool = True
     tokenList = []
-    restOfString = typeName
+    restOfString = typename
     while stillStuffToEat:
         firstToken, restOfString = EatToken(restOfString)
         tokenList.append(firstToken)
@@ -208,16 +185,16 @@
   re.compile(r"([a-zA-Z0-9_]*[a-zA-Z0-9_]*)<([a-zA-Z0-9_,:<>]+)>")
 
 
-def ParseTemplateType(typeName) -> Tuple[bool, str, List[str]]:
-    """ If the type is a template like "SOMETHING<SOME<THING,EL<SE>>>", then 
-  it returns (true,"SOMETHING","SOME<THING,EL<SE>>")
+def ParseTemplateType(typename) -> Tuple[bool, str, List[str]]:
+    """ If the type is a template like "SOMETHING<SOME<THING,EL<SE>>>", 
+    then it returns (true,"SOMETHING","SOME<THING,EL<SE>>")
   otherwise it returns (false,"","")"""
 
     # let's remove all whitespace from the type
     # split without argument uses any whitespace string as separator
     # (space, tab, newline, return or formfeed)
-    typeName = "".join(typeName.split())
-    matches = templateRegex.match(typeName)
+    typename = "".join(typename.split())
+    matches = templateRegex.match(typename)
     if matches == None:
         return (False, "", [])
     else:
@@ -228,234 +205,220 @@
         listOfDependentTypes = SplitListOfTypes(m.group(2))
         return (True, m.group(1), listOfDependentTypes)
 
-def ProcessTypeTree(
-    ancestors: List[str],
-    genOrderQueue: List[str],
-    structTypes: Dict[str, Dict],
-    typeName: str) -> None:
-  if typeName in ancestors:
+
+def ComputeOrderFromTypeTree(
+  ancestors: List[str], 
+  genOrder: List[str], 
+  shortTypename: str, schema: Dict[str, Dict]) -> None:
+
+  if shortTypename in ancestors:
     raise Exception(
       f"Cyclic dependency chain found: the last of {ancestors} "
-      + f"depends on {typeName} that is already in the list."
+      + f"depends on {shortTypename} 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 not (shortTypename in genOrder):
+    (isTemplate, _, dependentTypenames) = ParseTemplateType(shortTypename)
     if isTemplate:
-      for dependentTypeName in dependentTypeNames:
+      # if it is a template, it HAS dependent types... They can be 
+      # anything (primitive, collection, enum, structs..). 
+      # Let's process them!
+      for dependentTypename in dependentTypenames:
         # childAncestors = ancestors.copy()  NO TEMPLATE ANCESTOR!!!
-        # childAncestors.append(typeName)
-        ProcessTypeTree(
-            ancestors, genOrderQueue, structTypes, dependentTypeName
+        # childAncestors.append(typename)
+        ComputeOrderFromTypeTree(
+            ancestors, genOrder, dependentTypename, schema
         )
     else:
-      if typeName in structTypes:
-        ProcessStructType_DepthFirstRecursive(
-            genOrderQueue, structTypes, structTypes[typeName]
-        )
+      # If it is not template, we are only interested if it is a 
+      # dependency that we must take into account in the dep graph,
+      # i.e., a struct.
+      if IsShortStructType(shortTypename, schema):
+        struct:Dict = schema[GetLongTypename(shortTypename, schema)]
+        # The keys in the struct dict are the member names
+        # The values in the struct dict are the member types
+        for field in struct.keys():
+          # we fill the chain of dependent types (starting here)
+          ancestors.append(shortTypename)
+          ComputeOrderFromTypeTree(
+            ancestors, genOrder, struct[field], schema)
+          # don't forget to restore it!
+          ancestors.pop()
+        
+        # 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 shortTypename in genOrder:
+          genOrder.append(shortTypename)
 
-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)
+# +-----------------------+
+# |   Utility functions   |
+# +-----------------------+
+
+def IsShortStructType(typename: str, schema: Dict[str, Dict]) -> bool:
+  fullStructName = "struct " + typename
+  return (fullStructName in schema)
 
-def ProcessEnumerationType(outputStreams: GeneratedCode, typeDict: Dict) -> None:
+def GetLongTypename(shortTypename: str, schema: Dict):
+  if shortTypename.startswith("enum "):
+    raise RuntimeError('shortTypename.startswith("enum "):')
+  enumName: str = "enum " + shortTypename
+  isEnum = enumName in schema
 
-  # enumeration declarations
-  tsDeclText: StringIO = StringIO()
-  tsDeclText.write("enum %s\n" % typeDict["name"])
-  tsDeclText.write("{\n")
+  if shortTypename.startswith("struct "):
+    raise RuntimeError('shortTypename.startswith("struct "):')
+  structName: str = "struct " + shortTypename
+  isStruct = ("struct " + shortTypename) in schema
 
-  cppDeclText: StringIO = StringIO()
-  cppDeclText.write("enum %s\n" % typeDict["name"])
-  cppDeclText.write("{\n")
+  if isEnum and isStruct:
+    raise RuntimeError('Enums and structs cannot have the same name')
 
-  cppToStringText: StringIO = StringIO()
-  cppToStringText.write("enum %s\n" % typeDict["name"])
-  cppToStringText.write("{\n")
+  if isEnum:
+    return enumName
+  if isStruct:
+    return structName
+
+def IsTypename(fullName: str) -> bool:
+  return (fullName.startswith("enum ") or fullName.startswith("struct "))
+
+def IsEnumType(fullName: str) -> bool:
+  return fullName.startswith("enum ")
 
-  cppFromStringText: StringIO = StringIO()
-  cppFromStringText.write("enum %s\n" % typeDict["name"])
-  cppFromStringText.write("{\n")
+def IsStructType(fullName: str) -> bool:
+  return fullName.startswith("struct ")
 
-  for i in range(len(typeDict["fields"])):
-    field = typeDict["fields"][i]
-    name = field["name"]
+def GetShortTypename(fullTypename: str) -> str:
+  if fullTypename.startswith("struct "):
+    return fullTypename[7:] 
+  elif fullTypename.startswith("enum"):
+    return fullTypename[5:] 
+  else:
+    raise RuntimeError \
+      ('fullTypename should start with either "struct " or "enum "')
 
-    tsText.write("    %s" % name)
-    if i < len(typeDict["fields"]) - 1:
-        tsText.write(",")
-    tsText.write("\n")
+def CheckSchemaSchema(schema: Dict) -> None:
+  if not "rootName" in schema:
+      raise Exception("schema lacks the 'rootName' key")
+  for name in schema.keys():
+    if (not IsEnumType(name)) and (not IsStructType(name)) and \
+      (name != 'rootName'):
+      raise RuntimeError \
+        (f'Type "{name}" should start with "enum " or "struct "')
+
+  # TODO: check enum fields are unique (in whole namespace)
+  # TODO: check struct fields are unique (in each struct)
+
+# +-----------------------+
+# | Main processing logic |
+# +-----------------------+
+
+def ComputeRequiredDeclarationOrder(schema: dict) -> List[str]:
+  # sanity check
+  CheckSchemaSchema(schema)
 
-    cppText.write("    %s" % name)
-    if i < len(typeDict["fields"]) - 1:
-        cppText.write(",")
-    cppText.write("\n")
+  # we traverse the type dependency graph and we fill a queue with
+  # the required struct types, in a bottom-up fashion, to compute
+  # the declaration order
+  # The genOrder list contains the struct full names in the order
+  # where they must be defined.
+  # We do not care about the enums here... They do not depend upon
+  # anything and we'll handle them, in their original declaration 
+  # order, at the start
+  genOrder: List = []
+  for fullName in schema.keys():
+    if IsStructType(fullName):
+      realName: str = GetShortTypename(fullName)
+      ancestors: List[str] = []
+      ComputeOrderFromTypeTree(ancestors, genOrder, realName, schema)
+  return genOrder
 
-  tsText.write("};\n\n")
-  cppText.write("};\n\n")
-
-  outputStreams.tsEnums.write(tsText.getvalue())
-  outputStreams.cppEnums.write(cppText.getvalue())
+def ProcessSchema(schema: dict, genOrder: List[str]) -> Dict:
+  # sanity check
+  CheckSchemaSchema(schema)
 
-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  
-      }
-    """
+  # let's doctor the schema to clean it up a bit
+  # order DOES NOT matter for enums, even though it's a list
+  enums: List[Dict] = []
+  for fullName in schema.keys():
+    if IsEnumType(fullName):
+      # convert "enum Toto" to "Toto"
+      typename:str = GetShortTypename(fullName)
+      enum = {}
+      enum['name'] = typename
+      assert(type(schema[fullName]) == list)
+      enum['fields'] = schema[fullName] # must be a list
+      enums.append(enum)
 
-
-
-def ProcessStructType(outputStreams: GeneratedCode, typeDict) -> None:
-  tsText: StringIO = StringIO()
-  cppText: StringIO = StringIO()
+  # now that the order has been established, we actually store\
+  # the structs in the correct order
+  # the structs are like:
+  # example = [
+  #   {
+  #     "name": "Message1",
+  #     "fields": {
+  #       "someMember":"int32",
+  #       "someOtherMember":"vector<string>"
+  #     }
+  #   },
+  #   { 
+  #     "name": "Message2",
+  #     "fields": {
+  #       "someMember":"int32",
+  #       "someOtherMember22":"vector<Message1>"
+  #     }
+  #   }
+  # ]
 
-  tsText.write("class %s\n" % typeDict["name"])
-  tsText.write("{\n")
-
-  cppText.write("struct %s\n" % typeDict["name"])
-  cppText.write("{\n")
-
-  """
+  structs: List[Dict] = []
+  for i in range(len(genOrder)):
+    # this is already the short name
+    typename = genOrder[i]
+    fieldDict = schema["struct " + typename]
+    struct = {}
+    struct['name'] = typename
+    struct['fields'] = fieldDict
+    structs.append(struct)
   
-  GenerateSerializationCode(typeName,valueName)
+  templatingDict = {}
+  templatingDict['enums'] = enums
+  templatingDict['structs'] = structs
+  templatingDict['rootName'] = schema['rootName']
 
-  primitives:
-  -----------
-  int
-    jsonValue val(objectInt);
-    val.setValue("$name")
-    parent.add(("$name",$name)
-  double
-    ...
-  string
-    ...
-
-  collections:
-  -----------
-  dict { }
-
-
-
-
-
-  serializeValue()
-  """
+  return templatingDict
 
-  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"])
-    cppText.write("    %s %s;\n" % (cppType, name))
-
-  tsText.write("};\n\n")
-  cppText.write("};\n\n")
+# +-----------------------+
+# |    Write to files     |
+# +-----------------------+
 
-  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
-#include <cstdint>
-#include <string>
-#include <vector>
-#include <map>
-
-""" % (time.ctime(), rootName))
+# def WriteStreamsToFiles(rootName: str, genc: Dict[str, StringIO]) \
+#   -> None:
+#   pass
 
-  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"]
-
-  # 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] = {}
-
-  outputStreams: GeneratedCode = GeneratedCode()
-
-  WritePreambles(rootName, outputStreams)
+def LoadSchema(fn):
+  with open(fn, 'rb') as f:
+    schema = yaml.load(f)
+  return schema
 
-  # 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
+def GetTemplatingDictFromSchemaFilename(fn):
+  obj = LoadSchema(fn)
+  genOrder: str = ComputeRequiredDeclarationOrder(obj)
+  templatingDict = ProcessSchema(obj, genOrder)
+  return templatingDict
 
-    # 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
-        )
-
-    for i in range(len(genOrderQueue)):
-      typeName = genOrderQueue[i]
-      typeDict = structTypes[typeName]
-      ProcessStructType(outputStreams, typeDict)
-
-  return (rootName, outputStreams, genOrderQueue)
-
-
-def WriteStreamsToFiles(rootName: str, outputStreams: Dict[str, StringIO]) -> None:
-  pass
+# +-----------------------+
+# |      ENTRY POINT      |
+# +-----------------------+
 
 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" """
+    usage="""stonegentool.py [-h] [-o OUT_DIR] [-v] input_schema
+    EXAMPLE: python stonegentool.py -o "generated_files/" """
+      + """ "mainSchema.yaml,App Specific Commands.json" """
   )
-  parser.add_argument("input_schema", type=str, help="path to the schema file")
+  parser.add_argument("input_schema", type=str, \
+    help="path to the schema file")
   parser.add_argument(
     "-o",
     "--out_dir",
@@ -479,6 +442,219 @@
   inputSchemaFilename = args.input_schema
   outDir = args.out_dir
 
-  (rootName, outputStreams, _) = ProcessSchema(LoadSchema(inputSchemaFilename))
-  WriteStreamsToFiles(rootName, outputStreams)
+  schema: Dict = LoadSchema(inputSchemaFilename)
+  genOrder: List[str] = ComputeRequiredDeclarationOrder(schema)
+  processedSchema: Dict = ProcessSchema(schema,genOrder)
+
+
+# def GenEnumDecl(genc: GenCode, fullName: str, schema: Dict) -> None:
+#   """Writes the enumerations in genc"""
+#   enumDict:Dict=schema[fullName]
+#   # jinja2 template
+#   j2cppEnum = Template(trim(
+#   """ {{fullName}}
+#       {
+#           {% for key in enumDict.keys()%}
+#           {{key}},
+#           {%endfor%}
+#       };
+#   """))
+#   j2cppEnumR = j2cppEnum.render(locals())
+#   genc.cppEnums.write(j2cppEnumR)
+
+#   j2tsEnum = Template(trim(
+#   """ export {{fullName}}
+#       {
+#           {% for key in enumDict.keys()%}
+#           {{key}},
+#           {%endfor%}
+#       };
+#   """))
+#   j2cppEnumR = j2cppEnum.render(locals())
+#   genc.tsEnums.write(j2cppEnumR)
+
+  
+
+# def GetSerializationCode(typename: str,valueName: str, tempName: str)
+#   if IsPrimitiveType(typename) or IsTemplateCollection(typename):
+#     # no need to write code for the primitive types or collections.
+#     # It is handled in C++ by the template functions and in TS by 
+#     # the JSON.stringify code.
+#   elif IsStructType(typename):
+#     pass
+
+# def GenStructTypeDeclAndSerialize(genc: GenCode, type, schema) -> None:
+#   ######
+#   # CPP
+#   ######
+#   sampleCpp = """  struct Message1
+#     {
+#       int32_t a;
+#       std::string b;
+#       EnumMonth0 c;
+#       bool d;
+#     };
+
+#     Json::Value StoneSerialize(const Message1& value)
+#     {
+#       Json::Value result(Json::objectValue);
+#       result["a"] = StoneSerialize(value.a);
+#       result["b"] = StoneSerialize(value.b);
+#       result["c"] = StoneSerialize(value.c);
+#       result["d"] = StoneSerialize(value.d);
+#       return result;
+#     }
+#     """
+    
+
+#   ######
+#   # TS
+#   ######
+#   sampleTs = """
+#       {
+#         export class Message1 {
+#           a: number;
+#           b: string;
+#           c: EnumMonth0;
+#           d: boolean;
+#           public StoneSerialize(): string {
+#             let container: object = {};
+#             container['type'] = 'Message1';
+#             container['value'] = this;
+#             return JSON.stringify(container);
+#           }
+#         };
+#       }
+#     """
+
+
+
+
+#   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("{\n")
+
+#   """
+  
+#   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"])
+#     cppText.write("    %s %s;\n" % (cppType, name))
+
+#   tsText.write("};\n\n")
+#   cppText.write("};\n\n")
+
+#   genc.tsStructs.write(tsText.getvalue())
+#   genc.cppStructs.write(cppText.getvalue())
+
+
+# def GenerateCodeFromTsTemplate(genc)
+
+
+# +-----------------------+
+# |    CODE GENERATION    |
+# +-----------------------+
+
+# def GenPreambles(rootName: str, genc: GenCode) -> None:
+#   cppPreambleT = Template(trim(
+#     """// autogenerated by stonegentool on {{time.ctime()}} 
+#     // for module {{rootName}}
+#     #include <cstdint>
+#     #include <string>
+#     #include <vector>
+#     #include <map>
+#     namespace {{rootName}}
+#     {
+#       Json::Value StoneSerialize(int32_t value)
+#       {
+#         Json::Value result(value);
+#         return result;
+#       }
+#       Json::Value StoneSerialize(double value)
+#       {
+#         Json::Value result(value);
+#         return result;
+#       }
+#       Json::Value StoneSerialize(bool value)
+#       {
+#         Json::Value result(value);
+#         return result;
+#       }
+#       Json::Value StoneSerialize(const std::string& value)
+#       {
+#         // the following is better than 
+#         Json::Value result(value.data(),value.data()+value.size());
+#         return result;
+#       }
+#       template<typename T>
+#       Json::Value StoneSerialize(const std::map<std::string,T>& value)
+#       {
+#         Json::Value result(Json::objectValue);
+
+#         for (std::map<std::string, T>::const_iterator it = value.cbegin();
+#           it != value.cend(); ++it)
+#         {
+#           // it->first it->second
+#           result[it->first] = StoneSerialize(it->second);
+#         }
+#         return result;
+#       }
+#       template<typename T>
+#       Json::Value StoneSerialize(const std::vector<T>& value)
+#       {
+#         Json::Value result(Json::arrayValue);
+#         for (size_t i = 0; i < value.size(); ++i)
+#         {
+#           result.append(StoneSerialize(value[i]));
+#         }
+#         return result;
+#       }
+#     """
+#   cppPreambleR = cppPreambleT.render(locals())
+#   genc.cppPreamble.write(cppPreambleR)
+  
+#   tsPreambleT = Template(trim(
+#     """// autogenerated by stonegentool on {{time.ctime()}} 
+#     // for module {{rootName}}
+    
+#     namespace {{rootName}}
+#     {
+#     """
+#   tsPreambleR = tsPreambleT.render(locals())
+#   genc.tsPreamble.write(tsPreambleR)
+
+# def ComputeOrder_ProcessStruct( \
+#   genOrder: List[str], name:str, schema: Dict[str, str]) -> None:
+#   # let's generate the code according to the
+#   struct = schema[name]
+
+#   if not IsStructType(name):
+#     raise Exception(f'{typename} should start with "struct "')