# processors/process_call.py # -*- coding: utf-8 -*- import sympy import traceback # Asumiendo que estas funciones ahora existen y están adaptadas from .processor_utils import get_sympy_representation, sympy_expr_to_scl, format_variable_name, get_target_scl_name from .symbol_manager import SymbolManager # Necesitamos pasar el symbol_manager # Definir sufijo globalmente o importar SCL_SUFFIX = "_sympy_processed" def process_call(instruction, network_id, sympy_map, symbol_manager: SymbolManager, data): instr_uid = instruction["instruction_uid"] # Get original type before potential suffix/error was added by x1 or previous passes # This requires storing the original type perhaps, or removing known suffixes # Let's assume 'block_type' (FC/FB) and 'block_name' are correct from x1 block_name = instruction.get("block_name", f"UnknownCall_{instr_uid}") block_type = instruction.get("block_type") # FC, FB instance_db = instruction.get("instance_db") # Nombre del DB de instancia (para FB) # Check if already processed if instruction.get("type", "").endswith(SCL_SUFFIX) or "_error" in instruction.get("type", ""): return False # Formatear nombres SCL (para la llamada final) block_name_scl = format_variable_name(block_name) instance_db_scl = format_variable_name(instance_db) if instance_db else None # --- Manejo de EN --- en_input = instruction["inputs"].get("en") sympy_en_expr = get_sympy_representation(en_input, network_id, sympy_map, symbol_manager) if en_input else sympy.true if sympy_en_expr is None: # print(f"DEBUG Call {instr_uid}: EN dependency not ready.") return False # Dependencia EN no resuelta # --- Procesar Parámetros de Entrada --- scl_call_params = [] processed_inputs = {"en"} # Track processed pins to avoid duplicates if 'en' is also listed elsewhere dependencies_resolved = True # Iterar sobre las entradas que x1 debería haber poblado # Ordenar por nombre de pin para consistencia en la llamada SCL input_pin_names = sorted(instruction.get("inputs", {}).keys()) for pin_name in input_pin_names: if pin_name not in processed_inputs: # Skip 'en' if already handled source_info = instruction["inputs"][pin_name] # Get the representation of the source (SymPy, constant, or SCL string) source_sympy_or_const = get_sympy_representation(source_info, network_id, sympy_map, symbol_manager) if source_sympy_or_const is None: # print(f"DEBUG Call {instr_uid}: Input param '{pin_name}' dependency not ready.") dependencies_resolved = False break # Exit if one dependency is not ready # Convert the expression/constant to SCL for the call # Simplification of inputs is generally not needed here, convert directly param_scl_value = sympy_expr_to_scl(source_sympy_or_const, symbol_manager) # Parameter pin name needs formatting for SCL pin_name_scl = format_variable_name(pin_name) # Special check for DB_ANY or ANY_POINTER - pass name directly without := # We need the original parameter type info for this, which is not in the simplified JSON. # WORKAROUND: Check if param_scl_value looks like a DB name ("DB_NAME") # This is heuristic and might be wrong. Ideally, x1 should pass type info. # For now, we assume standard 'Param := Value' syntax. # if param_scl_value.startswith('"') and param_scl_value.endswith('"') and block_type == "FC": # Heuristic for DB_ANY? # scl_call_params.append(f"{pin_name_scl} := {param_scl_value}") # Still use := for clarity? TIA might infer # else: scl_call_params.append(f"{pin_name_scl} := {param_scl_value}") processed_inputs.add(pin_name) if not dependencies_resolved: return False # --- Construcción de la Llamada SCL (con parámetros) --- scl_call_body = "" param_string = ", ".join(scl_call_params) # Join parameters with commas if block_type == "FB": if not instance_db_scl: print(f"Error: Call FB '{block_name_scl}' (UID {instr_uid}) sin instancia.") instruction["scl"] = f"// ERROR: FB Call {block_name_scl} sin instancia" instruction["type"] = f"Call_FB_error" # Mark with error return True # Processed (with error) # FB Call: InstanceName(Param1 := Value1, Param2 := Value2); scl_call_body = f"{instance_db_scl}({param_string});" elif block_type == "FC": # FC Call: BlockName(Param1 := Value1, Param2 := Value2); scl_call_body = f"{block_name_scl}({param_string});" else: print(f"Advertencia: Tipo de bloque no soportado para Call UID {instr_uid}: {block_type}") scl_call_body = f"// ERROR: Call a bloque tipo '{block_type}' no soportado: {block_name_scl}" # Mark instruction type with error instruction["type"] = f"Call_{block_type or 'Unknown'}_error" # Add specific type if known # --- Aplicar Condición EN (usando la expresión SymPy EN) --- scl_final = "" if sympy_en_expr != sympy.true: # Simplify the EN condition before converting to SCL try: #simplified_en_expr = sympy.simplify_logic(sympy_en_expr, force=True) simplified_en_expr = sympy.logic.boolalg.to_dnf(sympy_en_expr, simplify=True) except Exception as e: print(f"Error simplifying EN for Call {instr_uid} ({block_name_scl}): {e}") simplified_en_expr = sympy_en_expr # Fallback en_condition_scl = sympy_expr_to_scl(simplified_en_expr, symbol_manager) # Avoid IF TRUE/FALSE blocks if en_condition_scl == "TRUE": scl_final = scl_call_body elif en_condition_scl == "FALSE": scl_final = f"// Call {block_name_scl} (UID {instr_uid}) condition simplified to FALSE." # Also update type to avoid further processing? # instruction["type"] = f"Call_{block_type}{SCL_SUFFIX}_Optimized" else: # Indent the call body within the IF block indented_call = "\n".join([f" {line}" for line in scl_call_body.splitlines()]) scl_final = f"IF {en_condition_scl} THEN\n{indented_call}\nEND_IF;" else: # No IF needed if EN is always TRUE scl_final = scl_call_body # --- Actualizar Instrucción y Mapa SymPy --- instruction["scl"] = scl_final # Guardar el SCL final generado # Update instruction type to mark as processed (unless already marked as error) if "_error" not in instruction.get("type", ""): instruction["type"] = f"Call_{block_type}{SCL_SUFFIX}" # Propagar el estado ENO (es la expresión SymPy de EN) map_key_eno = (network_id, instr_uid, "eno") sympy_map[map_key_eno] = sympy_en_expr # Guardar la expresión SymPy para ENO # --- Propagar Valores de Salida (Importante pero complejo) --- # Esto requiere conocer la interfaz del bloque llamado (que no tenemos aquí directamente) # O asumir convenciones estándar (ej. FCs tienen Ret_Val, FBs tienen outputs en su instancia) # Heurística simple: Si es un FC, intentar propagar Ret_Val si existe en outputs # Si es un FB, las salidas se acceden a través de la instancia (e.g., "MyInstance".Output1) # Por ahora, dejaremos la propagación de salidas más avanzada para una mejora futura # o requerirá pasar información de la interfaz del bloque llamado. # Ejemplo básico (necesita mejorar): # for pin_name, dest_list in instruction.get("outputs", {}).items(): # if pin_name != 'eno' and dest_list: # Asumir que hay un destino # map_key_out = (network_id, instr_uid, pin_name) # pin_name_scl = format_variable_name(pin_name) # if block_type == "FB" and instance_db_scl: # # Salida de FB: "Instancia".NombrePin # output_scl_access = f"{instance_db_scl}.{pin_name_scl}" # # Podríamos guardar el string SCL o crear/obtener un Symbol # sympy_out_symbol = symbol_manager.get_symbol(output_scl_access) # sympy_map[map_key_out] = sympy_out_symbol if sympy_out_symbol else output_scl_access # Prefiere Symbol # elif block_type == "FC": # # Salida de FC: Requiere asignar a una variable (temporal o de interfaz) # # Esto se complica porque el destino está en 'dest_list' # if len(dest_list) == 1 and dest_list[0].get("type") == "variable": # target_var_name = format_variable_name(dest_list[0].get("name")) # # Guardar el nombre del destino SCL que contendrá el valor # sympy_map[map_key_out] = target_var_name # # Necesitaríamos modificar scl_final para incluir la asignación: # # target_var_name := FC_Call(...); (requiere reestructurar la generación SCL) # else: # # Múltiples destinos o destino no variable es complejo para FC outputs # sympy_map[map_key_out] = f"/* TODO: Assign FC output {pin_name_scl} */" return True # --- Processor Information Function --- def get_processor_info(): """Devuelve la información para las llamadas a FC y FB.""" # Asegurarse que los type_name coincidan con los usados en x1 y x2 return [ {'type_name': 'call_fc', 'processor_func': process_call, 'priority': 6}, # Prioridad alta {'type_name': 'call_fb', 'processor_func': process_call, 'priority': 6} # Prioridad alta ]