words definition
This commit is contained in:
@@ -10,7 +10,7 @@ use crate::commands::AppCommand;
|
||||
use crate::engine::{
|
||||
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::services::pattern_editor;
|
||||
use crate::settings::Settings;
|
||||
@@ -35,6 +35,7 @@ pub struct App {
|
||||
pub metrics: Metrics,
|
||||
pub script_engine: ScriptEngine,
|
||||
pub variables: Variables,
|
||||
pub dict: Dictionary,
|
||||
pub rng: Rng,
|
||||
pub live_keys: Arc<LiveKeyState>,
|
||||
pub clipboard: Option<arboard::Clipboard>,
|
||||
@@ -47,8 +48,9 @@ pub struct App {
|
||||
impl App {
|
||||
pub fn new() -> Self {
|
||||
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 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());
|
||||
|
||||
Self {
|
||||
@@ -63,6 +65,7 @@ impl App {
|
||||
|
||||
metrics: Metrics::default(),
|
||||
variables,
|
||||
dict,
|
||||
rng,
|
||||
live_keys,
|
||||
script_engine,
|
||||
|
||||
@@ -6,7 +6,7 @@ use std::time::Duration;
|
||||
|
||||
use super::LinkState;
|
||||
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;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
|
||||
@@ -177,6 +177,7 @@ pub fn spawn_sequencer(
|
||||
link: Arc<LinkState>,
|
||||
playing: Arc<std::sync::atomic::AtomicBool>,
|
||||
variables: Variables,
|
||||
dict: Dictionary,
|
||||
rng: Rng,
|
||||
quantum: f64,
|
||||
live_keys: Arc<LiveKeyState>,
|
||||
@@ -197,6 +198,7 @@ pub fn spawn_sequencer(
|
||||
link,
|
||||
playing,
|
||||
variables,
|
||||
dict,
|
||||
rng,
|
||||
quantum,
|
||||
shared_state_clone,
|
||||
@@ -291,6 +293,7 @@ fn sequencer_loop(
|
||||
link: Arc<LinkState>,
|
||||
playing: Arc<std::sync::atomic::AtomicBool>,
|
||||
variables: Variables,
|
||||
dict: Dictionary,
|
||||
rng: Rng,
|
||||
quantum: f64,
|
||||
shared_state: Arc<Mutex<SharedSequencerState>>,
|
||||
@@ -298,7 +301,7 @@ fn sequencer_loop(
|
||||
) {
|
||||
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 pattern_cache = PatternCache::new();
|
||||
let mut runs_counter = RunsCounter::new();
|
||||
|
||||
@@ -102,6 +102,7 @@ fn main() -> io::Result<()> {
|
||||
Arc::clone(&link),
|
||||
Arc::clone(&playing),
|
||||
Arc::clone(&app.variables),
|
||||
Arc::clone(&app.dict),
|
||||
Arc::clone(&app.rng),
|
||||
settings.link.quantum,
|
||||
Arc::clone(&app.live_keys),
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ const KEYWORDS: &[&str] = &[
|
||||
"zoom", "scale!", "stack", "echo", "necho", "for", "div", "each", "at", "pop", "adsr", "ad",
|
||||
"?", "!?", "<<", ">>", "|", "@", "!", "pcycle", "tempo!", "prob", "sometimes", "often",
|
||||
"rarely", "almostAlways", "almostNever", "always", "never", "coin", "fill", "iter", "every",
|
||||
"gt", "lt",
|
||||
"gt", "lt", ":", ";", "apply",
|
||||
];
|
||||
const SOUND: &[&str] = &["sound", "s"];
|
||||
const CONTEXT: &[&str] = &[
|
||||
|
||||
@@ -42,3 +42,6 @@ mod notes;
|
||||
|
||||
#[path = "forth/intervals.rs"]
|
||||
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::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::sync::{Arc, Mutex};
|
||||
|
||||
@@ -29,16 +29,20 @@ pub fn new_vars() -> Variables {
|
||||
Arc::new(Mutex::new(HashMap::new()))
|
||||
}
|
||||
|
||||
pub fn new_dict() -> Dictionary {
|
||||
Arc::new(Mutex::new(HashMap::new()))
|
||||
}
|
||||
|
||||
pub fn seeded_rng(seed: u64) -> Rng {
|
||||
Arc::new(Mutex::new(StdRng::seed_from_u64(seed)))
|
||||
}
|
||||
|
||||
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 {
|
||||
Forth::new(new_vars(), seeded_rng(seed))
|
||||
Forth::new(new_vars(), new_dict(), seeded_rng(seed))
|
||||
}
|
||||
|
||||
pub fn run(script: &str) -> Forth {
|
||||
|
||||
Reference in New Issue
Block a user