#!/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