Spaces:
Sleeping
Sleeping
File size: 7,358 Bytes
13b4740 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 |
"""
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)
|