mirror of https://github.com/arendst/Tasmota.git
Berry improved `super()` for inheritance
This commit is contained in:
parent
36c89dc6f8
commit
5387345794
|
@ -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,18 +76,89 @@ 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 (argc >= 2) {
|
||||
if (be_isinstance(vm, 1) && be_isclass(vm, 2)) {
|
||||
/* leveled super, i.e. fix the parenthood class level */
|
||||
|
||||
/* 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 *bc = var_toobj(be_indexof(vm, 2));
|
||||
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_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 == bc) break; /* found */
|
||||
if (c == target_class) break; /* found */
|
||||
o = be_instance_super(o);
|
||||
}
|
||||
bvalue *top = be_incrtop(vm);
|
||||
|
@ -95,14 +169,12 @@ static int l_super(bvm *vm)
|
|||
}
|
||||
be_return(vm);
|
||||
} else {
|
||||
be_raise(vm, "type_error", "leveled super() requires 'instance' and 'class' arguments");
|
||||
}
|
||||
} 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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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,6 +259,11 @@ int be_instance_member(bvm *vm, binstance *instance, bstring *name, bvalue *dst)
|
|||
if (obj) {
|
||||
return type;
|
||||
} else { /* if no method found, try virtual */
|
||||
/* 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) {
|
||||
|
@ -272,6 +283,7 @@ int be_instance_member(bvm *vm, binstance *instance, bstring *name, bvalue *dst)
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return BE_NONE;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 -#
|
|
@ -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 -#
|
||||
|
|
Loading…
Reference in New Issue