Add double-stack words (2dup, 2drop, 2swap, 2over) and forget
Some checks failed
Deploy Website / deploy (push) Failing after 4m49s
Some checks failed
Deploy Website / deploy (push) Failing after 4m49s
This commit is contained in:
@@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Double-stack words: `2dup`, `2drop`, `2swap`, `2over`.
|
||||||
|
- `forget` word to remove user-defined words from the dictionary.
|
||||||
|
|
||||||
## [0.0.3] - 2026-02-02
|
## [0.0.3] - 2026-02-02
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@@ -13,6 +13,11 @@ pub enum Op {
|
|||||||
Rot,
|
Rot,
|
||||||
Nip,
|
Nip,
|
||||||
Tuck,
|
Tuck,
|
||||||
|
Dup2,
|
||||||
|
Drop2,
|
||||||
|
Swap2,
|
||||||
|
Over2,
|
||||||
|
Forget,
|
||||||
Add,
|
Add,
|
||||||
Sub,
|
Sub,
|
||||||
Mul,
|
Mul,
|
||||||
|
|||||||
@@ -262,6 +262,42 @@ impl Forth {
|
|||||||
let v = stack[len - 1].clone();
|
let v = stack[len - 1].clone();
|
||||||
stack.insert(len - 2, v);
|
stack.insert(len - 2, v);
|
||||||
}
|
}
|
||||||
|
Op::Dup2 => {
|
||||||
|
let len = stack.len();
|
||||||
|
if len < 2 {
|
||||||
|
return Err("stack underflow".into());
|
||||||
|
}
|
||||||
|
let a = stack[len - 2].clone();
|
||||||
|
let b = stack[len - 1].clone();
|
||||||
|
stack.push(a);
|
||||||
|
stack.push(b);
|
||||||
|
}
|
||||||
|
Op::Drop2 => {
|
||||||
|
let len = stack.len();
|
||||||
|
if len < 2 {
|
||||||
|
return Err("stack underflow".into());
|
||||||
|
}
|
||||||
|
stack.pop();
|
||||||
|
stack.pop();
|
||||||
|
}
|
||||||
|
Op::Swap2 => {
|
||||||
|
let len = stack.len();
|
||||||
|
if len < 4 {
|
||||||
|
return Err("stack underflow".into());
|
||||||
|
}
|
||||||
|
stack.swap(len - 4, len - 2);
|
||||||
|
stack.swap(len - 3, len - 1);
|
||||||
|
}
|
||||||
|
Op::Over2 => {
|
||||||
|
let len = stack.len();
|
||||||
|
if len < 4 {
|
||||||
|
return Err("stack underflow".into());
|
||||||
|
}
|
||||||
|
let a = stack[len - 4].clone();
|
||||||
|
let b = stack[len - 3].clone();
|
||||||
|
stack.push(a);
|
||||||
|
stack.push(b);
|
||||||
|
}
|
||||||
|
|
||||||
Op::Add => binary_op(stack, |a, b| a + b)?,
|
Op::Add => binary_op(stack, |a, b| a + b)?,
|
||||||
Op::Sub => binary_op(stack, |a, b| a - b)?,
|
Op::Sub => binary_op(stack, |a, b| a - b)?,
|
||||||
@@ -915,6 +951,10 @@ impl Forth {
|
|||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
stack.push(Value::Int(val as i64, None));
|
stack.push(Value::Int(val as i64, None));
|
||||||
}
|
}
|
||||||
|
Op::Forget => {
|
||||||
|
let name = stack.pop().ok_or("stack underflow")?.as_str()?.to_string();
|
||||||
|
self.dict.lock().unwrap().remove(&name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
pc += 1;
|
pc += 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -107,6 +107,46 @@ pub const WORDS: &[Word] = &[
|
|||||||
compile: Simple,
|
compile: Simple,
|
||||||
varargs: false,
|
varargs: false,
|
||||||
},
|
},
|
||||||
|
Word {
|
||||||
|
name: "2dup",
|
||||||
|
aliases: &[],
|
||||||
|
category: "Stack",
|
||||||
|
stack: "(a b -- a b a b)",
|
||||||
|
desc: "Duplicate top two values",
|
||||||
|
example: "1 2 2dup => 1 2 1 2",
|
||||||
|
compile: Simple,
|
||||||
|
varargs: false,
|
||||||
|
},
|
||||||
|
Word {
|
||||||
|
name: "2drop",
|
||||||
|
aliases: &[],
|
||||||
|
category: "Stack",
|
||||||
|
stack: "(a b --)",
|
||||||
|
desc: "Drop top two values",
|
||||||
|
example: "1 2 3 2drop => 1",
|
||||||
|
compile: Simple,
|
||||||
|
varargs: false,
|
||||||
|
},
|
||||||
|
Word {
|
||||||
|
name: "2swap",
|
||||||
|
aliases: &[],
|
||||||
|
category: "Stack",
|
||||||
|
stack: "(a b c d -- c d a b)",
|
||||||
|
desc: "Swap top two pairs",
|
||||||
|
example: "1 2 3 4 2swap => 3 4 1 2",
|
||||||
|
compile: Simple,
|
||||||
|
varargs: false,
|
||||||
|
},
|
||||||
|
Word {
|
||||||
|
name: "2over",
|
||||||
|
aliases: &[],
|
||||||
|
category: "Stack",
|
||||||
|
stack: "(a b c d -- a b c d a b)",
|
||||||
|
desc: "Copy second pair to top",
|
||||||
|
example: "1 2 3 4 2over => 1 2 3 4 1 2",
|
||||||
|
compile: Simple,
|
||||||
|
varargs: false,
|
||||||
|
},
|
||||||
// Arithmetic
|
// Arithmetic
|
||||||
Word {
|
Word {
|
||||||
name: "+",
|
name: "+",
|
||||||
@@ -2571,6 +2611,16 @@ pub const WORDS: &[Word] = &[
|
|||||||
compile: Simple,
|
compile: Simple,
|
||||||
varargs: false,
|
varargs: false,
|
||||||
},
|
},
|
||||||
|
Word {
|
||||||
|
name: "forget",
|
||||||
|
aliases: &[],
|
||||||
|
category: "Definitions",
|
||||||
|
stack: "(name --)",
|
||||||
|
desc: "Remove user-defined word from dictionary",
|
||||||
|
example: "\"double\" forget",
|
||||||
|
compile: Simple,
|
||||||
|
varargs: false,
|
||||||
|
},
|
||||||
// Generator
|
// Generator
|
||||||
Word {
|
Word {
|
||||||
name: "..",
|
name: "..",
|
||||||
@@ -2770,6 +2820,10 @@ pub(super) fn simple_op(name: &str) -> Option<Op> {
|
|||||||
"rot" => Op::Rot,
|
"rot" => Op::Rot,
|
||||||
"nip" => Op::Nip,
|
"nip" => Op::Nip,
|
||||||
"tuck" => Op::Tuck,
|
"tuck" => Op::Tuck,
|
||||||
|
"2dup" => Op::Dup2,
|
||||||
|
"2drop" => Op::Drop2,
|
||||||
|
"2swap" => Op::Swap2,
|
||||||
|
"2over" => Op::Over2,
|
||||||
"+" => Op::Add,
|
"+" => Op::Add,
|
||||||
"-" => Op::Sub,
|
"-" => Op::Sub,
|
||||||
"*" => Op::Mul,
|
"*" => Op::Mul,
|
||||||
@@ -2842,6 +2896,7 @@ pub(super) fn simple_op(name: &str) -> Option<Op> {
|
|||||||
"mstart" => Op::MidiStart,
|
"mstart" => Op::MidiStart,
|
||||||
"mstop" => Op::MidiStop,
|
"mstop" => Op::MidiStop,
|
||||||
"mcont" => Op::MidiContinue,
|
"mcont" => Op::MidiContinue,
|
||||||
|
"forget" => Op::Forget,
|
||||||
_ => return None,
|
_ => return None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use super::harness::*;
|
use super::harness::*;
|
||||||
|
use cagire::forth::Value;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn define_and_use_word() {
|
fn define_and_use_word() {
|
||||||
@@ -113,3 +114,44 @@ fn define_word_with_conditional() {
|
|||||||
f.evaluate("10 maybe-double", &ctx).unwrap();
|
f.evaluate("10 maybe-double", &ctx).unwrap();
|
||||||
assert_eq!(stack_int(&f), 20);
|
assert_eq!(stack_int(&f), 20);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn forget_removes_word() {
|
||||||
|
let f = forth();
|
||||||
|
let ctx = default_ctx();
|
||||||
|
f.evaluate(": double 2 * ;", &ctx).unwrap();
|
||||||
|
f.evaluate("5 double", &ctx).unwrap();
|
||||||
|
assert_eq!(stack_int(&f), 10);
|
||||||
|
f.clear_stack();
|
||||||
|
f.evaluate("\"double\" forget", &ctx).unwrap();
|
||||||
|
f.evaluate("double", &ctx).unwrap();
|
||||||
|
let stack = f.stack();
|
||||||
|
assert_eq!(stack.len(), 1);
|
||||||
|
match &stack[0] {
|
||||||
|
Value::Str(s, _) => assert_eq!(s, "double"),
|
||||||
|
other => panic!("expected Str, got {:?}", other),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn forget_nonexistent_is_noop() {
|
||||||
|
let f = forth();
|
||||||
|
let ctx = default_ctx();
|
||||||
|
f.evaluate("\"nosuchword\" forget", &ctx).unwrap();
|
||||||
|
f.evaluate("42", &ctx).unwrap();
|
||||||
|
assert_eq!(stack_int(&f), 42);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn forget_and_redefine() {
|
||||||
|
let f = forth();
|
||||||
|
let ctx = default_ctx();
|
||||||
|
f.evaluate(": foo 10 ;", &ctx).unwrap();
|
||||||
|
f.evaluate("foo", &ctx).unwrap();
|
||||||
|
assert_eq!(stack_int(&f), 10);
|
||||||
|
f.clear_stack();
|
||||||
|
f.evaluate("\"foo\" forget", &ctx).unwrap();
|
||||||
|
f.evaluate(": foo 20 ;", &ctx).unwrap();
|
||||||
|
f.evaluate("foo", &ctx).unwrap();
|
||||||
|
assert_eq!(stack_int(&f), 20);
|
||||||
|
}
|
||||||
|
|||||||
@@ -95,6 +95,46 @@ fn tuck_underflow() {
|
|||||||
expect_error("1 tuck", "stack underflow");
|
expect_error("1 tuck", "stack underflow");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn dup2() {
|
||||||
|
expect_stack("1 2 2dup", &[int(1), int(2), int(1), int(2)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn dup2_underflow() {
|
||||||
|
expect_error("1 2dup", "stack underflow");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn drop2() {
|
||||||
|
expect_stack("1 2 3 2drop", &[int(1)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn drop2_underflow() {
|
||||||
|
expect_error("1 2drop", "stack underflow");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn swap2() {
|
||||||
|
expect_stack("1 2 3 4 2swap", &[int(3), int(4), int(1), int(2)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn swap2_underflow() {
|
||||||
|
expect_error("1 2 3 2swap", "stack underflow");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn over2() {
|
||||||
|
expect_stack("1 2 3 4 2over", &[int(1), int(2), int(3), int(4), int(1), int(2)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn over2_underflow() {
|
||||||
|
expect_error("1 2 3 2over", "stack underflow");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn stack_persists() {
|
fn stack_persists() {
|
||||||
let f = forth();
|
let f = forth();
|
||||||
|
|||||||
Reference in New Issue
Block a user