conformance/
conformance.rs1use std::fmt::Write as _;
12
13fn main() {
14 let path = std::env::args()
15 .nth(1)
16 .expect("usage: conformance <corpus.json>");
17 let raw = std::fs::read_to_string(&path).expect("corpus file readable");
18 let corpus: Vec<String> = parse_json_strings(&raw);
19
20 let mut failures = 0usize;
21 let mut report = String::new();
22 for (index, query) in corpus.iter().enumerate() {
23 let source = surrealguard_syntax::source::SourceId::new(format!("corpus:{index}"));
24 let Ok(parsed) = surrealguard_syntax::parse::parse_source(source, query.as_str()) else {
25 failures += 1;
26 let _ = writeln!(report, "#{index}: parser returned no tree");
27 continue;
28 };
29 if let Some(range) = first_broken_range(parsed.tree().root_node()) {
30 failures += 1;
31 let snippet: String = query[range.clone()].chars().take(60).collect();
32 let context: String = query.chars().take(80).collect();
33 let _ = writeln!(
34 report,
35 "#{index}: ERROR at {range:?}: `{}`\n in: {}",
36 snippet.replace('\n', " "),
37 context.replace('\n', " ")
38 );
39 }
40 }
41
42 print!("{report}");
43 println!(
44 "---\n{failures}/{} corpus entries fail to parse",
45 corpus.len()
46 );
47 if failures > 0 {
48 std::process::exit(1);
49 }
50}
51
52fn first_broken_range(node: tree_sitter::Node<'_>) -> Option<std::ops::Range<usize>> {
53 if node.is_error() || node.is_missing() {
54 return Some(node.byte_range());
55 }
56 if !node.has_error() {
57 return None;
58 }
59 let mut cursor = node.walk();
60 let children: Vec<_> = node.children(&mut cursor).collect();
61 for child in children {
62 if let Some(range) = first_broken_range(child) {
63 return Some(range);
64 }
65 }
66 Some(node.byte_range())
67}
68
69fn parse_json_strings(raw: &str) -> Vec<String> {
71 let mut out = Vec::new();
72 let mut chars = raw.chars().peekable();
73 while let Some(c) = chars.next() {
74 if c != '"' {
75 continue;
76 }
77 let mut s = String::new();
78 while let Some(c) = chars.next() {
79 match c {
80 '"' => break,
81 '\\' => match chars.next() {
82 Some('n') => s.push('\n'),
83 Some('t') => s.push('\t'),
84 Some('u') => {
85 let hex: String = (0..4).filter_map(|_| chars.next()).collect();
86 if let Ok(code) = u32::from_str_radix(&hex, 16) {
87 if let Some(ch) = char::from_u32(code) {
88 s.push(ch);
89 }
90 }
91 }
92 Some(other) => s.push(other),
93 None => break,
94 },
95 other => s.push(other),
96 }
97 }
98 out.push(s);
99 }
100 out
101}