MIRROR: javascript for 馃悳's, a tiny runtime with big ambitions
1#include <stdlib.h>
2#include <string.h>
3
4#include "ant.h"
5#include "errors.h"
6#include "runtime.h"
7#include "internal.h"
8#include "descriptors.h"
9
10#include "modules/symbol.h"
11#include "modules/buffer.h"
12#include "modules/textcodec.h"
13#include "streams/codec.h"
14#include "streams/transform.h"
15
16ant_value_t g_tes_proto;
17ant_value_t g_tds_proto;
18
19typedef struct {
20 uint8_t pending[3];
21 uint8_t pending_len;
22} tes_state_t;
23
24static ant_value_t tes_get_ts(ant_value_t obj) {
25 return js_get_slot(obj, SLOT_ENTRIES);
26}
27
28static ant_value_t tds_get_ts(ant_value_t obj) {
29 return js_get_slot(obj, SLOT_ENTRIES);
30}
31
32bool tes_is_stream(ant_value_t obj) {
33 return is_object_type(obj)
34 && vtype(js_get_slot(obj, SLOT_DATA)) == T_NUM
35 && ts_is_stream(tes_get_ts(obj));
36}
37
38bool tds_is_stream(ant_value_t obj) {
39 return is_object_type(obj)
40 && vtype(js_get_slot(obj, SLOT_DATA)) == T_NUM
41 && ts_is_stream(tds_get_ts(obj));
42}
43
44ant_value_t tes_stream_readable(ant_value_t obj) {
45 return ts_stream_readable(tes_get_ts(obj));
46}
47
48ant_value_t tes_stream_writable(ant_value_t obj) {
49 return ts_stream_writable(tes_get_ts(obj));
50}
51
52ant_value_t tds_stream_readable(ant_value_t obj) {
53 return ts_stream_readable(tds_get_ts(obj));
54}
55
56ant_value_t tds_stream_writable(ant_value_t obj) {
57 return ts_stream_writable(tds_get_ts(obj));
58}
59
60static void tes_state_finalize(ant_t *js, ant_object_t *obj) {
61 if (!obj->extra_slots) return;
62 ant_extra_slot_t *entries = (ant_extra_slot_t *)obj->extra_slots;
63 for (uint8_t i = 0; i < obj->extra_count; i++) {
64 if (entries[i].slot == SLOT_DATA && vtype(entries[i].value) == T_NUM) {
65 free((tes_state_t *)(uintptr_t)(size_t)js_getnum(entries[i].value));
66 return;
67 }}
68}
69
70static void tds_state_finalize(ant_t *js, ant_object_t *obj) {
71 if (!obj->extra_slots) return;
72 ant_extra_slot_t *entries = (ant_extra_slot_t *)obj->extra_slots;
73 for (uint8_t i = 0; i < obj->extra_count; i++) {
74 if (entries[i].slot == SLOT_DATA && vtype(entries[i].value) == T_NUM) {
75 free((td_state_t *)(uintptr_t)(size_t)js_getnum(entries[i].value));
76 return;
77 }}
78}
79
80static ant_value_t codec_transform_controller(ant_value_t *args, int nargs) {
81 return (nargs > 1) ? args[1] : js_mkundef();
82}
83
84static ant_value_t codec_flush_controller(ant_value_t *args, int nargs) {
85 return (nargs > 0) ? args[0] : js_mkundef();
86}
87
88static ant_value_t tes_transform(ant_t *js, ant_value_t *args, int nargs) {
89 ant_value_t wrapper = js_get_slot(js->current_func, SLOT_DATA);
90 ant_value_t state_val = js_get_slot(wrapper, SLOT_DATA);
91
92 tes_state_t *st = (tes_state_t *)(uintptr_t)(size_t)js_getnum(state_val);
93 ant_value_t ctrl_obj = codec_transform_controller(args, nargs);
94 ant_value_t chunk = (nargs > 0) ? args[0] : js_mkundef();
95
96 size_t str_len = 0;
97 const char *str = NULL;
98
99 if (vtype(chunk) == T_STR) {
100 str = js_getstr(js, chunk, &str_len);
101 } else {
102 ant_value_t sv = js_tostring_val(js, chunk);
103 if (is_err(sv)) return sv;
104 str = js_getstr(js, sv, &str_len);
105 }
106 if (!str) { str = ""; str_len = 0; }
107
108 const uint8_t *s = (const uint8_t *)str;
109 size_t total = st->pending_len + str_len;
110
111 if (total == 0) return js_mkundef();
112
113 uint8_t *buf = malloc(total);
114 if (!buf) return js_mkerr(js, "out of memory");
115
116 size_t off = 0;
117 if (st->pending_len > 0) {
118 memcpy(buf, st->pending, st->pending_len);
119 off = st->pending_len;
120 st->pending_len = 0;
121 }
122 memcpy(buf + off, s, str_len);
123
124 size_t out_len = total;
125 if (out_len >= 3) {
126 uint8_t b0 = buf[out_len - 3], b1 = buf[out_len - 2], b2 = buf[out_len - 1];
127 if (b0 == 0xED && b1 >= 0xA0 && b1 <= 0xAF) {
128 st->pending[0] = b0;
129 st->pending[1] = b1;
130 st->pending[2] = b2;
131 st->pending_len = 3;
132 out_len -= 3;
133 }}
134
135 if (out_len == 0) {
136 free(buf);
137 return js_mkundef();
138 }
139
140 uint8_t *out = malloc(out_len * 4 / 3 + 4);
141 if (!out) { free(buf); return js_mkerr(js, "out of memory"); }
142
143 size_t i = 0, o = 0;
144 while (i < out_len) {
145 if (i + 5 < out_len &&
146 buf[i] == 0xED && buf[i+1] >= 0xA0 && buf[i+1] <= 0xAF &&
147 buf[i+3] == 0xED && buf[i+4] >= 0xB0 && buf[i+4] <= 0xBF) {
148 uint32_t hi_cp = ((uint32_t)0x0D << 12) | ((uint32_t)(buf[i+1] & 0x3F) << 6) | (buf[i+2] & 0x3F);
149 uint32_t lo_cp = ((uint32_t)0x0D << 12) | ((uint32_t)(buf[i+4] & 0x3F) << 6) | (buf[i+5] & 0x3F);
150 uint32_t cp = 0x10000 + ((hi_cp - 0xD800) << 10) + (lo_cp - 0xDC00);
151 out[o++] = (uint8_t)(0xF0 | (cp >> 18));
152 out[o++] = (uint8_t)(0x80 | ((cp >> 12) & 0x3F));
153 out[o++] = (uint8_t)(0x80 | ((cp >> 6) & 0x3F));
154 out[o++] = (uint8_t)(0x80 | (cp & 0x3F));
155 i += 6;
156 } else if (buf[i] == 0xED && i + 2 < out_len && buf[i+1] >= 0xA0 && buf[i+1] <= 0xBF) {
157 out[o++] = 0xEF; out[o++] = 0xBF; out[o++] = 0xBD;
158 i += 3;
159 } else out[o++] = buf[i++]; }
160
161 free(buf);
162
163 ArrayBufferData *ab = create_array_buffer_data(o);
164 if (!ab) { free(out); return js_mkerr(js, "out of memory"); }
165 memcpy(ab->data, out, o);
166 free(out);
167
168 ant_value_t result = create_typed_array(js, TYPED_ARRAY_UINT8, ab, 0, o, "Uint8Array");
169 return ts_is_controller(ctrl_obj)
170 ? ts_ctrl_enqueue(js, ctrl_obj, result)
171 : js_mkerr_typed(js, JS_ERR_TYPE, "Invalid TransformStreamDefaultController");
172}
173
174static ant_value_t tes_flush(ant_t *js, ant_value_t *args, int nargs) {
175 ant_value_t wrapper = js_get_slot(js->current_func, SLOT_DATA);
176 ant_value_t state_val = js_get_slot(wrapper, SLOT_DATA);
177 tes_state_t *st = (tes_state_t *)(uintptr_t)(size_t)js_getnum(state_val);
178 ant_value_t ctrl_obj = codec_flush_controller(args, nargs);
179
180 if (st->pending_len > 0) {
181 uint8_t fffd[3] = { 0xEF, 0xBF, 0xBD };
182 ArrayBufferData *ab = create_array_buffer_data(3);
183 if (!ab) return js_mkerr(js, "out of memory");
184 memcpy(ab->data, fffd, 3);
185 st->pending_len = 0;
186
187 ant_value_t result = create_typed_array(js, TYPED_ARRAY_UINT8, ab, 0, 3, "Uint8Array");
188 if (!ts_is_controller(ctrl_obj))
189 return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid TransformStreamDefaultController");
190 return ts_ctrl_enqueue(js, ctrl_obj, result);
191 }
192
193 return js_mkundef();
194}
195
196static ant_value_t js_tes_get_encoding(ant_t *js, ant_value_t *args, int nargs) {
197 return js_mkstr(js, "utf-8", 5);
198}
199
200static ant_value_t js_tes_get_readable(ant_t *js, ant_value_t *args, int nargs) {
201 ant_value_t ts_obj = tes_get_ts(js->this_val);
202 if (!ts_is_stream(ts_obj)) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid TextEncoderStream");
203 return ts_stream_readable(ts_obj);
204}
205
206static ant_value_t js_tes_get_writable(ant_t *js, ant_value_t *args, int nargs) {
207 ant_value_t ts_obj = tes_get_ts(js->this_val);
208 if (!ts_is_stream(ts_obj)) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid TextEncoderStream");
209 return ts_stream_writable(ts_obj);
210}
211
212static ant_value_t js_tes_ctor(ant_t *js, ant_value_t *args, int nargs) {
213 if (vtype(js->new_target) == T_UNDEF)
214 return js_mkerr_typed(js, JS_ERR_TYPE, "TextEncoderStream constructor requires 'new'");
215
216 tes_state_t *st = calloc(1, sizeof(tes_state_t));
217 if (!st) return js_mkerr(js, "out of memory");
218
219 ant_value_t obj = js_mkobj(js);
220 ant_value_t proto = js_instance_proto_from_new_target(js, g_tes_proto);
221 if (is_object_type(proto)) js_set_proto_init(obj, proto);
222 js_set_slot(obj, SLOT_DATA, ANT_PTR(st));
223 js_set_finalizer(obj, tes_state_finalize);
224
225 ant_value_t wrapper = js_mkobj(js);
226 js_set_slot(wrapper, SLOT_DATA, ANT_PTR(st));
227
228 ant_value_t transformer = js_mkobj(js);
229 ant_value_t transform_fn = js_heavy_mkfun(js, tes_transform, wrapper);
230 ant_value_t flush_fn = js_heavy_mkfun(js, tes_flush, wrapper);
231 js_set(js, transformer, "transform", transform_fn);
232 js_set(js, transformer, "flush", flush_fn);
233
234 ant_value_t ctor_args[1] = { transformer };
235 ant_value_t saved_new_target = js->new_target;
236 ant_value_t saved_this = js->this_val;
237 js->new_target = js_mknum(1);
238
239 ant_value_t ts_obj = js_ts_ctor(js, ctor_args, 1);
240 js->new_target = saved_new_target;
241 js->this_val = saved_this;
242
243 if (is_err(ts_obj)) { free(st); return ts_obj; }
244 js_set_slot(obj, SLOT_ENTRIES, ts_obj);
245 js_set_slot(wrapper, SLOT_ENTRIES, ts_obj);
246
247 return obj;
248}
249
250static ant_value_t tds_transform(ant_t *js, ant_value_t *args, int nargs) {
251 ant_value_t wrapper = js_get_slot(js->current_func, SLOT_DATA);
252 ant_value_t state_val = js_get_slot(wrapper, SLOT_DATA);
253 td_state_t *st = (td_state_t *)(uintptr_t)(size_t)js_getnum(state_val);
254 ant_value_t ctrl_obj = codec_transform_controller(args, nargs);
255
256 ant_value_t chunk = (nargs > 0) ? args[0] : js_mkundef();
257 const uint8_t *input = NULL;
258 size_t input_len = 0;
259
260 if (!is_object_type(chunk) || !buffer_source_get_bytes(js, chunk, &input, &input_len))
261 return js_mkerr_typed(js, JS_ERR_TYPE, "The provided value is not of type '(ArrayBuffer or ArrayBufferView)'");
262
263 ant_value_t result = td_decode(js, st, input, input_len, true);
264 if (is_err(result)) return result;
265
266 size_t slen = 0;
267 const char *sval = js_getstr(js, result, &slen);
268 if (sval && slen > 0) {
269 if (!ts_is_controller(ctrl_obj))
270 return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid TransformStreamDefaultController");
271 return ts_ctrl_enqueue(js, ctrl_obj, result);
272 }
273
274 return js_mkundef();
275}
276
277static ant_value_t tds_flush(ant_t *js, ant_value_t *args, int nargs) {
278 ant_value_t wrapper = js_get_slot(js->current_func, SLOT_DATA);
279 ant_value_t state_val = js_get_slot(wrapper, SLOT_DATA);
280 td_state_t *st = (td_state_t *)(uintptr_t)(size_t)js_getnum(state_val);
281 ant_value_t ctrl_obj = codec_flush_controller(args, nargs);
282
283 ant_value_t result = td_decode(js, st, NULL, 0, false);
284 if (is_err(result)) return result;
285
286 size_t slen = 0;
287 const char *sval = js_getstr(js, result, &slen);
288 if (sval && slen > 0) {
289 if (!ts_is_controller(ctrl_obj))
290 return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid TransformStreamDefaultController");
291 return ts_ctrl_enqueue(js, ctrl_obj, result);
292 }
293
294 return js_mkundef();
295}
296
297static ant_value_t js_tds_get_encoding(ant_t *js, ant_value_t *args, int nargs) {
298 ant_value_t state_val = js_get_slot(js->this_val, SLOT_DATA);
299 td_state_t *st = (td_state_t *)(uintptr_t)(size_t)js_getnum(state_val);
300 if (!st) return js_mkstr(js, "utf-8", 5);
301 switch (st->encoding) {
302 case TD_ENC_UTF16LE: return js_mkstr(js, "utf-16le", 8);
303 case TD_ENC_UTF16BE: return js_mkstr(js, "utf-16be", 8);
304 case TD_ENC_WINDOWS_1252: return js_mkstr(js, "windows-1252", 12);
305 case TD_ENC_ISO_8859_2: return js_mkstr(js, "iso-8859-2", 10);
306 default: return js_mkstr(js, "utf-8", 5);
307 }
308}
309
310static ant_value_t js_tds_get_fatal(ant_t *js, ant_value_t *args, int nargs) {
311 ant_value_t state_val = js_get_slot(js->this_val, SLOT_DATA);
312 td_state_t *st = (td_state_t *)(uintptr_t)(size_t)js_getnum(state_val);
313 return (st && st->fatal) ? js_true : js_false;
314}
315
316static ant_value_t js_tds_get_ignore_bom(ant_t *js, ant_value_t *args, int nargs) {
317 ant_value_t state_val = js_get_slot(js->this_val, SLOT_DATA);
318 td_state_t *st = (td_state_t *)(uintptr_t)(size_t)js_getnum(state_val);
319 return (st && st->ignore_bom) ? js_true : js_false;
320}
321
322static ant_value_t js_tds_get_readable(ant_t *js, ant_value_t *args, int nargs) {
323 ant_value_t ts_obj = tds_get_ts(js->this_val);
324 if (!ts_is_stream(ts_obj)) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid TextDecoderStream");
325 return ts_stream_readable(ts_obj);
326}
327
328static ant_value_t js_tds_get_writable(ant_t *js, ant_value_t *args, int nargs) {
329 ant_value_t ts_obj = tds_get_ts(js->this_val);
330 if (!ts_is_stream(ts_obj)) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid TextDecoderStream");
331 return ts_stream_writable(ts_obj);
332}
333
334static const char *tds_trim_label(const char *s, size_t len, size_t *out_len) {
335 while (len > 0 && (unsigned char)*s <= 0x20) { s++; len--; }
336 while (len > 0 && (unsigned char)s[len - 1] <= 0x20) { len--; }
337 *out_len = len;
338 return s;
339}
340
341static int tds_resolve_encoding(const char *s, size_t len) {
342 static const struct { const char *label; uint8_t label_len; td_encoding_t enc; } map[] = {
343 {"unicode-1-1-utf-8", 17, TD_ENC_UTF8}, {"unicode11utf8", 13, TD_ENC_UTF8},
344 {"unicode20utf8", 13, TD_ENC_UTF8}, {"utf-8", 5, TD_ENC_UTF8},
345 {"utf8", 4, TD_ENC_UTF8}, {"x-unicode20utf8",17, TD_ENC_UTF8},
346 {"windows-1252", 12, TD_ENC_WINDOWS_1252}, {"ascii", 5, TD_ENC_WINDOWS_1252},
347 {"unicodefffe", 11, TD_ENC_UTF16BE}, {"utf-16be", 8, TD_ENC_UTF16BE},
348 {"csunicode", 9, TD_ENC_UTF16LE}, {"iso-10646-ucs-2",16, TD_ENC_UTF16LE},
349 {"ucs-2", 5, TD_ENC_UTF16LE}, {"unicode", 7, TD_ENC_UTF16LE},
350 {"unicodefeff", 11, TD_ENC_UTF16LE}, {"utf-16", 6, TD_ENC_UTF16LE},
351 {"utf-16le", 8, TD_ENC_UTF16LE},
352 {"iso-8859-2", 10, TD_ENC_ISO_8859_2},
353 {NULL, 0, 0}
354 };
355 for (int i = 0; map[i].label; i++) {
356 if (len == map[i].label_len && strncasecmp(s, map[i].label, len) == 0) return (int)map[i].enc;
357 }
358 return -1;
359}
360
361static ant_value_t js_tds_ctor(ant_t *js, ant_value_t *args, int nargs) {
362 if (vtype(js->new_target) == T_UNDEF)
363 return js_mkerr_typed(js, JS_ERR_TYPE, "TextDecoderStream constructor requires 'new'");
364
365 td_encoding_t enc = TD_ENC_UTF8;
366 if (nargs > 0 && !is_undefined(args[0])) {
367 ant_value_t label = (vtype(args[0]) == T_STR) ? args[0] : coerce_to_str(js, args[0]);
368 if (is_err(label)) return label;
369
370 size_t llen;
371 const char *raw = js_getstr(js, label, &llen);
372 if (raw) {
373 size_t tlen;
374 const char *trimmed = tds_trim_label(raw, llen, &tlen);
375 int resolved = tds_resolve_encoding(trimmed, tlen);
376 if (resolved < 0) return js_mkerr_typed(
377 js, JS_ERR_RANGE, "Failed to construct 'TextDecoderStream': The encoding label provided ('%.*s') is invalid.",
378 (int)tlen, trimmed
379 );
380 enc = (td_encoding_t)resolved;
381 }
382 }
383
384 bool fatal = false;
385 bool ignore_bom = false;
386 if (nargs > 1 && is_object_type(args[1])) {
387 ant_value_t fv = js_getprop_fallback(js, args[1], "fatal");
388 if (is_err(fv)) return fv;
389 if (vtype(fv) != T_UNDEF) fatal = js_truthy(js, fv);
390 ant_value_t bv = js_getprop_fallback(js, args[1], "ignoreBOM");
391 if (is_err(bv)) return bv;
392 if (vtype(bv) != T_UNDEF) ignore_bom = js_truthy(js, bv);
393 }
394
395 td_state_t *st = td_state_new(enc, fatal, ignore_bom);
396 if (!st) return js_mkerr(js, "out of memory");
397
398 ant_value_t obj = js_mkobj(js);
399 ant_value_t proto = js_instance_proto_from_new_target(js, g_tds_proto);
400 if (is_object_type(proto)) js_set_proto_init(obj, proto);
401 js_set_slot(obj, SLOT_DATA, ANT_PTR(st));
402 js_set_finalizer(obj, tds_state_finalize);
403
404 ant_value_t wrapper = js_mkobj(js);
405 js_set_slot(wrapper, SLOT_DATA, ANT_PTR(st));
406
407 ant_value_t transformer = js_mkobj(js);
408 ant_value_t transform_fn = js_heavy_mkfun(js, tds_transform, wrapper);
409 ant_value_t flush_fn = js_heavy_mkfun(js, tds_flush, wrapper);
410 js_set(js, transformer, "transform", transform_fn);
411 js_set(js, transformer, "flush", flush_fn);
412
413 ant_value_t ctor_args[1] = { transformer };
414
415 ant_value_t saved_new_target = js->new_target;
416 ant_value_t saved_this = js->this_val;
417 js->new_target = js_mknum(1);
418
419 ant_value_t ts_obj = js_ts_ctor(js, ctor_args, 1);
420
421 js->new_target = saved_new_target;
422 js->this_val = saved_this;
423
424 if (is_err(ts_obj)) { free(st); return ts_obj; }
425
426 js_set_slot(obj, SLOT_ENTRIES, ts_obj);
427 js_set_slot(wrapper, SLOT_ENTRIES, ts_obj);
428
429 return obj;
430}
431
432void init_codec_stream_module(void) {
433 ant_t *js = rt->js;
434 ant_value_t g = js_glob(js);
435
436 g_tes_proto = js_mkobj(js);
437 js_set_getter_desc(js, g_tes_proto, "encoding", 8, js_mkfun(js_tes_get_encoding), JS_DESC_C);
438 js_set_getter_desc(js, g_tes_proto, "readable", 8, js_mkfun(js_tes_get_readable), JS_DESC_C);
439 js_set_getter_desc(js, g_tes_proto, "writable", 8, js_mkfun(js_tes_get_writable), JS_DESC_C);
440 js_set_sym(js, g_tes_proto, get_toStringTag_sym(), js_mkstr(js, "TextEncoderStream", 17));
441
442 ant_value_t tes_ctor = js_make_ctor(js, js_tes_ctor, g_tes_proto, "TextEncoderStream", 17);
443 js_set(js, g, "TextEncoderStream", tes_ctor);
444 js_set_descriptor(js, g, "TextEncoderStream", 17, JS_DESC_W | JS_DESC_C);
445
446 g_tds_proto = js_mkobj(js);
447 js_set_getter_desc(js, g_tds_proto, "encoding", 8, js_mkfun(js_tds_get_encoding), JS_DESC_C);
448 js_set_getter_desc(js, g_tds_proto, "fatal", 5, js_mkfun(js_tds_get_fatal), JS_DESC_C);
449 js_set_getter_desc(js, g_tds_proto, "ignoreBOM", 9, js_mkfun(js_tds_get_ignore_bom), JS_DESC_C);
450 js_set_getter_desc(js, g_tds_proto, "readable", 8, js_mkfun(js_tds_get_readable), JS_DESC_C);
451 js_set_getter_desc(js, g_tds_proto, "writable", 8, js_mkfun(js_tds_get_writable), JS_DESC_C);
452 js_set_sym(js, g_tds_proto, get_toStringTag_sym(), js_mkstr(js, "TextDecoderStream", 17));
453
454 ant_value_t tds_ctor = js_make_ctor(js, js_tds_ctor, g_tds_proto, "TextDecoderStream", 17);
455 js_set(js, g, "TextDecoderStream", tds_ctor);
456 js_set_descriptor(js, g, "TextDecoderStream", 17, JS_DESC_W | JS_DESC_C);
457}
458
459void gc_mark_codec_streams(ant_t *js, void (*mark)(ant_t *, ant_value_t)) {
460 mark(js, g_tes_proto);
461 mark(js, g_tds_proto);
462}