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;
|
|
|
|
|
use std::path::PathBuf;
|
|
|
|
|
|
|
|
|
|
use nescript::analyzer;
|
|
|
|
|
use nescript::codegen::CodeGen;
|
|
|
|
|
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,
|
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,
|
|
|
|
|
} => {
|
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 00:09:47 +00:00
|
|
|
match compile(&input, debug) {
|
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 00:09:47 +00:00
|
|
|
fn compile(input: &PathBuf, _debug: bool) -> Result<Vec<u8>, ()> {
|
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 source = std::fs::read_to_string(input).map_err(|e| {
|
|
|
|
|
eprintln!("error: failed to read {}: {e}", input.display());
|
|
|
|
|
})?;
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
// Code generation (still AST-based for M2; IR codegen comes in M3)
|
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 codegen = CodeGen::new(&analysis.var_allocations, &program.constants);
|
|
|
|
|
let instructions = codegen.generate(&program);
|
|
|
|
|
|
|
|
|
|
// Link into ROM
|
|
|
|
|
let linker = Linker::new(program.game.mirroring);
|
|
|
|
|
let rom = linker.link(&instructions);
|
|
|
|
|
|
|
|
|
|
Ok(rom)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn check(input: &PathBuf) -> Result<(), ()> {
|
|
|
|
|
let source = std::fs::read_to_string(input).map_err(|e| {
|
|
|
|
|
eprintln!("error: failed to read {}: {e}", input.display());
|
|
|
|
|
})?;
|
|
|
|
|
|
|
|
|
|
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(())
|
|
|
|
|
}
|