Created
October 19, 2022 01:32
-
-
Save jtlapp/2e5509faac7f5307aa5d09905b6dc514 to your computer and use it in GitHub Desktop.
Exercism rust Forth exercise using closures
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Implements https://exercism.org/tracks/rust/exercises/forth. Errors on line 168. | |
use std::cell::RefCell; | |
use std::collections::HashMap; | |
pub type Value = i32; | |
pub type Result = std::result::Result<(), Error>; | |
#[derive(Debug, PartialEq, Eq)] | |
pub enum Error { | |
DivisionByZero, | |
StackUnderflow, | |
UnknownWord, | |
InvalidWord, | |
} | |
type Operation = dyn Fn(&mut Stack) -> Result; | |
type OperationMap = HashMap<String, Box<Operation>>; | |
type Stack = Vec<Value>; | |
pub struct Forth { | |
op_map: RefCell<OperationMap>, | |
stack: Stack, | |
} | |
impl Forth { | |
pub fn new() -> Self { | |
// discord/rust @kpreid provided assistance with initialization | |
let forth = Forth { | |
op_map: RefCell::new(HashMap::from([ | |
("+".into(), Box::new(|s: &mut Stack| Forth::add(s)) as _), | |
( | |
"-".into(), | |
Box::new(|s: &mut Stack| Forth::subtract(s)) as _, | |
), | |
( | |
"*".into(), | |
Box::new(|s: &mut Stack| Forth::multiply(s)) as _, | |
), | |
("/".into(), Box::new(|s: &mut Stack| Forth::divide(s)) as _), | |
("dup".into(), Box::new(|s: &mut Stack| Forth::dup(s)) as _), | |
("drop".into(), Box::new(|s: &mut Stack| Forth::drop(s)) as _), | |
("swap".into(), Box::new(|s: &mut Stack| Forth::swap(s)) as _), | |
("over".into(), Box::new(|s: &mut Stack| Forth::over(s)) as _), | |
])), | |
stack: vec![], | |
}; | |
forth | |
} | |
pub fn stack(&self) -> &[Value] { | |
&self.stack | |
} | |
pub fn eval(&mut self, input: &str) -> Result { | |
let terms = input.split(' ').collect::<Vec<&str>>(); | |
let mut i = 0; | |
while i < terms.len() { | |
let term = &terms[i]; | |
if *term == ":" { | |
i = self.define_word(&terms, i + 1)?; | |
} else { | |
Self::eval_term(&self.op_map.borrow(), term, &mut self.stack)? | |
} | |
} | |
Ok(()) | |
} | |
fn eval_term(op_map: &OperationMap, term: &str, stack: &mut Stack) -> Result { | |
match op_map.get(term) { | |
Some(op) => Ok(op(stack)?), | |
None => match term.parse::<Value>() { | |
Ok(value) => Ok(stack.push(value)), | |
Err(_) => return Err(Error::UnknownWord), | |
}, | |
} | |
} | |
fn add(stack: &mut Stack) -> Result { | |
let sum = Self::pop(stack)? + Self::pop(stack)?; | |
stack.push(sum); | |
Ok(()) | |
} | |
fn subtract(stack: &mut Stack) -> Result { | |
let diff = -Self::pop(stack)? + Self::pop(stack)?; | |
stack.push(diff); | |
Ok(()) | |
} | |
fn multiply(stack: &mut Stack) -> Result { | |
let product = Self::pop(stack)? * Self::pop(stack)?; | |
stack.push(product); | |
Ok(()) | |
} | |
fn divide(stack: &mut Stack) -> Result { | |
let divisor = Self::pop(stack)?; | |
if divisor == 0 { | |
return Err(Error::DivisionByZero); | |
} | |
let dividend = Self::pop(stack)?; | |
stack.push(dividend / divisor); | |
Ok(()) | |
} | |
fn dup(stack: &mut Stack) -> Result { | |
match stack.last() { | |
Some(value) => { | |
stack.push(*value); | |
Ok(()) | |
} | |
None => Err(Error::StackUnderflow), | |
} | |
} | |
fn drop(stack: &mut Stack) -> Result { | |
Self::pop(stack)?; | |
Ok(()) | |
} | |
fn swap(stack: &mut Stack) -> Result { | |
let last = Self::pop(stack)?; | |
let prior = Self::pop(stack)?; | |
stack.push(last); | |
stack.push(prior); | |
Ok(()) | |
} | |
fn over(stack: &mut Stack) -> Result { | |
match stack.get(stack.len() - 2) { | |
Some(value) => { | |
stack.push(*value); | |
Ok(()) | |
} | |
None => Err(Error::StackUnderflow), | |
} | |
} | |
fn pop(stack: &mut Stack) -> std::result::Result<Value, Error> { | |
match stack.pop() { | |
Some(value) => Ok(value), | |
None => Err(Error::StackUnderflow), | |
} | |
} | |
fn define_word( | |
&mut self, | |
words: &Vec<&str>, | |
mut i: usize, | |
) -> std::result::Result<usize, Error> { | |
if i < words.len() { | |
let word = words[i]; | |
let mut terms: Vec<String> = vec![]; | |
i += 1; | |
while i < words.len() { | |
let term = words[i]; | |
if term == ";" { | |
return if terms.len() == 0 { | |
Err(Error::InvalidWord) | |
} else { | |
// for moving terms into closure without moving self | |
let terms_holder = MacroEval { terms, forth: self }; | |
self.op_map.borrow_mut().insert( | |
word.into(), | |
Box::new(move |stack: &mut Stack| -> Result { | |
//ERROR: self would need to be 'static. Why? | |
terms_holder.do_op(stack) | |
}), | |
); | |
Ok(i + 1) | |
}; | |
} else { | |
terms.push(term.into()); | |
} | |
} | |
} | |
Err(Error::InvalidWord) | |
} | |
} | |
struct MacroEval<'a> { | |
terms: Vec<String>, | |
forth: &'a Forth, | |
} | |
impl<'a> MacroEval<'a> { | |
fn do_op(&self, stack: &mut Stack) -> Result { | |
for term in self.terms.iter() { | |
Forth::eval_term(&self.forth.op_map.borrow(), term, stack)? | |
} | |
Ok(()) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment