@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.
Files changed (109) hide show
  1. package/CMakeLists.txt +198 -0
  2. package/LICENSE +21 -0
  3. package/README.md +49 -0
  4. package/cmake/mikrojs_bytecode.cmake +146 -0
  5. package/cmake.js +22 -0
  6. package/dist/index.d.ts +52 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +132 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/types.d.ts +43 -0
  11. package/dist/types.d.ts.map +1 -0
  12. package/dist/types.js +2 -0
  13. package/dist/types.js.map +1 -0
  14. package/include/byteorder_apple.h +11 -0
  15. package/include/byteorder_windows.h +12 -0
  16. package/include/mikrojs/cbor_helpers.h +24 -0
  17. package/include/mikrojs/cutils_wrap.h +59 -0
  18. package/include/mikrojs/errors.h +144 -0
  19. package/include/mikrojs/mem.h +11 -0
  20. package/include/mikrojs/mik_color.h +32 -0
  21. package/include/mikrojs/mikrojs.h +331 -0
  22. package/include/mikrojs/platform.h +82 -0
  23. package/include/mikrojs/private.h +281 -0
  24. package/include/mikrojs/utils.h +125 -0
  25. package/package.json +100 -0
  26. package/prebuilds/darwin-arm64/mikrojs.napi.node +0 -0
  27. package/prebuilds/linux-arm64/mikrojs.napi.node +0 -0
  28. package/prebuilds/linux-x64/mikrojs.napi.node +0 -0
  29. package/runtime/ble/ble.ts +231 -0
  30. package/runtime/ble/types.ts +194 -0
  31. package/runtime/ble/uuid.ts +89 -0
  32. package/runtime/ble/validators.ts +61 -0
  33. package/runtime/cbor/cbor.ts +1 -0
  34. package/runtime/cbor/types.ts +8 -0
  35. package/runtime/console/types.ts +50 -0
  36. package/runtime/env/env.ts +17 -0
  37. package/runtime/env/types.ts +12 -0
  38. package/runtime/format/types.ts +4 -0
  39. package/runtime/fs/fs.ts +93 -0
  40. package/runtime/fs/types.ts +92 -0
  41. package/runtime/globals.d.ts +87 -0
  42. package/runtime/http/helpers.ts +222 -0
  43. package/runtime/http/native.ts +151 -0
  44. package/runtime/http/request.ts +25 -0
  45. package/runtime/i2c/i2c.ts +35 -0
  46. package/runtime/i2c/types.ts +55 -0
  47. package/runtime/inspect/types.ts +10 -0
  48. package/runtime/internal.d.ts +456 -0
  49. package/runtime/kv/nvs.ts +17 -0
  50. package/runtime/kv/rtc.ts +17 -0
  51. package/runtime/kv/shared.ts +107 -0
  52. package/runtime/kv/types.ts +150 -0
  53. package/runtime/neopixel/neopixel.ts +38 -0
  54. package/runtime/neopixel/types.ts +27 -0
  55. package/runtime/pin/pin.ts +51 -0
  56. package/runtime/pin/types.ts +49 -0
  57. package/runtime/pwm/pwm.ts +32 -0
  58. package/runtime/pwm/types.ts +29 -0
  59. package/runtime/reader/reader.ts +167 -0
  60. package/runtime/reader/types.ts +34 -0
  61. package/runtime/result/native-result.node-shim.ts +44 -0
  62. package/runtime/result/result.ts +26 -0
  63. package/runtime/result/types.ts +60 -0
  64. package/runtime/schema/schema.ts +321 -0
  65. package/runtime/schema/types.ts +152 -0
  66. package/runtime/sleep/sleep.ts +14 -0
  67. package/runtime/sleep/types.ts +44 -0
  68. package/runtime/sntp/sntp.ts +54 -0
  69. package/runtime/sntp/types.ts +38 -0
  70. package/runtime/spi/spi.ts +31 -0
  71. package/runtime/spi/types.ts +42 -0
  72. package/runtime/stdio/stdio.ts +44 -0
  73. package/runtime/stdio/types.ts +22 -0
  74. package/runtime/stream/stream.ts +150 -0
  75. package/runtime/stream/types.ts +47 -0
  76. package/runtime/sys/sys.ts +90 -0
  77. package/runtime/sys/types.ts +131 -0
  78. package/runtime/test/test.ts +595 -0
  79. package/runtime/test/types.ts +97 -0
  80. package/runtime/uart/types.ts +75 -0
  81. package/runtime/uart/uart.ts +51 -0
  82. package/runtime/wifi/types.ts +156 -0
  83. package/runtime/wifi/wifi.ts +208 -0
  84. package/scripts/bundle-runtime.js +149 -0
  85. package/scripts/compare-minifiers.js +189 -0
  86. package/scripts/compile-bytecode.sh +38 -0
  87. package/scripts/copy-prebuild.js +20 -0
  88. package/scripts/generate-symbol-map.js +146 -0
  89. package/src/builtins.cpp +82 -0
  90. package/src/cutils_compat.c +38 -0
  91. package/src/eval_bytecode.cpp +42 -0
  92. package/src/fs.cpp +878 -0
  93. package/src/mem.cpp +63 -0
  94. package/src/mik_abort.cpp +160 -0
  95. package/src/mik_app_config.cpp +358 -0
  96. package/src/mik_cbor.cpp +334 -0
  97. package/src/mik_color.cpp +46 -0
  98. package/src/mik_console.cpp +422 -0
  99. package/src/mik_inspect.cpp +850 -0
  100. package/src/mik_repl.cpp +1122 -0
  101. package/src/mik_result.cpp +344 -0
  102. package/src/mik_stdio.cpp +147 -0
  103. package/src/mik_sys.cpp +239 -0
  104. package/src/mik_text_encoding.cpp +443 -0
  105. package/src/mikrojs.cpp +942 -0
  106. package/src/modules.cpp +944 -0
  107. package/src/platform_posix.cpp +134 -0
  108. package/src/timers.cpp +208 -0
  109. package/src/utils.cpp +173 -0
