1308 lines
30 KiB
Go
1308 lines
30 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
|
|
|
|
// InstructionNewRecord Create a new empty record.
|
|
InstructionNewRecord
|
|
// InstructionSetRecordItem Set the value of an item in the record, and create it if it does not already exist.
|
|
// [..., record, item]; the following byte should be the index of a string constant with the name of the property.
|
|
InstructionSetRecordItem
|
|
|
|
// 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"
|
|
case InstructionModInt:
|
|
return "MOD_INT"
|
|
case InstructionNewRecord:
|
|
return "NEW_RECORD"
|
|
case InstructionSetRecordItem:
|
|
return "SET_PROPERTY"
|
|
case InstructionIndexString:
|
|
return "INDEX_STRING"
|
|
}
|
|
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 InstructionSetRecordItem:
|
|
i := vm.Stack.Pop()
|
|
prop := vm.ReadConstant().(*StringValue)
|
|
r := vm.Stack.Peek().(*RecordValue)
|
|
|
|
r.Entries[prop.Text] = i
|
|
|
|
case InstructionNewRecord:
|
|
vm.Stack.Push(&RecordValue{
|
|
map[string]Value{},
|
|
})
|
|
|
|
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]
|
|
}
|