From b0444e13d450306b00fc982f9c564e1481e9ffad Mon Sep 17 00:00:00 2001 From: brevalferrari Date: Mon, 9 Jun 2025 19:08:30 +0200 Subject: [PATCH] complete doc --- Cargo.toml | 2 +- src/compiler.rs | 44 ++++++++++++++++++++++++++++++++++++++++++++ src/parser.rs | 21 +++++++++++++++++++++ 3 files changed, 66 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 42e60cb..2ae9e8e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bliplib" -version = "0.2.1" +version = "0.2.2" edition = "2024" authors = ["Breval Ferrari "] description = "The Bizarre Language for Intermodulation Programming (BLIP)" diff --git a/src/compiler.rs b/src/compiler.rs index c70545d..7355e0e 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -15,12 +15,15 @@ use thiserror::Error; cfg_if! { if #[cfg(test)] { + /// Static sample rate. pub const SAMPLE_RATE: u16 = 10; } else { + /// Static sample rate. pub const SAMPLE_RATE: u16 = 48000; } } +/// A wrapper for a Vec of tokens. #[derive(Debug, From, AsRef, Default)] pub struct TokenVec(pub(crate) Vec>); @@ -44,7 +47,9 @@ impl Type for Box { } } +/// Used for getting the `type_id` of a type without it being [`std::any::Any`]. pub trait Type { + /// Get the type ID of self without self being [`std::any::Any`]. fn type_id(&self) -> TypeId; } @@ -57,7 +62,9 @@ where } } +/// BLIP tokens implement this. pub trait Token: Debug + Type { + /// Applies itself to a context for compilation into samples. fn apply(&self, context: Context) -> Result; } @@ -68,6 +75,7 @@ impl PartialEq for TokenVec { } } +/// Litteral silence. No sound for the duration of a note. #[derive(Debug, Clone, Copy, Default)] #[cfg_attr(test, derive(PartialEq))] pub struct Silence; @@ -81,6 +89,7 @@ impl Token for Silence { } } +/// Used to indicate where the playback or export should start from in the sheet music. #[derive(Debug, Clone, Copy, Default)] #[cfg_attr(test, derive(PartialEq))] pub struct Marker; @@ -93,6 +102,7 @@ impl Token for Marker { } } +/// A note. The underlying `u8` is for its position in the provided list of notes to be parsed. #[derive(Debug, Clone, Copy, Default)] #[cfg_attr(test, derive(PartialEq))] pub struct Note(pub u8); @@ -106,6 +116,7 @@ impl Token for Note { } } +/// Describes the instant mutation of a variable by the result of the provided [`Expression`] using the current values of all available variables. #[derive(Debug, Clone, Default)] #[cfg_attr(test, derive(PartialEq))] pub struct VariableChange(pub char, pub Expression); @@ -124,14 +135,18 @@ impl Token for VariableChange { } } +/// A wrapper for other tokens which repeat those tokens N times using the number described by [`LoopCount`]. #[derive(Debug, Default)] #[cfg_attr(test, derive(PartialEq))] pub struct Loop(pub LoopCount, pub TokenVec); +/// Describes the number of times a loop should repeat its captured sequence of tokens. #[derive(Debug)] #[cfg_attr(test, derive(PartialEq))] pub enum LoopCount { + /// Fixed number of times Litteral(usize), + /// Variable number of times, using the current value of the underlying variable Variable(char), } @@ -163,6 +178,7 @@ impl Token for Loop { } } +/// The duration of the underlying tokens in a tuplet will fit in the current calculated duration of a single note. The length of all these tokens will be shrinked equally. #[derive(Debug, Default)] #[cfg_attr(test, derive(PartialEq))] pub struct Tuplet(pub TokenVec); @@ -204,6 +220,7 @@ impl Token for Tuplet { } } +/// A slope which describes a smooth change of a variable which will affect all captured tokens. Variable mutation occurs every frame / sample. #[derive(Debug, new, Default)] #[cfg_attr(test, derive(PartialEq))] pub struct Slope(pub VariableChange, pub TokenVec); @@ -225,10 +242,14 @@ impl Token for Slope { } } +/// A mathematical expression to be evaluated with a set of variables to form a single [`f64`] value. #[derive(Debug, new, Default)] pub struct Expression { + /// Expression origin, used for cloning. from: String, + /// The wrapped fasteval struct. pub(crate) instruction: Instruction, + /// The slab which contains all the information referenced by the [`Instruction`]. An Instruction without a Slab is usually unusable. pub(crate) slab: Slab, } @@ -268,39 +289,51 @@ impl FromStr for Expression { } } +/// Compiler context which persists from one token to the other. #[derive(Debug, Clone, new)] #[cfg_attr(test, derive(PartialEq))] pub struct Context { note_length_variable: String, note_index_variable: String, + /// Vec of samples resulting from compilation. #[new(default)] pub result: Vec, + /// Set of variables used for every [`Expression`] evaluation. #[new(into_iter = "(String, f64)")] pub variables: BTreeMap, + /// Instrument Expression which will be used to directly generate samples from the tokens. pub instrument: Expression, + /// Set of [`Slope`]s to be applied to their corresponding variable at every frame. The string is the variable name. #[new(into_iter = "(String, Expression)")] pub slopes: Vec<(String, Expression)>, } +/// A variable which is expected to be present in the [`Context`]'s set of variables could not be found. #[derive(Debug, Error)] #[cfg_attr(test, derive(PartialEq))] #[error("variable not found: {0}")] pub struct VariableNotFoundError(String); +/// A slope which is expected to be present in the [`Context`]'s set of slopes could not be found. #[derive(Debug, Error)] #[cfg_attr(test, derive(PartialEq))] #[error("expression not found: {0}")] pub struct SlopeNotFoundError(String); +/// An error occurred during the compilation of tokens into samples. #[derive(Debug, Error)] #[cfg_attr(test, derive(PartialEq))] pub enum CompilerError { + /// The error comes from a `fasteval` operation. #[error("expression evaluation: {0:?}")] FastEval(#[from] fasteval::Error), + /// See [`VariableNotFoundError`]. #[error(transparent)] VariableNotFound(#[from] VariableNotFoundError), + /// See [`SlopeNotFoundError`]. #[error(transparent)] SlopeNotFound(#[from] SlopeNotFoundError), + /// A tuplet was found which carried no underlying tokens. #[error( "🎉 You successfully made a nilplet / noplet (and I don't know what to do with it)\nTo resume compilation, remove any occurrence of \"[]\" in your sheet music." )] @@ -308,6 +341,7 @@ pub enum CompilerError { } impl Context { + /// Calculates the current note length according to the note length expression and the current values of registered variables. pub fn current_length(self) -> Result<(Self, f64), CompilerError> { let Context { note_length_variable, @@ -346,12 +380,14 @@ impl Context { )) } + /// Get a variable from the set. pub fn get(&self, name: impl AsRef + Into) -> Result<&f64, VariableNotFoundError> { self.variables .get(name.as_ref()) .ok_or(VariableNotFoundError(name.into())) } + /// Get a variable from the set with a mutable reference. pub fn get_mut( &mut self, name: impl AsRef + Into, @@ -361,6 +397,7 @@ impl Context { .ok_or(VariableNotFoundError(name.into())) } + /// Get a slope from the set using the name of the variable it's supposed to alter the value of. pub fn get_slopes_for( &self, var: impl for<'a> PartialEq<&'a String> + Into, @@ -403,10 +440,12 @@ impl Context { }) } + /// Evaluate the result of an Expression using the current values of the variables registered in the set. pub fn eval(&mut self, expr: &Expression) -> Result { Self::eval_with(expr, &mut self.variables) } + /// Evaluate the result of an Expression using the current values of the variables registered in the provided namespace. pub fn eval_with( Expression { from, @@ -421,6 +460,7 @@ impl Context { .inspect_err(|e| trace!("{from} = {e}")) } + /// Render a note (by providing the index of the note in the note list as `n`) or a silence (with None for `n`) into samples and return them in a Vec. pub fn render(mut self, n: Option) -> Result<(Self, Vec), CompilerError> { let curr_t = *self.get("t")?; if let Some(note) = n { @@ -451,16 +491,19 @@ impl Context { } } + /// Explodes the context, leaving only the resulting Vec of samples. pub fn finalize(self) -> Vec { self.result } } +/// Convenience struct for the whole compilation phase. #[derive(From)] #[cfg_attr(test, derive(Debug, PartialEq))] pub struct Compiler(Context); impl Compiler { + /// Applies a single token on the underlying [`Context`]. pub fn step(self, token: impl Token) -> Result { token.apply(self.0).map(Into::into) } @@ -472,6 +515,7 @@ impl Compiler { .into_iter() .try_fold(self, |acc, token| acc.step(token)) } + /// Applies every token on the underlying [`Context`] and returns the resulting samples as an iterator. pub fn compile_all( self, tokens: impl IntoIterator, diff --git a/src/parser.rs b/src/parser.rs index 673474b..c265a7e 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -29,8 +29,22 @@ use crate::compiler::{ VariableChange, }; +/// From `nom`'s [`IResult`](https://docs.rs/nom/8.0.0/nom/type.IResult.html) : +/// > Holds the result of parsing functions +/// > +/// > It depends on the input type `I`, the output type `O`, and the error type `E` +/// > (by default `(I, nom::ErrorKind)`) +/// > +/// > The `Ok` side is a pair containing the remainder of the input (the part of the data that +/// > was not parsed) and the produced value. The `Err` side contains an instance of `nom::Err`. +/// > +/// > Outside of the parsing code, you can use the [Finish::finish] method to convert +/// > it to a more common result type +/// +/// The error type in this IResult is [`LocatedVerboseError`]. pub type IResult> = nom::IResult; +/// A parser wrapper for `nom` 8 which adds a context to the error. #[derive(new)] pub struct VerboseParser>, I> { parser: P, @@ -83,12 +97,16 @@ impl>> nom::Parser for Ve } } +/// An error type for `nom` 8 which adds a context to parser errors using `anyhow::Error`. #[derive(Debug)] pub struct LocatedVerboseError { + /// Error location (I is the input type) pub location: I, + /// Error description / context pub error: Option, } +/// Expect the parser to succeed and if it doesn't, add a context to the error. pub fn expect( parser: P, error_message: impl Into>, @@ -108,6 +126,7 @@ impl FromExternalError for LocatedVerboseError { } } +/// Convenience struct for the whole parsing phase. Very generic. #[derive(new)] pub struct Parser where @@ -134,6 +153,7 @@ where V: AsRef<[char]>, 'p: 'a, { + /// Parse all the input into a Vec of tokens. Returns a descriptive error on failure. pub fn parse_all( &'p self, input: &'a str, @@ -492,6 +512,7 @@ where } } +/// Take the maximum amount of input before `cond` returns [`None`], using the result of the last successful evaluation of `cond` for the output. pub fn take_while_map(cond: F) -> impl FnMut(I) -> IResult> where I: Input + Copy,