[FL-3579, FL-3601, FL-3714] JavaScript runner (#3286)

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

Co-authored-by: SG <who.just.the.doctor@gmail.com>
Co-authored-by: Aleksandr Kutuzov <alleteam@gmail.com>
This commit is contained in:
Nikolay Minaylov 2024-02-12 11:54:32 +03:00 committed by GitHub
parent 389affd904
commit 0154018363
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
118 changed files with 17568 additions and 38 deletions

View file

@ -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/*

View file

@ -29,6 +29,7 @@ static const char* known_ext[] = {
[ArchiveFileTypeBadUsb] = ".txt",
[ArchiveFileTypeU2f] = "?",
[ArchiveFileTypeApplication] = ".fap",
[ArchiveFileTypeJS] = ".js",
[ArchiveFileTypeUpdateManifest] = ".fuf",
[ArchiveFileTypeFolder] = "?",
[ArchiveFileTypeUnknown] = "*",

View file

@ -16,6 +16,7 @@ typedef enum {
ArchiveFileTypeU2f,
ArchiveFileTypeUpdateManifest,
ArchiveFileTypeApplication,
ArchiveFileTypeJS,
ArchiveFileTypeFolder,
ArchiveFileTypeUnknown,
ArchiveFileTypeLoading,

View file

@ -30,6 +30,8 @@ static const char* archive_get_flipper_app_name(ArchiveFileTypeEnum file_type) {
return "U2F";
case ArchiveFileTypeUpdateManifest:
return "UpdaterApp";
case ArchiveFileTypeJS:
return "JS Runner";
default:
return NULL;
}

View file

@ -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(

View file

@ -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);

View file

@ -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);

View file

@ -7,9 +7,12 @@
#include <gui/view_holder.h>
#include <gui/modules/loading.h>
#include <dolphin/dolphin.h>
#include <lib/toolbox/path.h>
#define TAG "LoaderApplications"
#define JS_RUNNER_APP "JS Runner"
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_APP, furi_string_get_cstr(app->file_path));
}
}
// stop loading animation

View file

@ -5,6 +5,7 @@ App(
provides=[
"updater_app",
"storage_move_to_sd",
"js_app",
# "archive",
],
)

View file

@ -0,0 +1,41 @@
App(
appid="js_app",
name="JS Runner",
apptype=FlipperAppType.SYSTEM,
entry_point="js_app",
stack_size=2 * 1024,
resources="examples",
order=0,
)
App(
appid="js_dialog",
apptype=FlipperAppType.PLUGIN,
entry_point="js_dialog_ep",
requires=["js_app"],
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"],
)
App(
appid="js_uart",
apptype=FlipperAppType.PLUGIN,
entry_point="js_uart_ep",
requires=["js_app"],
sources=["modules/js_uart.c"],
)

View file

@ -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]);
}

View file

@ -0,0 +1,20 @@
let uart = require("uart");
uart.setup(115200);
// uart.write("\n");
uart.write([0x0a]);
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");
}
}

View file

@ -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();
}

View file

@ -0,0 +1,5 @@
print("print", 1);
console.log("log", 2);
console.warn("warn", 3);
console.error("error", 4);
console.debug("debug", 5);

View file

@ -0,0 +1,9 @@
print("start");
delay(1000)
print("1");
delay(1000)
print("2");
delay(1000)
print("3");
delay(1000)
print("end");

View file

@ -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");
}

View file

@ -0,0 +1,3 @@
let math = load("/ext/apps/Scripts/api.js");
let result = math.add(5, 10);
print(result);

View file

@ -0,0 +1,3 @@
({
add: function (a, b) { return a + b; },
})

View file

@ -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);
}

View file

@ -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]));
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View file

@ -0,0 +1,131 @@
#include <dialogs/dialogs.h>
#include "js_thread.h"
#include <storage/storage.h>
#include "js_app_i.h"
#include <toolbox/path.h>
#include <assets_icons.h>
#define TAG "JS app"
typedef struct {
JsThread* js_thread;
Gui* gui;
ViewDispatcher* view_dispatcher;
Loading* loading;
JsConsoleView* console_view;
} JsApp;
static uint32_t js_view_exit(void* context) {
UNUSED(context);
return VIEW_NONE;
}
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) {
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 js_callback(JsThreadEvent event, const char* msg, void* context) {
JsApp* app = context;
furi_assert(app);
if(event == JsThreadEventDone) {
FURI_LOG_I(TAG, "Script done");
console_view_print(app->console_view, "--- DONE ---");
} else if(event == JsThreadEventPrint) {
console_view_print(app->console_view, msg);
} else if(event == JsThreadEventError) {
console_view_print(app->console_view, "--- ERROR ---");
console_view_print(app->console_view, msg);
} else if(event == JsThreadEventErrorTrace) {
FuriString* compact_trace = furi_string_alloc_set_str(msg);
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 JsApp* js_app_alloc(void) {
JsApp* app = malloc(sizeof(JsApp));
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, JsAppViewLoading, loading_get_view(app->loading));
app->console_view = console_view_alloc();
view_dispatcher_add_view(
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 js_app_free(JsApp* app) {
console_view_free(app->console_view);
view_dispatcher_remove_view(app->view_dispatcher, JsAppViewConsole);
loading_free(app->loading);
view_dispatcher_remove_view(app->view_dispatcher, JsAppViewLoading);
view_dispatcher_free(app->view_dispatcher);
furi_record_close("gui");
free(app);
}
int32_t js_app(void* arg) {
JsApp* app = js_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", &I_js_script_10px);
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->js_thread = js_thread_run(furi_string_get_cstr(script_path), js_callback, app);
view_dispatcher_run(app->view_dispatcher);
js_thread_stop(app->js_thread);
} while(0);
furi_string_free(script_path);
js_app_free(app);
return 0;
} //-V773

View file

@ -0,0 +1,10 @@
#include <furi.h>
#include <gui/gui.h>
#include <gui/view_dispatcher.h>
#include <gui/modules/loading.h>
#include "views/console_view.h"
typedef enum {
JsAppViewConsole,
JsAppViewLoading,
} JsAppView;

View file

@ -0,0 +1,126 @@
#include <core/common_defines.h>
#include "js_modules.h"
#include <m-dict.h>
#include "modules/js_flipper.h"
#define TAG "JS modules"
typedef struct {
JsModeConstructor create;
JsModeDestructor destroy;
void* context;
} JsModuleData;
DICT_DEF2(JsModuleDict, FuriString*, FURI_STRING_OPLIST, JsModuleData, M_POD_OPLIST);
static const JsModuleDescriptor modules_builtin[] = {
{"flipper", js_flipper_create, NULL},
};
struct JsModules {
struct mjs* mjs;
JsModuleDict_t module_dict;
PluginManager* plugin_manager;
};
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) {
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) {
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) { //-V547
furi_string_free(module_name);
mjs_prepend_errorf(
modules->mjs, MJS_BAD_ARGS_ERROR, "\"%s\" module is already installed", name);
return MJS_UNDEFINED;
}
bool module_found = false;
// Check built-in modules
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) {
continue;
}
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/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;
}
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);
}
// 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) { //-V779
module_inst->context = module_inst->create(modules->mjs, &module_object);
}
}
if(module_object == MJS_UNDEFINED) { //-V547
mjs_prepend_errorf(modules->mjs, MJS_BAD_ARGS_ERROR, "\"%s\" module load fail", name);
}
furi_string_free(module_name);
return module_object;
}

View file

@ -0,0 +1,25 @@
#pragma once
#include "js_thread_i.h"
#include <flipper_application/flipper_application.h>
#include <flipper_application/plugins/plugin_manager.h>
#include <flipper_application/plugins/composite_resolver.h>
#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);
void js_modules_destroy(JsModules* modules);
mjs_val_t js_module_require(JsModules* modules, const char* name, size_t name_len);

View file

@ -0,0 +1,319 @@
#include <common/cs_dbg.h>
#include <toolbox/stream/file_stream.h>
#include <loader/firmware_api/firmware_api.h>
#include <flipper_application/api_hashtable/api_hashtable.h>
#include <flipper_application/plugins/composite_resolver.h>
#include <furi_hal.h>
#include "plugin_api/app_api_interface.h"
#include "js_thread.h"
#include "js_thread_i.h"
#include "js_modules.h"
#define TAG "JS"
struct JsThread {
FuriThread* thread;
FuriString* path;
CompositeApiResolver* resolver;
JsThreadCallback app_callback;
void* context;
JsModules* modules;
};
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;
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) {
furi_string_cat_printf(msg_str, "err %s ", mjs_strerror(mjs, err));
} else {
furi_string_cat_printf(msg_str, "%s ", name);
}
if(need_free) {
free(name);
name = NULL;
}
}
}
static void js_print(struct mjs* mjs) {
FuriString* msg_str = furi_string_alloc();
js_str_print(msg_str, mjs);
printf("%s\r\n", furi_string_get_cstr(msg_str));
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);
}
furi_string_free(msg_str);
mjs_return(mjs, MJS_UNDEFINED);
}
static void js_console_log(struct mjs* mjs) {
FuriString* msg_str = furi_string_alloc();
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 js_console_warn(struct mjs* mjs) {
FuriString* msg_str = furi_string_alloc();
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 js_console_error(struct mjs* mjs) {
FuriString* msg_str = furi_string_alloc();
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 js_console_debug(struct mjs* mjs) {
FuriString* msg_str = furi_string_alloc();
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 js_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);
}
}
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 false;
}
if(flags & ThreadEventStop) {
mjs_exit(mjs);
return true;
}
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 js_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* js_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 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);
void* addr = mjs_ffi_resolve(mjs, name);
mjs_return(mjs, mjs_mk_foreign(mjs, addr));
}
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);
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_get_context(mjs);
furi_assert(worker);
req_object = js_module_require(worker->modules, name, len);
}
mjs_return(mjs, req_object);
}
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);
mjs_val_t ret = mjs_mk_string(mjs, tmp_str, ~0, true);
mjs_return(mjs, ret);
}
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);
mjs_val_t ret = mjs_mk_string(mjs, tmp_str, ~0, true);
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();
composite_api_resolver_add(worker->resolver, firmware_api_interface);
composite_api_resolver_add(worker->resolver, application_api_interface);
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, 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, 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);
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);
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) {
worker->app_callback(JsThreadEventError, mjs_strerror(mjs, err), worker->context);
}
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, stack_trace, worker->context);
}
}
} else {
if(worker->app_callback) {
worker->app_callback(JsThreadEventDone, NULL, worker->context);
}
}
js_modules_destroy(worker->modules);
mjs_destroy(mjs);
composite_api_resolver_free(worker->resolver);
return 0;
}
JsThread* js_thread_run(const char* script_path, JsThreadCallback callback, void* context) {
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;
worker->context = context;
furi_thread_start(worker->thread);
return worker;
}
void js_thread_stop(JsThread* worker) {
furi_thread_flags_set(furi_thread_get_id(worker->thread), ThreadEventStop);
furi_thread_join(worker->thread);
furi_thread_free(worker->thread);
furi_string_free(worker->path);
free(worker);
}

View file

@ -0,0 +1,16 @@
#pragma once
typedef struct JsThread JsThread;
typedef enum {
JsThreadEventDone,
JsThreadEventError,
JsThreadEventPrint,
JsThreadEventErrorTrace,
} 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);

View file

@ -0,0 +1,25 @@
#pragma once
#include <furi.h>
#include <mjs_core_public.h>
#include <mjs_ffi_public.h>
#include <mjs_exec_public.h>
#include <mjs_object_public.h>
#include <mjs_string_public.h>
#include <mjs_array_public.h>
#include <mjs_util_public.h>
#include <mjs_primitive_public.h>
#include <mjs_array_buf_public.h>
#define INST_PROP_NAME "_"
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);

View file

@ -0,0 +1,410 @@
#include <core/common_defines.h>
#include "../js_modules.h"
#include <furi_hal.h>
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);
}
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, 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;
}
static 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);
}
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;
}

View file

@ -0,0 +1,154 @@
#include <core/common_defines.h>
#include "../js_modules.h"
#include <dialogs/dialogs.h>
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;
}
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_msg_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);
}
DialogMessageButton result = dialog_message_show(dialogs, message);
dialog_message_free(message);
furi_record_close(RECORD_DIALOGS);
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);
}
}
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, 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;
}
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;
}

View file

@ -0,0 +1,36 @@
#include <core/common_defines.h>
#include "../js_modules.h"
#include <furi_hal_version.h>
#include <power/power_service/power.h>
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, 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;
}

View file

@ -0,0 +1,4 @@
#pragma once
#include "../js_thread_i.h"
void* js_flipper_create(struct mjs* mjs, mjs_val_t* object);

View file

@ -0,0 +1,109 @@
#include <core/common_defines.h>
#include "../js_modules.h"
#include <notification/notification_messages.h>
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);
}
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, 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;
}
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;
}

View file

@ -0,0 +1,585 @@
#include <core/common_defines.h>
#include <furi_hal.h>
#include "../js_modules.h"
#include <m-array.h>
#define TAG "js_uart"
#define RX_BUF_LEN 2048
typedef struct {
bool setup_done;
FuriStreamBuffer* rx_stream;
FuriHalSerialHandle* serial_handle;
struct mjs* mjs;
} JsUartInst;
typedef struct {
size_t len;
char* data;
} PatternArrayItem;
ARRAY_DEF(PatternArray, PatternArrayItem, M_POD_OPLIST);
static void
js_uart_on_async_rx(FuriHalSerialHandle* handle, FuriHalSerialRxEvent event, void* context) {
JsUartInst* uart = context;
furi_assert(uart);
if(event & FuriHalSerialRxEventData) {
uint8_t data = furi_hal_serial_async_rx(handle);
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);
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;
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);
uart->serial_handle = furi_hal_serial_control_acquire(FuriHalSerialIdLpuart);
if(uart->serial_handle) {
furi_hal_serial_init(uart->serial_handle, baudrate);
furi_hal_serial_async_rx_start(uart->serial_handle, js_uart_on_async_rx, uart, false);
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_serial_tx(uart->serial_handle, (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_serial_tx(uart->serial_handle, (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_serial_tx(uart->serial_handle, (uint8_t*)&byte_val, 1);
}
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_serial_tx(uart->serial_handle, (uint8_t*)buf, len);
} else {
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 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);
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);
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 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;
}
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;
}
} 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;
}
}
}
} else {
return false;
}
} else {
return false;
}
return true;
}
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 < array_len; i++) {
if(PatternArray_get(patterns, i)->data[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;
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);
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;
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;
}
}
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 = js_uart_expect_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
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) {
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->data[i]) {
pattern_found = -1;
break;
}
}
if((is_timeout) || (pattern_found >= 0)) {
break;
}
// Search other patterns with the same start char
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 = 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;
break;
}
}
if(pattern_candidate >= 0) {
continue;
}
// Nothing found - reset buffer
buf_len = 0;
}
if(is_timeout) {
FURI_LOG_W(TAG, "Expect: timeout");
}
for(PatternArray_it(it, patterns); !PatternArray_end_p(it); PatternArray_next(it)) {
const PatternArrayItem* item = PatternArray_cref(it);
free(item->data);
}
PatternArray_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, 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;
}
static void js_uart_destroy(void* inst) {
JsUartInst* js_uart = inst;
if(js_uart->setup_done) {
furi_hal_serial_async_rx_stop(js_uart->serial_handle);
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);
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;
}

View file

@ -0,0 +1,9 @@
#pragma once
#include <flipper_application/api_hashtable/api_hashtable.h>
/*
* Resolver interface with private application's symbols.
* Implementation is contained in app_api_table.c
*/
extern const ElfApiInterface* const application_api_interface;

View file

@ -0,0 +1,27 @@
#include <flipper_application/api_hashtable/api_hashtable.h>
#include <flipper_application/api_hashtable/compilesort.hpp>
/*
* 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;

View file

@ -0,0 +1,13 @@
#include <assets_icons.h>
#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<sym_entry>(
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)));

View file

@ -0,0 +1,18 @@
#pragma once
#include <furi.h>
#include <mjs_core_public.h>
#ifdef __cplusplus
extern "C" {
#endif
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

View file

@ -0,0 +1,43 @@
#pragma once
#include <stdint.h>
/*
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";

View file

@ -0,0 +1,164 @@
#include "../js_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 JsConsoleView {
View* view;
};
typedef struct {
FuriString* text[CONSOLE_LINES];
} JsConsoleViewModel;
static void console_view_draw_callback(Canvas* canvas, void* _model) {
JsConsoleViewModel* 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);
return false;
}
void console_view_push_line(JsConsoleView* console_view, const char* text, bool line_trimmed) {
with_view_model(
console_view->view,
JsConsoleViewModel * 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(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;
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);
}
}
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(JsConsoleViewModel));
with_view_model(
console_view->view,
JsConsoleViewModel * model,
{
for(size_t i = 0; i < CONSOLE_LINES; i++) {
model->text[i] = furi_string_alloc();
}
},
true);
return console_view;
}
void console_view_free(JsConsoleView* console_view) {
with_view_model(
console_view->view,
JsConsoleViewModel * 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(JsConsoleView* console_view) {
return console_view->view;
}

View file

@ -0,0 +1,13 @@
#pragma once
#include <gui/view.h>
typedef struct JsConsoleView JsConsoleView;
JsConsoleView* console_view_alloc(void);
void console_view_free(JsConsoleView* console_view);
View* console_view_get_view(JsConsoleView* console_view);
void console_view_print(JsConsoleView* console_view, const char* text);

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View file

@ -38,6 +38,7 @@ libs = env.BuildModules(
"lfrfid",
"flipper_application",
"music_worker",
"mjs",
"nanopb",
"update_util",
"heatshrink",

36
lib/mjs/SConscript Normal file
View file

@ -0,0 +1,36 @@
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_array_public.h"),
File("mjs_primitive_public.h"),
File("mjs_util_public.h"),
File("mjs_array_buf_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")

157
lib/mjs/common/cs_dbg.c Normal file
View file

@ -0,0 +1,157 @@
/*
* 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 <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#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
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;
}
#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
}

148
lib/mjs/common/cs_dbg.h Normal file
View file

@ -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 <stdio.h>
#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_ */

108
lib/mjs/common/cs_dirent.c Normal file
View file

@ -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;

View file

@ -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 <limits.h>
#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_ */

65
lib/mjs/common/cs_file.c Normal file
View file

@ -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 <stdio.h>
#include <stdlib.h>
#ifdef CS_MMAP
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#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

48
lib/mjs/common/cs_file.h Normal file
View file

@ -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_ */

91
lib/mjs/common/cs_time.c Normal file
View file

@ -0,0 +1,91 @@
/*
* 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"
#if CS_ENABLE_STDIO
#ifndef _WIN32
#include <stddef.h>
/*
* 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 <sys/time.h>
#endif
#else
#include <windows.h>
#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;
}
#endif

42
lib/mjs/common/cs_time.h Normal file
View file

@ -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 <time.h>
#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_ */

View file

@ -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;
}

