@mikrojs/native 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CMakeLists.txt +198 -0
- package/LICENSE +21 -0
- package/README.md +49 -0
- package/cmake/mikrojs_bytecode.cmake +146 -0
- package/cmake.js +22 -0
- package/dist/index.d.ts +52 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +132 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +43 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/include/byteorder_apple.h +11 -0
- package/include/byteorder_windows.h +12 -0
- package/include/mikrojs/cbor_helpers.h +24 -0
- package/include/mikrojs/cutils_wrap.h +59 -0
- package/include/mikrojs/errors.h +144 -0
- package/include/mikrojs/mem.h +11 -0
- package/include/mikrojs/mik_color.h +32 -0
- package/include/mikrojs/mikrojs.h +331 -0
- package/include/mikrojs/platform.h +82 -0
- package/include/mikrojs/private.h +281 -0
- package/include/mikrojs/utils.h +125 -0
- package/package.json +100 -0
- package/prebuilds/darwin-arm64/mikrojs.napi.node +0 -0
- package/prebuilds/linux-arm64/mikrojs.napi.node +0 -0
- package/prebuilds/linux-x64/mikrojs.napi.node +0 -0
- package/runtime/ble/ble.ts +231 -0
- package/runtime/ble/types.ts +194 -0
- package/runtime/ble/uuid.ts +89 -0
- package/runtime/ble/validators.ts +61 -0
- package/runtime/cbor/cbor.ts +1 -0
- package/runtime/cbor/types.ts +8 -0
- package/runtime/console/types.ts +50 -0
- package/runtime/env/env.ts +17 -0
- package/runtime/env/types.ts +12 -0
- package/runtime/format/types.ts +4 -0
- package/runtime/fs/fs.ts +93 -0
- package/runtime/fs/types.ts +92 -0
- package/runtime/globals.d.ts +87 -0
- package/runtime/http/helpers.ts +222 -0
- package/runtime/http/native.ts +151 -0
- package/runtime/http/request.ts +25 -0
- package/runtime/i2c/i2c.ts +35 -0
- package/runtime/i2c/types.ts +55 -0
- package/runtime/inspect/types.ts +10 -0
- package/runtime/internal.d.ts +456 -0
- package/runtime/kv/nvs.ts +17 -0
- package/runtime/kv/rtc.ts +17 -0
- package/runtime/kv/shared.ts +107 -0
- package/runtime/kv/types.ts +150 -0
- package/runtime/neopixel/neopixel.ts +38 -0
- package/runtime/neopixel/types.ts +27 -0
- package/runtime/pin/pin.ts +51 -0
- package/runtime/pin/types.ts +49 -0
- package/runtime/pwm/pwm.ts +32 -0
- package/runtime/pwm/types.ts +29 -0
- package/runtime/reader/reader.ts +167 -0
- package/runtime/reader/types.ts +34 -0
- package/runtime/result/native-result.node-shim.ts +44 -0
- package/runtime/result/result.ts +26 -0
- package/runtime/result/types.ts +60 -0
- package/runtime/schema/schema.ts +321 -0
- package/runtime/schema/types.ts +152 -0
- package/runtime/sleep/sleep.ts +14 -0
- package/runtime/sleep/types.ts +44 -0
- package/runtime/sntp/sntp.ts +54 -0
- package/runtime/sntp/types.ts +38 -0
- package/runtime/spi/spi.ts +31 -0
- package/runtime/spi/types.ts +42 -0
- package/runtime/stdio/stdio.ts +44 -0
- package/runtime/stdio/types.ts +22 -0
- package/runtime/stream/stream.ts +150 -0
- package/runtime/stream/types.ts +47 -0
- package/runtime/sys/sys.ts +90 -0
- package/runtime/sys/types.ts +131 -0
- package/runtime/test/test.ts +595 -0
- package/runtime/test/types.ts +97 -0
- package/runtime/uart/types.ts +75 -0
- package/runtime/uart/uart.ts +51 -0
- package/runtime/wifi/types.ts +156 -0
- package/runtime/wifi/wifi.ts +208 -0
- package/scripts/bundle-runtime.js +149 -0
- package/scripts/compare-minifiers.js +189 -0
- package/scripts/compile-bytecode.sh +38 -0
- package/scripts/copy-prebuild.js +20 -0
- package/scripts/generate-symbol-map.js +146 -0
- package/src/builtins.cpp +82 -0
- package/src/cutils_compat.c +38 -0
- package/src/eval_bytecode.cpp +42 -0
- package/src/fs.cpp +878 -0
- package/src/mem.cpp +63 -0
- package/src/mik_abort.cpp +160 -0
- package/src/mik_app_config.cpp +358 -0
- package/src/mik_cbor.cpp +334 -0
- package/src/mik_color.cpp +46 -0
- package/src/mik_console.cpp +422 -0
- package/src/mik_inspect.cpp +850 -0
- package/src/mik_repl.cpp +1122 -0
- package/src/mik_result.cpp +344 -0
- package/src/mik_stdio.cpp +147 -0
- package/src/mik_sys.cpp +239 -0
- package/src/mik_text_encoding.cpp +443 -0
- package/src/mikrojs.cpp +942 -0
- package/src/modules.cpp +944 -0
- package/src/platform_posix.cpp +134 -0
- package/src/timers.cpp +208 -0
- package/src/utils.cpp +173 -0
package/src/mem.cpp
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#include "mikrojs/mem.h"
|
|
2
|
+
|
|
3
|
+
#include <cstdint>
|
|
4
|
+
#include <stddef.h>
|
|
5
|
+
#include <stdlib.h>
|
|
6
|
+
#include <string.h>
|
|
7
|
+
|
|
8
|
+
/*
|
|
9
|
+
* QuickJS uses malloc_usable_size to track memory consumption against its
|
|
10
|
+
* memory limit. ESP-IDF lacks a standard malloc_usable_size, and the
|
|
11
|
+
* heap_caps_get_allocated_size alternative reports block sizes including
|
|
12
|
+
* allocator overhead, causing QuickJS to over-count usage.
|
|
13
|
+
*
|
|
14
|
+
* We prepend each allocation with a size_t header storing the requested size.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
#define HDR_SIZE sizeof(size_t)
|
|
18
|
+
|
|
19
|
+
static inline size_t hdr_read(void* user) {
|
|
20
|
+
size_t sz;
|
|
21
|
+
memcpy(&sz, static_cast<char*>(user) - HDR_SIZE, sizeof(sz));
|
|
22
|
+
return sz;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
static inline void hdr_write(void* raw, size_t sz) {
|
|
26
|
+
memcpy(raw, &sz, sizeof(sz));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
size_t mik__malloc_usable_size(const void* ptr) {
|
|
30
|
+
if (!ptr) return 0;
|
|
31
|
+
return hdr_read(const_cast<void*>(ptr));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
void* mik__malloc(size_t size) {
|
|
35
|
+
void* raw = malloc(size + HDR_SIZE);
|
|
36
|
+
if (!raw) return nullptr;
|
|
37
|
+
hdr_write(raw, size);
|
|
38
|
+
return static_cast<char*>(raw) + HDR_SIZE;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
void* mik__mallocz(size_t size) { return mik__calloc(1, size); }
|
|
42
|
+
|
|
43
|
+
void* mik__calloc(size_t count, size_t size) {
|
|
44
|
+
if (size && count > SIZE_MAX / size) return nullptr;
|
|
45
|
+
size_t total = count * size;
|
|
46
|
+
void* raw = calloc(1, total + HDR_SIZE);
|
|
47
|
+
if (!raw) return nullptr;
|
|
48
|
+
hdr_write(raw, total);
|
|
49
|
+
return static_cast<char*>(raw) + HDR_SIZE;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
void mik__free(void* ptr) {
|
|
53
|
+
if (!ptr) return;
|
|
54
|
+
free(static_cast<char*>(ptr) - HDR_SIZE);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
void* mik__realloc(void* ptr, size_t size) {
|
|
58
|
+
void* raw = ptr ? static_cast<char*>(ptr) - HDR_SIZE : nullptr;
|
|
59
|
+
raw = realloc(raw, size + HDR_SIZE);
|
|
60
|
+
if (!raw) return nullptr;
|
|
61
|
+
hdr_write(raw, size);
|
|
62
|
+
return static_cast<char*>(raw) + HDR_SIZE;
|
|
63
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
#include <quickjs.h>
|
|
2
|
+
|
|
3
|
+
#include "mikrojs/utils.h"
|
|
4
|
+
|
|
5
|
+
/* Minimal AbortController / AbortSignal implementation.
|
|
6
|
+
*
|
|
7
|
+
* This is a lightweight subset of the WHATWG DOM spec — just enough
|
|
8
|
+
* for fetch timeouts and cooperative cancellation. No EventTarget
|
|
9
|
+
* dependency: listeners are stored in a simple array.
|
|
10
|
+
*
|
|
11
|
+
* Implements:
|
|
12
|
+
* AbortSignal: aborted, reason, throwIfAborted(), onabort,
|
|
13
|
+
* addEventListener("abort", fn), removeEventListener("abort", fn)
|
|
14
|
+
* AbortSignal.abort(reason) — pre-aborted signal
|
|
15
|
+
* AbortSignal.timeout(ms) — auto-aborts after delay
|
|
16
|
+
* AbortSignal.any(signals) — aborts when any input signal aborts
|
|
17
|
+
* AbortController: signal, abort(reason)
|
|
18
|
+
*
|
|
19
|
+
* Lazy-initialized: the JS class hierarchy is compiled and installed
|
|
20
|
+
* on first access to any of the four globals (AbortController,
|
|
21
|
+
* AbortSignal, AbortError, TimeoutError).
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
static const char abort_js[] = R"JS(
|
|
25
|
+
(function(g) {
|
|
26
|
+
"use strict";
|
|
27
|
+
|
|
28
|
+
class AbortError extends Error {
|
|
29
|
+
constructor(message) { super(message || "The operation was aborted"); }
|
|
30
|
+
get name() { return "AbortError"; }
|
|
31
|
+
}
|
|
32
|
+
class TimeoutError extends Error {
|
|
33
|
+
constructor(message) { super(message || "The operation timed out"); }
|
|
34
|
+
get name() { return "TimeoutError"; }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const _a = Symbol(), _r = Symbol(), _l = Symbol(), _s = Symbol();
|
|
38
|
+
const ABORT_REASON = () => new AbortError();
|
|
39
|
+
|
|
40
|
+
function doAbort(signal, reason) {
|
|
41
|
+
if (signal[_a]) return;
|
|
42
|
+
signal[_a] = true;
|
|
43
|
+
signal[_r] = reason;
|
|
44
|
+
if (typeof signal.onabort === "function") signal.onabort();
|
|
45
|
+
for (const fn of signal[_l]) fn();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
class AbortSignal {
|
|
49
|
+
constructor() {
|
|
50
|
+
this[_a] = false;
|
|
51
|
+
this[_r] = undefined;
|
|
52
|
+
this[_l] = [];
|
|
53
|
+
this.onabort = null;
|
|
54
|
+
}
|
|
55
|
+
get aborted() { return this[_a]; }
|
|
56
|
+
get reason() { return this[_r]; }
|
|
57
|
+
throwIfAborted() { if (this[_a]) throw this[_r]; }
|
|
58
|
+
addEventListener(type, fn) {
|
|
59
|
+
if (type === "abort" && typeof fn === "function") this[_l].push(fn);
|
|
60
|
+
}
|
|
61
|
+
removeEventListener(type, fn) {
|
|
62
|
+
if (type === "abort") this[_l] = this[_l].filter(f => f !== fn);
|
|
63
|
+
}
|
|
64
|
+
static abort(reason) {
|
|
65
|
+
const s = new AbortSignal();
|
|
66
|
+
doAbort(s, reason !== undefined ? reason : ABORT_REASON());
|
|
67
|
+
return s;
|
|
68
|
+
}
|
|
69
|
+
static timeout(ms) {
|
|
70
|
+
const s = new AbortSignal();
|
|
71
|
+
setTimeout(() => doAbort(s, new TimeoutError()), ms);
|
|
72
|
+
return s;
|
|
73
|
+
}
|
|
74
|
+
static any(signals) {
|
|
75
|
+
const s = new AbortSignal();
|
|
76
|
+
for (const i of signals) {
|
|
77
|
+
if (i.aborted) { doAbort(s, i.reason); return s; }
|
|
78
|
+
}
|
|
79
|
+
for (const i of signals) {
|
|
80
|
+
i.addEventListener("abort", () => doAbort(s, i.reason));
|
|
81
|
+
}
|
|
82
|
+
return s;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
class AbortController {
|
|
87
|
+
constructor() { this[_s] = new AbortSignal(); }
|
|
88
|
+
get signal() { return this[_s]; }
|
|
89
|
+
abort(reason) { doAbort(this[_s], reason !== undefined ? reason : ABORT_REASON()); }
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
g.AbortError = AbortError;
|
|
93
|
+
g.TimeoutError = TimeoutError;
|
|
94
|
+
g.AbortSignal = AbortSignal;
|
|
95
|
+
g.AbortController = AbortController;
|
|
96
|
+
})
|
|
97
|
+
)JS";
|
|
98
|
+
|
|
99
|
+
/* Names of the globals this module installs */
|
|
100
|
+
static const char* const abort_global_names[] = {"AbortController", "AbortSignal", "AbortError",
|
|
101
|
+
"TimeoutError"};
|
|
102
|
+
static constexpr int ABORT_GLOBAL_COUNT = 4;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Lazy getter: on first access to any of the four globals, delete all
|
|
106
|
+
* lazy getters, evaluate the JS to install real values, then return
|
|
107
|
+
* the requested one. The magic parameter identifies which global.
|
|
108
|
+
*
|
|
109
|
+
* Signature must match JS_CFUNC_getter_magic: (ctx, this_val, magic).
|
|
110
|
+
*/
|
|
111
|
+
static JSValue mik__abort_lazy_get(JSContext* ctx, JSValue this_val, int magic) {
|
|
112
|
+
|
|
113
|
+
JSValue global_obj = JS_GetGlobalObject(ctx);
|
|
114
|
+
|
|
115
|
+
/* Remove all four lazy getters so the JS assignment (g.X = Y) works */
|
|
116
|
+
for (int i = 0; i < ABORT_GLOBAL_COUNT; i++) {
|
|
117
|
+
JSAtom prop = JS_NewAtom(ctx, abort_global_names[i]);
|
|
118
|
+
JS_DeleteProperty(ctx, global_obj, prop, 0);
|
|
119
|
+
JS_FreeAtom(ctx, prop);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/* Evaluate and install the real globals */
|
|
123
|
+
JSValue wrapper =
|
|
124
|
+
JS_Eval(ctx, abort_js, sizeof(abort_js) - 1, "<abort>", JS_EVAL_TYPE_GLOBAL);
|
|
125
|
+
if (JS_IsException(wrapper)) {
|
|
126
|
+
JSValue exc = JS_GetException(ctx);
|
|
127
|
+
JS_FreeValue(ctx, exc);
|
|
128
|
+
JS_FreeValue(ctx, global_obj);
|
|
129
|
+
return JS_UNDEFINED;
|
|
130
|
+
}
|
|
131
|
+
JSValue ret = JS_Call(ctx, wrapper, JS_UNDEFINED, 1, &global_obj);
|
|
132
|
+
JS_FreeValue(ctx, wrapper);
|
|
133
|
+
if (JS_IsException(ret)) {
|
|
134
|
+
JSValue exc = JS_GetException(ctx);
|
|
135
|
+
JS_FreeValue(ctx, exc);
|
|
136
|
+
JS_FreeValue(ctx, global_obj);
|
|
137
|
+
return JS_UNDEFINED;
|
|
138
|
+
}
|
|
139
|
+
JS_FreeValue(ctx, ret);
|
|
140
|
+
|
|
141
|
+
/* Return the requested global */
|
|
142
|
+
JSValue val = JS_GetPropertyStr(ctx, global_obj, abort_global_names[magic]);
|
|
143
|
+
JS_FreeValue(ctx, global_obj);
|
|
144
|
+
return val;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
void mik__abort_init(JSContext* ctx, JSValue global_obj) {
|
|
148
|
+
/* Install lazy getters instead of eagerly evaluating the JS.
|
|
149
|
+
* Each getter shares the same C function, distinguished by magic. */
|
|
150
|
+
for (int i = 0; i < ABORT_GLOBAL_COUNT; i++) {
|
|
151
|
+
JSAtom prop = JS_NewAtom(ctx, abort_global_names[i]);
|
|
152
|
+
JSCFunctionType ft;
|
|
153
|
+
ft.getter_magic = mik__abort_lazy_get;
|
|
154
|
+
JSValue getter = JS_NewCFunction2(ctx, ft.generic, abort_global_names[i], 0,
|
|
155
|
+
JS_CFUNC_getter_magic, i);
|
|
156
|
+
JS_DefinePropertyGetSet(ctx, global_obj, prop, getter, JS_UNDEFINED,
|
|
157
|
+
JS_PROP_CONFIGURABLE | JS_PROP_ENUMERABLE);
|
|
158
|
+
JS_FreeAtom(ctx, prop);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
#include "mikrojs/mikrojs.h"
|
|
2
|
+
|
|
3
|
+
#include <cstdio>
|
|
4
|
+
#include <cstdlib>
|
|
5
|
+
#include <cstring>
|
|
6
|
+
#include <dirent.h>
|
|
7
|
+
#include <sys/stat.h>
|
|
8
|
+
|
|
9
|
+
#include "mikrojs/platform.h"
|
|
10
|
+
#include "mikrojs/private.h"
|
|
11
|
+
|
|
12
|
+
static const char* TAG = "mik_app_config";
|
|
13
|
+
|
|
14
|
+
void MIK_DefaultConfig(MIKConfig* config) {
|
|
15
|
+
config->restart_on_uncaught_exception = false;
|
|
16
|
+
config->restart_delay_ms = 1000;
|
|
17
|
+
config->stack_size = 0;
|
|
18
|
+
config->mem_reserved = 64 * 1024;
|
|
19
|
+
config->fs_read_max = 0; /* 0 = runtime default (65536) */
|
|
20
|
+
config->entry_point[0] = '\0';
|
|
21
|
+
config->wifi_country[0] = '\0';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/* Minimal JSON parser for config file — avoids cJSON dependency.
|
|
25
|
+
* Only handles the flat object with bool/number fields we need. */
|
|
26
|
+
static bool mik__json_get_bool(const char* json, const char* key, bool* out) {
|
|
27
|
+
char pattern[128];
|
|
28
|
+
snprintf(pattern, sizeof(pattern), "\"%s\"", key);
|
|
29
|
+
const char* p = strstr(json, pattern);
|
|
30
|
+
if (!p) return false;
|
|
31
|
+
p += strlen(pattern);
|
|
32
|
+
while (*p == ' ' || *p == '\t' || *p == ':') p++;
|
|
33
|
+
if (strncmp(p, "true", 4) == 0) { *out = true; return true; }
|
|
34
|
+
if (strncmp(p, "false", 5) == 0) { *out = false; return true; }
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
static bool mik__json_get_string(const char* json, const char* key, char* out, size_t out_size) {
|
|
39
|
+
char pattern[128];
|
|
40
|
+
snprintf(pattern, sizeof(pattern), "\"%s\"", key);
|
|
41
|
+
const char* p = strstr(json, pattern);
|
|
42
|
+
if (!p) return false;
|
|
43
|
+
p += strlen(pattern);
|
|
44
|
+
while (*p == ' ' || *p == '\t' || *p == ':') p++;
|
|
45
|
+
if (*p != '"') return false;
|
|
46
|
+
p++; /* skip opening quote */
|
|
47
|
+
const char* end = strchr(p, '"');
|
|
48
|
+
if (!end) return false;
|
|
49
|
+
size_t len = end - p;
|
|
50
|
+
if (len >= out_size) len = out_size - 1;
|
|
51
|
+
memcpy(out, p, len);
|
|
52
|
+
out[len] = '\0';
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
static bool mik__json_get_number(const char* json, const char* key, double* out) {
|
|
57
|
+
char pattern[128];
|
|
58
|
+
snprintf(pattern, sizeof(pattern), "\"%s\"", key);
|
|
59
|
+
const char* p = strstr(json, pattern);
|
|
60
|
+
if (!p) return false;
|
|
61
|
+
p += strlen(pattern);
|
|
62
|
+
while (*p == ' ' || *p == '\t' || *p == ':') p++;
|
|
63
|
+
char* end = nullptr;
|
|
64
|
+
double val = strtod(p, &end);
|
|
65
|
+
if (end == p) return false;
|
|
66
|
+
*out = val;
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/* Read a JSON file into a malloc'd buffer. Caller must free(). Returns NULL on failure. */
|
|
71
|
+
static char* mik__read_json_file(const char* filepath, struct stat* st) {
|
|
72
|
+
if (stat(filepath, st) != 0) return nullptr;
|
|
73
|
+
FILE* f = fopen(filepath, "r");
|
|
74
|
+
if (!f) return nullptr;
|
|
75
|
+
char* buf = static_cast<char*>(malloc(st->st_size + 1));
|
|
76
|
+
if (!buf) { fclose(f); return nullptr; }
|
|
77
|
+
size_t n = fread(buf, 1, st->st_size, f);
|
|
78
|
+
fclose(f);
|
|
79
|
+
buf[n] = '\0';
|
|
80
|
+
return buf;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/* Search for package.json: first at base_path, then in immediate subdirectories.
|
|
84
|
+
* Writes the found path into out_path and returns a malloc'd buffer (caller frees),
|
|
85
|
+
* or returns NULL if not found. */
|
|
86
|
+
static char* mik__find_package_json(const char* base_path, char* out_path, size_t out_size,
|
|
87
|
+
struct stat* st) {
|
|
88
|
+
/* Try base_path/package.json first */
|
|
89
|
+
snprintf(out_path, out_size, "%s/package.json", base_path);
|
|
90
|
+
char* buf = mik__read_json_file(out_path, st);
|
|
91
|
+
if (buf) return buf;
|
|
92
|
+
|
|
93
|
+
/* Scan immediate subdirectories */
|
|
94
|
+
DIR* dir = opendir(base_path);
|
|
95
|
+
if (!dir) return nullptr;
|
|
96
|
+
struct dirent* ent;
|
|
97
|
+
while ((ent = readdir(dir)) != nullptr) {
|
|
98
|
+
if (ent->d_name[0] == '.') continue;
|
|
99
|
+
char subdir[PATH_MAX];
|
|
100
|
+
snprintf(subdir, sizeof(subdir), "%s/%s", base_path, ent->d_name);
|
|
101
|
+
struct stat dir_st;
|
|
102
|
+
if (stat(subdir, &dir_st) != 0 || !S_ISDIR(dir_st.st_mode)) continue;
|
|
103
|
+
snprintf(out_path, out_size, "%s/%s/package.json", base_path, ent->d_name);
|
|
104
|
+
buf = mik__read_json_file(out_path, st);
|
|
105
|
+
if (buf) {
|
|
106
|
+
closedir(dir);
|
|
107
|
+
return buf;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
closedir(dir);
|
|
111
|
+
return nullptr;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/* Locate the value of a top-level key within a JSON object, respecting
|
|
115
|
+
* string quoting and nesting. Returns a pointer at the first non-whitespace
|
|
116
|
+
* character after the `:`, or NULL if the key is not a direct child of the
|
|
117
|
+
* root object. This avoids false matches where the search substring appears
|
|
118
|
+
* inside a string value (e.g. a "description" field containing the word
|
|
119
|
+
* "tests") or inside a nested object. Used by array/typed readers where
|
|
120
|
+
* precision matters; existing flat scalar helpers above are more permissive
|
|
121
|
+
* and preserve their prior behavior. */
|
|
122
|
+
static const char* mik__json_find_top_level_value(const char* json, const char* key) {
|
|
123
|
+
size_t key_len = strlen(key);
|
|
124
|
+
const char* p = json;
|
|
125
|
+
while (*p && *p != '{') p++;
|
|
126
|
+
if (*p != '{') return nullptr;
|
|
127
|
+
p++;
|
|
128
|
+
int depth = 1;
|
|
129
|
+
bool at_key = true;
|
|
130
|
+
while (*p && depth > 0) {
|
|
131
|
+
char c = *p;
|
|
132
|
+
if (c == '"') {
|
|
133
|
+
bool was_at_key = (depth == 1 && at_key);
|
|
134
|
+
const char* str = p + 1;
|
|
135
|
+
p = str;
|
|
136
|
+
while (*p && *p != '"') {
|
|
137
|
+
if (*p == '\\' && p[1]) p += 2;
|
|
138
|
+
else p++;
|
|
139
|
+
}
|
|
140
|
+
if (*p != '"') return nullptr;
|
|
141
|
+
if (was_at_key && (size_t)(p - str) == key_len && memcmp(str, key, key_len) == 0) {
|
|
142
|
+
p++;
|
|
143
|
+
while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') p++;
|
|
144
|
+
if (*p != ':') return nullptr;
|
|
145
|
+
p++;
|
|
146
|
+
while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') p++;
|
|
147
|
+
return p;
|
|
148
|
+
}
|
|
149
|
+
p++;
|
|
150
|
+
at_key = false;
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
if (c == '{' || c == '[') { depth++; p++; at_key = false; continue; }
|
|
154
|
+
if (c == '}' || c == ']') { depth--; p++; continue; }
|
|
155
|
+
if (c == ',' && depth == 1) { at_key = true; p++; continue; }
|
|
156
|
+
p++;
|
|
157
|
+
}
|
|
158
|
+
return nullptr;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/* Parse a "tests": ["path1", "path2", ...] array out of a JSON string.
|
|
162
|
+
* Only handles a flat array of quoted strings — no nested structures,
|
|
163
|
+
* no escape processing, no trailing commas. Returns 0 on success (even
|
|
164
|
+
* when the key is absent or the array is empty), nonzero on malloc error.
|
|
165
|
+
* Caller frees via MIK_FreeTests. */
|
|
166
|
+
static int mik__json_get_string_array(const char* json, const char* key, char*** out_items,
|
|
167
|
+
size_t* out_count) {
|
|
168
|
+
*out_items = nullptr;
|
|
169
|
+
*out_count = 0;
|
|
170
|
+
|
|
171
|
+
const char* p = mik__json_find_top_level_value(json, key);
|
|
172
|
+
if (!p || *p != '[') return 0;
|
|
173
|
+
p++;
|
|
174
|
+
|
|
175
|
+
/* First pass: count strings. */
|
|
176
|
+
size_t count = 0;
|
|
177
|
+
const char* scan = p;
|
|
178
|
+
while (*scan) {
|
|
179
|
+
while (*scan == ' ' || *scan == '\t' || *scan == '\n' || *scan == '\r' || *scan == ',') {
|
|
180
|
+
scan++;
|
|
181
|
+
}
|
|
182
|
+
if (*scan == ']' || *scan == '\0') break;
|
|
183
|
+
if (*scan != '"') return 0; /* malformed */
|
|
184
|
+
scan++;
|
|
185
|
+
const char* end = strchr(scan, '"');
|
|
186
|
+
if (!end) return 0;
|
|
187
|
+
count++;
|
|
188
|
+
scan = end + 1;
|
|
189
|
+
}
|
|
190
|
+
if (count == 0) return 0;
|
|
191
|
+
|
|
192
|
+
char** items = static_cast<char**>(calloc(count, sizeof(char*)));
|
|
193
|
+
if (!items) return -1;
|
|
194
|
+
|
|
195
|
+
/* Second pass: copy strings. */
|
|
196
|
+
size_t i = 0;
|
|
197
|
+
while (*p && i < count) {
|
|
198
|
+
while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r' || *p == ',') p++;
|
|
199
|
+
if (*p == ']' || *p == '\0') break;
|
|
200
|
+
if (*p != '"') break;
|
|
201
|
+
p++;
|
|
202
|
+
const char* end = strchr(p, '"');
|
|
203
|
+
if (!end) break;
|
|
204
|
+
size_t len = end - p;
|
|
205
|
+
char* s = static_cast<char*>(malloc(len + 1));
|
|
206
|
+
if (!s) {
|
|
207
|
+
for (size_t j = 0; j < i; j++) free(items[j]);
|
|
208
|
+
free(items);
|
|
209
|
+
return -1;
|
|
210
|
+
}
|
|
211
|
+
memcpy(s, p, len);
|
|
212
|
+
s[len] = '\0';
|
|
213
|
+
items[i++] = s;
|
|
214
|
+
p = end + 1;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
*out_items = items;
|
|
218
|
+
*out_count = i;
|
|
219
|
+
return 0;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
int MIK_LoadTests(const char* base_path, char*** out_paths, size_t* out_count) {
|
|
223
|
+
*out_paths = nullptr;
|
|
224
|
+
*out_count = 0;
|
|
225
|
+
|
|
226
|
+
char path[PATH_MAX];
|
|
227
|
+
struct stat st;
|
|
228
|
+
char* buf = mik__find_package_json(base_path, path, sizeof(path), &st);
|
|
229
|
+
if (!buf) return 0;
|
|
230
|
+
|
|
231
|
+
/* Derive the same prefix MIK_LoadConfig uses for "main", so test paths
|
|
232
|
+
* are resolved relative to the app directory rather than base_path. */
|
|
233
|
+
const char* pkg_dir = path + strlen(base_path);
|
|
234
|
+
char prefix[128] = "";
|
|
235
|
+
const char* last_slash = strrchr(pkg_dir, '/');
|
|
236
|
+
if (last_slash && last_slash != pkg_dir) {
|
|
237
|
+
size_t plen = last_slash - pkg_dir;
|
|
238
|
+
if (plen >= sizeof(prefix)) plen = sizeof(prefix) - 1;
|
|
239
|
+
memcpy(prefix, pkg_dir, plen);
|
|
240
|
+
prefix[plen] = '\0';
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
char** raw_items = nullptr;
|
|
244
|
+
size_t raw_count = 0;
|
|
245
|
+
int rc = mik__json_get_string_array(buf, "tests", &raw_items, &raw_count);
|
|
246
|
+
free(buf);
|
|
247
|
+
if (rc != 0 || raw_count == 0) {
|
|
248
|
+
MIK_FreeTests(raw_items, raw_count);
|
|
249
|
+
return rc;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/* Apply the same prefix MIK_LoadConfig uses for "main". */
|
|
253
|
+
char** resolved = static_cast<char**>(calloc(raw_count, sizeof(char*)));
|
|
254
|
+
if (!resolved) {
|
|
255
|
+
MIK_FreeTests(raw_items, raw_count);
|
|
256
|
+
return -1;
|
|
257
|
+
}
|
|
258
|
+
for (size_t i = 0; i < raw_count; i++) {
|
|
259
|
+
const char* entry = raw_items[i];
|
|
260
|
+
if (entry[0] == '.' && entry[1] == '/') entry += 2;
|
|
261
|
+
size_t need = strlen(prefix) + 1 + strlen(entry) + 1;
|
|
262
|
+
char* s = static_cast<char*>(malloc(need));
|
|
263
|
+
if (!s) {
|
|
264
|
+
for (size_t j = 0; j < i; j++) free(resolved[j]);
|
|
265
|
+
free(resolved);
|
|
266
|
+
MIK_FreeTests(raw_items, raw_count);
|
|
267
|
+
return -1;
|
|
268
|
+
}
|
|
269
|
+
snprintf(s, need, "%s/%s", prefix, entry);
|
|
270
|
+
resolved[i] = s;
|
|
271
|
+
}
|
|
272
|
+
MIK_FreeTests(raw_items, raw_count);
|
|
273
|
+
|
|
274
|
+
*out_paths = resolved;
|
|
275
|
+
*out_count = raw_count;
|
|
276
|
+
return 0;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
void MIK_FreeTests(char** paths, size_t count) {
|
|
280
|
+
if (!paths) return;
|
|
281
|
+
for (size_t i = 0; i < count; i++) free(paths[i]);
|
|
282
|
+
free(paths);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
int MIK_LoadConfig(const char* base_path, MIKConfig* config) {
|
|
286
|
+
const MIKPlatform* platform = MIK_GetPlatform();
|
|
287
|
+
MIK_DefaultConfig(config);
|
|
288
|
+
|
|
289
|
+
char path[PATH_MAX];
|
|
290
|
+
struct stat st;
|
|
291
|
+
|
|
292
|
+
/* Find the app directory via package.json.
|
|
293
|
+
* Search in base_path first, then in immediate subdirectories
|
|
294
|
+
* (the build places package.json alongside the entry in the app dir). */
|
|
295
|
+
char* buf = mik__find_package_json(base_path, path, sizeof(path), &st);
|
|
296
|
+
if (buf) {
|
|
297
|
+
/* Extract the directory where package.json was found */
|
|
298
|
+
char app_dir[PATH_MAX];
|
|
299
|
+
snprintf(app_dir, sizeof(app_dir), "%s", path);
|
|
300
|
+
char* last_slash = strrchr(app_dir, '/');
|
|
301
|
+
if (last_slash) *last_slash = '\0';
|
|
302
|
+
|
|
303
|
+
/* Compute prefix: the subdirectory relative to base_path */
|
|
304
|
+
const char* pkg_dir = path + strlen(base_path);
|
|
305
|
+
char prefix[128] = "";
|
|
306
|
+
last_slash = strrchr(const_cast<char*>(pkg_dir), '/');
|
|
307
|
+
if (last_slash && last_slash != pkg_dir) {
|
|
308
|
+
size_t plen = last_slash - pkg_dir;
|
|
309
|
+
if (plen >= sizeof(prefix)) plen = sizeof(prefix) - 1;
|
|
310
|
+
memcpy(prefix, pkg_dir, plen);
|
|
311
|
+
prefix[plen] = '\0';
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/* Load entry point from package.json "main" field */
|
|
315
|
+
char main_field[128];
|
|
316
|
+
if (mik__json_get_string(buf, "main", main_field, sizeof(main_field))) {
|
|
317
|
+
const char* entry = main_field;
|
|
318
|
+
if (entry[0] == '.' && entry[1] == '/') entry += 2;
|
|
319
|
+
snprintf(config->entry_point, sizeof(config->entry_point), "%s/%s", prefix, entry);
|
|
320
|
+
platform->log(MIK_LOG_INFO, TAG, "Entry point: %s", config->entry_point);
|
|
321
|
+
}
|
|
322
|
+
free(buf);
|
|
323
|
+
|
|
324
|
+
/* Load mikro.config.json from the same directory */
|
|
325
|
+
snprintf(path, sizeof(path), "%s/mikro.config.json", app_dir);
|
|
326
|
+
buf = mik__read_json_file(path, &st);
|
|
327
|
+
if (buf) {
|
|
328
|
+
bool bool_val;
|
|
329
|
+
double num_val;
|
|
330
|
+
|
|
331
|
+
if (mik__json_get_bool(buf, "restartOnUncaughtException", &bool_val)) {
|
|
332
|
+
config->restart_on_uncaught_exception = bool_val;
|
|
333
|
+
}
|
|
334
|
+
if (mik__json_get_number(buf, "restartDelay", &num_val)) {
|
|
335
|
+
config->restart_delay_ms = (int)num_val;
|
|
336
|
+
}
|
|
337
|
+
if (mik__json_get_number(buf, "stackSize", &num_val)) {
|
|
338
|
+
config->stack_size = (size_t)num_val;
|
|
339
|
+
}
|
|
340
|
+
if (mik__json_get_number(buf, "memReserved", &num_val)) {
|
|
341
|
+
config->mem_reserved = (uint32_t)num_val;
|
|
342
|
+
}
|
|
343
|
+
if (mik__json_get_number(buf, "fsReadMax", &num_val)) {
|
|
344
|
+
config->fs_read_max = (uint32_t)num_val;
|
|
345
|
+
}
|
|
346
|
+
mik__json_get_string(buf, "wifiCountry", config->wifi_country,
|
|
347
|
+
sizeof(config->wifi_country));
|
|
348
|
+
|
|
349
|
+
platform->log(MIK_LOG_INFO, TAG,
|
|
350
|
+
"Loaded config: restart=%d delay=%dms stack=%u reserved=%lu",
|
|
351
|
+
config->restart_on_uncaught_exception, config->restart_delay_ms,
|
|
352
|
+
(unsigned)config->stack_size, (unsigned long)config->mem_reserved);
|
|
353
|
+
free(buf);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return 0;
|
|
358
|
+
}
|