diff options
| -rw-r--r-- | pkg/lang/vm/code/op.go | 3 | ||||
| -rw-r--r-- | pkg/lang/vm/core.go | 71 | ||||
| -rw-r--r-- | pkg/lang/vm/errors.go | 8 | ||||
| -rw-r--r-- | pkg/lang/vm/exec.go | 172 | ||||
| -rw-r--r-- | pkg/lang/vm/setup.go | 1 | ||||
| -rw-r--r-- | pkg/lang/vm/text/op.go | 2 | ||||
| -rw-r--r-- | pkg/lang/vm/utils.go | 2 | ||||
| -rw-r--r-- | pkg/lang/vm/value/core_ptrs.go | 2 | ||||
| -rw-r--r-- | pkg/lang/vm/value/data.go | 39 | ||||
| -rw-r--r-- | pkg/lang/vm/value/env.go | 37 | ||||
| -rw-r--r-- | pkg/lang/vm/value/type.go | 4 | ||||
| -rw-r--r-- | pkg/lang/vm/value/value.go | 21 | ||||
| -rw-r--r-- | pkg/lang/vm/vm.go | 8 | ||||
| -rw-r--r-- | pkg/lang/vm/vm_test.go | 77 |
14 files changed, 390 insertions, 57 deletions
diff --git a/pkg/lang/vm/code/op.go b/pkg/lang/vm/code/op.go index 1fa8afc..1e501db 100644 --- a/pkg/lang/vm/code/op.go +++ b/pkg/lang/vm/code/op.go @@ -15,6 +15,7 @@ const ( OpPushArray OpPushFunction OpPushObject + OpPushType OpDrop @@ -27,6 +28,8 @@ const ( OpSetEnv OpAddToEnv + OpAnchorType + OpAdd OpSub OpIndex diff --git a/pkg/lang/vm/core.go b/pkg/lang/vm/core.go index 9796a11..59dcaed 100644 --- a/pkg/lang/vm/core.go +++ b/pkg/lang/vm/core.go @@ -50,27 +50,70 @@ func (vm *VM) createCoreFunctionType() value.Type { } } +func (vm *VM) createCoreTypeRefType() value.Type { + return value.Type{ + Kind: value.TypeRefType, + Methods: map[string]value.FunctionData{ + "$add_method": makeCoreFn(vm.coreTypeRefIntrAddMethod, 2), + }, + } +} + func (vm *VM) coreArrayLength(args []value.Value) (value.Value, error) { - a, err := vm.getThis() + a, err := ensureTypeThis[value.ArrayData](vm, value.ArrayType) if err != nil { return value.Value{}, err } - switch a.Type() { - case value.ArrayType: - arr := a.Data().(value.ArrayData) - len, err := arr.Len(vm.memory) - if err != nil { - return value.Value{}, err - } - res := value.NewInt(int64(len)) - return res, nil - default: - return value.Value{}, ErrInvalidOperandType{ - Op: code.OpTempArrLen, - X: a.Type(), + len, err := a.Len(vm.memory) + if err != nil { + return value.Value{}, err + } + res := value.NewInt(int64(len)) + return res, nil +} + +func (vm *VM) coreTypeRefIntrAddMethod(args []value.Value) (value.Value, error) { + ref, err := ensureTypeThis[value.TypeRefData](vm, value.TypeRefType) + if err != nil { + return value.Value{}, err + } + + nameData, err := ensureType[value.StringData](args[0], value.StringType) + if err != nil { + return value.Value{}, err + } + name, err := nameData.RawString(vm.memory) + if err != nil { + return value.Value{}, err + } + + fn, err := ensureType[value.FunctionData](args[1], value.FunctionType) + if err != nil { + return value.Value{}, err + } + + return value.Value{}, ref.AddMethod(vm.memory, name, fn) +} + +func ensureTypeThis[D value.Data](vm *VM, t value.TypeKind) (D, error) { + this, err := vm.getThis() + if err != nil { + return *new(D), err + } + + return ensureType[D](this, t) +} + +func ensureType[D value.Data](val value.Value, t value.TypeKind) (D, error) { + if val.Type() != t { + return *new(D), ErrInvalidOperandType{ + Op: code.OpNop, // TODO: Add error not dependent on op. + X: val.Type(), } } + + return val.Data().(D), nil } func makeCoreFn(f value.NativeFunc, argCount uint) value.FunctionData { diff --git a/pkg/lang/vm/errors.go b/pkg/lang/vm/errors.go index 4781179..fb37726 100644 --- a/pkg/lang/vm/errors.go +++ b/pkg/lang/vm/errors.go @@ -62,6 +62,14 @@ func (e ErrCorruptedMemCell) Error() string { return fmt.Sprintf("corrupted memory cell at %s", e.Ptr.String()) } +type ErrCantReanchorType struct { + Type value.TypeKind +} + +func (e ErrCantReanchorType) Error() string { + return fmt.Sprintf("can't reanchor type of value which already is of type %v", e.Type) +} + // Non-fatal errors, which will later be implemented as catchable exceptions type ErrInvalidOperandType struct { diff --git a/pkg/lang/vm/exec.go b/pkg/lang/vm/exec.go index 94557dc..be72158 100644 --- a/pkg/lang/vm/exec.go +++ b/pkg/lang/vm/exec.go @@ -69,6 +69,16 @@ func (vm *VM) execGetLocal(offset int) error { return nil } +func (vm *VM) execPushType(name string) error { + ref, err := value.NewType(vm.memory, name) + if err != nil { + return err + } + + vm.stack.Push(ref) + return nil +} + func (vm *VM) execGetMember(name string) error { parent, err := vm.stack.Pop() if err != nil { @@ -88,13 +98,53 @@ func (vm *VM) execGetMember(name string) error { member, ok := objCell.Get()[name] if ok { member = member.Clone(vm.memory) - } else { - member = value.NewNull() + + vm.stack.Push(member) + parent.Drop(vm.memory) + return nil } + } - vm.stack.Push(member) - parent.Drop(vm.memory) - return nil + if parent.Outlet().IsNull() { + outletPtr, err := vm.memory.Allocate(mem.CellKindOutlet) + if err != nil { + return err + } + parent = parent.WithOutlet(outletPtr) + } + + if parent.Type() == value.TypeRefType { + ref := parent.Data().(value.TypeRefData) + ptr := ref.TypeRef() + + cell, err := vm.getMemCell(ptr, mem.CellKindType, false) + if err != nil { + return err + } + + typ := cell.(value.TypeCell).Get() + + methodData, ok := typ.GetMethod(name) + + if !methodData.Env().IsNull() { + panic("methods with environments not implemented yet") + } + + if ok { + newEnv := value.NewEnv() + newEnv.Add(0, parent.Outlet()) + envPtr, err := newEnv.Allocate(vm.memory) + if err != nil { + return err + } + + method := value.NewFunction(0, 0).WithData(methodData.WithEnv(envPtr)) + // method = method.Clone(vm.memory) will only be necessary when we support methods with environments. + + vm.stack.Push(method) + parent.Drop(vm.memory) + return nil + } } typeCell, err := vm.getMemCell(parent.TypePtr(), mem.CellKindType, false) @@ -110,26 +160,15 @@ func (vm *VM) execGetMember(name string) error { return nil } - if parent.Outlet().IsNull() { - outletPtr, err := vm.memory.Allocate(mem.CellKindOutlet) - if err != nil { - return err - } - parent = parent.WithOutlet(outletPtr) - } - var envPtr mem.Ptr if member.Env().IsNull() { - envPtr, err = vm.memory.Allocate(mem.CellKindEnv) - if err != nil { - return err - } - newEnv := value.NewEnv() newEnv.Add(0, parent.Outlet()) // stackIndex can be 0, because the parent will be dropped when the function returns. - vm.memory.Set(envPtr, value.EnvCell(newEnv)) - member = member.WithEnv(envPtr) + envPtr, err = newEnv.Allocate(vm.memory) + if err != nil { + return err + } } else { // Clone the environment, with the parent at env 0. oldEnvCell, err := vm.getMemCell(member.Env(), mem.CellKindEnv, false) @@ -138,27 +177,21 @@ func (vm *VM) execGetMember(name string) error { } oldEnv := oldEnvCell.(value.EnvCell).Get() - newEnv := value.NewEnv() - - newEnv.Add(0, parent.Outlet()) - - for i := 0; i < oldEnv.Len(); i++ { - differentOutlet := oldEnv.GetOutlet(i) - if err = vm.memory.Retain(differentOutlet); err != nil { - return err - } - - newEnv.Add(oldEnv.GetStackIndex(i), differentOutlet) + newEnv, err := oldEnv.Clone(vm.memory) + if err != nil { + return err } - envPtr, err = vm.memory.Allocate(mem.CellKindEnv) + newEnv.Prepend(0, parent.Outlet()) + + envPtr, err = newEnv.Allocate(vm.memory) if err != nil { return err } - vm.memory.Set(envPtr, value.EnvCell(newEnv)) - member = member.WithEnv(envPtr) } + member = member.WithEnv(envPtr) + val := value.NewFunction(0, 0).WithData(member) vm.stack.Push(val) @@ -343,12 +376,51 @@ func (vm *VM) execAddToEnv(localIndex int) error { return nil } +func (vm *VM) execAnchorType() error { + o, err := vm.stack.Pop() + if err != nil { + return err + } + + t, err := vm.stack.Pop() + if err != nil { + return err + } + + if o.Type() != value.ObjectType { + return ErrInvalidOperandTypes{ + Op: code.OpAnchorType, + X: o.Type(), + Y: t.Type(), + } + } + + if t.Type() != value.TypeRefType { + return ErrInvalidOperandTypes{ + Op: code.OpAnchorType, + X: o.Type(), + Y: t.Type(), + } + } + + if !o.TypePtr().IsNull() { + return ErrCantReanchorType{ + Type: o.Type(), + } + } + + o = o.WithType(t.Data().(value.TypeRefData).TypeRef()) + + vm.stack.Push(o) + return nil +} + func (vm *VM) execAdd() error { - x, err := vm.popAndDrop() + x, err := vm.stack.Pop() if err != nil { return err } - y, err := vm.popAndDrop() + y, err := vm.stack.Pop() if err != nil { return err } @@ -389,10 +461,22 @@ func (vm *VM) execAdd() error { } } case value.StringType: + xv, err := x.Data().(value.StringData).RawString(vm.memory) + if err != nil { + return err + } + switch y.Type() { case value.StringType: - panic("not implemented") - // res = value.NewString(x.Data().(value.StringData).Get() + y.Data().(value.StringData).Get()) + yv, err := y.Data().(value.StringData).RawString(vm.memory) + if err != nil { + return err + } + + res, err = value.NewString(vm.memory, xv+yv) + if err != nil { + return err + } default: return ErrInvalidOperandTypes{ Op: code.OpAdd, @@ -408,6 +492,9 @@ func (vm *VM) execAdd() error { } } + x.Drop(vm.memory) + y.Drop(vm.memory) + vm.stack.Push(res) return nil } @@ -593,10 +680,11 @@ func (vm *VM) execCall(argCount uint) error { fn := f.Data().(value.FunctionData) if argCount != fn.Args() { - return ErrWrongNumberOfArguments{ - Got: argCount, - Needed: fn.Args(), - } + // TODO: Uncomment when push_function can set fn.Args() + // return ErrWrongNumberOfArguments{ + // Got: argCount, + // Needed: fn.Args(), + // } } if err = vm.stack.PushCall(fn.Pc(), vm.pc, fn.Env()); err != nil { diff --git a/pkg/lang/vm/setup.go b/pkg/lang/vm/setup.go index 754fd3a..db44b8a 100644 --- a/pkg/lang/vm/setup.go +++ b/pkg/lang/vm/setup.go @@ -27,6 +27,7 @@ func (vm *VM) setupCoreLib() error { {at: value.CORE_TYPE_BOOL, t: vm.createCoreBoolType()}, {at: value.CORE_TYPE_ARRAY, t: vm.createCoreArrayType()}, {at: value.CORE_TYPE_FUNCTION, t: vm.createCoreFunctionType()}, + {at: value.CORE_TYPE_TYPE_REF, t: vm.createCoreTypeRefType()}, } for _, job := range toAllocate { diff --git a/pkg/lang/vm/text/op.go b/pkg/lang/vm/text/op.go index 37b89dc..e6af940 100644 --- a/pkg/lang/vm/text/op.go +++ b/pkg/lang/vm/text/op.go @@ -15,6 +15,7 @@ var ( code.OpPushArray: "push_array", code.OpPushFunction: "push_function", code.OpPushObject: "push_object", + code.OpPushType: "push_type", code.OpDrop: "drop", code.OpGetGlobal: "get_global", code.OpGetLocal: "get_local", @@ -23,6 +24,7 @@ var ( code.OpGetEnv: "get_env", code.OpSetEnv: "set_env", code.OpAddToEnv: "add_to_env", + code.OpAnchorType: "anchor_type", code.OpAdd: "add", code.OpSub: "sub", code.OpIndex: "index", diff --git a/pkg/lang/vm/utils.go b/pkg/lang/vm/utils.go index a40a14f..46eccb1 100644 --- a/pkg/lang/vm/utils.go +++ b/pkg/lang/vm/utils.go @@ -30,7 +30,7 @@ func (vm *VM) popCallAndDrop() (int, error) { func (vm *VM) getMemCell(ptr mem.Ptr, kind mem.CellKind, allowNil bool) (mem.CellData, error) { if ptr.IsNull() { - return nil, ErrEnvNotSet + return nil, ErrEnvNotSet // TODO: Rename } if !vm.memory.Is(ptr, kind) { diff --git a/pkg/lang/vm/value/core_ptrs.go b/pkg/lang/vm/value/core_ptrs.go index 5462a8f..a1f0365 100644 --- a/pkg/lang/vm/value/core_ptrs.go +++ b/pkg/lang/vm/value/core_ptrs.go @@ -12,4 +12,6 @@ const ( CORE_TYPE_BOOL CORE_TYPE_ARRAY CORE_TYPE_FUNCTION + + CORE_TYPE_TYPE_REF ) diff --git a/pkg/lang/vm/value/data.go b/pkg/lang/vm/value/data.go index 9a8f7b6..e51904e 100644 --- a/pkg/lang/vm/value/data.go +++ b/pkg/lang/vm/value/data.go @@ -43,6 +43,14 @@ func (s StringData) String(m mem.Mem) (string, error) { } } +func (s StringData) RawString(m mem.Mem) (string, error) { + if data, err := m.Get(s.data); err == nil { + return data.(StringCell).Get(), nil + } else { + return "", err + } +} + type BoolData bool func (b BoolData) Get() bool { @@ -114,6 +122,10 @@ func (a ArrayData) Push(m mem.Mem, v Value) error { type NullData struct{} +func (n NullData) String(_ mem.Mem) (string, error) { + return "null", nil +} + type FunctionData struct { pc int args uint @@ -151,6 +163,33 @@ func (f FunctionData) String(_ mem.Mem) (string, error) { } } +type TypeRefData struct { + typeRef mem.Ptr +} + +func (t TypeRefData) String(_ mem.Mem) (string, error) { + return fmt.Sprintf("<type %v>", t.typeRef), nil +} + +func (t TypeRefData) TypeRef() mem.Ptr { + return t.typeRef +} + +func (t TypeRefData) AddMethod(m mem.Mem, name string, method FunctionData) error { + cell, err := m.Get(t.typeRef) + if err != nil { + return err + } + + typ := cell.(TypeCell).Get() + typ.Methods[name] = method + + if err := m.Set(t.typeRef, TypeCell(typ)); err != nil { + return err + } + return nil +} + type ObjectData struct { obj mem.Ptr } diff --git a/pkg/lang/vm/value/env.go b/pkg/lang/vm/value/env.go index 34a396c..2617699 100644 --- a/pkg/lang/vm/value/env.go +++ b/pkg/lang/vm/value/env.go @@ -12,6 +12,19 @@ func NewEnv() Env { } } +func (e *Env) Allocate(m mem.Mem) (mem.Ptr, error) { + ptr, err := m.Allocate(mem.CellKindEnv) + if err != nil { + return mem.NullPtr, err + } + + if err = m.Set(ptr, EnvCell(*e)); err != nil { + return mem.NullPtr, err + } + + return ptr, nil +} + func (e *Env) Add(stackIndex int, outlet mem.Ptr) { e.references = append(e.references, reference{ stackIndex: stackIndex, @@ -19,6 +32,30 @@ func (e *Env) Add(stackIndex int, outlet mem.Ptr) { }) } +func (e *Env) Prepend(stackIndex int, outlet mem.Ptr) { + e.references = append([]reference{ + { + stackIndex: stackIndex, + outlet: outlet, + }, + }, e.references...) +} + +func (e *Env) Clone(m mem.Mem) (Env, error) { + cloned := NewEnv() + for i := 0; i < e.Len(); i++ { + differentOutlet := e.GetOutlet(i) + if err := m.Retain(differentOutlet); err != nil { + return Env{}, err + } + + cloned.Add(e.GetStackIndex(i), differentOutlet) + } + return cloned, nil +} + +// TODO: Add bounds checking + func (e *Env) GetOutlet(envIndex int) mem.Ptr { return e.references[envIndex].outlet } diff --git a/pkg/lang/vm/value/type.go b/pkg/lang/vm/value/type.go index 4bda3c1..d25c20f 100644 --- a/pkg/lang/vm/value/type.go +++ b/pkg/lang/vm/value/type.go @@ -11,6 +11,7 @@ const ( ArrayType FunctionType + TypeRefType ObjectType ) @@ -30,6 +31,8 @@ func (t TypeKind) String() string { return "null" case FunctionType: return "function" + case TypeRefType: + return "type" case ObjectType: return "object" } @@ -39,6 +42,7 @@ func (t TypeKind) String() string { type Type struct { Kind TypeKind + Name string Methods map[string]FunctionData Statics map[string]Value } diff --git a/pkg/lang/vm/value/value.go b/pkg/lang/vm/value/value.go index f1ccc2d..953ac8b 100644 --- a/pkg/lang/vm/value/value.go +++ b/pkg/lang/vm/value/value.go @@ -73,6 +73,21 @@ func NewObject(m mem.Mem, t mem.Ptr) (Value, error) { return Value{t: t, d: ObjectData{obj: ptr}}, nil } +func NewType(m mem.Mem, name string) (Value, error) { + ptr, err := m.Allocate(mem.CellKindType) + if err != nil { + return Value{}, err + } + + t := Type{Kind: ObjectType, Name: name, Methods: make(map[string]FunctionData), Statics: make(map[string]Value)} + + if err = m.Set(ptr, TypeCell(t)); err != nil { + return Value{}, err + } + + return Value{t: CORE_TYPE_TYPE_REF, d: TypeRefData{typeRef: ptr}}, nil +} + func (v Value) TypePtr() mem.Ptr { return v.t } @@ -93,11 +108,17 @@ func (v Value) Type() TypeKind { return ArrayType case CORE_TYPE_FUNCTION: return FunctionType + case CORE_TYPE_TYPE_REF: + return TypeRefType default: return ObjectType } } +func (v Value) WithType(t mem.Ptr) Value { + return Value{t: t, d: v.d, outlet: v.outlet} +} + func (v Value) Data() Data { return v.d } diff --git a/pkg/lang/vm/vm.go b/pkg/lang/vm/vm.go index 966ec9f..41334a6 100644 --- a/pkg/lang/vm/vm.go +++ b/pkg/lang/vm/vm.go @@ -109,6 +109,11 @@ func (vm *VM) step(op code.Op) (stepDecision, error) { vm.execPushFunction(int(x)) case code.OpPushObject: err = vm.execPushObject() + case code.OpPushType: + name, advance := vm.code.GetString(vm.pc) + vm.pc += advance + + err = vm.execPushType(name) case code.OpDrop: _, err = vm.stack.Pop() @@ -147,6 +152,9 @@ func (vm *VM) step(op code.Op) (stepDecision, error) { err = vm.execAddToEnv(int(local)) + case code.OpAnchorType: + err = vm.execAnchorType() + case code.OpAdd: err = vm.execAdd() case code.OpSub: diff --git a/pkg/lang/vm/vm_test.go b/pkg/lang/vm/vm_test.go index c0b5c2e..325f200 100644 --- a/pkg/lang/vm/vm_test.go +++ b/pkg/lang/vm/vm_test.go @@ -229,6 +229,83 @@ func TestObject(t *testing.T) { test(t, src, "\"Petronij\"") } +func TestTypeConstruct(t *testing.T) { + /* + type Cat { + (name, age) { + this.name = name + this.age = age + } + + fn meow(this) { + return this.name + " says Meow!" + } + } + + var cat = Cat("Kitty", 3) + cat.meow() + */ + + src := ` + # Create a new type Cat + push_type "Cat" + + push_function @Cat:$init + push_string "$init" + + get_local 0 + get_member "$add_method" + + call 2 + + push_function @Cat:meow + push_string "meow" + + get_local 0 + get_member "$add_method" + + call 2 + + # Create a new instance of Cat + push_string "Kitty" + push_int 3 + get_local 0 + get_member "$init" + call 2 + + # Call the meow method + get_local 1 + get_member "meow" + call 0 + halt + + @Cat:$init: + get_env 0 + push_object + anchor_type + + get_local 0 + get_local 2 + set_member "name" + + get_local 1 + get_local 2 + set_member "age" + + ret + @Cat:meow: + push_string " says Meow!" + + get_env 0 + get_member "name" + + add + ret + ` + + test(t, src, "\"Kitty says Meow!\"") +} + func test(t *testing.T, src string, expected string) { bc := compile(t, src) vm := vm.New(&bc) |
