Mejora en el convertidor LAD de TwinCAT con integración de SymPy para optimización de expresiones lógicas. Se añadieron nuevas funcionalidades para el manejo de variables y ACTIONs, así como mejoras en la estructura del código SCL generado.
This commit is contained in:
parent
205e1f4c8d
commit
5da7dcad06
|
@ -2,9 +2,26 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""
|
"""
|
||||||
Convertidor simplificado de LAD TwinCAT a pseudocódigo estructurado
|
Convertidor simplificado de LAD TwinCAT a pseudocódigo estructurado
|
||||||
|
Versión mejorada con SymPy para optimización de expresiones lógicas
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
import sympy
|
||||||
|
from sympy import symbols, And, Or, Not, simplify
|
||||||
|
from sympy.logic.boolalg import to_dnf
|
||||||
|
|
||||||
|
class SymbolManager:
|
||||||
|
"""Gestor de símbolos para SymPy"""
|
||||||
|
def __init__(self):
|
||||||
|
self._symbol_cache = {}
|
||||||
|
|
||||||
|
def get_symbol(self, name):
|
||||||
|
"""Obtener o crear símbolo SymPy"""
|
||||||
|
if name not in self._symbol_cache:
|
||||||
|
# Limpiar nombre para SymPy (sin espacios, caracteres especiales)
|
||||||
|
clean_name = re.sub(r'[^a-zA-Z0-9_]', '_', name)
|
||||||
|
self._symbol_cache[name] = symbols(clean_name)
|
||||||
|
return self._symbol_cache[name]
|
||||||
|
|
||||||
class SimpleLadConverter:
|
class SimpleLadConverter:
|
||||||
"""Convertidor simplificado de LAD a código estructurado"""
|
"""Convertidor simplificado de LAD a código estructurado"""
|
||||||
|
@ -12,21 +29,89 @@ class SimpleLadConverter:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.networks = []
|
self.networks = []
|
||||||
self.current_network_id = 0
|
self.current_network_id = 0
|
||||||
|
self.symbol_manager = SymbolManager()
|
||||||
|
self.sympy_expressions = {} # Mapeo de network_id -> sympy expression
|
||||||
|
|
||||||
|
# Nuevas propiedades para estructura SCL completa
|
||||||
|
self.program_name = ""
|
||||||
|
self.program_path = ""
|
||||||
|
self.var_sections = {} # VAR, VAR_INPUT, VAR_OUTPUT, etc.
|
||||||
|
self.actions = {} # Diccionario de ACTIONs
|
||||||
|
|
||||||
def parse_file(self, file_path):
|
def parse_file(self, file_path):
|
||||||
"""Parse el archivo LAD"""
|
"""Parse el archivo LAD completo incluyendo variables y ACTIONs"""
|
||||||
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
|
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
|
||||||
content = f.read()
|
content = f.read()
|
||||||
|
|
||||||
|
# Extraer información del header
|
||||||
|
self._parse_header_info(content)
|
||||||
|
|
||||||
|
# Extraer declaraciones de variables
|
||||||
|
self._parse_variable_declarations(content)
|
||||||
|
|
||||||
# Encontrar sección LAD
|
# Encontrar sección LAD
|
||||||
lad_start = content.find('_LD_BODY')
|
lad_start = content.find('_LD_BODY')
|
||||||
if lad_start == -1:
|
if lad_start != -1:
|
||||||
|
# Extraer contenido LAD hasta ACTION o END_PROGRAM
|
||||||
|
action_start = content.find('\nACTION', lad_start)
|
||||||
|
end_program = content.find('\nEND_PROGRAM', lad_start)
|
||||||
|
|
||||||
|
lad_end = action_start if action_start != -1 else end_program
|
||||||
|
if lad_end == -1:
|
||||||
|
lad_end = len(content)
|
||||||
|
|
||||||
|
lad_content = content[lad_start:lad_end]
|
||||||
|
lines = lad_content.split('\n')
|
||||||
|
self._parse_networks(lines)
|
||||||
|
else:
|
||||||
print("No se encontró _LD_BODY")
|
print("No se encontró _LD_BODY")
|
||||||
return
|
|
||||||
|
|
||||||
# Extraer contenido LAD
|
# Extraer ACTIONs
|
||||||
lines = content[lad_start:].split('\n')
|
self._parse_actions(content)
|
||||||
self._parse_networks(lines)
|
|
||||||
|
def _parse_header_info(self, content):
|
||||||
|
"""Extraer información del header del programa"""
|
||||||
|
# Buscar PATH y nombre del programa
|
||||||
|
path_match = re.search(r'\(\* @PATH := \'([^\']+)\' \*\)', content)
|
||||||
|
if path_match:
|
||||||
|
self.program_path = path_match.group(1)
|
||||||
|
|
||||||
|
# Buscar nombre del programa/function_block
|
||||||
|
program_match = re.search(r'(PROGRAM|FUNCTION_BLOCK)\s+(\w+)', content)
|
||||||
|
if program_match:
|
||||||
|
self.program_name = program_match.group(2)
|
||||||
|
|
||||||
|
print(f"Programa encontrado: {self.program_name}")
|
||||||
|
if self.program_path:
|
||||||
|
print(f"Path: {self.program_path}")
|
||||||
|
|
||||||
|
def _parse_variable_declarations(self, content):
|
||||||
|
"""Extraer todas las declaraciones de variables"""
|
||||||
|
# Buscar todas las secciones VAR
|
||||||
|
var_patterns = [
|
||||||
|
(r'VAR_INPUT(.*?)END_VAR', 'VAR_INPUT'),
|
||||||
|
(r'VAR_OUTPUT(.*?)END_VAR', 'VAR_OUTPUT'),
|
||||||
|
(r'VAR_IN_OUT(.*?)END_VAR', 'VAR_IN_OUT'),
|
||||||
|
(r'VAR\s+(.*?)END_VAR', 'VAR'),
|
||||||
|
]
|
||||||
|
|
||||||
|
for pattern, var_type in var_patterns:
|
||||||
|
matches = re.findall(pattern, content, re.DOTALL | re.IGNORECASE)
|
||||||
|
if matches:
|
||||||
|
variables = []
|
||||||
|
for match in matches:
|
||||||
|
# Parsear cada línea de variable
|
||||||
|
for line in match.split('\n'):
|
||||||
|
line = line.strip()
|
||||||
|
if line and not line.startswith('(*') and ':' in line:
|
||||||
|
# Limpiar comentarios inline
|
||||||
|
if '(*' in line:
|
||||||
|
line = line[:line.find('(*')]
|
||||||
|
variables.append(line.strip())
|
||||||
|
|
||||||
|
if variables:
|
||||||
|
self.var_sections[var_type] = variables
|
||||||
|
print(f"Variables {var_type}: {len(variables)} encontradas")
|
||||||
|
|
||||||
def _parse_networks(self, lines):
|
def _parse_networks(self, lines):
|
||||||
"""Parse todas las redes"""
|
"""Parse todas las redes"""
|
||||||
|
@ -205,22 +290,52 @@ class SimpleLadConverter:
|
||||||
return i + 1, ' '.join(comment_lines)
|
return i + 1, ' '.join(comment_lines)
|
||||||
|
|
||||||
def convert_to_structured(self):
|
def convert_to_structured(self):
|
||||||
"""Convertir a código estructurado"""
|
"""Convertir a código SCL completo con variables y ACTIONs"""
|
||||||
output = []
|
output = []
|
||||||
output.append("// Código pseudo estructurado generado desde LAD TwinCAT")
|
|
||||||
output.append("// Compatible con IEC61131-3")
|
# Header del archivo
|
||||||
output.append("PROGRAM PumpControl_Converted")
|
output.append("(* Código SCL generado desde LAD TwinCAT *)")
|
||||||
|
output.append("(* Convertidor mejorado con SymPy - Estructura DNF preferida *)")
|
||||||
|
if self.program_path:
|
||||||
|
output.append(f"(* Path original: {self.program_path} *)")
|
||||||
output.append("")
|
output.append("")
|
||||||
|
|
||||||
|
# Declaración del programa
|
||||||
|
program_name = self.program_name if self.program_name else "ConvertedProgram"
|
||||||
|
output.append(f"PROGRAM {program_name}")
|
||||||
|
|
||||||
|
# Declaraciones de variables
|
||||||
|
self._add_variable_sections(output)
|
||||||
|
|
||||||
|
# Inicio del código principal
|
||||||
|
output.append("")
|
||||||
|
output.append("(* === CÓDIGO PRINCIPAL === *)")
|
||||||
|
output.append("")
|
||||||
|
|
||||||
|
# Lógica de las redes LAD
|
||||||
for network in self.networks:
|
for network in self.networks:
|
||||||
output.append(f" // Red {network['id']}")
|
output.append(f" // Red {network['id']}")
|
||||||
if network['comment']:
|
if network['comment']:
|
||||||
output.append(f" // {network['comment']}")
|
output.append(f" // {network['comment']}")
|
||||||
|
|
||||||
if network['logic'] and network['target']:
|
if network['logic'] and network['target']:
|
||||||
condition_str = self._convert_logic_to_string(network['logic'])
|
# Usar expresión DNF optimizada si está disponible
|
||||||
|
if network['id'] in self.sympy_expressions:
|
||||||
|
sympy_expr = self.sympy_expressions[network['id']]
|
||||||
|
condition_str = self._format_dnf_for_lad(sympy_expr)
|
||||||
|
output.append(f" // Optimizada con SymPy DNF")
|
||||||
|
else:
|
||||||
|
# Fallback al método original
|
||||||
|
condition_str = self._convert_logic_to_string(network['logic'])
|
||||||
|
output.append(f" // Sin optimización SymPy")
|
||||||
|
|
||||||
if condition_str:
|
if condition_str:
|
||||||
output.append(f" IF {condition_str} THEN")
|
# Si hay saltos de línea en la condición (múltiples términos OR)
|
||||||
|
if '\n' in condition_str:
|
||||||
|
output.append(f" IF {condition_str} THEN")
|
||||||
|
else:
|
||||||
|
output.append(f" IF {condition_str} THEN")
|
||||||
|
|
||||||
output.append(f" {network['target']} := TRUE;")
|
output.append(f" {network['target']} := TRUE;")
|
||||||
output.append(" ELSE")
|
output.append(" ELSE")
|
||||||
output.append(f" {network['target']} := FALSE;")
|
output.append(f" {network['target']} := FALSE;")
|
||||||
|
@ -230,9 +345,58 @@ class SimpleLadConverter:
|
||||||
|
|
||||||
output.append("")
|
output.append("")
|
||||||
|
|
||||||
|
# Fin del programa principal
|
||||||
output.append("END_PROGRAM")
|
output.append("END_PROGRAM")
|
||||||
|
|
||||||
|
# Agregar ACTIONs como subfunciones
|
||||||
|
self._add_actions_as_procedures(output, program_name)
|
||||||
|
|
||||||
return '\n'.join(output)
|
return '\n'.join(output)
|
||||||
|
|
||||||
|
def _add_variable_sections(self, output):
|
||||||
|
"""Agregar todas las secciones de variables al código"""
|
||||||
|
# Orden preferido de las secciones
|
||||||
|
section_order = ['VAR_INPUT', 'VAR_OUTPUT', 'VAR_IN_OUT', 'VAR']
|
||||||
|
|
||||||
|
for section_type in section_order:
|
||||||
|
if section_type in self.var_sections:
|
||||||
|
output.append(f"{section_type}")
|
||||||
|
for var_line in self.var_sections[section_type]:
|
||||||
|
# Asegurar que termine con punto y coma
|
||||||
|
if not var_line.endswith(';'):
|
||||||
|
var_line += ' ;'
|
||||||
|
output.append(f" {var_line}")
|
||||||
|
output.append("END_VAR")
|
||||||
|
output.append("")
|
||||||
|
|
||||||
|
def _add_actions_as_procedures(self, output, program_name):
|
||||||
|
"""Agregar ACTIONs como procedimientos independientes"""
|
||||||
|
if not self.actions:
|
||||||
|
return
|
||||||
|
|
||||||
|
output.append("")
|
||||||
|
output.append("(* === SUBFUNCIONES (ACTIONs convertidas) === *)")
|
||||||
|
output.append("")
|
||||||
|
|
||||||
|
for action_name, action_code in self.actions.items():
|
||||||
|
# Nombre completo como se usa en TwinCAT
|
||||||
|
full_name = f"{program_name}_{action_name}"
|
||||||
|
|
||||||
|
output.append(f"PROCEDURE {full_name}")
|
||||||
|
output.append("(* Convertida desde ACTION *)")
|
||||||
|
output.append("")
|
||||||
|
|
||||||
|
# Agregar el código de la ACTION con indentación
|
||||||
|
for line in action_code.split('\n'):
|
||||||
|
if line.strip():
|
||||||
|
output.append(f" {line.strip()}")
|
||||||
|
else:
|
||||||
|
output.append("")
|
||||||
|
|
||||||
|
output.append("")
|
||||||
|
output.append("END_PROCEDURE")
|
||||||
|
output.append("")
|
||||||
|
|
||||||
def _convert_logic_to_string(self, logic):
|
def _convert_logic_to_string(self, logic):
|
||||||
"""Convertir lógica LAD a string estructurado"""
|
"""Convertir lógica LAD a string estructurado"""
|
||||||
if not logic:
|
if not logic:
|
||||||
|
@ -333,32 +497,334 @@ class SimpleLadConverter:
|
||||||
|
|
||||||
return f"{prefix}UNKNOWN: {logic}"
|
return f"{prefix}UNKNOWN: {logic}"
|
||||||
|
|
||||||
|
def _logic_to_sympy(self, logic):
|
||||||
|
"""Convertir lógica LAD a expresión SymPy"""
|
||||||
|
if not logic:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if logic['type'] == 'CONTACT':
|
||||||
|
symbol = self.symbol_manager.get_symbol(logic['name'])
|
||||||
|
if logic['negated']:
|
||||||
|
return Not(symbol)
|
||||||
|
else:
|
||||||
|
return symbol
|
||||||
|
|
||||||
|
elif logic['type'] == 'AND':
|
||||||
|
sympy_operands = []
|
||||||
|
for operand in logic['operands']:
|
||||||
|
operand_sympy = self._logic_to_sympy(operand)
|
||||||
|
if operand_sympy is not None:
|
||||||
|
sympy_operands.append(operand_sympy)
|
||||||
|
|
||||||
|
if len(sympy_operands) > 1:
|
||||||
|
return And(*sympy_operands)
|
||||||
|
elif len(sympy_operands) == 1:
|
||||||
|
return sympy_operands[0]
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
elif logic['type'] == 'OR':
|
||||||
|
sympy_operands = []
|
||||||
|
for operand in logic['operands']:
|
||||||
|
operand_sympy = self._logic_to_sympy(operand)
|
||||||
|
if operand_sympy is not None:
|
||||||
|
sympy_operands.append(operand_sympy)
|
||||||
|
|
||||||
|
if len(sympy_operands) > 1:
|
||||||
|
return Or(*sympy_operands)
|
||||||
|
elif len(sympy_operands) == 1:
|
||||||
|
return sympy_operands[0]
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
elif logic['type'] == 'FUNCTION_BLOCK':
|
||||||
|
# Para function blocks, creamos un símbolo especial
|
||||||
|
fb_name = f"{logic['name']}({', '.join(logic['inputs'])})"
|
||||||
|
return self.symbol_manager.get_symbol(fb_name)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _sympy_to_structured_string(self, sympy_expr):
|
||||||
|
"""Convertir expresión SymPy a string estructurado"""
|
||||||
|
if sympy_expr is None:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
# Crear un mapeo inverso de símbolos a nombres originales
|
||||||
|
symbol_to_name = {}
|
||||||
|
for original_name, symbol in self.symbol_manager._symbol_cache.items():
|
||||||
|
symbol_to_name[str(symbol)] = original_name
|
||||||
|
|
||||||
|
# Convertir la expresión a string
|
||||||
|
str_expr = str(sympy_expr)
|
||||||
|
|
||||||
|
# Reemplazar símbolos por nombres originales
|
||||||
|
for symbol_str, original_name in symbol_to_name.items():
|
||||||
|
str_expr = str_expr.replace(symbol_str, original_name)
|
||||||
|
|
||||||
|
# Reemplazar operadores SymPy por operadores IEC61131-3
|
||||||
|
str_expr = str_expr.replace('&', ' AND ')
|
||||||
|
str_expr = str_expr.replace('|', ' OR ')
|
||||||
|
str_expr = str_expr.replace('~', 'NOT ')
|
||||||
|
|
||||||
|
# Limpiar espacios múltiples
|
||||||
|
str_expr = re.sub(r'\s+', ' ', str_expr)
|
||||||
|
|
||||||
|
return str_expr.strip()
|
||||||
|
|
||||||
|
def optimize_expressions(self):
|
||||||
|
"""Optimizar todas las expresiones usando SymPy - Preferir DNF para LAD"""
|
||||||
|
print("\n=== Optimizando expresiones con SymPy (forzando DNF para LAD) ===")
|
||||||
|
|
||||||
|
for network in self.networks:
|
||||||
|
if network['logic']:
|
||||||
|
print(f"\nOptimizando Red {network['id']}:")
|
||||||
|
|
||||||
|
# Convertir a SymPy
|
||||||
|
sympy_expr = self._logic_to_sympy(network['logic'])
|
||||||
|
if sympy_expr:
|
||||||
|
print(f" Expresión original: {sympy_expr}")
|
||||||
|
|
||||||
|
# Simplificar primero
|
||||||
|
try:
|
||||||
|
simplified = simplify(sympy_expr)
|
||||||
|
print(f" Simplificada: {simplified}")
|
||||||
|
|
||||||
|
# SIEMPRE convertir a DNF para LAD (forma natural: (AND) OR (AND) OR (AND))
|
||||||
|
dnf_expr = to_dnf(simplified)
|
||||||
|
print(f" DNF (forma LAD preferida): {dnf_expr}")
|
||||||
|
|
||||||
|
# Post-procesar para eliminar contradicciones
|
||||||
|
final_expr = self._post_process_expression(dnf_expr)
|
||||||
|
|
||||||
|
# Verificar si el post-procesamiento cambió algo
|
||||||
|
if str(final_expr) != str(dnf_expr):
|
||||||
|
print(f" Post-procesada: {final_expr}")
|
||||||
|
|
||||||
|
self.sympy_expressions[network['id']] = final_expr
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f" Error optimizando: {e}")
|
||||||
|
self.sympy_expressions[network['id']] = sympy_expr
|
||||||
|
|
||||||
|
def group_common_conditions(self):
|
||||||
|
"""Agrupar networks con condiciones similares o complementarias"""
|
||||||
|
print("\n=== Analizando agrupación de condiciones ===")
|
||||||
|
|
||||||
|
# Buscar networks que podrían agruparse
|
||||||
|
groupable_networks = []
|
||||||
|
for network in self.networks:
|
||||||
|
if (network['logic'] and network['target'] and
|
||||||
|
network['id'] in self.sympy_expressions):
|
||||||
|
groupable_networks.append(network)
|
||||||
|
|
||||||
|
if len(groupable_networks) < 2:
|
||||||
|
print("No hay suficientes networks para agrupar")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Analizar condiciones comunes
|
||||||
|
print(f"Analizando {len(groupable_networks)} networks para agrupación:")
|
||||||
|
|
||||||
|
for i, net1 in enumerate(groupable_networks):
|
||||||
|
for j, net2 in enumerate(groupable_networks[i+1:], i+1):
|
||||||
|
expr1 = self.sympy_expressions[net1['id']]
|
||||||
|
expr2 = self.sympy_expressions[net2['id']]
|
||||||
|
|
||||||
|
# Buscar factores comunes
|
||||||
|
try:
|
||||||
|
# Extraer factores comunes si es posible
|
||||||
|
common_factors = self._find_common_factors(expr1, expr2)
|
||||||
|
if common_factors:
|
||||||
|
print(f" Red {net1['id']} y Red {net2['id']} comparten: {common_factors}")
|
||||||
|
|
||||||
|
# Verificar si son complementarias (útil para SET/RESET)
|
||||||
|
if self._are_complementary(expr1, expr2):
|
||||||
|
print(f" Red {net1['id']} y Red {net2['id']} son complementarias")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f" Error analizando Red {net1['id']} y Red {net2['id']}: {e}")
|
||||||
|
|
||||||
|
def _find_common_factors(self, expr1, expr2):
|
||||||
|
"""Encontrar factores comunes entre dos expresiones"""
|
||||||
|
try:
|
||||||
|
# Convertir a conjuntos de símbolos para análisis básico
|
||||||
|
symbols1 = expr1.free_symbols
|
||||||
|
symbols2 = expr2.free_symbols
|
||||||
|
common_symbols = symbols1.intersection(symbols2)
|
||||||
|
|
||||||
|
if len(common_symbols) > 1:
|
||||||
|
return f"{len(common_symbols)} símbolos comunes"
|
||||||
|
|
||||||
|
return None
|
||||||
|
except:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _are_complementary(self, expr1, expr2):
|
||||||
|
"""Verificar si dos expresiones son complementarias"""
|
||||||
|
try:
|
||||||
|
# Verificar si expr1 == NOT(expr2) simplificado
|
||||||
|
complement = Not(expr2)
|
||||||
|
simplified_complement = simplify(complement)
|
||||||
|
simplified_expr1 = simplify(expr1)
|
||||||
|
|
||||||
|
return simplified_expr1.equals(simplified_complement)
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _post_process_expression(self, expr):
|
||||||
|
"""Post-procesar expresión para eliminar contradicciones obvias"""
|
||||||
|
try:
|
||||||
|
# Detectar contradicciones como X & ~X que deberían ser False
|
||||||
|
cleaned_expr = expr
|
||||||
|
|
||||||
|
# Aplicar simplificaciones adicionales
|
||||||
|
cleaned_expr = simplify(cleaned_expr)
|
||||||
|
|
||||||
|
# Si la expresión contiene contradicciones obvias, intentar limpiar
|
||||||
|
free_symbols = cleaned_expr.free_symbols
|
||||||
|
for symbol in free_symbols:
|
||||||
|
# Verificar si tenemos symbol & ~symbol en alguna parte
|
||||||
|
contradiction = And(symbol, Not(symbol))
|
||||||
|
if cleaned_expr.has(contradiction):
|
||||||
|
print(f" Detectada contradicción eliminable: {symbol} AND NOT {symbol}")
|
||||||
|
# Reemplazar contradicción por False
|
||||||
|
cleaned_expr = cleaned_expr.replace(contradiction, sympy.false)
|
||||||
|
cleaned_expr = simplify(cleaned_expr)
|
||||||
|
|
||||||
|
return cleaned_expr
|
||||||
|
except:
|
||||||
|
return expr
|
||||||
|
|
||||||
|
def _format_dnf_for_lad(self, sympy_expr):
|
||||||
|
"""Formatear expresión DNF para código LAD más legible"""
|
||||||
|
if sympy_expr is None:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
# Crear mapeo de símbolos a nombres originales
|
||||||
|
symbol_to_name = {}
|
||||||
|
for original_name, symbol in self.symbol_manager._symbol_cache.items():
|
||||||
|
symbol_to_name[str(symbol)] = original_name
|
||||||
|
|
||||||
|
# Convertir a string y analizar la estructura
|
||||||
|
str_expr = str(sympy_expr)
|
||||||
|
|
||||||
|
# Reemplazar símbolos por nombres originales
|
||||||
|
for symbol_str, original_name in symbol_to_name.items():
|
||||||
|
str_expr = str_expr.replace(symbol_str, original_name)
|
||||||
|
|
||||||
|
# Reemplazar operadores SymPy por IEC61131-3 primero
|
||||||
|
str_expr = str_expr.replace('&', ' AND ')
|
||||||
|
str_expr = str_expr.replace('|', ' OR ')
|
||||||
|
str_expr = str_expr.replace('~', 'NOT ')
|
||||||
|
|
||||||
|
# Limpiar espacios múltiples
|
||||||
|
str_expr = re.sub(r'\s+', ' ', str_expr).strip()
|
||||||
|
|
||||||
|
# Si es una expresión OR de términos AND principales, formatear cada término
|
||||||
|
# Buscar el patrón de OR principales (no anidados en paréntesis)
|
||||||
|
if ' OR ' in str_expr and not str_expr.startswith('('):
|
||||||
|
# Dividir por OR de nivel principal
|
||||||
|
# Esto es más complejo debido a paréntesis anidados
|
||||||
|
parts = self._split_main_or_terms(str_expr)
|
||||||
|
|
||||||
|
if len(parts) > 1:
|
||||||
|
formatted_terms = []
|
||||||
|
for part in parts:
|
||||||
|
part = part.strip()
|
||||||
|
# Asegurar que cada término tenga paréntesis si es complejo
|
||||||
|
if ' AND ' in part and not (part.startswith('(') and part.endswith(')')):
|
||||||
|
part = f"({part})"
|
||||||
|
formatted_terms.append(part)
|
||||||
|
|
||||||
|
# Unir con OR y saltos de línea para mejor legibilidad
|
||||||
|
return '\n OR '.join(formatted_terms)
|
||||||
|
|
||||||
|
return str_expr
|
||||||
|
|
||||||
|
def _split_main_or_terms(self, expr):
|
||||||
|
"""Dividir expresión por OR de nivel principal, respetando paréntesis"""
|
||||||
|
parts = []
|
||||||
|
current_part = ""
|
||||||
|
paren_level = 0
|
||||||
|
i = 0
|
||||||
|
|
||||||
|
while i < len(expr):
|
||||||
|
char = expr[i]
|
||||||
|
|
||||||
|
if char == '(':
|
||||||
|
paren_level += 1
|
||||||
|
current_part += char
|
||||||
|
elif char == ')':
|
||||||
|
paren_level -= 1
|
||||||
|
current_part += char
|
||||||
|
elif paren_level == 0 and expr[i:i+4] == ' OR ':
|
||||||
|
# OR de nivel principal encontrado
|
||||||
|
parts.append(current_part.strip())
|
||||||
|
current_part = ""
|
||||||
|
i += 3 # Saltar ' OR '
|
||||||
|
else:
|
||||||
|
current_part += char
|
||||||
|
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
# Agregar la última parte
|
||||||
|
if current_part.strip():
|
||||||
|
parts.append(current_part.strip())
|
||||||
|
|
||||||
|
return parts if len(parts) > 1 else [expr]
|
||||||
|
|
||||||
|
def _parse_actions(self, content):
|
||||||
|
"""Extraer todas las ACTIONs del programa"""
|
||||||
|
# Buscar patrón ACTION nombre: ... END_ACTION
|
||||||
|
action_pattern = r'ACTION\s+(\w+)\s*:(.*?)END_ACTION'
|
||||||
|
action_matches = re.findall(action_pattern, content, re.DOTALL | re.IGNORECASE)
|
||||||
|
|
||||||
|
for action_name, action_code in action_matches:
|
||||||
|
# Limpiar el código de la ACTION
|
||||||
|
clean_code = action_code.strip()
|
||||||
|
self.actions[action_name] = clean_code
|
||||||
|
print(f"ACTION encontrada: {action_name} ({len(clean_code)} caracteres)")
|
||||||
|
|
||||||
|
print(f"Total ACTIONs: {len(self.actions)}")
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""Función principal"""
|
"""Función principal"""
|
||||||
converter = SimpleLadConverter()
|
converter = SimpleLadConverter()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
print("=== Convertidor LAD Mejorado ===")
|
print("=== Convertidor LAD a SCL con SymPy ===")
|
||||||
print("Parseando archivo _PUMPCONTROL.EXP...")
|
|
||||||
|
|
||||||
converter.parse_file(".example/_PUMPCONTROL.EXP")
|
# Por ahora probar con SYRUPROOMCTRL que tiene variables y ACTIONs
|
||||||
|
file_path = ".example/SYRUPROOMCTRL.EXP"
|
||||||
|
output_name = "SYRUPROOMCTRL_scl"
|
||||||
|
|
||||||
|
print(f"Parseando archivo {file_path}...")
|
||||||
|
|
||||||
|
converter.parse_file(file_path)
|
||||||
|
|
||||||
print(f"Redes encontradas: {len(converter.networks)}")
|
print(f"Redes encontradas: {len(converter.networks)}")
|
||||||
|
print(f"Secciones de variables: {list(converter.var_sections.keys())}")
|
||||||
|
print(f"ACTIONs encontradas: {list(converter.actions.keys())}")
|
||||||
|
|
||||||
# Mostrar información de debug
|
# Mostrar información de debug
|
||||||
converter.print_debug_info()
|
converter.print_debug_info()
|
||||||
|
|
||||||
|
# NUEVO: Optimizar expresiones con SymPy
|
||||||
|
converter.optimize_expressions()
|
||||||
|
|
||||||
|
# NUEVO: Analizar agrupación de condiciones
|
||||||
|
converter.group_common_conditions()
|
||||||
|
|
||||||
# Convertir y guardar
|
# Convertir y guardar
|
||||||
print("\nGenerando código estructurado...")
|
print("\nGenerando código SCL completo...")
|
||||||
structured_code = converter.save_to_file("pump_control_output.txt")
|
structured_code = converter.save_to_file(f"{output_name}.txt")
|
||||||
|
|
||||||
# Mostrar el código generado
|
# Mostrar el código generado
|
||||||
lines = structured_code.split('\n')
|
lines = structured_code.split('\n')
|
||||||
print(f"\nCódigo generado ({len(lines)} líneas):")
|
print(f"\nCódigo SCL generado ({len(lines)} líneas):")
|
||||||
for i, line in enumerate(lines):
|
for i, line in enumerate(lines):
|
||||||
print(f"{i+1:3d}: {line}")
|
print(f"{i+1:3d}: {line}")
|
||||||
|
|
||||||
print(f"\n✓ Conversión completada!")
|
print(f"\n✓ Conversión SCL completada!")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error: {e}")
|
print(f"Error: {e}")
|
||||||
|
|
Loading…
Reference in New Issue