1use crate::types::Effect;
7use std::path::PathBuf;
8
9#[derive(Debug, Clone, PartialEq)]
11pub struct SourceLocation {
12 pub file: PathBuf,
13 pub line: usize,
14}
15
16impl std::fmt::Display for SourceLocation {
17 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18 write!(f, "{}:{}", self.file.display(), self.line)
19 }
20}
21
22#[derive(Debug, Clone, PartialEq)]
24pub enum Include {
25 Std(String),
27 Relative(String),
29}
30
31#[derive(Debug, Clone, PartialEq)]
32pub struct Program {
33 pub includes: Vec<Include>,
34 pub words: Vec<WordDef>,
35}
36
37#[derive(Debug, Clone, PartialEq)]
38pub struct WordDef {
39 pub name: String,
40 pub effect: Option<Effect>,
43 pub body: Vec<Statement>,
44 pub source: Option<SourceLocation>,
46}
47
48#[derive(Debug, Clone, PartialEq)]
49pub enum Statement {
50 IntLiteral(i64),
52
53 FloatLiteral(f64),
55
56 BoolLiteral(bool),
58
59 StringLiteral(String),
61
62 WordCall(String),
64
65 If {
70 then_branch: Vec<Statement>,
72 else_branch: Option<Vec<Statement>>,
74 },
75
76 Quotation { id: usize, body: Vec<Statement> },
85}
86
87impl Program {
88 pub fn new() -> Self {
89 Program {
90 includes: Vec::new(),
91 words: Vec::new(),
92 }
93 }
94
95 pub fn find_word(&self, name: &str) -> Option<&WordDef> {
96 self.words.iter().find(|w| w.name == name)
97 }
98
99 pub fn validate_word_calls(&self) -> Result<(), String> {
101 self.validate_word_calls_with_externals(&[])
102 }
103
104 pub fn validate_word_calls_with_externals(
109 &self,
110 external_words: &[&str],
111 ) -> Result<(), String> {
112 let builtins = [
115 "write_line",
117 "read_line",
118 "int->string",
119 "arg-count",
121 "arg",
122 "file-slurp",
124 "file-exists?",
125 "string-concat",
127 "string-length",
128 "string-byte-length",
129 "string-char-at",
130 "string-substring",
131 "char->string",
132 "string-find",
133 "string-split",
134 "string-contains",
135 "string-starts-with",
136 "string-empty",
137 "string-trim",
138 "string-to-upper",
139 "string-to-lower",
140 "string-equal",
141 "variant-field-count",
143 "variant-tag",
144 "variant-field-at",
145 "variant-append",
146 "variant-last",
147 "variant-init",
148 "make-variant",
149 "add",
151 "subtract",
152 "multiply",
153 "divide",
154 "=",
156 "<",
157 ">",
158 "<=",
159 ">=",
160 "<>",
161 "dup",
163 "drop",
164 "swap",
165 "over",
166 "rot",
167 "nip",
168 "tuck",
169 "pick",
170 "roll",
171 "and",
173 "or",
174 "not",
175 "make-channel",
177 "send",
178 "receive",
179 "close-channel",
180 "yield",
181 "call",
183 "times",
184 "while",
185 "until",
186 "forever",
187 "spawn",
188 "cond",
189 "tcp-listen",
191 "tcp-accept",
192 "tcp-read",
193 "tcp-write",
194 "tcp-close",
195 "f.add",
197 "f.subtract",
198 "f.multiply",
199 "f.divide",
200 "f.=",
202 "f.<",
203 "f.>",
204 "f.<=",
205 "f.>=",
206 "f.<>",
207 "int->float",
209 "float->int",
210 "float->string",
211 "string->float",
212 ];
213
214 for word in &self.words {
215 self.validate_statements(&word.body, &word.name, &builtins, external_words)?;
216 }
217
218 Ok(())
219 }
220
221 fn validate_statements(
223 &self,
224 statements: &[Statement],
225 word_name: &str,
226 builtins: &[&str],
227 external_words: &[&str],
228 ) -> Result<(), String> {
229 for statement in statements {
230 match statement {
231 Statement::WordCall(name) => {
232 if builtins.contains(&name.as_str()) {
234 continue;
235 }
236 if self.find_word(name).is_some() {
238 continue;
239 }
240 if external_words.contains(&name.as_str()) {
242 continue;
243 }
244 return Err(format!(
246 "Undefined word '{}' called in word '{}'. \
247 Did you forget to define it or misspell a built-in?",
248 name, word_name
249 ));
250 }
251 Statement::If {
252 then_branch,
253 else_branch,
254 } => {
255 self.validate_statements(then_branch, word_name, builtins, external_words)?;
257 if let Some(eb) = else_branch {
258 self.validate_statements(eb, word_name, builtins, external_words)?;
259 }
260 }
261 Statement::Quotation { body, .. } => {
262 self.validate_statements(body, word_name, builtins, external_words)?;
264 }
265 _ => {} }
267 }
268 Ok(())
269 }
270}
271
272impl Default for Program {
273 fn default() -> Self {
274 Self::new()
275 }
276}
277
278#[cfg(test)]
279mod tests {
280 use super::*;
281
282 #[test]
283 fn test_validate_builtin_words() {
284 let program = Program {
285 includes: vec![],
286 words: vec![WordDef {
287 name: "main".to_string(),
288 effect: None,
289 body: vec![
290 Statement::IntLiteral(2),
291 Statement::IntLiteral(3),
292 Statement::WordCall("add".to_string()),
293 Statement::WordCall("write_line".to_string()),
294 ],
295 source: None,
296 }],
297 };
298
299 assert!(program.validate_word_calls().is_ok());
301 }
302
303 #[test]
304 fn test_validate_user_defined_words() {
305 let program = Program {
306 includes: vec![],
307 words: vec![
308 WordDef {
309 name: "helper".to_string(),
310 effect: None,
311 body: vec![Statement::IntLiteral(42)],
312 source: None,
313 },
314 WordDef {
315 name: "main".to_string(),
316 effect: None,
317 body: vec![Statement::WordCall("helper".to_string())],
318 source: None,
319 },
320 ],
321 };
322
323 assert!(program.validate_word_calls().is_ok());
325 }
326
327 #[test]
328 fn test_validate_undefined_word() {
329 let program = Program {
330 includes: vec![],
331 words: vec![WordDef {
332 name: "main".to_string(),
333 effect: None,
334 body: vec![Statement::WordCall("undefined_word".to_string())],
335 source: None,
336 }],
337 };
338
339 let result = program.validate_word_calls();
341 assert!(result.is_err());
342 let error = result.unwrap_err();
343 assert!(error.contains("undefined_word"));
344 assert!(error.contains("main"));
345 }
346
347 #[test]
348 fn test_validate_misspelled_builtin() {
349 let program = Program {
350 includes: vec![],
351 words: vec![WordDef {
352 name: "main".to_string(),
353 effect: None,
354 body: vec![Statement::WordCall("wrte_line".to_string())], source: None,
356 }],
357 };
358
359 let result = program.validate_word_calls();
361 assert!(result.is_err());
362 let error = result.unwrap_err();
363 assert!(error.contains("wrte_line"));
364 assert!(error.contains("misspell"));
365 }
366}