words definition
This commit is contained in:
@@ -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("_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;
|
||||
}
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user