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" """)