View file

@ -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 <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#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_ */

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,359 @@
/*
* Copyright (c) 2004-2013 Sergey Lyubka <valenok@gmail.com>
* 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 <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#if defined(_WIN32) && _MSC_VER < 1700
typedef int bool;
enum { false = 0, true = 1 };
#else
#include <stdbool.h>
#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_ */

151
lib/mjs/common/mbuf.c Normal file
View file

@ -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 <assert.h>
#include <string.h>
#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 */

111
lib/mjs/common/mbuf.h Normal file
View file

@ -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 <stdlib.h>
#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_ */

45
lib/mjs/common/mg_mem.h Normal file
View file

@ -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_ */

175
lib/mjs/common/mg_str.c Normal file
View file

@ -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 <ctype.h>
#include <stdlib.h>
#include <string.h>
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);
}

113
lib/mjs/common/mg_str.h Normal file
View file

@ -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 <stddef.h>
#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_ */

89
lib/mjs/common/platform.h Normal file
View file

@ -0,0 +1,89 @@
#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
#define CS_P_FLIPPER 19
/* Next id: 20 */
#ifndef CS_PLATFORM
#define CS_PLATFORM CS_P_FLIPPER
#endif
#ifndef CS_PLATFORM
#error "CS_PLATFORM is not specified and we couldn't guess it."
#endif
#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
#if CS_PLATFORM == CS_P_FLIPPER
#include "platforms/platform_flipper.h"
#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_ */

