separate core from cli into submodules, add support for boolean and and or operations, make more fields public, fix passing arguments to functions
This commit is contained in:
parent
7498c85424
commit
62656c6dff
19 changed files with 202 additions and 99 deletions
603
core/vm.go
Normal file
603
core/vm.go
Normal file
|
|
@ -0,0 +1,603 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Pos int
|
||||
type Bytecode byte
|
||||
|
||||
const (
|
||||
// InstructionReturn return to previous call pointer
|
||||
InstructionReturn Bytecode = iota
|
||||
// InstructionPop pop and delete the first item on the stack
|
||||
InstructionPop
|
||||
|
||||
// InstructionAdd pop two and add them
|
||||
InstructionAdd
|
||||
// InstructionSub pop two and subtract the second from the first
|
||||
InstructionSub
|
||||
// InstructionMul pop two and multiply them
|
||||
InstructionMul
|
||||
// InstructionDiv pop two and divide the second by the first
|
||||
InstructionDiv
|
||||
// InstructionEquals whether the two top values on the stack are equal
|
||||
InstructionEquals
|
||||
// InstructionNotEqual whether the two top values on the stack are not equal
|
||||
InstructionNotEqual
|
||||
// InstructionNot inverts boolean (true => false, false => true)
|
||||
InstructionNot
|
||||
// InstructionLess pops two from stack, pushes whether the lowest is less than the highest
|
||||
InstructionLess
|
||||
// InstructionLessOrEqual pops two from stack, pushes whether the lowest is less or equal than the highest
|
||||
InstructionLessOrEqual
|
||||
// InstructionGreater pops two from stack, pushes whether the lowest is greater than the highest
|
||||
InstructionGreater
|
||||
// InstructionGreaterOrEqual pops two from stack, pushes whether the lowest is greater or equal than the highest
|
||||
InstructionGreaterOrEqual
|
||||
|
||||
// InstructionCall pops a function object from the stack and begins execution of the chunk
|
||||
InstructionCall
|
||||
|
||||
// InstructionDescend increase the scope depth
|
||||
InstructionDescend
|
||||
// InstructionAscend decrease the scope depth, and remove all variables on the stack which belong in a higher scope
|
||||
InstructionAscend
|
||||
|
||||
// InstructionJump jump forwards by the value of the next two bytes as a u16
|
||||
InstructionJump
|
||||
// InstructionJumpFalse jump by the value of the two next bytes as an unsigned integer if the first value (popped) from the stack is false
|
||||
InstructionJumpFalse
|
||||
// InstructionLoop jump by the value of the two next bytes as an unsigned integer backwards if the first value (popped) from the stack is true
|
||||
InstructionLoop
|
||||
|
||||
// InstructionGetLocal Push a constant to the stack (2 bytes, second = constant index)
|
||||
InstructionGetLocal
|
||||
// InstructionSetLocal Set a local variable
|
||||
InstructionSetLocal
|
||||
// InstructionDeclareLocal Declare a new local variable in the uppermost scope
|
||||
InstructionDeclareLocal
|
||||
// InstructionGetGlobal Set a global variable (the next byte is the index of the constant with the name of the variable
|
||||
InstructionGetGlobal
|
||||
// InstructionSetGlobal Push a constant to the stack (2 bytes, second = constant index)
|
||||
InstructionSetGlobal
|
||||
|
||||
// InstructionStringConversion Take the top value on the stack and convert it to a string
|
||||
InstructionStringConversion
|
||||
// InstructionStringConcatenation Add two strings together, with the second value on the stack as left and the top as right
|
||||
InstructionStringConcatenation
|
||||
|
||||
// InstructionSwap swap the two top items on the stack (1, 2 -> 2, 1)
|
||||
InstructionSwap
|
||||
|
||||
// InstructionAnd pop two booleans and push true if both are true
|
||||
InstructionAnd
|
||||
// InstructionOr pop two booleans and push true if either are true
|
||||
InstructionOr
|
||||
|
||||
// InstructionConstant Push a constant to the stack (2 bytes, second = constant index)
|
||||
InstructionConstant
|
||||
// InstructionTrue Push a true literal to the stack
|
||||
InstructionTrue
|
||||
// InstructionFalse Push a false literal to the stack
|
||||
InstructionFalse
|
||||
// InstructionNil Push a nil literal to the stack
|
||||
InstructionNil
|
||||
|
||||
// InstructionBreakpoint for debugging purposes
|
||||
InstructionBreakpoint
|
||||
)
|
||||
|
||||
func (b Bytecode) String() string {
|
||||
switch b {
|
||||
case InstructionReturn:
|
||||
return "RETURN"
|
||||
case InstructionPop:
|
||||
return "POP"
|
||||
case InstructionAdd:
|
||||
return "ADD"
|
||||
case InstructionSub:
|
||||
return "SUB"
|
||||
case InstructionMul:
|
||||
return "MUL"
|
||||
case InstructionDiv:
|
||||
return "DIV"
|
||||
case InstructionEquals:
|
||||
return "EQUALS"
|
||||
case InstructionNotEqual:
|
||||
return "NOT_EQUALS"
|
||||
case InstructionNot:
|
||||
return "NOT"
|
||||
case InstructionLess:
|
||||
return "LESS"
|
||||
case InstructionLessOrEqual:
|
||||
return "LESS_OR_EQUAL"
|
||||
case InstructionGreater:
|
||||
return "GREATER_OR_EQUAL"
|
||||
case InstructionGreaterOrEqual:
|
||||
return "GREATER_OR_EQUAL"
|
||||
case InstructionJump:
|
||||
return "JUMP"
|
||||
case InstructionJumpFalse:
|
||||
return "JUMP_FALSE"
|
||||
case InstructionLoop:
|
||||
return "LOOP"
|
||||
case InstructionConstant:
|
||||
return "CONSTANT"
|
||||
case InstructionTrue:
|
||||
return "TRUE"
|
||||
case InstructionFalse:
|
||||
return "FALSE"
|
||||
case InstructionNil:
|
||||
return "NIL"
|
||||
case InstructionGetLocal:
|
||||
return "GET_LOCAL"
|
||||
case InstructionDeclareLocal:
|
||||
return "DECLARE_LOCAL"
|
||||
case InstructionSetLocal:
|
||||
return "SET_LOCAL"
|
||||
case InstructionGetGlobal:
|
||||
return "GET_GLOBAL"
|
||||
case InstructionSetGlobal:
|
||||
return "SET_GLOBAL"
|
||||
case InstructionCall:
|
||||
return "CALL"
|
||||
case InstructionDescend:
|
||||
return "DESCEND"
|
||||
case InstructionAscend:
|
||||
return "ASCEND"
|
||||
case InstructionStringConversion:
|
||||
return "STRING_CONVERSION"
|
||||
case InstructionStringConcatenation:
|
||||
return "STRING_CONCATENATION"
|
||||
case InstructionSwap:
|
||||
return "SWAP"
|
||||
case InstructionAnd:
|
||||
return "AND"
|
||||
case InstructionOr:
|
||||
return "OR"
|
||||
case InstructionBreakpoint:
|
||||
return "BREAKPOINT"
|
||||
}
|
||||
return "UNDEFINED"
|
||||
}
|
||||
|
||||
type Chunk struct {
|
||||
Bytecode []Bytecode
|
||||
Constants []Value
|
||||
}
|
||||
|
||||
func (c Chunk) String() string {
|
||||
b := strings.Builder{}
|
||||
|
||||
b.WriteString("=v= chunk =v=\n")
|
||||
for i, bc := range c.Bytecode {
|
||||
b.WriteString(fmt.Sprintf("i=%d \t%d \t(%s)\n", i, bc, bc))
|
||||
}
|
||||
|
||||
b.WriteString("=-= constants =-=\n")
|
||||
|
||||
for i, ct := range c.Constants {
|
||||
b.WriteString(fmt.Sprintf("c=%d \t%s\n", i, ct))
|
||||
|
||||
f, ok := ct.(FunctionValue)
|
||||
if ok {
|
||||
b.WriteString(f.Chunk.String())
|
||||
}
|
||||
}
|
||||
|
||||
b.WriteString("=^= chunk =^=\n")
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func NewChunk(bytecode []Bytecode, constants []Value) *Chunk {
|
||||
return &Chunk{bytecode, constants}
|
||||
}
|
||||
|
||||
func RegisterGOBTypes() {
|
||||
gob.Register(StringValue(""))
|
||||
gob.Register(NumberValue(0))
|
||||
gob.Register(FunctionValue{
|
||||
Name: "",
|
||||
Params: nil,
|
||||
Chunk: nil,
|
||||
})
|
||||
}
|
||||
|
||||
func (c Chunk) Serialize() []byte {
|
||||
b := bytes.Buffer{}
|
||||
|
||||
e := gob.NewEncoder(&b)
|
||||
|
||||
err := e.Encode(c)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
return b.Bytes()
|
||||
}
|
||||
|
||||
func DeserializeChunk(b []byte) *Chunk {
|
||||
m := Chunk{}
|
||||
|
||||
buf := bytes.Buffer{}
|
||||
buf.Write(b)
|
||||
|
||||
d := gob.NewDecoder(&buf)
|
||||
|
||||
err := d.Decode(&m)
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
return &m
|
||||
}
|
||||
|
||||
type VM struct {
|
||||
// Replace with chunk of bytecode
|
||||
chunk *Chunk
|
||||
|
||||
// instruction pointer
|
||||
ip Pos
|
||||
scope Pos
|
||||
|
||||
// global variable storage
|
||||
globals map[string]Value
|
||||
variableEnd Pos
|
||||
|
||||
stack *Stack[Value]
|
||||
call *Stack[Call]
|
||||
}
|
||||
|
||||
type Call struct {
|
||||
chunk *Chunk
|
||||
ip Pos
|
||||
stackEnd Pos
|
||||
variableEnd Pos
|
||||
}
|
||||
|
||||
var DefaultGlobals = map[string]Value{
|
||||
"write": BuiltinFunctionValue{
|
||||
"write", // always remember where you come from...
|
||||
[]string{"value"},
|
||||
func(v map[string]Value) Value {
|
||||
println(v["value"].String())
|
||||
return nil
|
||||
},
|
||||
},
|
||||
"print": BuiltinFunctionValue{
|
||||
"print",
|
||||
[]string{"value"},
|
||||
func(v map[string]Value) Value {
|
||||
print(v["value"].String())
|
||||
return nil
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func NewVM(chunk *Chunk, stackSize Pos, callstackSize Pos) *VM {
|
||||
vm := &VM{
|
||||
chunk: chunk,
|
||||
stack: NewStack[Value](stackSize),
|
||||
call: NewStack[Call](callstackSize),
|
||||
|
||||
globals: DefaultGlobals,
|
||||
}
|
||||
|
||||
return vm
|
||||
}
|
||||
|
||||
// Next execute instruction
|
||||
// returns true if more instructions should be executed
|
||||
func (vm *VM) Next() bool {
|
||||
switch vm.NextByte() {
|
||||
case InstructionReturn:
|
||||
if vm.call.Current <= 0 {
|
||||
return false
|
||||
} else {
|
||||
v := vm.stack.Pop()
|
||||
c := vm.call.Pop()
|
||||
|
||||
// reset stack current and variable end
|
||||
vm.variableEnd = c.variableEnd
|
||||
vm.stack.Current = c.stackEnd
|
||||
|
||||
// reset to calling position
|
||||
vm.ip = c.ip
|
||||
vm.chunk = c.chunk
|
||||
|
||||
vm.stack.Push(v)
|
||||
}
|
||||
|
||||
case InstructionPop:
|
||||
vm.stack.Pop()
|
||||
|
||||
case InstructionConstant:
|
||||
vm.stack.Push(vm.ReadConstant())
|
||||
|
||||
case InstructionAdd:
|
||||
r := vm.stack.Pop().(NumberValue)
|
||||
l := vm.stack.Pop().(NumberValue)
|
||||
|
||||
vm.stack.Push(l + r)
|
||||
|
||||
case InstructionSub:
|
||||
r := vm.stack.Pop().(NumberValue)
|
||||
l := vm.stack.Pop().(NumberValue)
|
||||
|
||||
vm.stack.Push(l - r)
|
||||
|
||||
case InstructionMul:
|
||||
r := vm.stack.Pop().(NumberValue)
|
||||
l := vm.stack.Pop().(NumberValue)
|
||||
|
||||
vm.stack.Push(l * r)
|
||||
|
||||
case InstructionDiv:
|
||||
r := vm.stack.Pop().(NumberValue)
|
||||
l := vm.stack.Pop().(NumberValue)
|
||||
|
||||
vm.stack.Push(l / r)
|
||||
|
||||
case InstructionEquals:
|
||||
r := vm.stack.Pop().(NumberValue)
|
||||
l := vm.stack.Pop().(NumberValue)
|
||||
|
||||
vm.stack.Push(BoolValue(l == r))
|
||||
|
||||
case InstructionNotEqual:
|
||||
r := vm.stack.Pop().(NumberValue)
|
||||
l := vm.stack.Pop().(NumberValue)
|
||||
|
||||
vm.stack.Push(BoolValue(l != r))
|
||||
|
||||
case InstructionNot:
|
||||
b := vm.stack.Pop().(BoolValue)
|
||||
vm.stack.Push(!b)
|
||||
|
||||
case InstructionAnd:
|
||||
vm.stack.Push(vm.stack.Pop().(BoolValue) && vm.stack.Pop().(BoolValue))
|
||||
|
||||
case InstructionOr:
|
||||
vm.stack.Push(vm.stack.Pop().(BoolValue) || vm.stack.Pop().(BoolValue))
|
||||
|
||||
case InstructionLess:
|
||||
r := vm.stack.Pop().(NumberValue)
|
||||
l := vm.stack.Pop().(NumberValue)
|
||||
|
||||
vm.stack.Push(BoolValue(l < r))
|
||||
|
||||
case InstructionLessOrEqual:
|
||||
r := vm.stack.Pop().(NumberValue)
|
||||
l := vm.stack.Pop().(NumberValue)
|
||||
|
||||
vm.stack.Push(BoolValue(l <= r))
|
||||
|
||||
case InstructionGreater:
|
||||
r := vm.stack.Pop().(NumberValue)
|
||||
l := vm.stack.Pop().(NumberValue)
|
||||
|
||||
vm.stack.Push(BoolValue(l > r))
|
||||
|
||||
case InstructionGreaterOrEqual:
|
||||
r := vm.stack.Pop().(NumberValue)
|
||||
l := vm.stack.Pop().(NumberValue)
|
||||
|
||||
vm.stack.Push(BoolValue(l >= r))
|
||||
|
||||
case InstructionCall:
|
||||
v := vm.stack.Pop()
|
||||
switch f := v.(type) {
|
||||
case FunctionValue:
|
||||
vm.call.Push(Call{
|
||||
chunk: vm.chunk,
|
||||
ip: vm.ip,
|
||||
stackEnd: vm.stack.Current - Pos(len(f.Params)),
|
||||
variableEnd: vm.variableEnd,
|
||||
})
|
||||
|
||||
for i := len(f.Params) - 1; i >= 0; i-- {
|
||||
p := vm.stack.Current - Pos(len(f.Params)) + Pos(i)
|
||||
vm.stack.items[p] = &VariableValue{
|
||||
f.Params[i],
|
||||
vm.stack.items[p],
|
||||
vm.scope,
|
||||
}
|
||||
}
|
||||
|
||||
vm.variableEnd = vm.stack.Current
|
||||
|
||||
vm.chunk = f.Chunk
|
||||
vm.ip = 0
|
||||
case BuiltinFunctionValue:
|
||||
args := map[string]Value{}
|
||||
|
||||
for i := len(f.Parameters) - 1; i >= 0; i-- {
|
||||
args[f.Parameters[i]] = vm.stack.Pop()
|
||||
}
|
||||
|
||||
vm.stack.Push(f.F(args))
|
||||
default:
|
||||
vm.error(fmt.Sprintf("value called is not a function (%s)", v.String()))
|
||||
return false
|
||||
}
|
||||
|
||||
case InstructionJump:
|
||||
vm.ip += Pos(vm.NextU16())
|
||||
|
||||
case InstructionLoop:
|
||||
vm.ip -= Pos(vm.NextU16())
|
||||
|
||||
case InstructionJumpFalse:
|
||||
n := vm.NextU16()
|
||||
if !vm.stack.Pop().(BoolValue) {
|
||||
vm.ip += Pos(n)
|
||||
}
|
||||
|
||||
case InstructionGetLocal:
|
||||
name := vm.GetConstant(vm.NextByte()).(StringValue)
|
||||
v := vm.getVar(string(name))
|
||||
|
||||
if v == nil {
|
||||
vm.error(fmt.Sprintf("cannot get local: undefined variable %s", name))
|
||||
return false
|
||||
}
|
||||
|
||||
vm.stack.Push(v.value)
|
||||
|
||||
case InstructionSetLocal:
|
||||
value := vm.stack.Pop().(Value)
|
||||
name := vm.GetConstant(vm.NextByte()).(StringValue)
|
||||
|
||||
v := vm.getVar(string(name))
|
||||
|
||||
if v == nil {
|
||||
vm.error(fmt.Sprintf("cannot set local: undefined variable %s", name))
|
||||
}
|
||||
|
||||
v.value = value
|
||||
|
||||
case InstructionDeclareLocal:
|
||||
vm.addVar(
|
||||
string(vm.GetConstant(vm.NextByte()).(StringValue)),
|
||||
vm.stack.Pop().(Value),
|
||||
)
|
||||
|
||||
case InstructionGetGlobal:
|
||||
vm.stack.Push(vm.globals[string(vm.GetConstant(vm.NextByte()).(StringValue))])
|
||||
|
||||
case InstructionSetGlobal:
|
||||
vm.globals[string(vm.GetConstant(vm.NextByte()).(StringValue))] = vm.stack.Pop()
|
||||
|
||||
case InstructionTrue:
|
||||
vm.stack.Push(BoolValue(true))
|
||||
|
||||
case InstructionFalse:
|
||||
vm.stack.Push(BoolValue(false))
|
||||
|
||||
case InstructionNil:
|
||||
vm.stack.Push(NilValue{})
|
||||
|
||||
case InstructionDescend:
|
||||
vm.descend()
|
||||
|
||||
case InstructionAscend:
|
||||
vm.ascend()
|
||||
|
||||
case InstructionStringConversion:
|
||||
v := vm.stack.Pop()
|
||||
vm.stack.Push(StringValue(v.String()))
|
||||
|
||||
case InstructionStringConcatenation:
|
||||
r := vm.stack.Pop().(StringValue)
|
||||
l := vm.stack.Pop().(StringValue)
|
||||
|
||||
vm.stack.Push(l + r)
|
||||
|
||||
case InstructionSwap:
|
||||
r := vm.stack.Pop()
|
||||
l := vm.stack.Pop()
|
||||
|
||||
vm.stack.Push(r, l)
|
||||
|
||||
case InstructionBreakpoint:
|
||||
|
||||
default:
|
||||
panic("invalid byte code")
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (vm *VM) TryNextByte() (Bytecode, error) {
|
||||
if !vm.HasNext() {
|
||||
return 0, errors.New("there are no more instructions")
|
||||
}
|
||||
|
||||
v := vm.chunk.Bytecode[vm.ip]
|
||||
vm.ip++
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func (vm *VM) NextByte() Bytecode {
|
||||
b, err := vm.TryNextByte()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func (vm *VM) ascend() {
|
||||
vm.scope--
|
||||
|
||||
if vm.scope < 0 {
|
||||
panic("invalid scope")
|
||||
}
|
||||
|
||||
for ; vm.variableEnd > 0 && vm.stack.items[vm.variableEnd-1].(*VariableValue).scope > vm.scope; vm.variableEnd-- {
|
||||
vm.stack.Pop()
|
||||
}
|
||||
}
|
||||
|
||||
func (vm *VM) descend() {
|
||||
vm.scope++
|
||||
}
|
||||
|
||||
func (vm *VM) addVar(name string, value Value) {
|
||||
vm.variableEnd++
|
||||
vm.stack.Push(&VariableValue{
|
||||
name,
|
||||
value,
|
||||
vm.scope,
|
||||
})
|
||||
}
|
||||
|
||||
func (vm *VM) getVar(name string) *VariableValue {
|
||||
for i := vm.variableEnd - 1; i >= 0; i-- {
|
||||
v := vm.stack.items[i].(*VariableValue)
|
||||
|
||||
if v.name == name {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (vm *VM) HasNext() bool {
|
||||
return vm.ip < Pos(len(vm.chunk.Bytecode))
|
||||
}
|
||||
|
||||
func (vm *VM) GetConstant(id Bytecode) Value {
|
||||
return vm.chunk.Constants[id]
|
||||
}
|
||||
|
||||
func (vm *VM) ReadConstant() Value {
|
||||
return vm.GetConstant(vm.NextByte())
|
||||
}
|
||||
|
||||
func (vm *VM) NextU16() uint16 {
|
||||
return (uint16(vm.NextByte()) << 8) | uint16(vm.NextByte())
|
||||
}
|
||||
|
||||
func (vm *VM) error(error string) {
|
||||
log.Fatal(error)
|
||||
}
|
||||
|
||||
func (vm *VM) SetGlobal(name string, value Value) {
|
||||
vm.globals[name] = value
|
||||
}
|
||||
|
||||
func (vm *VM) GetGlobal(name string) Value {
|
||||
return vm.globals[name]
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue