restructure project
This commit is contained in:
270
internal/vm/cache.go
Normal file
270
internal/vm/cache.go
Normal file
@@ -0,0 +1,270 @@
|
||||
package vm
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type cachedFrame struct {
|
||||
Key uint // pointer to key
|
||||
Value *Frame // pointer to value
|
||||
Frequency uint // frequency of access
|
||||
}
|
||||
|
||||
type frequencyNode struct {
|
||||
count uint // frequency count - never decreases
|
||||
valuesList *list.List // valuesList contains pointer to the head of values linked list
|
||||
inner *list.Element // actual content of the next element
|
||||
}
|
||||
|
||||
// creates a new frequency list node with the given count
|
||||
func newFrequencyNode(count uint) *frequencyNode {
|
||||
return &frequencyNode{
|
||||
count: count,
|
||||
valuesList: list.New(),
|
||||
inner: nil,
|
||||
}
|
||||
}
|
||||
|
||||
type keyRefNode struct {
|
||||
inner *list.Element // contains the actual value wrapped by a list element
|
||||
parentFreqNode *list.Element // contains reference to the frequency node element
|
||||
keyRef uint // contains pointer to the key
|
||||
valueRef *Frame // value
|
||||
}
|
||||
|
||||
// creates a new KeyRef node which is used to represent the value in linked list
|
||||
func newKeyRefNode(keyRef uint, valueRef *Frame, parent *list.Element) *keyRefNode {
|
||||
return &keyRefNode{
|
||||
inner: nil,
|
||||
parentFreqNode: parent,
|
||||
keyRef: keyRef,
|
||||
valueRef: valueRef,
|
||||
}
|
||||
}
|
||||
|
||||
// FrameCache implements all the methods and data-structures required for a LFU cache for caching frames.
|
||||
type FrameCache struct {
|
||||
rwLock sync.RWMutex // rwLock is a read-write mutex which provides concurrent reads but exclusive writes
|
||||
lookupTable map[uint]*keyRefNode // a hash table of <KeyType, *ValueType> for quick reference of values based on keys
|
||||
frequencies *list.List // internal linked list that contains frequency mapping
|
||||
maxSize uint // maxSize represents the maximum number of elements that can be in the cache before eviction
|
||||
}
|
||||
|
||||
// MaxSize returns the maximum size of the cache at that point in time
|
||||
func (lfu *FrameCache) MaxSize() uint {
|
||||
lfu.rwLock.RLock()
|
||||
defer lfu.rwLock.RUnlock()
|
||||
|
||||
return lfu.maxSize
|
||||
}
|
||||
|
||||
// CurrentSize returns the number of elements in that cache
|
||||
func (lfu *FrameCache) CurrentSize() uint {
|
||||
lfu.rwLock.RLock()
|
||||
defer lfu.rwLock.RUnlock()
|
||||
|
||||
return uint(len(lfu.lookupTable))
|
||||
}
|
||||
|
||||
// IsFull checks if the LFU cache is full
|
||||
func (lfu *FrameCache) IsFull() bool {
|
||||
lfu.rwLock.RLock()
|
||||
defer lfu.rwLock.RUnlock()
|
||||
|
||||
return uint(len(lfu.lookupTable)) == lfu.maxSize
|
||||
}
|
||||
|
||||
// SetMaxSize updates the max size of the LFU cache
|
||||
func (lfu *FrameCache) SetMaxSize(size uint) {
|
||||
lfu.rwLock.Lock()
|
||||
defer lfu.rwLock.Unlock()
|
||||
|
||||
lfu.maxSize = size
|
||||
}
|
||||
|
||||
// evict the least recently used element from the cache, this function is unsafe to be called externally
|
||||
// because it doesn't provide locking mechanism.
|
||||
func (lfu *FrameCache) unsafeEvict() error {
|
||||
// WARNING: This function assumes that a write lock has been held by the caller already
|
||||
|
||||
// get the head node of the list
|
||||
headFreq := lfu.frequencies.Front()
|
||||
if headFreq == nil {
|
||||
// list is empty, this is a very unusual condition
|
||||
return fmt.Errorf("internal error: failed to evict, empty frequency list")
|
||||
}
|
||||
|
||||
headFreqInner := (headFreq.Value).(*frequencyNode)
|
||||
|
||||
if headFreqInner.valuesList.Len() == 0 {
|
||||
// again this is a very unusual condition
|
||||
return fmt.Errorf("internal error: failed to evict, empty values list")
|
||||
}
|
||||
|
||||
headValuesList := headFreqInner.valuesList
|
||||
// pop the head of this this values list
|
||||
headValueNode := headValuesList.Front()
|
||||
removeResult := headValuesList.Remove(headValueNode).(*keyRefNode)
|
||||
|
||||
// update the values list
|
||||
headFreqInner.valuesList = headValuesList
|
||||
|
||||
if headFreqInner.valuesList.Len() == 0 && headFreqInner.count > 1 {
|
||||
// this node can be removed from the frequency list
|
||||
freqList := lfu.frequencies
|
||||
freqList.Remove(headFreq)
|
||||
lfu.frequencies = freqList
|
||||
}
|
||||
|
||||
// remove the key from lookup table
|
||||
key := removeResult.keyRef
|
||||
delete(lfu.lookupTable, key)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Put method inserts a `<KeyType, ValueType>` to the LFU cache and updates internal
|
||||
// data structures to keep track of access frequencies, if the cache is full, it evicts the
|
||||
// least frequently used value from the cache.
|
||||
func (lfu *FrameCache) Put(key uint, value *Frame) error {
|
||||
// get write lock
|
||||
lfu.rwLock.Lock()
|
||||
defer lfu.rwLock.Unlock()
|
||||
|
||||
if _, ok := lfu.lookupTable[key]; ok {
|
||||
// update the cache value
|
||||
lfu.lookupTable[key].valueRef = value
|
||||
return nil
|
||||
}
|
||||
|
||||
if lfu.maxSize == uint(len(lfu.lookupTable)) {
|
||||
lfu.unsafeEvict()
|
||||
}
|
||||
|
||||
valueNode := newKeyRefNode(key, value, nil)
|
||||
|
||||
head := lfu.frequencies.Front()
|
||||
if head == nil {
|
||||
// fresh linked list
|
||||
freqNode := newFrequencyNode(1)
|
||||
head = lfu.frequencies.PushFront(freqNode)
|
||||
freqNode.inner = head
|
||||
|
||||
} else {
|
||||
node := head.Value.(*frequencyNode)
|
||||
if node.count != 1 {
|
||||
freqNode := newFrequencyNode(1)
|
||||
head = lfu.frequencies.PushFront(freqNode)
|
||||
freqNode.inner = head
|
||||
}
|
||||
}
|
||||
|
||||
valueNode.parentFreqNode = head
|
||||
node := head.Value.(*frequencyNode)
|
||||
head = node.valuesList.PushBack(valueNode)
|
||||
valueNode.inner = head
|
||||
|
||||
lfu.lookupTable[key] = valueNode
|
||||
return nil
|
||||
}
|
||||
|
||||
// Evict can be called to manually perform eviction
|
||||
func (lfu *FrameCache) Evict() error {
|
||||
lfu.rwLock.Lock()
|
||||
defer lfu.rwLock.Unlock()
|
||||
|
||||
return lfu.unsafeEvict()
|
||||
}
|
||||
|
||||
func (lfu *FrameCache) unsafeUpdateFrequency(valueNode *keyRefNode) {
|
||||
parentFreqNode := valueNode.parentFreqNode
|
||||
currentNode := parentFreqNode.Value.(*frequencyNode)
|
||||
nextParentFreqNode := parentFreqNode.Next()
|
||||
|
||||
var newParent *list.Element = nil
|
||||
|
||||
if nextParentFreqNode == nil {
|
||||
// this is the last node
|
||||
// create a new node with frequency + 1
|
||||
newFreqNode := newFrequencyNode(currentNode.count + 1)
|
||||
lfu.frequencies.PushBack(newFreqNode)
|
||||
newParent = parentFreqNode.Next()
|
||||
|
||||
} else {
|
||||
nextNode := nextParentFreqNode.Value.(*frequencyNode)
|
||||
if nextNode.count == (currentNode.count + 1) {
|
||||
newParent = nextParentFreqNode
|
||||
} else {
|
||||
// insert a node in between
|
||||
newFreqNode := newFrequencyNode(currentNode.count + 1)
|
||||
|
||||
lfu.frequencies.InsertAfter(newFreqNode, parentFreqNode)
|
||||
newParent = parentFreqNode.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// remove from the existing list
|
||||
currentNode.valuesList.Remove(valueNode.inner)
|
||||
|
||||
newParentNode := newParent.Value.(*frequencyNode)
|
||||
valueNode.parentFreqNode = newParent
|
||||
newValueNode := newParentNode.valuesList.PushBack(valueNode)
|
||||
valueNode.inner = newValueNode
|
||||
|
||||
// check if the current node is empty
|
||||
if currentNode.valuesList.Len() == 0 {
|
||||
// remove the current node
|
||||
lfu.frequencies.Remove(parentFreqNode)
|
||||
}
|
||||
}
|
||||
|
||||
// Get can be called to obtain the value for given key
|
||||
func (lfu *FrameCache) Get(key uint) (*Frame, bool) {
|
||||
lfu.rwLock.Lock()
|
||||
defer lfu.rwLock.Unlock()
|
||||
|
||||
// check if data is in the map
|
||||
valueNode, found := lfu.lookupTable[key]
|
||||
if !found {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
lfu.unsafeUpdateFrequency(valueNode)
|
||||
|
||||
return valueNode.valueRef, true
|
||||
}
|
||||
|
||||
// Delete removes the specified entry from LFU cache
|
||||
func (lfu *FrameCache) Delete(key uint) error {
|
||||
lfu.rwLock.Lock()
|
||||
defer lfu.rwLock.Unlock()
|
||||
|
||||
// check if the key is in the map
|
||||
valueNode, found := lfu.lookupTable[key]
|
||||
if !found {
|
||||
return fmt.Errorf("key %v not found", key)
|
||||
}
|
||||
|
||||
parentFreqNode := valueNode.parentFreqNode
|
||||
|
||||
currentNode := (parentFreqNode.Value).(*frequencyNode)
|
||||
currentNode.valuesList.Remove(valueNode.inner)
|
||||
|
||||
if currentNode.valuesList.Len() == 0 {
|
||||
lfu.frequencies.Remove(parentFreqNode)
|
||||
}
|
||||
|
||||
delete(lfu.lookupTable, key)
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewFrameCache ...
|
||||
func NewFrameCache(maxSize uint) *FrameCache {
|
||||
return &FrameCache{
|
||||
rwLock: sync.RWMutex{},
|
||||
lookupTable: make(map[uint]*keyRefNode),
|
||||
maxSize: maxSize,
|
||||
frequencies: list.New(),
|
||||
}
|
||||
}
|
||||
46
internal/vm/frame.go
Normal file
46
internal/vm/frame.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package vm
|
||||
|
||||
import (
|
||||
"monkey/internal/code"
|
||||
"monkey/internal/object"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var cache = NewFrameCache(32)
|
||||
|
||||
type Frame struct {
|
||||
cl *object.Closure
|
||||
ip int
|
||||
basePointer int
|
||||
}
|
||||
|
||||
func NewFrame(cl *object.Closure, basePointer int) *Frame {
|
||||
key := uint(uintptr(unsafe.Pointer(cl))) + uint(basePointer)
|
||||
if frame, ok := cache.Get(key); ok {
|
||||
frame.Reset()
|
||||
return frame
|
||||
}
|
||||
|
||||
frame := &Frame{
|
||||
cl: cl,
|
||||
ip: -1,
|
||||
basePointer: basePointer,
|
||||
}
|
||||
|
||||
cache.Put(key, frame)
|
||||
|
||||
return frame
|
||||
}
|
||||
|
||||
// NextOp ...
|
||||
func (f *Frame) NextOp() code.Opcode {
|
||||
return code.Opcode(f.Instructions()[f.ip+1])
|
||||
}
|
||||
|
||||
func (f *Frame) Reset() {
|
||||
f.ip = -1
|
||||
}
|
||||
|
||||
func (f *Frame) Instructions() code.Instructions {
|
||||
return f.cl.Fn.Instructions
|
||||
}
|
||||
928
internal/vm/vm.go
Normal file
928
internal/vm/vm.go
Normal file
@@ -0,0 +1,928 @@
|
||||
package vm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"monkey/internal/builtins"
|
||||
"monkey/internal/code"
|
||||
"monkey/internal/compiler"
|
||||
"monkey/internal/lexer"
|
||||
"monkey/internal/object"
|
||||
"monkey/internal/parser"
|
||||
"monkey/internal/utils"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
const StackSize = 2048
|
||||
const GlobalsSize = 65536
|
||||
const MaxFrames = 1024
|
||||
|
||||
var Null = &object.Null{}
|
||||
var True = &object.Boolean{Value: true}
|
||||
var False = &object.Boolean{Value: false}
|
||||
|
||||
// ExecModule compiles the named module and returns a *object.Module object
|
||||
func ExecModule(name string, state *VMState) (object.Object, error) {
|
||||
filename := utils.FindModule(name)
|
||||
if filename == "" {
|
||||
return nil, fmt.Errorf("ImportError: no module named '%s'", name)
|
||||
}
|
||||
|
||||
b, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("IOError: error reading module '%s': %s", name, err)
|
||||
}
|
||||
|
||||
l := lexer.New(string(b))
|
||||
p := parser.New(l)
|
||||
|
||||
module := p.ParseProgram()
|
||||
if len(p.Errors()) != 0 {
|
||||
return nil, fmt.Errorf("ParseError: %s", p.Errors())
|
||||
}
|
||||
|
||||
c := compiler.NewWithState(state.Symbols, state.Constants)
|
||||
err = c.Compile(module)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("CompileError: %s", err)
|
||||
}
|
||||
|
||||
code := c.Bytecode()
|
||||
state.Constants = code.Constants
|
||||
|
||||
machine := NewWithState(code, state)
|
||||
err = machine.Run()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("RuntimeError: error loading module '%s'", err)
|
||||
}
|
||||
|
||||
return state.ExportedHash(), nil
|
||||
}
|
||||
|
||||
type VMState struct {
|
||||
Constants []object.Object
|
||||
Globals []object.Object
|
||||
Symbols *compiler.SymbolTable
|
||||
}
|
||||
|
||||
func NewVMState() *VMState {
|
||||
symbolTable := compiler.NewSymbolTable()
|
||||
for i, builtin := range builtins.BuiltinsIndex {
|
||||
symbolTable.DefineBuiltin(i, builtin.Name)
|
||||
}
|
||||
|
||||
return &VMState{
|
||||
Constants: []object.Object{},
|
||||
Globals: make([]object.Object, GlobalsSize),
|
||||
Symbols: symbolTable,
|
||||
}
|
||||
}
|
||||
|
||||
// exported binding in the vm state. That is every binding that starts with a
|
||||
// capital letter. This is used by the module import system to wrap up the
|
||||
// compiled and evaulated module into an object.
|
||||
func (s *VMState) ExportedHash() *object.Hash {
|
||||
pairs := make(map[object.HashKey]object.HashPair)
|
||||
for name, symbol := range s.Symbols.Store {
|
||||
if unicode.IsUpper(rune(name[0])) {
|
||||
if symbol.Scope == compiler.GlobalScope {
|
||||
obj := s.Globals[symbol.Index]
|
||||
s := &object.String{Value: name}
|
||||
pairs[s.HashKey()] = object.HashPair{Key: s, Value: obj}
|
||||
}
|
||||
}
|
||||
}
|
||||
return &object.Hash{Pairs: pairs}
|
||||
}
|
||||
|
||||
type VM struct {
|
||||
Debug bool
|
||||
|
||||
state *VMState
|
||||
|
||||
stack []object.Object
|
||||
sp int // Always points to the next value. Top of stack is stack[sp-1]
|
||||
|
||||
frames []*Frame
|
||||
frame *Frame // Current frame or nil
|
||||
fp int // Always points to the current frame. Current frame is frames[fp-1]
|
||||
}
|
||||
|
||||
// New constructs a new monkey-lang bytecode virtual machine
|
||||
func New(bytecode *compiler.Bytecode) *VM {
|
||||
mainFn := &object.CompiledFunction{Instructions: bytecode.Instructions}
|
||||
mainClosure := &object.Closure{Fn: mainFn}
|
||||
mainFrame := NewFrame(mainClosure, 0)
|
||||
|
||||
frames := make([]*Frame, MaxFrames)
|
||||
frames[0] = mainFrame
|
||||
|
||||
state := NewVMState()
|
||||
state.Constants = bytecode.Constants
|
||||
|
||||
return &VM{
|
||||
state: state,
|
||||
|
||||
stack: make([]object.Object, StackSize),
|
||||
sp: 0,
|
||||
|
||||
frames: frames,
|
||||
frame: mainFrame,
|
||||
fp: 1,
|
||||
}
|
||||
}
|
||||
|
||||
func NewWithState(bytecode *compiler.Bytecode, state *VMState) *VM {
|
||||
mainFn := &object.CompiledFunction{Instructions: bytecode.Instructions}
|
||||
mainClosure := &object.Closure{Fn: mainFn}
|
||||
mainFrame := NewFrame(mainClosure, 0)
|
||||
|
||||
frames := make([]*Frame, MaxFrames)
|
||||
frames[0] = mainFrame
|
||||
|
||||
return &VM{
|
||||
state: state,
|
||||
|
||||
frames: frames,
|
||||
frame: mainFrame,
|
||||
fp: 1,
|
||||
|
||||
stack: make([]object.Object, StackSize),
|
||||
sp: 0,
|
||||
}
|
||||
}
|
||||
|
||||
func (vm *VM) pushFrame(f *Frame) {
|
||||
vm.frame = f
|
||||
vm.frames[vm.fp] = f
|
||||
vm.fp++
|
||||
}
|
||||
|
||||
func (vm *VM) popFrame() *Frame {
|
||||
vm.fp--
|
||||
vm.frame = vm.frames[vm.fp-1]
|
||||
return vm.frames[vm.fp]
|
||||
}
|
||||
|
||||
func (vm *VM) loadModule(name object.Object) error {
|
||||
s, ok := name.(*object.String)
|
||||
if !ok {
|
||||
return fmt.Errorf(
|
||||
"TypeError: import() expected argument #1 to be `str` got `%s`",
|
||||
name.Type(),
|
||||
)
|
||||
}
|
||||
|
||||
attrs, err := ExecModule(s.Value, vm.state)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
module := &object.Module{Name: s.Value, Attrs: attrs}
|
||||
return vm.push(module)
|
||||
}
|
||||
|
||||
func (vm *VM) LastPoppedStackElem() object.Object {
|
||||
return vm.stack[vm.sp]
|
||||
}
|
||||
|
||||
func (vm *VM) Run() error {
|
||||
var ip int
|
||||
var ins code.Instructions
|
||||
var op code.Opcode
|
||||
|
||||
if vm.Debug {
|
||||
log.Printf(
|
||||
"%-25s %-20s\n",
|
||||
fmt.Sprintf(
|
||||
"%04d %s", ip,
|
||||
strings.Split(ins[ip:].String(), "\n")[0][4:],
|
||||
),
|
||||
fmt.Sprintf(
|
||||
"[ip=%02d fp=%02d, sp=%02d]",
|
||||
ip, vm.fp-1, vm.sp,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
for vm.frame.ip < len(vm.frame.Instructions())-1 {
|
||||
vm.frame.ip++
|
||||
|
||||
ip = vm.frame.ip
|
||||
ins = vm.frame.Instructions()
|
||||
op = code.Opcode(ins[ip])
|
||||
|
||||
switch op {
|
||||
case code.OpConstant:
|
||||
constIndex := code.ReadUint16(ins[ip+1:])
|
||||
vm.frame.ip += 2
|
||||
|
||||
err := vm.push(vm.state.Constants[constIndex])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case code.OpAdd, code.OpSub, code.OpMul, code.OpDiv, code.OpMod, code.OpOr,
|
||||
code.OpAnd, code.OpBitwiseOR, code.OpBitwiseXOR, code.OpBitwiseAND,
|
||||
code.OpLeftShift, code.OpRightShift:
|
||||
err := vm.executeBinaryOperation(op)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case code.OpPop:
|
||||
vm.pop()
|
||||
|
||||
case code.OpTrue:
|
||||
err := vm.push(True)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case code.OpFalse:
|
||||
err := vm.push(False)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case code.OpEqual, code.OpNotEqual, code.OpGreaterThan, code.OpGreaterThanEqual:
|
||||
err := vm.executeComparison(op)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case code.OpNot:
|
||||
err := vm.executeNotOperator()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case code.OpBitwiseNOT:
|
||||
err := vm.executeBitwiseNotOperator()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case code.OpMinus:
|
||||
err := vm.executeMinusOperator()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case code.OpJump:
|
||||
pos := int(code.ReadUint16(ins[ip+1:]))
|
||||
vm.frame.ip = pos - 1
|
||||
|
||||
case code.OpJumpNotTruthy:
|
||||
pos := int(code.ReadUint16(ins[ip+1:]))
|
||||
vm.frame.ip += 2
|
||||
|
||||
condition := vm.pop()
|
||||
if !isTruthy(condition) {
|
||||
vm.frame.ip = pos - 1
|
||||
}
|
||||
|
||||
case code.OpNull:
|
||||
err := vm.push(Null)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case code.OpSetGlobal:
|
||||
globalIndex := code.ReadUint16(ins[ip+1:])
|
||||
vm.frame.ip += 2
|
||||
|
||||
ref := vm.pop()
|
||||
if immutable, ok := ref.(object.Immutable); ok {
|
||||
vm.state.Globals[globalIndex] = immutable.Clone()
|
||||
} else {
|
||||
vm.state.Globals[globalIndex] = ref
|
||||
}
|
||||
|
||||
err := vm.push(Null)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case code.OpAssignGlobal:
|
||||
globalIndex := code.ReadUint16(ins[ip+1:])
|
||||
vm.frame.ip += 2
|
||||
vm.state.Globals[globalIndex] = vm.pop()
|
||||
|
||||
err := vm.push(Null)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case code.OpAssignLocal:
|
||||
localIndex := code.ReadUint8(ins[ip+1:])
|
||||
vm.frame.ip += 1
|
||||
|
||||
vm.stack[vm.frame.basePointer+int(localIndex)] = vm.pop()
|
||||
|
||||
err := vm.push(Null)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case code.OpGetGlobal:
|
||||
globalIndex := code.ReadUint16(ins[ip+1:])
|
||||
vm.frame.ip += 2
|
||||
|
||||
err := vm.push(vm.state.Globals[globalIndex])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case code.OpArray:
|
||||
numElements := int(code.ReadUint16(ins[ip+1:]))
|
||||
vm.frame.ip += 2
|
||||
|
||||
array := vm.buildArray(vm.sp-numElements, vm.sp)
|
||||
vm.sp = vm.sp - numElements
|
||||
|
||||
err := vm.push(array)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case code.OpHash:
|
||||
numElements := int(code.ReadUint16(ins[ip+1:]))
|
||||
vm.frame.ip += 2
|
||||
|
||||
hash, err := vm.buildHash(vm.sp-numElements, vm.sp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
vm.sp = vm.sp - numElements
|
||||
|
||||
err = vm.push(hash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case code.OpSetItem:
|
||||
value := vm.pop()
|
||||
index := vm.pop()
|
||||
left := vm.pop()
|
||||
|
||||
err := vm.executeSetItem(left, index, value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case code.OpGetItem:
|
||||
index := vm.pop()
|
||||
left := vm.pop()
|
||||
|
||||
err := vm.executeGetItem(left, index)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case code.OpCall:
|
||||
numArgs := code.ReadUint8(ins[ip+1:])
|
||||
vm.frame.ip += 1
|
||||
|
||||
err := vm.executeCall(int(numArgs))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case code.OpReturn:
|
||||
returnValue := vm.pop()
|
||||
|
||||
frame := vm.popFrame()
|
||||
vm.sp = frame.basePointer - 1
|
||||
|
||||
err := vm.push(returnValue)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case code.OpSetLocal:
|
||||
localIndex := code.ReadUint8(ins[ip+1:])
|
||||
vm.frame.ip += 1
|
||||
|
||||
ref := vm.pop()
|
||||
if immutable, ok := ref.(object.Immutable); ok {
|
||||
vm.stack[vm.frame.basePointer+int(localIndex)] = immutable.Clone()
|
||||
} else {
|
||||
vm.stack[vm.frame.basePointer+int(localIndex)] = ref
|
||||
}
|
||||
|
||||
err := vm.push(Null)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case code.OpGetLocal:
|
||||
localIndex := code.ReadUint8(ins[ip+1:])
|
||||
vm.frame.ip += 1
|
||||
|
||||
err := vm.push(vm.stack[vm.frame.basePointer+int(localIndex)])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case code.OpGetBuiltin:
|
||||
builtinIndex := code.ReadUint8(ins[ip+1:])
|
||||
vm.frame.ip += 1
|
||||
|
||||
builtin := builtins.BuiltinsIndex[builtinIndex]
|
||||
|
||||
err := vm.push(builtin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case code.OpClosure:
|
||||
constIndex := code.ReadUint16(ins[ip+1:])
|
||||
numFree := code.ReadUint8(ins[ip+3:])
|
||||
vm.frame.ip += 3
|
||||
|
||||
err := vm.pushClosure(int(constIndex), int(numFree))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case code.OpGetFree:
|
||||
freeIndex := code.ReadUint8(ins[ip+1:])
|
||||
vm.frame.ip += 1
|
||||
|
||||
err := vm.push(vm.frame.cl.Free[freeIndex])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case code.OpCurrentClosure:
|
||||
currentClosure := vm.frame.cl
|
||||
err := vm.push(currentClosure)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case code.OpLoadModule:
|
||||
name := vm.pop()
|
||||
|
||||
err := vm.loadModule(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if vm.Debug {
|
||||
log.Printf(
|
||||
"%-25s [ip=%02d fp=%02d, sp=%02d]",
|
||||
"", ip, vm.fp-1, vm.sp,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (vm *VM) executeSetItem(left, index, value object.Object) error {
|
||||
switch {
|
||||
case left.Type() == object.ARRAY_OBJ && index.Type() == object.INTEGER_OBJ:
|
||||
return vm.executeArraySetItem(left, index, value)
|
||||
case left.Type() == object.HASH_OBJ:
|
||||
return vm.executeHashSetItem(left, index, value)
|
||||
default:
|
||||
return fmt.Errorf(
|
||||
"set item operation not supported: left=%s index=%s",
|
||||
left.Type(), index.Type(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func (vm *VM) executeGetItem(left, index object.Object) error {
|
||||
switch {
|
||||
case left.Type() == object.STRING_OBJ && index.Type() == object.INTEGER_OBJ:
|
||||
return vm.executeStringGetItem(left, index)
|
||||
case left.Type() == object.STRING_OBJ && index.Type() == object.STRING_OBJ:
|
||||
return vm.executeStringIndex(left, index)
|
||||
case left.Type() == object.ARRAY_OBJ && index.Type() == object.INTEGER_OBJ:
|
||||
return vm.executeArrayGetItem(left, index)
|
||||
case left.Type() == object.HASH_OBJ:
|
||||
return vm.executeHashGetItem(left, index)
|
||||
case left.Type() == object.MODULE_OBJ:
|
||||
return vm.executeHashGetItem(left.(*object.Module).Attrs, index)
|
||||
default:
|
||||
return fmt.Errorf(
|
||||
"index operator not supported: left=%s index=%s",
|
||||
left.Type(), index.Type(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func (vm *VM) executeArrayGetItem(array, index object.Object) error {
|
||||
arrayObject := array.(*object.Array)
|
||||
i := index.(*object.Integer).Value
|
||||
max := int64(len(arrayObject.Elements) - 1)
|
||||
|
||||
if i < 0 || i > max {
|
||||
return vm.push(Null)
|
||||
}
|
||||
|
||||
return vm.push(arrayObject.Elements[i])
|
||||
}
|
||||
|
||||
func (vm *VM) executeArraySetItem(array, index, value object.Object) error {
|
||||
arrayObject := array.(*object.Array)
|
||||
i := index.(*object.Integer).Value
|
||||
max := int64(len(arrayObject.Elements) - 1)
|
||||
|
||||
if i < 0 || i > max {
|
||||
return fmt.Errorf("index out of bounds: %d", i)
|
||||
}
|
||||
|
||||
arrayObject.Elements[i] = value
|
||||
return vm.push(Null)
|
||||
}
|
||||
|
||||
func (vm *VM) executeHashGetItem(hash, index object.Object) error {
|
||||
hashObject := hash.(*object.Hash)
|
||||
|
||||
key, ok := index.(object.Hashable)
|
||||
if !ok {
|
||||
return fmt.Errorf("unusable as hash key: %s", index.Type())
|
||||
}
|
||||
|
||||
pair, ok := hashObject.Pairs[key.HashKey()]
|
||||
if !ok {
|
||||
return vm.push(Null)
|
||||
}
|
||||
|
||||
return vm.push(pair.Value)
|
||||
}
|
||||
|
||||
func (vm *VM) executeHashSetItem(hash, index, value object.Object) error {
|
||||
hashObject := hash.(*object.Hash)
|
||||
|
||||
key, ok := index.(object.Hashable)
|
||||
if !ok {
|
||||
return fmt.Errorf("unusable as hash key: %s", index.Type())
|
||||
}
|
||||
|
||||
hashed := key.HashKey()
|
||||
hashObject.Pairs[hashed] = object.HashPair{Key: index, Value: value}
|
||||
|
||||
return vm.push(Null)
|
||||
}
|
||||
|
||||
func (vm *VM) buildHash(startIndex, endIndex int) (object.Object, error) {
|
||||
hashedPairs := make(map[object.HashKey]object.HashPair)
|
||||
|
||||
for i := startIndex; i < endIndex; i += 2 {
|
||||
key := vm.stack[i]
|
||||
value := vm.stack[i+1]
|
||||
|
||||
pair := object.HashPair{Key: key, Value: value}
|
||||
|
||||
hashKey, ok := key.(object.Hashable)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unusable as hash key: %s", key.Type())
|
||||
}
|
||||
|
||||
hashedPairs[hashKey.HashKey()] = pair
|
||||
}
|
||||
|
||||
return &object.Hash{Pairs: hashedPairs}, nil
|
||||
}
|
||||
|
||||
func (vm *VM) buildArray(startIndex, endIndex int) object.Object {
|
||||
elements := make([]object.Object, endIndex-startIndex)
|
||||
|
||||
for i := startIndex; i < endIndex; i++ {
|
||||
elements[i-startIndex] = vm.stack[i]
|
||||
}
|
||||
|
||||
return &object.Array{Elements: elements}
|
||||
}
|
||||
|
||||
func isTruthy(obj object.Object) bool {
|
||||
switch obj := obj.(type) {
|
||||
|
||||
case *object.Boolean:
|
||||
return obj.Value
|
||||
|
||||
case *object.Null:
|
||||
return false
|
||||
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func (vm *VM) push(o object.Object) error {
|
||||
if vm.sp >= StackSize {
|
||||
return fmt.Errorf("stack overflow")
|
||||
}
|
||||
|
||||
vm.stack[vm.sp] = o
|
||||
vm.sp++
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (vm *VM) pop() object.Object {
|
||||
o := vm.stack[vm.sp-1]
|
||||
vm.sp--
|
||||
return o
|
||||
}
|
||||
|
||||
func (vm *VM) executeBinaryOperation(op code.Opcode) error {
|
||||
right := vm.pop()
|
||||
left := vm.pop()
|
||||
|
||||
leftType := left.Type()
|
||||
rightType := right.Type()
|
||||
|
||||
switch {
|
||||
|
||||
// {"a": 1} + {"b": 2}
|
||||
case op == code.OpAdd && left.Type() == object.HASH_OBJ && right.Type() == object.HASH_OBJ:
|
||||
leftVal := left.(*object.Hash).Pairs
|
||||
rightVal := right.(*object.Hash).Pairs
|
||||
pairs := make(map[object.HashKey]object.HashPair)
|
||||
for k, v := range leftVal {
|
||||
pairs[k] = v
|
||||
}
|
||||
for k, v := range rightVal {
|
||||
pairs[k] = v
|
||||
}
|
||||
return vm.push(&object.Hash{Pairs: pairs})
|
||||
|
||||
// [1] + [2]
|
||||
case op == code.OpAdd && left.Type() == object.ARRAY_OBJ && right.Type() == object.ARRAY_OBJ:
|
||||
leftVal := left.(*object.Array).Elements
|
||||
rightVal := right.(*object.Array).Elements
|
||||
elements := make([]object.Object, len(leftVal)+len(rightVal))
|
||||
elements = append(leftVal, rightVal...)
|
||||
return vm.push(&object.Array{Elements: elements})
|
||||
|
||||
// [1] * 3
|
||||
case op == code.OpMul && left.Type() == object.ARRAY_OBJ && right.Type() == object.INTEGER_OBJ:
|
||||
leftVal := left.(*object.Array).Elements
|
||||
rightVal := int(right.(*object.Integer).Value)
|
||||
elements := leftVal
|
||||
for i := rightVal; i > 1; i-- {
|
||||
elements = append(elements, leftVal...)
|
||||
}
|
||||
return vm.push(&object.Array{Elements: elements})
|
||||
// 3 * [1]
|
||||
case op == code.OpMul && left.Type() == object.INTEGER_OBJ && right.Type() == object.ARRAY_OBJ:
|
||||
leftVal := int(left.(*object.Integer).Value)
|
||||
rightVal := right.(*object.Array).Elements
|
||||
elements := rightVal
|
||||
for i := leftVal; i > 1; i-- {
|
||||
elements = append(elements, rightVal...)
|
||||
}
|
||||
return vm.push(&object.Array{Elements: elements})
|
||||
|
||||
// " " * 4
|
||||
case op == code.OpMul && left.Type() == object.STRING_OBJ && right.Type() == object.INTEGER_OBJ:
|
||||
leftVal := left.(*object.String).Value
|
||||
rightVal := right.(*object.Integer).Value
|
||||
return vm.push(&object.String{Value: strings.Repeat(leftVal, int(rightVal))})
|
||||
// 4 * " "
|
||||
case op == code.OpMul && left.Type() == object.INTEGER_OBJ && right.Type() == object.STRING_OBJ:
|
||||
leftVal := left.(*object.Integer).Value
|
||||
rightVal := right.(*object.String).Value
|
||||
return vm.push(&object.String{Value: strings.Repeat(rightVal, int(leftVal))})
|
||||
|
||||
case leftType == object.BOOLEAN_OBJ && rightType == object.BOOLEAN_OBJ:
|
||||
return vm.executeBinaryBooleanOperation(op, left, right)
|
||||
case leftType == object.INTEGER_OBJ && rightType == object.INTEGER_OBJ:
|
||||
return vm.executeBinaryIntegerOperation(op, left, right)
|
||||
case leftType == object.STRING_OBJ && rightType == object.STRING_OBJ:
|
||||
return vm.executeBinaryStringOperation(op, left, right)
|
||||
default:
|
||||
return fmt.Errorf("unsupported types for binary operation: %s %s", leftType, rightType)
|
||||
}
|
||||
}
|
||||
|
||||
func (vm *VM) executeBinaryBooleanOperation(op code.Opcode, left, right object.Object) error {
|
||||
leftValue := left.(*object.Boolean).Value
|
||||
rightValue := right.(*object.Boolean).Value
|
||||
|
||||
var result bool
|
||||
|
||||
switch op {
|
||||
case code.OpOr:
|
||||
result = leftValue || rightValue
|
||||
case code.OpAnd:
|
||||
result = leftValue && rightValue
|
||||
default:
|
||||
return fmt.Errorf("unknown boolean operator: %d", op)
|
||||
}
|
||||
|
||||
return vm.push(&object.Boolean{Value: result})
|
||||
}
|
||||
|
||||
func (vm *VM) executeBinaryIntegerOperation(op code.Opcode, left, right object.Object) error {
|
||||
leftValue := left.(*object.Integer).Value
|
||||
rightValue := right.(*object.Integer).Value
|
||||
|
||||
var result int64
|
||||
|
||||
switch op {
|
||||
case code.OpAdd:
|
||||
result = leftValue + rightValue
|
||||
case code.OpSub:
|
||||
result = leftValue - rightValue
|
||||
case code.OpMul:
|
||||
result = leftValue * rightValue
|
||||
case code.OpDiv:
|
||||
result = leftValue / rightValue
|
||||
case code.OpMod:
|
||||
result = leftValue % rightValue
|
||||
case code.OpBitwiseOR:
|
||||
result = leftValue | rightValue
|
||||
case code.OpBitwiseXOR:
|
||||
result = leftValue ^ rightValue
|
||||
case code.OpBitwiseAND:
|
||||
result = leftValue & rightValue
|
||||
case code.OpLeftShift:
|
||||
result = leftValue << uint64(rightValue)
|
||||
case code.OpRightShift:
|
||||
result = leftValue >> uint64(rightValue)
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unknown integer operator: %d", op)
|
||||
}
|
||||
|
||||
return vm.push(&object.Integer{Value: result})
|
||||
}
|
||||
|
||||
func (vm *VM) executeBinaryStringOperation(op code.Opcode, left, right object.Object) error {
|
||||
if op != code.OpAdd {
|
||||
return fmt.Errorf("unknown string operator: %d", op)
|
||||
}
|
||||
|
||||
leftValue := left.(*object.String).Value
|
||||
rightValue := right.(*object.String).Value
|
||||
|
||||
return vm.push(&object.String{Value: leftValue + rightValue})
|
||||
}
|
||||
|
||||
func (vm *VM) executeComparison(op code.Opcode) error {
|
||||
right := vm.pop()
|
||||
left := vm.pop()
|
||||
|
||||
switch op {
|
||||
case code.OpEqual:
|
||||
return vm.push(nativeBoolToBooleanObject(left.(object.Comparable).Compare(right) == 0))
|
||||
case code.OpNotEqual:
|
||||
return vm.push(nativeBoolToBooleanObject(left.(object.Comparable).Compare(right) != 0))
|
||||
case code.OpGreaterThanEqual:
|
||||
return vm.push(nativeBoolToBooleanObject(left.(object.Comparable).Compare(right) > -1))
|
||||
case code.OpGreaterThan:
|
||||
return vm.push(nativeBoolToBooleanObject(left.(object.Comparable).Compare(right) == 1))
|
||||
default:
|
||||
return fmt.Errorf("unknown operator: %d (%s %s)",
|
||||
op, left.Type(), right.Type())
|
||||
}
|
||||
}
|
||||
|
||||
func (vm *VM) executeBitwiseNotOperator() error {
|
||||
operand := vm.pop()
|
||||
if i, ok := operand.(*object.Integer); ok {
|
||||
return vm.push(&object.Integer{Value: ^i.Value})
|
||||
}
|
||||
return fmt.Errorf("expected int got=%T", operand)
|
||||
}
|
||||
|
||||
func (vm *VM) executeNotOperator() error {
|
||||
operand := vm.pop()
|
||||
|
||||
switch operand {
|
||||
case True:
|
||||
return vm.push(False)
|
||||
case False:
|
||||
return vm.push(True)
|
||||
case Null:
|
||||
return vm.push(True)
|
||||
default:
|
||||
return vm.push(False)
|
||||
}
|
||||
}
|
||||
|
||||
func (vm *VM) executeMinusOperator() error {
|
||||
operand := vm.pop()
|
||||
|
||||
if i, ok := operand.(*object.Integer); ok {
|
||||
return vm.push(&object.Integer{Value: -i.Value})
|
||||
}
|
||||
|
||||
return fmt.Errorf("expected int got=%T", operand)
|
||||
}
|
||||
|
||||
func (vm *VM) executeCall(numArgs int) error {
|
||||
callee := vm.stack[vm.sp-1-numArgs]
|
||||
switch callee := callee.(type) {
|
||||
case *object.Closure:
|
||||
return vm.callClosure(callee, numArgs)
|
||||
case *object.Builtin:
|
||||
return vm.callBuiltin(callee, numArgs)
|
||||
default:
|
||||
return fmt.Errorf("calling non-function and non-built-in")
|
||||
}
|
||||
}
|
||||
|
||||
func (vm *VM) callClosure(cl *object.Closure, numArgs int) error {
|
||||
if numArgs != cl.Fn.NumParameters {
|
||||
return fmt.Errorf("wrong number of arguments: want=%d, got=%d", cl.Fn.NumParameters, numArgs)
|
||||
}
|
||||
|
||||
// Optimize tail calls and avoid a new frame
|
||||
if cl.Fn == vm.frame.cl.Fn {
|
||||
nextOP := vm.frame.NextOp()
|
||||
if nextOP == code.OpReturn {
|
||||
for p := 0; p < numArgs; p++ {
|
||||
vm.stack[vm.frame.basePointer+p] = vm.stack[vm.sp-numArgs+p]
|
||||
}
|
||||
vm.sp -= numArgs + 1
|
||||
vm.frame.ip = -1 // reset IP to the beginning of the frame
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
frame := NewFrame(cl, vm.sp-numArgs)
|
||||
vm.pushFrame(frame)
|
||||
vm.sp = frame.basePointer + cl.Fn.NumLocals
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (vm *VM) callBuiltin(builtin *object.Builtin, numArgs int) error {
|
||||
args := vm.stack[vm.sp-numArgs : vm.sp]
|
||||
|
||||
result := builtin.Fn(args...)
|
||||
vm.sp = vm.sp - numArgs - 1
|
||||
|
||||
if result != nil {
|
||||
err := vm.push(result)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
err := vm.push(Null)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (vm *VM) pushClosure(constIndex, numFree int) error {
|
||||
constant := vm.state.Constants[constIndex]
|
||||
function, ok := constant.(*object.CompiledFunction)
|
||||
if !ok {
|
||||
return fmt.Errorf("not a function %+v", constant)
|
||||
}
|
||||
|
||||
free := make([]object.Object, numFree)
|
||||
for i := 0; i < numFree; i++ {
|
||||
free[i] = vm.stack[vm.sp-numFree+i]
|
||||
}
|
||||
vm.sp = vm.sp - numFree
|
||||
|
||||
closure := &object.Closure{Fn: function, Free: free}
|
||||
return vm.push(closure)
|
||||
}
|
||||
|
||||
func (vm *VM) executeStringGetItem(str, index object.Object) error {
|
||||
stringObject := str.(*object.String)
|
||||
i := index.(*object.Integer).Value
|
||||
max := int64(len(stringObject.Value) - 1)
|
||||
|
||||
if i < 0 || i > max {
|
||||
return vm.push(&object.String{Value: ""})
|
||||
}
|
||||
|
||||
return vm.push(&object.String{Value: string(stringObject.Value[i])})
|
||||
}
|
||||
|
||||
func (vm *VM) executeStringIndex(str, index object.Object) error {
|
||||
stringObject := str.(*object.String)
|
||||
substr := index.(*object.String).Value
|
||||
|
||||
return vm.push(
|
||||
&object.Integer{
|
||||
Value: int64(strings.Index(stringObject.Value, substr)),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func nativeBoolToBooleanObject(input bool) *object.Boolean {
|
||||
if input {
|
||||
return True
|
||||
}
|
||||
return False
|
||||
}
|
||||
1215
internal/vm/vm_test.go
Normal file
1215
internal/vm/vm_test.go
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user