May 27, 2011

Scala and FORTH - part 2

So far we have only implemented primitive words, and in a typical Forth implementation these words are assembly language constructs. We've use Scala instead, of course. But we need to support creating programs, not just using what's there.

In Forth you program by compiling new words. A word definition starts with a ":" and ends with a ";". E.g. ": ANewWord quit ;" would define a new word called ANewWord, which would execute the existing word "quit" when invoked.

So what do we need to implement compilation? We'll start collecting definitions when ":" is encountered, and compile the gathered data when ";" ends the definition. So we need a place to put stuff until the actual compilation:
  var compilationStack = List[String]()

We also need to know whether to execute or compile a word, ":" will enter compilation mode, and ";" will switch back to interpret mode:
  var mode = Mode.interpret

object Mode extends Enumeration {
val interpret, compile = Value
}

Finally, our implementation will need to be able to differentiate between name and implementation:
  var nameDefinition = true

With that out of the way, we need to add compile time behaviour to the words:
  abstract class AbstractWord {
val name:String
def eval
def compile {
compilationStack = compilationStack ::: name :: Nil
}
}

The default compile implementation is to just add its own name to the compilation stack. ":" just needs to switch mode, while ";" also needs to compile the compilation stack. It does this by using the first stack element as name, and the rest as a list of word names to be executed:
  object Colon extends AbstractWord {
val name = ":"
def eval {
mode = Mode.compile
}
override def compile {
}
}

object SemiColon extends AbstractWord {
val name = ";"
def eval {
}
override def compile {
mode = Mode.interpret
val newName = compilationStack.head
val words = compilationStack.tail
dict += (newName -> new Word(name = newName, words = words))
compilationStack = List[String]()
nameDefinition = true
}
}

In the main loop we need to handle compilation:
  def main(args: Array[String]) {
...
for (item <- items) {
if (mode == Mode.interpret) {
...
}
} else {
if(nameDefinition) {
compilationStack = item :: compilationStack
nameDefinition = false
} else {
dict.get(item) match {
case Some(s:AbstractWord) => s.compile
case None => Console.print("Skipping " + item)
}
}
}
}
...
}

def initDict {
...
dict += (":" -> Colon)
dict += (";" -> SemiColon)
}

And that's it! A minimal Forth compiler ready to go:
Welcome to scalaFORTH
> : .. . . ;
ok
> 1 2 .. 1 2 . .
2 1 2 1 ok
>

Next time we'll tackle numbers at compile time. As of now scalaFORTH looks like this:
object FORTHv3 {
var stack = List[BigDecimal]()
var dict = Map[String, AbstractWord]()
var compilationStack = List[String]()
var mode = Mode.interpret
var nameDefinition = true

def main(args: Array[String]) {
initDict
Console.println("Welcome to scalaFORTH")
Console.print("> ")
while (true) {
val line = Console.in.readLine
val items = line.split(' ')
for (item <- items) {
if (mode == Mode.interpret) {
dict.get(item) match {
case Some(s:AbstractWord) => s.eval
case None => stack = BigDecimal.apply(item) :: stack
}
} else {
if(nameDefinition) {
compilationStack = item :: compilationStack
nameDefinition = false
} else {
dict.get(item) match {
case Some(s:AbstractWord) => s.compile
case None => Console.print("Skipping " + item)
}
}
}
}
Console.println(" ok")
Console.print("> ")
}
}

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

abstract class AbstractWord {
val name:String
def eval
def compile {
compilationStack = compilationStack ::: name :: Nil
}
}

class Word(var immediate: Boolean = false, val name: String, val words:List[String] = List()) extends AbstractWord {
def eval {
for (word <- words) {
dict.get(word) match {
case Some(s:AbstractWord) => s.eval
case None => Console.err.println("Word not found: " + word)
}
}
}
}

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

object Quit extends AbstractWord {
val name = "quit"
def eval {
Console.out.print("Live long and prosper! ")
System.exit(0)
}
}

object Mode extends Enumeration {
val interpret, compile = Value
}

object Colon extends AbstractWord {
val name = ":"
def eval {
mode = Mode.compile
}
override def compile {

}
}

object SemiColon extends AbstractWord {
val name = ";"
def eval {
}
override def compile {
mode = Mode.interpret
val newName = compilationStack.head
val words = compilationStack.tail
dict += (newName -> new Word(name = newName, words = words))
compilationStack = List[String]()
nameDefinition = true
}
}
}

No comments:

Post a Comment