Created
December 8, 2020 22:41
-
-
Save spellgen/3d23e0201c58f7bf25202e012f57d776 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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