ParamManagerScripts/backend/script_groups/TwinCat/lad_to_pseudocode_converter...

429 lines
15 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Convertidor mejorado de código LAD (Ladder Diagram) de TwinCAT a pseudocódigo estructurado
Compatible con IEC61131-3
Autor: Asistente AI
Fecha: 2024
"""
import re
from typing import List, Dict, Any, Optional, Tuple
from dataclasses import dataclass, field
from enum import Enum
class LadElementType(Enum):
CONTACT = "CONTACT"
COIL = "COIL"
FUNCTION_BLOCK = "FUNCTION_BLOCK"
OPERATOR = "OPERATOR"
ASSIGNMENT = "ASSIGNMENT"
CONDITION = "CONDITION"
@dataclass
class LadVariable:
"""Representa una variable en LAD"""
name: str
negated: bool = False
value: str = ""
@dataclass
class LadExpression:
"""Representa una expresión en LAD"""
operator: str = ""
operands: List[str] = field(default_factory=list)
result_var: str = ""
@dataclass
class LadNetwork:
"""Representa una red LAD completa"""
id: int
comment: str = ""
contacts: List[LadVariable] = field(default_factory=list)
operators: List[str] = field(default_factory=list)
function_blocks: List[Dict] = field(default_factory=list)
expressions: List[LadExpression] = field(default_factory=list)
outputs: List[LadVariable] = field(default_factory=list)
assignments: List[Dict] = field(default_factory=list)
class EnhancedLadConverter:
"""Convertidor mejorado de LAD a código estructurado"""
def __init__(self):
self.networks: List[LadNetwork] = []
self.output_lines: List[str] = []
def parse_file(self, file_path: str) -> None:
"""Parse el archivo LAD completo"""
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
content = f.read()
# Encontrar la sección LAD
lad_start = content.find('_LD_BODY')
if lad_start == -1:
raise ValueError("No se encontró sección _LD_BODY en el archivo")
# Extraer solo la sección LAD hasta END_PROGRAM
lad_end = content.find('END_PROGRAM', lad_start)
if lad_end == -1:
lad_content = content[lad_start:]
else:
lad_content = content[lad_start:lad_end]
self._parse_networks(lad_content)
def _parse_networks(self, content: str) -> None:
"""Parse todas las redes del contenido LAD"""
lines = content.split('\n')
i = 0
network_id = 0
while i < len(lines):
if lines[i].strip() == '_NETWORK':
network_id += 1
i = self._parse_single_network(lines, i, network_id)
else:
i += 1
def _parse_single_network(self, lines: List[str], start_idx: int, network_id: int) -> int:
"""Parse una red individual"""
network = LadNetwork(id=network_id)
i = start_idx + 1
# Parse comment
if i < len(lines) and lines[i].strip() == '_COMMENT':
i, comment = self._extract_comment(lines, i)
network.comment = comment
# Parse network content
while i < len(lines):
line = lines[i].strip()
if line == '_NETWORK':
# Inicio de nueva red
break
elif line == '_LD_ASSIGN':
i = self._parse_assignment_block(lines, i, network)
elif line == '_LD_CONTACT':
i = self._parse_contact(lines, i, network)
elif line == '_LD_AND' or line == '_LD_OR':
network.operators.append(line.replace('_LD_', ''))
i += 1
elif line.startswith('_FUNCTIONBLOCK'):
i = self._parse_function_block_detailed(lines, i, network)
else:
i += 1
self.networks.append(network)
return i
def _extract_comment(self, lines: List[str], start_idx: int) -> Tuple[int, str]:
"""Extrae comentario de la red"""
i = start_idx + 1
comment_lines = []
while i < len(lines):
line = lines[i].strip()
if line == '_END_COMMENT':
break
if line and not line.startswith('_'):
comment_lines.append(line)
i += 1
return i + 1, ' '.join(comment_lines)
def _parse_contact(self, lines: List[str], start_idx: int, network: LadNetwork) -> int:
"""Parse un contacto"""
i = start_idx + 1
if i < len(lines):
contact_name = lines[i].strip()
i += 1
# Verificar polaridad
negated = False
if i < len(lines) and lines[i].strip() == '_NEGATIV':
negated = True
contact = LadVariable(name=contact_name, negated=negated)
network.contacts.append(contact)
return i + 1
def _parse_assignment_block(self, lines: List[str], start_idx: int, network: LadNetwork) -> int:
"""Parse un bloque de asignación completo"""
i = start_idx + 1
assignment_data = {
'conditions': [],
'target_var': '',
'source_expr': '',
'operator': '',
'operands': [],
'function_name': '',
'fb_params': []
}
while i < len(lines):
line = lines[i].strip()
if line == '_NETWORK' or line == 'ENABLELIST_END':
break
elif line == '_LD_CONTACT':
i += 1
if i < len(lines):
contact_name = lines[i].strip()
i += 1
negated = False
if i < len(lines) and lines[i].strip() == '_NEGATIV':
negated = True
i += 1
assignment_data['conditions'].append(LadVariable(contact_name, negated))
elif line.startswith('_FUNCTIONBLOCK'):
# Parse function block dentro de asignación
i, fb_data = self._parse_functionblock_in_assignment(lines, i)
assignment_data.update(fb_data)
elif line.startswith('_OPERATOR'):
i, op_data = self._parse_operator_in_assignment(lines, i)
assignment_data.update(op_data)
elif line.startswith('_OUTPUT'):
# Siguiente información es sobre la salida
i = self._parse_output_info(lines, i, assignment_data)
elif not line.startswith('_') and line and 'ENABLELIST' not in line:
# Variable o expresión
if not assignment_data['target_var']:
assignment_data['source_expr'] = line
else:
i += 1
if assignment_data['target_var'] or assignment_data['function_name']:
network.assignments.append(assignment_data)
return i
def _parse_functionblock_in_assignment(self, lines: List[str], start_idx: int) -> Tuple[int, Dict]:
"""Parse function block dentro de asignación"""
i = start_idx + 1
fb_data = {'function_name': '', 'fb_params': []}
if i < len(lines):
fb_data['function_name'] = lines[i].strip()
i += 1
# Buscar parámetros
while i < len(lines) and not lines[i].strip().startswith('_OUTPUT'):
line = lines[i].strip()
if line.startswith('_OPERAND'):
i += 2 # Saltar _EXPRESSION y _POSITIV/_NEGATIV
if i < len(lines):
param = lines[i].strip()
fb_data['fb_params'].append(param)
i += 1
return i, fb_data
def _parse_operator_in_assignment(self, lines: List[str], start_idx: int) -> Tuple[int, Dict]:
"""Parse operador dentro de asignación"""
i = start_idx + 1
op_data = {'operator': '', 'operands': []}
# Buscar operandos y operador
while i < len(lines) and not lines[i].strip().startswith('_OUTPUT'):
line = lines[i].strip()
if line.startswith('_OPERAND'):
i += 2 # Saltar _EXPRESSION y _POSITIV/_NEGATIV
if i < len(lines):
operand = lines[i].strip()
op_data['operands'].append(operand)
elif line in ['ADD', 'SUB', 'MUL', 'DIV', 'AND', 'OR', 'LT', 'GT', 'EQ', 'SEL', 'MOVE']:
op_data['operator'] = line
i += 1
return i, op_data
def _parse_output_info(self, lines: List[str], start_idx: int, assignment_data: Dict) -> int:
"""Parse información de salida"""
i = start_idx + 1
# Saltar información de salida hasta encontrar la variable
while i < len(lines):
line = lines[i].strip()
if not line.startswith('_') and line and 'ENABLELIST' not in line:
assignment_data['target_var'] = line
break
i += 1
return i + 1
def _parse_function_block_detailed(self, lines: List[str], start_idx: int, network: LadNetwork) -> int:
"""Parse function block detallado"""
i = start_idx + 1
fb_data = {'name': '', 'params': [], 'outputs': []}
if i < len(lines):
fb_data['name'] = lines[i].strip()
i += 1
# Parse parámetros y salidas
while i < len(lines):
line = lines[i].strip()
if line.startswith('_OPERAND'):
i += 2
if i < len(lines):
param = lines[i].strip()
fb_data['params'].append(param)
elif line.startswith('_OUTPUT'):
i += 3 # Saltar información de salida
if i < len(lines):
output = lines[i].strip()
fb_data['outputs'].append(output)
elif line == '_NETWORK':
break
i += 1
network.function_blocks.append(fb_data)
return i
def convert_to_structured(self) -> str:
"""Convierte las redes a código pseudo estructurado"""
self.output_lines = [
"// Código pseudo estructurado generado desde LAD TwinCAT",
"// Compatible con IEC61131-3",
"PROGRAM Input_Converted",
""
]
for network in self.networks:
self._convert_network_to_structured(network)
self.output_lines.append("END_PROGRAM")
return '\n'.join(self.output_lines)
def _convert_network_to_structured(self, network: LadNetwork) -> None:
"""Convierte una red a código estructurado"""
self.output_lines.append(f" // Red {network.id}")
if network.comment:
self.output_lines.append(f" // {network.comment}")
# Procesar asignaciones de la red
for assignment in network.assignments:
self._convert_assignment_to_structured(assignment)
# Procesar function blocks independientes
for fb in network.function_blocks:
self._convert_function_block_to_structured(fb)
self.output_lines.append("")
def _convert_assignment_to_structured(self, assignment: Dict) -> None:
"""Convierte una asignación a código estructurado"""
conditions = assignment.get('conditions', [])
target_var = assignment.get('target_var', '')
source_expr = assignment.get('source_expr', '')
operator = assignment.get('operator', '')
operands = assignment.get('operands', [])
function_name = assignment.get('function_name', '')
fb_params = assignment.get('fb_params', [])
# Construir condiciones
condition_str = ""
if conditions:
condition_parts = []
for cond in conditions:
if cond.negated:
condition_parts.append(f"NOT {cond.name}")
else:
condition_parts.append(cond.name)
condition_str = " AND ".join(condition_parts)
# Construir la expresión de asignación
if function_name:
# Function block call
params_str = ", ".join(fb_params) if fb_params else ""
if target_var:
expr = f"{target_var} := {function_name}({params_str})"
else:
expr = f"{function_name}({params_str})"
elif operator and operands:
# Operación matemática/lógica
if len(operands) == 2:
if operator == "SEL":
expr = f"{target_var} := SEL({operands[0]}, {operands[1]}, condition)"
else:
expr = f"{target_var} := {operands[0]} {operator} {operands[1]}"
elif len(operands) == 1:
if operator == "MOVE":
expr = f"{target_var} := {operands[0]}"
else:
expr = f"{target_var} := {operator}({operands[0]})"
else:
expr = f"{target_var} := {operator}({', '.join(operands)})"
elif source_expr and target_var:
# Asignación simple
expr = f"{target_var} := {source_expr}"
else:
return # Sin información suficiente
# Generar código estructurado
if condition_str:
self.output_lines.append(f" IF {condition_str} THEN")
self.output_lines.append(f" {expr};")
self.output_lines.append(f" END_IF;")
else:
self.output_lines.append(f" {expr};")
def _convert_function_block_to_structured(self, fb: Dict) -> None:
"""Convierte un function block a código estructurado"""
name = fb.get('name', '')
params = fb.get('params', [])
if name:
params_str = ", ".join(params) if params else ""
self.output_lines.append(f" {name}({params_str});")
def save_to_file(self, output_path: str) -> None:
"""Guarda el código estructurado a un archivo"""
structured_code = self.convert_to_structured()
with open(output_path, 'w', encoding='utf-8') as f:
f.write(structured_code)
print(f"Código pseudo estructurado guardado en: {output_path}")
def main():
"""Función principal"""
import sys
if len(sys.argv) != 3:
print("Uso: python lad_to_pseudocode_converter_enhanced.py <archivo_entrada.EXP> <archivo_salida.txt>")
return
input_file = sys.argv[1]
output_file = sys.argv[2]
try:
converter = EnhancedLadConverter()
converter.parse_file(input_file)
converter.save_to_file(output_file)
print(f"Conversión completada exitosamente!")
print(f"Redes procesadas: {len(converter.networks)}")
# Mostrar estadísticas por red
for network in converter.networks[:5]: # Primeras 5 redes
print(f"Red {network.id}: {len(network.assignments)} asignaciones, {len(network.contacts)} contactos")
except Exception as e:
print(f"Error durante la conversión: {e}")
if __name__ == "__main__":
main()