139 lines
6.5 KiB
Python
139 lines
6.5 KiB
Python
#!/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" / "libremetaverse-lsl-tools" / "src" / "parser.rs"
|
|
GENERATED = ROOT / "crates" / "libremetaverse-lsl-tools" / "src" / "generated.rs"
|
|
TESTS = ROOT / "crates" / "libremetaverse-lsl-tools" / "tests" / "parser_compat.rs"
|
|
EXTENSION_TESTS = ROOT / "tests" / "compat" / "tests" / "extension_shims.rs"
|
|
DOC = ROOT / "crates" / "libremetaverse-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()
|