unleashed-firmware/lib/mjs/mjs_object.c
porta 8a95cb8d6b
[FL-3893] JS modules (#3841)
* feat: backport js_gpio from unleashed
* feat: backport js_keyboard, TextInputModel::minimum_length from unleashed
* fix: api version inconsistency
* style: js_gpio
* build: fix submodule ._ .
* refactor: js_gpio
* docs: type declarations for gpio
* feat: gpio interrupts
* fix: js_gpio freeing, resetting and minor stylistic changes
* style: js_gpio
* style: mlib array, fixme's
* feat: js_gpio adc
* feat: js_event_loop
* docs: js_event_loop
* feat: js_event_loop subscription cancellation
* feat: js_event_loop + js_gpio integration
* fix: js_event_loop memory leak
* feat: stop event loop on back button
* test: js: basic, math, event_loop
* feat: js_event_loop queue
* feat: js linkage to previously loaded plugins
* build: fix ci errors
* feat: js module ordered teardown
* feat: js_gui_defer_free
* feat: basic hourglass view
* style: JS ASS (Argument Schema for Scripts)
* fix: js_event_loop mem leaks and lifetime problems
* fix: crashing test and pvs false positives
* feat: mjs custom obj destructors, gui submenu view
* refactor: yank js_gui_defer_free (yuck)
* refactor: maybe_unsubscribe
* empty_screen, docs, typing fix-ups
* docs: navigation event & demo
* feat: submenu setHeader
* feat: text_input
* feat: text_box
* docs: text_box availability
* ci: silence irrelevant pvs low priority warning
* style: use furistring
* style: _get_at -> _safe_get
* fix: built-in module name assignment
* feat: js_dialog; refactor, optimize: js_gui
* docs: js_gui
* ci: silence pvs warning: Memory allocation is infallible
* style: fix storage spelling
* feat: foreign pointer signature checks
* feat: js_storage
* docs: js_storage
* fix: my unit test was breaking other tests ;_;
* ci: fix ci?
* Make doxygen happy
* docs: flipper, math, notification, global
* style: review suggestions
* style: review fixups
* fix: badusb demo script
* docs: badusb
* ci: add nofl
* ci: make linter happy
* Bump api version

Co-authored-by: Aleksandr Kutuzov <alleteam@gmail.com>
2024-10-14 19:42:11 +01:00

415 lines
12 KiB
C

/*
* Copyright (c) 2016 Cesanta Software Limited
* All rights reserved
*/
#include "mjs_object.h"
#include "mjs_core.h"
#include "mjs_internal.h"
#include "mjs_primitive.h"
#include "mjs_string.h"
#include "mjs_util.h"
#include "furi.h"
#include "common/mg_str.h"
MJS_PRIVATE mjs_val_t mjs_object_to_value(struct mjs_object* o) {
if(o == NULL) {
return MJS_NULL;
} else {
return mjs_legit_pointer_to_value(o) | MJS_TAG_OBJECT;
}
}
MJS_PRIVATE void mjs_obj_destructor(struct mjs* mjs, void* cell) {
struct mjs_object* obj = cell;
mjs_val_t obj_val = mjs_object_to_value(obj);
struct mjs_property* destructor = mjs_get_own_property(
mjs, obj_val, MJS_DESTRUCTOR_PROP_NAME, strlen(MJS_DESTRUCTOR_PROP_NAME));
if(!destructor) return;
if(!mjs_is_foreign(destructor->value)) return;
mjs_custom_obj_destructor_t destructor_fn = mjs_get_ptr(mjs, destructor->value);
if(destructor_fn) destructor_fn(mjs, obj_val);
}
MJS_PRIVATE struct mjs_object* get_object_struct(mjs_val_t v) {
struct mjs_object* ret = NULL;
if(mjs_is_null(v)) {
ret = NULL;
} else {
assert(mjs_is_object_based(v));
ret = (struct mjs_object*)get_ptr(v);
}
return ret;
}
mjs_val_t mjs_mk_object(struct mjs* mjs) {
struct mjs_object* o = new_object(mjs);
if(o == NULL) {
return MJS_NULL;
}
(void)mjs;
o->properties = NULL;
return mjs_object_to_value(o);
}
int mjs_is_object(mjs_val_t v) {
return (v & MJS_TAG_MASK) == MJS_TAG_OBJECT || (v & MJS_TAG_MASK) == MJS_TAG_ARRAY;
}
int mjs_is_object_based(mjs_val_t v) {
return ((v & MJS_TAG_MASK) == MJS_TAG_OBJECT) || ((v & MJS_TAG_MASK) == MJS_TAG_ARRAY) ||
((v & MJS_TAG_MASK) == MJS_TAG_ARRAY_BUF_VIEW);
}
MJS_PRIVATE struct mjs_property*
mjs_get_own_property(struct mjs* mjs, mjs_val_t obj, const char* name, size_t len) {
struct mjs_property* p;
struct mjs_object* o;
if(!mjs_is_object_based(obj)) {
return NULL;
}
o = get_object_struct(obj);
if(len <= 5) {
mjs_val_t ss = mjs_mk_string(mjs, name, len, 1);
for(p = o->properties; p != NULL; p = p->next) {
if(p->name == ss) return p;
}
} else {
for(p = o->properties; p != NULL; p = p->next) {
if(mjs_strcmp(mjs, &p->name, name, len) == 0) return p;
}
return p;
}
return NULL;
}
MJS_PRIVATE struct mjs_property*
mjs_get_own_property_v(struct mjs* mjs, mjs_val_t obj, mjs_val_t key) {
size_t n;
char* s = NULL;
int need_free = 0;
struct mjs_property* p = NULL;
mjs_err_t err = mjs_to_string(mjs, &key, &s, &n, &need_free);
if(err == MJS_OK) {
p = mjs_get_own_property(mjs, obj, s, n);
}
if(need_free) free(s);
return p;
}
MJS_PRIVATE struct mjs_property*
mjs_mk_property(struct mjs* mjs, mjs_val_t name, mjs_val_t value) {
struct mjs_property* p = new_property(mjs);
p->next = NULL;
p->name = name;
p->value = value;
return p;
}
mjs_val_t mjs_get(struct mjs* mjs, mjs_val_t obj, const char* name, size_t name_len) {
struct mjs_property* p;
if(name_len == (size_t)~0) {
name_len = strlen(name);
}
p = mjs_get_own_property(mjs, obj, name, name_len);
if(p == NULL) {
return MJS_UNDEFINED;
} else {
return p->value;
}
}
mjs_val_t mjs_get_v(struct mjs* mjs, mjs_val_t obj, mjs_val_t name) {
size_t n;
char* s = NULL;
int need_free = 0;
mjs_val_t ret = MJS_UNDEFINED;
mjs_err_t err = mjs_to_string(mjs, &name, &s, &n, &need_free);
if(err == MJS_OK) {
/* Successfully converted name value to string: get the property */
ret = mjs_get(mjs, obj, s, n);
}
if(need_free) {
free(s);
s = NULL;
}
return ret;
}
mjs_val_t mjs_get_v_proto(struct mjs* mjs, mjs_val_t obj, mjs_val_t key) {
struct mjs_property* p;
mjs_val_t pn = mjs_mk_string(mjs, MJS_PROTO_PROP_NAME, ~0, 1);
if((p = mjs_get_own_property_v(mjs, obj, key)) != NULL) return p->value;
if((p = mjs_get_own_property_v(mjs, obj, pn)) == NULL) return MJS_UNDEFINED;
return mjs_get_v_proto(mjs, p->value, key);
}
mjs_err_t
mjs_set(struct mjs* mjs, mjs_val_t obj, const char* name, size_t name_len, mjs_val_t val) {
return mjs_set_internal(mjs, obj, MJS_UNDEFINED, (char*)name, name_len, val);
}
mjs_err_t mjs_set_v(struct mjs* mjs, mjs_val_t obj, mjs_val_t name, mjs_val_t val) {
return mjs_set_internal(mjs, obj, name, NULL, 0, val);
}
MJS_PRIVATE mjs_err_t mjs_set_internal(
struct mjs* mjs,
mjs_val_t obj,
mjs_val_t name_v,
char* name,
size_t name_len,
mjs_val_t val) {
mjs_err_t rcode = MJS_OK;
struct mjs_property* p;
int need_free = 0;
if(name == NULL) {
/* Pointer was not provided, so obtain one from the name_v. */
rcode = mjs_to_string(mjs, &name_v, &name, &name_len, &need_free);
if(rcode != MJS_OK) {
goto clean;
}
} else {
/*
* Pointer was provided, so we ignore name_v. Here we set it to undefined,
* and the actual value will be calculated later if needed.
*/
name_v = MJS_UNDEFINED;
}
p = mjs_get_own_property(mjs, obj, name, name_len);
if(p == NULL) {
struct mjs_object* o;
if(!mjs_is_object_based(obj)) {
return MJS_REFERENCE_ERROR;
}
/*
* name_v might be not a string here. In this case, we need to create a new
* `name_v`, which will be a string.
*/
if(!mjs_is_string(name_v)) {
name_v = mjs_mk_string(mjs, name, name_len, 1);
}
p = mjs_mk_property(mjs, name_v, val);
o = get_object_struct(obj);
p->next = o->properties;
o->properties = p;
}
p->value = val;
clean:
if(need_free) {
free(name);
name = NULL;
}
return rcode;
}
MJS_PRIVATE void mjs_destroy_property(struct mjs_property** p) {
*p = NULL;
}
/*
* See comments in `object_public.h`
*/
int mjs_del(struct mjs* mjs, mjs_val_t obj, const char* name, size_t len) {
struct mjs_property *prop, *prev;
if(!mjs_is_object_based(obj)) {
return -1;
}
if(len == (size_t)~0) {
len = strlen(name);
}
for(prev = NULL, prop = get_object_struct(obj)->properties; prop != NULL;
prev = prop, prop = prop->next) {
size_t n;
const char* s = mjs_get_string(mjs, &prop->name, &n);
if(n == len && strncmp(s, name, len) == 0) {
if(prev) {
prev->next = prop->next;
} else {
get_object_struct(obj)->properties = prop->next;
}
mjs_destroy_property(&prop);
return 0;
}
}
return -1;
}
mjs_val_t mjs_next(struct mjs* mjs, mjs_val_t obj, mjs_val_t* iterator) {
struct mjs_property* p = NULL;
mjs_val_t key = MJS_UNDEFINED;
if(*iterator == MJS_UNDEFINED) {
struct mjs_object* o = get_object_struct(obj);
p = o->properties;
} else {
p = ((struct mjs_property*)get_ptr(*iterator))->next;
}
if(p == NULL) {
*iterator = MJS_UNDEFINED;
} else {
key = p->name;
*iterator = mjs_mk_foreign(mjs, p);
}
return key;
}
MJS_PRIVATE void mjs_op_create_object(struct mjs* mjs) {
mjs_val_t ret = MJS_UNDEFINED;
mjs_val_t proto_v = mjs_arg(mjs, 0);
if(!mjs_check_arg(mjs, 0, "proto", MJS_TYPE_OBJECT_GENERIC, &proto_v)) {
goto clean;
}
ret = mjs_mk_object(mjs);
mjs_set(mjs, ret, MJS_PROTO_PROP_NAME, ~0, proto_v);
clean:
mjs_return(mjs, ret);
}
mjs_val_t
mjs_struct_to_obj(struct mjs* mjs, const void* base, const struct mjs_c_struct_member* defs) {
mjs_val_t obj;
const struct mjs_c_struct_member* def = defs;
if(base == NULL || def == NULL) return MJS_UNDEFINED;
obj = mjs_mk_object(mjs);
/* Pin the object while it is being built */
mjs_own(mjs, &obj);
/*
* Because mjs inserts new properties at the head of the list,
* start from the end so the constructed object more closely resembles
* the definition.
*/
while(def->name != NULL)
def++;
for(def--; def >= defs; def--) {
mjs_val_t v = MJS_UNDEFINED;
const char* ptr = (const char*)base + def->offset;
switch(def->type) {
case MJS_STRUCT_FIELD_TYPE_STRUCT: {
const void* sub_base = (const void*)ptr;
const struct mjs_c_struct_member* sub_def =
(const struct mjs_c_struct_member*)def->arg;
v = mjs_struct_to_obj(mjs, sub_base, sub_def);
break;
}
case MJS_STRUCT_FIELD_TYPE_STRUCT_PTR: {
const void** sub_base = (const void**)ptr;
const struct mjs_c_struct_member* sub_def =
(const struct mjs_c_struct_member*)def->arg;
if(*sub_base != NULL) {
v = mjs_struct_to_obj(mjs, *sub_base, sub_def);
} else {
v = MJS_NULL;
}
break;
}
case MJS_STRUCT_FIELD_TYPE_INT: {
double value = (double)(*(int*)ptr);
v = mjs_mk_number(mjs, value);
break;
}
case MJS_STRUCT_FIELD_TYPE_BOOL: {
v = mjs_mk_boolean(mjs, *(bool*)ptr);
break;
}
case MJS_STRUCT_FIELD_TYPE_DOUBLE: {
v = mjs_mk_number(mjs, *(double*)ptr);
break;
}
case MJS_STRUCT_FIELD_TYPE_FLOAT: {
float value = *(float*)ptr;
v = mjs_mk_number(mjs, value);
break;
}
case MJS_STRUCT_FIELD_TYPE_CHAR_PTR: {
const char* value = *(const char**)ptr;
v = mjs_mk_string(mjs, value, ~0, 1);
break;
}
case MJS_STRUCT_FIELD_TYPE_VOID_PTR: {
v = mjs_mk_foreign(mjs, *(void**)ptr);
break;
}
case MJS_STRUCT_FIELD_TYPE_MG_STR_PTR: {
const struct mg_str* s = *(const struct mg_str**)ptr;
if(s != NULL) {
v = mjs_mk_string(mjs, s->p, s->len, 1);
} else {
v = MJS_NULL;
}
break;
}
case MJS_STRUCT_FIELD_TYPE_MG_STR: {
const struct mg_str* s = (const struct mg_str*)ptr;
v = mjs_mk_string(mjs, s->p, s->len, 1);
break;
}
case MJS_STRUCT_FIELD_TYPE_DATA: {
const char* dptr = (const char*)ptr;
const intptr_t dlen = (intptr_t)def->arg;
v = mjs_mk_string(mjs, dptr, dlen, 1);
break;
}
case MJS_STRUCT_FIELD_TYPE_INT8: {
double value = (double)(*(int8_t*)ptr);
v = mjs_mk_number(mjs, value);
break;
}
case MJS_STRUCT_FIELD_TYPE_INT16: {
double value = (double)(*(int16_t*)ptr);
v = mjs_mk_number(mjs, value);
break;
}
case MJS_STRUCT_FIELD_TYPE_UINT8: {
double value = (double)(*(uint8_t*)ptr);
v = mjs_mk_number(mjs, value);
break;
}
case MJS_STRUCT_FIELD_TYPE_UINT16: {
double value = (double)(*(uint16_t*)ptr);
v = mjs_mk_number(mjs, value);
break;
}
case MJS_STRUCT_FIELD_TYPE_CUSTOM: {
mjs_val_t (*fptr)(struct mjs*, const void*) =
(mjs_val_t(*)(struct mjs*, const void*))def->arg;
v = fptr(mjs, ptr);
}
default: {
break;
}
}
mjs_set(mjs, obj, def->name, ~0, v);
}
mjs_disown(mjs, &obj);
return obj;
}