words definition
This commit is contained in:
@@ -10,7 +10,7 @@ use crate::commands::AppCommand;
|
|||||||
use crate::engine::{
|
use crate::engine::{
|
||||||
LinkState, PatternChange, PatternSnapshot, SeqCommand, SequencerSnapshot, StepSnapshot,
|
LinkState, PatternChange, PatternSnapshot, SeqCommand, SequencerSnapshot, StepSnapshot,
|
||||||
};
|
};
|
||||||
use crate::model::{self, Bank, Pattern, Rng, ScriptEngine, StepContext, Variables};
|
use crate::model::{self, Bank, Dictionary, Pattern, Rng, ScriptEngine, StepContext, Variables};
|
||||||
use crate::page::Page;
|
use crate::page::Page;
|
||||||
use crate::services::pattern_editor;
|
use crate::services::pattern_editor;
|
||||||
use crate::settings::Settings;
|
use crate::settings::Settings;
|
||||||
@@ -35,6 +35,7 @@ pub struct App {
|
|||||||
pub metrics: Metrics,
|
pub metrics: Metrics,
|
||||||
pub script_engine: ScriptEngine,
|
pub script_engine: ScriptEngine,
|
||||||
pub variables: Variables,
|
pub variables: Variables,
|
||||||
|
pub dict: Dictionary,
|
||||||
pub rng: Rng,
|
pub rng: Rng,
|
||||||
pub live_keys: Arc<LiveKeyState>,
|
pub live_keys: Arc<LiveKeyState>,
|
||||||
pub clipboard: Option<arboard::Clipboard>,
|
pub clipboard: Option<arboard::Clipboard>,
|
||||||
@@ -47,8 +48,9 @@ pub struct App {
|
|||||||
impl App {
|
impl App {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let variables = Arc::new(Mutex::new(HashMap::new()));
|
let variables = Arc::new(Mutex::new(HashMap::new()));
|
||||||
|
let dict = Arc::new(Mutex::new(HashMap::new()));
|
||||||
let rng = Arc::new(Mutex::new(StdRng::seed_from_u64(0)));
|
let rng = Arc::new(Mutex::new(StdRng::seed_from_u64(0)));
|
||||||
let script_engine = ScriptEngine::new(Arc::clone(&variables), Arc::clone(&rng));
|
let script_engine = ScriptEngine::new(Arc::clone(&variables), Arc::clone(&dict), Arc::clone(&rng));
|
||||||
let live_keys = Arc::new(LiveKeyState::new());
|
let live_keys = Arc::new(LiveKeyState::new());
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
@@ -63,6 +65,7 @@ impl App {
|
|||||||
|
|
||||||
metrics: Metrics::default(),
|
metrics: Metrics::default(),
|
||||||
variables,
|
variables,
|
||||||
|
dict,
|
||||||
rng,
|
rng,
|
||||||
live_keys,
|
live_keys,
|
||||||
script_engine,
|
script_engine,
|
||||||
|
|||||||
@@ -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, StepContext, Variables};
|
use crate::model::{Dictionary, 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)]
|
||||||
@@ -177,6 +177,7 @@ pub fn spawn_sequencer(
|
|||||||
link: Arc<LinkState>,
|
link: Arc<LinkState>,
|
||||||
playing: Arc<std::sync::atomic::AtomicBool>,
|
playing: Arc<std::sync::atomic::AtomicBool>,
|
||||||
variables: Variables,
|
variables: Variables,
|
||||||
|
dict: Dictionary,
|
||||||
rng: Rng,
|
rng: Rng,
|
||||||
quantum: f64,
|
quantum: f64,
|
||||||
live_keys: Arc<LiveKeyState>,
|
live_keys: Arc<LiveKeyState>,
|
||||||
@@ -197,6 +198,7 @@ pub fn spawn_sequencer(
|
|||||||
link,
|
link,
|
||||||
playing,
|
playing,
|
||||||
variables,
|
variables,
|
||||||
|
dict,
|
||||||
rng,
|
rng,
|
||||||
quantum,
|
quantum,
|
||||||
shared_state_clone,
|
shared_state_clone,
|
||||||
@@ -291,6 +293,7 @@ fn sequencer_loop(
|
|||||||
link: Arc<LinkState>,
|
link: Arc<LinkState>,
|
||||||
playing: Arc<std::sync::atomic::AtomicBool>,
|
playing: Arc<std::sync::atomic::AtomicBool>,
|
||||||
variables: Variables,
|
variables: Variables,
|
||||||
|
dict: Dictionary,
|
||||||
rng: Rng,
|
rng: Rng,
|
||||||
quantum: f64,
|
quantum: f64,
|
||||||
shared_state: Arc<Mutex<SharedSequencerState>>,
|
shared_state: Arc<Mutex<SharedSequencerState>>,
|
||||||
@@ -298,7 +301,7 @@ fn sequencer_loop(
|
|||||||
) {
|
) {
|
||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
|
|
||||||
let script_engine = ScriptEngine::new(Arc::clone(&variables), rng);
|
let script_engine = ScriptEngine::new(Arc::clone(&variables), dict, rng);
|
||||||
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();
|
||||||
|
|||||||
@@ -102,6 +102,7 @@ fn main() -> io::Result<()> {
|
|||||||
Arc::clone(&link),
|
Arc::clone(&link),
|
||||||
Arc::clone(&playing),
|
Arc::clone(&playing),
|
||||||
Arc::clone(&app.variables),
|
Arc::clone(&app.variables),
|
||||||
|
Arc::clone(&app.dict),
|
||||||
Arc::clone(&app.rng),
|
Arc::clone(&app.rng),
|
||||||
settings.link.quantum,
|
settings.link.quantum,
|
||||||
Arc::clone(&app.live_keys),
|
Arc::clone(&app.live_keys),
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ impl StepContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub type Variables = Arc<Mutex<HashMap<String, Value>>>;
|
pub type Variables = Arc<Mutex<HashMap<String, Value>>>;
|
||||||
|
pub type Dictionary = Arc<Mutex<HashMap<String, Vec<Op>>>>;
|
||||||
pub type Rng = Arc<Mutex<StdRng>>;
|
pub type Rng = Arc<Mutex<StdRng>>;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
@@ -212,6 +213,7 @@ pub enum Op {
|
|||||||
LocalCycleEnd,
|
LocalCycleEnd,
|
||||||
Echo,
|
Echo,
|
||||||
Necho,
|
Necho,
|
||||||
|
Apply,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum WordCompile {
|
pub enum WordCompile {
|
||||||
@@ -1606,6 +1608,29 @@ pub const WORDS: &[Word] = &[
|
|||||||
example: "1 reset",
|
example: "1 reset",
|
||||||
compile: Param,
|
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> {
|
fn simple_op(name: &str) -> Option<Op> {
|
||||||
@@ -1672,6 +1697,7 @@ fn simple_op(name: &str) -> Option<Op> {
|
|||||||
"for" => Op::For,
|
"for" => Op::For,
|
||||||
"echo" => Op::Echo,
|
"echo" => Op::Echo,
|
||||||
"necho" => Op::Necho,
|
"necho" => Op::Necho,
|
||||||
|
"apply" => Op::Apply,
|
||||||
_ => return None,
|
_ => return None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -1751,7 +1777,7 @@ fn parse_interval(name: &str) -> Option<i64> {
|
|||||||
Some(simple)
|
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 {
|
for word in WORDS {
|
||||||
if word.name == name {
|
if word.name == name {
|
||||||
match &word.compile {
|
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())),
|
Context(ctx) => ops.push(Op::GetContext((*ctx).into())),
|
||||||
Param => ops.push(Op::SetParam(name.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) => {
|
Probability(p) => {
|
||||||
ops.push(Op::PushFloat(*p, None));
|
ops.push(Op::PushFloat(*p, None));
|
||||||
ops.push(Op::ChanceExec);
|
ops.push(Op::ChanceExec);
|
||||||
@@ -1810,6 +1836,12 @@ fn compile_word(name: &str, span: Option<SourceSpan>, ops: &mut Vec<Op>) -> bool
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// User-defined words from dictionary
|
||||||
|
if let Some(body) = dict.lock().unwrap().get(name) {
|
||||||
|
ops.extend(body.iter().cloned());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1905,7 +1937,7 @@ fn tokenize(input: &str) -> Vec<Token> {
|
|||||||
tokens
|
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 ops = Vec::new();
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
let mut pipe_parity = false;
|
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::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(start_pos) => {
|
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;
|
i += consumed;
|
||||||
let body_span = SourceSpan { start: *start_pos, end: end_pos + 1 };
|
let body_span = SourceSpan { start: *start_pos, end: end_pos + 1 };
|
||||||
ops.push(Op::Quotation(quote_ops, Some(body_span)));
|
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) => {
|
Token::Word(w, span) => {
|
||||||
let word = w.as_str();
|
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 {
|
if pipe_parity {
|
||||||
ops.push(Op::LocalCycleEnd);
|
ops.push(Op::LocalCycleEnd);
|
||||||
} else {
|
} else {
|
||||||
@@ -1934,7 +1972,7 @@ 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, 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;
|
i += consumed;
|
||||||
if else_ops.is_empty() {
|
if else_ops.is_empty() {
|
||||||
ops.push(Op::BranchIfZero(then_ops.len(), then_span, None));
|
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.push(Op::Branch(else_ops.len()));
|
||||||
ops.extend(else_ops);
|
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}"));
|
return Err(format!("unknown word: {word}"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1956,7 +1994,7 @@ fn compile(tokens: &[Token]) -> Result<Vec<Op>, String> {
|
|||||||
Ok(ops)
|
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 depth = 1;
|
||||||
let mut end_idx = None;
|
let mut end_idx = None;
|
||||||
|
|
||||||
@@ -1979,7 +2017,7 @@ fn compile_quotation(tokens: &[Token]) -> Result<(Vec<Op>, usize, usize), String
|
|||||||
Token::QuoteEnd(pos) => *pos,
|
Token::QuoteEnd(pos) => *pos,
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
let quote_ops = compile(&tokens[..end_idx])?;
|
let quote_ops = compile(&tokens[..end_idx], dict)?;
|
||||||
Ok((quote_ops, end_idx + 1, byte_pos))
|
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> {
|
fn tokens_span(tokens: &[Token]) -> Option<SourceSpan> {
|
||||||
let first = tokens.first().and_then(token_span)?;
|
let first = tokens.first().and_then(token_span)?;
|
||||||
let last = tokens.last().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)]
|
#[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 depth = 1;
|
||||||
let mut else_pos = None;
|
let mut else_pos = None;
|
||||||
let mut then_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 else_slice = &tokens[ep + 1..then_pos];
|
||||||
let then_span = tokens_span(then_slice);
|
let then_span = tokens_span(then_slice);
|
||||||
let else_span = tokens_span(else_slice);
|
let else_span = tokens_span(else_slice);
|
||||||
let then_ops = compile(then_slice)?;
|
let then_ops = compile(then_slice, dict)?;
|
||||||
let else_ops = compile(else_slice)?;
|
let else_ops = compile(else_slice, dict)?;
|
||||||
(then_ops, else_ops, then_span, else_span)
|
(then_ops, else_ops, then_span, else_span)
|
||||||
} else {
|
} else {
|
||||||
let then_slice = &tokens[..then_pos];
|
let then_slice = &tokens[..then_pos];
|
||||||
let then_span = tokens_span(then_slice);
|
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)
|
(then_ops, Vec::new(), then_span, None)
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -2045,14 +2107,16 @@ pub type Stack = Arc<Mutex<Vec<Value>>>;
|
|||||||
pub struct Forth {
|
pub struct Forth {
|
||||||
stack: Stack,
|
stack: Stack,
|
||||||
vars: Variables,
|
vars: Variables,
|
||||||
|
dict: Dictionary,
|
||||||
rng: Rng,
|
rng: Rng,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Forth {
|
impl Forth {
|
||||||
pub fn new(vars: Variables, rng: Rng) -> Self {
|
pub fn new(vars: Variables, dict: Dictionary, rng: Rng) -> Self {
|
||||||
Self {
|
Self {
|
||||||
stack: Arc::new(Mutex::new(Vec::new())),
|
stack: Arc::new(Mutex::new(Vec::new())),
|
||||||
vars,
|
vars,
|
||||||
|
dict,
|
||||||
rng,
|
rng,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2091,7 +2155,7 @@ impl Forth {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let tokens = tokenize(script);
|
let tokens = tokenize(script);
|
||||||
let ops = compile(&tokens)?;
|
let ops = compile(&tokens, &self.dict)?;
|
||||||
self.execute(&ops, ctx, trace)
|
self.execute(&ops, ctx, trace)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2824,6 +2888,23 @@ impl Forth {
|
|||||||
}
|
}
|
||||||
stack.push(selected);
|
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;
|
pc += 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,4 +5,4 @@ mod script;
|
|||||||
|
|
||||||
pub use file::{load, save};
|
pub use file::{load, save};
|
||||||
pub use project::{Bank, Pattern, PatternSpeed, Project};
|
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;
|
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 {
|
pub struct ScriptEngine {
|
||||||
forth: Forth,
|
forth: Forth,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ScriptEngine {
|
impl ScriptEngine {
|
||||||
pub fn new(vars: Variables, rng: Rng) -> Self {
|
pub fn new(vars: Variables, dict: Dictionary, rng: Rng) -> Self {
|
||||||
Self {
|
Self {
|
||||||
forth: Forth::new(vars, rng),
|
forth: Forth::new(vars, dict, rng),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ const KEYWORDS: &[&str] = &[
|
|||||||
"zoom", "scale!", "stack", "echo", "necho", "for", "div", "each", "at", "pop", "adsr", "ad",
|
"zoom", "scale!", "stack", "echo", "necho", "for", "div", "each", "at", "pop", "adsr", "ad",
|
||||||
"?", "!?", "<<", ">>", "|", "@", "!", "pcycle", "tempo!", "prob", "sometimes", "often",
|
"?", "!?", "<<", ">>", "|", "@", "!", "pcycle", "tempo!", "prob", "sometimes", "often",
|
||||||
"rarely", "almostAlways", "almostNever", "always", "never", "coin", "fill", "iter", "every",
|
"rarely", "almostAlways", "almostNever", "always", "never", "coin", "fill", "iter", "every",
|
||||||
"gt", "lt",
|
"gt", "lt", ":", ";", "apply",
|
||||||
];
|
];
|
||||||
const SOUND: &[&str] = &["sound", "s"];
|
const SOUND: &[&str] = &["sound", "s"];
|
||||||
const CONTEXT: &[&str] = &[
|
const CONTEXT: &[&str] = &[
|
||||||
|
|||||||
@@ -42,3 +42,6 @@ mod notes;
|
|||||||
|
|
||||||
#[path = "forth/intervals.rs"]
|
#[path = "forth/intervals.rs"]
|
||||||
mod intervals;
|
mod intervals;
|
||||||
|
|
||||||
|
#[path = "forth/definitions.rs"]
|
||||||
|
mod definitions;
|
||||||
|
|||||||
115
tests/forth/definitions.rs
Normal file
115
tests/forth/definitions.rs
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
use super::harness::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn define_and_use_word() {
|
||||||
|
expect_int(": double 2 * ; 5 double", 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn define_word_with_multiple_ops() {
|
||||||
|
expect_int(": triple dup dup + + ; 3 triple", 9);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn define_word_using_another_user_word() {
|
||||||
|
expect_int(": double 2 * ; : quad double double ; 3 quad", 12);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn redefine_word_overwrites() {
|
||||||
|
expect_int(": foo 10 ; : foo 20 ; foo", 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn word_with_param() {
|
||||||
|
let outputs = expect_outputs(": loud 0.9 gain ; \"kick\" s loud emit", 1);
|
||||||
|
assert!(outputs[0].contains("gain/0.9"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn word_available_across_evaluations() {
|
||||||
|
let f = forth();
|
||||||
|
let ctx = default_ctx();
|
||||||
|
f.evaluate(": hi 42 ;", &ctx).unwrap();
|
||||||
|
f.evaluate("hi", &ctx).unwrap();
|
||||||
|
assert_eq!(stack_int(&f), 42);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn word_defined_in_one_forth_available_in_same() {
|
||||||
|
let f = forth();
|
||||||
|
let ctx = default_ctx();
|
||||||
|
f.evaluate(": ten 10 ;", &ctx).unwrap();
|
||||||
|
f.clear_stack();
|
||||||
|
f.evaluate("ten ten +", &ctx).unwrap();
|
||||||
|
assert_eq!(stack_int(&f), 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unknown_word_errors() {
|
||||||
|
expect_error("nosuchword", "unknown word");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn missing_semicolon_errors() {
|
||||||
|
expect_error(": foo 10", "missing ';'");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn missing_name_errors() {
|
||||||
|
expect_error(":", "expected word name");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unexpected_semicolon_errors() {
|
||||||
|
expect_error(";", "unexpected ;");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn apply_executes_quotation() {
|
||||||
|
expect_int("5 { 2 * } apply", 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn apply_with_stack_ops() {
|
||||||
|
expect_int("3 4 { + } apply", 7);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn apply_empty_stack_errors() {
|
||||||
|
expect_error("apply", "stack underflow");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn apply_non_quotation_errors() {
|
||||||
|
expect_error("42 apply", "expected quotation");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn apply_nested() {
|
||||||
|
expect_int("2 { { 3 * } apply } apply", 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn define_word_containing_quotation() {
|
||||||
|
expect_int(": dbl { 2 * } apply ; 7 dbl", 14);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn define_word_with_sound() {
|
||||||
|
let outputs = expect_outputs(": kick \"kick\" s emit ; kick", 1);
|
||||||
|
assert!(outputs[0].contains("sound/kick"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn define_word_with_conditional() {
|
||||||
|
let f = forth();
|
||||||
|
let ctx = default_ctx();
|
||||||
|
f.evaluate(": maybe-double dup 5 gt if 2 * then ;", &ctx).unwrap();
|
||||||
|
f.clear_stack();
|
||||||
|
f.evaluate("3 maybe-double", &ctx).unwrap();
|
||||||
|
assert_eq!(stack_int(&f), 3);
|
||||||
|
f.clear_stack();
|
||||||
|
f.evaluate("10 maybe-double", &ctx).unwrap();
|
||||||
|
assert_eq!(stack_int(&f), 20);
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
use rand::rngs::StdRng;
|
use rand::rngs::StdRng;
|
||||||
use rand::SeedableRng;
|
use rand::SeedableRng;
|
||||||
use cagire::model::forth::{Forth, Rng, StepContext, Value, Variables};
|
use cagire::model::forth::{Dictionary, Forth, Rng, StepContext, Value, Variables};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
@@ -29,16 +29,20 @@ pub fn new_vars() -> Variables {
|
|||||||
Arc::new(Mutex::new(HashMap::new()))
|
Arc::new(Mutex::new(HashMap::new()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn new_dict() -> Dictionary {
|
||||||
|
Arc::new(Mutex::new(HashMap::new()))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn seeded_rng(seed: u64) -> Rng {
|
pub fn seeded_rng(seed: u64) -> Rng {
|
||||||
Arc::new(Mutex::new(StdRng::seed_from_u64(seed)))
|
Arc::new(Mutex::new(StdRng::seed_from_u64(seed)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn forth() -> Forth {
|
pub fn forth() -> Forth {
|
||||||
Forth::new(new_vars(), seeded_rng(42))
|
Forth::new(new_vars(), new_dict(), seeded_rng(42))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn forth_seeded(seed: u64) -> Forth {
|
pub fn forth_seeded(seed: u64) -> Forth {
|
||||||
Forth::new(new_vars(), seeded_rng(seed))
|
Forth::new(new_vars(), new_dict(), seeded_rng(seed))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(script: &str) -> Forth {
|
pub fn run(script: &str) -> Forth {
|
||||||
|
|||||||
Reference in New Issue
Block a user