1
0
Fork 0
mirror of https://github.com/imjasonh/nescript synced 2026-07-08 17:06:04 +00:00

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
This commit is contained in:
Claude 2026-04-12 00:22:11 +00:00
parent 058f7a87b6
commit 5434dda114
No known key found for this signature in database
15 changed files with 865 additions and 13 deletions

View file

@ -114,6 +114,7 @@ impl Parser {
let mut sprites = Vec::new();
let mut palettes = Vec::new();
let mut backgrounds = Vec::new();
let mut banks = Vec::new();
let mut start_state = None;
let mut on_frame = None;
let span = self.current_span();
@ -147,6 +148,9 @@ impl Parser {
TokenKind::KwBackground => {
backgrounds.push(self.parse_background_decl()?);
}
TokenKind::KwBank => {
banks.push(self.parse_bank_decl()?);
}
TokenKind::KwOn => {
// Top-level `on frame` — implicit single state for M1
on_frame = Some(self.parse_on_frame()?);
@ -202,6 +206,7 @@ impl Parser {
sprites,
palettes,
backgrounds,
banks,
start_state,
span,
})
@ -237,13 +242,16 @@ impl Parser {
let (val, _) = self.expect_ident()?;
mapper = match val.as_str() {
"NROM" => Mapper::NROM,
"MMC1" => Mapper::MMC1,
"UxROM" => Mapper::UxROM,
"MMC3" => Mapper::MMC3,
_ => {
return Err(Diagnostic::error(
ErrorCode::E0201,
format!("unknown mapper '{val}'"),
self.current_span(),
)
.with_help("supported mappers for M1: NROM"));
.with_help("supported mappers: NROM, MMC1, UxROM, MMC3"));
}
};
}
@ -450,6 +458,32 @@ impl Parser {
})
}
// ── Bank declaration ──
fn parse_bank_decl(&mut self) -> Result<BankDecl, Diagnostic> {
let start = self.current_span();
self.expect(&TokenKind::KwBank)?;
let (name, _) = self.expect_ident()?;
self.expect(&TokenKind::Colon)?;
let (type_str, _) = self.expect_ident()?;
let bank_type = match type_str.as_str() {
"prg" => BankType::Prg,
"chr" => BankType::Chr,
_ => {
return Err(Diagnostic::error(
ErrorCode::E0201,
format!("expected 'prg' or 'chr', found '{type_str}'"),
self.current_span(),
));
}
};
Ok(BankDecl {
name,
bank_type,
span: Span::new(start.file_id, start.start, self.current_span().start),
})
}
// ── Top-level on frame ──
fn parse_on_frame(&mut self) -> Result<Block, Diagnostic> {
@ -736,6 +770,16 @@ impl Parser {
let (name, _) = self.expect_ident()?;
Ok(Statement::SetPalette(name, span))
}
TokenKind::KwScroll => {
let span = self.current_span();
self.advance();
self.expect(&TokenKind::LParen)?;
let x = self.parse_expr()?;
self.expect(&TokenKind::Comma)?;
let y = self.parse_expr()?;
self.expect(&TokenKind::RParen)?;
Ok(Statement::Scroll(x, y, span))
}
TokenKind::Ident(_) => self.parse_assign_or_call(),
_ => Err(Diagnostic::error(
ErrorCode::E0201,
@ -1146,27 +1190,38 @@ impl Parser {
}
fn parse_unary(&mut self) -> Result<Expr, Diagnostic> {
match self.peek().clone() {
let expr = match self.peek().clone() {
TokenKind::Minus => {
let span = self.current_span();
self.advance();
let expr = self.parse_unary()?;
Ok(Expr::UnaryOp(UnaryOp::Negate, Box::new(expr), span))
Expr::UnaryOp(UnaryOp::Negate, Box::new(expr), span)
}
TokenKind::KwNot => {
let span = self.current_span();
self.advance();
let expr = self.parse_unary()?;
Ok(Expr::UnaryOp(UnaryOp::Not, Box::new(expr), span))
Expr::UnaryOp(UnaryOp::Not, Box::new(expr), span)
}
TokenKind::Tilde => {
let span = self.current_span();
self.advance();
let expr = self.parse_unary()?;
Ok(Expr::UnaryOp(UnaryOp::BitNot, Box::new(expr), span))
Expr::UnaryOp(UnaryOp::BitNot, Box::new(expr), span)
}
_ => self.parse_primary(),
_ => self.parse_primary()?,
};
self.parse_cast_suffix(expr)
}
fn parse_cast_suffix(&mut self, mut expr: Expr) -> Result<Expr, Diagnostic> {
while *self.peek() == TokenKind::KwAs {
let span = self.current_span();
self.advance();
let target_type = self.parse_type()?;
expr = Expr::Cast(Box::new(expr), target_type, span);
}
Ok(expr)
}
fn parse_primary(&mut self) -> Result<Expr, Diagnostic> {