run functions
This commit is contained in:
115
vm/vm.go
115
vm/vm.go
@@ -9,30 +9,54 @@ import (
|
|||||||
|
|
||||||
const StackSize = 2048
|
const StackSize = 2048
|
||||||
const GlobalsSize = 65536
|
const GlobalsSize = 65536
|
||||||
|
const MaxFrames = 1024
|
||||||
|
|
||||||
var Null = &object.Null{}
|
var Null = &object.Null{}
|
||||||
var True = &object.Boolean{Value: true}
|
var True = &object.Boolean{Value: true}
|
||||||
var False = &object.Boolean{Value: false}
|
var False = &object.Boolean{Value: false}
|
||||||
|
|
||||||
|
type Frame struct {
|
||||||
|
fn *object.CompiledFunction
|
||||||
|
ip int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFrame(fn *object.CompiledFunction) *Frame {
|
||||||
|
return &Frame{fn: fn, ip: -1}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Frame) Instructions() code.Instructions {
|
||||||
|
return f.fn.Instructions
|
||||||
|
}
|
||||||
|
|
||||||
type VM struct {
|
type VM struct {
|
||||||
constants []object.Object
|
constants []object.Object
|
||||||
instructions code.Instructions
|
|
||||||
|
|
||||||
stack []object.Object
|
stack []object.Object
|
||||||
sp int // Always points to the next value. Top of stack is stack[sp-1]
|
sp int // Always points to the next value. Top of stack is stack[sp-1]
|
||||||
|
|
||||||
globals []object.Object
|
globals []object.Object
|
||||||
|
|
||||||
|
frames []*Frame
|
||||||
|
framesIndex int
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(bytecode *compiler.Bytecode) *VM {
|
func New(bytecode *compiler.Bytecode) *VM {
|
||||||
|
mainFn := &object.CompiledFunction{Instructions: bytecode.Instructions}
|
||||||
|
mainFrame := NewFrame(mainFn)
|
||||||
|
|
||||||
|
frames := make([]*Frame, MaxFrames)
|
||||||
|
frames[0] = mainFrame
|
||||||
|
|
||||||
return &VM{
|
return &VM{
|
||||||
constants: bytecode.Constants,
|
constants: bytecode.Constants,
|
||||||
instructions: bytecode.Instructions,
|
|
||||||
|
|
||||||
stack: make([]object.Object, StackSize),
|
stack: make([]object.Object, StackSize),
|
||||||
sp: 0,
|
sp: 0,
|
||||||
|
|
||||||
globals: make([]object.Object, GlobalsSize),
|
globals: make([]object.Object, GlobalsSize),
|
||||||
|
|
||||||
|
frames: frames,
|
||||||
|
framesIndex: 1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,18 +66,40 @@ func NewWithGlobalState(bytecode *compiler.Bytecode, s []object.Object) *VM {
|
|||||||
return vm
|
return vm
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (vm *VM) currentFrame() *Frame {
|
||||||
|
return vm.frames[vm.framesIndex-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vm *VM) pushFrame(f *Frame) {
|
||||||
|
vm.frames[vm.framesIndex] = f
|
||||||
|
vm.framesIndex++
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vm *VM) popFrame() *Frame {
|
||||||
|
vm.framesIndex--
|
||||||
|
return vm.frames[vm.framesIndex]
|
||||||
|
}
|
||||||
|
|
||||||
func (vm *VM) LastPoppedStackElem() object.Object {
|
func (vm *VM) LastPoppedStackElem() object.Object {
|
||||||
return vm.stack[vm.sp]
|
return vm.stack[vm.sp]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (vm *VM) Run() error {
|
func (vm *VM) Run() error {
|
||||||
for ip := 0; ip < len(vm.instructions); ip++ {
|
var ip int
|
||||||
op := code.Opcode(vm.instructions[ip])
|
var ins code.Instructions
|
||||||
|
var op code.Opcode
|
||||||
|
|
||||||
|
for vm.currentFrame().ip < len(vm.currentFrame().Instructions())-1 {
|
||||||
|
vm.currentFrame().ip++
|
||||||
|
|
||||||
|
ip = vm.currentFrame().ip
|
||||||
|
ins = vm.currentFrame().Instructions()
|
||||||
|
op = code.Opcode(ins[ip])
|
||||||
|
|
||||||
switch op {
|
switch op {
|
||||||
case code.OpConstant:
|
case code.OpConstant:
|
||||||
constIndex := code.ReadUint16(vm.instructions[ip+1:])
|
constIndex := code.ReadUint16(ins[ip+1:])
|
||||||
ip += 2
|
vm.currentFrame().ip += 2
|
||||||
|
|
||||||
err := vm.push(vm.constants[constIndex])
|
err := vm.push(vm.constants[constIndex])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -100,16 +146,16 @@ func (vm *VM) Run() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case code.OpJump:
|
case code.OpJump:
|
||||||
pos := int(code.ReadUint16(vm.instructions[ip+1:]))
|
pos := int(code.ReadUint16(ins[ip+1:]))
|
||||||
ip = pos - 1
|
vm.currentFrame().ip = pos - 1
|
||||||
|
|
||||||
case code.OpJumpNotTruthy:
|
case code.OpJumpNotTruthy:
|
||||||
pos := int(code.ReadUint16(vm.instructions[ip+1:]))
|
pos := int(code.ReadUint16(ins[ip+1:]))
|
||||||
ip += 2
|
vm.currentFrame().ip += 2
|
||||||
|
|
||||||
condition := vm.pop()
|
condition := vm.pop()
|
||||||
if !isTruthy(condition) {
|
if !isTruthy(condition) {
|
||||||
ip = pos - 1
|
vm.currentFrame().ip = pos - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
case code.OpNull:
|
case code.OpNull:
|
||||||
@@ -119,14 +165,14 @@ func (vm *VM) Run() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case code.OpSetGlobal:
|
case code.OpSetGlobal:
|
||||||
globalIndex := code.ReadUint16(vm.instructions[ip+1:])
|
globalIndex := code.ReadUint16(ins[ip+1:])
|
||||||
ip += 2
|
vm.currentFrame().ip += 2
|
||||||
|
|
||||||
vm.globals[globalIndex] = vm.pop()
|
vm.globals[globalIndex] = vm.pop()
|
||||||
|
|
||||||
case code.OpGetGlobal:
|
case code.OpGetGlobal:
|
||||||
globalIndex := code.ReadUint16(vm.instructions[ip+1:])
|
globalIndex := code.ReadUint16(ins[ip+1:])
|
||||||
ip += 2
|
vm.currentFrame().ip += 2
|
||||||
|
|
||||||
err := vm.push(vm.globals[globalIndex])
|
err := vm.push(vm.globals[globalIndex])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -134,8 +180,8 @@ func (vm *VM) Run() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case code.OpArray:
|
case code.OpArray:
|
||||||
numElements := int(code.ReadUint16(vm.instructions[ip+1:]))
|
numElements := int(code.ReadUint16(ins[ip+1:]))
|
||||||
ip += 2
|
vm.currentFrame().ip += 2
|
||||||
|
|
||||||
array := vm.buildArray(vm.sp-numElements, vm.sp)
|
array := vm.buildArray(vm.sp-numElements, vm.sp)
|
||||||
vm.sp = vm.sp - numElements
|
vm.sp = vm.sp - numElements
|
||||||
@@ -146,8 +192,8 @@ func (vm *VM) Run() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case code.OpHash:
|
case code.OpHash:
|
||||||
numElements := int(code.ReadUint16(vm.instructions[ip+1:]))
|
numElements := int(code.ReadUint16(ins[ip+1:]))
|
||||||
ip += 2
|
vm.currentFrame().ip += 2
|
||||||
|
|
||||||
hash, err := vm.buildHash(vm.sp-numElements, vm.sp)
|
hash, err := vm.buildHash(vm.sp-numElements, vm.sp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -169,6 +215,33 @@ func (vm *VM) Run() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case code.OpCall:
|
||||||
|
fn, ok := vm.stack[vm.sp-1].(*object.CompiledFunction)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("calling non-function")
|
||||||
|
}
|
||||||
|
frame := NewFrame(fn)
|
||||||
|
vm.pushFrame(frame)
|
||||||
|
|
||||||
|
case code.OpReturnValue:
|
||||||
|
returnValue := vm.pop()
|
||||||
|
vm.popFrame()
|
||||||
|
vm.pop()
|
||||||
|
|
||||||
|
err := vm.push(returnValue)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
case code.OpReturn:
|
||||||
|
vm.popFrame()
|
||||||
|
vm.pop()
|
||||||
|
|
||||||
|
err := vm.push(Null)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -296,3 +296,93 @@ func TestIndexExpressions(t *testing.T) {
|
|||||||
|
|
||||||
runVmTests(t, tests)
|
runVmTests(t, tests)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCallingFunctionsWithoutArguments(t *testing.T) {
|
||||||
|
tests := []vmTestCase{
|
||||||
|
{
|
||||||
|
input: `
|
||||||
|
let fivePlusTen = fn() { 5 + 10; };
|
||||||
|
fivePlusTen();
|
||||||
|
`,
|
||||||
|
expected: 15,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: `
|
||||||
|
let one = fn() { 1; };
|
||||||
|
let two = fn() { 2; };
|
||||||
|
one() + two()
|
||||||
|
`,
|
||||||
|
expected: 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: `
|
||||||
|
let a = fn() { 1 };
|
||||||
|
let b = fn() { a() + 1 };
|
||||||
|
let c = fn() { b() + 1 };
|
||||||
|
c();
|
||||||
|
`,
|
||||||
|
expected: 3,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
runVmTests(t, tests)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFunctionsWithReturnStatements(t *testing.T) {
|
||||||
|
tests := []vmTestCase{
|
||||||
|
{
|
||||||
|
input: `
|
||||||
|
let earlyExit = fn() { return 99; 100; };
|
||||||
|
earlyExit();
|
||||||
|
`,
|
||||||
|
expected: 99,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: `
|
||||||
|
let earlyExit = fn() { return 99; return 100; };
|
||||||
|
earlyExit();
|
||||||
|
`,
|
||||||
|
expected: 99,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
runVmTests(t, tests)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFunctionsWithoutReturnValue(t *testing.T) {
|
||||||
|
tests := []vmTestCase{
|
||||||
|
{
|
||||||
|
input: `
|
||||||
|
let noReturn = fn() { };
|
||||||
|
noReturn();
|
||||||
|
`,
|
||||||
|
expected: Null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: `
|
||||||
|
let noReturn = fn() { };
|
||||||
|
let noReturnTwo = fn() { noReturn(); };
|
||||||
|
noReturn();
|
||||||
|
noReturnTwo();
|
||||||
|
`,
|
||||||
|
expected: Null,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
runVmTests(t, tests)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFirstClassFunctions(t *testing.T) {
|
||||||
|
tests := []vmTestCase{
|
||||||
|
{
|
||||||
|
input: `
|
||||||
|
let returnsOne = fn() { 1; };
|
||||||
|
let returnsOneReturner = fn() { returnsOne; };
|
||||||
|
returnsOneReturner()();
|
||||||
|
`,
|
||||||
|
expected: 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
runVmTests(t, tests)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user