use lumen_compiler::compiler::lexer::Lexer;
use lumen_compiler::compiler::lir::{IntrinsicId, OpCode};
use lumen_compiler::compiler::lower::lower;
use lumen_compiler::compiler::parser::Parser;
use lumen_compiler::compiler::resolve::resolve;
use lumen_compiler::compiler::typecheck::typecheck;
fn compile_to_lir(src: &str) -> lumen_compiler::compiler::lir::LirModule {
let mut lexer = Lexer::new(src, 1, 0);
let tokens = lexer.tokenize().expect("lex failed");
let mut parser = Parser::new(tokens);
let program = parser.parse_program(vec![]).expect("parse failed");
let symbols = resolve(&program).expect("resolve failed");
typecheck(&program, &symbols).expect("typecheck failed");
lower(&program, &symbols, src)
}
fn assert_compiles(src: &str) {
let mut lexer = Lexer::new(src, 1, 0);
let tokens = lexer.tokenize().expect("lex failed");
let mut parser = Parser::new(tokens);
let program = parser.parse_program(vec![]).expect("parse failed");
let symbols = resolve(&program).expect("resolve failed");
typecheck(&program, &symbols).expect("typecheck failed");
}
#[test]
fn is_type_compiles_and_returns_bool() {
let src = "cell check(x: Int) -> Bool\n return x is Int\nend";
let module = compile_to_lir(src);
let ops: Vec<_> = module.cells[0].instructions.iter().map(|i| i.op).collect();
assert!(
ops.contains(&OpCode::Is),
"should emit Is opcode for 'is' expression"
);
}
#[test]
fn is_type_with_string() {
assert_compiles("cell check(x: String) -> Bool\n return x is String\nend");
}
#[test]
fn is_type_with_any_value() {
assert_compiles("cell check() -> Bool\n let x = 42\n return x is Int\nend");
}
#[test]
fn is_type_loads_type_string_constant() {
let src = "cell check(x: Int) -> Bool\n return x is Int\nend";
let module = compile_to_lir(src);
let has_int_str = module.cells[0]
.constants
.iter()
.any(|c| matches!(c, lumen_compiler::compiler::lir::Constant::String(s) if s == "Int"));
assert!(
has_int_str,
"should have 'Int' string constant for is type check"
);
}
#[test]
fn is_type_in_if_condition() {
assert_compiles(
"cell check(x: Int) -> String\n if x is Int\n return \"yes\"\n end\n return \"no\"\nend",
);
}
#[test]
fn as_int_compiles() {
let src = "cell convert(x: Float) -> Int\n return x as Int\nend";
let module = compile_to_lir(src);
let ops: Vec<_> = module.cells[0].instructions.iter().map(|i| i.op).collect();
assert!(
ops.contains(&OpCode::Intrinsic),
"should emit Intrinsic opcode for 'as Int' cast"
);
let intr = module.cells[0]
.instructions
.iter()
.find(|i| i.op == OpCode::Intrinsic)
.expect("should have Intrinsic");
assert_eq!(
intr.b,
IntrinsicId::ToInt as u8,
"should use ToInt intrinsic"
);
}
#[test]
fn as_float_compiles() {
let src = "cell convert(x: Int) -> Float\n return x as Float\nend";
let module = compile_to_lir(src);
let intr = module.cells[0]
.instructions
.iter()
.find(|i| i.op == OpCode::Intrinsic)
.expect("should have Intrinsic for as Float");
assert_eq!(
intr.b,
IntrinsicId::ToFloat as u8,
"should use ToFloat intrinsic"
);
}
#[test]
fn as_string_compiles() {
let src = "cell convert(x: Int) -> String\n return x as String\nend";
let module = compile_to_lir(src);
let intr = module.cells[0]
.instructions
.iter()
.find(|i| i.op == OpCode::Intrinsic)
.expect("should have Intrinsic for as String");
assert_eq!(
intr.b,
IntrinsicId::ToString as u8,
"should use ToString intrinsic"
);
}
#[test]
fn as_bool_compiles() {
let src = "cell convert(x: Int) -> Bool\n return x as Bool\nend";
let module = compile_to_lir(src);
let not_count = module.cells[0]
.instructions
.iter()
.filter(|i| i.op == OpCode::Not)
.count();
assert!(
not_count >= 2,
"should emit double Not for Bool truthiness cast"
);
}
#[test]
fn as_float_pi() {
assert_compiles("cell main() -> Int\n return 3.14 as Int\nend");
}
#[test]
fn implicit_return_simple() {
let src = "cell add(a: Int, b: Int) -> Int\n a + b\nend";
let module = compile_to_lir(src);
let ops: Vec<_> = module.cells[0].instructions.iter().map(|i| i.op).collect();
assert!(
ops.contains(&OpCode::Return),
"should emit Return for implicit return"
);
assert!(ops.contains(&OpCode::Add), "should emit Add for a + b");
}
#[test]
fn implicit_return_preserves_explicit() {
let src = "cell add(a: Int, b: Int) -> Int\n return a + b\nend";
let module = compile_to_lir(src);
let ops: Vec<_> = module.cells[0].instructions.iter().map(|i| i.op).collect();
assert!(ops.contains(&OpCode::Return));
}
#[test]
fn implicit_return_with_let_then_expr() {
let src = "cell calc(x: Int) -> Int\n let y = x * 2\n y + 1\nend";
let module = compile_to_lir(src);
let ops: Vec<_> = module.cells[0].instructions.iter().map(|i| i.op).collect();
assert!(
ops.contains(&OpCode::Return),
"should emit Return for implicit return"
);
}
#[test]
fn no_implicit_return_without_return_type() {
let src = "cell greet()\n let x = 42\nend";
let module = compile_to_lir(src);
let instrs = &module.cells[0].instructions;
let last_two: Vec<_> = instrs.iter().rev().take(2).map(|i| i.op).collect();
assert_eq!(last_two, vec![OpCode::Return, OpCode::LoadNil]);
}
#[test]
fn implicit_return_string_literal() {
let src = "cell hello() -> String\n \"hello world\"\nend";
assert_compiles(src);
let module = compile_to_lir(src);
let ops: Vec<_> = module.cells[0].instructions.iter().map(|i| i.op).collect();
assert!(ops.contains(&OpCode::Return));
}
#[test]
fn is_and_as_combined() {
assert_compiles(
"cell process(x: Int) -> String\n if x is Int\n return x as String\n end\n return \"unknown\"\nend",
);
}