View file

@ -0,0 +1,65 @@
#include <furi.h>
#include <toolbox/stream/file_stream.h>
#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);
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 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;
(void)ln;
return 0;
}
void cs_log_printf(const char* fmt, ...) {
(void)fmt;
}

View file

@ -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.
*/
#pragma once
#if CS_PLATFORM == CS_P_FLIPPER
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#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_FLIPPER */

537
lib/mjs/common/str_util.c Normal file
View file

@ -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 1
#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 - <space> + ')
* 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 */

195
lib/mjs/common/str_util.h Normal file
View file

@ -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 <stdarg.h>
#include <stdlib.h>
#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 <stdint.h>
/*
* 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_ */

553
lib/mjs/ffi/ffi.c Normal file
View file

@ -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;
}

53
lib/mjs/ffi/ffi.h Normal file
View file

@ -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_ */

232
lib/mjs/mjs_array.c Normal file
View file

@ -0,0 +1,232 @@
/*
* Copyright (c) 2017 Cesanta Software Limited
* All rights reserved
*/
#include <stdio.h>
#include "common/str_util.h"
#include "mjs_array.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);
}

26
lib/mjs/mjs_array.h Normal file
View file

@ -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_ */

385
lib/mjs/mjs_array_buf.c Normal file
View file

@ -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));
}

