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:
parent
3b1a03981b
commit
40632f192c
6 changed files with 466 additions and 31 deletions
|
|
@ -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, <);
|
||||
|
|
@ -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(_, _) => {}
|
||||
|
|
|
|||
|
|
@ -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:?}"
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue