Simatic_XML_Parser_to_SCL/generators/generator_utils.py

150 lines
9.5 KiB
Python

# 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