150 lines
9.5 KiB
Python
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 |