391 lines
13 KiB
Python
391 lines
13 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
"""
|
|
Convertidor 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
|
|
from dataclasses import dataclass
|
|
from enum import Enum
|
|
|
|
|
|
class ElementType(Enum):
|
|
CONTACT = "CONTACT"
|
|
COIL = "COIL"
|
|
FUNCTION_BLOCK = "FUNCTION_BLOCK"
|
|
OPERATOR = "OPERATOR"
|
|
EXPRESSION = "EXPRESSION"
|
|
ASSIGN = "ASSIGN"
|
|
|
|
|
|
@dataclass
|
|
class LadderElement:
|
|
"""Representa un elemento de la escalera LAD"""
|
|
element_type: ElementType
|
|
name: str
|
|
expression: str = ""
|
|
positive: bool = True
|
|
enabled: bool = True
|
|
parameters: List[str] = None
|
|
outputs: List[str] = None
|
|
|
|
def __post_init__(self):
|
|
if self.parameters is None:
|
|
self.parameters = []
|
|
if self.outputs is None:
|
|
self.outputs = []
|
|
|
|
|
|
@dataclass
|
|
class LadderNetwork:
|
|
"""Representa una red completa de LAD"""
|
|
network_id: int
|
|
comment: str = ""
|
|
elements: List[LadderElement] = None
|
|
|
|
def __post_init__(self):
|
|
if self.elements is None:
|
|
self.elements = []
|
|
|
|
|
|
class LadToStructuredConverter:
|
|
"""Conversor principal de LAD a código estructurado"""
|
|
|
|
def __init__(self):
|
|
self.networks: List[LadderNetwork] = []
|
|
self.current_network: Optional[LadderNetwork] = None
|
|
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")
|
|
|
|
lad_content = content[lad_start:]
|
|
self._parse_lad_content(lad_content)
|
|
|
|
def _parse_lad_content(self, content: str) -> None:
|
|
"""Parse el contenido LAD línea por línea"""
|
|
lines = content.split('\n')
|
|
i = 0
|
|
|
|
while i < len(lines):
|
|
line = lines[i].strip()
|
|
|
|
if line == '_NETWORK':
|
|
i = self._parse_network(lines, i)
|
|
else:
|
|
i += 1
|
|
|
|
def _parse_network(self, lines: List[str], start_idx: int) -> int:
|
|
"""Parse una red individual"""
|
|
network_id = len(self.networks) + 1
|
|
network = LadderNetwork(network_id=network_id)
|
|
self.current_network = network
|
|
|
|
i = start_idx + 1
|
|
|
|
while i < len(lines) and not lines[i].strip().startswith('_NETWORK'):
|
|
line = lines[i].strip()
|
|
|
|
if line == '_COMMENT':
|
|
i, comment = self._parse_comment(lines, i)
|
|
network.comment = comment
|
|
elif line == '_LD_ASSIGN':
|
|
i = self._parse_assignment(lines, i)
|
|
elif line == '_LD_CONTACT':
|
|
i = self._parse_contact(lines, i)
|
|
elif line == '_LD_AND' or line == '_LD_OR':
|
|
i = self._parse_logic_operation(lines, i, line)
|
|
elif line.startswith('_FUNCTIONBLOCK'):
|
|
i = self._parse_function_block(lines, i)
|
|
elif line.startswith('_OPERATOR'):
|
|
i = self._parse_operator(lines, i)
|
|
else:
|
|
i += 1
|
|
|
|
self.networks.append(network)
|
|
return i
|
|
|
|
def _parse_comment(self, lines: List[str], start_idx: int) -> tuple[int, str]:
|
|
"""Parse un 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, '\n'.join(comment_lines)
|
|
|
|
def _parse_assignment(self, lines: List[str], start_idx: int) -> int:
|
|
"""Parse una asignación"""
|
|
i = start_idx + 1
|
|
|
|
# Buscar el elemento a asignar
|
|
target_var = ""
|
|
source_expr = ""
|
|
|
|
while i < len(lines):
|
|
line = lines[i].strip()
|
|
|
|
if line == '_EMPTY':
|
|
i += 1
|
|
continue
|
|
elif line == '_EXPRESSION':
|
|
i += 1
|
|
continue
|
|
elif line == '_POSITIV' or line == '_NEGATIV':
|
|
i += 1
|
|
continue
|
|
elif line.startswith('_OUTPUT'):
|
|
# Siguiente línea debería ser la variable de salida
|
|
i += 2 # Saltar _POSITIV/_NEGATIV
|
|
i += 1 # Saltar _NO_SET o _SET
|
|
if i < len(lines):
|
|
target_var = lines[i].strip()
|
|
break
|
|
elif line == 'ENABLELIST_END':
|
|
break
|
|
elif not line.startswith('_') and not line.startswith('ENABLELIST'):
|
|
if not target_var:
|
|
source_expr = line
|
|
i += 1
|
|
|
|
if target_var:
|
|
element = LadderElement(
|
|
element_type=ElementType.ASSIGN,
|
|
name=target_var,
|
|
expression=source_expr
|
|
)
|
|
self.current_network.elements.append(element)
|
|
|
|
return i + 1
|
|
|
|
def _parse_contact(self, lines: List[str], start_idx: int) -> int:
|
|
"""Parse un contacto"""
|
|
i = start_idx + 1
|
|
contact_name = ""
|
|
is_positive = True
|
|
|
|
if i < len(lines):
|
|
contact_name = lines[i].strip()
|
|
i += 1
|
|
|
|
if i < len(lines):
|
|
polarity = lines[i].strip()
|
|
is_positive = polarity == '_POSITIV'
|
|
i += 1
|
|
|
|
element = LadderElement(
|
|
element_type=ElementType.CONTACT,
|
|
name=contact_name,
|
|
positive=is_positive
|
|
)
|
|
self.current_network.elements.append(element)
|
|
|
|
return i
|
|
|
|
def _parse_logic_operation(self, lines: List[str], start_idx: int, operation: str) -> int:
|
|
"""Parse operaciones lógicas AND/OR"""
|
|
i = start_idx + 1
|
|
|
|
# Extraer el tipo de operación
|
|
op_type = operation.replace('_LD_', '')
|
|
|
|
element = LadderElement(
|
|
element_type=ElementType.OPERATOR,
|
|
name=op_type
|
|
)
|
|
self.current_network.elements.append(element)
|
|
|
|
return i
|
|
|
|
def _parse_function_block(self, lines: List[str], start_idx: int) -> int:
|
|
"""Parse un bloque de función"""
|
|
i = start_idx + 1
|
|
fb_name = ""
|
|
parameters = []
|
|
|
|
if i < len(lines):
|
|
fb_name = lines[i].strip()
|
|
i += 1
|
|
|
|
# Buscar parámetros
|
|
while i < len(lines):
|
|
line = lines[i].strip()
|
|
if line.startswith('_OPERAND'):
|
|
i += 2 # Saltar _EXPRESSION y _POSITIV
|
|
if i < len(lines):
|
|
param = lines[i].strip()
|
|
parameters.append(param)
|
|
elif line == '_OUTPUTS':
|
|
break
|
|
i += 1
|
|
|
|
element = LadderElement(
|
|
element_type=ElementType.FUNCTION_BLOCK,
|
|
name=fb_name,
|
|
parameters=parameters
|
|
)
|
|
self.current_network.elements.append(element)
|
|
|
|
return i
|
|
|
|
def _parse_operator(self, lines: List[str], start_idx: int) -> int:
|
|
"""Parse un operador"""
|
|
i = start_idx + 1
|
|
operator_name = ""
|
|
operands = []
|
|
|
|
while i < len(lines):
|
|
line = lines[i].strip()
|
|
|
|
if line.startswith('_OPERAND'):
|
|
i += 2 # Saltar _EXPRESSION y _POSITIV
|
|
if i < len(lines):
|
|
operand = lines[i].strip()
|
|
operands.append(operand)
|
|
elif line.startswith('_EXPRESSION') and i + 1 < len(lines):
|
|
next_line = lines[i + 1].strip()
|
|
if not next_line.startswith('_'):
|
|
operator_name = next_line
|
|
i += 1
|
|
elif line == '_OUTPUTS':
|
|
break
|
|
i += 1
|
|
|
|
element = LadderElement(
|
|
element_type=ElementType.OPERATOR,
|
|
name=operator_name,
|
|
parameters=operands
|
|
)
|
|
self.current_network.elements.append(element)
|
|
|
|
return i
|
|
|
|
def convert_to_structured(self) -> str:
|
|
"""Convierte las redes LAD a código pseudo estructurado"""
|
|
self.output_lines = []
|
|
|
|
self.output_lines.append("// Código pseudo estructurado generado desde LAD")
|
|
self.output_lines.append("// Compatible con IEC61131-3")
|
|
self.output_lines.append("")
|
|
|
|
for network in self.networks:
|
|
self._convert_network_to_structured(network)
|
|
self.output_lines.append("")
|
|
|
|
return '\n'.join(self.output_lines)
|
|
|
|
def _convert_network_to_structured(self, network: LadderNetwork) -> None:
|
|
"""Convierte una red a código estructurado"""
|
|
self.output_lines.append(f"// Red {network.network_id}")
|
|
|
|
if network.comment:
|
|
comment_lines = network.comment.split('\n')
|
|
for line in comment_lines:
|
|
if line.strip():
|
|
self.output_lines.append(f"// {line.strip()}")
|
|
|
|
# Procesar elementos de la red
|
|
conditions = []
|
|
assignments = []
|
|
|
|
for element in network.elements:
|
|
if element.element_type == ElementType.CONTACT:
|
|
condition = element.name
|
|
if not element.positive:
|
|
condition = f"NOT {condition}"
|
|
conditions.append(condition)
|
|
|
|
elif element.element_type == ElementType.ASSIGN:
|
|
if conditions:
|
|
condition_str = " AND ".join(conditions)
|
|
self.output_lines.append(f"IF {condition_str} THEN")
|
|
self.output_lines.append(f" {element.name} := {element.expression};")
|
|
self.output_lines.append("END_IF;")
|
|
else:
|
|
self.output_lines.append(f"{element.name} := {element.expression};")
|
|
|
|
elif element.element_type == ElementType.FUNCTION_BLOCK:
|
|
params_str = ", ".join(element.parameters) if element.parameters else ""
|
|
if conditions:
|
|
condition_str = " AND ".join(conditions)
|
|
self.output_lines.append(f"IF {condition_str} THEN")
|
|
self.output_lines.append(f" {element.name}({params_str});")
|
|
self.output_lines.append("END_IF;")
|
|
else:
|
|
self.output_lines.append(f"{element.name}({params_str});")
|
|
|
|
elif element.element_type == ElementType.OPERATOR:
|
|
if element.name == "AND":
|
|
# AND lógico - ya manejado en condiciones
|
|
pass
|
|
elif element.name == "OR":
|
|
# OR lógico - cambiar estrategia de condiciones
|
|
if len(conditions) > 1:
|
|
last_condition = conditions.pop()
|
|
conditions[-1] = f"({conditions[-1]} OR {last_condition})"
|
|
else:
|
|
# Otros operadores matemáticos
|
|
if element.parameters and len(element.parameters) >= 2:
|
|
expr = f"{element.parameters[0]} {element.name} {element.parameters[1]}"
|
|
if conditions:
|
|
condition_str = " AND ".join(conditions)
|
|
self.output_lines.append(f"IF {condition_str} THEN")
|
|
self.output_lines.append(f" // Resultado := {expr};")
|
|
self.output_lines.append("END_IF;")
|
|
else:
|
|
self.output_lines.append(f"// Resultado := {expr};")
|
|
|
|
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.py <archivo_entrada.EXP> <archivo_salida.txt>")
|
|
sys.exit(1)
|
|
|
|
input_file = sys.argv[1]
|
|
output_file = sys.argv[2]
|
|
|
|
try:
|
|
converter = LadToStructuredConverter()
|
|
converter.parse_file(input_file)
|
|
converter.save_to_file(output_file)
|
|
|
|
print(f"Conversión completada exitosamente!")
|
|
print(f"Redes procesadas: {len(converter.networks)}")
|
|
|
|
except Exception as e:
|
|
print(f"Error durante la conversión: {e}")
|
|
sys.exit(1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main() |