From e16b765959579e63c6a328b93ab278c5eb42c0a8 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 12 Apr 2026 16:38:59 +0000 Subject: [PATCH] Parser: audio statements (play / start_music / stop_music) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The \`play\`, \`start_music\`, and \`stop_music\` keywords were lexed but not parsed. Programs using them failed with a generic "unexpected token" error. Now: - \`Statement::Play(sfx_name, span)\` - \`Statement::StartMusic(track_name, span)\` - \`Statement::StopMusic(span)\` parse successfully and flow through analyzer / IR lowering / codegen as no-ops. Semantics match the spec but produce no output — no audio driver exists yet. Users who need sound can still wire in custom code via \`asm { ... }\` blocks. https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3 --- src/analyzer/mod.rs | 10 +++++++++- src/codegen/mod.rs | 3 +++ src/ir/lowering.rs | 4 ++++ src/parser/ast.rs | 14 +++++++++++++- src/parser/mod.rs | 17 +++++++++++++++++ src/parser/tests.rs | 21 +++++++++++++++++++++ 6 files changed, 67 insertions(+), 2 deletions(-) diff --git a/src/analyzer/mod.rs b/src/analyzer/mod.rs index 337af9b..989e499 100644 --- a/src/analyzer/mod.rs +++ b/src/analyzer/mod.rs @@ -974,6 +974,11 @@ impl Analyzer { // codegen parses and validates the body; analysis has // nothing to check. } + Statement::Play(_, _) | Statement::StartMusic(_, _) | Statement::StopMusic(_) => { + // Audio statements are parsed and recognized but + // currently generate no code — no audio driver exists. + // Users who need audio can use inline `asm` blocks. + } } } @@ -1219,7 +1224,10 @@ fn collect_calls_stmt(stmt: &Statement, calls: &mut Vec) { | Statement::Continue(_) | Statement::LoadBackground(_, _) | Statement::SetPalette(_, _) - | Statement::InlineAsm(_, _) => {} + | Statement::InlineAsm(_, _) + | Statement::Play(_, _) + | Statement::StartMusic(_, _) + | Statement::StopMusic(_) => {} } } diff --git a/src/codegen/mod.rs b/src/codegen/mod.rs index d3e042b..0c690c1 100644 --- a/src/codegen/mod.rs +++ b/src/codegen/mod.rs @@ -370,6 +370,9 @@ impl CodeGen { self.emit(BRK, AM::Implied); } }, + Statement::Play(_, _) | Statement::StartMusic(_, _) | Statement::StopMusic(_) => { + // Audio statements compile to no-ops for now. + } } } diff --git a/src/ir/lowering.rs b/src/ir/lowering.rs index 2fb39b7..cae844b 100644 --- a/src/ir/lowering.rs +++ b/src/ir/lowering.rs @@ -344,6 +344,10 @@ impl LoweringContext { Statement::InlineAsm(body, _) => { self.emit(IrOp::InlineAsm(body.clone())); } + Statement::Play(_, _) | Statement::StartMusic(_, _) | Statement::StopMusic(_) => { + // No audio driver yet — these parse but produce no + // IR. + } } } diff --git a/src/parser/ast.rs b/src/parser/ast.rs index 321a799..32a12ff 100644 --- a/src/parser/ast.rs +++ b/src/parser/ast.rs @@ -286,6 +286,15 @@ pub enum Statement { /// Inline 6502 assembly captured verbatim. The body is parsed by /// the codegen stage using `asm::parse_inline`. InlineAsm(String, Span), + /// Audio: `play SfxName` — trigger a one-shot sound effect. + /// Currently a no-op at codegen time; no audio driver exists. + Play(String, Span), + /// Audio: `start_music TrackName` — begin playing background music. + /// Currently a no-op at codegen time. + StartMusic(String, Span), + /// Audio: `stop_music` — stop any currently-playing music. + /// Currently a no-op at codegen time. + StopMusic(Span), } impl Statement { @@ -308,7 +317,10 @@ impl Statement { | Self::Scroll(_, _, s) | Self::DebugLog(_, s) | Self::DebugAssert(_, s) - | Self::InlineAsm(_, s) => *s, + | Self::InlineAsm(_, s) + | Self::Play(_, s) + | Self::StartMusic(_, s) + | Self::StopMusic(s) => *s, } } } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index d2b99da..baf631b 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -899,6 +899,23 @@ impl Parser { Ok(Statement::Scroll(x, y, span)) } TokenKind::KwDebug => self.parse_debug_statement(), + TokenKind::KwPlay => { + let span = self.current_span(); + self.advance(); + let (name, _) = self.expect_ident()?; + Ok(Statement::Play(name, span)) + } + TokenKind::KwStartMusic => { + let span = self.current_span(); + self.advance(); + let (name, _) = self.expect_ident()?; + Ok(Statement::StartMusic(name, span)) + } + TokenKind::KwStopMusic => { + let span = self.current_span(); + self.advance(); + Ok(Statement::StopMusic(span)) + } TokenKind::KwAsm => { let span = self.current_span(); self.advance(); // KwAsm diff --git a/src/parser/tests.rs b/src/parser/tests.rs index 1ba7eac..2f41578 100644 --- a/src/parser/tests.rs +++ b/src/parser/tests.rs @@ -665,6 +665,27 @@ fn parse_mmc3_mapper() { assert_eq!(prog.game.mapper, Mapper::MMC3); } +#[test] +fn parse_audio_statements() { + let src = r#" + game "Audio" { mapper: NROM } + on frame { + play JumpSfx + start_music MainTheme + stop_music + } + start Main + "#; + let prog = parse_ok(src); + let frame = prog.states[0].on_frame.as_ref().unwrap(); + assert!(matches!(frame.statements[0], Statement::Play(ref n, _) if n == "JumpSfx")); + assert!(matches!( + frame.statements[1], + Statement::StartMusic(ref n, _) if n == "MainTheme" + )); + assert!(matches!(frame.statements[2], Statement::StopMusic(_))); +} + #[test] fn parse_struct_decl() { let src = r#"