#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Convertidor simplificado de LAD TwinCAT a pseudocódigo estructurado """ import re class SimpleLadConverter: """Convertidor simplificado de LAD a código estructurado""" def __init__(self): self.networks = [] self.current_network_id = 0 def parse_file(self, file_path): """Parse el archivo LAD""" with open(file_path, 'r', encoding='utf-8', errors='ignore') as f: content = f.read() # Encontrar sección LAD lad_start = content.find('_LD_BODY') if lad_start == -1: print("No se encontró _LD_BODY") return # Extraer contenido LAD lines = content[lad_start:].split('\n') self._parse_networks(lines) def _parse_networks(self, lines): """Parse todas las redes""" i = 0 while i < len(lines): if lines[i].strip() == '_NETWORK': self.current_network_id += 1 i = self._parse_network(lines, i) else: i += 1 def _parse_network(self, lines, start_idx): """Parse una red individual con soporte mejorado para operadores LAD""" network = { 'id': self.current_network_id, 'comment': '', 'logic': None, 'target': '', 'function_blocks': [] } i = start_idx + 1 # Parse comentario if i < len(lines) and lines[i].strip() == '_COMMENT': i, comment = self._parse_comment(lines, i) network['comment'] = comment # Parse contenido de la red while i < len(lines): line = lines[i].strip() if line == '_NETWORK': break elif line == '_LD_ASSIGN': i += 1 # Parsear la lógica LAD después de _LD_ASSIGN i, logic = self._parse_lad_expression(lines, i) network['logic'] = logic elif line.startswith('_OUTPUT'): # Buscar variable de salida i += 1 while i < len(lines) and lines[i].strip().startswith('_'): i += 1 if i < len(lines) and lines[i].strip() and 'ENABLELIST' not in lines[i]: network['target'] = lines[i].strip() i += 1 else: i += 1 self.networks.append(network) return i def _parse_lad_expression(self, lines, start_idx): """Parse una expresión LAD recursivamente""" i = start_idx while i < len(lines): line = lines[i].strip() if line == '_LD_AND': return self._parse_and_expression(lines, i + 1) elif line == '_LD_OR': return self._parse_or_expression(lines, i + 1) elif line == '_LD_CONTACT': return self._parse_contact(lines, i + 1) elif line.startswith('_FUNCTIONBLOCK'): return self._parse_function_block(lines, i) elif line.startswith('_OUTPUT') or line == 'ENABLELIST : 0': break else: i += 1 return i, None def _parse_and_expression(self, lines, start_idx): """Parse una expresión AND""" i = start_idx operands = [] # Buscar operadores if i < len(lines) and lines[i].strip().startswith('_LD_OPERATOR'): # Extraer número de operandos operator_line = lines[i].strip() num_operands = int(operator_line.split(':')[-1].strip()) if ':' in operator_line else 2 i += 1 # Parse cada operando for _ in range(num_operands): i, operand = self._parse_lad_expression(lines, i) if operand: operands.append(operand) return i, {'type': 'AND', 'operands': operands} def _parse_or_expression(self, lines, start_idx): """Parse una expresión OR""" i = start_idx operands = [] # Buscar operadores if i < len(lines) and lines[i].strip().startswith('_LD_OPERATOR'): # Extraer número de operandos operator_line = lines[i].strip() num_operands = int(operator_line.split(':')[-1].strip()) if ':' in operator_line else 2 i += 1 # Parse cada operando for _ in range(num_operands): i, operand = self._parse_lad_expression(lines, i) if operand: operands.append(operand) return i, {'type': 'OR', 'operands': operands} def _parse_contact(self, lines, start_idx): """Parse un contacto LAD""" i = start_idx contact_name = "" negated = False # Obtener nombre del contacto if i < len(lines): contact_name = lines[i].strip() i += 1 # Verificar si hay expresión if i < len(lines) and lines[i].strip() == '_EXPRESSION': i += 1 # Verificar si está negado if i < len(lines): if lines[i].strip() == '_NEGATIV': negated = True i += 1 elif lines[i].strip() == '_POSITIV': i += 1 return i, {'type': 'CONTACT', 'name': contact_name, 'negated': negated} def _parse_function_block(self, lines, start_idx): """Parse un bloque de función""" i = start_idx + 1 fb_name = "" inputs = [] if i < len(lines): fb_name = lines[i].strip() i += 1 # Parse inputs del function block while i < len(lines) and not lines[i].strip().startswith('_OUTPUT'): line = lines[i].strip() if line.startswith('_OPERAND'): i += 2 # Saltar _EXPRESSION if i < len(lines): inputs.append(lines[i].strip()) i += 1 else: i += 1 return i, {'type': 'FUNCTION_BLOCK', 'name': fb_name, 'inputs': inputs} def _parse_comment(self, lines, start_idx): """Parse comentario""" 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 convert_to_structured(self): """Convertir a código estructurado""" output = [] output.append("// Código pseudo estructurado generado desde LAD TwinCAT") output.append("// Compatible con IEC61131-3") output.append("PROGRAM PumpControl_Converted") output.append("") for network in self.networks: output.append(f" // Red {network['id']}") if network['comment']: output.append(f" // {network['comment']}") if network['logic'] and network['target']: condition_str = self._convert_logic_to_string(network['logic']) if condition_str: output.append(f" IF {condition_str} THEN") output.append(f" {network['target']} := TRUE;") output.append(" ELSE") output.append(f" {network['target']} := FALSE;") output.append(" END_IF;") else: output.append(f" {network['target']} := TRUE; // Logic no reconocida") output.append("") output.append("END_PROGRAM") return '\n'.join(output) def _convert_logic_to_string(self, logic): """Convertir lógica LAD a string estructurado""" if not logic: return "" if logic['type'] == 'CONTACT': if logic['negated']: return f"NOT {logic['name']}" else: return logic['name'] elif logic['type'] == 'AND': operand_strings = [] for operand in logic['operands']: operand_str = self._convert_logic_to_string(operand) if operand_str: operand_strings.append(operand_str) if len(operand_strings) > 1: return "(" + " AND ".join(operand_strings) + ")" elif len(operand_strings) == 1: return operand_strings[0] else: return "" elif logic['type'] == 'OR': operand_strings = [] for operand in logic['operands']: operand_str = self._convert_logic_to_string(operand) if operand_str: operand_strings.append(operand_str) if len(operand_strings) > 1: return "(" + " OR ".join(operand_strings) + ")" elif len(operand_strings) == 1: return operand_strings[0] else: return "" elif logic['type'] == 'FUNCTION_BLOCK': inputs_str = ", ".join(logic['inputs']) if logic['inputs'] else "" return f"{logic['name']}({inputs_str})" return "" def save_to_file(self, output_path): """Guardar código estructurado""" structured_code = self.convert_to_structured() with open(output_path, 'w', encoding='utf-8') as f: f.write(structured_code) print(f"Código guardado en: {output_path}") return structured_code def print_debug_info(self): """Mostrar información de debug sobre los networks parseados""" print(f"\n=== DEBUG INFO - {len(self.networks)} networks encontrados ===") for network in self.networks: print(f"\nRed {network['id']}:") if network['comment']: print(f" Comentario: {network['comment']}") print(f" Target: {network['target']}") if network['logic']: print(f" Lógica: {self._debug_logic_string(network['logic'])}") condition_str = self._convert_logic_to_string(network['logic']) print(f" Condición: {condition_str}") else: print(" Sin lógica") def _debug_logic_string(self, logic, indent=0): """Crear string de debug para la lógica""" if not logic: return "None" prefix = " " * indent if logic['type'] == 'CONTACT': neg_str = " (NEGADO)" if logic['negated'] else "" return f"{prefix}CONTACT: {logic['name']}{neg_str}" elif logic['type'] == 'AND': result = f"{prefix}AND:\n" for operand in logic['operands']: result += self._debug_logic_string(operand, indent + 1) + "\n" return result.rstrip() elif logic['type'] == 'OR': result = f"{prefix}OR:\n" for operand in logic['operands']: result += self._debug_logic_string(operand, indent + 1) + "\n" return result.rstrip() elif logic['type'] == 'FUNCTION_BLOCK': return f"{prefix}FUNCTION_BLOCK: {logic['name']} inputs: {logic['inputs']}" return f"{prefix}UNKNOWN: {logic}" def main(): """Función principal""" converter = SimpleLadConverter() try: print("=== Convertidor LAD Mejorado ===") print("Parseando archivo _PUMPCONTROL.EXP...") converter.parse_file(".example/_PUMPCONTROL.EXP") print(f"Redes encontradas: {len(converter.networks)}") # Mostrar información de debug converter.print_debug_info() # Convertir y guardar print("\nGenerando código estructurado...") structured_code = converter.save_to_file("pump_control_output.txt") # Mostrar el código generado lines = structured_code.split('\n') print(f"\nCódigo generado ({len(lines)} líneas):") for i, line in enumerate(lines): print(f"{i+1:3d}: {line}") print(f"\n✓ Conversión completada!") except Exception as e: print(f"Error: {e}") import traceback traceback.print_exc() if __name__ == "__main__": main()