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)