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
|
|
|
use clap::Parser;
|
2026-04-12 10:01:44 +00:00
|
|
|
use std::path::{Path, PathBuf};
|
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
|
|
|
|
|
|
|
|
use nescript::analyzer;
|
2026-04-12 10:01:44 +00:00
|
|
|
use nescript::assets;
|
2026-04-12 10:46:01 +00:00
|
|
|
use nescript::codegen::{CodeGen, IrCodeGen};
|
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
|
|
|
use nescript::errors::render_diagnostics;
|
2026-04-11 23:34:35 +00:00
|
|
|
use nescript::ir;
|
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
|
|
|
use nescript::linker::Linker;
|
2026-04-11 23:34:35 +00:00
|
|
|
use nescript::optimizer;
|
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
|
|
|
|
|
|
|
|
#[derive(Parser)]
|
|
|
|
|
#[command(name = "nescript", about = "NEScript compiler — NES game development")]
|
|
|
|
|
enum Cli {
|
|
|
|
|
/// Compile a .ne source file into a .nes ROM
|
|
|
|
|
Build {
|
|
|
|
|
/// Input source file
|
|
|
|
|
input: PathBuf,
|
|
|
|
|
|
|
|
|
|
/// Output ROM file (default: input with .nes extension)
|
|
|
|
|
#[arg(short, long)]
|
|
|
|
|
output: Option<PathBuf>,
|
2026-04-12 00:09:47 +00:00
|
|
|
|
|
|
|
|
/// Enable debug mode (runtime checks, debug.log)
|
|
|
|
|
#[arg(long)]
|
|
|
|
|
debug: bool,
|
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
|
|
|
|
|
|
|
|
/// Dump generated 6502 assembly to stdout
|
|
|
|
|
#[arg(long)]
|
|
|
|
|
asm_dump: bool,
|
2026-04-12 10:23:43 +00:00
|
|
|
|
2026-04-12 11:31:01 +00:00
|
|
|
/// Dump the lowered IR program to stdout (after optimization)
|
|
|
|
|
#[arg(long)]
|
|
|
|
|
dump_ir: bool,
|
|
|
|
|
|
2026-04-12 17:43:39 +00:00
|
|
|
/// Dump a human-readable memory map of variable allocations
|
|
|
|
|
/// to stdout.
|
|
|
|
|
#[arg(long)]
|
|
|
|
|
memory_map: bool,
|
|
|
|
|
|
2026-04-12 10:46:01 +00:00
|
|
|
/// Use the legacy AST-based codegen. The default is the IR-based
|
|
|
|
|
/// codegen, which runs the optimizer passes before emitting 6502.
|
2026-04-12 10:23:43 +00:00
|
|
|
#[arg(long)]
|
2026-04-12 10:46:01 +00:00
|
|
|
use_ast: bool,
|
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
|
|
|
},
|
|
|
|
|
/// Type-check a source file without building
|
|
|
|
|
Check {
|
|
|
|
|
/// Input source file
|
|
|
|
|
input: PathBuf,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
|
let cli = Cli::parse();
|
|
|
|
|
|
|
|
|
|
match cli {
|
2026-04-12 00:09:47 +00:00
|
|
|
Cli::Build {
|
|
|
|
|
input,
|
|
|
|
|
output,
|
|
|
|
|
debug,
|
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
|
|
|
asm_dump,
|
2026-04-12 11:31:01 +00:00
|
|
|
dump_ir,
|
2026-04-12 17:43:39 +00:00
|
|
|
memory_map,
|
2026-04-12 10:46:01 +00:00
|
|
|
use_ast,
|
2026-04-12 00:09:47 +00:00
|
|
|
} => {
|
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
|
|
|
let output = output.unwrap_or_else(|| input.with_extension("nes"));
|
2026-04-12 11:31:01 +00:00
|
|
|
match compile(
|
|
|
|
|
&input,
|
|
|
|
|
&CompileOptions {
|
|
|
|
|
debug,
|
|
|
|
|
asm_dump,
|
|
|
|
|
dump_ir,
|
2026-04-12 17:43:39 +00:00
|
|
|
memory_map,
|
2026-04-12 11:31:01 +00:00
|
|
|
use_ast,
|
|
|
|
|
},
|
|
|
|
|
) {
|
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
|
|
|
Ok(rom) => {
|
|
|
|
|
std::fs::write(&output, rom).unwrap_or_else(|e| {
|
|
|
|
|
eprintln!("error: failed to write {}: {e}", output.display());
|
|
|
|
|
std::process::exit(1);
|
|
|
|
|
});
|
|
|
|
|
println!(
|
|
|
|
|
"compiled {} -> {} ({} bytes)",
|
|
|
|
|
input.display(),
|
|
|
|
|
output.display(),
|
|
|
|
|
std::fs::metadata(&output).map(|m| m.len()).unwrap_or(0)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
Err(()) => std::process::exit(1),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Cli::Check { input } => match check(&input) {
|
|
|
|
|
Ok(()) => println!("no errors found in {}", input.display()),
|
|
|
|
|
Err(()) => std::process::exit(1),
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-12 17:43:39 +00:00
|
|
|
/// Print a human-readable memory map of variable allocations.
|
|
|
|
|
/// Entries are sorted by address and labelled with their scope
|
|
|
|
|
/// (zero-page vs RAM).
|
|
|
|
|
fn print_memory_map(analysis: &nescript::analyzer::AnalysisResult) {
|
|
|
|
|
let mut allocs: Vec<_> = analysis.var_allocations.iter().collect();
|
|
|
|
|
allocs.sort_by_key(|a| a.address);
|
|
|
|
|
|
|
|
|
|
println!("=== NEScript Memory Map ===");
|
|
|
|
|
println!("Zero Page ($00-$FF):");
|
|
|
|
|
println!(" $00-$0F [SYSTEM] reserved (frame flag, input, state, params, scratch)");
|
|
|
|
|
for a in allocs.iter().filter(|a| a.address < 0x100) {
|
|
|
|
|
if a.size == 1 {
|
|
|
|
|
println!(" ${:04X} [USER] {} (u8)", a.address, a.name);
|
|
|
|
|
} else {
|
|
|
|
|
println!(
|
|
|
|
|
" ${:04X}-${:04X} [USER] {} ({} bytes)",
|
|
|
|
|
a.address,
|
|
|
|
|
a.address + a.size - 1,
|
|
|
|
|
a.name,
|
|
|
|
|
a.size
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let ram_allocs: Vec<_> = allocs.iter().filter(|a| a.address >= 0x100).collect();
|
|
|
|
|
if !ram_allocs.is_empty() {
|
|
|
|
|
println!("\nRAM ($0200-$07FF):");
|
|
|
|
|
println!(" $0200-$02FF [SYSTEM] OAM shadow buffer");
|
|
|
|
|
for a in &ram_allocs {
|
|
|
|
|
if a.size == 1 {
|
|
|
|
|
println!(" ${:04X} [USER] {} (u8)", a.address, a.name);
|
|
|
|
|
} else {
|
|
|
|
|
println!(
|
|
|
|
|
" ${:04X}-${:04X} [USER] {} ({} bytes)",
|
|
|
|
|
a.address,
|
|
|
|
|
a.address + a.size - 1,
|
|
|
|
|
a.name,
|
|
|
|
|
a.size
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Summary line.
|
|
|
|
|
let zp_used: u16 = allocs
|
|
|
|
|
.iter()
|
|
|
|
|
.filter(|a| a.address < 0x80)
|
|
|
|
|
.map(|a| a.size)
|
|
|
|
|
.sum();
|
|
|
|
|
let ram_used: u16 = allocs
|
|
|
|
|
.iter()
|
|
|
|
|
.filter(|a| a.address >= 0x300)
|
|
|
|
|
.map(|a| a.size)
|
|
|
|
|
.sum();
|
|
|
|
|
println!();
|
|
|
|
|
println!("Zero Page: {zp_used}/128 bytes used");
|
|
|
|
|
println!("Main RAM: {ram_used}/1280 bytes used");
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
fn dump_asm(instructions: &[nescript::asm::Instruction]) {
|
2026-04-12 16:29:15 +00:00
|
|
|
use nescript::asm::{AddressingMode, Opcode};
|
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
|
|
|
for inst in instructions {
|
2026-04-12 16:29:15 +00:00
|
|
|
// A bare `NOP` with a `Label` operand is a label *definition*
|
|
|
|
|
// (the pseudo-instruction the codegen emits when marking a
|
|
|
|
|
// position). Any other opcode with `Label` mode is an actual
|
|
|
|
|
// instruction like `JSR foo` or `JMP bar`, so we show the
|
|
|
|
|
// opcode + target.
|
|
|
|
|
if inst.opcode == Opcode::NOP {
|
|
|
|
|
if let AddressingMode::Label(name) = &inst.mode {
|
|
|
|
|
println!("{name}:");
|
|
|
|
|
continue;
|
|
|
|
|
}
|
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
|
|
|
}
|
|
|
|
|
println!(" {:?} {:?}", inst.opcode, inst.mode);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-12 11:31:01 +00:00
|
|
|
#[allow(clippy::struct_excessive_bools)]
|
|
|
|
|
struct CompileOptions {
|
|
|
|
|
debug: bool,
|
|
|
|
|
asm_dump: bool,
|
|
|
|
|
dump_ir: bool,
|
2026-04-12 17:43:39 +00:00
|
|
|
memory_map: bool,
|
2026-04-12 11:31:01 +00:00
|
|
|
use_ast: bool,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn compile(input: &PathBuf, opts: &CompileOptions) -> Result<Vec<u8>, ()> {
|
|
|
|
|
let debug = opts.debug;
|
|
|
|
|
let asm_dump = opts.asm_dump;
|
|
|
|
|
let dump_ir = opts.dump_ir;
|
2026-04-12 17:43:39 +00:00
|
|
|
let memory_map = opts.memory_map;
|
2026-04-12 11:31:01 +00:00
|
|
|
let use_ast = opts.use_ast;
|
2026-04-12 10:06:58 +00:00
|
|
|
let raw_source = std::fs::read_to_string(input).map_err(|e| {
|
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
|
|
|
eprintln!("error: failed to read {}: {e}", input.display());
|
|
|
|
|
})?;
|
|
|
|
|
|
2026-04-12 10:06:58 +00:00
|
|
|
// Preprocess: inline include directives
|
|
|
|
|
let source = nescript::parser::preprocess_source(&raw_source, Some(input)).map_err(|e| {
|
|
|
|
|
eprintln!("error: {e}");
|
|
|
|
|
})?;
|
|
|
|
|
|
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
|
|
|
let filename = input.to_string_lossy();
|
|
|
|
|
|
|
|
|
|
// Parse
|
|
|
|
|
let (program, parse_diags) = nescript::parser::parse(&source);
|
|
|
|
|
if !parse_diags.is_empty() {
|
|
|
|
|
render_diagnostics(&source, &filename, &parse_diags);
|
|
|
|
|
}
|
|
|
|
|
if parse_diags
|
|
|
|
|
.iter()
|
|
|
|
|
.any(nescript::errors::Diagnostic::is_error)
|
|
|
|
|
{
|
|
|
|
|
return Err(());
|
|
|
|
|
}
|
|
|
|
|
let program = program.ok_or(())?;
|
|
|
|
|
|
|
|
|
|
// Analyze
|
|
|
|
|
let analysis = analyzer::analyze(&program);
|
|
|
|
|
if !analysis.diagnostics.is_empty() {
|
|
|
|
|
render_diagnostics(&source, &filename, &analysis.diagnostics);
|
|
|
|
|
}
|
|
|
|
|
if analysis
|
|
|
|
|
.diagnostics
|
|
|
|
|
.iter()
|
|
|
|
|
.any(nescript::errors::Diagnostic::is_error)
|
|
|
|
|
{
|
|
|
|
|
return Err(());
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-11 23:34:35 +00:00
|
|
|
// IR lowering and optimization
|
|
|
|
|
let mut ir_program = ir::lower(&program, &analysis);
|
|
|
|
|
optimizer::optimize(&mut ir_program);
|
|
|
|
|
|
2026-04-12 11:31:01 +00:00
|
|
|
if dump_ir {
|
|
|
|
|
print!("{}", ir_program.pretty());
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-12 17:43:39 +00:00
|
|
|
if memory_map {
|
|
|
|
|
print_memory_map(&analysis);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-12 10:01:44 +00:00
|
|
|
// Resolve sprite assets (CHR data + tile indices) relative to the
|
|
|
|
|
// source file's directory, so `@binary` / `@chr` paths work naturally.
|
|
|
|
|
let source_dir = input.parent().unwrap_or_else(|| Path::new("."));
|
|
|
|
|
let sprites = assets::resolve_sprites(&program, source_dir).map_err(|e| {
|
|
|
|
|
eprintln!("error: {e}");
|
|
|
|
|
})?;
|
|
|
|
|
|
2026-04-12 10:46:01 +00:00
|
|
|
// Code generation: IR-based is the default. `--use-ast` switches to
|
|
|
|
|
// the legacy AST-based codegen for comparison and fallback.
|
2026-04-12 11:19:56 +00:00
|
|
|
let mut instructions = if use_ast {
|
2026-04-12 10:46:01 +00:00
|
|
|
CodeGen::new(&analysis.var_allocations, &program.constants)
|
2026-04-12 10:23:43 +00:00
|
|
|
.with_sprites(&sprites)
|
2026-04-12 11:36:44 +00:00
|
|
|
.with_enums(&program.enums)
|
2026-04-12 10:43:53 +00:00
|
|
|
.with_debug(debug)
|
2026-04-12 10:46:01 +00:00
|
|
|
.generate(&program)
|
2026-04-12 10:23:43 +00:00
|
|
|
} else {
|
2026-04-12 10:46:01 +00:00
|
|
|
IrCodeGen::new(&analysis.var_allocations, &ir_program)
|
2026-04-12 10:23:43 +00:00
|
|
|
.with_sprites(&sprites)
|
|
|
|
|
.with_debug(debug)
|
2026-04-12 10:46:01 +00:00
|
|
|
.generate(&ir_program)
|
2026-04-12 10:23:43 +00:00
|
|
|
};
|
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
|
|
|
|
2026-04-12 11:19:56 +00:00
|
|
|
// Peephole optimization: cheap pass that removes redundant
|
|
|
|
|
// store-then-load pairs over IR temp slots. Biggest win for the
|
|
|
|
|
// IR codegen, but safe for the AST codegen too.
|
|
|
|
|
nescript::codegen::peephole::optimize(&mut instructions);
|
|
|
|
|
|
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
|
|
|
if asm_dump {
|
|
|
|
|
dump_asm(&instructions);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-12 10:01:44 +00:00
|
|
|
// Link into ROM with sprite CHR data placed at each sprite's tile index.
|
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
|
|
|
let linker = Linker::new(program.game.mirroring);
|
2026-04-12 10:01:44 +00:00
|
|
|
let rom = linker.link_with_assets(&instructions, &sprites);
|
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
|
|
|
|
|
|
|
|
Ok(rom)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn check(input: &PathBuf) -> Result<(), ()> {
|
2026-04-12 10:06:58 +00:00
|
|
|
let raw_source = std::fs::read_to_string(input).map_err(|e| {
|
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
|
|
|
eprintln!("error: failed to read {}: {e}", input.display());
|
|
|
|
|
})?;
|
|
|
|
|
|
2026-04-12 10:06:58 +00:00
|
|
|
let source = nescript::parser::preprocess_source(&raw_source, Some(input)).map_err(|e| {
|
|
|
|
|
eprintln!("error: {e}");
|
|
|
|
|
})?;
|
|
|
|
|
|
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
|
|
|
let filename = input.to_string_lossy();
|
|
|
|
|
|
|
|
|
|
let (program, parse_diags) = nescript::parser::parse(&source);
|
|
|
|
|
if !parse_diags.is_empty() {
|
|
|
|
|
render_diagnostics(&source, &filename, &parse_diags);
|
|
|
|
|
}
|
|
|
|
|
if parse_diags
|
|
|
|
|
.iter()
|
|
|
|
|
.any(nescript::errors::Diagnostic::is_error)
|
|
|
|
|
{
|
|
|
|
|
return Err(());
|
|
|
|
|
}
|
|
|
|
|
let program = program.ok_or(())?;
|
|
|
|
|
|
|
|
|
|
let analysis = analyzer::analyze(&program);
|
|
|
|
|
if !analysis.diagnostics.is_empty() {
|
|
|
|
|
render_diagnostics(&source, &filename, &analysis.diagnostics);
|
|
|
|
|
}
|
|
|
|
|
if analysis
|
|
|
|
|
.diagnostics
|
|
|
|
|
.iter()
|
|
|
|
|
.any(nescript::errors::Diagnostic::is_error)
|
|
|
|
|
{
|
|
|
|
|
return Err(());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|