Skip to content

Instantly share code, notes, and snippets.

@spellgen
Created December 8, 2020 22:41
Show Gist options
  • Save spellgen/3d23e0201c58f7bf25202e012f57d776 to your computer and use it in GitHub Desktop.
Save spellgen/3d23e0201c58f7bf25202e012f57d776 to your computer and use it in GitHub Desktop.
package vm
import (
"bufio"
"fmt"
"io"
"strconv"
"strings"
)
type VM struct {
code []*Instruction
pos int // current position in code
accum int
cfg config
}
type config struct {
logLines bool
}
func NewVM() *VM {
return &VM{
code: make([]*Instruction, 0),
}
}
func (vm *VM) LogLines() *VM {
vm.cfg.logLines = true
return vm
}
// LoadCode loads a code in text format into the VM
func (vm *VM) LoadCode(r io.Reader) error {
s := bufio.NewScanner(r)
var n int
for s.Scan() {
n++
p := strings.Split(s.Text(), " ")
if len(p) != 2 {
return fmt.Errorf("bad instruction on line %d: %s", n, s.Text())
}
inst, err := vm.Parse(p[0], p[1])
if err != nil {
return err
}
vm.code = append(vm.code, inst)
}
return nil
}
func (vm *VM) Clone() *VM {
clone := *vm // shallow copy
clone.code = make([]*Instruction, len(vm.code))
for k := range vm.code {
i := *vm.code[k]
clone.code[k] = &i
}
return &clone
}
// Reset VM (but keep config)
func (vm *VM) Reset() *VM {
vm.pos = 0
vm.accum = 0
for k := range vm.code {
vm.code[k].Visits = 0
}
return vm
}
func (vm *VM) NextInstruction() *Instruction {
if vm.pos >= len(vm.code) {
return nil
}
return vm.code[vm.pos]
}
func (vm *VM) IsInfinite() bool {
next := vm.NextInstruction()
if next == nil {
return false
}
return next.Visits > 0
}
func (vm *VM) Code() []*Instruction {
return vm.code
}
func (vm *VM) Accumulator() int {
return vm.accum
}
func (vm *VM) CodeLength() int {
return len(vm.code)
}
func (vm *VM) Instruction(pos int) (*Instruction, error) {
if pos >= len(vm.code) {
return nil, fmt.Errorf("on Instruction(): code position (%d) outside of block (%d)", pos, len(vm.code))
}
return vm.code[pos], nil
}
func (vm *VM) SetInstruction(pos int, inst *Instruction) error {
if pos >= len(vm.code) {
return fmt.Errorf("on SetInstruction(): code position (%d) outside of block (%d)", pos, len(vm.code))
}
vm.code[pos] = inst
return nil
}
// Run the VM until done
func (vm *VM) Run(check PreCheck) {
for {
if vm.pos >= len(vm.code) {
return
}
if check(vm) {
return
}
vm.Step()
}
}
// PreCheck is a callback that gives the opportunity to signal done
type PreCheck func(*VM) bool
// Step advances the VM one instruction
func (vm *VM) Step() {
inst := vm.code[vm.pos]
inst.Visits++
if vm.cfg.logLines {
fmt.Printf("%03d %03s %d\n", vm.pos+1, inst.Op, inst.Arg)
}
inst.Func(vm, inst.Arg)
}
type (
Instruction struct {
Op string
Arg int
Visits int // how many times have we executed this instruction
Func OpFunc
}
)
type OpFunc func(*VM, int)
func (inst *Instruction) Clone() *Instruction {
clone := *inst
return &clone
}
// Parse turns a string op/arg pair into an instruction
func (vm *VM) Parse(op, stringArg string) (*Instruction, error) {
arg, err := strconv.Atoi(stringArg)
if err != nil {
return nil, err
}
inst := &Instruction{
Op: op,
Arg: arg,
Visits: 0,
}
switch op {
case "acc":
inst.Func = AccFunc
case "nop":
inst.Func = NopFunc
case "jmp":
inst.Func = JmpFunc
}
return inst, nil
}
func AccFunc(vm *VM, v int) {
vm.accum += v
vm.pos++
}
func NopFunc(vm *VM, v int) {
vm.pos++
}
func JmpFunc(vm *VM, v int) {
vm.pos += v
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment