unleashed-firmware/lib/mjs/mjs_array_buf.c
Nikolay Minaylov 0154018363
[FL-3579, FL-3601, FL-3714] JavaScript runner (#3286)
* FBT: cdefines to env, libs order
* API: strtod, modf, itoa, calloc
* Apps: elk js
* Apps: mjs
* JS: scripts as assets
* mjs: composite resolver
* mjs: stack trace
* ELK JS example removed
* MJS thread, MJS lib modified to support script interruption
* JS console UI
* Module system, BadUSB bindings rework
* JS notifications, simple dialog, BadUSB demo
* Custom dialogs, dialog demo
* MJS as system library, some dirty hacks to make it compile
* Plugin-based js modules
* js_uart(BadUART) module
* js_uart: support for byte array arguments
* Script icon and various fixes
* File browser: multiple extensions filter, running js scripts from app loader
* Running js scripts from archive browser
* JS Runner as system app
* Example scripts moved to /ext/apps/Scripts
* JS bytecode listing generation
* MJS builtin printf cleanup
* JS examples cleanup
* mbedtls version fix
* Unused lib cleanup
* Making PVS happy & TODOs cleanup
* TODOs cleanup #2
* MJS: initial typed arrays support
* JS: fix mem leak in uart destructor

Co-authored-by: SG <who.just.the.doctor@gmail.com>
Co-authored-by: Aleksandr Kutuzov <alleteam@gmail.com>
2024-02-12 15:54:32 +07:00

385 lines
11 KiB
C

#include "mjs_array_buf.h"
#include "common/cs_varint.h"
#include "common/mg_str.h"
#include "mjs_core.h"
#include "mjs_internal.h"
#include "mjs_primitive.h"
#include "mjs_object.h"
#include "mjs_array.h"
#include "mjs_util.h"
#include "mjs_exec_public.h"
#ifndef MJS_ARRAY_BUF_RESERVE
#define MJS_ARRAY_BUF_RESERVE 100
#endif
#define IS_SIGNED(type) \
(type == MJS_DATAVIEW_I8 || type == MJS_DATAVIEW_I16 || type == MJS_DATAVIEW_I32)
int mjs_is_array_buf(mjs_val_t v) {
return (v & MJS_TAG_MASK) == MJS_TAG_ARRAY_BUF;
}
int mjs_is_data_view(mjs_val_t v) {
return (v & MJS_TAG_MASK) == MJS_TAG_ARRAY_BUF_VIEW;
}
int mjs_is_typed_array(mjs_val_t v) {
return ((v & MJS_TAG_MASK) == MJS_TAG_ARRAY_BUF) ||
((v & MJS_TAG_MASK) == MJS_TAG_ARRAY_BUF_VIEW);
}
char* mjs_array_buf_get_ptr(struct mjs* mjs, mjs_val_t buf, size_t* bytelen) {
struct mbuf* m = &mjs->array_buffers;
size_t offset = buf & ~MJS_TAG_MASK;
char* ptr = m->buf + offset;
uint64_t len = 0;
size_t header_len = 0;
if(offset < m->len && cs_varint_decode((uint8_t*)ptr, m->len - offset, &len, &header_len)) {
if(bytelen) {
*bytelen = len;
}
return ptr + header_len;
}
return NULL;
}
static size_t mjs_dataview_get_element_len(mjs_dataview_type_t type) {
size_t len = 1;
switch(type) {
case MJS_DATAVIEW_U8:
case MJS_DATAVIEW_I8:
len = 1;
break;
case MJS_DATAVIEW_U16:
case MJS_DATAVIEW_I16:
len = 2;
break;
case MJS_DATAVIEW_U32:
case MJS_DATAVIEW_I32:
len = 4;
break;
default:
break;
}
return len;
}
static int64_t get_value(char* buf, mjs_dataview_type_t type) {
int64_t value = 0;
switch(type) {
case MJS_DATAVIEW_U8:
value = *(uint8_t*)buf;
break;
case MJS_DATAVIEW_I8:
value = *(int8_t*)buf;
break;
case MJS_DATAVIEW_U16:
value = *(uint16_t*)buf;
break;
case MJS_DATAVIEW_I16:
value = *(int16_t*)buf;
break;
case MJS_DATAVIEW_U32:
value = *(uint32_t*)buf;
break;
case MJS_DATAVIEW_I32:
value = *(int32_t*)buf;
break;
default:
break;
}
return value;
}
static void set_value(char* buf, int64_t value, mjs_dataview_type_t type) {
switch(type) {
case MJS_DATAVIEW_U8:
*(uint8_t*)buf = (uint8_t)value;
break;
case MJS_DATAVIEW_I8:
*(int8_t*)buf = (int8_t)value;
break;
case MJS_DATAVIEW_U16:
*(uint16_t*)buf = (uint16_t)value;
break;
case MJS_DATAVIEW_I16:
*(int16_t*)buf = (int16_t)value;
break;
case MJS_DATAVIEW_U32:
*(uint32_t*)buf = (uint32_t)value;
break;
case MJS_DATAVIEW_I32:
*(int32_t*)buf = (int32_t)value;
break;
default:
break;
}
}
static mjs_val_t mjs_dataview_get(struct mjs* mjs, mjs_val_t obj, size_t index) {
mjs_val_t buf_obj = mjs_get(mjs, obj, "buffer", -1);
size_t byte_len = 0;
char* buf = mjs_array_buf_get_ptr(mjs, buf_obj, &byte_len);
mjs_dataview_type_t type = mjs_get_int(mjs, mjs_get(mjs, obj, "_t", -1));
if((mjs_dataview_get_element_len(type) * (index + 1)) > byte_len) {
return MJS_UNDEFINED;
}
buf += mjs_dataview_get_element_len(type) * index;
int64_t value = get_value(buf, type);
return mjs_mk_number(mjs, value);
}
static mjs_err_t mjs_dataview_set(struct mjs* mjs, mjs_val_t obj, size_t index, int64_t value) {
mjs_val_t buf_obj = mjs_get(mjs, obj, "buffer", -1);
size_t byte_len = 0;
char* buf = mjs_array_buf_get_ptr(mjs, buf_obj, &byte_len);
mjs_dataview_type_t type = mjs_get_int(mjs, mjs_get(mjs, obj, "_t", -1));
if((mjs_dataview_get_element_len(type) * (index + 1)) > byte_len) {
return MJS_TYPE_ERROR;
}
buf += mjs_dataview_get_element_len(type) * index;
set_value(buf, value, type);
return MJS_OK;
}
mjs_val_t mjs_dataview_get_prop(struct mjs* mjs, mjs_val_t obj, mjs_val_t key) {
if(!mjs_is_number(key)) {
return MJS_UNDEFINED;
}
int index = mjs_get_int(mjs, key);
return mjs_dataview_get(mjs, obj, index);
}
mjs_err_t mjs_dataview_set_prop(struct mjs* mjs, mjs_val_t obj, mjs_val_t key, mjs_val_t val) {
if(!mjs_is_number(key)) {
return MJS_TYPE_ERROR;
}
int index = mjs_get_int(mjs, key);
int64_t value = 0;
if(mjs_is_number(val)) {
value = mjs_get_double(mjs, val);
} else if(mjs_is_boolean(val)) {
value = mjs_get_bool(mjs, val) ? (1) : (0);
}
return mjs_dataview_set(mjs, obj, index, value);
}
mjs_val_t mjs_dataview_get_buf(struct mjs* mjs, mjs_val_t obj) {
return mjs_get(mjs, obj, "buffer", -1);
}
mjs_val_t mjs_dataview_get_len(struct mjs* mjs, mjs_val_t obj) {
size_t bytelen = 0;
mjs_array_buf_get_ptr(mjs, mjs_dataview_get_buf(mjs, obj), &bytelen);
mjs_dataview_type_t type = mjs_get_int(mjs, mjs_get(mjs, obj, "_t", -1));
size_t element_len = mjs_dataview_get_element_len(type);
return mjs_mk_number(mjs, bytelen / element_len);
}
mjs_val_t mjs_mk_array_buf(struct mjs* mjs, char* data, size_t buf_len) {
struct mbuf* m = &mjs->array_buffers;
if((m->len + buf_len) > m->size) {
char* prev_buf = m->buf;
mbuf_resize(m, m->len + buf_len + MJS_ARRAY_BUF_RESERVE);
if(data >= prev_buf && data < (prev_buf + m->len)) {
data += m->buf - prev_buf;
}
}
size_t offset = m->len;
char* prev_buf = m->buf;
size_t header_len = cs_varint_llen(buf_len);
mbuf_insert(m, offset, NULL, header_len + buf_len);
if(data >= prev_buf && data < (prev_buf + m->len)) {
data += m->buf - prev_buf;
}
cs_varint_encode(buf_len, (unsigned char*)m->buf + offset, header_len);
if(data != NULL) {
memcpy(m->buf + offset + header_len, data, buf_len);
} else {
memset(m->buf + offset + header_len, 0, buf_len);
}
return (offset & ~MJS_TAG_MASK) | MJS_TAG_ARRAY_BUF;
}
void mjs_array_buf_slice(struct mjs* mjs) {
size_t nargs = mjs_nargs(mjs);
mjs_val_t src = mjs_get_this(mjs);
size_t start = 0;
size_t end = 0;
char* src_buf = NULL;
size_t src_len = 0;
bool args_correct = false;
do {
if(!mjs_is_array_buf(src)) {
break;
}
src_buf = mjs_array_buf_get_ptr(mjs, src, &src_len);
if((nargs == 0) || (nargs > 2)) {
break;
}
mjs_val_t start_obj = mjs_arg(mjs, 0);
if(!mjs_is_number(start_obj)) {
break;
}
start = mjs_get_int32(mjs, start_obj);
if(nargs == 2) {
mjs_val_t end_obj = mjs_arg(mjs, 1);
if(!mjs_is_number(end_obj)) {
break;
}
end = mjs_get_int32(mjs, end_obj);
} else {
end = src_len - 1;
}
if((start >= src_len) || (end >= src_len) || (start >= end)) {
break;
}
args_correct = true;
} while(0);
if(!args_correct) {
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "");
mjs_return(mjs, MJS_UNDEFINED);
return;
}
src_buf += start;
mjs_return(mjs, mjs_mk_array_buf(mjs, src_buf, end - start));
}
static mjs_val_t
mjs_mk_dataview_from_buf(struct mjs* mjs, mjs_val_t buf, mjs_dataview_type_t type) {
size_t len = 0;
mjs_array_buf_get_ptr(mjs, buf, &len);
if(len % mjs_dataview_get_element_len(type) != 0) {
mjs_prepend_errorf(
mjs, MJS_BAD_ARGS_ERROR, "Buffer len is not a multiple of element size");
return MJS_UNDEFINED;
}
mjs_val_t view_obj = mjs_mk_object(mjs);
mjs_set(mjs, view_obj, "_t", ~0, mjs_mk_number(mjs, (double)type));
mjs_set(mjs, view_obj, "buffer", ~0, buf);
view_obj &= ~MJS_TAG_MASK;
view_obj |= MJS_TAG_ARRAY_BUF_VIEW;
mjs_dataview_get(mjs, view_obj, 0);
return view_obj;
}
static mjs_val_t
mjs_mk_dataview(struct mjs* mjs, size_t len, mjs_val_t arr, mjs_dataview_type_t type) {
size_t elements_nb = 0;
if(mjs_is_array(arr)) {
if(!mjs_is_number(mjs_array_get(mjs, arr, 0))) {
return MJS_UNDEFINED;
}
elements_nb = mjs_array_length(mjs, arr);
} else {
elements_nb = len;
}
size_t element_len = mjs_dataview_get_element_len(type);
mjs_val_t buf_obj = mjs_mk_array_buf(mjs, NULL, element_len * elements_nb);
if(mjs_is_array(arr)) {
char* buf_ptr = mjs_array_buf_get_ptr(mjs, buf_obj, NULL);
for(uint8_t i = 0; i < elements_nb; i++) {
int64_t value = mjs_get_double(mjs, mjs_array_get(mjs, arr, i));
set_value(buf_ptr, value, type);
buf_ptr += element_len;
}
}
return mjs_mk_dataview_from_buf(mjs, buf_obj, type);
}
static void mjs_array_buf_new(struct mjs* mjs) {
mjs_val_t len_arg = mjs_arg(mjs, 0);
mjs_val_t buf_obj = MJS_UNDEFINED;
if(!mjs_is_number(len_arg)) {
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "");
} else {
int len = mjs_get_int(mjs, len_arg);
buf_obj = mjs_mk_array_buf(mjs, NULL, len);
}
mjs_return(mjs, buf_obj);
}
static void mjs_dataview_new(struct mjs* mjs, mjs_dataview_type_t type) {
mjs_val_t view_arg = mjs_arg(mjs, 0);
mjs_val_t view_obj = MJS_UNDEFINED;
if(mjs_is_array_buf(view_arg)) { // Create a view of existing ArrayBuf
view_obj = mjs_mk_dataview_from_buf(mjs, view_arg, type);
} else if(mjs_is_number(view_arg)) { // Create new typed array
int len = mjs_get_int(mjs, view_arg);
view_obj = mjs_mk_dataview(mjs, len, MJS_UNDEFINED, type);
} else if(mjs_is_array(view_arg)) { // Create new typed array from array
view_obj = mjs_mk_dataview(mjs, 0, view_arg, type);
} else {
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "");
}
mjs_return(mjs, view_obj);
}
static void mjs_new_u8_array(struct mjs* mjs) {
mjs_dataview_new(mjs, MJS_DATAVIEW_U8);
}
static void mjs_new_i8_array(struct mjs* mjs) {
mjs_dataview_new(mjs, MJS_DATAVIEW_I8);
}
static void mjs_new_u16_array(struct mjs* mjs) {
mjs_dataview_new(mjs, MJS_DATAVIEW_U16);
}
static void mjs_new_i16_array(struct mjs* mjs) {
mjs_dataview_new(mjs, MJS_DATAVIEW_I16);
}
static void mjs_new_u32_array(struct mjs* mjs) {
mjs_dataview_new(mjs, MJS_DATAVIEW_U32);
}
static void mjs_new_i32_array(struct mjs* mjs) {
mjs_dataview_new(mjs, MJS_DATAVIEW_I32);
}
void mjs_init_builtin_array_buf(struct mjs* mjs, mjs_val_t obj) {
mjs_set(mjs, obj, "ArrayBuffer", ~0, MJS_MK_FN(mjs_array_buf_new));
mjs_set(mjs, obj, "Uint8Array", ~0, MJS_MK_FN(mjs_new_u8_array));
mjs_set(mjs, obj, "Int8Array", ~0, MJS_MK_FN(mjs_new_i8_array));
mjs_set(mjs, obj, "Uint16Array", ~0, MJS_MK_FN(mjs_new_u16_array));
mjs_set(mjs, obj, "Int16Array", ~0, MJS_MK_FN(mjs_new_i16_array));
mjs_set(mjs, obj, "Uint32Array", ~0, MJS_MK_FN(mjs_new_u32_array));
mjs_set(mjs, obj, "Int32Array", ~0, MJS_MK_FN(mjs_new_i32_array));
}