changeset 513:dea3787a8f4b bgo-commands-codegen

Added support for struct metadata, to disable/enable handler support in Typescript or C++
author Benjamin Golinvaux <bgo@osimis.io>
date Mon, 11 Mar 2019 13:02:46 +0100
parents 97a16b694321
children 381144d2434f
files Resources/CodeGeneration/stonegentool.py Resources/CodeGeneration/template.in.h Resources/CodeGeneration/template.in.ts
diffstat 3 files changed, 80 insertions(+), 274 deletions(-) [+]
line wrap: on
line diff
--- a/Resources/CodeGeneration/stonegentool.py	Wed Mar 06 18:26:35 2019 +0100
+++ b/Resources/CodeGeneration/stonegentool.py	Mon Mar 11 13:02:46 2019 +0100
@@ -39,30 +39,6 @@
     # Return a single string:
     return '\n'.join(trimmed)
 
-
-class GenCode:
-    def __init__(self):
-
-        # file-wide preamble (#include directives, comment...)
-        self.cppPreamble = StringIO()
-
-        self.cppEnums = StringIO()
-        self.cppStructs = StringIO()
-        self.cppDispatcher = StringIO()
-        self.cppHandler = 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):
-        raise NotImplementedError()
-
-
 class JsonHelpers:
     """A set of utilities to perform JSON operations"""
 
@@ -234,6 +210,9 @@
         listOfDependentTypes = SplitListOfTypes(m.group(2))
         return (True, m.group(1), listOfDependentTypes)
 
