Theory can leave questions unanswered, but practice has to come up with something.
-- Mason Cooley
The goal is to write a simple implementation of Forth. One that can handle simple
mathematical expressions. Once we have that down then we will add functionality
as needed for our application. Extreme Programming encourages writing the test
case first. I am creating a class Forth that can parse statements and manipulate
a stack. So I would like to be able to write code:
test.results(cout);
return 0;
}
Create an instance of the Forth class and then use it to execute a Forth statement.
I should be able to test the size of the stack and be able to test if the
element on the top of the stack is equal to 3. If UnitTest is a class for tracking
unit tests and has a member function
Forth f;
f.execute("1 2 +");
score() that keeps track of the test
results than our first test case should look like:
Compiling this code fails, as expected, because we lack the UnitTest class,
and the Forth class. We are also missing cout.
int main() {
UnitTest test;
Forth f;
f.execute("1 2 +");
f.stackSize() == 1;
test.score(f.stackSize() == 1, "StackSize");
test.score(f.top() == 3, "Addition");
Here is a first pass at the class Forth.
Note that
class Forth {
private:
maptop() and stackSize() are functions
that are already implemented in deque<int>. So if we
go forward with this implementation top() and stackSize()
will be just wrappers around deque<int>. That is a waste of time for
now so move stack_ into the public space. This gives us:
class Forth {
private:
map
Now our code still needs a UnitTest class. The test cases indicate only two
functions, score() and results().
class UnitTest {
private:
map
The member testResults_ will hold all the test results, either
pass or fail. The results() member function will be responsible
for tabulating the scores.
Because we are exporting stack_ we need to change our initial test case
to use the member functions of stack_:
return 0;
}
int main() {
UnitTest test;
Forth f;
f.execute("1 2 +");
test.score(f.stack_.size() == 1, "StackSize");
test.score(f.stack_.front() == 3, "Addition");
test.results(cout);
Filling in the definitions of our member functions we get:
class UnitTest {
private:
map
void score(bool result, const string & unitTestName) {
testResults_[unitTestName] = result;
}
void results(ostream & o) {
map<string, bool>::iterator i;
int totalTests = 0;
int totalPassed = 0;
for(i = testResults_.begin(); i != testResults_.end(); i++) {
o << (*i).first << " " << ((*i).second ? "Passed" : "Failed") << endl;
totalTests++;
totalPassed += ((*i).second ? 1 : 0);
}
o << endl;
o << "Total Test Cases: " << totalTests << endl;
o << "Total Test Cases Passed: " << totalPassed << endl;
if (totalTests != totalPassed) {
o << "100% of the test cases must pass before release!" << endl;
}
}
};
class Forth {
public:
deque
Forth() {}
Forth(const Forth & rhs) {
stack_ = rhs.stack_;
}
~Forth() {}
Forth & operator=(Forth & rhs) {
stack_ = rhs.stack_;
return *this;
}
bool execute(const string & command) {
bool returnValue = true;
char * s = strdup(command.c_str());
char * token = strtok(s, " ");
while (token) {
if (string("+") == token) {
int num1 = stack_.front();
stack_.pop_front();
int num2 = stack_.front();
stack_.pop_front();
stack_.push_front(num1 + num2);
} else {
stack_.push_front(atoi(token));
}
token = strtok(NULL, " ");
}
return returnValue;
}
};
int main() {
UnitTest test;
Forth f;
f.execute("1 2 +");
test.score(f.stack_.size() == 1, "StackSize");
test.score(f.stack_.front() == 3, "Addition");
test.results(cout);
return 0;
}
#include <iostream>
#include <map>
#include <deque>
#include <string.h>
using namespace std;
Which passes our tests and produces the output:
Addition Passed
StackSize Passed
Total Test Cases: 2
Total Test Cases Passed: 2
Inspecting the code reveals that execute()
is defined too narrowly. To add new functions such as
- and * to our parser would require adding new if test cases,
which is very sloppy. Also, the test cases we have written are pretty scant, the
copy contructor and assignment operator for the class Forth are untested as of now. The Forth
and UnitTest classes should be broken out into their own modules. Wow, there's a lot
of work to do...
The source code for the article can be found here.
Main forth : forth.cpp : ;