WIP simplify
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
use super::ops::Op;
|
||||
use super::types::{Dictionary, SourceSpan};
|
||||
use super::words::{compile_word, simple_op};
|
||||
use super::words::compile_word;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
enum Token {
|
||||
@@ -25,6 +25,11 @@ fn tokenize(input: &str) -> Vec<Token> {
|
||||
continue;
|
||||
}
|
||||
|
||||
if c == '(' || c == ')' {
|
||||
chars.next();
|
||||
continue;
|
||||
}
|
||||
|
||||
if c == '"' {
|
||||
let start = pos;
|
||||
chars.next();
|
||||
@@ -88,7 +93,6 @@ fn tokenize(input: &str) -> Vec<Token> {
|
||||
fn compile(tokens: &[Token], dict: &Dictionary) -> Result<Vec<Op>, String> {
|
||||
let mut ops = Vec::new();
|
||||
let mut i = 0;
|
||||
let mut list_depth: usize = 0;
|
||||
|
||||
while i < tokens.len() {
|
||||
match &tokens[i] {
|
||||
@@ -122,20 +126,6 @@ fn compile(tokens: &[Token], dict: &Dictionary) -> Result<Vec<Op>, String> {
|
||||
ops.push(Op::Branch(else_ops.len()));
|
||||
ops.extend(else_ops);
|
||||
}
|
||||
} else if is_list_start(word) {
|
||||
ops.push(Op::ListStart);
|
||||
list_depth += 1;
|
||||
} else if is_list_end(word) {
|
||||
list_depth = list_depth.saturating_sub(1);
|
||||
if let Some(op) = simple_op(word) {
|
||||
ops.push(op);
|
||||
}
|
||||
} else if list_depth > 0 {
|
||||
let mut word_ops = Vec::new();
|
||||
if !compile_word(word, Some(*span), &mut word_ops, dict) {
|
||||
return Err(format!("unknown word: {word}"));
|
||||
}
|
||||
ops.push(Op::Quotation(word_ops, Some(*span)));
|
||||
} else if !compile_word(word, Some(*span), &mut ops, dict) {
|
||||
return Err(format!("unknown word: {word}"));
|
||||
}
|
||||
@@ -147,14 +137,6 @@ fn compile(tokens: &[Token], dict: &Dictionary) -> Result<Vec<Op>, String> {
|
||||
Ok(ops)
|
||||
}
|
||||
|
||||
fn is_list_start(word: &str) -> bool {
|
||||
matches!(word, "[" | "<" | "<<")
|
||||
}
|
||||
|
||||
fn is_list_end(word: &str) -> bool {
|
||||
matches!(word, "]" | ">" | ">>")
|
||||
}
|
||||
|
||||
fn compile_quotation(tokens: &[Token], dict: &Dictionary) -> Result<(Vec<Op>, usize, SourceSpan), String> {
|
||||
let mut depth = 1;
|
||||
let mut end_idx = None;
|
||||
|
||||
@@ -55,17 +55,14 @@ pub enum Op {
|
||||
Rand,
|
||||
Seed,
|
||||
Cycle,
|
||||
PCycle,
|
||||
TCycle,
|
||||
Choose,
|
||||
ChanceExec,
|
||||
ProbExec,
|
||||
Coin,
|
||||
Mtof,
|
||||
Ftom,
|
||||
ListStart,
|
||||
ListEnd,
|
||||
ListEndCycle,
|
||||
PCycle,
|
||||
ListEndPCycle,
|
||||
SetTempo,
|
||||
Every,
|
||||
Quotation(Vec<Op>, Option<SourceSpan>),
|
||||
@@ -85,4 +82,5 @@ pub enum Op {
|
||||
EmitN,
|
||||
ClearCmd,
|
||||
SetSpeed,
|
||||
At,
|
||||
}
|
||||
|
||||
@@ -47,8 +47,8 @@ pub enum Value {
|
||||
Int(i64, Option<SourceSpan>),
|
||||
Float(f64, Option<SourceSpan>),
|
||||
Str(String, Option<SourceSpan>),
|
||||
Marker,
|
||||
Quotation(Vec<Op>, Option<SourceSpan>),
|
||||
CycleList(Vec<Value>),
|
||||
}
|
||||
|
||||
impl PartialEq for Value {
|
||||
@@ -57,8 +57,8 @@ impl PartialEq for Value {
|
||||
(Value::Int(a, _), Value::Int(b, _)) => a == b,
|
||||
(Value::Float(a, _), Value::Float(b, _)) => a == b,
|
||||
(Value::Str(a, _), Value::Str(b, _)) => a == b,
|
||||
(Value::Marker, Value::Marker) => true,
|
||||
(Value::Quotation(a, _), Value::Quotation(b, _)) => a == b,
|
||||
(Value::CycleList(a), Value::CycleList(b)) => a == b,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
@@ -93,29 +93,25 @@ impl Value {
|
||||
Value::Int(i, _) => *i != 0,
|
||||
Value::Float(f, _) => *f != 0.0,
|
||||
Value::Str(s, _) => !s.is_empty(),
|
||||
Value::Marker => false,
|
||||
Value::Quotation(..) => true,
|
||||
Value::CycleList(items) => !items.is_empty(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn is_marker(&self) -> bool {
|
||||
matches!(self, Value::Marker)
|
||||
}
|
||||
|
||||
pub(super) fn to_param_string(&self) -> String {
|
||||
match self {
|
||||
Value::Int(i, _) => i.to_string(),
|
||||
Value::Float(f, _) => f.to_string(),
|
||||
Value::Str(s, _) => s.clone(),
|
||||
Value::Marker => String::new(),
|
||||
Value::Quotation(..) => String::new(),
|
||||
Value::CycleList(_) => String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn span(&self) -> Option<SourceSpan> {
|
||||
match self {
|
||||
Value::Int(_, s) | Value::Float(_, s) | Value::Str(_, s) | Value::Quotation(_, s) => *s,
|
||||
Value::Marker => None,
|
||||
Value::CycleList(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -124,6 +120,7 @@ impl Value {
|
||||
pub(super) struct CmdRegister {
|
||||
sound: Option<Value>,
|
||||
params: Vec<(String, Value)>,
|
||||
deltas: Vec<Value>,
|
||||
}
|
||||
|
||||
impl CmdRegister {
|
||||
@@ -135,6 +132,14 @@ impl CmdRegister {
|
||||
self.params.push((key, val));
|
||||
}
|
||||
|
||||
pub(super) fn set_deltas(&mut self, deltas: Vec<Value>) {
|
||||
self.deltas = deltas;
|
||||
}
|
||||
|
||||
pub(super) fn deltas(&self) -> &[Value] {
|
||||
&self.deltas
|
||||
}
|
||||
|
||||
pub(super) fn snapshot(&self) -> Option<(Value, Vec<(String, Value)>)> {
|
||||
self.sound
|
||||
.as_ref()
|
||||
|
||||
@@ -145,35 +145,26 @@ impl Forth {
|
||||
select_and_run(selected, stack, outputs, cmd)
|
||||
};
|
||||
|
||||
let drain_list_select_run = |idx_source: usize,
|
||||
err_msg: &str,
|
||||
stack: &mut Vec<Value>,
|
||||
outputs: &mut Vec<String>,
|
||||
cmd: &mut CmdRegister|
|
||||
-> Result<(), String> {
|
||||
let mut values = Vec::new();
|
||||
while let Some(v) = stack.pop() {
|
||||
if v.is_marker() {
|
||||
break;
|
||||
}
|
||||
values.push(v);
|
||||
}
|
||||
if values.is_empty() {
|
||||
return Err(err_msg.into());
|
||||
}
|
||||
values.reverse();
|
||||
let idx = idx_source % values.len();
|
||||
let selected = values[idx].clone();
|
||||
select_and_run(selected, stack, outputs, cmd)
|
||||
};
|
||||
|
||||
let emit_once = |cmd: &CmdRegister, outputs: &mut Vec<String>| -> Result<Option<Value>, String> {
|
||||
let emit_with_cycling = |cmd: &CmdRegister, emit_idx: usize, delta_secs: f64, outputs: &mut Vec<String>| -> Result<Option<Value>, String> {
|
||||
let (sound_val, params) = cmd.snapshot().ok_or("no sound set")?;
|
||||
let sound = sound_val.as_str()?.to_string();
|
||||
let resolved_sound_val = resolve_cycling(&sound_val, emit_idx);
|
||||
// Note: sound span is recorded by Op::Emit, not here
|
||||
let sound = resolved_sound_val.as_str()?.to_string();
|
||||
let resolved_params: Vec<(String, String)> =
|
||||
params.iter().map(|(k, v)| (k.clone(), v.to_param_string())).collect();
|
||||
emit_output(&sound, &resolved_params, ctx.step_duration(), ctx.nudge_secs, outputs);
|
||||
Ok(Some(sound_val))
|
||||
params.iter().map(|(k, v)| {
|
||||
let resolved = resolve_cycling(v, emit_idx);
|
||||
// Record selected span for params if they came from a CycleList
|
||||
if let Value::CycleList(_) = v {
|
||||
if let Some(span) = resolved.span() {
|
||||
if let Some(trace) = trace_cell.borrow_mut().as_mut() {
|
||||
trace.selected_spans.push(span);
|
||||
}
|
||||
}
|
||||
}
|
||||
(k.clone(), resolved.to_param_string())
|
||||
}).collect();
|
||||
emit_output(&sound, &resolved_params, ctx.step_duration(), delta_secs, outputs);
|
||||
Ok(Some(resolved_sound_val))
|
||||
};
|
||||
|
||||
while pc < ops.len() {
|
||||
@@ -361,12 +352,28 @@ impl Forth {
|
||||
}
|
||||
|
||||
Op::Emit => {
|
||||
if let Some(sound_val) = emit_once(cmd, outputs)? {
|
||||
if let Some(span) = sound_val.span() {
|
||||
let deltas = if cmd.deltas().is_empty() {
|
||||
vec![Value::Float(0.0, None)]
|
||||
} else {
|
||||
cmd.deltas().to_vec()
|
||||
};
|
||||
|
||||
for (emit_idx, delta_val) in deltas.iter().enumerate() {
|
||||
let delta_frac = delta_val.as_float()?;
|
||||
let delta_secs = ctx.nudge_secs + delta_frac * ctx.step_duration();
|
||||
// Record delta span for highlighting
|
||||
if let Some(span) = delta_val.span() {
|
||||
if let Some(trace) = trace_cell.borrow_mut().as_mut() {
|
||||
trace.selected_spans.push(span);
|
||||
}
|
||||
}
|
||||
if let Some(sound_val) = emit_with_cycling(cmd, emit_idx, delta_secs, outputs)? {
|
||||
if let Some(span) = sound_val.span() {
|
||||
if let Some(trace) = trace_cell.borrow_mut().as_mut() {
|
||||
trace.selected_spans.push(span);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -438,6 +445,19 @@ impl Forth {
|
||||
drain_select_run(count, idx, stack, outputs, cmd)?;
|
||||
}
|
||||
|
||||
Op::TCycle => {
|
||||
let count = stack.pop().ok_or("stack underflow")?.as_int()? as usize;
|
||||
if count == 0 {
|
||||
return Err("tcycle count must be > 0".into());
|
||||
}
|
||||
if stack.len() < count {
|
||||
return Err("stack underflow".into());
|
||||
}
|
||||
let start = stack.len() - count;
|
||||
let values: Vec<Value> = stack.drain(start..).collect();
|
||||
stack.push(Value::CycleList(values));
|
||||
}
|
||||
|
||||
Op::Choose => {
|
||||
let count = stack.pop().ok_or("stack underflow")?.as_int()? as usize;
|
||||
if count == 0 {
|
||||
@@ -606,37 +626,23 @@ impl Forth {
|
||||
cmd.set_param("dur".into(), Value::Float(dur, None));
|
||||
}
|
||||
|
||||
Op::ListStart => {
|
||||
stack.push(Value::Marker);
|
||||
}
|
||||
|
||||
Op::ListEnd => {
|
||||
let mut count = 0;
|
||||
let mut values = Vec::new();
|
||||
while let Some(v) = stack.pop() {
|
||||
if v.is_marker() {
|
||||
break;
|
||||
Op::At => {
|
||||
let top = stack.pop().ok_or("stack underflow")?;
|
||||
let deltas = match &top {
|
||||
Value::Float(..) => vec![top],
|
||||
Value::Int(n, _) if *n > 0 && stack.len() >= *n as usize => {
|
||||
let count = *n as usize;
|
||||
let mut vals = Vec::with_capacity(count);
|
||||
for _ in 0..count {
|
||||
vals.push(stack.pop().ok_or("stack underflow")?);
|
||||
}
|
||||
vals.reverse();
|
||||
vals
|
||||
}
|
||||
values.push(v);
|
||||
count += 1;
|
||||
}
|
||||
values.reverse();
|
||||
for v in values {
|
||||
stack.push(v);
|
||||
}
|
||||
stack.push(Value::Int(count, None));
|
||||
}
|
||||
|
||||
Op::ListEndCycle | Op::ListEndPCycle => {
|
||||
let idx_source = match &ops[pc] {
|
||||
Op::ListEndCycle => ctx.runs,
|
||||
_ => ctx.iter,
|
||||
Value::Int(..) => vec![top],
|
||||
_ => return Err("at expects number or list".into()),
|
||||
};
|
||||
let err_msg = match &ops[pc] {
|
||||
Op::ListEndCycle => "empty cycle list",
|
||||
_ => "empty pattern cycle list",
|
||||
};
|
||||
drain_list_select_run(idx_source, err_msg, stack, outputs, cmd)?;
|
||||
cmd.set_deltas(deltas);
|
||||
}
|
||||
|
||||
Op::Adsr => {
|
||||
@@ -699,8 +705,8 @@ impl Forth {
|
||||
if n < 0 {
|
||||
return Err("emit count must be >= 0".into());
|
||||
}
|
||||
for _ in 0..n {
|
||||
emit_once(cmd, outputs)?;
|
||||
for i in 0..n as usize {
|
||||
emit_with_cycling(cmd, i, ctx.nudge_secs, outputs)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -846,3 +852,12 @@ fn format_cmd(pairs: &[(String, String)]) -> String {
|
||||
let parts: Vec<String> = pairs.iter().map(|(k, v)| format!("{k}/{v}")).collect();
|
||||
format!("/{}", parts.join("/"))
|
||||
}
|
||||
|
||||
fn resolve_cycling(val: &Value, emit_idx: usize) -> Value {
|
||||
match val {
|
||||
Value::CycleList(items) if !items.is_empty() => {
|
||||
items[emit_idx % items.len()].clone()
|
||||
}
|
||||
other => other.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ pub const WORDS: &[Word] = &[
|
||||
},
|
||||
Word {
|
||||
name: "dupn",
|
||||
aliases: &[],
|
||||
aliases: &["!"],
|
||||
category: "Stack",
|
||||
stack: "(a n -- a a ... a)",
|
||||
desc: "Duplicate a onto stack n times",
|
||||
@@ -482,19 +482,28 @@ pub const WORDS: &[Word] = &[
|
||||
Word {
|
||||
name: "cycle",
|
||||
aliases: &[],
|
||||
category: "Lists",
|
||||
stack: "(..n n -- val)",
|
||||
desc: "Cycle through n items by step",
|
||||
example: "1 2 3 3 cycle",
|
||||
category: "Selection",
|
||||
stack: "(v1..vn n -- selected)",
|
||||
desc: "Cycle through n items by step runs",
|
||||
example: "60 64 67 3 cycle",
|
||||
compile: Simple,
|
||||
},
|
||||
Word {
|
||||
name: "pcycle",
|
||||
aliases: &[],
|
||||
category: "Lists",
|
||||
stack: "(..n n -- val)",
|
||||
desc: "Cycle through n items by pattern",
|
||||
example: "1 2 3 3 pcycle",
|
||||
category: "Selection",
|
||||
stack: "(v1..vn n -- selected)",
|
||||
desc: "Cycle through n items by pattern iteration",
|
||||
example: "60 64 67 3 pcycle",
|
||||
compile: Simple,
|
||||
},
|
||||
Word {
|
||||
name: "tcycle",
|
||||
aliases: &[],
|
||||
category: "Selection",
|
||||
stack: "(v1..vn n -- CycleList)",
|
||||
desc: "Create cycle list for emit-time resolution",
|
||||
example: "60 64 67 3 tcycle note",
|
||||
compile: Simple,
|
||||
},
|
||||
Word {
|
||||
@@ -799,41 +808,13 @@ pub const WORDS: &[Word] = &[
|
||||
example: "1 4 chain",
|
||||
compile: Simple,
|
||||
},
|
||||
// Lists
|
||||
Word {
|
||||
name: "[",
|
||||
aliases: &["<", "<<"],
|
||||
category: "Lists",
|
||||
stack: "(-- marker)",
|
||||
desc: "Start list",
|
||||
example: "[ 1 2 3 ]",
|
||||
compile: Simple,
|
||||
},
|
||||
Word {
|
||||
name: "]",
|
||||
name: "at",
|
||||
aliases: &[],
|
||||
category: "Lists",
|
||||
stack: "(marker..n -- n)",
|
||||
desc: "End list, push count",
|
||||
example: "[ 1 2 3 ] => 3",
|
||||
compile: Simple,
|
||||
},
|
||||
Word {
|
||||
name: ">",
|
||||
aliases: &[],
|
||||
category: "Lists",
|
||||
stack: "(marker..n -- val)",
|
||||
desc: "End cycle list, pick by step",
|
||||
example: "< 1 2 3 > => cycles through 1, 2, 3",
|
||||
compile: Simple,
|
||||
},
|
||||
Word {
|
||||
name: ">>",
|
||||
aliases: &[],
|
||||
category: "Lists",
|
||||
stack: "(marker..n -- val)",
|
||||
desc: "End pattern cycle list, pick by pattern",
|
||||
example: "<< 1 2 3 >> => cycles through 1, 2, 3 per pattern",
|
||||
category: "Time",
|
||||
stack: "(list|n --)",
|
||||
desc: "Set delta context for emit timing",
|
||||
example: "[ 0 0.5 ] at kick s . => emits at 0 and 0.5 of step",
|
||||
compile: Simple,
|
||||
},
|
||||
// Quotations
|
||||
@@ -2050,6 +2031,7 @@ pub(super) fn simple_op(name: &str) -> Option<Op> {
|
||||
"seed" => Op::Seed,
|
||||
"cycle" => Op::Cycle,
|
||||
"pcycle" => Op::PCycle,
|
||||
"tcycle" => Op::TCycle,
|
||||
"choose" => Op::Choose,
|
||||
"every" => Op::Every,
|
||||
"chance" => Op::ChanceExec,
|
||||
@@ -2061,10 +2043,7 @@ pub(super) fn simple_op(name: &str) -> Option<Op> {
|
||||
"!?" => Op::Unless,
|
||||
"tempo!" => Op::SetTempo,
|
||||
"speed!" => Op::SetSpeed,
|
||||
"[" => Op::ListStart,
|
||||
"]" => Op::ListEnd,
|
||||
">" => Op::ListEndCycle,
|
||||
">>" => Op::ListEndPCycle,
|
||||
"at" => Op::At,
|
||||
"adsr" => Op::Adsr,
|
||||
"ad" => Op::Ad,
|
||||
"apply" => Op::Apply,
|
||||
|
||||
Reference in New Issue
Block a user