bubbles/compiler/
validate.rs1use crate::compiler::ast::Stmt;
4use crate::compiler::program::Program;
5use crate::error::{DialogueError, Result};
6
7pub fn validate(program: &Program) -> Result<()> {
12 for variants in program.nodes.values() {
13 for node in variants {
14 validate_stmts(node.body.as_ref(), program)?;
15 }
16 }
17 Ok(())
18}
19
20fn validate_stmts(stmts: &[Stmt], program: &Program) -> Result<()> {
21 for stmt in stmts {
22 match stmt {
23 Stmt::Jump(target) | Stmt::Detour(target) if !program.node_exists(target) => {
24 return Err(DialogueError::Validation(format!(
25 "reference to unknown node '{target}'"
26 )));
27 }
28 Stmt::If {
29 branches,
30 else_body,
31 } => {
32 for b in branches {
33 validate_stmts(&b.body, program)?;
34 }
35 validate_stmts(else_body, program)?;
36 }
37 Stmt::Once {
38 body, else_body, ..
39 } => {
40 validate_stmts(body, program)?;
41 validate_stmts(else_body, program)?;
42 }
43 Stmt::Options(items) => {
44 for item in items {
45 validate_stmts(&item.body, program)?;
46 }
47 }
48 _ => {}
49 }
50 }
51 Ok(())
52}
53
54#[cfg(test)]
55mod tests {
56 use super::*;
57 use crate::compiler::compile;
58
59 #[test]
60 fn valid_jump_passes() {
61 let prog = compile("title: A\n---\n<<jump B>>\n===\ntitle: B\n---\n===\n").unwrap();
62 assert!(validate(&prog).is_ok());
63 }
64
65 #[test]
66 fn unknown_jump_target_fails() {
67 let prog = compile("title: A\n---\n<<jump Nonexistent>>\n===\n").unwrap();
68 assert!(validate(&prog).is_err());
69 }
70
71 #[test]
72 fn unknown_jump_inside_if_branch_fails() {
73 let prog =
74 compile("title: A\n---\n<<if true>>\n <<jump Ghost>>\n<<endif>>\n===\n").unwrap();
75 assert!(validate(&prog).is_err());
76 }
77
78 #[test]
79 fn unknown_detour_in_else_fails() {
80 let prog = compile(
81 "title: A\n---\n<<if false>>\n idle\n<<else>>\n <<detour Missing>>\n<<endif>>\n===\n",
82 )
83 .unwrap();
84 assert!(validate(&prog).is_err());
85 }
86
87 #[test]
88 fn unknown_jump_inside_once_fails() {
89 let prog =
90 compile("title: A\n---\n<<once>>\n <<jump Nope>>\n<<endonce>>\n===\n").unwrap();
91 assert!(validate(&prog).is_err());
92 }
93
94 #[test]
95 fn unknown_jump_inside_option_body_fails() {
96 let prog = compile("title: A\n---\n-> Go\n <<jump Bad>>\n===\n").unwrap();
97 assert!(validate(&prog).is_err());
98 }
99
100 #[test]
101 fn unknown_jump_inside_once_else_fails() {
102 let prog = compile(
103 "title: A\n---\n<<once>>\n ok\n<<else>>\n <<jump Nope>>\n<<endonce>>\n===\n",
104 )
105 .unwrap();
106 assert!(validate(&prog).is_err());
107 }
108}