enver commited on
Commit
96db1f1
·
verified ·
1 Parent(s): 1545ab5

tools: add core/ast_validator.py

Browse files
Files changed (1) hide show
  1. tools/core/ast_validator.py +140 -0
tools/core/ast_validator.py ADDED
@@ -0,0 +1,140 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ ast_validator.py
4
+
5
+ Pillar 5: Al-Kitāb of Sībawayh (Syntactic Governance & AST Integrity)
6
+ Provides strict AST parsing, bracket balancing, zero-loss placeholder detection,
7
+ and markdown code extraction.
8
+ """
9
+
10
+ import ast
11
+ import json
12
+ import re
13
+ import subprocess
14
+ from dataclasses import dataclass
15
+ from typing import Optional, List, Dict, Any
16
+
17
+
18
+ @dataclass
19
+ class AstValidationResult:
20
+ """Teleological evaluation outcome for AST and syntactic integrity."""
21
+ is_valid: bool
22
+ diagnostic_error: Optional[str] = None
23
+ target_language: str = "python"
24
+
25
+
26
+ class AynAstValidator:
27
+ """
28
+ Sovereign AST validator enforcing strict syntactic correctness
29
+ and zero-loss completeness across multiple target languages.
30
+ """
31
+
32
+ # Banned placeholder definitions constructed dynamically to prevent self-collision during audits
33
+ _PLACEHOLDER_MARKERS = [
34
+ r'//\s*' + 'TODO',
35
+ r'#\s*' + 'TODO',
36
+ r'/\*\s*' + 'TODO',
37
+ r'//\s*' + 'implement' + r'\s+here',
38
+ r'#\s*' + 'implement' + r'\s+here',
39
+ r'pass\s*#\s*' + 'implement',
40
+ r'//\s*\.\.\.\s*' + 'rest of code',
41
+ r'#\s*\.\.\.\s*' + 'rest of code',
42
+ r'/\*\s*\.\.\.\s*' + 'rest of code' + r'\s*\*/'
43
+ ]
44
+
45
+ @classmethod
46
+ def extract_code_block(cls, source_response: str, language_name: str = "python") -> str:
47
+ """Extracts pure code from markdown fences or returns the source text cleaned."""
48
+ trimmed_source = source_response.strip()
49
+ fence_pattern = rf"```(?:{language_name}|[a-zA-Z0-9_\-]+)?\s*([\s\S]*?)```"
50
+ fenced_blocks = list(re.finditer(fence_pattern, trimmed_source, re.IGNORECASE))
51
+ if fenced_blocks:
52
+ captured_blocks = [m.group(1).strip() for m in fenced_blocks]
53
+ return max(captured_blocks, key=len)
54
+ return trimmed_source
55
+
56
+ @classmethod
57
+ def validate_syntax(cls, source_code: str, language_name: str) -> AstValidationResult:
58
+ """Validates the AST structure according to language-specific compilers."""
59
+ normalized_lang = language_name.lower()
60
+
61
+ if normalized_lang in ["python", "py"]:
62
+ return cls._validate_python_ast(source_code)
63
+
64
+ if normalized_lang in ["json"]:
65
+ return cls._validate_json_syntax(source_code)
66
+
67
+ if normalized_lang in ["js", "javascript"]:
68
+ return cls._validate_javascript_ast(source_code)
69
+
70
+ return cls._validate_bracket_symmetry(source_code, normalized_lang)
71
+
72
+ @classmethod
73
+ def _validate_python_ast(cls, source_code: str) -> AstValidationResult:
74
+ try:
75
+ ast.parse(source_code)
76
+ return AstValidationResult(is_valid=True, target_language="python")
77
+ except SyntaxError as syntax_err:
78
+ message = f"Python SyntaxError at line {syntax_err.lineno}: {syntax_err.msg}"
79
+ return AstValidationResult(is_valid=False, diagnostic_error=message, target_language="python")
80
+
81
+ @classmethod
82
+ def _validate_json_syntax(cls, source_code: str) -> AstValidationResult:
83
+ try:
84
+ json.loads(source_code)
85
+ return AstValidationResult(is_valid=True, target_language="json")
86
+ except Exception as json_err:
87
+ return AstValidationResult(is_valid=False, diagnostic_error=f"JSON SyntaxError: {json_err}", target_language="json")
88
+
89
+ @classmethod
90
+ def _validate_javascript_ast(cls, source_code: str) -> AstValidationResult:
91
+ try:
92
+ execution = subprocess.run(
93
+ ["node", "--check"],
94
+ input=source_code,
95
+ capture_output=True,
96
+ text=True,
97
+ timeout=10
98
+ )
99
+ if execution.returncode == 0:
100
+ return AstValidationResult(is_valid=True, target_language="javascript")
101
+ err_diag = execution.stderr.strip() or "JavaScript SyntaxError detected by node compiler."
102
+ return AstValidationResult(is_valid=False, diagnostic_error=err_diag, target_language="javascript")
103
+ except FileNotFoundError as missing_runtime_err:
104
+ fallback_reason = f"Node runtime unavailable ({missing_runtime_err}); falling back to bracket inspection."
105
+ return cls._validate_bracket_symmetry(source_code, "javascript")
106
+ except Exception as exec_err:
107
+ return AstValidationResult(is_valid=False, diagnostic_error=f"Node execution failure: {exec_err}", target_language="javascript")
108
+
109
+ @classmethod
110
+ def _validate_bracket_symmetry(cls, source_code: str, language_name: str) -> AstValidationResult:
111
+ bracket_mapping = {'(': ')', '{': '}', '[': ']'}
112
+ closing_delimiters = set(bracket_mapping.values())
113
+ symbol_stack: List[str] = []
114
+
115
+ for char_token in source_code:
116
+ if char_token in bracket_mapping:
117
+ symbol_stack.append(bracket_mapping[char_token])
118
+ continue
119
+ if char_token not in closing_delimiters:
120
+ continue
121
+ if not symbol_stack or symbol_stack[-1] != char_token:
122
+ error_msg = f"Mismatched bracket delimiter '{char_token}' in {language_name} stream."
123
+ return AstValidationResult(is_valid=False, diagnostic_error=error_msg, target_language=language_name)
124
+ symbol_stack.pop()
125
+
126
+ if symbol_stack:
127
+ unclosed_symbols = ", ".join(symbol_stack)
128
+ error_msg = f"Unclosed brackets remaining in stream: [{unclosed_symbols}]"
129
+ return AstValidationResult(is_valid=False, diagnostic_error=error_msg, target_language=language_name)
130
+
131
+ return AstValidationResult(is_valid=True, target_language=language_name)
132
+
133
+ @classmethod
134
+ def detect_banned_placeholders(cls, source_code: str) -> List[str]:
135
+ """Detects banned lazy placeholders that violate the Zero-Loss sovereign standard."""
136
+ detected_violations: List[str] = []
137
+ for pattern_str in cls._PLACEHOLDER_MARKERS:
138
+ if re.search(pattern_str, source_code, re.IGNORECASE):
139
+ detected_violations.append(pattern_str)
140
+ return detected_violations