Mercurial > hg > orthanc-stone
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 "') |