1
0
Fork 0
mirror of https://github.com/imjasonh/nescript synced 2026-07-09 09:18:01 +00:00

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
This commit is contained in:
Claude 2026-04-12 02:04:49 +00:00
parent 3b1a03981b
commit 40632f192c
No known key found for this signature in database
6 changed files with 466 additions and 31 deletions

View file

@ -47,6 +47,7 @@ pub fn analyze(program: &Program) -> AnalysisResult {
call_graph: HashMap::new(),
max_depths: HashMap::new(),
stack_depth_limit: DEFAULT_STACK_DEPTH,
in_loop: false,
};
analyzer.analyze_program(program);
@ -68,6 +69,7 @@ struct Analyzer {
call_graph: HashMap<String, Vec<String>>,
max_depths: HashMap<String, u32>,
stack_depth_limit: u32,
in_loop: bool,
}
impl Analyzer {
@ -309,6 +311,31 @@ impl Analyzer {
}
}
Statement::Assign(lvalue, _, expr, span) => {
// Check if trying to assign to a constant
match lvalue {
LValue::Var(name) => {
if let Some(sym) = self.symbols.get(name) {
if sym.is_const {
self.diagnostics.push(Diagnostic::error(
ErrorCode::E0203,
format!("cannot assign to constant '{name}'"),
*span,
));
}
}
}
LValue::ArrayIndex(name, _) => {
if let Some(sym) = self.symbols.get(name) {
if sym.is_const {
self.diagnostics.push(Diagnostic::error(
ErrorCode::E0203,
format!("cannot assign to constant '{name}'"),
*span,
));
}
}
}
}
let ltype = self.lvalue_type(lvalue, *span);
if let Some(lt) = ltype {
self.check_expr_type(expr, &lt);
@ -327,10 +354,16 @@ impl Analyzer {
}
Statement::While(cond, body, _) => {
self.check_expr_type(cond, &NesType::Bool);
let was_in_loop = self.in_loop;
self.in_loop = true;
self.check_block(body, state_names);
self.in_loop = was_in_loop;
}
Statement::Loop(body, _) => {
let was_in_loop = self.in_loop;
self.in_loop = true;
self.check_block(body, state_names);
self.in_loop = was_in_loop;
}
Statement::Transition(name, span) => {
if !state_names.contains(&name.as_str()) {
@ -365,9 +398,25 @@ impl Analyzer {
self.check_expr_type(x, &NesType::U8);
self.check_expr_type(y, &NesType::U8);
}
Statement::Break(_)
| Statement::Continue(_)
| Statement::WaitFrame(_)
Statement::Break(span) => {
if !self.in_loop {
self.diagnostics.push(Diagnostic::error(
ErrorCode::E0203,
"break outside of loop",
*span,
));
}
}
Statement::Continue(span) => {
if !self.in_loop {
self.diagnostics.push(Diagnostic::error(
ErrorCode::E0203,
"continue outside of loop",
*span,
));
}
}
Statement::WaitFrame(_)
| Statement::Return(None, _)
| Statement::LoadBackground(_, _)
| Statement::SetPalette(_, _) => {}

View file

@ -223,3 +223,34 @@ fn analyze_undefined_function() {
);
assert!(errors.contains(&ErrorCode::E0503));
}
#[test]
fn analyze_const_assignment_error() {
let errors = analyze_errors(
r#"
game "Test" { mapper: NROM }
const SPEED: u8 = 2
on frame { SPEED = 5 }
start Main
"#,
);
assert!(
errors.contains(&ErrorCode::E0203),
"assigning to const should produce E0203, got: {errors:?}"
);
}
#[test]
fn analyze_break_outside_loop() {
let errors = analyze_errors(
r#"
game "Test" { mapper: NROM }
on frame { break }
start Main
"#,
);
assert!(
errors.contains(&ErrorCode::E0203),
"break outside loop should produce E0203, got: {errors:?}"
);
}