#!/usr/bin/env python3 """Audit issue 82's native grammar, parser-table, and recovery boundary.""" from __future__ import annotations import json import re from pathlib import Path import generate_api_shims ROOT = Path(__file__).resolve().parents[1] SOURCE = ROOT / "crates" / "metacrate-lsl-tools" / "src" / "parser.rs" GENERATED = ROOT / "crates" / "metacrate-lsl-tools" / "src" / "generated.rs" TESTS = ROOT / "crates" / "metacrate-lsl-tools" / "tests" / "parser_compat.rs" EXTENSION_TESTS = ROOT / "tests" / "compat" / "tests" / "extension_shims.rs" DOC = ROOT / "crates" / "metacrate-lsl-tools" / "README.md" WORKFLOW = ROOT / ".gitea" / "workflows" / "ci.yml" CATALOG = ROOT / "api" / "public-api.json" STUB_RE = re.compile( r"\b(?:not_implemented|unimplemented_api)\b|\b(?:todo|unimplemented)!\s*\(" ) TYPES = { "T:LibreMetaverse.LslTools.CSymbol": "crate::parser::CSymbol", "T:LibreMetaverse.LslTools.CSymbol.SymType": "crate::parser::CSymbolSymType", "T:LibreMetaverse.LslTools.Error": "crate::parser::LslError", "T:LibreMetaverse.LslTools.Literal": "crate::parser::Literal", "T:LibreMetaverse.LslTools.ParseStackEntry": "crate::parser::ParseStackEntry", "T:LibreMetaverse.LslTools.ParseState": "crate::parser::ParseState", "T:LibreMetaverse.LslTools.Parser": "crate::parser::Parser", "T:LibreMetaverse.LslTools.ParserAction": "crate::parser::ParserAction", "T:LibreMetaverse.LslTools.ParserEntry": "crate::parser::ParserEntry", "T:LibreMetaverse.LslTools.ParserOldAction": "crate::parser::ParserOldAction", "T:LibreMetaverse.LslTools.ParserReduce": "crate::parser::ParserReduce", "T:LibreMetaverse.LslTools.ParserShift": "crate::parser::ParserShift", "T:LibreMetaverse.LslTools.ParserSimpleAction": "crate::parser::ParserSimpleAction", "T:LibreMetaverse.LslTools.ParsingInfo": "crate::parser::ParsingInfo", "T:LibreMetaverse.LslTools.Precedence": "crate::parser::Precedence", "T:LibreMetaverse.LslTools.Precedence.PrecType": "crate::parser::PrecedencePrecType", "T:LibreMetaverse.LslTools.ProdItem": "crate::parser::ProdItem", "T:LibreMetaverse.LslTools.Production": "crate::parser::Production", "T:LibreMetaverse.LslTools.SymbolSet": "crate::parser::SymbolSet", "T:LibreMetaverse.LslTools.Transition": "crate::parser::Transition", "T:LibreMetaverse.LslTools.YyParser": "crate::parser::YyParser", "T:LibreMetaverse.LslTools.recoveredError": "crate::parser::RecoveredError", } def require_markers(path: Path, markers: tuple[str, ...]) -> None: text = path.read_text() missing = [marker for marker in markers if marker not in text] if missing: raise SystemExit(f"{path.name}: audit evidence missing: " + ", ".join(missing)) def catalog_member_count() -> int: catalog = json.loads(CATALOG.read_text()) assembly = next( value for value in catalog["assemblies"] if value["identity"]["name"] == "LibreMetaverse.LslTools" ) return sum( len(api_type["members"]) for api_type in assembly["types"] if api_type["doc_id"] in TYPES ) def generated_type_block(text: str, doc_id: str) -> str: marker = f"/// C# type: `{doc_id}`." start = text.find(marker) if start < 0: raise SystemExit(f"generated declaration is missing for {doc_id}") next_type = text.find("/// C# type:", start + len(marker)) return text[start:] if next_type < 0 else text[start:next_type] def main() -> None: for api_type, declaration in TYPES.items(): if generate_api_shims.NATIVE_TYPES.get(api_type) != declaration: raise SystemExit(f"issue 82 native type mapping is missing for {api_type}") if catalog_member_count() != 215: raise SystemExit("issue 82 expected 215 mapped parser members") source = SOURCE.read_text() if STUB_RE.search(source): raise SystemExit("issue 82 owned Rust stubs remain in parser.rs") if re.search(r"unsafe\s*\{|unsafe\s+impl|target_os\s*=\s*\"macos\"", source): raise SystemExit("parser.rs contains an unsafe or macOS-only implementation") generated = GENERATED.read_text() for api_type in TYPES: if STUB_RE.search(generated_type_block(generated, api_type)): raise SystemExit(f"issue 82 owned generated stubs remain for {api_type}") require_markers(SOURCE, ( "pub struct Grammar", "pub struct GrammarProduction", "struct ParserMachine", "fn first_sets", "fn lalr_lookaheads", "fn resolve_shift_reduce", "TableAction::Reject", "pub struct ParserConflict", "MAX_PARSER_STATES", "MAX_PARSER_STACK", "MAX_PARSER_STEPS", "MAX_RECOVERY_ERRORS", "pub struct ParseTree", "pub struct CSymbol", "pub struct SymbolSet", "pub struct Production", "pub struct Precedence", "pub struct ParseState", "pub struct Transition", "pub struct YyParser", "pub struct Parser", "fn parse_started", "fn recover", "DiagnosticCategory::ParserRecovery", "Error::InvalidOperation", )) require_markers(TESTS, ( "precedence_and_associativity_choose_the_reference_tree", "right_associative_unary_precedence_is_deterministic", "nonassociative_conflict_rejects_a_chained_operator", "empty_production_accepts_an_empty_input", "lalr_lookaheads_avoid_the_classic_slr_assignment_conflict", "error_token_recovery_discards_input_and_returns_recovered_tree", "representative_lsl_script_parses_to_expected_model", "parser_table_emission_is_byte_deterministic", "malformed_corpus_terminates_without_panics_or_unbounded_growth", "mapped_symbol_set_production_precedence_and_entry_apis_are_live", "shared follow mutation", "registered empty production", )) if TESTS.read_text().count("#[test]") != 10: raise SystemExit("issue 82 expected 10 focused parser fixtures") require_markers(EXTENSION_TESTS, ("assert!(YyParser::new().is_ok())",)) require_markers(DOC, ( "canonical LR(0)", "deterministic LALR(1)", "Shift/reduce conflicts", "nonassociative", "1,048,576", "16,777,216", "46 focused", "issue 83", "ubuntu-latest", )) require_markers(WORKFLOW, ("required-gate",)) print( "issue 82 audit: 22 native mapped parser types and 215 members, deterministic " "LALR tables, precedence conflicts, bounded shifts/reductions, error-token " "recovery, shared compatibility grammar state, 10 focused fixtures, docs, and " "ubuntu-only CI are present" ) if __name__ == "__main__": main()