comparison Resources/CodeGeneration/stonegentool.py @ 470:db093eb6b29d bgo-commands-codegen

Ongoing code generation tool
author bgo-osimis
date Tue, 12 Feb 2019 16:49:28 +0100
parents 52549faf47ba
children 125c19b294e3
comparison
equal deleted inserted replaced
469:52549faf47ba 470:db093eb6b29d
1 from __future__ import print_function 1 from typing import List,Dict
2 import sys, json 2 import sys, json
3 3
4 def LoadSchema(file_path : str): 4 def LoadSchema(file_path : str):
5 with open(file_path, 'r') as fp: 5 with open(file_path, 'r') as fp:
6 obj = json.load(fp) 6 obj = json.load(fp)
7 return obj 7 return obj
8 8
9 class Type: 9 class Type:
10 def __init__(self, canonicalTypeName:str, dependentTypes:str): 10 def __init__(self, canonicalTypeName:str, kind:str):
11 allowedTypeKinds = ["primitive","enum","struct","collection"]
11 """dependent type is the list of canonical types this type depends on. 12 """dependent type is the list of canonical types this type depends on.
12 For instance, vector<map<string,int32>> depends on map<string,int32> 13 For instance, vector<map<string,int32>> depends on map<string,int32>
13 that, in turn, depends on string and int32 that, in turn, depend on 14 that, in turn, depends on string and int32 that, in turn, depend on
14 nothing""" 15 nothing"""
15 self.canonicalTypeName = canonicalTypeName 16 self.canonicalTypeName = canonicalTypeName
17 assert(kind in allowedTypeKinds)
18
19 def setDependentTypes(self, dependentTypes:List[Type]) -> None:
16 self.dependentTypes = dependentTypes 20 self.dependentTypes = dependentTypes
17 def getDependentTypes(self): 21
22 def getDependentTypes(self) -> List[Type]:
18 return self.dependentTypes 23 return self.dependentTypes
19 def getCppTypeName(self): 24
25 def getCppTypeName(self) -> str:
20 # C++: prefix map vector and string with std::map, std::vector and std::string 26 # C++: prefix map vector and string with std::map, std::vector and std::string
21 # replace int32 by int32_t 27 # replace int32 by int32_t
22 # replace float32 by float 28 # replace float32 by float
23 # replace float64 by double 29 # replace float64 by double
24 retVal : str = self.canonicalTypeName.replace("map","std::map") 30 retVal : str = self.canonicalTypeName.replace("map","std::map")
25 retVal : str = self.canonicalTypeName.replace("vector","std::vector") 31 retVal : str = self.canonicalTypeName.replace("vector","std::vector")
26 retVal : str = self.canonicalTypeName.replace("int32","int32_t") 32 retVal : str = self.canonicalTypeName.replace("int32","int32_t")
27 retVal : str = self.canonicalTypeName.replace("float32","float") 33 retVal : str = self.canonicalTypeName.replace("float32","float")
28 retVal : str = self.canonicalTypeName.replace("float64","double") 34 retVal : str = self.canonicalTypeName.replace("float64","double")
29 return retVal 35 return retVal
30 def getTypeScriptTypeName(self): 36
37 def getTypeScriptTypeName(self) -> str:
31 # TS: replace vector with Array and map with Map 38 # TS: replace vector with Array and map with Map
32 # string remains string 39 # string remains string
33 # replace int32 by number 40 # replace int32 by number
34 # replace float32 by number 41 # replace float32 by number
35 # replace float64 by number 42 # replace float64 by number
41 retVal : str = self.canonicalTypeName.replace("bool","boolean") 48 retVal : str = self.canonicalTypeName.replace("bool","boolean")
42 return retVal 49 return retVal
43 50
44 class Schema: 51 class Schema:
45 def __init__(self, root_prefix : str, defined_types : List[Type]): 52 def __init__(self, root_prefix : str, defined_types : List[Type]):
46 self.root_prefix : str = root_prefix 53 self.rootName : str = root_prefix
47 self.defined_types : str = defined_types 54 self.definedTypes : str = defined_types
48 55
49 def ProcessSchemaPaths(inputSchemas : list[str]) 56 def CheckTypeSchema(definedType : Dict) -> None:
57 allowedDefinedTypeKinds = ["enum","struct"]
58 if not definedType.has_key('name'):
59 raise Exception("type lacks the 'name' key")
60 name = definedType['name']
61 if not definedType.has_key('kind'):
62 raise Exception(f"type {name} lacks the 'kind' key")
63 kind = definedType['kind']
64 if not (kind in allowedDefinedTypeKinds):
65 raise Exception(f"type {name} : kind {kind} is not allowed. It must be one of {allowedDefinedTypeKinds}")
66
67 if not definedType.has_key('fields'):
68 raise Exception("type {name} lacks the 'fields' key")
69
70 # generic check on all kinds of types
71 fields = definedType['fields']
72 for field in fields:
73 fieldName = field['name']
74 if not field.has_key('name'):
75 raise Exception("field in type {name} lacks the 'name' key")
76
77 # fields in struct must have types
78 if kind == 'struct':
79 for field in fields:
80 fieldName = field['name']
81 if not field.has_key('type'):
82 raise Exception(f"field {fieldName} in type {name} lacks the 'type' key")
83
84 def CheckSchemaSchema(schema : Dict) -> None:
85 if not schema.has_key('root_name'):
86 raise Exception("schema lacks the 'root_name' key")
87 if not schema.has_key('types'):
88 raise Exception("schema lacks the 'types' key")
89 for definedType in schema['types']:
90 CheckTypeSchema(definedType)
91
92 def CreateAndCacheTypeObject(allTypes : Dict[str,Type], typeDict : Dict) -> None:
93 """This does not set the dependentTypes field"""
94 typeName : str = typeDict['name']
95 if allTypes.has_key(typeName):
96 raise Exception(f'Type {name} is defined more than once!')
97 else:
98 typeObject = Type(typeName, typeDict['kind'])
99 allTypes[typeName] = typeObject
50 100
51 101
52 def ProcessSchemas(schemaPaths): 102 def ParseTemplateType(typeName) -> (bool,str,str):
53 schemas = [] 103 """ If the type is a template like "SOMETHING<SOME<THING,EL<SE>>>", then
54 for schemaPath in schemaPaths: 104 it returns (true,"SOMETHING","SOME<THING,EL<SE>>")
55 schemas.append(LoadSchema(schemaPath)) 105 otherwise it returns (false,"","")"""
56 return schemas 106
107
108
109
110 def GetCachedTypeObject(allTypes : Dict[str,Type], typeName : str) -> Type:
111 if allTypes.has_key('typeName')
112 return allTypes['typeName']
113 # if we reach this point, it means the type is NOT a struct or an enum.
114 # it is another (non directly user-defined) type that we must parse and create
115 # let's do it
116
117
118 def ComputeTopTypeTree(allTypes : Dict[str,Type], typeDict : Dict) -> None:
119 """Now that all the types we might be referring to are known (either structs
120 and enums already created OR primitive types OR collections), we can create
121 the whole dependency tree"""
122
123 if typeDict['kind'] == 'struct':
124 typeName : str = typeDict['name']
125 typeObject : Type = allTypes[typeName]
126 typeFields : List[Dict] = typeDict['fields']
127 dependentTypes = []
128 for typeField : Dict in typeFields:
129 typeFieldObject = CreateTypeObject(allTypes, typeField['name'])
130 dependentTypes.append(typeFieldObject)
131 elif typeDict['kind'] == 'enum':
132 # the enum objects are already created and have no dependencies
133 else:
134 raise Exception("Internal error: ComputeTopTypeTree() can only be called on the defined types (enums and structs)")
135
136
137
138
139 def ProcessSchema(schema : dict) -> None:
140 CheckSchemaSchema(schema)
141 rootName : str = schema['root_name']
142 definedTypes : list = schema['types']
143
144 # gather defined types
145 allTypes : Dict[str,Type] = {}
146
147 # gather all defined types (first pass) : structs and enums
148 # we do not parse the subtypes now as to not mandate
149 # a particular declaration order
150 for definedType in definedTypes:
151 CreateAndCacheTypeObject(allTypes,definedType)
152
153 # now we have objects for all the defined types
154 # let's build the whole type dependency tree
155 for definedType in definedTypes:
156 ComputeTypeTree(allTypes,definedType)
157
158
159
160
57 161
58 if __name__ == '__main__': 162 if __name__ == '__main__':
59 import argparse 163 import argparse
60 parser = argparse.ArgumentParser(usage = """stonegentool.py [-h] [-o OUT_DIR] [-v] input_schemas 164 parser = argparse.ArgumentParser(usage = """stonegentool.py [-h] [-o OUT_DIR] [-v] input_schemas
61 EXAMPLE: python command_gen.py -o "generated_files/" "mainSchema.json,App Specific Commands.json" """) 165 EXAMPLE: python command_gen.py -o "generated_files/" "mainSchema.json,App Specific Commands.json" """)
62 parser.add_argument("input_schemas", type=str, 166 parser.add_argument("input_schema", type=str,
63 help = "one or more schema files, as a comma-separated list of paths") 167 help = "path to the schema file")
64 parser.add_argument("-o", "--out_dir", type=str, default=".", 168 parser.add_argument("-o", "--out_dir", type=str, default=".",
65 help = """path of the directory where the files 169 help = """path of the directory where the files
66 will be generated. Default is current 170 will be generated. Default is current
67 working folder""") 171 working folder""")
68 parser.add_argument("-v", "--verbosity", action="count", default=0, 172 parser.add_argument("-v", "--verbosity", action="count", default=0,
69 help = """increase output verbosity (0 == errors 173 help = """increase output verbosity (0 == errors
70 only, 1 == some verbosity, 2 == nerd 174 only, 1 == some verbosity, 2 == nerd
71 mode""") 175 mode""")
72 176
73 args = parser.parse_args() 177 args = parser.parse_args()
74 inputSchemas = args.input_schemas.split(",") 178 inputSchemaFilename = args.input_schema
75 outDir = args.out_dir 179 outDir = args.out_dir
76 180
77 print("input schemas = " + str(inputSchemas)) 181 print("input schema = " + str(inputSchemaFilename))
78 print("out dir = " + str(outDir)) 182 print("out dir = " + str(outDir))
79 183
80 ProcessSchemaPaths(inputSchemas) 184 ProcessSchema(LoadSchema(inputSchemaFilename))
81 185
82
83 186
84 ################### 187 ###################
85 ## ATTIC ## 188 ## ATTIC ##
86 ################### 189 ###################
87 190