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 only the character // at the indexed position InstructionIndexString // InstructionSetIndexList set the item at a given index in a list. // [..., item, container, index] -> [..., item] InstructionSetIndexList // 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.Copy(), 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().Copy() name := vm.GetConstant(vm.NextByte()).(*StringValue).Text vm.setVar(name, value) case InstructionDeclareLocal: vm.addVar( vm.GetConstant(vm.NextByte()).(*StringValue).Text, vm.Stack.Peek().Copy(), ) 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().Copy()) 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].Copy()) 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].Copy()) 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 InstructionSetIndexList: n := vm.Stack.Pop().(*IntegerValue) l := vm.Stack.Pop().(*ListValue) i := n.Number.Int64() if i < 0 || int64(len(l.Items)) <= i { vm.error(fmt.Sprintf("index %d out of bounds", i)) } l.Items[i] = vm.Stack.Peek().Copy() 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].Copy() } 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] }