diff --git a/internal/object/array.go b/internal/object/array.go index 20ca28f..aced0e5 100644 --- a/internal/object/array.go +++ b/internal/object/array.go @@ -2,6 +2,7 @@ package object import ( "bytes" + "fmt" "strings" ) @@ -18,30 +19,30 @@ func (ao *Array) Bool() bool { return len(ao.Elements) > 0 } -func (a *Array) PopLeft() Object { - if len(a.Elements) > 0 { - e := a.Elements[0] - a.Elements = a.Elements[1:] +func (ao *Array) PopLeft() Object { + if len(ao.Elements) > 0 { + e := ao.Elements[0] + ao.Elements = ao.Elements[1:] return e } return &Null{} } -func (a *Array) PopRight() Object { - if len(a.Elements) > 0 { - e := a.Elements[(len(a.Elements) - 1)] - a.Elements = a.Elements[:(len(a.Elements) - 1)] +func (ao *Array) PopRight() Object { + if len(ao.Elements) > 0 { + e := ao.Elements[(len(ao.Elements) - 1)] + ao.Elements = ao.Elements[:(len(ao.Elements) - 1)] return e } return &Null{} } -func (a *Array) Prepend(obj Object) { - a.Elements = append([]Object{obj}, a.Elements...) +func (ao *Array) Prepend(obj Object) { + ao.Elements = append([]Object{obj}, ao.Elements...) } -func (a *Array) Append(obj Object) { - a.Elements = append(a.Elements, obj) +func (ao *Array) Append(obj Object) { + ao.Elements = append(ao.Elements, obj) } func (ao *Array) Inspect() string { @@ -69,6 +70,63 @@ func (ao *Array) Less(i, j int) bool { return false } +func (ao *Array) Add(other Object) (Object, error) { + if other.Type() != ao.Type() { + return nil, NewBinaryOpError(ao, other, "+") + } + + var elements []Object + elements = append(elements, ao.Elements...) + elements = append(elements, other.(*Array).Elements...) + + return &Array{Elements: elements}, nil + +} + +func (ao *Array) Mul(other Object) (Object, error) { + if other.Type() != IntegerType { + return nil, NewBinaryOpError(ao, other, "*") + } + + var elements []Object + N := int(other.(*Integer).Value) + for i := 0; i < N; i++ { + elements = append(elements, ao.Elements...) + } + + return &Array{Elements: elements}, nil +} + +func (ao *Array) Get(index Object) (Object, error) { + if !AssertTypes(index, IntegerType) { + return nil, fmt.Errorf("invalid type for array index, expected Integer got %s", index.Type()) + } + + i := index.(*Integer).Value + N := int64(len(ao.Elements)) + if i < 0 || i >= N { + return &Null{}, nil + } + + return ao.Elements[i], nil +} + +func (ao *Array) Set(index, other Object) error { + if !AssertTypes(index, IntegerType) { + return fmt.Errorf("invalid type for array index, expected Integer got %s", index.Type()) + } + + i := index.(*Integer).Value + N := int64(len(ao.Elements)) + if i < 0 || i >= N { + return fmt.Errorf("index out of bounds %d with array length %d", i, N) + } + + ao.Elements[i] = other + + return nil +} + func (ao *Array) Compare(other Object) int { if obj, ok := other.(*Array); ok { if len(ao.Elements) != len(obj.Elements) { diff --git a/internal/object/bool.go b/internal/object/bool.go index 968a33f..dac75a9 100644 --- a/internal/object/bool.go +++ b/internal/object/bool.go @@ -35,6 +35,24 @@ func (b *Boolean) Int() int { return 0 } +func (b *Boolean) LogicalAnd(other Object) (Object, error) { + if !AssertTypes(other, BooleanType, IntegerType) { + return nil, NewBinaryOpError(b, other, "&&") + } + return &Boolean{b.Value && other.Bool()}, nil +} + +func (b *Boolean) LogicalOr(other Object) (Object, error) { + if !AssertTypes(other, BooleanType, IntegerType) { + return nil, NewBinaryOpError(b, other, "||") + } + return &Boolean{b.Value || other.Bool()}, nil +} + +func (b *Boolean) LogicalNot() Object { + return &Boolean{!b.Value} +} + func (b *Boolean) Compare(other Object) int { if obj, ok := other.(*Boolean); ok { return b.Int() - obj.Int() diff --git a/internal/object/error.go b/internal/object/error.go index 4f0e632..1764530 100644 --- a/internal/object/error.go +++ b/internal/object/error.go @@ -1,5 +1,7 @@ package object +// error encountered. This object is tracked through the evaluator and when +// encountered stops evaluation of the program or body of a function. type Error struct { Message string } diff --git a/internal/object/errors.go b/internal/object/errors.go new file mode 100644 index 0000000..138181f --- /dev/null +++ b/internal/object/errors.go @@ -0,0 +1,20 @@ +package object + +import ( + "fmt" +) + +// BinaryOpError is an binary operation error usually resulting from mismatched types +type BinaryOpError struct { + left, right Object + op string +} + +func (e BinaryOpError) Error() string { + return fmt.Sprintf("unsupported types for binary operation: %s %s %s", e.left.Type(), e.op, e.right.Type()) +} + +// NewBinaryOpError returns a new BinaryOpError +func NewBinaryOpError(left, right Object, op string) BinaryOpError { + return BinaryOpError{left, right, op} +} diff --git a/internal/object/hash.go b/internal/object/hash.go index 5d5b462..8966587 100644 --- a/internal/object/hash.go +++ b/internal/object/hash.go @@ -75,6 +75,46 @@ func (h *Hash) String() string { return h.Inspect() } +func (h *Hash) Add(other Object) (Object, error) { + if other.Type() != h.Type() { + return nil, NewBinaryOpError(h, other, "+") + } + + pairs := make(map[HashKey]HashPair) + for k, v := range h.Pairs { + pairs[k] = v + } + for k, v := range other.(*Hash).Pairs { + pairs[k] = v + } + return &Hash{Pairs: pairs}, nil +} + +func (h *Hash) Get(index Object) (Object, error) { + key, ok := index.(Hashable) + if !ok { + return nil, fmt.Errorf("invalid hash key %s", index.Type()) + } + + pair, found := h.Pairs[key.HashKey()] + if !found { + return &Null{}, nil + } + + return pair.Value, nil +} + +func (h *Hash) Set(index, other Object) error { + key, ok := index.(Hashable) + if !ok { + return fmt.Errorf("invalid hash key %s", index.Type()) + } + + h.Pairs[key.HashKey()] = HashPair{Key: index, Value: other} + + return nil +} + func (h *Hash) Compare(other Object) int { if obj, ok := other.(*Hash); ok { if len(h.Pairs) != len(obj.Pairs) { diff --git a/internal/object/int.go b/internal/object/int.go index dcb7cd9..34e9df5 100644 --- a/internal/object/int.go +++ b/internal/object/int.go @@ -26,6 +26,94 @@ func (i *Integer) String() string { return i.Inspect() } +func (i *Integer) Add(other Object) (Object, error) { + if !AssertTypes(other, IntegerType) { + return nil, NewBinaryOpError(i, other, "+") + } + return &Integer{i.Value + other.(*Integer).Value}, nil +} + +func (i *Integer) Sub(other Object) (Object, error) { + if !AssertTypes(other, IntegerType) { + return nil, NewBinaryOpError(i, other, "-") + } + return &Integer{i.Value - other.(*Integer).Value}, nil +} + +func (i *Integer) Mul(other Object) (Object, error) { + switch other.Type() { + case IntegerType: + return &Integer{i.Value * other.(*Integer).Value}, nil + case StringType: + return other.(Mul).Mul(i) + case ArrayType: + return other.(Mul).Mul(i) + default: + return nil, NewBinaryOpError(i, other, "*") + } +} + +func (i *Integer) Div(other Object) (Object, error) { + if !AssertTypes(other, IntegerType) { + return nil, NewBinaryOpError(i, other, "/") + } + return &Integer{i.Value / other.(*Integer).Value}, nil +} + +func (i *Integer) Mod(other Object) (Object, error) { + if !AssertTypes(other, IntegerType) { + return nil, NewBinaryOpError(i, other, "%") + } + return &Integer{i.Value % other.(*Integer).Value}, nil +} + +func (i *Integer) BitwiseOr(other Object) (Object, error) { + if !AssertTypes(other, IntegerType) { + return nil, NewBinaryOpError(i, other, "|") + } + return &Integer{i.Value | other.(*Integer).Value}, nil +} + +func (i *Integer) BitwiseXor(other Object) (Object, error) { + if !AssertTypes(other, IntegerType) { + return nil, NewBinaryOpError(i, other, "^") + } + return &Integer{i.Value ^ other.(*Integer).Value}, nil +} + +func (i *Integer) BitwiseAnd(other Object) (Object, error) { + if !AssertTypes(other, IntegerType) { + return nil, NewBinaryOpError(i, other, "&") + } + return &Integer{i.Value & other.(*Integer).Value}, nil +} + +func (i *Integer) BitwiseNot() Object { + return &Integer{^i.Value} +} + +func (i *Integer) LeftShift(other Object) (Object, error) { + if !AssertTypes(other, IntegerType) { + return nil, NewBinaryOpError(i, other, "<<") + } + return &Integer{i.Value << other.(*Integer).Value}, nil +} + +func (i *Integer) RightShift(other Object) (Object, error) { + if !AssertTypes(other, IntegerType) { + return nil, NewBinaryOpError(i, other, ">>") + } + return &Integer{i.Value >> other.(*Integer).Value}, nil +} + +func (i *Integer) LogicalNot() Object { + return &Boolean{!i.Bool()} +} + +func (i *Integer) Negate() Object { + return &Integer{-i.Value} +} + func (i *Integer) Compare(other Object) int { if obj, ok := other.(*Integer); ok { switch { diff --git a/internal/object/module.go b/internal/object/module.go index bffa438..595f87d 100644 --- a/internal/object/module.go +++ b/internal/object/module.go @@ -24,6 +24,20 @@ func (m Module) Inspect() string { return fmt.Sprintf("", m.Name) } +func (m *Module) Get(index Object) (Object, error) { + key, ok := index.(Hashable) + if !ok { + return nil, fmt.Errorf("invalid module attribute %s", index.Type()) + } + + attr, found := m.Attrs.(*Hash).Pairs[key.HashKey()] + if !found { + return &Null{}, nil + } + + return attr.Value, nil +} + func (m Module) Compare(other Object) int { return 1 } diff --git a/internal/object/null.go b/internal/object/null.go index 653aa56..f0b59cd 100644 --- a/internal/object/null.go +++ b/internal/object/null.go @@ -18,6 +18,10 @@ func (n *Null) String() string { return n.Inspect() } +func (n *Null) LogicalNot() Object { + return &Boolean{!n.Bool()} +} + func (n *Null) Compare(other Object) int { if _, ok := other.(*Null); ok { return 0 diff --git a/internal/object/object.go b/internal/object/object.go index 7b52b1f..3208106 100644 --- a/internal/object/object.go +++ b/internal/object/object.go @@ -54,6 +54,91 @@ func (t Type) String() string { } } +// Add is the interface for binary addition +type Add interface { + Add(other Object) (Object, error) +} + +// Sub is the interface for binary subtraction +type Sub interface { + Sub(other Object) (Object, error) +} + +// Mul is the interface for binary multiplication +type Mul interface { + Mul(other Object) (Object, error) +} + +// Div is the interface for binary division +type Div interface { + Div(other Object) (Object, error) +} + +// Mod is the interface for binary modulo +type Mod interface { + Mod(other Object) (Object, error) +} + +// LogicalOr is the interface for logical or +type LogicalOr interface { + LogicalOr(other Object) (Object, error) +} + +// LogicalAnd is the interface for logical and +type LogicalAnd interface { + LogicalAnd(other Object) (Object, error) +} + +// BitwiseOr is the interface for bitwise or +type BitwiseOr interface { + BitwiseOr(other Object) (Object, error) +} + +// BitwiseAnd is the interface for bitwise and +type BitwiseAnd interface { + BitwiseAnd(other Object) (Object, error) +} + +// BitwiseXor is the interface for bitwise xor +type BitwiseXor interface { + BitwiseXor(other Object) (Object, error) +} + +// BitwiseNot is the interface for bitwise not +type BitwiseNot interface { + BitwiseNot() Object +} + +// LeftShift is the interface for bitwise left shift +type LeftShift interface { + LeftShift(other Object) (Object, error) +} + +// RightShift is the interface for bitwise right shift +type RightShift interface { + RightShift(other Object) (Object, error) +} + +// LogicalNot is the interface for logical not +type LogicalNot interface { + LogicalNot() Object +} + +// Negate is the interface for unary negation +type Negate interface { + Negate() Object +} + +// Setter is the interface for assigning a value to an index +type Setter interface { + Set(index, value Object) error +} + +// Getter is the interface for getting a value from an index +type Getter interface { + Get(index Object) (Object, error) +} + // Comparable is the interface for comparing two Object and their underlying // values. It is the responsibility of the caller (left) to check for types. // Returns `true` iif the types and values are identical, `false` otherwise. @@ -91,3 +176,12 @@ type Hashable interface { // BuiltinFunction represents the builtin function type type BuiltinFunction func(args ...Object) Object + +func AssertTypes(obj Object, types ...Type) bool { + for _, t := range types { + if t == obj.Type() { + return true + } + } + return false +} diff --git a/internal/object/str.go b/internal/object/str.go index 796af31..fab48e1 100644 --- a/internal/object/str.go +++ b/internal/object/str.go @@ -2,6 +2,7 @@ package object import ( "fmt" + "strings" "unicode/utf8" ) @@ -33,6 +34,33 @@ func (s *String) String() string { return s.Value } +func (s *String) Add(other Object) (Object, error) { + if !AssertTypes(other, StringType) { + return nil, NewBinaryOpError(s, other, "+") + } + return &String{s.Value + other.(*String).Value}, nil +} + +func (s *String) Mul(other Object) (Object, error) { + if !AssertTypes(other, IntegerType) { + return nil, NewBinaryOpError(s, other, "*") + } + return &String{strings.Repeat(s.Value, int(other.(*Integer).Value))}, nil +} + +func (s *String) Get(index Object) (Object, error) { + if !AssertTypes(index, IntegerType) { + return nil, fmt.Errorf("invalid type for string index, expected Integer got %s", index.Type()) + } + + i := int(index.(*Integer).Value) + if i < 0 || i >= len(s.Value) { + return &String{}, nil + } + + return &String{string(s.Value[i])}, nil +} + func (s *String) Compare(other Object) int { if obj, ok := other.(*String); ok { switch { diff --git a/internal/vm/vm.go b/internal/vm/vm.go index 3218347..32d735e 100644 --- a/internal/vm/vm.go +++ b/internal/vm/vm.go @@ -25,8 +25,29 @@ var Null = &object.Null{} var True = &object.Boolean{Value: true} var False = &object.Boolean{Value: false} -// ExecModule compiles the named module and returns a *object.Module object -func ExecModule(name string, state *VMState) (object.Object, error) { +func isTruthy(obj object.Object) bool { + switch obj := obj.(type) { + + case *object.Boolean: + return obj.Value + + case *object.Null: + return false + + default: + return true + } +} + +func nativeBoolToBooleanObject(input bool) *object.Boolean { + if input { + return True + } + return False +} + +// executeModule compiles the named module and returns a *object.Module object +func executeModule(name string, state *VMState) (object.Object, error) { filename := utils.FindModule(name) if filename == "" { return nil, fmt.Errorf("ImportError: no module named '%s'", name) @@ -115,6 +136,18 @@ type VM struct { fp int // Always points to the current frame. Current frame is frames[fp-1] } +func (vm *VM) pushFrame(f *Frame) { + vm.frame = f + vm.frames[vm.fp] = f + vm.fp++ +} + +func (vm *VM) popFrame() *Frame { + vm.fp-- + vm.frame = vm.frames[vm.fp-1] + return vm.frames[vm.fp] +} + // New constructs a new monkey-lang bytecode virtual machine func New(fn string, bytecode *compiler.Bytecode) *VM { mainFn := &object.CompiledFunction{Instructions: bytecode.Instructions} @@ -167,419 +200,26 @@ func NewWithState(fn string, bytecode *compiler.Bytecode, state *VMState) *VM { return vm } -func (vm *VM) pushFrame(f *Frame) { - vm.frame = f - vm.frames[vm.fp] = f - vm.fp++ -} - -func (vm *VM) popFrame() *Frame { - vm.fp-- - vm.frame = vm.frames[vm.fp-1] - return vm.frames[vm.fp] -} - -func (vm *VM) loadModule(name object.Object) error { - s, ok := name.(*object.String) - if !ok { - return fmt.Errorf( - "TypeError: import() expected argument #1 to be `str` got `%s`", - name.Type(), - ) +func (vm *VM) push(o object.Object) error { + if vm.sp >= StackSize { + return fmt.Errorf("stack overflow") } - attrs, err := ExecModule(s.Value, vm.state) - if err != nil { - return err - } - - module := &object.Module{Name: s.Value, Attrs: attrs} - return vm.push(module) -} - -func (vm *VM) LastPoppedStackElem() object.Object { - return vm.stack[vm.sp] -} - -func (vm *VM) Run() error { - var n int - var ip int - var ins code.Instructions - var op code.Opcode - - if vm.Debug { - start := time.Now() - defer func() { - log.Printf("%d instructions executed in %s", n, time.Now().Sub(start)) - }() - } - - for vm.frame.ip < len(vm.frame.Instructions())-1 { - vm.frame.ip++ - - ip = vm.frame.ip - ins = vm.frame.Instructions() - op = code.Opcode(ins[ip]) - - switch op { - case code.OpConstant: - constIndex := code.ReadUint16(ins[ip+1:]) - vm.frame.ip += 2 - - err := vm.push(vm.state.Constants[constIndex]) - if err != nil { - return err - } - - case code.OpAdd, code.OpSub, code.OpMul, code.OpDiv, code.OpMod, code.OpOr, - code.OpAnd, code.OpBitwiseOR, code.OpBitwiseXOR, code.OpBitwiseAND, - code.OpLeftShift, code.OpRightShift: - err := vm.executeBinaryOperation(op) - if err != nil { - return err - } - - case code.OpPop: - vm.pop() - - case code.OpTrue: - err := vm.push(True) - if err != nil { - return err - } - - case code.OpFalse: - err := vm.push(False) - if err != nil { - return err - } - - case code.OpEqual, code.OpNotEqual, code.OpGreaterThan, code.OpGreaterThanEqual: - err := vm.executeComparison(op) - if err != nil { - return err - } - - case code.OpNot: - err := vm.executeNotOperator() - if err != nil { - return err - } - - case code.OpBitwiseNOT: - err := vm.executeBitwiseNotOperator() - if err != nil { - return err - } - - case code.OpMinus: - err := vm.executeMinusOperator() - if err != nil { - return err - } - - case code.OpJump: - pos := int(code.ReadUint16(ins[ip+1:])) - vm.frame.ip = pos - 1 - - case code.OpJumpNotTruthy: - pos := int(code.ReadUint16(ins[ip+1:])) - vm.frame.ip += 2 - - condition := vm.pop() - if !isTruthy(condition) { - vm.frame.ip = pos - 1 - } - - case code.OpNull: - err := vm.push(Null) - if err != nil { - return err - } - - case code.OpSetGlobal: - globalIndex := code.ReadUint16(ins[ip+1:]) - vm.frame.ip += 2 - - ref := vm.pop() - if immutable, ok := ref.(object.Immutable); ok { - vm.state.Globals[globalIndex] = immutable.Clone() - } else { - vm.state.Globals[globalIndex] = ref - } - - err := vm.push(Null) - if err != nil { - return err - } - - case code.OpAssignGlobal: - globalIndex := code.ReadUint16(ins[ip+1:]) - vm.frame.ip += 2 - vm.state.Globals[globalIndex] = vm.pop() - - err := vm.push(Null) - if err != nil { - return err - } - - case code.OpAssignLocal: - localIndex := code.ReadUint8(ins[ip+1:]) - vm.frame.ip += 1 - - vm.stack[vm.frame.basePointer+int(localIndex)] = vm.pop() - - err := vm.push(Null) - if err != nil { - return err - } - - case code.OpGetGlobal: - globalIndex := code.ReadUint16(ins[ip+1:]) - vm.frame.ip += 2 - - err := vm.push(vm.state.Globals[globalIndex]) - if err != nil { - return err - } - - case code.OpArray: - numElements := int(code.ReadUint16(ins[ip+1:])) - vm.frame.ip += 2 - - array := vm.buildArray(vm.sp-numElements, vm.sp) - vm.sp = vm.sp - numElements - - err := vm.push(array) - if err != nil { - return err - } - - case code.OpHash: - numElements := int(code.ReadUint16(ins[ip+1:])) - vm.frame.ip += 2 - - hash, err := vm.buildHash(vm.sp-numElements, vm.sp) - if err != nil { - return err - } - vm.sp = vm.sp - numElements - - err = vm.push(hash) - if err != nil { - return err - } - - case code.OpSetItem: - value := vm.pop() - index := vm.pop() - left := vm.pop() - - err := vm.executeSetItem(left, index, value) - if err != nil { - return err - } - - case code.OpGetItem: - index := vm.pop() - left := vm.pop() - - err := vm.executeGetItem(left, index) - if err != nil { - return err - } - - case code.OpCall: - numArgs := code.ReadUint8(ins[ip+1:]) - vm.frame.ip += 1 - - err := vm.executeCall(int(numArgs)) - if err != nil { - return err - } - - case code.OpReturn: - returnValue := vm.pop() - - frame := vm.popFrame() - vm.sp = frame.basePointer - 1 - - err := vm.push(returnValue) - if err != nil { - return err - } - - case code.OpSetLocal: - localIndex := code.ReadUint8(ins[ip+1:]) - vm.frame.ip += 1 - - ref := vm.pop() - if immutable, ok := ref.(object.Immutable); ok { - vm.stack[vm.frame.basePointer+int(localIndex)] = immutable.Clone() - } else { - vm.stack[vm.frame.basePointer+int(localIndex)] = ref - } - - err := vm.push(Null) - if err != nil { - return err - } - - case code.OpGetLocal: - localIndex := code.ReadUint8(ins[ip+1:]) - vm.frame.ip += 1 - - err := vm.push(vm.stack[vm.frame.basePointer+int(localIndex)]) - if err != nil { - return err - } - - case code.OpGetBuiltin: - builtinIndex := code.ReadUint8(ins[ip+1:]) - vm.frame.ip++ - - builtin := builtins.BuiltinsIndex[builtinIndex] - - err := vm.push(builtin) - if err != nil { - return err - } - - case code.OpClosure: - constIndex := code.ReadUint16(ins[ip+1:]) - numFree := code.ReadUint8(ins[ip+3:]) - vm.frame.ip += 3 - - err := vm.pushClosure(int(constIndex), int(numFree)) - if err != nil { - return err - } - - case code.OpGetFree: - freeIndex := code.ReadUint8(ins[ip+1:]) - vm.frame.ip += 1 - - err := vm.push(vm.frame.cl.Free[freeIndex]) - if err != nil { - return err - } - - case code.OpCurrentClosure: - currentClosure := vm.frame.cl - err := vm.push(currentClosure) - if err != nil { - return err - } - - case code.OpLoadModule: - name := vm.pop() - - err := vm.loadModule(name) - if err != nil { - return err - } - - } - - if vm.Debug { - n++ - log.Printf( - "%-25s [ip=%02d fp=%02d, sp=%02d]", - "", ip, vm.fp-1, vm.sp, - ) - } - } + vm.stack[vm.sp] = o + vm.sp++ return nil } -func (vm *VM) executeSetItem(left, index, value object.Object) error { - switch { - case left.Type() == object.ArrayType && index.Type() == object.IntegerType: - return vm.executeArraySetItem(left, index, value) - case left.Type() == object.HashType: - return vm.executeHashSetItem(left, index, value) - default: - return fmt.Errorf( - "set item operation not supported: left=%s index=%s", - left.Type(), index.Type(), - ) - } +func (vm *VM) pop() object.Object { + o := vm.stack[vm.sp-1] + vm.sp-- + return o } -func (vm *VM) executeGetItem(left, index object.Object) error { - switch { - case left.Type() == object.StringType && index.Type() == object.IntegerType: - return vm.executeStringGetItem(left, index) - case left.Type() == object.StringType && index.Type() == object.StringType: - return vm.executeStringIndex(left, index) - case left.Type() == object.ArrayType && index.Type() == object.IntegerType: - return vm.executeArrayGetItem(left, index) - case left.Type() == object.HashType: - return vm.executeHashGetItem(left, index) - case left.Type() == object.ModuleType: - return vm.executeHashGetItem(left.(*object.Module).Attrs, index) - default: - return fmt.Errorf( - "index operator not supported: left=%s index=%s", - left.Type(), index.Type(), - ) - } -} - -func (vm *VM) executeArrayGetItem(array, index object.Object) error { - arrayObject := array.(*object.Array) - i := index.(*object.Integer).Value - max := int64(len(arrayObject.Elements) - 1) - - if i < 0 || i > max { - return vm.push(Null) - } - - return vm.push(arrayObject.Elements[i]) -} - -func (vm *VM) executeArraySetItem(array, index, value object.Object) error { - arrayObject := array.(*object.Array) - i := index.(*object.Integer).Value - max := int64(len(arrayObject.Elements) - 1) - - if i < 0 || i > max { - return fmt.Errorf("index out of bounds: %d", i) - } - - arrayObject.Elements[i] = value - return vm.push(Null) -} - -func (vm *VM) executeHashGetItem(hash, index object.Object) error { - hashObject := hash.(*object.Hash) - - key, ok := index.(object.Hashable) - if !ok { - return fmt.Errorf("unusable as hash key: %s", index.Type()) - } - - pair, ok := hashObject.Pairs[key.HashKey()] - if !ok { - return vm.push(Null) - } - - return vm.push(pair.Value) -} - -func (vm *VM) executeHashSetItem(hash, index, value object.Object) error { - hashObject := hash.(*object.Hash) - - key, ok := index.(object.Hashable) - if !ok { - return fmt.Errorf("unusable as hash key: %s", index.Type()) - } - - hashed := key.HashKey() - hashObject.Pairs[hashed] = object.HashPair{Key: index, Value: value} - - return vm.push(Null) +func (vm *VM) executeLoadModule() error { + name := vm.pop() + return vm.loadModule(name) } func (vm *VM) buildHash(startIndex, endIndex int) (object.Object, error) { @@ -602,246 +242,365 @@ func (vm *VM) buildHash(startIndex, endIndex int) (object.Object, error) { return &object.Hash{Pairs: hashedPairs}, nil } -func (vm *VM) buildArray(startIndex, endIndex int) object.Object { +func (vm *VM) executeMakeHash(startIndex, endIndex int) error { + hash, err := vm.buildHash(startIndex, endIndex) + if err != nil { + return err + } + vm.sp = startIndex + return vm.push(hash) +} + +func (vm *VM) buildArray(startIndex, endIndex int) (object.Object, error) { elements := make([]object.Object, endIndex-startIndex) for i := startIndex; i < endIndex; i++ { elements[i-startIndex] = vm.stack[i] } - return &object.Array{Elements: elements} + return &object.Array{Elements: elements}, nil } -func isTruthy(obj object.Object) bool { - switch obj := obj.(type) { - - case *object.Boolean: - return obj.Value - - case *object.Null: - return false - - default: - return true +func (vm *VM) executeMakeArray(startIndex, endIndex int) error { + hash, err := vm.buildArray(startIndex, endIndex) + if err != nil { + return err } + vm.sp = startIndex + return vm.push(hash) } -func (vm *VM) push(o object.Object) error { - if vm.sp >= StackSize { - return fmt.Errorf("stack overflow") - } - - vm.stack[vm.sp] = o - vm.sp++ - - return nil -} - -func (vm *VM) pop() object.Object { - o := vm.stack[vm.sp-1] - vm.sp-- - return o -} - -func (vm *VM) executeBinaryOperation(op code.Opcode) error { +func (vm *VM) executeAdd() error { right := vm.pop() left := vm.pop() - leftType := left.Type() - rightType := right.Type() - - switch { - - // {"a": 1} + {"b": 2} - case op == code.OpAdd && left.Type() == object.HashType && right.Type() == object.HashType: - leftVal := left.(*object.Hash).Pairs - rightVal := right.(*object.Hash).Pairs - pairs := make(map[object.HashKey]object.HashPair) - for k, v := range leftVal { - pairs[k] = v + if obj, ok := left.(object.Add); ok { + val, err := obj.Add(right) + if err != nil { + return err } - for k, v := range rightVal { - pairs[k] = v - } - return vm.push(&object.Hash{Pairs: pairs}) - - // [1] + [2] - case op == code.OpAdd && left.Type() == object.ArrayType && right.Type() == object.ArrayType: - leftVal := left.(*object.Array).Elements - rightVal := right.(*object.Array).Elements - elements := make([]object.Object, len(leftVal)+len(rightVal)) - elements = append(leftVal, rightVal...) - return vm.push(&object.Array{Elements: elements}) - - // [1] * 3 - case op == code.OpMul && left.Type() == object.ArrayType && right.Type() == object.IntegerType: - leftVal := left.(*object.Array).Elements - rightVal := int(right.(*object.Integer).Value) - elements := leftVal - for i := rightVal; i > 1; i-- { - elements = append(elements, leftVal...) - } - return vm.push(&object.Array{Elements: elements}) - // 3 * [1] - case op == code.OpMul && left.Type() == object.IntegerType && right.Type() == object.ArrayType: - leftVal := int(left.(*object.Integer).Value) - rightVal := right.(*object.Array).Elements - elements := rightVal - for i := leftVal; i > 1; i-- { - elements = append(elements, rightVal...) - } - return vm.push(&object.Array{Elements: elements}) - - // " " * 4 - case op == code.OpMul && left.Type() == object.StringType && right.Type() == object.IntegerType: - leftVal := left.(*object.String).Value - rightVal := right.(*object.Integer).Value - return vm.push(&object.String{Value: strings.Repeat(leftVal, int(rightVal))}) - // 4 * " " - case op == code.OpMul && left.Type() == object.IntegerType && right.Type() == object.StringType: - leftVal := left.(*object.Integer).Value - rightVal := right.(*object.String).Value - return vm.push(&object.String{Value: strings.Repeat(rightVal, int(leftVal))}) - - case leftType == object.BooleanType && rightType == object.BooleanType: - return vm.executeBinaryBooleanOperation(op, left, right) - case leftType == object.IntegerType && rightType == object.IntegerType: - return vm.executeBinaryIntegerOperation(op, left, right) - case leftType == object.StringType && rightType == object.StringType: - return vm.executeBinaryStringOperation(op, left, right) - default: - return fmt.Errorf("unsupported types for binary operation: %s %s", leftType, rightType) - } -} - -func (vm *VM) executeBinaryBooleanOperation(op code.Opcode, left, right object.Object) error { - leftValue := left.(*object.Boolean).Value - rightValue := right.(*object.Boolean).Value - - var result bool - - switch op { - case code.OpOr: - result = leftValue || rightValue - case code.OpAnd: - result = leftValue && rightValue - default: - return fmt.Errorf("unknown boolean operator: %d", op) + return vm.push(val) } - return vm.push(&object.Boolean{Value: result}) + return object.NewBinaryOpError(left, right, "+") } -func (vm *VM) executeBinaryIntegerOperation(op code.Opcode, left, right object.Object) error { - leftValue := left.(*object.Integer).Value - rightValue := right.(*object.Integer).Value - - var result int64 - - switch op { - case code.OpAdd: - result = leftValue + rightValue - case code.OpSub: - result = leftValue - rightValue - case code.OpMul: - result = leftValue * rightValue - case code.OpDiv: - result = leftValue / rightValue - case code.OpMod: - result = leftValue % rightValue - case code.OpBitwiseOR: - result = leftValue | rightValue - case code.OpBitwiseXOR: - result = leftValue ^ rightValue - case code.OpBitwiseAND: - result = leftValue & rightValue - case code.OpLeftShift: - result = leftValue << uint64(rightValue) - case code.OpRightShift: - result = leftValue >> uint64(rightValue) - - default: - return fmt.Errorf("unknown integer operator: %d", op) - } - - return vm.push(&object.Integer{Value: result}) -} - -func (vm *VM) executeBinaryStringOperation(op code.Opcode, left, right object.Object) error { - if op != code.OpAdd { - return fmt.Errorf("unknown string operator: %d", op) - } - - leftValue := left.(*object.String).Value - rightValue := right.(*object.String).Value - - return vm.push(&object.String{Value: leftValue + rightValue}) -} - -func (vm *VM) executeComparison(op code.Opcode) error { +func (vm *VM) executeSub() error { right := vm.pop() left := vm.pop() - switch op { - case code.OpEqual: - return vm.push(nativeBoolToBooleanObject(left.(object.Comparable).Compare(right) == 0)) - case code.OpNotEqual: - return vm.push(nativeBoolToBooleanObject(left.(object.Comparable).Compare(right) != 0)) - case code.OpGreaterThanEqual: - return vm.push(nativeBoolToBooleanObject(left.(object.Comparable).Compare(right) > -1)) - case code.OpGreaterThan: - return vm.push(nativeBoolToBooleanObject(left.(object.Comparable).Compare(right) == 1)) - default: - return fmt.Errorf("unknown operator: %d (%s %s)", - op, left.Type(), right.Type()) + if obj, ok := left.(object.Sub); ok { + val, err := obj.Sub(right) + if err != nil { + return err + } + return vm.push(val) } + + return object.NewBinaryOpError(left, right, "+") } -func (vm *VM) executeBitwiseNotOperator() error { - operand := vm.pop() - if i, ok := operand.(*object.Integer); ok { - return vm.push(&object.Integer{Value: ^i.Value}) +func (vm *VM) executeMul() error { + right := vm.pop() + left := vm.pop() + + if obj, ok := left.(object.Mul); ok { + val, err := obj.Mul(right) + if err != nil { + return err + } + return vm.push(val) } - return fmt.Errorf("expected int got=%T", operand) + + return object.NewBinaryOpError(left, right, "*") } -func (vm *VM) executeNotOperator() error { - operand := vm.pop() +func (vm *VM) executeDiv() error { + right := vm.pop() + left := vm.pop() - switch operand { - case True: - return vm.push(False) - case False: - return vm.push(True) - case Null: - return vm.push(True) - default: + if obj, ok := left.(object.Div); ok { + val, err := obj.Div(right) + if err != nil { + return err + } + return vm.push(val) + } + + return object.NewBinaryOpError(left, right, "+") +} + +func (vm *VM) executeMod() error { + right := vm.pop() + left := vm.pop() + + if obj, ok := left.(object.Mod); ok { + val, err := obj.Mod(right) + if err != nil { + return err + } + return vm.push(val) + } + + return object.NewBinaryOpError(left, right, "%") +} + +func (vm *VM) executeOr() error { + right := vm.pop() + left := vm.pop() + + if obj, ok := left.(object.LogicalOr); ok { + val, err := obj.LogicalOr(right) + if err != nil { + return err + } + return vm.push(val) + } + + return object.NewBinaryOpError(left, right, "||") +} + +func (vm *VM) executeAnd() error { + right := vm.pop() + left := vm.pop() + + if obj, ok := left.(object.LogicalAnd); ok { + val, err := obj.LogicalAnd(right) + if err != nil { + return err + } + return vm.push(val) + } + + return object.NewBinaryOpError(left, right, "&&") +} + +func (vm *VM) executeBitwiseOr() error { + right := vm.pop() + left := vm.pop() + + if obj, ok := left.(object.BitwiseOr); ok { + val, err := obj.BitwiseOr(right) + if err != nil { + return err + } + return vm.push(val) + } + + return object.NewBinaryOpError(left, right, "|") +} + +func (vm *VM) executeBitwiseXor() error { + right := vm.pop() + left := vm.pop() + + if obj, ok := left.(object.BitwiseXor); ok { + val, err := obj.BitwiseXor(right) + if err != nil { + return err + } + return vm.push(val) + } + + return object.NewBinaryOpError(left, right, "^") +} + +func (vm *VM) executeBitwiseAnd() error { + right := vm.pop() + left := vm.pop() + + if obj, ok := left.(object.BitwiseAnd); ok { + val, err := obj.BitwiseAnd(right) + if err != nil { + return err + } + return vm.push(val) + } + + return object.NewBinaryOpError(left, right, "&") +} + +func (vm *VM) executeBitwiseNot() error { + left := vm.pop() + + if obj, ok := left.(object.BitwiseNot); ok { + return vm.push(obj.BitwiseNot()) + } + + return fmt.Errorf("unsupported types for unary operation: ~%s", left.Type()) +} + +func (vm *VM) executeLeftShift() error { + right := vm.pop() + left := vm.pop() + + if obj, ok := left.(object.LeftShift); ok { + val, err := obj.LeftShift(right) + if err != nil { + return err + } + return vm.push(val) + } + + return object.NewBinaryOpError(left, right, "<<") +} + +func (vm *VM) executeRightShift() error { + right := vm.pop() + left := vm.pop() + + if obj, ok := left.(object.RightShift); ok { + val, err := obj.RightShift(right) + if err != nil { + return err + } + return vm.push(val) + } + + return object.NewBinaryOpError(left, right, ">>") +} + +func (vm *VM) executeEqual() error { + right := vm.pop() + left := vm.pop() + + if obj, ok := left.(object.Comparable); ok { + val := obj.Compare(right) + if val == 0 { + return vm.push(True) + } return vm.push(False) } + + return object.NewBinaryOpError(left, right, "==") } -func (vm *VM) executeMinusOperator() error { - operand := vm.pop() +func (vm *VM) executeNotEqual() error { + right := vm.pop() + left := vm.pop() - if i, ok := operand.(*object.Integer); ok { - return vm.push(&object.Integer{Value: -i.Value}) + if obj, ok := left.(object.Comparable); ok { + val := obj.Compare(right) + if val != 0 { + return vm.push(True) + } + return vm.push(False) } - return fmt.Errorf("expected int got=%T", operand) + return object.NewBinaryOpError(left, right, "!=") } -func (vm *VM) executeCall(numArgs int) error { - callee := vm.stack[vm.sp-1-numArgs] +func (vm *VM) executeGreaterThan() error { + right := vm.pop() + left := vm.pop() + + if obj, ok := left.(object.Comparable); ok { + val := obj.Compare(right) + if val == 1 { + return vm.push(True) + } + return vm.push(False) + } + + return object.NewBinaryOpError(left, right, ">") +} + +func (vm *VM) executeGreaterThanOrEqual() error { + right := vm.pop() + left := vm.pop() + + if obj, ok := left.(object.Comparable); ok { + val := obj.Compare(right) + if val >= 0 { + return vm.push(True) + } + return vm.push(False) + } + + return object.NewBinaryOpError(left, right, ">") +} + +func (vm *VM) executeNot() error { + left := vm.pop() + + if obj, ok := left.(object.LogicalNot); ok { + return vm.push(obj.LogicalNot()) + } + + return fmt.Errorf("unsupported types for unary operation: !%s", left.Type()) +} + +func (vm *VM) executeMinus() error { + left := vm.pop() + + if obj, ok := left.(object.Negate); ok { + return vm.push(obj.Negate()) + } + + return fmt.Errorf("unsupported types for unary operation: -%s", left.Type()) +} + +func (vm *VM) executeSetItem() error { + right := vm.pop() + index := vm.pop() + left := vm.pop() + + if obj, ok := left.(object.Setter); ok { + err := obj.Set(index, right) + if err != nil { + return err + } + return vm.push(Null) + } + + return fmt.Errorf( + "set item operation not supported: left=%s index=%s", + left.Type(), index.Type(), + ) +} + +func (vm *VM) executeGetItem() error { + index := vm.pop() + left := vm.pop() + + if obj, ok := left.(object.Getter); ok { + val, err := obj.Get(index) + if err != nil { + return err + } + return vm.push(val) + } + + return fmt.Errorf( + "index operator not supported: left=%s index=%s", + left.Type(), index.Type(), + ) +} + +func (vm *VM) executeCall(args int) error { + callee := vm.stack[vm.sp-1-args] switch callee := callee.(type) { case *object.Closure: - return vm.callClosure(callee, numArgs) + return vm.callClosure(callee, args) case *object.Builtin: - return vm.callBuiltin(callee, numArgs) + return vm.callBuiltin(callee, args) default: - return fmt.Errorf("calling non-function and non-built-in") + return fmt.Errorf( + "calling non-closure and non-builtin: %T %v", + callee, callee, + ) } } +func (vm *VM) executeReturn() error { + returnValue := vm.pop() + + frame := vm.popFrame() + vm.sp = frame.basePointer - 1 + + return vm.push(returnValue) +} + func (vm *VM) callClosure(cl *object.Closure, numArgs int) error { if numArgs != cl.Fn.NumParameters { return fmt.Errorf("wrong number of arguments: want=%d, got=%d", cl.Fn.NumParameters, numArgs) @@ -905,32 +664,247 @@ func (vm *VM) pushClosure(constIndex, numFree int) error { return vm.push(closure) } -func (vm *VM) executeStringGetItem(str, index object.Object) error { - stringObject := str.(*object.String) - i := index.(*object.Integer).Value - max := int64(len(stringObject.Value) - 1) - - if i < 0 || i > max { - return vm.push(&object.String{Value: ""}) +func (vm *VM) loadModule(name object.Object) error { + s, ok := name.(*object.String) + if !ok { + return fmt.Errorf( + "TypeError: import() expected argument #1 to be `str` got `%s`", + name.Type(), + ) } - return vm.push(&object.String{Value: string(stringObject.Value[i])}) -} - -func (vm *VM) executeStringIndex(str, index object.Object) error { - stringObject := str.(*object.String) - substr := index.(*object.String).Value - - return vm.push( - &object.Integer{ - Value: int64(strings.Index(stringObject.Value, substr)), - }, - ) -} - -func nativeBoolToBooleanObject(input bool) *object.Boolean { - if input { - return True + attrs, err := executeModule(s.Value, vm.state) + if err != nil { + return err } - return False + + module := &object.Module{Name: s.Value, Attrs: attrs} + return vm.push(module) +} + +func (vm *VM) LastPoppedStackElem() object.Object { + return vm.stack[vm.sp] +} + +func (vm *VM) Run() error { + var n int + var ip int + var ins code.Instructions + var op code.Opcode + + if vm.Debug { + start := time.Now() + defer func() { + log.Printf("%d instructions executeuted in %s", n, time.Now().Sub(start)) + }() + } + + var err error + + for vm.frame.ip < len(vm.frame.Instructions())-1 && err == nil { + vm.frame.ip++ + + ip = vm.frame.ip + ins = vm.frame.Instructions() + op = code.Opcode(ins[ip]) + + if vm.Debug { + n++ + log.Printf( + "%-25s %-20s\n", + fmt.Sprintf( + "%04d %s", ip, + strings.Split(ins[ip:].String(), "\n")[0][4:], + ), + fmt.Sprintf( + "[ip=%02d fp=%02d, sp=%02d]", + ip, vm.fp-1, vm.sp, + ), + ) + } + + switch op { + case code.OpConstant: + constIndex := code.ReadUint16(ins[ip+1:]) + vm.frame.ip += 2 + err = vm.push(vm.state.Constants[constIndex]) + + case code.OpPop: + vm.pop() + + case code.OpTrue: + err = vm.push(True) + + case code.OpFalse: + err = vm.push(False) + + case code.OpJump: + pos := int(code.ReadUint16(ins[ip+1:])) + vm.frame.ip = pos - 1 + + case code.OpJumpNotTruthy: + pos := int(code.ReadUint16(ins[ip+1:])) + vm.frame.ip += 2 + if !isTruthy(vm.pop()) { + vm.frame.ip = pos - 1 + } + + case code.OpNull: + err = vm.push(Null) + + case code.OpSetGlobal: + globalIndex := code.ReadUint16(ins[ip+1:]) + vm.frame.ip += 2 + + ref := vm.pop() + if immutable, ok := ref.(object.Immutable); ok { + vm.state.Globals[globalIndex] = immutable.Clone() + } else { + vm.state.Globals[globalIndex] = ref + } + + err = vm.push(Null) + + case code.OpAssignGlobal: + globalIndex := code.ReadUint16(ins[ip+1:]) + vm.frame.ip += 2 + vm.state.Globals[globalIndex] = vm.pop() + + err = vm.push(Null) + + case code.OpAssignLocal: + localIndex := code.ReadUint8(ins[ip+1:]) + vm.frame.ip += 1 + + vm.stack[vm.frame.basePointer+int(localIndex)] = vm.pop() + + err = vm.push(Null) + + case code.OpGetGlobal: + globalIndex := code.ReadUint16(ins[ip+1:]) + vm.frame.ip += 2 + err = vm.push(vm.state.Globals[globalIndex]) + + case code.OpArray: + numElements := int(code.ReadUint16(ins[ip+1:])) + vm.frame.ip += 2 + err = vm.executeMakeArray(vm.sp-numElements, vm.sp) + + case code.OpHash: + numElements := int(code.ReadUint16(ins[ip+1:])) + vm.frame.ip += 2 + err = vm.executeMakeHash(vm.sp-numElements, vm.sp) + + case code.OpSetItem: + err = vm.executeSetItem() + + case code.OpGetItem: + err = vm.executeGetItem() + + case code.OpCall: + args := int(code.ReadUint8(ins[ip+1:])) + vm.frame.ip++ + err = vm.executeCall(args) + + case code.OpReturn: + err = vm.executeReturn() + + case code.OpSetLocal: + localIndex := code.ReadUint8(ins[ip+1:]) + vm.frame.ip += 1 + + ref := vm.pop() + if immutable, ok := ref.(object.Immutable); ok { + vm.stack[vm.frame.basePointer+int(localIndex)] = immutable.Clone() + } else { + vm.stack[vm.frame.basePointer+int(localIndex)] = ref + } + + err = vm.push(Null) + + case code.OpGetLocal: + localIndex := code.ReadUint8(ins[ip+1:]) + vm.frame.ip += 1 + err = vm.push(vm.stack[vm.frame.basePointer+int(localIndex)]) + + case code.OpGetBuiltin: + builtinIndex := code.ReadUint8(ins[ip+1:]) + vm.frame.ip++ + builtin := builtins.BuiltinsIndex[builtinIndex] + err = vm.push(builtin) + + case code.OpClosure: + constIndex := code.ReadUint16(ins[ip+1:]) + numFree := code.ReadUint8(ins[ip+3:]) + vm.frame.ip += 3 + err = vm.pushClosure(int(constIndex), int(numFree)) + + case code.OpGetFree: + freeIndex := code.ReadUint8(ins[ip+1:]) + vm.frame.ip += 1 + err = vm.push(vm.frame.cl.Free[freeIndex]) + + case code.OpCurrentClosure: + currentClosure := vm.frame.cl + err = vm.push(currentClosure) + + case code.OpLoadModule: + err = vm.executeLoadModule() + + case code.OpAdd: + err = vm.executeAdd() + case code.OpSub: + err = vm.executeSub() + case code.OpMul: + err = vm.executeMul() + case code.OpDiv: + err = vm.executeDiv() + case code.OpMod: + err = vm.executeMod() + case code.OpOr: + err = vm.executeOr() + case code.OpAnd: + err = vm.executeAnd() + case code.OpBitwiseOR: + err = vm.executeBitwiseOr() + case code.OpBitwiseXOR: + err = vm.executeBitwiseXor() + case code.OpBitwiseAND: + err = vm.executeBitwiseAnd() + case code.OpLeftShift: + err = vm.executeLeftShift() + case code.OpRightShift: + err = vm.executeRightShift() + + case code.OpEqual: + err = vm.executeEqual() + case code.OpNotEqual: + err = vm.executeNotEqual() + case code.OpGreaterThan: + err = vm.executeGreaterThan() + case code.OpGreaterThanEqual: + err = vm.executeGreaterThanOrEqual() + + case code.OpNot: + err = vm.executeNot() + + case code.OpBitwiseNOT: + err = vm.executeBitwiseNot() + + case code.OpMinus: + err = vm.executeMinus() + + default: + err = fmt.Errorf("unhandled opcode: %s", op) + } + + if vm.Debug { + log.Printf( + "%-25s [ip=%02d fp=%02d, sp=%02d]", + "", ip, vm.fp-1, vm.sp, + ) + } + } + + return err }