THE CODE
This isn't Charley's unmodified code -- has local mods.
/* public_aa.ino */
/* Tue Feb 21 20:48:49 UTC 2017 */
/* locally from: interpreter-cortex_m0.ino */
const char versionbuf[] = "VERSION 0.0.01bP ";
/* mcu: Atmel SAMD21G18A
board: Arduino M0 Pro
Technology: ARM Cortex M0+ */
/* derived from: a Forth-like interpreter for the M0
in the Arduino IDE language environment,
by Charley Shattuck */
/* https://github.com/CharleyShattuck/Feather-M0-interpreter
A Forth text interpreter for the Adafruit Feather M0, using the Arduino IDE.
Charley says:
This example code is in the public domain */
/* ***************** code **************************** */
/* Structure of a dictionary entry */
typedef struct {
const char *name;
void (*function)();
} entry;
/* const stores in flashROM: */
const char str[] = "My rather curiously long string, as deposited in flashROM.";
/* simulated stack, with a storage depth of 16 integer values */
const int STKSIZE = 16; // must be a power of 2
const int STKMASK = 15; // STKSIZE less 1
int stack[STKSIZE]; // let's call it a stack as that is how it will be used, sort-of.
int p = 0;
/* TOS is Top Of Stack */
#define TOS stack[p]
/* NAMED creates a string in flash */
#define NAMED(x, y) const char x[]=y
/* Terminal Input Buffer for interpreter */
const byte maxtib = 16;
char tib[maxtib];
/* buffer required for strings read from flash */
char namebuf[maxtib];
byte pos;
/* push n to top of data stack */
void push(int n) {
p = (p + 1)& STKMASK;
TOS = n;
}
/* return top of stack */
int pop() {
int n = TOS;
p = (p - 1)& STKMASK;
return n;
}
/* ************* forth words ************* */
/* discard top of stack */
NAMED(_drop, "drop");
void drop() {
pop();
}
/* recover dropped stack item */
NAMED(_back, "back");
void back() {
for (int i = 1; i < STKSIZE; i++) drop();
}
/* copy top of stack */
NAMED(_dup, "dup");
void dup() {
push(TOS);
}
/* exchange top two stack items */
NAMED(_swap, "swap");
void swap() {
int a;
int b;
a = pop();
b = pop();
push(a);
push(b);
}
/* add top two items */
NAMED(_add, "+");
void add() {
int a = pop();
TOS = a + TOS;
}
/* locally-contributed code follows */
/* multiply top two items */
NAMED(_mult, "*");
void mult() {
int a = pop();
int b = pop();
TOS = a * b;
}
/* invert all bits in top of stack */
NAMED(_invert, "invert");
void invert() {
TOS = ~(TOS);
}
/* negate top of stack */
NAMED(_negate, "negate");
void negate() {
TOS = -(TOS);
}
/* push boolean 'true' (-1) */
NAMED(_true, "true");
void true_() {
push(1);
negate();
}
/* consider the binary representation for the numbers
expressed in decimal, here */
/* logic is inverted, not negated */
/* push boolean 'false' (0) onto the stack */
NAMED(_false, "false");
void false_() {
true_();
invert();
}
/* is TOS equal to TOS -1? */
NAMED(_eq, "="); // ( n n -- bool ) consumes both arguments, returns boolean
void eq() {
int a;
int b;
a = pop();
b = pop();
if ( a == b ) {
true_();
}
}
/* Is TOS equal to zero? */
NAMED(_zeq, "0="); // ( n -- bool ) yes it consumes TOS
void zeq() {
int a = 0;
push(a);
eq();
}
/* need a visible consequence to a boolean decision,
for testing at the serial tty (console) linux knows
as the /dev/ttyACM0 tty device.
So, 'truemsg' and 'falsemsg'.
*/
/* smart mouthed computer */
NAMED(_truemsg, "truemsg");
void truemsg() {
char buffer[10] = "So true. "; Serial.print(buffer);
}
/* and a bit curt or abrupt. */
NAMED(_falsemsg, "falsemsg");
void falsemsg() {
char buffer[15] = "That's false. "; Serial.print(buffer);
}
/* "is not equal to zero */
NAMED(_zneq, "0<>"); // ( bool -- )
void zneq() {
int bool_ = pop();
if (bool_) {
truemsg();
}
else {
falsemsg();
}
}
NAMED(_empty, "empty"); // ( n n n n ... -- ) empty the stack or zero it out, in this case
void empty() {
for (int i = 0; i < 18; i++) push(0);
}
/* end of locally-added code */
/* resume original author's coding: */
/* destructively display top of stack, decimal */
NAMED(_dot, ".");
void dot() {
Serial.print(pop());
Serial.print(" ");
}
/* display whole stack, decimal */
NAMED(_dotS, ".s");
void dotS() {
for (int i = 0; i < STKSIZE; i++) dot();
}
/* dump 16 bytes of RAM in hex with ascii on the side */
void dumpRAM() {
char buffer[5] = "";
char *ram;
int p = pop();
ram = (char*)p;
sprintf(buffer, "%4x", p);
Serial.print(buffer);
Serial.print(" ");
for (int i = 0; i < 16; i++) {
char c = *ram++;
sprintf(buffer, " %2x", (c & 0xff));
Serial.print(buffer);
}
ram = (char*)p;
Serial.print(" ");
for (int i = 0; i < 16; i++) {
buffer[0] = *ram++;
if (buffer[0] > 0x7f || buffer[0] < ' ') buffer[0] = '.';
buffer[1] = '\0';
Serial.print(buffer);
}
push(p + 16);
}
/* dump 256 bytes of RAM */
NAMED(_dumpr, "dump");
void rdumps() {
for (int i = 0; i < 16; i++) {
Serial.println();
dumpRAM();
}
}
/* more local code */
char ascii_char;
/* send one ascii character to the serial port */
NAMED(_emit, "emit");
void emit() {
ascii_char = pop();
Serial.print(ascii_char);
}
NAMED(_2emit_, "2emit");
void _2emit() {
emit();
emit();
}
/* everybody loves a nop */
NAMED(_nopp, "nop");
void nopp() { }
/* End of Forth interpreter words */
/* Now build the dictionary */
/* empty words don't cause an error */
NAMED(_nop, " ");
void nop() { }
/* Forward declaration required here */
NAMED(_words, "words");
void words();
/* How many words are in the dictionary? */
NAMED(_entries_, "entries");
void _entries();
/* table of names and function addresses in flash */
const entry dictionary[] = {
{_nop, nop},
{_words, words},
{_entries_, _entries},
{_drop, drop},
{_dup, dup},
{_back, back},
{_swap, swap},
{_add, add},
{_mult, mult},
{_invert, invert},
{_negate, negate},
{_true, true_},
{_false, false_},
{_eq, eq},
{_zeq, zeq},
{_truemsg, truemsg},
{_falsemsg, falsemsg},
{_zneq, zneq},
{_empty, empty},
{_dotS, dotS},
{_dot, dot},
{_dumpr, rdumps},
{_2emit_, _2emit},
{_emit, emit},
{_nopp, nopp},
};
const int entries = sizeof dictionary / sizeof dictionary[0];
/* Display all words in dictionary */
void words() {
for (int i = entries - 1; i >= 0; i--) {
strcpy(namebuf, dictionary[i].name);
Serial.print(namebuf);
Serial.print(" ");
}
}
/* how many words are there? */
void _entries() {
int a;
a = entries;
Serial.print(a);
Serial.print(" ");
}
/* Find a word in the dictionary, returning its position */
int locate() {
for (int i = entries; i >= 0; i--) {
strcpy(namebuf, dictionary[i].name);
if (!strcmp(tib, namebuf)) return i;
}
return 0;
}
/* Is the word in tib a number? */
int isNumber() {
char *endptr;
strtol(tib, &endptr, 0);
if (endptr == tib) return 0;
if (*endptr != '\0') return 0;
return 1;
}
/* Convert number in tib */
int number() {
char *endptr;
return (int) strtol(tib, &endptr, 0);
}
char ch;
void ok() {
if (ch == '\r') Serial.print("ok\r\n");
}
/* Incrementally read command line from serial port */
byte reading() {
if (!Serial.available()) return 1;
ch = Serial.read();
Serial.print(ch); // char-by-char input, echo
if (ch == '\n') {
Serial.print("\r\n"); // echo
return 1;
}
if (ch == '\r') {
return 0;
}
if (ch == ' ') return 0;
if (pos < maxtib) {
tib[pos++] = ch;
tib[pos] = 0;
}
return 1;
}
/* Block on reading the command line from serial port */
/* then echo each word */ // later: not anymore. We don't do that now.
void readword() {
pos = 0;
tib[0] = 0;
while (reading());
// Serial.print(tib); // we do our own echo now
}
/* Run a word via its name */
void runword() {
int place = locate();
// test for upper bound of 'place'
// this was critical to making Charley's code function without the Serial Monitor e.g. from within minicom or GTKTerm.
if ((place != 0) & (place < (entries - 1))) {
dictionary[place].function();
ok();
return;
}
if (isNumber()) {
push(number());
ok();
return;
}
Serial.println("?");
}
/* Arduino main loop */
void setup() {
Serial.begin(38400);
while (!Serial);
Serial.println ("Forth-like interpreter:\r\n");
Serial.print(versionbuf);
words(); // optional - may comment this out.
Serial.println();
}
void loop() {
readword();
runword();
}
// end.