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