Work on documentation
This commit is contained in:
@@ -61,7 +61,13 @@ fn tokenize(input: &str) -> Vec<Token> {
|
||||
continue;
|
||||
}
|
||||
// single ; is a word, create token
|
||||
tokens.push(Token::Word(";".to_string(), SourceSpan { start: pos, end: pos + 1 }));
|
||||
tokens.push(Token::Word(
|
||||
";".to_string(),
|
||||
SourceSpan {
|
||||
start: pos,
|
||||
end: pos + 1,
|
||||
},
|
||||
));
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -96,15 +102,33 @@ fn compile(tokens: &[Token], dict: &Dictionary) -> Result<Vec<Op>, String> {
|
||||
|
||||
while i < tokens.len() {
|
||||
match &tokens[i] {
|
||||
Token::Int(n, span) => ops.push(Op::PushInt(*n, Some(*span))),
|
||||
Token::Float(f, span) => ops.push(Op::PushFloat(*f, Some(*span))),
|
||||
Token::Int(n, span) => {
|
||||
let key = n.to_string();
|
||||
if let Some(body) = dict.lock().unwrap().get(&key).cloned() {
|
||||
ops.extend(body);
|
||||
} else {
|
||||
ops.push(Op::PushInt(*n, Some(*span)));
|
||||
}
|
||||
}
|
||||
Token::Float(f, span) => {
|
||||
let key = f.to_string();
|
||||
if let Some(body) = dict.lock().unwrap().get(&key).cloned() {
|
||||
ops.extend(body);
|
||||
} else {
|
||||
ops.push(Op::PushFloat(*f, Some(*span)));
|
||||
}
|
||||
}
|
||||
Token::Str(s, span) => ops.push(Op::PushStr(s.clone(), Some(*span))),
|
||||
Token::Word(w, span) => {
|
||||
let word = w.as_str();
|
||||
if word == "{" {
|
||||
let (quote_ops, consumed, end_span) = compile_quotation(&tokens[i + 1..], dict)?;
|
||||
let (quote_ops, consumed, end_span) =
|
||||
compile_quotation(&tokens[i + 1..], dict)?;
|
||||
i += consumed;
|
||||
let body_span = SourceSpan { start: span.start, end: end_span.end };
|
||||
let body_span = SourceSpan {
|
||||
start: span.start,
|
||||
end: end_span.end,
|
||||
};
|
||||
ops.push(Op::Quotation(quote_ops, Some(body_span)));
|
||||
} else if word == "}" {
|
||||
return Err("unexpected }".into());
|
||||
@@ -115,7 +139,8 @@ fn compile(tokens: &[Token], dict: &Dictionary) -> Result<Vec<Op>, String> {
|
||||
} else if word == ";" {
|
||||
return Err("unexpected ;".into());
|
||||
} else if word == "if" {
|
||||
let (then_ops, else_ops, consumed, then_span, else_span) = compile_if(&tokens[i + 1..], dict)?;
|
||||
let (then_ops, else_ops, consumed, then_span, else_span) =
|
||||
compile_if(&tokens[i + 1..], dict)?;
|
||||
i += consumed;
|
||||
if else_ops.is_empty() {
|
||||
ops.push(Op::BranchIfZero(then_ops.len(), then_span, None));
|
||||
@@ -137,7 +162,10 @@ fn compile(tokens: &[Token], dict: &Dictionary) -> Result<Vec<Op>, String> {
|
||||
Ok(ops)
|
||||
}
|
||||
|
||||
fn compile_quotation(tokens: &[Token], dict: &Dictionary) -> Result<(Vec<Op>, usize, SourceSpan), String> {
|
||||
fn compile_quotation(
|
||||
tokens: &[Token],
|
||||
dict: &Dictionary,
|
||||
) -> Result<(Vec<Op>, usize, SourceSpan), String> {
|
||||
let mut depth = 1;
|
||||
let mut end_idx = None;
|
||||
|
||||
@@ -172,13 +200,18 @@ fn token_span(tok: &Token) -> Option<SourceSpan> {
|
||||
}
|
||||
}
|
||||
|
||||
fn compile_colon_def(tokens: &[Token], dict: &Dictionary) -> Result<(usize, String, Vec<Op>), String> {
|
||||
fn compile_colon_def(
|
||||
tokens: &[Token],
|
||||
dict: &Dictionary,
|
||||
) -> Result<(usize, String, Vec<Op>), String> {
|
||||
if tokens.is_empty() {
|
||||
return Err("expected word name after ':'".into());
|
||||
}
|
||||
let name = match &tokens[0] {
|
||||
Token::Word(w, _) => w.clone(),
|
||||
_ => return Err("expected word name after ':'".into()),
|
||||
Token::Int(n, _) => n.to_string(),
|
||||
Token::Float(f, _) => f.to_string(),
|
||||
Token::Str(s, _) => s.clone(),
|
||||
};
|
||||
let mut semi_pos = None;
|
||||
for (i, tok) in tokens[1..].iter().enumerate() {
|
||||
@@ -198,11 +231,26 @@ fn compile_colon_def(tokens: &[Token], dict: &Dictionary) -> Result<(usize, Stri
|
||||
fn tokens_span(tokens: &[Token]) -> Option<SourceSpan> {
|
||||
let first = tokens.first().and_then(token_span)?;
|
||||
let last = tokens.last().and_then(token_span)?;
|
||||
Some(SourceSpan { start: first.start, end: last.end })
|
||||
Some(SourceSpan {
|
||||
start: first.start,
|
||||
end: last.end,
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn compile_if(tokens: &[Token], dict: &Dictionary) -> Result<(Vec<Op>, Vec<Op>, usize, Option<SourceSpan>, Option<SourceSpan>), String> {
|
||||
fn compile_if(
|
||||
tokens: &[Token],
|
||||
dict: &Dictionary,
|
||||
) -> Result<
|
||||
(
|
||||
Vec<Op>,
|
||||
Vec<Op>,
|
||||
usize,
|
||||
Option<SourceSpan>,
|
||||
Option<SourceSpan>,
|
||||
),
|
||||
String,
|
||||
> {
|
||||
let mut depth = 1;
|
||||
let mut else_pos = None;
|
||||
let mut then_pos = None;
|
||||
|
||||
@@ -79,11 +79,11 @@ pub enum Op {
|
||||
Loop,
|
||||
Degree(&'static [i64]),
|
||||
Oct,
|
||||
EmitN,
|
||||
ClearCmd,
|
||||
SetSpeed,
|
||||
At,
|
||||
IntRange,
|
||||
Generate,
|
||||
GeomRange,
|
||||
Times,
|
||||
}
|
||||
|
||||
@@ -91,45 +91,51 @@ impl Forth {
|
||||
let mut pc = 0;
|
||||
let trace_cell = std::cell::RefCell::new(trace);
|
||||
|
||||
let run_quotation =
|
||||
|quot: Value, stack: &mut Vec<Value>, outputs: &mut Vec<String>, cmd: &mut CmdRegister| -> Result<(), String> {
|
||||
match quot {
|
||||
Value::Quotation(quot_ops, body_span) => {
|
||||
if let Some(span) = body_span {
|
||||
if let Some(trace) = trace_cell.borrow_mut().as_mut() {
|
||||
trace.executed_spans.push(span);
|
||||
}
|
||||
let run_quotation = |quot: Value,
|
||||
stack: &mut Vec<Value>,
|
||||
outputs: &mut Vec<String>,
|
||||
cmd: &mut CmdRegister|
|
||||
-> Result<(), String> {
|
||||
match quot {
|
||||
Value::Quotation(quot_ops, body_span) => {
|
||||
if let Some(span) = body_span {
|
||||
if let Some(trace) = trace_cell.borrow_mut().as_mut() {
|
||||
trace.executed_spans.push(span);
|
||||
}
|
||||
let mut trace_opt = trace_cell.borrow_mut().take();
|
||||
self.execute_ops(
|
||||
"_ops,
|
||||
ctx,
|
||||
stack,
|
||||
outputs,
|
||||
cmd,
|
||||
trace_opt.as_deref_mut(),
|
||||
)?;
|
||||
*trace_cell.borrow_mut() = trace_opt;
|
||||
Ok(())
|
||||
}
|
||||
_ => Err("expected quotation".into()),
|
||||
}
|
||||
};
|
||||
|
||||
let select_and_run =
|
||||
|selected: Value, stack: &mut Vec<Value>, outputs: &mut Vec<String>, cmd: &mut CmdRegister| -> Result<(), String> {
|
||||
if let Some(span) = selected.span() {
|
||||
if let Some(trace) = trace_cell.borrow_mut().as_mut() {
|
||||
trace.selected_spans.push(span);
|
||||
}
|
||||
}
|
||||
if matches!(selected, Value::Quotation(..)) {
|
||||
run_quotation(selected, stack, outputs, cmd)
|
||||
} else {
|
||||
stack.push(selected);
|
||||
let mut trace_opt = trace_cell.borrow_mut().take();
|
||||
self.execute_ops(
|
||||
"_ops,
|
||||
ctx,
|
||||
stack,
|
||||
outputs,
|
||||
cmd,
|
||||
trace_opt.as_deref_mut(),
|
||||
)?;
|
||||
*trace_cell.borrow_mut() = trace_opt;
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
_ => Err("expected quotation".into()),
|
||||
}
|
||||
};
|
||||
|
||||
let select_and_run = |selected: Value,
|
||||
stack: &mut Vec<Value>,
|
||||
outputs: &mut Vec<String>,
|
||||
cmd: &mut CmdRegister|
|
||||
-> Result<(), String> {
|
||||
if let Some(span) = selected.span() {
|
||||
if let Some(trace) = trace_cell.borrow_mut().as_mut() {
|
||||
trace.selected_spans.push(span);
|
||||
}
|
||||
}
|
||||
if matches!(selected, Value::Quotation(..)) {
|
||||
run_quotation(selected, stack, outputs, cmd)
|
||||
} else {
|
||||
stack.push(selected);
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
|
||||
let drain_select_run = |count: usize,
|
||||
idx: usize,
|
||||
@@ -146,15 +152,20 @@ impl Forth {
|
||||
select_and_run(selected, stack, outputs, cmd)
|
||||
};
|
||||
|
||||
let emit_with_cycling = |cmd: &CmdRegister, emit_idx: usize, delta_secs: f64, 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_opt, params) = cmd.snapshot().ok_or("nothing to emit")?;
|
||||
let resolved_sound_val = sound_opt.map(|sv| resolve_cycling(sv, emit_idx));
|
||||
let sound_str = match &resolved_sound_val {
|
||||
Some(v) => Some(v.as_str()?.to_string()),
|
||||
None => None,
|
||||
};
|
||||
let resolved_params: Vec<(String, String)> =
|
||||
params.iter().map(|(k, v)| {
|
||||
let resolved_params: Vec<(String, String)> = params
|
||||
.iter()
|
||||
.map(|(k, v)| {
|
||||
let resolved = resolve_cycling(v, emit_idx);
|
||||
if let Value::CycleList(_) = v {
|
||||
if let Some(span) = resolved.span() {
|
||||
@@ -164,8 +175,15 @@ impl Forth {
|
||||
}
|
||||
}
|
||||
(k.clone(), resolved.to_param_string())
|
||||
}).collect();
|
||||
emit_output(sound_str.as_deref(), &resolved_params, ctx.step_duration(), delta_secs, outputs);
|
||||
})
|
||||
.collect();
|
||||
emit_output(
|
||||
sound_str.as_deref(),
|
||||
&resolved_params,
|
||||
ctx.step_duration(),
|
||||
delta_secs,
|
||||
outputs,
|
||||
);
|
||||
Ok(resolved_sound_val.map(|v| v.into_owned()))
|
||||
};
|
||||
|
||||
@@ -369,7 +387,9 @@ impl Forth {
|
||||
trace.selected_spans.push(span);
|
||||
}
|
||||
}
|
||||
if let Some(sound_val) = emit_with_cycling(cmd, emit_idx, delta_secs, outputs)? {
|
||||
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);
|
||||
@@ -417,7 +437,11 @@ impl Forth {
|
||||
let a = stack.pop().ok_or("stack underflow")?;
|
||||
match (&a, &b) {
|
||||
(Value::Int(a_i, _), Value::Int(b_i, _)) => {
|
||||
let (lo, hi) = if a_i <= b_i { (*a_i, *b_i) } else { (*b_i, *a_i) };
|
||||
let (lo, hi) = if a_i <= b_i {
|
||||
(*a_i, *b_i)
|
||||
} else {
|
||||
(*b_i, *a_i)
|
||||
};
|
||||
let val = self.rng.lock().unwrap().gen_range(lo..=hi);
|
||||
stack.push(Value::Int(val, None));
|
||||
}
|
||||
@@ -708,16 +732,6 @@ impl Forth {
|
||||
cmd.clear();
|
||||
}
|
||||
|
||||
Op::EmitN => {
|
||||
let n = stack.pop().ok_or("stack underflow")?.as_int()?;
|
||||
if n < 0 {
|
||||
return Err("emit count must be >= 0".into());
|
||||
}
|
||||
for i in 0..n as usize {
|
||||
emit_with_cycling(cmd, i, ctx.nudge_secs, outputs)?;
|
||||
}
|
||||
}
|
||||
|
||||
Op::IntRange => {
|
||||
let end = stack.pop().ok_or("stack underflow")?.as_int()?;
|
||||
let start = stack.pop().ok_or("stack underflow")?.as_int()?;
|
||||
@@ -748,6 +762,21 @@ impl Forth {
|
||||
}
|
||||
}
|
||||
|
||||
Op::Times => {
|
||||
let quot = stack.pop().ok_or("stack underflow")?;
|
||||
let count = stack.pop().ok_or("stack underflow")?.as_int()?;
|
||||
if count < 0 {
|
||||
return Err("times count must be >= 0".into());
|
||||
}
|
||||
for i in 0..count {
|
||||
self.vars
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert("i".to_string(), Value::Int(i, None));
|
||||
run_quotation(quot.clone(), stack, outputs, cmd)?;
|
||||
}
|
||||
}
|
||||
|
||||
Op::GeomRange => {
|
||||
let count = stack.pop().ok_or("stack underflow")?.as_int()?;
|
||||
let ratio = stack.pop().ok_or("stack underflow")?.as_float()?;
|
||||
|
||||
@@ -438,16 +438,6 @@ pub const WORDS: &[Word] = &[
|
||||
compile: Simple,
|
||||
varargs: false,
|
||||
},
|
||||
Word {
|
||||
name: ".!",
|
||||
aliases: &[],
|
||||
category: "Sound",
|
||||
stack: "(n --)",
|
||||
desc: "Emit current sound n times",
|
||||
example: "\"kick\" s 4 .!",
|
||||
compile: Simple,
|
||||
varargs: true,
|
||||
},
|
||||
// Variables (prefix syntax: @name to fetch, !name to store)
|
||||
Word {
|
||||
name: "@<var>",
|
||||
@@ -2280,6 +2270,16 @@ pub const WORDS: &[Word] = &[
|
||||
compile: Simple,
|
||||
varargs: false,
|
||||
},
|
||||
Word {
|
||||
name: "times",
|
||||
aliases: &[],
|
||||
category: "Control",
|
||||
stack: "(n quot --)",
|
||||
desc: "Execute quotation n times, @i holds current index",
|
||||
example: "4 { @i . } times => 0 1 2 3",
|
||||
compile: Simple,
|
||||
varargs: false,
|
||||
},
|
||||
];
|
||||
|
||||
pub(super) fn simple_op(name: &str) -> Option<Op> {
|
||||
@@ -2352,11 +2352,11 @@ pub(super) fn simple_op(name: &str) -> Option<Op> {
|
||||
"chain" => Op::Chain,
|
||||
"loop" => Op::Loop,
|
||||
"oct" => Op::Oct,
|
||||
".!" => Op::EmitN,
|
||||
"clear" => Op::ClearCmd,
|
||||
".." => Op::IntRange,
|
||||
"gen" => Op::Generate,
|
||||
"geom.." => Op::GeomRange,
|
||||
"times" => Op::Times,
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user