use dist_agent_lang::lexer::tokens::Literal;
use dist_agent_lang::parse_source;
use dist_agent_lang::parser::ast::{Expression, Statement};
use dist_agent_lang::{Lexer, Parser};
#[test]
fn test_parser_empty_input() {
let result = parse_source("");
assert!(
result.is_ok(),
"Empty input should parse successfully (catches loop bound mutations)"
);
let program = result.unwrap();
assert_eq!(
program.statements.len(),
0,
"Empty input should produce empty program (catches loop bound mutations)"
);
}
#[test]
fn test_parser_whitespace_only() {
let result = parse_source(" \n\t ");
assert!(result.is_ok(), "Whitespace-only input should parse");
let program = result.unwrap();
assert_eq!(
program.statements.len(),
0,
"Whitespace-only should produce empty program"
);
}
#[test]
fn test_parser_single_semicolon() {
let result = parse_source(";");
assert!(result.is_ok(), "Single semicolon should parse");
let program = result.unwrap();
assert!(
!program.statements.is_empty(),
"Semicolon produces statement"
);
}
#[test]
fn test_parser_single_number() {
let result = parse_source("42");
assert!(result.is_ok(), "Single number should parse");
let program = result.unwrap();
assert_eq!(
program.statements.len(),
1,
"Should have exactly 1 statement"
);
}
#[test]
fn test_parser_single_identifier() {
let result = parse_source("x");
assert!(result.is_ok(), "Single identifier should parse");
let program = result.unwrap();
assert_eq!(
program.statements.len(),
1,
"Should have exactly 1 statement"
);
}
#[test]
fn test_parser_array_literal() {
let code = "let arr = [1, 2, 3];";
let program = parse_source(code).unwrap();
assert!(!program.statements.is_empty());
if let Statement::Let(ref let_stmt) = program.statements[0] {
match &let_stmt.value {
Expression::ArrayLiteral(elements) => {
assert_eq!(elements.len(), 3, "[1,2,3] should produce 3 elements");
}
_ => panic!(
"Expected ArrayLiteral from [1,2,3], got {:?}",
let_stmt.value
),
}
}
}
#[test]
fn test_parser_object_literal() {
let code = r#"let m = {"a": 1, "b": 2};"#;
let program = parse_source(code).unwrap();
assert!(!program.statements.is_empty());
if let Statement::Let(ref let_stmt) = program.statements[0] {
match &let_stmt.value {
Expression::ObjectLiteral(map) => {
assert_eq!(
map.len(),
2,
r#"{{"a": 1, "b": 2}} should produce 2 key-value pairs"#
);
}
_ => panic!(
"Expected ObjectLiteral from object literal, got {:?}",
let_stmt.value
),
}
}
}
#[test]
fn test_parser_invalid_input_returns_error() {
let result = parse_source("let x = ");
assert!(
result.is_err(),
"Incomplete let statement should fail to parse"
);
}
#[test]
fn test_parser_invalid_syntax_returns_error() {
let result = parse_source("let = 1;");
assert!(
result.is_err(),
"Invalid let (missing identifier) should fail"
);
}
#[test]
fn test_parser_with_recovery_on_invalid_input() {
let code = "let a = 1; let b = ;";
let tokens = Lexer::new(code)
.tokenize_with_positions_immutable()
.unwrap();
let mut parser = Parser::new_with_positions(tokens);
let (program, errors) = parser.parse_with_recovery();
assert!(
!errors.is_empty(),
"Should collect parse errors from invalid statement"
);
assert!(
!program.statements.is_empty(),
"Should parse valid statements before error"
);
}
#[test]
fn test_parser_service_declaration() {
let code = "service Foo { }";
let program = parse_source(code).unwrap();
assert!(!program.statements.is_empty());
assert!(matches!(program.statements[0], Statement::Service(_)));
if let Statement::Service(ref s) = program.statements[0] {
assert_eq!(s.name, "Foo");
}
}
#[test]
fn test_parser_await_expression() {
let code = "let x = await some_call();";
let program = parse_source(code).unwrap();
assert!(!program.statements.is_empty());
if let Statement::Let(ref let_stmt) = program.statements[0] {
assert!(matches!(&let_stmt.value, Expression::Await(_)));
}
}
#[test]
fn test_parser_vec_macro() {
let code = "let arr = vec!(1, 2, 3);";
let result = parse_source(code);
if result.is_err() {
return;
}
let program = result.unwrap();
assert!(!program.statements.is_empty());
if let Statement::Let(ref let_stmt) = program.statements[0] {
match &let_stmt.value {
Expression::ArrayLiteral(elements) => {
assert_eq!(
elements.len(),
3,
"vec!(1,2,3) should produce ArrayLiteral with 3 elements"
);
}
Expression::FunctionCall(call) => {
assert_eq!(
call.name, "vec!",
"If not ArrayLiteral, should be FunctionCall with name 'vec!'"
);
assert_eq!(call.arguments.len(), 3);
}
_ => panic!(
"Expected ArrayLiteral or FunctionCall from vec!(1,2,3), got {:?}",
let_stmt.value
),
}
}
}
#[test]
fn test_parser_map_macro() {
let code = r#"let m = map!("a", 1, "b", 2);"#;
let result = parse_source(code);
if result.is_err() {
return;
}
let program = result.unwrap();
assert!(!program.statements.is_empty());
if let Statement::Let(ref let_stmt) = program.statements[0] {
match &let_stmt.value {
Expression::ObjectLiteral(map) => {
assert_eq!(
map.len(),
2,
"map! should produce ObjectLiteral with 2 key-value pairs"
);
}
Expression::FunctionCall(call) => {
assert_eq!(
call.name, "map!",
"If not ObjectLiteral, should be FunctionCall with name 'map!'"
);
assert_eq!(call.arguments.len(), 4); }
_ => panic!(
"Expected ObjectLiteral or FunctionCall from map!, got {:?}",
let_stmt.value
),
}
}
}
#[test]
fn test_parser_index_access() {
let code = "let x = arr[0];";
let program = parse_source(code).unwrap();
assert!(!program.statements.is_empty());
if let Statement::Let(ref let_stmt) = program.statements[0] {
match &let_stmt.value {
Expression::IndexAccess(container, index) => {
assert!(matches!(container.as_ref(), Expression::Identifier(_)));
assert!(matches!(index.as_ref(), Expression::Literal(_)));
}
_ => panic!("Expected IndexAccess from arr[0], got {:?}", let_stmt.value),
}
}
}
#[test]
fn test_parser_string_literal_in_let() {
let code = r#"let s = "hello";"#;
let program = parse_source(code).unwrap();
assert!(!program.statements.is_empty());
if let Statement::Let(ref let_stmt) = program.statements[0] {
match &let_stmt.value {
Expression::Literal(Literal::String(s)) => assert_eq!(s, "hello"),
_ => panic!("Expected string literal, got {:?}", let_stmt.value),
}
}
}
#[test]
fn test_parser_single_statement_exact_count() {
let code = "let x = 1;";
let program = parse_source(code).unwrap();
assert_eq!(
program.statements.len(),
1,
"Single statement should produce exactly 1 (catches <= mutation)"
);
}
#[test]
fn test_parser_empty_block() {
let code = "if (true) { }";
let program = parse_source(code).unwrap();
assert!(!program.statements.is_empty());
if let Statement::If(ref if_stmt) = program.statements[0] {
assert_eq!(
if_stmt.consequence.statements.len(),
0,
"Empty block should have 0 statements"
);
}
}
#[test]
fn test_parser_block_with_single_statement() {
let code = "if (true) { let x = 1; }";
let program = parse_source(code).unwrap();
if let Statement::If(ref if_stmt) = program.statements[0] {
assert_eq!(
if_stmt.consequence.statements.len(),
1,
"Block with 1 statement should have exactly 1"
);
}
}
#[test]
fn test_parser_binary_or_exact_structure() {
let code = "let x = a || b;";
let program = parse_source(code).unwrap();
if let Statement::Let(ref let_stmt) = program.statements[0] {
match &let_stmt.value {
Expression::BinaryOp(left, op, right) => {
use dist_agent_lang::lexer::tokens::Operator;
assert_eq!(*op, Operator::Or, "Should be Or operator");
assert!(matches!(left.as_ref(), Expression::Identifier(_)));
assert!(matches!(right.as_ref(), Expression::Identifier(_)));
}
_ => panic!("Expected BinaryOp(Or), got {:?}", let_stmt.value),
}
}
}
#[test]
fn test_parser_binary_and_exact_structure() {
let code = "let x = a && b;";
let program = parse_source(code).unwrap();
if let Statement::Let(ref let_stmt) = program.statements[0] {
match &let_stmt.value {
Expression::BinaryOp(_left, op, _right) => {
use dist_agent_lang::lexer::tokens::Operator;
assert_eq!(*op, Operator::And, "Should be And operator");
}
_ => panic!("Expected BinaryOp(And), got {:?}", let_stmt.value),
}
}
}
#[test]
fn test_parser_binary_equality_exact_structure() {
let code = "let x = a == b;";
let program = parse_source(code).unwrap();
if let Statement::Let(ref let_stmt) = program.statements[0] {
match &let_stmt.value {
Expression::BinaryOp(_left, op, _right) => {
use dist_agent_lang::lexer::tokens::Operator;
assert_eq!(
*op,
Operator::Equal,
"Should be Equal operator (== is tokenized as Equal)"
);
}
_ => panic!("Expected BinaryOp(Equal), got {:?}", let_stmt.value),
}
}
}
#[test]
fn test_parser_binary_not_equal_exact_structure() {
let code = "let x = a != b;";
let program = parse_source(code).unwrap();
if let Statement::Let(ref let_stmt) = program.statements[0] {
match &let_stmt.value {
Expression::BinaryOp(_left, op, _right) => {
use dist_agent_lang::lexer::tokens::Operator;
assert_eq!(*op, Operator::NotEqual, "Should be NotEqual operator");
}
_ => panic!("Expected BinaryOp(NotEqual), got {:?}", let_stmt.value),
}
}
}
#[test]
fn test_parser_binary_comparison_exact_structure() {
let code = "let x = a < b;";
let program = parse_source(code).unwrap();
if let Statement::Let(ref let_stmt) = program.statements[0] {
match &let_stmt.value {
Expression::BinaryOp(_left, op, _right) => {
use dist_agent_lang::lexer::tokens::Operator;
assert_eq!(*op, Operator::Less, "Should be Less operator");
}
_ => panic!("Expected BinaryOp(Less), got {:?}", let_stmt.value),
}
}
}
#[test]
fn test_parser_binary_term_exact_structure() {
let code = "let x = a + b;";
let program = parse_source(code).unwrap();
if let Statement::Let(ref let_stmt) = program.statements[0] {
match &let_stmt.value {
Expression::BinaryOp(_left, op, _right) => {
use dist_agent_lang::lexer::tokens::Operator;
assert_eq!(*op, Operator::Plus, "Should be Plus operator");
}
_ => panic!("Expected BinaryOp(Plus), got {:?}", let_stmt.value),
}
}
}
#[test]
fn test_parser_binary_factor_exact_structure() {
let code = "let x = a * b;";
let program = parse_source(code).unwrap();
if let Statement::Let(ref let_stmt) = program.statements[0] {
match &let_stmt.value {
Expression::BinaryOp(_left, op, _right) => {
use dist_agent_lang::lexer::tokens::Operator;
assert_eq!(*op, Operator::Star, "Should be Star operator");
}
_ => panic!("Expected BinaryOp(Star), got {:?}", let_stmt.value),
}
}
}
#[test]
fn test_parser_unary_not_operator() {
let code = "let x = !true;";
let program = parse_source(code).unwrap();
if let Statement::Let(ref let_stmt) = program.statements[0] {
match &let_stmt.value {
Expression::UnaryOp(op, expr) => {
use dist_agent_lang::lexer::tokens::Operator;
assert_eq!(*op, Operator::Not, "Should be Not operator");
assert!(matches!(
expr.as_ref(),
Expression::Literal(Literal::Bool(_))
));
}
_ => panic!("Expected UnaryOp(Not), got {:?}", let_stmt.value),
}
}
}
#[test]
fn test_parser_unary_minus_operator() {
let code = "let x = -42;";
let program = parse_source(code).unwrap();
if let Statement::Let(ref let_stmt) = program.statements[0] {
match &let_stmt.value {
Expression::UnaryOp(op, _expr) => {
use dist_agent_lang::lexer::tokens::Operator;
assert_eq!(*op, Operator::Minus, "Should be Minus operator");
}
_ => panic!("Expected UnaryOp(Minus), got {:?}", let_stmt.value),
}
}
}
#[test]
fn test_parser_field_access() {
let code = "let x = obj.field;";
let program = parse_source(code).unwrap();
if let Statement::Let(ref let_stmt) = program.statements[0] {
match &let_stmt.value {
Expression::FieldAccess(obj, field) => {
assert_eq!(field, "field");
assert!(matches!(obj.as_ref(), Expression::Identifier(_)));
}
_ => panic!("Expected FieldAccess, got {:?}", let_stmt.value),
}
}
}
#[test]
fn test_parser_nested_field_access() {
let code = "let x = obj.field1.field2;";
let program = parse_source(code).unwrap();
if let Statement::Let(ref let_stmt) = program.statements[0] {
match &let_stmt.value {
Expression::FieldAccess(inner_obj, field) => {
assert_eq!(field, "field2");
match inner_obj.as_ref() {
Expression::FieldAccess(obj, field1) => {
assert_eq!(field1, "field1");
assert!(matches!(obj.as_ref(), Expression::Identifier(_)));
}
_ => panic!("Expected nested FieldAccess, got {:?}", inner_obj),
}
}
_ => panic!("Expected FieldAccess, got {:?}", let_stmt.value),
}
}
}
#[test]
fn test_parser_chained_binary_operations() {
let code = "let x = a + b + c;";
let program = parse_source(code).unwrap();
if let Statement::Let(ref let_stmt) = program.statements[0] {
match &let_stmt.value {
Expression::BinaryOp(left, op, right) => {
use dist_agent_lang::lexer::tokens::Operator;
assert_eq!(*op, Operator::Plus);
match left.as_ref() {
Expression::BinaryOp(l, o, r) => {
assert_eq!(*o, Operator::Plus);
assert!(matches!(l.as_ref(), Expression::Identifier(_)));
assert!(matches!(r.as_ref(), Expression::Identifier(_)));
}
_ => panic!("Expected nested BinaryOp in left operand"),
}
assert!(matches!(right.as_ref(), Expression::Identifier(_)));
}
_ => panic!("Expected BinaryOp, got {:?}", let_stmt.value),
}
}
}
#[test]
fn test_parser_error_recovery_multiple_errors() {
let code = "let a = 1; let b = ; let c = 2;";
let tokens = Lexer::new(code)
.tokenize_with_positions_immutable()
.unwrap();
let mut parser = Parser::new_with_positions(tokens);
let (program, errors) = parser.parse_with_recovery();
assert!(
!errors.is_empty(),
"Should collect at least one parse error"
);
assert!(
!program.statements.is_empty(),
"Should parse valid statements"
);
}
#[test]
fn test_parser_error_recovery_invalid_block() {
let code = "if (true) { let x = ; }";
let tokens = Lexer::new(code)
.tokenize_with_positions_immutable()
.unwrap();
let mut parser = Parser::new_with_positions(tokens);
let (program, errors) = parser.parse_with_recovery();
assert!(
!errors.is_empty() || !program.statements.is_empty(),
"Should either collect errors or parse successfully"
);
}
#[test]
fn test_parser_error_recovery_at_eof() {
let code = "let x = ";
let tokens = Lexer::new(code)
.tokenize_with_positions_immutable()
.unwrap();
let mut parser = Parser::new_with_positions(tokens);
let (_program, errors) = parser.parse_with_recovery();
assert!(!errors.is_empty(), "Should collect error");
}
#[test]
fn test_parser_error_recovery_at_last_token() {
let code = "let x = 1; let y = ";
let tokens = Lexer::new(code)
.tokenize_with_positions_immutable()
.unwrap();
let mut parser = Parser::new_with_positions(tokens);
let (program, errors) = parser.parse_with_recovery();
assert!(!errors.is_empty(), "Should collect at least one error");
assert!(
!program.statements.is_empty(),
"Should parse first valid statement"
);
}
#[test]
fn test_parser_error_recovery_at_start() {
let code = "= 1;";
let tokens = Lexer::new(code)
.tokenize_with_positions_immutable()
.unwrap();
let mut parser = Parser::new_with_positions(tokens);
let (_program, errors) = parser.parse_with_recovery();
assert!(!errors.is_empty(), "Should collect error");
}
#[test]
fn test_parser_error_recovery_sync_point_is_next_token() {
let code = "let x = ; let y = 2;";
let tokens = Lexer::new(code)
.tokenize_with_positions_immutable()
.unwrap();
let mut parser = Parser::new_with_positions(tokens);
let (program, errors) = parser.parse_with_recovery();
assert!(!errors.is_empty(), "Should collect error");
assert!(
!program.statements.is_empty(),
"Should parse valid statement after recovery"
);
}
#[test]
fn test_parser_error_recovery_nested_blocks() {
let code = "if (true) { if (false) { let x = ; } }";
let tokens = Lexer::new(code)
.tokenize_with_positions_immutable()
.unwrap();
let mut parser = Parser::new_with_positions(tokens);
let (program, errors) = parser.parse_with_recovery();
assert!(
!errors.is_empty() || !program.statements.is_empty(),
"Should handle nested block errors"
);
}
#[test]
fn test_parser_error_recovery_no_sync_point_until_eof() {
let code = "let x = let y = ";
let tokens = Lexer::new(code)
.tokenize_with_positions_immutable()
.unwrap();
let mut parser = Parser::new_with_positions(tokens);
let (_program, errors) = parser.parse_with_recovery();
assert!(!errors.is_empty(), "Should collect error");
}
#[test]
fn test_parser_error_recovery_multiple_consecutive_errors() {
let code = "let a = ; let b = ; let c = ;";
let tokens = Lexer::new(code)
.tokenize_with_positions_immutable()
.unwrap();
let mut parser = Parser::new_with_positions(tokens);
let (_program, errors) = parser.parse_with_recovery();
assert!(!errors.is_empty(), "Should collect errors");
}
#[test]
fn test_parser_error_recovery_sync_point_is_eof() {
let code = "let x = 1 let y = 2";
let tokens = Lexer::new(code)
.tokenize_with_positions_immutable()
.unwrap();
let mut parser = Parser::new_with_positions(tokens);
let (_program, errors) = parser.parse_with_recovery();
assert!(!errors.is_empty(), "Should collect error");
}
#[test]
fn test_parser_error_recovery_start_plus_one_at_eof() {
let code = "let x =";
let tokens = Lexer::new(code)
.tokenize_with_positions_immutable()
.unwrap();
let mut parser = Parser::new_with_positions(tokens);
let (_program, errors) = parser.parse_with_recovery();
assert!(!errors.is_empty(), "Should collect error");
}
#[test]
fn test_parser_index_access_nested() {
let code = "let x = arr[i][j];";
let program = parse_source(code).unwrap();
if let Statement::Let(ref let_stmt) = program.statements[0] {
match &let_stmt.value {
Expression::IndexAccess(container, index) => {
assert!(matches!(index.as_ref(), Expression::Identifier(_)));
match container.as_ref() {
Expression::IndexAccess(inner_container, inner_index) => {
assert!(matches!(
inner_container.as_ref(),
Expression::Identifier(_)
));
assert!(matches!(inner_index.as_ref(), Expression::Identifier(_)));
}
_ => panic!("Expected nested IndexAccess"),
}
}
_ => panic!("Expected IndexAccess, got {:?}", let_stmt.value),
}
}
}
#[test]
fn test_parser_range_expression() {
let code = "let x = 1..10;";
let program = parse_source(code).unwrap();
if let Statement::Let(ref let_stmt) = program.statements[0] {
match &let_stmt.value {
Expression::Range(start, end) => {
assert!(matches!(
start.as_ref(),
Expression::Literal(Literal::Int(_))
));
assert!(matches!(end.as_ref(), Expression::Literal(Literal::Int(_))));
}
_ => panic!("Expected Range, got {:?}", let_stmt.value),
}
}
}
const TEN_MB: usize = 10 * 1024 * 1024;
#[test]
fn test_parse_source_rejects_oversized_source() {
let oversized = "x".repeat(TEN_MB + 1);
let result = parse_source(&oversized);
assert!(result.is_err(), "parse_source should reject source > 10MB");
let err = result.unwrap_err();
let msg = err.to_string();
assert!(
msg.contains("Source code too large"),
"error should mention source size limit; got: {}",
msg
);
assert!(
msg.contains("10485760"),
"error should include max size (10485760); got: {}",
msg
);
}
#[test]
fn test_parse_source_accepts_source_under_limit() {
let at_limit = "x".repeat(TEN_MB);
let result = parse_source(&at_limit);
assert!(
result.is_ok(),
"parse_source should accept source at 10MB; got: {:?}",
result.err()
);
}
#[test]
fn test_parse_source_rejects_too_many_tokens() {
let many_tokens = "1 ".repeat(1_000_000);
let result = parse_source(&many_tokens);
let err = result.unwrap_err();
let msg = err.to_string();
assert!(
msg.contains("Too many tokens") || msg.contains("too many tokens"),
"error should mention token limit; got: {}",
msg
);
assert!(
msg.contains("1000000"),
"error should include max token count (1000000); got: {}",
msg
);
}