369 lines
13 KiB
Python
369 lines
13 KiB
Python
#!/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() |