diff options
| author | Freya Murphy <freya@freyacat.org> | 2025-11-22 13:29:10 -0500 |
|---|---|---|
| committer | Freya Murphy <freya@freyacat.org> | 2025-11-22 13:29:10 -0500 |
| commit | 2274d33e469aca544a7aeb899a10769b973ae374 (patch) | |
| tree | 7ff10964234fd5e8917624508e249265a3d0b19c /graphics/src/audio/parse | |
| parent | audio: move 'asm' files out of data segment (diff) | |
| download | DungeonCrawl-2274d33e469aca544a7aeb899a10769b973ae374.tar.gz DungeonCrawl-2274d33e469aca544a7aeb899a10769b973ae374.tar.bz2 DungeonCrawl-2274d33e469aca544a7aeb899a10769b973ae374.zip | |
audio: refactor into seperate crate
Diffstat (limited to 'graphics/src/audio/parse')
| -rw-r--r-- | graphics/src/audio/parse/lex.rs | 218 | ||||
| -rw-r--r-- | graphics/src/audio/parse/macros.rs | 103 | ||||
| -rw-r--r-- | graphics/src/audio/parse/mod.rs | 12 | ||||
| -rw-r--r-- | graphics/src/audio/parse/parser.rs | 104 |
4 files changed, 0 insertions, 437 deletions
diff --git a/graphics/src/audio/parse/lex.rs b/graphics/src/audio/parse/lex.rs deleted file mode 100644 index 9969c9e..0000000 --- a/graphics/src/audio/parse/lex.rs +++ /dev/null @@ -1,218 +0,0 @@ -use std::{ - fmt::Display, - iter::Peekable, - str::{Chars, FromStr}, -}; - -use super::Result; -use crate::audio::{channel::DutyCycle, program::ChanSpec}; - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum Token { - Eof, - LineSeparator, - Pause(usize), - PauseLen(u32), - Jump(usize), - ChanSpec(ChanSpec), - SetVolume(u8), - SetPitch(u8), - SetNoiseMode(bool), - SetPulseDuty(DutyCycle), -} -impl Token { - pub const fn is_eol(self) -> bool { - matches!(self, Self::Eof | Self::LineSeparator) - } -} - -pub struct Lexer<'s> { - src: &'s str, - chars: Peekable<Chars<'s>>, - pos: usize, -} -impl<'s> Lexer<'s> { - pub fn new(src: &'s str) -> Self { - let chars = src.chars().peekable(); - Self { src, chars, pos: 0 } - } - - fn filter_char(&mut self, ch: Option<char>, advance: bool) -> Result<char> { - match ch { - Some(c) if c.is_control() && !matches!(c, '\n' | '\r' | '\t') => { - Err(invalid_char(c)) - } - Some(c) => { - if advance { - self.pos += c.len_utf8(); - } - Ok(c) - } - None => Ok('\0'), - } - } - - fn peek(&mut self) -> Result<char> { - let c = self.chars.peek().copied(); - self.filter_char(c, false) - } - - fn next(&mut self) -> Result<char> { - let c = self.chars.next(); - self.filter_char(c, true) - } - - fn next_int<T>(&mut self) -> Result<T> - where - T: FromStr, - <T as FromStr>::Err: Display, - { - let start = self.pos; - while self.peek()?.is_ascii_digit() { - self.next()?; - } - let str = &self.src[start..self.pos]; - str.parse::<T>().map_err(|e| e.to_string()) - } - - fn next_note(&mut self) -> Result<u8> { - if self.peek()?.is_ascii_digit() { - return self.next_int(); - } - - let note = match self.next()? { - 'a' => 80, - 'b' => 82, - 'c' => 83, - 'd' => 85, - 'e' => 87, - 'f' => 88, - 'g' => 90, - c => unexpected(c)?, - }; - - let octave = { - let c = self.next()?; - c.to_digit(10).ok_or_else(|| format!("invalid octave: {c}")) - }?; - - let off = match self.peek()? { - '#' => { - self.next()?; - 1 - } - 'b' => { - self.next()?; - -1 - } - _ => 0, - }; - - let pitch_u32 = (note + octave * 12).wrapping_add_signed(off); - let pitch = u8::try_from(pitch_u32).map_err(|e| format!("{e}"))?; - - Ok(pitch) - } - - fn next_bool(&mut self) -> Result<bool> { - match self.next_int()? { - 0 => Ok(false), - _ => Ok(true), - } - } - - fn next_duty_cycle(&mut self) -> Result<DutyCycle> { - match self.next_int()? { - 12 => Ok(DutyCycle::Percent12), - 25 => Ok(DutyCycle::Percent25), - 50 => Ok(DutyCycle::Percent50), - 75 => Ok(DutyCycle::Percent25Neg), - n => Err(format!("invalid duty cycle: {n}")), - } - } - - fn skip_comment(&mut self) -> Result<()> { - loop { - let next = self.next()?; - if next == '\n' || next == '\0' { - break; - } - } - Ok(()) - } - - fn next_pause(&mut self) -> Result<Token> { - let mut count = 1; - if self.peek()?.is_ascii_digit() { - count = self.next_int()?; - } - Ok(Token::Pause(count)) - } - - pub fn next_token(&mut self) -> Result<Token> { - use Token as T; - loop { - let peek = self.peek()?; - if peek == ';' { - self.skip_comment()?; - } else if matches!(peek, ' ' | '\t' | '\r') { - self.next()?; - } else { - break; - } - } - let token = match self.next()? { - // chan spec - 'a' => Token::ChanSpec(ChanSpec::PulseA), - 'b' => Token::ChanSpec(ChanSpec::PulseB), - 't' => Token::ChanSpec(ChanSpec::Triangle), - 'n' => Token::ChanSpec(ChanSpec::Noise), - // volume - 'v' => T::SetVolume(self.next_int()?), - // pitch - 'p' => T::SetPitch(self.next_note()?), - // duty cycle - 'd' => T::SetPulseDuty(self.next_duty_cycle()?), - // noise mode - 'm' => T::SetNoiseMode(self.next_bool()?), - // pause - '-' => self.next_pause()?, - // jump - 'j' => T::Jump(self.next_int()?), - // pause len - 'P' => T::PauseLen(self.next_int()?), - // eof - '\0' => T::Eof, - // new line - '\n' => T::LineSeparator, - // unexpected - c => unexpected(c)?, - }; - Ok(token) - } -} -impl Iterator for Lexer<'_> { - type Item = Result<Token>; - - fn next(&mut self) -> Option<Self::Item> { - Some(self.next_token()) - } -} - -fn invalid_char(ch: char) -> String { - match ch as u32 { - c @ 0x00..=0x7f => format!("invalid character (codepoint 0x{c:2x})"), - c => format!("invalid character (codepoint U+{c:04x})"), - } -} - -fn unexpected<T>(c: char) -> Result<T> { - let msg = match c { - '\0' => "unexpected end of file".to_owned(), - '\n' => "unexpected newline character".to_owned(), - '\t' => "unexpected tab character".to_owned(), - '\r' => "unexpected return character".to_owned(), - c => format!("unexpected character {c}"), - }; - Err(msg) -} diff --git a/graphics/src/audio/parse/macros.rs b/graphics/src/audio/parse/macros.rs deleted file mode 100644 index 1dc33eb..0000000 --- a/graphics/src/audio/parse/macros.rs +++ /dev/null @@ -1,103 +0,0 @@ -use std::{borrow::Cow, collections::HashMap, str::Lines}; - -struct PreProcessor<'s> { - src: &'s str, - pos: usize, - lines: Lines<'s>, - macros: HashMap<&'s str, &'s str>, -} -impl<'s> PreProcessor<'s> { - fn new(src: &'s str) -> Self { - let lines = src.lines(); - let macros = HashMap::new(); - Self { - src, - pos: 0, - lines, - macros, - } - } - - fn next(&mut self) -> Option<&'s str> { - self.lines.next().map(|line| { - self.pos += line.len() + 1; - line.trim() - }) - } - - fn read_macro(&mut self, full_name: &'s str) { - let name = &full_name[8..]; - let start = self.pos; - let mut end = start; - while let Some(line) = self.next() - && !matches!(line, "%end") - { - end = self.pos; - } - let str = &self.src[start..end]; - self.macros.insert(name, str); - } - - fn read_macros(&mut self) -> String { - let mut buf = String::new(); - while let Some(line) = self.next() { - if line.starts_with("%define ") { - self.read_macro(line); - } else { - buf.push_str(line); - buf.push('\n'); - } - } - buf - } - - fn process(&mut self) -> String { - let rest = self.read_macros(); - let mut lines = rest.lines().map(Cow::Borrowed).collect::<Vec<_>>(); - loop { - let mut count = 0; - for (name, body) in &self.macros { - count += fill_macro(&mut lines, name, body); - } - if count == 0 { - break; - } - } - lines.join("\n") - } -} - -fn fill_macro(contents: &mut Vec<Cow<'_, str>>, name: &str, body: &str) -> usize { - let mut count = 0; - let mut idx = 0; - loop { - if idx >= contents.len() { - break; - } - let line = &contents[idx]; - if line.starts_with(name) { - fill_macro_once(contents, idx, body); - count += 1; - } - idx += 1; - } - count -} - -fn fill_macro_once(contents: &mut Vec<Cow<'_, str>>, idx: usize, body: &str) { - let invoke_line = contents.remove(idx); - let args = invoke_line.split_whitespace().skip(1).collect::<Vec<_>>(); - - for line in body.lines().rev() { - let mut buf = String::from(line); - for (idx, arg) in args.iter().enumerate() { - let key = format!("${}", idx + 1); - buf = buf.replace(&key, arg); - } - contents.insert(idx, Cow::Owned(buf)); - } -} - -pub fn process(src: &str) -> String { - PreProcessor::new(src).process() -} diff --git a/graphics/src/audio/parse/mod.rs b/graphics/src/audio/parse/mod.rs deleted file mode 100644 index 9ff243d..0000000 --- a/graphics/src/audio/parse/mod.rs +++ /dev/null @@ -1,12 +0,0 @@ -use crate::audio::{parse::parser::Parser, program::Instruction}; - -pub type Result<T> = std::result::Result<T, String>; - -mod lex; -mod macros; -mod parser; - -pub fn parse(raw_src: &str) -> Result<Vec<Instruction>> { - let src = macros::process(raw_src); - Parser::new(&src).parse() -} diff --git a/graphics/src/audio/parse/parser.rs b/graphics/src/audio/parse/parser.rs deleted file mode 100644 index 5157fa1..0000000 --- a/graphics/src/audio/parse/parser.rs +++ /dev/null @@ -1,104 +0,0 @@ -use std::iter::Peekable; - -use crate::audio::program::{ChanSpec, Instruction}; - -use super::{ - Result, - lex::{Lexer, Token}, -}; - -pub struct Parser<'s> { - lexer: Peekable<Lexer<'s>>, -} -impl<'s> Parser<'s> { - pub fn new(src: &'s str) -> Self { - Self { - lexer: Lexer::new(src).peekable(), - } - } - - fn next(&mut self) -> Result<Token> { - self.lexer - .next() - .unwrap_or_else(|| Err("should not happen".to_owned())) - } - - fn peek(&mut self) -> Result<Token> { - self.lexer - .peek() - .map_or_else(|| Err("should not happen".to_owned()), Result::clone) - } - - fn parse_chan_spec(&mut self) -> Result<ChanSpec> { - match self.next()? { - Token::ChanSpec(spec) => Ok(spec), - t => Err(format!("expected channel specifier, got {t:?}")), - } - } - - fn parse_ins(&mut self, spec: ChanSpec) -> Result<Instruction> { - use Token as T; - let t = self.next()?; - let ins = match t { - T::SetPitch(pitch) => Instruction::SetPitch(spec, pitch), - T::SetVolume(volume) => Instruction::SetVolume(spec, volume), - T::SetNoiseMode(mode) if spec == ChanSpec::Noise => { - Instruction::SetNoiseMode(mode) - } - T::SetPulseDuty(duty_cycle) if spec == ChanSpec::PulseA => { - Instruction::SetPulseDutyA(duty_cycle) - } - T::SetPulseDuty(duty_cycle) if spec == ChanSpec::PulseB => { - Instruction::SetPulseDutyB(duty_cycle) - } - _ => unexpected(t)?, - }; - Ok(ins) - } - - fn parse_line(&mut self, prog: &mut Vec<Instruction>) -> Result<()> { - let spec = self.parse_chan_spec()?; - loop { - prog.push(self.parse_ins(spec)?); - let peek = self.peek()?; - if peek.is_eol() || matches!(peek, Token::Pause(_)) { - break; - } - } - Ok(()) - } - - pub fn parse(&mut self) -> Result<Vec<Instruction>> { - let mut prog = vec![]; - loop { - let t = self.peek()?; - match t { - Token::Eof => break, - Token::LineSeparator => { - self.next()?; - } - Token::Pause(count) => { - self.next()?; - for _ in 0..count { - prog.push(Instruction::Pause); - } - } - Token::Jump(pc) => { - self.next()?; - prog.push(Instruction::Jump(pc)); - } - Token::PauseLen(pause_len) => { - self.next()?; - prog.push(Instruction::PauseLen(pause_len)); - } - _ => self.parse_line(&mut prog)?, - } - } - Ok(prog) - } -} - -fn unexpected<T>(t: Token) -> Result<T> { - let msg = format!("unexpected token: {t:?}"); - Err(msg) -} |