Exportando DBs tambien

This commit is contained in:
Miguel 2025-04-20 03:22:13 +02:00
parent 5213d09bcf
commit 6c604176a1
5 changed files with 1549 additions and 706 deletions

273
paste.py Normal file
View File

@ -0,0 +1,273 @@
# x3_generate_scl.py
# -*- coding: utf-8 -*-
import json
import os
import re
import argparse
import sys
import traceback
# --- Importar Utilidades (mantener como estaba) ---
try:
from processors.processor_utils import format_variable_name
SCL_SUFFIX = "_sympy_processed"
GROUPED_COMMENT = "// Logic included in grouped IF"
except ImportError:
print("Advertencia: No se pudo importar 'format_variable_name'. Usando fallback.")
def format_variable_name(name): # Fallback BÁSICO
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
SCL_SUFFIX = "_sympy_processed"
GROUPED_COMMENT = "// Logic included in grouped IF"
# --- NUEVA FUNCIÓN para formatear valores iniciales SCL ---
def format_scl_start_value(value, datatype):
"""Formatea un valor para la inicialización SCL según el tipo."""
if value is None: return None
datatype_lower = datatype.lower() if datatype else ""
value_str = str(value)
if "bool" in datatype_lower:
return "TRUE" if value_str.lower() == 'true' else "FALSE"
elif "string" in datatype_lower:
escaped_value = value_str.replace("'", "''")
if escaped_value.startswith("'") and escaped_value.endswith("'"): escaped_value = escaped_value[1:-1]
return f"'{escaped_value}'"
elif "char" in datatype_lower: # Añadido Char
escaped_value = value_str.replace("'", "''")
if escaped_value.startswith("'") and escaped_value.endswith("'"): escaped_value = escaped_value[1:-1]
return f"'{escaped_value}'"
elif any(t in datatype_lower for t in ["int", "byte", "word", "dint", "dword", "lint", "lword", "sint", "usint", "uint", "udint", "ulint"]): # Ampliado
try: return str(int(value_str))
except ValueError:
if re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', value_str): return value_str
return f"'{value_str}'" # O como string si no es entero ni símbolo
elif "real" in datatype_lower or "lreal" in datatype_lower:
try:
f_val = float(value_str); 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): return value_str
return f"'{value_str}'"
elif "time" in datatype_lower: # Añadido Time, S5Time, LTime
# Quitar T#, LT#, S5T# si existen
prefix = ""
if value_str.upper().startswith("T#"): prefix="T#"; value_str = value_str[2:]
elif value_str.upper().startswith("LT#"): prefix="LT#"; value_str = value_str[3:]
elif value_str.upper().startswith("S5T#"): prefix="S5T#"; value_str = value_str[4:]
# Devolver con el prefijo correcto o T# por defecto si no había
if prefix: return f"{prefix}{value_str}"
elif "s5time" in datatype_lower: return f"S5T#{value_str}"
elif "ltime" in datatype_lower: return f"LT#{value_str}"
else: return f"T#{value_str}" # Default a TIME
elif "date" in datatype_lower: # Añadido Date, DT, TOD
if value_str.upper().startswith("D#"): return value_str
elif "dt" in datatype_lower or "date_and_time" in datatype_lower:
if value_str.upper().startswith("DT#"): return value_str
else: return f"DT#{value_str}" # Añadir prefijo DT#
elif "tod" in datatype_lower or "time_of_day" in datatype_lower:
if value_str.upper().startswith("TOD#"): return value_str
else: return f"TOD#{value_str}" # Añadir prefijo TOD#
else: return f"D#{value_str}" # Default a Date
# Fallback genérico
else:
if re.match(r'^[a-zA-Z_][a-zA-Z0-9_."#\[\]]+$', value_str): # Permitir más caracteres en símbolos/tipos
# Si es un UDT o Struct complejo, podría venir con comillas, quitarlas
if value_str.startswith('"') and value_str.endswith('"'):
return value_str[1:-1]
return value_str
else:
escaped_value = value_str.replace("'", "''")
return f"'{escaped_value}'"
# --- NUEVA FUNCIÓN RECURSIVA para generar declaraciones SCL (VAR/STRUCT/ARRAY) ---
# --- Función Principal de Generación SCL (MODIFICADA) ---
def generate_scl(processed_json_filepath, output_scl_filepath):
"""Genera un archivo SCL a partir del JSON procesado (FC/FB o DB)."""
if not os.path.exists(processed_json_filepath):
print(f"Error: Archivo JSON procesado no encontrado en '{processed_json_filepath}'")
return
print(f"Cargando JSON procesado desde: {processed_json_filepath}")
try:
with open(processed_json_filepath, 'r', encoding='utf-8') as f:
data = json.load(f)
except Exception as e:
print(f"Error al cargar o parsear JSON: {e}"); traceback.print_exc(); return
# --- Extracción de Información del Bloque (Común) ---
block_name = data.get('block_name', 'UnknownBlock')
block_number = data.get('block_number')
block_lang_original = data.get('language', 'Unknown') # Será "DB" para Data Blocks
block_type = data.get('block_type', 'Unknown') # FC, FB, GlobalDB
block_comment = data.get('block_comment', '')
scl_block_name = format_variable_name(block_name) # Nombre SCL seguro
print(f"Generando SCL para: {block_type} '{scl_block_name}' (Original: {block_name}, Lang: {block_lang_original})")
scl_output = []
# --- GENERACIÓN PARA DATA BLOCK (DB) ---
if block_lang_original == "DB":
print("Modo de generación: DATA_BLOCK")
scl_output.append(f"// Block Type: {block_type}")
scl_output.append(f"// Block Name (Original): {block_name}")
if block_number: scl_output.append(f"// Block Number: {block_number}")
if block_comment: scl_output.append(f"// Block Comment: {block_comment}")
scl_output.append("")
scl_output.append(f"DATA_BLOCK \"{scl_block_name}\"")
scl_output.append("{ S7_Optimized_Access := 'TRUE' }") # Asumir optimizado
scl_output.append("VERSION : 0.1")
scl_output.append("")
interface_data = data.get('interface', {})
static_vars = interface_data.get('Static', [])
if static_vars:
scl_output.append("VAR")
scl_output.extend(generate_scl_declarations(static_vars, indent_level=1))
scl_output.append("END_VAR")
scl_output.append("")
else:
print("Advertencia: No se encontró sección 'Static' o está vacía en la interfaz del DB.")
scl_output.append("VAR"); scl_output.append("END_VAR"); scl_output.append("")
scl_output.append("BEGIN"); scl_output.append(""); scl_output.append("END_DATA_BLOCK")
# --- GENERACIÓN PARA FUNCTION BLOCK / FUNCTION (FC/FB) ---
else:
print("Modo de generación: FUNCTION_BLOCK / FUNCTION")
scl_block_keyword = "FUNCTION_BLOCK" if block_type == "FB" else "FUNCTION"
# Cabecera del Bloque
scl_output.append(f"// Block Type: {block_type}")
scl_output.append(f"// Block Name (Original): {block_name}")
if block_number: scl_output.append(f"// Block Number: {block_number}")
scl_output.append(f"// Original Language: {block_lang_original}")
if block_comment: scl_output.append(f"// Block Comment: {block_comment}")
scl_output.append("")
# Manejar tipo de retorno para FUNCTION
return_type = "Void" # Default
interface_data = data.get('interface', {})
if scl_block_keyword == "FUNCTION" and interface_data.get('Return'):
return_member = interface_data['Return'][0] # Asumir un solo valor de retorno
return_type_raw = return_member.get('datatype', 'Void')
return_type = return_type_raw.strip('"') if return_type_raw.startswith('"') and return_type_raw.endswith('"') else return_type_raw
# Añadir comillas si es UDT
if return_type != return_type_raw: return_type = f'"{return_type}"'
scl_output.append(f"{scl_block_keyword} \"{scl_block_name}\" : {return_type}" if scl_block_keyword == "FUNCTION" else f"{scl_block_keyword} \"{scl_block_name}\"")
scl_output.append("{ S7_Optimized_Access := 'TRUE' }")
scl_output.append("VERSION : 0.1"); scl_output.append("")
# Declaraciones de Interfaz FC/FB
section_order = ["Input", "Output", "InOut", "Static", "Temp", "Constant"] # Return ya está en cabecera
declared_temps = set()
for section_name in section_order:
vars_in_section = interface_data.get(section_name, [])
if vars_in_section:
scl_section_keyword = f"VAR_{section_name.upper()}"
if section_name == "Static": scl_section_keyword = "VAR_STAT"
if section_name == "Temp": scl_section_keyword = "VAR_TEMP"
if section_name == "Constant": scl_section_keyword = "CONSTANT"
scl_output.append(scl_section_keyword)
scl_output.extend(generate_scl_declarations(vars_in_section, indent_level=1))
if section_name == "Temp": declared_temps.update(format_variable_name(v.get("name")) for v in vars_in_section if v.get("name"))
scl_output.append("END_VAR"); scl_output.append("")
# Declaraciones VAR_TEMP adicionales detectadas
temp_vars = set()
temp_pattern = re.compile(r'"?#(_temp_[a-zA-Z0-9_]+)"?|"?(_temp_[a-zA-Z0-9_]+)"?')
for network in data.get('networks', []):
for instruction in network.get('logic', []):
scl_code = instruction.get('scl', ''); edge_update_code = instruction.get('_edge_mem_update_scl','')
code_to_scan = (scl_code if scl_code else '') + '\n' + (edge_update_code if edge_update_code else '')
if code_to_scan:
found_temps = temp_pattern.findall(code_to_scan)
for temp_tuple in found_temps:
temp_name = next((t for t in temp_tuple if t), None)
if temp_name: temp_vars.add("#"+temp_name if not temp_name.startswith("#") else temp_name)
additional_temps = sorted(list(temp_vars - declared_temps))
if additional_temps:
if not interface_data.get("Temp"): scl_output.append("VAR_TEMP")
for var_name in additional_temps:
scl_name = format_variable_name(var_name); inferred_type = "Bool" # Asumir Bool
scl_output.append(f" {scl_name} : {inferred_type}; // Auto-generated temporary")
if not interface_data.get("Temp"): scl_output.append("END_VAR"); scl_output.append("")
# Cuerpo del Bloque FC/FB
scl_output.append("BEGIN"); scl_output.append("")
# Iterar por redes y lógica (como antes, incluyendo manejo STL Markdown)
for i, network in enumerate(data.get('networks', [])):
network_title = network.get('title', f'Network {network.get("id")}')
network_comment = network.get('comment', '')
network_lang = network.get('language', 'LAD')
scl_output.append(f" // Network {i+1}: {network_title} (Original Language: {network_lang})")
if network_comment:
for line in network_comment.splitlines(): scl_output.append(f" // {line}")
scl_output.append("")
network_has_code = False
if network_lang == "STL":
network_has_code = True
if network.get('logic') and network['logic'][0].get("type") == "RAW_STL_CHUNK":
raw_stl_code = network['logic'][0].get("stl", "// ERROR: STL code missing")
scl_output.append(f" {'//'} ```STL")
for stl_line in raw_stl_code.splitlines(): scl_output.append(f" {stl_line}")
scl_output.append(f" {'//'} ```")
else: scl_output.append(" // ERROR: Contenido STL inesperado.")
else: # LAD, FBD, SCL, etc.
for instruction in network.get('logic', []):
instruction_type = instruction.get("type", ""); scl_code = instruction.get('scl', ''); is_grouped = instruction.get("grouped", False)
if is_grouped: continue
if (instruction_type.endswith(SCL_SUFFIX) or instruction_type in ["RAW_SCL_CHUNK", "UNSUPPORTED_LANG"]) and scl_code:
is_only_comment = all(line.strip().startswith("//") for line in scl_code.splitlines() if line.strip())
is_if_block = scl_code.strip().startswith("IF")
if not is_only_comment or is_if_block:
network_has_code = True
for line in scl_code.splitlines(): scl_output.append(f" {line}")
if network_has_code: scl_output.append("")
else: scl_output.append(f" // Network did not produce printable SCL code."); scl_output.append("")
# Fin del bloque FC/FB
scl_output.append(f"END_{scl_block_keyword}")
# --- Escritura del Archivo SCL (Común) ---
print(f"Escribiendo archivo SCL en: {output_scl_filepath}")
try:
with open(output_scl_filepath, 'w', encoding='utf-8') as f:
for line in scl_output: f.write(line + '\n')
print("Generación de SCL completada.")
except Exception as e:
print(f"Error al escribir el archivo SCL: {e}"); traceback.print_exc()
# --- Ejecución (sin cambios) ---
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Generate final SCL file from processed JSON (FC/FB/DB).")
parser.add_argument("source_xml_filepath", help="Path to the original source XML file.")
args = parser.parse_args()
source_xml_file = args.source_xml_filepath
if not os.path.exists(source_xml_file):
print(f"Advertencia (x3): Archivo XML original no encontrado: '{source_xml_file}', pero se intentará encontrar el JSON procesado.")
# Continuar, pero verificar existencia del JSON procesado
xml_filename_base = os.path.splitext(os.path.basename(source_xml_file))[0]
base_dir = os.path.dirname(source_xml_file)
input_json_file = os.path.join(base_dir, f"{xml_filename_base}_simplified_processed.json")
output_scl_file = os.path.join(base_dir, f"{xml_filename_base}_simplified_processed.scl")
print(f"(x3) Generando SCL: '{os.path.relpath(input_json_file)}' -> '{os.path.relpath(output_scl_file)}'")
if not os.path.exists(input_json_file):
print(f"Error Fatal (x3): Archivo JSON procesado no encontrado: '{input_json_file}'")
print(f"Asegúrate de que 'x1_to_json.py' y 'x2_process.py' se ejecutaron correctamente para '{os.path.relpath(source_xml_file)}'.")
sys.exit(1)
else:
try:
generate_scl(input_json_file, output_scl_file)
except Exception as e:
print(f"Error Crítico (x3) durante la generación de SCL desde '{input_json_file}': {e}")
traceback.print_exc()
sys.exit(1)

