270 lines
11 KiB
Python
270 lines
11 KiB
Python
"""
|
|
Bracket Parser - Transformador de sintaxis con corchetes y detección contextual de ecuaciones
|
|
"""
|
|
import ast
|
|
import re
|
|
from typing import Tuple, Optional
|
|
|
|
|
|
class BracketParser:
|
|
"""Parser que convierte sintaxis con corchetes y detecta ecuaciones contextualmente"""
|
|
|
|
# Clases que soportan sintaxis con corchetes
|
|
BRACKET_CLASSES = {'IP4', 'Hex', 'Bin', 'Date', 'Dec', 'Chr'}
|
|
|
|
# Operadores de comparación que pueden formar ecuaciones
|
|
EQUATION_OPERATORS = {'==', '!=', '<', '<=', '>', '>=', '='}
|
|
|
|
def __init__(self):
|
|
self.debug = False
|
|
|
|
def parse_line(self, code_line: str) -> Tuple[str, str]:
|
|
"""
|
|
Parsea una línea de código aplicando todas las transformaciones
|
|
|
|
Returns:
|
|
(transformed_code, parse_info): Código transformado e información de parsing
|
|
"""
|
|
original_line = code_line.strip()
|
|
if not original_line or original_line.startswith('#'):
|
|
return code_line, "comment"
|
|
|
|
try:
|
|
# 1. Detectar y transformar atajo solve
|
|
transformed_line, has_solve_shortcut = self._transform_solve_shortcut(original_line)
|
|
if has_solve_shortcut:
|
|
return transformed_line, "solve_shortcut"
|
|
|
|
# 2. Detectar asignaciones de variables
|
|
if self._is_assignment(original_line):
|
|
return self._transform_assignment(original_line), "assignment"
|
|
|
|
# 3. Detectar ecuaciones standalone
|
|
if self._is_standalone_equation(original_line):
|
|
return f'_add_equation("{original_line}")', "equation"
|
|
|
|
# 4. Transformar sintaxis con corchetes
|
|
transformed_line = self._transform_brackets(original_line)
|
|
|
|
# 5. Si no hay transformaciones, devolver original
|
|
if transformed_line == original_line:
|
|
return original_line, "expression"
|
|
else:
|
|
return transformed_line, "bracket_transform"
|
|
|
|
except Exception as e:
|
|
if self.debug:
|
|
print(f"Error parsing line '{original_line}': {e}")
|
|
return code_line, "parse_error"
|
|
|
|
def _transform_solve_shortcut(self, line: str) -> Tuple[str, bool]:
|
|
"""
|
|
Transforma 'variable=?' en 'solve(variable)'
|
|
|
|
Returns:
|
|
(transformed_line, was_transformed)
|
|
"""
|
|
# Pattern: variable_name = ?
|
|
pattern = r'^([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*\?$'
|
|
match = re.match(pattern, line.strip())
|
|
|
|
if match:
|
|
var_name = match.group(1)
|
|
return f'solve({var_name})', True
|
|
|
|
return line, False
|
|
|
|
def _is_assignment(self, line: str) -> bool:
|
|
"""Detecta si una línea es una asignación de variable"""
|
|
try:
|
|
# Pattern: variable = expresión (que no sea ecuación)
|
|
if '=' in line and not any(op in line for op in ['==', '!=', '<=', '>=']):
|
|
# Verificar que sea una asignación válida de Python
|
|
parts = line.split('=', 1)
|
|
if len(parts) == 2:
|
|
var_part = parts[0].strip()
|
|
# Verificar que la parte izquierda sea un identificador válido
|
|
if re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', var_part):
|
|
return True
|
|
return False
|
|
except:
|
|
return False
|
|
|
|
def _transform_assignment(self, line: str) -> str:
|
|
"""Transforma asignación a llamada de función especial"""
|
|
parts = line.split('=', 1)
|
|
var_name = parts[0].strip()
|
|
expression = parts[1].strip()
|
|
|
|
# Transformar corchetes en la expresión
|
|
expression = self._transform_brackets(expression)
|
|
|
|
return f'_assign_variable("{var_name}", {expression})'
|
|
|
|
def _is_standalone_equation(self, line: str) -> bool:
|
|
"""
|
|
Determina si una línea es una ecuación standalone usando análisis AST
|
|
"""
|
|
try:
|
|
tree = ast.parse(line.strip())
|
|
|
|
if not tree.body:
|
|
return False
|
|
|
|
node = tree.body[0]
|
|
|
|
# Solo consideramos expresiones (no asignaciones)
|
|
if not isinstance(node, ast.Expr):
|
|
return False
|
|
|
|
# Verificar si es una comparación con operadores de ecuación
|
|
if isinstance(node.value, ast.Compare):
|
|
# Verificar que use operadores de ecuación
|
|
for op in node.value.ops:
|
|
if isinstance(op, (ast.Eq, ast.NotEq, ast.Lt, ast.LtE, ast.Gt, ast.GtE)):
|
|
return True
|
|
|
|
# Verificar si contiene un '=' que no sea parte de una asignación
|
|
# Esto es para casos como "x + 2 = 5" que no parsea como Compare
|
|
if '=' in line and not any(op in line for op in ['==', '!=', '<=', '>=']):
|
|
# Es un '=' simple, puede ser una ecuación
|
|
return True
|
|
|
|
return False
|
|
|
|
except SyntaxError:
|
|
# Si hay error de sintaxis, puede ser una ecuación mal formada
|
|
# como "x + 2 = 5" que Python no puede parsear
|
|
if '=' in line:
|
|
return True
|
|
return False
|
|
except Exception:
|
|
return False
|
|
|
|
def _transform_brackets(self, line: str) -> str:
|
|
"""
|
|
Transforma sintaxis Class[args] → Class("args") y maneja métodos
|
|
"""
|
|
# Pattern principal: ClassName[contenido]
|
|
pattern = r'(\b(?:' + '|'.join(self.BRACKET_CLASSES) + r')\b)\[([^\]]*)\]'
|
|
|
|
def replace_match(match):
|
|
class_name = match.group(1)
|
|
args_content = match.group(2).strip()
|
|
|
|
if not args_content:
|
|
# Caso: Class[] → Class()
|
|
return f'{class_name}()'
|
|
else:
|
|
# Caso: Class[args] → Class("args")
|
|
# Escapar comillas dobles en el contenido
|
|
escaped_content = args_content.replace('"', '\\"')
|
|
return f'{class_name}("{escaped_content}")'
|
|
|
|
# Aplicar transformación repetidamente hasta que no haya más cambios
|
|
transformed = line
|
|
while True:
|
|
new_transformed = re.sub(pattern, replace_match, transformed)
|
|
if new_transformed == transformed:
|
|
break
|
|
transformed = new_transformed
|
|
|
|
# Transformar corchetes vacíos en métodos: .método[] → .método()
|
|
method_pattern = r'\.([a-zA-Z_][a-zA-Z0-9_]*)\[\]'
|
|
transformed = re.sub(method_pattern, r'.\1()', transformed)
|
|
|
|
return transformed
|
|
|
|
|
|
class EquationDetector:
|
|
"""Detector específico para ecuaciones con análisis AST avanzado"""
|
|
|
|
@staticmethod
|
|
def is_equation_in_context(code: str, context: str = "standalone") -> bool:
|
|
"""
|
|
Determina si el código contiene una ecuación considerando el contexto
|
|
|
|
Args:
|
|
code: Código a analizar
|
|
context: Contexto ("standalone", "function_arg", "assignment")
|
|
"""
|
|
try:
|
|
tree = ast.parse(code.strip())
|
|
|
|
for node in ast.walk(tree):
|
|
if isinstance(node, ast.Compare):
|
|
# Verificar operadores de ecuación
|
|
for op in node.ops:
|
|
if isinstance(op, (ast.Eq, ast.NotEq, ast.Lt, ast.LtE, ast.Gt, ast.GtE)):
|
|
if context == "standalone":
|
|
# En contexto standalone, es una ecuación
|
|
return True
|
|
elif context == "function_arg":
|
|
# En argumentos de función, generalmente NO es ecuación
|
|
return False
|
|
|
|
# Verificar '=' simple (no comparación)
|
|
if '=' in code and context == "standalone":
|
|
# Verificar que no sea asignación Python válida
|
|
try:
|
|
ast.parse(code.strip())
|
|
return False # Es sintaxis Python válida
|
|
except SyntaxError:
|
|
return True # Puede ser ecuación mal formada para Python
|
|
|
|
return False
|
|
|
|
except Exception:
|
|
return False
|
|
|
|
|
|
# Funciones de utilidad para testing
|
|
def test_bracket_parser():
|
|
"""Función de testing para el bracket parser"""
|
|
parser = BracketParser()
|
|
parser.debug = True
|
|
|
|
test_cases = [
|
|
# Sintaxis con corchetes
|
|
("Hex[FF]", 'Hex("FF")', "bracket_transform"),
|
|
("IP4[192.168.1.1/24]", 'IP4("192.168.1.1/24")', "bracket_transform"),
|
|
("IP4[192.168.1.1/24].NetworkAddress[]", 'IP4("192.168.1.1/24").NetworkAddress()', "bracket_transform"),
|
|
("Bin[1010]", 'Bin("1010")', "bracket_transform"),
|
|
|
|
# Atajos solve
|
|
("x=?", "solve(x)", "solve_shortcut"),
|
|
("variable_name=?", "solve(variable_name)", "solve_shortcut"),
|
|
|
|
# Asignaciones
|
|
("z = 5", '_assign_variable("z", 5)', "assignment"),
|
|
("w = z**2 + 3", '_assign_variable("w", z**2 + 3)', "assignment"),
|
|
("result = Hex[FF]", '_assign_variable("result", Hex("FF"))', "assignment"),
|
|
|
|
# Ecuaciones standalone
|
|
("x + 2 = 5", '_add_equation("x + 2 = 5")', "equation"),
|
|
("3*a + b = 10", '_add_equation("3*a + b = 10")', "equation"),
|
|
("x > 5", "x > 5", "expression"), # Comparación válida de Python
|
|
("a == b", "a == b", "expression"), # Comparación válida de Python
|
|
|
|
# NO ecuaciones
|
|
("result = solve(x + 2, x)", '_assign_variable("result", solve(x + 2, x))', "assignment"), # Asignación Python
|
|
("2 + 3", "2 + 3", "expression"), # Expresión simple
|
|
("sin(pi/2)", "sin(pi/2)", "expression"), # Función
|
|
|
|
# Expresiones normales
|
|
("x + 2*y", "x + 2*y", "expression"),
|
|
("diff(x**2, x)", "diff(x**2, x)", "expression"),
|
|
]
|
|
|
|
print("=== Test Bracket Parser ===")
|
|
for test_input, expected_result, expected_info in test_cases:
|
|
result, info = parser.parse_line(test_input)
|
|
status = "✅" if result == expected_result and info == expected_info else "❌"
|
|
print(f"{status} '{test_input}' → '{result}' ({info})")
|
|
if result != expected_result or info != expected_info:
|
|
print(f" Esperado: '{expected_result}' ({expected_info})")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
test_bracket_parser()
|