aboutsummaryrefslogtreecommitdiff
path: root/pkg/lang
diff options
context:
space:
mode:
authorMel <einebeere@gmail.com>2022-06-01 19:51:40 +0000
committerGitHub <noreply@github.com>2022-06-01 19:51:40 +0000
commitedca72c160967f1918b65c91a40de89ecd8badda (patch)
treeb79feabc0889a3f6cfddf09906a486d09e5c60a1 /pkg/lang
parent33671436680e7922001df9921ee582c486c7c3f4 (diff)
downloadjinx-edca72c160967f1918b65c91a40de89ecd8badda.tar.zst
jinx-edca72c160967f1918b65c91a40de89ecd8badda.zip
Implement proper object types
Diffstat (limited to 'pkg/lang')
-rw-r--r--pkg/lang/vm/code/op.go3
-rw-r--r--pkg/lang/vm/core.go71
-rw-r--r--pkg/lang/vm/errors.go8
-rw-r--r--pkg/lang/vm/exec.go172
-rw-r--r--pkg/lang/vm/setup.go1
-rw-r--r--pkg/lang/vm/text/op.go2
-rw-r--r--pkg/lang/vm/utils.go2
-rw-r--r--pkg/lang/vm/value/core_ptrs.go2
-rw-r--r--pkg/lang/vm/value/data.go39
-rw-r--r--pkg/lang/vm/value/env.go37
-rw-r--r--pkg/lang/vm/value/type.go4
-rw-r--r--pkg/lang/vm/value/value.go21
-rw-r--r--pkg/lang/vm/vm.go8
-rw-r--r--pkg/lang/vm/vm_test.go77
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)