View File

@ -3,7 +3,8 @@ import subprocess
import os import os
import sys import sys
import locale import locale
import glob # <--- Importar glob para buscar archivos import glob # <--- Importar glob para buscar archivos
# (Función get_console_encoding y variable CONSOLE_ENCODING como en la respuesta anterior) # (Función get_console_encoding y variable CONSOLE_ENCODING como en la respuesta anterior)
def get_console_encoding(): def get_console_encoding():
@ -11,12 +12,14 @@ def get_console_encoding():
try: try:
return locale.getpreferredencoding(False) return locale.getpreferredencoding(False)
except Exception: except Exception:
return 'cp1252' return "cp1252"
CONSOLE_ENCODING = get_console_encoding() CONSOLE_ENCODING = get_console_encoding()
# Descomenta la siguiente línea si quieres ver la codificación detectada: # Descomenta la siguiente línea si quieres ver la codificación detectada:
# print(f"Detected console encoding: {CONSOLE_ENCODING}") # print(f"Detected console encoding: {CONSOLE_ENCODING}")
# (Función run_script como en la respuesta anterior, usando CONSOLE_ENCODING) # (Función run_script como en la respuesta anterior, usando CONSOLE_ENCODING)
def run_script(script_name, xml_arg): def run_script(script_name, xml_arg):
"""Runs a given script with the specified XML file argument.""" """Runs a given script with the specified XML file argument."""
@ -24,12 +27,14 @@ def run_script(script_name, xml_arg):
command = [sys.executable, script_path, xml_arg] command = [sys.executable, script_path, xml_arg]
print(f"\n--- Running {script_name} with argument: {xml_arg} ---") print(f"\n--- Running {script_name} with argument: {xml_arg} ---")
try: try:
result = subprocess.run(command, result = subprocess.run(
check=True, command,
capture_output=True, check=True,
text=True, capture_output=True,
encoding=CONSOLE_ENCODING, text=True,
errors='replace') # 'replace' para evitar errores encoding=CONSOLE_ENCODING,
errors="replace",
) # 'replace' para evitar errores
# Imprimir stdout y stderr # Imprimir stdout y stderr
# Eliminar saltos de línea extra al final si existen # Eliminar saltos de línea extra al final si existen
@ -49,26 +54,35 @@ def run_script(script_name, xml_arg):
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
print(f"Error running {script_name}:") print(f"Error running {script_name}:")
print(f"Return code: {e.returncode}") print(f"Return code: {e.returncode}")
stdout_decoded = e.stdout.decode(CONSOLE_ENCODING, errors='replace').strip() if isinstance(e.stdout, bytes) else (e.stdout or "").strip() stdout_decoded = (
stderr_decoded = e.stderr.decode(CONSOLE_ENCODING, errors='replace').strip() if isinstance(e.stderr, bytes) else (e.stderr or "").strip() e.stdout.decode(CONSOLE_ENCODING, errors="replace").strip()
if isinstance(e.stdout, bytes)
else (e.stdout or "").strip()
)
stderr_decoded = (
e.stderr.decode(CONSOLE_ENCODING, errors="replace").strip()
if isinstance(e.stderr, bytes)
else (e.stderr or "").strip()
)
if stdout_decoded: if stdout_decoded:
print("--- Stdout ---") print("--- Stdout ---")
print(stdout_decoded) print(stdout_decoded)
if stderr_decoded: if stderr_decoded:
print("--- Stderr ---") print("--- Stderr ---")
print(stderr_decoded) print(stderr_decoded)
print("--------------") print("--------------")
return False return False
except Exception as e: except Exception as e:
print(f"An unexpected error occurred while running {script_name}: {e}") print(f"An unexpected error occurred while running {script_name}: {e}")
return False return False
# --- NUEVA FUNCIÓN PARA SELECCIONAR ARCHIVO --- # --- NUEVA FUNCIÓN PARA SELECCIONAR ARCHIVO ---
def select_xml_file(): def select_xml_file():
"""Busca archivos .xml, los lista y pide al usuario que elija uno.""" """Busca archivos .xml, los lista y pide al usuario que elija uno."""
print("No XML file specified. Searching for XML files in current directory...") print("No XML file specified. Searching for XML files in current directory...")
# Buscar archivos .xml en el directorio actual (.) # Buscar archivos .xml en el directorio actual (.)
xml_files = sorted(glob.glob('*.xml')) # sorted para orden alfabético xml_files = sorted(glob.glob("*.xml")) # sorted para orden alfabético
if not xml_files: if not xml_files:
print("Error: No .xml files found in the current directory.") print("Error: No .xml files found in the current directory.")
@ -80,7 +94,9 @@ def select_xml_file():
while True: while True:
try: try:
choice = input(f"Enter the number of the file to process (1-{len(xml_files)}): ") choice = input(
f"Enter the number of the file to process (1-{len(xml_files)}): "
)
choice_num = int(choice) choice_num = int(choice)
if 1 <= choice_num <= len(xml_files): if 1 <= choice_num <= len(xml_files):
selected_file = xml_files[choice_num - 1] selected_file = xml_files[choice_num - 1]
@ -90,9 +106,11 @@ def select_xml_file():
print("Invalid choice. Please enter a number from the list.") print("Invalid choice. Please enter a number from the list.")
except ValueError: except ValueError:
print("Invalid input. Please enter a number.") print("Invalid input. Please enter a number.")
except EOFError: # Manejar si la entrada se cierra inesperadamente except EOFError: # Manejar si la entrada se cierra inesperadamente
print("\nSelection cancelled.") print("\nSelection cancelled.")
sys.exit(1) sys.exit(1)
# --- FIN NUEVA FUNCIÓN --- # --- FIN NUEVA FUNCIÓN ---
@ -100,30 +118,36 @@ if __name__ == "__main__":
# Imports necesarios para esta sección # Imports necesarios para esta sección
import os import os
import sys import sys
import glob # Asegúrate de que glob esté importado al principio del archivo import glob # Asegúrate de que glob esté importado al principio del archivo
# Directorio base donde buscar los archivos XML (relativo al script) # Directorio base donde buscar los archivos XML (relativo al script)
base_search_dir = "XML Project" base_search_dir = "XML Project"
script_dir = os.path.dirname(__file__) # Directorio donde está x0_main.py script_dir = os.path.dirname(__file__) # Directorio donde está x0_main.py
xml_project_dir = os.path.join(script_dir, base_search_dir) xml_project_dir = os.path.join(script_dir, base_search_dir)
print(f"Buscando archivos XML recursivamente en: '{xml_project_dir}'") print(f"Buscando archivos XML recursivamente en: '{xml_project_dir}'")
# Verificar si el directorio 'XML Project' existe # Verificar si el directorio 'XML Project' existe
if not os.path.isdir(xml_project_dir): if not os.path.isdir(xml_project_dir):
print(f"Error: El directorio '{xml_project_dir}' no existe o no es un directorio.") print(
print("Por favor, crea el directorio 'XML Project' en la misma carpeta que este script y coloca tus archivos XML dentro.") f"Error: El directorio '{xml_project_dir}' no existe o no es un directorio."
)
print(
"Por favor, crea el directorio 'XML Project' en la misma carpeta que este script y coloca tus archivos XML dentro."
)
sys.exit(1) sys.exit(1)
# Buscar todos los archivos .xml recursivamente dentro de xml_project_dir # Buscar todos los archivos .xml recursivamente dentro de xml_project_dir
# Usamos os.path.join para construir la ruta de búsqueda correctamente # Usamos os.path.join para construir la ruta de búsqueda correctamente
# y '**/*.xml' para la recursividad con glob # y '**/*.xml' para la recursividad con glob
search_pattern = os.path.join(xml_project_dir, '**', '*.xml') search_pattern = os.path.join(xml_project_dir, "**", "*.xml")
xml_files_found = glob.glob(search_pattern, recursive=True) xml_files_found = glob.glob(search_pattern, recursive=True)
if not xml_files_found: if not xml_files_found:
print(f"No se encontraron archivos XML en '{xml_project_dir}' o sus subdirectorios.") print(
sys.exit(0) # Salir limpiamente si no hay archivos f"No se encontraron archivos XML en '{xml_project_dir}' o sus subdirectorios."
)
sys.exit(0) # Salir limpiamente si no hay archivos
print(f"Se encontraron {len(xml_files_found)} archivos XML para procesar:") print(f"Se encontraron {len(xml_files_found)} archivos XML para procesar:")
# Ordenar para un procesamiento predecible (opcional) # Ordenar para un procesamiento predecible (opcional)
@ -141,7 +165,9 @@ if __name__ == "__main__":
processed_count = 0 processed_count = 0
failed_count = 0 failed_count = 0
for xml_filepath in xml_files_found: for xml_filepath in xml_files_found:
print(f"\n--- Iniciando pipeline para: {os.path.relpath(xml_filepath, script_dir)} ---") print(
f"\n--- Iniciando pipeline para: {os.path.relpath(xml_filepath, script_dir)} ---"
)
# Usar la ruta absoluta para evitar problemas si los scripts cambian de directorio # Usar la ruta absoluta para evitar problemas si los scripts cambian de directorio
absolute_xml_filepath = os.path.abspath(xml_filepath) absolute_xml_filepath = os.path.abspath(xml_filepath)
@ -150,26 +176,37 @@ if __name__ == "__main__":
# La función run_script ya está definida en tu script x0_main.py # La función run_script ya está definida en tu script x0_main.py
success = True success = True
if not run_script(script1, absolute_xml_filepath): if not run_script(script1, absolute_xml_filepath):
print(f"\nPipeline falló en el script '{script1}' para el archivo: {os.path.relpath(xml_filepath, script_dir)}") print(
f"\nPipeline falló en el script '{script1}' para el archivo: {os.path.relpath(xml_filepath, script_dir)}"
)
success = False success = False
elif not run_script(script2, absolute_xml_filepath): elif not run_script(script2, absolute_xml_filepath):
print(f"\nPipeline falló en el script '{script2}' para el archivo: {os.path.relpath(xml_filepath, script_dir)}") print(
f"\nPipeline falló en el script '{script2}' para el archivo: {os.path.relpath(xml_filepath, script_dir)}"
)
success = False success = False
elif not run_script(script3, absolute_xml_filepath): elif not run_script(script3, absolute_xml_filepath):
print(f"\nPipeline falló en el script '{script3}' para el archivo: {os.path.relpath(xml_filepath, script_dir)}") print(
f"\nPipeline falló en el script '{script3}' para el archivo: {os.path.relpath(xml_filepath, script_dir)}"
)
success = False success = False
if success: if success:
print(f"--- Pipeline completado exitosamente para: {os.path.relpath(xml_filepath, script_dir)} ---") print(
f"--- Pipeline completado exitosamente para: {os.path.relpath(xml_filepath, script_dir)} ---"
)
processed_count += 1 processed_count += 1
else: else:
failed_count += 1 failed_count += 1
print(f"--- Pipeline falló para: {os.path.relpath(xml_filepath, script_dir)} ---") print(
f"--- Pipeline falló para: {os.path.relpath(xml_filepath, script_dir)} ---"
)
print("\n--- Resumen Final del Procesamiento ---") print("\n--- Resumen Final del Procesamiento ---")
print(f"Total de archivos XML encontrados: {len(xml_files_found)}") print(f"Total de archivos XML encontrados: {len(xml_files_found)}")
print(f"Archivos procesados exitosamente por el pipeline completo: {processed_count}") print(
f"Archivos procesados exitosamente por el pipeline completo: {processed_count}"
)
print(f"Archivos que fallaron en algún punto del pipeline: {failed_count}") print(f"Archivos que fallaron en algún punto del pipeline: {failed_count}")
print("---------------------------------------") print("---------------------------------------")
xml_filename = None xml_filename = None
@ -217,4 +254,4 @@ if __name__ == "__main__":
else: else:
print("\nPipeline failed at script:", script2) print("\nPipeline failed at script:", script2)
else: else:
print("\nPipeline failed at script:", script1) print("\nPipeline failed at script:", script1)

