complete doc

This commit is contained in:
brevalferrari 2025-06-09 19:08:30 +02:00
parent 4f39e98e8b
commit b0444e13d4
3 changed files with 66 additions and 1 deletions

View file

@ -1,6 +1,6 @@
[package]
name = "bliplib"
version = "0.2.1"
version = "0.2.2"
edition = "2024"
authors = ["Breval Ferrari <breval.ferrari@fish.golf>"]
description = "The Bizarre Language for Intermodulation Programming (BLIP)"

View file

@ -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<Box<dyn Token>>);
@ -44,7 +47,9 @@ impl Type for Box<dyn Token> {
}
}
/// 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<Context, CompilerError>;
}
@ -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<f64>,
/// Set of variables used for every [`Expression`] evaluation.
#[new(into_iter = "(String, f64)")]
pub variables: BTreeMap<String, f64>,
/// 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<str> + Into<String>) -> 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<str> + Into<String>,
@ -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<String>,
@ -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<f64, fasteval::Error> {
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<u8>) -> Result<(Self, Vec<f64>), 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<f64> {
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<Self, CompilerError> {
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<Item = impl Token>,

View file

@ -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<I, O, E = LocatedVerboseError<I>> = nom::IResult<I, O, E>;
/// A parser wrapper for `nom` 8 which adds a context to the error.
#[derive(new)]
pub struct VerboseParser<P: nom::Parser<I, Error = LocatedVerboseError<I>>, I> {
parser: P,
@ -83,12 +97,16 @@ impl<I, P: nom::Parser<I, Error = LocatedVerboseError<I>>> nom::Parser<I> for Ve
}
}
/// An error type for `nom` 8 which adds a context to parser errors using `anyhow::Error`.
#[derive(Debug)]
pub struct LocatedVerboseError<I> {
/// Error location (I is the input type)
pub location: I,
/// Error description / context
pub error: Option<anyhow::Error>,
}
/// Expect the parser to succeed and if it doesn't, add a context to the error.
pub fn expect<P, I>(
parser: P,
error_message: impl Into<Cow<'static, str>>,
@ -108,6 +126,7 @@ impl<I> FromExternalError<I, anyhow::Error> for LocatedVerboseError<I> {
}
}
/// Convenience struct for the whole parsing phase. Very generic.
#[derive(new)]
pub struct Parser<N, NS, S, SS, SV, V>
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<F, I, O>(cond: F) -> impl FnMut(I) -> IResult<I, O, LocatedVerboseError<I>>
where
I: Input + Copy,