anglais/core/vm.go
2026-07-13 23:34:19 +02:00

1282 lines
29 KiB
Go

package core
import (
"bytes"
"encoding/gob"
"errors"
"fmt"
"log"
"math"
"math/big"
"os"
"strconv"
"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
// InstructionAddFloat pop two floats and add them
InstructionAddFloat
// InstructionSubFloat pop two floats and subtract the second from the first
InstructionSubFloat
// InstructionMulFloat pop two floats and multiply them
InstructionMulFloat
// InstructionDivFloat pop two floats and divide the second by the first
InstructionDivFloat
// InstructionNegateFloat negate the float; if it was positive, make it negative, and vice versa.
InstructionNegateFloat
// InstructionAddInt pop two ints and add them
InstructionAddInt
// InstructionSubInt pop two ints and subtract the second from the first
InstructionSubInt
// InstructionMulInt pop two ints and multiply them
InstructionMulInt
// InstructionDivInt pop two ints and divide the second by the first
InstructionDivInt
// InstructionModInt pop two ints and compute the modulo of the first by the second
InstructionModInt
// InstructionNegateInt negate the int; if it was positive, make it negative, and vice versa.
InstructionNegateInt
// 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
// InstructionLessFloat pops two from stack, pushes whether the lowest is less than the highest
InstructionLessFloat
// InstructionLessOrEqualFloat pops two from stack, pushes whether the lowest is less or equal than the highest
InstructionLessOrEqualFloat
// InstructionGreaterFloat pops two from stack, pushes whether the lowest is greater than the highest
InstructionGreaterFloat
// InstructionGreaterOrEqualFloat pops two from stack, pushes whether the lowest is greater or equal than the highest
InstructionGreaterOrEqualFloat
// InstructionLessInt pops two from stack, pushes whether the lowest is less than the highest
InstructionLessInt
// InstructionLessOrEqualInt pops two from stack, pushes whether the lowest is less or equal than the highest
InstructionLessOrEqualInt
// InstructionGreaterInt pops two from stack, pushes whether the lowest is greater than the highest
InstructionGreaterInt
// InstructionGreaterOrEqualInt pops two from stack, pushes whether the lowest is greater or equal than the highest
InstructionGreaterOrEqualInt
// InstructionAccessProperty gets a property from a value, and pops it onto the stack
InstructionAccessProperty
// 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
// InstructionConcatStrings Add two strings together, with the second value on the stack as left and the top as right
InstructionConcatStrings
// InstructionSwap swap the two top items on the stack (1, 2 -> 2, 1)
InstructionSwap
// InstructionDuplicate push a copy of the item on top of the stack (1 -> 1, 1)
InstructionDuplicate
// 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
// InstructionAppend Append to a list. stack: (... > list > item) => (... > list)
InstructionAppend
// InstructionFormList Form items on the stack into a list. The 2 bytes after the instructions are the amount of
// items to include) The order is reversed compared to on the stack; the top value on the stack is the last in the
// list.
InstructionFormList
// InstructionConcatLists concatenate lists, producing a new list with the values of both lists. Pops two lists.
InstructionConcatLists
// InstructionFormTuple pop n+1 (u16) items from the stack, and create a new tuple with the items. The top value
// on the stack is the last value in the tuple.
InstructionFormTuple
// InstructionDestructureTuple pop a tuple, and push all its items to the stack, with the top item on the stack
// being the last item in the tuple.
InstructionDestructureTuple
// InstructionIndexList index into a list. The lower item is the container, and the top item
// is the index. [..., container, index] -> [..., item]
InstructionIndexList
// InstructionIndexTuple index into a tuple. The lower item is the container, and the top item
// is the index. [..., container, index] -> [..., item]
InstructionIndexTuple
// InstructionIndexString index into a string. The lower item is the container, and the top item
// is the index. [..., container, index] -> [..., item]. Produces a new string with the character
// at the position
InstructionIndexString
// InstructionBreakpoint for debugging purposes
InstructionBreakpoint
)
func (b Bytecode) String() string {
switch b {
case InstructionReturn:
return "RETURN"
case InstructionPop:
return "POP"
case InstructionAddFloat:
return "ADD_FLOAT"
case InstructionSubFloat:
return "SUB_FLOAT"
case InstructionMulFloat:
return "MUL_FLOAT"
case InstructionDivFloat:
return "DIV_FLOAT"
case InstructionNegateFloat:
return "NEGATE_FLOAT"
case InstructionAddInt:
return "ADD_INT"
case InstructionSubInt:
return "SUB_INT"
case InstructionMulInt:
return "MUL_INT"
case InstructionDivInt:
return "DIV_INT"
case InstructionNegateInt:
return "NEGATE_INT"
case InstructionEquals:
return "EQUALS"
case InstructionNotEqual:
return "NOT_EQUALS"
case InstructionNot:
return "NOT"
case InstructionLessFloat:
return "LESS_FLOAT"
case InstructionLessOrEqualFloat:
return "LESS_OR_EQUAL_FLOAT"
case InstructionGreaterFloat:
return "GREATER_FLOAT"
case InstructionGreaterOrEqualFloat:
return "GREATER_OR_EQUAL_FLOAT"
case InstructionLessInt:
return "LESS_INT"
case InstructionLessOrEqualInt:
return "LESS_OR_EQUAL_INT"
case InstructionGreaterInt:
return "GREATER_INT"
case InstructionGreaterOrEqualInt:
return "GREATER_OR_EQUAL_INT"
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 InstructionConcatStrings:
return "STRING_CONCATENATION"
case InstructionSwap:
return "SWAP"
case InstructionAnd:
return "AND"
case InstructionOr:
return "OR"
case InstructionFormList:
return "FORM_LIST"
case InstructionBreakpoint:
return "BREAKPOINT"
case InstructionAppend:
return "APPEND"
case InstructionAccessProperty:
return "ACCESS_PROPERTY"
case InstructionConcatLists:
return "CONCAT_LISTS"
case InstructionDuplicate:
return "DUPLICATE"
case InstructionFormTuple:
return "FORM_TUPLE"
case InstructionIndexList:
return "INDEX_LIST"
case InstructionIndexTuple:
return "INDEX_TUPLE"
case InstructionDestructureTuple:
return "DESTRUCTURE_TUPLE"
}
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.DebugString()))
f, ok := ct.(*FunctionValue)
if ok {
b.WriteString(f.Chunk.String())
}
}
b.WriteString("=^= chunk =^=\n")
return b.String()
}
func (c *Chunk) Equals(other *Chunk) bool {
if len(c.Bytecode) != len(other.Bytecode) {
return false
}
for i, bc := range c.Bytecode {
if other.Bytecode[i] != bc {
return false
}
}
if len(c.Constants) != len(other.Constants) {
return false
}
for i := 0; i < len(c.Constants); i++ {
if other.Constants[i] != c.Constants[i] {
return false
}
}
return true
}
func NewChunk(bytecode []Bytecode, constants []Value) *Chunk {
return &Chunk{bytecode, constants}
}
func RegisterGOBTypes() {
gob.Register(&StringValue{""})
gob.Register(&BoolValue{false})
gob.Register(&FloatValue{0})
gob.Register(&FunctionValue{
Name: "",
Params: nil,
Chunk: nil,
})
// Signatures
gob.Register(&NilSignature{})
gob.Register(&FloatSignature{})
gob.Register(&StringSignature{})
gob.Register(&FunctionSignature{})
gob.Register(&ListSignature{})
gob.Register(&ObjectSignature{})
gob.Register(&BooleanSignature{})
}
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
// global variable storage
globals map[string]Value
// local variable storage
scope *Scope
Stack *Stack[Value]
call *Stack[Call]
}
type Scope struct {
current map[string]Value
parent *Scope
}
type Call struct {
chunk *Chunk
ip Pos
scope *Scope
}
var DefaultGlobals = map[string]Value{
"println": &BuiltinFunctionValue{
"write", // always remember where you come from...
&FunctionSignature{
[]TypeSignature{&AnySignature{}},
&NilSignature{},
},
func(_ *VM, this Value, v []Value) (Value, error) {
println(v[0].String())
return &NilValue{}, nil
},
nil,
false,
},
"print": &BuiltinFunctionValue{
"print",
&FunctionSignature{
[]TypeSignature{&AnySignature{}},
&NilSignature{},
},
func(_ *VM, this Value, v []Value) (Value, error) {
print(v[0].String())
return &NilValue{}, nil
},
nil,
false,
},
"format": &BuiltinFunctionValue{
"format",
&FunctionSignature{
[]TypeSignature{
&StringSignature{},
&ListSignature{
&AnySignature{},
},
},
&StringSignature{},
},
func(vm *VM, value Value, m []Value) (Value, error) {
b := strings.Builder{}
template := m[0].(*StringValue).Text
valuies := m[1].(*ListValue).Items
vi := 0
last := 0
for i := 0; i < len(template); i++ {
if template[i] == '%' {
b.WriteString(template[last:i])
b.WriteString(valuies[vi].String())
vi++
last = i + 1
}
}
b.WriteString(template[last:])
return GoToValue(b.String()), nil
},
nil,
true,
},
"char": &BuiltinFunctionValue{
"char",
&FunctionSignature{
[]TypeSignature{&IntegerSignature{}},
&StringSignature{},
},
func(vm *VM, this Value, args []Value) (Value, error) {
n := args[0].(*IntegerValue).Number
b := n.Bytes()[0]
return &StringValue{
string([]byte{b}),
}, nil
},
nil,
true,
},
"byte": &BuiltinFunctionValue{
"byte",
&FunctionSignature{
[]TypeSignature{&StringSignature{}},
&IntegerSignature{},
},
func(vm *VM, this Value, args []Value) (Value, error) {
s := args[0].(*StringValue).Text
n := new(big.Int).SetBytes([]byte(s))
return &IntegerValue{n}, nil
},
nil,
true,
},
"assert": &BuiltinFunctionValue{
"assert",
&FunctionSignature{
[]TypeSignature{
&BooleanSignature{},
},
&NilSignature{},
},
func(vm *VM, this Value, params []Value) (Value, error) {
b := params[0].(*BoolValue)
if !b.Boolean {
return nil, errors.New(fmt.Sprintf("assertion failed: %s", b))
}
return &NilValue{}, nil
},
nil,
false,
},
"assertEq": &BuiltinFunctionValue{
"assertEq",
&FunctionSignature{
[]TypeSignature{
&AnySignature{},
&AnySignature{},
},
&NilSignature{},
},
func(vm *VM, this Value, params []Value) (Value, error) {
a := params[0]
b := params[1]
if !a.Equals(b) {
return nil, errors.New(fmt.Sprintf("assertion failed: %s does not equal %s", a, b))
}
return &NilValue{}, nil
},
nil,
false,
},
"assertNotEq": &BuiltinFunctionValue{
"assertNotEq",
&FunctionSignature{
[]TypeSignature{
&AnySignature{},
&AnySignature{},
},
&NilSignature{},
},
func(vm *VM, this Value, params []Value) (Value, error) {
a := params[0]
b := params[1]
if a.Equals(b) {
return nil, errors.New(fmt.Sprintf("assertion failed: %s does not equal %s", a, b))
}
return &NilValue{}, nil
},
nil,
false,
},
"str": &BuiltinFunctionValue{
"str",
&FunctionSignature{
[]TypeSignature{&AnySignature{}},
&StringSignature{},
},
func(vm *VM, _ Value, args []Value) (Value, error) {
return GoToValue(args[0].String()), nil
},
nil,
true,
},
"int": &BuiltinFunctionValue{
"int",
&FunctionSignature{
[]TypeSignature{
quickComposite(
&IntegerSignature{},
&FloatSignature{},
&StringSignature{},
),
},
&CompositeSignature{
&IntegerSignature{},
&NilSignature{},
},
},
func(vm *VM, _ Value, args []Value) (Value, error) {
switch v := args[0].(type) {
case *IntegerValue:
return &IntegerValue{v.Number}, nil // this might need to clone the value instead
case *FloatValue:
n := new(big.Int).SetInt64(int64(v.Number))
return &IntegerValue{n}, nil
case *StringValue:
n, success := new(big.Int).SetString(v.Text, 0) // determine base
if !success {
return &NilValue{}, nil
}
return &IntegerValue{n}, nil
default:
return nil, errors.New(fmt.Sprintf("%s cannot become an integer (undefined)", v))
}
},
nil,
true,
},
"float": &BuiltinFunctionValue{
"float",
&FunctionSignature{
[]TypeSignature{
quickComposite(
&FloatSignature{},
&IntegerSignature{},
&StringSignature{},
),
},
&FloatSignature{},
},
func(vm *VM, _ Value, args []Value) (Value, error) {
switch v := args[0].(type) {
case *IntegerValue:
n, _ := v.Number.Float64()
return &FloatValue{n}, nil
case *FloatValue:
return v.Clone(), nil
case *StringValue:
num, err := strconv.ParseFloat(v.Text, FloatSize)
if err != nil {
return &FloatValue{}, nil
}
return &FloatValue{num}, nil
default:
return nil, errors.New(fmt.Sprintf("%s cannot become an integer (undefined)", v))
}
},
nil,
true,
},
"typeof": &BuiltinFunctionValue{
Name: "typeof",
Signature: &FunctionSignature{
In: []TypeSignature{&AnySignature{}},
Out: &StringSignature{},
},
F: func(vm *VM, this Value, args []Value) (Value, error) {
v := args[0]
sig := SignatureOf(v)
return GoToValue(sig.String()), nil
},
Constant: true,
},
"exit": &BuiltinFunctionValue{
"exit",
&FunctionSignature{
[]TypeSignature{&FloatSignature{}},
&NilSignature{},
},
func(vm *VM, this Value, args []Value) (Value, error) {
os.Exit(int(args[0].(*FloatValue).Number))
return &NilValue{}, nil
},
nil,
false,
},
"floor": &BuiltinFunctionValue{
"floor",
&FunctionSignature{
[]TypeSignature{&FloatSignature{}},
&FloatSignature{},
},
func(vm *VM, this Value, args []Value) (Value, error) {
return &FloatValue{math.Floor(args[0].(*FloatValue).Number)}, nil
},
nil,
true,
},
"ceil": &BuiltinFunctionValue{
"ceil",
&FunctionSignature{
[]TypeSignature{&FloatSignature{}},
&FloatSignature{},
},
func(vm *VM, this Value, args []Value) (Value, error) {
return &FloatValue{math.Ceil(args[0].(*FloatValue).Number)}, nil
},
nil,
true,
},
"roundd": &BuiltinFunctionValue{
"roundd",
&FunctionSignature{
[]TypeSignature{&FloatSignature{}, &IntegerSignature{}},
&FloatSignature{},
},
func(vm *VM, this Value, args []Value) (Value, error) {
x := args[0].(*FloatValue).Number
decimals, _ := args[1].(*IntegerValue).Number.Float64()
multiplier := math.Pow(10, decimals)
return &FloatValue{math.Round(x*multiplier) / multiplier}, nil
},
nil,
true,
},
}
func NewVM(chunk *Chunk, stackSize Pos, callstackSize Pos) *VM {
vm := &VM{
chunk: chunk,
Stack: NewStack[Value](stackSize),
call: NewStack[Call](callstackSize),
globals: DefaultGlobals,
scope: &Scope{
current: map[string]Value{},
},
}
return vm
}
// Next execute instruction
// returns true if more instructions should be executed
func (vm *VM) Next() bool {
if !vm.HasNext() {
return false
}
switch vm.NextByte() {
case InstructionReturn:
if vm.call.Current == 0 {
return false
}
v := vm.Stack.Pop()
c := vm.call.Pop()
// reset stack current and variable end and scope
vm.scope = c.scope
// reset to calling position
vm.ip = c.ip
vm.chunk = c.chunk
vm.Stack.Push(v)
case InstructionPop:
vm.Stack.Pop()
case InstructionConstant:
c := vm.ReadConstant()
if c, ok := c.(*FunctionValue); ok {
c.Scope = vm.scope
}
vm.Stack.Push(c)
case InstructionAddFloat:
r := vm.Stack.Pop().(*FloatValue).Number
l := vm.Stack.Pop().(*FloatValue).Number
vm.Stack.Push(&FloatValue{l + r})
case InstructionSubFloat:
r := vm.Stack.Pop().(*FloatValue).Number
l := vm.Stack.Pop().(*FloatValue).Number
vm.Stack.Push(&FloatValue{l - r})
case InstructionMulFloat:
r := vm.Stack.Pop().(*FloatValue).Number
l := vm.Stack.Pop().(*FloatValue).Number
vm.Stack.Push(&FloatValue{l * r})
case InstructionDivFloat:
r := vm.Stack.Pop().(*FloatValue).Number
l := vm.Stack.Pop().(*FloatValue).Number
vm.Stack.Push(&FloatValue{l / r})
case InstructionNegateFloat:
v := vm.Stack.Pop().(*FloatValue).Number
vm.Stack.Push(&FloatValue{-v})
case InstructionAddInt:
r := vm.Stack.Pop().(*IntegerValue).Number
l := vm.Stack.Pop().(*IntegerValue).Number
vm.Stack.Push(&IntegerValue{new(big.Int).Add(l, r)})
case InstructionSubInt:
r := vm.Stack.Pop().(*IntegerValue).Number
l := vm.Stack.Pop().(*IntegerValue).Number
vm.Stack.Push(&IntegerValue{new(big.Int).Sub(l, r)})
case InstructionMulInt:
r := vm.Stack.Pop().(*IntegerValue).Number
l := vm.Stack.Pop().(*IntegerValue).Number
vm.Stack.Push(&IntegerValue{new(big.Int).Mul(l, r)})
case InstructionDivInt:
r := vm.Stack.Pop().(*IntegerValue).Number
l := vm.Stack.Pop().(*IntegerValue).Number
vm.Stack.Push(&IntegerValue{new(big.Int).Div(l, r)})
case InstructionModInt:
r := vm.Stack.Pop().(*IntegerValue).Number
l := vm.Stack.Pop().(*IntegerValue).Number
vm.Stack.Push(&IntegerValue{new(big.Int).Mod(l, r)})
case InstructionNegateInt:
v := vm.Stack.Pop().(*IntegerValue).Number
vm.Stack.Push(&IntegerValue{new(big.Int).Neg(v)})
case InstructionEquals:
vm.Stack.Push(
&BoolValue{vm.Stack.Pop().Equals(vm.Stack.Pop())},
)
case InstructionNotEqual:
vm.Stack.Push(
&BoolValue{!vm.Stack.Pop().Equals(vm.Stack.Pop())},
)
case InstructionNot:
b := vm.Stack.Pop().(*BoolValue).Boolean
vm.Stack.Push(&BoolValue{!b})
case InstructionAnd:
r := vm.Stack.Pop().(*BoolValue).Boolean
l := vm.Stack.Pop().(*BoolValue).Boolean
vm.Stack.Push(&BoolValue{l && r})
case InstructionOr:
r := vm.Stack.Pop().(*BoolValue).Boolean
l := vm.Stack.Pop().(*BoolValue).Boolean
vm.Stack.Push(&BoolValue{l || r})
case InstructionLessFloat:
r := vm.Stack.Pop().(*FloatValue).Number
l := vm.Stack.Pop().(*FloatValue).Number
vm.Stack.Push(&BoolValue{l < r})
case InstructionLessOrEqualFloat:
r := vm.Stack.Pop().(*FloatValue).Number
l := vm.Stack.Pop().(*FloatValue).Number
vm.Stack.Push(&BoolValue{l <= r})
case InstructionGreaterFloat:
r := vm.Stack.Pop().(*FloatValue).Number
l := vm.Stack.Pop().(*FloatValue).Number
vm.Stack.Push(&BoolValue{l > r})
case InstructionGreaterOrEqualFloat:
r := vm.Stack.Pop().(*FloatValue).Number
l := vm.Stack.Pop().(*FloatValue).Number
vm.Stack.Push(&BoolValue{l >= r})
case InstructionLessInt:
r := vm.Stack.Pop().(*IntegerValue).Number
l := vm.Stack.Pop().(*IntegerValue).Number
vm.Stack.Push(&BoolValue{l.Cmp(r) == -1})
case InstructionLessOrEqualInt:
r := vm.Stack.Pop().(*IntegerValue).Number
l := vm.Stack.Pop().(*IntegerValue).Number
vm.Stack.Push(&BoolValue{l.Cmp(r) != 1})
case InstructionGreaterInt:
r := vm.Stack.Pop().(*IntegerValue).Number
l := vm.Stack.Pop().(*IntegerValue).Number
vm.Stack.Push(&BoolValue{l.Cmp(r) == 1})
case InstructionGreaterOrEqualInt:
r := vm.Stack.Pop().(*IntegerValue).Number
l := vm.Stack.Pop().(*IntegerValue).Number
vm.Stack.Push(&BoolValue{l.Cmp(r) != -1})
case InstructionCall:
v := vm.Stack.Pop()
switch f := v.(type) {
case *FunctionValue:
vm.call.Push(Call{
chunk: vm.chunk,
ip: vm.ip,
scope: vm.scope,
})
vm.scope = f.Scope
vm.descend()
for i := len(f.Params) - 1; i >= 0; i-- {
vm.addVar(f.Params[i].Name, vm.Stack.Pop())
}
if f.Parent != nil {
vm.addVar("this", f.Parent)
}
vm.chunk = f.Chunk
vm.ip = 0
case *BuiltinFunctionValue:
args := make([]Value, len(f.Signature.In))
for i := len(f.Signature.In) - 1; i >= 0; i-- {
args[i] = vm.Stack.Pop()
}
v, err := f.F(vm, f.Parent, args)
if err != nil {
vm.error(err.Error())
}
if v == nil {
v = &NilValue{}
}
vm.Stack.Push(v)
default:
vm.error(fmt.Sprintf("%s (%s) is not callable ", v.DebugString(), v.Type()))
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).Boolean {
vm.ip += Pos(n)
}
case InstructionGetLocal:
name := vm.GetConstant(vm.NextByte()).(*StringValue).Text
v := vm.getVar(name)
if v == nil {
vm.error(fmt.Sprintf("cannot get local: undefined variable %s", name))
return false
}
vm.Stack.Push(v)
case InstructionSetLocal:
value := vm.Stack.Peek().Clone()
name := vm.GetConstant(vm.NextByte()).(*StringValue).Text
vm.setVar(name, value)
case InstructionDeclareLocal:
vm.addVar(
vm.GetConstant(vm.NextByte()).(*StringValue).Text,
vm.Stack.Peek().Clone(),
)
case InstructionGetGlobal:
vm.Stack.Push(vm.globals[vm.GetConstant(vm.NextByte()).(*StringValue).Text])
case InstructionSetGlobal:
vm.globals[vm.GetConstant(vm.NextByte()).(*StringValue).Text] = vm.Stack.Pop()
case InstructionTrue:
vm.Stack.Push(&BoolValue{true})
case InstructionFalse:
vm.Stack.Push(&BoolValue{false})
case InstructionNil:
vm.Stack.Push(&NilValue{})
case InstructionFormList:
n := int(vm.NextU16())
items := make([]Value, n)
for i := n - 1; i >= 0; i-- {
items[i] = vm.Stack.Pop()
}
vm.Stack.Push(&ListValue{
items,
})
case InstructionAppend:
value := vm.Stack.Pop()
list := vm.Stack.Pop().(*ListValue)
list.Items = append(list.Items, value)
vm.Stack.Push(list)
case InstructionConcatLists:
r := vm.Stack.Pop().(*ListValue)
l := vm.Stack.Pop().(*ListValue)
vm.Stack.Push(&ListValue{
append(l.Items, r.Items...),
})
case InstructionFormTuple:
n := int(vm.NextU16())
items := make([]Value, n)
for i := n - 1; i >= 0; i-- {
items[i] = vm.Stack.Pop()
}
vm.Stack.Push(&TupleValue{
items,
})
case InstructionDestructureTuple:
t := vm.Stack.Pop().(*TupleValue)
vm.Stack.Push(t.Items...)
case InstructionDescend:
vm.descend()
case InstructionAscend:
vm.ascend()
case InstructionStringConversion:
v := vm.Stack.Pop()
vm.Stack.Push(&StringValue{v.String()})
case InstructionConcatStrings:
r := vm.Stack.Pop().(*StringValue).Text
l := vm.Stack.Pop().(*StringValue).Text
vm.Stack.Push(&StringValue{l + r})
case InstructionSwap:
r := vm.Stack.Pop()
l := vm.Stack.Pop()
vm.Stack.Push(r, l)
case InstructionDuplicate:
vm.Stack.Push(vm.Stack.Peek().Clone())
case InstructionAccessProperty:
source := vm.Stack.Pop()
property := vm.ReadConstant()
member, err := source.Get(property.(*StringValue).String())
if err != nil {
vm.error(err.Error())
}
// add parent if function
if member.Type() == FunctionValueType {
member.(*FunctionValue).Parent = source
} else if member.Type() == BuiltinFunctionValueType {
member.(*BuiltinFunctionValue).Parent = source
}
vm.Stack.Push(member)
case InstructionIndexList:
i := vm.Stack.Pop().(*IntegerValue)
l := vm.Stack.Pop().(*ListValue)
n := int(i.Number.Int64())
if n < 0 || len(l.Items) <= n {
vm.error(fmt.Sprintf("index %d out of bounds", n))
}
vm.Stack.Push(l.Items[n].Clone())
case InstructionIndexTuple:
i := vm.Stack.Pop().(*IntegerValue)
t := vm.Stack.Pop().(*TupleValue)
n := int(i.Number.Int64())
if n < 0 || len(t.Items) <= n {
vm.error(fmt.Sprintf("index %d out of bounds", n))
}
vm.Stack.Push(t.Items[n].Clone())
case InstructionIndexString:
i := vm.Stack.Pop().(*IntegerValue)
s := vm.Stack.Pop().(*StringValue)
n := int(i.Number.Int64())
if n < 0 || len(s.Text) <= n {
vm.error(fmt.Sprintf("index %d out of bounds", n))
}
vm.Stack.Push(&StringValue{string(s.Text[n])})
case InstructionBreakpoint:
/*
// I'm keeping this
s := vm.scope
log.Printf("breakpoint %d", vm.ip)
for s != nil {
log.Printf("%s", s.current)
s = s.parent
}
*/
vm.Stack.Push(&NilValue{})
default:
panic("invalid byte code")
}
return true
}
func (vm *VM) Call(v Value, args []Value) (Value, error) {
switch f := v.(type) {
case *FunctionValue:
vm.call.Push(Call{
chunk: vm.chunk,
ip: vm.ip,
scope: vm.scope,
})
vm.scope = f.Scope
vm.descend()
for i := 0; i < len(f.Params); i++ {
vm.addVar(f.Params[i].Name, args[i])
}
if f.Parent != nil {
vm.addVar("this", f.Parent)
}
vm.chunk = f.Chunk
vm.ip = 0
for vm.chunk.Bytecode[vm.ip] != InstructionReturn && vm.Next() {
}
vm.Next()
return vm.Stack.Pop(), nil
case *BuiltinFunctionValue:
return f.F(vm, f.Parent, args)
}
return nil, errors.New(fmt.Sprintf("value is not a function (%s)", v.DebugString()))
}
func (vm *VM) SetChunk(c *Chunk) {
vm.chunk = c
}
func (vm *VM) TryNextByte() (Bytecode, error) {
if !vm.HasNext() {
return 0, errors.New("there are no more instructions")
}
for int(vm.ip) >= len(vm.chunk.Bytecode) && vm.call.Current > 0 {
c := vm.call.Pop()
vm.ip = c.ip
vm.chunk = c.chunk
vm.scope = c.scope
}
if int(vm.ip) >= len(vm.chunk.Bytecode) {
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() {
if vm.scope.parent == nil {
panic("invalid scope")
}
vm.scope = vm.scope.parent
}
func (vm *VM) descend() {
old := vm.scope
vm.scope = &Scope{
map[string]Value{},
old,
}
}
func (vm *VM) addVar(name string, value Value) {
vm.scope.current[name] = value
}
func (vm *VM) getVar(name string) Value {
s := vm.scope
for s != nil {
if v, ok := s.current[name]; ok {
return v
}
s = s.parent
}
return nil
}
func (vm *VM) setVar(name string, v Value) {
s := vm.scope
for s != nil {
if _, ok := s.current[name]; ok {
s.current[name] = v
break
}
s = s.parent
}
}
func (vm *VM) HasNext() bool {
return vm.ip < Pos(len(vm.chunk.Bytecode)) || vm.call.Current > 0
}
func (vm *VM) GetConstant(id Bytecode) Value {
return vm.chunk.Constants[id].Clone()
}
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]
}