1
0
Fork 0
mirror of https://github.com/imjasonh/nescript synced 2026-07-08 17:06:04 +00:00
Commit graph

202 commits

Author SHA1 Message Date
Claude
281198cbb9
docs: struct types in the language guide
https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
2026-04-12 16:18:29 +00:00
Claude
225d40cea5
Language: struct types
Adds composite \`struct\` types with field access:

    struct Vec2 { x: u8, y: u8 }

    var pos: Vec2
    pos.x = 100
    pos.y = pos.x + 5

- Lexer: \`struct\` keyword
- AST: \`StructDecl\` with \`StructField\` list; \`NesType::Struct(name)\`
  for struct-typed variable declarations; \`Expr::FieldAccess\` and
  \`LValue::Field\` for reads/writes
- Parser: top-level \`struct Name { field: type, ... }\` (optional
  trailing comma) and \`ident.field\` syntax in both expression and
  lvalue position
- Analyzer: \`register_struct\` computes contiguous field offsets
  (no padding) and stores them in \`struct_layouts\`. Struct variables
  synthesize a \`VarAllocation\` per field under the name
  \`"struct_var.field"\`, and \`Expr::FieldAccess\` / \`LValue::Field\`
  resolve against those. Unknown struct types and unknown fields
  emit E0201.
- IR lowering + AST codegen: treat struct field access as ordinary
  variable access against the synthetic per-field symbols. No new IR
  ops are needed.

v1 structs only support primitive fields (u8/i8/bool). Nested structs,
u16 fields, and array fields are not yet allowed.

https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
2026-04-12 16:18:05 +00:00
Claude
9c102cb68e
Codegen: copy propagation + dead LDA elimination in peephole
Adds two new passes that dramatically close the IR-vs-AST output
size gap:

- **copy_propagate_temps**: tracks the source of each \`STA\` into an
  IR temp slot (\$80-\$FF). Subsequent \`LDA\` of that slot is rewritten
  to load from the original source directly. The same rewrite applies
  to arithmetic/logical ops that read a temp (\`ADC\`/\`SBC\`/\`AND\`/
  \`ORA\`/\`EOR\`/\`CMP\`). The now-unused \`STA\` is picked up by the
  dead-store pass on the next iteration.
- **remove_dead_loads**: drops an \`LDA\` whose value is immediately
  overwritten by another A-clobbering load (\`LDA\`/\`PLA\`/\`TXA\`/\`TYA\`).

The existing dead-store pass is also extended to scan through labels
and branches within a function body, since IR temp slots are
SSA-unique per function. Function boundaries (\`__ir_fn_\`/\`__fn_\` labels,
\`RTS\`/\`RTI\`) still stop the scan.

Instruction count comparison (IR vs AST codegen on examples/):

                             before            after
    arrays_and_functions.ne  359 vs 236   ->   264 vs 236  (-95 ir)
    bouncing_ball.ne         192 vs 144   ->   135 vs 144  (-57 ir, IR now wins)
    coin_cavern.ne           412 vs 291   ->   290 vs 291  (-122 ir, IR ties)
    hello_sprite.ne          116 vs 64    ->    73 vs 64
    mmc1_banked.ne           378 vs 247   ->   261 vs 247
    sprites_and_palettes.ne  194 vs 113   ->   121 vs 113
    state_machine.ne         323 vs 229   ->   233 vs 229

IR is now competitive with the AST codegen on every example and
beats it on bouncing_ball and coin_cavern.

https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
2026-04-12 16:06:26 +00:00
4ff4eb5028
Merge pull request #1 from imjasonh/claude/implement-plan-with-tests-WdMI4 2026-04-12 08:03:04 -04:00
Claude
de49c1a8ba
docs: future-work.md adds enums, peephole, --dump-ir
https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
2026-04-12 11:43:01 +00:00
Claude
e1bddbc0db
Codegen: A-value tracking peephole for redundant LDA elimination
Tracks what the accumulator currently holds across a linear run of
instructions. When we see \`LDA addr\` or \`LDA #val\` and A is
already known to hold that value, the load is dropped. A-modifying
ops (ADC, SBC, shifts, transfers from X/Y, etc.) clear the tracker.
Any control-flow instruction or label resets it too, so JSR / branches
remain safe.

Reduces STA/LDA count in bouncing_ball from 113 to 108.

https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
2026-04-12 11:42:04 +00:00
Claude
d4daa6d0a9
docs: enum type + new CLI flags in the language guide
Documents the \`enum Name { Variant, ... }\` syntax and adds
\`--dump-ir\` and \`--use-ast\` to the CLI flag table. Also adds
an integration test covering enum-variant-as-condition and variant
assignment through the full compile pipeline.

https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
2026-04-12 11:39:12 +00:00
Claude
dbfcb313bc
Language: enum declarations
Adds \`enum Name { V1, V2, ... }\` as a top-level declaration. Each
variant is registered as a \`u8\` constant equal to its index in the
declaration. Variant names are global and must be unique across all
enums and other symbols (E0501 on collision).

- Lexer: \`enum\` keyword
- AST: \`EnumDecl { name, variants, span }\` field on \`Program\`
- Parser: top-level \`enum\` syntax with optional trailing commas
- Analyzer: \`register_enum\` flattens variants into the symbol table
- IR lowering and AST codegen: variants resolve through the existing
  \`const_values\` path
- Tests cover parsing, duplicate detection, and usage

https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
2026-04-12 11:36:44 +00:00
Claude
ae051f5909
CLI: --dump-ir flag + IrProgram::pretty
Adds \`IrProgram::pretty()\` for a readable IR dump and wires it to a
new \`--dump-ir\` build flag. Useful for debugging IR-level issues
(optimizer, lowering). Refactored \`compile()\` to take a
\`CompileOptions\` struct to keep clippy's \`fn-params-excessive-bools\`
lint happy.

https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
2026-04-12 11:31:01 +00:00
Claude
8b35ba9e13
Codegen: peephole dead-store elimination for IR temps
Adds a per-instruction scan: for each \`STA slot\` where slot is an
IR temp (\$80-\$FF), walks forward through the instruction stream.
If the slot is overwritten with no intervening read, the original
STA is dead and is removed. Conservatively bails at any control-flow
boundary (jump, branch, call, return, label definition) since a
jumper or callee might read the slot.

Complements the existing peephole passes.

https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
2026-04-12 11:27:16 +00:00
Claude
3f87aa1e30
Analyzer: W0103 also fires on unused state-local variables
Previously the unused-variable warning only ran on globals. Now it
walks \`state.locals\` too, so state-scoped variables that are
declared and never read also emit W0103. Factored out to
\`check_unused_var\` to share the \`_\`-prefix exemption and help text.

https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
2026-04-12 11:21:53 +00:00
Claude
b7c806523d
Codegen: peephole pass for redundant STA/LDA pairs
Adds src/codegen/peephole.rs with two passes that run to fixed point
after codegen:
- \`STA slot; LDA slot\` over an IR temp (\$80-\$FF): the LDA is
  redundant since A already holds the value. Dropped.
- \`LDA addr; STA addr\` (same address): the STA is a no-op since
  the byte was just loaded from that slot. Dropped.

Conservative on user variables (not IR temps) so intervening IRQs or
function calls can't invalidate the optimization. This is the biggest
single win for IR codegen output quality, since IR codegen currently
stores every temp to ZP regardless of reuse.

https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
2026-04-12 11:19:56 +00:00
Claude
c49c36b516
Update future-work.md with recently completed items
Documents the analyzer improvements (call arity, return type, W0101,
W0102, W0104, E0301, E0505), the \`on scanline\` parser/analyzer
support, and the inline assembly subsystem. Reorders the remaining
priority list.

https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
2026-04-12 11:17:19 +00:00
Claude
121b0b1968
Inline assembly: asm { ... } blocks
- Lexer: after \`asm\` keyword, next \`{\` triggers raw-text capture of
  the body until the matching \`}\`, emitted as a new \`AsmBody\` token
- Parser: \`asm { ... }\` produces \`Statement::InlineAsm(body, span)\`
- Analyzer: treats inline asm as opaque (no checks)
- IR: new \`IrOp::InlineAsm(String)\` variant that passes the body
  through the optimizer unchanged
- \`src/asm/inline_parser.rs\`: minimal 6502 mnemonic parser supporting
  every addressing mode we emit elsewhere (immediate, ZP/ABS with X/Y,
  indirect, indirect-X/Y, labels, branches, implied, accumulator)
- Both IR and AST codegen splice parsed instructions inline
- Integration test covers a mix of implied + immediate + ZP + A modes

https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
2026-04-12 11:16:18 +00:00
Claude
f8743cf95e
Analyzer: W0104 warn on dead code after terminator
When a block contains a statement after \`return\`, \`break\`,
\`continue\`, or \`transition\`, the trailing statement is unreachable
and the analyzer now emits W0104 pointing at it. The warning includes
a secondary label on the terminator showing where execution stops.

Adds \`Statement::span\` helper to the AST so diagnostics can point at
arbitrary statements cleanly.

https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
2026-04-12 11:08:14 +00:00
Claude
e688397594
Analyzer: E0301 error on RAM exhaustion
The linear RAM allocator now checks for overflow. The zero-page region
is capped at \$80 (leaving \$80-\$FF for IR codegen temp slots), and the
main RAM allocator stops at \$0800 (end of NES internal RAM). Overflow
emits E0301 with a helpful note pointing at the declaration.

https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
2026-04-12 11:05:13 +00:00
Claude
1e4e384501
Analyzer: W0101 warn on expensive multiply/divide
Emits W0101 on \`a * b\`, \`a / b\`, \`a % b\` expressions where both
operands are non-constant (neither is an integer literal). These lower
to calls into the software multiply/divide routines and can blow the
6502 cycle budget. Multiplying by a constant is still silent because
the optimizer can strength-reduce power-of-2 cases to shifts and small
factors to add chains.

https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
2026-04-12 11:01:03 +00:00
Claude
4e05c008ce
Parser: E0505 error on multiple 'start' declarations
Previously a second \`start X\` statement silently overrode the first.
Now emits E0505 at parse time.

https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
2026-04-12 10:59:06 +00:00
Claude
814cbe54bd
Parser/analyzer: on scanline(N) handlers
Adds parser support for \`on scanline(N) { ... }\` event handlers
inside state bodies. The analyzer enforces that scanline handlers
are only allowed with the MMC3 mapper (the only mapper we target
with scanline IRQ support), and validates the body like any other
state handler.

Codegen for the scanline IRQ vector is still TODO — the handler
is parsed and validated but not yet hooked up to the MMC3 IRQ
counter. When we get to it, the linker needs to install an IRQ
handler that writes to \$C000/\$C001/\$E000/\$E001.

https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
2026-04-12 10:57:34 +00:00
Claude
21a74652f2
Analyzer: W0102 warn on infinite loop without yield
Emits W0102 when a \`loop\` body contains no \`break\`, \`return\`,
\`transition\`, or \`wait_frame\`, since such loops spin forever and
prevent vblank from being handled. The check recurses into nested
if/while bodies so that \`break\` behind a condition still counts.

https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
2026-04-12 10:54:30 +00:00
Claude
6f0e4a0a74
Analyzer: validate return statement types
Tracks the current function's declared return type and checks that
`return value` statements match it. Returning from a void function
with a value produces E0203; returning a value with the wrong type
produces E0201. State handler `return value` is still silently
accepted (the value is discarded).

https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
2026-04-12 10:52:06 +00:00
Claude
28e1627953
Analyzer: validate function call arity
Tracks parameter types per function in a signature map, and validates
that each Statement::Call and Expr::Call has the right number of
arguments (E0203). Also type-checks each argument against its declared
parameter type. Previously, calls with the wrong argument count silently
passed semantic analysis and only broke later at codegen or runtime.

https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
2026-04-12 10:49:36 +00:00
Claude
3007266edf
Make IR codegen the default, fall back to AST via --use-ast
The IR-based codegen now matches all features of the AST codegen
(state dispatch, multi-OAM, P1/P2 input, scroll, debug.log/assert),
so flip the default. The legacy AST codegen is still available via
--use-ast for comparison and fallback during validation.

https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
2026-04-12 10:46:01 +00:00
Claude
d609b77cd7
IR codegen: scroll, debug.log, debug.assert
Adds Scroll / DebugLog / DebugAssert variants to IrOp, wires them into
the IR lowering, optimizer liveness tracking, and IR codegen. Scroll
emits two PPU $2005 writes; debug statements emit writes to $4800 when
IrCodeGen::with_debug(true) is set, stripped otherwise. These were the
last feature gaps versus the AST codegen path, so IR codegen is now a
full replacement.

https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
2026-04-12 10:43:53 +00:00
Claude
8a6441071e
IR codegen: Player 2 controller support
ReadInput now takes an explicit player index (0 = P1, 1 = P2). The IR
lowering for ButtonRead threads the player through, and the IR codegen
selects the correct zero-page input byte ($01 for P1, $08 for P2).

https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
2026-04-12 10:40:42 +00:00
Claude
adf50aaa7f
IR codegen: on_enter handlers, frame handler return-to-main-loop
- Initial program boot calls the start state's on_enter handler if it exists
- Transition IR op now JSRs the target state's on_enter handler after
  updating current_state and before jumping to the main dispatch loop
- Frame handlers translate their Return(None) terminator to
  JMP __ir_main_loop instead of RTS (they're not called via JSR)
- Added function_exists() helper and function_names HashSet for quick lookup
- Added in_frame_handler flag to track context during terminator emission

All 271 tests still pass, all 7 examples still compile through --use-ir.

https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
2026-04-12 10:36:31 +00:00
Claude
5660a8b7c3
IR codegen: state dispatch, multi-OAM, and transition support
State machine dispatch:
- IrProgram now stores states (Vec<String>) and start_state
- Lowering captures state metadata before walking the AST
- IR codegen generates a main loop with vblank wait and CMP+BNE+JMP
  dispatch table, matching the AST codegen's layout
- Each frame handler ends with JMP __ir_main_loop
- current_state initialized to the start state's index at boot

Multi-OAM support:
- next_oam_slot counter, reset at the start of each _frame function
- Sequential allocation of 4-byte OAM entries at $0200 + slot*4
- Silently drops draws beyond slot 63 (OAM full)

Transition codegen:
- IrOp::Transition now looks up the target state's index from
  state_indices, writes it to ZP $03, and JMPs back to main loop
- Previously this was a no-op placeholder

Shared constants:
- ZP_FRAME_FLAG ($00) and ZP_CURRENT_STATE ($03) match AST codegen

Tests: 271 total (5 new IR codegen tests + 2 new integration tests)
All 7 examples compile through --use-ir, including multi-state games
and programs with transitions and functions.

https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
2026-04-12 10:33:58 +00:00
Claude
7e94cf37e3
Update future-work.md to reflect IR codegen completion 2026-04-12 10:24:12 +00:00
Claude
1ede169f1e
Implement IR-based code generator (--use-ir)
New src/codegen/ir_codegen.rs walks IrProgram and emits 6502 instructions.
This enables optimizer passes to actually affect the output ROM.

Design:
- Each IR temp gets a zero-page slot at $80 + temp_index
- Functions reset the temp counter at entry (temps don't outlive functions)
- Globals map by name to their analyzer-assigned zero-page addresses
- Operands are loaded into A, computed, stored back to the dest slot

Handles all IrOp variants:
- LoadImm, LoadVar, StoreVar (basic loads/stores)
- Add/Sub (CLC+ADC / SEC+SBC)
- Mul (JSR __multiply runtime routine)
- And/Or/Xor (zero-page operands)
- ShiftLeft/ShiftRight (repeated ASL/LSR)
- Negate/Complement (EOR #$FF + optional two's complement)
- CmpEq/Ne/Lt/Gt/LtEq/GtEq (CMP + conditional branch + 0/1)
- ArrayLoad/ArrayStore (TAX + ZeroPageX/AbsoluteX)
- Call (ZP param passing + JSR)
- DrawSprite (OAM slot 0 write, uses sprite_tiles map)
- ReadInput (LDA $01, P1 input)
- WaitFrame (poll frame flag at $00)

All terminators:
- Jump (JMP to block label)
- Branch (LDA temp + BNE true / JMP false)
- Return (optional value in A + RTS)
- Unreachable (BRK)

IR lowering fixes:
- ReadInput now has a destination IrTemp (was a side-effect-only op)
- ButtonRead uses the proper input temp instead of uninitialized register
- Logical AND/OR use new emit_move helper (OR with zero) instead of
  bogus raw VarId for path merging

CLI:
- New --use-ir flag on `build` subcommand to opt in to IR codegen
- Default remains AST codegen (for now); IR codegen is experimental

All 7 examples compile through the IR pipeline and produce valid iNES ROMs.

Tests: 266 total (7 new ir_codegen unit + 2 new integration).

https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
2026-04-12 10:23:43 +00:00
Claude
5512567349
Asset pipeline: resolve @binary and @chr file paths
resolve_sprites() now handles all three AssetSource variants:
- Inline(bytes): use directly (existing behavior)
- Binary(path): read raw bytes from file relative to source_dir
- Chr(path): convert PNG to CHR via png_to_chr()

Missing files are silently skipped rather than erroring, so
declarations can reference assets that haven't been added yet.
This keeps existing tests that use placeholder file paths working.

Updated future-work.md: moved include directive, P2 controller,
sprite resolution, shift-assign, debug statements, warnings, and
asset wiring to the "completed" section. Remaining work is IR codegen,
audio, on_scanline, and language features (structs/enums).

Tests: 257 total (3 new resolve_sprites tests)

https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
2026-04-12 10:13:26 +00:00
Claude
45b2b2a279
Add debug.log and debug.assert statements
Parser:
- debug.log(expr, ...) and debug.assert(cond) syntax
- parse_debug_statement() handles both methods
- New AST variants: Statement::DebugLog and Statement::DebugAssert

Codegen:
- CodeGen::with_debug(bool) to toggle debug instrumentation
- DebugLog writes each expression to $4800 (Mesen debug port)
- DebugAssert evaluates condition; on false, writes $FF to $4800 + BRK
- Both are stripped entirely when debug_mode = false (release builds)

CLI:
- --debug flag now wired through to CodeGen
- Without --debug, all debug statements produce zero bytes

Analyzer:
- Type-checks DebugAssert condition as bool
- Walks DebugLog args for reads (used_vars tracking)

IR lowering:
- DebugLog/DebugAssert are no-ops (codegen handles them directly)

254 tests (5 new: 3 parser + 2 codegen).

https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
2026-04-12 10:11:32 +00:00
Claude
6efef6c4c5
Add include directive for multi-file projects
Parser/preprocess module:
- New src/parser/preprocess.rs handles source-level include inlining
- 'include "path"' at line start splices the target file's content
- Paths resolved relative to the including file's directory
- Circular include detection via visited set (canonicalized paths)
- Non-cycle re-includes allowed (sibling branches can reuse files)

main.rs:
- Both compile() and check() run preprocess_source() before parsing
- Include resolution failures produce a clear error message

Tests: 4 new preprocess unit tests (249 total)
- passthrough, include line parsing, temp-file roundtrip

https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
2026-04-12 10:06:58 +00:00
Claude
18e9bed258
Add Player 2 controller support
Parser:
- p1.button.X and p2.button.X syntax
- Emits Expr::ButtonRead with Some(Player::P1) or Some(Player::P2)
- Plain button.X still works (defaults to P1)
- Removed #[allow(dead_code)] from Player::P1 and Player::P2

Runtime:
- NMI handler now reads both $4016 (JOY1) and $4017 (JOY2) in
  the same loop, shifting 8 bits into ZP_INPUT_P1 ($01) and
  ZP_INPUT_P2 ($08) simultaneously
- JOY2 register constant added

Codegen:
- Button reads select correct ZP address based on Player variant
- gen_condition and gen_expr both handle P1/P2 dispatching

Tests: 245 (3 new parser tests: p1/p2 button read + shift-assign)

https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
2026-04-12 10:05:12 +00:00
Claude
6430c3a935
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
2026-04-12 10:01:44 +00:00
Claude
5d2d242520
Update future-work.md: mark completed items
13 items moved from backlog to "recently completed":
state dispatch, function calls, break/continue, return, transition,
array indexing, scroll, multiply/divide/modulo, shifts, multi-OAM,
const assignment error, break-outside-loop error, math in linker.

Remaining priority: IR codegen, sprite resolution, include, asset
pipeline wiring, debug mode, error polish, audio, language features.

https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
2026-04-12 02:07:50 +00:00
Claude
1bbc2bc160
Implement break/continue/return/transition/call and multi-OAM sprites
Control flow:
- Break: JMP to break_label from loop_stack
- Continue: JMP to continue_label from loop_stack
- Return: evaluate expr into A (if present), RTS
- Transition: write state index to ZP $03, JMP __main_loop
- Call: evaluate args to ZP param slots, JSR __fn_name

Multi-OAM sprite support:
- next_oam_slot counter (0-63) increments per draw
- Reset to 0 at the start of each frame handler
- Each draw writes to sequential 4-byte OAM entries ($0200+slot*4)
- Silently stops drawing if >64 sprites per frame (OAM full)

All 233 tests pass, clippy clean.

https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
2026-04-12 02:07:00 +00:00
Claude
40632f192c
Implement codegen for state dispatch, functions, arrays, math, scroll
State machine dispatch:
- Assign each state a numeric index, store in ZP $03
- Main loop dispatch table: CMP + BNE + JMP trampoline pattern
  (avoids branch range limits for large programs)
- on_enter/on_exit handlers generated as JSR targets
- Transition statement writes state index + JSR enter/exit handlers

Function calls:
- Function bodies emitted as labeled subroutines with RTS
- Statement::Call generates parameter passing via ZP + JSR
- Statement::Return generates RTS (with value in A if present)
- Parameter slots at ZP $04-$07

Break/continue:
- Loop stack tracks continue/break label pairs
- Break generates JMP to break_label
- Continue generates JMP to continue_label
- While and Loop push/pop the stack

Array indexing:
- LValue::ArrayIndex generates TAX + STA absolute,X
- Expr::ArrayIndex generates TAX + LDA absolute,X / ZP,X
- Compound array assignments (+=, -=, &=, |=, ^=) load-modify-store

Scroll:
- scroll(x, y) writes to PPU $2005 twice (X then Y)

Math:
- Multiply generates JSR __multiply (shift-and-add routine)
- Divide generates JSR __divide (restoring division)
- Modulo loads remainder from $03 after divide
- ShiftLeft generates ASL A, ShiftRight generates LSR A
- Math routines wired into linker

Error validations:
- E0203 for assignment to const variables
- Break/continue outside loop detection (in_loop tracking)

233 tests (8 new codegen + 2 analyzer + 2 integration), all passing.

https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
2026-04-12 02:04:49 +00:00
Claude
3b1a03981b
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
2026-04-12 01:10:11 +00:00
Claude
bc165ed647
Fix bugs found during code review
Issues found by systematic review of all compiler modules.
Each fix includes a regression test.

Parser fixes:
- Add EOF checks to function call arg parsing loops (lines 984, 1269)
  to prevent infinite loops on unterminated call expressions
- Fix span end positions: use .end instead of .start for 9 declaration
  span computations (var, const, fun, state, bank, sprite, palette,
  background, block)
- Add missing compound assignment operators (&= |= ^=) to
  parse_assign_op() for array element assignments

Codegen fix:
- Fix NOT operator: old code used EOR #$FF + AND #$01 which produced
  wrong results for non-boolean inputs (e.g., NOT 0x42 returned 1
  instead of 0). New code uses BEQ/branch to properly map any nonzero
  value to 0 and zero to 1.

Assembler fixes:
- Add branch offset range validation: assert offset is in -128..127
  range instead of silently wrapping on overflow
- Panic on unresolved labels instead of silently leaving placeholder
  0x00 bytes in the output

Verified non-issues (reviewer false positives):
- NMI P register: 6502 hardware pushes P automatically on NMI entry;
  RTI restores it. PHP/PLP not needed.
- Controller read buffer: 8 iterations of LSR+ROL fully replace all
  8 bits, so prior contents don't matter.
- advance() panic: tokens always has at least Eof from lexer.

4 new regression tests, 225 total.

https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
2026-04-12 01:00:27 +00:00
Claude
07dd5bd7e8
Add fuzz testing and parser edge case regression tests
Fuzz targets (cargo +nightly fuzz):
- fuzz_lexer: lexer must never panic on arbitrary input
- fuzz_parser: parser must never panic on arbitrary input
- fuzz_compile: full pipeline (parse → analyze → IR → optimize →
  codegen → assemble → link) must never panic

Results: 3.3M iterations across all targets, zero crashes.
Seed corpus from all 7 examples + crafted edge cases.

11 new parser edge case tests:
- draw_followed_by_statement: regression for keyword-arg lookahead bug
- draw_with_frame_followed_by_statement: same with frame: arg
- nested_if_else_chain: 3-deep else-if chain
- deeply_nested_blocks: 4-deep nested if blocks
- empty_function_body: fun noop() {}
- empty_state_handlers: on enter/exit/frame {}
- button_start_in_condition: "start" keyword as button name
- button_select_in_condition: "select" keyword as button name
- multiple_draws_in_sequence: 3 draws followed by assignment
- parser_no_panic_on_garbage: 25 malformed inputs
- lexer_no_panic_on_garbage: 10 malformed inputs

221 tests total, all pre-commit checks pass.

https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
2026-04-12 00:52:35 +00:00
Claude
d7092f703d
Add comprehensive documentation
docs/language-guide.md (895 lines):
  Complete reference for every language feature with code examples.
  Covers: program structure, types, expressions, statements, assets,
  mappers, error codes, and compiler flags.

docs/architecture.md (130 lines):
  Compiler pipeline overview, module descriptions, testing guide.

docs/nes-reference.md (190 lines):
  NES hardware quick reference: CPU, memory map, PPU, iNES format.

https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
2026-04-12 00:41:34 +00:00
Claude
b8c9e41276
Add README, LICENSE, examples, fix draw parser lookahead
README: project overview, quick start, feature list, example table
LICENSE: MIT
4 new examples covering all language features:
  - arrays_and_functions: arrays, while loops, inline/regular functions
  - state_machine: multi-state flow with on enter/exit handlers
  - sprites_and_palettes: inline CHR data, palette switching, scroll, cast
  - mmc1_banked: MMC1 mapper, bank declarations, software multiply

Parser fix: draw statement keyword-arg parsing now checks for ':'
lookahead before consuming an identifier, preventing it from eating
the next statement as a keyword argument (e.g., `i += 1` after a draw).

https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
2026-04-12 00:38:19 +00:00
Claude
5434dda114
M4+M5: Optimizer passes, type casting, bank switching, math runtime
Milestone 4 — Optimization & Polish:
- Strength reduction: multiply by power-of-2 → shift left
- Zero-page promotion analysis: rank variables by access frequency
- `as` type casting expression in parser/AST/analyzer
- `scroll(x, y)` statement
- `--asm-dump` flag for viewing generated assembly
- Extended optimizer tests (strength reduction, frequency analysis)

Milestone 5 — Bank Switching & Release:
- Mapper support: MMC1 (1), UxROM (2), MMC3 (4) in parser and ROM builder
- Bank declarations: `bank Name: prg` / `bank Name: chr`
- Linker::with_mapper for mapper-aware ROM generation
- Software multiply (8x8→16, shift-and-add algorithm)
- Software divide (8÷8→8, restoring division algorithm)
- ROM tests for mapper encoding round-trip
- Integration test for MMC1 compilation

210 tests total (18 new), all pre-commit checks pass.

https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
2026-04-12 00:22:11 +00:00
Claude
058f7a87b6
M3: Asset pipeline, sprite/palette/background declarations, debug symbols
Parser extensions:
- sprite declarations with chr: @chr("file.png"), @binary("file.bin"), or inline [hex]
- palette declarations with colors: [0x0F, 0x00, 0x10, 0x20]
- background declarations with chr: asset source
- @chr/@binary asset source parsing
- load_background and set_palette statements
- --debug CLI flag (plumbed through, not yet wired to codegen)

Asset pipeline (new module):
- PNG → CHR tile conversion using image crate (8x8 tiles, 2-bitplane encoding)
- NES color palette table (all 64 standard NES colors as RGB)
- Nearest-color matching (Euclidean distance in RGB space)

Debug module (new):
- Source map (ROM address → source Span mapping)
- Debug symbols with variable address table
- Mesen-compatible .mlb label export
- .sym symbol table export

192 tests total (13 new: 5 parser + 3 asset + 5 debug)

https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
2026-04-12 00:09:47 +00:00
Claude
0dc06f7f1a
M2: Wire IR pipeline, add Coin Cavern example and integration tests
Pipeline:
- main.rs now runs IR lowering and optimization before codegen
- IR is built and optimized but output still uses AST-based codegen
  (IR-based codegen is a future improvement)

Coin Cavern example (examples/coin_cavern.ne):
- 3-state game: Title → Playing → GameOver
- Functions (clamp_x), constants, gravity physics, coin collection
- Demonstrates most M2 language features

Integration tests (14 total, 7 new):
- program_with_functions: functions with params and return values
- program_with_while_loop: while loops compile correctly
- program_with_fast_slow_vars: placement hints accepted
- program_with_multi_state_transitions: 3-state cycle
- coin_cavern_compiles: full Coin Cavern example
- ir_pipeline_produces_ir: validates IR lowering + optimizer
- error_test_recursion_detected: E0402 for recursive functions

https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
2026-04-11 23:34:35 +00:00
Claude
192d9c5c3d
M2: Add call graph analysis, recursion detection, and optimizer
Analyzer extensions:
- Call graph construction from function bodies and state handlers
- DFS-based recursion detection (direct and mutual) with E0402 errors
- Max call depth computation per entry point with E0401 enforcement
- Function declarations registered as symbols (E0503 for undefined calls)
- Collects calls from all statement/expression types recursively

Optimizer (new module):
- Constant folding: evaluate known-constant arithmetic at compile time
- Dead code elimination: remove ops with unused destination temps
- Both operate per-basic-block in a single pass

171 tests total (22 new: 6 analyzer + 11 IR lowering + 5 optimizer)

https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
2026-04-11 23:32:12 +00:00
Claude
664ccc05db
M2: Add function/array parsing, IR data structures and lowering
Parser extensions:
- Function declarations with params and return types (fun, inline fun)
- Array type syntax (u8[16]), array literal expressions ([1, 2, 3])
- fast/slow variable placement hints
- Functions stored in Program AST

IR module (new):
- IrProgram, IrFunction, IrBasicBlock, IrOp, IrTerminator types
- AST-to-IR lowering pass with:
  - Expression lowering to virtual temps
  - Control flow (if/else, while, loop) to basic blocks with branches
  - Break/continue via loop context stack
  - Short-circuit logical and/or
  - Button reads, draw sprites, wait_frame
  - Constants inlined as LoadImm
  - State handlers lowered as separate IR functions

Tests: 6 new parser tests, 11 new IR lowering tests (153 total)

https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
2026-04-11 23:25:26 +00:00
Claude
fd5a940c89
Add example builds to CI, document sprite name behavior
- CI: new "Build Examples" job compiles all .ne files and validates
  the output ROMs have correct iNES headers
- Examples: add notes explaining that sprite names (Smiley, Ball) are
  parsed but not yet resolved — all draws use the built-in CHR tile 0.
  Custom sprite declarations come in M3.
- Codegen: explicit `let _ = &draw.sprite_name` to document the
  intentional skip, with comment about M2/M3 scope

https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
2026-04-11 22:55:43 +00:00
Claude
a20ddcbbab
Add example games and quick-start guide
Two example .ne programs with compiled .nes ROMs:
- hello_sprite: d-pad controlled sprite movement
- bouncing_ball: auto-bouncing sprite with edge detection

Includes README with build instructions and emulator setup.

https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
2026-04-11 22:17:18 +00:00
Claude
39ca246151
Implement NEScript compiler Milestone 1 ("Hello Sprite")
Complete implementation of the NEScript compiler pipeline for M1:
- Lexer: full tokenization with hex/binary/decimal literals, all keywords, operators
- Parser: recursive descent with Pratt expression parsing (M1 subset)
- Analyzer: symbol resolution, type checking, memory allocation
- 6502 Assembler: full opcode encoding table (~150 valid combinations)
- Code Generator: AST → 6502 instructions (direct, no IR for M1)
- Runtime: NES hardware init, NMI handler, controller read, OAM DMA
- Linker: NROM layout, vector table, palette loading, CHR data
- ROM Builder: iNES header generation, PRG/CHR padding
- CLI: `build` and `check` subcommands via clap

143 tests across all modules:
- 22 lexer tests (literals, keywords, operators, error recovery)
- 18 parser tests (expressions, statements, game structure, errors)
- 7 analyzer tests (symbol resolution, memory allocation, transitions)
- 30 assembler tests (every addressing mode, label resolution)
- 7 codegen tests (var init, arithmetic, buttons, draw, comparisons)
- 11 runtime tests (init sequence, NMI handler, controller read)
- 10 ROM builder tests (iNES format, mirroring, banking, validation)
- 5 linker tests (vector table, CHR data, palette loading)
- 7 integration tests (end-to-end compilation, error detection)

CI: GitHub Actions for check, fmt, clippy, test
Pre-commit: script for local fmt + clippy + test validation

https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
2026-04-11 22:07:56 +00:00