Tuesday, September 22, 2009

Interpreter basics

  There have been Z-machine interpreters created for most platforms and in most languages. Mine is not the first written in C# but my goals are perhaps a bit different. As I mentioned, my goal is not to create a monolithic interpreter, but rather a class library usable by a host of interpreter front-ends. I currently test my library against several front-ends including: applications using windows console, windows forms, WPF, ASP.Net, and XNA for the Xbox360.

  Infocom released almost three dozen Z-machine games which spanned six distinct versions of the Z-machine platform. Hundreds more have been written by other authors since then (many can be found at the IF Archive) and two minor version changes to the Z-machine have been added bringing the total to eight. I wish to support these new games and Infocom's full catalog as well, which includes the version 6 games that had graphics capabilities for the first time. Version 6 is really a different beast from the others (versions 7 and 8 are successors to 5 rather than 6) and requires a lot of extra effort to support.

  So what does a Z-machine do? After initializing, it executes code until hitting a read opcode (or additionally read_char in version 4 and up) then waits for input. After receiving input, execution continues with the next instruction. Most of the time is spent waiting for input from the user. Using these simple states we can 'run' one cycle of our machine like this:

public void Run()
{
  lock (this.lockObject)
  {
    switch (this.State)
    {
      case MachineState.Initializing:
        this.Initialize();
        break;
      case MachineState.Running:
        this.ExecuteInstruction();
        break;
      case MachineState.ReadingInput:
        this.ReadInput();
        break;
    }
  }
}


  Initialize() seeds the random number generator, places the first routine on the stack, and then sets the state to Running. ExecuteInstruction() decodes and executes a single operation. If that operation is 'read' or 'read_char' it then switches the state to ReadingInput. ReadInput() checks the input buffer. If input has been terminated by a carriage return or a 'terminating character' specified by a table in memory it then switches the state back to Running.

  We synchronize on a private object and only run one machine cycle at a time to allow the front-end to determine the nature of the loop between cycles as well as details like how many threads may service the machine.

Next time I'll go into detail about how to decode an operation and execute it.

No comments:

Post a Comment