use super::*;
use crate::lexer::Lexer;
fn parse_input(input: &str) -> Result<Program, Box<CompileError>> {
let mut lexer = Lexer::new(input);
let tokens = lexer.tokenize();
let mut parser = Parser::new(tokens);
parser.parse()
}
#[test]
fn set_assignment_keeps_to_as_separator() {
let input = r#"Set start to 1."#;
let result = parse_input(input).expect("Set assignment should parse");
assert_eq!(result.statements.len(), 1);
match &result.statements[0] {
Statement::VarDecl { name, value, .. } => {
assert_eq!(name, "start");
assert!(matches!(value, Some(Expr::IntegerLit(1))));
}
other => panic!("Expected VarDecl, got {:?}", other),
}
}
#[test]
fn for_each_range_keeps_to_as_bound() {
let input = r#"For each number from 1 to 3, print the number."#;
let result = parse_input(input).expect("for-each range should parse");
assert_eq!(result.statements.len(), 1);
match &result.statements[0] {
Statement::ForRange {
variable: _,
range: Expr::Range { start, end, inclusive },
body,
} => {
assert!(matches!(start.as_ref(), Expr::IntegerLit(1)));
assert!(matches!(end.as_ref(), Expr::IntegerLit(3)));
assert!(matches!(inclusive, true));
assert_eq!(body.len(), 1);
}
other => panic!("Expected ForRange, got {:?}", other),
}
}
#[test]
fn function_call_with_to_connector_parses() {
let input = r#"To greet with a text called name. Return a text, name."#;
let result = parse_input(input).expect("function definition should parse");
assert_eq!(result.statements.len(), 1);
match &result.statements[0] {
Statement::FunctionDef { name, params, .. } => {
assert_eq!(name, "greet");
assert_eq!(params.len(), 1);
assert_eq!(params[0].0, "name");
}
other => panic!("Expected FunctionDef, got {:?}", other),
}
let call_input = r#"greet to "world"."#;
let call_result = parse_input(call_input).expect("call with 'to' should parse");
match &call_result.statements[0] {
Statement::FunctionCall { name, args } => {
assert_eq!(name, "greet");
assert_eq!(args.len(), 1);
assert!(matches!(
&args[0],
Expr::StringLit(s) if s == "world"
));
}
other => panic!("Expected FunctionCall, got {:?}", other),
}
}
#[test]
fn set_value_to_nested_call_with_to_connector() {
let input = r#"Set x to calc to 3."#;
let result = parse_input(input).expect("Set with nested 'to' call should parse");
assert_eq!(result.statements.len(), 1);
match &result.statements[0] {
Statement::VarDecl { name, value, .. } => {
assert_eq!(name, "x");
assert!(matches!(
value,
Some(Expr::FunctionCall { name: callee, args })
if callee == "calc" && args.len() == 1 && matches!(&args[0], Expr::IntegerLit(3)
)));
}
other => panic!("Expected VarDecl with nested FunctionCall value, got {:?}", other),
}
}
#[test]
fn identifier_range_bounds_do_not_become_calls() {
let input = "Set start to 1.\nSet end to 3.\nFor each number from start to end, print the number.\n";
let result = parse_input(input).expect("identifier range bounds should parse");
assert_eq!(result.statements.len(), 3);
match &result.statements[2] {
Statement::ForRange {
range: Expr::Range { start, end, .. },
..
} => {
assert!(matches!(start.as_ref(), Expr::Identifier(s) if s == "start"));
assert!(matches!(end.as_ref(), Expr::Identifier(s) if s == "end"));
}
other => panic!("Expected ForRange with identifier bounds, got {:?}", other),
}
}
#[test]
fn append_each_from_identifier_source_keeps_to_separator() {
let input = "append each x from source to dest.";
let result = parse_input(input).expect("append each from identifier source should parse");
assert_eq!(result.statements.len(), 1);
match &result.statements[0] {
Statement::ForEach { collection, .. } => {
assert!(matches!(collection, Expr::Identifier(s) if s == "source"));
}
other => panic!("Expected ForEach wrapping the append, got {:?}", other),
}
}
#[test]
fn element_index_identifier_keeps_of_separator() {
let input = "a number called item is element j of items.";
let result = parse_input(input).expect("element N of with identifier index should parse");
assert_eq!(result.statements.len(), 1);
match &result.statements[0] {
Statement::VarDecl {
value: Some(Expr::ElementAccess { list, index }),
..
} => {
assert!(matches!(index.as_ref(), Expr::Identifier(s) if s == "j"));
assert!(matches!(list.as_ref(), Expr::Identifier(s) if s == "items"));
}
other => panic!("Expected VarDecl with ElementAccess value, got {:?}", other),
}
}
#[test]
fn set_element_and_byte_identifier_index_keeps_of_separator() {
let result = parse_input("Set element j of items to 5.")
.expect("Set element N of with identifier index should parse");
match &result.statements[0] {
Statement::ElementSet { list, index, .. } => {
assert!(matches!(index, Expr::Identifier(s) if s == "j"));
assert_eq!(list, "items");
}
other => panic!("Expected ElementSet, got {:?}", other),
}
let result = parse_input("Set byte i of buf to 5.")
.expect("Set byte N of with identifier index should parse");
match &result.statements[0] {
Statement::ByteSet { buffer, index, .. } => {
assert!(matches!(index, Expr::Identifier(s) if s == "i"));
assert_eq!(buffer, "buf");
}
other => panic!("Expected ByteSet, got {:?}", other),
}
}
#[test]
fn connectors_and_reserved_words_coexist_in_one_program() {
let input = r#"
To greet with a text called name. Return a text, name.
Set start to 1.
Set end to 3.
For each number from start to end, print the number.
append each x from source to dest.
a number called item is element j of items.
Set element k of items to 9.
Set byte b of buf to 9.
greet to "world".
"#;
let result = parse_input(input).expect("connectors and reserved words should coexist");
assert_eq!(result.statements.len(), 9);
assert!(matches!(result.statements[0], Statement::FunctionDef { .. }));
assert!(matches!(result.statements[1], Statement::VarDecl { .. }));
assert!(matches!(result.statements[2], Statement::VarDecl { .. }));
assert!(matches!(
&result.statements[3],
Statement::ForRange { range: Expr::Range { start, end, .. }, .. }
if matches!(start.as_ref(), Expr::Identifier(s) if s == "start")
&& matches!(end.as_ref(), Expr::Identifier(s) if s == "end")
));
assert!(matches!(
&result.statements[4],
Statement::ForEach { collection, .. }
if matches!(collection, Expr::Identifier(s) if s == "source")
));
assert!(matches!(
&result.statements[5],
Statement::VarDecl { value: Some(Expr::ElementAccess { .. }), .. }
));
assert!(matches!(result.statements[6], Statement::ElementSet { .. }));
assert!(matches!(result.statements[7], Statement::ByteSet { .. }));
assert!(matches!(
&result.statements[8],
Statement::FunctionCall { name, args }
if name == "greet" && args.len() == 1
));
}
#[test]
fn braced_multi_arg_call_as_byte_index_uses_own_of_connector() {
let input = "a number called v is byte {ci of 1 and 2} of buf.";
let result = parse_input(input).expect("braced call as byte index should parse");
match &result.statements[0] {
Statement::VarDecl { value: Some(Expr::ByteAccess { buffer, index }), .. } => {
assert!(matches!(buffer.as_ref(), Expr::Identifier(s) if s == "buf"));
assert!(matches!(
index.as_ref(),
Expr::FunctionCall { name, args }
if name == "ci" && args.len() == 2
));
}
other => panic!("Expected VarDecl with ByteAccess value, got {:?}", other),
}
}
#[test]
fn braced_single_arg_call_as_byte_index_uses_own_of_connector() {
let input = "a number called v is byte {id of 3} of buf.";
let result = parse_input(input).expect("braced single-arg call as byte index should parse");
match &result.statements[0] {
Statement::VarDecl { value: Some(Expr::ByteAccess { index, .. }), .. } => {
assert!(matches!(
index.as_ref(),
Expr::FunctionCall { name, args }
if name == "id" && args.len() == 1
));
}
other => panic!("Expected VarDecl with ByteAccess value, got {:?}", other),
}
}
#[test]
fn braced_call_as_element_index_uses_own_of_connector() {
let input = "a number called v is element {idfn of 2} of lst.";
let result = parse_input(input).expect("braced call as element index should parse");
match &result.statements[0] {
Statement::VarDecl { value: Some(Expr::ElementAccess { list, index }), .. } => {
assert!(matches!(list.as_ref(), Expr::Identifier(s) if s == "lst"));
assert!(matches!(
index.as_ref(),
Expr::FunctionCall { name, args }
if name == "idfn" && args.len() == 1
));
}
other => panic!("Expected VarDecl with ElementAccess value, got {:?}", other),
}
}
#[test]
fn braced_call_uses_own_to_connector_in_range_start_bound() {
let input = "For each number from {calc to 3} to 10, print the number.";
let result = parse_input(input).expect("braced call in range start bound should parse");
match &result.statements[0] {
Statement::ForRange { range: Expr::Range { start, end, .. }, .. } => {
assert!(matches!(
start.as_ref(),
Expr::FunctionCall { name, args }
if name == "calc" && args.len() == 1
));
assert!(matches!(end.as_ref(), Expr::IntegerLit(10)));
}
other => panic!("Expected ForRange, got {:?}", other),
}
}
#[test]
fn map_literal_value_inside_suppressed_of_context_uses_own_of_connector() {
let input = "a number called v is byte {\"k\": ci of 1 and 2} of buf.";
let result = parse_input(input).expect("map literal as byte index should parse");
match &result.statements[0] {
Statement::VarDecl { value: Some(Expr::ByteAccess { index, .. }), .. } => {
match index.as_ref() {
Expr::MapLit { pairs } => {
assert_eq!(pairs.len(), 1);
assert!(matches!(&pairs[0].0, Expr::StringLit(s) if s == "k"));
assert!(matches!(
&pairs[0].1,
Expr::FunctionCall { name, args }
if name == "ci" && args.len() == 2
));
}
other => panic!("Expected MapLit index, got {:?}", other),
}
}
other => panic!("Expected VarDecl with ByteAccess value, got {:?}", other),
}
}
#[test]
fn braced_group_coexists_with_reserved_connectors_in_one_program() {
let input = r#"
Set start to 1.
Set end to 3.
For each number from start to end, print the number.
append each x from source to dest.
a number called item is element j of items.
Set element k of items to 9.
Set byte b of buf to 9.
a number called computed is byte {ci of 1 and 2} of buf.
"#;
let result = parse_input(input).expect("braced group should coexist with reserved words");
assert_eq!(result.statements.len(), 8);
assert!(matches!(
&result.statements[2],
Statement::ForRange { range: Expr::Range { start, end, .. }, .. }
if matches!(start.as_ref(), Expr::Identifier(s) if s == "start")
&& matches!(end.as_ref(), Expr::Identifier(s) if s == "end")
));
assert!(matches!(
&result.statements[3],
Statement::ForEach { collection, .. }
if matches!(collection, Expr::Identifier(s) if s == "source")
));
assert!(matches!(
&result.statements[4],
Statement::VarDecl { value: Some(Expr::ElementAccess { index, .. }), .. }
if matches!(index.as_ref(), Expr::Identifier(s) if s == "j")
));
assert!(matches!(result.statements[5], Statement::ElementSet { .. }));
assert!(matches!(result.statements[6], Statement::ByteSet { .. }));
assert!(matches!(
&result.statements[7],
Statement::VarDecl { value: Some(Expr::ByteAccess { index, .. }), .. }
if matches!(
index.as_ref(),
Expr::FunctionCall { name, args }
if name == "ci" && args.len() == 2
)
));
}