#!/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 ") 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()