From 22c638beac4721ed6f94f9a3d51b4a77f329a502 Mon Sep 17 00:00:00 2001 From: SG Date: Fri, 15 Sep 2023 18:41:21 +0300 Subject: [PATCH 01/31] FBT: cdefines to env, libs order --- scripts/fbt_tools/fbt_extapps.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/fbt_tools/fbt_extapps.py b/scripts/fbt_tools/fbt_extapps.py index 6059628f004..0c92a205b0b 100644 --- a/scripts/fbt_tools/fbt_extapps.py +++ b/scripts/fbt_tools/fbt_extapps.py @@ -55,7 +55,8 @@ def _setup_app_env(self): ) self.app_env.Append( CPPDEFINES=[ - ("FAP_VERSION", f'"{".".join(map(str, self.app.fap_version))}"') + ("FAP_VERSION", f'"{".".join(map(str, self.app.fap_version))}"'), + *self.app.cdefines, ], ) self.app_env.VariantDir(self.app_work_dir, self.app._appdir, duplicate=False) @@ -138,7 +139,7 @@ def _build_private_lib(self, lib_def): def _build_app(self): self.app_env.Append( - LIBS=[*self.app.fap_libs, *self.private_libs], + LIBS=[*self.app.fap_libs, *self.private_libs, *self.app.fap_libs], CPPPATH=[self.app_env.Dir(self.app_work_dir), self.app._appdir], ) From 5ce03c575f3129b66147b80f562f9137879c565f Mon Sep 17 00:00:00 2001 From: SG Date: Fri, 15 Sep 2023 18:41:42 +0300 Subject: [PATCH 02/31] API: strtod, modf, itoa, calloc --- firmware/targets/f7/api_symbols.csv | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 0819851543b..dd86d5ac119 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -632,7 +632,7 @@ Function,+,byte_input_get_view,View*,ByteInput* Function,+,byte_input_set_header_text,void,"ByteInput*, const char*" Function,+,byte_input_set_result_callback,void,"ByteInput*, ByteInputCallback, ByteChangedCallback, void*, uint8_t*, uint8_t" Function,-,bzero,void,"void*, size_t" -Function,-,calloc,void*,"size_t, size_t" +Function,+,calloc,void*,"size_t, size_t" Function,+,canvas_clear,void,Canvas* Function,+,canvas_commit,void,Canvas* Function,+,canvas_current_font_height,uint8_t,const Canvas* @@ -1792,7 +1792,7 @@ Function,-,isupper,int,int Function,-,isupper_l,int,"int, locale_t" Function,-,isxdigit,int,int Function,-,isxdigit_l,int,"int, locale_t" -Function,-,itoa,char*,"int, char*, int" +Function,+,itoa,char*,"int, char*, int" Function,-,j0,double,double Function,-,j0f,float,float Function,-,j1,double,double @@ -2080,7 +2080,7 @@ Function,-,mkstemp,int,char* Function,-,mkstemps,int,"char*, int" Function,-,mktemp,char*,char* Function,-,mktime,time_t,tm* -Function,-,modf,double,"double, double*" +Function,+,modf,double,"double, double*" Function,-,modff,float,"float, float*" Function,-,modfl,long double,"long double, long double*" Function,-,mrand48,long, @@ -2699,7 +2699,7 @@ Function,-,strsep,char*,"char**, const char*" Function,-,strsignal,char*,int Function,+,strspn,size_t,"const char*, const char*" Function,+,strstr,char*,"const char*, const char*" -Function,-,strtod,double,"const char*, char**" +Function,+,strtod,double,"const char*, char**" Function,-,strtod_l,double,"const char*, char**, locale_t" Function,+,strtof,float,"const char*, char**" Function,-,strtof_l,float,"const char*, char**, locale_t" From a57c730fb675bcdba66b3467fab40b0e3623ee57 Mon Sep 17 00:00:00 2001 From: SG Date: Fri, 15 Sep 2023 18:42:03 +0300 Subject: [PATCH 03/31] Apps: elk js --- applications/system/elk_js/LICENSE | 15 + applications/system/elk_js/application.fam | 9 + applications/system/elk_js/elk.c | 1429 +++++++++++++++++++ applications/system/elk_js/elk.h | 62 + applications/system/elk_js/elk_js.c | 400 ++++++ applications/system/elk_js/ffi/ffi.c | 705 +++++++++ applications/system/elk_js/ffi/ffi.h | 452 ++++++ applications/system/elk_js/ffi/ffi_cfi.h | 54 + applications/system/elk_js/ffi/ffi_common.h | 146 ++ applications/system/elk_js/ffi/fficonfig.h | 213 +++ applications/system/elk_js/ffi/ffitarget.h | 77 + applications/system/elk_js/ffi/internal.h | 7 + applications/system/elk_js/ffi/prep_cif.c | 233 +++ applications/system/elk_js/ffi/sysV-thumb.S | 227 +++ applications/system/elk_js/ffi/types.c | 101 ++ applications/system/elk_js/scripts/api.js | 3 + applications/system/elk_js/scripts/blink.js | 10 + applications/system/elk_js/scripts/ffi.js | 64 + applications/system/elk_js/scripts/req.js | 3 + 19 files changed, 4210 insertions(+) create mode 100644 applications/system/elk_js/LICENSE create mode 100644 applications/system/elk_js/application.fam create mode 100644 applications/system/elk_js/elk.c create mode 100644 applications/system/elk_js/elk.h create mode 100644 applications/system/elk_js/elk_js.c create mode 100644 applications/system/elk_js/ffi/ffi.c create mode 100644 applications/system/elk_js/ffi/ffi.h create mode 100644 applications/system/elk_js/ffi/ffi_cfi.h create mode 100644 applications/system/elk_js/ffi/ffi_common.h create mode 100644 applications/system/elk_js/ffi/fficonfig.h create mode 100644 applications/system/elk_js/ffi/ffitarget.h create mode 100644 applications/system/elk_js/ffi/internal.h create mode 100644 applications/system/elk_js/ffi/prep_cif.c create mode 100644 applications/system/elk_js/ffi/sysV-thumb.S create mode 100644 applications/system/elk_js/ffi/types.c create mode 100644 applications/system/elk_js/scripts/api.js create mode 100644 applications/system/elk_js/scripts/blink.js create mode 100644 applications/system/elk_js/scripts/ffi.js create mode 100644 applications/system/elk_js/scripts/req.js diff --git a/applications/system/elk_js/LICENSE b/applications/system/elk_js/LICENSE new file mode 100644 index 00000000000..86837a11165 --- /dev/null +++ b/applications/system/elk_js/LICENSE @@ -0,0 +1,15 @@ +Copyright (c) 2013-2021 Cesanta Software Limited +All rights reserved + +This software is dual-licensed: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License version 3 as +published by the Free Software Foundation. For the terms of this +license, see http://www.fsf.org/licensing/licenses/agpl-3.0.html + +You are free to use this software under the terms of the GNU Affero General +Public License, but WITHOUT ANY WARRANTY; without even the implied +warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +See the GNU General Public License for more details. + +Alternatively, you can license this software under a commercial +license, please contact us at https://cesanta.com/contact.html diff --git a/applications/system/elk_js/application.fam b/applications/system/elk_js/application.fam new file mode 100644 index 00000000000..19ef9fa2366 --- /dev/null +++ b/applications/system/elk_js/application.fam @@ -0,0 +1,9 @@ +App( + appid="elk_js", + name="Elk JS", + apptype=FlipperAppType.EXTERNAL, + entry_point="elk_js_app", + stack_size=4 * 1024, + sources=["*.c", "*.S"], + order=1, +) diff --git a/applications/system/elk_js/elk.c b/applications/system/elk_js/elk.c new file mode 100644 index 00000000000..a771503c94b --- /dev/null +++ b/applications/system/elk_js/elk.c @@ -0,0 +1,1429 @@ +// Copyright (c) 2013-2022 Cesanta Software Limited +// All rights reserved +// +// This software is dual-licensed: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License version 3 as +// published by the Free Software Foundation. For the terms of this +// license, see http://www.fsf.org/licensing/licenses/agpl-3.0.html +// +// You are free to use this software under the terms of the GNU General +// Public License, but WITHOUT ANY WARRANTY; without even the implied +// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU General Public License for more details. +// +// Alternatively, you can license this software under a commercial +// license, please contact us at https://cesanta.com/contact.html + +#if defined(__GNUC__) && !defined(JS_OPT) && !defined(ARDUINO_AVR_UNO) && \ + !defined(ARDUINO_AVR_NANO) && !defined(ARDUINO_AVR_PRO) && \ + !defined(__APPLE__) +#pragma GCC optimize("O3,inline") +#endif + +#include +#include +#include +#include +#include +#include + +#include "elk.h" + +#ifndef JS_EXPR_MAX +#define JS_EXPR_MAX 20 +#endif + +#ifndef JS_GC_THRESHOLD +#define JS_GC_THRESHOLD 0.75 +#endif + +typedef uint32_t jsoff_t; + +struct js { + jsoff_t css; // Max observed C stack size + jsoff_t lwm; // JS RAM low watermark: min free RAM observed + const char *code; // Currently parsed code snippet + char errmsg[33]; // Error message placeholder + uint8_t tok; // Last parsed token value + uint8_t consumed; // Indicator that last parsed token was consumed + uint8_t flags; // Execution flags, see F_* constants below +#define F_NOEXEC 1U // Parse code, but not execute +#define F_LOOP 2U // We're inside the loop +#define F_CALL 4U // We're inside a function call +#define F_BREAK 8U // Exit the loop +#define F_RETURN 16U // Return has been executed + jsoff_t clen; // Code snippet length + jsoff_t pos; // Current parsing position + jsoff_t toff; // Offset of the last parsed token + jsoff_t tlen; // Length of the last parsed token + jsoff_t nogc; // Entity offset to exclude from GC + jsval_t tval; // Holds last parsed numeric or string literal value + jsval_t scope; // Current scope + uint8_t *mem; // Available JS memory + jsoff_t size; // Memory size + jsoff_t brk; // Current mem usage boundary + jsoff_t gct; // GC threshold. If brk > gct, trigger GC + jsoff_t maxcss; // Maximum allowed C stack size usage + void *cstk; // C stack pointer at the beginning of js_eval() +}; + +// A JS memory stores diffenent entities: objects, properties, strings +// All entities are packed to the beginning of a buffer. +// The `brk` marks the end of the used memory: +// +// | entity1 | entity2| .... |entityN| unused memory | +// |---------|--------|------|-------|------------------------------| +// js.mem js.brk js.size +// +// Each entity is 4-byte aligned, therefore 2 LSB bits store entity type. +// Object: 8 bytes: offset of the first property, offset of the upper obj +// Property: 8 bytes + val: 4 byte next prop, 4 byte key offs, N byte value +// String: 4xN bytes: 4 byte len << 2, 4byte-aligned 0-terminated data +// +// If C functions are imported, they use the upper part of memory as stack for +// passing params. Each argument is pushed to the top of the memory as jsval_t, +// and js.size is decreased by sizeof(jsval_t), i.e. 8 bytes. When function +// returns, js.size is restored back. So js.size is used as a stack pointer. + +// clang-format off +enum { + TOK_ERR, TOK_EOF, TOK_IDENTIFIER, TOK_NUMBER, TOK_STRING, TOK_SEMICOLON, + TOK_LPAREN, TOK_RPAREN, TOK_LBRACE, TOK_RBRACE, + // Keyword tokens + TOK_BREAK = 50, TOK_CASE, TOK_CATCH, TOK_CLASS, TOK_CONST, TOK_CONTINUE, + TOK_DEFAULT, TOK_DELETE, TOK_DO, TOK_ELSE, TOK_FINALLY, TOK_FOR, TOK_FUNC, + TOK_IF, TOK_IN, TOK_INSTANCEOF, TOK_LET, TOK_NEW, TOK_RETURN, TOK_SWITCH, + TOK_THIS, TOK_THROW, TOK_TRY, TOK_VAR, TOK_VOID, TOK_WHILE, TOK_WITH, + TOK_YIELD, TOK_UNDEF, TOK_NULL, TOK_TRUE, TOK_FALSE, + // JS Operator tokens + TOK_DOT = 100, TOK_CALL, TOK_POSTINC, TOK_POSTDEC, TOK_NOT, TOK_TILDA, // 100 + TOK_TYPEOF, TOK_UPLUS, TOK_UMINUS, TOK_EXP, TOK_MUL, TOK_DIV, TOK_REM, // 106 + TOK_PLUS, TOK_MINUS, TOK_SHL, TOK_SHR, TOK_ZSHR, TOK_LT, TOK_LE, TOK_GT, // 113 + TOK_GE, TOK_EQ, TOK_NE, TOK_AND, TOK_XOR, TOK_OR, TOK_LAND, TOK_LOR, // 121 + TOK_COLON, TOK_Q, TOK_ASSIGN, TOK_PLUS_ASSIGN, TOK_MINUS_ASSIGN, + TOK_MUL_ASSIGN, TOK_DIV_ASSIGN, TOK_REM_ASSIGN, TOK_SHL_ASSIGN, + TOK_SHR_ASSIGN, TOK_ZSHR_ASSIGN, TOK_AND_ASSIGN, TOK_XOR_ASSIGN, + TOK_OR_ASSIGN, TOK_COMMA, +}; + +enum { + // IMPORTANT: T_OBJ, T_PROP, T_STR must go first. That is required by the + // memory layout functions: memory entity types are encoded in the 2 bits, + // thus type values must be 0,1,2,3 + T_OBJ, T_PROP, T_STR, T_UNDEF, T_NULL, T_NUM, T_BOOL, T_FUNC, T_CODEREF, + T_CFUNC, T_ERR +}; + +static const char *typestr(uint8_t t) { + const char *names[] = { "object", "prop", "string", "undefined", "null", + "number", "boolean", "function", "coderef", + "cfunc", "err", "nan" }; + return (t < sizeof(names) / sizeof(names[0])) ? names[t] : "??"; +} + +// Pack JS values into uin64_t, double nan, per IEEE 754 +// 64bit "double": 1 bit sign, 11 bits exponent, 52 bits mantissa +// +// seeeeeee|eeeemmmm|mmmmmmmm|mmmmmmmm|mmmmmmmm|mmmmmmmm|mmmmmmmm|mmmmmmmm +// 11111111|11110000|00000000|00000000|00000000|00000000|00000000|00000000 inf +// 11111111|11111000|00000000|00000000|00000000|00000000|00000000|00000000 qnan +// +// 11111111|1111tttt|vvvvvvvv|vvvvvvvv|vvvvvvvv|vvvvvvvv|vvvvvvvv|vvvvvvvv +// NaN marker |type| 48-bit placeholder for values: pointers, strings +// +// On 64-bit platforms, pointers are really 48 bit only, so they can fit, +// provided they are sign extended +static jsval_t tov(double d) { union { double d; jsval_t v; } u = {d}; return u.v; } +static double tod(jsval_t v) { union { jsval_t v; double d; } u = {v}; return u.d; } +static jsval_t mkval(uint8_t type, uint64_t data) { return ((jsval_t) 0x7ff0U << 48U) | ((jsval_t) (type) << 48) | (data & 0xffffffffffffUL); } +static bool is_nan(jsval_t v) { return (v >> 52U) == 0x7ffU; } +static uint8_t vtype(jsval_t v) { return is_nan(v) ? ((v >> 48U) & 15U) : (uint8_t) T_NUM; } +static size_t vdata(jsval_t v) { return (size_t) (v & ~((jsval_t) 0x7fffUL << 48U)); } +static jsval_t mkcoderef(jsval_t off, jsoff_t len) { return mkval(T_CODEREF, (off & 0xffffffU) | ((jsval_t)(len & 0xffffffU) << 24U)); } +static jsoff_t coderefoff(jsval_t v) { return v & 0xffffffU; } +static jsoff_t codereflen(jsval_t v) { return (v >> 24U) & 0xffffffU; } + +static uint8_t unhex(uint8_t c) { return (c >= '0' && c <= '9') ? (uint8_t) (c - '0') : (c >= 'a' && c <= 'f') ? (uint8_t) (c - 'W') : (c >= 'A' && c <= 'F') ? (uint8_t) (c - '7') : 0; } +static bool is_space(int c) { return c == ' ' || c == '\r' || c == '\n' || c == '\t' || c == '\f' || c == '\v'; } +static bool is_digit(int c) { return c >= '0' && c <= '9'; } +static bool is_xdigit(int c) { return is_digit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); } +static bool is_alpha(int c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); } +static bool is_ident_begin(int c) { return c == '_' || c == '$' || is_alpha(c); } +static bool is_ident_continue(int c) { return c == '_' || c == '$' || is_alpha(c) || is_digit(c); } +static bool is_err(jsval_t v) { return vtype(v) == T_ERR; } +static bool is_unary(uint8_t tok) { return tok >= TOK_POSTINC && tok <= TOK_UMINUS; } +static bool is_assign(uint8_t tok) { return (tok >= TOK_ASSIGN && tok <= TOK_OR_ASSIGN); } +static void saveoff(struct js *js, jsoff_t off, jsoff_t val) { memcpy(&js->mem[off], &val, sizeof(val)); } +static void saveval(struct js *js, jsoff_t off, jsval_t val) { memcpy(&js->mem[off], &val, sizeof(val)); } +static jsoff_t loadoff(struct js *js, jsoff_t off) { jsoff_t v = 0; assert(js->brk <= js->size); memcpy(&v, &js->mem[off], sizeof(v)); return v; } +static jsoff_t offtolen(jsoff_t off) { return (off >> 2) - 1; } +static jsoff_t vstrlen(struct js *js, jsval_t v) { return offtolen(loadoff(js, (jsoff_t) vdata(v))); } +static jsval_t loadval(struct js *js, jsoff_t off) { jsval_t v = 0; memcpy(&v, &js->mem[off], sizeof(v)); return v; } +static jsval_t upper(struct js *js, jsval_t scope) { return mkval(T_OBJ, loadoff(js, (jsoff_t) (vdata(scope) + sizeof(jsoff_t)))); } +static jsoff_t align32(jsoff_t v) { return ((v + 3) >> 2) << 2; } + +#define CHECKV(_v) do { if (is_err(_v)) { res = (_v); goto done; } } while (0) +#define EXPECT(_tok, _e) do { if (next(js) != _tok) { _e; return js_mkerr(js, "parse error"); }; js->consumed = 1; } while (0) +// clang-format on + +// Forward declarations of the private functions +static size_t tostr(struct js *js, jsval_t value, char *buf, size_t len); +static jsval_t js_expr(struct js *js); +static jsval_t js_stmt(struct js *js); +static jsval_t do_op(struct js *, uint8_t op, jsval_t l, jsval_t r); + +static void setlwm(struct js *js) { + jsoff_t n = 0, css = 0; + if (js->brk < js->size) n = js->size - js->brk; + if (js->lwm > n) js->lwm = n; + if ((char *) js->cstk > (char *) &n) + css = (jsoff_t) ((char *) js->cstk - (char *) &n); + if (css > js->css) js->css = css; +} + +// Copy src to dst, make no overflows, 0-terminate. Return bytes copied +static size_t cpy(char *dst, size_t dstlen, const char *src, size_t srclen) { + size_t i = 0; + for (i = 0; i < dstlen && i < srclen && src[i] != 0; i++) dst[i] = src[i]; + if (dstlen > 0) dst[i < dstlen ? i : dstlen - 1] = '\0'; + return i; +} + +// Stringify JS object +static size_t strobj(struct js *js, jsval_t obj, char *buf, size_t len) { + size_t n = cpy(buf, len, "{", 1); + jsoff_t next = loadoff(js, (jsoff_t) vdata(obj)) & ~3U; // First prop offset + while (next < js->brk && next != 0) { // Iterate over props + jsoff_t koff = loadoff(js, next + (jsoff_t) sizeof(next)); + jsval_t val = loadval(js, next + (jsoff_t) (sizeof(next) + sizeof(koff))); + // printf("PROP %u, koff %u\n", next & ~3, koff); + n += cpy(buf + n, len - n, ",", n == 1 ? 0 : 1); + n += tostr(js, mkval(T_STR, koff), buf + n, len - n); + n += cpy(buf + n, len - n, ":", 1); + n += tostr(js, val, buf + n, len - n); + next = loadoff(js, next) & ~3U; // Load next prop offset + } + return n + cpy(buf + n, len - n, "}", 1); +} + +// Stringify numeric JS value +static size_t strnum(jsval_t value, char *buf, size_t len) { + double dv = tod(value), iv; + const char *fmt = (float)modf(dv, &iv) == (float)0.0f ? "%.17g" : "%g"; + return (size_t) snprintf(buf, len, fmt, dv); +} + +// Return mem offset and length of the JS string +static jsoff_t vstr(struct js *js, jsval_t value, jsoff_t *len) { + jsoff_t off = (jsoff_t) vdata(value); + if (len) *len = offtolen(loadoff(js, off)); + return (jsoff_t) (off + sizeof(off)); +} + +// Stringify string JS value +static size_t strstring(struct js *js, jsval_t value, char *buf, size_t len) { + jsoff_t slen, off = vstr(js, value, &slen); + size_t n = 0; + n += cpy(buf + n, len - n, "\"", 1); + n += cpy(buf + n, len - n, (char *) &js->mem[off], slen); + n += cpy(buf + n, len - n, "\"", 1); + return n; +} + +// Stringify JS function +static size_t strfunc(struct js *js, jsval_t value, char *buf, size_t len) { + jsoff_t sn, off = vstr(js, value, &sn); + size_t n = cpy(buf, len, "function", 8); + return n + cpy(buf + n, len - n, (char *) &js->mem[off], sn); +} + +jsval_t js_mkerr(struct js *js, const char *xx, ...) { + va_list ap; + size_t n = cpy(js->errmsg, sizeof(js->errmsg), "ERROR: ", 7); + va_start(ap, xx); + vsnprintf(js->errmsg + n, sizeof(js->errmsg) - n, xx, ap); + va_end(ap); + js->errmsg[sizeof(js->errmsg) - 1] = '\0'; + js->pos = js->clen, js->tok = TOK_EOF, js->consumed = 0; // Jump to the end + return mkval(T_ERR, 0); +} + +// Stringify JS value into the given buffer +static size_t tostr(struct js *js, jsval_t value, char *buf, size_t len) { + switch (vtype(value)) { // clang-format off + case T_UNDEF: return cpy(buf, len, "undefined", 9); + case T_NULL: return cpy(buf, len, "null", 4); + case T_BOOL: return cpy(buf, len, vdata(value) & 1 ? "true" : "false", vdata(value) & 1 ? 4 : 5); + case T_OBJ: return strobj(js, value, buf, len); + case T_STR: return strstring(js, value, buf, len); + case T_NUM: return strnum(value, buf, len); + case T_FUNC: return strfunc(js, value, buf, len); + case T_CFUNC: return (size_t) snprintf(buf, len, "\"c_func_0x%lx\"", (unsigned long) vdata(value)); + case T_PROP: return (size_t) snprintf(buf, len, "PROP@%lu", (unsigned long) vdata(value)); + default: return (size_t) snprintf(buf, len, "VTYPE%d", vtype(value)); + } // clang-format on +} + +// Stringify JS value into a free JS memory block +const char *js_str(struct js *js, jsval_t value) { + // Leave jsoff_t placeholder between js->brk and a stringify buffer, + // in case if next step is convert it into a JS variable + char *buf = (char *) &js->mem[js->brk + sizeof(jsoff_t)]; + size_t len, available = js->size - js->brk - sizeof(jsoff_t); + if (is_err(value)) return js->errmsg; + if (js->brk + sizeof(jsoff_t) >= js->size) return ""; + len = tostr(js, value, buf, available); + js_mkstr(js, NULL, len); + return buf; +} + +static bool js_truthy(struct js *js, jsval_t v) { + uint8_t t = vtype(v); + return (t == T_BOOL && vdata(v) != 0) || (t == T_NUM && tod(v) != (double)0.0) || + (t == T_OBJ || t == T_FUNC) || (t == T_STR && vstrlen(js, v) > 0); +} + +static jsoff_t js_alloc(struct js *js, size_t size) { + jsoff_t ofs = js->brk; + size = align32((jsoff_t) size); // 4-byte align, (n + k - 1) / k * k + if (js->brk + size > js->size) return ~(jsoff_t) 0; + js->brk += (jsoff_t) size; + return ofs; +} + +static jsval_t mkentity(struct js *js, jsoff_t b, const void *buf, size_t len) { + jsoff_t ofs = js_alloc(js, len + sizeof(b)); + if (ofs == (jsoff_t) ~0) return js_mkerr(js, "oom"); + memcpy(&js->mem[ofs], &b, sizeof(b)); + // Using memmove - in case we're stringifying data from the free JS mem + if (buf != NULL) memmove(&js->mem[ofs + sizeof(b)], buf, len); + if ((b & 3) == T_STR) js->mem[ofs + sizeof(b) + len - 1] = 0; // 0-terminate + // printf("MKE: %u @ %u type %d\n", js->brk - ofs, ofs, b & 3); + return mkval(b & 3, ofs); +} + +jsval_t js_mkstr(struct js *js, const void *ptr, size_t len) { + jsoff_t n = (jsoff_t) (len + 1); + // printf("MKSTR %u %u\n", n, js->brk); + return mkentity(js, (jsoff_t) ((n << 2) | T_STR), ptr, n); +} + +static jsval_t mkobj(struct js *js, jsoff_t parent) { + return mkentity(js, 0 | T_OBJ, &parent, sizeof(parent)); +} + +static jsval_t setprop(struct js *js, jsval_t obj, jsval_t k, jsval_t v) { + jsoff_t koff = (jsoff_t) vdata(k); // Key offset + jsoff_t b, head = (jsoff_t) vdata(obj); // Property list head + char buf[sizeof(koff) + sizeof(v)]; // Property memory layout + memcpy(&b, &js->mem[head], sizeof(b)); // Load current 1st prop offset + memcpy(buf, &koff, sizeof(koff)); // Initialize prop data: copy key + memcpy(buf + sizeof(koff), &v, sizeof(v)); // Copy value + jsoff_t brk = js->brk | T_OBJ; // New prop offset + memcpy(&js->mem[head], &brk, sizeof(brk)); // Repoint head to the new prop + // printf("PROP: %u -> %u\n", b, brk); + return mkentity(js, (b & ~3U) | T_PROP, buf, sizeof(buf)); // Create new prop +} + +// Return T_OBJ/T_PROP/T_STR entity size based on the first word in memory +static inline jsoff_t esize(jsoff_t w) { + switch (w & 3U) { // clang-format off + case T_OBJ: return (jsoff_t) (sizeof(jsoff_t) + sizeof(jsoff_t)); + case T_PROP: return (jsoff_t) (sizeof(jsoff_t) + sizeof(jsoff_t) + sizeof(jsval_t)); + case T_STR: return (jsoff_t) (sizeof(jsoff_t) + align32(w >> 2U)); + default: return (jsoff_t) ~0U; + } // clang-format on +} + +static bool is_mem_entity(uint8_t t) { + return t == T_OBJ || t == T_PROP || t == T_STR || t == T_FUNC; +} + +#define GCMASK ~(((jsoff_t) ~0) >> 1) // Entity deletion marker +static void js_fixup_offsets(struct js *js, jsoff_t start, jsoff_t size) { + for (jsoff_t n, v, off = 0; off < js->brk; off += n) { // start from 0! + v = loadoff(js, off); + n = esize(v & ~GCMASK); + if (v & GCMASK) continue; // To be deleted, don't bother + if ((v & 3) != T_OBJ && (v & 3) != T_PROP) continue; + if (v > start) saveoff(js, off, v - size); + if ((v & 3) == T_OBJ) { + jsoff_t u = loadoff(js, (jsoff_t) (off + sizeof(jsoff_t))); + if (u > start) saveoff(js, (jsoff_t) (off + sizeof(jsoff_t)), u - size); + } + if ((v & 3) == T_PROP) { + jsoff_t koff = loadoff(js, (jsoff_t) (off + sizeof(off))); + if (koff > start) saveoff(js, (jsoff_t) (off + sizeof(off)), koff - size); + jsval_t val = loadval(js, (jsoff_t) (off + sizeof(off) + sizeof(off))); + if (is_mem_entity(vtype(val)) && vdata(val) > start) { + saveval(js, (jsoff_t) (off + sizeof(off) + sizeof(off)), + mkval(vtype(val), (unsigned long) (vdata(val) - size))); + } + } + } + // Fixup js->scope + jsoff_t off = (jsoff_t) vdata(js->scope); + if (off > start) js->scope = mkval(T_OBJ, off - size); + if (js->nogc >= start) js->nogc -= size; + // Fixup code that we're executing now, if required + if (js->code > (char *) js->mem && (int32_t)(js->code - (char *) js->mem) < (int32_t)js->size && + (int32_t)(js->code - (char *) js->mem) > (int32_t)start) { + js->code -= size; + // printf("GC-ing code under us!! %ld\n", js->code - (char *) js->mem); + } + // printf("FIXEDOFF %u %u\n", start, size); +} + +static void js_delete_marked_entities(struct js *js) { + for (jsoff_t n, v, off = 0; off < js->brk; off += n) { + v = loadoff(js, off); + n = esize(v & ~GCMASK); + if (v & GCMASK) { // This entity is marked for deletion, remove it + // printf("DEL: %4u %d %x\n", off, v & 3, n); + // assert(off + n <= js->brk); + js_fixup_offsets(js, off, n); + memmove(&js->mem[off], &js->mem[off + n], js->brk - off - n); + js->brk -= n; // Shrink brk boundary by the size of deleted entity + n = 0; // We shifted data, next iteration stay on this offset + } + } +} + +static void js_mark_all_entities_for_deletion(struct js *js) { + for (jsoff_t v, off = 0; off < js->brk; off += esize(v)) { + v = loadoff(js, off); + saveoff(js, off, v | GCMASK); + } +} + +static jsoff_t js_unmark_entity(struct js *js, jsoff_t off) { + jsoff_t v = loadoff(js, off); + if (v & GCMASK) { + saveoff(js, off, v & ~GCMASK); + // printf("UNMARK %5u %d\n", off, v & 3); + if ((v & 3) == T_OBJ) js_unmark_entity(js, v & ~(GCMASK | 3)); + if ((v & 3) == T_PROP) { + js_unmark_entity(js, v & ~(GCMASK | 3)); // Unmark next prop + js_unmark_entity(js, loadoff(js, (jsoff_t) (off + sizeof(off)))); // key + jsval_t val = loadval(js, (jsoff_t) (off + sizeof(off) + sizeof(off))); + if (is_mem_entity(vtype(val))) js_unmark_entity(js, (jsoff_t) vdata(val)); + } + } + return v & ~(GCMASK | 3U); +} + +static void js_unmark_used_entities(struct js *js) { + jsval_t scope = js->scope; + do { + js_unmark_entity(js, (jsoff_t) vdata(scope)); + scope = upper(js, scope); + } while (vdata(scope) != 0); // When global scope is GC-ed, stop + if (js->nogc) js_unmark_entity(js, js->nogc); + // printf("UNMARK: nogc %u\n", js->nogc); + // js_dump(js); +} + +void js_gc(struct js *js) { + // printf("================== GC %u\n", js->nogc); + setlwm(js); + if (js->nogc == (jsoff_t) ~0) return; // ~0 is a special case: GC Is disabled + js_mark_all_entities_for_deletion(js); + js_unmark_used_entities(js); + js_delete_marked_entities(js); +} + +// Skip whitespaces and comments +static jsoff_t skiptonext(const char *code, jsoff_t len, jsoff_t n) { + // printf("SKIP: [%.*s]\n", len - n, &code[n]); + while (n < len) { + if (is_space(code[n])) { + n++; + } else if (n + 1 < len && code[n] == '/' && code[n + 1] == '/') { + for (n += 2; n < len && code[n] != '\n';) n++; + } else if (n + 3 < len && code[n] == '/' && code[n + 1] == '*') { + for (n += 4; n < len && (code[n - 2] != '*' || code[n - 1] != '/');) n++; + } else { + break; + } + } + return n; +} + +static bool streq(const char *buf, size_t len, const char *p, size_t n) { + return n == len && memcmp(buf, p, len) == 0; +} + +static uint8_t parsekeyword(const char *buf, size_t len) { + switch (buf[0]) { // clang-format off + case 'b': if (streq("break", 5, buf, len)) return TOK_BREAK; break; + case 'c': if (streq("class", 5, buf, len)) return TOK_CLASS; if (streq("case", 4, buf, len)) return TOK_CASE; if (streq("catch", 5, buf, len)) return TOK_CATCH; if (streq("const", 5, buf, len)) return TOK_CONST; if (streq("continue", 8, buf, len)) return TOK_CONTINUE; break; + case 'd': if (streq("do", 2, buf, len)) return TOK_DO; if (streq("default", 7, buf, len)) return TOK_DEFAULT; break; // if (streq("delete", 6, buf, len)) return TOK_DELETE; break; + case 'e': if (streq("else", 4, buf, len)) return TOK_ELSE; break; + case 'f': if (streq("for", 3, buf, len)) return TOK_FOR; if (streq("function", 8, buf, len)) return TOK_FUNC; if (streq("finally", 7, buf, len)) return TOK_FINALLY; if (streq("false", 5, buf, len)) return TOK_FALSE; break; + case 'i': if (streq("if", 2, buf, len)) return TOK_IF; if (streq("in", 2, buf, len)) return TOK_IN; if (streq("instanceof", 10, buf, len)) return TOK_INSTANCEOF; break; + case 'l': if (streq("let", 3, buf, len)) return TOK_LET; break; + case 'n': if (streq("new", 3, buf, len)) return TOK_NEW; if (streq("null", 4, buf, len)) return TOK_NULL; break; + case 'r': if (streq("return", 6, buf, len)) return TOK_RETURN; break; + case 's': if (streq("switch", 6, buf, len)) return TOK_SWITCH; break; + case 't': if (streq("try", 3, buf, len)) return TOK_TRY; if (streq("this", 4, buf, len)) return TOK_THIS; if (streq("throw", 5, buf, len)) return TOK_THROW; if (streq("true", 4, buf, len)) return TOK_TRUE; if (streq("typeof", 6, buf, len)) return TOK_TYPEOF; break; + case 'u': if (streq("undefined", 9, buf, len)) return TOK_UNDEF; break; + case 'v': if (streq("var", 3, buf, len)) return TOK_VAR; if (streq("void", 4, buf, len)) return TOK_VOID; break; + case 'w': if (streq("while", 5, buf, len)) return TOK_WHILE; if (streq("with", 4, buf, len)) return TOK_WITH; break; + case 'y': if (streq("yield", 5, buf, len)) return TOK_YIELD; break; + } // clang-format on + return TOK_IDENTIFIER; +} + +static uint8_t parseident(const char *buf, jsoff_t len, jsoff_t *tlen) { + if (is_ident_begin(buf[0])) { + while (*tlen < len && is_ident_continue(buf[*tlen])) (*tlen)++; + return parsekeyword(buf, *tlen); + } + return TOK_ERR; +} + +static uint8_t next(struct js *js) { + if (js->consumed == 0) return js->tok; + js->consumed = 0; + js->tok = TOK_ERR; + js->toff = js->pos = skiptonext(js->code, js->clen, js->pos); + js->tlen = 0; + const char *buf = js->code + js->toff; + // clang-format off + if (js->toff >= js->clen) { js->tok = TOK_EOF; return js->tok; } +#define TOK(T, LEN) { js->tok = T; js->tlen = (LEN); break; } +#define LOOK(OFS, CH) js->toff + OFS < js->clen && buf[OFS] == CH + switch (buf[0]) { + case '?': TOK(TOK_Q, 1); + case ':': TOK(TOK_COLON, 1); + case '(': TOK(TOK_LPAREN, 1); + case ')': TOK(TOK_RPAREN, 1); + case '{': TOK(TOK_LBRACE, 1); + case '}': TOK(TOK_RBRACE, 1); + case ';': TOK(TOK_SEMICOLON, 1); + case ',': TOK(TOK_COMMA, 1); + case '!': if (LOOK(1, '=') && LOOK(2, '=')) TOK(TOK_NE, 3); TOK(TOK_NOT, 1); + case '.': TOK(TOK_DOT, 1); + case '~': TOK(TOK_TILDA, 1); + case '-': if (LOOK(1, '-')) TOK(TOK_POSTDEC, 2); if (LOOK(1, '=')) TOK(TOK_MINUS_ASSIGN, 2); TOK(TOK_MINUS, 1); + case '+': if (LOOK(1, '+')) TOK(TOK_POSTINC, 2); if (LOOK(1, '=')) TOK(TOK_PLUS_ASSIGN, 2); TOK(TOK_PLUS, 1); + case '*': if (LOOK(1, '*')) TOK(TOK_EXP, 2); if (LOOK(1, '=')) TOK(TOK_MUL_ASSIGN, 2); TOK(TOK_MUL, 1); + case '/': if (LOOK(1, '=')) TOK(TOK_DIV_ASSIGN, 2); TOK(TOK_DIV, 1); + case '%': if (LOOK(1, '=')) TOK(TOK_REM_ASSIGN, 2); TOK(TOK_REM, 1); + case '&': if (LOOK(1, '&')) TOK(TOK_LAND, 2); if (LOOK(1, '=')) TOK(TOK_AND_ASSIGN, 2); TOK(TOK_AND, 1); + case '|': if (LOOK(1, '|')) TOK(TOK_LOR, 2); if (LOOK(1, '=')) TOK(TOK_OR_ASSIGN, 2); TOK(TOK_OR, 1); + case '=': if (LOOK(1, '=') && LOOK(2, '=')) TOK(TOK_EQ, 3); TOK(TOK_ASSIGN, 1); + case '<': if (LOOK(1, '<') && LOOK(2, '=')) TOK(TOK_SHL_ASSIGN, 3); if (LOOK(1, '<')) TOK(TOK_SHL, 2); if (LOOK(1, '=')) TOK(TOK_LE, 2); TOK(TOK_LT, 1); + case '>': if (LOOK(1, '>') && LOOK(2, '=')) TOK(TOK_SHR_ASSIGN, 3); if (LOOK(1, '>')) TOK(TOK_SHR, 2); if (LOOK(1, '=')) TOK(TOK_GE, 2); TOK(TOK_GT, 1); + case '^': if (LOOK(1, '=')) TOK(TOK_XOR_ASSIGN, 2); TOK(TOK_XOR, 1); + case '"': case '\'': + js->tlen++; + while (js->toff + js->tlen < js->clen && buf[js->tlen] != buf[0]) { + uint8_t increment = 1; + if (buf[js->tlen] == '\\') { + if (js->toff + js->tlen + 2 > js->clen) break; + increment = 2; + if (buf[js->tlen + 1] == 'x') { + if (js->toff + js->tlen + 4 > js->clen) break; + increment = 4; + } + } + js->tlen += increment; + } + if (buf[0] == buf[js->tlen]) js->tok = TOK_STRING, js->tlen++; + break; + case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { + char *end; + js->tval = tov(strtod(buf, &end)); // TODO(lsm): protect against OOB access + TOK(TOK_NUMBER, (jsoff_t) (end - buf)); + } + default: js->tok = parseident(buf, js->clen - js->toff, &js->tlen); break; + } // clang-format on + js->pos = js->toff + js->tlen; + // printf("NEXT: %d %d [%.*s]\n", js->tok, js->pos, (int) js->tlen, buf); + return js->tok; +} + +static inline uint8_t lookahead(struct js *js) { + uint8_t old = js->tok, tok = 0; + jsoff_t pos = js->pos; + js->consumed = 1; + tok = next(js); + js->pos = pos, js->tok = old; + return tok; +} + +static void mkscope(struct js *js) { + assert((js->flags & F_NOEXEC) == 0); + jsoff_t prev = (jsoff_t) vdata(js->scope); + js->scope = mkobj(js, prev); + // printf("ENTER SCOPE %u, prev %u\n", (jsoff_t) vdata(js->scope), prev); +} + +static void delscope(struct js *js) { + js->scope = upper(js, js->scope); + // printf("EXIT SCOPE %u\n", (jsoff_t) vdata(js->scope)); +} + +static jsval_t js_block(struct js *js, bool create_scope) { + jsval_t res = js_mkundef(); + if (create_scope) mkscope(js); // Enter new scope + js->consumed = 1; + // jsoff_t pos = js->pos; + while (next(js) != TOK_EOF && next(js) != TOK_RBRACE && !is_err(res)) { + uint8_t t = js->tok; + res = js_stmt(js); + if (!is_err(res) && t != TOK_LBRACE && t != TOK_IF && t != TOK_WHILE && + js->tok != TOK_SEMICOLON) { + res = js_mkerr(js, "; expected"); + break; + } + } + // printf("BLOCKEND %s\n", js_str(js, res)); + if (create_scope) delscope(js); // Exit scope + return res; +} + +// Seach for property in a single object +static jsoff_t lkp(struct js *js, jsval_t obj, const char *buf, size_t len) { + jsoff_t off = loadoff(js, (jsoff_t) vdata(obj)) & ~3U; // Load first prop off + // printf("LKP: %lu %u [%.*s]\n", vdata(obj), off, (int) len, buf); + while (off < js->brk && off != 0) { // Iterate over props + jsoff_t koff = loadoff(js, (jsoff_t) (off + sizeof(off))); + jsoff_t klen = (loadoff(js, koff) >> 2) - 1; + const char *p = (char *) &js->mem[koff + sizeof(koff)]; + // printf(" %u %u[%.*s]\n", off, (int) klen, (int) klen, p); + if (streq(buf, len, p, klen)) return off; // Found ! + off = loadoff(js, off) & ~3U; // Load next prop offset + } + return 0; // Not found +} + +// Lookup variable in the scope chain +static jsval_t lookup(struct js *js, const char *buf, size_t len) { + if (js->flags & F_NOEXEC) return 0; + for (jsval_t scope = js->scope;;) { + jsoff_t off = lkp(js, scope, buf, len); + if (off != 0) return mkval(T_PROP, off); + if (vdata(scope) == 0) break; + scope = + mkval(T_OBJ, loadoff(js, (jsoff_t) (vdata(scope) + sizeof(jsoff_t)))); + } + return js_mkerr(js, "'%.*s' not found", (int) len, buf); +} + +static jsval_t resolveprop(struct js *js, jsval_t v) { + if (vtype(v) != T_PROP) return v; + return resolveprop(js, + loadval(js, (jsoff_t) (vdata(v) + sizeof(jsoff_t) * 2))); +} + +static jsval_t assign(struct js *js, jsval_t lhs, jsval_t val) { + saveval(js, (jsoff_t) ((vdata(lhs) & ~3U) + sizeof(jsoff_t) * 2), val); + return lhs; +} + +static jsval_t do_assign_op(struct js *js, uint8_t op, jsval_t l, jsval_t r) { + uint8_t m[] = {TOK_PLUS, TOK_MINUS, TOK_MUL, TOK_DIV, TOK_REM, TOK_SHL, + TOK_SHR, TOK_ZSHR, TOK_AND, TOK_XOR, TOK_OR}; + jsval_t res = do_op(js, m[op - TOK_PLUS_ASSIGN], resolveprop(js, l), r); + return assign(js, l, res); +} + +static jsval_t do_string_op(struct js *js, uint8_t op, jsval_t l, jsval_t r) { + jsoff_t n1, off1 = vstr(js, l, &n1); + jsoff_t n2, off2 = vstr(js, r, &n2); + if (op == TOK_PLUS) { + jsval_t res = js_mkstr(js, NULL, n1 + n2); + // printf("STRPLUS %u %u %u %u [%.*s] [%.*s]\n", n1, off1, n2, off2, (int) + // n1, + // &js->mem[off1], (int) n2, &js->mem[off2]); + if (vtype(res) == T_STR) { + jsoff_t n, off = vstr(js, res, &n); + memmove(&js->mem[off], &js->mem[off1], n1); + memmove(&js->mem[off + n1], &js->mem[off2], n2); + } + return res; + } else if (op == TOK_EQ) { + bool eq = n1 == n2 && memcmp(&js->mem[off1], &js->mem[off2], n1) == 0; + return mkval(T_BOOL, eq ? 1 : 0); + } else if (op == TOK_NE) { + bool eq = n1 == n2 && memcmp(&js->mem[off1], &js->mem[off2], n1) == 0; + return mkval(T_BOOL, eq ? 0 : 1); + } else { + return js_mkerr(js, "bad str op"); + } +} + +static jsval_t do_dot_op(struct js *js, jsval_t l, jsval_t r) { + const char *ptr = (char *) &js->code[coderefoff(r)]; + if (vtype(r) != T_CODEREF) return js_mkerr(js, "ident expected"); + // Handle stringvalue.length + if (vtype(l) == T_STR && streq(ptr, codereflen(r), "length", 6)) { + return tov(offtolen(loadoff(js, (jsoff_t) vdata(l)))); + } + if (vtype(l) != T_OBJ) return js_mkerr(js, "lookup in non-obj"); + jsoff_t off = lkp(js, l, ptr, codereflen(r)); + return off == 0 ? js_mkundef() : mkval(T_PROP, off); +} + +static jsval_t js_call_params(struct js *js) { + jsoff_t pos = js->pos; + uint8_t flags = js->flags; + js->flags |= F_NOEXEC; + js->consumed = 1; + for (bool comma = false; next(js) != TOK_EOF; comma = true) { + if (!comma && next(js) == TOK_RPAREN) break; + js_expr(js); + if (next(js) == TOK_RPAREN) break; + EXPECT(TOK_COMMA, js->flags = flags); + } + EXPECT(TOK_RPAREN, js->flags = flags); + js->flags = flags; + return mkcoderef(pos, js->pos - pos - js->tlen); +} + +static void reverse(jsval_t *args, int nargs) { + for (int i = 0; i < nargs / 2; i++) { + jsval_t tmp = args[i]; + args[i] = args[nargs - i - 1], args[nargs - i - 1] = tmp; + } +} + +// Call native C function +static jsval_t call_c(struct js *js, + jsval_t (*fn)(struct js *, jsval_t *, int)) { + int argc = 0; + while (js->pos < js->clen) { + if (next(js) == TOK_RPAREN) break; + jsval_t arg = resolveprop(js, js_expr(js)); + if (js->brk + sizeof(arg) > js->size) return js_mkerr(js, "call oom"); + js->size -= (jsoff_t) sizeof(arg); + memcpy(&js->mem[js->size], &arg, sizeof(arg)); + argc++; + // printf(" arg %d -> %s\n", argc, js_str(js, arg)); + if (next(js) == TOK_COMMA) js->consumed = 1; + } + reverse((jsval_t *) &js->mem[js->size], argc); + jsval_t res = fn(js, (jsval_t *) &js->mem[js->size], argc); + setlwm(js); + js->size += (jsoff_t) sizeof(jsval_t) * (jsoff_t) argc; // Restore stack + return res; +} + +// Call JS function. 'fn' looks like this: "(a,b) { return a + b; }" +static jsval_t call_js(struct js *js, const char *fn, jsoff_t fnlen) { + jsoff_t fnpos = 1; + // printf("JSCALL [%.*s] -> %.*s\n", (int) js->clen, js->code, (int) fnlen, + // fn); + // printf("JSCALL, nogc %u [%.*s]\n", js->nogc, (int) fnlen, fn); + mkscope(js); // Create function call scope + // Loop over arguments list "(a, b)" and set scope variables + while (fnpos < fnlen) { + fnpos = skiptonext(fn, fnlen, fnpos); // Skip to the identifier + if (fnpos < fnlen && fn[fnpos] == ')') break; // Closing paren? break! + jsoff_t identlen = 0; // Identifier length + uint8_t tok = parseident(&fn[fnpos], fnlen - fnpos, &identlen); + if (tok != TOK_IDENTIFIER) break; + // Here we have argument name. Calculate arg value + // printf(" [%.*s] -> %u [%.*s] -> ", (int) identlen, &fn[fnpos], js->pos, + // (int) js->clen, js->code); + js->pos = skiptonext(js->code, js->clen, js->pos); + js->consumed = 1; + jsval_t v = js->code[js->pos] == ')' ? js_mkundef() : js_expr(js); + // Set argument in the function scope + setprop(js, js->scope, js_mkstr(js, &fn[fnpos], identlen), v); + js->pos = skiptonext(js->code, js->clen, js->pos); + if (js->pos < js->clen && js->code[js->pos] == ',') js->pos++; + fnpos = skiptonext(fn, fnlen, fnpos + identlen); // Skip past identifier + if (fnpos < fnlen && fn[fnpos] == ',') fnpos++; // And skip comma + } + if (fnpos < fnlen && fn[fnpos] == ')') fnpos++; // Skip to the function body + fnpos = skiptonext(fn, fnlen, fnpos); // Up to the opening brace + if (fnpos < fnlen && fn[fnpos] == '{') fnpos++; // And skip the brace + size_t n = fnlen - fnpos - 1U; // Function code with stripped braces + // printf("flags: %d, body: %zu [%.*s]\n", js->flags, n, (int) n, &fn[fnpos]); + js->flags = F_CALL; // Mark we're in the function call + jsval_t res = js_eval(js, &fn[fnpos], n); // Call function, no GC + if (!is_err(res) && !(js->flags & F_RETURN)) res = js_mkundef(); // No return + delscope(js); // Delete call scope + // printf(" -> %d [%s], tok %d\n", js->flags, js_str(js, res), js->tok); + return res; +} + +static jsval_t do_call_op(struct js *js, jsval_t func, jsval_t args) { + if (vtype(args) != T_CODEREF) return js_mkerr(js, "bad call"); + if (vtype(func) != T_FUNC && vtype(func) != T_CFUNC) + return js_mkerr(js, "calling non-function"); + const char *code = js->code; // Save current parser state + jsoff_t clen = js->clen, pos = js->pos; // code, position and code length + js->code = &js->code[coderefoff(args)]; // Point parser to args + js->clen = codereflen(args); // Set args length + js->pos = skiptonext(js->code, js->clen, 0); // Skip to 1st arg + uint8_t tok = js->tok, flags = js->flags; // Save flags + jsoff_t nogc = js->nogc; + jsval_t res = js_mkundef(); + if (vtype(func) == T_FUNC) { + jsoff_t fnlen, fnoff = vstr(js, func, &fnlen); + js->nogc = (jsoff_t) (fnoff - sizeof(jsoff_t)); + res = call_js(js, (const char *) (&js->mem[fnoff]), fnlen); + } else { + res = call_c(js, (jsval_t(*)(struct js *, jsval_t *, int)) vdata(func)); + } + js->code = code, js->clen = clen, js->pos = pos; // Restore parser + js->flags = flags, js->tok = tok, js->nogc = nogc; + js->consumed = 1; + return res; +} + +// clang-format off +static jsval_t do_op(struct js *js, uint8_t op, jsval_t lhs, jsval_t rhs) { + if (js->flags & F_NOEXEC) return 0; + jsval_t l = resolveprop(js, lhs), r = resolveprop(js, rhs); + // printf("OP %d %d %d\n", op, vtype(lhs), vtype(r)); + setlwm(js); + if (is_err(l)) return l; + if (is_err(r)) return r; + if (is_assign(op) && vtype(lhs) != T_PROP) return js_mkerr(js, "bad lhs"); + switch (op) { + case TOK_TYPEOF: return js_mkstr(js, typestr(vtype(r)), strlen(typestr(vtype(r)))); + case TOK_CALL: return do_call_op(js, l, r); + case TOK_ASSIGN: return assign(js, lhs, r); + case TOK_POSTINC: { do_assign_op(js, TOK_PLUS_ASSIGN, lhs, tov(1)); return l; } + case TOK_POSTDEC: { do_assign_op(js, TOK_MINUS_ASSIGN, lhs, tov(1)); return l; } + case TOK_NOT: if (vtype(r) == T_BOOL) return mkval(T_BOOL, !vdata(r)); break; + } + if (is_assign(op)) return do_assign_op(js, op, lhs, r); + if (vtype(l) == T_STR && vtype(r) == T_STR) return do_string_op(js, op, l, r); + if (is_unary(op) && vtype(r) != T_NUM) return js_mkerr(js, "type mismatch"); + if (!is_unary(op) && op != TOK_DOT && (vtype(l) != T_NUM || vtype(r) != T_NUM)) return js_mkerr(js, "type mismatch"); + double a = tod(l), b = tod(r); + switch (op) { + //case TOK_EXP: return tov(pow(a, b)); + case TOK_DIV: return tod(r) == 0 ? js_mkerr(js, "div by zero") : tov(a / b); + case TOK_REM: return tov(a - b * ((double) (long) (a / b))); + case TOK_MUL: return tov(a * b); + case TOK_PLUS: return tov(a + b); + case TOK_MINUS: return tov(a - b); + case TOK_XOR: return tov((double)((long) a ^ (long) b)); + case TOK_AND: return tov((double)((long) a & (long) b)); + case TOK_OR: return tov((double)((long) a | (long) b)); + case TOK_UMINUS: return tov(-b); + case TOK_UPLUS: return r; + case TOK_TILDA: return tov((double)(~(long) b)); + case TOK_NOT: return mkval(T_BOOL, b == 0); + case TOK_SHL: return tov((double)((long) a << (long) b)); + case TOK_SHR: return tov((double)((long) a >> (long) b)); + case TOK_DOT: return do_dot_op(js, l, r); + case TOK_EQ: return mkval(T_BOOL, (long) a == (long) b); + case TOK_NE: return mkval(T_BOOL, (long) a != (long) b); + case TOK_LT: return mkval(T_BOOL, a < b); + case TOK_LE: return mkval(T_BOOL, a <= b); + case TOK_GT: return mkval(T_BOOL, a > b); + case TOK_GE: return mkval(T_BOOL, a >= b); + default: return js_mkerr(js, "unknown op %d", (int) op); // LCOV_EXCL_LINE + } +} // clang-format on + +static jsval_t js_str_literal(struct js *js) { + uint8_t *in = (uint8_t *) &js->code[js->toff]; + uint8_t *out = &js->mem[js->brk + sizeof(jsoff_t)]; + size_t n1 = 0, n2 = 0; + // printf("STR %u %lu %lu\n", js->brk, js->tlen, js->clen); + if (js->brk + sizeof(jsoff_t) + js->tlen > js->size) + return js_mkerr(js, "oom"); + while (n2++ + 2 < js->tlen) { + if (in[n2] == '\\') { + if (in[n2 + 1] == in[0]) { + out[n1++] = in[0]; + } else if (in[n2 + 1] == 'n') { + out[n1++] = '\n'; + } else if (in[n2 + 1] == 't') { + out[n1++] = '\t'; + } else if (in[n2 + 1] == 'r') { + out[n1++] = '\r'; + } else if (in[n2 + 1] == 'x' && is_xdigit(in[n2 + 2]) && + is_xdigit(in[n2 + 3])) { + out[n1++] = (uint8_t) ((unhex(in[n2 + 2]) << 4U) | unhex(in[n2 + 3])); + n2 += 2; + } else { + return js_mkerr(js, "bad str literal"); + } + n2++; + } else { + out[n1++] = ((uint8_t *) js->code)[js->toff + n2]; + } + } + return js_mkstr(js, NULL, n1); +} + +static jsval_t js_obj_literal(struct js *js) { + uint8_t exe = !(js->flags & F_NOEXEC); + // printf("OLIT1\n"); + jsval_t obj = exe ? mkobj(js, 0) : js_mkundef(); + if (is_err(obj)) return obj; + js->consumed = 1; + while (next(js) != TOK_RBRACE) { + jsval_t key = 0; + if (js->tok == TOK_IDENTIFIER) { + if (exe) key = js_mkstr(js, js->code + js->toff, js->tlen); + } else if (js->tok == TOK_STRING) { + if (exe) key = js_str_literal(js); + } else { + return js_mkerr(js, "parse error"); + } + js->consumed = 1; + EXPECT(TOK_COLON, ); + jsval_t val = js_expr(js); + if (exe) { + // printf("XXXX [%s] scope: %lu\n", js_str(js, val), vdata(js->scope)); + if (is_err(val)) return val; + if (is_err(key)) return key; + jsval_t res = setprop(js, obj, key, resolveprop(js, val)); + if (is_err(res)) return res; + } + if (next(js) == TOK_RBRACE) break; + EXPECT(TOK_COMMA, ); + } + EXPECT(TOK_RBRACE, ); + return obj; +} + +static jsval_t js_func_literal(struct js *js) { + uint8_t flags = js->flags; // Save current flags + js->consumed = 1; + EXPECT(TOK_LPAREN, js->flags = flags); + jsoff_t pos = js->pos - 1; + for (bool comma = false; next(js) != TOK_EOF; comma = true) { + if (!comma && next(js) == TOK_RPAREN) break; + EXPECT(TOK_IDENTIFIER, js->flags = flags); + if (next(js) == TOK_RPAREN) break; + EXPECT(TOK_COMMA, js->flags = flags); + } + EXPECT(TOK_RPAREN, js->flags = flags); + EXPECT(TOK_LBRACE, js->flags = flags); + js->consumed = 0; + js->flags |= F_NOEXEC; // Set no-execution flag to parse the + jsval_t res = js_block(js, false); // Skip function body - no exec + if (is_err(res)) { // But fail short on parse error + js->flags = flags; + return res; + } + js->flags = flags; // Restore flags + jsval_t str = js_mkstr(js, &js->code[pos], js->pos - pos); + js->consumed = 1; + // printf("FUNC: %u [%.*s]\n", pos, js->pos - pos, &js->code[pos]); + return mkval(T_FUNC, (unsigned long) vdata(str)); +} + +#define RTL_BINOP(_f1, _f2, _cond) \ + jsval_t res = _f1(js); \ + while (!is_err(res) && (_cond)) { \ + uint8_t op = js->tok; \ + js->consumed = 1; \ + jsval_t rhs = _f2(js); \ + if (is_err(rhs)) return rhs; \ + res = do_op(js, op, res, rhs); \ + } \ + return res; + +#define LTR_BINOP(_f, _cond) \ + jsval_t res = _f(js); \ + while (!is_err(res) && (_cond)) { \ + uint8_t op = js->tok; \ + js->consumed = 1; \ + jsval_t rhs = _f(js); \ + if (is_err(rhs)) return rhs; \ + res = do_op(js, op, res, rhs); \ + } \ + return res; + +static jsval_t js_literal(struct js *js) { + next(js); + setlwm(js); + // printf("css : %u\n", js->css); + if (js->maxcss > 0 && js->css > js->maxcss) return js_mkerr(js, "C stack"); + js->consumed = 1; + switch (js->tok) { // clang-format off + case TOK_ERR: return js_mkerr(js, "parse error"); + case TOK_NUMBER: return js->tval; + case TOK_STRING: return js_str_literal(js); + case TOK_LBRACE: return js_obj_literal(js); + case TOK_FUNC: return js_func_literal(js); + case TOK_NULL: return js_mknull(); + case TOK_UNDEF: return js_mkundef(); + case TOK_TRUE: return js_mktrue(); + case TOK_FALSE: return js_mkfalse(); + case TOK_IDENTIFIER: return mkcoderef((jsoff_t) js->toff, (jsoff_t) js->tlen); + default: return js_mkerr(js, "bad expr"); + } // clang-format on +} + +static jsval_t js_group(struct js *js) { + if (next(js) == TOK_LPAREN) { + js->consumed = 1; + jsval_t v = js_expr(js); + if (is_err(v)) return v; + if (next(js) != TOK_RPAREN) return js_mkerr(js, ") expected"); + js->consumed = 1; + return v; + } else { + return js_literal(js); + } +} + +static jsval_t js_call_dot(struct js *js) { + jsval_t res = js_group(js); + if (is_err(res)) return res; + if (vtype(res) == T_CODEREF) { + res = lookup(js, &js->code[coderefoff(res)], codereflen(res)); + } + while (next(js) == TOK_LPAREN || next(js) == TOK_DOT) { + if (js->tok == TOK_DOT) { + js->consumed = 1; + res = do_op(js, TOK_DOT, res, js_group(js)); + } else { + jsval_t params = js_call_params(js); + if (is_err(params)) return params; + res = do_op(js, TOK_CALL, res, params); + } + } + return res; +} + +static jsval_t js_postfix(struct js *js) { + jsval_t res = js_call_dot(js); + if (is_err(res)) return res; + next(js); + if (js->tok == TOK_POSTINC || js->tok == TOK_POSTDEC) { + js->consumed = 1; + res = do_op(js, js->tok, res, 0); + } + return res; +} + +static jsval_t js_unary(struct js *js) { + if (next(js) == TOK_NOT || js->tok == TOK_TILDA || js->tok == TOK_TYPEOF || + js->tok == TOK_MINUS || js->tok == TOK_PLUS) { + uint8_t t = js->tok; + if (t == TOK_MINUS) t = TOK_UMINUS; + if (t == TOK_PLUS) t = TOK_UPLUS; + js->consumed = 1; + return do_op(js, t, js_mkundef(), js_unary(js)); + } else { + return js_postfix(js); + } +} + +static jsval_t js_mul_div_rem(struct js *js) { + LTR_BINOP(js_unary, + (next(js) == TOK_MUL || js->tok == TOK_DIV || js->tok == TOK_REM)); +} + +static jsval_t js_plus_minus(struct js *js) { + LTR_BINOP(js_mul_div_rem, (next(js) == TOK_PLUS || js->tok == TOK_MINUS)); +} + +static jsval_t js_shifts(struct js *js) { + LTR_BINOP(js_plus_minus, (next(js) == TOK_SHR || next(js) == TOK_SHL || + next(js) == TOK_ZSHR)); +} + +static jsval_t js_comparison(struct js *js) { + LTR_BINOP(js_shifts, (next(js) == TOK_LT || next(js) == TOK_LE || + next(js) == TOK_GT || next(js) == TOK_GE)); +} + +static jsval_t js_equality(struct js *js) { + LTR_BINOP(js_comparison, (next(js) == TOK_EQ || next(js) == TOK_NE)); +} + +static jsval_t js_bitwise_and(struct js *js) { + LTR_BINOP(js_equality, (next(js) == TOK_AND)); +} + +static jsval_t js_bitwise_xor(struct js *js) { + LTR_BINOP(js_bitwise_and, (next(js) == TOK_XOR)); +} + +static jsval_t js_bitwise_or(struct js *js) { + LTR_BINOP(js_bitwise_xor, (next(js) == TOK_OR)); +} + +static jsval_t js_logical_and(struct js *js) { + jsval_t res = js_bitwise_or(js); + if (is_err(res)) return res; + uint8_t flags = js->flags; + while (next(js) == TOK_LAND) { + js->consumed = 1; + res = resolveprop(js, res); + if (!js_truthy(js, res)) js->flags |= F_NOEXEC; // false && ... shortcut + if (js->flags & F_NOEXEC) { + js_logical_and(js); + } else { + res = js_logical_and(js); + } + } + js->flags = flags; + return res; +} + +static jsval_t js_logical_or(struct js *js) { + jsval_t res = js_logical_and(js); + if (is_err(res)) return res; + uint8_t flags = js->flags; + while (next(js) == TOK_LOR) { + js->consumed = 1; + res = resolveprop(js, res); + if (js_truthy(js, res)) js->flags |= F_NOEXEC; // true || ... shortcut + if (js->flags & F_NOEXEC) { + js_logical_or(js); + } else { + res = js_logical_or(js); + } + } + js->flags = flags; + return res; +} + +static jsval_t js_ternary(struct js *js) { + jsval_t res = js_logical_or(js); + if (next(js) == TOK_Q) { + uint8_t flags = js->flags; + js->consumed = 1; + if (js_truthy(js, resolveprop(js, res))) { + res = js_ternary(js); + js->flags |= F_NOEXEC; + EXPECT(TOK_COLON, js->flags = flags); + js_ternary(js); + js->flags = flags; + } else { + js->flags |= F_NOEXEC; + js_ternary(js); + EXPECT(TOK_COLON, js->flags = flags); + js->flags = flags; + res = js_ternary(js); + } + } + return res; +} + +static jsval_t js_assignment(struct js *js) { + RTL_BINOP(js_ternary, js_assignment, + (next(js) == TOK_ASSIGN || js->tok == TOK_PLUS_ASSIGN || + js->tok == TOK_MINUS_ASSIGN || js->tok == TOK_MUL_ASSIGN || + js->tok == TOK_DIV_ASSIGN || js->tok == TOK_REM_ASSIGN || + js->tok == TOK_SHL_ASSIGN || js->tok == TOK_SHR_ASSIGN || + js->tok == TOK_ZSHR_ASSIGN || js->tok == TOK_AND_ASSIGN || + js->tok == TOK_XOR_ASSIGN || js->tok == TOK_OR_ASSIGN)); +} + +static jsval_t js_expr(struct js *js) { + return js_assignment(js); +} + +static jsval_t js_let(struct js *js) { + uint8_t exe = !(js->flags & F_NOEXEC); + js->consumed = 1; + for (;;) { + EXPECT(TOK_IDENTIFIER, ); + js->consumed = 0; + jsoff_t noff = js->toff, nlen = js->tlen; + char *name = (char *) &js->code[noff]; + jsval_t v = js_mkundef(); + js->consumed = 1; + if (next(js) == TOK_ASSIGN) { + js->consumed = 1; + v = js_expr(js); + if (is_err(v)) return v; // Propagate error if any + } + if (exe) { + if (lkp(js, js->scope, name, nlen) > 0) + return js_mkerr(js, "'%.*s' already declared", (int) nlen, name); + jsval_t x = + setprop(js, js->scope, js_mkstr(js, name, nlen), resolveprop(js, v)); + if (is_err(x)) return x; + } + if (next(js) == TOK_SEMICOLON || next(js) == TOK_EOF) break; // Stop + EXPECT(TOK_COMMA, ); + } + return js_mkundef(); +} + +static jsval_t js_block_or_stmt(struct js *js) { + if (next(js) == TOK_LBRACE) return js_block(js, !(js->flags & F_NOEXEC)); + jsval_t res = resolveprop(js, js_stmt(js)); + js->consumed = 0; // + return res; +} + +static jsval_t js_if(struct js *js) { + js->consumed = 1; + EXPECT(TOK_LPAREN, ); + jsval_t res = js_mkundef(), cond = resolveprop(js, js_expr(js)); + EXPECT(TOK_RPAREN, ); + bool cond_true = js_truthy(js, cond), exe = !(js->flags & F_NOEXEC); + // printf("IF COND: %s, true? %d\n", js_str(js, cond), cond_true); + if (!cond_true) js->flags |= F_NOEXEC; + jsval_t blk = js_block_or_stmt(js); + if (cond_true) res = blk; + if (exe && !cond_true) js->flags &= (uint8_t) ~F_NOEXEC; + if (lookahead(js) == TOK_ELSE) { + js->consumed = 1; + next(js); + js->consumed = 1; + if (cond_true) js->flags |= F_NOEXEC; + blk = js_block_or_stmt(js); + if (!cond_true) res = blk; + if (cond_true && exe) js->flags &= (uint8_t) ~F_NOEXEC; + } + return res; +} + +static inline bool expect(struct js *js, uint8_t tok, jsval_t *res) { + if (next(js) != tok) { + *res = js_mkerr(js, "parse error"); + return false; + } else { + js->consumed = 1; + return true; + } +} + +static inline bool is_err2(jsval_t *v, jsval_t *res) { + bool r = is_err(*v); + if (r) *res = *v; + return r; +} + +static jsval_t js_for(struct js *js) { + uint8_t flags = js->flags, exe = !(flags & F_NOEXEC); + jsval_t v, res = js_mkundef(); + jsoff_t pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; + if (exe) mkscope(js); // Enter new scope + if (!expect(js, TOK_FOR, &res)) goto done; + if (!expect(js, TOK_LPAREN, &res)) goto done; + + if (next(js) == TOK_SEMICOLON) { // initialisation + } else if (next(js) == TOK_LET) { + v = js_let(js); + if (is_err2(&v, &res)) goto done; + } else { + v = js_expr(js); + if (is_err2(&v, &res)) goto done; + } + if (!expect(js, TOK_SEMICOLON, &res)) goto done; + js->flags |= F_NOEXEC; + pos1 = js->pos; // condition + if (next(js) != TOK_SEMICOLON) { + v = js_expr(js); + if (is_err2(&v, &res)) goto done; + } + if (!expect(js, TOK_SEMICOLON, &res)) goto done; + pos2 = js->pos; // final expr + if (next(js) != TOK_RPAREN) { + v = js_expr(js); + if (is_err2(&v, &res)) goto done; + } + if (!expect(js, TOK_RPAREN, &res)) goto done; + pos3 = js->pos; // body + v = js_block_or_stmt(js); + if (is_err2(&v, &res)) goto done; + pos4 = js->pos; // end of body + while (!(flags & F_NOEXEC)) { + js->flags = flags, js->pos = pos1, js->consumed = 1; + if (next(js) != TOK_SEMICOLON) { // Is condition specified? + v = resolveprop(js, js_expr(js)); // Yes. check condition + if (is_err2(&v, &res)) goto done; // Fail short on error + if (!js_truthy(js, v)) break; // Exit the loop if condition is false + } + js->pos = pos3, js->consumed = 1, js->flags |= F_LOOP; // Execute the + v = js_block_or_stmt(js); // loop body + if (is_err2(&v, &res)) goto done; // Fail on error + if (js->flags & F_BREAK) break; // break was executed - exit the loop! + js->flags = flags, js->pos = pos2, js->consumed = 1; // Jump to final expr + if (next(js) != TOK_RPAREN) { // Is it specified? + v = js_expr(js); // Yes. Execute it + if (is_err2(&v, &res)) goto done; // On error, fail short + } + } + js->pos = pos4, js->tok = TOK_SEMICOLON, js->consumed = 0; +done: + if (exe) delscope(js); // Exit scope + js->flags = flags; // Restore flags + return res; +} + +static jsval_t js_break(struct js *js) { + if (js->flags & F_NOEXEC) { + } else { + if (!(js->flags & F_LOOP)) return js_mkerr(js, "not in loop"); + js->flags |= F_BREAK | F_NOEXEC; + } + js->consumed = 1; + return js_mkundef(); +} + +static jsval_t js_continue(struct js *js) { + if (js->flags & F_NOEXEC) { + } else { + if (!(js->flags & F_LOOP)) return js_mkerr(js, "not in loop"); + js->flags |= F_NOEXEC; + } + js->consumed = 1; + return js_mkundef(); +} + +static jsval_t js_return(struct js *js) { + uint8_t exe = !(js->flags & F_NOEXEC); + js->consumed = 1; + if (exe && !(js->flags & F_CALL)) return js_mkerr(js, "not in func"); + if (next(js) == TOK_SEMICOLON) return js_mkundef(); + jsval_t res = resolveprop(js, js_expr(js)); + if (exe) { + js->pos = js->clen; // Shift to the end - exit the code snippet + js->flags |= F_RETURN; // Tell caller we've executed + } + return resolveprop(js, res); +} + +static jsval_t js_stmt(struct js *js) { + jsval_t res; + // jsoff_t pos = js->pos - js->tlen; + if (js->brk > js->gct) js_gc(js); + switch (next(js)) { // clang-format off + case TOK_CASE: case TOK_CATCH: case TOK_CLASS: case TOK_CONST: + case TOK_DEFAULT: case TOK_DELETE: case TOK_DO: case TOK_FINALLY: + case TOK_IN: case TOK_INSTANCEOF: case TOK_NEW: case TOK_SWITCH: + case TOK_THIS: case TOK_THROW: case TOK_TRY: case TOK_VAR: case TOK_VOID: + case TOK_WITH: case TOK_WHILE: case TOK_YIELD: + res = js_mkerr(js, "'%.*s' not implemented", (int) js->tlen, js->code + js->toff); + break; + case TOK_CONTINUE: res = js_continue(js); break; + case TOK_BREAK: res = js_break(js); break; + case TOK_LET: res = js_let(js); break; + case TOK_IF: res = js_if(js); break; + case TOK_LBRACE: res = js_block(js, !(js->flags & F_NOEXEC)); break; + case TOK_FOR: res = js_for(js); break; // 25222 -> 27660 + case TOK_RETURN: res = js_return(js); break; + default: res = resolveprop(js, js_expr(js)); break; + } + //printf("STMT [%.*s] -> %s, tok %d, flags %d\n", (int) (js->pos - pos), &js->code[pos], js_str(js, res), next(js), js->flags); + if (next(js) != TOK_SEMICOLON && next(js) != TOK_EOF && next(js) != TOK_RBRACE) return js_mkerr(js, "; expected"); + js->consumed = 1; + // clang-format on + return res; +} + +struct js *js_create(void *buf, size_t len) { + struct js *js = NULL; + if (len < sizeof(*js) + esize(T_OBJ)) return js; + memset(buf, 0, len); // Important! + js = (struct js *) buf; // struct js lives at the beginning + js->mem = (uint8_t *) (js + 1); // Then goes memory for JS data + js->size = (jsoff_t) (len - sizeof(*js)); // JS memory size + js->scope = mkobj(js, 0); // Create global scope + js->size = js->size / 8U * 8U; // Align js->size by 8 byte + js->lwm = js->size; // Initial LWM: 100% free + js->gct = js->size / 2; + return js; +} + +// clang-format off +void js_setgct(struct js *js, size_t gct) { js->gct = (jsoff_t) gct; } +void js_setmaxcss(struct js *js, size_t max) { js->maxcss = (jsoff_t) max; } +jsval_t js_mktrue(void) { return mkval(T_BOOL, 1); } +jsval_t js_mkfalse(void) { return mkval(T_BOOL, 0); } +jsval_t js_mkundef(void) { return mkval(T_UNDEF, 0); } +jsval_t js_mknull(void) { return mkval(T_NULL, 0); } +jsval_t js_mknum(double value) { return tov(value); } +jsval_t js_mkobj(struct js *js) { return mkobj(js, 0); } +jsval_t js_mkfun(jsval_t (*fn)(struct js *, jsval_t *, int)) { return mkval(T_CFUNC, (size_t) (void *) fn); } +double js_getnum(jsval_t value) { return tod(value); } +int js_getbool(jsval_t value) { return vdata(value) & 1 ? 1 : 0; } + +jsval_t js_glob(struct js *js) { (void) js; return mkval(T_OBJ, 0); } + +void js_set(struct js *js, jsval_t obj, const char *key, jsval_t val) { + if (vtype(obj) == T_OBJ) setprop(js, obj, js_mkstr(js, key, strlen(key)), val); +} + +char *js_getstr(struct js *js, jsval_t value, size_t *len) { + if (vtype(value) != T_STR) return NULL; + jsoff_t n, off = vstr(js, value, &n); + if (len != NULL) *len = n; + return (char *) &js->mem[off]; +} + +int js_type(jsval_t val) { + switch (vtype(val)) { + case T_UNDEF: return JS_UNDEF; + case T_NULL: return JS_NULL; + case T_BOOL: return vdata(val) == 0 ? JS_FALSE: JS_TRUE; + case T_STR: return JS_STR; + case T_NUM: return JS_NUM; + case T_ERR: return JS_ERR; + default: return JS_PRIV; + } +} +void js_stats(struct js *js, size_t *total, size_t *lwm, size_t *css) { + if (total) *total = js->size; + if (lwm) *lwm = js->lwm; + if (css) *css = js->css; +} +// clang-format on + +bool js_chkargs(jsval_t *args, int nargs, const char *spec) { + int i = 0, ok = 1; + for (; ok && i < nargs && spec[i]; i++) { + uint8_t t = vtype(args[i]), c = (uint8_t) spec[i]; + ok = (c == 'b' && t == T_BOOL) || (c == 'd' && t == T_NUM) || + (c == 's' && t == T_STR) || (c == 'j'); + } + if (spec[i] != '\0' || i != nargs) ok = 0; + return ok; +} + +jsval_t js_eval(struct js *js, const char *buf, size_t len) { + // printf("EVAL: [%.*s]\n", (int) len, buf); + jsval_t res = js_mkundef(); + if (len == (size_t) ~0U) len = strlen(buf); + js->consumed = 1; + js->tok = TOK_ERR; + js->code = buf; + js->clen = (jsoff_t) len; + js->pos = 0; + js->cstk = &res; + while (next(js) != TOK_EOF && !is_err(res)) { + res = js_stmt(js); + } + return res; +} + +#ifdef JS_DUMP +void js_dump(struct js *js) { + jsoff_t off = 0, v; + printf("JS size %u, brk %u, lwm %u, css %u, nogc %u\n", js->size, js->brk, + js->lwm, (unsigned) js->css, js->nogc); + while (off < js->brk) { + memcpy(&v, &js->mem[off], sizeof(v)); + printf(" %5u: ", off); + if ((v & 3U) == T_OBJ) { + printf("OBJ %u %u\n", v & ~3U, + loadoff(js, (jsoff_t) (off + sizeof(off)))); + } else if ((v & 3U) == T_PROP) { + jsoff_t koff = loadoff(js, (jsoff_t) (off + sizeof(v))); + jsval_t val = loadval(js, (jsoff_t) (off + sizeof(v) + sizeof(v))); + printf("PROP next %u, koff %u vtype %d vdata %lu\n", v & ~3U, koff, + vtype(val), (unsigned long) vdata(val)); + } else if ((v & 3) == T_STR) { + jsoff_t len = offtolen(v); + printf("STR %u [%.*s]\n", len, (int) len, js->mem + off + sizeof(v)); + } else { + printf("???\n"); + break; + } + off += esize(v); + } +} +#endif diff --git a/applications/system/elk_js/elk.h b/applications/system/elk_js/elk.h new file mode 100644 index 00000000000..711634a2a14 --- /dev/null +++ b/applications/system/elk_js/elk.h @@ -0,0 +1,62 @@ +// Copyright (c) 2013-2022 Cesanta Software Limited +// All rights reserved +// +// This software is dual-licensed: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License version 3 as +// published by the Free Software Foundation. For the terms of this +// license, see http://www.fsf.org/licensing/licenses/agpl-3.0.html +// +// You are free to use this software under the terms of the GNU General +// Public License, but WITHOUT ANY WARRANTY; without even the implied +// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU General Public License for more details. +// +// Alternatively, you can license this software under a commercial +// license, please contact us at https://cesanta.com/contact.html + +#define JS_VERSION "3.0.0" +#pragma once + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct js; // JS engine (opaque) +typedef uint64_t jsval_t; // JS value + +struct js *js_create(void *buf, size_t len); // Create JS instance +jsval_t js_eval(struct js *, const char *, size_t); // Execute JS code +jsval_t js_glob(struct js *); // Return global object +const char *js_str(struct js *, jsval_t val); // Stringify JS value +bool js_chkargs(jsval_t *, int, const char *); // Check args validity +void js_setmaxcss(struct js *, size_t); // Set max C stack size +void js_setgct(struct js *, size_t); // Set GC trigger threshold +void js_stats(struct js *, size_t *total, size_t *min, size_t *cstacksize); +void js_dump(struct js *); // Print debug info. Requires -DJS_DUMP + +// Create JS values from C values +jsval_t js_mkundef(void); // Create undefined +jsval_t js_mknull(void); // Create null, null, true, false +jsval_t js_mktrue(void); // Create true +jsval_t js_mkfalse(void); // Create false +jsval_t js_mkstr(struct js *, const void *, size_t); // Create string +jsval_t js_mknum(double); // Create number +jsval_t js_mkerr(struct js *js, const char *fmt, ...); // Create error +jsval_t js_mkfun(jsval_t (*fn)(struct js *, jsval_t *, int)); // Create func +jsval_t js_mkobj(struct js *); // Create object +void js_set(struct js *, jsval_t, const char *, jsval_t); // Set obj attr + +// Extract C values from JS values +enum { JS_UNDEF, JS_NULL, JS_TRUE, JS_FALSE, JS_STR, JS_NUM, JS_ERR, JS_PRIV }; +int js_type(jsval_t val); // Return JS value type +double js_getnum(jsval_t val); // Get number +int js_getbool(jsval_t val); // Get boolean, 0 or 1 +char *js_getstr(struct js *js, jsval_t val, size_t *len); // Get string + +#ifdef __cplusplus +} +#endif diff --git a/applications/system/elk_js/elk_js.c b/applications/system/elk_js/elk_js.c new file mode 100644 index 00000000000..f89d6fe2e97 --- /dev/null +++ b/applications/system/elk_js/elk_js.c @@ -0,0 +1,400 @@ +#include +#include "elk.h" +#include +#include +#include +#include +#include +#include "ffi/ffi.h" +#include + +#define TAG "JS" + +typedef enum { + ArgTypeVoid, + ArgTypeUint8, + ArgTypeSint8, + ArgTypeUint16, + ArgTypeSint16, + ArgTypeUint32, + ArgTypeSint32, + ArgTypeUint64, + ArgTypeSint64, + ArgTypeFloat, + ArgTypeDouble, + ArgTypePointer, + ArgTypeString, +} ArgType; + +void test_ffi() { + ffi_cif cif; + ffi_type* args[1]; + void* values[1]; + char* s; + ffi_arg rc; + + /* Initialize the argument info vectors */ + args[0] = &ffi_type_pointer; + values[0] = &s; + + void (*fn)(void) = (void (*)(void))puts; + + /* Initialize the cif */ + if(ffi_prep_cif(&cif, FFI_DEFAULT_ABI, 1, &ffi_type_sint, args) == FFI_OK) { + s = "Hello World!"; + ffi_call(&cif, fn, &rc, values); + s = "This is cool!"; + ffi_call(&cif, fn, &rc, values); + } +} + +static ffi_type* type_from_argtype(ArgType argtype) { + switch(argtype) { + case ArgTypeVoid: + return &ffi_type_void; + case ArgTypeUint8: + return &ffi_type_uint8; + case ArgTypeSint8: + return &ffi_type_sint8; + case ArgTypeUint16: + return &ffi_type_uint16; + case ArgTypeSint16: + return &ffi_type_sint16; + case ArgTypeUint32: + return &ffi_type_uint32; + case ArgTypeSint32: + return &ffi_type_sint32; + case ArgTypeUint64: + return &ffi_type_uint64; + case ArgTypeSint64: + return &ffi_type_sint64; + case ArgTypeFloat: + return &ffi_type_float; + case ArgTypeDouble: + return &ffi_type_double; + case ArgTypePointer: + return &ffi_type_pointer; + case ArgTypeString: + return &ffi_type_pointer; + } + + return NULL; +} + +static jsval_t js_global_fficall(struct js* js, jsval_t* args, int nargs) { + Elf32_Addr addr; + const char* name = js_getstr(js, args[1], NULL); + uint32_t hash = elf_symbolname_hash(name); + if(!firmware_api_interface->resolver_callback(firmware_api_interface, hash, &addr)) { + FURI_LOG_E(TAG, "FFI: cannot find \"%s\"", name); + return js_mkundef(); + } + FURI_LOG_I(TAG, "FFI {"); + void (*fn)(void) = (void (*)(void))addr; + FURI_LOG_I(TAG, " \"%s\" = 0x%p", name, fn); + + const size_t arg_count = (nargs - 2) / 2; + FURI_LOG_I(TAG, " args count %u", arg_count); + + ffi_type* ffi_args[arg_count]; + uint64_t ffi_vals[arg_count]; + void* ffi_vals_p[arg_count]; + for(size_t i = 0; i < arg_count; i++) { + ArgType type = (ArgType)js_getnum(args[2 + i * 2]); + jsval_t arg = args[3 + i * 2]; + ffi_args[i] = type_from_argtype(type); + switch(type) { + case ArgTypeVoid: + ffi_vals[i] = 0; + FURI_LOG_I(TAG, " arg %u void", i); + break; + case ArgTypeUint8: + case ArgTypeSint8: + case ArgTypeUint16: + case ArgTypeSint16: + case ArgTypeUint32: + case ArgTypeSint32: + case ArgTypeUint64: + case ArgTypeSint64: + case ArgTypeFloat: + case ArgTypeDouble: + ffi_vals[i] = js_getnum(arg); + FURI_LOG_I(TAG, " arg %u num = %f", i, (double)ffi_vals[i]); + break; + case ArgTypePointer: + ffi_vals[i] = js_getnum(arg); + FURI_LOG_I(TAG, " arg %u ptr = 0x%p", i, (void*)(uint32_t)ffi_vals[i]); + break; + case ArgTypeString: + ffi_vals[i] = (uint32_t)js_getstr(js, arg, NULL); + FURI_LOG_I(TAG, " arg %u str = \"%s\"", i, (const char*)(uint32_t)ffi_vals[i]); + break; + default: + FURI_LOG_E(TAG, "FFI: invalid arg type %u", type); + return js_mkundef(); + } + + ffi_vals_p[i] = &ffi_vals[i]; + } + + ArgType type = (ArgType)js_getnum(args[0]); + ffi_arg rc; + ffi_cif cif; + + if(ffi_prep_cif(&cif, FFI_DEFAULT_ABI, arg_count, type_from_argtype(type), ffi_args) == + FFI_OK) { + ffi_call(&cif, fn, &rc, ffi_vals_p); + } else { + FURI_LOG_E(TAG, "FFI: ffi_prep_cif failed"); + } + + jsval_t ret; + + switch(type) { + case ArgTypeVoid: + FURI_LOG_I(TAG, " ret void"); + ret = js_mknum(0); + break; + case ArgTypeUint8: + ret = js_mknum((uint8_t)rc); + break; + case ArgTypeSint8: + ret = js_mknum((int8_t)rc); + break; + case ArgTypeUint16: + ret = js_mknum((uint16_t)rc); + break; + case ArgTypeSint16: + ret = js_mknum((int16_t)rc); + break; + case ArgTypeUint32: + ret = js_mknum((uint32_t)rc); + break; + case ArgTypeSint32: + ret = js_mknum((int32_t)rc); + break; + case ArgTypeUint64: + ret = js_mknum((uint64_t)rc); + break; + case ArgTypeSint64: + ret = js_mknum((int64_t)rc); + break; + case ArgTypeFloat: + FURI_LOG_I(TAG, " ret double %f", (double)(float)rc); + ret = js_mknum((float)rc); + break; + case ArgTypeDouble: + FURI_LOG_I(TAG, " ret double %f", (double)rc); + ret = js_mknum((double)rc); + break; + case ArgTypePointer: + FURI_LOG_I(TAG, " ret pointer 0x%p", (void*)rc); + ret = js_mknum((uint32_t)rc); + break; + case ArgTypeString: + FURI_LOG_I(TAG, " ret string \"%s\"", (char*)rc); + ret = js_mkstr(js, (char*)rc, strlen((char*)rc)); + break; + default: + FURI_LOG_E(TAG, "FFI: ret error"); + ret = js_mkundef(); + break; + } + + FURI_LOG_I(TAG, "}"); + + return ret; +} + +static jsval_t js_global_ffires(struct js* js, jsval_t* args, int nargs) { + UNUSED(js); + UNUSED(nargs); + Elf32_Addr addr; + const char* name = js_getstr(js, args[0], NULL); + uint32_t hash = elf_symbolname_hash(name); + if(!firmware_api_interface->resolver_callback(firmware_api_interface, hash, &addr)) { + FURI_LOG_E(TAG, "RES: cannot find %s", name); + return js_mkundef(); + } + + FURI_LOG_I(TAG, "RES: \"%s\" = 0x%p", name, (void*)addr); + return js_mknum((uint32_t)addr); +} + +static void notify(const NotificationSequence* sequence) { + NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); + notification_message(notification, sequence); + furi_record_close(RECORD_NOTIFICATION); +} + +static jsval_t js_global_delay(struct js* js, jsval_t* args, int nargs) { + UNUSED(js); + UNUSED(nargs); + furi_delay_ms(js_getnum(args[0])); + return js_mkundef(); +} + +static jsval_t js_led_red(struct js* js, jsval_t* args, int nargs) { + UNUSED(js); + UNUSED(args); + UNUSED(nargs); + notify(&sequence_set_only_red_255); + return js_mkundef(); +} + +static jsval_t js_led_green(struct js* js, jsval_t* args, int nargs) { + UNUSED(js); + UNUSED(args); + UNUSED(nargs); + notify(&sequence_set_only_green_255); + return js_mkundef(); +} + +static jsval_t js_led_blue(struct js* js, jsval_t* args, int nargs) { + UNUSED(js); + UNUSED(args); + UNUSED(nargs); + notify(&sequence_set_only_blue_255); + return js_mkundef(); +} + +static jsval_t js_global_print(struct js* js, jsval_t* args, int nargs) { + for(int i = 0; i < nargs; i++) { + const char* space = i == 0 ? "" : " "; + printf("%s%s", space, js_str(js, args[i])); + } + printf("\r\n"); + return js_mkundef(); +} + +static jsval_t js_global_print_pointer(struct js* js, jsval_t* args, int nargs) { + UNUSED(js); + UNUSED(nargs); + uint32_t p = (uint32_t)js_getnum(args[0]); + printf("%lu\r\n", p); + return js_mkundef(); +} + +static jsval_t js_global_require(struct js* js, jsval_t* args, int nargs) { + UNUSED(nargs); + Storage* storage = furi_record_open(RECORD_STORAGE); + jsval_t res = js_mkundef(); + File* file = storage_file_alloc(storage); + char* data = NULL; + const char* name = js_getstr(js, args[0], NULL); + + do { + if(!storage_file_open(file, name, FSAM_READ, FSOM_OPEN_EXISTING)) { + FURI_LOG_E("JS", "Cannot open %s", name); + break; + } + + size_t size = storage_file_size(file); + data = (char*)malloc(size + 1); + if(storage_file_read(file, data, size) != size) { + FURI_LOG_E("JS", "Cannot read %s", name); + break; + } + data[size] = '\0'; + res = js_eval(js, data, ~0U); + + if(js_type(res) == JS_ERR) { + FURI_LOG_E("JS", "%s: %s", name, js_str(js, res)); + } + + free(data); + } while(false); + + if(data) free(data); + storage_file_free(file); + + furi_record_close(RECORD_STORAGE); + return res; +} + +static bool js_do(const char* text) { + bool result = false; + const size_t memory_size = 16 * 1024; + uint8_t* memory = (uint8_t*)malloc(memory_size); + struct js* js = js_create(memory, memory_size); + + jsval_t global = js_glob(js); + jsval_t led = js_mkobj(js); + jsval_t arg = js_mkobj(js); + + js_set(js, global, "arg", arg); + js_set(js, arg, "none", js_mknum(ArgTypeVoid)); + js_set(js, arg, "uint8", js_mknum(ArgTypeUint8)); + js_set(js, arg, "sint8", js_mknum(ArgTypeSint8)); + js_set(js, arg, "uint16", js_mknum(ArgTypeUint16)); + js_set(js, arg, "sint16", js_mknum(ArgTypeSint16)); + js_set(js, arg, "uint32", js_mknum(ArgTypeUint32)); + js_set(js, arg, "sint32", js_mknum(ArgTypeSint32)); + js_set(js, arg, "uint64", js_mknum(ArgTypeUint64)); + js_set(js, arg, "sint64", js_mknum(ArgTypeSint64)); + js_set(js, arg, "float", js_mknum(ArgTypeFloat)); + js_set(js, arg, "double", js_mknum(ArgTypeDouble)); + js_set(js, arg, "pointer", js_mknum(ArgTypePointer)); + js_set(js, arg, "string", js_mknum(ArgTypeString)); + js_set(js, global, "fficall", js_mkfun(js_global_fficall)); + js_set(js, global, "ffires", js_mkfun(js_global_ffires)); + + js_set(js, global, "delay", js_mkfun(js_global_delay)); + js_set(js, global, "print", js_mkfun(js_global_print)); + js_set(js, global, "print_pointer", js_mkfun(js_global_print_pointer)); + js_set(js, global, "require", js_mkfun(js_global_require)); + + js_set(js, global, "led", led); + js_set(js, led, "red", js_mkfun(js_led_red)); + js_set(js, led, "green", js_mkfun(js_led_green)); + js_set(js, led, "blue", js_mkfun(js_led_blue)); + + jsval_t res = js_eval(js, text, ~0U); + + size_t total_ram = 0, min_ram = 0, stack = 0; + js_stats(js, &total_ram, &min_ram, &stack); + FURI_LOG_I("JS", "RAM: total %u, lowest free %u, C stack: %u", total_ram, min_ram, stack); + + if(js_type(res) == JS_ERR) { + FURI_LOG_E("JS", "%s", js_str(js, res)); + result = false; + } else { + result = true; + } + + free(memory); + return result; +} + +int32_t elk_js_app(void* arg) { + Storage* storage = furi_record_open(RECORD_STORAGE); + DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); + FuriString* name = furi_string_alloc_set(EXT_PATH("scripts")); + File* file = storage_file_alloc(storage); + char* data = NULL; + + do { + if(arg != NULL && strlen(arg) > 0) { + furi_string_set(name, (const char*)arg); + } else { + DialogsFileBrowserOptions browser_options; + dialog_file_browser_set_basic_options(&browser_options, ".js", NULL); + if(!dialog_file_browser_show(dialogs, name, name, &browser_options)) break; + } + + if(!storage_file_open(file, furi_string_get_cstr(name), FSAM_READ, FSOM_OPEN_EXISTING)) + break; + size_t size = storage_file_size(file); + data = (char*)malloc(size + 1); + if(storage_file_read(file, data, size) != size) break; + data[size] = '\0'; + js_do(data); + } while(false); + + if(data) free(data); + furi_string_free(name); + storage_file_free(file); + furi_record_close(RECORD_STORAGE); + furi_record_close(RECORD_DIALOGS); + return 0; +} \ No newline at end of file diff --git a/applications/system/elk_js/ffi/ffi.c b/applications/system/elk_js/ffi/ffi.c new file mode 100644 index 00000000000..aece4ccdf5b --- /dev/null +++ b/applications/system/elk_js/ffi/ffi.c @@ -0,0 +1,705 @@ +/* ----------------------------------------------------------------------- + ffi.c - Copyright (c) 2011 Timothy Wall + Copyright (c) 2011 Plausible Labs Cooperative, Inc. + Copyright (c) 2011 Anthony Green + Copyright (c) 2011 Free Software Foundation + Copyright (c) 1998, 2008, 2011 Red Hat, Inc. + ARM Foreign Function Interface + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + ``Software''), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + ----------------------------------------------------------------------- */ + +#include "fficonfig.h" +#include "ffi.h" +#include "ffi_common.h" +#include +#include +#include "internal.h" + +#if FFI_EXEC_TRAMPOLINE_TABLE + +#ifdef __MACH__ +#include +#endif + +#else +extern unsigned int ffi_arm_trampoline[2] FFI_HIDDEN; +#endif + +/* Forward declares. */ +static int vfp_type_p(const ffi_type*); +static void layout_vfp_args(ffi_cif*); + +static void* ffi_align(ffi_type* ty, void* p) { + /* Align if necessary */ + size_t alignment; +#ifdef _WIN32_WCE + alignment = 4; +#else + alignment = ty->alignment; + if(alignment < 4) alignment = 4; +#endif + return (void*)ALIGN(p, alignment); +} + +static size_t ffi_put_arg(ffi_type* ty, void* src, void* dst) { + size_t z = ty->size; + + switch(ty->type) { + case FFI_TYPE_SINT8: + *(UINT32*)dst = *(SINT8*)src; + break; + case FFI_TYPE_UINT8: + *(UINT32*)dst = *(UINT8*)src; + break; + case FFI_TYPE_SINT16: + *(UINT32*)dst = *(SINT16*)src; + break; + case FFI_TYPE_UINT16: + *(UINT32*)dst = *(UINT16*)src; + break; + + case FFI_TYPE_INT: + case FFI_TYPE_SINT32: + case FFI_TYPE_UINT32: + case FFI_TYPE_POINTER: + case FFI_TYPE_FLOAT: + *(UINT32*)dst = *(UINT32*)src; + break; + + case FFI_TYPE_SINT64: + case FFI_TYPE_UINT64: + case FFI_TYPE_DOUBLE: + *(UINT64*)dst = *(UINT64*)src; + break; + + case FFI_TYPE_STRUCT: + case FFI_TYPE_COMPLEX: + memcpy(dst, src, z); + break; + + default: + abort(); + } + + return ALIGN(z, 4); +} + +/* ffi_prep_args is called once stack space has been allocated + for the function's arguments. + The vfp_space parameter is the load area for VFP regs, the return + value is cif->vfp_used (word bitset of VFP regs used for passing + arguments). These are only used for the VFP hard-float ABI. +*/ +static void ffi_prep_args_SYSV(ffi_cif* cif, int flags, void* rvalue, void** avalue, char* argp) { + ffi_type** arg_types = cif->arg_types; + int i, n; + + if(flags == ARM_TYPE_STRUCT) { + *(void**)argp = rvalue; + argp += 4; + } + + for(i = 0, n = cif->nargs; i < n; i++) { + ffi_type* ty = arg_types[i]; + argp = ffi_align(ty, argp); + argp += ffi_put_arg(ty, avalue[i], argp); + } +} + +static void ffi_prep_args_VFP( + ffi_cif* cif, + int flags, + void* rvalue, + void** avalue, + char* stack, + char* vfp_space) { + ffi_type** arg_types = cif->arg_types; + int i, n, vi = 0; + char *argp, *regp, *eo_regp; + char stack_used = 0; + char done_with_regs = 0; + + /* The first 4 words on the stack are used for values + passed in core registers. */ + regp = stack; + eo_regp = argp = regp + 16; + + /* If the function returns an FFI_TYPE_STRUCT in memory, + that address is passed in r0 to the function. */ + if(flags == ARM_TYPE_STRUCT) { + *(void**)regp = rvalue; + regp += 4; + } + + for(i = 0, n = cif->nargs; i < n; i++) { + ffi_type* ty = arg_types[i]; + void* a = avalue[i]; + int is_vfp_type = vfp_type_p(ty); + + /* Allocated in VFP registers. */ + if(vi < cif->vfp_nargs && is_vfp_type) { + char* vfp_slot = vfp_space + cif->vfp_args[vi++] * 4; + ffi_put_arg(ty, a, vfp_slot); + continue; + } + /* Try allocating in core registers. */ + else if(!done_with_regs && !is_vfp_type) { + char* tregp = ffi_align(ty, regp); + size_t size = ty->size; + size = (size < 4) ? 4 : size; // pad + /* Check if there is space left in the aligned register + area to place the argument. */ + if(tregp + size <= eo_regp) { + regp = tregp + ffi_put_arg(ty, a, tregp); + done_with_regs = (regp == argp); + // ensure we did not write into the stack area + FFI_ASSERT(regp <= argp); + continue; + } + /* In case there are no arguments in the stack area yet, + the argument is passed in the remaining core registers + and on the stack. */ + else if(!stack_used) { + stack_used = 1; + done_with_regs = 1; + argp = tregp + ffi_put_arg(ty, a, tregp); + FFI_ASSERT(eo_regp < argp); + continue; + } + } + /* Base case, arguments are passed on the stack */ + stack_used = 1; + argp = ffi_align(ty, argp); + argp += ffi_put_arg(ty, a, argp); + } +} + +/* Perform machine dependent cif processing */ +ffi_status ffi_prep_cif_machdep(ffi_cif* cif) { + int flags = 0, cabi = cif->abi; + size_t bytes = cif->bytes; + + /* Map out the register placements of VFP register args. The VFP + hard-float calling conventions are slightly more sophisticated + than the base calling conventions, so we do it here instead of + in ffi_prep_args(). */ + if(cabi == FFI_VFP) layout_vfp_args(cif); + + /* Set the return type flag */ + switch(cif->rtype->type) { + case FFI_TYPE_VOID: + flags = ARM_TYPE_VOID; + break; + + case FFI_TYPE_INT: + case FFI_TYPE_UINT8: + case FFI_TYPE_SINT8: + case FFI_TYPE_UINT16: + case FFI_TYPE_SINT16: + case FFI_TYPE_UINT32: + case FFI_TYPE_SINT32: + case FFI_TYPE_POINTER: + flags = ARM_TYPE_INT; + break; + + case FFI_TYPE_SINT64: + case FFI_TYPE_UINT64: + flags = ARM_TYPE_INT64; + break; + + case FFI_TYPE_FLOAT: + flags = (cabi == FFI_VFP ? ARM_TYPE_VFP_S : ARM_TYPE_INT); + break; + case FFI_TYPE_DOUBLE: + flags = (cabi == FFI_VFP ? ARM_TYPE_VFP_D : ARM_TYPE_INT64); + break; + + case FFI_TYPE_STRUCT: + case FFI_TYPE_COMPLEX: + if(cabi == FFI_VFP) { + int h = vfp_type_p(cif->rtype); + + flags = ARM_TYPE_VFP_N; + if(h == 0x100 + FFI_TYPE_FLOAT) flags = ARM_TYPE_VFP_S; + if(h == 0x100 + FFI_TYPE_DOUBLE) flags = ARM_TYPE_VFP_D; + if(h != 0) break; + } + + /* A Composite Type not larger than 4 bytes is returned in r0. + A Composite Type larger than 4 bytes, or whose size cannot + be determined statically ... is stored in memory at an + address passed [in r0]. */ + if(cif->rtype->size <= 4) + flags = ARM_TYPE_INT; + else { + flags = ARM_TYPE_STRUCT; + bytes += 4; + } + break; + + default: + abort(); + } + + /* Round the stack up to a multiple of 8 bytes. This isn't needed + everywhere, but it is on some platforms, and it doesn't harm anything + when it isn't needed. */ + bytes = ALIGN(bytes, 8); + + /* Minimum stack space is the 4 register arguments that we pop. */ + if(bytes < 4 * 4) bytes = 4 * 4; + + cif->bytes = bytes; + cif->flags = flags; + + return FFI_OK; +} + +/* Perform machine dependent cif processing for variadic calls */ +ffi_status + ffi_prep_cif_machdep_var(ffi_cif* cif, unsigned int nfixedargs, unsigned int ntotalargs) { + UNUSED(nfixedargs); + UNUSED(ntotalargs); + /* VFP variadic calls actually use the SYSV ABI */ + if(cif->abi == FFI_VFP) cif->abi = FFI_SYSV; + + return ffi_prep_cif_machdep(cif); +} + +/* Prototypes for assembly functions, in sysv.S. */ + +struct call_frame { + void* fp; + void* lr; + void* rvalue; + int flags; + void* closure; +}; + +extern void ffi_call_SYSV(void* stack, struct call_frame*, void (*fn)(void)) FFI_HIDDEN; +extern void ffi_call_VFP(void* vfp_space, struct call_frame*, void (*fn)(void), unsigned vfp_used) + FFI_HIDDEN; + +static void + ffi_call_int(ffi_cif* cif, void (*fn)(void), void* rvalue, void** avalue, void* closure) { + int flags = cif->flags; + ffi_type* rtype = cif->rtype; + size_t bytes, rsize, vfp_size; + char *stack, *vfp_space, *new_rvalue; + struct call_frame* frame; + + rsize = 0; + if(rvalue == NULL) { + /* If the return value is a struct and we don't have a return + value address then we need to make one. Otherwise the return + value is in registers and we can ignore them. */ + if(flags == ARM_TYPE_STRUCT) + rsize = rtype->size; + else + flags = ARM_TYPE_VOID; + } else if(flags == ARM_TYPE_VFP_N) { + /* Largest case is double x 4. */ + rsize = 32; + } else if(flags == ARM_TYPE_INT && rtype->type == FFI_TYPE_STRUCT) + rsize = 4; + + /* Largest case. */ + vfp_size = (cif->abi == FFI_VFP && cif->vfp_used ? 8 * 8 : 0); + + bytes = cif->bytes; + stack = alloca(vfp_size + bytes + sizeof(struct call_frame) + rsize); + + vfp_space = NULL; + if(vfp_size) { + vfp_space = stack; + stack += vfp_size; + } + + frame = (struct call_frame*)(stack + bytes); + + new_rvalue = rvalue; + if(rsize) new_rvalue = (void*)(frame + 1); + + frame->rvalue = new_rvalue; + frame->flags = flags; + frame->closure = closure; + + if(vfp_space) { + ffi_prep_args_VFP(cif, flags, new_rvalue, avalue, stack, vfp_space); + ffi_call_VFP(vfp_space, frame, fn, cif->vfp_used); + } else { + ffi_prep_args_SYSV(cif, flags, new_rvalue, avalue, stack); + ffi_call_SYSV(stack, frame, fn); + } + + if(rvalue && rvalue != new_rvalue) memcpy(rvalue, new_rvalue, rtype->size); +} + +void ffi_call(ffi_cif* cif, void (*fn)(void), void* rvalue, void** avalue) { + ffi_call_int(cif, fn, rvalue, avalue, NULL); +} + +void ffi_call_go(ffi_cif* cif, void (*fn)(void), void* rvalue, void** avalue, void* closure) { + ffi_call_int(cif, fn, rvalue, avalue, closure); +} + +static void* ffi_prep_incoming_args_SYSV(ffi_cif* cif, void* rvalue, char* argp, void** avalue) { + ffi_type** arg_types = cif->arg_types; + int i, n; + + if(cif->flags == ARM_TYPE_STRUCT) { + rvalue = *(void**)argp; + argp += 4; + } + + for(i = 0, n = cif->nargs; i < n; i++) { + ffi_type* ty = arg_types[i]; + size_t z = ty->size; + + argp = ffi_align(ty, argp); + avalue[i] = (void*)argp; + argp += z; + } + + return rvalue; +} + +static void* ffi_prep_incoming_args_VFP( + ffi_cif* cif, + void* rvalue, + char* stack, + char* vfp_space, + void** avalue) { + ffi_type** arg_types = cif->arg_types; + int i, n, vi = 0; + char *argp, *regp, *eo_regp; + char done_with_regs = 0; + char stack_used = 0; + + regp = stack; + eo_regp = argp = regp + 16; + + if(cif->flags == ARM_TYPE_STRUCT) { + rvalue = *(void**)regp; + regp += 4; + } + + for(i = 0, n = cif->nargs; i < n; i++) { + ffi_type* ty = arg_types[i]; + int is_vfp_type = vfp_type_p(ty); + size_t z = ty->size; + + if(vi < cif->vfp_nargs && is_vfp_type) { + avalue[i] = vfp_space + cif->vfp_args[vi++] * 4; + continue; + } else if(!done_with_regs && !is_vfp_type) { + char* tregp = ffi_align(ty, regp); + + z = (z < 4) ? 4 : z; // pad + + /* If the arguments either fits into the registers or uses registers + and stack, while we haven't read other things from the stack */ + if(tregp + z <= eo_regp || !stack_used) { + /* Because we're little endian, this is what it turns into. */ + avalue[i] = (void*)tregp; + regp = tregp + z; + + /* If we read past the last core register, make sure we + have not read from the stack before and continue + reading after regp. */ + if(regp > eo_regp) { + FFI_ASSERT(!stack_used); + argp = regp; + } + if(regp >= eo_regp) { + done_with_regs = 1; + stack_used = 1; + } + continue; + } + } + + stack_used = 1; + argp = ffi_align(ty, argp); + avalue[i] = (void*)argp; + argp += z; + } + + return rvalue; +} + +struct closure_frame { + char vfp_space[8 * 8] __attribute__((aligned(8))); + char result[8 * 4]; + char argp[]; +}; + +int FFI_HIDDEN ffi_closure_inner_SYSV( + ffi_cif* cif, + void (*fun)(ffi_cif*, void*, void**, void*), + void* user_data, + struct closure_frame* frame) { + void** avalue = (void**)alloca(cif->nargs * sizeof(void*)); + void* rvalue = ffi_prep_incoming_args_SYSV(cif, frame->result, frame->argp, avalue); + fun(cif, rvalue, avalue, user_data); + return cif->flags; +} + +int FFI_HIDDEN ffi_closure_inner_VFP( + ffi_cif* cif, + void (*fun)(ffi_cif*, void*, void**, void*), + void* user_data, + struct closure_frame* frame) { + void** avalue = (void**)alloca(cif->nargs * sizeof(void*)); + void* rvalue = + ffi_prep_incoming_args_VFP(cif, frame->result, frame->argp, frame->vfp_space, avalue); + fun(cif, rvalue, avalue, user_data); + return cif->flags; +} + +void ffi_closure_SYSV(void) FFI_HIDDEN; +void ffi_closure_VFP(void) FFI_HIDDEN; +void ffi_go_closure_SYSV(void) FFI_HIDDEN; +void ffi_go_closure_VFP(void) FFI_HIDDEN; + +/* the cif must already be prep'ed */ +#if 0 +ffi_status +ffi_prep_closure_loc (ffi_closure * closure, + ffi_cif * cif, + void (*fun) (ffi_cif *, void *, void **, void *), + void *user_data, void *codeloc) +{ + void (*closure_func) (void) = ffi_closure_SYSV; + + if (cif->abi == FFI_VFP) + { + /* We only need take the vfp path if there are vfp arguments. */ + if (cif->vfp_used) + closure_func = ffi_closure_VFP; + } + else if (cif->abi != FFI_SYSV) + return FFI_BAD_ABI; + +#if FFI_EXEC_TRAMPOLINE_TABLE + void **config = (void **)((uint8_t *)codeloc - PAGE_MAX_SIZE); + config[0] = closure; + config[1] = closure_func; +#else + memcpy (closure->tramp, ffi_arm_trampoline, 8); + __clear_cache(closure->tramp, closure->tramp + 8); /* clear data map */ + __clear_cache(codeloc, codeloc + 8); /* clear insn map */ + *(void (**)(void))(closure->tramp + 8) = closure_func; +#endif + + closure->cif = cif; + closure->fun = fun; + closure->user_data = user_data; + + return FFI_OK; +} + +ffi_status +ffi_prep_go_closure (ffi_go_closure *closure, ffi_cif *cif, + void (*fun) (ffi_cif *, void *, void **, void *)) +{ + void (*closure_func) (void) = ffi_go_closure_SYSV; + + if (cif->abi == FFI_VFP) + { + /* We only need take the vfp path if there are vfp arguments. */ + if (cif->vfp_used) + closure_func = ffi_go_closure_VFP; + } + else if (cif->abi != FFI_SYSV) + return FFI_BAD_ABI; + + closure->tramp = closure_func; + closure->cif = cif; + closure->fun = fun; + + return FFI_OK; +} +#endif + +/* Below are routines for VFP hard-float support. */ + +/* A subroutine of vfp_type_p. Given a structure type, return the type code + of the first non-structure element. Recurse for structure elements. + Return -1 if the structure is in fact empty, i.e. no nested elements. */ + +static int is_hfa0(const ffi_type* ty) { + ffi_type** elements = ty->elements; + int i, ret = -1; + + if(elements != NULL) + for(i = 0; elements[i]; ++i) { + ret = elements[i]->type; + if(ret == FFI_TYPE_STRUCT || ret == FFI_TYPE_COMPLEX) { + ret = is_hfa0(elements[i]); + if(ret < 0) continue; + } + break; + } + + return ret; +} + +/* A subroutine of vfp_type_p. Given a structure type, return true if all + of the non-structure elements are the same as CANDIDATE. */ + +static int is_hfa1(const ffi_type* ty, int candidate) { + ffi_type** elements = ty->elements; + int i; + + if(elements != NULL) + for(i = 0; elements[i]; ++i) { + int t = elements[i]->type; + if(t == FFI_TYPE_STRUCT || t == FFI_TYPE_COMPLEX) { + if(!is_hfa1(elements[i], candidate)) return 0; + } else if(t != candidate) + return 0; + } + + return 1; +} + +/* Determine if TY is an homogenous floating point aggregate (HFA). + That is, a structure consisting of 1 to 4 members of all the same type, + where that type is a floating point scalar. + Returns non-zero iff TY is an HFA. The result is an encoded value where + bits 0-7 contain the type code, and bits 8-10 contain the element count. */ + +static int vfp_type_p(const ffi_type* ty) { + ffi_type** elements; + int candidate, i; + size_t size, ele_count; + + /* Quickest tests first. */ + candidate = ty->type; + switch(ty->type) { + default: + return 0; + case FFI_TYPE_FLOAT: + case FFI_TYPE_DOUBLE: + ele_count = 1; + goto done; + case FFI_TYPE_COMPLEX: + candidate = ty->elements[0]->type; + if(candidate != FFI_TYPE_FLOAT && candidate != FFI_TYPE_DOUBLE) return 0; + ele_count = 2; + goto done; + case FFI_TYPE_STRUCT: + break; + } + + /* No HFA types are smaller than 4 bytes, or larger than 32 bytes. */ + size = ty->size; + if(size < 4 || size > 32) return 0; + + /* Find the type of the first non-structure member. */ + elements = ty->elements; + candidate = elements[0]->type; + if(candidate == FFI_TYPE_STRUCT || candidate == FFI_TYPE_COMPLEX) { + for(i = 0;; ++i) { + candidate = is_hfa0(elements[i]); + if(candidate >= 0) break; + } + } + + /* If the first member is not a floating point type, it's not an HFA. + Also quickly re-check the size of the structure. */ + switch(candidate) { + case FFI_TYPE_FLOAT: + ele_count = size / sizeof(float); + if(size != ele_count * sizeof(float)) return 0; + break; + case FFI_TYPE_DOUBLE: + ele_count = size / sizeof(double); + if(size != ele_count * sizeof(double)) return 0; + break; + default: + return 0; + } + if(ele_count > 4) return 0; + + /* Finally, make sure that all scalar elements are the same type. */ + for(i = 0; elements[i]; ++i) { + int t = elements[i]->type; + if(t == FFI_TYPE_STRUCT || t == FFI_TYPE_COMPLEX) { + if(!is_hfa1(elements[i], candidate)) return 0; + } else if(t != candidate) + return 0; + } + + /* All tests succeeded. Encode the result. */ +done: + return (ele_count << 8) | candidate; +} + +static int place_vfp_arg(ffi_cif* cif, int h) { + unsigned short reg = cif->vfp_reg_free; + int align = 1, nregs = h >> 8; + + if((h & 0xff) == FFI_TYPE_DOUBLE) align = 2, nregs *= 2; + + /* Align register number. */ + if((reg & 1) && align == 2) reg++; + + while(reg + nregs <= 16) { + int s, new_used = 0; + for(s = reg; s < reg + nregs; s++) { + new_used |= (1 << s); + if(cif->vfp_used & (1 << s)) { + reg += align; + goto next_reg; + } + } + /* Found regs to allocate. */ + cif->vfp_used |= new_used; + cif->vfp_args[cif->vfp_nargs++] = reg; + + /* Update vfp_reg_free. */ + if(cif->vfp_used & (1 << cif->vfp_reg_free)) { + reg += nregs; + while(cif->vfp_used & (1 << reg)) reg += 1; + cif->vfp_reg_free = reg; + } + return 0; + next_reg:; + } + // done, mark all regs as used + cif->vfp_reg_free = 16; + cif->vfp_used = 0xFFFF; + return 1; +} + +static void layout_vfp_args(ffi_cif* cif) { + unsigned int i; + /* Init VFP fields */ + cif->vfp_used = 0; + cif->vfp_nargs = 0; + cif->vfp_reg_free = 0; + memset(cif->vfp_args, -1, 16); /* Init to -1. */ + + for(i = 0; i < cif->nargs; i++) { + int h = vfp_type_p(cif->arg_types[i]); + if(h && place_vfp_arg(cif, h) == 1) break; + } +} \ No newline at end of file diff --git a/applications/system/elk_js/ffi/ffi.h b/applications/system/elk_js/ffi/ffi.h new file mode 100644 index 00000000000..0d299bbc16c --- /dev/null +++ b/applications/system/elk_js/ffi/ffi.h @@ -0,0 +1,452 @@ +/* -----------------------------------------------------------------*-C-*- + libffi 3.2.1 - Copyright (c) 2011, 2014 Anthony Green + - Copyright (c) 1996-2003, 2007, 2008 Red Hat, Inc. + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the ``Software''), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + ----------------------------------------------------------------------- */ + +/* ------------------------------------------------------------------- + The basic API is described in the README file. + The raw API is designed to bypass some of the argument packing + and unpacking on architectures for which it can be avoided. + The closure API allows interpreted functions to be packaged up + inside a C function pointer, so that they can be called as C functions, + with no understanding on the client side that they are interpreted. + It can also be used in other cases in which it is necessary to package + up a user specified parameter and a function pointer as a single + function pointer. + The closure API must be implemented in order to get its functionality, + e.g. for use by gij. Routines are provided to emulate the raw API + if the underlying platform doesn't allow faster implementation. + More details on the raw and cloure API can be found in: + http://gcc.gnu.org/ml/java/1999-q3/msg00138.html + and + http://gcc.gnu.org/ml/java/1999-q3/msg00174.html + -------------------------------------------------------------------- */ + +#ifndef LIBFFI_H +#define LIBFFI_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* Specify which architecture libffi is configured for. */ +#ifndef ARM +#define ARM +#endif + +/* ---- System configuration information --------------------------------- */ + +#include "ffitarget.h" + +#ifndef LIBFFI_ASM + +#if defined(_MSC_VER) && !defined(__clang__) +#define __attribute__(X) +#endif + +#include +#include + +/* LONG_LONG_MAX is not always defined (not if STRICT_ANSI, for example). + But we can find it either under the correct ANSI name, or under GNU + C's internal name. */ + +#define FFI_64_BIT_MAX 9223372036854775807 + +#ifdef LONG_LONG_MAX +#define FFI_LONG_LONG_MAX LONG_LONG_MAX +#else +#ifdef LLONG_MAX +#define FFI_LONG_LONG_MAX LLONG_MAX +#ifdef _AIX52 /* or newer has C99 LLONG_MAX */ +#undef FFI_64_BIT_MAX +#define FFI_64_BIT_MAX 9223372036854775807LL +#endif /* _AIX52 or newer */ +#else +#ifdef __GNUC__ +#define FFI_LONG_LONG_MAX __LONG_LONG_MAX__ +#endif +#ifdef _AIX /* AIX 5.1 and earlier have LONGLONG_MAX */ +#ifndef __PPC64__ +#if defined(__IBMC__) || defined(__IBMCPP__) +#define FFI_LONG_LONG_MAX LONGLONG_MAX +#endif +#endif /* __PPC64__ */ +#undef FFI_64_BIT_MAX +#define FFI_64_BIT_MAX 9223372036854775807LL +#endif +#endif +#endif + +/* The closure code assumes that this works on pointers, i.e. a size_t */ +/* can hold a pointer. */ + +typedef struct _ffi_type { + size_t size; + unsigned short alignment; + unsigned short type; + struct _ffi_type** elements; +} ffi_type; + +#ifndef LIBFFI_HIDE_BASIC_TYPES +#if SCHAR_MAX == 127 +#define ffi_type_uchar ffi_type_uint8 +#define ffi_type_schar ffi_type_sint8 +#else +#error "char size not supported" +#endif + +#if SHRT_MAX == 32767 +#define ffi_type_ushort ffi_type_uint16 +#define ffi_type_sshort ffi_type_sint16 +#elif SHRT_MAX == 2147483647 +#define ffi_type_ushort ffi_type_uint32 +#define ffi_type_sshort ffi_type_sint32 +#else +#error "short size not supported" +#endif + +#if INT_MAX == 32767 +#define ffi_type_uint ffi_type_uint16 +#define ffi_type_sint ffi_type_sint16 +#elif INT_MAX == 2147483647 +#define ffi_type_uint ffi_type_uint32 +#define ffi_type_sint ffi_type_sint32 +#elif INT_MAX == 9223372036854775807 +#define ffi_type_uint ffi_type_uint64 +#define ffi_type_sint ffi_type_sint64 +#else +#error "int size not supported" +#endif + +#if LONG_MAX == 2147483647 +#if FFI_LONG_LONG_MAX != FFI_64_BIT_MAX +#error "no 64-bit data type supported" +#endif +#elif LONG_MAX != FFI_64_BIT_MAX +#error "long size not supported" +#endif + +#if LONG_MAX == 2147483647 +#define ffi_type_ulong ffi_type_uint32 +#define ffi_type_slong ffi_type_sint32 +#elif LONG_MAX == FFI_64_BIT_MAX +#define ffi_type_ulong ffi_type_uint64 +#define ffi_type_slong ffi_type_sint64 +#else +#error "long size not supported" +#endif + +/* Need minimal decorations for DLLs to works on Windows. */ +/* GCC has autoimport and autoexport. Rely on Libtool to */ +/* help MSVC export from a DLL, but always declare data */ +/* to be imported for MSVC clients. This costs an extra */ +/* indirection for MSVC clients using the static version */ +/* of the library, but don't worry about that. Besides, */ +/* as a workaround, they can define FFI_BUILDING if they */ +/* *know* they are going to link with the static library. */ +#if defined _MSC_VER && !defined FFI_BUILDING +#define FFI_EXTERN extern __declspec(dllimport) +#else +#define FFI_EXTERN extern +#endif + +/* These are defined in types.c */ +FFI_EXTERN ffi_type ffi_type_void; +FFI_EXTERN ffi_type ffi_type_uint8; +FFI_EXTERN ffi_type ffi_type_sint8; +FFI_EXTERN ffi_type ffi_type_uint16; +FFI_EXTERN ffi_type ffi_type_sint16; +FFI_EXTERN ffi_type ffi_type_uint32; +FFI_EXTERN ffi_type ffi_type_sint32; +FFI_EXTERN ffi_type ffi_type_uint64; +FFI_EXTERN ffi_type ffi_type_sint64; +FFI_EXTERN ffi_type ffi_type_float; +FFI_EXTERN ffi_type ffi_type_double; +FFI_EXTERN ffi_type ffi_type_pointer; + +#if 0 +FFI_EXTERN ffi_type ffi_type_longdouble; +#else +#define ffi_type_longdouble ffi_type_double +#endif + +#ifdef FFI_TARGET_HAS_COMPLEX_TYPE +FFI_EXTERN ffi_type ffi_type_complex_float; +FFI_EXTERN ffi_type ffi_type_complex_double; +#if 0 +FFI_EXTERN ffi_type ffi_type_complex_longdouble; +#else +#define ffi_type_complex_longdouble ffi_type_complex_double +#endif +#endif +#endif /* LIBFFI_HIDE_BASIC_TYPES */ + +typedef enum { FFI_OK = 0, FFI_BAD_TYPEDEF, FFI_BAD_ABI } ffi_status; + +typedef unsigned FFI_TYPE; + +typedef struct { + ffi_abi abi; + unsigned nargs; + ffi_type** arg_types; + ffi_type* rtype; + unsigned bytes; + unsigned flags; +#ifdef FFI_EXTRA_CIF_FIELDS + FFI_EXTRA_CIF_FIELDS; +#endif +} ffi_cif; + +#if 0 +/* Used to adjust size/alignment of ffi types. */ +void ffi_prep_types (ffi_abi abi); +#endif + +/* ---- Definitions for the raw API -------------------------------------- */ + +#ifndef FFI_SIZEOF_ARG +#if LONG_MAX == 2147483647 +#define FFI_SIZEOF_ARG 4 +#elif LONG_MAX == FFI_64_BIT_MAX +#define FFI_SIZEOF_ARG 8 +#endif +#endif + +#ifndef FFI_SIZEOF_JAVA_RAW +#define FFI_SIZEOF_JAVA_RAW FFI_SIZEOF_ARG +#endif + +typedef union { + ffi_sarg sint; + ffi_arg uint; + float flt; + char data[FFI_SIZEOF_ARG]; + void* ptr; +} ffi_raw; + +#if FFI_SIZEOF_JAVA_RAW == 4 && FFI_SIZEOF_ARG == 8 +/* This is a special case for mips64/n32 ABI (and perhaps others) where + sizeof(void *) is 4 and FFI_SIZEOF_ARG is 8. */ +typedef union { + signed int sint; + unsigned int uint; + float flt; + char data[FFI_SIZEOF_JAVA_RAW]; + void* ptr; +} ffi_java_raw; +#else +typedef ffi_raw ffi_java_raw; +#endif + +void ffi_raw_call(ffi_cif* cif, void (*fn)(void), void* rvalue, ffi_raw* avalue); + +void ffi_ptrarray_to_raw(ffi_cif* cif, void** args, ffi_raw* raw); +void ffi_raw_to_ptrarray(ffi_cif* cif, ffi_raw* raw, void** args); +size_t ffi_raw_size(ffi_cif* cif); + +/* This is analogous to the raw API, except it uses Java parameter */ +/* packing, even on 64-bit machines. I.e. on 64-bit machines */ +/* longs and doubles are followed by an empty 64-bit word. */ + +void ffi_java_raw_call(ffi_cif* cif, void (*fn)(void), void* rvalue, ffi_java_raw* avalue); + +void ffi_java_ptrarray_to_raw(ffi_cif* cif, void** args, ffi_java_raw* raw); +void ffi_java_raw_to_ptrarray(ffi_cif* cif, ffi_java_raw* raw, void** args); +size_t ffi_java_raw_size(ffi_cif* cif); + +/* ---- Definitions for closures ----------------------------------------- */ + +#undef FFI_CLOSURES + +#if FFI_CLOSURES + +#ifdef _MSC_VER +__declspec(align(8)) +#endif + typedef struct { +#if 0 + void *trampoline_table; + void *trampoline_table_entry; +#else + char tramp[FFI_TRAMPOLINE_SIZE]; +#endif + ffi_cif* cif; + void (*fun)(ffi_cif*, void*, void**, void*); + void* user_data; +#ifdef __GNUC__ +} ffi_closure __attribute__((aligned(8))); +#else +} ffi_closure; +#ifdef __sgi +#pragma pack 0 +#endif +#endif + +void* ffi_closure_alloc(size_t size, void** code); +void ffi_closure_free(void*); + +ffi_status ffi_prep_closure( + ffi_closure*, + ffi_cif*, + void (*fun)(ffi_cif*, void*, void**, void*), + void* user_data); + +ffi_status ffi_prep_closure_loc( + ffi_closure*, + ffi_cif*, + void (*fun)(ffi_cif*, void*, void**, void*), + void* user_data, + void* codeloc); + +#ifdef __sgi +#pragma pack 8 +#endif +typedef struct { +#if 0 + void *trampoline_table; + void *trampoline_table_entry; +#else + char tramp[FFI_TRAMPOLINE_SIZE]; +#endif + ffi_cif* cif; + +#if !FFI_NATIVE_RAW_API + + /* if this is enabled, then a raw closure has the same layout + as a regular closure. We use this to install an intermediate + handler to do the transaltion, void** -> ffi_raw*. */ + + void (*translate_args)(ffi_cif*, void*, void**, void*); + void* this_closure; + +#endif + + void (*fun)(ffi_cif*, void*, ffi_raw*, void*); + void* user_data; + +} ffi_raw_closure; + +typedef struct { +#if 0 + void *trampoline_table; + void *trampoline_table_entry; +#else + char tramp[FFI_TRAMPOLINE_SIZE]; +#endif + + ffi_cif* cif; + +#if !FFI_NATIVE_RAW_API + + /* if this is enabled, then a raw closure has the same layout + as a regular closure. We use this to install an intermediate + handler to do the transaltion, void** -> ffi_raw*. */ + + void (*translate_args)(ffi_cif*, void*, void**, void*); + void* this_closure; + +#endif + + void (*fun)(ffi_cif*, void*, ffi_java_raw*, void*); + void* user_data; + +} ffi_java_raw_closure; + +ffi_status ffi_prep_raw_closure( + ffi_raw_closure*, + ffi_cif* cif, + void (*fun)(ffi_cif*, void*, ffi_raw*, void*), + void* user_data); + +ffi_status ffi_prep_raw_closure_loc( + ffi_raw_closure*, + ffi_cif* cif, + void (*fun)(ffi_cif*, void*, ffi_raw*, void*), + void* user_data, + void* codeloc); + +ffi_status ffi_prep_java_raw_closure( + ffi_java_raw_closure*, + ffi_cif* cif, + void (*fun)(ffi_cif*, void*, ffi_java_raw*, void*), + void* user_data); + +ffi_status ffi_prep_java_raw_closure_loc( + ffi_java_raw_closure*, + ffi_cif* cif, + void (*fun)(ffi_cif*, void*, ffi_java_raw*, void*), + void* user_data, + void* codeloc); + +#endif /* FFI_CLOSURES */ + +/* ---- Public interface definition -------------------------------------- */ + +ffi_status + ffi_prep_cif(ffi_cif* cif, ffi_abi abi, unsigned int nargs, ffi_type* rtype, ffi_type** atypes); + +ffi_status ffi_prep_cif_var( + ffi_cif* cif, + ffi_abi abi, + unsigned int nfixedargs, + unsigned int ntotalargs, + ffi_type* rtype, + ffi_type** atypes); + +void ffi_call(ffi_cif* cif, void (*fn)(void), void* rvalue, void** avalue); + +/* Useful for eliminating compiler warnings */ +#define FFI_FN(f) ((void (*)(void))f) + +/* ---- Definitions shared with assembly code ---------------------------- */ + +#endif + +/* If these change, update src/mips/ffitarget.h. */ +#define FFI_TYPE_VOID 0 +#define FFI_TYPE_INT 1 +#define FFI_TYPE_FLOAT 2 +#define FFI_TYPE_DOUBLE 3 +#if 0 +#define FFI_TYPE_LONGDOUBLE 4 +#else +#define FFI_TYPE_LONGDOUBLE FFI_TYPE_DOUBLE +#endif +#define FFI_TYPE_UINT8 5 +#define FFI_TYPE_SINT8 6 +#define FFI_TYPE_UINT16 7 +#define FFI_TYPE_SINT16 8 +#define FFI_TYPE_UINT32 9 +#define FFI_TYPE_SINT32 10 +#define FFI_TYPE_UINT64 11 +#define FFI_TYPE_SINT64 12 +#define FFI_TYPE_STRUCT 13 +#define FFI_TYPE_POINTER 14 +#define FFI_TYPE_COMPLEX 15 + +/* This should always refer to the last type code (for sanity checks) */ +#define FFI_TYPE_LAST FFI_TYPE_COMPLEX + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/applications/system/elk_js/ffi/ffi_cfi.h b/applications/system/elk_js/ffi/ffi_cfi.h new file mode 100644 index 00000000000..d734ed928f4 --- /dev/null +++ b/applications/system/elk_js/ffi/ffi_cfi.h @@ -0,0 +1,54 @@ +/* ----------------------------------------------------------------------- + ffi_cfi.h - Copyright (c) 2014 Red Hat, Inc. + Conditionally assemble cfi directives. Only necessary for building libffi. + ----------------------------------------------------------------------- */ + +#ifndef FFI_CFI_H +#define FFI_CFI_H + +#ifdef HAVE_AS_CFI_PSEUDO_OP + +#define cfi_startproc .cfi_startproc +#define cfi_endproc .cfi_endproc +#define cfi_def_cfa(reg, off) .cfi_def_cfa reg, off +#define cfi_def_cfa_register(reg) .cfi_def_cfa_register reg +#define cfi_def_cfa_offset(off) .cfi_def_cfa_offset off +#define cfi_adjust_cfa_offset(off) .cfi_adjust_cfa_offset off +#define cfi_offset(reg, off) .cfi_offset reg, off +#define cfi_rel_offset(reg, off) .cfi_rel_offset reg, off +#define cfi_register(r1, r2) .cfi_register r1, r2 +#define cfi_return_column(reg) .cfi_return_column reg +#define cfi_restore(reg) .cfi_restore reg +#define cfi_same_value(reg) .cfi_same_value reg +#define cfi_undefined(reg) .cfi_undefined reg +#define cfi_remember_state .cfi_remember_state +#define cfi_restore_state .cfi_restore_state +#define cfi_window_save .cfi_window_save +#define cfi_personality(enc, exp) .cfi_personality enc, exp +#define cfi_lsda(enc, exp) .cfi_lsda enc, exp +#define cfi_escape(...) .cfi_escape __VA_ARGS__ + +#else + +#define cfi_startproc +#define cfi_endproc +#define cfi_def_cfa(reg, off) +#define cfi_def_cfa_register(reg) +#define cfi_def_cfa_offset(off) +#define cfi_adjust_cfa_offset(off) +#define cfi_offset(reg, off) +#define cfi_rel_offset(reg, off) +#define cfi_register(r1, r2) +#define cfi_return_column(reg) +#define cfi_restore(reg) +#define cfi_same_value(reg) +#define cfi_undefined(reg) +#define cfi_remember_state +#define cfi_restore_state +#define cfi_window_save +#define cfi_personality(enc, exp) +#define cfi_lsda(enc, exp) +#define cfi_escape(...) + +#endif /* HAVE_AS_CFI_PSEUDO_OP */ +#endif /* FFI_CFI_H */ \ No newline at end of file diff --git a/applications/system/elk_js/ffi/ffi_common.h b/applications/system/elk_js/ffi/ffi_common.h new file mode 100644 index 00000000000..995b9e1e366 --- /dev/null +++ b/applications/system/elk_js/ffi/ffi_common.h @@ -0,0 +1,146 @@ +/* ----------------------------------------------------------------------- + ffi_common.h - Copyright (C) 2011, 2012, 2013 Anthony Green + Copyright (C) 2007 Free Software Foundation, Inc + Copyright (c) 1996 Red Hat, Inc. + + Common internal definitions and macros. Only necessary for building + libffi. + ----------------------------------------------------------------------- */ + +#ifndef FFI_COMMON_H +#define FFI_COMMON_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "fficonfig.h" + +/* Do not move this. Some versions of AIX are very picky about where + this is positioned. */ +#ifdef __GNUC__ +#if HAVE_ALLOCA_H +#include +#else +/* mingw64 defines this already in malloc.h. */ +#ifndef alloca +#define alloca __builtin_alloca +#endif +#endif +#define MAYBE_UNUSED __attribute__((__unused__)) +#else +#define MAYBE_UNUSED +#if HAVE_ALLOCA_H +#include +#else +#ifdef _AIX +#pragma alloca +#else +#ifndef alloca /* predefined by HP cc +Olibcalls */ +#ifdef _MSC_VER +#define alloca _alloca +#else +char* alloca(); +#endif +#endif +#endif +#endif +#endif + +/* Check for the existence of memcpy. */ +#if STDC_HEADERS +#include +#else +#ifndef HAVE_MEMCPY +#define memcpy(d, s, n) bcopy((s), (d), (n)) +#endif +#endif + +#if defined(FFI_DEBUG) +#include +#endif + +#ifdef FFI_DEBUG +void ffi_assert(char* expr, char* file, int line); +void ffi_stop_here(void); +void ffi_type_test(ffi_type* a, char* file, int line); + +#define FFI_ASSERT(x) ((x) ? (void)0 : ffi_assert(#x, __FILE__, __LINE__)) +#define FFI_ASSERT_AT(x, f, l) ((x) ? 0 : ffi_assert(#x, (f), (l))) +#define FFI_ASSERT_VALID_TYPE(x) ffi_type_test(x, __FILE__, __LINE__) +#else +#define FFI_ASSERT(x) +#define FFI_ASSERT_AT(x, f, l) +#define FFI_ASSERT_VALID_TYPE(x) +#endif + +#define ALIGN(v, a) (((((size_t)(v)) - 1) | ((a)-1)) + 1) +#define ALIGN_DOWN(v, a) (((size_t)(v)) & -a) + +/* Perform machine dependent cif processing */ +ffi_status ffi_prep_cif_machdep(ffi_cif* cif); +ffi_status + ffi_prep_cif_machdep_var(ffi_cif* cif, unsigned int nfixedargs, unsigned int ntotalargs); + +#if HAVE_LONG_DOUBLE_VARIANT +/* Used to adjust size/alignment of ffi types. */ +void ffi_prep_types(ffi_abi abi); +#endif + +/* Used internally, but overridden by some architectures */ +ffi_status ffi_prep_cif_core( + ffi_cif* cif, + ffi_abi abi, + unsigned int isvariadic, + unsigned int nfixedargs, + unsigned int ntotalargs, + ffi_type* rtype, + ffi_type** atypes); + +/* Extended cif, used in callback from assembly routine */ +typedef struct { + ffi_cif* cif; + void* rvalue; + void** avalue; +} extended_cif; + +/* Terse sized type definitions. */ +#if defined(_MSC_VER) || defined(__sgi) || defined(__SUNPRO_C) +typedef unsigned char UINT8; +typedef signed char SINT8; +typedef unsigned short UINT16; +typedef signed short SINT16; +typedef unsigned int UINT32; +typedef signed int SINT32; +#ifdef _MSC_VER +typedef unsigned __int64 UINT64; +typedef signed __int64 SINT64; +#else +#include +typedef uint64_t UINT64; +typedef int64_t SINT64; +#endif +#else +typedef unsigned int UINT8 __attribute__((__mode__(__QI__))); +typedef signed int SINT8 __attribute__((__mode__(__QI__))); +typedef unsigned int UINT16 __attribute__((__mode__(__HI__))); +typedef signed int SINT16 __attribute__((__mode__(__HI__))); +typedef unsigned int UINT32 __attribute__((__mode__(__SI__))); +typedef signed int SINT32 __attribute__((__mode__(__SI__))); +typedef unsigned int UINT64 __attribute__((__mode__(__DI__))); +typedef signed int SINT64 __attribute__((__mode__(__DI__))); +#endif + +typedef float FLOAT32; + +#ifndef __GNUC__ +#define __builtin_expect(x, expected_value) (x) +#endif +#define LIKELY(x) __builtin_expect(!!(x), 1) +#define UNLIKELY(x) __builtin_expect((x) != 0, 0) + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/applications/system/elk_js/ffi/fficonfig.h b/applications/system/elk_js/ffi/fficonfig.h new file mode 100644 index 00000000000..6b58c3db425 --- /dev/null +++ b/applications/system/elk_js/ffi/fficonfig.h @@ -0,0 +1,213 @@ +/* fficonfig.h. Generated from fficonfig.h.in by configure. */ +/* fficonfig.h.in. Generated from configure.ac by autoheader. */ + +/* Define if building universal (internal helper macro) */ +/* #undef AC_APPLE_UNIVERSAL_BUILD */ + +/* Define to one of `_getb67', `GETB67', `getb67' for Cray-2 and Cray-YMP + systems. This function is required for `alloca.c' support on those systems. + */ +/* #undef CRAY_STACKSEG_END */ + +/* Define to 1 if using `alloca.c'. */ +/* #undef C_ALLOCA */ + +/* Define to the flags needed for the .section .eh_frame directive. */ +#define EH_FRAME_FLAGS "aw" + +/* Define this if you want extra debugging. */ +/* #undef FFI_DEBUG */ + +/* Cannot use PROT_EXEC on this target, so, we revert to alternative means */ +/* #undef FFI_EXEC_TRAMPOLINE_TABLE */ + +/* Define this if you want to enable pax emulated trampolines */ +/* #undef FFI_MMAP_EXEC_EMUTRAMP_PAX */ + +/* Cannot use malloc on this target, so, we revert to alternative means */ +/* #undef FFI_MMAP_EXEC_WRIT */ + +/* Define this if you do not want support for the raw API. */ +/* #undef FFI_NO_RAW_API */ + +/* Define this if you do not want support for aggregate types. */ +/* #undef FFI_NO_STRUCTS */ + +/* Define to 1 if you have `alloca', as a function or macro. */ +#define HAVE_ALLOCA 1 + +/* Define to 1 if you have and it should be used (not on Ultrix). + */ +#define HAVE_ALLOCA_H 1 + +/* Define if your assembler supports .ascii. */ +/* #undef HAVE_AS_ASCII_PSEUDO_OP */ + +/* Define if your assembler supports .cfi_* directives. */ +#define HAVE_AS_CFI_PSEUDO_OP 1 + +/* Define if your assembler supports .register. */ +/* #undef HAVE_AS_REGISTER_PSEUDO_OP */ + +/* Define if your assembler and linker support unaligned PC relative relocs. + */ +/* #undef HAVE_AS_SPARC_UA_PCREL */ + +/* Define if your assembler supports .string. */ +/* #undef HAVE_AS_STRING_PSEUDO_OP */ + +/* Define if your assembler supports unwind section type. */ +/* #undef HAVE_AS_X86_64_UNWIND_SECTION_TYPE */ + +/* Define if your assembler supports PC relative relocs. */ +/* #undef HAVE_AS_X86_PCREL */ + +/* Define to 1 if you have the header file. */ +// #define HAVE_DLFCN_H 1 + +/* Define if __attribute__((visibility("hidden"))) is supported. */ +#define HAVE_HIDDEN_VISIBILITY_ATTRIBUTE 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_INTTYPES_H 1 + +/* Define if you have the long double type and it is bigger than a double */ +/* #undef HAVE_LONG_DOUBLE */ + +/* Define if you support more than one size of the long double type */ +/* #undef HAVE_LONG_DOUBLE_VARIANT */ + +/* Define to 1 if you have the `memcpy' function. */ +#define HAVE_MEMCPY 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_MEMORY_H 1 + +/* Define to 1 if you have the `mkostemp' function. */ +/* #undef HAVE_MKOSTEMP */ + +/* Define to 1 if you have the `mmap' function. */ +#define HAVE_MMAP 1 + +/* Define if mmap with MAP_ANON(YMOUS) works. */ +#define HAVE_MMAP_ANON 1 + +/* Define if mmap of /dev/zero works. */ +#define HAVE_MMAP_DEV_ZERO 1 + +/* Define if read-only mmap of a plain file works. */ +#define HAVE_MMAP_FILE 1 + +/* Define if .eh_frame sections should be read-only. */ +/* #undef HAVE_RO_EH_FRAME */ + +/* Define to 1 if you have the header file. */ +#define HAVE_STDINT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDLIB_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRINGS_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRING_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_MMAN_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TYPES_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_UNISTD_H 1 + +/* Define to the sub-directory in which libtool stores uninstalled libraries. + */ +#define LT_OBJDIR ".libs/" + +/* Define to 1 if your C compiler doesn't accept -c and -o together. */ +/* #undef NO_MINUS_C_MINUS_O */ + +/* Name of package */ +#define PACKAGE "libffi" + +/* Define to the address where bug reports for this package should be sent. */ +#define PACKAGE_BUGREPORT "http://github.com/atgreen/libffi/issues" + +/* Define to the full name of this package. */ +#define PACKAGE_NAME "libffi" + +/* Define to the full name and version of this package. */ +#define PACKAGE_STRING "libffi 3.2.1" + +/* Define to the one symbol short name of this package. */ +#define PACKAGE_TARNAME "libffi" + +/* Define to the home page for this package. */ +#define PACKAGE_URL "" + +/* Define to the version of this package. */ +#define PACKAGE_VERSION "3.2.1" + +/* The size of `double', as computed by sizeof. */ +#define SIZEOF_DOUBLE 8 + +/* The size of `long double', as computed by sizeof. */ +#define SIZEOF_LONG_DOUBLE 8 + +/* The size of `size_t', as computed by sizeof. */ +#define SIZEOF_SIZE_T 4 + +/* If using the C implementation of alloca, define if you know the + direction of stack growth for your system; otherwise it will be + automatically deduced at runtime. + STACK_DIRECTION > 0 => grows toward higher addresses + STACK_DIRECTION < 0 => grows toward lower addresses + STACK_DIRECTION = 0 => direction of growth unknown */ +/* #undef STACK_DIRECTION */ + +/* Define to 1 if you have the ANSI C header files. */ +#define STDC_HEADERS 1 + +/* Define if symbols are underscored. */ +/* #undef SYMBOL_UNDERSCORE */ + +/* Define this if you are using Purify and want to suppress spurious messages. + */ +/* #undef USING_PURIFY */ + +/* Version number of package */ +#define VERSION "3.2.1" + +/* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most + significant byte first (like Motorola and SPARC, unlike Intel). */ +#if defined AC_APPLE_UNIVERSAL_BUILD +#if defined __BIG_ENDIAN__ +#define WORDS_BIGENDIAN 1 +#endif +#else +#ifndef WORDS_BIGENDIAN +/* # undef WORDS_BIGENDIAN */ +#endif +#endif + +/* Define to `unsigned int' if does not define. */ +/* #undef size_t */ + +#ifdef HAVE_HIDDEN_VISIBILITY_ATTRIBUTE +#ifdef LIBFFI_ASM +#define FFI_HIDDEN(name) .hidden name +#else +#define FFI_HIDDEN __attribute__((visibility("hidden"))) +#endif +#else +#ifdef LIBFFI_ASM +#define FFI_HIDDEN(name) +#else +#define FFI_HIDDEN +#endif +#endif diff --git a/applications/system/elk_js/ffi/ffitarget.h b/applications/system/elk_js/ffi/ffitarget.h new file mode 100644 index 00000000000..6ace9d2bf94 --- /dev/null +++ b/applications/system/elk_js/ffi/ffitarget.h @@ -0,0 +1,77 @@ +/* -----------------------------------------------------------------*-C-*- + ffitarget.h - Copyright (c) 2012 Anthony Green + Copyright (c) 2010 CodeSourcery + Copyright (c) 1996-2003 Red Hat, Inc. + Target configuration macros for ARM. + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + ``Software''), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + ----------------------------------------------------------------------- */ + +#ifndef LIBFFI_TARGET_H +#define LIBFFI_TARGET_H + +#ifndef LIBFFI_H +#error "Please do not include ffitarget.h directly into your source. Use ffi.h instead." +#endif + +#ifndef LIBFFI_ASM +typedef unsigned long ffi_arg; +typedef signed long ffi_sarg; + +typedef enum ffi_abi { + FFI_FIRST_ABI = 0, + FFI_SYSV, + FFI_VFP, + FFI_LAST_ABI, +#ifdef __ARM_PCS_VFP + FFI_DEFAULT_ABI = FFI_VFP, +#else + FFI_DEFAULT_ABI = FFI_SYSV, +#endif +} ffi_abi; +#endif + +#define FFI_EXTRA_CIF_FIELDS \ + int vfp_used; \ + unsigned short vfp_reg_free, vfp_nargs; \ + signed char vfp_args[16] + +#define FFI_TARGET_SPECIFIC_VARIADIC +#define FFI_TARGET_HAS_COMPLEX_TYPE + +/* ---- Definitions for closures ----------------------------------------- */ + +#define FFI_CLOSURES 0 +#define FFI_GO_CLOSURES 0 +#define FFI_NATIVE_RAW_API 0 + +#if defined(FFI_EXEC_TRAMPOLINE_TABLE) && FFI_EXEC_TRAMPOLINE_TABLE + +#ifdef __MACH__ +#define FFI_TRAMPOLINE_SIZE 12 +#define FFI_TRAMPOLINE_CLOSURE_OFFSET 8 +#else +#error "No trampoline table implementation" +#endif + +#else +#define FFI_TRAMPOLINE_SIZE 12 +#define FFI_TRAMPOLINE_CLOSURE_OFFSET FFI_TRAMPOLINE_SIZE +#endif + +#endif \ No newline at end of file diff --git a/applications/system/elk_js/ffi/internal.h b/applications/system/elk_js/ffi/internal.h new file mode 100644 index 00000000000..ffdf973bacc --- /dev/null +++ b/applications/system/elk_js/ffi/internal.h @@ -0,0 +1,7 @@ +#define ARM_TYPE_VFP_S 0 +#define ARM_TYPE_VFP_D 1 +#define ARM_TYPE_VFP_N 2 +#define ARM_TYPE_INT64 3 +#define ARM_TYPE_INT 4 +#define ARM_TYPE_VOID 5 +#define ARM_TYPE_STRUCT 6 \ No newline at end of file diff --git a/applications/system/elk_js/ffi/prep_cif.c b/applications/system/elk_js/ffi/prep_cif.c new file mode 100644 index 00000000000..1f09efb016f --- /dev/null +++ b/applications/system/elk_js/ffi/prep_cif.c @@ -0,0 +1,233 @@ +/* ----------------------------------------------------------------------- + prep_cif.c - Copyright (c) 2011, 2012 Anthony Green + Copyright (c) 1996, 1998, 2007 Red Hat, Inc. + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + ``Software''), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + ----------------------------------------------------------------------- */ + +#include "ffi.h" +#include "ffi_common.h" +#include + +/* Round up to FFI_SIZEOF_ARG. */ + +#define STACK_ARG_SIZE(x) ALIGN(x, FFI_SIZEOF_ARG) + +/* Perform machine independent initialization of aggregate type + specifications. */ + +static ffi_status initialize_aggregate(ffi_type* arg, size_t* offsets) { + ffi_type** ptr; + + if(UNLIKELY(arg == NULL || arg->elements == NULL)) return FFI_BAD_TYPEDEF; + + arg->size = 0; + arg->alignment = 0; + + ptr = &(arg->elements[0]); + + if(UNLIKELY(ptr == 0)) return FFI_BAD_TYPEDEF; + + while((*ptr) != NULL) { + if(UNLIKELY(((*ptr)->size == 0) && (initialize_aggregate((*ptr), NULL) != FFI_OK))) + return FFI_BAD_TYPEDEF; + + /* Perform a sanity check on the argument type */ + FFI_ASSERT_VALID_TYPE(*ptr); + + arg->size = ALIGN(arg->size, (*ptr)->alignment); + if(offsets) *offsets++ = arg->size; + arg->size += (*ptr)->size; + + arg->alignment = (arg->alignment > (*ptr)->alignment) ? arg->alignment : (*ptr)->alignment; + + ptr++; + } + + /* Structure size includes tail padding. This is important for + structures that fit in one register on ABIs like the PowerPC64 + Linux ABI that right justify small structs in a register. + It's also needed for nested structure layout, for example + struct A { long a; char b; }; struct B { struct A x; char y; }; + should find y at an offset of 2*sizeof(long) and result in a + total size of 3*sizeof(long). */ + arg->size = ALIGN(arg->size, arg->alignment); + + /* On some targets, the ABI defines that structures have an additional + alignment beyond the "natural" one based on their elements. */ +#ifdef FFI_AGGREGATE_ALIGNMENT + if(FFI_AGGREGATE_ALIGNMENT > arg->alignment) arg->alignment = FFI_AGGREGATE_ALIGNMENT; +#endif + + if(arg->size == 0) + return FFI_BAD_TYPEDEF; + else + return FFI_OK; +} + +#ifndef __CRIS__ +/* The CRIS ABI specifies structure elements to have byte + alignment only, so it completely overrides this functions, + which assumes "natural" alignment and padding. */ + +/* Perform machine independent ffi_cif preparation, then call + machine dependent routine. */ + +/* For non variadic functions isvariadic should be 0 and + nfixedargs==ntotalargs. + For variadic calls, isvariadic should be 1 and nfixedargs + and ntotalargs set as appropriate. nfixedargs must always be >=1 */ + +ffi_status FFI_HIDDEN ffi_prep_cif_core( + ffi_cif* cif, + ffi_abi abi, + unsigned int isvariadic, + unsigned int nfixedargs, + unsigned int ntotalargs, + ffi_type* rtype, + ffi_type** atypes) { + unsigned bytes = 0; + unsigned int i; + ffi_type** ptr; + + FFI_ASSERT(cif != NULL); + FFI_ASSERT((!isvariadic) || (nfixedargs >= 1)); + FFI_ASSERT(nfixedargs <= ntotalargs); + + if(!(abi > FFI_FIRST_ABI && abi < FFI_LAST_ABI)) return FFI_BAD_ABI; + + cif->abi = abi; + cif->arg_types = atypes; + cif->nargs = ntotalargs; + cif->rtype = rtype; + + cif->flags = 0; + +#if HAVE_LONG_DOUBLE_VARIANT + ffi_prep_types(abi); +#endif + + /* Initialize the return type if necessary */ + if((cif->rtype->size == 0) && (initialize_aggregate(cif->rtype, NULL) != FFI_OK)) + return FFI_BAD_TYPEDEF; + +#ifndef FFI_TARGET_HAS_COMPLEX_TYPE + if(rtype->type == FFI_TYPE_COMPLEX) abort(); +#endif + /* Perform a sanity check on the return type */ + FFI_ASSERT_VALID_TYPE(cif->rtype); + + /* x86, x86-64 and s390 stack space allocation is handled in prep_machdep. */ +#if !defined FFI_TARGET_SPECIFIC_STACK_SPACE_ALLOCATION + /* Make space for the return structure pointer */ + if(cif->rtype->type == FFI_TYPE_STRUCT +#ifdef TILE + && (cif->rtype->size > 10 * FFI_SIZEOF_ARG) +#endif +#ifdef XTENSA + && (cif->rtype->size > 16) +#endif +#ifdef NIOS2 + && (cif->rtype->size > 8) +#endif + ) + bytes = STACK_ARG_SIZE(sizeof(void*)); +#endif + + for(ptr = cif->arg_types, i = cif->nargs; i > 0; i--, ptr++) { + /* Initialize any uninitialized aggregate type definitions */ + if(((*ptr)->size == 0) && (initialize_aggregate((*ptr), NULL) != FFI_OK)) + return FFI_BAD_TYPEDEF; + +#ifndef FFI_TARGET_HAS_COMPLEX_TYPE + if((*ptr)->type == FFI_TYPE_COMPLEX) abort(); +#endif + /* Perform a sanity check on the argument type, do this + check after the initialization. */ + FFI_ASSERT_VALID_TYPE(*ptr); + +#if !defined FFI_TARGET_SPECIFIC_STACK_SPACE_ALLOCATION + { + /* Add any padding if necessary */ + if(((*ptr)->alignment - 1) & bytes) bytes = (unsigned)ALIGN(bytes, (*ptr)->alignment); + +#ifdef TILE + if(bytes < 10 * FFI_SIZEOF_ARG && + bytes + STACK_ARG_SIZE((*ptr)->size) > 10 * FFI_SIZEOF_ARG) { + /* An argument is never split between the 10 parameter + registers and the stack. */ + bytes = 10 * FFI_SIZEOF_ARG; + } +#endif +#ifdef XTENSA + if(bytes <= 6 * 4 && bytes + STACK_ARG_SIZE((*ptr)->size) > 6 * 4) bytes = 6 * 4; +#endif + + bytes += STACK_ARG_SIZE((*ptr)->size); + } +#endif + } + + cif->bytes = bytes; + + /* Perform machine dependent cif processing */ +#ifdef FFI_TARGET_SPECIFIC_VARIADIC + if(isvariadic) return ffi_prep_cif_machdep_var(cif, nfixedargs, ntotalargs); +#endif + + return ffi_prep_cif_machdep(cif); +} +#endif /* not __CRIS__ */ + +ffi_status + ffi_prep_cif(ffi_cif* cif, ffi_abi abi, unsigned int nargs, ffi_type* rtype, ffi_type** atypes) { + return ffi_prep_cif_core(cif, abi, 0, nargs, nargs, rtype, atypes); +} + +ffi_status ffi_prep_cif_var( + ffi_cif* cif, + ffi_abi abi, + unsigned int nfixedargs, + unsigned int ntotalargs, + ffi_type* rtype, + ffi_type** atypes) { + return ffi_prep_cif_core(cif, abi, 1, nfixedargs, ntotalargs, rtype, atypes); +} + +#if FFI_CLOSURES + +ffi_status ffi_prep_closure( + ffi_closure* closure, + ffi_cif* cif, + void (*fun)(ffi_cif*, void*, void**, void*), + void* user_data) { + return ffi_prep_closure_loc(closure, cif, fun, user_data, closure); +} + +#endif + +ffi_status ffi_get_struct_offsets(ffi_abi abi, ffi_type* struct_type, size_t* offsets) { + if(!(abi > FFI_FIRST_ABI && abi < FFI_LAST_ABI)) return FFI_BAD_ABI; + if(struct_type->type != FFI_TYPE_STRUCT) return FFI_BAD_TYPEDEF; + +#if HAVE_LONG_DOUBLE_VARIANT + ffi_prep_types(abi); +#endif + + return initialize_aggregate(struct_type, offsets); +} \ No newline at end of file diff --git a/applications/system/elk_js/ffi/sysV-thumb.S b/applications/system/elk_js/ffi/sysV-thumb.S new file mode 100644 index 00000000000..8f1607f3801 --- /dev/null +++ b/applications/system/elk_js/ffi/sysV-thumb.S @@ -0,0 +1,227 @@ +/* ----------------------------------------------------------------------- + sysv.S - Copyright (c) 1998, 2008, 2011 Red Hat, Inc. + Copyright (c) 2011 Plausible Labs Cooperative, Inc. + ARM Foreign Function Interface + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + ``Software''), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + ----------------------------------------------------------------------- */ + +#define LIBFFI_ASM +#include "fficonfig.h" +#include "ffi.h" +#include "ffi_cfi.h" +#include "internal.h" + +/* GCC 4.8 provides __ARM_ARCH; construct it otherwise. */ +#ifndef __ARM_ARCH +# if defined(__ARM_ARCH_7__) || defined(__ARM_ARCH_7A__) \ + || defined(__ARM_ARCH_7R__) || defined(__ARM_ARCH_7M__) \ + || defined(__ARM_ARCH_7EM__) +# define __ARM_ARCH 7 +# elif defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_6J__) \ + || defined(__ARM_ARCH_6K__) || defined(__ARM_ARCH_6Z__) \ + || defined(__ARM_ARCH_6ZK__) || defined(__ARM_ARCH_6T2__) \ + || defined(__ARM_ARCH_6M__) +# define __ARM_ARCH 6 +# elif defined(__ARM_ARCH_5__) || defined(__ARM_ARCH_5T__) \ + || defined(__ARM_ARCH_5E__) || defined(__ARM_ARCH_5TE__) \ + || defined(__ARM_ARCH_5TEJ__) +# define __ARM_ARCH 5 +# else +# define __ARM_ARCH 4 +# endif +#endif + +/* Conditionally compile unwinder directives. */ +#ifdef __ARM_EABI__1 +# define UNWIND(...) __VA_ARGS__ +#else +# define UNWIND(...) +#endif + +#if defined(HAVE_AS_CFI_PSEUDO_OP) && defined(__ARM_EABI__) + .cfi_sections .debug_frame +#endif + +#define CONCAT(a, b) CONCAT2(a, b) +#define CONCAT2(a, b) a ## b + +#ifdef __USER_LABEL_PREFIX__ +# define CNAME(X) CONCAT (__USER_LABEL_PREFIX__, X) +#else +# define CNAME(X) X +#endif +#ifdef __ELF__ +# define SIZE(X) .size CNAME(X), . - CNAME(X) +# define TYPE(X, Y) .type CNAME(X), Y +#else +# define SIZE(X) +# define TYPE(X, Y) +#endif + +#define ARM_FUNC_START_LOCAL(name) \ + .align 3; \ + TYPE(CNAME(name), %function); \ + CNAME(name): + +#define ARM_FUNC_START(name) \ + .globl CNAME(name); \ + FFI_HIDDEN(CNAME(name)); \ + ARM_FUNC_START_LOCAL(name) + +#define ARM_FUNC_END(name) \ + SIZE(name) + +/* Aid in defining a jump table with 8 bytes between entries. */ +/* ??? The clang assembler doesn't handle .if with symbolic expressions. */ +#if 1 +# define E(index) +#else +# define E(index) \ + .if . - 0b - 8*index; \ + .error "type table out of sync"; \ + .endif +#endif + + .text + .syntax unified + .thumb + +#ifndef __clang__ + /* We require interworking on LDM, which implies ARMv5T, + which implies the existance of BLX. */ + .arch armv6t2 +#endif + + /* Note that we use STC and LDC to encode VFP instructions, + so that we do not need ".fpu vfp", nor get that added to + the object file attributes. These will not be executed + unless the FFI_VFP abi is used. */ + + @ r0: stack + @ r1: frame + @ r2: fn + @ r3: vfp_used + +ARM_FUNC_START(ffi_call_VFP) + UNWIND(.fnstart) + cfi_startproc + + cmp r3, #3 @ load only d0 if possible + + bgt greater + ldc p11, cr0, [r0] @ vldrle d0, [sp] + b done + + greater: + ldc p11, cr0, [r0], {16} @ vldmgt sp, {d0-d7} + done: + add r0, #64 @ discard the vfp register args + /* FALLTHRU */ +ARM_FUNC_END(ffi_call_VFP) + +ARM_FUNC_START(ffi_call_SYSV) + stm r1, {fp, lr} + mov fp, r1 + + @ This is a bit of a lie wrt the origin of the unwind info, but + @ now we've got the usual frame pointer and two saved registers. + UNWIND(.save {fp,lr}) + UNWIND(.setfp fp, sp) + cfi_def_cfa(fp, 8) + cfi_rel_offset(fp, 0) + cfi_rel_offset(lr, 4) + + mov sp, r0 @ install the stack pointer + mov lr, r2 @ move the fn pointer out of the way + ldr ip, [fp, #16] @ install the static chain + ldmia sp!, {r0-r3} @ move first 4 parameters in registers. + blx lr @ call fn + + @ Load r2 with the pointer to storage for the return value + @ Load r3 with the return type code + ldr r2, [fp, #8] + ldr r3, [fp, #12] + + @ Deallocate the stack with the arguments. + mov sp, fp + cfi_def_cfa_register(sp) + + @ Store values stored in registers. + adr ip, 0f + add ip, ip, r3, lsl #3 + mov pc, ip + .align 3 +0: +E(ARM_TYPE_VFP_S) + stc p10, cr0, [r2] @ vstr s0, [r2] + pop {fp,pc} +E(ARM_TYPE_VFP_D) + stc p11, cr0, [r2] @ vstr d0, [r2] + pop {fp,pc} +E(ARM_TYPE_VFP_N) + stc p11, cr0, [r2], {8} @ vstm r2, {d0-d3} + pop {fp,pc} +E(ARM_TYPE_INT64) + str r1, [r2, #4] + nop + nop + nop +E(ARM_TYPE_INT) + str r0, [r2] + pop {fp,pc} + nop +E(ARM_TYPE_VOID) + pop {fp,pc} + nop + nop +E(ARM_TYPE_STRUCT) + pop {fp,pc} + + cfi_endproc + UNWIND(.fnend) +ARM_FUNC_END(ffi_call_SYSV) + +#if FFI_EXEC_TRAMPOLINE_TABLE + +#ifdef __MACH__ +#include + +.align PAGE_MAX_SHIFT +ARM_FUNC_START(ffi_closure_trampoline_table_page) +.rept PAGE_MAX_SIZE / FFI_TRAMPOLINE_SIZE + adr ip, #-PAGE_MAX_SIZE @ the config page is PAGE_MAX_SIZE behind the trampoline page + sub ip, #8 @ account for pc bias + ldr pc, [ip, #4] @ jump to ffi_closure_SYSV or ffi_closure_VFP +.endr +ARM_FUNC_END(ffi_closure_trampoline_table_page) +#endif + +#else + +ARM_FUNC_START(ffi_arm_trampoline) +0: adr ip, 0b + ldr pc, 1f +1: .long 0 +ARM_FUNC_END(ffi_arm_trampoline) + +#endif /* FFI_EXEC_TRAMPOLINE_TABLE */ + +#if defined __ELF__ && defined __linux__ + .section .note.GNU-stack,"",%progbits +#endif \ No newline at end of file diff --git a/applications/system/elk_js/ffi/types.c b/applications/system/elk_js/ffi/types.c new file mode 100644 index 00000000000..5a04e9949fd --- /dev/null +++ b/applications/system/elk_js/ffi/types.c @@ -0,0 +1,101 @@ +/* ----------------------------------------------------------------------- + types.c - Copyright (c) 1996, 1998 Red Hat, Inc. + + Predefined ffi_types needed by libffi. + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + ``Software''), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + ----------------------------------------------------------------------- */ + +/* Hide the basic type definitions from the header file, so that we + can redefine them here as "const". */ +#define LIBFFI_HIDE_BASIC_TYPES +#define FFI_EXTERN + +#include "ffi.h" +#include "ffi_common.h" + +/* Type definitions */ + +#define FFI_TYPEDEF(name, type, id, maybe_const) \ + struct struct_align_##name { \ + char c; \ + type x; \ + }; \ + FFI_EXTERN \ + maybe_const ffi_type ffi_type_##name = { \ + sizeof(type), offsetof(struct struct_align_##name, x), id, NULL} + +#define FFI_COMPLEX_TYPEDEF(name, type, maybe_const) \ + static ffi_type* ffi_elements_complex_##name[2] = {(ffi_type*)(&ffi_type_##name), NULL}; \ + struct struct_align_complex_##name { \ + char c; \ + _Complex type x; \ + }; \ + FFI_EXTERN \ + maybe_const ffi_type ffi_type_complex_##name = { \ + sizeof(_Complex type), \ + offsetof(struct struct_align_complex_##name, x), \ + FFI_TYPE_COMPLEX, \ + (ffi_type**)ffi_elements_complex_##name} + +/* Size and alignment are fake here. They must not be 0. */ +FFI_EXTERN const ffi_type ffi_type_void = {1, 1, FFI_TYPE_VOID, NULL}; + +FFI_TYPEDEF(uint8, UINT8, FFI_TYPE_UINT8, const); +FFI_TYPEDEF(sint8, SINT8, FFI_TYPE_SINT8, const); +FFI_TYPEDEF(uint16, UINT16, FFI_TYPE_UINT16, const); +FFI_TYPEDEF(sint16, SINT16, FFI_TYPE_SINT16, const); +FFI_TYPEDEF(uint32, UINT32, FFI_TYPE_UINT32, const); +FFI_TYPEDEF(sint32, SINT32, FFI_TYPE_SINT32, const); +FFI_TYPEDEF(uint64, UINT64, FFI_TYPE_UINT64, const); +FFI_TYPEDEF(sint64, SINT64, FFI_TYPE_SINT64, const); + +FFI_TYPEDEF(pointer, void*, FFI_TYPE_POINTER, const); + +FFI_TYPEDEF(float, float, FFI_TYPE_FLOAT, const); +FFI_TYPEDEF(double, double, FFI_TYPE_DOUBLE, const); + +#if !defined HAVE_LONG_DOUBLE_VARIANT || defined __alpha__ +#define FFI_LDBL_CONST const +#else +#define FFI_LDBL_CONST +#endif + +#ifdef __alpha__ +/* Even if we're not configured to default to 128-bit long double, + maintain binary compatibility, as -mlong-double-128 can be used + at any time. */ +/* Validate the hard-coded number below. */ +#if defined(__LONG_DOUBLE_128__) && FFI_TYPE_LONGDOUBLE != 4 +#error FFI_TYPE_LONGDOUBLE out of date +#endif +const ffi_type ffi_type_longdouble = {16, 16, 4, NULL}; +#elif FFI_TYPE_LONGDOUBLE != FFI_TYPE_DOUBLE +FFI_TYPEDEF(longdouble, long double, FFI_TYPE_LONGDOUBLE, FFI_LDBL_CONST); +#endif + +#ifdef FFI_TARGET_HAS_COMPLEX_TYPE +FFI_COMPLEX_TYPEDEF(float, float, const); +FFI_COMPLEX_TYPEDEF(double, double, const); +#if FFI_TYPE_LONGDOUBLE != FFI_TYPE_DOUBLE +FFI_COMPLEX_TYPEDEF(longdouble, long double, FFI_LDBL_CONST); +#endif +#endif \ No newline at end of file diff --git a/applications/system/elk_js/scripts/api.js b/applications/system/elk_js/scripts/api.js new file mode 100644 index 00000000000..ad3b26e1563 --- /dev/null +++ b/applications/system/elk_js/scripts/api.js @@ -0,0 +1,3 @@ +({ + add: function (a, b) { return a + b; }, +}) \ No newline at end of file diff --git a/applications/system/elk_js/scripts/blink.js b/applications/system/elk_js/scripts/blink.js new file mode 100644 index 00000000000..8f890608c90 --- /dev/null +++ b/applications/system/elk_js/scripts/blink.js @@ -0,0 +1,10 @@ +print(led); +print('RED'); +led.red(); +delay(500); +print("GREEN"); +led.green(); +delay(500); +print("BLUE"); +led.blue(); +delay(500); \ No newline at end of file diff --git a/applications/system/elk_js/scripts/ffi.js b/applications/system/elk_js/scripts/ffi.js new file mode 100644 index 00000000000..57ef4d7ae1c --- /dev/null +++ b/applications/system/elk_js/scripts/ffi.js @@ -0,0 +1,64 @@ +let record = ({ + open: function (name) { return fficall(arg.pointer, "furi_record_open", arg.string, name); }, + close: function (name) { return fficall(arg.none, "furi_record_close", arg.string, name); }, +}); + +let notification = ({ + name: "notification", + message: function (record, sequence) { + return fficall(arg.none, "notification_message", arg.pointer, record, arg.pointer, sequence); + }, + + audiovisual_alert: function () { return ffires("sequence_audiovisual_alert"); }, + blink_blue_10: function () { return ffires("sequence_blink_blue_10"); }, + blink_blue_100: function () { return ffires("sequence_blink_blue_100"); }, + blink_cyan_10: function () { return ffires("sequence_blink_cyan_10"); }, + blink_cyan_100: function () { return ffires("sequence_blink_cyan_100"); }, + blink_green_10: function () { return ffires("sequence_blink_green_10"); }, + blink_green_100: function () { return ffires("sequence_blink_green_100"); }, + blink_magenta_10: function () { return ffires("sequence_blink_magenta_10"); }, + blink_magenta_100: function () { return ffires("sequence_blink_magenta_100"); }, + blink_red_10: function () { return ffires("sequence_blink_red_10"); }, + blink_red_100: function () { return ffires("sequence_blink_red_100"); }, + blink_start_blue: function () { return ffires("sequence_blink_start_blue"); }, + blink_start_cyan: function () { return ffires("sequence_blink_start_cyan"); }, + blink_start_green: function () { return ffires("sequence_blink_start_green"); }, + blink_start_magenta: function () { return ffires("sequence_blink_start_magenta"); }, + blink_start_red: function () { return ffires("sequence_blink_start_red"); }, + blink_start_yellow: function () { return ffires("sequence_blink_start_yellow"); }, + blink_stop: function () { return ffires("sequence_blink_stop"); }, + blink_white_100: function () { return ffires("sequence_blink_white_100"); }, + blink_yellow_10: function () { return ffires("sequence_blink_yellow_10"); }, + blink_yellow_100: function () { return ffires("sequence_blink_yellow_100"); }, + charged: function () { return ffires("sequence_charged"); }, + charging: function () { return ffires("sequence_charging"); }, + display_backlight_enforce_auto: function () { return ffires("sequence_display_backlight_enforce_auto"); }, + display_backlight_enforce_on: function () { return ffires("sequence_display_backlight_enforce_on"); }, + display_backlight_off: function () { return ffires("sequence_display_backlight_off"); }, + display_backlight_off_delay_1000: function () { return ffires("sequence_display_backlight_off_delay_1000"); }, + display_backlight_on: function () { return ffires("sequence_display_backlight_on"); }, + double_vibro: function () { return ffires("sequence_double_vibro"); }, + error: function () { return ffires("sequence_error"); }, + not_charging: function () { return ffires("sequence_not_charging"); }, + reset_blue: function () { return ffires("sequence_reset_blue"); }, + reset_display: function () { return ffires("sequence_reset_display"); }, + reset_green: function () { return ffires("sequence_reset_green"); }, + reset_red: function () { return ffires("sequence_reset_red"); }, + reset_rgb: function () { return ffires("sequence_reset_rgb"); }, + reset_sound: function () { return ffires("sequence_reset_sound"); }, + reset_vibro: function () { return ffires("sequence_reset_vibro"); }, + set_blue_255: function () { return ffires("sequence_set_blue_255"); }, + set_green_255: function () { return ffires("sequence_set_green_255"); }, + set_only_blue_255: function () { return ffires("sequence_set_only_blue_255"); }, + set_only_green_255: function () { return ffires("sequence_set_only_green_255"); }, + set_only_red_255: function () { return ffires("sequence_set_only_red_255"); }, + set_red_255: function () { return ffires("sequence_set_red_255"); }, + set_vibro_on: function () { return ffires("sequence_set_vibro_on"); }, + single_vibro: function () { return ffires("sequence_single_vibro"); }, + solid_yellow: function () { return ffires("sequence_solid_yellow"); }, + success: function () { return ffires("sequence_success"); }, +}); + +let notification_app = record.open(notification.name); +notification.message(notification_app, notification.success()); +record.close(notification.name); \ No newline at end of file diff --git a/applications/system/elk_js/scripts/req.js b/applications/system/elk_js/scripts/req.js new file mode 100644 index 00000000000..e40ce6b5706 --- /dev/null +++ b/applications/system/elk_js/scripts/req.js @@ -0,0 +1,3 @@ +let math = require("/ext/scripts/api.js"); +let result = math.add(5, 10); +print(result); \ No newline at end of file From 58781b583b4b9e0c32fb807691637f2895e85587 Mon Sep 17 00:00:00 2001 From: SG Date: Fri, 15 Sep 2023 18:43:23 +0300 Subject: [PATCH 04/31] Apps: mjs --- applications/system/elk_mjs/application.fam | 23 + .../system/elk_mjs/lib/mjs/common/cs_dbg.c | 144 ++ .../system/elk_mjs/lib/mjs/common/cs_dbg.h | 148 ++ .../system/elk_mjs/lib/mjs/common/cs_dirent.c | 108 ++ .../system/elk_mjs/lib/mjs/common/cs_dirent.h | 51 + .../system/elk_mjs/lib/mjs/common/cs_file.c | 67 + .../system/elk_mjs/lib/mjs/common/cs_file.h | 48 + .../system/elk_mjs/lib/mjs/common/cs_time.c | 87 + .../system/elk_mjs/lib/mjs/common/cs_time.h | 42 + .../system/elk_mjs/lib/mjs/common/cs_varint.c | 76 + .../system/elk_mjs/lib/mjs/common/cs_varint.h | 59 + .../elk_mjs/lib/mjs/common/frozen/frozen.c | 1528 +++++++++++++++++ .../elk_mjs/lib/mjs/common/frozen/frozen.h | 359 ++++ .../system/elk_mjs/lib/mjs/common/mbuf.c | 151 ++ .../system/elk_mjs/lib/mjs/common/mbuf.h | 111 ++ .../system/elk_mjs/lib/mjs/common/mg_mem.h | 45 + .../system/elk_mjs/lib/mjs/common/mg_str.c | 175 ++ .../system/elk_mjs/lib/mjs/common/mg_str.h | 113 ++ .../system/elk_mjs/lib/mjs/common/platform.h | 123 ++ .../lib/mjs/common/platforms/platform_stm32.h | 53 + .../system/elk_mjs/lib/mjs/common/str_util.c | 537 ++++++ .../system/elk_mjs/lib/mjs/common/str_util.h | 195 +++ applications/system/elk_mjs/lib/mjs/ffi/ffi.c | 553 ++++++ applications/system/elk_mjs/lib/mjs/ffi/ffi.h | 53 + .../system/elk_mjs/lib/mjs/mjs_array.c | 233 +++ .../system/elk_mjs/lib/mjs/mjs_array.h | 26 + .../system/elk_mjs/lib/mjs/mjs_array_public.h | 47 + .../system/elk_mjs/lib/mjs/mjs_bcode.c | 147 ++ .../system/elk_mjs/lib/mjs/mjs_bcode.h | 105 ++ .../system/elk_mjs/lib/mjs/mjs_builtin.c | 171 ++ .../system/elk_mjs/lib/mjs/mjs_builtin.h | 22 + .../system/elk_mjs/lib/mjs/mjs_conversion.c | 78 + .../system/elk_mjs/lib/mjs/mjs_conversion.h | 40 + .../system/elk_mjs/lib/mjs/mjs_core.c | 400 +++++ .../system/elk_mjs/lib/mjs/mjs_core.h | 180 ++ .../system/elk_mjs/lib/mjs/mjs_core_public.h | 232 +++ .../system/elk_mjs/lib/mjs/mjs_dataview.c | 86 + .../system/elk_mjs/lib/mjs/mjs_dataview.h | 32 + .../system/elk_mjs/lib/mjs/mjs_exec.c | 1184 +++++++++++++ .../system/elk_mjs/lib/mjs/mjs_exec.h | 27 + .../system/elk_mjs/lib/mjs/mjs_exec_public.h | 35 + .../system/elk_mjs/lib/mjs/mjs_features.h | 33 + applications/system/elk_mjs/lib/mjs/mjs_ffi.c | 1215 +++++++++++++ applications/system/elk_mjs/lib/mjs/mjs_ffi.h | 137 ++ .../system/elk_mjs/lib/mjs/mjs_ffi_public.h | 38 + applications/system/elk_mjs/lib/mjs/mjs_gc.c | 535 ++++++ applications/system/elk_mjs/lib/mjs/mjs_gc.h | 60 + .../system/elk_mjs/lib/mjs/mjs_gc_public.h | 25 + .../system/elk_mjs/lib/mjs/mjs_internal.h | 90 + .../system/elk_mjs/lib/mjs/mjs_json.c | 519 ++++++ .../system/elk_mjs/lib/mjs/mjs_json.h | 32 + .../system/elk_mjs/lib/mjs/mjs_license.h | 17 + applications/system/elk_mjs/lib/mjs/mjs_mm.h | 44 + .../system/elk_mjs/lib/mjs/mjs_object.c | 395 +++++ .../system/elk_mjs/lib/mjs/mjs_object.h | 59 + .../elk_mjs/lib/mjs/mjs_object_public.h | 121 ++ .../system/elk_mjs/lib/mjs/mjs_parser.c | 1032 +++++++++++ .../system/elk_mjs/lib/mjs/mjs_parser.h | 21 + .../system/elk_mjs/lib/mjs/mjs_primitive.c | 160 ++ .../system/elk_mjs/lib/mjs/mjs_primitive.h | 41 + .../elk_mjs/lib/mjs/mjs_primitive_public.h | 121 ++ .../system/elk_mjs/lib/mjs/mjs_string.c | 602 +++++++ .../system/elk_mjs/lib/mjs/mjs_string.h | 47 + .../elk_mjs/lib/mjs/mjs_string_public.h | 78 + applications/system/elk_mjs/lib/mjs/mjs_tok.c | 247 +++ applications/system/elk_mjs/lib/mjs/mjs_tok.h | 141 ++ .../system/elk_mjs/lib/mjs/mjs_util.c | 476 +++++ .../system/elk_mjs/lib/mjs/mjs_util.h | 57 + .../system/elk_mjs/lib/mjs/mjs_util_public.h | 50 + applications/system/elk_mjs/m_js.c | 201 +++ applications/system/elk_mjs/scripts/about.js | 350 ++++ applications/system/elk_mjs/scripts/api.js | 3 + applications/system/elk_mjs/scripts/ffi.js | 14 + applications/system/elk_mjs/scripts/req.js | 3 + 74 files changed, 14858 insertions(+) create mode 100644 applications/system/elk_mjs/application.fam create mode 100644 applications/system/elk_mjs/lib/mjs/common/cs_dbg.c create mode 100644 applications/system/elk_mjs/lib/mjs/common/cs_dbg.h create mode 100644 applications/system/elk_mjs/lib/mjs/common/cs_dirent.c create mode 100644 applications/system/elk_mjs/lib/mjs/common/cs_dirent.h create mode 100644 applications/system/elk_mjs/lib/mjs/common/cs_file.c create mode 100644 applications/system/elk_mjs/lib/mjs/common/cs_file.h create mode 100644 applications/system/elk_mjs/lib/mjs/common/cs_time.c create mode 100644 applications/system/elk_mjs/lib/mjs/common/cs_time.h create mode 100644 applications/system/elk_mjs/lib/mjs/common/cs_varint.c create mode 100644 applications/system/elk_mjs/lib/mjs/common/cs_varint.h create mode 100644 applications/system/elk_mjs/lib/mjs/common/frozen/frozen.c create mode 100644 applications/system/elk_mjs/lib/mjs/common/frozen/frozen.h create mode 100644 applications/system/elk_mjs/lib/mjs/common/mbuf.c create mode 100644 applications/system/elk_mjs/lib/mjs/common/mbuf.h create mode 100644 applications/system/elk_mjs/lib/mjs/common/mg_mem.h create mode 100644 applications/system/elk_mjs/lib/mjs/common/mg_str.c create mode 100644 applications/system/elk_mjs/lib/mjs/common/mg_str.h create mode 100644 applications/system/elk_mjs/lib/mjs/common/platform.h create mode 100644 applications/system/elk_mjs/lib/mjs/common/platforms/platform_stm32.h create mode 100644 applications/system/elk_mjs/lib/mjs/common/str_util.c create mode 100644 applications/system/elk_mjs/lib/mjs/common/str_util.h create mode 100644 applications/system/elk_mjs/lib/mjs/ffi/ffi.c create mode 100644 applications/system/elk_mjs/lib/mjs/ffi/ffi.h create mode 100644 applications/system/elk_mjs/lib/mjs/mjs_array.c create mode 100644 applications/system/elk_mjs/lib/mjs/mjs_array.h create mode 100644 applications/system/elk_mjs/lib/mjs/mjs_array_public.h create mode 100644 applications/system/elk_mjs/lib/mjs/mjs_bcode.c create mode 100644 applications/system/elk_mjs/lib/mjs/mjs_bcode.h create mode 100644 applications/system/elk_mjs/lib/mjs/mjs_builtin.c create mode 100644 applications/system/elk_mjs/lib/mjs/mjs_builtin.h create mode 100644 applications/system/elk_mjs/lib/mjs/mjs_conversion.c create mode 100644 applications/system/elk_mjs/lib/mjs/mjs_conversion.h create mode 100644 applications/system/elk_mjs/lib/mjs/mjs_core.c create mode 100644 applications/system/elk_mjs/lib/mjs/mjs_core.h create mode 100644 applications/system/elk_mjs/lib/mjs/mjs_core_public.h create mode 100644 applications/system/elk_mjs/lib/mjs/mjs_dataview.c create mode 100644 applications/system/elk_mjs/lib/mjs/mjs_dataview.h create mode 100644 applications/system/elk_mjs/lib/mjs/mjs_exec.c create mode 100644 applications/system/elk_mjs/lib/mjs/mjs_exec.h create mode 100644 applications/system/elk_mjs/lib/mjs/mjs_exec_public.h create mode 100644 applications/system/elk_mjs/lib/mjs/mjs_features.h create mode 100644 applications/system/elk_mjs/lib/mjs/mjs_ffi.c create mode 100644 applications/system/elk_mjs/lib/mjs/mjs_ffi.h create mode 100644 applications/system/elk_mjs/lib/mjs/mjs_ffi_public.h create mode 100644 applications/system/elk_mjs/lib/mjs/mjs_gc.c create mode 100644 applications/system/elk_mjs/lib/mjs/mjs_gc.h create mode 100644 applications/system/elk_mjs/lib/mjs/mjs_gc_public.h create mode 100644 applications/system/elk_mjs/lib/mjs/mjs_internal.h create mode 100644 applications/system/elk_mjs/lib/mjs/mjs_json.c create mode 100644 applications/system/elk_mjs/lib/mjs/mjs_json.h create mode 100644 applications/system/elk_mjs/lib/mjs/mjs_license.h create mode 100644 applications/system/elk_mjs/lib/mjs/mjs_mm.h create mode 100644 applications/system/elk_mjs/lib/mjs/mjs_object.c create mode 100644 applications/system/elk_mjs/lib/mjs/mjs_object.h create mode 100644 applications/system/elk_mjs/lib/mjs/mjs_object_public.h create mode 100644 applications/system/elk_mjs/lib/mjs/mjs_parser.c create mode 100644 applications/system/elk_mjs/lib/mjs/mjs_parser.h create mode 100644 applications/system/elk_mjs/lib/mjs/mjs_primitive.c create mode 100644 applications/system/elk_mjs/lib/mjs/mjs_primitive.h create mode 100644 applications/system/elk_mjs/lib/mjs/mjs_primitive_public.h create mode 100644 applications/system/elk_mjs/lib/mjs/mjs_string.c create mode 100644 applications/system/elk_mjs/lib/mjs/mjs_string.h create mode 100644 applications/system/elk_mjs/lib/mjs/mjs_string_public.h create mode 100644 applications/system/elk_mjs/lib/mjs/mjs_tok.c create mode 100644 applications/system/elk_mjs/lib/mjs/mjs_tok.h create mode 100644 applications/system/elk_mjs/lib/mjs/mjs_util.c create mode 100644 applications/system/elk_mjs/lib/mjs/mjs_util.h create mode 100644 applications/system/elk_mjs/lib/mjs/mjs_util_public.h create mode 100644 applications/system/elk_mjs/m_js.c create mode 100644 applications/system/elk_mjs/scripts/about.js create mode 100644 applications/system/elk_mjs/scripts/api.js create mode 100644 applications/system/elk_mjs/scripts/ffi.js create mode 100644 applications/system/elk_mjs/scripts/req.js diff --git a/applications/system/elk_mjs/application.fam b/applications/system/elk_mjs/application.fam new file mode 100644 index 00000000000..ff93ec2d45d --- /dev/null +++ b/applications/system/elk_mjs/application.fam @@ -0,0 +1,23 @@ +App( + appid="m_js", + name="MJS", + fap_category="Tools", + apptype=FlipperAppType.EXTERNAL, + entry_point="m_js_app", + stack_size=8 * 1024, + cdefines=[ + ("CS_PLATFORM", "CS_P_STM32"), + "MJS_EXPOSE_PRIVATE", + ], + fap_private_libs=[ + Lib( + name="mjs", + cflags=[ + "-Wno-redundant-decls", + "-Wno-unused-function", + ], + ), + ], + fap_libs=["gcc"], + order=0, +) diff --git a/applications/system/elk_mjs/lib/mjs/common/cs_dbg.c b/applications/system/elk_mjs/lib/mjs/common/cs_dbg.c new file mode 100644 index 00000000000..c1861cfad84 --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/common/cs_dbg.c @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2014-2018 Cesanta Software Limited + * All rights reserved + * + * Licensed under the Apache License, Version 2.0 (the ""License""); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an ""AS IS"" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "cs_dbg.h" + +#include +#include +#include +#include + +#include "cs_time.h" +#include "str_util.h" + +enum cs_log_level cs_log_level WEAK = +#if CS_ENABLE_DEBUG + LL_VERBOSE_DEBUG; +#else + LL_ERROR; +#endif + +#if CS_ENABLE_STDIO +static char* s_file_level = NULL; + +void cs_log_set_file_level(const char* file_level) WEAK; + +FILE* cs_log_file WEAK = NULL; + +#if CS_LOG_ENABLE_TS_DIFF +double cs_log_ts WEAK; +#endif + +enum cs_log_level cs_log_cur_msg_level WEAK = LL_NONE; + +void cs_log_set_file_level(const char* file_level) { + char* fl = s_file_level; + if(file_level != NULL) { + s_file_level = strdup(file_level); + } else { + s_file_level = NULL; + } + free(fl); +} + +int cs_log_print_prefix(enum cs_log_level level, const char* file, int ln) WEAK; +int cs_log_print_prefix(enum cs_log_level level, const char* file, int ln) { + char prefix[CS_LOG_PREFIX_LEN], *q; + const char* p; + size_t fl = 0, ll = 0, pl = 0; + + if(level > cs_log_level && s_file_level == NULL) return 0; + + p = file + strlen(file); + + while(p != file) { + const char c = *(p - 1); + if(c == '/' || c == '\\') break; + p--; + fl++; + } + + ll = (ln < 10000 ? (ln < 1000 ? (ln < 100 ? (ln < 10 ? 1 : 2) : 3) : 4) : 5); + if(fl > (sizeof(prefix) - ll - 2)) fl = (sizeof(prefix) - ll - 2); + + pl = fl + 1 + ll; + memcpy(prefix, p, fl); + q = prefix + pl; + memset(q, ' ', sizeof(prefix) - pl); + do { + *(--q) = '0' + (ln % 10); + ln /= 10; + } while(ln > 0); + *(--q) = ':'; + + if(s_file_level != NULL) { + enum cs_log_level pll = cs_log_level; + struct mg_str fl = mg_mk_str(s_file_level), ps = MG_MK_STR_N(prefix, pl); + struct mg_str k, v; + while((fl = mg_next_comma_list_entry_n(fl, &k, &v)).p != NULL) { + bool yes = !(!mg_str_starts_with(ps, k) || v.len == 0); + if(!yes) continue; + pll = (enum cs_log_level)(*v.p - '0'); + break; + } + if(level > pll) return 0; + } + + if(cs_log_file == NULL) cs_log_file = stderr; + cs_log_cur_msg_level = level; + fwrite(prefix, 1, sizeof(prefix), cs_log_file); +#if CS_LOG_ENABLE_TS_DIFF + { + double now = cs_time(); + fprintf(cs_log_file, "%7u ", (unsigned int)((now - cs_log_ts) * 1000000)); + cs_log_ts = now; + } +#endif + return 1; +} + +void cs_log_printf(const char* fmt, ...) WEAK; +void cs_log_printf(const char* fmt, ...) { + va_list ap; + va_start(ap, fmt); + vfprintf(cs_log_file, fmt, ap); + va_end(ap); + fputc('\n', cs_log_file); + fflush(cs_log_file); + cs_log_cur_msg_level = LL_NONE; +} + +void cs_log_set_file(FILE* file) WEAK; +void cs_log_set_file(FILE* file) { + cs_log_file = file; +} + +#else + +void cs_log_set_file_level(const char* file_level) { + (void)file_level; +} + +#endif /* CS_ENABLE_STDIO */ + +void cs_log_set_level(enum cs_log_level level) WEAK; +void cs_log_set_level(enum cs_log_level level) { + cs_log_level = level; +#if CS_LOG_ENABLE_TS_DIFF && CS_ENABLE_STDIO + cs_log_ts = cs_time(); +#endif +} diff --git a/applications/system/elk_mjs/lib/mjs/common/cs_dbg.h b/applications/system/elk_mjs/lib/mjs/common/cs_dbg.h new file mode 100644 index 00000000000..d8b68a447c7 --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/common/cs_dbg.h @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2014-2018 Cesanta Software Limited + * All rights reserved + * + * Licensed under the Apache License, Version 2.0 (the ""License""); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an ""AS IS"" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CS_COMMON_CS_DBG_H_ +#define CS_COMMON_CS_DBG_H_ + +#include "platform.h" + +#if CS_ENABLE_STDIO +#include +#endif + +#ifndef CS_ENABLE_DEBUG +#define CS_ENABLE_DEBUG 0 +#endif + +#ifndef CS_LOG_PREFIX_LEN +#define CS_LOG_PREFIX_LEN 24 +#endif + +#ifndef CS_LOG_ENABLE_TS_DIFF +#define CS_LOG_ENABLE_TS_DIFF 0 +#endif + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* + * Log level; `LL_INFO` is the default. Use `cs_log_set_level()` to change it. + */ +enum cs_log_level { + LL_NONE = -1, + LL_ERROR = 0, + LL_WARN = 1, + LL_INFO = 2, + LL_DEBUG = 3, + LL_VERBOSE_DEBUG = 4, + + _LL_MIN = -2, + _LL_MAX = 5, +}; + +/* + * Set max log level to print; messages with the level above the given one will + * not be printed. + */ +void cs_log_set_level(enum cs_log_level level); + +/* + * A comma-separated set of prefix=level. + * prefix is matched against the log prefix exactly as printed, including line + * number, but partial match is ok. Check stops on first matching entry. + * If nothing matches, default level is used. + * + * Examples: + * main.c:=4 - everything from main C at verbose debug level. + * mongoose.c=1,mjs.c=1,=4 - everything at verbose debug except mg_* and mjs_* + * + */ +void cs_log_set_file_level(const char* file_level); + +/* + * Helper function which prints message prefix with the given `level`. + * If message should be printed (according to the current log level + * and filter), prints the prefix and returns 1, otherwise returns 0. + * + * Clients should typically just use `LOG()` macro. + */ +int cs_log_print_prefix(enum cs_log_level level, const char* fname, int line); + +extern enum cs_log_level cs_log_level; + +#if CS_ENABLE_STDIO + +/* + * Set file to write logs into. If `NULL`, logs go to `stderr`. + */ +void cs_log_set_file(FILE* file); + +/* + * Prints log to the current log file, appends "\n" in the end and flushes the + * stream. + */ +void cs_log_printf(const char* fmt, ...) PRINTF_LIKE(1, 2); + +#if CS_ENABLE_STDIO + +/* + * Format and print message `x` with the given level `l`. Example: + * + * ```c + * LOG(LL_INFO, ("my info message: %d", 123)); + * LOG(LL_DEBUG, ("my debug message: %d", 123)); + * ``` + */ +#define LOG(l, x) \ + do { \ + if(cs_log_print_prefix(l, __FILE__, __LINE__)) { \ + cs_log_printf x; \ + } \ + } while(0) + +#else + +#define LOG(l, x) ((void)l) + +#endif + +#ifndef CS_NDEBUG + +/* + * Shortcut for `LOG(LL_VERBOSE_DEBUG, (...))` + */ +#define DBG(x) LOG(LL_VERBOSE_DEBUG, x) + +#else /* NDEBUG */ + +#define DBG(x) + +#endif + +#else /* CS_ENABLE_STDIO */ + +#define LOG(l, x) +#define DBG(x) + +#endif + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* CS_COMMON_CS_DBG_H_ */ diff --git a/applications/system/elk_mjs/lib/mjs/common/cs_dirent.c b/applications/system/elk_mjs/lib/mjs/common/cs_dirent.c new file mode 100644 index 00000000000..892a43b72e4 --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/common/cs_dirent.c @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2014-2018 Cesanta Software Limited + * All rights reserved + * + * Licensed under the Apache License, Version 2.0 (the ""License""); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an ""AS IS"" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef EXCLUDE_COMMON + +#include "mg_mem.h" +#include "cs_dirent.h" + +/* + * This file contains POSIX opendir/closedir/readdir API implementation + * for systems which do not natively support it (e.g. Windows). + */ + +#ifdef _WIN32 +struct win32_dir { + DIR d; + HANDLE handle; + WIN32_FIND_DATAW info; + struct dirent result; +}; + +DIR *opendir(const char *name) { + struct win32_dir *dir = NULL; + wchar_t wpath[MAX_PATH]; + DWORD attrs; + + if (name == NULL) { + SetLastError(ERROR_BAD_ARGUMENTS); + } else if ((dir = (struct win32_dir *) MG_MALLOC(sizeof(*dir))) == NULL) { + SetLastError(ERROR_NOT_ENOUGH_MEMORY); + } else { + to_wchar(name, wpath, ARRAY_SIZE(wpath)); + attrs = GetFileAttributesW(wpath); + if (attrs != 0xFFFFFFFF && (attrs & FILE_ATTRIBUTE_DIRECTORY)) { + (void) wcscat(wpath, L"\\*"); + dir->handle = FindFirstFileW(wpath, &dir->info); + dir->result.d_name[0] = '\0'; + } else { + MG_FREE(dir); + dir = NULL; + } + } + + return (DIR *) dir; +} + +int closedir(DIR *d) { + struct win32_dir *dir = (struct win32_dir *) d; + int result = 0; + + if (dir != NULL) { + if (dir->handle != INVALID_HANDLE_VALUE) + result = FindClose(dir->handle) ? 0 : -1; + MG_FREE(dir); + } else { + result = -1; + SetLastError(ERROR_BAD_ARGUMENTS); + } + + return result; +} + +struct dirent *readdir(DIR *d) { + struct win32_dir *dir = (struct win32_dir *) d; + struct dirent *result = NULL; + + if (dir) { + memset(&dir->result, 0, sizeof(dir->result)); + if (dir->handle != INVALID_HANDLE_VALUE) { + result = &dir->result; + (void) WideCharToMultiByte(CP_UTF8, 0, dir->info.cFileName, -1, + result->d_name, sizeof(result->d_name), NULL, + NULL); + + if (!FindNextFileW(dir->handle, &dir->info)) { + (void) FindClose(dir->handle); + dir->handle = INVALID_HANDLE_VALUE; + } + + } else { + SetLastError(ERROR_FILE_NOT_FOUND); + } + } else { + SetLastError(ERROR_BAD_ARGUMENTS); + } + + return result; +} +#endif + +#endif /* EXCLUDE_COMMON */ + +/* ISO C requires a translation unit to contain at least one declaration */ +typedef int cs_dirent_dummy; diff --git a/applications/system/elk_mjs/lib/mjs/common/cs_dirent.h b/applications/system/elk_mjs/lib/mjs/common/cs_dirent.h new file mode 100644 index 00000000000..603af5c65a4 --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/common/cs_dirent.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2014-2018 Cesanta Software Limited + * All rights reserved + * + * Licensed under the Apache License, Version 2.0 (the ""License""); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an ""AS IS"" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CS_COMMON_CS_DIRENT_H_ +#define CS_COMMON_CS_DIRENT_H_ + +#include + +#include "platform.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#ifdef CS_DEFINE_DIRENT +typedef struct { int dummy; } DIR; + +struct dirent { + int d_ino; +#ifdef _WIN32 + char d_name[MAX_PATH]; +#else + /* TODO(rojer): Use PATH_MAX but make sure it's sane on every platform */ + char d_name[256]; +#endif +}; + +DIR *opendir(const char *dir_name); +int closedir(DIR *dir); +struct dirent *readdir(DIR *dir); +#endif /* CS_DEFINE_DIRENT */ + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* CS_COMMON_CS_DIRENT_H_ */ diff --git a/applications/system/elk_mjs/lib/mjs/common/cs_file.c b/applications/system/elk_mjs/lib/mjs/common/cs_file.c new file mode 100644 index 00000000000..e9db5ca1cd8 --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/common/cs_file.c @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2014-2018 Cesanta Software Limited + * All rights reserved + * + * Licensed under the Apache License, Version 2.0 (the ""License""); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an ""AS IS"" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "cs_file.h" + +#include +#include + +#ifdef CS_MMAP +#include +#include +#include +#endif + +#ifndef EXCLUDE_COMMON +char *cs_read_file(const char *path, size_t *size) WEAK; +char *cs_read_file(const char *path, size_t *size) { + FILE *fp; + char *data = NULL; + if ((fp = fopen(path, "rb")) == NULL) { + } else if (fseek(fp, 0, SEEK_END) != 0) { + fclose(fp); + } else { + *size = ftell(fp); + data = (char *) malloc(*size + 1); + if (data != NULL) { + fseek(fp, 0, SEEK_SET); /* Some platforms might not have rewind(), Oo */ + if (fread(data, 1, *size, fp) != *size) { + free(data); + return NULL; + } + data[*size] = '\0'; + } + fclose(fp); + } + return data; +} +#endif /* EXCLUDE_COMMON */ + +#ifdef CS_MMAP +char *cs_mmap_file(const char *path, size_t *size) WEAK; +char *cs_mmap_file(const char *path, size_t *size) { + char *r; + int fd = open(path, O_RDONLY, 0); + struct stat st; + if (fd < 0) return NULL; + fstat(fd, &st); + *size = (size_t) st.st_size; + r = (char *) mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if (r == MAP_FAILED) return NULL; + return r; +} +#endif diff --git a/applications/system/elk_mjs/lib/mjs/common/cs_file.h b/applications/system/elk_mjs/lib/mjs/common/cs_file.h new file mode 100644 index 00000000000..a30973389de --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/common/cs_file.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2014-2018 Cesanta Software Limited + * All rights reserved + * + * Licensed under the Apache License, Version 2.0 (the ""License""); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an ""AS IS"" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CS_COMMON_CS_FILE_H_ +#define CS_COMMON_CS_FILE_H_ + +#include "platform.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* + * Read whole file `path` in memory. It is responsibility of the caller + * to `free()` allocated memory. File content is guaranteed to be + * '\0'-terminated. File size is returned in `size` variable, which does not + * count terminating `\0`. + * Return: allocated memory, or NULL on error. + */ +char *cs_read_file(const char *path, size_t *size); + +#ifdef CS_MMAP +/* + * Only on platforms which support mmapping: mmap file `path` to the returned + * address. File size is written to `*size`. + */ +char *cs_mmap_file(const char *path, size_t *size); +#endif + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* CS_COMMON_CS_FILE_H_ */ diff --git a/applications/system/elk_mjs/lib/mjs/common/cs_time.c b/applications/system/elk_mjs/lib/mjs/common/cs_time.c new file mode 100644 index 00000000000..a8eb7c24a36 --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/common/cs_time.c @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2014-2018 Cesanta Software Limited + * All rights reserved + * + * Licensed under the Apache License, Version 2.0 (the ""License""); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an ""AS IS"" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "cs_time.h" + +#ifndef _WIN32 +#include +/* + * There is no sys/time.h on ARMCC. + */ +#if !(defined(__ARMCC_VERSION) || defined(__ICCARM__)) && !defined(__TI_COMPILER_VERSION__) && \ + (!defined(CS_PLATFORM) || CS_PLATFORM != CS_P_NXP_LPC) +#include +#endif +#else +#include +#endif + +double cs_time(void) WEAK; +double cs_time(void) { + double now; +#ifndef _WIN32 + struct timeval tv; + if(gettimeofday(&tv, NULL /* tz */) != 0) return 0; + now = (double)tv.tv_sec + (((double)tv.tv_usec) / (double)1000000.0); +#else + SYSTEMTIME sysnow; + FILETIME ftime; + GetLocalTime(&sysnow); + SystemTimeToFileTime(&sysnow, &ftime); + /* + * 1. VC 6.0 doesn't support conversion uint64 -> double, so, using int64 + * This should not cause a problems in this (21th) century + * 2. Windows FILETIME is a number of 100-nanosecond intervals since January + * 1, 1601 while time_t is a number of _seconds_ since January 1, 1970 UTC, + * thus, we need to convert to seconds and adjust amount (subtract 11644473600 + * seconds) + */ + now = + (double)(((int64_t)ftime.dwLowDateTime + ((int64_t)ftime.dwHighDateTime << 32)) / 10000000.0) - + 11644473600; +#endif /* _WIN32 */ + return now; +} + +double cs_timegm(const struct tm* tm) { + /* Month-to-day offset for non-leap-years. */ + static const int month_day[12] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}; + + /* Most of the calculation is easy; leap years are the main difficulty. */ + int month = tm->tm_mon % 12; + int year = tm->tm_year + tm->tm_mon / 12; + int year_for_leap; + int64_t rt; + + if(month < 0) { /* Negative values % 12 are still negative. */ + month += 12; + --year; + } + + /* This is the number of Februaries since 1900. */ + year_for_leap = (month > 1) ? year + 1 : year; + + rt = tm->tm_sec /* Seconds */ + + 60 * (tm->tm_min /* Minute = 60 seconds */ + + 60 * (tm->tm_hour /* Hour = 60 minutes */ + + 24 * (month_day[month] + tm->tm_mday - 1 /* Day = 24 hours */ + + 365 * (year - 70) /* Year = 365 days */ + + (year_for_leap - 69) / 4 /* Every 4 years is leap... */ + - (year_for_leap - 1) / 100 /* Except centuries... */ + + (year_for_leap + 299) / 400))); /* Except 400s. */ + return rt < 0 ? -1 : (double)rt; +} diff --git a/applications/system/elk_mjs/lib/mjs/common/cs_time.h b/applications/system/elk_mjs/lib/mjs/common/cs_time.h new file mode 100644 index 00000000000..413528c860e --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/common/cs_time.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2014-2018 Cesanta Software Limited + * All rights reserved + * + * Licensed under the Apache License, Version 2.0 (the ""License""); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an ""AS IS"" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CS_COMMON_CS_TIME_H_ +#define CS_COMMON_CS_TIME_H_ + +#include + +#include "platform.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* Sub-second granularity time(). */ +double cs_time(void); + +/* + * Similar to (non-standard) timegm, converts broken-down time into the number + * of seconds since Unix Epoch. + */ +double cs_timegm(const struct tm* tm); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* CS_COMMON_CS_TIME_H_ */ diff --git a/applications/system/elk_mjs/lib/mjs/common/cs_varint.c b/applications/system/elk_mjs/lib/mjs/common/cs_varint.c new file mode 100644 index 00000000000..fdd5e91a0d0 --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/common/cs_varint.c @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2014-2018 Cesanta Software Limited + * All rights reserved + * + * Licensed under the Apache License, Version 2.0 (the ""License""); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an ""AS IS"" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "cs_varint.h" + +size_t cs_varint_llen(uint64_t num) { + size_t llen = 0; + + do { + llen++; + } while (num >>= 7); + + return llen; +} + +size_t cs_varint_encode(uint64_t num, uint8_t *buf, size_t buf_size) { + size_t llen = 0; + + do { + uint8_t byte = num & 0x7f; + num >>= 7; + if (num != 0) byte |= 0x80; + if (llen < buf_size) *buf++ = byte; + llen++; + } while (num != 0); + + return llen; +} + +bool cs_varint_decode(const uint8_t *buf, size_t buf_size, uint64_t *num, + size_t *llen) { + size_t i = 0, shift = 0; + uint64_t n = 0; + + do { + if (i == buf_size || i == (8 * sizeof(*num) / 7 + 1)) return false; + /* + * Each byte of varint contains 7 bits, in little endian order. + * MSB is a continuation bit: it tells whether next byte is used. + */ + n |= ((uint64_t)(buf[i] & 0x7f)) << shift; + /* + * First we increment i, then check whether it is within boundary and + * whether decoded byte had continuation bit set. + */ + i++; + shift += 7; + } while (shift < sizeof(uint64_t) * 8 && (buf[i - 1] & 0x80)); + + *num = n; + *llen = i; + + return true; +} + +uint64_t cs_varint_decode_unsafe(const uint8_t *buf, int *llen) { + uint64_t v; + size_t l; + cs_varint_decode(buf, ~0, &v, &l); + *llen = l; + return v; +} diff --git a/applications/system/elk_mjs/lib/mjs/common/cs_varint.h b/applications/system/elk_mjs/lib/mjs/common/cs_varint.h new file mode 100644 index 00000000000..bef9513cfef --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/common/cs_varint.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2014-2018 Cesanta Software Limited + * All rights reserved + * + * Licensed under the Apache License, Version 2.0 (the ""License""); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an ""AS IS"" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CS_COMMON_CS_VARINT_H_ +#define CS_COMMON_CS_VARINT_H_ + +#if defined(_WIN32) && _MSC_VER < 1700 +typedef unsigned char uint8_t; +typedef unsigned __int64 uint64_t; +#else +#include +#include +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* Returns number of bytes required to encode `num`. */ +size_t cs_varint_llen(uint64_t num); + +/* + * Encodes `num` into `buf`. + * Returns number of bytes required to encode `num`. + * Note: return value may be greater than `buf_size` but the function will only + * write `buf_size` bytes. + */ +size_t cs_varint_encode(uint64_t num, uint8_t *buf, size_t buf_size); + +/* + * Decodes varint stored in `buf`. + * Stores the number of bytes consumed into `llen`. + * If there aren't enough bytes in `buf` to decode a number, returns false. + */ +bool cs_varint_decode(const uint8_t *buf, size_t buf_size, uint64_t *num, + size_t *llen); + +uint64_t cs_varint_decode_unsafe(const uint8_t *buf, int *llen); + +#ifdef __cplusplus +} +#endif + +#endif /* CS_COMMON_CS_VARINT_H_ */ diff --git a/applications/system/elk_mjs/lib/mjs/common/frozen/frozen.c b/applications/system/elk_mjs/lib/mjs/common/frozen/frozen.c new file mode 100644 index 00000000000..40d973812b7 --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/common/frozen/frozen.c @@ -0,0 +1,1528 @@ +/* + * Copyright (c) 2004-2013 Sergey Lyubka + * Copyright (c) 2018 Cesanta Software Limited + * All rights reserved + * + * Licensed under the Apache License, Version 2.0 (the ""License""); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an ""AS IS"" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define _CRT_SECURE_NO_WARNINGS /* Disable deprecation warning in VS2005+ */ + +#include "frozen.h" + +#include +#include +#include +#include +#include + +#if !defined(WEAK) +#if(defined(__GNUC__) || defined(__TI_COMPILER_VERSION__)) && !defined(_WIN32) +#define WEAK __attribute__((weak)) +#else +#define WEAK +#endif +#endif + +#ifdef _WIN32 +#undef snprintf +#undef vsnprintf +#define snprintf cs_win_snprintf +#define vsnprintf cs_win_vsnprintf +int cs_win_snprintf(char* str, size_t size, const char* format, ...); +int cs_win_vsnprintf(char* str, size_t size, const char* format, va_list ap); +#if _MSC_VER >= 1700 +#include +#else +typedef _int64 int64_t; +typedef unsigned _int64 uint64_t; +#endif +#define PRId64 "I64d" +#define PRIu64 "I64u" +#else /* _WIN32 */ +/* wants this for C++ */ +#ifndef __STDC_FORMAT_MACROS +#define __STDC_FORMAT_MACROS +#endif +#include +#endif /* _WIN32 */ + +#ifndef INT64_FMT +#define INT64_FMT PRId64 +#endif +#ifndef UINT64_FMT +#define UINT64_FMT PRIu64 +#endif + +#ifndef va_copy +#define va_copy(x, y) x = y +#endif + +#ifndef JSON_ENABLE_ARRAY +#define JSON_ENABLE_ARRAY 1 +#endif + +struct frozen { + const char* end; + const char* cur; + + const char* cur_name; + size_t cur_name_len; + + /* For callback API */ + char path[JSON_MAX_PATH_LEN]; + size_t path_len; + void* callback_data; + json_walk_callback_t callback; +}; + +struct fstate { + const char* ptr; + size_t path_len; +}; + +#define SET_STATE(fr, ptr, str, len) \ + struct fstate fstate = {(ptr), (fr)->path_len}; \ + json_append_to_path((fr), (str), (len)); + +#define CALL_BACK(fr, tok, value, len) \ + do { \ + if((fr)->callback && ((fr)->path_len == 0 || (fr)->path[(fr)->path_len - 1] != '.')) { \ + struct json_token t = {(value), (int)(len), (tok)}; \ + \ + /* Call the callback with the given value and current name */ \ + (fr)->callback( \ + (fr)->callback_data, (fr)->cur_name, (fr)->cur_name_len, (fr)->path, &t); \ + \ + /* Reset the name */ \ + (fr)->cur_name = NULL; \ + (fr)->cur_name_len = 0; \ + } \ + } while(0) + +static int json_append_to_path(struct frozen* f, const char* str, int size) { + int n = f->path_len; + int left = sizeof(f->path) - n - 1; + if(size > left) size = left; + memcpy(f->path + n, str, size); + f->path[n + size] = '\0'; + f->path_len += size; + return n; +} + +static void json_truncate_path(struct frozen* f, size_t len) { + f->path_len = len; + f->path[len] = '\0'; +} + +static int json_parse_object(struct frozen* f); +static int json_parse_value(struct frozen* f); + +#define EXPECT(cond, err_code) \ + do { \ + if(!(cond)) return (err_code); \ + } while(0) + +#define TRY(expr) \ + do { \ + int _n = expr; \ + if(_n < 0) return _n; \ + } while(0) + +#define END_OF_STRING (-1) + +static int json_left(const struct frozen* f) { + return f->end - f->cur; +} + +static int json_isspace(int ch) { + return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n'; +} + +static void json_skip_whitespaces(struct frozen* f) { + while(f->cur < f->end && json_isspace(*f->cur)) f->cur++; +} + +static int json_cur(struct frozen* f) { + json_skip_whitespaces(f); + return f->cur >= f->end ? END_OF_STRING : *(unsigned char*)f->cur; +} + +static int json_test_and_skip(struct frozen* f, int expected) { + int ch = json_cur(f); + if(ch == expected) { + f->cur++; + return 0; + } + return ch == END_OF_STRING ? JSON_STRING_INCOMPLETE : JSON_STRING_INVALID; +} + +static int json_isalpha(int ch) { + return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'); +} + +static int json_isdigit(int ch) { + return ch >= '0' && ch <= '9'; +} + +static int json_isxdigit(int ch) { + return json_isdigit(ch) || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F'); +} + +static int json_get_escape_len(const char* s, int len) { + switch(*s) { + case 'u': + return len < 6 ? JSON_STRING_INCOMPLETE : + json_isxdigit(s[1]) && json_isxdigit(s[2]) && json_isxdigit(s[3]) && + json_isxdigit(s[4]) ? + 5 : + JSON_STRING_INVALID; + case '"': + case '\\': + case '/': + case 'b': + case 'f': + case 'n': + case 'r': + case 't': + return len < 2 ? JSON_STRING_INCOMPLETE : 1; + default: + return JSON_STRING_INVALID; + } +} + +/* identifier = letter { letter | digit | '_' } */ +static int json_parse_identifier(struct frozen* f) { + EXPECT(json_isalpha(json_cur(f)), JSON_STRING_INVALID); + { + SET_STATE(f, f->cur, "", 0); + while(f->cur < f->end && + (*f->cur == '_' || json_isalpha(*f->cur) || json_isdigit(*f->cur))) { + f->cur++; + } + json_truncate_path(f, fstate.path_len); + CALL_BACK(f, JSON_TYPE_STRING, fstate.ptr, f->cur - fstate.ptr); + } + return 0; +} + +static int json_get_utf8_char_len(unsigned char ch) { + if((ch & 0x80) == 0) return 1; + switch(ch & 0xf0) { + case 0xf0: + return 4; + case 0xe0: + return 3; + default: + return 2; + } +} + +/* string = '"' { quoted_printable_chars } '"' */ +static int json_parse_string(struct frozen* f) { + int n, ch = 0, len = 0; + TRY(json_test_and_skip(f, '"')); + { + SET_STATE(f, f->cur, "", 0); + for(; f->cur < f->end; f->cur += len) { + ch = *(unsigned char*)f->cur; + len = json_get_utf8_char_len((unsigned char)ch); + EXPECT(ch >= 32 && len > 0, JSON_STRING_INVALID); /* No control chars */ + EXPECT(len <= json_left(f), JSON_STRING_INCOMPLETE); + if(ch == '\\') { + EXPECT((n = json_get_escape_len(f->cur + 1, json_left(f))) > 0, n); + len += n; + } else if(ch == '"') { + json_truncate_path(f, fstate.path_len); + CALL_BACK(f, JSON_TYPE_STRING, fstate.ptr, f->cur - fstate.ptr); + f->cur++; + break; + }; + } + } + return ch == '"' ? 0 : JSON_STRING_INCOMPLETE; +} + +/* number = [ '-' ] digit+ [ '.' digit+ ] [ ['e'|'E'] ['+'|'-'] digit+ ] */ +static int json_parse_number(struct frozen* f) { + int ch = json_cur(f); + SET_STATE(f, f->cur, "", 0); + if(ch == '-') f->cur++; + EXPECT(f->cur < f->end, JSON_STRING_INCOMPLETE); + if(f->cur + 1 < f->end && f->cur[0] == '0' && f->cur[1] == 'x') { + f->cur += 2; + EXPECT(f->cur < f->end, JSON_STRING_INCOMPLETE); + EXPECT(json_isxdigit(f->cur[0]), JSON_STRING_INVALID); + while(f->cur < f->end && json_isxdigit(f->cur[0])) f->cur++; + } else { + EXPECT(json_isdigit(f->cur[0]), JSON_STRING_INVALID); + while(f->cur < f->end && json_isdigit(f->cur[0])) f->cur++; + if(f->cur < f->end && f->cur[0] == '.') { + f->cur++; + EXPECT(f->cur < f->end, JSON_STRING_INCOMPLETE); + EXPECT(json_isdigit(f->cur[0]), JSON_STRING_INVALID); + while(f->cur < f->end && json_isdigit(f->cur[0])) f->cur++; + } + if(f->cur < f->end && (f->cur[0] == 'e' || f->cur[0] == 'E')) { + f->cur++; + EXPECT(f->cur < f->end, JSON_STRING_INCOMPLETE); + if((f->cur[0] == '+' || f->cur[0] == '-')) f->cur++; + EXPECT(f->cur < f->end, JSON_STRING_INCOMPLETE); + EXPECT(json_isdigit(f->cur[0]), JSON_STRING_INVALID); + while(f->cur < f->end && json_isdigit(f->cur[0])) f->cur++; + } + } + json_truncate_path(f, fstate.path_len); + CALL_BACK(f, JSON_TYPE_NUMBER, fstate.ptr, f->cur - fstate.ptr); + return 0; +} + +#if JSON_ENABLE_ARRAY +/* array = '[' [ value { ',' value } ] ']' */ +static int json_parse_array(struct frozen* f) { + int i = 0, current_path_len; + char buf[20]; + CALL_BACK(f, JSON_TYPE_ARRAY_START, NULL, 0); + TRY(json_test_and_skip(f, '[')); + { + { + SET_STATE(f, f->cur - 1, "", 0); + while(json_cur(f) != ']') { + snprintf(buf, sizeof(buf), "[%d]", i); + i++; + current_path_len = json_append_to_path(f, buf, strlen(buf)); + f->cur_name = f->path + strlen(f->path) - strlen(buf) + 1 /*opening brace*/; + f->cur_name_len = strlen(buf) - 2 /*braces*/; + TRY(json_parse_value(f)); + json_truncate_path(f, current_path_len); + if(json_cur(f) == ',') f->cur++; + } + TRY(json_test_and_skip(f, ']')); + json_truncate_path(f, fstate.path_len); + CALL_BACK(f, JSON_TYPE_ARRAY_END, fstate.ptr, f->cur - fstate.ptr); + } + } + return 0; +} +#endif /* JSON_ENABLE_ARRAY */ + +static int json_expect(struct frozen* f, const char* s, int len, enum json_token_type tok_type) { + int i, n = json_left(f); + SET_STATE(f, f->cur, "", 0); + for(i = 0; i < len; i++) { + if(i >= n) return JSON_STRING_INCOMPLETE; + if(f->cur[i] != s[i]) return JSON_STRING_INVALID; + } + f->cur += len; + json_truncate_path(f, fstate.path_len); + + CALL_BACK(f, tok_type, fstate.ptr, f->cur - fstate.ptr); + + return 0; +} + +/* value = 'null' | 'true' | 'false' | number | string | array | object */ +static int json_parse_value(struct frozen* f) { + int ch = json_cur(f); + + switch(ch) { + case '"': + TRY(json_parse_string(f)); + break; + case '{': + TRY(json_parse_object(f)); + break; +#if JSON_ENABLE_ARRAY + case '[': + TRY(json_parse_array(f)); + break; +#endif + case 'n': + TRY(json_expect(f, "null", 4, JSON_TYPE_NULL)); + break; + case 't': + TRY(json_expect(f, "true", 4, JSON_TYPE_TRUE)); + break; + case 'f': + TRY(json_expect(f, "false", 5, JSON_TYPE_FALSE)); + break; + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + TRY(json_parse_number(f)); + break; + default: + return ch == END_OF_STRING ? JSON_STRING_INCOMPLETE : JSON_STRING_INVALID; + } + + return 0; +} + +/* key = identifier | string */ +static int json_parse_key(struct frozen* f) { + int ch = json_cur(f); + if(json_isalpha(ch)) { + TRY(json_parse_identifier(f)); + } else if(ch == '"') { + TRY(json_parse_string(f)); + } else { + return ch == END_OF_STRING ? JSON_STRING_INCOMPLETE : JSON_STRING_INVALID; + } + return 0; +} + +/* pair = key ':' value */ +static int json_parse_pair(struct frozen* f) { + int current_path_len; + const char* tok; + json_skip_whitespaces(f); + tok = f->cur; + TRY(json_parse_key(f)); + { + f->cur_name = *tok == '"' ? tok + 1 : tok; + f->cur_name_len = *tok == '"' ? f->cur - tok - 2 : f->cur - tok; + current_path_len = json_append_to_path(f, f->cur_name, f->cur_name_len); + } + TRY(json_test_and_skip(f, ':')); + TRY(json_parse_value(f)); + json_truncate_path(f, current_path_len); + return 0; +} + +/* object = '{' pair { ',' pair } '}' */ +static int json_parse_object(struct frozen* f) { + CALL_BACK(f, JSON_TYPE_OBJECT_START, NULL, 0); + TRY(json_test_and_skip(f, '{')); + { + SET_STATE(f, f->cur - 1, ".", 1); + while(json_cur(f) != '}') { + TRY(json_parse_pair(f)); + if(json_cur(f) == ',') f->cur++; + } + TRY(json_test_and_skip(f, '}')); + json_truncate_path(f, fstate.path_len); + CALL_BACK(f, JSON_TYPE_OBJECT_END, fstate.ptr, f->cur - fstate.ptr); + } + return 0; +} + +static int json_doit(struct frozen* f) { + if(f->cur == 0 || f->end < f->cur) return JSON_STRING_INVALID; + if(f->end == f->cur) return JSON_STRING_INCOMPLETE; + return json_parse_value(f); +} + +int json_escape(struct json_out* out, const char* p, size_t len) WEAK; +int json_escape(struct json_out* out, const char* p, size_t len) { + size_t i, cl, n = 0; + const char* hex_digits = "0123456789abcdef"; + const char* specials = "btnvfr"; + + for(i = 0; i < len; i++) { + unsigned char ch = ((unsigned char*)p)[i]; + if(ch == '"' || ch == '\\') { + n += out->printer(out, "\\", 1); + n += out->printer(out, p + i, 1); + } else if(ch >= '\b' && ch <= '\r') { + n += out->printer(out, "\\", 1); + n += out->printer(out, &specials[ch - '\b'], 1); + } else if(isprint(ch)) { + n += out->printer(out, p + i, 1); + } else if((cl = json_get_utf8_char_len(ch)) == 1) { + n += out->printer(out, "\\u00", 4); + n += out->printer(out, &hex_digits[(ch >> 4) % 0xf], 1); + n += out->printer(out, &hex_digits[ch % 0xf], 1); + } else { + n += out->printer(out, p + i, cl); + i += cl - 1; + } + } + + return n; +} + +int json_printer_buf(struct json_out* out, const char* buf, size_t len) WEAK; +int json_printer_buf(struct json_out* out, const char* buf, size_t len) { + size_t avail = out->u.buf.size - out->u.buf.len; + size_t n = len < avail ? len : avail; + memcpy(out->u.buf.buf + out->u.buf.len, buf, n); + out->u.buf.len += n; + if(out->u.buf.size > 0) { + size_t idx = out->u.buf.len; + if(idx >= out->u.buf.size) idx = out->u.buf.size - 1; + out->u.buf.buf[idx] = '\0'; + } + return len; +} + +int json_printer_file(struct json_out* out, const char* buf, size_t len) WEAK; +int json_printer_file(struct json_out* out, const char* buf, size_t len) { + return fwrite(buf, 1, len, out->u.fp); +} + +#if JSON_ENABLE_BASE64 +static int b64idx(int c) { + if(c < 26) { + return c + 'A'; + } else if(c < 52) { + return c - 26 + 'a'; + } else if(c < 62) { + return c - 52 + '0'; + } else { + return c == 62 ? '+' : '/'; + } +} + +static int b64rev(int c) { + if(c >= 'A' && c <= 'Z') { + return c - 'A'; + } else if(c >= 'a' && c <= 'z') { + return c + 26 - 'a'; + } else if(c >= '0' && c <= '9') { + return c + 52 - '0'; + } else if(c == '+') { + return 62; + } else if(c == '/') { + return 63; + } else { + return 64; + } +} + +static int b64enc(struct json_out* out, const unsigned char* p, int n) { + char buf[4]; + int i, len = 0; + for(i = 0; i < n; i += 3) { + int a = p[i], b = i + 1 < n ? p[i + 1] : 0, c = i + 2 < n ? p[i + 2] : 0; + buf[0] = b64idx(a >> 2); + buf[1] = b64idx((a & 3) << 4 | (b >> 4)); + buf[2] = b64idx((b & 15) << 2 | (c >> 6)); + buf[3] = b64idx(c & 63); + if(i + 1 >= n) buf[2] = '='; + if(i + 2 >= n) buf[3] = '='; + len += out->printer(out, buf, sizeof(buf)); + } + return len; +} + +static int b64dec(const char* src, int n, char* dst) { + const char* end = src + n; + int len = 0; + while(src + 3 < end) { + int a = b64rev(src[0]), b = b64rev(src[1]), c = b64rev(src[2]), d = b64rev(src[3]); + dst[len++] = (a << 2) | (b >> 4); + if(src[2] != '=') { + dst[len++] = (b << 4) | (c >> 2); + if(src[3] != '=') { + dst[len++] = (c << 6) | d; + } + } + src += 4; + } + return len; +} +#endif /* JSON_ENABLE_BASE64 */ + +static unsigned char hexdec(const char* s) { +#define HEXTOI(x) (x >= '0' && x <= '9' ? x - '0' : x - 'W') + int a = tolower(*(const unsigned char*)s); + int b = tolower(*(const unsigned char*)(s + 1)); + return (HEXTOI(a) << 4) | HEXTOI(b); +} + +int json_vprintf(struct json_out* out, const char* fmt, va_list xap) WEAK; +int json_vprintf(struct json_out* out, const char* fmt, va_list xap) { + int len = 0; + const char *quote = "\"", *null = "null"; + va_list ap; + va_copy(ap, xap); + + while(*fmt != '\0') { + if(strchr(":, \r\n\t[]{}\"", *fmt) != NULL) { + len += out->printer(out, fmt, 1); + fmt++; + } else if(fmt[0] == '%') { + char buf[21]; + size_t skip = 2; + + if(fmt[1] == 'l' && fmt[2] == 'l' && (fmt[3] == 'd' || fmt[3] == 'u')) { + int64_t val = va_arg(ap, int64_t); + const char* fmt2 = fmt[3] == 'u' ? "%" UINT64_FMT : "%" INT64_FMT; + snprintf(buf, sizeof(buf), fmt2, val); + len += out->printer(out, buf, strlen(buf)); + skip += 2; + } else if(fmt[1] == 'z' && fmt[2] == 'u') { + size_t val = va_arg(ap, size_t); + snprintf(buf, sizeof(buf), "%lu", (unsigned long)val); + len += out->printer(out, buf, strlen(buf)); + skip += 1; + } else if(fmt[1] == 'M') { + json_printf_callback_t f = va_arg(ap, json_printf_callback_t); + len += f(out, &ap); + } else if(fmt[1] == 'B') { + int val = va_arg(ap, int); + const char* str = val ? "true" : "false"; + len += out->printer(out, str, strlen(str)); + } else if(fmt[1] == 'H') { +#if JSON_ENABLE_HEX + const char* hex = "0123456789abcdef"; + int i, n = va_arg(ap, int); + const unsigned char* p = va_arg(ap, const unsigned char*); + len += out->printer(out, quote, 1); + for(i = 0; i < n; i++) { + len += out->printer(out, &hex[(p[i] >> 4) & 0xf], 1); + len += out->printer(out, &hex[p[i] & 0xf], 1); + } + len += out->printer(out, quote, 1); +#endif /* JSON_ENABLE_HEX */ + } else if(fmt[1] == 'V') { +#if JSON_ENABLE_BASE64 + const unsigned char* p = va_arg(ap, const unsigned char*); + int n = va_arg(ap, int); + len += out->printer(out, quote, 1); + len += b64enc(out, p, n); + len += out->printer(out, quote, 1); +#endif /* JSON_ENABLE_BASE64 */ + } else if(fmt[1] == 'Q' || (fmt[1] == '.' && fmt[2] == '*' && fmt[3] == 'Q')) { + size_t l = 0; + const char* p; + + if(fmt[1] == '.') { + l = (size_t)va_arg(ap, int); + skip += 2; + } + p = va_arg(ap, char*); + + if(p == NULL) { + len += out->printer(out, null, 4); + } else { + if(fmt[1] == 'Q') { + l = strlen(p); + } + len += out->printer(out, quote, 1); + len += json_escape(out, p, l); + len += out->printer(out, quote, 1); + } + } else { + /* + * we delegate printing to the system printf. + * The goal here is to delegate all modifiers parsing to the system + * printf, as you can see below we still have to parse the format + * types. + * + * Currently, %s with strings longer than 20 chars will require + * double-buffering (an auxiliary buffer will be allocated from heap). + * TODO(dfrank): reimplement %s and %.*s in order to avoid that. + */ + + const char* end_of_format_specifier = "sdfFeEgGlhuIcx.*-0123456789"; + int n = strspn(fmt + 1, end_of_format_specifier); + char* pbuf = buf; + int need_len, size = sizeof(buf); + char fmt2[20]; + va_list ap_copy; + strncpy(fmt2, fmt, n + 1 > (int)sizeof(fmt2) ? sizeof(fmt2) : (size_t)n + 1); + fmt2[n + 1] = '\0'; + + va_copy(ap_copy, ap); + need_len = vsnprintf(pbuf, size, fmt2, ap_copy); + va_end(ap_copy); + + if(need_len < 0) { + /* + * Windows & eCos vsnprintf implementation return -1 on overflow + * instead of needed size. + */ + pbuf = NULL; + while(need_len < 0) { + free(pbuf); + size *= 2; + if((pbuf = (char*)malloc(size)) == NULL) break; + va_copy(ap_copy, ap); + need_len = vsnprintf(pbuf, size, fmt2, ap_copy); + va_end(ap_copy); + } + } else if(need_len >= (int)sizeof(buf)) { + /* + * resulting string doesn't fit into a stack-allocated buffer `buf`, + * so we need to allocate a new buffer from heap and use it + */ + if((pbuf = (char*)malloc(need_len + 1)) != NULL) { + va_copy(ap_copy, ap); + vsnprintf(pbuf, need_len + 1, fmt2, ap_copy); + va_end(ap_copy); + } + } + if(pbuf == NULL) { + buf[0] = '\0'; + pbuf = buf; + } + + /* + * however we need to parse the type ourselves in order to advance + * the va_list by the correct amount; there is no portable way to + * inherit the advancement made by vprintf. + * 32-bit (linux or windows) passes va_list by value. + */ + if((n + 1 == strlen("%" PRId64) && strcmp(fmt2, "%" PRId64) == 0) || + (n + 1 == strlen("%" PRIu64) && strcmp(fmt2, "%" PRIu64) == 0)) { + (void)va_arg(ap, int64_t); + } else if(strcmp(fmt2, "%.*s") == 0) { + (void)va_arg(ap, int); + (void)va_arg(ap, char*); + } else { + switch(fmt2[n]) { + case 'u': + case 'd': + (void)va_arg(ap, int); + break; + case 'g': + case 'f': + (void)va_arg(ap, double); + break; + case 'p': + (void)va_arg(ap, void*); + break; + default: + /* many types are promoted to int */ + (void)va_arg(ap, int); + } + } + + len += out->printer(out, pbuf, strlen(pbuf)); + skip = n + 1; + + /* If buffer was allocated from heap, free it */ + if(pbuf != buf) { + free(pbuf); + pbuf = NULL; + } + } + fmt += skip; + } else if(*fmt == '_' || json_isalpha(*fmt)) { + len += out->printer(out, quote, 1); + while(*fmt == '_' || json_isalpha(*fmt) || json_isdigit(*fmt)) { + len += out->printer(out, fmt, 1); + fmt++; + } + len += out->printer(out, quote, 1); + } else { + len += out->printer(out, fmt, 1); + fmt++; + } + } + va_end(ap); + + return len; +} + +int json_printf(struct json_out* out, const char* fmt, ...) WEAK; +int json_printf(struct json_out* out, const char* fmt, ...) { + int n; + va_list ap; + va_start(ap, fmt); + n = json_vprintf(out, fmt, ap); + va_end(ap); + return n; +} + +int json_printf_array(struct json_out* out, va_list* ap) WEAK; +int json_printf_array(struct json_out* out, va_list* ap) { + int len = 0; + char* arr = va_arg(*ap, char*); + size_t i, arr_size = va_arg(*ap, size_t); + size_t elem_size = va_arg(*ap, size_t); + const char* fmt = va_arg(*ap, char*); + len += json_printf(out, "[", 1); + for(i = 0; arr != NULL && i < arr_size / elem_size; i++) { + union { + int64_t i; + double d; + } val; + memcpy(&val, arr + i * elem_size, elem_size > sizeof(val) ? sizeof(val) : elem_size); + if(i > 0) len += json_printf(out, ", "); + if(strpbrk(fmt, "efg") != NULL) { + len += json_printf(out, fmt, val.d); + } else { + len += json_printf(out, fmt, val.i); + } + } + len += json_printf(out, "]", 1); + return len; +} + +#ifdef _WIN32 +int cs_win_vsnprintf(char* str, size_t size, const char* format, va_list ap) WEAK; +int cs_win_vsnprintf(char* str, size_t size, const char* format, va_list ap) { + int res = _vsnprintf(str, size, format, ap); + va_end(ap); + if(res >= size) { + str[size - 1] = '\0'; + } + return res; +} + +int cs_win_snprintf(char* str, size_t size, const char* format, ...) WEAK; +int cs_win_snprintf(char* str, size_t size, const char* format, ...) { + int res; + va_list ap; + va_start(ap, format); + res = vsnprintf(str, size, format, ap); + va_end(ap); + return res; +} +#endif /* _WIN32 */ + +int json_walk( + const char* json_string, + int json_string_length, + json_walk_callback_t callback, + void* callback_data) WEAK; +int json_walk( + const char* json_string, + int json_string_length, + json_walk_callback_t callback, + void* callback_data) { + struct frozen frozen; + + memset(&frozen, 0, sizeof(frozen)); + frozen.end = json_string + json_string_length; + frozen.cur = json_string; + frozen.callback_data = callback_data; + frozen.callback = callback; + + TRY(json_doit(&frozen)); + + return frozen.cur - json_string; +} + +struct scan_array_info { + int found; + char path[JSON_MAX_PATH_LEN]; + struct json_token* token; +}; + +static void json_scanf_array_elem_cb( + void* callback_data, + const char* name, + size_t name_len, + const char* path, + const struct json_token* token) { + struct scan_array_info* info = (struct scan_array_info*)callback_data; + + (void)name; + (void)name_len; + + if(strcmp(path, info->path) == 0) { + *info->token = *token; + info->found = 1; + } +} + +int json_scanf_array_elem( + const char* s, + int len, + const char* path, + int idx, + struct json_token* token) WEAK; +int json_scanf_array_elem( + const char* s, + int len, + const char* path, + int idx, + struct json_token* token) { + struct scan_array_info info; + info.token = token; + info.found = 0; + memset(token, 0, sizeof(*token)); + snprintf(info.path, sizeof(info.path), "%s[%d]", path, idx); + json_walk(s, len, json_scanf_array_elem_cb, &info); + return info.found ? token->len : -1; +} + +struct json_scanf_info { + int num_conversions; + char* path; + const char* fmt; + void* target; + void* user_data; + int type; +}; + +int json_unescape(const char* src, int slen, char* dst, int dlen) WEAK; +int json_unescape(const char* src, int slen, char* dst, int dlen) { + char *send = (char*)src + slen, *dend = dst + dlen, *orig_dst = dst, *p; + const char *esc1 = "\"\\/bfnrt", *esc2 = "\"\\/\b\f\n\r\t"; + + while(src < send) { + if(*src == '\\') { + if(++src >= send) return JSON_STRING_INCOMPLETE; + if(*src == 'u') { + if(send - src < 5) return JSON_STRING_INCOMPLETE; + /* Here we go: this is a \u.... escape. Process simple one-byte chars */ + if(src[1] == '0' && src[2] == '0') { + /* This is \u00xx character from the ASCII range */ + if(dst < dend) *dst = hexdec(src + 3); + src += 4; + } else { + /* Complex \uXXXX escapes drag utf8 lib... Do it at some stage */ + return JSON_STRING_INVALID; + } + } else if((p = (char*)strchr(esc1, *src)) != NULL) { + if(dst < dend) *dst = esc2[p - esc1]; + } else { + return JSON_STRING_INVALID; + } + } else { + if(dst < dend) *dst = *src; + } + dst++; + src++; + } + + return dst - orig_dst; +} + +static void json_scanf_cb( + void* callback_data, + const char* name, + size_t name_len, + const char* path, + const struct json_token* token) { + struct json_scanf_info* info = (struct json_scanf_info*)callback_data; + char buf[32]; /* Must be enough to hold numbers */ + + (void)name; + (void)name_len; + + if(token->ptr == NULL) { + /* + * We're not interested here in the events for which we have no value; + * namely, JSON_TYPE_OBJECT_START and JSON_TYPE_ARRAY_START + */ + return; + } + + if(strcmp(path, info->path) != 0) { + /* It's not the path we're looking for, so, just ignore this callback */ + return; + } + + switch(info->type) { + case 'B': + info->num_conversions++; + switch(sizeof(bool)) { + case sizeof(char): + *(char*)info->target = (token->type == JSON_TYPE_TRUE ? 1 : 0); + break; + case sizeof(int): + *(int*)info->target = (token->type == JSON_TYPE_TRUE ? 1 : 0); + break; + default: + /* should never be here */ + abort(); + } + break; + case 'M': { + union { + void* p; + json_scanner_t f; + } u = {info->target}; + info->num_conversions++; + u.f(token->ptr, token->len, info->user_data); + break; + } + case 'Q': { + char** dst = (char**)info->target; + if(token->type == JSON_TYPE_NULL) { + *dst = NULL; + } else { + int unescaped_len = json_unescape(token->ptr, token->len, NULL, 0); + if(unescaped_len >= 0 && (*dst = (char*)malloc(unescaped_len + 1)) != NULL) { + info->num_conversions++; + if(json_unescape(token->ptr, token->len, *dst, unescaped_len) == unescaped_len) { + (*dst)[unescaped_len] = '\0'; + } else { + free(*dst); + *dst = NULL; + } + } + } + break; + } + case 'H': { +#if JSON_ENABLE_HEX + char** dst = (char**)info->user_data; + int i, len = token->len / 2; + *(int*)info->target = len; + if((*dst = (char*)malloc(len + 1)) != NULL) { + for(i = 0; i < len; i++) { + (*dst)[i] = hexdec(token->ptr + 2 * i); + } + (*dst)[len] = '\0'; + info->num_conversions++; + } +#endif /* JSON_ENABLE_HEX */ + break; + } + case 'V': { +#if JSON_ENABLE_BASE64 + char** dst = (char**)info->target; + int len = token->len * 4 / 3 + 2; + if((*dst = (char*)malloc(len + 1)) != NULL) { + int n = b64dec(token->ptr, token->len, *dst); + (*dst)[n] = '\0'; + *(int*)info->user_data = n; + info->num_conversions++; + } +#endif /* JSON_ENABLE_BASE64 */ + break; + } + case 'T': + info->num_conversions++; + *(struct json_token*)info->target = *token; + break; + default: + if(token->len >= (int)sizeof(buf)) break; + /* Before converting, copy into tmp buffer in order to 0-terminate it */ + memcpy(buf, token->ptr, token->len); + buf[token->len] = '\0'; + /* NB: Use of base 0 for %d, %ld, %u and %lu is intentional. */ + if(info->fmt[1] == 'd' || (info->fmt[1] == 'l' && info->fmt[2] == 'd') || + info->fmt[1] == 'i') { + char* endptr = NULL; + long r = strtol(buf, &endptr, 0 /* base */); + if(*endptr == '\0') { + if(info->fmt[1] == 'l') { + *((long*)info->target) = r; + } else { + *((int*)info->target) = (int)r; + } + info->num_conversions++; + } + } else if(info->fmt[1] == 'u' || (info->fmt[1] == 'l' && info->fmt[2] == 'u')) { + char* endptr = NULL; + unsigned long r = strtoul(buf, &endptr, 0 /* base */); + if(*endptr == '\0') { + if(info->fmt[1] == 'l') { + *((unsigned long*)info->target) = r; + } else { + *((unsigned int*)info->target) = (unsigned int)r; + } + info->num_conversions++; + } + } else { +#if !JSON_MINIMAL + info->num_conversions += sscanf(buf, info->fmt, info->target); +#endif + } + break; + } +} + +int json_vscanf(const char* s, int len, const char* fmt, va_list ap) WEAK; +int json_vscanf(const char* s, int len, const char* fmt, va_list ap) { + char path[JSON_MAX_PATH_LEN] = "", fmtbuf[20]; + int i = 0; + char* p = NULL; + struct json_scanf_info info = {0, path, fmtbuf, NULL, NULL, 0}; + + while(fmt[i] != '\0') { + if(fmt[i] == '{') { + strcat(path, "."); + i++; + } else if(fmt[i] == '}') { + if((p = strrchr(path, '.')) != NULL) *p = '\0'; + i++; + } else if(fmt[i] == '%') { + info.target = va_arg(ap, void*); + info.type = fmt[i + 1]; + switch(fmt[i + 1]) { + case 'M': + case 'V': + case 'H': + info.user_data = va_arg(ap, void*); + /* FALLTHROUGH */ + case 'B': + case 'Q': + case 'T': + i += 2; + break; + default: { + const char* delims = ", \t\r\n]}"; + int conv_len = strcspn(fmt + i + 1, delims) + 1; + memcpy(fmtbuf, fmt + i, conv_len); + fmtbuf[conv_len] = '\0'; + i += conv_len; + i += strspn(fmt + i, delims); + break; + } + } + json_walk(s, len, json_scanf_cb, &info); + } else if(json_isalpha(fmt[i]) || json_get_utf8_char_len(fmt[i]) > 1) { + char* pe; + const char* delims = ": \r\n\t"; + int key_len = strcspn(&fmt[i], delims); + if((p = strrchr(path, '.')) != NULL) p[1] = '\0'; + pe = path + strlen(path); + memcpy(pe, fmt + i, key_len); + pe[key_len] = '\0'; + i += key_len + strspn(fmt + i + key_len, delims); + } else { + i++; + } + } + return info.num_conversions; +} + +int json_scanf(const char* str, int len, const char* fmt, ...) WEAK; +int json_scanf(const char* str, int len, const char* fmt, ...) { + int result; + va_list ap; + va_start(ap, fmt); + result = json_vscanf(str, len, fmt, ap); + va_end(ap); + return result; +} + +int json_vfprintf(const char* file_name, const char* fmt, va_list ap) WEAK; +int json_vfprintf(const char* file_name, const char* fmt, va_list ap) { + int res = -1; + FILE* fp = fopen(file_name, "wb"); + if(fp != NULL) { + struct json_out out = JSON_OUT_FILE(fp); + res = json_vprintf(&out, fmt, ap); + fputc('\n', fp); + fclose(fp); + } + return res; +} + +int json_fprintf(const char* file_name, const char* fmt, ...) WEAK; +int json_fprintf(const char* file_name, const char* fmt, ...) { + int result; + va_list ap; + va_start(ap, fmt); + result = json_vfprintf(file_name, fmt, ap); + va_end(ap); + return result; +} + +char* json_fread(const char* path) WEAK; +char* json_fread(const char* path) { + FILE* fp; + char* data = NULL; + if((fp = fopen(path, "rb")) == NULL) { + } else if(fseek(fp, 0, SEEK_END) != 0) { + fclose(fp); + } else { + long size = ftell(fp); + if(size > 0 && (data = (char*)malloc(size + 1)) != NULL) { + fseek(fp, 0, SEEK_SET); /* Some platforms might not have rewind(), Oo */ + if(fread(data, 1, size, fp) != (size_t)size) { + free(data); + data = NULL; + } else { + data[size] = '\0'; + } + } + fclose(fp); + } + return data; +} + +struct json_setf_data { + const char* json_path; + const char* base; /* Pointer to the source JSON string */ + int matched; /* Matched part of json_path */ + int pos; /* Offset of the mutated value begin */ + int end; /* Offset of the mutated value end */ + int prev; /* Offset of the previous token end */ +}; + +static int get_matched_prefix_len(const char* s1, const char* s2) { + int i = 0; + while(s1[i] && s2[i] && s1[i] == s2[i]) i++; + return i; +} + +static void json_vsetf_cb( + void* userdata, + const char* name, + size_t name_len, + const char* path, + const struct json_token* t) { + struct json_setf_data* data = (struct json_setf_data*)userdata; + int off, len = get_matched_prefix_len(path, data->json_path); + if(t->ptr == NULL) return; + off = t->ptr - data->base; + if(len > data->matched) data->matched = len; + + /* + * If there is no exact path match, set the mutation position to tbe end + * of the object or array + */ + if(len < data->matched && data->pos == 0 && + (t->type == JSON_TYPE_OBJECT_END || t->type == JSON_TYPE_ARRAY_END)) { + data->pos = data->end = data->prev; + } + + /* Exact path match. Set mutation position to the value of this token */ + if(strcmp(path, data->json_path) == 0 && t->type != JSON_TYPE_OBJECT_START && + t->type != JSON_TYPE_ARRAY_START) { + data->pos = off; + data->end = off + t->len; + } + + /* + * For deletion, we need to know where the previous value ends, because + * we don't know where matched value key starts. + * When the mutation position is not yet set, remember each value end. + * When the mutation position is already set, but it is at the beginning + * of the object/array, we catch the end of the object/array and see + * whether the object/array start is closer then previously stored prev. + */ + if(data->pos == 0) { + data->prev = off + t->len; /* pos is not yet set */ + } else if((t->ptr[0] == '[' || t->ptr[0] == '{') && off + 1 < data->pos && off + 1 > data->prev) { + data->prev = off + 1; + } + (void)name; + (void)name_len; +} + +int json_vsetf( + const char* s, + int len, + struct json_out* out, + const char* json_path, + const char* json_fmt, + va_list ap) WEAK; +int json_vsetf( + const char* s, + int len, + struct json_out* out, + const char* json_path, + const char* json_fmt, + va_list ap) { + struct json_setf_data data; + memset(&data, 0, sizeof(data)); + data.json_path = json_path; + data.base = s; + data.end = len; + json_walk(s, len, json_vsetf_cb, &data); + if(json_fmt == NULL) { + /* Deletion codepath */ + json_printf(out, "%.*s", data.prev, s); + /* Trim comma after the value that begins at object/array start */ + if(s[data.prev - 1] == '{' || s[data.prev - 1] == '[') { + int i = data.end; + while(i < len && json_isspace(s[i])) i++; + if(s[i] == ',') data.end = i + 1; /* Point after comma */ + } + json_printf(out, "%.*s", len - data.end, s + data.end); + } else { + /* Modification codepath */ + int n, off = data.matched, depth = 0; + + /* Print the unchanged beginning */ + json_printf(out, "%.*s", data.pos, s); + + /* Add missing keys */ + while((n = strcspn(&json_path[off], ".[")) > 0) { + if(s[data.prev - 1] != '{' && s[data.prev - 1] != '[' && depth == 0) { + json_printf(out, ","); + } + if(off > 0 && json_path[off - 1] != '.') break; + json_printf(out, "%.*Q:", n, json_path + off); + off += n; + if(json_path[off] != '\0') { + json_printf(out, "%c", json_path[off] == '.' ? '{' : '['); + depth++; + off++; + } + } + /* Print the new value */ + json_vprintf(out, json_fmt, ap); + + /* Close brackets/braces of the added missing keys */ + for(; off > data.matched; off--) { + int ch = json_path[off]; + const char* p = ch == '.' ? "}" : ch == '[' ? "]" : ""; + json_printf(out, "%s", p); + } + + /* Print the rest of the unchanged string */ + json_printf(out, "%.*s", len - data.end, s + data.end); + } + return data.end > data.pos ? 1 : 0; +} + +int json_setf( + const char* s, + int len, + struct json_out* out, + const char* json_path, + const char* json_fmt, + ...) WEAK; +int json_setf( + const char* s, + int len, + struct json_out* out, + const char* json_path, + const char* json_fmt, + ...) { + int result; + va_list ap; + va_start(ap, json_fmt); + result = json_vsetf(s, len, out, json_path, json_fmt, ap); + va_end(ap); + return result; +} + +struct prettify_data { + struct json_out* out; + int level; + int last_token; +}; + +static void indent(struct json_out* out, int level) { + while(level-- > 0) out->printer(out, " ", 2); +} + +static void print_key(struct prettify_data* pd, const char* path, const char* name, int name_len) { + if(pd->last_token != JSON_TYPE_INVALID && pd->last_token != JSON_TYPE_ARRAY_START && + pd->last_token != JSON_TYPE_OBJECT_START) { + pd->out->printer(pd->out, ",", 1); + } + if(path[0] != '\0') pd->out->printer(pd->out, "\n", 1); + indent(pd->out, pd->level); + if(path[0] != '\0' && path[strlen(path) - 1] != ']') { + pd->out->printer(pd->out, "\"", 1); + pd->out->printer(pd->out, name, (int)name_len); + pd->out->printer(pd->out, "\"", 1); + pd->out->printer(pd->out, ": ", 2); + } +} + +static void prettify_cb( + void* userdata, + const char* name, + size_t name_len, + const char* path, + const struct json_token* t) { + struct prettify_data* pd = (struct prettify_data*)userdata; + switch(t->type) { + case JSON_TYPE_OBJECT_START: + case JSON_TYPE_ARRAY_START: + print_key(pd, path, name, name_len); + pd->out->printer(pd->out, t->type == JSON_TYPE_ARRAY_START ? "[" : "{", 1); + pd->level++; + break; + case JSON_TYPE_OBJECT_END: + case JSON_TYPE_ARRAY_END: + pd->level--; + if(pd->last_token != JSON_TYPE_INVALID && pd->last_token != JSON_TYPE_ARRAY_START && + pd->last_token != JSON_TYPE_OBJECT_START) { + pd->out->printer(pd->out, "\n", 1); + indent(pd->out, pd->level); + } + pd->out->printer(pd->out, t->type == JSON_TYPE_ARRAY_END ? "]" : "}", 1); + break; + case JSON_TYPE_NUMBER: + case JSON_TYPE_NULL: + case JSON_TYPE_TRUE: + case JSON_TYPE_FALSE: + case JSON_TYPE_STRING: + print_key(pd, path, name, name_len); + if(t->type == JSON_TYPE_STRING) pd->out->printer(pd->out, "\"", 1); + pd->out->printer(pd->out, t->ptr, t->len); + if(t->type == JSON_TYPE_STRING) pd->out->printer(pd->out, "\"", 1); + break; + default: + break; + } + pd->last_token = t->type; +} + +int json_prettify(const char* s, int len, struct json_out* out) WEAK; +int json_prettify(const char* s, int len, struct json_out* out) { + struct prettify_data pd = {out, 0, JSON_TYPE_INVALID}; + return json_walk(s, len, prettify_cb, &pd); +} + +int json_prettify_file(const char* file_name) WEAK; +int json_prettify_file(const char* file_name) { + int res = -1; + char* s = json_fread(file_name); + FILE* fp; + if(s != NULL && (fp = fopen(file_name, "wb")) != NULL) { + struct json_out out = JSON_OUT_FILE(fp); + res = json_prettify(s, strlen(s), &out); + if(res < 0) { + /* On error, restore the old content */ + fclose(fp); + fp = fopen(file_name, "wb"); + fseek(fp, 0, SEEK_SET); + fwrite(s, 1, strlen(s), fp); + } else { + fputc('\n', fp); + } + fclose(fp); + } + free(s); + return res; +} + +struct next_data { + void* handle; // Passed handle. Changed if a next entry is found + const char* path; // Path to the iterated object/array + int path_len; // Path length - optimisation + int found; // Non-0 if found the next entry + struct json_token* key; // Object's key + struct json_token* val; // Object's value + int* idx; // Array index +}; + +static void next_set_key(struct next_data* d, const char* name, int name_len, int is_array) { + if(is_array) { + /* Array. Set index and reset key */ + if(d->key != NULL) { + d->key->len = 0; + d->key->ptr = NULL; + } + if(d->idx != NULL) *d->idx = atoi(name); + } else { + /* Object. Set key and make index -1 */ + if(d->key != NULL) { + d->key->ptr = name; + d->key->len = name_len; + } + if(d->idx != NULL) *d->idx = -1; + } +} + +static void json_next_cb( + void* userdata, + const char* name, + size_t name_len, + const char* path, + const struct json_token* t) { + struct next_data* d = (struct next_data*)userdata; + const char* p = path + d->path_len; + if(d->found) return; + if(d->path_len >= (int)strlen(path)) return; + if(strncmp(d->path, path, d->path_len) != 0) return; + if(strchr(p + 1, '.') != NULL) return; /* More nested objects - skip */ + if(strchr(p + 1, '[') != NULL) return; /* Ditto for arrays */ + // {OBJECT,ARRAY}_END types do not pass name, _START does. Save key. + if(t->type == JSON_TYPE_OBJECT_START || t->type == JSON_TYPE_ARRAY_START) { + next_set_key(d, name, name_len, p[0] == '['); + } else if(d->handle == NULL || d->handle < (void*)t->ptr) { + if(t->type != JSON_TYPE_OBJECT_END && t->type != JSON_TYPE_ARRAY_END) { + next_set_key(d, name, name_len, p[0] == '['); + } + if(d->val != NULL) *d->val = *t; + d->handle = (void*)t->ptr; + d->found = 1; + } +} + +static void* json_next( + const char* s, + int len, + void* handle, + const char* path, + struct json_token* key, + struct json_token* val, + int* i) { + struct json_token tmpval, *v = val == NULL ? &tmpval : val; + struct json_token tmpkey, *k = key == NULL ? &tmpkey : key; + int tmpidx, *pidx = i == NULL ? &tmpidx : i; + struct next_data data = {handle, path, (int)strlen(path), 0, k, v, pidx}; + json_walk(s, len, json_next_cb, &data); + return data.found ? data.handle : NULL; +} + +void* json_next_key( + const char* s, + int len, + void* handle, + const char* path, + struct json_token* key, + struct json_token* val) WEAK; +void* json_next_key( + const char* s, + int len, + void* handle, + const char* path, + struct json_token* key, + struct json_token* val) { + return json_next(s, len, handle, path, key, val, NULL); +} + +void* json_next_elem( + const char* s, + int len, + void* handle, + const char* path, + int* idx, + struct json_token* val) WEAK; +void* json_next_elem( + const char* s, + int len, + void* handle, + const char* path, + int* idx, + struct json_token* val) { + return json_next(s, len, handle, path, NULL, val, idx); +} + +static int json_sprinter(struct json_out* out, const char* str, size_t len) { + size_t old_len = out->u.buf.buf == NULL ? 0 : strlen(out->u.buf.buf); + size_t new_len = len + old_len; + char* p = (char*)realloc(out->u.buf.buf, new_len + 1); + if(p != NULL) { + memcpy(p + old_len, str, len); + p[new_len] = '\0'; + out->u.buf.buf = p; + } + return len; +} + +char* json_vasprintf(const char* fmt, va_list ap) WEAK; +char* json_vasprintf(const char* fmt, va_list ap) { + struct json_out out; + memset(&out, 0, sizeof(out)); + out.printer = json_sprinter; + json_vprintf(&out, fmt, ap); + return out.u.buf.buf; +} + +char* json_asprintf(const char* fmt, ...) WEAK; +char* json_asprintf(const char* fmt, ...) { + char* result = NULL; + va_list ap; + va_start(ap, fmt); + result = json_vasprintf(fmt, ap); + va_end(ap); + return result; +} diff --git a/applications/system/elk_mjs/lib/mjs/common/frozen/frozen.h b/applications/system/elk_mjs/lib/mjs/common/frozen/frozen.h new file mode 100644 index 00000000000..2eda5ae688f --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/common/frozen/frozen.h @@ -0,0 +1,359 @@ +/* + * Copyright (c) 2004-2013 Sergey Lyubka + * Copyright (c) 2018 Cesanta Software Limited + * All rights reserved + * + * Licensed under the Apache License, Version 2.0 (the ""License""); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an ""AS IS"" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CS_FROZEN_FROZEN_H_ +#define CS_FROZEN_FROZEN_H_ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#include +#include +#include + +#if defined(_WIN32) && _MSC_VER < 1700 +typedef int bool; +enum { false = 0, true = 1 }; +#else +#include +#endif + +/* JSON token type */ +enum json_token_type { + JSON_TYPE_INVALID = 0, /* memsetting to 0 should create INVALID value */ + JSON_TYPE_STRING, + JSON_TYPE_NUMBER, + JSON_TYPE_TRUE, + JSON_TYPE_FALSE, + JSON_TYPE_NULL, + JSON_TYPE_OBJECT_START, + JSON_TYPE_OBJECT_END, + JSON_TYPE_ARRAY_START, + JSON_TYPE_ARRAY_END, + + JSON_TYPES_CNT +}; + +/* + * Structure containing token type and value. Used in `json_walk()` and + * `json_scanf()` with the format specifier `%T`. + */ +struct json_token { + const char* ptr; /* Points to the beginning of the value */ + int len; /* Value length */ + enum json_token_type type; /* Type of the token, possible values are above */ +}; + +#define JSON_INVALID_TOKEN \ + { 0, 0, JSON_TYPE_INVALID } + +/* Error codes */ +#define JSON_STRING_INVALID -1 +#define JSON_STRING_INCOMPLETE -2 + +/* + * Callback-based SAX-like API. + * + * Property name and length is given only if it's available: i.e. if current + * event is an object's property. In other cases, `name` is `NULL`. For + * example, name is never given: + * - For the first value in the JSON string; + * - For events JSON_TYPE_OBJECT_END and JSON_TYPE_ARRAY_END + * + * E.g. for the input `{ "foo": 123, "bar": [ 1, 2, { "baz": true } ] }`, + * the sequence of callback invocations will be as follows: + * + * - type: JSON_TYPE_OBJECT_START, name: NULL, path: "", value: NULL + * - type: JSON_TYPE_NUMBER, name: "foo", path: ".foo", value: "123" + * - type: JSON_TYPE_ARRAY_START, name: "bar", path: ".bar", value: NULL + * - type: JSON_TYPE_NUMBER, name: "0", path: ".bar[0]", value: "1" + * - type: JSON_TYPE_NUMBER, name: "1", path: ".bar[1]", value: "2" + * - type: JSON_TYPE_OBJECT_START, name: "2", path: ".bar[2]", value: NULL + * - type: JSON_TYPE_TRUE, name: "baz", path: ".bar[2].baz", value: "true" + * - type: JSON_TYPE_OBJECT_END, name: NULL, path: ".bar[2]", value: "{ \"baz\": + *true }" + * - type: JSON_TYPE_ARRAY_END, name: NULL, path: ".bar", value: "[ 1, 2, { + *\"baz\": true } ]" + * - type: JSON_TYPE_OBJECT_END, name: NULL, path: "", value: "{ \"foo\": 123, + *\"bar\": [ 1, 2, { \"baz\": true } ] }" + */ +typedef void (*json_walk_callback_t)( + void* callback_data, + const char* name, + size_t name_len, + const char* path, + const struct json_token* token); + +/* + * Parse `json_string`, invoking `callback` in a way similar to SAX parsers; + * see `json_walk_callback_t`. + * Return number of processed bytes, or a negative error code. + */ +int json_walk( + const char* json_string, + int json_string_length, + json_walk_callback_t callback, + void* callback_data); + +/* + * JSON generation API. + * struct json_out abstracts output, allowing alternative printing plugins. + */ +struct json_out { + int (*printer)(struct json_out*, const char* str, size_t len); + union { + struct { + char* buf; + size_t size; + size_t len; + } buf; + void* data; + FILE* fp; + } u; +}; + +extern int json_printer_buf(struct json_out*, const char*, size_t); +extern int json_printer_file(struct json_out*, const char*, size_t); + +#define JSON_OUT_BUF(buf, len) \ + { \ + json_printer_buf, { \ + { buf, len, 0 } \ + } \ + } +#define JSON_OUT_FILE(fp) \ + { \ + json_printer_file, { \ + { (char*)fp, 0, 0 } \ + } \ + } + +typedef int (*json_printf_callback_t)(struct json_out*, va_list* ap); + +/* + * Generate formatted output into a given sting buffer. + * This is a superset of printf() function, with extra format specifiers: + * - `%B` print json boolean, `true` or `false`. Accepts an `int`. + * - `%Q` print quoted escaped string or `null`. Accepts a `const char *`. + * - `%.*Q` same as `%Q`, but with length. Accepts `int`, `const char *` + * - `%V` print quoted base64-encoded string. Accepts a `const char *`, `int`. + * - `%H` print quoted hex-encoded string. Accepts a `int`, `const char *`. + * - `%M` invokes a json_printf_callback_t function. That callback function + * can consume more parameters. + * + * Return number of bytes printed. If the return value is bigger than the + * supplied buffer, that is an indicator of overflow. In the overflow case, + * overflown bytes are not printed. + */ +int json_printf(struct json_out*, const char* fmt, ...); +int json_vprintf(struct json_out*, const char* fmt, va_list ap); + +/* + * Same as json_printf, but prints to a file. + * File is created if does not exist. File is truncated if already exists. + */ +int json_fprintf(const char* file_name, const char* fmt, ...); +int json_vfprintf(const char* file_name, const char* fmt, va_list ap); + +/* + * Print JSON into an allocated 0-terminated string. + * Return allocated string, or NULL on error. + * Example: + * + * ```c + * char *str = json_asprintf("{a:%H}", 3, "abc"); + * printf("%s\n", str); // Prints "616263" + * free(str); + * ``` + */ +char* json_asprintf(const char* fmt, ...); +char* json_vasprintf(const char* fmt, va_list ap); + +/* + * Helper %M callback that prints contiguous C arrays. + * Consumes void *array_ptr, size_t array_size, size_t elem_size, char *fmt + * Return number of bytes printed. + */ +int json_printf_array(struct json_out*, va_list* ap); + +/* + * Scan JSON string `str`, performing scanf-like conversions according to `fmt`. + * This is a `scanf()` - like function, with following differences: + * + * 1. Object keys in the format string may be not quoted, e.g. "{key: %d}" + * 2. Order of keys in an object is irrelevant. + * 3. Several extra format specifiers are supported: + * - %B: consumes `int *` (or `char *`, if `sizeof(bool) == sizeof(char)`), + * expects boolean `true` or `false`. + * - %Q: consumes `char **`, expects quoted, JSON-encoded string. Scanned + * string is malloc-ed, caller must free() the string. + * - %V: consumes `char **`, `int *`. Expects base64-encoded string. + * Result string is base64-decoded, malloced and NUL-terminated. + * The length of result string is stored in `int *` placeholder. + * Caller must free() the result. + * - %H: consumes `int *`, `char **`. + * Expects a hex-encoded string, e.g. "fa014f". + * Result string is hex-decoded, malloced and NUL-terminated. + * The length of the result string is stored in `int *` placeholder. + * Caller must free() the result. + * - %M: consumes custom scanning function pointer and + * `void *user_data` parameter - see json_scanner_t definition. + * - %T: consumes `struct json_token *`, fills it out with matched token. + * + * Return number of elements successfully scanned & converted. + * Negative number means scan error. + */ +int json_scanf(const char* str, int str_len, const char* fmt, ...); +int json_vscanf(const char* str, int str_len, const char* fmt, va_list ap); + +/* json_scanf's %M handler */ +typedef void (*json_scanner_t)(const char* str, int len, void* user_data); + +/* + * Helper function to scan array item with given path and index. + * Fills `token` with the matched JSON token. + * Return -1 if no array element found, otherwise non-negative token length. + */ +int json_scanf_array_elem( + const char* s, + int len, + const char* path, + int index, + struct json_token* token); + +/* + * Unescape JSON-encoded string src,slen into dst, dlen. + * src and dst may overlap. + * If destination buffer is too small (or zero-length), result string is not + * written but the length is counted nevertheless (similar to snprintf). + * Return the length of unescaped string in bytes. + */ +int json_unescape(const char* src, int slen, char* dst, int dlen); + +/* + * Escape a string `str`, `str_len` into the printer `out`. + * Return the number of bytes printed. + */ +int json_escape(struct json_out* out, const char* str, size_t str_len); + +/* + * Read the whole file in memory. + * Return malloc-ed file content, or NULL on error. The caller must free(). + */ +char* json_fread(const char* file_name); + +/* + * Update given JSON string `s,len` by changing the value at given `json_path`. + * The result is saved to `out`. If `json_fmt` == NULL, that deletes the key. + * If path is not present, missing keys are added. Array path without an + * index pushes a value to the end of an array. + * Return 1 if the string was changed, 0 otherwise. + * + * Example: s is a JSON string { "a": 1, "b": [ 2 ] } + * json_setf(s, len, out, ".a", "7"); // { "a": 7, "b": [ 2 ] } + * json_setf(s, len, out, ".b", "7"); // { "a": 1, "b": 7 } + * json_setf(s, len, out, ".b[]", "7"); // { "a": 1, "b": [ 2,7 ] } + * json_setf(s, len, out, ".b", NULL); // { "a": 1 } + */ +int json_setf( + const char* s, + int len, + struct json_out* out, + const char* json_path, + const char* json_fmt, + ...); + +int json_vsetf( + const char* s, + int len, + struct json_out* out, + const char* json_path, + const char* json_fmt, + va_list ap); + +/* + * Pretty-print JSON string `s,len` into `out`. + * Return number of processed bytes in `s`. + */ +int json_prettify(const char* s, int len, struct json_out* out); + +/* + * Prettify JSON file `file_name`. + * Return number of processed bytes, or negative number of error. + * On error, file content is not modified. + */ +int json_prettify_file(const char* file_name); + +/* + * Iterate over an object at given JSON `path`. + * On each iteration, fill the `key` and `val` tokens. It is OK to pass NULL + * for `key`, or `val`, in which case they won't be populated. + * Return an opaque value suitable for the next iteration, or NULL when done. + * + * Example: + * + * ```c + * void *h = NULL; + * struct json_token key, val; + * while ((h = json_next_key(s, len, h, ".foo", &key, &val)) != NULL) { + * printf("[%.*s] -> [%.*s]\n", key.len, key.ptr, val.len, val.ptr); + * } + * ``` + */ +void* json_next_key( + const char* s, + int len, + void* handle, + const char* path, + struct json_token* key, + struct json_token* val); + +/* + * Iterate over an array at given JSON `path`. + * Similar to `json_next_key`, but fills array index `idx` instead of `key`. + */ +void* json_next_elem( + const char* s, + int len, + void* handle, + const char* path, + int* idx, + struct json_token* val); + +#ifndef JSON_MAX_PATH_LEN +#define JSON_MAX_PATH_LEN 256 +#endif + +#ifndef JSON_MINIMAL +#define JSON_MINIMAL 0 +#endif + +#ifndef JSON_ENABLE_BASE64 +#define JSON_ENABLE_BASE64 !JSON_MINIMAL +#endif + +#ifndef JSON_ENABLE_HEX +#define JSON_ENABLE_HEX !JSON_MINIMAL +#endif + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* CS_FROZEN_FROZEN_H_ */ diff --git a/applications/system/elk_mjs/lib/mjs/common/mbuf.c b/applications/system/elk_mjs/lib/mjs/common/mbuf.c new file mode 100644 index 00000000000..458d8cca0f1 --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/common/mbuf.c @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2014-2018 Cesanta Software Limited + * All rights reserved + * + * Licensed under the Apache License, Version 2.0 (the ""License""); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an ""AS IS"" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef EXCLUDE_COMMON + +#include +#include +#include "mbuf.h" + +#ifndef MBUF_REALLOC +#define MBUF_REALLOC realloc +#endif + +#ifndef MBUF_FREE +#define MBUF_FREE free +#endif + +void mbuf_init(struct mbuf *mbuf, size_t initial_size) WEAK; +void mbuf_init(struct mbuf *mbuf, size_t initial_size) { + mbuf->len = mbuf->size = 0; + mbuf->buf = NULL; + mbuf_resize(mbuf, initial_size); +} + +void mbuf_free(struct mbuf *mbuf) WEAK; +void mbuf_free(struct mbuf *mbuf) { + if (mbuf->buf != NULL) { + MBUF_FREE(mbuf->buf); + mbuf_init(mbuf, 0); + } +} + +void mbuf_resize(struct mbuf *a, size_t new_size) WEAK; +void mbuf_resize(struct mbuf *a, size_t new_size) { + if (new_size > a->size || (new_size < a->size && new_size >= a->len)) { + char *buf = (char *) MBUF_REALLOC(a->buf, new_size); + /* + * In case realloc fails, there's not much we can do, except keep things as + * they are. Note that NULL is a valid return value from realloc when + * size == 0, but that is covered too. + */ + if (buf == NULL && new_size != 0) return; + a->buf = buf; + a->size = new_size; + } +} + +void mbuf_trim(struct mbuf *mbuf) WEAK; +void mbuf_trim(struct mbuf *mbuf) { + mbuf_resize(mbuf, mbuf->len); +} + +size_t mbuf_insert(struct mbuf *a, size_t off, const void *buf, size_t) WEAK; +size_t mbuf_insert(struct mbuf *a, size_t off, const void *buf, size_t len) { + char *p = NULL; + + assert(a != NULL); + assert(a->len <= a->size); + assert(off <= a->len); + + /* check overflow */ + if (~(size_t) 0 - (size_t) a->buf < len) return 0; + + if (a->len + len <= a->size) { + memmove(a->buf + off + len, a->buf + off, a->len - off); + if (buf != NULL) { + memcpy(a->buf + off, buf, len); + } + a->len += len; + } else { + size_t min_size = (a->len + len); + size_t new_size = (size_t)(min_size * MBUF_SIZE_MULTIPLIER); + if (new_size - min_size > MBUF_SIZE_MAX_HEADROOM) { + new_size = min_size + MBUF_SIZE_MAX_HEADROOM; + } + p = (char *) MBUF_REALLOC(a->buf, new_size); + if (p == NULL && new_size != min_size) { + new_size = min_size; + p = (char *) MBUF_REALLOC(a->buf, new_size); + } + if (p != NULL) { + a->buf = p; + if (off != a->len) { + memmove(a->buf + off + len, a->buf + off, a->len - off); + } + if (buf != NULL) memcpy(a->buf + off, buf, len); + a->len += len; + a->size = new_size; + } else { + len = 0; + } + } + + return len; +} + +size_t mbuf_append(struct mbuf *a, const void *buf, size_t len) WEAK; +size_t mbuf_append(struct mbuf *a, const void *buf, size_t len) { + return mbuf_insert(a, a->len, buf, len); +} + +size_t mbuf_append_and_free(struct mbuf *a, void *buf, size_t len) WEAK; +size_t mbuf_append_and_free(struct mbuf *a, void *data, size_t len) { + size_t ret; + /* Optimization: if the buffer is currently empty, + * take over the user-provided buffer. */ + if (a->len == 0) { + if (a->buf != NULL) free(a->buf); + a->buf = (char *) data; + a->len = a->size = len; + return len; + } + ret = mbuf_insert(a, a->len, data, len); + free(data); + return ret; +} + +void mbuf_remove(struct mbuf *mb, size_t n) WEAK; +void mbuf_remove(struct mbuf *mb, size_t n) { + if (n > 0 && n <= mb->len) { + memmove(mb->buf, mb->buf + n, mb->len - n); + mb->len -= n; + } +} + +void mbuf_clear(struct mbuf *mb) WEAK; +void mbuf_clear(struct mbuf *mb) { + mb->len = 0; +} + +void mbuf_move(struct mbuf *from, struct mbuf *to) WEAK; +void mbuf_move(struct mbuf *from, struct mbuf *to) { + memcpy(to, from, sizeof(*to)); + memset(from, 0, sizeof(*from)); +} + +#endif /* EXCLUDE_COMMON */ diff --git a/applications/system/elk_mjs/lib/mjs/common/mbuf.h b/applications/system/elk_mjs/lib/mjs/common/mbuf.h new file mode 100644 index 00000000000..0ca9dd7d174 --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/common/mbuf.h @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2014-2018 Cesanta Software Limited + * All rights reserved + * + * Licensed under the Apache License, Version 2.0 (the ""License""); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an ""AS IS"" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Mbufs are mutable/growing memory buffers, like C++ strings. + * Mbuf can append data to the end of a buffer or insert data into arbitrary + * position in the middle of a buffer. The buffer grows automatically when + * needed. + */ + +#ifndef CS_COMMON_MBUF_H_ +#define CS_COMMON_MBUF_H_ + +#include +#include "platform.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +#ifndef MBUF_SIZE_MULTIPLIER +#define MBUF_SIZE_MULTIPLIER 1.5 +#endif + +#ifndef MBUF_SIZE_MAX_HEADROOM +#ifdef BUFSIZ +#define MBUF_SIZE_MAX_HEADROOM BUFSIZ +#else +#define MBUF_SIZE_MAX_HEADROOM 1024 +#endif +#endif + +/* Memory buffer descriptor */ +struct mbuf { + char *buf; /* Buffer pointer */ + size_t len; /* Data length. Data is located between offset 0 and len. */ + size_t size; /* Buffer size allocated by realloc(1). Must be >= len */ +}; + +/* + * Initialises an Mbuf. + * `initial_capacity` specifies the initial capacity of the mbuf. + */ +void mbuf_init(struct mbuf *, size_t initial_capacity); + +/* Frees the space allocated for the mbuffer and resets the mbuf structure. */ +void mbuf_free(struct mbuf *); + +/* + * Appends data to the Mbuf. + * + * Returns the number of bytes appended or 0 if out of memory. + */ +size_t mbuf_append(struct mbuf *, const void *data, size_t data_size); + +/* + * Appends data to the Mbuf and frees it (data must be heap-allocated). + * + * Returns the number of bytes appended or 0 if out of memory. + * data is freed irrespective of return value. + */ +size_t mbuf_append_and_free(struct mbuf *, void *data, size_t data_size); + +/* + * Inserts data at a specified offset in the Mbuf. + * + * Existing data will be shifted forwards and the buffer will + * be grown if necessary. + * Returns the number of bytes inserted. + */ +size_t mbuf_insert(struct mbuf *, size_t, const void *, size_t); + +/* Removes `data_size` bytes from the beginning of the buffer. */ +void mbuf_remove(struct mbuf *, size_t data_size); + +/* + * Resizes an Mbuf. + * + * If `new_size` is smaller than buffer's `len`, the + * resize is not performed. + */ +void mbuf_resize(struct mbuf *, size_t new_size); + +/* Moves the state from one mbuf to the other. */ +void mbuf_move(struct mbuf *from, struct mbuf *to); + +/* Removes all the data from mbuf (if any). */ +void mbuf_clear(struct mbuf *); + +/* Shrinks an Mbuf by resizing its `size` to `len`. */ +void mbuf_trim(struct mbuf *); + +#if defined(__cplusplus) +} +#endif /* __cplusplus */ + +#endif /* CS_COMMON_MBUF_H_ */ diff --git a/applications/system/elk_mjs/lib/mjs/common/mg_mem.h b/applications/system/elk_mjs/lib/mjs/common/mg_mem.h new file mode 100644 index 00000000000..7fe8381d49e --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/common/mg_mem.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2014-2018 Cesanta Software Limited + * All rights reserved + * + * Licensed under the Apache License, Version 2.0 (the ""License""); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an ""AS IS"" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CS_COMMON_MG_MEM_H_ +#define CS_COMMON_MG_MEM_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef MG_MALLOC +#define MG_MALLOC malloc +#endif + +#ifndef MG_CALLOC +#define MG_CALLOC calloc +#endif + +#ifndef MG_REALLOC +#define MG_REALLOC realloc +#endif + +#ifndef MG_FREE +#define MG_FREE free +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* CS_COMMON_MG_MEM_H_ */ diff --git a/applications/system/elk_mjs/lib/mjs/common/mg_str.c b/applications/system/elk_mjs/lib/mjs/common/mg_str.c new file mode 100644 index 00000000000..3ce1f5d2a3b --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/common/mg_str.c @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2014-2018 Cesanta Software Limited + * All rights reserved + * + * Licensed under the Apache License, Version 2.0 (the ""License""); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an ""AS IS"" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "mg_mem.h" +#include "mg_str.h" +#include "platform.h" + +#include +#include +#include + +int mg_ncasecmp(const char* s1, const char* s2, size_t len) WEAK; + +struct mg_str mg_mk_str(const char* s) WEAK; +struct mg_str mg_mk_str(const char* s) { + struct mg_str ret = {s, 0}; + if(s != NULL) ret.len = strlen(s); + return ret; +} + +struct mg_str mg_mk_str_n(const char* s, size_t len) WEAK; +struct mg_str mg_mk_str_n(const char* s, size_t len) { + struct mg_str ret = {s, len}; + return ret; +} + +int mg_vcmp(const struct mg_str* str1, const char* str2) WEAK; +int mg_vcmp(const struct mg_str* str1, const char* str2) { + size_t n2 = strlen(str2), n1 = str1->len; + int r = strncmp(str1->p, str2, (n1 < n2) ? n1 : n2); + if(r == 0) { + return n1 - n2; + } + return r; +} + +int mg_vcasecmp(const struct mg_str* str1, const char* str2) WEAK; +int mg_vcasecmp(const struct mg_str* str1, const char* str2) { + size_t n2 = strlen(str2), n1 = str1->len; + int r = mg_ncasecmp(str1->p, str2, (n1 < n2) ? n1 : n2); + if(r == 0) { + return n1 - n2; + } + return r; +} + +static struct mg_str mg_strdup_common(const struct mg_str s, int nul_terminate) { + struct mg_str r = {NULL, 0}; + if(s.len > 0 && s.p != NULL) { + char* sc = (char*)MG_MALLOC(s.len + (nul_terminate ? 1 : 0)); + if(sc != NULL) { + memcpy(sc, s.p, s.len); + if(nul_terminate) sc[s.len] = '\0'; + r.p = sc; + r.len = s.len; + } + } + return r; +} + +struct mg_str mg_strdup(const struct mg_str s) WEAK; +struct mg_str mg_strdup(const struct mg_str s) { + return mg_strdup_common(s, 0 /* NUL-terminate */); +} + +struct mg_str mg_strdup_nul(const struct mg_str s) WEAK; +struct mg_str mg_strdup_nul(const struct mg_str s) { + return mg_strdup_common(s, 1 /* NUL-terminate */); +} + +const char* mg_strchr(const struct mg_str s, int c) WEAK; +const char* mg_strchr(const struct mg_str s, int c) { + size_t i; + for(i = 0; i < s.len; i++) { + if(s.p[i] == c) return &s.p[i]; + } + return NULL; +} + +int mg_strcmp(const struct mg_str str1, const struct mg_str str2) WEAK; +int mg_strcmp(const struct mg_str str1, const struct mg_str str2) { + size_t i = 0; + while(i < str1.len && i < str2.len) { + int c1 = str1.p[i]; + int c2 = str2.p[i]; + if(c1 < c2) return -1; + if(c1 > c2) return 1; + i++; + } + if(i < str1.len) return 1; + if(i < str2.len) return -1; + return 0; +} + +int mg_strncmp(const struct mg_str, const struct mg_str, size_t n) WEAK; +int mg_strncmp(const struct mg_str str1, const struct mg_str str2, size_t n) { + struct mg_str s1 = str1; + struct mg_str s2 = str2; + + if(s1.len > n) { + s1.len = n; + } + if(s2.len > n) { + s2.len = n; + } + return mg_strcmp(s1, s2); +} + +int mg_strcasecmp(const struct mg_str str1, const struct mg_str str2) WEAK; +int mg_strcasecmp(const struct mg_str str1, const struct mg_str str2) { + size_t i = 0; + while(i < str1.len && i < str2.len) { + int c1 = tolower((int)str1.p[i]); + int c2 = tolower((int)str2.p[i]); + if(c1 < c2) return -1; + if(c1 > c2) return 1; + i++; + } + if(i < str1.len) return 1; + if(i < str2.len) return -1; + return 0; +} + +void mg_strfree(struct mg_str* s) WEAK; +void mg_strfree(struct mg_str* s) { + char* sp = (char*)s->p; + s->p = NULL; + s->len = 0; + if(sp != NULL) free(sp); +} + +const char* mg_strstr(const struct mg_str haystack, const struct mg_str needle) WEAK; +const char* mg_strstr(const struct mg_str haystack, const struct mg_str needle) { + size_t i; + if(needle.len > haystack.len) return NULL; + for(i = 0; i <= haystack.len - needle.len; i++) { + if(memcmp(haystack.p + i, needle.p, needle.len) == 0) { + return haystack.p + i; + } + } + return NULL; +} + +struct mg_str mg_strstrip(struct mg_str s) WEAK; +struct mg_str mg_strstrip(struct mg_str s) { + while(s.len > 0 && isspace((int)*s.p)) { + s.p++; + s.len--; + } + while(s.len > 0 && isspace((int)*(s.p + s.len - 1))) { + s.len--; + } + return s; +} + +int mg_str_starts_with(struct mg_str s, struct mg_str prefix) WEAK; +int mg_str_starts_with(struct mg_str s, struct mg_str prefix) { + const struct mg_str sp = MG_MK_STR_N(s.p, prefix.len); + if(s.len < prefix.len) return 0; + return (mg_strcmp(sp, prefix) == 0); +} diff --git a/applications/system/elk_mjs/lib/mjs/common/mg_str.h b/applications/system/elk_mjs/lib/mjs/common/mg_str.h new file mode 100644 index 00000000000..f32f2464784 --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/common/mg_str.h @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2014-2018 Cesanta Software Limited + * All rights reserved + * + * Licensed under the Apache License, Version 2.0 (the ""License""); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an ""AS IS"" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CS_COMMON_MG_STR_H_ +#define CS_COMMON_MG_STR_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Describes chunk of memory */ +struct mg_str { + const char *p; /* Memory chunk pointer */ + size_t len; /* Memory chunk length */ +}; + +/* + * Helper function for creating mg_str struct from plain C string. + * `NULL` is allowed and becomes `{NULL, 0}`. + */ +struct mg_str mg_mk_str(const char *s); + +/* + * Like `mg_mk_str`, but takes string length explicitly. + */ +struct mg_str mg_mk_str_n(const char *s, size_t len); + +/* Macro for initializing mg_str. */ +#define MG_MK_STR(str_literal) \ + { str_literal, sizeof(str_literal) - 1 } +#define MG_MK_STR_N(str_literal, len) \ + { str_literal, len } +#define MG_NULL_STR \ + { NULL, 0 } + +/* + * Cross-platform version of `strcmp()` where where first string is + * specified by `struct mg_str`. + */ +int mg_vcmp(const struct mg_str *str2, const char *str1); + +/* + * Cross-platform version of `strncasecmp()` where first string is + * specified by `struct mg_str`. + */ +int mg_vcasecmp(const struct mg_str *str2, const char *str1); + +/* Creates a copy of s (heap-allocated). */ +struct mg_str mg_strdup(const struct mg_str s); + +/* + * Creates a copy of s (heap-allocated). + * Resulting string is NUL-terminated (but NUL is not included in len). + */ +struct mg_str mg_strdup_nul(const struct mg_str s); + +/* + * Locates character in a string. + */ +const char *mg_strchr(const struct mg_str s, int c); + +/* + * Compare two `mg_str`s; return value is the same as `strcmp`. + */ +int mg_strcmp(const struct mg_str str1, const struct mg_str str2); + +/* + * Like `mg_strcmp`, but compares at most `n` characters. + */ +int mg_strncmp(const struct mg_str str1, const struct mg_str str2, size_t n); + +/* + * Compare two `mg_str`s ignoreing case; return value is the same as `strcmp`. + */ +int mg_strcasecmp(const struct mg_str str1, const struct mg_str str2); + +/* + * Free the string (assuming it was heap allocated). + */ +void mg_strfree(struct mg_str *s); + +/* + * Finds the first occurrence of a substring `needle` in the `haystack`. + */ +const char *mg_strstr(const struct mg_str haystack, const struct mg_str needle); + +/* Strip whitespace at the start and the end of s */ +struct mg_str mg_strstrip(struct mg_str s); + +/* Returns 1 if s starts with the given prefix. */ +int mg_str_starts_with(struct mg_str s, struct mg_str prefix); + +#ifdef __cplusplus +} +#endif + +#endif /* CS_COMMON_MG_STR_H_ */ diff --git a/applications/system/elk_mjs/lib/mjs/common/platform.h b/applications/system/elk_mjs/lib/mjs/common/platform.h new file mode 100644 index 00000000000..c963a3ba56b --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/common/platform.h @@ -0,0 +1,123 @@ +#ifndef CS_COMMON_PLATFORM_H_ +#define CS_COMMON_PLATFORM_H_ + +/* + * For the "custom" platform, includes and dependencies can be + * provided through mg_locals.h. + */ +#define CS_P_CUSTOM 0 +#define CS_P_UNIX 1 +#define CS_P_WINDOWS 2 +#define CS_P_ESP32 15 +#define CS_P_ESP8266 3 +#define CS_P_CC3100 6 +#define CS_P_CC3200 4 +#define CS_P_CC3220 17 +#define CS_P_MSP432 5 +#define CS_P_TM4C129 14 +#define CS_P_MBED 7 +#define CS_P_WINCE 8 +#define CS_P_NXP_LPC 13 +#define CS_P_NXP_KINETIS 9 +#define CS_P_NRF51 12 +#define CS_P_NRF52 10 +#define CS_P_PIC32 11 +#define CS_P_RS14100 18 +#define CS_P_STM32 16 +/* Next id: 19 */ + +/* If not specified explicitly, we guess platform by defines. */ +#ifndef CS_PLATFORM + +#if defined(TARGET_IS_MSP432P4XX) || defined(__MSP432P401R__) +#define CS_PLATFORM CS_P_MSP432 +#elif defined(cc3200) || defined(TARGET_IS_CC3200) +#define CS_PLATFORM CS_P_CC3200 +#elif defined(cc3220) || defined(TARGET_IS_CC3220) +#define CS_PLATFORM CS_P_CC3220 +#elif defined(__unix__) || defined(__APPLE__) +#define CS_PLATFORM CS_P_UNIX +#elif defined(WINCE) +#define CS_PLATFORM CS_P_WINCE +#elif defined(_WIN32) +#define CS_PLATFORM CS_P_WINDOWS +#elif defined(__MBED__) +#define CS_PLATFORM CS_P_MBED +#elif defined(__USE_LPCOPEN) +#define CS_PLATFORM CS_P_NXP_LPC +#elif defined(FRDM_K64F) || defined(FREEDOM) +#define CS_PLATFORM CS_P_NXP_KINETIS +#elif defined(PIC32) +#define CS_PLATFORM CS_P_PIC32 +#elif defined(ESP_PLATFORM) +#define CS_PLATFORM CS_P_ESP32 +#elif defined(ICACHE_FLASH) +#define CS_PLATFORM CS_P_ESP8266 +#elif defined(TARGET_IS_TM4C129_RA0) || defined(TARGET_IS_TM4C129_RA1) || \ + defined(TARGET_IS_TM4C129_RA2) +#define CS_PLATFORM CS_P_TM4C129 +#elif defined(RS14100) +#define CS_PLATFORM CS_P_RS14100 +#elif defined(STM32) +#define CS_PLATFORM CS_P_STM32 +#endif + +#ifndef CS_PLATFORM +#error "CS_PLATFORM is not specified and we couldn't guess it." +#endif + +#endif /* !defined(CS_PLATFORM) */ + +#define MG_NET_IF_SOCKET 1 +#define MG_NET_IF_SIMPLELINK 2 +#define MG_NET_IF_LWIP_LOW_LEVEL 3 +#define MG_NET_IF_PIC32 4 +#define MG_NET_IF_NULL 5 + +#define MG_SSL_IF_OPENSSL 1 +#define MG_SSL_IF_MBEDTLS 2 +#define MG_SSL_IF_SIMPLELINK 3 + +#include "platforms/platform_stm32.h" +#if CS_PLATFORM == CS_P_CUSTOM +#include +#endif + +/* Common stuff */ + +#if !defined(PRINTF_LIKE) +#if defined(__GNUC__) || defined(__clang__) || defined(__TI_COMPILER_VERSION__) +#define PRINTF_LIKE(f, a) __attribute__((format(printf, f, a))) +#else +#define PRINTF_LIKE(f, a) +#endif +#endif + +#if !defined(WEAK) +#if(defined(__GNUC__) || defined(__clang__) || defined(__TI_COMPILER_VERSION__)) && \ + !defined(_WIN32) +#define WEAK __attribute__((weak)) +#else +#define WEAK +#endif +#endif + +#ifdef __GNUC__ +#define NORETURN __attribute__((noreturn)) +#define NOINLINE __attribute__((noinline)) +#define WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +#define NOINSTR __attribute__((no_instrument_function)) +#define DO_NOT_WARN_UNUSED __attribute__((unused)) +#else +#define NORETURN +#define NOINLINE +#define WARN_UNUSED_RESULT +#define NOINSTR +#define DO_NOT_WARN_UNUSED +#endif /* __GNUC__ */ + +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0])) +#endif + +#endif /* CS_COMMON_PLATFORM_H_ */ \ No newline at end of file diff --git a/applications/system/elk_mjs/lib/mjs/common/platforms/platform_stm32.h b/applications/system/elk_mjs/lib/mjs/common/platforms/platform_stm32.h new file mode 100644 index 00000000000..8f6bbcb5a9c --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/common/platforms/platform_stm32.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2014-2018 Cesanta Software Limited + * All rights reserved + * + * Licensed under the Apache License, Version 2.0 (the ""License""); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an ""AS IS"" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CS_COMMON_PLATFORMS_PLATFORM_STM32_H_ +#define CS_COMMON_PLATFORMS_PLATFORM_STM32_H_ +#if CS_PLATFORM == CS_P_STM32 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +// #include + +// #include + +#define to64(x) strtoll(x, NULL, 10) +#define INT64_FMT "lld" +#define SIZE_T_FMT "u" +typedef struct stat cs_stat_t; +#define DIRSEP '/' + +#ifndef CS_ENABLE_STDIO +#define CS_ENABLE_STDIO 0 +#endif + +#ifndef MG_ENABLE_FILESYSTEM +#define MG_ENABLE_FILESYSTEM 0 +#endif + +#endif /* CS_PLATFORM == CS_P_STM32 */ +#endif /* CS_COMMON_PLATFORMS_PLATFORM_STM32_H_ */ \ No newline at end of file diff --git a/applications/system/elk_mjs/lib/mjs/common/str_util.c b/applications/system/elk_mjs/lib/mjs/common/str_util.c new file mode 100644 index 00000000000..889e9560adc --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/common/str_util.c @@ -0,0 +1,537 @@ +/* + * Copyright (c) 2014-2018 Cesanta Software Limited + * All rights reserved + * + * Licensed under the Apache License, Version 2.0 (the ""License""); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an ""AS IS"" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef EXCLUDE_COMMON + +#include "str_util.h" +#include "mg_mem.h" +#include "platform.h" + +#ifndef C_DISABLE_BUILTIN_SNPRINTF +#define C_DISABLE_BUILTIN_SNPRINTF 0 +#endif + +#include "mg_mem.h" + +size_t c_strnlen(const char* s, size_t maxlen) WEAK; +size_t c_strnlen(const char* s, size_t maxlen) { + size_t l = 0; + for(; l < maxlen && s[l] != '\0'; l++) { + } + return l; +} + +#define C_SNPRINTF_APPEND_CHAR(ch) \ + do { \ + if(i < (int)buf_size) buf[i] = ch; \ + i++; \ + } while(0) + +#define C_SNPRINTF_FLAG_ZERO 1 + +#if C_DISABLE_BUILTIN_SNPRINTF +int c_vsnprintf(char* buf, size_t buf_size, const char* fmt, va_list ap) WEAK; +int c_vsnprintf(char* buf, size_t buf_size, const char* fmt, va_list ap) { + return vsnprintf(buf, buf_size, fmt, ap); +} +#else +static int c_itoa(char* buf, size_t buf_size, int64_t num, int base, int flags, int field_width) { + char tmp[40]; + int i = 0, k = 0, neg = 0; + + if(num < 0) { + neg++; + num = -num; + } + + /* Print into temporary buffer - in reverse order */ + do { + int rem = num % base; + if(rem < 10) { + tmp[k++] = '0' + rem; + } else { + tmp[k++] = 'a' + (rem - 10); + } + num /= base; + } while(num > 0); + + /* Zero padding */ + if(flags && C_SNPRINTF_FLAG_ZERO) { + while(k < field_width && k < (int)sizeof(tmp) - 1) { + tmp[k++] = '0'; + } + } + + /* And sign */ + if(neg) { + tmp[k++] = '-'; + } + + /* Now output */ + while(--k >= 0) { + C_SNPRINTF_APPEND_CHAR(tmp[k]); + } + + return i; +} + +int c_vsnprintf(char* buf, size_t buf_size, const char* fmt, va_list ap) WEAK; +int c_vsnprintf(char* buf, size_t buf_size, const char* fmt, va_list ap) { + int ch, i = 0, len_mod, flags, precision, field_width; + + while((ch = *fmt++) != '\0') { + if(ch != '%') { + C_SNPRINTF_APPEND_CHAR(ch); + } else { + /* + * Conversion specification: + * zero or more flags (one of: # 0 - + ') + * an optional minimum field width (digits) + * an optional precision (. followed by digits, or *) + * an optional length modifier (one of: hh h l ll L q j z t) + * conversion specifier (one of: d i o u x X e E f F g G a A c s p n) + */ + flags = field_width = precision = len_mod = 0; + + /* Flags. only zero-pad flag is supported. */ + if(*fmt == '0') { + flags |= C_SNPRINTF_FLAG_ZERO; + } + + /* Field width */ + while(*fmt >= '0' && *fmt <= '9') { + field_width *= 10; + field_width += *fmt++ - '0'; + } + /* Dynamic field width */ + if(*fmt == '*') { + field_width = va_arg(ap, int); + fmt++; + } + + /* Precision */ + if(*fmt == '.') { + fmt++; + if(*fmt == '*') { + precision = va_arg(ap, int); + fmt++; + } else { + while(*fmt >= '0' && *fmt <= '9') { + precision *= 10; + precision += *fmt++ - '0'; + } + } + } + + /* Length modifier */ + switch(*fmt) { + case 'h': + case 'l': + case 'L': + case 'I': + case 'q': + case 'j': + case 'z': + case 't': + len_mod = *fmt++; + if(*fmt == 'h') { + len_mod = 'H'; + fmt++; + } + if(*fmt == 'l') { + len_mod = 'q'; + fmt++; + } + break; + } + + ch = *fmt++; + if(ch == 's') { + const char* s = va_arg(ap, const char*); /* Always fetch parameter */ + int j; + int pad = field_width - (precision >= 0 ? c_strnlen(s, precision) : 0); + for(j = 0; j < pad; j++) { + C_SNPRINTF_APPEND_CHAR(' '); + } + + /* `s` may be NULL in case of %.*s */ + if(s != NULL) { + /* Ignore negative and 0 precisions */ + for(j = 0; (precision <= 0 || j < precision) && s[j] != '\0'; j++) { + C_SNPRINTF_APPEND_CHAR(s[j]); + } + } + } else if(ch == 'c') { + ch = va_arg(ap, int); /* Always fetch parameter */ + C_SNPRINTF_APPEND_CHAR(ch); + } else if(ch == 'd' && len_mod == 0) { + i += c_itoa(buf + i, buf_size - i, va_arg(ap, int), 10, flags, field_width); + } else if(ch == 'd' && len_mod == 'l') { + i += c_itoa(buf + i, buf_size - i, va_arg(ap, long), 10, flags, field_width); +#ifdef SSIZE_MAX + } else if(ch == 'd' && len_mod == 'z') { + i += c_itoa(buf + i, buf_size - i, va_arg(ap, ssize_t), 10, flags, field_width); +#endif + } else if(ch == 'd' && len_mod == 'q') { + i += c_itoa(buf + i, buf_size - i, va_arg(ap, int64_t), 10, flags, field_width); + } else if((ch == 'x' || ch == 'u') && len_mod == 0) { + i += c_itoa( + buf + i, + buf_size - i, + va_arg(ap, unsigned), + ch == 'x' ? 16 : 10, + flags, + field_width); + } else if((ch == 'x' || ch == 'u') && len_mod == 'l') { + i += c_itoa( + buf + i, + buf_size - i, + va_arg(ap, unsigned long), + ch == 'x' ? 16 : 10, + flags, + field_width); + } else if((ch == 'x' || ch == 'u') && len_mod == 'z') { + i += c_itoa( + buf + i, + buf_size - i, + va_arg(ap, size_t), + ch == 'x' ? 16 : 10, + flags, + field_width); + } else if(ch == 'p') { + unsigned long num = (unsigned long)(uintptr_t)va_arg(ap, void*); + C_SNPRINTF_APPEND_CHAR('0'); + C_SNPRINTF_APPEND_CHAR('x'); + i += c_itoa(buf + i, buf_size - i, num, 16, flags, 0); + } else { +#ifndef NO_LIBC + /* + * TODO(lsm): abort is not nice in a library, remove it + * Also, ESP8266 SDK doesn't have it + */ + abort(); +#endif + } + } + } + + /* Zero-terminate the result */ + if(buf_size > 0) { + buf[i < (int)buf_size ? i : (int)buf_size - 1] = '\0'; + } + + return i; +} +#endif + +int c_snprintf(char* buf, size_t buf_size, const char* fmt, ...) WEAK; +int c_snprintf(char* buf, size_t buf_size, const char* fmt, ...) { + int result; + va_list ap; + va_start(ap, fmt); + result = c_vsnprintf(buf, buf_size, fmt, ap); + va_end(ap); + return result; +} + +#ifdef _WIN32 +int to_wchar(const char* path, wchar_t* wbuf, size_t wbuf_len) { + int ret; + char buf[MAX_PATH * 2], buf2[MAX_PATH * 2], *p; + + strncpy(buf, path, sizeof(buf)); + buf[sizeof(buf) - 1] = '\0'; + + /* Trim trailing slashes. Leave backslash for paths like "X:\" */ + p = buf + strlen(buf) - 1; + while(p > buf && p[-1] != ':' && (p[0] == '\\' || p[0] == '/')) *p-- = '\0'; + + memset(wbuf, 0, wbuf_len * sizeof(wchar_t)); + ret = MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, (int)wbuf_len); + + /* + * Convert back to Unicode. If doubly-converted string does not match the + * original, something is fishy, reject. + */ + WideCharToMultiByte(CP_UTF8, 0, wbuf, (int)wbuf_len, buf2, sizeof(buf2), NULL, NULL); + if(strcmp(buf, buf2) != 0) { + wbuf[0] = L'\0'; + ret = 0; + } + + return ret; +} +#endif /* _WIN32 */ + +/* The simplest O(mn) algorithm. Better implementation are GPLed */ +const char* c_strnstr(const char* s, const char* find, size_t slen) WEAK; +const char* c_strnstr(const char* s, const char* find, size_t slen) { + size_t find_length = strlen(find); + size_t i; + + for(i = 0; i < slen; i++) { + if(i + find_length > slen) { + return NULL; + } + + if(strncmp(&s[i], find, find_length) == 0) { + return &s[i]; + } + } + + return NULL; +} + +#if CS_ENABLE_STRDUP +char* strdup(const char* src) WEAK; +char* strdup(const char* src) { + size_t len = strlen(src) + 1; + char* ret = MG_MALLOC(len); + if(ret != NULL) { + strcpy(ret, src); + } + return ret; +} +#endif + +void cs_to_hex(char* to, const unsigned char* p, size_t len) WEAK; +void cs_to_hex(char* to, const unsigned char* p, size_t len) { + static const char* hex = "0123456789abcdef"; + + for(; len--; p++) { + *to++ = hex[p[0] >> 4]; + *to++ = hex[p[0] & 0x0f]; + } + *to = '\0'; +} + +static int fourbit(int ch) { + if(ch >= '0' && ch <= '9') { + return ch - '0'; + } else if(ch >= 'a' && ch <= 'f') { + return ch - 'a' + 10; + } else if(ch >= 'A' && ch <= 'F') { + return ch - 'A' + 10; + } + return 0; +} + +void cs_from_hex(char* to, const char* p, size_t len) WEAK; +void cs_from_hex(char* to, const char* p, size_t len) { + size_t i; + + for(i = 0; i < len; i += 2) { + *to++ = (fourbit(p[i]) << 4) + fourbit(p[i + 1]); + } + *to = '\0'; +} + +#if CS_ENABLE_TO64 +int64_t cs_to64(const char* s) WEAK; +int64_t cs_to64(const char* s) { + int64_t result = 0; + int64_t neg = 1; + while(*s && isspace((unsigned char)*s)) s++; + if(*s == '-') { + neg = -1; + s++; + } + while(isdigit((unsigned char)*s)) { + result *= 10; + result += (*s - '0'); + s++; + } + return result * neg; +} +#endif + +static int str_util_lowercase(const char* s) { + return tolower(*(const unsigned char*)s); +} + +int mg_ncasecmp(const char* s1, const char* s2, size_t len) WEAK; +int mg_ncasecmp(const char* s1, const char* s2, size_t len) { + int diff = 0; + + if(len > 0) do { + diff = str_util_lowercase(s1++) - str_util_lowercase(s2++); + } while(diff == 0 && s1[-1] != '\0' && --len > 0); + + return diff; +} + +int mg_casecmp(const char* s1, const char* s2) WEAK; +int mg_casecmp(const char* s1, const char* s2) { + return mg_ncasecmp(s1, s2, (size_t)~0); +} + +int mg_asprintf(char** buf, size_t size, const char* fmt, ...) WEAK; +int mg_asprintf(char** buf, size_t size, const char* fmt, ...) { + int ret; + va_list ap; + va_start(ap, fmt); + ret = mg_avprintf(buf, size, fmt, ap); + va_end(ap); + return ret; +} + +int mg_avprintf(char** buf, size_t size, const char* fmt, va_list ap) WEAK; +int mg_avprintf(char** buf, size_t size, const char* fmt, va_list ap) { + va_list ap_copy; + int len; + + va_copy(ap_copy, ap); + len = vsnprintf(*buf, size, fmt, ap_copy); + va_end(ap_copy); + + if(len < 0) { + /* eCos and Windows are not standard-compliant and return -1 when + * the buffer is too small. Keep allocating larger buffers until we + * succeed or out of memory. */ + *buf = NULL; /* LCOV_EXCL_START */ + while(len < 0) { + MG_FREE(*buf); + if(size == 0) { + size = 5; + } + size *= 2; + if((*buf = (char*)MG_MALLOC(size)) == NULL) { + len = -1; + break; + } + va_copy(ap_copy, ap); + len = vsnprintf(*buf, size - 1, fmt, ap_copy); + va_end(ap_copy); + } + + /* + * Microsoft version of vsnprintf() is not always null-terminated, so put + * the terminator manually + */ + (*buf)[len] = 0; + /* LCOV_EXCL_STOP */ + } else if(len >= (int)size) { + /* Standard-compliant code path. Allocate a buffer that is large enough. */ + if((*buf = (char*)MG_MALLOC(len + 1)) == NULL) { + len = -1; /* LCOV_EXCL_LINE */ + } else { /* LCOV_EXCL_LINE */ + va_copy(ap_copy, ap); + len = vsnprintf(*buf, len + 1, fmt, ap_copy); + va_end(ap_copy); + } + } + + return len; +} + +const char* mg_next_comma_list_entry(const char*, struct mg_str*, struct mg_str*) WEAK; +const char* mg_next_comma_list_entry(const char* list, struct mg_str* val, struct mg_str* eq_val) { + struct mg_str ret = mg_next_comma_list_entry_n(mg_mk_str(list), val, eq_val); + return ret.p; +} + +struct mg_str + mg_next_comma_list_entry_n(struct mg_str list, struct mg_str* val, struct mg_str* eq_val) WEAK; +struct mg_str + mg_next_comma_list_entry_n(struct mg_str list, struct mg_str* val, struct mg_str* eq_val) { + if(list.len == 0) { + /* End of the list */ + list = mg_mk_str(NULL); + } else { + const char* chr = NULL; + *val = list; + + if((chr = mg_strchr(*val, ',')) != NULL) { + /* Comma found. Store length and shift the list ptr */ + val->len = chr - val->p; + chr++; + list.len -= (chr - list.p); + list.p = chr; + } else { + /* This value is the last one */ + list = mg_mk_str_n(list.p + list.len, 0); + } + + if(eq_val != NULL) { + /* Value has form "x=y", adjust pointers and lengths */ + /* so that val points to "x", and eq_val points to "y". */ + eq_val->len = 0; + eq_val->p = (const char*)memchr(val->p, '=', val->len); + if(eq_val->p != NULL) { + eq_val->p++; /* Skip over '=' character */ + eq_val->len = val->p + val->len - eq_val->p; + val->len = (eq_val->p - val->p) - 1; + } + } + } + + return list; +} + +size_t mg_match_prefix_n(const struct mg_str, const struct mg_str) WEAK; +size_t mg_match_prefix_n(const struct mg_str pattern, const struct mg_str str) { + const char* or_str; + size_t res = 0, len = 0, i = 0, j = 0; + + if((or_str = (const char*)memchr(pattern.p, '|', pattern.len)) != NULL || + (or_str = (const char*)memchr(pattern.p, ',', pattern.len)) != NULL) { + struct mg_str pstr = {pattern.p, (size_t)(or_str - pattern.p)}; + res = mg_match_prefix_n(pstr, str); + if(res > 0) return res; + pstr.p = or_str + 1; + pstr.len = (pattern.p + pattern.len) - (or_str + 1); + return mg_match_prefix_n(pstr, str); + } + + for(; i < pattern.len && j < str.len; i++, j++) { + if(pattern.p[i] == '?') { + continue; + } else if(pattern.p[i] == '*') { + i++; + if(i < pattern.len && pattern.p[i] == '*') { + i++; + len = str.len - j; + } else { + len = 0; + while(j + len < str.len && str.p[j + len] != '/') len++; + } + if(i == pattern.len || (pattern.p[i] == '$' && i == pattern.len - 1)) return j + len; + do { + const struct mg_str pstr = {pattern.p + i, pattern.len - i}; + const struct mg_str sstr = {str.p + j + len, str.len - j - len}; + res = mg_match_prefix_n(pstr, sstr); + } while(res == 0 && len != 0 && len-- > 0); + return res == 0 ? 0 : j + res + len; + } else if(str_util_lowercase(&pattern.p[i]) != str_util_lowercase(&str.p[j])) { + break; + } + } + if(i < pattern.len && pattern.p[i] == '$') { + return j == str.len ? str.len : 0; + } + return i == pattern.len ? j : 0; +} + +size_t mg_match_prefix(const char*, int, const char*) WEAK; +size_t mg_match_prefix(const char* pattern, int pattern_len, const char* str) { + const struct mg_str pstr = {pattern, (size_t)pattern_len}; + struct mg_str s = {str, 0}; + if(str != NULL) s.len = strlen(str); + return mg_match_prefix_n(pstr, s); +} + +#endif /* EXCLUDE_COMMON */ diff --git a/applications/system/elk_mjs/lib/mjs/common/str_util.h b/applications/system/elk_mjs/lib/mjs/common/str_util.h new file mode 100644 index 00000000000..13873b52c4f --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/common/str_util.h @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2014-2018 Cesanta Software Limited + * All rights reserved + * + * Licensed under the Apache License, Version 2.0 (the ""License""); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an ""AS IS"" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CS_COMMON_STR_UTIL_H_ +#define CS_COMMON_STR_UTIL_H_ + +#include +#include + +#include "mg_str.h" +#include "platform.h" + +#ifndef CS_ENABLE_STRDUP +#define CS_ENABLE_STRDUP 0 +#endif + +#ifndef CS_ENABLE_TO64 +#define CS_ENABLE_TO64 0 +#endif + +/* + * Expands to a string representation of its argument: e.g. + * `CS_STRINGIFY_LIT(5) expands to "5"` + */ +#if !defined(_MSC_VER) || _MSC_VER >= 1900 +#define CS_STRINGIFY_LIT(...) #__VA_ARGS__ +#else +#define CS_STRINGIFY_LIT(x) #x +#endif + +/* + * Expands to a string representation of its argument, which is allowed + * to be a macro: e.g. + * + * #define FOO 123 + * CS_STRINGIFY_MACRO(FOO) + * + * expands to 123. + */ +#define CS_STRINGIFY_MACRO(x) CS_STRINGIFY_LIT(x) + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Equivalent of standard `strnlen()`. + */ +size_t c_strnlen(const char* s, size_t maxlen); + +/* + * Equivalent of standard `snprintf()`. + */ +int c_snprintf(char* buf, size_t buf_size, const char* format, ...) PRINTF_LIKE(3, 4); + +/* + * Equivalent of standard `vsnprintf()`. + */ +int c_vsnprintf(char* buf, size_t buf_size, const char* format, va_list ap); + +/* + * Find the first occurrence of find in s, where the search is limited to the + * first slen characters of s. + */ +const char* c_strnstr(const char* s, const char* find, size_t slen); + +/* + * Stringify binary data. Output buffer size must be 2 * size_of_input + 1 + * because each byte of input takes 2 bytes in string representation + * plus 1 byte for the terminating \0 character. + */ +void cs_to_hex(char* to, const unsigned char* p, size_t len); + +/* + * Convert stringified binary data back to binary. + * Does the reverse of `cs_to_hex()`. + */ +void cs_from_hex(char* to, const char* p, size_t len); + +#if CS_ENABLE_STRDUP +/* + * Equivalent of standard `strdup()`, defined if only `CS_ENABLE_STRDUP` is 1. + */ +char* strdup(const char* src); +#endif + +#if CS_ENABLE_TO64 +#include +/* + * Simple string -> int64 conversion routine. + */ +int64_t cs_to64(const char* s); +#endif + +/* + * Cross-platform version of `strncasecmp()`. + */ +int mg_ncasecmp(const char* s1, const char* s2, size_t len); + +/* + * Cross-platform version of `strcasecmp()`. + */ +int mg_casecmp(const char* s1, const char* s2); + +/* + * Prints message to the buffer. If the buffer is large enough to hold the + * message, it returns buffer. If buffer is to small, it allocates a large + * enough buffer on heap and returns allocated buffer. + * This is a supposed use case: + * + * ```c + * char buf[5], *p = buf; + * mg_avprintf(&p, sizeof(buf), "%s", "hi there"); + * use_p_somehow(p); + * if (p != buf) { + * free(p); + * } + * ``` + * + * The purpose of this is to avoid malloc-ing if generated strings are small. + */ +int mg_asprintf(char** buf, size_t size, const char* fmt, ...) PRINTF_LIKE(3, 4); + +/* Same as mg_asprintf, but takes varargs list. */ +int mg_avprintf(char** buf, size_t size, const char* fmt, va_list ap); + +/* + * A helper function for traversing a comma separated list of values. + * It returns a list pointer shifted to the next value or NULL if the end + * of the list found. + * The value is stored in a val vector. If the value has a form "x=y", then + * eq_val vector is initialised to point to the "y" part, and val vector length + * is adjusted to point only to "x". + * If the list is just a comma separated list of entries, like "aa,bb,cc" then + * `eq_val` will contain zero-length string. + * + * The purpose of this function is to parse comma separated string without + * any copying/memory allocation. + */ +const char* mg_next_comma_list_entry(const char* list, struct mg_str* val, struct mg_str* eq_val); + +/* + * Like `mg_next_comma_list_entry()`, but takes `list` as `struct mg_str`. + * NB: Test return value's .p, not .len. On last itreation that yields result + * .len will be 0 but .p will not. When finished, .p will be NULL. + */ +struct mg_str + mg_next_comma_list_entry_n(struct mg_str list, struct mg_str* val, struct mg_str* eq_val); + +/* + * Matches 0-terminated string (mg_match_prefix) or string with given length + * mg_match_prefix_n against a glob pattern. Glob syntax: + * ``` + * - * matches zero or more characters until a slash character / + * - ** matches zero or more characters + * - ? Matches exactly one character which is not a slash / + * - | or , divides alternative patterns + * - any other character matches itself + * ``` + * Match is case-insensitive. Return number of bytes matched. + * Examples: + * ``` + * mg_match_prefix("a*f", len, "abcdefgh") == 6 + * mg_match_prefix("a*f", len, "abcdexgh") == 0 + * mg_match_prefix("a*f|de*,xy", len, "defgh") == 5 + * mg_match_prefix("?*", len, "abc") == 3 + * mg_match_prefix("?*", len, "") == 0 + * ``` + */ +size_t mg_match_prefix(const char* pattern, int pattern_len, const char* str); + +/* + * Like `mg_match_prefix()`, but takes `pattern` and `str` as `struct mg_str`. + */ +size_t mg_match_prefix_n(const struct mg_str pattern, const struct mg_str str); + +#ifdef __cplusplus +} +#endif + +#endif /* CS_COMMON_STR_UTIL_H_ */ diff --git a/applications/system/elk_mjs/lib/mjs/ffi/ffi.c b/applications/system/elk_mjs/lib/mjs/ffi/ffi.c new file mode 100644 index 00000000000..19d0981aeb0 --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/ffi/ffi.c @@ -0,0 +1,553 @@ +/* + * Copyright (c) 2016 Cesanta Software Limited + * All rights reserved + */ + +#include "ffi.h" + +#define IS_W(arg) ((arg).ctype == FFI_CTYPE_WORD) +#define IS_D(arg) ((arg).ctype == FFI_CTYPE_DOUBLE) +#define IS_F(arg) ((arg).ctype == FFI_CTYPE_FLOAT) + +#define W(arg) ((ffi_word_t)(arg).v.i) +#define D(arg) ((arg).v.d) +#define F(arg) ((arg).v.f) + +void ffi_set_word(struct ffi_arg* arg, ffi_word_t v) { + arg->ctype = FFI_CTYPE_WORD; + arg->v.i = v; +} + +void ffi_set_bool(struct ffi_arg* arg, bool v) { + arg->ctype = FFI_CTYPE_BOOL; + arg->v.i = v; +} + +void ffi_set_ptr(struct ffi_arg* arg, void* v) { + ffi_set_word(arg, (ffi_word_t)v); +} + +void ffi_set_double(struct ffi_arg* arg, double v) { + arg->ctype = FFI_CTYPE_DOUBLE; + arg->v.d = v; +} + +void ffi_set_float(struct ffi_arg* arg, float v) { + arg->ctype = FFI_CTYPE_FLOAT; + arg->v.f = v; +} + +/* + * The ARM ABI uses only 4 32-bit registers for paramter passing. + * Xtensa call0 calling-convention (as used by Espressif) has 6. + * + * Focusing only on implementing FFI with registers means we can simplify a lot. + * + * ARM has some quasi-alignment rules when mixing double and integers as + * arguments. Only: + * a) double, int32_t, int32_t + * b) int32_t, double + * would fit in 4 registers. (the same goes for uint64_t). + * + * In order to simplify further, when a double-width argument is present, we + * allow only two arguments. + */ + +/* + * We need to support x86_64 in order to support local tests. + * x86_64 has more and wider registers, but unlike the two main + * embedded platforms we target it has a separate register file for + * integer values and for floating point values (both for passing args and + * return values). E.g. if a double value is passed as a second argument + * it gets passed in the first available floating point register. + * + * I.e, the compiler generates exactly the same code for: + * + * void foo(int a, double b) {...} + * + * and + * + * void foo(double b, int a) {...} + * + * + */ + +typedef ffi_word_t (*w4w_t)(ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t); +typedef ffi_word_t (*w5w_t)(ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t); +typedef ffi_word_t (*w6w_t)(ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t); + +typedef ffi_word_t (*wdw_t)(double, ffi_word_t); +typedef ffi_word_t (*wwd_t)(ffi_word_t, double); +typedef ffi_word_t (*wdd_t)(double, double); + +typedef ffi_word_t (*wwwd_t)(ffi_word_t, ffi_word_t, double); +typedef ffi_word_t (*wwdw_t)(ffi_word_t, double, ffi_word_t); +typedef ffi_word_t (*wwdd_t)(ffi_word_t, double, double); +typedef ffi_word_t (*wdww_t)(double, ffi_word_t, ffi_word_t); +typedef ffi_word_t (*wdwd_t)(double, ffi_word_t, double); +typedef ffi_word_t (*wddw_t)(double, double, ffi_word_t); +typedef ffi_word_t (*wddd_t)(double, double, double); + +typedef ffi_word_t (*wfw_t)(float, ffi_word_t); +typedef ffi_word_t (*wwf_t)(ffi_word_t, float); +typedef ffi_word_t (*wff_t)(float, float); + +typedef ffi_word_t (*wwwf_t)(ffi_word_t, ffi_word_t, float); +typedef ffi_word_t (*wwfw_t)(ffi_word_t, float, ffi_word_t); +typedef ffi_word_t (*wwff_t)(ffi_word_t, float, float); +typedef ffi_word_t (*wfww_t)(float, ffi_word_t, ffi_word_t); +typedef ffi_word_t (*wfwf_t)(float, ffi_word_t, float); +typedef ffi_word_t (*wffw_t)(float, float, ffi_word_t); +typedef ffi_word_t (*wfff_t)(float, float, float); + +typedef bool (*b4w_t)(ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t); +typedef bool (*b5w_t)(ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t); +typedef bool (*b6w_t)(ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t); +typedef bool (*bdw_t)(double, ffi_word_t); +typedef bool (*bwd_t)(ffi_word_t, double); +typedef bool (*bdd_t)(double, double); + +typedef bool (*bwwd_t)(ffi_word_t, ffi_word_t, double); +typedef bool (*bwdw_t)(ffi_word_t, double, ffi_word_t); +typedef bool (*bwdd_t)(ffi_word_t, double, double); +typedef bool (*bdww_t)(double, ffi_word_t, ffi_word_t); +typedef bool (*bdwd_t)(double, ffi_word_t, double); +typedef bool (*bddw_t)(double, double, ffi_word_t); +typedef bool (*bddd_t)(double, double, double); + +typedef bool (*bfw_t)(float, ffi_word_t); +typedef bool (*bwf_t)(ffi_word_t, float); +typedef bool (*bff_t)(float, float); + +typedef bool (*bwwf_t)(ffi_word_t, ffi_word_t, float); +typedef bool (*bwfw_t)(ffi_word_t, float, ffi_word_t); +typedef bool (*bwff_t)(ffi_word_t, float, float); +typedef bool (*bfww_t)(float, ffi_word_t, ffi_word_t); +typedef bool (*bfwf_t)(float, ffi_word_t, float); +typedef bool (*bffw_t)(float, float, ffi_word_t); +typedef bool (*bfff_t)(float, float, float); + +typedef double (*d4w_t)(ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t); +typedef double (*d5w_t)(ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t); +typedef double (*d6w_t)(ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t); +typedef double (*ddw_t)(double, ffi_word_t); +typedef double (*dwd_t)(ffi_word_t, double); +typedef double (*ddd_t)(double, double); + +typedef double (*dwwd_t)(ffi_word_t, ffi_word_t, double); +typedef double (*dwdw_t)(ffi_word_t, double, ffi_word_t); +typedef double (*dwdd_t)(ffi_word_t, double, double); +typedef double (*ddww_t)(double, ffi_word_t, ffi_word_t); +typedef double (*ddwd_t)(double, ffi_word_t, double); +typedef double (*dddw_t)(double, double, ffi_word_t); +typedef double (*dddd_t)(double, double, double); + +typedef float (*f4w_t)(ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t); +typedef float (*f5w_t)(ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t); +typedef float (*f6w_t)(ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t); +typedef float (*ffw_t)(float, ffi_word_t); +typedef float (*fwf_t)(ffi_word_t, float); +typedef float (*fff_t)(float, float); + +typedef float (*fwwf_t)(ffi_word_t, ffi_word_t, float); +typedef float (*fwfw_t)(ffi_word_t, float, ffi_word_t); +typedef float (*fwff_t)(ffi_word_t, float, float); +typedef float (*ffww_t)(float, ffi_word_t, ffi_word_t); +typedef float (*ffwf_t)(float, ffi_word_t, float); +typedef float (*fffw_t)(float, float, ffi_word_t); +typedef float (*ffff_t)(float, float, float); + +int ffi_call_mjs(ffi_fn_t* func, int nargs, struct ffi_arg* res, struct ffi_arg* args) { + int i, doubles = 0, floats = 0; + + if(nargs > 6) return -1; + for(i = 0; i < nargs; i++) { + doubles += (IS_D(args[i])); + floats += (IS_F(args[i])); + } + + /* Doubles and floats are not supported together atm */ + if(doubles > 0 && floats > 0) { + return -1; + } + + switch(res->ctype) { + case FFI_CTYPE_WORD: { /* {{{ */ + ffi_word_t r; + if(doubles == 0) { + if(floats == 0) { + /* + * No double and no float args: we currently support up to 6 + * word-sized arguments + */ + if(nargs <= 4) { + w4w_t f = (w4w_t)func; + r = f(W(args[0]), W(args[1]), W(args[2]), W(args[3])); + } else if(nargs == 5) { + w5w_t f = (w5w_t)func; + r = f(W(args[0]), W(args[1]), W(args[2]), W(args[3]), W(args[4])); + } else if(nargs == 6) { + w6w_t f = (w6w_t)func; + r = f(W(args[0]), W(args[1]), W(args[2]), W(args[3]), W(args[4]), W(args[5])); + } else { + abort(); + } + } else { + /* There are some floats */ + switch(nargs) { + case 0: + case 1: + case 2: + if(IS_F(args[0]) && IS_F(args[1])) { + wff_t f = (wff_t)func; + r = f(F(args[0]), F(args[1])); + } else if(IS_F(args[0])) { + wfw_t f = (wfw_t)func; + r = f(F(args[0]), W(args[1])); + } else { + wwf_t f = (wwf_t)func; + r = f(W(args[0]), F(args[1])); + } + break; + + case 3: + if(IS_W(args[0]) && IS_W(args[1]) && IS_F(args[2])) { + wwwf_t f = (wwwf_t)func; + r = f(W(args[0]), W(args[1]), F(args[2])); + } else if(IS_W(args[0]) && IS_F(args[1]) && IS_W(args[2])) { + wwfw_t f = (wwfw_t)func; + r = f(W(args[0]), F(args[1]), W(args[2])); + } else if(IS_W(args[0]) && IS_F(args[1]) && IS_F(args[2])) { + wwff_t f = (wwff_t)func; + r = f(W(args[0]), F(args[1]), F(args[2])); + } else if(IS_F(args[0]) && IS_W(args[1]) && IS_W(args[2])) { + wfww_t f = (wfww_t)func; + r = f(F(args[0]), W(args[1]), W(args[2])); + } else if(IS_F(args[0]) && IS_W(args[1]) && IS_F(args[2])) { + wfwf_t f = (wfwf_t)func; + r = f(F(args[0]), W(args[1]), F(args[2])); + } else if(IS_F(args[0]) && IS_F(args[1]) && IS_W(args[2])) { + wffw_t f = (wffw_t)func; + r = f(F(args[0]), F(args[1]), W(args[2])); + } else if(IS_F(args[0]) && IS_F(args[1]) && IS_F(args[2])) { + wfff_t f = (wfff_t)func; + r = f(F(args[0]), F(args[1]), F(args[2])); + } else { + // The above checks should be exhaustive + abort(); + } + break; + default: + return -1; + } + } + } else { + /* There are some doubles */ + switch(nargs) { + case 0: + case 1: + case 2: + if(IS_D(args[0]) && IS_D(args[1])) { + wdd_t f = (wdd_t)func; + r = f(D(args[0]), D(args[1])); + } else if(IS_D(args[0])) { + wdw_t f = (wdw_t)func; + r = f(D(args[0]), W(args[1])); + } else { + wwd_t f = (wwd_t)func; + r = f(W(args[0]), D(args[1])); + } + break; + + case 3: + if(IS_W(args[0]) && IS_W(args[1]) && IS_D(args[2])) { + wwwd_t f = (wwwd_t)func; + r = f(W(args[0]), W(args[1]), D(args[2])); + } else if(IS_W(args[0]) && IS_D(args[1]) && IS_W(args[2])) { + wwdw_t f = (wwdw_t)func; + r = f(W(args[0]), D(args[1]), W(args[2])); + } else if(IS_W(args[0]) && IS_D(args[1]) && IS_D(args[2])) { + wwdd_t f = (wwdd_t)func; + r = f(W(args[0]), D(args[1]), D(args[2])); + } else if(IS_D(args[0]) && IS_W(args[1]) && IS_W(args[2])) { + wdww_t f = (wdww_t)func; + r = f(D(args[0]), W(args[1]), W(args[2])); + } else if(IS_D(args[0]) && IS_W(args[1]) && IS_D(args[2])) { + wdwd_t f = (wdwd_t)func; + r = f(D(args[0]), W(args[1]), D(args[2])); + } else if(IS_D(args[0]) && IS_D(args[1]) && IS_W(args[2])) { + wddw_t f = (wddw_t)func; + r = f(D(args[0]), D(args[1]), W(args[2])); + } else if(IS_D(args[0]) && IS_D(args[1]) && IS_D(args[2])) { + wddd_t f = (wddd_t)func; + r = f(D(args[0]), D(args[1]), D(args[2])); + } else { + // The above checks should be exhaustive + abort(); + } + break; + default: + return -1; + } + } + res->v.i = (uint64_t)r; + } break; /* }}} */ + case FFI_CTYPE_BOOL: { /* {{{ */ + ffi_word_t r; + if(doubles == 0) { + if(floats == 0) { + /* + * No double and no float args: we currently support up to 6 + * word-sized arguments + */ + if(nargs <= 4) { + b4w_t f = (b4w_t)func; + r = f(W(args[0]), W(args[1]), W(args[2]), W(args[3])); + } else if(nargs == 5) { + b5w_t f = (b5w_t)func; + r = f(W(args[0]), W(args[1]), W(args[2]), W(args[3]), W(args[4])); + } else if(nargs == 6) { + b6w_t f = (b6w_t)func; + r = f(W(args[0]), W(args[1]), W(args[2]), W(args[3]), W(args[4]), W(args[5])); + } else { + abort(); + } + } else { + /* There are some floats */ + switch(nargs) { + case 0: + case 1: + case 2: + if(IS_F(args[0]) && IS_F(args[1])) { + bff_t f = (bff_t)func; + r = f(F(args[0]), F(args[1])); + } else if(IS_F(args[0])) { + bfw_t f = (bfw_t)func; + r = f(F(args[0]), W(args[1])); + } else { + bwf_t f = (bwf_t)func; + r = f(W(args[0]), F(args[1])); + } + break; + + case 3: + if(IS_W(args[0]) && IS_W(args[1]) && IS_F(args[2])) { + bwwf_t f = (bwwf_t)func; + r = f(W(args[0]), W(args[1]), F(args[2])); + } else if(IS_W(args[0]) && IS_F(args[1]) && IS_W(args[2])) { + bwfw_t f = (bwfw_t)func; + r = f(W(args[0]), F(args[1]), W(args[2])); + } else if(IS_W(args[0]) && IS_F(args[1]) && IS_F(args[2])) { + bwff_t f = (bwff_t)func; + r = f(W(args[0]), F(args[1]), F(args[2])); + } else if(IS_F(args[0]) && IS_W(args[1]) && IS_W(args[2])) { + bfww_t f = (bfww_t)func; + r = f(F(args[0]), W(args[1]), W(args[2])); + } else if(IS_F(args[0]) && IS_W(args[1]) && IS_F(args[2])) { + bfwf_t f = (bfwf_t)func; + r = f(F(args[0]), W(args[1]), F(args[2])); + } else if(IS_F(args[0]) && IS_F(args[1]) && IS_W(args[2])) { + bffw_t f = (bffw_t)func; + r = f(F(args[0]), F(args[1]), W(args[2])); + } else if(IS_F(args[0]) && IS_F(args[1]) && IS_F(args[2])) { + bfff_t f = (bfff_t)func; + r = f(F(args[0]), F(args[1]), F(args[2])); + } else { + // The above checks should be exhaustive + abort(); + } + break; + default: + return -1; + } + } + } else { + /* There are some doubles */ + switch(nargs) { + case 0: + case 1: + case 2: + if(IS_D(args[0]) && IS_D(args[1])) { + bdd_t f = (bdd_t)func; + r = f(D(args[0]), D(args[1])); + } else if(IS_D(args[0])) { + bdw_t f = (bdw_t)func; + r = f(D(args[0]), W(args[1])); + } else { + bwd_t f = (bwd_t)func; + r = f(W(args[0]), D(args[1])); + } + break; + + case 3: + if(IS_W(args[0]) && IS_W(args[1]) && IS_D(args[2])) { + bwwd_t f = (bwwd_t)func; + r = f(W(args[0]), W(args[1]), D(args[2])); + } else if(IS_W(args[0]) && IS_D(args[1]) && IS_W(args[2])) { + bwdw_t f = (bwdw_t)func; + r = f(W(args[0]), D(args[1]), W(args[2])); + } else if(IS_W(args[0]) && IS_D(args[1]) && IS_D(args[2])) { + bwdd_t f = (bwdd_t)func; + r = f(W(args[0]), D(args[1]), D(args[2])); + } else if(IS_D(args[0]) && IS_W(args[1]) && IS_W(args[2])) { + bdww_t f = (bdww_t)func; + r = f(D(args[0]), W(args[1]), W(args[2])); + } else if(IS_D(args[0]) && IS_W(args[1]) && IS_D(args[2])) { + bdwd_t f = (bdwd_t)func; + r = f(D(args[0]), W(args[1]), D(args[2])); + } else if(IS_D(args[0]) && IS_D(args[1]) && IS_W(args[2])) { + bddw_t f = (bddw_t)func; + r = f(D(args[0]), D(args[1]), W(args[2])); + } else if(IS_D(args[0]) && IS_D(args[1]) && IS_D(args[2])) { + bddd_t f = (bddd_t)func; + r = f(D(args[0]), D(args[1]), D(args[2])); + } else { + // The above checks should be exhaustive + abort(); + } + break; + default: + return -1; + } + } + res->v.i = (uint64_t)r; + } break; /* }}} */ + case FFI_CTYPE_DOUBLE: { /* {{{ */ + double r; + if(doubles == 0) { + /* No double args: we currently support up to 6 word-sized arguments + */ + if(nargs <= 4) { + d4w_t f = (d4w_t)func; + r = f(W(args[0]), W(args[1]), W(args[2]), W(args[3])); + } else if(nargs == 5) { + d5w_t f = (d5w_t)func; + r = f(W(args[0]), W(args[1]), W(args[2]), W(args[3]), W(args[4])); + } else if(nargs == 6) { + d6w_t f = (d6w_t)func; + r = f(W(args[0]), W(args[1]), W(args[2]), W(args[3]), W(args[4]), W(args[5])); + } else { + abort(); + } + } else { + switch(nargs) { + case 0: + case 1: + case 2: + if(IS_D(args[0]) && IS_D(args[1])) { + ddd_t f = (ddd_t)func; + r = f(D(args[0]), D(args[1])); + } else if(IS_D(args[0])) { + ddw_t f = (ddw_t)func; + r = f(D(args[0]), W(args[1])); + } else { + dwd_t f = (dwd_t)func; + r = f(W(args[0]), D(args[1])); + } + break; + + case 3: + if(IS_W(args[0]) && IS_W(args[1]) && IS_D(args[2])) { + dwwd_t f = (dwwd_t)func; + r = f(W(args[0]), W(args[1]), D(args[2])); + } else if(IS_W(args[0]) && IS_D(args[1]) && IS_W(args[2])) { + dwdw_t f = (dwdw_t)func; + r = f(W(args[0]), D(args[1]), W(args[2])); + } else if(IS_W(args[0]) && IS_D(args[1]) && IS_D(args[2])) { + dwdd_t f = (dwdd_t)func; + r = f(W(args[0]), D(args[1]), D(args[2])); + } else if(IS_D(args[0]) && IS_W(args[1]) && IS_W(args[2])) { + ddww_t f = (ddww_t)func; + r = f(D(args[0]), W(args[1]), W(args[2])); + } else if(IS_D(args[0]) && IS_W(args[1]) && IS_D(args[2])) { + ddwd_t f = (ddwd_t)func; + r = f(D(args[0]), W(args[1]), D(args[2])); + } else if(IS_D(args[0]) && IS_D(args[1]) && IS_W(args[2])) { + dddw_t f = (dddw_t)func; + r = f(D(args[0]), D(args[1]), W(args[2])); + } else if(IS_D(args[0]) && IS_D(args[1]) && IS_D(args[2])) { + dddd_t f = (dddd_t)func; + r = f(D(args[0]), D(args[1]), D(args[2])); + } else { + // The above checks should be exhaustive + abort(); + } + break; + default: + return -1; + } + } + res->v.d = r; + } break; /* }}} */ + case FFI_CTYPE_FLOAT: { /* {{{ */ + double r; + if(floats == 0) { + /* No float args: we currently support up to 6 word-sized arguments + */ + if(nargs <= 4) { + f4w_t f = (f4w_t)func; + r = f(W(args[0]), W(args[1]), W(args[2]), W(args[3])); + } else if(nargs == 5) { + f5w_t f = (f5w_t)func; + r = f(W(args[0]), W(args[1]), W(args[2]), W(args[3]), W(args[4])); + } else if(nargs == 6) { + f6w_t f = (f6w_t)func; + r = f(W(args[0]), W(args[1]), W(args[2]), W(args[3]), W(args[4]), W(args[5])); + } else { + abort(); + } + } else { + /* There are some float args */ + switch(nargs) { + case 0: + case 1: + case 2: + if(IS_F(args[0]) && IS_F(args[1])) { + fff_t f = (fff_t)func; + r = f(F(args[0]), F(args[1])); + } else if(IS_F(args[0])) { + ffw_t f = (ffw_t)func; + r = f(F(args[0]), W(args[1])); + } else { + fwf_t f = (fwf_t)func; + r = f(W(args[0]), F(args[1])); + } + break; + + case 3: + if(IS_W(args[0]) && IS_W(args[1]) && IS_F(args[2])) { + fwwf_t f = (fwwf_t)func; + r = f(W(args[0]), W(args[1]), F(args[2])); + } else if(IS_W(args[0]) && IS_F(args[1]) && IS_W(args[2])) { + fwfw_t f = (fwfw_t)func; + r = f(W(args[0]), F(args[1]), W(args[2])); + } else if(IS_W(args[0]) && IS_F(args[1]) && IS_F(args[2])) { + fwff_t f = (fwff_t)func; + r = f(W(args[0]), F(args[1]), F(args[2])); + } else if(IS_F(args[0]) && IS_W(args[1]) && IS_W(args[2])) { + ffww_t f = (ffww_t)func; + r = f(F(args[0]), W(args[1]), W(args[2])); + } else if(IS_F(args[0]) && IS_W(args[1]) && IS_F(args[2])) { + ffwf_t f = (ffwf_t)func; + r = f(F(args[0]), W(args[1]), F(args[2])); + } else if(IS_F(args[0]) && IS_F(args[1]) && IS_W(args[2])) { + fffw_t f = (fffw_t)func; + r = f(F(args[0]), F(args[1]), W(args[2])); + } else if(IS_F(args[0]) && IS_F(args[1]) && IS_F(args[2])) { + ffff_t f = (ffff_t)func; + r = f(F(args[0]), F(args[1]), F(args[2])); + } else { + // The above checks should be exhaustive + abort(); + } + break; + default: + return -1; + } + } + res->v.f = r; + } break; /* }}} */ + } + + return 0; +} diff --git a/applications/system/elk_mjs/lib/mjs/ffi/ffi.h b/applications/system/elk_mjs/lib/mjs/ffi/ffi.h new file mode 100644 index 00000000000..2543841ecac --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/ffi/ffi.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2016 Cesanta Software Limited + * All rights reserved + */ + +#ifndef MJS_FFI_FFI_H_ +#define MJS_FFI_FFI_H_ + +#include "../common/platform.h" + +#if defined(__cplusplus) +extern "C" { +#endif /* __cplusplus */ + +/* + * Maximum number of word-sized args to ffi-ed function. If at least one + * of the args is double, only 2 args are allowed. + */ +#define FFI_MAX_ARGS_CNT 6 + +typedef void(ffi_fn_t)(void); + +typedef intptr_t ffi_word_t; + +enum ffi_ctype { + FFI_CTYPE_WORD, + FFI_CTYPE_BOOL, + FFI_CTYPE_FLOAT, + FFI_CTYPE_DOUBLE, +}; + +struct ffi_arg { + enum ffi_ctype ctype; + union { + uint64_t i; + double d; + float f; + } v; +}; + +int ffi_call_mjs(ffi_fn_t* func, int nargs, struct ffi_arg* res, struct ffi_arg* args); + +void ffi_set_word(struct ffi_arg* arg, ffi_word_t v); +void ffi_set_bool(struct ffi_arg* arg, bool v); +void ffi_set_ptr(struct ffi_arg* arg, void* v); +void ffi_set_double(struct ffi_arg* arg, double v); +void ffi_set_float(struct ffi_arg* arg, float v); + +#if defined(__cplusplus) +} +#endif /* __cplusplus */ + +#endif /* MJS_FFI_FFI_H_ */ diff --git a/applications/system/elk_mjs/lib/mjs/mjs_array.c b/applications/system/elk_mjs/lib/mjs/mjs_array.c new file mode 100644 index 00000000000..a29514afd1b --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/mjs_array.c @@ -0,0 +1,233 @@ +/* + * Copyright (c) 2017 Cesanta Software Limited + * All rights reserved + */ + +#include +#include "common/str_util.h" +#include "mjs_array.h" +#include "mjs_conversion.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" + +#define SPLICE_NEW_ITEM_IDX 2 + +/* like c_snprintf but returns `size` if write is truncated */ +static int v_sprintf_s(char* buf, size_t size, const char* fmt, ...) { + size_t n; + va_list ap; + va_start(ap, fmt); + n = c_vsnprintf(buf, size, fmt, ap); + if(n > size) { + return size; + } + return n; +} + +mjs_val_t mjs_mk_array(struct mjs* mjs) { + mjs_val_t ret = mjs_mk_object(mjs); + /* change the tag to MJS_TAG_ARRAY */ + ret &= ~MJS_TAG_MASK; + ret |= MJS_TAG_ARRAY; + return ret; +} + +int mjs_is_array(mjs_val_t v) { + return (v & MJS_TAG_MASK) == MJS_TAG_ARRAY; +} + +mjs_val_t mjs_array_get(struct mjs* mjs, mjs_val_t arr, unsigned long index) { + return mjs_array_get2(mjs, arr, index, NULL); +} + +mjs_val_t mjs_array_get2(struct mjs* mjs, mjs_val_t arr, unsigned long index, int* has) { + mjs_val_t res = MJS_UNDEFINED; + + if(has != NULL) { + *has = 0; + } + + if(mjs_is_object(arr)) { + struct mjs_property* p; + char buf[20]; + int n = v_sprintf_s(buf, sizeof(buf), "%lu", index); + p = mjs_get_own_property(mjs, arr, buf, n); + if(p != NULL) { + if(has != NULL) { + *has = 1; + } + res = p->value; + } + } + + return res; +} + +unsigned long mjs_array_length(struct mjs* mjs, mjs_val_t v) { + struct mjs_property* p; + unsigned long len = 0; + + if(!mjs_is_object(v)) { + len = 0; + goto clean; + } + + for(p = get_object_struct(v)->properties; p != NULL; p = p->next) { + int ok = 0; + unsigned long n = 0; + str_to_ulong(mjs, p->name, &ok, &n); + if(ok && n >= len && n < 0xffffffff) { + len = n + 1; + } + } + +clean: + return len; +} + +mjs_err_t mjs_array_set(struct mjs* mjs, mjs_val_t arr, unsigned long index, mjs_val_t v) { + mjs_err_t ret = MJS_OK; + + if(mjs_is_object(arr)) { + char buf[20]; + int n = v_sprintf_s(buf, sizeof(buf), "%lu", index); + ret = mjs_set(mjs, arr, buf, n, v); + } else { + ret = MJS_TYPE_ERROR; + } + + return ret; +} + +void mjs_array_del(struct mjs* mjs, mjs_val_t arr, unsigned long index) { + char buf[20]; + int n = v_sprintf_s(buf, sizeof(buf), "%lu", index); + mjs_del(mjs, arr, buf, n); +} + +mjs_err_t mjs_array_push(struct mjs* mjs, mjs_val_t arr, mjs_val_t v) { + return mjs_array_set(mjs, arr, mjs_array_length(mjs, arr), v); +} + +MJS_PRIVATE void mjs_array_push_internal(struct mjs* mjs) { + mjs_err_t rcode = MJS_OK; + mjs_val_t ret = MJS_UNDEFINED; + int nargs = mjs_nargs(mjs); + int i; + + /* Make sure that `this` is an array */ + if(!mjs_check_arg(mjs, -1 /*this*/, "this", MJS_TYPE_OBJECT_ARRAY, NULL)) { + goto clean; + } + + /* Push all args */ + for(i = 0; i < nargs; i++) { + rcode = mjs_array_push(mjs, mjs->vals.this_obj, mjs_arg(mjs, i)); + if(rcode != MJS_OK) { + mjs_prepend_errorf(mjs, rcode, ""); + goto clean; + } + } + + /* Return the new array length */ + ret = mjs_mk_number(mjs, mjs_array_length(mjs, mjs->vals.this_obj)); + +clean: + mjs_return(mjs, ret); + return; +} + +static void move_item(struct mjs* mjs, mjs_val_t arr, unsigned long from, unsigned long to) { + mjs_val_t cur = mjs_array_get(mjs, arr, from); + mjs_array_set(mjs, arr, to, cur); + mjs_array_del(mjs, arr, from); +} + +MJS_PRIVATE void mjs_array_splice(struct mjs* mjs) { + int nargs = mjs_nargs(mjs); + mjs_err_t rcode = MJS_OK; + mjs_val_t ret = mjs_mk_array(mjs); + mjs_val_t start_v = MJS_UNDEFINED; + mjs_val_t deleteCount_v = MJS_UNDEFINED; + int start = 0; + int arr_len; + int delete_cnt = 0; + int new_items_cnt = 0; + int delta = 0; + int i; + + /* Make sure that `this` is an array */ + if(!mjs_check_arg(mjs, -1 /*this*/, "this", MJS_TYPE_OBJECT_ARRAY, NULL)) { + goto clean; + } + + /* Get array length */ + arr_len = mjs_array_length(mjs, mjs->vals.this_obj); + + /* get start from arg 0 */ + if(!mjs_check_arg(mjs, 0, "start", MJS_TYPE_NUMBER, &start_v)) { + goto clean; + } + start = mjs_normalize_idx(mjs_get_int(mjs, start_v), arr_len); + + /* Handle deleteCount */ + if(nargs >= SPLICE_NEW_ITEM_IDX) { + /* deleteCount is given; use it */ + if(!mjs_check_arg(mjs, 1, "deleteCount", MJS_TYPE_NUMBER, &deleteCount_v)) { + goto clean; + } + delete_cnt = mjs_get_int(mjs, deleteCount_v); + new_items_cnt = nargs - SPLICE_NEW_ITEM_IDX; + } else { + /* deleteCount is not given; assume the end of the array */ + delete_cnt = arr_len - start; + } + if(delete_cnt > arr_len - start) { + delete_cnt = arr_len - start; + } else if(delete_cnt < 0) { + delete_cnt = 0; + } + + /* delta at which subsequent array items should be moved */ + delta = new_items_cnt - delete_cnt; + + /* + * copy items which are going to be deleted to the separate array (will be + * returned) + */ + for(i = 0; i < delete_cnt; i++) { + mjs_val_t cur = mjs_array_get(mjs, mjs->vals.this_obj, start + i); + rcode = mjs_array_push(mjs, ret, cur); + if(rcode != MJS_OK) { + mjs_prepend_errorf(mjs, rcode, ""); + goto clean; + } + } + + /* If needed, move subsequent items */ + if(delta < 0) { + for(i = start; i < arr_len; i++) { + if(i >= start - delta) { + move_item(mjs, mjs->vals.this_obj, i, i + delta); + } else { + mjs_array_del(mjs, mjs->vals.this_obj, i); + } + } + } else if(delta > 0) { + for(i = arr_len - 1; i >= start; i--) { + move_item(mjs, mjs->vals.this_obj, i, i + delta); + } + } + + /* Set new items to the array */ + for(i = 0; i < nargs - SPLICE_NEW_ITEM_IDX; i++) { + mjs_array_set(mjs, mjs->vals.this_obj, start + i, mjs_arg(mjs, SPLICE_NEW_ITEM_IDX + i)); + } + +clean: + mjs_return(mjs, ret); +} diff --git a/applications/system/elk_mjs/lib/mjs/mjs_array.h b/applications/system/elk_mjs/lib/mjs/mjs_array.h new file mode 100644 index 00000000000..896a83be539 --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/mjs_array.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2014 Cesanta Software Limited + * All rights reserved + */ + +#ifndef MJS_ARRAY_H_ +#define MJS_ARRAY_H_ + +#include "mjs_internal.h" +#include "mjs_array_public.h" + +#if defined(__cplusplus) +extern "C" { +#endif /* __cplusplus */ + +MJS_PRIVATE mjs_val_t mjs_array_get2(struct mjs* mjs, mjs_val_t arr, unsigned long index, int* has); + +MJS_PRIVATE void mjs_array_splice(struct mjs* mjs); + +MJS_PRIVATE void mjs_array_push_internal(struct mjs* mjs); + +#if defined(__cplusplus) +} +#endif /* __cplusplus */ + +#endif /* MJS_ARRAY_H_ */ diff --git a/applications/system/elk_mjs/lib/mjs/mjs_array_public.h b/applications/system/elk_mjs/lib/mjs/mjs_array_public.h new file mode 100644 index 00000000000..8d043d86edf --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/mjs_array_public.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2017 Cesanta Software Limited + * All rights reserved + */ + +/* + * === Arrays + */ + +#ifndef MJS_ARRAY_PUBLIC_H_ +#define MJS_ARRAY_PUBLIC_H_ + +#include "mjs_core_public.h" + +#if defined(__cplusplus) +extern "C" { +#endif /* __cplusplus */ + +/* Make an empty array object */ +mjs_val_t mjs_mk_array(struct mjs* mjs); + +/* Returns length on an array. If `arr` is not an array, 0 is returned. */ +unsigned long mjs_array_length(struct mjs* mjs, mjs_val_t arr); + +/* Insert value `v` in array `arr` at the end of the array. */ +mjs_err_t mjs_array_push(struct mjs* mjs, mjs_val_t arr, mjs_val_t v); + +/* + * Return array member at index `index`. If `index` is out of bounds, undefined + * is returned. + */ +mjs_val_t mjs_array_get(struct mjs*, mjs_val_t arr, unsigned long index); + +/* Insert value `v` into `arr` at index `index`. */ +mjs_err_t mjs_array_set(struct mjs* mjs, mjs_val_t arr, unsigned long index, mjs_val_t v); + +/* Returns true if the given value is an array */ +int mjs_is_array(mjs_val_t v); + +/* Delete value in array `arr` at index `index`, if it exists. */ +void mjs_array_del(struct mjs* mjs, mjs_val_t arr, unsigned long index); + +#if defined(__cplusplus) +} +#endif /* __cplusplus */ + +#endif /* MJS_ARRAY_PUBLIC_H_ */ diff --git a/applications/system/elk_mjs/lib/mjs/mjs_bcode.c b/applications/system/elk_mjs/lib/mjs/mjs_bcode.c new file mode 100644 index 00000000000..48a8ac8c75c --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/mjs_bcode.c @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2017 Cesanta Software Limited + * All rights reserved + */ + +#include "common/cs_varint.h" + +#include "mjs_internal.h" +#include "mjs_bcode.h" +#include "mjs_core.h" +#include "mjs_tok.h" + +static void add_lineno_map_item(struct pstate* pstate) { + if(pstate->last_emitted_line_no < pstate->line_no) { + int offset = pstate->cur_idx - pstate->start_bcode_idx; + size_t offset_llen = cs_varint_llen(offset); + size_t lineno_llen = cs_varint_llen(pstate->line_no); + mbuf_resize( + &pstate->offset_lineno_map, + pstate->offset_lineno_map.size + offset_llen + lineno_llen); + + /* put offset */ + cs_varint_encode( + offset, + (uint8_t*)pstate->offset_lineno_map.buf + pstate->offset_lineno_map.len, + offset_llen); + pstate->offset_lineno_map.len += offset_llen; + + /* put line_no */ + cs_varint_encode( + pstate->line_no, + (uint8_t*)pstate->offset_lineno_map.buf + pstate->offset_lineno_map.len, + lineno_llen); + pstate->offset_lineno_map.len += lineno_llen; + + pstate->last_emitted_line_no = pstate->line_no; + } +} + +MJS_PRIVATE void emit_byte(struct pstate* pstate, uint8_t byte) { + add_lineno_map_item(pstate); + mbuf_insert(&pstate->mjs->bcode_gen, pstate->cur_idx, &byte, sizeof(byte)); + pstate->cur_idx += sizeof(byte); +} + +MJS_PRIVATE void emit_int(struct pstate* pstate, int64_t n) { + struct mbuf* b = &pstate->mjs->bcode_gen; + size_t llen = cs_varint_llen(n); + add_lineno_map_item(pstate); + mbuf_insert(b, pstate->cur_idx, NULL, llen); + cs_varint_encode(n, (uint8_t*)b->buf + pstate->cur_idx, llen); + pstate->cur_idx += llen; +} + +MJS_PRIVATE void emit_str(struct pstate* pstate, const char* ptr, size_t len) { + struct mbuf* b = &pstate->mjs->bcode_gen; + size_t llen = cs_varint_llen(len); + add_lineno_map_item(pstate); + mbuf_insert(b, pstate->cur_idx, NULL, llen + len); + cs_varint_encode(len, (uint8_t*)b->buf + pstate->cur_idx, llen); + memcpy(b->buf + pstate->cur_idx + llen, ptr, len); + pstate->cur_idx += llen + len; +} + +MJS_PRIVATE int + mjs_bcode_insert_offset(struct pstate* p, struct mjs* mjs, size_t offset, size_t v) { + int llen = (int)cs_varint_llen(v); + int diff = llen - MJS_INIT_OFFSET_SIZE; + assert(offset < mjs->bcode_gen.len); + if(diff > 0) { + mbuf_resize(&mjs->bcode_gen, mjs->bcode_gen.size + diff); + } + /* + * Offset is going to take more than one was reserved, so, move the data + * forward + */ + memmove( + mjs->bcode_gen.buf + offset + llen, + mjs->bcode_gen.buf + offset + MJS_INIT_OFFSET_SIZE, + mjs->bcode_gen.len - offset - MJS_INIT_OFFSET_SIZE); + mjs->bcode_gen.len += diff; + cs_varint_encode(v, (uint8_t*)mjs->bcode_gen.buf + offset, llen); + + /* + * If current parsing index is after the offset at which we've inserted new + * varint, the index might need to be adjusted + */ + if(p->cur_idx >= (int)offset) { + p->cur_idx += diff; + } + return diff; +} + +MJS_PRIVATE void mjs_bcode_part_add(struct mjs* mjs, const struct mjs_bcode_part* bp) { + mbuf_append(&mjs->bcode_parts, bp, sizeof(*bp)); +} + +MJS_PRIVATE struct mjs_bcode_part* mjs_bcode_part_get(struct mjs* mjs, int num) { + assert(num < mjs_bcode_parts_cnt(mjs)); + return (struct mjs_bcode_part*)(mjs->bcode_parts.buf + num * sizeof(struct mjs_bcode_part)); +} + +MJS_PRIVATE struct mjs_bcode_part* mjs_bcode_part_get_by_offset(struct mjs* mjs, size_t offset) { + int i; + int parts_cnt = mjs_bcode_parts_cnt(mjs); + struct mjs_bcode_part* bp = NULL; + + if(offset >= mjs->bcode_len) { + return NULL; + } + + for(i = 0; i < parts_cnt; i++) { + bp = mjs_bcode_part_get(mjs, i); + if(offset < bp->start_idx + bp->data.len) { + break; + } + } + + /* given the non-corrupted data, the needed part must be found */ + assert(i < parts_cnt); + + return bp; +} + +MJS_PRIVATE int mjs_bcode_parts_cnt(struct mjs* mjs) { + return mjs->bcode_parts.len / sizeof(struct mjs_bcode_part); +} + +MJS_PRIVATE void mjs_bcode_commit(struct mjs* mjs) { + struct mjs_bcode_part bp; + memset(&bp, 0, sizeof(bp)); + + /* Make sure the bcode doesn't occupy any extra space */ + mbuf_trim(&mjs->bcode_gen); + + /* Transfer the ownership of the bcode data */ + bp.data.p = mjs->bcode_gen.buf; + bp.data.len = mjs->bcode_gen.len; + mbuf_init(&mjs->bcode_gen, 0); + + bp.start_idx = mjs->bcode_len; + bp.exec_res = MJS_ERRS_CNT; + + mjs_bcode_part_add(mjs, &bp); + + mjs->bcode_len += bp.data.len; +} diff --git a/applications/system/elk_mjs/lib/mjs/mjs_bcode.h b/applications/system/elk_mjs/lib/mjs/mjs_bcode.h new file mode 100644 index 00000000000..2d077ccecea --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/mjs_bcode.h @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2017 Cesanta Software Limited + * All rights reserved + */ + +#ifndef MJS_BCODE_H_ +#define MJS_BCODE_H_ + +#include "mjs_internal.h" + +#include "mjs_core.h" + +#if defined(__cplusplus) +extern "C" { +#endif /* __cplusplus */ + +enum mjs_opcode { + OP_NOP, /* ( -- ) */ + OP_DROP, /* ( a -- ) */ + OP_DUP, /* ( a -- a a ) */ + OP_SWAP, /* ( a b -- b a ) */ + OP_JMP, /* ( -- ) */ + OP_JMP_TRUE, /* ( -- ) */ + OP_JMP_NEUTRAL_TRUE, /* ( -- ) */ + OP_JMP_FALSE, /* ( -- ) */ + OP_JMP_NEUTRAL_FALSE, /* ( -- ) */ + OP_FIND_SCOPE, /* ( a -- a b ) */ + OP_PUSH_SCOPE, /* ( -- a ) */ + OP_PUSH_STR, /* ( -- a ) */ + OP_PUSH_TRUE, /* ( -- a ) */ + OP_PUSH_FALSE, /* ( -- a ) */ + OP_PUSH_INT, /* ( -- a ) */ + OP_PUSH_DBL, /* ( -- a ) */ + OP_PUSH_NULL, /* ( -- a ) */ + OP_PUSH_UNDEF, /* ( -- a ) */ + OP_PUSH_OBJ, /* ( -- a ) */ + OP_PUSH_ARRAY, /* ( -- a ) */ + OP_PUSH_FUNC, /* ( -- a ) */ + OP_PUSH_THIS, /* ( -- a ) */ + OP_GET, /* ( key obj -- obj[key] ) */ + OP_CREATE, /* ( key obj -- ) */ + OP_EXPR, /* ( ... -- a ) */ + OP_APPEND, /* ( a b -- ) */ + OP_SET_ARG, /* ( a -- a ) */ + OP_NEW_SCOPE, /* ( -- ) */ + OP_DEL_SCOPE, /* ( -- ) */ + OP_CALL, /* ( func param1 param2 ... num_params -- result ) */ + OP_RETURN, /* ( -- ) */ + OP_LOOP, /* ( -- ) Push break & continue addresses to loop_labels */ + OP_BREAK, /* ( -- ) */ + OP_CONTINUE, /* ( -- ) */ + OP_SETRETVAL, /* ( a -- ) */ + OP_EXIT, /* ( -- ) */ + OP_BCODE_HEADER, /* ( -- ) */ + OP_ARGS, /* ( -- ) Mark the beginning of function call arguments */ + OP_FOR_IN_NEXT, /* ( name obj iter_ptr -- name obj iter_ptr_next ) */ + OP_MAX +}; + +struct pstate; +struct mjs; + +MJS_PRIVATE void emit_byte(struct pstate* pstate, uint8_t byte); +MJS_PRIVATE void emit_int(struct pstate* pstate, int64_t n); +MJS_PRIVATE void emit_str(struct pstate* pstate, const char* ptr, size_t len); + +/* + * Inserts provided offset `v` at the offset `offset`. + * + * Returns delta at which the code was moved; the delta can be any: 0 or + * positive or negative. + */ +MJS_PRIVATE int + mjs_bcode_insert_offset(struct pstate* p, struct mjs* mjs, size_t offset, size_t v); + +/* + * Adds a new bcode part; does not retain `bp`. + */ +MJS_PRIVATE void mjs_bcode_part_add(struct mjs* mjs, const struct mjs_bcode_part* bp); + +/* + * Returns bcode part by the bcode number + */ +MJS_PRIVATE struct mjs_bcode_part* mjs_bcode_part_get(struct mjs* mjs, int num); + +/* + * Returns bcode part by the global bcode offset + */ +MJS_PRIVATE struct mjs_bcode_part* mjs_bcode_part_get_by_offset(struct mjs* mjs, size_t offset); + +/* + * Returns a number of bcode parts + */ +MJS_PRIVATE int mjs_bcode_parts_cnt(struct mjs* mjs); + +/* + * Adds the bcode being generated (mjs->bcode_gen) as a next bcode part + */ +MJS_PRIVATE void mjs_bcode_commit(struct mjs* mjs); + +#if defined(__cplusplus) +} +#endif /* __cplusplus */ + +#endif /* MJS_BCODE_H_ */ diff --git a/applications/system/elk_mjs/lib/mjs/mjs_builtin.c b/applications/system/elk_mjs/lib/mjs/mjs_builtin.c new file mode 100644 index 00000000000..36ad43cf2e3 --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/mjs_builtin.c @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2017 Cesanta Software Limited + * All rights reserved + */ + +#include "mjs_bcode.h" +#include "mjs_core.h" +#include "mjs_dataview.h" +#include "mjs_exec.h" +#include "mjs_gc.h" +#include "mjs_internal.h" +#include "mjs_json.h" +#include "mjs_object.h" +#include "mjs_primitive.h" +#include "mjs_string.h" +#include "mjs_util.h" + +static void mjs_print(struct mjs* mjs) { + size_t i, num_args = mjs_nargs(mjs); + for(i = 0; i < num_args; i++) { + mjs_fprintf(mjs_arg(mjs, i), mjs, stdout); + putchar(' '); + } + putchar('\n'); + mjs_return(mjs, MJS_UNDEFINED); + (void)mjs; +} + +/* + * If the file with the given filename was already loaded, returns the + * corresponding bcode part; otherwise returns NULL. + */ +static struct mjs_bcode_part* mjs_get_loaded_file_bcode(struct mjs* mjs, const char* filename) { + int parts_cnt = mjs_bcode_parts_cnt(mjs); + int i; + + if(filename == NULL) { + return 0; + } + + for(i = 0; i < parts_cnt; i++) { + struct mjs_bcode_part* bp = mjs_bcode_part_get(mjs, i); + const char* cur_fn = mjs_get_bcode_filename(mjs, bp); + if(strcmp(filename, cur_fn) == 0) { + return bp; + } + } + return NULL; +} + +static void mjs_load(struct mjs* mjs) { + mjs_val_t res = MJS_UNDEFINED; + mjs_val_t arg0 = mjs_arg(mjs, 0); + mjs_val_t arg1 = mjs_arg(mjs, 1); + int custom_global = 0; /* whether the custom global object was provided */ + + if(mjs_is_string(arg0)) { + const char* path = mjs_get_cstring(mjs, &arg0); + struct mjs_bcode_part* bp = NULL; + mjs_err_t ret; + + if(mjs_is_object(arg1)) { + custom_global = 1; + push_mjs_val(&mjs->scopes, arg1); + } + bp = mjs_get_loaded_file_bcode(mjs, path); + if(bp == NULL) { + /* File was not loaded before, so, load */ + ret = mjs_exec_file(mjs, path, &res); + } else { + /* + * File was already loaded before, so if it was evaluated successfully, + * then skip the evaluation at all (and assume MJS_OK); otherwise + * re-evaluate it again. + * + * However, if the custom global object was provided, then reevaluate + * the file in any case. + */ + if(bp->exec_res != MJS_OK || custom_global) { + ret = mjs_execute(mjs, bp->start_idx, &res); + } else { + ret = MJS_OK; + } + } + if(ret != MJS_OK) { + /* + * arg0 and path might be invalidated by executing a file, so refresh + * them + */ + arg0 = mjs_arg(mjs, 0); + path = mjs_get_cstring(mjs, &arg0); + mjs_prepend_errorf(mjs, ret, "failed to exec file \"%s\"", path); + goto clean; + } + + clean: + if(custom_global) { + mjs_pop_val(&mjs->scopes); + } + } + mjs_return(mjs, res); +} + +static void mjs_get_mjs(struct mjs* mjs) { + mjs_return(mjs, mjs_mk_foreign(mjs, mjs)); +} + +static void mjs_chr(struct mjs* mjs) { + mjs_val_t arg0 = mjs_arg(mjs, 0), res = MJS_NULL; + int n = mjs_get_int(mjs, arg0); + if(mjs_is_number(arg0) && n >= 0 && n <= 255) { + uint8_t s = n; + res = mjs_mk_string(mjs, (const char*)&s, sizeof(s), 1); + } + mjs_return(mjs, res); +} + +static void mjs_do_gc(struct mjs* mjs) { + mjs_val_t arg0 = mjs_arg(mjs, 0); + mjs_gc(mjs, mjs_is_boolean(arg0) ? mjs_get_bool(mjs, arg0) : 0); + mjs_return(mjs, arg0); +} + +static void mjs_s2o(struct mjs* mjs) { + mjs_return( + mjs, + mjs_struct_to_obj( + mjs, + mjs_get_ptr(mjs, mjs_arg(mjs, 0)), + (const struct mjs_c_struct_member*)mjs_get_ptr(mjs, mjs_arg(mjs, 1)))); +} + +void mjs_init_builtin(struct mjs* mjs, mjs_val_t obj) { + mjs_val_t v; + + mjs_set(mjs, obj, "global", ~0, obj); + + mjs_set(mjs, obj, "load", ~0, mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_load)); + // mjs_set(mjs, obj, "print", ~0, mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_print)); + mjs_set(mjs, obj, "ffi", ~0, mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_ffi_call)); + mjs_set( + mjs, obj, "ffi_cb_free", ~0, mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_ffi_cb_free)); + mjs_set(mjs, obj, "mkstr", ~0, mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_mkstr)); + mjs_set(mjs, obj, "getMJS", ~0, mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_get_mjs)); + mjs_set(mjs, obj, "die", ~0, mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_die)); + mjs_set(mjs, obj, "gc", ~0, mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_do_gc)); + mjs_set(mjs, obj, "chr", ~0, mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_chr)); + mjs_set(mjs, obj, "s2o", ~0, mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_s2o)); + + /* + * Populate JSON.parse() and JSON.stringify() + */ + // v = mjs_mk_object(mjs); + // mjs_set( + // mjs, v, "stringify", ~0, mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_op_json_stringify)); + // mjs_set(mjs, v, "parse", ~0, mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_op_json_parse)); + // mjs_set(mjs, obj, "JSON", ~0, v); + + /* + * Populate Object.create() + */ + v = mjs_mk_object(mjs); + mjs_set(mjs, v, "create", ~0, mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_op_create_object)); + mjs_set(mjs, obj, "Object", ~0, v); + + /* + * Populate numeric stuff + */ + mjs_set(mjs, obj, "NaN", ~0, MJS_TAG_NAN); + mjs_set(mjs, obj, "isNaN", ~0, mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_op_isnan)); +} diff --git a/applications/system/elk_mjs/lib/mjs/mjs_builtin.h b/applications/system/elk_mjs/lib/mjs/mjs_builtin.h new file mode 100644 index 00000000000..9bbb2b0fe7f --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/mjs_builtin.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2016 Cesanta Software Limited + * All rights reserved + */ + +#ifndef MJS_BUILTIN_H_ +#define MJS_BUILTIN_H_ + +#include "mjs_core_public.h" +#include "mjs_internal.h" + +#if defined(__cplusplus) +extern "C" { +#endif /* __cplusplus */ + +void mjs_init_builtin(struct mjs* mjs, mjs_val_t obj); + +#if defined(__cplusplus) +} +#endif /* __cplusplus */ + +#endif /* MJS_BUILTIN_H_ */ diff --git a/applications/system/elk_mjs/lib/mjs/mjs_conversion.c b/applications/system/elk_mjs/lib/mjs/mjs_conversion.c new file mode 100644 index 00000000000..1bd12cba037 --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/mjs_conversion.c @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2016 Cesanta Software Limited + * All rights reserved + */ + +#include "mjs_conversion.h" +#include "mjs_object.h" +#include "mjs_primitive.h" +#include "mjs_string.h" +#include "mjs_util.h" + +MJS_PRIVATE 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 { + ret = MJS_TYPE_ERROR; + mjs_set_errorf(mjs, ret, "unknown type to convert to string"); + } + +clean: + return ret; +} + +MJS_PRIVATE 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); +} + +MJS_PRIVATE int mjs_is_truthy(struct mjs* mjs, mjs_val_t v) { + return mjs_get_bool(mjs, mjs_to_boolean_v(mjs, v)); +} diff --git a/applications/system/elk_mjs/lib/mjs/mjs_conversion.h b/applications/system/elk_mjs/lib/mjs/mjs_conversion.h new file mode 100644 index 00000000000..57af87c848c --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/mjs_conversion.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2016 Cesanta Software Limited + * All rights reserved + */ + +#ifndef MJS_CONVERSION_H_ +#define MJS_CONVERSION_H_ + +#include "mjs_internal.h" +#include "mjs_core.h" + +#if defined(__cplusplus) +extern "C" { +#endif /* __cplusplus */ + +/* + * Tries to convert `mjs_val_t` to a string, returns MJS_OK if successful. + * String is returned as a pair of pointers: `char **p, size_t *sizep`. + * + * Caller must also provide a non-null `need_free`, and if it is non-zero, + * then the string `*p` should be freed by the caller. + * + * MJS does not support `toString()` and `valueOf()`, so, passing an object + * always results in `MJS_TYPE_ERROR`. + */ +MJS_PRIVATE mjs_err_t + mjs_to_string(struct mjs* mjs, mjs_val_t* v, char** p, size_t* sizep, int* need_free); + +/* + * Converts value to boolean as in the expression `if (v)`. + */ +MJS_PRIVATE mjs_val_t mjs_to_boolean_v(struct mjs* mjs, mjs_val_t v); + +MJS_PRIVATE int mjs_is_truthy(struct mjs* mjs, mjs_val_t v); + +#if defined(__cplusplus) +} +#endif /* __cplusplus */ + +#endif /* MJS_CONVERSION_H_ */ diff --git a/applications/system/elk_mjs/lib/mjs/mjs_core.c b/applications/system/elk_mjs/lib/mjs/mjs_core.c new file mode 100644 index 00000000000..eec8f4eca73 --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/mjs_core.c @@ -0,0 +1,400 @@ +/* + * Copyright (c) 2017 Cesanta Software Limited + * All rights reserved + */ + +#include "common/cs_varint.h" +#include "common/str_util.h" + +#include "mjs_bcode.h" +#include "mjs_builtin.h" +#include "mjs_core.h" +#include "mjs_exec.h" +#include "mjs_ffi.h" +#include "mjs_internal.h" +#include "mjs_object.h" +#include "mjs_primitive.h" +#include "mjs_string.h" +#include "mjs_util.h" + +#ifndef MJS_OBJECT_ARENA_SIZE +#define MJS_OBJECT_ARENA_SIZE 20 +#endif +#ifndef MJS_PROPERTY_ARENA_SIZE +#define MJS_PROPERTY_ARENA_SIZE 20 +#endif +#ifndef MJS_FUNC_FFI_ARENA_SIZE +#define MJS_FUNC_FFI_ARENA_SIZE 20 +#endif + +#ifndef MJS_OBJECT_ARENA_INC_SIZE +#define MJS_OBJECT_ARENA_INC_SIZE 10 +#endif +#ifndef MJS_PROPERTY_ARENA_INC_SIZE +#define MJS_PROPERTY_ARENA_INC_SIZE 10 +#endif +#ifndef MJS_FUNC_FFI_ARENA_INC_SIZE +#define MJS_FUNC_FFI_ARENA_INC_SIZE 10 +#endif + +void mjs_destroy(struct mjs* mjs) { + { + int parts_cnt = mjs_bcode_parts_cnt(mjs); + int i; + for(i = 0; i < parts_cnt; i++) { + struct mjs_bcode_part* bp = mjs_bcode_part_get(mjs, i); + if(!bp->in_rom) { + free((void*)bp->data.p); + } + } + } + + mbuf_free(&mjs->bcode_gen); + mbuf_free(&mjs->bcode_parts); + mbuf_free(&mjs->stack); + mbuf_free(&mjs->call_stack); + mbuf_free(&mjs->arg_stack); + mbuf_free(&mjs->owned_strings); + mbuf_free(&mjs->foreign_strings); + mbuf_free(&mjs->owned_values); + mbuf_free(&mjs->scopes); + mbuf_free(&mjs->loop_addresses); + mbuf_free(&mjs->json_visited_stack); + free(mjs->error_msg); + free(mjs->stack_trace); + mjs_ffi_args_free_list(mjs); + gc_arena_destroy(mjs, &mjs->object_arena); + gc_arena_destroy(mjs, &mjs->property_arena); + gc_arena_destroy(mjs, &mjs->ffi_sig_arena); + free(mjs); +} + +struct mjs* mjs_create(void) { + mjs_val_t global_object; + struct mjs* mjs = calloc(1, sizeof(*mjs)); + mbuf_init(&mjs->stack, 0); + mbuf_init(&mjs->call_stack, 0); + mbuf_init(&mjs->arg_stack, 0); + mbuf_init(&mjs->owned_strings, 0); + mbuf_init(&mjs->foreign_strings, 0); + mbuf_init(&mjs->bcode_gen, 0); + mbuf_init(&mjs->bcode_parts, 0); + mbuf_init(&mjs->owned_values, 0); + mbuf_init(&mjs->scopes, 0); + mbuf_init(&mjs->loop_addresses, 0); + mbuf_init(&mjs->json_visited_stack, 0); + + mjs->bcode_len = 0; + + /* + * The compacting GC exploits the null terminator of the previous string as a + * marker. + */ + { + char z = 0; + mbuf_append(&mjs->owned_strings, &z, 1); + } + + gc_arena_init( + &mjs->object_arena, + sizeof(struct mjs_object), + MJS_OBJECT_ARENA_SIZE, + MJS_OBJECT_ARENA_INC_SIZE); + gc_arena_init( + &mjs->property_arena, + sizeof(struct mjs_property), + MJS_PROPERTY_ARENA_SIZE, + MJS_PROPERTY_ARENA_INC_SIZE); + gc_arena_init( + &mjs->ffi_sig_arena, + sizeof(struct mjs_ffi_sig), + MJS_FUNC_FFI_ARENA_SIZE, + MJS_FUNC_FFI_ARENA_INC_SIZE); + mjs->ffi_sig_arena.destructor = mjs_ffi_sig_destructor; + + global_object = mjs_mk_object(mjs); + mjs_init_builtin(mjs, global_object); + mjs_set_ffi_resolver(mjs, dlsym); + push_mjs_val(&mjs->scopes, global_object); + mjs->vals.this_obj = MJS_UNDEFINED; + mjs->vals.dataview_proto = MJS_UNDEFINED; + + return mjs; +} + +mjs_err_t mjs_set_errorf(struct mjs* mjs, mjs_err_t err, const char* fmt, ...) { + va_list ap; + va_start(ap, fmt); + free(mjs->error_msg); + mjs->error_msg = NULL; + mjs->error = err; + if(fmt != NULL) { + mg_avprintf(&mjs->error_msg, 0, fmt, ap); + } + va_end(ap); + return err; +} + +mjs_err_t mjs_prepend_errorf(struct mjs* mjs, mjs_err_t err, const char* fmt, ...) { + char* old_error_msg = mjs->error_msg; + char* new_error_msg = NULL; + va_list ap; + va_start(ap, fmt); + + /* err should never be MJS_OK here */ + assert(err != MJS_OK); + + mjs->error_msg = NULL; + /* set error if only it wasn't already set to some error */ + if(mjs->error == MJS_OK) { + mjs->error = err; + } + mg_avprintf(&new_error_msg, 0, fmt, ap); + va_end(ap); + + if(old_error_msg != NULL) { + mg_asprintf(&mjs->error_msg, 0, "%s: %s", new_error_msg, old_error_msg); + free(new_error_msg); + free(old_error_msg); + } else { + mjs->error_msg = new_error_msg; + } + return err; +} + +void mjs_print_error(struct mjs* mjs, FILE* fp, const char* msg, int print_stack_trace) { + (void)fp; + + if(print_stack_trace && mjs->stack_trace != NULL) { + // TODO: mjs fix + // fprintf(fp, "%s", mjs->stack_trace); + } + + if(msg == NULL) { + msg = "MJS error"; + } + + // TODO: mjs fix + // fprintf(fp, "%s: %s\n", msg, mjs_strerror(mjs, mjs->error)); +} + +MJS_PRIVATE void mjs_die(struct mjs* mjs) { + mjs_val_t msg_v = MJS_UNDEFINED; + const char* msg = NULL; + size_t msg_len = 0; + + /* get idx from arg 0 */ + if(!mjs_check_arg(mjs, 0, "msg", MJS_TYPE_STRING, &msg_v)) { + goto clean; + } + + msg = mjs_get_string(mjs, &msg_v, &msg_len); + + /* TODO(dfrank): take error type as an argument */ + mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "%.*s", (int)msg_len, msg); + +clean: + mjs_return(mjs, MJS_UNDEFINED); +} + +const char* mjs_strerror(struct mjs* mjs, enum mjs_err err) { + const char* err_names[] = { + "NO_ERROR", + "SYNTAX_ERROR", + "REFERENCE_ERROR", + "TYPE_ERROR", + "OUT_OF_MEMORY", + "INTERNAL_ERROR", + "NOT_IMPLEMENTED", + "FILE_OPEN_ERROR", + "BAD_ARGUMENTS"}; + return mjs->error_msg == NULL || mjs->error_msg[0] == '\0' ? err_names[err] : mjs->error_msg; +} + +MJS_PRIVATE size_t mjs_get_func_addr(mjs_val_t v) { + return v & ~MJS_TAG_MASK; +} + +MJS_PRIVATE enum mjs_type mjs_get_type(mjs_val_t v) { + int tag; + if(mjs_is_number(v)) { + return MJS_TYPE_NUMBER; + } + tag = (v & MJS_TAG_MASK) >> 48; + switch(tag) { + case MJS_TAG_FOREIGN >> 48: + return MJS_TYPE_FOREIGN; + case MJS_TAG_UNDEFINED >> 48: + return MJS_TYPE_UNDEFINED; + case MJS_TAG_OBJECT >> 48: + return MJS_TYPE_OBJECT_GENERIC; + case MJS_TAG_ARRAY >> 48: + return MJS_TYPE_OBJECT_ARRAY; + case MJS_TAG_FUNCTION >> 48: + return MJS_TYPE_OBJECT_FUNCTION; + case MJS_TAG_STRING_I >> 48: + case MJS_TAG_STRING_O >> 48: + case MJS_TAG_STRING_F >> 48: + case MJS_TAG_STRING_D >> 48: + case MJS_TAG_STRING_5 >> 48: + return MJS_TYPE_STRING; + case MJS_TAG_BOOLEAN >> 48: + return MJS_TYPE_BOOLEAN; + case MJS_TAG_NULL >> 48: + return MJS_TYPE_NULL; + default: + abort(); + return MJS_TYPE_UNDEFINED; + } +} + +mjs_val_t mjs_get_global(struct mjs* mjs) { + return *vptr(&mjs->scopes, 0); +} + +static void mjs_append_stack_trace_line(struct mjs* mjs, size_t offset) { + if(offset != MJS_BCODE_OFFSET_EXIT) { + const char* filename = mjs_get_bcode_filename_by_offset(mjs, offset); + int line_no = mjs_get_lineno_by_offset(mjs, offset); + char* new_line = NULL; + const char* fmt = " at %s:%d\n"; + if(filename == NULL) { + // TODO: mjs fix + // fprintf( + // stderr, + // "ERROR during stack trace generation: wrong bcode offset %d\n", + // (int)offset); + filename = ""; + } + mg_asprintf(&new_line, 0, fmt, filename, line_no); + + if(mjs->stack_trace != NULL) { + char* old = mjs->stack_trace; + mg_asprintf(&mjs->stack_trace, 0, "%s%s", mjs->stack_trace, new_line); + free(old); + free(new_line); + } else { + mjs->stack_trace = new_line; + } + } +} + +MJS_PRIVATE void mjs_gen_stack_trace(struct mjs* mjs, size_t offset) { + mjs_append_stack_trace_line(mjs, offset); + while(mjs->call_stack.len >= sizeof(mjs_val_t) * CALL_STACK_FRAME_ITEMS_CNT) { + int i; + + /* set current offset to it to the offset stored in the frame */ + offset = mjs_get_int(mjs, *vptr(&mjs->call_stack, -1 - CALL_STACK_FRAME_ITEM_RETURN_ADDR)); + + /* pop frame from the call stack */ + for(i = 0; i < CALL_STACK_FRAME_ITEMS_CNT; i++) { + mjs_pop_val(&mjs->call_stack); + } + + mjs_append_stack_trace_line(mjs, offset); + } +} + +void mjs_own(struct mjs* mjs, mjs_val_t* v) { + mbuf_append(&mjs->owned_values, &v, sizeof(v)); +} + +int mjs_disown(struct mjs* mjs, mjs_val_t* v) { + mjs_val_t** vp = (mjs_val_t**)(mjs->owned_values.buf + mjs->owned_values.len - sizeof(v)); + + for(; (char*)vp >= mjs->owned_values.buf; vp--) { + if(*vp == v) { + *vp = *(mjs_val_t**)(mjs->owned_values.buf + mjs->owned_values.len - sizeof(v)); + mjs->owned_values.len -= sizeof(v); + return 1; + } + } + + return 0; +} + +/* + * Returns position in the data stack at which the called function is located, + * and which should be later replaced with the returned value. + */ +MJS_PRIVATE int mjs_getretvalpos(struct mjs* mjs) { + int pos; + mjs_val_t* ppos = vptr(&mjs->call_stack, -1); + // LOG(LL_INFO, ("ppos: %p %d", ppos, mjs_stack_size(&mjs->call_stack))); + assert(ppos != NULL && mjs_is_number(*ppos)); + pos = mjs_get_int(mjs, *ppos) - 1; + assert(pos < (int)mjs_stack_size(&mjs->stack)); + return pos; +} + +int mjs_nargs(struct mjs* mjs) { + int top = mjs_stack_size(&mjs->stack); + int pos = mjs_getretvalpos(mjs) + 1; + // LOG(LL_INFO, ("top: %d pos: %d", top, pos)); + return pos > 0 && pos < top ? top - pos : 0; +} + +mjs_val_t mjs_arg(struct mjs* mjs, int arg_index) { + mjs_val_t res = MJS_UNDEFINED; + int top = mjs_stack_size(&mjs->stack); + int pos = mjs_getretvalpos(mjs) + 1; + // LOG(LL_INFO, ("idx %d pos: %d", arg_index, pos)); + if(pos > 0 && pos + arg_index < top) { + res = *vptr(&mjs->stack, pos + arg_index); + } + return res; +} + +void mjs_return(struct mjs* mjs, mjs_val_t v) { + int pos = mjs_getretvalpos(mjs); + // LOG(LL_INFO, ("pos: %d", pos)); + mjs->stack.len = sizeof(mjs_val_t) * pos; + mjs_push(mjs, v); +} + +MJS_PRIVATE mjs_val_t vtop(struct mbuf* m) { + size_t size = mjs_stack_size(m); + return size > 0 ? *vptr(m, size - 1) : MJS_UNDEFINED; +} + +MJS_PRIVATE size_t mjs_stack_size(const struct mbuf* m) { + return m->len / sizeof(mjs_val_t); +} + +MJS_PRIVATE mjs_val_t* vptr(struct mbuf* m, int idx) { + int size = mjs_stack_size(m); + if(idx < 0) idx = size + idx; + return idx >= 0 && idx < size ? &((mjs_val_t*)m->buf)[idx] : NULL; +} + +MJS_PRIVATE mjs_val_t mjs_pop(struct mjs* mjs) { + if(mjs->stack.len == 0) { + mjs_set_errorf(mjs, MJS_INTERNAL_ERROR, "stack underflow"); + return MJS_UNDEFINED; + } else { + return mjs_pop_val(&mjs->stack); + } +} + +MJS_PRIVATE void push_mjs_val(struct mbuf* m, mjs_val_t v) { + mbuf_append(m, &v, sizeof(v)); +} + +MJS_PRIVATE mjs_val_t mjs_pop_val(struct mbuf* m) { + mjs_val_t v = MJS_UNDEFINED; + assert(m->len >= sizeof(v)); + if(m->len >= sizeof(v)) { + memcpy(&v, m->buf + m->len - sizeof(v), sizeof(v)); + m->len -= sizeof(v); + } + return v; +} + +MJS_PRIVATE void mjs_push(struct mjs* mjs, mjs_val_t v) { + push_mjs_val(&mjs->stack, v); +} + +void mjs_set_generate_jsc(struct mjs* mjs, int generate_jsc) { + mjs->generate_jsc = generate_jsc; +} diff --git a/applications/system/elk_mjs/lib/mjs/mjs_core.h b/applications/system/elk_mjs/lib/mjs/mjs_core.h new file mode 100644 index 00000000000..b236b2dd180 --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/mjs_core.h @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2017 Cesanta Software Limited + * All rights reserved + */ + +#ifndef MJS_CORE_H +#define MJS_CORE_H + +#include "mjs_ffi.h" +#include "mjs_gc.h" +#include "mjs_internal.h" + +#if defined(__cplusplus) +extern "C" { +#endif /* __cplusplus */ + +#define JUMP_INSTRUCTION_SIZE 2 + +enum mjs_type { + /* Primitive types */ + MJS_TYPE_UNDEFINED, + MJS_TYPE_NULL, + MJS_TYPE_BOOLEAN, + MJS_TYPE_NUMBER, + MJS_TYPE_STRING, + MJS_TYPE_FOREIGN, + + /* Different classes of Object type */ + MJS_TYPE_OBJECT_GENERIC, + MJS_TYPE_OBJECT_ARRAY, + MJS_TYPE_OBJECT_FUNCTION, + /* + * TODO(dfrank): if we support prototypes, need to add items for them here + */ + + MJS_TYPES_CNT +}; + +enum mjs_call_stack_frame_item { + CALL_STACK_FRAME_ITEM_RETVAL_STACK_IDX, /* TOS */ + CALL_STACK_FRAME_ITEM_LOOP_ADDR_IDX, + CALL_STACK_FRAME_ITEM_SCOPE_IDX, + CALL_STACK_FRAME_ITEM_RETURN_ADDR, + CALL_STACK_FRAME_ITEM_THIS, + + CALL_STACK_FRAME_ITEMS_CNT +}; + +/* + * A tag is made of the sign bit and the 4 lower order bits of byte 6. + * So in total we have 32 possible tags. + * + * Tag (1,0) however cannot hold a zero payload otherwise it's interpreted as an + * INFINITY; for simplicity we're just not going to use that combination. + */ +#define MAKE_TAG(s, t) ((uint64_t)(s) << 63 | (uint64_t)0x7ff0 << 48 | (uint64_t)(t) << 48) + +#define MJS_TAG_OBJECT MAKE_TAG(1, 1) +#define MJS_TAG_FOREIGN MAKE_TAG(1, 2) +#define MJS_TAG_UNDEFINED MAKE_TAG(1, 3) +#define MJS_TAG_BOOLEAN MAKE_TAG(1, 4) +#define MJS_TAG_NAN MAKE_TAG(1, 5) +#define MJS_TAG_STRING_I MAKE_TAG(1, 6) /* Inlined string len < 5 */ +#define MJS_TAG_STRING_5 MAKE_TAG(1, 7) /* Inlined string len 5 */ +#define MJS_TAG_STRING_O MAKE_TAG(1, 8) /* Owned string */ +#define MJS_TAG_STRING_F MAKE_TAG(1, 9) /* Foreign string */ +#define MJS_TAG_STRING_C MAKE_TAG(1, 10) /* String chunk */ +#define MJS_TAG_STRING_D MAKE_TAG(1, 11) /* Dictionary string */ +#define MJS_TAG_ARRAY MAKE_TAG(1, 12) +#define MJS_TAG_FUNCTION MAKE_TAG(1, 13) +#define MJS_TAG_FUNCTION_FFI MAKE_TAG(1, 14) +#define MJS_TAG_NULL MAKE_TAG(1, 15) + +#define MJS_TAG_MASK MAKE_TAG(1, 15) + +struct mjs_vals { + /* Current `this` value */ + mjs_val_t this_obj; + mjs_val_t dataview_proto; + + /* + * The object against which the last `OP_GET` was invoked. Needed for + * "method invocation pattern". + */ + mjs_val_t last_getprop_obj; +}; + +struct mjs_bcode_part { + /* Global index of the bcode part */ + size_t start_idx; + + /* Actual bcode data */ + struct { + const char* p; /* Memory chunk pointer */ + size_t len; /* Memory chunk length */ + } data; + + /* + * Result of evaluation (not parsing: if there is an error during parsing, + * the bcode is not even committed). It is used to determine whether we + * need to evaluate the file: if file was already evaluated, and the result + * was MJS_OK, then we won't evaluate it again. Otherwise, we will. + */ + mjs_err_t exec_res : 4; + + /* If set, bcode data does not need to be freed */ + unsigned in_rom : 1; +}; + +struct mjs { + struct mbuf bcode_gen; + struct mbuf bcode_parts; + size_t bcode_len; + struct mbuf stack; + struct mbuf call_stack; + struct mbuf arg_stack; + struct mbuf scopes; /* Scope objects */ + struct mbuf loop_addresses; /* Addresses for breaks & continues */ + struct mbuf owned_strings; /* Sequence of (varint len, char data[]) */ + struct mbuf foreign_strings; /* Sequence of (varint len, char *data) */ + struct mbuf owned_values; + struct mbuf json_visited_stack; + struct mjs_vals vals; + char* error_msg; + char* stack_trace; + enum mjs_err error; + mjs_ffi_resolver_t* dlsym; /* Symbol resolver function for FFI */ + ffi_cb_args_t* ffi_cb_args; /* List of FFI args descriptors */ + size_t cur_bcode_offset; + + struct gc_arena object_arena; + struct gc_arena property_arena; + struct gc_arena ffi_sig_arena; + + unsigned inhibit_gc : 1; + unsigned need_gc : 1; + unsigned generate_jsc : 1; +}; + +/* + * Bcode header: type of the items, and item numbers. + */ +typedef uint32_t mjs_header_item_t; +enum mjs_header_items { + MJS_HDR_ITEM_TOTAL_SIZE, /* Total size of the bcode (not counting the + OP_BCODE_HEADER byte) */ + MJS_HDR_ITEM_BCODE_OFFSET, /* Offset to the start of the actual bcode (not + counting the OP_BCODE_HEADER byte) */ + MJS_HDR_ITEM_MAP_OFFSET, /* Offset to the start of offset-to-line_no mapping + k*/ + + MJS_HDR_ITEMS_CNT +}; + +MJS_PRIVATE size_t mjs_get_func_addr(mjs_val_t v); + +MJS_PRIVATE int mjs_getretvalpos(struct mjs* mjs); + +MJS_PRIVATE enum mjs_type mjs_get_type(mjs_val_t v); + +/* + * Prints stack trace starting from the given bcode offset; other offsets + * (if any) will be fetched from the call_stack. + */ +MJS_PRIVATE void mjs_gen_stack_trace(struct mjs* mjs, size_t offset); + +MJS_PRIVATE mjs_val_t vtop(struct mbuf* m); +MJS_PRIVATE size_t mjs_stack_size(const struct mbuf* m); +MJS_PRIVATE mjs_val_t* vptr(struct mbuf* m, int idx); +MJS_PRIVATE void push_mjs_val(struct mbuf* m, mjs_val_t v); +MJS_PRIVATE mjs_val_t mjs_pop_val(struct mbuf* m); +MJS_PRIVATE mjs_val_t mjs_pop(struct mjs* mjs); +MJS_PRIVATE void mjs_push(struct mjs* mjs, mjs_val_t v); +MJS_PRIVATE void mjs_die(struct mjs* mjs); + +#if defined(__cplusplus) +} +#endif /* __cplusplus */ + +#endif /* MJS_CORE_H */ diff --git a/applications/system/elk_mjs/lib/mjs/mjs_core_public.h b/applications/system/elk_mjs/lib/mjs/mjs_core_public.h new file mode 100644 index 00000000000..387fc3fec41 --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/mjs_core_public.h @@ -0,0 +1,232 @@ +/* + * Copyright (c) 2016 Cesanta Software Limited + * All rights reserved + */ + +#ifndef MJS_CORE_PUBLIC_H_ +#define MJS_CORE_PUBLIC_H_ + +#if !defined(_MSC_VER) || _MSC_VER >= 1700 +#include +#else +typedef unsigned __int64 uint64_t; +typedef int int32_t; +typedef unsigned char uint8_t; +#endif +#include +#include +#include "mjs_features.h" + +#if defined(__cplusplus) +extern "C" { +#endif /* __cplusplus */ + +#ifndef MJS_ENABLE_DEBUG +#define MJS_ENABLE_DEBUG 0 +#endif + +/* + * Double-precision floating-point number, IEEE 754 + * + * 64 bit (8 bytes) in total + * 1 bit sign + * 11 bits exponent + * 52 bits mantissa + * 7 6 5 4 3 2 1 0 + * seeeeeee|eeeemmmm|mmmmmmmm|mmmmmmmm|mmmmmmmm|mmmmmmmm|mmmmmmmm|mmmmmmmm + * + * If an exponent is all-1 and mantissa is all-0, then it is an INFINITY: + * 11111111|11110000|00000000|00000000|00000000|00000000|00000000|00000000 + * + * If an exponent is all-1 and mantissa's MSB is 1, it is a quiet NaN: + * 11111111|11111000|00000000|00000000|00000000|00000000|00000000|00000000 + * + * MJS NaN-packing: + * sign and exponent is 0xfff + * 4 bits specify type (tag), must be non-zero + * 48 bits specify value + * + * 11111111|1111tttt|vvvvvvvv|vvvvvvvv|vvvvvvvv|vvvvvvvv|vvvvvvvv|vvvvvvvv + * NaN marker |type| 48-bit placeholder for values: pointers, strings + * + * On 64-bit platforms, pointers are really 48 bit only, so they can fit, + * provided they are sign extended + */ + +typedef uint64_t mjs_val_t; + +/* This if-0 is a dirty workaround to force etags to pick `struct mjs` */ +#if 0 +/* Opaque structure. MJS engine context. */ +struct mjs { + /* ... */ +}; +#endif + +struct mjs; + +typedef enum mjs_err { + MJS_OK, + MJS_SYNTAX_ERROR, + MJS_REFERENCE_ERROR, + MJS_TYPE_ERROR, + MJS_OUT_OF_MEMORY, + MJS_INTERNAL_ERROR, + MJS_NOT_IMPLEMENTED_ERROR, + MJS_FILE_READ_ERROR, + MJS_BAD_ARGS_ERROR, + + MJS_ERRS_CNT +} mjs_err_t; +struct mjs; + +/* Create MJS instance */ +struct mjs* mjs_create(); + +struct mjs_create_opts { + /* use non-default bytecode definition file, testing-only */ + const struct bf_code* code; +}; + +/* + * Like `msj_create()`, but allows to customize initial MJS state, see `struct + * mjs_create_opts`. + */ +struct mjs* mjs_create_opt(struct mjs_create_opts opts); + +/* Destroy MJS instance */ +void mjs_destroy(struct mjs* mjs); + +mjs_val_t mjs_get_global(struct mjs* mjs); + +/* + * Tells the GC about an MJS value variable/field owned by C code. + * + * The user's C code should own mjs_val_t variables if the value's lifetime + * crosses any invocation of `mjs_exec()` and friends, including `mjs_call()`. + * + * The registration of the variable prevents the GC from mistakenly treat the + * object as garbage. + * + * User code should also explicitly disown the variables with `mjs_disown()` + * once it goes out of scope or the structure containing the mjs_val_t field is + * freed. + * + * Consider the following examples: + * + * Correct (owning is not necessary): + * ```c + * mjs_val_t res; + * mjs_exec(mjs, "....some script", &res); + * // ... use res somehow + * + * mjs_val_t res; + * mjs_exec(mjs, "....some script2", &res); + * // ... use new res somehow + * ``` + * + * WRONG: + * ```c + * mjs_val_t res1; + * mjs_exec(mjs, "....some script", &res1); + * + * mjs_val_t res2; + * mjs_exec(mjs, "....some script2", &res2); + * + * // ... use res1 (WRONG!) and res2 + * ``` + * + * The code above is wrong, because after the second invocation of + * `mjs_exec()`, the value of `res1` is invalidated. + * + * Correct (res1 is owned) + * ```c + * mjs_val_t res1 = MJS_UNDEFINED; + * mjs_own(mjs, &res1); + * mjs_exec(mjs, "....some script", &res1); + * + * mjs_val_t res2 = MJS_UNDEFINED; + * mjs_exec(mjs, "....some script2", &res2); + * + * // ... use res1 and res2 + * mjs_disown(mjs, &res1); + * ``` + * + * NOTE that we explicly initialized `res1` to a valid value before owning it + * (in this case, the value is `MJS_UNDEFINED`). Owning an uninitialized + * variable is an undefined behaviour. + * + * Of course, it's not an error to own a variable even if it's not mandatory: + * e.g. in the last example we could own both `res1` and `res2`. Probably it + * would help us in the future, when we refactor the code so that `res2` has to + * be owned, and we could forget to do that. + * + * Also, if the user code has some C function called from MJS, and in this C + * function some MJS value (`mjs_val_t`) needs to be stored somewhere and to + * stay alive after the C function has returned, it also needs to be properly + * owned. + */ +void mjs_own(struct mjs* mjs, mjs_val_t* v); + +/* + * Disowns the value previously owned by `mjs_own()`. + * + * Returns 1 if value is found, 0 otherwise. + */ +int mjs_disown(struct mjs* mjs, mjs_val_t* v); + +mjs_err_t mjs_set_errorf(struct mjs* mjs, mjs_err_t err, const char* fmt, ...); + +/* + * If there is no error message already set, then it's equal to + * `mjs_set_errorf()`. + * + * Otherwise, an old message gets prepended with the new one, followed by a + * colon. (the previously set error code is kept) + */ +mjs_err_t mjs_prepend_errorf(struct mjs* mjs, mjs_err_t err, const char* fmt, ...); + +/* + * Print the last error details. If print_stack_trace is non-zero, also + * print stack trace. `msg` is the message which gets prepended to the actual + * error message, if it's NULL, then "MJS error" is used. + */ +void mjs_print_error(struct mjs* mjs, FILE* fp, const char* msg, int print_stack_trace); + +/* + * return a string representation of an error. + * the error string might be overwritten by calls to `mjs_set_errorf`. + */ +const char* mjs_strerror(struct mjs* mjs, enum mjs_err err); + +/* + * Sets whether *.jsc files are generated when *.js file is executed. By + * default it's 0. + * + * If either `MJS_GENERATE_JSC` or `CS_MMAP` is off, then this function has no + * effect. + */ +void mjs_set_generate_jsc(struct mjs* mjs, int generate_jsc); + +/* + * When invoked from a cfunction, returns number of arguments passed to the + * current JS function call. + */ +int mjs_nargs(struct mjs* mjs); + +/* + * When invoked from a cfunction, returns n-th argument to the current JS + * function call. + */ +mjs_val_t mjs_arg(struct mjs* mjs, int n); + +/* + * Sets return value for the current JS function call. + */ +void mjs_return(struct mjs* mjs, mjs_val_t v); + +#if defined(__cplusplus) +} +#endif /* __cplusplus */ + +#endif /* MJS_CORE_PUBLIC_H_ */ diff --git a/applications/system/elk_mjs/lib/mjs/mjs_dataview.c b/applications/system/elk_mjs/lib/mjs/mjs_dataview.c new file mode 100644 index 00000000000..007f7b00111 --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/mjs_dataview.c @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2017 Cesanta Software Limited + * All rights reserved + */ + +#include "mjs_exec_public.h" +#include "mjs_internal.h" +#include "mjs_object.h" +#include "mjs_primitive.h" +#include "mjs_util.h" + +void* mjs_mem_to_ptr(unsigned val) { + return (void*)(uintptr_t)val; +} + +void* mjs_mem_get_ptr(void* base, int offset) { + return (char*)base + offset; +} + +void mjs_mem_set_ptr(void* ptr, void* val) { + *(void**)ptr = val; +} + +double mjs_mem_get_dbl(void* ptr) { + double v; + memcpy(&v, ptr, sizeof(v)); + return v; +} + +void mjs_mem_set_dbl(void* ptr, double val) { + memcpy(ptr, &val, sizeof(val)); +} + +/* + * TODO(dfrank): add support for unsigned ints to ffi and use + * unsigned int here + */ +double mjs_mem_get_uint(void* ptr, int size, int bigendian) { + uint8_t* p = (uint8_t*)ptr; + int i, inc = bigendian ? 1 : -1; + unsigned int res = 0; + p += bigendian ? 0 : size - 1; + for(i = 0; i < size; i++, p += inc) { + res <<= 8; + res |= *p; + } + return res; +} + +/* + * TODO(dfrank): add support for unsigned ints to ffi and use + * unsigned int here + */ +double mjs_mem_get_int(void* ptr, int size, int bigendian) { + uint8_t* p = (uint8_t*)ptr; + int i, inc = bigendian ? 1 : -1; + int res = 0; + p += bigendian ? 0 : size - 1; + + for(i = 0; i < size; i++, p += inc) { + res <<= 8; + res |= *p; + } + + /* sign-extend */ + { + int extra = sizeof(res) - size; + for(i = 0; i < extra; i++) res <<= 8; + for(i = 0; i < extra; i++) res >>= 8; + } + + return res; +} + +void mjs_mem_set_uint(void* ptr, unsigned int val, int size, int bigendian) { + uint8_t* p = (uint8_t*)ptr + (bigendian ? size - 1 : 0); + int i, inc = bigendian ? -1 : 1; + for(i = 0; i < size; i++, p += inc) { + *p = val & 0xff; + val >>= 8; + } +} + +void mjs_mem_set_int(void* ptr, int val, int size, int bigendian) { + mjs_mem_set_uint(ptr, val, size, bigendian); +} diff --git a/applications/system/elk_mjs/lib/mjs/mjs_dataview.h b/applications/system/elk_mjs/lib/mjs/mjs_dataview.h new file mode 100644 index 00000000000..6bd10425506 --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/mjs_dataview.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2017 Cesanta Software Limited + * All rights reserved + */ + +#ifndef MJS_DATAVIEW_H_ +#define MJS_DATAVIEW_H_ + +#if defined(__cplusplus) +extern "C" { +#endif /* __cplusplus */ + +/* + * Functions for memory introspection. + * These are supposed to be FFI-ed and used from the JS environment. + */ + +void* mjs_mem_to_ptr(unsigned int val); +void* mjs_mem_get_ptr(void* base, int offset); +void mjs_mem_set_ptr(void* ptr, void* val); +double mjs_mem_get_dbl(void* ptr); +void mjs_mem_set_dbl(void* ptr, double val); +double mjs_mem_get_uint(void* ptr, int size, int bigendian); +double mjs_mem_get_int(void* ptr, int size, int bigendian); +void mjs_mem_set_uint(void* ptr, unsigned int val, int size, int bigendian); +void mjs_mem_set_int(void* ptr, int val, int size, int bigendian); + +#if defined(__cplusplus) +} +#endif /* __cplusplus */ + +#endif /* MJS_DATAVIEW_H_ */ diff --git a/applications/system/elk_mjs/lib/mjs/mjs_exec.c b/applications/system/elk_mjs/lib/mjs/mjs_exec.c new file mode 100644 index 00000000000..43d13bfbf43 --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/mjs_exec.c @@ -0,0 +1,1184 @@ +/* + * Copyright (c) 2017 Cesanta Software Limited + * All rights reserved + */ + +#include "common/cs_file.h" +#include "common/cs_varint.h" + +#include "mjs_array.h" +#include "mjs_bcode.h" +#include "mjs_conversion.h" +#include "mjs_core.h" +#include "mjs_exec.h" +#include "mjs_internal.h" +#include "mjs_object.h" +#include "mjs_parser.h" +#include "mjs_primitive.h" +#include "mjs_string.h" +#include "mjs_tok.h" +#include "mjs_util.h" + +#if MJS_GENERATE_JSC && defined(CS_MMAP) +#include +#endif + +/* + * Pushes call stack frame. Offset is a global bcode offset. Retval_stack_idx + * is an index in mjs->stack at which return value should be written later. + */ +static void call_stack_push_frame(struct mjs* mjs, size_t offset, mjs_val_t retval_stack_idx) { + /* Pop `this` value, and apply it */ + mjs_val_t this_obj = mjs_pop_val(&mjs->arg_stack); + + /* + * NOTE: the layout is described by enum mjs_call_stack_frame_item + */ + push_mjs_val(&mjs->call_stack, mjs->vals.this_obj); + mjs->vals.this_obj = this_obj; + + push_mjs_val(&mjs->call_stack, mjs_mk_number(mjs, (double)offset)); + push_mjs_val(&mjs->call_stack, mjs_mk_number(mjs, (double)mjs_stack_size(&mjs->scopes))); + push_mjs_val( + &mjs->call_stack, mjs_mk_number(mjs, (double)mjs_stack_size(&mjs->loop_addresses))); + push_mjs_val(&mjs->call_stack, retval_stack_idx); +} + +/* + * Restores call stack frame. Returns the return address. + */ +static size_t call_stack_restore_frame(struct mjs* mjs) { + size_t retval_stack_idx, return_address, scope_index, loop_addr_index; + assert(mjs_stack_size(&mjs->call_stack) >= CALL_STACK_FRAME_ITEMS_CNT); + + /* + * NOTE: the layout is described by enum mjs_call_stack_frame_item + */ + retval_stack_idx = mjs_get_int(mjs, mjs_pop_val(&mjs->call_stack)); + loop_addr_index = mjs_get_int(mjs, mjs_pop_val(&mjs->call_stack)); + scope_index = mjs_get_int(mjs, mjs_pop_val(&mjs->call_stack)); + return_address = mjs_get_int(mjs, mjs_pop_val(&mjs->call_stack)); + mjs->vals.this_obj = mjs_pop_val(&mjs->call_stack); + + /* Remove created scopes */ + while(mjs_stack_size(&mjs->scopes) > scope_index) { + mjs_pop_val(&mjs->scopes); + } + + /* Remove loop addresses */ + while(mjs_stack_size(&mjs->loop_addresses) > loop_addr_index) { + mjs_pop_val(&mjs->loop_addresses); + } + + /* Shrink stack, leave return value on top */ + mjs->stack.len = retval_stack_idx * sizeof(mjs_val_t); + + /* Jump to the return address */ + return return_address; +} + +static mjs_val_t mjs_find_scope(struct mjs* mjs, mjs_val_t key) { + size_t num_scopes = mjs_stack_size(&mjs->scopes); + while(num_scopes > 0) { + mjs_val_t scope = *vptr(&mjs->scopes, num_scopes - 1); + num_scopes--; + if(mjs_get_own_property_v(mjs, scope, key) != NULL) return scope; + } + mjs_set_errorf(mjs, MJS_REFERENCE_ERROR, "[%s] is not defined", mjs_get_cstring(mjs, &key)); + return MJS_UNDEFINED; +} + +mjs_val_t mjs_get_this(struct mjs* mjs) { + return mjs->vals.this_obj; +} + +static double do_arith_op(double da, double db, int op, bool* resnan) { + *resnan = false; + + if(isnan(da) || isnan(db)) { + *resnan = true; + return 0; + } + /* clang-format off */ + switch (op) { + case TOK_MINUS: return da - db; + case TOK_PLUS: return da + db; + case TOK_MUL: return da * db; + case TOK_DIV: + if (db != 0) { + return da / db; + } else { + /* TODO(dfrank): add support for Infinity and return it here */ + *resnan = true; + return 0; + } + case TOK_REM: + /* + * TODO(dfrank): probably support remainder operation as it is in JS + * (which works with non-integer divisor). + */ + db = (int) db; + if (db != 0) { + bool neg = false; + if (da < 0) { + neg = true; + da = -da; + } + if (db < 0) { + db = -db; + } + da = (double) ((int64_t) da % (int64_t) db); + if (neg) { + da = -da; + } + return da; + } else { + *resnan = true; + return 0; + } + case TOK_AND: return (double) ((int64_t) da & (int64_t) db); + case TOK_OR: return (double) ((int64_t) da | (int64_t) db); + case TOK_XOR: return (double) ((int64_t) da ^ (int64_t) db); + case TOK_LSHIFT: return (double) ((int64_t) da << (int64_t) db); + case TOK_RSHIFT: return (double) ((int64_t) da >> (int64_t) db); + case TOK_URSHIFT: return (double) ((uint32_t) da >> (uint32_t) db); + } + /* clang-format on */ + *resnan = true; + return 0; +} + +static void set_no_autoconversion_error(struct mjs* mjs) { + mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "implicit type conversion is prohibited"); +} + +static mjs_val_t do_op(struct mjs* mjs, mjs_val_t a, mjs_val_t b, int op) { + mjs_val_t ret = MJS_UNDEFINED; + bool resnan = false; + if((mjs_is_foreign(a) || mjs_is_number(a)) && (mjs_is_foreign(b) || mjs_is_number(b))) { + int is_result_ptr = 0; + double da, db, result; + + if(mjs_is_foreign(a) && mjs_is_foreign(b)) { + /* When two operands are pointers, only subtraction is supported */ + if(op != TOK_MINUS) { + mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "invalid operands"); + } + } else if(mjs_is_foreign(a) || mjs_is_foreign(b)) { + /* + * When one of the operands is a pointer, only + and - are supported, + * and the result is a pointer. + */ + if(op != TOK_MINUS && op != TOK_PLUS) { + mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "invalid operands"); + } + is_result_ptr = 1; + } + da = mjs_is_number(a) ? mjs_get_double(mjs, a) : (double)(uintptr_t)mjs_get_ptr(mjs, a); + db = mjs_is_number(b) ? mjs_get_double(mjs, b) : (double)(uintptr_t)mjs_get_ptr(mjs, b); + result = do_arith_op(da, db, op, &resnan); + if(resnan) { + ret = MJS_TAG_NAN; + } else { + /* + * If at least one of the operands was a pointer, result should also be + * a pointer + */ + ret = is_result_ptr ? mjs_mk_foreign(mjs, (void*)(uintptr_t)result) : + mjs_mk_number(mjs, result); + } + } else if(mjs_is_string(a) && mjs_is_string(b) && (op == TOK_PLUS)) { + ret = s_concat(mjs, a, b); + } else { + set_no_autoconversion_error(mjs); + } + return ret; +} + +static void op_assign(struct mjs* mjs, int op) { + mjs_val_t val = mjs_pop(mjs); + mjs_val_t obj = mjs_pop(mjs); + mjs_val_t key = mjs_pop(mjs); + if(mjs_is_object(obj) && mjs_is_string(key)) { + mjs_val_t v = mjs_get_v(mjs, obj, key); + mjs_set_v(mjs, obj, key, do_op(mjs, v, val, op)); + mjs_push(mjs, v); + } else { + mjs_set_errorf(mjs, MJS_TYPE_ERROR, "invalid operand"); + } +} + +static int check_equal(struct mjs* mjs, mjs_val_t a, mjs_val_t b) { + int ret = 0; + if(a == MJS_TAG_NAN && b == MJS_TAG_NAN) { + ret = 0; + } else if(a == b) { + ret = 1; + } else if(mjs_is_number(a) && mjs_is_number(b)) { + /* + * The case of equal numbers is handled above, so here the result is always + * false + */ + ret = 0; + } else if(mjs_is_string(a) && mjs_is_string(b)) { + ret = s_cmp(mjs, a, b) == 0; + } else if(mjs_is_foreign(a) && b == MJS_NULL) { + ret = mjs_get_ptr(mjs, a) == NULL; + } else if(a == MJS_NULL && mjs_is_foreign(b)) { + ret = mjs_get_ptr(mjs, b) == NULL; + } else { + ret = 0; + } + return ret; +} + +static void exec_expr(struct mjs* mjs, int op) { + switch(op) { + case TOK_DOT: + break; + case TOK_MINUS: + case TOK_PLUS: + case TOK_MUL: + case TOK_DIV: + case TOK_REM: + case TOK_XOR: + case TOK_AND: + case TOK_OR: + case TOK_LSHIFT: + case TOK_RSHIFT: + case TOK_URSHIFT: { + mjs_val_t b = mjs_pop(mjs); + mjs_val_t a = mjs_pop(mjs); + mjs_push(mjs, do_op(mjs, a, b, op)); + break; + } + case TOK_UNARY_MINUS: { + double a = mjs_get_double(mjs, mjs_pop(mjs)); + mjs_push(mjs, mjs_mk_number(mjs, -a)); + break; + } + case TOK_NOT: { + mjs_val_t val = mjs_pop(mjs); + mjs_push(mjs, mjs_mk_boolean(mjs, !mjs_is_truthy(mjs, val))); + break; + } + case TOK_TILDA: { + double a = mjs_get_double(mjs, mjs_pop(mjs)); + mjs_push(mjs, mjs_mk_number(mjs, (double)(~(int64_t)a))); + break; + } + case TOK_UNARY_PLUS: + break; + case TOK_EQ: + mjs_set_errorf(mjs, MJS_NOT_IMPLEMENTED_ERROR, "Use ===, not =="); + break; + case TOK_NE: + mjs_set_errorf(mjs, MJS_NOT_IMPLEMENTED_ERROR, "Use !==, not !="); + break; + case TOK_EQ_EQ: { + mjs_val_t a = mjs_pop(mjs); + mjs_val_t b = mjs_pop(mjs); + mjs_push(mjs, mjs_mk_boolean(mjs, check_equal(mjs, a, b))); + break; + } + case TOK_NE_NE: { + mjs_val_t a = mjs_pop(mjs); + mjs_val_t b = mjs_pop(mjs); + mjs_push(mjs, mjs_mk_boolean(mjs, !check_equal(mjs, a, b))); + break; + } + case TOK_LT: { + double b = mjs_get_double(mjs, mjs_pop(mjs)); + double a = mjs_get_double(mjs, mjs_pop(mjs)); + mjs_push(mjs, mjs_mk_boolean(mjs, a < b)); + break; + } + case TOK_GT: { + double b = mjs_get_double(mjs, mjs_pop(mjs)); + double a = mjs_get_double(mjs, mjs_pop(mjs)); + mjs_push(mjs, mjs_mk_boolean(mjs, a > b)); + break; + } + case TOK_LE: { + double b = mjs_get_double(mjs, mjs_pop(mjs)); + double a = mjs_get_double(mjs, mjs_pop(mjs)); + mjs_push(mjs, mjs_mk_boolean(mjs, a <= b)); + break; + } + case TOK_GE: { + double b = mjs_get_double(mjs, mjs_pop(mjs)); + double a = mjs_get_double(mjs, mjs_pop(mjs)); + mjs_push(mjs, mjs_mk_boolean(mjs, a >= b)); + break; + } + case TOK_ASSIGN: { + mjs_val_t val = mjs_pop(mjs); + mjs_val_t obj = mjs_pop(mjs); + mjs_val_t key = mjs_pop(mjs); + if(mjs_is_object(obj)) { + mjs_set_v(mjs, obj, key, val); + } else if(mjs_is_foreign(obj)) { + /* + * We don't have setters, so in order to support properties which behave + * like setters, we have to parse key right here, instead of having real + * built-in prototype objects + */ + + int ikey = mjs_get_int(mjs, key); + int ival = mjs_get_int(mjs, val); + + if(!mjs_is_number(key)) { + mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "index must be a number"); + val = MJS_UNDEFINED; + } else if(!mjs_is_number(val) || ival < 0 || ival > 0xff) { + mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "only number 0 .. 255 can be assigned"); + val = MJS_UNDEFINED; + } else { + uint8_t* ptr = (uint8_t*)mjs_get_ptr(mjs, obj); + *(ptr + ikey) = (uint8_t)ival; + } + } else { + mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "unsupported object type"); + } + mjs_push(mjs, val); + break; + } + case TOK_POSTFIX_PLUS: { + mjs_val_t obj = mjs_pop(mjs); + mjs_val_t key = mjs_pop(mjs); + if(mjs_is_object(obj) && mjs_is_string(key)) { + mjs_val_t v = mjs_get_v(mjs, obj, key); + mjs_val_t v1 = do_op(mjs, v, mjs_mk_number(mjs, 1), TOK_PLUS); + mjs_set_v(mjs, obj, key, v1); + mjs_push(mjs, v); + } else { + mjs_set_errorf(mjs, MJS_TYPE_ERROR, "invalid operand for ++"); + } + break; + } + case TOK_POSTFIX_MINUS: { + mjs_val_t obj = mjs_pop(mjs); + mjs_val_t key = mjs_pop(mjs); + if(mjs_is_object(obj) && mjs_is_string(key)) { + mjs_val_t v = mjs_get_v(mjs, obj, key); + mjs_val_t v1 = do_op(mjs, v, mjs_mk_number(mjs, 1), TOK_MINUS); + mjs_set_v(mjs, obj, key, v1); + mjs_push(mjs, v); + } else { + mjs_set_errorf(mjs, MJS_TYPE_ERROR, "invalid operand for --"); + } + break; + } + case TOK_MINUS_MINUS: { + mjs_val_t obj = mjs_pop(mjs); + mjs_val_t key = mjs_pop(mjs); + if(mjs_is_object(obj) && mjs_is_string(key)) { + mjs_val_t v = mjs_get_v(mjs, obj, key); + v = do_op(mjs, v, mjs_mk_number(mjs, 1), TOK_MINUS); + mjs_set_v(mjs, obj, key, v); + mjs_push(mjs, v); + } else { + mjs_set_errorf(mjs, MJS_TYPE_ERROR, "invalid operand for --"); + } + break; + } + case TOK_PLUS_PLUS: { + mjs_val_t obj = mjs_pop(mjs); + mjs_val_t key = mjs_pop(mjs); + if(mjs_is_object(obj) && mjs_is_string(key)) { + mjs_val_t v = mjs_get_v(mjs, obj, key); + v = do_op(mjs, v, mjs_mk_number(mjs, 1), TOK_PLUS); + mjs_set_v(mjs, obj, key, v); + mjs_push(mjs, v); + } else { + mjs_set_errorf(mjs, MJS_TYPE_ERROR, "invalid operand for ++"); + } + break; + } + /* + * NOTE: TOK_LOGICAL_AND and TOK_LOGICAL_OR don't need to be here, because + * they are just naturally handled by the short-circuit evaluation. + * See PARSE_LTR_BINOP() macro in mjs_parser.c. + */ + + /* clang-format off */ + case TOK_MINUS_ASSIGN: op_assign(mjs, TOK_MINUS); break; + case TOK_PLUS_ASSIGN: op_assign(mjs, TOK_PLUS); break; + case TOK_MUL_ASSIGN: op_assign(mjs, TOK_MUL); break; + case TOK_DIV_ASSIGN: op_assign(mjs, TOK_DIV); break; + case TOK_REM_ASSIGN: op_assign(mjs, TOK_REM); break; + case TOK_AND_ASSIGN: op_assign(mjs, TOK_AND); break; + case TOK_OR_ASSIGN: op_assign(mjs, TOK_OR); break; + case TOK_XOR_ASSIGN: op_assign(mjs, TOK_XOR); break; + case TOK_LSHIFT_ASSIGN: op_assign(mjs, TOK_LSHIFT); break; + case TOK_RSHIFT_ASSIGN: op_assign(mjs, TOK_RSHIFT); break; + case TOK_URSHIFT_ASSIGN: op_assign(mjs, TOK_URSHIFT); break; + case TOK_COMMA: break; + /* clang-format on */ + case TOK_KEYWORD_TYPEOF: + mjs_push(mjs, mjs_mk_string(mjs, mjs_typeof(mjs_pop(mjs)), ~0, 1)); + break; + default: + LOG(LL_ERROR, ("Unknown expr: %d", op)); + break; + } +} + +static int getprop_builtin_string( + struct mjs* mjs, + mjs_val_t val, + const char* name, + size_t name_len, + mjs_val_t* res) { + int isnum = 0; + int idx = cstr_to_ulong(name, name_len, &isnum); + + if(strcmp(name, "length") == 0) { + size_t val_len; + mjs_get_string(mjs, &val, &val_len); + *res = mjs_mk_number(mjs, (double)val_len); + return 1; + } else if(strcmp(name, "at") == 0 || strcmp(name, "charCodeAt") == 0) { + *res = mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_string_char_code_at); + return 1; + } else if(strcmp(name, "indexOf") == 0) { + *res = mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_string_index_of); + return 1; + } else if(strcmp(name, "slice") == 0) { + *res = mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_string_slice); + return 1; + } else if(isnum) { + /* + * string subscript: return a new one-byte string if the index + * is not out of bounds + */ + size_t val_len; + const char* str = mjs_get_string(mjs, &val, &val_len); + if(idx >= 0 && idx < (int)val_len) { + *res = mjs_mk_string(mjs, str + idx, 1, 1); + } else { + *res = MJS_UNDEFINED; + } + return 1; + } + return 0; +} + +static int getprop_builtin_array( + struct mjs* mjs, + mjs_val_t val, + const char* name, + size_t name_len, + mjs_val_t* res) { + if(strcmp(name, "splice") == 0) { + *res = mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_array_splice); + return 1; + } else if(strcmp(name, "push") == 0) { + *res = mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_array_push_internal); + return 1; + } else if(strcmp(name, "length") == 0) { + *res = mjs_mk_number(mjs, mjs_array_length(mjs, val)); + return 1; + } + + (void)name_len; + return 0; +} + +static int getprop_builtin_foreign( + struct mjs* mjs, + mjs_val_t val, + const char* name, + size_t name_len, + mjs_val_t* res) { + int isnum = 0; + int idx = cstr_to_ulong(name, name_len, &isnum); + + if(!isnum) { + mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "index must be a number"); + } else { + uint8_t* ptr = (uint8_t*)mjs_get_ptr(mjs, val); + *res = mjs_mk_number(mjs, *(ptr + idx)); + } + return 1; +} + +static void mjs_apply_(struct mjs* mjs) { + mjs_val_t res = MJS_UNDEFINED, *args = NULL; + mjs_val_t func = mjs->vals.this_obj, v = mjs_arg(mjs, 1); + int i, nargs = 0; + if(mjs_is_array(v)) { + nargs = mjs_array_length(mjs, v); + args = calloc(nargs, sizeof(args[0])); + for(i = 0; i < nargs; i++) args[i] = mjs_array_get(mjs, v, i); + } + mjs_apply(mjs, &res, func, mjs_arg(mjs, 0), nargs, args); + free(args); + mjs_return(mjs, res); +} + +static int getprop_builtin(struct mjs* mjs, mjs_val_t val, mjs_val_t name, mjs_val_t* res) { + size_t n; + char* s = NULL; + int need_free = 0; + int handled = 0; + + mjs_err_t err = mjs_to_string(mjs, &name, &s, &n, &need_free); + + if(err == MJS_OK) { + if(mjs_is_string(val)) { + handled = getprop_builtin_string(mjs, val, s, n, res); + } else if(s != NULL && n == 5 && strncmp(s, "apply", n) == 0) { + *res = mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_apply_); + handled = 1; + } else if(mjs_is_array(val)) { + handled = getprop_builtin_array(mjs, val, s, n, res); + } else if(mjs_is_foreign(val)) { + handled = getprop_builtin_foreign(mjs, val, s, n, res); + } + } + + if(need_free) { + free(s); + s = NULL; + } + + return handled; +} + +MJS_PRIVATE mjs_err_t mjs_execute(struct mjs* mjs, size_t off, mjs_val_t* res) { + size_t i; + uint8_t prev_opcode = OP_MAX; + uint8_t opcode = OP_MAX; + + /* + * remember lengths of all stacks, they will be restored in case of an error + */ + int stack_len = mjs->stack.len; + int call_stack_len = mjs->call_stack.len; + int arg_stack_len = mjs->arg_stack.len; + int scopes_len = mjs->scopes.len; + int loop_addresses_len = mjs->loop_addresses.len; + size_t start_off = off; + const uint8_t* code; + + struct mjs_bcode_part bp = *mjs_bcode_part_get_by_offset(mjs, off); + + mjs_set_errorf(mjs, MJS_OK, NULL); + free(mjs->stack_trace); + mjs->stack_trace = NULL; + + off -= bp.start_idx; + + for(i = off; i < bp.data.len; i++) { + mjs->cur_bcode_offset = i; + + if(mjs->need_gc) { + if(maybe_gc(mjs)) { + mjs->need_gc = 0; + } + } +#if MJS_AGGRESSIVE_GC + maybe_gc(mjs); +#endif + + code = (const uint8_t*)bp.data.p; +#if MJS_ENABLE_DEBUG + mjs_disasm_single(code, i); +#endif + prev_opcode = opcode; + opcode = code[i]; + switch(opcode) { + case OP_BCODE_HEADER: { + mjs_header_item_t bcode_offset; + memcpy( + &bcode_offset, + code + i + 1 + sizeof(mjs_header_item_t) * MJS_HDR_ITEM_BCODE_OFFSET, + sizeof(bcode_offset)); + i += bcode_offset; + } break; + case OP_PUSH_NULL: + mjs_push(mjs, mjs_mk_null()); + break; + case OP_PUSH_UNDEF: + mjs_push(mjs, mjs_mk_undefined()); + break; + case OP_PUSH_FALSE: + mjs_push(mjs, mjs_mk_boolean(mjs, 0)); + break; + case OP_PUSH_TRUE: + mjs_push(mjs, mjs_mk_boolean(mjs, 1)); + break; + case OP_PUSH_OBJ: + mjs_push(mjs, mjs_mk_object(mjs)); + break; + case OP_PUSH_ARRAY: + mjs_push(mjs, mjs_mk_array(mjs)); + break; + case OP_PUSH_FUNC: { + int llen, n = cs_varint_decode_unsafe(&code[i + 1], &llen); + mjs_push(mjs, mjs_mk_function(mjs, bp.start_idx + i - n)); + i += llen; + break; + } + case OP_PUSH_THIS: + mjs_push(mjs, mjs->vals.this_obj); + break; + case OP_JMP: { + int llen, n = cs_varint_decode_unsafe(&code[i + 1], &llen); + i += n + llen; + break; + } + case OP_JMP_FALSE: { + int llen, n = cs_varint_decode_unsafe(&code[i + 1], &llen); + i += llen; + if(!mjs_is_truthy(mjs, mjs_pop(mjs))) { + mjs_push(mjs, MJS_UNDEFINED); + i += n; + } + break; + } + /* + * OP_JMP_NEUTRAL_... ops are like as OP_JMP_..., but they are completely + * stack-neutral: they just check the TOS, and increment instruction + * pointer if the TOS is truthy/falsy. + */ + case OP_JMP_NEUTRAL_TRUE: { + int llen, n = cs_varint_decode_unsafe(&code[i + 1], &llen); + i += llen; + if(mjs_is_truthy(mjs, vtop(&mjs->stack))) { + i += n; + } + break; + } + case OP_JMP_NEUTRAL_FALSE: { + int llen, n = cs_varint_decode_unsafe(&code[i + 1], &llen); + i += llen; + if(!mjs_is_truthy(mjs, vtop(&mjs->stack))) { + i += n; + } + break; + } + case OP_FIND_SCOPE: { + mjs_val_t key = vtop(&mjs->stack); + mjs_push(mjs, mjs_find_scope(mjs, key)); + break; + } + case OP_CREATE: { + mjs_val_t obj = mjs_pop(mjs); + mjs_val_t key = mjs_pop(mjs); + if(mjs_get_own_property_v(mjs, obj, key) == NULL) { + mjs_set_v(mjs, obj, key, MJS_UNDEFINED); + } + break; + } + case OP_APPEND: { + mjs_val_t val = mjs_pop(mjs); + mjs_val_t arr = mjs_pop(mjs); + mjs_err_t err = mjs_array_push(mjs, arr, val); + if(err != MJS_OK) { + mjs_set_errorf(mjs, MJS_TYPE_ERROR, "append to non-array"); + } + break; + } + case OP_GET: { + mjs_val_t obj = mjs_pop(mjs); + mjs_val_t key = mjs_pop(mjs); + mjs_val_t val = MJS_UNDEFINED; + + if(!getprop_builtin(mjs, obj, key, &val)) { + if(mjs_is_object(obj)) { + val = mjs_get_v_proto(mjs, obj, key); + } else { + mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "type error"); + } + } + + mjs_push(mjs, val); + if(prev_opcode != OP_FIND_SCOPE) { + /* + * Previous opcode was not OP_FIND_SCOPE, so it's some "custom" + * object which might be used as `this`, so, save it + */ + mjs->vals.last_getprop_obj = obj; + } else { + /* + * Previous opcode was OP_FIND_SCOPE, so we're getting value from + * the scope, and it should *not* be used as `this` + */ + mjs->vals.last_getprop_obj = MJS_UNDEFINED; + } + break; + } + case OP_DEL_SCOPE: + if(mjs->scopes.len <= 1) { + mjs_set_errorf(mjs, MJS_INTERNAL_ERROR, "scopes underflow"); + } else { + mjs_pop_val(&mjs->scopes); + } + break; + case OP_NEW_SCOPE: + push_mjs_val(&mjs->scopes, mjs_mk_object(mjs)); + break; + case OP_PUSH_SCOPE: + assert(mjs_stack_size(&mjs->scopes) > 0); + mjs_push(mjs, vtop(&mjs->scopes)); + break; + case OP_PUSH_STR: { + int llen, n = cs_varint_decode_unsafe(&code[i + 1], &llen); + mjs_push(mjs, mjs_mk_string(mjs, (char*)code + i + 1 + llen, n, 1)); + i += llen + n; + break; + } + case OP_PUSH_INT: { + int llen; + int64_t n = cs_varint_decode_unsafe(&code[i + 1], &llen); + mjs_push(mjs, mjs_mk_number(mjs, (double)n)); + i += llen; + break; + } + case OP_PUSH_DBL: { + int llen, n = cs_varint_decode_unsafe(&code[i + 1], &llen); + mjs_push(mjs, mjs_mk_number(mjs, strtod((char*)code + i + 1 + llen, NULL))); + i += llen + n; + break; + } + case OP_FOR_IN_NEXT: { + /* + * Data stack layout: + * ... <-- Bottom of the data stack + * (string) + * (object) + * <-- Top of the data stack + */ + mjs_val_t* iterator = vptr(&mjs->stack, -1); + mjs_val_t obj = *vptr(&mjs->stack, -2); + if(mjs_is_object(obj)) { + mjs_val_t var_name = *vptr(&mjs->stack, -3); + mjs_val_t key = mjs_next(mjs, obj, iterator); + if(key != MJS_UNDEFINED) { + mjs_val_t scope = mjs_find_scope(mjs, var_name); + mjs_set_v(mjs, scope, var_name, key); + } + } else { + mjs_set_errorf(mjs, MJS_TYPE_ERROR, "can't iterate over non-object value"); + } + break; + } + case OP_RETURN: { + /* + * Return address is saved as a global bcode offset, so we need to + * convert it to the local offset + */ + size_t off_ret = call_stack_restore_frame(mjs); + if(off_ret != MJS_BCODE_OFFSET_EXIT) { + bp = *mjs_bcode_part_get_by_offset(mjs, off_ret); + code = (const uint8_t*)bp.data.p; + i = off_ret - bp.start_idx; + LOG(LL_VERBOSE_DEBUG, ("RETURNING TO %d", (int)off_ret + 1)); + } else { + goto clean; + } + // mjs_dump(mjs, 0, stdout); + break; + } + case OP_ARGS: { + /* + * If OP_ARGS follows OP_GET, then last_getprop_obj is set to `this` + * value; otherwise, last_getprop_obj is irrelevant and we have to + * reset it to `undefined` + */ + if(prev_opcode != OP_GET) { + mjs->vals.last_getprop_obj = MJS_UNDEFINED; + } + + /* + * Push last_getprop_obj, which is going to be used as `this`, see + * OP_CALL + */ + push_mjs_val(&mjs->arg_stack, mjs->vals.last_getprop_obj); + /* + * Push current size of data stack, it's needed to place arguments + * properly + */ + push_mjs_val(&mjs->arg_stack, mjs_mk_number(mjs, (double)mjs_stack_size(&mjs->stack))); + break; + } + case OP_CALL: { + // LOG(LL_INFO, ("BEFORE CALL")); + // mjs_dump(mjs, 0, stdout); + int func_pos; + mjs_val_t* func; + mjs_val_t retval_stack_idx = vtop(&mjs->arg_stack); + func_pos = mjs_get_int(mjs, retval_stack_idx) - 1; + func = vptr(&mjs->stack, func_pos); + + /* Drop data stack size (pushed by OP_ARGS) */ + mjs_pop_val(&mjs->arg_stack); + + if(mjs_is_function(*func)) { + size_t off_call; + call_stack_push_frame(mjs, bp.start_idx + i, retval_stack_idx); + + /* + * Function offset is a global bcode offset, so we need to convert it + * to the local offset + */ + off_call = mjs_get_func_addr(*func) - 1; + bp = *mjs_bcode_part_get_by_offset(mjs, off_call); + code = (const uint8_t*)bp.data.p; + i = off_call - bp.start_idx; + + *func = MJS_UNDEFINED; // Return value + // LOG(LL_VERBOSE_DEBUG, ("CALLING %d", i + 1)); + } else if(mjs_is_string(*func) || mjs_is_ffi_sig(*func)) { + /* Call ffi-ed function */ + + call_stack_push_frame(mjs, bp.start_idx + i, retval_stack_idx); + + /* Perform the ffi-ed function call */ + mjs_ffi_call2(mjs); + + call_stack_restore_frame(mjs); + } else if(mjs_is_foreign(*func)) { + /* Call cfunction */ + + call_stack_push_frame(mjs, bp.start_idx + i, retval_stack_idx); + + /* Perform the cfunction call */ + ((void (*)(struct mjs*))mjs_get_ptr(mjs, *func))(mjs); + + call_stack_restore_frame(mjs); + } else { + mjs_set_errorf(mjs, MJS_TYPE_ERROR, "calling non-callable"); + } + break; + } + case OP_SET_ARG: { + int llen1, llen2, n, arg_no = cs_varint_decode_unsafe(&code[i + 1], &llen1); + mjs_val_t obj, key, v; + n = cs_varint_decode_unsafe(&code[i + llen1 + 1], &llen2); + key = mjs_mk_string(mjs, (char*)code + i + 1 + llen1 + llen2, n, 1); + obj = vtop(&mjs->scopes); + v = mjs_arg(mjs, arg_no); + mjs_set_v(mjs, obj, key, v); + i += llen1 + llen2 + n; + break; + } + case OP_SETRETVAL: { + if(mjs_stack_size(&mjs->call_stack) < CALL_STACK_FRAME_ITEMS_CNT) { + mjs_set_errorf(mjs, MJS_INTERNAL_ERROR, "cannot return"); + } else { + size_t retval_pos = mjs_get_int( + mjs, *vptr(&mjs->call_stack, -1 - CALL_STACK_FRAME_ITEM_RETVAL_STACK_IDX)); + *vptr(&mjs->stack, retval_pos - 1) = mjs_pop(mjs); + } + // LOG(LL_INFO, ("AFTER SETRETVAL")); + // mjs_dump(mjs, 0, stdout); + break; + } + case OP_EXPR: { + int op = code[i + 1]; + exec_expr(mjs, op); + i++; + break; + } + case OP_DROP: { + mjs_pop(mjs); + break; + } + case OP_DUP: { + mjs_push(mjs, vtop(&mjs->stack)); + break; + } + case OP_SWAP: { + mjs_val_t a = mjs_pop(mjs); + mjs_val_t b = mjs_pop(mjs); + mjs_push(mjs, a); + mjs_push(mjs, b); + break; + } + case OP_LOOP: { + int l1, l2, off = cs_varint_decode_unsafe(&code[i + 1], &l1); + /* push scope index */ + push_mjs_val( + &mjs->loop_addresses, mjs_mk_number(mjs, (double)mjs_stack_size(&mjs->scopes))); + + /* push break offset */ + push_mjs_val( + &mjs->loop_addresses, + mjs_mk_number(mjs, (double)(i + 1 /* OP_LOOP */ + l1 + off))); + off = cs_varint_decode_unsafe(&code[i + 1 + l1], &l2); + + /* push continue offset */ + push_mjs_val( + &mjs->loop_addresses, + mjs_mk_number(mjs, (double)(i + 1 /* OP_LOOP*/ + l1 + l2 + off))); + i += l1 + l2; + break; + } + case OP_CONTINUE: { + if(mjs_stack_size(&mjs->loop_addresses) >= 3) { + size_t scopes_len = mjs_get_int(mjs, *vptr(&mjs->loop_addresses, -3)); + assert(mjs_stack_size(&mjs->scopes) >= scopes_len); + mjs->scopes.len = scopes_len * sizeof(mjs_val_t); + + /* jump to "continue" address */ + i = mjs_get_int(mjs, vtop(&mjs->loop_addresses)) - 1; + } else { + mjs_set_errorf(mjs, MJS_SYNTAX_ERROR, "misplaced 'continue'"); + } + } break; + case OP_BREAK: { + if(mjs_stack_size(&mjs->loop_addresses) >= 3) { + size_t scopes_len; + /* drop "continue" address */ + mjs_pop_val(&mjs->loop_addresses); + + /* pop "break" address and jump to it */ + i = mjs_get_int(mjs, mjs_pop_val(&mjs->loop_addresses)) - 1; + + /* restore scope index */ + scopes_len = mjs_get_int(mjs, mjs_pop_val(&mjs->loop_addresses)); + assert(mjs_stack_size(&mjs->scopes) >= scopes_len); + mjs->scopes.len = scopes_len * sizeof(mjs_val_t); + + LOG(LL_VERBOSE_DEBUG, ("BREAKING TO %d", (int)i + 1)); + } else { + mjs_set_errorf(mjs, MJS_SYNTAX_ERROR, "misplaced 'break'"); + } + } break; + case OP_NOP: + break; + case OP_EXIT: + i = bp.data.len; + break; + default: +#if MJS_ENABLE_DEBUG + mjs_dump(mjs, 1); +#endif + mjs_set_errorf( + mjs, + MJS_INTERNAL_ERROR, + "Unknown opcode: %d, off %d+%d", + (int)opcode, + (int)bp.start_idx, + (int)i); + i = bp.data.len; + break; + } + if(mjs->error != MJS_OK) { + mjs_gen_stack_trace(mjs, bp.start_idx + i - 1 /* undo the i++ */); + + /* restore stack lenghts */ + mjs->stack.len = stack_len; + mjs->call_stack.len = call_stack_len; + mjs->arg_stack.len = arg_stack_len; + mjs->scopes.len = scopes_len; + mjs->loop_addresses.len = loop_addresses_len; + + /* script will evaluate to `undefined` */ + mjs_push(mjs, MJS_UNDEFINED); + break; + } + } + +clean: + /* Remember result of the evaluation of this bcode part */ + mjs_bcode_part_get_by_offset(mjs, start_off)->exec_res = mjs->error; + + *res = mjs_pop(mjs); + return mjs->error; +} + +MJS_PRIVATE mjs_err_t mjs_exec_internal( + struct mjs* mjs, + const char* path, + const char* src, + int generate_jsc, + mjs_val_t* res) { + size_t off = mjs->bcode_len; + mjs_val_t r = MJS_UNDEFINED; + mjs->error = mjs_parse(path, src, mjs); +#if MJS_ENABLE_DEBUG + if(cs_log_level >= LL_VERBOSE_DEBUG) mjs_dump(mjs, 1); +#endif + if(generate_jsc == -1) generate_jsc = mjs->generate_jsc; + if(mjs->error == MJS_OK) { +#if MJS_GENERATE_JSC && defined(CS_MMAP) + if(generate_jsc && path != NULL) { + const char* jsext = ".js"; + int basename_len = (int)strlen(path) - strlen(jsext); + if(basename_len > 0 && strcmp(path + basename_len, jsext) == 0) { + /* source file has a .js extension: create a .jsc counterpart */ + int rewrite = 1; + int read_mmapped = 1; + + /* construct .jsc filename */ + const char* jscext = ".jsc"; + char filename_jsc[basename_len + strlen(jscext) + 1 /* nul-term */]; + memcpy(filename_jsc, path, basename_len); + strcpy(filename_jsc + basename_len, jscext); + + /* get last bcode part */ + struct mjs_bcode_part* bp = mjs_bcode_part_get(mjs, mjs_bcode_parts_cnt(mjs) - 1); + + /* + * before writing .jsc file, check if it already exists and has the + * same contents + * + * TODO(dfrank): probably store crc32 before the bcode data, and only + * compare it. + */ + { + size_t size; + char* data = cs_mmap_file(filename_jsc, &size); + if(data != NULL) { + if(size == bp->data.len) { + if(memcmp(data, bp->data.p, size) == 0) { + /* .jsc file is up to date, so don't rewrite it */ + rewrite = 0; + } + } + munmap(data, size); + } + } + + /* try to open .jsc file for writing */ + if(rewrite) { + FILE* fp = fopen(filename_jsc, "wb"); + if(fp != NULL) { + /* write last bcode part to .jsc */ + fwrite(bp->data.p, bp->data.len, 1, fp); + fclose(fp); + } else { + LOG(LL_WARN, ("Failed to open %s for writing", filename_jsc)); + read_mmapped = 0; + } + } + + if(read_mmapped) { + /* free RAM buffer with last bcode part */ + free((void*)bp->data.p); + + /* mmap .jsc file and set last bcode part buffer to it */ + bp->data.p = cs_mmap_file(filename_jsc, &bp->data.len); + bp->in_rom = 1; + } + } + } +#else + (void)generate_jsc; +#endif + + mjs_execute(mjs, off, &r); + } + if(res != NULL) *res = r; + return mjs->error; +} + +mjs_err_t mjs_exec(struct mjs* mjs, const char* src, mjs_val_t* res) { + return mjs_exec_internal(mjs, "", src, 0 /* generate_jsc */, res); +} + +mjs_err_t mjs_exec_file(struct mjs* mjs, const char* path, mjs_val_t* res) { + mjs_err_t error = MJS_FILE_READ_ERROR; + mjs_val_t r = MJS_UNDEFINED; + size_t size; + char* source_code = cs_read_file(path, &size); + + if(source_code == NULL) { + error = MJS_FILE_READ_ERROR; + mjs_prepend_errorf(mjs, error, "failed to read file \"%s\"", path); + goto clean; + } + + r = MJS_UNDEFINED; + error = mjs_exec_internal(mjs, path, source_code, -1, &r); + free(source_code); + +clean: + if(res != NULL) *res = r; + return error; +} + +mjs_err_t + mjs_call(struct mjs* mjs, mjs_val_t* res, mjs_val_t func, mjs_val_t this_val, int nargs, ...) { + va_list ap; + int i; + mjs_err_t ret; + mjs_val_t* args = calloc(nargs, sizeof(mjs_val_t)); + va_start(ap, nargs); + for(i = 0; i < nargs; i++) { + args[i] = va_arg(ap, mjs_val_t); + } + va_end(ap); + + ret = mjs_apply(mjs, res, func, this_val, nargs, args); + + free(args); + return ret; +} + +mjs_err_t mjs_apply( + struct mjs* mjs, + mjs_val_t* res, + mjs_val_t func, + mjs_val_t this_val, + int nargs, + mjs_val_t* args) { + mjs_val_t r, prev_this_val, retval_stack_idx, *resp; + int i; + + if(!mjs_is_function(func) && !mjs_is_foreign(func) && !mjs_is_ffi_sig(func)) { + return mjs_set_errorf(mjs, MJS_TYPE_ERROR, "calling non-callable"); + } + + LOG(LL_VERBOSE_DEBUG, ("applying func %d", (int)mjs_get_func_addr(func))); + + prev_this_val = mjs->vals.this_obj; + + /* Push callable which will be later replaced with the return value */ + mjs_push(mjs, func); + resp = vptr(&mjs->stack, -1); + + /* Remember index by which return value should be written */ + retval_stack_idx = mjs_mk_number(mjs, (double)mjs_stack_size(&mjs->stack)); + + // Push all arguments + for(i = 0; i < nargs; i++) { + mjs_push(mjs, args[i]); + } + + /* Push this value to arg_stack, call_stack_push_frame() expects that */ + push_mjs_val(&mjs->arg_stack, this_val); + + /* Push call stack frame, just like OP_CALL does that */ + call_stack_push_frame(mjs, MJS_BCODE_OFFSET_EXIT, retval_stack_idx); + + if(mjs_is_foreign(func)) { + ((void (*)(struct mjs*))mjs_get_ptr(mjs, func))(mjs); + if(res != NULL) *res = *resp; + } else if(mjs_is_ffi_sig(func)) { + mjs_ffi_call2(mjs); + if(res != NULL) *res = *resp; + } else { + size_t addr = mjs_get_func_addr(func); + mjs_execute(mjs, addr, &r); + if(res != NULL) *res = r; + } + + /* + * If there was an error, we need to restore frame and do the cleanup + * which is otherwise done by OP_RETURN + */ + if(mjs->error != MJS_OK) { + call_stack_restore_frame(mjs); + + // Pop cell at which the returned value should've been written + mjs_pop(mjs); + } + mjs->vals.this_obj = prev_this_val; + + return mjs->error; +} diff --git a/applications/system/elk_mjs/lib/mjs/mjs_exec.h b/applications/system/elk_mjs/lib/mjs/mjs_exec.h new file mode 100644 index 00000000000..07465a5c685 --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/mjs_exec.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2016 Cesanta Software Limited + * All rights reserved + */ + +#ifndef MJS_EXEC_H_ +#define MJS_EXEC_H_ + +#include "mjs_exec_public.h" + +/* + * A special bcode offset value which causes mjs_execute() to exit immediately; + * used in mjs_apply(). + */ +#define MJS_BCODE_OFFSET_EXIT ((size_t)0x7fffffff) + +#if defined(__cplusplus) +extern "C" { +#endif /* __cplusplus */ + +MJS_PRIVATE mjs_err_t mjs_execute(struct mjs* mjs, size_t off, mjs_val_t* res); + +#if defined(__cplusplus) +} +#endif /* __cplusplus */ + +#endif /* MJS_EXEC_H_ */ diff --git a/applications/system/elk_mjs/lib/mjs/mjs_exec_public.h b/applications/system/elk_mjs/lib/mjs/mjs_exec_public.h new file mode 100644 index 00000000000..1d65435a548 --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/mjs_exec_public.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2016 Cesanta Software Limited + * All rights reserved + */ + +#ifndef MJS_EXEC_PUBLIC_H_ +#define MJS_EXEC_PUBLIC_H_ + +#include "mjs_core_public.h" +#include + +#if defined(__cplusplus) +extern "C" { +#endif /* __cplusplus */ + +mjs_err_t mjs_exec(struct mjs*, const char* src, mjs_val_t* res); +mjs_err_t mjs_exec_buf(struct mjs*, const char* src, size_t, mjs_val_t* res); + +mjs_err_t mjs_exec_file(struct mjs* mjs, const char* path, mjs_val_t* res); +mjs_err_t mjs_apply( + struct mjs* mjs, + mjs_val_t* res, + mjs_val_t func, + mjs_val_t this_val, + int nargs, + mjs_val_t* args); +mjs_err_t + mjs_call(struct mjs* mjs, mjs_val_t* res, mjs_val_t func, mjs_val_t this_val, int nargs, ...); +mjs_val_t mjs_get_this(struct mjs* mjs); + +#if defined(__cplusplus) +} +#endif /* __cplusplus */ + +#endif /* MJS_EXEC_PUBLIC_H_ */ diff --git a/applications/system/elk_mjs/lib/mjs/mjs_features.h b/applications/system/elk_mjs/lib/mjs/mjs_features.h new file mode 100644 index 00000000000..6706f32389b --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/mjs_features.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2017 Cesanta Software Limited + * All rights reserved + */ + +#ifndef MJS_FEATURES_H_ +#define MJS_FEATURES_H_ + +#if !defined(MJS_AGGRESSIVE_GC) +#define MJS_AGGRESSIVE_GC 0 +#endif + +#if !defined(MJS_MEMORY_STATS) +#define MJS_MEMORY_STATS 0 +#endif + +/* + * MJS_GENERATE_JSC: if enabled, and if mmapping is also enabled (CS_MMAP), + * then execution of any .js file will result in creation of a .jsc file with + * precompiled bcode, and this .jsc file will be mmapped, instead of keeping + * bcode in RAM. + * + * By default it's enabled (provided that CS_MMAP is defined) + */ +#if !defined(MJS_GENERATE_JSC) +#if defined(CS_MMAP) +#define MJS_GENERATE_JSC 1 +#else +#define MJS_GENERATE_JSC 0 +#endif +#endif + +#endif /* MJS_FEATURES_H_ */ diff --git a/applications/system/elk_mjs/lib/mjs/mjs_ffi.c b/applications/system/elk_mjs/lib/mjs/mjs_ffi.c new file mode 100644 index 00000000000..ccfc7ef15e1 --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/mjs_ffi.c @@ -0,0 +1,1215 @@ +/* + * Copyright (c) 2017 Cesanta Software Limited + * All rights reserved + */ + +#include "common/mg_str.h" + +#include "ffi/ffi.h" +#include "mjs_core.h" +#include "mjs_exec.h" +#include "mjs_internal.h" +#include "mjs_primitive.h" +#include "mjs_string.h" +#include "mjs_util.h" + +/* + * on linux this is enabled only if __USE_GNU is defined, but we cannot set it + * because dlfcn could have been included already. + */ +#ifndef RTLD_DEFAULT +#define RTLD_DEFAULT NULL +#endif + +static ffi_fn_t* get_cb_impl_by_signature(const mjs_ffi_sig_t* sig); + +/* + * Data of the two related arguments: callback function pointer and the + * userdata for it + */ +struct cbdata { + /* JS callback function */ + mjs_val_t func; + /* JS userdata */ + mjs_val_t userdata; + + /* index of the function pointer param */ + int8_t func_idx; + /* index of the userdata param */ + int8_t userdata_idx; +}; + +void mjs_set_ffi_resolver(struct mjs* mjs, mjs_ffi_resolver_t* dlsym) { + mjs->dlsym = dlsym; +} + +static mjs_ffi_ctype_t parse_cval_type(struct mjs* mjs, const char* s, const char* e) { + struct mg_str ms = MG_NULL_STR; + /* Trim leading and trailing whitespace */ + while(s < e && isspace((int)*s)) s++; + while(e > s && isspace((int)e[-1])) e--; + ms.p = s; + ms.len = e - s; + if(mg_vcmp(&ms, "void") == 0) { + return MJS_FFI_CTYPE_NONE; + } else if(mg_vcmp(&ms, "userdata") == 0) { + return MJS_FFI_CTYPE_USERDATA; + } else if(mg_vcmp(&ms, "int") == 0) { + return MJS_FFI_CTYPE_INT; + } else if(mg_vcmp(&ms, "bool") == 0) { + return MJS_FFI_CTYPE_BOOL; + } else if(mg_vcmp(&ms, "double") == 0) { + return MJS_FFI_CTYPE_DOUBLE; + } else if(mg_vcmp(&ms, "float") == 0) { + return MJS_FFI_CTYPE_FLOAT; + } else if(mg_vcmp(&ms, "char*") == 0 || mg_vcmp(&ms, "char *") == 0) { + return MJS_FFI_CTYPE_CHAR_PTR; + } else if(mg_vcmp(&ms, "void*") == 0 || mg_vcmp(&ms, "void *") == 0) { + return MJS_FFI_CTYPE_VOID_PTR; + } else if(mg_vcmp(&ms, "struct mg_str") == 0) { + return MJS_FFI_CTYPE_STRUCT_MG_STR; + } else if(mg_vcmp(&ms, "struct mg_str *") == 0 || mg_vcmp(&ms, "struct mg_str*") == 0) { + return MJS_FFI_CTYPE_STRUCT_MG_STR_PTR; + } else { + mjs_prepend_errorf( + mjs, MJS_TYPE_ERROR, "failed to parse val type \"%.*s\"", (int)ms.len, ms.p); + return MJS_FFI_CTYPE_INVALID; + } +} + +static const char* find_paren(const char* s, const char* e) { + for(; s < e; s++) { + if(*s == '(') return s; + } + return NULL; +} + +static const char* find_closing_paren(const char* s, const char* e) { + int nesting = 1; + while(s < e) { + if(*s == '(') { + nesting++; + } else if(*s == ')') { + if(--nesting == 0) break; + } + s++; + } + return (s < e ? s : NULL); +} + +MJS_PRIVATE mjs_err_t mjs_parse_ffi_signature( + struct mjs* mjs, + const char* s, + int sig_len, + mjs_ffi_sig_t* sig, + enum ffi_sig_type sig_type) { + mjs_err_t ret = MJS_OK; + int vtidx = 0; + const char *cur, *e, *tmp_e, *tmp; + struct mg_str rt = MG_NULL_STR, fn = MG_NULL_STR, args = MG_NULL_STR; + mjs_ffi_ctype_t val_type = MJS_FFI_CTYPE_INVALID; + if(sig_len == ~0) { + sig_len = strlen(s); + } + e = s + sig_len; + + mjs_ffi_sig_init(sig); + + /* Skip leading spaces */ + for(cur = s; cur < e && isspace((int)*cur); cur++) + ; + + /* FInd the first set of parens */ + tmp_e = find_paren(cur, e); + if(tmp_e == NULL || tmp_e - s < 2) { + ret = MJS_TYPE_ERROR; + mjs_prepend_errorf(mjs, ret, "1"); + goto clean; + } + tmp = find_closing_paren(tmp_e + 1, e); + if(tmp == NULL) { + ret = MJS_TYPE_ERROR; + mjs_prepend_errorf(mjs, ret, "2"); + goto clean; + } + + /* Now see if we have a second set of parens */ + args.p = find_paren(tmp + 1, e); + if(args.p == NULL) { + /* We don't - it's a regular function signature */ + fn.p = tmp_e - 1; + while(fn.p > cur && isspace((int)*fn.p)) fn.p--; + while(fn.p > cur && (isalnum((int)*fn.p) || *fn.p == '_')) { + fn.p--; + fn.len++; + } + fn.p++; + rt.p = cur; + rt.len = fn.p - rt.p; + /* Stuff inside parens is args */ + args.p = tmp_e + 1; + args.len = tmp - args.p; + } else { + /* We do - it's a function pointer, like void (*foo)(...). + * Stuff inside the first pair of parens is the function name */ + fn.p = tmp + 1; + fn.len = args.p - tmp; + rt.p = cur; + rt.len = tmp_e - rt.p; + args.p++; + tmp = find_closing_paren(args.p, e); + if(tmp == NULL) { + ret = MJS_TYPE_ERROR; + mjs_prepend_errorf(mjs, ret, "3"); + goto clean; + } + args.len = tmp - args.p; + /* + * We ignore the name and leave sig->fn NULL here, but it will later be + * set to the appropriate callback implementation. + */ + sig->is_callback = 1; + } + + val_type = parse_cval_type(mjs, rt.p, rt.p + rt.len); + if(val_type == MJS_FFI_CTYPE_INVALID) { + ret = mjs->error; + goto clean; + } + mjs_ffi_sig_set_val_type(sig, vtidx++, val_type); + + /* Parse function name {{{ */ + if(!sig->is_callback) { + char buf[100]; + if(mjs->dlsym == NULL) { + ret = MJS_TYPE_ERROR; + mjs_prepend_errorf(mjs, ret, "resolver is not set, call mjs_set_ffi_resolver"); + goto clean; + } + + snprintf(buf, sizeof(buf), "%.*s", (int)fn.len, fn.p); + sig->fn = (ffi_fn_t*)mjs->dlsym(RTLD_DEFAULT, buf); + if(sig->fn == NULL) { + ret = MJS_TYPE_ERROR; + mjs_prepend_errorf(mjs, ret, "dlsym('%s') failed", buf); + goto clean; + } + } else { + tmp_e = strchr(tmp_e, ')'); + if(tmp_e == NULL) { + ret = MJS_TYPE_ERROR; + goto clean; + } + } + + /* Advance cur to the beginning of the arg list */ + cur = tmp_e = args.p; + + /* Parse all args {{{ */ + while(tmp_e - args.p < (ptrdiff_t)args.len) { + int level = 0; /* nested parens level */ + int is_fp = 0; /* set to 1 is current arg is a callback function ptr */ + tmp_e = cur; + + /* Advance tmp_e until the next arg separator */ + while(*tmp_e && (level > 0 || (*tmp_e != ',' && *tmp_e != ')'))) { + switch(*tmp_e) { + case '(': + level++; + /* + * only function pointer params can have parens, so, set the flag + * that it's going to be a function pointer + */ + is_fp = 1; + break; + case ')': + level--; + break; + } + tmp_e++; + } + + if(tmp_e == cur) break; + + /* Parse current arg */ + if(is_fp) { + /* Current argument is a callback function pointer */ + if(sig->cb_sig != NULL) { + /* + * We already have parsed some callback argument. Currently we don't + * support more than one callback argument, so, return error + * TODO(dfrank): probably improve + */ + ret = MJS_TYPE_ERROR; + mjs_prepend_errorf(mjs, ret, "only one callback is allowed"); + goto clean; + } + + sig->cb_sig = calloc(sizeof(*sig->cb_sig), 1); + ret = mjs_parse_ffi_signature(mjs, cur, tmp_e - cur, sig->cb_sig, FFI_SIG_CALLBACK); + if(ret != MJS_OK) { + mjs_ffi_sig_free(sig->cb_sig); + free(sig->cb_sig); + sig->cb_sig = NULL; + goto clean; + } + val_type = MJS_FFI_CTYPE_CALLBACK; + } else { + /* Some non-function argument */ + val_type = parse_cval_type(mjs, cur, tmp_e); + if(val_type == MJS_FFI_CTYPE_INVALID) { + /* parse_cval_type() has already set error message */ + ret = MJS_TYPE_ERROR; + goto clean; + } + } + + if(!mjs_ffi_sig_set_val_type(sig, vtidx++, val_type)) { + ret = MJS_TYPE_ERROR; + mjs_prepend_errorf(mjs, ret, "too many callback args"); + goto clean; + } + + if(*tmp_e == ',') { + /* Advance cur to the next argument */ + cur = tmp_e + 1; + while(*cur == ' ') cur++; + } else { + /* No more arguments */ + break; + } + } + /* }}} */ + + /* Analyze the results and see if they are obviously wrong */ + mjs_ffi_sig_validate(mjs, sig, sig_type); + if(!sig->is_valid) { + ret = MJS_TYPE_ERROR; + goto clean; + } + + /* If the signature represents a callback, find the suitable implementation */ + if(sig->is_callback) { + sig->fn = get_cb_impl_by_signature(sig); + if(sig->fn == NULL) { + ret = MJS_TYPE_ERROR; + mjs_prepend_errorf( + mjs, + ret, + "the callback signature is valid, but there's " + "no existing callback implementation for it"); + goto clean; + } + } + +clean: + if(ret != MJS_OK) { + mjs_prepend_errorf(mjs, ret, "bad ffi signature: \"%.*s\"", sig_len, s); + sig->is_valid = 0; + } + return ret; +} + +/* C callbacks implementation {{{ */ + +/* An argument or a return value for C callback impl */ +union ffi_cb_data_val { + void* p; + uintptr_t w; + double d; + float f; +}; + +struct ffi_cb_data { + union ffi_cb_data_val args[MJS_CB_ARGS_MAX_CNT]; +}; + +static union ffi_cb_data_val ffi_cb_impl_generic(void* param, struct ffi_cb_data* data) { + struct mjs_ffi_cb_args* cbargs = (struct mjs_ffi_cb_args*)param; + mjs_val_t *args, res = MJS_UNDEFINED; + union ffi_cb_data_val ret; + int i; + struct mjs* mjs = cbargs->mjs; + mjs_ffi_ctype_t return_ctype = MJS_FFI_CTYPE_NONE; + mjs_err_t err; + + memset(&ret, 0, sizeof(ret)); + mjs_own(mjs, &res); + + /* There must be at least one argument: a userdata */ + assert(cbargs->sig.args_cnt > 0); + + /* Create JS arguments */ + args = calloc(1, sizeof(mjs_val_t) * cbargs->sig.args_cnt); + for(i = 0; i < cbargs->sig.args_cnt; i++) { + mjs_ffi_ctype_t val_type = + cbargs->sig.val_types[i + 1 /* first val_type is return value type */]; + switch(val_type) { + case MJS_FFI_CTYPE_USERDATA: + args[i] = cbargs->userdata; + break; + case MJS_FFI_CTYPE_INT: + args[i] = mjs_mk_number(mjs, (double)data->args[i].w); + break; + case MJS_FFI_CTYPE_BOOL: + args[i] = mjs_mk_boolean(mjs, !!data->args[i].w); + break; + case MJS_FFI_CTYPE_CHAR_PTR: { + const char* s = (char*)data->args[i].w; + if(s == NULL) s = ""; + args[i] = mjs_mk_string(mjs, s, ~0, 1); + break; + } + case MJS_FFI_CTYPE_VOID_PTR: + args[i] = mjs_mk_foreign(mjs, (void*)data->args[i].w); + break; + case MJS_FFI_CTYPE_DOUBLE: + args[i] = mjs_mk_number(mjs, data->args[i].d); + break; + case MJS_FFI_CTYPE_FLOAT: + args[i] = mjs_mk_number(mjs, data->args[i].f); + break; + case MJS_FFI_CTYPE_STRUCT_MG_STR_PTR: { + struct mg_str* s = (struct mg_str*)(void*)data->args[i].w; + args[i] = mjs_mk_string(mjs, s->p, s->len, 1); + break; + } + default: + /* should never be here */ + LOG(LL_ERROR, ("unexpected val type for arg #%d: %d\n", i, val_type)); + abort(); + } + } + + /* + * save return ctype outside of `cbargs` before calling the callback, because + * callback might call `ffi_cb_free()`, which will effectively invalidate + * `cbargs` + */ + return_ctype = cbargs->sig.val_types[0]; + + /* Call JS function */ + LOG(LL_VERBOSE_DEBUG, + ("calling JS callback void-void %d from C", mjs_get_int(mjs, cbargs->func))); + err = mjs_apply(mjs, &res, cbargs->func, MJS_UNDEFINED, cbargs->sig.args_cnt, args); + /* + * cbargs might be invalidated by the callback (if it called ffi_cb_free), so + * null it out + */ + cbargs = NULL; + if(err != MJS_OK) { + /* + * There's not much we can do about the error here; let's at least print it + */ + mjs_print_error(mjs, stderr, "MJS callback error", 1 /* print_stack_trace */); + + goto clean; + } + + /* Get return value, if needed */ + switch(return_ctype) { + case MJS_FFI_CTYPE_NONE: + /* do nothing */ + break; + case MJS_FFI_CTYPE_INT: + ret.w = mjs_get_int(mjs, res); + break; + case MJS_FFI_CTYPE_BOOL: + ret.w = mjs_get_bool(mjs, res); + break; + case MJS_FFI_CTYPE_VOID_PTR: + ret.p = mjs_get_ptr(mjs, res); + break; + case MJS_FFI_CTYPE_DOUBLE: + ret.d = mjs_get_double(mjs, res); + break; + case MJS_FFI_CTYPE_FLOAT: + ret.f = (float)mjs_get_double(mjs, res); + break; + default: + /* should never be here */ + LOG(LL_ERROR, ("unexpected return val type %d\n", return_ctype)); + abort(); + } + +clean: + free(args); + mjs_disown(mjs, &res); + return ret; +} + +static void ffi_init_cb_data_wwww( + struct ffi_cb_data* data, + uintptr_t w0, + uintptr_t w1, + uintptr_t w2, + uintptr_t w3, + uintptr_t w4, + uintptr_t w5) { + memset(data, 0, sizeof(*data)); + data->args[0].w = w0; + data->args[1].w = w1; + data->args[2].w = w2; + data->args[3].w = w3; + data->args[4].w = w4; + data->args[5].w = w5; +} + +static uintptr_t ffi_cb_impl_wpwwwww( + uintptr_t w0, + uintptr_t w1, + uintptr_t w2, + uintptr_t w3, + uintptr_t w4, + uintptr_t w5) { + struct ffi_cb_data data; + ffi_init_cb_data_wwww(&data, w0, w1, w2, w3, w4, w5); + return ffi_cb_impl_generic((void*)w0, &data).w; +} + +static uintptr_t ffi_cb_impl_wwpwwww( + uintptr_t w0, + uintptr_t w1, + uintptr_t w2, + uintptr_t w3, + uintptr_t w4, + uintptr_t w5) { + struct ffi_cb_data data; + ffi_init_cb_data_wwww(&data, w0, w1, w2, w3, w4, w5); + return ffi_cb_impl_generic((void*)w1, &data).w; +} + +static uintptr_t ffi_cb_impl_wwwpwww( + uintptr_t w0, + uintptr_t w1, + uintptr_t w2, + uintptr_t w3, + uintptr_t w4, + uintptr_t w5) { + struct ffi_cb_data data; + ffi_init_cb_data_wwww(&data, w0, w1, w2, w3, w4, w5); + return ffi_cb_impl_generic((void*)w2, &data).w; +} + +static uintptr_t ffi_cb_impl_wwwwpww( + uintptr_t w0, + uintptr_t w1, + uintptr_t w2, + uintptr_t w3, + uintptr_t w4, + uintptr_t w5) { + struct ffi_cb_data data; + ffi_init_cb_data_wwww(&data, w0, w1, w2, w3, w4, w5); + return ffi_cb_impl_generic((void*)w3, &data).w; +} + +static uintptr_t ffi_cb_impl_wwwwwpw( + uintptr_t w0, + uintptr_t w1, + uintptr_t w2, + uintptr_t w3, + uintptr_t w4, + uintptr_t w5) { + struct ffi_cb_data data; + ffi_init_cb_data_wwww(&data, w0, w1, w2, w3, w4, w5); + return ffi_cb_impl_generic((void*)w4, &data).w; +} + +static uintptr_t ffi_cb_impl_wwwwwwp( + uintptr_t w0, + uintptr_t w1, + uintptr_t w2, + uintptr_t w3, + uintptr_t w4, + uintptr_t w5) { + struct ffi_cb_data data; + ffi_init_cb_data_wwww(&data, w0, w1, w2, w3, w4, w5); + return ffi_cb_impl_generic((void*)w5, &data).w; +} + +static uintptr_t ffi_cb_impl_wpd(uintptr_t w0, double d1) { + struct ffi_cb_data data; + + memset(&data, 0, sizeof(data)); + data.args[0].w = w0; + data.args[1].d = d1; + + return ffi_cb_impl_generic((void*)w0, &data).w; +} + +static uintptr_t ffi_cb_impl_wdp(double d0, uintptr_t w1) { + struct ffi_cb_data data; + + memset(&data, 0, sizeof(data)); + data.args[0].d = d0; + data.args[1].w = w1; + + return ffi_cb_impl_generic((void*)w1, &data).w; +} +/* }}} */ + +static struct mjs_ffi_cb_args** + ffi_get_matching(struct mjs_ffi_cb_args** plist, mjs_val_t func, mjs_val_t userdata) { + for(; *plist != NULL; plist = &((*plist)->next)) { + if((*plist)->func == func && (*plist)->userdata == userdata) { + break; + } + } + return plist; +} + +static ffi_fn_t* get_cb_impl_by_signature(const mjs_ffi_sig_t* sig) { + if(sig->is_valid) { + int i; + int double_cnt = 0; + int float_cnt = 0; + int userdata_idx = 0 /* not a valid value: index 0 means return value */; + + for(i = 1 /*0th item is a return value*/; i < MJS_CB_SIGNATURE_MAX_SIZE; i++) { + mjs_ffi_ctype_t type = sig->val_types[i]; + switch(type) { + case MJS_FFI_CTYPE_DOUBLE: + double_cnt++; + break; + case MJS_FFI_CTYPE_FLOAT: + float_cnt++; + break; + case MJS_FFI_CTYPE_USERDATA: + assert(userdata_idx == 0); /* Otherwise is_valid should be 0 */ + userdata_idx = i; + break; + default: + break; + } + } + + if(float_cnt > 0) { + /* TODO(dfrank): add support for floats in callbacks */ + return NULL; + } + + assert(userdata_idx > 0); /* Otherwise is_valid should be 0 */ + + if(sig->args_cnt <= MJS_CB_ARGS_MAX_CNT) { + if(mjs_ffi_is_regular_word_or_void(sig->val_types[0])) { + /* Return type is a word or void */ + switch(double_cnt) { + case 0: + /* No double arguments */ + switch(userdata_idx) { + case 1: + return (ffi_fn_t*)ffi_cb_impl_wpwwwww; + case 2: + return (ffi_fn_t*)ffi_cb_impl_wwpwwww; + case 3: + return (ffi_fn_t*)ffi_cb_impl_wwwpwww; + case 4: + return (ffi_fn_t*)ffi_cb_impl_wwwwpww; + case 5: + return (ffi_fn_t*)ffi_cb_impl_wwwwwpw; + case 6: + return (ffi_fn_t*)ffi_cb_impl_wwwwwwp; + default: + /* should never be here */ + abort(); + } + break; + case 1: + /* 1 double argument */ + switch(userdata_idx) { + case 1: + return (ffi_fn_t*)ffi_cb_impl_wpd; + case 2: + return (ffi_fn_t*)ffi_cb_impl_wdp; + } + break; + } + } + } else { + /* Too many arguments for the built-in callback impls */ + /* TODO(dfrank): add support for custom app-dependent resolver */ + } + } + + return NULL; +} + +MJS_PRIVATE mjs_val_t mjs_ffi_sig_to_value(struct mjs_ffi_sig* psig) { + if(psig == NULL) { + return MJS_NULL; + } else { + return mjs_legit_pointer_to_value(psig) | MJS_TAG_FUNCTION_FFI; + } +} + +MJS_PRIVATE int mjs_is_ffi_sig(mjs_val_t v) { + return (v & MJS_TAG_MASK) == MJS_TAG_FUNCTION_FFI; +} + +MJS_PRIVATE struct mjs_ffi_sig* mjs_get_ffi_sig_struct(mjs_val_t v) { + struct mjs_ffi_sig* ret = NULL; + assert(mjs_is_ffi_sig(v)); + ret = (struct mjs_ffi_sig*)get_ptr(v); + return ret; +} + +MJS_PRIVATE mjs_val_t mjs_mk_ffi_sig(struct mjs* mjs) { + struct mjs_ffi_sig* psig = new_ffi_sig(mjs); + mjs_ffi_sig_init(psig); + return mjs_ffi_sig_to_value(psig); +} + +MJS_PRIVATE void mjs_ffi_sig_destructor(struct mjs* mjs, void* psig) { + mjs_ffi_sig_free((mjs_ffi_sig_t*)psig); + (void)mjs; +} + +MJS_PRIVATE mjs_err_t mjs_ffi_call(struct mjs* mjs) { + mjs_err_t e = MJS_OK; + const char* sig_str = NULL; + mjs_val_t sig_str_v = mjs_arg(mjs, 0); + mjs_val_t ret_v = MJS_UNDEFINED; + struct mjs_ffi_sig* psig = mjs_get_ffi_sig_struct(mjs_mk_ffi_sig(mjs)); + size_t sig_str_len; + + sig_str = mjs_get_string(mjs, &sig_str_v, &sig_str_len); + e = mjs_parse_ffi_signature(mjs, sig_str, sig_str_len, psig, FFI_SIG_FUNC); + if(e != MJS_OK) goto clean; + ret_v = mjs_ffi_sig_to_value(psig); + +clean: + mjs_return(mjs, ret_v); + return e; +} + +MJS_PRIVATE mjs_err_t mjs_ffi_call2(struct mjs* mjs) { + mjs_err_t ret = MJS_OK; + mjs_ffi_sig_t* psig = NULL; + mjs_ffi_ctype_t rtype; + mjs_val_t sig_v = *vptr(&mjs->stack, mjs_getretvalpos(mjs)); + + int i, nargs; + struct ffi_arg res; + struct ffi_arg args[FFI_MAX_ARGS_CNT]; + struct cbdata cbdata; + + /* TODO(dfrank): support multiple callbacks */ + mjs_val_t resv = mjs_mk_undefined(); + + /* + * String arguments, needed to support short strings which are packed into + * mjs_val_t itself + */ + mjs_val_t argvs[FFI_MAX_ARGS_CNT]; + struct mg_str argvmgstr[FFI_MAX_ARGS_CNT]; + + if(mjs_is_ffi_sig(sig_v)) { + psig = mjs_get_ffi_sig_struct(sig_v); + } else { + ret = MJS_TYPE_ERROR; + mjs_prepend_errorf(mjs, ret, "non-ffi-callable value"); + goto clean; + } + + memset(&cbdata, 0, sizeof(cbdata)); + cbdata.func_idx = -1; + cbdata.userdata_idx = -1; + + rtype = psig->val_types[0]; + + switch(rtype) { + case MJS_FFI_CTYPE_DOUBLE: + res.ctype = FFI_CTYPE_DOUBLE; + break; + case MJS_FFI_CTYPE_FLOAT: + res.ctype = FFI_CTYPE_FLOAT; + break; + case MJS_FFI_CTYPE_BOOL: + res.ctype = FFI_CTYPE_BOOL; + break; + case MJS_FFI_CTYPE_USERDATA: + case MJS_FFI_CTYPE_INT: + case MJS_FFI_CTYPE_CHAR_PTR: + case MJS_FFI_CTYPE_VOID_PTR: + case MJS_FFI_CTYPE_NONE: + res.ctype = FFI_CTYPE_WORD; + break; + + case MJS_FFI_CTYPE_INVALID: + ret = MJS_TYPE_ERROR; + mjs_prepend_errorf(mjs, ret, "wrong ffi return type"); + goto clean; + } + res.v.i = 0; + + nargs = mjs_stack_size(&mjs->stack) - mjs_get_int(mjs, vtop(&mjs->call_stack)); + + if(nargs != psig->args_cnt) { + ret = MJS_TYPE_ERROR; + mjs_prepend_errorf( + mjs, ret, "got %d actuals, but function takes %d args", nargs, psig->args_cnt); + goto clean; + } + + for(i = 0; i < nargs; i++) { + mjs_val_t arg = mjs_arg(mjs, i); + + switch(psig->val_types[1 /* retval type */ + i]) { + case MJS_FFI_CTYPE_NONE: + /* + * Void argument: in any case, it's an error, because if C function + * takes no arguments, then the FFI-ed JS function should be called + * without any arguments, and thus we'll not face "void" here. + */ + ret = MJS_TYPE_ERROR; + if(i == 0) { + /* FFI signature is correct, but invocation is wrong */ + mjs_prepend_errorf(mjs, ret, "ffi-ed function takes no arguments"); + } else { + /* + * FFI signature is wrong: we can't have "void" as a non-first + * "argument" + */ + mjs_prepend_errorf(mjs, ret, "bad ffi arg #%d type: \"void\"", i); + } + + goto clean; + case MJS_FFI_CTYPE_USERDATA: + /* Userdata for the callback */ + if(cbdata.userdata_idx != -1) { + ret = MJS_TYPE_ERROR; + mjs_prepend_errorf( + mjs, ret, "two or more userdata args: #%d and %d", cbdata.userdata_idx, i); + + goto clean; + } + cbdata.userdata = arg; + cbdata.userdata_idx = i; + break; + case MJS_FFI_CTYPE_INT: { + int intval = 0; + if(mjs_is_number(arg)) { + intval = mjs_get_int(mjs, arg); + } else if(mjs_is_boolean(arg)) { + intval = mjs_get_bool(mjs, arg); + } else { + ret = MJS_TYPE_ERROR; + mjs_prepend_errorf( + mjs, + ret, + "actual arg #%d is not an int (the type idx is: %s)", + i, + mjs_typeof(arg)); + } + ffi_set_word(&args[i], intval); + } break; + case MJS_FFI_CTYPE_STRUCT_MG_STR_PTR: { + if(!mjs_is_string(arg)) { + ret = MJS_TYPE_ERROR; + mjs_prepend_errorf( + mjs, + ret, + "actual arg #%d is not a string (the type idx is: %s)", + i, + mjs_typeof(arg)); + goto clean; + } + argvs[i] = arg; + argvmgstr[i].p = mjs_get_string(mjs, &argvs[i], &argvmgstr[i].len); + /* + * String argument should be saved separately in order to support + * short strings (which are packed into mjs_val_t itself) + */ + ffi_set_ptr(&args[i], (void*)&argvmgstr[i]); + break; + } + case MJS_FFI_CTYPE_BOOL: { + int intval = 0; + if(mjs_is_number(arg)) { + intval = !!mjs_get_int(mjs, arg); + } else if(mjs_is_boolean(arg)) { + intval = mjs_get_bool(mjs, arg); + } else { + ret = MJS_TYPE_ERROR; + mjs_prepend_errorf( + mjs, + ret, + "actual arg #%d is not a bool (the type idx is: %s)", + i, + mjs_typeof(arg)); + } + ffi_set_word(&args[i], intval); + } break; + case MJS_FFI_CTYPE_DOUBLE: + ffi_set_double(&args[i], mjs_get_double(mjs, arg)); + break; + case MJS_FFI_CTYPE_FLOAT: + ffi_set_float(&args[i], (float)mjs_get_double(mjs, arg)); + break; + case MJS_FFI_CTYPE_CHAR_PTR: { + size_t s; + if(mjs_is_string(arg)) { + /* + * String argument should be saved separately in order to support + * short strings (which are packed into mjs_val_t itself) + */ + argvs[i] = arg; + ffi_set_ptr(&args[i], (void*)mjs_get_string(mjs, &argvs[i], &s)); + } else if(mjs_is_null(arg)) { + ffi_set_ptr(&args[i], NULL); + } else { + ret = MJS_TYPE_ERROR; + mjs_prepend_errorf( + mjs, + ret, + "actual arg #%d is not a string (the type idx is: %s)", + i, + mjs_typeof(arg)); + goto clean; + } + } break; + case MJS_FFI_CTYPE_VOID_PTR: + if(mjs_is_string(arg)) { + size_t n; + /* + * String argument should be saved separately in order to support + * short strings (which are packed into mjs_val_t itself) + */ + argvs[i] = arg; + ffi_set_ptr(&args[i], (void*)mjs_get_string(mjs, &argvs[i], &n)); + } else if(mjs_is_foreign(arg)) { + ffi_set_ptr(&args[i], (void*)mjs_get_ptr(mjs, arg)); + } else if(mjs_is_null(arg)) { + ffi_set_ptr(&args[i], NULL); + } else { + ret = MJS_TYPE_ERROR; + mjs_prepend_errorf(mjs, ret, "actual arg #%d is not a ptr", i); + goto clean; + } + break; + case MJS_FFI_CTYPE_CALLBACK: + if(mjs_is_function(arg) || mjs_is_foreign(arg) || mjs_is_ffi_sig(arg)) { + /* + * Current argument is a callback function pointer: remember the given + * JS function and the argument index + */ + cbdata.func = arg; + cbdata.func_idx = i; + } else { + ret = MJS_TYPE_ERROR; + mjs_prepend_errorf( + mjs, + ret, + "actual arg #%d is not a function, but %s", + i, + mjs_stringify_type((enum mjs_type)arg)); + goto clean; + } + break; + case MJS_FFI_CTYPE_INVALID: + /* parse_cval_type() has already set a more detailed error */ + ret = MJS_TYPE_ERROR; + mjs_prepend_errorf(mjs, ret, "wrong arg type"); + goto clean; + default: + abort(); + break; + } + } + + if(cbdata.userdata_idx >= 0 && cbdata.func_idx >= 0) { + struct mjs_ffi_cb_args* cbargs = NULL; + struct mjs_ffi_cb_args** pitem = NULL; + + /* the function takes a callback */ + + /* + * Get cbargs: either reuse the existing one (if the matching item exists), + * or create a new one. + */ + pitem = ffi_get_matching(&mjs->ffi_cb_args, cbdata.func, cbdata.userdata); + if(*pitem == NULL) { + /* No matching cbargs item; we need to add a new one */ + cbargs = calloc(1, sizeof(*cbargs)); + cbargs->mjs = mjs; + cbargs->func = cbdata.func; + cbargs->userdata = cbdata.userdata; + mjs_ffi_sig_copy(&cbargs->sig, psig->cb_sig); + + /* Establish a link to the newly allocated item */ + *pitem = cbargs; + } else { + /* Found matching item: reuse it */ + cbargs = *pitem; + } + + { + union { + ffi_fn_t* fn; + void* p; + } u; + u.fn = psig->cb_sig->fn; + ffi_set_ptr(&args[cbdata.func_idx], u.p); + ffi_set_ptr(&args[cbdata.userdata_idx], cbargs); + } + } else if(!(cbdata.userdata_idx == -1 && cbdata.func_idx == -1)) { + /* + * incomplete signature: it contains either the function pointer or + * userdata. It should contain both or none. + * + * It should be handled in mjs_parse_ffi_signature(). + */ + abort(); + } + + ffi_call_mjs(psig->fn, nargs, &res, args); + + switch(rtype) { + case MJS_FFI_CTYPE_CHAR_PTR: { + const char* s = (const char*)(uintptr_t)res.v.i; + if(s != NULL) { + resv = mjs_mk_string(mjs, s, ~0, 1); + } else { + resv = MJS_NULL; + } + break; + } + case MJS_FFI_CTYPE_VOID_PTR: + resv = mjs_mk_foreign(mjs, (void*)(uintptr_t)res.v.i); + break; + case MJS_FFI_CTYPE_INT: + resv = mjs_mk_number(mjs, (int)res.v.i); + break; + case MJS_FFI_CTYPE_BOOL: + resv = mjs_mk_boolean(mjs, !!res.v.i); + break; + case MJS_FFI_CTYPE_DOUBLE: + resv = mjs_mk_number(mjs, res.v.d); + break; + case MJS_FFI_CTYPE_FLOAT: + resv = mjs_mk_number(mjs, res.v.f); + break; + default: + resv = mjs_mk_undefined(); + break; + } + +clean: + /* + * If there was some error, prepend an error message with the subject + * signature + */ + if(ret != MJS_OK) { + mjs_prepend_errorf(mjs, ret, "failed to call FFIed function"); + /* TODO(dfrank) stringify mjs_ffi_sig_t in some human-readable format */ + } + mjs_return(mjs, resv); + + return ret; +} + +/* + * TODO(dfrank): make it return boolean (when booleans are supported), instead + * of a number + */ +MJS_PRIVATE void mjs_ffi_cb_free(struct mjs* mjs) { + mjs_val_t ret = mjs_mk_number(mjs, 0); + mjs_val_t func = mjs_arg(mjs, 0); + mjs_val_t userdata = mjs_arg(mjs, 1); + + if(mjs_is_function(func)) { + struct mjs_ffi_cb_args** pitem = ffi_get_matching(&mjs->ffi_cb_args, func, userdata); + if(*pitem != NULL) { + /* Found matching item: remove it from the linked list, and free */ + struct mjs_ffi_cb_args* cbargs = *pitem; + *pitem = cbargs->next; + mjs_ffi_sig_free(&cbargs->sig); + free(cbargs); + ret = mjs_mk_number(mjs, 1); + } + } else { + mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "missing argument 'func'"); + } + + mjs_return(mjs, ret); +} + +void mjs_ffi_args_free_list(struct mjs* mjs) { + ffi_cb_args_t* next = mjs->ffi_cb_args; + + while(next != NULL) { + ffi_cb_args_t* cur = next; + next = next->next; + free(cur); + } +} + +MJS_PRIVATE void mjs_ffi_sig_init(mjs_ffi_sig_t* sig) { + memset(sig, 0, sizeof(*sig)); +} + +MJS_PRIVATE void mjs_ffi_sig_copy(mjs_ffi_sig_t* to, const mjs_ffi_sig_t* from) { + memcpy(to, from, sizeof(*to)); + if(from->cb_sig != NULL) { + to->cb_sig = calloc(sizeof(*to->cb_sig), 1); + mjs_ffi_sig_copy(to->cb_sig, from->cb_sig); + } +} + +MJS_PRIVATE void mjs_ffi_sig_free(mjs_ffi_sig_t* sig) { + if(sig->cb_sig != NULL) { + free(sig->cb_sig); + sig->cb_sig = NULL; + } +} + +MJS_PRIVATE int mjs_ffi_sig_set_val_type(mjs_ffi_sig_t* sig, int idx, mjs_ffi_ctype_t type) { + if(idx < MJS_CB_SIGNATURE_MAX_SIZE) { + sig->val_types[idx] = type; + return 1; + } else { + /* Index is too large */ + return 0; + } +} + +MJS_PRIVATE int + mjs_ffi_sig_validate(struct mjs* mjs, mjs_ffi_sig_t* sig, enum ffi_sig_type sig_type) { + int ret = 0; + int i; + int callback_idx = 0; + int userdata_idx = 0; + + sig->is_valid = 0; + + switch(sig_type) { + case FFI_SIG_FUNC: + /* Make sure return type is fine */ + if(sig->val_types[0] != MJS_FFI_CTYPE_NONE && sig->val_types[0] != MJS_FFI_CTYPE_INT && + sig->val_types[0] != MJS_FFI_CTYPE_BOOL && sig->val_types[0] != MJS_FFI_CTYPE_DOUBLE && + sig->val_types[0] != MJS_FFI_CTYPE_FLOAT && + sig->val_types[0] != MJS_FFI_CTYPE_VOID_PTR && + sig->val_types[0] != MJS_FFI_CTYPE_CHAR_PTR) { + mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "invalid return value type"); + goto clean; + } + break; + case FFI_SIG_CALLBACK: + /* Make sure return type is fine */ + if(sig->val_types[0] != MJS_FFI_CTYPE_NONE && sig->val_types[0] != MJS_FFI_CTYPE_INT && + sig->val_types[0] != MJS_FFI_CTYPE_BOOL && sig->val_types[0] != MJS_FFI_CTYPE_DOUBLE && + sig->val_types[0] != MJS_FFI_CTYPE_FLOAT && + sig->val_types[0] != MJS_FFI_CTYPE_VOID_PTR) { + mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "invalid return value type"); + goto clean; + } + } + + /* Handle argument types */ + for(i = 1; i < MJS_CB_SIGNATURE_MAX_SIZE; i++) { + mjs_ffi_ctype_t type = sig->val_types[i]; + switch(type) { + case MJS_FFI_CTYPE_USERDATA: + if(userdata_idx != 0) { + /* There must be at most one userdata arg, but we have more */ + mjs_prepend_errorf( + mjs, + MJS_TYPE_ERROR, + "more than one userdata arg: #%d and #%d", + (userdata_idx - 1), + (i - 1)); + goto clean; + } + userdata_idx = i; + break; + case MJS_FFI_CTYPE_CALLBACK: + switch(sig_type) { + case FFI_SIG_FUNC: + break; + case FFI_SIG_CALLBACK: + mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "callback can't take another callback"); + goto clean; + } + callback_idx = i; + break; + case MJS_FFI_CTYPE_INT: + case MJS_FFI_CTYPE_BOOL: + case MJS_FFI_CTYPE_VOID_PTR: + case MJS_FFI_CTYPE_CHAR_PTR: + case MJS_FFI_CTYPE_STRUCT_MG_STR_PTR: + case MJS_FFI_CTYPE_DOUBLE: + case MJS_FFI_CTYPE_FLOAT: + /* Do nothing */ + break; + case MJS_FFI_CTYPE_NONE: + /* No more arguments */ + goto args_over; + default: + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "invalid ffi_ctype: %d", type); + goto clean; + } + + sig->args_cnt++; + } +args_over: + + switch(sig_type) { + case FFI_SIG_FUNC: + if(!((callback_idx > 0 && userdata_idx > 0) || (callback_idx == 0 && userdata_idx == 0))) { + mjs_prepend_errorf( + mjs, + MJS_TYPE_ERROR, + "callback and userdata should be either both " + "present or both absent"); + goto clean; + } + break; + case FFI_SIG_CALLBACK: + if(userdata_idx == 0) { + /* No userdata arg */ + mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "no userdata arg"); + goto clean; + } + break; + } + + ret = 1; + +clean: + if(ret) { + sig->is_valid = 1; + } + return ret; +} + +MJS_PRIVATE int mjs_ffi_is_regular_word(mjs_ffi_ctype_t type) { + switch(type) { + case MJS_FFI_CTYPE_INT: + case MJS_FFI_CTYPE_BOOL: + return 1; + default: + return 0; + } +} + +MJS_PRIVATE int mjs_ffi_is_regular_word_or_void(mjs_ffi_ctype_t type) { + return (type == MJS_FFI_CTYPE_NONE || mjs_ffi_is_regular_word(type)); +} + +#ifdef _WIN32 +void* dlsym(void* handle, const char* name) { + static HANDLE msvcrt_dll; + void* sym = NULL; + if(msvcrt_dll == NULL) msvcrt_dll = GetModuleHandle("msvcrt.dll"); + if((sym = GetProcAddress(GetModuleHandle(NULL), name)) == NULL) { + sym = GetProcAddress(msvcrt_dll, name); + } + return sym; +} +#elif !defined(__unix__) && !defined(__APPLE__) +void* dlsym(void* handle, const char* name) { + (void)handle; + (void)name; + return NULL; +} +#endif diff --git a/applications/system/elk_mjs/lib/mjs/mjs_ffi.h b/applications/system/elk_mjs/lib/mjs/mjs_ffi.h new file mode 100644 index 00000000000..5d5782bef3f --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/mjs_ffi.h @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2017 Cesanta Software Limited + * All rights reserved + */ + +#ifndef MJS_FFI_H_ +#define MJS_FFI_H_ + +#include "ffi/ffi.h" +#include "mjs_ffi_public.h" +#include "mjs_internal.h" + +#if defined(__cplusplus) +extern "C" { +#endif /* __cplusplus */ + +mjs_ffi_resolver_t dlsym; + +#define MJS_CB_ARGS_MAX_CNT 6 +#define MJS_CB_SIGNATURE_MAX_SIZE (MJS_CB_ARGS_MAX_CNT + 1 /* return type */) + +typedef uint8_t mjs_ffi_ctype_t; + +enum ffi_sig_type { + FFI_SIG_FUNC, + FFI_SIG_CALLBACK, +}; + +/* + * Parsed FFI signature + */ +struct mjs_ffi_sig { + /* + * Callback signature, corresponds to the arg of type MJS_FFI_CTYPE_CALLBACK + * TODO(dfrank): probably we'll need to support multiple callback/userdata + * pairs + * + * NOTE(dfrank): instances of this structure are grouped into GC arenas and + * managed by GC, and for the GC mark to work, the first element should be + * a pointer (so that the two LSBs are not used). + */ + struct mjs_ffi_sig* cb_sig; + + /* + * The first item is the return value type (for `void`, `MJS_FFI_CTYPE_NONE` + * is used); the rest are arguments. If some argument is + * `MJS_FFI_CTYPE_NONE`, it means that there are no more arguments. + */ + mjs_ffi_ctype_t val_types[MJS_CB_SIGNATURE_MAX_SIZE]; + + /* + * Function to call. If `is_callback` is not set, then it's the function + * obtained by dlsym; otherwise it's a pointer to the appropriate callback + * implementation. + */ + ffi_fn_t* fn; + + /* Number of arguments in the signature */ + int8_t args_cnt; + + /* + * If set, then the signature represents the callback (as opposed to a normal + * function), and `fn` points to the suitable callback implementation. + */ + unsigned is_callback : 1; + unsigned is_valid : 1; +}; +typedef struct mjs_ffi_sig mjs_ffi_sig_t; + +/* Initialize new FFI signature */ +MJS_PRIVATE void mjs_ffi_sig_init(mjs_ffi_sig_t* sig); +/* Copy existing FFI signature */ +MJS_PRIVATE void mjs_ffi_sig_copy(mjs_ffi_sig_t* to, const mjs_ffi_sig_t* from); +/* Free FFI signature. NOTE: the pointer `sig` itself is not freed */ +MJS_PRIVATE void mjs_ffi_sig_free(mjs_ffi_sig_t* sig); + +/* + * Creates a new FFI signature from the GC arena, and return mjs_val_t which + * wraps it. + */ +MJS_PRIVATE mjs_val_t mjs_mk_ffi_sig(struct mjs* mjs); + +/* + * Checks whether the given value is a FFI signature. + */ +MJS_PRIVATE int mjs_is_ffi_sig(mjs_val_t v); + +/* + * Wraps FFI signature structure into mjs_val_t value. + */ +MJS_PRIVATE mjs_val_t mjs_ffi_sig_to_value(struct mjs_ffi_sig* psig); + +/* + * Extracts a pointer to the FFI signature struct from the mjs_val_t value. + */ +MJS_PRIVATE struct mjs_ffi_sig* mjs_get_ffi_sig_struct(mjs_val_t v); + +/* + * A wrapper for mjs_ffi_sig_free() suitable to use as a GC cell destructor. + */ +MJS_PRIVATE void mjs_ffi_sig_destructor(struct mjs* mjs, void* psig); + +MJS_PRIVATE int mjs_ffi_sig_set_val_type(mjs_ffi_sig_t* sig, int idx, mjs_ffi_ctype_t type); +MJS_PRIVATE int + mjs_ffi_sig_validate(struct mjs* mjs, mjs_ffi_sig_t* sig, enum ffi_sig_type sig_type); +MJS_PRIVATE int mjs_ffi_is_regular_word(mjs_ffi_ctype_t type); +MJS_PRIVATE int mjs_ffi_is_regular_word_or_void(mjs_ffi_ctype_t type); + +struct mjs_ffi_cb_args { + struct mjs_ffi_cb_args* next; + struct mjs* mjs; + mjs_ffi_sig_t sig; + mjs_val_t func; + mjs_val_t userdata; +}; +typedef struct mjs_ffi_cb_args ffi_cb_args_t; + +/* + * cfunction: + * Parses the FFI signature string and returns a value wrapping mjs_ffi_sig_t. + */ +MJS_PRIVATE mjs_err_t mjs_ffi_call(struct mjs* mjs); + +/* + * cfunction: + * Performs the FFI signature call. + */ +MJS_PRIVATE mjs_err_t mjs_ffi_call2(struct mjs* mjs); + +MJS_PRIVATE void mjs_ffi_cb_free(struct mjs*); +MJS_PRIVATE void mjs_ffi_args_free_list(struct mjs* mjs); + +#if defined(__cplusplus) +} +#endif /* __cplusplus */ + +#endif /* MJS_FFI_H_ */ diff --git a/applications/system/elk_mjs/lib/mjs/mjs_ffi_public.h b/applications/system/elk_mjs/lib/mjs/mjs_ffi_public.h new file mode 100644 index 00000000000..331fe2f17d5 --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/mjs_ffi_public.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2016 Cesanta Software Limited + * All rights reserved + */ + +#ifndef MJS_FFI_PUBLIC_H_ +#define MJS_FFI_PUBLIC_H_ + +#include "mjs_core_public.h" + +#if defined(__cplusplus) +extern "C" { +#endif /* __cplusplus */ + +enum mjs_ffi_ctype { + MJS_FFI_CTYPE_NONE, + MJS_FFI_CTYPE_USERDATA, + MJS_FFI_CTYPE_CALLBACK, + MJS_FFI_CTYPE_INT, + MJS_FFI_CTYPE_BOOL, + MJS_FFI_CTYPE_DOUBLE, + MJS_FFI_CTYPE_FLOAT, + MJS_FFI_CTYPE_CHAR_PTR, + MJS_FFI_CTYPE_VOID_PTR, + MJS_FFI_CTYPE_STRUCT_MG_STR_PTR, + MJS_FFI_CTYPE_STRUCT_MG_STR, + MJS_FFI_CTYPE_INVALID, +}; + +typedef void*(mjs_ffi_resolver_t)(void* handle, const char* symbol); + +void mjs_set_ffi_resolver(struct mjs* mjs, mjs_ffi_resolver_t* dlsym); + +#if defined(__cplusplus) +} +#endif /* __cplusplus */ + +#endif /* MJS_FFI_PUBLIC_H_ */ diff --git a/applications/system/elk_mjs/lib/mjs/mjs_gc.c b/applications/system/elk_mjs/lib/mjs/mjs_gc.c new file mode 100644 index 00000000000..7a6bf778b72 --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/mjs_gc.c @@ -0,0 +1,535 @@ +/* + * Copyright (c) 2014 Cesanta Software Limited + * All rights reserved + */ + +#include + +#include "common/cs_varint.h" +#include "common/mbuf.h" + +#include "mjs_core.h" +#include "mjs_ffi.h" +#include "mjs_gc.h" +#include "mjs_internal.h" +#include "mjs_object.h" +#include "mjs_primitive.h" +#include "mjs_string.h" + +/* + * Macros for marking reachable things: use bit 0. + */ +#define MARK(p) (((struct gc_cell*)(p))->head.word |= 1) +#define UNMARK(p) (((struct gc_cell*)(p))->head.word &= ~1) +#define MARKED(p) (((struct gc_cell*)(p))->head.word & 1) + +/* + * Similar to `MARK()` / `UNMARK()` / `MARKED()`, but `.._FREE` counterparts + * are intended to mark free cells (as opposed to used ones), so they use + * bit 1. + */ +#define MARK_FREE(p) (((struct gc_cell*)(p))->head.word |= 2) +#define UNMARK_FREE(p) (((struct gc_cell*)(p))->head.word &= ~2) +#define MARKED_FREE(p) (((struct gc_cell*)(p))->head.word & 2) + +/* + * When each arena has that or less free cells, GC will be scheduled + */ +#define GC_ARENA_CELLS_RESERVE 2 + +static struct gc_block* gc_new_block(struct gc_arena* a, size_t size); +static void gc_free_block(struct gc_block* b); +static void gc_mark_mbuf_pt(struct mjs* mjs, const struct mbuf* mbuf); + +MJS_PRIVATE struct mjs_object* new_object(struct mjs* mjs) { + return (struct mjs_object*)gc_alloc_cell(mjs, &mjs->object_arena); +} + +MJS_PRIVATE struct mjs_property* new_property(struct mjs* mjs) { + return (struct mjs_property*)gc_alloc_cell(mjs, &mjs->property_arena); +} + +MJS_PRIVATE struct mjs_ffi_sig* new_ffi_sig(struct mjs* mjs) { + return (struct mjs_ffi_sig*)gc_alloc_cell(mjs, &mjs->ffi_sig_arena); +} + +/* Initializes a new arena. */ +MJS_PRIVATE void gc_arena_init( + struct gc_arena* a, + size_t cell_size, + size_t initial_size, + size_t size_increment) { + assert(cell_size >= sizeof(uintptr_t)); + + memset(a, 0, sizeof(*a)); + a->cell_size = cell_size; + a->size_increment = size_increment; + a->blocks = gc_new_block(a, initial_size); +} + +MJS_PRIVATE void gc_arena_destroy(struct mjs* mjs, struct gc_arena* a) { + struct gc_block* b; + + if(a->blocks != NULL) { + gc_sweep(mjs, a, 0); + for(b = a->blocks; b != NULL;) { + struct gc_block* tmp; + tmp = b; + b = b->next; + gc_free_block(tmp); + } + } +} + +static void gc_free_block(struct gc_block* b) { + free(b->base); + free(b); +} + +static struct gc_block* gc_new_block(struct gc_arena* a, size_t size) { + struct gc_cell* cur; + struct gc_block* b; + + b = (struct gc_block*)calloc(1, sizeof(*b)); + if(b == NULL) abort(); + + b->size = size; + b->base = (struct gc_cell*)calloc(a->cell_size, b->size); + if(b->base == NULL) abort(); + + for(cur = GC_CELL_OP(a, b->base, +, 0); cur < GC_CELL_OP(a, b->base, +, b->size); + cur = GC_CELL_OP(a, cur, +, 1)) { + cur->head.link = a->free; + a->free = cur; + } + + return b; +} + +/* + * Returns whether the given arena has GC_ARENA_CELLS_RESERVE or less free + * cells + */ +static int gc_arena_is_gc_needed(struct gc_arena* a) { + struct gc_cell* r = a->free; + int i; + + for(i = 0; i <= GC_ARENA_CELLS_RESERVE; i++, r = r->head.link) { + if(r == NULL) { + return 1; + } + } + + return 0; +} + +MJS_PRIVATE int gc_strings_is_gc_needed(struct mjs* mjs) { + struct mbuf* m = &mjs->owned_strings; + return (double)m->len / (double)m->size > (double)0.9; +} + +MJS_PRIVATE void* gc_alloc_cell(struct mjs* mjs, struct gc_arena* a) { + struct gc_cell* r; + + if(a->free == NULL) { + struct gc_block* b = gc_new_block(a, a->size_increment); + b->next = a->blocks; + a->blocks = b; + } + r = a->free; + + UNMARK(r); + + a->free = r->head.link; + +#if MJS_MEMORY_STATS + a->allocations++; + a->alive++; +#endif + + /* Schedule GC if needed */ + if(gc_arena_is_gc_needed(a)) { + mjs->need_gc = 1; + } + + /* + * TODO(mkm): minor opt possible since most of the fields + * are overwritten downstream, but not worth the yak shave time + * when fields are added to GC-able structures */ + memset(r, 0, a->cell_size); + return (void*)r; +} + +/* + * Scans the arena and add all unmarked cells to the free list. + * + * Empty blocks get deallocated. The head of the free list will contais cells + * from the last (oldest) block. Cells will thus be allocated in block order. + */ +void gc_sweep(struct mjs* mjs, struct gc_arena* a, size_t start) { + struct gc_block* b; + struct gc_cell* cur; + struct gc_block** prevp = &a->blocks; +#if MJS_MEMORY_STATS + a->alive = 0; +#endif + + /* + * Before we sweep, we should mark all free cells in a way that is + * distinguishable from marked used cells. + */ + { + struct gc_cell* next; + for(cur = a->free; cur != NULL; cur = next) { + next = cur->head.link; + MARK_FREE(cur); + } + } + + /* + * We'll rebuild the whole `free` list, so initially we just reset it + */ + a->free = NULL; + + for(b = a->blocks; b != NULL;) { + size_t freed_in_block = 0; + /* + * if it turns out that this block is 100% garbage + * we can release the whole block, but the addition + * of it's cells to the free list has to be undone. + */ + struct gc_cell* prev_free = a->free; + + for(cur = GC_CELL_OP(a, b->base, +, start); cur < GC_CELL_OP(a, b->base, +, b->size); + cur = GC_CELL_OP(a, cur, +, 1)) { + if(MARKED(cur)) { + /* The cell is used and marked */ + UNMARK(cur); +#if MJS_MEMORY_STATS + a->alive++; +#endif + } else { + /* + * The cell is either: + * - free + * - garbage that's about to be freed + */ + + if(MARKED_FREE(cur)) { + /* The cell is free, so, just unmark it */ + UNMARK_FREE(cur); + } else { + /* + * The cell is used and should be freed: call the destructor and + * reset the memory + */ + if(a->destructor != NULL) { + a->destructor(mjs, cur); + } + memset(cur, 0, a->cell_size); + } + + /* Add this cell to the `free` list */ + cur->head.link = a->free; + a->free = cur; + freed_in_block++; +#if MJS_MEMORY_STATS + a->garbage++; +#endif + } + } + + /* + * don't free the initial block, which is at the tail + * because it has a special size aimed at reducing waste + * and simplifying initial startup. TODO(mkm): improve + * */ + if(b->next != NULL && freed_in_block == b->size) { + *prevp = b->next; + gc_free_block(b); + b = *prevp; + a->free = prev_free; + } else { + prevp = &b->next; + b = b->next; + } + } +} + +/* Mark an FFI signature */ +static void gc_mark_ffi_sig(struct mjs* mjs, mjs_val_t* v) { + struct mjs_ffi_sig* psig; + + assert(mjs_is_ffi_sig(*v)); + + psig = mjs_get_ffi_sig_struct(*v); + + /* + * we treat all object like things like objects but they might be functions, + * gc_check_val checks the appropriate arena per actual value type. + */ + if(!gc_check_val(mjs, *v)) { + abort(); + } + + if(MARKED(psig)) return; + + MARK(psig); +} + +/* Mark an object */ +static void gc_mark_object(struct mjs* mjs, mjs_val_t* v) { + struct mjs_object* obj_base; + struct mjs_property* prop; + struct mjs_property* next; + + assert(mjs_is_object(*v)); + + obj_base = get_object_struct(*v); + + /* + * we treat all object like things like objects but they might be functions, + * gc_check_val checks the appropriate arena per actual value type. + */ + if(!gc_check_val(mjs, *v)) { + abort(); + } + + if(MARKED(obj_base)) return; + + /* mark object itself, and its properties */ + for((prop = obj_base->properties), MARK(obj_base); prop != NULL; prop = next) { + if(!gc_check_ptr(&mjs->property_arena, prop)) { + abort(); + } + + gc_mark(mjs, &prop->name); + gc_mark(mjs, &prop->value); + + next = prop->next; + MARK(prop); + } + + /* mark object's prototype */ + /* + * We dropped support for object prototypes in MJS. + * If we ever bring it back, don't forget to mark it + */ + /* gc_mark(mjs, mjs_get_proto(mjs, v)); */ +} + +/* Mark a string value */ +static void gc_mark_string(struct mjs* mjs, mjs_val_t* v) { + mjs_val_t h, tmp = 0; + char* s; + + /* clang-format off */ + + /* + * If a value points to an unmarked string we shall: + * 1. save the first 6 bytes of the string + * since we need to be able to distinguish real values from + * the saved first 6 bytes of the string, we need to tag the chunk + * as MJS_TAG_STRING_C + * 2. encode value's address (v) into the first 6 bytes of the string. + * 3. put the saved 8 bytes (tag + chunk) back into the value. + * 4. mark the string by putting '\1' in the NUL terminator of the previous + * string chunk. + * + * If a value points to an already marked string we shall: + * (0, <6 bytes of a pointer to a mjs_val_t>), hence we have to skip + * the first byte. We tag the value pointer as a MJS_TAG_FOREIGN + * so that it won't be followed during recursive mark. + * + * ... the rest is the same + * + * Note: 64-bit pointers can be represented with 48-bits + */ + + /* clang-format on */ + + assert((*v & MJS_TAG_MASK) == MJS_TAG_STRING_O); + + s = mjs->owned_strings.buf + gc_string_mjs_val_to_offset(*v); + assert(s < mjs->owned_strings.buf + mjs->owned_strings.len); + if(s[-1] == '\0') { + memcpy(&tmp, s, sizeof(tmp) - 2); + tmp |= MJS_TAG_STRING_C; + } else { + memcpy(&tmp, s, sizeof(tmp) - 2); + tmp |= MJS_TAG_FOREIGN; + } + + h = (mjs_val_t)(uintptr_t)v; + s[-1] = 1; + memcpy(s, &h, sizeof(h) - 2); + memcpy(v, &tmp, sizeof(tmp)); +} + +MJS_PRIVATE void gc_mark(struct mjs* mjs, mjs_val_t* v) { + if(mjs_is_object(*v)) { + gc_mark_object(mjs, v); + } + if(mjs_is_ffi_sig(*v)) { + gc_mark_ffi_sig(mjs, v); + } + if((*v & MJS_TAG_MASK) == MJS_TAG_STRING_O) { + gc_mark_string(mjs, v); + } +} + +MJS_PRIVATE uint64_t gc_string_mjs_val_to_offset(mjs_val_t v) { + return (((uint64_t)(uintptr_t)get_ptr(v)) & ~MJS_TAG_MASK); +} + +MJS_PRIVATE mjs_val_t gc_string_val_from_offset(uint64_t s) { + return s | MJS_TAG_STRING_O; +} + +void gc_compact_strings(struct mjs* mjs) { + char* p = mjs->owned_strings.buf + 1; + uint64_t h, next, head = 1; + int len, llen; + + while(p < mjs->owned_strings.buf + mjs->owned_strings.len) { + if(p[-1] == '\1') { + /* relocate and update ptrs */ + h = 0; + memcpy(&h, p, sizeof(h) - 2); + + /* + * relocate pointers until we find the tail. + * The tail is marked with MJS_TAG_STRING_C, + * while mjs_val_t link pointers are tagged with MJS_TAG_FOREIGN + */ + for(; (h & MJS_TAG_MASK) != MJS_TAG_STRING_C; h = next) { + h &= ~MJS_TAG_MASK; + memcpy(&next, (char*)(uintptr_t)h, sizeof(h)); + + *(mjs_val_t*)(uintptr_t)h = gc_string_val_from_offset(head); + } + h &= ~MJS_TAG_MASK; + + /* + * the tail contains the first 6 bytes we stole from + * the actual string. + */ + len = cs_varint_decode_unsafe((unsigned char*)&h, &llen); + len += llen + 1; + + /* + * restore the saved 6 bytes + * TODO(mkm): think about endianness + */ + memcpy(p, &h, sizeof(h) - 2); + + /* + * and relocate the string data by packing it to the left. + */ + memmove(mjs->owned_strings.buf + head, p, len); + mjs->owned_strings.buf[head - 1] = 0x0; + p += len; + head += len; + } else { + len = cs_varint_decode_unsafe((unsigned char*)p, &llen); + len += llen + 1; + + p += len; + } + } + + mjs->owned_strings.len = head; +} + +MJS_PRIVATE int maybe_gc(struct mjs* mjs) { + if(!mjs->inhibit_gc) { + mjs_gc(mjs, 0); + return 1; + } + return 0; +} + +/* + * mark an array of `mjs_val_t` values (*not pointers* to them) + */ +static void gc_mark_val_array(struct mjs* mjs, mjs_val_t* vals, size_t len) { + mjs_val_t* vp; + for(vp = vals; vp < vals + len; vp++) { + gc_mark(mjs, vp); + } +} + +/* + * mark an mbuf containing *pointers* to `mjs_val_t` values + */ +static void gc_mark_mbuf_pt(struct mjs* mjs, const struct mbuf* mbuf) { + mjs_val_t** vp; + for(vp = (mjs_val_t**)mbuf->buf; (char*)vp < mbuf->buf + mbuf->len; vp++) { + gc_mark(mjs, *vp); + } +} + +/* + * mark an mbuf containing `mjs_val_t` values (*not pointers* to them) + */ +static void gc_mark_mbuf_val(struct mjs* mjs, const struct mbuf* mbuf) { + gc_mark_val_array(mjs, (mjs_val_t*)mbuf->buf, mbuf->len / sizeof(mjs_val_t)); +} + +static void gc_mark_ffi_cbargs_list(struct mjs* mjs, ffi_cb_args_t* cbargs) { + for(; cbargs != NULL; cbargs = cbargs->next) { + gc_mark(mjs, &cbargs->func); + gc_mark(mjs, &cbargs->userdata); + } +} + +/* Perform garbage collection */ +void mjs_gc(struct mjs* mjs, int full) { + gc_mark_val_array(mjs, (mjs_val_t*)&mjs->vals, sizeof(mjs->vals) / sizeof(mjs_val_t)); + + gc_mark_mbuf_pt(mjs, &mjs->owned_values); + gc_mark_mbuf_val(mjs, &mjs->scopes); + gc_mark_mbuf_val(mjs, &mjs->stack); + gc_mark_mbuf_val(mjs, &mjs->call_stack); + + gc_mark_ffi_cbargs_list(mjs, mjs->ffi_cb_args); + + gc_compact_strings(mjs); + + gc_sweep(mjs, &mjs->object_arena, 0); + gc_sweep(mjs, &mjs->property_arena, 0); + gc_sweep(mjs, &mjs->ffi_sig_arena, 0); + + if(full) { + /* + * In case of full GC, we also resize strings buffer, but we still leave + * some extra space (at most, `_MJS_STRING_BUF_RESERVE`) in order to avoid + * frequent reallocations + */ + size_t trimmed_size = mjs->owned_strings.len + _MJS_STRING_BUF_RESERVE; + if(trimmed_size < mjs->owned_strings.size) { + mbuf_resize(&mjs->owned_strings, trimmed_size); + } + } +} + +MJS_PRIVATE int gc_check_val(struct mjs* mjs, mjs_val_t v) { + if(mjs_is_object(v)) { + return gc_check_ptr(&mjs->object_arena, get_object_struct(v)); + } + if(mjs_is_ffi_sig(v)) { + return gc_check_ptr(&mjs->ffi_sig_arena, mjs_get_ffi_sig_struct(v)); + } + return 1; +} + +MJS_PRIVATE int gc_check_ptr(const struct gc_arena* a, const void* ptr) { + const struct gc_cell* p = (const struct gc_cell*)ptr; + struct gc_block* b; + for(b = a->blocks; b != NULL; b = b->next) { + if(p >= b->base && p < GC_CELL_OP(a, b->base, +, b->size)) { + return 1; + } + } + return 0; +} diff --git a/applications/system/elk_mjs/lib/mjs/mjs_gc.h b/applications/system/elk_mjs/lib/mjs/mjs_gc.h new file mode 100644 index 00000000000..4f469d05dd2 --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/mjs_gc.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2014 Cesanta Software Limited + * All rights reserved + */ + +#ifndef MJS_GC_H_ +#define MJS_GC_H_ + +#include "mjs_core.h" +#include "mjs_mm.h" +#include "mjs_internal.h" +#include "mjs_gc_public.h" + +#if defined(__cplusplus) +extern "C" { +#endif /* __cplusplus */ + +/* + * performs arithmetics on gc_cell pointers as if they were arena->cell_size + * bytes wide + */ +#define GC_CELL_OP(arena, cell, op, arg) \ + ((struct gc_cell*)(((char*)(cell))op((arg) * (arena)->cell_size))) + +struct gc_cell { + union { + struct gc_cell* link; + uintptr_t word; + } head; +}; + +MJS_PRIVATE int gc_strings_is_gc_needed(struct mjs* mjs); + +/* perform gc if not inhibited */ +MJS_PRIVATE int maybe_gc(struct mjs* mjs); + +MJS_PRIVATE struct mjs_object* new_object(struct mjs*); +MJS_PRIVATE struct mjs_property* new_property(struct mjs*); +MJS_PRIVATE struct mjs_ffi_sig* new_ffi_sig(struct mjs* mjs); + +MJS_PRIVATE void gc_mark(struct mjs* mjs, mjs_val_t* val); + +MJS_PRIVATE void gc_arena_init(struct gc_arena*, size_t, size_t, size_t); +MJS_PRIVATE void gc_arena_destroy(struct mjs*, struct gc_arena* a); +MJS_PRIVATE void gc_sweep(struct mjs*, struct gc_arena*, size_t); +MJS_PRIVATE void* gc_alloc_cell(struct mjs*, struct gc_arena*); + +MJS_PRIVATE uint64_t gc_string_mjs_val_to_offset(mjs_val_t v); + +/* return 0 if v is an object/function with a bad pointer */ +MJS_PRIVATE int gc_check_val(struct mjs* mjs, mjs_val_t v); + +/* checks whether a pointer is within the ranges of an arena */ +MJS_PRIVATE int gc_check_ptr(const struct gc_arena* a, const void* p); + +#if defined(__cplusplus) +} +#endif /* __cplusplus */ + +#endif /* MJS_GC_H_ */ diff --git a/applications/system/elk_mjs/lib/mjs/mjs_gc_public.h b/applications/system/elk_mjs/lib/mjs/mjs_gc_public.h new file mode 100644 index 00000000000..4ee2d209f30 --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/mjs_gc_public.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2014 Cesanta Software Limited + * All rights reserved + */ + +#ifndef MJS_GC_PUBLIC_H_ +#define MJS_GC_PUBLIC_H_ + +#include "mjs_core_public.h" + +#if defined(__cplusplus) +extern "C" { +#endif /* __cplusplus */ + +/* + * Perform garbage collection. + * Pass true to full in order to reclaim unused heap back to the OS. + */ +void mjs_gc(struct mjs* mjs, int full); + +#if defined(__cplusplus) +} +#endif /* __cplusplus */ + +#endif /* MJS_GC_PUBLIC_H_ */ diff --git a/applications/system/elk_mjs/lib/mjs/mjs_internal.h b/applications/system/elk_mjs/lib/mjs/mjs_internal.h new file mode 100644 index 00000000000..d35ce5e700f --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/mjs_internal.h @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2016 Cesanta Software Limited + * All rights reserved + */ + +#ifndef MJS_INTERNAL_H_ +#define MJS_INTERNAL_H_ + +#include +#include +#include +#include +#include +#include + +#ifndef FAST +#define FAST +#endif + +#ifndef STATIC +#define STATIC +#endif + +#ifndef ENDL +#define ENDL "\n" +#endif + +#ifdef MJS_EXPOSE_PRIVATE +#define MJS_PRIVATE +#define MJS_EXTERN extern +#else +#define MJS_PRIVATE static +#define MJS_EXTERN static +#endif + +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) +#endif + +#if !defined(WEAK) +#if(defined(__GNUC__) || defined(__TI_COMPILER_VERSION__)) && !defined(_WIN32) +#define WEAK __attribute__((weak)) +#else +#define WEAK +#endif +#endif + +#ifndef CS_ENABLE_STDIO +#define CS_ENABLE_STDIO 1 +#endif + +#include "common/cs_dbg.h" +#include "common/cs_file.h" +#include "common/mbuf.h" + +#if defined(_WIN32) && _MSC_VER < 1700 +typedef signed char int8_t; +typedef unsigned char uint8_t; +typedef int int32_t; +typedef unsigned int uint32_t; +typedef short int16_t; +typedef unsigned short uint16_t; +typedef __int64 int64_t; +typedef unsigned long uintptr_t; +#define STRX(x) #x +#define STR(x) STRX(x) +#define __func__ __FILE__ ":" STR(__LINE__) +// #define snprintf _snprintf +#define vsnprintf _vsnprintf +#define isnan(x) _isnan(x) +#define va_copy(x, y) (x) = (y) +#define CS_DEFINE_DIRENT +#include +#else +#if defined(__unix__) || defined(__APPLE__) +#include +#endif +#endif + +/* + * Number of bytes reserved for the jump offset initially. The most practical + * value is 1, but for testing it's useful to set it to 0 and to some large + * value as well (like, 4), to make sure that the code behaves correctly under + * all circumstances. + */ +#ifndef MJS_INIT_OFFSET_SIZE +#define MJS_INIT_OFFSET_SIZE 1 +#endif + +#endif /* MJS_INTERNAL_H_ */ diff --git a/applications/system/elk_mjs/lib/mjs/mjs_json.c b/applications/system/elk_mjs/lib/mjs/mjs_json.c new file mode 100644 index 00000000000..45839968ccd --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/mjs_json.c @@ -0,0 +1,519 @@ +/* + * Copyright (c) 2016 Cesanta Software Limited + * All rights reserved + */ + +#include "common/str_util.h" +#include "common/frozen/frozen.h" +#include "mjs_array.h" +#include "mjs_internal.h" +#include "mjs_conversion.h" +#include "mjs_core.h" +#include "mjs_object.h" +#include "mjs_primitive.h" +#include "mjs_string.h" + +#define BUF_LEFT(size, used) (((size_t)(used) < (size)) ? ((size) - (used)) : 0) + +/* + * Returns whether the value of given type should be skipped when generating + * JSON output + * + * So far it always returns 0, but we might add some logic later, if we + * implement some non-jsonnable objects + */ +static int should_skip_for_json(enum mjs_type type) { + int ret; + switch(type) { + /* All permitted values */ + case MJS_TYPE_NULL: + case MJS_TYPE_BOOLEAN: + case MJS_TYPE_NUMBER: + case MJS_TYPE_STRING: + case MJS_TYPE_OBJECT_GENERIC: + case MJS_TYPE_OBJECT_ARRAY: + ret = 0; + break; + default: + ret = 1; + break; + } + return ret; +} + +static const char* hex_digits = "0123456789abcdef"; +static char* append_hex(char* buf, char* limit, uint8_t c) { + if(buf < limit) *buf++ = 'u'; + if(buf < limit) *buf++ = '0'; + if(buf < limit) *buf++ = '0'; + if(buf < limit) *buf++ = hex_digits[(int)((c >> 4) % 0xf)]; + if(buf < limit) *buf++ = hex_digits[(int)(c & 0xf)]; + return buf; +} + +/* + * Appends quoted s to buf. Any double quote contained in s will be escaped. + * Returns the number of characters that would have been added, + * like snprintf. + * If size is zero it doesn't output anything but keeps counting. + */ +static int snquote(char* buf, size_t size, const char* s, size_t len) { + char* limit = buf + size; + const char* end; + /* + * String single character escape sequence: + * http://www.ecma-international.org/ecma-262/6.0/index.html#table-34 + * + * 0x8 -> \b + * 0x9 -> \t + * 0xa -> \n + * 0xb -> \v + * 0xc -> \f + * 0xd -> \r + */ + const char* specials = "btnvfr"; + size_t i = 0; + + i++; + if(buf < limit) *buf++ = '"'; + + for(end = s + len; s < end; s++) { + if(*s == '"' || *s == '\\') { + i++; + if(buf < limit) *buf++ = '\\'; + } else if(*s >= '\b' && *s <= '\r') { + i += 2; + if(buf < limit) *buf++ = '\\'; + if(buf < limit) *buf++ = specials[*s - '\b']; + continue; + } else if((unsigned char)*s < '\b' || (*s > '\r' && *s < ' ')) { + i += 6 /* \uXXXX */; + if(buf < limit) *buf++ = '\\'; + buf = append_hex(buf, limit, (uint8_t)*s); + continue; + } + i++; + if(buf < limit) *buf++ = *s; + } + + i++; + if(buf < limit) *buf++ = '"'; + + if(buf < limit) { + *buf = '\0'; + } else if(size != 0) { + /* + * There is no room for the NULL char, but the size wasn't zero, so we can + * safely put NULL in the previous byte + */ + *(buf - 1) = '\0'; + } + return i; +} + +MJS_PRIVATE mjs_err_t to_json_or_debug( + struct mjs* mjs, + mjs_val_t v, + char* buf, + size_t size, + size_t* res_len, + uint8_t is_debug) { + mjs_val_t el; + char* vp; + mjs_err_t rcode = MJS_OK; + size_t len = 0; + /* + * TODO(dfrank) : also push all `mjs_val_t`s that are declared below + */ + + if(size > 0) *buf = '\0'; + + if(!is_debug && should_skip_for_json(mjs_get_type(v))) { + goto clean; + } + + for(vp = mjs->json_visited_stack.buf; + vp < mjs->json_visited_stack.buf + mjs->json_visited_stack.len; + vp += sizeof(mjs_val_t)) { + if(*(mjs_val_t*)vp == v) { + strncpy(buf, "[Circular]", size); + len = 10; + goto clean; + } + } + + switch(mjs_get_type(v)) { + case MJS_TYPE_NULL: + case MJS_TYPE_BOOLEAN: + case MJS_TYPE_NUMBER: + case MJS_TYPE_UNDEFINED: + case MJS_TYPE_FOREIGN: + /* For those types, regular `mjs_to_string()` works */ + { + /* TODO: refactor: mjs_to_string allocates memory every time */ + char* p = NULL; + int need_free = 0; + rcode = mjs_to_string(mjs, &v, &p, &len, &need_free); + c_snprintf(buf, size, "%.*s", (int)len, p); + if(need_free) { + free(p); + } + } + goto clean; + + case MJS_TYPE_STRING: { + /* + * For strings we can't just use `primitive_to_str()`, because we need + * quoted value + */ + size_t n; + const char* str = mjs_get_string(mjs, &v, &n); + len = snquote(buf, size, str, n); + goto clean; + } + + case MJS_TYPE_OBJECT_FUNCTION: + case MJS_TYPE_OBJECT_GENERIC: { + char* b = buf; + struct mjs_property* prop = NULL; + struct mjs_object* o = NULL; + + mbuf_append(&mjs->json_visited_stack, (char*)&v, sizeof(v)); + b += c_snprintf(b, BUF_LEFT(size, b - buf), "{"); + o = get_object_struct(v); + for(prop = o->properties; prop != NULL; prop = prop->next) { + size_t n; + const char* s; + if(!is_debug && should_skip_for_json(mjs_get_type(prop->value))) { + continue; + } + if(b - buf != 1) { /* Not the first property to be printed */ + b += c_snprintf(b, BUF_LEFT(size, b - buf), ","); + } + s = mjs_get_string(mjs, &prop->name, &n); + b += c_snprintf(b, BUF_LEFT(size, b - buf), "\"%.*s\":", (int)n, s); + { + size_t tmp = 0; + rcode = + to_json_or_debug(mjs, prop->value, b, BUF_LEFT(size, b - buf), &tmp, is_debug); + if(rcode != MJS_OK) { + goto clean_iter; + } + b += tmp; + } + } + + b += c_snprintf(b, BUF_LEFT(size, b - buf), "}"); + mjs->json_visited_stack.len -= sizeof(v); + + clean_iter: + len = b - buf; + goto clean; + } + case MJS_TYPE_OBJECT_ARRAY: { + int has; + char* b = buf; + size_t i, alen = mjs_array_length(mjs, v); + mbuf_append(&mjs->json_visited_stack, (char*)&v, sizeof(v)); + b += c_snprintf(b, BUF_LEFT(size, b - buf), "["); + for(i = 0; i < alen; i++) { + el = mjs_array_get2(mjs, v, i, &has); + if(has) { + size_t tmp = 0; + if(!is_debug && should_skip_for_json(mjs_get_type(el))) { + b += c_snprintf(b, BUF_LEFT(size, b - buf), "null"); + } else { + rcode = to_json_or_debug(mjs, el, b, BUF_LEFT(size, b - buf), &tmp, is_debug); + if(rcode != MJS_OK) { + goto clean; + } + } + b += tmp; + } else { + b += c_snprintf(b, BUF_LEFT(size, b - buf), "null"); + } + if(i != alen - 1) { + b += c_snprintf(b, BUF_LEFT(size, b - buf), ","); + } + } + b += c_snprintf(b, BUF_LEFT(size, b - buf), "]"); + mjs->json_visited_stack.len -= sizeof(v); + len = b - buf; + goto clean; + } + + case MJS_TYPES_CNT: + abort(); + } + + abort(); + + len = 0; /* for compilers that don't know about abort() */ + goto clean; + +clean: + if(rcode != MJS_OK) { + len = 0; + } + if(res_len != NULL) { + *res_len = len; + } + return rcode; +} + +MJS_PRIVATE mjs_err_t + mjs_json_stringify(struct mjs* mjs, mjs_val_t v, char* buf, size_t size, char** res) { + mjs_err_t rcode = MJS_OK; + char* p = buf; + size_t len; + + to_json_or_debug(mjs, v, buf, size, &len, 0); + + if(len >= size) { + /* Buffer is not large enough. Allocate a bigger one */ + p = (char*)malloc(len + 1); + rcode = mjs_json_stringify(mjs, v, p, len + 1, res); + assert(*res == p); + goto clean; + } else { + *res = p; + goto clean; + } + +clean: + /* + * If we're going to return an error, and we allocated a buffer, then free + * it. Otherwise, caller should free it. + */ + if(rcode != MJS_OK && p != buf) { + free(p); + } + return rcode; +} + +/* + * JSON parsing frame: a separate frame is allocated for each nested + * object/array during parsing + */ +struct json_parse_frame { + mjs_val_t val; + struct json_parse_frame* up; +}; + +/* + * Context for JSON parsing by means of json_walk() + */ +struct json_parse_ctx { + struct mjs* mjs; + mjs_val_t result; + struct json_parse_frame* frame; + enum mjs_err rcode; +}; + +/* Allocate JSON parse frame */ +static struct json_parse_frame* alloc_json_frame(struct json_parse_ctx* ctx, mjs_val_t v) { + struct json_parse_frame* frame = + (struct json_parse_frame*)calloc(sizeof(struct json_parse_frame), 1); + frame->val = v; + mjs_own(ctx->mjs, &frame->val); + return frame; +} + +/* Free JSON parse frame, return the previous one (which may be NULL) */ +static struct json_parse_frame* + free_json_frame(struct json_parse_ctx* ctx, struct json_parse_frame* frame) { + struct json_parse_frame* up = frame->up; + mjs_disown(ctx->mjs, &frame->val); + free(frame); + return up; +} + +/* Callback for json_walk() */ +static void frozen_cb( + void* data, + const char* name, + size_t name_len, + const char* path, + const struct json_token* token) { + struct json_parse_ctx* ctx = (struct json_parse_ctx*)data; + mjs_val_t v = MJS_UNDEFINED; + + (void)path; + + mjs_own(ctx->mjs, &v); + + switch(token->type) { + case JSON_TYPE_STRING: { + char* dst; + if(token->len > 0 && (dst = malloc(token->len)) != NULL) { + int len = json_unescape(token->ptr, token->len, dst, token->len); + if(len < 0) { + mjs_prepend_errorf(ctx->mjs, MJS_TYPE_ERROR, "invalid JSON string"); + break; + } + v = mjs_mk_string(ctx->mjs, dst, len, 1 /* copy */); + free(dst); + } else { + /* + * This branch is for 0-len strings, and for malloc errors + * TODO(lsm): on malloc error, propagate the error upstream + */ + v = mjs_mk_string(ctx->mjs, "", 0, 1 /* copy */); + } + break; + } + case JSON_TYPE_NUMBER: + v = mjs_mk_number(ctx->mjs, strtod(token->ptr, NULL)); + break; + case JSON_TYPE_TRUE: + v = mjs_mk_boolean(ctx->mjs, 1); + break; + case JSON_TYPE_FALSE: + v = mjs_mk_boolean(ctx->mjs, 0); + break; + case JSON_TYPE_NULL: + v = MJS_NULL; + break; + case JSON_TYPE_OBJECT_START: + v = mjs_mk_object(ctx->mjs); + break; + case JSON_TYPE_ARRAY_START: + v = mjs_mk_array(ctx->mjs); + break; + + case JSON_TYPE_OBJECT_END: + case JSON_TYPE_ARRAY_END: { + /* Object or array has finished: deallocate its frame */ + ctx->frame = free_json_frame(ctx, ctx->frame); + } break; + + default: + LOG(LL_ERROR, ("Wrong token type %d\n", token->type)); + break; + } + + if(!mjs_is_undefined(v)) { + if(name != NULL && name_len != 0) { + /* Need to define a property on the current object/array */ + if(mjs_is_object(ctx->frame->val)) { + mjs_set(ctx->mjs, ctx->frame->val, name, name_len, v); + } else if(mjs_is_array(ctx->frame->val)) { + /* + * TODO(dfrank): consult name_len. Currently it's not a problem due to + * the implementation details of frozen, but it might change + */ + int idx = (int)strtod(name, NULL); + mjs_array_set(ctx->mjs, ctx->frame->val, idx, v); + } else { + LOG(LL_ERROR, ("Current value is neither object nor array\n")); + } + } else { + /* This is a root value */ + assert(ctx->frame == NULL); + + /* + * This value will also be the overall result of JSON parsing + * (it's already owned by the `mjs_alt_json_parse()`) + */ + ctx->result = v; + } + + if(token->type == JSON_TYPE_OBJECT_START || token->type == JSON_TYPE_ARRAY_START) { + /* New object or array has just started, so we need to allocate a frame + * for it */ + struct json_parse_frame* new_frame = alloc_json_frame(ctx, v); + new_frame->up = ctx->frame; + ctx->frame = new_frame; + } + } + + mjs_disown(ctx->mjs, &v); +} + +MJS_PRIVATE mjs_err_t mjs_json_parse(struct mjs* mjs, const char* str, size_t len, mjs_val_t* res) { + struct json_parse_ctx* ctx = (struct json_parse_ctx*)calloc(sizeof(struct json_parse_ctx), 1); + int json_res; + enum mjs_err rcode = MJS_OK; + + ctx->mjs = mjs; + ctx->result = MJS_UNDEFINED; + ctx->frame = NULL; + ctx->rcode = MJS_OK; + + mjs_own(mjs, &ctx->result); + + { + /* + * We have to reallocate the buffer before invoking json_walk, because + * frozen_cb can create new strings, which can result in the reallocation + * of mjs string mbuf, invalidating the `str` pointer. + */ + char* stmp = malloc(len); + memcpy(stmp, str, len); + json_res = json_walk(stmp, len, frozen_cb, ctx); + free(stmp); + stmp = NULL; + + /* str might have been invalidated, so null it out */ + str = NULL; + } + + if(ctx->rcode != MJS_OK) { + rcode = ctx->rcode; + mjs_prepend_errorf(mjs, rcode, "invalid JSON string"); + } else if(json_res < 0) { + /* There was an error during parsing */ + rcode = MJS_TYPE_ERROR; + mjs_prepend_errorf(mjs, rcode, "invalid JSON string"); + } else { + /* Expression is parsed successfully */ + *res = ctx->result; + + /* There should be no allocated frames */ + assert(ctx->frame == NULL); + } + + if(rcode != MJS_OK) { + /* There might be some allocated frames in case of malformed JSON */ + while(ctx->frame != NULL) { + ctx->frame = free_json_frame(ctx, ctx->frame); + } + } + + mjs_disown(mjs, &ctx->result); + free(ctx); + + return rcode; +} + +MJS_PRIVATE void mjs_op_json_stringify(struct mjs* mjs) { + mjs_val_t ret = MJS_UNDEFINED; + mjs_val_t val = mjs_arg(mjs, 0); + + if(mjs_nargs(mjs) < 1) { + mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "missing a value to stringify"); + } else { + char* p = NULL; + if(mjs_json_stringify(mjs, val, NULL, 0, &p) == MJS_OK) { + ret = mjs_mk_string(mjs, p, ~0, 1 /* copy */); + free(p); + } + } + + mjs_return(mjs, ret); +} + +MJS_PRIVATE void mjs_op_json_parse(struct mjs* mjs) { + mjs_val_t ret = MJS_UNDEFINED; + mjs_val_t arg0 = mjs_arg(mjs, 0); + + if(mjs_is_string(arg0)) { + size_t len; + const char* str = mjs_get_string(mjs, &arg0, &len); + mjs_json_parse(mjs, str, len, &ret); + } else { + mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "string argument required"); + } + + mjs_return(mjs, ret); +} diff --git a/applications/system/elk_mjs/lib/mjs/mjs_json.h b/applications/system/elk_mjs/lib/mjs/mjs_json.h new file mode 100644 index 00000000000..f92a30d1ad9 --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/mjs_json.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2016 Cesanta Software Limited + * All rights reserved + */ + +#ifndef MJS_JSON_H_ +#define MJS_JSON_H_ + +#if defined(__cplusplus) +extern "C" { +#endif /* __cplusplus */ + +MJS_PRIVATE mjs_err_t to_json_or_debug( + struct mjs* mjs, + mjs_val_t v, + char* buf, + size_t size, + size_t* res_len, + uint8_t is_debug); + +MJS_PRIVATE mjs_err_t + mjs_json_stringify(struct mjs* mjs, mjs_val_t v, char* buf, size_t size, char** res); +MJS_PRIVATE void mjs_op_json_stringify(struct mjs* mjs); +MJS_PRIVATE void mjs_op_json_parse(struct mjs* mjs); + +MJS_PRIVATE mjs_err_t mjs_json_parse(struct mjs* mjs, const char* str, size_t len, mjs_val_t* res); + +#if defined(__cplusplus) +} +#endif /* __cplusplus */ + +#endif /* MJS_JSON_H_ */ diff --git a/applications/system/elk_mjs/lib/mjs/mjs_license.h b/applications/system/elk_mjs/lib/mjs/mjs_license.h new file mode 100644 index 00000000000..82a5407191a --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/mjs_license.h @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2017 Cesanta Software Limited + * All rights reserved + * + * This software is dual-licensed: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. For the terms of this + * license, see . + * + * You are free to use this software under the terms of the GNU General + * Public License, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * Alternatively, you can license this software under a commercial + * license, as set out in . + */ diff --git a/applications/system/elk_mjs/lib/mjs/mjs_mm.h b/applications/system/elk_mjs/lib/mjs/mjs_mm.h new file mode 100644 index 00000000000..07bd423d5af --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/mjs_mm.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2014-2016 Cesanta Software Limited + * All rights reserved + */ + +#ifndef MJS_MM_H_ +#define MJS_MM_H_ + +#include "mjs_internal.h" + +#if defined(__cplusplus) +extern "C" { +#endif /* __cplusplus */ + +struct mjs; + +typedef void (*gc_cell_destructor_t)(struct mjs* mjs, void*); + +struct gc_block { + struct gc_block* next; + struct gc_cell* base; + size_t size; +}; + +struct gc_arena { + struct gc_block* blocks; + size_t size_increment; + struct gc_cell* free; /* head of free list */ + size_t cell_size; + +#if MJS_MEMORY_STATS + unsigned long allocations; /* cumulative counter of allocations */ + unsigned long garbage; /* cumulative counter of garbage */ + unsigned long alive; /* number of living cells */ +#endif + + gc_cell_destructor_t destructor; +}; + +#if defined(__cplusplus) +} +#endif /* __cplusplus */ + +#endif /* MJS_MM_H_ */ diff --git a/applications/system/elk_mjs/lib/mjs/mjs_object.c b/applications/system/elk_mjs/lib/mjs/mjs_object.c new file mode 100644 index 00000000000..7b4a3fe81bc --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/mjs_object.c @@ -0,0 +1,395 @@ +/* + * Copyright (c) 2016 Cesanta Software Limited + * All rights reserved + */ + +#include "mjs_object.h" +#include "mjs_conversion.h" +#include "mjs_core.h" +#include "mjs_internal.h" +#include "mjs_primitive.h" +#include "mjs_string.h" +#include "mjs_util.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 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(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; +} + +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(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(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(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; +} diff --git a/applications/system/elk_mjs/lib/mjs/mjs_object.h b/applications/system/elk_mjs/lib/mjs/mjs_object.h new file mode 100644 index 00000000000..1c4810385a7 --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/mjs_object.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2016 Cesanta Software Limited + * All rights reserved + */ + +#ifndef MJS_OBJECT_H_ +#define MJS_OBJECT_H_ + +#include "mjs_object_public.h" +#include "mjs_internal.h" + +#if defined(__cplusplus) +extern "C" { +#endif /* __cplusplus */ + +struct mjs; + +struct mjs_property { + struct mjs_property* next; /* Linkage in struct mjs_object::properties */ + mjs_val_t name; /* Property name (a string) */ + mjs_val_t value; /* Property value */ +}; + +struct mjs_object { + struct mjs_property* properties; +}; + +MJS_PRIVATE struct mjs_object* get_object_struct(mjs_val_t v); +MJS_PRIVATE struct mjs_property* + mjs_get_own_property(struct mjs* mjs, mjs_val_t obj, const char* name, size_t len); + +MJS_PRIVATE struct mjs_property* + mjs_get_own_property_v(struct mjs* mjs, mjs_val_t obj, mjs_val_t key); + +/* + * A worker function for `mjs_set()` and `mjs_set_v()`: it takes name as both + * ptr+len and mjs_val_t. If `name` pointer is not NULL, it takes precedence + * over `name_v`. + */ +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); + +/* + * Implementation of `Object.create(proto)` + */ +MJS_PRIVATE void mjs_op_create_object(struct mjs* mjs); + +#define MJS_PROTO_PROP_NAME "__p" /* Make it < 5 chars */ + +#if defined(__cplusplus) +} +#endif /* __cplusplus */ + +#endif /* MJS_OBJECT_H_ */ diff --git a/applications/system/elk_mjs/lib/mjs/mjs_object_public.h b/applications/system/elk_mjs/lib/mjs/mjs_object_public.h new file mode 100644 index 00000000000..bebb7c8eeb2 --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/mjs_object_public.h @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2016 Cesanta Software Limited + * All rights reserved + */ + +#ifndef MJS_OBJECT_PUBLIC_H_ +#define MJS_OBJECT_PUBLIC_H_ + +#include +#include "mjs_core_public.h" +#include "mjs_ffi_public.h" + +#if defined(__cplusplus) +extern "C" { +#endif /* __cplusplus */ + +/* + * Returns true if the given value is an object or array. + */ +int mjs_is_object(mjs_val_t v); + +/* Make an empty object */ +mjs_val_t mjs_mk_object(struct mjs* mjs); + +/* Field types for struct-object conversion. */ +enum mjs_struct_field_type { + MJS_STRUCT_FIELD_TYPE_INVALID, + MJS_STRUCT_FIELD_TYPE_STRUCT, /* Struct, arg points to def. */ + MJS_STRUCT_FIELD_TYPE_STRUCT_PTR, /* Ptr to struct, arg points to def. */ + MJS_STRUCT_FIELD_TYPE_INT, + MJS_STRUCT_FIELD_TYPE_BOOL, + MJS_STRUCT_FIELD_TYPE_DOUBLE, + MJS_STRUCT_FIELD_TYPE_FLOAT, + MJS_STRUCT_FIELD_TYPE_CHAR_PTR, /* NUL-terminated string. */ + MJS_STRUCT_FIELD_TYPE_VOID_PTR, /* Converted to foreign ptr. */ + MJS_STRUCT_FIELD_TYPE_MG_STR_PTR, /* Converted to string. */ + MJS_STRUCT_FIELD_TYPE_MG_STR, /* Converted to string. */ + MJS_STRUCT_FIELD_TYPE_DATA, /* Data, arg is length, becomes string. */ + MJS_STRUCT_FIELD_TYPE_INT8, + MJS_STRUCT_FIELD_TYPE_INT16, + MJS_STRUCT_FIELD_TYPE_UINT8, + MJS_STRUCT_FIELD_TYPE_UINT16, + /* + * User-provided function. Arg is a pointer to function that takes void * + * (pointer to field within the struct) and returns mjs_val_t: + * mjs_val_t field_value(struct mjs *mjs, const void *field_ptr) { ... } + */ + MJS_STRUCT_FIELD_TYPE_CUSTOM, +}; + +/* C structure layout descriptor - needed by mjs_struct_to_obj */ +struct mjs_c_struct_member { + const char* name; + int offset; + enum mjs_struct_field_type type; + const void* arg; /* Additional argument, used for some types. */ +}; + +/* Create flat JS object from a C memory descriptor */ +mjs_val_t + mjs_struct_to_obj(struct mjs* mjs, const void* base, const struct mjs_c_struct_member* members); + +/* + * Lookup property `name` in object `obj`. If `obj` holds no such property, + * an `undefined` value is returned. + * + * If `name_len` is ~0, `name` is assumed to be NUL-terminated and + * `strlen(name)` is used. + */ +mjs_val_t mjs_get(struct mjs* mjs, mjs_val_t obj, const char* name, size_t name_len); + +/* + * Like mjs_get but with a JS string. + */ +mjs_val_t mjs_get_v(struct mjs* mjs, mjs_val_t obj, mjs_val_t name); + +/* + * Like mjs_get_v but lookup the prototype chain. + */ +mjs_val_t mjs_get_v_proto(struct mjs* mjs, mjs_val_t obj, mjs_val_t key); + +/* + * Set object property. Behaves just like JavaScript assignment. + */ +mjs_err_t mjs_set(struct mjs* mjs, mjs_val_t obj, const char* name, size_t len, mjs_val_t val); + +/* + * Like mjs_set but the name is already a JS string. + */ +mjs_err_t mjs_set_v(struct mjs* mjs, mjs_val_t obj, mjs_val_t name, mjs_val_t val); + +/* + * Delete own property `name` of the object `obj`. Does not follow the + * prototype chain. + * + * If `name_len` is ~0, `name` is assumed to be NUL-terminated and + * `strlen(name)` is used. + * + * Returns 0 on success, -1 on error. + */ +int mjs_del(struct mjs* mjs, mjs_val_t obj, const char* name, size_t len); + +/* + * Iterate over `obj` properties. + * First call should set `iterator` to MJS_UNDEFINED. + * Return object's key (a string), or MJS_UNDEFINED when no more keys left. + * Do not mutate the object during iteration. + * + * Example: + * mjs_val_t key, iter = MJS_UNDEFINED; + * while ((key = mjs_next(mjs, obj, &iter)) != MJS_UNDEFINED) { + * // Do something with the obj/key ... + * } + */ +mjs_val_t mjs_next(struct mjs* mjs, mjs_val_t obj, mjs_val_t* iterator); + +#if defined(__cplusplus) +} +#endif /* __cplusplus */ + +#endif /* MJS_OBJECT_PUBLIC_H_ */ diff --git a/applications/system/elk_mjs/lib/mjs/mjs_parser.c b/applications/system/elk_mjs/lib/mjs/mjs_parser.c new file mode 100644 index 00000000000..5224124dacd --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/mjs_parser.c @@ -0,0 +1,1032 @@ +/* + * Copyright (c) 2017 Cesanta Software Limited + * All rights reserved + */ + +#include "common/cs_varint.h" + +#include "mjs_bcode.h" +#include "mjs_core.h" +#include "mjs_internal.h" +#include "mjs_parser.h" +#include "mjs_string.h" +#include "mjs_tok.h" + +#ifndef MAX_TOKS_IN_EXPR +#define MAX_TOKS_IN_EXPR 40 +#endif + +#define FAIL_ERR(p, code) \ + do { \ + mjs_set_errorf( \ + p->mjs, code, "parse error at line %d: [%.*s]", p->line_no, 10, p->tok.ptr); \ + return code; \ + } while(0) + +#define pnext1(p) \ + do { \ + LOG(LL_VERBOSE_DEBUG, (" PNEXT %d", __LINE__)); \ + pnext(p); \ + } while(0) + +#define SYNTAX_ERROR(p) FAIL_ERR(p, MJS_SYNTAX_ERROR) +#undef EXPECT +#define EXPECT(p, t) \ + if((p)->tok.tok != (t)) \ + SYNTAX_ERROR(p); \ + else \ + pnext1(p); + +static mjs_err_t parse_statement(struct pstate* p); +static mjs_err_t parse_expr(struct pstate* p); + +static int ptest(struct pstate* p) { + struct pstate saved = *p; + int tok = pnext(p); + *p = saved; + return tok; +} + +static int s_unary_ops[] = { + TOK_NOT, + TOK_TILDA, + TOK_PLUS_PLUS, + TOK_MINUS_MINUS, + TOK_KEYWORD_TYPEOF, + TOK_MINUS, + TOK_PLUS, + TOK_EOF}; +static int s_comparison_ops[] = {TOK_LT, TOK_LE, TOK_GT, TOK_GE, TOK_EOF}; +static int s_postfix_ops[] = {TOK_PLUS_PLUS, TOK_MINUS_MINUS, TOK_EOF}; +static int s_equality_ops[] = {TOK_EQ, TOK_NE, TOK_EQ_EQ, TOK_NE_NE, TOK_EOF}; +static int s_assign_ops[] = { + TOK_ASSIGN, + TOK_PLUS_ASSIGN, + TOK_MINUS_ASSIGN, + TOK_MUL_ASSIGN, + TOK_DIV_ASSIGN, + TOK_REM_ASSIGN, + TOK_LSHIFT_ASSIGN, + TOK_RSHIFT_ASSIGN, + TOK_URSHIFT_ASSIGN, + TOK_AND_ASSIGN, + TOK_XOR_ASSIGN, + TOK_OR_ASSIGN, + TOK_EOF}; + +static int findtok(int* toks, int tok) { + int i = 0; + while(tok != toks[i] && toks[i] != TOK_EOF) i++; + return toks[i]; +} + +static void emit_op(struct pstate* pstate, int tok) { + assert(tok >= 0 && tok <= 255); + emit_byte(pstate, OP_EXPR); + emit_byte(pstate, (uint8_t)tok); +} + +#define BINOP_STACK_FRAME_SIZE 16 +#define STACK_LIMIT 8192 + +// Intentionally left as macro rather than a function, to let the +// compiler to inline calls and mimimize runtime stack usage. +#define PARSE_LTR_BINOP(p, f1, f2, ops, prev_op) \ + do { \ + mjs_err_t res = MJS_OK; \ + p->depth++; \ + if(p->depth > (STACK_LIMIT / BINOP_STACK_FRAME_SIZE)) { \ + mjs_set_errorf(p->mjs, MJS_SYNTAX_ERROR, "parser stack overflow"); \ + res = MJS_SYNTAX_ERROR; \ + goto binop_clean; \ + } \ + if((res = f1(p, TOK_EOF)) != MJS_OK) goto binop_clean; \ + if(prev_op != TOK_EOF) emit_op(p, prev_op); \ + if(findtok(ops, p->tok.tok) != TOK_EOF) { \ + int op = p->tok.tok; \ + size_t off_if = 0; \ + /* For AND/OR, implement short-circuit evaluation */ \ + if(ops[0] == TOK_LOGICAL_AND || ops[0] == TOK_LOGICAL_OR) { \ + emit_byte( \ + p, \ + (uint8_t)(ops[0] == TOK_LOGICAL_AND ? OP_JMP_NEUTRAL_FALSE : OP_JMP_NEUTRAL_TRUE)); \ + off_if = p->cur_idx; \ + emit_init_offset(p); \ + /* No need to emit TOK_LOGICAL_AND and TOK_LOGICAL_OR: */ \ + /* Just drop the first value, and evaluate the second one. */ \ + emit_byte(p, (uint8_t)OP_DROP); \ + op = TOK_EOF; \ + } \ + pnext1(p); \ + if((res = f2(p, op)) != MJS_OK) goto binop_clean; \ + \ + if(off_if != 0) { \ + mjs_bcode_insert_offset( \ + p, p->mjs, off_if, p->cur_idx - off_if - MJS_INIT_OFFSET_SIZE); \ + } \ + } \ + binop_clean: \ + p->depth--; \ + return res; \ + } while(0) + +#define PARSE_RTL_BINOP(p, f1, f2, ops, prev_op) \ + do { \ + mjs_err_t res = MJS_OK; \ + (void)prev_op; \ + if((res = f1(p, TOK_EOF)) != MJS_OK) return res; \ + if(findtok(ops, p->tok.tok) != TOK_EOF) { \ + int op = p->tok.tok; \ + pnext1(p); \ + if((res = f2(p, TOK_EOF)) != MJS_OK) return res; \ + emit_op(p, op); \ + } \ + return res; \ + } while(0) + +#if MJS_INIT_OFFSET_SIZE > 0 +static void emit_init_offset(struct pstate* p) { + size_t i; + for(i = 0; i < MJS_INIT_OFFSET_SIZE; i++) { + emit_byte(p, 0); + } +} +#else +static void emit_init_offset(struct pstate* p) { + (void)p; +} +#endif + +static mjs_err_t parse_statement_list(struct pstate* p, int et) { + mjs_err_t res = MJS_OK; + int drop = 0; + pnext1(p); + while(res == MJS_OK && p->tok.tok != TOK_EOF && p->tok.tok != et) { + if(drop) emit_byte(p, OP_DROP); + res = parse_statement(p); + drop = 1; + while(p->tok.tok == TOK_SEMICOLON) pnext1(p); + } + + /* + * Client code expects statement list to contain a value, so if the statement + * list was empty, push `undefined`. + */ + if(!drop) { + emit_byte(p, OP_PUSH_UNDEF); + } + return res; +} + +static mjs_err_t parse_block(struct pstate* p, int mkscope) { + mjs_err_t res = MJS_OK; + p->depth++; + if(p->depth > (STACK_LIMIT / BINOP_STACK_FRAME_SIZE)) { + mjs_set_errorf(p->mjs, MJS_SYNTAX_ERROR, "parser stack overflow"); + res = MJS_SYNTAX_ERROR; + return res; + } + LOG(LL_VERBOSE_DEBUG, ("[%.*s]", 10, p->tok.ptr)); + if(mkscope) emit_byte(p, OP_NEW_SCOPE); + res = parse_statement_list(p, TOK_CLOSE_CURLY); + EXPECT(p, TOK_CLOSE_CURLY); + if(mkscope) emit_byte(p, OP_DEL_SCOPE); + return res; +} + +static mjs_err_t parse_function(struct pstate* p) { + size_t prologue, off; + int arg_no = 0; + int name_provided = 0; + mjs_err_t res = MJS_OK; + + EXPECT(p, TOK_KEYWORD_FUNCTION); + + if(p->tok.tok == TOK_IDENT) { + /* Function name was provided */ + struct tok tmp = p->tok; + name_provided = 1; + emit_byte(p, OP_PUSH_STR); + emit_str(p, tmp.ptr, tmp.len); + emit_byte(p, OP_PUSH_SCOPE); + emit_byte(p, OP_CREATE); + emit_byte(p, OP_PUSH_STR); + emit_str(p, tmp.ptr, tmp.len); + emit_byte(p, OP_FIND_SCOPE); + pnext1(p); + } + + emit_byte(p, OP_JMP); + off = p->cur_idx; + emit_init_offset(p); + + prologue = p->cur_idx; + + EXPECT(p, TOK_OPEN_PAREN); + emit_byte(p, OP_NEW_SCOPE); + // Emit names of function arguments + while(p->tok.tok != TOK_CLOSE_PAREN) { + if(p->tok.tok != TOK_IDENT) SYNTAX_ERROR(p); + emit_byte(p, OP_SET_ARG); + emit_int(p, arg_no); + arg_no++; + emit_str(p, p->tok.ptr, p->tok.len); + if(ptest(p) == TOK_COMMA) pnext1(p); + pnext1(p); + } + EXPECT(p, TOK_CLOSE_PAREN); + if((res = parse_block(p, 0)) != MJS_OK) return res; + emit_byte(p, OP_RETURN); + prologue += mjs_bcode_insert_offset(p, p->mjs, off, p->cur_idx - off - MJS_INIT_OFFSET_SIZE); + emit_byte(p, OP_PUSH_FUNC); + emit_int(p, p->cur_idx - 1 /* OP_PUSH_FUNC */ - prologue); + if(name_provided) { + emit_op(p, TOK_ASSIGN); + } + + return res; +} + +static mjs_err_t parse_object_literal(struct pstate* p) { + mjs_err_t res = MJS_OK; + EXPECT(p, TOK_OPEN_CURLY); + emit_byte(p, OP_PUSH_OBJ); + while(p->tok.tok != TOK_CLOSE_CURLY) { + if(p->tok.tok != TOK_IDENT && p->tok.tok != TOK_STR) SYNTAX_ERROR(p); + emit_byte(p, OP_DUP); + emit_byte(p, OP_PUSH_STR); + emit_str(p, p->tok.ptr, p->tok.len); + emit_byte(p, OP_SWAP); + pnext1(p); + EXPECT(p, TOK_COLON); + if((res = parse_expr(p)) != MJS_OK) return res; + emit_op(p, TOK_ASSIGN); + emit_byte(p, OP_DROP); + if(p->tok.tok == TOK_COMMA) { + pnext1(p); + } else if(p->tok.tok != TOK_CLOSE_CURLY) { + SYNTAX_ERROR(p); + } + } + return res; +} + +static mjs_err_t parse_array_literal(struct pstate* p) { + mjs_err_t res = MJS_OK; + EXPECT(p, TOK_OPEN_BRACKET); + emit_byte(p, OP_PUSH_ARRAY); + while(p->tok.tok != TOK_CLOSE_BRACKET) { + emit_byte(p, OP_DUP); + if((res = parse_expr(p)) != MJS_OK) return res; + emit_byte(p, OP_APPEND); + if(p->tok.tok == TOK_COMMA) pnext1(p); + } + return res; +} + +static enum mjs_err parse_literal(struct pstate* p, const struct tok* t) { + struct mbuf* bcode_gen = &p->mjs->bcode_gen; + enum mjs_err res = MJS_OK; + int tok = t->tok; + LOG(LL_VERBOSE_DEBUG, ("[%.*s] %p", p->tok.len, p->tok.ptr, (void*)&t)); + switch(t->tok) { + case TOK_KEYWORD_FALSE: + emit_byte(p, OP_PUSH_FALSE); + break; + case TOK_KEYWORD_TRUE: + emit_byte(p, OP_PUSH_TRUE); + break; + case TOK_KEYWORD_UNDEFINED: + emit_byte(p, OP_PUSH_UNDEF); + break; + case TOK_KEYWORD_NULL: + emit_byte(p, OP_PUSH_NULL); + break; + case TOK_IDENT: { + int prev_tok = p->prev_tok; + int next_tok = ptest(p); + emit_byte(p, OP_PUSH_STR); + emit_str(p, t->ptr, t->len); + emit_byte(p, (uint8_t)(prev_tok == TOK_DOT ? OP_SWAP : OP_FIND_SCOPE)); + if(!findtok(s_assign_ops, next_tok) && !findtok(s_postfix_ops, next_tok) && + /* TODO(dfrank): fix: it doesn't work for prefix ops */ + !findtok(s_postfix_ops, prev_tok)) { + emit_byte(p, OP_GET); + } + break; + } + case TOK_NUM: { + double iv, d = strtod(t->ptr, NULL); + unsigned long uv = strtoul(t->ptr + 2, NULL, 16); + if(t->ptr[0] == '0' && t->ptr[1] == 'x') d = uv; + if(modf(d, &iv) == 0) { + emit_byte(p, OP_PUSH_INT); + emit_int(p, (int64_t)d); + } else { + emit_byte(p, OP_PUSH_DBL); + emit_str(p, t->ptr, t->len); + } + break; + } + case TOK_STR: { + size_t oldlen; + emit_byte(p, OP_PUSH_STR); + oldlen = bcode_gen->len; + embed_string(bcode_gen, p->cur_idx, t->ptr, t->len, EMBSTR_UNESCAPE); + p->cur_idx += bcode_gen->len - oldlen; + } break; + case TOK_OPEN_BRACKET: + res = parse_array_literal(p); + break; + case TOK_OPEN_CURLY: + res = parse_object_literal(p); + break; + case TOK_OPEN_PAREN: + pnext1(p); + res = parse_expr(p); + if(p->tok.tok != TOK_CLOSE_PAREN) SYNTAX_ERROR(p); + break; + case TOK_KEYWORD_FUNCTION: + res = parse_function(p); + break; + case TOK_KEYWORD_THIS: + emit_byte(p, OP_PUSH_THIS); + break; + default: + SYNTAX_ERROR(p); + } + if(tok != TOK_KEYWORD_FUNCTION) pnext1(p); + return res; +} + +static mjs_err_t parse_call_dot_mem(struct pstate* p, int prev_op) { + int ops[] = {TOK_DOT, TOK_OPEN_PAREN, TOK_OPEN_BRACKET, TOK_EOF}; + mjs_err_t res = MJS_OK; + if((res = parse_literal(p, &p->tok)) != MJS_OK) return res; + while(findtok(ops, p->tok.tok) != TOK_EOF) { + if(p->tok.tok == TOK_OPEN_BRACKET) { + int prev_tok = p->prev_tok; + EXPECT(p, TOK_OPEN_BRACKET); + if((res = parse_expr(p)) != MJS_OK) return res; + emit_byte(p, OP_SWAP); + EXPECT(p, TOK_CLOSE_BRACKET); + if(!findtok(s_assign_ops, p->tok.tok) && !findtok(s_postfix_ops, p->tok.tok) && + /* TODO(dfrank): fix: it doesn't work for prefix ops */ + !findtok(s_postfix_ops, prev_tok)) { + emit_byte(p, OP_GET); + } + } else if(p->tok.tok == TOK_OPEN_PAREN) { + EXPECT(p, TOK_OPEN_PAREN); + emit_byte(p, OP_ARGS); + while(p->tok.tok != TOK_CLOSE_PAREN) { + if((res = parse_expr(p)) != MJS_OK) return res; + if(p->tok.tok == TOK_COMMA) pnext1(p); + } + emit_byte(p, OP_CALL); + EXPECT(p, TOK_CLOSE_PAREN); + } else if(p->tok.tok == TOK_DOT) { + EXPECT(p, TOK_DOT); + if((res = parse_call_dot_mem(p, TOK_DOT)) != MJS_OK) return res; + } + } + (void)prev_op; + return res; +} + +static mjs_err_t parse_postfix(struct pstate* p, int prev_op) { + mjs_err_t res = MJS_OK; + if((res = parse_call_dot_mem(p, prev_op)) != MJS_OK) return res; + if(p->tok.tok == TOK_PLUS_PLUS || p->tok.tok == TOK_MINUS_MINUS) { + int op = p->tok.tok == TOK_PLUS_PLUS ? TOK_POSTFIX_PLUS : TOK_POSTFIX_MINUS; + emit_op(p, op); + pnext1(p); + } + return res; +} + +static mjs_err_t parse_unary(struct pstate* p, int prev_op) { + mjs_err_t res = MJS_OK; + int op = TOK_EOF; + if(findtok(s_unary_ops, p->tok.tok) != TOK_EOF) { + op = p->tok.tok; + pnext1(p); + } + if(findtok(s_unary_ops, p->tok.tok) != TOK_EOF) { + res = parse_unary(p, prev_op); + } else { + res = parse_postfix(p, prev_op); + } + if(res != MJS_OK) return res; + if(op != TOK_EOF) { + if(op == TOK_MINUS) op = TOK_UNARY_MINUS; + if(op == TOK_PLUS) op = TOK_UNARY_PLUS; + emit_op(p, op); + } + return res; +} + +static mjs_err_t parse_mul_div_rem(struct pstate* p, int prev_op) { + int ops[] = {TOK_MUL, TOK_DIV, TOK_REM, TOK_EOF}; + PARSE_LTR_BINOP(p, parse_unary, parse_mul_div_rem, ops, prev_op); +} + +static mjs_err_t parse_plus_minus(struct pstate* p, int prev_op) { + int ops[] = {TOK_PLUS, TOK_MINUS, TOK_EOF}; + PARSE_LTR_BINOP(p, parse_mul_div_rem, parse_plus_minus, ops, prev_op); +} + +static mjs_err_t parse_shifts(struct pstate* p, int prev_op) { + int ops[] = {TOK_LSHIFT, TOK_RSHIFT, TOK_URSHIFT, TOK_EOF}; + PARSE_LTR_BINOP(p, parse_plus_minus, parse_shifts, ops, prev_op); +} + +static mjs_err_t parse_comparison(struct pstate* p, int prev_op) { + PARSE_LTR_BINOP(p, parse_shifts, parse_comparison, s_comparison_ops, prev_op); +} + +static mjs_err_t parse_equality(struct pstate* p, int prev_op) { + PARSE_LTR_BINOP(p, parse_comparison, parse_equality, s_equality_ops, prev_op); +} + +static mjs_err_t parse_bitwise_and(struct pstate* p, int prev_op) { + int ops[] = {TOK_AND, TOK_EOF}; + PARSE_LTR_BINOP(p, parse_equality, parse_bitwise_and, ops, prev_op); +} + +static mjs_err_t parse_bitwise_xor(struct pstate* p, int prev_op) { + int ops[] = {TOK_XOR, TOK_EOF}; + PARSE_LTR_BINOP(p, parse_bitwise_and, parse_bitwise_xor, ops, prev_op); +} + +static mjs_err_t parse_bitwise_or(struct pstate* p, int prev_op) { + int ops[] = {TOK_OR, TOK_EOF}; + PARSE_LTR_BINOP(p, parse_bitwise_xor, parse_bitwise_or, ops, prev_op); +} + +static mjs_err_t parse_logical_and(struct pstate* p, int prev_op) { + int ops[] = {TOK_LOGICAL_AND, TOK_EOF}; + PARSE_LTR_BINOP(p, parse_bitwise_or, parse_logical_and, ops, prev_op); +} + +static mjs_err_t parse_logical_or(struct pstate* p, int prev_op) { + int ops[] = {TOK_LOGICAL_OR, TOK_EOF}; + PARSE_LTR_BINOP(p, parse_logical_and, parse_logical_or, ops, prev_op); +} + +static mjs_err_t parse_ternary(struct pstate* p, int prev_op) { + mjs_err_t res = MJS_OK; + if((res = parse_logical_or(p, TOK_EOF)) != MJS_OK) return res; + if(prev_op != TOK_EOF) emit_op(p, prev_op); + + if(p->tok.tok == TOK_QUESTION) { + size_t off_if, off_endif, off_else; + EXPECT(p, TOK_QUESTION); + + emit_byte(p, OP_JMP_FALSE); + off_if = p->cur_idx; + emit_init_offset(p); + + if((res = parse_ternary(p, TOK_EOF)) != MJS_OK) return res; + + emit_byte(p, OP_JMP); + off_else = p->cur_idx; + emit_init_offset(p); + off_endif = p->cur_idx; + + emit_byte(p, OP_DROP); + + EXPECT(p, TOK_COLON); + if((res = parse_ternary(p, TOK_EOF)) != MJS_OK) return res; + + /* + * NOTE: if inserting offset causes the code to move, off_endif needs to be + * adjusted + */ + off_endif += mjs_bcode_insert_offset( + p, p->mjs, off_else, p->cur_idx - off_else - MJS_INIT_OFFSET_SIZE); + + mjs_bcode_insert_offset(p, p->mjs, off_if, off_endif - off_if - MJS_INIT_OFFSET_SIZE); + } + + return res; +} + +static mjs_err_t parse_assignment(struct pstate* p, int prev_op) { + PARSE_RTL_BINOP(p, parse_ternary, parse_assignment, s_assign_ops, prev_op); +} + +static mjs_err_t parse_expr(struct pstate* p) { + return parse_assignment(p, TOK_EOF); +} + +static mjs_err_t parse_let(struct pstate* p) { + mjs_err_t res = MJS_OK; + LOG(LL_VERBOSE_DEBUG, ("[%.*s]", 10, p->tok.ptr)); + EXPECT(p, TOK_KEYWORD_LET); + for(;;) { + struct tok tmp = p->tok; + EXPECT(p, TOK_IDENT); + + emit_byte(p, OP_PUSH_STR); + emit_str(p, tmp.ptr, tmp.len); + emit_byte(p, OP_PUSH_SCOPE); + emit_byte(p, OP_CREATE); + + if(p->tok.tok == TOK_ASSIGN) { + pnext1(p); + emit_byte(p, OP_PUSH_STR); + emit_str(p, tmp.ptr, tmp.len); + emit_byte(p, OP_FIND_SCOPE); + if((res = parse_expr(p)) != MJS_OK) return res; + emit_op(p, TOK_ASSIGN); + } else { + emit_byte(p, OP_PUSH_UNDEF); + } + if(p->tok.tok == TOK_COMMA) { + emit_byte(p, OP_DROP); + pnext1(p); + } + if(p->tok.tok == TOK_SEMICOLON || p->tok.tok == TOK_EOF) break; + } + return res; +} + +static mjs_err_t parse_block_or_stmt(struct pstate* p, int cs) { + if(ptest(p) == TOK_OPEN_CURLY) { + return parse_block(p, cs); + } else { + return parse_statement(p); + } +} + +static mjs_err_t parse_for_in(struct pstate* p) { + mjs_err_t res = MJS_OK; + size_t off_b, off_check_end; + + /* new scope should be pushed before OP_LOOP instruction */ + emit_byte(p, OP_NEW_SCOPE); + + /* Put iterator variable name to the stack */ + if(p->tok.tok == TOK_KEYWORD_LET) { + EXPECT(p, TOK_KEYWORD_LET); + emit_byte(p, OP_PUSH_STR); + emit_str(p, p->tok.ptr, p->tok.len); + emit_byte(p, OP_PUSH_SCOPE); + emit_byte(p, OP_CREATE); + } + emit_byte(p, OP_PUSH_STR); + emit_str(p, p->tok.ptr, p->tok.len); + + /* Put object to the stack */ + EXPECT(p, TOK_IDENT); + EXPECT(p, TOK_KEYWORD_IN); + parse_expr(p); + EXPECT(p, TOK_CLOSE_PAREN); + + emit_byte(p, OP_PUSH_UNDEF); /* Push iterator */ + + /* Before parsing condition statement, push break/continue offsets */ + emit_byte(p, OP_LOOP); + off_b = p->cur_idx; + emit_init_offset(p); + emit_byte(p, 0); /* Point OP_CONTINUE to the next instruction */ + + emit_byte(p, OP_FOR_IN_NEXT); + emit_byte(p, OP_DUP); + emit_byte(p, OP_JMP_FALSE); + off_check_end = p->cur_idx; + emit_init_offset(p); + + // Parse loop body + if(p->tok.tok == TOK_OPEN_CURLY) { + if((res = parse_statement_list(p, TOK_CLOSE_CURLY)) != MJS_OK) return res; + pnext1(p); + } else { + if((res = parse_statement(p)) != MJS_OK) return res; + } + emit_byte(p, OP_DROP); + emit_byte(p, OP_CONTINUE); + + /* jump cond -> break */ + mjs_bcode_insert_offset( + p, p->mjs, off_check_end, p->cur_idx - off_check_end - MJS_INIT_OFFSET_SIZE); + + /* NOTE: jump C -> cond link is already established, it's constant: zero */ + + emit_byte(p, OP_BREAK); + + /* jump B -> cond */ + mjs_bcode_insert_offset(p, p->mjs, off_b, p->cur_idx - off_b - MJS_INIT_OFFSET_SIZE); + + emit_byte(p, OP_DROP); + emit_byte(p, OP_DROP); + emit_byte(p, OP_DROP); + emit_byte(p, OP_DEL_SCOPE); + + return res; +} + +static int check_for_in(struct pstate* p) { + struct pstate saved = *p; + int forin = 0; + if(p->tok.tok == TOK_KEYWORD_LET) pnext1(p); + if(p->tok.tok == TOK_IDENT) { + pnext1(p); + if(p->tok.tok == TOK_KEYWORD_IN) forin = 1; + } + *p = saved; + return forin; +} + +static mjs_err_t parse_for(struct pstate* p) { + mjs_err_t res = MJS_OK; + size_t off_b, off_c, off_init_end; + size_t off_incr_begin, off_cond_begin, off_cond_end; + int buf_cur_idx; + + LOG(LL_VERBOSE_DEBUG, ("[%.*s]", 10, p->tok.ptr)); + EXPECT(p, TOK_KEYWORD_FOR); + EXPECT(p, TOK_OPEN_PAREN); + + /* Look forward - is it for..in ? */ + if(check_for_in(p)) return parse_for_in(p); + + /* + * BC is a break+continue offsets (a part of OP_LOOP opcode) + * + * BC init incr cond body break del_scope + * || | ^ ^ | ^ ^ + * || +--|-----+ | | | + * |+-------+ +--------+ | + * +---------------------------------+ + * + * The order to setup links: + * + * cond -> break + * init -> cond + * C -> incr + * B -> del_scope + */ + + /* new scope should be pushed before OP_LOOP instruction */ + emit_byte(p, OP_NEW_SCOPE); + + /* Before parsing condition statement, push break/continue offsets */ + emit_byte(p, OP_LOOP); + off_b = p->cur_idx; + emit_init_offset(p); + off_c = p->cur_idx; + emit_init_offset(p); + + /* Parse init statement */ + if(p->tok.tok == TOK_KEYWORD_LET) { + if((res = parse_let(p)) != MJS_OK) return res; + } else { + if((res = parse_expr(p)) != MJS_OK) return res; + } + EXPECT(p, TOK_SEMICOLON); + emit_byte(p, OP_DROP); + + emit_byte(p, OP_JMP); + off_init_end = p->cur_idx; + emit_init_offset(p); + + off_incr_begin = p->cur_idx; + off_cond_begin = p->cur_idx; + + /* Parse cond statement */ + if((res = parse_expr(p)) != MJS_OK) return res; + EXPECT(p, TOK_SEMICOLON); + + /* Parse incr statement */ + /* Incr statement should be placed before cond, so, adjust cur_idx */ + buf_cur_idx = p->cur_idx; + p->cur_idx = off_incr_begin; + + if((res = parse_expr(p)) != MJS_OK) return res; + EXPECT(p, TOK_CLOSE_PAREN); + emit_byte(p, OP_DROP); + + /* + * Now incr is inserted before cond, so we adjust cur_idx back, and set + * off_cond_begin to the correct value + */ + { + int incr_size = p->cur_idx - off_incr_begin; + off_cond_begin += incr_size; + p->cur_idx = buf_cur_idx + incr_size; + } + + /* p->cur_idx is now at the end of "cond" */ + /* Exit the loop if false */ + emit_byte(p, OP_JMP_FALSE); + off_cond_end = p->cur_idx; + emit_init_offset(p); + + /* Parse loop body */ + if(p->tok.tok == TOK_OPEN_CURLY) { + if((res = parse_statement_list(p, TOK_CLOSE_CURLY)) != MJS_OK) return res; + pnext1(p); + } else { + if((res = parse_statement(p)) != MJS_OK) return res; + } + emit_byte(p, OP_DROP); + emit_byte(p, OP_CONTINUE); + + /* p->cur_idx is at the "break" item now */ + + /* jump cond -> break */ + mjs_bcode_insert_offset( + p, p->mjs, off_cond_end, p->cur_idx - off_cond_end - MJS_INIT_OFFSET_SIZE); + + /* jump init -> cond (and adjust off_incr_begin which may move) */ + off_incr_begin += mjs_bcode_insert_offset( + p, p->mjs, off_init_end, off_cond_begin - off_init_end - MJS_INIT_OFFSET_SIZE); + + /* jump C -> incr */ + mjs_bcode_insert_offset(p, p->mjs, off_c, off_incr_begin - off_c - MJS_INIT_OFFSET_SIZE); + + emit_byte(p, OP_BREAK); + + /* jump B -> del_scope */ + mjs_bcode_insert_offset(p, p->mjs, off_b, p->cur_idx - off_b - MJS_INIT_OFFSET_SIZE); + + emit_byte(p, OP_DEL_SCOPE); + + return res; +} + +static mjs_err_t parse_while(struct pstate* p) { + size_t off_cond_end, off_b; + mjs_err_t res = MJS_OK; + + EXPECT(p, TOK_KEYWORD_WHILE); + EXPECT(p, TOK_OPEN_PAREN); + + /* new scope should be pushed before OP_LOOP instruction */ + emit_byte(p, OP_NEW_SCOPE); + + /* + * BC is a break+continue offsets (a part of OP_LOOP opcode) + * + * BC cond body break del_scope + * || ^ | ^ ^ + * || | | | | + * |+-+ +------+ | + * +------------------+ + * + * The order to setup links: + * + * cond -> break + * C -> cond + * B -> del_scope + */ + + emit_byte(p, OP_LOOP); + off_b = p->cur_idx; + emit_init_offset(p); + emit_byte(p, 0); /* Point OP_CONTINUE to the next instruction */ + + // parse condition statement + if((res = parse_expr(p)) != MJS_OK) return res; + EXPECT(p, TOK_CLOSE_PAREN); + + // Exit the loop if false + emit_byte(p, OP_JMP_FALSE); + off_cond_end = p->cur_idx; + emit_init_offset(p); + + // Parse loop body + if(p->tok.tok == TOK_OPEN_CURLY) { + if((res = parse_statement_list(p, TOK_CLOSE_CURLY)) != MJS_OK) return res; + pnext1(p); + } else { + if((res = parse_statement(p)) != MJS_OK) return res; + } + emit_byte(p, OP_DROP); + emit_byte(p, OP_CONTINUE); + + /* jump cond -> break */ + mjs_bcode_insert_offset( + p, p->mjs, off_cond_end, p->cur_idx - off_cond_end - MJS_INIT_OFFSET_SIZE); + + /* NOTE: jump C -> cond link is already established, it's constant: zero */ + + emit_byte(p, OP_BREAK); + + /* jump B -> cond */ + mjs_bcode_insert_offset(p, p->mjs, off_b, p->cur_idx - off_b - MJS_INIT_OFFSET_SIZE); + + emit_byte(p, OP_DEL_SCOPE); + return res; +} + +static mjs_err_t parse_if(struct pstate* p) { + size_t off_if, off_endif; + mjs_err_t res = MJS_OK; + LOG(LL_VERBOSE_DEBUG, ("[%.*s]", 10, p->tok.ptr)); + EXPECT(p, TOK_KEYWORD_IF); + EXPECT(p, TOK_OPEN_PAREN); + if((res = parse_expr(p)) != MJS_OK) return res; + + emit_byte(p, OP_JMP_FALSE); + off_if = p->cur_idx; + emit_init_offset(p); + + EXPECT(p, TOK_CLOSE_PAREN); + if((res = parse_block_or_stmt(p, 1)) != MJS_OK) return res; + + if(p->tok.tok == TOK_KEYWORD_ELSE) { + /* + * Else clause is present, so, if the condition is not true, the jump + * target (off_endif) should be not the current offset, but the offset + * after jump-over-else opcode + */ + size_t off_else, off_endelse; + pnext1(p); + emit_byte(p, OP_JMP); + off_else = p->cur_idx; + emit_init_offset(p); + off_endif = p->cur_idx; + + emit_byte(p, OP_DROP); + if((res = parse_block_or_stmt(p, 1)) != MJS_OK) return res; + off_endelse = p->cur_idx; + + /* + * NOTE: if inserting offset causes the code to move, off_endif needs to be + * adjusted + */ + off_endif += mjs_bcode_insert_offset( + p, p->mjs, off_else, off_endelse - off_else - MJS_INIT_OFFSET_SIZE); + } else { + /* Else clause is not present, so, current offset is a jump target + * (off_endif) */ + off_endif = p->cur_idx; + } + + mjs_bcode_insert_offset(p, p->mjs, off_if, off_endif - off_if - MJS_INIT_OFFSET_SIZE); + + return res; +} + +static void pstate_revert(struct pstate* p, struct pstate* old, int old_bcode_gen_len) { + p->pos = old->pos; + p->line_no = old->line_no; + p->last_emitted_line_no = old->last_emitted_line_no; + p->offset_lineno_map.len = old->offset_lineno_map.len; + p->prev_tok = old->prev_tok; + p->tok = old->tok; + p->mjs->bcode_gen.len = old_bcode_gen_len; + p->cur_idx = old->cur_idx; + p->depth = old->depth; +} + +static mjs_err_t parse_return(struct pstate* p) { + int old_bcode_gen_len; + struct pstate p_saved; + EXPECT(p, TOK_KEYWORD_RETURN); + p_saved = *p; + old_bcode_gen_len = p->mjs->bcode_gen.len; + if(parse_expr(p) != MJS_OK) { + /* + * Failed to parse an expression to return, so return the parser to the + * prior state and push undefined. + */ + pstate_revert(p, &p_saved, old_bcode_gen_len); + emit_byte(p, OP_PUSH_UNDEF); + } + emit_byte(p, OP_SETRETVAL); + emit_byte(p, OP_RETURN); + return MJS_OK; +} + +static mjs_err_t parse_statement(struct pstate* p) { + LOG(LL_VERBOSE_DEBUG, ("[%.*s]", 10, p->tok.ptr)); + switch(p->tok.tok) { + case TOK_SEMICOLON: + emit_byte(p, OP_PUSH_UNDEF); + pnext1(p); + return MJS_OK; + case TOK_KEYWORD_LET: + return parse_let(p); + case TOK_OPEN_CURLY: + return parse_block(p, 1); + case TOK_KEYWORD_RETURN: + return parse_return(p); + case TOK_KEYWORD_FOR: + return parse_for(p); + case TOK_KEYWORD_WHILE: + return parse_while(p); + case TOK_KEYWORD_BREAK: + emit_byte(p, OP_PUSH_UNDEF); + emit_byte(p, OP_BREAK); + pnext1(p); + return MJS_OK; + case TOK_KEYWORD_CONTINUE: + emit_byte(p, OP_CONTINUE); + pnext1(p); + return MJS_OK; + case TOK_KEYWORD_IF: + return parse_if(p); + case TOK_KEYWORD_CASE: + case TOK_KEYWORD_CATCH: + case TOK_KEYWORD_DELETE: + case TOK_KEYWORD_DO: + case TOK_KEYWORD_INSTANCEOF: + case TOK_KEYWORD_NEW: + case TOK_KEYWORD_SWITCH: + case TOK_KEYWORD_THROW: + case TOK_KEYWORD_TRY: + case TOK_KEYWORD_VAR: + case TOK_KEYWORD_VOID: + case TOK_KEYWORD_WITH: + mjs_set_errorf( + p->mjs, MJS_SYNTAX_ERROR, "[%.*s] is not implemented", p->tok.len, p->tok.ptr); + return MJS_SYNTAX_ERROR; + default: { + mjs_err_t res = MJS_OK; + for(;;) { + if((res = parse_expr(p)) != MJS_OK) return res; + if(p->tok.tok != TOK_COMMA) break; + emit_byte(p, OP_DROP); + pnext1(p); + } + return res; + } + } +} + +MJS_PRIVATE mjs_err_t mjs_parse(const char* path, const char* buf, struct mjs* mjs) { + mjs_err_t res = MJS_OK; + struct pstate p; + size_t start_idx, llen; + int map_len; + mjs_header_item_t bcode_offset, map_offset, total_size; + + pinit(path, buf, &p); + p.mjs = mjs; + p.cur_idx = p.mjs->bcode_gen.len; + emit_byte(&p, OP_BCODE_HEADER); + + /* + * TODO(dfrank): don't access mjs->bcode_gen directly, use emit_... API which + * takes care of p->cur_idx + */ + + /* Remember starting bcode position, and reserve the room for bcode header */ + start_idx = p.mjs->bcode_gen.len; + mbuf_append(&p.mjs->bcode_gen, NULL, sizeof(mjs_header_item_t) * MJS_HDR_ITEMS_CNT); + + /* Append NULL-terminated filename */ + mbuf_append(&p.mjs->bcode_gen, path, strlen(path) + 1 /* null-terminate */); + + bcode_offset = p.mjs->bcode_gen.len - start_idx; + memcpy( + p.mjs->bcode_gen.buf + start_idx + sizeof(mjs_header_item_t) * MJS_HDR_ITEM_BCODE_OFFSET, + &bcode_offset, + sizeof(mjs_header_item_t)); + + p.start_bcode_idx = p.mjs->bcode_gen.len; + p.cur_idx = p.mjs->bcode_gen.len; + + res = parse_statement_list(&p, TOK_EOF); + emit_byte(&p, OP_EXIT); + + /* remember map offset */ + map_offset = p.mjs->bcode_gen.len - start_idx; + memcpy( + p.mjs->bcode_gen.buf + start_idx + sizeof(mjs_header_item_t) * MJS_HDR_ITEM_MAP_OFFSET, + &map_offset, + sizeof(mjs_header_item_t)); + + /* put map length varint */ + map_len = p.offset_lineno_map.len; + llen = cs_varint_llen(map_len); + mbuf_resize(&p.mjs->bcode_gen, p.mjs->bcode_gen.size + llen); + cs_varint_encode(map_len, (uint8_t*)p.mjs->bcode_gen.buf + p.mjs->bcode_gen.len, llen); + p.mjs->bcode_gen.len += llen; + + /* put the map itself */ + mbuf_append(&p.mjs->bcode_gen, p.offset_lineno_map.buf, p.offset_lineno_map.len); + + total_size = p.mjs->bcode_gen.len - start_idx; + memcpy( + p.mjs->bcode_gen.buf + start_idx + sizeof(mjs_header_item_t) * MJS_HDR_ITEM_TOTAL_SIZE, + &total_size, + sizeof(mjs_header_item_t)); + + mbuf_free(&p.offset_lineno_map); + + /* + * If parsing was successful, commit the bcode; otherwise drop generated + * bcode + */ + if(res == MJS_OK) { + mjs_bcode_commit(mjs); + } else { + mbuf_free(&mjs->bcode_gen); + } + + return res; +} diff --git a/applications/system/elk_mjs/lib/mjs/mjs_parser.h b/applications/system/elk_mjs/lib/mjs/mjs_parser.h new file mode 100644 index 00000000000..8f47099faf9 --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/mjs_parser.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2016 Cesanta Software Limited + * All rights reserved + */ + +#ifndef MJS_PARSER_H +#define MJS_PARSER_H + +#include "mjs_internal.h" + +#if defined(__cplusplus) +extern "C" { +#endif /* __cplusplus */ + +MJS_PRIVATE mjs_err_t mjs_parse(const char* path, const char* buf, struct mjs*); + +#if defined(__cplusplus) +} +#endif /* __cplusplus */ + +#endif /* MJS_PARSER_H */ diff --git a/applications/system/elk_mjs/lib/mjs/mjs_primitive.c b/applications/system/elk_mjs/lib/mjs/mjs_primitive.c new file mode 100644 index 00000000000..b63a268e567 --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/mjs_primitive.c @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2017 Cesanta Software Limited + * All rights reserved + */ + +#include "mjs_core.h" +#include "mjs_internal.h" +#include "mjs_primitive.h" + +mjs_val_t mjs_mk_null(void) { + return MJS_NULL; +} + +int mjs_is_null(mjs_val_t v) { + return v == MJS_NULL; +} + +mjs_val_t mjs_mk_undefined(void) { + return MJS_UNDEFINED; +} + +int mjs_is_undefined(mjs_val_t v) { + return v == MJS_UNDEFINED; +} + +mjs_val_t mjs_mk_number(struct mjs* mjs, double v) { + mjs_val_t res; + (void)mjs; + /* not every NaN is a JS NaN */ + if(isnan(v)) { + res = MJS_TAG_NAN; + } else { + union { + double d; + mjs_val_t r; + } u; + u.d = v; + res = u.r; + } + return res; +} + +static double get_double(mjs_val_t v) { + union { + double d; + mjs_val_t v; + } u; + u.v = v; + /* Due to NaN packing, any non-numeric value is already a valid NaN value */ + return u.d; +} + +double mjs_get_double(struct mjs* mjs, mjs_val_t v) { + (void)mjs; + return get_double(v); +} + +int mjs_get_int(struct mjs* mjs, mjs_val_t v) { + (void)mjs; + /* + * NOTE(dfrank): without double cast, all numbers >= 0x80000000 are always + * converted to exactly 0x80000000. + */ + return (int)(unsigned int)get_double(v); +} + +int32_t mjs_get_int32(struct mjs* mjs, mjs_val_t v) { + (void)mjs; + return (int32_t)get_double(v); +} + +int mjs_is_number(mjs_val_t v) { + return v == MJS_TAG_NAN || !isnan(get_double(v)); +} + +mjs_val_t mjs_mk_boolean(struct mjs* mjs, int v) { + (void)mjs; + return (v ? 1 : 0) | MJS_TAG_BOOLEAN; +} + +int mjs_get_bool(struct mjs* mjs, mjs_val_t v) { + (void)mjs; + if(mjs_is_boolean(v)) { + return v & 1; + } else { + return 0; + } +} + +int mjs_is_boolean(mjs_val_t v) { + return (v & MJS_TAG_MASK) == MJS_TAG_BOOLEAN; +} + +#define MJS_IS_POINTER_LEGIT(n) \ + (((n)&MJS_TAG_MASK) == 0 || ((n)&MJS_TAG_MASK) == (~0 & MJS_TAG_MASK)) + +MJS_PRIVATE mjs_val_t mjs_pointer_to_value(struct mjs* mjs, void* p) { + uint64_t n = ((uint64_t)(uintptr_t)p); + + if(!MJS_IS_POINTER_LEGIT(n)) { + mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "invalid pointer value: %p", p); + } + return n & ~MJS_TAG_MASK; +} + +MJS_PRIVATE mjs_val_t mjs_legit_pointer_to_value(void* p) { + uint64_t n = ((uint64_t)(uintptr_t)p); + + assert(MJS_IS_POINTER_LEGIT(n)); + return n & ~MJS_TAG_MASK; +} + +MJS_PRIVATE void* get_ptr(mjs_val_t v) { + return (void*)(uintptr_t)(v & 0xFFFFFFFFFFFFUL); +} + +void* mjs_get_ptr(struct mjs* mjs, mjs_val_t v) { + (void)mjs; + if(!mjs_is_foreign(v)) { + return NULL; + } + return get_ptr(v); +} + +mjs_val_t mjs_mk_foreign(struct mjs* mjs, void* p) { + (void)mjs; + return mjs_pointer_to_value(mjs, p) | MJS_TAG_FOREIGN; +} + +mjs_val_t mjs_mk_foreign_func(struct mjs* mjs, mjs_func_ptr_t fn) { + union { + mjs_func_ptr_t fn; + void* p; + } u; + u.fn = fn; + (void)mjs; + return mjs_pointer_to_value(mjs, u.p) | MJS_TAG_FOREIGN; +} + +int mjs_is_foreign(mjs_val_t v) { + return (v & MJS_TAG_MASK) == MJS_TAG_FOREIGN; +} + +mjs_val_t mjs_mk_function(struct mjs* mjs, size_t off) { + (void)mjs; + return (mjs_val_t)off | MJS_TAG_FUNCTION; +} + +int mjs_is_function(mjs_val_t v) { + return (v & MJS_TAG_MASK) == MJS_TAG_FUNCTION; +} + +MJS_PRIVATE void mjs_op_isnan(struct mjs* mjs) { + mjs_val_t ret = MJS_UNDEFINED; + mjs_val_t val = mjs_arg(mjs, 0); + + ret = mjs_mk_boolean(mjs, val == MJS_TAG_NAN); + + mjs_return(mjs, ret); +} diff --git a/applications/system/elk_mjs/lib/mjs/mjs_primitive.h b/applications/system/elk_mjs/lib/mjs/mjs_primitive.h new file mode 100644 index 00000000000..6d6f9178515 --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/mjs_primitive.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2016 Cesanta Software Limited + * All rights reserved + */ + +#ifndef MJS_PRIMITIVE_H +#define MJS_PRIMITIVE_H + +#include "mjs_primitive_public.h" +#include "mjs_internal.h" + +#if defined(__cplusplus) +extern "C" { +#endif /* __cplusplus */ + +/* + * Convert a pointer to mjs_val_t. If pointer is not valid, mjs crashes. + */ +MJS_PRIVATE mjs_val_t mjs_legit_pointer_to_value(void* p); + +/* + * Convert a pointer to mjs_val_t. If pointer is not valid, error is set + * in the mjs context. + */ +MJS_PRIVATE mjs_val_t mjs_pointer_to_value(struct mjs* mjs, void* p); + +/* + * Extracts a pointer from the mjs_val_t value. + */ +MJS_PRIVATE void* get_ptr(mjs_val_t v); + +/* + * Implementation for JS isNaN() + */ +MJS_PRIVATE void mjs_op_isnan(struct mjs* mjs); + +#if defined(__cplusplus) +} +#endif /* __cplusplus */ + +#endif /* MJS_PRIMITIVE_H */ diff --git a/applications/system/elk_mjs/lib/mjs/mjs_primitive_public.h b/applications/system/elk_mjs/lib/mjs/mjs_primitive_public.h new file mode 100644 index 00000000000..ef07735e612 --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/mjs_primitive_public.h @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2016 Cesanta Software Limited + * All rights reserved + */ + +#ifndef MJS_PRIMITIVE_PUBLIC_H_ +#define MJS_PRIMITIVE_PUBLIC_H_ + +#include "mjs_core_public.h" + +#if defined(__cplusplus) +extern "C" { +#endif /* __cplusplus */ + +/* JavaScript `null` value */ +#define MJS_NULL MJS_TAG_NULL + +/* JavaScript `undefined` value */ +#define MJS_UNDEFINED MJS_TAG_UNDEFINED + +/* Function pointer type used in `mjs_mk_foreign_func`. */ +typedef void (*mjs_func_ptr_t)(void); + +/* + * Make `null` primitive value. + * + * NOTE: this function is deprecated and will be removed in future releases. + * Use `MJS_NULL` instead. + */ +mjs_val_t mjs_mk_null(void); + +/* Returns true if given value is a primitive `null` value */ +int mjs_is_null(mjs_val_t v); + +/* + * Make `undefined` primitive value. + * + * NOTE: this function is deprecated and will be removed in future releases. + * Use `MJS_UNDEFINED` instead. + */ +mjs_val_t mjs_mk_undefined(void); + +/* Returns true if given value is a primitive `undefined` value */ +int mjs_is_undefined(mjs_val_t v); + +/* Make numeric primitive value */ +mjs_val_t mjs_mk_number(struct mjs* mjs, double num); + +/* + * Returns number value stored in `mjs_val_t` as `double`. + * + * Returns NaN for non-numbers. + */ +double mjs_get_double(struct mjs* mjs, mjs_val_t v); + +/* + * Returns number value stored in `mjs_val_t` as `int`. If the number value is + * not an integer, the fraction part will be discarded. + * + * If the given value is a non-number, or NaN, the result is undefined. + */ +int mjs_get_int(struct mjs* mjs, mjs_val_t v); + +/* + * Like mjs_get_int but ensures that the returned type + * is a 32-bit signed integer. + */ +int32_t mjs_get_int32(struct mjs* mjs, mjs_val_t v); + +/* Returns true if given value is a primitive number value */ +int mjs_is_number(mjs_val_t v); + +/* + * Make JavaScript value that holds C/C++ `void *` pointer. + * + * A foreign value is completely opaque and JS code cannot do anything useful + * with it except holding it in properties and passing it around. + * It behaves like a sealed object with no properties. + * + * NOTE: + * Only valid pointers (as defined by each supported architecture) will fully + * preserved. In particular, all supported 64-bit architectures (x86_64, ARM-64) + * actually define a 48-bit virtual address space. + * Foreign values will be sign-extended as required, i.e creating a foreign + * value of something like `(void *) -1` will work as expected. This is + * important because in some 64-bit OSs (e.g. Solaris) the user stack grows + * downwards from the end of the address space. + * + * If you need to store exactly sizeof(void*) bytes of raw data where + * `sizeof(void*)` >= 8, please use byte arrays instead. + */ +mjs_val_t mjs_mk_foreign(struct mjs* mjs, void* ptr); + +/* + * Make JavaScript value that holds C/C++ function pointer, similarly to + * `mjs_mk_foreign`. + */ +mjs_val_t mjs_mk_foreign_func(struct mjs* mjs, mjs_func_ptr_t fn); + +/* + * Returns `void *` pointer stored in `mjs_val_t`. + * + * Returns NULL if the value is not a foreign pointer. + */ +void* mjs_get_ptr(struct mjs* mjs, mjs_val_t v); + +/* Returns true if given value holds `void *` pointer */ +int mjs_is_foreign(mjs_val_t v); + +mjs_val_t mjs_mk_boolean(struct mjs* mjs, int v); +int mjs_get_bool(struct mjs* mjs, mjs_val_t v); +int mjs_is_boolean(mjs_val_t v); + +mjs_val_t mjs_mk_function(struct mjs* mjs, size_t off); +int mjs_is_function(mjs_val_t v); + +#if defined(__cplusplus) +} +#endif /* __cplusplus */ + +#endif /* MJS_PRIMITIVE_PUBLIC_H_ */ diff --git a/applications/system/elk_mjs/lib/mjs/mjs_string.c b/applications/system/elk_mjs/lib/mjs/mjs_string.c new file mode 100644 index 00000000000..0d7b693d543 --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/mjs_string.c @@ -0,0 +1,602 @@ +/* + * Copyright (c) 2017 Cesanta Software Limited + * All rights reserved + */ + +#include "mjs_string.h" +#include "common/cs_varint.h" +#include "common/mg_str.h" +#include "mjs_conversion.h" +#include "mjs_core.h" +#include "mjs_internal.h" +#include "mjs_primitive.h" +#include "mjs_util.h" + +// No UTF +typedef unsigned short Rune; +static int chartorune(Rune* rune, const char* str) { + *rune = *(unsigned char*)str; + return 1; +} +static int runetochar(char* str, Rune* rune) { + str[0] = (char)*rune; + return 1; +} + +#ifndef MJS_STRING_BUF_RESERVE +#define MJS_STRING_BUF_RESERVE 100 +#endif + +MJS_PRIVATE size_t unescape(const char* s, size_t len, char* to); + +MJS_PRIVATE void embed_string( + struct mbuf* m, + size_t offset, + const char* p, + size_t len, + uint8_t /*enum embstr_flags*/ flags); + +/* TODO(lsm): NaN payload location depends on endianness, make crossplatform */ +#define GET_VAL_NAN_PAYLOAD(v) ((char*)&(v)) + +int mjs_is_string(mjs_val_t v) { + uint64_t t = v & MJS_TAG_MASK; + return t == MJS_TAG_STRING_I || t == MJS_TAG_STRING_F || t == MJS_TAG_STRING_O || + t == MJS_TAG_STRING_5 || t == MJS_TAG_STRING_D; +} + +mjs_val_t mjs_mk_string(struct mjs* mjs, const char* p, size_t len, int copy) { + struct mbuf* m; + mjs_val_t offset, tag = MJS_TAG_STRING_F; + if(len == 0) { + /* + * Zero length for foreign string has a special meaning (that the foreign + * string is not inlined into mjs_val_t), so when creating a zero-length + * string, we always assume it'll be owned. Since the length is zero, it + * doesn't matter anyway. + */ + copy = 1; + } + m = copy ? &mjs->owned_strings : &mjs->foreign_strings; + offset = m->len; + + if(len == ~((size_t)0)) len = strlen(p); + + if(copy) { + /* owned string */ + if(len <= 4) { + char* s = GET_VAL_NAN_PAYLOAD(offset) + 1; + offset = 0; + if(p != 0) { + memcpy(s, p, len); + } + s[-1] = len; + tag = MJS_TAG_STRING_I; + } else if(len == 5) { + char* s = GET_VAL_NAN_PAYLOAD(offset); + offset = 0; + if(p != 0) { + memcpy(s, p, len); + } + tag = MJS_TAG_STRING_5; + // } else if ((dict_index = v_find_string_in_dictionary(p, len)) >= 0) { + // offset = 0; + // GET_VAL_NAN_PAYLOAD(offset)[0] = dict_index; + // tag = MJS_TAG_STRING_D; + } else { + if(gc_strings_is_gc_needed(mjs)) { + mjs->need_gc = 1; + } + + /* + * Before embedding new string, check if the reallocation is needed. If + * so, perform the reallocation by calling `mbuf_resize` manually, since + * we need to preallocate some extra space (`MJS_STRING_BUF_RESERVE`) + */ + if((m->len + len) > m->size) { + char* prev_buf = m->buf; + mbuf_resize(m, m->len + len + MJS_STRING_BUF_RESERVE); + + /* + * There is a corner case: when the source pointer is located within + * the mbuf. In this case, we should adjust the pointer, because it + * might have just been reallocated. + */ + if(p >= prev_buf && p < (prev_buf + m->len)) { + p += (m->buf - prev_buf); + } + } + + embed_string(m, m->len, p, len, EMBSTR_ZERO_TERM); + tag = MJS_TAG_STRING_O; + } + } else { + /* foreign string */ + if(sizeof(void*) <= 4 && len <= (1 << 15)) { + /* small foreign strings can fit length and ptr in the mjs_val_t */ + offset = (uint64_t)len << 32 | (uint64_t)(uintptr_t)p; + } else { + /* bigger strings need indirection that uses ram */ + size_t pos = m->len; + size_t llen = cs_varint_llen(len); + + /* allocate space for len and ptr */ + mbuf_insert(m, pos, NULL, llen + sizeof(p)); + + cs_varint_encode(len, (uint8_t*)(m->buf + pos), llen); + memcpy(m->buf + pos + llen, &p, sizeof(p)); + } + tag = MJS_TAG_STRING_F; + } + + /* NOTE(lsm): don't use pointer_to_value, 32-bit ptrs will truncate */ + return (offset & ~MJS_TAG_MASK) | tag; +} + +/* Get a pointer to string and string length. */ +const char* mjs_get_string(struct mjs* mjs, mjs_val_t* v, size_t* sizep) { + uint64_t tag = v[0] & MJS_TAG_MASK; + const char* p = NULL; + size_t size = 0, llen; + + if(!mjs_is_string(*v)) { + goto clean; + } + + if(tag == MJS_TAG_STRING_I) { + p = GET_VAL_NAN_PAYLOAD(*v) + 1; + size = p[-1]; + } else if(tag == MJS_TAG_STRING_5) { + p = GET_VAL_NAN_PAYLOAD(*v); + size = 5; + // } else if (tag == MJS_TAG_STRING_D) { + // int index = ((unsigned char *) GET_VAL_NAN_PAYLOAD(*v))[0]; + // size = v_dictionary_strings[index].len; + // p = v_dictionary_strings[index].p; + } else if(tag == MJS_TAG_STRING_O) { + size_t offset = (size_t)gc_string_mjs_val_to_offset(*v); + char* s = mjs->owned_strings.buf + offset; + uint64_t v = 0; + if(offset < mjs->owned_strings.len && + cs_varint_decode((uint8_t*)s, mjs->owned_strings.len - offset, &v, &llen)) { + size = v; + p = s + llen; + } else { + goto clean; + } + } else if(tag == MJS_TAG_STRING_F) { + /* + * short foreign strings on <=32-bit machines can be encoded in a compact + * form: + * + * 7 6 5 4 3 2 1 0 + * 11111111|1111tttt|llllllll|llllllll|ssssssss|ssssssss|ssssssss|ssssssss + * + * Strings longer than 2^26 will be indireceted through the foreign_strings + * mbuf. + * + * We don't use a different tag to represent those two cases. Instead, all + * foreign strings represented with the help of the foreign_strings mbuf + * will have the upper 16-bits of the payload set to zero. This allows us to + * represent up to 477 million foreign strings longer than 64k. + */ + uint16_t len = (*v >> 32) & 0xFFFF; + if(sizeof(void*) <= 4 && len != 0) { + size = (size_t)len; + p = (const char*)(uintptr_t)*v; + } else { + size_t offset = (size_t)gc_string_mjs_val_to_offset(*v); + char* s = mjs->foreign_strings.buf + offset; + uint64_t v = 0; + if(offset < mjs->foreign_strings.len && + cs_varint_decode((uint8_t*)s, mjs->foreign_strings.len - offset, &v, &llen)) { + size = v; + memcpy((char**)&p, s + llen, sizeof(p)); + } else { + goto clean; + } + } + } else { + assert(0); + } + +clean: + if(sizep != NULL) { + *sizep = size; + } + return p; +} + +const char* mjs_get_cstring(struct mjs* mjs, mjs_val_t* value) { + size_t size; + const char* s = mjs_get_string(mjs, value, &size); + if(s == NULL) return NULL; + if(s[size] != 0 || strlen(s) != size) { + return NULL; + } + return s; +} + +int mjs_strcmp(struct mjs* mjs, mjs_val_t* a, const char* b, size_t len) { + size_t n; + const char* s; + if(len == (size_t)~0) len = strlen(b); + s = mjs_get_string(mjs, a, &n); + if(n != len) { + return n - len; + } + return strncmp(s, b, len); +} + +MJS_PRIVATE unsigned long cstr_to_ulong(const char* s, size_t len, int* ok) { + char* e; + unsigned long res = strtoul(s, &e, 10); + *ok = (e == s + len) && len != 0; + return res; +} + +MJS_PRIVATE mjs_err_t str_to_ulong(struct mjs* mjs, mjs_val_t v, int* ok, unsigned long* res) { + enum mjs_err ret = MJS_OK; + size_t len = 0; + const char* p = mjs_get_string(mjs, &v, &len); + *res = cstr_to_ulong(p, len, ok); + + return ret; +} + +MJS_PRIVATE int s_cmp(struct mjs* mjs, mjs_val_t a, mjs_val_t b) { + size_t a_len, b_len; + const char *a_ptr, *b_ptr; + + a_ptr = mjs_get_string(mjs, &a, &a_len); + b_ptr = mjs_get_string(mjs, &b, &b_len); + + if(a_len == b_len) { + return memcmp(a_ptr, b_ptr, a_len); + } + if(a_len > b_len) { + return 1; + } else if(a_len < b_len) { + return -1; + } else { + return 0; + } +} + +MJS_PRIVATE mjs_val_t s_concat(struct mjs* mjs, mjs_val_t a, mjs_val_t b) { + size_t a_len, b_len, res_len; + const char *a_ptr, *b_ptr, *res_ptr; + mjs_val_t res; + + /* Find out lengths of both srtings */ + a_ptr = mjs_get_string(mjs, &a, &a_len); + b_ptr = mjs_get_string(mjs, &b, &b_len); + + /* Create a placeholder string */ + res = mjs_mk_string(mjs, NULL, a_len + b_len, 1); + + /* mjs_mk_string() may have reallocated mbuf - revalidate pointers */ + a_ptr = mjs_get_string(mjs, &a, &a_len); + b_ptr = mjs_get_string(mjs, &b, &b_len); + + /* Copy strings into the placeholder */ + res_ptr = mjs_get_string(mjs, &res, &res_len); + memcpy((char*)res_ptr, a_ptr, a_len); + memcpy((char*)res_ptr + a_len, b_ptr, b_len); + + return res; +} + +MJS_PRIVATE void mjs_string_slice(struct mjs* mjs) { + int nargs = mjs_nargs(mjs); + mjs_val_t ret = mjs_mk_number(mjs, 0); + mjs_val_t beginSlice_v = MJS_UNDEFINED; + mjs_val_t endSlice_v = MJS_UNDEFINED; + int beginSlice = 0; + int endSlice = 0; + size_t size; + const char* s = NULL; + + /* get string from `this` */ + if(!mjs_check_arg(mjs, -1 /*this*/, "this", MJS_TYPE_STRING, NULL)) { + goto clean; + } + s = mjs_get_string(mjs, &mjs->vals.this_obj, &size); + + /* get idx from arg 0 */ + if(!mjs_check_arg(mjs, 0, "beginSlice", MJS_TYPE_NUMBER, &beginSlice_v)) { + goto clean; + } + beginSlice = mjs_normalize_idx(mjs_get_int(mjs, beginSlice_v), size); + + if(nargs >= 2) { + /* endSlice is given; use it */ + /* get idx from arg 0 */ + if(!mjs_check_arg(mjs, 1, "endSlice", MJS_TYPE_NUMBER, &endSlice_v)) { + goto clean; + } + endSlice = mjs_normalize_idx(mjs_get_int(mjs, endSlice_v), size); + } else { + /* endSlice is not given; assume the end of the string */ + endSlice = size; + } + + if(endSlice < beginSlice) { + endSlice = beginSlice; + } + + ret = mjs_mk_string(mjs, s + beginSlice, endSlice - beginSlice, 1); + +clean: + mjs_return(mjs, ret); +} + +MJS_PRIVATE void mjs_string_index_of(struct mjs* mjs) { + mjs_val_t ret = mjs_mk_number(mjs, -1); + mjs_val_t substr_v = MJS_UNDEFINED; + mjs_val_t idx_v = MJS_UNDEFINED; + int idx = 0; + const char *str = NULL, *substr = NULL; + size_t str_len = 0, substr_len = 0; + + if(!mjs_check_arg(mjs, -1 /* this */, "this", MJS_TYPE_STRING, NULL)) { + goto clean; + } + str = mjs_get_string(mjs, &mjs->vals.this_obj, &str_len); + + if(!mjs_check_arg(mjs, 0, "searchValue", MJS_TYPE_STRING, &substr_v)) { + goto clean; + } + substr = mjs_get_string(mjs, &substr_v, &substr_len); + if(mjs_nargs(mjs) > 1) { + if(!mjs_check_arg(mjs, 1, "fromIndex", MJS_TYPE_NUMBER, &idx_v)) { + goto clean; + } + idx = mjs_get_int(mjs, idx_v); + if(idx < 0) idx = 0; + if((size_t)idx > str_len) idx = str_len; + } + { + const char* substr_p; + struct mg_str mgstr, mgsubstr; + mgstr.p = str + idx; + mgstr.len = str_len - idx; + mgsubstr.p = substr; + mgsubstr.len = substr_len; + substr_p = mg_strstr(mgstr, mgsubstr); + if(substr_p != NULL) { + ret = mjs_mk_number(mjs, (int)(substr_p - str)); + } + } + +clean: + mjs_return(mjs, ret); +} + +MJS_PRIVATE void mjs_string_char_code_at(struct mjs* mjs) { + mjs_val_t ret = MJS_UNDEFINED; + mjs_val_t idx_v = MJS_UNDEFINED; + int idx = 0; + size_t size; + const char* s = NULL; + + /* get string from `this` */ + if(!mjs_check_arg(mjs, -1 /*this*/, "this", MJS_TYPE_STRING, NULL)) { + goto clean; + } + s = mjs_get_string(mjs, &mjs->vals.this_obj, &size); + + /* get idx from arg 0 */ + if(!mjs_check_arg(mjs, 0, "index", MJS_TYPE_NUMBER, &idx_v)) { + goto clean; + } + idx = mjs_normalize_idx(mjs_get_int(mjs, idx_v), size); + if(idx >= 0 && idx < (int)size) { + ret = mjs_mk_number(mjs, ((unsigned char*)s)[idx]); + } + +clean: + mjs_return(mjs, ret); +} + +MJS_PRIVATE void mjs_mkstr(struct mjs* mjs) { + int nargs = mjs_nargs(mjs); + mjs_val_t ret = MJS_UNDEFINED; + + char* ptr = NULL; + int offset = 0; + int len = 0; + int copy = 0; + + mjs_val_t ptr_v = MJS_UNDEFINED; + mjs_val_t offset_v = MJS_UNDEFINED; + mjs_val_t len_v = MJS_UNDEFINED; + mjs_val_t copy_v = MJS_UNDEFINED; + + if(nargs == 2) { + ptr_v = mjs_arg(mjs, 0); + len_v = mjs_arg(mjs, 1); + } else if(nargs == 3) { + ptr_v = mjs_arg(mjs, 0); + offset_v = mjs_arg(mjs, 1); + len_v = mjs_arg(mjs, 2); + } else if(nargs == 4) { + ptr_v = mjs_arg(mjs, 0); + offset_v = mjs_arg(mjs, 1); + len_v = mjs_arg(mjs, 2); + copy_v = mjs_arg(mjs, 3); + } else { + mjs_prepend_errorf( + mjs, + MJS_TYPE_ERROR, + "mkstr takes 2, 3 or 4 arguments: (ptr, len), (ptr, " + "offset, len) or (ptr, offset, len, copy)"); + goto clean; + } + + if(!mjs_is_foreign(ptr_v)) { + mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "ptr should be a foreign pointer"); + goto clean; + } + + if(offset_v != MJS_UNDEFINED && !mjs_is_number(offset_v)) { + mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "offset should be a number"); + goto clean; + } + + if(!mjs_is_number(len_v)) { + mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "len should be a number"); + goto clean; + } + + copy = mjs_is_truthy(mjs, copy_v); + + /* all arguments are fine */ + + ptr = (char*)mjs_get_ptr(mjs, ptr_v); + if(offset_v != MJS_UNDEFINED) { + offset = mjs_get_int(mjs, offset_v); + } + len = mjs_get_int(mjs, len_v); + + ret = mjs_mk_string(mjs, ptr + offset, len, copy); + +clean: + mjs_return(mjs, ret); +} + +enum unescape_error { + SLRE_INVALID_HEX_DIGIT, + SLRE_INVALID_ESC_CHAR, + SLRE_UNTERM_ESC_SEQ, +}; + +static int hex(int c) { + if(c >= '0' && c <= '9') return c - '0'; + if(c >= 'a' && c <= 'f') return c - 'a' + 10; + if(c >= 'A' && c <= 'F') return c - 'A' + 10; + return -SLRE_INVALID_HEX_DIGIT; +} + +static int nextesc(const char** p) { + const unsigned char* s = (unsigned char*)(*p)++; + switch(*s) { + case 0: + return -SLRE_UNTERM_ESC_SEQ; + case 'c': + ++*p; + return *s & 31; + case 'b': + return '\b'; + case 't': + return '\t'; + case 'n': + return '\n'; + case 'v': + return '\v'; + case 'f': + return '\f'; + case 'r': + return '\r'; + case '\\': + return '\\'; + case 'u': + if(isxdigit(s[1]) && isxdigit(s[2]) && isxdigit(s[3]) && isxdigit(s[4])) { + (*p) += 4; + return hex(s[1]) << 12 | hex(s[2]) << 8 | hex(s[3]) << 4 | hex(s[4]); + } + return -SLRE_INVALID_HEX_DIGIT; + case 'x': + if(isxdigit(s[1]) && isxdigit(s[2])) { + (*p) += 2; + return (hex(s[1]) << 4) | hex(s[2]); + } + return -SLRE_INVALID_HEX_DIGIT; + default: + return -SLRE_INVALID_ESC_CHAR; + } +} + +MJS_PRIVATE size_t unescape(const char* s, size_t len, char* to) { + const char* end = s + len; + size_t n = 0; + char tmp[4]; + Rune r; + + while(s < end) { + s += chartorune(&r, s); + if(r == '\\' && s < end) { + switch(*s) { + case '"': + s++, r = '"'; + break; + case '\'': + s++, r = '\''; + break; + case '\n': + s++, r = '\n'; + break; + default: { + const char* tmp_s = s; + int i = nextesc(&s); + switch(i) { + case -SLRE_INVALID_ESC_CHAR: + r = '\\'; + s = tmp_s; + n += runetochar(to == NULL ? tmp : to + n, &r); + s += chartorune(&r, s); + break; + case -SLRE_INVALID_HEX_DIGIT: + default: + r = i; + } + } + } + } + n += runetochar(to == NULL ? tmp : to + n, &r); + } + + return n; +} + +MJS_PRIVATE void embed_string( + struct mbuf* m, + size_t offset, + const char* p, + size_t len, + uint8_t /*enum embstr_flags*/ flags) { + char* old_base = m->buf; + uint8_t p_backed_by_mbuf = p >= old_base && p < old_base + m->len; + size_t n = (flags & EMBSTR_UNESCAPE) ? unescape(p, len, NULL) : len; + + /* Calculate how many bytes length takes */ + size_t k = cs_varint_llen(n); + + /* total length: varing length + string len + zero-term */ + size_t tot_len = k + n + !!(flags & EMBSTR_ZERO_TERM); + + /* Allocate buffer */ + mbuf_insert(m, offset, NULL, tot_len); + + /* Fixup p if it was relocated by mbuf_insert() above */ + if(p_backed_by_mbuf) { + p += m->buf - old_base; + } + + /* Write length */ + cs_varint_encode(n, (unsigned char*)m->buf + offset, k); + + /* Write string */ + if(p != 0) { + if(flags & EMBSTR_UNESCAPE) { + unescape(p, len, m->buf + offset + k); + } else { + memcpy(m->buf + offset + k, p, len); + } + } + + /* add NULL-terminator if needed */ + if(flags & EMBSTR_ZERO_TERM) { + m->buf[offset + tot_len - 1] = '\0'; + } +} diff --git a/applications/system/elk_mjs/lib/mjs/mjs_string.h b/applications/system/elk_mjs/lib/mjs/mjs_string.h new file mode 100644 index 00000000000..ba6869b6247 --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/mjs_string.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2016 Cesanta Software Limited + * All rights reserved + */ + +#ifndef MJS_STRING_H_ +#define MJS_STRING_H_ + +#include "mjs_internal.h" +#include "mjs_string_public.h" + +#if defined(__cplusplus) +extern "C" { +#endif /* __cplusplus */ + +/* + * Size of the extra space for strings mbuf that is needed to avoid frequent + * reallocations + */ +#define _MJS_STRING_BUF_RESERVE 100 + +MJS_PRIVATE unsigned long cstr_to_ulong(const char* s, size_t len, int* ok); +MJS_PRIVATE mjs_err_t str_to_ulong(struct mjs* mjs, mjs_val_t v, int* ok, unsigned long* res); +MJS_PRIVATE int s_cmp(struct mjs* mjs, mjs_val_t a, mjs_val_t b); +MJS_PRIVATE mjs_val_t s_concat(struct mjs* mjs, mjs_val_t a, mjs_val_t b); + +MJS_PRIVATE void embed_string( + struct mbuf* m, + size_t offset, + const char* p, + size_t len, + uint8_t /*enum embstr_flags*/ flags); + +MJS_PRIVATE void mjs_mkstr(struct mjs* mjs); + +MJS_PRIVATE void mjs_string_slice(struct mjs* mjs); +MJS_PRIVATE void mjs_string_index_of(struct mjs* mjs); +MJS_PRIVATE void mjs_string_char_code_at(struct mjs* mjs); + +#define EMBSTR_ZERO_TERM 1 +#define EMBSTR_UNESCAPE 2 + +#if defined(__cplusplus) +} +#endif /* __cplusplus */ + +#endif /* MJS_STRING_H_ */ diff --git a/applications/system/elk_mjs/lib/mjs/mjs_string_public.h b/applications/system/elk_mjs/lib/mjs/mjs_string_public.h new file mode 100644 index 00000000000..8d73aaf7f67 --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/mjs_string_public.h @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2016 Cesanta Software Limited + * All rights reserved + */ + +#ifndef MJS_STRING_PUBLIC_H_ +#define MJS_STRING_PUBLIC_H_ + +#include "mjs_core_public.h" + +#define MJS_STRING_LITERAL_MAX_LEN 128 + +#if defined(__cplusplus) +extern "C" { +#endif /* __cplusplus */ + +/* + * Creates a string primitive value. + * `str` must point to the utf8 string of length `len`. + * If `len` is ~0, `str` is assumed to be NUL-terminated and `strlen(str)` is + * used. + * + * If `copy` is non-zero, the string data is copied and owned by the GC. The + * caller can free the string data afterwards. Otherwise (`copy` is zero), the + * caller owns the string data, and is responsible for not freeing it while it + * is used. + */ +mjs_val_t mjs_mk_string(struct mjs* mjs, const char* str, size_t len, int copy); + +/* Returns true if given value is a primitive string value */ +int mjs_is_string(mjs_val_t v); + +/* + * Returns a pointer to the string stored in `mjs_val_t`. + * + * String length returned in `len`, which is allowed to be NULL. Returns NULL + * if the value is not a string. + * + * JS strings can contain embedded NUL chars and may or may not be NUL + * terminated. + * + * CAUTION: creating new JavaScript object, array, or string may kick in a + * garbage collector, which in turn may relocate string data and invalidate + * pointer returned by `mjs_get_string()`. + * + * Short JS strings are embedded inside the `mjs_val_t` value itself. This + * is why a pointer to a `mjs_val_t` is required. It also means that the string + * data will become invalid once that `mjs_val_t` value goes out of scope. + */ +const char* mjs_get_string(struct mjs* mjs, mjs_val_t* v, size_t* len); + +/* + * Returns a pointer to the string stored in `mjs_val_t`. + * + * Returns NULL if the value is not a string or if the string is not compatible + * with a C string. + * + * C compatible strings contain exactly one NUL char, in terminal position. + * + * All strings owned by the MJS engine (see `mjs_mk_string()`) are guaranteed to + * be NUL terminated. Out of these, those that don't include embedded NUL chars + * are guaranteed to be C compatible. + */ +const char* mjs_get_cstring(struct mjs* mjs, mjs_val_t* v); + +/* + * Returns the standard strcmp comparison code after comparing a JS string a + * with a possibly non null-terminated string b. NOTE: the strings are equal + * only if their length is equal, i.e. the len field doesn't imply strncmp + * behaviour. + */ +int mjs_strcmp(struct mjs* mjs, mjs_val_t* a, const char* b, size_t len); + +#if defined(__cplusplus) +} +#endif /* __cplusplus */ + +#endif /* MJS_STRING_PUBLIC_H_ */ diff --git a/applications/system/elk_mjs/lib/mjs/mjs_tok.c b/applications/system/elk_mjs/lib/mjs/mjs_tok.c new file mode 100644 index 00000000000..bdff5a86a23 --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/mjs_tok.c @@ -0,0 +1,247 @@ +/* + * Copyright (c) 2017 Cesanta Software Limited + * All rights reserved + */ + +#include +#include + +#include "common/cs_dbg.h" +#include "mjs_tok.h" + +MJS_PRIVATE void pinit(const char* file_name, const char* buf, struct pstate* p) { + memset(p, 0, sizeof(*p)); + p->line_no = 1; + p->last_emitted_line_no = 1; + p->file_name = file_name; + p->buf = p->pos = buf; + mbuf_init(&p->offset_lineno_map, 0); +} + +// We're not relying on the target libc ctype, as it may incorrectly +// handle negative arguments, e.g. isspace(-1). +static int mjs_is_space(int c) { + return c == ' ' || c == '\r' || c == '\n' || c == '\t' || c == '\f' || c == '\v'; +} + +MJS_PRIVATE int mjs_is_digit(int c) { + return c >= '0' && c <= '9'; +} + +static int mjs_is_alpha(int c) { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); +} + +MJS_PRIVATE int mjs_is_ident(int c) { + return c == '_' || c == '$' || mjs_is_alpha(c); +} + +// Try to parse a token that can take one or two chars. +static int longtok(struct pstate* p, const char* first_chars, const char* second_chars) { + if(strchr(first_chars, p->pos[0]) == NULL) return TOK_EOF; + if(p->pos[1] != '\0' && strchr(second_chars, p->pos[1]) != NULL) { + p->tok.len++; + p->pos++; + return p->pos[-1] << 8 | p->pos[0]; + } + return p->pos[0]; +} + +// Try to parse a token that takes exactly 3 chars. +static int longtok3(struct pstate* p, char a, char b, char c) { + if(p->pos[0] == a && p->pos[1] == b && p->pos[2] == c) { + p->tok.len += 2; + p->pos += 2; + return p->pos[-2] << 16 | p->pos[-1] << 8 | p->pos[0]; + } + return TOK_EOF; +} + +// Try to parse a token that takes exactly 4 chars. +static int longtok4(struct pstate* p, char a, char b, char c, char d) { + if(p->pos[0] == a && p->pos[1] == b && p->pos[2] == c && p->pos[3] == d) { + p->tok.len += 3; + p->pos += 3; + return p->pos[-3] << 24 | p->pos[-2] << 16 | p->pos[-1] << 8 | p->pos[0]; + } + return TOK_EOF; +} + +static int getnum(struct pstate* p) { + if(p->pos[0] == '0' && p->pos[1] == 'x') { + // MSVC6 strtod cannot parse 0x... numbers, thus this ugly workaround. + strtoul(p->pos + 2, (char**)&p->pos, 16); + } else { + strtod(p->pos, (char**)&p->pos); + } + p->tok.len = p->pos - p->tok.ptr; + p->pos--; + return TOK_NUM; +} + +static int is_reserved_word_token(const char* s, int len) { + const char* reserved[] = {"break", "case", "catch", "continue", "debugger", "default", + "delete", "do", "else", "false", "finally", "for", + "function", "if", "in", "instanceof", "new", "null", + "return", "switch", "this", "throw", "true", "try", + "typeof", "var", "void", "while", "with", "let", + "undefined", NULL}; + int i; + if(!mjs_is_alpha(s[0])) return 0; + for(i = 0; reserved[i] != NULL; i++) { + if(len == (int)strlen(reserved[i]) && strncmp(s, reserved[i], len) == 0) return i + 1; + } + return 0; +} + +static int getident(struct pstate* p) { + while(mjs_is_ident(p->pos[0]) || mjs_is_digit(p->pos[0])) p->pos++; + p->tok.len = p->pos - p->tok.ptr; + p->pos--; + return TOK_IDENT; +} + +static int getstr(struct pstate* p) { + int quote = *p->pos++; + p->tok.ptr++; + while(p->pos[0] != '\0' && p->pos[0] != quote) { + if(p->pos[0] == '\\' && p->pos[1] != '\0' && + (p->pos[1] == quote || strchr("bfnrtv\\", p->pos[1]) != NULL)) { + p->pos += 2; + } else { + p->pos++; + } + } + p->tok.len = p->pos - p->tok.ptr; + return TOK_STR; +} + +static void skip_spaces_and_comments(struct pstate* p) { + const char* pos; + do { + pos = p->pos; + while(mjs_is_space(p->pos[0])) { + if(p->pos[0] == '\n') p->line_no++; + p->pos++; + } + if(p->pos[0] == '/' && p->pos[1] == '/') { + while(p->pos[0] != '\0' && p->pos[0] != '\n') p->pos++; + } + if(p->pos[0] == '/' && p->pos[1] == '*') { + p->pos += 2; + while(p->pos[0] != '\0') { + if(p->pos[0] == '\n') p->line_no++; + if(p->pos[0] == '*' && p->pos[1] == '/') { + p->pos += 2; + break; + } + p->pos++; + } + } + } while(pos < p->pos); +} + +static int ptranslate(int tok) { +#define DT(a, b) ((a) << 8 | (b)) +#define TT(a, b, c) ((a) << 16 | (b) << 8 | (c)) +#define QT(a, b, c, d) ((a) << 24 | (b) << 16 | (c) << 8 | (d)) + /* Map token ID produced by mjs_tok.c to token ID produced by lemon */ + /* clang-format off */ + switch (tok) { + case ':': return TOK_COLON; + case ';': return TOK_SEMICOLON; + case ',': return TOK_COMMA; + case '=': return TOK_ASSIGN; + case '{': return TOK_OPEN_CURLY; + case '}': return TOK_CLOSE_CURLY; + case '(': return TOK_OPEN_PAREN; + case ')': return TOK_CLOSE_PAREN; + case '[': return TOK_OPEN_BRACKET; + case ']': return TOK_CLOSE_BRACKET; + case '*': return TOK_MUL; + case '+': return TOK_PLUS; + case '-': return TOK_MINUS; + case '/': return TOK_DIV; + case '%': return TOK_REM; + case '&': return TOK_AND; + case '|': return TOK_OR; + case '^': return TOK_XOR; + case '.': return TOK_DOT; + case '?': return TOK_QUESTION; + case '!': return TOK_NOT; + case '~': return TOK_TILDA; + case '<': return TOK_LT; + case '>': return TOK_GT; + case DT('<','<'): return TOK_LSHIFT; + case DT('>','>'): return TOK_RSHIFT; + case DT('-','-'): return TOK_MINUS_MINUS; + case DT('+','+'): return TOK_PLUS_PLUS; + case DT('+','='): return TOK_PLUS_ASSIGN; + case DT('-','='): return TOK_MINUS_ASSIGN; + case DT('*','='): return TOK_MUL_ASSIGN; + case DT('/','='): return TOK_DIV_ASSIGN; + case DT('&','='): return TOK_AND_ASSIGN; + case DT('|','='): return TOK_OR_ASSIGN; + case DT('%','='): return TOK_REM_ASSIGN; + case DT('^','='): return TOK_XOR_ASSIGN; + case DT('=','='): return TOK_EQ; + case DT('!','='): return TOK_NE; + case DT('<','='): return TOK_LE; + case DT('>','='): return TOK_GE; + case DT('&','&'): return TOK_LOGICAL_AND; + case DT('|','|'): return TOK_LOGICAL_OR; + case TT('=','=','='): return TOK_EQ_EQ; + case TT('!','=','='): return TOK_NE_NE; + case TT('<','<','='): return TOK_LSHIFT_ASSIGN; + case TT('>','>','='): return TOK_RSHIFT_ASSIGN; + case TT('>','>','>'): return TOK_URSHIFT; + case QT('>','>','>','='): return TOK_URSHIFT_ASSIGN; + } + /* clang-format on */ + return tok; +} + +MJS_PRIVATE int pnext(struct pstate* p) { + int tmp, tok = TOK_INVALID; + + skip_spaces_and_comments(p); + p->tok.ptr = p->pos; + p->tok.len = 1; + + if(p->pos[0] == '\0') { + tok = TOK_EOF; + } else if(mjs_is_digit(p->pos[0])) { + tok = getnum(p); + } else if(p->pos[0] == '\'' || p->pos[0] == '"') { + tok = getstr(p); + } else if(mjs_is_ident(p->pos[0])) { + tok = getident(p); + /* + * NOTE: getident() has side effects on `p`, and `is_reserved_word_token()` + * relies on them. Since in C the order of evaluation of the operands is + * undefined, `is_reserved_word_token()` should be called in a separate + * statement. + */ + tok += is_reserved_word_token(p->tok.ptr, p->tok.len); + } else if(strchr(",.:;{}[]()?", p->pos[0]) != NULL) { + tok = p->pos[0]; + } else if( + (tmp = longtok3(p, '<', '<', '=')) != TOK_EOF || + (tmp = longtok3(p, '>', '>', '=')) != TOK_EOF || + (tmp = longtok4(p, '>', '>', '>', '=')) != TOK_EOF || + (tmp = longtok3(p, '>', '>', '>')) != TOK_EOF || + (tmp = longtok3(p, '=', '=', '=')) != TOK_EOF || + (tmp = longtok3(p, '!', '=', '=')) != TOK_EOF || + (tmp = longtok(p, "&", "&=")) != TOK_EOF || (tmp = longtok(p, "|", "|=")) != TOK_EOF || + (tmp = longtok(p, "<", "<=")) != TOK_EOF || (tmp = longtok(p, ">", ">=")) != TOK_EOF || + (tmp = longtok(p, "-", "-=")) != TOK_EOF || (tmp = longtok(p, "+", "+=")) != TOK_EOF) { + tok = tmp; + } else if((tmp = longtok(p, "^~+-%/*<>=!|&", "=")) != TOK_EOF) { + tok = tmp; + } + if(p->pos[0] != '\0') p->pos++; + LOG(LL_VERBOSE_DEBUG, (" --> %d [%.*s]", tok, p->tok.len, p->tok.ptr)); + p->prev_tok = p->tok.tok; + p->tok.tok = ptranslate(tok); + return p->tok.tok; +} diff --git a/applications/system/elk_mjs/lib/mjs/mjs_tok.h b/applications/system/elk_mjs/lib/mjs/mjs_tok.h new file mode 100644 index 00000000000..03d8fe6fa49 --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/mjs_tok.h @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2016 Cesanta Software Limited + * All rights reserved + */ + +#ifndef MJS_TOK_H_ +#define MJS_TOK_H_ + +#include "mjs_internal.h" + +#if defined(__cplusplus) +extern "C" { +#endif /* __cplusplus */ + +struct tok { + int tok; + int len; + const char* ptr; +}; + +struct pstate { + const char* file_name; /* Source code file name */ + const char* buf; /* Nul-terminated source code buffer */ + const char* pos; /* Current position */ + int line_no; /* Line number */ + int last_emitted_line_no; + struct mbuf offset_lineno_map; + int prev_tok; /* Previous token, for prefix increment / decrement */ + struct tok tok; /* Parsed token */ + struct mjs* mjs; + int start_bcode_idx; /* Index in mjs->bcode at which parsing was started */ + int cur_idx; /* Index in mjs->bcode at which newly generated code is inserted + */ + int depth; +}; + +enum { + TOK_EOF, + TOK_INVALID, + + TOK_COLON, + TOK_SEMICOLON, + TOK_COMMA, + TOK_ASSIGN, + TOK_OPEN_CURLY, + TOK_CLOSE_CURLY, + TOK_OPEN_PAREN, + TOK_CLOSE_PAREN, + TOK_OPEN_BRACKET, + TOK_CLOSE_BRACKET, + TOK_MUL, + TOK_PLUS, + TOK_MINUS, + TOK_DIV, + TOK_REM, + TOK_AND, + TOK_OR, + TOK_XOR, + TOK_DOT, + TOK_QUESTION, + TOK_NOT, + TOK_TILDA, + TOK_LT, + TOK_GT, + TOK_LSHIFT, + TOK_RSHIFT, + TOK_MINUS_MINUS, + TOK_PLUS_PLUS, + TOK_PLUS_ASSIGN, + TOK_MINUS_ASSIGN, + TOK_MUL_ASSIGN, + TOK_DIV_ASSIGN, + TOK_AND_ASSIGN, + TOK_OR_ASSIGN, + TOK_REM_ASSIGN, + TOK_XOR_ASSIGN, + TOK_EQ, + TOK_NE, + TOK_LE, + TOK_GE, + TOK_LOGICAL_AND, + TOK_LOGICAL_OR, + TOK_EQ_EQ, + TOK_NE_NE, + TOK_LSHIFT_ASSIGN, + TOK_RSHIFT_ASSIGN, + TOK_URSHIFT, + TOK_URSHIFT_ASSIGN, + + TOK_UNARY_PLUS, + TOK_UNARY_MINUS, + TOK_POSTFIX_PLUS, + TOK_POSTFIX_MINUS, + + TOK_NUM = 200, /* Make sure they don't clash with ascii '+', '{', etc */ + TOK_STR, + TOK_IDENT, + TOK_KEYWORD_BREAK, + TOK_KEYWORD_CASE, + TOK_KEYWORD_CATCH, + TOK_KEYWORD_CONTINUE, + TOK_KEYWORD_DEBUGGER, + TOK_KEYWORD_DEFAULT, + TOK_KEYWORD_DELETE, + TOK_KEYWORD_DO, + TOK_KEYWORD_ELSE, + TOK_KEYWORD_FALSE, + TOK_KEYWORD_FINALLY, + TOK_KEYWORD_FOR, + TOK_KEYWORD_FUNCTION, + TOK_KEYWORD_IF, + TOK_KEYWORD_IN, + TOK_KEYWORD_INSTANCEOF, + TOK_KEYWORD_NEW, + TOK_KEYWORD_NULL, + TOK_KEYWORD_RETURN, + TOK_KEYWORD_SWITCH, + TOK_KEYWORD_THIS, + TOK_KEYWORD_THROW, + TOK_KEYWORD_TRUE, + TOK_KEYWORD_TRY, + TOK_KEYWORD_TYPEOF, + TOK_KEYWORD_VAR, + TOK_KEYWORD_VOID, + TOK_KEYWORD_WHILE, + TOK_KEYWORD_WITH, + TOK_KEYWORD_LET, + TOK_KEYWORD_UNDEFINED, + TOK_MAX +}; + +MJS_PRIVATE void pinit(const char* file_name, const char* buf, struct pstate*); +MJS_PRIVATE int pnext(struct pstate*); +MJS_PRIVATE int mjs_is_ident(int c); +MJS_PRIVATE int mjs_is_digit(int c); + +#if defined(__cplusplus) +} +#endif /* __cplusplus */ + +#endif /* MJS_TOK_H_ */ diff --git a/applications/system/elk_mjs/lib/mjs/mjs_util.c b/applications/system/elk_mjs/lib/mjs/mjs_util.c new file mode 100644 index 00000000000..fa17024dcc7 --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/mjs_util.c @@ -0,0 +1,476 @@ +/* + * 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" + +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"; + 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", ""); + } else if(mjs_is_object(v)) { + json_printf(out, "%s", ""); + } else if(mjs_is_foreign(v)) { + json_printf( + out, "%s%lx%s", ""); + } else if(mjs_is_function(v)) { + json_printf(out, "%s%d%s", ""); + } 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", ""); + } +} + +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); +} + +#if MJS_ENABLE_DEBUG + +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) { + char buf[40]; + size_t start_i = i; + size_t llen; + uint64_t n; + + snprintf(buf, sizeof(buf), "\t%-3u %-8s", (unsigned)i, opcodetostr(code[i])); + + switch(code[i]) { + case OP_PUSH_FUNC: { + cs_varint_decode(&code[i + 1], ~0, &n, &llen); + LOG(LL_VERBOSE_DEBUG, ("%s %04u", buf, (unsigned)(i - n))); + i += llen; + break; + } + case OP_PUSH_INT: { + cs_varint_decode(&code[i + 1], ~0, &n, &llen); + LOG(LL_VERBOSE_DEBUG, ("%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); + LOG(LL_VERBOSE_DEBUG, + ("%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); + LOG(LL_VERBOSE_DEBUG, ("%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); + LOG(LL_VERBOSE_DEBUG, + ("%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); + LOG(LL_VERBOSE_DEBUG, + ("%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 */ + LOG(LL_VERBOSE_DEBUG, ("%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; + LOG(LL_VERBOSE_DEBUG, + ("%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: + LOG(LL_VERBOSE_DEBUG, ("%s", buf)); + break; + } + return i - start_i; +} + +void mjs_disasm(const uint8_t* code, size_t len) { + 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); + 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; + } + } +} + +static void mjs_dump_obj_stack(const char* name, const struct mbuf* m, struct mjs* mjs) { + char buf[50]; + size_t i, n; + n = mjs_stack_size(m); + LOG(LL_VERBOSE_DEBUG, ("%12s (%d elems): ", name, (int)n)); + for(i = 0; i < n; i++) { + mjs_sprintf(((mjs_val_t*)m->buf)[i], mjs, buf, sizeof(buf)); + LOG(LL_VERBOSE_DEBUG, ("%34s", buf)); + } +} + +void mjs_dump(struct mjs* mjs, int do_disasm) { + LOG(LL_VERBOSE_DEBUG, ("------- MJS VM DUMP BEGIN")); + mjs_dump_obj_stack("DATA_STACK", &mjs->stack, mjs); + mjs_dump_obj_stack("CALL_STACK", &mjs->call_stack, mjs); + mjs_dump_obj_stack("SCOPES", &mjs->scopes, mjs); + mjs_dump_obj_stack("LOOP_OFFSETS", &mjs->loop_addresses, mjs); + mjs_dump_obj_stack("ARG_STACK", &mjs->arg_stack, mjs); + if(do_disasm) { + int parts_cnt = mjs_bcode_parts_cnt(mjs); + int i; + LOG(LL_VERBOSE_DEBUG, ("%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); + } + } + LOG(LL_VERBOSE_DEBUG, ("------- MJS VM DUMP END")); +} + +#endif + +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; +} diff --git a/applications/system/elk_mjs/lib/mjs/mjs_util.h b/applications/system/elk_mjs/lib/mjs/mjs_util.h new file mode 100644 index 00000000000..ccdf3b5081f --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/mjs_util.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2016 Cesanta Software Limited + * All rights reserved + */ + +#ifndef MJS_UTIL_H_ +#define MJS_UTIL_H_ + +#include "common/frozen/frozen.h" +#include "mjs_core.h" +#include "mjs_util_public.h" + +#if defined(__cplusplus) +extern "C" { +#endif /* __cplusplus */ + +struct mjs_bcode_part; + +#if MJS_ENABLE_DEBUG +MJS_PRIVATE const char* opcodetostr(uint8_t opcode); +MJS_PRIVATE size_t mjs_disasm_single(const uint8_t* code, size_t i); +#endif + +MJS_PRIVATE const char* mjs_stringify_type(enum mjs_type t); + +/* + * Checks that the given argument is provided, and checks its type. If check + * fails, sets error in the mjs context, and returns 0; otherwise returns 1. + * + * If `arg_num` >= 0, checks argument; otherwise (`arg_num` is negative) checks + * `this`. `arg_name` is used for the error message only. If `parg` is not + * NULL, writes resulting value at this location in case of success. + */ +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_normalize_idx takes and index in the string and the string size, and + * returns the index which is >= 0 and <= size. Negative index is interpreted + * as size + index. + */ +MJS_PRIVATE int mjs_normalize_idx(int idx, int size); + +MJS_PRIVATE const char* mjs_get_bcode_filename(struct mjs* mjs, struct mjs_bcode_part* bp); + +/* Print JS value `v` to the JSON stream `out`. */ +void mjs_jprintf(mjs_val_t v, struct mjs* mjs, struct json_out* out); + +#if defined(__cplusplus) +} +#endif /* __cplusplus */ + +#endif /* MJS_UTIL_H_ */ diff --git a/applications/system/elk_mjs/lib/mjs/mjs_util_public.h b/applications/system/elk_mjs/lib/mjs/mjs_util_public.h new file mode 100644 index 00000000000..a6a3d477f34 --- /dev/null +++ b/applications/system/elk_mjs/lib/mjs/mjs_util_public.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2016 Cesanta Software Limited + * All rights reserved + */ + +#ifndef MJS_UTIL_PUBLIC_H_ +#define MJS_UTIL_PUBLIC_H_ + +#include "mjs_core_public.h" +#include + +#if defined(__cplusplus) +extern "C" { +#endif /* __cplusplus */ + +const char* mjs_typeof(mjs_val_t v); + +void mjs_fprintf(mjs_val_t v, struct mjs* mjs, FILE* fp); +void mjs_sprintf(mjs_val_t v, struct mjs* mjs, char* buf, size_t buflen); + +#if MJS_ENABLE_DEBUG + +void mjs_disasm(const uint8_t* code, size_t len); +void mjs_dump(struct mjs* mjs, int do_disasm); + +#endif + +/* + * Returns the filename corresponding to the given bcode offset. + */ +const char* mjs_get_bcode_filename_by_offset(struct mjs* mjs, int offset); + +/* + * Returns the line number corresponding to the given bcode offset. + */ +int mjs_get_lineno_by_offset(struct mjs* mjs, int offset); + +/* + * Returns bcode offset of the corresponding call frame cf_num, where 0 means + * the currently executing function, 1 means the first return address, etc. + * + * If given cf_num is too large, -1 is returned. + */ +int mjs_get_offset_by_call_frame_num(struct mjs* mjs, int cf_num); + +#if defined(__cplusplus) +} +#endif /* __cplusplus */ + +#endif /* MJS_UTIL_PUBLIC_H_ */ diff --git a/applications/system/elk_mjs/m_js.c b/applications/system/elk_mjs/m_js.c new file mode 100644 index 00000000000..0b8a6b128f1 --- /dev/null +++ b/applications/system/elk_mjs/m_js.c @@ -0,0 +1,201 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TAG "MJS" + +// TODO: mjs fix +void cs_log_printf(const char* fmt, ...) { + UNUSED(fmt); +} + +// TODO: mjs fix +int cs_log_print_prefix(enum cs_log_level level, const char* file, int ln) { + UNUSED(level); + UNUSED(file); + UNUSED(ln); + return 0; +} + +char* cs_read_file(const char* path, size_t* size) { + Storage* storage = furi_record_open(RECORD_STORAGE); + Stream* stream = file_stream_alloc(storage); + char* data = NULL; + if(!file_stream_open(stream, path, FSAM_READ, FSOM_OPEN_EXISTING)) { + } else { + *size = stream_size(stream); + data = (char*)malloc(*size + 1); + if(data != NULL) { + stream_rewind(stream); + if(stream_read(stream, (uint8_t*)data, *size) != *size) { + file_stream_close(stream); + furi_record_close(RECORD_STORAGE); + stream_free(stream); + free(data); + return NULL; + } + data[*size] = '\0'; + } + } + file_stream_close(stream); + furi_record_close(RECORD_STORAGE); + stream_free(stream); + return data; +} + +char* json_fread(const char* path) { + UNUSED(path); + return NULL; +} + +int json_vfprintf(const char* file_name, const char* fmt, va_list ap) { + UNUSED(file_name); + UNUSED(fmt); + UNUSED(ap); + return 0; +} + +int json_prettify_file(const char* file_name) { + UNUSED(file_name); + return 0; +} + +static void mjs_print(struct mjs* mjs) { + size_t i, num_args = mjs_nargs(mjs); + for(i = 0; i < num_args; i++) { + char* name = NULL; + size_t name_len = 0; + int need_free = 0; + mjs_val_t arg = mjs_arg(mjs, i); + mjs_err_t err = mjs_to_string(mjs, &arg, &name, &name_len, &need_free); + if(err != MJS_OK) { + printf("err %s ", mjs_strerror(mjs, err)); + } else { + printf("%s ", name); + } + + if(need_free) { + free(name); + name = NULL; + } + } + printf("\r\n"); + mjs_return(mjs, MJS_UNDEFINED); +} + +static void mjs_delay(struct mjs* mjs) { + double ms = mjs_get_int(mjs, mjs_arg(mjs, 0)); + furi_delay_ms(ms); + mjs_return(mjs, MJS_UNDEFINED); +} + +static void* my_dlsym(void* handle, const char* name) { + UNUSED(handle); + Elf32_Addr addr = 0; + uint32_t hash = elf_symbolname_hash(name); + if(!firmware_api_interface->resolver_callback(firmware_api_interface, hash, &addr)) { + FURI_LOG_E(TAG, "FFI: cannot find \"%s\"", name); + return NULL; + } + + return (void*)addr; +} + +static void mjs_ffi_address(struct mjs* mjs) { + mjs_val_t name_v = mjs_arg(mjs, 0); + size_t len; + const char* name = mjs_get_string(mjs, &name_v, &len); + void* addr = my_dlsym(NULL, name); + mjs_return(mjs, mjs_mk_foreign(mjs, addr)); +} + +static void mjs_global_to_string(struct mjs* mjs) { + double num = mjs_get_int(mjs, mjs_arg(mjs, 0)); + char tmp_str[] = "-2147483648"; + itoa(num, tmp_str, 10); + mjs_val_t ret = mjs_mk_string(mjs, tmp_str, ~0, true); + mjs_return(mjs, ret); +} + +static void mjs_global_to_hex_string(struct mjs* mjs) { + double num = mjs_get_int(mjs, mjs_arg(mjs, 0)); + char tmp_str[] = "-FFFFFFFF"; + itoa(num, tmp_str, 16); + mjs_val_t ret = mjs_mk_string(mjs, tmp_str, ~0, true); + mjs_return(mjs, ret); +} + +#define MFS_MK_FN(fn) mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)fn) + +static int js_do(const char* path) { + struct mjs* mjs = mjs_create(); + mjs_val_t global = mjs_get_global(mjs); + mjs_set(mjs, global, "print", ~0, MFS_MK_FN(mjs_print)); + mjs_set(mjs, global, "delay", ~0, MFS_MK_FN(mjs_delay)); + mjs_set(mjs, global, "to_string", ~0, MFS_MK_FN(mjs_global_to_string)); + mjs_set(mjs, global, "to_hex_string", ~0, MFS_MK_FN(mjs_global_to_hex_string)); + mjs_set(mjs, global, "ffi_address", ~0, MFS_MK_FN(mjs_ffi_address)); + + mjs_set_ffi_resolver(mjs, my_dlsym); + + mjs_err_t err = mjs_exec_file(mjs, path, NULL); + + if(err != MJS_OK) { + FURI_LOG_E(TAG, "mjs_exec_file: %s", mjs_strerror(mjs, err)); + if(mjs->stack_trace != NULL) { + FURI_LOG_E(TAG, "stack: %s", mjs->stack_trace); + } + } + + mjs_destroy(mjs); + return 0; +} + +int32_t m_js_app(void* arg) { + UNUSED(arg); + FuriString* name = furi_string_alloc_set(EXT_PATH("scripts/mjs")); + + ViewDispatcher* view_dispatcher = view_dispatcher_alloc(); + Loading* loading = loading_alloc(); + + Gui* gui = furi_record_open("gui"); + view_dispatcher_enable_queue(view_dispatcher); + view_dispatcher_attach_to_gui(view_dispatcher, gui, ViewDispatcherTypeFullscreen); + view_dispatcher_add_view(view_dispatcher, 0, loading_get_view(loading)); + view_dispatcher_switch_to_view(view_dispatcher, 0); + + do { + if(arg != NULL && strlen(arg) > 0) { + furi_string_set(name, (const char*)arg); + } else { + DialogsFileBrowserOptions browser_options; + dialog_file_browser_set_basic_options(&browser_options, ".js", NULL); + DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); + if(!dialog_file_browser_show(dialogs, name, name, &browser_options)) break; + furi_record_close(RECORD_DIALOGS); + } + + js_do(furi_string_get_cstr(name)); + } while(false); + + view_dispatcher_remove_view(view_dispatcher, 0); + loading_free(loading); + view_dispatcher_free(view_dispatcher); + furi_record_close("gui"); + + furi_string_free(name); + return 0; +} \ No newline at end of file diff --git a/applications/system/elk_mjs/scripts/about.js b/applications/system/elk_mjs/scripts/about.js new file mode 100644 index 00000000000..b1725b51944 --- /dev/null +++ b/applications/system/elk_mjs/scripts/about.js @@ -0,0 +1,350 @@ + +let RECORD = ({ + open: function (api) { + let ret = Object.create(this.prototype); + ret.name = api.name; + ret.ptr = ret.api.open(ret.name); + return ret; + }, + prototype: { + api: { + open: ffi("void* furi_record_open(char *)"), + close: ffi("void furi_record_close(char *)"), + }, + close: function () { + this.api.close(this.name); + } + } +}); + +let GUI = ({ + name: "gui", + align: { + left: 0, + right: 1, + top: 2, + bottom: 3, + center: 4, + }, +}); + +let DIALOGS = ({ + name: "dialogs", + show: function (record, message) { + return ffi("int dialog_message_show(void*, void*)")(record.ptr, message.ptr); + }, +}); + +let DIALOG_MESSAGE = ({ + alloc: function () { + let ret = Object.create(this.prototype); + ret.ptr = ret.api.alloc(); + return ret; + }, + button_type: { + back: 0, + left: 1, + center: 2, + right: 3, + }, + prototype: { + api: { + alloc: ffi("void* dialog_message_alloc()"), + free: ffi("void dialog_message_free(void*)"), + set_header: ffi("void dialog_message_set_header(void*, char*, int, int, int, int)"), + set_text: ffi("void dialog_message_set_text(void*, char*, int, int, int, int)"), + set_buttons: ffi("void dialog_message_set_buttons(void*, char*, char*, char*)"), + set_icon: ffi("void dialog_message_set_icon(void*, void*, int, int)"), + }, + free: function () { + this.api.free(this.ptr); + }, + set_header: function (text, x, y, align_x, align_y) { + this.api.set_header(this.ptr, text, x, y, align_x, align_y); + }, + set_text: function (text, x, y, align_x, align_y) { + this.api.set_text(this.ptr, text, x, y, align_x, align_y); + }, + set_buttons: function (text_left, text_center, text_right) { + this.api.set_buttons(this.ptr, text_left, text_center, text_right); + }, + set_icon: function (icon, x, y) { + this.api.set_icon(this.ptr, icon, x, y); + }, + set_icon_from_fw: function (icon_name, x, y) { + this.api.set_icon(this.ptr, ffi_address(icon_name), x, y); + }, + } +}); + +let VIEW_DISPATCHER = ({ + alloc: function (record, type) { + let ret = Object.create(this.prototype); + ret.ptr = ret.api.alloc(); + ret.api.attach_to_gui(ret.ptr, record.ptr, type); + return ret; + }, + type: { + desktop: 0, + window: 1, + fullscreen: 2, + }, + prototype: { + api: { + alloc: ffi("void* view_dispatcher_alloc()"), + free: ffi("void view_dispatcher_free(void*)"), + add_view: ffi("void view_dispatcher_add_view(void*, int, void*)"), + remove_view: ffi("void view_dispatcher_remove_view(void*, int)"), + attach_to_gui: ffi("void view_dispatcher_attach_to_gui(void*, void*, int)"), + switch_to_view: ffi("void view_dispatcher_switch_to_view(void*, int)"), + }, + free: function () { + this.api.free(this.ptr); + }, + add_view: function (view) { + this.api.add_view(this.ptr, view.index, view.get_view()); + }, + remove_view: function (view) { + this.api.remove_view(this.ptr, view.index); + }, + switch_to_view: function (view) { + this.api.switch_to_view(this.ptr, view.index); + }, + } +}); + +let EMPTY_SCREEN = ({ + alloc: function (index) { + let ret = Object.create(this.prototype); + ret.ptr = ret.api.alloc(); + ret.index = index; + return ret; + }, + prototype: { + api: { + alloc: ffi("void* empty_screen_alloc()"), + free: ffi("void empty_screen_free(void*)"), + get_view: ffi("void* empty_screen_get_view(void*)"), + }, + free: function () { + this.api.free(this.ptr); + }, + get_view: function () { + return this.api.get_view(this.ptr); + }, + } +}); + +let STD = ({ + malloc: ffi('void *malloc(int)'), + free: ffi('void free(void*)'), +}); + +let C_STR = ({ + create: function (str) { + let len = str.length; + let ret = Object.create(this.prototype); + ret.mem = STD.malloc(len + 1); + ret.text = mkstr(ret.mem, len); + for (let i = 0; i < len; i++) { + ret.mem[i] = str.at(i); + } + return ret; + }, + prototype: { + free: function () { + STD.free(this.mem); + }, + set: function (str) { + let len = str.length; + STD.free(this.mem); + this.mem = STD.malloc(len + 1); + this.text = mkstr(this.mem, len); + for (let i = 0; i < len; i++) { + this.mem[i] = str.at(i); + } + } + } +}); + +let FURI_HAL_VERSION = ({ + get_name: ffi("char *furi_hal_version_get_name_ptr()"), + get_hw_version: ffi("int furi_hal_version_get_hw_version()"), + get_hw_target: ffi("int furi_hal_version_get_hw_target()"), + get_hw_body: ffi("int furi_hal_version_get_hw_body()"), + get_hw_connect: ffi("int furi_hal_version_get_hw_connect()"), + get_hw_region_name: ffi("char* furi_hal_version_get_hw_region_name()"), + get_region_name: ffi("char* furi_hal_region_get_name()"), + version_uid: ffi("char* furi_hal_version_uid()"), + version_uid_size: ffi("int furi_hal_version_uid_size()"), + + get_firmware_version: ffi("void* furi_hal_version_get_firmware_version()"), + get_version: ffi("char* version_get_version(void*)"), + get_builddate: ffi("char* version_get_builddate(void*)"), + get_dirty_flag: ffi("int version_get_dirty_flag(void*)"), + get_githash: ffi("char* version_get_githash(void*)"), + get_gitbranchnum: ffi("char* version_get_gitbranchnum(void*)"), + get_target: ffi("int version_get_target(void*)"), + get_gitbranch: ffi("char* version_get_gitbranch(void*)"), +}); + +// init c-string +let text_back = C_STR.create("Back"); +let text_next = C_STR.create("Next"); +let text_header = C_STR.create(""); +let text_body = C_STR.create(""); + +// open records +let dialogs = RECORD.open(DIALOGS); +let gui = RECORD.open(GUI); + +// create objects +let message = DIALOG_MESSAGE.alloc(); +let view_dispatcher = VIEW_DISPATCHER.alloc(gui, VIEW_DISPATCHER.type.fullscreen); +let empty_screen = EMPTY_SCREEN.alloc(0); + +// configure objects +view_dispatcher.add_view(empty_screen); +view_dispatcher.switch_to_view(empty_screen); + +// init result +let screen_index = 0; +let screen_result = DIALOG_MESSAGE.button_type.right; + +let screens = [ + function () { + text_header.set("Product: Flipper Zero\nModel: FZ.1\n"); + text_body.set("FCC ID: 2A2V6-FZ\nIC: 27624-FZ"); + message.set_header(text_header.text, 0, 0, GUI.align.left, GUI.align.top); + message.set_text(text_body.text, 0, 26, GUI.align.left, GUI.align.top); + let result = DIALOGS.show(dialogs, message); + message.set_header(null, 0, 0, GUI.align.left, GUI.align.top); + message.set_text(null, 0, 0, GUI.align.left, GUI.align.top); + return result; + }, + function () { + text_body.set("Flipper Devices Inc\nSuite B #551, 2803\nPhiladelphia Pike, Claymont\nDE, USA 19703\n"); + message.set_text(text_body.text, 0, 0, GUI.align.left, GUI.align.top); + let result = DIALOGS.show(dialogs, message); + message.set_text(null, 0, 0, GUI.align.left, GUI.align.top); + return result; + }, + function () { + text_body.set("For all compliance\ncertificates please visit:\nwww.flipp.dev/compliance"); + message.set_text(text_body.text, 0, 0, GUI.align.left, GUI.align.top); + let result = DIALOGS.show(dialogs, message); + message.set_text(null, 0, 0, GUI.align.left, GUI.align.top); + return result; + }, + function () { + message.set_icon_from_fw("I_Certification1_103x56", 13, 0); + let result = DIALOGS.show(dialogs, message); + message.set_icon(null, 0, 0); + return result; + }, + function () { + message.set_icon_from_fw("I_Certification2_46x33", 15, 10); + let result = DIALOGS.show(dialogs, message); + message.set_icon(null, 0, 0); + return result; + }, + function () { + let my_name = FURI_HAL_VERSION.get_name(); + if (my_name === null) { + my_name = "Unknown"; + } + let str = to_string(FURI_HAL_VERSION.get_hw_version()) + "."; + str += "F" + to_string(FURI_HAL_VERSION.get_hw_target()); + str += "B" + to_string(FURI_HAL_VERSION.get_hw_body()); + str += "C" + to_string(FURI_HAL_VERSION.get_hw_connect()); + str += " " + FURI_HAL_VERSION.get_hw_region_name() + ":" + FURI_HAL_VERSION.get_region_name(); + str += " " + my_name + "\n"; + str += "Serial Number:\n"; + + + let uid = FURI_HAL_VERSION.version_uid(); + let uid_size = FURI_HAL_VERSION.version_uid_size(); + for (let i = 0; i < uid_size; i++) { + str += to_hex_string(uid.at(i)); + } + + text_header.set("HW Version Info:"); + text_body.set(str); + message.set_header(text_header.text, 0, 0, GUI.align.left, GUI.align.top); + message.set_text(text_body.text, 0, 13, GUI.align.left, GUI.align.top); + let result = DIALOGS.show(dialogs, message); + message.set_header(null, 0, 0, GUI.align.left, GUI.align.top); + message.set_text(null, 0, 0, GUI.align.left, GUI.align.top); + return result; + }, + function () { + let ver = FURI_HAL_VERSION.get_firmware_version(); + let str = ""; + + if (ver === null) { + str = "No info\n"; + } else { + str = FURI_HAL_VERSION.get_version(ver) + " ["; + str += FURI_HAL_VERSION.get_builddate(ver) + "]\n"; + if (FURI_HAL_VERSION.get_dirty_flag(ver) > 0) { + str += "[!]"; + } + str += FURI_HAL_VERSION.get_githash(ver) + " ["; + str += FURI_HAL_VERSION.get_gitbranchnum(ver) + "]\n"; + + str += "[" + to_string(FURI_HAL_VERSION.get_target(ver)) + "] "; + str += FURI_HAL_VERSION.get_gitbranch(ver); + } + + text_header.set("FW Version Info:"); + text_body.set(str); + message.set_header(text_header.text, 0, 0, GUI.align.left, GUI.align.top); + message.set_text(text_body.text, 0, 13, GUI.align.left, GUI.align.top); + let result = DIALOGS.show(dialogs, message); + message.set_header(null, 0, 0, GUI.align.left, GUI.align.top); + message.set_text(null, 0, 0, GUI.align.left, GUI.align.top); + return result; + }, +]; + +// app body +while (screen_result !== DIALOG_MESSAGE.button_type.back) { + if (screen_index >= screens.length - 1) { + message.set_buttons(text_back.text, null, null); + } else { + message.set_buttons(text_back.text, null, text_next.text); + } + + screen_result = screens[screen_index](); + + if (screen_result === DIALOG_MESSAGE.button_type.left) { + if (screen_index <= 0) { + break; + } else { + screen_index--; + } + } else if (screen_result === DIALOG_MESSAGE.button_type.right) { + if (screen_index < screens.length) { + screen_index++; + } + } else if (screen_result === DIALOG_MESSAGE.button_type.back) { + break; + } +} + +// free c-string +message.free(); + +// free objects +view_dispatcher.remove_view(empty_screen); +view_dispatcher.free(); +empty_screen.free(); + +text_back.free(); +text_next.free(); +text_header.free(); +text_body.free(); + +// close records +dialogs.close(); +gui.close(); \ No newline at end of file diff --git a/applications/system/elk_mjs/scripts/api.js b/applications/system/elk_mjs/scripts/api.js new file mode 100644 index 00000000000..ad3b26e1563 --- /dev/null +++ b/applications/system/elk_mjs/scripts/api.js @@ -0,0 +1,3 @@ +({ + add: function (a, b) { return a + b; }, +}) \ No newline at end of file diff --git a/applications/system/elk_mjs/scripts/ffi.js b/applications/system/elk_mjs/scripts/ffi.js new file mode 100644 index 00000000000..d9612be85c0 --- /dev/null +++ b/applications/system/elk_mjs/scripts/ffi.js @@ -0,0 +1,14 @@ +let record = ({ + open: ffi("void* furi_record_open(char *)"), + close: ffi("void furi_record_close(char *)"), +}); + +let notification = ({ + name: "notification", + message: ffi("void notification_message(void*, void*)"), + success: ffi_address("sequence_success"), +}); + +let notification_app = record.open(notification.name); +notification.message(notification_app, notification.success); +record.close(notification.name); \ No newline at end of file diff --git a/applications/system/elk_mjs/scripts/req.js b/applications/system/elk_mjs/scripts/req.js new file mode 100644 index 00000000000..0ffbf6df984 --- /dev/null +++ b/applications/system/elk_mjs/scripts/req.js @@ -0,0 +1,3 @@ +let math = load("/ext/scripts/api.js"); +let result = math.add(5, 10); +print(result); \ No newline at end of file From 15be27b213b9d89bfea422ce42472892b5774b70 Mon Sep 17 00:00:00 2001 From: SG Date: Fri, 15 Sep 2023 18:46:48 +0300 Subject: [PATCH 05/31] JS: scripts as assets --- applications/system/elk_js/application.fam | 1 + applications/system/elk_js/{scripts => assets}/api.js | 0 applications/system/elk_js/{scripts => assets}/blink.js | 0 applications/system/elk_js/{scripts => assets}/ffi.js | 0 applications/system/elk_js/{scripts => assets}/req.js | 0 applications/system/elk_js/elk_js.c | 2 +- applications/system/elk_mjs/application.fam | 1 + applications/system/elk_mjs/{scripts => assets}/about.js | 0 applications/system/elk_mjs/{scripts => assets}/api.js | 0 applications/system/elk_mjs/{scripts => assets}/ffi.js | 0 applications/system/elk_mjs/{scripts => assets}/req.js | 0 applications/system/elk_mjs/m_js.c | 2 +- 12 files changed, 4 insertions(+), 2 deletions(-) rename applications/system/elk_js/{scripts => assets}/api.js (100%) rename applications/system/elk_js/{scripts => assets}/blink.js (100%) rename applications/system/elk_js/{scripts => assets}/ffi.js (100%) rename applications/system/elk_js/{scripts => assets}/req.js (100%) rename applications/system/elk_mjs/{scripts => assets}/about.js (100%) rename applications/system/elk_mjs/{scripts => assets}/api.js (100%) rename applications/system/elk_mjs/{scripts => assets}/ffi.js (100%) rename applications/system/elk_mjs/{scripts => assets}/req.js (100%) diff --git a/applications/system/elk_js/application.fam b/applications/system/elk_js/application.fam index 19ef9fa2366..d19d9ed8b4f 100644 --- a/applications/system/elk_js/application.fam +++ b/applications/system/elk_js/application.fam @@ -5,5 +5,6 @@ App( entry_point="elk_js_app", stack_size=4 * 1024, sources=["*.c", "*.S"], + fap_file_assets="assets", order=1, ) diff --git a/applications/system/elk_js/scripts/api.js b/applications/system/elk_js/assets/api.js similarity index 100% rename from applications/system/elk_js/scripts/api.js rename to applications/system/elk_js/assets/api.js diff --git a/applications/system/elk_js/scripts/blink.js b/applications/system/elk_js/assets/blink.js similarity index 100% rename from applications/system/elk_js/scripts/blink.js rename to applications/system/elk_js/assets/blink.js diff --git a/applications/system/elk_js/scripts/ffi.js b/applications/system/elk_js/assets/ffi.js similarity index 100% rename from applications/system/elk_js/scripts/ffi.js rename to applications/system/elk_js/assets/ffi.js diff --git a/applications/system/elk_js/scripts/req.js b/applications/system/elk_js/assets/req.js similarity index 100% rename from applications/system/elk_js/scripts/req.js rename to applications/system/elk_js/assets/req.js diff --git a/applications/system/elk_js/elk_js.c b/applications/system/elk_js/elk_js.c index f89d6fe2e97..beea588371b 100644 --- a/applications/system/elk_js/elk_js.c +++ b/applications/system/elk_js/elk_js.c @@ -369,7 +369,7 @@ static bool js_do(const char* text) { int32_t elk_js_app(void* arg) { Storage* storage = furi_record_open(RECORD_STORAGE); DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); - FuriString* name = furi_string_alloc_set(EXT_PATH("scripts")); + FuriString* name = furi_string_alloc_set(APP_ASSETS_PATH()); File* file = storage_file_alloc(storage); char* data = NULL; diff --git a/applications/system/elk_mjs/application.fam b/applications/system/elk_mjs/application.fam index ff93ec2d45d..aa5dd1d0222 100644 --- a/applications/system/elk_mjs/application.fam +++ b/applications/system/elk_mjs/application.fam @@ -19,5 +19,6 @@ App( ), ], fap_libs=["gcc"], + fap_file_assets="assets", order=0, ) diff --git a/applications/system/elk_mjs/scripts/about.js b/applications/system/elk_mjs/assets/about.js similarity index 100% rename from applications/system/elk_mjs/scripts/about.js rename to applications/system/elk_mjs/assets/about.js diff --git a/applications/system/elk_mjs/scripts/api.js b/applications/system/elk_mjs/assets/api.js similarity index 100% rename from applications/system/elk_mjs/scripts/api.js rename to applications/system/elk_mjs/assets/api.js diff --git a/applications/system/elk_mjs/scripts/ffi.js b/applications/system/elk_mjs/assets/ffi.js similarity index 100% rename from applications/system/elk_mjs/scripts/ffi.js rename to applications/system/elk_mjs/assets/ffi.js diff --git a/applications/system/elk_mjs/scripts/req.js b/applications/system/elk_mjs/assets/req.js similarity index 100% rename from applications/system/elk_mjs/scripts/req.js rename to applications/system/elk_mjs/assets/req.js diff --git a/applications/system/elk_mjs/m_js.c b/applications/system/elk_mjs/m_js.c index 0b8a6b128f1..db5126bf480 100644 --- a/applications/system/elk_mjs/m_js.c +++ b/applications/system/elk_mjs/m_js.c @@ -166,7 +166,7 @@ static int js_do(const char* path) { int32_t m_js_app(void* arg) { UNUSED(arg); - FuriString* name = furi_string_alloc_set(EXT_PATH("scripts/mjs")); + FuriString* name = furi_string_alloc_set(APP_ASSETS_PATH()); ViewDispatcher* view_dispatcher = view_dispatcher_alloc(); Loading* loading = loading_alloc(); From 5b7655925b6174cd8f558a64a7d282880e536e00 Mon Sep 17 00:00:00 2001 From: SG Date: Fri, 15 Sep 2023 19:21:59 +0300 Subject: [PATCH 06/31] mjs: composite resolver --- .../elk_mjs/addon_api/app_api_interface.h | 9 +++++++ .../elk_mjs/addon_api/app_api_table.cpp | 27 +++++++++++++++++++ .../elk_mjs/addon_api/app_api_table_i.h | 9 +++++++ applications/system/elk_mjs/application.fam | 2 +- applications/system/elk_mjs/m_js.c | 13 ++++++++- 5 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 applications/system/elk_mjs/addon_api/app_api_interface.h create mode 100644 applications/system/elk_mjs/addon_api/app_api_table.cpp create mode 100644 applications/system/elk_mjs/addon_api/app_api_table_i.h diff --git a/applications/system/elk_mjs/addon_api/app_api_interface.h b/applications/system/elk_mjs/addon_api/app_api_interface.h new file mode 100644 index 00000000000..d0db44c4aa0 --- /dev/null +++ b/applications/system/elk_mjs/addon_api/app_api_interface.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +/* + * Resolver interface with private application's symbols. + * Implementation is contained in app_api_table.c + */ +extern const ElfApiInterface* const application_api_interface; \ No newline at end of file diff --git a/applications/system/elk_mjs/addon_api/app_api_table.cpp b/applications/system/elk_mjs/addon_api/app_api_table.cpp new file mode 100644 index 00000000000..aacfb8c181d --- /dev/null +++ b/applications/system/elk_mjs/addon_api/app_api_table.cpp @@ -0,0 +1,27 @@ +#include +#include + +/* + * This file contains an implementation of a symbol table + * with private app's symbols. It is used by composite API resolver + * to load plugins that use internal application's APIs. + */ +#include "app_api_table_i.h" + +static_assert(!has_hash_collisions(app_api_table), "Detected API method hash collision!"); + +constexpr HashtableApiInterface applicaton_hashtable_api_interface{ + { + .api_version_major = 0, + .api_version_minor = 0, + /* generic resolver using pre-sorted array */ + .resolver_callback = &elf_resolve_from_hashtable, + }, + /* pointers to application's API table boundaries */ + .table_cbegin = app_api_table.cbegin(), + .table_cend = app_api_table.cend(), +}; + +/* Casting to generic resolver to use in Composite API resolver */ +extern "C" const ElfApiInterface* const application_api_interface = + &applicaton_hashtable_api_interface; diff --git a/applications/system/elk_mjs/addon_api/app_api_table_i.h b/applications/system/elk_mjs/addon_api/app_api_table_i.h new file mode 100644 index 00000000000..796344ff061 --- /dev/null +++ b/applications/system/elk_mjs/addon_api/app_api_table_i.h @@ -0,0 +1,9 @@ +#include +/* + * A list of app's private functions and objects to expose for plugins. + * It is used to generate a table of symbols for import resolver to use. + * TBD: automatically generate this table from app's header files + */ +static constexpr auto app_api_table = sort(create_array_t( + API_VARIABLE(I_Certification1_103x56, const Icon), + API_VARIABLE(I_Certification2_46x33, const Icon))); \ No newline at end of file diff --git a/applications/system/elk_mjs/application.fam b/applications/system/elk_mjs/application.fam index aa5dd1d0222..d9ea3bddf04 100644 --- a/applications/system/elk_mjs/application.fam +++ b/applications/system/elk_mjs/application.fam @@ -18,7 +18,7 @@ App( ], ), ], - fap_libs=["gcc"], + fap_libs=["gcc", "assets"], fap_file_assets="assets", order=0, ) diff --git a/applications/system/elk_mjs/m_js.c b/applications/system/elk_mjs/m_js.c index db5126bf480..9b808cb5757 100644 --- a/applications/system/elk_mjs/m_js.c +++ b/applications/system/elk_mjs/m_js.c @@ -14,9 +14,13 @@ #include #include #include +#include +#include "addon_api/app_api_interface.h" #define TAG "MJS" +static CompositeApiResolver* resolver; + // TODO: mjs fix void cs_log_printf(const char* fmt, ...) { UNUSED(fmt); @@ -106,7 +110,9 @@ static void* my_dlsym(void* handle, const char* name) { UNUSED(handle); Elf32_Addr addr = 0; uint32_t hash = elf_symbolname_hash(name); - if(!firmware_api_interface->resolver_callback(firmware_api_interface, hash, &addr)) { + const ElfApiInterface* api = composite_api_resolver_get(resolver); + + if(!api->resolver_callback(api, hash, &addr)) { FURI_LOG_E(TAG, "FFI: cannot find \"%s\"", name); return NULL; } @@ -170,6 +176,9 @@ int32_t m_js_app(void* arg) { ViewDispatcher* view_dispatcher = view_dispatcher_alloc(); Loading* loading = loading_alloc(); + resolver = composite_api_resolver_alloc(); + composite_api_resolver_add(resolver, firmware_api_interface); + composite_api_resolver_add(resolver, application_api_interface); Gui* gui = furi_record_open("gui"); view_dispatcher_enable_queue(view_dispatcher); @@ -196,6 +205,8 @@ int32_t m_js_app(void* arg) { view_dispatcher_free(view_dispatcher); furi_record_close("gui"); + composite_api_resolver_free(resolver); + furi_string_free(name); return 0; } \ No newline at end of file From 6130d3158ad285f359fc3dba5b6d758c91cc45b9 Mon Sep 17 00:00:00 2001 From: SG Date: Fri, 15 Sep 2023 19:30:45 +0300 Subject: [PATCH 07/31] mjs: stack trace --- applications/system/elk_mjs/m_js.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/applications/system/elk_mjs/m_js.c b/applications/system/elk_mjs/m_js.c index 9b808cb5757..d3f4afd2141 100644 --- a/applications/system/elk_mjs/m_js.c +++ b/applications/system/elk_mjs/m_js.c @@ -160,9 +160,9 @@ static int js_do(const char* path) { mjs_err_t err = mjs_exec_file(mjs, path, NULL); if(err != MJS_OK) { - FURI_LOG_E(TAG, "mjs_exec_file: %s", mjs_strerror(mjs, err)); + FURI_LOG_E(TAG, "Exec error: %s", mjs_strerror(mjs, err)); if(mjs->stack_trace != NULL) { - FURI_LOG_E(TAG, "stack: %s", mjs->stack_trace); + FURI_LOG_E(TAG, "Stack trace:\n%s", mjs->stack_trace); } } From e2c817edaf74836d06f17c0df606d5c78d15967e Mon Sep 17 00:00:00 2001 From: nminaylov Date: Tue, 19 Sep 2023 18:47:14 +0300 Subject: [PATCH 08/31] ELK JS example removed --- applications/system/elk_js/LICENSE | 15 - applications/system/elk_js/application.fam | 10 - applications/system/elk_js/assets/blink.js | 10 - applications/system/elk_js/assets/ffi.js | 64 - applications/system/elk_js/assets/req.js | 3 - applications/system/elk_js/elk.c | 1429 ----------------- applications/system/elk_js/elk.h | 62 - applications/system/elk_js/elk_js.c | 400 ----- applications/system/elk_js/ffi/ffi.c | 705 -------- applications/system/elk_js/ffi/ffi.h | 452 ------ applications/system/elk_js/ffi/ffi_cfi.h | 54 - applications/system/elk_js/ffi/ffi_common.h | 146 -- applications/system/elk_js/ffi/fficonfig.h | 213 --- applications/system/elk_js/ffi/ffitarget.h | 77 - applications/system/elk_js/ffi/internal.h | 7 - applications/system/elk_js/ffi/prep_cif.c | 233 --- applications/system/elk_js/ffi/sysV-thumb.S | 227 --- applications/system/elk_js/ffi/types.c | 101 -- applications/system/elk_mjs/assets/api.js | 3 - applications/system/elk_mjs/assets/req.js | 3 - .../addon_api/app_api_interface.h | 0 .../addon_api/app_api_table.cpp | 0 .../addon_api/app_api_table_i.h | 0 .../system/{elk_mjs => mjs}/application.fam | 0 .../system/{elk_mjs => mjs}/assets/about.js | 0 .../system/{elk_js => mjs}/assets/api.js | 0 .../system/{elk_mjs => mjs}/assets/ffi.js | 0 applications/system/mjs/assets/req.js | 3 + .../{elk_mjs => mjs}/lib/mjs/common/cs_dbg.c | 0 .../{elk_mjs => mjs}/lib/mjs/common/cs_dbg.h | 0 .../lib/mjs/common/cs_dirent.c | 0 .../lib/mjs/common/cs_dirent.h | 0 .../{elk_mjs => mjs}/lib/mjs/common/cs_file.c | 0 .../{elk_mjs => mjs}/lib/mjs/common/cs_file.h | 0 .../{elk_mjs => mjs}/lib/mjs/common/cs_time.c | 0 .../{elk_mjs => mjs}/lib/mjs/common/cs_time.h | 0 .../lib/mjs/common/cs_varint.c | 0 .../lib/mjs/common/cs_varint.h | 0 .../lib/mjs/common/frozen/frozen.c | 0 .../lib/mjs/common/frozen/frozen.h | 0 .../{elk_mjs => mjs}/lib/mjs/common/mbuf.c | 0 .../{elk_mjs => mjs}/lib/mjs/common/mbuf.h | 0 .../{elk_mjs => mjs}/lib/mjs/common/mg_mem.h | 0 .../{elk_mjs => mjs}/lib/mjs/common/mg_str.c | 0 .../{elk_mjs => mjs}/lib/mjs/common/mg_str.h | 0 .../lib/mjs/common/platform.h | 0 .../lib/mjs/common/platforms/platform_stm32.h | 0 .../lib/mjs/common/str_util.c | 0 .../lib/mjs/common/str_util.h | 0 .../system/{elk_mjs => mjs}/lib/mjs/ffi/ffi.c | 0 .../system/{elk_mjs => mjs}/lib/mjs/ffi/ffi.h | 0 .../{elk_mjs => mjs}/lib/mjs/mjs_array.c | 0 .../{elk_mjs => mjs}/lib/mjs/mjs_array.h | 0 .../lib/mjs/mjs_array_public.h | 0 .../{elk_mjs => mjs}/lib/mjs/mjs_bcode.c | 0 .../{elk_mjs => mjs}/lib/mjs/mjs_bcode.h | 0 .../{elk_mjs => mjs}/lib/mjs/mjs_builtin.c | 0 .../{elk_mjs => mjs}/lib/mjs/mjs_builtin.h | 0 .../{elk_mjs => mjs}/lib/mjs/mjs_conversion.c | 0 .../{elk_mjs => mjs}/lib/mjs/mjs_conversion.h | 0 .../{elk_mjs => mjs}/lib/mjs/mjs_core.c | 0 .../{elk_mjs => mjs}/lib/mjs/mjs_core.h | 0 .../lib/mjs/mjs_core_public.h | 0 .../{elk_mjs => mjs}/lib/mjs/mjs_dataview.c | 0 .../{elk_mjs => mjs}/lib/mjs/mjs_dataview.h | 0 .../{elk_mjs => mjs}/lib/mjs/mjs_exec.c | 0 .../{elk_mjs => mjs}/lib/mjs/mjs_exec.h | 0 .../lib/mjs/mjs_exec_public.h | 0 .../{elk_mjs => mjs}/lib/mjs/mjs_features.h | 0 .../system/{elk_mjs => mjs}/lib/mjs/mjs_ffi.c | 0 .../system/{elk_mjs => mjs}/lib/mjs/mjs_ffi.h | 0 .../{elk_mjs => mjs}/lib/mjs/mjs_ffi_public.h | 0 .../system/{elk_mjs => mjs}/lib/mjs/mjs_gc.c | 0 .../system/{elk_mjs => mjs}/lib/mjs/mjs_gc.h | 0 .../{elk_mjs => mjs}/lib/mjs/mjs_gc_public.h | 0 .../{elk_mjs => mjs}/lib/mjs/mjs_internal.h | 0 .../{elk_mjs => mjs}/lib/mjs/mjs_json.c | 0 .../{elk_mjs => mjs}/lib/mjs/mjs_json.h | 0 .../{elk_mjs => mjs}/lib/mjs/mjs_license.h | 0 .../system/{elk_mjs => mjs}/lib/mjs/mjs_mm.h | 0 .../{elk_mjs => mjs}/lib/mjs/mjs_object.c | 0 .../{elk_mjs => mjs}/lib/mjs/mjs_object.h | 0 .../lib/mjs/mjs_object_public.h | 0 .../{elk_mjs => mjs}/lib/mjs/mjs_parser.c | 75 +- .../{elk_mjs => mjs}/lib/mjs/mjs_parser.h | 0 .../{elk_mjs => mjs}/lib/mjs/mjs_primitive.c | 0 .../{elk_mjs => mjs}/lib/mjs/mjs_primitive.h | 0 .../lib/mjs/mjs_primitive_public.h | 0 .../{elk_mjs => mjs}/lib/mjs/mjs_string.c | 0 .../{elk_mjs => mjs}/lib/mjs/mjs_string.h | 0 .../lib/mjs/mjs_string_public.h | 0 .../system/{elk_mjs => mjs}/lib/mjs/mjs_tok.c | 0 .../system/{elk_mjs => mjs}/lib/mjs/mjs_tok.h | 0 .../{elk_mjs => mjs}/lib/mjs/mjs_util.c | 0 .../{elk_mjs => mjs}/lib/mjs/mjs_util.h | 0 .../lib/mjs/mjs_util_public.h | 0 applications/system/{elk_mjs => mjs}/m_js.c | 0 97 files changed, 41 insertions(+), 4251 deletions(-) delete mode 100644 applications/system/elk_js/LICENSE delete mode 100644 applications/system/elk_js/application.fam delete mode 100644 applications/system/elk_js/assets/blink.js delete mode 100644 applications/system/elk_js/assets/ffi.js delete mode 100644 applications/system/elk_js/assets/req.js delete mode 100644 applications/system/elk_js/elk.c delete mode 100644 applications/system/elk_js/elk.h delete mode 100644 applications/system/elk_js/elk_js.c delete mode 100644 applications/system/elk_js/ffi/ffi.c delete mode 100644 applications/system/elk_js/ffi/ffi.h delete mode 100644 applications/system/elk_js/ffi/ffi_cfi.h delete mode 100644 applications/system/elk_js/ffi/ffi_common.h delete mode 100644 applications/system/elk_js/ffi/fficonfig.h delete mode 100644 applications/system/elk_js/ffi/ffitarget.h delete mode 100644 applications/system/elk_js/ffi/internal.h delete mode 100644 applications/system/elk_js/ffi/prep_cif.c delete mode 100644 applications/system/elk_js/ffi/sysV-thumb.S delete mode 100644 applications/system/elk_js/ffi/types.c delete mode 100644 applications/system/elk_mjs/assets/api.js delete mode 100644 applications/system/elk_mjs/assets/req.js rename applications/system/{elk_mjs => mjs}/addon_api/app_api_interface.h (100%) rename applications/system/{elk_mjs => mjs}/addon_api/app_api_table.cpp (100%) rename applications/system/{elk_mjs => mjs}/addon_api/app_api_table_i.h (100%) rename applications/system/{elk_mjs => mjs}/application.fam (100%) rename applications/system/{elk_mjs => mjs}/assets/about.js (100%) rename applications/system/{elk_js => mjs}/assets/api.js (100%) rename applications/system/{elk_mjs => mjs}/assets/ffi.js (100%) create mode 100644 applications/system/mjs/assets/req.js rename applications/system/{elk_mjs => mjs}/lib/mjs/common/cs_dbg.c (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/common/cs_dbg.h (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/common/cs_dirent.c (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/common/cs_dirent.h (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/common/cs_file.c (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/common/cs_file.h (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/common/cs_time.c (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/common/cs_time.h (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/common/cs_varint.c (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/common/cs_varint.h (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/common/frozen/frozen.c (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/common/frozen/frozen.h (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/common/mbuf.c (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/common/mbuf.h (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/common/mg_mem.h (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/common/mg_str.c (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/common/mg_str.h (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/common/platform.h (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/common/platforms/platform_stm32.h (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/common/str_util.c (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/common/str_util.h (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/ffi/ffi.c (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/ffi/ffi.h (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/mjs_array.c (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/mjs_array.h (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/mjs_array_public.h (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/mjs_bcode.c (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/mjs_bcode.h (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/mjs_builtin.c (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/mjs_builtin.h (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/mjs_conversion.c (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/mjs_conversion.h (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/mjs_core.c (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/mjs_core.h (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/mjs_core_public.h (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/mjs_dataview.c (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/mjs_dataview.h (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/mjs_exec.c (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/mjs_exec.h (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/mjs_exec_public.h (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/mjs_features.h (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/mjs_ffi.c (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/mjs_ffi.h (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/mjs_ffi_public.h (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/mjs_gc.c (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/mjs_gc.h (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/mjs_gc_public.h (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/mjs_internal.h (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/mjs_json.c (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/mjs_json.h (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/mjs_license.h (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/mjs_mm.h (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/mjs_object.c (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/mjs_object.h (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/mjs_object_public.h (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/mjs_parser.c (95%) rename applications/system/{elk_mjs => mjs}/lib/mjs/mjs_parser.h (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/mjs_primitive.c (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/mjs_primitive.h (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/mjs_primitive_public.h (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/mjs_string.c (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/mjs_string.h (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/mjs_string_public.h (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/mjs_tok.c (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/mjs_tok.h (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/mjs_util.c (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/mjs_util.h (100%) rename applications/system/{elk_mjs => mjs}/lib/mjs/mjs_util_public.h (100%) rename applications/system/{elk_mjs => mjs}/m_js.c (100%) diff --git a/applications/system/elk_js/LICENSE b/applications/system/elk_js/LICENSE deleted file mode 100644 index 86837a11165..00000000000 --- a/applications/system/elk_js/LICENSE +++ /dev/null @@ -1,15 +0,0 @@ -Copyright (c) 2013-2021 Cesanta Software Limited -All rights reserved - -This software is dual-licensed: you can redistribute it and/or modify -it under the terms of the GNU Affero General Public License version 3 as -published by the Free Software Foundation. For the terms of this -license, see http://www.fsf.org/licensing/licenses/agpl-3.0.html - -You are free to use this software under the terms of the GNU Affero General -Public License, but WITHOUT ANY WARRANTY; without even the implied -warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -See the GNU General Public License for more details. - -Alternatively, you can license this software under a commercial -license, please contact us at https://cesanta.com/contact.html diff --git a/applications/system/elk_js/application.fam b/applications/system/elk_js/application.fam deleted file mode 100644 index d19d9ed8b4f..00000000000 --- a/applications/system/elk_js/application.fam +++ /dev/null @@ -1,10 +0,0 @@ -App( - appid="elk_js", - name="Elk JS", - apptype=FlipperAppType.EXTERNAL, - entry_point="elk_js_app", - stack_size=4 * 1024, - sources=["*.c", "*.S"], - fap_file_assets="assets", - order=1, -) diff --git a/applications/system/elk_js/assets/blink.js b/applications/system/elk_js/assets/blink.js deleted file mode 100644 index 8f890608c90..00000000000 --- a/applications/system/elk_js/assets/blink.js +++ /dev/null @@ -1,10 +0,0 @@ -print(led); -print('RED'); -led.red(); -delay(500); -print("GREEN"); -led.green(); -delay(500); -print("BLUE"); -led.blue(); -delay(500); \ No newline at end of file diff --git a/applications/system/elk_js/assets/ffi.js b/applications/system/elk_js/assets/ffi.js deleted file mode 100644 index 57ef4d7ae1c..00000000000 --- a/applications/system/elk_js/assets/ffi.js +++ /dev/null @@ -1,64 +0,0 @@ -let record = ({ - open: function (name) { return fficall(arg.pointer, "furi_record_open", arg.string, name); }, - close: function (name) { return fficall(arg.none, "furi_record_close", arg.string, name); }, -}); - -let notification = ({ - name: "notification", - message: function (record, sequence) { - return fficall(arg.none, "notification_message", arg.pointer, record, arg.pointer, sequence); - }, - - audiovisual_alert: function () { return ffires("sequence_audiovisual_alert"); }, - blink_blue_10: function () { return ffires("sequence_blink_blue_10"); }, - blink_blue_100: function () { return ffires("sequence_blink_blue_100"); }, - blink_cyan_10: function () { return ffires("sequence_blink_cyan_10"); }, - blink_cyan_100: function () { return ffires("sequence_blink_cyan_100"); }, - blink_green_10: function () { return ffires("sequence_blink_green_10"); }, - blink_green_100: function () { return ffires("sequence_blink_green_100"); }, - blink_magenta_10: function () { return ffires("sequence_blink_magenta_10"); }, - blink_magenta_100: function () { return ffires("sequence_blink_magenta_100"); }, - blink_red_10: function () { return ffires("sequence_blink_red_10"); }, - blink_red_100: function () { return ffires("sequence_blink_red_100"); }, - blink_start_blue: function () { return ffires("sequence_blink_start_blue"); }, - blink_start_cyan: function () { return ffires("sequence_blink_start_cyan"); }, - blink_start_green: function () { return ffires("sequence_blink_start_green"); }, - blink_start_magenta: function () { return ffires("sequence_blink_start_magenta"); }, - blink_start_red: function () { return ffires("sequence_blink_start_red"); }, - blink_start_yellow: function () { return ffires("sequence_blink_start_yellow"); }, - blink_stop: function () { return ffires("sequence_blink_stop"); }, - blink_white_100: function () { return ffires("sequence_blink_white_100"); }, - blink_yellow_10: function () { return ffires("sequence_blink_yellow_10"); }, - blink_yellow_100: function () { return ffires("sequence_blink_yellow_100"); }, - charged: function () { return ffires("sequence_charged"); }, - charging: function () { return ffires("sequence_charging"); }, - display_backlight_enforce_auto: function () { return ffires("sequence_display_backlight_enforce_auto"); }, - display_backlight_enforce_on: function () { return ffires("sequence_display_backlight_enforce_on"); }, - display_backlight_off: function () { return ffires("sequence_display_backlight_off"); }, - display_backlight_off_delay_1000: function () { return ffires("sequence_display_backlight_off_delay_1000"); }, - display_backlight_on: function () { return ffires("sequence_display_backlight_on"); }, - double_vibro: function () { return ffires("sequence_double_vibro"); }, - error: function () { return ffires("sequence_error"); }, - not_charging: function () { return ffires("sequence_not_charging"); }, - reset_blue: function () { return ffires("sequence_reset_blue"); }, - reset_display: function () { return ffires("sequence_reset_display"); }, - reset_green: function () { return ffires("sequence_reset_green"); }, - reset_red: function () { return ffires("sequence_reset_red"); }, - reset_rgb: function () { return ffires("sequence_reset_rgb"); }, - reset_sound: function () { return ffires("sequence_reset_sound"); }, - reset_vibro: function () { return ffires("sequence_reset_vibro"); }, - set_blue_255: function () { return ffires("sequence_set_blue_255"); }, - set_green_255: function () { return ffires("sequence_set_green_255"); }, - set_only_blue_255: function () { return ffires("sequence_set_only_blue_255"); }, - set_only_green_255: function () { return ffires("sequence_set_only_green_255"); }, - set_only_red_255: function () { return ffires("sequence_set_only_red_255"); }, - set_red_255: function () { return ffires("sequence_set_red_255"); }, - set_vibro_on: function () { return ffires("sequence_set_vibro_on"); }, - single_vibro: function () { return ffires("sequence_single_vibro"); }, - solid_yellow: function () { return ffires("sequence_solid_yellow"); }, - success: function () { return ffires("sequence_success"); }, -}); - -let notification_app = record.open(notification.name); -notification.message(notification_app, notification.success()); -record.close(notification.name); \ No newline at end of file diff --git a/applications/system/elk_js/assets/req.js b/applications/system/elk_js/assets/req.js deleted file mode 100644 index e40ce6b5706..00000000000 --- a/applications/system/elk_js/assets/req.js +++ /dev/null @@ -1,3 +0,0 @@ -let math = require("/ext/scripts/api.js"); -let result = math.add(5, 10); -print(result); \ No newline at end of file diff --git a/applications/system/elk_js/elk.c b/applications/system/elk_js/elk.c deleted file mode 100644 index a771503c94b..00000000000 --- a/applications/system/elk_js/elk.c +++ /dev/null @@ -1,1429 +0,0 @@ -// Copyright (c) 2013-2022 Cesanta Software Limited -// All rights reserved -// -// This software is dual-licensed: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License version 3 as -// published by the Free Software Foundation. For the terms of this -// license, see http://www.fsf.org/licensing/licenses/agpl-3.0.html -// -// You are free to use this software under the terms of the GNU General -// Public License, but WITHOUT ANY WARRANTY; without even the implied -// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// See the GNU General Public License for more details. -// -// Alternatively, you can license this software under a commercial -// license, please contact us at https://cesanta.com/contact.html - -#if defined(__GNUC__) && !defined(JS_OPT) && !defined(ARDUINO_AVR_UNO) && \ - !defined(ARDUINO_AVR_NANO) && !defined(ARDUINO_AVR_PRO) && \ - !defined(__APPLE__) -#pragma GCC optimize("O3,inline") -#endif - -#include -#include -#include -#include -#include -#include - -#include "elk.h" - -#ifndef JS_EXPR_MAX -#define JS_EXPR_MAX 20 -#endif - -#ifndef JS_GC_THRESHOLD -#define JS_GC_THRESHOLD 0.75 -#endif - -typedef uint32_t jsoff_t; - -struct js { - jsoff_t css; // Max observed C stack size - jsoff_t lwm; // JS RAM low watermark: min free RAM observed - const char *code; // Currently parsed code snippet - char errmsg[33]; // Error message placeholder - uint8_t tok; // Last parsed token value - uint8_t consumed; // Indicator that last parsed token was consumed - uint8_t flags; // Execution flags, see F_* constants below -#define F_NOEXEC 1U // Parse code, but not execute -#define F_LOOP 2U // We're inside the loop -#define F_CALL 4U // We're inside a function call -#define F_BREAK 8U // Exit the loop -#define F_RETURN 16U // Return has been executed - jsoff_t clen; // Code snippet length - jsoff_t pos; // Current parsing position - jsoff_t toff; // Offset of the last parsed token - jsoff_t tlen; // Length of the last parsed token - jsoff_t nogc; // Entity offset to exclude from GC - jsval_t tval; // Holds last parsed numeric or string literal value - jsval_t scope; // Current scope - uint8_t *mem; // Available JS memory - jsoff_t size; // Memory size - jsoff_t brk; // Current mem usage boundary - jsoff_t gct; // GC threshold. If brk > gct, trigger GC - jsoff_t maxcss; // Maximum allowed C stack size usage - void *cstk; // C stack pointer at the beginning of js_eval() -}; - -// A JS memory stores diffenent entities: objects, properties, strings -// All entities are packed to the beginning of a buffer. -// The `brk` marks the end of the used memory: -// -// | entity1 | entity2| .... |entityN| unused memory | -// |---------|--------|------|-------|------------------------------| -// js.mem js.brk js.size -// -// Each entity is 4-byte aligned, therefore 2 LSB bits store entity type. -// Object: 8 bytes: offset of the first property, offset of the upper obj -// Property: 8 bytes + val: 4 byte next prop, 4 byte key offs, N byte value -// String: 4xN bytes: 4 byte len << 2, 4byte-aligned 0-terminated data -// -// If C functions are imported, they use the upper part of memory as stack for -// passing params. Each argument is pushed to the top of the memory as jsval_t, -// and js.size is decreased by sizeof(jsval_t), i.e. 8 bytes. When function -// returns, js.size is restored back. So js.size is used as a stack pointer. - -// clang-format off -enum { - TOK_ERR, TOK_EOF, TOK_IDENTIFIER, TOK_NUMBER, TOK_STRING, TOK_SEMICOLON, - TOK_LPAREN, TOK_RPAREN, TOK_LBRACE, TOK_RBRACE, - // Keyword tokens - TOK_BREAK = 50, TOK_CASE, TOK_CATCH, TOK_CLASS, TOK_CONST, TOK_CONTINUE, - TOK_DEFAULT, TOK_DELETE, TOK_DO, TOK_ELSE, TOK_FINALLY, TOK_FOR, TOK_FUNC, - TOK_IF, TOK_IN, TOK_INSTANCEOF, TOK_LET, TOK_NEW, TOK_RETURN, TOK_SWITCH, - TOK_THIS, TOK_THROW, TOK_TRY, TOK_VAR, TOK_VOID, TOK_WHILE, TOK_WITH, - TOK_YIELD, TOK_UNDEF, TOK_NULL, TOK_TRUE, TOK_FALSE, - // JS Operator tokens - TOK_DOT = 100, TOK_CALL, TOK_POSTINC, TOK_POSTDEC, TOK_NOT, TOK_TILDA, // 100 - TOK_TYPEOF, TOK_UPLUS, TOK_UMINUS, TOK_EXP, TOK_MUL, TOK_DIV, TOK_REM, // 106 - TOK_PLUS, TOK_MINUS, TOK_SHL, TOK_SHR, TOK_ZSHR, TOK_LT, TOK_LE, TOK_GT, // 113 - TOK_GE, TOK_EQ, TOK_NE, TOK_AND, TOK_XOR, TOK_OR, TOK_LAND, TOK_LOR, // 121 - TOK_COLON, TOK_Q, TOK_ASSIGN, TOK_PLUS_ASSIGN, TOK_MINUS_ASSIGN, - TOK_MUL_ASSIGN, TOK_DIV_ASSIGN, TOK_REM_ASSIGN, TOK_SHL_ASSIGN, - TOK_SHR_ASSIGN, TOK_ZSHR_ASSIGN, TOK_AND_ASSIGN, TOK_XOR_ASSIGN, - TOK_OR_ASSIGN, TOK_COMMA, -}; - -enum { - // IMPORTANT: T_OBJ, T_PROP, T_STR must go first. That is required by the - // memory layout functions: memory entity types are encoded in the 2 bits, - // thus type values must be 0,1,2,3 - T_OBJ, T_PROP, T_STR, T_UNDEF, T_NULL, T_NUM, T_BOOL, T_FUNC, T_CODEREF, - T_CFUNC, T_ERR -}; - -static const char *typestr(uint8_t t) { - const char *names[] = { "object", "prop", "string", "undefined", "null", - "number", "boolean", "function", "coderef", - "cfunc", "err", "nan" }; - return (t < sizeof(names) / sizeof(names[0])) ? names[t] : "??"; -} - -// Pack JS values into uin64_t, double nan, per IEEE 754 -// 64bit "double": 1 bit sign, 11 bits exponent, 52 bits mantissa -// -// seeeeeee|eeeemmmm|mmmmmmmm|mmmmmmmm|mmmmmmmm|mmmmmmmm|mmmmmmmm|mmmmmmmm -// 11111111|11110000|00000000|00000000|00000000|00000000|00000000|00000000 inf -// 11111111|11111000|00000000|00000000|00000000|00000000|00000000|00000000 qnan -// -// 11111111|1111tttt|vvvvvvvv|vvvvvvvv|vvvvvvvv|vvvvvvvv|vvvvvvvv|vvvvvvvv -// NaN marker |type| 48-bit placeholder for values: pointers, strings -// -// On 64-bit platforms, pointers are really 48 bit only, so they can fit, -// provided they are sign extended -static jsval_t tov(double d) { union { double d; jsval_t v; } u = {d}; return u.v; } -static double tod(jsval_t v) { union { jsval_t v; double d; } u = {v}; return u.d; } -static jsval_t mkval(uint8_t type, uint64_t data) { return ((jsval_t) 0x7ff0U << 48U) | ((jsval_t) (type) << 48) | (data & 0xffffffffffffUL); } -static bool is_nan(jsval_t v) { return (v >> 52U) == 0x7ffU; } -static uint8_t vtype(jsval_t v) { return is_nan(v) ? ((v >> 48U) & 15U) : (uint8_t) T_NUM; } -static size_t vdata(jsval_t v) { return (size_t) (v & ~((jsval_t) 0x7fffUL << 48U)); } -static jsval_t mkcoderef(jsval_t off, jsoff_t len) { return mkval(T_CODEREF, (off & 0xffffffU) | ((jsval_t)(len & 0xffffffU) << 24U)); } -static jsoff_t coderefoff(jsval_t v) { return v & 0xffffffU; } -static jsoff_t codereflen(jsval_t v) { return (v >> 24U) & 0xffffffU; } - -static uint8_t unhex(uint8_t c) { return (c >= '0' && c <= '9') ? (uint8_t) (c - '0') : (c >= 'a' && c <= 'f') ? (uint8_t) (c - 'W') : (c >= 'A' && c <= 'F') ? (uint8_t) (c - '7') : 0; } -static bool is_space(int c) { return c == ' ' || c == '\r' || c == '\n' || c == '\t' || c == '\f' || c == '\v'; } -static bool is_digit(int c) { return c >= '0' && c <= '9'; } -static bool is_xdigit(int c) { return is_digit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); } -static bool is_alpha(int c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); } -static bool is_ident_begin(int c) { return c == '_' || c == '$' || is_alpha(c); } -static bool is_ident_continue(int c) { return c == '_' || c == '$' || is_alpha(c) || is_digit(c); } -static bool is_err(jsval_t v) { return vtype(v) == T_ERR; } -static bool is_unary(uint8_t tok) { return tok >= TOK_POSTINC && tok <= TOK_UMINUS; } -static bool is_assign(uint8_t tok) { return (tok >= TOK_ASSIGN && tok <= TOK_OR_ASSIGN); } -static void saveoff(struct js *js, jsoff_t off, jsoff_t val) { memcpy(&js->mem[off], &val, sizeof(val)); } -static void saveval(struct js *js, jsoff_t off, jsval_t val) { memcpy(&js->mem[off], &val, sizeof(val)); } -static jsoff_t loadoff(struct js *js, jsoff_t off) { jsoff_t v = 0; assert(js->brk <= js->size); memcpy(&v, &js->mem[off], sizeof(v)); return v; } -static jsoff_t offtolen(jsoff_t off) { return (off >> 2) - 1; } -static jsoff_t vstrlen(struct js *js, jsval_t v) { return offtolen(loadoff(js, (jsoff_t) vdata(v))); } -static jsval_t loadval(struct js *js, jsoff_t off) { jsval_t v = 0; memcpy(&v, &js->mem[off], sizeof(v)); return v; } -static jsval_t upper(struct js *js, jsval_t scope) { return mkval(T_OBJ, loadoff(js, (jsoff_t) (vdata(scope) + sizeof(jsoff_t)))); } -static jsoff_t align32(jsoff_t v) { return ((v + 3) >> 2) << 2; } - -#define CHECKV(_v) do { if (is_err(_v)) { res = (_v); goto done; } } while (0) -#define EXPECT(_tok, _e) do { if (next(js) != _tok) { _e; return js_mkerr(js, "parse error"); }; js->consumed = 1; } while (0) -// clang-format on - -// Forward declarations of the private functions -static size_t tostr(struct js *js, jsval_t value, char *buf, size_t len); -static jsval_t js_expr(struct js *js); -static jsval_t js_stmt(struct js *js); -static jsval_t do_op(struct js *, uint8_t op, jsval_t l, jsval_t r); - -static void setlwm(struct js *js) { - jsoff_t n = 0, css = 0; - if (js->brk < js->size) n = js->size - js->brk; - if (js->lwm > n) js->lwm = n; - if ((char *) js->cstk > (char *) &n) - css = (jsoff_t) ((char *) js->cstk - (char *) &n); - if (css > js->css) js->css = css; -} - -// Copy src to dst, make no overflows, 0-terminate. Return bytes copied -static size_t cpy(char *dst, size_t dstlen, const char *src, size_t srclen) { - size_t i = 0; - for (i = 0; i < dstlen && i < srclen && src[i] != 0; i++) dst[i] = src[i]; - if (dstlen > 0) dst[i < dstlen ? i : dstlen - 1] = '\0'; - return i; -} - -// Stringify JS object -static size_t strobj(struct js *js, jsval_t obj, char *buf, size_t len) { - size_t n = cpy(buf, len, "{", 1); - jsoff_t next = loadoff(js, (jsoff_t) vdata(obj)) & ~3U; // First prop offset - while (next < js->brk && next != 0) { // Iterate over props - jsoff_t koff = loadoff(js, next + (jsoff_t) sizeof(next)); - jsval_t val = loadval(js, next + (jsoff_t) (sizeof(next) + sizeof(koff))); - // printf("PROP %u, koff %u\n", next & ~3, koff); - n += cpy(buf + n, len - n, ",", n == 1 ? 0 : 1); - n += tostr(js, mkval(T_STR, koff), buf + n, len - n); - n += cpy(buf + n, len - n, ":", 1); - n += tostr(js, val, buf + n, len - n); - next = loadoff(js, next) & ~3U; // Load next prop offset - } - return n + cpy(buf + n, len - n, "}", 1); -} - -// Stringify numeric JS value -static size_t strnum(jsval_t value, char *buf, size_t len) { - double dv = tod(value), iv; - const char *fmt = (float)modf(dv, &iv) == (float)0.0f ? "%.17g" : "%g"; - return (size_t) snprintf(buf, len, fmt, dv); -} - -// Return mem offset and length of the JS string -static jsoff_t vstr(struct js *js, jsval_t value, jsoff_t *len) { - jsoff_t off = (jsoff_t) vdata(value); - if (len) *len = offtolen(loadoff(js, off)); - return (jsoff_t) (off + sizeof(off)); -} - -// Stringify string JS value -static size_t strstring(struct js *js, jsval_t value, char *buf, size_t len) { - jsoff_t slen, off = vstr(js, value, &slen); - size_t n = 0; - n += cpy(buf + n, len - n, "\"", 1); - n += cpy(buf + n, len - n, (char *) &js->mem[off], slen); - n += cpy(buf + n, len - n, "\"", 1); - return n; -} - -// Stringify JS function -static size_t strfunc(struct js *js, jsval_t value, char *buf, size_t len) { - jsoff_t sn, off = vstr(js, value, &sn); - size_t n = cpy(buf, len, "function", 8); - return n + cpy(buf + n, len - n, (char *) &js->mem[off], sn); -} - -jsval_t js_mkerr(struct js *js, const char *xx, ...) { - va_list ap; - size_t n = cpy(js->errmsg, sizeof(js->errmsg), "ERROR: ", 7); - va_start(ap, xx); - vsnprintf(js->errmsg + n, sizeof(js->errmsg) - n, xx, ap); - va_end(ap); - js->errmsg[sizeof(js->errmsg) - 1] = '\0'; - js->pos = js->clen, js->tok = TOK_EOF, js->consumed = 0; // Jump to the end - return mkval(T_ERR, 0); -} - -// Stringify JS value into the given buffer -static size_t tostr(struct js *js, jsval_t value, char *buf, size_t len) { - switch (vtype(value)) { // clang-format off - case T_UNDEF: return cpy(buf, len, "undefined", 9); - case T_NULL: return cpy(buf, len, "null", 4); - case T_BOOL: return cpy(buf, len, vdata(value) & 1 ? "true" : "false", vdata(value) & 1 ? 4 : 5); - case T_OBJ: return strobj(js, value, buf, len); - case T_STR: return strstring(js, value, buf, len); - case T_NUM: return strnum(value, buf, len); - case T_FUNC: return strfunc(js, value, buf, len); - case T_CFUNC: return (size_t) snprintf(buf, len, "\"c_func_0x%lx\"", (unsigned long) vdata(value)); - case T_PROP: return (size_t) snprintf(buf, len, "PROP@%lu", (unsigned long) vdata(value)); - default: return (size_t) snprintf(buf, len, "VTYPE%d", vtype(value)); - } // clang-format on -} - -// Stringify JS value into a free JS memory block -const char *js_str(struct js *js, jsval_t value) { - // Leave jsoff_t placeholder between js->brk and a stringify buffer, - // in case if next step is convert it into a JS variable - char *buf = (char *) &js->mem[js->brk + sizeof(jsoff_t)]; - size_t len, available = js->size - js->brk - sizeof(jsoff_t); - if (is_err(value)) return js->errmsg; - if (js->brk + sizeof(jsoff_t) >= js->size) return ""; - len = tostr(js, value, buf, available); - js_mkstr(js, NULL, len); - return buf; -} - -static bool js_truthy(struct js *js, jsval_t v) { - uint8_t t = vtype(v); - return (t == T_BOOL && vdata(v) != 0) || (t == T_NUM && tod(v) != (double)0.0) || - (t == T_OBJ || t == T_FUNC) || (t == T_STR && vstrlen(js, v) > 0); -} - -static jsoff_t js_alloc(struct js *js, size_t size) { - jsoff_t ofs = js->brk; - size = align32((jsoff_t) size); // 4-byte align, (n + k - 1) / k * k - if (js->brk + size > js->size) return ~(jsoff_t) 0; - js->brk += (jsoff_t) size; - return ofs; -} - -static jsval_t mkentity(struct js *js, jsoff_t b, const void *buf, size_t len) { - jsoff_t ofs = js_alloc(js, len + sizeof(b)); - if (ofs == (jsoff_t) ~0) return js_mkerr(js, "oom"); - memcpy(&js->mem[ofs], &b, sizeof(b)); - // Using memmove - in case we're stringifying data from the free JS mem - if (buf != NULL) memmove(&js->mem[ofs + sizeof(b)], buf, len); - if ((b & 3) == T_STR) js->mem[ofs + sizeof(b) + len - 1] = 0; // 0-terminate - // printf("MKE: %u @ %u type %d\n", js->brk - ofs, ofs, b & 3); - return mkval(b & 3, ofs); -} - -jsval_t js_mkstr(struct js *js, const void *ptr, size_t len) { - jsoff_t n = (jsoff_t) (len + 1); - // printf("MKSTR %u %u\n", n, js->brk); - return mkentity(js, (jsoff_t) ((n << 2) | T_STR), ptr, n); -} - -static jsval_t mkobj(struct js *js, jsoff_t parent) { - return mkentity(js, 0 | T_OBJ, &parent, sizeof(parent)); -} - -static jsval_t setprop(struct js *js, jsval_t obj, jsval_t k, jsval_t v) { - jsoff_t koff = (jsoff_t) vdata(k); // Key offset - jsoff_t b, head = (jsoff_t) vdata(obj); // Property list head - char buf[sizeof(koff) + sizeof(v)]; // Property memory layout - memcpy(&b, &js->mem[head], sizeof(b)); // Load current 1st prop offset - memcpy(buf, &koff, sizeof(koff)); // Initialize prop data: copy key - memcpy(buf + sizeof(koff), &v, sizeof(v)); // Copy value - jsoff_t brk = js->brk | T_OBJ; // New prop offset - memcpy(&js->mem[head], &brk, sizeof(brk)); // Repoint head to the new prop - // printf("PROP: %u -> %u\n", b, brk); - return mkentity(js, (b & ~3U) | T_PROP, buf, sizeof(buf)); // Create new prop -} - -// Return T_OBJ/T_PROP/T_STR entity size based on the first word in memory -static inline jsoff_t esize(jsoff_t w) { - switch (w & 3U) { // clang-format off - case T_OBJ: return (jsoff_t) (sizeof(jsoff_t) + sizeof(jsoff_t)); - case T_PROP: return (jsoff_t) (sizeof(jsoff_t) + sizeof(jsoff_t) + sizeof(jsval_t)); - case T_STR: return (jsoff_t) (sizeof(jsoff_t) + align32(w >> 2U)); - default: return (jsoff_t) ~0U; - } // clang-format on -} - -static bool is_mem_entity(uint8_t t) { - return t == T_OBJ || t == T_PROP || t == T_STR || t == T_FUNC; -} - -#define GCMASK ~(((jsoff_t) ~0) >> 1) // Entity deletion marker -static void js_fixup_offsets(struct js *js, jsoff_t start, jsoff_t size) { - for (jsoff_t n, v, off = 0; off < js->brk; off += n) { // start from 0! - v = loadoff(js, off); - n = esize(v & ~GCMASK); - if (v & GCMASK) continue; // To be deleted, don't bother - if ((v & 3) != T_OBJ && (v & 3) != T_PROP) continue; - if (v > start) saveoff(js, off, v - size); - if ((v & 3) == T_OBJ) { - jsoff_t u = loadoff(js, (jsoff_t) (off + sizeof(jsoff_t))); - if (u > start) saveoff(js, (jsoff_t) (off + sizeof(jsoff_t)), u - size); - } - if ((v & 3) == T_PROP) { - jsoff_t koff = loadoff(js, (jsoff_t) (off + sizeof(off))); - if (koff > start) saveoff(js, (jsoff_t) (off + sizeof(off)), koff - size); - jsval_t val = loadval(js, (jsoff_t) (off + sizeof(off) + sizeof(off))); - if (is_mem_entity(vtype(val)) && vdata(val) > start) { - saveval(js, (jsoff_t) (off + sizeof(off) + sizeof(off)), - mkval(vtype(val), (unsigned long) (vdata(val) - size))); - } - } - } - // Fixup js->scope - jsoff_t off = (jsoff_t) vdata(js->scope); - if (off > start) js->scope = mkval(T_OBJ, off - size); - if (js->nogc >= start) js->nogc -= size; - // Fixup code that we're executing now, if required - if (js->code > (char *) js->mem && (int32_t)(js->code - (char *) js->mem) < (int32_t)js->size && - (int32_t)(js->code - (char *) js->mem) > (int32_t)start) { - js->code -= size; - // printf("GC-ing code under us!! %ld\n", js->code - (char *) js->mem); - } - // printf("FIXEDOFF %u %u\n", start, size); -} - -static void js_delete_marked_entities(struct js *js) { - for (jsoff_t n, v, off = 0; off < js->brk; off += n) { - v = loadoff(js, off); - n = esize(v & ~GCMASK); - if (v & GCMASK) { // This entity is marked for deletion, remove it - // printf("DEL: %4u %d %x\n", off, v & 3, n); - // assert(off + n <= js->brk); - js_fixup_offsets(js, off, n); - memmove(&js->mem[off], &js->mem[off + n], js->brk - off - n); - js->brk -= n; // Shrink brk boundary by the size of deleted entity - n = 0; // We shifted data, next iteration stay on this offset - } - } -} - -static void js_mark_all_entities_for_deletion(struct js *js) { - for (jsoff_t v, off = 0; off < js->brk; off += esize(v)) { - v = loadoff(js, off); - saveoff(js, off, v | GCMASK); - } -} - -static jsoff_t js_unmark_entity(struct js *js, jsoff_t off) { - jsoff_t v = loadoff(js, off); - if (v & GCMASK) { - saveoff(js, off, v & ~GCMASK); - // printf("UNMARK %5u %d\n", off, v & 3); - if ((v & 3) == T_OBJ) js_unmark_entity(js, v & ~(GCMASK | 3)); - if ((v & 3) == T_PROP) { - js_unmark_entity(js, v & ~(GCMASK | 3)); // Unmark next prop - js_unmark_entity(js, loadoff(js, (jsoff_t) (off + sizeof(off)))); // key - jsval_t val = loadval(js, (jsoff_t) (off + sizeof(off) + sizeof(off))); - if (is_mem_entity(vtype(val))) js_unmark_entity(js, (jsoff_t) vdata(val)); - } - } - return v & ~(GCMASK | 3U); -} - -static void js_unmark_used_entities(struct js *js) { - jsval_t scope = js->scope; - do { - js_unmark_entity(js, (jsoff_t) vdata(scope)); - scope = upper(js, scope); - } while (vdata(scope) != 0); // When global scope is GC-ed, stop - if (js->nogc) js_unmark_entity(js, js->nogc); - // printf("UNMARK: nogc %u\n", js->nogc); - // js_dump(js); -} - -void js_gc(struct js *js) { - // printf("================== GC %u\n", js->nogc); - setlwm(js); - if (js->nogc == (jsoff_t) ~0) return; // ~0 is a special case: GC Is disabled - js_mark_all_entities_for_deletion(js); - js_unmark_used_entities(js); - js_delete_marked_entities(js); -} - -// Skip whitespaces and comments -static jsoff_t skiptonext(const char *code, jsoff_t len, jsoff_t n) { - // printf("SKIP: [%.*s]\n", len - n, &code[n]); - while (n < len) { - if (is_space(code[n])) { - n++; - } else if (n + 1 < len && code[n] == '/' && code[n + 1] == '/') { - for (n += 2; n < len && code[n] != '\n';) n++; - } else if (n + 3 < len && code[n] == '/' && code[n + 1] == '*') { - for (n += 4; n < len && (code[n - 2] != '*' || code[n - 1] != '/');) n++; - } else { - break; - } - } - return n; -} - -static bool streq(const char *buf, size_t len, const char *p, size_t n) { - return n == len && memcmp(buf, p, len) == 0; -} - -static uint8_t parsekeyword(const char *buf, size_t len) { - switch (buf[0]) { // clang-format off - case 'b': if (streq("break", 5, buf, len)) return TOK_BREAK; break; - case 'c': if (streq("class", 5, buf, len)) return TOK_CLASS; if (streq("case", 4, buf, len)) return TOK_CASE; if (streq("catch", 5, buf, len)) return TOK_CATCH; if (streq("const", 5, buf, len)) return TOK_CONST; if (streq("continue", 8, buf, len)) return TOK_CONTINUE; break; - case 'd': if (streq("do", 2, buf, len)) return TOK_DO; if (streq("default", 7, buf, len)) return TOK_DEFAULT; break; // if (streq("delete", 6, buf, len)) return TOK_DELETE; break; - case 'e': if (streq("else", 4, buf, len)) return TOK_ELSE; break; - case 'f': if (streq("for", 3, buf, len)) return TOK_FOR; if (streq("function", 8, buf, len)) return TOK_FUNC; if (streq("finally", 7, buf, len)) return TOK_FINALLY; if (streq("false", 5, buf, len)) return TOK_FALSE; break; - case 'i': if (streq("if", 2, buf, len)) return TOK_IF; if (streq("in", 2, buf, len)) return TOK_IN; if (streq("instanceof", 10, buf, len)) return TOK_INSTANCEOF; break; - case 'l': if (streq("let", 3, buf, len)) return TOK_LET; break; - case 'n': if (streq("new", 3, buf, len)) return TOK_NEW; if (streq("null", 4, buf, len)) return TOK_NULL; break; - case 'r': if (streq("return", 6, buf, len)) return TOK_RETURN; break; - case 's': if (streq("switch", 6, buf, len)) return TOK_SWITCH; break; - case 't': if (streq("try", 3, buf, len)) return TOK_TRY; if (streq("this", 4, buf, len)) return TOK_THIS; if (streq("throw", 5, buf, len)) return TOK_THROW; if (streq("true", 4, buf, len)) return TOK_TRUE; if (streq("typeof", 6, buf, len)) return TOK_TYPEOF; break; - case 'u': if (streq("undefined", 9, buf, len)) return TOK_UNDEF; break; - case 'v': if (streq("var", 3, buf, len)) return TOK_VAR; if (streq("void", 4, buf, len)) return TOK_VOID; break; - case 'w': if (streq("while", 5, buf, len)) return TOK_WHILE; if (streq("with", 4, buf, len)) return TOK_WITH; break; - case 'y': if (streq("yield", 5, buf, len)) return TOK_YIELD; break; - } // clang-format on - return TOK_IDENTIFIER; -} - -static uint8_t parseident(const char *buf, jsoff_t len, jsoff_t *tlen) { - if (is_ident_begin(buf[0])) { - while (*tlen < len && is_ident_continue(buf[*tlen])) (*tlen)++; - return parsekeyword(buf, *tlen); - } - return TOK_ERR; -} - -static uint8_t next(struct js *js) { - if (js->consumed == 0) return js->tok; - js->consumed = 0; - js->tok = TOK_ERR; - js->toff = js->pos = skiptonext(js->code, js->clen, js->pos); - js->tlen = 0; - const char *buf = js->code + js->toff; - // clang-format off - if (js->toff >= js->clen) { js->tok = TOK_EOF; return js->tok; } -#define TOK(T, LEN) { js->tok = T; js->tlen = (LEN); break; } -#define LOOK(OFS, CH) js->toff + OFS < js->clen && buf[OFS] == CH - switch (buf[0]) { - case '?': TOK(TOK_Q, 1); - case ':': TOK(TOK_COLON, 1); - case '(': TOK(TOK_LPAREN, 1); - case ')': TOK(TOK_RPAREN, 1); - case '{': TOK(TOK_LBRACE, 1); - case '}': TOK(TOK_RBRACE, 1); - case ';': TOK(TOK_SEMICOLON, 1); - case ',': TOK(TOK_COMMA, 1); - case '!': if (LOOK(1, '=') && LOOK(2, '=')) TOK(TOK_NE, 3); TOK(TOK_NOT, 1); - case '.': TOK(TOK_DOT, 1); - case '~': TOK(TOK_TILDA, 1); - case '-': if (LOOK(1, '-')) TOK(TOK_POSTDEC, 2); if (LOOK(1, '=')) TOK(TOK_MINUS_ASSIGN, 2); TOK(TOK_MINUS, 1); - case '+': if (LOOK(1, '+')) TOK(TOK_POSTINC, 2); if (LOOK(1, '=')) TOK(TOK_PLUS_ASSIGN, 2); TOK(TOK_PLUS, 1); - case '*': if (LOOK(1, '*')) TOK(TOK_EXP, 2); if (LOOK(1, '=')) TOK(TOK_MUL_ASSIGN, 2); TOK(TOK_MUL, 1); - case '/': if (LOOK(1, '=')) TOK(TOK_DIV_ASSIGN, 2); TOK(TOK_DIV, 1); - case '%': if (LOOK(1, '=')) TOK(TOK_REM_ASSIGN, 2); TOK(TOK_REM, 1); - case '&': if (LOOK(1, '&')) TOK(TOK_LAND, 2); if (LOOK(1, '=')) TOK(TOK_AND_ASSIGN, 2); TOK(TOK_AND, 1); - case '|': if (LOOK(1, '|')) TOK(TOK_LOR, 2); if (LOOK(1, '=')) TOK(TOK_OR_ASSIGN, 2); TOK(TOK_OR, 1); - case '=': if (LOOK(1, '=') && LOOK(2, '=')) TOK(TOK_EQ, 3); TOK(TOK_ASSIGN, 1); - case '<': if (LOOK(1, '<') && LOOK(2, '=')) TOK(TOK_SHL_ASSIGN, 3); if (LOOK(1, '<')) TOK(TOK_SHL, 2); if (LOOK(1, '=')) TOK(TOK_LE, 2); TOK(TOK_LT, 1); - case '>': if (LOOK(1, '>') && LOOK(2, '=')) TOK(TOK_SHR_ASSIGN, 3); if (LOOK(1, '>')) TOK(TOK_SHR, 2); if (LOOK(1, '=')) TOK(TOK_GE, 2); TOK(TOK_GT, 1); - case '^': if (LOOK(1, '=')) TOK(TOK_XOR_ASSIGN, 2); TOK(TOK_XOR, 1); - case '"': case '\'': - js->tlen++; - while (js->toff + js->tlen < js->clen && buf[js->tlen] != buf[0]) { - uint8_t increment = 1; - if (buf[js->tlen] == '\\') { - if (js->toff + js->tlen + 2 > js->clen) break; - increment = 2; - if (buf[js->tlen + 1] == 'x') { - if (js->toff + js->tlen + 4 > js->clen) break; - increment = 4; - } - } - js->tlen += increment; - } - if (buf[0] == buf[js->tlen]) js->tok = TOK_STRING, js->tlen++; - break; - case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { - char *end; - js->tval = tov(strtod(buf, &end)); // TODO(lsm): protect against OOB access - TOK(TOK_NUMBER, (jsoff_t) (end - buf)); - } - default: js->tok = parseident(buf, js->clen - js->toff, &js->tlen); break; - } // clang-format on - js->pos = js->toff + js->tlen; - // printf("NEXT: %d %d [%.*s]\n", js->tok, js->pos, (int) js->tlen, buf); - return js->tok; -} - -static inline uint8_t lookahead(struct js *js) { - uint8_t old = js->tok, tok = 0; - jsoff_t pos = js->pos; - js->consumed = 1; - tok = next(js); - js->pos = pos, js->tok = old; - return tok; -} - -static void mkscope(struct js *js) { - assert((js->flags & F_NOEXEC) == 0); - jsoff_t prev = (jsoff_t) vdata(js->scope); - js->scope = mkobj(js, prev); - // printf("ENTER SCOPE %u, prev %u\n", (jsoff_t) vdata(js->scope), prev); -} - -static void delscope(struct js *js) { - js->scope = upper(js, js->scope); - // printf("EXIT SCOPE %u\n", (jsoff_t) vdata(js->scope)); -} - -static jsval_t js_block(struct js *js, bool create_scope) { - jsval_t res = js_mkundef(); - if (create_scope) mkscope(js); // Enter new scope - js->consumed = 1; - // jsoff_t pos = js->pos; - while (next(js) != TOK_EOF && next(js) != TOK_RBRACE && !is_err(res)) { - uint8_t t = js->tok; - res = js_stmt(js); - if (!is_err(res) && t != TOK_LBRACE && t != TOK_IF && t != TOK_WHILE && - js->tok != TOK_SEMICOLON) { - res = js_mkerr(js, "; expected"); - break; - } - } - // printf("BLOCKEND %s\n", js_str(js, res)); - if (create_scope) delscope(js); // Exit scope - return res; -} - -// Seach for property in a single object -static jsoff_t lkp(struct js *js, jsval_t obj, const char *buf, size_t len) { - jsoff_t off = loadoff(js, (jsoff_t) vdata(obj)) & ~3U; // Load first prop off - // printf("LKP: %lu %u [%.*s]\n", vdata(obj), off, (int) len, buf); - while (off < js->brk && off != 0) { // Iterate over props - jsoff_t koff = loadoff(js, (jsoff_t) (off + sizeof(off))); - jsoff_t klen = (loadoff(js, koff) >> 2) - 1; - const char *p = (char *) &js->mem[koff + sizeof(koff)]; - // printf(" %u %u[%.*s]\n", off, (int) klen, (int) klen, p); - if (streq(buf, len, p, klen)) return off; // Found ! - off = loadoff(js, off) & ~3U; // Load next prop offset - } - return 0; // Not found -} - -// Lookup variable in the scope chain -static jsval_t lookup(struct js *js, const char *buf, size_t len) { - if (js->flags & F_NOEXEC) return 0; - for (jsval_t scope = js->scope;;) { - jsoff_t off = lkp(js, scope, buf, len); - if (off != 0) return mkval(T_PROP, off); - if (vdata(scope) == 0) break; - scope = - mkval(T_OBJ, loadoff(js, (jsoff_t) (vdata(scope) + sizeof(jsoff_t)))); - } - return js_mkerr(js, "'%.*s' not found", (int) len, buf); -} - -static jsval_t resolveprop(struct js *js, jsval_t v) { - if (vtype(v) != T_PROP) return v; - return resolveprop(js, - loadval(js, (jsoff_t) (vdata(v) + sizeof(jsoff_t) * 2))); -} - -static jsval_t assign(struct js *js, jsval_t lhs, jsval_t val) { - saveval(js, (jsoff_t) ((vdata(lhs) & ~3U) + sizeof(jsoff_t) * 2), val); - return lhs; -} - -static jsval_t do_assign_op(struct js *js, uint8_t op, jsval_t l, jsval_t r) { - uint8_t m[] = {TOK_PLUS, TOK_MINUS, TOK_MUL, TOK_DIV, TOK_REM, TOK_SHL, - TOK_SHR, TOK_ZSHR, TOK_AND, TOK_XOR, TOK_OR}; - jsval_t res = do_op(js, m[op - TOK_PLUS_ASSIGN], resolveprop(js, l), r); - return assign(js, l, res); -} - -static jsval_t do_string_op(struct js *js, uint8_t op, jsval_t l, jsval_t r) { - jsoff_t n1, off1 = vstr(js, l, &n1); - jsoff_t n2, off2 = vstr(js, r, &n2); - if (op == TOK_PLUS) { - jsval_t res = js_mkstr(js, NULL, n1 + n2); - // printf("STRPLUS %u %u %u %u [%.*s] [%.*s]\n", n1, off1, n2, off2, (int) - // n1, - // &js->mem[off1], (int) n2, &js->mem[off2]); - if (vtype(res) == T_STR) { - jsoff_t n, off = vstr(js, res, &n); - memmove(&js->mem[off], &js->mem[off1], n1); - memmove(&js->mem[off + n1], &js->mem[off2], n2); - } - return res; - } else if (op == TOK_EQ) { - bool eq = n1 == n2 && memcmp(&js->mem[off1], &js->mem[off2], n1) == 0; - return mkval(T_BOOL, eq ? 1 : 0); - } else if (op == TOK_NE) { - bool eq = n1 == n2 && memcmp(&js->mem[off1], &js->mem[off2], n1) == 0; - return mkval(T_BOOL, eq ? 0 : 1); - } else { - return js_mkerr(js, "bad str op"); - } -} - -static jsval_t do_dot_op(struct js *js, jsval_t l, jsval_t r) { - const char *ptr = (char *) &js->code[coderefoff(r)]; - if (vtype(r) != T_CODEREF) return js_mkerr(js, "ident expected"); - // Handle stringvalue.length - if (vtype(l) == T_STR && streq(ptr, codereflen(r), "length", 6)) { - return tov(offtolen(loadoff(js, (jsoff_t) vdata(l)))); - } - if (vtype(l) != T_OBJ) return js_mkerr(js, "lookup in non-obj"); - jsoff_t off = lkp(js, l, ptr, codereflen(r)); - return off == 0 ? js_mkundef() : mkval(T_PROP, off); -} - -static jsval_t js_call_params(struct js *js) { - jsoff_t pos = js->pos; - uint8_t flags = js->flags; - js->flags |= F_NOEXEC; - js->consumed = 1; - for (bool comma = false; next(js) != TOK_EOF; comma = true) { - if (!comma && next(js) == TOK_RPAREN) break; - js_expr(js); - if (next(js) == TOK_RPAREN) break; - EXPECT(TOK_COMMA, js->flags = flags); - } - EXPECT(TOK_RPAREN, js->flags = flags); - js->flags = flags; - return mkcoderef(pos, js->pos - pos - js->tlen); -} - -static void reverse(jsval_t *args, int nargs) { - for (int i = 0; i < nargs / 2; i++) { - jsval_t tmp = args[i]; - args[i] = args[nargs - i - 1], args[nargs - i - 1] = tmp; - } -} - -// Call native C function -static jsval_t call_c(struct js *js, - jsval_t (*fn)(struct js *, jsval_t *, int)) { - int argc = 0; - while (js->pos < js->clen) { - if (next(js) == TOK_RPAREN) break; - jsval_t arg = resolveprop(js, js_expr(js)); - if (js->brk + sizeof(arg) > js->size) return js_mkerr(js, "call oom"); - js->size -= (jsoff_t) sizeof(arg); - memcpy(&js->mem[js->size], &arg, sizeof(arg)); - argc++; - // printf(" arg %d -> %s\n", argc, js_str(js, arg)); - if (next(js) == TOK_COMMA) js->consumed = 1; - } - reverse((jsval_t *) &js->mem[js->size], argc); - jsval_t res = fn(js, (jsval_t *) &js->mem[js->size], argc); - setlwm(js); - js->size += (jsoff_t) sizeof(jsval_t) * (jsoff_t) argc; // Restore stack - return res; -} - -// Call JS function. 'fn' looks like this: "(a,b) { return a + b; }" -static jsval_t call_js(struct js *js, const char *fn, jsoff_t fnlen) { - jsoff_t fnpos = 1; - // printf("JSCALL [%.*s] -> %.*s\n", (int) js->clen, js->code, (int) fnlen, - // fn); - // printf("JSCALL, nogc %u [%.*s]\n", js->nogc, (int) fnlen, fn); - mkscope(js); // Create function call scope - // Loop over arguments list "(a, b)" and set scope variables - while (fnpos < fnlen) { - fnpos = skiptonext(fn, fnlen, fnpos); // Skip to the identifier - if (fnpos < fnlen && fn[fnpos] == ')') break; // Closing paren? break! - jsoff_t identlen = 0; // Identifier length - uint8_t tok = parseident(&fn[fnpos], fnlen - fnpos, &identlen); - if (tok != TOK_IDENTIFIER) break; - // Here we have argument name. Calculate arg value - // printf(" [%.*s] -> %u [%.*s] -> ", (int) identlen, &fn[fnpos], js->pos, - // (int) js->clen, js->code); - js->pos = skiptonext(js->code, js->clen, js->pos); - js->consumed = 1; - jsval_t v = js->code[js->pos] == ')' ? js_mkundef() : js_expr(js); - // Set argument in the function scope - setprop(js, js->scope, js_mkstr(js, &fn[fnpos], identlen), v); - js->pos = skiptonext(js->code, js->clen, js->pos); - if (js->pos < js->clen && js->code[js->pos] == ',') js->pos++; - fnpos = skiptonext(fn, fnlen, fnpos + identlen); // Skip past identifier - if (fnpos < fnlen && fn[fnpos] == ',') fnpos++; // And skip comma - } - if (fnpos < fnlen && fn[fnpos] == ')') fnpos++; // Skip to the function body - fnpos = skiptonext(fn, fnlen, fnpos); // Up to the opening brace - if (fnpos < fnlen && fn[fnpos] == '{') fnpos++; // And skip the brace - size_t n = fnlen - fnpos - 1U; // Function code with stripped braces - // printf("flags: %d, body: %zu [%.*s]\n", js->flags, n, (int) n, &fn[fnpos]); - js->flags = F_CALL; // Mark we're in the function call - jsval_t res = js_eval(js, &fn[fnpos], n); // Call function, no GC - if (!is_err(res) && !(js->flags & F_RETURN)) res = js_mkundef(); // No return - delscope(js); // Delete call scope - // printf(" -> %d [%s], tok %d\n", js->flags, js_str(js, res), js->tok); - return res; -} - -static jsval_t do_call_op(struct js *js, jsval_t func, jsval_t args) { - if (vtype(args) != T_CODEREF) return js_mkerr(js, "bad call"); - if (vtype(func) != T_FUNC && vtype(func) != T_CFUNC) - return js_mkerr(js, "calling non-function"); - const char *code = js->code; // Save current parser state - jsoff_t clen = js->clen, pos = js->pos; // code, position and code length - js->code = &js->code[coderefoff(args)]; // Point parser to args - js->clen = codereflen(args); // Set args length - js->pos = skiptonext(js->code, js->clen, 0); // Skip to 1st arg - uint8_t tok = js->tok, flags = js->flags; // Save flags - jsoff_t nogc = js->nogc; - jsval_t res = js_mkundef(); - if (vtype(func) == T_FUNC) { - jsoff_t fnlen, fnoff = vstr(js, func, &fnlen); - js->nogc = (jsoff_t) (fnoff - sizeof(jsoff_t)); - res = call_js(js, (const char *) (&js->mem[fnoff]), fnlen); - } else { - res = call_c(js, (jsval_t(*)(struct js *, jsval_t *, int)) vdata(func)); - } - js->code = code, js->clen = clen, js->pos = pos; // Restore parser - js->flags = flags, js->tok = tok, js->nogc = nogc; - js->consumed = 1; - return res; -} - -// clang-format off -static jsval_t do_op(struct js *js, uint8_t op, jsval_t lhs, jsval_t rhs) { - if (js->flags & F_NOEXEC) return 0; - jsval_t l = resolveprop(js, lhs), r = resolveprop(js, rhs); - // printf("OP %d %d %d\n", op, vtype(lhs), vtype(r)); - setlwm(js); - if (is_err(l)) return l; - if (is_err(r)) return r; - if (is_assign(op) && vtype(lhs) != T_PROP) return js_mkerr(js, "bad lhs"); - switch (op) { - case TOK_TYPEOF: return js_mkstr(js, typestr(vtype(r)), strlen(typestr(vtype(r)))); - case TOK_CALL: return do_call_op(js, l, r); - case TOK_ASSIGN: return assign(js, lhs, r); - case TOK_POSTINC: { do_assign_op(js, TOK_PLUS_ASSIGN, lhs, tov(1)); return l; } - case TOK_POSTDEC: { do_assign_op(js, TOK_MINUS_ASSIGN, lhs, tov(1)); return l; } - case TOK_NOT: if (vtype(r) == T_BOOL) return mkval(T_BOOL, !vdata(r)); break; - } - if (is_assign(op)) return do_assign_op(js, op, lhs, r); - if (vtype(l) == T_STR && vtype(r) == T_STR) return do_string_op(js, op, l, r); - if (is_unary(op) && vtype(r) != T_NUM) return js_mkerr(js, "type mismatch"); - if (!is_unary(op) && op != TOK_DOT && (vtype(l) != T_NUM || vtype(r) != T_NUM)) return js_mkerr(js, "type mismatch"); - double a = tod(l), b = tod(r); - switch (op) { - //case TOK_EXP: return tov(pow(a, b)); - case TOK_DIV: return tod(r) == 0 ? js_mkerr(js, "div by zero") : tov(a / b); - case TOK_REM: return tov(a - b * ((double) (long) (a / b))); - case TOK_MUL: return tov(a * b); - case TOK_PLUS: return tov(a + b); - case TOK_MINUS: return tov(a - b); - case TOK_XOR: return tov((double)((long) a ^ (long) b)); - case TOK_AND: return tov((double)((long) a & (long) b)); - case TOK_OR: return tov((double)((long) a | (long) b)); - case TOK_UMINUS: return tov(-b); - case TOK_UPLUS: return r; - case TOK_TILDA: return tov((double)(~(long) b)); - case TOK_NOT: return mkval(T_BOOL, b == 0); - case TOK_SHL: return tov((double)((long) a << (long) b)); - case TOK_SHR: return tov((double)((long) a >> (long) b)); - case TOK_DOT: return do_dot_op(js, l, r); - case TOK_EQ: return mkval(T_BOOL, (long) a == (long) b); - case TOK_NE: return mkval(T_BOOL, (long) a != (long) b); - case TOK_LT: return mkval(T_BOOL, a < b); - case TOK_LE: return mkval(T_BOOL, a <= b); - case TOK_GT: return mkval(T_BOOL, a > b); - case TOK_GE: return mkval(T_BOOL, a >= b); - default: return js_mkerr(js, "unknown op %d", (int) op); // LCOV_EXCL_LINE - } -} // clang-format on - -static jsval_t js_str_literal(struct js *js) { - uint8_t *in = (uint8_t *) &js->code[js->toff]; - uint8_t *out = &js->mem[js->brk + sizeof(jsoff_t)]; - size_t n1 = 0, n2 = 0; - // printf("STR %u %lu %lu\n", js->brk, js->tlen, js->clen); - if (js->brk + sizeof(jsoff_t) + js->tlen > js->size) - return js_mkerr(js, "oom"); - while (n2++ + 2 < js->tlen) { - if (in[n2] == '\\') { - if (in[n2 + 1] == in[0]) { - out[n1++] = in[0]; - } else if (in[n2 + 1] == 'n') { - out[n1++] = '\n'; - } else if (in[n2 + 1] == 't') { - out[n1++] = '\t'; - } else if (in[n2 + 1] == 'r') { - out[n1++] = '\r'; - } else if (in[n2 + 1] == 'x' && is_xdigit(in[n2 + 2]) && - is_xdigit(in[n2 + 3])) { - out[n1++] = (uint8_t) ((unhex(in[n2 + 2]) << 4U) | unhex(in[n2 + 3])); - n2 += 2; - } else { - return js_mkerr(js, "bad str literal"); - } - n2++; - } else { - out[n1++] = ((uint8_t *) js->code)[js->toff + n2]; - } - } - return js_mkstr(js, NULL, n1); -} - -static jsval_t js_obj_literal(struct js *js) { - uint8_t exe = !(js->flags & F_NOEXEC); - // printf("OLIT1\n"); - jsval_t obj = exe ? mkobj(js, 0) : js_mkundef(); - if (is_err(obj)) return obj; - js->consumed = 1; - while (next(js) != TOK_RBRACE) { - jsval_t key = 0; - if (js->tok == TOK_IDENTIFIER) { - if (exe) key = js_mkstr(js, js->code + js->toff, js->tlen); - } else if (js->tok == TOK_STRING) { - if (exe) key = js_str_literal(js); - } else { - return js_mkerr(js, "parse error"); - } - js->consumed = 1; - EXPECT(TOK_COLON, ); - jsval_t val = js_expr(js); - if (exe) { - // printf("XXXX [%s] scope: %lu\n", js_str(js, val), vdata(js->scope)); - if (is_err(val)) return val; - if (is_err(key)) return key; - jsval_t res = setprop(js, obj, key, resolveprop(js, val)); - if (is_err(res)) return res; - } - if (next(js) == TOK_RBRACE) break; - EXPECT(TOK_COMMA, ); - } - EXPECT(TOK_RBRACE, ); - return obj; -} - -static jsval_t js_func_literal(struct js *js) { - uint8_t flags = js->flags; // Save current flags - js->consumed = 1; - EXPECT(TOK_LPAREN, js->flags = flags); - jsoff_t pos = js->pos - 1; - for (bool comma = false; next(js) != TOK_EOF; comma = true) { - if (!comma && next(js) == TOK_RPAREN) break; - EXPECT(TOK_IDENTIFIER, js->flags = flags); - if (next(js) == TOK_RPAREN) break; - EXPECT(TOK_COMMA, js->flags = flags); - } - EXPECT(TOK_RPAREN, js->flags = flags); - EXPECT(TOK_LBRACE, js->flags = flags); - js->consumed = 0; - js->flags |= F_NOEXEC; // Set no-execution flag to parse the - jsval_t res = js_block(js, false); // Skip function body - no exec - if (is_err(res)) { // But fail short on parse error - js->flags = flags; - return res; - } - js->flags = flags; // Restore flags - jsval_t str = js_mkstr(js, &js->code[pos], js->pos - pos); - js->consumed = 1; - // printf("FUNC: %u [%.*s]\n", pos, js->pos - pos, &js->code[pos]); - return mkval(T_FUNC, (unsigned long) vdata(str)); -} - -#define RTL_BINOP(_f1, _f2, _cond) \ - jsval_t res = _f1(js); \ - while (!is_err(res) && (_cond)) { \ - uint8_t op = js->tok; \ - js->consumed = 1; \ - jsval_t rhs = _f2(js); \ - if (is_err(rhs)) return rhs; \ - res = do_op(js, op, res, rhs); \ - } \ - return res; - -#define LTR_BINOP(_f, _cond) \ - jsval_t res = _f(js); \ - while (!is_err(res) && (_cond)) { \ - uint8_t op = js->tok; \ - js->consumed = 1; \ - jsval_t rhs = _f(js); \ - if (is_err(rhs)) return rhs; \ - res = do_op(js, op, res, rhs); \ - } \ - return res; - -static jsval_t js_literal(struct js *js) { - next(js); - setlwm(js); - // printf("css : %u\n", js->css); - if (js->maxcss > 0 && js->css > js->maxcss) return js_mkerr(js, "C stack"); - js->consumed = 1; - switch (js->tok) { // clang-format off - case TOK_ERR: return js_mkerr(js, "parse error"); - case TOK_NUMBER: return js->tval; - case TOK_STRING: return js_str_literal(js); - case TOK_LBRACE: return js_obj_literal(js); - case TOK_FUNC: return js_func_literal(js); - case TOK_NULL: return js_mknull(); - case TOK_UNDEF: return js_mkundef(); - case TOK_TRUE: return js_mktrue(); - case TOK_FALSE: return js_mkfalse(); - case TOK_IDENTIFIER: return mkcoderef((jsoff_t) js->toff, (jsoff_t) js->tlen); - default: return js_mkerr(js, "bad expr"); - } // clang-format on -} - -static jsval_t js_group(struct js *js) { - if (next(js) == TOK_LPAREN) { - js->consumed = 1; - jsval_t v = js_expr(js); - if (is_err(v)) return v; - if (next(js) != TOK_RPAREN) return js_mkerr(js, ") expected"); - js->consumed = 1; - return v; - } else { - return js_literal(js); - } -} - -static jsval_t js_call_dot(struct js *js) { - jsval_t res = js_group(js); - if (is_err(res)) return res; - if (vtype(res) == T_CODEREF) { - res = lookup(js, &js->code[coderefoff(res)], codereflen(res)); - } - while (next(js) == TOK_LPAREN || next(js) == TOK_DOT) { - if (js->tok == TOK_DOT) { - js->consumed = 1; - res = do_op(js, TOK_DOT, res, js_group(js)); - } else { - jsval_t params = js_call_params(js); - if (is_err(params)) return params; - res = do_op(js, TOK_CALL, res, params); - } - } - return res; -} - -static jsval_t js_postfix(struct js *js) { - jsval_t res = js_call_dot(js); - if (is_err(res)) return res; - next(js); - if (js->tok == TOK_POSTINC || js->tok == TOK_POSTDEC) { - js->consumed = 1; - res = do_op(js, js->tok, res, 0); - } - return res; -} - -static jsval_t js_unary(struct js *js) { - if (next(js) == TOK_NOT || js->tok == TOK_TILDA || js->tok == TOK_TYPEOF || - js->tok == TOK_MINUS || js->tok == TOK_PLUS) { - uint8_t t = js->tok; - if (t == TOK_MINUS) t = TOK_UMINUS; - if (t == TOK_PLUS) t = TOK_UPLUS; - js->consumed = 1; - return do_op(js, t, js_mkundef(), js_unary(js)); - } else { - return js_postfix(js); - } -} - -static jsval_t js_mul_div_rem(struct js *js) { - LTR_BINOP(js_unary, - (next(js) == TOK_MUL || js->tok == TOK_DIV || js->tok == TOK_REM)); -} - -static jsval_t js_plus_minus(struct js *js) { - LTR_BINOP(js_mul_div_rem, (next(js) == TOK_PLUS || js->tok == TOK_MINUS)); -} - -static jsval_t js_shifts(struct js *js) { - LTR_BINOP(js_plus_minus, (next(js) == TOK_SHR || next(js) == TOK_SHL || - next(js) == TOK_ZSHR)); -} - -static jsval_t js_comparison(struct js *js) { - LTR_BINOP(js_shifts, (next(js) == TOK_LT || next(js) == TOK_LE || - next(js) == TOK_GT || next(js) == TOK_GE)); -} - -static jsval_t js_equality(struct js *js) { - LTR_BINOP(js_comparison, (next(js) == TOK_EQ || next(js) == TOK_NE)); -} - -static jsval_t js_bitwise_and(struct js *js) { - LTR_BINOP(js_equality, (next(js) == TOK_AND)); -} - -static jsval_t js_bitwise_xor(struct js *js) { - LTR_BINOP(js_bitwise_and, (next(js) == TOK_XOR)); -} - -static jsval_t js_bitwise_or(struct js *js) { - LTR_BINOP(js_bitwise_xor, (next(js) == TOK_OR)); -} - -static jsval_t js_logical_and(struct js *js) { - jsval_t res = js_bitwise_or(js); - if (is_err(res)) return res; - uint8_t flags = js->flags; - while (next(js) == TOK_LAND) { - js->consumed = 1; - res = resolveprop(js, res); - if (!js_truthy(js, res)) js->flags |= F_NOEXEC; // false && ... shortcut - if (js->flags & F_NOEXEC) { - js_logical_and(js); - } else { - res = js_logical_and(js); - } - } - js->flags = flags; - return res; -} - -static jsval_t js_logical_or(struct js *js) { - jsval_t res = js_logical_and(js); - if (is_err(res)) return res; - uint8_t flags = js->flags; - while (next(js) == TOK_LOR) { - js->consumed = 1; - res = resolveprop(js, res); - if (js_truthy(js, res)) js->flags |= F_NOEXEC; // true || ... shortcut - if (js->flags & F_NOEXEC) { - js_logical_or(js); - } else { - res = js_logical_or(js); - } - } - js->flags = flags; - return res; -} - -static jsval_t js_ternary(struct js *js) { - jsval_t res = js_logical_or(js); - if (next(js) == TOK_Q) { - uint8_t flags = js->flags; - js->consumed = 1; - if (js_truthy(js, resolveprop(js, res))) { - res = js_ternary(js); - js->flags |= F_NOEXEC; - EXPECT(TOK_COLON, js->flags = flags); - js_ternary(js); - js->flags = flags; - } else { - js->flags |= F_NOEXEC; - js_ternary(js); - EXPECT(TOK_COLON, js->flags = flags); - js->flags = flags; - res = js_ternary(js); - } - } - return res; -} - -static jsval_t js_assignment(struct js *js) { - RTL_BINOP(js_ternary, js_assignment, - (next(js) == TOK_ASSIGN || js->tok == TOK_PLUS_ASSIGN || - js->tok == TOK_MINUS_ASSIGN || js->tok == TOK_MUL_ASSIGN || - js->tok == TOK_DIV_ASSIGN || js->tok == TOK_REM_ASSIGN || - js->tok == TOK_SHL_ASSIGN || js->tok == TOK_SHR_ASSIGN || - js->tok == TOK_ZSHR_ASSIGN || js->tok == TOK_AND_ASSIGN || - js->tok == TOK_XOR_ASSIGN || js->tok == TOK_OR_ASSIGN)); -} - -static jsval_t js_expr(struct js *js) { - return js_assignment(js); -} - -static jsval_t js_let(struct js *js) { - uint8_t exe = !(js->flags & F_NOEXEC); - js->consumed = 1; - for (;;) { - EXPECT(TOK_IDENTIFIER, ); - js->consumed = 0; - jsoff_t noff = js->toff, nlen = js->tlen; - char *name = (char *) &js->code[noff]; - jsval_t v = js_mkundef(); - js->consumed = 1; - if (next(js) == TOK_ASSIGN) { - js->consumed = 1; - v = js_expr(js); - if (is_err(v)) return v; // Propagate error if any - } - if (exe) { - if (lkp(js, js->scope, name, nlen) > 0) - return js_mkerr(js, "'%.*s' already declared", (int) nlen, name); - jsval_t x = - setprop(js, js->scope, js_mkstr(js, name, nlen), resolveprop(js, v)); - if (is_err(x)) return x; - } - if (next(js) == TOK_SEMICOLON || next(js) == TOK_EOF) break; // Stop - EXPECT(TOK_COMMA, ); - } - return js_mkundef(); -} - -static jsval_t js_block_or_stmt(struct js *js) { - if (next(js) == TOK_LBRACE) return js_block(js, !(js->flags & F_NOEXEC)); - jsval_t res = resolveprop(js, js_stmt(js)); - js->consumed = 0; // - return res; -} - -static jsval_t js_if(struct js *js) { - js->consumed = 1; - EXPECT(TOK_LPAREN, ); - jsval_t res = js_mkundef(), cond = resolveprop(js, js_expr(js)); - EXPECT(TOK_RPAREN, ); - bool cond_true = js_truthy(js, cond), exe = !(js->flags & F_NOEXEC); - // printf("IF COND: %s, true? %d\n", js_str(js, cond), cond_true); - if (!cond_true) js->flags |= F_NOEXEC; - jsval_t blk = js_block_or_stmt(js); - if (cond_true) res = blk; - if (exe && !cond_true) js->flags &= (uint8_t) ~F_NOEXEC; - if (lookahead(js) == TOK_ELSE) { - js->consumed = 1; - next(js); - js->consumed = 1; - if (cond_true) js->flags |= F_NOEXEC; - blk = js_block_or_stmt(js); - if (!cond_true) res = blk; - if (cond_true && exe) js->flags &= (uint8_t) ~F_NOEXEC; - } - return res; -} - -static inline bool expect(struct js *js, uint8_t tok, jsval_t *res) { - if (next(js) != tok) { - *res = js_mkerr(js, "parse error"); - return false; - } else { - js->consumed = 1; - return true; - } -} - -static inline bool is_err2(jsval_t *v, jsval_t *res) { - bool r = is_err(*v); - if (r) *res = *v; - return r; -} - -static jsval_t js_for(struct js *js) { - uint8_t flags = js->flags, exe = !(flags & F_NOEXEC); - jsval_t v, res = js_mkundef(); - jsoff_t pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; - if (exe) mkscope(js); // Enter new scope - if (!expect(js, TOK_FOR, &res)) goto done; - if (!expect(js, TOK_LPAREN, &res)) goto done; - - if (next(js) == TOK_SEMICOLON) { // initialisation - } else if (next(js) == TOK_LET) { - v = js_let(js); - if (is_err2(&v, &res)) goto done; - } else { - v = js_expr(js); - if (is_err2(&v, &res)) goto done; - } - if (!expect(js, TOK_SEMICOLON, &res)) goto done; - js->flags |= F_NOEXEC; - pos1 = js->pos; // condition - if (next(js) != TOK_SEMICOLON) { - v = js_expr(js); - if (is_err2(&v, &res)) goto done; - } - if (!expect(js, TOK_SEMICOLON, &res)) goto done; - pos2 = js->pos; // final expr - if (next(js) != TOK_RPAREN) { - v = js_expr(js); - if (is_err2(&v, &res)) goto done; - } - if (!expect(js, TOK_RPAREN, &res)) goto done; - pos3 = js->pos; // body - v = js_block_or_stmt(js); - if (is_err2(&v, &res)) goto done; - pos4 = js->pos; // end of body - while (!(flags & F_NOEXEC)) { - js->flags = flags, js->pos = pos1, js->consumed = 1; - if (next(js) != TOK_SEMICOLON) { // Is condition specified? - v = resolveprop(js, js_expr(js)); // Yes. check condition - if (is_err2(&v, &res)) goto done; // Fail short on error - if (!js_truthy(js, v)) break; // Exit the loop if condition is false - } - js->pos = pos3, js->consumed = 1, js->flags |= F_LOOP; // Execute the - v = js_block_or_stmt(js); // loop body - if (is_err2(&v, &res)) goto done; // Fail on error - if (js->flags & F_BREAK) break; // break was executed - exit the loop! - js->flags = flags, js->pos = pos2, js->consumed = 1; // Jump to final expr - if (next(js) != TOK_RPAREN) { // Is it specified? - v = js_expr(js); // Yes. Execute it - if (is_err2(&v, &res)) goto done; // On error, fail short - } - } - js->pos = pos4, js->tok = TOK_SEMICOLON, js->consumed = 0; -done: - if (exe) delscope(js); // Exit scope - js->flags = flags; // Restore flags - return res; -} - -static jsval_t js_break(struct js *js) { - if (js->flags & F_NOEXEC) { - } else { - if (!(js->flags & F_LOOP)) return js_mkerr(js, "not in loop"); - js->flags |= F_BREAK | F_NOEXEC; - } - js->consumed = 1; - return js_mkundef(); -} - -static jsval_t js_continue(struct js *js) { - if (js->flags & F_NOEXEC) { - } else { - if (!(js->flags & F_LOOP)) return js_mkerr(js, "not in loop"); - js->flags |= F_NOEXEC; - } - js->consumed = 1; - return js_mkundef(); -} - -static jsval_t js_return(struct js *js) { - uint8_t exe = !(js->flags & F_NOEXEC); - js->consumed = 1; - if (exe && !(js->flags & F_CALL)) return js_mkerr(js, "not in func"); - if (next(js) == TOK_SEMICOLON) return js_mkundef(); - jsval_t res = resolveprop(js, js_expr(js)); - if (exe) { - js->pos = js->clen; // Shift to the end - exit the code snippet - js->flags |= F_RETURN; // Tell caller we've executed - } - return resolveprop(js, res); -} - -static jsval_t js_stmt(struct js *js) { - jsval_t res; - // jsoff_t pos = js->pos - js->tlen; - if (js->brk > js->gct) js_gc(js); - switch (next(js)) { // clang-format off - case TOK_CASE: case TOK_CATCH: case TOK_CLASS: case TOK_CONST: - case TOK_DEFAULT: case TOK_DELETE: case TOK_DO: case TOK_FINALLY: - case TOK_IN: case TOK_INSTANCEOF: case TOK_NEW: case TOK_SWITCH: - case TOK_THIS: case TOK_THROW: case TOK_TRY: case TOK_VAR: case TOK_VOID: - case TOK_WITH: case TOK_WHILE: case TOK_YIELD: - res = js_mkerr(js, "'%.*s' not implemented", (int) js->tlen, js->code + js->toff); - break; - case TOK_CONTINUE: res = js_continue(js); break; - case TOK_BREAK: res = js_break(js); break; - case TOK_LET: res = js_let(js); break; - case TOK_IF: res = js_if(js); break; - case TOK_LBRACE: res = js_block(js, !(js->flags & F_NOEXEC)); break; - case TOK_FOR: res = js_for(js); break; // 25222 -> 27660 - case TOK_RETURN: res = js_return(js); break; - default: res = resolveprop(js, js_expr(js)); break; - } - //printf("STMT [%.*s] -> %s, tok %d, flags %d\n", (int) (js->pos - pos), &js->code[pos], js_str(js, res), next(js), js->flags); - if (next(js) != TOK_SEMICOLON && next(js) != TOK_EOF && next(js) != TOK_RBRACE) return js_mkerr(js, "; expected"); - js->consumed = 1; - // clang-format on - return res; -} - -struct js *js_create(void *buf, size_t len) { - struct js *js = NULL; - if (len < sizeof(*js) + esize(T_OBJ)) return js; - memset(buf, 0, len); // Important! - js = (struct js *) buf; // struct js lives at the beginning - js->mem = (uint8_t *) (js + 1); // Then goes memory for JS data - js->size = (jsoff_t) (len - sizeof(*js)); // JS memory size - js->scope = mkobj(js, 0); // Create global scope - js->size = js->size / 8U * 8U; // Align js->size by 8 byte - js->lwm = js->size; // Initial LWM: 100% free - js->gct = js->size / 2; - return js; -} - -// clang-format off -void js_setgct(struct js *js, size_t gct) { js->gct = (jsoff_t) gct; } -void js_setmaxcss(struct js *js, size_t max) { js->maxcss = (jsoff_t) max; } -jsval_t js_mktrue(void) { return mkval(T_BOOL, 1); } -jsval_t js_mkfalse(void) { return mkval(T_BOOL, 0); } -jsval_t js_mkundef(void) { return mkval(T_UNDEF, 0); } -jsval_t js_mknull(void) { return mkval(T_NULL, 0); } -jsval_t js_mknum(double value) { return tov(value); } -jsval_t js_mkobj(struct js *js) { return mkobj(js, 0); } -jsval_t js_mkfun(jsval_t (*fn)(struct js *, jsval_t *, int)) { return mkval(T_CFUNC, (size_t) (void *) fn); } -double js_getnum(jsval_t value) { return tod(value); } -int js_getbool(jsval_t value) { return vdata(value) & 1 ? 1 : 0; } - -jsval_t js_glob(struct js *js) { (void) js; return mkval(T_OBJ, 0); } - -void js_set(struct js *js, jsval_t obj, const char *key, jsval_t val) { - if (vtype(obj) == T_OBJ) setprop(js, obj, js_mkstr(js, key, strlen(key)), val); -} - -char *js_getstr(struct js *js, jsval_t value, size_t *len) { - if (vtype(value) != T_STR) return NULL; - jsoff_t n, off = vstr(js, value, &n); - if (len != NULL) *len = n; - return (char *) &js->mem[off]; -} - -int js_type(jsval_t val) { - switch (vtype(val)) { - case T_UNDEF: return JS_UNDEF; - case T_NULL: return JS_NULL; - case T_BOOL: return vdata(val) == 0 ? JS_FALSE: JS_TRUE; - case T_STR: return JS_STR; - case T_NUM: return JS_NUM; - case T_ERR: return JS_ERR; - default: return JS_PRIV; - } -} -void js_stats(struct js *js, size_t *total, size_t *lwm, size_t *css) { - if (total) *total = js->size; - if (lwm) *lwm = js->lwm; - if (css) *css = js->css; -} -// clang-format on - -bool js_chkargs(jsval_t *args, int nargs, const char *spec) { - int i = 0, ok = 1; - for (; ok && i < nargs && spec[i]; i++) { - uint8_t t = vtype(args[i]), c = (uint8_t) spec[i]; - ok = (c == 'b' && t == T_BOOL) || (c == 'd' && t == T_NUM) || - (c == 's' && t == T_STR) || (c == 'j'); - } - if (spec[i] != '\0' || i != nargs) ok = 0; - return ok; -} - -jsval_t js_eval(struct js *js, const char *buf, size_t len) { - // printf("EVAL: [%.*s]\n", (int) len, buf); - jsval_t res = js_mkundef(); - if (len == (size_t) ~0U) len = strlen(buf); - js->consumed = 1; - js->tok = TOK_ERR; - js->code = buf; - js->clen = (jsoff_t) len; - js->pos = 0; - js->cstk = &res; - while (next(js) != TOK_EOF && !is_err(res)) { - res = js_stmt(js); - } - return res; -} - -#ifdef JS_DUMP -void js_dump(struct js *js) { - jsoff_t off = 0, v; - printf("JS size %u, brk %u, lwm %u, css %u, nogc %u\n", js->size, js->brk, - js->lwm, (unsigned) js->css, js->nogc); - while (off < js->brk) { - memcpy(&v, &js->mem[off], sizeof(v)); - printf(" %5u: ", off); - if ((v & 3U) == T_OBJ) { - printf("OBJ %u %u\n", v & ~3U, - loadoff(js, (jsoff_t) (off + sizeof(off)))); - } else if ((v & 3U) == T_PROP) { - jsoff_t koff = loadoff(js, (jsoff_t) (off + sizeof(v))); - jsval_t val = loadval(js, (jsoff_t) (off + sizeof(v) + sizeof(v))); - printf("PROP next %u, koff %u vtype %d vdata %lu\n", v & ~3U, koff, - vtype(val), (unsigned long) vdata(val)); - } else if ((v & 3) == T_STR) { - jsoff_t len = offtolen(v); - printf("STR %u [%.*s]\n", len, (int) len, js->mem + off + sizeof(v)); - } else { - printf("???\n"); - break; - } - off += esize(v); - } -} -#endif diff --git a/applications/system/elk_js/elk.h b/applications/system/elk_js/elk.h deleted file mode 100644 index 711634a2a14..00000000000 --- a/applications/system/elk_js/elk.h +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) 2013-2022 Cesanta Software Limited -// All rights reserved -// -// This software is dual-licensed: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License version 3 as -// published by the Free Software Foundation. For the terms of this -// license, see http://www.fsf.org/licensing/licenses/agpl-3.0.html -// -// You are free to use this software under the terms of the GNU General -// Public License, but WITHOUT ANY WARRANTY; without even the implied -// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// See the GNU General Public License for more details. -// -// Alternatively, you can license this software under a commercial -// license, please contact us at https://cesanta.com/contact.html - -#define JS_VERSION "3.0.0" -#pragma once - -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -struct js; // JS engine (opaque) -typedef uint64_t jsval_t; // JS value - -struct js *js_create(void *buf, size_t len); // Create JS instance -jsval_t js_eval(struct js *, const char *, size_t); // Execute JS code -jsval_t js_glob(struct js *); // Return global object -const char *js_str(struct js *, jsval_t val); // Stringify JS value -bool js_chkargs(jsval_t *, int, const char *); // Check args validity -void js_setmaxcss(struct js *, size_t); // Set max C stack size -void js_setgct(struct js *, size_t); // Set GC trigger threshold -void js_stats(struct js *, size_t *total, size_t *min, size_t *cstacksize); -void js_dump(struct js *); // Print debug info. Requires -DJS_DUMP - -// Create JS values from C values -jsval_t js_mkundef(void); // Create undefined -jsval_t js_mknull(void); // Create null, null, true, false -jsval_t js_mktrue(void); // Create true -jsval_t js_mkfalse(void); // Create false -jsval_t js_mkstr(struct js *, const void *, size_t); // Create string -jsval_t js_mknum(double); // Create number -jsval_t js_mkerr(struct js *js, const char *fmt, ...); // Create error -jsval_t js_mkfun(jsval_t (*fn)(struct js *, jsval_t *, int)); // Create func -jsval_t js_mkobj(struct js *); // Create object -void js_set(struct js *, jsval_t, const char *, jsval_t); // Set obj attr - -// Extract C values from JS values -enum { JS_UNDEF, JS_NULL, JS_TRUE, JS_FALSE, JS_STR, JS_NUM, JS_ERR, JS_PRIV }; -int js_type(jsval_t val); // Return JS value type -double js_getnum(jsval_t val); // Get number -int js_getbool(jsval_t val); // Get boolean, 0 or 1 -char *js_getstr(struct js *js, jsval_t val, size_t *len); // Get string - -#ifdef __cplusplus -} -#endif diff --git a/applications/system/elk_js/elk_js.c b/applications/system/elk_js/elk_js.c deleted file mode 100644 index beea588371b..00000000000 --- a/applications/system/elk_js/elk_js.c +++ /dev/null @@ -1,400 +0,0 @@ -#include -#include "elk.h" -#include -#include -#include -#include -#include -#include "ffi/ffi.h" -#include - -#define TAG "JS" - -typedef enum { - ArgTypeVoid, - ArgTypeUint8, - ArgTypeSint8, - ArgTypeUint16, - ArgTypeSint16, - ArgTypeUint32, - ArgTypeSint32, - ArgTypeUint64, - ArgTypeSint64, - ArgTypeFloat, - ArgTypeDouble, - ArgTypePointer, - ArgTypeString, -} ArgType; - -void test_ffi() { - ffi_cif cif; - ffi_type* args[1]; - void* values[1]; - char* s; - ffi_arg rc; - - /* Initialize the argument info vectors */ - args[0] = &ffi_type_pointer; - values[0] = &s; - - void (*fn)(void) = (void (*)(void))puts; - - /* Initialize the cif */ - if(ffi_prep_cif(&cif, FFI_DEFAULT_ABI, 1, &ffi_type_sint, args) == FFI_OK) { - s = "Hello World!"; - ffi_call(&cif, fn, &rc, values); - s = "This is cool!"; - ffi_call(&cif, fn, &rc, values); - } -} - -static ffi_type* type_from_argtype(ArgType argtype) { - switch(argtype) { - case ArgTypeVoid: - return &ffi_type_void; - case ArgTypeUint8: - return &ffi_type_uint8; - case ArgTypeSint8: - return &ffi_type_sint8; - case ArgTypeUint16: - return &ffi_type_uint16; - case ArgTypeSint16: - return &ffi_type_sint16; - case ArgTypeUint32: - return &ffi_type_uint32; - case ArgTypeSint32: - return &ffi_type_sint32; - case ArgTypeUint64: - return &ffi_type_uint64; - case ArgTypeSint64: - return &ffi_type_sint64; - case ArgTypeFloat: - return &ffi_type_float; - case ArgTypeDouble: - return &ffi_type_double; - case ArgTypePointer: - return &ffi_type_pointer; - case ArgTypeString: - return &ffi_type_pointer; - } - - return NULL; -} - -static jsval_t js_global_fficall(struct js* js, jsval_t* args, int nargs) { - Elf32_Addr addr; - const char* name = js_getstr(js, args[1], NULL); - uint32_t hash = elf_symbolname_hash(name); - if(!firmware_api_interface->resolver_callback(firmware_api_interface, hash, &addr)) { - FURI_LOG_E(TAG, "FFI: cannot find \"%s\"", name); - return js_mkundef(); - } - FURI_LOG_I(TAG, "FFI {"); - void (*fn)(void) = (void (*)(void))addr; - FURI_LOG_I(TAG, " \"%s\" = 0x%p", name, fn); - - const size_t arg_count = (nargs - 2) / 2; - FURI_LOG_I(TAG, " args count %u", arg_count); - - ffi_type* ffi_args[arg_count]; - uint64_t ffi_vals[arg_count]; - void* ffi_vals_p[arg_count]; - for(size_t i = 0; i < arg_count; i++) { - ArgType type = (ArgType)js_getnum(args[2 + i * 2]); - jsval_t arg = args[3 + i * 2]; - ffi_args[i] = type_from_argtype(type); - switch(type) { - case ArgTypeVoid: - ffi_vals[i] = 0; - FURI_LOG_I(TAG, " arg %u void", i); - break; - case ArgTypeUint8: - case ArgTypeSint8: - case ArgTypeUint16: - case ArgTypeSint16: - case ArgTypeUint32: - case ArgTypeSint32: - case ArgTypeUint64: - case ArgTypeSint64: - case ArgTypeFloat: - case ArgTypeDouble: - ffi_vals[i] = js_getnum(arg); - FURI_LOG_I(TAG, " arg %u num = %f", i, (double)ffi_vals[i]); - break; - case ArgTypePointer: - ffi_vals[i] = js_getnum(arg); - FURI_LOG_I(TAG, " arg %u ptr = 0x%p", i, (void*)(uint32_t)ffi_vals[i]); - break; - case ArgTypeString: - ffi_vals[i] = (uint32_t)js_getstr(js, arg, NULL); - FURI_LOG_I(TAG, " arg %u str = \"%s\"", i, (const char*)(uint32_t)ffi_vals[i]); - break; - default: - FURI_LOG_E(TAG, "FFI: invalid arg type %u", type); - return js_mkundef(); - } - - ffi_vals_p[i] = &ffi_vals[i]; - } - - ArgType type = (ArgType)js_getnum(args[0]); - ffi_arg rc; - ffi_cif cif; - - if(ffi_prep_cif(&cif, FFI_DEFAULT_ABI, arg_count, type_from_argtype(type), ffi_args) == - FFI_OK) { - ffi_call(&cif, fn, &rc, ffi_vals_p); - } else { - FURI_LOG_E(TAG, "FFI: ffi_prep_cif failed"); - } - - jsval_t ret; - - switch(type) { - case ArgTypeVoid: - FURI_LOG_I(TAG, " ret void"); - ret = js_mknum(0); - break; - case ArgTypeUint8: - ret = js_mknum((uint8_t)rc); - break; - case ArgTypeSint8: - ret = js_mknum((int8_t)rc); - break; - case ArgTypeUint16: - ret = js_mknum((uint16_t)rc); - break; - case ArgTypeSint16: - ret = js_mknum((int16_t)rc); - break; - case ArgTypeUint32: - ret = js_mknum((uint32_t)rc); - break; - case ArgTypeSint32: - ret = js_mknum((int32_t)rc); - break; - case ArgTypeUint64: - ret = js_mknum((uint64_t)rc); - break; - case ArgTypeSint64: - ret = js_mknum((int64_t)rc); - break; - case ArgTypeFloat: - FURI_LOG_I(TAG, " ret double %f", (double)(float)rc); - ret = js_mknum((float)rc); - break; - case ArgTypeDouble: - FURI_LOG_I(TAG, " ret double %f", (double)rc); - ret = js_mknum((double)rc); - break; - case ArgTypePointer: - FURI_LOG_I(TAG, " ret pointer 0x%p", (void*)rc); - ret = js_mknum((uint32_t)rc); - break; - case ArgTypeString: - FURI_LOG_I(TAG, " ret string \"%s\"", (char*)rc); - ret = js_mkstr(js, (char*)rc, strlen((char*)rc)); - break; - default: - FURI_LOG_E(TAG, "FFI: ret error"); - ret = js_mkundef(); - break; - } - - FURI_LOG_I(TAG, "}"); - - return ret; -} - -static jsval_t js_global_ffires(struct js* js, jsval_t* args, int nargs) { - UNUSED(js); - UNUSED(nargs); - Elf32_Addr addr; - const char* name = js_getstr(js, args[0], NULL); - uint32_t hash = elf_symbolname_hash(name); - if(!firmware_api_interface->resolver_callback(firmware_api_interface, hash, &addr)) { - FURI_LOG_E(TAG, "RES: cannot find %s", name); - return js_mkundef(); - } - - FURI_LOG_I(TAG, "RES: \"%s\" = 0x%p", name, (void*)addr); - return js_mknum((uint32_t)addr); -} - -static void notify(const NotificationSequence* sequence) { - NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); - notification_message(notification, sequence); - furi_record_close(RECORD_NOTIFICATION); -} - -static jsval_t js_global_delay(struct js* js, jsval_t* args, int nargs) { - UNUSED(js); - UNUSED(nargs); - furi_delay_ms(js_getnum(args[0])); - return js_mkundef(); -} - -static jsval_t js_led_red(struct js* js, jsval_t* args, int nargs) { - UNUSED(js); - UNUSED(args); - UNUSED(nargs); - notify(&sequence_set_only_red_255); - return js_mkundef(); -} - -static jsval_t js_led_green(struct js* js, jsval_t* args, int nargs) { - UNUSED(js); - UNUSED(args); - UNUSED(nargs); - notify(&sequence_set_only_green_255); - return js_mkundef(); -} - -static jsval_t js_led_blue(struct js* js, jsval_t* args, int nargs) { - UNUSED(js); - UNUSED(args); - UNUSED(nargs); - notify(&sequence_set_only_blue_255); - return js_mkundef(); -} - -static jsval_t js_global_print(struct js* js, jsval_t* args, int nargs) { - for(int i = 0; i < nargs; i++) { - const char* space = i == 0 ? "" : " "; - printf("%s%s", space, js_str(js, args[i])); - } - printf("\r\n"); - return js_mkundef(); -} - -static jsval_t js_global_print_pointer(struct js* js, jsval_t* args, int nargs) { - UNUSED(js); - UNUSED(nargs); - uint32_t p = (uint32_t)js_getnum(args[0]); - printf("%lu\r\n", p); - return js_mkundef(); -} - -static jsval_t js_global_require(struct js* js, jsval_t* args, int nargs) { - UNUSED(nargs); - Storage* storage = furi_record_open(RECORD_STORAGE); - jsval_t res = js_mkundef(); - File* file = storage_file_alloc(storage); - char* data = NULL; - const char* name = js_getstr(js, args[0], NULL); - - do { - if(!storage_file_open(file, name, FSAM_READ, FSOM_OPEN_EXISTING)) { - FURI_LOG_E("JS", "Cannot open %s", name); - break; - } - - size_t size = storage_file_size(file); - data = (char*)malloc(size + 1); - if(storage_file_read(file, data, size) != size) { - FURI_LOG_E("JS", "Cannot read %s", name); - break; - } - data[size] = '\0'; - res = js_eval(js, data, ~0U); - - if(js_type(res) == JS_ERR) { - FURI_LOG_E("JS", "%s: %s", name, js_str(js, res)); - } - - free(data); - } while(false); - - if(data) free(data); - storage_file_free(file); - - furi_record_close(RECORD_STORAGE); - return res; -} - -static bool js_do(const char* text) { - bool result = false; - const size_t memory_size = 16 * 1024; - uint8_t* memory = (uint8_t*)malloc(memory_size); - struct js* js = js_create(memory, memory_size); - - jsval_t global = js_glob(js); - jsval_t led = js_mkobj(js); - jsval_t arg = js_mkobj(js); - - js_set(js, global, "arg", arg); - js_set(js, arg, "none", js_mknum(ArgTypeVoid)); - js_set(js, arg, "uint8", js_mknum(ArgTypeUint8)); - js_set(js, arg, "sint8", js_mknum(ArgTypeSint8)); - js_set(js, arg, "uint16", js_mknum(ArgTypeUint16)); - js_set(js, arg, "sint16", js_mknum(ArgTypeSint16)); - js_set(js, arg, "uint32", js_mknum(ArgTypeUint32)); - js_set(js, arg, "sint32", js_mknum(ArgTypeSint32)); - js_set(js, arg, "uint64", js_mknum(ArgTypeUint64)); - js_set(js, arg, "sint64", js_mknum(ArgTypeSint64)); - js_set(js, arg, "float", js_mknum(ArgTypeFloat)); - js_set(js, arg, "double", js_mknum(ArgTypeDouble)); - js_set(js, arg, "pointer", js_mknum(ArgTypePointer)); - js_set(js, arg, "string", js_mknum(ArgTypeString)); - js_set(js, global, "fficall", js_mkfun(js_global_fficall)); - js_set(js, global, "ffires", js_mkfun(js_global_ffires)); - - js_set(js, global, "delay", js_mkfun(js_global_delay)); - js_set(js, global, "print", js_mkfun(js_global_print)); - js_set(js, global, "print_pointer", js_mkfun(js_global_print_pointer)); - js_set(js, global, "require", js_mkfun(js_global_require)); - - js_set(js, global, "led", led); - js_set(js, led, "red", js_mkfun(js_led_red)); - js_set(js, led, "green", js_mkfun(js_led_green)); - js_set(js, led, "blue", js_mkfun(js_led_blue)); - - jsval_t res = js_eval(js, text, ~0U); - - size_t total_ram = 0, min_ram = 0, stack = 0; - js_stats(js, &total_ram, &min_ram, &stack); - FURI_LOG_I("JS", "RAM: total %u, lowest free %u, C stack: %u", total_ram, min_ram, stack); - - if(js_type(res) == JS_ERR) { - FURI_LOG_E("JS", "%s", js_str(js, res)); - result = false; - } else { - result = true; - } - - free(memory); - return result; -} - -int32_t elk_js_app(void* arg) { - Storage* storage = furi_record_open(RECORD_STORAGE); - DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); - FuriString* name = furi_string_alloc_set(APP_ASSETS_PATH()); - File* file = storage_file_alloc(storage); - char* data = NULL; - - do { - if(arg != NULL && strlen(arg) > 0) { - furi_string_set(name, (const char*)arg); - } else { - DialogsFileBrowserOptions browser_options; - dialog_file_browser_set_basic_options(&browser_options, ".js", NULL); - if(!dialog_file_browser_show(dialogs, name, name, &browser_options)) break; - } - - if(!storage_file_open(file, furi_string_get_cstr(name), FSAM_READ, FSOM_OPEN_EXISTING)) - break; - size_t size = storage_file_size(file); - data = (char*)malloc(size + 1); - if(storage_file_read(file, data, size) != size) break; - data[size] = '\0'; - js_do(data); - } while(false); - - if(data) free(data); - furi_string_free(name); - storage_file_free(file); - furi_record_close(RECORD_STORAGE); - furi_record_close(RECORD_DIALOGS); - return 0; -} \ No newline at end of file diff --git a/applications/system/elk_js/ffi/ffi.c b/applications/system/elk_js/ffi/ffi.c deleted file mode 100644 index aece4ccdf5b..00000000000 --- a/applications/system/elk_js/ffi/ffi.c +++ /dev/null @@ -1,705 +0,0 @@ -/* ----------------------------------------------------------------------- - ffi.c - Copyright (c) 2011 Timothy Wall - Copyright (c) 2011 Plausible Labs Cooperative, Inc. - Copyright (c) 2011 Anthony Green - Copyright (c) 2011 Free Software Foundation - Copyright (c) 1998, 2008, 2011 Red Hat, Inc. - ARM Foreign Function Interface - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - ``Software''), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - DEALINGS IN THE SOFTWARE. - ----------------------------------------------------------------------- */ - -#include "fficonfig.h" -#include "ffi.h" -#include "ffi_common.h" -#include -#include -#include "internal.h" - -#if FFI_EXEC_TRAMPOLINE_TABLE - -#ifdef __MACH__ -#include -#endif - -#else -extern unsigned int ffi_arm_trampoline[2] FFI_HIDDEN; -#endif - -/* Forward declares. */ -static int vfp_type_p(const ffi_type*); -static void layout_vfp_args(ffi_cif*); - -static void* ffi_align(ffi_type* ty, void* p) { - /* Align if necessary */ - size_t alignment; -#ifdef _WIN32_WCE - alignment = 4; -#else - alignment = ty->alignment; - if(alignment < 4) alignment = 4; -#endif - return (void*)ALIGN(p, alignment); -} - -static size_t ffi_put_arg(ffi_type* ty, void* src, void* dst) { - size_t z = ty->size; - - switch(ty->type) { - case FFI_TYPE_SINT8: - *(UINT32*)dst = *(SINT8*)src; - break; - case FFI_TYPE_UINT8: - *(UINT32*)dst = *(UINT8*)src; - break; - case FFI_TYPE_SINT16: - *(UINT32*)dst = *(SINT16*)src; - break; - case FFI_TYPE_UINT16: - *(UINT32*)dst = *(UINT16*)src; - break; - - case FFI_TYPE_INT: - case FFI_TYPE_SINT32: - case FFI_TYPE_UINT32: - case FFI_TYPE_POINTER: - case FFI_TYPE_FLOAT: - *(UINT32*)dst = *(UINT32*)src; - break; - - case FFI_TYPE_SINT64: - case FFI_TYPE_UINT64: - case FFI_TYPE_DOUBLE: - *(UINT64*)dst = *(UINT64*)src; - break; - - case FFI_TYPE_STRUCT: - case FFI_TYPE_COMPLEX: - memcpy(dst, src, z); - break; - - default: - abort(); - } - - return ALIGN(z, 4); -} - -/* ffi_prep_args is called once stack space has been allocated - for the function's arguments. - The vfp_space parameter is the load area for VFP regs, the return - value is cif->vfp_used (word bitset of VFP regs used for passing - arguments). These are only used for the VFP hard-float ABI. -*/ -static void ffi_prep_args_SYSV(ffi_cif* cif, int flags, void* rvalue, void** avalue, char* argp) { - ffi_type** arg_types = cif->arg_types; - int i, n; - - if(flags == ARM_TYPE_STRUCT) { - *(void**)argp = rvalue; - argp += 4; - } - - for(i = 0, n = cif->nargs; i < n; i++) { - ffi_type* ty = arg_types[i]; - argp = ffi_align(ty, argp); - argp += ffi_put_arg(ty, avalue[i], argp); - } -} - -static void ffi_prep_args_VFP( - ffi_cif* cif, - int flags, - void* rvalue, - void** avalue, - char* stack, - char* vfp_space) { - ffi_type** arg_types = cif->arg_types; - int i, n, vi = 0; - char *argp, *regp, *eo_regp; - char stack_used = 0; - char done_with_regs = 0; - - /* The first 4 words on the stack are used for values - passed in core registers. */ - regp = stack; - eo_regp = argp = regp + 16; - - /* If the function returns an FFI_TYPE_STRUCT in memory, - that address is passed in r0 to the function. */ - if(flags == ARM_TYPE_STRUCT) { - *(void**)regp = rvalue; - regp += 4; - } - - for(i = 0, n = cif->nargs; i < n; i++) { - ffi_type* ty = arg_types[i]; - void* a = avalue[i]; - int is_vfp_type = vfp_type_p(ty); - - /* Allocated in VFP registers. */ - if(vi < cif->vfp_nargs && is_vfp_type) { - char* vfp_slot = vfp_space + cif->vfp_args[vi++] * 4; - ffi_put_arg(ty, a, vfp_slot); - continue; - } - /* Try allocating in core registers. */ - else if(!done_with_regs && !is_vfp_type) { - char* tregp = ffi_align(ty, regp); - size_t size = ty->size; - size = (size < 4) ? 4 : size; // pad - /* Check if there is space left in the aligned register - area to place the argument. */ - if(tregp + size <= eo_regp) { - regp = tregp + ffi_put_arg(ty, a, tregp); - done_with_regs = (regp == argp); - // ensure we did not write into the stack area - FFI_ASSERT(regp <= argp); - continue; - } - /* In case there are no arguments in the stack area yet, - the argument is passed in the remaining core registers - and on the stack. */ - else if(!stack_used) { - stack_used = 1; - done_with_regs = 1; - argp = tregp + ffi_put_arg(ty, a, tregp); - FFI_ASSERT(eo_regp < argp); - continue; - } - } - /* Base case, arguments are passed on the stack */ - stack_used = 1; - argp = ffi_align(ty, argp); - argp += ffi_put_arg(ty, a, argp); - } -} - -/* Perform machine dependent cif processing */ -ffi_status ffi_prep_cif_machdep(ffi_cif* cif) { - int flags = 0, cabi = cif->abi; - size_t bytes = cif->bytes; - - /* Map out the register placements of VFP register args. The VFP - hard-float calling conventions are slightly more sophisticated - than the base calling conventions, so we do it here instead of - in ffi_prep_args(). */ - if(cabi == FFI_VFP) layout_vfp_args(cif); - - /* Set the return type flag */ - switch(cif->rtype->type) { - case FFI_TYPE_VOID: - flags = ARM_TYPE_VOID; - break; - - case FFI_TYPE_INT: - case FFI_TYPE_UINT8: - case FFI_TYPE_SINT8: - case FFI_TYPE_UINT16: - case FFI_TYPE_SINT16: - case FFI_TYPE_UINT32: - case FFI_TYPE_SINT32: - case FFI_TYPE_POINTER: - flags = ARM_TYPE_INT; - break; - - case FFI_TYPE_SINT64: - case FFI_TYPE_UINT64: - flags = ARM_TYPE_INT64; - break; - - case FFI_TYPE_FLOAT: - flags = (cabi == FFI_VFP ? ARM_TYPE_VFP_S : ARM_TYPE_INT); - break; - case FFI_TYPE_DOUBLE: - flags = (cabi == FFI_VFP ? ARM_TYPE_VFP_D : ARM_TYPE_INT64); - break; - - case FFI_TYPE_STRUCT: - case FFI_TYPE_COMPLEX: - if(cabi == FFI_VFP) { - int h = vfp_type_p(cif->rtype); - - flags = ARM_TYPE_VFP_N; - if(h == 0x100 + FFI_TYPE_FLOAT) flags = ARM_TYPE_VFP_S; - if(h == 0x100 + FFI_TYPE_DOUBLE) flags = ARM_TYPE_VFP_D; - if(h != 0) break; - } - - /* A Composite Type not larger than 4 bytes is returned in r0. - A Composite Type larger than 4 bytes, or whose size cannot - be determined statically ... is stored in memory at an - address passed [in r0]. */ - if(cif->rtype->size <= 4) - flags = ARM_TYPE_INT; - else { - flags = ARM_TYPE_STRUCT; - bytes += 4; - } - break; - - default: - abort(); - } - - /* Round the stack up to a multiple of 8 bytes. This isn't needed - everywhere, but it is on some platforms, and it doesn't harm anything - when it isn't needed. */ - bytes = ALIGN(bytes, 8); - - /* Minimum stack space is the 4 register arguments that we pop. */ - if(bytes < 4 * 4) bytes = 4 * 4; - - cif->bytes = bytes; - cif->flags = flags; - - return FFI_OK; -} - -/* Perform machine dependent cif processing for variadic calls */ -ffi_status - ffi_prep_cif_machdep_var(ffi_cif* cif, unsigned int nfixedargs, unsigned int ntotalargs) { - UNUSED(nfixedargs); - UNUSED(ntotalargs); - /* VFP variadic calls actually use the SYSV ABI */ - if(cif->abi == FFI_VFP) cif->abi = FFI_SYSV; - - return ffi_prep_cif_machdep(cif); -} - -/* Prototypes for assembly functions, in sysv.S. */ - -struct call_frame { - void* fp; - void* lr; - void* rvalue; - int flags; - void* closure; -}; - -extern void ffi_call_SYSV(void* stack, struct call_frame*, void (*fn)(void)) FFI_HIDDEN; -extern void ffi_call_VFP(void* vfp_space, struct call_frame*, void (*fn)(void), unsigned vfp_used) - FFI_HIDDEN; - -static void - ffi_call_int(ffi_cif* cif, void (*fn)(void), void* rvalue, void** avalue, void* closure) { - int flags = cif->flags; - ffi_type* rtype = cif->rtype; - size_t bytes, rsize, vfp_size; - char *stack, *vfp_space, *new_rvalue; - struct call_frame* frame; - - rsize = 0; - if(rvalue == NULL) { - /* If the return value is a struct and we don't have a return - value address then we need to make one. Otherwise the return - value is in registers and we can ignore them. */ - if(flags == ARM_TYPE_STRUCT) - rsize = rtype->size; - else - flags = ARM_TYPE_VOID; - } else if(flags == ARM_TYPE_VFP_N) { - /* Largest case is double x 4. */ - rsize = 32; - } else if(flags == ARM_TYPE_INT && rtype->type == FFI_TYPE_STRUCT) - rsize = 4; - - /* Largest case. */ - vfp_size = (cif->abi == FFI_VFP && cif->vfp_used ? 8 * 8 : 0); - - bytes = cif->bytes; - stack = alloca(vfp_size + bytes + sizeof(struct call_frame) + rsize); - - vfp_space = NULL; - if(vfp_size) { - vfp_space = stack; - stack += vfp_size; - } - - frame = (struct call_frame*)(stack + bytes); - - new_rvalue = rvalue; - if(rsize) new_rvalue = (void*)(frame + 1); - - frame->rvalue = new_rvalue; - frame->flags = flags; - frame->closure = closure; - - if(vfp_space) { - ffi_prep_args_VFP(cif, flags, new_rvalue, avalue, stack, vfp_space); - ffi_call_VFP(vfp_space, frame, fn, cif->vfp_used); - } else { - ffi_prep_args_SYSV(cif, flags, new_rvalue, avalue, stack); - ffi_call_SYSV(stack, frame, fn); - } - - if(rvalue && rvalue != new_rvalue) memcpy(rvalue, new_rvalue, rtype->size); -} - -void ffi_call(ffi_cif* cif, void (*fn)(void), void* rvalue, void** avalue) { - ffi_call_int(cif, fn, rvalue, avalue, NULL); -} - -void ffi_call_go(ffi_cif* cif, void (*fn)(void), void* rvalue, void** avalue, void* closure) { - ffi_call_int(cif, fn, rvalue, avalue, closure); -} - -static void* ffi_prep_incoming_args_SYSV(ffi_cif* cif, void* rvalue, char* argp, void** avalue) { - ffi_type** arg_types = cif->arg_types; - int i, n; - - if(cif->flags == ARM_TYPE_STRUCT) { - rvalue = *(void**)argp; - argp += 4; - } - - for(i = 0, n = cif->nargs; i < n; i++) { - ffi_type* ty = arg_types[i]; - size_t z = ty->size; - - argp = ffi_align(ty, argp); - avalue[i] = (void*)argp; - argp += z; - } - - return rvalue; -} - -static void* ffi_prep_incoming_args_VFP( - ffi_cif* cif, - void* rvalue, - char* stack, - char* vfp_space, - void** avalue) { - ffi_type** arg_types = cif->arg_types; - int i, n, vi = 0; - char *argp, *regp, *eo_regp; - char done_with_regs = 0; - char stack_used = 0; - - regp = stack; - eo_regp = argp = regp + 16; - - if(cif->flags == ARM_TYPE_STRUCT) { - rvalue = *(void**)regp; - regp += 4; - } - - for(i = 0, n = cif->nargs; i < n; i++) { - ffi_type* ty = arg_types[i]; - int is_vfp_type = vfp_type_p(ty); - size_t z = ty->size; - - if(vi < cif->vfp_nargs && is_vfp_type) { - avalue[i] = vfp_space + cif->vfp_args[vi++] * 4; - continue; - } else if(!done_with_regs && !is_vfp_type) { - char* tregp = ffi_align(ty, regp); - - z = (z < 4) ? 4 : z; // pad - - /* If the arguments either fits into the registers or uses registers - and stack, while we haven't read other things from the stack */ - if(tregp + z <= eo_regp || !stack_used) { - /* Because we're little endian, this is what it turns into. */ - avalue[i] = (void*)tregp; - regp = tregp + z; - - /* If we read past the last core register, make sure we - have not read from the stack before and continue - reading after regp. */ - if(regp > eo_regp) { - FFI_ASSERT(!stack_used); - argp = regp; - } - if(regp >= eo_regp) { - done_with_regs = 1; - stack_used = 1; - } - continue; - } - } - - stack_used = 1; - argp = ffi_align(ty, argp); - avalue[i] = (void*)argp; - argp += z; - } - - return rvalue; -} - -struct closure_frame { - char vfp_space[8 * 8] __attribute__((aligned(8))); - char result[8 * 4]; - char argp[]; -}; - -int FFI_HIDDEN ffi_closure_inner_SYSV( - ffi_cif* cif, - void (*fun)(ffi_cif*, void*, void**, void*), - void* user_data, - struct closure_frame* frame) { - void** avalue = (void**)alloca(cif->nargs * sizeof(void*)); - void* rvalue = ffi_prep_incoming_args_SYSV(cif, frame->result, frame->argp, avalue); - fun(cif, rvalue, avalue, user_data); - return cif->flags; -} - -int FFI_HIDDEN ffi_closure_inner_VFP( - ffi_cif* cif, - void (*fun)(ffi_cif*, void*, void**, void*), - void* user_data, - struct closure_frame* frame) { - void** avalue = (void**)alloca(cif->nargs * sizeof(void*)); - void* rvalue = - ffi_prep_incoming_args_VFP(cif, frame->result, frame->argp, frame->vfp_space, avalue); - fun(cif, rvalue, avalue, user_data); - return cif->flags; -} - -void ffi_closure_SYSV(void) FFI_HIDDEN; -void ffi_closure_VFP(void) FFI_HIDDEN; -void ffi_go_closure_SYSV(void) FFI_HIDDEN; -void ffi_go_closure_VFP(void) FFI_HIDDEN; - -/* the cif must already be prep'ed */ -#if 0 -ffi_status -ffi_prep_closure_loc (ffi_closure * closure, - ffi_cif * cif, - void (*fun) (ffi_cif *, void *, void **, void *), - void *user_data, void *codeloc) -{ - void (*closure_func) (void) = ffi_closure_SYSV; - - if (cif->abi == FFI_VFP) - { - /* We only need take the vfp path if there are vfp arguments. */ - if (cif->vfp_used) - closure_func = ffi_closure_VFP; - } - else if (cif->abi != FFI_SYSV) - return FFI_BAD_ABI; - -#if FFI_EXEC_TRAMPOLINE_TABLE - void **config = (void **)((uint8_t *)codeloc - PAGE_MAX_SIZE); - config[0] = closure; - config[1] = closure_func; -#else - memcpy (closure->tramp, ffi_arm_trampoline, 8); - __clear_cache(closure->tramp, closure->tramp + 8); /* clear data map */ - __clear_cache(codeloc, codeloc + 8); /* clear insn map */ - *(void (**)(void))(closure->tramp + 8) = closure_func; -#endif - - closure->cif = cif; - closure->fun = fun; - closure->user_data = user_data; - - return FFI_OK; -} - -ffi_status -ffi_prep_go_closure (ffi_go_closure *closure, ffi_cif *cif, - void (*fun) (ffi_cif *, void *, void **, void *)) -{ - void (*closure_func) (void) = ffi_go_closure_SYSV; - - if (cif->abi == FFI_VFP) - { - /* We only need take the vfp path if there are vfp arguments. */ - if (cif->vfp_used) - closure_func = ffi_go_closure_VFP; - } - else if (cif->abi != FFI_SYSV) - return FFI_BAD_ABI; - - closure->tramp = closure_func; - closure->cif = cif; - closure->fun = fun; - - return FFI_OK; -} -#endif - -/* Below are routines for VFP hard-float support. */ - -/* A subroutine of vfp_type_p. Given a structure type, return the type code - of the first non-structure element. Recurse for structure elements. - Return -1 if the structure is in fact empty, i.e. no nested elements. */ - -static int is_hfa0(const ffi_type* ty) { - ffi_type** elements = ty->elements; - int i, ret = -1; - - if(elements != NULL) - for(i = 0; elements[i]; ++i) { - ret = elements[i]->type; - if(ret == FFI_TYPE_STRUCT || ret == FFI_TYPE_COMPLEX) { - ret = is_hfa0(elements[i]); - if(ret < 0) continue; - } - break; - } - - return ret; -} - -/* A subroutine of vfp_type_p. Given a structure type, return true if all - of the non-structure elements are the same as CANDIDATE. */ - -static int is_hfa1(const ffi_type* ty, int candidate) { - ffi_type** elements = ty->elements; - int i; - - if(elements != NULL) - for(i = 0; elements[i]; ++i) { - int t = elements[i]->type; - if(t == FFI_TYPE_STRUCT || t == FFI_TYPE_COMPLEX) { - if(!is_hfa1(elements[i], candidate)) return 0; - } else if(t != candidate) - return 0; - } - - return 1; -} - -/* Determine if TY is an homogenous floating point aggregate (HFA). - That is, a structure consisting of 1 to 4 members of all the same type, - where that type is a floating point scalar. - Returns non-zero iff TY is an HFA. The result is an encoded value where - bits 0-7 contain the type code, and bits 8-10 contain the element count. */ - -static int vfp_type_p(const ffi_type* ty) { - ffi_type** elements; - int candidate, i; - size_t size, ele_count; - - /* Quickest tests first. */ - candidate = ty->type; - switch(ty->type) { - default: - return 0; - case FFI_TYPE_FLOAT: - case FFI_TYPE_DOUBLE: - ele_count = 1; - goto done; - case FFI_TYPE_COMPLEX: - candidate = ty->elements[0]->type; - if(candidate != FFI_TYPE_FLOAT && candidate != FFI_TYPE_DOUBLE) return 0; - ele_count = 2; - goto done; - case FFI_TYPE_STRUCT: - break; - } - - /* No HFA types are smaller than 4 bytes, or larger than 32 bytes. */ - size = ty->size; - if(size < 4 || size > 32) return 0; - - /* Find the type of the first non-structure member. */ - elements = ty->elements; - candidate = elements[0]->type; - if(candidate == FFI_TYPE_STRUCT || candidate == FFI_TYPE_COMPLEX) { - for(i = 0;; ++i) { - candidate = is_hfa0(elements[i]); - if(candidate >= 0) break; - } - } - - /* If the first member is not a floating point type, it's not an HFA. - Also quickly re-check the size of the structure. */ - switch(candidate) { - case FFI_TYPE_FLOAT: - ele_count = size / sizeof(float); - if(size != ele_count * sizeof(float)) return 0; - break; - case FFI_TYPE_DOUBLE: - ele_count = size / sizeof(double); - if(size != ele_count * sizeof(double)) return 0; - break; - default: - return 0; - } - if(ele_count > 4) return 0; - - /* Finally, make sure that all scalar elements are the same type. */ - for(i = 0; elements[i]; ++i) { - int t = elements[i]->type; - if(t == FFI_TYPE_STRUCT || t == FFI_TYPE_COMPLEX) { - if(!is_hfa1(elements[i], candidate)) return 0; - } else if(t != candidate) - return 0; - } - - /* All tests succeeded. Encode the result. */ -done: - return (ele_count << 8) | candidate; -} - -static int place_vfp_arg(ffi_cif* cif, int h) { - unsigned short reg = cif->vfp_reg_free; - int align = 1, nregs = h >> 8; - - if((h & 0xff) == FFI_TYPE_DOUBLE) align = 2, nregs *= 2; - - /* Align register number. */ - if((reg & 1) && align == 2) reg++; - - while(reg + nregs <= 16) { - int s, new_used = 0; - for(s = reg; s < reg + nregs; s++) { - new_used |= (1 << s); - if(cif->vfp_used & (1 << s)) { - reg += align; - goto next_reg; - } - } - /* Found regs to allocate. */ - cif->vfp_used |= new_used; - cif->vfp_args[cif->vfp_nargs++] = reg; - - /* Update vfp_reg_free. */ - if(cif->vfp_used & (1 << cif->vfp_reg_free)) { - reg += nregs; - while(cif->vfp_used & (1 << reg)) reg += 1; - cif->vfp_reg_free = reg; - } - return 0; - next_reg:; - } - // done, mark all regs as used - cif->vfp_reg_free = 16; - cif->vfp_used = 0xFFFF; - return 1; -} - -static void layout_vfp_args(ffi_cif* cif) { - unsigned int i; - /* Init VFP fields */ - cif->vfp_used = 0; - cif->vfp_nargs = 0; - cif->vfp_reg_free = 0; - memset(cif->vfp_args, -1, 16); /* Init to -1. */ - - for(i = 0; i < cif->nargs; i++) { - int h = vfp_type_p(cif->arg_types[i]); - if(h && place_vfp_arg(cif, h) == 1) break; - } -} \ No newline at end of file diff --git a/applications/system/elk_js/ffi/ffi.h b/applications/system/elk_js/ffi/ffi.h deleted file mode 100644 index 0d299bbc16c..00000000000 --- a/applications/system/elk_js/ffi/ffi.h +++ /dev/null @@ -1,452 +0,0 @@ -/* -----------------------------------------------------------------*-C-*- - libffi 3.2.1 - Copyright (c) 2011, 2014 Anthony Green - - Copyright (c) 1996-2003, 2007, 2008 Red Hat, Inc. - Permission is hereby granted, free of charge, to any person - obtaining a copy of this software and associated documentation - files (the ``Software''), to deal in the Software without - restriction, including without limitation the rights to use, copy, - modify, merge, publish, distribute, sublicense, and/or sell copies - of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be - included in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - DEALINGS IN THE SOFTWARE. - ----------------------------------------------------------------------- */ - -/* ------------------------------------------------------------------- - The basic API is described in the README file. - The raw API is designed to bypass some of the argument packing - and unpacking on architectures for which it can be avoided. - The closure API allows interpreted functions to be packaged up - inside a C function pointer, so that they can be called as C functions, - with no understanding on the client side that they are interpreted. - It can also be used in other cases in which it is necessary to package - up a user specified parameter and a function pointer as a single - function pointer. - The closure API must be implemented in order to get its functionality, - e.g. for use by gij. Routines are provided to emulate the raw API - if the underlying platform doesn't allow faster implementation. - More details on the raw and cloure API can be found in: - http://gcc.gnu.org/ml/java/1999-q3/msg00138.html - and - http://gcc.gnu.org/ml/java/1999-q3/msg00174.html - -------------------------------------------------------------------- */ - -#ifndef LIBFFI_H -#define LIBFFI_H - -#ifdef __cplusplus -extern "C" { -#endif - -/* Specify which architecture libffi is configured for. */ -#ifndef ARM -#define ARM -#endif - -/* ---- System configuration information --------------------------------- */ - -#include "ffitarget.h" - -#ifndef LIBFFI_ASM - -#if defined(_MSC_VER) && !defined(__clang__) -#define __attribute__(X) -#endif - -#include -#include - -/* LONG_LONG_MAX is not always defined (not if STRICT_ANSI, for example). - But we can find it either under the correct ANSI name, or under GNU - C's internal name. */ - -#define FFI_64_BIT_MAX 9223372036854775807 - -#ifdef LONG_LONG_MAX -#define FFI_LONG_LONG_MAX LONG_LONG_MAX -#else -#ifdef LLONG_MAX -#define FFI_LONG_LONG_MAX LLONG_MAX -#ifdef _AIX52 /* or newer has C99 LLONG_MAX */ -#undef FFI_64_BIT_MAX -#define FFI_64_BIT_MAX 9223372036854775807LL -#endif /* _AIX52 or newer */ -#else -#ifdef __GNUC__ -#define FFI_LONG_LONG_MAX __LONG_LONG_MAX__ -#endif -#ifdef _AIX /* AIX 5.1 and earlier have LONGLONG_MAX */ -#ifndef __PPC64__ -#if defined(__IBMC__) || defined(__IBMCPP__) -#define FFI_LONG_LONG_MAX LONGLONG_MAX -#endif -#endif /* __PPC64__ */ -#undef FFI_64_BIT_MAX -#define FFI_64_BIT_MAX 9223372036854775807LL -#endif -#endif -#endif - -/* The closure code assumes that this works on pointers, i.e. a size_t */ -/* can hold a pointer. */ - -typedef struct _ffi_type { - size_t size; - unsigned short alignment; - unsigned short type; - struct _ffi_type** elements; -} ffi_type; - -#ifndef LIBFFI_HIDE_BASIC_TYPES -#if SCHAR_MAX == 127 -#define ffi_type_uchar ffi_type_uint8 -#define ffi_type_schar ffi_type_sint8 -#else -#error "char size not supported" -#endif - -#if SHRT_MAX == 32767 -#define ffi_type_ushort ffi_type_uint16 -#define ffi_type_sshort ffi_type_sint16 -#elif SHRT_MAX == 2147483647 -#define ffi_type_ushort ffi_type_uint32 -#define ffi_type_sshort ffi_type_sint32 -#else -#error "short size not supported" -#endif - -#if INT_MAX == 32767 -#define ffi_type_uint ffi_type_uint16 -#define ffi_type_sint ffi_type_sint16 -#elif INT_MAX == 2147483647 -#define ffi_type_uint ffi_type_uint32 -#define ffi_type_sint ffi_type_sint32 -#elif INT_MAX == 9223372036854775807 -#define ffi_type_uint ffi_type_uint64 -#define ffi_type_sint ffi_type_sint64 -#else -#error "int size not supported" -#endif - -#if LONG_MAX == 2147483647 -#if FFI_LONG_LONG_MAX != FFI_64_BIT_MAX -#error "no 64-bit data type supported" -#endif -#elif LONG_MAX != FFI_64_BIT_MAX -#error "long size not supported" -#endif - -#if LONG_MAX == 2147483647 -#define ffi_type_ulong ffi_type_uint32 -#define ffi_type_slong ffi_type_sint32 -#elif LONG_MAX == FFI_64_BIT_MAX -#define ffi_type_ulong ffi_type_uint64 -#define ffi_type_slong ffi_type_sint64 -#else -#error "long size not supported" -#endif - -/* Need minimal decorations for DLLs to works on Windows. */ -/* GCC has autoimport and autoexport. Rely on Libtool to */ -/* help MSVC export from a DLL, but always declare data */ -/* to be imported for MSVC clients. This costs an extra */ -/* indirection for MSVC clients using the static version */ -/* of the library, but don't worry about that. Besides, */ -/* as a workaround, they can define FFI_BUILDING if they */ -/* *know* they are going to link with the static library. */ -#if defined _MSC_VER && !defined FFI_BUILDING -#define FFI_EXTERN extern __declspec(dllimport) -#else -#define FFI_EXTERN extern -#endif - -/* These are defined in types.c */ -FFI_EXTERN ffi_type ffi_type_void; -FFI_EXTERN ffi_type ffi_type_uint8; -FFI_EXTERN ffi_type ffi_type_sint8; -FFI_EXTERN ffi_type ffi_type_uint16; -FFI_EXTERN ffi_type ffi_type_sint16; -FFI_EXTERN ffi_type ffi_type_uint32; -FFI_EXTERN ffi_type ffi_type_sint32; -FFI_EXTERN ffi_type ffi_type_uint64; -FFI_EXTERN ffi_type ffi_type_sint64; -FFI_EXTERN ffi_type ffi_type_float; -FFI_EXTERN ffi_type ffi_type_double; -FFI_EXTERN ffi_type ffi_type_pointer; - -#if 0 -FFI_EXTERN ffi_type ffi_type_longdouble; -#else -#define ffi_type_longdouble ffi_type_double -#endif - -#ifdef FFI_TARGET_HAS_COMPLEX_TYPE -FFI_EXTERN ffi_type ffi_type_complex_float; -FFI_EXTERN ffi_type ffi_type_complex_double; -#if 0 -FFI_EXTERN ffi_type ffi_type_complex_longdouble; -#else -#define ffi_type_complex_longdouble ffi_type_complex_double -#endif -#endif -#endif /* LIBFFI_HIDE_BASIC_TYPES */ - -typedef enum { FFI_OK = 0, FFI_BAD_TYPEDEF, FFI_BAD_ABI } ffi_status; - -typedef unsigned FFI_TYPE; - -typedef struct { - ffi_abi abi; - unsigned nargs; - ffi_type** arg_types; - ffi_type* rtype; - unsigned bytes; - unsigned flags; -#ifdef FFI_EXTRA_CIF_FIELDS - FFI_EXTRA_CIF_FIELDS; -#endif -} ffi_cif; - -#if 0 -/* Used to adjust size/alignment of ffi types. */ -void ffi_prep_types (ffi_abi abi); -#endif - -/* ---- Definitions for the raw API -------------------------------------- */ - -#ifndef FFI_SIZEOF_ARG -#if LONG_MAX == 2147483647 -#define FFI_SIZEOF_ARG 4 -#elif LONG_MAX == FFI_64_BIT_MAX -#define FFI_SIZEOF_ARG 8 -#endif -#endif - -#ifndef FFI_SIZEOF_JAVA_RAW -#define FFI_SIZEOF_JAVA_RAW FFI_SIZEOF_ARG -#endif - -typedef union { - ffi_sarg sint; - ffi_arg uint; - float flt; - char data[FFI_SIZEOF_ARG]; - void* ptr; -} ffi_raw; - -#if FFI_SIZEOF_JAVA_RAW == 4 && FFI_SIZEOF_ARG == 8 -/* This is a special case for mips64/n32 ABI (and perhaps others) where - sizeof(void *) is 4 and FFI_SIZEOF_ARG is 8. */ -typedef union { - signed int sint; - unsigned int uint; - float flt; - char data[FFI_SIZEOF_JAVA_RAW]; - void* ptr; -} ffi_java_raw; -#else -typedef ffi_raw ffi_java_raw; -#endif - -void ffi_raw_call(ffi_cif* cif, void (*fn)(void), void* rvalue, ffi_raw* avalue); - -void ffi_ptrarray_to_raw(ffi_cif* cif, void** args, ffi_raw* raw); -void ffi_raw_to_ptrarray(ffi_cif* cif, ffi_raw* raw, void** args); -size_t ffi_raw_size(ffi_cif* cif); - -/* This is analogous to the raw API, except it uses Java parameter */ -/* packing, even on 64-bit machines. I.e. on 64-bit machines */ -/* longs and doubles are followed by an empty 64-bit word. */ - -void ffi_java_raw_call(ffi_cif* cif, void (*fn)(void), void* rvalue, ffi_java_raw* avalue); - -void ffi_java_ptrarray_to_raw(ffi_cif* cif, void** args, ffi_java_raw* raw); -void ffi_java_raw_to_ptrarray(ffi_cif* cif, ffi_java_raw* raw, void** args); -size_t ffi_java_raw_size(ffi_cif* cif); - -/* ---- Definitions for closures ----------------------------------------- */ - -#undef FFI_CLOSURES - -#if FFI_CLOSURES - -#ifdef _MSC_VER -__declspec(align(8)) -#endif - typedef struct { -#if 0 - void *trampoline_table; - void *trampoline_table_entry; -#else - char tramp[FFI_TRAMPOLINE_SIZE]; -#endif - ffi_cif* cif; - void (*fun)(ffi_cif*, void*, void**, void*); - void* user_data; -#ifdef __GNUC__ -} ffi_closure __attribute__((aligned(8))); -#else -} ffi_closure; -#ifdef __sgi -#pragma pack 0 -#endif -#endif - -void* ffi_closure_alloc(size_t size, void** code); -void ffi_closure_free(void*); - -ffi_status ffi_prep_closure( - ffi_closure*, - ffi_cif*, - void (*fun)(ffi_cif*, void*, void**, void*), - void* user_data); - -ffi_status ffi_prep_closure_loc( - ffi_closure*, - ffi_cif*, - void (*fun)(ffi_cif*, void*, void**, void*), - void* user_data, - void* codeloc); - -#ifdef __sgi -#pragma pack 8 -#endif -typedef struct { -#if 0 - void *trampoline_table; - void *trampoline_table_entry; -#else - char tramp[FFI_TRAMPOLINE_SIZE]; -#endif - ffi_cif* cif; - -#if !FFI_NATIVE_RAW_API - - /* if this is enabled, then a raw closure has the same layout - as a regular closure. We use this to install an intermediate - handler to do the transaltion, void** -> ffi_raw*. */ - - void (*translate_args)(ffi_cif*, void*, void**, void*); - void* this_closure; - -#endif - - void (*fun)(ffi_cif*, void*, ffi_raw*, void*); - void* user_data; - -} ffi_raw_closure; - -typedef struct { -#if 0 - void *trampoline_table; - void *trampoline_table_entry; -#else - char tramp[FFI_TRAMPOLINE_SIZE]; -#endif - - ffi_cif* cif; - -#if !FFI_NATIVE_RAW_API - - /* if this is enabled, then a raw closure has the same layout - as a regular closure. We use this to install an intermediate - handler to do the transaltion, void** -> ffi_raw*. */ - - void (*translate_args)(ffi_cif*, void*, void**, void*); - void* this_closure; - -#endif - - void (*fun)(ffi_cif*, void*, ffi_java_raw*, void*); - void* user_data; - -} ffi_java_raw_closure; - -ffi_status ffi_prep_raw_closure( - ffi_raw_closure*, - ffi_cif* cif, - void (*fun)(ffi_cif*, void*, ffi_raw*, void*), - void* user_data); - -ffi_status ffi_prep_raw_closure_loc( - ffi_raw_closure*, - ffi_cif* cif, - void (*fun)(ffi_cif*, void*, ffi_raw*, void*), - void* user_data, - void* codeloc); - -ffi_status ffi_prep_java_raw_closure( - ffi_java_raw_closure*, - ffi_cif* cif, - void (*fun)(ffi_cif*, void*, ffi_java_raw*, void*), - void* user_data); - -ffi_status ffi_prep_java_raw_closure_loc( - ffi_java_raw_closure*, - ffi_cif* cif, - void (*fun)(ffi_cif*, void*, ffi_java_raw*, void*), - void* user_data, - void* codeloc); - -#endif /* FFI_CLOSURES */ - -/* ---- Public interface definition -------------------------------------- */ - -ffi_status - ffi_prep_cif(ffi_cif* cif, ffi_abi abi, unsigned int nargs, ffi_type* rtype, ffi_type** atypes); - -ffi_status ffi_prep_cif_var( - ffi_cif* cif, - ffi_abi abi, - unsigned int nfixedargs, - unsigned int ntotalargs, - ffi_type* rtype, - ffi_type** atypes); - -void ffi_call(ffi_cif* cif, void (*fn)(void), void* rvalue, void** avalue); - -/* Useful for eliminating compiler warnings */ -#define FFI_FN(f) ((void (*)(void))f) - -/* ---- Definitions shared with assembly code ---------------------------- */ - -#endif - -/* If these change, update src/mips/ffitarget.h. */ -#define FFI_TYPE_VOID 0 -#define FFI_TYPE_INT 1 -#define FFI_TYPE_FLOAT 2 -#define FFI_TYPE_DOUBLE 3 -#if 0 -#define FFI_TYPE_LONGDOUBLE 4 -#else -#define FFI_TYPE_LONGDOUBLE FFI_TYPE_DOUBLE -#endif -#define FFI_TYPE_UINT8 5 -#define FFI_TYPE_SINT8 6 -#define FFI_TYPE_UINT16 7 -#define FFI_TYPE_SINT16 8 -#define FFI_TYPE_UINT32 9 -#define FFI_TYPE_SINT32 10 -#define FFI_TYPE_UINT64 11 -#define FFI_TYPE_SINT64 12 -#define FFI_TYPE_STRUCT 13 -#define FFI_TYPE_POINTER 14 -#define FFI_TYPE_COMPLEX 15 - -/* This should always refer to the last type code (for sanity checks) */ -#define FFI_TYPE_LAST FFI_TYPE_COMPLEX - -#ifdef __cplusplus -} -#endif - -#endif \ No newline at end of file diff --git a/applications/system/elk_js/ffi/ffi_cfi.h b/applications/system/elk_js/ffi/ffi_cfi.h deleted file mode 100644 index d734ed928f4..00000000000 --- a/applications/system/elk_js/ffi/ffi_cfi.h +++ /dev/null @@ -1,54 +0,0 @@ -/* ----------------------------------------------------------------------- - ffi_cfi.h - Copyright (c) 2014 Red Hat, Inc. - Conditionally assemble cfi directives. Only necessary for building libffi. - ----------------------------------------------------------------------- */ - -#ifndef FFI_CFI_H -#define FFI_CFI_H - -#ifdef HAVE_AS_CFI_PSEUDO_OP - -#define cfi_startproc .cfi_startproc -#define cfi_endproc .cfi_endproc -#define cfi_def_cfa(reg, off) .cfi_def_cfa reg, off -#define cfi_def_cfa_register(reg) .cfi_def_cfa_register reg -#define cfi_def_cfa_offset(off) .cfi_def_cfa_offset off -#define cfi_adjust_cfa_offset(off) .cfi_adjust_cfa_offset off -#define cfi_offset(reg, off) .cfi_offset reg, off -#define cfi_rel_offset(reg, off) .cfi_rel_offset reg, off -#define cfi_register(r1, r2) .cfi_register r1, r2 -#define cfi_return_column(reg) .cfi_return_column reg -#define cfi_restore(reg) .cfi_restore reg -#define cfi_same_value(reg) .cfi_same_value reg -#define cfi_undefined(reg) .cfi_undefined reg -#define cfi_remember_state .cfi_remember_state -#define cfi_restore_state .cfi_restore_state -#define cfi_window_save .cfi_window_save -#define cfi_personality(enc, exp) .cfi_personality enc, exp -#define cfi_lsda(enc, exp) .cfi_lsda enc, exp -#define cfi_escape(...) .cfi_escape __VA_ARGS__ - -#else - -#define cfi_startproc -#define cfi_endproc -#define cfi_def_cfa(reg, off) -#define cfi_def_cfa_register(reg) -#define cfi_def_cfa_offset(off) -#define cfi_adjust_cfa_offset(off) -#define cfi_offset(reg, off) -#define cfi_rel_offset(reg, off) -#define cfi_register(r1, r2) -#define cfi_return_column(reg) -#define cfi_restore(reg) -#define cfi_same_value(reg) -#define cfi_undefined(reg) -#define cfi_remember_state -#define cfi_restore_state -#define cfi_window_save -#define cfi_personality(enc, exp) -#define cfi_lsda(enc, exp) -#define cfi_escape(...) - -#endif /* HAVE_AS_CFI_PSEUDO_OP */ -#endif /* FFI_CFI_H */ \ No newline at end of file diff --git a/applications/system/elk_js/ffi/ffi_common.h b/applications/system/elk_js/ffi/ffi_common.h deleted file mode 100644 index 995b9e1e366..00000000000 --- a/applications/system/elk_js/ffi/ffi_common.h +++ /dev/null @@ -1,146 +0,0 @@ -/* ----------------------------------------------------------------------- - ffi_common.h - Copyright (C) 2011, 2012, 2013 Anthony Green - Copyright (C) 2007 Free Software Foundation, Inc - Copyright (c) 1996 Red Hat, Inc. - - Common internal definitions and macros. Only necessary for building - libffi. - ----------------------------------------------------------------------- */ - -#ifndef FFI_COMMON_H -#define FFI_COMMON_H - -#ifdef __cplusplus -extern "C" { -#endif - -#include "fficonfig.h" - -/* Do not move this. Some versions of AIX are very picky about where - this is positioned. */ -#ifdef __GNUC__ -#if HAVE_ALLOCA_H -#include -#else -/* mingw64 defines this already in malloc.h. */ -#ifndef alloca -#define alloca __builtin_alloca -#endif -#endif -#define MAYBE_UNUSED __attribute__((__unused__)) -#else -#define MAYBE_UNUSED -#if HAVE_ALLOCA_H -#include -#else -#ifdef _AIX -#pragma alloca -#else -#ifndef alloca /* predefined by HP cc +Olibcalls */ -#ifdef _MSC_VER -#define alloca _alloca -#else -char* alloca(); -#endif -#endif -#endif -#endif -#endif - -/* Check for the existence of memcpy. */ -#if STDC_HEADERS -#include -#else -#ifndef HAVE_MEMCPY -#define memcpy(d, s, n) bcopy((s), (d), (n)) -#endif -#endif - -#if defined(FFI_DEBUG) -#include -#endif - -#ifdef FFI_DEBUG -void ffi_assert(char* expr, char* file, int line); -void ffi_stop_here(void); -void ffi_type_test(ffi_type* a, char* file, int line); - -#define FFI_ASSERT(x) ((x) ? (void)0 : ffi_assert(#x, __FILE__, __LINE__)) -#define FFI_ASSERT_AT(x, f, l) ((x) ? 0 : ffi_assert(#x, (f), (l))) -#define FFI_ASSERT_VALID_TYPE(x) ffi_type_test(x, __FILE__, __LINE__) -#else -#define FFI_ASSERT(x) -#define FFI_ASSERT_AT(x, f, l) -#define FFI_ASSERT_VALID_TYPE(x) -#endif - -#define ALIGN(v, a) (((((size_t)(v)) - 1) | ((a)-1)) + 1) -#define ALIGN_DOWN(v, a) (((size_t)(v)) & -a) - -/* Perform machine dependent cif processing */ -ffi_status ffi_prep_cif_machdep(ffi_cif* cif); -ffi_status - ffi_prep_cif_machdep_var(ffi_cif* cif, unsigned int nfixedargs, unsigned int ntotalargs); - -#if HAVE_LONG_DOUBLE_VARIANT -/* Used to adjust size/alignment of ffi types. */ -void ffi_prep_types(ffi_abi abi); -#endif - -/* Used internally, but overridden by some architectures */ -ffi_status ffi_prep_cif_core( - ffi_cif* cif, - ffi_abi abi, - unsigned int isvariadic, - unsigned int nfixedargs, - unsigned int ntotalargs, - ffi_type* rtype, - ffi_type** atypes); - -/* Extended cif, used in callback from assembly routine */ -typedef struct { - ffi_cif* cif; - void* rvalue; - void** avalue; -} extended_cif; - -/* Terse sized type definitions. */ -#if defined(_MSC_VER) || defined(__sgi) || defined(__SUNPRO_C) -typedef unsigned char UINT8; -typedef signed char SINT8; -typedef unsigned short UINT16; -typedef signed short SINT16; -typedef unsigned int UINT32; -typedef signed int SINT32; -#ifdef _MSC_VER -typedef unsigned __int64 UINT64; -typedef signed __int64 SINT64; -#else -#include -typedef uint64_t UINT64; -typedef int64_t SINT64; -#endif -#else -typedef unsigned int UINT8 __attribute__((__mode__(__QI__))); -typedef signed int SINT8 __attribute__((__mode__(__QI__))); -typedef unsigned int UINT16 __attribute__((__mode__(__HI__))); -typedef signed int SINT16 __attribute__((__mode__(__HI__))); -typedef unsigned int UINT32 __attribute__((__mode__(__SI__))); -typedef signed int SINT32 __attribute__((__mode__(__SI__))); -typedef unsigned int UINT64 __attribute__((__mode__(__DI__))); -typedef signed int SINT64 __attribute__((__mode__(__DI__))); -#endif - -typedef float FLOAT32; - -#ifndef __GNUC__ -#define __builtin_expect(x, expected_value) (x) -#endif -#define LIKELY(x) __builtin_expect(!!(x), 1) -#define UNLIKELY(x) __builtin_expect((x) != 0, 0) - -#ifdef __cplusplus -} -#endif - -#endif \ No newline at end of file diff --git a/applications/system/elk_js/ffi/fficonfig.h b/applications/system/elk_js/ffi/fficonfig.h deleted file mode 100644 index 6b58c3db425..00000000000 --- a/applications/system/elk_js/ffi/fficonfig.h +++ /dev/null @@ -1,213 +0,0 @@ -/* fficonfig.h. Generated from fficonfig.h.in by configure. */ -/* fficonfig.h.in. Generated from configure.ac by autoheader. */ - -/* Define if building universal (internal helper macro) */ -/* #undef AC_APPLE_UNIVERSAL_BUILD */ - -/* Define to one of `_getb67', `GETB67', `getb67' for Cray-2 and Cray-YMP - systems. This function is required for `alloca.c' support on those systems. - */ -/* #undef CRAY_STACKSEG_END */ - -/* Define to 1 if using `alloca.c'. */ -/* #undef C_ALLOCA */ - -/* Define to the flags needed for the .section .eh_frame directive. */ -#define EH_FRAME_FLAGS "aw" - -/* Define this if you want extra debugging. */ -/* #undef FFI_DEBUG */ - -/* Cannot use PROT_EXEC on this target, so, we revert to alternative means */ -/* #undef FFI_EXEC_TRAMPOLINE_TABLE */ - -/* Define this if you want to enable pax emulated trampolines */ -/* #undef FFI_MMAP_EXEC_EMUTRAMP_PAX */ - -/* Cannot use malloc on this target, so, we revert to alternative means */ -/* #undef FFI_MMAP_EXEC_WRIT */ - -/* Define this if you do not want support for the raw API. */ -/* #undef FFI_NO_RAW_API */ - -/* Define this if you do not want support for aggregate types. */ -/* #undef FFI_NO_STRUCTS */ - -/* Define to 1 if you have `alloca', as a function or macro. */ -#define HAVE_ALLOCA 1 - -/* Define to 1 if you have and it should be used (not on Ultrix). - */ -#define HAVE_ALLOCA_H 1 - -/* Define if your assembler supports .ascii. */ -/* #undef HAVE_AS_ASCII_PSEUDO_OP */ - -/* Define if your assembler supports .cfi_* directives. */ -#define HAVE_AS_CFI_PSEUDO_OP 1 - -/* Define if your assembler supports .register. */ -/* #undef HAVE_AS_REGISTER_PSEUDO_OP */ - -/* Define if your assembler and linker support unaligned PC relative relocs. - */ -/* #undef HAVE_AS_SPARC_UA_PCREL */ - -/* Define if your assembler supports .string. */ -/* #undef HAVE_AS_STRING_PSEUDO_OP */ - -/* Define if your assembler supports unwind section type. */ -/* #undef HAVE_AS_X86_64_UNWIND_SECTION_TYPE */ - -/* Define if your assembler supports PC relative relocs. */ -/* #undef HAVE_AS_X86_PCREL */ - -/* Define to 1 if you have the header file. */ -// #define HAVE_DLFCN_H 1 - -/* Define if __attribute__((visibility("hidden"))) is supported. */ -#define HAVE_HIDDEN_VISIBILITY_ATTRIBUTE 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_INTTYPES_H 1 - -/* Define if you have the long double type and it is bigger than a double */ -/* #undef HAVE_LONG_DOUBLE */ - -/* Define if you support more than one size of the long double type */ -/* #undef HAVE_LONG_DOUBLE_VARIANT */ - -/* Define to 1 if you have the `memcpy' function. */ -#define HAVE_MEMCPY 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_MEMORY_H 1 - -/* Define to 1 if you have the `mkostemp' function. */ -/* #undef HAVE_MKOSTEMP */ - -/* Define to 1 if you have the `mmap' function. */ -#define HAVE_MMAP 1 - -/* Define if mmap with MAP_ANON(YMOUS) works. */ -#define HAVE_MMAP_ANON 1 - -/* Define if mmap of /dev/zero works. */ -#define HAVE_MMAP_DEV_ZERO 1 - -/* Define if read-only mmap of a plain file works. */ -#define HAVE_MMAP_FILE 1 - -/* Define if .eh_frame sections should be read-only. */ -/* #undef HAVE_RO_EH_FRAME */ - -/* Define to 1 if you have the header file. */ -#define HAVE_STDINT_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_STDLIB_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_STRINGS_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_STRING_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_SYS_MMAN_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_SYS_STAT_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_SYS_TYPES_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_UNISTD_H 1 - -/* Define to the sub-directory in which libtool stores uninstalled libraries. - */ -#define LT_OBJDIR ".libs/" - -/* Define to 1 if your C compiler doesn't accept -c and -o together. */ -/* #undef NO_MINUS_C_MINUS_O */ - -/* Name of package */ -#define PACKAGE "libffi" - -/* Define to the address where bug reports for this package should be sent. */ -#define PACKAGE_BUGREPORT "http://github.com/atgreen/libffi/issues" - -/* Define to the full name of this package. */ -#define PACKAGE_NAME "libffi" - -/* Define to the full name and version of this package. */ -#define PACKAGE_STRING "libffi 3.2.1" - -/* Define to the one symbol short name of this package. */ -#define PACKAGE_TARNAME "libffi" - -/* Define to the home page for this package. */ -#define PACKAGE_URL "" - -/* Define to the version of this package. */ -#define PACKAGE_VERSION "3.2.1" - -/* The size of `double', as computed by sizeof. */ -#define SIZEOF_DOUBLE 8 - -/* The size of `long double', as computed by sizeof. */ -#define SIZEOF_LONG_DOUBLE 8 - -/* The size of `size_t', as computed by sizeof. */ -#define SIZEOF_SIZE_T 4 - -/* If using the C implementation of alloca, define if you know the - direction of stack growth for your system; otherwise it will be - automatically deduced at runtime. - STACK_DIRECTION > 0 => grows toward higher addresses - STACK_DIRECTION < 0 => grows toward lower addresses - STACK_DIRECTION = 0 => direction of growth unknown */ -/* #undef STACK_DIRECTION */ - -/* Define to 1 if you have the ANSI C header files. */ -#define STDC_HEADERS 1 - -/* Define if symbols are underscored. */ -/* #undef SYMBOL_UNDERSCORE */ - -/* Define this if you are using Purify and want to suppress spurious messages. - */ -/* #undef USING_PURIFY */ - -/* Version number of package */ -#define VERSION "3.2.1" - -/* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most - significant byte first (like Motorola and SPARC, unlike Intel). */ -#if defined AC_APPLE_UNIVERSAL_BUILD -#if defined __BIG_ENDIAN__ -#define WORDS_BIGENDIAN 1 -#endif -#else -#ifndef WORDS_BIGENDIAN -/* # undef WORDS_BIGENDIAN */ -#endif -#endif - -/* Define to `unsigned int' if does not define. */ -/* #undef size_t */ - -#ifdef HAVE_HIDDEN_VISIBILITY_ATTRIBUTE -#ifdef LIBFFI_ASM -#define FFI_HIDDEN(name) .hidden name -#else -#define FFI_HIDDEN __attribute__((visibility("hidden"))) -#endif -#else -#ifdef LIBFFI_ASM -#define FFI_HIDDEN(name) -#else -#define FFI_HIDDEN -#endif -#endif diff --git a/applications/system/elk_js/ffi/ffitarget.h b/applications/system/elk_js/ffi/ffitarget.h deleted file mode 100644 index 6ace9d2bf94..00000000000 --- a/applications/system/elk_js/ffi/ffitarget.h +++ /dev/null @@ -1,77 +0,0 @@ -/* -----------------------------------------------------------------*-C-*- - ffitarget.h - Copyright (c) 2012 Anthony Green - Copyright (c) 2010 CodeSourcery - Copyright (c) 1996-2003 Red Hat, Inc. - Target configuration macros for ARM. - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - ``Software''), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - DEALINGS IN THE SOFTWARE. - ----------------------------------------------------------------------- */ - -#ifndef LIBFFI_TARGET_H -#define LIBFFI_TARGET_H - -#ifndef LIBFFI_H -#error "Please do not include ffitarget.h directly into your source. Use ffi.h instead." -#endif - -#ifndef LIBFFI_ASM -typedef unsigned long ffi_arg; -typedef signed long ffi_sarg; - -typedef enum ffi_abi { - FFI_FIRST_ABI = 0, - FFI_SYSV, - FFI_VFP, - FFI_LAST_ABI, -#ifdef __ARM_PCS_VFP - FFI_DEFAULT_ABI = FFI_VFP, -#else - FFI_DEFAULT_ABI = FFI_SYSV, -#endif -} ffi_abi; -#endif - -#define FFI_EXTRA_CIF_FIELDS \ - int vfp_used; \ - unsigned short vfp_reg_free, vfp_nargs; \ - signed char vfp_args[16] - -#define FFI_TARGET_SPECIFIC_VARIADIC -#define FFI_TARGET_HAS_COMPLEX_TYPE - -/* ---- Definitions for closures ----------------------------------------- */ - -#define FFI_CLOSURES 0 -#define FFI_GO_CLOSURES 0 -#define FFI_NATIVE_RAW_API 0 - -#if defined(FFI_EXEC_TRAMPOLINE_TABLE) && FFI_EXEC_TRAMPOLINE_TABLE - -#ifdef __MACH__ -#define FFI_TRAMPOLINE_SIZE 12 -#define FFI_TRAMPOLINE_CLOSURE_OFFSET 8 -#else -#error "No trampoline table implementation" -#endif - -#else -#define FFI_TRAMPOLINE_SIZE 12 -#define FFI_TRAMPOLINE_CLOSURE_OFFSET FFI_TRAMPOLINE_SIZE -#endif - -#endif \ No newline at end of file diff --git a/applications/system/elk_js/ffi/internal.h b/applications/system/elk_js/ffi/internal.h deleted file mode 100644 index ffdf973bacc..00000000000 --- a/applications/system/elk_js/ffi/internal.h +++ /dev/null @@ -1,7 +0,0 @@ -#define ARM_TYPE_VFP_S 0 -#define ARM_TYPE_VFP_D 1 -#define ARM_TYPE_VFP_N 2 -#define ARM_TYPE_INT64 3 -#define ARM_TYPE_INT 4 -#define ARM_TYPE_VOID 5 -#define ARM_TYPE_STRUCT 6 \ No newline at end of file diff --git a/applications/system/elk_js/ffi/prep_cif.c b/applications/system/elk_js/ffi/prep_cif.c deleted file mode 100644 index 1f09efb016f..00000000000 --- a/applications/system/elk_js/ffi/prep_cif.c +++ /dev/null @@ -1,233 +0,0 @@ -/* ----------------------------------------------------------------------- - prep_cif.c - Copyright (c) 2011, 2012 Anthony Green - Copyright (c) 1996, 1998, 2007 Red Hat, Inc. - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - ``Software''), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - DEALINGS IN THE SOFTWARE. - ----------------------------------------------------------------------- */ - -#include "ffi.h" -#include "ffi_common.h" -#include - -/* Round up to FFI_SIZEOF_ARG. */ - -#define STACK_ARG_SIZE(x) ALIGN(x, FFI_SIZEOF_ARG) - -/* Perform machine independent initialization of aggregate type - specifications. */ - -static ffi_status initialize_aggregate(ffi_type* arg, size_t* offsets) { - ffi_type** ptr; - - if(UNLIKELY(arg == NULL || arg->elements == NULL)) return FFI_BAD_TYPEDEF; - - arg->size = 0; - arg->alignment = 0; - - ptr = &(arg->elements[0]); - - if(UNLIKELY(ptr == 0)) return FFI_BAD_TYPEDEF; - - while((*ptr) != NULL) { - if(UNLIKELY(((*ptr)->size == 0) && (initialize_aggregate((*ptr), NULL) != FFI_OK))) - return FFI_BAD_TYPEDEF; - - /* Perform a sanity check on the argument type */ - FFI_ASSERT_VALID_TYPE(*ptr); - - arg->size = ALIGN(arg->size, (*ptr)->alignment); - if(offsets) *offsets++ = arg->size; - arg->size += (*ptr)->size; - - arg->alignment = (arg->alignment > (*ptr)->alignment) ? arg->alignment : (*ptr)->alignment; - - ptr++; - } - - /* Structure size includes tail padding. This is important for - structures that fit in one register on ABIs like the PowerPC64 - Linux ABI that right justify small structs in a register. - It's also needed for nested structure layout, for example - struct A { long a; char b; }; struct B { struct A x; char y; }; - should find y at an offset of 2*sizeof(long) and result in a - total size of 3*sizeof(long). */ - arg->size = ALIGN(arg->size, arg->alignment); - - /* On some targets, the ABI defines that structures have an additional - alignment beyond the "natural" one based on their elements. */ -#ifdef FFI_AGGREGATE_ALIGNMENT - if(FFI_AGGREGATE_ALIGNMENT > arg->alignment) arg->alignment = FFI_AGGREGATE_ALIGNMENT; -#endif - - if(arg->size == 0) - return FFI_BAD_TYPEDEF; - else - return FFI_OK; -} - -#ifndef __CRIS__ -/* The CRIS ABI specifies structure elements to have byte - alignment only, so it completely overrides this functions, - which assumes "natural" alignment and padding. */ - -/* Perform machine independent ffi_cif preparation, then call - machine dependent routine. */ - -/* For non variadic functions isvariadic should be 0 and - nfixedargs==ntotalargs. - For variadic calls, isvariadic should be 1 and nfixedargs - and ntotalargs set as appropriate. nfixedargs must always be >=1 */ - -ffi_status FFI_HIDDEN ffi_prep_cif_core( - ffi_cif* cif, - ffi_abi abi, - unsigned int isvariadic, - unsigned int nfixedargs, - unsigned int ntotalargs, - ffi_type* rtype, - ffi_type** atypes) { - unsigned bytes = 0; - unsigned int i; - ffi_type** ptr; - - FFI_ASSERT(cif != NULL); - FFI_ASSERT((!isvariadic) || (nfixedargs >= 1)); - FFI_ASSERT(nfixedargs <= ntotalargs); - - if(!(abi > FFI_FIRST_ABI && abi < FFI_LAST_ABI)) return FFI_BAD_ABI; - - cif->abi = abi; - cif->arg_types = atypes; - cif->nargs = ntotalargs; - cif->rtype = rtype; - - cif->flags = 0; - -#if HAVE_LONG_DOUBLE_VARIANT - ffi_prep_types(abi); -#endif - - /* Initialize the return type if necessary */ - if((cif->rtype->size == 0) && (initialize_aggregate(cif->rtype, NULL) != FFI_OK)) - return FFI_BAD_TYPEDEF; - -#ifndef FFI_TARGET_HAS_COMPLEX_TYPE - if(rtype->type == FFI_TYPE_COMPLEX) abort(); -#endif - /* Perform a sanity check on the return type */ - FFI_ASSERT_VALID_TYPE(cif->rtype); - - /* x86, x86-64 and s390 stack space allocation is handled in prep_machdep. */ -#if !defined FFI_TARGET_SPECIFIC_STACK_SPACE_ALLOCATION - /* Make space for the return structure pointer */ - if(cif->rtype->type == FFI_TYPE_STRUCT -#ifdef TILE - && (cif->rtype->size > 10 * FFI_SIZEOF_ARG) -#endif -#ifdef XTENSA - && (cif->rtype->size > 16) -#endif -#ifdef NIOS2 - && (cif->rtype->size > 8) -#endif - ) - bytes = STACK_ARG_SIZE(sizeof(void*)); -#endif - - for(ptr = cif->arg_types, i = cif->nargs; i > 0; i--, ptr++) { - /* Initialize any uninitialized aggregate type definitions */ - if(((*ptr)->size == 0) && (initialize_aggregate((*ptr), NULL) != FFI_OK)) - return FFI_BAD_TYPEDEF; - -#ifndef FFI_TARGET_HAS_COMPLEX_TYPE - if((*ptr)->type == FFI_TYPE_COMPLEX) abort(); -#endif - /* Perform a sanity check on the argument type, do this - check after the initialization. */ - FFI_ASSERT_VALID_TYPE(*ptr); - -#if !defined FFI_TARGET_SPECIFIC_STACK_SPACE_ALLOCATION - { - /* Add any padding if necessary */ - if(((*ptr)->alignment - 1) & bytes) bytes = (unsigned)ALIGN(bytes, (*ptr)->alignment); - -#ifdef TILE - if(bytes < 10 * FFI_SIZEOF_ARG && - bytes + STACK_ARG_SIZE((*ptr)->size) > 10 * FFI_SIZEOF_ARG) { - /* An argument is never split between the 10 parameter - registers and the stack. */ - bytes = 10 * FFI_SIZEOF_ARG; - } -#endif -#ifdef XTENSA - if(bytes <= 6 * 4 && bytes + STACK_ARG_SIZE((*ptr)->size) > 6 * 4) bytes = 6 * 4; -#endif - - bytes += STACK_ARG_SIZE((*ptr)->size); - } -#endif - } - - cif->bytes = bytes; - - /* Perform machine dependent cif processing */ -#ifdef FFI_TARGET_SPECIFIC_VARIADIC - if(isvariadic) return ffi_prep_cif_machdep_var(cif, nfixedargs, ntotalargs); -#endif - - return ffi_prep_cif_machdep(cif); -} -#endif /* not __CRIS__ */ - -ffi_status - ffi_prep_cif(ffi_cif* cif, ffi_abi abi, unsigned int nargs, ffi_type* rtype, ffi_type** atypes) { - return ffi_prep_cif_core(cif, abi, 0, nargs, nargs, rtype, atypes); -} - -ffi_status ffi_prep_cif_var( - ffi_cif* cif, - ffi_abi abi, - unsigned int nfixedargs, - unsigned int ntotalargs, - ffi_type* rtype, - ffi_type** atypes) { - return ffi_prep_cif_core(cif, abi, 1, nfixedargs, ntotalargs, rtype, atypes); -} - -#if FFI_CLOSURES - -ffi_status ffi_prep_closure( - ffi_closure* closure, - ffi_cif* cif, - void (*fun)(ffi_cif*, void*, void**, void*), - void* user_data) { - return ffi_prep_closure_loc(closure, cif, fun, user_data, closure); -} - -#endif - -ffi_status ffi_get_struct_offsets(ffi_abi abi, ffi_type* struct_type, size_t* offsets) { - if(!(abi > FFI_FIRST_ABI && abi < FFI_LAST_ABI)) return FFI_BAD_ABI; - if(struct_type->type != FFI_TYPE_STRUCT) return FFI_BAD_TYPEDEF; - -#if HAVE_LONG_DOUBLE_VARIANT - ffi_prep_types(abi); -#endif - - return initialize_aggregate(struct_type, offsets); -} \ No newline at end of file diff --git a/applications/system/elk_js/ffi/sysV-thumb.S b/applications/system/elk_js/ffi/sysV-thumb.S deleted file mode 100644 index 8f1607f3801..00000000000 --- a/applications/system/elk_js/ffi/sysV-thumb.S +++ /dev/null @@ -1,227 +0,0 @@ -/* ----------------------------------------------------------------------- - sysv.S - Copyright (c) 1998, 2008, 2011 Red Hat, Inc. - Copyright (c) 2011 Plausible Labs Cooperative, Inc. - ARM Foreign Function Interface - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - ``Software''), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - DEALINGS IN THE SOFTWARE. - ----------------------------------------------------------------------- */ - -#define LIBFFI_ASM -#include "fficonfig.h" -#include "ffi.h" -#include "ffi_cfi.h" -#include "internal.h" - -/* GCC 4.8 provides __ARM_ARCH; construct it otherwise. */ -#ifndef __ARM_ARCH -# if defined(__ARM_ARCH_7__) || defined(__ARM_ARCH_7A__) \ - || defined(__ARM_ARCH_7R__) || defined(__ARM_ARCH_7M__) \ - || defined(__ARM_ARCH_7EM__) -# define __ARM_ARCH 7 -# elif defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_6J__) \ - || defined(__ARM_ARCH_6K__) || defined(__ARM_ARCH_6Z__) \ - || defined(__ARM_ARCH_6ZK__) || defined(__ARM_ARCH_6T2__) \ - || defined(__ARM_ARCH_6M__) -# define __ARM_ARCH 6 -# elif defined(__ARM_ARCH_5__) || defined(__ARM_ARCH_5T__) \ - || defined(__ARM_ARCH_5E__) || defined(__ARM_ARCH_5TE__) \ - || defined(__ARM_ARCH_5TEJ__) -# define __ARM_ARCH 5 -# else -# define __ARM_ARCH 4 -# endif -#endif - -/* Conditionally compile unwinder directives. */ -#ifdef __ARM_EABI__1 -# define UNWIND(...) __VA_ARGS__ -#else -# define UNWIND(...) -#endif - -#if defined(HAVE_AS_CFI_PSEUDO_OP) && defined(__ARM_EABI__) - .cfi_sections .debug_frame -#endif - -#define CONCAT(a, b) CONCAT2(a, b) -#define CONCAT2(a, b) a ## b - -#ifdef __USER_LABEL_PREFIX__ -# define CNAME(X) CONCAT (__USER_LABEL_PREFIX__, X) -#else -# define CNAME(X) X -#endif -#ifdef __ELF__ -# define SIZE(X) .size CNAME(X), . - CNAME(X) -# define TYPE(X, Y) .type CNAME(X), Y -#else -# define SIZE(X) -# define TYPE(X, Y) -#endif - -#define ARM_FUNC_START_LOCAL(name) \ - .align 3; \ - TYPE(CNAME(name), %function); \ - CNAME(name): - -#define ARM_FUNC_START(name) \ - .globl CNAME(name); \ - FFI_HIDDEN(CNAME(name)); \ - ARM_FUNC_START_LOCAL(name) - -#define ARM_FUNC_END(name) \ - SIZE(name) - -/* Aid in defining a jump table with 8 bytes between entries. */ -/* ??? The clang assembler doesn't handle .if with symbolic expressions. */ -#if 1 -# define E(index) -#else -# define E(index) \ - .if . - 0b - 8*index; \ - .error "type table out of sync"; \ - .endif -#endif - - .text - .syntax unified - .thumb - -#ifndef __clang__ - /* We require interworking on LDM, which implies ARMv5T, - which implies the existance of BLX. */ - .arch armv6t2 -#endif - - /* Note that we use STC and LDC to encode VFP instructions, - so that we do not need ".fpu vfp", nor get that added to - the object file attributes. These will not be executed - unless the FFI_VFP abi is used. */ - - @ r0: stack - @ r1: frame - @ r2: fn - @ r3: vfp_used - -ARM_FUNC_START(ffi_call_VFP) - UNWIND(.fnstart) - cfi_startproc - - cmp r3, #3 @ load only d0 if possible - - bgt greater - ldc p11, cr0, [r0] @ vldrle d0, [sp] - b done - - greater: - ldc p11, cr0, [r0], {16} @ vldmgt sp, {d0-d7} - done: - add r0, #64 @ discard the vfp register args - /* FALLTHRU */ -ARM_FUNC_END(ffi_call_VFP) - -ARM_FUNC_START(ffi_call_SYSV) - stm r1, {fp, lr} - mov fp, r1 - - @ This is a bit of a lie wrt the origin of the unwind info, but - @ now we've got the usual frame pointer and two saved registers. - UNWIND(.save {fp,lr}) - UNWIND(.setfp fp, sp) - cfi_def_cfa(fp, 8) - cfi_rel_offset(fp, 0) - cfi_rel_offset(lr, 4) - - mov sp, r0 @ install the stack pointer - mov lr, r2 @ move the fn pointer out of the way - ldr ip, [fp, #16] @ install the static chain - ldmia sp!, {r0-r3} @ move first 4 parameters in registers. - blx lr @ call fn - - @ Load r2 with the pointer to storage for the return value - @ Load r3 with the return type code - ldr r2, [fp, #8] - ldr r3, [fp, #12] - - @ Deallocate the stack with the arguments. - mov sp, fp - cfi_def_cfa_register(sp) - - @ Store values stored in registers. - adr ip, 0f - add ip, ip, r3, lsl #3 - mov pc, ip - .align 3 -0: -E(ARM_TYPE_VFP_S) - stc p10, cr0, [r2] @ vstr s0, [r2] - pop {fp,pc} -E(ARM_TYPE_VFP_D) - stc p11, cr0, [r2] @ vstr d0, [r2] - pop {fp,pc} -E(ARM_TYPE_VFP_N) - stc p11, cr0, [r2], {8} @ vstm r2, {d0-d3} - pop {fp,pc} -E(ARM_TYPE_INT64) - str r1, [r2, #4] - nop - nop - nop -E(ARM_TYPE_INT) - str r0, [r2] - pop {fp,pc} - nop -E(ARM_TYPE_VOID) - pop {fp,pc} - nop - nop -E(ARM_TYPE_STRUCT) - pop {fp,pc} - - cfi_endproc - UNWIND(.fnend) -ARM_FUNC_END(ffi_call_SYSV) - -#if FFI_EXEC_TRAMPOLINE_TABLE - -#ifdef __MACH__ -#include - -.align PAGE_MAX_SHIFT -ARM_FUNC_START(ffi_closure_trampoline_table_page) -.rept PAGE_MAX_SIZE / FFI_TRAMPOLINE_SIZE - adr ip, #-PAGE_MAX_SIZE @ the config page is PAGE_MAX_SIZE behind the trampoline page - sub ip, #8 @ account for pc bias - ldr pc, [ip, #4] @ jump to ffi_closure_SYSV or ffi_closure_VFP -.endr -ARM_FUNC_END(ffi_closure_trampoline_table_page) -#endif - -#else - -ARM_FUNC_START(ffi_arm_trampoline) -0: adr ip, 0b - ldr pc, 1f -1: .long 0 -ARM_FUNC_END(ffi_arm_trampoline) - -#endif /* FFI_EXEC_TRAMPOLINE_TABLE */ - -#if defined __ELF__ && defined __linux__ - .section .note.GNU-stack,"",%progbits -#endif \ No newline at end of file diff --git a/applications/system/elk_js/ffi/types.c b/applications/system/elk_js/ffi/types.c deleted file mode 100644 index 5a04e9949fd..00000000000 --- a/applications/system/elk_js/ffi/types.c +++ /dev/null @@ -1,101 +0,0 @@ -/* ----------------------------------------------------------------------- - types.c - Copyright (c) 1996, 1998 Red Hat, Inc. - - Predefined ffi_types needed by libffi. - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - ``Software''), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - DEALINGS IN THE SOFTWARE. - ----------------------------------------------------------------------- */ - -/* Hide the basic type definitions from the header file, so that we - can redefine them here as "const". */ -#define LIBFFI_HIDE_BASIC_TYPES -#define FFI_EXTERN - -#include "ffi.h" -#include "ffi_common.h" - -/* Type definitions */ - -#define FFI_TYPEDEF(name, type, id, maybe_const) \ - struct struct_align_##name { \ - char c; \ - type x; \ - }; \ - FFI_EXTERN \ - maybe_const ffi_type ffi_type_##name = { \ - sizeof(type), offsetof(struct struct_align_##name, x), id, NULL} - -#define FFI_COMPLEX_TYPEDEF(name, type, maybe_const) \ - static ffi_type* ffi_elements_complex_##name[2] = {(ffi_type*)(&ffi_type_##name), NULL}; \ - struct struct_align_complex_##name { \ - char c; \ - _Complex type x; \ - }; \ - FFI_EXTERN \ - maybe_const ffi_type ffi_type_complex_##name = { \ - sizeof(_Complex type), \ - offsetof(struct struct_align_complex_##name, x), \ - FFI_TYPE_COMPLEX, \ - (ffi_type**)ffi_elements_complex_##name} - -/* Size and alignment are fake here. They must not be 0. */ -FFI_EXTERN const ffi_type ffi_type_void = {1, 1, FFI_TYPE_VOID, NULL}; - -FFI_TYPEDEF(uint8, UINT8, FFI_TYPE_UINT8, const); -FFI_TYPEDEF(sint8, SINT8, FFI_TYPE_SINT8, const); -FFI_TYPEDEF(uint16, UINT16, FFI_TYPE_UINT16, const); -FFI_TYPEDEF(sint16, SINT16, FFI_TYPE_SINT16, const); -FFI_TYPEDEF(uint32, UINT32, FFI_TYPE_UINT32, const); -FFI_TYPEDEF(sint32, SINT32, FFI_TYPE_SINT32, const); -FFI_TYPEDEF(uint64, UINT64, FFI_TYPE_UINT64, const); -FFI_TYPEDEF(sint64, SINT64, FFI_TYPE_SINT64, const); - -FFI_TYPEDEF(pointer, void*, FFI_TYPE_POINTER, const); - -FFI_TYPEDEF(float, float, FFI_TYPE_FLOAT, const); -FFI_TYPEDEF(double, double, FFI_TYPE_DOUBLE, const); - -#if !defined HAVE_LONG_DOUBLE_VARIANT || defined __alpha__ -#define FFI_LDBL_CONST const -#else -#define FFI_LDBL_CONST -#endif - -#ifdef __alpha__ -/* Even if we're not configured to default to 128-bit long double, - maintain binary compatibility, as -mlong-double-128 can be used - at any time. */ -/* Validate the hard-coded number below. */ -#if defined(__LONG_DOUBLE_128__) && FFI_TYPE_LONGDOUBLE != 4 -#error FFI_TYPE_LONGDOUBLE out of date -#endif -const ffi_type ffi_type_longdouble = {16, 16, 4, NULL}; -#elif FFI_TYPE_LONGDOUBLE != FFI_TYPE_DOUBLE -FFI_TYPEDEF(longdouble, long double, FFI_TYPE_LONGDOUBLE, FFI_LDBL_CONST); -#endif - -#ifdef FFI_TARGET_HAS_COMPLEX_TYPE -FFI_COMPLEX_TYPEDEF(float, float, const); -FFI_COMPLEX_TYPEDEF(double, double, const); -#if FFI_TYPE_LONGDOUBLE != FFI_TYPE_DOUBLE -FFI_COMPLEX_TYPEDEF(longdouble, long double, FFI_LDBL_CONST); -#endif -#endif \ No newline at end of file diff --git a/applications/system/elk_mjs/assets/api.js b/applications/system/elk_mjs/assets/api.js deleted file mode 100644 index ad3b26e1563..00000000000 --- a/applications/system/elk_mjs/assets/api.js +++ /dev/null @@ -1,3 +0,0 @@ -({ - add: function (a, b) { return a + b; }, -}) \ No newline at end of file diff --git a/applications/system/elk_mjs/assets/req.js b/applications/system/elk_mjs/assets/req.js deleted file mode 100644 index 0ffbf6df984..00000000000 --- a/applications/system/elk_mjs/assets/req.js +++ /dev/null @@ -1,3 +0,0 @@ -let math = load("/ext/scripts/api.js"); -let result = math.add(5, 10); -print(result); \ No newline at end of file diff --git a/applications/system/elk_mjs/addon_api/app_api_interface.h b/applications/system/mjs/addon_api/app_api_interface.h similarity index 100% rename from applications/system/elk_mjs/addon_api/app_api_interface.h rename to applications/system/mjs/addon_api/app_api_interface.h diff --git a/applications/system/elk_mjs/addon_api/app_api_table.cpp b/applications/system/mjs/addon_api/app_api_table.cpp similarity index 100% rename from applications/system/elk_mjs/addon_api/app_api_table.cpp rename to applications/system/mjs/addon_api/app_api_table.cpp diff --git a/applications/system/elk_mjs/addon_api/app_api_table_i.h b/applications/system/mjs/addon_api/app_api_table_i.h similarity index 100% rename from applications/system/elk_mjs/addon_api/app_api_table_i.h rename to applications/system/mjs/addon_api/app_api_table_i.h diff --git a/applications/system/elk_mjs/application.fam b/applications/system/mjs/application.fam similarity index 100% rename from applications/system/elk_mjs/application.fam rename to applications/system/mjs/application.fam diff --git a/applications/system/elk_mjs/assets/about.js b/applications/system/mjs/assets/about.js similarity index 100% rename from applications/system/elk_mjs/assets/about.js rename to applications/system/mjs/assets/about.js diff --git a/applications/system/elk_js/assets/api.js b/applications/system/mjs/assets/api.js similarity index 100% rename from applications/system/elk_js/assets/api.js rename to applications/system/mjs/assets/api.js diff --git a/applications/system/elk_mjs/assets/ffi.js b/applications/system/mjs/assets/ffi.js similarity index 100% rename from applications/system/elk_mjs/assets/ffi.js rename to applications/system/mjs/assets/ffi.js diff --git a/applications/system/mjs/assets/req.js b/applications/system/mjs/assets/req.js new file mode 100644 index 00000000000..9d8f1266798 --- /dev/null +++ b/applications/system/mjs/assets/req.js @@ -0,0 +1,3 @@ +let math = load("/ext/apps_assets/m_js/api.js"); +let result = math.add(5, 10); +print(result); \ No newline at end of file diff --git a/applications/system/elk_mjs/lib/mjs/common/cs_dbg.c b/applications/system/mjs/lib/mjs/common/cs_dbg.c similarity index 100% rename from applications/system/elk_mjs/lib/mjs/common/cs_dbg.c rename to applications/system/mjs/lib/mjs/common/cs_dbg.c diff --git a/applications/system/elk_mjs/lib/mjs/common/cs_dbg.h b/applications/system/mjs/lib/mjs/common/cs_dbg.h similarity index 100% rename from applications/system/elk_mjs/lib/mjs/common/cs_dbg.h rename to applications/system/mjs/lib/mjs/common/cs_dbg.h diff --git a/applications/system/elk_mjs/lib/mjs/common/cs_dirent.c b/applications/system/mjs/lib/mjs/common/cs_dirent.c similarity index 100% rename from applications/system/elk_mjs/lib/mjs/common/cs_dirent.c rename to applications/system/mjs/lib/mjs/common/cs_dirent.c diff --git a/applications/system/elk_mjs/lib/mjs/common/cs_dirent.h b/applications/system/mjs/lib/mjs/common/cs_dirent.h similarity index 100% rename from applications/system/elk_mjs/lib/mjs/common/cs_dirent.h rename to applications/system/mjs/lib/mjs/common/cs_dirent.h diff --git a/applications/system/elk_mjs/lib/mjs/common/cs_file.c b/applications/system/mjs/lib/mjs/common/cs_file.c similarity index 100% rename from applications/system/elk_mjs/lib/mjs/common/cs_file.c rename to applications/system/mjs/lib/mjs/common/cs_file.c diff --git a/applications/system/elk_mjs/lib/mjs/common/cs_file.h b/applications/system/mjs/lib/mjs/common/cs_file.h similarity index 100% rename from applications/system/elk_mjs/lib/mjs/common/cs_file.h rename to applications/system/mjs/lib/mjs/common/cs_file.h diff --git a/applications/system/elk_mjs/lib/mjs/common/cs_time.c b/applications/system/mjs/lib/mjs/common/cs_time.c similarity index 100% rename from applications/system/elk_mjs/lib/mjs/common/cs_time.c rename to applications/system/mjs/lib/mjs/common/cs_time.c diff --git a/applications/system/elk_mjs/lib/mjs/common/cs_time.h b/applications/system/mjs/lib/mjs/common/cs_time.h similarity index 100% rename from applications/system/elk_mjs/lib/mjs/common/cs_time.h rename to applications/system/mjs/lib/mjs/common/cs_time.h diff --git a/applications/system/elk_mjs/lib/mjs/common/cs_varint.c b/applications/system/mjs/lib/mjs/common/cs_varint.c similarity index 100% rename from applications/system/elk_mjs/lib/mjs/common/cs_varint.c rename to applications/system/mjs/lib/mjs/common/cs_varint.c diff --git a/applications/system/elk_mjs/lib/mjs/common/cs_varint.h b/applications/system/mjs/lib/mjs/common/cs_varint.h similarity index 100% rename from applications/system/elk_mjs/lib/mjs/common/cs_varint.h rename to applications/system/mjs/lib/mjs/common/cs_varint.h diff --git a/applications/system/elk_mjs/lib/mjs/common/frozen/frozen.c b/applications/system/mjs/lib/mjs/common/frozen/frozen.c similarity index 100% rename from applications/system/elk_mjs/lib/mjs/common/frozen/frozen.c rename to applications/system/mjs/lib/mjs/common/frozen/frozen.c diff --git a/applications/system/elk_mjs/lib/mjs/common/frozen/frozen.h b/applications/system/mjs/lib/mjs/common/frozen/frozen.h similarity index 100% rename from applications/system/elk_mjs/lib/mjs/common/frozen/frozen.h rename to applications/system/mjs/lib/mjs/common/frozen/frozen.h diff --git a/applications/system/elk_mjs/lib/mjs/common/mbuf.c b/applications/system/mjs/lib/mjs/common/mbuf.c similarity index 100% rename from applications/system/elk_mjs/lib/mjs/common/mbuf.c rename to applications/system/mjs/lib/mjs/common/mbuf.c diff --git a/applications/system/elk_mjs/lib/mjs/common/mbuf.h b/applications/system/mjs/lib/mjs/common/mbuf.h similarity index 100% rename from applications/system/elk_mjs/lib/mjs/common/mbuf.h rename to applications/system/mjs/lib/mjs/common/mbuf.h diff --git a/applications/system/elk_mjs/lib/mjs/common/mg_mem.h b/applications/system/mjs/lib/mjs/common/mg_mem.h similarity index 100% rename from applications/system/elk_mjs/lib/mjs/common/mg_mem.h rename to applications/system/mjs/lib/mjs/common/mg_mem.h diff --git a/applications/system/elk_mjs/lib/mjs/common/mg_str.c b/applications/system/mjs/lib/mjs/common/mg_str.c similarity index 100% rename from applications/system/elk_mjs/lib/mjs/common/mg_str.c rename to applications/system/mjs/lib/mjs/common/mg_str.c diff --git a/applications/system/elk_mjs/lib/mjs/common/mg_str.h b/applications/system/mjs/lib/mjs/common/mg_str.h similarity index 100% rename from applications/system/elk_mjs/lib/mjs/common/mg_str.h rename to applications/system/mjs/lib/mjs/common/mg_str.h diff --git a/applications/system/elk_mjs/lib/mjs/common/platform.h b/applications/system/mjs/lib/mjs/common/platform.h similarity index 100% rename from applications/system/elk_mjs/lib/mjs/common/platform.h rename to applications/system/mjs/lib/mjs/common/platform.h diff --git a/applications/system/elk_mjs/lib/mjs/common/platforms/platform_stm32.h b/applications/system/mjs/lib/mjs/common/platforms/platform_stm32.h similarity index 100% rename from applications/system/elk_mjs/lib/mjs/common/platforms/platform_stm32.h rename to applications/system/mjs/lib/mjs/common/platforms/platform_stm32.h diff --git a/applications/system/elk_mjs/lib/mjs/common/str_util.c b/applications/system/mjs/lib/mjs/common/str_util.c similarity index 100% rename from applications/system/elk_mjs/lib/mjs/common/str_util.c rename to applications/system/mjs/lib/mjs/common/str_util.c diff --git a/applications/system/elk_mjs/lib/mjs/common/str_util.h b/applications/system/mjs/lib/mjs/common/str_util.h similarity index 100% rename from applications/system/elk_mjs/lib/mjs/common/str_util.h rename to applications/system/mjs/lib/mjs/common/str_util.h diff --git a/applications/system/elk_mjs/lib/mjs/ffi/ffi.c b/applications/system/mjs/lib/mjs/ffi/ffi.c similarity index 100% rename from applications/system/elk_mjs/lib/mjs/ffi/ffi.c rename to applications/system/mjs/lib/mjs/ffi/ffi.c diff --git a/applications/system/elk_mjs/lib/mjs/ffi/ffi.h b/applications/system/mjs/lib/mjs/ffi/ffi.h similarity index 100% rename from applications/system/elk_mjs/lib/mjs/ffi/ffi.h rename to applications/system/mjs/lib/mjs/ffi/ffi.h diff --git a/applications/system/elk_mjs/lib/mjs/mjs_array.c b/applications/system/mjs/lib/mjs/mjs_array.c similarity index 100% rename from applications/system/elk_mjs/lib/mjs/mjs_array.c rename to applications/system/mjs/lib/mjs/mjs_array.c diff --git a/applications/system/elk_mjs/lib/mjs/mjs_array.h b/applications/system/mjs/lib/mjs/mjs_array.h similarity index 100% rename from applications/system/elk_mjs/lib/mjs/mjs_array.h rename to applications/system/mjs/lib/mjs/mjs_array.h diff --git a/applications/system/elk_mjs/lib/mjs/mjs_array_public.h b/applications/system/mjs/lib/mjs/mjs_array_public.h similarity index 100% rename from applications/system/elk_mjs/lib/mjs/mjs_array_public.h rename to applications/system/mjs/lib/mjs/mjs_array_public.h diff --git a/applications/system/elk_mjs/lib/mjs/mjs_bcode.c b/applications/system/mjs/lib/mjs/mjs_bcode.c similarity index 100% rename from applications/system/elk_mjs/lib/mjs/mjs_bcode.c rename to applications/system/mjs/lib/mjs/mjs_bcode.c diff --git a/applications/system/elk_mjs/lib/mjs/mjs_bcode.h b/applications/system/mjs/lib/mjs/mjs_bcode.h similarity index 100% rename from applications/system/elk_mjs/lib/mjs/mjs_bcode.h rename to applications/system/mjs/lib/mjs/mjs_bcode.h diff --git a/applications/system/elk_mjs/lib/mjs/mjs_builtin.c b/applications/system/mjs/lib/mjs/mjs_builtin.c similarity index 100% rename from applications/system/elk_mjs/lib/mjs/mjs_builtin.c rename to applications/system/mjs/lib/mjs/mjs_builtin.c diff --git a/applications/system/elk_mjs/lib/mjs/mjs_builtin.h b/applications/system/mjs/lib/mjs/mjs_builtin.h similarity index 100% rename from applications/system/elk_mjs/lib/mjs/mjs_builtin.h rename to applications/system/mjs/lib/mjs/mjs_builtin.h diff --git a/applications/system/elk_mjs/lib/mjs/mjs_conversion.c b/applications/system/mjs/lib/mjs/mjs_conversion.c similarity index 100% rename from applications/system/elk_mjs/lib/mjs/mjs_conversion.c rename to applications/system/mjs/lib/mjs/mjs_conversion.c diff --git a/applications/system/elk_mjs/lib/mjs/mjs_conversion.h b/applications/system/mjs/lib/mjs/mjs_conversion.h similarity index 100% rename from applications/system/elk_mjs/lib/mjs/mjs_conversion.h rename to applications/system/mjs/lib/mjs/mjs_conversion.h diff --git a/applications/system/elk_mjs/lib/mjs/mjs_core.c b/applications/system/mjs/lib/mjs/mjs_core.c similarity index 100% rename from applications/system/elk_mjs/lib/mjs/mjs_core.c rename to applications/system/mjs/lib/mjs/mjs_core.c diff --git a/applications/system/elk_mjs/lib/mjs/mjs_core.h b/applications/system/mjs/lib/mjs/mjs_core.h similarity index 100% rename from applications/system/elk_mjs/lib/mjs/mjs_core.h rename to applications/system/mjs/lib/mjs/mjs_core.h diff --git a/applications/system/elk_mjs/lib/mjs/mjs_core_public.h b/applications/system/mjs/lib/mjs/mjs_core_public.h similarity index 100% rename from applications/system/elk_mjs/lib/mjs/mjs_core_public.h rename to applications/system/mjs/lib/mjs/mjs_core_public.h diff --git a/applications/system/elk_mjs/lib/mjs/mjs_dataview.c b/applications/system/mjs/lib/mjs/mjs_dataview.c similarity index 100% rename from applications/system/elk_mjs/lib/mjs/mjs_dataview.c rename to applications/system/mjs/lib/mjs/mjs_dataview.c diff --git a/applications/system/elk_mjs/lib/mjs/mjs_dataview.h b/applications/system/mjs/lib/mjs/mjs_dataview.h similarity index 100% rename from applications/system/elk_mjs/lib/mjs/mjs_dataview.h rename to applications/system/mjs/lib/mjs/mjs_dataview.h diff --git a/applications/system/elk_mjs/lib/mjs/mjs_exec.c b/applications/system/mjs/lib/mjs/mjs_exec.c similarity index 100% rename from applications/system/elk_mjs/lib/mjs/mjs_exec.c rename to applications/system/mjs/lib/mjs/mjs_exec.c diff --git a/applications/system/elk_mjs/lib/mjs/mjs_exec.h b/applications/system/mjs/lib/mjs/mjs_exec.h similarity index 100% rename from applications/system/elk_mjs/lib/mjs/mjs_exec.h rename to applications/system/mjs/lib/mjs/mjs_exec.h diff --git a/applications/system/elk_mjs/lib/mjs/mjs_exec_public.h b/applications/system/mjs/lib/mjs/mjs_exec_public.h similarity index 100% rename from applications/system/elk_mjs/lib/mjs/mjs_exec_public.h rename to applications/system/mjs/lib/mjs/mjs_exec_public.h diff --git a/applications/system/elk_mjs/lib/mjs/mjs_features.h b/applications/system/mjs/lib/mjs/mjs_features.h similarity index 100% rename from applications/system/elk_mjs/lib/mjs/mjs_features.h rename to applications/system/mjs/lib/mjs/mjs_features.h diff --git a/applications/system/elk_mjs/lib/mjs/mjs_ffi.c b/applications/system/mjs/lib/mjs/mjs_ffi.c similarity index 100% rename from applications/system/elk_mjs/lib/mjs/mjs_ffi.c rename to applications/system/mjs/lib/mjs/mjs_ffi.c diff --git a/applications/system/elk_mjs/lib/mjs/mjs_ffi.h b/applications/system/mjs/lib/mjs/mjs_ffi.h similarity index 100% rename from applications/system/elk_mjs/lib/mjs/mjs_ffi.h rename to applications/system/mjs/lib/mjs/mjs_ffi.h diff --git a/applications/system/elk_mjs/lib/mjs/mjs_ffi_public.h b/applications/system/mjs/lib/mjs/mjs_ffi_public.h similarity index 100% rename from applications/system/elk_mjs/lib/mjs/mjs_ffi_public.h rename to applications/system/mjs/lib/mjs/mjs_ffi_public.h diff --git a/applications/system/elk_mjs/lib/mjs/mjs_gc.c b/applications/system/mjs/lib/mjs/mjs_gc.c similarity index 100% rename from applications/system/elk_mjs/lib/mjs/mjs_gc.c rename to applications/system/mjs/lib/mjs/mjs_gc.c diff --git a/applications/system/elk_mjs/lib/mjs/mjs_gc.h b/applications/system/mjs/lib/mjs/mjs_gc.h similarity index 100% rename from applications/system/elk_mjs/lib/mjs/mjs_gc.h rename to applications/system/mjs/lib/mjs/mjs_gc.h diff --git a/applications/system/elk_mjs/lib/mjs/mjs_gc_public.h b/applications/system/mjs/lib/mjs/mjs_gc_public.h similarity index 100% rename from applications/system/elk_mjs/lib/mjs/mjs_gc_public.h rename to applications/system/mjs/lib/mjs/mjs_gc_public.h diff --git a/applications/system/elk_mjs/lib/mjs/mjs_internal.h b/applications/system/mjs/lib/mjs/mjs_internal.h similarity index 100% rename from applications/system/elk_mjs/lib/mjs/mjs_internal.h rename to applications/system/mjs/lib/mjs/mjs_internal.h diff --git a/applications/system/elk_mjs/lib/mjs/mjs_json.c b/applications/system/mjs/lib/mjs/mjs_json.c similarity index 100% rename from applications/system/elk_mjs/lib/mjs/mjs_json.c rename to applications/system/mjs/lib/mjs/mjs_json.c diff --git a/applications/system/elk_mjs/lib/mjs/mjs_json.h b/applications/system/mjs/lib/mjs/mjs_json.h similarity index 100% rename from applications/system/elk_mjs/lib/mjs/mjs_json.h rename to applications/system/mjs/lib/mjs/mjs_json.h diff --git a/applications/system/elk_mjs/lib/mjs/mjs_license.h b/applications/system/mjs/lib/mjs/mjs_license.h similarity index 100% rename from applications/system/elk_mjs/lib/mjs/mjs_license.h rename to applications/system/mjs/lib/mjs/mjs_license.h diff --git a/applications/system/elk_mjs/lib/mjs/mjs_mm.h b/applications/system/mjs/lib/mjs/mjs_mm.h similarity index 100% rename from applications/system/elk_mjs/lib/mjs/mjs_mm.h rename to applications/system/mjs/lib/mjs/mjs_mm.h diff --git a/applications/system/elk_mjs/lib/mjs/mjs_object.c b/applications/system/mjs/lib/mjs/mjs_object.c similarity index 100% rename from applications/system/elk_mjs/lib/mjs/mjs_object.c rename to applications/system/mjs/lib/mjs/mjs_object.c diff --git a/applications/system/elk_mjs/lib/mjs/mjs_object.h b/applications/system/mjs/lib/mjs/mjs_object.h similarity index 100% rename from applications/system/elk_mjs/lib/mjs/mjs_object.h rename to applications/system/mjs/lib/mjs/mjs_object.h diff --git a/applications/system/elk_mjs/lib/mjs/mjs_object_public.h b/applications/system/mjs/lib/mjs/mjs_object_public.h similarity index 100% rename from applications/system/elk_mjs/lib/mjs/mjs_object_public.h rename to applications/system/mjs/lib/mjs/mjs_object_public.h diff --git a/applications/system/elk_mjs/lib/mjs/mjs_parser.c b/applications/system/mjs/lib/mjs/mjs_parser.c similarity index 95% rename from applications/system/elk_mjs/lib/mjs/mjs_parser.c rename to applications/system/mjs/lib/mjs/mjs_parser.c index 5224124dacd..503b169426a 100644 --- a/applications/system/elk_mjs/lib/mjs/mjs_parser.c +++ b/applications/system/mjs/lib/mjs/mjs_parser.c @@ -91,43 +91,44 @@ static void emit_op(struct pstate* pstate, int tok) { // Intentionally left as macro rather than a function, to let the // compiler to inline calls and mimimize runtime stack usage. -#define PARSE_LTR_BINOP(p, f1, f2, ops, prev_op) \ - do { \ - mjs_err_t res = MJS_OK; \ - p->depth++; \ - if(p->depth > (STACK_LIMIT / BINOP_STACK_FRAME_SIZE)) { \ - mjs_set_errorf(p->mjs, MJS_SYNTAX_ERROR, "parser stack overflow"); \ - res = MJS_SYNTAX_ERROR; \ - goto binop_clean; \ - } \ - if((res = f1(p, TOK_EOF)) != MJS_OK) goto binop_clean; \ - if(prev_op != TOK_EOF) emit_op(p, prev_op); \ - if(findtok(ops, p->tok.tok) != TOK_EOF) { \ - int op = p->tok.tok; \ - size_t off_if = 0; \ - /* For AND/OR, implement short-circuit evaluation */ \ - if(ops[0] == TOK_LOGICAL_AND || ops[0] == TOK_LOGICAL_OR) { \ - emit_byte( \ - p, \ - (uint8_t)(ops[0] == TOK_LOGICAL_AND ? OP_JMP_NEUTRAL_FALSE : OP_JMP_NEUTRAL_TRUE)); \ - off_if = p->cur_idx; \ - emit_init_offset(p); \ - /* No need to emit TOK_LOGICAL_AND and TOK_LOGICAL_OR: */ \ - /* Just drop the first value, and evaluate the second one. */ \ - emit_byte(p, (uint8_t)OP_DROP); \ - op = TOK_EOF; \ - } \ - pnext1(p); \ - if((res = f2(p, op)) != MJS_OK) goto binop_clean; \ - \ - if(off_if != 0) { \ - mjs_bcode_insert_offset( \ - p, p->mjs, off_if, p->cur_idx - off_if - MJS_INIT_OFFSET_SIZE); \ - } \ - } \ - binop_clean: \ - p->depth--; \ - return res; \ +#define PARSE_LTR_BINOP(p, f1, f2, ops, prev_op) \ + do { \ + mjs_err_t res = MJS_OK; \ + p->depth++; \ + if(p->depth > (STACK_LIMIT / BINOP_STACK_FRAME_SIZE)) { \ + mjs_set_errorf(p->mjs, MJS_SYNTAX_ERROR, "parser stack overflow"); \ + res = MJS_SYNTAX_ERROR; \ + goto binop_clean; \ + } \ + if((res = f1(p, TOK_EOF)) != MJS_OK) goto binop_clean; \ + if(prev_op != TOK_EOF) emit_op(p, prev_op); \ + if(findtok(ops, p->tok.tok) != TOK_EOF) { \ + int op = p->tok.tok; \ + size_t off_if = 0; \ + /* For AND/OR, implement short-circuit evaluation */ \ + if(ops[0] == TOK_LOGICAL_AND || ops[0] == TOK_LOGICAL_OR) { \ + emit_byte( \ + p, \ + (uint8_t)(ops[0] == TOK_LOGICAL_AND ? OP_JMP_NEUTRAL_FALSE : \ + OP_JMP_NEUTRAL_TRUE)); \ + off_if = p->cur_idx; \ + emit_init_offset(p); \ + /* No need to emit TOK_LOGICAL_AND and TOK_LOGICAL_OR: */ \ + /* Just drop the first value, and evaluate the second one. */ \ + emit_byte(p, (uint8_t)OP_DROP); \ + op = TOK_EOF; \ + } \ + pnext1(p); \ + if((res = f2(p, op)) != MJS_OK) goto binop_clean; \ + \ + if(off_if != 0) { \ + mjs_bcode_insert_offset( \ + p, p->mjs, off_if, p->cur_idx - off_if - MJS_INIT_OFFSET_SIZE); \ + } \ + } \ + binop_clean: \ + p->depth--; \ + return res; \ } while(0) #define PARSE_RTL_BINOP(p, f1, f2, ops, prev_op) \ diff --git a/applications/system/elk_mjs/lib/mjs/mjs_parser.h b/applications/system/mjs/lib/mjs/mjs_parser.h similarity index 100% rename from applications/system/elk_mjs/lib/mjs/mjs_parser.h rename to applications/system/mjs/lib/mjs/mjs_parser.h diff --git a/applications/system/elk_mjs/lib/mjs/mjs_primitive.c b/applications/system/mjs/lib/mjs/mjs_primitive.c similarity index 100% rename from applications/system/elk_mjs/lib/mjs/mjs_primitive.c rename to applications/system/mjs/lib/mjs/mjs_primitive.c diff --git a/applications/system/elk_mjs/lib/mjs/mjs_primitive.h b/applications/system/mjs/lib/mjs/mjs_primitive.h similarity index 100% rename from applications/system/elk_mjs/lib/mjs/mjs_primitive.h rename to applications/system/mjs/lib/mjs/mjs_primitive.h diff --git a/applications/system/elk_mjs/lib/mjs/mjs_primitive_public.h b/applications/system/mjs/lib/mjs/mjs_primitive_public.h similarity index 100% rename from applications/system/elk_mjs/lib/mjs/mjs_primitive_public.h rename to applications/system/mjs/lib/mjs/mjs_primitive_public.h diff --git a/applications/system/elk_mjs/lib/mjs/mjs_string.c b/applications/system/mjs/lib/mjs/mjs_string.c similarity index 100% rename from applications/system/elk_mjs/lib/mjs/mjs_string.c rename to applications/system/mjs/lib/mjs/mjs_string.c diff --git a/applications/system/elk_mjs/lib/mjs/mjs_string.h b/applications/system/mjs/lib/mjs/mjs_string.h similarity index 100% rename from applications/system/elk_mjs/lib/mjs/mjs_string.h rename to applications/system/mjs/lib/mjs/mjs_string.h diff --git a/applications/system/elk_mjs/lib/mjs/mjs_string_public.h b/applications/system/mjs/lib/mjs/mjs_string_public.h similarity index 100% rename from applications/system/elk_mjs/lib/mjs/mjs_string_public.h rename to applications/system/mjs/lib/mjs/mjs_string_public.h diff --git a/applications/system/elk_mjs/lib/mjs/mjs_tok.c b/applications/system/mjs/lib/mjs/mjs_tok.c similarity index 100% rename from applications/system/elk_mjs/lib/mjs/mjs_tok.c rename to applications/system/mjs/lib/mjs/mjs_tok.c diff --git a/applications/system/elk_mjs/lib/mjs/mjs_tok.h b/applications/system/mjs/lib/mjs/mjs_tok.h similarity index 100% rename from applications/system/elk_mjs/lib/mjs/mjs_tok.h rename to applications/system/mjs/lib/mjs/mjs_tok.h diff --git a/applications/system/elk_mjs/lib/mjs/mjs_util.c b/applications/system/mjs/lib/mjs/mjs_util.c similarity index 100% rename from applications/system/elk_mjs/lib/mjs/mjs_util.c rename to applications/system/mjs/lib/mjs/mjs_util.c diff --git a/applications/system/elk_mjs/lib/mjs/mjs_util.h b/applications/system/mjs/lib/mjs/mjs_util.h similarity index 100% rename from applications/system/elk_mjs/lib/mjs/mjs_util.h rename to applications/system/mjs/lib/mjs/mjs_util.h diff --git a/applications/system/elk_mjs/lib/mjs/mjs_util_public.h b/applications/system/mjs/lib/mjs/mjs_util_public.h similarity index 100% rename from applications/system/elk_mjs/lib/mjs/mjs_util_public.h rename to applications/system/mjs/lib/mjs/mjs_util_public.h diff --git a/applications/system/elk_mjs/m_js.c b/applications/system/mjs/m_js.c similarity index 100% rename from applications/system/elk_mjs/m_js.c rename to applications/system/mjs/m_js.c From f032aa24d1a42c55bd3c4056da6040ba6d61bb32 Mon Sep 17 00:00:00 2001 From: nminaylov Date: Mon, 25 Sep 2023 19:57:36 +0300 Subject: [PATCH 09/31] MJS thread, MJS lib modified to support script interruption --- applications/system/mjs/application.fam | 2 +- applications/system/mjs/assets/delay.js | 9 + applications/system/mjs/lib/mjs/mjs_core.c | 15 +- applications/system/mjs/lib/mjs/mjs_core.h | 3 + .../system/mjs/lib/mjs/mjs_core_public.h | 11 +- applications/system/mjs/lib/mjs/mjs_exec.c | 14 +- applications/system/mjs/lib/mjs/mjs_ffi.c | 5 +- applications/system/mjs/lib/mjs/mjs_ffi.h | 2 - .../system/mjs/lib/mjs/mjs_ffi_public.h | 2 +- applications/system/mjs/m_js.c | 185 ++------------ applications/system/mjs/mjs_thread.c | 230 ++++++++++++++++++ applications/system/mjs/mjs_thread.h | 11 + 12 files changed, 310 insertions(+), 179 deletions(-) create mode 100644 applications/system/mjs/assets/delay.js create mode 100644 applications/system/mjs/mjs_thread.c create mode 100644 applications/system/mjs/mjs_thread.h diff --git a/applications/system/mjs/application.fam b/applications/system/mjs/application.fam index d9ea3bddf04..d7c197fddbb 100644 --- a/applications/system/mjs/application.fam +++ b/applications/system/mjs/application.fam @@ -4,7 +4,7 @@ App( fap_category="Tools", apptype=FlipperAppType.EXTERNAL, entry_point="m_js_app", - stack_size=8 * 1024, + stack_size=2 * 1024, cdefines=[ ("CS_PLATFORM", "CS_P_STM32"), "MJS_EXPOSE_PRIVATE", diff --git a/applications/system/mjs/assets/delay.js b/applications/system/mjs/assets/delay.js new file mode 100644 index 00000000000..9f64abee800 --- /dev/null +++ b/applications/system/mjs/assets/delay.js @@ -0,0 +1,9 @@ +print("start"); +delay(1000) +print("1"); +delay(1000) +print("2"); +delay(1000) +print("3"); +delay(1000) +print("end"); \ No newline at end of file diff --git a/applications/system/mjs/lib/mjs/mjs_core.c b/applications/system/mjs/lib/mjs/mjs_core.c index eec8f4eca73..661b4f31d74 100644 --- a/applications/system/mjs/lib/mjs/mjs_core.c +++ b/applications/system/mjs/lib/mjs/mjs_core.c @@ -69,9 +69,10 @@ void mjs_destroy(struct mjs* mjs) { free(mjs); } -struct mjs* mjs_create(void) { +struct mjs* mjs_create(void* context) { mjs_val_t global_object; struct mjs* mjs = calloc(1, sizeof(*mjs)); + mjs->context = context; mbuf_init(&mjs->stack, 0); mbuf_init(&mjs->call_stack, 0); mbuf_init(&mjs->arg_stack, 0); @@ -114,7 +115,7 @@ struct mjs* mjs_create(void) { global_object = mjs_mk_object(mjs); mjs_init_builtin(mjs, global_object); - mjs_set_ffi_resolver(mjs, dlsym); + mjs_set_ffi_resolver(mjs, NULL, NULL); push_mjs_val(&mjs->scopes, global_object); mjs->vals.this_obj = MJS_UNDEFINED; mjs->vals.dataview_proto = MJS_UNDEFINED; @@ -135,6 +136,16 @@ mjs_err_t mjs_set_errorf(struct mjs* mjs, mjs_err_t err, const char* fmt, ...) { return err; } +void mjs_exit(struct mjs* mjs) { + free(mjs->error_msg); + mjs->error_msg = NULL; + mjs->error = MJS_NEED_EXIT; +} + +void mjs_set_flags_poller(struct mjs* mjs, mjs_flags_poller_t poller) { + mjs->flags_poller = poller; +} + mjs_err_t mjs_prepend_errorf(struct mjs* mjs, mjs_err_t err, const char* fmt, ...) { char* old_error_msg = mjs->error_msg; char* new_error_msg = NULL; diff --git a/applications/system/mjs/lib/mjs/mjs_core.h b/applications/system/mjs/lib/mjs/mjs_core.h index b236b2dd180..f767410717b 100644 --- a/applications/system/mjs/lib/mjs/mjs_core.h +++ b/applications/system/mjs/lib/mjs/mjs_core.h @@ -125,8 +125,11 @@ struct mjs { char* stack_trace; enum mjs_err error; mjs_ffi_resolver_t* dlsym; /* Symbol resolver function for FFI */ + void* dlsym_handle; ffi_cb_args_t* ffi_cb_args; /* List of FFI args descriptors */ size_t cur_bcode_offset; + mjs_flags_poller_t flags_poller; + void* context; struct gc_arena object_arena; struct gc_arena property_arena; diff --git a/applications/system/mjs/lib/mjs/mjs_core_public.h b/applications/system/mjs/lib/mjs/mjs_core_public.h index 387fc3fec41..6fbe754ce75 100644 --- a/applications/system/mjs/lib/mjs/mjs_core_public.h +++ b/applications/system/mjs/lib/mjs/mjs_core_public.h @@ -76,12 +76,17 @@ typedef enum mjs_err { MJS_FILE_READ_ERROR, MJS_BAD_ARGS_ERROR, + MJS_NEED_EXIT, + MJS_ERRS_CNT } mjs_err_t; + +typedef void (*mjs_flags_poller_t)(struct mjs* mjs); + struct mjs; /* Create MJS instance */ -struct mjs* mjs_create(); +struct mjs* mjs_create(void* context); struct mjs_create_opts { /* use non-default bytecode definition file, testing-only */ @@ -177,6 +182,10 @@ int mjs_disown(struct mjs* mjs, mjs_val_t* v); mjs_err_t mjs_set_errorf(struct mjs* mjs, mjs_err_t err, const char* fmt, ...); +void mjs_exit(struct mjs* mjs); + +void mjs_set_flags_poller(struct mjs* mjs, mjs_flags_poller_t poller); + /* * If there is no error message already set, then it's equal to * `mjs_set_errorf()`. diff --git a/applications/system/mjs/lib/mjs/mjs_exec.c b/applications/system/mjs/lib/mjs/mjs_exec.c index 43d13bfbf43..f38ad5e4dcf 100644 --- a/applications/system/mjs/lib/mjs/mjs_exec.c +++ b/applications/system/mjs/lib/mjs/mjs_exec.c @@ -395,13 +395,13 @@ static void exec_expr(struct mjs* mjs, int op) { } break; } - /* + /* * NOTE: TOK_LOGICAL_AND and TOK_LOGICAL_OR don't need to be here, because * they are just naturally handled by the short-circuit evaluation. * See PARSE_LTR_BINOP() macro in mjs_parser.c. */ - /* clang-format off */ + /* clang-format off */ case TOK_MINUS_ASSIGN: op_assign(mjs, TOK_MINUS); break; case TOK_PLUS_ASSIGN: op_assign(mjs, TOK_PLUS); break; case TOK_MUL_ASSIGN: op_assign(mjs, TOK_MUL); break; @@ -967,7 +967,17 @@ MJS_PRIVATE mjs_err_t mjs_execute(struct mjs* mjs, size_t off, mjs_val_t* res) { i = bp.data.len; break; } + + if(mjs->flags_poller) { + mjs->flags_poller(mjs); + } + if(mjs->error != MJS_OK) { + if(mjs->error == MJS_NEED_EXIT) { + mjs->error = MJS_OK; + goto clean; + } + mjs_gen_stack_trace(mjs, bp.start_idx + i - 1 /* undo the i++ */); /* restore stack lenghts */ diff --git a/applications/system/mjs/lib/mjs/mjs_ffi.c b/applications/system/mjs/lib/mjs/mjs_ffi.c index ccfc7ef15e1..8d4d7df0983 100644 --- a/applications/system/mjs/lib/mjs/mjs_ffi.c +++ b/applications/system/mjs/lib/mjs/mjs_ffi.c @@ -39,8 +39,9 @@ struct cbdata { int8_t userdata_idx; }; -void mjs_set_ffi_resolver(struct mjs* mjs, mjs_ffi_resolver_t* dlsym) { +void mjs_set_ffi_resolver(struct mjs* mjs, mjs_ffi_resolver_t* dlsym, void* handle) { mjs->dlsym = dlsym; + mjs->dlsym_handle = handle; } static mjs_ffi_ctype_t parse_cval_type(struct mjs* mjs, const char* s, const char* e) { @@ -188,7 +189,7 @@ MJS_PRIVATE mjs_err_t mjs_parse_ffi_signature( } snprintf(buf, sizeof(buf), "%.*s", (int)fn.len, fn.p); - sig->fn = (ffi_fn_t*)mjs->dlsym(RTLD_DEFAULT, buf); + sig->fn = (ffi_fn_t*)mjs->dlsym(mjs->dlsym_handle, buf); if(sig->fn == NULL) { ret = MJS_TYPE_ERROR; mjs_prepend_errorf(mjs, ret, "dlsym('%s') failed", buf); diff --git a/applications/system/mjs/lib/mjs/mjs_ffi.h b/applications/system/mjs/lib/mjs/mjs_ffi.h index 5d5782bef3f..64716cb2485 100644 --- a/applications/system/mjs/lib/mjs/mjs_ffi.h +++ b/applications/system/mjs/lib/mjs/mjs_ffi.h @@ -14,8 +14,6 @@ extern "C" { #endif /* __cplusplus */ -mjs_ffi_resolver_t dlsym; - #define MJS_CB_ARGS_MAX_CNT 6 #define MJS_CB_SIGNATURE_MAX_SIZE (MJS_CB_ARGS_MAX_CNT + 1 /* return type */) diff --git a/applications/system/mjs/lib/mjs/mjs_ffi_public.h b/applications/system/mjs/lib/mjs/mjs_ffi_public.h index 331fe2f17d5..194f25b9d53 100644 --- a/applications/system/mjs/lib/mjs/mjs_ffi_public.h +++ b/applications/system/mjs/lib/mjs/mjs_ffi_public.h @@ -29,7 +29,7 @@ enum mjs_ffi_ctype { typedef void*(mjs_ffi_resolver_t)(void* handle, const char* symbol); -void mjs_set_ffi_resolver(struct mjs* mjs, mjs_ffi_resolver_t* dlsym); +void mjs_set_ffi_resolver(struct mjs* mjs, mjs_ffi_resolver_t* dlsym, void* handle); #if defined(__cplusplus) } diff --git a/applications/system/mjs/m_js.c b/applications/system/mjs/m_js.c index d3f4afd2141..97552a58e4b 100644 --- a/applications/system/mjs/m_js.c +++ b/applications/system/mjs/m_js.c @@ -1,184 +1,30 @@ #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include #include #include #include -#include -#include -#include "addon_api/app_api_interface.h" +#include "mjs_thread.h" +#include -#define TAG "MJS" +#define TAG "MJS app" -static CompositeApiResolver* resolver; +typedef struct { + MjsThread* mjs_thread; +} MjsApp; -// TODO: mjs fix -void cs_log_printf(const char* fmt, ...) { - UNUSED(fmt); -} - -// TODO: mjs fix -int cs_log_print_prefix(enum cs_log_level level, const char* file, int ln) { - UNUSED(level); - UNUSED(file); - UNUSED(ln); - return 0; -} - -char* cs_read_file(const char* path, size_t* size) { - Storage* storage = furi_record_open(RECORD_STORAGE); - Stream* stream = file_stream_alloc(storage); - char* data = NULL; - if(!file_stream_open(stream, path, FSAM_READ, FSOM_OPEN_EXISTING)) { - } else { - *size = stream_size(stream); - data = (char*)malloc(*size + 1); - if(data != NULL) { - stream_rewind(stream); - if(stream_read(stream, (uint8_t*)data, *size) != *size) { - file_stream_close(stream); - furi_record_close(RECORD_STORAGE); - stream_free(stream); - free(data); - return NULL; - } - data[*size] = '\0'; - } - } - file_stream_close(stream); - furi_record_close(RECORD_STORAGE); - stream_free(stream); - return data; -} - -char* json_fread(const char* path) { - UNUSED(path); - return NULL; -} - -int json_vfprintf(const char* file_name, const char* fmt, va_list ap) { - UNUSED(file_name); - UNUSED(fmt); - UNUSED(ap); - return 0; -} - -int json_prettify_file(const char* file_name) { - UNUSED(file_name); - return 0; -} - -static void mjs_print(struct mjs* mjs) { - size_t i, num_args = mjs_nargs(mjs); - for(i = 0; i < num_args; i++) { - char* name = NULL; - size_t name_len = 0; - int need_free = 0; - mjs_val_t arg = mjs_arg(mjs, i); - mjs_err_t err = mjs_to_string(mjs, &arg, &name, &name_len, &need_free); - if(err != MJS_OK) { - printf("err %s ", mjs_strerror(mjs, err)); - } else { - printf("%s ", name); - } - - if(need_free) { - free(name); - name = NULL; - } - } - printf("\r\n"); - mjs_return(mjs, MJS_UNDEFINED); -} - -static void mjs_delay(struct mjs* mjs) { - double ms = mjs_get_int(mjs, mjs_arg(mjs, 0)); - furi_delay_ms(ms); - mjs_return(mjs, MJS_UNDEFINED); -} - -static void* my_dlsym(void* handle, const char* name) { - UNUSED(handle); - Elf32_Addr addr = 0; - uint32_t hash = elf_symbolname_hash(name); - const ElfApiInterface* api = composite_api_resolver_get(resolver); - - if(!api->resolver_callback(api, hash, &addr)) { - FURI_LOG_E(TAG, "FFI: cannot find \"%s\"", name); - return NULL; - } - - return (void*)addr; -} - -static void mjs_ffi_address(struct mjs* mjs) { - mjs_val_t name_v = mjs_arg(mjs, 0); - size_t len; - const char* name = mjs_get_string(mjs, &name_v, &len); - void* addr = my_dlsym(NULL, name); - mjs_return(mjs, mjs_mk_foreign(mjs, addr)); -} - -static void mjs_global_to_string(struct mjs* mjs) { - double num = mjs_get_int(mjs, mjs_arg(mjs, 0)); - char tmp_str[] = "-2147483648"; - itoa(num, tmp_str, 10); - mjs_val_t ret = mjs_mk_string(mjs, tmp_str, ~0, true); - mjs_return(mjs, ret); -} - -static void mjs_global_to_hex_string(struct mjs* mjs) { - double num = mjs_get_int(mjs, mjs_arg(mjs, 0)); - char tmp_str[] = "-FFFFFFFF"; - itoa(num, tmp_str, 16); - mjs_val_t ret = mjs_mk_string(mjs, tmp_str, ~0, true); - mjs_return(mjs, ret); -} - -#define MFS_MK_FN(fn) mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)fn) - -static int js_do(const char* path) { - struct mjs* mjs = mjs_create(); - mjs_val_t global = mjs_get_global(mjs); - mjs_set(mjs, global, "print", ~0, MFS_MK_FN(mjs_print)); - mjs_set(mjs, global, "delay", ~0, MFS_MK_FN(mjs_delay)); - mjs_set(mjs, global, "to_string", ~0, MFS_MK_FN(mjs_global_to_string)); - mjs_set(mjs, global, "to_hex_string", ~0, MFS_MK_FN(mjs_global_to_hex_string)); - mjs_set(mjs, global, "ffi_address", ~0, MFS_MK_FN(mjs_ffi_address)); - - mjs_set_ffi_resolver(mjs, my_dlsym); - - mjs_err_t err = mjs_exec_file(mjs, path, NULL); - - if(err != MJS_OK) { - FURI_LOG_E(TAG, "Exec error: %s", mjs_strerror(mjs, err)); - if(mjs->stack_trace != NULL) { - FURI_LOG_E(TAG, "Stack trace:\n%s", mjs->stack_trace); - } - } - - mjs_destroy(mjs); - return 0; +void mjs_done_callback(void* context) { + UNUSED(context); + FURI_LOG_I(TAG, "mjs_done_callback"); } int32_t m_js_app(void* arg) { UNUSED(arg); FuriString* name = furi_string_alloc_set(APP_ASSETS_PATH()); + MjsApp* app = malloc(sizeof(MjsApp)); + ViewDispatcher* view_dispatcher = view_dispatcher_alloc(); Loading* loading = loading_alloc(); - resolver = composite_api_resolver_alloc(); - composite_api_resolver_add(resolver, firmware_api_interface); - composite_api_resolver_add(resolver, application_api_interface); Gui* gui = furi_record_open("gui"); view_dispatcher_enable_queue(view_dispatcher); @@ -197,7 +43,10 @@ int32_t m_js_app(void* arg) { furi_record_close(RECORD_DIALOGS); } - js_do(furi_string_get_cstr(name)); + app->mjs_thread = mjs_thread_run(furi_string_get_cstr(name), mjs_done_callback, app); + // furi_delay_ms(3000); + // mjs_thread_stop(app->mjs_thread); + mjs_thread_free(app->mjs_thread); } while(false); view_dispatcher_remove_view(view_dispatcher, 0); @@ -205,8 +54,8 @@ int32_t m_js_app(void* arg) { view_dispatcher_free(view_dispatcher); furi_record_close("gui"); - composite_api_resolver_free(resolver); - furi_string_free(name); + + free(app); return 0; } \ No newline at end of file diff --git a/applications/system/mjs/mjs_thread.c b/applications/system/mjs/mjs_thread.c new file mode 100644 index 00000000000..31d108ef5f2 --- /dev/null +++ b/applications/system/mjs/mjs_thread.c @@ -0,0 +1,230 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "addon_api/app_api_interface.h" +#include "mjs_thread.h" + +#define TAG "MJS" + +struct MjsThread { + FuriThread* thread; + FuriString* path; + CompositeApiResolver* resolver; + MjsThreadCallback end_callback; + void* context; +}; + +typedef enum { + ThreadEventStop = (1 << 0), + ThreadEventTest = (1 << 1), +} WorkerEventFlags; + +// TODO: mjs fix +void cs_log_printf(const char* fmt, ...) { + UNUSED(fmt); +} + +// TODO: mjs fix +int cs_log_print_prefix(enum cs_log_level level, const char* file, int ln) { + UNUSED(level); + UNUSED(file); + UNUSED(ln); + return 0; +} + +char* cs_read_file(const char* path, size_t* size) { + Storage* storage = furi_record_open(RECORD_STORAGE); + Stream* stream = file_stream_alloc(storage); + char* data = NULL; + if(!file_stream_open(stream, path, FSAM_READ, FSOM_OPEN_EXISTING)) { + } else { + *size = stream_size(stream); + data = (char*)malloc(*size + 1); + if(data != NULL) { + stream_rewind(stream); + if(stream_read(stream, (uint8_t*)data, *size) != *size) { + file_stream_close(stream); + furi_record_close(RECORD_STORAGE); + stream_free(stream); + free(data); + return NULL; + } + data[*size] = '\0'; + } + } + file_stream_close(stream); + furi_record_close(RECORD_STORAGE); + stream_free(stream); + return data; +} + +char* json_fread(const char* path) { + UNUSED(path); + return NULL; +} + +int json_vfprintf(const char* file_name, const char* fmt, va_list ap) { + UNUSED(file_name); + UNUSED(fmt); + UNUSED(ap); + return 0; +} + +int json_prettify_file(const char* file_name) { + UNUSED(file_name); + return 0; +} + +static void mjs_print(struct mjs* mjs) { + size_t i, num_args = mjs_nargs(mjs); + for(i = 0; i < num_args; i++) { + char* name = NULL; + size_t name_len = 0; + int need_free = 0; + mjs_val_t arg = mjs_arg(mjs, i); + mjs_err_t err = mjs_to_string(mjs, &arg, &name, &name_len, &need_free); + if(err != MJS_OK) { + printf("err %s ", mjs_strerror(mjs, err)); + } else { + printf("%s ", name); + } + + if(need_free) { + free(name); + name = NULL; + } + } + printf("\r\n"); + mjs_return(mjs, MJS_UNDEFINED); +} + +static void mjs_exit_flag_poll(struct mjs* mjs) { + uint32_t flags = furi_thread_flags_wait(ThreadEventStop, FuriFlagWaitAny, 0); + if(flags & FuriFlagError) { + return; + } + if(flags & ThreadEventStop) { + mjs_exit(mjs); + } +} + +static void mjs_delay(struct mjs* mjs) { + int ms = mjs_get_int(mjs, mjs_arg(mjs, 0)); + uint32_t flags = furi_thread_flags_wait(ThreadEventStop, FuriFlagWaitAny, ms); + mjs_return(mjs, MJS_UNDEFINED); + if(flags & FuriFlagError) { + return; + } + if(flags & ThreadEventStop) { + mjs_exit(mjs); + } +} + +static void* my_dlsym(void* handle, const char* name) { + CompositeApiResolver* resolver = handle; + Elf32_Addr addr = 0; + uint32_t hash = elf_symbolname_hash(name); + const ElfApiInterface* api = composite_api_resolver_get(resolver); + + if(!api->resolver_callback(api, hash, &addr)) { + FURI_LOG_E(TAG, "FFI: cannot find \"%s\"", name); + return NULL; + } + + return (void*)addr; +} + +static void mjs_ffi_address(struct mjs* mjs) { + mjs_val_t name_v = mjs_arg(mjs, 0); + size_t len; + const char* name = mjs_get_string(mjs, &name_v, &len); + void* addr = my_dlsym(mjs->dlsym_handle, name); + mjs_return(mjs, mjs_mk_foreign(mjs, addr)); +} + +static void mjs_global_to_string(struct mjs* mjs) { + double num = mjs_get_int(mjs, mjs_arg(mjs, 0)); + char tmp_str[] = "-2147483648"; + itoa(num, tmp_str, 10); + mjs_val_t ret = mjs_mk_string(mjs, tmp_str, ~0, true); + mjs_return(mjs, ret); +} + +static void mjs_global_to_hex_string(struct mjs* mjs) { + double num = mjs_get_int(mjs, mjs_arg(mjs, 0)); + char tmp_str[] = "-FFFFFFFF"; + itoa(num, tmp_str, 16); + mjs_val_t ret = mjs_mk_string(mjs, tmp_str, ~0, true); + mjs_return(mjs, ret); +} + +#define MFS_MK_FN(fn) mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)fn) + +static int32_t mjs_thread(void* arg) { + MjsThread* worker = arg; + worker->resolver = composite_api_resolver_alloc(); + composite_api_resolver_add(worker->resolver, firmware_api_interface); + composite_api_resolver_add(worker->resolver, application_api_interface); + + struct mjs* mjs = mjs_create(worker); + mjs_val_t global = mjs_get_global(mjs); + mjs_set(mjs, global, "print", ~0, MFS_MK_FN(mjs_print)); + mjs_set(mjs, global, "delay", ~0, MFS_MK_FN(mjs_delay)); + mjs_set(mjs, global, "to_string", ~0, MFS_MK_FN(mjs_global_to_string)); + mjs_set(mjs, global, "to_hex_string", ~0, MFS_MK_FN(mjs_global_to_hex_string)); + mjs_set(mjs, global, "ffi_address", ~0, MFS_MK_FN(mjs_ffi_address)); + + mjs_set_ffi_resolver(mjs, my_dlsym, worker->resolver); + + mjs_set_flags_poller(mjs, mjs_exit_flag_poll); + + mjs_err_t err = mjs_exec_file(mjs, furi_string_get_cstr(worker->path), NULL); + + if(err != MJS_OK) { + FURI_LOG_E(TAG, "Exec error: %s", mjs_strerror(mjs, err)); + if(mjs->stack_trace != NULL) { + FURI_LOG_E(TAG, "Stack trace:\n%s", mjs->stack_trace); + } + } + + mjs_destroy(mjs); + + composite_api_resolver_free(worker->resolver); + + if(worker->end_callback) { + worker->end_callback(worker->context); + } + + return 0; +} + +MjsThread* mjs_thread_run(const char* script_path, MjsThreadCallback callback, void* context) { + MjsThread* worker = malloc(sizeof(MjsThread)); + worker->path = furi_string_alloc_set(script_path); + worker->thread = furi_thread_alloc_ex("MjsThread", 8 * 1024, mjs_thread, worker); + worker->end_callback = callback; + worker->context = context; + furi_thread_start(worker->thread); + return worker; +} + +void mjs_thread_stop(MjsThread* worker) { + furi_thread_flags_set(furi_thread_get_id(worker->thread), ThreadEventStop); +} + +void mjs_thread_free(MjsThread* worker) { + furi_thread_join(worker->thread); + furi_thread_free(worker->thread); + furi_string_free(worker->path); + free(worker); +} diff --git a/applications/system/mjs/mjs_thread.h b/applications/system/mjs/mjs_thread.h new file mode 100644 index 00000000000..1f5805199de --- /dev/null +++ b/applications/system/mjs/mjs_thread.h @@ -0,0 +1,11 @@ +#pragma once + +typedef struct MjsThread MjsThread; + +typedef void (*MjsThreadCallback)(void* context); + +MjsThread* mjs_thread_run(const char* script_path, MjsThreadCallback callback, void* context); + +void mjs_thread_stop(MjsThread* worker); + +void mjs_thread_free(MjsThread* worker); From c49334f813f3d88acd7191a36a076e4a9542979c Mon Sep 17 00:00:00 2001 From: nminaylov Date: Wed, 4 Oct 2023 15:38:28 +0300 Subject: [PATCH 10/31] JS console UI --- applications/system/mjs/application.fam | 2 +- applications/system/mjs/assets/print_test.js | 7 + applications/system/mjs/lib/mjs/mjs_core.c | 2 +- applications/system/mjs/m_js.c | 61 ------- applications/system/mjs/mjs_app.c | 131 +++++++++++++++ applications/system/mjs/mjs_app_i.h | 10 ++ applications/system/mjs/mjs_thread.c | 36 ++-- applications/system/mjs/mjs_thread.h | 10 +- applications/system/mjs/views/console_font.h | 43 +++++ applications/system/mjs/views/console_view.c | 165 +++++++++++++++++++ applications/system/mjs/views/console_view.h | 13 ++ 11 files changed, 406 insertions(+), 74 deletions(-) create mode 100644 applications/system/mjs/assets/print_test.js delete mode 100644 applications/system/mjs/m_js.c create mode 100644 applications/system/mjs/mjs_app.c create mode 100644 applications/system/mjs/mjs_app_i.h create mode 100644 applications/system/mjs/views/console_font.h create mode 100644 applications/system/mjs/views/console_view.c create mode 100644 applications/system/mjs/views/console_view.h diff --git a/applications/system/mjs/application.fam b/applications/system/mjs/application.fam index d7c197fddbb..c9d932ba193 100644 --- a/applications/system/mjs/application.fam +++ b/applications/system/mjs/application.fam @@ -3,7 +3,7 @@ App( name="MJS", fap_category="Tools", apptype=FlipperAppType.EXTERNAL, - entry_point="m_js_app", + entry_point="mjs_app", stack_size=2 * 1024, cdefines=[ ("CS_PLATFORM", "CS_P_STM32"), diff --git a/applications/system/mjs/assets/print_test.js b/applications/system/mjs/assets/print_test.js new file mode 100644 index 00000000000..1fc4949bc59 --- /dev/null +++ b/applications/system/mjs/assets/print_test.js @@ -0,0 +1,7 @@ +print("aaa", 1234, "0x" + to_hex_string(0xeba)); +print("q¢w€e𐍈rйt"); +print("qwertyuiopasdfghjklzxcvbnm1234567890-=poiuytrewqlkjhgfdsamnbvcxz=-0987654321"); +print("Line 1\nLine 2"); +// qwertyuiop +// print("\n"); +// print("a"); \ No newline at end of file diff --git a/applications/system/mjs/lib/mjs/mjs_core.c b/applications/system/mjs/lib/mjs/mjs_core.c index 661b4f31d74..be40c89c133 100644 --- a/applications/system/mjs/lib/mjs/mjs_core.c +++ b/applications/system/mjs/lib/mjs/mjs_core.c @@ -268,7 +268,7 @@ static void mjs_append_stack_trace_line(struct mjs* mjs, size_t offset) { const char* filename = mjs_get_bcode_filename_by_offset(mjs, offset); int line_no = mjs_get_lineno_by_offset(mjs, offset); char* new_line = NULL; - const char* fmt = " at %s:%d\n"; + const char* fmt = "at %s:%d\n"; if(filename == NULL) { // TODO: mjs fix // fprintf( diff --git a/applications/system/mjs/m_js.c b/applications/system/mjs/m_js.c deleted file mode 100644 index 97552a58e4b..00000000000 --- a/applications/system/mjs/m_js.c +++ /dev/null @@ -1,61 +0,0 @@ -#include -#include -#include -#include -#include -#include "mjs_thread.h" -#include - -#define TAG "MJS app" - -typedef struct { - MjsThread* mjs_thread; -} MjsApp; - -void mjs_done_callback(void* context) { - UNUSED(context); - FURI_LOG_I(TAG, "mjs_done_callback"); -} - -int32_t m_js_app(void* arg) { - UNUSED(arg); - FuriString* name = furi_string_alloc_set(APP_ASSETS_PATH()); - - MjsApp* app = malloc(sizeof(MjsApp)); - - ViewDispatcher* view_dispatcher = view_dispatcher_alloc(); - Loading* loading = loading_alloc(); - - Gui* gui = furi_record_open("gui"); - view_dispatcher_enable_queue(view_dispatcher); - view_dispatcher_attach_to_gui(view_dispatcher, gui, ViewDispatcherTypeFullscreen); - view_dispatcher_add_view(view_dispatcher, 0, loading_get_view(loading)); - view_dispatcher_switch_to_view(view_dispatcher, 0); - - do { - if(arg != NULL && strlen(arg) > 0) { - furi_string_set(name, (const char*)arg); - } else { - DialogsFileBrowserOptions browser_options; - dialog_file_browser_set_basic_options(&browser_options, ".js", NULL); - DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); - if(!dialog_file_browser_show(dialogs, name, name, &browser_options)) break; - furi_record_close(RECORD_DIALOGS); - } - - app->mjs_thread = mjs_thread_run(furi_string_get_cstr(name), mjs_done_callback, app); - // furi_delay_ms(3000); - // mjs_thread_stop(app->mjs_thread); - mjs_thread_free(app->mjs_thread); - } while(false); - - view_dispatcher_remove_view(view_dispatcher, 0); - loading_free(loading); - view_dispatcher_free(view_dispatcher); - furi_record_close("gui"); - - furi_string_free(name); - - free(app); - return 0; -} \ No newline at end of file diff --git a/applications/system/mjs/mjs_app.c b/applications/system/mjs/mjs_app.c new file mode 100644 index 00000000000..e6e7f9a3f89 --- /dev/null +++ b/applications/system/mjs/mjs_app.c @@ -0,0 +1,131 @@ +#include +#include "mjs_thread.h" +#include +#include "mjs_app_i.h" +#include + +#define TAG "MJS app" + +typedef struct { + MjsThread* mjs_thread; + Gui* gui; + ViewDispatcher* view_dispatcher; + Loading* loading; + MjsConsoleView* console_view; +} MjsApp; + +static uint32_t mjs_view_exit(void* context) { + UNUSED(context); + return VIEW_NONE; +} + +static void mjs_app_compact_trace(FuriString* trace_str) { + // Keep only first line + size_t line_end = furi_string_search_char(trace_str, '\n'); + if(line_end > 0) { + furi_string_left(trace_str, line_end); + } + + // Remove full path + FuriString* file_name = furi_string_alloc(); + size_t filename_start = furi_string_search_rchar(trace_str, '/'); + if(filename_start > 0) { + filename_start++; + furi_string_set_n( + file_name, trace_str, filename_start, furi_string_size(trace_str) - filename_start); + furi_string_printf(trace_str, "at %s", furi_string_get_cstr(file_name)); + } + + furi_string_free(file_name); +} + +static void mjs_callback(MjsThreadEvent event, const char* msg, void* context) { + MjsApp* app = context; + furi_assert(app); + + if(event == MjsThreadEventDone) { + FURI_LOG_I(TAG, "Script done"); + console_view_print(app->console_view, "--- DONE ---"); + } else if(event == MjsThreadEventPrint) { + console_view_print(app->console_view, msg); + } else if(event == MjsThreadEventError) { + console_view_print(app->console_view, "--- ERROR ---"); + console_view_print(app->console_view, msg); + } else if(event == MjsThreadEventErrorTrace) { + FuriString* compact_trace = furi_string_alloc_set_str(msg); + mjs_app_compact_trace(compact_trace); + console_view_print(app->console_view, furi_string_get_cstr(compact_trace)); + furi_string_free(compact_trace); + console_view_print(app->console_view, "See logs for full trace"); + } +} + +static MjsApp* mjs_app_alloc(void) { + MjsApp* app = malloc(sizeof(MjsApp)); + + app->view_dispatcher = view_dispatcher_alloc(); + app->loading = loading_alloc(); + + app->gui = furi_record_open("gui"); + view_dispatcher_enable_queue(app->view_dispatcher); + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + view_dispatcher_add_view( + app->view_dispatcher, MjsAppViewLoading, loading_get_view(app->loading)); + + app->console_view = console_view_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, MjsAppViewConsole, console_view_get_view(app->console_view)); + view_set_previous_callback(console_view_get_view(app->console_view), mjs_view_exit); + view_dispatcher_switch_to_view(app->view_dispatcher, MjsAppViewConsole); + + return app; +} + +static void mjs_app_free(MjsApp* app) { + console_view_free(app->console_view); + view_dispatcher_remove_view(app->view_dispatcher, MjsAppViewConsole); + loading_free(app->loading); + view_dispatcher_remove_view(app->view_dispatcher, MjsAppViewLoading); + + view_dispatcher_free(app->view_dispatcher); + furi_record_close("gui"); + + free(app); +} + +int32_t mjs_app(void* arg) { + MjsApp* app = mjs_app_alloc(); + + FuriString* script_path = furi_string_alloc_set(APP_ASSETS_PATH()); + do { + if(arg != NULL && strlen(arg) > 0) { + furi_string_set(script_path, (const char*)arg); + } else { + DialogsFileBrowserOptions browser_options; + dialog_file_browser_set_basic_options(&browser_options, ".js", NULL); + DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); + if(!dialog_file_browser_show(dialogs, script_path, script_path, &browser_options)) + break; + furi_record_close(RECORD_DIALOGS); + } + FuriString* name = furi_string_alloc(); + path_extract_filename(script_path, name, false); + FuriString* start_text = + furi_string_alloc_printf("Running %s", furi_string_get_cstr(name)); + console_view_print(app->console_view, furi_string_get_cstr(start_text)); + console_view_print(app->console_view, "------------"); + furi_string_free(name); + furi_string_free(start_text); + + app->mjs_thread = mjs_thread_run(furi_string_get_cstr(script_path), mjs_callback, app); + view_dispatcher_run(app->view_dispatcher); + + mjs_thread_stop(app->mjs_thread); + mjs_thread_free(app->mjs_thread); + } while(0); + + furi_string_free(script_path); + + mjs_app_free(app); + return 0; +} \ No newline at end of file diff --git a/applications/system/mjs/mjs_app_i.h b/applications/system/mjs/mjs_app_i.h new file mode 100644 index 00000000000..2b2391b530d --- /dev/null +++ b/applications/system/mjs/mjs_app_i.h @@ -0,0 +1,10 @@ +#include +#include +#include +#include +#include "views/console_view.h" + +typedef enum { + MjsAppViewConsole, + MjsAppViewLoading, +} MjsAppView; diff --git a/applications/system/mjs/mjs_thread.c b/applications/system/mjs/mjs_thread.c index 31d108ef5f2..58ca66275ef 100644 --- a/applications/system/mjs/mjs_thread.c +++ b/applications/system/mjs/mjs_thread.c @@ -20,7 +20,7 @@ struct MjsThread { FuriThread* thread; FuriString* path; CompositeApiResolver* resolver; - MjsThreadCallback end_callback; + MjsThreadCallback app_callback; void* context; }; @@ -86,6 +86,7 @@ int json_prettify_file(const char* file_name) { } static void mjs_print(struct mjs* mjs) { + FuriString* msg_str = furi_string_alloc(); size_t i, num_args = mjs_nargs(mjs); for(i = 0; i < num_args; i++) { char* name = NULL; @@ -94,17 +95,24 @@ static void mjs_print(struct mjs* mjs) { mjs_val_t arg = mjs_arg(mjs, i); mjs_err_t err = mjs_to_string(mjs, &arg, &name, &name_len, &need_free); if(err != MJS_OK) { - printf("err %s ", mjs_strerror(mjs, err)); + furi_string_cat_printf(msg_str, "err %s ", mjs_strerror(mjs, err)); } else { - printf("%s ", name); + furi_string_cat_printf(msg_str, "%s ", name); } - if(need_free) { free(name); name = NULL; } } - printf("\r\n"); + printf("%s\r\n", furi_string_get_cstr(msg_str)); + + MjsThread* worker = mjs->context; + furi_assert(worker); + if(worker->app_callback) { + worker->app_callback(MjsThreadEventPrint, furi_string_get_cstr(msg_str), worker->context); + } + + // TODO: print callback mjs_return(mjs, MJS_UNDEFINED); } @@ -177,6 +185,7 @@ static int32_t mjs_thread(void* arg) { composite_api_resolver_add(worker->resolver, application_api_interface); struct mjs* mjs = mjs_create(worker); + // js_modules_create(worker); mjs_val_t global = mjs_get_global(mjs); mjs_set(mjs, global, "print", ~0, MFS_MK_FN(mjs_print)); mjs_set(mjs, global, "delay", ~0, MFS_MK_FN(mjs_delay)); @@ -192,19 +201,26 @@ static int32_t mjs_thread(void* arg) { if(err != MJS_OK) { FURI_LOG_E(TAG, "Exec error: %s", mjs_strerror(mjs, err)); + if(worker->app_callback) { + worker->app_callback(MjsThreadEventError, mjs_strerror(mjs, err), worker->context); + } if(mjs->stack_trace != NULL) { FURI_LOG_E(TAG, "Stack trace:\n%s", mjs->stack_trace); + if(worker->app_callback) { + worker->app_callback(MjsThreadEventErrorTrace, mjs->stack_trace, worker->context); + } + } + } else { + if(worker->app_callback) { + worker->app_callback(MjsThreadEventDone, NULL, worker->context); } } + // js_modules_destroy(worker); mjs_destroy(mjs); composite_api_resolver_free(worker->resolver); - if(worker->end_callback) { - worker->end_callback(worker->context); - } - return 0; } @@ -212,7 +228,7 @@ MjsThread* mjs_thread_run(const char* script_path, MjsThreadCallback callback, v MjsThread* worker = malloc(sizeof(MjsThread)); worker->path = furi_string_alloc_set(script_path); worker->thread = furi_thread_alloc_ex("MjsThread", 8 * 1024, mjs_thread, worker); - worker->end_callback = callback; + worker->app_callback = callback; worker->context = context; furi_thread_start(worker->thread); return worker; diff --git a/applications/system/mjs/mjs_thread.h b/applications/system/mjs/mjs_thread.h index 1f5805199de..34d434e2d4f 100644 --- a/applications/system/mjs/mjs_thread.h +++ b/applications/system/mjs/mjs_thread.h @@ -2,7 +2,15 @@ typedef struct MjsThread MjsThread; -typedef void (*MjsThreadCallback)(void* context); +typedef enum { + MjsThreadEventDone, + MjsThreadEventError, + MjsThreadEventPrint, + MjsThreadEventErrorTrace, + // TODO: input wait, .... +} MjsThreadEvent; + +typedef void (*MjsThreadCallback)(MjsThreadEvent event, const char* msg, void* context); MjsThread* mjs_thread_run(const char* script_path, MjsThreadCallback callback, void* context); diff --git a/applications/system/mjs/views/console_font.h b/applications/system/mjs/views/console_font.h new file mode 100644 index 00000000000..d22110aee3f --- /dev/null +++ b/applications/system/mjs/views/console_font.h @@ -0,0 +1,43 @@ +#pragma once + +#include + +/* + Fontname: -misc-spleen-medium-r-normal--8-80-72-72-C-50-ISO10646-1 + Copyright: Copyright (c) 2018-2022, Frederic Cambus + Glyphs: 96/472 + BBX Build Mode: 2 +*/ +static const uint8_t u8g2_font_spleen5x8_mr[] = + "`\2\3\2\3\4\1\1\4\5\10\0\377\6\377\7\377\1\77\2\217\3\325 \6\305\372\274\2!\10\305" + "Zaw(\7\42\12\305:\245$JrV\0#\15\305\332I\62(\245$\31\224\62\0$\13\305Z" + "\331R\23\65e\214\0%\15\305zI\224\24\263\60)%!\0&\16\305ZY\22%\221\224$R\244" + "\244\0'\7\305Za\235\31(\10\305z\215\255\25\0)\10\305:i\261\255\6*\13\305\372X\24I" + "C$\225\1+\12\305\372h\30\15R\230\3,\10\305\372\314a\226\1-\10\305\372\344!'\1.\7" + "\305\372\34s\0/\13\305za\26fa\26\206\0\60\12\305\332R%\261\224\42\35\61\10\305\372\231\330" + "\66\3\62\12\305\332R\61\222\302!\6\63\12\305\332R-M\242H\7\64\14\305\272a\22%\321\220\205" + "\71\0\65\12\305\272C\22\256a\262\3\66\12\305\332R\70U\242H\7\67\13\305\272C\22\205Y\61G" + "\0\70\12\305\332RI\252D\221\16\71\12\305\332R%\212\306H\7:\10\305\372\264\34\317\1;\11\305" + "\372\264\34\12\263\14<\11\305\372HVL\313\0=\11\305\372\224!\36r\20>\11\305\332i\61\253#" + "\0\77\12\305:R\61\253C\71\2@\13\305\332R%Q\22%\235\1A\14\305\332R%J\206$J" + "\242\30B\12\305\272Se\252D\311\16C\10\305\332K\330:\3D\14\305\272S%J\242$Jv\0" + "E\11\305\332K\70\205\351\14F\12\305\332K\30Na\16\1G\14\305\332K\230(Q\22E\63\0H" + "\16\305\272Q\22%C\22%Q\22\305\0I\10\305\332[\330\66\3J\11\305\332[\330\244#\0K\14" + "\305\272Q\22%S%J\242\30L\7\305\272a\327\31M\16\305\272Q\62$C\22%Q\22\305\0N" + "\15\305\272Q\242$JEI\224(\6O\14\305\332R%J\242$\212t\0P\13\305\272S%J\246" + "\60\207\0Q\14\305\332R%J\242$\212D\5R\13\305\272S%J\246J\24\3S\11\305\332K\252" + "\206\311\16T\10\305\272\203\24v\7U\15\305\272Q\22%Q\22%Q\64\3V\14\305\272Q\22%Q" + "\22E\232\16W\16\305\272Q\22%Q\62$C\22\305\0X\14\305\272Q\22E\232T\211b\0Y\14" + "\305\272Q\22%Q\64&;\0Z\12\305\272C\230\65\16\61\0[\10\305:S\330\343\2\134\13\305\32" + "a\32\246a\32&\0]\10\305:c\237\26\0^\11\305\372YR\313\311\0_\7\305\372\334\207\4`" + "\7\305:i\316\21a\12\305\372\240\32-Q\64\3b\14\305\32a\70U\242$Jv\0c\11\305\372" + "\340\22Vg\0d\14\305za\264DI\224D\321\14e\13\305\372\340\22%C\222\316\0f\12\305Z" + "R\230ma\35\1g\14\305\372\340\22%Q\244&\23\0h\14\305\32a\70U\242$J\242\30i\11" + "\305\372\71\42\26e\0j\11\305\372\71\24\66i\0k\13\305\32a))iIT\6l\10\305:a" + "\257\62\0m\15\305\372X\224\14\311\220DI\24\3n\14\305\372\330T\211\222(\211b\0o\13\305\372" + "\240T\211\222(\322\1p\13\305\372\330T\211\222)\14\1q\13\305\372\340\22%Q\64V\0r\12\305" + "\372\340\22%a\35\2s\11\305\372\340\222\252\311\16t\11\305:a\266\205U\31u\14\305\372X\224D" + "I\224D\321\14v\14\305\372X\224DI\24i:\0w\15\305\372X\224D\311\220\14I\24\3x\13" + "\305\372X\24iR%\212\1y\14\305\372X\224DI\24\215\311\4z\12\305\372\330\20f\265!\6{" + "\12\305ZR\230\31\253\12\0|\7\305Za\77\1}\13\305\32j\30jZ\30i\0~\11\305\372\244" + "H\321I\0\177\6\305\372\274\2\0\0\0\4\377\377\0"; diff --git a/applications/system/mjs/views/console_view.c b/applications/system/mjs/views/console_view.c new file mode 100644 index 00000000000..84343786945 --- /dev/null +++ b/applications/system/mjs/views/console_view.c @@ -0,0 +1,165 @@ +#include "../mjs_app_i.h" +#include "console_font.h" + +#define CONSOLE_LINES 8 +#define CONSOLE_CHAR_W 5 +#define LINE_BREAKS_MAX 3 +#define LINE_LEN_MAX (128 / CONSOLE_CHAR_W) + +struct MjsConsoleView { + View* view; +}; + +typedef struct { + FuriString* text[CONSOLE_LINES]; +} MjsConsoleViewModel; + +static void console_view_draw_callback(Canvas* canvas, void* _model) { + MjsConsoleViewModel* model = _model; + + canvas_set_color(canvas, ColorBlack); + canvas_set_custom_u8g2_font(canvas, u8g2_font_spleen5x8_mr); + uint8_t line_h = canvas_current_font_height(canvas); + + for(size_t i = 0; i < CONSOLE_LINES; i++) { + canvas_draw_str(canvas, 0, (i + 1) * line_h - 1, furi_string_get_cstr(model->text[i])); + if(furi_string_size(model->text[i]) > LINE_LEN_MAX) { + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 128 - 7, (i + 1) * line_h - 1, "..."); + canvas_set_custom_u8g2_font(canvas, u8g2_font_spleen5x8_mr); + } + } +} + +static bool console_view_input_callback(InputEvent* event, void* context) { + UNUSED(event); + UNUSED(context); + // TODO: -> сallback -> queue -> mjs thread + return false; +} + +void console_view_push_line(MjsConsoleView* console_view, const char* text, bool line_trimmed) { + with_view_model( + console_view->view, + MjsConsoleViewModel * model, + { + FuriString* str_temp = model->text[0]; + for(size_t i = 0; i < CONSOLE_LINES - 1; i++) { + model->text[i] = model->text[i + 1]; + } + if(!line_trimmed) { + furi_string_printf(str_temp, "%.*s", LINE_LEN_MAX, text); + } else { + // Leave some space for dots + furi_string_printf(str_temp, "%.*s ", LINE_LEN_MAX - 1, text); + } + model->text[CONSOLE_LINES - 1] = str_temp; + }, + true); +} + +void console_view_print(MjsConsoleView* console_view, const char* text) { + char line_buf[LINE_LEN_MAX + 1]; + uint8_t line_buf_cnt = 0; + uint8_t utf8_bytes_left = 0; + uint8_t line_break_cnt = 0; + bool line_trim = false; + + for(size_t i = 0; i < strlen(text); i++) { + if(text[i] & 0x80) { // UTF8 or another non-ascii character byte + if(utf8_bytes_left > 0) { + utf8_bytes_left--; + if(utf8_bytes_left == 0) { + line_buf[line_buf_cnt++] = '?'; + } + } else { + if((text[i] & 0xE0) == 0xC0) { + utf8_bytes_left = 1; + } else if((text[i] & 0xF0) == 0xE0) { + utf8_bytes_left = 2; + } else if((text[i] & 0xF8) == 0xF0) { + utf8_bytes_left = 3; + } else { + line_buf[line_buf_cnt++] = '?'; + } + } + } else { + if(utf8_bytes_left > 0) { + utf8_bytes_left = 0; + line_buf[line_buf_cnt++] = '?'; + if(line_buf_cnt >= LINE_LEN_MAX) { + line_break_cnt++; + if(line_break_cnt >= LINE_BREAKS_MAX) { + line_trim = true; + break; + } + line_buf[line_buf_cnt] = '\0'; + console_view_push_line(console_view, line_buf, false); + line_buf_cnt = 1; + line_buf[0] = ' '; + } + } + + if(text[i] == '\n') { + line_buf[line_buf_cnt] = '\0'; + line_buf_cnt = 0; + console_view_push_line(console_view, line_buf, false); + } else { + line_buf[line_buf_cnt++] = text[i]; + } + + if(line_buf_cnt >= LINE_LEN_MAX) { + line_break_cnt++; + if(line_break_cnt >= LINE_BREAKS_MAX) { + line_trim = true; + break; + } + line_buf[line_buf_cnt] = '\0'; + console_view_push_line(console_view, line_buf, false); + line_buf_cnt = 1; + line_buf[0] = ' '; + } + } + } + if(line_buf_cnt > 0) { + line_buf[line_buf_cnt] = '\0'; + console_view_push_line(console_view, line_buf, line_trim); + } +} + +MjsConsoleView* console_view_alloc(void) { + MjsConsoleView* console_view = malloc(sizeof(MjsConsoleView)); + console_view->view = view_alloc(); + view_set_draw_callback(console_view->view, console_view_draw_callback); + view_set_input_callback(console_view->view, console_view_input_callback); + view_allocate_model(console_view->view, ViewModelTypeLocking, sizeof(MjsConsoleViewModel)); + + with_view_model( + console_view->view, + MjsConsoleViewModel * model, + { + for(size_t i = 0; i < CONSOLE_LINES; i++) { + model->text[i] = furi_string_alloc(); + } + }, + true); + return console_view; +} + +void console_view_free(MjsConsoleView* console_view) { + with_view_model( + console_view->view, + MjsConsoleViewModel * model, + { + for(size_t i = 0; i < CONSOLE_LINES; i++) { + furi_string_free(model->text[i]); + } + }, + false); + view_free(console_view->view); + free(console_view); +} + +View* console_view_get_view(MjsConsoleView* console_view) { + return console_view->view; +} \ No newline at end of file diff --git a/applications/system/mjs/views/console_view.h b/applications/system/mjs/views/console_view.h new file mode 100644 index 00000000000..41edcd9d7da --- /dev/null +++ b/applications/system/mjs/views/console_view.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +typedef struct MjsConsoleView MjsConsoleView; + +MjsConsoleView* console_view_alloc(void); + +void console_view_free(MjsConsoleView* console_view); + +View* console_view_get_view(MjsConsoleView* console_view); + +void console_view_print(MjsConsoleView* console_view, const char* text); From f972937a34e4c5f90058a426a1248f28b818cf68 Mon Sep 17 00:00:00 2001 From: nminaylov Date: Fri, 6 Oct 2023 20:56:45 +0300 Subject: [PATCH 11/31] Module system, BadUSB bindings rework --- applications/system/mjs/application.fam | 4 +- applications/system/mjs/assets/badusb.js | 28 ++ applications/system/mjs/assets/console.js | 5 + applications/system/mjs/assets/require_api.js | 4 + .../system/mjs/{mjs_app.c => js_app.c} | 58 +-- .../system/mjs/{mjs_app_i.h => js_app_i.h} | 6 +- applications/system/mjs/js_modules.c | 71 ++++ applications/system/mjs/js_modules.h | 10 + .../system/mjs/{mjs_thread.c => js_thread.c} | 177 ++++++-- applications/system/mjs/js_thread.h | 19 + applications/system/mjs/js_thread_i.h | 19 + applications/system/mjs/mjs_thread.h | 19 - applications/system/mjs/modules/js_badusb.c | 394 ++++++++++++++++++ applications/system/mjs/modules/js_badusb.h | 6 + applications/system/mjs/modules/js_badusb.md | 82 ++++ applications/system/mjs/modules/js_flipper.c | 36 ++ applications/system/mjs/modules/js_flipper.h | 4 + applications/system/mjs/views/console_view.c | 28 +- applications/system/mjs/views/console_view.h | 10 +- 19 files changed, 870 insertions(+), 110 deletions(-) create mode 100644 applications/system/mjs/assets/badusb.js create mode 100644 applications/system/mjs/assets/console.js create mode 100644 applications/system/mjs/assets/require_api.js rename applications/system/mjs/{mjs_app.c => js_app.c} (70%) rename applications/system/mjs/{mjs_app_i.h => js_app_i.h} (71%) create mode 100644 applications/system/mjs/js_modules.c create mode 100644 applications/system/mjs/js_modules.h rename applications/system/mjs/{mjs_thread.c => js_thread.c} (55%) create mode 100644 applications/system/mjs/js_thread.h create mode 100644 applications/system/mjs/js_thread_i.h delete mode 100644 applications/system/mjs/mjs_thread.h create mode 100644 applications/system/mjs/modules/js_badusb.c create mode 100644 applications/system/mjs/modules/js_badusb.h create mode 100644 applications/system/mjs/modules/js_badusb.md create mode 100644 applications/system/mjs/modules/js_flipper.c create mode 100644 applications/system/mjs/modules/js_flipper.h diff --git a/applications/system/mjs/application.fam b/applications/system/mjs/application.fam index c9d932ba193..2dc9267ae5f 100644 --- a/applications/system/mjs/application.fam +++ b/applications/system/mjs/application.fam @@ -1,9 +1,9 @@ App( - appid="m_js", + appid="mjs", name="MJS", fap_category="Tools", apptype=FlipperAppType.EXTERNAL, - entry_point="mjs_app", + entry_point="js_app", stack_size=2 * 1024, cdefines=[ ("CS_PLATFORM", "CS_P_STM32"), diff --git a/applications/system/mjs/assets/badusb.js b/applications/system/mjs/assets/badusb.js new file mode 100644 index 00000000000..071453fdd28 --- /dev/null +++ b/applications/system/mjs/assets/badusb.js @@ -0,0 +1,28 @@ +let badusb = require("badusb"); + +badusb.setup({ vid: 0xAAAA, pid: 0xBBBB, mfr_name: "manufacturer", prod_name: "product" }); +delay(1000); + +if (badusb.isConnected()) { + print("Connected"); + badusb.press("a"); + badusb.press("A"); + badusb.press(9); + badusb.press(0x0f); + badusb.press("SHIFT", 0x0C); + badusb.press("CTRL", "a"); + delay(500); + badusb.press("RIGHT"); + badusb.hold("a"); + delay(2000); + badusb.release("a"); + badusb.hold("SHIFT", "b"); + delay(2000); + badusb.release(); + badusb.print("Hello") + badusb.println("abcd") + badusb.print("12345", 1000); + delay(1000); +} else { + print("USB not connected"); +} diff --git a/applications/system/mjs/assets/console.js b/applications/system/mjs/assets/console.js new file mode 100644 index 00000000000..06d394c533a --- /dev/null +++ b/applications/system/mjs/assets/console.js @@ -0,0 +1,5 @@ +print("print", 1); +console.log("log", 2); +console.warn("warn", 3); +console.error("error", 4); +console.debug("debug", 5); diff --git a/applications/system/mjs/assets/require_api.js b/applications/system/mjs/assets/require_api.js new file mode 100644 index 00000000000..b4b13483e09 --- /dev/null +++ b/applications/system/mjs/assets/require_api.js @@ -0,0 +1,4 @@ +let flipper = require("flipper"); +print("Name:", flipper.getName()); +print("Model:", flipper.getModel()); +print("Battery level:", flipper.getBatteryCharge()); \ No newline at end of file diff --git a/applications/system/mjs/mjs_app.c b/applications/system/mjs/js_app.c similarity index 70% rename from applications/system/mjs/mjs_app.c rename to applications/system/mjs/js_app.c index e6e7f9a3f89..a8cafdac81f 100644 --- a/applications/system/mjs/mjs_app.c +++ b/applications/system/mjs/js_app.c @@ -1,25 +1,25 @@ #include -#include "mjs_thread.h" +#include "js_thread.h" #include -#include "mjs_app_i.h" +#include "js_app_i.h" #include #define TAG "MJS app" typedef struct { - MjsThread* mjs_thread; + JsThread* js_thread; Gui* gui; ViewDispatcher* view_dispatcher; Loading* loading; - MjsConsoleView* console_view; -} MjsApp; + JsConsoleView* console_view; +} JsApp; -static uint32_t mjs_view_exit(void* context) { +static uint32_t js_view_exit(void* context) { UNUSED(context); return VIEW_NONE; } -static void mjs_app_compact_trace(FuriString* trace_str) { +static void js_app_compact_trace(FuriString* trace_str) { // Keep only first line size_t line_end = furi_string_search_char(trace_str, '\n'); if(line_end > 0) { @@ -39,29 +39,29 @@ static void mjs_app_compact_trace(FuriString* trace_str) { furi_string_free(file_name); } -static void mjs_callback(MjsThreadEvent event, const char* msg, void* context) { - MjsApp* app = context; +static void js_callback(JsThreadEvent event, const char* msg, void* context) { + JsApp* app = context; furi_assert(app); - if(event == MjsThreadEventDone) { + if(event == JsThreadEventDone) { FURI_LOG_I(TAG, "Script done"); console_view_print(app->console_view, "--- DONE ---"); - } else if(event == MjsThreadEventPrint) { + } else if(event == JsThreadEventPrint) { console_view_print(app->console_view, msg); - } else if(event == MjsThreadEventError) { + } else if(event == JsThreadEventError) { console_view_print(app->console_view, "--- ERROR ---"); console_view_print(app->console_view, msg); - } else if(event == MjsThreadEventErrorTrace) { + } else if(event == JsThreadEventErrorTrace) { FuriString* compact_trace = furi_string_alloc_set_str(msg); - mjs_app_compact_trace(compact_trace); + js_app_compact_trace(compact_trace); console_view_print(app->console_view, furi_string_get_cstr(compact_trace)); furi_string_free(compact_trace); console_view_print(app->console_view, "See logs for full trace"); } } -static MjsApp* mjs_app_alloc(void) { - MjsApp* app = malloc(sizeof(MjsApp)); +static JsApp* js_app_alloc(void) { + JsApp* app = malloc(sizeof(JsApp)); app->view_dispatcher = view_dispatcher_alloc(); app->loading = loading_alloc(); @@ -70,22 +70,22 @@ static MjsApp* mjs_app_alloc(void) { view_dispatcher_enable_queue(app->view_dispatcher); view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); view_dispatcher_add_view( - app->view_dispatcher, MjsAppViewLoading, loading_get_view(app->loading)); + app->view_dispatcher, JsAppViewLoading, loading_get_view(app->loading)); app->console_view = console_view_alloc(); view_dispatcher_add_view( - app->view_dispatcher, MjsAppViewConsole, console_view_get_view(app->console_view)); - view_set_previous_callback(console_view_get_view(app->console_view), mjs_view_exit); - view_dispatcher_switch_to_view(app->view_dispatcher, MjsAppViewConsole); + app->view_dispatcher, JsAppViewConsole, console_view_get_view(app->console_view)); + view_set_previous_callback(console_view_get_view(app->console_view), js_view_exit); + view_dispatcher_switch_to_view(app->view_dispatcher, JsAppViewConsole); return app; } -static void mjs_app_free(MjsApp* app) { +static void js_app_free(JsApp* app) { console_view_free(app->console_view); - view_dispatcher_remove_view(app->view_dispatcher, MjsAppViewConsole); + view_dispatcher_remove_view(app->view_dispatcher, JsAppViewConsole); loading_free(app->loading); - view_dispatcher_remove_view(app->view_dispatcher, MjsAppViewLoading); + view_dispatcher_remove_view(app->view_dispatcher, JsAppViewLoading); view_dispatcher_free(app->view_dispatcher); furi_record_close("gui"); @@ -93,8 +93,8 @@ static void mjs_app_free(MjsApp* app) { free(app); } -int32_t mjs_app(void* arg) { - MjsApp* app = mjs_app_alloc(); +int32_t js_app(void* arg) { + JsApp* app = js_app_alloc(); FuriString* script_path = furi_string_alloc_set(APP_ASSETS_PATH()); do { @@ -117,15 +117,15 @@ int32_t mjs_app(void* arg) { furi_string_free(name); furi_string_free(start_text); - app->mjs_thread = mjs_thread_run(furi_string_get_cstr(script_path), mjs_callback, app); + app->js_thread = js_thread_run(furi_string_get_cstr(script_path), js_callback, app); view_dispatcher_run(app->view_dispatcher); - mjs_thread_stop(app->mjs_thread); - mjs_thread_free(app->mjs_thread); + js_thread_stop(app->js_thread); + js_thread_free(app->js_thread); } while(0); furi_string_free(script_path); - mjs_app_free(app); + js_app_free(app); return 0; } \ No newline at end of file diff --git a/applications/system/mjs/mjs_app_i.h b/applications/system/mjs/js_app_i.h similarity index 71% rename from applications/system/mjs/mjs_app_i.h rename to applications/system/mjs/js_app_i.h index 2b2391b530d..bc958414065 100644 --- a/applications/system/mjs/mjs_app_i.h +++ b/applications/system/mjs/js_app_i.h @@ -5,6 +5,6 @@ #include "views/console_view.h" typedef enum { - MjsAppViewConsole, - MjsAppViewLoading, -} MjsAppView; + JsAppViewConsole, + JsAppViewLoading, +} JsAppView; diff --git a/applications/system/mjs/js_modules.c b/applications/system/mjs/js_modules.c new file mode 100644 index 00000000000..123dcb06cc3 --- /dev/null +++ b/applications/system/mjs/js_modules.c @@ -0,0 +1,71 @@ +#include +#include "js_modules.h" +#include "modules/js_flipper.h" +#include "modules/js_badusb.h" + +typedef void* (*JsModeConstructor)(struct mjs* mjs, mjs_val_t* object); +typedef void (*JsModeDestructor)(void* inst); + +static void* js_test_create(struct mjs* mjs, mjs_val_t* object) { + UNUSED(mjs); + FURI_LOG_E(TAG, "js_test_create"); + *object = MJS_UNDEFINED; + return (void*)1; +} + +static void js_test_destroy(void* inst) { + FURI_LOG_E(TAG, "js_test_destroy"); + UNUSED(inst); +} + +static const struct { + char* name; + JsModeConstructor create; + JsModeDestructor destroy; +} module_defs[] = { + {"test", js_test_create, js_test_destroy}, + {"flipper", js_flipper_create, NULL}, + {"badusb", js_badusb_create, js_badusb_destroy}}; + +struct JsModules { + struct mjs* mjs; + void* module_inst[COUNT_OF(module_defs)]; +}; + +JsModules* js_modules_create(struct mjs* mjs) { + JsModules* modules = malloc(sizeof(JsModules)); + modules->mjs = mjs; + return modules; +} + +void js_modules_destroy(JsModules* modules) { + for(size_t i = 0; i < COUNT_OF(module_defs); i++) { + if((module_defs[i].destroy) && (modules->module_inst[i])) { + module_defs[i].destroy(modules->module_inst[i]); + } + } + free(modules); +} + +mjs_val_t js_module_require(JsModules* modules, const char* name, size_t name_len) { + mjs_val_t module_object = MJS_UNDEFINED; + for(size_t i = 0; i < COUNT_OF(module_defs); i++) { + size_t name_compare_len = strlen(module_defs[i].name); + + if(name_compare_len != name_len) { + continue; + } + + if(strncmp(name, module_defs[i].name, name_compare_len) == 0) { + if(modules->module_inst[i]) { + break; + // TODO: "already exists" error + } + if(module_defs[i].create) { + modules->module_inst[i] = module_defs[i].create(modules->mjs, &module_object); + } + break; + } + } + return module_object; +} \ No newline at end of file diff --git a/applications/system/mjs/js_modules.h b/applications/system/mjs/js_modules.h new file mode 100644 index 00000000000..4e8f866c386 --- /dev/null +++ b/applications/system/mjs/js_modules.h @@ -0,0 +1,10 @@ +#pragma once +#include "js_thread_i.h" + +typedef struct JsModules JsModules; + +JsModules* js_modules_create(struct mjs* mjs); + +void js_modules_destroy(JsModules* modules); + +mjs_val_t js_module_require(JsModules* modules, const char* name, size_t name_len); diff --git a/applications/system/mjs/mjs_thread.c b/applications/system/mjs/js_thread.c similarity index 55% rename from applications/system/mjs/mjs_thread.c rename to applications/system/mjs/js_thread.c index 58ca66275ef..5be9a560f2b 100644 --- a/applications/system/mjs/mjs_thread.c +++ b/applications/system/mjs/js_thread.c @@ -1,27 +1,22 @@ -#include -#include -#include -#include -#include -#include -#include -#include #include #include #include #include #include #include "addon_api/app_api_interface.h" -#include "mjs_thread.h" +#include "js_thread.h" +#include "js_thread_i.h" +#include "js_modules.h" -#define TAG "MJS" +#define TAG "JS" -struct MjsThread { +struct JsThread { FuriThread* thread; FuriString* path; CompositeApiResolver* resolver; - MjsThreadCallback app_callback; + JsThreadCallback app_callback; void* context; + JsModules* modules; }; typedef enum { @@ -29,6 +24,30 @@ typedef enum { ThreadEventTest = (1 << 1), } WorkerEventFlags; +// static void obj_type(mjs_val_t v) { +// if(mjs_is_number(v)) { +// FURI_LOG_I(TAG, "mjs_is_number"); +// } else if(mjs_is_boolean(v)) { +// FURI_LOG_I(TAG, "mjs_is_boolean"); +// } else if(mjs_is_string(v)) { +// FURI_LOG_I(TAG, "mjs_is_string"); +// } else if(mjs_is_array(v)) { +// FURI_LOG_I(TAG, "mjs_is_array"); +// } else if(mjs_is_object(v)) { +// FURI_LOG_I(TAG, "mjs_is_object"); +// } else if(mjs_is_foreign(v)) { +// FURI_LOG_I(TAG, "mjs_is_foreign"); +// } else if(mjs_is_function(v)) { +// FURI_LOG_I(TAG, "mjs_is_function"); +// } else if(mjs_is_null(v)) { +// FURI_LOG_I(TAG, "mjs_is_null"); +// } else if(mjs_is_undefined(v)) { +// FURI_LOG_I(TAG, "mjs_is_undefined"); +// } else { +// FURI_LOG_I(TAG, "unknown"); +// } +// } + // TODO: mjs fix void cs_log_printf(const char* fmt, ...) { UNUSED(fmt); @@ -85,10 +104,9 @@ int json_prettify_file(const char* file_name) { return 0; } -static void mjs_print(struct mjs* mjs) { - FuriString* msg_str = furi_string_alloc(); - size_t i, num_args = mjs_nargs(mjs); - for(i = 0; i < num_args; i++) { +static void mjs_str_print(FuriString* msg_str, struct mjs* mjs) { + size_t num_args = mjs_nargs(mjs); + for(size_t i = 0; i < num_args; i++) { char* name = NULL; size_t name_len = 0; int need_free = 0; @@ -104,15 +122,54 @@ static void mjs_print(struct mjs* mjs) { name = NULL; } } +} + +static void mjs_print(struct mjs* mjs) { + FuriString* msg_str = furi_string_alloc(); + mjs_str_print(msg_str, mjs); + printf("%s\r\n", furi_string_get_cstr(msg_str)); - MjsThread* worker = mjs->context; + JsThread* worker = mjs->context; furi_assert(worker); if(worker->app_callback) { - worker->app_callback(MjsThreadEventPrint, furi_string_get_cstr(msg_str), worker->context); + worker->app_callback(JsThreadEventPrint, furi_string_get_cstr(msg_str), worker->context); } - // TODO: print callback + furi_string_free(msg_str); + + mjs_return(mjs, MJS_UNDEFINED); +} + +static void mjs_console_log(struct mjs* mjs) { + FuriString* msg_str = furi_string_alloc(); + mjs_str_print(msg_str, mjs); + FURI_LOG_I(TAG, "%s", furi_string_get_cstr(msg_str)); + furi_string_free(msg_str); + mjs_return(mjs, MJS_UNDEFINED); +} + +static void mjs_console_warn(struct mjs* mjs) { + FuriString* msg_str = furi_string_alloc(); + mjs_str_print(msg_str, mjs); + FURI_LOG_W(TAG, "%s", furi_string_get_cstr(msg_str)); + furi_string_free(msg_str); + mjs_return(mjs, MJS_UNDEFINED); +} + +static void mjs_console_error(struct mjs* mjs) { + FuriString* msg_str = furi_string_alloc(); + mjs_str_print(msg_str, mjs); + FURI_LOG_E(TAG, "%s", furi_string_get_cstr(msg_str)); + furi_string_free(msg_str); + mjs_return(mjs, MJS_UNDEFINED); +} + +static void mjs_console_debug(struct mjs* mjs) { + FuriString* msg_str = furi_string_alloc(); + mjs_str_print(msg_str, mjs); + FURI_LOG_D(TAG, "%s", furi_string_get_cstr(msg_str)); + furi_string_free(msg_str); mjs_return(mjs, MJS_UNDEFINED); } @@ -126,16 +183,36 @@ static void mjs_exit_flag_poll(struct mjs* mjs) { } } -static void mjs_delay(struct mjs* mjs) { - int ms = mjs_get_int(mjs, mjs_arg(mjs, 0)); - uint32_t flags = furi_thread_flags_wait(ThreadEventStop, FuriFlagWaitAny, ms); - mjs_return(mjs, MJS_UNDEFINED); +bool js_delay_with_flags(struct mjs* mjs, uint32_t time) { + uint32_t flags = furi_thread_flags_wait(ThreadEventStop, FuriFlagWaitAny, time); if(flags & FuriFlagError) { - return; + return false; } if(flags & ThreadEventStop) { mjs_exit(mjs); + return true; } + return false; +} + +static void mjs_delay(struct mjs* mjs) { + bool args_correct = false; + int ms = 0; + + if(mjs_nargs(mjs) == 1) { + mjs_val_t arg = mjs_arg(mjs, 0); + if(mjs_is_number(arg)) { + ms = mjs_get_int(mjs, arg); + args_correct = true; + } + } + if(!args_correct) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + js_delay_with_flags(mjs, ms); + mjs_return(mjs, MJS_UNDEFINED); } static void* my_dlsym(void* handle, const char* name) { @@ -160,6 +237,24 @@ static void mjs_ffi_address(struct mjs* mjs) { mjs_return(mjs, mjs_mk_foreign(mjs, addr)); } +static void mjs_require(struct mjs* mjs) { + mjs_val_t name_v = mjs_arg(mjs, 0); + size_t len; + const char* name = mjs_get_string(mjs, &name_v, &len); + mjs_val_t req_object = MJS_UNDEFINED; + if((len == 0) || (name == NULL)) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "String argument is expected"); + } else { + JsThread* worker = mjs->context; + furi_assert(worker); + req_object = js_module_require(worker->modules, name, len); + if(req_object == MJS_UNDEFINED) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "\"%s\" module not found", name); + } + } + mjs_return(mjs, req_object); +} + static void mjs_global_to_string(struct mjs* mjs) { double num = mjs_get_int(mjs, mjs_arg(mjs, 0)); char tmp_str[] = "-2147483648"; @@ -176,22 +271,28 @@ static void mjs_global_to_hex_string(struct mjs* mjs) { mjs_return(mjs, ret); } -#define MFS_MK_FN(fn) mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)fn) - -static int32_t mjs_thread(void* arg) { - MjsThread* worker = arg; +static int32_t js_thread(void* arg) { + JsThread* worker = arg; worker->resolver = composite_api_resolver_alloc(); composite_api_resolver_add(worker->resolver, firmware_api_interface); composite_api_resolver_add(worker->resolver, application_api_interface); struct mjs* mjs = mjs_create(worker); - // js_modules_create(worker); + worker->modules = js_modules_create(mjs); mjs_val_t global = mjs_get_global(mjs); mjs_set(mjs, global, "print", ~0, MFS_MK_FN(mjs_print)); mjs_set(mjs, global, "delay", ~0, MFS_MK_FN(mjs_delay)); mjs_set(mjs, global, "to_string", ~0, MFS_MK_FN(mjs_global_to_string)); mjs_set(mjs, global, "to_hex_string", ~0, MFS_MK_FN(mjs_global_to_hex_string)); mjs_set(mjs, global, "ffi_address", ~0, MFS_MK_FN(mjs_ffi_address)); + mjs_set(mjs, global, "require", ~0, MFS_MK_FN(mjs_require)); + + mjs_val_t console_obj = mjs_mk_object(mjs); + mjs_set(mjs, console_obj, "log", ~0, MFS_MK_FN(mjs_console_log)); + mjs_set(mjs, console_obj, "warn", ~0, MFS_MK_FN(mjs_console_warn)); + mjs_set(mjs, console_obj, "error", ~0, MFS_MK_FN(mjs_console_error)); + mjs_set(mjs, console_obj, "debug", ~0, MFS_MK_FN(mjs_console_debug)); + mjs_set(mjs, global, "console", ~0, console_obj); mjs_set_ffi_resolver(mjs, my_dlsym, worker->resolver); @@ -202,43 +303,43 @@ static int32_t mjs_thread(void* arg) { if(err != MJS_OK) { FURI_LOG_E(TAG, "Exec error: %s", mjs_strerror(mjs, err)); if(worker->app_callback) { - worker->app_callback(MjsThreadEventError, mjs_strerror(mjs, err), worker->context); + worker->app_callback(JsThreadEventError, mjs_strerror(mjs, err), worker->context); } if(mjs->stack_trace != NULL) { FURI_LOG_E(TAG, "Stack trace:\n%s", mjs->stack_trace); if(worker->app_callback) { - worker->app_callback(MjsThreadEventErrorTrace, mjs->stack_trace, worker->context); + worker->app_callback(JsThreadEventErrorTrace, mjs->stack_trace, worker->context); } } } else { if(worker->app_callback) { - worker->app_callback(MjsThreadEventDone, NULL, worker->context); + worker->app_callback(JsThreadEventDone, NULL, worker->context); } } - // js_modules_destroy(worker); mjs_destroy(mjs); + js_modules_destroy(worker->modules); composite_api_resolver_free(worker->resolver); return 0; } -MjsThread* mjs_thread_run(const char* script_path, MjsThreadCallback callback, void* context) { - MjsThread* worker = malloc(sizeof(MjsThread)); +JsThread* js_thread_run(const char* script_path, JsThreadCallback callback, void* context) { + JsThread* worker = malloc(sizeof(JsThread)); worker->path = furi_string_alloc_set(script_path); - worker->thread = furi_thread_alloc_ex("MjsThread", 8 * 1024, mjs_thread, worker); + worker->thread = furi_thread_alloc_ex("JsThread", 8 * 1024, js_thread, worker); worker->app_callback = callback; worker->context = context; furi_thread_start(worker->thread); return worker; } -void mjs_thread_stop(MjsThread* worker) { +void js_thread_stop(JsThread* worker) { furi_thread_flags_set(furi_thread_get_id(worker->thread), ThreadEventStop); } -void mjs_thread_free(MjsThread* worker) { +void js_thread_free(JsThread* worker) { furi_thread_join(worker->thread); furi_thread_free(worker->thread); furi_string_free(worker->path); diff --git a/applications/system/mjs/js_thread.h b/applications/system/mjs/js_thread.h new file mode 100644 index 00000000000..c18d3d7f26a --- /dev/null +++ b/applications/system/mjs/js_thread.h @@ -0,0 +1,19 @@ +#pragma once + +typedef struct JsThread JsThread; + +typedef enum { + JsThreadEventDone, + JsThreadEventError, + JsThreadEventPrint, + JsThreadEventErrorTrace, + // TODO: input wait, .... +} JsThreadEvent; + +typedef void (*JsThreadCallback)(JsThreadEvent event, const char* msg, void* context); + +JsThread* js_thread_run(const char* script_path, JsThreadCallback callback, void* context); + +void js_thread_stop(JsThread* worker); + +void js_thread_free(JsThread* worker); diff --git a/applications/system/mjs/js_thread_i.h b/applications/system/mjs/js_thread_i.h new file mode 100644 index 00000000000..87faa5bf7bc --- /dev/null +++ b/applications/system/mjs/js_thread_i.h @@ -0,0 +1,19 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TAG "JS" + +#define INST_PROP_NAME "_" + +#define MFS_MK_FN(fn) mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)fn) + +bool js_delay_with_flags(struct mjs* mjs, uint32_t time); diff --git a/applications/system/mjs/mjs_thread.h b/applications/system/mjs/mjs_thread.h deleted file mode 100644 index 34d434e2d4f..00000000000 --- a/applications/system/mjs/mjs_thread.h +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once - -typedef struct MjsThread MjsThread; - -typedef enum { - MjsThreadEventDone, - MjsThreadEventError, - MjsThreadEventPrint, - MjsThreadEventErrorTrace, - // TODO: input wait, .... -} MjsThreadEvent; - -typedef void (*MjsThreadCallback)(MjsThreadEvent event, const char* msg, void* context); - -MjsThread* mjs_thread_run(const char* script_path, MjsThreadCallback callback, void* context); - -void mjs_thread_stop(MjsThread* worker); - -void mjs_thread_free(MjsThread* worker); diff --git a/applications/system/mjs/modules/js_badusb.c b/applications/system/mjs/modules/js_badusb.c new file mode 100644 index 00000000000..a538b93ba97 --- /dev/null +++ b/applications/system/mjs/modules/js_badusb.c @@ -0,0 +1,394 @@ +#include +#include "js_modules.h" +#include + +typedef struct { + FuriHalUsbHidConfig* hid_cfg; + FuriHalUsbInterface* usb_if_prev; + uint8_t key_hold_cnt; +} JsBadusbInst; + +static const struct { + char* name; + uint16_t code; +} key_codes[] = { + {"CTRL", KEY_MOD_LEFT_CTRL}, + {"SHIFT", KEY_MOD_LEFT_SHIFT}, + {"ALT", KEY_MOD_LEFT_ALT}, + {"GUI", KEY_MOD_LEFT_GUI}, + + {"DOWN", HID_KEYBOARD_DOWN_ARROW}, + {"LEFT", HID_KEYBOARD_LEFT_ARROW}, + {"RIGHT", HID_KEYBOARD_RIGHT_ARROW}, + {"UP", HID_KEYBOARD_UP_ARROW}, + + {"ENTER", HID_KEYBOARD_RETURN}, + {"PAUSE", HID_KEYBOARD_PAUSE}, + {"CAPSLOCK", HID_KEYBOARD_CAPS_LOCK}, + {"DELETE", HID_KEYBOARD_DELETE_FORWARD}, + {"BACKSPACE", HID_KEYBOARD_DELETE}, + {"END", HID_KEYBOARD_END}, + {"ESC", HID_KEYBOARD_ESCAPE}, + {"HOME", HID_KEYBOARD_HOME}, + {"INSERT", HID_KEYBOARD_INSERT}, + {"NUMLOCK", HID_KEYPAD_NUMLOCK}, + {"PAGEUP", HID_KEYBOARD_PAGE_UP}, + {"PAGEDOWN", HID_KEYBOARD_PAGE_DOWN}, + {"PRINTSCREEN", HID_KEYBOARD_PRINT_SCREEN}, + {"SCROLLLOCK", HID_KEYBOARD_SCROLL_LOCK}, + {"SPACE", HID_KEYBOARD_SPACEBAR}, + {"TAB", HID_KEYBOARD_TAB}, + {"MENU", HID_KEYBOARD_APPLICATION}, + + {"F1", HID_KEYBOARD_F1}, + {"F2", HID_KEYBOARD_F2}, + {"F3", HID_KEYBOARD_F3}, + {"F4", HID_KEYBOARD_F4}, + {"F5", HID_KEYBOARD_F5}, + {"F6", HID_KEYBOARD_F6}, + {"F7", HID_KEYBOARD_F7}, + {"F8", HID_KEYBOARD_F8}, + {"F9", HID_KEYBOARD_F9}, + {"F10", HID_KEYBOARD_F10}, + {"F11", HID_KEYBOARD_F11}, + {"F12", HID_KEYBOARD_F12}, +}; + +static bool setup_parse_params(struct mjs* mjs, mjs_val_t arg, FuriHalUsbHidConfig* hid_cfg) { + if(!mjs_is_object(arg)) { + return false; + } + mjs_val_t vid_obj = mjs_get(mjs, arg, "vid", ~0); + mjs_val_t pid_obj = mjs_get(mjs, arg, "pid", ~0); + mjs_val_t mfr_obj = mjs_get(mjs, arg, "mfr_name", ~0); + mjs_val_t prod_obj = mjs_get(mjs, arg, "prod_name", ~0); + + if(mjs_is_number(vid_obj) && mjs_is_number(pid_obj)) { + hid_cfg->vid = mjs_get_int32(mjs, vid_obj); + hid_cfg->pid = mjs_get_int32(mjs, pid_obj); + } else { + return false; + } + + if(mjs_is_string(mfr_obj)) { + size_t str_len = 0; + const char* str_temp = mjs_get_string(mjs, &mfr_obj, &str_len); + if((str_len == 0) || (str_temp == NULL)) { + return false; + } + strlcpy(hid_cfg->manuf, str_temp, sizeof(hid_cfg->manuf)); + } + + if(mjs_is_string(prod_obj)) { + size_t str_len = 0; + const char* str_temp = mjs_get_string(mjs, &prod_obj, &str_len); + if((str_len == 0) || (str_temp == NULL)) { + return false; + } + strlcpy(hid_cfg->product, str_temp, sizeof(hid_cfg->product)); + } + + return true; +} + +static void js_badusb_setup(struct mjs* mjs) { + mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); + JsBadusbInst* badusb = mjs_get_ptr(mjs, obj_inst); + furi_assert(badusb); + + if(badusb->usb_if_prev) { + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "HID is already started"); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + bool args_correct = false; + size_t num_args = mjs_nargs(mjs); + if(num_args == 0) { + // No arguments: start USB HID with default settings + args_correct = true; + } else if(num_args == 1) { + badusb->hid_cfg = malloc(sizeof(FuriHalUsbHidConfig)); + // Parse argument object + args_correct = setup_parse_params(mjs, mjs_arg(mjs, 0), badusb->hid_cfg); + } + if(!args_correct) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + badusb->usb_if_prev = furi_hal_usb_get_config(); + + if(!furi_hal_usb_set_config(&usb_hid, badusb->hid_cfg)) { + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "USB is locked, close companion app first"); + badusb->usb_if_prev = NULL; + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + mjs_return(mjs, MJS_UNDEFINED); +} + +static void js_badusb_is_connected(struct mjs* mjs) { + mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); + JsBadusbInst* badusb = mjs_get_ptr(mjs, obj_inst); + furi_assert(badusb); + + if(badusb->usb_if_prev == NULL) { + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "HID is not started"); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + bool is_connected = furi_hal_hid_is_connected(); + mjs_return(mjs, mjs_mk_boolean(mjs, is_connected)); +} + +uint16_t get_keycode_by_name(const char* key_name, size_t name_len) { + if(name_len == 1) { // Single char + return (HID_ASCII_TO_KEY(key_name[0])); + } + + for(size_t i = 0; i < COUNT_OF(key_codes); i++) { + size_t key_cmd_len = strlen(key_codes[i].name); + if(key_cmd_len != name_len) { + continue; + } + + if(strncmp(key_name, key_codes[i].name, name_len) == 0) { + return key_codes[i].code; + } + } + + return HID_KEYBOARD_NONE; +} + +static bool parse_keycode(struct mjs* mjs, size_t nargs, uint16_t* keycode) { + uint16_t key_tmp = 0; + for(size_t i = 0; i < nargs; i++) { + mjs_val_t arg = mjs_arg(mjs, i); + if(mjs_is_string(arg)) { + size_t name_len = 0; + const char* key_name = mjs_get_string(mjs, &arg, &name_len); + if((key_name == NULL) || (name_len == 0)) { + // String error + return false; + } + uint16_t str_key = get_keycode_by_name(key_name, name_len); + if(str_key == HID_KEYBOARD_NONE) { + // Unknown key code + return false; + } + if((str_key & 0xFF) && (key_tmp & 0xFF)) { + // Main key is already defined + return false; + } + key_tmp |= str_key; + } else if(mjs_is_number(arg)) { + uint32_t keycode_number = (uint32_t)mjs_get_int32(mjs, arg); + if(((key_tmp & 0xFF) != 0) || (keycode_number > 0xFF)) { + return false; + } + key_tmp |= keycode_number & 0xFF; + } else { + return false; + } + } + *keycode = key_tmp; + return true; +} + +static void js_badusb_press(struct mjs* mjs) { + mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); + JsBadusbInst* badusb = mjs_get_ptr(mjs, obj_inst); + furi_assert(badusb); + if(badusb->usb_if_prev == NULL) { + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "HID is not started"); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + bool args_correct = false; + uint16_t keycode = HID_KEYBOARD_NONE; + size_t num_args = mjs_nargs(mjs); + if(num_args > 0) { + args_correct = parse_keycode(mjs, num_args, &keycode); + } + if(!args_correct) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + furi_hal_hid_kb_press(keycode); + furi_hal_hid_kb_release(keycode); + mjs_return(mjs, MJS_UNDEFINED); +} + +static void js_badusb_hold(struct mjs* mjs) { + mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); + JsBadusbInst* badusb = mjs_get_ptr(mjs, obj_inst); + furi_assert(badusb); + if(badusb->usb_if_prev == NULL) { + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "HID is not started"); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + bool args_correct = false; + uint16_t keycode = HID_KEYBOARD_NONE; + size_t num_args = mjs_nargs(mjs); + if(num_args > 0) { + args_correct = parse_keycode(mjs, num_args, &keycode); + } + if(!args_correct) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + if(keycode & 0xFF) { + badusb->key_hold_cnt++; + if(badusb->key_hold_cnt > (HID_KB_MAX_KEYS - 1)) { + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Too many keys are hold"); + furi_hal_hid_kb_release_all(); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + } + furi_hal_hid_kb_press(keycode); + mjs_return(mjs, MJS_UNDEFINED); +} + +static void js_badusb_release(struct mjs* mjs) { + mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); + JsBadusbInst* badusb = mjs_get_ptr(mjs, obj_inst); + furi_assert(badusb); + if(badusb->usb_if_prev == NULL) { + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "HID is not started"); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + bool args_correct = false; + uint16_t keycode = HID_KEYBOARD_NONE; + size_t num_args = mjs_nargs(mjs); + if(num_args == 0) { + furi_hal_hid_kb_release_all(); + badusb->key_hold_cnt = 0; + mjs_return(mjs, MJS_UNDEFINED); + return; + } else { + args_correct = parse_keycode(mjs, num_args, &keycode); + } + if(!args_correct) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + if((keycode & 0xFF) && (badusb->key_hold_cnt > 0)) { + badusb->key_hold_cnt--; + } + furi_hal_hid_kb_release(keycode); + mjs_return(mjs, MJS_UNDEFINED); +} + +static void badusb_print(struct mjs* mjs, bool ln) { + mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); + JsBadusbInst* badusb = mjs_get_ptr(mjs, obj_inst); + furi_assert(badusb); + if(badusb->usb_if_prev == NULL) { + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "HID is not started"); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + bool args_correct = false; + const char* text_str = NULL; + size_t text_len = 0; + uint32_t delay_val = 0; + do { + mjs_val_t obj_string = MJS_UNDEFINED; + size_t num_args = mjs_nargs(mjs); + if(num_args == 1) { + obj_string = mjs_arg(mjs, 0); + } else if(num_args == 2) { + obj_string = mjs_arg(mjs, 0); + mjs_val_t obj_delay = mjs_arg(mjs, 1); + if(!mjs_is_number(obj_delay)) { + break; + } + delay_val = (uint32_t)mjs_get_int32(mjs, obj_delay); + if(delay_val > 60000) { + break; + } + } + + if(!mjs_is_string(obj_string)) { + break; + } + text_str = mjs_get_string(mjs, &obj_string, &text_len); + if((text_str == NULL) || (text_len == 0)) { + break; + } + args_correct = true; + } while(0); + + if(!args_correct) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + for(size_t i = 0; i < text_len; i++) { + uint16_t keycode = HID_ASCII_TO_KEY(text_str[i]); + furi_hal_hid_kb_press(keycode); + furi_hal_hid_kb_release(keycode); + if(delay_val > 0) { + bool need_exit = js_delay_with_flags(mjs, delay_val); + if(need_exit) { + mjs_return(mjs, MJS_UNDEFINED); + return; + } + } + } + if(ln) { + furi_hal_hid_kb_press(HID_KEYBOARD_RETURN); + furi_hal_hid_kb_release(HID_KEYBOARD_RETURN); + } + + mjs_return(mjs, MJS_UNDEFINED); +} + +static void js_badusb_print(struct mjs* mjs) { + badusb_print(mjs, false); +} + +static void js_badusb_println(struct mjs* mjs) { + badusb_print(mjs, true); +} + +void* js_badusb_create(struct mjs* mjs, mjs_val_t* object) { + JsBadusbInst* badusb = malloc(sizeof(JsBadusbInst)); + mjs_val_t badusb_obj = mjs_mk_object(mjs); + mjs_set(mjs, badusb_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, badusb)); + mjs_set(mjs, badusb_obj, "setup", ~0, MFS_MK_FN(js_badusb_setup)); + mjs_set(mjs, badusb_obj, "isConnected", ~0, MFS_MK_FN(js_badusb_is_connected)); + mjs_set(mjs, badusb_obj, "press", ~0, MFS_MK_FN(js_badusb_press)); + mjs_set(mjs, badusb_obj, "hold", ~0, MFS_MK_FN(js_badusb_hold)); + mjs_set(mjs, badusb_obj, "release", ~0, MFS_MK_FN(js_badusb_release)); + mjs_set(mjs, badusb_obj, "print", ~0, MFS_MK_FN(js_badusb_print)); + mjs_set(mjs, badusb_obj, "println", ~0, MFS_MK_FN(js_badusb_println)); + *object = badusb_obj; + return badusb; +} + +void js_badusb_destroy(void* inst) { + JsBadusbInst* badusb = inst; + if(badusb->usb_if_prev) { + furi_hal_hid_kb_release_all(); + furi_check(furi_hal_usb_set_config(badusb->usb_if_prev, NULL)); + } + if(badusb->hid_cfg) { + free(badusb->hid_cfg); + } + free(badusb); +} diff --git a/applications/system/mjs/modules/js_badusb.h b/applications/system/mjs/modules/js_badusb.h new file mode 100644 index 00000000000..2b33ef6d1fc --- /dev/null +++ b/applications/system/mjs/modules/js_badusb.h @@ -0,0 +1,6 @@ +#pragma once +#include "js_thread_i.h" + +void* js_badusb_create(struct mjs* mjs, mjs_val_t* object); + +void js_badusb_destroy(void* inst); diff --git a/applications/system/mjs/modules/js_badusb.md b/applications/system/mjs/modules/js_badusb.md new file mode 100644 index 00000000000..dc3dd8b4874 --- /dev/null +++ b/applications/system/mjs/modules/js_badusb.md @@ -0,0 +1,82 @@ +# BadUSB module +```js +let badusb = require("badusb"); +``` +# Methods +## setup +Start USB HID with optional parameters. Should be called before all other methods. +### Params +Configuration object (optional): +- vid, pid (number): VID and PID values, both are mandatory +- mfr_name (string): Manufacturer name (32 ASCII characters max), optional +- prod_name (string): Product name (32 ASCII characters max), optional + +### Example: +```js +// Start USB HID with default parameters +badusb.setup(); +// Start USB HID with custom vid:pid = AAAA:BBBB, manufacturer and product strings not defined +badusb.setup({ vid: 0xAAAA, pid: 0xBBBB }); +// Start USB HID with custom vid:pid = AAAA:BBBB, manufacturer string = "Flipper Devices", product string = "Flipper Zero" +badusb.setup({ vid: 0xAAAA, pid: 0xBBBB, mfr_name: "Flipper Devices", prod_name: "Flipper Zero" }); +``` + +## press +Press and release a key +### Params +Key name, modifier names +//TODO: key codes list + +### Example: +```js +badusb.press("a"); // Press "a" key +badusb.press("A"); // SHIFT + "a" +badusb.press("CTRL", "a"); // CTRL + "a" +badusb.press("CTRL", "SHIFT", "ESC"); // CTRL + SHIFT + ESC combo +badusb.press(98); // Press key with HID code (dec) 98 (Numpad 0 / Insert) +badusb.press(0x47); // Press key with HID code (hex) 0x47 (Scroll lock) +``` + +## hold +### Params +Same as `press` + +### Example: +```js +badusb.hold("a"); // Press and hold "a" key +badusb.hold("CTRL", "v"); // Press and hold CTRL + "v" combo +``` + +## release +### Params +Same as `press` +No params - release all keys + +### Example: +```js +badusb.release(); // Release all keys +badusb.release("a"); // Release "a" key +``` + +## print +Print a string +### Params +- a string to print +- (optional) delay between key presses + +### Example: +```js +badusb.print("Hello, world!"); // print "Hello, world!" +badusb.print("Hello, world!", 100); // Add 100ms delay between key presses +``` + +## println +Same as `print` but ended with "ENTER" press +### Params +- a string to print +- (optional) delay between key presses + +### Example: +```js +badusb.println("Hello, world!"); // print "Hello, world!" and press "ENTER" +``` \ No newline at end of file diff --git a/applications/system/mjs/modules/js_flipper.c b/applications/system/mjs/modules/js_flipper.c new file mode 100644 index 00000000000..0361ccd1ca7 --- /dev/null +++ b/applications/system/mjs/modules/js_flipper.c @@ -0,0 +1,36 @@ +#include +#include "js_modules.h" +#include +#include + +static void js_flipper_get_model(struct mjs* mjs) { + mjs_val_t ret = mjs_mk_string(mjs, furi_hal_version_get_model_name(), ~0, true); + mjs_return(mjs, ret); +} + +static void js_flipper_get_name(struct mjs* mjs) { + const char* name_str = furi_hal_version_get_name_ptr(); + if(name_str == NULL) { + name_str = "Unknown"; + } + mjs_val_t ret = mjs_mk_string(mjs, name_str, ~0, true); + mjs_return(mjs, ret); +} + +static void js_flipper_get_battery(struct mjs* mjs) { + Power* power = furi_record_open(RECORD_POWER); + PowerInfo info; + power_get_info(power, &info); + furi_record_close(RECORD_POWER); + mjs_return(mjs, mjs_mk_number(mjs, info.charge)); +} + +void* js_flipper_create(struct mjs* mjs, mjs_val_t* object) { + mjs_val_t flipper_obj = mjs_mk_object(mjs); + mjs_set(mjs, flipper_obj, "getModel", ~0, MFS_MK_FN(js_flipper_get_model)); + mjs_set(mjs, flipper_obj, "getName", ~0, MFS_MK_FN(js_flipper_get_name)); + mjs_set(mjs, flipper_obj, "getBatteryCharge", ~0, MFS_MK_FN(js_flipper_get_battery)); + *object = flipper_obj; + + return (void*)1; +} diff --git a/applications/system/mjs/modules/js_flipper.h b/applications/system/mjs/modules/js_flipper.h new file mode 100644 index 00000000000..9e124ed68be --- /dev/null +++ b/applications/system/mjs/modules/js_flipper.h @@ -0,0 +1,4 @@ +#pragma once +#include "js_thread_i.h" + +void* js_flipper_create(struct mjs* mjs, mjs_val_t* object); diff --git a/applications/system/mjs/views/console_view.c b/applications/system/mjs/views/console_view.c index 84343786945..c2b92c670b2 100644 --- a/applications/system/mjs/views/console_view.c +++ b/applications/system/mjs/views/console_view.c @@ -1,4 +1,4 @@ -#include "../mjs_app_i.h" +#include "../js_app_i.h" #include "console_font.h" #define CONSOLE_LINES 8 @@ -6,16 +6,16 @@ #define LINE_BREAKS_MAX 3 #define LINE_LEN_MAX (128 / CONSOLE_CHAR_W) -struct MjsConsoleView { +struct JsConsoleView { View* view; }; typedef struct { FuriString* text[CONSOLE_LINES]; -} MjsConsoleViewModel; +} JsConsoleViewModel; static void console_view_draw_callback(Canvas* canvas, void* _model) { - MjsConsoleViewModel* model = _model; + JsConsoleViewModel* model = _model; canvas_set_color(canvas, ColorBlack); canvas_set_custom_u8g2_font(canvas, u8g2_font_spleen5x8_mr); @@ -38,10 +38,10 @@ static bool console_view_input_callback(InputEvent* event, void* context) { return false; } -void console_view_push_line(MjsConsoleView* console_view, const char* text, bool line_trimmed) { +void console_view_push_line(JsConsoleView* console_view, const char* text, bool line_trimmed) { with_view_model( console_view->view, - MjsConsoleViewModel * model, + JsConsoleViewModel * model, { FuriString* str_temp = model->text[0]; for(size_t i = 0; i < CONSOLE_LINES - 1; i++) { @@ -58,7 +58,7 @@ void console_view_push_line(MjsConsoleView* console_view, const char* text, bool true); } -void console_view_print(MjsConsoleView* console_view, const char* text) { +void console_view_print(JsConsoleView* console_view, const char* text) { char line_buf[LINE_LEN_MAX + 1]; uint8_t line_buf_cnt = 0; uint8_t utf8_bytes_left = 0; @@ -127,16 +127,16 @@ void console_view_print(MjsConsoleView* console_view, const char* text) { } } -MjsConsoleView* console_view_alloc(void) { - MjsConsoleView* console_view = malloc(sizeof(MjsConsoleView)); +JsConsoleView* console_view_alloc(void) { + JsConsoleView* console_view = malloc(sizeof(JsConsoleView)); console_view->view = view_alloc(); view_set_draw_callback(console_view->view, console_view_draw_callback); view_set_input_callback(console_view->view, console_view_input_callback); - view_allocate_model(console_view->view, ViewModelTypeLocking, sizeof(MjsConsoleViewModel)); + view_allocate_model(console_view->view, ViewModelTypeLocking, sizeof(JsConsoleViewModel)); with_view_model( console_view->view, - MjsConsoleViewModel * model, + JsConsoleViewModel * model, { for(size_t i = 0; i < CONSOLE_LINES; i++) { model->text[i] = furi_string_alloc(); @@ -146,10 +146,10 @@ MjsConsoleView* console_view_alloc(void) { return console_view; } -void console_view_free(MjsConsoleView* console_view) { +void console_view_free(JsConsoleView* console_view) { with_view_model( console_view->view, - MjsConsoleViewModel * model, + JsConsoleViewModel * model, { for(size_t i = 0; i < CONSOLE_LINES; i++) { furi_string_free(model->text[i]); @@ -160,6 +160,6 @@ void console_view_free(MjsConsoleView* console_view) { free(console_view); } -View* console_view_get_view(MjsConsoleView* console_view) { +View* console_view_get_view(JsConsoleView* console_view) { return console_view->view; } \ No newline at end of file diff --git a/applications/system/mjs/views/console_view.h b/applications/system/mjs/views/console_view.h index 41edcd9d7da..9fcd1a3dce9 100644 --- a/applications/system/mjs/views/console_view.h +++ b/applications/system/mjs/views/console_view.h @@ -2,12 +2,12 @@ #include -typedef struct MjsConsoleView MjsConsoleView; +typedef struct JsConsoleView JsConsoleView; -MjsConsoleView* console_view_alloc(void); +JsConsoleView* console_view_alloc(void); -void console_view_free(MjsConsoleView* console_view); +void console_view_free(JsConsoleView* console_view); -View* console_view_get_view(MjsConsoleView* console_view); +View* console_view_get_view(JsConsoleView* console_view); -void console_view_print(MjsConsoleView* console_view, const char* text); +void console_view_print(JsConsoleView* console_view, const char* text); From 5e58b0a0fc77b09c708708b4e610aaf28cd2314f Mon Sep 17 00:00:00 2001 From: nminaylov Date: Tue, 10 Oct 2023 13:18:43 +0300 Subject: [PATCH 12/31] JS notifications, simple dialog, BadUSB demo --- applications/system/mjs/assets/badusb.js | 1 + applications/system/mjs/assets/badusb_demo.js | 33 +++++++ applications/system/mjs/assets/notify.js | 9 ++ applications/system/mjs/js_modules.c | 7 +- applications/system/mjs/modules/js_dialog.c | 59 ++++++++++++ applications/system/mjs/modules/js_dialog.h | 6 ++ .../system/mjs/modules/js_notification.c | 93 +++++++++++++++++++ .../system/mjs/modules/js_notification.h | 6 ++ 8 files changed, 213 insertions(+), 1 deletion(-) create mode 100644 applications/system/mjs/assets/badusb_demo.js create mode 100644 applications/system/mjs/assets/notify.js create mode 100644 applications/system/mjs/modules/js_dialog.c create mode 100644 applications/system/mjs/modules/js_dialog.h create mode 100644 applications/system/mjs/modules/js_notification.c create mode 100644 applications/system/mjs/modules/js_notification.h diff --git a/applications/system/mjs/assets/badusb.js b/applications/system/mjs/assets/badusb.js index 071453fdd28..462fcc0b32d 100644 --- a/applications/system/mjs/assets/badusb.js +++ b/applications/system/mjs/assets/badusb.js @@ -19,6 +19,7 @@ if (badusb.isConnected()) { badusb.hold("SHIFT", "b"); delay(2000); badusb.release(); + badusb.press("ENTER"); badusb.print("Hello") badusb.println("abcd") badusb.print("12345", 1000); diff --git a/applications/system/mjs/assets/badusb_demo.js b/applications/system/mjs/assets/badusb_demo.js new file mode 100644 index 00000000000..21090f6034b --- /dev/null +++ b/applications/system/mjs/assets/badusb_demo.js @@ -0,0 +1,33 @@ +let badusb = require("badusb"); +let notify = require("notification"); +let flipper = require("flipper"); +let dialog = require("dialog"); + +badusb.setup({ vid: 0xAAAA, pid: 0xBBBB, mfr_name: "Flipper", prod_name: "Zero" }); +dialog.message("BadUSB demo", "Press OK to start"); + +if (badusb.isConnected()) { + notify.blink("green", "short"); + print("USB is connected"); + + badusb.println("Hello, world!"); + + badusb.press("CTRL", "a"); + badusb.press("CTRL", "c"); + badusb.press("DOWN"); + delay(1000); + badusb.press("CTRL", "v"); + delay(1000); + badusb.press("CTRL", "v"); + + badusb.println("1234", 200); + + badusb.println("Flipper Model: " + flipper.getModel()); + badusb.println("Flipper Name: " + flipper.getName()); + badusb.println("Battery level: " + to_string(flipper.getBatteryCharge()) + "%"); + + notify.success(); +} else { + print("USB not connected"); + notify.error(); +} diff --git a/applications/system/mjs/assets/notify.js b/applications/system/mjs/assets/notify.js new file mode 100644 index 00000000000..20f60c732e9 --- /dev/null +++ b/applications/system/mjs/assets/notify.js @@ -0,0 +1,9 @@ +let notify = require("notification"); +notify.error(); +delay(1000); +notify.success(); +delay(1000); +for (let i = 0; i < 10; i++) { + notify.blink("red", "short"); + delay(500); +} \ No newline at end of file diff --git a/applications/system/mjs/js_modules.c b/applications/system/mjs/js_modules.c index 123dcb06cc3..594f05f22db 100644 --- a/applications/system/mjs/js_modules.c +++ b/applications/system/mjs/js_modules.c @@ -2,6 +2,8 @@ #include "js_modules.h" #include "modules/js_flipper.h" #include "modules/js_badusb.h" +#include "modules/js_notification.h" +#include "modules/js_dialog.h" typedef void* (*JsModeConstructor)(struct mjs* mjs, mjs_val_t* object); typedef void (*JsModeDestructor)(void* inst); @@ -25,7 +27,10 @@ static const struct { } module_defs[] = { {"test", js_test_create, js_test_destroy}, {"flipper", js_flipper_create, NULL}, - {"badusb", js_badusb_create, js_badusb_destroy}}; + {"badusb", js_badusb_create, js_badusb_destroy}, + {"notification", js_notification_create, js_notification_destroy}, + {"dialog", js_dialog_create, NULL}, +}; struct JsModules { struct mjs* mjs; diff --git a/applications/system/mjs/modules/js_dialog.c b/applications/system/mjs/modules/js_dialog.c new file mode 100644 index 00000000000..b6aefc14c7a --- /dev/null +++ b/applications/system/mjs/modules/js_dialog.c @@ -0,0 +1,59 @@ +#include +#include "js_modules.h" +#include + +static bool js_dialog_parse_params(struct mjs* mjs, const char** hdr, const char** msg) { + size_t num_args = mjs_nargs(mjs); + if(num_args != 2) { + return false; + } + mjs_val_t header_obj = mjs_arg(mjs, 0); + mjs_val_t msg_obj = mjs_arg(mjs, 1); + if((!mjs_is_string(header_obj)) || (!mjs_is_string(msg_obj))) { + return false; + } + + size_t arg_len = 0; + *hdr = mjs_get_string(mjs, &header_obj, &arg_len); + if(arg_len == 0) { + *hdr = NULL; + } + + *msg = mjs_get_string(mjs, &msg_obj, &arg_len); + if(arg_len == 0) { + *msg = NULL; + } + + return true; +} + +static void js_dialog_message(struct mjs* mjs) { + const char* dialog_header = NULL; + const char* dialog_msg = NULL; + if(!js_dialog_parse_params(mjs, &dialog_header, &dialog_msg)) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); + DialogMessage* message = dialog_message_alloc(); + dialog_message_set_buttons(message, NULL, "OK", NULL); + if(dialog_header) { + dialog_message_set_header(message, dialog_header, 64, 3, AlignCenter, AlignTop); + } + if(dialog_msg) { + dialog_message_set_text(message, dialog_msg, 64, 26, AlignCenter, AlignTop); + } + dialog_message_show(dialogs, message); + dialog_message_free(message); + furi_record_close(RECORD_DIALOGS); + mjs_return(mjs, MJS_UNDEFINED); +} + +void* js_dialog_create(struct mjs* mjs, mjs_val_t* object) { + mjs_val_t dialog_obj = mjs_mk_object(mjs); + mjs_set(mjs, dialog_obj, "message", ~0, MFS_MK_FN(js_dialog_message)); + *object = dialog_obj; + + return (void*)1; +} diff --git a/applications/system/mjs/modules/js_dialog.h b/applications/system/mjs/modules/js_dialog.h new file mode 100644 index 00000000000..27a04a84fd6 --- /dev/null +++ b/applications/system/mjs/modules/js_dialog.h @@ -0,0 +1,6 @@ +#pragma once +#include "js_thread_i.h" + +void* js_dialog_create(struct mjs* mjs, mjs_val_t* object); + +void js_dialog_destroy(void* inst); diff --git a/applications/system/mjs/modules/js_notification.c b/applications/system/mjs/modules/js_notification.c new file mode 100644 index 00000000000..742e9b8184f --- /dev/null +++ b/applications/system/mjs/modules/js_notification.c @@ -0,0 +1,93 @@ +#include +#include "js_modules.h" +#include + +static void js_notify(struct mjs* mjs, const NotificationSequence* sequence) { + mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); + NotificationApp* notification = mjs_get_ptr(mjs, obj_inst); + furi_assert(notification); + notification_message(notification, sequence); +} + +static void js_notify_success(struct mjs* mjs) { + js_notify(mjs, &sequence_success); + mjs_return(mjs, MJS_UNDEFINED); +} + +static void js_notify_error(struct mjs* mjs) { + js_notify(mjs, &sequence_error); + mjs_return(mjs, MJS_UNDEFINED); +} + +static const struct { + const char* color_name; + const NotificationSequence* sequence_short; + const NotificationSequence* sequence_long; +} led_sequences[] = { + {"blue", &sequence_blink_blue_10, &sequence_blink_blue_100}, + {"red", &sequence_blink_red_10, &sequence_blink_red_100}, + {"green", &sequence_blink_green_10, &sequence_blink_green_100}, + {"yellow", &sequence_blink_yellow_10, &sequence_blink_yellow_100}, + {"cyan", &sequence_blink_cyan_10, &sequence_blink_cyan_100}, + {"magenta", &sequence_blink_magenta_10, &sequence_blink_magenta_100}, +}; + +static void js_notify_blink(struct mjs* mjs) { + const NotificationSequence* sequence = NULL; + do { + size_t num_args = mjs_nargs(mjs); + if(num_args != 2) { + break; + } + mjs_val_t color_obj = mjs_arg(mjs, 0); + mjs_val_t type_obj = mjs_arg(mjs, 1); + if((!mjs_is_string(color_obj)) || (!mjs_is_string(type_obj))) break; + + size_t arg_len = 0; + const char* arg_str = mjs_get_string(mjs, &color_obj, &arg_len); + if((arg_len == 0) || (arg_str == NULL)) break; + + int32_t color_id = -1; + for(size_t i = 0; i < COUNT_OF(led_sequences); i++) { + size_t name_len = strlen(led_sequences[i].color_name); + if(arg_len != name_len) continue; + if(strncmp(arg_str, led_sequences[i].color_name, arg_len) == 0) { + color_id = i; + break; + } + } + if(color_id == -1) break; + + arg_str = mjs_get_string(mjs, &type_obj, &arg_len); + if((arg_len == 0) || (arg_str == NULL)) break; + if(strncmp(arg_str, "short", arg_len) == 0) { + sequence = led_sequences[color_id].sequence_short; + } else if(strncmp(arg_str, "long", arg_len) == 0) { + sequence = led_sequences[color_id].sequence_long; + } + } while(0); + + if(sequence == NULL) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); + } else { + js_notify(mjs, sequence); + } + mjs_return(mjs, MJS_UNDEFINED); +} + +void* js_notification_create(struct mjs* mjs, mjs_val_t* object) { + NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); + mjs_val_t notify_obj = mjs_mk_object(mjs); + mjs_set(mjs, notify_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, notification)); + mjs_set(mjs, notify_obj, "success", ~0, MFS_MK_FN(js_notify_success)); + mjs_set(mjs, notify_obj, "error", ~0, MFS_MK_FN(js_notify_error)); + mjs_set(mjs, notify_obj, "blink", ~0, MFS_MK_FN(js_notify_blink)); + *object = notify_obj; + + return notification; +} + +void js_notification_destroy(void* inst) { + UNUSED(inst); + furi_record_close(RECORD_NOTIFICATION); +} diff --git a/applications/system/mjs/modules/js_notification.h b/applications/system/mjs/modules/js_notification.h new file mode 100644 index 00000000000..db450afd00f --- /dev/null +++ b/applications/system/mjs/modules/js_notification.h @@ -0,0 +1,6 @@ +#pragma once +#include "js_thread_i.h" + +void* js_notification_create(struct mjs* mjs, mjs_val_t* object); + +void js_notification_destroy(void* inst); From 7cec4e8d81572f496a57019ca1b8ed9c86762746 Mon Sep 17 00:00:00 2001 From: nminaylov Date: Fri, 13 Oct 2023 16:12:42 +0300 Subject: [PATCH 13/31] Custom dialogs, dialog demo --- applications/system/mjs/assets/dialog.js | 19 +++++ applications/system/mjs/modules/js_dialog.c | 87 ++++++++++++++++++++- 2 files changed, 102 insertions(+), 4 deletions(-) create mode 100644 applications/system/mjs/assets/dialog.js diff --git a/applications/system/mjs/assets/dialog.js b/applications/system/mjs/assets/dialog.js new file mode 100644 index 00000000000..9fc44f8b9e2 --- /dev/null +++ b/applications/system/mjs/assets/dialog.js @@ -0,0 +1,19 @@ +let dialog = require("dialog"); + +let result1 = dialog.message("Dialog demo", "Press OK to start"); +print(result1); + +let dialog_params = ({ + header: "Test_header", + text: "Test_text", + button_left: "Left", + button_right: "Right", + button_center: "OK" +}); + +let result2 = dialog.custom(dialog_params); +if (result2 === "") { + print("Back is pressed"); +} else { + print(result2, "is pressed"); +} diff --git a/applications/system/mjs/modules/js_dialog.c b/applications/system/mjs/modules/js_dialog.c index b6aefc14c7a..22e38fdac7b 100644 --- a/applications/system/mjs/modules/js_dialog.c +++ b/applications/system/mjs/modules/js_dialog.c @@ -2,7 +2,7 @@ #include "js_modules.h" #include -static bool js_dialog_parse_params(struct mjs* mjs, const char** hdr, const char** msg) { +static bool js_dialog_msg_parse_params(struct mjs* mjs, const char** hdr, const char** msg) { size_t num_args = mjs_nargs(mjs); if(num_args != 2) { return false; @@ -30,7 +30,7 @@ static bool js_dialog_parse_params(struct mjs* mjs, const char** hdr, const char static void js_dialog_message(struct mjs* mjs) { const char* dialog_header = NULL; const char* dialog_msg = NULL; - if(!js_dialog_parse_params(mjs, &dialog_header, &dialog_msg)) { + if(!js_dialog_msg_parse_params(mjs, &dialog_header, &dialog_msg)) { mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); mjs_return(mjs, MJS_UNDEFINED); return; @@ -44,15 +44,94 @@ static void js_dialog_message(struct mjs* mjs) { if(dialog_msg) { dialog_message_set_text(message, dialog_msg, 64, 26, AlignCenter, AlignTop); } - dialog_message_show(dialogs, message); + DialogMessageButton result = dialog_message_show(dialogs, message); dialog_message_free(message); furi_record_close(RECORD_DIALOGS); - mjs_return(mjs, MJS_UNDEFINED); + mjs_return(mjs, mjs_mk_boolean(mjs, result == DialogMessageButtonCenter)); +} + +static void js_dialog_custom(struct mjs* mjs) { + DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); + DialogMessage* message = dialog_message_alloc(); + + bool params_correct = false; + + do { + if(mjs_nargs(mjs) != 1) { + break; + } + mjs_val_t params_obj = mjs_arg(mjs, 0); + if(!mjs_is_object(params_obj)) { + break; + } + + mjs_val_t text_obj = mjs_get(mjs, params_obj, "header", ~0); + size_t arg_len = 0; + const char* text_str = mjs_get_string(mjs, &text_obj, &arg_len); + if(arg_len == 0) { + text_str = NULL; + } + if(text_str) { + dialog_message_set_header(message, text_str, 64, 3, AlignCenter, AlignTop); + } + + text_obj = mjs_get(mjs, params_obj, "text", ~0); + text_str = mjs_get_string(mjs, &text_obj, &arg_len); + if(arg_len == 0) { + text_str = NULL; + } + if(text_str) { + dialog_message_set_text(message, text_str, 64, 26, AlignCenter, AlignTop); + } + + mjs_val_t btn_obj[3] = { + mjs_get(mjs, params_obj, "button_left", ~0), + mjs_get(mjs, params_obj, "button_center", ~0), + mjs_get(mjs, params_obj, "button_right", ~0), + }; + const char* btn_text[3] = {NULL, NULL, NULL}; + + for(uint8_t i = 0; i < 3; i++) { + if(!mjs_is_string(btn_obj[i])) { + continue; + } + btn_text[i] = mjs_get_string(mjs, &btn_obj[i], &arg_len); + if(arg_len == 0) { + btn_text[i] = NULL; + } + } + + dialog_message_set_buttons(message, btn_text[0], btn_text[1], btn_text[2]); + + DialogMessageButton result = dialog_message_show(dialogs, message); + mjs_val_t return_obj = MJS_UNDEFINED; + if(result == DialogMessageButtonLeft) { + return_obj = mjs_mk_string(mjs, btn_text[0], ~0, true); + } else if(result == DialogMessageButtonCenter) { + return_obj = mjs_mk_string(mjs, btn_text[1], ~0, true); + } else if(result == DialogMessageButtonRight) { + return_obj = mjs_mk_string(mjs, btn_text[2], ~0, true); + } else { + return_obj = mjs_mk_string(mjs, "", ~0, true); + } + + mjs_return(mjs, return_obj); + params_correct = true; + } while(0); + + dialog_message_free(message); + furi_record_close(RECORD_DIALOGS); + + if(!params_correct) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); + mjs_return(mjs, MJS_UNDEFINED); + } } void* js_dialog_create(struct mjs* mjs, mjs_val_t* object) { mjs_val_t dialog_obj = mjs_mk_object(mjs); mjs_set(mjs, dialog_obj, "message", ~0, MFS_MK_FN(js_dialog_message)); + mjs_set(mjs, dialog_obj, "custom", ~0, MFS_MK_FN(js_dialog_custom)); *object = dialog_obj; return (void*)1; From a6791dc6d864bf8d51f3f983bce56833b65a690d Mon Sep 17 00:00:00 2001 From: nminaylov Date: Tue, 17 Oct 2023 18:59:58 +0300 Subject: [PATCH 14/31] MJS as system library, some dirty hacks to make it compile --- applications/system/mjs/application.fam | 25 ++--- applications/system/mjs/js_modules.c | 99 ++++++++++++++----- applications/system/mjs/js_modules.h | 8 +- applications/system/mjs/js_thread.c | 95 ++---------------- applications/system/mjs/js_thread_i.h | 2 +- .../system/mjs/lib/mjs/common/cs_file.c | 67 ------------- .../system/mjs/lib/mjs/mjs_conversion.c | 78 --------------- .../system/mjs/lib/mjs/mjs_conversion.h | 40 -------- applications/system/mjs/modules/module_test.c | 18 ++++ firmware/targets/f18/target.json | 1 + firmware/targets/f7/api_symbols.csv | 74 +++++++++++++- firmware/targets/f7/target.json | 1 + lib/SConscript | 1 + lib/mjs/SConscript | 34 +++++++ .../mjs/lib => lib}/mjs/common/cs_dbg.c | 13 +++ .../mjs/lib => lib}/mjs/common/cs_dbg.h | 0 .../mjs/lib => lib}/mjs/common/cs_dirent.c | 0 .../mjs/lib => lib}/mjs/common/cs_dirent.h | 0 lib/mjs/common/cs_file.c | 65 ++++++++++++ .../mjs/lib => lib}/mjs/common/cs_file.h | 0 .../mjs/lib => lib}/mjs/common/cs_time.c | 0 .../mjs/lib => lib}/mjs/common/cs_time.h | 0 .../mjs/lib => lib}/mjs/common/cs_varint.c | 0 .../mjs/lib => lib}/mjs/common/cs_varint.h | 0 .../lib => lib}/mjs/common/frozen/frozen.c | 0 .../lib => lib}/mjs/common/frozen/frozen.h | 0 .../system/mjs/lib => lib}/mjs/common/mbuf.c | 0 .../system/mjs/lib => lib}/mjs/common/mbuf.h | 0 .../mjs/lib => lib}/mjs/common/mg_mem.h | 0 .../mjs/lib => lib}/mjs/common/mg_str.c | 0 .../mjs/lib => lib}/mjs/common/mg_str.h | 0 .../mjs/lib => lib}/mjs/common/platform.h | 44 +-------- lib/mjs/common/platforms/platform_flipper.c | 57 +++++++++++ .../mjs/common/platforms/platform_flipper.h | 11 +-- .../mjs/lib => lib}/mjs/common/str_util.c | 0 .../mjs/lib => lib}/mjs/common/str_util.h | 0 .../system/mjs/lib => lib}/mjs/ffi/ffi.c | 0 .../system/mjs/lib => lib}/mjs/ffi/ffi.h | 0 .../system/mjs/lib => lib}/mjs/mjs_array.c | 1 - .../system/mjs/lib => lib}/mjs/mjs_array.h | 0 .../mjs/lib => lib}/mjs/mjs_array_public.h | 0 .../system/mjs/lib => lib}/mjs/mjs_bcode.c | 0 .../system/mjs/lib => lib}/mjs/mjs_bcode.h | 0 .../system/mjs/lib => lib}/mjs/mjs_builtin.c | 0 .../system/mjs/lib => lib}/mjs/mjs_builtin.h | 0 .../system/mjs/lib => lib}/mjs/mjs_core.c | 8 ++ .../system/mjs/lib => lib}/mjs/mjs_core.h | 47 --------- .../mjs/lib => lib}/mjs/mjs_core_public.h | 62 +++++++++--- .../system/mjs/lib => lib}/mjs/mjs_dataview.c | 0 .../system/mjs/lib => lib}/mjs/mjs_dataview.h | 0 .../system/mjs/lib => lib}/mjs/mjs_exec.c | 1 - .../system/mjs/lib => lib}/mjs/mjs_exec.h | 0 .../mjs/lib => lib}/mjs/mjs_exec_public.h | 1 - .../system/mjs/lib => lib}/mjs/mjs_features.h | 0 .../system/mjs/lib => lib}/mjs/mjs_ffi.c | 7 ++ .../system/mjs/lib => lib}/mjs/mjs_ffi.h | 0 .../mjs/lib => lib}/mjs/mjs_ffi_public.h | 2 + .../system/mjs/lib => lib}/mjs/mjs_gc.c | 0 .../system/mjs/lib => lib}/mjs/mjs_gc.h | 0 .../mjs/lib => lib}/mjs/mjs_gc_public.h | 0 .../system/mjs/lib => lib}/mjs/mjs_internal.h | 2 + .../system/mjs/lib => lib}/mjs/mjs_json.c | 2 +- .../system/mjs/lib => lib}/mjs/mjs_json.h | 0 .../system/mjs/lib => lib}/mjs/mjs_license.h | 0 .../system/mjs/lib => lib}/mjs/mjs_mm.h | 0 .../system/mjs/lib => lib}/mjs/mjs_object.c | 1 - .../system/mjs/lib => lib}/mjs/mjs_object.h | 0 .../mjs/lib => lib}/mjs/mjs_object_public.h | 0 .../system/mjs/lib => lib}/mjs/mjs_parser.c | 0 .../system/mjs/lib => lib}/mjs/mjs_parser.h | 0 .../mjs/lib => lib}/mjs/mjs_primitive.c | 0 .../mjs/lib => lib}/mjs/mjs_primitive.h | 0 .../lib => lib}/mjs/mjs_primitive_public.h | 0 .../system/mjs/lib => lib}/mjs/mjs_string.c | 1 - .../system/mjs/lib => lib}/mjs/mjs_string.h | 0 .../mjs/lib => lib}/mjs/mjs_string_public.h | 0 .../system/mjs/lib => lib}/mjs/mjs_tok.c | 0 .../system/mjs/lib => lib}/mjs/mjs_tok.h | 0 .../system/mjs/lib => lib}/mjs/mjs_util.c | 67 +++++++++++++ .../system/mjs/lib => lib}/mjs/mjs_util.h | 0 .../mjs/lib => lib}/mjs/mjs_util_public.h | 19 ++++ 81 files changed, 526 insertions(+), 428 deletions(-) delete mode 100644 applications/system/mjs/lib/mjs/common/cs_file.c delete mode 100644 applications/system/mjs/lib/mjs/mjs_conversion.c delete mode 100644 applications/system/mjs/lib/mjs/mjs_conversion.h create mode 100644 applications/system/mjs/modules/module_test.c create mode 100644 lib/mjs/SConscript rename {applications/system/mjs/lib => lib}/mjs/common/cs_dbg.c (91%) rename {applications/system/mjs/lib => lib}/mjs/common/cs_dbg.h (100%) rename {applications/system/mjs/lib => lib}/mjs/common/cs_dirent.c (100%) rename {applications/system/mjs/lib => lib}/mjs/common/cs_dirent.h (100%) create mode 100644 lib/mjs/common/cs_file.c rename {applications/system/mjs/lib => lib}/mjs/common/cs_file.h (100%) rename {applications/system/mjs/lib => lib}/mjs/common/cs_time.c (100%) rename {applications/system/mjs/lib => lib}/mjs/common/cs_time.h (100%) rename {applications/system/mjs/lib => lib}/mjs/common/cs_varint.c (100%) rename {applications/system/mjs/lib => lib}/mjs/common/cs_varint.h (100%) rename {applications/system/mjs/lib => lib}/mjs/common/frozen/frozen.c (100%) rename {applications/system/mjs/lib => lib}/mjs/common/frozen/frozen.h (100%) rename {applications/system/mjs/lib => lib}/mjs/common/mbuf.c (100%) rename {applications/system/mjs/lib => lib}/mjs/common/mbuf.h (100%) rename {applications/system/mjs/lib => lib}/mjs/common/mg_mem.h (100%) rename {applications/system/mjs/lib => lib}/mjs/common/mg_str.c (100%) rename {applications/system/mjs/lib => lib}/mjs/common/mg_str.h (100%) rename {applications/system/mjs/lib => lib}/mjs/common/platform.h (59%) create mode 100644 lib/mjs/common/platforms/platform_flipper.c rename applications/system/mjs/lib/mjs/common/platforms/platform_stm32.h => lib/mjs/common/platforms/platform_flipper.h (81%) rename {applications/system/mjs/lib => lib}/mjs/common/str_util.c (100%) rename {applications/system/mjs/lib => lib}/mjs/common/str_util.h (100%) rename {applications/system/mjs/lib => lib}/mjs/ffi/ffi.c (100%) rename {applications/system/mjs/lib => lib}/mjs/ffi/ffi.h (100%) rename {applications/system/mjs/lib => lib}/mjs/mjs_array.c (99%) rename {applications/system/mjs/lib => lib}/mjs/mjs_array.h (100%) rename {applications/system/mjs/lib => lib}/mjs/mjs_array_public.h (100%) rename {applications/system/mjs/lib => lib}/mjs/mjs_bcode.c (100%) rename {applications/system/mjs/lib => lib}/mjs/mjs_bcode.h (100%) rename {applications/system/mjs/lib => lib}/mjs/mjs_builtin.c (100%) rename {applications/system/mjs/lib => lib}/mjs/mjs_builtin.h (100%) rename {applications/system/mjs/lib => lib}/mjs/mjs_core.c (98%) rename {applications/system/mjs/lib => lib}/mjs/mjs_core.h (71%) rename {applications/system/mjs/lib => lib}/mjs/mjs_core_public.h (79%) rename {applications/system/mjs/lib => lib}/mjs/mjs_dataview.c (100%) rename {applications/system/mjs/lib => lib}/mjs/mjs_dataview.h (100%) rename {applications/system/mjs/lib => lib}/mjs/mjs_exec.c (99%) rename {applications/system/mjs/lib => lib}/mjs/mjs_exec.h (100%) rename {applications/system/mjs/lib => lib}/mjs/mjs_exec_public.h (90%) rename {applications/system/mjs/lib => lib}/mjs/mjs_features.h (100%) rename {applications/system/mjs/lib => lib}/mjs/mjs_ffi.c (99%) rename {applications/system/mjs/lib => lib}/mjs/mjs_ffi.h (100%) rename {applications/system/mjs/lib => lib}/mjs/mjs_ffi_public.h (93%) rename {applications/system/mjs/lib => lib}/mjs/mjs_gc.c (100%) rename {applications/system/mjs/lib => lib}/mjs/mjs_gc.h (100%) rename {applications/system/mjs/lib => lib}/mjs/mjs_gc_public.h (100%) rename {applications/system/mjs/lib => lib}/mjs/mjs_internal.h (98%) rename {applications/system/mjs/lib => lib}/mjs/mjs_json.c (99%) rename {applications/system/mjs/lib => lib}/mjs/mjs_json.h (100%) rename {applications/system/mjs/lib => lib}/mjs/mjs_license.h (100%) rename {applications/system/mjs/lib => lib}/mjs/mjs_mm.h (100%) rename {applications/system/mjs/lib => lib}/mjs/mjs_object.c (99%) rename {applications/system/mjs/lib => lib}/mjs/mjs_object.h (100%) rename {applications/system/mjs/lib => lib}/mjs/mjs_object_public.h (100%) rename {applications/system/mjs/lib => lib}/mjs/mjs_parser.c (100%) rename {applications/system/mjs/lib => lib}/mjs/mjs_parser.h (100%) rename {applications/system/mjs/lib => lib}/mjs/mjs_primitive.c (100%) rename {applications/system/mjs/lib => lib}/mjs/mjs_primitive.h (100%) rename {applications/system/mjs/lib => lib}/mjs/mjs_primitive_public.h (100%) rename {applications/system/mjs/lib => lib}/mjs/mjs_string.c (99%) rename {applications/system/mjs/lib => lib}/mjs/mjs_string.h (100%) rename {applications/system/mjs/lib => lib}/mjs/mjs_string_public.h (100%) rename {applications/system/mjs/lib => lib}/mjs/mjs_tok.c (100%) rename {applications/system/mjs/lib => lib}/mjs/mjs_tok.h (100%) rename {applications/system/mjs/lib => lib}/mjs/mjs_util.c (88%) rename {applications/system/mjs/lib => lib}/mjs/mjs_util.h (100%) rename {applications/system/mjs/lib => lib}/mjs/mjs_util_public.h (63%) diff --git a/applications/system/mjs/application.fam b/applications/system/mjs/application.fam index 2dc9267ae5f..feed2e77d71 100644 --- a/applications/system/mjs/application.fam +++ b/applications/system/mjs/application.fam @@ -1,24 +1,19 @@ App( - appid="mjs", - name="MJS", + appid="js_app", + name="JS", fap_category="Tools", apptype=FlipperAppType.EXTERNAL, entry_point="js_app", stack_size=2 * 1024, - cdefines=[ - ("CS_PLATFORM", "CS_P_STM32"), - "MJS_EXPOSE_PRIVATE", - ], - fap_private_libs=[ - Lib( - name="mjs", - cflags=[ - "-Wno-redundant-decls", - "-Wno-unused-function", - ], - ), - ], fap_libs=["gcc", "assets"], fap_file_assets="assets", order=0, ) + +App( + appid="js_module_test", + apptype=FlipperAppType.PLUGIN, + entry_point="js_module_test_ep", + requires=["js_app"], + sources=["modules/module_test.c"], +) diff --git a/applications/system/mjs/js_modules.c b/applications/system/mjs/js_modules.c index 594f05f22db..87291df5dd5 100644 --- a/applications/system/mjs/js_modules.c +++ b/applications/system/mjs/js_modules.c @@ -1,5 +1,6 @@ #include #include "js_modules.h" +#include #include "modules/js_flipper.h" #include "modules/js_badusb.h" #include "modules/js_notification.h" @@ -8,24 +9,19 @@ typedef void* (*JsModeConstructor)(struct mjs* mjs, mjs_val_t* object); typedef void (*JsModeDestructor)(void* inst); -static void* js_test_create(struct mjs* mjs, mjs_val_t* object) { - UNUSED(mjs); - FURI_LOG_E(TAG, "js_test_create"); - *object = MJS_UNDEFINED; - return (void*)1; -} +typedef struct { + JsModeConstructor create; + JsModeDestructor destroy; + void* context; +} JsModuleData; -static void js_test_destroy(void* inst) { - FURI_LOG_E(TAG, "js_test_destroy"); - UNUSED(inst); -} +DICT_DEF2(JsModuleDict, FuriString*, FURI_STRING_OPLIST, JsModuleData, M_POD_OPLIST); static const struct { char* name; JsModeConstructor create; JsModeDestructor destroy; -} module_defs[] = { - {"test", js_test_create, js_test_destroy}, +} modules_builtin[] = { {"flipper", js_flipper_create, NULL}, {"badusb", js_badusb_create, js_badusb_destroy}, {"notification", js_notification_create, js_notification_destroy}, @@ -34,43 +30,92 @@ static const struct { struct JsModules { struct mjs* mjs; - void* module_inst[COUNT_OF(module_defs)]; + JsModuleDict_t module_dict; + PluginManager* plugin_manager; }; -JsModules* js_modules_create(struct mjs* mjs) { +JsModules* js_modules_create(struct mjs* mjs, CompositeApiResolver* resolver) { JsModules* modules = malloc(sizeof(JsModules)); modules->mjs = mjs; + JsModuleDict_init(modules->module_dict); + + modules->plugin_manager = plugin_manager_alloc( + PLUGIN_APP_ID, PLUGIN_API_VERSION, composite_api_resolver_get(resolver)); + return modules; } void js_modules_destroy(JsModules* modules) { - for(size_t i = 0; i < COUNT_OF(module_defs); i++) { - if((module_defs[i].destroy) && (modules->module_inst[i])) { - module_defs[i].destroy(modules->module_inst[i]); + JsModuleDict_it_t it; + for(JsModuleDict_it(it, modules->module_dict); !JsModuleDict_end_p(it); + JsModuleDict_next(it)) { + const JsModuleDict_itref_t* module_itref = JsModuleDict_cref(it); + if(module_itref->value.destroy) { + module_itref->value.destroy(module_itref->value.context); } } + plugin_manager_free(modules->plugin_manager); + JsModuleDict_clear(modules->module_dict); free(modules); } mjs_val_t js_module_require(JsModules* modules, const char* name, size_t name_len) { - mjs_val_t module_object = MJS_UNDEFINED; - for(size_t i = 0; i < COUNT_OF(module_defs); i++) { - size_t name_compare_len = strlen(module_defs[i].name); + FuriString* module_name = furi_string_alloc_set_str(name); + // Check if module is already installed + JsModuleData* module_inst = JsModuleDict_get(modules->module_dict, module_name); + if(module_inst) { + furi_string_free(module_name); + // TODO: "already exists" error + return MJS_UNDEFINED; + } + + bool module_found = false; + // Check built-in modules + for(size_t i = 0; i < COUNT_OF(modules_builtin); i++) { + size_t name_compare_len = strlen(modules_builtin[i].name); if(name_compare_len != name_len) { continue; } - if(strncmp(name, module_defs[i].name, name_compare_len) == 0) { - if(modules->module_inst[i]) { + if(strncmp(name, modules_builtin[i].name, name_compare_len) == 0) { + JsModuleData module = { + .create = modules_builtin[i].create, .destroy = modules_builtin[i].destroy}; + JsModuleDict_set_at(modules->module_dict, module_name, module); + module_found = true; + FURI_LOG_I(TAG, "Using built-in module %s", name); + break; + } + } + + // External module load + if(!module_found) { + FuriString* module_path = furi_string_alloc(); + furi_string_printf(module_path, "%s/%s.fal", APP_DATA_PATH("plugins"), name); + FURI_LOG_I(TAG, "Loading external module %s", furi_string_get_cstr(module_path)); + do { + PluginManagerError load_error = plugin_manager_load_single( + modules->plugin_manager, furi_string_get_cstr(module_path)); + if(load_error != PluginManagerErrorNone) { break; - // TODO: "already exists" error } - if(module_defs[i].create) { - modules->module_inst[i] = module_defs[i].create(modules->mjs, &module_object); - } - break; + // TODO: get ep struct, compare name, add to dict + module_found = true; + } while(0); + furi_string_free(module_path); + } + + // Run module constructor + mjs_val_t module_object = MJS_UNDEFINED; + if(module_found) { + module_inst = JsModuleDict_get(modules->module_dict, module_name); + furi_assert(module_inst); + if(module_inst->create) { + module_inst->context = module_inst->create(modules->mjs, &module_object); } } + + furi_string_free(module_name); + return module_object; } \ No newline at end of file diff --git a/applications/system/mjs/js_modules.h b/applications/system/mjs/js_modules.h index 4e8f866c386..e741c683464 100644 --- a/applications/system/mjs/js_modules.h +++ b/applications/system/mjs/js_modules.h @@ -1,9 +1,15 @@ #pragma once #include "js_thread_i.h" +#include +#include +#include + +#define PLUGIN_APP_ID "js" +#define PLUGIN_API_VERSION 1 typedef struct JsModules JsModules; -JsModules* js_modules_create(struct mjs* mjs); +JsModules* js_modules_create(struct mjs* mjs, CompositeApiResolver* resolver); void js_modules_destroy(JsModules* modules); diff --git a/applications/system/mjs/js_thread.c b/applications/system/mjs/js_thread.c index 5be9a560f2b..36bfec383ee 100644 --- a/applications/system/mjs/js_thread.c +++ b/applications/system/mjs/js_thread.c @@ -24,86 +24,6 @@ typedef enum { ThreadEventTest = (1 << 1), } WorkerEventFlags; -// static void obj_type(mjs_val_t v) { -// if(mjs_is_number(v)) { -// FURI_LOG_I(TAG, "mjs_is_number"); -// } else if(mjs_is_boolean(v)) { -// FURI_LOG_I(TAG, "mjs_is_boolean"); -// } else if(mjs_is_string(v)) { -// FURI_LOG_I(TAG, "mjs_is_string"); -// } else if(mjs_is_array(v)) { -// FURI_LOG_I(TAG, "mjs_is_array"); -// } else if(mjs_is_object(v)) { -// FURI_LOG_I(TAG, "mjs_is_object"); -// } else if(mjs_is_foreign(v)) { -// FURI_LOG_I(TAG, "mjs_is_foreign"); -// } else if(mjs_is_function(v)) { -// FURI_LOG_I(TAG, "mjs_is_function"); -// } else if(mjs_is_null(v)) { -// FURI_LOG_I(TAG, "mjs_is_null"); -// } else if(mjs_is_undefined(v)) { -// FURI_LOG_I(TAG, "mjs_is_undefined"); -// } else { -// FURI_LOG_I(TAG, "unknown"); -// } -// } - -// TODO: mjs fix -void cs_log_printf(const char* fmt, ...) { - UNUSED(fmt); -} - -// TODO: mjs fix -int cs_log_print_prefix(enum cs_log_level level, const char* file, int ln) { - UNUSED(level); - UNUSED(file); - UNUSED(ln); - return 0; -} - -char* cs_read_file(const char* path, size_t* size) { - Storage* storage = furi_record_open(RECORD_STORAGE); - Stream* stream = file_stream_alloc(storage); - char* data = NULL; - if(!file_stream_open(stream, path, FSAM_READ, FSOM_OPEN_EXISTING)) { - } else { - *size = stream_size(stream); - data = (char*)malloc(*size + 1); - if(data != NULL) { - stream_rewind(stream); - if(stream_read(stream, (uint8_t*)data, *size) != *size) { - file_stream_close(stream); - furi_record_close(RECORD_STORAGE); - stream_free(stream); - free(data); - return NULL; - } - data[*size] = '\0'; - } - } - file_stream_close(stream); - furi_record_close(RECORD_STORAGE); - stream_free(stream); - return data; -} - -char* json_fread(const char* path) { - UNUSED(path); - return NULL; -} - -int json_vfprintf(const char* file_name, const char* fmt, va_list ap) { - UNUSED(file_name); - UNUSED(fmt); - UNUSED(ap); - return 0; -} - -int json_prettify_file(const char* file_name) { - UNUSED(file_name); - return 0; -} - static void mjs_str_print(FuriString* msg_str, struct mjs* mjs) { size_t num_args = mjs_nargs(mjs); for(size_t i = 0; i < num_args; i++) { @@ -130,7 +50,7 @@ static void mjs_print(struct mjs* mjs) { printf("%s\r\n", furi_string_get_cstr(msg_str)); - JsThread* worker = mjs->context; + JsThread* worker = mjs_get_context(mjs); furi_assert(worker); if(worker->app_callback) { worker->app_callback(JsThreadEventPrint, furi_string_get_cstr(msg_str), worker->context); @@ -233,7 +153,7 @@ static void mjs_ffi_address(struct mjs* mjs) { mjs_val_t name_v = mjs_arg(mjs, 0); size_t len; const char* name = mjs_get_string(mjs, &name_v, &len); - void* addr = my_dlsym(mjs->dlsym_handle, name); + void* addr = mjs_ffi_resolve(mjs, name); mjs_return(mjs, mjs_mk_foreign(mjs, addr)); } @@ -245,7 +165,7 @@ static void mjs_require(struct mjs* mjs) { if((len == 0) || (name == NULL)) { mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "String argument is expected"); } else { - JsThread* worker = mjs->context; + JsThread* worker = mjs_get_context(mjs); furi_assert(worker); req_object = js_module_require(worker->modules, name, len); if(req_object == MJS_UNDEFINED) { @@ -278,7 +198,7 @@ static int32_t js_thread(void* arg) { composite_api_resolver_add(worker->resolver, application_api_interface); struct mjs* mjs = mjs_create(worker); - worker->modules = js_modules_create(mjs); + worker->modules = js_modules_create(mjs, worker->resolver); mjs_val_t global = mjs_get_global(mjs); mjs_set(mjs, global, "print", ~0, MFS_MK_FN(mjs_print)); mjs_set(mjs, global, "delay", ~0, MFS_MK_FN(mjs_delay)); @@ -305,10 +225,11 @@ static int32_t js_thread(void* arg) { if(worker->app_callback) { worker->app_callback(JsThreadEventError, mjs_strerror(mjs, err), worker->context); } - if(mjs->stack_trace != NULL) { - FURI_LOG_E(TAG, "Stack trace:\n%s", mjs->stack_trace); + const char* stack_trace = mjs_get_stack_trace(mjs); + if(stack_trace != NULL) { + FURI_LOG_E(TAG, "Stack trace:\n%s", stack_trace); if(worker->app_callback) { - worker->app_callback(JsThreadEventErrorTrace, mjs->stack_trace, worker->context); + worker->app_callback(JsThreadEventErrorTrace, stack_trace, worker->context); } } } else { diff --git a/applications/system/mjs/js_thread_i.h b/applications/system/mjs/js_thread_i.h index 87faa5bf7bc..2c3a29ca7ac 100644 --- a/applications/system/mjs/js_thread_i.h +++ b/applications/system/mjs/js_thread_i.h @@ -7,7 +7,7 @@ #include #include #include -#include +#include #include #define TAG "JS" diff --git a/applications/system/mjs/lib/mjs/common/cs_file.c b/applications/system/mjs/lib/mjs/common/cs_file.c deleted file mode 100644 index e9db5ca1cd8..00000000000 --- a/applications/system/mjs/lib/mjs/common/cs_file.c +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (c) 2014-2018 Cesanta Software Limited - * All rights reserved - * - * Licensed under the Apache License, Version 2.0 (the ""License""); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an ""AS IS"" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "cs_file.h" - -#include -#include - -#ifdef CS_MMAP -#include -#include -#include -#endif - -#ifndef EXCLUDE_COMMON -char *cs_read_file(const char *path, size_t *size) WEAK; -char *cs_read_file(const char *path, size_t *size) { - FILE *fp; - char *data = NULL; - if ((fp = fopen(path, "rb")) == NULL) { - } else if (fseek(fp, 0, SEEK_END) != 0) { - fclose(fp); - } else { - *size = ftell(fp); - data = (char *) malloc(*size + 1); - if (data != NULL) { - fseek(fp, 0, SEEK_SET); /* Some platforms might not have rewind(), Oo */ - if (fread(data, 1, *size, fp) != *size) { - free(data); - return NULL; - } - data[*size] = '\0'; - } - fclose(fp); - } - return data; -} -#endif /* EXCLUDE_COMMON */ - -#ifdef CS_MMAP -char *cs_mmap_file(const char *path, size_t *size) WEAK; -char *cs_mmap_file(const char *path, size_t *size) { - char *r; - int fd = open(path, O_RDONLY, 0); - struct stat st; - if (fd < 0) return NULL; - fstat(fd, &st); - *size = (size_t) st.st_size; - r = (char *) mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); - if (r == MAP_FAILED) return NULL; - return r; -} -#endif diff --git a/applications/system/mjs/lib/mjs/mjs_conversion.c b/applications/system/mjs/lib/mjs/mjs_conversion.c deleted file mode 100644 index 1bd12cba037..00000000000 --- a/applications/system/mjs/lib/mjs/mjs_conversion.c +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (c) 2016 Cesanta Software Limited - * All rights reserved - */ - -#include "mjs_conversion.h" -#include "mjs_object.h" -#include "mjs_primitive.h" -#include "mjs_string.h" -#include "mjs_util.h" - -MJS_PRIVATE 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 { - ret = MJS_TYPE_ERROR; - mjs_set_errorf(mjs, ret, "unknown type to convert to string"); - } - -clean: - return ret; -} - -MJS_PRIVATE 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); -} - -MJS_PRIVATE int mjs_is_truthy(struct mjs* mjs, mjs_val_t v) { - return mjs_get_bool(mjs, mjs_to_boolean_v(mjs, v)); -} diff --git a/applications/system/mjs/lib/mjs/mjs_conversion.h b/applications/system/mjs/lib/mjs/mjs_conversion.h deleted file mode 100644 index 57af87c848c..00000000000 --- a/applications/system/mjs/lib/mjs/mjs_conversion.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2016 Cesanta Software Limited - * All rights reserved - */ - -#ifndef MJS_CONVERSION_H_ -#define MJS_CONVERSION_H_ - -#include "mjs_internal.h" -#include "mjs_core.h" - -#if defined(__cplusplus) -extern "C" { -#endif /* __cplusplus */ - -/* - * Tries to convert `mjs_val_t` to a string, returns MJS_OK if successful. - * String is returned as a pair of pointers: `char **p, size_t *sizep`. - * - * Caller must also provide a non-null `need_free`, and if it is non-zero, - * then the string `*p` should be freed by the caller. - * - * MJS does not support `toString()` and `valueOf()`, so, passing an object - * always results in `MJS_TYPE_ERROR`. - */ -MJS_PRIVATE mjs_err_t - mjs_to_string(struct mjs* mjs, mjs_val_t* v, char** p, size_t* sizep, int* need_free); - -/* - * Converts value to boolean as in the expression `if (v)`. - */ -MJS_PRIVATE mjs_val_t mjs_to_boolean_v(struct mjs* mjs, mjs_val_t v); - -MJS_PRIVATE int mjs_is_truthy(struct mjs* mjs, mjs_val_t v); - -#if defined(__cplusplus) -} -#endif /* __cplusplus */ - -#endif /* MJS_CONVERSION_H_ */ diff --git a/applications/system/mjs/modules/module_test.c b/applications/system/mjs/modules/module_test.c new file mode 100644 index 00000000000..404681b9a1d --- /dev/null +++ b/applications/system/mjs/modules/module_test.c @@ -0,0 +1,18 @@ +#include +#include "js_modules.h" + +void plugin_test(void) { + FURI_LOG_I("", "Test"); + mjs_val_t flipper_obj = mjs_mk_object(NULL); + UNUSED(flipper_obj); +} + +static const FlipperAppPluginDescriptor advanced_plugin1_descriptor = { + .appid = PLUGIN_APP_ID, + .ep_api_version = PLUGIN_API_VERSION, + .entry_point = &plugin_test, +}; + +const FlipperAppPluginDescriptor* js_module_test_ep() { + return &advanced_plugin1_descriptor; +} diff --git a/firmware/targets/f18/target.json b/firmware/targets/f18/target.json index 2d14813f6da..f0f7e2e6884 100644 --- a/firmware/targets/f18/target.json +++ b/firmware/targets/f18/target.json @@ -26,6 +26,7 @@ "assets", "one_wire", "music_worker", + "mjs", "misc", "flipper_application", "flipperformat", diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index dd86d5ac119..5281025bba7 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,38.0,, +Version,+,38.1,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -137,6 +137,12 @@ Header,+,lib/libusb_stm32/inc/usbd_core.h,, Header,+,lib/mbedtls/include/mbedtls/des.h,, Header,+,lib/mbedtls/include/mbedtls/sha1.h,, Header,+,lib/micro-ecc/uECC.h,, +Header,+,lib/mjs/mjs_core_public.h,, +Header,+,lib/mjs/mjs_exec_public.h,, +Header,+,lib/mjs/mjs_object_public.h,, +Header,+,lib/mjs/mjs_primitive_public.h,, +Header,+,lib/mjs/mjs_string_public.h,, +Header,+,lib/mjs/mjs_util_public.h,, Header,+,lib/mlib/m-algo.h,, Header,+,lib/mlib/m-array.h,, Header,+,lib/mlib/m-bptree.h,, @@ -2073,6 +2079,72 @@ Function,-,mfkey32_free,void,Mfkey32* Function,+,mfkey32_get_auth_sectors,uint16_t,FuriString* Function,-,mfkey32_process_data,void,"Mfkey32*, uint8_t*, uint16_t, _Bool, _Bool" Function,-,mfkey32_set_callback,void,"Mfkey32*, Mfkey32ParseDataCallback, void*" +Function,+,mjs_apply,mjs_err_t,"mjs*, mjs_val_t*, mjs_val_t, mjs_val_t, int, mjs_val_t*" +Function,+,mjs_arg,mjs_val_t,"mjs*, int" +Function,+,mjs_call,mjs_err_t,"mjs*, mjs_val_t*, mjs_val_t, mjs_val_t, int, ..." +Function,+,mjs_create,mjs*,void* +Function,+,mjs_del,int,"mjs*, mjs_val_t, const char*, size_t" +Function,+,mjs_destroy,void,mjs* +Function,+,mjs_disown,int,"mjs*, mjs_val_t*" +Function,+,mjs_exec,mjs_err_t,"mjs*, const char*, mjs_val_t*" +Function,+,mjs_exec_file,mjs_err_t,"mjs*, const char*, mjs_val_t*" +Function,+,mjs_exit,void,mjs* +Function,+,mjs_ffi_resolve,void*,"mjs*, const char*" +Function,-,mjs_fprintf,void,"mjs_val_t, mjs*, FILE*" +Function,+,mjs_get,mjs_val_t,"mjs*, mjs_val_t, const char*, size_t" +Function,-,mjs_get_bcode_filename_by_offset,const char*,"mjs*, int" +Function,+,mjs_get_bool,int,"mjs*, mjs_val_t" +Function,+,mjs_get_context,void*,mjs* +Function,+,mjs_get_cstring,const char*,"mjs*, mjs_val_t*" +Function,+,mjs_get_double,double,"mjs*, mjs_val_t" +Function,+,mjs_get_global,mjs_val_t,mjs* +Function,+,mjs_get_int,int,"mjs*, mjs_val_t" +Function,+,mjs_get_int32,int32_t,"mjs*, mjs_val_t" +Function,+,mjs_get_lineno_by_offset,int,"mjs*, int" +Function,+,mjs_get_offset_by_call_frame_num,int,"mjs*, int" +Function,+,mjs_get_ptr,void*,"mjs*, mjs_val_t" +Function,+,mjs_get_stack_trace,const char*,mjs* +Function,+,mjs_get_string,const char*,"mjs*, mjs_val_t*, size_t*" +Function,+,mjs_get_this,mjs_val_t,mjs* +Function,+,mjs_get_v,mjs_val_t,"mjs*, mjs_val_t, mjs_val_t" +Function,+,mjs_get_v_proto,mjs_val_t,"mjs*, mjs_val_t, mjs_val_t" +Function,+,mjs_is_boolean,int,mjs_val_t +Function,+,mjs_is_foreign,int,mjs_val_t +Function,+,mjs_is_function,int,mjs_val_t +Function,+,mjs_is_null,int,mjs_val_t +Function,+,mjs_is_number,int,mjs_val_t +Function,+,mjs_is_object,int,mjs_val_t +Function,+,mjs_is_string,int,mjs_val_t +Function,+,mjs_is_truthy,int,"mjs*, mjs_val_t" +Function,+,mjs_is_undefined,int,mjs_val_t +Function,+,mjs_mk_boolean,mjs_val_t,"mjs*, int" +Function,+,mjs_mk_foreign,mjs_val_t,"mjs*, void*" +Function,+,mjs_mk_foreign_func,mjs_val_t,"mjs*, mjs_func_ptr_t" +Function,+,mjs_mk_function,mjs_val_t,"mjs*, size_t" +Function,+,mjs_mk_null,mjs_val_t, +Function,+,mjs_mk_number,mjs_val_t,"mjs*, double" +Function,+,mjs_mk_object,mjs_val_t,mjs* +Function,+,mjs_mk_string,mjs_val_t,"mjs*, const char*, size_t, int" +Function,+,mjs_mk_undefined,mjs_val_t, +Function,+,mjs_nargs,int,mjs* +Function,+,mjs_next,mjs_val_t,"mjs*, mjs_val_t, mjs_val_t*" +Function,+,mjs_own,void,"mjs*, mjs_val_t*" +Function,+,mjs_prepend_errorf,mjs_err_t,"mjs*, mjs_err_t, const char*, ..." +Function,+,mjs_print_error,void,"mjs*, FILE*, const char*, int" +Function,+,mjs_return,void,"mjs*, mjs_val_t" +Function,+,mjs_set,mjs_err_t,"mjs*, mjs_val_t, const char*, size_t, mjs_val_t" +Function,+,mjs_set_errorf,mjs_err_t,"mjs*, mjs_err_t, const char*, ..." +Function,+,mjs_set_ffi_resolver,void,"mjs*, mjs_ffi_resolver_t*, void*" +Function,+,mjs_set_flags_poller,void,"mjs*, mjs_flags_poller_t" +Function,-,mjs_set_generate_jsc,void,"mjs*, int" +Function,+,mjs_set_v,mjs_err_t,"mjs*, mjs_val_t, mjs_val_t, mjs_val_t" +Function,+,mjs_sprintf,void,"mjs_val_t, mjs*, char*, size_t" +Function,+,mjs_strcmp,int,"mjs*, mjs_val_t*, const char*, size_t" +Function,+,mjs_strerror,const char*,"mjs*, mjs_err" +Function,+,mjs_struct_to_obj,mjs_val_t,"mjs*, const void*, const mjs_c_struct_member*" +Function,+,mjs_to_boolean_v,mjs_val_t,"mjs*, mjs_val_t" +Function,+,mjs_to_string,mjs_err_t,"mjs*, mjs_val_t*, char**, size_t*, int*" +Function,+,mjs_typeof,const char*,mjs_val_t Function,-,mkdtemp,char*,char* Function,-,mkostemp,int,"char*, int" Function,-,mkostemps,int,"char*, int, int" diff --git a/firmware/targets/f7/target.json b/firmware/targets/f7/target.json index 9bb87000c70..d3fc16ffd03 100644 --- a/firmware/targets/f7/target.json +++ b/firmware/targets/f7/target.json @@ -39,6 +39,7 @@ "one_wire", "ibutton", "music_worker", + "mjs", "misc", "mbedtls", "lfrfid", diff --git a/lib/SConscript b/lib/SConscript index 907a5a41def..fae90b43510 100644 --- a/lib/SConscript +++ b/lib/SConscript @@ -102,6 +102,7 @@ libs = env.BuildModules( "lfrfid", "flipper_application", "music_worker", + "mjs", ], ) diff --git a/lib/mjs/SConscript b/lib/mjs/SConscript new file mode 100644 index 00000000000..329a6f40401 --- /dev/null +++ b/lib/mjs/SConscript @@ -0,0 +1,34 @@ +Import("env") + +env.Append( + CPPPATH=[ + "#/lib/mjs", + ], + SDK_HEADERS=[ + File("mjs_core_public.h"), + File("mjs_exec_public.h"), + File("mjs_object_public.h"), + File("mjs_string_public.h"), + File("mjs_primitive_public.h"), + File("mjs_util_public.h"), + ], +) + +libenv = env.Clone(FW_LIB_NAME="mjs") +libenv.ApplyLibFlags() + +libenv.AppendUnique( + CCFLAGS=[ + # Required for lib to be linkable with .faps + "-mword-relocations", + "-mlong-calls", + "-Wno-redundant-decls", + "-Wno-unused-function", + ], +) + +sources = libenv.GlobRecursive("*.c*") + +lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources) +libenv.Install("${LIB_DIST_DIR}", lib) +Return("lib") diff --git a/applications/system/mjs/lib/mjs/common/cs_dbg.c b/lib/mjs/common/cs_dbg.c similarity index 91% rename from applications/system/mjs/lib/mjs/common/cs_dbg.c rename to lib/mjs/common/cs_dbg.c index c1861cfad84..45747b0f370 100644 --- a/applications/system/mjs/lib/mjs/common/cs_dbg.c +++ b/lib/mjs/common/cs_dbg.c @@ -129,6 +129,19 @@ void cs_log_set_file(FILE* file) { #else +int cs_log_print_prefix(enum cs_log_level level, const char* file, int ln) WEAK; +int cs_log_print_prefix(enum cs_log_level level, const char* file, int ln) { + (void)level; + (void)file; + (void)ln; + return 0; +} + +void cs_log_printf(const char* fmt, ...) WEAK; +void cs_log_printf(const char* fmt, ...) { + (void)fmt; +} + void cs_log_set_file_level(const char* file_level) { (void)file_level; } diff --git a/applications/system/mjs/lib/mjs/common/cs_dbg.h b/lib/mjs/common/cs_dbg.h similarity index 100% rename from applications/system/mjs/lib/mjs/common/cs_dbg.h rename to lib/mjs/common/cs_dbg.h diff --git a/applications/system/mjs/lib/mjs/common/cs_dirent.c b/lib/mjs/common/cs_dirent.c similarity index 100% rename from applications/system/mjs/lib/mjs/common/cs_dirent.c rename to lib/mjs/common/cs_dirent.c diff --git a/applications/system/mjs/lib/mjs/common/cs_dirent.h b/lib/mjs/common/cs_dirent.h similarity index 100% rename from applications/system/mjs/lib/mjs/common/cs_dirent.h rename to lib/mjs/common/cs_dirent.h diff --git a/lib/mjs/common/cs_file.c b/lib/mjs/common/cs_file.c new file mode 100644 index 00000000000..d4aea7da2b9 --- /dev/null +++ b/lib/mjs/common/cs_file.c @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2014-2018 Cesanta Software Limited + * All rights reserved + * + * Licensed under the Apache License, Version 2.0 (the ""License""); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an ""AS IS"" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "cs_file.h" + +#include +#include + +#ifdef CS_MMAP +#include +#include +#include +#endif + +#ifdef CS_MMAP +char* cs_read_file(const char* path, size_t* size) WEAK; +char* cs_read_file(const char* path, size_t* size) { + FILE* fp; + char* data = NULL; + if((fp = fopen(path, "rb")) == NULL) { + } else if(fseek(fp, 0, SEEK_END) != 0) { + fclose(fp); + } else { + *size = ftell(fp); + data = (char*)malloc(*size + 1); + if(data != NULL) { + fseek(fp, 0, SEEK_SET); /* Some platforms might not have rewind(), Oo */ + if(fread(data, 1, *size, fp) != *size) { + free(data); + return NULL; + } + data[*size] = '\0'; + } + fclose(fp); + } + return data; +} + +char* cs_mmap_file(const char* path, size_t* size) WEAK; +char* cs_mmap_file(const char* path, size_t* size) { + char* r; + int fd = open(path, O_RDONLY, 0); + struct stat st; + if(fd < 0) return NULL; + fstat(fd, &st); + *size = (size_t)st.st_size; + r = (char*)mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if(r == MAP_FAILED) return NULL; + return r; +} +#endif diff --git a/applications/system/mjs/lib/mjs/common/cs_file.h b/lib/mjs/common/cs_file.h similarity index 100% rename from applications/system/mjs/lib/mjs/common/cs_file.h rename to lib/mjs/common/cs_file.h diff --git a/applications/system/mjs/lib/mjs/common/cs_time.c b/lib/mjs/common/cs_time.c similarity index 100% rename from applications/system/mjs/lib/mjs/common/cs_time.c rename to lib/mjs/common/cs_time.c diff --git a/applications/system/mjs/lib/mjs/common/cs_time.h b/lib/mjs/common/cs_time.h similarity index 100% rename from applications/system/mjs/lib/mjs/common/cs_time.h rename to lib/mjs/common/cs_time.h diff --git a/applications/system/mjs/lib/mjs/common/cs_varint.c b/lib/mjs/common/cs_varint.c similarity index 100% rename from applications/system/mjs/lib/mjs/common/cs_varint.c rename to lib/mjs/common/cs_varint.c diff --git a/applications/system/mjs/lib/mjs/common/cs_varint.h b/lib/mjs/common/cs_varint.h similarity index 100% rename from applications/system/mjs/lib/mjs/common/cs_varint.h rename to lib/mjs/common/cs_varint.h diff --git a/applications/system/mjs/lib/mjs/common/frozen/frozen.c b/lib/mjs/common/frozen/frozen.c similarity index 100% rename from applications/system/mjs/lib/mjs/common/frozen/frozen.c rename to lib/mjs/common/frozen/frozen.c diff --git a/applications/system/mjs/lib/mjs/common/frozen/frozen.h b/lib/mjs/common/frozen/frozen.h similarity index 100% rename from applications/system/mjs/lib/mjs/common/frozen/frozen.h rename to lib/mjs/common/frozen/frozen.h diff --git a/applications/system/mjs/lib/mjs/common/mbuf.c b/lib/mjs/common/mbuf.c similarity index 100% rename from applications/system/mjs/lib/mjs/common/mbuf.c rename to lib/mjs/common/mbuf.c diff --git a/applications/system/mjs/lib/mjs/common/mbuf.h b/lib/mjs/common/mbuf.h similarity index 100% rename from applications/system/mjs/lib/mjs/common/mbuf.h rename to lib/mjs/common/mbuf.h diff --git a/applications/system/mjs/lib/mjs/common/mg_mem.h b/lib/mjs/common/mg_mem.h similarity index 100% rename from applications/system/mjs/lib/mjs/common/mg_mem.h rename to lib/mjs/common/mg_mem.h diff --git a/applications/system/mjs/lib/mjs/common/mg_str.c b/lib/mjs/common/mg_str.c similarity index 100% rename from applications/system/mjs/lib/mjs/common/mg_str.c rename to lib/mjs/common/mg_str.c diff --git a/applications/system/mjs/lib/mjs/common/mg_str.h b/lib/mjs/common/mg_str.h similarity index 100% rename from applications/system/mjs/lib/mjs/common/mg_str.h rename to lib/mjs/common/mg_str.h diff --git a/applications/system/mjs/lib/mjs/common/platform.h b/lib/mjs/common/platform.h similarity index 59% rename from applications/system/mjs/lib/mjs/common/platform.h rename to lib/mjs/common/platform.h index c963a3ba56b..5a2d58d84ff 100644 --- a/applications/system/mjs/lib/mjs/common/platform.h +++ b/lib/mjs/common/platform.h @@ -24,50 +24,17 @@ #define CS_P_PIC32 11 #define CS_P_RS14100 18 #define CS_P_STM32 16 -/* Next id: 19 */ +#define CS_P_FLIPPER 19 +/* Next id: 20 */ -/* If not specified explicitly, we guess platform by defines. */ #ifndef CS_PLATFORM - -#if defined(TARGET_IS_MSP432P4XX) || defined(__MSP432P401R__) -#define CS_PLATFORM CS_P_MSP432 -#elif defined(cc3200) || defined(TARGET_IS_CC3200) -#define CS_PLATFORM CS_P_CC3200 -#elif defined(cc3220) || defined(TARGET_IS_CC3220) -#define CS_PLATFORM CS_P_CC3220 -#elif defined(__unix__) || defined(__APPLE__) -#define CS_PLATFORM CS_P_UNIX -#elif defined(WINCE) -#define CS_PLATFORM CS_P_WINCE -#elif defined(_WIN32) -#define CS_PLATFORM CS_P_WINDOWS -#elif defined(__MBED__) -#define CS_PLATFORM CS_P_MBED -#elif defined(__USE_LPCOPEN) -#define CS_PLATFORM CS_P_NXP_LPC -#elif defined(FRDM_K64F) || defined(FREEDOM) -#define CS_PLATFORM CS_P_NXP_KINETIS -#elif defined(PIC32) -#define CS_PLATFORM CS_P_PIC32 -#elif defined(ESP_PLATFORM) -#define CS_PLATFORM CS_P_ESP32 -#elif defined(ICACHE_FLASH) -#define CS_PLATFORM CS_P_ESP8266 -#elif defined(TARGET_IS_TM4C129_RA0) || defined(TARGET_IS_TM4C129_RA1) || \ - defined(TARGET_IS_TM4C129_RA2) -#define CS_PLATFORM CS_P_TM4C129 -#elif defined(RS14100) -#define CS_PLATFORM CS_P_RS14100 -#elif defined(STM32) -#define CS_PLATFORM CS_P_STM32 +#define CS_PLATFORM CS_P_FLIPPER #endif #ifndef CS_PLATFORM #error "CS_PLATFORM is not specified and we couldn't guess it." #endif -#endif /* !defined(CS_PLATFORM) */ - #define MG_NET_IF_SOCKET 1 #define MG_NET_IF_SIMPLELINK 2 #define MG_NET_IF_LWIP_LOW_LEVEL 3 @@ -78,9 +45,8 @@ #define MG_SSL_IF_MBEDTLS 2 #define MG_SSL_IF_SIMPLELINK 3 -#include "platforms/platform_stm32.h" -#if CS_PLATFORM == CS_P_CUSTOM -#include +#if CS_PLATFORM == CS_P_FLIPPER +#include "platforms/platform_flipper.h" #endif /* Common stuff */ diff --git a/lib/mjs/common/platforms/platform_flipper.c b/lib/mjs/common/platforms/platform_flipper.c new file mode 100644 index 00000000000..c2b80c86a83 --- /dev/null +++ b/lib/mjs/common/platforms/platform_flipper.c @@ -0,0 +1,57 @@ +#include +#include +#include "../cs_dbg.h" + +char* cs_read_file(const char* path, size_t* size) { + Storage* storage = furi_record_open(RECORD_STORAGE); + Stream* stream = file_stream_alloc(storage); + char* data = NULL; + if(!file_stream_open(stream, path, FSAM_READ, FSOM_OPEN_EXISTING)) { + } else { + *size = stream_size(stream); + data = (char*)malloc(*size + 1); + if(data != NULL) { + stream_rewind(stream); + if(stream_read(stream, (uint8_t*)data, *size) != *size) { + file_stream_close(stream); + furi_record_close(RECORD_STORAGE); + stream_free(stream); + free(data); + return NULL; + } + data[*size] = '\0'; + } + } + file_stream_close(stream); + furi_record_close(RECORD_STORAGE); + stream_free(stream); + return data; +} + +char* json_fread(const char* path) { + UNUSED(path); + return NULL; +} + +int json_vfprintf(const char* file_name, const char* fmt, va_list ap) { + UNUSED(file_name); + UNUSED(fmt); + UNUSED(ap); + return 0; +} + +int json_prettify_file(const char* file_name) { + UNUSED(file_name); + return 0; +} + +int cs_log_print_prefix(enum cs_log_level level, const char* file, int ln) { + (void)level; + (void)file; + (void)ln; + return 0; +} + +void cs_log_printf(const char* fmt, ...) { + (void)fmt; +} diff --git a/applications/system/mjs/lib/mjs/common/platforms/platform_stm32.h b/lib/mjs/common/platforms/platform_flipper.h similarity index 81% rename from applications/system/mjs/lib/mjs/common/platforms/platform_stm32.h rename to lib/mjs/common/platforms/platform_flipper.h index 8f6bbcb5a9c..2a118e0fa91 100644 --- a/applications/system/mjs/lib/mjs/common/platforms/platform_stm32.h +++ b/lib/mjs/common/platforms/platform_flipper.h @@ -15,9 +15,8 @@ * limitations under the License. */ -#ifndef CS_COMMON_PLATFORMS_PLATFORM_STM32_H_ -#define CS_COMMON_PLATFORMS_PLATFORM_STM32_H_ -#if CS_PLATFORM == CS_P_STM32 +#pragma once +#if CS_PLATFORM == CS_P_FLIPPER #include #include @@ -31,9 +30,6 @@ #include #include #include -// #include - -// #include #define to64(x) strtoll(x, NULL, 10) #define INT64_FMT "lld" @@ -49,5 +45,4 @@ typedef struct stat cs_stat_t; #define MG_ENABLE_FILESYSTEM 0 #endif -#endif /* CS_PLATFORM == CS_P_STM32 */ -#endif /* CS_COMMON_PLATFORMS_PLATFORM_STM32_H_ */ \ No newline at end of file +#endif /* CS_PLATFORM == CS_P_FLIPPER */ \ No newline at end of file diff --git a/applications/system/mjs/lib/mjs/common/str_util.c b/lib/mjs/common/str_util.c similarity index 100% rename from applications/system/mjs/lib/mjs/common/str_util.c rename to lib/mjs/common/str_util.c diff --git a/applications/system/mjs/lib/mjs/common/str_util.h b/lib/mjs/common/str_util.h similarity index 100% rename from applications/system/mjs/lib/mjs/common/str_util.h rename to lib/mjs/common/str_util.h diff --git a/applications/system/mjs/lib/mjs/ffi/ffi.c b/lib/mjs/ffi/ffi.c similarity index 100% rename from applications/system/mjs/lib/mjs/ffi/ffi.c rename to lib/mjs/ffi/ffi.c diff --git a/applications/system/mjs/lib/mjs/ffi/ffi.h b/lib/mjs/ffi/ffi.h similarity index 100% rename from applications/system/mjs/lib/mjs/ffi/ffi.h rename to lib/mjs/ffi/ffi.h diff --git a/applications/system/mjs/lib/mjs/mjs_array.c b/lib/mjs/mjs_array.c similarity index 99% rename from applications/system/mjs/lib/mjs/mjs_array.c rename to lib/mjs/mjs_array.c index a29514afd1b..c74487d65a8 100644 --- a/applications/system/mjs/lib/mjs/mjs_array.c +++ b/lib/mjs/mjs_array.c @@ -6,7 +6,6 @@ #include #include "common/str_util.h" #include "mjs_array.h" -#include "mjs_conversion.h" #include "mjs_core.h" #include "mjs_internal.h" #include "mjs_object.h" diff --git a/applications/system/mjs/lib/mjs/mjs_array.h b/lib/mjs/mjs_array.h similarity index 100% rename from applications/system/mjs/lib/mjs/mjs_array.h rename to lib/mjs/mjs_array.h diff --git a/applications/system/mjs/lib/mjs/mjs_array_public.h b/lib/mjs/mjs_array_public.h similarity index 100% rename from applications/system/mjs/lib/mjs/mjs_array_public.h rename to lib/mjs/mjs_array_public.h diff --git a/applications/system/mjs/lib/mjs/mjs_bcode.c b/lib/mjs/mjs_bcode.c similarity index 100% rename from applications/system/mjs/lib/mjs/mjs_bcode.c rename to lib/mjs/mjs_bcode.c diff --git a/applications/system/mjs/lib/mjs/mjs_bcode.h b/lib/mjs/mjs_bcode.h similarity index 100% rename from applications/system/mjs/lib/mjs/mjs_bcode.h rename to lib/mjs/mjs_bcode.h diff --git a/applications/system/mjs/lib/mjs/mjs_builtin.c b/lib/mjs/mjs_builtin.c similarity index 100% rename from applications/system/mjs/lib/mjs/mjs_builtin.c rename to lib/mjs/mjs_builtin.c diff --git a/applications/system/mjs/lib/mjs/mjs_builtin.h b/lib/mjs/mjs_builtin.h similarity index 100% rename from applications/system/mjs/lib/mjs/mjs_builtin.h rename to lib/mjs/mjs_builtin.h diff --git a/applications/system/mjs/lib/mjs/mjs_core.c b/lib/mjs/mjs_core.c similarity index 98% rename from applications/system/mjs/lib/mjs/mjs_core.c rename to lib/mjs/mjs_core.c index be40c89c133..1fd76fd4af5 100644 --- a/applications/system/mjs/lib/mjs/mjs_core.c +++ b/lib/mjs/mjs_core.c @@ -146,6 +146,10 @@ void mjs_set_flags_poller(struct mjs* mjs, mjs_flags_poller_t poller) { mjs->flags_poller = poller; } +void* mjs_get_context(struct mjs* mjs) { + return mjs->context; +} + mjs_err_t mjs_prepend_errorf(struct mjs* mjs, mjs_err_t err, const char* fmt, ...) { char* old_error_msg = mjs->error_msg; char* new_error_msg = NULL; @@ -222,6 +226,10 @@ const char* mjs_strerror(struct mjs* mjs, enum mjs_err err) { return mjs->error_msg == NULL || mjs->error_msg[0] == '\0' ? err_names[err] : mjs->error_msg; } +const char* mjs_get_stack_trace(struct mjs* mjs) { + return mjs->stack_trace; +} + MJS_PRIVATE size_t mjs_get_func_addr(mjs_val_t v) { return v & ~MJS_TAG_MASK; } diff --git a/applications/system/mjs/lib/mjs/mjs_core.h b/lib/mjs/mjs_core.h similarity index 71% rename from applications/system/mjs/lib/mjs/mjs_core.h rename to lib/mjs/mjs_core.h index f767410717b..97e3d849e80 100644 --- a/applications/system/mjs/lib/mjs/mjs_core.h +++ b/lib/mjs/mjs_core.h @@ -16,26 +16,6 @@ extern "C" { #define JUMP_INSTRUCTION_SIZE 2 -enum mjs_type { - /* Primitive types */ - MJS_TYPE_UNDEFINED, - MJS_TYPE_NULL, - MJS_TYPE_BOOLEAN, - MJS_TYPE_NUMBER, - MJS_TYPE_STRING, - MJS_TYPE_FOREIGN, - - /* Different classes of Object type */ - MJS_TYPE_OBJECT_GENERIC, - MJS_TYPE_OBJECT_ARRAY, - MJS_TYPE_OBJECT_FUNCTION, - /* - * TODO(dfrank): if we support prototypes, need to add items for them here - */ - - MJS_TYPES_CNT -}; - enum mjs_call_stack_frame_item { CALL_STACK_FRAME_ITEM_RETVAL_STACK_IDX, /* TOS */ CALL_STACK_FRAME_ITEM_LOOP_ADDR_IDX, @@ -46,33 +26,6 @@ enum mjs_call_stack_frame_item { CALL_STACK_FRAME_ITEMS_CNT }; -/* - * A tag is made of the sign bit and the 4 lower order bits of byte 6. - * So in total we have 32 possible tags. - * - * Tag (1,0) however cannot hold a zero payload otherwise it's interpreted as an - * INFINITY; for simplicity we're just not going to use that combination. - */ -#define MAKE_TAG(s, t) ((uint64_t)(s) << 63 | (uint64_t)0x7ff0 << 48 | (uint64_t)(t) << 48) - -#define MJS_TAG_OBJECT MAKE_TAG(1, 1) -#define MJS_TAG_FOREIGN MAKE_TAG(1, 2) -#define MJS_TAG_UNDEFINED MAKE_TAG(1, 3) -#define MJS_TAG_BOOLEAN MAKE_TAG(1, 4) -#define MJS_TAG_NAN MAKE_TAG(1, 5) -#define MJS_TAG_STRING_I MAKE_TAG(1, 6) /* Inlined string len < 5 */ -#define MJS_TAG_STRING_5 MAKE_TAG(1, 7) /* Inlined string len 5 */ -#define MJS_TAG_STRING_O MAKE_TAG(1, 8) /* Owned string */ -#define MJS_TAG_STRING_F MAKE_TAG(1, 9) /* Foreign string */ -#define MJS_TAG_STRING_C MAKE_TAG(1, 10) /* String chunk */ -#define MJS_TAG_STRING_D MAKE_TAG(1, 11) /* Dictionary string */ -#define MJS_TAG_ARRAY MAKE_TAG(1, 12) -#define MJS_TAG_FUNCTION MAKE_TAG(1, 13) -#define MJS_TAG_FUNCTION_FFI MAKE_TAG(1, 14) -#define MJS_TAG_NULL MAKE_TAG(1, 15) - -#define MJS_TAG_MASK MAKE_TAG(1, 15) - struct mjs_vals { /* Current `this` value */ mjs_val_t this_obj; diff --git a/applications/system/mjs/lib/mjs/mjs_core_public.h b/lib/mjs/mjs_core_public.h similarity index 79% rename from applications/system/mjs/lib/mjs/mjs_core_public.h rename to lib/mjs/mjs_core_public.h index 6fbe754ce75..460d285e76a 100644 --- a/applications/system/mjs/lib/mjs/mjs_core_public.h +++ b/lib/mjs/mjs_core_public.h @@ -55,6 +55,33 @@ extern "C" { typedef uint64_t mjs_val_t; +/* + * A tag is made of the sign bit and the 4 lower order bits of byte 6. + * So in total we have 32 possible tags. + * + * Tag (1,0) however cannot hold a zero payload otherwise it's interpreted as an + * INFINITY; for simplicity we're just not going to use that combination. + */ +#define MAKE_TAG(s, t) ((uint64_t)(s) << 63 | (uint64_t)0x7ff0 << 48 | (uint64_t)(t) << 48) + +#define MJS_TAG_OBJECT MAKE_TAG(1, 1) +#define MJS_TAG_FOREIGN MAKE_TAG(1, 2) +#define MJS_TAG_UNDEFINED MAKE_TAG(1, 3) +#define MJS_TAG_BOOLEAN MAKE_TAG(1, 4) +#define MJS_TAG_NAN MAKE_TAG(1, 5) +#define MJS_TAG_STRING_I MAKE_TAG(1, 6) /* Inlined string len < 5 */ +#define MJS_TAG_STRING_5 MAKE_TAG(1, 7) /* Inlined string len 5 */ +#define MJS_TAG_STRING_O MAKE_TAG(1, 8) /* Owned string */ +#define MJS_TAG_STRING_F MAKE_TAG(1, 9) /* Foreign string */ +#define MJS_TAG_STRING_C MAKE_TAG(1, 10) /* String chunk */ +#define MJS_TAG_STRING_D MAKE_TAG(1, 11) /* Dictionary string */ +#define MJS_TAG_ARRAY MAKE_TAG(1, 12) +#define MJS_TAG_FUNCTION MAKE_TAG(1, 13) +#define MJS_TAG_FUNCTION_FFI MAKE_TAG(1, 14) +#define MJS_TAG_NULL MAKE_TAG(1, 15) + +#define MJS_TAG_MASK MAKE_TAG(1, 15) + /* This if-0 is a dirty workaround to force etags to pick `struct mjs` */ #if 0 /* Opaque structure. MJS engine context. */ @@ -65,6 +92,26 @@ struct mjs { struct mjs; +enum mjs_type { + /* Primitive types */ + MJS_TYPE_UNDEFINED, + MJS_TYPE_NULL, + MJS_TYPE_BOOLEAN, + MJS_TYPE_NUMBER, + MJS_TYPE_STRING, + MJS_TYPE_FOREIGN, + + /* Different classes of Object type */ + MJS_TYPE_OBJECT_GENERIC, + MJS_TYPE_OBJECT_ARRAY, + MJS_TYPE_OBJECT_FUNCTION, + /* + * TODO(dfrank): if we support prototypes, need to add items for them here + */ + + MJS_TYPES_CNT +}; + typedef enum mjs_err { MJS_OK, MJS_SYNTAX_ERROR, @@ -88,17 +135,6 @@ struct mjs; /* Create MJS instance */ struct mjs* mjs_create(void* context); -struct mjs_create_opts { - /* use non-default bytecode definition file, testing-only */ - const struct bf_code* code; -}; - -/* - * Like `msj_create()`, but allows to customize initial MJS state, see `struct - * mjs_create_opts`. - */ -struct mjs* mjs_create_opt(struct mjs_create_opts opts); - /* Destroy MJS instance */ void mjs_destroy(struct mjs* mjs); @@ -186,6 +222,8 @@ void mjs_exit(struct mjs* mjs); void mjs_set_flags_poller(struct mjs* mjs, mjs_flags_poller_t poller); +void* mjs_get_context(struct mjs* mjs); + /* * If there is no error message already set, then it's equal to * `mjs_set_errorf()`. @@ -208,6 +246,8 @@ void mjs_print_error(struct mjs* mjs, FILE* fp, const char* msg, int print_stack */ const char* mjs_strerror(struct mjs* mjs, enum mjs_err err); +const char* mjs_get_stack_trace(struct mjs* mjs); + /* * Sets whether *.jsc files are generated when *.js file is executed. By * default it's 0. diff --git a/applications/system/mjs/lib/mjs/mjs_dataview.c b/lib/mjs/mjs_dataview.c similarity index 100% rename from applications/system/mjs/lib/mjs/mjs_dataview.c rename to lib/mjs/mjs_dataview.c diff --git a/applications/system/mjs/lib/mjs/mjs_dataview.h b/lib/mjs/mjs_dataview.h similarity index 100% rename from applications/system/mjs/lib/mjs/mjs_dataview.h rename to lib/mjs/mjs_dataview.h diff --git a/applications/system/mjs/lib/mjs/mjs_exec.c b/lib/mjs/mjs_exec.c similarity index 99% rename from applications/system/mjs/lib/mjs/mjs_exec.c rename to lib/mjs/mjs_exec.c index f38ad5e4dcf..93440a570c5 100644 --- a/applications/system/mjs/lib/mjs/mjs_exec.c +++ b/lib/mjs/mjs_exec.c @@ -8,7 +8,6 @@ #include "mjs_array.h" #include "mjs_bcode.h" -#include "mjs_conversion.h" #include "mjs_core.h" #include "mjs_exec.h" #include "mjs_internal.h" diff --git a/applications/system/mjs/lib/mjs/mjs_exec.h b/lib/mjs/mjs_exec.h similarity index 100% rename from applications/system/mjs/lib/mjs/mjs_exec.h rename to lib/mjs/mjs_exec.h diff --git a/applications/system/mjs/lib/mjs/mjs_exec_public.h b/lib/mjs/mjs_exec_public.h similarity index 90% rename from applications/system/mjs/lib/mjs/mjs_exec_public.h rename to lib/mjs/mjs_exec_public.h index 1d65435a548..2a25ae5228b 100644 --- a/applications/system/mjs/lib/mjs/mjs_exec_public.h +++ b/lib/mjs/mjs_exec_public.h @@ -14,7 +14,6 @@ extern "C" { #endif /* __cplusplus */ mjs_err_t mjs_exec(struct mjs*, const char* src, mjs_val_t* res); -mjs_err_t mjs_exec_buf(struct mjs*, const char* src, size_t, mjs_val_t* res); mjs_err_t mjs_exec_file(struct mjs* mjs, const char* path, mjs_val_t* res); mjs_err_t mjs_apply( diff --git a/applications/system/mjs/lib/mjs/mjs_features.h b/lib/mjs/mjs_features.h similarity index 100% rename from applications/system/mjs/lib/mjs/mjs_features.h rename to lib/mjs/mjs_features.h diff --git a/applications/system/mjs/lib/mjs/mjs_ffi.c b/lib/mjs/mjs_ffi.c similarity index 99% rename from applications/system/mjs/lib/mjs/mjs_ffi.c rename to lib/mjs/mjs_ffi.c index 8d4d7df0983..13b639d3806 100644 --- a/applications/system/mjs/lib/mjs/mjs_ffi.c +++ b/lib/mjs/mjs_ffi.c @@ -44,6 +44,13 @@ void mjs_set_ffi_resolver(struct mjs* mjs, mjs_ffi_resolver_t* dlsym, void* hand mjs->dlsym_handle = handle; } +void* mjs_ffi_resolve(struct mjs* mjs, const char* symbol) { + if(mjs->dlsym) { + return mjs->dlsym(mjs->dlsym_handle, symbol); + } + return NULL; +} + static mjs_ffi_ctype_t parse_cval_type(struct mjs* mjs, const char* s, const char* e) { struct mg_str ms = MG_NULL_STR; /* Trim leading and trailing whitespace */ diff --git a/applications/system/mjs/lib/mjs/mjs_ffi.h b/lib/mjs/mjs_ffi.h similarity index 100% rename from applications/system/mjs/lib/mjs/mjs_ffi.h rename to lib/mjs/mjs_ffi.h diff --git a/applications/system/mjs/lib/mjs/mjs_ffi_public.h b/lib/mjs/mjs_ffi_public.h similarity index 93% rename from applications/system/mjs/lib/mjs/mjs_ffi_public.h rename to lib/mjs/mjs_ffi_public.h index 194f25b9d53..310e5351245 100644 --- a/applications/system/mjs/lib/mjs/mjs_ffi_public.h +++ b/lib/mjs/mjs_ffi_public.h @@ -31,6 +31,8 @@ typedef void*(mjs_ffi_resolver_t)(void* handle, const char* symbol); void mjs_set_ffi_resolver(struct mjs* mjs, mjs_ffi_resolver_t* dlsym, void* handle); +void* mjs_ffi_resolve(struct mjs* mjs, const char* symbol); + #if defined(__cplusplus) } #endif /* __cplusplus */ diff --git a/applications/system/mjs/lib/mjs/mjs_gc.c b/lib/mjs/mjs_gc.c similarity index 100% rename from applications/system/mjs/lib/mjs/mjs_gc.c rename to lib/mjs/mjs_gc.c diff --git a/applications/system/mjs/lib/mjs/mjs_gc.h b/lib/mjs/mjs_gc.h similarity index 100% rename from applications/system/mjs/lib/mjs/mjs_gc.h rename to lib/mjs/mjs_gc.h diff --git a/applications/system/mjs/lib/mjs/mjs_gc_public.h b/lib/mjs/mjs_gc_public.h similarity index 100% rename from applications/system/mjs/lib/mjs/mjs_gc_public.h rename to lib/mjs/mjs_gc_public.h diff --git a/applications/system/mjs/lib/mjs/mjs_internal.h b/lib/mjs/mjs_internal.h similarity index 98% rename from applications/system/mjs/lib/mjs/mjs_internal.h rename to lib/mjs/mjs_internal.h index d35ce5e700f..d395ed1ea4a 100644 --- a/applications/system/mjs/lib/mjs/mjs_internal.h +++ b/lib/mjs/mjs_internal.h @@ -25,6 +25,8 @@ #define ENDL "\n" #endif +#define MJS_EXPOSE_PRIVATE // TODO: + #ifdef MJS_EXPOSE_PRIVATE #define MJS_PRIVATE #define MJS_EXTERN extern diff --git a/applications/system/mjs/lib/mjs/mjs_json.c b/lib/mjs/mjs_json.c similarity index 99% rename from applications/system/mjs/lib/mjs/mjs_json.c rename to lib/mjs/mjs_json.c index 45839968ccd..a5045cb5f40 100644 --- a/applications/system/mjs/lib/mjs/mjs_json.c +++ b/lib/mjs/mjs_json.c @@ -7,11 +7,11 @@ #include "common/frozen/frozen.h" #include "mjs_array.h" #include "mjs_internal.h" -#include "mjs_conversion.h" #include "mjs_core.h" #include "mjs_object.h" #include "mjs_primitive.h" #include "mjs_string.h" +#include "mjs_util_public.h" #define BUF_LEFT(size, used) (((size_t)(used) < (size)) ? ((size) - (used)) : 0) diff --git a/applications/system/mjs/lib/mjs/mjs_json.h b/lib/mjs/mjs_json.h similarity index 100% rename from applications/system/mjs/lib/mjs/mjs_json.h rename to lib/mjs/mjs_json.h diff --git a/applications/system/mjs/lib/mjs/mjs_license.h b/lib/mjs/mjs_license.h similarity index 100% rename from applications/system/mjs/lib/mjs/mjs_license.h rename to lib/mjs/mjs_license.h diff --git a/applications/system/mjs/lib/mjs/mjs_mm.h b/lib/mjs/mjs_mm.h similarity index 100% rename from applications/system/mjs/lib/mjs/mjs_mm.h rename to lib/mjs/mjs_mm.h diff --git a/applications/system/mjs/lib/mjs/mjs_object.c b/lib/mjs/mjs_object.c similarity index 99% rename from applications/system/mjs/lib/mjs/mjs_object.c rename to lib/mjs/mjs_object.c index 7b4a3fe81bc..e3f18abbaff 100644 --- a/applications/system/mjs/lib/mjs/mjs_object.c +++ b/lib/mjs/mjs_object.c @@ -4,7 +4,6 @@ */ #include "mjs_object.h" -#include "mjs_conversion.h" #include "mjs_core.h" #include "mjs_internal.h" #include "mjs_primitive.h" diff --git a/applications/system/mjs/lib/mjs/mjs_object.h b/lib/mjs/mjs_object.h similarity index 100% rename from applications/system/mjs/lib/mjs/mjs_object.h rename to lib/mjs/mjs_object.h diff --git a/applications/system/mjs/lib/mjs/mjs_object_public.h b/lib/mjs/mjs_object_public.h similarity index 100% rename from applications/system/mjs/lib/mjs/mjs_object_public.h rename to lib/mjs/mjs_object_public.h diff --git a/applications/system/mjs/lib/mjs/mjs_parser.c b/lib/mjs/mjs_parser.c similarity index 100% rename from applications/system/mjs/lib/mjs/mjs_parser.c rename to lib/mjs/mjs_parser.c diff --git a/applications/system/mjs/lib/mjs/mjs_parser.h b/lib/mjs/mjs_parser.h similarity index 100% rename from applications/system/mjs/lib/mjs/mjs_parser.h rename to lib/mjs/mjs_parser.h diff --git a/applications/system/mjs/lib/mjs/mjs_primitive.c b/lib/mjs/mjs_primitive.c similarity index 100% rename from applications/system/mjs/lib/mjs/mjs_primitive.c rename to lib/mjs/mjs_primitive.c diff --git a/applications/system/mjs/lib/mjs/mjs_primitive.h b/lib/mjs/mjs_primitive.h similarity index 100% rename from applications/system/mjs/lib/mjs/mjs_primitive.h rename to lib/mjs/mjs_primitive.h diff --git a/applications/system/mjs/lib/mjs/mjs_primitive_public.h b/lib/mjs/mjs_primitive_public.h similarity index 100% rename from applications/system/mjs/lib/mjs/mjs_primitive_public.h rename to lib/mjs/mjs_primitive_public.h diff --git a/applications/system/mjs/lib/mjs/mjs_string.c b/lib/mjs/mjs_string.c similarity index 99% rename from applications/system/mjs/lib/mjs/mjs_string.c rename to lib/mjs/mjs_string.c index 0d7b693d543..f74bf1074fb 100644 --- a/applications/system/mjs/lib/mjs/mjs_string.c +++ b/lib/mjs/mjs_string.c @@ -6,7 +6,6 @@ #include "mjs_string.h" #include "common/cs_varint.h" #include "common/mg_str.h" -#include "mjs_conversion.h" #include "mjs_core.h" #include "mjs_internal.h" #include "mjs_primitive.h" diff --git a/applications/system/mjs/lib/mjs/mjs_string.h b/lib/mjs/mjs_string.h similarity index 100% rename from applications/system/mjs/lib/mjs/mjs_string.h rename to lib/mjs/mjs_string.h diff --git a/applications/system/mjs/lib/mjs/mjs_string_public.h b/lib/mjs/mjs_string_public.h similarity index 100% rename from applications/system/mjs/lib/mjs/mjs_string_public.h rename to lib/mjs/mjs_string_public.h diff --git a/applications/system/mjs/lib/mjs/mjs_tok.c b/lib/mjs/mjs_tok.c similarity index 100% rename from applications/system/mjs/lib/mjs/mjs_tok.c rename to lib/mjs/mjs_tok.c diff --git a/applications/system/mjs/lib/mjs/mjs_tok.h b/lib/mjs/mjs_tok.h similarity index 100% rename from applications/system/mjs/lib/mjs/mjs_tok.h rename to lib/mjs/mjs_tok.h diff --git a/applications/system/mjs/lib/mjs/mjs_util.c b/lib/mjs/mjs_util.c similarity index 88% rename from applications/system/mjs/lib/mjs/mjs_util.c rename to lib/mjs/mjs_util.c index fa17024dcc7..383b0182414 100644 --- a/applications/system/mjs/lib/mjs/mjs_util.c +++ b/lib/mjs/mjs_util.c @@ -474,3 +474,70 @@ int mjs_get_offset_by_call_frame_num(struct mjs* mjs, int cf_num) { } 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 { + 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)); +} diff --git a/applications/system/mjs/lib/mjs/mjs_util.h b/lib/mjs/mjs_util.h similarity index 100% rename from applications/system/mjs/lib/mjs/mjs_util.h rename to lib/mjs/mjs_util.h diff --git a/applications/system/mjs/lib/mjs/mjs_util_public.h b/lib/mjs/mjs_util_public.h similarity index 63% rename from applications/system/mjs/lib/mjs/mjs_util_public.h rename to lib/mjs/mjs_util_public.h index a6a3d477f34..b41cdb8c962 100644 --- a/applications/system/mjs/lib/mjs/mjs_util_public.h +++ b/lib/mjs/mjs_util_public.h @@ -43,6 +43,25 @@ int mjs_get_lineno_by_offset(struct mjs* mjs, int offset); */ int mjs_get_offset_by_call_frame_num(struct mjs* mjs, int cf_num); +/* + * Tries to convert `mjs_val_t` to a string, returns MJS_OK if successful. + * String is returned as a pair of pointers: `char **p, size_t *sizep`. + * + * Caller must also provide a non-null `need_free`, and if it is non-zero, + * then the string `*p` should be freed by the caller. + * + * MJS does not support `toString()` and `valueOf()`, so, passing an object + * always results in `MJS_TYPE_ERROR`. + */ +mjs_err_t mjs_to_string(struct mjs* mjs, mjs_val_t* v, char** p, size_t* sizep, int* need_free); + +/* + * Converts value to boolean as in the expression `if (v)`. + */ +mjs_val_t mjs_to_boolean_v(struct mjs* mjs, mjs_val_t v); + +int mjs_is_truthy(struct mjs* mjs, mjs_val_t v); + #if defined(__cplusplus) } #endif /* __cplusplus */ From 573d9122039b49f2c1779c816816568c00cd0a53 Mon Sep 17 00:00:00 2001 From: nminaylov Date: Wed, 18 Oct 2023 15:52:39 +0300 Subject: [PATCH 15/31] Plugin-based js modules --- applications/system/mjs/application.fam | 22 ++++++++++++-- applications/system/mjs/js_modules.c | 30 +++++++++---------- applications/system/mjs/js_modules.h | 9 ++++++ applications/system/mjs/js_thread.c | 2 +- applications/system/mjs/js_thread_i.h | 2 -- applications/system/mjs/modules/js_badusb.c | 20 +++++++++++-- applications/system/mjs/modules/js_badusb.h | 6 ---- applications/system/mjs/modules/js_dialog.c | 18 ++++++++++- applications/system/mjs/modules/js_dialog.h | 6 ---- .../system/mjs/modules/js_notification.c | 20 +++++++++++-- .../system/mjs/modules/js_notification.h | 6 ---- applications/system/mjs/modules/module_test.c | 18 ----------- .../app_api_interface.h | 0 .../app_api_table.cpp | 0 .../app_api_table_i.h | 2 ++ .../system/mjs/plugin_api/js_plugin_api.h | 14 +++++++++ .../plugins/plugin_manager.c | 3 +- 17 files changed, 115 insertions(+), 63 deletions(-) delete mode 100644 applications/system/mjs/modules/js_badusb.h delete mode 100644 applications/system/mjs/modules/js_dialog.h delete mode 100644 applications/system/mjs/modules/js_notification.h delete mode 100644 applications/system/mjs/modules/module_test.c rename applications/system/mjs/{addon_api => plugin_api}/app_api_interface.h (100%) rename applications/system/mjs/{addon_api => plugin_api}/app_api_table.cpp (100%) rename applications/system/mjs/{addon_api => plugin_api}/app_api_table_i.h (81%) create mode 100644 applications/system/mjs/plugin_api/js_plugin_api.h diff --git a/applications/system/mjs/application.fam b/applications/system/mjs/application.fam index feed2e77d71..75b55d820a9 100644 --- a/applications/system/mjs/application.fam +++ b/applications/system/mjs/application.fam @@ -11,9 +11,25 @@ App( ) App( - appid="js_module_test", + appid="js_dialog", apptype=FlipperAppType.PLUGIN, - entry_point="js_module_test_ep", + entry_point="js_dialog_ep", requires=["js_app"], - sources=["modules/module_test.c"], + sources=["modules/js_dialog.c"], +) + +App( + appid="js_notification", + apptype=FlipperAppType.PLUGIN, + entry_point="js_notification_ep", + requires=["js_app"], + sources=["modules/js_notification.c"], +) + +App( + appid="js_badusb", + apptype=FlipperAppType.PLUGIN, + entry_point="js_badusb_ep", + requires=["js_app"], + sources=["modules/js_badusb.c"], ) diff --git a/applications/system/mjs/js_modules.c b/applications/system/mjs/js_modules.c index 87291df5dd5..43bf74200fd 100644 --- a/applications/system/mjs/js_modules.c +++ b/applications/system/mjs/js_modules.c @@ -2,12 +2,8 @@ #include "js_modules.h" #include #include "modules/js_flipper.h" -#include "modules/js_badusb.h" -#include "modules/js_notification.h" -#include "modules/js_dialog.h" -typedef void* (*JsModeConstructor)(struct mjs* mjs, mjs_val_t* object); -typedef void (*JsModeDestructor)(void* inst); +#define TAG "JS modules" typedef struct { JsModeConstructor create; @@ -17,15 +13,8 @@ typedef struct { DICT_DEF2(JsModuleDict, FuriString*, FURI_STRING_OPLIST, JsModuleData, M_POD_OPLIST); -static const struct { - char* name; - JsModeConstructor create; - JsModeDestructor destroy; -} modules_builtin[] = { +static const JsModuleDescriptor modules_builtin[] = { {"flipper", js_flipper_create, NULL}, - {"badusb", js_badusb_create, js_badusb_destroy}, - {"notification", js_notification_create, js_notification_destroy}, - {"dialog", js_dialog_create, NULL}, }; struct JsModules { @@ -91,15 +80,26 @@ mjs_val_t js_module_require(JsModules* modules, const char* name, size_t name_le // External module load if(!module_found) { FuriString* module_path = furi_string_alloc(); - furi_string_printf(module_path, "%s/%s.fal", APP_DATA_PATH("plugins"), name); + furi_string_printf(module_path, "%s/js_%s.fal", APP_DATA_PATH("plugins"), name); FURI_LOG_I(TAG, "Loading external module %s", furi_string_get_cstr(module_path)); do { + uint32_t plugin_cnt_last = plugin_manager_get_count(modules->plugin_manager); PluginManagerError load_error = plugin_manager_load_single( modules->plugin_manager, furi_string_get_cstr(module_path)); if(load_error != PluginManagerErrorNone) { break; } - // TODO: get ep struct, compare name, add to dict + const JsModuleDescriptor* plugin = + plugin_manager_get_ep(modules->plugin_manager, plugin_cnt_last); + furi_assert(plugin); + + if(strncmp(name, plugin->name, name_len) != 0) { + FURI_LOG_E(TAG, "Module name missmatch %s", plugin->name); + break; + } + JsModuleData module = {.create = plugin->create, .destroy = plugin->destroy}; + JsModuleDict_set_at(modules->module_dict, module_name, module); + module_found = true; } while(0); furi_string_free(module_path); diff --git a/applications/system/mjs/js_modules.h b/applications/system/mjs/js_modules.h index e741c683464..aff4d7e8df1 100644 --- a/applications/system/mjs/js_modules.h +++ b/applications/system/mjs/js_modules.h @@ -7,6 +7,15 @@ #define PLUGIN_APP_ID "js" #define PLUGIN_API_VERSION 1 +typedef void* (*JsModeConstructor)(struct mjs* mjs, mjs_val_t* object); +typedef void (*JsModeDestructor)(void* inst); + +typedef struct { + char* name; + JsModeConstructor create; + JsModeDestructor destroy; +} JsModuleDescriptor; + typedef struct JsModules JsModules; JsModules* js_modules_create(struct mjs* mjs, CompositeApiResolver* resolver); diff --git a/applications/system/mjs/js_thread.c b/applications/system/mjs/js_thread.c index 36bfec383ee..92d631a7897 100644 --- a/applications/system/mjs/js_thread.c +++ b/applications/system/mjs/js_thread.c @@ -3,7 +3,7 @@ #include #include #include -#include "addon_api/app_api_interface.h" +#include "plugin_api/app_api_interface.h" #include "js_thread.h" #include "js_thread_i.h" #include "js_modules.h" diff --git a/applications/system/mjs/js_thread_i.h b/applications/system/mjs/js_thread_i.h index 2c3a29ca7ac..c4a9c765321 100644 --- a/applications/system/mjs/js_thread_i.h +++ b/applications/system/mjs/js_thread_i.h @@ -10,8 +10,6 @@ #include #include -#define TAG "JS" - #define INST_PROP_NAME "_" #define MFS_MK_FN(fn) mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)fn) diff --git a/applications/system/mjs/modules/js_badusb.c b/applications/system/mjs/modules/js_badusb.c index a538b93ba97..68c8d197a7d 100644 --- a/applications/system/mjs/modules/js_badusb.c +++ b/applications/system/mjs/modules/js_badusb.c @@ -366,7 +366,7 @@ static void js_badusb_println(struct mjs* mjs) { badusb_print(mjs, true); } -void* js_badusb_create(struct mjs* mjs, mjs_val_t* object) { +static void* js_badusb_create(struct mjs* mjs, mjs_val_t* object) { JsBadusbInst* badusb = malloc(sizeof(JsBadusbInst)); mjs_val_t badusb_obj = mjs_mk_object(mjs); mjs_set(mjs, badusb_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, badusb)); @@ -381,7 +381,7 @@ void* js_badusb_create(struct mjs* mjs, mjs_val_t* object) { return badusb; } -void js_badusb_destroy(void* inst) { +static void js_badusb_destroy(void* inst) { JsBadusbInst* badusb = inst; if(badusb->usb_if_prev) { furi_hal_hid_kb_release_all(); @@ -392,3 +392,19 @@ void js_badusb_destroy(void* inst) { } free(badusb); } + +static const JsModuleDescriptor js_badusb_desc = { + "badusb", + js_badusb_create, + js_badusb_destroy, +}; + +static const FlipperAppPluginDescriptor plugin_descriptor = { + .appid = PLUGIN_APP_ID, + .ep_api_version = PLUGIN_API_VERSION, + .entry_point = &js_badusb_desc, +}; + +const FlipperAppPluginDescriptor* js_badusb_ep(void) { + return &plugin_descriptor; +} diff --git a/applications/system/mjs/modules/js_badusb.h b/applications/system/mjs/modules/js_badusb.h deleted file mode 100644 index 2b33ef6d1fc..00000000000 --- a/applications/system/mjs/modules/js_badusb.h +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once -#include "js_thread_i.h" - -void* js_badusb_create(struct mjs* mjs, mjs_val_t* object); - -void js_badusb_destroy(void* inst); diff --git a/applications/system/mjs/modules/js_dialog.c b/applications/system/mjs/modules/js_dialog.c index 22e38fdac7b..7514b52afe5 100644 --- a/applications/system/mjs/modules/js_dialog.c +++ b/applications/system/mjs/modules/js_dialog.c @@ -128,7 +128,7 @@ static void js_dialog_custom(struct mjs* mjs) { } } -void* js_dialog_create(struct mjs* mjs, mjs_val_t* object) { +static void* js_dialog_create(struct mjs* mjs, mjs_val_t* object) { mjs_val_t dialog_obj = mjs_mk_object(mjs); mjs_set(mjs, dialog_obj, "message", ~0, MFS_MK_FN(js_dialog_message)); mjs_set(mjs, dialog_obj, "custom", ~0, MFS_MK_FN(js_dialog_custom)); @@ -136,3 +136,19 @@ void* js_dialog_create(struct mjs* mjs, mjs_val_t* object) { return (void*)1; } + +static const JsModuleDescriptor js_dialog_desc = { + "dialog", + js_dialog_create, + NULL, +}; + +static const FlipperAppPluginDescriptor plugin_descriptor = { + .appid = PLUGIN_APP_ID, + .ep_api_version = PLUGIN_API_VERSION, + .entry_point = &js_dialog_desc, +}; + +const FlipperAppPluginDescriptor* js_dialog_ep(void) { + return &plugin_descriptor; +} diff --git a/applications/system/mjs/modules/js_dialog.h b/applications/system/mjs/modules/js_dialog.h deleted file mode 100644 index 27a04a84fd6..00000000000 --- a/applications/system/mjs/modules/js_dialog.h +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once -#include "js_thread_i.h" - -void* js_dialog_create(struct mjs* mjs, mjs_val_t* object); - -void js_dialog_destroy(void* inst); diff --git a/applications/system/mjs/modules/js_notification.c b/applications/system/mjs/modules/js_notification.c index 742e9b8184f..42897c782fc 100644 --- a/applications/system/mjs/modules/js_notification.c +++ b/applications/system/mjs/modules/js_notification.c @@ -75,7 +75,7 @@ static void js_notify_blink(struct mjs* mjs) { mjs_return(mjs, MJS_UNDEFINED); } -void* js_notification_create(struct mjs* mjs, mjs_val_t* object) { +static void* js_notification_create(struct mjs* mjs, mjs_val_t* object) { NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); mjs_val_t notify_obj = mjs_mk_object(mjs); mjs_set(mjs, notify_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, notification)); @@ -87,7 +87,23 @@ void* js_notification_create(struct mjs* mjs, mjs_val_t* object) { return notification; } -void js_notification_destroy(void* inst) { +static void js_notification_destroy(void* inst) { UNUSED(inst); furi_record_close(RECORD_NOTIFICATION); } + +static const JsModuleDescriptor js_notification_desc = { + "notification", + js_notification_create, + js_notification_destroy, +}; + +static const FlipperAppPluginDescriptor plugin_descriptor = { + .appid = PLUGIN_APP_ID, + .ep_api_version = PLUGIN_API_VERSION, + .entry_point = &js_notification_desc, +}; + +const FlipperAppPluginDescriptor* js_notification_ep(void) { + return &plugin_descriptor; +} \ No newline at end of file diff --git a/applications/system/mjs/modules/js_notification.h b/applications/system/mjs/modules/js_notification.h deleted file mode 100644 index db450afd00f..00000000000 --- a/applications/system/mjs/modules/js_notification.h +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once -#include "js_thread_i.h" - -void* js_notification_create(struct mjs* mjs, mjs_val_t* object); - -void js_notification_destroy(void* inst); diff --git a/applications/system/mjs/modules/module_test.c b/applications/system/mjs/modules/module_test.c deleted file mode 100644 index 404681b9a1d..00000000000 --- a/applications/system/mjs/modules/module_test.c +++ /dev/null @@ -1,18 +0,0 @@ -#include -#include "js_modules.h" - -void plugin_test(void) { - FURI_LOG_I("", "Test"); - mjs_val_t flipper_obj = mjs_mk_object(NULL); - UNUSED(flipper_obj); -} - -static const FlipperAppPluginDescriptor advanced_plugin1_descriptor = { - .appid = PLUGIN_APP_ID, - .ep_api_version = PLUGIN_API_VERSION, - .entry_point = &plugin_test, -}; - -const FlipperAppPluginDescriptor* js_module_test_ep() { - return &advanced_plugin1_descriptor; -} diff --git a/applications/system/mjs/addon_api/app_api_interface.h b/applications/system/mjs/plugin_api/app_api_interface.h similarity index 100% rename from applications/system/mjs/addon_api/app_api_interface.h rename to applications/system/mjs/plugin_api/app_api_interface.h diff --git a/applications/system/mjs/addon_api/app_api_table.cpp b/applications/system/mjs/plugin_api/app_api_table.cpp similarity index 100% rename from applications/system/mjs/addon_api/app_api_table.cpp rename to applications/system/mjs/plugin_api/app_api_table.cpp diff --git a/applications/system/mjs/addon_api/app_api_table_i.h b/applications/system/mjs/plugin_api/app_api_table_i.h similarity index 81% rename from applications/system/mjs/addon_api/app_api_table_i.h rename to applications/system/mjs/plugin_api/app_api_table_i.h index 796344ff061..73a349b2bc1 100644 --- a/applications/system/mjs/addon_api/app_api_table_i.h +++ b/applications/system/mjs/plugin_api/app_api_table_i.h @@ -1,9 +1,11 @@ #include +#include "js_plugin_api.h" /* * A list of app's private functions and objects to expose for plugins. * It is used to generate a table of symbols for import resolver to use. * TBD: automatically generate this table from app's header files */ static constexpr auto app_api_table = sort(create_array_t( + API_METHOD(js_delay_with_flags, bool, (struct mjs*, uint32_t)), API_VARIABLE(I_Certification1_103x56, const Icon), API_VARIABLE(I_Certification2_46x33, const Icon))); \ No newline at end of file diff --git a/applications/system/mjs/plugin_api/js_plugin_api.h b/applications/system/mjs/plugin_api/js_plugin_api.h new file mode 100644 index 00000000000..b36c1c489ad --- /dev/null +++ b/applications/system/mjs/plugin_api/js_plugin_api.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +bool js_delay_with_flags(struct mjs* mjs, uint32_t time); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/flipper_application/plugins/plugin_manager.c b/lib/flipper_application/plugins/plugin_manager.c index e2a7b83f422..8f30ed13ec5 100644 --- a/lib/flipper_application/plugins/plugin_manager.c +++ b/lib/flipper_application/plugins/plugin_manager.c @@ -66,7 +66,8 @@ PluginManagerError plugin_manager_load_single(PluginManager* manager, const char FlipperApplicationLoadStatus load_status = flipper_application_map_to_memory(lib); if(load_status != FlipperApplicationLoadStatusSuccess) { - FURI_LOG_E(TAG, "Failed to load module_demo_plugin1.fal"); + FURI_LOG_E(TAG, "Failed to load %s", path); + error = PluginManagerErrorLoaderError; break; } From a8f3dca7819b4ecd02c33ac20ee9081236d6e758 Mon Sep 17 00:00:00 2001 From: nminaylov Date: Fri, 27 Oct 2023 18:29:04 +0300 Subject: [PATCH 16/31] js_uart(BadUART) module --- applications/system/mjs/application.fam | 8 + applications/system/mjs/assets/uart.js | 21 + applications/system/mjs/js_app.c | 1 - applications/system/mjs/js_thread.c | 36 +- applications/system/mjs/js_thread.h | 2 - applications/system/mjs/js_thread_i.h | 9 + applications/system/mjs/modules/js_uart.c | 441 ++++++++++++++++++ .../system/mjs/plugin_api/app_api_table_i.h | 2 + .../system/mjs/plugin_api/js_plugin_api.h | 4 + firmware/targets/f7/api_symbols.csv | 10 +- lib/mjs/SConscript | 1 + 11 files changed, 522 insertions(+), 13 deletions(-) create mode 100644 applications/system/mjs/assets/uart.js create mode 100644 applications/system/mjs/modules/js_uart.c diff --git a/applications/system/mjs/application.fam b/applications/system/mjs/application.fam index 75b55d820a9..002284df761 100644 --- a/applications/system/mjs/application.fam +++ b/applications/system/mjs/application.fam @@ -33,3 +33,11 @@ App( requires=["js_app"], sources=["modules/js_badusb.c"], ) + +App( + appid="js_uart", + apptype=FlipperAppType.PLUGIN, + entry_point="js_uart_ep", + requires=["js_app"], + sources=["modules/js_uart.c"], +) diff --git a/applications/system/mjs/assets/uart.js b/applications/system/mjs/assets/uart.js new file mode 100644 index 00000000000..1276f578e11 --- /dev/null +++ b/applications/system/mjs/assets/uart.js @@ -0,0 +1,21 @@ +let uart = require("uart"); +uart.setup(115200); + +uart.write("\n"); +let console_resp = uart.expect("# ", 1000); +if (console_resp === undefined) { + print("No CLI response"); +} else { + uart.write("uci\n"); + let uci_state = uart.expect([": not found", "Usage: "]); + if (uci_state === 1) { + uart.expect("# "); + uart.write("uci show wireless\n"); + uart.expect(".key="); + print("key:", uart.readln()); + } else { + print("uci cmd not found"); + } +} + + diff --git a/applications/system/mjs/js_app.c b/applications/system/mjs/js_app.c index a8cafdac81f..4f8e1b3ebae 100644 --- a/applications/system/mjs/js_app.c +++ b/applications/system/mjs/js_app.c @@ -121,7 +121,6 @@ int32_t js_app(void* arg) { view_dispatcher_run(app->view_dispatcher); js_thread_stop(app->js_thread); - js_thread_free(app->js_thread); } while(0); furi_string_free(script_path); diff --git a/applications/system/mjs/js_thread.c b/applications/system/mjs/js_thread.c index 92d631a7897..5108ba0da4e 100644 --- a/applications/system/mjs/js_thread.c +++ b/applications/system/mjs/js_thread.c @@ -19,11 +19,6 @@ struct JsThread { JsModules* modules; }; -typedef enum { - ThreadEventStop = (1 << 0), - ThreadEventTest = (1 << 1), -} WorkerEventFlags; - static void mjs_str_print(FuriString* msg_str, struct mjs* mjs) { size_t num_args = mjs_nargs(mjs); for(size_t i = 0; i < num_args; i++) { @@ -115,6 +110,32 @@ bool js_delay_with_flags(struct mjs* mjs, uint32_t time) { return false; } +void js_flags_set(struct mjs* mjs, uint32_t flags) { + JsThread* worker = mjs_get_context(mjs); + furi_assert(worker); + furi_thread_flags_set(furi_thread_get_id(worker->thread), flags); +} + +uint32_t js_flags_wait(struct mjs* mjs, uint32_t flags_mask, uint32_t timeout) { + flags_mask |= ThreadEventStop; + uint32_t flags = furi_thread_flags_get(); + furi_check((flags & FuriFlagError) == 0); + if(flags == 0) { + flags = furi_thread_flags_wait(flags_mask, FuriFlagWaitAny, timeout); + } else { + uint32_t state = furi_thread_flags_clear(flags & flags_mask); + furi_check((state & FuriFlagError) == 0); + } + + if(flags & FuriFlagError) { + return 0; + } + if(flags & ThreadEventStop) { + mjs_exit(mjs); + } + return flags; +} + static void mjs_delay(struct mjs* mjs) { bool args_correct = false; int ms = 0; @@ -238,8 +259,8 @@ static int32_t js_thread(void* arg) { } } - mjs_destroy(mjs); js_modules_destroy(worker->modules); + mjs_destroy(mjs); composite_api_resolver_free(worker->resolver); @@ -258,9 +279,6 @@ JsThread* js_thread_run(const char* script_path, JsThreadCallback callback, void void js_thread_stop(JsThread* worker) { furi_thread_flags_set(furi_thread_get_id(worker->thread), ThreadEventStop); -} - -void js_thread_free(JsThread* worker) { furi_thread_join(worker->thread); furi_thread_free(worker->thread); furi_string_free(worker->path); diff --git a/applications/system/mjs/js_thread.h b/applications/system/mjs/js_thread.h index c18d3d7f26a..87303b5a9d3 100644 --- a/applications/system/mjs/js_thread.h +++ b/applications/system/mjs/js_thread.h @@ -15,5 +15,3 @@ typedef void (*JsThreadCallback)(JsThreadEvent event, const char* msg, void* con JsThread* js_thread_run(const char* script_path, JsThreadCallback callback, void* context); void js_thread_stop(JsThread* worker); - -void js_thread_free(JsThread* worker); diff --git a/applications/system/mjs/js_thread_i.h b/applications/system/mjs/js_thread_i.h index c4a9c765321..8e726bed66f 100644 --- a/applications/system/mjs/js_thread_i.h +++ b/applications/system/mjs/js_thread_i.h @@ -14,4 +14,13 @@ #define MFS_MK_FN(fn) mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)fn) +typedef enum { + ThreadEventStop = (1 << 0), + ThreadEventCustomDataRx = (1 << 1), +} WorkerEventFlags; + bool js_delay_with_flags(struct mjs* mjs, uint32_t time); + +void js_flags_set(struct mjs* mjs, uint32_t flags); + +uint32_t js_flags_wait(struct mjs* mjs, uint32_t flags, uint32_t timeout); diff --git a/applications/system/mjs/modules/js_uart.c b/applications/system/mjs/modules/js_uart.c new file mode 100644 index 00000000000..f27726446de --- /dev/null +++ b/applications/system/mjs/modules/js_uart.c @@ -0,0 +1,441 @@ +#include +#include +#include "js_modules.h" +#include + +#define TAG "js_uart" +#define RX_BUF_LEN 2048 + +typedef struct { + bool setup_done; + FuriStreamBuffer* rx_stream; + struct mjs* mjs; +} JsUartInst; + +typedef struct { + size_t len; + char* text; +} PatternListItem; + +ARRAY_DEF(PatternList, PatternListItem, M_POD_OPLIST); + +static void js_uart_on_irq_cb(UartIrqEvent ev, uint8_t data, void* context) { + JsUartInst* uart = context; + furi_assert(uart); + + if(ev == UartIrqEventRXNE) { + furi_stream_buffer_send(uart->rx_stream, &data, 1, 0); + js_flags_set(uart->mjs, ThreadEventCustomDataRx); + } +} + +static void js_uart_setup(struct mjs* mjs) { + mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); + JsUartInst* uart = mjs_get_ptr(mjs, obj_inst); + furi_assert(uart); + + bool args_correct = false; + uint32_t baudrate = 0; + + if(mjs_nargs(mjs) == 1) { + mjs_val_t arg = mjs_arg(mjs, 0); + if(mjs_is_number(arg)) { + baudrate = mjs_get_int32(mjs, arg); + args_correct = true; + } + } + if(!args_correct) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + uart->rx_stream = furi_stream_buffer_alloc(RX_BUF_LEN, 1); + furi_hal_uart_init(FuriHalUartIdLPUART1, baudrate); + furi_hal_uart_set_irq_cb(FuriHalUartIdLPUART1, js_uart_on_irq_cb, uart); + uart->setup_done = true; +} + +static void js_uart_write(struct mjs* mjs) { + mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); + JsUartInst* uart = mjs_get_ptr(mjs, obj_inst); + furi_assert(uart); + if(!uart->setup_done) { + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "UART is not configured"); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + bool args_correct = true; + + size_t num_args = mjs_nargs(mjs); + for(size_t i = 0; i < num_args; i++) { + mjs_val_t arg = mjs_arg(mjs, i); + if(mjs_is_string(arg)) { + size_t str_len = 0; + const char* arg_str = mjs_get_string(mjs, &arg, &str_len); + if((str_len == 0) || (arg_str == NULL)) { + args_correct = false; + break; + } + furi_hal_uart_tx(FuriHalUartIdLPUART1, (uint8_t*)arg_str, str_len); + } else if(mjs_is_number(arg)) { + uint32_t byte_val = mjs_get_int32(mjs, arg); + if(byte_val > 0xFF) { + args_correct = false; + break; + } + furi_hal_uart_tx(FuriHalUartIdLPUART1, (uint8_t*)&byte_val, 1); + } else { + // TODO: mjs_is_array(v) + args_correct = false; + break; + } + } + + if(!args_correct) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); + } + mjs_return(mjs, MJS_UNDEFINED); +} + +static size_t js_uart_receive(JsUartInst* uart, char* buf, size_t len, uint32_t timeout) { + size_t bytes_read = 0; + while(1) { + uint32_t flags = ThreadEventCustomDataRx; + if(furi_stream_buffer_is_empty(uart->rx_stream)) { + flags = js_flags_wait(uart->mjs, ThreadEventCustomDataRx, timeout); + } + if(flags == 0) { // Timeout + break; + } else if(flags & ThreadEventStop) { // Exit flag + bytes_read = 0; + break; + } else if(flags & ThreadEventCustomDataRx) { // New data received + size_t rx_len = + furi_stream_buffer_receive(uart->rx_stream, &buf[bytes_read], len - bytes_read, 0); + bytes_read += rx_len; + if(bytes_read == len) { + break; + } + } + } + return bytes_read; +} + +static void js_uart_read(struct mjs* mjs) { + mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); + JsUartInst* uart = mjs_get_ptr(mjs, obj_inst); + furi_assert(uart); + if(!uart->setup_done) { + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "UART is not configured"); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + size_t read_len = 0; + uint32_t timeout = FuriWaitForever; + + do { + size_t num_args = mjs_nargs(mjs); + if(num_args == 1) { + mjs_val_t arg = mjs_arg(mjs, 0); + if(!mjs_is_number(arg)) { + break; + } + read_len = mjs_get_int32(mjs, arg); + } else if(num_args == 2) { + mjs_val_t len_arg = mjs_arg(mjs, 0); + mjs_val_t timeout_arg = mjs_arg(mjs, 1); + if((!mjs_is_number(len_arg)) || (!mjs_is_number(timeout_arg))) { + break; + } + read_len = mjs_get_int32(mjs, len_arg); + timeout = mjs_get_int32(mjs, timeout_arg); + } + } while(0); + + if(read_len == 0) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + char* read_buf = malloc(read_len); + size_t bytes_read = js_uart_receive(uart, read_buf, read_len, timeout); + + mjs_val_t return_obj = MJS_UNDEFINED; + if(bytes_read > 0) { + return_obj = mjs_mk_string(mjs, read_buf, bytes_read, true); + } + mjs_return(mjs, return_obj); + free(read_buf); +} + +static void js_uart_readln(struct mjs* mjs) { + mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); + JsUartInst* uart = mjs_get_ptr(mjs, obj_inst); + furi_assert(uart); + if(!uart->setup_done) { + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "UART is not configured"); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + bool args_correct = false; + uint32_t timeout = FuriWaitForever; + + do { + size_t num_args = mjs_nargs(mjs); + if(num_args > 1) { + break; + } else if(num_args == 1) { + mjs_val_t arg = mjs_arg(mjs, 0); + if(!mjs_is_number(arg)) { + break; + } + timeout = mjs_get_int32(mjs, arg); + } + args_correct = true; + } while(0); + + if(!args_correct) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + FuriString* rx_buf = furi_string_alloc(); + size_t bytes_read = 0; + char read_char = 0; + + while(1) { + size_t read_len = js_uart_receive(uart, &read_char, 1, timeout); + if(read_len != 1) { + break; + } + if((read_char == '\r') || (read_char == '\n')) { + break; + } else { + furi_string_push_back(rx_buf, read_char); + bytes_read++; + } + } + + mjs_val_t return_obj = MJS_UNDEFINED; + if(bytes_read > 0) { + return_obj = mjs_mk_string(mjs, furi_string_get_cstr(rx_buf), bytes_read, true); + } + mjs_return(mjs, return_obj); + furi_string_free(rx_buf); +} + +static bool js_uart_expect_parse_args(struct mjs* mjs, PatternList_t patterns, uint32_t* timeout) { + size_t num_args = mjs_nargs(mjs); + if(num_args == 2) { + mjs_val_t timeout_arg = mjs_arg(mjs, 1); + if(!mjs_is_number(timeout_arg)) { + return false; + } + *timeout = mjs_get_int32(mjs, timeout_arg); + } else if(num_args != 1) { + return false; + } + mjs_val_t patterns_arg = mjs_arg(mjs, 0); + if(mjs_is_string(patterns_arg)) { // Single string argument + size_t str_len = 0; + const char* arg_str = mjs_get_string(mjs, &patterns_arg, &str_len); + if((str_len == 0) || (arg_str == NULL)) { + return false; + } + PatternListItem* item = PatternList_push_new(patterns); + item->text = malloc(str_len + 1); + memcpy(item->text, arg_str, str_len); + item->len = str_len; + } else if(mjs_is_array(patterns_arg)) { // Array of strings + size_t patterns_cnt = mjs_array_length(mjs, patterns_arg); + for(size_t i = 0; i < patterns_cnt; i++) { + mjs_val_t arg = mjs_array_get(mjs, patterns_arg, i); + if(!mjs_is_string(arg)) { + return false; + } + size_t str_len = 0; + const char* arg_str = mjs_get_string(mjs, &arg, &str_len); + if((str_len == 0) || (arg_str == NULL)) { + return false; + } + PatternListItem* item = PatternList_push_new(patterns); + item->text = malloc(str_len + 1); + memcpy(item->text, arg_str, str_len); + item->len = str_len; + } + // TODO: support for binary patterns + } + return true; +} + +static int32_t check_pattern_start(PatternList_t patterns, char value, int32_t pattern_last) { + size_t list_len = PatternList_size(patterns); + if((pattern_last + 1) >= (int32_t)list_len) { + return (-1); + } + for(size_t i = pattern_last + 1; i < list_len; i++) { + if(PatternList_get(patterns, i)->text[0] == value) { + return i; + } + } + return (-1); +} + +static void js_uart_expect(struct mjs* mjs) { + mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); + JsUartInst* uart = mjs_get_ptr(mjs, obj_inst); + furi_assert(uart); + if(!uart->setup_done) { + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "UART is not configured"); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + uint32_t timeout = FuriWaitForever; + PatternList_t patterns; + PatternList_init(patterns); + + if(!js_uart_expect_parse_args(mjs, patterns, &timeout)) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); + mjs_return(mjs, MJS_UNDEFINED); + PatternList_clear(patterns); + return; + } + + size_t pattern_len_max = 0; + PatternList_it_t it; + for(PatternList_it(it, patterns); !PatternList_end_p(it); PatternList_next(it)) { + const PatternListItem* item = PatternList_cref(it); + if(item->len > pattern_len_max) { + pattern_len_max = item->len; + } + } + + char* compare_buf = malloc(pattern_len_max); + int32_t pattern_found = -1; + int32_t pattern_candidate = -1; + size_t buf_len = 0; + bool is_timeout = false; + + while(1) { + if(buf_len == 0) { + // Empty buffer - read by 1 byte to find pattern start + size_t bytes_read = js_uart_receive(uart, &compare_buf[0], 1, timeout); + if(bytes_read != 1) { + is_timeout = true; + break; + } + pattern_candidate = check_pattern_start(patterns, compare_buf[0], -1); + if(pattern_candidate == -1) { + continue; + } + buf_len = 1; + } + assert(pattern_candidate >= 0); + + // Read next and try to find pattern match + PatternListItem* pattern_cur = PatternList_get(patterns, pattern_candidate); + pattern_found = pattern_candidate; + for(size_t i = 0; i < pattern_cur->len; i++) { + if(i >= buf_len) { + size_t bytes_read = js_uart_receive(uart, &compare_buf[i], 1, timeout); + if(bytes_read != 1) { + is_timeout = true; + break; + } + buf_len++; + } + if(compare_buf[i] != pattern_cur->text[i]) { + pattern_found = -1; + break; + } + } + if((is_timeout) || (pattern_found >= 0)) { + break; + } + + // Search other patterns with the same start char + pattern_candidate = check_pattern_start(patterns, compare_buf[0], pattern_candidate); + if(pattern_candidate >= 0) { + continue; + } + + // Look for another pattern start + for(size_t i = 1; i < buf_len; i++) { + pattern_candidate = check_pattern_start(patterns, compare_buf[i], -1); + if(pattern_candidate >= 0) { + memmove(&compare_buf[0], &compare_buf[i], buf_len - i); + buf_len -= i; + break; + } + } + if(pattern_candidate >= 0) { + continue; + } + // Nothing found - reset buffer + buf_len = 0; + } + + if(is_timeout) { + FURI_LOG_W(TAG, "Expect: timeout"); + } + + for(PatternList_it(it, patterns); !PatternList_end_p(it); PatternList_next(it)) { + const PatternListItem* item = PatternList_cref(it); + free(item->text); + } + PatternList_clear(patterns); + free(compare_buf); + + if(pattern_found >= 0) { + mjs_return(mjs, mjs_mk_number(mjs, pattern_found)); + } else { + mjs_return(mjs, MJS_UNDEFINED); + } +} + +static void* js_uart_create(struct mjs* mjs, mjs_val_t* object) { + JsUartInst* js_uart = malloc(sizeof(JsUartInst)); + js_uart->mjs = mjs; + mjs_val_t uart_obj = mjs_mk_object(mjs); + mjs_set(mjs, uart_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, js_uart)); + mjs_set(mjs, uart_obj, "setup", ~0, MFS_MK_FN(js_uart_setup)); + mjs_set(mjs, uart_obj, "write", ~0, MFS_MK_FN(js_uart_write)); + mjs_set(mjs, uart_obj, "read", ~0, MFS_MK_FN(js_uart_read)); + mjs_set(mjs, uart_obj, "readln", ~0, MFS_MK_FN(js_uart_readln)); + mjs_set(mjs, uart_obj, "expect", ~0, MFS_MK_FN(js_uart_expect)); + *object = uart_obj; + + return js_uart; +} + +static void js_uart_destroy(void* inst) { + JsUartInst* js_uart = inst; + if(js_uart->setup_done) { + furi_hal_uart_set_irq_cb(FuriHalUartIdLPUART1, NULL, NULL); + furi_hal_uart_deinit(FuriHalUartIdLPUART1); + furi_stream_buffer_free(js_uart->rx_stream); + } + free(js_uart); +} + +static const JsModuleDescriptor js_uart_desc = { + "uart", + js_uart_create, + js_uart_destroy, +}; + +static const FlipperAppPluginDescriptor plugin_descriptor = { + .appid = PLUGIN_APP_ID, + .ep_api_version = PLUGIN_API_VERSION, + .entry_point = &js_uart_desc, +}; + +const FlipperAppPluginDescriptor* js_uart_ep(void) { + return &plugin_descriptor; +} diff --git a/applications/system/mjs/plugin_api/app_api_table_i.h b/applications/system/mjs/plugin_api/app_api_table_i.h index 73a349b2bc1..8f1ebd5f4b3 100644 --- a/applications/system/mjs/plugin_api/app_api_table_i.h +++ b/applications/system/mjs/plugin_api/app_api_table_i.h @@ -7,5 +7,7 @@ */ static constexpr auto app_api_table = sort(create_array_t( API_METHOD(js_delay_with_flags, bool, (struct mjs*, uint32_t)), + API_METHOD(js_flags_set, void, (struct mjs*, uint32_t)), + API_METHOD(js_flags_wait, uint32_t, (struct mjs*, uint32_t, uint32_t)), API_VARIABLE(I_Certification1_103x56, const Icon), API_VARIABLE(I_Certification2_46x33, const Icon))); \ No newline at end of file diff --git a/applications/system/mjs/plugin_api/js_plugin_api.h b/applications/system/mjs/plugin_api/js_plugin_api.h index b36c1c489ad..4918605876d 100644 --- a/applications/system/mjs/plugin_api/js_plugin_api.h +++ b/applications/system/mjs/plugin_api/js_plugin_api.h @@ -9,6 +9,10 @@ extern "C" { bool js_delay_with_flags(struct mjs* mjs, uint32_t time); +void js_flags_set(struct mjs* mjs, uint32_t flags); + +uint32_t js_flags_wait(struct mjs* mjs, uint32_t flags, uint32_t timeout); + #ifdef __cplusplus } #endif \ No newline at end of file diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 862b9cb5d06..ddb72fe36a9 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,39.3,, +Version,+,39.4,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -139,6 +139,7 @@ Header,+,lib/libusb_stm32/inc/usbd_core.h,, Header,+,lib/mbedtls/include/mbedtls/des.h,, Header,+,lib/mbedtls/include/mbedtls/sha1.h,, Header,+,lib/micro-ecc/uECC.h,, +Header,+,lib/mjs/mjs_array_public.h,, Header,+,lib/mjs/mjs_core_public.h,, Header,+,lib/mjs/mjs_exec_public.h,, Header,+,lib/mjs/mjs_object_public.h,, @@ -2090,6 +2091,11 @@ Function,-,mfkey32_process_data,void,"Mfkey32*, uint8_t*, uint16_t, _Bool, _Bool Function,-,mfkey32_set_callback,void,"Mfkey32*, Mfkey32ParseDataCallback, void*" Function,+,mjs_apply,mjs_err_t,"mjs*, mjs_val_t*, mjs_val_t, mjs_val_t, int, mjs_val_t*" Function,+,mjs_arg,mjs_val_t,"mjs*, int" +Function,+,mjs_array_del,void,"mjs*, mjs_val_t, unsigned long" +Function,+,mjs_array_get,mjs_val_t,"mjs*, mjs_val_t, unsigned long" +Function,+,mjs_array_length,unsigned long,"mjs*, mjs_val_t" +Function,+,mjs_array_push,mjs_err_t,"mjs*, mjs_val_t, mjs_val_t" +Function,+,mjs_array_set,mjs_err_t,"mjs*, mjs_val_t, unsigned long, mjs_val_t" Function,+,mjs_call,mjs_err_t,"mjs*, mjs_val_t*, mjs_val_t, mjs_val_t, int, ..." Function,+,mjs_create,mjs*,void* Function,+,mjs_del,int,"mjs*, mjs_val_t, const char*, size_t" @@ -2117,6 +2123,7 @@ Function,+,mjs_get_string,const char*,"mjs*, mjs_val_t*, size_t*" Function,+,mjs_get_this,mjs_val_t,mjs* Function,+,mjs_get_v,mjs_val_t,"mjs*, mjs_val_t, mjs_val_t" Function,+,mjs_get_v_proto,mjs_val_t,"mjs*, mjs_val_t, mjs_val_t" +Function,+,mjs_is_array,int,mjs_val_t Function,+,mjs_is_boolean,int,mjs_val_t Function,+,mjs_is_foreign,int,mjs_val_t Function,+,mjs_is_function,int,mjs_val_t @@ -2126,6 +2133,7 @@ Function,+,mjs_is_object,int,mjs_val_t Function,+,mjs_is_string,int,mjs_val_t Function,+,mjs_is_truthy,int,"mjs*, mjs_val_t" Function,+,mjs_is_undefined,int,mjs_val_t +Function,+,mjs_mk_array,mjs_val_t,mjs* Function,+,mjs_mk_boolean,mjs_val_t,"mjs*, int" Function,+,mjs_mk_foreign,mjs_val_t,"mjs*, void*" Function,+,mjs_mk_foreign_func,mjs_val_t,"mjs*, mjs_func_ptr_t" diff --git a/lib/mjs/SConscript b/lib/mjs/SConscript index 329a6f40401..2d95af4b6f2 100644 --- a/lib/mjs/SConscript +++ b/lib/mjs/SConscript @@ -9,6 +9,7 @@ env.Append( File("mjs_exec_public.h"), File("mjs_object_public.h"), File("mjs_string_public.h"), + File("mjs_array_public.h"), File("mjs_primitive_public.h"), File("mjs_util_public.h"), ], From 49920a78d2160bbea3059cb2021524b86fd29bcc Mon Sep 17 00:00:00 2001 From: nminaylov Date: Mon, 30 Oct 2023 18:04:03 +0300 Subject: [PATCH 17/31] js_uart: support for byte array arguments --- .../system/{mjs => js_app}/application.fam | 0 .../system/{mjs => js_app}/assets/about.js | 0 .../system/{mjs => js_app}/assets/api.js | 0 .../system/{mjs => js_app}/assets/badusb.js | 0 .../{mjs => js_app}/assets/badusb_demo.js | 0 .../system/{mjs => js_app}/assets/console.js | 0 .../system/{mjs => js_app}/assets/delay.js | 0 .../system/{mjs => js_app}/assets/dialog.js | 0 .../system/{mjs => js_app}/assets/ffi.js | 0 .../system/{mjs => js_app}/assets/notify.js | 0 .../{mjs => js_app}/assets/print_test.js | 0 .../system/{mjs => js_app}/assets/req.js | 0 .../{mjs => js_app}/assets/require_api.js | 0 .../system/{mjs => js_app}/assets/uart.js | 8 +- applications/system/{mjs => js_app}/js_app.c | 0 .../system/{mjs => js_app}/js_app_i.h | 0 .../system/{mjs => js_app}/js_modules.c | 0 .../system/{mjs => js_app}/js_modules.h | 0 .../system/{mjs => js_app}/js_thread.c | 0 .../system/{mjs => js_app}/js_thread.h | 0 .../system/{mjs => js_app}/js_thread_i.h | 0 .../{mjs => js_app}/modules/js_badusb.c | 0 .../{mjs => js_app}/modules/js_badusb.md | 0 .../{mjs => js_app}/modules/js_dialog.c | 0 .../{mjs => js_app}/modules/js_flipper.c | 0 .../{mjs => js_app}/modules/js_flipper.h | 0 .../{mjs => js_app}/modules/js_notification.c | 0 .../system/{mjs => js_app}/modules/js_uart.c | 165 +++++++++++++----- .../plugin_api/app_api_interface.h | 0 .../plugin_api/app_api_table.cpp | 0 .../plugin_api/app_api_table_i.h | 0 .../plugin_api/js_plugin_api.h | 0 .../{mjs => js_app}/views/console_font.h | 0 .../{mjs => js_app}/views/console_view.c | 0 .../{mjs => js_app}/views/console_view.h | 0 35 files changed, 122 insertions(+), 51 deletions(-) rename applications/system/{mjs => js_app}/application.fam (100%) rename applications/system/{mjs => js_app}/assets/about.js (100%) rename applications/system/{mjs => js_app}/assets/api.js (100%) rename applications/system/{mjs => js_app}/assets/badusb.js (100%) rename applications/system/{mjs => js_app}/assets/badusb_demo.js (100%) rename applications/system/{mjs => js_app}/assets/console.js (100%) rename applications/system/{mjs => js_app}/assets/delay.js (100%) rename applications/system/{mjs => js_app}/assets/dialog.js (100%) rename applications/system/{mjs => js_app}/assets/ffi.js (100%) rename applications/system/{mjs => js_app}/assets/notify.js (100%) rename applications/system/{mjs => js_app}/assets/print_test.js (100%) rename applications/system/{mjs => js_app}/assets/req.js (100%) rename applications/system/{mjs => js_app}/assets/require_api.js (100%) rename applications/system/{mjs => js_app}/assets/uart.js (75%) rename applications/system/{mjs => js_app}/js_app.c (100%) rename applications/system/{mjs => js_app}/js_app_i.h (100%) rename applications/system/{mjs => js_app}/js_modules.c (100%) rename applications/system/{mjs => js_app}/js_modules.h (100%) rename applications/system/{mjs => js_app}/js_thread.c (100%) rename applications/system/{mjs => js_app}/js_thread.h (100%) rename applications/system/{mjs => js_app}/js_thread_i.h (100%) rename applications/system/{mjs => js_app}/modules/js_badusb.c (100%) rename applications/system/{mjs => js_app}/modules/js_badusb.md (100%) rename applications/system/{mjs => js_app}/modules/js_dialog.c (100%) rename applications/system/{mjs => js_app}/modules/js_flipper.c (100%) rename applications/system/{mjs => js_app}/modules/js_flipper.h (100%) rename applications/system/{mjs => js_app}/modules/js_notification.c (100%) rename applications/system/{mjs => js_app}/modules/js_uart.c (70%) rename applications/system/{mjs => js_app}/plugin_api/app_api_interface.h (100%) rename applications/system/{mjs => js_app}/plugin_api/app_api_table.cpp (100%) rename applications/system/{mjs => js_app}/plugin_api/app_api_table_i.h (100%) rename applications/system/{mjs => js_app}/plugin_api/js_plugin_api.h (100%) rename applications/system/{mjs => js_app}/views/console_font.h (100%) rename applications/system/{mjs => js_app}/views/console_view.c (100%) rename applications/system/{mjs => js_app}/views/console_view.h (100%) diff --git a/applications/system/mjs/application.fam b/applications/system/js_app/application.fam similarity index 100% rename from applications/system/mjs/application.fam rename to applications/system/js_app/application.fam diff --git a/applications/system/mjs/assets/about.js b/applications/system/js_app/assets/about.js similarity index 100% rename from applications/system/mjs/assets/about.js rename to applications/system/js_app/assets/about.js diff --git a/applications/system/mjs/assets/api.js b/applications/system/js_app/assets/api.js similarity index 100% rename from applications/system/mjs/assets/api.js rename to applications/system/js_app/assets/api.js diff --git a/applications/system/mjs/assets/badusb.js b/applications/system/js_app/assets/badusb.js similarity index 100% rename from applications/system/mjs/assets/badusb.js rename to applications/system/js_app/assets/badusb.js diff --git a/applications/system/mjs/assets/badusb_demo.js b/applications/system/js_app/assets/badusb_demo.js similarity index 100% rename from applications/system/mjs/assets/badusb_demo.js rename to applications/system/js_app/assets/badusb_demo.js diff --git a/applications/system/mjs/assets/console.js b/applications/system/js_app/assets/console.js similarity index 100% rename from applications/system/mjs/assets/console.js rename to applications/system/js_app/assets/console.js diff --git a/applications/system/mjs/assets/delay.js b/applications/system/js_app/assets/delay.js similarity index 100% rename from applications/system/mjs/assets/delay.js rename to applications/system/js_app/assets/delay.js diff --git a/applications/system/mjs/assets/dialog.js b/applications/system/js_app/assets/dialog.js similarity index 100% rename from applications/system/mjs/assets/dialog.js rename to applications/system/js_app/assets/dialog.js diff --git a/applications/system/mjs/assets/ffi.js b/applications/system/js_app/assets/ffi.js similarity index 100% rename from applications/system/mjs/assets/ffi.js rename to applications/system/js_app/assets/ffi.js diff --git a/applications/system/mjs/assets/notify.js b/applications/system/js_app/assets/notify.js similarity index 100% rename from applications/system/mjs/assets/notify.js rename to applications/system/js_app/assets/notify.js diff --git a/applications/system/mjs/assets/print_test.js b/applications/system/js_app/assets/print_test.js similarity index 100% rename from applications/system/mjs/assets/print_test.js rename to applications/system/js_app/assets/print_test.js diff --git a/applications/system/mjs/assets/req.js b/applications/system/js_app/assets/req.js similarity index 100% rename from applications/system/mjs/assets/req.js rename to applications/system/js_app/assets/req.js diff --git a/applications/system/mjs/assets/require_api.js b/applications/system/js_app/assets/require_api.js similarity index 100% rename from applications/system/mjs/assets/require_api.js rename to applications/system/js_app/assets/require_api.js diff --git a/applications/system/mjs/assets/uart.js b/applications/system/js_app/assets/uart.js similarity index 75% rename from applications/system/mjs/assets/uart.js rename to applications/system/js_app/assets/uart.js index 1276f578e11..4c88fd322a8 100644 --- a/applications/system/mjs/assets/uart.js +++ b/applications/system/js_app/assets/uart.js @@ -1,8 +1,10 @@ let uart = require("uart"); uart.setup(115200); -uart.write("\n"); -let console_resp = uart.expect("# ", 1000); +// uart.write("\n"); +uart.write([0x0a]); +// let console_resp = uart.expect("# ", 1000); +let console_resp = uart.expect([0x23, 0x20], 1000); if (console_resp === undefined) { print("No CLI response"); } else { @@ -17,5 +19,3 @@ if (console_resp === undefined) { print("uci cmd not found"); } } - - diff --git a/applications/system/mjs/js_app.c b/applications/system/js_app/js_app.c similarity index 100% rename from applications/system/mjs/js_app.c rename to applications/system/js_app/js_app.c diff --git a/applications/system/mjs/js_app_i.h b/applications/system/js_app/js_app_i.h similarity index 100% rename from applications/system/mjs/js_app_i.h rename to applications/system/js_app/js_app_i.h diff --git a/applications/system/mjs/js_modules.c b/applications/system/js_app/js_modules.c similarity index 100% rename from applications/system/mjs/js_modules.c rename to applications/system/js_app/js_modules.c diff --git a/applications/system/mjs/js_modules.h b/applications/system/js_app/js_modules.h similarity index 100% rename from applications/system/mjs/js_modules.h rename to applications/system/js_app/js_modules.h diff --git a/applications/system/mjs/js_thread.c b/applications/system/js_app/js_thread.c similarity index 100% rename from applications/system/mjs/js_thread.c rename to applications/system/js_app/js_thread.c diff --git a/applications/system/mjs/js_thread.h b/applications/system/js_app/js_thread.h similarity index 100% rename from applications/system/mjs/js_thread.h rename to applications/system/js_app/js_thread.h diff --git a/applications/system/mjs/js_thread_i.h b/applications/system/js_app/js_thread_i.h similarity index 100% rename from applications/system/mjs/js_thread_i.h rename to applications/system/js_app/js_thread_i.h diff --git a/applications/system/mjs/modules/js_badusb.c b/applications/system/js_app/modules/js_badusb.c similarity index 100% rename from applications/system/mjs/modules/js_badusb.c rename to applications/system/js_app/modules/js_badusb.c diff --git a/applications/system/mjs/modules/js_badusb.md b/applications/system/js_app/modules/js_badusb.md similarity index 100% rename from applications/system/mjs/modules/js_badusb.md rename to applications/system/js_app/modules/js_badusb.md diff --git a/applications/system/mjs/modules/js_dialog.c b/applications/system/js_app/modules/js_dialog.c similarity index 100% rename from applications/system/mjs/modules/js_dialog.c rename to applications/system/js_app/modules/js_dialog.c diff --git a/applications/system/mjs/modules/js_flipper.c b/applications/system/js_app/modules/js_flipper.c similarity index 100% rename from applications/system/mjs/modules/js_flipper.c rename to applications/system/js_app/modules/js_flipper.c diff --git a/applications/system/mjs/modules/js_flipper.h b/applications/system/js_app/modules/js_flipper.h similarity index 100% rename from applications/system/mjs/modules/js_flipper.h rename to applications/system/js_app/modules/js_flipper.h diff --git a/applications/system/mjs/modules/js_notification.c b/applications/system/js_app/modules/js_notification.c similarity index 100% rename from applications/system/mjs/modules/js_notification.c rename to applications/system/js_app/modules/js_notification.c diff --git a/applications/system/mjs/modules/js_uart.c b/applications/system/js_app/modules/js_uart.c similarity index 70% rename from applications/system/mjs/modules/js_uart.c rename to applications/system/js_app/modules/js_uart.c index f27726446de..47b4aeeacbe 100644 --- a/applications/system/mjs/modules/js_uart.c +++ b/applications/system/js_app/modules/js_uart.c @@ -14,10 +14,10 @@ typedef struct { typedef struct { size_t len; - char* text; -} PatternListItem; + char* data; +} PatternArrayItem; -ARRAY_DEF(PatternList, PatternListItem, M_POD_OPLIST); +ARRAY_DEF(PatternArray, PatternArrayItem, M_POD_OPLIST); static void js_uart_on_irq_cb(UartIrqEvent ev, uint8_t data, void* context) { JsUartInst* uart = context; @@ -86,8 +86,25 @@ static void js_uart_write(struct mjs* mjs) { break; } furi_hal_uart_tx(FuriHalUartIdLPUART1, (uint8_t*)&byte_val, 1); + } else if(mjs_is_array(arg)) { + size_t array_len = mjs_array_length(mjs, arg); + for(size_t i = 0; i < array_len; i++) { + mjs_val_t array_arg = mjs_array_get(mjs, arg, i); + if(!mjs_is_number(array_arg)) { + args_correct = false; + break; + } + uint32_t byte_val = mjs_get_int32(mjs, array_arg); + if(byte_val > 0xFF) { + args_correct = false; + break; + } + furi_hal_uart_tx(FuriHalUartIdLPUART1, (uint8_t*)&byte_val, 1); + } + if(!args_correct) { + break; + } } else { - // TODO: mjs_is_array(v) args_correct = false; break; } @@ -229,7 +246,49 @@ static void js_uart_readln(struct mjs* mjs) { furi_string_free(rx_buf); } -static bool js_uart_expect_parse_args(struct mjs* mjs, PatternList_t patterns, uint32_t* timeout) { +static bool js_uart_expect_parse_string(struct mjs* mjs, mjs_val_t arg, PatternArray_t patterns) { + size_t str_len = 0; + const char* arg_str = mjs_get_string(mjs, &arg, &str_len); + if((str_len == 0) || (arg_str == NULL)) { + return false; + } + PatternArrayItem* item = PatternArray_push_new(patterns); + item->data = malloc(str_len + 1); + memcpy(item->data, arg_str, str_len); + item->len = str_len; + return true; +} + +static bool js_uart_expect_parse_array(struct mjs* mjs, mjs_val_t arg, PatternArray_t patterns) { + size_t array_len = mjs_array_length(mjs, arg); + if(array_len == 0) { + return false; + } + char* array_data = malloc(array_len + 1); + + for(size_t i = 0; i < array_len; i++) { + mjs_val_t array_arg = mjs_array_get(mjs, arg, i); + if(!mjs_is_number(array_arg)) { + free(array_data); + return false; + } + + uint32_t byte_val = mjs_get_int32(mjs, array_arg); + if(byte_val > 0xFF) { + free(array_data); + return false; + } + array_data[i] = byte_val; + } + + PatternArrayItem* item = PatternArray_push_new(patterns); + item->data = array_data; + item->len = array_len; + return true; +} + +static bool + js_uart_expect_parse_args(struct mjs* mjs, PatternArray_t patterns, uint32_t* timeout) { size_t num_args = mjs_nargs(mjs); if(num_args == 2) { mjs_val_t timeout_arg = mjs_arg(mjs, 1); @@ -241,45 +300,52 @@ static bool js_uart_expect_parse_args(struct mjs* mjs, PatternList_t patterns, u return false; } mjs_val_t patterns_arg = mjs_arg(mjs, 0); - if(mjs_is_string(patterns_arg)) { // Single string argument - size_t str_len = 0; - const char* arg_str = mjs_get_string(mjs, &patterns_arg, &str_len); - if((str_len == 0) || (arg_str == NULL)) { + if(mjs_is_string(patterns_arg)) { // Single string pattern + if(!js_uart_expect_parse_string(mjs, patterns_arg, patterns)) { + return false; + } + } else if(mjs_is_array(patterns_arg)) { + size_t array_len = mjs_array_length(mjs, patterns_arg); + if(array_len == 0) { return false; } - PatternListItem* item = PatternList_push_new(patterns); - item->text = malloc(str_len + 1); - memcpy(item->text, arg_str, str_len); - item->len = str_len; - } else if(mjs_is_array(patterns_arg)) { // Array of strings - size_t patterns_cnt = mjs_array_length(mjs, patterns_arg); - for(size_t i = 0; i < patterns_cnt; i++) { - mjs_val_t arg = mjs_array_get(mjs, patterns_arg, i); - if(!mjs_is_string(arg)) { + mjs_val_t array_arg = mjs_array_get(mjs, patterns_arg, 0); + + if(mjs_is_number(array_arg)) { // Binary array pattern + if(!js_uart_expect_parse_array(mjs, patterns_arg, patterns)) { return false; } - size_t str_len = 0; - const char* arg_str = mjs_get_string(mjs, &arg, &str_len); - if((str_len == 0) || (arg_str == NULL)) { - return false; + } else if((mjs_is_string(array_arg)) || (mjs_is_array(array_arg))) { // Multiple patterns + for(size_t i = 0; i < array_len; i++) { + mjs_val_t arg = mjs_array_get(mjs, patterns_arg, i); + + if(mjs_is_string(arg)) { + if(!js_uart_expect_parse_string(mjs, arg, patterns)) { + return false; + } + } else if(mjs_is_array(arg)) { + if(!js_uart_expect_parse_array(mjs, arg, patterns)) { + return false; + } + } } - PatternListItem* item = PatternList_push_new(patterns); - item->text = malloc(str_len + 1); - memcpy(item->text, arg_str, str_len); - item->len = str_len; + } else { + return false; } - // TODO: support for binary patterns + } else { + return false; } return true; } -static int32_t check_pattern_start(PatternList_t patterns, char value, int32_t pattern_last) { - size_t list_len = PatternList_size(patterns); - if((pattern_last + 1) >= (int32_t)list_len) { +static int32_t + js_uart_expect_check_pattern_start(PatternArray_t patterns, char value, int32_t pattern_last) { + size_t array_len = PatternArray_size(patterns); + if((pattern_last + 1) >= (int32_t)array_len) { return (-1); } - for(size_t i = pattern_last + 1; i < list_len; i++) { - if(PatternList_get(patterns, i)->text[0] == value) { + for(size_t i = pattern_last + 1; i < array_len; i++) { + if(PatternArray_get(patterns, i)->data[0] == value) { return i; } } @@ -297,20 +363,24 @@ static void js_uart_expect(struct mjs* mjs) { } uint32_t timeout = FuriWaitForever; - PatternList_t patterns; - PatternList_init(patterns); + PatternArray_t patterns; + PatternArray_it_t it; + PatternArray_init(patterns); if(!js_uart_expect_parse_args(mjs, patterns, &timeout)) { mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); mjs_return(mjs, MJS_UNDEFINED); - PatternList_clear(patterns); + for(PatternArray_it(it, patterns); !PatternArray_end_p(it); PatternArray_next(it)) { + const PatternArrayItem* item = PatternArray_cref(it); + free(item->data); + } + PatternArray_clear(patterns); return; } size_t pattern_len_max = 0; - PatternList_it_t it; - for(PatternList_it(it, patterns); !PatternList_end_p(it); PatternList_next(it)) { - const PatternListItem* item = PatternList_cref(it); + for(PatternArray_it(it, patterns); !PatternArray_end_p(it); PatternArray_next(it)) { + const PatternArrayItem* item = PatternArray_cref(it); if(item->len > pattern_len_max) { pattern_len_max = item->len; } @@ -330,7 +400,7 @@ static void js_uart_expect(struct mjs* mjs) { is_timeout = true; break; } - pattern_candidate = check_pattern_start(patterns, compare_buf[0], -1); + pattern_candidate = js_uart_expect_check_pattern_start(patterns, compare_buf[0], -1); if(pattern_candidate == -1) { continue; } @@ -339,7 +409,7 @@ static void js_uart_expect(struct mjs* mjs) { assert(pattern_candidate >= 0); // Read next and try to find pattern match - PatternListItem* pattern_cur = PatternList_get(patterns, pattern_candidate); + PatternArrayItem* pattern_cur = PatternArray_get(patterns, pattern_candidate); pattern_found = pattern_candidate; for(size_t i = 0; i < pattern_cur->len; i++) { if(i >= buf_len) { @@ -350,7 +420,7 @@ static void js_uart_expect(struct mjs* mjs) { } buf_len++; } - if(compare_buf[i] != pattern_cur->text[i]) { + if(compare_buf[i] != pattern_cur->data[i]) { pattern_found = -1; break; } @@ -360,14 +430,15 @@ static void js_uart_expect(struct mjs* mjs) { } // Search other patterns with the same start char - pattern_candidate = check_pattern_start(patterns, compare_buf[0], pattern_candidate); + pattern_candidate = + js_uart_expect_check_pattern_start(patterns, compare_buf[0], pattern_candidate); if(pattern_candidate >= 0) { continue; } // Look for another pattern start for(size_t i = 1; i < buf_len; i++) { - pattern_candidate = check_pattern_start(patterns, compare_buf[i], -1); + pattern_candidate = js_uart_expect_check_pattern_start(patterns, compare_buf[i], -1); if(pattern_candidate >= 0) { memmove(&compare_buf[0], &compare_buf[i], buf_len - i); buf_len -= i; @@ -385,11 +456,11 @@ static void js_uart_expect(struct mjs* mjs) { FURI_LOG_W(TAG, "Expect: timeout"); } - for(PatternList_it(it, patterns); !PatternList_end_p(it); PatternList_next(it)) { - const PatternListItem* item = PatternList_cref(it); - free(item->text); + for(PatternArray_it(it, patterns); !PatternArray_end_p(it); PatternArray_next(it)) { + const PatternArrayItem* item = PatternArray_cref(it); + free(item->data); } - PatternList_clear(patterns); + PatternArray_clear(patterns); free(compare_buf); if(pattern_found >= 0) { diff --git a/applications/system/mjs/plugin_api/app_api_interface.h b/applications/system/js_app/plugin_api/app_api_interface.h similarity index 100% rename from applications/system/mjs/plugin_api/app_api_interface.h rename to applications/system/js_app/plugin_api/app_api_interface.h diff --git a/applications/system/mjs/plugin_api/app_api_table.cpp b/applications/system/js_app/plugin_api/app_api_table.cpp similarity index 100% rename from applications/system/mjs/plugin_api/app_api_table.cpp rename to applications/system/js_app/plugin_api/app_api_table.cpp diff --git a/applications/system/mjs/plugin_api/app_api_table_i.h b/applications/system/js_app/plugin_api/app_api_table_i.h similarity index 100% rename from applications/system/mjs/plugin_api/app_api_table_i.h rename to applications/system/js_app/plugin_api/app_api_table_i.h diff --git a/applications/system/mjs/plugin_api/js_plugin_api.h b/applications/system/js_app/plugin_api/js_plugin_api.h similarity index 100% rename from applications/system/mjs/plugin_api/js_plugin_api.h rename to applications/system/js_app/plugin_api/js_plugin_api.h diff --git a/applications/system/mjs/views/console_font.h b/applications/system/js_app/views/console_font.h similarity index 100% rename from applications/system/mjs/views/console_font.h rename to applications/system/js_app/views/console_font.h diff --git a/applications/system/mjs/views/console_view.c b/applications/system/js_app/views/console_view.c similarity index 100% rename from applications/system/mjs/views/console_view.c rename to applications/system/js_app/views/console_view.c diff --git a/applications/system/mjs/views/console_view.h b/applications/system/js_app/views/console_view.h similarity index 100% rename from applications/system/mjs/views/console_view.h rename to applications/system/js_app/views/console_view.h From 7032c90bc75ec4dce74615c223b4c3326ca786b2 Mon Sep 17 00:00:00 2001 From: nminaylov Date: Fri, 3 Nov 2023 19:31:02 +0300 Subject: [PATCH 18/31] Script icon and various fixes --- applications/system/js_app/application.fam | 3 +- applications/system/js_app/icon.png | Bin 0 -> 3635 bytes applications/system/js_app/js_app.c | 3 +- applications/system/js_app/js_modules.c | 7 +- applications/system/js_app/js_thread.c | 3 - applications/system/js_app/js_thread.h | 1 - .../system/js_app/modules/js_badusb.md | 82 ------------------ assets/icons/Archive/js_script_10px.png | Bin 0 -> 3635 bytes 8 files changed, 10 insertions(+), 89 deletions(-) create mode 100644 applications/system/js_app/icon.png delete mode 100644 applications/system/js_app/modules/js_badusb.md create mode 100644 assets/icons/Archive/js_script_10px.png diff --git a/applications/system/js_app/application.fam b/applications/system/js_app/application.fam index 002284df761..5dd372e6619 100644 --- a/applications/system/js_app/application.fam +++ b/applications/system/js_app/application.fam @@ -1,6 +1,6 @@ App( appid="js_app", - name="JS", + name="JS Runner", fap_category="Tools", apptype=FlipperAppType.EXTERNAL, entry_point="js_app", @@ -8,6 +8,7 @@ App( fap_libs=["gcc", "assets"], fap_file_assets="assets", order=0, + fap_icon="icon.png", ) App( diff --git a/applications/system/js_app/icon.png b/applications/system/js_app/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..77ac76337ea8a76928e45b79335fae7a7602adac GIT binary patch literal 3635 zcmaJ@c|4Te+rMpvB3VN+jd&`{EcVIRmr-MBY@-rmW-!dw%wR^5l2Wo|%bHM9Lz^Pm zDnhm>63UV#p&=nzddKs7dVcR8Z}0ubIoCP&_xruBbDisaKG#KOM>`2|C2;@%BEIG^o4aN2?EQ4| z833%t6>XC8%$K?X;Lb@&n>R$qh`Y8WILcjki4a z7ij>1Ss@NPf_2YqRp2fS5CrJmI(wuWhkr#?I%{|Upd?@jEIqMBAQ1yJ>9{t@0!=%B zdoB+N;y?-jkU6I;!N6?^VEVhgsR+^D*2`FtS<%2#LogZZr1*A~P(tS^lXQ|1cCWO%KTLD06 zx%1h&ky+xz_~`Js-vpWWy&-=+XqE;x-90zBUUgYe7?5)8;PrWrjr9o88wKtK*9H|I z1^V29fWc)i-F~sWBjT^amSZ_atLD3MuRAxHsO%KqvSmiy_j)z-Q>^JP-C^2LY{Vk} z?|OgOEN~*zJfozpAZqOdjv7*miJxc@wuDiaCe+p&qff8J@9P}VyMIhe?$R9gVudr< zp^@_hlVrWSIdw#O@-%!-HEm5SIaC7Ro_DHqe7yNl3%M@htAJpvB(^IAFprqmzO1w* z36S#FgL9SuAh~uq!CX@SXiG2r1OQc^cO1KwqJVxW3INu5XZGK-*u3?bEVoWF`6ta02i^Ph?RO(6KC)k{@&u#__o4hy{%3^spqOI7L8%RFW$OK+Q3WLkbur&8H2 zWaQ)gqRvB~gIPfMrFeW~Crr}s@_XE|%NJ~XE@G858s+h0Sbg^deY^_RFhowdE*HlW z-6I$qDKjm4{G8Nt^4Y{hJ1iP&bxVC(txUR7)I%)l9PIgD;N)9QW$Izl%k^wu#Qj99 zEmz~~?p?cc)sO!rDdU}t+9TO~Wz+; zK8WOk(v!*)wA;Lqf;5|bIfzJauyI2#8R+Wr9tK4%IM1e;Epe*Z}3x`NwI}j3zCx`I_(xH z-wPHLzVLc@ovMaVJyC;1MKa6?Kiis!{~UCcRM9zmZur*rmVnBn`hbE?`+~nJG`?Q& z|D?}WF!kfocKUMlJ`x9lOmMy=czO)+Vh1ZZEfaI^MWD zJ17@d^S-KoH7@#wO?Jb_XYQNT>GA5Q(9A$9uKm! zvPag|EJRkz<69+JCG{a$ltI}M*@S&v`xacNeX!}(Y4PdI=}j}ni?~JgMap7_O6yK( zm7W8v1E(0Q;MNc_y=tm(Snow~eesJ)4}WF<>&%>aeTAEc5?iFxL(&yoC`FfwsMo?; zQpc3OR(3slvrv%rG;5&bJ>~6(R%Ao?ofQY2Eahge7f7DP_t_=v$cffZIYn7T&*u{- zAjdFA;WQ|%x zPeew<=GEMl13z2V5`NSUbH2&XIg0oQJQElbs6L}~=AlrKkg9No@Quy-B5@*5H$k?V z%2{p|lkik!B-82?z9}!`bqD;lGCSp7iRQ>1QT_>S)j;p+*&ctsLv&Cof_8HmJ=ywL zG-k+-98;l6Qp`|T4wv1xAAVcs%i%H?)(O^{@g$^ zhK!%is)($(U2%qe;fKv!x-?5>kJ!w`(fZzcmr0yzWU;xSQOH*Q#Ip7qd6Elz1}p_YC>@&qr3OC`AR0@J&Xx}V;!km}8)b8nUY zxMy22KH}l%-F<3-L+R`Lc(DA{%oJ*>aEi)Z@~pjkusYD9`}O%*i4x_xxG`mK%E6DU z>tTTl@oi;ouiAw4ar!eyTOV`#zg;&dSPaWK?bYJGR&uAj=DkK=Pwj`$lk<{MDsl%t z@=~U&9%-^Yf~sx87pPrd&wfZy@>Q;^9gp}>VHfJukehJw#>vf#HoU1o7L7b`x3>0y z7zJYxw+n@!k7Y&D`m$y+Lm$zP?3a9QG4|}Q!JDT}escbNz5T_&i?+dx)8z}IQz(vR z7os+pt6f0JLtd}ueZE|fqjFruuBQA~1P}HJAKa*);E|h`vlltHJbE_yQxTykH^7)tCFH28DoZF;e)I_&-5m9h^Z{GzJl5 ztOL`=L!mH`A>Yz7fEyVb8yXsFf#6WME(B@-f$M9-brJf82$&w|pAXFU$RPM3FxIyJ z_~WmT-~bkjj(|WyLql~!b#-VAe+bOj*cbwZL*Q_2K0=$xp|WsnZ7Ngkw}Lg1iD!`L zEE0_h+EB##(t=q?FhAP=9D_ptSC-2BCsq8ULD)Dt1f~PsIM#2VgTwz1rBMDwGg%no zf8+f>g_*7#IuU{)GHJmKJpc0i)HYnv5mpQ$jzwd*(rD!0nRE`Iv1rTy8XW}J*MWof zZX^x{g+X;FI&fWmLl9J#0E6no_JKfq9dLLObpyQbZ#4%8gguqX!cp-=dut?^udhQQ z5fD~JmKMgAP+dc~fj$goV`Bk_8(7*{>f68#taOcF(BE8Z8a|jpq_Tc<3IFBl{gJy- zEEGE5vo(=H3Lz3~88iy$?}Q*oe~tzAN4Ys37v zu=$IBp9Yc2e-8|P;iJQK`8_wV$rOP{m<1M_=T5C8xG literal 0 HcmV?d00001 diff --git a/applications/system/js_app/js_app.c b/applications/system/js_app/js_app.c index 4f8e1b3ebae..67573793f05 100644 --- a/applications/system/js_app/js_app.c +++ b/applications/system/js_app/js_app.c @@ -3,6 +3,7 @@ #include #include "js_app_i.h" #include +#include #define TAG "MJS app" @@ -102,7 +103,7 @@ int32_t js_app(void* arg) { furi_string_set(script_path, (const char*)arg); } else { DialogsFileBrowserOptions browser_options; - dialog_file_browser_set_basic_options(&browser_options, ".js", NULL); + dialog_file_browser_set_basic_options(&browser_options, ".js", &I_js_script_10px); DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); if(!dialog_file_browser_show(dialogs, script_path, script_path, &browser_options)) break; diff --git a/applications/system/js_app/js_modules.c b/applications/system/js_app/js_modules.c index 43bf74200fd..64373339dbb 100644 --- a/applications/system/js_app/js_modules.c +++ b/applications/system/js_app/js_modules.c @@ -54,7 +54,8 @@ mjs_val_t js_module_require(JsModules* modules, const char* name, size_t name_le JsModuleData* module_inst = JsModuleDict_get(modules->module_dict, module_name); if(module_inst) { furi_string_free(module_name); - // TODO: "already exists" error + mjs_prepend_errorf( + modules->mjs, MJS_BAD_ARGS_ERROR, "\"%s\" module is already installed", name); return MJS_UNDEFINED; } @@ -115,6 +116,10 @@ mjs_val_t js_module_require(JsModules* modules, const char* name, size_t name_le } } + if(module_object == MJS_UNDEFINED) { + mjs_prepend_errorf(modules->mjs, MJS_BAD_ARGS_ERROR, "\"%s\" module load fail", name); + } + furi_string_free(module_name); return module_object; diff --git a/applications/system/js_app/js_thread.c b/applications/system/js_app/js_thread.c index 5108ba0da4e..fe431b8670c 100644 --- a/applications/system/js_app/js_thread.c +++ b/applications/system/js_app/js_thread.c @@ -189,9 +189,6 @@ static void mjs_require(struct mjs* mjs) { JsThread* worker = mjs_get_context(mjs); furi_assert(worker); req_object = js_module_require(worker->modules, name, len); - if(req_object == MJS_UNDEFINED) { - mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "\"%s\" module not found", name); - } } mjs_return(mjs, req_object); } diff --git a/applications/system/js_app/js_thread.h b/applications/system/js_app/js_thread.h index 87303b5a9d3..969715ec1a4 100644 --- a/applications/system/js_app/js_thread.h +++ b/applications/system/js_app/js_thread.h @@ -7,7 +7,6 @@ typedef enum { JsThreadEventError, JsThreadEventPrint, JsThreadEventErrorTrace, - // TODO: input wait, .... } JsThreadEvent; typedef void (*JsThreadCallback)(JsThreadEvent event, const char* msg, void* context); diff --git a/applications/system/js_app/modules/js_badusb.md b/applications/system/js_app/modules/js_badusb.md deleted file mode 100644 index dc3dd8b4874..00000000000 --- a/applications/system/js_app/modules/js_badusb.md +++ /dev/null @@ -1,82 +0,0 @@ -# BadUSB module -```js -let badusb = require("badusb"); -``` -# Methods -## setup -Start USB HID with optional parameters. Should be called before all other methods. -### Params -Configuration object (optional): -- vid, pid (number): VID and PID values, both are mandatory -- mfr_name (string): Manufacturer name (32 ASCII characters max), optional -- prod_name (string): Product name (32 ASCII characters max), optional - -### Example: -```js -// Start USB HID with default parameters -badusb.setup(); -// Start USB HID with custom vid:pid = AAAA:BBBB, manufacturer and product strings not defined -badusb.setup({ vid: 0xAAAA, pid: 0xBBBB }); -// Start USB HID with custom vid:pid = AAAA:BBBB, manufacturer string = "Flipper Devices", product string = "Flipper Zero" -badusb.setup({ vid: 0xAAAA, pid: 0xBBBB, mfr_name: "Flipper Devices", prod_name: "Flipper Zero" }); -``` - -## press -Press and release a key -### Params -Key name, modifier names -//TODO: key codes list - -### Example: -```js -badusb.press("a"); // Press "a" key -badusb.press("A"); // SHIFT + "a" -badusb.press("CTRL", "a"); // CTRL + "a" -badusb.press("CTRL", "SHIFT", "ESC"); // CTRL + SHIFT + ESC combo -badusb.press(98); // Press key with HID code (dec) 98 (Numpad 0 / Insert) -badusb.press(0x47); // Press key with HID code (hex) 0x47 (Scroll lock) -``` - -## hold -### Params -Same as `press` - -### Example: -```js -badusb.hold("a"); // Press and hold "a" key -badusb.hold("CTRL", "v"); // Press and hold CTRL + "v" combo -``` - -## release -### Params -Same as `press` -No params - release all keys - -### Example: -```js -badusb.release(); // Release all keys -badusb.release("a"); // Release "a" key -``` - -## print -Print a string -### Params -- a string to print -- (optional) delay between key presses - -### Example: -```js -badusb.print("Hello, world!"); // print "Hello, world!" -badusb.print("Hello, world!", 100); // Add 100ms delay between key presses -``` - -## println -Same as `print` but ended with "ENTER" press -### Params -- a string to print -- (optional) delay between key presses - -### Example: -```js -badusb.println("Hello, world!"); // print "Hello, world!" and press "ENTER" -``` \ No newline at end of file diff --git a/assets/icons/Archive/js_script_10px.png b/assets/icons/Archive/js_script_10px.png new file mode 100644 index 0000000000000000000000000000000000000000..77ac76337ea8a76928e45b79335fae7a7602adac GIT binary patch literal 3635 zcmaJ@c|4Te+rMpvB3VN+jd&`{EcVIRmr-MBY@-rmW-!dw%wR^5l2Wo|%bHM9Lz^Pm zDnhm>63UV#p&=nzddKs7dVcR8Z}0ubIoCP&_xruBbDisaKG#KOM>`2|C2;@%BEIG^o4aN2?EQ4| z833%t6>XC8%$K?X;Lb@&n>R$qh`Y8WILcjki4a z7ij>1Ss@NPf_2YqRp2fS5CrJmI(wuWhkr#?I%{|Upd?@jEIqMBAQ1yJ>9{t@0!=%B zdoB+N;y?-jkU6I;!N6?^VEVhgsR+^D*2`FtS<%2#LogZZr1*A~P(tS^lXQ|1cCWO%KTLD06 zx%1h&ky+xz_~`Js-vpWWy&-=+XqE;x-90zBUUgYe7?5)8;PrWrjr9o88wKtK*9H|I z1^V29fWc)i-F~sWBjT^amSZ_atLD3MuRAxHsO%KqvSmiy_j)z-Q>^JP-C^2LY{Vk} z?|OgOEN~*zJfozpAZqOdjv7*miJxc@wuDiaCe+p&qff8J@9P}VyMIhe?$R9gVudr< zp^@_hlVrWSIdw#O@-%!-HEm5SIaC7Ro_DHqe7yNl3%M@htAJpvB(^IAFprqmzO1w* z36S#FgL9SuAh~uq!CX@SXiG2r1OQc^cO1KwqJVxW3INu5XZGK-*u3?bEVoWF`6ta02i^Ph?RO(6KC)k{@&u#__o4hy{%3^spqOI7L8%RFW$OK+Q3WLkbur&8H2 zWaQ)gqRvB~gIPfMrFeW~Crr}s@_XE|%NJ~XE@G858s+h0Sbg^deY^_RFhowdE*HlW z-6I$qDKjm4{G8Nt^4Y{hJ1iP&bxVC(txUR7)I%)l9PIgD;N)9QW$Izl%k^wu#Qj99 zEmz~~?p?cc)sO!rDdU}t+9TO~Wz+; zK8WOk(v!*)wA;Lqf;5|bIfzJauyI2#8R+Wr9tK4%IM1e;Epe*Z}3x`NwI}j3zCx`I_(xH z-wPHLzVLc@ovMaVJyC;1MKa6?Kiis!{~UCcRM9zmZur*rmVnBn`hbE?`+~nJG`?Q& z|D?}WF!kfocKUMlJ`x9lOmMy=czO)+Vh1ZZEfaI^MWD zJ17@d^S-KoH7@#wO?Jb_XYQNT>GA5Q(9A$9uKm! zvPag|EJRkz<69+JCG{a$ltI}M*@S&v`xacNeX!}(Y4PdI=}j}ni?~JgMap7_O6yK( zm7W8v1E(0Q;MNc_y=tm(Snow~eesJ)4}WF<>&%>aeTAEc5?iFxL(&yoC`FfwsMo?; zQpc3OR(3slvrv%rG;5&bJ>~6(R%Ao?ofQY2Eahge7f7DP_t_=v$cffZIYn7T&*u{- zAjdFA;WQ|%x zPeew<=GEMl13z2V5`NSUbH2&XIg0oQJQElbs6L}~=AlrKkg9No@Quy-B5@*5H$k?V z%2{p|lkik!B-82?z9}!`bqD;lGCSp7iRQ>1QT_>S)j;p+*&ctsLv&Cof_8HmJ=ywL zG-k+-98;l6Qp`|T4wv1xAAVcs%i%H?)(O^{@g$^ zhK!%is)($(U2%qe;fKv!x-?5>kJ!w`(fZzcmr0yzWU;xSQOH*Q#Ip7qd6Elz1}p_YC>@&qr3OC`AR0@J&Xx}V;!km}8)b8nUY zxMy22KH}l%-F<3-L+R`Lc(DA{%oJ*>aEi)Z@~pjkusYD9`}O%*i4x_xxG`mK%E6DU z>tTTl@oi;ouiAw4ar!eyTOV`#zg;&dSPaWK?bYJGR&uAj=DkK=Pwj`$lk<{MDsl%t z@=~U&9%-^Yf~sx87pPrd&wfZy@>Q;^9gp}>VHfJukehJw#>vf#HoU1o7L7b`x3>0y z7zJYxw+n@!k7Y&D`m$y+Lm$zP?3a9QG4|}Q!JDT}escbNz5T_&i?+dx)8z}IQz(vR z7os+pt6f0JLtd}ueZE|fqjFruuBQA~1P}HJAKa*);E|h`vlltHJbE_yQxTykH^7)tCFH28DoZF;e)I_&-5m9h^Z{GzJl5 ztOL`=L!mH`A>Yz7fEyVb8yXsFf#6WME(B@-f$M9-brJf82$&w|pAXFU$RPM3FxIyJ z_~WmT-~bkjj(|WyLql~!b#-VAe+bOj*cbwZL*Q_2K0=$xp|WsnZ7Ngkw}Lg1iD!`L zEE0_h+EB##(t=q?FhAP=9D_ptSC-2BCsq8ULD)Dt1f~PsIM#2VgTwz1rBMDwGg%no zf8+f>g_*7#IuU{)GHJmKJpc0i)HYnv5mpQ$jzwd*(rD!0nRE`Iv1rTy8XW}J*MWof zZX^x{g+X;FI&fWmLl9J#0E6no_JKfq9dLLObpyQbZ#4%8gguqX!cp-=dut?^udhQQ z5fD~JmKMgAP+dc~fj$goV`Bk_8(7*{>f68#taOcF(BE8Z8a|jpq_Tc<3IFBl{gJy- zEEGE5vo(=H3Lz3~88iy$?}Q*oe~tzAN4Ys37v zu=$IBp9Yc2e-8|P;iJQK`8_wV$rOP{m<1M_=T5C8xG literal 0 HcmV?d00001 From 9823d52d92224d65682fd348537457e2ddefaac8 Mon Sep 17 00:00:00 2001 From: nminaylov Date: Wed, 22 Nov 2023 20:11:15 +0300 Subject: [PATCH 19/31] File browser: multiple extensions filter, running js scripts from app loader --- .../gui/modules/file_browser_worker.c | 71 ++++++++++++++----- .../gui/modules/file_browser_worker.h | 4 +- .../services/loader/loader_applications.c | 39 ++++++---- 3 files changed, 81 insertions(+), 33 deletions(-) diff --git a/applications/services/gui/modules/file_browser_worker.c b/applications/services/gui/modules/file_browser_worker.c index 857acbbaa41..2636c9555f1 100644 --- a/applications/services/gui/modules/file_browser_worker.c +++ b/applications/services/gui/modules/file_browser_worker.c @@ -32,12 +32,12 @@ typedef enum { (WorkerEvtStop | WorkerEvtLoad | WorkerEvtFolderEnter | WorkerEvtFolderExit | \ WorkerEvtFolderRefresh | WorkerEvtConfigChange) -ARRAY_DEF(idx_last_array, int32_t) +ARRAY_DEF(IdxLastArray, int32_t) +ARRAY_DEF(ExtFilterArray, FuriString*, FURI_STRING_OPLIST) struct BrowserWorker { FuriThread* thread; - FuriString* filter_extension; FuriString* path_start; FuriString* path_current; FuriString* path_next; @@ -46,7 +46,8 @@ struct BrowserWorker { uint32_t load_count; bool skip_assets; bool hide_dot_files; - idx_last_array_t idx_last; + IdxLastArray_t idx_last; + ExtFilterArray_t ext_filter; void* cb_ctx; BrowserWorkerFolderOpenCallback folder_cb; @@ -78,6 +79,31 @@ static bool browser_path_trim(FuriString* path) { } return is_root; } +static void browser_parse_ext_filter(ExtFilterArray_t ext_filter, const char* filter_str) { + if(!filter_str) { + return; + } + + size_t len = strlen(filter_str); + if(len == 0) { + return; + } + + size_t str_offset = 0; + FuriString* ext_temp = furi_string_alloc(); + while(1) { + size_t ext_len = strcspn(&filter_str[str_offset], "|"); + + furi_string_set_strn(ext_temp, &filter_str[str_offset], ext_len); + ExtFilterArray_push_back(ext_filter, ext_temp); + + str_offset += ext_len + 1; + if(str_offset >= len) { + break; + } + } + furi_string_free(ext_temp); +} static bool browser_filter_by_name(BrowserWorker* browser, FuriString* name, bool is_folder) { // Skip dot files if enabled @@ -96,12 +122,20 @@ static bool browser_filter_by_name(BrowserWorker* browser, FuriString* name, boo } } else { // Filter files by extension - if((furi_string_empty(browser->filter_extension)) || - (furi_string_cmp_str(browser->filter_extension, "*") == 0)) { + if(ExtFilterArray_size(browser->ext_filter) == 0) { return true; } - if(furi_string_end_with(name, browser->filter_extension)) { - return true; + + ExtFilterArray_it_t it; + for(ExtFilterArray_it(it, browser->ext_filter); !ExtFilterArray_end_p(it); + ExtFilterArray_next(it)) { + FuriString* ext = *ExtFilterArray_cref(it); + if((furi_string_empty(ext)) || (furi_string_cmp_str(ext, "*") == 0)) { + return true; + } + if(furi_string_end_with(name, ext)) { + return true; + } } } return false; @@ -288,7 +322,7 @@ static int32_t browser_worker(void* context) { if(browser_path_is_file(browser->path_next)) { path_extract_filename(browser->path_next, filename, false); } - idx_last_array_reset(browser->idx_last); + IdxLastArray_reset(browser->idx_last); furi_thread_flags_set(furi_thread_get_id(browser->thread), WorkerEvtFolderEnter); } @@ -298,7 +332,7 @@ static int32_t browser_worker(void* context) { bool is_root = browser_folder_check_and_switch(path); // Push previous selected item index to history array - idx_last_array_push_back(browser->idx_last, browser->item_sel_idx); + IdxLastArray_push_back(browser->idx_last, browser->item_sel_idx); int32_t file_idx = 0; browser_folder_init(browser, path, filename, &items_cnt, &file_idx); @@ -321,9 +355,9 @@ static int32_t browser_worker(void* context) { int32_t file_idx = 0; browser_folder_init(browser, path, filename, &items_cnt, &file_idx); - if(idx_last_array_size(browser->idx_last) > 0) { + if(IdxLastArray_size(browser->idx_last) > 0) { // Pop previous selected item index from history array - idx_last_array_pop_back(&file_idx, browser->idx_last); + IdxLastArray_pop_back(&file_idx, browser->idx_last); } furi_string_set(browser->path_current, path); FURI_LOG_D( @@ -375,14 +409,15 @@ static int32_t browser_worker(void* context) { BrowserWorker* file_browser_worker_alloc( FuriString* path, const char* base_path, - const char* filter_ext, + const char* ext_filter, bool skip_assets, bool hide_dot_files) { BrowserWorker* browser = malloc(sizeof(BrowserWorker)); - idx_last_array_init(browser->idx_last); + IdxLastArray_init(browser->idx_last); + ExtFilterArray_init(browser->ext_filter); - browser->filter_extension = furi_string_alloc_set(filter_ext); + browser_parse_ext_filter(browser->ext_filter, ext_filter); browser->skip_assets = skip_assets; browser->hide_dot_files = hide_dot_files; @@ -407,12 +442,12 @@ void file_browser_worker_free(BrowserWorker* browser) { furi_thread_join(browser->thread); furi_thread_free(browser->thread); - furi_string_free(browser->filter_extension); furi_string_free(browser->path_next); furi_string_free(browser->path_current); furi_string_free(browser->path_start); - idx_last_array_clear(browser->idx_last); + IdxLastArray_clear(browser->idx_last); + ExtFilterArray_clear(browser->ext_filter); free(browser); } @@ -453,12 +488,12 @@ void file_browser_worker_set_long_load_callback( void file_browser_worker_set_config( BrowserWorker* browser, FuriString* path, - const char* filter_ext, + const char* ext_filter, bool skip_assets, bool hide_dot_files) { furi_assert(browser); furi_string_set(browser->path_next, path); - furi_string_set(browser->filter_extension, filter_ext); + browser_parse_ext_filter(browser->ext_filter, ext_filter); browser->skip_assets = skip_assets; browser->hide_dot_files = hide_dot_files; furi_thread_flags_set(furi_thread_get_id(browser->thread), WorkerEvtConfigChange); diff --git a/applications/services/gui/modules/file_browser_worker.h b/applications/services/gui/modules/file_browser_worker.h index 3b4be6aa77b..c5eb61b60b3 100644 --- a/applications/services/gui/modules/file_browser_worker.h +++ b/applications/services/gui/modules/file_browser_worker.h @@ -24,7 +24,7 @@ typedef void (*BrowserWorkerLongLoadCallback)(void* context); BrowserWorker* file_browser_worker_alloc( FuriString* path, const char* base_path, - const char* filter_ext, + const char* ext_filter, bool skip_assets, bool hide_dot_files); @@ -51,7 +51,7 @@ void file_browser_worker_set_long_load_callback( void file_browser_worker_set_config( BrowserWorker* browser, FuriString* path, - const char* filter_ext, + const char* ext_filter, bool skip_assets, bool hide_dot_files); diff --git a/applications/services/loader/loader_applications.c b/applications/services/loader/loader_applications.c index 2e1de6134aa..013fa8f52d3 100644 --- a/applications/services/loader/loader_applications.c +++ b/applications/services/loader/loader_applications.c @@ -7,9 +7,12 @@ #include #include #include +#include #define TAG "LoaderApplications" +#define JS_RUNNER_PATH EXT_PATH("apps/Tools/js_app.fap") + struct LoaderApplications { FuriThread* thread; void (*closed_cb)(void*); @@ -36,7 +39,7 @@ void loader_applications_free(LoaderApplications* loader_applications) { } typedef struct { - FuriString* fap_path; + FuriString* file_path; DialogsApp* dialogs; Storage* storage; Loader* loader; @@ -48,7 +51,7 @@ typedef struct { static LoaderApplicationsApp* loader_applications_app_alloc() { LoaderApplicationsApp* app = malloc(sizeof(LoaderApplicationsApp)); //-V799 - app->fap_path = furi_string_alloc_set(EXT_PATH("apps")); + app->file_path = furi_string_alloc_set(EXT_PATH("apps")); app->dialogs = furi_record_open(RECORD_DIALOGS); app->storage = furi_record_open(RECORD_STORAGE); app->loader = furi_record_open(RECORD_LOADER); @@ -73,7 +76,7 @@ static void loader_applications_app_free(LoaderApplicationsApp* app) { furi_record_close(RECORD_LOADER); furi_record_close(RECORD_DIALOGS); furi_record_close(RECORD_STORAGE); - furi_string_free(app->fap_path); + furi_string_free(app->file_path); free(app); } @@ -84,13 +87,19 @@ static bool loader_applications_item_callback( FuriString* item_name) { LoaderApplicationsApp* loader_applications_app = context; furi_assert(loader_applications_app); - return flipper_application_load_name_and_icon( - path, loader_applications_app->storage, icon_ptr, item_name); + if(furi_string_end_with(path, ".fap")) { + return flipper_application_load_name_and_icon( + path, loader_applications_app->storage, icon_ptr, item_name); + } else { + path_extract_filename(path, item_name, false); + memcpy(*icon_ptr, icon_get_data(&I_js_script_10px), FAP_MANIFEST_MAX_ICON_SIZE); + return true; + } } static bool loader_applications_select_app(LoaderApplicationsApp* loader_applications_app) { const DialogsFileBrowserOptions browser_options = { - .extension = ".fap", + .extension = ".fap|.js", .skip_assets = true, .icon = &I_unknown_10px, .hide_ext = true, @@ -101,8 +110,8 @@ static bool loader_applications_select_app(LoaderApplicationsApp* loader_applica return dialog_file_browser_show( loader_applications_app->dialogs, - loader_applications_app->fap_path, - loader_applications_app->fap_path, + loader_applications_app->file_path, + loader_applications_app->file_path, &browser_options); } @@ -117,9 +126,8 @@ static void loader_pubsub_callback(const void* message, void* context) { } } -static void loader_applications_start_app(LoaderApplicationsApp* app) { - const char* name = furi_string_get_cstr(app->fap_path); - +static void + loader_applications_start_app(LoaderApplicationsApp* app, const char* name, const char* args) { dolphin_deed(DolphinDeedPluginStart); // load app @@ -127,7 +135,7 @@ static void loader_applications_start_app(LoaderApplicationsApp* app) { FuriPubSubSubscription* subscription = furi_pubsub_subscribe(loader_get_pubsub(app->loader), loader_pubsub_callback, thread_id); - LoaderStatus status = loader_start_with_gui_error(app->loader, name, NULL); + LoaderStatus status = loader_start_with_gui_error(app->loader, name, args); if(status == LoaderStatusOk) { furi_thread_flags_wait(APPLICATION_STOP_EVENT, FuriFlagWaitAny, FuriWaitForever); @@ -144,7 +152,12 @@ static int32_t loader_applications_thread(void* p) { view_holder_start(app->view_holder); while(loader_applications_select_app(app)) { - loader_applications_start_app(app); + if(!furi_string_end_with(app->file_path, ".js")) { + loader_applications_start_app(app, furi_string_get_cstr(app->file_path), NULL); + } else { + loader_applications_start_app( + app, JS_RUNNER_PATH, furi_string_get_cstr(app->file_path)); + } } // stop loading animation From 7c1ad3569837385310e698516df8df30e920f625 Mon Sep 17 00:00:00 2001 From: nminaylov Date: Wed, 22 Nov 2023 20:47:07 +0300 Subject: [PATCH 20/31] Running js scripts from archive browser --- applications/main/archive/helpers/archive_browser.h | 1 + applications/main/archive/helpers/archive_files.h | 1 + applications/main/archive/scenes/archive_scene_browser.c | 4 ++++ applications/main/archive/views/archive_browser_view.c | 1 + 4 files changed, 7 insertions(+) diff --git a/applications/main/archive/helpers/archive_browser.h b/applications/main/archive/helpers/archive_browser.h index 5e66a3dbbcb..30548284bdd 100644 --- a/applications/main/archive/helpers/archive_browser.h +++ b/applications/main/archive/helpers/archive_browser.h @@ -29,6 +29,7 @@ static const char* known_ext[] = { [ArchiveFileTypeBadUsb] = ".txt", [ArchiveFileTypeU2f] = "?", [ArchiveFileTypeApplication] = ".fap", + [ArchiveFileTypeJS] = ".js", [ArchiveFileTypeUpdateManifest] = ".fuf", [ArchiveFileTypeFolder] = "?", [ArchiveFileTypeUnknown] = "*", diff --git a/applications/main/archive/helpers/archive_files.h b/applications/main/archive/helpers/archive_files.h index 1822befa358..0c7386c922e 100644 --- a/applications/main/archive/helpers/archive_files.h +++ b/applications/main/archive/helpers/archive_files.h @@ -16,6 +16,7 @@ typedef enum { ArchiveFileTypeU2f, ArchiveFileTypeUpdateManifest, ArchiveFileTypeApplication, + ArchiveFileTypeJS, ArchiveFileTypeFolder, ArchiveFileTypeUnknown, ArchiveFileTypeLoading, diff --git a/applications/main/archive/scenes/archive_scene_browser.c b/applications/main/archive/scenes/archive_scene_browser.c index 370830a0018..53d6c9a013f 100644 --- a/applications/main/archive/scenes/archive_scene_browser.c +++ b/applications/main/archive/scenes/archive_scene_browser.c @@ -9,6 +9,8 @@ #define TAG "ArchiveSceneBrowser" +#define JS_RUNNER_PATH EXT_PATH("apps/Tools/js_app.fap") + #define SCENE_STATE_DEFAULT (0) #define SCENE_STATE_NEED_REFRESH (1) @@ -30,6 +32,8 @@ static const char* archive_get_flipper_app_name(ArchiveFileTypeEnum file_type) { return "U2F"; case ArchiveFileTypeUpdateManifest: return "UpdaterApp"; + case ArchiveFileTypeJS: + return JS_RUNNER_PATH; default: return NULL; } diff --git a/applications/main/archive/views/archive_browser_view.c b/applications/main/archive/views/archive_browser_view.c index ba147f74c8c..2b65486cd72 100644 --- a/applications/main/archive/views/archive_browser_view.c +++ b/applications/main/archive/views/archive_browser_view.c @@ -34,6 +34,7 @@ static const Icon* ArchiveItemIcons[] = { [ArchiveFileTypeUnknown] = &I_unknown_10px, [ArchiveFileTypeLoading] = &I_loading_10px, [ArchiveFileTypeApplication] = &I_unknown_10px, + [ArchiveFileTypeJS] = &I_js_script_10px, }; void archive_browser_set_callback( From 22d9d1a353aeb9aa180f976606a5a56da1172b18 Mon Sep 17 00:00:00 2001 From: nminaylov Date: Fri, 24 Nov 2023 15:50:00 +0300 Subject: [PATCH 21/31] JS Runner as system app --- applications/main/archive/scenes/archive_scene_browser.c | 4 +--- applications/services/loader/loader_applications.c | 4 ++-- applications/system/application.fam | 1 + applications/system/js_app/application.fam | 6 +----- applications/system/js_app/modules/js_badusb.c | 2 +- applications/system/js_app/modules/js_dialog.c | 2 +- applications/system/js_app/modules/js_flipper.c | 2 +- applications/system/js_app/modules/js_flipper.h | 2 +- applications/system/js_app/modules/js_notification.c | 2 +- applications/system/js_app/modules/js_uart.c | 2 +- 10 files changed, 11 insertions(+), 16 deletions(-) diff --git a/applications/main/archive/scenes/archive_scene_browser.c b/applications/main/archive/scenes/archive_scene_browser.c index 53d6c9a013f..d156d171d5f 100644 --- a/applications/main/archive/scenes/archive_scene_browser.c +++ b/applications/main/archive/scenes/archive_scene_browser.c @@ -9,8 +9,6 @@ #define TAG "ArchiveSceneBrowser" -#define JS_RUNNER_PATH EXT_PATH("apps/Tools/js_app.fap") - #define SCENE_STATE_DEFAULT (0) #define SCENE_STATE_NEED_REFRESH (1) @@ -33,7 +31,7 @@ static const char* archive_get_flipper_app_name(ArchiveFileTypeEnum file_type) { case ArchiveFileTypeUpdateManifest: return "UpdaterApp"; case ArchiveFileTypeJS: - return JS_RUNNER_PATH; + return "JS Runner"; default: return NULL; } diff --git a/applications/services/loader/loader_applications.c b/applications/services/loader/loader_applications.c index 013fa8f52d3..235cf20ec01 100644 --- a/applications/services/loader/loader_applications.c +++ b/applications/services/loader/loader_applications.c @@ -11,7 +11,7 @@ #define TAG "LoaderApplications" -#define JS_RUNNER_PATH EXT_PATH("apps/Tools/js_app.fap") +#define JS_RUNNER_APP "JS Runner" struct LoaderApplications { FuriThread* thread; @@ -156,7 +156,7 @@ static int32_t loader_applications_thread(void* p) { loader_applications_start_app(app, furi_string_get_cstr(app->file_path), NULL); } else { loader_applications_start_app( - app, JS_RUNNER_PATH, furi_string_get_cstr(app->file_path)); + app, JS_RUNNER_APP, furi_string_get_cstr(app->file_path)); } } diff --git a/applications/system/application.fam b/applications/system/application.fam index a59f840e456..2d4dfcbb82c 100644 --- a/applications/system/application.fam +++ b/applications/system/application.fam @@ -5,6 +5,7 @@ App( provides=[ "updater_app", "storage_move_to_sd", + "js_app", # "archive", ], ) diff --git a/applications/system/js_app/application.fam b/applications/system/js_app/application.fam index 5dd372e6619..fc4ac3f9cbb 100644 --- a/applications/system/js_app/application.fam +++ b/applications/system/js_app/application.fam @@ -1,14 +1,10 @@ App( appid="js_app", name="JS Runner", - fap_category="Tools", - apptype=FlipperAppType.EXTERNAL, + apptype=FlipperAppType.SYSTEM, entry_point="js_app", stack_size=2 * 1024, - fap_libs=["gcc", "assets"], - fap_file_assets="assets", order=0, - fap_icon="icon.png", ) App( diff --git a/applications/system/js_app/modules/js_badusb.c b/applications/system/js_app/modules/js_badusb.c index 68c8d197a7d..7750200d884 100644 --- a/applications/system/js_app/modules/js_badusb.c +++ b/applications/system/js_app/modules/js_badusb.c @@ -1,5 +1,5 @@ #include -#include "js_modules.h" +#include "../js_modules.h" #include typedef struct { diff --git a/applications/system/js_app/modules/js_dialog.c b/applications/system/js_app/modules/js_dialog.c index 7514b52afe5..e5e8d6a5677 100644 --- a/applications/system/js_app/modules/js_dialog.c +++ b/applications/system/js_app/modules/js_dialog.c @@ -1,5 +1,5 @@ #include -#include "js_modules.h" +#include "../js_modules.h" #include static bool js_dialog_msg_parse_params(struct mjs* mjs, const char** hdr, const char** msg) { diff --git a/applications/system/js_app/modules/js_flipper.c b/applications/system/js_app/modules/js_flipper.c index 0361ccd1ca7..a56af74e8d1 100644 --- a/applications/system/js_app/modules/js_flipper.c +++ b/applications/system/js_app/modules/js_flipper.c @@ -1,5 +1,5 @@ #include -#include "js_modules.h" +#include "../js_modules.h" #include #include diff --git a/applications/system/js_app/modules/js_flipper.h b/applications/system/js_app/modules/js_flipper.h index 9e124ed68be..3b05389cc77 100644 --- a/applications/system/js_app/modules/js_flipper.h +++ b/applications/system/js_app/modules/js_flipper.h @@ -1,4 +1,4 @@ #pragma once -#include "js_thread_i.h" +#include "../js_thread_i.h" void* js_flipper_create(struct mjs* mjs, mjs_val_t* object); diff --git a/applications/system/js_app/modules/js_notification.c b/applications/system/js_app/modules/js_notification.c index 42897c782fc..31b89a70d7c 100644 --- a/applications/system/js_app/modules/js_notification.c +++ b/applications/system/js_app/modules/js_notification.c @@ -1,5 +1,5 @@ #include -#include "js_modules.h" +#include "../js_modules.h" #include static void js_notify(struct mjs* mjs, const NotificationSequence* sequence) { diff --git a/applications/system/js_app/modules/js_uart.c b/applications/system/js_app/modules/js_uart.c index 47b4aeeacbe..e865cc54b3a 100644 --- a/applications/system/js_app/modules/js_uart.c +++ b/applications/system/js_app/modules/js_uart.c @@ -1,6 +1,6 @@ #include #include -#include "js_modules.h" +#include "../js_modules.h" #include #define TAG "js_uart" From 96e33c226f36c6f1c4d11d97e3499cd634d2345b Mon Sep 17 00:00:00 2001 From: nminaylov Date: Fri, 24 Nov 2023 16:28:15 +0300 Subject: [PATCH 22/31] Example scripts moved to /ext/apps/Scripts --- applications/system/js_app/application.fam | 1 + .../system/js_app/{assets => examples/apps/Scripts}/about.js | 0 .../system/js_app/{assets => examples/apps/Scripts}/api.js | 0 .../system/js_app/{assets => examples/apps/Scripts}/badusb.js | 0 .../js_app/{assets => examples/apps/Scripts}/badusb_demo.js | 0 .../system/js_app/{assets => examples/apps/Scripts}/console.js | 0 .../system/js_app/{assets => examples/apps/Scripts}/delay.js | 0 .../system/js_app/{assets => examples/apps/Scripts}/dialog.js | 0 .../system/js_app/{assets => examples/apps/Scripts}/ffi.js | 0 .../system/js_app/{assets => examples/apps/Scripts}/notify.js | 0 .../js_app/{assets => examples/apps/Scripts}/print_test.js | 0 .../system/js_app/{assets => examples/apps/Scripts}/req.js | 0 .../js_app/{assets => examples/apps/Scripts}/require_api.js | 0 .../system/js_app/{assets => examples/apps/Scripts}/uart.js | 0 14 files changed, 1 insertion(+) rename applications/system/js_app/{assets => examples/apps/Scripts}/about.js (100%) rename applications/system/js_app/{assets => examples/apps/Scripts}/api.js (100%) rename applications/system/js_app/{assets => examples/apps/Scripts}/badusb.js (100%) rename applications/system/js_app/{assets => examples/apps/Scripts}/badusb_demo.js (100%) rename applications/system/js_app/{assets => examples/apps/Scripts}/console.js (100%) rename applications/system/js_app/{assets => examples/apps/Scripts}/delay.js (100%) rename applications/system/js_app/{assets => examples/apps/Scripts}/dialog.js (100%) rename applications/system/js_app/{assets => examples/apps/Scripts}/ffi.js (100%) rename applications/system/js_app/{assets => examples/apps/Scripts}/notify.js (100%) rename applications/system/js_app/{assets => examples/apps/Scripts}/print_test.js (100%) rename applications/system/js_app/{assets => examples/apps/Scripts}/req.js (100%) rename applications/system/js_app/{assets => examples/apps/Scripts}/require_api.js (100%) rename applications/system/js_app/{assets => examples/apps/Scripts}/uart.js (100%) diff --git a/applications/system/js_app/application.fam b/applications/system/js_app/application.fam index fc4ac3f9cbb..29b4ebb5977 100644 --- a/applications/system/js_app/application.fam +++ b/applications/system/js_app/application.fam @@ -4,6 +4,7 @@ App( apptype=FlipperAppType.SYSTEM, entry_point="js_app", stack_size=2 * 1024, + resources="examples", order=0, ) diff --git a/applications/system/js_app/assets/about.js b/applications/system/js_app/examples/apps/Scripts/about.js similarity index 100% rename from applications/system/js_app/assets/about.js rename to applications/system/js_app/examples/apps/Scripts/about.js diff --git a/applications/system/js_app/assets/api.js b/applications/system/js_app/examples/apps/Scripts/api.js similarity index 100% rename from applications/system/js_app/assets/api.js rename to applications/system/js_app/examples/apps/Scripts/api.js diff --git a/applications/system/js_app/assets/badusb.js b/applications/system/js_app/examples/apps/Scripts/badusb.js similarity index 100% rename from applications/system/js_app/assets/badusb.js rename to applications/system/js_app/examples/apps/Scripts/badusb.js diff --git a/applications/system/js_app/assets/badusb_demo.js b/applications/system/js_app/examples/apps/Scripts/badusb_demo.js similarity index 100% rename from applications/system/js_app/assets/badusb_demo.js rename to applications/system/js_app/examples/apps/Scripts/badusb_demo.js diff --git a/applications/system/js_app/assets/console.js b/applications/system/js_app/examples/apps/Scripts/console.js similarity index 100% rename from applications/system/js_app/assets/console.js rename to applications/system/js_app/examples/apps/Scripts/console.js diff --git a/applications/system/js_app/assets/delay.js b/applications/system/js_app/examples/apps/Scripts/delay.js similarity index 100% rename from applications/system/js_app/assets/delay.js rename to applications/system/js_app/examples/apps/Scripts/delay.js diff --git a/applications/system/js_app/assets/dialog.js b/applications/system/js_app/examples/apps/Scripts/dialog.js similarity index 100% rename from applications/system/js_app/assets/dialog.js rename to applications/system/js_app/examples/apps/Scripts/dialog.js diff --git a/applications/system/js_app/assets/ffi.js b/applications/system/js_app/examples/apps/Scripts/ffi.js similarity index 100% rename from applications/system/js_app/assets/ffi.js rename to applications/system/js_app/examples/apps/Scripts/ffi.js diff --git a/applications/system/js_app/assets/notify.js b/applications/system/js_app/examples/apps/Scripts/notify.js similarity index 100% rename from applications/system/js_app/assets/notify.js rename to applications/system/js_app/examples/apps/Scripts/notify.js diff --git a/applications/system/js_app/assets/print_test.js b/applications/system/js_app/examples/apps/Scripts/print_test.js similarity index 100% rename from applications/system/js_app/assets/print_test.js rename to applications/system/js_app/examples/apps/Scripts/print_test.js diff --git a/applications/system/js_app/assets/req.js b/applications/system/js_app/examples/apps/Scripts/req.js similarity index 100% rename from applications/system/js_app/assets/req.js rename to applications/system/js_app/examples/apps/Scripts/req.js diff --git a/applications/system/js_app/assets/require_api.js b/applications/system/js_app/examples/apps/Scripts/require_api.js similarity index 100% rename from applications/system/js_app/assets/require_api.js rename to applications/system/js_app/examples/apps/Scripts/require_api.js diff --git a/applications/system/js_app/assets/uart.js b/applications/system/js_app/examples/apps/Scripts/uart.js similarity index 100% rename from applications/system/js_app/assets/uart.js rename to applications/system/js_app/examples/apps/Scripts/uart.js From 6be0df3981c3cffa3118ed3024435f0849ab2d31 Mon Sep 17 00:00:00 2001 From: nminaylov Date: Mon, 27 Nov 2023 19:52:18 +0300 Subject: [PATCH 23/31] JS bytecode listing generation --- .../js_app/examples/apps/Scripts/req.js | 2 +- applications/system/js_app/js_app.c | 2 +- applications/system/js_app/js_thread.c | 36 ++++++ lib/mjs/mjs_util.c | 104 ++++++++++-------- lib/mjs/mjs_util_public.h | 10 +- targets/f18/api_symbols.csv | 2 + targets/f7/api_symbols.csv | 2 + 7 files changed, 106 insertions(+), 52 deletions(-) diff --git a/applications/system/js_app/examples/apps/Scripts/req.js b/applications/system/js_app/examples/apps/Scripts/req.js index 9d8f1266798..59d99b94cb7 100644 --- a/applications/system/js_app/examples/apps/Scripts/req.js +++ b/applications/system/js_app/examples/apps/Scripts/req.js @@ -1,3 +1,3 @@ -let math = load("/ext/apps_assets/m_js/api.js"); +let math = load("/ext/apps/Scripts/api.js"); let result = math.add(5, 10); print(result); \ No newline at end of file diff --git a/applications/system/js_app/js_app.c b/applications/system/js_app/js_app.c index 67573793f05..58006ad5795 100644 --- a/applications/system/js_app/js_app.c +++ b/applications/system/js_app/js_app.c @@ -5,7 +5,7 @@ #include #include -#define TAG "MJS app" +#define TAG "JS app" typedef struct { JsThread* js_thread; diff --git a/applications/system/js_app/js_thread.c b/applications/system/js_app/js_thread.c index fe431b8670c..946be363e41 100644 --- a/applications/system/js_app/js_thread.c +++ b/applications/system/js_app/js_thread.c @@ -3,6 +3,7 @@ #include #include #include +#include #include "plugin_api/app_api_interface.h" #include "js_thread.h" #include "js_thread_i.h" @@ -209,6 +210,22 @@ static void mjs_global_to_hex_string(struct mjs* mjs) { mjs_return(mjs, ret); } +static void js_dump_write_callback(void* ctx, const char* format, ...) { + File* file = ctx; + furi_assert(ctx); + + FuriString* str = furi_string_alloc(); + + va_list args; + va_start(args, format); + furi_string_vprintf(str, format, args); + furi_string_cat(str, "\n"); + va_end(args); + + storage_file_write(file, furi_string_get_cstr(str), furi_string_size(str)); + furi_string_free(str); +} + static int32_t js_thread(void* arg) { JsThread* worker = arg; worker->resolver = composite_api_resolver_alloc(); @@ -238,6 +255,25 @@ static int32_t js_thread(void* arg) { mjs_err_t err = mjs_exec_file(mjs, furi_string_get_cstr(worker->path), NULL); + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + FuriString* dump_path = furi_string_alloc_set(worker->path); + furi_string_cat(dump_path, ".lst"); + + Storage* storage = furi_record_open(RECORD_STORAGE); + File* file = storage_file_alloc(storage); + + if(storage_file_open( + file, furi_string_get_cstr(dump_path), FSAM_WRITE, FSOM_CREATE_ALWAYS)) { + mjs_disasm_all(mjs, js_dump_write_callback, file); + } + + storage_file_close(file); + storage_file_free(file); + furi_record_close(RECORD_STORAGE); + + furi_string_free(dump_path); + } + if(err != MJS_OK) { FURI_LOG_E(TAG, "Exec error: %s", mjs_strerror(mjs, err)); if(worker->app_callback) { diff --git a/lib/mjs/mjs_util.c b/lib/mjs/mjs_util.c index 383b0182414..39bdbb5795f 100644 --- a/lib/mjs/mjs_util.c +++ b/lib/mjs/mjs_util.c @@ -14,6 +14,7 @@ #include "mjs_string.h" #include "mjs_util.h" #include "mjs_tok.h" +#include const char* mjs_typeof(mjs_val_t v) { return mjs_stringify_type(mjs_get_type(v)); @@ -93,8 +94,6 @@ void mjs_fprintf(mjs_val_t v, struct mjs* mjs, FILE* fp) { mjs_jprintf(v, mjs, &out); } -#if MJS_ENABLE_DEBUG - MJS_PRIVATE const char* opcodetostr(uint8_t opcode) { static const char* names[] = { "NOP", @@ -143,24 +142,27 @@ MJS_PRIVATE const char* opcodetostr(uint8_t opcode) { return name; } -MJS_PRIVATE size_t mjs_disasm_single(const uint8_t* code, size_t i) { +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; - snprintf(buf, sizeof(buf), "\t%-3u %-8s", (unsigned)i, opcodetostr(code[i])); + 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); - LOG(LL_VERBOSE_DEBUG, ("%s %04u", buf, (unsigned)(i - n))); + 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); - LOG(LL_VERBOSE_DEBUG, ("%s\t%lu", buf, (unsigned long)n)); + print_cb(print_ctx, "%s\t%lu", buf, (unsigned long)n); i += llen; break; } @@ -169,15 +171,15 @@ MJS_PRIVATE size_t mjs_disasm_single(const uint8_t* code, size_t i) { uint64_t arg_no; cs_varint_decode(&code[i + 1], ~0, &arg_no, &llen); cs_varint_decode(&code[i + llen + 1], ~0, &n, &llen2); - LOG(LL_VERBOSE_DEBUG, - ("%s\t[%.*s] %u", buf, (int)n, code + i + 1 + llen + llen2, (unsigned)arg_no)); + 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); - LOG(LL_VERBOSE_DEBUG, ("%s\t[%.*s]", buf, (int)n, code + i + 1 + llen)); + print_cb(print_ctx, "%s\t[%.*s]", buf, (int)n, code + i + 1 + llen); i += llen + n; break; } @@ -187,10 +189,11 @@ MJS_PRIVATE size_t mjs_disasm_single(const uint8_t* code, size_t i) { case OP_JMP_FALSE: case OP_JMP_NEUTRAL_FALSE: { cs_varint_decode(&code[i + 1], ~0, &n, &llen); - LOG(LL_VERBOSE_DEBUG, - ("%s\t%u", - buf, - (unsigned)(i + n + llen + 1 /* becaue i will be incremented on the usual terms */))); + 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; } @@ -199,12 +202,13 @@ MJS_PRIVATE size_t mjs_disasm_single(const uint8_t* code, size_t i) { uint64_t n1, n2; cs_varint_decode(&code[i + 1], ~0, &n1, &l1); cs_varint_decode(&code[i + l1 + 1], ~0, &n2, &l2); - LOG(LL_VERBOSE_DEBUG, - ("%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)); + 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; } @@ -258,7 +262,7 @@ MJS_PRIVATE size_t mjs_disasm_single(const uint8_t* code, size_t i) { case TOK_URSHIFT_ASSIGN: name = ">>>="; break; } /* clang-format on */ - LOG(LL_VERBOSE_DEBUG, ("%s\t%s", buf, name)); + print_cb(print_ctx, "%s\t%s", buf, name); i++; break; } @@ -272,28 +276,29 @@ MJS_PRIVATE size_t mjs_disasm_single(const uint8_t* code, size_t i) { &code[i + 1 + MJS_HDR_ITEM_MAP_OFFSET * sizeof(total_size)], sizeof(map_offset)); i += sizeof(mjs_header_item_t) * MJS_HDR_ITEMS_CNT; - LOG(LL_VERBOSE_DEBUG, - ("%s\t[%s] end:%lu map_offset: %lu", - buf, - &code[i + 1], - (unsigned long)start + total_size, - (unsigned long)start + map_offset)); + 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: - LOG(LL_VERBOSE_DEBUG, ("%s", buf)); + print_cb(print_ctx, "%s", buf); break; } return i - start_i; } -void mjs_disasm(const uint8_t* code, size_t len) { +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); + 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)); @@ -312,38 +317,49 @@ void mjs_disasm(const uint8_t* code, size_t len) { } } -static void mjs_dump_obj_stack(const char* name, const struct mbuf* m, struct mjs* mjs) { +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); - LOG(LL_VERBOSE_DEBUG, ("%12s (%d elems): ", name, (int)n)); + 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)); - LOG(LL_VERBOSE_DEBUG, ("%34s", buf)); + print_cb(print_ctx, "%34s", buf); } } -void mjs_dump(struct mjs* mjs, int do_disasm) { - LOG(LL_VERBOSE_DEBUG, ("------- MJS VM DUMP BEGIN")); - mjs_dump_obj_stack("DATA_STACK", &mjs->stack, mjs); - mjs_dump_obj_stack("CALL_STACK", &mjs->call_stack, mjs); - mjs_dump_obj_stack("SCOPES", &mjs->scopes, mjs); - mjs_dump_obj_stack("LOOP_OFFSETS", &mjs->loop_addresses, mjs); - mjs_dump_obj_stack("ARG_STACK", &mjs->arg_stack, mjs); +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; - LOG(LL_VERBOSE_DEBUG, ("%23s", "CODE:")); + 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); + mjs_disasm((uint8_t*)bp->data.p, bp->data.len, print_cb, print_ctx); } } - LOG(LL_VERBOSE_DEBUG, ("------- MJS VM DUMP END")); + print_cb(print_ctx, "------- MJS VM DUMP END"); } -#endif - MJS_PRIVATE int mjs_check_arg( struct mjs* mjs, int arg_num, diff --git a/lib/mjs/mjs_util_public.h b/lib/mjs/mjs_util_public.h index b41cdb8c962..35c00803f9d 100644 --- a/lib/mjs/mjs_util_public.h +++ b/lib/mjs/mjs_util_public.h @@ -13,17 +13,15 @@ extern "C" { #endif /* __cplusplus */ +typedef void (*MjsPrintCallback)(void* ctx, const char* format, ...); + const char* mjs_typeof(mjs_val_t v); void mjs_fprintf(mjs_val_t v, struct mjs* mjs, FILE* fp); void mjs_sprintf(mjs_val_t v, struct mjs* mjs, char* buf, size_t buflen); -#if MJS_ENABLE_DEBUG - -void mjs_disasm(const uint8_t* code, size_t len); -void mjs_dump(struct mjs* mjs, int do_disasm); - -#endif +void mjs_disasm_all(struct mjs* mjs, MjsPrintCallback print_cb, void* print_ctx); +void mjs_dump(struct mjs* mjs, int do_disasm, MjsPrintCallback print_cb, void* print_ctx); /* * Returns the filename corresponding to the given bcode offset. diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 8cee3fab50f..d82953f6fa1 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1749,7 +1749,9 @@ Function,+,mjs_call,mjs_err_t,"mjs*, mjs_val_t*, mjs_val_t, mjs_val_t, int, ..." Function,+,mjs_create,mjs*,void* Function,+,mjs_del,int,"mjs*, mjs_val_t, const char*, size_t" Function,+,mjs_destroy,void,mjs* +Function,-,mjs_disasm_all,void,"mjs*, MjsPrintCallback, void*" Function,+,mjs_disown,int,"mjs*, mjs_val_t*" +Function,-,mjs_dump,void,"mjs*, int, MjsPrintCallback, void*" Function,+,mjs_exec,mjs_err_t,"mjs*, const char*, mjs_val_t*" Function,+,mjs_exec_file,mjs_err_t,"mjs*, const char*, mjs_val_t*" Function,+,mjs_exit,void,mjs* diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index f81c3566527..1227c834f62 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -2266,7 +2266,9 @@ Function,+,mjs_call,mjs_err_t,"mjs*, mjs_val_t*, mjs_val_t, mjs_val_t, int, ..." Function,+,mjs_create,mjs*,void* Function,+,mjs_del,int,"mjs*, mjs_val_t, const char*, size_t" Function,+,mjs_destroy,void,mjs* +Function,-,mjs_disasm_all,void,"mjs*, MjsPrintCallback, void*" Function,+,mjs_disown,int,"mjs*, mjs_val_t*" +Function,-,mjs_dump,void,"mjs*, int, MjsPrintCallback, void*" Function,+,mjs_exec,mjs_err_t,"mjs*, const char*, mjs_val_t*" Function,+,mjs_exec_file,mjs_err_t,"mjs*, const char*, mjs_val_t*" Function,+,mjs_exit,void,mjs* From 3e30df26ccf835d29c3d489c0871286ad9ba9d2a Mon Sep 17 00:00:00 2001 From: nminaylov Date: Tue, 12 Dec 2023 15:48:05 +0300 Subject: [PATCH 24/31] MJS builtin printf cleanup --- applications/system/js_app/js_thread.c | 60 +++++++++++++------------- lib/mjs/common/cs_time.c | 10 +++-- lib/mjs/common/str_util.c | 2 +- lib/mjs/mjs_builtin.c | 12 ------ lib/mjs/mjs_core.c | 4 +- lib/mjs/mjs_core.h | 2 +- lib/mjs/mjs_core_public.h | 2 +- lib/mjs/mjs_exec.c | 4 +- lib/mjs/mjs_internal.h | 8 ++-- targets/f18/api_symbols.csv | 2 +- targets/f7/api_symbols.csv | 2 +- 11 files changed, 50 insertions(+), 58 deletions(-) diff --git a/applications/system/js_app/js_thread.c b/applications/system/js_app/js_thread.c index 946be363e41..ffd9fc98403 100644 --- a/applications/system/js_app/js_thread.c +++ b/applications/system/js_app/js_thread.c @@ -20,7 +20,7 @@ struct JsThread { JsModules* modules; }; -static void mjs_str_print(FuriString* msg_str, struct mjs* mjs) { +static void js_str_print(FuriString* msg_str, struct mjs* mjs) { size_t num_args = mjs_nargs(mjs); for(size_t i = 0; i < num_args; i++) { char* name = NULL; @@ -40,9 +40,9 @@ static void mjs_str_print(FuriString* msg_str, struct mjs* mjs) { } } -static void mjs_print(struct mjs* mjs) { +static void js_print(struct mjs* mjs) { FuriString* msg_str = furi_string_alloc(); - mjs_str_print(msg_str, mjs); + js_str_print(msg_str, mjs); printf("%s\r\n", furi_string_get_cstr(msg_str)); @@ -57,39 +57,39 @@ static void mjs_print(struct mjs* mjs) { mjs_return(mjs, MJS_UNDEFINED); } -static void mjs_console_log(struct mjs* mjs) { +static void js_console_log(struct mjs* mjs) { FuriString* msg_str = furi_string_alloc(); - mjs_str_print(msg_str, mjs); + js_str_print(msg_str, mjs); FURI_LOG_I(TAG, "%s", furi_string_get_cstr(msg_str)); furi_string_free(msg_str); mjs_return(mjs, MJS_UNDEFINED); } -static void mjs_console_warn(struct mjs* mjs) { +static void js_console_warn(struct mjs* mjs) { FuriString* msg_str = furi_string_alloc(); - mjs_str_print(msg_str, mjs); + js_str_print(msg_str, mjs); FURI_LOG_W(TAG, "%s", furi_string_get_cstr(msg_str)); furi_string_free(msg_str); mjs_return(mjs, MJS_UNDEFINED); } -static void mjs_console_error(struct mjs* mjs) { +static void js_console_error(struct mjs* mjs) { FuriString* msg_str = furi_string_alloc(); - mjs_str_print(msg_str, mjs); + js_str_print(msg_str, mjs); FURI_LOG_E(TAG, "%s", furi_string_get_cstr(msg_str)); furi_string_free(msg_str); mjs_return(mjs, MJS_UNDEFINED); } -static void mjs_console_debug(struct mjs* mjs) { +static void js_console_debug(struct mjs* mjs) { FuriString* msg_str = furi_string_alloc(); - mjs_str_print(msg_str, mjs); + js_str_print(msg_str, mjs); FURI_LOG_D(TAG, "%s", furi_string_get_cstr(msg_str)); furi_string_free(msg_str); mjs_return(mjs, MJS_UNDEFINED); } -static void mjs_exit_flag_poll(struct mjs* mjs) { +static void js_exit_flag_poll(struct mjs* mjs) { uint32_t flags = furi_thread_flags_wait(ThreadEventStop, FuriFlagWaitAny, 0); if(flags & FuriFlagError) { return; @@ -137,7 +137,7 @@ uint32_t js_flags_wait(struct mjs* mjs, uint32_t flags_mask, uint32_t timeout) { return flags; } -static void mjs_delay(struct mjs* mjs) { +static void js_delay(struct mjs* mjs) { bool args_correct = false; int ms = 0; @@ -157,7 +157,7 @@ static void mjs_delay(struct mjs* mjs) { mjs_return(mjs, MJS_UNDEFINED); } -static void* my_dlsym(void* handle, const char* name) { +static void* js_dlsym(void* handle, const char* name) { CompositeApiResolver* resolver = handle; Elf32_Addr addr = 0; uint32_t hash = elf_symbolname_hash(name); @@ -171,7 +171,7 @@ static void* my_dlsym(void* handle, const char* name) { return (void*)addr; } -static void mjs_ffi_address(struct mjs* mjs) { +static void js_ffi_address(struct mjs* mjs) { mjs_val_t name_v = mjs_arg(mjs, 0); size_t len; const char* name = mjs_get_string(mjs, &name_v, &len); @@ -179,7 +179,7 @@ static void mjs_ffi_address(struct mjs* mjs) { mjs_return(mjs, mjs_mk_foreign(mjs, addr)); } -static void mjs_require(struct mjs* mjs) { +static void js_require(struct mjs* mjs) { mjs_val_t name_v = mjs_arg(mjs, 0); size_t len; const char* name = mjs_get_string(mjs, &name_v, &len); @@ -194,7 +194,7 @@ static void mjs_require(struct mjs* mjs) { mjs_return(mjs, req_object); } -static void mjs_global_to_string(struct mjs* mjs) { +static void js_global_to_string(struct mjs* mjs) { double num = mjs_get_int(mjs, mjs_arg(mjs, 0)); char tmp_str[] = "-2147483648"; itoa(num, tmp_str, 10); @@ -202,7 +202,7 @@ static void mjs_global_to_string(struct mjs* mjs) { mjs_return(mjs, ret); } -static void mjs_global_to_hex_string(struct mjs* mjs) { +static void js_global_to_hex_string(struct mjs* mjs) { double num = mjs_get_int(mjs, mjs_arg(mjs, 0)); char tmp_str[] = "-FFFFFFFF"; itoa(num, tmp_str, 16); @@ -235,23 +235,23 @@ static int32_t js_thread(void* arg) { struct mjs* mjs = mjs_create(worker); worker->modules = js_modules_create(mjs, worker->resolver); mjs_val_t global = mjs_get_global(mjs); - mjs_set(mjs, global, "print", ~0, MFS_MK_FN(mjs_print)); - mjs_set(mjs, global, "delay", ~0, MFS_MK_FN(mjs_delay)); - mjs_set(mjs, global, "to_string", ~0, MFS_MK_FN(mjs_global_to_string)); - mjs_set(mjs, global, "to_hex_string", ~0, MFS_MK_FN(mjs_global_to_hex_string)); - mjs_set(mjs, global, "ffi_address", ~0, MFS_MK_FN(mjs_ffi_address)); - mjs_set(mjs, global, "require", ~0, MFS_MK_FN(mjs_require)); + mjs_set(mjs, global, "print", ~0, MFS_MK_FN(js_print)); + mjs_set(mjs, global, "delay", ~0, MFS_MK_FN(js_delay)); + mjs_set(mjs, global, "to_string", ~0, MFS_MK_FN(js_global_to_string)); + mjs_set(mjs, global, "to_hex_string", ~0, MFS_MK_FN(js_global_to_hex_string)); + mjs_set(mjs, global, "ffi_address", ~0, MFS_MK_FN(js_ffi_address)); + mjs_set(mjs, global, "require", ~0, MFS_MK_FN(js_require)); mjs_val_t console_obj = mjs_mk_object(mjs); - mjs_set(mjs, console_obj, "log", ~0, MFS_MK_FN(mjs_console_log)); - mjs_set(mjs, console_obj, "warn", ~0, MFS_MK_FN(mjs_console_warn)); - mjs_set(mjs, console_obj, "error", ~0, MFS_MK_FN(mjs_console_error)); - mjs_set(mjs, console_obj, "debug", ~0, MFS_MK_FN(mjs_console_debug)); + mjs_set(mjs, console_obj, "log", ~0, MFS_MK_FN(js_console_log)); + mjs_set(mjs, console_obj, "warn", ~0, MFS_MK_FN(js_console_warn)); + mjs_set(mjs, console_obj, "error", ~0, MFS_MK_FN(js_console_error)); + mjs_set(mjs, console_obj, "debug", ~0, MFS_MK_FN(js_console_debug)); mjs_set(mjs, global, "console", ~0, console_obj); - mjs_set_ffi_resolver(mjs, my_dlsym, worker->resolver); + mjs_set_ffi_resolver(mjs, js_dlsym, worker->resolver); - mjs_set_flags_poller(mjs, mjs_exit_flag_poll); + mjs_set_exec_flags_poller(mjs, js_exit_flag_poll); mjs_err_t err = mjs_exec_file(mjs, furi_string_get_cstr(worker->path), NULL); diff --git a/lib/mjs/common/cs_time.c b/lib/mjs/common/cs_time.c index a8eb7c24a36..fc3fa8ffe0b 100644 --- a/lib/mjs/common/cs_time.c +++ b/lib/mjs/common/cs_time.c @@ -17,6 +17,8 @@ #include "cs_time.h" +#if CS_ENABLE_STDIO + #ifndef _WIN32 #include /* @@ -50,9 +52,9 @@ double cs_time(void) { * thus, we need to convert to seconds and adjust amount (subtract 11644473600 * seconds) */ - now = - (double)(((int64_t)ftime.dwLowDateTime + ((int64_t)ftime.dwHighDateTime << 32)) / 10000000.0) - - 11644473600; + now = (double)(((int64_t)ftime.dwLowDateTime + ((int64_t)ftime.dwHighDateTime << 32)) / + 10000000.0) - + 11644473600; #endif /* _WIN32 */ return now; } @@ -85,3 +87,5 @@ double cs_timegm(const struct tm* tm) { + (year_for_leap + 299) / 400))); /* Except 400s. */ return rt < 0 ? -1 : (double)rt; } + +#endif \ No newline at end of file diff --git a/lib/mjs/common/str_util.c b/lib/mjs/common/str_util.c index 889e9560adc..76e40682f3b 100644 --- a/lib/mjs/common/str_util.c +++ b/lib/mjs/common/str_util.c @@ -22,7 +22,7 @@ #include "platform.h" #ifndef C_DISABLE_BUILTIN_SNPRINTF -#define C_DISABLE_BUILTIN_SNPRINTF 0 +#define C_DISABLE_BUILTIN_SNPRINTF 1 #endif #include "mg_mem.h" diff --git a/lib/mjs/mjs_builtin.c b/lib/mjs/mjs_builtin.c index 36ad43cf2e3..fe05cbee5a4 100644 --- a/lib/mjs/mjs_builtin.c +++ b/lib/mjs/mjs_builtin.c @@ -15,17 +15,6 @@ #include "mjs_string.h" #include "mjs_util.h" -static void mjs_print(struct mjs* mjs) { - size_t i, num_args = mjs_nargs(mjs); - for(i = 0; i < num_args; i++) { - mjs_fprintf(mjs_arg(mjs, i), mjs, stdout); - putchar(' '); - } - putchar('\n'); - mjs_return(mjs, MJS_UNDEFINED); - (void)mjs; -} - /* * If the file with the given filename was already loaded, returns the * corresponding bcode part; otherwise returns NULL. @@ -136,7 +125,6 @@ void mjs_init_builtin(struct mjs* mjs, mjs_val_t obj) { mjs_set(mjs, obj, "global", ~0, obj); mjs_set(mjs, obj, "load", ~0, mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_load)); - // mjs_set(mjs, obj, "print", ~0, mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_print)); mjs_set(mjs, obj, "ffi", ~0, mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_ffi_call)); mjs_set( mjs, obj, "ffi_cb_free", ~0, mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_ffi_cb_free)); diff --git a/lib/mjs/mjs_core.c b/lib/mjs/mjs_core.c index 1fd76fd4af5..89cc5c0e109 100644 --- a/lib/mjs/mjs_core.c +++ b/lib/mjs/mjs_core.c @@ -142,8 +142,8 @@ void mjs_exit(struct mjs* mjs) { mjs->error = MJS_NEED_EXIT; } -void mjs_set_flags_poller(struct mjs* mjs, mjs_flags_poller_t poller) { - mjs->flags_poller = poller; +void mjs_set_exec_flags_poller(struct mjs* mjs, mjs_flags_poller_t poller) { + mjs->exec_flags_poller = poller; } void* mjs_get_context(struct mjs* mjs) { diff --git a/lib/mjs/mjs_core.h b/lib/mjs/mjs_core.h index 97e3d849e80..2dcb6bab68e 100644 --- a/lib/mjs/mjs_core.h +++ b/lib/mjs/mjs_core.h @@ -81,7 +81,7 @@ struct mjs { void* dlsym_handle; ffi_cb_args_t* ffi_cb_args; /* List of FFI args descriptors */ size_t cur_bcode_offset; - mjs_flags_poller_t flags_poller; + mjs_flags_poller_t exec_flags_poller; void* context; struct gc_arena object_arena; diff --git a/lib/mjs/mjs_core_public.h b/lib/mjs/mjs_core_public.h index 460d285e76a..e92383d0113 100644 --- a/lib/mjs/mjs_core_public.h +++ b/lib/mjs/mjs_core_public.h @@ -220,7 +220,7 @@ mjs_err_t mjs_set_errorf(struct mjs* mjs, mjs_err_t err, const char* fmt, ...); void mjs_exit(struct mjs* mjs); -void mjs_set_flags_poller(struct mjs* mjs, mjs_flags_poller_t poller); +void mjs_set_exec_flags_poller(struct mjs* mjs, mjs_flags_poller_t poller); void* mjs_get_context(struct mjs* mjs); diff --git a/lib/mjs/mjs_exec.c b/lib/mjs/mjs_exec.c index 93440a570c5..83c842844c6 100644 --- a/lib/mjs/mjs_exec.c +++ b/lib/mjs/mjs_exec.c @@ -967,8 +967,8 @@ MJS_PRIVATE mjs_err_t mjs_execute(struct mjs* mjs, size_t off, mjs_val_t* res) { break; } - if(mjs->flags_poller) { - mjs->flags_poller(mjs); + if(mjs->exec_flags_poller) { + mjs->exec_flags_poller(mjs); } if(mjs->error != MJS_OK) { diff --git a/lib/mjs/mjs_internal.h b/lib/mjs/mjs_internal.h index d395ed1ea4a..eb1bccbd7fe 100644 --- a/lib/mjs/mjs_internal.h +++ b/lib/mjs/mjs_internal.h @@ -25,14 +25,14 @@ #define ENDL "\n" #endif -#define MJS_EXPOSE_PRIVATE // TODO: +#ifndef MJS_EXPOSE_PRIVATE +#define MJS_EXPOSE_PRIVATE 1 +#endif -#ifdef MJS_EXPOSE_PRIVATE +#if MJS_EXPOSE_PRIVATE #define MJS_PRIVATE -#define MJS_EXTERN extern #else #define MJS_PRIVATE static -#define MJS_EXTERN static #endif #ifndef ARRAY_SIZE diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index d82953f6fa1..f47a10ba8f4 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1803,7 +1803,7 @@ Function,+,mjs_return,void,"mjs*, mjs_val_t" Function,+,mjs_set,mjs_err_t,"mjs*, mjs_val_t, const char*, size_t, mjs_val_t" Function,+,mjs_set_errorf,mjs_err_t,"mjs*, mjs_err_t, const char*, ..." Function,+,mjs_set_ffi_resolver,void,"mjs*, mjs_ffi_resolver_t*, void*" -Function,+,mjs_set_flags_poller,void,"mjs*, mjs_flags_poller_t" +Function,+,mjs_set_exec_flags_poller,void,"mjs*, mjs_flags_poller_t" Function,-,mjs_set_generate_jsc,void,"mjs*, int" Function,+,mjs_set_v,mjs_err_t,"mjs*, mjs_val_t, mjs_val_t, mjs_val_t" Function,+,mjs_sprintf,void,"mjs_val_t, mjs*, char*, size_t" diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 1227c834f62..d1a520fb4f2 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -2320,7 +2320,7 @@ Function,+,mjs_return,void,"mjs*, mjs_val_t" Function,+,mjs_set,mjs_err_t,"mjs*, mjs_val_t, const char*, size_t, mjs_val_t" Function,+,mjs_set_errorf,mjs_err_t,"mjs*, mjs_err_t, const char*, ..." Function,+,mjs_set_ffi_resolver,void,"mjs*, mjs_ffi_resolver_t*, void*" -Function,+,mjs_set_flags_poller,void,"mjs*, mjs_flags_poller_t" +Function,+,mjs_set_exec_flags_poller,void,"mjs*, mjs_flags_poller_t" Function,-,mjs_set_generate_jsc,void,"mjs*, int" Function,+,mjs_set_v,mjs_err_t,"mjs*, mjs_val_t, mjs_val_t, mjs_val_t" Function,+,mjs_sprintf,void,"mjs_val_t, mjs*, char*, size_t" From 973467e003122f29db24091dedb14fce0b9b574b Mon Sep 17 00:00:00 2001 From: nminaylov Date: Tue, 12 Dec 2023 15:53:26 +0300 Subject: [PATCH 25/31] JS examples cleanup --- .../js_app/examples/apps/Scripts/about.js | 350 ------------------ .../apps/Scripts/{uart.js => bad_uart.js} | 3 +- .../js_app/examples/apps/Scripts/badusb.js | 29 -- .../js_app/examples/apps/Scripts/ffi.js | 14 - .../examples/apps/Scripts/{req.js => load.js} | 0 .../apps/Scripts/{api.js => load_api.js} | 0 .../examples/apps/Scripts/print_test.js | 7 - .../examples/apps/Scripts/require_api.js | 4 - 8 files changed, 1 insertion(+), 406 deletions(-) delete mode 100644 applications/system/js_app/examples/apps/Scripts/about.js rename applications/system/js_app/examples/apps/Scripts/{uart.js => bad_uart.js} (82%) delete mode 100644 applications/system/js_app/examples/apps/Scripts/badusb.js delete mode 100644 applications/system/js_app/examples/apps/Scripts/ffi.js rename applications/system/js_app/examples/apps/Scripts/{req.js => load.js} (100%) rename applications/system/js_app/examples/apps/Scripts/{api.js => load_api.js} (100%) delete mode 100644 applications/system/js_app/examples/apps/Scripts/print_test.js delete mode 100644 applications/system/js_app/examples/apps/Scripts/require_api.js diff --git a/applications/system/js_app/examples/apps/Scripts/about.js b/applications/system/js_app/examples/apps/Scripts/about.js deleted file mode 100644 index b1725b51944..00000000000 --- a/applications/system/js_app/examples/apps/Scripts/about.js +++ /dev/null @@ -1,350 +0,0 @@ - -let RECORD = ({ - open: function (api) { - let ret = Object.create(this.prototype); - ret.name = api.name; - ret.ptr = ret.api.open(ret.name); - return ret; - }, - prototype: { - api: { - open: ffi("void* furi_record_open(char *)"), - close: ffi("void furi_record_close(char *)"), - }, - close: function () { - this.api.close(this.name); - } - } -}); - -let GUI = ({ - name: "gui", - align: { - left: 0, - right: 1, - top: 2, - bottom: 3, - center: 4, - }, -}); - -let DIALOGS = ({ - name: "dialogs", - show: function (record, message) { - return ffi("int dialog_message_show(void*, void*)")(record.ptr, message.ptr); - }, -}); - -let DIALOG_MESSAGE = ({ - alloc: function () { - let ret = Object.create(this.prototype); - ret.ptr = ret.api.alloc(); - return ret; - }, - button_type: { - back: 0, - left: 1, - center: 2, - right: 3, - }, - prototype: { - api: { - alloc: ffi("void* dialog_message_alloc()"), - free: ffi("void dialog_message_free(void*)"), - set_header: ffi("void dialog_message_set_header(void*, char*, int, int, int, int)"), - set_text: ffi("void dialog_message_set_text(void*, char*, int, int, int, int)"), - set_buttons: ffi("void dialog_message_set_buttons(void*, char*, char*, char*)"), - set_icon: ffi("void dialog_message_set_icon(void*, void*, int, int)"), - }, - free: function () { - this.api.free(this.ptr); - }, - set_header: function (text, x, y, align_x, align_y) { - this.api.set_header(this.ptr, text, x, y, align_x, align_y); - }, - set_text: function (text, x, y, align_x, align_y) { - this.api.set_text(this.ptr, text, x, y, align_x, align_y); - }, - set_buttons: function (text_left, text_center, text_right) { - this.api.set_buttons(this.ptr, text_left, text_center, text_right); - }, - set_icon: function (icon, x, y) { - this.api.set_icon(this.ptr, icon, x, y); - }, - set_icon_from_fw: function (icon_name, x, y) { - this.api.set_icon(this.ptr, ffi_address(icon_name), x, y); - }, - } -}); - -let VIEW_DISPATCHER = ({ - alloc: function (record, type) { - let ret = Object.create(this.prototype); - ret.ptr = ret.api.alloc(); - ret.api.attach_to_gui(ret.ptr, record.ptr, type); - return ret; - }, - type: { - desktop: 0, - window: 1, - fullscreen: 2, - }, - prototype: { - api: { - alloc: ffi("void* view_dispatcher_alloc()"), - free: ffi("void view_dispatcher_free(void*)"), - add_view: ffi("void view_dispatcher_add_view(void*, int, void*)"), - remove_view: ffi("void view_dispatcher_remove_view(void*, int)"), - attach_to_gui: ffi("void view_dispatcher_attach_to_gui(void*, void*, int)"), - switch_to_view: ffi("void view_dispatcher_switch_to_view(void*, int)"), - }, - free: function () { - this.api.free(this.ptr); - }, - add_view: function (view) { - this.api.add_view(this.ptr, view.index, view.get_view()); - }, - remove_view: function (view) { - this.api.remove_view(this.ptr, view.index); - }, - switch_to_view: function (view) { - this.api.switch_to_view(this.ptr, view.index); - }, - } -}); - -let EMPTY_SCREEN = ({ - alloc: function (index) { - let ret = Object.create(this.prototype); - ret.ptr = ret.api.alloc(); - ret.index = index; - return ret; - }, - prototype: { - api: { - alloc: ffi("void* empty_screen_alloc()"), - free: ffi("void empty_screen_free(void*)"), - get_view: ffi("void* empty_screen_get_view(void*)"), - }, - free: function () { - this.api.free(this.ptr); - }, - get_view: function () { - return this.api.get_view(this.ptr); - }, - } -}); - -let STD = ({ - malloc: ffi('void *malloc(int)'), - free: ffi('void free(void*)'), -}); - -let C_STR = ({ - create: function (str) { - let len = str.length; - let ret = Object.create(this.prototype); - ret.mem = STD.malloc(len + 1); - ret.text = mkstr(ret.mem, len); - for (let i = 0; i < len; i++) { - ret.mem[i] = str.at(i); - } - return ret; - }, - prototype: { - free: function () { - STD.free(this.mem); - }, - set: function (str) { - let len = str.length; - STD.free(this.mem); - this.mem = STD.malloc(len + 1); - this.text = mkstr(this.mem, len); - for (let i = 0; i < len; i++) { - this.mem[i] = str.at(i); - } - } - } -}); - -let FURI_HAL_VERSION = ({ - get_name: ffi("char *furi_hal_version_get_name_ptr()"), - get_hw_version: ffi("int furi_hal_version_get_hw_version()"), - get_hw_target: ffi("int furi_hal_version_get_hw_target()"), - get_hw_body: ffi("int furi_hal_version_get_hw_body()"), - get_hw_connect: ffi("int furi_hal_version_get_hw_connect()"), - get_hw_region_name: ffi("char* furi_hal_version_get_hw_region_name()"), - get_region_name: ffi("char* furi_hal_region_get_name()"), - version_uid: ffi("char* furi_hal_version_uid()"), - version_uid_size: ffi("int furi_hal_version_uid_size()"), - - get_firmware_version: ffi("void* furi_hal_version_get_firmware_version()"), - get_version: ffi("char* version_get_version(void*)"), - get_builddate: ffi("char* version_get_builddate(void*)"), - get_dirty_flag: ffi("int version_get_dirty_flag(void*)"), - get_githash: ffi("char* version_get_githash(void*)"), - get_gitbranchnum: ffi("char* version_get_gitbranchnum(void*)"), - get_target: ffi("int version_get_target(void*)"), - get_gitbranch: ffi("char* version_get_gitbranch(void*)"), -}); - -// init c-string -let text_back = C_STR.create("Back"); -let text_next = C_STR.create("Next"); -let text_header = C_STR.create(""); -let text_body = C_STR.create(""); - -// open records -let dialogs = RECORD.open(DIALOGS); -let gui = RECORD.open(GUI); - -// create objects -let message = DIALOG_MESSAGE.alloc(); -let view_dispatcher = VIEW_DISPATCHER.alloc(gui, VIEW_DISPATCHER.type.fullscreen); -let empty_screen = EMPTY_SCREEN.alloc(0); - -// configure objects -view_dispatcher.add_view(empty_screen); -view_dispatcher.switch_to_view(empty_screen); - -// init result -let screen_index = 0; -let screen_result = DIALOG_MESSAGE.button_type.right; - -let screens = [ - function () { - text_header.set("Product: Flipper Zero\nModel: FZ.1\n"); - text_body.set("FCC ID: 2A2V6-FZ\nIC: 27624-FZ"); - message.set_header(text_header.text, 0, 0, GUI.align.left, GUI.align.top); - message.set_text(text_body.text, 0, 26, GUI.align.left, GUI.align.top); - let result = DIALOGS.show(dialogs, message); - message.set_header(null, 0, 0, GUI.align.left, GUI.align.top); - message.set_text(null, 0, 0, GUI.align.left, GUI.align.top); - return result; - }, - function () { - text_body.set("Flipper Devices Inc\nSuite B #551, 2803\nPhiladelphia Pike, Claymont\nDE, USA 19703\n"); - message.set_text(text_body.text, 0, 0, GUI.align.left, GUI.align.top); - let result = DIALOGS.show(dialogs, message); - message.set_text(null, 0, 0, GUI.align.left, GUI.align.top); - return result; - }, - function () { - text_body.set("For all compliance\ncertificates please visit:\nwww.flipp.dev/compliance"); - message.set_text(text_body.text, 0, 0, GUI.align.left, GUI.align.top); - let result = DIALOGS.show(dialogs, message); - message.set_text(null, 0, 0, GUI.align.left, GUI.align.top); - return result; - }, - function () { - message.set_icon_from_fw("I_Certification1_103x56", 13, 0); - let result = DIALOGS.show(dialogs, message); - message.set_icon(null, 0, 0); - return result; - }, - function () { - message.set_icon_from_fw("I_Certification2_46x33", 15, 10); - let result = DIALOGS.show(dialogs, message); - message.set_icon(null, 0, 0); - return result; - }, - function () { - let my_name = FURI_HAL_VERSION.get_name(); - if (my_name === null) { - my_name = "Unknown"; - } - let str = to_string(FURI_HAL_VERSION.get_hw_version()) + "."; - str += "F" + to_string(FURI_HAL_VERSION.get_hw_target()); - str += "B" + to_string(FURI_HAL_VERSION.get_hw_body()); - str += "C" + to_string(FURI_HAL_VERSION.get_hw_connect()); - str += " " + FURI_HAL_VERSION.get_hw_region_name() + ":" + FURI_HAL_VERSION.get_region_name(); - str += " " + my_name + "\n"; - str += "Serial Number:\n"; - - - let uid = FURI_HAL_VERSION.version_uid(); - let uid_size = FURI_HAL_VERSION.version_uid_size(); - for (let i = 0; i < uid_size; i++) { - str += to_hex_string(uid.at(i)); - } - - text_header.set("HW Version Info:"); - text_body.set(str); - message.set_header(text_header.text, 0, 0, GUI.align.left, GUI.align.top); - message.set_text(text_body.text, 0, 13, GUI.align.left, GUI.align.top); - let result = DIALOGS.show(dialogs, message); - message.set_header(null, 0, 0, GUI.align.left, GUI.align.top); - message.set_text(null, 0, 0, GUI.align.left, GUI.align.top); - return result; - }, - function () { - let ver = FURI_HAL_VERSION.get_firmware_version(); - let str = ""; - - if (ver === null) { - str = "No info\n"; - } else { - str = FURI_HAL_VERSION.get_version(ver) + " ["; - str += FURI_HAL_VERSION.get_builddate(ver) + "]\n"; - if (FURI_HAL_VERSION.get_dirty_flag(ver) > 0) { - str += "[!]"; - } - str += FURI_HAL_VERSION.get_githash(ver) + " ["; - str += FURI_HAL_VERSION.get_gitbranchnum(ver) + "]\n"; - - str += "[" + to_string(FURI_HAL_VERSION.get_target(ver)) + "] "; - str += FURI_HAL_VERSION.get_gitbranch(ver); - } - - text_header.set("FW Version Info:"); - text_body.set(str); - message.set_header(text_header.text, 0, 0, GUI.align.left, GUI.align.top); - message.set_text(text_body.text, 0, 13, GUI.align.left, GUI.align.top); - let result = DIALOGS.show(dialogs, message); - message.set_header(null, 0, 0, GUI.align.left, GUI.align.top); - message.set_text(null, 0, 0, GUI.align.left, GUI.align.top); - return result; - }, -]; - -// app body -while (screen_result !== DIALOG_MESSAGE.button_type.back) { - if (screen_index >= screens.length - 1) { - message.set_buttons(text_back.text, null, null); - } else { - message.set_buttons(text_back.text, null, text_next.text); - } - - screen_result = screens[screen_index](); - - if (screen_result === DIALOG_MESSAGE.button_type.left) { - if (screen_index <= 0) { - break; - } else { - screen_index--; - } - } else if (screen_result === DIALOG_MESSAGE.button_type.right) { - if (screen_index < screens.length) { - screen_index++; - } - } else if (screen_result === DIALOG_MESSAGE.button_type.back) { - break; - } -} - -// free c-string -message.free(); - -// free objects -view_dispatcher.remove_view(empty_screen); -view_dispatcher.free(); -empty_screen.free(); - -text_back.free(); -text_next.free(); -text_header.free(); -text_body.free(); - -// close records -dialogs.close(); -gui.close(); \ No newline at end of file diff --git a/applications/system/js_app/examples/apps/Scripts/uart.js b/applications/system/js_app/examples/apps/Scripts/bad_uart.js similarity index 82% rename from applications/system/js_app/examples/apps/Scripts/uart.js rename to applications/system/js_app/examples/apps/Scripts/bad_uart.js index 4c88fd322a8..8166c61f551 100644 --- a/applications/system/js_app/examples/apps/Scripts/uart.js +++ b/applications/system/js_app/examples/apps/Scripts/bad_uart.js @@ -3,8 +3,7 @@ uart.setup(115200); // uart.write("\n"); uart.write([0x0a]); -// let console_resp = uart.expect("# ", 1000); -let console_resp = uart.expect([0x23, 0x20], 1000); +let console_resp = uart.expect("# ", 1000); if (console_resp === undefined) { print("No CLI response"); } else { diff --git a/applications/system/js_app/examples/apps/Scripts/badusb.js b/applications/system/js_app/examples/apps/Scripts/badusb.js deleted file mode 100644 index 462fcc0b32d..00000000000 --- a/applications/system/js_app/examples/apps/Scripts/badusb.js +++ /dev/null @@ -1,29 +0,0 @@ -let badusb = require("badusb"); - -badusb.setup({ vid: 0xAAAA, pid: 0xBBBB, mfr_name: "manufacturer", prod_name: "product" }); -delay(1000); - -if (badusb.isConnected()) { - print("Connected"); - badusb.press("a"); - badusb.press("A"); - badusb.press(9); - badusb.press(0x0f); - badusb.press("SHIFT", 0x0C); - badusb.press("CTRL", "a"); - delay(500); - badusb.press("RIGHT"); - badusb.hold("a"); - delay(2000); - badusb.release("a"); - badusb.hold("SHIFT", "b"); - delay(2000); - badusb.release(); - badusb.press("ENTER"); - badusb.print("Hello") - badusb.println("abcd") - badusb.print("12345", 1000); - delay(1000); -} else { - print("USB not connected"); -} diff --git a/applications/system/js_app/examples/apps/Scripts/ffi.js b/applications/system/js_app/examples/apps/Scripts/ffi.js deleted file mode 100644 index d9612be85c0..00000000000 --- a/applications/system/js_app/examples/apps/Scripts/ffi.js +++ /dev/null @@ -1,14 +0,0 @@ -let record = ({ - open: ffi("void* furi_record_open(char *)"), - close: ffi("void furi_record_close(char *)"), -}); - -let notification = ({ - name: "notification", - message: ffi("void notification_message(void*, void*)"), - success: ffi_address("sequence_success"), -}); - -let notification_app = record.open(notification.name); -notification.message(notification_app, notification.success); -record.close(notification.name); \ No newline at end of file diff --git a/applications/system/js_app/examples/apps/Scripts/req.js b/applications/system/js_app/examples/apps/Scripts/load.js similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/req.js rename to applications/system/js_app/examples/apps/Scripts/load.js diff --git a/applications/system/js_app/examples/apps/Scripts/api.js b/applications/system/js_app/examples/apps/Scripts/load_api.js similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/api.js rename to applications/system/js_app/examples/apps/Scripts/load_api.js diff --git a/applications/system/js_app/examples/apps/Scripts/print_test.js b/applications/system/js_app/examples/apps/Scripts/print_test.js deleted file mode 100644 index 1fc4949bc59..00000000000 --- a/applications/system/js_app/examples/apps/Scripts/print_test.js +++ /dev/null @@ -1,7 +0,0 @@ -print("aaa", 1234, "0x" + to_hex_string(0xeba)); -print("q¢w€e𐍈rйt"); -print("qwertyuiopasdfghjklzxcvbnm1234567890-=poiuytrewqlkjhgfdsamnbvcxz=-0987654321"); -print("Line 1\nLine 2"); -// qwertyuiop -// print("\n"); -// print("a"); \ No newline at end of file diff --git a/applications/system/js_app/examples/apps/Scripts/require_api.js b/applications/system/js_app/examples/apps/Scripts/require_api.js deleted file mode 100644 index b4b13483e09..00000000000 --- a/applications/system/js_app/examples/apps/Scripts/require_api.js +++ /dev/null @@ -1,4 +0,0 @@ -let flipper = require("flipper"); -print("Name:", flipper.getName()); -print("Model:", flipper.getModel()); -print("Battery level:", flipper.getBatteryCharge()); \ No newline at end of file From 05611bd0c25b931c371de88bf50510b2b5c52461 Mon Sep 17 00:00:00 2001 From: nminaylov Date: Tue, 12 Dec 2023 16:31:32 +0300 Subject: [PATCH 26/31] mbedtls version fix --- lib/mbedtls | 2 +- targets/f18/api_symbols.csv | 5 ++--- targets/f18/target.json | 3 --- targets/f7/api_symbols.csv | 7 +++++-- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/lib/mbedtls b/lib/mbedtls index d65aeb37349..edb8fec9882 160000 --- a/lib/mbedtls +++ b/lib/mbedtls @@ -1 +1 @@ -Subproject commit d65aeb37349ad1a50e0f6c9b694d4b5290d60e49 +Subproject commit edb8fec9882084344a314368ac7fd957a187519c diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 529923c3eac..ffdf4bad04b 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -83,7 +83,7 @@ Header,+,lib/mbedtls/include/mbedtls/ecp.h,, Header,+,lib/mbedtls/include/mbedtls/md.h,, Header,+,lib/mbedtls/include/mbedtls/md5.h,, Header,+,lib/mbedtls/include/mbedtls/sha1.h,, -Header,+,lib/micro-ecc/uECC.h,, +Header,+,lib/mbedtls/include/mbedtls/sha256.h,, Header,+,lib/mjs/mjs_array_public.h,, Header,+,lib/mjs/mjs_core_public.h,, Header,+,lib/mjs/mjs_exec_public.h,, @@ -91,7 +91,6 @@ Header,+,lib/mjs/mjs_object_public.h,, Header,+,lib/mjs/mjs_primitive_public.h,, Header,+,lib/mjs/mjs_string_public.h,, Header,+,lib/mjs/mjs_util_public.h,, -Header,+,lib/mbedtls/include/mbedtls/sha256.h,, Header,+,lib/mlib/m-algo.h,, Header,+,lib/mlib/m-array.h,, Header,+,lib/mlib/m-bptree.h,, @@ -1933,8 +1932,8 @@ Function,+,mjs_print_error,void,"mjs*, FILE*, const char*, int" Function,+,mjs_return,void,"mjs*, mjs_val_t" Function,+,mjs_set,mjs_err_t,"mjs*, mjs_val_t, const char*, size_t, mjs_val_t" Function,+,mjs_set_errorf,mjs_err_t,"mjs*, mjs_err_t, const char*, ..." -Function,+,mjs_set_ffi_resolver,void,"mjs*, mjs_ffi_resolver_t*, void*" Function,+,mjs_set_exec_flags_poller,void,"mjs*, mjs_flags_poller_t" +Function,+,mjs_set_ffi_resolver,void,"mjs*, mjs_ffi_resolver_t*, void*" Function,-,mjs_set_generate_jsc,void,"mjs*, int" Function,+,mjs_set_v,mjs_err_t,"mjs*, mjs_val_t, mjs_val_t, mjs_val_t" Function,+,mjs_sprintf,void,"mjs_val_t, mjs*, char*, size_t" diff --git a/targets/f18/target.json b/targets/f18/target.json index b10aca70458..1e2b4ee943b 100644 --- a/targets/f18/target.json +++ b/targets/f18/target.json @@ -27,12 +27,9 @@ "assets", "one_wire", "music_worker", -<<<<<<< HEAD "mjs", "misc", -======= "mbedtls", ->>>>>>> dev "flipper_application", "toolbox", "u8g2", diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 5ea9daf6b5a..8db08ad29ae 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,49.3,, +Version,+,49.2,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -2104,7 +2104,9 @@ Function,-,mbedtls_ecdsa_init,void,mbedtls_ecdsa_context* Function,-,mbedtls_ecdsa_read_signature,int,"mbedtls_ecdsa_context*, const unsigned char*, size_t, const unsigned char*, size_t" Function,-,mbedtls_ecdsa_read_signature_restartable,int,"mbedtls_ecdsa_context*, const unsigned char*, size_t, const unsigned char*, size_t, mbedtls_ecdsa_restart_ctx*" Function,-,mbedtls_ecdsa_sign,int,"mbedtls_ecp_group*, mbedtls_mpi*, mbedtls_mpi*, const mbedtls_mpi*, const unsigned char*, size_t, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_ecdsa_sign_restartable,int,"mbedtls_ecp_group*, mbedtls_mpi*, mbedtls_mpi*, const mbedtls_mpi*, const unsigned char*, size_t, int (*)(void*, unsigned char*, size_t), void*, int (*)(void*, unsigned char*, size_t), void*, mbedtls_ecdsa_restart_ctx*" Function,-,mbedtls_ecdsa_verify,int,"mbedtls_ecp_group*, const unsigned char*, size_t, const mbedtls_ecp_point*, const mbedtls_mpi*, const mbedtls_mpi*" +Function,-,mbedtls_ecdsa_verify_restartable,int,"mbedtls_ecp_group*, const unsigned char*, size_t, const mbedtls_ecp_point*, const mbedtls_mpi*, const mbedtls_mpi*, mbedtls_ecdsa_restart_ctx*" Function,-,mbedtls_ecdsa_write_signature,int,"mbedtls_ecdsa_context*, mbedtls_md_type_t, const unsigned char*, size_t, unsigned char*, size_t, size_t*, int (*)(void*, unsigned char*, size_t), void*" Function,-,mbedtls_ecdsa_write_signature_restartable,int,"mbedtls_ecdsa_context*, mbedtls_md_type_t, const unsigned char*, size_t, unsigned char*, size_t, size_t*, int (*)(void*, unsigned char*, size_t), void*, mbedtls_ecdsa_restart_ctx*" Function,-,mbedtls_ecp_check_privkey,int,"const mbedtls_ecp_group*, const mbedtls_mpi*" @@ -2115,6 +2117,7 @@ Function,-,mbedtls_ecp_curve_info_from_grp_id,const mbedtls_ecp_curve_info*,mbed Function,-,mbedtls_ecp_curve_info_from_name,const mbedtls_ecp_curve_info*,const char* Function,-,mbedtls_ecp_curve_info_from_tls_id,const mbedtls_ecp_curve_info*,uint16_t Function,-,mbedtls_ecp_curve_list,const mbedtls_ecp_curve_info*, +Function,-,mbedtls_ecp_export,int,"const mbedtls_ecp_keypair*, mbedtls_ecp_group*, mbedtls_mpi*, mbedtls_ecp_point*" Function,-,mbedtls_ecp_gen_key,int,"mbedtls_ecp_group_id, mbedtls_ecp_keypair*, int (*)(void*, unsigned char*, size_t), void*" Function,-,mbedtls_ecp_gen_keypair,int,"mbedtls_ecp_group*, mbedtls_mpi*, mbedtls_ecp_point*, int (*)(void*, unsigned char*, size_t), void*" Function,-,mbedtls_ecp_gen_keypair_base,int,"mbedtls_ecp_group*, const mbedtls_ecp_point*, mbedtls_mpi*, mbedtls_ecp_point*, int (*)(void*, unsigned char*, size_t), void*" @@ -2168,11 +2171,11 @@ Function,-,mbedtls_md_hmac_finish,int,"mbedtls_md_context_t*, unsigned char*" Function,-,mbedtls_md_hmac_reset,int,mbedtls_md_context_t* Function,-,mbedtls_md_hmac_starts,int,"mbedtls_md_context_t*, const unsigned char*, size_t" Function,-,mbedtls_md_hmac_update,int,"mbedtls_md_context_t*, const unsigned char*, size_t" +Function,-,mbedtls_md_info_from_ctx,const mbedtls_md_info_t*,const mbedtls_md_context_t* Function,-,mbedtls_md_info_from_string,const mbedtls_md_info_t*,const char* Function,-,mbedtls_md_info_from_type,const mbedtls_md_info_t*,mbedtls_md_type_t Function,-,mbedtls_md_init,void,mbedtls_md_context_t* Function,-,mbedtls_md_list,const int*, -Function,-,mbedtls_md_process,int,"mbedtls_md_context_t*, const unsigned char*" Function,-,mbedtls_md_setup,int,"mbedtls_md_context_t*, const mbedtls_md_info_t*, int" Function,-,mbedtls_md_starts,int,mbedtls_md_context_t* Function,-,mbedtls_md_update,int,"mbedtls_md_context_t*, const unsigned char*, size_t" From 102e891c63ed01debe3a33ff9b991d9db5c622da Mon Sep 17 00:00:00 2001 From: nminaylov Date: Tue, 12 Dec 2023 16:37:52 +0300 Subject: [PATCH 27/31] Unused lib cleanup --- targets/f18/target.json | 1 - targets/f7/target.json | 1 - 2 files changed, 2 deletions(-) diff --git a/targets/f18/target.json b/targets/f18/target.json index 1e2b4ee943b..7345fceec66 100644 --- a/targets/f18/target.json +++ b/targets/f18/target.json @@ -28,7 +28,6 @@ "one_wire", "music_worker", "mjs", - "misc", "mbedtls", "flipper_application", "toolbox", diff --git a/targets/f7/target.json b/targets/f7/target.json index 49e2a8aeff0..14090026947 100644 --- a/targets/f7/target.json +++ b/targets/f7/target.json @@ -39,7 +39,6 @@ "ibutton", "music_worker", "mjs", - "misc", "mbedtls", "lfrfid", "flipper_application", From 98ec2ad4d48220edc79a88399da9bc87f297971e Mon Sep 17 00:00:00 2001 From: nminaylov Date: Tue, 12 Dec 2023 16:56:26 +0300 Subject: [PATCH 28/31] Making PVS happy & TODOs cleanup --- .pvsoptions | 2 +- applications/system/js_app/js_app.c | 2 +- applications/system/js_app/js_modules.c | 8 ++++---- applications/system/js_app/js_thread.c | 2 +- applications/system/js_app/views/console_view.c | 1 - 5 files changed, 7 insertions(+), 8 deletions(-) diff --git a/.pvsoptions b/.pvsoptions index 3337d7eb5c2..8606eef1540 100644 --- a/.pvsoptions +++ b/.pvsoptions @@ -1 +1 @@ ---ignore-ccache -C gccarm --rules-config .pvsconfig -e lib/cmsis_core -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/littlefs -e lib/mbedtls -e lib/microtar -e lib/mlib -e lib/stm32wb_cmsis -e lib/stm32wb_copro -e lib/stm32wb_hal -e lib/u8g2 -e lib/nanopb -e */arm-none-eabi/* +--ignore-ccache -C gccarm --rules-config .pvsconfig -e lib/cmsis_core -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/littlefs -e lib/mbedtls -e lib/microtar -e lib/mlib -e lib/stm32wb_cmsis -e lib/stm32wb_copro -e lib/stm32wb_hal -e lib/u8g2 -e lib/nanopb -e lib/mjs -e */arm-none-eabi/* diff --git a/applications/system/js_app/js_app.c b/applications/system/js_app/js_app.c index 58006ad5795..e99cc32498f 100644 --- a/applications/system/js_app/js_app.c +++ b/applications/system/js_app/js_app.c @@ -128,4 +128,4 @@ int32_t js_app(void* arg) { js_app_free(app); return 0; -} \ No newline at end of file +} //-V773 \ No newline at end of file diff --git a/applications/system/js_app/js_modules.c b/applications/system/js_app/js_modules.c index 64373339dbb..0e2d6bf25ff 100644 --- a/applications/system/js_app/js_modules.c +++ b/applications/system/js_app/js_modules.c @@ -52,7 +52,7 @@ mjs_val_t js_module_require(JsModules* modules, const char* name, size_t name_le FuriString* module_name = furi_string_alloc_set_str(name); // Check if module is already installed JsModuleData* module_inst = JsModuleDict_get(modules->module_dict, module_name); - if(module_inst) { + if(module_inst) { //-V547 furi_string_free(module_name); mjs_prepend_errorf( modules->mjs, MJS_BAD_ARGS_ERROR, "\"%s\" module is already installed", name); @@ -61,7 +61,7 @@ mjs_val_t js_module_require(JsModules* modules, const char* name, size_t name_le bool module_found = false; // Check built-in modules - for(size_t i = 0; i < COUNT_OF(modules_builtin); i++) { + for(size_t i = 0; i < COUNT_OF(modules_builtin); i++) { //-V1008 size_t name_compare_len = strlen(modules_builtin[i].name); if(name_compare_len != name_len) { @@ -111,12 +111,12 @@ mjs_val_t js_module_require(JsModules* modules, const char* name, size_t name_le if(module_found) { module_inst = JsModuleDict_get(modules->module_dict, module_name); furi_assert(module_inst); - if(module_inst->create) { + if(module_inst->create) { //-V779 module_inst->context = module_inst->create(modules->mjs, &module_object); } } - if(module_object == MJS_UNDEFINED) { + if(module_object == MJS_UNDEFINED) { //-V547 mjs_prepend_errorf(modules->mjs, MJS_BAD_ARGS_ERROR, "\"%s\" module load fail", name); } diff --git a/applications/system/js_app/js_thread.c b/applications/system/js_app/js_thread.c index ffd9fc98403..4797bb8aa1b 100644 --- a/applications/system/js_app/js_thread.c +++ b/applications/system/js_app/js_thread.c @@ -301,7 +301,7 @@ static int32_t js_thread(void* arg) { } JsThread* js_thread_run(const char* script_path, JsThreadCallback callback, void* context) { - JsThread* worker = malloc(sizeof(JsThread)); + JsThread* worker = malloc(sizeof(JsThread)); //-V799 worker->path = furi_string_alloc_set(script_path); worker->thread = furi_thread_alloc_ex("JsThread", 8 * 1024, js_thread, worker); worker->app_callback = callback; diff --git a/applications/system/js_app/views/console_view.c b/applications/system/js_app/views/console_view.c index c2b92c670b2..b87e5352847 100644 --- a/applications/system/js_app/views/console_view.c +++ b/applications/system/js_app/views/console_view.c @@ -34,7 +34,6 @@ static void console_view_draw_callback(Canvas* canvas, void* _model) { static bool console_view_input_callback(InputEvent* event, void* context) { UNUSED(event); UNUSED(context); - // TODO: -> сallback -> queue -> mjs thread return false; } From 453190914d31c9f51b1cd44d98eb6433bf365b52 Mon Sep 17 00:00:00 2001 From: nminaylov Date: Tue, 12 Dec 2023 17:23:21 +0300 Subject: [PATCH 29/31] TODOs cleanup #2 --- lib/mjs/common/frozen/frozen.c | 2 +- lib/mjs/mjs_core.c | 3 --- lib/mjs/mjs_json.c | 4 ++-- targets/f18/api_symbols.csv | 2 +- targets/f7/api_symbols.csv | 2 +- 5 files changed, 5 insertions(+), 8 deletions(-) diff --git a/lib/mjs/common/frozen/frozen.c b/lib/mjs/common/frozen/frozen.c index 40d973812b7..923e6a556b4 100644 --- a/lib/mjs/common/frozen/frozen.c +++ b/lib/mjs/common/frozen/frozen.c @@ -883,7 +883,7 @@ int json_unescape(const char* src, int slen, char* dst, int dlen) { if(dst < dend) *dst = hexdec(src + 3); src += 4; } else { - /* Complex \uXXXX escapes drag utf8 lib... Do it at some stage */ + /* Complex \uXX XX escapes drag utf8 lib... Do it at some stage */ return JSON_STRING_INVALID; } } else if((p = (char*)strchr(esc1, *src)) != NULL) { diff --git a/lib/mjs/mjs_core.c b/lib/mjs/mjs_core.c index 89cc5c0e109..5bc7a010029 100644 --- a/lib/mjs/mjs_core.c +++ b/lib/mjs/mjs_core.c @@ -181,7 +181,6 @@ void mjs_print_error(struct mjs* mjs, FILE* fp, const char* msg, int print_stack (void)fp; if(print_stack_trace && mjs->stack_trace != NULL) { - // TODO: mjs fix // fprintf(fp, "%s", mjs->stack_trace); } @@ -189,7 +188,6 @@ void mjs_print_error(struct mjs* mjs, FILE* fp, const char* msg, int print_stack msg = "MJS error"; } - // TODO: mjs fix // fprintf(fp, "%s: %s\n", msg, mjs_strerror(mjs, mjs->error)); } @@ -278,7 +276,6 @@ static void mjs_append_stack_trace_line(struct mjs* mjs, size_t offset) { char* new_line = NULL; const char* fmt = "at %s:%d\n"; if(filename == NULL) { - // TODO: mjs fix // fprintf( // stderr, // "ERROR during stack trace generation: wrong bcode offset %d\n", diff --git a/lib/mjs/mjs_json.c b/lib/mjs/mjs_json.c index a5045cb5f40..234fbb347ca 100644 --- a/lib/mjs/mjs_json.c +++ b/lib/mjs/mjs_json.c @@ -87,7 +87,7 @@ static int snquote(char* buf, size_t size, const char* s, size_t len) { if(buf < limit) *buf++ = specials[*s - '\b']; continue; } else if((unsigned char)*s < '\b' || (*s > '\r' && *s < ' ')) { - i += 6 /* \uXXXX */; + i += 6 /* \uXX XX */; if(buf < limit) *buf++ = '\\'; buf = append_hex(buf, limit, (uint8_t)*s); continue; @@ -150,7 +150,7 @@ MJS_PRIVATE mjs_err_t to_json_or_debug( case MJS_TYPE_FOREIGN: /* For those types, regular `mjs_to_string()` works */ { - /* TODO: refactor: mjs_to_string allocates memory every time */ + /* refactor: mjs_to_string allocates memory every time */ char* p = NULL; int need_free = 0; rcode = mjs_to_string(mjs, &v, &p, &len, &need_free); diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index ffdf4bad04b..45cfe01fee8 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1928,7 +1928,7 @@ Function,+,mjs_nargs,int,mjs* Function,+,mjs_next,mjs_val_t,"mjs*, mjs_val_t, mjs_val_t*" Function,+,mjs_own,void,"mjs*, mjs_val_t*" Function,+,mjs_prepend_errorf,mjs_err_t,"mjs*, mjs_err_t, const char*, ..." -Function,+,mjs_print_error,void,"mjs*, FILE*, const char*, int" +Function,-,mjs_print_error,void,"mjs*, FILE*, const char*, int" Function,+,mjs_return,void,"mjs*, mjs_val_t" Function,+,mjs_set,mjs_err_t,"mjs*, mjs_val_t, const char*, size_t, mjs_val_t" Function,+,mjs_set_errorf,mjs_err_t,"mjs*, mjs_err_t, const char*, ..." diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 8db08ad29ae..35a6dfdc016 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -2449,7 +2449,7 @@ Function,+,mjs_nargs,int,mjs* Function,+,mjs_next,mjs_val_t,"mjs*, mjs_val_t, mjs_val_t*" Function,+,mjs_own,void,"mjs*, mjs_val_t*" Function,+,mjs_prepend_errorf,mjs_err_t,"mjs*, mjs_err_t, const char*, ..." -Function,+,mjs_print_error,void,"mjs*, FILE*, const char*, int" +Function,-,mjs_print_error,void,"mjs*, FILE*, const char*, int" Function,+,mjs_return,void,"mjs*, mjs_val_t" Function,+,mjs_set,mjs_err_t,"mjs*, mjs_val_t, const char*, size_t, mjs_val_t" Function,+,mjs_set_errorf,mjs_err_t,"mjs*, mjs_err_t, const char*, ..." From 647deccd0d0eb158f328b7c2bc271559cce13a56 Mon Sep 17 00:00:00 2001 From: nminaylov Date: Thu, 28 Dec 2023 21:45:05 +0300 Subject: [PATCH 30/31] MJS: initial typed arrays support --- .../examples/apps/Scripts/array_buf_test.js | 8 + .../js_app/examples/apps/Scripts/uart_echo.js | 11 + applications/system/js_app/js_thread.c | 20 +- applications/system/js_app/js_thread_i.h | 3 +- .../system/js_app/modules/js_badusb.c | 14 +- .../system/js_app/modules/js_dialog.c | 4 +- .../system/js_app/modules/js_flipper.c | 6 +- .../system/js_app/modules/js_notification.c | 6 +- applications/system/js_app/modules/js_uart.c | 74 +++- lib/mjs/SConscript | 1 + lib/mjs/common/platforms/platform_flipper.c | 8 + lib/mjs/mjs_array_buf.c | 385 ++++++++++++++++++ lib/mjs/mjs_array_buf.h | 27 ++ lib/mjs/mjs_array_buf_public.h | 37 ++ lib/mjs/mjs_builtin.c | 3 + lib/mjs/mjs_core.c | 6 + lib/mjs/mjs_core.h | 1 + lib/mjs/mjs_core_public.h | 5 + lib/mjs/mjs_exec.c | 59 +++ lib/mjs/mjs_gc.c | 6 +- lib/mjs/mjs_json.c | 4 + lib/mjs/mjs_object.c | 13 +- lib/mjs/mjs_object_public.h | 5 + lib/mjs/mjs_primitive_public.h | 2 + lib/mjs/mjs_util.c | 8 + targets/f18/api_symbols.csv | 10 +- targets/f7/api_symbols.csv | 10 +- 27 files changed, 695 insertions(+), 41 deletions(-) create mode 100644 applications/system/js_app/examples/apps/Scripts/array_buf_test.js create mode 100644 applications/system/js_app/examples/apps/Scripts/uart_echo.js create mode 100644 lib/mjs/mjs_array_buf.c create mode 100644 lib/mjs/mjs_array_buf.h create mode 100644 lib/mjs/mjs_array_buf_public.h diff --git a/applications/system/js_app/examples/apps/Scripts/array_buf_test.js b/applications/system/js_app/examples/apps/Scripts/array_buf_test.js new file mode 100644 index 00000000000..ddb572ee783 --- /dev/null +++ b/applications/system/js_app/examples/apps/Scripts/array_buf_test.js @@ -0,0 +1,8 @@ +let arr_1 = Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); +print("len =", arr_1.buffer.byteLength); + +let arr_2 = Uint8Array(arr_1.buffer.slice(2, 6)); +print("slice len =", arr_2.buffer.byteLength); +for (let i = 0; i < arr_2.buffer.byteLength; i++) { + print(arr_2[i]); +} diff --git a/applications/system/js_app/examples/apps/Scripts/uart_echo.js b/applications/system/js_app/examples/apps/Scripts/uart_echo.js new file mode 100644 index 00000000000..2f3957fd19e --- /dev/null +++ b/applications/system/js_app/examples/apps/Scripts/uart_echo.js @@ -0,0 +1,11 @@ +let uart = require("uart"); +uart.setup(115200); + +while (1) { + let rx_data = uart.readBytes(1, 0); + if (rx_data !== undefined) { + uart.write(rx_data); + let data_view = Uint8Array(rx_data); + print("0x" + to_hex_string(data_view[0])); + } +} \ No newline at end of file diff --git a/applications/system/js_app/js_thread.c b/applications/system/js_app/js_thread.c index 4797bb8aa1b..22007d013d3 100644 --- a/applications/system/js_app/js_thread.c +++ b/applications/system/js_app/js_thread.c @@ -235,18 +235,18 @@ static int32_t js_thread(void* arg) { struct mjs* mjs = mjs_create(worker); worker->modules = js_modules_create(mjs, worker->resolver); mjs_val_t global = mjs_get_global(mjs); - mjs_set(mjs, global, "print", ~0, MFS_MK_FN(js_print)); - mjs_set(mjs, global, "delay", ~0, MFS_MK_FN(js_delay)); - mjs_set(mjs, global, "to_string", ~0, MFS_MK_FN(js_global_to_string)); - mjs_set(mjs, global, "to_hex_string", ~0, MFS_MK_FN(js_global_to_hex_string)); - mjs_set(mjs, global, "ffi_address", ~0, MFS_MK_FN(js_ffi_address)); - mjs_set(mjs, global, "require", ~0, MFS_MK_FN(js_require)); + mjs_set(mjs, global, "print", ~0, MJS_MK_FN(js_print)); + mjs_set(mjs, global, "delay", ~0, MJS_MK_FN(js_delay)); + mjs_set(mjs, global, "to_string", ~0, MJS_MK_FN(js_global_to_string)); + mjs_set(mjs, global, "to_hex_string", ~0, MJS_MK_FN(js_global_to_hex_string)); + mjs_set(mjs, global, "ffi_address", ~0, MJS_MK_FN(js_ffi_address)); + mjs_set(mjs, global, "require", ~0, MJS_MK_FN(js_require)); mjs_val_t console_obj = mjs_mk_object(mjs); - mjs_set(mjs, console_obj, "log", ~0, MFS_MK_FN(js_console_log)); - mjs_set(mjs, console_obj, "warn", ~0, MFS_MK_FN(js_console_warn)); - mjs_set(mjs, console_obj, "error", ~0, MFS_MK_FN(js_console_error)); - mjs_set(mjs, console_obj, "debug", ~0, MFS_MK_FN(js_console_debug)); + mjs_set(mjs, console_obj, "log", ~0, MJS_MK_FN(js_console_log)); + mjs_set(mjs, console_obj, "warn", ~0, MJS_MK_FN(js_console_warn)); + mjs_set(mjs, console_obj, "error", ~0, MJS_MK_FN(js_console_error)); + mjs_set(mjs, console_obj, "debug", ~0, MJS_MK_FN(js_console_debug)); mjs_set(mjs, global, "console", ~0, console_obj); mjs_set_ffi_resolver(mjs, js_dlsym, worker->resolver); diff --git a/applications/system/js_app/js_thread_i.h b/applications/system/js_app/js_thread_i.h index 8e726bed66f..a73cbb4bc51 100644 --- a/applications/system/js_app/js_thread_i.h +++ b/applications/system/js_app/js_thread_i.h @@ -9,11 +9,10 @@ #include #include #include +#include #define INST_PROP_NAME "_" -#define MFS_MK_FN(fn) mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)fn) - typedef enum { ThreadEventStop = (1 << 0), ThreadEventCustomDataRx = (1 << 1), diff --git a/applications/system/js_app/modules/js_badusb.c b/applications/system/js_app/modules/js_badusb.c index 7750200d884..6b19faea21f 100644 --- a/applications/system/js_app/modules/js_badusb.c +++ b/applications/system/js_app/modules/js_badusb.c @@ -370,13 +370,13 @@ static void* js_badusb_create(struct mjs* mjs, mjs_val_t* object) { JsBadusbInst* badusb = malloc(sizeof(JsBadusbInst)); mjs_val_t badusb_obj = mjs_mk_object(mjs); mjs_set(mjs, badusb_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, badusb)); - mjs_set(mjs, badusb_obj, "setup", ~0, MFS_MK_FN(js_badusb_setup)); - mjs_set(mjs, badusb_obj, "isConnected", ~0, MFS_MK_FN(js_badusb_is_connected)); - mjs_set(mjs, badusb_obj, "press", ~0, MFS_MK_FN(js_badusb_press)); - mjs_set(mjs, badusb_obj, "hold", ~0, MFS_MK_FN(js_badusb_hold)); - mjs_set(mjs, badusb_obj, "release", ~0, MFS_MK_FN(js_badusb_release)); - mjs_set(mjs, badusb_obj, "print", ~0, MFS_MK_FN(js_badusb_print)); - mjs_set(mjs, badusb_obj, "println", ~0, MFS_MK_FN(js_badusb_println)); + mjs_set(mjs, badusb_obj, "setup", ~0, MJS_MK_FN(js_badusb_setup)); + mjs_set(mjs, badusb_obj, "isConnected", ~0, MJS_MK_FN(js_badusb_is_connected)); + mjs_set(mjs, badusb_obj, "press", ~0, MJS_MK_FN(js_badusb_press)); + mjs_set(mjs, badusb_obj, "hold", ~0, MJS_MK_FN(js_badusb_hold)); + mjs_set(mjs, badusb_obj, "release", ~0, MJS_MK_FN(js_badusb_release)); + mjs_set(mjs, badusb_obj, "print", ~0, MJS_MK_FN(js_badusb_print)); + mjs_set(mjs, badusb_obj, "println", ~0, MJS_MK_FN(js_badusb_println)); *object = badusb_obj; return badusb; } diff --git a/applications/system/js_app/modules/js_dialog.c b/applications/system/js_app/modules/js_dialog.c index e5e8d6a5677..34de6d64160 100644 --- a/applications/system/js_app/modules/js_dialog.c +++ b/applications/system/js_app/modules/js_dialog.c @@ -130,8 +130,8 @@ static void js_dialog_custom(struct mjs* mjs) { static void* js_dialog_create(struct mjs* mjs, mjs_val_t* object) { mjs_val_t dialog_obj = mjs_mk_object(mjs); - mjs_set(mjs, dialog_obj, "message", ~0, MFS_MK_FN(js_dialog_message)); - mjs_set(mjs, dialog_obj, "custom", ~0, MFS_MK_FN(js_dialog_custom)); + mjs_set(mjs, dialog_obj, "message", ~0, MJS_MK_FN(js_dialog_message)); + mjs_set(mjs, dialog_obj, "custom", ~0, MJS_MK_FN(js_dialog_custom)); *object = dialog_obj; return (void*)1; diff --git a/applications/system/js_app/modules/js_flipper.c b/applications/system/js_app/modules/js_flipper.c index a56af74e8d1..17c0ad36b8a 100644 --- a/applications/system/js_app/modules/js_flipper.c +++ b/applications/system/js_app/modules/js_flipper.c @@ -27,9 +27,9 @@ static void js_flipper_get_battery(struct mjs* mjs) { void* js_flipper_create(struct mjs* mjs, mjs_val_t* object) { mjs_val_t flipper_obj = mjs_mk_object(mjs); - mjs_set(mjs, flipper_obj, "getModel", ~0, MFS_MK_FN(js_flipper_get_model)); - mjs_set(mjs, flipper_obj, "getName", ~0, MFS_MK_FN(js_flipper_get_name)); - mjs_set(mjs, flipper_obj, "getBatteryCharge", ~0, MFS_MK_FN(js_flipper_get_battery)); + mjs_set(mjs, flipper_obj, "getModel", ~0, MJS_MK_FN(js_flipper_get_model)); + mjs_set(mjs, flipper_obj, "getName", ~0, MJS_MK_FN(js_flipper_get_name)); + mjs_set(mjs, flipper_obj, "getBatteryCharge", ~0, MJS_MK_FN(js_flipper_get_battery)); *object = flipper_obj; return (void*)1; diff --git a/applications/system/js_app/modules/js_notification.c b/applications/system/js_app/modules/js_notification.c index 31b89a70d7c..e1084be1390 100644 --- a/applications/system/js_app/modules/js_notification.c +++ b/applications/system/js_app/modules/js_notification.c @@ -79,9 +79,9 @@ static void* js_notification_create(struct mjs* mjs, mjs_val_t* object) { NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); mjs_val_t notify_obj = mjs_mk_object(mjs); mjs_set(mjs, notify_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, notification)); - mjs_set(mjs, notify_obj, "success", ~0, MFS_MK_FN(js_notify_success)); - mjs_set(mjs, notify_obj, "error", ~0, MFS_MK_FN(js_notify_error)); - mjs_set(mjs, notify_obj, "blink", ~0, MFS_MK_FN(js_notify_blink)); + mjs_set(mjs, notify_obj, "success", ~0, MJS_MK_FN(js_notify_success)); + mjs_set(mjs, notify_obj, "error", ~0, MJS_MK_FN(js_notify_error)); + mjs_set(mjs, notify_obj, "blink", ~0, MJS_MK_FN(js_notify_blink)); *object = notify_obj; return notification; diff --git a/applications/system/js_app/modules/js_uart.c b/applications/system/js_app/modules/js_uart.c index e865cc54b3a..cb64680fec5 100644 --- a/applications/system/js_app/modules/js_uart.c +++ b/applications/system/js_app/modules/js_uart.c @@ -34,6 +34,12 @@ static void js_uart_setup(struct mjs* mjs) { JsUartInst* uart = mjs_get_ptr(mjs, obj_inst); furi_assert(uart); + if(uart->setup_done) { + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "UART is already configured"); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + bool args_correct = false; uint32_t baudrate = 0; @@ -104,6 +110,14 @@ static void js_uart_write(struct mjs* mjs) { if(!args_correct) { break; } + } else if(mjs_is_typed_array(arg)) { + mjs_val_t array_buf = arg; + if(mjs_is_data_view(arg)) { + array_buf = mjs_dataview_get_buf(mjs, arg); + } + size_t len = 0; + char* buf = mjs_array_buf_get_ptr(mjs, array_buf, &len); + furi_hal_uart_tx(FuriHalUartIdLPUART1, (uint8_t*)buf, len); } else { args_correct = false; break; @@ -246,6 +260,55 @@ static void js_uart_readln(struct mjs* mjs) { furi_string_free(rx_buf); } +static void js_uart_read_bytes(struct mjs* mjs) { + mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); + JsUartInst* uart = mjs_get_ptr(mjs, obj_inst); + furi_assert(uart); + if(!uart->setup_done) { + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "UART is not configured"); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + size_t read_len = 0; + uint32_t timeout = FuriWaitForever; + + do { + size_t num_args = mjs_nargs(mjs); + if(num_args == 1) { + mjs_val_t arg = mjs_arg(mjs, 0); + if(!mjs_is_number(arg)) { + break; + } + read_len = mjs_get_int32(mjs, arg); + } else if(num_args == 2) { + mjs_val_t len_arg = mjs_arg(mjs, 0); + mjs_val_t timeout_arg = mjs_arg(mjs, 1); + if((!mjs_is_number(len_arg)) || (!mjs_is_number(timeout_arg))) { + break; + } + read_len = mjs_get_int32(mjs, len_arg); + timeout = mjs_get_int32(mjs, timeout_arg); + } + } while(0); + + if(read_len == 0) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + char* read_buf = malloc(read_len); + size_t bytes_read = js_uart_receive(uart, read_buf, read_len, timeout); + + mjs_val_t return_obj = MJS_UNDEFINED; + if(bytes_read > 0) { + return_obj = mjs_mk_array_buf(mjs, read_buf, bytes_read); + } + mjs_return(mjs, return_obj); + free(read_buf); +} + static bool js_uart_expect_parse_string(struct mjs* mjs, mjs_val_t arg, PatternArray_t patterns) { size_t str_len = 0; const char* arg_str = mjs_get_string(mjs, &arg, &str_len); @@ -475,11 +538,12 @@ static void* js_uart_create(struct mjs* mjs, mjs_val_t* object) { js_uart->mjs = mjs; mjs_val_t uart_obj = mjs_mk_object(mjs); mjs_set(mjs, uart_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, js_uart)); - mjs_set(mjs, uart_obj, "setup", ~0, MFS_MK_FN(js_uart_setup)); - mjs_set(mjs, uart_obj, "write", ~0, MFS_MK_FN(js_uart_write)); - mjs_set(mjs, uart_obj, "read", ~0, MFS_MK_FN(js_uart_read)); - mjs_set(mjs, uart_obj, "readln", ~0, MFS_MK_FN(js_uart_readln)); - mjs_set(mjs, uart_obj, "expect", ~0, MFS_MK_FN(js_uart_expect)); + mjs_set(mjs, uart_obj, "setup", ~0, MJS_MK_FN(js_uart_setup)); + mjs_set(mjs, uart_obj, "write", ~0, MJS_MK_FN(js_uart_write)); + mjs_set(mjs, uart_obj, "read", ~0, MJS_MK_FN(js_uart_read)); + mjs_set(mjs, uart_obj, "readln", ~0, MJS_MK_FN(js_uart_readln)); + mjs_set(mjs, uart_obj, "readBytes", ~0, MJS_MK_FN(js_uart_read_bytes)); + mjs_set(mjs, uart_obj, "expect", ~0, MJS_MK_FN(js_uart_expect)); *object = uart_obj; return js_uart; diff --git a/lib/mjs/SConscript b/lib/mjs/SConscript index 2d95af4b6f2..35b45fc91e6 100644 --- a/lib/mjs/SConscript +++ b/lib/mjs/SConscript @@ -12,6 +12,7 @@ env.Append( File("mjs_array_public.h"), File("mjs_primitive_public.h"), File("mjs_util_public.h"), + File("mjs_array_buf_public.h"), ], ) diff --git a/lib/mjs/common/platforms/platform_flipper.c b/lib/mjs/common/platforms/platform_flipper.c index c2b80c86a83..bbef561d072 100644 --- a/lib/mjs/common/platforms/platform_flipper.c +++ b/lib/mjs/common/platforms/platform_flipper.c @@ -1,6 +1,7 @@ #include #include #include "../cs_dbg.h" +#include "../frozen/frozen.h" char* cs_read_file(const char* path, size_t* size) { Storage* storage = furi_record_open(RECORD_STORAGE); @@ -45,6 +46,13 @@ int json_prettify_file(const char* file_name) { return 0; } +int json_printer_file(struct json_out* out, const char* buf, size_t len) { + UNUSED(out); + UNUSED(buf); + UNUSED(len); + return 0; +} + int cs_log_print_prefix(enum cs_log_level level, const char* file, int ln) { (void)level; (void)file; diff --git a/lib/mjs/mjs_array_buf.c b/lib/mjs/mjs_array_buf.c new file mode 100644 index 00000000000..df7760a3d06 --- /dev/null +++ b/lib/mjs/mjs_array_buf.c @@ -0,0 +1,385 @@ +#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)); +} diff --git a/lib/mjs/mjs_array_buf.h b/lib/mjs/mjs_array_buf.h new file mode 100644 index 00000000000..2ff90afdf95 --- /dev/null +++ b/lib/mjs/mjs_array_buf.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2016 Cesanta Software Limited + * All rights reserved + */ + +#pragma once + +#include "mjs_internal.h" +#include "mjs_array_buf_public.h" + +#if defined(__cplusplus) +extern "C" { +#endif /* __cplusplus */ + +mjs_val_t mjs_dataview_get_prop(struct mjs* mjs, mjs_val_t obj, mjs_val_t key); + +mjs_err_t mjs_dataview_set_prop(struct mjs* mjs, mjs_val_t obj, mjs_val_t key, mjs_val_t val); + +void mjs_init_builtin_array_buf(struct mjs* mjs, mjs_val_t obj); + +mjs_val_t mjs_dataview_get_len(struct mjs* mjs, mjs_val_t obj); + +void mjs_array_buf_slice(struct mjs* mjs); + +#if defined(__cplusplus) +} +#endif /* __cplusplus */ diff --git a/lib/mjs/mjs_array_buf_public.h b/lib/mjs/mjs_array_buf_public.h new file mode 100644 index 00000000000..8b7081e169d --- /dev/null +++ b/lib/mjs/mjs_array_buf_public.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2016 Cesanta Software Limited + * All rights reserved + */ + +#pragma once + +#include "mjs_core_public.h" + +#if defined(__cplusplus) +extern "C" { +#endif /* __cplusplus */ + +typedef enum { + MJS_DATAVIEW_U8, + MJS_DATAVIEW_I8, + MJS_DATAVIEW_U16, + MJS_DATAVIEW_I16, + MJS_DATAVIEW_U32, + MJS_DATAVIEW_I32, +} mjs_dataview_type_t; + +int mjs_is_array_buf(mjs_val_t v); + +int mjs_is_data_view(mjs_val_t v); + +int mjs_is_typed_array(mjs_val_t v); + +mjs_val_t mjs_mk_array_buf(struct mjs* mjs, char* data, size_t buf_len); + +char* mjs_array_buf_get_ptr(struct mjs* mjs, mjs_val_t buf, size_t* bytelen); + +mjs_val_t mjs_dataview_get_buf(struct mjs* mjs, mjs_val_t obj); + +#if defined(__cplusplus) +} +#endif /* __cplusplus */ diff --git a/lib/mjs/mjs_builtin.c b/lib/mjs/mjs_builtin.c index fe05cbee5a4..afcf9ce6f07 100644 --- a/lib/mjs/mjs_builtin.c +++ b/lib/mjs/mjs_builtin.c @@ -14,6 +14,7 @@ #include "mjs_primitive.h" #include "mjs_string.h" #include "mjs_util.h" +#include "mjs_array_buf.h" /* * If the file with the given filename was already loaded, returns the @@ -156,4 +157,6 @@ void mjs_init_builtin(struct mjs* mjs, mjs_val_t obj) { */ mjs_set(mjs, obj, "NaN", ~0, MJS_TAG_NAN); mjs_set(mjs, obj, "isNaN", ~0, mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_op_isnan)); + + mjs_init_builtin_array_buf(mjs, obj); } diff --git a/lib/mjs/mjs_core.c b/lib/mjs/mjs_core.c index 5bc7a010029..aae196599f2 100644 --- a/lib/mjs/mjs_core.c +++ b/lib/mjs/mjs_core.c @@ -60,6 +60,7 @@ void mjs_destroy(struct mjs* mjs) { mbuf_free(&mjs->scopes); mbuf_free(&mjs->loop_addresses); mbuf_free(&mjs->json_visited_stack); + mbuf_free(&mjs->array_buffers); free(mjs->error_msg); free(mjs->stack_trace); mjs_ffi_args_free_list(mjs); @@ -84,6 +85,7 @@ struct mjs* mjs_create(void* context) { mbuf_init(&mjs->scopes, 0); mbuf_init(&mjs->loop_addresses, 0); mbuf_init(&mjs->json_visited_stack, 0); + mbuf_init(&mjs->array_buffers, 0); mjs->bcode_len = 0; @@ -259,6 +261,10 @@ MJS_PRIVATE enum mjs_type mjs_get_type(mjs_val_t v) { return MJS_TYPE_BOOLEAN; case MJS_TAG_NULL >> 48: return MJS_TYPE_NULL; + case MJS_TAG_ARRAY_BUF >> 48: + return MJS_TYPE_ARRAY_BUF; + case MJS_TAG_ARRAY_BUF_VIEW >> 48: + return MJS_TYPE_ARRAY_BUF_VIEW; default: abort(); return MJS_TYPE_UNDEFINED; diff --git a/lib/mjs/mjs_core.h b/lib/mjs/mjs_core.h index 2dcb6bab68e..81372498768 100644 --- a/lib/mjs/mjs_core.h +++ b/lib/mjs/mjs_core.h @@ -73,6 +73,7 @@ struct mjs { struct mbuf foreign_strings; /* Sequence of (varint len, char *data) */ struct mbuf owned_values; struct mbuf json_visited_stack; + struct mbuf array_buffers; struct mjs_vals vals; char* error_msg; char* stack_trace; diff --git a/lib/mjs/mjs_core_public.h b/lib/mjs/mjs_core_public.h index e92383d0113..2e22c025ffd 100644 --- a/lib/mjs/mjs_core_public.h +++ b/lib/mjs/mjs_core_public.h @@ -80,6 +80,9 @@ typedef uint64_t mjs_val_t; #define MJS_TAG_FUNCTION_FFI MAKE_TAG(1, 14) #define MJS_TAG_NULL MAKE_TAG(1, 15) +#define MJS_TAG_ARRAY_BUF MAKE_TAG(0, 1) /* ArrayBuffer */ +#define MJS_TAG_ARRAY_BUF_VIEW MAKE_TAG(0, 2) /* DataView */ + #define MJS_TAG_MASK MAKE_TAG(1, 15) /* This if-0 is a dirty workaround to force etags to pick `struct mjs` */ @@ -100,6 +103,8 @@ enum mjs_type { MJS_TYPE_NUMBER, MJS_TYPE_STRING, MJS_TYPE_FOREIGN, + MJS_TYPE_ARRAY_BUF, + MJS_TYPE_ARRAY_BUF_VIEW, /* Different classes of Object type */ MJS_TYPE_OBJECT_GENERIC, diff --git a/lib/mjs/mjs_exec.c b/lib/mjs/mjs_exec.c index 83c842844c6..265e7d5c39b 100644 --- a/lib/mjs/mjs_exec.c +++ b/lib/mjs/mjs_exec.c @@ -17,6 +17,7 @@ #include "mjs_string.h" #include "mjs_tok.h" #include "mjs_util.h" +#include "mjs_array_buf.h" #if MJS_GENERATE_JSC && defined(CS_MMAP) #include @@ -316,6 +317,11 @@ static void exec_expr(struct mjs* mjs, int op) { mjs_val_t key = mjs_pop(mjs); if(mjs_is_object(obj)) { mjs_set_v(mjs, obj, key, val); + } else if(mjs_is_data_view(obj)) { + mjs_err_t err = mjs_dataview_set_prop(mjs, obj, key, val); + if(err != MJS_OK) { + mjs_prepend_errorf(mjs, err, ""); + } } else if(mjs_is_foreign(obj)) { /* * We don't have setters, so in order to support properties which behave @@ -502,6 +508,53 @@ static int getprop_builtin_foreign( return 1; } +static int getprop_builtin_array_buf( + struct mjs* mjs, + mjs_val_t val, + const char* name, + size_t name_len, + mjs_val_t* res) { + if(strcmp(name, "byteLength") == 0) { + size_t len = 0; + mjs_array_buf_get_ptr(mjs, val, &len); + *res = mjs_mk_number(mjs, len); + return 1; + } else if(strcmp(name, "getPtr") == 0) { + void* ptr = mjs_array_buf_get_ptr(mjs, val, NULL); + *res = mjs_mk_foreign(mjs, ptr); + return 1; + } else if(strcmp(name, "slice") == 0) { + *res = mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_array_buf_slice); + return 1; + } + + (void)name_len; + return 0; +} + +static int getprop_builtin_data_view( + struct mjs* mjs, + mjs_val_t val, + const char* name, + size_t name_len, + mjs_val_t* res) { + if(strcmp(name, "byteLength") == 0) { + size_t len = 0; + mjs_array_buf_get_ptr(mjs, mjs_dataview_get_buf(mjs, val), &len); + *res = mjs_mk_number(mjs, len); + return 1; + } else if(strcmp(name, "length") == 0) { + *res = mjs_dataview_get_len(mjs, val); + return 1; + } else if(strcmp(name, "buffer") == 0) { + *res = mjs_dataview_get_buf(mjs, val); + return 1; + } + + (void)name_len; + return 0; +} + static void mjs_apply_(struct mjs* mjs) { mjs_val_t res = MJS_UNDEFINED, *args = NULL; mjs_val_t func = mjs->vals.this_obj, v = mjs_arg(mjs, 1); @@ -534,6 +587,10 @@ static int getprop_builtin(struct mjs* mjs, mjs_val_t val, mjs_val_t name, mjs_v handled = getprop_builtin_array(mjs, val, s, n, res); } else if(mjs_is_foreign(val)) { handled = getprop_builtin_foreign(mjs, val, s, n, res); + } else if(mjs_is_array_buf(val)) { + handled = getprop_builtin_array_buf(mjs, val, s, n, res); + } else if(mjs_is_data_view(val)) { + handled = getprop_builtin_data_view(mjs, val, s, n, res); } } @@ -688,6 +745,8 @@ MJS_PRIVATE mjs_err_t mjs_execute(struct mjs* mjs, size_t off, mjs_val_t* res) { if(!getprop_builtin(mjs, obj, key, &val)) { if(mjs_is_object(obj)) { val = mjs_get_v_proto(mjs, obj, key); + } else if((mjs_is_data_view(obj) && (mjs_is_number(key)))) { + val = mjs_dataview_get_prop(mjs, obj, key); } else { mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "type error"); } diff --git a/lib/mjs/mjs_gc.c b/lib/mjs/mjs_gc.c index 7a6bf778b72..ca0ac06f1f0 100644 --- a/lib/mjs/mjs_gc.c +++ b/lib/mjs/mjs_gc.c @@ -283,7 +283,7 @@ static void gc_mark_object(struct mjs* mjs, mjs_val_t* v) { struct mjs_property* prop; struct mjs_property* next; - assert(mjs_is_object(*v)); + assert(mjs_is_object_based(*v)); obj_base = get_object_struct(*v); @@ -367,7 +367,7 @@ static void gc_mark_string(struct mjs* mjs, mjs_val_t* v) { } MJS_PRIVATE void gc_mark(struct mjs* mjs, mjs_val_t* v) { - if(mjs_is_object(*v)) { + if(mjs_is_object_based(*v)) { gc_mark_object(mjs, v); } if(mjs_is_ffi_sig(*v)) { @@ -514,7 +514,7 @@ void mjs_gc(struct mjs* mjs, int full) { } MJS_PRIVATE int gc_check_val(struct mjs* mjs, mjs_val_t v) { - if(mjs_is_object(v)) { + if(mjs_is_object_based(v)) { return gc_check_ptr(&mjs->object_arena, get_object_struct(v)); } if(mjs_is_ffi_sig(v)) { diff --git a/lib/mjs/mjs_json.c b/lib/mjs/mjs_json.c index 234fbb347ca..829b3b4c0f8 100644 --- a/lib/mjs/mjs_json.c +++ b/lib/mjs/mjs_json.c @@ -30,6 +30,8 @@ static int should_skip_for_json(enum mjs_type type) { case MJS_TYPE_BOOLEAN: case MJS_TYPE_NUMBER: case MJS_TYPE_STRING: + case MJS_TYPE_ARRAY_BUF: + case MJS_TYPE_ARRAY_BUF_VIEW: case MJS_TYPE_OBJECT_GENERIC: case MJS_TYPE_OBJECT_ARRAY: ret = 0; @@ -148,6 +150,8 @@ MJS_PRIVATE mjs_err_t to_json_or_debug( case MJS_TYPE_NUMBER: case MJS_TYPE_UNDEFINED: case MJS_TYPE_FOREIGN: + case MJS_TYPE_ARRAY_BUF: + case MJS_TYPE_ARRAY_BUF_VIEW: /* For those types, regular `mjs_to_string()` works */ { /* refactor: mjs_to_string allocates memory every time */ diff --git a/lib/mjs/mjs_object.c b/lib/mjs/mjs_object.c index e3f18abbaff..2aea1bd46ad 100644 --- a/lib/mjs/mjs_object.c +++ b/lib/mjs/mjs_object.c @@ -25,7 +25,7 @@ MJS_PRIVATE struct mjs_object* get_object_struct(mjs_val_t v) { if(mjs_is_null(v)) { ret = NULL; } else { - assert(mjs_is_object(v)); + assert(mjs_is_object_based(v)); ret = (struct mjs_object*)get_ptr(v); } return ret; @@ -45,12 +45,17 @@ 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(obj)) { + if(!mjs_is_object_based(obj)) { return NULL; } @@ -177,7 +182,7 @@ MJS_PRIVATE mjs_err_t mjs_set_internal( if(p == NULL) { struct mjs_object* o; - if(!mjs_is_object(obj)) { + if(!mjs_is_object_based(obj)) { return MJS_REFERENCE_ERROR; } @@ -216,7 +221,7 @@ MJS_PRIVATE void mjs_destroy_property(struct mjs_property** p) { 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(obj)) { + if(!mjs_is_object_based(obj)) { return -1; } if(len == (size_t)~0) { diff --git a/lib/mjs/mjs_object_public.h b/lib/mjs/mjs_object_public.h index bebb7c8eeb2..f9f06c61647 100644 --- a/lib/mjs/mjs_object_public.h +++ b/lib/mjs/mjs_object_public.h @@ -19,6 +19,11 @@ extern "C" { */ int mjs_is_object(mjs_val_t v); +/* + * Returns true if the given value type is object-based (object, array, dataview). + */ +int mjs_is_object_based(mjs_val_t v); + /* Make an empty object */ mjs_val_t mjs_mk_object(struct mjs* mjs); diff --git a/lib/mjs/mjs_primitive_public.h b/lib/mjs/mjs_primitive_public.h index ef07735e612..87075d9c9f6 100644 --- a/lib/mjs/mjs_primitive_public.h +++ b/lib/mjs/mjs_primitive_public.h @@ -18,6 +18,8 @@ extern "C" { /* JavaScript `undefined` value */ #define MJS_UNDEFINED MJS_TAG_UNDEFINED +#define MJS_MK_FN(fn) mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)fn) + /* Function pointer type used in `mjs_mk_foreign_func`. */ typedef void (*mjs_func_ptr_t)(void); diff --git a/lib/mjs/mjs_util.c b/lib/mjs/mjs_util.c index 39bdbb5795f..a31dba6bb5c 100644 --- a/lib/mjs/mjs_util.c +++ b/lib/mjs/mjs_util.c @@ -14,6 +14,7 @@ #include "mjs_string.h" #include "mjs_util.h" #include "mjs_tok.h" +#include "mjs_array_buf.h" #include const char* mjs_typeof(mjs_val_t v) { @@ -40,6 +41,10 @@ MJS_PRIVATE const char* mjs_stringify_type(enum mjs_type t) { 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 "???"; } @@ -532,6 +537,9 @@ mjs_err_t mjs_to_string(struct mjs* mjs, mjs_val_t* v, char** p, size_t* sizep, } 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"); diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 17ec74d5862..2fe8bc5313b 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,50.0,, +Version,+,50.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -84,6 +84,7 @@ Header,+,lib/mbedtls/include/mbedtls/md.h,, Header,+,lib/mbedtls/include/mbedtls/md5.h,, Header,+,lib/mbedtls/include/mbedtls/sha1.h,, Header,+,lib/mbedtls/include/mbedtls/sha256.h,, +Header,+,lib/mjs/mjs_array_buf_public.h,, Header,+,lib/mjs/mjs_array_public.h,, Header,+,lib/mjs/mjs_core_public.h,, Header,+,lib/mjs/mjs_exec_public.h,, @@ -1881,6 +1882,7 @@ Function,+,menu_reset,void,Menu* Function,+,menu_set_selected_item,void,"Menu*, uint32_t" Function,+,mjs_apply,mjs_err_t,"mjs*, mjs_val_t*, mjs_val_t, mjs_val_t, int, mjs_val_t*" Function,+,mjs_arg,mjs_val_t,"mjs*, int" +Function,+,mjs_array_buf_get_ptr,char*,"mjs*, mjs_val_t, size_t*" Function,+,mjs_array_del,void,"mjs*, mjs_val_t, unsigned long" Function,+,mjs_array_get,mjs_val_t,"mjs*, mjs_val_t, unsigned long" Function,+,mjs_array_length,unsigned long,"mjs*, mjs_val_t" @@ -1888,6 +1890,7 @@ Function,+,mjs_array_push,mjs_err_t,"mjs*, mjs_val_t, mjs_val_t" Function,+,mjs_array_set,mjs_err_t,"mjs*, mjs_val_t, unsigned long, mjs_val_t" Function,+,mjs_call,mjs_err_t,"mjs*, mjs_val_t*, mjs_val_t, mjs_val_t, int, ..." Function,+,mjs_create,mjs*,void* +Function,+,mjs_dataview_get_buf,mjs_val_t,"mjs*, mjs_val_t" Function,+,mjs_del,int,"mjs*, mjs_val_t, const char*, size_t" Function,+,mjs_destroy,void,mjs* Function,-,mjs_disasm_all,void,"mjs*, MjsPrintCallback, void*" @@ -1916,16 +1919,21 @@ Function,+,mjs_get_this,mjs_val_t,mjs* Function,+,mjs_get_v,mjs_val_t,"mjs*, mjs_val_t, mjs_val_t" Function,+,mjs_get_v_proto,mjs_val_t,"mjs*, mjs_val_t, mjs_val_t" Function,+,mjs_is_array,int,mjs_val_t +Function,+,mjs_is_array_buf,int,mjs_val_t Function,+,mjs_is_boolean,int,mjs_val_t +Function,+,mjs_is_data_view,int,mjs_val_t Function,+,mjs_is_foreign,int,mjs_val_t Function,+,mjs_is_function,int,mjs_val_t Function,+,mjs_is_null,int,mjs_val_t Function,+,mjs_is_number,int,mjs_val_t Function,+,mjs_is_object,int,mjs_val_t +Function,+,mjs_is_object_based,int,mjs_val_t Function,+,mjs_is_string,int,mjs_val_t Function,+,mjs_is_truthy,int,"mjs*, mjs_val_t" +Function,+,mjs_is_typed_array,int,mjs_val_t Function,+,mjs_is_undefined,int,mjs_val_t Function,+,mjs_mk_array,mjs_val_t,mjs* +Function,+,mjs_mk_array_buf,mjs_val_t,"mjs*, char*, size_t" Function,+,mjs_mk_boolean,mjs_val_t,"mjs*, int" Function,+,mjs_mk_foreign,mjs_val_t,"mjs*, void*" Function,+,mjs_mk_foreign_func,mjs_val_t,"mjs*, mjs_func_ptr_t" diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index eae9435d5a3..f4a1f5f1fd0 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,50.0,, +Version,+,50.1,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -97,6 +97,7 @@ Header,+,lib/mbedtls/include/mbedtls/md.h,, Header,+,lib/mbedtls/include/mbedtls/md5.h,, Header,+,lib/mbedtls/include/mbedtls/sha1.h,, Header,+,lib/mbedtls/include/mbedtls/sha256.h,, +Header,+,lib/mjs/mjs_array_buf_public.h,, Header,+,lib/mjs/mjs_array_public.h,, Header,+,lib/mjs/mjs_core_public.h,, Header,+,lib/mjs/mjs_exec_public.h,, @@ -2401,6 +2402,7 @@ Function,+,mf_ultralight_support_feature,_Bool,"const uint32_t, const uint32_t" Function,+,mf_ultralight_verify,_Bool,"MfUltralightData*, const FuriString*" Function,+,mjs_apply,mjs_err_t,"mjs*, mjs_val_t*, mjs_val_t, mjs_val_t, int, mjs_val_t*" Function,+,mjs_arg,mjs_val_t,"mjs*, int" +Function,+,mjs_array_buf_get_ptr,char*,"mjs*, mjs_val_t, size_t*" Function,+,mjs_array_del,void,"mjs*, mjs_val_t, unsigned long" Function,+,mjs_array_get,mjs_val_t,"mjs*, mjs_val_t, unsigned long" Function,+,mjs_array_length,unsigned long,"mjs*, mjs_val_t" @@ -2408,6 +2410,7 @@ Function,+,mjs_array_push,mjs_err_t,"mjs*, mjs_val_t, mjs_val_t" Function,+,mjs_array_set,mjs_err_t,"mjs*, mjs_val_t, unsigned long, mjs_val_t" Function,+,mjs_call,mjs_err_t,"mjs*, mjs_val_t*, mjs_val_t, mjs_val_t, int, ..." Function,+,mjs_create,mjs*,void* +Function,+,mjs_dataview_get_buf,mjs_val_t,"mjs*, mjs_val_t" Function,+,mjs_del,int,"mjs*, mjs_val_t, const char*, size_t" Function,+,mjs_destroy,void,mjs* Function,-,mjs_disasm_all,void,"mjs*, MjsPrintCallback, void*" @@ -2436,16 +2439,21 @@ Function,+,mjs_get_this,mjs_val_t,mjs* Function,+,mjs_get_v,mjs_val_t,"mjs*, mjs_val_t, mjs_val_t" Function,+,mjs_get_v_proto,mjs_val_t,"mjs*, mjs_val_t, mjs_val_t" Function,+,mjs_is_array,int,mjs_val_t +Function,+,mjs_is_array_buf,int,mjs_val_t Function,+,mjs_is_boolean,int,mjs_val_t +Function,+,mjs_is_data_view,int,mjs_val_t Function,+,mjs_is_foreign,int,mjs_val_t Function,+,mjs_is_function,int,mjs_val_t Function,+,mjs_is_null,int,mjs_val_t Function,+,mjs_is_number,int,mjs_val_t Function,+,mjs_is_object,int,mjs_val_t +Function,+,mjs_is_object_based,int,mjs_val_t Function,+,mjs_is_string,int,mjs_val_t Function,+,mjs_is_truthy,int,"mjs*, mjs_val_t" +Function,+,mjs_is_typed_array,int,mjs_val_t Function,+,mjs_is_undefined,int,mjs_val_t Function,+,mjs_mk_array,mjs_val_t,mjs* +Function,+,mjs_mk_array_buf,mjs_val_t,"mjs*, char*, size_t" Function,+,mjs_mk_boolean,mjs_val_t,"mjs*, int" Function,+,mjs_mk_foreign,mjs_val_t,"mjs*, void*" Function,+,mjs_mk_foreign_func,mjs_val_t,"mjs*, mjs_func_ptr_t" From 0e8a785a6cad79771c0cbae365409e885bdcd1cc Mon Sep 17 00:00:00 2001 From: Aleksandr Kutuzov Date: Mon, 12 Feb 2024 15:39:59 +0700 Subject: [PATCH 31/31] JS: fix mem leak in uart destructor --- applications/system/js_app/modules/js_uart.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/applications/system/js_app/modules/js_uart.c b/applications/system/js_app/modules/js_uart.c index 231b4fb5f7b..8f251c276eb 100644 --- a/applications/system/js_app/modules/js_uart.c +++ b/applications/system/js_app/modules/js_uart.c @@ -562,8 +562,9 @@ static void js_uart_destroy(void* inst) { furi_hal_serial_deinit(js_uart->serial_handle); furi_hal_serial_control_release(js_uart->serial_handle); js_uart->serial_handle = NULL; - furi_stream_buffer_free(js_uart->rx_stream); } + + furi_stream_buffer_free(js_uart->rx_stream); free(js_uart); }