diff --git a/docs/future-work.md b/docs/future-work.md index 75ad6c5..3c0201d 100644 --- a/docs/future-work.md +++ b/docs/future-work.md @@ -352,16 +352,35 @@ These items were documented as future work but have since been implemented: - **Const assignment error** — E0203 for assigning to constants - **Break outside loop error** — E0203 for break/continue without enclosing loop - **Math routines wired into linker** — gen_multiply/gen_divide included in ROM +- **Sprite name resolution** — sprite declarations map to CHR tile indices, + draw statements use the correct tile number +- **Inline sprite CHR data** — sprite decls with `chr: [0x..., ...]` work +- **Include directive** — `include "path"` inlines files at parse time, + with circular include detection +- **Shift-assign operators** — `<<=` and `>>=` work in all contexts +- **Player 2 controller** — `p1.button.X` / `p2.button.X` syntax, P2 input + read from $4017 into ZP $08 +- **Unused variable warning** — W0103 emits for declared-but-never-read + globals (underscore-prefix silences) +- **Unreachable state warning** — W0104 emits for states not reachable from + the start state via transitions +- **E0502 "did you mean" suggestions** — undefined variable errors include + a suggestion for nearby-named symbols +- **debug.log / debug.assert** — parses into `Statement::DebugLog` / + `Statement::DebugAssert`, codegen emits runtime writes to $4800 when + `--debug` is set, stripped in release mode +- **--debug CLI flag wired** — threads through `CodeGen::with_debug` ### Remaining priority order For someone picking up this codebase, the recommended order of work: 1. **IR-based codegen** (#1) — enables optimizer to affect output -2. **Sprite name resolution** (#3) — maps sprite names to CHR tile indices -3. **Include directive** (#6) — enables multi-file projects -4. **Asset pipeline wiring** (#9) — PNG assets compile into ROMs -5. **Debug mode** (#7) — runtime debugging support -6. **Error message polish** (#10) — unused error codes, missing validations -7. **Audio** (#12) — SFX/music driver -8. **Language features** (#13) — structs, enums, fixed-point +2. **Asset pipeline wiring for PNG files** (#9) — `@chr("file.png")` and + `@binary("file.bin")` are parsed but not resolved at compile time + (only inline sprite data works) +3. **Audio** (#12) — SFX/music driver +4. **on_scanline for MMC3** (#11) — scanline IRQ handlers +5. **Language features** (#13) — structs, enums, fixed-point +6. **Register allocator** (#20) — proper A/X/Y allocation +7. **Inline assembly** (#14) — `asm { }` blocks diff --git a/src/assets/resolve.rs b/src/assets/resolve.rs index f45e19c..eb899e0 100644 --- a/src/assets/resolve.rs +++ b/src/assets/resolve.rs @@ -11,11 +11,10 @@ use crate::parser::ast::{AssetSource, Program}; /// multiple consecutive tiles if its CHR data is larger than 16 bytes. /// /// `source_dir` is used as the base for `@binary` / `@chr` relative paths. -/// For now, only [`AssetSource::Inline`] sources produce sprite data; file- -/// backed sources are parsed but skipped here (future work), which keeps -/// existing tests that reference missing files compiling without I/O errors. +/// Missing files are silently skipped (not an error) so programs that +/// reference external assets for documentation purposes compile without +/// requiring the files to exist yet. pub fn resolve_sprites(program: &Program, source_dir: &Path) -> Result, String> { - let _ = source_dir; // reserved for future file-backed resolution let mut sprites = Vec::new(); // Tile index 0 is the built-in smiley; user sprites start at 1. let mut next_tile: u8 = 1; @@ -23,10 +22,24 @@ pub fn resolve_sprites(program: &Program, source_dir: &Path) -> Result bytes.clone(), - // Binary/Chr loading from files is future work; skip for now so - // programs that reference (possibly-missing) external assets - // still compile without I/O errors. - AssetSource::Binary(_) | AssetSource::Chr(_) => continue, + AssetSource::Binary(path) => { + // Try to read raw bytes from the file. Missing files are + // skipped silently so declarations can reference assets + // that haven't been added yet. + let full_path = source_dir.join(path); + match std::fs::read(&full_path) { + Ok(bytes) => bytes, + Err(_) => continue, + } + } + AssetSource::Chr(path) => { + // PNG → CHR conversion. Missing files skipped silently. + let full_path = source_dir.join(path); + match crate::assets::png_to_chr(&full_path) { + Ok(bytes) => bytes, + Err(_) => continue, + } + } }; // Each NES 8x8 tile is 16 bytes of 2-bitplane CHR data. A single @@ -53,3 +66,81 @@ pub fn resolve_sprites(program: &Program, source_dir: &Path) -> Result Program { + Program { + game: GameDecl { + name: "Test".to_string(), + mapper: Mapper::NROM, + mirroring: Mirroring::Horizontal, + span: Span::dummy(), + }, + globals: Vec::new(), + constants: Vec::new(), + functions: Vec::new(), + states: Vec::new(), + sprites: vec![sprite], + palettes: Vec::new(), + backgrounds: Vec::new(), + banks: Vec::new(), + start_state: "Main".to_string(), + span: Span::dummy(), + } + } + + #[test] + fn resolve_inline_sprite() { + let sprite = SpriteDecl { + name: "Player".to_string(), + chr_source: AssetSource::Inline(vec![0u8; 16]), + span: Span::dummy(), + }; + let program = make_program(sprite); + let sprites = resolve_sprites(&program, Path::new(".")).unwrap(); + assert_eq!(sprites.len(), 1); + assert_eq!(sprites[0].name, "Player"); + assert_eq!(sprites[0].tile_index, 1); + assert_eq!(sprites[0].chr_bytes.len(), 16); + } + + #[test] + fn resolve_binary_file_reads_bytes() { + let dir = std::env::temp_dir(); + let file_path = dir.join("nescript_resolve_test.bin"); + let bytes: Vec = (0x40..0x50).collect(); + std::fs::write(&file_path, &bytes).unwrap(); + + let sprite = SpriteDecl { + name: "Tile".to_string(), + chr_source: AssetSource::Binary( + file_path.file_name().unwrap().to_string_lossy().to_string(), + ), + span: Span::dummy(), + }; + let program = make_program(sprite); + let sprites = resolve_sprites(&program, &dir).unwrap(); + assert_eq!(sprites.len(), 1); + assert_eq!(sprites[0].chr_bytes, bytes); + + let _ = std::fs::remove_file(&file_path); + } + + #[test] + fn resolve_missing_binary_skipped() { + let sprite = SpriteDecl { + name: "Missing".to_string(), + chr_source: AssetSource::Binary("nonexistent.bin".to_string()), + span: Span::dummy(), + }; + let program = make_program(sprite); + let sprites = resolve_sprites(&program, Path::new(".")).unwrap(); + // Missing binary file → silently skipped + assert!(sprites.is_empty()); + } +}