comparison 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
comparison
equal deleted inserted replaced
490:6470248790db 491:8e7e151ef472
1 import json 1 import json
2 import yaml
2 import re 3 import re
3 import sys 4 import sys
5 from jinja2 import Template
4 from typing import ( 6 from typing import (
5 Any, 7 Any,
6 Dict, 8 Dict,
7 Generator, 9 Generator,
8 Iterable, 10 Iterable,
20 """ 22 """
21 1 2 3 4 5 6 7 23 1 2 3 4 5 6 7
22 12345678901234567890123456789012345678901234567890123456789012345678901234567890 24 12345678901234567890123456789012345678901234567890123456789012345678901234567890
23 """ 25 """
24 26
25 class GeneratedCode: 27 # see https://stackoverflow.com/a/2504457/2927708
28 def trim(docstring):
29 if not docstring:
30 return ''
31 # Convert tabs to spaces (following the normal Python rules)
32 # and split into a list of lines:
33 lines = docstring.expandtabs().splitlines()
34 # Determine minimum indentation (first line doesn't count):
35 indent = sys.maxsize
36 for line in lines[1:]:
37 stripped = line.lstrip()
38 if stripped:
39 indent = min(indent, len(line) - len(stripped))
40 # Remove indentation (first line is special):
41 trimmed = [lines[0].strip()]
42 if indent < sys.maxsize:
43 for line in lines[1:]:
44 trimmed.append(line[indent:].rstrip())
45 # Strip off trailing and leading blank lines:
46 while trimmed and not trimmed[-1]:
47 trimmed.pop()
48 while trimmed and not trimmed[0]:
49 trimmed.pop(0)
50 # Return a single string:
51 return '\n'.join(trimmed)
52
53
54 class GenCode:
26 def __init__(self): 55 def __init__(self):
27 56
28 # file-wide preamble (#include directives, comment...) 57 # file-wide preamble (#include directives, comment...)
29 self.cppPreamble = StringIO() 58 self.cppPreamble = StringIO()
30 59
42 self.tsHandler = StringIO() 71 self.tsHandler = StringIO()
43 72
44 def FlattenToFiles(self, outputDir: str): 73 def FlattenToFiles(self, outputDir: str):
45 raise NotImplementedError() 74 raise NotImplementedError()
46 75
47
48 raise Exception("""
49 $$$$TODO check field names are unique
50 """)
51 76
52 class JsonHelpers: 77 class JsonHelpers:
53 """A set of utilities to perform JSON operations""" 78 """A set of utilities to perform JSON operations"""
54 79
55 @staticmethod 80 @staticmethod
81 106
82 107
83 def LoadSchemaFromJson(filePath: str): 108 def LoadSchemaFromJson(filePath: str):
84 return JsonHelpers.loadJsonWithComments(filePath) 109 return JsonHelpers.loadJsonWithComments(filePath)
85 110
86 def GetCppTypeNameFromCanonical(canonicalTypeName: str) -> str: 111 def GetCppTypenameFromCanonical(canonicalTypename: str) -> str:
87 # C++: prefix map vector and string with std::map, std::vector and 112 # C++: prefix map vector and string with std::map, std::vector and
88 # std::string 113 # std::string
89 # replace int32 by int32_t 114 # replace int32 by int32_t
90 # replace float32 by float 115 # replace float32 by float
91 # replace float64 by double 116 # replace float64 by double
92 retVal: str = canonicalTypeName 117 retVal: str = canonicalTypename
93 retVal = retVal.replace("map", "std::map") 118 retVal = retVal.replace("map", "std::map")
94 retVal = retVal.replace("vector", "std::vector") 119 retVal = retVal.replace("vector", "std::vector")
95 retVal = retVal.replace("int32", "int32_t") 120 retVal = retVal.replace("int32", "int32_t")
96 retVal = retVal.replace("float32", "float") 121 retVal = retVal.replace("float32", "float")
97 retVal = retVal.replace("float64", "double") 122 retVal = retVal.replace("float64", "double")
98 return retVal 123 return retVal
99 124
100 def GetTypeScriptTypeNameFromCanonical(canonicalTypeName: str) -> str: 125 def GetTypeScriptTypenameFromCanonical(canonicalTypename: str) -> str:
101 # TS: replace vector with Array and map with Map 126 # TS: replace vector with Array and map with Map
102 # string remains string 127 # string remains string
103 # replace int32 by number 128 # replace int32 by number
104 # replace float32 by number 129 # replace float32 by number
105 # replace float64 by number 130 # replace float64 by number
106 retVal: str = canonicalTypeName 131 retVal: str = canonicalTypename
107 retVal = retVal.replace("map", "Map") 132 retVal = retVal.replace("map", "Map")
108 retVal = retVal.replace("vector", "Array") 133 retVal = retVal.replace("vector", "Array")
109 retVal = retVal.replace("int32", "number") 134 retVal = retVal.replace("int32", "number")
110 retVal = retVal.replace("float32", "number") 135 retVal = retVal.replace("float32", "number")
111 retVal = retVal.replace("float64", "number") 136 retVal = retVal.replace("float64", "number")
112 retVal = retVal.replace("bool", "boolean") 137 retVal = retVal.replace("bool", "boolean")
113 return retVal 138 return retVal
114
115 # class Schema:
116 # def __init__(self, root_prefix : str, defined_types : List[Type]):
117 # self.rootName : str = root_prefix
118 # self.definedTypes : str = defined_types
119
120 def CheckTypeSchema(definedType: Dict) -> None:
121 allowedDefinedTypeKinds = ["enum", "struct"]
122 if not "name" in definedType:
123 raise Exception("type lacks the 'name' key")
124 name = definedType["name"]
125 if not "kind" in definedType:
126 raise Exception(f"type {name} lacks the 'kind' key")
127 kind = definedType["kind"]
128 if not (kind in allowedDefinedTypeKinds):
129 raise Exception(
130 f"type {name} : kind {kind} is not allowed. "
131 + f"It must be one of {allowedDefinedTypeKinds}"
132 )
133
134 if not "fields" in definedType:
135 raise Exception("type {name} lacks the 'fields' key")
136
137 # generic check on all kinds of types
138 fields = definedType["fields"]
139 for field in fields:
140 fieldName = field["name"]
141 if not "name" in field:
142 raise Exception("field in type {name} lacks the 'name' key")
143
144 # fields in struct must have types
145 if kind == "struct":
146 for field in fields:
147 fieldName = field["name"]
148 if not "type" in field:
149 raise Exception(
150 f"field {fieldName} in type {name} " + "has no 'type' key"
151 )
152
153
154 def CheckSchemaSchema(schema: Dict) -> None:
155 if not "root_name" in schema:
156 raise Exception("schema lacks the 'root_name' key")
157 if not "types" in schema:
158 raise Exception("schema lacks the 'types' key")
159 for definedType in schema["types"]:
160 CheckTypeSchema(definedType)
161
162 139
163 def EatToken(sentence: str) -> Tuple[str, str]: 140 def EatToken(sentence: str) -> Tuple[str, str]:
164 """splits "A,B,C" into "A" and "B,C" where A, B and C are type names 141 """splits "A,B,C" into "A" and "B,C" where A, B and C are type names
165 (including templates) like "int32", "TotoTutu", or 142 (including templates) like "int32", "TotoTutu", or
166 "map<map<int32,vector<string>>,map<string,int32>>" """ 143 "map<map<int32,vector<string>>,map<string,int32>>" """
181 elif sentence[i] == ">": 158 elif sentence[i] == ">":
182 templateLevel -= 1 159 templateLevel -= 1
183 return (sentence, "") 160 return (sentence, "")
184 161
185 162
186 def SplitListOfTypes(typeName: str) -> List[str]: 163 def SplitListOfTypes(typename: str) -> List[str]:
187 """Splits something like 164 """Splits something like
188 vector<string>,int32,map<string,map<string,int32>> 165 vector<string>,int32,map<string,map<string,int32>>
189 in: 166 in:
190 - vector<string> 167 - vector<string>
191 - int32 168 - int32
193 170
194 This is not possible with a regex so 171 This is not possible with a regex so
195 """ 172 """
196 stillStuffToEat: bool = True 173 stillStuffToEat: bool = True
197 tokenList = [] 174 tokenList = []
198 restOfString = typeName 175 restOfString = typename
199 while stillStuffToEat: 176 while stillStuffToEat:
200 firstToken, restOfString = EatToken(restOfString) 177 firstToken, restOfString = EatToken(restOfString)
201 tokenList.append(firstToken) 178 tokenList.append(firstToken)
202 if restOfString == "": 179 if restOfString == "":
203 stillStuffToEat = False 180 stillStuffToEat = False
206 183
207 templateRegex = \ 184 templateRegex = \
208 re.compile(r"([a-zA-Z0-9_]*[a-zA-Z0-9_]*)<([a-zA-Z0-9_,:<>]+)>") 185 re.compile(r"([a-zA-Z0-9_]*[a-zA-Z0-9_]*)<([a-zA-Z0-9_,:<>]+)>")
209 186
210 187
211 def ParseTemplateType(typeName) -> Tuple[bool, str, List[str]]: 188 def ParseTemplateType(typename) -> Tuple[bool, str, List[str]]:
212 """ If the type is a template like "SOMETHING<SOME<THING,EL<SE>>>", then 189 """ If the type is a template like "SOMETHING<SOME<THING,EL<SE>>>",
213 it returns (true,"SOMETHING","SOME<THING,EL<SE>>") 190 then it returns (true,"SOMETHING","SOME<THING,EL<SE>>")
214 otherwise it returns (false,"","")""" 191 otherwise it returns (false,"","")"""
215 192
216 # let's remove all whitespace from the type 193 # let's remove all whitespace from the type
217 # split without argument uses any whitespace string as separator 194 # split without argument uses any whitespace string as separator
218 # (space, tab, newline, return or formfeed) 195 # (space, tab, newline, return or formfeed)
219 typeName = "".join(typeName.split()) 196 typename = "".join(typename.split())
220 matches = templateRegex.match(typeName) 197 matches = templateRegex.match(typename)
221 if matches == None: 198 if matches == None:
222 return (False, "", []) 199 return (False, "", [])
223 else: 200 else:
224 m = cast(Match[str], matches) 201 m = cast(Match[str], matches)
225 assert len(m.groups()) == 2 202 assert len(m.groups()) == 2
226 # we need to split with the commas that are outside of the 203 # we need to split with the commas that are outside of the
227 # defined types. Simply splitting at commas won't work 204 # defined types. Simply splitting at commas won't work
228 listOfDependentTypes = SplitListOfTypes(m.group(2)) 205 listOfDependentTypes = SplitListOfTypes(m.group(2))
229 return (True, m.group(1), listOfDependentTypes) 206 return (True, m.group(1), listOfDependentTypes)
230 207
231 def ProcessTypeTree( 208
232 ancestors: List[str], 209 def ComputeOrderFromTypeTree(
233 genOrderQueue: List[str], 210 ancestors: List[str],
234 structTypes: Dict[str, Dict], 211 genOrder: List[str],
235 typeName: str) -> None: 212 shortTypename: str, schema: Dict[str, Dict]) -> None:
236 if typeName in ancestors: 213
214 if shortTypename in ancestors:
237 raise Exception( 215 raise Exception(
238 f"Cyclic dependency chain found: the last of {ancestors} " 216 f"Cyclic dependency chain found: the last of {ancestors} "
239 + f"depends on {typeName} that is already in the list." 217 + f"depends on {shortTypename} that is already in the list."
240 ) 218 )
241 219
242 if not (typeName in genOrderQueue): 220 if not (shortTypename in genOrder):
243 # if we reach this point, it means the type is NOT a struct or an enum. 221 (isTemplate, _, dependentTypenames) = ParseTemplateType(shortTypename)
244 # it is another (non directly user-defined) type that we must parse and
245 # create. Let's do it!
246 (isTemplate, _, dependentTypeNames) = ParseTemplateType(typeName)
247 if isTemplate: 222 if isTemplate:
248 for dependentTypeName in dependentTypeNames: 223 # if it is a template, it HAS dependent types... They can be
224 # anything (primitive, collection, enum, structs..).
225 # Let's process them!
226 for dependentTypename in dependentTypenames:
249 # childAncestors = ancestors.copy() NO TEMPLATE ANCESTOR!!! 227 # childAncestors = ancestors.copy() NO TEMPLATE ANCESTOR!!!
250 # childAncestors.append(typeName) 228 # childAncestors.append(typename)
251 ProcessTypeTree( 229 ComputeOrderFromTypeTree(
252 ancestors, genOrderQueue, structTypes, dependentTypeName 230 ancestors, genOrder, dependentTypename, schema
253 ) 231 )
254 else: 232 else:
255 if typeName in structTypes: 233 # If it is not template, we are only interested if it is a
256 ProcessStructType_DepthFirstRecursive( 234 # dependency that we must take into account in the dep graph,
257 genOrderQueue, structTypes, structTypes[typeName] 235 # i.e., a struct.
258 ) 236 if IsShortStructType(shortTypename, schema):
259 237 struct:Dict = schema[GetLongTypename(shortTypename, schema)]
260 def ProcessStructType_DepthFirstRecursive(genOrderQueue: List[str], \ 238 # The keys in the struct dict are the member names
261 structTypes: Dict[str, Dict], typeDict: Dict) -> None: 239 # The values in the struct dict are the member types
262 # let's generate the code according to the 240 for field in struct.keys():
263 typeName: str = typeDict["name"] 241 # we fill the chain of dependent types (starting here)
264 if typeDict["kind"] != "struct": 242 ancestors.append(shortTypename)
265 raise Exception( 243 ComputeOrderFromTypeTree(
266 f"Unexpected kind '{typeDict['kind']}' for " + "type '{typeName}'" 244 ancestors, genOrder, struct[field], schema)
267 ) 245 # don't forget to restore it!
268 typeFields: List[Dict] = typeDict["fields"] 246 ancestors.pop()
269 for typeField in typeFields: 247
270 ancestors = [typeName] 248 # now we're pretty sure our dependencies have been processed,
271 ProcessTypeTree(ancestors, genOrderQueue, structTypes, typeField["type"]) 249 # we can start marking our code for generation (it might
272 # now we're pretty sure our dependencies have been processed, 250 # already have been done if someone referenced us earlier)
273 # we can start marking our code for generation (it might already have 251 if not shortTypename in genOrder:
274 # been done if someone referenced us earlier) 252 genOrder.append(shortTypename)
275 if not typeName in genOrderQueue: 253
276 genOrderQueue.append(typeName) 254 # +-----------------------+
277 255 # | Utility functions |
278 def ProcessEnumerationType(outputStreams: GeneratedCode, typeDict: Dict) -> None: 256 # +-----------------------+
279 257
280 # enumeration declarations 258 def IsShortStructType(typename: str, schema: Dict[str, Dict]) -> bool:
281 tsDeclText: StringIO = StringIO() 259 fullStructName = "struct " + typename
282 tsDeclText.write("enum %s\n" % typeDict["name"]) 260 return (fullStructName in schema)
283 tsDeclText.write("{\n") 261
284 262 def GetLongTypename(shortTypename: str, schema: Dict):
285 cppDeclText: StringIO = StringIO() 263 if shortTypename.startswith("enum "):
286 cppDeclText.write("enum %s\n" % typeDict["name"]) 264 raise RuntimeError('shortTypename.startswith("enum "):')
287 cppDeclText.write("{\n") 265 enumName: str = "enum " + shortTypename
288 266 isEnum = enumName in schema
289 cppToStringText: StringIO = StringIO() 267
290 cppToStringText.write("enum %s\n" % typeDict["name"]) 268 if shortTypename.startswith("struct "):
291 cppToStringText.write("{\n") 269 raise RuntimeError('shortTypename.startswith("struct "):')
292 270 structName: str = "struct " + shortTypename
293 cppFromStringText: StringIO = StringIO() 271 isStruct = ("struct " + shortTypename) in schema
294 cppFromStringText.write("enum %s\n" % typeDict["name"]) 272
295 cppFromStringText.write("{\n") 273 if isEnum and isStruct:
296 274 raise RuntimeError('Enums and structs cannot have the same name')
297 for i in range(len(typeDict["fields"])): 275
298 field = typeDict["fields"][i] 276 if isEnum:
299 name = field["name"] 277 return enumName
300 278 if isStruct:
301 tsText.write(" %s" % name) 279 return structName
302 if i < len(typeDict["fields"]) - 1: 280
303 tsText.write(",") 281 def IsTypename(fullName: str) -> bool:
304 tsText.write("\n") 282 return (fullName.startswith("enum ") or fullName.startswith("struct "))
305 283
306 cppText.write(" %s" % name) 284 def IsEnumType(fullName: str) -> bool:
307 if i < len(typeDict["fields"]) - 1: 285 return fullName.startswith("enum ")
308 cppText.write(",") 286
309 cppText.write("\n") 287 def IsStructType(fullName: str) -> bool:
310 288 return fullName.startswith("struct ")
311 tsText.write("};\n\n") 289
312 cppText.write("};\n\n") 290 def GetShortTypename(fullTypename: str) -> str:
313 291 if fullTypename.startswith("struct "):
314 outputStreams.tsEnums.write(tsText.getvalue()) 292 return fullTypename[7:]
315 outputStreams.cppEnums.write(cppText.getvalue()) 293 elif fullTypename.startswith("enum"):
316 294 return fullTypename[5:]
317 def GetSerializationCode(typeName: str,valueName: str, tempName: str) 295 else:
318 if IsPrimitiveType(typeName): 296 raise RuntimeError \
319 """ 297 ('fullTypename should start with either "struct " or "enum "')
320 json::Value val(objectTypeInt...) 298
321 val.setValue(valueName) <--- val 299 def CheckSchemaSchema(schema: Dict) -> None:
322 """ 300 if not "rootName" in schema:
323 elif IsArray(typeName) 301 raise Exception("schema lacks the 'rootName' key")
324 """ 302 for name in schema.keys():
325 { 303 if (not IsEnumType(name)) and (not IsStructType(name)) and \
326 json::Value val(objectTypeArray...) 304 (name != 'rootName'):
327 for(size_t i = 0; i < {fieldName}.size(); ++i) 305 raise RuntimeError \
328 { 306 (f'Type "{name}" should start with "enum " or "struct "')
329 json::Value val(objectTypeArray...) 307
330 } 308 # TODO: check enum fields are unique (in whole namespace)
331 val.setValue(valueName) 309 # TODO: check struct fields are unique (in each struct)
332 // <--- the calling code will insert collection/field writing here, 310
333 // like "parent.set("{fieldName}",val) or parent.append(val) 311 # +-----------------------+
334 $collectValue 312 # | Main processing logic |
335 } 313 # +-----------------------+
336 """ 314
337 315 def ComputeRequiredDeclarationOrder(schema: dict) -> List[str]:
338 316 # sanity check
339 317 CheckSchemaSchema(schema)
340 def ProcessStructType(outputStreams: GeneratedCode, typeDict) -> None: 318
341 tsText: StringIO = StringIO() 319 # we traverse the type dependency graph and we fill a queue with
342 cppText: StringIO = StringIO() 320 # the required struct types, in a bottom-up fashion, to compute
343 321 # the declaration order
344 tsText.write("class %s\n" % typeDict["name"]) 322 # The genOrder list contains the struct full names in the order
345 tsText.write("{\n") 323 # where they must be defined.
346 324 # We do not care about the enums here... They do not depend upon
347 cppText.write("struct %s\n" % typeDict["name"]) 325 # anything and we'll handle them, in their original declaration
348 cppText.write("{\n") 326 # order, at the start
349 327 genOrder: List = []
350 """ 328 for fullName in schema.keys():
329 if IsStructType(fullName):
330 realName: str = GetShortTypename(fullName)
331 ancestors: List[str] = []
332 ComputeOrderFromTypeTree(ancestors, genOrder, realName, schema)
333 return genOrder
334
335 def ProcessSchema(schema: dict, genOrder: List[str]) -> Dict:
336 # sanity check
337 CheckSchemaSchema(schema)
338
339 # let's doctor the schema to clean it up a bit
340 # order DOES NOT matter for enums, even though it's a list
341 enums: List[Dict] = []
342 for fullName in schema.keys():
343 if IsEnumType(fullName):
344 # convert "enum Toto" to "Toto"
345 typename:str = GetShortTypename(fullName)
346 enum = {}
347 enum['name'] = typename
348 assert(type(schema[fullName]) == list)
349 enum['fields'] = schema[fullName] # must be a list
350 enums.append(enum)
351
352 # now that the order has been established, we actually store\
353 # the structs in the correct order
354 # the structs are like:
355 # example = [
356 # {
357 # "name": "Message1",
358 # "fields": {
359 # "someMember":"int32",
360 # "someOtherMember":"vector<string>"
361 # }
362 # },
363 # {
364 # "name": "Message2",
365 # "fields": {
366 # "someMember":"int32",
367 # "someOtherMember22":"vector<Message1>"
368 # }
369 # }
370 # ]
371
372 structs: List[Dict] = []
373 for i in range(len(genOrder)):
374 # this is already the short name
375 typename = genOrder[i]
376 fieldDict = schema["struct " + typename]
377 struct = {}
378 struct['name'] = typename
379 struct['fields'] = fieldDict
380 structs.append(struct)
351 381
352 GenerateSerializationCode(typeName,valueName) 382 templatingDict = {}
353 383 templatingDict['enums'] = enums
354 primitives: 384 templatingDict['structs'] = structs
355 ----------- 385 templatingDict['rootName'] = schema['rootName']
356 int 386
357 jsonValue val(objectInt); 387 return templatingDict
358 val.setValue("$name") 388
359 parent.add(("$name",$name) 389 # +-----------------------+
360 double 390 # | Write to files |
361 ... 391 # +-----------------------+
362 string 392
363 ... 393 # def WriteStreamsToFiles(rootName: str, genc: Dict[str, StringIO]) \
364 394 # -> None:
365 collections: 395 # pass
366 ----------- 396
367 dict { } 397 def LoadSchema(fn):
368 398 with open(fn, 'rb') as f:
369 399 schema = yaml.load(f)
370 400 return schema
371 401
372 402 def GetTemplatingDictFromSchemaFilename(fn):
373 serializeValue() 403 obj = LoadSchema(fn)
374 """ 404 genOrder: str = ComputeRequiredDeclarationOrder(obj)
375 405 templatingDict = ProcessSchema(obj, genOrder)
376 for i in range(len(typeDict["fields"])): 406 return templatingDict
377 field = typeDict["fields"][i] 407
378 name = field["name"] 408 # +-----------------------+
379 tsType = GetTypeScriptTypeNameFromCanonical(field["type"]) 409 # | ENTRY POINT |
380 tsText.write(" public %s %s;\n" % (tsType, name)) 410 # +-----------------------+
381 cppType = GetCppTypeNameFromCanonical(field["type"])
382 cppText.write(" %s %s;\n" % (cppType, name))
383
384 tsText.write("};\n\n")
385 cppText.write("};\n\n")
386
387 outputStreams.tsStructs.write(tsText.getvalue())
388 outputStreams.cppStructs.write(cppText.getvalue())
389
390
391 def WritePreambles(rootName: str, outputStreams: GeneratedCode) -> None:
392 outputStreams.cppPreamble.write(
393 """// autogenerated by stonegentool on %s for module %s
394 #include <cstdint>
395 #include <string>
396 #include <vector>
397 #include <map>
398
399 """ % (time.ctime(), rootName))
400
401 outputStreams.tsPreamble.write(
402 """// autogenerated by stonegentool on %s for module %s
403 """ % (time.ctime(), rootName))
404
405
406 def ProcessSchema(schema: dict) -> Tuple[str, GeneratedCode, List[str]]:
407 CheckSchemaSchema(schema)
408 rootName: str = schema["root_name"]
409 definedTypes: list = schema["types"]
410
411 # this will be filled with the generation queue. That is, the type
412 # names in the order where they must be defined.
413 genOrderQueue: List = []
414
415 # the struct names are mapped to their JSON dictionary
416 structTypes: Dict[str, Dict] = {}
417
418 outputStreams: GeneratedCode = GeneratedCode()
419
420 WritePreambles(rootName, outputStreams)
421
422 # the order here is the generation order
423 for definedType in definedTypes:
424 if definedType["kind"] == "enum":
425 ProcessEnumerationType(outputStreams, definedType)
426
427 for definedType in definedTypes:
428 if definedType["kind"] == "struct":
429 structTypes[definedType["name"]] = definedType
430
431 # the order here is NOT the generation order: the types
432 # will be processed according to their dependency graph
433 for definedType in definedTypes:
434 if definedType["kind"] == "struct":
435 ProcessStructType_DepthFirstRecursive(
436 genOrderQueue, structTypes, definedType
437 )
438
439 for i in range(len(genOrderQueue)):
440 typeName = genOrderQueue[i]
441 typeDict = structTypes[typeName]
442 ProcessStructType(outputStreams, typeDict)
443
444 return (rootName, outputStreams, genOrderQueue)
445
446
447 def WriteStreamsToFiles(rootName: str, outputStreams: Dict[str, StringIO]) -> None:
448 pass
449 411
450 if __name__ == "__main__": 412 if __name__ == "__main__":
451 import argparse 413 import argparse
452 414
453 parser = argparse.ArgumentParser( 415 parser = argparse.ArgumentParser(
454 usage="""stonegentool.py [-h] [-o OUT_DIR] [-v] input_schemas 416 usage="""stonegentool.py [-h] [-o OUT_DIR] [-v] input_schema
455 EXAMPLE: python command_gen.py -o "generated_files/" """ 417 EXAMPLE: python stonegentool.py -o "generated_files/" """
456 + """ "mainSchema.json,App Specific Commands.json" """ 418 + """ "mainSchema.yaml,App Specific Commands.json" """
457 ) 419 )
458 parser.add_argument("input_schema", type=str, help="path to the schema file") 420 parser.add_argument("input_schema", type=str, \
421 help="path to the schema file")
459 parser.add_argument( 422 parser.add_argument(
460 "-o", 423 "-o",
461 "--out_dir", 424 "--out_dir",
462 type=str, 425 type=str,
463 default=".", 426 default=".",
477 440
478 args = parser.parse_args() 441 args = parser.parse_args()
479 inputSchemaFilename = args.input_schema 442 inputSchemaFilename = args.input_schema
480 outDir = args.out_dir 443 outDir = args.out_dir
481 444
482 (rootName, outputStreams, _) = ProcessSchema(LoadSchema(inputSchemaFilename)) 445 schema: Dict = LoadSchema(inputSchemaFilename)
483 WriteStreamsToFiles(rootName, outputStreams) 446 genOrder: List[str] = ComputeRequiredDeclarationOrder(schema)
484 447 processedSchema: Dict = ProcessSchema(schema,genOrder)
448
449
450 # def GenEnumDecl(genc: GenCode, fullName: str, schema: Dict) -> None:
451 # """Writes the enumerations in genc"""
452 # enumDict:Dict=schema[fullName]
453 # # jinja2 template
454 # j2cppEnum = Template(trim(
455 # """ {{fullName}}
456 # {
457 # {% for key in enumDict.keys()%}
458 # {{key}},
459 # {%endfor%}
460 # };
461 # """))
462 # j2cppEnumR = j2cppEnum.render(locals())
463 # genc.cppEnums.write(j2cppEnumR)
464
465 # j2tsEnum = Template(trim(
466 # """ export {{fullName}}
467 # {
468 # {% for key in enumDict.keys()%}
469 # {{key}},
470 # {%endfor%}
471 # };
472 # """))
473 # j2cppEnumR = j2cppEnum.render(locals())
474 # genc.tsEnums.write(j2cppEnumR)
475
476
477
478 # def GetSerializationCode(typename: str,valueName: str, tempName: str)
479 # if IsPrimitiveType(typename) or IsTemplateCollection(typename):
480 # # no need to write code for the primitive types or collections.
481 # # It is handled in C++ by the template functions and in TS by
482 # # the JSON.stringify code.
483 # elif IsStructType(typename):
484 # pass
485
486 # def GenStructTypeDeclAndSerialize(genc: GenCode, type, schema) -> None:
487 # ######
488 # # CPP
489 # ######
490 # sampleCpp = """ struct Message1
491 # {
492 # int32_t a;
493 # std::string b;
494 # EnumMonth0 c;
495 # bool d;
496 # };
497
498 # Json::Value StoneSerialize(const Message1& value)
499 # {
500 # Json::Value result(Json::objectValue);
501 # result["a"] = StoneSerialize(value.a);
502 # result["b"] = StoneSerialize(value.b);
503 # result["c"] = StoneSerialize(value.c);
504 # result["d"] = StoneSerialize(value.d);
505 # return result;
506 # }
507 # """
508
509
510 # ######
511 # # TS
512 # ######
513 # sampleTs = """
514 # {
515 # export class Message1 {
516 # a: number;
517 # b: string;
518 # c: EnumMonth0;
519 # d: boolean;
520 # public StoneSerialize(): string {
521 # let container: object = {};
522 # container['type'] = 'Message1';
523 # container['value'] = this;
524 # return JSON.stringify(container);
525 # }
526 # };
527 # }
528 # """
529
530
531
532
533 # tsText: StringIO = StringIO()
534 # cppText: StringIO = StringIO()
535
536 # tsText.write("class %s\n" % typeDict["name"])
537 # tsText.write("{\n")
538
539 # cppText.write("struct %s\n" % typeDict["name"])
540 # cppText.write("{\n")
541
542 # """
543
544 # GenerateSerializationCode(typename,valueName)
545
546 # primitives:
547 # -----------
548 # int
549 # jsonValue val(objectInt);
550 # val.setValue("$name")
551 # parent.add(("$name",$name)
552 # double
553 # ...
554 # string
555 # ...
556
557 # collections:
558 # -----------
559 # dict { }
560
561 # serializeValue()
562 # """
563
564 # for i in range(len(typeDict["fields"])):
565 # field = typeDict["fields"][i]
566 # name = field["name"]
567 # tsType = GetTypeScriptTypenameFromCanonical(field["type"])
568 # tsText.write(" public %s %s;\n" % (tsType, name))
569 # cppType = GetCppTypenameFromCanonical(field["type"])
570 # cppText.write(" %s %s;\n" % (cppType, name))
571
572 # tsText.write("};\n\n")
573 # cppText.write("};\n\n")
574
575 # genc.tsStructs.write(tsText.getvalue())
576 # genc.cppStructs.write(cppText.getvalue())
577
578
579 # def GenerateCodeFromTsTemplate(genc)
580
581
582 # +-----------------------+
583 # | CODE GENERATION |
584 # +-----------------------+
585
586 # def GenPreambles(rootName: str, genc: GenCode) -> None:
587 # cppPreambleT = Template(trim(
588 # """// autogenerated by stonegentool on {{time.ctime()}}
589 # // for module {{rootName}}
590 # #include <cstdint>
591 # #include <string>
592 # #include <vector>
593 # #include <map>
594 # namespace {{rootName}}
595 # {
596 # Json::Value StoneSerialize(int32_t value)
597 # {
598 # Json::Value result(value);
599 # return result;
600 # }
601 # Json::Value StoneSerialize(double value)
602 # {
603 # Json::Value result(value);
604 # return result;
605 # }
606 # Json::Value StoneSerialize(bool value)
607 # {
608 # Json::Value result(value);
609 # return result;
610 # }
611 # Json::Value StoneSerialize(const std::string& value)
612 # {
613 # // the following is better than
614 # Json::Value result(value.data(),value.data()+value.size());
615 # return result;
616 # }
617 # template<typename T>
618 # Json::Value StoneSerialize(const std::map<std::string,T>& value)
619 # {
620 # Json::Value result(Json::objectValue);
621
622 # for (std::map<std::string, T>::const_iterator it = value.cbegin();
623 # it != value.cend(); ++it)
624 # {
625 # // it->first it->second
626 # result[it->first] = StoneSerialize(it->second);
627 # }
628 # return result;
629 # }
630 # template<typename T>
631 # Json::Value StoneSerialize(const std::vector<T>& value)
632 # {
633 # Json::Value result(Json::arrayValue);
634 # for (size_t i = 0; i < value.size(); ++i)
635 # {
636 # result.append(StoneSerialize(value[i]));
637 # }
638 # return result;
639 # }
640 # """
641 # cppPreambleR = cppPreambleT.render(locals())
642 # genc.cppPreamble.write(cppPreambleR)
643
644 # tsPreambleT = Template(trim(
645 # """// autogenerated by stonegentool on {{time.ctime()}}
646 # // for module {{rootName}}
647
648 # namespace {{rootName}}
649 # {
650 # """
651 # tsPreambleR = tsPreambleT.render(locals())
652 # genc.tsPreamble.write(tsPreambleR)
653
654 # def ComputeOrder_ProcessStruct( \
655 # genOrder: List[str], name:str, schema: Dict[str, str]) -> None:
656 # # let's generate the code according to the
657 # struct = schema[name]
658
659 # if not IsStructType(name):
660 # raise Exception(f'{typename} should start with "struct "')