1
0
Fork 0
mirror of https://github.com/imjasonh/nescript synced 2026-07-09 09:18:01 +00:00
nescript/src/ir/tests.rs

278 lines
6.4 KiB
Rust
Raw Normal View History

use super::*;
use crate::analyzer;
use crate::parser;
fn lower_ok(input: &str) -> IrProgram {
let (prog, diags) = parser::parse(input);
assert!(diags.is_empty(), "parse errors: {diags:?}");
let prog = prog.unwrap();
let analysis = analyzer::analyze(&prog);
assert!(
analysis.diagnostics.iter().all(|d| !d.is_error()),
"analysis errors: {:?}",
analysis.diagnostics
);
lower(&prog, &analysis)
}
#[test]
fn lower_minimal_program() {
let ir = lower_ok(
r#"
game "Test" { mapper: NROM }
var px: u8 = 128
on frame { px = 1 }
start Main
"#,
);
assert_eq!(ir.globals.len(), 1);
assert_eq!(ir.globals[0].name, "px");
assert_eq!(ir.globals[0].init_value, Some(128));
// Should have at least one function (the frame handler)
assert!(!ir.functions.is_empty());
}
#[test]
fn lower_var_assignment() {
let ir = lower_ok(
r#"
game "Test" { mapper: NROM }
var x: u8 = 0
on frame { x = 42 }
start Main
"#,
);
let frame_fn = ir
.functions
.iter()
.find(|f| f.name.contains("frame"))
.unwrap();
// Should have a StoreVar op
let has_store = frame_fn
.blocks
.iter()
.flat_map(|b| &b.ops)
.any(|op| matches!(op, IrOp::StoreVar(..)));
assert!(has_store, "should emit StoreVar for assignment");
}
#[test]
fn lower_plus_assign() {
let ir = lower_ok(
r#"
game "Test" { mapper: NROM }
var x: u8 = 0
on frame { x += 5 }
start Main
"#,
);
let frame_fn = ir
.functions
.iter()
.find(|f| f.name.contains("frame"))
.unwrap();
let has_add = frame_fn
.blocks
.iter()
.flat_map(|b| &b.ops)
.any(|op| matches!(op, IrOp::Add(..)));
assert!(has_add, "should emit Add for += operator");
}
#[test]
fn lower_if_creates_branch() {
let ir = lower_ok(
r#"
game "Test" { mapper: NROM }
var x: u8 = 0
on frame {
if x == 0 { x = 1 }
}
start Main
"#,
);
let frame_fn = ir
.functions
.iter()
.find(|f| f.name.contains("frame"))
.unwrap();
let has_branch = frame_fn
.blocks
.iter()
.any(|b| matches!(&b.terminator, IrTerminator::Branch(..)));
assert!(
has_branch,
"if statement should produce a Branch terminator"
);
}
#[test]
fn lower_while_creates_loop() {
let ir = lower_ok(
r#"
game "Test" { mapper: NROM }
var x: u8 = 0
on frame {
while x < 10 { x += 1 }
}
start Main
"#,
);
let frame_fn = ir
.functions
.iter()
.find(|f| f.name.contains("frame"))
.unwrap();
// A while loop needs at least 3 blocks: condition check, body, and exit
assert!(
frame_fn.blocks.len() >= 3,
"while should create multiple blocks, got {}",
frame_fn.blocks.len()
);
}
#[test]
fn lower_button_read() {
let ir = lower_ok(
r#"
game "Test" { mapper: NROM }
var px: u8 = 0
on frame {
if button.right { px += 1 }
}
start Main
"#,
);
let frame_fn = ir
.functions
.iter()
.find(|f| f.name.contains("frame"))
.unwrap();
let has_input = frame_fn
.blocks
.iter()
.flat_map(|b| &b.ops)
.any(|op| matches!(op, IrOp::ReadInput(_, _)));
assert!(has_input, "button read should emit ReadInput op");
}
#[test]
fn lower_draw_sprite() {
let ir = lower_ok(
r#"
game "Test" { mapper: NROM }
var px: u8 = 0
var py: u8 = 0
on frame { draw Smiley at: (px, py) }
start Main
"#,
);
let frame_fn = ir
.functions
.iter()
.find(|f| f.name.contains("frame"))
.unwrap();
let has_draw = frame_fn
.blocks
.iter()
.flat_map(|b| &b.ops)
.any(|op| matches!(op, IrOp::DrawSprite { .. }));
assert!(has_draw, "should emit DrawSprite op");
}
#[test]
fn lower_constants_become_immediates() {
let ir = lower_ok(
r#"
game "Test" { mapper: NROM }
const SPEED: u8 = 3
var px: u8 = 0
on frame { px += SPEED }
start Main
"#,
);
let frame_fn = ir
.functions
.iter()
.find(|f| f.name.contains("frame"))
.unwrap();
// SPEED should be lowered to LoadImm(_, 3)
let has_imm3 = frame_fn
.blocks
.iter()
.flat_map(|b| &b.ops)
.any(|op| matches!(op, IrOp::LoadImm(_, 3)));
assert!(has_imm3, "constant should be inlined as LoadImm");
}
#[test]
fn lower_multiple_states() {
let ir = lower_ok(
r#"
game "Test" { mapper: NROM }
state Title {
on enter { wait_frame }
on frame { wait_frame }
}
state Game {
on frame { wait_frame }
}
start Title
"#,
);
// Should have: Title_enter, Title_frame, Game_frame
assert!(
ir.functions.len() >= 3,
"should have at least 3 functions for 2 states, got {}",
ir.functions.len()
);
let names: Vec<&str> = ir.functions.iter().map(|f| f.name.as_str()).collect();
assert!(
names.iter().any(|n| n.contains("Title_enter")),
"should have Title_enter handler"
);
assert!(
names.iter().any(|n| n.contains("Title_frame")),
"should have Title_frame handler"
);
assert!(
names.iter().any(|n| n.contains("Game_frame")),
"should have Game_frame handler"
);
}
#[test]
fn lower_op_count() {
let ir = lower_ok(
r#"
game "Test" { mapper: NROM }
var x: u8 = 0
on frame { x = 1 }
start Main
"#,
);
assert!(ir.op_count() > 0, "should have some IR ops");
}
#[test]
fn lower_wait_frame() {
let ir = lower_ok(
r#"
game "Test" { mapper: NROM }
on frame { wait_frame }
start Main
"#,
);
let frame_fn = ir
.functions
.iter()
.find(|f| f.name.contains("frame"))
.unwrap();
let has_wait = frame_fn
.blocks
.iter()
.flat_map(|b| &b.ops)
.any(|op| matches!(op, IrOp::WaitFrame));
assert!(has_wait, "should emit WaitFrame op");
}