use nescript::analyzer; use nescript::codegen::CodeGen; use nescript::linker::Linker; use nescript::rom; /// Compile a `NEScript` source string into a .nes ROM. fn compile(source: &str) -> Vec { let (program, diags) = nescript::parser::parse(source); assert!( diags.is_empty(), "unexpected parse errors: {diags:?}\nsource:\n{source}" ); let program = program.expect("parse should succeed"); let analysis = analyzer::analyze(&program); assert!( analysis.diagnostics.iter().all(|d| !d.is_error()), "unexpected analysis errors: {:?}", analysis.diagnostics ); let codegen = CodeGen::new(&analysis.var_allocations, &program.constants); let instructions = codegen.generate(&program); let linker = Linker::new(program.game.mirroring); linker.link(&instructions) } #[test] fn hello_sprite_compiles_to_valid_rom() { let source = include_str!("integration/hello_sprite.ne"); let rom_data = compile(source); // Validate iNES format let info = rom::validate_ines(&rom_data).expect("should be valid iNES"); assert_eq!(info.prg_banks, 1, "should be 1 PRG bank (16 KB)"); assert_eq!(info.chr_banks, 1, "should have CHR ROM"); assert_eq!(info.mapper, 0, "should be NROM (mapper 0)"); // ROM should be 16 header + 16 KB PRG + 8 KB CHR assert_eq!(rom_data.len(), 16 + 16384 + 8192); } #[test] fn hello_sprite_has_correct_vectors() { let source = include_str!("integration/hello_sprite.ne"); let rom_data = compile(source); // Vector table at the end of PRG ROM let prg_end = 16 + 16384; let nmi = u16::from_le_bytes([rom_data[prg_end - 6], rom_data[prg_end - 5]]); let reset = u16::from_le_bytes([rom_data[prg_end - 4], rom_data[prg_end - 3]]); let irq = u16::from_le_bytes([rom_data[prg_end - 2], rom_data[prg_end - 1]]); assert!(nmi >= 0xC000, "NMI vector should be in ROM space"); assert_eq!(reset, 0xC000, "RESET should point to $C000"); assert!(irq >= 0xC000, "IRQ vector should be in ROM space"); assert!(nmi != reset, "NMI and RESET should be different"); } #[test] fn minimal_program_compiles() { let source = r#" game "Minimal" { mapper: NROM } on frame { wait_frame } start Main "#; let rom_data = compile(source); let info = rom::validate_ines(&rom_data).expect("should be valid iNES"); assert_eq!(info.mapper, 0); } #[test] fn program_with_state_machine() { let source = r#" game "States" { mapper: NROM } state Title { on frame { if button.start { transition Game } } } state Game { var score: u8 = 0 on frame { score += 1 } } start Title "#; let rom_data = compile(source); rom::validate_ines(&rom_data).expect("should be valid iNES"); } #[test] fn program_with_constants() { let source = r#" game "Constants" { mapper: NROM } const SPEED: u8 = 3 var px: u8 = 100 on frame { if button.right { px += SPEED } } start Main "#; let rom_data = compile(source); rom::validate_ines(&rom_data).expect("should be valid iNES"); } #[test] fn error_test_missing_game() { let source = "var x: u8 = 0\nstart Main"; let (_, diags) = nescript::parser::parse(source); assert!( diags.iter().any(nescript::errors::Diagnostic::is_error), "should produce error" ); } #[test] fn error_test_undefined_transition() { let source = r#" game "T" { mapper: NROM } state Main { on frame { transition Nonexistent } } start Main "#; let (program, parse_diags) = nescript::parser::parse(source); assert!(parse_diags.is_empty()); let analysis = analyzer::analyze(&program.unwrap()); assert!( analysis .diagnostics .iter() .any(nescript::errors::Diagnostic::is_error), "should detect undefined transition target" ); }