6 from pathlib import Path
7 from abc import ABC, abstractmethod
10 reHex = re.compile(r"^0x([0-9a-fA-F]+)$")
11 reGranuel = re.compile(r"^(?P<num>[0-9]+)@(?P<g>.+)$")
12 reMacroRef = re.compile(r"^\*(?P<var>[a-zA-z0-9]+)$")
13 reInt = re.compile(r"^[0-9]+$")
14 def __init__(self) -> None:
18 def expand_str(s: str, param_dict):
19 if Preprocessor.reInt.match(s) is not None:
22 mo = Preprocessor.reHex.match(s)
26 mo = Preprocessor.reGranuel.match(s)
30 granuel = param_dict["granule"][mg['g']]
33 mo = Preprocessor.reMacroRef.match(s)
36 return param_dict[mg['var']]
38 return s.format_map(param_dict)
40 class DataObject(ABC):
41 def __init__(self, name, record):
50 return DataObject.create("", record)
53 def create(key, record):
54 if PrimitiveType.can_create(record):
55 return PrimitiveType(record)
58 t = name if "$type" not in record else record['$type']
61 name = record["$name"].strip()
63 if not key.startswith('@') and "$type" not in record:
64 return PlainOldObject(name, record)
68 return RangedObject(name, record)
70 return ForEachIndexObject(name, record)
71 elif t == "case_range_index":
72 return Condition(record)
74 return PlainOldObject(name, record)
76 return VariableDeclarationObject(record)
77 elif t == "memory_map":
78 return MemoryMapObject(record)
80 return RawObject(name, record)
82 def _parse(self, record):
83 for k, v in record.items():
85 self.ctrl_field[k.strip("$")] = FieldType.create(k, v)
86 elif k.startswith("@"):
87 self.ctrl_field[k.strip("@")] = DataObject.create(k, v)
89 self.user_field[k] = DataObject.create(k, v)
91 def expand(self, param={}):
93 for f in self.ctrl_field.values():
94 if not isinstance(f, DataObject):
96 obj2.update(**f.expand(param))
99 _param = {**param, **obj2}
100 for k, v in self.user_field.items():
101 if isinstance(v, DataObject):
102 obj[k] = v.expand(_param)
106 return {**obj, **obj2}
110 def __init__(self, record) -> None:
111 self._record = record
115 def create(field, value):
116 if field == "$range":
117 return RangeType(value)
122 def _parse(self, record):
126 def get(self, param):
132 class PrimitiveType(DataObject):
133 def __init__(self, record) -> None:
134 super().__init__("", record)
137 def can_create(value):
138 return type(value) in (str, int, bool)
140 def _parse(self, record):
141 if type(record) not in (str, int, bool):
142 raise Exception(f"{type(self).__name__} require primitive type input")
145 if type(record) == str:
146 self.__get_fn = self.__process_str
148 self.__get_fn = lambda x: self.val
150 def __process_str(self, param):
151 return Preprocessor.expand_str(self.val, param)
153 def expand(self, param={}):
154 return self.__get_fn(param)
156 class RangeType(FieldType):
157 def __init__(self, record) -> None:
158 self.__ranged_component = re.compile(r"^(?P<index>[^.]+)$|^(?P<start>.+?)\.\.(?P<end>.+)$")
159 super().__init__(record)
161 def _parse(self, record):
162 return super()._parse(record)
164 def get(self, param):
165 record = self._record.strip('[]')
168 for component in record.split(','):
169 component = component.strip()
170 mo = self.__ranged_component.match(component)
172 raise Exception(f"value '{component}' is not valid range component")
175 if mo["index"] is not None:
176 self.__value.append(Preprocessor.expand_str(mo['index'], param))
178 start = Preprocessor.expand_str(mo['start'], param)
179 end = Preprocessor.expand_str(mo['end'], param)
180 self.__value += [x for x in range(start, end + 1)]
186 class VariableDeclarationObject(DataObject):
187 def __init__(self, record):
188 super().__init__("", record)
190 def _parse(self, record):
191 return super()._parse(record)
193 def expand(self, param={}):
194 obj = super().expand(param)
198 class Condition(DataObject):
199 def __init__(self, record):
200 super().__init__("", record)
202 def _parse(self, record):
203 super()._parse(record)
204 if "range" not in self.ctrl_field:
205 raise Exception("condition body must contains valid range case")
206 if "true" not in self.ctrl_field:
207 raise Exception("condition body must contains 'True' handling case")
210 def expand(self, param={}):
211 self.__range_lst = self.ctrl_field["range"].get(param)
212 if param["index"] in self.__range_lst:
213 return self.ctrl_field["true"].expand(param)
214 elif "else" in self.ctrl_field:
215 return self.ctrl_field["else"].expand(param)
218 class ArrayObject(DataObject):
219 def __init__(self, record,
220 nested_array = False,
221 el_factory = lambda x: DataObject.create("", x)):
222 self._el_factory = el_factory
223 self._nested_array = nested_array
225 super().__init__("", record)
227 def _parse(self, record):
228 if not isinstance(record, list):
229 raise Exception(f"{type(self).__name__} require array input")
233 self.content.append(self._el_factory(x))
235 def expand(self, param={}):
237 for x in self.content:
238 obj = x.expand(param)
239 if isinstance(obj, list) and not self._nested_array:
246 class MemoryMapObject(DataObject):
247 class GranuleObject(DataObject):
248 def __init__(self, record):
249 super().__init__("", record)
251 def _parse(self, record):
253 for k, v in record.items():
254 self.__granules[k] = DataObject.create(k, v)
256 def expand(self, param={}):
258 for k, v in self.__granules.items():
259 val = v.expand(param)
261 if not isinstance(val, int):
262 raise Exception("The granule definition must be either integer or int-castable string")
268 def __init__(self, record):
269 super().__init__("", record)
271 def _parse(self, record):
272 for k, v in record.items():
273 if k.startswith("$"):
274 self.ctrl_field[k.strip("$")] = FieldType.create(k, v)
275 elif k.startswith("@"):
276 self.ctrl_field[k.strip("@")] = DataObject.create(k, v)
278 if "granule" in record:
279 self.__g = MemoryMapObject.GranuleObject(record["granule"])
281 if "regions" in record:
282 self.__regions = ArrayObject(record["regions"])
284 if "width" in record:
285 self.__width = DataObject.create("width", record["width"])
287 def __process(self, start_addr, idx, regions):
288 if idx >= len(regions):
289 raise Exception("Unbounded region definition")
294 ne = regions[idx + 1]
295 if "start" not in ne or "size" not in e:
296 e["start"] = start_addr
298 self.__process(start_addr + e["size"], idx + 1, regions)
299 e["start"] = ne['start'] - e["size"]
303 e["start"] = (e["start"] + b) & ~b
305 if e["start"] < start_addr:
306 raise Exception(f"starting addr {hex(e['start'])} overrlapping with {hex(start_addr)}")
308 start_addr = e["start"]
311 self.__process(start_addr, idx + 1, regions)
312 ne = regions[idx + 1]
313 e["size"] = ne['start'] - start_addr
315 return start_addr + e["size"]
317 def expand(self, param={}):
318 super().expand(param)
320 g = self.__g.expand(param)
324 width = self.__width.expand(param)
325 if not isinstance(width, int):
326 raise Exception("'width' attribute must be integer")
328 regions = self.__regions.expand(param)
331 for i in range(len(regions)):
332 start_addr = self.__process(start_addr, i, regions)
334 if math.log2(start_addr) > width:
335 raise Exception("memory size larger than speicified address width")
342 class ForEachIndexObject(DataObject):
343 def __init__(self, name, record):
344 super().__init__(name, record)
346 for k, v in record.items():
347 self.steps.append(DataObject.create(k, v))
349 def _parse(self, record):
350 super()._parse(record)
352 def expand(self, param={}):
353 if "index" not in param:
354 raise Exception(f"'{type(self).__name__}' require parameter 'index'.")
356 for cond in self.steps:
357 obj.update(**cond.expand(param))
360 class RawObject(DataObject):
361 def __init__(self, name, record):
362 super().__init__(name, record)
364 def _parse(self, record):
365 return super()._parse(record)
367 def expand(self, param={}):
368 return super().expand(param)
370 class PlainOldObject(DataObject):
371 def __init__(self, name, record):
372 super().__init__(name, record)
374 def _parse(self, record):
375 return super()._parse(record)
377 def expand(self, param={}):
378 return super().expand(param)
380 class RangedObject(DataObject):
381 def __init__(self, name, record):
382 super().__init__(name, record)
384 def _parse(self, record):
385 super()._parse(record)
387 def expand(self, param={}):
388 if "range" not in self.ctrl_field:
389 raise Exception("RangedObject with ranged type must have 'range' field defined")
392 indices = self.ctrl_field["range"].get(param)
395 out_lst.append(super().expand(param))
402 class TemplateExpander:
403 def __init__(self, template_path, project_path, arch) -> None:
405 self.tbase_path = template_path.joinpath(arch)
406 self.pbase_path = project_path
408 self.__helper_fns = {
410 "hex": lambda x: hex(x)
414 self.__load_mappings()
416 def __load_config(self):
418 cfg_file: Path = self.tbase_path.joinpath("config.json")
419 if not cfg_file.is_file():
420 raise Exception(f"config not found. ({cfg_file})")
422 obj = json.loads(cfg_file.read_text())
423 for k, v in obj.items():
424 o = DataObject.create(k, v).expand()
427 def __load_mappings(self):
429 mapping_file: Path = self.tbase_path.joinpath("mappings")
430 if not mapping_file.is_file():
431 raise Exception(f"config not found. ({mapping_file})")
433 with mapping_file.open() as f:
440 src, dest = l.split("->")
443 if src in self.mapping:
444 raise Exception(f"repeating entry ({src})")
446 self.mapping[src] = dest.strip()
449 for k, v in self.mapping.items():
450 src: Path = self.tbase_path.joinpath(k)
451 dest: Path = self.pbase_path.joinpath(v)
452 if not src.is_file():
455 if not dest.parent.exists():
456 dest.parent.mkdir(parents=True)
458 self.data["template"] = k
459 template = jinja2.Template(src.read_text(), trim_blocks=True)
460 template.globals.update(**self.__helper_fns)
461 out = template.render(data=self.data)
465 print(f"rendered: {k} -> {v}")
470 parser = argparse.ArgumentParser()
471 parser.add_argument("--arch", default='i386')
472 parser.add_argument("-twd", "--template_dir", default=str(Path.cwd()))
473 parser.add_argument("-pwd", "--project_dir", default=str(Path.cwd()))
475 args = parser.parse_args()
477 expander = TemplateExpander(Path(args.template_dir), Path(args.project_dir), args.arch)
480 # pprint.pprint(expander.data)
482 if __name__ == "__main__":