@@ -0,0 +1,239 @@
1
+ #include <quickjs.h>
2
+ #include <sys/time.h>
3
+ #include <time.h>
4
+
5
+ #ifdef CONFIG_IDF_TARGET
6
+ #include <esp_app_desc.h>
7
+ #include <esp_chip_info.h>
8
+ #include <esp_flash.h>
9
+ #ifdef CONFIG_SPIRAM
10
+ #include <esp_psram.h>
11
+ #endif
12
+ #endif
13
+
14
+ #include "mikrojs/platform.h"
15
+ #include "mikrojs/private.h"
16
+ #include "mikrojs/utils.h"
17
+
18
+ static JSValue mik__sys_eval_script(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
19
+ const char* str;
20
+ size_t len;
21
+ str = JS_ToCStringLen(ctx, &len, argv[0]);
22
+ if (!str) {
23
+ return JS_EXCEPTION;
24
+ }
25
+ JSValue ret = JS_Eval(ctx, str, len, "<evalScript>",
26
+ JS_EVAL_TYPE_GLOBAL | JS_EVAL_FLAG_ASYNC | JS_EVAL_FLAG_BACKTRACE_BARRIER);
27
+ JS_FreeCString(ctx, str);
28
+ return ret;
29
+ }
30
+
31
+ static JSValue mik__sys_memory_usage(JSContext* ctx, JSValue this_val, int argc,
32
+ JSValue* argv) {
33
+ JSMemoryUsage mem;
34
+ JS_ComputeMemoryUsage(JS_GetRuntime(ctx), &mem);
35
+
36
+ const MIKPlatform* platform = MIK_GetPlatform();
37
+
38
+ JSValue obj = JS_NewObject(ctx);
39
+ JS_SetPropertyStr(ctx, obj, "heapTotal",
40
+ JS_NewInt64(ctx, mem.malloc_limit > 0 ? mem.malloc_limit : mem.malloc_size));
41
+ JS_SetPropertyStr(ctx, obj, "heapUsed", JS_NewInt64(ctx, mem.malloc_size));
42
+ JS_SetPropertyStr(ctx, obj, "systemFree",
43
+ JS_NewInt64(ctx, (int64_t)platform->get_free_system_mem()));
44
+ JS_SetPropertyStr(ctx, obj, "systemMinFree",
45
+ JS_NewInt64(ctx, (int64_t)platform->get_min_free_system_mem()));
46
+ JS_SetPropertyStr(ctx, obj, "systemTotal",
47
+ JS_NewInt64(ctx, (int64_t)platform->get_total_system_mem()));
48
+ JS_SetPropertyStr(ctx, obj, "systemLargestFree",
49
+ JS_NewInt64(ctx, (int64_t)platform->get_largest_free_system_mem()));
50
+ return obj;
51
+ }
52
+
53
+ /* Curated QuickJS-heap breakdown. Covers the "where is memory going?"
54
+ * fields from JSMemoryUsage; skips redundancy with `memoryUsage()`
55
+ * (malloc_size/limit) and low-signal fields (atom counts, pc2line debug
56
+ * info, allocation counts, internal fast-vs-regular distinctions).
57
+ * If you need one of the excluded fields for a specific investigation,
58
+ * add it here — they're all available via JS_ComputeMemoryUsage. */
59
+ static JSValue mik__sys_js_memory_usage(JSContext* ctx, JSValue this_val, int argc,
60
+ JSValue* argv) {
61
+ JSMemoryUsage mem;
62
+ JS_ComputeMemoryUsage(JS_GetRuntime(ctx), &mem);
63
+
64
+ const struct {
65
+ const char* name;
66
+ int64_t value;
67
+ } fields[] = {
68
+ {"strCount", mem.str_count},
69
+ {"strSize", mem.str_size},
70
+ {"objCount", mem.obj_count},
71
+ {"objSize", mem.obj_size},
72
+ {"propCount", mem.prop_count},
73
+ {"propSize", mem.prop_size},
74
+ {"shapeCount", mem.shape_count},
75
+ {"shapeSize", mem.shape_size},
76
+ {"jsFuncCount", mem.js_func_count},
77
+ {"jsFuncCodeSize", mem.js_func_code_size},
78
+ {"arrayCount", mem.array_count},
79
+ {"fastArrayElements", mem.fast_array_elements},
80
+ {"binaryObjectSize", mem.binary_object_size},
81
+ };
82
+
83
+ JSValue obj = JS_NewObject(ctx);
84
+ for (const auto& f : fields) {
85
+ JS_SetPropertyStr(ctx, obj, f.name, JS_NewInt64(ctx, f.value));
86
+ }
87
+ return obj;
88
+ }
89
+
90
+ static JSValue mik__sys_gc(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
91
+ JS_RunGC(JS_GetRuntime(ctx));
92
+ return JS_UNDEFINED;
93
+ }
94
+
95
+ static JSValue mik__sys_active_timers(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
96
+ MIKRuntime* mik_rt = static_cast<MIKRuntime*>(JS_GetContextOpaque(ctx));
97
+ CHECK_NOT_NULL(mik_rt);
98
+ return JS_NewInt32(ctx, static_cast<int32_t>(mik_rt->timers->entries.size()));
99
+ }
100
+
101
+ static JSValue mik__sys_set_time(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
102
+ int64_t millis;
103
+ if (JS_ToInt64(ctx, &millis, argv[0])) {
104
+ return JS_EXCEPTION;
105
+ }
106
+ struct timeval tv = {
107
+ .tv_sec = static_cast<time_t>(millis / 1000),
108
+ .tv_usec = static_cast<suseconds_t>((millis % 1000) * 1000),
109
+ };
110
+ if (settimeofday(&tv, NULL) != 0) {
111
+ return mik_throw_errno(ctx, errno);
112
+ }
113
+ return JS_UNDEFINED;
114
+ }
115
+
116
+ static JSValue mik__sys_uptime(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
117
+ const MIKPlatform* platform = MIK_GetPlatform();
118
+ JSValue obj = JS_NewObject(ctx);
119
+ JS_SetPropertyStr(ctx, obj, "boot",
120
+ JS_NewFloat64(ctx, static_cast<double>(platform->get_boot_us()) / 1000.0));
121
+ JS_SetPropertyStr(ctx, obj, "rtc",
122
+ JS_NewFloat64(ctx, static_cast<double>(platform->get_rtc_us()) / 1000.0));
123
+ return obj;
124
+ }
125
+
126
+ static JSValue mik__sys_restart(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
127
+ MIK_GetPlatform()->restart();
128
+ return JS_UNDEFINED;
129
+ }
130
+
131
+ static JSValue mik__sys_board(JSContext* ctx) {
132
+ JSValue obj = JS_NewObject(ctx);
133
+ #ifdef MIK_BOARD_NAME
134
+ JS_SetPropertyStr(ctx, obj, "name", JS_NewString(ctx, MIK_BOARD_NAME));
135
+ #else
136
+ JS_SetPropertyStr(ctx, obj, "name", JS_NewString(ctx, "generic"));
137
+ #endif
138
+
139
+ #ifdef CONFIG_IDF_TARGET
140
+ JS_SetPropertyStr(ctx, obj, "chip", JS_NewString(ctx, CONFIG_IDF_TARGET));
141
+
142
+ esp_chip_info_t chip_info;
143
+ esp_chip_info(&chip_info);
144
+ JS_SetPropertyStr(ctx, obj, "cores", JS_NewInt32(ctx, chip_info.cores));
145
+ JS_SetPropertyStr(ctx, obj, "revision", JS_NewInt32(ctx, chip_info.revision));
146
+
147
+ /* Features as string array */
148
+ JSValue features = JS_NewArray(ctx);
149
+ uint32_t fi = 0;
150
+ if (chip_info.features & CHIP_FEATURE_WIFI_BGN)
151
+ JS_SetPropertyUint32(ctx, features, fi++, JS_NewString(ctx, "wifi"));
152
+ if (chip_info.features & CHIP_FEATURE_BLE)
153
+ JS_SetPropertyUint32(ctx, features, fi++, JS_NewString(ctx, "ble"));
154
+ if (chip_info.features & CHIP_FEATURE_BT)
155
+ JS_SetPropertyUint32(ctx, features, fi++, JS_NewString(ctx, "bt"));
156
+ if (chip_info.features & CHIP_FEATURE_IEEE802154)
157
+ JS_SetPropertyUint32(ctx, features, fi++, JS_NewString(ctx, "ieee802154"));
158
+ JS_SetPropertyStr(ctx, obj, "features", features);
159
+
160
+ /* Flash size in bytes */
161
+ uint32_t flash_size = 0;
162
+ esp_flash_get_size(NULL, &flash_size);
163
+ JS_SetPropertyStr(ctx, obj, "flash", JS_NewInt64(ctx, flash_size));
164
+
165
+ /* PSRAM size in bytes */
166
+ #ifdef CONFIG_SPIRAM
167
+ JS_SetPropertyStr(ctx, obj, "psram", JS_NewInt64(ctx, esp_psram_get_size()));
168
+ #else
169
+ JS_SetPropertyStr(ctx, obj, "psram", JS_NewInt64(ctx, 0));
170
+ #endif
171
+
172
+ #else
173
+ JS_SetPropertyStr(ctx, obj, "chip", JS_NewString(ctx, "host"));
174
+ JS_SetPropertyStr(ctx, obj, "cores", JS_NewInt32(ctx, 1));
175
+ JS_SetPropertyStr(ctx, obj, "revision", JS_NewInt32(ctx, 0));
176
+ JS_SetPropertyStr(ctx, obj, "features", JS_NewArray(ctx));
177
+ JS_SetPropertyStr(ctx, obj, "flash", JS_NewInt64(ctx, 0));
178
+ JS_SetPropertyStr(ctx, obj, "psram", JS_NewInt64(ctx, 0));
179
+ #endif
180
+ return obj;
181
+ }
182
+
183
+ // MIK_BUILD_DATE_UTC is a real UTC ISO-8601 string injected by CMake at
184
+ // configure time (`string(TIMESTAMP ... UTC)`). Preferred over `__DATE__`/
185
+ // `__TIME__` and `esp_app_desc_t::date/time`, which are the build host's
186
+ // *local* time and can't be correctly labelled as UTC without knowing the
187
+ // build machine's timezone.
188
+ #ifndef MIK_BUILD_DATE_UTC
189
+ #define MIK_BUILD_DATE_UTC "unknown"
190
+ #endif
191
+
192
+ static JSValue mik__sys_firmware(JSContext* ctx) {
193
+ JSValue obj = JS_NewObject(ctx);
194
+ #ifdef CONFIG_IDF_TARGET
195
+ const esp_app_desc_t* desc = esp_app_get_description();
196
+ char elf_hash[65];
197
+ for (int i = 0; i < 32; i++) {
198
+ snprintf(elf_hash + i * 2, 3, "%02x", desc->app_elf_sha256[i]);
199
+ }
200
+ JS_SetPropertyStr(ctx, obj, "hash", JS_NewString(ctx, elf_hash));
201
+ JS_SetPropertyStr(ctx, obj, "date", JS_NewString(ctx, MIK_BUILD_DATE_UTC));
202
+ JS_SetPropertyStr(ctx, obj, "idfVersion", JS_NewString(ctx, desc->idf_ver));
203
+ #else
204
+ JS_SetPropertyStr(ctx, obj, "hash", JS_NewString(ctx, "dev"));
205
+ JS_SetPropertyStr(ctx, obj, "date", JS_NewString(ctx, MIK_BUILD_DATE_UTC));
206
+ JS_SetPropertyStr(ctx, obj, "idfVersion", JS_NewString(ctx, "n/a"));
207
+ #endif
208
+ return obj;
209
+ }
210
+
211
+ void mik__sys_api_init(JSContext* ctx, JSValue ns) {
212
+ JS_SetPropertyStr(ctx, ns, "evalScript",
213
+ JS_NewCFunction(ctx, mik__sys_eval_script, "evalScript", 1));
214
+ JS_SetPropertyStr(ctx, ns, "memoryUsage",
215
+ JS_NewCFunction(ctx, mik__sys_memory_usage, "memoryUsage", 0));
216
+ JS_SetPropertyStr(ctx, ns, "jsMemoryUsage",
217
+ JS_NewCFunction(ctx, mik__sys_js_memory_usage, "jsMemoryUsage", 0));
218
+ JS_SetPropertyStr(ctx, ns, "gc", JS_NewCFunction(ctx, mik__sys_gc, "gc", 0));
219
+ JS_SetPropertyStr(ctx, ns, "activeTimers",
220
+ JS_NewCFunction(ctx, mik__sys_active_timers, "activeTimers", 0));
221
+ JS_SetPropertyStr(ctx, ns, "setTime",
222
+ JS_NewCFunction(ctx, mik__sys_set_time, "setTime", 1));
223
+ JS_SetPropertyStr(ctx, ns, "uptime",
224
+ JS_NewCFunction(ctx, mik__sys_uptime, "uptime", 0));
225
+ JS_SetPropertyStr(ctx, ns, "restart",
226
+ JS_NewCFunction(ctx, mik__sys_restart, "restart", 0));
227
+ JS_SetPropertyStr(ctx, ns, "version",
228
+ #ifdef MIK_FW_VERSION
229
+ JS_NewString(ctx, MIK_FW_VERSION));
230
+ #else
231
+ JS_NewString(ctx, "0.0.0-dev"));
232
+ #endif
233
+ JS_SetPropertyStr(ctx, ns, "board", mik__sys_board(ctx));
234
+ JS_SetPropertyStr(ctx, ns, "firmware", mik__sys_firmware(ctx));
235
+
236
+ const char* device_id = MIK_GetPlatform()->get_device_id();
237
+ JS_SetPropertyStr(ctx, ns, "deviceId",
238
+ device_id ? JS_NewString(ctx, device_id) : JS_UNDEFINED);
239
+ }
@@ -0,0 +1,443 @@
1
+ #include <quickjs.h>
2
+ #include <string.h>
3
+
4
+ #include "mikrojs/utils.h"
5
+
6
+ /* ---- TextEncoder ---- */
7
+
8
+ static JSClassID textencoder_class_id;
9
+
10
+ static JSClassDef textencoder_classdef = {
11
+ .class_name = "TextEncoder",
12
+ .finalizer = nullptr,
13
+ .gc_mark = nullptr,
14
+ .call = nullptr,
15
+ .exotic = nullptr,
16
+ };
17
+
18
+ static JSValue mik__text_encoder_constructor(JSContext* ctx, JSValue new_target, int argc,
19
+ JSValue* argv) {
20
+ return JS_NewObjectClass(ctx, textencoder_class_id);
21
+ }
22
+
23
+ static JSValue mik__text_encoder_encode(JSContext* ctx, JSValue this_val, int argc,
24
+ JSValue* argv) {
25
+ const char* str = JS_ToCString(ctx, argv[0]);
26
+ if (!str) {
27
+ return JS_EXCEPTION;
28
+ }
29
+ JSValue ret = JS_NewUint8ArrayCopy(ctx, (const uint8_t*)str, strlen(str));
30
+ JS_FreeCString(ctx, str);
31
+ return ret;
32
+ }
33
+
34
+ static const JSCFunctionListEntry mik__text_encoder_proto_funcs[] = {
35
+ MIK_CFUNC_DEF("encode", 1, mik__text_encoder_encode),
36
+ };
37
+
38
+ /* ---- TextDecoder ---- */
39
+
40
+ typedef struct {
41
+ uint8_t pending[4]; /* up to 3 bytes of a leading incomplete sequence */
42
+ uint8_t pending_len; /* 0..3 */
43
+ } MIKTextDecoder;
44
+
45
+ static JSClassID textdecoder_class_id;
46
+
47
+ static void mik__text_decoder_finalizer(JSRuntime* rt, JSValue val) {
48
+ MIKTextDecoder* state = static_cast<MIKTextDecoder*>(JS_GetOpaque(val, textdecoder_class_id));
49
+ if (state) js_free_rt(rt, state);
50
+ }
51
+
52
+ static JSClassDef textdecoder_classdef = {
53
+ .class_name = "TextDecoder",
54
+ .finalizer = mik__text_decoder_finalizer,
55
+ .gc_mark = nullptr,
56
+ .call = nullptr,
57
+ .exotic = nullptr,
58
+ };
59
+
60
+ /* Case-insensitive ASCII match for utf-8 / utf8 (with optional surrounding
61
+ * ASCII whitespace, matching WHATWG "get an encoding" label trimming). */
62
+ static bool is_utf8_label(const char* s) {
63
+ while (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r' || *s == '\f') s++;
64
+ const char* end = s + strlen(s);
65
+ while (end > s && (end[-1] == ' ' || end[-1] == '\t' || end[-1] == '\n' ||
66
+ end[-1] == '\r' || end[-1] == '\f')) {
67
+ end--;
68
+ }
69
+ size_t len = (size_t)(end - s);
70
+ char buf[8];
71
+ if (len == 0 || len >= sizeof(buf)) return false;
72
+ for (size_t i = 0; i < len; i++) {
73
+ char c = s[i];
74
+ if (c >= 'A' && c <= 'Z') c = (char)(c + 32);
75
+ buf[i] = c;
76
+ }
77
+ buf[len] = '\0';
78
+ return strcmp(buf, "utf-8") == 0 || strcmp(buf, "utf8") == 0;
79
+ }
80
+
81
+ static JSValue mik__text_decoder_constructor(JSContext* ctx, JSValue new_target, int argc,
82
+ JSValue* argv) {
83
+ if (argc >= 1 && !JS_IsUndefined(argv[0])) {
84
+ const char* label = JS_ToCString(ctx, argv[0]);
85
+ if (!label) return JS_EXCEPTION;
86
+ bool ok = is_utf8_label(label);
87
+ JS_FreeCString(ctx, label);
88
+ if (!ok) {
89
+ return JS_ThrowRangeError(ctx, "TextDecoder: only 'utf-8' is supported");
90
+ }
91
+ }
92
+ JSValue obj = JS_NewObjectClass(ctx, textdecoder_class_id);
93
+ if (JS_IsException(obj)) return obj;
94
+ MIKTextDecoder* state = static_cast<MIKTextDecoder*>(js_mallocz(ctx, sizeof(MIKTextDecoder)));
95
+ if (!state) {
96
+ JS_FreeValue(ctx, obj);
97
+ return JS_EXCEPTION;
98
+ }
99
+ JS_SetOpaque(obj, state);
100
+ return obj;
101
+ }
102
+
103
+ /* Return the expected UTF-8 sequence length given a lead byte, or 0 if the
104
+ * byte is not a valid lead. */
105
+ static int utf8_seq_len(uint8_t b) {
106
+ if (b < 0x80) return 1;
107
+ if ((b & 0xE0) == 0xC0) return 2;
108
+ if ((b & 0xF0) == 0xE0) return 3;
109
+ if ((b & 0xF8) == 0xF0) return 4;
110
+ return 0;
111
+ }
112
+
113
+ /* Validate a complete UTF-8 sequence of the given length. Checks continuation
114
+ * bytes, overlong encodings, surrogate range, and max codepoint. */
115
+ static bool utf8_seq_valid(const uint8_t* p, int len) {
116
+ if (len == 1) return p[0] < 0x80;
117
+ for (int i = 1; i < len; i++) {
118
+ if ((p[i] & 0xC0) != 0x80) return false;
119
+ }
120
+ if (len == 2) {
121
+ return p[0] >= 0xC2; /* reject overlong */
122
+ }
123
+ if (len == 3) {
124
+ if (p[0] == 0xE0 && p[1] < 0xA0) return false; /* overlong */
125
+ if (p[0] == 0xED && p[1] >= 0xA0) return false; /* surrogate */
126
+ return true;
127
+ }
128
+ if (len == 4) {
129
+ if (p[0] == 0xF0 && p[1] < 0x90) return false; /* overlong */
130
+ if (p[0] == 0xF4 && p[1] >= 0x90) return false; /* > U+10FFFF */
131
+ if (p[0] > 0xF4) return false;
132
+ return true;
133
+ }
134
+ return false;
135
+ }
136
+
137
+ /* Append U+FFFD (EF BF BD) to buffer. */
138
+ static void append_replacement(uint8_t* out, size_t* out_len) {
139
+ out[(*out_len)++] = 0xEF;
140
+ out[(*out_len)++] = 0xBF;
141
+ out[(*out_len)++] = 0xBD;
142
+ }
143
+
144
+ static JSValue mik__text_decoder_decode(JSContext* ctx, JSValue this_val, int argc,
145
+ JSValue* argv) {
146
+ MIKTextDecoder* state =
147
+ static_cast<MIKTextDecoder*>(JS_GetOpaque2(ctx, this_val, textdecoder_class_id));
148
+ if (!state) return JS_EXCEPTION;
149
+
150
+ const uint8_t* data = nullptr;
151
+ size_t len = 0;
152
+ if (argc >= 1 && !JS_IsUndefined(argv[0])) {
153
+ if (JS_GetTypedArrayType(argv[0]) != JS_TYPED_ARRAY_UINT8) {
154
+ return JS_ThrowTypeError(ctx, "expected Uint8Array");
155
+ }
156
+ data = JS_GetUint8Array(ctx, &len, argv[0]);
157
+ if (!data) return JS_EXCEPTION;
158
+ }
159
+
160
+ bool stream = false;
161
+ if (argc >= 2 && JS_IsObject(argv[1])) {
162
+ JSValue sv = JS_GetPropertyStr(ctx, argv[1], "stream");
163
+ if (JS_IsException(sv)) return JS_EXCEPTION;
164
+ stream = JS_ToBool(ctx, sv);
165
+ JS_FreeValue(ctx, sv);
166
+ }
167
+
168
+ /* Common fast path: no pending bytes, scan `data` in place. Slow path
169
+ * (pending carry-over) copies pending + data into a buffer. */
170
+ size_t total = state->pending_len + len;
171
+ const uint8_t* input;
172
+ uint8_t stackbuf[8];
173
+ uint8_t* heap_input = nullptr;
174
+ if (state->pending_len == 0) {
175
+ input = data;
176
+ } else if (total <= sizeof(stackbuf)) {
177
+ memcpy(stackbuf, state->pending, state->pending_len);
178
+ if (len > 0) memcpy(stackbuf + state->pending_len, data, len);
179
+ input = stackbuf;
180
+ } else {
181
+ heap_input = static_cast<uint8_t*>(js_malloc(ctx, total));
182
+ if (!heap_input) return JS_EXCEPTION;
183
+ memcpy(heap_input, state->pending, state->pending_len);
184
+ memcpy(heap_input + state->pending_len, data, len);
185
+ input = heap_input;
186
+ }
187
+ state->pending_len = 0;
188
+
189
+ /* Worst case output: every byte becomes U+FFFD (3 bytes). */
190
+ size_t out_cap = total * 3;
191
+ if (out_cap == 0) out_cap = 1;
192
+ uint8_t* out = static_cast<uint8_t*>(js_malloc(ctx, out_cap));
193
+ if (!out) {
194
+ if (heap_input) js_free(ctx, heap_input);
195
+ return JS_EXCEPTION;
196
+ }
197
+ size_t out_len = 0;
198
+
199
+ size_t i = 0;
200
+ while (i < total) {
201
+ uint8_t b = input[i];
202
+ int seq = utf8_seq_len(b);
203
+ if (seq == 0) {
204
+ append_replacement(out, &out_len);
205
+ i++;
206
+ continue;
207
+ }
208
+ if (i + seq > total) {
209
+ /* Incomplete trailing sequence. */
210
+ if (stream) {
211
+ size_t held = total - i;
212
+ for (size_t k = 0; k < held; k++) state->pending[k] = input[i + k];
213
+ state->pending_len = (uint8_t)held;
214
+ break;
215
+ }
216
+ /* Flush: one U+FFFD per held byte. */
217
+ for (size_t k = i; k < total; k++) append_replacement(out, &out_len);
218
+ i = total;
219
+ break;
220
+ }
221
+ if (!utf8_seq_valid(input + i, seq)) {
222
+ append_replacement(out, &out_len);
223
+ i++;
224
+ continue;
225
+ }
226
+ memcpy(out + out_len, input + i, seq);
227
+ out_len += seq;
228
+ i += seq;
229
+ }
230
+
231
+ if (heap_input) js_free(ctx, heap_input);
232
+ JSValue ret = JS_NewStringLen(ctx, (const char*)out, out_len);
233
+ js_free(ctx, out);
234
+ return ret;
235
+ }
236
+
237
+ static const JSCFunctionListEntry mik__text_decoder_proto_funcs[] = {
238
+ MIK_CFUNC_DEF("decode", 1, mik__text_decoder_decode),
239
+ };
240
+
241
+ /* ---- btoa / atob ---- */
242
+
243
+ static const char b64_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
244
+
245
+ static const uint8_t b64_decode_table[256] = {
246
+ /* clang-format off */
247
+ 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
248
+ 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
249
+ 255,255,255,255,255,255,255,255,255,255,255, 62,255,255,255, 63,
250
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,255,255,255, 0,255,255,
251
+ 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
252
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,255,255,255,255,255,
253
+ 255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
254
+ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,255,255,255,255,255,
255
+ 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
256
+ 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
257
+ 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
258
+ 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
259
+ 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
260
+ 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
261
+ 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
262
+ 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
263
+ /* clang-format on */
264
+ };
265
+
266
+ /* btoa: encode a binary string to base64.
267
+ * Per the web spec, each JS character's code point is treated as a raw byte.
268
+ * We must iterate by code point, not by UTF-8 bytes, since JS_ToCStringLen
269
+ * returns UTF-8 where e.g. U+0080 becomes two bytes (\xC2\x80). */
270
+ static JSValue mik__btoa(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
271
+ JSValue str_val = argv[0];
272
+ JSValue len_val = JS_GetPropertyStr(ctx, str_val, "length");
273
+ int64_t len;
274
+ if (JS_ToInt64(ctx, &len, len_val)) {
275
+ JS_FreeValue(ctx, len_val);
276
+ return JS_EXCEPTION;
277
+ }
278
+ JS_FreeValue(ctx, len_val);
279
+
280
+ /* Extract code points into a byte buffer, validating latin1 range */
281
+ uint8_t* bytes = static_cast<uint8_t*>(js_malloc(ctx, len > 0 ? len : 1));
282
+ if (!bytes) return JS_EXCEPTION;
283
+
284
+ JSAtom charCodeAt_atom = JS_NewAtom(ctx, "charCodeAt");
285
+ for (int64_t i = 0; i < len; i++) {
286
+ JSValue idx = JS_NewInt64(ctx, i);
287
+ JSValue code = JS_Invoke(ctx, str_val, charCodeAt_atom, 1, &idx);
288
+ JS_FreeValue(ctx, idx);
289
+ if (JS_IsException(code)) {
290
+ JS_FreeAtom(ctx, charCodeAt_atom);
291
+ js_free(ctx, bytes);
292
+ return JS_EXCEPTION;
293
+ }
294
+ int32_t cp;
295
+ JS_ToInt32(ctx, &cp, code);
296
+ JS_FreeValue(ctx, code);
297
+ if (cp > 0xFF) {
298
+ JS_FreeAtom(ctx, charCodeAt_atom);
299
+ js_free(ctx, bytes);
300
+ return JS_ThrowRangeError(ctx,
301
+ "The string to be encoded contains characters outside of the "
302
+ "Latin1 range");
303
+ }
304
+ bytes[i] = (uint8_t)cp;
305
+ }
306
+ JS_FreeAtom(ctx, charCodeAt_atom);
307
+
308
+ size_t out_len = 4 * ((len + 2) / 3);
309
+ char* out = static_cast<char*>(js_malloc(ctx, out_len + 1));
310
+ if (!out) {
311
+ js_free(ctx, bytes);
312
+ return JS_EXCEPTION;
313
+ }
314
+
315
+ size_t j = 0;
316
+ for (int64_t i = 0; i < len; i += 3) {
317
+ uint32_t a = bytes[i];
318
+ uint32_t b = (i + 1 < len) ? bytes[i + 1] : 0;
319
+ uint32_t c = (i + 2 < len) ? bytes[i + 2] : 0;
320
+ uint32_t triple = (a << 16) | (b << 8) | c;
321
+
322
+ out[j++] = b64_table[(triple >> 18) & 0x3F];
323
+ out[j++] = b64_table[(triple >> 12) & 0x3F];
324
+ out[j++] = (i + 1 < len) ? b64_table[(triple >> 6) & 0x3F] : '=';
325
+ out[j++] = (i + 2 < len) ? b64_table[triple & 0x3F] : '=';
326
+ }
327
+ out[j] = '\0';
328
+
329
+ js_free(ctx, bytes);
330
+ JSValue ret = JS_NewStringLen(ctx, out, j);
331
+ js_free(ctx, out);
332
+ return ret;
333
+ }
334
+
335
+ /* atob: decode a base64 string to a binary string */
336
+ static JSValue mik__atob(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
337
+ size_t len;
338
+ const char* str = JS_ToCStringLen(ctx, &len, argv[0]);
339
+ if (!str) return JS_EXCEPTION;
340
+
341
+ /* Strip whitespace and validate */
342
+ size_t out_cap = (len * 3) / 4 + 1;
343
+ char* out = static_cast<char*>(js_malloc(ctx, out_cap));
344
+ if (!out) {
345
+ JS_FreeCString(ctx, str);
346
+ return JS_EXCEPTION;
347
+ }
348
+
349
+ uint32_t accum = 0;
350
+ int bits = 0;
351
+ size_t j = 0;
352
+ int pad = 0;
353
+
354
+ for (size_t i = 0; i < len; i++) {
355
+ char ch = str[i];
356
+ if (ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' || ch == '\f') continue;
357
+ if (ch == '=') {
358
+ pad++;
359
+ continue;
360
+ }
361
+ if (pad > 0) {
362
+ /* Data after padding */
363
+ js_free(ctx, out);
364
+ JS_FreeCString(ctx, str);
365
+ return JS_ThrowSyntaxError(ctx,
366
+ "The string to be decoded is not correctly encoded");
367
+ }
368
+ uint8_t val = b64_decode_table[(uint8_t)ch];
369
+ if (val == 255) {
370
+ js_free(ctx, out);
371
+ JS_FreeCString(ctx, str);
372
+ return JS_ThrowSyntaxError(ctx,
373
+ "The string to be decoded is not correctly encoded");
374
+ }
375
+ accum = (accum << 6) | val;
376
+ bits += 6;
377
+ if (bits >= 8) {
378
+ bits -= 8;
379
+ out[j++] = (char)((accum >> bits) & 0xFF);
380
+ }
381
+ }
382
+
383
+ JS_FreeCString(ctx, str);
384
+
385
+ /* Convert decoded bytes to a JS string. Bytes 0x80-0xFF must become
386
+ * their corresponding Unicode code points (U+0080-U+00FF), which in
387
+ * UTF-8 are two-byte sequences. Build a UTF-8 buffer. */
388
+ size_t utf8_cap = j * 2 + 1; /* worst case: every byte is 0x80-0xFF */
389
+ char* utf8 = static_cast<char*>(js_malloc(ctx, utf8_cap));
390
+ if (!utf8) {
391
+ js_free(ctx, out);
392
+ return JS_EXCEPTION;
393
+ }
394
+ size_t utf8_len = 0;
395
+ for (size_t i = 0; i < j; i++) {
396
+ uint8_t b = (uint8_t)out[i];
397
+ if (b < 0x80) {
398
+ utf8[utf8_len++] = (char)b;
399
+ } else {
400
+ utf8[utf8_len++] = (char)(0xC0 | (b >> 6));
401
+ utf8[utf8_len++] = (char)(0x80 | (b & 0x3F));
402
+ }
403
+ }
404
+
405
+ js_free(ctx, out);
406
+ JSValue ret = JS_NewStringLen(ctx, utf8, utf8_len);
407
+ js_free(ctx, utf8);
408
+ return ret;
409
+ }
410
+
411
+ /* ---- Registration ---- */
412
+
413
+ void mik__text_encoding_init(JSContext* ctx, JSValue global) {
414
+ JSRuntime* rt = JS_GetRuntime(ctx);
415
+
416
+ /* TextEncoder */
417
+ JS_NewClassID(rt, &textencoder_class_id);
418
+ JS_NewClass(rt, textencoder_class_id, &textencoder_classdef);
419
+ JSValue te_proto = JS_NewObject(ctx);
420
+ JS_SetPropertyFunctionList(ctx, te_proto, mik__text_encoder_proto_funcs,
421
+ countof(mik__text_encoder_proto_funcs));
422
+ JS_SetClassProto(ctx, textencoder_class_id, te_proto);
423
+ JSValue te_ctor = JS_NewCFunction2(ctx, mik__text_encoder_constructor, "TextEncoder", 0,
424
+ JS_CFUNC_constructor, 0);
425
+ JS_DefinePropertyValueStr(ctx, global, "TextEncoder", te_ctor, JS_PROP_C_W_E);
426
+
427
+ /* TextDecoder */
428
+ JS_NewClassID(rt, &textdecoder_class_id);
429
+ JS_NewClass(rt, textdecoder_class_id, &textdecoder_classdef);
430
+ JSValue td_proto = JS_NewObject(ctx);
431
+ JS_SetPropertyFunctionList(ctx, td_proto, mik__text_decoder_proto_funcs,
432
+ countof(mik__text_decoder_proto_funcs));
433
+ JS_SetClassProto(ctx, textdecoder_class_id, td_proto);
434
+ JSValue td_ctor = JS_NewCFunction2(ctx, mik__text_decoder_constructor, "TextDecoder", 0,
435
+ JS_CFUNC_constructor, 0);
436
+ JS_DefinePropertyValueStr(ctx, global, "TextDecoder", td_ctor, JS_PROP_C_W_E);
437
+
438
+ /* btoa / atob */
439
+ JS_DefinePropertyValueStr(ctx, global, "btoa",
440
+ JS_NewCFunction(ctx, mik__btoa, "btoa", 1), JS_PROP_C_W_E);
441
+ JS_DefinePropertyValueStr(ctx, global, "atob",
442
+ JS_NewCFunction(ctx, mik__atob, "atob", 1), JS_PROP_C_W_E);
443
+ }