import json import os from dataclasses import dataclass INDENT = 8 WIDTH = 80 LAST = "};" SV = "std::string_view" NS = "esxr::" PREAMBLE = '#include "esx_reader.hpp"\n\nusing namespace esxr;' def transpose(seq): return list(zip(*seq)) def clean(seq): return [x for x in seq if x is not None] def indented_block(items): lines = [] line = "" for item in items: if len(line) + len(item) + 1 > WIDTH: lines += [line[:-1]] line = "" if not line: line = " " * INDENT line += item + ", " if line.strip(): lines += [line] return "\n".join(lines) def max_length(list): return max(map(lambda x: len(x), list)) def wrap_bracket(list): return ["{" + string + "}" for string in list] def padded_join(lists): maximums = [max_length(x) for x in lists] padded = [] for max, l in zip(maximums, lists): padded += [[x + " " * (max - len(x)) for x in l]] padded_transposed = transpose(padded) return ["".join(x) for x in padded_transposed] @dataclass class Map: first_type: str second_type: str first_name: str second_name: str reverse: bool formats: list[str] data: dict F_DECL = "static constexpr std::pair<{0}, {1}> {2}_builtin[] {{" F_SOA = """static constexpr auto {0}_std = utility::array_builtin_to_std({0}_builtin); static constexpr auto {0} = utility::map_to_soa({0}_std);""" F_FUNC_DECL = "[[nodiscard]] std::optional<{0}> {4}{1}_to_{2}({3} {1}) noexcept" F_FUNC_BODY_FORWARD = " return utility::soa_first_to_second({0}, {1});" F_FUNC_BODY_REVERSE = " return utility::soa_second_to_first({0}, {1});" @property def name(self): return self.first_name + "_" + self.second_name + "_map" @property def decl(self): return Map.F_DECL.format(self.first_type, self.second_type, self.name) @property def to_soa(self): return Map.F_SOA.format(self.name) def _forward_decl(self, namespace=""): return Map.F_FUNC_DECL.format(self.second_type, self.first_name, self.second_name, self.first_type, namespace) def _reverse_decl(self, namespace=""): return Map.F_FUNC_DECL.format(self.first_type, self.second_name, self.first_name, self.second_type, namespace) @property def forward_decl(self): return self._forward_decl() @property def reverse_decl(self): return self._reverse_decl() @property def func_decls(self): decls = [self.forward_decl] + ([self.reverse_decl] if self.reverse else []) return "\n".join(decls) @property def forward_func(self): return self._forward_decl(NS) + "\n{\n" + Map.F_FUNC_BODY_FORWARD.format(self.name, self.first_name) + "\n}" @property def reverse_func(self): return self._reverse_decl(NS) + "\n{\n" + Map.F_FUNC_BODY_REVERSE.format(self.name, self.second_name) + "\n}" @property def funcs(self): f = [self.forward_func] + ([self.reverse_func] if self.reverse else []) return "\n".join(f) def data_to_list(self, data): parts = [] for format in self.formats: parts += [[format.format(*x) for x in data]] return indented_block(wrap_bracket(padded_join(parts))) @property def lut(self): parts = [self.decl, self.data_to_list(self.data), LAST] return "\n".join(parts) def add_extra(data): grup = {"fourcc": "GRUP", "name": "Group", "flags": []} note = {"fourcc": "NOTE", "name": "Note", "flags": []} data += [grup, note] data.sort(key=lambda x: x["fourcc"]) # read in our data sets fdir = os.path.dirname(__file__) with open(os.path.join(fdir, "records.json"), "r") as f: record_data = json.load(f) add_extra(record_data) with open(os.path.join(fdir, "refr_flags.json"), "r") as f: refr_data = json.load(f) with open(os.path.join(fdir, "group_type.json"), "r") as f: group_type_data = json.load(f) # create our maps group_name_map = Map( first_type = "GroupType", second_type = SV, first_name = "group_type", second_name = "name", reverse = False, formats = ["GroupType::{0}", ", \"{1}\""], data = [(x['type'], x['name']) for x in group_type_data] ) record_name_map = Map( first_type = "RecordType", second_type = SV, first_name = "record_type", second_name = "name", reverse = False, formats = ["RecordType::{0}", ", \"{1}\""], data = [(x['fourcc'], x['name']) for x in record_data] ) record_fourcc_map = Map( first_type = "RecordType", second_type = "FourCC", first_name = "record_type", second_name = "fourcc", reverse = True, formats = ["RecordType::{0}", ", fourcc_from_cstr(\"{0}\")"], data = [(x['fourcc'], ) for x in record_data] ) flag_desc_map = Map( first_type = "Flag", second_type = SV, first_name = "flag", second_name = "description", reverse = False, formats = ["{{RecordType::{0}, {1:>2}}}", ", \"{2}\""], data = [(x['fourcc'], y['bit'], y['description']) for x in record_data for y in x['flags']] ) refr_flag_desc_map = Map( first_type = "RefrFlag", second_type = SV, first_name = "refr_flag", second_name = "description", reverse = False, formats = ["{{RecordType::{0}, {1:>2}}}", ", \"{2}\""], data = [(x['fourcc'], y['bit'], y['description']) for x in refr_data for y in x['flags']] ) maps = [group_name_map, record_fourcc_map, record_name_map, flag_desc_map, refr_flag_desc_map] def gen_enum(data): # generate sorted list of signatures sigs = [r["fourcc"] for r in data] string = "\n".join(("enum class RecordType {", indented_block(sigs), LAST)) return string def main(): lut = [PREAMBLE] lut += map(lambda m: m.lut, maps) lut += map(lambda m: m.to_soa, maps) lut += map(lambda m: m.funcs, maps) lut = "\n\n".join(lut) with open(os.path.join(fdir, "esx_reader_lut.cpp"), "w") as f: f.write(lut) header = [] header += [gen_enum(record_data)] header += map(lambda m: m.func_decls, maps) header = "\n".join(header) with open(os.path.join(fdir, "esx_reader.fragment.hpp"), "w") as f: f.write(header) if __name__ == "__main__": main()