Simatic_XML_Parser_to_SCL/x2_process.py

738 lines
32 KiB
Python

# -*- coding: utf-8 -*-
import json
import os
import copy
import traceback
import re # Import regex for PBox SCL parsing if needed later
# --- Constantes y Configuración ---
SCL_SUFFIX = "_scl"
# Global data variable to be accessible by processors needing network context
data = {}
# --- Helper Functions ---
def get_scl_representation(source_info, network_id, scl_map, access_map):
"""
Busca la representación SCL de una entrada.
source_info: Puede ser {'type': 'powerrail'}, un Access dict, o un Connection dict, o una lista (OR).
"""
if not source_info:
return None # Entrada no conectada o dependencia no lista
# Si es una lista (rama OR), procesarla recursivamente
if isinstance(source_info, list):
scl_parts = []
all_resolved = True
for sub_source in source_info:
sub_scl = get_scl_representation(sub_source, network_id, scl_map, access_map)
if sub_scl is None:
all_resolved = False
# print(f"DEBUG: Dependencia no resuelta DENTRO de rama OR: {sub_source}")
break
# Evitar paréntesis innecesarios si ya es una expresión simple o contenida
if sub_scl in ["TRUE", "FALSE"] or (sub_scl.startswith('"') and sub_scl.endswith('"')) or sub_scl.isdigit() or (sub_scl.startswith('(') and sub_scl.endswith(')')):
scl_parts.append(sub_scl)
else:
scl_parts.append(f"({sub_scl})") # Añadir paréntesis por precaución de precedencia
if all_resolved:
or_expr = " OR ".join(scl_parts) if len(scl_parts) > 1 else (scl_parts[0] if scl_parts else "FALSE") # Default a FALSE si lista vacía?
# print(f"DEBUG: Rama OR resuelta a: {or_expr}")
return or_expr
else:
return None # Dependencia en la rama no resuelta
# Si no es lista, procesar como fuente única
source_type = source_info.get('type')
if source_type == 'powerrail':
return "TRUE"
elif source_type == 'variable':
name = source_info.get('name')
return name if name else f"_ERR_VAR_NO_NAME_{source_info.get('uid')}_"
elif source_type == 'constant':
dtype = str(source_info.get('datatype', '')).upper()
value = source_info.get('value')
try:
if dtype == 'BOOL': return str(value).upper()
elif dtype in ['INT', 'DINT', 'SINT', 'USINT', 'UINT', 'UDINT', 'LINT', 'ULINT', 'WORD', 'DWORD', 'LWORD', 'BYTE']: return str(value)
elif dtype in ['REAL', 'LREAL']:
s_val = str(value)
# Asegurar formato decimal para SCL, incluso para enteros como 1.0
if '.' not in s_val and 'e' not in s_val.lower():
s_val += ".0"
return s_val
elif dtype == 'STRING': return f"'{str(value)}'"
elif dtype == 'TYPEDCONSTANT': return str(value) # Ej: DINT#60
else: return f"'{str(value)}'" # Otros tipos como string
except Exception as e:
print(f"Advertencia: Error formateando constante {source_info}: {e}")
return f"_ERR_CONST_FORMAT_{source_info.get('uid')}_"
elif source_type == 'connection':
map_key = (network_id, source_info.get('source_instruction_uid'), source_info.get('source_pin'))
# print(f"DEBUG: Buscando en scl_map por {map_key}")
result = scl_map.get(map_key)
# if result is not None: print(f"DEBUG: Encontrado: {result}")
# else: print(f"DEBUG: No encontrado.")
return result # Devuelve valor o None si no existe
elif source_type == 'unknown_source':
print(f"Advertencia: Refiriendo a fuente desconocida UID: {source_info.get('uid')}")
return f"_ERR_UNKNOWN_SRC_{source_info.get('uid')}_"
else:
print(f"Advertencia: Tipo de fuente desconocido o inválido: {source_info}")
return f"_ERR_INVALID_SRC_TYPE_"
def generate_temp_var_name(network_id, instr_uid, pin_name):
"""Genera un nombre único para una variable temporal SCL."""
net_id_clean = str(network_id).replace('-', '_')
instr_uid_clean = str(instr_uid).replace('-', '_')
pin_name_clean = str(pin_name).replace('-', '_').lower()
prefix = "_" if str(net_id_clean)[0].isdigit() else ""
return f"{prefix}temp_{net_id_clean}_{instr_uid_clean}_{pin_name_clean}"
def get_target_scl_name(instruction, output_pin_name, network_id, default_to_temp=True):
"""Determina el nombre SCL del destino (variable o temporal)."""
instr_uid = instruction['instruction_uid']
output_pin_data = instruction['outputs'].get(output_pin_name)
target_scl = None
if output_pin_data and isinstance(output_pin_data, list) and len(output_pin_data) == 1:
dest_access = output_pin_data[0]
if dest_access.get('type') == 'variable':
target_scl = dest_access.get('name')
if not target_scl: # Si el nombre es None o vacío
print(f"Error: Variable de destino para {instr_uid}.{output_pin_name} no tiene nombre (UID: {dest_access.get('uid')}). {'Usando temporal.' if default_to_temp else 'Ignorando.'}")
target_scl = generate_temp_var_name(network_id, instr_uid, output_pin_name) if default_to_temp else None
elif dest_access.get('type') == 'constant':
print(f"Advertencia: Instrucción {instr_uid} intenta escribir en constante UID {dest_access.get('uid')}. {'Usando temporal.' if default_to_temp else 'Ignorando.'}")
target_scl = generate_temp_var_name(network_id, instr_uid, output_pin_name) if default_to_temp else None
else:
print(f"Advertencia: Destino de {instr_uid}.{output_pin_name} no es variable ni constante: {dest_access.get('type')}. {'Usando temporal.' if default_to_temp else 'Ignorando.'}")
target_scl = generate_temp_var_name(network_id, instr_uid, output_pin_name) if default_to_temp else None
elif default_to_temp:
# print(f"DEBUG: Usando temporal para {instr_uid}.{output_pin_name} (no hay destino único o no se requiere destino directo)")
target_scl = generate_temp_var_name(network_id, instr_uid, output_pin_name)
return target_scl
# --- Procesadores de Instrucciones ---
def process_contact(instruction, network_id, scl_map, access_map):
"""Traduce Contact a una expresión booleana SCL y actualiza scl_map."""
instr_uid = instruction['instruction_uid']
instr_type = instruction['type']
if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type: return False
is_negated = False # TODO: Determinar si es negado (ej. por 'Name')
# print(f"DEBUG: Intentando procesar CONTACT{' (N)' if is_negated else ''} - UID: {instr_uid} en Red: {network_id}")
# --- CORRECCIÓN: Manejar 'in' implícito ---
in_input = instruction['inputs'].get('in')
in_rlo_scl = None
if in_input is None:
# Si no hay pin 'in' conectado explícitamente, asumir TRUE
# print(f"DEBUG: Asumiendo IN=TRUE para CONTACT UID {instr_uid} (pin 'in' no conectado)")
in_rlo_scl = "TRUE"
else:
in_rlo_scl = get_scl_representation(in_input, network_id, scl_map, access_map)
# --- FIN CORRECCIÓN ---
operand_scl = get_scl_representation(instruction['inputs'].get('operand'), network_id, scl_map, access_map)
if in_rlo_scl is None or operand_scl is None:
# print(f"DEBUG: Dependencia no resuelta para CONTACT UID: {instr_uid} (in={in_rlo_scl}, op={operand_scl})")
return False
term = f"NOT {operand_scl}" if is_negated else operand_scl
# Envolver término en paréntesis si es necesario
if not (term.startswith('"') and term.endswith('"')):
if ' ' in term and not (term.startswith('(') and term.endswith(')')):
term = f"({term})"
new_rlo_scl = ""
if in_rlo_scl == "TRUE":
new_rlo_scl = term
else:
# Envolver RLO anterior en paréntesis si es necesario
if ('AND' in in_rlo_scl or 'OR' in in_rlo_scl) and not (in_rlo_scl.startswith('(') and in_rlo_scl.endswith(')')):
in_rlo_processed = f"({in_rlo_scl})"
else:
in_rlo_processed = in_rlo_scl
new_rlo_scl = f"{in_rlo_processed} AND {term}"
map_key = (network_id, instr_uid, 'out')
scl_map[map_key] = new_rlo_scl
instruction['scl'] = f"// RLO updated by Contact {instr_uid}: {new_rlo_scl}"
instruction['type'] = instr_type + SCL_SUFFIX
return True
def process_eq(instruction, network_id, scl_map, access_map):
"""Traduce Eq (comparación) a una expresión booleana SCL y actualiza scl_map."""
instr_uid = instruction['instruction_uid']
instr_type = instruction['type']
if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type: return False
# print(f"DEBUG: Intentando procesar EQ - UID: {instr_uid} en Red: {network_id}")
in1_scl = get_scl_representation(instruction['inputs'].get('in1'), network_id, scl_map, access_map)
in2_scl = get_scl_representation(instruction['inputs'].get('in2'), network_id, scl_map, access_map)
if in1_scl is None or in2_scl is None:
# print(f"DEBUG: Dependencia no resuelta para EQ UID: {instr_uid} (in1={in1_scl}, in2={in2_scl})")
return False
op1 = f"({in1_scl})" if ' ' in in1_scl else in1_scl
op2 = f"({in2_scl})" if ' ' in in2_scl else in2_scl
comparison_scl = f"{op1} = {op2}"
map_key_out = (network_id, instr_uid, 'out')
scl_map[map_key_out] = comparison_scl
# --- Manejo de ENO (CORREGIDO) ---
pre_input = instruction['inputs'].get('pre')
# Asumir TRUE si 'pre' no está conectado explícitamente
pre_scl = "TRUE" if pre_input is None else get_scl_representation(pre_input, network_id, scl_map, access_map)
if pre_scl is None: # Si está conectado pero no resuelto
# print(f"DEBUG: Dependencia PRE no resuelta para EQ UID {instr_uid}")
return False # No se puede determinar ENO si PRE no está listo
# El estado ENO de una comparación es TRUE si se ejecuta (EN/PRE=TRUE) y FALSE si no.
map_key_eno = (network_id, instr_uid, 'eno')
scl_map[map_key_eno] = pre_scl # ENO sigue a la habilitación PRE
# --- Fin Manejo de ENO ---
instruction['scl'] = f"// Comparison Eq {instr_uid}: {comparison_scl}"
instruction['type'] = instr_type + SCL_SUFFIX
return True
def process_coil(instruction, network_id, scl_map, access_map):
"""Traduce Coil a una asignación SCL."""
instr_uid = instruction['instruction_uid']
instr_type = instruction['type']
if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type: return False
# print(f"DEBUG: Intentando procesar COIL - UID: {instr_uid} en Red: {network_id}")
in_rlo_scl = get_scl_representation(instruction['inputs'].get('in'), network_id, scl_map, access_map)
operand_info = instruction['inputs'].get('operand') # Obtener info completa del operando
operand_scl = get_scl_representation(operand_info, network_id, scl_map, access_map)
if in_rlo_scl is None or operand_scl is None:
# print(f"DEBUG: Dependencia no resuelta para COIL UID: {instr_uid} (in={in_rlo_scl}, op={operand_scl})")
return False
if not (operand_info and operand_info.get('type') == 'variable'):
print(f"Error: Operando de COIL UID {instr_uid} no es una variable: {operand_info}")
instruction['scl'] = f"// ERROR: Coil {instr_uid} operando no es variable"
instruction['type'] = instr_type + "_error"
return True
# Simplificar RLO si es posible antes de asignar
if in_rlo_scl == "(TRUE)": in_rlo_scl = "TRUE"
elif in_rlo_scl == "(FALSE)": in_rlo_scl = "FALSE"
scl_final = f"{operand_scl} := {in_rlo_scl};"
instruction['scl'] = scl_final
instruction['type'] = instr_type + SCL_SUFFIX
# print(f"INFO: COIL UID: {instr_uid} procesado. SCL: {scl_final}")
return True
def process_convert(instruction, network_id, scl_map, access_map):
"""Traduce Convert a SCL, usando temporal si es necesario."""
instr_uid = instruction['instruction_uid']
instr_type = instruction['type']
if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type: return False
# print(f"DEBUG: Intentando procesar CONVERT - UID: {instr_uid} en Red: {network_id}")
# --- CORRECCIÓN: Manejo de EN ---
en_input = instruction['inputs'].get('en')
en_scl = None
if en_input is None:
# print(f"DEBUG: Asumiendo EN=TRUE para CONVERT UID {instr_uid} (pin 'en' no conectado)")
en_scl = "TRUE"
else:
en_scl = get_scl_representation(en_input, network_id, scl_map, access_map)
if en_scl is None: # Bloquear si EN está conectado pero no resuelto
# print(f"DEBUG: Dependencia EN no resuelta para CONVERT UID: {instr_uid}")
return False
# --- FIN CORRECCIÓN ---
in_scl = get_scl_representation(instruction['inputs'].get('in'), network_id, scl_map, access_map)
if in_scl is None:
# print(f"DEBUG: Dependencia IN no resuelta para CONVERT UID: {instr_uid}")
return False
target_scl = get_target_scl_name(instruction, 'out', network_id, default_to_temp=True)
if target_scl is None:
print(f"Error Interno: No se pudo determinar destino para CONVERT UID {instr_uid}")
instruction['scl'] = f"// ERROR: No se pudo determinar destino para Convert {instr_uid}"
instruction['type'] += "_error"
return True
conversion_expr = in_scl # Asume conversión implícita por ahora
scl_core = f"{target_scl} := {conversion_expr};"
# --- CORRECCIÓN: Generación de IF ---
if en_scl != "TRUE":
scl_final = f"IF {en_scl} THEN\n {scl_core}\nEND_IF;"
else:
scl_final = scl_core
# --- FIN CORRECCIÓN ---
instruction['scl'] = scl_final
instruction['type'] = instr_type + SCL_SUFFIX
map_key_out = (network_id, instr_uid, 'out')
scl_map[map_key_out] = target_scl
# --- CORRECCIÓN: Añadir ENO a scl_map ---
map_key_eno = (network_id, instr_uid, 'eno')
scl_map[map_key_eno] = en_scl # ENO sigue a EN
# --- FIN CORRECCIÓN ---
# print(f"INFO: CONVERT UID: {instr_uid} procesado. SCL: {scl_final.splitlines()[0]}...")
return True
def process_mod(instruction, network_id, scl_map, access_map):
"""Traduce Mod (módulo) a SCL, usando temporal si es necesario."""
instr_uid = instruction['instruction_uid']
instr_type = instruction['type']
if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type: return False
# print(f"DEBUG: Intentando procesar MOD - UID: {instr_uid} en Red: {network_id}")
# --- CORRECCIÓN: Manejo de EN ---
en_input = instruction['inputs'].get('en')
en_scl = None
if en_input is None:
# print(f"DEBUG: Asumiendo EN=TRUE para MOD UID {instr_uid} (pin 'en' no conectado)")
en_scl = "TRUE"
else:
en_scl = get_scl_representation(en_input, network_id, scl_map, access_map)
if en_scl is None: # Bloquear si EN está conectado pero no resuelto
# print(f"DEBUG: Dependencia EN no resuelta para MOD UID: {instr_uid}")
return False
# --- FIN CORRECCIÓN ---
in1_scl = get_scl_representation(instruction['inputs'].get('in1'), network_id, scl_map, access_map)
in2_scl = get_scl_representation(instruction['inputs'].get('in2'), network_id, scl_map, access_map)
if in1_scl is None or in2_scl is None:
# print(f"DEBUG: Dependencia no resuelta para MOD UID: {instr_uid} (in1={in1_scl}, in2={in2_scl})")
return False
target_scl = get_target_scl_name(instruction, 'out', network_id, default_to_temp=True)
if target_scl is None:
print(f"Error Interno: No se pudo determinar destino para MOD UID {instr_uid}")
instruction['scl'] = f"// ERROR: No se pudo determinar destino para Mod {instr_uid}"
instruction['type'] += "_error"
return True
op1 = f"({in1_scl})" if ' ' in in1_scl else in1_scl
op2 = f"({in2_scl})" if ' ' in in2_scl else in2_scl
scl_core = f"{target_scl} := {op1} MOD {op2};"
# --- CORRECCIÓN: Generación de IF ---
if en_scl != "TRUE":
scl_final = f"IF {en_scl} THEN\n {scl_core}\nEND_IF;"
else:
scl_final = scl_core
# --- FIN CORRECCIÓN ---
instruction['scl'] = scl_final
instruction['type'] = instr_type + SCL_SUFFIX
map_key_out = (network_id, instr_uid, 'out')
scl_map[map_key_out] = target_scl
# --- CORRECCIÓN: Añadir ENO a scl_map ---
map_key_eno = (network_id, instr_uid, 'eno')
scl_map[map_key_eno] = en_scl # ENO sigue a EN
# --- FIN CORRECCIÓN ---
# print(f"INFO: MOD UID: {instr_uid} procesado. SCL: {scl_final.splitlines()[0]}...")
return True
def process_add(instruction, network_id, scl_map, access_map):
instr_uid = instruction['instruction_uid']
instr_type = instruction['type']
if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type: return False
# print(f"DEBUG: Intentando procesar ADD - UID: {instr_uid} en Red: {network_id}")
# --- CORRECCIÓN: Manejo de EN ---
en_input = instruction['inputs'].get('en')
en_scl = None
if en_input is None:
# print(f"DEBUG: Asumiendo EN=TRUE para ADD UID {instr_uid} (pin 'en' no conectado)")
en_scl = "TRUE"
else:
en_scl = get_scl_representation(en_input, network_id, scl_map, access_map)
if en_scl is None: # Bloquear si EN está conectado pero no resuelto
# print(f"DEBUG: Dependencia EN no resuelta para ADD UID: {instr_uid}")
return False
# --- FIN CORRECCIÓN ---
in1_scl = get_scl_representation(instruction['inputs'].get('in1'), network_id, scl_map, access_map)
in2_scl = get_scl_representation(instruction['inputs'].get('in2'), network_id, scl_map, access_map)
if in1_scl is None or in2_scl is None:
# print(f"DEBUG: Dependencia no resuelta para ADD UID: {instr_uid} (in1={in1_scl}, in2={in2_scl})")
return False
target_scl = get_target_scl_name(instruction, 'out', network_id, default_to_temp=True)
if target_scl is None:
print(f"Error Interno: No se pudo determinar destino para ADD UID {instr_uid}")
instruction['scl'] = f"// ERROR: No se pudo determinar destino para Add {instr_uid}"
instruction['type'] += "_error"
return True
op1 = f"({in1_scl})" if ' ' in in1_scl else in1_scl
op2 = f"({in2_scl})" if ' ' in in2_scl else in2_scl
scl_core = f"{target_scl} := {op1} + {op2};"
# --- CORRECCIÓN: Generación de IF ---
if en_scl != "TRUE":
scl_final = f"IF {en_scl} THEN\n {scl_core}\nEND_IF;"
else:
scl_final = scl_core
# --- FIN CORRECCIÓN ---
instruction['scl'] = scl_final
instruction['type'] = instr_type + SCL_SUFFIX
map_key_out = (network_id, instr_uid, 'out')
scl_map[map_key_out] = target_scl
# --- CORRECCIÓN: Añadir ENO a scl_map ---
map_key_eno = (network_id, instr_uid, 'eno')
scl_map[map_key_eno] = en_scl # ENO sigue a EN
# --- FIN CORRECCIÓN ---
# print(f"INFO: ADD UID: {instr_uid} procesado. SCL: {scl_final.splitlines()[0]}...")
return True
def process_move(instruction, network_id, scl_map, access_map):
instr_uid = instruction['instruction_uid']
instr_type = instruction['type']
if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type: return False
# print(f"DEBUG: Intentando procesar MOVE - UID: {instr_uid} en Red: {network_id}")
# --- CORRECCIÓN: Manejo de EN ---
en_input = instruction['inputs'].get('en')
en_scl = None
if en_input is None:
# print(f"DEBUG: Asumiendo EN=TRUE para MOVE UID {instr_uid} (pin 'en' no conectado)")
en_scl = "TRUE"
else:
en_scl = get_scl_representation(en_input, network_id, scl_map, access_map)
if en_scl is None: # Bloquear si EN está conectado pero no resuelto
# print(f"DEBUG: Dependencia EN no resuelta para MOVE UID: {instr_uid}")
return False
# --- FIN CORRECCIÓN ---
in_scl = get_scl_representation(instruction['inputs'].get('in'), network_id, scl_map, access_map)
if in_scl is None:
# print(f"DEBUG: Dependencia IN no resuelta para MOVE UID: {instr_uid}")
return False
target_scl = get_target_scl_name(instruction, 'out1', network_id, default_to_temp=False) # No usar temp por defecto
if target_scl is None:
print(f"Advertencia: MOVE UID: {instr_uid} no tiene un destino variable único claro en out1. No se procesa.")
return False
scl_core = f"{target_scl} := {in_scl};"
# --- CORRECCIÓN: Generación de IF ---
if en_scl != "TRUE":
scl_final = f"IF {en_scl} THEN\n {scl_core}\nEND_IF;"
else:
scl_final = scl_core
# --- FIN CORRECCIÓN ---
instruction['scl'] = scl_final
instruction['type'] = instr_type + SCL_SUFFIX
# print(f"INFO: MOVE UID: {instr_uid} procesado. SCL: {scl_final.splitlines()[0]}...")
# Move no tiene ENO explícito en la mayoría de implementaciones LAD/SCL,
# por lo que no añadimos map_key_eno aquí normalmente.
return True
def process_pbox(instruction, network_id, scl_map, access_map, network_logic_list):
"""
Traduce PBox a SCL. Asume P_TRIG (flanco positivo) si detecta uso típico,
si no, pasa el valor del bit directamente.
"""
instr_uid = instruction['instruction_uid']
instr_type = instruction['type']
if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type: return False
# print(f"DEBUG: Intentando procesar PBOX - UID: {instr_uid} en Red: {network_id}")
mem_bit_input = instruction['inputs'].get('bit')
mem_bit_scl = get_scl_representation(mem_bit_input, network_id, scl_map, access_map)
if mem_bit_scl is None:
# print(f"DEBUG: Dependencia no resuelta para PBOX UID: {instr_uid} (bit={mem_bit_scl})")
return False
if not (mem_bit_input and mem_bit_input.get('type') == 'variable'):
print(f"Error: Entrada 'bit' de PBOX UID {instr_uid} no es una variable: {mem_bit_input}")
instruction['scl'] = f"// ERROR: PBox {instr_uid} entrada 'bit' no es variable"
instruction['type'] += "_error"
return True
is_likely_p_trig = False
consuming_coil_uid = None
for potential_consumer in network_logic_list:
consumer_inputs = potential_consumer.get('inputs', {})
coil_input_signal = consumer_inputs.get('in')
if isinstance(coil_input_signal, dict) and \
coil_input_signal.get('type') == 'connection' and \
coil_input_signal.get('source_instruction_uid') == instr_uid and \
coil_input_signal.get('source_pin') == 'out':
consumer_type = potential_consumer.get('type', '').replace('_scl','').replace('_error','')
if consumer_type == 'Coil':
consuming_coil_uid = potential_consumer['instruction_uid']
is_likely_p_trig = True
break
rlo_scl = None
if is_likely_p_trig:
clk_source_found = False
current_instr_index = -1
for i, instr in enumerate(network_logic_list):
if instr['instruction_uid'] == instr_uid:
current_instr_index = i
break
if current_instr_index != -1:
for i in range(current_instr_index - 1, -1, -1):
prev_instr = network_logic_list[i]
prev_instr_uid = prev_instr['instruction_uid']
prev_instr_type = prev_instr.get('type', '').replace(SCL_SUFFIX, '').replace('_error', '')
if prev_instr_type in ['Contact', 'Eq', 'O', 'PBox', 'And', 'Xor', 'Ne', 'Gt', 'Lt', 'Ge', 'Le']:
map_key_prev_out = (network_id, prev_instr_uid, 'out')
potential_clk_scl = scl_map.get(map_key_prev_out)
if potential_clk_scl is not None:
rlo_scl = potential_clk_scl
clk_source_found = True
break
elif prev_instr_type in ['Move', 'Add', 'Convert', 'Mod']:
map_key_prev_eno = (network_id, prev_instr_uid, 'eno')
potential_clk_scl = scl_map.get(map_key_prev_eno)
if potential_clk_scl is not None:
rlo_scl = potential_clk_scl
clk_source_found = True
break
if not clk_source_found:
print(f"Error: No se pudo inferir la fuente CLK para PBOX UID {instr_uid} (probable P_TRIG).")
instruction['scl'] = f"// ERROR: PBox {instr_uid} sin fuente CLK implícita clara"
instruction['type'] += "_error"
return True
if rlo_scl is None:
# print(f"DEBUG: Dependencia CLK (inferida) no resuelta para PBOX UID: {instr_uid}")
return False
scl_comment = ""
if is_likely_p_trig:
clk_signal_formatted = f"({rlo_scl})" if ' ' in rlo_scl else rlo_scl
result_scl = f"P_TRIG_FUNC(CLK := {clk_signal_formatted}, M := {mem_bit_scl})"
scl_comment = f"// Edge detection PBox {instr_uid} -> {result_scl} (CLK source inferred)"
else:
print(f"Advertencia: PBox UID {instr_uid} no coincide con patrón P_TRIG->Coil. Pasando valor de {mem_bit_scl} directamente.")
result_scl = mem_bit_scl
scl_comment = f"// PBox {instr_uid} - Passing value from bit: {result_scl}"
map_key_out = (network_id, instr_uid, 'out')
scl_map[map_key_out] = result_scl
instruction['scl'] = scl_comment
instruction['type'] = instr_type + SCL_SUFFIX
return True
def process_o(instruction, network_id, scl_map, access_map):
"""Traduce O (OR lógico) a una expresión booleana SCL."""
instr_uid = instruction['instruction_uid']
instr_type = instruction['type']
if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type: return False
# print(f"DEBUG: Intentando procesar O - UID: {instr_uid} en Red: {network_id}")
input_pins = [pin for pin in instruction['inputs'] if pin.startswith('in')]
if not input_pins:
print(f"Error: Instrucción O UID {instr_uid} no tiene pines de entrada 'inX'.")
instruction['scl'] = f"// ERROR: O {instr_uid} sin pines de entrada"
instruction['type'] += "_error"
return True
scl_parts = []
all_resolved = True
for pin in sorted(input_pins):
in_scl = get_scl_representation(instruction['inputs'][pin], network_id, scl_map, access_map)
if in_scl is None:
all_resolved = False
# print(f"DEBUG: Dependencia no resuelta para O UID: {instr_uid} (pin {pin})")
break
term = f"({in_scl})" if (' ' in in_scl or 'AND' in in_scl) and not (in_scl.startswith('(') and in_scl.endswith(')')) else in_scl
scl_parts.append(term)
if not all_resolved:
return False
result_scl = " OR ".join(scl_parts) if len(scl_parts) > 1 else (scl_parts[0] if scl_parts else "FALSE")
map_key_out = (network_id, instr_uid, 'out')
scl_map[map_key_out] = result_scl
instruction['scl'] = f"// Logic O {instr_uid}: {result_scl}"
instruction['type'] = instr_type + SCL_SUFFIX
return True
# --- Bucle Principal de Procesamiento ---
def process_json_to_scl(json_filepath):
"""Lee el JSON, aplica los procesadores iterativamente y guarda el resultado."""
if not os.path.exists(json_filepath):
print(f"Error: Archivo JSON no encontrado en {json_filepath}")
return
print(f"Cargando JSON desde: {json_filepath}")
try:
with open(json_filepath, 'r', encoding='utf-8') as f:
global data
data = json.load(f)
except Exception as e:
print(f"Error al cargar o parsear JSON: {e}")
return
# Reconstruir access_map dinámicamente
network_access_maps = {}
# print("Creando mapas de acceso por red...")
for network in data.get('networks', []):
net_id = network['id']
current_access_map = {}
for instr in network.get('logic', []):
for pin, source in instr.get('inputs', {}).items():
sources_to_check = source if isinstance(source, list) else ([source] if isinstance(source, dict) else [])
for src in sources_to_check:
if isinstance(src, dict) and src.get('uid') and src.get('scope') and src.get('type') in ['variable', 'constant']:
current_access_map[src['uid']] = src
for pin, 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('scope') and dest.get('type') in ['variable', 'constant']:
current_access_map[dest['uid']] = dest
network_access_maps[net_id] = current_access_map
scl_map = {}
max_passes = 25 # Aumentado ligeramente por si acaso
passes = 0
# Lista de procesadores con orden corregido
processors = [
process_convert,
process_mod,
process_eq,
process_contact,
process_o,
process_pbox,
process_add,
process_move,
process_coil,
# process_group_ifs, # Aplazado por ahora para simplificar
]
# Mapa de procesadores usando lower case para la clave
processor_map = {func.__name__.split('_')[1].lower(): func for func in processors}
print("\n--- Iniciando Bucle de Procesamiento Iterativo ---")
while passes < max_passes:
passes += 1
made_change_in_pass = False
print(f"\n--- Pase {passes} ---")
for network in data.get('networks', []):
network_id = network['id']
access_map = network_access_maps.get(network_id, {})
network_logic = network.get('logic', [])
for instruction in network_logic:
instr_type_original = instruction['type']
if instr_type_original.endswith(SCL_SUFFIX) or "_error" in instr_type_original:
continue
# Búsqueda de procesador (case-insensitive)
instr_type_lower_lookup = instr_type_original.lower()
func_to_call = processor_map.get(instr_type_lower_lookup)
if func_to_call:
# print(f"DEBUG: Intentando {func_to_call.__name__} para {instr_type_original} UID {instruction['instruction_uid']}")
try:
# Pasar lista lógica si es PBox
if func_to_call == process_pbox:
changed = func_to_call(instruction, network_id, scl_map, access_map, network_logic)
else:
changed = func_to_call(instruction, network_id, scl_map, access_map)
if changed:
# print(f"DEBUG: Cambio realizado por {func_to_call.__name__} en UID {instruction['instruction_uid']}")
made_change_in_pass = True
# No hacer break, permite que el procesador de grupo actúe si se añade después
except Exception as e:
print(f"ERROR al ejecutar {func_to_call.__name__} en UID {instruction.get('instruction_uid')} Red {network_id}: {e}")
traceback.print_exc()
instruction['scl'] = f"// ERROR during processing: {e}"
instruction['type'] += "_error"
made_change_in_pass = True
# --- Llamada Opcional al Procesador de Agrupación (APLAZADO) ---
# if made_change_in_pass: # Solo intentar agrupar si hubo otros cambios?
# for network in data.get('networks', []):
# # ... (código para llamar a process_group_ifs) ...
# pass
if not made_change_in_pass:
print(f"\n--- No se hicieron cambios en el pase {passes}. Proceso completado. ---")
break
# else: print(f"DEBUG: Se hicieron cambios en el pase {passes}. Continuando...")
if passes == max_passes:
print(f"\n--- Límite de {max_passes} pases alcanzado. Puede haber dependencias circulares o lógica no procesada. ---")
output_filename = json_filepath.replace('.json', '_scl_processed.json')
print(f"\nGuardando JSON procesado 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 completado.")
except Exception as e:
print(f"Error al guardar el JSON procesado: {e}")
# --- Ejecución ---
if __name__ == "__main__":
input_json_file = 'BlenderRun_ProdTime_simplified.json'
process_json_to_scl(input_json_file)