Skip to content

Commit

Permalink
forth: Two tests which have definitions not as the first word in an `…
Browse files Browse the repository at this point in the history
…eval` (exercism#962)

* Two tests which have definitions not as the first word in an `eval`

I've seen solutions to this exercise which test if the first character
or lexeme in an `eval` call is `:`, handle a definition if it is, and
then process the rest of the input as normal operations. I feel like
that shouldn't be allowed; a Forth interpreter should be able to
handle definitions interleaved with operations and sequences of
multiple definitions.

* Fix a bug in `forth` where user-defined words were deferred

This commit fixes a bug in the definition of `Forth::step_word` where,
to evaluate a user-defined word, it appended that word's definition to
the end of the current code list, which caused that word's definition
to be evaluated only after any remaining code. This commit makes
`step_word` corecursive with `step_term`; to evaluate a user-defined
word, it repeatedly invokes `step_term` on each of the terms in the
definition of that word.

This commit also adds calls to `eprintln` on several error cases, so
that failing tests will log to standard error when and where they fail.
  • Loading branch information
gefjon authored Aug 14, 2020
1 parent 2d163cf commit 4393330
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 19 deletions.
57 changes: 38 additions & 19 deletions exercises/forth/example.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,8 @@ impl Forth {

fn run(&mut self) -> ForthResult {
while let Some(term) = self.code.pop_front() {
self.step_term(term)?
self.step_term(term)?;
}

Forth::ok()
}

Expand All @@ -78,25 +77,38 @@ impl Forth {
Term::Number(value) => self.push(value),
Term::Word(word) => self.step_word(&word),
Term::StartDefinition => self.store_definition(),
Term::EndDefinition => Err(Error::InvalidWord),
Term::EndDefinition => {
eprintln!("`;` without preceding `:`");
Err(Error::InvalidWord)
}
}
}

fn step_word(&mut self, word: &str) -> ForthResult {
self.defs
.get(word)
.ok_or(Error::UnknownWord)
.map(Clone::clone)
.map(|mut code| self.code.append(&mut code))
.or_else(|_| self.step_built_in(word))
if let Some(def) = self.defs.get(word) {
let mut def = def.clone();
while let Some(term) = def.pop_front() {
self.step_term(term)?;
}
Self::ok()
} else {
self.step_built_in(word)
}
}

fn divide((a, b): (Value, Value)) -> StackResult<Value> {
a.checked_div(b).ok_or_else(|| {
eprintln!("Cannot divide {} by {}", a, b);
Error::DivisionByZero
})
}

fn step_built_in(&mut self, word: &str) -> ForthResult {
match word {
"+" => self.bin_op(|(a, b)| Ok(a + b)),
"-" => self.bin_op(|(a, b)| Ok(a - b)),
"*" => self.bin_op(|(a, b)| Ok(a * b)),
"/" => self.bin_op(|(a, b)| a.checked_div(b).ok_or(Error::DivisionByZero)),
"/" => self.bin_op(Self::divide),
"dup" => self.pop().and_then(|a| self.push(a).and(self.push(a))),
"drop" => self.pop().and(Forth::ok()),
"swap" => self
Expand All @@ -105,7 +117,10 @@ impl Forth {
"over" => self
.pop_two()
.and_then(|(a, b)| self.push(a).and(self.push(b)).and(self.push(a))),
_ => Err(Error::UnknownWord),
_ => {
eprintln!("{} is undefined", word);
Err(Error::UnknownWord)
}
}
}

Expand Down Expand Up @@ -133,7 +148,10 @@ impl Forth {
}

fn pop(&mut self) -> StackResult<Value> {
self.stack.pop_back().ok_or(Error::StackUnderflow)
self.stack.pop_back().ok_or_else(|| {
eprintln!("Stack underflow");
Error::StackUnderflow
})
}

fn pop_two(&mut self) -> StackResult<(Value, Value)> {
Expand All @@ -154,14 +172,15 @@ impl Forth {
for t in code.iter() {
match t {
Term::Number(_) => resolved_def.push_back(t.clone()),
Term::Word(s) => {
if let Some(cs) = self.defs.get(s) {
resolved_def.append(&mut cs.clone());
} else {
resolved_def.push_back(t.clone());
}
Term::Word(s) => if let Some(cs) = self.defs.get(s) {
resolved_def.append(&mut cs.clone());
} else {
resolved_def.push_back(t.clone());
}
_ => {
eprintln!("Nested definition in {}", name);
return Err(Error::InvalidWord);
}
_ => unimplemented!("not even sure a definition in a definition is valid Forth"),
}
}
self.defs.insert(name, resolved_def);
Expand Down
17 changes: 17 additions & 0 deletions exercises/forth/tests/forth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -343,3 +343,20 @@ fn calling_non_existing_word() {
let mut f = Forth::new();
assert_eq!(Err(Error::UnknownWord), f.eval("1 foo"));
}

#[test]
#[ignore]
fn multiple_definitions() {
let mut f = Forth::new();
assert!(f.eval(": one 1 ; : two 2 ; one two +").is_ok());
assert_eq!(vec![3], f.stack());
}

#[test]
#[ignore]
fn definitions_after_ops() {
let mut f = Forth::new();
assert!(f.eval("1 2 + : addone 1 + ; addone").is_ok());
assert_eq!(vec![4], f.stack());
}

0 comments on commit 4393330

Please sign in to comment.