# generators/generator_utils.py # -*- coding: utf-8 -*- import re # --- Importar format_variable_name desde processors --- # Es mejor mantenerlo centralizado si se usa en varios pasos. try: from processors.processor_utils import format_variable_name except ImportError: print("Advertencia: No se pudo importar 'format_variable_name' desde processors.processor_utils.") print("Usando una implementación local básica (¡PUEDE FALLAR CON NOMBRES COMPLEJOS!).") def format_variable_name(name): # Fallback if not name: return "_INVALID_NAME_" if name.startswith('"') and name.endswith('"'): return name prefix = "#" if name.startswith("#") else "" if prefix: name = name[1:] if name and name[0].isdigit(): name = "_" + name name = re.sub(r"[^a-zA-Z0-9_]", "_", name) return prefix + name # --- Fin Fallback --- # para formatear valores iniciales def format_scl_start_value(value, datatype): """Formatea un valor para la inicialización SCL/Markdown según el tipo.""" if value is None: return None datatype_lower = datatype.lower() if datatype else "" value_str = str(value); value_str_unquoted = value_str if value_str.startswith('"') and value_str.endswith('"') and len(value_str) > 1: value_str_unquoted = value_str[1:-1] elif value_str.startswith("'") and value_str.endswith("'") and len(value_str) > 1: value_str_unquoted = value_str[1:-1] # Integer-like if any(t in datatype_lower for t in ["int","byte","word","dint","dword","lint","lword","sint","usint","uint","udint","ulint"]): try: return str(int(value_str_unquoted)) except ValueError: if re.match(r"^[a-zA-Z_][a-zA-Z0-9_]*$", value_str_unquoted): return value_str_unquoted escaped_for_scl = value_str_unquoted.replace("\\", "\\\\").replace("'", "''").replace("\n", "").replace("\r", "") return f"'{escaped_for_scl}'" # Fallback as string # Bool elif "bool" in datatype_lower: return "TRUE" if value_str_unquoted.lower() == "true" else "FALSE" # String/Char elif "string" in datatype_lower: escaped_value = value_str_unquoted.replace("'", "''"); return f"'{escaped_value}'" elif "char" in datatype_lower: escaped_value = value_str_unquoted.replace("'", "''"); return f"'{escaped_value}'" # Real elif "real" in datatype_lower or "lreal" in datatype_lower: try: f_val = float(value_str_unquoted); s_val = str(f_val) if "." not in s_val and "e" not in s_val.lower(): s_val += ".0" return s_val except ValueError: if re.match(r"^[a-zA-Z_][a-zA-Z0-9_]*$", value_str_unquoted): return value_str_unquoted escaped_for_scl = value_str_unquoted.replace("\\", "\\\\").replace("'", "''").replace("\n", "").replace("\r", ""); return f"'{escaped_for_scl}'" # Fallback # Time elif "time" in datatype_lower: prefix, val_to_use = "", value_str_unquoted if val_to_use.upper().startswith("T#"): prefix, val_to_use = "T#", val_to_use[2:] elif val_to_use.upper().startswith("LT#"): prefix, val_to_use = "LT#", val_to_use[3:] elif val_to_use.upper().startswith("S5T#"): prefix, val_to_use = "S5T#", val_to_use[4:] if "s5time" in datatype_lower: return f"S5T#{val_to_use}" elif "ltime" in datatype_lower: return f"LT#{val_to_use}" else: return f"T#{val_to_use}" # Date/Time Of Day elif "date" in datatype_lower: # Must check DTL/DT/TOD first val_to_use = value_str_unquoted if "dtl" in datatype_lower or "date_and_time" in datatype_lower: prefix = "DTL#" if val_to_use.upper().startswith("DTL#") else "DTL#"; val_to_use = val_to_use[4:] if val_to_use.upper().startswith("DTL#") else val_to_use; return f"{prefix}{val_to_use}" elif "dt" in datatype_lower: prefix = "DT#" if val_to_use.upper().startswith("DT#") else "DT#"; val_to_use = val_to_use[3:] if val_to_use.upper().startswith("DT#") else val_to_use; return f"{prefix}{val_to_use}" elif "tod" in datatype_lower or "time_of_day" in datatype_lower: prefix = "TOD#" if val_to_use.upper().startswith("TOD#") else "TOD#"; val_to_use = val_to_use[4:] if val_to_use.upper().startswith("TOD#") else val_to_use; return f"{prefix}{val_to_use}" else: # Default to Date D# prefix = "D#" if val_to_use.upper().startswith("D#") else "D#"; val_to_use = val_to_use[2:] if val_to_use.upper().startswith("D#") else val_to_use; return f"{prefix}{val_to_use}" # Fallback else: if re.match(r'^[a-zA-Z_#"][a-zA-Z0-9_."#\[\]%]+$', value_str): # Check if it looks like a symbol/path if value_str.startswith('"') and value_str.endswith('"') and len(value_str) > 1: return value_str[1:-1] # UDT literal? if '"' in value_str and "." in value_str and value_str.count('"') == 2: return value_str # DB access? if not value_str.startswith('"') and not value_str.startswith("'"): if value_str.startswith("#") or value_str.startswith("%"): return value_str # Temp or Absolute else: return value_str # Symbolic constant? return value_str # Other complex string? else: # Final fallback: treat as string literal escaped_for_scl = value_str_unquoted.replace("\\", "\\\\").replace("'", "''").replace("\n", "").replace("\r", ""); return f"'{escaped_for_scl}'" def generate_scl_declarations(variables, indent_level=1): """Genera las líneas SCL para declarar variables, structs y arrays.""" scl_lines = [] indent = " " * indent_level for var in variables: var_name_scl = format_variable_name(var.get("name")) var_dtype_raw = var.get("datatype", "VARIANT") var_comment = var.get("comment") start_value = var.get("start_value") children = var.get("children") array_elements = var.get("array_elements") # Limpiar y determinar tipo base var_dtype_cleaned = var_dtype_raw if isinstance(var_dtype_raw, str): if var_dtype_raw.startswith('"') and var_dtype_raw.endswith('"'): var_dtype_cleaned = var_dtype_raw[1:-1] array_match = re.match(r'(Array\[.*\]\s+of\s+)"(.*)"', var_dtype_raw, re.IGNORECASE) if array_match: var_dtype_cleaned = f"{array_match.group(1)}{array_match.group(2)}" base_type_for_init = var_dtype_cleaned array_prefix_for_decl = "" if isinstance(var_dtype_cleaned, str) and var_dtype_cleaned.lower().startswith("array["): # Check if string before lower() match = re.match(r"(Array\[.*\]\s+of\s+)(.*)", var_dtype_cleaned, re.IGNORECASE) if match: array_prefix_for_decl, base_type_for_init = match.group(1), match.group(2).strip() # Construir tipo para declaración declaration_dtype = var_dtype_raw if base_type_for_init != var_dtype_cleaned and not array_prefix_for_decl: # Simple UDT/Complex if isinstance(base_type_for_init, str) and not base_type_for_init.startswith('"'): declaration_dtype = f'"{base_type_for_init}"' else: declaration_dtype = base_type_for_init elif array_prefix_for_decl and base_type_for_init != var_dtype_cleaned: # Array of UDT/Complex if isinstance(base_type_for_init, str) and not base_type_for_init.startswith('"'): declaration_dtype = f'{array_prefix_for_decl}"{base_type_for_init}"' else: declaration_dtype = f"{array_prefix_for_decl}{base_type_for_init}" declaration_line = f"{indent}{var_name_scl} : {declaration_dtype}" init_value_scl = None # Manejar Arrays / Structs / Simples if array_elements: try: indices_numeric = {int(k): v for k, v in array_elements.items()} sorted_indices_str = [str(k) for k in sorted(indices_numeric.keys())] except ValueError: print(f"Advertencia: Índices array no numéricos para '{var_name_scl}'."); sorted_indices_str = sorted(array_elements.keys()) init_values = [] for idx_str in sorted_indices_str: try: formatted_val = format_scl_start_value(array_elements[idx_str], base_type_for_init); init_values.append(formatted_val) except Exception as e_fmt: print(f"ERROR formato array idx {idx_str} de '{var_name_scl}': {e_fmt}"); init_values.append(f"/*ERR_FMT_{idx_str}*/") valid_inits = [v for v in init_values if v is not None] if valid_inits: init_value_scl = f"[{', '.join(valid_inits)}]" elif array_elements: print(f"Advertencia: Valores iniciales array '{var_name_scl}' son None/inválidos.") elif children: scl_lines.append(declaration_line); scl_lines.append(f"{indent}STRUCT") scl_lines.extend(generate_scl_declarations(children, indent_level + 1)) scl_lines.append(f"{indent}END_STRUCT;") if var_comment: scl_lines.append(f"{indent}// {var_comment}") scl_lines.append(""); continue else: # Simple if start_value is not None: try: init_value_scl = format_scl_start_value(start_value, base_type_for_init) except Exception as e_fmt_simple: print(f"ERROR formato simple '{var_name_scl}': {e_fmt_simple}"); init_value_scl = f"/*ERR_FMT_SIMPLE*/" # Añadir inicialización y comentario if init_value_scl is not None: declaration_line += f" := {init_value_scl}" declaration_line += ";" if var_comment: declaration_line += f" // {var_comment}" scl_lines.append(declaration_line) return scl_lines