mexico/mexigo/interpreter/interpreter.go
2019-12-08 17:55:49 +01:00

90 lines
2.6 KiB
Go

package interpreter
import (
"fmt"
"github.com/pkg/errors"
)
type Interpreter struct {
machine Machine
program []Codeline
programCounter int
programPointer int
}
// Feeds a new mexico machine with given code.
func Run(commands []Codeline) error {
var i Interpreter
// Set the given code as commands for the interpreter
setCmdErr := i.SetCommands(commands)
if setCmdErr != nil {
return setCmdErr
}
// Let's run this program :)
return i.Run()
}
// Sets the given array as new program for the interpreter
func (i *Interpreter) SetCommands(commands []Codeline) error {
i.program = commands
i.programCounter = 0
return i.GoToNextCommand()
}
// Searches for the next command, starting from the current value of the programCounter.
// This may sound odd, because in most other architectures, this is just programCounter++, and there would be no need
// for a function like this. However, mexico has a BASIC-style program line numbering, means we need to search for the
// next line number containing code, because there may be one or more empty lines between the current and the next line.
// This is exactly what GoToNextCommand() does.
// If there is no next command, most likely because we reached the end of the program, an error is thrown.
func (i *Interpreter) GoToNextCommand() error {
// Iterate over all lines to find the first which has a greater line number than the current one
for index, line := range i.program {
if line.Linenumber >= i.programCounter {
// Found, set and return
i.programCounter = line.Linenumber
i.programPointer = index
return nil
}
}
// No next command found. Throw error.
return errors.New(fmt.Sprintf("Found no commands after line %d. Stopping.", i.programCounter))
}
// Runs the commands, unless an error is encountered, then it doesn't run the commands.
func (i *Interpreter) Run() error {
defer i.machine.Tape.DebugPrintTape()
defer i.machine.Stack.DebugPrintStack()
for {
// Get current command
cmd := i.program[i.programPointer]
// Run command in the machine
jumpLine, doJump, runErr := i.machine.RunCommand(cmd.Code)
if runErr != nil {
// Encountered an error during runtime, stop execution
return runErr
}
// Check if we should jump anywhere else than to the next code line
if doJump {
// Yes, do it then.
i.programCounter = jumpLine
} else {
// We are not asked to jump anywhere, move on to the next code line then.
i.programCounter++
}
// Skip empty lines if there are any.
nextCmdErr := i.GoToNextCommand()
if nextCmdErr != nil {
// Encountered error finding the next command, throw it
return nextCmdErr
}
}
}