complete cli memo menu

This commit is contained in:
brevalferrari 2025-06-07 20:04:22 +02:00
parent 2f5d9aed32
commit 9a5e237733
7 changed files with 152 additions and 15 deletions

View file

@ -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"

2
doc/examples.txt Normal file
View file

@ -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)

View file

@ -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)

17
doc/fasteval/literals.txt Normal file
View file

@ -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

View file

@ -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

View file

@ -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<AudioFormat, anyhow::Error> {
tag::<&'a str, LocatedSpan<&'a str>, nom::error::Error<LocatedSpan<&'a str>>>(
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<AudioFormat, anyhow::Error> {
.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<u8> },
/// 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,
}

View file

@ -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::<Vec<&'static str>>()
).into(),
};
println!("* {}{}", Into::<&'static str>::into(discriminant), ext)
}
}
}
Ok(())
}