diff --git a/CHANGELOG.md b/CHANGELOG.md index f4a01c052..7c8a07150 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ All notable changes to this project will be documented in this file. ### Added - Version bump to monitor possible HTTP issues releated to ``SetOption128`` +### Changed +- Berry now compiling in ``strict`` mode to catch more bugs + ## [9.5.0.5] 20210815 ### Added - Inital support for Wi-Fi extender (#12784) diff --git a/lib/libesp32/Berry/default/be_modtab.c b/lib/libesp32/Berry/default/be_modtab.c index 0d3adf04d..edde70854 100644 --- a/lib/libesp32/Berry/default/be_modtab.c +++ b/lib/libesp32/Berry/default/be_modtab.c @@ -20,6 +20,7 @@ be_extern_native_module(sys); be_extern_native_module(debug); be_extern_native_module(gc); be_extern_native_module(solidify); +be_extern_native_module(strict); be_extern_native_module(introspect); /* Tasmota specific */ @@ -72,6 +73,9 @@ BERRY_LOCAL const bntvmodule* const be_module_table[] = { #endif #if BE_USE_INTROSPECT_MODULE &be_native_module(introspect), +#endif +#if BE_USE_STRICT_MODULE + &be_native_module(strict), #endif /* user-defined modules register start */ diff --git a/lib/libesp32/Berry/default/berry_conf.h b/lib/libesp32/Berry/default/berry_conf.h index aaa244434..e51ebd1a7 100644 --- a/lib/libesp32/Berry/default/berry_conf.h +++ b/lib/libesp32/Berry/default/berry_conf.h @@ -45,7 +45,7 @@ * Use precompiled objects to avoid creating these objects at * runtime. Enable this macro can greatly optimize RAM usage. * Default: 1 -// **/ + **/ #define BE_USE_PRECOMPILED_OBJECT 1 /* Macro: BE_DEBUG_RUNTIME_INFO @@ -142,6 +142,14 @@ **/ #define BE_USE_DEBUG_HOOK 0 +/* Macro: BE_USE_DEBUG_GC + * Enable GC debug mode. This causes an actual gc after each + * allocation. It's much slower and should not be used + * in production code. + * Default: 0 + **/ +#define BE_USE_DEBUG_GC 0 + /* Macro: BE_USE_XXX_MODULE * These macros control whether the related module is compiled. * When they are true, they will enable related modules. At this @@ -159,6 +167,7 @@ #define BE_USE_GC_MODULE 1 #define BE_USE_SOLIDIFY_MODULE 1 #define BE_USE_INTROSPECT_MODULE 1 +#define BE_USE_STRICT_MODULE 1 /* Macro: BE_EXPLICIT_XXX * If these macros are defined, the corresponding function will diff --git a/lib/libesp32/Berry/default/embedded/Tasmota.be b/lib/libesp32/Berry/default/embedded/Tasmota.be index 0c6c5d1eb..9266c135c 100644 --- a/lib/libesp32/Berry/default/embedded/Tasmota.be +++ b/lib/libesp32/Berry/default/embedded/Tasmota.be @@ -93,7 +93,7 @@ class Tasmota var sub_event = event var rl = string.split(rl_list[0],'#') for it:rl - found=self.find_key_i(sub_event,it) + var found=self.find_key_i(sub_event,it) if found == nil return false end sub_event = sub_event[found] end @@ -152,7 +152,7 @@ class Tasmota var i=0 while itype; + } } return type; } BERRY_API bbool be_getmember(bvm *vm, int index, const char *k) { - return ins_member(vm, index, k) != BE_NIL; + return ins_member(vm, index, k, bfalse) != BE_NIL; } BERRY_API bbool be_getmethod(bvm *vm, int index, const char *k) { - return basetype(ins_member(vm, index, k)) == BE_FUNCTION; + return basetype(ins_member(vm, index, k, btrue)) == BE_FUNCTION; } BERRY_API bbool be_getindex(bvm *vm, int index) diff --git a/lib/libesp32/Berry/src/be_class.c b/lib/libesp32/Berry/src/be_class.c index 720bba05c..4bd264446 100644 --- a/lib/libesp32/Berry/src/be_class.c +++ b/lib/libesp32/Berry/src/be_class.c @@ -178,6 +178,7 @@ void be_class_upvalue_init(bvm *vm, bclass *c) } } +/* (internal) Instanciate an instance for a single class and initialize variables to nil */ static binstance* newobjself(bvm *vm, bclass *c) { size_t size = sizeof(binstance) + sizeof(bvalue) * (c->nvar - 1); @@ -185,15 +186,17 @@ static binstance* newobjself(bvm *vm, bclass *c) binstance *obj = cast_instance(gco); be_assert(obj != NULL); if (obj) { /* initialize members */ - bvalue *v = obj->members, *end = v + c->nvar; - while (v < end) { var_setnil(v); ++v; } - obj->_class = c; - obj->super = NULL; - obj->sub = NULL; + bvalue *v = obj->members, *end = v + c->nvar; /* instance variables is a simple array of pointers at obj->members of size c->nvar */ + while (v < end) { var_setnil(v); ++v; } /* Initialize all instance variables to `nil` */ + obj->_class = c; /* set its class object */ + obj->super = NULL; /* no super class instance for now */ + obj->sub = NULL; /* no subclass instance for now */ } return obj; } +/* (internal) Instanciate the whole chain of instances when there is a class hierarchy */ +/* All variables set to nil, constructors are not called here */ static binstance* newobject(bvm *vm, bclass *c) { binstance *obj, *prev; @@ -201,23 +204,26 @@ static binstance* newobject(bvm *vm, bclass *c) obj = prev = newobjself(vm, c); var_setinstance(vm->top, obj); be_incrtop(vm); /* protect new objects from GC */ - for (c = c->super; c; c = c->super) { + for (c = c->super; c; c = c->super) { /* initialize one instance object per class and per superclass */ prev->super = newobjself(vm, c); - prev->super->sub = prev; + prev->super->sub = prev; /* link the super/sub classes instances */ prev = prev->super; } be_stackpop(vm, 1); return obj; } +/* Instanciate new instance from stack with argc parameters */ +/* Pushes the constructor on the stack to be executed if a construtor is found */ +/* Returns true if a constructor is found */ bbool be_class_newobj(bvm *vm, bclass *c, bvalue *reg, int argc, int mode) { bvalue init; size_t pos = reg - vm->reg; - binstance *obj = newobject(vm, c); - reg = vm->reg + pos - mode; /* the stack may have changed */ + binstance *obj = newobject(vm, c); /* create empty object hierarchy from class hierarchy */ + reg = vm->reg + pos - mode; /* the stack may have changed, mode=1 when class is instanciated from module #104 */ var_setinstance(reg, obj); - var_setinstance(reg + mode, obj); + var_setinstance(reg + mode, obj); /* copy to reg and reg+1 if mode==1 */ /* find constructor */ obj = instance_member(vm, obj, str_literal(vm, "init"), &init); if (obj && var_type(&init) != MT_VARIABLE) { @@ -231,6 +237,10 @@ bbool be_class_newobj(bvm *vm, bclass *c, bvalue *reg, int argc, int mode) return bfalse; } +/* 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 */ +/* TODO need to support synthetic members */ int be_instance_member(bvm *vm, binstance *obj, bstring *name, bvalue *dst) { int type; diff --git a/lib/libesp32/Berry/src/be_code.c b/lib/libesp32/Berry/src/be_code.c index 56bd9f530..e9d0e4d9f 100644 --- a/lib/libesp32/Berry/src/be_code.c +++ b/lib/libesp32/Berry/src/be_code.c @@ -56,6 +56,7 @@ static void codelineinfo(bfuncinfo *finfo) #define codelineinfo(finfo) #endif +/* Add new instruction in the code vector */ static int codeinst(bfuncinfo *finfo, binstruction ins) { /* put new instruction in code array */ @@ -77,10 +78,13 @@ static int codeABx(bfuncinfo *finfo, bopcode op, int a, int bx) return codeinst(finfo, ISET_OP(op) | ISET_RA(a) | ISET_Bx(bx)); } +/* Move value from register b to register a */ +/* Check the previous instruction to compact both instruction as one if possible */ +/* If b is a constant, add LDCONST or add MOVE otherwise */ static void code_move(bfuncinfo *finfo, int a, int b) { - if (finfo->pc) { - binstruction *i = be_vector_end(&finfo->code); + if (finfo->pc) { /* If not the first instruction of the function */ + binstruction *i = be_vector_end(&finfo->code); /* get the last instruction */ bopcode op = IGET_OP(*i); if (op <= OP_LDNIL) { /* binop or unop */ /* remove redundant MOVE instruction */ @@ -98,6 +102,8 @@ static void code_move(bfuncinfo *finfo, int a, int b) } } +/* Free register at top (checks that it´s a register) */ +/* Warning: the register must be at top of stack */ static void free_expreg(bfuncinfo *finfo, bexpdesc *e) { /* release temporary register */ @@ -106,6 +112,8 @@ static void free_expreg(bfuncinfo *finfo, bexpdesc *e) } } +/* Privat. Allocate `count` new registers on the stack and uptade proto´s max nstack accordingly */ +/* Note: deallocate is simpler and handled by a macro */ static void allocstack(bfuncinfo *finfo, int count) { int nstack = finfo->freereg + count; @@ -117,6 +125,7 @@ static void allocstack(bfuncinfo *finfo, int count) } } +/* Allocate `count` registers at top of stack, update stack accordingly */ int be_code_allocregs(bfuncinfo *finfo, int count) { int base = finfo->freereg; @@ -227,6 +236,8 @@ void be_code_patchjump(bfuncinfo *finfo, int jmp) patchlistaux(finfo, jmp, finfo->pc, finfo->pc); } +/* Allocate new constant for value k */ +/* If k is NULL then push `nil` value */ static int newconst(bfuncinfo *finfo, bvalue *k) { int idx = be_vector_count(&finfo->kvec); @@ -239,6 +250,8 @@ static int newconst(bfuncinfo *finfo, bvalue *k) return idx; } +/* Find constant by value and return constant number, or -1 if constant does not exist */ +/* The search is linear and lilited to 50 elements for performance reasons */ static int findconst(bfuncinfo *finfo, bexpdesc *e) { int i, count = be_vector_count(&finfo->kvec); @@ -273,10 +286,11 @@ static int findconst(bfuncinfo *finfo, bexpdesc *e) return -1; } +/* convert expdesc to constant and return kreg index (either constant kindex or register number) */ static int exp2const(bfuncinfo *finfo, bexpdesc *e) { - int idx = findconst(finfo, e); - if (idx == -1) { + int idx = findconst(finfo, e); /* does the constant already exist? */ + if (idx == -1) { /* if not add it */ bvalue k; switch (e->type) { case ETINT: @@ -291,16 +305,16 @@ static int exp2const(bfuncinfo *finfo, bexpdesc *e) k.type = BE_STRING; k.v.s = e->v.s; break; - default: + default: /* all other values are filled later */ break; } - idx = newconst(finfo, &k); + idx = newconst(finfo, &k); /* create new constant */ } - if (idx < 256) { - e->type = ETCONST; + if (idx < 256) { /* if constant number fits in KB or KC */ + e->type = ETCONST; /* new type is constant by index */ e->v.idx = setK(idx); } else { /* index value is too large */ - e->type = ETREG; + e->type = ETREG; /* does not fit in compact mode, allocate an explicit register and emit LDCONTS */ e->v.idx = be_code_allocregs(finfo, 1); codeABx(finfo, OP_LDCONST, e->v.idx, idx); } @@ -368,6 +382,9 @@ static void code_closure(bfuncinfo *finfo, int idx, int dst) codeABx(finfo, OP_CLOSURE, dst, idx); /* load closure to register */ } +/* Given an integer, check if we should create a constant */ +/* True for values 0..3 and if there is room for kindex */ +/* This optimization makes code more compact for commonly used ints */ static bbool constint(bfuncinfo *finfo, bint i) { /* cache common numbers */ @@ -378,6 +395,9 @@ static bbool constint(bfuncinfo *finfo, bint i) return bfalse; } +/* Compute variable from an expdesc */ +/* Return constant index, or existing register or fallback to dst */ +/* At exit, If dst is `freereg`, the register is allocated */ static int var2reg(bfuncinfo *finfo, bexpdesc *e, int dst) { if (dst < 0) { /* if unspecified, allocate a new register if needed */ @@ -435,7 +455,7 @@ static int var2reg(bfuncinfo *finfo, bexpdesc *e, int dst) static int exp2reg(bfuncinfo *finfo, bexpdesc *e, int dst) { int reg = var2reg(finfo, e, dst); - if (hasjump(e)) { + if (hasjump(e)) { /* if conditional expression */ int pcf = NO_JUMP; /* position of an eventual LOAD false */ int pct = NO_JUMP; /* position of an eventual LOAD true */ int jpt = appendjump(finfo, jumpboolop(e, 1), e); @@ -452,6 +472,11 @@ static int exp2reg(bfuncinfo *finfo, bexpdesc *e, int dst) return reg; } +/* Select dest registers from both expressions */ +/* If one of them is already a register, keep it */ +/* If e1 or e2 are registers, we keep the lowest and free the highest (that must be at top) */ +/* If none is a register, allocate a new one */ +/* Returns the destination register, guaranteed to be ETREG */ static int codedestreg(bfuncinfo *finfo, bexpdesc *e1, bexpdesc *e2, int dst) { int cand_dst = dst; @@ -477,6 +502,8 @@ static int codedestreg(bfuncinfo *finfo, bexpdesc *e1, bexpdesc *e2, int dst) } } +/* compute binary expression and update e1 as result */ +/* On exit, e1 is guaranteed to be ETREG, which may have been allocated */ static void binaryexp(bfuncinfo *finfo, bopcode op, bexpdesc *e1, bexpdesc *e2, int dst) { if (dst < 0) { dst = finfo->freereg; } @@ -485,7 +512,7 @@ static void binaryexp(bfuncinfo *finfo, bopcode op, bexpdesc *e1, bexpdesc *e2, dst = codedestreg(finfo, e1, e2, dst); codeABC(finfo, op, dst, src1, src2); e1->type = ETREG; - e1->v.idx = dst; + e1->v.idx = dst; /* update register as output */ } void be_code_prebinop(bfuncinfo *finfo, int op, bexpdesc *e) @@ -503,6 +530,7 @@ void be_code_prebinop(bfuncinfo *finfo, int op, bexpdesc *e) } } +/* Apply binary operator `op` to e1 and e2, result in e1 */ void be_code_binop(bfuncinfo *finfo, int op, bexpdesc *e1, bexpdesc *e2, int dst) { switch (op) { @@ -527,6 +555,8 @@ void be_code_binop(bfuncinfo *finfo, int op, bexpdesc *e1, bexpdesc *e2, int dst } } +/* Apply unary operator and return register number */ +/* If input is register, change in place or allocate new register */ static void unaryexp(bfuncinfo *finfo, bopcode op, bexpdesc *e) { int src = exp2anyreg(finfo, e); @@ -536,6 +566,9 @@ static void unaryexp(bfuncinfo *finfo, bopcode op, bexpdesc *e) e->v.idx = dst; } +/* Apply not to conditional expression */ +/* If literal compute the value */ +/* Or invert t/f subexpressions */ static void code_not(bexpdesc *e) { switch (e->type) { @@ -555,6 +588,7 @@ static void code_not(bexpdesc *e) e->type = ETBOOL; } +/* Negative value of literal or emit NEG opcode */ static int code_neg(bfuncinfo *finfo, bexpdesc *e) { switch (e->type) { @@ -568,6 +602,7 @@ static int code_neg(bfuncinfo *finfo, bexpdesc *e) return 0; } +/* Bit flip of literal or emit FLIP opcode */ static int code_flip(bfuncinfo *finfo, bexpdesc *e) { switch (e->type) { @@ -580,6 +615,7 @@ static int code_flip(bfuncinfo *finfo, bexpdesc *e) return 0; } +/* Apply unary operator: not, neg or bitflip */ int be_code_unop(bfuncinfo *finfo, int op, bexpdesc *e) { switch (op) { @@ -624,24 +660,28 @@ static void setsfxvar(bfuncinfo *finfo, bopcode op, bexpdesc *e1, int src) codeABC(finfo, op, obj, e1->v.ss.idx, src); } +/* Assign expr e2 to e1 */ +/* e1 must be in a register and have a valid idx */ +/* return 1 if assignment was possible, 0 if type is not compatible */ int be_code_setvar(bfuncinfo *finfo, bexpdesc *e1, bexpdesc *e2) { int src = exp2reg(finfo, e2, - e1->type == ETLOCAL ? e1->v.idx : -1); + e1->type == ETLOCAL ? e1->v.idx : -1); /* Convert e2 to kreg */ + /* If e1 is a local variable, use the register */ if (e1->type != ETLOCAL || e1->v.idx != src) { - free_expreg(finfo, e2); /* free source (only ETREG) */ + free_expreg(finfo, e2); /* free source (checks only ETREG) */ /* TODO e2 is at top */ } switch (e1->type) { case ETLOCAL: /* It can't be ETREG. */ if (e1->v.idx != src) { - code_move(finfo, e1->v.idx, src); + code_move(finfo, e1->v.idx, src); /* do explicit move only if needed */ } break; - case ETGLOBAL: /* store to grobal R(A) -> G(Bx) */ + case ETGLOBAL: /* store to grobal R(A) -> G(Bx) by global index */ setsupvar(finfo, OP_SETGBL, e1, src); break; - case ETNGLOBAL: /* store to grobal R(A) -> G(Bx) */ + case ETNGLOBAL: /* store to global R(A) -> G(Bx) by name */ setbgblvar(finfo, OP_SETNGBL, e1, src); break; case ETUPVAL: @@ -659,6 +699,9 @@ int be_code_setvar(bfuncinfo *finfo, bexpdesc *e1, bexpdesc *e2) return 0; } +/* Get the expdesc as a register */ +/* if already in a register, use the existing register */ +/* if local or const, allocate a new register and copy value */ int be_code_nextreg(bfuncinfo *finfo, bexpdesc *e) { int dst = finfo->freereg; @@ -682,6 +725,9 @@ int be_code_getmethod(bfuncinfo *finfo, bexpdesc *e) return dst; } +/* Generate a CALL instruction at base register with argc consecutive values */ +/* i.e. arg1 is base+1... */ +/* Important: argc registers are freed upon call, which are supposed to be registers above base */ void be_code_call(bfuncinfo *finfo, int base, int argc) { codeABC(finfo, OP_CALL, base, argc, 0); @@ -758,6 +804,8 @@ void be_code_ret(bfuncinfo *finfo, bexpdesc *e) } } +/* Package a suffix object from `c` with key `k` */ +/* Both expdesc are materialized in kregs */ static void package_suffix(bfuncinfo *finfo, bexpdesc *c, bexpdesc *k) { int key = exp2anyreg(finfo, k); @@ -771,12 +819,14 @@ int be_code_nglobal(bfuncinfo *finfo, bexpdesc *k) return exp2anyreg(finfo, k); } +/* Package a MEMBER suffix object from `c` with key `k` */ void be_code_member(bfuncinfo *finfo, bexpdesc *c, bexpdesc *k) { package_suffix(finfo, c, k); c->type = ETMEMBER; } +/* Package a INDEX suffix object from `c` with key `k` */ void be_code_index(bfuncinfo *finfo, bexpdesc *c, bexpdesc *k) { package_suffix(finfo, c, k); @@ -787,15 +837,15 @@ void be_code_class(bfuncinfo *finfo, bexpdesc *dst, bclass *c) { int src; bvalue var; - var_setclass(&var, c); - src = newconst(finfo, &var); - if (dst->type == ETLOCAL) { + var_setclass(&var, c); /* new var of CLASS type */ + src = newconst(finfo, &var); /* allocate a new constant and return kreg */ + if (dst->type == ETLOCAL) { /* if target is a local variable, just assign */ codeABx(finfo, OP_LDCONST, dst->v.idx, src); - } else { + } else { /* otherwise set as global with same name as class name */ codeABx(finfo, OP_LDCONST, finfo->freereg, src); codeABx(finfo, OP_SETGBL, finfo->freereg, dst->v.idx); } - codeABx(finfo, OP_CLASS, 0, src); + codeABx(finfo, OP_CLASS, 0, src); /* emit CLASS opcode to register class */ } void be_code_setsuper(bfuncinfo *finfo, bexpdesc *c, bexpdesc *s) @@ -807,6 +857,12 @@ void be_code_setsuper(bfuncinfo *finfo, bexpdesc *c, bexpdesc *s) free_expreg(finfo, s); } +/* Emit IMPORT opcode for import module */ +/* `m` is module name, is copied to register if not already */ +/* `v` is destination where the imported module is stored */ +/* If destination is a local variable, it is the destination of the IMPORT opcode */ +/* otherwise the value is copied to a temporary register and stored to the destination */ +/* TODO is this optilization useful, isn´t it done anyways by be_code_move optim? */ void be_code_import(bfuncinfo *finfo, bexpdesc *m, bexpdesc *v) { int dst, src = exp2anyreg(finfo, m); @@ -840,6 +896,10 @@ void be_code_catch(bfuncinfo *finfo, int base, int ecnt, int vcnt, int *jmp) } } +/* Emit RAISE opcode */ +/* e1 is the exception code */ +/* e2 is the exception description */ +/* both are materialized to a temp register (if not null) */ void be_code_raise(bfuncinfo *finfo, bexpdesc *e1, bexpdesc *e2) { if (e1) { @@ -847,7 +907,7 @@ void be_code_raise(bfuncinfo *finfo, bexpdesc *e1, bexpdesc *e2) int src2 = e2 ? exp2anyreg(finfo, e2) : 0; codeABC(finfo, OP_RAISE, e2 != NULL, src1, src2); } else { - codeABC(finfo, OP_RAISE, 2, 0, 0); + codeABC(finfo, OP_RAISE, 2, 0, 0); /* rethrow the current exception with parameters already on top of stack */ } /* release the register occupied by the expression */ free_expreg(finfo, e1); diff --git a/lib/libesp32/Berry/src/be_exec.c b/lib/libesp32/Berry/src/be_exec.c index 4bca65877..2d71dab0f 100644 --- a/lib/libesp32/Berry/src/be_exec.c +++ b/lib/libesp32/Berry/src/be_exec.c @@ -82,6 +82,8 @@ void be_throw(bvm *vm, int errorcode) } } +/* Fatal error Exit */ +/* Raise a BE_EXIT exception if within a try/catch block, or exit VM */ BERRY_API void be_exit(bvm *vm, int status) { if (vm->errjmp) { @@ -99,6 +101,8 @@ void be_throw_message(bvm *vm, int errorcode, const char *msg) be_throw(vm, errorcode); } +/* Exec protected: exec function and capture any exception and contain it within call */ +/* Exceptions or fatal errors are not propagated */ int be_execprotected(bvm *vm, bpfunc f, void *data) { struct blongjmp jmp; @@ -292,6 +296,7 @@ static void m_pcall(bvm *vm, void *data) be_dofunc(vm, p->v, p->argc); } +/* Protected call: contain any exception of fatal error and restore context if something went wrong */ int be_protectedcall(bvm *vm, bvalue *v, int argc) { int res; @@ -308,7 +313,8 @@ int be_protectedcall(bvm *vm, bvalue *v, int argc) } #if BE_DEBUG && defined(be_assert) -/* increase top register */ +/* increase top register and return new top */ +/* Does not expand the stack if there is not enough room, but may corrupt memory */ bvalue* be_incrtop(bvm *vm) { bvalue *top = vm->top++; @@ -317,6 +323,7 @@ bvalue* be_incrtop(bvm *vm) } #endif +/* TODO what is the difference with be_stack_push? */ void be_stackpush(bvm *vm) { /* make sure there is enough stack space */ @@ -324,6 +331,7 @@ void be_stackpush(bvm *vm) be_incrtop(vm); } +/* check that the stack is able to store `count` items, and increase stack if needed */ void be_stack_require(bvm *vm, int count) { if (vm->top + count >= vm->stacktop) { @@ -331,6 +339,7 @@ void be_stack_require(bvm *vm, int count) } } +/* Scan the entire callstack and adjust all pointer by `offset` */ static void update_callstack(bvm *vm, intptr_t offset) { bcallframe *cf = be_stack_top(&vm->callstack); @@ -353,20 +362,25 @@ static void update_upvalues(bvm *vm, intptr_t offset) } } +/* Resize the stack to new `size` as number of elements */ +/* Then update all pointers in callstack and upvalues with the new stack address */ static void stack_resize(bvm *vm, size_t size) { intptr_t offset; - bvalue *old = vm->stack; - size_t os = (vm->stacktop - old) * sizeof(bvalue); - vm->stack = be_realloc(vm, old, os, sizeof(bvalue) * size); - vm->stacktop = vm->stack + size; - offset = ptr_offset(vm->stack, old); + bvalue *old = vm->stack; /* save original pointer of stack before resize */ + size_t os = (vm->stacktop - old) * sizeof(bvalue); /* size of current stack allocated in bytes */ + vm->stack = be_realloc(vm, old, os, sizeof(bvalue) * size); /* reallocate with the new size */ + vm->stacktop = vm->stack + size; /* compute new stacktop */ + offset = ptr_offset(vm->stack, old); /* compute the address difference between old and ne stack addresses */ /* update callframes */ update_callstack(vm, offset); /* update open upvalues */ update_upvalues(vm, offset); } +/* Stack resize internal API */ +/* Increases the stack by `n` elements, reallocate stack if needed and update all callstacks and upvals */ +/* Check if we are above the max allowed stack */ void be_stack_expansion(bvm *vm, int n) { size_t size = vm->stacktop - vm->stack; diff --git a/lib/libesp32/Berry/src/be_globallib.c b/lib/libesp32/Berry/src/be_globallib.c index 8b5e16abd..1d8267b95 100644 --- a/lib/libesp32/Berry/src/be_globallib.c +++ b/lib/libesp32/Berry/src/be_globallib.c @@ -83,4 +83,4 @@ module global (scope: global, depend: BE_USE_GLOBAL_MODULE) { #include "../generate/be_fixed_global.h" #endif -#endif /* BE_USE_INTROSPECT_MODULE */ +#endif /* BE_USE_GLOBAL_MODULE */ diff --git a/lib/libesp32/Berry/src/be_module.c b/lib/libesp32/Berry/src/be_module.c index fcda03173..096a7c826 100644 --- a/lib/libesp32/Berry/src/be_module.c +++ b/lib/libesp32/Berry/src/be_module.c @@ -252,6 +252,16 @@ static void cache_module(bvm *vm, bstring *name) *v = vm->top[-1]; } +/* Try to run '()' function of module. Module is already loaded. */ +static void module_init(bvm *vm) { + if (be_getmember(vm, -1, "init")) { + /* found, call it with no parameter */ + be_call(vm, 0); + /* we don't care about the result */ + } + be_pop(vm, 1); +} + /* load module to vm->top */ int be_module_load(bvm *vm, bstring *path) { @@ -260,8 +270,11 @@ int be_module_load(bvm *vm, bstring *path) res = load_native(vm, path); if (res == BE_IO_ERROR) res = load_package(vm, path); - if (res == BE_OK) + if (res == BE_OK) { cache_module(vm, path); + /* on first load of the module, try running the '()' function */ + module_init(vm); + } } return res; } diff --git a/lib/libesp32/Berry/src/be_opcodes.h b/lib/libesp32/Berry/src/be_opcodes.h index f72049dbe..2fe9dbcae 100644 --- a/lib/libesp32/Berry/src/be_opcodes.h +++ b/lib/libesp32/Berry/src/be_opcodes.h @@ -51,7 +51,7 @@ OPCODE(CLOSE), /* A | close upvalues */ OPCODE(IMPORT), /* A, B, C | IF (A == C) import module name as RK(B) to RK(A), ELSE from module RK(C) import name as RK(B) to RK(A) */ OPCODE(EXBLK), /* A, Bx | ... */ OPCODE(CATCH), /* A, B, C | ... */ -OPCODE(RAISE), /* A, B, C | ... */ +OPCODE(RAISE), /* A, B, C | RAISE(B,C) B is code, C is description. A==0 only B provided, A==1 B and C are provided, A==2 rethrow with both parameters already on stack */ OPCODE(CLASS), /* Bx | init class in K[Bx] */ OPCODE(GETNGBL), /* A, B | R(A) <- GLOBAL[B] by name */ OPCODE(SETNGBL) /* A, B | R(A) -> GLOBAL[B] by name */ diff --git a/lib/libesp32/Berry/src/be_parser.c b/lib/libesp32/Berry/src/be_parser.c index 4a32c189d..f326d34d0 100644 --- a/lib/libesp32/Berry/src/be_parser.c +++ b/lib/libesp32/Berry/src/be_parser.c @@ -87,6 +87,8 @@ static void match_token(bparser *parser, btokentype type) scan_next_token(parser); /* skip this token */ } +/* Check that the next token is not of type `type` */ +/* or raise an exception */ static void match_notoken(bparser *parser, btokentype type) { if (next_type(parser) == type) { /* error when token is match */ @@ -95,6 +97,7 @@ static void match_notoken(bparser *parser, btokentype type) } } +/* check that if the expdesc is a symbol, it is avalid one or raise an exception */ static void check_symbol(bparser *parser, bexpdesc *e) { if (e->type == ETVOID && e->v.s == NULL) { /* error when token is not a symbol */ @@ -103,6 +106,7 @@ static void check_symbol(bparser *parser, bexpdesc *e) } } +/* check that the value in `e` is valid for a variable, i.e. conatins a value or a valid symbol */ static void check_var(bparser *parser, bexpdesc *e) { check_symbol(parser, e); /* check the token is a symbol */ @@ -172,14 +176,15 @@ void end_varinfo(bparser *parser, int beginpc) #endif +/* Initialize bblockinfo structure */ static void begin_block(bfuncinfo *finfo, bblockinfo *binfo, int type) { - binfo->prev = finfo->binfo; - finfo->binfo = binfo; + binfo->prev = finfo->binfo; /* save previous block */ + finfo->binfo = binfo; /* tell parser this is the current block */ binfo->type = (bbyte)type; binfo->hasupval = 0; - binfo->beginpc = finfo->pc; - binfo->nactlocals = (bbyte)be_list_count(finfo->local); + binfo->beginpc = finfo->pc; /* set starting pc for this block */ + binfo->nactlocals = (bbyte)be_list_count(finfo->local); /* count number of local variables in previous block */ if (type & BLOCK_LOOP) { binfo->breaklist = NO_JUMP; binfo->continuelist = NO_JUMP; @@ -197,9 +202,9 @@ static void end_block_ex(bparser *parser, int beginpc) be_code_patchlist(finfo, binfo->continuelist, binfo->beginpc); } end_varinfo(parser, beginpc); - be_list_resize(parser->vm, finfo->local, binfo->nactlocals); - finfo->freereg = binfo->nactlocals; - finfo->binfo = binfo->prev; + be_list_resize(parser->vm, finfo->local, binfo->nactlocals); /* remove local variables from this block, they are now out of scope */ + finfo->freereg = binfo->nactlocals; /* adjust first free register accordingly */ + finfo->binfo = binfo->prev; /* restore previous block */ } static void end_block(bparser *parser) @@ -207,6 +212,8 @@ static void end_block(bparser *parser) end_block_ex(parser, -1); } +/* Return the name of the source for this parser, could be `string`, + `stdin` or the name of the current function */ static bstring* parser_source(bparser *parser) { if (parser->finfo) { @@ -215,29 +222,30 @@ static bstring* parser_source(bparser *parser) return be_newstr(parser->vm, parser->lexer.fname); } +/* Initialize a function block and create a new `bprotoˋ */ static void begin_func(bparser *parser, bfuncinfo *finfo, bblockinfo *binfo) { bvm *vm = parser->vm; bproto *proto = be_newproto(vm); var_setproto(vm->top, proto); be_stackpush(vm); - be_vector_init(vm, &finfo->code, sizeof(binstruction)); + be_vector_init(vm, &finfo->code, sizeof(binstruction)); /* vector for code, vectors are not gced */ proto->code = be_vector_data(&finfo->code); proto->codesize = be_vector_capacity(&finfo->code); - be_vector_init(vm, &finfo->kvec, sizeof(bvalue)); + be_vector_init(vm, &finfo->kvec, sizeof(bvalue)); /* vector for constants */ proto->ktab = be_vector_data(&finfo->kvec); proto->nconst = be_vector_capacity(&finfo->kvec); - be_vector_init(vm, &finfo->pvec, sizeof(bproto*)); + be_vector_init(vm, &finfo->pvec, sizeof(bproto*)); /* vector for subprotos */ proto->ptab = be_vector_data(&finfo->pvec); proto->nproto = be_vector_capacity(&finfo->pvec); - proto->source = parser_source(parser); - finfo->local = be_list_new(vm); - var_setlist(vm->top, finfo->local); + proto->source = parser_source(parser); /* keep a copy of source for function */ + finfo->local = be_list_new(vm); /* list for local variables */ + var_setlist(vm->top, finfo->local); /* push list of local variables on the stack (avoid gc) */ be_stackpush(vm); - finfo->upval = be_map_new(vm); + finfo->upval = be_map_new(vm); /* push a map for upvals on stack */ var_setmap(vm->top, finfo->upval); be_stackpush(vm); - finfo->prev = parser->finfo; + finfo->prev = parser->finfo; /* init finfo */ finfo->lexer = &parser->lexer; finfo->proto = proto; finfo->freereg = 0; @@ -258,6 +266,7 @@ static void begin_func(bparser *parser, bfuncinfo *finfo, bblockinfo *binfo) begin_block(finfo, binfo, 0); } +/* compute the upval structure */ static void setupvals(bfuncinfo *finfo) { bproto *proto = finfo->proto; @@ -282,6 +291,7 @@ static void setupvals(bfuncinfo *finfo) } } +/* Function is complete, finalize bproto */ static void end_func(bparser *parser) { bvm *vm = parser->vm; @@ -289,9 +299,9 @@ static void end_func(bparser *parser) bproto *proto = finfo->proto; be_code_ret(finfo, NULL); /* append a return to last code */ - end_block(parser); - setupvals(finfo); - proto->code = be_vector_release(vm, &finfo->code); + end_block(parser); /* close block */ + setupvals(finfo); /* close upvals */ + proto->code = be_vector_release(vm, &finfo->code); /* compact all vectors and return NULL if empty */ proto->codesize = finfo->pc; proto->ktab = be_vector_release(vm, &finfo->kvec); proto->nconst = be_vector_count(&finfo->kvec); @@ -305,10 +315,11 @@ static void end_func(bparser *parser) proto->varinfo = be_vector_release(vm, &finfo->varvec); proto->nvarinfo = be_vector_count(&finfo->varvec); #endif - parser->finfo = parser->finfo->prev; + parser->finfo = parser->finfo->prev; /* restore previous `finfo` */ be_stackpop(vm, 2); /* pop upval and local */ } +/* is the next token a binary operator? If yes return the operator or `OP_NOT_BINARY` */ static btokentype get_binop(bparser *parser) { btokentype op = next_type(parser); @@ -318,6 +329,8 @@ static btokentype get_binop(bparser *parser) return OP_NOT_BINARY; } +/* is the next token a unary operator? If yes return the operator or `OP_NOT_BINARY` */ +/* operator 'negative' 'not' and 'flip' */ static btokentype get_unary_op(bparser *parser) { btokentype op = next_type(parser); @@ -327,6 +340,8 @@ static btokentype get_unary_op(bparser *parser) return OP_NOT_UNARY; } +/* is the next token an assignment operator? If yes return the operator or `OP_NOT_BINARY` */ +/* `=`, `+=`, ... `>>=` */ static btokentype get_assign_op(bparser *parser) { btokentype op = next_type(parser); @@ -336,6 +351,7 @@ static btokentype get_assign_op(bparser *parser) return OP_NOT_ASSIGN; } +/* Initialize bexpdesc structure with specific type and value as int */ static void init_exp(bexpdesc *e, exptype_t type, bint i) { e->type = (bbyte)type; @@ -346,6 +362,8 @@ static void init_exp(bexpdesc *e, exptype_t type, bint i) e->v.i = i; } +/* find local variable by name, starting at index `begin` */ +/* linear search, returns -1 if not found */ static int find_localvar(bfuncinfo *finfo, bstring *s, int begin) { int i, count = be_list_count(finfo->local); @@ -358,12 +376,22 @@ static int find_localvar(bfuncinfo *finfo, bstring *s, int begin) return -1; /* not found */ } +/* create a new local variable by name, or return the current register if already exists */ +/* returns the Reg number for the variable */ +/* If STRICT, we don't allow a var to hide a var from outer scope */ +/* We don't allow the same var to be defined twice in same scope */ static int new_localvar(bparser *parser, bstring *name) { bfuncinfo *finfo = parser->finfo; - int reg = find_localvar(finfo, name, finfo->binfo->nactlocals); + int reg = find_localvar(finfo, name, finfo->binfo->nactlocals); /* look if already exists skipping the local variables from upper blocks */ + /* 'strict': raise an exception if the local variable shadows another local variable */ if (reg == -1) { bvalue *var; + if (comp_is_strict(parser->vm)) { + if (find_localvar(finfo, name, 0) >= 0 && str(name)[0] != '.') { /* we do accept nested redifinition of internal variables starting with ':' */ + push_error(parser, "strict: redefinition of '%s' from outer scope", str(name)); + } + } reg = be_list_count(finfo->local); /* new local index */ var = be_list_push(parser->vm, finfo->local, NULL); var_setstr(var, name); @@ -371,10 +399,13 @@ static int new_localvar(bparser *parser, bstring *name) be_code_allocregs(finfo, 1); /* use a register */ } begin_varinfo(parser, name); + } else { + push_error(parser, "redefinition of '%s'", str(name)); } return reg; } +/* Find upval by name, if found return its index number, or -1 */ static int find_upval(bfuncinfo *finfo, bstring *s) { bvm *vm = finfo->lexer->vm; @@ -385,6 +416,8 @@ static int find_upval(bfuncinfo *finfo, bstring *s) return -1; } +/* Recursively search for upper blocks that are referenced in upvals */ +/* and mark them with `hasupval` */ static void mark_upval(bfuncinfo *finfo, int level) { bblockinfo *binfo = finfo->prev->binfo; @@ -413,12 +446,14 @@ static int new_upval(bvm *vm, bfuncinfo *finfo, bstring *name, bexpdesc *var) return index; } +/* create a new variable in currenr context as name, and create expdesc for it */ +/* If within a block, create as local, otherwise as global */ static void new_var(bparser *parser, bstring *name, bexpdesc *var) { bfuncinfo *finfo = parser->finfo; if (finfo->prev || finfo->binfo->prev || parser->islocal) { init_exp(var, ETLOCAL, 0); - var->v.idx = new_localvar(parser, name); + var->v.idx = new_localvar(parser, name); /* if local, contains the index in current local var list */ } else { init_exp(var, ETGLOBAL, 0); var->v.idx = be_global_new(parser->vm, name); @@ -465,6 +500,9 @@ static int singlevaraux(bvm *vm, bfuncinfo *finfo, bstring *s, bexpdesc *var) } } +/* get variable from next toden as name */ +/* and create an expdesc from it */ +/* can be new, global, named global, upval */ static void singlevar(bparser *parser, bexpdesc *var) { bexpdesc key; @@ -490,6 +528,10 @@ static void singlevar(bparser *parser, bexpdesc *var) } } +/* Parse function or method definition variable list */ +/* Create an implicit local variable for each argument starting at R0 */ +/* Update function proto argc to the expected number or arguments */ +/* Raise an exception if multiple arguments have the same name */ static void func_varlist(bparser *parser) { bexpdesc v; @@ -502,36 +544,36 @@ static void func_varlist(bparser *parser) str = next_token(parser).u.s; match_token(parser, TokenId); /* match and skip ID */ /* new local variable */ - if (find_localvar(parser->finfo, str, 0) == -1) { - new_var(parser, str, &v); - } else { - push_error(parser, "redefinition of '%s'", str(str)); - } + new_var(parser, str, &v); } } match_token(parser, OptRBK); /* skip ')' */ parser->finfo->proto->argc = parser->finfo->freereg; } +/* Parse a function includind arg list and body */ +/* Given name and type (function or method) */ +/* Returns `bproto` object */ static bproto* funcbody(bparser *parser, bstring *name, int type) { bfuncinfo finfo; bblockinfo binfo; /* '(' varlist ')' block 'end' */ - begin_func(parser, &finfo, &binfo); + begin_func(parser, &finfo, &binfo); /* init new function context */ finfo.proto->name = name; - if (type & FUNC_METHOD) { + if (type & FUNC_METHOD) { /* If method, add an implicit first argument `self` */ new_localvar(parser, parser_newstr(parser, "self")); } - func_varlist(parser); - stmtlist(parser); - end_func(parser); + func_varlist(parser); /* parse arg list */ + stmtlist(parser); /* parse statement without final `end` */ + end_func(parser); /* close function context */ match_token(parser, KeyEnd); /* skip 'end' */ - return finfo.proto; + return finfo.proto; /* return fully constructed `bproto` */ } -/* anonymous function */ +/* anonymous function, build `bproto` object with name `` */ +/* and build a expdesc for the bproto */ static void anon_func(bparser *parser, bexpdesc *e) { bproto *proto; @@ -559,11 +601,7 @@ static void lambda_varlist(bparser *parser) str = next_token(parser).u.s; match_token(parser, TokenId); /* match and skip ID */ /* new local variable */ - if (find_localvar(parser->finfo, str, 0) == -1) { - new_var(parser, str, &v); - } else { - push_error(parser, "redefinition of '%s'", str(str)); - } + new_var(parser, str, &v); } } match_token(parser, OptArrow); /* skip '->' */ @@ -590,6 +628,9 @@ static void lambda_expr(bparser *parser, bexpdesc *e) be_stackpop(parser->vm, 1); } +/* Instanciate a builtin type by name */ +/* Allocates a new register for the value, and call empty constructor */ +/* Is allocated as LOCAL and must be changed to REG when completed */ static void new_primtype(bparser *parser, const char *type, bexpdesc *e) { int idx; @@ -601,17 +642,19 @@ static void new_primtype(bparser *parser, const char *type, bexpdesc *e) init_exp(e, ETGLOBAL, idx); idx = be_code_nextreg(finfo, e); be_code_call(finfo, idx, 0); - e->type = ETLOCAL; + e->type = ETLOCAL; /* declare as local, will be changed to ETREG when completely initialized */ } +/* Parse next member within a list */ +/* `l` contains the current list. The expr is evaluated and added to the list */ static void list_nextmember(bparser *parser, bexpdesc *l) { bexpdesc e, v = *l; bfuncinfo *finfo = parser->finfo; expr(parser, &e); /* value */ - check_var(parser, &e); - be_code_binop(finfo, OptConnect, &v, &e, -1); - be_code_freeregs(finfo, 1); + check_var(parser, &e); /* check that we don´t have an unknown symbol */ + be_code_binop(finfo, OptConnect, &v, &e, -1); /* add it to list with CONNECT */ + be_code_freeregs(finfo, 1); /* since left arg is LOCAL, an ETREG was allocated. Free it */ } static void map_nextmember(bparser *parser, bexpdesc *l) @@ -619,25 +662,25 @@ static void map_nextmember(bparser *parser, bexpdesc *l) bexpdesc e, v = *l; bfuncinfo *finfo = parser->finfo; expr(parser, &e); /* key */ - check_var(parser, &e); - be_code_index(finfo, &v, &e); + check_var(parser, &e); /* check if value is valid */ + be_code_index(finfo, &v, &e); /* package as `v` as INDEX suffix for value `e` */ match_token(parser, OptColon); /* ':' */ - expr(parser, &e); /* value */ - check_var(parser, &e); - be_code_setvar(finfo, &v, &e); + expr(parser, &e); /* value in `e` */ + check_var(parser, &e); /* check if value is correct */ + be_code_setvar(finfo, &v, &e); /* set suffi INDEX value to e */ } static void list_expr(bparser *parser, bexpdesc *e) { /* '[' {expr ','} [expr] ']' */ - new_primtype(parser, "list", e); /* new list */ + new_primtype(parser, "list", e); /* new list, created as LOCAL first */ while (next_type(parser) != OptRSB) { list_nextmember(parser, e); if (!match_skip(parser, OptComma)) { /* ',' */ break; } } - e->type = ETREG; + e->type = ETREG; /* then turned to REG */ match_token(parser, OptRSB); /* skip ']' */ } @@ -655,14 +698,16 @@ static void map_expr(bparser *parser, bexpdesc *e) match_token(parser, OptRBR); /* skip '}' */ } +/* push each argument as new reg and return number of args */ +/* TODO `e` is ignored by caller */ static int exprlist(bparser *parser, bexpdesc *e) { bfuncinfo *finfo = parser->finfo; int n = 1; /* expr { ',' expr } */ - expr(parser, e); - check_var(parser, e); - be_code_nextreg(finfo, e); + expr(parser, e); /* parse expr */ + check_var(parser, e); /* check if valid */ + be_code_nextreg(finfo, e); /* move result to next reg */ while (match_skip(parser, OptComma)) { /* ',' */ expr(parser, e); check_var(parser, e); @@ -672,6 +717,9 @@ static int exprlist(bparser *parser, bexpdesc *e) return n; } +/* parse call to method or function */ +/* `e` can be a member (method) or a register */ +/* On return, `e` is ETREG to the result of the call */ static void call_expr(bparser *parser, bexpdesc *e) { bexpdesc args; @@ -685,14 +733,15 @@ static void call_expr(bparser *parser, bexpdesc *e) if (ismember) { base = be_code_getmethod(finfo, e); } else { - base = be_code_nextreg(finfo, e); + base = be_code_nextreg(finfo, e); /* allocate a new base reg if not at top already */ } + /* base is always taken at top of freereg and allocates 1 reg for function and 2 regs for method */ scan_next_token(parser); /* skip '(' */ - if (next_type(parser) != OptRBK) { - argc = exprlist(parser, &args); + if (next_type(parser) != OptRBK) { /* if arg list is not empty */ + argc = exprlist(parser, &args); /* push each argument as new reg and return number of args */ } match_token(parser, OptRBK); /* skip ')' */ - argc += ismember; + argc += ismember; /* if method there is an additional implicit arg */ be_code_call(finfo, base, argc); if (e->type != ETREG) { e->type = ETREG; @@ -700,6 +749,8 @@ static void call_expr(bparser *parser, bexpdesc *e) } } +/* Parse member expression */ +/* Generates an ETMEMBER object that is materialized later into GETMBR, GETMET or SETMBR */ static void member_expr(bparser *parser, bexpdesc *e) { bstring *str; @@ -841,15 +892,32 @@ static void compound_assign(bparser *parser, int op, bexpdesc *l, bexpdesc *r) } } +/* check if we need to create a new local variable with this name to be assigned to */ +/* Returns true if it´s a new local variable */ +/* A new implicit local variable is created if no global has the same name (excluding builtins) */ +/* This means that you can override a builtin silently */ +/* This also means that a function cannot create a global, they must preexist or create with `global` module */ +/* TODO add warning in strict mode */ static int check_newvar(bparser *parser, bexpdesc *e) { if (e->type == ETGLOBAL) { if (e->v.idx < be_builtin_count(parser->vm)) { e->v.s = be_builtin_name(parser->vm, e->v.idx); + if (comp_is_strict(parser->vm)) { + push_error(parser, "strict: redefinition of builtin '%s'", + str(e->v.s)); + } return btrue; } return bfalse; } + if (comp_is_strict(parser->vm)) { + bfuncinfo *finfo = parser->finfo; + if ((e->type == ETVOID) && (finfo->prev || finfo->binfo->prev || parser->islocal)) { + push_error(parser, "strict: no global '%s', did you mean 'var %s'?", + str(e->v.s), str(e->v.s)); + } + } return e->type == ETVOID; } @@ -915,39 +983,42 @@ static void cond_expr(bparser *parser, bexpdesc *e) static void sub_expr(bparser *parser, bexpdesc *e, int prio) { bfuncinfo *finfo = parser->finfo; - btokentype op = get_unary_op(parser); - if (op != OP_NOT_UNARY) { + btokentype op = get_unary_op(parser); /* check if first token in unary op */ + if (op != OP_NOT_UNARY) { /* unary op found */ int line, res; - scan_next_token(parser); - line = parser->lexer.linenumber; - sub_expr(parser, e, UNARY_OP_PRIO); - check_var(parser, e); - res = be_code_unop(finfo, op, e); + scan_next_token(parser); /* move to next token */ + line = parser->lexer.linenumber; /* remember line number for error reporting */ + sub_expr(parser, e, UNARY_OP_PRIO); /* parse subexpr with new prio */ + check_var(parser, e); /* check that the value is ok */ + res = be_code_unop(finfo, op, e); /* apply unary op with optimizations if the token is a value */ if (res) { /* encode unary op */ parser->lexer.linenumber = line; push_error(parser, "wrong type argument to unary '%s'", res == 1 ? "negative" : "bit-flip"); } } else { - suffix_expr(parser, e); + suffix_expr(parser, e); /* parse left part of binop */ } - op = get_binop(parser); - while (op != OP_NOT_BINARY && prio > binary_op_prio(op)) { + op = get_binop(parser); /* check if binop */ + while (op != OP_NOT_BINARY && prio > binary_op_prio(op)) { /* is binop applicable */ bexpdesc e2; - check_var(parser, e); - scan_next_token(parser); + check_var(parser, e); /* check that left part is valid */ + scan_next_token(parser); /* move to next token */ be_code_prebinop(finfo, op, e); /* and or */ init_exp(&e2, ETVOID, 0); - sub_expr(parser, &e2, binary_op_prio(op)); - check_var(parser, &e2); + sub_expr(parser, &e2, binary_op_prio(op)); /* parse right side */ + check_var(parser, &e2); /* check if valid */ be_code_binop(finfo, op, e, &e2, -1); /* encode binary op */ - op = get_binop(parser); + op = get_binop(parser); /* is there a following binop? */ } if (prio == ASSIGN_OP_PRIO) { cond_expr(parser, e); } } +/* Parse new expression and return value in `e` (overwritten) */ +/* Initializes an empty expdes and parse subexpr */ +/* Always allocates a new temp register at top of freereg */ static void expr(bparser *parser, bexpdesc *e) { init_exp(e, ETVOID, 0); diff --git a/lib/libesp32/Berry/src/be_parser.h b/lib/libesp32/Berry/src/be_parser.h index 7d06fdc23..5b5510f2c 100644 --- a/lib/libesp32/Berry/src/be_parser.h +++ b/lib/libesp32/Berry/src/be_parser.h @@ -12,7 +12,7 @@ #include "be_string.h" typedef enum { - ETVOID, + ETVOID, /* unknown (new variable or error) */ ETNIL, ETBOOL, ETREAL, @@ -20,13 +20,13 @@ typedef enum { ETSTRING, ETPROTO, ETCONST, - ETLOCAL, - ETGLOBAL, + ETLOCAL, /* local variable, allocated until end of scope */ + ETGLOBAL, /* global by index number */ ETUPVAL, - ETMEMBER, - ETINDEX, - ETREG, - ETNGLOBAL + ETMEMBER, /* member accessor (by name) */ + ETINDEX, /* index accessor (ex array index) */ + ETREG, /* temporary register, can be freed if top of stack */ + ETNGLOBAL /* named global */ } exptype_t; typedef struct { diff --git a/lib/libesp32/Berry/src/be_strictlib.c b/lib/libesp32/Berry/src/be_strictlib.c new file mode 100644 index 000000000..b40329886 --- /dev/null +++ b/lib/libesp32/Berry/src/be_strictlib.c @@ -0,0 +1,40 @@ +/******************************************************************** +** Copyright (c) 2018-2021 Guan Wenliang & Stephan Hadinger +** This file is part of the Berry default interpreter. +** skiars@qq.com, https://github.com/Skiars/berry +** See Copyright Notice in the LICENSE file or at +** https://github.com/Skiars/berry/blob/master/LICENSE +********************************************************************/ +#include "be_object.h" +#include "be_module.h" +#include "be_string.h" +#include "be_vector.h" +#include "be_class.h" +#include "be_debug.h" +#include "be_map.h" +#include "be_vm.h" + +#if BE_USE_STRICT_MODULE + +static int m_init(bvm *vm) +{ + comp_set_strict(vm); /* enable compiler strict mode */ + be_return_nil(vm); +} + +#if !BE_USE_PRECOMPILED_OBJECT +be_native_module_attr_table(strict) { + be_native_module_function("init", m_init), +}; + +be_define_native_module(strict, NULL); +#else +/* @const_object_info_begin +module strict (scope: strict, depend: BE_USE_STRICT_MODULE) { + init, func(m_init) +} +@const_object_info_end */ +#include "../generate/be_fixed_strict.h" +#endif + +#endif /* BE_USE_STRICT_MODULE */ diff --git a/lib/libesp32/Berry/src/be_vm.c b/lib/libesp32/Berry/src/be_vm.c index ec30e8ce4..359aaa2d5 100644 --- a/lib/libesp32/Berry/src/be_vm.c +++ b/lib/libesp32/Berry/src/be_vm.c @@ -28,14 +28,14 @@ #define vm_error(vm, except, ...) \ be_raise(vm, except, be_pushfstring(vm, __VA_ARGS__)) -#define RA() (reg + IGET_RA(ins)) -#define RKB() ((isKB(ins) ? ktab : reg) + KR2idx(IGET_RKB(ins))) -#define RKC() ((isKC(ins) ? ktab : reg) + KR2idx(IGET_RKC(ins))) +#define RA() (reg + IGET_RA(ins)) /* Get value of register A */ +#define RKB() ((isKB(ins) ? ktab : reg) + KR2idx(IGET_RKB(ins))) /* Get value of register or constant B */ +#define RKC() ((isKC(ins) ? ktab : reg) + KR2idx(IGET_RKC(ins))) /* Get value of register or constant C */ -#define var2cl(_v) cast(bclosure*, var_toobj(_v)) -#define var2real(_v) (var_isreal(_v) ? (_v)->v.r : (breal)(_v)->v.i) -#define val2bool(v) ((v) ? btrue : bfalse) -#define ibinop(op, a, b) ((a)->v.i op (b)->v.i) +#define var2cl(_v) cast(bclosure*, var_toobj(_v)) /* cast var to closure */ +#define var2real(_v) (var_isreal(_v) ? (_v)->v.r : (breal)(_v)->v.i) /* get var as real or convert to real if integer */ +#define val2bool(v) ((v) ? btrue : bfalse) /* get var as bool (trur if non zero) */ +#define ibinop(op, a, b) ((a)->v.i op (b)->v.i) /* apply binary operator to both arguments as integers */ #if BE_USE_DEBUG_HOOK #define DEBUG_HOOK() \ @@ -149,6 +149,8 @@ static void call_error(bvm *vm, bvalue *v) "'%s' value is not callable", be_vtype2str(v)); } +/* Check that the return value is bool or raise an exception */ +/* `obj` and `method` are only passed for error reporting */ static void check_bool(bvm *vm, binstance *obj, const char *method) { if (!var_isbool(vm->top)) { @@ -182,25 +184,29 @@ static void do_linehook(bvm *vm) } #endif +/* Prepare the stack for the function/method call */ +/* `func` is a pointer to the function/method on the stack, it contains the closure before call and the result after the call */ +/* `nstackˋ is the stack depth used by the function (determined by compiler), we add BE_STACK_FREE_MIN as a safety margin */ static void precall(bvm *vm, bvalue *func, int nstack, int mode) { bcallframe *cf; - int expan = nstack + BE_STACK_FREE_MIN; - if (vm->stacktop < func + expan) { - size_t fpos = func - vm->stack; - be_stack_expansion(vm, expan); - func = vm->stack + fpos; + int expan = nstack + BE_STACK_FREE_MIN; /* `expan` is the minimum required space on the stack */ + if (vm->stacktop < func + expan) { /* do we have too little space left on the stack? */ + size_t fpos = func - vm->stack; /* compute offset of `func` from base stack, in case stack is reallocated and base address changes */ + be_stack_expansion(vm, expan); /* expand stack (vector object), warning stack address changes */ + func = vm->stack + fpos; /* recompute `func` address with new stack address */ } - be_stack_push(vm, &vm->callstack, NULL); - cf = be_stack_top(&vm->callstack); + be_stack_push(vm, &vm->callstack, NULL); /* push a NULL value on callstack */ + cf = be_stack_top(&vm->callstack); /* get address of new callframe at top of callstack */ cf->func = func - mode; - cf->top = vm->top; - cf->reg = vm->reg; - vm->reg = func + 1; - vm->top = vm->reg + nstack; - vm->cf = cf; + cf->top = vm->top; /* save previous stack top */ + cf->reg = vm->reg; /* save previous stack base */ + vm->reg = func + 1; /* new stack base is right after function */ + vm->top = vm->reg + nstack; /* new stack top is above the registers used by the function, so we don´t mess with them */ + vm->cf = cf; /* set new current callframe */ } +/* Prepare call of closure, setting the instruction pointer (ip) */ static void push_closure(bvm *vm, bvalue *func, int nstack, int mode) { bclosure *cl = var_toobj(func); @@ -469,9 +475,9 @@ static void vm_exec(bvm *vm) vm->cf->status |= BASE_FRAME; newframe: /* a new call frame */ be_assert(var_isclosure(vm->cf->func)); - clos = var_toobj(vm->cf->func); - ktab = clos->proto->ktab; - reg = vm->reg; + clos = var_toobj(vm->cf->func); /* `clos` is the current function/closure */ + ktab = clos->proto->ktab; /* `ktab` is the current constant table */ + reg = vm->reg; /* `reg` is the current stack base for the callframe */ vm_exec_loop() { opcase(LDNIL): { var_setnil(RA()); @@ -1019,7 +1025,7 @@ newframe: /* a new call frame */ dispatch(); } opcase(RAISE): { - if (IGET_RA(ins) < 2) { + if (IGET_RA(ins) < 2) { /* A==2 means no arguments are passed to RAISE, i.e. rethrow with current exception */ bvalue *top = vm->top; top[0] = *RKB(); /* push the exception value to top */ if (IGET_RA(ins)) { /* has exception argument? */ @@ -1046,8 +1052,8 @@ newframe: /* a new call frame */ dispatch(); } opcase(CALL): { - bvalue *var = RA(); - int mode = 0, argc = IGET_RKB(ins); + bvalue *var = RA(); /* `var` is the register for the call followed by arguments */ + int mode = 0, argc = IGET_RKB(ins); /* B contains number of arguments pushed on stack */ recall: /* goto: instantiation class and call constructor */ switch (var_type(var)) { case NOT_METHOD: @@ -1055,8 +1061,8 @@ newframe: /* a new call frame */ ++var, --argc, mode = 1; goto recall; case BE_CLASS: - if (be_class_newobj(vm, var_toobj(var), var, ++argc, mode)) { - reg = vm->reg + mode; + if (be_class_newobj(vm, var_toobj(var), var, ++argc, mode)) { /* instanciate object and find constructor */ + reg = vm->reg + mode; /* constructor found */ mode = 0; var = RA() + 1; /* to next register */ goto recall; /* call constructor */ @@ -1072,15 +1078,15 @@ newframe: /* a new call frame */ } case BE_CLOSURE: { bvalue *v, *end; - bproto *proto = var2cl(var)->proto; - push_closure(vm, var, proto->nstack, mode); - reg = vm->reg; - v = reg + argc; - end = reg + proto->argc; - for (; v < end; ++v) { + bproto *proto = var2cl(var)->proto; /* get proto for closure */ + push_closure(vm, var, proto->nstack, mode); /* prepare stack for closure */ + reg = vm->reg; /* `reg` has changed, now new base register */ + v = reg + argc; /* end of provided arguments */ + end = reg + proto->argc; /* end of expected arguments */ + for (; v < end; ++v) { /* set all not provided arguments to nil */ var_setnil(v); } - goto newframe; + goto newframe; /* continue execution of the closure */ } case BE_NTVCLOS: { bntvclos *f = var_toobj(var); diff --git a/lib/libesp32/Berry/src/be_vm.h b/lib/libesp32/Berry/src/be_vm.h index 86926d421..627c8fcaf 100644 --- a/lib/libesp32/Berry/src/be_vm.h +++ b/lib/libesp32/Berry/src/be_vm.h @@ -14,9 +14,14 @@ #define comp_set_named_gbl(vm) ((vm)->compopt |= (1<compopt &= ~(1<compopt & (1<compopt |= (1<compopt &= ~(1<") - - home_btn = lv_btn(scr) - home_btn.set_pos(120,vres-40) - home_btn.set_size(80, 30) - home_btn.add_style(lv.OBJ_PART_MAIN, tastyle) - home_label = lv_label(home_btn) - home_label.set_text(lv.SYMBOL_OK) - - btn.del() - - - - - - logo.set_src("A:/Sunrise.bin") - logo.align(0,0,0,0) - - - - btn.set_height(120) - label.del() - logo = lv.img_create(btn) - logo.set_tasmota_logo() - - logo.set_zoom(384) - logo.set_angle(400) - - logo.set_style_local_image_recolor_opa(lv.OBJ_PART_MAIN, lv.STATE_DEFAULT, 255) - logo.set_style_local_image_recolor(lv.OBJ_PART_MAIN, lv.STATE_DEFAULT, lv_color(lv.BLUE)) - - - #- logo on splash screen -# - btn.del() - logo = lv.img_create(lv.scr_act()) - logo.set_tasmota_logo() - logo.set_style_local_image_recolor_opa(lv.OBJ_PART_MAIN, lv.STATE_DEFAULT, 255) - logo.set_style_local_image_recolor(lv.OBJ_PART_MAIN, lv.STATE_DEFAULT, lv_color(lv.WHITE)) - logo.align(0,0,0,0) - logo.set_zoom(400) - - - #- anil -# - logo.set_zoom(384) - - angle = 0 - do_rotate = nil - do_rotate = def () angle += 100 logo.set_angle(angle) tasmota.timer(100, do_rotate) end - - t48 = lv.tasmota_font(48) - label.set_text("A") - if t48 != nil label.set_style_local_text_font(lv.OBJ_PART_MAIN, lv.STATE_DEFAULT, t48) end - btn.set_height(120) - - - f10 = lv.montserrat_font(10) - if f10 != nil label.set_style_local_text_font(lv.OBJ_PART_MAIN, lv.STATE_DEFAULT, f10) end - - #- try zooming an image -# - img = lv.img_create(btn) - label.del() - img.set_src(lv.SYMBOL_OK) - - - -#- - f8 = lv.montserrat_font(8) - if f8 != nil label.set_style_local_text_font(lv.OBJ_PART_MAIN, lv.STATE_DEFAULT, f8) end --# -#- -f14 = lv.montserrat_font(14) -f28 = lv.montserrat_font(28) -btn.set_height(80) -btn.set_style_local_bg_color(0, lv.STATE_DEFAULT, lv_color(0xFFEEFF)) -label.set_style_local_text_font(0, lv.STATE_DEFAULT, f28) - - -scr = lv.scr_act() -scr.set_style_local_bg_color(lv.OBJ_PART_MAIN, lv.STATE_DEFAULT, lv_color(0x400000)) -scr.set_style_local_value_font(lv.OBJ_PART_MAIN, lv.STATE_DEFAULT, f28) - -label.set_style_local_value_font(lv.BTN_PART_MAIN, lv.STATE_DEFAULT, f28) --# - - #- lv_obj_set_style_local_bg_color(lv_scr_act(), LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_RED); -# - #- lv.obj_set_style_local_bg_color(lv.scr_act(), lv.OBJ_PART_MAIN, lv.STATE_DEFAULT, lv_color(0x00FF00)) -# - - #- lv_obj_set_style_local_bg_opa( lv_scr_act(), LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_COVER); -# - - #- - lv.demo() - -# -else - print('ILI9341 pins are not configured') -end - -#- -lv.obj_set_style_local_bg_color(lv.scr_act(), lv.OBJ_PART_MAIN, lv.STATE_DEFAULT, lv_color(lv.BLACK)) - -gauge = lv.gauge_create(lv.scr_act()) -gauge.set_size(150,150) -co = lv.cpicker_create(lv.scr_act()) -co.set_size(150,150) -co.set_pos(170,20) -k = lv.keyboard_create(lv.scr_act()) - -cal = lv.calendar_create(lv.scr_act()) -cal.del() -c = lv.checkbox_create(lv.scr_act()) -c.del() -c = lv.chart_create(lv.scr_act()) -c.del() - -co.del() - -k = lv.keyboard_create(lv.scr_act()) -k.del() - -led = lv.led_create(lv.scr_act()) -led.del() - -m = lv.msgbox_create(lv.scr_act()) -m.del() - -# menu item -rol = lv.roller_create(lv.scr_act()) -rol.del() -sl = lv.slider_create(lv.scr_act()) -sl.del() - -sp = lv.spinner_create(lv.scr_act()) -sp.del() - -w = lv.win_create(lv.scr_act()) -w.del() - -t = lv.textarea_create(lv.scr_act()) -t.set_text("Tasmota") -t.del() - --# \ No newline at end of file diff --git a/tasmota/berry/examples/watch_renaissance/autoexec.be b/tasmota/berry/examples/watch_renaissance/autoexec.be index 072d5d151..37adda377 100644 --- a/tasmota/berry/examples/watch_renaissance/autoexec.be +++ b/tasmota/berry/examples/watch_renaissance/autoexec.be @@ -38,9 +38,9 @@ ren_sec.set_pos(110,10) prev_day = -1 def set_watch() - now = tasmota.rtc() - time_raw = now['local'] + now['timezone'] * 60 - time = tasmota.time_dump(time_raw) + var now = tasmota.rtc() + var time_raw = now['local'] + now['timezone'] * 60 + var time = tasmota.time_dump(time_raw) # set second ren_sec.set_angle(60 * time['sec']) # set minutes diff --git a/tasmota/berry/modules/partition.be b/tasmota/berry/modules/partition.be index 364ad636d..db9773424 100644 --- a/tasmota/berry/modules/partition.be +++ b/tasmota/berry/modules/partition.be @@ -44,7 +44,7 @@ def crc32_create_table() return a end -crc32_table = crc32_create_table() +var crc32_table = crc32_create_table() def crc32_update(buf, crc) crc ^= 0xffffffff @@ -137,7 +137,6 @@ class Partition_info # print("Segment count", seg_count) var seg_offset = addr + 0x20 # sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t) = 24 + 8 - var seg_size = 0 for seg_num:0..seg_count-1 # print(string.format("Reading 0x%08X", seg_offset)) @@ -375,7 +374,6 @@ class Partition #- parse the raw bytes to a structured list of partition items -# def parse() - var i for i:0..94 # there are maximum 95 slots + md5 (0xC00) var item_raw = self.raw[i*32..(i+1)*32-1] var magic = item_raw.get(0,2) @@ -754,7 +752,7 @@ class Partition_manager : Driver end #- create and register driver in Tasmota -# -partition_manager = Partition_manager() +var partition_manager = Partition_manager() tasmota.add_driver(partition_manager) ## can be removed if put in 'autoexec.bat' partition_manager.web_add_handler() @@ -771,4 +769,4 @@ import partition p = partition.Partition() print(p) --# \ No newline at end of file +-# diff --git a/tasmota/xdrv_52_9_berry.ino b/tasmota/xdrv_52_9_berry.ino index 47f31c34a..213645f1f 100644 --- a/tasmota/xdrv_52_9_berry.ino +++ b/tasmota/xdrv_52_9_berry.ino @@ -277,7 +277,8 @@ void BerryInit(void) { do { berry.vm = be_vm_new(); /* create a virtual machine instance */ be_set_obs_hook(berry.vm, &BerryObservability); - comp_set_named_gbl(berry.vm); + comp_set_named_gbl(berry.vm); /* Enable named globals in Berry compiler */ + comp_set_strict(berry.vm); /* Enable strict mode in Berry compiler */ be_load_custom_libs(berry.vm); // Register functions