comparison Resources/CodeGeneration/stonegentool.py @ 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 7105a0bad250
children 1dbf2d9ed1e4
comparison
equal deleted inserted replaced
510:97a16b694321 513:dea3787a8f4b
37 while trimmed and not trimmed[0]: 37 while trimmed and not trimmed[0]:
38 trimmed.pop(0) 38 trimmed.pop(0)
39 # Return a single string: 39 # Return a single string:
40 return '\n'.join(trimmed) 40 return '\n'.join(trimmed)
41 41
42
43 class GenCode:
44 def __init__(self):
45
46 # file-wide preamble (#include directives, comment...)
47 self.cppPreamble = StringIO()
48
49 self.cppEnums = StringIO()
50 self.cppStructs = StringIO()
51 self.cppDispatcher = StringIO()
52 self.cppHandler = StringIO()
53
54 # file-wide preamble (module directives, comment...)
55 self.tsPreamble = StringIO()
56
57 self.tsEnums = StringIO()
58 self.tsStructs = StringIO()
59 self.tsDispatcher = StringIO()
60 self.tsHandler = StringIO()
61
62 def FlattenToFiles(self, outputDir):
63 raise NotImplementedError()
64
65
66 class JsonHelpers: 42 class JsonHelpers:
67 """A set of utilities to perform JSON operations""" 43 """A set of utilities to perform JSON operations"""
68 44
69 @staticmethod 45 @staticmethod
70 def removeCommentsFromJsonContent(string): 46 def removeCommentsFromJsonContent(string):
232 # we need to split with the commas that are outside of the 208 # we need to split with the commas that are outside of the
233 # defined types. Simply splitting at commas won't work 209 # defined types. Simply splitting at commas won't work
234 listOfDependentTypes = SplitListOfTypes(m.group(2)) 210 listOfDependentTypes = SplitListOfTypes(m.group(2))
235 return (True, m.group(1), listOfDependentTypes) 211 return (True, m.group(1), listOfDependentTypes)
236 212
213 def GetStructFields(struct):
214 """This filters out the __meta__ key from the struct fields"""
215 return [k for k in struct.keys() if k != '__meta__']
237 216
238 def ComputeOrderFromTypeTree( 217 def ComputeOrderFromTypeTree(
239 ancestors, 218 ancestors,
240 genOrder, 219 genOrder,
241 shortTypename, schema): 220 shortTypename, schema):
266 struct = schema[GetLongTypename(shortTypename, schema)] 245 struct = schema[GetLongTypename(shortTypename, schema)]
267 # The keys in the struct dict are the member names 246 # The keys in the struct dict are the member names
268 # The values in the struct dict are the member types 247 # The values in the struct dict are the member types
269 if struct: 248 if struct:
270 # we reach this if struct is not None AND not empty 249 # we reach this if struct is not None AND not empty
271 for field in struct.keys(): 250 for field in GetStructFields(struct):
272 # we fill the chain of dependent types (starting here) 251 # we fill the chain of dependent types (starting here)
273 ancestors.append(shortTypename) 252 ancestors.append(shortTypename)
274 ComputeOrderFromTypeTree( 253 ComputeOrderFromTypeTree(
275 ancestors, genOrder, struct[field], schema) 254 ancestors, genOrder, struct[field], schema)
276 # don't forget to restore it! 255 # don't forget to restore it!
338 317
339 # TODO: check enum fields are unique (in whole namespace) 318 # TODO: check enum fields are unique (in whole namespace)
340 # TODO: check struct fields are unique (in each struct) 319 # TODO: check struct fields are unique (in each struct)
341 # TODO: check that in the source schema, there are spaces after each colon 320 # TODO: check that in the source schema, there are spaces after each colon
342 321
322 nonTypeKeys = ['rootName']
323 def GetTypesInSchema(schema):
324 """Returns the top schema keys that are actual type names"""
325 typeList = [k for k in schema if k not in nonTypeKeys]
326 return typeList
327
343 # +-----------------------+ 328 # +-----------------------+
344 # | Main processing logic | 329 # | Main processing logic |
345 # +-----------------------+ 330 # +-----------------------+
346 331
347 def ComputeRequiredDeclarationOrder(schema): 332 def ComputeRequiredDeclarationOrder(schema):
355 # where they must be defined. 340 # where they must be defined.
356 # We do not care about the enums here... They do not depend upon 341 # We do not care about the enums here... They do not depend upon
357 # anything and we'll handle them, in their original declaration 342 # anything and we'll handle them, in their original declaration
358 # order, at the start 343 # order, at the start
359 genOrder = [] 344 genOrder = []
360 for fullName in schema.keys(): 345 for fullName in GetTypesInSchema(schema):
361 if IsStructType(fullName): 346 if IsStructType(fullName):
362 realName = GetShortTypename(fullName) 347 realName = GetShortTypename(fullName)
363 ancestors = [] 348 ancestors = []
364 ComputeOrderFromTypeTree(ancestors, genOrder, realName, schema) 349 ComputeOrderFromTypeTree(ancestors, genOrder, realName, schema)
365 return genOrder 350 return genOrder
351
352 def GetStructFields(fieldDict):
353 """Returns the regular (non __meta__) struct fields"""
354 # the following happens for empty structs
355 if fieldDict == None:
356 return fieldDict
357 ret = {}
358 for k,v in fieldDict.items():
359 if k != "__meta__":
360 ret[k] = v
361 return ret
362
363 def GetStructMetadata(fieldDict):
364 """Returns the __meta__ struct fields (there are default values that
365 can be overridden by entries in the schema
366 Not tested because it's a fail-safe: if something is broken in meta,
367 dependent projects will not build."""
368 metadataDict = {}
369 metadataDict['handleInCpp'] = True
370 metadataDict['handleInTypescript'] = True
371
372 # Empty types are allowed
373 if fieldDict != None:
374 for k,v in fieldDict.items():
375 if k == "__meta__":
376 # let's examine the various metadata entries
377 for metaKey,metaValue in v.items():
378 # we only accept overriding EXISTING entries
379 if metaKey in metadataDict:
380 # simple check, valid for now
381 if type(metaValue) != bool:
382 raise RuntimeError("Wrong value for metadata key")
383 metadataDict[metaKey] = metaValue
384 else:
385 raise RuntimeError("Wrong key \"{metaKey}\" in metadata. Allowed keys are \"{metadataDict.keys()}\"")
386 return metadataDict
366 387
367 def ProcessSchema(schema, genOrder): 388 def ProcessSchema(schema, genOrder):
368 # sanity check 389 # sanity check
369 CheckSchemaSchema(schema) 390 CheckSchemaSchema(schema)
370 391
406 # this is already the short name 427 # this is already the short name
407 typename = genOrder[i] 428 typename = genOrder[i]
408 fieldDict = schema["struct " + typename] 429 fieldDict = schema["struct " + typename]
409 struct = {} 430 struct = {}
410 struct['name'] = typename 431 struct['name'] = typename
411 struct['fields'] = fieldDict 432 struct['fields'] = GetStructFields(fieldDict)
433 struct['__meta__'] = GetStructMetadata(fieldDict)
412 structs.append(struct) 434 structs.append(struct)
413 435
414 templatingDict = {} 436 templatingDict = {}
415 templatingDict['enums'] = enums 437 templatingDict['enums'] = enums
416 templatingDict['structs'] = structs 438 templatingDict['structs'] = structs
440 for i in range(len(schemaText)-1): 462 for i in range(len(schemaText)-1):
441 ch = schemaText[i] 463 ch = schemaText[i]
442 nextCh = schemaText[i+1] 464 nextCh = schemaText[i+1]
443 if ch == ':': 465 if ch == ':':
444 if not (nextCh == ' ' or nextCh == '\n'): 466 if not (nextCh == ' ' or nextCh == '\n'):
445 assert(False) 467 lineNumber = schemaText.count("\n",0,i) + 1
468 raise RuntimeError(f"Error at line {lineNumber} in the schema: colons must be followed by a space or a newline!")
446 schema = yaml.load(schemaText) 469 schema = yaml.load(schemaText)
447 return schema 470 return schema
448 471
449 def GetTemplatingDictFromSchemaFilename(fn): 472 def GetTemplatingDictFromSchemaFilename(fn):
450 obj = LoadSchema(fn) 473 obj = LoadSchema(fn)
455 return templatingDict 478 return templatingDict
456 479
457 # +-----------------------+ 480 # +-----------------------+
458 # | ENTRY POINT | 481 # | ENTRY POINT |
459 # +-----------------------+ 482 # +-----------------------+
483 def Process(schemaFile, outDir):
484 tdico = GetTemplatingDictFromSchemaFilename(schemaFile)
485
486 tsTemplateFile = \
487 os.path.join(os.path.dirname(__file__), 'template.in.ts')
488 template = MakeTemplateFromFile(tsTemplateFile)
489 renderedTsCode = template.render(**tdico)
490 outputTsFile = os.path.join( \
491 outDir,str(tdico['rootName']) + "_generated.ts")
492 with open(outputTsFile,"wt",encoding='utf8') as outFile:
493 outFile.write(renderedTsCode)
494
495 cppTemplateFile = \
496 os.path.join(os.path.dirname(__file__), 'template.in.h')
497 template = MakeTemplateFromFile(cppTemplateFile)
498 renderedCppCode = template.render(**tdico)
499 outputCppFile = os.path.join( \
500 outDir, str(tdico['rootName']) + "_generated.hpp")
501 with open(outputCppFile,"wt",encoding='utf8') as outFile:
502 outFile.write(renderedCppCode)
460 503
461 if __name__ == "__main__": 504 if __name__ == "__main__":
462 import argparse 505 import argparse
463 506
464 parser = argparse.ArgumentParser( 507 parser = argparse.ArgumentParser(
488 ) 531 )
489 532
490 args = parser.parse_args() 533 args = parser.parse_args()
491 schemaFile = args.input_schema 534 schemaFile = args.input_schema
492 outDir = args.out_dir 535 outDir = args.out_dir
493 536 Process(schemaFile, outDir)
494 tdico = GetTemplatingDictFromSchemaFilename(schemaFile)
495
496 tsTemplateFile = \
497 os.path.join(os.path.dirname(__file__), 'template.in.ts')
498 template = MakeTemplateFromFile(tsTemplateFile)
499 renderedTsCode = template.render(**tdico)
500 outputTsFile = os.path.join( \
501 outDir,str(tdico['rootName']) + "_generated.ts")
502 with open(outputTsFile,"wt",encoding='utf8') as outFile:
503 outFile.write(renderedTsCode)
504
505 cppTemplateFile = \
506 os.path.join(os.path.dirname(__file__), 'template.in.h')
507 template = MakeTemplateFromFile(cppTemplateFile)
508 renderedCppCode = template.render(**tdico)
509 outputCppFile = os.path.join( \
510 outDir, str(tdico['rootName']) + "_generated.hpp")
511 with open(outputCppFile,"wt",encoding='utf8') as outFile:
512 outFile.write(renderedCppCode)
513
514 # def GenEnumDecl(genc: GenCode, fullName: str, schema: Dict) -> None:
515 # """Writes the enumerations in genc"""
516 # enumDict:Dict=schema[fullName]
517 # # jinja2 template
518 # j2cppEnum = Template(trim(
519 # """ {{fullName}}
520 # {
521 # {% for key in enumDict.keys()%}
522 # {{key}},
523 # {%endfor%}
524 # };
525 # """))
526 # j2cppEnumR = j2cppEnum.render(locals())
527 # genc.cppEnums.write(j2cppEnumR)
528
529 # j2tsEnum = Template(trim(
530 # """ export {{fullName}}
531 # {
532 # {% for key in enumDict.keys()%}
533 # {{key}},
534 # {%endfor%}
535 # };
536 # """))
537 # j2cppEnumR = j2cppEnum.render(locals())
538 # genc.tsEnums.write(j2cppEnumR)
539
540
541
542 # def GetSerializationCode(typename: str,valueName: str, tempName: str)
543 # if IsPrimitiveType(typename) or IsTemplateCollection(typename):
544 # # no need to write code for the primitive types or collections.
545 # # It is handled in C++ by the template functions and in TS by
546 # # the JSON.stringify code.
547 # elif IsStructType(typename):
548 # pass
549
550 # def GenStructTypeDeclAndSerialize(genc: GenCode, type, schema) -> None:
551 # ######
552 # # CPP
553 # ######
554 # sampleCpp = """ struct Message1
555 # {
556 # int32_t a;
557 # std::string b;
558 # EnumMonth0 c;
559 # bool d;
560 # };
561
562 # Json::Value StoneSerialize(const Message1& value)
563 # {
564 # Json::Value result(Json::objectValue);
565 # result["a"] = StoneSerialize(value.a);
566 # result["b"] = StoneSerialize(value.b);
567 # result["c"] = StoneSerialize(value.c);
568 # result["d"] = StoneSerialize(value.d);
569 # return result;
570 # }
571 # """
572
573
574 # ######
575 # # TS
576 # ######
577 # sampleTs = """
578 # {
579 # export class Message1 {
580 # a: number;
581 # b: string;
582 # c: EnumMonth0;
583 # d: boolean;
584 # public StoneSerialize(): string {
585 # let container: object = {};
586 # container['type'] = 'Message1';
587 # container['value'] = this;
588 # return JSON.stringify(container);
589 # }
590 # };
591 # }
592 # """
593
594
595
596
597 # tsText: StringIO = StringIO()
598 # cppText: StringIO = StringIO()
599
600 # tsText.write("class %s\n" % typeDict["name"])
601 # tsText.write("{\n")
602
603 # cppText.write("struct %s\n" % typeDict["name"])
604 # cppText.write("{\n")
605
606 # """
607
608 # GenerateSerializationCode(typename,valueName)
609
610 # primitives:
611 # -----------
612 # int
613 # jsonValue val(objectInt);
614 # val.setValue("$name")
615 # parent.add(("$name",$name)
616 # double
617 # ...
618 # string
619 # ...
620
621 # collections:
622 # -----------
623 # dict { }
624
625 # serializeValue()
626 # """
627
628 # for i in range(len(typeDict["fields"])):
629 # field = typeDict["fields"][i]
630 # name = field["name"]
631 # tsType = GetTypeScriptTypenameFromCanonical(field["type"])
632 # tsText.write(" public %s %s;\n" % (tsType, name))
633 # cppType = GetCppTypenameFromCanonical(field["type"])
634 # cppText.write(" %s %s;\n" % (cppType, name))
635
636 # tsText.write("};\n\n")
637 # cppText.write("};\n\n")
638
639 # genc.tsStructs.write(tsText.getvalue())
640 # genc.cppStructs.write(cppText.getvalue())
641
642
643 # def GenerateCodeFromTsTemplate(genc)
644
645
646 # +-----------------------+
647 # | CODE GENERATION |
648 # +-----------------------+
649
650 # def GenPreambles(rootName: str, genc: GenCode) -> None:
651 # cppPreambleT = Template(trim(
652 # """// autogenerated by stonegentool on {{time.ctime()}}
653 # // for module {{rootName}}
654 # #include <cstdint>
655 # #include <string>
656 # #include <vector>
657 # #include <map>
658 # namespace {{rootName}}
659 # {
660 # Json::Value StoneSerialize(int32_t value)
661 # {
662 # Json::Value result(value);
663 # return result;
664 # }
665 # Json::Value StoneSerialize(double value)
666 # {
667 # Json::Value result(value);
668 # return result;
669 # }
670 # Json::Value StoneSerialize(bool value)
671 # {
672 # Json::Value result(value);
673 # return result;
674 # }
675 # Json::Value StoneSerialize(const std::string& value)
676 # {
677 # // the following is better than
678 # Json::Value result(value.data(),value.data()+value.size());
679 # return result;
680 # }
681 # template<typename T>
682 # Json::Value StoneSerialize(const std::map<std::string,T>& value)
683 # {
684 # Json::Value result(Json::objectValue);
685
686 # for (std::map<std::string, T>::const_iterator it = value.cbegin();
687 # it != value.cend(); ++it)
688 # {
689 # // it->first it->second
690 # result[it->first] = StoneSerialize(it->second);
691 # }
692 # return result;
693 # }
694 # template<typename T>
695 # Json::Value StoneSerialize(const std::vector<T>& value)
696 # {
697 # Json::Value result(Json::arrayValue);
698 # for (size_t i = 0; i < value.size(); ++i)
699 # {
700 # result.append(StoneSerialize(value[i]));
701 # }
702 # return result;
703 # }
704 # """
705 # cppPreambleR = cppPreambleT.render(locals())
706 # genc.cppPreamble.write(cppPreambleR)
707
708 # tsPreambleT = Template(trim(
709 # """// autogenerated by stonegentool on {{time.ctime()}}
710 # // for module {{rootName}}
711
712 # namespace {{rootName}}
713 # {
714 # """
715 # tsPreambleR = tsPreambleT.render(locals())
716 # genc.tsPreamble.write(tsPreambleR)
717
718 # def ComputeOrder_ProcessStruct( \
719 # genOrder: List[str], name:str, schema: Dict[str, str]) -> None:
720 # # let's generate the code according to the
721 # struct = schema[name]
722
723 # if not IsStructType(name):
724 # raise Exception(f'{typename} should start with "struct "')
725
726