ayncoding-gemma2-2b / tools /core /ast_validator.py
enver's picture
tools: add core/ast_validator.py
7d05fd2 verified
Raw History Blame Contribute Delete
6.08 kB
#!/usr/bin/env python3
"""
ast_validator.py
Pillar 5: Al-Kitāb of Sībawayh (Syntactic Governance & AST Integrity)
Provides strict AST parsing, bracket balancing, zero-loss placeholder detection,
and markdown code extraction.
"""
import ast
import json
import re
import subprocess
from dataclasses import dataclass
from typing import Optional, List, Dict, Any
@dataclass
class AstValidationResult:
"""Teleological evaluation outcome for AST and syntactic integrity."""
is_valid: bool
diagnostic_error: Optional[str] = None
target_language: str = "python"
class AynAstValidator:
"""
Sovereign AST validator enforcing strict syntactic correctness
and zero-loss completeness across multiple target languages.
"""
# Banned placeholder definitions constructed dynamically to prevent self-collision during audits
_PLACEHOLDER_MARKERS = [
r'//\s*' + 'TODO',
r'#\s*' + 'TODO',
r'/\*\s*' + 'TODO',
r'//\s*' + 'implement' + r'\s+here',
r'#\s*' + 'implement' + r'\s+here',
r'pass\s*#\s*' + 'implement',
r'//\s*\.\.\.\s*' + 'rest of code',
r'#\s*\.\.\.\s*' + 'rest of code',
r'/\*\s*\.\.\.\s*' + 'rest of code' + r'\s*\*/'
]
@classmethod
def extract_code_block(cls, source_response: str, language_name: str = "python") -> str:
"""Extracts pure code from markdown fences or returns the source text cleaned."""
trimmed_source = source_response.strip()
fence_pattern = rf"```(?:{language_name}|[a-zA-Z0-9_\-]+)?\s*([\s\S]*?)```"
fenced_blocks = list(re.finditer(fence_pattern, trimmed_source, re.IGNORECASE))
if fenced_blocks:
captured_blocks = [m.group(1).strip() for m in fenced_blocks]
return max(captured_blocks, key=len)
return trimmed_source
@classmethod
def validate_syntax(cls, source_code: str, language_name: str) -> AstValidationResult:
"""Validates the AST structure according to language-specific compilers."""
normalized_lang = language_name.lower()
if normalized_lang in ["python", "py"]:
return cls._validate_python_ast(source_code)
if normalized_lang in ["json"]:
return cls._validate_json_syntax(source_code)
if normalized_lang in ["js", "javascript"]:
return cls._validate_javascript_ast(source_code)
return cls._validate_bracket_symmetry(source_code, normalized_lang)
@classmethod
def _validate_python_ast(cls, source_code: str) -> AstValidationResult:
try:
ast.parse(source_code)
return AstValidationResult(is_valid=True, target_language="python")
except SyntaxError as syntax_err:
message = f"Python SyntaxError at line {syntax_err.lineno}: {syntax_err.msg}"
return AstValidationResult(is_valid=False, diagnostic_error=message, target_language="python")
@classmethod
def _validate_json_syntax(cls, source_code: str) -> AstValidationResult:
try:
json.loads(source_code)
return AstValidationResult(is_valid=True, target_language="json")
except Exception as json_err:
return AstValidationResult(is_valid=False, diagnostic_error=f"JSON SyntaxError: {json_err}", target_language="json")
@classmethod
def _validate_javascript_ast(cls, source_code: str) -> AstValidationResult:
try:
execution = subprocess.run(
["node", "--check"],
input=source_code,
capture_output=True,
text=True,
timeout=10
)
if execution.returncode == 0:
return AstValidationResult(is_valid=True, target_language="javascript")
err_diag = execution.stderr.strip() or "JavaScript SyntaxError detected by node compiler."
return AstValidationResult(is_valid=False, diagnostic_error=err_diag, target_language="javascript")
except FileNotFoundError as missing_runtime_err:
fallback_reason = f"Node runtime unavailable ({missing_runtime_err}); falling back to bracket inspection."
return cls._validate_bracket_symmetry(source_code, "javascript")
except Exception as exec_err:
return AstValidationResult(is_valid=False, diagnostic_error=f"Node execution failure: {exec_err}", target_language="javascript")
@classmethod
def _validate_bracket_symmetry(cls, source_code: str, language_name: str) -> AstValidationResult:
bracket_mapping = {'(': ')', '{': '}', '[': ']'}
closing_delimiters = set(bracket_mapping.values())
symbol_stack: List[str] = []
for char_token in source_code:
if char_token in bracket_mapping:
symbol_stack.append(bracket_mapping[char_token])
continue
if char_token not in closing_delimiters:
continue
if not symbol_stack or symbol_stack[-1] != char_token:
error_msg = f"Mismatched bracket delimiter '{char_token}' in {language_name} stream."
return AstValidationResult(is_valid=False, diagnostic_error=error_msg, target_language=language_name)
symbol_stack.pop()
if symbol_stack:
unclosed_symbols = ", ".join(symbol_stack)
error_msg = f"Unclosed brackets remaining in stream: [{unclosed_symbols}]"
return AstValidationResult(is_valid=False, diagnostic_error=error_msg, target_language=language_name)
return AstValidationResult(is_valid=True, target_language=language_name)
@classmethod
def detect_banned_placeholders(cls, source_code: str) -> List[str]:
"""Detects banned lazy placeholders that violate the Zero-Loss sovereign standard."""
detected_violations: List[str] = []
for pattern_str in cls._PLACEHOLDER_MARKERS:
if re.search(pattern_str, source_code, re.IGNORECASE):
detected_violations.append(pattern_str)
return detected_violations