+def GetStructFields(struct):
+  """This filters out the __meta__ key from the struct fields"""
+  return [k for k in struct.keys() if k != '__meta__']
 
 def ComputeOrderFromTypeTree(
   ancestors, 
@@ -268,7 +247,7 @@
         # The values in the struct dict are the member types
         if struct:
           # we reach this if struct is not None AND not empty
-          for field in struct.keys():
+          for field in GetStructFields(struct):
             # we fill the chain of dependent types (starting here)
             ancestors.append(shortTypename)
             ComputeOrderFromTypeTree(
@@ -340,6 +319,12 @@
   # TODO: check struct fields are unique (in each struct)
   # TODO: check that in the source schema, there are spaces after each colon
 
+nonTypeKeys = ['rootName']
+def GetTypesInSchema(schema):
+  """Returns the top schema keys that are actual type names"""
+  typeList = [k for k in schema if k not in nonTypeKeys]
+  return typeList
+
 # +-----------------------+
 # | Main processing logic |
 # +-----------------------+
@@ -357,13 +342,49 @@
   # anything and we'll handle them, in their original declaration 
   # order, at the start
   genOrder = []
-  for fullName in schema.keys():
+  for fullName in GetTypesInSchema(schema):
     if IsStructType(fullName):
       realName = GetShortTypename(fullName)
       ancestors = []
       ComputeOrderFromTypeTree(ancestors, genOrder, realName, schema)
   return genOrder
 
+def GetStructFields(fieldDict):
+  """Returns the regular (non __meta__) struct fields"""
+  # the following happens for empty structs
+  if fieldDict == None:
+    return fieldDict
+  ret = {}
+  for k,v in fieldDict.items():
+    if k != "__meta__":
+      ret[k] = v
+  return ret
+
+def GetStructMetadata(fieldDict):
+  """Returns the __meta__ struct fields (there are default values that
+     can be overridden by entries in the schema
+     Not tested because it's a fail-safe: if something is broken in meta, 
+     dependent projects will not build."""
+  metadataDict = {}
+  metadataDict['handleInCpp'] = True
+  metadataDict['handleInTypescript'] = True
+  
+  # Empty types are allowed
+  if fieldDict != None:
+    for k,v in fieldDict.items():
+      if k == "__meta__":
+        # let's examine the various metadata entries
+        for metaKey,metaValue in v.items():
+          # we only accept overriding EXISTING entries
+          if metaKey in metadataDict:
+            # simple check, valid for now
+            if type(metaValue) != bool:
+              raise RuntimeError("Wrong value for metadata key")
+            metadataDict[metaKey] = metaValue
+          else:
+            raise RuntimeError("Wrong key \"{metaKey}\" in metadata. Allowed keys are \"{metadataDict.keys()}\"")
+  return metadataDict
+
 def ProcessSchema(schema, genOrder):
   # sanity check
   CheckSchemaSchema(schema)
@@ -408,7 +429,8 @@
     fieldDict = schema["struct " + typename]
     struct = {}
     struct['name'] = typename
-    struct['fields'] = fieldDict
+    struct['fields'] = GetStructFields(fieldDict)
+    struct['__meta__'] = GetStructMetadata(fieldDict)
     structs.append(struct)
   
   templatingDict = {}
@@ -442,7 +464,8 @@
       nextCh = schemaText[i+1]
       if ch == ':':
         if not (nextCh == ' ' or nextCh == '\n'):
-          assert(False)
+          lineNumber = schemaText.count("\n",0,i) + 1
+          raise RuntimeError(f"Error at line {lineNumber} in the schema: colons must be followed by a space or a newline!")
     schema = yaml.load(schemaText)
   return schema
 
@@ -457,6 +480,26 @@
 # +-----------------------+
 # |      ENTRY POINT      |
 # +-----------------------+
+def Process(schemaFile, outDir):
+  tdico = GetTemplatingDictFromSchemaFilename(schemaFile)
+
+  tsTemplateFile = \
+    os.path.join(os.path.dirname(__file__), 'template.in.ts')
+  template = MakeTemplateFromFile(tsTemplateFile)
+  renderedTsCode = template.render(**tdico)
+  outputTsFile = os.path.join( \
+    outDir,str(tdico['rootName']) + "_generated.ts")
+  with open(outputTsFile,"wt",encoding='utf8') as outFile:
+    outFile.write(renderedTsCode)
+
+  cppTemplateFile = \
+    os.path.join(os.path.dirname(__file__), 'template.in.h')
+  template = MakeTemplateFromFile(cppTemplateFile)
+  renderedCppCode = template.render(**tdico)
+  outputCppFile = os.path.join( \
+    outDir, str(tdico['rootName']) + "_generated.hpp")
+  with open(outputCppFile,"wt",encoding='utf8') as outFile:
+    outFile.write(renderedCppCode)
 
 if __name__ == "__main__":
   import argparse
@@ -490,237 +533,4 @@
   args = parser.parse_args()
   schemaFile = args.input_schema
   outDir = args.out_dir
-
-  tdico = GetTemplatingDictFromSchemaFilename(schemaFile)
-
-  tsTemplateFile = \
-    os.path.join(os.path.dirname(__file__), 'template.in.ts')
-  template = MakeTemplateFromFile(tsTemplateFile)
-  renderedTsCode = template.render(**tdico)
-  outputTsFile = os.path.join( \
-    outDir,str(tdico['rootName']) + "_generated.ts")
-  with open(outputTsFile,"wt",encoding='utf8') as outFile:
-    outFile.write(renderedTsCode)
-
-  cppTemplateFile = \
-    os.path.join(os.path.dirname(__file__), 'template.in.h')
-  template = MakeTemplateFromFile(cppTemplateFile)
-  renderedCppCode = template.render(**tdico)
-  outputCppFile = os.path.join( \
-    outDir, str(tdico['rootName']) + "_generated.hpp")
-  with open(outputCppFile,"wt",encoding='utf8') as outFile:
-    outFile.write(renderedCppCode)
-
-# 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 "')
-
-
+  Process(schemaFile, outDir)
--- a/Resources/CodeGeneration/template.in.h	Wed Mar 06 18:26:35 2019 +0100
+++ b/Resources/CodeGeneration/template.in.h	Mon Mar 11 13:02:46 2019 +0100
@@ -352,8 +352,8 @@
   class IHandler
   {
   public:
-{% for struct in structs%}    virtual bool Handle(const {{struct['name']}}& value) = 0;
-{% endfor %}  };
+{% for struct in structs%}{% if struct['__meta__'].handleInCpp %}    virtual bool Handle(const {{struct['name']}}& value) = 0;
+{% endif %}{% endfor %}  };
 
   /** Service function for StoneDispatchToHandler */
   inline bool StoneDispatchJsonToHandler(
@@ -366,13 +366,13 @@
       // this should never ever happen
       throw std::runtime_error("Caught empty type while dispatching");
     }
-{% for struct in structs%}    else if (type == "{{rootName}}.{{struct['name']}}")
+{% for struct in structs%}{% if struct['__meta__'].handleInCpp %}    else if (type == "{{rootName}}.{{struct['name']}}")
     {
       {{struct['name']}} value;
       _StoneDeserializeValue(value, jsonValue["value"]);
       return handler->Handle(value);
     }
-{% endfor %}    else
+{% endif %}{% endfor %}    else
     {
       return false;
     }
--- a/Resources/CodeGeneration/template.in.ts	Wed Mar 06 18:26:35 2019 +0100
+++ b/Resources/CodeGeneration/template.in.ts	Mon Mar 11 13:02:46 2019 +0100
@@ -66,13 +66,10 @@
     return result;
   }
 }
-
 {% endfor %}
-
 export interface IHandler {
-  {% for struct in structs%}    Handle{{struct['name']}}(value:  {{struct['name']}}): boolean;
-  {% endfor %}
-};
+{% for struct in structs%}{% if struct['__meta__'].handleInTypescript %}  Handle{{struct['name']}}(value:  {{struct['name']}}): boolean;
+{% endif %}{% endfor %}};
 
 /** Service function for StoneDispatchToHandler */
 export function StoneDispatchJsonToHandler(
@@ -85,13 +82,12 @@
     // this should never ever happen
     throw new Error("Caught empty type while dispatching");
   }
-{% for struct in structs%}    else if (type == "{{rootName}}.{{struct['name']}}")
+{% for struct in structs%}{% if struct['__meta__'].handleInTypescript %}  else if (type == "{{rootName}}.{{struct['name']}}")
   {
     let value = jsonValue["value"] as {{struct['name']}};
     return handler.Handle{{struct['name']}}(value);
   }
-{% endfor %}
-  else
+{% endif %}{% endfor %}  else
   {
     return false;
   }