1
0
Fork 0
mirror of https://github.com/imjasonh/nescript synced 2026-07-08 08:55:38 +00:00

Sprite resolution, asset wiring, shift-assign, unreachable state warning

Sprite/asset pipeline:
- Linker::link_with_assets() places sprite CHR data in ROM at correct tile
- assets::resolve_sprites() walks Program for inline sprite bytes
- CodeGen::with_sprites() maps sprite names to tile indices
- gen_draw() uses correct tile index from sprite declarations
- main.rs wires the full resolution pipeline

Shift-assign operators (<<= and >>=):
- AssignOp::ShiftLeftAssign and ShiftRightAssign variants
- Parser handles in both statement and array index contexts
- Codegen emits ASL A / LSR A
- IR lowering maps to ShiftLeft/ShiftRight ops

Unreachable state warning (W0104):
- BFS from start state finds reachable states via transitions
- States not reached produce W0104 warning

Error polish helpers:
- suggest_var_name() for "did you mean" suggestions
- emit_undefined_var() for E0502 with typo hints
- Used by analyzer for better diagnostics

242 tests pass, clippy clean.

https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
This commit is contained in:
Claude 2026-04-12 10:01:44 +00:00
parent 5d2d242520
commit 6430c3a935
No known key found for this signature in database
13 changed files with 737 additions and 17 deletions

View file

@ -1,4 +1,7 @@
use std::path::Path;
use nescript::analyzer;
use nescript::assets;
use nescript::codegen::CodeGen;
use nescript::ir;
use nescript::linker::Linker;
@ -25,11 +28,15 @@ fn compile(source: &str) -> Vec<u8> {
let mut ir_program = ir::lower(&program, &analysis);
optimizer::optimize(&mut ir_program);
let codegen = CodeGen::new(&analysis.var_allocations, &program.constants);
let sprites = assets::resolve_sprites(&program, Path::new("."))
.expect("sprite resolution should succeed");
let codegen =
CodeGen::new(&analysis.var_allocations, &program.constants).with_sprites(&sprites);
let instructions = codegen.generate(&program);
let linker = Linker::new(program.game.mirroring);
linker.link(&instructions)
linker.link_with_assets(&instructions, &sprites)
}
// ── M1 Tests ──
@ -371,11 +378,77 @@ fn compile_with_mapper(source: &str) -> Vec<u8> {
let mut ir_program = ir::lower(&program, &analysis);
nescript::optimizer::optimize(&mut ir_program);
let codegen = nescript::codegen::CodeGen::new(&analysis.var_allocations, &program.constants);
let sprites = assets::resolve_sprites(&program, Path::new("."))
.expect("sprite resolution should succeed");
let codegen = nescript::codegen::CodeGen::new(&analysis.var_allocations, &program.constants)
.with_sprites(&sprites);
let instructions = codegen.generate(&program);
let linker = Linker::with_mapper(program.game.mirroring, program.game.mapper);
linker.link(&instructions)
linker.link_with_assets(&instructions, &sprites)
}
#[test]
fn sprite_resolution_uses_tile_index() {
// The Player sprite has 16 unique bytes of CHR data. Because tile index 0
// is reserved for the built-in smiley, the compiler should place Player
// at tile index 1 and `draw Player` should store that tile index in OAM.
//
// We check this in two ways:
// 1. The CHR ROM contains Player's bytes at tile 1 (offset 16).
// 2. The PRG ROM contains the immediate-load sequence `A9 01 8D 01 02`
// (LDA #$01 ; STA $0201) — writing tile index 1 into OAM byte 1.
let source = r#"
game "SpriteTile" { mapper: NROM }
sprite Player {
chr: [0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F]
}
var px: u8 = 128
var py: u8 = 120
state Title {
on frame {
draw Player at: (px, py)
}
}
start Title
"#;
let rom_data = compile(source);
// CHR ROM begins right after PRG ROM (16 header + 16384 PRG).
let chr_start = 16 + 16384;
// Tile 1 lives at CHR offset 16 (16 bytes per tile).
let tile1 = &rom_data[chr_start + 16..chr_start + 32];
assert_eq!(
tile1,
&[
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D,
0x1E, 0x1F
],
"Player sprite CHR bytes should be placed at tile index 1",
);
// The default smiley tile at index 0 should still be non-zero (untouched).
let tile0 = &rom_data[chr_start..chr_start + 16];
assert_ne!(
tile0, &[0u8; 16],
"tile 0 should still contain the default smiley",
);
// In PRG ROM, look for `LDA #$01 ; STA $0201` which writes the Player's
// tile index (1) into the tile-index byte of the first OAM slot.
let prg = &rom_data[16..16 + 16384];
let pattern = [0xA9u8, 0x01, 0x8D, 0x01, 0x02];
assert!(
prg.windows(pattern.len()).any(|w| w == pattern),
"PRG ROM should contain LDA #$01 ; STA $0201 for draw Player",
);
}
#[test]