""" Validação prévia estruturada de código gerado. Verifica padrões básicos antes de executar ou chamar API novamente. """ import ast import re from typing import Tuple, List, Dict, Optional class CodeValidator: """Valida estrutura básica do código gerado""" def __init__(self): self.errors = [] self.warnings = [] def validate(self, code: str, task_description: Optional[str] = None) -> Tuple[bool, List[str], List[str]]: """ Valida código e retorna (is_valid, errors, warnings) Args: code: Código Python a ser validado task_description: Descrição da tarefa para validar quantidades Returns: (is_valid, errors, warnings) """ self.errors = [] self.warnings = [] # 1. Validação de sintaxe básica if not self._validate_syntax(code): return False, self.errors, self.warnings # 2. Validação de estrutura de classe self._validate_class_structure(code) # 3. Validação de imports self._validate_imports(code) # 4. Validação de padrões de código self._validate_code_patterns(code) # 5. Validação de quantidade (se task_description fornecido) if task_description: self._validate_quantity(code, task_description) # Se há erros críticos, código é inválido critical_errors = [e for e in self.errors if 'CRITICAL' in e or 'ERROR' in e] is_valid = len(critical_errors) == 0 return is_valid, self.errors, self.warnings def _validate_syntax(self, code: str) -> bool: """Valida sintaxe Python básica""" try: ast.parse(code) return True except SyntaxError as e: self.errors.append(f"CRITICAL: SyntaxError - {str(e)}") return False def _validate_class_structure(self, code: str): """Valida estrutura da classe Task""" tree = ast.parse(code) has_class = False has_init = False has_additional_reset = False for node in ast.walk(tree): if isinstance(node, ast.ClassDef): has_class = True # Verificar se herda de Task for base in node.bases: if isinstance(base, ast.Name) and base.id == 'Task': break else: self.errors.append("CRITICAL: Class must inherit from Task") # Verificar métodos for item in node.body: if isinstance(item, ast.FunctionDef): if item.name == '__init__': has_init = True # Verificar se chama additional_reset() for stmt in ast.walk(item): if isinstance(stmt, ast.Call): if isinstance(stmt.func, ast.Attribute): if stmt.func.attr == 'additional_reset': has_additional_reset = True if not has_class: self.errors.append("CRITICAL: No class definition found") if not has_init: self.errors.append("CRITICAL: No __init__ method found (not 'init')") if has_init and not has_additional_reset: self.warnings.append("WARNING: __init__ should call self.additional_reset()") def _validate_imports(self, code: str): """Valida imports duplicados""" import_lines = [] seen_imports = set() for line in code.split('\n'): stripped = line.strip() if stripped.startswith('import ') or stripped.startswith('from '): import_lines.append(stripped) # Normalizar para detectar duplicatas normalized = re.sub(r'\s+', ' ', stripped) if normalized in seen_imports: self.warnings.append(f"WARNING: Duplicate import: {stripped}") seen_imports.add(normalized) def _validate_code_patterns(self, code: str): """Valida padrões comuns de código""" # Verificar base_pose incorreto if re.search(r'base_pose\s*=\s*\([^)]*0\.\d+[^)]*\)\s*[^,\(]', code): # Padrão: base_pose = (0.5, 0, 0.02) sem rotação if not re.search(r'base_pose\s*=\s*\(\([^)]+\)\s*,\s*\([^)]+\)\)', code): self.errors.append("CRITICAL: base_pose must be ((x,y,z), (qx,qy,qz,qw)), not just (x,y,z)") # Verificar uso incorreto de utils.apply() # Deve retornar posição e adicionar rotação apply_patterns = re.findall(r'utils\.apply\([^)]+\)', code) for pattern in apply_patterns: # Verificar se está dentro de uma tupla com rotação context = code[max(0, code.find(pattern)-50):code.find(pattern)+len(pattern)+50] if 'base_pose[1]' not in context and '(0, 0, 0, 1)' not in context: self.warnings.append("WARNING: utils.apply() should be followed by rotation: (utils.apply(...), base_pose[1])") # Verificar p.getQuaternionFromEuler if 'p.getQuaternionFromEuler' in code: self.warnings.append("WARNING: Use (0, 0, 0, 1) instead of p.getQuaternionFromEuler()") # Verificar loops sem variável if re.search(r'for\s+in\s+range', code): self.errors.append("CRITICAL: for loop missing variable: use 'for i in range(n)'") def _validate_quantity(self, code: str, task_description: str): """Valida se a quantidade de objetos corresponde à descrição""" # Extrair número da descrição numbers = re.findall(r'\b(\d+)\s+(?:blocos?|blocks?|objetos?|objects?|esferas?|spheres?|cilindros?|cylinders?)\b', task_description.lower()) if not numbers: # Tentar padrões alternativos numbers = re.findall(r'\b(?:uma|um|one|a)\s+(?:fileira|row|linha|line)\s+de\s+(\d+)', task_description.lower()) if numbers: expected_count = int(numbers[0]) # Contar range() no código range_matches = re.findall(r'range\((\d+)\)', code) if range_matches: actual_count = max([int(m) for m in range_matches]) if actual_count != expected_count: self.errors.append( f"CRITICAL: Task asks for {expected_count} objects, but code creates {actual_count} " f"(found: range({actual_count}))" ) def validate_generated_code(code: str, task_description: Optional[str] = None) -> Tuple[bool, List[str], List[str]]: """ Função de conveniência para validar código Args: code: Código Python a ser validado task_description: Descrição da tarefa (opcional) Returns: (is_valid, errors, warnings) """ validator = CodeValidator() return validator.validate(code, task_description)