mirror of
https://github.com/DarkFlippers/unleashed-firmware.git
synced 2024-11-30 07:06:12 +03:00
0154018363
* 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>
568 lines
17 KiB
C
568 lines
17 KiB
C
/*
|
|
* Copyright (c) 2017 Cesanta Software Limited
|
|
* All rights reserved
|
|
*/
|
|
|
|
#include "common/cs_varint.h"
|
|
#include "common/frozen/frozen.h"
|
|
#include "mjs_array.h"
|
|
#include "mjs_bcode.h"
|
|
#include "mjs_core.h"
|
|
#include "mjs_internal.h"
|
|
#include "mjs_object.h"
|
|
#include "mjs_primitive.h"
|
|
#include "mjs_string.h"
|
|
#include "mjs_util.h"
|
|
#include "mjs_tok.h"
|
|
#include "mjs_array_buf.h"
|
|
#include <furi.h>
|
|
|
|
const char* mjs_typeof(mjs_val_t v) {
|
|
return mjs_stringify_type(mjs_get_type(v));
|
|
}
|
|
|
|
MJS_PRIVATE const char* mjs_stringify_type(enum mjs_type t) {
|
|
switch(t) {
|
|
case MJS_TYPE_NUMBER:
|
|
return "number";
|
|
case MJS_TYPE_BOOLEAN:
|
|
return "boolean";
|
|
case MJS_TYPE_STRING:
|
|
return "string";
|
|
case MJS_TYPE_OBJECT_ARRAY:
|
|
return "array";
|
|
case MJS_TYPE_OBJECT_GENERIC:
|
|
return "object";
|
|
case MJS_TYPE_FOREIGN:
|
|
return "foreign_ptr";
|
|
case MJS_TYPE_OBJECT_FUNCTION:
|
|
return "function";
|
|
case MJS_TYPE_NULL:
|
|
return "null";
|
|
case MJS_TYPE_UNDEFINED:
|
|
return "undefined";
|
|
case MJS_TYPE_ARRAY_BUF:
|
|
return "array_buf";
|
|
case MJS_TYPE_ARRAY_BUF_VIEW:
|
|
return "data_view";
|
|
default:
|
|
return "???";
|
|
}
|
|
}
|
|
|
|
void mjs_jprintf(mjs_val_t v, struct mjs* mjs, struct json_out* out) {
|
|
if(mjs_is_number(v)) {
|
|
double iv, d = mjs_get_double(mjs, v);
|
|
if(modf(d, &iv) == 0) {
|
|
json_printf(out, "%" INT64_FMT, (int64_t)d);
|
|
} else {
|
|
json_printf(out, "%f", mjs_get_double(mjs, v));
|
|
}
|
|
} else if(mjs_is_boolean(v)) {
|
|
json_printf(out, "%s", mjs_get_bool(mjs, v) ? "true" : "false");
|
|
} else if(mjs_is_string(v)) {
|
|
size_t i, size;
|
|
const char* s = mjs_get_string(mjs, &v, &size);
|
|
for(i = 0; i < size; i++) {
|
|
int ch = ((unsigned char*)s)[i];
|
|
if(isprint(ch)) {
|
|
json_printf(out, "%c", ch);
|
|
} else {
|
|
json_printf(out, "%s%02x", "\\x", ch);
|
|
}
|
|
}
|
|
} else if(mjs_is_array(v)) {
|
|
json_printf(out, "%s", "<array>");
|
|
} else if(mjs_is_object(v)) {
|
|
json_printf(out, "%s", "<object>");
|
|
} else if(mjs_is_foreign(v)) {
|
|
json_printf(
|
|
out, "%s%lx%s", "<foreign_ptr@", (unsigned long)(uintptr_t)mjs_get_ptr(mjs, v), ">");
|
|
} else if(mjs_is_function(v)) {
|
|
json_printf(out, "%s%d%s", "<function@", (int)mjs_get_func_addr(v), ">");
|
|
} else if(mjs_is_null(v)) {
|
|
json_printf(out, "%s", "null");
|
|
} else if(mjs_is_undefined(v)) {
|
|
json_printf(out, "%s", "undefined");
|
|
} else {
|
|
json_printf(out, "%s%" INT64_FMT "%s", "<???", (int64_t)v, ">");
|
|
}
|
|
}
|
|
|
|
void mjs_sprintf(mjs_val_t v, struct mjs* mjs, char* buf, size_t n) {
|
|
struct json_out out = JSON_OUT_BUF(buf, n);
|
|
mjs_jprintf(v, mjs, &out);
|
|
}
|
|
|
|
void mjs_fprintf(mjs_val_t v, struct mjs* mjs, FILE* fp) {
|
|
struct json_out out = JSON_OUT_FILE(fp);
|
|
mjs_jprintf(v, mjs, &out);
|
|
}
|
|
|
|
MJS_PRIVATE const char* opcodetostr(uint8_t opcode) {
|
|
static const char* names[] = {
|
|
"NOP",
|
|
"DROP",
|
|
"DUP",
|
|
"SWAP",
|
|
"JMP",
|
|
"JMP_TRUE",
|
|
"JMP_NEUTRAL_TRUE",
|
|
"JMP_FALSE",
|
|
"JMP_NEUTRAL_FALSE",
|
|
"FIND_SCOPE",
|
|
"PUSH_SCOPE",
|
|
"PUSH_STR",
|
|
"PUSH_TRUE",
|
|
"PUSH_FALSE",
|
|
"PUSH_INT",
|
|
"PUSH_DBL",
|
|
"PUSH_NULL",
|
|
"PUSH_UNDEF",
|
|
"PUSH_OBJ",
|
|
"PUSH_ARRAY",
|
|
"PUSH_FUNC",
|
|
"PUSH_THIS",
|
|
"GET",
|
|
"CREATE",
|
|
"EXPR",
|
|
"APPEND",
|
|
"SET_ARG",
|
|
"NEW_SCOPE",
|
|
"DEL_SCOPE",
|
|
"CALL",
|
|
"RETURN",
|
|
"LOOP",
|
|
"BREAK",
|
|
"CONTINUE",
|
|
"SETRETVAL",
|
|
"EXIT",
|
|
"BCODE_HDR",
|
|
"ARGS",
|
|
"FOR_IN_NEXT",
|
|
};
|
|
const char* name = "???";
|
|
assert(ARRAY_SIZE(names) == OP_MAX);
|
|
if(opcode < ARRAY_SIZE(names)) name = names[opcode];
|
|
return name;
|
|
}
|
|
|
|
MJS_PRIVATE size_t
|
|
mjs_disasm_single(const uint8_t* code, size_t i, MjsPrintCallback print_cb, void* print_ctx) {
|
|
char buf[40];
|
|
size_t start_i = i;
|
|
size_t llen;
|
|
uint64_t n;
|
|
|
|
furi_assert(print_cb);
|
|
|
|
snprintf(buf, sizeof(buf), "%-3u\t%-8s", (unsigned)i, opcodetostr(code[i]));
|
|
|
|
switch(code[i]) {
|
|
case OP_PUSH_FUNC: {
|
|
cs_varint_decode(&code[i + 1], ~0, &n, &llen);
|
|
print_cb(print_ctx, "%s %04u", buf, (unsigned)(i - n));
|
|
i += llen;
|
|
break;
|
|
}
|
|
case OP_PUSH_INT: {
|
|
cs_varint_decode(&code[i + 1], ~0, &n, &llen);
|
|
print_cb(print_ctx, "%s\t%lu", buf, (unsigned long)n);
|
|
i += llen;
|
|
break;
|
|
}
|
|
case OP_SET_ARG: {
|
|
size_t llen2;
|
|
uint64_t arg_no;
|
|
cs_varint_decode(&code[i + 1], ~0, &arg_no, &llen);
|
|
cs_varint_decode(&code[i + llen + 1], ~0, &n, &llen2);
|
|
print_cb(
|
|
print_ctx, "%s\t[%.*s] %u", buf, (int)n, code + i + 1 + llen + llen2, (unsigned)arg_no);
|
|
i += llen + llen2 + n;
|
|
break;
|
|
}
|
|
case OP_PUSH_STR:
|
|
case OP_PUSH_DBL: {
|
|
cs_varint_decode(&code[i + 1], ~0, &n, &llen);
|
|
print_cb(print_ctx, "%s\t[%.*s]", buf, (int)n, code + i + 1 + llen);
|
|
i += llen + n;
|
|
break;
|
|
}
|
|
case OP_JMP:
|
|
case OP_JMP_TRUE:
|
|
case OP_JMP_NEUTRAL_TRUE:
|
|
case OP_JMP_FALSE:
|
|
case OP_JMP_NEUTRAL_FALSE: {
|
|
cs_varint_decode(&code[i + 1], ~0, &n, &llen);
|
|
print_cb(
|
|
print_ctx,
|
|
"%s\t%u",
|
|
buf,
|
|
(unsigned)(i + n + llen + 1 /* becaue i will be incremented on the usual terms */));
|
|
i += llen;
|
|
break;
|
|
}
|
|
case OP_LOOP: {
|
|
size_t l1, l2;
|
|
uint64_t n1, n2;
|
|
cs_varint_decode(&code[i + 1], ~0, &n1, &l1);
|
|
cs_varint_decode(&code[i + l1 + 1], ~0, &n2, &l2);
|
|
print_cb(
|
|
print_ctx,
|
|
"%s\tB:%lu C:%lu (%d)",
|
|
buf,
|
|
(unsigned long)(i + 1 /* OP_LOOP */ + l1 + n1),
|
|
(unsigned long)(i + 1 /* OP_LOOP */ + l1 + l2 + n2),
|
|
(int)i);
|
|
i += l1 + l2;
|
|
break;
|
|
}
|
|
case OP_EXPR: {
|
|
int op = code[i + 1];
|
|
const char* name = "???";
|
|
/* clang-format off */
|
|
switch (op) {
|
|
case TOK_DOT: name = "."; break;
|
|
case TOK_MINUS: name = "-"; break;
|
|
case TOK_PLUS: name = "+"; break;
|
|
case TOK_MUL: name = "*"; break;
|
|
case TOK_DIV: name = "/"; break;
|
|
case TOK_REM: name = "%"; break;
|
|
case TOK_XOR: name = "^"; break;
|
|
case TOK_AND: name = "&"; break;
|
|
case TOK_OR: name = "|"; break;
|
|
case TOK_LSHIFT: name = "<<"; break;
|
|
case TOK_RSHIFT: name = ">>"; break;
|
|
case TOK_URSHIFT: name = ">>>"; break;
|
|
case TOK_UNARY_MINUS: name = "- (unary)"; break;
|
|
case TOK_UNARY_PLUS: name = "+ (unary)"; break;
|
|
case TOK_NOT: name = "!"; break;
|
|
case TOK_TILDA: name = "~"; break;
|
|
case TOK_EQ: name = "=="; break;
|
|
case TOK_NE: name = "!="; break;
|
|
case TOK_EQ_EQ: name = "==="; break;
|
|
case TOK_NE_NE: name = "!=="; break;
|
|
case TOK_LT: name = "<"; break;
|
|
case TOK_GT: name = ">"; break;
|
|
case TOK_LE: name = "<="; break;
|
|
case TOK_GE: name = ">="; break;
|
|
case TOK_ASSIGN: name = "="; break;
|
|
case TOK_POSTFIX_PLUS: name = "++ (postfix)"; break;
|
|
case TOK_POSTFIX_MINUS: name = "-- (postfix)"; break;
|
|
case TOK_MINUS_MINUS: name = "--"; break;
|
|
case TOK_PLUS_PLUS: name = "++"; break;
|
|
case TOK_LOGICAL_AND: name = "&&"; break;
|
|
case TOK_LOGICAL_OR: name = "||"; break;
|
|
case TOK_KEYWORD_TYPEOF: name = "typeof"; break;
|
|
case TOK_PLUS_ASSIGN: name = "+="; break;
|
|
case TOK_MINUS_ASSIGN: name = "-="; break;
|
|
case TOK_MUL_ASSIGN: name = "*="; break;
|
|
case TOK_DIV_ASSIGN: name = "/="; break;
|
|
case TOK_REM_ASSIGN: name = "%="; break;
|
|
case TOK_XOR_ASSIGN: name = "^="; break;
|
|
case TOK_AND_ASSIGN: name = "&="; break;
|
|
case TOK_OR_ASSIGN: name = "|="; break;
|
|
case TOK_LSHIFT_ASSIGN: name = "<<="; break;
|
|
case TOK_RSHIFT_ASSIGN: name = ">>="; break;
|
|
case TOK_URSHIFT_ASSIGN: name = ">>>="; break;
|
|
}
|
|
/* clang-format on */
|
|
print_cb(print_ctx, "%s\t%s", buf, name);
|
|
i++;
|
|
break;
|
|
}
|
|
case OP_BCODE_HEADER: {
|
|
size_t start = 0;
|
|
mjs_header_item_t map_offset = 0, total_size = 0;
|
|
start = i;
|
|
memcpy(&total_size, &code[i + 1], sizeof(total_size));
|
|
memcpy(
|
|
&map_offset,
|
|
&code[i + 1 + MJS_HDR_ITEM_MAP_OFFSET * sizeof(total_size)],
|
|
sizeof(map_offset));
|
|
i += sizeof(mjs_header_item_t) * MJS_HDR_ITEMS_CNT;
|
|
print_cb(
|
|
print_ctx,
|
|
"%s\t[%s] end:%lu map_offset: %lu",
|
|
buf,
|
|
&code[i + 1],
|
|
(unsigned long)start + total_size,
|
|
(unsigned long)start + map_offset);
|
|
i += strlen((char*)(code + i + 1)) + 1;
|
|
break;
|
|
}
|
|
default:
|
|
print_cb(print_ctx, "%s", buf);
|
|
break;
|
|
}
|
|
return i - start_i;
|
|
}
|
|
|
|
void mjs_disasm(const uint8_t* code, size_t len, MjsPrintCallback print_cb, void* print_ctx) {
|
|
size_t i, start = 0;
|
|
mjs_header_item_t map_offset = 0, total_size = 0;
|
|
|
|
for(i = 0; i < len; i++) {
|
|
size_t delta = mjs_disasm_single(code, i, print_cb, print_ctx);
|
|
if(code[i] == OP_BCODE_HEADER) {
|
|
start = i;
|
|
memcpy(&total_size, &code[i + 1], sizeof(total_size));
|
|
memcpy(
|
|
&map_offset,
|
|
&code[i + 1 + MJS_HDR_ITEM_MAP_OFFSET * sizeof(total_size)],
|
|
sizeof(map_offset));
|
|
}
|
|
|
|
i += delta;
|
|
|
|
if(map_offset > 0 && i == start + map_offset) {
|
|
i = start + total_size - 1;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
void mjs_disasm_all(struct mjs* mjs, MjsPrintCallback print_cb, void* print_ctx) {
|
|
int parts_cnt = mjs_bcode_parts_cnt(mjs);
|
|
for(int i = 0; i < parts_cnt; i++) {
|
|
struct mjs_bcode_part* bp = mjs_bcode_part_get(mjs, i);
|
|
mjs_disasm((uint8_t*)bp->data.p, bp->data.len, print_cb, print_ctx);
|
|
}
|
|
}
|
|
|
|
static void mjs_dump_obj_stack(
|
|
struct mjs* mjs,
|
|
const char* name,
|
|
const struct mbuf* m,
|
|
MjsPrintCallback print_cb,
|
|
void* print_ctx) {
|
|
char buf[50];
|
|
size_t i, n;
|
|
n = mjs_stack_size(m);
|
|
print_cb(print_ctx, "%12s (%d elems): ", name, (int)n);
|
|
for(i = 0; i < n; i++) {
|
|
mjs_sprintf(((mjs_val_t*)m->buf)[i], mjs, buf, sizeof(buf));
|
|
print_cb(print_ctx, "%34s", buf);
|
|
}
|
|
}
|
|
|
|
void mjs_dump(struct mjs* mjs, int do_disasm, MjsPrintCallback print_cb, void* print_ctx) {
|
|
print_cb(print_ctx, "------- MJS VM DUMP BEGIN");
|
|
mjs_dump_obj_stack(mjs, "DATA_STACK", &mjs->stack, print_cb, print_ctx);
|
|
mjs_dump_obj_stack(mjs, "CALL_STACK", &mjs->call_stack, print_cb, print_ctx);
|
|
mjs_dump_obj_stack(mjs, "SCOPES", &mjs->scopes, print_cb, print_ctx);
|
|
mjs_dump_obj_stack(mjs, "LOOP_OFFSETS", &mjs->loop_addresses, print_cb, print_ctx);
|
|
mjs_dump_obj_stack(mjs, "ARG_STACK", &mjs->arg_stack, print_cb, print_ctx);
|
|
if(do_disasm) {
|
|
int parts_cnt = mjs_bcode_parts_cnt(mjs);
|
|
int i;
|
|
print_cb(print_ctx, "%23s", "CODE:");
|
|
for(i = 0; i < parts_cnt; i++) {
|
|
struct mjs_bcode_part* bp = mjs_bcode_part_get(mjs, i);
|
|
mjs_disasm((uint8_t*)bp->data.p, bp->data.len, print_cb, print_ctx);
|
|
}
|
|
}
|
|
print_cb(print_ctx, "------- MJS VM DUMP END");
|
|
}
|
|
|
|
MJS_PRIVATE int mjs_check_arg(
|
|
struct mjs* mjs,
|
|
int arg_num,
|
|
const char* arg_name,
|
|
enum mjs_type expected_type,
|
|
mjs_val_t* parg) {
|
|
mjs_val_t arg = MJS_UNDEFINED;
|
|
enum mjs_type actual_type;
|
|
|
|
if(arg_num >= 0) {
|
|
int nargs = mjs_nargs(mjs);
|
|
if(nargs < arg_num + 1) {
|
|
mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "missing argument %s", arg_name);
|
|
return 0;
|
|
}
|
|
|
|
arg = mjs_arg(mjs, arg_num);
|
|
} else {
|
|
/* use `this` */
|
|
arg = mjs->vals.this_obj;
|
|
}
|
|
|
|
actual_type = mjs_get_type(arg);
|
|
if(actual_type != expected_type) {
|
|
mjs_prepend_errorf(
|
|
mjs,
|
|
MJS_TYPE_ERROR,
|
|
"%s should be a %s, %s given",
|
|
arg_name,
|
|
mjs_stringify_type(expected_type),
|
|
mjs_stringify_type(actual_type));
|
|
return 0;
|
|
}
|
|
|
|
if(parg != NULL) {
|
|
*parg = arg;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
MJS_PRIVATE int mjs_normalize_idx(int idx, int size) {
|
|
if(idx < 0) {
|
|
idx = size + idx;
|
|
if(idx < 0) {
|
|
idx = 0;
|
|
}
|
|
}
|
|
if(idx > size) {
|
|
idx = size;
|
|
}
|
|
return idx;
|
|
}
|
|
|
|
MJS_PRIVATE const char* mjs_get_bcode_filename(struct mjs* mjs, struct mjs_bcode_part* bp) {
|
|
(void)mjs;
|
|
return bp->data.p + 1 /* OP_BCODE_HEADER */ + sizeof(mjs_header_item_t) * MJS_HDR_ITEMS_CNT;
|
|
}
|
|
|
|
const char* mjs_get_bcode_filename_by_offset(struct mjs* mjs, int offset) {
|
|
const char* ret = NULL;
|
|
struct mjs_bcode_part* bp = mjs_bcode_part_get_by_offset(mjs, offset);
|
|
if(bp != NULL) {
|
|
ret = mjs_get_bcode_filename(mjs, bp);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
int mjs_get_lineno_by_offset(struct mjs* mjs, int offset) {
|
|
size_t llen;
|
|
uint64_t map_len;
|
|
int prev_line_no, ret = 1;
|
|
struct mjs_bcode_part* bp = mjs_bcode_part_get_by_offset(mjs, offset);
|
|
uint8_t *p, *pe;
|
|
if(bp != NULL) {
|
|
mjs_header_item_t map_offset, bcode_offset;
|
|
memcpy(
|
|
&map_offset,
|
|
bp->data.p + 1 /* OP_BCODE_HEADER */ +
|
|
sizeof(mjs_header_item_t) * MJS_HDR_ITEM_MAP_OFFSET,
|
|
sizeof(map_offset));
|
|
|
|
memcpy(
|
|
&bcode_offset,
|
|
bp->data.p + 1 /* OP_BCODE_HEADER */ +
|
|
sizeof(mjs_header_item_t) * MJS_HDR_ITEM_BCODE_OFFSET,
|
|
sizeof(bcode_offset));
|
|
|
|
offset -= (1 /* OP_BCODE_HEADER */ + bcode_offset) + bp->start_idx;
|
|
|
|
/* get pointer to the length of the map followed by the map itself */
|
|
p = (uint8_t*)bp->data.p + 1 /* OP_BCODE_HEADER */ + map_offset;
|
|
|
|
cs_varint_decode(p, ~0, &map_len, &llen);
|
|
p += llen;
|
|
pe = p + map_len;
|
|
|
|
prev_line_no = 1;
|
|
while(p < pe) {
|
|
uint64_t cur_offset, line_no;
|
|
cs_varint_decode(p, ~0, &cur_offset, &llen);
|
|
p += llen;
|
|
cs_varint_decode(p, ~0, &line_no, &llen);
|
|
p += llen;
|
|
|
|
if(cur_offset >= (uint64_t)offset) {
|
|
ret = prev_line_no;
|
|
break;
|
|
}
|
|
prev_line_no = line_no;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
int mjs_get_offset_by_call_frame_num(struct mjs* mjs, int cf_num) {
|
|
int ret = -1;
|
|
if(cf_num == 0) {
|
|
/* Return current bcode offset */
|
|
ret = mjs->cur_bcode_offset;
|
|
} else if(
|
|
cf_num > 0 &&
|
|
mjs->call_stack.len >= sizeof(mjs_val_t) * CALL_STACK_FRAME_ITEMS_CNT * cf_num) {
|
|
/* Get offset from the call_stack */
|
|
int pos = CALL_STACK_FRAME_ITEM_RETURN_ADDR + CALL_STACK_FRAME_ITEMS_CNT * (cf_num - 1);
|
|
mjs_val_t val = *vptr(&mjs->call_stack, -1 - pos);
|
|
ret = mjs_get_int(mjs, val);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
mjs_err_t mjs_to_string(struct mjs* mjs, mjs_val_t* v, char** p, size_t* sizep, int* need_free) {
|
|
mjs_err_t ret = MJS_OK;
|
|
|
|
*p = NULL;
|
|
*sizep = 0;
|
|
*need_free = 0;
|
|
|
|
if(mjs_is_string(*v)) {
|
|
*p = (char*)mjs_get_string(mjs, v, sizep);
|
|
} else if(mjs_is_number(*v)) {
|
|
char buf[50] = "";
|
|
struct json_out out = JSON_OUT_BUF(buf, sizeof(buf));
|
|
mjs_jprintf(*v, mjs, &out);
|
|
*sizep = strlen(buf);
|
|
*p = malloc(*sizep + 1);
|
|
if(*p == NULL) {
|
|
ret = MJS_OUT_OF_MEMORY;
|
|
goto clean;
|
|
}
|
|
memmove(*p, buf, *sizep + 1);
|
|
*need_free = 1;
|
|
} else if(mjs_is_boolean(*v)) {
|
|
if(mjs_get_bool(mjs, *v)) {
|
|
*p = "true";
|
|
*sizep = 4;
|
|
} else {
|
|
*p = "false";
|
|
*sizep = 5;
|
|
}
|
|
} else if(mjs_is_undefined(*v)) {
|
|
*p = "undefined";
|
|
*sizep = 9;
|
|
} else if(mjs_is_null(*v)) {
|
|
*p = "null";
|
|
*sizep = 4;
|
|
} else if(mjs_is_object(*v)) {
|
|
ret = MJS_TYPE_ERROR;
|
|
mjs_set_errorf(mjs, ret, "conversion from object to string is not supported");
|
|
} else if(mjs_is_foreign(*v)) {
|
|
*p = "TODO_foreign";
|
|
*sizep = 12;
|
|
} else if(mjs_is_typed_array(*v)) {
|
|
*p = "TODO_typed_array";
|
|
*sizep = 16;
|
|
} else {
|
|
ret = MJS_TYPE_ERROR;
|
|
mjs_set_errorf(mjs, ret, "unknown type to convert to string");
|
|
}
|
|
|
|
clean:
|
|
return ret;
|
|
}
|
|
|
|
mjs_val_t mjs_to_boolean_v(struct mjs* mjs, mjs_val_t v) {
|
|
size_t len;
|
|
int is_truthy;
|
|
|
|
is_truthy = ((mjs_is_boolean(v) && mjs_get_bool(mjs, v)) ||
|
|
(mjs_is_number(v) && mjs_get_double(mjs, v) != (double)0.0) ||
|
|
(mjs_is_string(v) && mjs_get_string(mjs, &v, &len) && len > 0) ||
|
|
(mjs_is_function(v)) || (mjs_is_foreign(v)) || (mjs_is_object(v))) &&
|
|
v != MJS_TAG_NAN;
|
|
|
|
return mjs_mk_boolean(mjs, is_truthy);
|
|
}
|
|
|
|
int mjs_is_truthy(struct mjs* mjs, mjs_val_t v) {
|
|
return mjs_get_bool(mjs, mjs_to_boolean_v(mjs, v));
|
|
}
|