sourcebrainfuck::Interpreter.fan

**
** Interpreter for brainfuck instructions
** 
class Interpreter
{
  const static Log log := Interpreter#.pod.log
  
  **
  ** Program to interprete
  ** 
  Token[] program { private set }
  
  **
  ** Array to write
  ** 
  Int[] tape { private set }
  
  **
  ** Data pointer
  ** 
  Int dp := 0 { private set }
  
  ** 
  ** Program pointer
  ** 
  Int pp := 0 { private set }
  
  **
  ** Matched brackets
  ** 
  private Int:Int brackets

  **
  ** Index the matching brackets to speed up the program.
  ** Instead of move from one instruction to another to find the
  ** matching bracket it "jumps" directly to the position.
  ** 
  static [Int:Int] indexBrackets (Token[] program)
  {
    result := Int:Int[:]
    levels := Int:Int[:]
    level  := 0

    (0..<program.size).each |i|
    {
      token := program[i]
      switch(token.instruction)
      {
        case Instruction.jumpForward:
          ++level
          levels[level] = i
        case Instruction.jumpBackward:
          match := levels[level]
          result[i] = match
          result[match] = i
          --level
        default:
      }
    }

    return result
  }

  **
  ** Creates the interpreter with the program and
  ** the size of the array
  ** 
  new make(Token[] program, Int size := 3000)
  {
    this.program  = program
    this.tape     = Int[,].fill(0, size)
    this.brackets = indexBrackets(program)
  }

  Bool off()
  {
    pp >= program.size
  }

  Void forward()
  {
    ++pp
  }

  Void run()
  {
    while(!off)
    {
      execute
      forward
    }
  }

  Void execute()
  {
    switch(program[pp].instruction)
    {
      case Instruction.incrementData:
        incrementData
      case Instruction.decrementData:
        decrementData
      case Instruction.incrementPointer:
        incrementPointer
      case Instruction.decrementPointer:
        decrementPointer
      case Instruction.input:
        input
      case Instruction.output:           
        output
      case Instruction.jumpForward:
        jumpForward
      case Instruction.jumpBackward:
        jumpBackward
    }
  }

  Void incrementData()
  {
    tape[dp] = tape[dp] + 1
  }

  Void decrementData()
  {
    tape[dp] = tape[dp] - 1
  }

  Void incrementPointer()
  {
    ++dp
    if (dp > tape.size)
    {
      throw BrainfuckErr(program[pp], "Memory pointer overflow")
    }
  }

  Void decrementPointer()
  {
    --dp
    if (dp < 0)
    {
      throw BrainfuckErr(program[pp], "Memory pointer underflow")
    }
  }

  Void input()
  {
    valid := false
    while(!valid)
    {
      try
      {
        n := Env.cur.in.readLine.toInt
        if (n < 0 || n > 255)
        {
          echo("Invalid input: must be a number between 0 and 255")
        }
        else
        {
          tape[dp] = n
          valid = true
        }
      }
      catch (Err e)
      {
        echo("Invalid input: not a number")
      }
    }
  }

  Void output()
  {
    Env.cur.out.print(tape[dp].toChar)
  }

  Void jumpForward()
  {
    if (tape[dp] != 0) return
    pp = brackets[pp]
  }

  Void jumpBackward()
  {
    if (tape[dp] == 0) return
    pp = brackets[pp]
  }

  override Str toStr()
  {
    return "Program[$pp]=${program[pp]} Tape[$dp]=${tape[dp]}"
  }
}