May 24, 2011

Scala and FORTH - part 1

I was sorting my books the other day, and I came across an old copy of a FORTH manual. Yes we're talking FORTH from the dark ages, before all caps fell out of fashion (the manual was printed in 1981 to be more precise). It must have been more than 20 years since I programmed in FORTH.

Why not see how far I can get writing my own Forth using Scala, I thought to myself. So let's start! But before we start beware that this is a purely iterative process. I have no idea if I'll paint myself into a corner eventually. (It does seems likely though...) Enter scalaFORTH, which will be loosely based on figFORTH.

Forth is interpreted, and is used interactively, so we'll start with a prompt within a loop:
object FORTHv1 {

def main(args: Array[String]) {
Console.println("Welcome to scalaFORTH")
Console.print("> ")
while (true) {
val line = Console.in.readLine
if (line == "quit") System.exit(0)
Console.println(" ok")
Console.print("> ")
}
}
}
As you can see, quit is the command to exit the interpreter. Traditionally Forth is case insensitive, but the aim of scalaForth is not to be a faithful, to-the-letter, implemetation of figFORTH, just an attempt to get that warm feeling you get when programming Forth.

One fairly unique aspect of Forth is that it's stack based, and functions are typically performed on arguments on the stack, like on HP calculators. So "function(1, 2)" would look like "1 2 function" in Forth. One of the simplest and most used words is "." (dot). "." pops the top element from the stack and prints it on the console. A word is the Forth equivalent of a subroutine, method or what have you. Words are stored in a dictionary in a compiled format so that they can execute directly.
object FORTHv2 {
var stack = List[BigDecimal]()
var dict = Map[String, AbstractWord]()

def main(args: Array[String]) {
initDict
Console.println("Welcome to scalaFORTH")
Console.print("> ")
while (true) {
val line = Console.in.readLine
// if (line == "quit") System.exit(0)
val items = line.split(' ')
for (item <- items) {
dict.get(item) match {
case Some(s:AbstractWord) => s.eval
case None => stack = BigDecimal.apply(item) :: stack
}
}
Console.println(" ok")
Console.print("> ")
}
}

def initDict {
dict += ("." -> Dot)
dict += ("quit" -> Quit)
}

abstract class AbstractWord {
def eval
}

object Dot extends AbstractWord {
def eval {
Console.out.print(stack.head + " ")
stack=stack.tail
}
}

object Quit extends AbstractWord {
def eval {
Console.out.print("Live long and prosper! ")
System.exit(0)
}
}
}
A stack and a dictionary has been added, and also the base class for all words. If the interpreter fails to look up a word, it will try to parse it as a number instead. Note that decimal numbers are used, figForth only supported integers.

Now that we have a dictionary, we can put "quit" there, and we also added ".". Run scalaFORTH and try some numbers!
Welcome to scalaFORTH
> 1 2 3
ok
> . . .
3 2 1 ok
> quit
Live long and prosper!

In the next part I'll try to get compilation to work.

3 comments:

  1. Trevligt läsning! Ser fram emot fortsättningen.

    /Mr Spock

    ReplyDelete
  2. Beautiful!
    Set a course for the next episode...
    Make it so ;-)

    ReplyDelete