# x3_generate_scl.py # -*- coding: utf-8 -*- import json import os import re import argparse import sys import traceback # Importar traceback para errores # --- Importar Utilidades y Constantes (Asumiendo ubicación) --- try: # Intenta importar desde el paquete de procesadores si está estructurado así from processors.processor_utils import format_variable_name # Definir SCL_SUFFIX aquí o importarlo si está centralizado 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 ) except ImportError: print( "Advertencia: No se pudo importar 'format_variable_name' desde processors.processor_utils." ) print( "Usando una implementación local básica (¡PUEDE FALLAR CON NOMBRES COMPLEJOS!)." ) # Implementación local BÁSICA como fallback (MENOS RECOMENDADA) def format_variable_name(name): if not name: return "_INVALID_NAME_" if name.startswith('"') and name.endswith('"'): return name # Mantener comillas 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" # para formatear valores iniciales def format_scl_start_value(value, datatype): """Formatea un valor para la inicialización SCL según el tipo.""" # Add initial debug print # print(f"DEBUG format_scl_start_value: value='{value}', datatype='{datatype}'") if value is None: return None # Retornar None si no hay valor datatype_lower = datatype.lower() if datatype else "" value_str = str(value) # Intentar quitar comillas si existen (para manejar "TRUE" vs TRUE) if value_str.startswith('"') and value_str.endswith('"') and len(value_str) > 1: value_str_unquoted = value_str[1:-1] elif value_str.startswith("'") and value_str.endswith("'") and len(value_str) > 1: value_str_unquoted = value_str[1:-1] else: value_str_unquoted = value_str # --- Integer-like types --- if any( t in datatype_lower for t in [ "int", "byte", "word", "dint", "dword", "lint", "lword", "sint", "usint", "uint", "udint", "ulint", ] ): try: # Intentar convertir el valor (sin comillas) a entero return str(int(value_str_unquoted)) except ValueError: # Si no es un entero válido, podría ser una constante simbólica if re.match(r"^[a-zA-Z_][a-zA-Z0-9_]*$", value_str_unquoted): return value_str_unquoted # Devolver como símbolo # --- Fallback for non-integer, non-symbol --- print( f"DEBUG format_scl_start_value: Fallback for int-like. value_str_unquoted='{repr(value_str_unquoted)}', datatype='{datatype}'" ) # More debug # MODIFIED FALLBACK: Escape newlines and use repr() for safety before formatting try: # Escape backslashes and single quotes properly for SCL string literal escaped_for_scl = value_str_unquoted.replace("\\", "\\\\").replace( "'", "''" ) # Remove potential newlines that break Python f-string; SCL strings usually don't span lines implicitly escaped_for_scl = escaped_for_scl.replace("\n", "").replace("\r", "") # Format as SCL string literal formatted_scl_string = f"'{escaped_for_scl}'" print( f"DEBUG format_scl_start_value: Fallback result='{formatted_scl_string}'" ) return formatted_scl_string except Exception as format_exc: print( f"ERROR format_scl_start_value: Exception during fallback formatting: {format_exc}" ) return f"'ERROR_FORMATTING_{value_str_unquoted[:20]}'" # Return an error string # --- Other types (Bool, Real, String, Char, Time, Date, etc.) --- elif "bool" in datatype_lower: # Comparar sin importar mayúsculas/minúsculas y sin comillas return "TRUE" if value_str_unquoted.lower() == "true" else "FALSE" elif "string" in datatype_lower: # Usar el valor sin comillas originales y escapar las internas escaped_value = value_str_unquoted.replace("'", "''") return f"'{escaped_value}'" elif "char" in datatype_lower: # Usar el valor sin comillas originales y escapar las internas escaped_value = value_str_unquoted.replace("'", "''") # SCL usa comillas simples para Char. Asegurar que sea un solo caracter si es posible? # Por ahora, solo formatear. Longitud se verifica en TIA. return f"'{escaped_value}'" elif "real" in datatype_lower or "lreal" in datatype_lower: try: # Intentar convertir a float f_val = float(value_str_unquoted) s_val = str(f_val) # Asegurar que tenga punto decimal si es entero if "." not in s_val and "e" not in s_val.lower(): s_val += ".0" return s_val except ValueError: # Podría ser constante simbólica if re.match(r"^[a-zA-Z_][a-zA-Z0-9_]*$", value_str_unquoted): return value_str_unquoted print( f"Advertencia: Valor '{value_str}' no reconocido como real o símbolo para tipo {datatype}. Devolviendo como string." ) # Use the robust fallback formatting here too escaped_for_scl = ( value_str_unquoted.replace("\\", "\\\\") .replace("'", "''") .replace("\n", "") .replace("\r", "") ) return f"'{escaped_for_scl}'" elif "time" in datatype_lower: # Quitar prefijos y añadir el correcto según el tipo específico prefix = "" val_to_use = value_str_unquoted # Usar valor sin comillas if val_to_use.upper().startswith("T#"): prefix = "T#" val_to_use = val_to_use[2:] elif val_to_use.upper().startswith("LT#"): prefix = "LT#" val_to_use = val_to_use[3:] elif val_to_use.upper().startswith("S5T#"): prefix = "S5T#" val_to_use = val_to_use[4:] if "s5time" in datatype_lower: return f"S5T#{val_to_use}" elif "ltime" in datatype_lower: return f"LT#{val_to_use}" else: return f"T#{val_to_use}" # Default a TIME elif "date" in datatype_lower: val_to_use = value_str_unquoted # Handle DTL first as it's longer if "dtl" in datatype_lower or "date_and_time" in datatype_lower: prefix = "DTL#" if val_to_use.upper().startswith("DTL#") else "DTL#" val_to_use = ( val_to_use[4:] if val_to_use.upper().startswith("DTL#") else val_to_use ) return f"{prefix}{val_to_use}" elif "dt" in datatype_lower: prefix = "DT#" if val_to_use.upper().startswith("DT#") else "DT#" val_to_use = ( val_to_use[3:] if val_to_use.upper().startswith("DT#") else val_to_use ) return f"{prefix}{val_to_use}" elif "tod" in datatype_lower or "time_of_day" in datatype_lower: prefix = "TOD#" if val_to_use.upper().startswith("TOD#") else "TOD#" val_to_use = ( val_to_use[4:] if val_to_use.upper().startswith("TOD#") else val_to_use ) return f"{prefix}{val_to_use}" else: # Default a Date D# prefix = "D#" if val_to_use.upper().startswith("D#") else "D#" val_to_use = ( val_to_use[2:] if val_to_use.upper().startswith("D#") else val_to_use ) return f"{prefix}{val_to_use}" # --- Fallback for completely unknown types or complex structures --- else: # Si es un nombre válido (posiblemente UDT, constante global, etc.), devolverlo tal cual # Ajustar regex para permitir más caracteres si es necesario if re.match( r'^[a-zA-Z_#"][a-zA-Z0-9_."#\[\]%]+$', value_str ): # Permitir % para accesos tipo %DB1.DBD0 # Quitar comillas externas si es un UDT o struct complejo if ( value_str.startswith('"') and value_str.endswith('"') and len(value_str) > 1 ): return value_str[1:-1] # Mantener comillas si es acceso a DB ("DB_Name".Var) if '"' in value_str and "." in value_str and value_str.count('"') == 2: return value_str # Si no tiene comillas y es un nombre simple o acceso #temp o %I0.0 etc if not value_str.startswith('"') and not value_str.startswith("'"): # Formatear nombres simples, pero dejar accesos % y # tal cual if value_str.startswith("#") or value_str.startswith("%"): return value_str else: # return format_variable_name(value_str) # Evitar formatear aquí, puede ser una constante return value_str # Return as is if it looks symbolic # Devolver el valor original si tiene comillas internas o estructura compleja no manejada arriba return value_str else: # Si no parece un nombre/símbolo/acceso, tratarlo como string (último recurso) print( f"DEBUG format_scl_start_value: Fallback final. value_str_unquoted='{repr(value_str_unquoted)}', datatype='{datatype}'" ) # Use the robust fallback formatting escaped_for_scl = ( value_str_unquoted.replace("\\", "\\\\") .replace("'", "''") .replace("\n", "") .replace("\r", "") ) return f"'{escaped_for_scl}'" # ... (generate_scl_declarations and generate_scl function remain the same as the previous version) ... # --- (Incluye aquí las funciones generate_scl_declarations y generate_scl SIN CAMBIOS respecto a la respuesta anterior) --- # --- 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") 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 # Limpiar comillas del tipo de dato si es UDT/String/etc. var_dtype_cleaned = var_dtype_raw if isinstance(var_dtype_raw, str): if var_dtype_raw.startswith('"') and var_dtype_raw.endswith('"'): var_dtype_cleaned = var_dtype_raw[1:-1] # Manejar caso 'Array [...] of "MyUDT"' array_match = re.match( r'(Array\[.*\]\s+of\s+)"(.*)"', var_dtype_raw, re.IGNORECASE ) if array_match: var_dtype_cleaned = f"{array_match.group(1)}{array_match.group(2)}" # Quitar comillas del tipo base # Determinar tipo base para inicialización (importante para arrays) base_type_for_init = var_dtype_cleaned array_prefix_for_decl = "" if var_dtype_cleaned.lower().startswith("array["): match = re.match( r"(Array\[.*\]\s+of\s+)(.*)", var_dtype_cleaned, re.IGNORECASE ) if match: array_prefix_for_decl = match.group(1) base_type_for_init = match.group(2).strip() # Construir tipo de dato para la declaración SCL declaration_dtype = var_dtype_raw # Usar el raw por defecto # Si es UDT o tipo complejo que requiere comillas y no es array simple if base_type_for_init != var_dtype_cleaned and not array_prefix_for_decl: # Poner comillas si no las tiene ya el tipo base if not base_type_for_init.startswith('"'): declaration_dtype = f'"{base_type_for_init}"' else: declaration_dtype = base_type_for_init # Ya tiene comillas # Si es array de UDT/complejo, reconstruir con comillas en el tipo base elif array_prefix_for_decl and base_type_for_init != var_dtype_cleaned: if not base_type_for_init.startswith('"'): declaration_dtype = f'{array_prefix_for_decl}"{base_type_for_init}"' else: declaration_dtype = f"{array_prefix_for_decl}{base_type_for_init}" declaration_line = f"{indent}{var_name_scl} : {declaration_dtype}" init_value_scl = None # ---- Arrays ---- if array_elements: # Ordenar índices (asumiendo que son numéricos '0', '1', ...) try: # Extraer números de los índices string indices_numeric = {int(k): v for k, v in array_elements.items()} sorted_indices = sorted(indices_numeric.keys()) # Mapear de nuevo a string para buscar valor sorted_indices_str = [str(k) for k in sorted_indices] except ValueError: # Fallback a orden alfabético si los índices no son números print( f"Advertencia: Índices de array no numéricos para '{var_name_scl}'. Usando orden alfabético." ) sorted_indices_str = sorted(array_elements.keys()) init_values = [] for idx_str in sorted_indices_str: try: formatted_val = format_scl_start_value( array_elements[idx_str], base_type_for_init ) init_values.append(formatted_val) except Exception as e_fmt: print( f"ERROR: Falló formateo para índice {idx_str} de array '{var_name_scl}'. Valor: {array_elements[idx_str]}. Error: {e_fmt}" ) init_values.append(f"/*ERR_FMT_{idx_str}*/") # Placeholder de error # Filtrar Nones que pueden venir de format_scl_start_value si el valor era None valid_inits = [v for v in init_values if v is not None] if valid_inits: # Si todos los valores son iguales y es un array grande, podríamos usar notación x(value) # Simplificación: por ahora, listar todos init_value_scl = f"[{', '.join(valid_inits)}]" elif array_elements: # Si había elementos pero todos formatearon a None print( f"Advertencia: Todos los valores iniciales para array '{var_name_scl}' son None o inválidos." ) # ---- Structs ---- elif children: # El valor inicial de un struct se maneja recursivamente dentro # Añadir comentario? Puede ser redundante. scl_lines.append( declaration_line ) # Añadir línea de declaración base STRUCT scl_lines.append(f"{indent}STRUCT") # Llamada recursiva para los miembros internos scl_lines.extend(generate_scl_declarations(children, indent_level + 1)) scl_lines.append(f"{indent}END_STRUCT;") if var_comment: # Comentario después de END_STRUCT scl_lines.append(f"{indent}// {var_comment}") scl_lines.append("") # Línea extra para legibilidad continue # Saltar el resto de la lógica para este struct # ---- Tipos Simples ---- else: if start_value is not None: try: init_value_scl = format_scl_start_value( start_value, base_type_for_init ) # Usar tipo base except Exception as e_fmt_simple: print( f"ERROR: Falló formateo para valor simple de '{var_name_scl}'. Valor: {start_value}. Error: {e_fmt_simple}" ) init_value_scl = f"/*ERR_FMT_SIMPLE*/" # Placeholder # Añadir inicialización si existe y no es None if init_value_scl is not None: declaration_line += f" := {init_value_scl}" declaration_line += ";" # Añadir comentario si existe 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): """Genera un archivo SCL a partir del JSON procesado (FC/FB/OB o DB).""" # Actualizado 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") # Lenguaje original (SCL, LAD, DB...) block_type = data.get( "block_type", "Unknown" ) # Tipo de bloque (FC, FB, GlobalDB, OB) <-- Usar este 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})" # Quitado lenguaje original del log ) scl_output = [] # --- MODIFICADO: GENERACIÓN PARA DATA BLOCK (GlobalDB) --- if block_type == "GlobalDB": # <-- Comprobar tipo de bloque 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: # Dividir comentarios largos en múltiples líneas comment_lines = block_comment.splitlines() scl_output.append(f"// Block Comment:") for line in comment_lines: scl_output.append(f"// {line}") 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", {}) # En DBs, la sección relevante suele ser 'Static' static_vars = interface_data.get("Static", []) if static_vars: scl_output.append("VAR") # Usar la función recursiva para generar declaraciones 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." ) # Añadir bloque VAR vacío si no hay variables scl_output.append("VAR") scl_output.append("END_VAR") scl_output.append("") scl_output.append("BEGIN") scl_output.append( " // Los Data Blocks no tienen código ejecutable en BEGIN/END" ) scl_output.append("END_DATA_BLOCK") # --- MODIFICADO: GENERACIÓN PARA FC/FB/OB --- else: # Determinar palabra clave SCL scl_block_keyword = "FUNCTION_BLOCK" # Default if block_type == "FC": scl_block_keyword = "FUNCTION" elif block_type == "OB": scl_block_keyword = "ORGANIZATION_BLOCK" elif block_type == "FB": scl_block_keyword = "FUNCTION_BLOCK" else: # Fallback print( f"Advertencia: Tipo de bloque desconocido '{block_type}', usando FUNCTION_BLOCK." ) scl_block_keyword = "FUNCTION_BLOCK" # O quizás lanzar error? print(f"Modo de generación: {scl_block_keyword}") # 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}") # Indicar lenguaje original de las redes si es relevante original_net_langs = set( n.get("language", "Unknown") for n in data.get("networks", []) ) scl_output.append( f"// Original Network Languages: {', '.join(l for l in original_net_langs if l != 'Unknown')}" ) if block_comment: comment_lines = block_comment.splitlines() scl_output.append(f"// Block Comment:") for line in comment_lines: scl_output.append(f"// {line}") scl_output.append("") # Manejar tipo de retorno para FUNCTION (FC) return_type = "Void" # Default interface_data = data.get("interface", {}) if scl_block_keyword == "FUNCTION" and interface_data.get("Return"): # Asumir un solo valor de retorno return_member = interface_data["Return"][0] return_type_raw = return_member.get("datatype", "Void") # Limpiar comillas si es UDT/String return_type = ( return_type_raw[1:-1] if isinstance(return_type_raw, str) and return_type_raw.startswith('"') and return_type_raw.endswith('"') else return_type_raw ) # Añadir comillas si es UDT y no las tenía if ( return_type != return_type_raw and not return_type_raw.lower().startswith("array") ): return_type = f'"{return_type}"' else: # Mantener raw si es tipo básico o ya tenía comillas return_type = return_type_raw # Línea de declaración del bloque if scl_block_keyword == "FUNCTION": scl_output.append(f'{scl_block_keyword} "{scl_block_name}" : {return_type}') else: # FB y OB scl_output.append(f'{scl_block_keyword} "{scl_block_name}"') # Atributos y versión scl_output.append("{ S7_Optimized_Access := 'TRUE' }") # Asumir optimizado scl_output.append("VERSION : 0.1") scl_output.append("") # Declaraciones de Interfaz (Input, Output, InOut, Static, Temp, Constant) # Orden estándar SCL section_order = ["Input", "Output", "InOut", "Static", "Temp", "Constant"] declared_temps = set() # Para rastrear temps ya declaradas has_declarations = False for section_name in section_order: vars_in_section = interface_data.get(section_name, []) if vars_in_section: has_declarations = True # Mapeo de nombres de sección JSON a palabras clave SCL VAR_ scl_section_keyword = f"VAR_{section_name.upper()}" if section_name == "Static": scl_section_keyword = "VAR_STAT" # Para FBs if section_name == "Temp": scl_section_keyword = "VAR_TEMP" if section_name == "Constant": scl_section_keyword = "CONSTANT" # CONSTANT no usa VAR_ scl_output.append(scl_section_keyword) # Usar la función recursiva para generar declaraciones scl_output.extend( generate_scl_declarations(vars_in_section, indent_level=1) ) # Añadir END_VAR (o END_CONSTANT) scl_output.append( "END_VAR" if section_name != "Constant" else "END_CONSTANT" ) scl_output.append("") # Línea en blanco # Guardar nombres de Temp declarados explícitamente if section_name == "Temp": declared_temps.update( format_variable_name(v.get("name")) for v in vars_in_section if v.get("name") ) # Declaraciones VAR_TEMP adicionales (auto-detectadas) # Buscar variables que empiecen con #_temp_ en el SCL generado temp_vars_detected = set() # Patrón para encontrar #variable o "#variable" temp_pattern = re.compile( r'"?(#\w+)"?' ) # Busca # seguido de caracteres alfanuméricos for network in data.get("networks", []): for instruction in network.get("logic", []): # Revisar el SCL final y el SCL de actualización de memoria si existe scl_code = instruction.get("scl", "") edge_update_code = instruction.get( "_edge_mem_update_scl", "" ) # Para flancos code_to_scan = ( (scl_code if scl_code else "") + "\n" + (edge_update_code if edge_update_code else "") ) if code_to_scan: # Usar findall para encontrar todas las ocurrencias found_temps = temp_pattern.findall(code_to_scan) for temp_name in found_temps: # findall devuelve el grupo capturado (#...) if temp_name: temp_vars_detected.add(temp_name) # Filtrar las que ya estaban declaradas additional_temps = sorted(list(temp_vars_detected - declared_temps)) if additional_temps: print(f"INFO: Detectadas {len(additional_temps)} VAR_TEMP adicionales.") # Si no se declaró la sección Temp antes, añadirla ahora if "Temp" not in interface_data or not interface_data["Temp"]: scl_output.append("VAR_TEMP") for temp_name in additional_temps: # Formatear por si acaso, aunque el patrón ya debería dar #nombre scl_name = format_variable_name(temp_name) # Inferir tipo (Bool es lo más común para temporales internos) # Se podría mejorar si el nombre da pistas (ej. _temp_r para Real) inferred_type = "Bool" # Asumir Bool por defecto scl_output.append( f" {scl_name} : {inferred_type}; // Auto-generated temporary" ) # Si abrimos la sección aquí, cerrarla if "Temp" not in interface_data or not interface_data["Temp"]: scl_output.append("END_VAR") scl_output.append("") # --- Cuerpo del Bloque (BEGIN...END) --- scl_output.append("BEGIN") scl_output.append("") # Iterar por redes y lógica (incluyendo manejo STL/SCL crudo) for i, network in enumerate(data.get("networks", [])): network_title = network.get( "title", f'Network {network.get("id", i+1)}' ) # Usar i+1 si falta ID network_comment = network.get("comment", "") network_lang = network.get("language", "LAD") # Lenguaje original de la red scl_output.append( f" // Network {i+1}: {network_title} (Original Language: {network_lang})" ) if network_comment: # Indentar comentarios de red for line in network_comment.splitlines(): scl_output.append(f" // {line}") scl_output.append("") # Línea en blanco antes del código de red network_has_code = False logic_in_network = network.get("logic", []) if not logic_in_network: scl_output.append(f" // Network {i+1} has no logic elements.") scl_output.append("") continue # --- Manejo Especial Redes STL --- if network_lang == "STL": # Asumir que la lógica STL está en el primer elemento como RAW_STL_CHUNK if logic_in_network[0].get("type") == "RAW_STL_CHUNK": network_has_code = True raw_stl_code = logic_in_network[0].get( "stl", "// ERROR: STL code missing" ) # Incrustar STL como comentario multi-línea o delimitado scl_output.append(f" // --- BEGIN STL Network {i+1} ---") # Comentar cada línea STL for stl_line in raw_stl_code.splitlines(): scl_output.append(f" // {stl_line}") scl_output.append(f" // --- END STL Network {i+1} ---") scl_output.append("") # Línea en blanco después else: scl_output.append( f" // ERROR: Contenido STL inesperado en Network {i+1}." ) scl_output.append("") # --- Manejo Redes SCL/LAD/FBD procesadas --- else: # Iterar por las instrucciones procesadas for instruction in logic_in_network: instruction_type = instruction.get("type", "") scl_code = instruction.get("scl", "") is_grouped = instruction.get("grouped", False) # Saltar instrucciones agrupadas (su lógica está en el IF) if is_grouped: continue # Incluir SCL si la instrucción fue procesada o es un chunk crudo/error/placeholder if ( instruction_type.endswith(SCL_SUFFIX) or instruction_type in [ "RAW_SCL_CHUNK", "UNSUPPORTED_LANG", "UNSUPPORTED_CONTENT", "PARSING_ERROR", ] or "_error" in instruction_type # Incluir errores comentados ) and scl_code: # Comprobar si el SCL es solo un comentario (a menos que sea un bloque IF) is_only_comment = all( line.strip().startswith("//") for line in scl_code.splitlines() if line.strip() ) is_if_block = scl_code.strip().startswith("IF") # Añadir el SCL indentado si no es solo un comentario (o si es un IF/Error) if ( not is_only_comment or is_if_block or "_error" in instruction_type or instruction_type in [ "UNSUPPORTED_LANG", "UNSUPPORTED_CONTENT", "PARSING_ERROR", ] ): network_has_code = True for line in scl_code.splitlines(): scl_output.append(f" {line}") # Indentar código # Añadir línea en blanco después de cada bloque SCL para legibilidad scl_output.append("") # Si la red no produjo código SCL imprimible (ej. solo lógica interna) if ( not network_has_code and network_lang != "STL" ): # No añadir para STL ya comentado scl_output.append( f" // Network {i+1} did not produce printable SCL code." ) scl_output.append("") # Fin del bloque FC/FB/OB scl_output.append(f"END_{scl_block_keyword}") # <-- Usar keyword determinada # --- 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 --- if __name__ == "__main__": # Imports necesarios solo para la ejecución como script principal import argparse import os import sys import traceback # Asegurarse que traceback está importado # Configurar ArgumentParser para recibir la ruta del XML original obligatoria parser = argparse.ArgumentParser( description="Generate final SCL file from processed JSON (_simplified_processed.json). Expects original XML filepath as argument." ) parser.add_argument( "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).", ) args = parser.parse_args() # Parsea los argumentos de sys.argv source_xml_file = args.source_xml_filepath # Obtiene la ruta del XML original # Verificar si el archivo XML original existe (como referencia) 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." ) # Derivar nombres de archivos de entrada (JSON procesado) y salida (SCL) 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 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" ) # Cambiar extensión de salida a .scl output_scl_file = os.path.join( base_dir, f"{xml_filename_base}_generated.scl" # Cambiado nombre de salida ) 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 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 'x2_process.py' se ejecutó correctamente para '{os.path.relpath(source_xml_file)}'." ) sys.exit(1) # Salir si el archivo necesario no está else: # Llamar a la función principal de generación SCL del script try: generate_scl(input_json_file, output_scl_file) sys.exit(0) # Salir con éxito explícitamente except Exception as e: print( f"Error Crítico (x3) durante la generación de SCL desde '{input_json_file}': {e}" ) # traceback ya debería estar importado traceback.print_exc() sys.exit(1) # Salir con error si la función principal falla