seqc/
ast.rs

1//! Abstract Syntax Tree for Seq
2//!
3//! Minimal AST sufficient for hello-world and basic programs.
4//! Will be extended as we add more language features.
5
6use crate::types::Effect;
7use std::path::PathBuf;
8
9/// Source location for error reporting
10#[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/// Include statement
23#[derive(Debug, Clone, PartialEq)]
24pub enum Include {
25    /// Standard library include: `include std:http`
26    Std(String),
27    /// Relative path include: `include "my-utils"`
28    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    /// Optional stack effect declaration
41    /// Example: ( ..a Int -- ..a Bool )
42    pub effect: Option<Effect>,
43    pub body: Vec<Statement>,
44    /// Source location for error reporting (collision detection)
45    pub source: Option<SourceLocation>,
46}
47
48#[derive(Debug, Clone, PartialEq)]
49pub enum Statement {
50    /// Integer literal: pushes value onto stack
51    IntLiteral(i64),
52
53    /// Floating-point literal: pushes IEEE 754 double onto stack
54    FloatLiteral(f64),
55
56    /// Boolean literal: pushes true/false onto stack
57    BoolLiteral(bool),
58
59    /// String literal: pushes string onto stack
60    StringLiteral(String),
61
62    /// Word call: calls another word or built-in
63    WordCall(String),
64
65    /// Conditional: if/else/then
66    ///
67    /// Pops an integer from the stack (0 = zero, non-zero = non-zero)
68    /// and executes the appropriate branch
69    If {
70        /// Statements to execute when condition is non-zero (the 'then' clause)
71        then_branch: Vec<Statement>,
72        /// Optional statements to execute when condition is zero (the 'else' clause)
73        else_branch: Option<Vec<Statement>>,
74    },
75
76    /// Quotation: [ ... ]
77    ///
78    /// A block of deferred code (quotation/lambda)
79    /// Quotations are first-class values that can be pushed onto the stack
80    /// and executed later with combinators like `call`, `times`, or `while`
81    ///
82    /// The id field is used by the typechecker to track the inferred type
83    /// (Quotation vs Closure) for this quotation. The id is assigned during parsing.
84    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    /// Validate that all word calls reference either a defined word or a built-in
100    pub fn validate_word_calls(&self) -> Result<(), String> {
101        self.validate_word_calls_with_externals(&[])
102    }
103
104    /// Validate that all word calls reference a defined word, built-in, or external word.
105    ///
106    /// The `external_words` parameter should contain names of words available from
107    /// external sources (e.g., included modules) that should be considered valid.
108    pub fn validate_word_calls_with_externals(
109        &self,
110        external_words: &[&str],
111    ) -> Result<(), String> {
112        // List of known runtime built-ins
113        // IMPORTANT: Keep this in sync with codegen.rs WordCall matching
114        let builtins = [
115            // I/O operations
116            "write_line",
117            "read_line",
118            "int->string",
119            // Command-line arguments
120            "arg-count",
121            "arg",
122            // File operations
123            "file-slurp",
124            "file-exists?",
125            // String operations
126            "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 operations
142            "variant-field-count",
143            "variant-tag",
144            "variant-field-at",
145            "variant-append",
146            "variant-last",
147            "variant-init",
148            "make-variant",
149            // Arithmetic operations
150            "add",
151            "subtract",
152            "multiply",
153            "divide",
154            // Comparison operations (return 0 or 1)
155            "=",
156            "<",
157            ">",
158            "<=",
159            ">=",
160            "<>",
161            // Stack operations (simple - no parameters)
162            "dup",
163            "drop",
164            "swap",
165            "over",
166            "rot",
167            "nip",
168            "tuck",
169            "pick",
170            "roll",
171            // Boolean operations
172            "and",
173            "or",
174            "not",
175            // Concurrency operations
176            "make-channel",
177            "send",
178            "receive",
179            "close-channel",
180            "yield",
181            // Quotation operations
182            "call",
183            "times",
184            "while",
185            "until",
186            "forever",
187            "spawn",
188            "cond",
189            // TCP operations
190            "tcp-listen",
191            "tcp-accept",
192            "tcp-read",
193            "tcp-write",
194            "tcp-close",
195            // Float arithmetic operations
196            "f.add",
197            "f.subtract",
198            "f.multiply",
199            "f.divide",
200            // Float comparison operations
201            "f.=",
202            "f.<",
203            "f.>",
204            "f.<=",
205            "f.>=",
206            "f.<>",
207            // Type conversions
208            "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    /// Helper to validate word calls in a list of statements (recursively)
222    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                    // Check if it's a built-in
233                    if builtins.contains(&name.as_str()) {
234                        continue;
235                    }
236                    // Check if it's a user-defined word
237                    if self.find_word(name).is_some() {
238                        continue;
239                    }
240                    // Check if it's an external word (from includes)
241                    if external_words.contains(&name.as_str()) {
242                        continue;
243                    }
244                    // Undefined word!
245                    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                    // Recursively validate both branches
256                    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                    // Recursively validate quotation body
263                    self.validate_statements(body, word_name, builtins, external_words)?;
264                }
265                _ => {} // Literals don't need validation
266            }
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        // Should succeed - add and write_line are built-ins
300        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        // Should succeed - helper is defined
324        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        // Should fail - undefined_word is not a built-in or user-defined word
340        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())], // typo
355                source: None,
356            }],
357        };
358
359        // Should fail with helpful message
360        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}