Mercurial > hg > orthanc-stone
comparison Resources/CodeGeneration/stonegentool.py @ 472:3db3289e1c25 bgo-commands-codegen
Ongoing codegen work
author | bgo-osimis |
---|---|
date | Wed, 13 Feb 2019 06:46:36 +0100 |
parents | 125c19b294e3 |
children | 628941d63b8c |
comparison
equal
deleted
inserted
replaced
471:125c19b294e3 | 472:3db3289e1c25 |
---|---|
1 from typing import List,Dict | 1 from typing import List,Dict,Set |
2 import sys | 2 import sys |
3 import json | 3 import json |
4 import re | 4 import re |
5 | |
6 """ | |
7 1 2 3 4 5 6 7 | |
8 12345678901234567890123456789012345678901234567890123456789012345678901234567890 | |
9 """ | |
5 | 10 |
6 def LoadSchema(file_path : str): | 11 def LoadSchema(file_path : str): |
7 with open(file_path, 'r') as fp: | 12 with open(file_path, 'r') as fp: |
8 obj = json.load(fp) | 13 obj = json.load(fp) |
9 return obj | 14 return obj |
10 | 15 |
11 class Type: | 16 # class Type: |
12 def __init__(self, canonicalTypeName:str, kind:str): | 17 # def __init__(self, canonicalTypeName:str, kind:str): |
13 allowedTypeKinds = ["primitive","enum","struct","collection"] | 18 # allowedTypeKinds = ["primitive","enum","struct","collection"] |
14 """dependent type is the list of canonical types this type depends on. | 19 # """dependent type is the list of canonical types this type depends on. |
15 For instance, vector<map<string,int32>> depends on map<string,int32> | 20 # For instance, vector<map<string,int32>> depends on map<string,int32> |
16 that, in turn, depends on string and int32 that, in turn, depend on | 21 # that, in turn, depends on string and int32 that, in turn, depend on |
17 nothing""" | 22 # nothing""" |
18 self.canonicalTypeName = canonicalTypeName | 23 # self.canonicalTypeName = canonicalTypeName |
19 assert(kind in allowedTypeKinds) | 24 # assert(kind in allowedTypeKinds) |
20 | 25 |
21 def setDependentTypes(self, dependentTypes:List[Type]) -> None: | 26 # def setDependentTypes(self, dependentTypes:List[Type]) -> None: |
22 self.dependentTypes = dependentTypes | 27 # self.dependentTypes = dependentTypes |
23 | 28 |
24 def getDependentTypes(self) -> List[Type]: | 29 # def getDependentTypes(self) -> List[Type]: |
25 return self.dependentTypes | 30 # return self.dependentTypes |
26 | 31 |
27 def getCppTypeName(self) -> str: | 32 def GetCppTypeNameFromCanonical(canonicalTypeName : str) -> str: |
28 # C++: prefix map vector and string with std::map, std::vector and std::string | 33 # C++: prefix map vector and string with std::map, std::vector and |
34 # std::string | |
29 # replace int32 by int32_t | 35 # replace int32 by int32_t |
30 # replace float32 by float | 36 # replace float32 by float |
31 # replace float64 by double | 37 # replace float64 by double |
32 retVal : str = self.canonicalTypeName.replace("map","std::map") | 38 retVal : str = canonicalTypeName.replace("map","std::map") |
33 retVal : str = self.canonicalTypeName.replace("vector","std::vector") | 39 retVal : str = canonicalTypeName.replace("vector","std::vector") |
34 retVal : str = self.canonicalTypeName.replace("int32","int32_t") | 40 retVal : str = canonicalTypeName.replace("int32","int32_t") |
35 retVal : str = self.canonicalTypeName.replace("float32","float") | 41 retVal : str = canonicalTypeName.replace("float32","float") |
36 retVal : str = self.canonicalTypeName.replace("float64","double") | 42 retVal : str = canonicalTypeName.replace("float64","double") |
37 return retVal | 43 return retVal |
38 | 44 |
39 def getTypeScriptTypeName(self) -> str: | 45 def GetTypeScriptTypeNameFromCanonical(canonicalTypeName : str) -> str: |
40 # TS: replace vector with Array and map with Map | 46 # TS: replace vector with Array and map with Map |
41 # string remains string | 47 # string remains string |
42 # replace int32 by number | 48 # replace int32 by number |
43 # replace float32 by number | 49 # replace float32 by number |
44 # replace float64 by number | 50 # replace float64 by number |
45 retVal : str = self.canonicalTypeName.replace("map","Map") | 51 retVal : str = canonicalTypeName.replace("map","Map") |
46 retVal : str = self.canonicalTypeName.replace("vector","Array") | 52 retVal : str = canonicalTypeName.replace("vector","Array") |
47 retVal : str = self.canonicalTypeName.replace("int32","number") | 53 retVal : str = canonicalTypeName.replace("int32","number") |
48 retVal : str = self.canonicalTypeName.replace("float32","number") | 54 retVal : str = canonicalTypeName.replace("float32","number") |
49 retVal : str = self.canonicalTypeName.replace("float64","number") | 55 retVal : str = canonicalTypeName.replace("float64","number") |
50 retVal : str = self.canonicalTypeName.replace("bool","boolean") | 56 retVal : str = canonicalTypeName.replace("bool","boolean") |
51 return retVal | 57 return retVal |
52 | 58 |
53 class Schema: | 59 # class Schema: |
54 def __init__(self, root_prefix : str, defined_types : List[Type]): | 60 # def __init__(self, root_prefix : str, defined_types : List[Type]): |
55 self.rootName : str = root_prefix | 61 # self.rootName : str = root_prefix |
56 self.definedTypes : str = defined_types | 62 # self.definedTypes : str = defined_types |
57 | 63 |
58 def CheckTypeSchema(definedType : Dict) -> None: | 64 def CheckTypeSchema(definedType : Dict) -> None: |
59 allowedDefinedTypeKinds = ["enum","struct"] | 65 allowedDefinedTypeKinds = ["enum","struct"] |
60 if not definedType.has_key('name'): | 66 if not definedType.has_key('name'): |
61 raise Exception("type lacks the 'name' key") | 67 raise Exception("type lacks the 'name' key") |
62 name = definedType['name'] | 68 name = definedType['name'] |
63 if not definedType.has_key('kind'): | 69 if not definedType.has_key('kind'): |
64 raise Exception(f"type {name} lacks the 'kind' key") | 70 raise Exception(f"type {name} lacks the 'kind' key") |
65 kind = definedType['kind'] | 71 kind = definedType['kind'] |
66 if not (kind in allowedDefinedTypeKinds): | 72 if not (kind in allowedDefinedTypeKinds): |
67 raise Exception(f"type {name} : kind {kind} is not allowed. It must be one of {allowedDefinedTypeKinds}") | 73 raise Exception(f"type {name} : kind {kind} is not allowed. " + |
74 f"It must be one of {allowedDefinedTypeKinds}") | |
68 | 75 |
69 if not definedType.has_key('fields'): | 76 if not definedType.has_key('fields'): |
70 raise Exception("type {name} lacks the 'fields' key") | 77 raise Exception("type {name} lacks the 'fields' key") |
71 | 78 |
72 # generic check on all kinds of types | 79 # generic check on all kinds of types |
79 # fields in struct must have types | 86 # fields in struct must have types |
80 if kind == 'struct': | 87 if kind == 'struct': |
81 for field in fields: | 88 for field in fields: |
82 fieldName = field['name'] | 89 fieldName = field['name'] |
83 if not field.has_key('type'): | 90 if not field.has_key('type'): |
84 raise Exception(f"field {fieldName} in type {name} lacks the 'type' key") | 91 raise Exception(f"field {fieldName} in type {name} " |
92 + "lacks the 'type' key") | |
85 | 93 |
86 def CheckSchemaSchema(schema : Dict) -> None: | 94 def CheckSchemaSchema(schema : Dict) -> None: |
87 if not schema.has_key('root_name'): | 95 if not schema.has_key('root_name'): |
88 raise Exception("schema lacks the 'root_name' key") | 96 raise Exception("schema lacks the 'root_name' key") |
89 if not schema.has_key('types'): | 97 if not schema.has_key('types'): |
90 raise Exception("schema lacks the 'types' key") | 98 raise Exception("schema lacks the 'types' key") |
91 for definedType in schema['types']: | 99 for definedType in schema['types']: |
92 CheckTypeSchema(definedType) | 100 CheckTypeSchema(definedType) |
93 | 101 |
94 def CreateAndCacheTypeObject(allTypes : Dict[str,Type], typeDict : Dict) -> None: | 102 # def CreateAndCacheTypeObject(allTypes : Dict[str,Type], typeDict : Dict) -> None: |
95 """This does not set the dependentTypes field""" | 103 # """This does not set the dependentTypes field""" |
96 typeName : str = typeDict['name'] | 104 # typeName : str = typeDict['name'] |
97 if allTypes.has_key(typeName): | 105 # if allTypes.has_key(typeName): |
98 raise Exception(f'Type {typeName} is defined more than once!') | 106 # raise Exception(f'Type {typeName} is defined more than once!') |
99 else: | 107 # else: |
100 typeObject = Type(typeName, typeDict['kind']) | 108 # typeObject = Type(typeName, typeDict['kind']) |
101 allTypes[typeName] = typeObject | 109 # allTypes[typeName] = typeObject |
102 | |
103 | |
104 | 110 |
105 def EatToken(sentence : str) -> (str,str): | 111 def EatToken(sentence : str) -> (str,str): |
106 """splits "A,B,C" into "A" and "B,C" where A, B and C are type names | 112 """splits "A,B,C" into "A" and "B,C" where A, B and C are type names |
107 (including templates) like "int32", "TotoTutu", or | 113 (including templates) like "int32", "TotoTutu", or |
108 "map<map<int32,vector<string>>,map<string,int32>>" """ | 114 "map<map<int32,vector<string>>,map<string,int32>>" """ |
109 token = [] | 115 |
110 if sentence.count('<') != sentence.count('>'): | 116 if sentence.count('<') != sentence.count('>'): |
111 raise Exception(f"Error in the partial template type list {sentence}. The number of < and > do not match!") | 117 raise Exception(f"Error in the partial template type list {sentence}." |
118 + " The number of < and > do not match!") | |
112 | 119 |
113 # the template level we're currently in | 120 # the template level we're currently in |
114 templateLevel = 0 | 121 templateLevel = 0 |
115 for i in len(sentence): | 122 for i in len(sentence): |
116 if (sentence[i] == ",") and (templateLevel == 0): | 123 if (sentence[i] == ",") and (templateLevel == 0): |
137 while stillStuffToEat: | 144 while stillStuffToEat: |
138 firstToken,restOfString = EatToken(restOfString) | 145 firstToken,restOfString = EatToken(restOfString) |
139 tokenList.append(firstToken) | 146 tokenList.append(firstToken) |
140 return tokenList | 147 return tokenList |
141 | 148 |
142 templateRegex = re.compile(r"([a-zA-Z0-9_]*[a-zA-Z0-9_]*)<([a-zA-Z0-9_,:<>]+)>") | 149 templateRegex = \ |
150 re.compile(r"([a-zA-Z0-9_]*[a-zA-Z0-9_]*)<([a-zA-Z0-9_,:<>]+)>") | |
151 | |
143 def ParseTemplateType(typeName) -> (bool,str,List[str]): | 152 def ParseTemplateType(typeName) -> (bool,str,List[str]): |
144 """ If the type is a template like "SOMETHING<SOME<THING,EL<SE>>>", then | 153 """ If the type is a template like "SOMETHING<SOME<THING,EL<SE>>>", then |
145 it returns (true,"SOMETHING","SOME<THING,EL<SE>>") | 154 it returns (true,"SOMETHING","SOME<THING,EL<SE>>") |
146 otherwise it returns (false,"","")""" | 155 otherwise it returns (false,"","")""" |
147 | 156 |
156 # we need to split with the commas that are outside of the defined types | 165 # we need to split with the commas that are outside of the defined types |
157 # simply splitting at commas won't work | 166 # simply splitting at commas won't work |
158 listOfDependentTypes = SplitListOfTypes(matches.group(2)) | 167 listOfDependentTypes = SplitListOfTypes(matches.group(2)) |
159 return (True,matches.group(1),listOfDependentTypes) | 168 return (True,matches.group(1),listOfDependentTypes) |
160 | 169 |
161 def GetPrimitiveType(typeName : str) -> Type: | 170 |
162 if allTypes.has_key(typeName): | 171 # def GetPrimitiveType(typeName : str) -> Type: |
163 return allTypes[typeName] | 172 # if allTypes.has_key(typeName): |
164 else: | 173 # return allTypes[typeName] |
165 primitiveTypes = ['int32', 'float32', 'float64', 'string'] | 174 # else: |
166 if not (typeName in primitiveTypes): | 175 # primitiveTypes = ['int32', 'float32', 'float64', 'string'] |
167 raise Exception(f"Type {typeName} is unknown.") | 176 # if not (typeName in primitiveTypes): |
168 typeObject = Type(typeName,'primitive') | 177 # raise Exception(f"Type {typeName} is unknown.") |
169 # there are no dependent types in a primitive type --> Type object | 178 # typeObject = Type(typeName,'primitive') |
170 # constrution is finished at this point | 179 # # there are no dependent types in a primitive type --> Type object |
171 allTypes[typeName] = typeObject | 180 # # constrution is finished at this point |
172 return typeObject | 181 # allTypes[typeName] = typeObject |
182 # return typeObject | |
173 | 183 |
174 def ProcessTypeTree( | 184 def ProcessTypeTree( |
175 ancestors : List[str] | 185 ancestors : List[str] |
176 , generationQueue : List[str] | 186 , genOrderQueue : List[str] |
177 , structTypes : Dict[str,Dict], typeName : str) -> None: | 187 , structTypes : Dict[str,Dict], typeName : str) -> None: |
178 if typeName in ancestors: | 188 if typeName in ancestors: |
179 raise Exception(f"Cyclic dependency chain found: the last of {ancestors} depends on {typeName} that is already in the list.") | 189 raise Exception(f"Cyclic dependency chain found: the last of {ancestors} " |
180 | 190 + f"depends on {typeName} that is already in the list.") |
181 if not (typeName in generationQueue): | 191 |
192 if not (typeName in genOrderQueue): | |
182 # if we reach this point, it means the type is NOT a struct or an enum. | 193 # if we reach this point, it means the type is NOT a struct or an enum. |
183 # it is another (non directly user-defined) type that we must parse and create | 194 # it is another (non directly user-defined) type that we must parse and |
184 # let's do it | 195 # create. Let's do it! |
185 dependentTypes = [] | 196 (isTemplate,_,parameters) = ParseTemplateType(typeName) |
186 (isTemplate,templateType,parameters) = ParseTemplateType(typeName) | |
187 if isTemplate: | 197 if isTemplate: |
188 dependentTypeNames : List[str] = SplitListOfTypes(parameters) | 198 dependentTypeNames : List[str] = SplitListOfTypes(parameters) |
189 for dependentTypeName in dependentTypeNames: | 199 for dependentTypeName in dependentTypeNames: |
190 # childAncestors = ancestors.copy() NO TEMPLATE ANCESTOR!!! | 200 # childAncestors = ancestors.copy() NO TEMPLATE ANCESTOR!!! |
191 # childAncestors.append(typeName) | 201 # childAncestors.append(typeName) |
192 ProcessTypeTree(ancestors, processedTypes, structTypes, dependentTypeName) | 202 ProcessTypeTree(ancestors, genOrderQueue, |
203 structTypes, dependentTypeName) | |
193 else: | 204 else: |
194 if structTypes.has_key(typeName): | 205 if structTypes.has_key(typeName): |
195 ProcesStructType_DepthFirstRecursive(generationQueue, structTypes, | 206 ProcessStructType_DepthFirstRecursive(genOrderQueue, structTypes, |
196 structTypes[typeName]) | 207 structTypes[typeName]) |
197 | 208 |
198 def ProcesStructType_DepthFirstRecursive( | 209 def ProcessStructType_DepthFirstRecursive( |
199 generationQueue : List[str], structTypes : Dict[str,Dict] | 210 genOrderQueue : List[str], structTypes : Dict[str,Dict] |
200 , typeDict : Dict) -> None: | 211 , typeDict : Dict) -> None: |
201 # let's generate the code according to the | 212 # let's generate the code according to the |
202 typeName : str = typeDict['name'] | 213 typeName : str = typeDict['name'] |
203 if typeDict['kind'] != 'struct': | 214 if typeDict['kind'] != 'struct': |
204 raise Exception(f"Unexpected kind '{typeDict['kind']}' for " + | 215 raise Exception(f"Unexpected kind '{typeDict['kind']}' for " + |
205 "type '{typeName}'") | 216 "type '{typeName}'") |
206 typeFields : List[Dict] = typeDict['fields'] | 217 typeFields : List[Dict] = typeDict['fields'] |
207 for typeField in typeFields: | 218 for typeField in typeFields: |
208 ancestors = [typeName] | 219 ancestors = [typeName] |
209 ProcessTypeTree(ancestors, generationQueue | 220 ProcessTypeTree(ancestors, genOrderQueue |
210 , structTypes, typeField['name']) | 221 , structTypes, typeField['name']) |
211 # now we're pretty sure our dependencies have been processed, | 222 # now we're pretty sure our dependencies have been processed, |
212 # we can start marking our code for generation | 223 # we can start marking our code for generation |
213 generationQueue.append(typeName) | 224 genOrderQueue.append(typeName) |
225 | |
226 def ProcessEnumerationType(processedTypes, definedType) -> None: | |
227 print(f"About to process enumeration: {definedType['name']}") | |
214 | 228 |
215 def ProcessSchema(schema : dict) -> None: | 229 def ProcessSchema(schema : dict) -> None: |
216 CheckSchemaSchema(schema) | 230 CheckSchemaSchema(schema) |
217 rootName : str = schema['root_name'] | 231 rootName : str = schema['root_name'] |
218 definedTypes : list = schema['types'] | 232 definedTypes : list = schema['types'] |
219 | 233 |
220 # mark already processed types | 234 print(f"Processing schema. rootName = f{rootName}") |
221 processedTypes : set[str] = set() | 235 # this will be filled with the generation queue. That is, the type |
236 # names in the order where they must be defined. | |
237 genOrderQueue : Set = set() | |
222 | 238 |
223 # the struct names are mapped to their JSON dictionary | 239 # the struct names are mapped to their JSON dictionary |
224 structTypes : Dict[str,Dict] = {} | 240 structTypes : Dict[str,Dict] = {} |
225 | 241 |
226 # the order here is the generation order | 242 # the order here is the generation order |
227 for definedType in definedTypes: | 243 for definedType in definedTypes: |
228 if definedType['kind'] == 'enum': | 244 if definedType['kind'] == 'enum': |
229 ProcessEnumerationType(processedTypes, definedType); | 245 ProcessEnumerationType(genOrderQueue, definedType) |
230 | 246 |
231 # the order here is NOT the generation order: the types | 247 # the order here is NOT the generation order: the types |
232 # will be processed according to their dependency graph | 248 # will be processed according to their dependency graph |
233 for definedType in definedTypes: | 249 for definedType in definedTypes: |
234 if definedType['kind'] == 'struct': | 250 if definedType['kind'] == 'struct': |
235 structTypes[definedType['name']] = definedType | 251 structTypes[definedType['name']] = definedType |
236 ProcesStructType_DepthFirstRecursive(processedTypes,structTypes,definedType) | 252 ProcessStructType_DepthFirstRecursive(genOrderQueue,structTypes, |
253 definedType) | |
254 | |
255 print(f"genOrderQueue = {genOrderQueue}") | |
237 | 256 |
238 if __name__ == '__main__': | 257 if __name__ == '__main__': |
239 import argparse | 258 import argparse |
240 parser = argparse.ArgumentParser(usage = """stonegentool.py [-h] [-o OUT_DIR] [-v] input_schemas | 259 parser = argparse.ArgumentParser( |
241 EXAMPLE: python command_gen.py -o "generated_files/" "mainSchema.json,App Specific Commands.json" """) | 260 usage = """stonegentool.py [-h] [-o OUT_DIR] [-v] input_schemas |
261 EXAMPLE: python command_gen.py -o "generated_files/" """ | |
262 + """ "mainSchema.json,App Specific Commands.json" """) | |
242 parser.add_argument("input_schema", type=str, | 263 parser.add_argument("input_schema", type=str, |
243 help = "path to the schema file") | 264 help = "path to the schema file") |
244 parser.add_argument("-o", "--out_dir", type=str, default=".", | 265 parser.add_argument("-o", "--out_dir", type=str, default=".", |
245 help = """path of the directory where the files | 266 help = """path of the directory where the files |
246 will be generated. Default is current | 267 will be generated. Default is current |