import re import pprint import re from collections import defaultdict # ===================================================================== # Unified DynamicObject (replaces DynamicMObject + DynamicCppObject) # ===================================================================== class DynamicObject(dict): """ A unified dynamic object that: - Behaves like a dict - Supports attribute access - Auto-creates nested fields on demand """ def __getattr__(self, name): if name not in self: self[name] = DynamicObject() return self[name] def __setattr__(self, name, value): self[name] = value def ensure_path(self, path_parts): obj = self for part in path_parts: if part not in obj: obj[part] = DynamicObject() obj = obj[part] return obj def __repr__(self): return dict.__repr__(self) # ===================================================================== # MATLAB FRD REG PARSER # ===================================================================== def parse_oFRDReg_from_mfile(filename): oFRDReg = DynamicObject() # Regex to capture: oFRDReg. = ; pattern = re.compile(r"oFRDReg\.([A-Za-z0-9_.]+)\s*=\s*([^;]+);") with open(filename, "r") as f: for line in f: match = pattern.search(line) if match: full_path = match.group(1) value_raw = match.group(2).strip() # Convert numeric/string values when possible try: value = eval(value_raw) except: value = value_raw # Split path: e.g. MSI.vcm_sel → ["MSI", "vcm_sel"] parts = full_path.split(".") parts = [ p.upper() for p in parts ] attr_name = parts[-1] parent_path = parts[:-1] # Create nested path parent = oFRDReg.ensure_path(parent_path) setattr(parent, attr_name, value) return oFRDReg # ===================================================================== # Macro expansion helper # ===================================================================== def expand_macro_pattern(pattern, **kwargs): """ Expand a macro pattern containing tokens like: CH1##_##address##_OFFSET into the final register string. kwargs maps token names → values, e.g. address="CFG". """ s = pattern # Replace tokens inside patterns like address##_XYZ for key, value in kwargs.items(): s = s.replace(key, value) # Resolve concatenation '##' s = s.replace("##", "") # Clean up double underscores while "__" in s: s = s.replace("__", "_") return s # ===================================================================== # C MACRO PARSER # ===================================================================== def remove_cpp_comments(text): """ Remove /* block comments */ and // line comments. """ # Remove multiline comments text = re.sub(r"/\*.*?\*/", "", text, flags=re.DOTALL) # Removedefines text = re.sub(r"#define.*", "", text) # Remove single-line comments text = re.sub(r"//.*", "", text) return text def parse_c_macros(filename): registers = DynamicObject() # Macro → register expansion patterns macro_definitions = { # 3-argument write (slow/fast) "SLOW_WRITE_FIELD": ( "CH1_##address##", "CH1_##address##_##field##", ), "FAST_WRITE_FIELD": ( "CH0_##address##", "CH0_##address##_##field##", ), # Read field, 2 arguments "SLOW_READ_FIELD": ( "CH1_##address##", "CH1_##address##_##field##", ), "FAST_READ_FIELD": ( "CH0_##address##", "CH0_##address##_##field##", ), # Register read/write (1 or 2 arguments) "SLOW_READ_REGISTER": ( "CH1_##address##", ), "FAST_READ_REGISTER": ( "CH0_##address##", ), "SLOW_WRITE_REGISTER": ( "CH1_##address##", ), "FAST_WRITE_REGISTER": ( "CH0_##address##", ), "FAST_WRITE_INHIBIT": ( "CH0##_##address1##_CH0_##address2##", ), "SLOW_WRITE_INHIBIT": ( "CH1##_##address1##_CH0_##address2##", ) } # Generic regex for ALL macro calls (1–4 args) call_regex = re.compile( r"([A-Z_]+)\s*\(\s*([A-Za-z0-9_]+)" # FUN and arg1: address1 r"(?:\s*,\s*([A-Za-z0-9_]+))?" # arg2: field, address2 r"(?:\s*,\s*([A-Za-z0-9_+\-*/() ]+))?" # arg3: field, data r"(?:\s*,\s*([A-Za-z0-9_+\-*/() ]+))?" # arg4: data (expression allowed) r"\s*\)" ) with open(filename, "r") as f: text = f.read() text = remove_cpp_comments(text) for match in call_regex.finditer(text): macro, a1, a2, a3, a4 = match.groups() text = text.replace(match.group(0), "") # remove after processing if macro not in macro_definitions or "READ" in macro: continue patterns = macro_definitions[macro] # Build the macro variable map kwargs = {} if a4 == None: if a1: kwargs["address"] = a1 if a2: kwargs["field"] = a2 if a3: kwargs["data"] = a3 else: if a1: kwargs["address1"] = a1 if a2: kwargs["address2"] = a2 if a3: kwargs["field"] = a3 if a4: kwargs["data"] = a4 # Expand patterns into real register names expanded = [expand_macro_pattern(p, **kwargs) for p in patterns] # Store results if a2 and a3: # WRITE_FIELD registers[expanded[0].upper()] = {a2.upper(): a3} else: # REGISTER read/write registers[expanded[0].upper()] = a3 return registers def upper_keys_recursive(d): new_dict = {} for k, v in d.items(): key_up = str(k).upper() if isinstance(v, dict): new_dict[key_up] = upper_keys_recursive(v) else: new_dict[key_up] = v return new_dict def diff_dicts(d1, d2, path="",dictnames = ["M","CPP"]): """ Recursively compare two dictionaries and print differences. Shows: - Keys only in d1 - Keys only in d2 - Keys in both where values differ """ # Keys present in both dictionaries common_keys = d1.keys() & d2.keys() # Keys only in first dict only_d1 = d1.keys() - d2.keys() for key in sorted(only_d1): print(f"{path}{key}: only in {dictnames[0]}") # Keys only in second dict only_d2 = d2.keys() - d1.keys() for key in sorted(only_d2): print(f"{path}{key}: only in {dictnames[1]}") # Compare common keys for key in sorted(common_keys): v1 = d1[key] v2 = d2[key] new_path = f"{path}{key}." # If both values are dictionaries → recurse if isinstance(v1, dict) and isinstance(v2, dict): diff_dicts(v1, v2, new_path) continue # If values differ → print if v1 != v2: print(f"{path}{key}:") print(f" {dictnames[0]} = {v1}") print(f" {dictnames[1]} = {v2}") # ===================================================================== # Example usage # ===================================================================== # Example usage: if __name__ == "__main__": obj = parse_oFRDReg_from_mfile("startTau_generic_Generated.m") pprint.pprint(obj) regs = parse_c_macros("/home/lucas/git/mems_stable/trixel3_software/tic/source/trixel3_tic_mems_model_lbd001rrxab11.cpp") pprint.pprint(regs) diff_dicts(regs,obj) print("Fin")