From 4fa49e2181fcce3c213b64e8df09185ca49ac959 Mon Sep 17 00:00:00 2001 From: Breval Ferrari Date: Tue, 20 May 2025 16:45:30 +0200 Subject: [PATCH] first four parsers --- src/cli/main.rs | 79 +++++++++---------- src/compiler.rs | 54 +++++++++++-- src/lib.rs | 2 + src/parser.rs | 203 +++++++++++++++++++++++++++++++++++++++++------- 4 files changed, 263 insertions(+), 75 deletions(-) diff --git a/src/cli/main.rs b/src/cli/main.rs index 22a619b..2c48f6e 100644 --- a/src/cli/main.rs +++ b/src/cli/main.rs @@ -1,3 +1,5 @@ +#![allow(dead_code)] + use std::{ error::Error, fmt::Debug, @@ -7,32 +9,32 @@ use std::{ str::FromStr, }; -use anyhow::{Context, anyhow}; -use bliplib::parser::TokenParser; -use clap::{Parser, builder::EnumValueParser}; -use derive_new::new; +use anyhow::anyhow; +use bliplib::compiler::Expression; +use clap::Parser; use derive_wrapper::AsRef; -use fasteval::{Compiler, Instruction, Slab}; use hound::SampleFormat; use mp3lame_encoder::{Bitrate, Quality}; use nom::{ - AsBytes, Compare, Finish, Input, Offset, Parser as _, + Finish, Parser as P, branch::alt, bytes::complete::tag, character::complete::{char, u16, usize}, - combinator::{opt, rest, success, value}, - error::{ErrorKind, ParseError}, + combinator::{rest, success, value}, + error::ErrorKind, sequence::preceded, }; use nom_locate::{LocatedSpan, position}; -use strum::{Display, EnumDiscriminants, EnumString, IntoDiscriminant, IntoStaticStr, ToString}; +use strum::{EnumDiscriminants, EnumString, IntoDiscriminant, IntoStaticStr}; use thiserror::Error; const DEFAULT_INSTRUMENT: &str = "sin(2*PI*(442+442*((n+1)/N))*t)"; const DEFAULT_LENGTH: &str = "2^(2-log_2(l))*(60/T)"; fn main() { - dbg!(Cli::parse()); + let cli = Cli::parse(); + #[cfg(debug_assertions)] + dbg!(cli); } #[derive(Parser)] @@ -188,31 +190,6 @@ where )) } -#[derive(new)] -#[cfg_attr(debug_assertions, derive(Debug))] -struct Expression { - instruction: Instruction, - slab: Slab, -} - -impl Clone for Expression { - fn clone(&self) -> Self { - unimplemented!() - } -} - -impl FromStr for Expression { - type Err = fasteval::Error; - fn from_str(s: &str) -> Result { - let mut slab = Slab::new(); - let instruction = fasteval::Parser::new() - .parse(s, &mut slab.ps)? - .from(&slab.ps) - .compile(&slab.ps, &mut slab.cs); - Ok(Expression::new(instruction, slab)) - } -} - #[derive(Parser, Clone)] #[cfg_attr(debug_assertions, derive(Debug))] #[group(required = false, multiple = false)] @@ -293,8 +270,12 @@ impl Default for AudioFormat { } } -fn audio_format_parser<'a>(input: &'a str) -> Result { - fn mp3<'a>() -> impl TokenParser<&'a str, AudioFormat> { +fn audio_format_parser(input: &str) -> Result { + fn mp3<'a>() -> impl P< + LocatedSpan<&'a str>, + Output = AudioFormat, + Error = nom::error::Error>, + > { preceded( tag::<&'a str, LocatedSpan<&'a str>, nom::error::Error>>( AudioFormatDiscriminants::Mp3.into(), @@ -342,7 +323,11 @@ fn audio_format_parser<'a>(input: &'a str) -> Result }), ) } - fn wav<'a>() -> impl TokenParser<&'a str, AudioFormat> { + fn wav<'a>() -> impl P< + LocatedSpan<&'a str>, + Output = AudioFormat, + Error = nom::error::Error>, + > { preceded( tag::<&'a str, LocatedSpan<&'a str>, nom::error::Error>>( AudioFormatDiscriminants::Wav.into(), @@ -352,7 +337,11 @@ fn audio_format_parser<'a>(input: &'a str) -> Result .map(|(bps, sample_format)| AudioFormat::Wav { bps, sample_format }), ) } - fn flac<'a>() -> impl TokenParser<&'a str, AudioFormat> { + fn flac<'a>() -> impl P< + LocatedSpan<&'a str>, + Output = AudioFormat, + Error = nom::error::Error>, + > { preceded( tag::<&'a str, LocatedSpan<&'a str>, nom::error::Error>>( AudioFormatDiscriminants::Flac.into(), @@ -362,11 +351,15 @@ fn audio_format_parser<'a>(input: &'a str) -> Result .map(|bps| AudioFormat::Flac { bps }), ) } - fn parser<'a>() -> impl TokenParser<&'a str, AudioFormat> { + fn parser<'a>() -> impl P< + LocatedSpan<&'a str>, + Output = AudioFormat, + Error = nom::error::Error>, + > { alt(( - mp3(), - wav(), - flac(), + mp3::<'a>(), + wav::<'a>(), + flac::<'a>(), rest.map_res(|r: LocatedSpan<&'a str>| { Ok::>>(AudioFormat::Raw( RawAudioFormat::try_from(*r) diff --git a/src/compiler.rs b/src/compiler.rs index 7929a26..6b696f9 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -1,42 +1,84 @@ -use std::collections::HashMap; +use std::{collections::HashMap, str::FromStr}; -use fasteval::Instruction; +use derive_new::new; +use fasteval::{Compiler, Instruction, Slab}; pub type TokenVec = Vec>; pub trait Token { - fn apply(&self, context: Context) -> Context; + fn apply(&self, context: Context) -> Context { + todo!() + } } #[cfg_attr(debug_assertions, derive(Default))] #[derive(Clone, Copy)] pub struct Silence; +impl Token for Silence {} + #[cfg_attr(debug_assertions, derive(Default))] #[derive(Clone, Copy)] pub struct Marker; +impl Token for Marker {} + #[cfg_attr(debug_assertions, derive(Default))] #[derive(Clone, Copy)] pub struct Note(pub u8); +impl Token for Note {} + #[cfg_attr(debug_assertions, derive(Default))] -pub struct VariableChange(pub char, pub Instruction); +pub struct VariableChange(pub char, pub Expression); + +impl Token for VariableChange {} #[cfg_attr(debug_assertions, derive(Default))] pub struct Loop(pub usize, pub TokenVec); +impl Token for Loop {} + #[cfg_attr(debug_assertions, derive(Default))] pub struct Tuplet(pub TokenVec); +impl Token for Tuplet {} + #[cfg_attr(debug_assertions, derive(Default))] pub struct Slope(pub VariableChange, pub TokenVec); +impl Token for Slope {} + +#[derive(new)] +#[cfg_attr(debug_assertions, derive(Debug, Default))] +pub struct Expression { + pub(crate) instruction: Instruction, + pub(crate) slab: Slab, +} + +impl Clone for Expression { + fn clone(&self) -> Self { + unimplemented!() + } +} + +impl FromStr for Expression { + type Err = fasteval::Error; + fn from_str(s: &str) -> Result { + let mut slab = Slab::new(); + let instruction = fasteval::Parser::new() + .parse(s, &mut slab.ps)? + .from(&slab.ps) + .compile(&slab.ps, &mut slab.cs); + Ok(Expression::new(instruction, slab)) + } +} + pub struct Context { pub result: Vec, pub variables: HashMap, - pub instrument: Instruction, - pub slopes: HashMap, + pub instrument: Expression, + pub slopes: HashMap, } impl Context { diff --git a/src/lib.rs b/src/lib.rs index b771520..6d854b1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,2 +1,4 @@ +#![allow(dead_code)] + pub mod compiler; pub mod parser; diff --git a/src/parser.rs b/src/parser.rs index 98ac45e..139db70 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,20 +1,23 @@ -use std::collections::HashMap; +use std::{ + collections::{BTreeMap, HashMap}, + fmt::Display, +}; use derive_builder::Builder; -use nom::{Compare, Input, combinator::success, error::Error}; +use fasteval::Evaler; +use nom::{ + AsBytes, AsChar, Compare, FindToken, Finish, IResult, Input, Offset, Parser as NomParser, + branch::alt, + bytes::complete::{tag, take}, + character::{complete::char, streaming::one_of}, + combinator::value, + error::{Error, ErrorKind}, + multi::many0, + sequence::preceded, +}; use nom_locate::LocatedSpan; -use crate::compiler::{Marker, Note, Silence, Token, VariableChange}; - -pub trait TokenParser: - nom::Parser, Output = O, Error = Error>> -{ -} - -impl TokenParser for T where - T: nom::Parser, Output = O, Error = Error>> -{ -} +use crate::compiler::{Expression, Marker, Note, Silence, Token, VariableChange}; pub struct ParserParametters<'n, 's, 'v, I: Input + Clone + Compare, C: Into> { pub notes: &'n [I], @@ -23,33 +26,181 @@ pub struct ParserParametters<'n, 's, 'v, I: Input + Clone + Compare, C: Into< } #[derive(Builder)] -pub struct Parser<'i, 'n, 's, 'v, I: Input + Clone + Compare, C: Into> { - input: &'i I, - notes: &'n [I], - slopes: &'s HashMap, +pub struct Parser<'i, 'n, 's, 'v, C: Into> { + input: &'i str, + notes: &'n [String], + slopes: &'s HashMap, variables: &'v [C], } -impl<'i, 'n, 's, 'v, I: Input + Clone + Compare, C: Into> Parser<'i, 'n, 's, 'v, I, C> { - pub fn parse_all() -> Result>, Error>> { - todo!() +impl<'i, 'n, 's, 'v, C> Parser<'i, 'n, 's, 'v, C> +where + C: Into + Clone + Ord + Display, + &'v [C]: FindToken, +{ + pub fn parse_all( + &self, + ) -> Result>, Error>> { + many0(alt(( + Silence::parser().map(into_box), + Marker::parser().map(into_box), + Note::parser(self.notes).map(into_box), + VariableChange::parser::, C>(self.variables).map(into_box), + ))) + .parse_complete(LocatedSpan::new(self.input)) + .finish() + .map(|(_, o)| o) } } +fn into_box(token: impl Token + 'static) -> Box { + Box::new(token) +} + +pub type TokenResult<'a, T> = IResult, T>; + impl Silence { - fn parser() -> impl TokenParser { - success(Default::default()) + fn parser<'a>() -> impl NomParser< + LocatedSpan<&'a str>, + Output = Self, + Error = nom::error::Error>, + > { + value(Self, char('.')) } } impl Marker { - fn parser() -> impl TokenParser { - success(Default::default()) + fn parser<'a>() -> impl NomParser< + LocatedSpan<&'a str>, + Output = Self, + Error = nom::error::Error>, + > { + value(Marker, char('%')) } } impl Note { - fn parser() -> impl TokenParser { - success(Default::default()) + fn parser<'a, 'n>( + notes: &'n [String], + ) -> impl NomParser< + LocatedSpan<&'a str>, + Output = Self, + Error = nom::error::Error>, + > { + move |input: LocatedSpan<&'a str>| { + let mut parsers: Vec) -> TokenResult<'a, Self>>> = + notes + .iter() + .enumerate() + .map(|(i, t)| { + Box::new(move |input: LocatedSpan<&'a str>| { + value(Note(i.clone() as u8), tag(t.clone().as_str())).parse(input) + }) + as Box) -> TokenResult<'a, Self>> + }) + .collect(); + alt(parsers.as_mut_slice()).parse(input) + } + } +} + +impl VariableChange { + fn parser<'c, I: Input + AsBytes + Offset, C: Into + Clone + Ord + Display>( + variables: &'c [C], + ) -> impl for<'a> Fn(LocatedSpan<&'a str>) -> TokenResult<'a, Self> + where + ::Item: AsChar, + &'c [C]: FindToken, + { + move |i: LocatedSpan<&str>| { + preceded(char('$'), one_of(variables)) + .and(expression_parser(variables)) + .map(|(name, change)| VariableChange(name, change)) + .parse(i) + } + } +} + +/// Will return the longest valid fasteval expression +fn expression_parser<'c, C: Into + Ord + Display>( + variables: &'c [C], +) -> impl Fn(LocatedSpan<&str>) -> TokenResult { + move |input: LocatedSpan<&str>| { + let mut end_index = 0; + let mut current_expression = None; + while input.input_len() > end_index { + if let Some(e) = (&input[..end_index + 1]) + .parse::() + .ok() + .and_then(|e| { + e.instruction + .eval( + &e.slab, + &mut BTreeMap::from_iter( + variables.into_iter().map(|v| (v.to_string(), 0.0)), + ), + ) + .ok() + .is_some() + .then_some(e) + }) + { + current_expression = Some(e); + end_index += 1; + } else { + if let Some(e) = current_expression { + return take(end_index).parse(input).map(move |(r, _)| (r, e)); + } else { + return Err(nom::Err::Failure(nom::error::Error::new( + input, + ErrorKind::Satisfy, + ))); + } + } + } + if let Some(e) = current_expression { + Ok::<(LocatedSpan<&str>, Expression), nom::Err>>>(( + input, e, + )) + } else { + Err(nom::Err::Incomplete(nom::Needed::Unknown)) + } + } +} + +#[cfg(test)] +mod tests { + use nom_locate::LocatedSpan; + + use crate::parser::expression_parser; + + #[test] + fn expression_parser_test() { + let parser = expression_parser::(&['x']); + let mut working_test_cases = vec![ + "1", + "1x", + "56coucou", // should stop after 56 because c is not a known variable + "8x + 1 heille salut ça va ou quoi 46 - 5x", + ]; + let mut not_working_test_cases = vec![ + "", + "(", + "y", + "abcdexx489", + " ", // spaces are not expressions + " h", // because of previous, this fails + " 1", // this too but the parser should remain dumb and not expect spaces before / after + ]; + + for test in working_test_cases.drain(..) { + let output = parser(LocatedSpan::new(test)); + assert!(matches!(output, Ok(_)), "result was not Ok: {output:?}"); + } + + for test in not_working_test_cases.drain(..) { + let output = parser(LocatedSpan::new(test)); + assert!(matches!(output, Err(_)), "result was not Err: {output:?}"); + } } }