about summary refs log tree commit diff
path: root/pkg
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
parent33671436680e7922001df9921ee582c486c7c3f4 (diff)
downloadjinx-edca72c160967f1918b65c91a40de89ecd8badda.tar.zst
jinx-edca72c160967f1918b65c91a40de89ecd8badda.zip
Implement proper object types
Diffstat (limited to 'pkg')
-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)