Berry improved `super()` for inheritance

This commit is contained in:
Stephan Hadinger 2021-08-26 19:30:57 +02:00
parent 36c89dc6f8
commit 5387345794
4 changed files with 253 additions and 38 deletions

View File

@ -11,6 +11,9 @@
#include "be_mem.h"
#include "be_gc.h"
#include "be_class.h"
#include "be_vector.h"
#include "be_string.h"
#include "be_map.h"
#include <string.h>
#define READLINE_STEP 100
@ -73,36 +76,105 @@ static int l_input(bvm *vm)
return m_readline(vm);
}
/* Look in the current class and all super classes for a method corresponding to a specific closure pointer */
static bclass *find_class_closure(bclass *cl, bclosure *needle)
{
while (cl) {
bmapnode *node; /* iterate on members of the class */
bmap *members = be_class_members(cl);
if (members) { /* only iterate if there are members */
bmapiter iter = be_map_iter();
while ((node = be_map_next(members, &iter)) != NULL) {
if (var_type(&node->value) == BE_CLOSURE) { /* only native functions are considered */
bclosure *clos_iter = var_toobj(&node->value); /* retrieve the method's closure */
if (clos_iter == needle) {
/* we found the closure, we now know its class */
return cl;
}
}
}
}
cl = be_class_super(cl); /* move to super class */
}
return NULL; /* not found */
}
static int l_super(bvm *vm)
{
int argc = be_top(vm);
if (argc) {
/* if no argument, or arg 1 is nil, return nil */
if (argc == 0 || be_isnil(vm, 1)) {
be_return_nil(vm);
}
/* if arg 1 is a class, simply return super */
if (be_isclass(vm, 1)) {
be_getsuper(vm, 1);
be_return(vm);
}
/* arg 1 is an instance */
if (be_isinstance(vm, 1)) {
binstance *o = var_toobj(be_indexof(vm, 1));
bclass *target_class = NULL; /* the minimal class expected, or any super class */
bclass *base_class = NULL; /* current class of the caller, if any */
/* if arg 2 is present, it must be a class */
if (argc >= 2) {
if (be_isinstance(vm, 1) && be_isclass(vm, 2)) {
/* leveled super, i.e. fix the parenthood class level */
binstance *o = var_toobj(be_indexof(vm, 1));
bclass *bc = var_toobj(be_indexof(vm, 2));
while (o) {
bclass *c = be_instance_class(o);
if (c == bc) break; /* found */
o = be_instance_super(o);
}
bvalue *top = be_incrtop(vm);
if (o) {
var_setinstance(top, o); /* return the instance with the specified parent class */
} else {
var_setnil(top); /* not found, return nil */
}
be_return(vm);
if (be_isclass(vm, 2)) {
target_class = var_toobj(be_indexof(vm, 2));
} else if (be_isnil(vm, 2)) {
// ignore, revert to standard super() behavior if second arg is explicit nil
} else {
be_raise(vm, "type_error", "leveled super() requires 'instance' and 'class' arguments");
}
}
/* now the more complex part, if arg 1 is an instance */
/* if instance is the sole argument, try to find if it comes from a method of a class and set 'base_class' accordinly */
/* later it will be equivalent to passing this class as second argument */
if (argc == 1) {
/* we look in the callstack for the caller's closure */
int size = be_stack_count(&vm->callstack);
if (size >= 2) { /* need at least 2 stackframes: current (for super() native) and caller (the one we are interested in) */
bcallframe *caller = be_vector_at(&vm->callstack, size - 2); /* get the callframe of caller */
bvalue *func = caller->func; /* function object of caller */
if (var_type(func) == BE_CLOSURE) { /* only useful if the caller is a Berry closure (i.e. not native) */
bclosure *clos_ctx = var_toobj(func); /* this is the closure we look for in the class chain */
base_class = find_class_closure(o->_class, clos_ctx); /* iterate on current and super classes to find where the closure belongs */
}
}
}
if (base_class || target_class) {
if (base_class) {
target_class = base_class->super;
if (!target_class) be_return_nil(vm); /* fast exit if top class */
}
/* leveled super, i.e. fix the parenthood class level */
if (o) {
o = be_instance_super(o); /* always skip the current class and move to super */
}
while (o) {
bclass *c = be_instance_class(o);
if (c == target_class) break; /* found */
o = be_instance_super(o);
}
bvalue *top = be_incrtop(vm);
if (o) {
var_setinstance(top, o); /* return the instance with the specified parent class */
} else {
var_setnil(top); /* not found, return nil */
}
be_return(vm);
} else {
/* simple use of super */
be_getsuper(vm, 1);
be_return(vm);
}
}
/* fall through, return nil if we don't know what to do */
be_return_nil(vm);
}

View File

@ -14,6 +14,7 @@
#include "be_vm.h"
#include "be_func.h"
#include "be_var.h"
#include <string.h>
#define check_members(vm, c) \
if (!(c)->members) { \
@ -237,6 +238,11 @@ bbool be_class_newobj(bvm *vm, bclass *c, bvalue *reg, int argc, int mode)
return bfalse;
}
/* Default empty constructor */
static int default_init_native_method(bvm *vm) {
be_return_nil(vm);
}
/* Find instance member by name and copy value to `dst` */
/* Input: none of `obj`, `name` and `dst` may not be NULL */
/* Returns the type of the member or BE_NONE if member not found */
@ -253,22 +259,28 @@ int be_instance_member(bvm *vm, binstance *instance, bstring *name, bvalue *dst)
if (obj) {
return type;
} else { /* if no method found, try virtual */
/* get method 'member' */
obj = instance_member(vm, instance, str_literal(vm, "member"), vm->top);
if (obj && basetype(var_type(vm->top)) == BE_FUNCTION) {
bvalue *top = vm->top;
var_setinstance(&top[1], instance);
var_setstr(&top[2], name);
vm->top += 3; /* prevent gc collection results */
be_dofunc(vm, top, 2); /* call method 'member' */
vm->top -= 3;
*dst = *vm->top; /* copy result to R(A) */
if (obj && var_type(dst) == MT_VARIABLE) {
*dst = obj->members[dst->v.i];
}
type = var_type(dst);
if (type != BE_NIL) {
return type;
/* if 'init' does not exist, create a virtual empty constructor */
if (strcmp(str(name), "init") == 0) {
var_setntvfunc(dst, default_init_native_method);
return var_type(dst);
} else {
/* get method 'member' */
obj = instance_member(vm, instance, str_literal(vm, "member"), vm->top);
if (obj && basetype(var_type(vm->top)) == BE_FUNCTION) {
bvalue *top = vm->top;
var_setinstance(&top[1], instance);
var_setstr(&top[2], name);
vm->top += 3; /* prevent gc collection results */
be_dofunc(vm, top, 2); /* call method 'member' */
vm->top -= 3;
*dst = *vm->top; /* copy result to R(A) */
if (obj && var_type(dst) == MT_VARIABLE) {
*dst = obj->members[dst->v.i];
}
type = var_type(dst);
if (type != BE_NIL) {
return type;
}
}
}
}

View File

@ -0,0 +1,132 @@
#- test for new auto class inference of super() -#
#- test that we can call init() even if it's not defined -#
class Z end
z=Z()
assert(z.init != nil)
z.init() #- should do nothing -#
#- check the old way still works -#
class A1
var a
def init(a)
self.a = a
end
end
class B1:A1
var b
def init(a,b)
super(self,A1).init(a)
self.b = b
end
end
class C1:B1
var c
def init(a,b,c)
super(self,B1).init(a,b)
self.c = c
end
end
#- -#
c1=C1(1,2,3)
assert(c1.a == 1)
assert(c1.b == 2)
assert(c1.c == 3)
#- test simple behavior -#
class A0 var a end
class B0:A0 var b end
class C0:B0 end
c0=C0()
assert(classof(c0) == C0)
assert(classof(super(c0)) == B0)
assert(classof(super(super(c0))) == A0)
assert(super(super(super(c0))) == nil)
assert(super(C0) == B0)
assert(super(super(C0)) == A0)
assert(super(super(super(C0))) == nil)
assert(classof(super(c0,B0)) == B0)
assert(classof(super(c0,A0)) == A0)
#- test auto inference of target superclass -#
class A
var a
def init(a)
self.a = a
end
end
class B:A
var b
def init(a,b)
super(self).init(a)
self.b = b
end
end
class C:B
var c
def init(a,b,c)
super(self).init(a,b)
self.c = c
end
end
#- -#
c=C(1,2,3)
assert(c.a == 1)
assert(c.b == 2)
assert(c.c == 3)class A
end
class B:A
var b
def init(a,b) super(self).init(a) self.b = b end
end
class C:B
var c
def init(a,b,c) super(self).init(a,b) self.c = c end
end
c=C(1,2,3)
#- variant if A2 does not have an init() method, still works -#
class A2
static a=1
end
class B2:A2
var b
def init(a,b) super(self).init(a) self.b = b end
end
class C2:B2
var c
def init(a,b,c) super(self).init(a,b) self.c = c end
end
#- -#
c2=C2(1,2,3)
assert(c2.a == 1)
assert(c2.b == 2)
assert(c2.c == 3)
#- difference in behavior whether the second arg is provided or not -#
class A3
end
class B3:A3
def b1()
return super(self)
end
def b2(c)
return super(self, c)
end
end
class C3:B3
end
#- -#
b3=B3()
c3=C3()
assert(classof(c3.b1()) == A3)
assert(classof(b3.b1()) == A3)
assert(classof(c3.b2(B3)) == B3)
assert(classof(c3.b2(A3)) == A3)
assert(classof(c3.b2(nil)) == B3) #- testing super(self<C3>,nil) in B3::b2() -#
assert(c3.b2(C3) == nil) #- if specifying the current class, can't find any relevant class in supers -#

View File

@ -26,10 +26,9 @@ assert(classname(super(super(C))) == 'A')
assert(super(super(super(C))) == nil)
#- super() levele -#
assert(super(a,A) == a)
assert(classname(super(a,A)) == 'A')
assert(classname(super(b,B)) == 'B')
assert(classname(super(c,C)) == 'C')
assert(super(a,A) == nil)
assert(super(b,B) == nil)
assert(super(c,C) == nil)
assert(classname(super(c,B)) == 'B')
assert(classname(super(c,A)) == 'A')
assert(super(c,map) == nil) #- not a parent class -#