seqc/
parser.rs

1//! Simple parser for Seq syntax
2//!
3//! Syntax:
4//! ```text
5//! : word-name ( stack-effect )
6//!   statement1
7//!   statement2
8//!   ... ;
9//! ```
10
11use crate::ast::{Include, Program, Statement, WordDef};
12use crate::types::{Effect, StackType, Type};
13
14pub struct Parser {
15    tokens: Vec<String>,
16    pos: usize,
17    /// Counter for assigning unique IDs to quotations
18    /// Used by the typechecker to track inferred types
19    next_quotation_id: usize,
20}
21
22impl Parser {
23    pub fn new(source: &str) -> Self {
24        let tokens = tokenize(source);
25        Parser {
26            tokens,
27            pos: 0,
28            next_quotation_id: 0,
29        }
30    }
31
32    pub fn parse(&mut self) -> Result<Program, String> {
33        let mut program = Program::new();
34
35        // Check for unclosed string error from tokenizer
36        if self.tokens.iter().any(|t| t == "<<<UNCLOSED_STRING>>>") {
37            return Err("Unclosed string literal - missing closing quote".to_string());
38        }
39
40        while !self.is_at_end() {
41            self.skip_comments();
42            if self.is_at_end() {
43                break;
44            }
45
46            // Check for include statement
47            if self.check("include") {
48                let include = self.parse_include()?;
49                program.includes.push(include);
50                continue;
51            }
52
53            let word = self.parse_word_def()?;
54            program.words.push(word);
55        }
56
57        Ok(program)
58    }
59
60    /// Parse an include statement:
61    ///   include std:http     -> Include::Std("http")
62    ///   include "my-utils"   -> Include::Relative("my-utils")
63    fn parse_include(&mut self) -> Result<Include, String> {
64        self.consume("include");
65
66        let token = self
67            .advance()
68            .ok_or("Expected module name after 'include'")?
69            .clone();
70
71        // Check for std: prefix (tokenizer splits this into "std", ":", "name")
72        if token == "std" {
73            // Expect : token
74            if !self.consume(":") {
75                return Err("Expected ':' after 'std' in include statement".to_string());
76            }
77            // Get the module name
78            let name = self
79                .advance()
80                .ok_or("Expected module name after 'std:'")?
81                .clone();
82            return Ok(Include::Std(name));
83        }
84
85        // Check for quoted string (relative path)
86        if token.starts_with('"') && token.ends_with('"') {
87            let path = token.trim_start_matches('"').trim_end_matches('"');
88            return Ok(Include::Relative(path.to_string()));
89        }
90
91        Err(format!(
92            "Invalid include syntax '{}'. Use 'include std:name' or 'include \"path\"'",
93            token
94        ))
95    }
96
97    fn parse_word_def(&mut self) -> Result<WordDef, String> {
98        // Expect ':'
99        if !self.consume(":") {
100            return Err(format!(
101                "Expected ':' to start word definition, got '{}'",
102                self.current()
103            ));
104        }
105
106        // Get word name
107        let name = self
108            .advance()
109            .ok_or("Expected word name after ':'")?
110            .clone();
111
112        // Parse stack effect if present: ( ..a Int -- ..a Bool )
113        let effect = if self.check("(") {
114            Some(self.parse_stack_effect()?)
115        } else {
116            None
117        };
118
119        // Parse body until ';'
120        let mut body = Vec::new();
121        while !self.check(";") {
122            if self.is_at_end() {
123                return Err(format!("Unexpected end of file in word '{}'", name));
124            }
125
126            // Skip comments and newlines in body
127            self.skip_comments();
128            if self.check(";") {
129                break;
130            }
131
132            body.push(self.parse_statement()?);
133        }
134
135        // Consume ';'
136        self.consume(";");
137
138        Ok(WordDef {
139            name,
140            effect,
141            body,
142            source: None,
143        })
144    }
145
146    fn parse_statement(&mut self) -> Result<Statement, String> {
147        let token = self.advance().ok_or("Unexpected end of file")?.clone();
148
149        // Check if it looks like a float literal (contains . or scientific notation)
150        // Must check this BEFORE integer parsing
151        if let Some(f) = is_float_literal(&token)
152            .then(|| token.parse::<f64>().ok())
153            .flatten()
154        {
155            return Ok(Statement::FloatLiteral(f));
156        }
157
158        // Try to parse as integer literal
159        if let Ok(n) = token.parse::<i64>() {
160            return Ok(Statement::IntLiteral(n));
161        }
162
163        // Try to parse as boolean literal
164        if token == "true" {
165            return Ok(Statement::BoolLiteral(true));
166        }
167        if token == "false" {
168            return Ok(Statement::BoolLiteral(false));
169        }
170
171        // Try to parse as string literal
172        if token.starts_with('"') {
173            let raw = token.trim_start_matches('"').trim_end_matches('"');
174            let unescaped = unescape_string(raw)?;
175            return Ok(Statement::StringLiteral(unescaped));
176        }
177
178        // Check for conditional
179        if token == "if" {
180            return self.parse_if();
181        }
182
183        // Check for quotation
184        if token == "[" {
185            return self.parse_quotation();
186        }
187
188        // Otherwise it's a word call
189        Ok(Statement::WordCall(token))
190    }
191
192    fn parse_if(&mut self) -> Result<Statement, String> {
193        let mut then_branch = Vec::new();
194
195        // Parse then branch until 'else' or 'then'
196        loop {
197            if self.is_at_end() {
198                return Err("Unexpected end of file in 'if' statement".to_string());
199            }
200
201            // Skip comments and newlines
202            self.skip_comments();
203
204            if self.check("else") {
205                self.advance();
206                // Parse else branch
207                break;
208            }
209
210            if self.check("then") {
211                self.advance();
212                // End of if without else
213                return Ok(Statement::If {
214                    then_branch,
215                    else_branch: None,
216                });
217            }
218
219            then_branch.push(self.parse_statement()?);
220        }
221
222        // Parse else branch until 'then'
223        let mut else_branch = Vec::new();
224        loop {
225            if self.is_at_end() {
226                return Err("Unexpected end of file in 'else' branch".to_string());
227            }
228
229            // Skip comments and newlines
230            self.skip_comments();
231
232            if self.check("then") {
233                self.advance();
234                return Ok(Statement::If {
235                    then_branch,
236                    else_branch: Some(else_branch),
237                });
238            }
239
240            else_branch.push(self.parse_statement()?);
241        }
242    }
243
244    fn parse_quotation(&mut self) -> Result<Statement, String> {
245        let mut body = Vec::new();
246
247        // Parse statements until ']'
248        loop {
249            if self.is_at_end() {
250                return Err("Unexpected end of file in quotation".to_string());
251            }
252
253            // Skip comments and newlines
254            self.skip_comments();
255
256            if self.check("]") {
257                self.advance();
258                let id = self.next_quotation_id;
259                self.next_quotation_id += 1;
260                return Ok(Statement::Quotation { id, body });
261            }
262
263            body.push(self.parse_statement()?);
264        }
265    }
266
267    /// Parse a stack effect declaration: ( ..a Int -- ..a Bool )
268    fn parse_stack_effect(&mut self) -> Result<Effect, String> {
269        // Consume '('
270        if !self.consume("(") {
271            return Err("Expected '(' to start stack effect".to_string());
272        }
273
274        // Parse input stack types (until '--' or ')')
275        let (input_row_var, input_types) =
276            self.parse_type_list_until(&["--", ")"], "stack effect inputs", 0)?;
277
278        // Consume '--'
279        if !self.consume("--") {
280            return Err("Expected '--' separator in stack effect".to_string());
281        }
282
283        // Parse output stack types (until ')')
284        let (output_row_var, output_types) =
285            self.parse_type_list_until(&[")"], "stack effect outputs", 0)?;
286
287        // Consume ')'
288        if !self.consume(")") {
289            return Err("Expected ')' to end stack effect".to_string());
290        }
291
292        // Build input and output StackTypes
293        let inputs = self.build_stack_type(input_row_var, input_types);
294        let outputs = self.build_stack_type(output_row_var, output_types);
295
296        Ok(Effect::new(inputs, outputs))
297    }
298
299    /// Parse a single type token into a Type
300    fn parse_type(&self, token: &str) -> Result<Type, String> {
301        match token {
302            "Int" => Ok(Type::Int),
303            "Float" => Ok(Type::Float),
304            "Bool" => Ok(Type::Bool),
305            "String" => Ok(Type::String),
306            _ => {
307                // Check if it's a type variable (starts with uppercase)
308                if let Some(first_char) = token.chars().next() {
309                    if first_char.is_uppercase() {
310                        Ok(Type::Var(token.to_string()))
311                    } else {
312                        Err(format!(
313                            "Unknown type: '{}'. Expected Int, Bool, String, Closure, or a type variable (uppercase)",
314                            token
315                        ))
316                    }
317                } else {
318                    Err(format!("Invalid type: '{}'", token))
319                }
320            }
321        }
322    }
323
324    /// Validate row variable name
325    /// Row variables must start with a lowercase letter and contain only alphanumeric characters
326    fn validate_row_var_name(&self, name: &str) -> Result<(), String> {
327        if name.is_empty() {
328            return Err("Row variable must have a name after '..'".to_string());
329        }
330
331        // Must start with lowercase letter
332        let first_char = name.chars().next().unwrap();
333        if !first_char.is_ascii_lowercase() {
334            return Err(format!(
335                "Row variable '..{}' must start with a lowercase letter (a-z)",
336                name
337            ));
338        }
339
340        // Rest must be alphanumeric or underscore
341        for ch in name.chars() {
342            if !ch.is_alphanumeric() && ch != '_' {
343                return Err(format!(
344                    "Row variable '..{}' can only contain letters, numbers, and underscores",
345                    name
346                ));
347            }
348        }
349
350        // Check for reserved keywords (type names that might confuse users)
351        match name {
352            "Int" | "Bool" | "String" => {
353                return Err(format!(
354                    "Row variable '..{}' cannot use type name as identifier",
355                    name
356                ));
357            }
358            _ => {}
359        }
360
361        Ok(())
362    }
363
364    /// Parse a list of types until one of the given terminators is reached
365    /// Returns (optional row variable, list of types)
366    /// Used by both parse_stack_effect and parse_quotation_type
367    ///
368    /// depth: Current nesting depth for quotation types (0 at top level)
369    fn parse_type_list_until(
370        &mut self,
371        terminators: &[&str],
372        context: &str,
373        depth: usize,
374    ) -> Result<(Option<String>, Vec<Type>), String> {
375        const MAX_QUOTATION_DEPTH: usize = 32;
376
377        if depth > MAX_QUOTATION_DEPTH {
378            return Err(format!(
379                "Quotation type nesting exceeds maximum depth of {} (possible deeply nested types or DOS attack)",
380                MAX_QUOTATION_DEPTH
381            ));
382        }
383
384        let mut types = Vec::new();
385        let mut row_var = None;
386
387        while !terminators.iter().any(|t| self.check(t)) {
388            if self.is_at_end() {
389                return Err(format!(
390                    "Unexpected end while parsing {} - expected one of: {}",
391                    context,
392                    terminators.join(", ")
393                ));
394            }
395
396            let token = self
397                .advance()
398                .ok_or_else(|| format!("Unexpected end in {}", context))?
399                .clone();
400
401            // Check for row variable: ..name
402            if token.starts_with("..") {
403                let var_name = token.trim_start_matches("..").to_string();
404                self.validate_row_var_name(&var_name)?;
405                row_var = Some(var_name);
406            } else if token == "Closure" {
407                // Closure type: Closure[effect]
408                if !self.consume("[") {
409                    return Err("Expected '[' after 'Closure' in type signature".to_string());
410                }
411                let effect_type = self.parse_quotation_type(depth)?;
412                match effect_type {
413                    Type::Quotation(effect) => {
414                        types.push(Type::Closure {
415                            effect,
416                            captures: Vec::new(), // Filled in by type checker
417                        });
418                    }
419                    _ => unreachable!("parse_quotation_type should return Quotation"),
420                }
421            } else if token == "[" {
422                // Nested quotation type
423                types.push(self.parse_quotation_type(depth)?);
424            } else {
425                // Parse as concrete type
426                types.push(self.parse_type(&token)?);
427            }
428        }
429
430        Ok((row_var, types))
431    }
432
433    /// Parse a quotation type: [inputs -- outputs]
434    /// Note: The opening '[' has already been consumed
435    ///
436    /// depth: Current nesting depth (incremented for each nested quotation)
437    fn parse_quotation_type(&mut self, depth: usize) -> Result<Type, String> {
438        // Parse input stack types (until '--' or ']')
439        let (input_row_var, input_types) =
440            self.parse_type_list_until(&["--", "]"], "quotation type inputs", depth + 1)?;
441
442        // Require '--' separator for clarity
443        if !self.consume("--") {
444            // Check if user closed with ] without separator
445            if self.check("]") {
446                return Err(
447                    "Quotation types require '--' separator. Did you mean '[Int -- ]' or '[ -- Int]'?"
448                        .to_string(),
449                );
450            }
451            return Err("Expected '--' separator in quotation type".to_string());
452        }
453
454        // Parse output stack types (until ']')
455        let (output_row_var, output_types) =
456            self.parse_type_list_until(&["]"], "quotation type outputs", depth + 1)?;
457
458        // Consume ']'
459        if !self.consume("]") {
460            return Err("Expected ']' to end quotation type".to_string());
461        }
462
463        // Build input and output StackTypes
464        let inputs = self.build_stack_type(input_row_var, input_types);
465        let outputs = self.build_stack_type(output_row_var, output_types);
466
467        Ok(Type::Quotation(Box::new(Effect::new(inputs, outputs))))
468    }
469
470    /// Build a StackType from an optional row variable and a list of types
471    /// Example: row_var="a", types=[Int, Bool] => RowVar("a") with Int on top of Bool
472    ///
473    /// IMPORTANT: ALL stack effects are implicitly row-polymorphic in concatenative languages.
474    /// This means:
475    ///   ( -- )        becomes  ( ..rest -- ..rest )       - no-op, preserves stack
476    ///   ( -- Int )    becomes  ( ..rest -- ..rest Int )   - pushes Int
477    ///   ( Int -- )    becomes  ( ..rest Int -- ..rest )   - consumes Int
478    ///   ( Int -- Int) becomes  ( ..rest Int -- ..rest Int ) - transforms top
479    fn build_stack_type(&self, row_var: Option<String>, types: Vec<Type>) -> StackType {
480        // Always use row polymorphism - this is fundamental to concatenative semantics
481        let base = match row_var {
482            Some(name) => StackType::RowVar(name),
483            None => StackType::RowVar("rest".to_string()),
484        };
485
486        // Push types onto the stack (bottom to top order)
487        types.into_iter().fold(base, |stack, ty| stack.push(ty))
488    }
489
490    fn skip_comments(&mut self) {
491        loop {
492            if self.check("#") {
493                // Skip until newline
494                while !self.is_at_end() && self.current() != "\n" {
495                    self.advance();
496                }
497                if !self.is_at_end() {
498                    self.advance(); // skip newline
499                }
500            } else if self.check("\n") {
501                // Skip blank lines
502                self.advance();
503            } else {
504                break;
505            }
506        }
507    }
508
509    fn check(&self, expected: &str) -> bool {
510        if self.is_at_end() {
511            return false;
512        }
513        self.current() == expected
514    }
515
516    fn consume(&mut self, expected: &str) -> bool {
517        if self.check(expected) {
518            self.advance();
519            true
520        } else {
521            false
522        }
523    }
524
525    fn current(&self) -> &str {
526        if self.is_at_end() {
527            ""
528        } else {
529            &self.tokens[self.pos]
530        }
531    }
532
533    fn advance(&mut self) -> Option<&String> {
534        if self.is_at_end() {
535            None
536        } else {
537            let token = &self.tokens[self.pos];
538            self.pos += 1;
539            Some(token)
540        }
541    }
542
543    fn is_at_end(&self) -> bool {
544        self.pos >= self.tokens.len()
545    }
546}
547
548/// Check if a token looks like a float literal
549///
550/// Float literals contain either:
551/// - A decimal point: `3.14`, `.5`, `5.`
552/// - Scientific notation: `1e10`, `1E-5`, `1.5e3`
553///
554/// This check must happen BEFORE integer parsing to avoid
555/// parsing "5" in "5.0" as an integer.
556fn is_float_literal(token: &str) -> bool {
557    // Skip leading minus sign for negative numbers
558    let s = token.strip_prefix('-').unwrap_or(token);
559
560    // Must have at least one digit
561    if s.is_empty() {
562        return false;
563    }
564
565    // Check for decimal point or scientific notation
566    s.contains('.') || s.contains('e') || s.contains('E')
567}
568
569/// Process escape sequences in a string literal
570///
571/// Supported escape sequences:
572/// - `\"` -> `"`  (quote)
573/// - `\\` -> `\`  (backslash)
574/// - `\n` -> newline
575/// - `\r` -> carriage return
576/// - `\t` -> tab
577///
578/// # Errors
579/// Returns error if an unknown escape sequence is encountered
580fn unescape_string(s: &str) -> Result<String, String> {
581    let mut result = String::new();
582    let mut chars = s.chars();
583
584    while let Some(ch) = chars.next() {
585        if ch == '\\' {
586            match chars.next() {
587                Some('"') => result.push('"'),
588                Some('\\') => result.push('\\'),
589                Some('n') => result.push('\n'),
590                Some('r') => result.push('\r'),
591                Some('t') => result.push('\t'),
592                Some(c) => {
593                    return Err(format!(
594                        "Unknown escape sequence '\\{}' in string literal. \
595                         Supported: \\\" \\\\ \\n \\r \\t",
596                        c
597                    ));
598                }
599                None => {
600                    return Err("String ends with incomplete escape sequence '\\'".to_string());
601                }
602            }
603        } else {
604            result.push(ch);
605        }
606    }
607
608    Ok(result)
609}
610
611fn tokenize(source: &str) -> Vec<String> {
612    let mut tokens = Vec::new();
613    let mut current = String::new();
614    let mut in_string = false;
615    let mut prev_was_backslash = false;
616
617    for ch in source.chars() {
618        if in_string {
619            current.push(ch);
620            if ch == '"' && !prev_was_backslash {
621                // Unescaped quote ends the string
622                in_string = false;
623                tokens.push(current.clone());
624                current.clear();
625                prev_was_backslash = false;
626            } else if ch == '\\' && !prev_was_backslash {
627                // Start of escape sequence
628                prev_was_backslash = true;
629            } else {
630                // Regular character or escaped character
631                prev_was_backslash = false;
632            }
633        } else if ch == '"' {
634            if !current.is_empty() {
635                tokens.push(current.clone());
636                current.clear();
637            }
638            in_string = true;
639            current.push(ch);
640            prev_was_backslash = false;
641        } else if ch.is_whitespace() {
642            if !current.is_empty() {
643                tokens.push(current.clone());
644                current.clear();
645            }
646            // Preserve newlines for comment handling
647            if ch == '\n' {
648                tokens.push("\n".to_string());
649            }
650        } else if "():;[]".contains(ch) {
651            if !current.is_empty() {
652                tokens.push(current.clone());
653                current.clear();
654            }
655            tokens.push(ch.to_string());
656        } else {
657            current.push(ch);
658        }
659    }
660
661    // Check for unclosed string literal
662    if in_string {
663        // Return error by adding a special error token
664        // The parser will handle this as a parse error
665        tokens.push("<<<UNCLOSED_STRING>>>".to_string());
666    } else if !current.is_empty() {
667        tokens.push(current);
668    }
669
670    tokens
671}
672
673#[cfg(test)]
674mod tests {
675    use super::*;
676
677    #[test]
678    fn test_parse_hello_world() {
679        let source = r#"
680: main ( -- )
681  "Hello, World!" write_line ;
682"#;
683
684        let mut parser = Parser::new(source);
685        let program = parser.parse().unwrap();
686
687        assert_eq!(program.words.len(), 1);
688        assert_eq!(program.words[0].name, "main");
689        assert_eq!(program.words[0].body.len(), 2);
690
691        match &program.words[0].body[0] {
692            Statement::StringLiteral(s) => assert_eq!(s, "Hello, World!"),
693            _ => panic!("Expected StringLiteral"),
694        }
695
696        match &program.words[0].body[1] {
697            Statement::WordCall(name) => assert_eq!(name, "write_line"),
698            _ => panic!("Expected WordCall"),
699        }
700    }
701
702    #[test]
703    fn test_parse_with_numbers() {
704        let source = ": add-example ( -- ) 2 3 add ;";
705
706        let mut parser = Parser::new(source);
707        let program = parser.parse().unwrap();
708
709        assert_eq!(program.words[0].body.len(), 3);
710        assert_eq!(program.words[0].body[0], Statement::IntLiteral(2));
711        assert_eq!(program.words[0].body[1], Statement::IntLiteral(3));
712        assert_eq!(
713            program.words[0].body[2],
714            Statement::WordCall("add".to_string())
715        );
716    }
717
718    #[test]
719    fn test_parse_escaped_quotes() {
720        let source = r#": main ( -- ) "Say \"hello\" there" write_line ;"#;
721
722        let mut parser = Parser::new(source);
723        let program = parser.parse().unwrap();
724
725        assert_eq!(program.words.len(), 1);
726        assert_eq!(program.words[0].body.len(), 2);
727
728        match &program.words[0].body[0] {
729            // Escape sequences should be processed: \" becomes actual quote
730            Statement::StringLiteral(s) => assert_eq!(s, "Say \"hello\" there"),
731            _ => panic!("Expected StringLiteral with escaped quotes"),
732        }
733    }
734
735    #[test]
736    fn test_escape_sequences() {
737        let source = r#": main ( -- ) "Line 1\nLine 2\tTabbed" write_line ;"#;
738
739        let mut parser = Parser::new(source);
740        let program = parser.parse().unwrap();
741
742        match &program.words[0].body[0] {
743            Statement::StringLiteral(s) => assert_eq!(s, "Line 1\nLine 2\tTabbed"),
744            _ => panic!("Expected StringLiteral"),
745        }
746    }
747
748    #[test]
749    fn test_unknown_escape_sequence() {
750        let source = r#": main ( -- ) "Bad \x sequence" write_line ;"#;
751
752        let mut parser = Parser::new(source);
753        let result = parser.parse();
754
755        assert!(result.is_err());
756        assert!(result.unwrap_err().contains("Unknown escape sequence"));
757    }
758
759    #[test]
760    fn test_unclosed_string_literal() {
761        let source = r#": main ( -- ) "unclosed string ;"#;
762
763        let mut parser = Parser::new(source);
764        let result = parser.parse();
765
766        assert!(result.is_err());
767        assert!(result.unwrap_err().contains("Unclosed string literal"));
768    }
769
770    #[test]
771    fn test_multiple_word_definitions() {
772        let source = r#"
773: double ( Int -- Int )
774  2 multiply ;
775
776: quadruple ( Int -- Int )
777  double double ;
778"#;
779
780        let mut parser = Parser::new(source);
781        let program = parser.parse().unwrap();
782
783        assert_eq!(program.words.len(), 2);
784        assert_eq!(program.words[0].name, "double");
785        assert_eq!(program.words[1].name, "quadruple");
786
787        // Verify stack effects were parsed
788        assert!(program.words[0].effect.is_some());
789        assert!(program.words[1].effect.is_some());
790    }
791
792    #[test]
793    fn test_user_word_calling_user_word() {
794        let source = r#"
795: helper ( -- )
796  "helper called" write_line ;
797
798: main ( -- )
799  helper ;
800"#;
801
802        let mut parser = Parser::new(source);
803        let program = parser.parse().unwrap();
804
805        assert_eq!(program.words.len(), 2);
806
807        // Check main calls helper
808        match &program.words[1].body[0] {
809            Statement::WordCall(name) => assert_eq!(name, "helper"),
810            _ => panic!("Expected WordCall to helper"),
811        }
812    }
813
814    #[test]
815    fn test_parse_simple_stack_effect() {
816        // Test: ( Int -- Bool )
817        // With implicit row polymorphism, this becomes: ( ..rest Int -- ..rest Bool )
818        let source = ": test ( Int -- Bool ) 1 ;";
819        let mut parser = Parser::new(source);
820        let program = parser.parse().unwrap();
821
822        assert_eq!(program.words.len(), 1);
823        let word = &program.words[0];
824        assert!(word.effect.is_some());
825
826        let effect = word.effect.as_ref().unwrap();
827
828        // Input: Int on RowVar("rest") (implicit row polymorphism)
829        assert_eq!(
830            effect.inputs,
831            StackType::Cons {
832                rest: Box::new(StackType::RowVar("rest".to_string())),
833                top: Type::Int
834            }
835        );
836
837        // Output: Bool on RowVar("rest") (implicit row polymorphism)
838        assert_eq!(
839            effect.outputs,
840            StackType::Cons {
841                rest: Box::new(StackType::RowVar("rest".to_string())),
842                top: Type::Bool
843            }
844        );
845    }
846
847    #[test]
848    fn test_parse_row_polymorphic_stack_effect() {
849        // Test: ( ..a Int -- ..a Bool )
850        let source = ": test ( ..a Int -- ..a Bool ) 1 ;";
851        let mut parser = Parser::new(source);
852        let program = parser.parse().unwrap();
853
854        assert_eq!(program.words.len(), 1);
855        let word = &program.words[0];
856        assert!(word.effect.is_some());
857
858        let effect = word.effect.as_ref().unwrap();
859
860        // Input: Int on RowVar("a")
861        assert_eq!(
862            effect.inputs,
863            StackType::Cons {
864                rest: Box::new(StackType::RowVar("a".to_string())),
865                top: Type::Int
866            }
867        );
868
869        // Output: Bool on RowVar("a")
870        assert_eq!(
871            effect.outputs,
872            StackType::Cons {
873                rest: Box::new(StackType::RowVar("a".to_string())),
874                top: Type::Bool
875            }
876        );
877    }
878
879    #[test]
880    fn test_parse_invalid_row_var_starts_with_digit() {
881        // Test: Row variable cannot start with digit
882        let source = ": test ( ..123 Int -- ) ;";
883        let mut parser = Parser::new(source);
884        let result = parser.parse();
885
886        assert!(result.is_err());
887        let err_msg = result.unwrap_err();
888        assert!(
889            err_msg.contains("lowercase letter"),
890            "Expected error about lowercase letter, got: {}",
891            err_msg
892        );
893    }
894
895    #[test]
896    fn test_parse_invalid_row_var_starts_with_uppercase() {
897        // Test: Row variable cannot start with uppercase (that's a type variable)
898        let source = ": test ( ..Int Int -- ) ;";
899        let mut parser = Parser::new(source);
900        let result = parser.parse();
901
902        assert!(result.is_err());
903        let err_msg = result.unwrap_err();
904        assert!(
905            err_msg.contains("lowercase letter") || err_msg.contains("type name"),
906            "Expected error about lowercase letter or type name, got: {}",
907            err_msg
908        );
909    }
910
911    #[test]
912    fn test_parse_invalid_row_var_with_special_chars() {
913        // Test: Row variable cannot contain special characters
914        let source = ": test ( ..a-b Int -- ) ;";
915        let mut parser = Parser::new(source);
916        let result = parser.parse();
917
918        assert!(result.is_err());
919        let err_msg = result.unwrap_err();
920        assert!(
921            err_msg.contains("letters, numbers, and underscores")
922                || err_msg.contains("Unknown type"),
923            "Expected error about valid characters, got: {}",
924            err_msg
925        );
926    }
927
928    #[test]
929    fn test_parse_valid_row_var_with_underscore() {
930        // Test: Row variable CAN contain underscore
931        let source = ": test ( ..my_row Int -- ..my_row Bool ) ;";
932        let mut parser = Parser::new(source);
933        let result = parser.parse();
934
935        assert!(result.is_ok(), "Should accept row variable with underscore");
936    }
937
938    #[test]
939    fn test_parse_multiple_types_stack_effect() {
940        // Test: ( Int String -- Bool )
941        // With implicit row polymorphism: ( ..rest Int String -- ..rest Bool )
942        let source = ": test ( Int String -- Bool ) 1 ;";
943        let mut parser = Parser::new(source);
944        let program = parser.parse().unwrap();
945
946        let effect = program.words[0].effect.as_ref().unwrap();
947
948        // Input: String on Int on RowVar("rest")
949        let (rest, top) = effect.inputs.clone().pop().unwrap();
950        assert_eq!(top, Type::String);
951        let (rest2, top2) = rest.pop().unwrap();
952        assert_eq!(top2, Type::Int);
953        assert_eq!(rest2, StackType::RowVar("rest".to_string()));
954
955        // Output: Bool on RowVar("rest") (implicit row polymorphism)
956        assert_eq!(
957            effect.outputs,
958            StackType::Cons {
959                rest: Box::new(StackType::RowVar("rest".to_string())),
960                top: Type::Bool
961            }
962        );
963    }
964
965    #[test]
966    fn test_parse_type_variable() {
967        // Test: ( ..a T -- ..a T T ) for dup
968        let source = ": dup ( ..a T -- ..a T T ) ;";
969        let mut parser = Parser::new(source);
970        let program = parser.parse().unwrap();
971
972        let effect = program.words[0].effect.as_ref().unwrap();
973
974        // Input: T on RowVar("a")
975        assert_eq!(
976            effect.inputs,
977            StackType::Cons {
978                rest: Box::new(StackType::RowVar("a".to_string())),
979                top: Type::Var("T".to_string())
980            }
981        );
982
983        // Output: T on T on RowVar("a")
984        let (rest, top) = effect.outputs.clone().pop().unwrap();
985        assert_eq!(top, Type::Var("T".to_string()));
986        let (rest2, top2) = rest.pop().unwrap();
987        assert_eq!(top2, Type::Var("T".to_string()));
988        assert_eq!(rest2, StackType::RowVar("a".to_string()));
989    }
990
991    #[test]
992    fn test_parse_empty_stack_effect() {
993        // Test: ( -- )
994        // In concatenative languages, even empty effects are row-polymorphic
995        // ( -- ) means ( ..rest -- ..rest ) - preserves stack
996        let source = ": test ( -- ) ;";
997        let mut parser = Parser::new(source);
998        let program = parser.parse().unwrap();
999
1000        let effect = program.words[0].effect.as_ref().unwrap();
1001
1002        // Both inputs and outputs should use the same implicit row variable
1003        assert_eq!(effect.inputs, StackType::RowVar("rest".to_string()));
1004        assert_eq!(effect.outputs, StackType::RowVar("rest".to_string()));
1005    }
1006
1007    #[test]
1008    fn test_parse_invalid_type() {
1009        // Test invalid type (lowercase, not a row var)
1010        let source = ": test ( invalid -- Bool ) ;";
1011        let mut parser = Parser::new(source);
1012        let result = parser.parse();
1013
1014        assert!(result.is_err());
1015        assert!(result.unwrap_err().contains("Unknown type"));
1016    }
1017
1018    #[test]
1019    fn test_parse_unclosed_stack_effect() {
1020        // Test unclosed stack effect - parser tries to parse all tokens until ')' or EOF
1021        // In this case, it encounters "body" which is an invalid type
1022        let source = ": test ( Int -- Bool body ;";
1023        let mut parser = Parser::new(source);
1024        let result = parser.parse();
1025
1026        assert!(result.is_err());
1027        let err_msg = result.unwrap_err();
1028        // Parser will try to parse "body" as a type and fail
1029        assert!(err_msg.contains("Unknown type"));
1030    }
1031
1032    #[test]
1033    fn test_parse_simple_quotation_type() {
1034        // Test: ( [Int -- Int] -- )
1035        let source = ": apply ( [Int -- Int] -- ) ;";
1036        let mut parser = Parser::new(source);
1037        let program = parser.parse().unwrap();
1038
1039        let effect = program.words[0].effect.as_ref().unwrap();
1040
1041        // Input should be: Quotation(Int -- Int) on RowVar("rest")
1042        let (rest, top) = effect.inputs.clone().pop().unwrap();
1043        match top {
1044            Type::Quotation(quot_effect) => {
1045                // Check quotation's input: Int on RowVar("rest")
1046                assert_eq!(
1047                    quot_effect.inputs,
1048                    StackType::Cons {
1049                        rest: Box::new(StackType::RowVar("rest".to_string())),
1050                        top: Type::Int
1051                    }
1052                );
1053                // Check quotation's output: Int on RowVar("rest")
1054                assert_eq!(
1055                    quot_effect.outputs,
1056                    StackType::Cons {
1057                        rest: Box::new(StackType::RowVar("rest".to_string())),
1058                        top: Type::Int
1059                    }
1060                );
1061            }
1062            _ => panic!("Expected Quotation type, got {:?}", top),
1063        }
1064        assert_eq!(rest, StackType::RowVar("rest".to_string()));
1065    }
1066
1067    #[test]
1068    fn test_parse_quotation_type_with_row_vars() {
1069        // Test: ( ..a [..a T -- ..a Bool] -- ..a )
1070        let source = ": test ( ..a [..a T -- ..a Bool] -- ..a ) ;";
1071        let mut parser = Parser::new(source);
1072        let program = parser.parse().unwrap();
1073
1074        let effect = program.words[0].effect.as_ref().unwrap();
1075
1076        // Input: Quotation on RowVar("a")
1077        let (rest, top) = effect.inputs.clone().pop().unwrap();
1078        match top {
1079            Type::Quotation(quot_effect) => {
1080                // Check quotation's input: T on RowVar("a")
1081                let (q_in_rest, q_in_top) = quot_effect.inputs.clone().pop().unwrap();
1082                assert_eq!(q_in_top, Type::Var("T".to_string()));
1083                assert_eq!(q_in_rest, StackType::RowVar("a".to_string()));
1084
1085                // Check quotation's output: Bool on RowVar("a")
1086                let (q_out_rest, q_out_top) = quot_effect.outputs.clone().pop().unwrap();
1087                assert_eq!(q_out_top, Type::Bool);
1088                assert_eq!(q_out_rest, StackType::RowVar("a".to_string()));
1089            }
1090            _ => panic!("Expected Quotation type, got {:?}", top),
1091        }
1092        assert_eq!(rest, StackType::RowVar("a".to_string()));
1093    }
1094
1095    #[test]
1096    fn test_parse_nested_quotation_type() {
1097        // Test: ( [[Int -- Int] -- Bool] -- )
1098        let source = ": nested ( [[Int -- Int] -- Bool] -- ) ;";
1099        let mut parser = Parser::new(source);
1100        let program = parser.parse().unwrap();
1101
1102        let effect = program.words[0].effect.as_ref().unwrap();
1103
1104        // Input: Quotation([Int -- Int] -- Bool) on RowVar("rest")
1105        let (_, top) = effect.inputs.clone().pop().unwrap();
1106        match top {
1107            Type::Quotation(outer_effect) => {
1108                // Outer quotation input: [Int -- Int] on RowVar("rest")
1109                let (_, outer_in_top) = outer_effect.inputs.clone().pop().unwrap();
1110                match outer_in_top {
1111                    Type::Quotation(inner_effect) => {
1112                        // Inner quotation: Int -- Int
1113                        assert!(matches!(
1114                            inner_effect.inputs.clone().pop().unwrap().1,
1115                            Type::Int
1116                        ));
1117                        assert!(matches!(
1118                            inner_effect.outputs.clone().pop().unwrap().1,
1119                            Type::Int
1120                        ));
1121                    }
1122                    _ => panic!("Expected nested Quotation type"),
1123                }
1124
1125                // Outer quotation output: Bool
1126                let (_, outer_out_top) = outer_effect.outputs.clone().pop().unwrap();
1127                assert_eq!(outer_out_top, Type::Bool);
1128            }
1129            _ => panic!("Expected Quotation type"),
1130        }
1131    }
1132
1133    #[test]
1134    fn test_parse_deeply_nested_quotation_type_exceeds_limit() {
1135        // Test: Deeply nested quotation types should fail with max depth error
1136        // Build a quotation type nested 35 levels deep (exceeds MAX_QUOTATION_DEPTH = 32)
1137        let mut source = String::from(": deep ( ");
1138
1139        // Build opening brackets: [[[[[[...
1140        for _ in 0..35 {
1141            source.push_str("[ -- ");
1142        }
1143
1144        source.push_str("Int");
1145
1146        // Build closing brackets: ...]]]]]]
1147        for _ in 0..35 {
1148            source.push_str(" ]");
1149        }
1150
1151        source.push_str(" -- ) ;");
1152
1153        let mut parser = Parser::new(&source);
1154        let result = parser.parse();
1155
1156        // Should fail with depth limit error
1157        assert!(result.is_err());
1158        let err_msg = result.unwrap_err();
1159        assert!(
1160            err_msg.contains("depth") || err_msg.contains("32"),
1161            "Expected depth limit error, got: {}",
1162            err_msg
1163        );
1164    }
1165
1166    #[test]
1167    fn test_parse_empty_quotation_type() {
1168        // Test: ( [ -- ] -- )
1169        // An empty quotation type is also row-polymorphic: [ ..rest -- ..rest ]
1170        let source = ": empty-quot ( [ -- ] -- ) ;";
1171        let mut parser = Parser::new(source);
1172        let program = parser.parse().unwrap();
1173
1174        let effect = program.words[0].effect.as_ref().unwrap();
1175
1176        let (_, top) = effect.inputs.clone().pop().unwrap();
1177        match top {
1178            Type::Quotation(quot_effect) => {
1179                // Empty quotation preserves the stack (row-polymorphic)
1180                assert_eq!(quot_effect.inputs, StackType::RowVar("rest".to_string()));
1181                assert_eq!(quot_effect.outputs, StackType::RowVar("rest".to_string()));
1182            }
1183            _ => panic!("Expected Quotation type"),
1184        }
1185    }
1186
1187    #[test]
1188    fn test_parse_quotation_type_in_output() {
1189        // Test: ( -- [Int -- Int] )
1190        let source = ": maker ( -- [Int -- Int] ) ;";
1191        let mut parser = Parser::new(source);
1192        let program = parser.parse().unwrap();
1193
1194        let effect = program.words[0].effect.as_ref().unwrap();
1195
1196        // Output should be: Quotation(Int -- Int) on RowVar("rest")
1197        let (_, top) = effect.outputs.clone().pop().unwrap();
1198        match top {
1199            Type::Quotation(quot_effect) => {
1200                assert!(matches!(
1201                    quot_effect.inputs.clone().pop().unwrap().1,
1202                    Type::Int
1203                ));
1204                assert!(matches!(
1205                    quot_effect.outputs.clone().pop().unwrap().1,
1206                    Type::Int
1207                ));
1208            }
1209            _ => panic!("Expected Quotation type"),
1210        }
1211    }
1212
1213    #[test]
1214    fn test_parse_unclosed_quotation_type() {
1215        // Test: ( [Int -- Int -- )  (missing ])
1216        let source = ": broken ( [Int -- Int -- ) ;";
1217        let mut parser = Parser::new(source);
1218        let result = parser.parse();
1219
1220        assert!(result.is_err());
1221        let err_msg = result.unwrap_err();
1222        // Parser might error with various messages depending on where it fails
1223        // It should at least indicate a parsing problem
1224        assert!(
1225            err_msg.contains("Unclosed")
1226                || err_msg.contains("Expected")
1227                || err_msg.contains("Unexpected"),
1228            "Got error: {}",
1229            err_msg
1230        );
1231    }
1232
1233    #[test]
1234    fn test_parse_multiple_quotation_types() {
1235        // Test: ( [Int -- Int] [String -- Bool] -- )
1236        let source = ": multi ( [Int -- Int] [String -- Bool] -- ) ;";
1237        let mut parser = Parser::new(source);
1238        let program = parser.parse().unwrap();
1239
1240        let effect = program.words[0].effect.as_ref().unwrap();
1241
1242        // Pop second quotation (String -- Bool)
1243        let (rest, top) = effect.inputs.clone().pop().unwrap();
1244        match top {
1245            Type::Quotation(quot_effect) => {
1246                assert!(matches!(
1247                    quot_effect.inputs.clone().pop().unwrap().1,
1248                    Type::String
1249                ));
1250                assert!(matches!(
1251                    quot_effect.outputs.clone().pop().unwrap().1,
1252                    Type::Bool
1253                ));
1254            }
1255            _ => panic!("Expected Quotation type"),
1256        }
1257
1258        // Pop first quotation (Int -- Int)
1259        let (_, top2) = rest.pop().unwrap();
1260        match top2 {
1261            Type::Quotation(quot_effect) => {
1262                assert!(matches!(
1263                    quot_effect.inputs.clone().pop().unwrap().1,
1264                    Type::Int
1265                ));
1266                assert!(matches!(
1267                    quot_effect.outputs.clone().pop().unwrap().1,
1268                    Type::Int
1269                ));
1270            }
1271            _ => panic!("Expected Quotation type"),
1272        }
1273    }
1274
1275    #[test]
1276    fn test_parse_quotation_type_without_separator() {
1277        // Test: ( [Int] -- ) should be REJECTED
1278        //
1279        // Design decision: The '--' separator is REQUIRED for clarity.
1280        // [Int] looks like a list type in most languages, not a consumer function.
1281        // This would confuse users.
1282        //
1283        // Require explicit syntax:
1284        // - `[Int -- ]` for quotation that consumes Int and produces nothing
1285        // - `[ -- Int]` for quotation that produces Int
1286        // - `[Int -- Int]` for transformation
1287        let source = ": consumer ( [Int] -- ) ;";
1288        let mut parser = Parser::new(source);
1289        let result = parser.parse();
1290
1291        // Should fail with helpful error message
1292        assert!(result.is_err());
1293        let err_msg = result.unwrap_err();
1294        assert!(
1295            err_msg.contains("require") && err_msg.contains("--"),
1296            "Expected error about missing '--' separator, got: {}",
1297            err_msg
1298        );
1299    }
1300
1301    #[test]
1302    fn test_parse_no_stack_effect() {
1303        // Test word without stack effect (should still work)
1304        let source = ": test 1 2 add ;";
1305        let mut parser = Parser::new(source);
1306        let program = parser.parse().unwrap();
1307
1308        assert_eq!(program.words.len(), 1);
1309        assert!(program.words[0].effect.is_none());
1310    }
1311
1312    #[test]
1313    fn test_parse_simple_quotation() {
1314        let source = r#"
1315: test ( -- Quot )
1316  [ 1 add ] ;
1317"#;
1318
1319        let mut parser = Parser::new(source);
1320        let program = parser.parse().unwrap();
1321
1322        assert_eq!(program.words.len(), 1);
1323        assert_eq!(program.words[0].name, "test");
1324        assert_eq!(program.words[0].body.len(), 1);
1325
1326        match &program.words[0].body[0] {
1327            Statement::Quotation { body, .. } => {
1328                assert_eq!(body.len(), 2);
1329                assert_eq!(body[0], Statement::IntLiteral(1));
1330                assert_eq!(body[1], Statement::WordCall("add".to_string()));
1331            }
1332            _ => panic!("Expected Quotation statement"),
1333        }
1334    }
1335
1336    #[test]
1337    fn test_parse_empty_quotation() {
1338        let source = ": test [ ] ;";
1339
1340        let mut parser = Parser::new(source);
1341        let program = parser.parse().unwrap();
1342
1343        assert_eq!(program.words.len(), 1);
1344
1345        match &program.words[0].body[0] {
1346            Statement::Quotation { body, .. } => {
1347                assert_eq!(body.len(), 0);
1348            }
1349            _ => panic!("Expected Quotation statement"),
1350        }
1351    }
1352
1353    #[test]
1354    fn test_parse_quotation_with_call() {
1355        let source = r#"
1356: test ( -- )
1357  5 [ 1 add ] call ;
1358"#;
1359
1360        let mut parser = Parser::new(source);
1361        let program = parser.parse().unwrap();
1362
1363        assert_eq!(program.words.len(), 1);
1364        assert_eq!(program.words[0].body.len(), 3);
1365
1366        assert_eq!(program.words[0].body[0], Statement::IntLiteral(5));
1367
1368        match &program.words[0].body[1] {
1369            Statement::Quotation { body, .. } => {
1370                assert_eq!(body.len(), 2);
1371            }
1372            _ => panic!("Expected Quotation"),
1373        }
1374
1375        assert_eq!(
1376            program.words[0].body[2],
1377            Statement::WordCall("call".to_string())
1378        );
1379    }
1380
1381    #[test]
1382    fn test_parse_nested_quotation() {
1383        let source = ": test [ [ 1 add ] call ] ;";
1384
1385        let mut parser = Parser::new(source);
1386        let program = parser.parse().unwrap();
1387
1388        assert_eq!(program.words.len(), 1);
1389
1390        match &program.words[0].body[0] {
1391            Statement::Quotation {
1392                body: outer_body, ..
1393            } => {
1394                assert_eq!(outer_body.len(), 2);
1395
1396                match &outer_body[0] {
1397                    Statement::Quotation {
1398                        body: inner_body, ..
1399                    } => {
1400                        assert_eq!(inner_body.len(), 2);
1401                        assert_eq!(inner_body[0], Statement::IntLiteral(1));
1402                        assert_eq!(inner_body[1], Statement::WordCall("add".to_string()));
1403                    }
1404                    _ => panic!("Expected nested Quotation"),
1405                }
1406
1407                assert_eq!(outer_body[1], Statement::WordCall("call".to_string()));
1408            }
1409            _ => panic!("Expected Quotation"),
1410        }
1411    }
1412
1413    #[test]
1414    fn test_parse_while_with_quotations() {
1415        let source = r#"
1416: countdown ( Int -- )
1417  [ dup 0 > ] [ 1 subtract ] while drop ;
1418"#;
1419
1420        let mut parser = Parser::new(source);
1421        let program = parser.parse().unwrap();
1422
1423        assert_eq!(program.words.len(), 1);
1424        assert_eq!(program.words[0].body.len(), 4);
1425
1426        // First quotation: [ dup 0 > ]
1427        match &program.words[0].body[0] {
1428            Statement::Quotation { body: pred, .. } => {
1429                assert_eq!(pred.len(), 3);
1430                assert_eq!(pred[0], Statement::WordCall("dup".to_string()));
1431                assert_eq!(pred[1], Statement::IntLiteral(0));
1432                assert_eq!(pred[2], Statement::WordCall(">".to_string()));
1433            }
1434            _ => panic!("Expected predicate quotation"),
1435        }
1436
1437        // Second quotation: [ 1 subtract ]
1438        match &program.words[0].body[1] {
1439            Statement::Quotation { body, .. } => {
1440                assert_eq!(body.len(), 2);
1441                assert_eq!(body[0], Statement::IntLiteral(1));
1442                assert_eq!(body[1], Statement::WordCall("subtract".to_string()));
1443            }
1444            _ => panic!("Expected body quotation"),
1445        }
1446
1447        // while call
1448        assert_eq!(
1449            program.words[0].body[2],
1450            Statement::WordCall("while".to_string())
1451        );
1452
1453        // drop
1454        assert_eq!(
1455            program.words[0].body[3],
1456            Statement::WordCall("drop".to_string())
1457        );
1458    }
1459
1460    #[test]
1461    fn test_parse_simple_closure_type() {
1462        // Test: ( Int -- Closure[Int -- Int] )
1463        let source = ": make-adder ( Int -- Closure[Int -- Int] ) ;";
1464        let mut parser = Parser::new(source);
1465        let program = parser.parse().unwrap();
1466
1467        assert_eq!(program.words.len(), 1);
1468        let word = &program.words[0];
1469        assert!(word.effect.is_some());
1470
1471        let effect = word.effect.as_ref().unwrap();
1472
1473        // Input: Int on RowVar("rest")
1474        let (input_rest, input_top) = effect.inputs.clone().pop().unwrap();
1475        assert_eq!(input_top, Type::Int);
1476        assert_eq!(input_rest, StackType::RowVar("rest".to_string()));
1477
1478        // Output: Closure[Int -- Int] on RowVar("rest")
1479        let (output_rest, output_top) = effect.outputs.clone().pop().unwrap();
1480        match output_top {
1481            Type::Closure { effect, captures } => {
1482                // Closure effect: Int -> Int
1483                assert_eq!(
1484                    effect.inputs,
1485                    StackType::Cons {
1486                        rest: Box::new(StackType::RowVar("rest".to_string())),
1487                        top: Type::Int
1488                    }
1489                );
1490                assert_eq!(
1491                    effect.outputs,
1492                    StackType::Cons {
1493                        rest: Box::new(StackType::RowVar("rest".to_string())),
1494                        top: Type::Int
1495                    }
1496                );
1497                // Captures should be empty (filled in by type checker)
1498                assert_eq!(captures.len(), 0);
1499            }
1500            _ => panic!("Expected Closure type, got {:?}", output_top),
1501        }
1502        assert_eq!(output_rest, StackType::RowVar("rest".to_string()));
1503    }
1504
1505    #[test]
1506    fn test_parse_closure_type_with_row_vars() {
1507        // Test: ( ..a Config -- ..a Closure[Request -- Response] )
1508        let source = ": make-handler ( ..a Config -- ..a Closure[Request -- Response] ) ;";
1509        let mut parser = Parser::new(source);
1510        let program = parser.parse().unwrap();
1511
1512        let effect = program.words[0].effect.as_ref().unwrap();
1513
1514        // Output: Closure on RowVar("a")
1515        let (rest, top) = effect.outputs.clone().pop().unwrap();
1516        match top {
1517            Type::Closure { effect, .. } => {
1518                // Closure effect: Request -> Response
1519                let (_, in_top) = effect.inputs.clone().pop().unwrap();
1520                assert_eq!(in_top, Type::Var("Request".to_string()));
1521                let (_, out_top) = effect.outputs.clone().pop().unwrap();
1522                assert_eq!(out_top, Type::Var("Response".to_string()));
1523            }
1524            _ => panic!("Expected Closure type"),
1525        }
1526        assert_eq!(rest, StackType::RowVar("a".to_string()));
1527    }
1528
1529    #[test]
1530    fn test_parse_closure_type_missing_bracket() {
1531        // Test: ( Int -- Closure ) should fail
1532        let source = ": broken ( Int -- Closure ) ;";
1533        let mut parser = Parser::new(source);
1534        let result = parser.parse();
1535
1536        assert!(result.is_err());
1537        let err_msg = result.unwrap_err();
1538        assert!(
1539            err_msg.contains("[") && err_msg.contains("Closure"),
1540            "Expected error about missing '[' after Closure, got: {}",
1541            err_msg
1542        );
1543    }
1544
1545    #[test]
1546    fn test_parse_closure_type_in_input() {
1547        // Test: ( Closure[Int -- Int] -- )
1548        let source = ": apply-closure ( Closure[Int -- Int] -- ) ;";
1549        let mut parser = Parser::new(source);
1550        let program = parser.parse().unwrap();
1551
1552        let effect = program.words[0].effect.as_ref().unwrap();
1553
1554        // Input: Closure[Int -- Int] on RowVar("rest")
1555        let (_, top) = effect.inputs.clone().pop().unwrap();
1556        match top {
1557            Type::Closure { effect, .. } => {
1558                // Verify closure effect
1559                assert!(matches!(effect.inputs.clone().pop().unwrap().1, Type::Int));
1560                assert!(matches!(effect.outputs.clone().pop().unwrap().1, Type::Int));
1561            }
1562            _ => panic!("Expected Closure type in input"),
1563        }
1564    }
1565}