trace
This commit is contained in:
@@ -6,7 +6,7 @@ use std::time::Duration;
|
|||||||
|
|
||||||
use super::LinkState;
|
use super::LinkState;
|
||||||
use crate::config::{MAX_BANKS, MAX_PATTERNS};
|
use crate::config::{MAX_BANKS, MAX_PATTERNS};
|
||||||
use crate::model::{ExecutionTrace, Rng, ScriptEngine, SourceSpan, StepContext, Variables};
|
use crate::model::{ExecutionTrace, Rng, ScriptEngine, StepContext, Variables};
|
||||||
use crate::state::LiveKeyState;
|
use crate::state::LiveKeyState;
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
|
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
|
||||||
@@ -87,13 +87,13 @@ pub struct ActivePatternState {
|
|||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
pub struct SharedSequencerState {
|
pub struct SharedSequencerState {
|
||||||
pub active_patterns: Vec<ActivePatternState>,
|
pub active_patterns: Vec<ActivePatternState>,
|
||||||
pub step_traces: HashMap<(usize, usize, usize), Vec<SourceSpan>>,
|
pub step_traces: HashMap<(usize, usize, usize), ExecutionTrace>,
|
||||||
pub event_count: usize,
|
pub event_count: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SequencerSnapshot {
|
pub struct SequencerSnapshot {
|
||||||
pub active_patterns: Vec<ActivePatternState>,
|
pub active_patterns: Vec<ActivePatternState>,
|
||||||
pub step_traces: HashMap<(usize, usize, usize), Vec<SourceSpan>>,
|
pub step_traces: HashMap<(usize, usize, usize), ExecutionTrace>,
|
||||||
pub event_count: usize,
|
pub event_count: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,7 +118,7 @@ impl SequencerSnapshot {
|
|||||||
.map(|p| p.iter)
|
.map(|p| p.iter)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_trace(&self, bank: usize, pattern: usize, step: usize) -> Option<&Vec<SourceSpan>> {
|
pub fn get_trace(&self, bank: usize, pattern: usize, step: usize) -> Option<&ExecutionTrace> {
|
||||||
self.step_traces.get(&(bank, pattern, step))
|
self.step_traces.get(&(bank, pattern, step))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -302,7 +302,7 @@ fn sequencer_loop(
|
|||||||
let mut audio_state = AudioState::new();
|
let mut audio_state = AudioState::new();
|
||||||
let mut pattern_cache = PatternCache::new();
|
let mut pattern_cache = PatternCache::new();
|
||||||
let mut runs_counter = RunsCounter::new();
|
let mut runs_counter = RunsCounter::new();
|
||||||
let mut step_traces: HashMap<(usize, usize, usize), Vec<SourceSpan>> = HashMap::new();
|
let mut step_traces: HashMap<(usize, usize, usize), ExecutionTrace> = HashMap::new();
|
||||||
let mut event_count: usize = 0;
|
let mut event_count: usize = 0;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
@@ -409,8 +409,8 @@ fn sequencer_loop(
|
|||||||
script_engine.evaluate_with_trace(script, &ctx, &mut trace)
|
script_engine.evaluate_with_trace(script, &ctx, &mut trace)
|
||||||
{
|
{
|
||||||
step_traces.insert(
|
step_traces.insert(
|
||||||
(active.bank, active.pattern, step_idx),
|
(active.bank, active.pattern, source_idx),
|
||||||
std::mem::take(&mut trace.selected_spans),
|
std::mem::take(&mut trace),
|
||||||
);
|
);
|
||||||
for cmd in cmds {
|
for cmd in cmds {
|
||||||
match audio_tx.try_send(AudioCommand::Evaluate(cmd)) {
|
match audio_tx.try_send(AudioCommand::Evaluate(cmd)) {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ pub struct SourceSpan {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct ExecutionTrace {
|
pub struct ExecutionTrace {
|
||||||
|
pub executed_spans: Vec<SourceSpan>,
|
||||||
pub selected_spans: Vec<SourceSpan>,
|
pub selected_spans: Vec<SourceSpan>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,7 +43,7 @@ pub enum Value {
|
|||||||
Float(f64, Option<SourceSpan>),
|
Float(f64, Option<SourceSpan>),
|
||||||
Str(String, Option<SourceSpan>),
|
Str(String, Option<SourceSpan>),
|
||||||
Marker,
|
Marker,
|
||||||
Quotation(Vec<Op>),
|
Quotation(Vec<Op>, Option<SourceSpan>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for Value {
|
impl PartialEq for Value {
|
||||||
@@ -52,7 +53,7 @@ impl PartialEq for Value {
|
|||||||
(Value::Float(a, _), Value::Float(b, _)) => a == b,
|
(Value::Float(a, _), Value::Float(b, _)) => a == b,
|
||||||
(Value::Str(a, _), Value::Str(b, _)) => a == b,
|
(Value::Str(a, _), Value::Str(b, _)) => a == b,
|
||||||
(Value::Marker, Value::Marker) => true,
|
(Value::Marker, Value::Marker) => true,
|
||||||
(Value::Quotation(a), Value::Quotation(b)) => a == b,
|
(Value::Quotation(a, _), Value::Quotation(b, _)) => a == b,
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -110,7 +111,7 @@ impl Value {
|
|||||||
Value::Float(f, _) => *f != 0.0,
|
Value::Float(f, _) => *f != 0.0,
|
||||||
Value::Str(s, _) => !s.is_empty(),
|
Value::Str(s, _) => !s.is_empty(),
|
||||||
Value::Marker => false,
|
Value::Marker => false,
|
||||||
Value::Quotation(_) => true,
|
Value::Quotation(..) => true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,14 +125,14 @@ impl Value {
|
|||||||
Value::Float(f, _) => f.to_string(),
|
Value::Float(f, _) => f.to_string(),
|
||||||
Value::Str(s, _) => s.clone(),
|
Value::Str(s, _) => s.clone(),
|
||||||
Value::Marker => String::new(),
|
Value::Marker => String::new(),
|
||||||
Value::Quotation(_) => String::new(),
|
Value::Quotation(..) => String::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn span(&self) -> Option<SourceSpan> {
|
fn span(&self) -> Option<SourceSpan> {
|
||||||
match self {
|
match self {
|
||||||
Value::Int(_, s) | Value::Float(_, s) | Value::Str(_, s) => *s,
|
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,
|
And,
|
||||||
Or,
|
Or,
|
||||||
Not,
|
Not,
|
||||||
BranchIfZero(usize),
|
BranchIfZero(usize, Option<SourceSpan>, Option<SourceSpan>),
|
||||||
Branch(usize),
|
Branch(usize),
|
||||||
NewCmd,
|
NewCmd,
|
||||||
SetParam(String),
|
SetParam(String),
|
||||||
@@ -201,7 +202,7 @@ pub enum Op {
|
|||||||
SetTempo,
|
SetTempo,
|
||||||
Each,
|
Each,
|
||||||
Every,
|
Every,
|
||||||
Quotation(Vec<Op>),
|
Quotation(Vec<Op>, Option<SourceSpan>),
|
||||||
When,
|
When,
|
||||||
Unless,
|
Unless,
|
||||||
Adsr,
|
Adsr,
|
||||||
@@ -1826,8 +1827,8 @@ enum Token {
|
|||||||
Float(f64, SourceSpan),
|
Float(f64, SourceSpan),
|
||||||
Str(String, SourceSpan),
|
Str(String, SourceSpan),
|
||||||
Word(String, SourceSpan),
|
Word(String, SourceSpan),
|
||||||
QuoteStart,
|
QuoteStart(usize),
|
||||||
QuoteEnd,
|
QuoteEnd(usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tokenize(input: &str) -> Vec<Token> {
|
fn tokenize(input: &str) -> Vec<Token> {
|
||||||
@@ -1869,13 +1870,13 @@ fn tokenize(input: &str) -> Vec<Token> {
|
|||||||
|
|
||||||
if c == '{' {
|
if c == '{' {
|
||||||
chars.next();
|
chars.next();
|
||||||
tokens.push(Token::QuoteStart);
|
tokens.push(Token::QuoteStart(pos));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if c == '}' {
|
if c == '}' {
|
||||||
chars.next();
|
chars.next();
|
||||||
tokens.push(Token::QuoteEnd);
|
tokens.push(Token::QuoteEnd(pos));
|
||||||
continue;
|
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::Int(n, span) => ops.push(Op::PushInt(*n, Some(*span))),
|
||||||
Token::Float(f, span) => ops.push(Op::PushFloat(*f, 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::Str(s, span) => ops.push(Op::PushStr(s.clone(), Some(*span))),
|
||||||
Token::QuoteStart => {
|
Token::QuoteStart(start_pos) => {
|
||||||
let (quote_ops, consumed) = compile_quotation(&tokens[i + 1..])?;
|
let (quote_ops, consumed, end_pos) = compile_quotation(&tokens[i + 1..])?;
|
||||||
i += consumed;
|
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());
|
return Err("unexpected }".into());
|
||||||
}
|
}
|
||||||
Token::Word(w, span) => {
|
Token::Word(w, span) => {
|
||||||
@@ -1932,13 +1934,13 @@ fn compile(tokens: &[Token]) -> Result<Vec<Op>, String> {
|
|||||||
}
|
}
|
||||||
pipe_parity = !pipe_parity;
|
pipe_parity = !pipe_parity;
|
||||||
} else if word == "if" {
|
} 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;
|
i += consumed;
|
||||||
if else_ops.is_empty() {
|
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);
|
ops.extend(then_ops);
|
||||||
} else {
|
} 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.extend(then_ops);
|
||||||
ops.push(Op::Branch(else_ops.len()));
|
ops.push(Op::Branch(else_ops.len()));
|
||||||
ops.extend(else_ops);
|
ops.extend(else_ops);
|
||||||
@@ -1954,17 +1956,17 @@ fn compile(tokens: &[Token]) -> Result<Vec<Op>, String> {
|
|||||||
Ok(ops)
|
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 depth = 1;
|
||||||
let mut end_pos = None;
|
let mut end_idx = None;
|
||||||
|
|
||||||
for (i, tok) in tokens.iter().enumerate() {
|
for (i, tok) in tokens.iter().enumerate() {
|
||||||
match tok {
|
match tok {
|
||||||
Token::QuoteStart => depth += 1,
|
Token::QuoteStart(_) => depth += 1,
|
||||||
Token::QuoteEnd => {
|
Token::QuoteEnd(_) => {
|
||||||
depth -= 1;
|
depth -= 1;
|
||||||
if depth == 0 {
|
if depth == 0 {
|
||||||
end_pos = Some(i);
|
end_idx = Some(i);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1972,12 +1974,31 @@ fn compile_quotation(tokens: &[Token]) -> Result<(Vec<Op>, usize), String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let end_pos = end_pos.ok_or("missing }")?;
|
let end_idx = end_idx.ok_or("missing }")?;
|
||||||
let quote_ops = compile(&tokens[..end_pos])?;
|
let byte_pos = match &tokens[end_idx] {
|
||||||
Ok((quote_ops, end_pos + 1))
|
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 depth = 1;
|
||||||
let mut else_pos = None;
|
let mut else_pos = None;
|
||||||
let mut then_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_pos = then_pos.ok_or("missing 'then'")?;
|
||||||
|
|
||||||
let (then_ops, else_ops) = if let Some(ep) = else_pos {
|
let (then_ops, else_ops, then_span, else_span) = if let Some(ep) = else_pos {
|
||||||
let then_ops = compile(&tokens[..ep])?;
|
let then_slice = &tokens[..ep];
|
||||||
let else_ops = compile(&tokens[ep + 1..then_pos])?;
|
let else_slice = &tokens[ep + 1..then_pos];
|
||||||
(then_ops, else_ops)
|
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 {
|
} else {
|
||||||
let then_ops = compile(&tokens[..then_pos])?;
|
let then_slice = &tokens[..then_pos];
|
||||||
(then_ops, Vec::new())
|
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>>>;
|
pub type Stack = Arc<Mutex<Vec<Value>>>;
|
||||||
@@ -2232,10 +2259,19 @@ impl Forth {
|
|||||||
stack.push(Value::Int(if v { 0 } else { 1 }, None));
|
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")?;
|
let v = stack.pop().ok_or("stack underflow")?;
|
||||||
if !v.is_truthy() {
|
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;
|
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) => {
|
Op::Branch(offset) => {
|
||||||
@@ -2386,7 +2422,12 @@ impl Forth {
|
|||||||
let val: f64 = self.rng.lock().unwrap().gen();
|
let val: f64 = self.rng.lock().unwrap().gen();
|
||||||
if val < prob {
|
if val < prob {
|
||||||
match quot {
|
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();
|
let mut trace_opt = trace_cell.borrow_mut().take();
|
||||||
self.execute_ops("_ops, ctx, stack, outputs, time_stack, cmd, trace_opt.as_deref_mut())?;
|
self.execute_ops("_ops, ctx, stack, outputs, time_stack, cmd, trace_opt.as_deref_mut())?;
|
||||||
*trace_cell.borrow_mut() = trace_opt;
|
*trace_cell.borrow_mut() = trace_opt;
|
||||||
@@ -2402,7 +2443,12 @@ impl Forth {
|
|||||||
let val: f64 = self.rng.lock().unwrap().gen();
|
let val: f64 = self.rng.lock().unwrap().gen();
|
||||||
if val < pct / 100.0 {
|
if val < pct / 100.0 {
|
||||||
match quot {
|
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();
|
let mut trace_opt = trace_cell.borrow_mut().take();
|
||||||
self.execute_ops("_ops, ctx, stack, outputs, time_stack, cmd, trace_opt.as_deref_mut())?;
|
self.execute_ops("_ops, ctx, stack, outputs, time_stack, cmd, trace_opt.as_deref_mut())?;
|
||||||
*trace_cell.borrow_mut() = trace_opt;
|
*trace_cell.borrow_mut() = trace_opt;
|
||||||
@@ -2426,8 +2472,8 @@ impl Forth {
|
|||||||
stack.push(Value::Int(if result { 1 } else { 0 }, None));
|
stack.push(Value::Int(if result { 1 } else { 0 }, None));
|
||||||
}
|
}
|
||||||
|
|
||||||
Op::Quotation(quote_ops) => {
|
Op::Quotation(quote_ops, body_span) => {
|
||||||
stack.push(Value::Quotation(quote_ops.clone()));
|
stack.push(Value::Quotation(quote_ops.clone(), *body_span));
|
||||||
}
|
}
|
||||||
|
|
||||||
Op::When => {
|
Op::When => {
|
||||||
@@ -2435,7 +2481,12 @@ impl Forth {
|
|||||||
let quot = stack.pop().ok_or("stack underflow")?;
|
let quot = stack.pop().ok_or("stack underflow")?;
|
||||||
if cond.is_truthy() {
|
if cond.is_truthy() {
|
||||||
match quot {
|
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();
|
let mut trace_opt = trace_cell.borrow_mut().take();
|
||||||
self.execute_ops("_ops, ctx, stack, outputs, time_stack, cmd, trace_opt.as_deref_mut())?;
|
self.execute_ops("_ops, ctx, stack, outputs, time_stack, cmd, trace_opt.as_deref_mut())?;
|
||||||
*trace_cell.borrow_mut() = trace_opt;
|
*trace_cell.borrow_mut() = trace_opt;
|
||||||
@@ -2450,7 +2501,12 @@ impl Forth {
|
|||||||
let quot = stack.pop().ok_or("stack underflow")?;
|
let quot = stack.pop().ok_or("stack underflow")?;
|
||||||
if !cond.is_truthy() {
|
if !cond.is_truthy() {
|
||||||
match quot {
|
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();
|
let mut trace_opt = trace_cell.borrow_mut().take();
|
||||||
self.execute_ops("_ops, ctx, stack, outputs, time_stack, cmd, trace_opt.as_deref_mut())?;
|
self.execute_ops("_ops, ctx, stack, outputs, time_stack, cmd, trace_opt.as_deref_mut())?;
|
||||||
*trace_cell.borrow_mut() = trace_opt;
|
*trace_cell.borrow_mut() = trace_opt;
|
||||||
@@ -2715,7 +2771,12 @@ impl Forth {
|
|||||||
.ok_or("for requires subdivide first")?;
|
.ok_or("for requires subdivide first")?;
|
||||||
|
|
||||||
match quot {
|
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() {
|
for (i, (sub_start, sub_dur)) in subs.iter().enumerate() {
|
||||||
time_stack.push(TimeContext {
|
time_stack.push(TimeContext {
|
||||||
start: *sub_start,
|
start: *sub_start,
|
||||||
|
|||||||
@@ -175,22 +175,36 @@ const INTERVALS: &[&str] = &[
|
|||||||
"M14", "P15",
|
"M14", "P15",
|
||||||
];
|
];
|
||||||
|
|
||||||
fn is_note(word: &str) -> bool {
|
const NOTES: &[&str] = &[
|
||||||
let bytes = word.as_bytes();
|
"c0", "c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8", "c9",
|
||||||
if bytes.len() < 2 {
|
"d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9",
|
||||||
return false;
|
"e0", "e1", "e2", "e3", "e4", "e5", "e6", "e7", "e8", "e9",
|
||||||
}
|
"f0", "f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8", "f9",
|
||||||
if !matches!(bytes[0], b'a'..=b'g' | b'A'..=b'G') {
|
"g0", "g1", "g2", "g3", "g4", "g5", "g6", "g7", "g8", "g9",
|
||||||
return false;
|
"a0", "a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9",
|
||||||
}
|
"b0", "b1", "b2", "b3", "b4", "b5", "b6", "b7", "b8", "b9",
|
||||||
let rest = &bytes[1..];
|
"cs0", "cs1", "cs2", "cs3", "cs4", "cs5", "cs6", "cs7", "cs8", "cs9",
|
||||||
let digits_start = if rest.first().is_some_and(|&b| b == b'#' || b == b's' || b == b'b') {
|
"ds0", "ds1", "ds2", "ds3", "ds4", "ds5", "ds6", "ds7", "ds8", "ds9",
|
||||||
1
|
"es0", "es1", "es2", "es3", "es4", "es5", "es6", "es7", "es8", "es9",
|
||||||
} else {
|
"fs0", "fs1", "fs2", "fs3", "fs4", "fs5", "fs6", "fs7", "fs8", "fs9",
|
||||||
0
|
"gs0", "gs1", "gs2", "gs3", "gs4", "gs5", "gs6", "gs7", "gs8", "gs9",
|
||||||
};
|
"as0", "as1", "as2", "as3", "as4", "as5", "as6", "as7", "as8", "as9",
|
||||||
rest[digits_start..].iter().all(|&b| b.is_ascii_digit()) && digits_start < rest.len()
|
"bs0", "bs1", "bs2", "bs3", "bs4", "bs5", "bs6", "bs7", "bs8", "bs9",
|
||||||
}
|
"cb0", "cb1", "cb2", "cb3", "cb4", "cb5", "cb6", "cb7", "cb8", "cb9",
|
||||||
|
"db0", "db1", "db2", "db3", "db4", "db5", "db6", "db7", "db8", "db9",
|
||||||
|
"eb0", "eb1", "eb2", "eb3", "eb4", "eb5", "eb6", "eb7", "eb8", "eb9",
|
||||||
|
"fb0", "fb1", "fb2", "fb3", "fb4", "fb5", "fb6", "fb7", "fb8", "fb9",
|
||||||
|
"gb0", "gb1", "gb2", "gb3", "gb4", "gb5", "gb6", "gb7", "gb8", "gb9",
|
||||||
|
"ab0", "ab1", "ab2", "ab3", "ab4", "ab5", "ab6", "ab7", "ab8", "ab9",
|
||||||
|
"bb0", "bb1", "bb2", "bb3", "bb4", "bb5", "bb6", "bb7", "bb8", "bb9",
|
||||||
|
"c#0", "c#1", "c#2", "c#3", "c#4", "c#5", "c#6", "c#7", "c#8", "c#9",
|
||||||
|
"d#0", "d#1", "d#2", "d#3", "d#4", "d#5", "d#6", "d#7", "d#8", "d#9",
|
||||||
|
"e#0", "e#1", "e#2", "e#3", "e#4", "e#5", "e#6", "e#7", "e#8", "e#9",
|
||||||
|
"f#0", "f#1", "f#2", "f#3", "f#4", "f#5", "f#6", "f#7", "f#8", "f#9",
|
||||||
|
"g#0", "g#1", "g#2", "g#3", "g#4", "g#5", "g#6", "g#7", "g#8", "g#9",
|
||||||
|
"a#0", "a#1", "a#2", "a#3", "a#4", "a#5", "a#6", "a#7", "a#8", "a#9",
|
||||||
|
"b#0", "b#1", "b#2", "b#3", "b#4", "b#5", "b#6", "b#7", "b#8", "b#9",
|
||||||
|
];
|
||||||
|
|
||||||
pub fn tokenize_line(line: &str) -> Vec<Token> {
|
pub fn tokenize_line(line: &str) -> Vec<Token> {
|
||||||
let mut tokens = Vec::new();
|
let mut tokens = Vec::new();
|
||||||
@@ -287,7 +301,8 @@ fn classify_word(word: &str) -> TokenKind {
|
|||||||
return TokenKind::Interval;
|
return TokenKind::Interval;
|
||||||
}
|
}
|
||||||
|
|
||||||
if is_note(word) {
|
let lower = word.to_ascii_lowercase();
|
||||||
|
if NOTES.contains(&lower.as_str()) {
|
||||||
return TokenKind::Note;
|
return TokenKind::Note;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,15 +314,20 @@ fn classify_word(word: &str) -> TokenKind {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn highlight_line(line: &str) -> Vec<(Style, String)> {
|
pub fn highlight_line(line: &str) -> Vec<(Style, String)> {
|
||||||
highlight_line_with_runtime(line, &[])
|
highlight_line_with_runtime(line, &[], &[])
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn highlight_line_with_runtime(line: &str, runtime_spans: &[SourceSpan]) -> Vec<(Style, String)> {
|
pub fn highlight_line_with_runtime(
|
||||||
|
line: &str,
|
||||||
|
executed_spans: &[SourceSpan],
|
||||||
|
selected_spans: &[SourceSpan],
|
||||||
|
) -> Vec<(Style, String)> {
|
||||||
let tokens = tokenize_line(line);
|
let tokens = tokenize_line(line);
|
||||||
let mut result = Vec::new();
|
let mut result = Vec::new();
|
||||||
let mut last_end = 0;
|
let mut last_end = 0;
|
||||||
|
|
||||||
let runtime_bg = Color::Rgb(80, 60, 20);
|
let executed_bg = Color::Rgb(40, 35, 50);
|
||||||
|
let selected_bg = Color::Rgb(80, 60, 20);
|
||||||
|
|
||||||
for token in tokens {
|
for token in tokens {
|
||||||
if token.start > last_end {
|
if token.start > last_end {
|
||||||
@@ -317,13 +337,18 @@ pub fn highlight_line_with_runtime(line: &str, runtime_spans: &[SourceSpan]) ->
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let is_runtime = runtime_spans
|
let is_selected = selected_spans
|
||||||
|
.iter()
|
||||||
|
.any(|span| overlaps(token.start, token.end, span.start, span.end));
|
||||||
|
let is_executed = executed_spans
|
||||||
.iter()
|
.iter()
|
||||||
.any(|span| overlaps(token.start, token.end, span.start, span.end));
|
.any(|span| overlaps(token.start, token.end, span.start, span.end));
|
||||||
|
|
||||||
let mut style = token.kind.style();
|
let mut style = token.kind.style();
|
||||||
if is_runtime {
|
if is_selected {
|
||||||
style = style.bg(runtime_bg).add_modifier(Modifier::BOLD);
|
style = style.bg(selected_bg).add_modifier(Modifier::BOLD);
|
||||||
|
} else if is_executed {
|
||||||
|
style = style.bg(executed_bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
result.push((style, line[token.start..token.end].to_string()));
|
result.push((style, line[token.start..token.end].to_string()));
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use ratatui::Frame;
|
|||||||
|
|
||||||
use crate::app::App;
|
use crate::app::App;
|
||||||
use crate::engine::{LinkState, SequencerSnapshot};
|
use crate::engine::{LinkState, SequencerSnapshot};
|
||||||
use crate::model::forth::SourceSpan;
|
use crate::model::SourceSpan;
|
||||||
use crate::page::Page;
|
use crate::page::Page;
|
||||||
use crate::state::{Modal, PatternField};
|
use crate::state::{Modal, PatternField};
|
||||||
use crate::views::highlight::{self, highlight_line, highlight_line_with_runtime};
|
use crate::views::highlight::{self, highlight_line, highlight_line_with_runtime};
|
||||||
@@ -14,6 +14,18 @@ use crate::widgets::{ConfirmModal, ModalFrame, TextInputModal};
|
|||||||
|
|
||||||
use super::{audio_view, doc_view, main_view, patterns_view, title_view};
|
use super::{audio_view, doc_view, main_view, patterns_view, title_view};
|
||||||
|
|
||||||
|
fn adjust_spans_for_line(spans: &[SourceSpan], line_start: usize, line_len: usize) -> Vec<SourceSpan> {
|
||||||
|
spans.iter().filter_map(|s| {
|
||||||
|
if s.end <= line_start || s.start >= line_start + line_len {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(SourceSpan {
|
||||||
|
start: s.start.max(line_start) - line_start,
|
||||||
|
end: (s.end.min(line_start + line_len)) - line_start,
|
||||||
|
})
|
||||||
|
}).collect()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn render(frame: &mut Frame, app: &mut App, link: &LinkState, snapshot: &SequencerSnapshot) {
|
pub fn render(frame: &mut Frame, app: &mut App, link: &LinkState, snapshot: &SequencerSnapshot) {
|
||||||
let term = frame.area();
|
let term = frame.area();
|
||||||
let blank = " ".repeat(term.width as usize);
|
let blank = " ".repeat(term.width as usize);
|
||||||
@@ -393,41 +405,25 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term
|
|||||||
};
|
};
|
||||||
frame.render_widget(empty, centered_area);
|
frame.render_widget(empty, centered_area);
|
||||||
} else {
|
} else {
|
||||||
let runtime_spans = if app.ui.runtime_highlight && app.playback.playing {
|
let trace = if app.ui.runtime_highlight && app.playback.playing {
|
||||||
snapshot.get_trace(
|
let source = pattern.resolve_source(step_idx);
|
||||||
app.editor_ctx.bank,
|
snapshot.get_trace(app.editor_ctx.bank, app.editor_ctx.pattern, source)
|
||||||
app.editor_ctx.pattern,
|
|
||||||
step_idx,
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut offset = 0usize;
|
let mut line_start = 0usize;
|
||||||
let lines: Vec<Line> = script
|
let lines: Vec<Line> = script
|
||||||
.lines()
|
.lines()
|
||||||
.map(|line_str| {
|
.map(|line_str| {
|
||||||
let tokens = if let Some(traces) = runtime_spans {
|
let tokens = if let Some(t) = trace {
|
||||||
let shifted: Vec<_> = traces
|
let exec = adjust_spans_for_line(&t.executed_spans, line_start, line_str.len());
|
||||||
.iter()
|
let sel = adjust_spans_for_line(&t.selected_spans, line_start, line_str.len());
|
||||||
.filter_map(|s| {
|
highlight_line_with_runtime(line_str, &exec, &sel)
|
||||||
let start = s.start.saturating_sub(offset);
|
|
||||||
let end = s.end.saturating_sub(offset);
|
|
||||||
if end > 0 && start < line_str.len() {
|
|
||||||
Some(SourceSpan {
|
|
||||||
start: start.min(line_str.len()),
|
|
||||||
end: end.min(line_str.len()),
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
highlight_line_with_runtime(line_str, &shifted)
|
|
||||||
} else {
|
} else {
|
||||||
highlight_line(line_str)
|
highlight_line(line_str)
|
||||||
};
|
};
|
||||||
offset += line_str.len() + 1;
|
line_start += line_str.len() + 1;
|
||||||
let spans: Vec<Span> = tokens
|
let spans: Vec<Span> = tokens
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(style, text)| Span::styled(text, style))
|
.map(|(style, text)| Span::styled(text, style))
|
||||||
@@ -459,8 +455,9 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term
|
|||||||
|
|
||||||
let (cursor_row, cursor_col) = app.editor_ctx.text.cursor();
|
let (cursor_row, cursor_col) = app.editor_ctx.text.cursor();
|
||||||
|
|
||||||
let runtime_spans = if app.ui.runtime_highlight && app.playback.playing {
|
let trace = if app.ui.runtime_highlight && app.playback.playing {
|
||||||
snapshot.get_trace(app.editor_ctx.bank, app.editor_ctx.pattern, app.editor_ctx.step)
|
let source = app.current_edit_pattern().resolve_source(app.editor_ctx.step);
|
||||||
|
snapshot.get_trace(app.editor_ctx.bank, app.editor_ctx.pattern, source)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
@@ -480,25 +477,16 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term
|
|||||||
let mut spans: Vec<Span> = Vec::new();
|
let mut spans: Vec<Span> = Vec::new();
|
||||||
|
|
||||||
let line_start = line_offsets[row];
|
let line_start = line_offsets[row];
|
||||||
let line_end = line_start + line.len();
|
let (exec_spans, sel_spans) = if let Some(t) = trace {
|
||||||
let adjusted_spans: Vec<crate::model::SourceSpan> = runtime_spans
|
(
|
||||||
.map(|rs| {
|
adjust_spans_for_line(&t.executed_spans, line_start, line.len()),
|
||||||
rs.iter()
|
adjust_spans_for_line(&t.selected_spans, line_start, line.len()),
|
||||||
.filter_map(|s| {
|
)
|
||||||
if s.start < line_end && s.end > line_start {
|
|
||||||
Some(crate::model::SourceSpan {
|
|
||||||
start: s.start.saturating_sub(line_start),
|
|
||||||
end: s.end.saturating_sub(line_start).min(line.len()),
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
None
|
(Vec::new(), Vec::new())
|
||||||
}
|
};
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
})
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
let tokens = highlight::highlight_line_with_runtime(line, &adjusted_spans);
|
let tokens = highlight::highlight_line_with_runtime(line, &exec_spans, &sel_spans);
|
||||||
|
|
||||||
if row == cursor_row {
|
if row == cursor_row {
|
||||||
let mut col = 0;
|
let mut col = 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user