This commit is contained in:
2026-01-23 10:37:48 +01:00
parent 1bb5ba0061
commit a88904ed0f
4 changed files with 192 additions and 118 deletions

View File

@@ -11,6 +11,7 @@ pub struct SourceSpan {
#[derive(Clone, Debug, Default)]
pub struct ExecutionTrace {
pub executed_spans: Vec<SourceSpan>,
pub selected_spans: Vec<SourceSpan>,
}
@@ -42,7 +43,7 @@ pub enum Value {
Float(f64, Option<SourceSpan>),
Str(String, Option<SourceSpan>),
Marker,
Quotation(Vec<Op>),
Quotation(Vec<Op>, Option<SourceSpan>),
}
impl PartialEq for Value {
@@ -52,7 +53,7 @@ impl PartialEq for Value {
(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::Quotation(a, _), Value::Quotation(b, _)) => a == b,
_ => false,
}
}
@@ -110,7 +111,7 @@ impl Value {
Value::Float(f, _) => *f != 0.0,
Value::Str(s, _) => !s.is_empty(),
Value::Marker => false,
Value::Quotation(_) => true,
Value::Quotation(..) => true,
}
}
@@ -124,14 +125,14 @@ impl Value {
Value::Float(f, _) => f.to_string(),
Value::Str(s, _) => s.clone(),
Value::Marker => String::new(),
Value::Quotation(_) => String::new(),
Value::Quotation(..) => String::new(),
}
}
fn span(&self) -> Option<SourceSpan> {
match self {
Value::Int(_, s) | Value::Float(_, s) | Value::Str(_, s) => *s,
Value::Marker | Value::Quotation(_) => None,
Value::Marker | Value::Quotation(..) => None,
}
}
}
@@ -170,7 +171,7 @@ pub enum Op {
And,
Or,
Not,
BranchIfZero(usize),
BranchIfZero(usize, Option<SourceSpan>, Option<SourceSpan>),
Branch(usize),
NewCmd,
SetParam(String),
@@ -201,7 +202,7 @@ pub enum Op {
SetTempo,
Each,
Every,
Quotation(Vec<Op>),
Quotation(Vec<Op>, Option<SourceSpan>),
When,
Unless,
Adsr,
@@ -1826,8 +1827,8 @@ enum Token {
Float(f64, SourceSpan),
Str(String, SourceSpan),
Word(String, SourceSpan),
QuoteStart,
QuoteEnd,
QuoteStart(usize),
QuoteEnd(usize),
}
fn tokenize(input: &str) -> Vec<Token> {
@@ -1869,13 +1870,13 @@ fn tokenize(input: &str) -> Vec<Token> {
if c == '{' {
chars.next();
tokens.push(Token::QuoteStart);
tokens.push(Token::QuoteStart(pos));
continue;
}
if c == '}' {
chars.next();
tokens.push(Token::QuoteEnd);
tokens.push(Token::QuoteEnd(pos));
continue;
}
@@ -1914,12 +1915,13 @@ fn compile(tokens: &[Token]) -> Result<Vec<Op>, String> {
Token::Int(n, span) => ops.push(Op::PushInt(*n, Some(*span))),
Token::Float(f, span) => ops.push(Op::PushFloat(*f, Some(*span))),
Token::Str(s, span) => ops.push(Op::PushStr(s.clone(), Some(*span))),
Token::QuoteStart => {
let (quote_ops, consumed) = compile_quotation(&tokens[i + 1..])?;
Token::QuoteStart(start_pos) => {
let (quote_ops, consumed, end_pos) = compile_quotation(&tokens[i + 1..])?;
i += consumed;
ops.push(Op::Quotation(quote_ops));
let body_span = SourceSpan { start: *start_pos, end: end_pos + 1 };
ops.push(Op::Quotation(quote_ops, Some(body_span)));
}
Token::QuoteEnd => {
Token::QuoteEnd(_) => {
return Err("unexpected }".into());
}
Token::Word(w, span) => {
@@ -1932,13 +1934,13 @@ fn compile(tokens: &[Token]) -> Result<Vec<Op>, String> {
}
pipe_parity = !pipe_parity;
} else if word == "if" {
let (then_ops, else_ops, consumed) = compile_if(&tokens[i + 1..])?;
let (then_ops, else_ops, consumed, then_span, else_span) = compile_if(&tokens[i + 1..])?;
i += consumed;
if else_ops.is_empty() {
ops.push(Op::BranchIfZero(then_ops.len()));
ops.push(Op::BranchIfZero(then_ops.len(), then_span, None));
ops.extend(then_ops);
} else {
ops.push(Op::BranchIfZero(then_ops.len() + 1));
ops.push(Op::BranchIfZero(then_ops.len() + 1, then_span, else_span));
ops.extend(then_ops);
ops.push(Op::Branch(else_ops.len()));
ops.extend(else_ops);
@@ -1954,17 +1956,17 @@ fn compile(tokens: &[Token]) -> Result<Vec<Op>, String> {
Ok(ops)
}
fn compile_quotation(tokens: &[Token]) -> Result<(Vec<Op>, usize), String> {
fn compile_quotation(tokens: &[Token]) -> Result<(Vec<Op>, usize, usize), String> {
let mut depth = 1;
let mut end_pos = None;
let mut end_idx = None;
for (i, tok) in tokens.iter().enumerate() {
match tok {
Token::QuoteStart => depth += 1,
Token::QuoteEnd => {
Token::QuoteStart(_) => depth += 1,
Token::QuoteEnd(_) => {
depth -= 1;
if depth == 0 {
end_pos = Some(i);
end_idx = Some(i);
break;
}
}
@@ -1972,12 +1974,31 @@ fn compile_quotation(tokens: &[Token]) -> Result<(Vec<Op>, usize), String> {
}
}
let end_pos = end_pos.ok_or("missing }")?;
let quote_ops = compile(&tokens[..end_pos])?;
Ok((quote_ops, end_pos + 1))
let end_idx = end_idx.ok_or("missing }")?;
let byte_pos = match &tokens[end_idx] {
Token::QuoteEnd(pos) => *pos,
_ => unreachable!(),
};
let quote_ops = compile(&tokens[..end_idx])?;
Ok((quote_ops, end_idx + 1, byte_pos))
}
fn compile_if(tokens: &[Token]) -> Result<(Vec<Op>, Vec<Op>, usize), String> {
fn token_span(tok: &Token) -> Option<SourceSpan> {
match tok {
Token::Int(_, s) | Token::Float(_, s) | Token::Str(_, s) | Token::Word(_, s) => Some(*s),
Token::QuoteStart(p) => Some(SourceSpan { start: *p, end: *p + 1 }),
Token::QuoteEnd(p) => Some(SourceSpan { start: *p, end: *p + 1 }),
}
}
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 })
}
#[allow(clippy::type_complexity)]
fn compile_if(tokens: &[Token]) -> 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;
@@ -2001,16 +2022,22 @@ fn compile_if(tokens: &[Token]) -> Result<(Vec<Op>, Vec<Op>, usize), String> {
let then_pos = then_pos.ok_or("missing 'then'")?;
let (then_ops, else_ops) = if let Some(ep) = else_pos {
let then_ops = compile(&tokens[..ep])?;
let else_ops = compile(&tokens[ep + 1..then_pos])?;
(then_ops, else_ops)
let (then_ops, else_ops, then_span, else_span) = if let Some(ep) = else_pos {
let then_slice = &tokens[..ep];
let else_slice = &tokens[ep + 1..then_pos];
let then_span = tokens_span(then_slice);
let else_span = tokens_span(else_slice);
let then_ops = compile(then_slice)?;
let else_ops = compile(else_slice)?;
(then_ops, else_ops, then_span, else_span)
} else {
let then_ops = compile(&tokens[..then_pos])?;
(then_ops, Vec::new())
let then_slice = &tokens[..then_pos];
let then_span = tokens_span(then_slice);
let then_ops = compile(then_slice)?;
(then_ops, Vec::new(), then_span, None)
};
Ok((then_ops, else_ops, then_pos + 1))
Ok((then_ops, else_ops, then_pos + 1, then_span, else_span))
}
pub type Stack = Arc<Mutex<Vec<Value>>>;
@@ -2232,10 +2259,19 @@ impl Forth {
stack.push(Value::Int(if v { 0 } else { 1 }, None));
}
Op::BranchIfZero(offset) => {
Op::BranchIfZero(offset, then_span, else_span) => {
let v = stack.pop().ok_or("stack underflow")?;
if !v.is_truthy() {
if let Some(span) = else_span {
if let Some(trace) = trace_cell.borrow_mut().as_mut() {
trace.executed_spans.push(*span);
}
}
pc += offset;
} else if let Some(span) = then_span {
if let Some(trace) = trace_cell.borrow_mut().as_mut() {
trace.executed_spans.push(*span);
}
}
}
Op::Branch(offset) => {
@@ -2386,7 +2422,12 @@ impl Forth {
let val: f64 = self.rng.lock().unwrap().gen();
if val < prob {
match quot {
Value::Quotation(quot_ops) => {
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(&quot_ops, ctx, stack, outputs, time_stack, cmd, trace_opt.as_deref_mut())?;
*trace_cell.borrow_mut() = trace_opt;
@@ -2402,7 +2443,12 @@ impl Forth {
let val: f64 = self.rng.lock().unwrap().gen();
if val < pct / 100.0 {
match quot {
Value::Quotation(quot_ops) => {
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(&quot_ops, ctx, stack, outputs, time_stack, cmd, trace_opt.as_deref_mut())?;
*trace_cell.borrow_mut() = trace_opt;
@@ -2426,8 +2472,8 @@ impl Forth {
stack.push(Value::Int(if result { 1 } else { 0 }, None));
}
Op::Quotation(quote_ops) => {
stack.push(Value::Quotation(quote_ops.clone()));
Op::Quotation(quote_ops, body_span) => {
stack.push(Value::Quotation(quote_ops.clone(), *body_span));
}
Op::When => {
@@ -2435,7 +2481,12 @@ impl Forth {
let quot = stack.pop().ok_or("stack underflow")?;
if cond.is_truthy() {
match quot {
Value::Quotation(quot_ops) => {
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(&quot_ops, ctx, stack, outputs, time_stack, cmd, trace_opt.as_deref_mut())?;
*trace_cell.borrow_mut() = trace_opt;
@@ -2450,7 +2501,12 @@ impl Forth {
let quot = stack.pop().ok_or("stack underflow")?;
if !cond.is_truthy() {
match quot {
Value::Quotation(quot_ops) => {
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(&quot_ops, ctx, stack, outputs, time_stack, cmd, trace_opt.as_deref_mut())?;
*trace_cell.borrow_mut() = trace_opt;
@@ -2715,7 +2771,12 @@ impl Forth {
.ok_or("for requires subdivide first")?;
match quot {
Value::Quotation(quot_ops) => {
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);
}
}
for (i, (sub_start, sub_dur)) in subs.iter().enumerate() {
time_stack.push(TimeContext {
start: *sub_start,