mirror of
https://github.com/DarkFlippers/unleashed-firmware
synced 2024-11-10 06:54:19 +00:00
[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:
parent
389affd904
commit
0154018363
118 changed files with 17568 additions and 38 deletions
|
@ -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/*
|
||||
|
|
|
@ -29,6 +29,7 @@ static const char* known_ext[] = {
|
|||
[ArchiveFileTypeBadUsb] = ".txt",
|
||||
[ArchiveFileTypeU2f] = "?",
|
||||
[ArchiveFileTypeApplication] = ".fap",
|
||||
[ArchiveFileTypeJS] = ".js",
|
||||
[ArchiveFileTypeUpdateManifest] = ".fuf",
|
||||
[ArchiveFileTypeFolder] = "?",
|
||||
[ArchiveFileTypeUnknown] = "*",
|
||||
|
|
|
@ -16,6 +16,7 @@ typedef enum {
|
|||
ArchiveFileTypeU2f,
|
||||
ArchiveFileTypeUpdateManifest,
|
||||
ArchiveFileTypeApplication,
|
||||
ArchiveFileTypeJS,
|
||||
ArchiveFileTypeFolder,
|
||||
ArchiveFileTypeUnknown,
|
||||
ArchiveFileTypeLoading,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -5,6 +5,7 @@ App(
|
|||
provides=[
|
||||
"updater_app",
|
||||
"storage_move_to_sd",
|
||||
"js_app",
|
||||
# "archive",
|
||||
],
|
||||
)
|
||||
|
|
41
applications/system/js_app/application.fam
Normal file
41
applications/system/js_app/application.fam
Normal 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"],
|
||||
)
|
|
@ -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]);
|
||||
}
|
20
applications/system/js_app/examples/apps/Scripts/bad_uart.js
Normal file
20
applications/system/js_app/examples/apps/Scripts/bad_uart.js
Normal 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");
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
print("print", 1);
|
||||
console.log("log", 2);
|
||||
console.warn("warn", 3);
|
||||
console.error("error", 4);
|
||||
console.debug("debug", 5);
|
|
@ -0,0 +1,9 @@
|
|||
print("start");
|
||||
delay(1000)
|
||||
print("1");
|
||||
delay(1000)
|
||||
print("2");
|
||||
delay(1000)
|
||||
print("3");
|
||||
delay(1000)
|
||||
print("end");
|
19
applications/system/js_app/examples/apps/Scripts/dialog.js
Normal file
19
applications/system/js_app/examples/apps/Scripts/dialog.js
Normal 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");
|
||||
}
|
3
applications/system/js_app/examples/apps/Scripts/load.js
Normal file
3
applications/system/js_app/examples/apps/Scripts/load.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
let math = load("/ext/apps/Scripts/api.js");
|
||||
let result = math.add(5, 10);
|
||||
print(result);
|
|
@ -0,0 +1,3 @@
|
|||
({
|
||||
add: function (a, b) { return a + b; },
|
||||
})
|
|
@ -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);
|
||||
}
|
|
@ -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]));
|
||||
}
|
||||
}
|
BIN
applications/system/js_app/icon.png
Normal file
BIN
applications/system/js_app/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.5 KiB |
131
applications/system/js_app/js_app.c
Normal file
131
applications/system/js_app/js_app.c
Normal 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
|
10
applications/system/js_app/js_app_i.h
Normal file
10
applications/system/js_app/js_app_i.h
Normal 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;
|
126
applications/system/js_app/js_modules.c
Normal file
126
applications/system/js_app/js_modules.c
Normal 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;
|
||||
}
|
25
applications/system/js_app/js_modules.h
Normal file
25
applications/system/js_app/js_modules.h
Normal 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);
|
319
applications/system/js_app/js_thread.c
Normal file
319
applications/system/js_app/js_thread.c
Normal 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);
|
||||
}
|
16
applications/system/js_app/js_thread.h
Normal file
16
applications/system/js_app/js_thread.h
Normal 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);
|
25
applications/system/js_app/js_thread_i.h
Normal file
25
applications/system/js_app/js_thread_i.h
Normal 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);
|
410
applications/system/js_app/modules/js_badusb.c
Normal file
410
applications/system/js_app/modules/js_badusb.c
Normal 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;
|
||||
}
|
154
applications/system/js_app/modules/js_dialog.c
Normal file
154
applications/system/js_app/modules/js_dialog.c
Normal 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;
|
||||
}
|
36
applications/system/js_app/modules/js_flipper.c
Normal file
36
applications/system/js_app/modules/js_flipper.c
Normal 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;
|
||||
}
|
4
applications/system/js_app/modules/js_flipper.h
Normal file
4
applications/system/js_app/modules/js_flipper.h
Normal file
|
@ -0,0 +1,4 @@
|
|||
#pragma once
|
||||
#include "../js_thread_i.h"
|
||||
|
||||
void* js_flipper_create(struct mjs* mjs, mjs_val_t* object);
|
109
applications/system/js_app/modules/js_notification.c
Normal file
109
applications/system/js_app/modules/js_notification.c
Normal 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;
|
||||
}
|
585
applications/system/js_app/modules/js_uart.c
Normal file
585
applications/system/js_app/modules/js_uart.c
Normal 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;
|
||||
}
|
|
@ -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;
|
27
applications/system/js_app/plugin_api/app_api_table.cpp
Normal file
27
applications/system/js_app/plugin_api/app_api_table.cpp
Normal 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;
|
13
applications/system/js_app/plugin_api/app_api_table_i.h
Normal file
13
applications/system/js_app/plugin_api/app_api_table_i.h
Normal 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)));
|
18
applications/system/js_app/plugin_api/js_plugin_api.h
Normal file
18
applications/system/js_app/plugin_api/js_plugin_api.h
Normal 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
|
43
applications/system/js_app/views/console_font.h
Normal file
43
applications/system/js_app/views/console_font.h
Normal 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";
|
164
applications/system/js_app/views/console_view.c
Normal file
164
applications/system/js_app/views/console_view.c
Normal 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;
|
||||
}
|
13
applications/system/js_app/views/console_view.h
Normal file
13
applications/system/js_app/views/console_view.h
Normal 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);
|
BIN
assets/icons/Archive/js_script_10px.png
Normal file
BIN
assets/icons/Archive/js_script_10px.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.5 KiB |
|
@ -38,6 +38,7 @@ libs = env.BuildModules(
|
|||
"lfrfid",
|
||||
"flipper_application",
|
||||
"music_worker",
|
||||
"mjs",
|
||||
"nanopb",
|
||||
"update_util",
|
||||
"heatshrink",
|
||||
|
|
36
lib/mjs/SConscript
Normal file
36
lib/mjs/SConscript
Normal 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
157
lib/mjs/common/cs_dbg.c
Normal 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
148
lib/mjs/common/cs_dbg.h
Normal 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
108
lib/mjs/common/cs_dirent.c
Normal 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;
|
51
lib/mjs/common/cs_dirent.h
Normal file
51
lib/mjs/common/cs_dirent.h
Normal 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
65
lib/mjs/common/cs_file.c
Normal 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
48
lib/mjs/common/cs_file.h
Normal 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
91
lib/mjs/common/cs_time.c
Normal 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
42
lib/mjs/common/cs_time.h
Normal 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_ */
|
76
lib/mjs/common/cs_varint.c
Normal file
76
lib/mjs/common/cs_varint.c
Normal 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;
|
||||
}
|
59
lib/mjs/common/cs_varint.h
Normal file
59
lib/mjs/common/cs_varint.h
Normal 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_ */
|
1528
lib/mjs/common/frozen/frozen.c
Normal file
1528
lib/mjs/common/frozen/frozen.c
Normal file
File diff suppressed because it is too large
Load diff
359
lib/mjs/common/frozen/frozen.h
Normal file
359
lib/mjs/common/frozen/frozen.h
Normal 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
151
lib/mjs/common/mbuf.c
Normal 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
111
lib/mjs/common/mbuf.h
Normal 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
45
lib/mjs/common/mg_mem.h
Normal 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
175
lib/mjs/common/mg_str.c
Normal 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
113
lib/mjs/common/mg_str.h
Normal 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
89
lib/mjs/common/platform.h
Normal 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_ */
|
65
lib/mjs/common/platforms/platform_flipper.c
Normal file
65
lib/mjs/common/platforms/platform_flipper.c
Normal 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;
|
||||
}
|
48
lib/mjs/common/platforms/platform_flipper.h
Normal file
48
lib/mjs/common/platforms/platform_flipper.h
Normal 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
537
lib/mjs/common/str_util.c
Normal 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
195
lib/mjs/common/str_util.h
Normal 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
553
lib/mjs/ffi/ffi.c
Normal 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
53
lib/mjs/ffi/ffi.h
Normal 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
232
lib/mjs/mjs_array.c
Normal 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
26
lib/mjs/mjs_array.h
Normal 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
385
lib/mjs/mjs_array_buf.c
Normal 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
27
lib/mjs/mjs_array_buf.h
Normal 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 */
|
37
lib/mjs/mjs_array_buf_public.h
Normal file
37
lib/mjs/mjs_array_buf_public.h
Normal 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 */
|
47
lib/mjs/mjs_array_public.h
Normal file
47
lib/mjs/mjs_array_public.h
Normal 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
147
lib/mjs/mjs_bcode.c
Normal 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
105
lib/mjs/mjs_bcode.h
Normal 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
162
lib/mjs/mjs_builtin.c
Normal 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
22
lib/mjs/mjs_builtin.h
Normal 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
422
lib/mjs/mjs_core.c
Normal 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
137
lib/mjs/mjs_core.h
Normal 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
286
lib/mjs/mjs_core_public.h
Normal 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
86
lib/mjs/mjs_dataview.c
Normal 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
32
lib/mjs/mjs_dataview.h
Normal 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
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
27
lib/mjs/mjs_exec.h
Normal 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
34
lib/mjs/mjs_exec_public.h
Normal 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
33
lib/mjs/mjs_features.h
Normal 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
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
135
lib/mjs/mjs_ffi.h
Normal 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
40
lib/mjs/mjs_ffi_public.h
Normal 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
535
lib/mjs/mjs_gc.c
Normal 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
60
lib/mjs/mjs_gc.h
Normal 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
25
lib/mjs/mjs_gc_public.h
Normal 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
92
lib/mjs/mjs_internal.h
Normal 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
523
lib/mjs/mjs_json.c
Normal 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
32
lib/mjs/mjs_json.h
Normal 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
17
lib/mjs/mjs_license.h
Normal 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
44
lib/mjs/mjs_mm.h
Normal 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
399
lib/mjs/mjs_object.c
Normal 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
59
lib/mjs/mjs_object.h
Normal 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
Loading…
Reference in a new issue