interscript_rs/
grammar.rs1use crate::tree::{resolve, TNode};
8use parsanol::parg::frontend::compile::{compile_entry_mode, Tables};
9use parsanol::parg::frontend::parser::parse_grammar_with_mode;
10use parsanol::portable::parslet_transform::to_parslet_compatible;
11use parsanol::portable::Grammar;
12use std::sync::OnceLock;
13
14pub const ISC_PARG: &str = include_str!("../grammar/isc.parg");
21
22fn compiled() -> &'static Grammar {
23 static GRAMMAR: OnceLock<Grammar> = OnceLock::new();
24 GRAMMAR.get_or_init(|| {
25 let (parsed, mode) = parse_grammar_with_mode(ISC_PARG)
26 .expect("embedded grammar/isc.parg must parse");
27 compile_entry_mode(
28 &parsed.document,
29 "isc",
30 &Tables::new(),
31 &parsed.atomic_rules,
32 mode.as_deref(),
33 )
34 .expect("embedded grammar/isc.parg must compile")
35 })
36}
37
38pub fn parse_isc(source: &str) -> Result<TNode, (usize, Vec<String>)> {
41 let grammar = compiled();
42 let mut arena = parsanol::portable::AstArena::for_input(source.len().max(1 << 16));
43 let mut parser = parsanol::portable::PortableParser::new(grammar, source, &mut arena);
44 let ast = match parser.parse() {
45 Ok(ast) => ast,
46 Err(_) => {
47 let wire = match parser.failure_wire() {
48 Some((offset, expected)) => (offset, expected),
49 None => (source.len(), Vec::new()),
50 };
51 return Err(wire);
52 }
53 };
54 let shaped = to_parslet_compatible(&ast, &mut arena, source);
55 Ok(resolve(&shaped, &arena, source))
56}
57
58pub fn run_grammar_suite() -> (usize, usize, Vec<String>) {
61 let (parsed, mode) = parse_grammar_with_mode(ISC_PARG).expect("grammar parses");
62 let grammar = compile_entry_mode(
63 &parsed.document,
64 "isc",
65 &Tables::new(),
66 &parsed.atomic_rules,
67 mode.as_deref(),
68 )
69 .expect("grammar compiles");
70
71 let mut passed = 0usize;
72 let mut failed = 0usize;
73 let mut failures = Vec::new();
74 for test in &parsed.document.tests {
75 let mut arena = parsanol::portable::AstArena::for_input(test.input.len().max(1 << 12));
76 let mut parser = parsanol::portable::PortableParser::new(&grammar, &test.input, &mut arena);
77 let ok = parser.parse().is_ok();
78 let wanted = test.kind == "accept";
79 if ok == wanted {
80 passed += 1;
81 } else {
82 failed += 1;
83 failures.push(format!("{} {:?} (parsed={ok})", test.kind, test.input));
84 }
85 }
86 (passed, failed, failures)
87}
88
89#[cfg(test)]
90mod tests {
91 use super::*;
92
93 #[test]
94 fn embedded_suite_passes() {
95 let (passed, failed, failures) = run_grammar_suite();
96 assert_eq!(failed, 0, "failures: {failures:?}");
97 assert!(passed >= 8, "expected the parg suite, got {passed} passes");
98 }
99
100 #[test]
101 fn minimal_map_parses() {
102 let src = r#"system "T:a-b:c-d:1" { stage main { parallel { sub "a" "b" } } }"#;
103 assert!(parse_isc(src).is_ok());
104 }
105
106 #[test]
107 fn truncated_map_fails_with_wire() {
108 let src = r#"system "T:a-b:c-d:1" {"#;
109 let err = parse_isc(src).unwrap_err();
110 assert!(err.1.iter().any(|e| e.contains("metadata") || e.contains("stage") || e.contains('"')), "{err:?}");
111 }
112}