words definition

This commit is contained in:
2026-01-23 11:15:15 +01:00
parent a88904ed0f
commit 74f178f271
10 changed files with 237 additions and 27 deletions

View File

@@ -35,6 +35,7 @@ impl StepContext {
}
pub type Variables = Arc<Mutex<HashMap<String, Value>>>;
pub type Dictionary = Arc<Mutex<HashMap<String, Vec<Op>>>>;
pub type Rng = Arc<Mutex<StdRng>>;
#[derive(Clone, Debug)]
@@ -212,6 +213,7 @@ pub enum Op {
LocalCycleEnd,
Echo,
Necho,
Apply,
}
pub enum WordCompile {
@@ -1606,6 +1608,29 @@ pub const WORDS: &[Word] = &[
example: "1 reset",
compile: Param,
},
// Quotation execution
Word {
name: "apply",
stack: "(quot --)",
desc: "Execute quotation unconditionally",
example: "{ 2 * } apply",
compile: Simple,
},
// Word definitions
Word {
name: ":",
stack: "( -- )",
desc: "Begin word definition",
example: ": kick \"kick\" s emit ;",
compile: Simple,
},
Word {
name: ";",
stack: "( -- )",
desc: "End word definition",
example: ": kick \"kick\" s emit ;",
compile: Simple,
},
];
fn simple_op(name: &str) -> Option<Op> {
@@ -1672,6 +1697,7 @@ fn simple_op(name: &str) -> Option<Op> {
"for" => Op::For,
"echo" => Op::Echo,
"necho" => Op::Necho,
"apply" => Op::Apply,
_ => return None,
})
}
@@ -1751,7 +1777,7 @@ fn parse_interval(name: &str) -> Option<i64> {
Some(simple)
}
fn compile_word(name: &str, span: Option<SourceSpan>, ops: &mut Vec<Op>) -> bool {
fn compile_word(name: &str, span: Option<SourceSpan>, ops: &mut Vec<Op>, dict: &Dictionary) -> bool {
for word in WORDS {
if word.name == name {
match &word.compile {
@@ -1762,7 +1788,7 @@ fn compile_word(name: &str, span: Option<SourceSpan>, ops: &mut Vec<Op>) -> bool
}
Context(ctx) => ops.push(Op::GetContext((*ctx).into())),
Param => ops.push(Op::SetParam(name.into())),
Alias(target) => return compile_word(target, span, ops),
Alias(target) => return compile_word(target, span, ops, dict),
Probability(p) => {
ops.push(Op::PushFloat(*p, None));
ops.push(Op::ChanceExec);
@@ -1810,6 +1836,12 @@ fn compile_word(name: &str, span: Option<SourceSpan>, ops: &mut Vec<Op>) -> bool
return true;
}
// User-defined words from dictionary
if let Some(body) = dict.lock().unwrap().get(name) {
ops.extend(body.iter().cloned());
return true;
}
false
}
@@ -1905,7 +1937,7 @@ fn tokenize(input: &str) -> Vec<Token> {
tokens
}
fn compile(tokens: &[Token]) -> Result<Vec<Op>, String> {
fn compile(tokens: &[Token], dict: &Dictionary) -> Result<Vec<Op>, String> {
let mut ops = Vec::new();
let mut i = 0;
let mut pipe_parity = false;
@@ -1916,7 +1948,7 @@ fn compile(tokens: &[Token]) -> Result<Vec<Op>, String> {
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(start_pos) => {
let (quote_ops, consumed, end_pos) = compile_quotation(&tokens[i + 1..])?;
let (quote_ops, consumed, end_pos) = compile_quotation(&tokens[i + 1..], dict)?;
i += consumed;
let body_span = SourceSpan { start: *start_pos, end: end_pos + 1 };
ops.push(Op::Quotation(quote_ops, Some(body_span)));
@@ -1926,7 +1958,13 @@ fn compile(tokens: &[Token]) -> Result<Vec<Op>, String> {
}
Token::Word(w, span) => {
let word = w.as_str();
if word == "|" {
if word == ":" {
let (consumed, name, body) = compile_colon_def(&tokens[i + 1..], dict)?;
i += consumed;
dict.lock().unwrap().insert(name, body);
} else if word == ";" {
return Err("unexpected ;".into());
} else if word == "|" {
if pipe_parity {
ops.push(Op::LocalCycleEnd);
} else {
@@ -1934,7 +1972,7 @@ fn compile(tokens: &[Token]) -> Result<Vec<Op>, String> {
}
pipe_parity = !pipe_parity;
} else if word == "if" {
let (then_ops, else_ops, consumed, then_span, else_span) = compile_if(&tokens[i + 1..])?;
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));
@@ -1945,7 +1983,7 @@ fn compile(tokens: &[Token]) -> Result<Vec<Op>, String> {
ops.push(Op::Branch(else_ops.len()));
ops.extend(else_ops);
}
} else if !compile_word(word, Some(*span), &mut ops) {
} else if !compile_word(word, Some(*span), &mut ops, dict) {
return Err(format!("unknown word: {word}"));
}
}
@@ -1956,7 +1994,7 @@ fn compile(tokens: &[Token]) -> Result<Vec<Op>, String> {
Ok(ops)
}
fn compile_quotation(tokens: &[Token]) -> Result<(Vec<Op>, usize, usize), String> {
fn compile_quotation(tokens: &[Token], dict: &Dictionary) -> Result<(Vec<Op>, usize, usize), String> {
let mut depth = 1;
let mut end_idx = None;
@@ -1979,7 +2017,7 @@ fn compile_quotation(tokens: &[Token]) -> Result<(Vec<Op>, usize, usize), String
Token::QuoteEnd(pos) => *pos,
_ => unreachable!(),
};
let quote_ops = compile(&tokens[..end_idx])?;
let quote_ops = compile(&tokens[..end_idx], dict)?;
Ok((quote_ops, end_idx + 1, byte_pos))
}
@@ -1991,6 +2029,30 @@ fn token_span(tok: &Token) -> Option<SourceSpan> {
}
}
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()),
};
let mut semi_pos = None;
for (i, tok) in tokens[1..].iter().enumerate() {
if let Token::Word(w, _) = tok {
if w == ";" {
semi_pos = Some(i + 1);
break;
}
}
}
let semi_pos = semi_pos.ok_or("missing ';' in word definition")?;
let body_tokens = &tokens[1..semi_pos];
let body_ops = compile(body_tokens, dict)?;
// consumed = name + body + semicolon
Ok((semi_pos + 1, name, body_ops))
}
fn tokens_span(tokens: &[Token]) -> Option<SourceSpan> {
let first = tokens.first().and_then(token_span)?;
let last = tokens.last().and_then(token_span)?;
@@ -1998,7 +2060,7 @@ fn tokens_span(tokens: &[Token]) -> Option<SourceSpan> {
}
#[allow(clippy::type_complexity)]
fn compile_if(tokens: &[Token]) -> 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;
@@ -2027,13 +2089,13 @@ fn compile_if(tokens: &[Token]) -> Result<(Vec<Op>, Vec<Op>, usize, Option<Sourc
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)?;
let then_ops = compile(then_slice, dict)?;
let else_ops = compile(else_slice, dict)?;
(then_ops, else_ops, then_span, else_span)
} else {
let then_slice = &tokens[..then_pos];
let then_span = tokens_span(then_slice);
let then_ops = compile(then_slice)?;
let then_ops = compile(then_slice, dict)?;
(then_ops, Vec::new(), then_span, None)
};
@@ -2045,14 +2107,16 @@ pub type Stack = Arc<Mutex<Vec<Value>>>;
pub struct Forth {
stack: Stack,
vars: Variables,
dict: Dictionary,
rng: Rng,
}
impl Forth {
pub fn new(vars: Variables, rng: Rng) -> Self {
pub fn new(vars: Variables, dict: Dictionary, rng: Rng) -> Self {
Self {
stack: Arc::new(Mutex::new(Vec::new())),
vars,
dict,
rng,
}
}
@@ -2091,7 +2155,7 @@ impl Forth {
}
let tokens = tokenize(script);
let ops = compile(&tokens)?;
let ops = compile(&tokens, &self.dict)?;
self.execute(&ops, ctx, trace)
}
@@ -2824,6 +2888,23 @@ impl Forth {
}
stack.push(selected);
}
Op::Apply => {
let quot = stack.pop().ok_or("stack underflow")?;
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(&quot_ops, ctx, stack, outputs, time_stack, cmd, trace_opt.as_deref_mut())?;
*trace_cell.borrow_mut() = trace_opt;
}
_ => return Err("expected quotation".into()),
}
}
}
pc += 1;
}

View File

@@ -5,4 +5,4 @@ mod script;
pub use file::{load, save};
pub use project::{Bank, Pattern, PatternSpeed, Project};
pub use script::{ExecutionTrace, Rng, ScriptEngine, SourceSpan, StepContext, Variables};
pub use script::{Dictionary, ExecutionTrace, Rng, ScriptEngine, SourceSpan, StepContext, Variables};

View File

@@ -1,15 +1,15 @@
use super::forth::Forth;
pub use super::forth::{ExecutionTrace, Rng, SourceSpan, StepContext, Variables};
pub use super::forth::{Dictionary, ExecutionTrace, Rng, SourceSpan, StepContext, Variables};
pub struct ScriptEngine {
forth: Forth,
}
impl ScriptEngine {
pub fn new(vars: Variables, rng: Rng) -> Self {
pub fn new(vars: Variables, dict: Dictionary, rng: Rng) -> Self {
Self {
forth: Forth::new(vars, rng),
forth: Forth::new(vars, dict, rng),
}
}