I made a small stack-oriented language in Rust. It tries to run anything it is given. To elaborate: Division by zero gives zero. If there is nothing on the stack and a request is made that involves popping off the stack, instead of just throwing an error, it acts as if 0.0
is always available on the stack. Currently it just supports a few small basic arithmetical operations, swapping the stack, duplicating the stack, printing and a jump command. It is a smaller version of the simpleStack language. The tests are currently more like sanity checks. Here is the code:
src/lib.rs
mod words;
/// Given a list of commands, execute the commands.
///
/// # Arguments
///
/// * `tokens` - A slice of tokens to be executed.
/// * `stack` - The stack to keep the current state of the program.
fn execute_program(tokens: &[&str],
stack: &mut Vec<f64>,
output: &mut Vec<f64>) -> Vec<f64> {
// Analogous to the role of a "register" for a Turing machine.
let mut reg: usize = 0;
loop {
let tok = tokens.get(reg);
match tok {
Some(&"+") => words::add(stack),
Some(&"-") => words::sub(stack),
Some(&"*") => words::mul(stack),
Some(&"/") => words::div(stack),
Some(&"dup") => words::dup(stack),
Some(&"swp") => words::swp(stack),
Some(&"jnz") => words::jnz(stack, &mut reg),
Some(&"print") => words::print_float(stack, output),
Some(_) => words::parse_number(tok.unwrap(), stack),
None => break
}
reg += 1;
}
output.to_vec()
}
/// Evaluates a string of code.
///
/// # Arguments
///
/// * `code` - The string of code to be executed.
///
/// *Note* The value returned is the "output" of the code. Output is not done
/// through stdout for easier debugging.
pub fn eval(code: String) -> Vec<f64> {
let tokens: Vec<&str> = code.split(' ').collect();
let mut stack: Vec<f64> = Vec::new();
let mut output: Vec<f64> = Vec::new();
execute_program(tokens.as_slice(), &mut stack, &mut output)
}
src/words.rs
//! The `word` module contains the verbs and nouns that create a program. Verbs
//! are functions (regardless of airity) and nouns are data.
/// Extracts two values off the top of a stack.
///
/// # Arguments
///
/// * `$stack` - stack to be mutated.
macro_rules! get_ops {
($stack:expr) => {
($stack.pop().unwrap_or(0.0),
$stack.pop().unwrap_or(0.0))
}
}
/// Parses a numerical value to a float.
///
/// # Arguments
///
/// `token` - The value to be converted to a float.
/// `stack` - The stack to push the token onto.
///
/// *Note* - If `parse_number` is **not** given a number, it will still return
/// `0.0`.
#[inline(always)]
pub fn parse_number(token: &str, stack: &mut Vec<f64>) {
let number = token.parse::<f64>().unwrap_or(0.0);
stack.push(number);
}
/// Pops the top two elements off the stack and adds them.
///
/// # Arguments
///
/// * `stack` - The stack to pop from and push onto.
///
/// *Note* - If no number is available to pop from the stack, a default value
/// of `0.0` is used.
#[inline(always)]
pub fn add(stack: &mut Vec<f64>) {
let (a, b) = get_ops!(stack);
stack.push(a + b);
}
/// Pops the top two elements off the stack and subtracts them.
///
/// # Arguments
///
/// * `stack` - The stack to pop from and push onto.
///
/// *Note* - If no number is available to pop from the stack, a default value
/// of `0.0` is used.
#[inline(always)]
pub fn sub(stack: &mut Vec<f64>) {
let (a, b) = get_ops!(stack);
stack.push(a - b);
}
/// Pops the top two elements off the stack and multiplies them.
///
/// # Arguments
///
/// * `stack` - The stack to pop from and push onto.
///
/// *Note* - If no number is available to pop from the stack, a default value
/// of `0.0` is used.
#[inline(always)]
pub fn mul(stack: &mut Vec<f64>) {
let (a, b) = get_ops!(stack);
stack.push(a * b);
}
/// Pops the top two elements off the stack and divides them.
///
/// # Arguments
///
/// * `stack` - The stack to pop from and push onto.
///
/// *Note* - If no number is available to pop from the stack, a default value
/// of `0.0` is used. If division by `0.0` occurs, then a value of `0.0` pushed
/// to `stack` instead.
#[inline(always)]
pub fn div(stack: &mut Vec<f64>) {
let (a, b) = get_ops!(stack);
if b == 0.0 {
stack.push(0.0);
} else {
stack.push(a / b);
}
}
/// Pops the top element off the stack and pushes two copies of it on the stack.
///
/// # Arguments
///
/// * `stack` - The stack to pop from and push onto.
///
/// *Note* - If no number is available to pop from the stack, a default value
/// of `0.0` is used, thus `0.0` is pushed on to the stack twice.
#[inline(always)]
pub fn dup(stack: &mut Vec<f64>) {
let to_dup = stack.pop().unwrap_or(0.0);
stack.push(to_dup);
stack.push(to_dup);
}
/// Pops the top two elements off the stack and swaps their values.
///
/// # Arguments
///
/// * `stack` - The stack to pop from and push onto.
///
/// *Note* - If no number is available to pop from the stack, a default value
/// of `0.0` is used.
#[inline(always)]
pub fn swp(stack: &mut Vec<f64>) {
let (first, second) = get_ops!(stack);
stack.push(second);
stack.push(first);
}
/// Pops off two values off the stack. If the first value is not zero, take the
/// value of the second value and jump to that location in code.
///
/// # Arguments
///
/// * `reg` - The the current location of the register.
/// * `stack` - The stack to pop from and push onto.
///
#[inline(always)]
pub fn jnz(stack: &mut Vec<f64>, reg: &mut usize) {
let (cond, jump) = get_ops!(stack);
if cond != 0.0 {
*reg = jump as usize;
}
}
/// Prints the top value of a particular stack.
///
/// # Arguments
///
/// * `stack` - The stack to pop from.
/// * `output` - The output vector to push onto.
///
/// *Note* - Does not "print" to stdout, instead it prints to the `output` par-
/// ameter. This is for better debugging and test.
#[inline(always)]
pub fn print_float(stack: &mut Vec<f64>, output: &mut Vec<f64>) {
output.push(stack.pop().unwrap_or(0.0))
}
src/bin/imastack.rs
extern crate imastack;
use std::io;
use std::io::Write;
/// Simple REPL for the imastack langauge.
fn main() {
loop {
let mut code = String::new();
print!("> ");
io::stdout().flush().unwrap();
io::stdin().read_line(&mut code)
.expect("Failed to read line");
let output = imastack::eval(code.trim().to_string());
for num in output {
print!("{} ", num);
}
println!()
}
}
Cargo.toml
[package]
name = "imastack"
version = "0.1.0"
authors = ["Christopher Sumnicht <[email protected]>"]
tests/integration_test.rs
extern crate imastack;
#[test]
fn basic_add() {
assert_eq!(
imastack::eval("1 2 + print".to_string()),
vec![3.0]);
}
#[test]
fn basic_sub() {
assert_eq!(
imastack::eval("1 2 - print".to_string()),
vec![1.0]);
}
#[test]
fn basic_mul() {
assert_eq!(
imastack::eval("3 3 * print".to_string()),
vec![9.0]);
}
#[test]
fn basic_div() {
assert_eq!(
imastack::eval("3 6 / print".to_string()),
vec![2.0]);
}
#[test]
fn div_by_zero_is_zero() {
assert_eq!(
imastack::eval("0 1 / print".to_string()),
vec![0.0]);
}
#[test]
fn basic_swp() {
assert_eq!(
imastack::eval("1 2 swp print print".to_string()),
vec![2.0, 1.0]);
}
#[test]
fn basic_dup() {
assert_eq!(
imastack::eval("1 dup print print".to_string()),
vec![1.0, 1.0]);
}
#[test]
fn basic_jnz() {
assert_eq!(
imastack::eval("1 4 jnz 0 1 print".to_string()),
vec![1.0]);
}
Here is the Cargo project on Github. A basic REPL can be launched with cargo run --bin imastack
.