Tasmota/lib/libesp32/berry_mapping/src/be_class_wrapper.c
2022-04-05 21:38:43 +02:00

490 lines
20 KiB
C

/*********************************************************************************************\
* Class wrappers for native objects
*
* These class are simple wrappers (containers) for a pointer of an external object.
* The pointer is stored interanlly by the class.
*
* The constructor of this class must accept the first argument to be `comptr`,
* in such case, the constructor must store the pointer.
* The class is not supposed to free the object at `deinit` time.
\*********************************************************************************************/
#include "be_mapping.h"
#include "be_exec.h"
#include <string.h>
typedef intptr_t (*fn_any_callable)(intptr_t p0, intptr_t p1, intptr_t p2, intptr_t p3,
intptr_t p4, intptr_t p5, intptr_t p6, intptr_t p7);
/*********************************************************************************************\
* Converision from real <-> int
*
* Warning, works only if sizeof(intptr_t) == sizeof(breal)
* On ESP32, int=32bits, real=float (32bits)
\*********************************************************************************************/
static intptr_t realasint(breal v) {
intptr_t i;
i = *((intptr_t*) &v);
return i;
}
static breal intasreal(intptr_t v) {
breal r;
r = *((breal*) &v);
return r;
}
/*********************************************************************************************\
* Create an object of `class_name` given an external poinrt `ptr`.
*
* Instanciates the class and calls `init()` with `ptr` wrapped in `comptr` as single arg.
* Both arguments but nost bu NULL.
*
* On return, the created instance is top of stack.
\*********************************************************************************************/
void be_create_class_wrapper(bvm *vm, const char * class_name, void * ptr) {
if (ptr == NULL) {
be_throw(vm, BE_MALLOC_FAIL);
}
be_getglobal(vm, class_name); // stack = class
be_call(vm, 0); // instanciate, stack = instance
be_getmember(vm, -1, "init"); // stack = instance, init_func
be_pushvalue(vm, -2); // stack = instance, init_func, instance
be_pushcomptr(vm, ptr); // stack = instance, init_func, instance, ptr
be_call(vm, 2); // stack = instance, ret, instance, ptr
be_pop(vm, 3); // stack = instance
}
/*********************************************************************************************\
* Find an object by global or composite name.
*
* I.e. `lv.lv_object` will check for a global called `lv` and a member `lv_object`
*
* Only supports one level of depth, meaning a class within a module.
* Does not check the type of the object found.
*
* Arguments:
* `name`: can be NULL, in such case considers the member as not found
*
* Case 1: (no dot in name) `lv_wifi_bars` will look for a global variable `lv_wifi_bars`
* Case 2: (dot in name) `lvgl.lv_obj` will get global `lvgl` and look for `lv_obj` within this module
*
* Returns the number of elements pushed on the stack: 1 for module, 2 for instance method, 0 if not found
\*********************************************************************************************/
int be_find_global_or_module_member(bvm *vm, const char * name) {
char *saveptr;
if (name == NULL) {
be_pushnil(vm);
return 0;
}
char name_buf[strlen(name)+1];
strcpy(name_buf, name);
char * prefix = strtok_r(name_buf, ".", &saveptr);
char * suffix = strtok_r(NULL, ".", &saveptr);
if (suffix) {
if (!be_getglobal(vm, prefix)) {
// global not found, try module
be_pop(vm, 1);
if (!be_getmodule(vm, prefix)) {
return 0;
}
}
if (!be_isnil(vm, -1)) {
if (be_getmember(vm, -1, suffix)) {
if (be_isinstance(vm, -2)) { // instance, so we need to push method + instance
be_pushvalue(vm, -2);
be_remove(vm, -3);
return 2;
} else { // not instane, so keep only the top object
be_remove(vm, -2);
return 1;
}
} else {
be_pop(vm, 2);
return 0;
}
}
be_pop(vm, 1); // remove nil
return 0;
} else { // no suffix, get the global object
if (be_getglobal(vm, prefix)) {
return 1;
}
be_pop(vm, 1);
return 0;
}
}
/*********************************************************************************************\
* Automatically parse Berry stack and call the C function accordingly
*
* This function takes the n incoming arguments and pushes them as arguments
* on the stack for the C function:
* - be_int -> int32_t
* - be_bool -> int32_t with value 0/1
* - be_string -> const char *
* - be_instance -> gets the member "_p" and pushes as void*
*
* This works because C silently ignores any unwanted arguments.
* There is a strong requirements that all ints and pointers are 32 bits.
* Float is not supported but could be added. Double cannot be supported because they are 64 bits
*
* Optional argument:
* - return_type: the C function return value is int32_t and is converted to the
* relevant Berry object depending on this char:
* '' (default): nil, no value
* 'i' be_int
* 'f' be_real (float)
* 'b' be_bool
* 's' be_str
* '$' be_str but the buffer must be `free()`ed
* '&' bytes() object, pointer to buffer returned, and size passed with an additional (size_t*) argument
*
* - arg_type: optionally check the types of input arguments, or throw an error
* string of argument types, '[' indicates that the following parameters are optional
* '.' don't care
* 'i' be_int
* 'b' be_bool
* 's' be_string
* 'f' be_real (float)
* 'c' C comptr (general pointer)
* '-': skip argument and ignore
* '~': send the length of the previous bytes() buffer (or raise an exception if no length known)
* 'lv_obj' be_instance of type or subtype
* '^lv_event_cb^' callback of a named class - will call `_lvgl.gen_cb(arg_type, closure, self)` and expects a callback address in return
* '@': pass a pointer to the Berry VM (virtual parameter added, must be the first argument)
*
* Ex: ".ii" takes 3 arguments, first one is any type, followed by 2 ints
\*********************************************************************************************/
// general form of lv_obj_t* function, up to 4 parameters
// We can only send 32 bits arguments (no 64 bits nor double) and we expect pointers to be 32 bits
// read a single value at stack position idx, convert to int.
// if object instance, get `_p` member and convert it recursively
intptr_t be_convert_single_elt(bvm *vm, int idx, const char * arg_type, int *buf_len) {
// berry_log_C("be_convert_single_elt(idx=%i, argtype='%s', type=%s)", idx, arg_type ? arg_type : "", be_typename(vm, idx));
int ret = 0;
char provided_type = 0;
idx = be_absindex(vm, idx); // make sure we have an absolute index
// berry_log_C(">> 0 idx=%i arg_type=%s", idx, arg_type ? arg_type : "NULL");
if (arg_type == NULL) { arg_type = "."; } // if no type provided, replace with wildchar
size_t arg_type_len = strlen(arg_type);
// handle callbacks first, since a wrong parameter will always yield to a crash
if (arg_type_len > 1 && arg_type[0] == '^') { // it is a callback
arg_type++; // skip first character
if (be_isclosure(vm, idx)) {
ret = be_find_global_or_module_member(vm, "cb.make_cb");
// ret may 1 if direct function, or 2 if method+instance. 0 indicates an error
if (ret) {
be_pushvalue(vm, idx); // push function/closure as arg1
be_pushvalue(vm, 1); // push `self` as arg2
be_pushstring(vm, arg_type); // push name of the callback type (string) as arg3
be_call(vm, 2 + ret);
const void * func = be_tocomptr(vm, -(3 + ret));
be_pop(vm, 3 + ret);
// berry_log_C("func=%p", func);
return (int32_t) func;
} else {
be_raisef(vm, "type_error", "Can't find callback generator: 'cb.make_cb'");
}
} else {
be_raise(vm, "type_error", "Closure expected for callback type");
}
}
// first convert the value to int32
if (be_isint(vm, idx)) {
if (arg_type[0] == 'f') {
ret = realasint((float)be_toint(vm, idx)); provided_type = 'f';
} else {
ret = be_toint(vm, idx); provided_type = 'i'; }
}
else if (be_isbool(vm, idx)) { ret = be_tobool(vm, idx); provided_type = 'b'; }
else if (be_isstring(vm, idx)) { ret = (intptr_t) be_tostring(vm, idx); provided_type = 's'; }
else if (be_iscomptr(vm, idx)) { ret = (intptr_t) be_tocomptr(vm, idx); provided_type = 'c'; }
else if (be_isnil(vm, idx)) { ret = 0; provided_type = 'c'; }
else if (be_isreal(vm, idx)) { ret = realasint(be_toreal(vm, idx)); provided_type = 'f'; }
// check if simple type was a match
if (provided_type) {
bbool type_ok = bfalse;
type_ok = (arg_type[0] == '.'); // any type is accepted
type_ok = type_ok || (arg_type[0] == provided_type && arg_type[1] == 0); // or type is a match (single char only)
type_ok = type_ok || (ret == 0 && arg_type_len != 1); // or NULL is accepted for an instance
if (!type_ok) {
be_raisef(vm, "type_error", "Unexpected argument type '%c', expected '%s'", provided_type, arg_type);
}
// berry_log_C("be_convert_single_elt provided type=%i", ret);
return ret;
}
// non-simple type
if (be_isinstance(vm, idx)) {
// check if the instance is a subclass of `bytes()``
if (be_isbytes(vm, idx)) {
size_t len;
intptr_t ret = (intptr_t) be_tobytes(vm, idx, &len);
if (buf_len) { *buf_len = (int) len; }
return ret;
} else {
// we accept either `_p` or `.p` attribute to retrieve a pointer
if (!be_getmember(vm, idx, "_p")) {
be_pop(vm, 1); // remove `nil`
be_getmember(vm, idx, ".p");
} // else `nil` is on top of stack
int32_t ret = be_convert_single_elt(vm, -1, NULL, NULL); // recurse
be_pop(vm, 1);
if (arg_type_len > 1) {
// Check type
be_classof(vm, idx);
int class_found = be_find_global_or_module_member(vm, arg_type);
// Stack: class_of_idx, class_of_target (or nil)
if (class_found) {
if (!be_isderived(vm, -2)) {
be_raisef(vm, "type_error", "Unexpected class type '%s', expected '%s'", be_classname(vm, idx), arg_type);
}
} else {
be_raisef(vm, "value_error", "Unable to find class '%s' (%d)", arg_type, arg_type_len);
}
be_pop(vm, 2);
} else if (arg_type[0] != '.') {
be_raisef(vm, "value_error", "Unexpected instance type '%s', expected '%s'", be_classname(vm, idx), arg_type);
}
return ret;
}
} else {
be_raisef(vm, "value_error", "Unexpected '%s'", be_typename(vm, idx));
}
return ret;
}
/*********************************************************************************************\
* Calling any LVGL function with auto-mapping
*
\*********************************************************************************************/
// check input parameters, and create callbacks if needed
// change values in place
//
// Format:
// - either a lowercase character encoding for a simple type
// - 'b': bool
// - 'i': int (int32_t)
// - 's': string (const char *)
// - '.': any argument (no check)
// - '-': skip argument and ignore
// - '~': send the length of the previous bytes() buffer (or raise an exception if no length known)
// - if return type is '&' (bytes), an implicit additional parameter is passed as (size_t*) to return the length in bytes
//
// - a class name surroungded by parenthesis
// - '(lv_button)' -> lv_button class or derived
// - '[lv_event_cb]' -> callback type, still prefixed with '^' to mark that it is cb
//
// Returns the number of parameters sent to the function
//
int be_check_arg_type(bvm *vm, int arg_start, int argc, const char * arg_type, intptr_t p[8]) {
bbool arg_type_check = (arg_type != NULL); // is type checking activated
int32_t arg_idx = 0; // position in arg_type string
bbool arg_optional = bfalse; // are remaining types optional?
char type_short_name[32];
uint32_t p_idx = 0; // index in p[], is incremented with each parameter except '-'
int32_t buf_len = -1; // stores the length of a bytes() buffer to be used as '~' attribute
// special case when first parameter is '@', pass pointer to VM
if (NULL != arg_type && arg_type[arg_idx] == '@') {
arg_idx++;
p[p_idx] = (intptr_t) vm;
p_idx++;
}
for (uint32_t i = 0; i < argc; i++) {
type_short_name[0] = 0; // clear string
// extract individual type
if (arg_type) {
if (arg_type[arg_idx] == '[' || arg_type[arg_idx] == ']') { // '[' is a marker that following parameters are optional and default to NULL
arg_optional = btrue;
arg_idx++;
}
switch (arg_type[arg_idx]) {
case '-':
arg_idx++;
continue; // ignore current parameter and advance
case '.':
case 'a'...'z':
type_short_name[0] = arg_type[arg_idx];
type_short_name[1] = 0;
arg_idx++;
break;
case '(':
case '^':
{
uint32_t prefix = 0;
if (arg_type[arg_idx] == '^') {
type_short_name[0] = '^';
type_short_name[1] = 0;
prefix = 1;
}
uint32_t offset = 0;
arg_idx++;
while (arg_type[arg_idx + offset] != ')' && arg_type[arg_idx + offset] != '^' && arg_type[arg_idx + offset] != 0 && offset+prefix+1 < sizeof(type_short_name)) {
type_short_name[offset+prefix] = arg_type[arg_idx + offset];
type_short_name[offset+prefix+1] = 0;
offset++;
}
if (arg_type[arg_idx + offset] == 0) {
arg_type = NULL; // no more parameters, stop iterations
}
arg_idx += offset + 1;
}
break;
case 0:
arg_type = NULL; // stop iterations
break;
}
}
// berry_log_C(">> be_call_c_func arg %i, type %s", i, arg_type_check ? type_short_name : "<null>");
p[p_idx] = be_convert_single_elt(vm, i + arg_start, arg_type_check ? type_short_name : NULL, &buf_len);
// berry_log_C("< ret[%i]=%i", p_idx, p[p_idx]);
p_idx++;
if (arg_type && arg_type[arg_idx] == '~') { // if next argument is virtual
if (buf_len < 0) {
be_raisef(vm, "value_error", "no bytes() length known");
}
p[p_idx] = buf_len; // add the previous buffer len
p_idx++;
arg_idx++; // skip this arg
}
}
// check if we are missing arguments
if (!arg_optional && arg_type && arg_type[arg_idx] != 0 && arg_type[arg_idx] != '[') {
be_raisef(vm, "value_error", "Missing arguments, remaining type '%s'", &arg_type[arg_idx]);
}
return p_idx;
}
//
// Internal function
//
// Called for constructors, i.e. C function mapped to Berry `init()`
//
// Pre-conditions:
// The instance must be at stack position `1` (default when calling `init()`)
//
// Arguments:
// vm: point to Berry vm (as usual)
// ptr: the C pointer for internal data (can be NULL), will be stored in an instance variable
// name: name of instance variable to store the pointer as `comptr`.
// If NULL, this function does nothing
// the name can be prefixed with `+` or `=`, if so first char is ignored.
// Ex: `+_p` stores in instance variable `_p`
// `+` forbids any NULL value (raises an exception) while `=` allows a NULL value
static void be_set_ctor_ptr(bvm *vm, void * ptr, const char *name) {
if (name == NULL) return; // do nothing if no name of attribute
if (name[0] == '+' && ptr == NULL) { be_raise(vm, "value_error", "argument cannot be NULL"); }
if (name[0] == '+' || name[0] == '=') { name++; } // skip prefix '^' if any
if (strlen(name) == 0) return; // do nothing if name is empty
be_pushcomptr(vm, ptr);
if (be_setmember(vm, 1, name)) {
be_pop(vm, 1);
} else {
be_raisef(vm, "attribute_error", "Missing member '%s' in ctor", name);
}
}
/*********************************************************************************************\
* CType handler for Berry
\*********************************************************************************************/
int be_call_ctype_func(bvm *vm, const void *definition) {
be_ctype_args_t* args = (be_ctype_args_t*) definition;
return be_call_c_func(vm, args->func, args->return_type, args->arg_type);
}
/*********************************************************************************************\
* Call a C function with auto-mapping
*
* Arguments:
* vm: pointer to Berry vm (as ususal)
* func: pointer to C function
* return_type: how to convert the result into a Berry type
* arg_type: string describing the optional and mandatory parameters
*
* Note: the C function mapping supports max 8 arguments and does not directly support
* pointers to values (although it is possible to mimick with classes)
\*********************************************************************************************/
int be_call_c_func(bvm *vm, const void * func, const char * return_type, const char * arg_type) {
intptr_t p[8] = {0,0,0,0,0,0,0,0};
int argc = be_top(vm); // Get the number of arguments
// the following describe the active payload for the C function (start and count)
// this is because the `init()` constructor first arg is not passed to the C function
int arg_start = 1; // start with standard values
int arg_count = argc;
// check if we call a constructor, in this case we store the return type into the new object
// check if we call a constructor with a comptr as first arg
if (return_type && (return_type[0] == '+' || return_type[0] == '=')) {
if (argc > 1 && be_iscomptr(vm, 2)) {
void * obj = be_tocomptr(vm, 2);
be_set_ctor_ptr(vm, obj, return_type);
be_return_nil(vm);
} else {
// we need to discard the first arg
arg_start++;
arg_count--;
}
}
fn_any_callable f = (fn_any_callable) func;
size_t return_len = 0; // when returning a bytes buffer, this holds the length of the buffer, while the return value of the function is `void*`
int c_args = be_check_arg_type(vm, arg_start, arg_count, arg_type, p);
if (return_type != NULL && return_type[0] == '&') {
if (c_args < 8) { p[c_args] = (intptr_t) &return_len; }
}
intptr_t ret = 0;
if (f) ret = (*f)(p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7]);
// berry_log_C("be_call_c_func '%s' -> '%s': (%i,%i,%i,%i,%i,%i) -> %i", return_type, arg_type, p[0], p[1], p[2], p[3], p[4], p[5], ret);
if ((return_type == NULL) || (strlen(return_type) == 0)) { be_return_nil(vm); } // does not return
else if (return_type[0] == '+' || return_type[0] == '=') {
void * obj = (void*) ret;
be_set_ctor_ptr(vm, obj, return_type);
be_return_nil(vm);
}
else if (strlen(return_type) == 1) {
switch (return_type[0]) {
case '.': // fallback next
case 'i': be_pushint(vm, ret); break;
case 'f': be_pushreal(vm, intasreal(ret)); break;
case 'b': be_pushbool(vm, ret); break;
case 'c': be_pushcomptr(vm, (void*) ret); break;
case 's': if (ret) {be_pushstring(vm, (const char*) ret);} else {be_pushnil(vm);} break; // push `nil` if no string
case '$': if (ret) {be_pushstring(vm, (const char*) ret); free((void*)ret);} else {be_pushnil(vm);} break;
case '&': be_pushbytes(vm, (void*) ret, return_len); break;
default: be_raise(vm, "internal_error", "Unsupported return type"); break;
}
be_return(vm);
} else { // class name
be_find_global_or_module_member(vm, return_type);
be_pushcomptr(vm, (void*) ret); // stack = class, ptr
be_call(vm, 1); // instanciate with 2 arguments, stack = instance, ptr, -1
be_pop(vm, 1); // stack = instance
be_return(vm);
}
}