mirror of
https://github.com/imjasonh/nescript
synced 2026-07-08 08:55:38 +00:00
analyzer/lowering: support nested struct fields and array struct fields
Struct field types beyond the v1 scalar set (`u8`, `i8`, `u16`,
`bool`) used to error out with `E0201: struct fields must be
u8/i8/u16/bool`. The size accumulator already handled them
correctly — what was missing was: (1) the analyzer side that
synthesizes per-leaf symbols and allocations for nested structs
plus a single array-typed symbol for array fields, (2) the
parser's chained-field-access path, and (3) the IR-lowering
recursion through nested struct literal initializers and array
literal field values.
The synthetic-variable model carries through unchanged: a
`var p: Player` where `Player { pos: Vec2, hp: u8, inv: u8[4] }`
and `Vec2 { x: u8, y: u8 }` produces flat allocations for
`p.pos.x`, `p.pos.y`, `p.hp`, and `p.inv`, plus an intermediate
`p.pos` Struct symbol so dotted-name lookups still resolve. Array
fields get a single allocation with the array type so the
existing `Expr::ArrayIndex` lowering path handles `p.inv[i]`
without changes. Array-of-structs is still rejected with E0201
because the synthetic model can't index per-element layouts
without further codegen work.
The parser change is the only structural move: `parse_primary`
and `parse_assign_or_call` now loop the dot chain into a single
joined identifier so `p.pos.x` becomes `FieldAccess("p.pos", "x")`
and `p.inv[0]` becomes `ArrayIndex("p.inv", 0)`. The downstream
analyzer and IR lowering use the same `format!("{name}.{field}")`
join they already used for one-level access — no plumbing
changes required.
Includes a new `examples/nested_structs.ne` that exercises both
features end-to-end with two `Hero` instances carrying nested
positions and inventory arrays. The reproducibility tripwire
ROM is committed alongside it and the emulator harness has a
matching pair of golden files.
https://claude.ai/code/session_01KEczoNUX3WmcFLfq6iAQxB
This commit is contained in:
parent
d4e613fb7c
commit
7294ae3efa
12 changed files with 478 additions and 90 deletions
|
|
@ -30,6 +30,7 @@ Open any `.nes` file in an NES emulator ([Mesen](https://www.mesen.ca/), [FCEUX]
|
|||
| `palette_and_background.ne` | palette, background, set_palette, load_background | Reset-time initial load plus vblank-safe runtime swaps |
|
||||
| `friendly_assets.ne` | named colours, grouped palette, pixel art, tilemap+legend, palette_map, scalar sfx pitch, note-name music | Exercises every "friendlier" asset syntax at once — the `palette` uses `bg0..sp3` + a shared `universal:`, the sprite is authored as ASCII pixel art, the background uses a `legend { ... } + map:` tilemap with a `palette_map:` for attributes, the sfx uses a scalar `pitch:` + `envelope:` alias, and the music uses note names (`C4, E4 40, rest 10`) with a `tempo:` default. |
|
||||
| `noise_triangle_sfx.ne` | `channel: noise`, `channel: triangle` on `sfx` blocks | Demonstrates the noise and triangle sfx channels. Declares one noise burst and one triangle bass note, plays each on a timer so the emulator harness captures both the pixel output and the APU state. |
|
||||
| `nested_structs.ne` | nested struct fields, array struct fields, chained literals | Two `Hero` instances each carry a `Vec2` position and a `u8[4]` inventory. Exercises `hero.pos.x` chained access, `hero.inv[i]` array-field access, and chained struct-literal initializers (`Hero { pos: Vec2 { x: ..., y: ... }, inv: [...] }`). |
|
||||
| `platformer.ne` | **every subsystem** | End-to-end side-scrolling demo: custom CHR tileset, full 32×30 nametable with per-region attribute palettes, 2×2 metasprite hero with gravity/jump physics, wrap-around horizontal scrolling, stomp-or-die enemy collisions with a live stomp-count HUD, coin pickups, user-declared SFX + music, and a Title → Playing → GameOver state machine with a proximity-based autopilot so the headless harness cycles through stomp, stomp, die, and retry inside six seconds. Regenerate the tile art with `cargo run --bin gen_platformer_tiles`. |
|
||||
|
||||
## Emulator Controls
|
||||
|
|
|
|||
54
examples/nested_structs.ne
Normal file
54
examples/nested_structs.ne
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
// Demonstrates nested struct fields and array fields inside structs.
|
||||
//
|
||||
// Two `Hero` instances each carry a `Vec2` position (a nested struct)
|
||||
// and a small inventory `u8[4]` (an array field). Both heroes scoot
|
||||
// across the screen in opposite directions, and their inventory bytes
|
||||
// are advanced once per frame so the analyzer's flat-allocation model
|
||||
// is exercised end-to-end.
|
||||
//
|
||||
// Compile with the default IR codegen:
|
||||
// nescript build examples/nested_structs.ne
|
||||
|
||||
game "NestedStructs" { mapper: NROM }
|
||||
|
||||
// Inner structs must be declared before any struct that nests them —
|
||||
// the analyzer doesn't topologically sort declarations.
|
||||
struct Vec2 {
|
||||
x: u8,
|
||||
y: u8,
|
||||
}
|
||||
|
||||
// `pos` is a nested-struct field; `inv` is an array field. Both are
|
||||
// fully addressable as `hero.pos.x` / `hero.inv[i]`. Layout in RAM
|
||||
// is contiguous: pos.x, pos.y, hp, inv[0..3] — total 7 bytes per
|
||||
// instance.
|
||||
struct Hero {
|
||||
pos: Vec2,
|
||||
hp: u8,
|
||||
inv: u8[4],
|
||||
}
|
||||
|
||||
// Hero positions are initialized inline so the on-frame handler
|
||||
// only has to step them. Inventory bytes are a separate `var`
|
||||
// because struct literals don't accept array fields yet.
|
||||
var hero1: Hero = Hero { pos: Vec2 { x: 32, y: 96 }, hp: 100, inv: [1, 2, 3, 4] }
|
||||
var hero2: Hero = Hero { pos: Vec2 { x: 200, y: 128 }, hp: 100, inv: [10, 20, 30, 40] }
|
||||
|
||||
on frame {
|
||||
// Walk both heroes — hero1 moves right, hero2 moves left, both
|
||||
// wrap around at the screen edge so the demo runs forever.
|
||||
hero1.pos.x += 1
|
||||
hero2.pos.x -= 1
|
||||
|
||||
// Cycle the inventory bytes so the synthetic-array allocation
|
||||
// path takes a real read/write pair every frame.
|
||||
hero1.inv[0] += 1
|
||||
hero2.inv[0] += 1
|
||||
|
||||
draw Smiley at: (hero1.pos.x, hero1.pos.y)
|
||||
draw Smiley at: (hero2.pos.x, hero2.pos.y)
|
||||
|
||||
wait_frame
|
||||
}
|
||||
|
||||
start Main
|
||||
BIN
examples/nested_structs.nes
Normal file
BIN
examples/nested_structs.nes
Normal file
Binary file not shown.
|
|
@ -16,8 +16,9 @@ enum Direction { Up, Down, Left, Right }
|
|||
|
||||
enum AnimFrame { Idle, Run1, Run2 }
|
||||
|
||||
// A struct bundles related state into a single variable. Only u8 /
|
||||
// i8 / bool fields are allowed in v0.1 — no nesting, arrays, or u16.
|
||||
// A struct bundles related state into a single variable.
|
||||
// See examples/nested_structs.ne for nested-struct and array-field
|
||||
// fields; this example sticks to flat scalar fields for simplicity.
|
||||
struct Player {
|
||||
x: u8,
|
||||
y: u8,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue