diff --git a/.gitignore b/.gitignore index 53c8da544..2dbf50516 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ tasmota/tasmota.ino.cpp tasmota*.bin tasmota*.bin.gz tasmota*.map +tasmota*.map.gz platformio_override.ini platformio_tasmota_cenv.ini diff --git a/CHANGELOG.md b/CHANGELOG.md index f4a01c052..baddf5d75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ 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 + +### Fixed +- Fixed PWM5 on ESP32C3 + ## [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 b84559548..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 */ @@ -101,6 +105,7 @@ BERRY_LOCAL const bntvmodule* const be_module_table[] = { extern void be_load_tasmota_ntvlib(bvm *vm); extern void be_load_wirelib(bvm *vm); extern void be_load_Driver_class(bvm *vm); +extern void be_load_Timer_class(bvm *vm); extern void be_load_driver_i2c_lib(bvm *vm); extern void be_load_md5_lib(bvm *vm); extern void be_load_aes_gcm_lib(bvm *vm); @@ -131,6 +136,7 @@ BERRY_API void be_load_custom_libs(bvm *vm) #if !BE_USE_PRECOMPILED_OBJECT /* be_load_xxxlib(vm); */ #endif + be_load_Timer_class(vm); be_load_tasmota_ntvlib(vm); be_load_Driver_class(vm); be_load_md5_lib(vm); diff --git a/lib/libesp32/Berry/default/be_tasmotalib.c b/lib/libesp32/Berry/default/be_tasmotalib.c index f14657f33..81c6c6016 100644 --- a/lib/libesp32/Berry/default/be_tasmotalib.c +++ b/lib/libesp32/Berry/default/be_tasmotalib.c @@ -925,162 +925,156 @@ const bclosure exec_rules_closure = { /*******************************************************************/ - -/******************************************************************** - "def set_timer(delay,f) " - "if !self._timers self._timers=[] end " - "self._timers.push([self.millis(delay),f]) " - "end " -********************************************************************/ /******************************************************************** ** Solidified function: set_timer ********************************************************************/ be_local_closure(set_timer, /* name */ be_nested_proto( - 9, /* nstack */ - 3, /* argc */ + 10, /* nstack */ + 4, /* argc */ 0, /* has upvals */ NULL, /* no upvals */ 0, /* has sup protos */ NULL, /* no sub protos */ 1, /* has constants */ - ( &(const bvalue[ 3]) { /* upvals */ - { { .s=be_nested_const_str("_timers", -1694866380, 7) }, BE_STRING}, - { { .s=be_nested_const_str("push", -2022703139, 4) }, BE_STRING}, - { { .s=be_nested_const_str("millis", 1214679063, 6) }, BE_STRING}, + ( &(const bvalue[ 4]) { /* constants */ + be_nested_string("_timers", -1694866380, 7), /* R256 - K0 */ + be_nested_string("push", -2022703139, 4), /* R257 - K1 */ + be_nested_string("Timer", -346839614, 5), /* R258 - K2 */ + be_nested_string("millis", 1214679063, 6), /* R259 - K3 */ }), (be_nested_const_str("set_timer", 2135414533, 9)), - (be_nested_const_str("string", 398550328, 6)), + (be_nested_const_str("input", -103256197, 5)), ( &(const binstruction[16]) { /* code */ - 0x880C0100, // 0000 GETMBR R3 R0 R256 - 0x740E0002, // 0001 JMPT R3 #0005 - 0x600C000A, // 0002 GETGBL R3 G10 - 0x7C0C0000, // 0003 CALL R3 0 - 0x90020003, // 0004 SETMBR R0 R256 R3 - 0x880C0100, // 0005 GETMBR R3 R0 R256 - 0x8C0C0701, // 0006 GETMET R3 R3 R257 - 0x6014000A, // 0007 GETGBL R5 G10 - 0x7C140000, // 0008 CALL R5 0 - 0x8C180102, // 0009 GETMET R6 R0 R258 - 0x5C200200, // 000A MOVE R8 R1 - 0x7C180400, // 000B CALL R6 2 - 0x40180A06, // 000C CONNECT R6 R5 R6 - 0x40180A02, // 000D CONNECT R6 R5 R2 - 0x7C0C0400, // 000E CALL R3 2 + 0x88100100, // 0000 GETMBR R4 R0 R256 + 0x74120002, // 0001 JMPT R4 #0005 + 0x6010000A, // 0002 GETGBL R4 G10 + 0x7C100000, // 0003 CALL R4 0 + 0x90020004, // 0004 SETMBR R0 R256 R4 + 0x88100100, // 0005 GETMBR R4 R0 R256 + 0x8C100901, // 0006 GETMET R4 R4 R257 + 0xB81A0400, // 0007 GETNGBL R6 R258 + 0x8C1C0103, // 0008 GETMET R7 R0 R259 + 0x5C240200, // 0009 MOVE R9 R1 + 0x7C1C0400, // 000A CALL R7 2 + 0x5C200400, // 000B MOVE R8 R2 + 0x5C240600, // 000C MOVE R9 R3 + 0x7C180600, // 000D CALL R6 3 + 0x7C100400, // 000E CALL R4 2 0x80000000, // 000F RET 0 R0 }) ) ); /*******************************************************************/ - -/******************************************************************** - // run every 50ms tick - "def run_deferred() " - "if self._timers " - "var i=0 " - "while i int`` @@ -79,7 +94,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 @@ -127,9 +142,9 @@ class Tasmota return false end - def set_timer(delay,f) + def set_timer(delay,f,id) if !self._timers self._timers=[] end - self._timers.push([self.millis(delay),f]) + self._timers.push(Timer(self.millis(delay),f,id)) end # run every 50ms tick @@ -137,8 +152,8 @@ class Tasmota if self._timers 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_byteslib.c b/lib/libesp32/Berry/src/be_byteslib.c index c171ccf02..4c35d2113 100644 --- a/lib/libesp32/Berry/src/be_byteslib.c +++ b/lib/libesp32/Berry/src/be_byteslib.c @@ -28,6 +28,198 @@ typedef struct buf_impl { uint8_t buf[]; // the actual data } buf_impl; +/******************************************************************** +** Base64 lib from https://github.com/Densaugeo/base64_arduino +** +********************************************************************/ + +/* binary_to_base64: + * Description: + * Converts a single byte from a binary value to the corresponding base64 character + * Parameters: + * v - Byte to convert + * Returns: + * ascii code of base64 character. If byte is >= 64, then there is not corresponding base64 character + * and 255 is returned + */ +static unsigned char binary_to_base64(unsigned char v); + +/* base64_to_binary: + * Description: + * Converts a single byte from a base64 character to the corresponding binary value + * Parameters: + * c - Base64 character (as ascii code) + * Returns: + * 6-bit binary value + */ +static unsigned char base64_to_binary(unsigned char c); + +/* encode_base64_length: + * Description: + * Calculates length of base64 string needed for a given number of binary bytes + * Parameters: + * input_length - Amount of binary data in bytes + * Returns: + * Number of base64 characters needed to encode input_length bytes of binary data + */ +static unsigned int encode_base64_length(unsigned int input_length); + +/* decode_base64_length: + * Description: + * Calculates number of bytes of binary data in a base64 string + * Parameters: + * input - Base64-encoded null-terminated string + * Returns: + * Number of bytes of binary data in input + */ +static unsigned int decode_base64_length(unsigned char input[]); + +/* encode_base64: + * Description: + * Converts an array of bytes to a base64 null-terminated string + * Parameters: + * input - Pointer to input data + * input_length - Number of bytes to read from input pointer + * output - Pointer to output string. Null terminator will be added automatically + * Returns: + * Length of encoded string in bytes (not including null terminator) + */ +static unsigned int encode_base64(unsigned char input[], unsigned int input_length, unsigned char output[]); + +/* decode_base64: + * Description: + * Converts a base64 null-terminated string to an array of bytes + * Parameters: + * input - Pointer to input string + * output - Pointer to output array + * Returns: + * Number of bytes in the decoded binary + */ +static unsigned int decode_base64(unsigned char input[], unsigned char output[]); + +static unsigned char binary_to_base64(unsigned char v) { + // Capital letters - 'A' is ascii 65 and base64 0 + if(v < 26) return v + 'A'; + + // Lowercase letters - 'a' is ascii 97 and base64 26 + if(v < 52) return v + 71; + + // Digits - '0' is ascii 48 and base64 52 + if(v < 62) return v - 4; + + // '+' is ascii 43 and base64 62 + if(v == 62) return '+'; + + // '/' is ascii 47 and base64 63 + if(v == 63) return '/'; + + return 64; +} + +static unsigned char base64_to_binary(unsigned char c) { + // Capital letters - 'A' is ascii 65 and base64 0 + if('A' <= c && c <= 'Z') return c - 'A'; + + // Lowercase letters - 'a' is ascii 97 and base64 26 + if('a' <= c && c <= 'z') return c - 71; + + // Digits - '0' is ascii 48 and base64 52 + if('0' <= c && c <= '9') return c + 4; + + // '+' is ascii 43 and base64 62 + if(c == '+') return 62; + + // '/' is ascii 47 and base64 63 + if(c == '/') return 63; + + return 255; +} + +static unsigned int encode_base64_length(unsigned int input_length) { + return (input_length + 2)/3*4; +} + +static unsigned int decode_base64_length(unsigned char input[]) { + unsigned char *start = input; + + while(base64_to_binary(input[0]) < 64) { + ++input; + } + + unsigned int input_length = input - start; + + unsigned int output_length = input_length/4*3; + + switch(input_length % 4) { + default: return output_length; + case 2: return output_length + 1; + case 3: return output_length + 2; + } +} + +static unsigned int encode_base64(unsigned char input[], unsigned int input_length, unsigned char output[]) { + unsigned int full_sets = input_length/3; + + // While there are still full sets of 24 bits... + for(unsigned int i = 0; i < full_sets; ++i) { + output[0] = binary_to_base64( input[0] >> 2); + output[1] = binary_to_base64((input[0] & 0x03) << 4 | input[1] >> 4); + output[2] = binary_to_base64((input[1] & 0x0F) << 2 | input[2] >> 6); + output[3] = binary_to_base64( input[2] & 0x3F); + + input += 3; + output += 4; + } + + switch(input_length % 3) { + case 0: + output[0] = '\0'; + break; + case 1: + output[0] = binary_to_base64( input[0] >> 2); + output[1] = binary_to_base64((input[0] & 0x03) << 4); + output[2] = '='; + output[3] = '='; + output[4] = '\0'; + break; + case 2: + output[0] = binary_to_base64( input[0] >> 2); + output[1] = binary_to_base64((input[0] & 0x03) << 4 | input[1] >> 4); + output[2] = binary_to_base64((input[1] & 0x0F) << 2); + output[3] = '='; + output[4] = '\0'; + break; + } + + return encode_base64_length(input_length); +} + +static unsigned int decode_base64(unsigned char input[], unsigned char output[]) { + unsigned int output_length = decode_base64_length(input); + + // While there are still full sets of 24 bits... + for(unsigned int i = 2; i < output_length; i += 3) { + output[0] = base64_to_binary(input[0]) << 2 | base64_to_binary(input[1]) >> 4; + output[1] = base64_to_binary(input[1]) << 4 | base64_to_binary(input[2]) >> 2; + output[2] = base64_to_binary(input[2]) << 6 | base64_to_binary(input[3]); + + input += 4; + output += 3; + } + + switch(output_length % 3) { + case 1: + output[0] = base64_to_binary(input[0]) << 2 | base64_to_binary(input[1]) >> 4; + break; + case 2: + output[0] = base64_to_binary(input[0]) << 2 | base64_to_binary(input[1]) >> 4; + output[1] = base64_to_binary(input[1]) << 4 | base64_to_binary(input[2]) >> 2; + break; + } + + return output_length; +} + /******************************************************************** ** Buffer low-level implementation ** @@ -343,13 +535,26 @@ static size_t tohex(char * out, size_t outsz, const uint8_t * in, size_t insz) { static int m_tostring(bvm *vm) { + int argc = be_top(vm); + int max_len = 32; /* limit to 32 bytes by default */ + int truncated = 0; + if (argc > 1 && be_isint(vm, 2)) { + max_len = be_toint(vm, 2); /* you can specify the len as second argument, or 0 for unlimited */ + } buf_impl * buf = bytes_check_data(vm, 0); size_t len = buf->len; - size_t hex_len = len * 2 + 5 + 2 + 2 + 1; /* reserve size for `bytes("")\0` - 9 chars */ + if (max_len > 0 && len > max_len) { + len = max_len; /* limit output size */ + truncated = 1; + } + size_t hex_len = len * 2 + 5 + 2 + 2 + 1 + truncated * 3; /* reserve size for `bytes("")\0` - 9 chars */ char * hex_out = be_pushbuffer(vm, hex_len); size_t l = be_strlcpy(hex_out, "bytes('", hex_len); - l += tohex(&hex_out[l], hex_len - l, buf_get_buf(buf), buf->len); + l += tohex(&hex_out[l], hex_len - l, buf_get_buf(buf), len); + if (truncated) { + l += be_strlcpy(&hex_out[l], "...", hex_len - l); + } l += be_strlcpy(&hex_out[l], "')", hex_len - l); be_pushnstring(vm, hex_out, l); /* make escape string from buffer */ @@ -699,6 +904,56 @@ static int m_nequal(bvm *vm) return bytes_equal(vm, bfalse); } +/* + * Converts bytes() to a base64 string + * + * Note: there are no line breaks inserted + * + * `b.tob64() -> string` + */ +static int m_tob64(bvm *vm) +{ + buf_impl * buf = bytes_check_data(vm, 0); + size_t len = buf->len; + size_t b64_len = encode_base64_length(len) + 1; /* size of base64 encoded string for this binary length, add NULL terminator */ + + char * b64_out = be_pushbuffer(vm, b64_len); + size_t converted = encode_base64(buf_get_buf(buf), len, b64_out); + + be_pushnstring(vm, b64_out, converted); /* make string from buffer */ + be_remove(vm, -2); /* remove buffer */ + be_return(vm); +} + +/* + * Converts base63 to bytes() + * + * `bytes().fromb64() -> bytes()` + */ +static int m_fromb64(bvm *vm) +{ + int argc = be_top(vm); + if (argc >= 2 && be_isstring(vm, 2)) { + const char *s = be_tostring(vm, 2); + size_t len = be_strlen(vm, 2); + size_t bin_len = decode_base64_length(s); /* do a first pass to calculate the buffer size */ + + buf_impl * buf = bytes_check_data(vm, 0); + buf = bytes_resize(vm, buf, bin_len); /* resize if needed */ + if (bin_len > buf->size) { /* avoid overflow */ + be_raise(vm, "memory_error", "cannot allocate buffer"); + } + + size_t bin_len_final = decode_base64(s, buf_get_buf(buf)); /* decode */ + buf->len = bin_len_final; + be_pop(vm, 1); /* remove arg to leave instance */ + be_return(vm); + } + be_raise(vm, "type_error", "operand must be a string"); + be_return_nil(vm); +} + + /* * Advanced API */ @@ -973,6 +1228,8 @@ void be_load_byteslib(bvm *vm) { "tostring", m_tostring }, { "asstring", m_asstring }, { "fromstring", m_fromstring }, + { "tob64", m_tob64 }, + { "fromb64", m_fromb64 }, { "add", m_add }, { "get", m_getu }, { "geti", m_geti }, @@ -1006,6 +1263,8 @@ class be_class_bytes (scope: global, name: bytes) { tostring, func(m_tostring) asstring, func(m_asstring) fromstring, func(m_fromstring) + tob64, func(m_tob64) + fromb64, func(m_fromb64) add, func(m_add) get, func(m_getu) geti, func(m_geti) 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 e4f238d5c..e9d0e4d9f 100644 --- a/lib/libesp32/Berry/src/be_code.c +++ b/lib/libesp32/Berry/src/be_code.c @@ -23,8 +23,8 @@ #define min(a, b) ((a) < (b) ? (a) : (b)) #define notexpr(e) isset((e)->not, NOT_EXPR) #define notmask(e) isset((e)->not, NOT_MASK) -#define exp2anyreg(f, e) exp2reg(f, e, (f)->freereg) -#define var2anyreg(f, e) var2reg(f, e, (f)->freereg) +#define exp2anyreg(f, e) exp2reg(f, e, -1) /* -1 means allocate a new register if needed */ +#define var2anyreg(f, e) var2reg(f, e, -1) /* -1 means allocate a new register if needed */ #define hasjump(e) ((e)->t != (e)->f || notexpr(e)) #define code_bool(f, r, b, j) codeABC(f, OP_LDBOOL, r, b, j) #define code_call(f, a, b) codeABC(f, OP_CALL, a, b, 0) @@ -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); } @@ -321,9 +335,38 @@ static void free_suffix(bfuncinfo *finfo, bexpdesc *e) } } +static int suffix_destreg(bfuncinfo *finfo, bexpdesc *e1, int dst) +{ + int cand_dst = dst; /* candidate for new dst */ + int nlocal = be_list_count(finfo->local); + int reg1 = (e1->v.ss.tt == ETREG) ? e1->v.ss.obj : -1; /* check if obj is ETREG or -1 */ + int reg2 = (!isK(e1->v.ss.idx) && e1->v.ss.idx >= nlocal) ? e1->v.ss.idx : -1; /* check if idx is ETREG or -1 */ + + if (reg1 >= 0 && reg2 >= 0) { + /* both are ETREG, we keep the lowest and discard the other */ + if (reg1 != reg2) { + cand_dst = min(reg1, reg2); + be_code_freeregs(finfo, 1); /* and free the other one */ + } else { + cand_dst = reg1; /* both ETREG are equal, we return its value */ + } + } else if (reg1 >= 0) { + cand_dst = reg1; + } else if (reg2 >= 0) { + cand_dst = reg2; + } else { + // dst unchanged + } + + if (dst >= finfo->freereg) { + dst = cand_dst; /* if dst was allocating a new register, use the more precise candidate */ + } + return dst; +} + static int code_suffix(bfuncinfo *finfo, bopcode op, bexpdesc *e, int dst) { - free_suffix(finfo, e); /* free temporary registers */ + dst = suffix_destreg(finfo, e, dst); if (dst > finfo->freereg) { dst = finfo->freereg; } @@ -339,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 */ @@ -349,8 +395,14 @@ 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 */ + dst = finfo->freereg; + } be_assert(e != NULL); switch (e->type) { case ETINT: @@ -403,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); @@ -420,31 +472,47 @@ static int exp2reg(bfuncinfo *finfo, bexpdesc *e, int dst) return reg; } -static int codedestreg(bfuncinfo *finfo, bexpdesc *e1, bexpdesc *e2) +/* 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 dst, con1 = e1->type == ETREG, con2 = e2->type == ETREG; + int cand_dst = dst; + int con1 = e1->type == ETREG, con2 = e2->type == ETREG; if (con1 && con2) { - dst = min(e1->v.idx, e2->v.idx); + cand_dst = min(e1->v.idx, e2->v.idx); be_code_freeregs(finfo, 1); } else if (con1) { - dst = e1->v.idx; + cand_dst = e1->v.idx; } else if (con2) { - dst = e2->v.idx; + cand_dst = e2->v.idx; } else { - dst = be_code_allocregs(finfo, 1); + if (dst >= finfo->freereg) { + cand_dst = be_code_allocregs(finfo, 1); + return cand_dst; + } + } + if (dst >= finfo->freereg) { + return cand_dst; + } else { + return dst; } - return dst; } -static void binaryexp(bfuncinfo *finfo, bopcode op, bexpdesc *e1, bexpdesc *e2) +/* 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) { - int src1 = exp2anyreg(finfo, e1); + if (dst < 0) { dst = finfo->freereg; } + int src1 = exp2reg(finfo, e1, dst); /* potentially force the target for src1 reg */ int src2 = exp2anyreg(finfo, e2); - int dst = codedestreg(finfo, e1, 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) @@ -462,7 +530,8 @@ void be_code_prebinop(bfuncinfo *finfo, int op, bexpdesc *e) } } -void be_code_binop(bfuncinfo *finfo, int op, bexpdesc *e1, bexpdesc *e2) +/* 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) { case OptAnd: @@ -480,12 +549,14 @@ void be_code_binop(bfuncinfo *finfo, int op, bexpdesc *e1, bexpdesc *e2) case OptNE: case OptGT: case OptGE: case OptConnect: case OptBitAnd: case OptBitOr: case OptBitXor: case OptShiftL: case OptShiftR: - binaryexp(finfo, (bopcode)(op - OptAdd), e1, e2); + binaryexp(finfo, (bopcode)(op - OptAdd), e1, e2, dst); break; default: break; } } +/* 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); @@ -495,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) { @@ -514,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) { @@ -527,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) { @@ -539,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) { @@ -583,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 : finfo->freereg); + 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: @@ -618,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; @@ -641,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); @@ -717,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); @@ -730,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); @@ -746,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) @@ -766,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); @@ -799,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) { @@ -806,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_code.h b/lib/libesp32/Berry/src/be_code.h index f7fdb2d55..54d0c317c 100644 --- a/lib/libesp32/Berry/src/be_code.h +++ b/lib/libesp32/Berry/src/be_code.h @@ -14,7 +14,7 @@ int be_code_allocregs(bfuncinfo *finfo, int count); void be_code_prebinop(bfuncinfo *finfo, int op, bexpdesc *e); -void be_code_binop(bfuncinfo *finfo, int op, bexpdesc *e1, bexpdesc *e2); +void be_code_binop(bfuncinfo *finfo, int op, bexpdesc *e1, bexpdesc *e2, int dst); int be_code_unop(bfuncinfo *finfo, int op, bexpdesc *e); int be_code_setvar(bfuncinfo *finfo, bexpdesc *e1, bexpdesc *e2); int be_code_nextreg(bfuncinfo *finfo, bexpdesc *e); 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 c19f83e9a..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); - 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; @@ -823,9 +874,11 @@ static void suffix_alloc_reg(bparser *parser, bexpdesc *l) /* compound assignment */ static void compound_assign(bparser *parser, int op, bexpdesc *l, bexpdesc *r) { + int dst = -1; /* destination register in case of compound assignment */ if (op != OptAssign) { /* check left variable */ check_var(parser, l); /* cache the register of the object when continuously assigning */ + dst = parser->finfo->freereg; suffix_alloc_reg(parser, l); } expr(parser, r); /* right expression */ @@ -834,20 +887,37 @@ static void compound_assign(bparser *parser, int op, bexpdesc *l, bexpdesc *r) bexpdesc e = *l; op = op < OptAndAssign ? op - OptAddAssign + OptAdd : op - OptAndAssign + OptBitAnd; - be_code_binop(parser->finfo, op, &e, r); /* coding operation */ + be_code_binop(parser->finfo, op, &e, r, dst); /* coding operation */ *r = e; } } +/* 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; } @@ -913,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); - be_code_binop(finfo, op, e, &e2); /* encode binary op */ - op = get_binop(parser); + 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); /* 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< 995326: - print("\u001b[31;1m!!! Tasmota firmware size is too big with {} bytes. Max size is 995326 bytes !!! \u001b[0m".format(ORG_FIRMWARE_SIZE)) + print( + "\u001b[31;1m!!! Tasmota firmware size is too big with {} bytes. Max size is 995326 bytes !!! \u001b[0m".format( + ORG_FIRMWARE_SIZE + ) + ) else: - print("Compression reduced firmware size by {:.0f}% (was {} bytes, now {} bytes)".format((GZ_FIRMWARE_SIZE / ORG_FIRMWARE_SIZE) * 100, ORG_FIRMWARE_SIZE, GZ_FIRMWARE_SIZE)) + print( + "Compression reduced firmware size by {:.0f}% (was {} bytes, now {} bytes)".format( + (GZ_FIRMWARE_SIZE / ORG_FIRMWARE_SIZE) * 100, + ORG_FIRMWARE_SIZE, + GZ_FIRMWARE_SIZE, + ) + ) - env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", [bin_gzip]) + if not tasmotapiolib.is_env_set(tasmotapiolib.DISABLE_BIN_GZ, env): + env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", [bin_gzip]) diff --git a/pio-tools/name-firmware.py b/pio-tools/name-firmware.py index ce6ee0339..b49def236 100644 --- a/pio-tools/name-firmware.py +++ b/pio-tools/name-firmware.py @@ -1,54 +1,27 @@ -Import('env') -Import('projenv') +Import("env") +Import("projenv") import os import shutil +import pathlib + +import tasmotapiolib -OUTPUT_DIR = "build_output{}".format(os.path.sep) def bin_map_copy(source, target, env): - variant = str(target[0]).split(os.path.sep)[2] + firsttarget = pathlib.Path(target[0].path) - # check if output directories exist and create if necessary - if not os.path.isdir(OUTPUT_DIR): - os.mkdir(OUTPUT_DIR) - - for d in ['firmware', 'map']: - if not os.path.isdir("{}{}".format(OUTPUT_DIR, d)): - os.mkdir("{}{}".format(OUTPUT_DIR, d)) - - # create string with location and file names based on variant - map_file = "{}map{}{}.map".format(OUTPUT_DIR, os.path.sep, variant) - bin_file = "{}firmware{}{}.bin".format(OUTPUT_DIR, os.path.sep, variant) + # get locations and file names based on variant + map_file = tasmotapiolib.get_final_map_path(env) + bin_file = tasmotapiolib.get_final_bin_path(env) # check if new target files exist and remove if necessary for f in [map_file, bin_file]: - if os.path.isfile(f): - os.remove(f) + if f.is_file(): + f.unlink() - # copy firmware.bin to firmware/.bin - shutil.copy(str(target[0]), bin_file) + # copy firmware.bin and map to final destination + shutil.copy(firsttarget, bin_file) + shutil.move(tasmotapiolib.get_source_map_path(env), map_file) - # move firmware.map to map/.map - if os.path.isfile("firmware.map"): - shutil.move("firmware.map", map_file) - #map_new_loc = str(target[0]).split(os.path.sep)[0] + os.path.sep + str(target[0]).split(os.path.sep)[1] + os.path.sep + str(target[0]).split(os.path.sep)[2] + os.path.sep + "Tasmota.map" - # PIO env variables see: https://github.com/platformio/platformio-core/blob/develop/platformio/builder/main.py#L108:L128 - proj_build_dir = env["PROJECT_BUILD_DIR"] - #build_dir = env["BUILD_DIR"] - pio_env = env["PIOENV"] - proj_dir = env["PROJECT_DIR"] - map_name = str(proj_dir).split(os.path.sep)[-1] - map_new_loc = proj_build_dir + os.path.sep + pio_env + os.path.sep + map_name + ".map" - #print("proj_build_dir: {}".format(proj_build_dir)) - #print("pioenv: {}".format(pio_env)) - #print("build_dir: {}".format(build_dir)) - #print("map_name: {}".format(map_name)) - #print("proj_dir: {}".format(proj_dir)) - #print("map_new_loc: {}".format(map_new_loc)) - - # move Tasmota.map to map/.map - if os.path.isfile(map_new_loc): - shutil.move(map_new_loc, map_file) - -env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", [bin_map_copy]) +env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", bin_map_copy) diff --git a/pio-tools/tasmotapiolib.py b/pio-tools/tasmotapiolib.py new file mode 100644 index 000000000..866be0336 --- /dev/null +++ b/pio-tools/tasmotapiolib.py @@ -0,0 +1,121 @@ +"""Supporting library for pio-tools scripts + +This also provides functions to allow overrides of some settings, see the available +overides below. + +Overrides can be set using environment variables or .ini file settings for controlling +build output file locations and formats. + +To set a value using an environment variable, prefix the value with "TASMOTA_" and +ensure the entire value is UPPER CASE, for example in bash, it would be: + + export TASMOTA_DISABLE_MAP_GZ=1 + +To set a value in your .ini file, such as in platformio_override.ini, create a +[tasmota] section, and put the key ensuring it is all lower case, for example: + +[tasmota] +disable_map_gz = 1 +map_dir = /tmp/map_files/ + +Values in .ini files override environment variables + +""" +import pathlib +import os + +# === AVAILABLE OVERRIDES === +# if set to 1, will not gzip esp8266 bin files +DISABLE_BIN_GZ = "disable_bin_gz" +# if set, an alternative ptah to put generated .bin files, relative to project directory +BIN_DIR = "bin_dir" +# if set to 1, will not gzip generated .map files +DISABLE_MAP_GZ = "disable_map_gz" +# if set, an alternative path to put generated .map files, relative to project directory +MAP_DIR = "map_dir" + +# === END AVAILABLE OVERRIDES === + + +# This is the default output directory +OUTPUT_DIR = pathlib.Path("build_output") + + +def get_variant(env) -> str: + """Get the current build variant.""" + return env["PIOENV"] + + +def get_final_bin_path(env) -> pathlib.Path: + """Path to the final destination for the .bin + + If the parent directory does not exist, it will be created""" + firmware_dir = get_override_path(BIN_DIR, env) + firmware_dir.mkdir(parents=True, exist_ok=True) + return firmware_dir / "{}.bin".format(get_variant(env)) + + +def get_final_map_path(env) -> pathlib.Path: + """Path to the final destination for the .map file + + If the parent directory does not exist, it will be created""" + map_dir = get_override_path(MAP_DIR, env) + map_dir.mkdir(parents=True, exist_ok=True) + return map_dir / "{}.map".format(get_variant(env)) + + +def get_source_map_path(env) -> pathlib.Path: + """Path to the built .map file. + + Tests potential locations, returning the first match. + Raises FileNotFoundError if no match found""" + fwmap_path = pathlib.Path("firmware.map") + if fwmap_path.is_file(): + return fwmap_path + + # firmware maybe in project build directory + # PIO env variables see: https://github.com/platformio/platformio-core/blob/develop/platformio/builder/main.py#L108:L128 + proj_build_dir = pathlib.Path(env["PROJECT_BUILD_DIR"]) + proj_dir = pathlib.Path(env["PROJECT_DIR"]) + map_name = proj_dir.parts[-1] + ".map" + fwmap_path = proj_build_dir / get_variant(env) / map_name + + if fwmap_path.is_file(): + return fwmap_path + + raise FileNotFoundError + + +def get_tasmota_override_option(name: str, env): + """Gets a set override option from a .ini or env variable, None if no match""" + config = env.GetProjectConfig() + override = config.get("tasmota", name.lower(), None) + if override is not None: + return override + # Return env if available + return os.environ.get("TASMOTA_" + name.upper()) + + +def get_override_path(pathtype: str, env) -> pathlib.Path: + """ + Returns a path to a givens override path if set, otherwise OUTPUT_DIR is used + + pathtype must be either MAP_DIR or BIN_DIR. + """ + override = get_tasmota_override_option(pathtype, env) + if override: + return pathlib.Path(override) + if pathtype == BIN_DIR: + return OUTPUT_DIR / "firmware" + elif pathtype == MAP_DIR: + return OUTPUT_DIR / "map" + raise ValueError + + +def is_env_set(name: str, env): + """True if the enviornment variable is set to `1`""" + val = get_tasmota_override_option(name, env) + if val: + val = val.strip() + return val == "1" + return False diff --git a/platformio_tasmota_cenv_sample.ini b/platformio_tasmota_cenv_sample.ini index 4b47e9383..05f221c56 100644 --- a/platformio_tasmota_cenv_sample.ini +++ b/platformio_tasmota_cenv_sample.ini @@ -16,6 +16,17 @@ build_flags = ${env.build_flags} -D PIO_FRAMEWORK_ARDUINO_MMU_CACHE16_IRAM48_SECHEAP_SHARED -Wno-switch-unreachable +; Settings in [tasmota] do NOT affect firmware building +[tasmota] +; Uncomment next line if you do NOT want gzipped map file(s) +;disable_map_gz = 1 +; Uncomment and specify a folder where to place the map file(s) (default set to folder build_output) +;map_dir = /tmp/map_files/ +; Uncomment next line if you do NOT want additionally gzipped firmware file(s) +;disable_bin_gz = 1 +; Uncomment and specify a folder where to place the firmware file(s) (default set to folder build_output) +;bin_dir = /tmp/bin_files/ + [env:tasmota-rangeextender] build_flags = ${env.build_flags} -D FIRMWARE_RANGE_EXTENDER diff --git a/tasmota/berry/examples/lvgl_demo_M5stack_black.be b/tasmota/berry/examples/lvgl_demo_M5stack_black.be deleted file mode 100644 index 8cbe449d9..000000000 --- a/tasmota/berry/examples/lvgl_demo_M5stack_black.be +++ /dev/null @@ -1,219 +0,0 @@ -#- - - Demo for M5Stack black edition, using ILI9341 - - - -# - -if gpio.pin_used(gpio.ILI9341_CS) && gpio.pin_used(gpio.ILI9341_DC) && gpio.pin_used(gpio.OLED_RESET) - - lv.start(udisplay.ILI9341_M5Stack_Core) - - hres = lv.get_hor_res() - vres = lv.get_ver_res() - - scr = lv.scr_act() - scr.set_style_local_bg_color(lv.OBJ_PART_MAIN, lv.STATE_DEFAULT, lv_color(lv.RED)) - scr.set_style_local_bg_opa(lv.OBJ_PART_MAIN, lv.STATE_DEFAULT, lv.OPA_COVER) - - scr.set_style_local_bg_color(lv.OBJ_PART_MAIN, lv.STATE_DEFAULT, lv_color(0xFFA000)) - scr.set_style_local_bg_grad_color(lv.OBJ_PART_MAIN, lv.STATE_DEFAULT, lv_color(0xFF4000)) - scr.set_style_local_bg_color(lv.OBJ_PART_MAIN, lv.STATE_DEFAULT, lv_color(0x000080)) - scr.set_style_local_bg_grad_color(lv.OBJ_PART_MAIN, lv.STATE_DEFAULT, lv_color(0x000030)) - scr.set_style_local_bg_grad_dir(lv.OBJ_PART_MAIN, lv.STATE_DEFAULT, lv.GRAD_DIR_VER) - - #- logo = lv.img_create(scr) - logo.set_src("A:/Sunrise320.bin") - logo.align(0,0,0,0) -# - - btn = lv_btn(scr) - btn.set_pos(40,40) - btn.set_size(180, 45) - #btn.set_style_local_bg_color(lv.OBJ_PART_MAIN, lv.STATE_DEFAULT, lv_color(0x1fa3ec)) - - - label = lv_label(btn) - label.set_text(lv.SYMBOL_HOME + "Tasmota") - #label.set_style_local_text_color(lv.OBJ_PART_MAIN, lv.STATE_DEFAULT, lv_color(0xFFFFFF)) - #f28 = lv.montserrat_font(28) - #if f28 != nil label.set_style_local_text_font(lv.OBJ_PART_MAIN, lv.STATE_DEFAULT, f28) end - btn.align(0, lv.ALIGN_CENTER, 0, 0) - - - log = lv_label(scr) - f20 = lv.montserrat_font(20) - if f20 != nil log.set_style_local_text_font(lv.OBJ_PART_MAIN, lv.STATE_DEFAULT, f20) end - #- log.set_long_mode(lv.LABEL_LONG_DOT) -# - log.set_long_mode(lv.LABEL_LONG_SROLL) - log.set_width(hres) - log.set_align(lv.LABEL_ALIGN_LEFT) - log.set_style_local_bg_color(lv.OBJ_PART_MAIN, lv.STATE_DEFAULT, lv_color(0xD00000)) - log.set_style_local_bg_opa(lv.OBJ_PART_MAIN, lv.STATE_DEFAULT, lv.OPA_COVER) - log.set_style_local_text_color(lv.OBJ_PART_MAIN, lv.STATE_DEFAULT, lv_color(0xFFFFFF)) - log.set_text("Welcome to Tasmota - long text rolling") - log.set_text("Welcome to Tasmota") - - tastyle = lv_style() - - tastyle.set_bg_color(lv.STATE_DEFAULT, lv_color(0x1fa3ec)) - tastyle.set_text_color(lv.STATE_DEFAULT, lv_color(0xFFFFFF)) - tastyle.set_radius(lv.STATE_DEFAULT, 10) - tastyle.set_bg_opa(lv.STATE_DEFAULT, lv.OPA_COVER) - f20 = lv.montserrat_font(20) - if f20 != nil tastyle.set_text_font(lv.STATE_DEFAULT, f20) end - - btn.add_style(lv.OBJ_PART_MAIN, tastyle) - - prev_btn = lv_btn(scr) - prev_btn.set_pos(20,vres-40) - prev_btn.set_size(80, 30) - prev_btn.add_style(lv.OBJ_PART_MAIN, tastyle) - prev_label = lv_label(prev_btn) - # prev_label.set_text("Prev") - prev_label.set_text("<") - - next_btn = lv_btn(scr) - next_btn.set_pos(220,vres-40) - next_btn.set_size(80, 30) - next_btn.add_style(lv.OBJ_PART_MAIN, tastyle) - next_label = lv_label(next_btn) - #next_label.set_text("Next") - next_label.set_text(">") - - 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/support.ino b/tasmota/support.ino index 2e17c7915..22ff91854 100644 --- a/tasmota/support.ino +++ b/tasmota/support.ino @@ -316,7 +316,7 @@ float CharToFloat(const char *str) char *pt = strbuf; if (*pt == '\0') { return 0.0; } - while ((*pt != '\0') && isblank(*pt)) { pt++; } // Trim leading spaces + while ((*pt != '\0') && isspace(*pt)) { pt++; } // Trim leading spaces signed char sign = 1; if (*pt == '-') { sign = -1; } @@ -524,17 +524,18 @@ bool StrCaseStr_P(const char* source, const char* search) { } bool IsNumeric(const char* value) { + // Test for characters '-.0123456789' char *digit = (char*)value; while (isdigit(*digit) || *digit == '.' || *digit == '-') { digit++; } return (*digit == '\0'); } -char* Trim(char* p) -{ +char* Trim(char* p) { + // Remove leading and trailing tab, \n, \v, \f, \r and space if (*p != '\0') { - while ((*p != '\0') && isblank(*p)) { p++; } // Trim leading spaces + while ((*p != '\0') && isspace(*p)) { p++; } // Trim leading spaces char* q = p + strlen(p) -1; - while ((q >= p) && isblank(*q)) { q--; } // Trim trailing spaces + while ((q >= p) && isspace(*q)) { q--; } // Trim trailing spaces q++; *q = '\0'; } diff --git a/tasmota/xdrv_01_webserver.ino b/tasmota/xdrv_01_webserver.ino index 04a403b3f..27f8bd981 100644 --- a/tasmota/xdrv_01_webserver.ino +++ b/tasmota/xdrv_01_webserver.ino @@ -666,7 +666,7 @@ bool HttpCheckPriviledgedAccess(bool autorequestauth = true) return true; } } - AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP "Referer denied")); + AddLog(LOG_LEVEL_INFO, PSTR(D_LOG_HTTP "Referer denied. Use 'SO128 1' for HTTP API commands. 'Webpassword' is recommended.")); return false; } else { return true; diff --git a/tasmota/xdrv_02_9_mqtt.ino b/tasmota/xdrv_02_9_mqtt.ino index 78f7c88c1..0277bbc46 100644 --- a/tasmota/xdrv_02_9_mqtt.ino +++ b/tasmota/xdrv_02_9_mqtt.ino @@ -197,8 +197,8 @@ void MqttInit(void) { Settings->mqtt_port = 8883; #endif //USE_MQTT_AZURE_IOT #ifdef USE_MQTT_TLS - if ((8883 == Settings->mqtt_port) || (8884 == Settings->mqtt_port)) { - // Turn on TLS for port 8883 (TLS) and 8884 (TLS, client certificate) + if ((8883 == Settings->mqtt_port) || (8884 == Settings->mqtt_port) || (443 == Settings->mqtt_port)) { + // Turn on TLS for port 8883 (TLS), 8884 (TLS, client certificate), 443 (TLS, user/password) Settings->flag4.mqtt_tls = true; } Mqtt.mqtt_tls = Settings->flag4.mqtt_tls; // this flag should not change even if we change the SetOption (until reboot) diff --git a/tasmota/xdrv_10_rules.ino b/tasmota/xdrv_10_rules.ino index d2f323760..a4d454040 100644 --- a/tasmota/xdrv_10_rules.ino +++ b/tasmota/xdrv_10_rules.ino @@ -801,6 +801,11 @@ bool RuleSetProcess(uint8_t rule_set, String &event_saved) bool RulesProcessEvent(const char *json_event) { +#ifdef USE_BERRY + // events are passed to Berry before Rules engine + callBerryRule(json_event); +#endif + if (Rules.busy) { return false; } Rules.busy = true; @@ -858,7 +863,11 @@ void RulesInit(void) void RulesEvery50ms(void) { +#ifdef USE_BERRY + if (!Rules.busy) { // Emitting Rules events is always enabled with Berry +#else if (Settings->rule_enabled && !Rules.busy) { // Any rule enabled +#endif char json_event[120]; if (-1 == Rules.new_power) { Rules.new_power = TasmotaGlobal.power; } diff --git a/tasmota/xdrv_52_9_berry.ino b/tasmota/xdrv_52_9_berry.ino index 47f31c34a..6095a7750 100644 --- a/tasmota/xdrv_52_9_berry.ino +++ b/tasmota/xdrv_52_9_berry.ino @@ -93,12 +93,13 @@ extern "C" { \*********************************************************************************************/ // // call a function (if exists) of type void -> void -bool callBerryRule(void) { +// If event == nullptr, then take XdrvMailbox.data +bool callBerryRule(const char *event) { if (berry.rules_busy) { return false; } berry.rules_busy = true; char * json_event = XdrvMailbox.data; bool serviced = false; - serviced = callBerryEventDispatcher(PSTR("rule"), nullptr, 0, XdrvMailbox.data); + serviced = callBerryEventDispatcher(PSTR("rule"), nullptr, 0, event ? event : XdrvMailbox.data); berry.rules_busy = false; return serviced; // TODO event not handled } @@ -277,7 +278,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 @@ -731,7 +733,7 @@ bool Xdrv52(uint8_t function) // Berry wide commands and events case FUNC_RULES_PROCESS: - result = callBerryRule(); + result = callBerryRule(nullptr); break; case FUNC_MQTT_DATA: result = callBerryEventDispatcher(PSTR("mqtt_data"), XdrvMailbox.topic, 0, XdrvMailbox.data, XdrvMailbox.data_len); diff --git a/tasmota/xdrv_59_influxdb.ino b/tasmota/xdrv_59_influxdb.ino index 9bc7f5510..ecafe8003 100644 --- a/tasmota/xdrv_59_influxdb.ino +++ b/tasmota/xdrv_59_influxdb.ino @@ -84,10 +84,11 @@ struct { String _serverUrl; // Connection info String _writeUrl; // Cached full write url String _lastErrorResponse; // Server reponse or library error message for last failed request - uint32_t _lastRequestTime = 0; // Last time in ms we made are a request to server + uint32_t _lastRequestTime = 0; // Last time in ms we made a request to server int interval = 0; int _lastStatusCode = 0; // HTTP status code of last request to server int _lastRetryAfter = 0; // Store retry timeout suggested by server after last request + uint8_t log_level = LOG_LEVEL_DEBUG_MORE; bool _connectionReuse; // true if HTTP connection should be kept open. Usable for frequent writes. Default false bool init = false; } IFDB; @@ -161,7 +162,7 @@ void InfluxDbAfterRequest(int expectedStatusCode, bool modifyLastConnStatus) { IFDB._lastRequestTime = millis(); // AddLog(LOG_LEVEL_DEBUG, PSTR("IFX: HTTP status code %d"), IFDB._lastStatusCode); IFDB._lastRetryAfter = 0; - if (IFDB._lastStatusCode >= 429) { //retryable server errors + if (IFDB._lastStatusCode >= 429) { // Retryable server errors if (IFDBhttpClient->hasHeader(RetryAfter)) { IFDB._lastRetryAfter = IFDBhttpClient->header(RetryAfter).toInt(); AddLog(LOG_LEVEL_DEBUG, PSTR("IFX: Reply after %d"), IFDB._lastRetryAfter); @@ -171,12 +172,12 @@ void InfluxDbAfterRequest(int expectedStatusCode, bool modifyLastConnStatus) { IFDB._lastErrorResponse = ""; if (IFDB._lastStatusCode != expectedStatusCode) { if (IFDB._lastStatusCode > 0) { - IFDB._lastErrorResponse = IFDBhttpClient->getString(); - AddLog(LOG_LEVEL_INFO, PSTR("IFX: %s"), IFDB._lastErrorResponse.c_str()); // {"error":"database not found: \"db\""} + IFDB._lastErrorResponse = IFDBhttpClient->getString(); // {"error":"database not found: \"db\""}\n } else { IFDB._lastErrorResponse = IFDBhttpClient->errorToString(IFDB._lastStatusCode); - AddLog(LOG_LEVEL_INFO, PSTR("IFX: Error %s"), IFDB._lastErrorResponse.c_str()); } + IFDB._lastErrorResponse.trim(); // Remove trailing \n + AddLog(LOG_LEVEL_INFO, PSTR("IFX: Error %s"), IFDB._lastErrorResponse.c_str()); } } @@ -191,8 +192,6 @@ bool InfluxDbValidateConnection(void) { if (1 == Settings->influxdb_version) { url += InfluxDbAuth(); } - // on version 1.8.9 /health works fine -// String url = IFDB._serverUrl + "/health"; AddLog(LOG_LEVEL_INFO, PSTR("IFX: Validating connection to %s"), url.c_str()); if (!IFDBhttpClient->begin(*IFDBwifiClient, url)) { @@ -221,7 +220,8 @@ int InfluxDbPostData(const char *data) { return false; } - AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("IFX: Sending\n%s"), data); + Trim((char*)data); // Remove trailing \n + AddLog(IFDB.log_level, PSTR("IFX: Sending\n%s"), data); IFDBhttpClient->addHeader(F("Content-Type"), F("text/plain")); InfluxDbBeforeRequest(); IFDB._lastStatusCode = IFDBhttpClient->POST((uint8_t*)data, strlen(data)); @@ -235,19 +235,22 @@ int InfluxDbPostData(const char *data) { * Data preparation \*********************************************************************************************/ -char* InfluxDbNumber(char* alternative, const char* source) { - // Test for valid numeric data ('-.0123456789') or ON, OFF etc. as defined in kOptions - if (source != nullptr) { - char* out = (char*)source; - // Convert special text as found in kOptions to a number - // Like "OFF" -> 0, "ON" -> 1, "TOGGLE" -> 2 - int number = GetStateNumber(source); - if (number >= 0) { - itoa(number, alternative, 10); - out = alternative; - } - if (IsNumeric(out)) { - return out; +char* InfluxDbNumber(char* alternative, JsonParserToken value) { + if (value.isValid()) { + char* source = (char*)value.getStr(); + // Test for valid numeric data ('-.0123456789') or ON, OFF etc. as defined in kOptions + if (source != nullptr) { + char* out = source; + // Convert special text as found in kOptions to a number + // Like "OFF" -> 0, "ON" -> 1, "TOGGLE" -> 2 + int number = GetStateNumber(source); + if (number >= 0) { + itoa(number, alternative, 10); + out = alternative; + } + if (IsNumeric(out)) { + return out; + } } } return nullptr; @@ -256,10 +259,12 @@ char* InfluxDbNumber(char* alternative, const char* source) { void InfluxDbProcessJson(void) { if (!IFDB.init) { return; } -// AddLog(LOG_LEVEL_DEBUG, PSTR("IFX: JSON %s"), ResponseData()); + AddLog(IFDB.log_level, PSTR("IFX: Process %s"), ResponseData()); + +// String jsonStr = ResponseData(); // Make a copy before use +// JsonParser parser((char *)jsonStr.c_str()); + JsonParser parser((char *)ResponseData()); // Destroys ResponseData but saves heap space - String jsonStr = ResponseData(); - JsonParser parser((char *)jsonStr.c_str()); JsonParserObject root = parser.getRootObject(); if (root) { char number[12]; // '1' to '255' @@ -280,8 +285,8 @@ void InfluxDbProcessJson(void) { if (value2.isObject()) { JsonParserObject Object3 = value2.getObject(); for (auto key3 : Object3) { - const char* value = InfluxDbNumber(number, key3.getValue().getStr()); - if (value != nullptr) { + char* value = InfluxDbNumber(number, key3.getValue()); + if ((value != nullptr) && key2.isValid() && key3.isValid()) { // Level 3 LowerCase(sensor, key2.getStr()); LowerCase(type, key3.getStr()); @@ -294,44 +299,47 @@ void InfluxDbProcessJson(void) { } else { // Level 2 // { ... "ANALOG":{"Temperature":184.72},"DS18B20":{"Id":"01144A0CB2AA","Temperature":24.88},"HTU21":{"Temperature":25.32,"Humidity":49.2,"DewPoint":13.88},"Global":{"Temperature":24.88,"Humidity":49.2,"DewPoint":13.47}, ... } - bool isarray = value2.isArray(); - const char* value = InfluxDbNumber(number, (isarray) ? (value2.getArray())[0].getStr() : value2.getStr()); - if (value != nullptr) { + if (!key1.isValid() || !value2.isValid()) { continue; } + LowerCase(type, key2.getStr()); + bool is_id = (!strcmp_P(type, PSTR("id"))); // Index for DS18B20 + bool is_array = value2.isArray(); + char* value = nullptr; + if (is_id && !is_array) { + snprintf_P(sensor_id, sizeof(sensor_id), PSTR(",id=%s"), value2.getStr()); + } else { + value = InfluxDbNumber(number, (is_array) ? (value2.getArray())[0] : value2); + } + if ((value != nullptr) && key2.isValid()) { LowerCase(sensor, key1.getStr()); - LowerCase(type, key2.getStr()); // AddLog(LOG_LEVEL_DEBUG, PSTR("IFX2: sensor %s (%s), type %s (%s)"), key1.getStr(), sensor, key2.getStr(), type); - if (strcmp(type, "id") == 0) { // Index for DS18B20 - snprintf_P(sensor_id, sizeof(sensor_id), PSTR(",id=%s"), value); - } else { - if (isarray) { - JsonParserArray arr = value2.getArray(); - uint32_t i = 0; - for (auto val : arr) { - i++; - // power1,device=shelly25,sensor=energy value=0.00 - // power2,device=shelly25,sensor=energy value=4.12 - snprintf_P(linebuf, sizeof(linebuf), PSTR("%s%d,device=%s,sensor=%s%s value=%s\n"), - type, i, TasmotaGlobal.mqtt_topic, sensor, sensor_id, val.getStr()); - data += linebuf; - } - } else { - // temperature,device=demo,sensor=ds18b20,id=01144A0CB2AA value=22.63 - snprintf_P(linebuf, sizeof(linebuf), PSTR("%s,device=%s,sensor=%s%s value=%s\n"), - type, TasmotaGlobal.mqtt_topic, sensor, sensor_id, value); + if (is_array) { + JsonParserArray arr = value2.getArray(); + uint32_t i = 0; + for (auto val : arr) { + i++; + // power1,device=shelly25,sensor=energy value=0.00 + // power2,device=shelly25,sensor=energy value=4.12 + snprintf_P(linebuf, sizeof(linebuf), PSTR("%s%d,device=%s,sensor=%s%s value=%s\n"), + type, i, TasmotaGlobal.mqtt_topic, sensor, sensor_id, val.getStr()); data += linebuf; } - sensor_id[0] = '\0'; + } else { + // temperature,device=demo,sensor=ds18b20,id=01144A0CB2AA value=22.63 + snprintf_P(linebuf, sizeof(linebuf), PSTR("%s,device=%s,sensor=%s%s value=%s\n"), + type, TasmotaGlobal.mqtt_topic, sensor, sensor_id, value); + data += linebuf; } + sensor_id[0] = '\0'; } } } } else { // Level 1 // {"Time":"2021-08-13T14:15:56","Switch1":"ON","Switch2":"OFF", ... "TempUnit":"C"} - const char* value = InfluxDbNumber(number, value1.getStr()); - if (value != nullptr) { + char* value = InfluxDbNumber(number, value1); + if ((value != nullptr) && key1.isValid()) { LowerCase(type, key1.getStr()); // switch1,device=demo,sensor=device value=0 // power1,device=demo,sensor=device value=1 @@ -391,6 +399,7 @@ void InfluxDbLoop(void) { \*********************************************************************************************/ #define D_PRFX_INFLUXDB "Ifx" +#define D_CMND_INFLUXDBLOG "Log" #define D_CMND_INFLUXDBHOST "Host" #define D_CMND_INFLUXDBPORT "Port" #define D_CMND_INFLUXDBUSER "User" @@ -401,14 +410,14 @@ void InfluxDbLoop(void) { #define D_CMND_INFLUXDBBUCKET "Bucket" const char kInfluxDbCommands[] PROGMEM = D_PRFX_INFLUXDB "|" // Prefix - "|" + "|" D_CMND_INFLUXDBLOG "|" D_CMND_INFLUXDBHOST "|" D_CMND_INFLUXDBPORT "|" D_CMND_INFLUXDBUSER "|" D_CMND_INFLUXDBORG "|" D_CMND_INFLUXDBPASSWORD "|" D_CMND_INFLUXDBTOKEN "|" D_CMND_INFLUXDBDATABASE "|" D_CMND_INFLUXDBBUCKET; void (* const InfluxCommand[])(void) PROGMEM = { - &CmndInfluxDbState, + &CmndInfluxDbState, &CmndInfluxDbLog, &CmndInfluxDbHost, &CmndInfluxDbPort, &CmndInfluxDbUser, &CmndInfluxDbUser, &CmndInfluxDbPassword, &CmndInfluxDbPassword, @@ -437,6 +446,13 @@ void CmndInfluxDbState(void) { } } +void CmndInfluxDbLog(void) { + if ((XdrvMailbox.payload >= LOG_LEVEL_NONE) && (XdrvMailbox.payload <= LOG_LEVEL_DEBUG_MORE)) { + IFDB.log_level = XdrvMailbox.payload; + } + ResponseCmndNumber(IFDB.log_level); +} + void CmndInfluxDbHost(void) { if (XdrvMailbox.data_len > 0) { SettingsUpdateText(SET_INFLUXDB_HOST, (SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? PSTR(INFLUXDB_HOST) : XdrvMailbox.data); diff --git a/tasmota/xdrv_interface.ino b/tasmota/xdrv_interface.ino index e3c637231..0444ad11d 100644 --- a/tasmota/xdrv_interface.ino +++ b/tasmota/xdrv_interface.ino @@ -1083,8 +1083,8 @@ bool XdrvRulesProcess(bool teleperiod, const char* event) { char* data_save = XdrvMailbox.data; XdrvMailbox.data = (char*)event; bool rule_handled = XdrvCallDriver(10, (teleperiod) ? FUNC_TELEPERIOD_RULES_PROCESS : FUNC_RULES_PROCESS); -#ifdef USE_BERRY - // events are passed to both Rules engine AND Berry engine +#if defined(USE_BERRY) && !defined(USE_RULES) + // events are sent to Berry in Rules driver, or here if USE_RULES is not defined (only on a subset) bool berry_handled = XdrvCallDriver(52, FUNC_RULES_PROCESS); rule_handled |= berry_handled; #endif diff --git a/tasmota/xsns_53_sml.ino b/tasmota/xsns_53_sml.ino index 184261771..5c3644d1f 100755 --- a/tasmota/xsns_53_sml.ino +++ b/tasmota/xsns_53_sml.ino @@ -1198,7 +1198,7 @@ double CharToDouble(const char *str) strlcpy(strbuf, str, sizeof(strbuf)); char *pt = strbuf; - while ((*pt != '\0') && isblank(*pt)) { pt++; } // Trim leading spaces + while ((*pt != '\0') && isspace(*pt)) { pt++; } // Trim leading spaces signed char sign = 1; if (*pt == '-') { sign = -1; }