mirror of
https://github.com/imjasonh/nescript
synced 2026-07-10 09:42:37 +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
This commit is contained in:
parent
1fca6864ac
commit
39ca246151
32 changed files with 6306 additions and 0 deletions
128
src/analyzer/tests.rs
Normal file
128
src/analyzer/tests.rs
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
use super::*;
|
||||
use crate::errors::ErrorCode;
|
||||
use crate::parser;
|
||||
|
||||
fn analyze_ok(input: &str) -> AnalysisResult {
|
||||
let (prog, diags) = parser::parse(input);
|
||||
assert!(diags.is_empty(), "parse errors: {diags:?}");
|
||||
let prog = prog.unwrap();
|
||||
let result = analyze(&prog);
|
||||
assert!(
|
||||
result.diagnostics.iter().all(|d| !d.is_error()),
|
||||
"analysis errors: {:?}",
|
||||
result.diagnostics
|
||||
);
|
||||
result
|
||||
}
|
||||
|
||||
fn analyze_errors(input: &str) -> Vec<ErrorCode> {
|
||||
let (prog, parse_diags) = parser::parse(input);
|
||||
if prog.is_none() {
|
||||
return parse_diags.into_iter().map(|d| d.code).collect();
|
||||
}
|
||||
let result = analyze(&prog.unwrap());
|
||||
result.diagnostics.into_iter().map(|d| d.code).collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn analyze_minimal_program() {
|
||||
let result = analyze_ok(
|
||||
r#"
|
||||
game "Test" { mapper: NROM }
|
||||
var px: u8 = 128
|
||||
on frame { px = 1 }
|
||||
start Main
|
||||
"#,
|
||||
);
|
||||
assert!(result.symbols.contains_key("px"));
|
||||
assert_eq!(result.var_allocations.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn analyze_allocates_zero_page() {
|
||||
let result = analyze_ok(
|
||||
r#"
|
||||
game "Test" { mapper: NROM }
|
||||
var x: u8 = 0
|
||||
var y: u8 = 0
|
||||
on frame { x = 1 }
|
||||
start Main
|
||||
"#,
|
||||
);
|
||||
// u8 vars should be allocated in zero page starting at $10
|
||||
assert_eq!(result.var_allocations[0].address, 0x10);
|
||||
assert_eq!(result.var_allocations[1].address, 0x11);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn analyze_duplicate_var() {
|
||||
let errors = analyze_errors(
|
||||
r#"
|
||||
game "Test" { mapper: NROM }
|
||||
var x: u8 = 0
|
||||
var x: u8 = 1
|
||||
on frame { x = 1 }
|
||||
start Main
|
||||
"#,
|
||||
);
|
||||
assert!(errors.contains(&ErrorCode::E0501));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn analyze_undefined_transition() {
|
||||
let errors = analyze_errors(
|
||||
r#"
|
||||
game "Test" { mapper: NROM }
|
||||
state Main {
|
||||
on frame { transition Nonexistent }
|
||||
}
|
||||
start Main
|
||||
"#,
|
||||
);
|
||||
assert!(errors.contains(&ErrorCode::E0404));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn analyze_valid_transition() {
|
||||
let _result = analyze_ok(
|
||||
r#"
|
||||
game "Test" { mapper: NROM }
|
||||
state Main {
|
||||
on frame { transition Other }
|
||||
}
|
||||
state Other {
|
||||
on frame { wait_frame }
|
||||
}
|
||||
start Main
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn analyze_start_state_exists() {
|
||||
let errors = analyze_errors(
|
||||
r#"
|
||||
game "Test" { mapper: NROM }
|
||||
state Main {
|
||||
on frame { wait_frame }
|
||||
}
|
||||
start Nonexistent
|
||||
"#,
|
||||
);
|
||||
assert!(errors.contains(&ErrorCode::E0404));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn analyze_const_symbol() {
|
||||
let result = analyze_ok(
|
||||
r#"
|
||||
game "Test" { mapper: NROM }
|
||||
const SPEED: u8 = 2
|
||||
var px: u8 = 0
|
||||
on frame { px = SPEED }
|
||||
start Main
|
||||
"#,
|
||||
);
|
||||
let sym = result.symbols.get("SPEED").unwrap();
|
||||
assert!(sym.is_const);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue