Mercurial > hg > orthanc-stone
comparison Resources/CodeGeneration/stonegentool.py @ 471:125c19b294e3 bgo-commands-codegen
Ongoing codegen work
author | bgo-osimis |
---|---|
date | Wed, 13 Feb 2019 06:24:35 +0100 |
parents | db093eb6b29d |
children | 3db3289e1c25 |
comparison
equal
deleted
inserted
replaced
470:db093eb6b29d | 471:125c19b294e3 |
---|---|
1 from typing import List,Dict | 1 from typing import List,Dict |
2 import sys, json | 2 import sys |
3 import json | |
4 import re | |
3 | 5 |
4 def LoadSchema(file_path : str): | 6 def LoadSchema(file_path : str): |
5 with open(file_path, 'r') as fp: | 7 with open(file_path, 'r') as fp: |
6 obj = json.load(fp) | 8 obj = json.load(fp) |
7 return obj | 9 return obj |
91 | 93 |
92 def CreateAndCacheTypeObject(allTypes : Dict[str,Type], typeDict : Dict) -> None: | 94 def CreateAndCacheTypeObject(allTypes : Dict[str,Type], typeDict : Dict) -> None: |
93 """This does not set the dependentTypes field""" | 95 """This does not set the dependentTypes field""" |
94 typeName : str = typeDict['name'] | 96 typeName : str = typeDict['name'] |
95 if allTypes.has_key(typeName): | 97 if allTypes.has_key(typeName): |
96 raise Exception(f'Type {name} is defined more than once!') | 98 raise Exception(f'Type {typeName} is defined more than once!') |
97 else: | 99 else: |
98 typeObject = Type(typeName, typeDict['kind']) | 100 typeObject = Type(typeName, typeDict['kind']) |
99 allTypes[typeName] = typeObject | 101 allTypes[typeName] = typeObject |
100 | 102 |
101 | 103 |
102 def ParseTemplateType(typeName) -> (bool,str,str): | 104 |
105 def EatToken(sentence : str) -> (str,str): | |
106 """splits "A,B,C" into "A" and "B,C" where A, B and C are type names | |
107 (including templates) like "int32", "TotoTutu", or | |
108 "map<map<int32,vector<string>>,map<string,int32>>" """ | |
109 token = [] | |
110 if sentence.count('<') != sentence.count('>'): | |
111 raise Exception(f"Error in the partial template type list {sentence}. The number of < and > do not match!") | |
112 | |
113 # the template level we're currently in | |
114 templateLevel = 0 | |
115 for i in len(sentence): | |
116 if (sentence[i] == ",") and (templateLevel == 0): | |
117 return (sentence[0:i],sentence[i+1:]) | |
118 elif (sentence[i] == "<"): | |
119 templateLevel += 1 | |
120 elif (sentence[i] == ">"): | |
121 templateLevel -= 1 | |
122 return (sentence,"") | |
123 | |
124 def SplitListOfTypes(typeName : str) -> List[str]: | |
125 """Splits something like | |
126 vector<string>,int32,map<string,map<string,int32>> | |
127 in: | |
128 - vector<string> | |
129 - int32 | |
130 map<string,map<string,int32>> | |
131 | |
132 This is not possible with a regex so | |
133 """ | |
134 stillStuffToEat : bool = True | |
135 tokenList = [] | |
136 restOfString = typeName | |
137 while stillStuffToEat: | |
138 firstToken,restOfString = EatToken(restOfString) | |
139 tokenList.append(firstToken) | |
140 return tokenList | |
141 | |
142 templateRegex = re.compile(r"([a-zA-Z0-9_]*[a-zA-Z0-9_]*)<([a-zA-Z0-9_,:<>]+)>") | |
143 def ParseTemplateType(typeName) -> (bool,str,List[str]): | |
103 """ If the type is a template like "SOMETHING<SOME<THING,EL<SE>>>", then | 144 """ If the type is a template like "SOMETHING<SOME<THING,EL<SE>>>", then |
104 it returns (true,"SOMETHING","SOME<THING,EL<SE>>") | 145 it returns (true,"SOMETHING","SOME<THING,EL<SE>>") |
105 otherwise it returns (false,"","")""" | 146 otherwise it returns (false,"","")""" |
106 | 147 |
107 | 148 # let's remove all whitespace from the type |
108 | 149 # split without argument uses any whitespace string as separator |
109 | 150 # (space, tab, newline, return or formfeed) |
110 def GetCachedTypeObject(allTypes : Dict[str,Type], typeName : str) -> Type: | 151 typeName = "".join(typeName.split()) |
111 if allTypes.has_key('typeName') | 152 matches = templateRegex.match(typeName) |
112 return allTypes['typeName'] | 153 if matches == None: |
113 # if we reach this point, it means the type is NOT a struct or an enum. | 154 return (False,"","") |
114 # it is another (non directly user-defined) type that we must parse and create | 155 else: |
115 # let's do it | 156 # we need to split with the commas that are outside of the defined types |
116 | 157 # simply splitting at commas won't work |
117 | 158 listOfDependentTypes = SplitListOfTypes(matches.group(2)) |
118 def ComputeTopTypeTree(allTypes : Dict[str,Type], typeDict : Dict) -> None: | 159 return (True,matches.group(1),listOfDependentTypes) |
119 """Now that all the types we might be referring to are known (either structs | 160 |
120 and enums already created OR primitive types OR collections), we can create | 161 def GetPrimitiveType(typeName : str) -> Type: |
121 the whole dependency tree""" | 162 if allTypes.has_key(typeName): |
122 | 163 return allTypes[typeName] |
123 if typeDict['kind'] == 'struct': | 164 else: |
165 primitiveTypes = ['int32', 'float32', 'float64', 'string'] | |
166 if not (typeName in primitiveTypes): | |
167 raise Exception(f"Type {typeName} is unknown.") | |
168 typeObject = Type(typeName,'primitive') | |
169 # there are no dependent types in a primitive type --> Type object | |
170 # constrution is finished at this point | |
171 allTypes[typeName] = typeObject | |
172 return typeObject | |
173 | |
174 def ProcessTypeTree( | |
175 ancestors : List[str] | |
176 , generationQueue : List[str] | |
177 , structTypes : Dict[str,Dict], typeName : str) -> None: | |
178 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.") | |
180 | |
181 if not (typeName in generationQueue): | |
182 # 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 | |
184 # let's do it | |
185 dependentTypes = [] | |
186 (isTemplate,templateType,parameters) = ParseTemplateType(typeName) | |
187 if isTemplate: | |
188 dependentTypeNames : List[str] = SplitListOfTypes(parameters) | |
189 for dependentTypeName in dependentTypeNames: | |
190 # childAncestors = ancestors.copy() NO TEMPLATE ANCESTOR!!! | |
191 # childAncestors.append(typeName) | |
192 ProcessTypeTree(ancestors, processedTypes, structTypes, dependentTypeName) | |
193 else: | |
194 if structTypes.has_key(typeName): | |
195 ProcesStructType_DepthFirstRecursive(generationQueue, structTypes, | |
196 structTypes[typeName]) | |
197 | |
198 def ProcesStructType_DepthFirstRecursive( | |
199 generationQueue : List[str], structTypes : Dict[str,Dict] | |
200 , typeDict : Dict) -> None: | |
201 # let's generate the code according to the | |
124 typeName : str = typeDict['name'] | 202 typeName : str = typeDict['name'] |
125 typeObject : Type = allTypes[typeName] | 203 if typeDict['kind'] != 'struct': |
204 raise Exception(f"Unexpected kind '{typeDict['kind']}' for " + | |
205 "type '{typeName}'") | |
126 typeFields : List[Dict] = typeDict['fields'] | 206 typeFields : List[Dict] = typeDict['fields'] |
127 dependentTypes = [] | 207 for typeField in typeFields: |
128 for typeField : Dict in typeFields: | 208 ancestors = [typeName] |
129 typeFieldObject = CreateTypeObject(allTypes, typeField['name']) | 209 ProcessTypeTree(ancestors, generationQueue |
130 dependentTypes.append(typeFieldObject) | 210 , structTypes, typeField['name']) |
131 elif typeDict['kind'] == 'enum': | 211 # now we're pretty sure our dependencies have been processed, |
132 # the enum objects are already created and have no dependencies | 212 # we can start marking our code for generation |
133 else: | 213 generationQueue.append(typeName) |
134 raise Exception("Internal error: ComputeTopTypeTree() can only be called on the defined types (enums and structs)") | |
135 | |
136 | |
137 | |
138 | 214 |
139 def ProcessSchema(schema : dict) -> None: | 215 def ProcessSchema(schema : dict) -> None: |
140 CheckSchemaSchema(schema) | 216 CheckSchemaSchema(schema) |
141 rootName : str = schema['root_name'] | 217 rootName : str = schema['root_name'] |
142 definedTypes : list = schema['types'] | 218 definedTypes : list = schema['types'] |
143 | 219 |
144 # gather defined types | 220 # mark already processed types |
145 allTypes : Dict[str,Type] = {} | 221 processedTypes : set[str] = set() |
146 | 222 |
147 # gather all defined types (first pass) : structs and enums | 223 # the struct names are mapped to their JSON dictionary |
148 # we do not parse the subtypes now as to not mandate | 224 structTypes : Dict[str,Dict] = {} |
149 # a particular declaration order | 225 |
226 # the order here is the generation order | |
150 for definedType in definedTypes: | 227 for definedType in definedTypes: |
151 CreateAndCacheTypeObject(allTypes,definedType) | 228 if definedType['kind'] == 'enum': |
152 | 229 ProcessEnumerationType(processedTypes, definedType); |
153 # now we have objects for all the defined types | 230 |
154 # let's build the whole type dependency tree | 231 # the order here is NOT the generation order: the types |
232 # will be processed according to their dependency graph | |
155 for definedType in definedTypes: | 233 for definedType in definedTypes: |
156 ComputeTypeTree(allTypes,definedType) | 234 if definedType['kind'] == 'struct': |
157 | 235 structTypes[definedType['name']] = definedType |
158 | 236 ProcesStructType_DepthFirstRecursive(processedTypes,structTypes,definedType) |
159 | |
160 | |
161 | 237 |
162 if __name__ == '__main__': | 238 if __name__ == '__main__': |
163 import argparse | 239 import argparse |
164 parser = argparse.ArgumentParser(usage = """stonegentool.py [-h] [-o OUT_DIR] [-v] input_schemas | 240 parser = argparse.ArgumentParser(usage = """stonegentool.py [-h] [-o OUT_DIR] [-v] input_schemas |
165 EXAMPLE: python command_gen.py -o "generated_files/" "mainSchema.json,App Specific Commands.json" """) | 241 EXAMPLE: python command_gen.py -o "generated_files/" "mainSchema.json,App Specific Commands.json" """) |