Skip to main content

seqc/ast/
program.rs

1//! Program-level AST methods: word-call validation, auto-generated variant
2//! constructors (`Make-Variant`), and type fix-up for union types declared
3//! in stack effects.
4
5use crate::types::{Effect, StackType, Type};
6
7use super::{Program, Statement, WordDef};
8
9impl Program {
10    pub fn new() -> Self {
11        Program {
12            includes: Vec::new(),
13            unions: Vec::new(),
14            words: Vec::new(),
15        }
16    }
17
18    pub fn find_word(&self, name: &str) -> Option<&WordDef> {
19        self.words.iter().find(|w| w.name == name)
20    }
21
22    /// Validate that all word calls reference either a defined word or a built-in
23    pub fn validate_word_calls(&self) -> Result<(), String> {
24        self.validate_word_calls_with_externals(&[])
25    }
26
27    /// Validate that all word calls reference a defined word, built-in, or external word.
28    ///
29    /// The `external_words` parameter should contain names of words available from
30    /// external sources (e.g., included modules) that should be considered valid.
31    pub fn validate_word_calls_with_externals(
32        &self,
33        external_words: &[&str],
34    ) -> Result<(), String> {
35        // List of known runtime built-ins
36        // IMPORTANT: Keep this in sync with codegen.rs WordCall matching
37        let builtins = [
38            // I/O operations
39            "io.write",
40            "io.write-line",
41            "io.read-line",
42            "io.read-n",
43            "int->string",
44            "symbol->string",
45            "string->symbol",
46            // Command-line arguments
47            "args.count",
48            "args.at",
49            // File operations
50            "file.slurp",
51            "file.exists?",
52            "file.for-each-line",
53            "file.spit",
54            "file.append",
55            "file.delete",
56            "file.size",
57            // Directory operations
58            "dir.exists?",
59            "dir.make",
60            "dir.delete",
61            "dir.list",
62            // String operations
63            "string.concat",
64            "string.length",
65            "string.byte-length",
66            "string.char-at",
67            "string.substring",
68            "char->string",
69            "string.find",
70            "string.split",
71            "string.contains",
72            "string.starts-with",
73            "string.empty?",
74            "string.trim",
75            "string.chomp",
76            "string.to-upper",
77            "string.to-lower",
78            "string.equal?",
79            "string.join",
80            "string.json-escape",
81            "string->int",
82            // Symbol operations
83            "symbol.=",
84            // Encoding operations
85            "encoding.base64-encode",
86            "encoding.base64-decode",
87            "encoding.base64url-encode",
88            "encoding.base64url-decode",
89            "encoding.hex-encode",
90            "encoding.hex-decode",
91            // Crypto operations
92            "crypto.sha256",
93            "crypto.hmac-sha256",
94            "crypto.constant-time-eq",
95            "crypto.random-bytes",
96            "crypto.random-int",
97            "crypto.uuid4",
98            "crypto.aes-gcm-encrypt",
99            "crypto.aes-gcm-decrypt",
100            "crypto.pbkdf2-sha256",
101            "crypto.ed25519-keypair",
102            "crypto.ed25519-sign",
103            "crypto.ed25519-verify",
104            // HTTP client operations
105            "http.get",
106            "http.post",
107            "http.put",
108            "http.delete",
109            // List operations
110            "list.make",
111            "list.push",
112            "list.get",
113            "list.set",
114            "list.map",
115            "list.filter",
116            "list.fold",
117            "list.each",
118            "list.length",
119            "list.empty?",
120            "list.reverse",
121            "list.first",
122            "list.last",
123            // Map operations
124            "map.make",
125            "map.get",
126            "map.set",
127            "map.has?",
128            "map.remove",
129            "map.keys",
130            "map.values",
131            "map.size",
132            "map.empty?",
133            "map.each",
134            "map.fold",
135            // Variant operations
136            "variant.field-count",
137            "variant.tag",
138            "variant.field-at",
139            "variant.append",
140            "variant.first",
141            "variant.last",
142            "variant.init",
143            "variant.make-0",
144            "variant.make-1",
145            "variant.make-2",
146            "variant.make-3",
147            "variant.make-4",
148            // SON wrap aliases
149            "wrap-0",
150            "wrap-1",
151            "wrap-2",
152            "wrap-3",
153            "wrap-4",
154            // Integer arithmetic operations
155            "i.add",
156            "i.subtract",
157            "i.multiply",
158            "i.divide",
159            "i.modulo",
160            // Terse integer arithmetic
161            "i.+",
162            "i.-",
163            "i.*",
164            "i./",
165            "i.%",
166            // Integer comparison operations (return 0 or 1)
167            "i.=",
168            "i.<",
169            "i.>",
170            "i.<=",
171            "i.>=",
172            "i.<>",
173            // Integer comparison operations (verbose form)
174            "i.eq",
175            "i.lt",
176            "i.gt",
177            "i.lte",
178            "i.gte",
179            "i.neq",
180            // Stack operations (simple - no parameters)
181            "dup",
182            "drop",
183            "swap",
184            "over",
185            "rot",
186            "nip",
187            "tuck",
188            "2dup",
189            "3drop",
190            "pick",
191            "roll",
192            // Aux stack operations
193            ">aux",
194            "aux>",
195            // Boolean operations
196            "and",
197            "or",
198            "not",
199            // Bitwise operations
200            "band",
201            "bor",
202            "bxor",
203            "bnot",
204            "i.neg",
205            "negate",
206            // Arithmetic sugar (resolved to concrete ops by typechecker)
207            "+",
208            "-",
209            "*",
210            "/",
211            "%",
212            "=",
213            "<",
214            ">",
215            "<=",
216            ">=",
217            "<>",
218            "shl",
219            "shr",
220            "popcount",
221            "clz",
222            "ctz",
223            "int-bits",
224            // Channel operations
225            "chan.make",
226            "chan.send",
227            "chan.receive",
228            "chan.close",
229            "chan.yield",
230            // Quotation operations
231            "call",
232            // Dataflow combinators
233            "dip",
234            "keep",
235            "bi",
236            "if",
237            "strand.spawn",
238            "strand.weave",
239            "strand.resume",
240            "strand.weave-cancel",
241            "yield",
242            "cond",
243            // TCP operations
244            "tcp.listen",
245            "tcp.accept",
246            "tcp.read",
247            "tcp.write",
248            "tcp.close",
249            // UDP operations
250            "udp.bind",
251            "udp.send-to",
252            "udp.receive-from",
253            "udp.close",
254            // OS operations
255            "os.getenv",
256            "os.home-dir",
257            "os.current-dir",
258            "os.path-exists",
259            "os.path-is-file",
260            "os.path-is-dir",
261            "os.path-join",
262            "os.path-parent",
263            "os.path-filename",
264            "os.exit",
265            "os.name",
266            "os.arch",
267            // Signal handling
268            "signal.trap",
269            "signal.received?",
270            "signal.pending?",
271            "signal.default",
272            "signal.ignore",
273            "signal.clear",
274            "signal.SIGINT",
275            "signal.SIGTERM",
276            "signal.SIGHUP",
277            "signal.SIGPIPE",
278            "signal.SIGUSR1",
279            "signal.SIGUSR2",
280            "signal.SIGCHLD",
281            "signal.SIGALRM",
282            "signal.SIGCONT",
283            // Terminal operations
284            "terminal.raw-mode",
285            "terminal.read-char",
286            "terminal.read-char?",
287            "terminal.width",
288            "terminal.height",
289            "terminal.flush",
290            // Float arithmetic operations (verbose form)
291            "f.add",
292            "f.subtract",
293            "f.multiply",
294            "f.divide",
295            // Float arithmetic operations (terse form)
296            "f.+",
297            "f.-",
298            "f.*",
299            "f./",
300            // Float comparison operations (symbol form)
301            "f.=",
302            "f.<",
303            "f.>",
304            "f.<=",
305            "f.>=",
306            "f.<>",
307            // Float comparison operations (verbose form)
308            "f.eq",
309            "f.lt",
310            "f.gt",
311            "f.lte",
312            "f.gte",
313            "f.neq",
314            // Type conversions
315            "int->float",
316            "float->int",
317            "float->string",
318            "string->float",
319            // Byte construction (binary protocol encoders)
320            "int.to-bytes-i32-be",
321            "float.to-bytes-f32-be",
322            // Test framework operations
323            "test.init",
324            "test.set-name",
325            "test.finish",
326            "test.has-failures",
327            "test.assert",
328            "test.assert-not",
329            "test.assert-eq",
330            "test.assert-eq-str",
331            "test.fail",
332            "test.pass-count",
333            "test.fail-count",
334            // Time operations
335            "time.now",
336            "time.nanos",
337            "time.sleep-ms",
338            // SON serialization
339            "son.dump",
340            "son.dump-pretty",
341            // Stack introspection (for REPL)
342            "stack.dump",
343            // Regex operations
344            "regex.match?",
345            "regex.find",
346            "regex.find-all",
347            "regex.replace",
348            "regex.replace-all",
349            "regex.captures",
350            "regex.split",
351            "regex.valid?",
352            // Compression operations
353            "compress.gzip",
354            "compress.gzip-level",
355            "compress.gunzip",
356            "compress.zstd",
357            "compress.zstd-level",
358            "compress.unzstd",
359        ];
360
361        for word in &self.words {
362            self.validate_statements(&word.body, &word.name, &builtins, external_words)?;
363        }
364
365        Ok(())
366    }
367
368    /// Helper to validate word calls in a list of statements (recursively)
369    fn validate_statements(
370        &self,
371        statements: &[Statement],
372        word_name: &str,
373        builtins: &[&str],
374        external_words: &[&str],
375    ) -> Result<(), String> {
376        for statement in statements {
377            match statement {
378                Statement::WordCall { name, .. } => {
379                    // Check if it's a built-in
380                    if builtins.contains(&name.as_str()) {
381                        continue;
382                    }
383                    // Check if it's a user-defined word
384                    if self.find_word(name).is_some() {
385                        continue;
386                    }
387                    // Check if it's an external word (from includes)
388                    if external_words.contains(&name.as_str()) {
389                        continue;
390                    }
391                    // Undefined word!
392                    return Err(format!(
393                        "Undefined word '{}' called in word '{}'. \
394                         Did you forget to define it or misspell a built-in?",
395                        name, word_name
396                    ));
397                }
398                Statement::If {
399                    then_branch,
400                    else_branch,
401                    span: _,
402                } => {
403                    // Recursively validate both branches
404                    self.validate_statements(then_branch, word_name, builtins, external_words)?;
405                    if let Some(eb) = else_branch {
406                        self.validate_statements(eb, word_name, builtins, external_words)?;
407                    }
408                }
409                Statement::Quotation { body, .. } => {
410                    // Recursively validate quotation body
411                    self.validate_statements(body, word_name, builtins, external_words)?;
412                }
413                Statement::Match { arms, span: _ } => {
414                    // Recursively validate each match arm's body
415                    for arm in arms {
416                        self.validate_statements(&arm.body, word_name, builtins, external_words)?;
417                    }
418                }
419                _ => {} // Literals don't need validation
420            }
421        }
422        Ok(())
423    }
424
425    /// Generate constructor words for all union definitions
426    ///
427    /// Maximum number of fields a variant can have (limited by runtime support)
428    pub const MAX_VARIANT_FIELDS: usize = 12;
429
430    /// Generate helper words for union types:
431    /// 1. Constructors: `Make-VariantName` - creates variant instances
432    /// 2. Predicates: `is-VariantName?` - tests if value is a specific variant
433    /// 3. Accessors: `VariantName-fieldname` - extracts field values (RFC #345)
434    ///
435    /// Example: For `union Message { Get { chan: Int } }`
436    /// Generates:
437    ///   `: Make-Get ( Int -- Message ) :Get variant.make-1 ;`
438    ///   `: is-Get? ( Message -- Bool ) variant.tag :Get symbol.= ;`
439    ///   `: Get-chan ( Message -- Int ) 0 variant.field-at ;`
440    ///
441    /// Returns an error if any variant exceeds the maximum field count.
442    pub fn generate_constructors(&mut self) -> Result<(), String> {
443        let mut new_words = Vec::new();
444
445        for union_def in &self.unions {
446            for variant in &union_def.variants {
447                let field_count = variant.fields.len();
448
449                // Check field count limit before generating constructor
450                if field_count > Self::MAX_VARIANT_FIELDS {
451                    return Err(format!(
452                        "Variant '{}' in union '{}' has {} fields, but the maximum is {}. \
453                         Consider grouping fields into nested union types.",
454                        variant.name,
455                        union_def.name,
456                        field_count,
457                        Self::MAX_VARIANT_FIELDS
458                    ));
459                }
460
461                // 1. Generate constructor: Make-VariantName
462                let constructor_name = format!("Make-{}", variant.name);
463                let mut input_stack = StackType::RowVar("a".to_string());
464                for field in &variant.fields {
465                    let field_type = parse_type_name(&field.type_name);
466                    input_stack = input_stack.push(field_type);
467                }
468                let output_stack =
469                    StackType::RowVar("a".to_string()).push(Type::Union(union_def.name.clone()));
470                let effect = Effect::new(input_stack, output_stack);
471                let body = vec![
472                    Statement::Symbol(variant.name.clone()),
473                    Statement::WordCall {
474                        name: format!("variant.make-{}", field_count),
475                        span: None,
476                    },
477                ];
478                new_words.push(WordDef {
479                    name: constructor_name,
480                    effect: Some(effect),
481                    body,
482                    source: variant.source.clone(),
483                    allowed_lints: vec![],
484                });
485
486                // 2. Generate predicate: is-VariantName?
487                // Effect: ( UnionType -- Bool )
488                // Body: variant.tag :VariantName symbol.=
489                let predicate_name = format!("is-{}?", variant.name);
490                let predicate_input =
491                    StackType::RowVar("a".to_string()).push(Type::Union(union_def.name.clone()));
492                let predicate_output = StackType::RowVar("a".to_string()).push(Type::Bool);
493                let predicate_effect = Effect::new(predicate_input, predicate_output);
494                let predicate_body = vec![
495                    Statement::WordCall {
496                        name: "variant.tag".to_string(),
497                        span: None,
498                    },
499                    Statement::Symbol(variant.name.clone()),
500                    Statement::WordCall {
501                        name: "symbol.=".to_string(),
502                        span: None,
503                    },
504                ];
505                new_words.push(WordDef {
506                    name: predicate_name,
507                    effect: Some(predicate_effect),
508                    body: predicate_body,
509                    source: variant.source.clone(),
510                    allowed_lints: vec![],
511                });
512
513                // 3. Generate field accessors: VariantName-fieldname
514                // Effect: ( UnionType -- FieldType )
515                // Body: N variant.field-at
516                for (index, field) in variant.fields.iter().enumerate() {
517                    let accessor_name = format!("{}-{}", variant.name, field.name);
518                    let field_type = parse_type_name(&field.type_name);
519                    let accessor_input = StackType::RowVar("a".to_string())
520                        .push(Type::Union(union_def.name.clone()));
521                    let accessor_output = StackType::RowVar("a".to_string()).push(field_type);
522                    let accessor_effect = Effect::new(accessor_input, accessor_output);
523                    let accessor_body = vec![
524                        Statement::IntLiteral(index as i64),
525                        Statement::WordCall {
526                            name: "variant.field-at".to_string(),
527                            span: None,
528                        },
529                    ];
530                    new_words.push(WordDef {
531                        name: accessor_name,
532                        effect: Some(accessor_effect),
533                        body: accessor_body,
534                        source: variant.source.clone(), // Use variant's source for field accessors
535                        allowed_lints: vec![],
536                    });
537                }
538            }
539        }
540
541        self.words.extend(new_words);
542        Ok(())
543    }
544
545    /// RFC #345: Fix up type variables in stack effects that should be union types
546    ///
547    /// When parsing files with includes, type variables like "Message" in
548    /// `( Message -- Int )` may be parsed as `Type::Var("Message")` if the
549    /// union definition is in an included file. After resolving includes,
550    /// we know all union names and can convert these to `Type::Union("Message")`.
551    ///
552    /// This ensures proper nominal type checking for union types across files.
553    pub fn fixup_union_types(&mut self) {
554        // Collect all union names from the program
555        let union_names: std::collections::HashSet<String> =
556            self.unions.iter().map(|u| u.name.clone()).collect();
557
558        // Fix up types in all word effects
559        for word in &mut self.words {
560            if let Some(ref mut effect) = word.effect {
561                Self::fixup_stack_type(&mut effect.inputs, &union_names);
562                Self::fixup_stack_type(&mut effect.outputs, &union_names);
563            }
564        }
565    }
566
567    /// Recursively fix up types in a stack type
568    fn fixup_stack_type(stack: &mut StackType, union_names: &std::collections::HashSet<String>) {
569        match stack {
570            StackType::Empty | StackType::RowVar(_) => {}
571            StackType::Cons { rest, top } => {
572                Self::fixup_type(top, union_names);
573                Self::fixup_stack_type(rest, union_names);
574            }
575        }
576    }
577
578    /// Fix up a single type, converting Type::Var to Type::Union if it matches a union name
579    fn fixup_type(ty: &mut Type, union_names: &std::collections::HashSet<String>) {
580        match ty {
581            Type::Var(name) if union_names.contains(name) => {
582                *ty = Type::Union(name.clone());
583            }
584            Type::Quotation(effect) => {
585                Self::fixup_stack_type(&mut effect.inputs, union_names);
586                Self::fixup_stack_type(&mut effect.outputs, union_names);
587            }
588            Type::Closure { effect, captures } => {
589                Self::fixup_stack_type(&mut effect.inputs, union_names);
590                Self::fixup_stack_type(&mut effect.outputs, union_names);
591                for cap in captures {
592                    Self::fixup_type(cap, union_names);
593                }
594            }
595            _ => {}
596        }
597    }
598}
599
600/// Parse a type name string into a Type
601/// Used by constructor generation to build stack effects
602fn parse_type_name(name: &str) -> Type {
603    match name {
604        "Int" => Type::Int,
605        "Float" => Type::Float,
606        "Bool" => Type::Bool,
607        "String" => Type::String,
608        "Channel" => Type::Channel,
609        other => Type::Union(other.to_string()),
610    }
611}
612
613impl Default for Program {
614    fn default() -> Self {
615        Self::new()
616    }
617}