mirror of
https://github.com/imjasonh/nescript
synced 2026-07-08 08:55:38 +00:00
Add comprehensive future work document
docs/future-work.md (347 lines) catalogs every known gap, incomplete feature, and planned improvement with file:line references: - IR-based codegen (IR exists but output uses AST codegen) - 11 codegen gaps (calls, returns, transitions, arrays, mul/div, scroll) - Sprite name resolution and multi-OAM support - State machine dispatch (only start state generates code) - Include directive, debug mode, scroll hardware - Asset pipeline wiring (PNG conversion exists but unused) - 10 unused error codes, missing validations - Scanline IRQ, audio, inline assembly - Language features: structs, enums, fixed-point, metasprites - Missing shift-assign operators, P2 controller, register allocator - WASM build target, compiler benchmarks - Prioritized recommended order of work for contributors https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
This commit is contained in:
parent
bc165ed647
commit
3b1a03981b
1 changed files with 347 additions and 0 deletions
347
docs/future-work.md
Normal file
347
docs/future-work.md
Normal file
|
|
@ -0,0 +1,347 @@
|
|||
# Future Work
|
||||
|
||||
This document catalogs known gaps, incomplete features, and planned improvements
|
||||
in the NEScript compiler. Items are organized by priority and area.
|
||||
|
||||
---
|
||||
|
||||
## 1. IR-Based Code Generation
|
||||
|
||||
**Status**: The IR pipeline (lowering + optimization) runs during compilation but
|
||||
its output is discarded. Code generation still works from the AST directly
|
||||
(`src/codegen/mod.rs`). This means IR-level optimizations have no effect on the
|
||||
final ROM.
|
||||
|
||||
**What exists**:
|
||||
- `src/ir/mod.rs`: Complete IR type definitions (`IrProgram`, `IrFunction`,
|
||||
`IrBasicBlock`, `IrOp`, `IrTerminator`)
|
||||
- `src/ir/lowering.rs`: AST → IR translation for all statement and expression types
|
||||
- `src/optimizer/mod.rs`: Constant folding, dead code elimination, strength
|
||||
reduction, ZP promotion analysis, function inlining — all operating on IR
|
||||
|
||||
**What's needed**:
|
||||
- A new `src/codegen/ir_codegen.rs` that walks `IrProgram` and emits 6502
|
||||
`Instruction` sequences from `IrOp`/`IrTerminator` instead of from AST nodes
|
||||
- Register allocation strategy for IR temps → A/X/Y/zero-page spill slots
|
||||
- Replace the `CodeGen::generate(&program)` call in `main.rs` with
|
||||
`ir_codegen::generate(&ir_program, &analysis)`
|
||||
- Once working, delete the AST-based codegen entirely
|
||||
|
||||
**IR lowering issues to fix first** (found during code review):
|
||||
- `ButtonRead` emits `ReadInput` with no destination temp, then uses uninitialized
|
||||
temp in the `And` mask operation (`src/ir/lowering.rs:534`). The fix: `ReadInput`
|
||||
should store the input byte into a temp, or the lowering should emit
|
||||
`LoadVar(t, input_var_id)` after `ReadInput`.
|
||||
- Logical AND/OR use raw `VarId(self.next_var_id)` for temp storage without
|
||||
registering it (`src/ir/lowering.rs:603,637`). Should use `IrTemp` instead.
|
||||
- Break/continue create unreachable blocks that contain subsequent dead statements
|
||||
(`src/ir/lowering.rs:259`). Should either skip lowering after a terminator, or
|
||||
the dead code elimination pass should handle it.
|
||||
|
||||
**Impact**: Enables all optimizer passes to actually affect output quality.
|
||||
Currently the optimizer is validated by tests but its results are thrown away.
|
||||
|
||||
---
|
||||
|
||||
## 2. Codegen Gaps (AST-Based)
|
||||
|
||||
These features are parsed and analyzed but produce no 6502 output:
|
||||
|
||||
| Feature | Location | Status |
|
||||
|---------|----------|--------|
|
||||
| Function calls | `codegen:148` | `Statement::Call` is a no-op |
|
||||
| Return values | `codegen:148` | `Statement::Return` is a no-op |
|
||||
| State transitions | `codegen:148` | `Statement::Transition` is a no-op |
|
||||
| Array indexing | `codegen:199-200` | `LValue::ArrayIndex` assignment is a no-op |
|
||||
| Array expressions | `codegen:417` | `Expr::ArrayIndex`, `Expr::ArrayLiteral` are no-ops |
|
||||
| Function call expressions | `codegen:417` | `Expr::Call` returns nothing |
|
||||
| Scroll | `codegen:151-152` | `Statement::Scroll` is a no-op |
|
||||
| Load background | `codegen:154-155` | `Statement::LoadBackground` is a no-op |
|
||||
| Set palette | `codegen:154-155` | `Statement::SetPalette` is a no-op |
|
||||
| Multiply/divide/modulo | `codegen:450-452` | `BinOp::Mul/Div/Mod` only emit left operand |
|
||||
| Dynamic shifts | `ir/lowering:575` | Shift amount is hardcoded to 1 |
|
||||
|
||||
**Priority fixes for a working multi-state game**:
|
||||
1. **Function calls**: JSR to function label, pass args via zero-page, return via A
|
||||
2. **State transitions**: Write state ID to a zero-page variable, jump to dispatcher
|
||||
3. **Array indexing**: Use X register for index, LDA absolute,X for loads
|
||||
|
||||
---
|
||||
|
||||
## 3. Sprite Name Resolution
|
||||
|
||||
**Status**: `draw SpriteName at: (x, y)` parses the sprite name but codegen
|
||||
ignores it. All draws use OAM slot 0 with CHR tile index 0 (the built-in smiley).
|
||||
|
||||
**What's needed**:
|
||||
- Track a mapping from sprite name → CHR tile index in the linker
|
||||
- When a `sprite` declaration provides inline CHR data or `@chr("file.png")`,
|
||||
write that data to the CHR ROM at a known tile index
|
||||
- In codegen, look up the sprite name to get the tile index and write it to
|
||||
OAM byte 1 (tile number)
|
||||
- Support multiple OAM slots: track a `next_oam_slot` counter per frame,
|
||||
allocate slots 0..63 as draws are emitted, warn if >64
|
||||
|
||||
---
|
||||
|
||||
## 4. Multi-OAM Sprite Support
|
||||
|
||||
**Status**: Every `draw` writes to the same OAM bytes ($0200-$0203). Only one
|
||||
sprite is visible at a time.
|
||||
|
||||
**What's needed**:
|
||||
- Frame-level OAM slot allocator: each `draw` gets the next available 4-byte slot
|
||||
- Counter reset at the start of each frame handler
|
||||
- Warn at compile time if a frame handler has >64 static draws
|
||||
- Runtime: clear unused OAM slots to Y=$FE (off-screen) at frame start
|
||||
|
||||
---
|
||||
|
||||
## 5. State Machine Dispatch
|
||||
|
||||
**Status**: The codegen only generates code for the `start` state. Other states
|
||||
are parsed and analyzed but their frame handlers are never called.
|
||||
|
||||
**What's needed**:
|
||||
- A `current_state` zero-page variable holding the active state index
|
||||
- A dispatch table at the start of the main loop: load `current_state`, branch to
|
||||
the corresponding state's frame handler
|
||||
- `transition StateName` writes the new state index to `current_state`
|
||||
- Generate `on_enter` call on transition, `on_exit` call before leaving
|
||||
|
||||
---
|
||||
|
||||
## 6. Include Directive
|
||||
|
||||
**Status**: The `include` keyword is lexed (`KwInclude`) but not parsed or
|
||||
implemented.
|
||||
|
||||
**What's needed**:
|
||||
- Parser: `include "path.ne"` at top level
|
||||
- File resolution: relative to the including file's directory
|
||||
- Circular include detection (track include stack)
|
||||
- Merge included declarations into the main `Program` AST
|
||||
- Span tracking: included files need their own `file_id` for error messages
|
||||
|
||||
---
|
||||
|
||||
## 7. Debug Mode
|
||||
|
||||
**Status**: `--debug` flag is accepted by the CLI but has no effect.
|
||||
|
||||
**What's needed**:
|
||||
- `debug.log(expr, ...)`: write values to emulator debug port ($4800)
|
||||
- `debug.assert(expr)`: emit runtime check, halt on failure
|
||||
- `debug.overlay(x, y, text)`: render text to a reserved nametable region
|
||||
- Array bounds checking in debug mode (compare index against array size)
|
||||
- Frame overrun detection: count cycles per frame, warn if approaching vblank
|
||||
- All debug code stripped in release mode (already designed: `DebugLog`/`DebugAssert`
|
||||
in the `Statement` enum are defined in the spec but not yet in the AST)
|
||||
|
||||
---
|
||||
|
||||
## 8. Scroll Hardware Writes
|
||||
|
||||
**Status**: `scroll(x, y)` is parsed but produces no output.
|
||||
|
||||
**What's needed**:
|
||||
- Write X scroll value to PPU register $2005
|
||||
- Write Y scroll value to PPU register $2005 (second write)
|
||||
- Must happen during vblank (inside NMI handler or after `wait_frame`)
|
||||
- Split-screen scroll requires MMC3 scanline IRQ (`on scanline`)
|
||||
|
||||
---
|
||||
|
||||
## 9. Asset Pipeline Completion
|
||||
|
||||
**Status**: PNG → CHR conversion exists (`src/assets/chr.rs`) but is never called
|
||||
from the compilation pipeline.
|
||||
|
||||
### 9a. Wire `@chr("file.png")` to actual PNG loading
|
||||
- When a sprite/background declares `chr: @chr("path.png")`, call `png_to_chr()`
|
||||
during compilation
|
||||
- Resolve the path relative to the source file
|
||||
- Store resulting CHR data in the ROM's CHR section at a known tile index
|
||||
|
||||
### 9b. Wire `@binary("file.bin")` to raw file inclusion
|
||||
- Read the file as raw bytes and include in CHR or PRG ROM
|
||||
|
||||
### 9c. Palette extraction from PNG
|
||||
- `@palette("file.png")`: analyze image colors, map to nearest NES palette entries
|
||||
- Already have `nearest_nes_color()` in `src/assets/palette.rs`
|
||||
|
||||
### 9d. Nametable conversion
|
||||
- Full 256×240 PNG → 960-byte nametable + 64-byte attribute table
|
||||
- Tile deduplication (max 256 unique tiles per pattern table)
|
||||
- Error if >256 unique tiles
|
||||
|
||||
---
|
||||
|
||||
## 10. Error Message Polish
|
||||
|
||||
**Status**: Errors work and render with ariadne, but many error paths use generic
|
||||
messages.
|
||||
|
||||
### Unused error codes
|
||||
These are defined in `ErrorCode` but never emitted:
|
||||
- `E0202` — invalid cast
|
||||
- `E0203` — invalid operation for type
|
||||
- `E0301` — zero-page overflow
|
||||
- `E0403` — unreachable state
|
||||
- `E0505` — multiple start declarations
|
||||
- `W0101` — expensive multiply/divide operation
|
||||
- `W0102` — loop without break or wait_frame
|
||||
- `W0103` — unused variable
|
||||
- `W0104` — unreachable code after return/break/transition
|
||||
|
||||
### Missing validations
|
||||
- No error for assigning to a `const`
|
||||
- No error for `break`/`continue` outside a loop
|
||||
- No warning for variables declared but never read
|
||||
- No error for `return` with wrong type vs function signature
|
||||
- No error for calling a function with wrong argument count/types
|
||||
|
||||
---
|
||||
|
||||
## 11. Scanline IRQ (MMC3)
|
||||
|
||||
**Status**: `on scanline(N)` is in the spec and `on_scanline` field exists in
|
||||
`StateDecl`, but parsing and codegen are not implemented.
|
||||
|
||||
**What's needed**:
|
||||
- Parser: `on scanline(N) { ... }` event handler in state bodies
|
||||
- MMC3 IRQ setup: write scanline counter to $C000/$C001/$E000/$E001
|
||||
- IRQ handler generation: branch to the scanline handler code
|
||||
- Only valid with `mapper: MMC3`
|
||||
|
||||
---
|
||||
|
||||
## 12. Audio
|
||||
|
||||
**Status**: `play`, `start_music`, `stop_music` keywords are lexed but produce
|
||||
no output. No audio driver exists.
|
||||
|
||||
**What's needed**:
|
||||
- `@sfx("file.nsf")` / `@music("file.ftm")` asset directives
|
||||
- Audio driver running in the NMI handler (after OAM DMA)
|
||||
- `play SfxName` → trigger one-shot sound effect
|
||||
- `start_music TrackName` / `stop_music` → start/stop background music
|
||||
- FamiTracker export format parsing (complex — consider using existing tools)
|
||||
|
||||
---
|
||||
|
||||
## 13. Language Features (Post-v0.1)
|
||||
|
||||
From the spec's "Reserved for Future Versions" section:
|
||||
|
||||
| Feature | Description |
|
||||
|---------|-------------|
|
||||
| **Structs** | `struct Vec2 { x: u8, y: u8 }` — composite types with known layout |
|
||||
| **Enums** | `enum Direction { Up, Down, Left, Right }` — mapped to u8 values |
|
||||
| **Fixed-point** | `fixed8.8` type for sub-pixel movement |
|
||||
| **Text/HUD** | Font sheet declarations, layout system for scores/health/menus |
|
||||
| **Metasprites** | Multi-tile sprite groups with relative positioning |
|
||||
| **Tilemaps** | Declarative level data with collision queries |
|
||||
| **SRAM/saves** | Persistent storage declarations for battery-backed save data |
|
||||
| **NES 2.0** | Extended iNES header format |
|
||||
|
||||
---
|
||||
|
||||
## 14. Inline Assembly
|
||||
|
||||
**Status**: `asm { }` blocks are lexed (`KwAsm`, `AsmBody`) but not parsed or
|
||||
compiled. The lexer has raw-capture mode for asm content.
|
||||
|
||||
**What's needed**:
|
||||
- Parser: capture asm body text, parse `{variable_name}` substitutions
|
||||
- Codegen: emit raw 6502 instructions with variable address substitution
|
||||
- Labels: local to the asm block scope
|
||||
- `raw asm { }` variant with no substitution or safety checks
|
||||
|
||||
---
|
||||
|
||||
## 15. Compiler Performance
|
||||
|
||||
**Status**: Compilation is fast (<100ms for all examples) but has no benchmarks.
|
||||
|
||||
**What's needed**:
|
||||
- `cargo bench` benchmarks for each pipeline phase
|
||||
- Regression test: compilation must stay under 500ms for any reasonable project
|
||||
- Profile-guided optimization of hot paths (lexer, parser)
|
||||
|
||||
---
|
||||
|
||||
## 16. WASM Build Target
|
||||
|
||||
**What's needed**:
|
||||
- Factor out all file I/O behind a trait (`FileSystem` / `VFS`)
|
||||
- Core pipeline takes `&str` source → `Vec<u8>` ROM with no filesystem access
|
||||
- Compile the compiler to WASM for a browser-based IDE
|
||||
- In-browser NES emulator integration for instant preview
|
||||
|
||||
---
|
||||
|
||||
## 17. Open Design Questions
|
||||
|
||||
From the engineering plan:
|
||||
|
||||
1. **Inline asm label syntax**: `.label:` (ca65 style) vs `label:` (generic)?
|
||||
2. **Debug port address**: $4800 is conventional but not universal. Support
|
||||
multiple debug output methods?
|
||||
3. **OAM allocation strategy**: Sequential allocation vs priority-based with
|
||||
automatic sprite cycling for the 8-per-scanline limit?
|
||||
4. **Error recovery granularity**: How aggressively should the parser recover?
|
||||
More recovery = more errors per compile, but risk of cascading false errors.
|
||||
|
||||
---
|
||||
|
||||
## 18. Missing Assignment Operators
|
||||
|
||||
`<<=` and `>>=` (shift-assign) are lexed as `ShiftLeftAssign`/`ShiftRightAssign`
|
||||
tokens but have no corresponding variants in the `AssignOp` AST enum. They can
|
||||
never appear in parsed code. Adding them requires:
|
||||
- New `AssignOp::ShiftLeftAssign` and `AssignOp::ShiftRightAssign` variants
|
||||
- Parser handling in `parse_assign_or_call` (alongside the other compound ops)
|
||||
- Codegen: load value, shift, store back
|
||||
|
||||
---
|
||||
|
||||
## 19. Player 2 Controller
|
||||
|
||||
`Player::P1` and `Player::P2` are defined in the AST but marked `#[allow(dead_code)]`.
|
||||
The parser always produces `ButtonRead(None, ...)` — it never parses `p1.button.X`
|
||||
or `p2.button.X` syntax. The runtime only reads controller 1 ($4016).
|
||||
|
||||
**What's needed**:
|
||||
- Parser: `p1.button.X` and `p2.button.X` syntax producing `Player::P1`/`P2`
|
||||
- Runtime: read controller 2 from $4017 into a second zero-page byte
|
||||
- Codegen: select the correct input byte based on player
|
||||
|
||||
---
|
||||
|
||||
## 20. Register Allocator
|
||||
|
||||
The plan describes `src/codegen/regalloc.rs` for managing A/X/Y allocation, but
|
||||
no register allocator exists. The current codegen uses A for everything and
|
||||
spills to zero-page $02 for comparisons. A proper allocator would:
|
||||
- Track A/X/Y liveness across basic blocks
|
||||
- Use X for array indexing and loop counters
|
||||
- Use Y as secondary index
|
||||
- Spill to zero-page temps only when all three are live
|
||||
|
||||
---
|
||||
|
||||
## Priority Order
|
||||
|
||||
For someone picking up this codebase, the recommended order of work:
|
||||
|
||||
1. **State machine dispatch** (#5) — makes multi-state games actually work
|
||||
2. **Function call codegen** (#2, calls/returns) — makes functions actually work
|
||||
3. **Multi-OAM sprites** (#4) — makes games with >1 sprite possible
|
||||
4. **Array index codegen** (#2, arrays) — makes array-based games work
|
||||
5. **IR-based codegen** (#1) — enables optimizer to affect output
|
||||
6. **Sprite name resolution** (#3) — makes custom graphics work
|
||||
7. **Include directive** (#6) — enables multi-file projects
|
||||
8. **Asset pipeline wiring** (#9) — PNG assets compile into ROMs
|
||||
9. **Error message polish** (#10) — better developer experience
|
||||
10. **Debug mode** (#7) — runtime debugging support
|
||||
Loading…
Add table
Add a link
Reference in a new issue