Begin testing

This commit is contained in:
2026-01-20 18:07:39 +01:00
parent 8b0b98024a
commit 8eb33dd8b4
13 changed files with 1039 additions and 3 deletions

View File

@@ -24,7 +24,7 @@ impl StepContext {
pub type Variables = Arc<Mutex<HashMap<String, Value>>>; pub type Variables = Arc<Mutex<HashMap<String, Value>>>;
pub type Rng = Arc<Mutex<StdRng>>; pub type Rng = Arc<Mutex<StdRng>>;
#[derive(Clone, Debug)] #[derive(Clone, Debug, PartialEq)]
pub enum Value { pub enum Value {
Int(i64), Int(i64),
Float(f64), Float(f64),
@@ -1628,14 +1628,29 @@ fn compile_if(tokens: &[Token]) -> Result<(Vec<Op>, Vec<Op>, usize), String> {
Ok((then_ops, else_ops, then_pos + 1)) Ok((then_ops, else_ops, then_pos + 1))
} }
pub type Stack = Arc<Mutex<Vec<Value>>>;
pub struct Forth { pub struct Forth {
stack: Stack,
vars: Variables, vars: Variables,
rng: Rng, rng: Rng,
} }
impl Forth { impl Forth {
pub fn new(vars: Variables, rng: Rng) -> Self { pub fn new(vars: Variables, rng: Rng) -> Self {
Self { vars, rng } Self {
stack: Arc::new(Mutex::new(Vec::new())),
vars,
rng,
}
}
pub fn stack(&self) -> Vec<Value> {
self.stack.lock().unwrap().clone()
}
pub fn clear_stack(&self) {
self.stack.lock().unwrap().clear();
} }
pub fn evaluate(&self, script: &str, ctx: &StepContext) -> Result<Vec<String>, String> { pub fn evaluate(&self, script: &str, ctx: &StepContext) -> Result<Vec<String>, String> {
@@ -1649,7 +1664,7 @@ impl Forth {
} }
fn execute(&self, ops: &[Op], ctx: &StepContext) -> Result<Vec<String>, String> { fn execute(&self, ops: &[Op], ctx: &StepContext) -> Result<Vec<String>, String> {
let mut stack: Vec<Value> = Vec::new(); let mut stack = self.stack.lock().unwrap();
let mut outputs: Vec<String> = Vec::new(); let mut outputs: Vec<String> = Vec::new();
let mut time_stack: Vec<TimeContext> = vec![TimeContext { let mut time_stack: Vec<TimeContext> = vec![TimeContext {
start: 0.0, start: 0.0,

View File

@@ -0,0 +1,152 @@
use super::harness::*;
#[test]
fn add_integers() {
expect_int("2 3 +", 5);
}
#[test]
fn add_floats() {
expect_int("2.5 3.5 +", 6);
}
#[test]
fn add_mixed() {
expect_float("2 3.5 +", 5.5);
}
#[test]
fn sub() {
expect_int("10 3 -", 7);
}
#[test]
fn sub_negative() {
expect_int("3 10 -", -7);
}
#[test]
fn mul() {
expect_int("4 5 *", 20);
}
#[test]
fn mul_floats() {
expect_int("2.5 4 *", 10);
}
#[test]
fn div() {
expect_int("10 2 /", 5);
}
#[test]
fn div_float_result() {
expect_float("7 2 /", 3.5);
}
#[test]
fn modulo() {
expect_int("7 3 mod", 1);
}
#[test]
fn modulo_exact() {
expect_int("9 3 mod", 0);
}
#[test]
fn neg_int() {
expect_int("5 neg", -5);
}
#[test]
fn neg_float() {
expect_float("3.5 neg", -3.5);
}
#[test]
fn neg_double() {
expect_int("-5 neg", 5);
}
#[test]
fn abs_positive() {
expect_int("5 abs", 5);
}
#[test]
fn abs_negative() {
expect_int("-5 abs", 5);
}
#[test]
fn abs_float() {
expect_float("-3.5 abs", 3.5);
}
#[test]
fn floor() {
expect_int("3.7 floor", 3);
}
#[test]
fn floor_negative() {
expect_int("-3.2 floor", -4);
}
#[test]
fn ceil() {
expect_int("3.2 ceil", 4);
}
#[test]
fn ceil_negative() {
expect_int("-3.7 ceil", -3);
}
#[test]
fn round_down() {
expect_int("3.4 round", 3);
}
#[test]
fn round_up() {
expect_int("3.6 round", 4);
}
#[test]
fn round_half() {
expect_int("3.5 round", 4);
}
#[test]
fn min() {
expect_int("3 5 min", 3);
}
#[test]
fn min_reverse() {
expect_int("5 3 min", 3);
}
#[test]
fn max() {
expect_int("3 5 max", 5);
}
#[test]
fn max_reverse() {
expect_int("5 3 max", 5);
}
#[test]
fn chain() {
// (2 + 3) * 4 - 1 = 19
expect_int("2 3 + 4 * 1 -", 19);
}
#[test]
fn underflow() {
expect_error("1 +", "stack underflow");
}

View File

@@ -0,0 +1,136 @@
use super::harness::*;
#[test]
fn eq_true() {
expect_int("3 3 =", 1);
}
#[test]
fn eq_false() {
expect_int("3 4 =", 0);
}
#[test]
fn eq_mixed_types() {
expect_int("3.0 3 =", 1);
}
#[test]
fn ne_true() {
expect_int("3 4 <>", 1);
}
#[test]
fn ne_false() {
expect_int("3 3 <>", 0);
}
#[test]
fn lt_true() {
expect_int("2 3 <", 1);
}
#[test]
fn lt_equal() {
expect_int("3 3 <", 0);
}
#[test]
fn lt_false() {
expect_int("4 3 <", 0);
}
#[test]
fn gt_true() {
expect_int("4 3 >", 1);
}
#[test]
fn gt_equal() {
expect_int("3 3 >", 0);
}
#[test]
fn gt_false() {
expect_int("2 3 >", 0);
}
#[test]
fn le_less() {
expect_int("2 3 <=", 1);
}
#[test]
fn le_equal() {
expect_int("3 3 <=", 1);
}
#[test]
fn le_greater() {
expect_int("4 3 <=", 0);
}
#[test]
fn ge_greater() {
expect_int("4 3 >=", 1);
}
#[test]
fn ge_equal() {
expect_int("3 3 >=", 1);
}
#[test]
fn ge_less() {
expect_int("2 3 >=", 0);
}
#[test]
fn and_tt() {
expect_int("1 1 and", 1);
}
#[test]
fn and_tf() {
expect_int("1 0 and", 0);
}
#[test]
fn and_ff() {
expect_int("0 0 and", 0);
}
#[test]
fn or_tt() {
expect_int("1 1 or", 1);
}
#[test]
fn or_tf() {
expect_int("1 0 or", 1);
}
#[test]
fn or_ff() {
expect_int("0 0 or", 0);
}
#[test]
fn not_true() {
expect_int("1 not", 0);
}
#[test]
fn not_false() {
expect_int("0 not", 1);
}
#[test]
fn truthy_nonzero() {
expect_int("5 not", 0);
}
#[test]
fn truthy_negative() {
expect_int("-1 not", 0);
}

View File

@@ -0,0 +1,71 @@
use super::harness::*;
#[test]
fn step() {
let ctx = ctx_with(|c| c.step = 7);
let f = run_ctx("step", &ctx);
assert_eq!(stack_int(&f), 7);
}
#[test]
fn beat() {
let ctx = ctx_with(|c| c.beat = 4.5);
let f = run_ctx("beat", &ctx);
assert!((stack_float(&f) - 4.5).abs() < 1e-9);
}
#[test]
fn bank() {
let ctx = ctx_with(|c| c.bank = 2);
let f = run_ctx("bank", &ctx);
assert_eq!(stack_int(&f), 2);
}
#[test]
fn pattern() {
let ctx = ctx_with(|c| c.pattern = 3);
let f = run_ctx("pattern", &ctx);
assert_eq!(stack_int(&f), 3);
}
#[test]
fn tempo() {
let ctx = ctx_with(|c| c.tempo = 140.0);
let f = run_ctx("tempo", &ctx);
assert!((stack_float(&f) - 140.0).abs() < 1e-9);
}
#[test]
fn phase() {
let ctx = ctx_with(|c| c.phase = 0.25);
let f = run_ctx("phase", &ctx);
assert!((stack_float(&f) - 0.25).abs() < 1e-9);
}
#[test]
fn slot() {
let ctx = ctx_with(|c| c.slot = 5);
let f = run_ctx("slot", &ctx);
assert_eq!(stack_int(&f), 5);
}
#[test]
fn runs() {
let ctx = ctx_with(|c| c.runs = 10);
let f = run_ctx("runs", &ctx);
assert_eq!(stack_int(&f), 10);
}
#[test]
fn stepdur() {
// stepdur = 60.0 / tempo / 4.0 / speed = 60 / 120 / 4 / 1 = 0.125
let f = run("stepdur");
assert!((stack_float(&f) - 0.125).abs() < 1e-9);
}
#[test]
fn context_in_computation() {
let ctx = ctx_with(|c| c.step = 3);
let f = run_ctx("60 step +", &ctx);
assert_eq!(stack_int(&f), 63);
}

View File

@@ -0,0 +1,64 @@
use super::harness::*;
#[test]
fn if_then_true() {
expect_int("1 if 42 then", 42);
}
#[test]
fn if_then_false() {
let f = run("0 if 42 then");
assert!(f.stack().is_empty());
}
#[test]
fn if_then_with_base() {
expect_int("100 0 if 50 + then", 100);
}
#[test]
fn if_else_true() {
expect_int("1 if 42 else 99 then", 42);
}
#[test]
fn if_else_false() {
expect_int("0 if 42 else 99 then", 99);
}
#[test]
fn nested_tt() {
expect_int("1 if 1 if 100 else 200 then else 300 then", 100);
}
#[test]
fn nested_tf() {
expect_int("1 if 0 if 100 else 200 then else 300 then", 200);
}
#[test]
fn nested_f() {
expect_int("0 if 1 if 100 else 200 then else 300 then", 300);
}
#[test]
fn if_with_computation() {
expect_int("3 2 > if 42 else 99 then", 42);
}
#[test]
fn missing_then() {
expect_error("1 if 42", "missing 'then'");
}
#[test]
fn deeply_nested() {
expect_int("1 if 1 if 1 if 42 then then then", 42);
}
#[test]
fn chained_conditionals() {
// First if leaves nothing, second if runs
let f = run("0 if 1 then 1 if 42 then");
assert_eq!(stack_int(&f), 42);
}

View File

@@ -0,0 +1,106 @@
use super::harness::*;
#[test]
fn empty_script() {
expect_error("", "empty script");
}
#[test]
fn whitespace_only() {
expect_error(" \n\t ", "empty script");
}
#[test]
fn unknown_word() {
expect_error("foobar", "unknown word");
}
#[test]
fn string_not_number() {
expect_error(r#""hello" neg"#, "expected number");
}
#[test]
fn get_expects_string() {
expect_error("42 get", "expected string");
}
#[test]
fn set_expects_string() {
expect_error("1 2 set", "expected string");
}
#[test]
fn comment_ignored() {
expect_int("1 (this is a comment) 2 +", 3);
}
#[test]
fn multiline_comment() {
expect_int("1 (multi\nline\ncomment) 2 +", 3);
}
#[test]
fn negative_literal() {
expect_int("-5", -5);
}
#[test]
fn float_literal() {
let f = run("3.14159");
let val = stack_float(&f);
assert!((val - 3.14159).abs() < 1e-9);
}
#[test]
fn string_with_spaces() {
let f = run(r#""hello world" "x" set "x" get"#);
match stack_top(&f) {
crate::model::forth::Value::Str(s) => assert_eq!(s, "hello world"),
other => panic!("expected string, got {:?}", other),
}
}
#[test]
fn list_count() {
// [ 1 2 3 ] => stack: 1 2 3 3 (items + count on top)
let f = run("[ 1 2 3 ]");
assert_eq!(stack_int(&f), 3);
}
#[test]
fn list_empty() {
expect_int("[ ]", 0);
}
#[test]
fn list_preserves_values() {
// [ 10 20 ] => stack: 10 20 2
// drop => 10 20
// + => 30
expect_int("[ 10 20 ] drop +", 30);
}
#[test]
fn conditional_based_on_step() {
let ctx0 = ctx_with(|c| c.step = 0);
let ctx1 = ctx_with(|c| c.step = 1);
let f0 = run_ctx("step 2 mod 0 = if 100 else 200 then", &ctx0);
let f1 = run_ctx("step 2 mod 0 = if 100 else 200 then", &ctx1);
assert_eq!(stack_int(&f0), 100);
assert_eq!(stack_int(&f1), 200);
}
#[test]
fn accumulator() {
let f = forth();
let ctx = default_ctx();
f.evaluate(r#"0 "acc" set"#, &ctx).unwrap();
for _ in 0..5 {
f.clear_stack();
f.evaluate(r#""acc" get 1 + dup "acc" set"#, &ctx).unwrap();
}
assert_eq!(stack_int(&f), 5);
}

View File

@@ -0,0 +1,136 @@
use crate::model::forth::{Forth, Rng, StepContext, Value, Variables};
use rand::rngs::StdRng;
use rand::SeedableRng;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
pub fn default_ctx() -> StepContext {
StepContext {
step: 0,
beat: 0.0,
bank: 0,
pattern: 0,
tempo: 120.0,
phase: 0.0,
slot: 0,
runs: 0,
speed: 1.0,
}
}
pub fn ctx_with(f: impl FnOnce(&mut StepContext)) -> StepContext {
let mut ctx = default_ctx();
f(&mut ctx);
ctx
}
pub fn new_vars() -> Variables {
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))
}
pub fn forth_seeded(seed: u64) -> Forth {
Forth::new(new_vars(), seeded_rng(seed))
}
pub fn run(script: &str) -> Forth {
let f = forth();
f.evaluate(script, &default_ctx()).unwrap();
f
}
pub fn run_ctx(script: &str, ctx: &StepContext) -> Forth {
let f = forth();
f.evaluate(script, ctx).unwrap();
f
}
pub fn stack_top(f: &Forth) -> Value {
f.stack().pop().expect("stack empty")
}
pub fn stack_int(f: &Forth) -> i64 {
match stack_top(f) {
Value::Int(i) => i,
other => panic!("expected Int, got {:?}", other),
}
}
pub fn stack_float(f: &Forth) -> f64 {
match stack_top(f) {
Value::Float(x) => x,
Value::Int(i) => i as f64,
other => panic!("expected number, got {:?}", other),
}
}
pub fn expect_stack(script: &str, expected: &[Value]) {
let f = run(script);
let stack = f.stack();
assert_eq!(stack, expected, "script: {}", script);
}
pub fn expect_int(script: &str, expected: i64) {
expect_stack(script, &[Value::Int(expected)]);
}
pub fn expect_float(script: &str, expected: f64) {
let f = run(script);
let stack = f.stack();
assert_eq!(stack.len(), 1, "expected single value on stack");
let val = stack_float(&f);
assert!(
(val - expected).abs() < 1e-9,
"expected {}, got {}",
expected,
val
);
}
pub fn expect_floats_close(script: &str, expected: f64, epsilon: f64) {
let f = run(script);
let val = stack_float(&f);
assert!(
(val - expected).abs() < epsilon,
"expected ~{}, got {}",
expected,
val
);
}
pub fn expect_error(script: &str, expected_substr: &str) {
let f = forth();
let result = f.evaluate(script, &default_ctx());
assert!(result.is_err(), "expected error for '{}'", script);
let err = result.unwrap_err();
assert!(
err.contains(expected_substr),
"error '{}' does not contain '{}'",
err,
expected_substr
);
}
pub fn expect_outputs(script: &str, count: usize) -> Vec<String> {
let f = forth();
let outputs = f.evaluate(script, &default_ctx()).unwrap();
assert_eq!(outputs.len(), count, "expected {} outputs", count);
outputs
}
pub fn expect_output_contains(script: &str, substr: &str) {
let outputs = expect_outputs(script, 1);
assert!(
outputs[0].contains(substr),
"output '{}' does not contain '{}'",
outputs[0],
substr
);
}

View File

@@ -0,0 +1,10 @@
mod arithmetic;
mod comparison;
mod context;
mod control_flow;
mod errors;
mod harness;
mod randomness;
mod sound;
mod stack;
mod variables;

View File

@@ -0,0 +1,114 @@
use super::harness::*;
#[test]
fn rand_in_range() {
let f = forth_seeded(12345);
f.evaluate("0 10 rand", &default_ctx()).unwrap();
let val = stack_float(&f);
assert!(val >= 0.0 && val < 10.0, "rand {} not in [0, 10)", val);
}
#[test]
fn rand_deterministic() {
let f1 = forth_seeded(99);
let f2 = forth_seeded(99);
f1.evaluate("0 100 rand", &default_ctx()).unwrap();
f2.evaluate("0 100 rand", &default_ctx()).unwrap();
assert_eq!(f1.stack(), f2.stack());
}
#[test]
fn rrand_inclusive() {
let f = forth_seeded(42);
for _ in 0..20 {
f.clear_stack();
f.evaluate("1 3 rrand", &default_ctx()).unwrap();
let val = stack_int(&f);
assert!(val >= 1 && val <= 3, "rrand {} not in [1, 3]", val);
}
}
#[test]
fn seed_resets() {
let f1 = forth_seeded(1);
f1.evaluate("42 seed 0 100 rand", &default_ctx()).unwrap();
let f2 = forth_seeded(999);
f2.evaluate("42 seed 0 100 rand", &default_ctx()).unwrap();
assert_eq!(f1.stack(), f2.stack());
}
#[test]
fn coin_binary() {
let f = forth_seeded(42);
f.evaluate("coin", &default_ctx()).unwrap();
let val = stack_int(&f);
assert!(val == 0 || val == 1);
}
#[test]
fn chance_zero() {
expect_int("0.0 chance", 0);
}
#[test]
fn chance_one() {
expect_int("1.0 chance", 1);
}
#[test]
fn choose_from_list() {
let f = forth_seeded(42);
f.evaluate("10 20 30 3 choose", &default_ctx()).unwrap();
let val = stack_int(&f);
assert!(val == 10 || val == 20 || val == 30);
}
#[test]
fn choose_underflow() {
expect_error("1 2 5 choose", "stack underflow");
}
#[test]
fn cycle_deterministic() {
for runs in 0..6 {
let ctx = ctx_with(|c| c.runs = runs);
let f = run_ctx("10 20 30 3 cycle", &ctx);
let expected = [10, 20, 30][runs % 3];
assert_eq!(stack_int(&f), expected, "cycle at runs={}", runs);
}
}
#[test]
fn cycle_zero_count() {
expect_error("1 2 3 0 cycle", "cycle count must be > 0");
}
#[test]
fn mtof_a4() {
expect_float("69 mtof", 440.0);
}
#[test]
fn mtof_octave() {
expect_float("81 mtof", 880.0);
}
#[test]
fn mtof_c4() {
expect_floats_close("60 mtof", 261.6255653, 0.001);
}
#[test]
fn ftom_440() {
expect_float("440 ftom", 69.0);
}
#[test]
fn ftom_880() {
expect_float("880 ftom", 81.0);
}
#[test]
fn mtof_ftom_roundtrip() {
expect_float("60 mtof ftom", 60.0);
}

View File

@@ -0,0 +1,101 @@
use super::harness::*;
#[test]
fn basic_emit() {
let outputs = expect_outputs(r#""kick" sound emit"#, 1);
assert!(outputs[0].contains("sound/kick"));
}
#[test]
fn alias_s() {
let outputs = expect_outputs(r#""snare" s emit"#, 1);
assert!(outputs[0].contains("sound/snare"));
}
#[test]
fn with_params() {
let outputs = expect_outputs(r#""kick" s 440 freq 0.5 gain emit"#, 1);
assert!(outputs[0].contains("sound/kick"));
assert!(outputs[0].contains("freq/440"));
assert!(outputs[0].contains("gain/0.5"));
}
#[test]
fn auto_dur() {
let outputs = expect_outputs(r#""kick" s emit"#, 1);
assert!(outputs[0].contains("dur/"));
}
#[test]
fn auto_delaytime() {
let outputs = expect_outputs(r#""kick" s emit"#, 1);
assert!(outputs[0].contains("delaytime/"));
}
#[test]
fn emit_no_sound() {
expect_error("emit", "no sound set");
}
#[test]
fn implicit_emit() {
let outputs = expect_outputs(r#""kick" s 440 freq"#, 1);
assert!(outputs[0].contains("sound/kick"));
assert!(outputs[0].contains("freq/440"));
}
#[test]
fn multiple_emits() {
let outputs = expect_outputs(r#""kick" s emit "snare" s emit"#, 2);
assert!(outputs[0].contains("sound/kick"));
assert!(outputs[1].contains("sound/snare"));
}
#[test]
fn subdivide_each() {
let outputs = expect_outputs(r#""kick" s 4 div each"#, 4);
}
#[test]
fn window_pop() {
let outputs = expect_outputs(
r#"0.0 0.5 window "kick" s emit pop 0.5 1.0 window "snare" s emit"#,
2,
);
assert!(outputs[0].contains("sound/kick"));
assert!(outputs[1].contains("sound/snare"));
}
#[test]
fn pop_root_fails() {
expect_error("pop", "cannot pop root time context");
}
#[test]
fn subdivide_zero() {
expect_error(r#""kick" s 0 div each"#, "subdivide count must be > 0");
}
#[test]
fn each_without_div() {
expect_error(r#""kick" s each"#, "each requires subdivide first");
}
#[test]
fn envelope_params() {
let outputs = expect_outputs(
r#""synth" s 0.01 attack 0.1 decay 0.7 sustain 0.3 release emit"#,
1,
);
assert!(outputs[0].contains("attack/0.01"));
assert!(outputs[0].contains("decay/0.1"));
assert!(outputs[0].contains("sustain/0.7"));
assert!(outputs[0].contains("release/0.3"));
}
#[test]
fn filter_params() {
let outputs = expect_outputs(r#""synth" s 2000 lpf 0.5 lpq emit"#, 1);
assert!(outputs[0].contains("lpf/2000"));
assert!(outputs[0].contains("lpq/0.5"));
}

View File

@@ -0,0 +1,90 @@
use super::harness::*;
use crate::model::forth::Value::Int;
#[test]
fn dup() {
expect_stack("3 dup", &[Int(3), Int(3)]);
}
#[test]
fn dup_underflow() {
expect_error("dup", "stack underflow");
}
#[test]
fn drop() {
expect_stack("1 2 drop", &[Int(1)]);
}
#[test]
fn drop_underflow() {
expect_error("drop", "stack underflow");
}
#[test]
fn swap() {
expect_stack("1 2 swap", &[Int(2), Int(1)]);
}
#[test]
fn swap_underflow() {
expect_error("1 swap", "stack underflow");
}
#[test]
fn over() {
expect_stack("1 2 over", &[Int(1), Int(2), Int(1)]);
}
#[test]
fn over_underflow() {
expect_error("1 over", "stack underflow");
}
#[test]
fn rot() {
expect_stack("1 2 3 rot", &[Int(2), Int(3), Int(1)]);
}
#[test]
fn rot_underflow() {
expect_error("1 2 rot", "stack underflow");
}
#[test]
fn nip() {
expect_stack("1 2 nip", &[Int(2)]);
}
#[test]
fn nip_underflow() {
expect_error("1 nip", "stack underflow");
}
#[test]
fn tuck() {
expect_stack("1 2 tuck", &[Int(2), Int(1), Int(2)]);
}
#[test]
fn tuck_underflow() {
expect_error("1 tuck", "stack underflow");
}
#[test]
fn stack_persists() {
let f = forth();
let ctx = default_ctx();
f.evaluate("1 2 3", &ctx).unwrap();
assert_eq!(f.stack(), vec![Int(1), Int(2), Int(3)]);
f.evaluate("4 5", &ctx).unwrap();
assert_eq!(f.stack(), vec![Int(1), Int(2), Int(3), Int(4), Int(5)]);
}
#[test]
fn clear_stack() {
let f = forth();
f.evaluate("1 2 3", &default_ctx()).unwrap();
f.clear_stack();
assert!(f.stack().is_empty());
}

View File

@@ -0,0 +1,39 @@
use super::harness::*;
#[test]
fn set_get() {
expect_int(r#"42 "x" set "x" get"#, 42);
}
#[test]
fn get_nonexistent() {
expect_int(r#""novar" get"#, 0);
}
#[test]
fn persistence_across_evals() {
let f = forth();
let ctx = default_ctx();
f.evaluate(r#"10 "counter" set"#, &ctx).unwrap();
f.clear_stack();
f.evaluate(r#""counter" get 1 +"#, &ctx).unwrap();
assert_eq!(stack_int(&f), 11);
}
#[test]
fn overwrite() {
expect_int(r#"1 "x" set 99 "x" set "x" get"#, 99);
}
#[test]
fn multiple_vars() {
let f = run(r#"10 "a" set 20 "b" set "a" get "b" get +"#);
assert_eq!(stack_int(&f), 30);
}
#[test]
fn float_var() {
let f = run(r#"3.14 "pi" set "pi" get"#);
let val = stack_float(&f);
assert!((val - 3.14).abs() < 1e-9);
}

View File

@@ -1,5 +1,7 @@
mod file; mod file;
pub mod forth; pub mod forth;
#[cfg(test)]
mod forth_tests;
mod project; mod project;
mod script; mod script;