File diff suppressed because it is too large Load Diff

View File

@ -5,29 +5,30 @@ import os
import copy import copy
import traceback import traceback
import re import re
import importlib import importlib
import sys import sys
import sympy # Import sympy import sympy # Import sympy
# Import necessary components from processors directory # Import necessary components from processors directory
from processors.processor_utils import ( from processors.processor_utils import (
format_variable_name, # Keep if used outside processors format_variable_name, # Keep if used outside processors
sympy_expr_to_scl, # Needed for IF grouping and maybe others sympy_expr_to_scl, # Needed for IF grouping and maybe others
# get_target_scl_name might be used here? Unlikely. # get_target_scl_name might be used here? Unlikely.
) )
from processors.symbol_manager import SymbolManager # Import the manager from processors.symbol_manager import SymbolManager # Import the manager
# --- Constantes y Configuración --- # --- Constantes y Configuración ---
# SCL_SUFFIX = "_scl" # Old suffix # SCL_SUFFIX = "_scl" # Old suffix
SCL_SUFFIX = "_sympy_processed" # New suffix to indicate processing method SCL_SUFFIX = "_sympy_processed" # New suffix to indicate processing method
GROUPED_COMMENT = "// Logic included in grouped IF" GROUPED_COMMENT = "// Logic included in grouped IF"
SIMPLIFIED_IF_COMMENT = "// Simplified IF condition by script" # May still be useful SIMPLIFIED_IF_COMMENT = "// Simplified IF condition by script" # May still be useful
# Global data dictionary (consider passing 'data' as argument if needed elsewhere) # Global data dictionary (consider passing 'data' as argument if needed elsewhere)
# It's currently used by process_group_ifs implicitly via the outer scope, # It's currently used by process_group_ifs implicitly via the outer scope,
# which works but passing it explicitly might be cleaner. # which works but passing it explicitly might be cleaner.
data = {} data = {}
def process_group_ifs(instruction, network_id, sympy_map, symbol_manager, data): def process_group_ifs(instruction, network_id, sympy_map, symbol_manager, data):
""" """
Busca condiciones (ya procesadas -> tienen expr SymPy en sympy_map) Busca condiciones (ya procesadas -> tienen expr SymPy en sympy_map)
@ -37,24 +38,45 @@ def process_group_ifs(instruction, network_id, sympy_map, symbol_manager, data):
(Esta es la implementación de la función como la tenías en el archivo original) (Esta es la implementación de la función como la tenías en el archivo original)
""" """
instr_uid = instruction["instruction_uid"] instr_uid = instruction["instruction_uid"]
instr_type_original = instruction.get("type", "").replace(SCL_SUFFIX, "").replace("_error", "") instr_type_original = (
instruction.get("type", "").replace(SCL_SUFFIX, "").replace("_error", "")
)
made_change = False made_change = False
# Check if this instruction *could* generate a condition suitable for grouping # Check if this instruction *could* generate a condition suitable for grouping
# It must have been processed by the new SymPy method # It must have been processed by the new SymPy method
if ( if (
not instruction.get("type", "").endswith(SCL_SUFFIX) # Check if processed by new method not instruction.get("type", "").endswith(
SCL_SUFFIX
) # Check if processed by new method
or "_error" in instruction.get("type", "") or "_error" in instruction.get("type", "")
or instruction.get("grouped", False) or instruction.get("grouped", False)
or instr_type_original not in [ # Original types that produce boolean results or instr_type_original
"Contact", "O", "Eq", "Ne", "Gt", "Lt", "Ge", "Le", "PBox", "NBox", "And", "Xor", "Not" # Add others like comparison not in [ # Original types that produce boolean results
"Contact",
"O",
"Eq",
"Ne",
"Gt",
"Lt",
"Ge",
"Le",
"PBox",
"NBox",
"And",
"Xor",
"Not", # Add others like comparison
] ]
): ):
return False return False
# Avoid reagruping if SCL already contains a complex IF (less likely now) # Avoid reagruping if SCL already contains a complex IF (less likely now)
current_scl = instruction.get("scl", "") current_scl = instruction.get("scl", "")
if current_scl.strip().startswith("IF") and "END_IF;" in current_scl and GROUPED_COMMENT not in current_scl: if (
current_scl.strip().startswith("IF")
and "END_IF;" in current_scl
and GROUPED_COMMENT not in current_scl
):
return False return False
# *** Get the SymPy expression for the condition *** # *** Get the SymPy expression for the condition ***
@ -62,20 +84,34 @@ def process_group_ifs(instruction, network_id, sympy_map, symbol_manager, data):
sympy_condition_expr = sympy_map.get(map_key_out) sympy_condition_expr = sympy_map.get(map_key_out)
# No SymPy expression found or trivial conditions # No SymPy expression found or trivial conditions
if sympy_condition_expr is None or sympy_condition_expr in [sympy.true, sympy.false]: if sympy_condition_expr is None or sympy_condition_expr in [
sympy.true,
sympy.false,
]:
return False return False
# --- Find consumer instructions (logic similar to before) --- # --- Find consumer instructions (logic similar to before) ---
grouped_instructions_cores = [] grouped_instructions_cores = []
consumer_instr_list = [] consumer_instr_list = []
network_logic = next((net["logic"] for net in data["networks"] if net["id"] == network_id), []) network_logic = next(
if not network_logic: return False (net["logic"] for net in data["networks"] if net["id"] == network_id), []
)
if not network_logic:
return False
groupable_types = [ # Types whose *final SCL* we want to group groupable_types = [ # Types whose *final SCL* we want to group
"Move", "Add", "Sub", "Mul", "Div", "Mod", "Convert", "Move",
"Call_FC", "Call_FB", # Assuming these generate final SCL in their processors now "Add",
"Sub",
"Mul",
"Div",
"Mod",
"Convert",
"Call_FC",
"Call_FB", # Assuming these generate final SCL in their processors now
# SCoil/RCoil might also be groupable if their SCL is final assignment # SCoil/RCoil might also be groupable if their SCL is final assignment
"SCoil", "RCoil" "SCoil",
"RCoil",
] ]
for consumer_instr in network_logic: for consumer_instr in network_logic:
@ -84,31 +120,45 @@ def process_group_ifs(instruction, network_id, sympy_map, symbol_manager, data):
continue continue
consumer_en = consumer_instr.get("inputs", {}).get("en") consumer_en = consumer_instr.get("inputs", {}).get("en")
consumer_type = consumer_instr.get("type", "") # Current type suffix matters consumer_type = consumer_instr.get("type", "") # Current type suffix matters
consumer_type_original = consumer_type.replace(SCL_SUFFIX, "").replace("_error", "") consumer_type_original = consumer_type.replace(SCL_SUFFIX, "").replace(
"_error", ""
)
is_enabled_by_us = False is_enabled_by_us = False
if ( isinstance(consumer_en, dict) and consumer_en.get("type") == "connection" and if (
consumer_en.get("source_instruction_uid") == instr_uid and isinstance(consumer_en, dict)
consumer_en.get("source_pin") == "out"): and consumer_en.get("type") == "connection"
and consumer_en.get("source_instruction_uid") == instr_uid
and consumer_en.get("source_pin") == "out"
):
is_enabled_by_us = True is_enabled_by_us = True
# Check if consumer is groupable AND has its final SCL generated # Check if consumer is groupable AND has its final SCL generated
# The suffix check needs adjustment based on how terminating processors set it. # The suffix check needs adjustment based on how terminating processors set it.
# Assuming processors like Move, Add, Call, SCoil, RCoil NOW generate final SCL and add a suffix. # Assuming processors like Move, Add, Call, SCoil, RCoil NOW generate final SCL and add a suffix.
if ( is_enabled_by_us and consumer_type.endswith(SCL_SUFFIX) and # Or a specific "final_scl" suffix if (
consumer_type_original in groupable_types ): is_enabled_by_us
and consumer_type.endswith(SCL_SUFFIX) # Or a specific "final_scl" suffix
and consumer_type_original in groupable_types
):
consumer_scl = consumer_instr.get("scl", "") consumer_scl = consumer_instr.get("scl", "")
# Extract core SCL (logic is similar, maybe simpler if SCL is cleaner now) # Extract core SCL (logic is similar, maybe simpler if SCL is cleaner now)
core_scl = None core_scl = None
if consumer_scl: if consumer_scl:
# If consumer SCL itself is an IF generated by EN, take the body # If consumer SCL itself is an IF generated by EN, take the body
if consumer_scl.strip().startswith("IF"): if consumer_scl.strip().startswith("IF"):
match = re.search(r"THEN\s*(.*?)\s*END_IF;", consumer_scl, re.DOTALL | re.IGNORECASE) match = re.search(
core_scl = match.group(1).strip() if match else None r"THEN\s*(.*?)\s*END_IF;",
elif not consumer_scl.strip().startswith("//"): # Otherwise, take the whole line if not comment consumer_scl,
core_scl = consumer_scl.strip() re.DOTALL | re.IGNORECASE,
)
core_scl = match.group(1).strip() if match else None
elif not consumer_scl.strip().startswith(
"//"
): # Otherwise, take the whole line if not comment
core_scl = consumer_scl.strip()
if core_scl: if core_scl:
grouped_instructions_cores.append(core_scl) grouped_instructions_cores.append(core_scl)
@ -116,15 +166,19 @@ def process_group_ifs(instruction, network_id, sympy_map, symbol_manager, data):
# --- If groupable consumers found --- # --- If groupable consumers found ---
if len(grouped_instructions_cores) > 1: if len(grouped_instructions_cores) > 1:
print(f"INFO: Agrupando {len(grouped_instructions_cores)} instr. bajo condición de {instr_type_original} UID {instr_uid}") print(
f"INFO: Agrupando {len(grouped_instructions_cores)} instr. bajo condición de {instr_type_original} UID {instr_uid}"
)
# *** Simplify the SymPy condition *** # *** Simplify the SymPy condition ***
try: try:
#simplified_expr = sympy.simplify_logic(sympy_condition_expr, force=True) # simplified_expr = sympy.simplify_logic(sympy_condition_expr, force=True)
simplified_expr = sympy.logic.boolalg.to_dnf(sympy_condition_expr, simplify=True) simplified_expr = sympy.logic.boolalg.to_dnf(
sympy_condition_expr, simplify=True
)
except Exception as e: except Exception as e:
print(f"Error simplifying condition for grouping UID {instr_uid}: {e}") print(f"Error simplifying condition for grouping UID {instr_uid}: {e}")
simplified_expr = sympy_condition_expr # Fallback simplified_expr = sympy_condition_expr # Fallback
# *** Convert simplified condition to SCL string *** # *** Convert simplified condition to SCL string ***
condition_scl_simplified = sympy_expr_to_scl(simplified_expr, symbol_manager) condition_scl_simplified = sympy_expr_to_scl(simplified_expr, symbol_manager)
@ -132,7 +186,9 @@ def process_group_ifs(instruction, network_id, sympy_map, symbol_manager, data):
# *** Build the grouped IF SCL *** # *** Build the grouped IF SCL ***
scl_grouped_lines = [f"IF {condition_scl_simplified} THEN"] scl_grouped_lines = [f"IF {condition_scl_simplified} THEN"]
for core_line in grouped_instructions_cores: for core_line in grouped_instructions_cores:
indented_core = "\n".join([f" {line.strip()}" for line in core_line.splitlines()]) indented_core = "\n".join(
[f" {line.strip()}" for line in core_line.splitlines()]
)
scl_grouped_lines.append(indented_core) scl_grouped_lines.append(indented_core)
scl_grouped_lines.append("END_IF;") scl_grouped_lines.append("END_IF;")
final_grouped_scl = "\n".join(scl_grouped_lines) final_grouped_scl = "\n".join(scl_grouped_lines)
@ -147,18 +203,19 @@ def process_group_ifs(instruction, network_id, sympy_map, symbol_manager, data):
return made_change return made_change
def load_processors(processors_dir="processors"): def load_processors(processors_dir="processors"):
""" """
Escanea el directorio, importa módulos, construye el mapa y una lista Escanea el directorio, importa módulos, construye el mapa y una lista
ordenada por prioridad. ordenada por prioridad.
""" """
processor_map = {} processor_map = {}
processor_list_unsorted = [] # Lista para guardar (priority, type_name, func) processor_list_unsorted = [] # Lista para guardar (priority, type_name, func)
default_priority = 10 # Prioridad si no se define en get_processor_info default_priority = 10 # Prioridad si no se define en get_processor_info
if not os.path.isdir(processors_dir): if not os.path.isdir(processors_dir):
print(f"Error: Directorio de procesadores no encontrado: '{processors_dir}'") print(f"Error: Directorio de procesadores no encontrado: '{processors_dir}'")
return processor_map, [] # Devuelve mapa vacío y lista vacía return processor_map, [] # Devuelve mapa vacío y lista vacía
print(f"Cargando procesadores desde: '{processors_dir}'") print(f"Cargando procesadores desde: '{processors_dir}'")
processors_package = os.path.basename(processors_dir) processors_package = os.path.basename(processors_dir)
@ -170,7 +227,9 @@ def load_processors(processors_dir="processors"):
try: try:
module = importlib.import_module(full_module_name) module = importlib.import_module(full_module_name)
if hasattr(module, 'get_processor_info') and callable(module.get_processor_info): if hasattr(module, "get_processor_info") and callable(
module.get_processor_info
):
processor_info = module.get_processor_info() processor_info = module.get_processor_info()
info_list = [] info_list = []
if isinstance(processor_info, dict): if isinstance(processor_info, dict):
@ -178,29 +237,51 @@ def load_processors(processors_dir="processors"):
elif isinstance(processor_info, list): elif isinstance(processor_info, list):
info_list = processor_info info_list = processor_info
else: else:
print(f" Advertencia: get_processor_info en {full_module_name} devolvió tipo inesperado. Se ignora.") print(
f" Advertencia: get_processor_info en {full_module_name} devolvió tipo inesperado. Se ignora."
)
continue continue
for info in info_list: for info in info_list:
if isinstance(info, dict) and 'type_name' in info and 'processor_func' in info: if (
type_name = info['type_name'].lower() isinstance(info, dict)
processor_func = info['processor_func'] and "type_name" in info
and "processor_func" in info
):
type_name = info["type_name"].lower()
processor_func = info["processor_func"]
# Obtener prioridad, usar default si no existe # Obtener prioridad, usar default si no existe
priority = info.get('priority', default_priority) priority = info.get("priority", default_priority)
if callable(processor_func): if callable(processor_func):
if type_name in processor_map: if type_name in processor_map:
print(f" Advertencia: '{type_name}' en {full_module_name} sobrescribe definición anterior.") print(
f" Advertencia: '{type_name}' en {full_module_name} sobrescribe definición anterior."
)
processor_map[type_name] = processor_func processor_map[type_name] = processor_func
# Añadir a la lista para ordenar # Añadir a la lista para ordenar
processor_list_unsorted.append({'priority': priority, 'type_name': type_name, 'func': processor_func}) processor_list_unsorted.append(
print(f" - Cargado '{type_name}' (Prio: {priority}) desde {module_name_rel}.py") {
"priority": priority,
"type_name": type_name,
"func": processor_func,
}
)
print(
f" - Cargado '{type_name}' (Prio: {priority}) desde {module_name_rel}.py"
)
else: else:
print(f" Advertencia: 'processor_func' para '{type_name}' en {full_module_name} no es callable.") print(
f" Advertencia: 'processor_func' para '{type_name}' en {full_module_name} no es callable."
)
else: else:
print(f" Advertencia: Entrada inválida en {full_module_name}: {info}") print(
f" Advertencia: Entrada inválida en {full_module_name}: {info}"
)
else: else:
print(f" Advertencia: Módulo {module_name_rel}.py no tiene 'get_processor_info'.") print(
f" Advertencia: Módulo {module_name_rel}.py no tiene 'get_processor_info'."
)
except ImportError as e: except ImportError as e:
print(f"Error importando {full_module_name}: {e}") print(f"Error importando {full_module_name}: {e}")
@ -209,22 +290,24 @@ def load_processors(processors_dir="processors"):
traceback.print_exc() traceback.print_exc()
# Ordenar la lista por prioridad (menor primero) # Ordenar la lista por prioridad (menor primero)
processor_list_sorted = sorted(processor_list_unsorted, key=lambda x: x['priority']) processor_list_sorted = sorted(processor_list_unsorted, key=lambda x: x["priority"])
print(f"\nTotal de tipos de procesadores cargados: {len(processor_map)}") print(f"\nTotal de tipos de procesadores cargados: {len(processor_map)}")
print(f"Orden de procesamiento por prioridad: {[item['type_name'] for item in processor_list_sorted]}") print(
f"Orden de procesamiento por prioridad: {[item['type_name'] for item in processor_list_sorted]}"
)
# Devolver el mapa (para lookup rápido si es necesario) y la lista ordenada # Devolver el mapa (para lookup rápido si es necesario) y la lista ordenada
return processor_map, processor_list_sorted return processor_map, processor_list_sorted
# --- Bucle Principal de Procesamiento (Modificado para STL) --- # --- Bucle Principal de Procesamiento (Modificado para STL) ---
def process_json_to_scl(json_filepath): def process_json_to_scl(json_filepath):
""" """
Lee el JSON simplificado, aplica los procesadores dinámicamente cargados Lee JSON simplificado, aplica procesadores dinámicos (ignorando redes STL y bloques DB),
siguiendo un orden de prioridad (ignorando redes STL), y guarda el JSON procesado. y guarda JSON procesado.
""" """
global data # Necesario para que load_processors y process_group_ifs (definidas fuera) puedan acceder a ella. global data
# Considerar pasar 'data' como argumento si es posible refactorizar.
if not os.path.exists(json_filepath): if not os.path.exists(json_filepath):
print(f"Error: JSON no encontrado: {json_filepath}") print(f"Error: JSON no encontrado: {json_filepath}")
@ -232,48 +315,83 @@ def process_json_to_scl(json_filepath):
print(f"Cargando JSON desde: {json_filepath}") print(f"Cargando JSON desde: {json_filepath}")
try: try:
with open(json_filepath, "r", encoding="utf-8") as f: with open(json_filepath, "r", encoding="utf-8") as f:
data = json.load(f) # Carga en 'data' global data = json.load(f)
except Exception as e: except Exception as e:
print(f"Error al cargar JSON: {e}") print(f"Error al cargar JSON: {e}")
traceback.print_exc() traceback.print_exc()
return return
# --- Carga dinámica de procesadores --- # --- Obtener lenguaje del bloque principal ---
block_language = data.get("language", "Unknown")
block_type = data.get("block_type", "Unknown") # FC, FB, GlobalDB
print(f"Procesando bloque tipo: {block_type}, Lenguaje principal: {block_language}")
# --- SI ES UN DB, SALTAR EL PROCESAMIENTO LÓGICO ---
if block_language == "DB":
print(
"INFO: El bloque es un Data Block (DB). Saltando procesamiento lógico de x2."
)
# Simplemente guardamos una copia (o el mismo archivo si no se requiere sufijo)
output_filename = json_filepath.replace(
"_simplified.json", "_simplified_processed.json"
)
print(f"Guardando JSON de DB (sin cambios lógicos) en: {output_filename}")
try:
with open(output_filename, "w", encoding="utf-8") as f:
json.dump(data, f, indent=4, ensure_ascii=False)
print("Guardado de DB completado.")
except Exception as e:
print(f"Error Crítico al guardar JSON del DB: {e}")
traceback.print_exc()
return # <<< SALIR TEMPRANO PARA DBs
# --- SI NO ES DB, CONTINUAR CON EL PROCESAMIENTO LÓGICO (FC/FB) ---
print("INFO: El bloque es FC/FB. Iniciando procesamiento lógico...")
script_dir = os.path.dirname(__file__) script_dir = os.path.dirname(__file__)
processors_dir_path = os.path.join(script_dir, 'processors') processors_dir_path = os.path.join(script_dir, "processors")
processor_map, sorted_processors = load_processors(processors_dir_path) processor_map, sorted_processors = load_processors(processors_dir_path)
if not processor_map: if not processor_map:
print("Error crítico: No se cargaron procesadores. Abortando.") print("Error crítico: No se cargaron procesadores. Abortando.")
return return
# --- Crear mapas de acceso por red ---
network_access_maps = {} network_access_maps = {}
# (La lógica para llenar network_access_maps no cambia, puedes copiarla de tu original) # Crear mapas de acceso por red (copiado/adaptado de versión anterior)
for network in data.get("networks", []): for network in data.get("networks", []):
net_id = network["id"] net_id = network["id"]
current_access_map = {} current_access_map = {}
for instr in network.get("logic", []): for instr in network.get("logic", []):
for _, source in instr.get("inputs", {}).items(): for _, source in instr.get("inputs", {}).items():
sources_to_check = (source if isinstance(source, list) else ([source] if isinstance(source, dict) else [])) sources_to_check = (
for src in sources_to_check: source
if (isinstance(src, dict) and src.get("uid") and src.get("type") in ["variable", "constant"]): if isinstance(source, list)
current_access_map[src["uid"]] = src else ([source] if isinstance(source, dict) else [])
for _, dest_list in instr.get("outputs", {}).items(): )
if isinstance(dest_list, list): for src in sources_to_check:
for dest in dest_list: if (
if (isinstance(dest, dict) and dest.get("uid") and dest.get("type") in ["variable", "constant"]): isinstance(src, dict)
current_access_map[dest["uid"]] = dest and src.get("uid")
and src.get("type") in ["variable", "constant"]
):
current_access_map[src["uid"]] = src
for _, dest_list in instr.get("outputs", {}).items():
if isinstance(dest_list, list):
for dest in dest_list:
if (
isinstance(dest, dict)
and dest.get("uid")
and dest.get("type") in ["variable", "constant"]
):
current_access_map[dest["uid"]] = dest
network_access_maps[net_id] = current_access_map network_access_maps[net_id] = current_access_map
# --- Inicializar mapa SymPy y SymbolManager ---
symbol_manager = SymbolManager() symbol_manager = SymbolManager()
sympy_map = {} sympy_map = {}
max_passes = 30 max_passes = 30
passes = 0 passes = 0
processing_complete = False processing_complete = False
print("\n--- Iniciando Bucle de Procesamiento Iterativo (con SymPy y prioridad) ---") print("\n--- Iniciando Bucle de Procesamiento Iterativo (FC/FB) ---")
while passes < max_passes and not processing_complete: while passes < max_passes and not processing_complete:
passes += 1 passes += 1
made_change_in_base_pass = False made_change_in_base_pass = False
@ -284,90 +402,99 @@ def process_json_to_scl(json_filepath):
# --- FASE 1: Procesadores Base (Ignorando STL) --- # --- FASE 1: Procesadores Base (Ignorando STL) ---
print(f" Fase 1 (SymPy Base - Orden por Prioridad):") print(f" Fase 1 (SymPy Base - Orden por Prioridad):")
num_sympy_processed_this_pass = 0 num_sympy_processed_this_pass = 0 # Resetear contador para el pase
for processor_info in sorted_processors: for processor_info in sorted_processors:
current_type_name = processor_info['type_name'] current_type_name = processor_info["type_name"]
func_to_call = processor_info['func'] func_to_call = processor_info["func"]
for network in data.get("networks", []): for network in data.get("networks", []):
network_id = network["id"] network_id = network["id"]
network_lang = network.get("language", "LAD") # Obtener lenguaje de la red network_lang = network.get("language", "LAD")
# *** IGNORAR REDES STL EN ESTA FASE ***
if network_lang == "STL": if network_lang == "STL":
continue # Saltar al siguiente network continue # Saltar STL
access_map = network_access_maps.get(network_id, {}) access_map = network_access_maps.get(network_id, {})
network_logic = network.get("logic", []) network_logic = network.get("logic", [])
for instruction in network_logic: for instruction in network_logic:
instr_uid = instruction.get("instruction_uid") instr_uid = instruction.get("instruction_uid")
instr_type_original = instruction.get("type", "Unknown") instr_type_original = instruction.get("type", "Unknown")
if (
# Saltar si ya procesado, error, agrupado o es chunk STL/SCL/Unsupported instr_type_original.endswith(SCL_SUFFIX)
if (instr_type_original.endswith(SCL_SUFFIX)
or "_error" in instr_type_original or "_error" in instr_type_original
or instruction.get("grouped", False) or instruction.get("grouped", False)
or instr_type_original in ["RAW_STL_CHUNK", "RAW_SCL_CHUNK", "UNSUPPORTED_LANG"]): or instr_type_original
in ["RAW_STL_CHUNK", "RAW_SCL_CHUNK", "UNSUPPORTED_LANG"]
):
continue continue
# Determinar tipo efectivo (como antes)
lookup_key = instr_type_original.lower() lookup_key = instr_type_original.lower()
effective_type_name = lookup_key effective_type_name = lookup_key
if instr_type_original == "Call": if instr_type_original == "Call":
block_type = instruction.get("block_type", "").upper() block_type = instruction.get("block_type", "").upper()
if block_type == "FC": effective_type_name = "call_fc" if block_type == "FC":
elif block_type == "FB": effective_type_name = "call_fb" effective_type_name = "call_fc"
elif block_type == "FB":
effective_type_name = "call_fb"
# Llamar al procesador si coincide el tipo
if effective_type_name == current_type_name: if effective_type_name == current_type_name:
try: try:
# Pasa sympy_map, symbol_manager y data changed = func_to_call(
changed = func_to_call(instruction, network_id, sympy_map, symbol_manager, data) instruction, network_id, sympy_map, symbol_manager, data
)
if changed: if changed:
made_change_in_base_pass = True made_change_in_base_pass = True
num_sympy_processed_this_pass += 1 num_sympy_processed_this_pass += 1
except Exception as e: except Exception as e:
print(f"ERROR(SymPy Base) al procesar {instr_type_original} UID {instr_uid}: {e}") print(
traceback.print_exc() f"ERROR(SymPy Base) al procesar {instr_type_original} UID {instr_uid}: {e}"
instruction["scl"] = f"// ERROR en SymPy procesador base: {e}" )
instruction["type"] = instr_type_original + "_error" traceback.print_exc()
made_change_in_base_pass = True # Marcar cambio aunque sea error instruction["scl"] = (
print(f" -> {num_sympy_processed_this_pass} instrucciones (no STL) procesadas con SymPy.") f"// ERROR en SymPy procesador base: {e}"
)
instruction["type"] = instr_type_original + "_error"
made_change_in_base_pass = True
print(
f" -> {num_sympy_processed_this_pass} instrucciones (no STL) procesadas con SymPy."
)
# --- FASE 2: Agrupación IF (Ignorando STL) --- # --- FASE 2: Agrupación IF (Ignorando STL) ---
if made_change_in_base_pass or passes == 1: if (
made_change_in_base_pass or passes == 1
): # Ejecutar siempre en el primer pase
print(f" Fase 2 (Agrupación IF con Simplificación):") print(f" Fase 2 (Agrupación IF con Simplificación):")
num_grouped_this_pass = 0 num_grouped_this_pass = 0 # Resetear contador para el pase
for network in data.get("networks", []): for network in data.get("networks", []):
network_id = network["id"] network_id = network["id"]
network_lang = network.get("language", "LAD") # Obtener lenguaje network_lang = network.get("language", "LAD")
# *** IGNORAR REDES STL EN ESTA FASE ***
if network_lang == "STL": if network_lang == "STL":
continue # Saltar red STL continue # Saltar STL
network_logic = network.get("logic", []) network_logic = network.get("logic", [])
for instruction in network_logic: for instruction in network_logic:
try: try:
# Llama a process_group_ifs (que necesita acceso a 'data' global o pasado) group_changed = process_group_ifs(
group_changed = process_group_ifs(instruction, network_id, sympy_map, symbol_manager, data) instruction, network_id, sympy_map, symbol_manager, data
if group_changed: )
made_change_in_group_pass = True if group_changed:
num_grouped_this_pass += 1 made_change_in_group_pass = True
num_grouped_this_pass += 1
except Exception as e: except Exception as e:
print(f"ERROR(GroupLoop) al intentar agrupar desde UID {instruction.get('instruction_uid')}: {e}") print(
traceback.print_exc() f"ERROR(GroupLoop) al intentar agrupar desde UID {instruction.get('instruction_uid')}: {e}"
print(f" -> {num_grouped_this_pass} agrupaciones realizadas (en redes no STL).") )
traceback.print_exc()
print(
f" -> {num_grouped_this_pass} agrupaciones realizadas (en redes no STL)."
)
# --- Comprobar si se completó el procesamiento --- # --- Comprobar si se completó el procesamiento ---
if not made_change_in_base_pass and not made_change_in_group_pass: if not made_change_in_base_pass and not made_change_in_group_pass:
print(f"\n--- No se hicieron más cambios en el pase {passes}. Proceso iterativo completado. ---") print(
f"\n--- No se hicieron más cambios en el pase {passes}. Proceso iterativo completado. ---"
)
processing_complete = True processing_complete = True
else: else:
print(f"--- Fin Pase {passes}: {num_sympy_processed_this_pass} proc SymPy, {num_grouped_this_pass} agrup. Continuando...") print(
f"--- Fin Pase {passes}: {num_sympy_processed_this_pass} proc SymPy, {num_grouped_this_pass} agrup. Continuando..."
)
# --- Comprobar límite de pases --- # --- Comprobar límite de pases ---
if passes == max_passes and not processing_complete: if passes == max_passes and not processing_complete:
@ -376,46 +503,51 @@ def process_json_to_scl(json_filepath):
# --- FIN BUCLE ITERATIVO --- # --- FIN BUCLE ITERATIVO ---
# --- Verificación Final (Ajustada para RAW_STL_CHUNK) --- # --- Verificación Final (Ajustada para RAW_STL_CHUNK) ---
print("\n--- Verificación Final de Instrucciones No Procesadas ---") print("\n--- Verificación Final de Instrucciones No Procesadas (FC/FB) ---")
unprocessed_count = 0 unprocessed_count = 0
unprocessed_details = [] unprocessed_details = []
# Añadir RAW_STL_CHUNK a los tipos ignorados ignored_types = [
ignored_types = ['raw_scl_chunk', 'unsupported_lang', 'raw_stl_chunk'] # Añadido raw_stl_chunk "raw_scl_chunk",
"unsupported_lang",
"raw_stl_chunk",
] # Añadido raw_stl_chunk
for network in data.get("networks", []): for network in data.get("networks", []):
network_id = network.get("id", "Unknown ID") network_id = network.get("id", "Unknown ID")
network_title = network.get("title", f"Network {network_id}") network_title = network.get("title", f"Network {network_id}")
network_lang = network.get("language", "LAD") # Obtener lenguaje network_lang = network.get("language", "LAD")
if network_lang == "STL":
# No verificar instrucciones dentro de redes STL, ya que no se procesan continue # No verificar redes STL
if network_lang == "STL": for instruction in network.get("logic", []):
continue instr_uid = instruction.get("instruction_uid", "Unknown UID")
instr_type = instruction.get("type", "Unknown Type")
for instruction in network.get("logic", []): is_grouped = instruction.get("grouped", False)
instr_uid = instruction.get("instruction_uid", "Unknown UID") if (
instr_type = instruction.get("type", "Unknown Type") not instr_type.endswith(SCL_SUFFIX)
is_grouped = instruction.get("grouped", False) and "_error" not in instr_type
and not is_grouped
# Condición revisada para ignorar los chunks crudos and instr_type.lower() not in ignored_types
if (not instr_type.endswith(SCL_SUFFIX) and ):
"_error" not in instr_type and unprocessed_count += 1
not is_grouped and unprocessed_details.append(
instr_type.lower() not in ignored_types): # Verifica contra lista actualizada f" - Red '{network_title}' (ID: {network_id}, Lang: {network_lang}), "
unprocessed_count += 1 f"Instrucción UID: {instr_uid}, Tipo: '{instr_type}'"
unprocessed_details.append( )
f" - Red '{network_title}' (ID: {network_id}, Lang: {network_lang}), "
f"Instrucción UID: {instr_uid}, Tipo: '{instr_type}'"
)
if unprocessed_count > 0: if unprocessed_count > 0:
print(f"ADVERTENCIA: Se encontraron {unprocessed_count} instrucciones (no STL) que parecen no haber sido procesadas:") print(
for detail in unprocessed_details: print(detail) f"ADVERTENCIA: Se encontraron {unprocessed_count} instrucciones (no STL) que parecen no haber sido procesadas:"
)
for detail in unprocessed_details:
print(detail)
else: else:
print("INFO: Todas las instrucciones relevantes (no STL) parecen haber sido procesadas o agrupadas.") print(
"INFO: Todas las instrucciones relevantes (no STL) parecen haber sido procesadas o agrupadas."
)
# --- Guardar JSON Final --- # --- Guardar JSON Final ---
output_filename = json_filepath.replace("_simplified.json", "_simplified_processed.json") output_filename = json_filepath.replace(
print(f"\nGuardando JSON procesado en: {output_filename}") "_simplified.json", "_simplified_processed.json"
)
print(f"\nGuardando JSON procesado (FC/FB) en: {output_filename}")
try: try:
with open(output_filename, "w", encoding="utf-8") as f: with open(output_filename, "w", encoding="utf-8") as f:
json.dump(data, f, indent=4, ensure_ascii=False) json.dump(data, f, indent=4, ensure_ascii=False)
@ -424,6 +556,7 @@ def process_json_to_scl(json_filepath):
print(f"Error Crítico al guardar JSON procesado: {e}") print(f"Error Crítico al guardar JSON procesado: {e}")
traceback.print_exc() traceback.print_exc()
# --- Ejecución (sin cambios) --- # --- Ejecución (sin cambios) ---
if __name__ == "__main__": if __name__ == "__main__":
# Imports necesarios solo para la ejecución como script principal # Imports necesarios solo para la ejecución como script principal
@ -436,43 +569,55 @@ if __name__ == "__main__":
description="Process simplified JSON (_simplified.json) to embed SCL logic (SymPy version). Expects original XML filepath as argument." description="Process simplified JSON (_simplified.json) to embed SCL logic (SymPy version). Expects original XML filepath as argument."
) )
parser.add_argument( parser.add_argument(
"source_xml_filepath", # Argumento posicional obligatorio "source_xml_filepath", # Argumento posicional obligatorio
help="Path to the original source XML file (passed from x0_main.py, used to derive JSON input name).", help="Path to the original source XML file (passed from x0_main.py, used to derive JSON input name).",
) )
args = parser.parse_args() # Parsea los argumentos de sys.argv args = parser.parse_args() # Parsea los argumentos de sys.argv
source_xml_file = args.source_xml_filepath # Obtiene la ruta del XML original source_xml_file = args.source_xml_filepath # Obtiene la ruta del XML original
# Verificar si el archivo XML original existe (como referencia, útil para depuración) # Verificar si el archivo XML original existe (como referencia, útil para depuración)
# No es estrictamente necesario para la lógica aquí, pero ayuda a confirmar # No es estrictamente necesario para la lógica aquí, pero ayuda a confirmar
if not os.path.exists(source_xml_file): if not os.path.exists(source_xml_file):
print(f"Advertencia (x2): Archivo XML original no encontrado: '{source_xml_file}', pero se intentará encontrar el JSON correspondiente.") print(
# No salir necesariamente, pero es bueno saberlo. f"Advertencia (x2): Archivo XML original no encontrado: '{source_xml_file}', pero se intentará encontrar el JSON correspondiente."
)
# No salir necesariamente, pero es bueno saberlo.
# Derivar nombre del archivo JSON de entrada (_simplified.json) # Derivar nombre del archivo JSON de entrada (_simplified.json)
xml_filename_base = os.path.splitext(os.path.basename(source_xml_file))[0] xml_filename_base = os.path.splitext(os.path.basename(source_xml_file))[0]
# Asumir que el JSON simplificado está en el mismo directorio que el XML original # Asumir que el JSON simplificado está en el mismo directorio que el XML original
input_dir = os.path.dirname(source_xml_file) # Directorio del XML original input_dir = os.path.dirname(source_xml_file) # Directorio del XML original
input_json_file = os.path.join(input_dir, f"{xml_filename_base}_simplified.json") input_json_file = os.path.join(input_dir, f"{xml_filename_base}_simplified.json")
# Determinar el nombre esperado del archivo JSON procesado de salida # Determinar el nombre esperado del archivo JSON procesado de salida
output_json_file = os.path.join(input_dir, f"{xml_filename_base}_simplified_processed.json") output_json_file = os.path.join(
input_dir, f"{xml_filename_base}_simplified_processed.json"
print(f"(x2) Procesando: '{os.path.relpath(input_json_file)}' -> '{os.path.relpath(output_json_file)}'") )
print(
f"(x2) Procesando: '{os.path.relpath(input_json_file)}' -> '{os.path.relpath(output_json_file)}'"
)
# Verificar si el archivo JSON de entrada (_simplified.json) EXISTE antes de procesar # Verificar si el archivo JSON de entrada (_simplified.json) EXISTE antes de procesar
if not os.path.exists(input_json_file): if not os.path.exists(input_json_file):
print(f"Error Fatal (x2): El archivo de entrada JSON simplificado no existe: '{input_json_file}'") print(
print(f"Asegúrate de que 'x1_to_json.py' se ejecutó correctamente para '{os.path.relpath(source_xml_file)}'.") f"Error Fatal (x2): El archivo de entrada JSON simplificado no existe: '{input_json_file}'"
sys.exit(1) # Salir si el archivo necesario no está )
print(
f"Asegúrate de que 'x1_to_json.py' se ejecutó correctamente para '{os.path.relpath(source_xml_file)}'."
)
sys.exit(1) # Salir si el archivo necesario no está
else: else:
# Llamar a la función principal de procesamiento del script # Llamar a la función principal de procesamiento del script
# Asumiendo que tu función principal se llama process_json_to_scl(input_json_path) # Asumiendo que tu función principal se llama process_json_to_scl(input_json_path)
try: try:
process_json_to_scl(input_json_file) process_json_to_scl(input_json_file)
except Exception as e: except Exception as e:
print(f"Error Crítico (x2) durante el procesamiento de '{input_json_file}': {e}") print(
f"Error Crítico (x2) durante el procesamiento de '{input_json_file}': {e}"
)
import traceback import traceback
traceback.print_exc() traceback.print_exc()
sys.exit(1) # Salir con error si la función principal falla sys.exit(1) # Salir con error si la función principal falla

View File

@ -5,290 +5,481 @@ import os
import re import re
import argparse import argparse
import sys import sys
import traceback # Importar traceback para errores import traceback # Importar traceback para errores
# --- Importar Utilidades y Constantes (Asumiendo ubicación) --- # --- Importar Utilidades y Constantes (Asumiendo ubicación) ---
try: try:
# Intenta importar desde el paquete de procesadores si está estructurado así # Intenta importar desde el paquete de procesadores si está estructurado así
from processors.processor_utils import format_variable_name from processors.processor_utils import format_variable_name
# Definir SCL_SUFFIX aquí o importarlo si está centralizado # Definir SCL_SUFFIX aquí o importarlo si está centralizado
SCL_SUFFIX = "_sympy_processed" # Asegúrate que coincida con x2_process.py SCL_SUFFIX = "_sympy_processed" # Asegúrate que coincida con x2_process.py
GROUPED_COMMENT = "// Logic included in grouped IF" # Opcional, si se usa para filtrar GROUPED_COMMENT = (
"// Logic included in grouped IF" # Opcional, si se usa para filtrar
)
except ImportError: except ImportError:
print("Advertencia: No se pudo importar 'format_variable_name' desde processors.processor_utils.") print(
print("Usando una implementación local básica (¡PUEDE FALLAR CON NOMBRES COMPLEJOS!).") "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!)."
)
# Implementación local BÁSICA como fallback (MENOS RECOMENDADA) # Implementación local BÁSICA como fallback (MENOS RECOMENDADA)
def format_variable_name(name): def format_variable_name(name):
if not name: return "_INVALID_NAME_" if not name:
if name.startswith('"') and name.endswith('"'): return name # Mantener comillas return "_INVALID_NAME_"
if name.startswith('"') and name.endswith('"'):
return name # Mantener comillas
prefix = "#" if name.startswith("#") else "" prefix = "#" if name.startswith("#") else ""
if prefix: name = name[1:] if prefix:
if name and name[0].isdigit(): name = "_" + name name = name[1:]
if name and name[0].isdigit():
name = "_" + name
name = re.sub(r"[^a-zA-Z0-9_]", "_", name) name = re.sub(r"[^a-zA-Z0-9_]", "_", name)
return prefix + name return prefix + name
SCL_SUFFIX = "_sympy_processed" SCL_SUFFIX = "_sympy_processed"
GROUPED_COMMENT = "// Logic included in grouped IF" GROUPED_COMMENT = "// Logic included in grouped IF"
# --- Función Principal de Generación SCL --- # para formatear valores iniciales
def format_scl_start_value(value, datatype):
"""Formatea un valor para la inicialización SCL según el tipo."""
if value is None:
return None
datatype_lower = datatype.lower() if datatype else ""
value_str = str(value)
if "bool" in datatype_lower:
return "TRUE" if value_str.lower() == "true" else "FALSE"
elif "string" in datatype_lower:
escaped_value = value_str.replace("'", "''")
if escaped_value.startswith("'") and escaped_value.endswith("'"):
escaped_value = escaped_value[1:-1]
return f"'{escaped_value}'"
elif "char" in datatype_lower: # Añadido Char
escaped_value = value_str.replace("'", "''")
if escaped_value.startswith("'") and escaped_value.endswith("'"):
escaped_value = escaped_value[1:-1]
return f"'{escaped_value}'"
elif any(
t in datatype_lower
for t in [
"int",
"byte",
"word",
"dint",
"dword",
"lint",
"lword",
"sint",
"usint",
"uint",
"udint",
"ulint",
]
): # Ampliado
try:
return str(int(value_str))
except ValueError:
if re.match(r"^[a-zA-Z_][a-zA-Z0-9_]*$", value_str):
return value_str
return f"'{value_str}'" # O como string si no es entero ni símbolo
elif "real" in datatype_lower or "lreal" in datatype_lower:
try:
f_val = float(value_str)
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):
return value_str
return f"'{value_str}'"
elif "time" in datatype_lower: # Añadido Time, S5Time, LTime
# Quitar T#, LT#, S5T# si existen
prefix = ""
if value_str.upper().startswith("T#"):
prefix = "T#"
value_str = value_str[2:]
elif value_str.upper().startswith("LT#"):
prefix = "LT#"
value_str = value_str[3:]
elif value_str.upper().startswith("S5T#"):
prefix = "S5T#"
value_str = value_str[4:]
# Devolver con el prefijo correcto o T# por defecto si no había
if prefix:
return f"{prefix}{value_str}"
elif "s5time" in datatype_lower:
return f"S5T#{value_str}"
elif "ltime" in datatype_lower:
return f"LT#{value_str}"
else:
return f"T#{value_str}" # Default a TIME
elif "date" in datatype_lower: # Añadido Date, DT, TOD
if value_str.upper().startswith("D#"):
return value_str
elif "dt" in datatype_lower or "date_and_time" in datatype_lower:
if value_str.upper().startswith("DT#"):
return value_str
else:
return f"DT#{value_str}" # Añadir prefijo DT#
elif "tod" in datatype_lower or "time_of_day" in datatype_lower:
if value_str.upper().startswith("TOD#"):
return value_str
else:
return f"TOD#{value_str}" # Añadir prefijo TOD#
else:
return f"D#{value_str}" # Default a Date
# Fallback genérico
else:
if re.match(
r'^[a-zA-Z_][a-zA-Z0-9_."#\[\]]+$', value_str
): # Permitir más caracteres en símbolos/tipos
# Si es un UDT o Struct complejo, podría venir con comillas, quitarlas
if value_str.startswith('"') and value_str.endswith('"'):
return value_str[1:-1]
return value_str
else:
escaped_value = value_str.replace("'", "''")
return f"'{escaped_value}'"
# --- NUEVA FUNCIÓN RECURSIVA para generar declaraciones SCL (VAR/STRUCT/ARRAY) ---
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")
# Limpiar comillas de tipos de datos UDT ("MyType" -> MyType)
var_dtype = (
var_dtype_raw.strip('"')
if var_dtype_raw.startswith('"') and var_dtype_raw.endswith('"')
else var_dtype_raw
)
var_comment = var.get("comment")
start_value = var.get("start_value")
children = var.get("children") # Para structs
array_elements = var.get("array_elements") # Para arrays
# Manejar tipos de datos Array especiales
array_match = re.match(r"(Array\[.*\]\s+of\s+)(.*)", var_dtype, re.IGNORECASE)
base_type_for_init = var_dtype
declaration_dtype = var_dtype
if array_match:
array_prefix = array_match.group(1)
base_type_raw = array_match.group(2).strip()
# Limpiar comillas del tipo base del array
base_type_for_init = (
base_type_raw.strip('"')
if base_type_raw.startswith('"') and base_type_raw.endswith('"')
else base_type_raw
)
declaration_dtype = (
f'{array_prefix}"{base_type_for_init}"'
if '"' not in base_type_raw
else f"{array_prefix}{base_type_raw}"
) # Reconstruir con comillas si es UDT
# Reconstruir declaración con comillas si es UDT y no array
elif (
not array_match and var_dtype != base_type_for_init
): # Es un tipo que necesita comillas (UDT)
declaration_dtype = f'"{var_dtype}"'
declaration_line = f"{indent}{var_name_scl} : {declaration_dtype}"
init_value = None
# ---- Arrays ----
if array_elements:
# Ordenar índices (asumiendo que son numéricos)
try:
sorted_indices = sorted(array_elements.keys(), key=int)
except ValueError:
sorted_indices = sorted(
array_elements.keys()
) # Fallback a orden alfabético
init_values = [
format_scl_start_value(array_elements[idx], base_type_for_init)
for idx in sorted_indices
]
valid_inits = [v for v in init_values if v is not None]
if valid_inits:
init_value = f"[{', '.join(valid_inits)}]"
# ---- Structs ----
elif children:
# No añadir comentario // Struct aquí, es redundante
scl_lines.append(declaration_line) # Añadir línea de declaración base
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("") # Línea extra
continue # Saltar resto para Struct
# ---- Tipos Simples ----
else:
if start_value is not None:
init_value = format_scl_start_value(start_value, var_dtype)
# Añadir inicialización si existe
if init_value:
declaration_line += f" := {init_value}"
declaration_line += ";"
if var_comment:
declaration_line += f" // {var_comment}"
scl_lines.append(declaration_line)
return scl_lines
# --- Función Principal de Generación SCL ---
def generate_scl(processed_json_filepath, output_scl_filepath): def generate_scl(processed_json_filepath, output_scl_filepath):
"""Genera un archivo SCL a partir del JSON procesado por x2_process (versión SymPy).""" """Genera un archivo SCL a partir del JSON procesado (FC/FB o DB)."""
if not os.path.exists(processed_json_filepath): if not os.path.exists(processed_json_filepath):
print(f"Error: Archivo JSON procesado no encontrado en '{processed_json_filepath}'") print(
f"Error: Archivo JSON procesado no encontrado en '{processed_json_filepath}'"
)
return return
print(f"Cargando JSON procesado desde: {processed_json_filepath}") print(f"Cargando JSON procesado desde: {processed_json_filepath}")
try: try:
with open(processed_json_filepath, 'r', encoding='utf-8') as f: with open(processed_json_filepath, "r", encoding="utf-8") as f:
data = json.load(f) data = json.load(f)
except Exception as e: except Exception as e:
print(f"Error al cargar o parsear JSON: {e}") print(f"Error al cargar o parsear JSON: {e}")
traceback.print_exc() traceback.print_exc()
return return
# --- Extracción de Información del Bloque --- # --- Extracción de Información del Bloque (Común) ---
block_name = data.get('block_name', 'UnknownBlock') block_name = data.get("block_name", "UnknownBlock")
block_number = data.get('block_number') block_number = data.get("block_number")
block_lang_original = data.get('language', 'LAD') # Lenguaje original block_lang_original = data.get("language", "Unknown") # Será "DB" para Data Blocks
# Determinar tipo de bloque SCL (Asumir FB si no se especifica) block_type = data.get("block_type", "Unknown") # FC, FB, GlobalDB
# Idealmente, x1_to_json.py guardaría esto en data['block_type_scl'] = 'FC' o 'FB' block_comment = data.get("block_comment", "")
block_type_scl = data.get('block_type_scl', 'FUNCTION_BLOCK') scl_block_name = format_variable_name(block_name) # Nombre SCL seguro
block_comment = data.get('block_comment', '') print(
f"Generando SCL para: {block_type} '{scl_block_name}' (Original: {block_name}, Lang: {block_lang_original})"
# Usar format_variable_name para el nombre del bloque en SCL )
scl_block_name = format_variable_name(block_name)
print(f"Generando SCL para {block_type_scl}: {scl_block_name} (Original: {block_name})")
# --- Identificación de Variables Temporales y Estáticas ---
# La detección basada en regex sobre el SCL final debería seguir funcionando
temp_vars = set()
stat_vars = set()
# Regex mejorado para capturar variables temporales que empiezan con # o _temp_
# y estáticas (si usas un prefijo como 'stat_' o para bits de memoria de flanco)
temp_pattern = re.compile(r'"?#(_temp_[a-zA-Z0-9_]+)"?|"?(_temp_[a-zA-Z0-9_]+)"?') # Captura con o sin #
stat_pattern = re.compile(r'"?(stat_[a-zA-Z0-9_]+)"?') # Para memorias de flanco si usan prefijo 'stat_'
edge_memory_bits = set() # Para detectar bits de memoria de flanco por nombre
for network in data.get('networks', []):
for instruction in network.get('logic', []):
scl_code = instruction.get('scl', '')
# Buscar también en _edge_mem_update_scl si existe
edge_update_code = instruction.get('_edge_mem_update_scl','')
code_to_scan = (scl_code if scl_code else '') + '\n' + (edge_update_code if edge_update_code else '')
if code_to_scan:
# Buscar #_temp_... o _temp_...
found_temps = temp_pattern.findall(code_to_scan)
for temp_tuple in found_temps:
# findall devuelve tuplas por los grupos de captura, tomar el no vacío
temp_name = next((t for t in temp_tuple if t), None)
if temp_name:
temp_vars.add("#"+temp_name if not temp_name.startswith("#") else temp_name) # Asegurar que empiece con #
# Buscar estáticas (ej: stat_...)
found_stats = stat_pattern.findall(code_to_scan)
stat_vars.update(found_stats)
# Identificar explícitamente bits de memoria usados por PBox/NBox
# Asumiendo que el nombre se guarda en el JSON (requiere ajuste en x1/x2)
# if instruction.get("type","").startswith(("PBox", "NBox")):
# mem_bit_info = instruction.get("inputs", {}).get("bit")
# if mem_bit_info and mem_bit_info.get("type") == "variable":
# edge_memory_bits.add(format_variable_name(mem_bit_info.get("name")))
print(f"Variables temporales (#_temp_...) detectadas: {len(temp_vars)}")
# Si se detectan memorias de flanco, añadirlas a stat_vars si no tienen prefijo 'stat_'
# stat_vars.update(edge_memory_bits - stat_vars) # Añadir solo las nuevas
print(f"Variables estáticas (stat_...) detectadas: {len(stat_vars)}")
# --- Construcción del String SCL ---
scl_output = [] scl_output = []
# Cabecera del Bloque # --- GENERACIÓN PARA DATA BLOCK (DB) ---
scl_output.append(f"// Block Name (Original): {block_name}") if block_lang_original == "DB":
if block_number: scl_output.append(f"// Block Number: {block_number}") print("Modo de generación: DATA_BLOCK")
scl_output.append(f"// Original Language: {block_lang_original}") scl_output.append(f"// Block Type: {block_type}")
if block_comment: scl_output.append(f"// Block Comment: {block_comment}") scl_output.append(f"// Block Name (Original): {block_name}")
scl_output.append("") if block_number:
scl_output.append(f"{block_type_scl} \"{scl_block_name}\"") scl_output.append(f"// Block Number: {block_number}")
scl_output.append("{ S7_Optimized_Access := 'TRUE' }") if block_comment:
scl_output.append("VERSION : 0.1") scl_output.append(f"// Block Comment: {block_comment}")
scl_output.append("") scl_output.append("")
scl_output.append(f'DATA_BLOCK "{scl_block_name}"')
scl_output.append("{ S7_Optimized_Access := 'TRUE' }") # Asumir optimizado
scl_output.append("VERSION : 0.1")
scl_output.append("")
interface_data = data.get("interface", {})
static_vars = interface_data.get("Static", [])
if static_vars:
scl_output.append("VAR")
scl_output.extend(generate_scl_declarations(static_vars, indent_level=1))
scl_output.append("END_VAR")
scl_output.append("")
else:
print(
"Advertencia: No se encontró sección 'Static' o está vacía en la interfaz del DB."
)
scl_output.append("VAR")
scl_output.append("END_VAR")
scl_output.append("")
scl_output.append("BEGIN")
scl_output.append("")
scl_output.append("END_DATA_BLOCK")
# Declaraciones de Interfaz (Implementación básica) # --- GENERACIÓN PARA FUNCTION BLOCK / FUNCTION (FC/FB) ---
interface_sections = ["Input", "Output", "InOut", "Static", "Temp", "Constant", "Return"] else:
interface_data = data.get('interface', {}) print("Modo de generación: FUNCTION_BLOCK / FUNCTION")
scl_block_keyword = "FUNCTION_BLOCK" if block_type == "FB" else "FUNCTION"
# Cabecera del Bloque
scl_output.append(f"// Block Type: {block_type}")
scl_output.append(f"// Block Name (Original): {block_name}")
if block_number:
scl_output.append(f"// Block Number: {block_number}")
scl_output.append(f"// Original Language: {block_lang_original}")
if block_comment:
scl_output.append(f"// Block Comment: {block_comment}")
scl_output.append("")
# Manejar tipo de retorno para FUNCTION
return_type = "Void" # Default
interface_data = data.get("interface", {})
if scl_block_keyword == "FUNCTION" and interface_data.get("Return"):
return_member = interface_data["Return"][
0
] # Asumir un solo valor de retorno
return_type_raw = return_member.get("datatype", "Void")
return_type = (
return_type_raw.strip('"')
if return_type_raw.startswith('"') and return_type_raw.endswith('"')
else return_type_raw
)
# Añadir comillas si es UDT
if return_type != return_type_raw:
return_type = f'"{return_type}"'
for section_name in interface_sections: scl_output.append(
scl_section_name = section_name f'{scl_block_keyword} "{scl_block_name}" : {return_type}'
# Ajustar nombres de sección para SCL (Static -> STAT, Temp -> TEMP) if scl_block_keyword == "FUNCTION"
if section_name == "Static": scl_section_name = "STAT" else f'{scl_block_keyword} "{scl_block_name}"'
if section_name == "Temp": scl_section_name = "TEMP" # Usar VAR_TEMP para variables #temp )
scl_output.append("{ S7_Optimized_Access := 'TRUE' }")
vars_in_section = interface_data.get(section_name, []) scl_output.append("VERSION : 0.1")
# No declarar VAR_TEMP aquí, se hará después con las detectadas/originales
if section_name == "Temp": continue
# No declarar VAR_STAT aquí si ya lo hacemos abajo con las detectadas
if section_name == "Static" and stat_vars: continue
if vars_in_section or (section_name == "Static" and stat_vars): # Incluir STAT si hay detectadas
# Usar VAR para Input/Output/InOut/Constant/Return
var_keyword = "VAR" if section_name != "Static" else "VAR_STAT"
scl_output.append(f"{var_keyword}_{section_name.upper()}")
for var in vars_in_section:
var_name = var.get('name')
var_dtype = var.get('datatype', 'VARIANT') # Default a VARIANT
if var_name:
# Usar format_variable_name CORRECTO
scl_name = format_variable_name(var_name)
scl_output.append(f" {scl_name} : {var_dtype};")
# Declarar stat_vars detectadas si esta es la sección STAT
if section_name == "Static" and stat_vars:
for var_name in sorted(list(stat_vars)):
# Asumir Bool para stat_, podría necesitar inferencia
scl_output.append(f" {format_variable_name(var_name)} : Bool; // Auto-detected STAT")
scl_output.append("END_VAR")
scl_output.append("")
# Declaraciones Estáticas (Si no estaban en la interfaz y se detectaron)
# Esto es redundante si la sección VAR_STAT ya se generó arriba
# if stat_vars and not interface_data.get("Static"):
# scl_output.append("VAR_STAT")
# for var_name in sorted(list(stat_vars)):
# scl_output.append(f" {format_variable_name(var_name)} : Bool; // Auto-detected STAT")
# scl_output.append("END_VAR")
# scl_output.append("")
# Declaraciones Temporales (Interfaz Temp + _temp_ detectadas)
scl_output.append("VAR_TEMP")
declared_temps = set()
interface_temps = interface_data.get('Temp', [])
if interface_temps:
for var in interface_temps:
var_name = var.get('name')
var_dtype = var.get('datatype', 'VARIANT')
if var_name:
scl_name = format_variable_name(var_name)
scl_output.append(f" {scl_name} : {var_dtype};")
declared_temps.add(scl_name) # Marcar como declarada
# Declarar las _temp_ generadas si no estaban ya en la interfaz Temp
if temp_vars:
for var_name in sorted(list(temp_vars)):
scl_name = format_variable_name(var_name) # #_temp_...
if scl_name not in declared_temps:
# Inferencia básica de tipo
inferred_type = "Bool" # Asumir Bool para la mayoría de temps de lógica
# Se podría mejorar si los procesadores añadieran info de tipo
scl_output.append(f" {scl_name} : {inferred_type}; // Auto-generated temporary")
declared_temps.add(scl_name)
scl_output.append("END_VAR")
scl_output.append("")
# Cuerpo del Bloque
scl_output.append("BEGIN")
scl_output.append("")
# Iterar por redes y lógica
for i, network in enumerate(data.get('networks', [])):
network_title = network.get('title', f'Network {network.get("id")}')
network_comment = network.get('comment', '')
network_lang = network.get('language', 'LAD') # O el lenguaje original
scl_output.append(f" // Network {i+1}: {network_title} (Original Language: {network_lang})")
if network_comment:
for line in network_comment.splitlines():
scl_output.append(f" // {line}")
scl_output.append("") scl_output.append("")
network_has_code = False # Declaraciones de Interfaz FC/FB
section_order = [
"Input",
"Output",
"InOut",
"Static",
"Temp",
"Constant",
] # Return ya está en cabecera
declared_temps = set()
for section_name in section_order:
vars_in_section = interface_data.get(section_name, [])
if vars_in_section:
scl_section_keyword = f"VAR_{section_name.upper()}"
if section_name == "Static":
scl_section_keyword = "VAR_STAT"
if section_name == "Temp":
scl_section_keyword = "VAR_TEMP"
if section_name == "Constant":
scl_section_keyword = "CONSTANT"
scl_output.append(scl_section_keyword)
scl_output.extend(
generate_scl_declarations(vars_in_section, indent_level=1)
)
if section_name == "Temp":
declared_temps.update(
format_variable_name(v.get("name"))
for v in vars_in_section
if v.get("name")
)
scl_output.append("END_VAR")
scl_output.append("")
# --- NUEVO MANEJO STL con formato Markdown --- # Declaraciones VAR_TEMP adicionales detectadas
if network_lang == "STL": temp_vars = set()
network_has_code = True # Marcar que la red tiene contenido temp_pattern = re.compile(
if network.get('logic') and isinstance(network['logic'], list) and len(network['logic']) > 0: r'"?#(_temp_[a-zA-Z0-9_]+)"?|"?(_temp_[a-zA-Z0-9_]+)"?'
stl_chunk = network['logic'][0] )
if stl_chunk.get("type") == "RAW_STL_CHUNK" and "stl" in stl_chunk: for network in data.get("networks", []):
raw_stl_code = stl_chunk["stl"] for instruction in network.get("logic", []):
# Añadir marcador de inicio (como comentario SCL para evitar errores) scl_code = instruction.get("scl", "")
scl_output.append(f" {'//'} ```STL") # Doble '//' para asegurar que sea comentario edge_update_code = instruction.get("_edge_mem_update_scl", "")
# Escribir el código STL crudo, indentado code_to_scan = (
(scl_code if scl_code else "")
+ "\n"
+ (edge_update_code if edge_update_code else "")
)
if code_to_scan:
found_temps = temp_pattern.findall(code_to_scan)
for temp_tuple in found_temps:
temp_name = next((t for t in temp_tuple if t), None)
if temp_name:
temp_vars.add(
"#" + temp_name
if not temp_name.startswith("#")
else temp_name
)
additional_temps = sorted(list(temp_vars - declared_temps))
if additional_temps:
if not interface_data.get("Temp"):
scl_output.append("VAR_TEMP")
for var_name in additional_temps:
scl_name = format_variable_name(var_name)
inferred_type = "Bool" # Asumir Bool
scl_output.append(
f" {scl_name} : {inferred_type}; // Auto-generated temporary"
)
if not interface_data.get("Temp"):
scl_output.append("END_VAR")
scl_output.append("")
# Cuerpo del Bloque FC/FB
scl_output.append("BEGIN")
scl_output.append("")
# Iterar por redes y lógica (como antes, incluyendo manejo STL Markdown)
for i, network in enumerate(data.get("networks", [])):
network_title = network.get("title", f'Network {network.get("id")}')
network_comment = network.get("comment", "")
network_lang = network.get("language", "LAD")
scl_output.append(
f" // Network {i+1}: {network_title} (Original Language: {network_lang})"
)
if network_comment:
for line in network_comment.splitlines():
scl_output.append(f" // {line}")
scl_output.append("")
network_has_code = False
if network_lang == "STL":
network_has_code = True
if (
network.get("logic")
and network["logic"][0].get("type") == "RAW_STL_CHUNK"
):
raw_stl_code = network["logic"][0].get(
"stl", "// ERROR: STL code missing"
)
scl_output.append(f" {'//'} ```STL")
for stl_line in raw_stl_code.splitlines(): for stl_line in raw_stl_code.splitlines():
# Añadir indentación estándar de SCL scl_output.append(f" {stl_line}")
scl_output.append(f" {stl_line}") # <-- STL sin comentar
# Añadir marcador de fin (como comentario SCL)
scl_output.append(f" {'//'} ```") scl_output.append(f" {'//'} ```")
else: else:
scl_output.append(" // ERROR: Contenido STL inesperado en JSON.") scl_output.append(" // ERROR: Contenido STL inesperado.")
else: else: # LAD, FBD, SCL, etc.
scl_output.append(" // ERROR: No se encontró lógica STL en JSON para esta red.") for instruction in network.get("logic", []):
scl_output.append("") # Línea en blanco después de la red STL instruction_type = instruction.get("type", "")
# --- FIN NUEVO MANEJO STL con formato Markdown --- scl_code = instruction.get("scl", "")
else: is_grouped = instruction.get("grouped", False)
if is_grouped:
# Iterar sobre la 'logica' de la red continue
for instruction in network.get('logic', []): if (
instruction_type = instruction.get("type", "") instruction_type.endswith(SCL_SUFFIX)
scl_code = instruction.get('scl', "") # Obtener SCL generado por x2 or instruction_type in ["RAW_SCL_CHUNK", "UNSUPPORTED_LANG"]
) and scl_code:
# Saltar instrucciones agrupadas is_only_comment = all(
if instruction.get("grouped", False): line.strip().startswith("//")
continue for line in scl_code.splitlines()
if line.strip()
# Escribir SCL si es un tipo procesado y tiene código relevante )
# (Ignorar comentarios de depuración de SymPy) is_if_block = scl_code.strip().startswith("IF")
if instruction_type.endswith(SCL_SUFFIX) and scl_code: if not is_only_comment or is_if_block:
is_internal_sympy_comment_only = scl_code.strip().startswith("// SymPy") or \ network_has_code = True
scl_code.strip().startswith("// PBox SymPy processed") or \ for line in scl_code.splitlines():
scl_code.strip().startswith("// NBox SymPy processed") scl_output.append(f" {line}")
# O podría ser más genérico: ignorar cualquier línea que solo sea comentario SCL
is_only_comment = all(line.strip().startswith("//") for line in scl_code.splitlines())
# Escribir solo si NO es un comentario interno de SymPy O si es un bloque IF (que sí debe escribirse)
if not is_only_comment or scl_code.strip().startswith("IF"):
network_has_code = True
for line in scl_code.splitlines():
# Añadir indentación estándar
scl_output.append(f" {line}")
# Incluir también tipos especiales directamente
elif instruction_type in ["RAW_SCL_CHUNK", "UNSUPPORTED_LANG"] and scl_code:
network_has_code = True
for line in scl_code.splitlines():
scl_output.append(f" {line}") # Indentar
# Podríamos añadir comentarios para errores si se desea
# elif "_error" in instruction_type:
# network_has_code = True
# scl_output.append(f" // ERROR processing instruction UID {instruction.get('instruction_uid')}: {instruction.get('scl', 'No details')}")
if network_has_code: if network_has_code:
scl_output.append("") # Línea en blanco después del código de la red scl_output.append("")
else: else:
scl_output.append(f" // Network did not produce printable SCL code.") scl_output.append(f" // Network did not produce printable SCL code.")
scl_output.append("") scl_output.append("")
# Fin del bloque FC/FB
scl_output.append(f"END_{scl_block_keyword}")
# Fin del bloque # --- Escritura del Archivo SCL (Común) ---
scl_output.append("END_FUNCTION_BLOCK") # O END_FUNCTION si es FC
# --- Escritura del Archivo SCL ---
print(f"Escribiendo archivo SCL en: {output_scl_filepath}") print(f"Escribiendo archivo SCL en: {output_scl_filepath}")
try: try:
with open(output_scl_filepath, 'w', encoding='utf-8') as f: with open(output_scl_filepath, "w", encoding="utf-8") as f:
for line in scl_output: for line in scl_output:
f.write(line + '\n') f.write(line + "\n")
print("Generación de SCL completada.") print("Generación de SCL completada.")
except Exception as e: except Exception as e:
print(f"Error al escribir el archivo SCL: {e}") print(f"Error al escribir el archivo SCL: {e}")
@ -301,47 +492,61 @@ if __name__ == "__main__":
import argparse import argparse
import os import os
import sys import sys
import traceback # Asegurarse que traceback está importado si se usa en generate_scl import traceback # Asegurarse que traceback está importado si se usa en generate_scl
# Configurar ArgumentParser para recibir la ruta del XML original obligatoria # Configurar ArgumentParser para recibir la ruta del XML original obligatoria
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description="Generate final SCL file from processed JSON (_simplified_processed.json). Expects original XML filepath as argument." description="Generate final SCL file from processed JSON (_simplified_processed.json). Expects original XML filepath as argument."
) )
parser.add_argument( parser.add_argument(
"source_xml_filepath", # Argumento posicional obligatorio "source_xml_filepath", # Argumento posicional obligatorio
help="Path to the original source XML file (passed from x0_main.py, used to derive input/output names).", help="Path to the original source XML file (passed from x0_main.py, used to derive input/output names).",
) )
args = parser.parse_args() # Parsea los argumentos de sys.argv args = parser.parse_args() # Parsea los argumentos de sys.argv
source_xml_file = args.source_xml_filepath # Obtiene la ruta del XML original source_xml_file = args.source_xml_filepath # Obtiene la ruta del XML original
# Verificar si el archivo XML original existe (como referencia) # Verificar si el archivo XML original existe (como referencia)
if not os.path.exists(source_xml_file): if not os.path.exists(source_xml_file):
print(f"Advertencia (x3): Archivo XML original no encontrado: '{source_xml_file}', pero se intentará encontrar el JSON procesado.") print(
# No salir necesariamente. f"Advertencia (x3): Archivo XML original no encontrado: '{source_xml_file}', pero se intentará encontrar el JSON procesado."
)
# No salir necesariamente.
# Derivar nombres de archivos de entrada (JSON procesado) y salida (SCL) # Derivar nombres de archivos de entrada (JSON procesado) y salida (SCL)
xml_filename_base = os.path.splitext(os.path.basename(source_xml_file))[0] xml_filename_base = os.path.splitext(os.path.basename(source_xml_file))[0]
# Asumir que los archivos están en el mismo directorio que el XML original # Asumir que los archivos están en el mismo directorio que el XML original
base_dir = os.path.dirname(source_xml_file) # Directorio del XML original base_dir = os.path.dirname(source_xml_file) # Directorio del XML original
input_json_file = os.path.join(base_dir, f"{xml_filename_base}_simplified_processed.json") input_json_file = os.path.join(
output_scl_file = os.path.join(base_dir, f"{xml_filename_base}_simplified_processed.scl") base_dir, f"{xml_filename_base}_simplified_processed.json"
)
output_scl_file = os.path.join(
base_dir, f"{xml_filename_base}_simplified_processed.scl"
)
print(f"(x3) Generando SCL: '{os.path.relpath(input_json_file)}' -> '{os.path.relpath(output_scl_file)}'") print(
f"(x3) Generando SCL: '{os.path.relpath(input_json_file)}' -> '{os.path.relpath(output_scl_file)}'"
)
# Verificar si el archivo JSON procesado de entrada EXISTE # Verificar si el archivo JSON procesado de entrada EXISTE
if not os.path.exists(input_json_file): if not os.path.exists(input_json_file):
print(f"Error Fatal (x3): Archivo JSON procesado no encontrado: '{input_json_file}'") print(
print(f"Asegúrate de que 'x2_process.py' se ejecutó correctamente para '{os.path.relpath(source_xml_file)}'.") f"Error Fatal (x3): Archivo JSON procesado no encontrado: '{input_json_file}'"
sys.exit(1) # Salir si el archivo necesario no está )
print(
f"Asegúrate de que 'x2_process.py' se ejecutó correctamente para '{os.path.relpath(source_xml_file)}'."
)
sys.exit(1) # Salir si el archivo necesario no está
else: else:
# Llamar a la función principal de generación SCL del script # Llamar a la función principal de generación SCL del script
# Asumiendo que tu función principal se llama generate_scl(input_json_path, output_scl_path) # Asumiendo que tu función principal se llama generate_scl(input_json_path, output_scl_path)
try: try:
generate_scl(input_json_file, output_scl_file) generate_scl(input_json_file, output_scl_file)
except Exception as e: except Exception as e:
print(f"Error Crítico (x3) durante la generación de SCL desde '{input_json_file}': {e}") print(
f"Error Crítico (x3) durante la generación de SCL desde '{input_json_file}': {e}"
)
# traceback ya debería estar importado si generate_scl lo necesita # traceback ya debería estar importado si generate_scl lo necesita
traceback.print_exc() traceback.print_exc()
sys.exit(1) # Salir con error si la función principal falla sys.exit(1) # Salir con error si la función principal falla