27
lib/mjs/mjs_array_buf.h Normal file
View file

@ -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 */

View file

@ -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 */

View file

@ -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_ */

147
lib/mjs/mjs_bcode.c Normal file
View file

@ -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;
}

105
lib/mjs/mjs_bcode.h Normal file
View file

@ -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_ */

162
lib/mjs/mjs_builtin.c Normal file
View file

@ -0,0 +1,162 @@
/*
* 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"
#include "mjs_array_buf.h"
/*
* 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, "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));
mjs_init_builtin_array_buf(mjs, obj);
}

22
lib/mjs/mjs_builtin.h Normal file
View file

@ -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_ */

422
lib/mjs/mjs_core.c Normal file
View file

@ -0,0 +1,422 @@
/*
* 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);
mbuf_free(&mjs->array_buffers);
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* 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);
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);
mbuf_init(&mjs->array_buffers, 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, NULL, NULL);
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;
}
void mjs_exit(struct mjs* mjs) {
free(mjs->error_msg);
mjs->error_msg = NULL;
mjs->error = MJS_NEED_EXIT;
}
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) {
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;
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) {
// fprintf(fp, "%s", mjs->stack_trace);
}
if(msg == NULL) {
msg = "MJS error";
}
// 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;
}
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;
}
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;
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;
}
}
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) {
// fprintf(
// stderr,
// "ERROR during stack trace generation: wrong bcode offset %d\n",
// (int)offset);
filename = "<unknown-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;
}

137
lib/mjs/mjs_core.h Normal file
View file

@ -0,0 +1,137 @@
/*
* 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_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
};
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 mbuf array_buffers;
struct mjs_vals vals;
char* error_msg;
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 exec_flags_poller;
void* context;
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 */

