use super::*;
use crate::ast::{Pattern, Statement};
use crate::types::{StackType, Type};
#[test]
fn test_parse_hello_world() {
let source = r#"
: main ( -- )
"Hello, World!" write_line ;
"#;
let mut parser = Parser::new(source);
let program = parser.parse().unwrap();
assert_eq!(program.words.len(), 1);
assert_eq!(program.words[0].name, "main");
assert_eq!(program.words[0].body.len(), 2);
match &program.words[0].body[0] {
Statement::StringLiteral(s) => assert_eq!(s, "Hello, World!"),
_ => panic!("Expected StringLiteral"),
}
match &program.words[0].body[1] {
Statement::WordCall { name, .. } => assert_eq!(name, "write_line"),
_ => panic!("Expected WordCall"),
}
}
#[test]
fn test_parse_with_numbers() {
let source = ": add-example ( -- ) 2 3 add ;";
let mut parser = Parser::new(source);
let program = parser.parse().unwrap();
assert_eq!(program.words[0].body.len(), 3);
assert_eq!(program.words[0].body[0], Statement::IntLiteral(2));
assert_eq!(program.words[0].body[1], Statement::IntLiteral(3));
assert!(matches!(
&program.words[0].body[2],
Statement::WordCall { name, .. } if name == "add"
));
}
#[test]
fn test_parse_hex_literals() {
let source = ": test ( -- ) 0xFF 0x10 0X1A ;";
let mut parser = Parser::new(source);
let program = parser.parse().unwrap();
assert_eq!(program.words[0].body[0], Statement::IntLiteral(255));
assert_eq!(program.words[0].body[1], Statement::IntLiteral(16));
assert_eq!(program.words[0].body[2], Statement::IntLiteral(26));
}
#[test]
fn test_parse_binary_literals() {
let source = ": test ( -- ) 0b1010 0B1111 0b0 ;";
let mut parser = Parser::new(source);
let program = parser.parse().unwrap();
assert_eq!(program.words[0].body[0], Statement::IntLiteral(10));
assert_eq!(program.words[0].body[1], Statement::IntLiteral(15));
assert_eq!(program.words[0].body[2], Statement::IntLiteral(0));
}
#[test]
fn test_parse_invalid_hex_literal() {
let source = ": test ( -- ) 0xGG ;";
let mut parser = Parser::new(source);
let err = parser.parse().unwrap_err();
assert!(err.contains("Invalid hex literal"));
}
#[test]
fn test_parse_invalid_binary_literal() {
let source = ": test ( -- ) 0b123 ;";
let mut parser = Parser::new(source);
let err = parser.parse().unwrap_err();
assert!(err.contains("Invalid binary literal"));
}
#[test]
fn test_parse_escaped_quotes() {
let source = r#": main ( -- ) "Say \"hello\" there" write_line ;"#;
let mut parser = Parser::new(source);
let program = parser.parse().unwrap();
assert_eq!(program.words.len(), 1);
assert_eq!(program.words[0].body.len(), 2);
match &program.words[0].body[0] {
Statement::StringLiteral(s) => assert_eq!(s, "Say \"hello\" there"),
_ => panic!("Expected StringLiteral with escaped quotes"),
}
}
#[test]
fn test_escaped_quote_at_end_of_string() {
let source = r#": main ( -- ) "hello\"" io.write-line ;"#;
let mut parser = Parser::new(source);
let program = parser.parse().unwrap();
assert_eq!(program.words.len(), 1);
match &program.words[0].body[0] {
Statement::StringLiteral(s) => assert_eq!(s, "hello\""),
_ => panic!("Expected StringLiteral ending with escaped quote"),
}
}
#[test]
fn test_escaped_quote_at_start_of_string() {
let source = r#": main ( -- ) "\"hello" io.write-line ;"#;
let mut parser = Parser::new(source);
let program = parser.parse().unwrap();
match &program.words[0].body[0] {
Statement::StringLiteral(s) => assert_eq!(s, "\"hello"),
_ => panic!("Expected StringLiteral starting with escaped quote"),
}
}
#[test]
fn test_escape_sequences() {
let source = r#": main ( -- ) "Line 1\nLine 2\tTabbed" write_line ;"#;
let mut parser = Parser::new(source);
let program = parser.parse().unwrap();
match &program.words[0].body[0] {
Statement::StringLiteral(s) => assert_eq!(s, "Line 1\nLine 2\tTabbed"),
_ => panic!("Expected StringLiteral"),
}
}
#[test]
fn test_unknown_escape_sequence() {
let source = r#": main ( -- ) "Bad \q sequence" write_line ;"#;
let mut parser = Parser::new(source);
let result = parser.parse();
assert!(result.is_err());
assert!(result.unwrap_err().contains("Unknown escape sequence"));
}
#[test]
fn test_hex_escape_sequence() {
let source = r#": main ( -- ) "\x1b[2K\x41" io.write-line ;"#;
let mut parser = Parser::new(source);
let program = parser.parse().unwrap();
match &program.words[0].body[0] {
Statement::StringLiteral(s) => {
assert_eq!(s.len(), 5); assert_eq!(s.as_bytes()[0], 0x1b); assert_eq!(s.as_bytes()[4], 0x41); }
_ => panic!("Expected StringLiteral"),
}
}
#[test]
fn test_hex_escape_null_byte() {
let source = r#": main ( -- ) "before\x00after" io.write-line ;"#;
let mut parser = Parser::new(source);
let program = parser.parse().unwrap();
match &program.words[0].body[0] {
Statement::StringLiteral(s) => {
assert_eq!(s.len(), 12); assert_eq!(s.as_bytes()[6], 0x00);
}
_ => panic!("Expected StringLiteral"),
}
}
#[test]
fn test_hex_escape_uppercase() {
let source = r#": main ( -- ) "\x41\x42\x4F" io.write-line ;"#;
let mut parser = Parser::new(source);
let program = parser.parse().unwrap();
match &program.words[0].body[0] {
Statement::StringLiteral(s) => {
assert_eq!(s, "ABO"); }
_ => panic!("Expected StringLiteral"),
}
}
#[test]
fn test_hex_escape_high_bytes() {
let source = r#": main ( -- ) "\xFF" io.write-line ;"#;
let mut parser = Parser::new(source);
let program = parser.parse().unwrap();
match &program.words[0].body[0] {
Statement::StringLiteral(s) => {
assert_eq!(s, "\u{00FF}");
assert_eq!(s.chars().next().unwrap(), 'ÿ');
}
_ => panic!("Expected StringLiteral"),
}
}
#[test]
fn test_hex_escape_incomplete() {
let source = r#": main ( -- ) "\x1" io.write-line ;"#;
let mut parser = Parser::new(source);
let result = parser.parse();
assert!(result.is_err());
assert!(result.unwrap_err().contains("Incomplete hex escape"));
}
#[test]
fn test_hex_escape_invalid_digits() {
let source = r#": main ( -- ) "\xGG" io.write-line ;"#;
let mut parser = Parser::new(source);
let result = parser.parse();
assert!(result.is_err());
assert!(result.unwrap_err().contains("Invalid hex escape"));
}
#[test]
fn test_hex_escape_at_end_of_string() {
let source = r#": main ( -- ) "test\x" io.write-line ;"#;
let mut parser = Parser::new(source);
let result = parser.parse();
assert!(result.is_err());
assert!(result.unwrap_err().contains("Incomplete hex escape"));
}
#[test]
fn test_unclosed_string_literal() {
let source = r#": main ( -- ) "unclosed string ;"#;
let mut parser = Parser::new(source);
let result = parser.parse();
assert!(result.is_err());
let err_msg = result.unwrap_err();
assert!(err_msg.contains("Unclosed string literal"));
assert!(
err_msg.contains("line 1"),
"Expected line number in error: {}",
err_msg
);
assert!(
err_msg.contains("column 15"),
"Expected column number in error: {}",
err_msg
);
}
#[test]
fn test_multiple_word_definitions() {
let source = r#"
: double ( Int -- Int )
2 multiply ;
: quadruple ( Int -- Int )
double double ;
"#;
let mut parser = Parser::new(source);
let program = parser.parse().unwrap();
assert_eq!(program.words.len(), 2);
assert_eq!(program.words[0].name, "double");
assert_eq!(program.words[1].name, "quadruple");
assert!(program.words[0].effect.is_some());
assert!(program.words[1].effect.is_some());
}
#[test]
fn test_user_word_calling_user_word() {
let source = r#"
: helper ( -- )
"helper called" write_line ;
: main ( -- )
helper ;
"#;
let mut parser = Parser::new(source);
let program = parser.parse().unwrap();
assert_eq!(program.words.len(), 2);
match &program.words[1].body[0] {
Statement::WordCall { name, .. } => assert_eq!(name, "helper"),
_ => panic!("Expected WordCall to helper"),
}
}
#[test]
fn test_parse_simple_stack_effect() {
let source = ": test ( Int -- Bool ) 1 ;";
let mut parser = Parser::new(source);
let program = parser.parse().unwrap();
assert_eq!(program.words.len(), 1);
let word = &program.words[0];
assert!(word.effect.is_some());
let effect = word.effect.as_ref().unwrap();
assert_eq!(
effect.inputs,
StackType::Cons {
rest: Box::new(StackType::RowVar("rest".to_string())),
top: Type::Int
}
);
assert_eq!(
effect.outputs,
StackType::Cons {
rest: Box::new(StackType::RowVar("rest".to_string())),
top: Type::Bool
}
);
}
#[test]
fn test_parse_row_polymorphic_stack_effect() {
let source = ": test ( ..a Int -- ..a Bool ) 1 ;";
let mut parser = Parser::new(source);
let program = parser.parse().unwrap();
assert_eq!(program.words.len(), 1);
let word = &program.words[0];
assert!(word.effect.is_some());
let effect = word.effect.as_ref().unwrap();
assert_eq!(
effect.inputs,
StackType::Cons {
rest: Box::new(StackType::RowVar("a".to_string())),
top: Type::Int
}
);
assert_eq!(
effect.outputs,
StackType::Cons {
rest: Box::new(StackType::RowVar("a".to_string())),
top: Type::Bool
}
);
}
#[test]
fn test_parse_invalid_row_var_starts_with_digit() {
let source = ": test ( ..123 Int -- ) ;";
let mut parser = Parser::new(source);
let result = parser.parse();
assert!(result.is_err());
let err_msg = result.unwrap_err();
assert!(
err_msg.contains("lowercase letter"),
"Expected error about lowercase letter, got: {}",
err_msg
);
}
#[test]
fn test_parse_invalid_row_var_starts_with_uppercase() {
let source = ": test ( ..Int Int -- ) ;";
let mut parser = Parser::new(source);
let result = parser.parse();
assert!(result.is_err());
let err_msg = result.unwrap_err();
assert!(
err_msg.contains("lowercase letter") || err_msg.contains("type name"),
"Expected error about lowercase letter or type name, got: {}",
err_msg
);
}
#[test]
fn test_parse_invalid_row_var_with_special_chars() {
let source = ": test ( ..a-b Int -- ) ;";
let mut parser = Parser::new(source);
let result = parser.parse();
assert!(result.is_err());
let err_msg = result.unwrap_err();
assert!(
err_msg.contains("letters, numbers, and underscores") || err_msg.contains("Unknown type"),
"Expected error about valid characters, got: {}",
err_msg
);
}
#[test]
fn test_parse_valid_row_var_with_underscore() {
let source = ": test ( ..my_row Int -- ..my_row Bool ) ;";
let mut parser = Parser::new(source);
let result = parser.parse();
assert!(result.is_ok(), "Should accept row variable with underscore");
}
#[test]
fn test_parse_multiple_types_stack_effect() {
let source = ": test ( Int String -- Bool ) 1 ;";
let mut parser = Parser::new(source);
let program = parser.parse().unwrap();
let effect = program.words[0].effect.as_ref().unwrap();
let (rest, top) = effect.inputs.clone().pop().unwrap();
assert_eq!(top, Type::String);
let (rest2, top2) = rest.pop().unwrap();
assert_eq!(top2, Type::Int);
assert_eq!(rest2, StackType::RowVar("rest".to_string()));
assert_eq!(
effect.outputs,
StackType::Cons {
rest: Box::new(StackType::RowVar("rest".to_string())),
top: Type::Bool
}
);
}
#[test]
fn test_parse_type_variable() {
let source = ": dup ( ..a T -- ..a T T ) ;";
let mut parser = Parser::new(source);
let program = parser.parse().unwrap();
let effect = program.words[0].effect.as_ref().unwrap();
assert_eq!(
effect.inputs,
StackType::Cons {
rest: Box::new(StackType::RowVar("a".to_string())),
top: Type::Var("T".to_string())
}
);
let (rest, top) = effect.outputs.clone().pop().unwrap();
assert_eq!(top, Type::Var("T".to_string()));
let (rest2, top2) = rest.pop().unwrap();
assert_eq!(top2, Type::Var("T".to_string()));
assert_eq!(rest2, StackType::RowVar("a".to_string()));
}
#[test]
fn test_parse_empty_stack_effect() {
let source = ": test ( -- ) ;";
let mut parser = Parser::new(source);
let program = parser.parse().unwrap();
let effect = program.words[0].effect.as_ref().unwrap();
assert_eq!(effect.inputs, StackType::RowVar("rest".to_string()));
assert_eq!(effect.outputs, StackType::RowVar("rest".to_string()));
}
#[test]
fn test_parse_invalid_type() {
let source = ": test ( invalid -- Bool ) ;";
let mut parser = Parser::new(source);
let result = parser.parse();
assert!(result.is_err());
assert!(result.unwrap_err().contains("Unknown type"));
}
#[test]
fn test_parse_unclosed_stack_effect() {
let source = ": test ( Int -- Bool body ;";
let mut parser = Parser::new(source);
let result = parser.parse();
assert!(result.is_err());
let err_msg = result.unwrap_err();
assert!(err_msg.contains("Unknown type"));
}
#[test]
fn test_parse_simple_quotation_type() {
let source = ": apply ( [Int -- Int] -- ) ;";
let mut parser = Parser::new(source);
let program = parser.parse().unwrap();
let effect = program.words[0].effect.as_ref().unwrap();
let (rest, top) = effect.inputs.clone().pop().unwrap();
match top {
Type::Quotation(quot_effect) => {
assert_eq!(
quot_effect.inputs,
StackType::Cons {
rest: Box::new(StackType::RowVar("rest".to_string())),
top: Type::Int
}
);
assert_eq!(
quot_effect.outputs,
StackType::Cons {
rest: Box::new(StackType::RowVar("rest".to_string())),
top: Type::Int
}
);
}
_ => panic!("Expected Quotation type, got {:?}", top),
}
assert_eq!(rest, StackType::RowVar("rest".to_string()));
}
#[test]
fn test_parse_quotation_type_with_row_vars() {
let source = ": test ( ..a [..a T -- ..a Bool] -- ..a ) ;";
let mut parser = Parser::new(source);
let program = parser.parse().unwrap();
let effect = program.words[0].effect.as_ref().unwrap();
let (rest, top) = effect.inputs.clone().pop().unwrap();
match top {
Type::Quotation(quot_effect) => {
let (q_in_rest, q_in_top) = quot_effect.inputs.clone().pop().unwrap();
assert_eq!(q_in_top, Type::Var("T".to_string()));
assert_eq!(q_in_rest, StackType::RowVar("a".to_string()));
let (q_out_rest, q_out_top) = quot_effect.outputs.clone().pop().unwrap();
assert_eq!(q_out_top, Type::Bool);
assert_eq!(q_out_rest, StackType::RowVar("a".to_string()));
}
_ => panic!("Expected Quotation type, got {:?}", top),
}
assert_eq!(rest, StackType::RowVar("a".to_string()));
}
#[test]
fn test_parse_nested_quotation_type() {
let source = ": nested ( [[Int -- Int] -- Bool] -- ) ;";
let mut parser = Parser::new(source);
let program = parser.parse().unwrap();
let effect = program.words[0].effect.as_ref().unwrap();
let (_, top) = effect.inputs.clone().pop().unwrap();
match top {
Type::Quotation(outer_effect) => {
let (_, outer_in_top) = outer_effect.inputs.clone().pop().unwrap();
match outer_in_top {
Type::Quotation(inner_effect) => {
assert!(matches!(
inner_effect.inputs.clone().pop().unwrap().1,
Type::Int
));
assert!(matches!(
inner_effect.outputs.clone().pop().unwrap().1,
Type::Int
));
}
_ => panic!("Expected nested Quotation type"),
}
let (_, outer_out_top) = outer_effect.outputs.clone().pop().unwrap();
assert_eq!(outer_out_top, Type::Bool);
}
_ => panic!("Expected Quotation type"),
}
}
#[test]
fn test_parse_deeply_nested_quotation_type_exceeds_limit() {
let mut source = String::from(": deep ( ");
for _ in 0..35 {
source.push_str("[ -- ");
}
source.push_str("Int");
for _ in 0..35 {
source.push_str(" ]");
}
source.push_str(" -- ) ;");
let mut parser = Parser::new(&source);
let result = parser.parse();
assert!(result.is_err());
let err_msg = result.unwrap_err();
assert!(
err_msg.contains("depth") || err_msg.contains("32"),
"Expected depth limit error, got: {}",
err_msg
);
}
#[test]
fn test_parse_empty_quotation_type() {
let source = ": empty-quot ( [ -- ] -- ) ;";
let mut parser = Parser::new(source);
let program = parser.parse().unwrap();
let effect = program.words[0].effect.as_ref().unwrap();
let (_, top) = effect.inputs.clone().pop().unwrap();
match top {
Type::Quotation(quot_effect) => {
assert_eq!(quot_effect.inputs, StackType::RowVar("rest".to_string()));
assert_eq!(quot_effect.outputs, StackType::RowVar("rest".to_string()));
}
_ => panic!("Expected Quotation type"),
}
}
#[test]
fn test_parse_quotation_type_in_output() {
let source = ": maker ( -- [Int -- Int] ) ;";
let mut parser = Parser::new(source);
let program = parser.parse().unwrap();
let effect = program.words[0].effect.as_ref().unwrap();
let (_, top) = effect.outputs.clone().pop().unwrap();
match top {
Type::Quotation(quot_effect) => {
assert!(matches!(
quot_effect.inputs.clone().pop().unwrap().1,
Type::Int
));
assert!(matches!(
quot_effect.outputs.clone().pop().unwrap().1,
Type::Int
));
}
_ => panic!("Expected Quotation type"),
}
}
#[test]
fn test_parse_unclosed_quotation_type() {
let source = ": broken ( [Int -- Int -- ) ;";
let mut parser = Parser::new(source);
let result = parser.parse();
assert!(result.is_err());
let err_msg = result.unwrap_err();
assert!(
err_msg.contains("Unclosed")
|| err_msg.contains("Expected")
|| err_msg.contains("Unexpected"),
"Got error: {}",
err_msg
);
}
#[test]
fn test_parse_multiple_quotation_types() {
let source = ": multi ( [Int -- Int] [String -- Bool] -- ) ;";
let mut parser = Parser::new(source);
let program = parser.parse().unwrap();
let effect = program.words[0].effect.as_ref().unwrap();
let (rest, top) = effect.inputs.clone().pop().unwrap();
match top {
Type::Quotation(quot_effect) => {
assert!(matches!(
quot_effect.inputs.clone().pop().unwrap().1,
Type::String
));
assert!(matches!(
quot_effect.outputs.clone().pop().unwrap().1,
Type::Bool
));
}
_ => panic!("Expected Quotation type"),
}
let (_, top2) = rest.pop().unwrap();
match top2 {
Type::Quotation(quot_effect) => {
assert!(matches!(
quot_effect.inputs.clone().pop().unwrap().1,
Type::Int
));
assert!(matches!(
quot_effect.outputs.clone().pop().unwrap().1,
Type::Int
));
}
_ => panic!("Expected Quotation type"),
}
}
#[test]
fn test_parse_quotation_type_without_separator() {
let source = ": consumer ( [Int] -- ) ;";
let mut parser = Parser::new(source);
let result = parser.parse();
assert!(result.is_err());
let err_msg = result.unwrap_err();
assert!(
err_msg.contains("require") && err_msg.contains("--"),
"Expected error about missing '--' separator, got: {}",
err_msg
);
}
#[test]
fn test_parse_bare_quotation_type_rejected() {
let source = ": apply-twice ( Int Quotation -- Int ) ;";
let mut parser = Parser::new(source);
let result = parser.parse();
assert!(result.is_err());
let err_msg = result.unwrap_err();
assert!(
err_msg.contains("Quotation") && err_msg.contains("not a valid type"),
"Expected error about 'Quotation' not being valid, got: {}",
err_msg
);
assert!(
err_msg.contains("[Int -- Int]") || err_msg.contains("[ -- ]"),
"Expected error to suggest explicit syntax, got: {}",
err_msg
);
}
#[test]
fn test_parse_no_stack_effect() {
let source = ": test 1 2 add ;";
let mut parser = Parser::new(source);
let program = parser.parse().unwrap();
assert_eq!(program.words.len(), 1);
assert!(program.words[0].effect.is_none());
}
#[test]
fn test_parse_simple_quotation() {
let source = r#"
: test ( -- Quot )
[ 1 add ] ;
"#;
let mut parser = Parser::new(source);
let program = parser.parse().unwrap();
assert_eq!(program.words.len(), 1);
assert_eq!(program.words[0].name, "test");
assert_eq!(program.words[0].body.len(), 1);
match &program.words[0].body[0] {
Statement::Quotation { body, .. } => {
assert_eq!(body.len(), 2);
assert_eq!(body[0], Statement::IntLiteral(1));
assert!(matches!(&body[1], Statement::WordCall { name, .. } if name == "add"));
}
_ => panic!("Expected Quotation statement"),
}
}
#[test]
fn test_parse_empty_quotation() {
let source = ": test [ ] ;";
let mut parser = Parser::new(source);
let program = parser.parse().unwrap();
assert_eq!(program.words.len(), 1);
match &program.words[0].body[0] {
Statement::Quotation { body, .. } => {
assert_eq!(body.len(), 0);
}
_ => panic!("Expected Quotation statement"),
}
}
#[test]
fn test_parse_quotation_with_call() {
let source = r#"
: test ( -- )
5 [ 1 add ] call ;
"#;
let mut parser = Parser::new(source);
let program = parser.parse().unwrap();
assert_eq!(program.words.len(), 1);
assert_eq!(program.words[0].body.len(), 3);
assert_eq!(program.words[0].body[0], Statement::IntLiteral(5));
match &program.words[0].body[1] {
Statement::Quotation { body, .. } => {
assert_eq!(body.len(), 2);
}
_ => panic!("Expected Quotation"),
}
assert!(matches!(
&program.words[0].body[2],
Statement::WordCall { name, .. } if name == "call"
));
}
#[test]
fn test_parse_nested_quotation() {
let source = ": test [ [ 1 add ] call ] ;";
let mut parser = Parser::new(source);
let program = parser.parse().unwrap();
assert_eq!(program.words.len(), 1);
match &program.words[0].body[0] {
Statement::Quotation {
body: outer_body, ..
} => {
assert_eq!(outer_body.len(), 2);
match &outer_body[0] {
Statement::Quotation {
body: inner_body, ..
} => {
assert_eq!(inner_body.len(), 2);
assert_eq!(inner_body[0], Statement::IntLiteral(1));
assert!(
matches!(&inner_body[1], Statement::WordCall { name, .. } if name == "add")
);
}
_ => panic!("Expected nested Quotation"),
}
assert!(matches!(&outer_body[1], Statement::WordCall { name, .. } if name == "call"));
}
_ => panic!("Expected Quotation"),
}
}
#[test]
fn test_parse_while_with_quotations() {
let source = r#"
: countdown ( Int -- )
[ dup 0 > ] [ 1 subtract ] while drop ;
"#;
let mut parser = Parser::new(source);
let program = parser.parse().unwrap();
assert_eq!(program.words.len(), 1);
assert_eq!(program.words[0].body.len(), 4);
match &program.words[0].body[0] {
Statement::Quotation { body: pred, .. } => {
assert_eq!(pred.len(), 3);
assert!(matches!(&pred[0], Statement::WordCall { name, .. } if name == "dup"));
assert_eq!(pred[1], Statement::IntLiteral(0));
assert!(matches!(&pred[2], Statement::WordCall { name, .. } if name == ">"));
}
_ => panic!("Expected predicate quotation"),
}
match &program.words[0].body[1] {
Statement::Quotation { body, .. } => {
assert_eq!(body.len(), 2);
assert_eq!(body[0], Statement::IntLiteral(1));
assert!(matches!(&body[1], Statement::WordCall { name, .. } if name == "subtract"));
}
_ => panic!("Expected body quotation"),
}
assert!(matches!(
&program.words[0].body[2],
Statement::WordCall { name, .. } if name == "while"
));
assert!(matches!(
&program.words[0].body[3],
Statement::WordCall { name, .. } if name == "drop"
));
}
#[test]
fn test_parse_simple_closure_type() {
let source = ": make-adder ( Int -- Closure[Int -- Int] ) ;";
let mut parser = Parser::new(source);
let program = parser.parse().unwrap();
assert_eq!(program.words.len(), 1);
let word = &program.words[0];
assert!(word.effect.is_some());
let effect = word.effect.as_ref().unwrap();
let (input_rest, input_top) = effect.inputs.clone().pop().unwrap();
assert_eq!(input_top, Type::Int);
assert_eq!(input_rest, StackType::RowVar("rest".to_string()));
let (output_rest, output_top) = effect.outputs.clone().pop().unwrap();
match output_top {
Type::Closure { effect, captures } => {
assert_eq!(
effect.inputs,
StackType::Cons {
rest: Box::new(StackType::RowVar("rest".to_string())),
top: Type::Int
}
);
assert_eq!(
effect.outputs,
StackType::Cons {
rest: Box::new(StackType::RowVar("rest".to_string())),
top: Type::Int
}
);
assert_eq!(captures.len(), 0);
}
_ => panic!("Expected Closure type, got {:?}", output_top),
}
assert_eq!(output_rest, StackType::RowVar("rest".to_string()));
}
#[test]
fn test_parse_closure_type_with_row_vars() {
let source = ": make-handler ( ..a Config -- ..a Closure[Request -- Response] ) ;";
let mut parser = Parser::new(source);
let program = parser.parse().unwrap();
let effect = program.words[0].effect.as_ref().unwrap();
let (rest, top) = effect.outputs.clone().pop().unwrap();
match top {
Type::Closure { effect, .. } => {
let (_, in_top) = effect.inputs.clone().pop().unwrap();
assert_eq!(in_top, Type::Var("Request".to_string()));
let (_, out_top) = effect.outputs.clone().pop().unwrap();
assert_eq!(out_top, Type::Var("Response".to_string()));
}
_ => panic!("Expected Closure type"),
}
assert_eq!(rest, StackType::RowVar("a".to_string()));
}
#[test]
fn test_parse_closure_type_missing_bracket() {
let source = ": broken ( Int -- Closure ) ;";
let mut parser = Parser::new(source);
let result = parser.parse();
assert!(result.is_err());
let err_msg = result.unwrap_err();
assert!(
err_msg.contains("[") && err_msg.contains("Closure"),
"Expected error about missing '[' after Closure, got: {}",
err_msg
);
}
#[test]
fn test_parse_closure_type_in_input() {
let source = ": apply-closure ( Closure[Int -- Int] -- ) ;";
let mut parser = Parser::new(source);
let program = parser.parse().unwrap();
let effect = program.words[0].effect.as_ref().unwrap();
let (_, top) = effect.inputs.clone().pop().unwrap();
match top {
Type::Closure { effect, .. } => {
assert!(matches!(effect.inputs.clone().pop().unwrap().1, Type::Int));
assert!(matches!(effect.outputs.clone().pop().unwrap().1, Type::Int));
}
_ => panic!("Expected Closure type in input"),
}
}
#[test]
fn test_token_position_single_line() {
let source = ": main ( -- ) ;";
let tokens = tokenize(source);
assert_eq!(tokens[0].text, ":");
assert_eq!(tokens[0].line, 0);
assert_eq!(tokens[0].column, 0);
assert_eq!(tokens[1].text, "main");
assert_eq!(tokens[1].line, 0);
assert_eq!(tokens[1].column, 2);
assert_eq!(tokens[2].text, "(");
assert_eq!(tokens[2].line, 0);
assert_eq!(tokens[2].column, 7);
}
#[test]
fn test_token_position_multiline() {
let source = ": main ( -- )\n 42\n;";
let tokens = tokenize(source);
let token_42 = tokens.iter().find(|t| t.text == "42").unwrap();
assert_eq!(token_42.line, 1);
assert_eq!(token_42.column, 2);
let token_semi = tokens.iter().find(|t| t.text == ";").unwrap();
assert_eq!(token_semi.line, 2);
assert_eq!(token_semi.column, 0);
}
#[test]
fn test_word_def_source_location_span() {
let source = r#": helper ( -- )
"hello"
write_line
;
: main ( -- )
helper
;"#;
let mut parser = Parser::new(source);
let program = parser.parse().unwrap();
assert_eq!(program.words.len(), 2);
let helper = &program.words[0];
assert_eq!(helper.name, "helper");
let helper_source = helper.source.as_ref().unwrap();
assert_eq!(helper_source.start_line, 0);
assert_eq!(helper_source.end_line, 3);
let main_word = &program.words[1];
assert_eq!(main_word.name, "main");
let main_source = main_word.source.as_ref().unwrap();
assert_eq!(main_source.start_line, 5);
assert_eq!(main_source.end_line, 7);
}
#[test]
fn test_token_position_string_with_newline() {
let source = "\"line1\\nline2\"";
let tokens = tokenize(source);
assert_eq!(tokens.len(), 1);
assert_eq!(tokens[0].line, 0);
assert_eq!(tokens[0].column, 0);
}
#[test]
fn test_parse_simple_union() {
let source = r#"
union Message {
Get { response-chan: Int }
Set { value: Int }
}
: main ( -- ) ;
"#;
let mut parser = Parser::new(source);
let program = parser.parse().unwrap();
assert_eq!(program.unions.len(), 1);
let union_def = &program.unions[0];
assert_eq!(union_def.name, "Message");
assert_eq!(union_def.variants.len(), 2);
assert_eq!(union_def.variants[0].name, "Get");
assert_eq!(union_def.variants[0].fields.len(), 1);
assert_eq!(union_def.variants[0].fields[0].name, "response-chan");
assert_eq!(union_def.variants[0].fields[0].type_name, "Int");
assert_eq!(union_def.variants[1].name, "Set");
assert_eq!(union_def.variants[1].fields.len(), 1);
assert_eq!(union_def.variants[1].fields[0].name, "value");
assert_eq!(union_def.variants[1].fields[0].type_name, "Int");
}
#[test]
fn test_parse_union_with_multiple_fields() {
let source = r#"
union Report {
Data { op: Int, delta: Int, total: Int }
Empty
}
: main ( -- ) ;
"#;
let mut parser = Parser::new(source);
let program = parser.parse().unwrap();
assert_eq!(program.unions.len(), 1);
let union_def = &program.unions[0];
assert_eq!(union_def.name, "Report");
assert_eq!(union_def.variants.len(), 2);
let data_variant = &union_def.variants[0];
assert_eq!(data_variant.name, "Data");
assert_eq!(data_variant.fields.len(), 3);
assert_eq!(data_variant.fields[0].name, "op");
assert_eq!(data_variant.fields[1].name, "delta");
assert_eq!(data_variant.fields[2].name, "total");
let empty_variant = &union_def.variants[1];
assert_eq!(empty_variant.name, "Empty");
assert_eq!(empty_variant.fields.len(), 0);
}
#[test]
fn test_parse_union_lowercase_name_error() {
let source = r#"
union message {
Get { }
}
"#;
let mut parser = Parser::new(source);
let result = parser.parse();
assert!(result.is_err());
assert!(result.unwrap_err().contains("uppercase"));
}
#[test]
fn test_parse_union_empty_error() {
let source = r#"
union Message {
}
"#;
let mut parser = Parser::new(source);
let result = parser.parse();
assert!(result.is_err());
assert!(result.unwrap_err().contains("at least one variant"));
}
#[test]
fn test_parse_union_duplicate_variant_error() {
let source = r#"
union Message {
Get { x: Int }
Get { y: String }
}
"#;
let mut parser = Parser::new(source);
let result = parser.parse();
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.contains("Duplicate variant name"));
assert!(err.contains("Get"));
}
#[test]
fn test_parse_union_duplicate_field_error() {
let source = r#"
union Data {
Record { x: Int, x: String }
}
"#;
let mut parser = Parser::new(source);
let result = parser.parse();
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.contains("Duplicate field name"));
assert!(err.contains("x"));
}
#[test]
fn test_parse_simple_match() {
let source = r#"
: handle ( -- )
match
Get -> send-response
Set -> process-set
end
;
"#;
let mut parser = Parser::new(source);
let program = parser.parse().unwrap();
assert_eq!(program.words.len(), 1);
assert_eq!(program.words[0].body.len(), 1);
match &program.words[0].body[0] {
Statement::Match { arms, span: _ } => {
assert_eq!(arms.len(), 2);
match &arms[0].pattern {
Pattern::Variant(name) => assert_eq!(name, "Get"),
_ => panic!("Expected Variant pattern"),
}
assert_eq!(arms[0].body.len(), 1);
match &arms[1].pattern {
Pattern::Variant(name) => assert_eq!(name, "Set"),
_ => panic!("Expected Variant pattern"),
}
assert_eq!(arms[1].body.len(), 1);
}
_ => panic!("Expected Match statement"),
}
}
#[test]
fn test_parse_match_with_bindings() {
let source = r#"
: handle ( -- )
match
Get { >chan } -> chan send-response
Report { >delta >total } -> delta total process
end
;
"#;
let mut parser = Parser::new(source);
let program = parser.parse().unwrap();
assert_eq!(program.words.len(), 1);
match &program.words[0].body[0] {
Statement::Match { arms, span: _ } => {
assert_eq!(arms.len(), 2);
match &arms[0].pattern {
Pattern::VariantWithBindings { name, bindings } => {
assert_eq!(name, "Get");
assert_eq!(bindings.len(), 1);
assert_eq!(bindings[0], "chan");
}
_ => panic!("Expected VariantWithBindings pattern"),
}
match &arms[1].pattern {
Pattern::VariantWithBindings { name, bindings } => {
assert_eq!(name, "Report");
assert_eq!(bindings.len(), 2);
assert_eq!(bindings[0], "delta");
assert_eq!(bindings[1], "total");
}
_ => panic!("Expected VariantWithBindings pattern"),
}
}
_ => panic!("Expected Match statement"),
}
}
#[test]
fn test_parse_match_bindings_require_prefix() {
let source = r#"
: handle ( -- )
match
Get { chan } -> chan send-response
end
;
"#;
let mut parser = Parser::new(source);
let result = parser.parse();
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.contains(">chan"));
assert!(err.contains("stack extraction"));
}
#[test]
fn test_parse_match_with_body_statements() {
let source = r#"
: handle ( -- )
match
Get -> 1 2 add send-response
Set -> process-value store
end
;
"#;
let mut parser = Parser::new(source);
let program = parser.parse().unwrap();
match &program.words[0].body[0] {
Statement::Match { arms, span: _ } => {
assert_eq!(arms[0].body.len(), 4);
assert_eq!(arms[0].body[0], Statement::IntLiteral(1));
assert_eq!(arms[0].body[1], Statement::IntLiteral(2));
assert!(matches!(&arms[0].body[2], Statement::WordCall { name, .. } if name == "add"));
assert_eq!(arms[1].body.len(), 2);
}
_ => panic!("Expected Match statement"),
}
}
#[test]
fn test_parse_match_empty_error() {
let source = r#"
: handle ( -- )
match
end
;
"#;
let mut parser = Parser::new(source);
let result = parser.parse();
assert!(result.is_err());
assert!(result.unwrap_err().contains("at least one arm"));
}
#[test]
fn test_parse_symbol_literal() {
let source = r#"
: main ( -- )
:hello drop
;
"#;
let mut parser = Parser::new(source);
let program = parser.parse().unwrap();
assert_eq!(program.words.len(), 1);
let main = &program.words[0];
assert_eq!(main.body.len(), 2);
match &main.body[0] {
Statement::Symbol(name) => assert_eq!(name, "hello"),
_ => panic!("Expected Symbol statement, got {:?}", main.body[0]),
}
}
#[test]
fn test_parse_symbol_with_hyphen() {
let source = r#"
: main ( -- )
:hello-world drop
;
"#;
let mut parser = Parser::new(source);
let program = parser.parse().unwrap();
match &program.words[0].body[0] {
Statement::Symbol(name) => assert_eq!(name, "hello-world"),
_ => panic!("Expected Symbol statement"),
}
}
#[test]
fn test_parse_symbol_starting_with_digit_fails() {
let source = r#"
: main ( -- )
:123abc drop
;
"#;
let mut parser = Parser::new(source);
let result = parser.parse();
assert!(result.is_err());
assert!(result.unwrap_err().contains("cannot start with a digit"));
}
#[test]
fn test_parse_symbol_with_invalid_char_fails() {
let source = r#"
: main ( -- )
:hello@world drop
;
"#;
let mut parser = Parser::new(source);
let result = parser.parse();
assert!(result.is_err());
assert!(result.unwrap_err().contains("invalid character"));
}
#[test]
fn test_parse_symbol_special_chars_allowed() {
let source = r#"
: main ( -- )
:empty? drop
:save! drop
;
"#;
let mut parser = Parser::new(source);
let program = parser.parse().unwrap();
match &program.words[0].body[0] {
Statement::Symbol(name) => assert_eq!(name, "empty?"),
_ => panic!("Expected Symbol statement"),
}
match &program.words[0].body[2] {
Statement::Symbol(name) => assert_eq!(name, "save!"),
_ => panic!("Expected Symbol statement"),
}
}