diff --git a/lib/libesp32/Berry/src/be_baselib.c b/lib/libesp32/Berry/src/be_baselib.c index d5d3e56f1..27a650827 100644 --- a/lib/libesp32/Berry/src/be_baselib.c +++ b/lib/libesp32/Berry/src/be_baselib.c @@ -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 #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); } diff --git a/lib/libesp32/Berry/src/be_class.c b/lib/libesp32/Berry/src/be_class.c index b8e3ea2fb..5441b372b 100644 --- a/lib/libesp32/Berry/src/be_class.c +++ b/lib/libesp32/Berry/src/be_class.c @@ -14,6 +14,7 @@ #include "be_vm.h" #include "be_func.h" #include "be_var.h" +#include #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; + } } } } diff --git a/lib/libesp32/Berry/tests/super_auto.be b/lib/libesp32/Berry/tests/super_auto.be new file mode 100644 index 000000000..8d7ede85f --- /dev/null +++ b/lib/libesp32/Berry/tests/super_auto.be @@ -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,nil) in B3::b2() -# + +assert(c3.b2(C3) == nil) #- if specifying the current class, can't find any relevant class in supers -# \ No newline at end of file diff --git a/lib/libesp32/Berry/tests/super_leveled.be b/lib/libesp32/Berry/tests/super_leveled.be index 09b7a115b..e88914941 100644 --- a/lib/libesp32/Berry/tests/super_leveled.be +++ b/lib/libesp32/Berry/tests/super_leveled.be @@ -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 -#