From 9a5e2377339a41502286208fb4cc626b88154d14 Mon Sep 17 00:00:00 2001 From: brevalferrari Date: Sat, 7 Jun 2025 20:04:22 +0200 Subject: [PATCH] complete cli memo menu --- Cargo.toml | 1 + doc/examples.txt | 2 ++ doc/fasteval/functions.txt | 30 ++++++++++++++++++ doc/fasteval/literals.txt | 17 ++++++++++ doc/fasteval/operators.txt | 11 +++++++ src/cli/cli.rs | 43 ++++++++++++++++++-------- src/cli/main.rs | 63 ++++++++++++++++++++++++++++++++++++-- 7 files changed, 152 insertions(+), 15 deletions(-) create mode 100644 doc/examples.txt create mode 100644 doc/fasteval/functions.txt create mode 100644 doc/fasteval/literals.txt create mode 100644 doc/fasteval/operators.txt diff --git a/Cargo.toml b/Cargo.toml index b4ff9ae..1da9e96 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,6 +47,7 @@ raw = ["raw_audio"] name = "blip" path = "src/cli/main.rs" required-features = ["bin"] +include = ["doc/language-design", "doc/fasteval/*"] [[example]] name = "blip" diff --git a/doc/examples.txt b/doc/examples.txt new file mode 100644 index 0000000..deeeb99 --- /dev/null +++ b/doc/examples.txt @@ -0,0 +1,2 @@ +sine wave: sin(2*pi()*(442*2^((n+1)/N))*t) +classic BeepComp length: 2^(2-log(2, l))*(60/T) \ No newline at end of file diff --git a/doc/fasteval/functions.txt b/doc/fasteval/functions.txt new file mode 100644 index 0000000..26c10c8 --- /dev/null +++ b/doc/fasteval/functions.txt @@ -0,0 +1,30 @@ + * print(...strings and values...) -- Prints to stderr. Very useful to 'probe' an expression. + Evaluates to the last value. + Example: `print("x is", x, "and y is", y)` + Example: `x + print("y:", y) + z == x+y+z` + + * log(base=10, val) -- Logarithm with optional 'base' as first argument. + If not provided, 'base' defaults to '10'. + Example: `log(100) + log(e(), 100)` + + * e() -- Euler's number (2.718281828459045) + * pi() -- π (3.141592653589793) + + * int(val) + * ceil(val) + * floor(val) + * round(modulus=1, val) -- Round with optional 'modulus' as first argument. + Example: `round(1.23456) == 1 && round(0.001, 1.23456) == 1.235` + + * abs(val) + * sign(val) + + * min(val, ...) -- Example: `min(1, -2, 3, -4) == -4` + * max(val, ...) -- Example: `max(1, -2, 3, -4) == 3` + + * sin(radians) * asin(val) + * cos(radians) * acos(val) + * tan(radians) * atan(val) + * sinh(val) * asinh(val) + * cosh(val) * acosh(val) + * tanh(val) * atanh(val) \ No newline at end of file diff --git a/doc/fasteval/literals.txt b/doc/fasteval/literals.txt new file mode 100644 index 0000000..1cd3b27 --- /dev/null +++ b/doc/fasteval/literals.txt @@ -0,0 +1,17 @@ +Several numeric formats are supported: + + Integers: 1, 2, 10, 100, 1001 + + Decimals: 1.0, 1.23456, 0.000001 + + Exponents: 1e3, 1E3, 1e-3, 1E-3, 1.2345e100 + + Suffix: + 1.23p = 0.00000000000123 + 1.23n = 0.00000000123 + 1.23µ, 1.23u = 0.00000123 + 1.23m = 0.00123 + 1.23K, 1.23k = 1230 + 1.23M = 1230000 + 1.23G = 1230000000 + 1.23T = 1230000000000 \ No newline at end of file diff --git a/doc/fasteval/operators.txt b/doc/fasteval/operators.txt new file mode 100644 index 0000000..ec1787a --- /dev/null +++ b/doc/fasteval/operators.txt @@ -0,0 +1,11 @@ +Listed in order of precedence: + + (Highest Precedence) ^ Exponentiation + % Modulo + / Division + * Multiplication + - Subtraction + + Addition + == != < <= >= > Comparisons (all have equal precedence) + && and Logical AND with short-circuit + (Lowest Precedence) || or Logical OR with short-circuit diff --git a/src/cli/cli.rs b/src/cli/cli.rs index 9cfd4eb..9bda8ee 100644 --- a/src/cli/cli.rs +++ b/src/cli/cli.rs @@ -26,7 +26,7 @@ use nom::{ sequence::preceded, }; use nom_locate::{LocatedSpan, position}; -use strum::{EnumDiscriminants, EnumString, IntoDiscriminant, IntoStaticStr}; +use strum::{EnumDiscriminants, EnumIter, EnumString, IntoDiscriminant, IntoStaticStr}; use thiserror::Error; const DEFAULT_INSTRUMENT: &str = "sin(2*pi()*(442*2^((n+1)/N))*t)"; @@ -43,7 +43,7 @@ pub(super) enum Cli { Export(ExportOpts), /// Memo menu for examples and general help about syntax and supported audio formats #[command(subcommand)] - Memo(Memo), + Memo(MemoKind), } #[derive(Debug, Parser, Clone, Getters)] @@ -275,7 +275,10 @@ pub(super) struct ExportOpts { } #[derive(Clone, EnumDiscriminants)] -#[strum_discriminants(derive(EnumString, IntoStaticStr), strum(serialize_all = "lowercase"))] +#[strum_discriminants( + derive(EnumString, IntoStaticStr, EnumIter), + strum(serialize_all = "lowercase") +)] pub(super) enum AudioFormat { Mp3 { bitrate: Bitrate, @@ -368,8 +371,8 @@ fn audio_format_parser(input: &str) -> Result { tag::<&'a str, LocatedSpan<&'a str>, nom::error::Error>>( AudioFormatDiscriminants::Wav.into(), ), - u16.or(success(320)) - .and(value(SampleFormat::Float, char('f')).or(success(SampleFormat::Int))) + u16.or(success(16)) + .and(value(SampleFormat::Float, char('f')).or(success(SampleFormat::Float))) .map(|(bps, sample_format)| AudioFormat::Wav { bps, sample_format }), ) } @@ -411,7 +414,7 @@ fn audio_format_parser(input: &str) -> Result { .map_err(|e| anyhow!("{e:?}")) } -#[derive(Debug, Parser, Clone, EnumString, Default)] +#[derive(Debug, Parser, Clone, EnumString, Default, EnumIter, IntoStaticStr)] #[strum(ascii_case_insensitive)] pub(super) enum RawAudioFormat { ALaw, @@ -438,15 +441,29 @@ pub(super) enum RawAudioFormat { } #[derive(Debug, Parser, Clone)] -pub(super) enum Memo { - Syntax, - #[command(subcommand)] - Example(Example), +pub(super) enum MemoKind { + #[command(flatten)] + Syntax(SyntaxTarget), + /// Show a list of examples or a specific example from the list + Examples { n: Option }, + /// Print all available formats Formats, } #[derive(Debug, Parser, Clone)] -pub(super) enum Example { - List, - N { id: u8 }, +pub(super) enum SyntaxTarget { + /// Print BLIP's grammar + Blip, + #[command(flatten)] + Expressions(FastEvalSyntaxSection), +} + +#[derive(Debug, Parser, Clone)] +pub(super) enum FastEvalSyntaxSection { + /// Print available functions and constants for expressions + Functions, + /// Print available operators for expressions + Ops, + /// Print available literals for expressions + Literals, } diff --git a/src/cli/main.rs b/src/cli/main.rs index d3d6d6c..4107167 100644 --- a/src/cli/main.rs +++ b/src/cli/main.rs @@ -1,5 +1,8 @@ +//! See [the lib docs](https://docs.rs/bliplib) + mod cli; use std::{ + borrow::Cow, collections::{HashMap, HashSet}, fs::File, io::{Cursor, Write, read_to_string, stdout}, @@ -17,8 +20,12 @@ use dasp_sample::Sample; use hound::{SampleFormat, WavSpec, WavWriter}; use log::{debug, error, info, warn}; use rodio::{OutputStream, Sink, buffer::SamplesBuffer}; +use strum::IntoEnumIterator; -use crate::cli::{ExportOpts, PlayOpts}; +use crate::cli::{ + AudioFormatDiscriminants, ExportOpts, FastEvalSyntaxSection, MemoKind, PlayOpts, + RawAudioFormat, SyntaxTarget, +}; fn main() -> anyhow::Result<()> { env_logger::init(); @@ -82,7 +89,59 @@ fn main() -> anyhow::Result<()> { info!("writing samples to output"); writer.write_all(buff.get_ref())?; } - Memo(_opts) => todo!(), + Memo(MemoKind::Syntax(s)) => println!( + "{}", + match s { + SyntaxTarget::Blip => include_str!("../../doc/language-design.txt"), + SyntaxTarget::Expressions(FastEvalSyntaxSection::Functions) => + include_str!("../../doc/fasteval/functions.txt"), + SyntaxTarget::Expressions(FastEvalSyntaxSection::Literals) => + include_str!("../../doc/fasteval/literals.txt"), + SyntaxTarget::Expressions(FastEvalSyntaxSection::Ops) => + include_str!("../../doc/fasteval/operators.txt"), + } + ), + Memo(MemoKind::Examples { n }) => { + let mut examples = include_str!("../../doc/examples.txt") + .lines() + .filter_map(|l| l.split_once(':')); + match n { + None => { + for (id, (name, _)) in examples.enumerate() { + println!("{id}\t{name}") + } + } + Some(id) => println!( + "{}", + examples + .nth(id.into()) + .context("example not found")? + .1 + .trim_start() + ), + } + } + Memo(MemoKind::Formats) => { + for discriminant in AudioFormatDiscriminants::iter() { + use AudioFormatDiscriminants::*; + let ext: Cow<'static, str> = match discriminant { + Mp3 => { + "\t[int, bitrate]\t\t[str, quality from 'w' (\"worst\") to 'b' (\"best\")]\t(default: mp3320g)".into() + } + Wav => { + "\t[int, bytes per sample]\t['f', set sample format to float instead of int]\t(default: wav16f)".into() + } + Flac => "\t[int, bits per sample]\t\t\t\t\t\t\t\t(default: flac320000)".into(), + Raw => format!( + "\t[str, subformat]\t\t\t\t\t\t\t\t(default: rawmulaw)\nraw subformats include: {:#?}", + RawAudioFormat::iter() + .map(Into::into) + .collect::>() + ).into(), + }; + println!("* {}{}", Into::<&'static str>::into(discriminant), ext) + } + } } Ok(()) }