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