286
lib/mjs/mjs_core_public.h Normal file
View file

@ -0,0 +1,286 @@
/*
* 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 <stdint.h>
#else
typedef unsigned __int64 uint64_t;
typedef int int32_t;
typedef unsigned char uint8_t;
#endif
#include <stdio.h>
#include <stddef.h>
#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;
/*
* 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_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` */
#if 0
/* Opaque structure. MJS engine context. */
struct mjs {
/* ... */
};
#endif
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,
MJS_TYPE_ARRAY_BUF,
MJS_TYPE_ARRAY_BUF_VIEW,
/* 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,
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_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(void* context);
/* 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, ...);
void mjs_exit(struct mjs* mjs);
void mjs_set_exec_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()`.
*
* 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);
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.
*
* 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_ */

86
lib/mjs/mjs_dataview.c Normal file
View file

@ -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);
}

32
lib/mjs/mjs_dataview.h Normal file
View file

@ -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_ */

1252
lib/mjs/mjs_exec.c Normal file

File diff suppressed because it is too large Load diff

27
lib/mjs/mjs_exec.h Normal file
View file

@ -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_ */

34
lib/mjs/mjs_exec_public.h Normal file
View file

@ -0,0 +1,34 @@
/*
* 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 <stdio.h>
#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_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_ */

