Files
Cagire/crates/forth/src/words/compile.rs
2026-02-22 23:50:35 +01:00

309 lines
8.0 KiB
Rust

use std::sync::Arc;
use crate::ops::Op;
use crate::theory;
use crate::types::{Dictionary, SourceSpan};
use super::{lookup_word, WordCompile::*};
pub(super) fn simple_op(name: &str) -> Option<Op> {
Some(match name {
"dup" => Op::Dup,
"dupn" => Op::Dupn,
"drop" => Op::Drop,
"swap" => Op::Swap,
"over" => Op::Over,
"rot" => Op::Rot,
"nip" => Op::Nip,
"tuck" => Op::Tuck,
"2dup" => Op::Dup2,
"2drop" => Op::Drop2,
"2swap" => Op::Swap2,
"2over" => Op::Over2,
"rev" => Op::Rev,
"shuffle" => Op::Shuffle,
"sort" => Op::Sort,
"rsort" => Op::RSort,
"sum" => Op::Sum,
"prod" => Op::Prod,
"+" => Op::Add,
"-" => Op::Sub,
"*" => Op::Mul,
"/" => Op::Div,
"mod" => Op::Mod,
"neg" => Op::Neg,
"abs" => Op::Abs,
"floor" => Op::Floor,
"ceil" => Op::Ceil,
"round" => Op::Round,
"min" => Op::Min,
"max" => Op::Max,
"pow" => Op::Pow,
"sqrt" => Op::Sqrt,
"sin" => Op::Sin,
"cos" => Op::Cos,
"log" => Op::Log,
"=" => Op::Eq,
"!=" => Op::Ne,
"lt" => Op::Lt,
"gt" => Op::Gt,
"<=" => Op::Le,
">=" => Op::Ge,
"and" => Op::And,
"or" => Op::Or,
"not" => Op::Not,
"xor" => Op::Xor,
"nand" => Op::Nand,
"nor" => Op::Nor,
"ifelse" => Op::IfElse,
"pick" => Op::Pick,
"sound" => Op::NewCmd,
"." => Op::Emit,
"rand" => Op::Rand(None),
"exprand" => Op::ExpRand(None),
"logrand" => Op::LogRand(None),
"seed" => Op::Seed,
"cycle" => Op::Cycle(None),
"pcycle" => Op::PCycle(None),
"choose" => Op::Choose(None),
"bounce" => Op::Bounce(None),
"wchoose" => Op::WChoose(None),
"every" => Op::Every(None),
"bjork" => Op::Bjork(None),
"pbjork" => Op::PBjork(None),
"chance" => Op::ChanceExec(None),
"prob" => Op::ProbExec(None),
"coin" => Op::Coin(None),
"mtof" => Op::Mtof,
"ftom" => Op::Ftom,
"?" => Op::When,
"!?" => Op::Unless,
"tempo!" => Op::SetTempo,
"speed!" => Op::SetSpeed,
"at" => Op::At,
"arp" => Op::Arp,
"adsr" => Op::Adsr,
"ad" => Op::Ad,
"apply" => Op::Apply,
"ramp" => Op::Ramp,
"triangle" => Op::Triangle,
"range" => Op::Range,
"linmap" => Op::LinMap,
"expmap" => Op::ExpMap,
"perlin" => Op::Perlin,
"loop" => Op::Loop,
"oct" => Op::Oct,
"clear" => Op::ClearCmd,
".." => Op::IntRange,
".," => Op::StepRange,
"gen" => Op::Generate,
"geom.." => Op::GeomRange,
"euclid" => Op::Euclid,
"euclidrot" => Op::EuclidRot,
"times" => Op::Times,
"m." => Op::MidiEmit,
"ccval" => Op::GetMidiCC,
"mclock" => Op::MidiClock,
"mstart" => Op::MidiStart,
"mstop" => Op::MidiStop,
"mcont" => Op::MidiContinue,
"forget" => Op::Forget,
"lfo" => Op::ModLfo(0),
"tlfo" => Op::ModLfo(1),
"wlfo" => Op::ModLfo(2),
"qlfo" => Op::ModLfo(3),
"slide" => Op::ModSlide(0),
"expslide" => Op::ModSlide(1),
"sslide" => Op::ModSlide(2),
"jit" => Op::ModRnd(0),
"sjit" => Op::ModRnd(1),
"drunk" => Op::ModRnd(2),
"env" => Op::ModEnv,
_ => return None,
})
}
fn parse_note_name(name: &str) -> Option<i64> {
let name = name.to_lowercase();
let bytes = name.as_bytes();
if bytes.len() < 2 {
return None;
}
let base = match bytes[0] {
b'c' => 0,
b'd' => 2,
b'e' => 4,
b'f' => 5,
b'g' => 7,
b'a' => 9,
b'b' => 11,
_ => return None,
};
let (modifier, octave_start) = match bytes[1] {
b'#' | b's' => (1, 2),
b'b' if bytes.len() > 2 && bytes[2].is_ascii_digit() => (-1, 2),
b'0'..=b'9' => (0, 1),
_ => return None,
};
let octave_str = &name[octave_start..];
let octave: i64 = octave_str.parse().ok()?;
if !(-1..=9).contains(&octave) {
return None;
}
Some((octave + 1) * 12 + base + modifier)
}
fn parse_interval(name: &str) -> Option<i64> {
let simple = match name {
"P1" | "unison" => 0,
"m2" => 1,
"M2" => 2,
"m3" => 3,
"M3" => 4,
"P4" => 5,
"aug4" | "dim5" | "tritone" => 6,
"P5" => 7,
"m6" => 8,
"M6" => 9,
"m7" => 10,
"M7" => 11,
"P8" => 12,
"m9" => 13,
"M9" => 14,
"m10" => 15,
"M10" => 16,
"P11" => 17,
"aug11" => 18,
"P12" => 19,
"m13" => 20,
"M13" => 21,
"m14" => 22,
"M14" => 23,
"P15" => 24,
_ => return None,
};
Some(simple)
}
fn attach_span(op: &mut Op, span: SourceSpan) {
match op {
Op::Rand(s) | Op::ExpRand(s) | Op::LogRand(s) | Op::Coin(s)
| Op::Choose(s) | Op::WChoose(s) | Op::Cycle(s) | Op::PCycle(s)
| Op::Bounce(s) | Op::ChanceExec(s) | Op::ProbExec(s)
| Op::Every(s)
| Op::Bjork(s) | Op::PBjork(s) => *s = Some(span),
_ => {}
}
}
pub(crate) fn compile_word(
name: &str,
span: Option<SourceSpan>,
ops: &mut Vec<Op>,
dict: &Dictionary,
) -> bool {
match name {
"linramp" => {
ops.push(Op::PushFloat(1.0, span));
ops.push(Op::Ramp);
return true;
}
"expramp" => {
ops.push(Op::PushFloat(3.0, span));
ops.push(Op::Ramp);
return true;
}
"logramp" => {
ops.push(Op::PushFloat(0.3, span));
ops.push(Op::Ramp);
return true;
}
_ => {}
}
if let Some(pattern) = theory::lookup(name) {
ops.push(Op::Degree(pattern));
return true;
}
if let Some(intervals) = theory::chords::lookup(name) {
ops.push(Op::Chord(intervals));
return true;
}
if let Some(word) = lookup_word(name) {
match &word.compile {
Simple => {
if let Some(mut op) = simple_op(word.name) {
if let Some(sp) = span {
attach_span(&mut op, sp);
}
ops.push(op);
}
}
Context(ctx) => ops.push(Op::GetContext(ctx)),
Param => ops.push(Op::SetParam(word.name)),
Probability(p) => {
ops.push(Op::PushFloat(*p, None));
ops.push(Op::ChanceExec(span));
}
}
return true;
}
if let Some(var_name) = name.strip_prefix('@') {
if !var_name.is_empty() {
ops.push(Op::PushStr(Arc::from(var_name), span));
ops.push(Op::Get);
return true;
}
}
if let Some(var_name) = name.strip_prefix('!') {
if !var_name.is_empty() {
ops.push(Op::PushStr(Arc::from(var_name), span));
ops.push(Op::Set);
return true;
}
}
if let Some(var_name) = name.strip_prefix(',') {
if !var_name.is_empty() {
ops.push(Op::PushStr(Arc::from(var_name), span));
ops.push(Op::SetKeep);
return true;
}
}
if let Some(midi) = parse_note_name(name) {
ops.push(Op::PushInt(midi, span));
return true;
}
if let Some(semitones) = parse_interval(name) {
ops.push(Op::Dup);
ops.push(Op::PushInt(semitones, span));
ops.push(Op::Add);
return true;
}
if let Some(op) = simple_op(name) {
ops.push(op);
return true;
}
if let Some(body) = dict.lock().get(name) {
ops.extend(body.iter().cloned());
return true;
}
ops.push(Op::PushStr(Arc::from(name), span));
true
}