diff --git a/CHANGELOG.md b/CHANGELOG.md index 70dd7987f..8a1995158 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ All notable changes to this project will be documented in this file. - Zigbee support for decimal Voltage/Current/Power on power metering plugs - Command ``UrlFetch `` to download a file to filesystem - Zigbee basic support for Green Power +- Berry add ``introspect.setmodule(name:string, value:any) -> nil`` ### Changed - ESP32 Increase number of button GPIOs from 8 to 28 (#16518) diff --git a/lib/libesp32/berry/src/be_introspectlib.c b/lib/libesp32/berry/src/be_introspectlib.c index 1e5b521d3..28f506d18 100644 --- a/lib/libesp32/berry/src/be_introspectlib.c +++ b/lib/libesp32/berry/src/be_introspectlib.c @@ -66,10 +66,25 @@ static void m_findmember_protected(bvm *vm, void* data) static int m_findmember(bvm *vm) { int top = be_top(vm); - if (top >= 2 && (be_isinstance(vm, 1) || be_ismodule(vm, 1) || be_isclass(vm, 1)) && be_isstring(vm, 2)) { - int ret = be_execprotected(vm, &m_findmember_protected, (void*) be_tostring(vm, 2)); - if (ret == BE_OK) { - be_return(vm); + bbool protected = btrue; /* run protected, i.e. don't raise an exception if not found */ + if (top >= 3) { + protected = !be_tobool(vm, 3); + } + if (top >= 2) { + if (protected && (be_isinstance(vm, 1) || be_ismodule(vm, 1) || be_isclass(vm, 1)) && be_isstring(vm, 2)) { + int ret = be_execprotected(vm, &m_findmember_protected, (void*) be_tostring(vm, 2)); + if (ret == BE_OK) { + be_return(vm); + } + } else { + /* run unprotected */ + if (be_getmember(vm, 1, be_tostring(vm, 2))) { + be_return(vm); + } else { + /* not found, return module 'undefined' */ + be_getmodule(vm, "undefined"); + be_return(vm); + } } } be_return_nil(vm); @@ -143,6 +158,20 @@ static int m_getmodule(bvm *vm) be_return_nil(vm); } +/* set or chang the cached value for the named module, this allows monkey patching. **USE WITH CARE** */ +static int m_setmodule(bvm *vm) +{ + int top = be_top(vm); + if (top >= 2) { + bvalue *v = be_indexof(vm, 1); + if (var_isstr(v)) { + be_pushvalue(vm, 2); /* ensure the second arg is at top of stack */ + be_cache_module(vm, var_tostr(v)); + } + } + be_return_nil(vm); +} + /* checks if the function (berry bytecode bproto only) is hinted as a method */ static int m_ismethod(bvm *vm) { @@ -167,6 +196,7 @@ be_native_module_attr_table(introspect) { be_native_module_function("set", m_setmember), be_native_module_function("module", m_getmodule), + be_native_module_function("setmodule", m_setmodule), be_native_module_function("toptr", m_toptr), be_native_module_function("fromptr", m_fromptr), @@ -184,6 +214,7 @@ module introspect (scope: global, depend: BE_USE_INTROSPECT_MODULE) { set, func(m_setmember) module, func(m_getmodule) + setmodule, func(m_setmodule) toptr, func(m_toptr) fromptr, func(m_fromptr) diff --git a/lib/libesp32/berry/src/be_module.c b/lib/libesp32/berry/src/be_module.c index bf7c7720f..2d5e826b6 100644 --- a/lib/libesp32/berry/src/be_module.c +++ b/lib/libesp32/berry/src/be_module.c @@ -245,7 +245,7 @@ static bvalue* load_cached(bvm *vm, bstring *path) return v; } -static void cache_module(bvm *vm, bstring *name) +void be_cache_module(bvm *vm, bstring *name) { bvalue *v; if (vm->module.loaded == NULL) { @@ -282,7 +282,7 @@ int be_module_load(bvm *vm, bstring *path) if (res == BE_OK) { /* on first load of the module, try running the '()' function */ module_init(vm); - cache_module(vm, path); + be_cache_module(vm, path); } } return res; diff --git a/lib/libesp32/berry/src/be_module.h b/lib/libesp32/berry/src/be_module.h index 4f9b869de..26760660f 100644 --- a/lib/libesp32/berry/src/be_module.h +++ b/lib/libesp32/berry/src/be_module.h @@ -34,6 +34,7 @@ typedef struct bmodule { bmodule* be_module_new(bvm *vm); void be_module_delete(bvm *vm, bmodule *module); int be_module_load(bvm *vm, bstring *path); +void be_cache_module(bvm *vm, bstring *name); int be_module_attr(bvm *vm, bmodule *module, bstring *attr, bvalue *dst); bbool be_module_setmember(bvm *vm, bmodule *module, bstring *attr, bvalue *src); const char* be_module_name(bmodule *module); diff --git a/lib/libesp32/berry/tests/module.be b/lib/libesp32/berry/tests/module.be new file mode 100644 index 000000000..2c59aa4b7 --- /dev/null +++ b/lib/libesp32/berry/tests/module.be @@ -0,0 +1,45 @@ +# test for monkey patching of modules + +import string +import introspect + +var string_orig = string + +introspect.setmodule("string", 42) +import string +assert(string == 42) + +# set back original value +introspect.setmodule("string", string_orig) +import string +assert(type(string) == 'module') +assert(str(string) == '') + +# +# demo, how to monkey patch string with a new function `foo()` returning `bar` +# +import string +import introspect +var string_orig = string # keep a copy of the original string module +var string_alt = module('string') # create a new module + +# function `super()` is a closure to capture the original value of string +string_alt.super = def() + return string_orig +end + +# function `member()` is a clousre to capture the original value of string module +string_alt.member = def(k) + import introspect + return introspect.get(string_orig, k, true) +end + +string_alt.foo = def() return 'bar' end + +# replace the entry for module "string", from now on each `import string` will use `string_alt` +introspect.setmodule("string", string_alt) +import string + +# test the new string module +assert(string.tolower('abCD') == 'abcd') +assert(string.foo() == 'bar')