33
lib/mjs/mjs_features.h Normal file
View file

@ -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_ */

1223
lib/mjs/mjs_ffi.c Normal file

File diff suppressed because it is too large Load diff

135
lib/mjs/mjs_ffi.h Normal file
View file

@ -0,0 +1,135 @@
/*
* 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 */
#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_ */

40
lib/mjs/mjs_ffi_public.h Normal file
View file

@ -0,0 +1,40 @@
/*
* 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, void* handle);
void* mjs_ffi_resolve(struct mjs* mjs, const char* symbol);
#if defined(__cplusplus)
}
#endif /* __cplusplus */
#endif /* MJS_FFI_PUBLIC_H_ */

535
lib/mjs/mjs_gc.c Normal file
View file

@ -0,0 +1,535 @@
/*
* Copyright (c) 2014 Cesanta Software Limited
* All rights reserved
*/
#include <stdio.h>
#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_based(*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_based(*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_based(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;
}

60
lib/mjs/mjs_gc.h Normal file
View file

@ -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_ */

25
lib/mjs/mjs_gc_public.h Normal file
View file

@ -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_ */

92
lib/mjs/mjs_internal.h Normal file
View file

@ -0,0 +1,92 @@
/*
* Copyright (c) 2016 Cesanta Software Limited
* All rights reserved
*/
#ifndef MJS_INTERNAL_H_
#define MJS_INTERNAL_H_
#include <assert.h>
#include <ctype.h>
#include <math.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#ifndef FAST
#define FAST
#endif
#ifndef STATIC
#define STATIC
#endif
#ifndef ENDL
#define ENDL "\n"
#endif
#ifndef MJS_EXPOSE_PRIVATE
#define MJS_EXPOSE_PRIVATE 1
#endif
#if MJS_EXPOSE_PRIVATE
#define MJS_PRIVATE
#else
#define MJS_PRIVATE 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 <windows.h>
#else
#if defined(__unix__) || defined(__APPLE__)
#include <dlfcn.h>
#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_ */

523
lib/mjs/mjs_json.c Normal file
View file

@ -0,0 +1,523 @@
/*
* 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_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)
/*
* 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_ARRAY_BUF:
case MJS_TYPE_ARRAY_BUF_VIEW:
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 /* \uXX XX */;
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:
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 */
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);
}

32
lib/mjs/mjs_json.h Normal file
View file

@ -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_ */

17
lib/mjs/mjs_license.h Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
*
* 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 <https://www.cesanta.com/license>.
*/

44
lib/mjs/mjs_mm.h Normal file
View file

@ -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_ */

399
lib/mjs/mjs_object.c Normal file
View file

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

59
lib/mjs/mjs_object.h Normal file
View file

@ -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_ */

Some files were not shown because too many files have changed in this diff Show more