Lines
93.83 %
Functions
89.17 %
Branches
48.4 %
//use std::convert::TryFrom;
use std::collections::HashMap;
type StackType = i16;
type DWType = i32; // double widith type, used by */ to do more accurate math.
pub enum GasLimit {
Unlimited,
Limited(usize),
}
#[derive(Debug)]
pub enum StackMachineError {
UnkownError,
NumberStackUnderflow,
UnhandledTrap,
RanOutOfGas,
pub enum TrapHandled {
Handled,
NotHandled,
// Chain of Command Pattern
pub trait HandleTrap {
fn handle_trap(
&mut self,
trap_id: StackType,
st: &mut StackMachineState,
) -> Result<TrapHandled, StackMachineError>;
pub struct TrapHandler<'a> {
handled_trap: StackType,
to_run: Box<
dyn Fn(StackType, &mut StackMachineState) -> Result<TrapHandled, StackMachineError> + 'a,
>,
impl<'a> TrapHandler<'a> {
pub fn new<C>(handled_trap: StackType, f: C) -> TrapHandler<'a>
where
C: Fn(StackType, &mut StackMachineState) -> Result<TrapHandled, StackMachineError> + 'a,
{
TrapHandler {
handled_trap,
to_run: Box::new(f),
impl<'a> HandleTrap for TrapHandler<'a> {
trap_number: StackType,
) -> Result<TrapHandled, StackMachineError> {
if trap_number == self.handled_trap {
return (self.to_run)(self.handled_trap, st);
Ok(TrapHandled::NotHandled)
#[derive(Debug, Clone)]
pub enum Opcode {
// redirect flow
JMP,
JR,
JRZ,
JRNZ,
CALL,
// comparison ops
CMPZ,
CMPNZ,
EQ,
LT,
GT,
// load integer
LDI(StackType),
// stack ops
DROP,
SWAP,
TWODROP,
TWOSWAP,
RET,
DUP,
TWODUP,
QuestionDup,
TRAP,
OVER,
TWOOVER,
TWOROT,
ROT,
ToR,
RFrom,
RFetch,
DEPTH,
//math ops
ADD,
SUB,
MUL,
DIV,
MOD,
STARSLASH, // */ does *tiply then /ide using DWType (Double Width Type)
NEGATE,
OnePlus,
OneMinus,
TwoPlus,
TwoMinus,
LShift,
RShift,
// logic operators
NOT,
AND,
OR,
XOR,
ZeroLess,
ZeroEqual,
ZeroGreater,
// misc
NOP,
//variable
// VARIABLE(StackType), // used to put variable +resses on the stack.
BANG,
AT,
// GETCONSTANT(String),
SETCONSTANT(String),
CREATE(String),
ALLOT,
// io
PRINT,
EMIT,
CHAR(char),
KEY,
PRINTTEXT(String),
SPACE,
SPACES,
CR,
QuestionMark,
RightAlign,
// COUNT,
// TYPE,
//chars and strings
// SDoubleTick(String),
//random
SETSEED,
RAND,
DEBUG(String),
pub struct StackMachineState {
pub data_stack: Vec<StackType>,
pub return_stack: Vec<StackType>,
pub opcodes: Vec<Opcode>,
pub variable_addresses: HashMap<String, StackType>,
pub variables: Vec<StackType>,
pub constants: HashMap<String, StackType>,
pub text: Vec<String>,
pub memory: Vec<u8>,
// pub loop_stack: Vec<StackType>,
pc: usize,
gas_used: usize,
random_seed: usize,
debug: bool,
impl Default for StackMachineState {
fn default() -> Self {
Self::new()
impl StackMachineState {
pub fn new() -> StackMachineState {
let mut s = StackMachineState {
data_stack: Vec::new(),
return_stack: Vec::new(),
opcodes: Vec::new(),
variable_addresses: HashMap::new(),
variables: Vec::new(),
constants: HashMap::new(),
text: Vec::new(),
memory: Vec::new(),
// loop_stack: Vec::new(),
pc: 0,
gas_used: 0,
random_seed: 7,
debug: false,
};
// these are system defined constants.
s.constants.insert("FALSE".to_string(), 0);
s.constants.insert("TRUE".to_string(), 1);
s.constants.insert("BL".to_string(), 32); // blank ( ie space)
s
pub fn gas_used(&self) -> usize {
self.gas_used
pub struct StackMachine {
pub st: StackMachineState,
pub trap_handlers: Vec<Box<dyn HandleTrap>>,
impl Default for StackMachine {
impl StackMachine {
pub fn new() -> StackMachine {
StackMachine {
st: StackMachineState::new(),
trap_handlers: Vec::new(),
pub fn execute(
starting_point: usize,
gas_limit: GasLimit,
) -> Result<String, StackMachineError> {
self.st.gas_used = 0;
self.st.pc = starting_point;
let mut output: String = String::from("");
macro_rules! NS_POP {
() => {
self.st
.data_stack
.pop()
.ok_or(StackMachineError::NumberStackUnderflow)?
if self.st.debug {
println!("{:?}", self.st.opcodes);
loop {
println!("{}: opcode:{:?}", self.st.pc, self.st.opcodes[self.st.pc]);
let mut pc_reset = false;
match &self.st.opcodes[self.st.pc] {
Opcode::PRINT => {
let x = NS_POP!();
output.push_str(&format!("{} ", x).to_string());
Opcode::EMIT => {
let x: StackType = NS_POP!();
match x {
0..=127 => output.push(x as u8 as char),
_ => todo!(),
Opcode::CHAR(c) => match c.is_ascii() {
true => self.st.data_stack.push(*c as u8 as StackType),
false => todo!(),
},
Opcode::PRINTTEXT(s) => {
// println!("{}", s);
output.push_str(s);
Opcode::SPACE => {
output.push(' ');
Opcode::SPACES => {
for _i in 0..x {
Opcode::CR => {
output.push('\n');
Opcode::QuestionMark => {
let var_addr = NS_POP!() as usize;
let value = self.st.variables[var_addr];
output.push_str(&format!("{} ", value).to_string());
Opcode::RightAlign => {
let width = NS_POP!() as usize;
let value = NS_POP!() as usize;
output.push_str(&format!("{:width$}", value, width = width));
/*
Opcode::COUNT => {
let caddr1 = NS_POP!() as StackType;
self.st.data_stack.push(caddr1 + 1);
let len = self.st.memory[caddr1 as usize];
self.st.data_stack.push(len as StackType);
Opcode::TYPE => {
let caddr = NS_POP!() as usize;
let len = NS_POP!() as usize;
for i in 1..len {
output.push(self.st.memory[caddr + i] as char);
Opcode::SDoubleTick(s) => {
let addr = self.st.memory.len();
let bytes = s.as_str().as_bytes();
self.st.memory.push(bytes.len() as u8);
for mybyte in bytes.iter().take(bytes.len()) {
self.st.memory.push(*mybyte);
self.st.data_stack.push(bytes.len() as StackType);
self.st.data_stack.push(addr as StackType);
*/
Opcode::JR => {
self.st.pc = (self.st.pc as StackType + x) as usize;
pc_reset = true;
Opcode::CALL => {
self.st.return_stack.push(self.st.pc as StackType + 1);
self.st.pc = NS_POP!() as usize;
Opcode::TWOOVER => {
let x1 = NS_POP!();
let x2 = NS_POP!();
let y1 = NS_POP!();
let y2 = NS_POP!();
self.st.data_stack.push(y2);
self.st.data_stack.push(y1);
self.st.data_stack.push(x2);
self.st.data_stack.push(x1);
Opcode::OVER => {
let y = NS_POP!();
self.st.data_stack.push(y);
self.st.data_stack.push(x);
Opcode::ROT => {
let z = NS_POP!();
self.st.data_stack.push(z);
Opcode::TWOROT => {
let z1 = NS_POP!();
let z2 = NS_POP!();
self.st.data_stack.push(z2);
self.st.data_stack.push(z1);
Opcode::DEPTH => {
.push(self.st.data_stack.len() as StackType);
Opcode::CMPZ => {
if x == 0 {
self.st.data_stack.push(0);
} else {
self.st.data_stack.push(-1);
Opcode::CMPNZ => {
Opcode::EQ => {
if x == y {
Opcode::LT => {
println!("{} < {} = {}", y, x, y < x);
if y < x {
Opcode::GT => {
println!("{} > {} = {}", y, x, y > x);
if y > x {
Opcode::ZeroLess => {
self.st.data_stack.push(if x < 0 { 1 } else { 0 });
Opcode::ZeroEqual => {
self.st.data_stack.push(if x == 0 { 1 } else { 0 });
Opcode::ZeroGreater => {
self.st.data_stack.push(if x > 0 { 1 } else { 0 });
Opcode::JRZ => {
if y == 0 {
Opcode::JRNZ => {
if y != 0 {
Opcode::JMP => {
Opcode::LDI(x) => self.st.data_stack.push(*x),
Opcode::DROP => {
let _x = NS_POP!();
Opcode::TWODROP => {
Opcode::RET => {
match self.st.return_stack.pop() {
None => return Ok(output),
Some(oldpc) => self.st.pc = oldpc as usize,
Opcode::NEGATE => {
self.st.data_stack.push(-x);
Opcode::ADD => {
self.st.data_stack.push(x + y);
Opcode::SUB => {
self.st.data_stack.push(y - x);
Opcode::MUL => {
self.st.data_stack.push(x * y);
Opcode::DIV => {
self.st.data_stack.push(y / x);
Opcode::MOD => {
self.st.data_stack.push(y % x);
Opcode::STARSLASH => {
// used Double with so that math is more accurate..
let x: DWType = NS_POP!() as DWType;
let y: DWType = NS_POP!() as DWType;
let z: DWType = NS_POP!() as DWType;
let temp = (z * y / x) as StackType;
self.st.data_stack.push(temp as StackType);
Opcode::OnePlus => {
self.st.data_stack.push(x + 1);
Opcode::OneMinus => {
self.st.data_stack.push(x - 1);
Opcode::TwoPlus => {
self.st.data_stack.push(x + 2);
Opcode::TwoMinus => {
self.st.data_stack.push(x - 2);
Opcode::LShift => {
self.st.data_stack.push(y << x);
Opcode::RShift => {
self.st.data_stack.push(y >> x);
// logical operators
Opcode::NOT => {
self.st.data_stack.push(!x);
Opcode::AND => {
self.st.data_stack.push(x & y);
Opcode::OR => {
self.st.data_stack.push(x | y);
Opcode::VARIABLE(addr) => {
self.st.data_stack.push(*addr);
Opcode::CREATE(var) => {
if let std::collections::hash_map::Entry::Vacant(e) =
self.st.variable_addresses.entry(var.to_uppercase())
let addr = self.st.variables.len();
e.insert(addr as StackType);
self.st.variables.push(0); // initialize value to zero.
// output error saying that the variable already exists..
Opcode::ALLOT => {
let size = NS_POP!() as usize;
let addr = NS_POP!() as usize;
for i in 0..size {
self.st.memory[addr + i] = 0;
Opcode::BANG => {
let value = NS_POP!();
self.st.variables[var_addr] = value;
Opcode::AT => {
self.st.data_stack.push(value);
Opcode::GETCONSTANT(con) => {
match self.st.constants.get(con) {
Some(x) => self.st.data_stack.push(*x),
None => todo!(), //Err(ForthError::ConstantDoesNotExist(con.to_string())),
Opcode::SETCONSTANT(con) => {
self.st.constants.insert(con.to_string(), x);
Opcode::XOR => {
self.st.data_stack.push(x ^ y);
Opcode::DUP => {
Opcode::TWODUP => {
Opcode::QuestionDup => {
if x != 0 {
Opcode::SWAP => {
Opcode::TWOSWAP => {
Opcode::TRAP => {
// We are going to say that TRAPs always have a numeric code on the number stack to define which TRAP is being called
let trap_id = NS_POP!();
for h in self.trap_handlers.iter_mut() {
if let TrapHandled::Handled = h.handle_trap(trap_id, &mut self.st)? {
return Ok("".to_string());
return Err(StackMachineError::UnhandledTrap);
Opcode::ToR => {
//takes a value from the parameter( number) stack and push it on the return stack
// println!("TOR {}", x);
self.st.return_stack.push(x);
// println!("{:?}", self.st.return_stack);
Opcode::RFrom => {
// takes a value from the return stack and put it on the parameter stack.
Some(x) => self.st.data_stack.push(x),
Opcode::RFetch => {
// copies the value from the return stack and puts it on the number stack.
Some(x) => {
Opcode::NOP => {}
// sets random seed
Opcode::SETSEED => {
self.st.random_seed = NS_POP!() as usize;
// pushes a random number on the stack.
Opcode::RAND => {
self.st.random_seed ^= self.st.random_seed << 13;
self.st.random_seed ^= self.st.random_seed >> 17;
self.st.random_seed ^= self.st.random_seed << 5;
self.st.data_stack.push(self.st.random_seed as StackType)
Opcode::KEY => todo!(), // need to find a way to do this in rust.. (detect keydown, and get value of key in ascii)
Opcode::DEBUG(_s) => {} // ignore.. this is for debugging the stack.
if !pc_reset {
self.st.pc += 1;
self.st.gas_used += 1;
//if self.st.debug {
// println!("after data stack: {:?}", self.st.data_stack);
// println!("after return stack: {:?}", self.st.return_stack);
// println!("================");
//}
if let GasLimit::Limited(x) = gas_limit {
if self.st.gas_used > x {
return Err(StackMachineError::RanOutOfGas);
//Ok(format!("{} ok.", output))
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_char() {
let mut sm = StackMachine::new();
sm.st
.opcodes
.extend_from_slice(&[Opcode::CHAR('a'), Opcode::RET]);
sm.execute(0, GasLimit::Limited(100)).unwrap();
assert_eq!(sm.st.data_stack, vec![97]);
fn test_rshift() {
sm.st.data_stack.extend_from_slice(&[4, 1]);
// Put the opcodes into the *memory*
.extend_from_slice(&[Opcode::RShift, Opcode::RET]);
assert_eq!(sm.st.data_stack, vec![2]);
sm.st.data_stack.clear();
sm.st.data_stack.extend_from_slice(&[16, 2]);
assert_eq!(sm.st.data_stack, vec![4]);
fn test_lshift() {
.extend_from_slice(&[Opcode::LShift, Opcode::RET]);
assert_eq!(sm.st.data_stack, vec![8]);
sm.st.data_stack.extend_from_slice(&[8, 2]);
assert_eq!(sm.st.data_stack, vec![32]);
fn test_tor() {
// Populate the number stack
sm.st.data_stack.extend_from_slice(&[1]);
//sm.st.return_stack.extend_from_slice(&[0]);
sm.st.opcodes.extend_from_slice(&[Opcode::ToR, Opcode::RET]);
assert_eq!(sm.st.data_stack.len(), 0);
// assert_eq!(sm.st.return_stack, vec![1]); value on return stack is removed with the RET code
fn test_rfrom() {
sm.st.return_stack.extend_from_slice(&[1]);
.extend_from_slice(&[Opcode::RFrom, Opcode::RET]);
assert_eq!(sm.st.data_stack, vec![1]);
assert!(sm.st.return_stack.is_empty());
fn test_rfetch() {
.extend_from_slice(&[Opcode::RFetch, Opcode::RET]);
// assert_eq!(sm.st.return_stack, vec![1]); // RET will remove the 1 from the return stack
fn test_print() {
.extend_from_slice(&[Opcode::PRINT, Opcode::RET]);
assert_eq!(sm.execute(0, GasLimit::Limited(100)).unwrap(), "1 ");
fn test_negate() {
.extend_from_slice(&[Opcode::NEGATE, Opcode::RET]);
assert_eq!(sm.st.data_stack, vec![-1]);
fn test_rot() {
sm.st.data_stack.extend_from_slice(&[1, 2, 3]);
sm.st.opcodes.extend_from_slice(&[Opcode::ROT, Opcode::RET]);
assert_eq!(sm.st.data_stack, vec![2, 3, 1]);
assert!(sm.st.gas_used() > 0);
fn test_over() {
.extend_from_slice(&[Opcode::OVER, Opcode::RET]);
assert_eq!(sm.st.data_stack, vec![1, 2, 3, 2]);
fn test_execute_jr_forward() {
sm.st.data_stack.extend_from_slice(&[321, 394]);
sm.st.opcodes.extend_from_slice(&[
Opcode::LDI(0),
Opcode::LDI(1),
Opcode::LDI(2),
Opcode::LDI(2), // Jump to location 6 with the JR statement, relative jump of 1
Opcode::JR,
Opcode::LDI(3),
Opcode::LDI(4),
Opcode::LDI(5),
Opcode::RET,
]);
// Execute the instructions
assert_eq!(sm.st.data_stack, vec![321, 394, 0, 1, 2, 4, 5]);
fn test_execute_jr_backward() {
Opcode::LDI(-5), // Jump to the LDI(0)
sm.execute(3, GasLimit::Limited(100)).unwrap();
assert_eq!(sm.st.data_stack, vec![321, 394, 2, 0, 1]);
fn test_execute_jrz_forward() {
Opcode::LDI(1), // This won't happen because TOS won't be zero...
Opcode::LDI(2), // TOS for JRZ
Opcode::JRZ,
Opcode::LDI(2), // Relative Jump of 1
Opcode::JRZ, // Jump over the LDI(6)
Opcode::LDI(6),
Opcode::LDI(7),
Opcode::LDI(8),
assert_eq!(sm.st.data_stack, vec![321, 394, 0, 1, 2, 3, 4, 5, 7, 8]);
fn test_execute_jrz_backward() {
Opcode::LDI(-2), // TOS for JRZ
Opcode::LDI(-12), // Relative Jump to start of code
sm.execute(2, GasLimit::Limited(100)).unwrap();
assert_eq!(sm.st.data_stack, vec![321, 394, 1, 2, 3, 4, 5, 0]);
fn test_execute_jrnz_forward() {
Opcode::LDI(0), // This won't happen because TOS is zero...
Opcode::JRNZ,
Opcode::JRNZ, // Jump over the LDI(6)
fn test_execute_jrnz_backward() {
fn test_execute_cmpz_1() {
sm.st.data_stack.extend_from_slice(&[123, 321, 0]);
.extend_from_slice(&[Opcode::CMPZ, Opcode::RET]);
assert_eq!(sm.st.data_stack, vec![123, 321, 0]);
fn test_execute_cmpz_2() {
sm.st.data_stack.extend_from_slice(&[123, 321, 1]);
assert_eq!(sm.st.data_stack, vec![123, 321, -1]);
fn test_execute_cmpnz_1() {
.extend_from_slice(&[Opcode::CMPNZ, Opcode::RET]);
fn test_execute_cmpnz_2() {
fn test_execute_eq() {
sm.st.data_stack.extend_from_slice(&[123, 321]);
sm.st.opcodes.extend_from_slice(&[Opcode::EQ, Opcode::RET]);
assert_eq!(sm.st.data_stack, vec![0]);
// 0 = 0 => true (-1)
sm.st.data_stack.extend_from_slice(&[0]);
fn test_execute_lt() {
sm.st.opcodes.extend_from_slice(&[Opcode::LT, Opcode::RET]);
sm.st.data_stack.extend_from_slice(&[-1]);
fn test_execute_gt() {
sm.st.opcodes.extend_from_slice(&[Opcode::GT, Opcode::RET]);
sm.st.data_stack.extend_from_slice(&[-2]);
fn test_execute_call() {
Opcode::CALL,
Opcode::LDI(10),
Opcode::LDI(15),
Opcode::LDI(20),
Opcode::LDI(25),
Opcode::LDI(9),
assert_eq!(
sm.st.data_stack,
vec![321, 394, 0, 2, 4, 6, 8, 9, 7, 5, 3, 1]
);
fn test_emit() {
sm.st.data_stack.extend_from_slice(&[65, 66]);
.extend_from_slice(&[Opcode::EMIT, Opcode::EMIT, Opcode::RET]);
// AB should be returned by execute
assert_eq!(sm.execute(0, GasLimit::Limited(100)).unwrap(), "BA");
// now just assume that the output is printed..
// maybe change how this program caches and displays output?
assert_eq!(sm.st.data_stack, vec![]);
fn test_execute_ldi() {
assert_eq!(sm.st.data_stack, vec![321, 394, 0, 1, 2]);
fn test_execute_drop() {
Opcode::DROP,
assert_eq!(sm.st.data_stack, vec![321, 394, 0, 2]);
#[should_panic]
fn test_execute_drop_error() {
fn test_execute_swap() {
Opcode::SWAP,
assert_eq!(sm.st.data_stack, vec![321, 394, 1, 0, 2]);
fn test_execute_add() {
sm.st.opcodes.extend_from_slice(&[Opcode::ADD, Opcode::RET]);
assert_eq!(sm.st.data_stack, vec![444]);
//noather example
sm.st.data_stack.extend_from_slice(&[17, 20, 132, 3, 9]);
sm.st.opcodes.clear();
sm.st.opcodes.push(Opcode::ADD);
sm.st.opcodes.push(Opcode::RET);
assert_eq!(sm.st.data_stack, vec![17, 20, 132, 12]);
assert_eq!(sm.st.data_stack, vec![17, 20, 144]);
println!("{}", sm.execute(0, GasLimit::Limited(100)).unwrap());
assert_eq!(sm.st.data_stack, vec![181]);
fn test_execute_sub() {
sm.st.data_stack.extend_from_slice(&[444, 321]);
sm.st.opcodes.extend_from_slice(&[Opcode::SUB, Opcode::RET]);
assert_eq!(sm.st.data_stack, vec![123]);
fn test_execute_mul() {
sm.st.data_stack.extend_from_slice(&[321, 2]);
sm.st.opcodes.extend_from_slice(&[Opcode::MUL, Opcode::RET]);
assert_eq!(sm.st.data_stack, vec![642]);
fn test_execute_div() {
sm.st.data_stack.extend_from_slice(&[642, 321]);
sm.st.opcodes.extend_from_slice(&[Opcode::DIV, Opcode::RET]);
fn test_execute_mod() {
sm.st.data_stack.extend_from_slice(&[10, 3]);
sm.st.opcodes.extend_from_slice(&[Opcode::MOD, Opcode::RET]);
fn test_execute_starslash() {
// ie */
sm.st.data_stack.extend_from_slice(&[2000, 34, 100]);
.extend_from_slice(&[Opcode::STARSLASH, Opcode::RET]);
assert_eq!(sm.st.data_stack, vec![680]);
fn test_execute_not() {
sm.st.data_stack.extend_from_slice(&[321, 0]);
sm.st.opcodes.extend_from_slice(&[Opcode::NOT, Opcode::RET]);
assert_eq!(sm.st.data_stack, vec![321, -1]);
assert_eq!(sm.st.data_stack, vec![321, 0]);
fn test_execute_and() {
sm.st.data_stack.extend_from_slice(&[15, 1]);
sm.st.opcodes.extend_from_slice(&[Opcode::AND, Opcode::RET]);
fn test_execute_or() {
sm.st.data_stack.extend_from_slice(&[8, 7]);
sm.st.opcodes.extend_from_slice(&[Opcode::OR, Opcode::RET]);
assert_eq!(sm.st.data_stack, vec![15]);
fn test_execute_xor() {
sm.st.data_stack.extend_from_slice(&[16, 17]);
sm.st.opcodes.extend_from_slice(&[Opcode::XOR, Opcode::RET]);
fn test_execute_dup() {
sm.st.data_stack.extend_from_slice(&[123, 394]);
sm.st.opcodes.extend_from_slice(&[Opcode::DUP, Opcode::RET]);
assert_eq!(sm.st.data_stack, vec![123, 394, 394]);
fn test_execute_run_out_of_gas() {
sm.execute(0, GasLimit::Limited(10)).unwrap();
fn test_handle_trap_1() {
sm.trap_handlers
.push(Box::from(TrapHandler::new(100, |_trap_id, st| {
st.data_stack
.ok_or(StackMachineError::NumberStackUnderflow)?;
st.data_stack.push(200);
Ok(TrapHandled::Handled)
})));
sm.st.data_stack.extend_from_slice(&[50, 100]);
.extend_from_slice(&[Opcode::TRAP, Opcode::RET]);
assert_eq!(sm.st.data_stack, vec![200]);
fn test_handle_trap_2() {
.push(Box::from(TrapHandler::new(-100, |_trap_id, st| {
st.data_stack.push(-100);
.push(Box::from(TrapHandler::new(-200, |_trap_id, st| {
st.data_stack.push(-200);
// Populate the number stack, with a value (50), and the trap number (100)
fn test_unhandled_trap_1() {
match sm.execute(0, GasLimit::Limited(100)) {
Err(StackMachineError::UnhandledTrap) => (),
r => panic!("Incorrect error type returned {:?}", r),