# processors/symbol_manager.py import sympy import re class SymbolManager: def __init__(self): # plc_name -> py_id (e.g., '"DB".Var' -> 'v0_') self.plc_to_py_id = {} # py_id -> Symbol object (e.g., 'v0_' -> sympy.Symbol('v0_')) self.py_id_to_symbol = {} # py_id -> plc_name (e.g., 'v0_' -> '"DB".Var') - Inverse mapping self.py_id_to_plc = {} self.counter = 0 # Pre-define common keywords/constants to avoid mapping them self.reserved_names = {"TRUE", "FALSE"} # Add others if needed def _generate_py_id(self): py_id = f"v{self.counter}_" self.counter += 1 # Extremely unlikely collision, but check anyway while py_id in self.py_id_to_symbol: py_id = f"v{self.counter}_" self.counter += 1 return py_id def get_symbol(self, plc_var_name): """Gets/Creates a SymPy Symbol for a PLC variable name.""" if plc_var_name is None: print("Warning: Attempted to get symbol for None PLC name.") return None # Or handle error appropriately if plc_var_name.upper() in self.reserved_names: print( f"Warning: Attempted to create symbol for reserved name: {plc_var_name}" ) return None # Or handle differently (e.g., return sympy.true/false?) if plc_var_name not in self.plc_to_py_id: py_id = self._generate_py_id() self.plc_to_py_id[plc_var_name] = py_id self.py_id_to_plc[py_id] = plc_var_name self.py_id_to_symbol[py_id] = sympy.symbols(py_id) # print(f"DEBUG SymbolManager: Created {py_id} -> {plc_var_name}") # Debug else: py_id = self.plc_to_py_id[plc_var_name] return self.py_id_to_symbol.get(py_id) def get_plc_name(self, py_id): """Gets the original PLC name from a py_id.""" return self.py_id_to_plc.get(py_id) def get_inverse_map(self): """Returns the map needed for postprocessing (py_id -> plc_name).""" return self.py_id_to_plc.copy() # Helper function to extract PLC variable name from JSON operand info def extract_plc_variable_name(operand_info): if operand_info and operand_info.get("type") == "variable": return operand_info.get("name") elif operand_info and operand_info.get("type") == "unknown_structure": # Handle direct memory addresses like DB960.X448.0 area = operand_info.get("Area") block_number = operand_info.get("BlockNumber") bit_offset = operand_info.get("BitOffset") data_type = operand_info.get("Type") if area and block_number and bit_offset is not None: if area == "DB" and data_type == "Bool": # Convert bit offset to byte and bit byte_offset = int(bit_offset) // 8 bit_pos = int(bit_offset) % 8 return f"%DB{block_number}.DBX{byte_offset}.{bit_pos}" elif area == "DB" and data_type in ["Word", "Int"]: byte_offset = int(bit_offset) // 8 return f"%DB{block_number}.DBW{byte_offset}" elif area == "DB" and data_type in ["DWord", "DInt", "Real"]: byte_offset = int(bit_offset) // 8 return f"%DB{block_number}.DBD{byte_offset}" else: # Other area types (M, I, Q, etc.) if data_type == "Bool": byte_offset = int(bit_offset) // 8 bit_pos = int(bit_offset) % 8 return f"%{area}{byte_offset}.{bit_pos}" else: byte_offset = int(bit_offset) // 8 if data_type in ["Word", "Int"]: return f"%{area}W{byte_offset}" elif data_type in ["DWord", "DInt", "Real"]: return f"%{area}D{byte_offset}" else: return f"%{area}{byte_offset}" return None # Not a variable or info missing