This commit is contained in:
@@ -6,8 +6,8 @@ mod vm;
|
||||
mod words;
|
||||
|
||||
pub use types::{
|
||||
CcAccess, Dictionary, ExecutionTrace, Rng, SourceSpan, StepContext, Value, Variables,
|
||||
VariablesMap,
|
||||
CcAccess, Dictionary, ExecutionTrace, ResolvedValue, Rng, SourceSpan, StepContext, Value,
|
||||
Variables, VariablesMap,
|
||||
};
|
||||
pub use vm::Forth;
|
||||
pub use words::{lookup_word, Word, WordCompile, WORDS};
|
||||
|
||||
@@ -65,18 +65,18 @@ pub enum Op {
|
||||
Get,
|
||||
Set,
|
||||
GetContext(&'static str),
|
||||
Rand,
|
||||
ExpRand,
|
||||
LogRand,
|
||||
Rand(Option<SourceSpan>),
|
||||
ExpRand(Option<SourceSpan>),
|
||||
LogRand(Option<SourceSpan>),
|
||||
Seed,
|
||||
Cycle,
|
||||
PCycle,
|
||||
Choose,
|
||||
Bounce,
|
||||
WChoose,
|
||||
ChanceExec,
|
||||
ProbExec,
|
||||
Coin,
|
||||
Cycle(Option<SourceSpan>),
|
||||
PCycle(Option<SourceSpan>),
|
||||
Choose(Option<SourceSpan>),
|
||||
Bounce(Option<SourceSpan>),
|
||||
WChoose(Option<SourceSpan>),
|
||||
ChanceExec(Option<SourceSpan>),
|
||||
ProbExec(Option<SourceSpan>),
|
||||
Coin(Option<SourceSpan>),
|
||||
Mtof,
|
||||
Ftom,
|
||||
SetTempo,
|
||||
|
||||
@@ -18,10 +18,30 @@ pub struct SourceSpan {
|
||||
pub end: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ResolvedValue {
|
||||
Int(i64),
|
||||
Float(f64),
|
||||
Bool(bool),
|
||||
Str(Arc<str>),
|
||||
}
|
||||
|
||||
impl ResolvedValue {
|
||||
pub fn display(&self) -> String {
|
||||
match self {
|
||||
ResolvedValue::Int(i) => i.to_string(),
|
||||
ResolvedValue::Float(f) => format!("{f:.2}"),
|
||||
ResolvedValue::Bool(b) => if *b { "yes" } else { "no" }.into(),
|
||||
ResolvedValue::Str(s) => s.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct ExecutionTrace {
|
||||
pub executed_spans: Vec<SourceSpan>,
|
||||
pub selected_spans: Vec<SourceSpan>,
|
||||
pub resolved: Vec<(SourceSpan, ResolvedValue)>,
|
||||
}
|
||||
|
||||
pub struct StepContext<'a> {
|
||||
|
||||
@@ -8,8 +8,8 @@ use std::sync::Arc;
|
||||
use super::compiler::compile_script;
|
||||
use super::ops::Op;
|
||||
use super::types::{
|
||||
CmdRegister, Dictionary, ExecutionTrace, Rng, Stack, StepContext, Value, Variables,
|
||||
VariablesMap,
|
||||
CmdRegister, Dictionary, ExecutionTrace, ResolvedValue, Rng, SourceSpan, Stack, StepContext,
|
||||
Value, Variables, VariablesMap,
|
||||
};
|
||||
|
||||
pub struct Forth {
|
||||
@@ -637,7 +637,7 @@ impl Forth {
|
||||
stack.push(val);
|
||||
}
|
||||
|
||||
Op::Rand => {
|
||||
Op::Rand(word_span) => {
|
||||
let b = stack.pop().ok_or("stack underflow")?;
|
||||
let a = stack.pop().ok_or("stack underflow")?;
|
||||
match (&a, &b) {
|
||||
@@ -648,6 +648,7 @@ impl Forth {
|
||||
(*b_i, *a_i)
|
||||
};
|
||||
let val = self.rng.lock().gen_range(lo..=hi);
|
||||
record_resolved(&trace_cell, *word_span, ResolvedValue::Int(val));
|
||||
stack.push(Value::Int(val, None));
|
||||
}
|
||||
_ => {
|
||||
@@ -659,11 +660,12 @@ impl Forth {
|
||||
} else {
|
||||
self.rng.lock().gen_range(lo..hi)
|
||||
};
|
||||
record_resolved(&trace_cell, *word_span, ResolvedValue::Float(val));
|
||||
stack.push(Value::Float(val, None));
|
||||
}
|
||||
}
|
||||
}
|
||||
Op::ExpRand => {
|
||||
Op::ExpRand(word_span) => {
|
||||
let hi = stack.pop().ok_or("stack underflow")?.as_float()?;
|
||||
let lo = stack.pop().ok_or("stack underflow")?.as_float()?;
|
||||
if lo <= 0.0 || hi <= 0.0 {
|
||||
@@ -672,9 +674,10 @@ impl Forth {
|
||||
let (lo, hi) = if lo <= hi { (lo, hi) } else { (hi, lo) };
|
||||
let u: f64 = self.rng.lock().gen();
|
||||
let val = lo * (hi / lo).powf(u);
|
||||
record_resolved(&trace_cell, *word_span, ResolvedValue::Float(val));
|
||||
stack.push(Value::Float(val, None));
|
||||
}
|
||||
Op::LogRand => {
|
||||
Op::LogRand(word_span) => {
|
||||
let hi = stack.pop().ok_or("stack underflow")?.as_float()?;
|
||||
let lo = stack.pop().ok_or("stack underflow")?.as_float()?;
|
||||
if lo <= 0.0 || hi <= 0.0 {
|
||||
@@ -683,6 +686,7 @@ impl Forth {
|
||||
let (lo, hi) = if lo <= hi { (lo, hi) } else { (hi, lo) };
|
||||
let u: f64 = self.rng.lock().gen();
|
||||
let val = hi * (lo / hi).powf(u);
|
||||
record_resolved(&trace_cell, *word_span, ResolvedValue::Float(val));
|
||||
stack.push(Value::Float(val, None));
|
||||
}
|
||||
Op::Seed => {
|
||||
@@ -690,28 +694,42 @@ impl Forth {
|
||||
*self.rng.lock() = StdRng::seed_from_u64(s as u64);
|
||||
}
|
||||
|
||||
Op::Cycle | Op::PCycle => {
|
||||
Op::Cycle(word_span) | Op::PCycle(word_span) => {
|
||||
let count = stack.pop().ok_or("stack underflow")?.as_int()? as usize;
|
||||
if count == 0 {
|
||||
return Err("cycle count must be > 0".into());
|
||||
}
|
||||
let idx = match &ops[pc] {
|
||||
Op::Cycle => ctx.runs,
|
||||
Op::Cycle(_) => ctx.runs,
|
||||
_ => ctx.iter,
|
||||
} % count;
|
||||
if let Some(span) = word_span {
|
||||
if stack.len() >= count {
|
||||
let start = stack.len() - count;
|
||||
let selected = &stack[start + idx];
|
||||
record_resolved_from_value(&trace_cell, Some(*span), selected);
|
||||
}
|
||||
}
|
||||
drain_select_run(count, idx, stack, outputs, cmd)?;
|
||||
}
|
||||
|
||||
Op::Choose => {
|
||||
Op::Choose(word_span) => {
|
||||
let count = stack.pop().ok_or("stack underflow")?.as_int()? as usize;
|
||||
if count == 0 {
|
||||
return Err("choose count must be > 0".into());
|
||||
}
|
||||
let idx = self.rng.lock().gen_range(0..count);
|
||||
if let Some(span) = word_span {
|
||||
if stack.len() >= count {
|
||||
let start = stack.len() - count;
|
||||
let selected = &stack[start + idx];
|
||||
record_resolved_from_value(&trace_cell, Some(*span), selected);
|
||||
}
|
||||
}
|
||||
drain_select_run(count, idx, stack, outputs, cmd)?;
|
||||
}
|
||||
|
||||
Op::Bounce => {
|
||||
Op::Bounce(word_span) => {
|
||||
let count = stack.pop().ok_or("stack underflow")?.as_int()? as usize;
|
||||
if count == 0 {
|
||||
return Err("bounce count must be > 0".into());
|
||||
@@ -723,10 +741,17 @@ impl Forth {
|
||||
let raw = ctx.runs % period;
|
||||
if raw < count { raw } else { period - raw }
|
||||
};
|
||||
if let Some(span) = word_span {
|
||||
if stack.len() >= count {
|
||||
let start = stack.len() - count;
|
||||
let selected = &stack[start + idx];
|
||||
record_resolved_from_value(&trace_cell, Some(*span), selected);
|
||||
}
|
||||
}
|
||||
drain_select_run(count, idx, stack, outputs, cmd)?;
|
||||
}
|
||||
|
||||
Op::WChoose => {
|
||||
Op::WChoose(word_span) => {
|
||||
let count = stack.pop().ok_or("stack underflow")?.as_int()? as usize;
|
||||
if count == 0 {
|
||||
return Err("wchoose count must be > 0".into());
|
||||
@@ -763,25 +788,30 @@ impl Forth {
|
||||
}
|
||||
}
|
||||
let selected = values.swap_remove(selected_idx);
|
||||
record_resolved_from_value(&trace_cell, *word_span, &selected);
|
||||
select_and_run(selected, stack, outputs, cmd)?;
|
||||
}
|
||||
|
||||
Op::ChanceExec | Op::ProbExec => {
|
||||
Op::ChanceExec(word_span) | Op::ProbExec(word_span) => {
|
||||
let threshold = stack.pop().ok_or("stack underflow")?.as_float()?;
|
||||
let quot = stack.pop().ok_or("stack underflow")?;
|
||||
let val: f64 = self.rng.lock().gen();
|
||||
let limit = match &ops[pc] {
|
||||
Op::ChanceExec => threshold,
|
||||
Op::ChanceExec(_) => threshold,
|
||||
_ => threshold / 100.0,
|
||||
};
|
||||
if val < limit {
|
||||
let fired = val < limit;
|
||||
record_resolved(&trace_cell, *word_span, ResolvedValue::Bool(fired));
|
||||
if fired {
|
||||
run_quotation(quot, stack, outputs, cmd)?;
|
||||
}
|
||||
}
|
||||
|
||||
Op::Coin => {
|
||||
Op::Coin(word_span) => {
|
||||
let val: f64 = self.rng.lock().gen();
|
||||
stack.push(Value::Int(if val < 0.5 { 1 } else { 0 }, None));
|
||||
let result = val < 0.5;
|
||||
record_resolved(&trace_cell, *word_span, ResolvedValue::Bool(result));
|
||||
stack.push(Value::Int(if result { 1 } else { 0 }, None));
|
||||
}
|
||||
|
||||
Op::Every => {
|
||||
@@ -1241,6 +1271,36 @@ impl Forth {
|
||||
}
|
||||
}
|
||||
|
||||
fn record_resolved(
|
||||
trace_cell: &std::cell::RefCell<Option<&mut ExecutionTrace>>,
|
||||
span: Option<SourceSpan>,
|
||||
value: ResolvedValue,
|
||||
) {
|
||||
if let Some(span) = span {
|
||||
if let Some(trace) = trace_cell.borrow_mut().as_mut() {
|
||||
trace.resolved.push((span, value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn record_resolved_from_value(
|
||||
trace_cell: &std::cell::RefCell<Option<&mut ExecutionTrace>>,
|
||||
span: Option<SourceSpan>,
|
||||
value: &Value,
|
||||
) {
|
||||
if let Some(span) = span {
|
||||
let resolved = match value {
|
||||
Value::Int(i, _) => ResolvedValue::Int(*i),
|
||||
Value::Float(f, _) => ResolvedValue::Float(*f),
|
||||
Value::Str(s, _) => ResolvedValue::Str(s.clone()),
|
||||
_ => return,
|
||||
};
|
||||
if let Some(trace) = trace_cell.borrow_mut().as_mut() {
|
||||
trace.resolved.push((span, resolved));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_dev_param(params: &[(&str, Value)]) -> u8 {
|
||||
params
|
||||
.iter()
|
||||
@@ -1323,7 +1383,7 @@ fn emit_output(
|
||||
let _ = write!(&mut out, "delta/{nudge_secs}");
|
||||
}
|
||||
|
||||
if sound.is_some() && !has_dur {
|
||||
if !has_dur {
|
||||
if !out.ends_with('/') {
|
||||
out.push('/');
|
||||
}
|
||||
|
||||
@@ -59,19 +59,19 @@ pub(super) fn simple_op(name: &str) -> Option<Op> {
|
||||
"pick" => Op::Pick,
|
||||
"sound" => Op::NewCmd,
|
||||
"." => Op::Emit,
|
||||
"rand" => Op::Rand,
|
||||
"exprand" => Op::ExpRand,
|
||||
"logrand" => Op::LogRand,
|
||||
"rand" => Op::Rand(None),
|
||||
"exprand" => Op::ExpRand(None),
|
||||
"logrand" => Op::LogRand(None),
|
||||
"seed" => Op::Seed,
|
||||
"cycle" => Op::Cycle,
|
||||
"pcycle" => Op::PCycle,
|
||||
"choose" => Op::Choose,
|
||||
"bounce" => Op::Bounce,
|
||||
"wchoose" => Op::WChoose,
|
||||
"cycle" => Op::Cycle(None),
|
||||
"pcycle" => Op::PCycle(None),
|
||||
"choose" => Op::Choose(None),
|
||||
"bounce" => Op::Bounce(None),
|
||||
"wchoose" => Op::WChoose(None),
|
||||
"every" => Op::Every,
|
||||
"chance" => Op::ChanceExec,
|
||||
"prob" => Op::ProbExec,
|
||||
"coin" => Op::Coin,
|
||||
"chance" => Op::ChanceExec(None),
|
||||
"prob" => Op::ProbExec(None),
|
||||
"coin" => Op::Coin(None),
|
||||
"mtof" => Op::Mtof,
|
||||
"ftom" => Op::Ftom,
|
||||
"?" => Op::When,
|
||||
@@ -187,6 +187,15 @@ fn parse_interval(name: &str) -> Option<i64> {
|
||||
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) => *s = Some(span),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn compile_word(
|
||||
name: &str,
|
||||
span: Option<SourceSpan>,
|
||||
@@ -225,7 +234,10 @@ pub(crate) fn compile_word(
|
||||
if let Some(word) = lookup_word(name) {
|
||||
match &word.compile {
|
||||
Simple => {
|
||||
if let Some(op) = simple_op(word.name) {
|
||||
if let Some(mut op) = simple_op(word.name) {
|
||||
if let Some(sp) = span {
|
||||
attach_span(&mut op, sp);
|
||||
}
|
||||
ops.push(op);
|
||||
}
|
||||
}
|
||||
@@ -233,7 +245,7 @@ pub(crate) fn compile_word(
|
||||
Param => ops.push(Op::SetParam(word.name)),
|
||||
Probability(p) => {
|
||||
ops.push(Op::PushFloat(*p, None));
|
||||
ops.push(Op::ChanceExec);
|
||||
ops.push(Op::ChanceExec(span));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
Reference in New Issue
Block a user