1use std::sync::OnceLock;
4
5use sipha::engine::{Engine, ParseError, ParseOutput, RecoverMultiResult};
6use sipha::incremental::reparse as sipha_reparse;
7pub use sipha::incremental::TextEdit;
8use sipha::insn::ParseGraph;
9use sipha::parsed_doc::ParsedDoc;
10use sipha::red::SyntaxNode;
11use sipha::types::Span;
12
13use crate::grammar::{
14 build_expression_grammar, build_grammar, build_program_grammar, build_signature_grammar,
15};
16
17type BuiltAndGraph = (sipha::builder::BuiltGraph, ParseGraph);
18
19fn cache_grammar<F>(storage: &'static OnceLock<BuiltAndGraph>, build: F) -> &'static BuiltAndGraph
20where
21 F: FnOnce() -> sipha::builder::BuiltGraph,
22{
23 storage.get_or_init(|| {
24 let grammar = build();
25 let graph = grammar.as_graph();
26 (grammar, graph)
27 })
28}
29
30macro_rules! cached_grammar_fn {
31 ($name:ident, $build:ident) => {
32 fn $name() -> &'static BuiltAndGraph {
33 static STORAGE: OnceLock<BuiltAndGraph> = OnceLock::new();
34 cache_grammar(&STORAGE, $build)
35 }
36 };
37}
38
39cached_grammar_fn!(token_stream_built_and_graph, build_grammar);
40cached_grammar_fn!(expression_built_and_graph, build_expression_grammar);
41cached_grammar_fn!(program_built_and_graph, build_program_grammar);
42cached_grammar_fn!(signature_built_and_graph, build_signature_grammar);
43
44fn run_parse(
46 source: &str,
47 get_graph: fn() -> &'static BuiltAndGraph,
48) -> Result<ParseOutput, ParseError> {
49 let (_, graph) = get_graph();
50 let mut engine = Engine::new().with_memo();
51 engine.parse(graph, source.as_bytes())
52}
53
54fn parse_to_syntax_root(
55 source: &str,
56 get_graph: fn() -> &'static BuiltAndGraph,
57) -> Result<Option<SyntaxNode>, ParseError> {
58 let out = run_parse(source, get_graph)?;
59 Ok(out.syntax_root(source.as_bytes()))
60}
61
62fn parse_to_output(
63 source: &str,
64 get_graph: fn() -> &'static BuiltAndGraph,
65) -> Result<ParseOutput, ParseError> {
66 run_parse(source, get_graph)
67}
68
69pub fn parse_tokens(source: &str) -> Result<ParseOutput, ParseError> {
74 run_parse(source, token_stream_built_and_graph)
75}
76
77pub fn parse(source: &str) -> Result<Option<SyntaxNode>, ParseError> {
82 parse_to_syntax_root(source, program_built_and_graph)
83}
84
85pub fn parse_expression(source: &str) -> Result<Option<SyntaxNode>, ParseError> {
89 parse_to_syntax_root(source, expression_built_and_graph)
90}
91
92pub fn parse_signatures(source: &str) -> Result<Option<SyntaxNode>, ParseError> {
97 parse_to_syntax_root(source, signature_built_and_graph)
98}
99
100pub fn parse_to_doc(source: &str) -> Result<Option<ParsedDoc>, ParseError> {
106 let out = parse_to_output(source, program_built_and_graph)?;
107 Ok(ParsedDoc::new(source.as_bytes().to_vec(), &out))
108}
109
110pub fn parse_recovering(source: &str) -> Result<ParseOutput, (ParseOutput, ParseError)> {
117 let (_, graph) = program_built_and_graph();
118 let mut engine = Engine::new().with_memo();
119 engine.parse_recovering(graph, source.as_bytes())
120}
121
122pub fn parse_recovering_multi(
130 source: &str,
131 max_errors: usize,
132) -> Result<ParseOutput, RecoverMultiResult> {
133 let (_, graph) = program_built_and_graph();
134 let mut engine = Engine::new().with_memo();
135 engine.parse_recovering_multi(graph, source.as_bytes(), max_errors)
136}
137
138#[must_use]
143pub fn program_literals() -> &'static sipha::insn::LiteralTable {
144 &program_built_and_graph().1.literals
145}
146
147#[must_use]
151pub fn program_rule_names() -> &'static [&'static str] {
152 program_built_and_graph().1.rule_names
153}
154
155pub fn reparse(
161 old_source: &str,
162 old_root: &SyntaxNode,
163 edit: &TextEdit,
164) -> Result<Option<SyntaxNode>, ParseError> {
165 let (_, graph) = program_built_and_graph();
166 let mut engine = Engine::new().with_memo();
167 sipha_reparse(&mut engine, graph, old_source.as_bytes(), old_root, edit)
168}
169
170#[must_use]
177pub fn reparse_or_parse(
178 old_source: &str,
179 old_root: Option<&SyntaxNode>,
180 edit: &TextEdit,
181) -> Option<SyntaxNode> {
182 let new_source_bytes = edit.apply(old_source.as_bytes());
183 let new_source = std::str::from_utf8(&new_source_bytes).ok()?;
184 if let Some(root) = old_root {
185 if let Ok(Some(new_root)) = reparse(old_source, root, edit) {
186 return Some(new_root);
187 }
188 }
189 parse(new_source).ok().and_then(std::convert::identity)
190}
191
192#[must_use]
194pub fn program_expected_labels() -> &'static [&'static str] {
195 program_built_and_graph().1.expected_labels
196}
197
198#[must_use]
205pub fn parse_error_to_diagnostics(
206 parse_err: &ParseError,
207 source: &str,
208) -> Vec<sipha::error::SemanticDiagnostic> {
209 let source_bytes = source.as_bytes();
210 let line_index = sipha::line_index::LineIndex::new(source_bytes);
211 let (span, message) = match parse_err {
212 ParseError::NoMatch(diag) => {
213 let message = diag.format_with_source(
214 source_bytes,
215 &line_index,
216 Some(program_literals()),
217 Some(program_rule_names()),
218 Some(program_expected_labels()),
219 );
220 (Span::new(diag.furthest, diag.furthest), message)
221 }
222 ParseError::BadGraph => (Span::new(0, 0), "malformed parse graph".to_string()),
223 };
224 vec![sipha::error::SemanticDiagnostic {
225 span,
226 message,
227 severity: sipha::error::Severity::Error,
228 code: Some("parse_error".to_string()),
229 file_id: None,
230 related: vec![],
231 }]
232}
233
234#[must_use]
244pub fn parse_error_to_miette(
245 e: &ParseError,
246 source: &str,
247 filename: &str,
248) -> Option<miette::Report> {
249 e.to_miette_report(
250 source,
251 filename,
252 Some(program_literals()),
253 Some(program_rule_names()),
254 Some(program_expected_labels()),
255 )
256}
257
258#[cfg(test)]
259mod tests {
260 use sipha::red::SyntaxElement;
261 use sipha::types::IntoSyntaxKind;
262
263 use crate::syntax::Kind;
264
265 use super::{parse, parse_expression, parse_tokens, reparse};
266
267 #[test]
268 fn parse_tokens_valid() {
269 let out = parse_tokens("var x = 42").unwrap();
270 let root = out.syntax_root("var x = 42".as_bytes());
271 assert!(root.is_some(), "token stream should produce a root");
272 }
273
274 #[test]
275 fn parse_tokens_invalid() {
276 let result = parse_tokens("'unterminated string");
277 assert!(result.is_err(), "unterminated string should fail");
278 }
279
280 #[test]
283 fn parse_edge_unterminated_double_quote_string() {
284 let result = parse(r#"return "hello"#);
285 assert!(
286 result.is_err(),
287 "unterminated double-quote string should fail"
288 );
289 }
290
291 #[test]
292 fn parse_edge_unterminated_single_quote_string() {
293 let result = parse("return 'x");
294 assert!(
295 result.is_err(),
296 "unterminated single-quote string should fail"
297 );
298 }
299
300 #[test]
301 fn parse_edge_empty_input() {
302 let result = parse("");
303 assert!(result.is_ok(), "empty input should not panic");
304 let _ = result.unwrap();
306 }
307
308 #[test]
309 fn parse_edge_only_whitespace() {
310 let result = parse(" \n\t ");
311 assert!(result.is_ok());
312 }
313
314 #[test]
315 fn parse_edge_incomplete_binary_op() {
316 let result = parse("return 1 + ");
317 assert!(result.is_err(), "incomplete expression after + should fail");
318 }
319
320 #[test]
321 fn parse_edge_unclosed_paren() {
322 let result = parse("return (1 + 2");
323 assert!(result.is_err(), "unclosed parenthesis should fail");
324 }
325
326 #[test]
327 fn parse_edge_unclosed_brace() {
328 let result = parse("function f() { return 1;");
329 let _ = result;
331 }
332
333 #[test]
334 fn parse_edge_odd_operator_sequence() {
335 let result = parse("return 1 * * 2;");
336 let _ = result;
338 }
339
340 #[test]
341 fn parse_edge_recovery_produces_partial_tree() {
342 use super::parse_recovering_multi;
343 let source = "var x = 1; return ( ; var y = 2;";
344 let out = parse_recovering_multi(source, 5);
345 if let Err(err) = &out {
348 assert!(
349 err.partial.syntax_root(source.as_bytes()).is_some(),
350 "recovery Err should yield partial syntax root"
351 );
352 }
353 }
354
355 #[test]
356 fn parse_expression_valid() {
357 let root = parse_expression("1").unwrap();
358 assert!(root.is_some(), "simple expression should parse");
359 }
360
361 #[test]
362 fn parse_expression_invalid() {
363 let result = parse_expression("1 + ");
364 assert!(result.is_err() || result.as_ref().ok().and_then(|r| r.as_ref()).is_none());
365 }
366
367 #[test]
368 fn parse_valid_program() {
369 let root = parse("return 1 + 2").unwrap().expect("root");
370 assert_eq!(root.kind_as::<Kind>(), Some(Kind::NodeRoot));
371 let node_children: Vec<_> = root
372 .children()
373 .filter_map(|c| match c {
374 SyntaxElement::Node(n) => Some(n),
375 _ => None,
376 })
377 .collect();
378 assert!(
379 !node_children.is_empty(),
380 "root should have statement children"
381 );
382 assert_eq!(
383 node_children[0].kind_as::<Kind>(),
384 Some(Kind::NodeReturnStmt),
385 "first statement should be return"
386 );
387 }
388
389 #[test]
390 fn parse_invalid_program() {
391 let result = parse("return (");
393 assert!(result.is_err(), "invalid program should return parse error");
394 }
395
396 #[test]
397 fn parse_recovering_multi_collects_multiple_errors() {
398 use super::parse_recovering_multi;
399
400 let source = "return ( ; var x = ";
402 let result = parse_recovering_multi(source, 10);
403 let err = result.expect_err("recovery should return Err with collected errors");
404 assert!(
405 err.errors.len() >= 2,
406 "expected at least 2 parse errors, got {}",
407 err.errors.len()
408 );
409 }
410
411 #[test]
412 fn parse_error_to_miette_produces_report() {
413 use super::parse_error_to_miette;
414
415 let source = "return (";
416 let err = parse(source).unwrap_err();
417 let filename = "test.leek";
418 let report = parse_error_to_miette(&err, source, filename);
419 assert!(
420 report.is_some(),
421 "NoMatch parse error should produce a miette report"
422 );
423 let report = report.unwrap();
424 let report_str = format!("{report:?}");
425 assert!(
426 report_str.contains("expected") || report_str.contains("test.leek"),
427 "report should contain expected tokens or filename: {:?}",
428 report_str
429 );
430 }
431
432 #[test]
433 fn assert_parse_sexp() {
434 use sipha_diff::{assert_parse_eq, syntax_node_to_sexp, SexpOptions};
435
436 let opts = SexpOptions {
437 kind_to_name: Some(|k| Some(crate::syntax::kind_name(k))),
438 ..SexpOptions::semantic_only()
439 };
440 let root = parse_expression("1").unwrap().expect("root");
441 let expected = syntax_node_to_sexp(&root, &opts);
442 assert_parse_eq(parse_expression("1"), "1", &expected, &opts);
443 assert!(
444 expected.contains("EXPR"),
445 "readable kind names in S-expression"
446 );
447 }
448
449 #[test]
450 fn reparse_after_edit() {
451 let old = "var x = 1;";
452 let root = parse(old).unwrap().expect("root");
453 let edit = super::TextEdit {
454 start: 8,
455 end: 9,
456 new_text: b"2".to_vec(),
457 };
458 let new_root = reparse(old, &root, &edit).unwrap();
459 let new_root = new_root.expect("reparse should yield root");
460 let new_text = new_root.collect_text();
461 assert!(
462 new_text.contains("2"),
463 "edited content in reparsed tree: {:?}",
464 new_text
465 );
466 }
467
468 fn assert_parse_function_decl(source: &str, test_name: &str) {
473 let root = parse(source).unwrap().expect(test_name);
474 let funcs = root.find_all_nodes(Kind::NodeFunctionDecl.into_syntax_kind());
475 assert!(
476 !funcs.is_empty(),
477 "{}: expected at least one NodeFunctionDecl in {:?}",
478 test_name,
479 source
480 );
481 }
482
483 #[test]
484 fn parse_function_untyped_params_no_return() {
485 assert_parse_function_decl("function a(b, c) {}", "untyped params, no return");
486 }
487
488 #[test]
489 fn parse_function_untyped_params_arrow_return() {
490 assert_parse_function_decl("function a(b, c) -> void {}", "untyped params, -> void");
491 }
492
493 #[test]
494 fn parse_function_untyped_params_fat_arrow_return() {
495 assert_parse_function_decl("function a(b, c) => void {}", "untyped params, => void");
496 }
497
498 #[test]
499 fn parse_function_mixed_params_fat_arrow_return() {
500 assert_parse_function_decl(
501 "function a(integer b, c) => void {}",
502 "mixed params, => void",
503 );
504 }
505
506 #[test]
507 fn parse_function_no_params() {
508 assert_parse_function_decl("function a() {}", "no params");
509 }
510
511 #[test]
512 fn parse_function_typed_params_arrow_return() {
513 assert_parse_function_decl(
514 "function a(integer x, integer y) -> integer {}",
515 "typed params, -> integer",
516 );
517 }
518
519 #[test]
522 fn parse_program_with_function_type_two_args_return() {
523 let source = "var f = null as Function<integer, integer => void>;";
524 let root = parse(source)
525 .unwrap()
526 .expect("Function<integer, integer => void>");
527 let type_exprs = root.find_all_nodes(Kind::NodeTypeExpr.into_syntax_kind());
528 assert!(
529 !type_exprs.is_empty(),
530 "expected NodeTypeExpr for Function<...> type"
531 );
532 }
533
534 #[test]
535 fn parse_program_with_function_type_zero_params() {
536 let source = "var f = null as Function< => void>;";
537 let root = parse(source).unwrap().expect("Function< => void>");
538 let type_exprs = root.find_all_nodes(Kind::NodeTypeExpr.into_syntax_kind());
539 assert!(
540 !type_exprs.is_empty(),
541 "expected NodeTypeExpr for Function< => void>"
542 );
543 }
544
545 #[test]
546 fn parse_program_with_function_type_one_param() {
547 let source = "var f = null as Function<integer => void>;";
548 let root = parse(source).unwrap().expect("Function<integer => void>");
549 let type_exprs = root.find_all_nodes(Kind::NodeTypeExpr.into_syntax_kind());
550 assert!(
551 !type_exprs.is_empty(),
552 "expected NodeTypeExpr for Function<integer => void>"
553 );
554 }
555
556 #[test]
557 fn parse_program_with_function_type_three_params() {
558 let source = "var f = null as Function<integer, string, real => boolean>;";
559 let root = parse(source)
560 .unwrap()
561 .expect("Function<integer, string, real => boolean>");
562 let type_exprs = root.find_all_nodes(Kind::NodeTypeExpr.into_syntax_kind());
563 assert!(
564 !type_exprs.is_empty(),
565 "expected NodeTypeExpr for Function<...>"
566 );
567 }
568
569 #[test]
572 fn parse_anonymous_function() {
573 let source = "var sum = function(a, b) { return a + b; };";
574 let root = parse(source).unwrap().expect("anonymous function");
575 let anon_fns = root.find_all_nodes(Kind::NodeAnonFn.into_syntax_kind());
576 assert!(
577 !anon_fns.is_empty(),
578 "expected NodeAnonFn for function(a, b) {{ ... }}"
579 );
580 }
581}