MIRROR: javascript for 馃悳's, a tiny runtime with big ambitions
1#include "utils.h"
2#include "messages.h"
3
4#include <oxc.h>
5#include <stdio.h>
6#include <stdbool.h>
7#include <sys/stat.h>
8#include <crprintf.h>
9
10const char *const module_resolve_extensions[] = {
11 ".js", ".mjs", ".cjs",
12 ".ts", ".mts", ".cts",
13 ".json", ".node", NULL
14};
15
16int hex_digit(char c) {
17 static const int8_t lookup[256] = {
18 ['0']=0, ['1']=1, ['2']=2, ['3']=3, ['4']=4, ['5']=5, ['6']=6, ['7']=7, ['8']=8, ['9']=9,
19 ['a']=10, ['b']=11, ['c']=12, ['d']=13, ['e']=14, ['f']=15,
20 ['A']=10, ['B']=11, ['C']=12, ['D']=13, ['E']=14, ['F']=15,
21 };
22 int8_t val = lookup[(unsigned char)c];
23 return val ? val : (c == '0' ? 0 : -1);
24}
25
26char hex_char(int v) {
27 return "0123456789abcdef"[v & 0x0f];
28}
29
30uint64_t hash_key(const char *key, size_t len) {
31 uint64_t hash = 14695981039346656037ULL;
32 size_t i = 0;
33
34 for (; i + 8 <= len; i += 8) {
35 uint64_t word;
36 memcpy(&word, key + i, 8);
37 hash ^= word;
38 hash *= 1099511628211ULL;
39 }
40
41 for (; i < len; i++) {
42 hash ^= (uint8_t)key[i];
43 hash *= 1099511628211ULL;
44 }
45
46 return hash;
47}
48
49double half_to_double(uint16_t bits16) {
50 uint32_t sign = ((uint32_t)bits16 & 0x8000u) << 16;
51 uint32_t exp = ((uint32_t)bits16 >> 10) & 0x1fu;
52 uint32_t mant = (uint32_t)bits16 & 0x03ffu;
53
54 uint32_t bits32 = 0;
55 float out = 0.0f;
56
57 if (exp == 0) {
58 if (mant == 0) {
59 bits32 = sign;
60 } else {
61 uint32_t exp32 = 113;
62 while ((mant & 0x0400u) == 0) {
63 mant <<= 1;
64 exp32--;
65 }
66 mant &= 0x03ffu;
67 bits32 = sign | (exp32 << 23) | (mant << 13);
68 }}
69
70 else if (exp == 0x1fu) bits32 = sign | 0x7f800000u | (mant << 13);
71 else bits32 = sign | ((exp + 112u) << 23) | (mant << 13);
72
73 memcpy(&out, &bits32, sizeof(out));
74 return (double)out;
75}
76
77uint16_t double_to_half(double value) {
78 float input = (float)value;
79
80 uint32_t bits32 = 0;
81 uint32_t sign = 0;
82 uint32_t exp = 0;
83 uint32_t mant = 0;
84 int32_t exp16 = 0;
85 uint16_t out = 0;
86
87 memcpy(&bits32, &input, sizeof(bits32));
88 sign = (bits32 >> 16) & 0x8000u;
89 exp = (bits32 >> 23) & 0xffu;
90 mant = bits32 & 0x007fffffu;
91
92 if (exp == 0xffu) {
93 if (mant != 0) return (uint16_t)(sign | 0x7e00u);
94 return (uint16_t)(sign | 0x7c00u);
95 }
96
97 exp16 = (int32_t)exp - 127 + 15;
98 if (exp16 >= 0x1f) return (uint16_t)(sign | 0x7c00u);
99
100 if (exp16 <= 0) {
101 if (exp16 < -10) return (uint16_t)sign;
102 mant |= 0x00800000u;
103 uint32_t shift = (uint32_t)(14 - exp16);
104 uint16_t sub = (uint16_t)(mant >> shift);
105 if ((mant >> (shift - 1)) & 1u) sub++;
106 return (uint16_t)(sign | sub);
107 }
108
109 out = (uint16_t)(sign | ((uint32_t)exp16 << 10) | (mant >> 13));
110 if (mant & 0x00001000u) out++;
111
112 return out;
113}
114
115int is_typescript_file(const char *filename) {
116 if (filename == NULL) return 0;
117 size_t len = strlen(filename);
118 if (len < 3) return 0;
119
120 const char *ext = filename + len;
121 while (ext > filename && *(ext - 1) != '.' && *(ext - 1) != '/') ext--;
122 if (ext == filename || *(ext - 1) != '.') return 0;
123 ext--;
124
125 return (strcmp(ext, ".ts") == 0 || strcmp(ext, ".mts") == 0 || strcmp(ext, ".cts") == 0);
126}
127
128int strip_typescript_inplace(
129 char **buffer,
130 size_t len,
131 const char *filename,
132 int is_module,
133 size_t *out_len,
134 const char **error_detail
135) {
136 if (out_len) *out_len = len;
137 if (error_detail) *error_detail = NULL;
138 if (!is_typescript_file(filename)) return 0;
139
140 if (!buffer || !*buffer) {
141 if (error_detail) *error_detail = "null input/output passed";
142 return OXC_ERR_NULL_INPUT;
143 }
144
145 char *input = *buffer;
146 char error_buf[256] = {0};
147 size_t stripped_len = 0;
148
149 int strip_error = OXC_ERR_TRANSFORM_FAILED;
150 char *stripped = OXC_strip_types_owned(
151 input, filename, is_module,
152 &stripped_len, &strip_error,
153 error_buf, sizeof(error_buf)
154 );
155
156 if (!stripped) {
157 if (error_buf[0] != '\0') {
158 size_t msg_len = strlen(error_buf);
159 size_t copy_len = msg_len > len ? len : msg_len;
160 memcpy(input, error_buf, copy_len);
161 input[copy_len] = '\0';
162 } else input[0] = '\0';
163
164 if (error_detail) {
165 *error_detail = input[0] != '\0' ? input : "unknown strip error";
166 }
167
168 return strip_error;
169 }
170
171 char *next = realloc(input, stripped_len + 1);
172 if (!next) {
173 free(stripped);
174 if (error_detail) *error_detail = "out of memory while resizing strip output buffer";
175 return OXC_ERR_OUTPUT_TOO_LARGE;
176 }
177
178 memcpy(next, stripped, stripped_len + 1);
179 free(stripped);
180
181 *buffer = next;
182 if (out_len) *out_len = stripped_len;
183
184 return 0;
185}
186
187static bool is_entrypoint_script_extension(const char *ext) {
188 return
189 ext &&
190 strcmp(ext, ".json") != 0 &&
191 strcmp(ext, ".node") != 0;
192}
193
194static bool has_js_extension(const char *filename) {
195 const char *slash = strrchr(filename, '/');
196 const char *base = slash ? slash + 1 : filename;
197 const char *dot = strrchr(base, '.');
198 if (!dot) return false;
199 for (const char *const *ext = module_resolve_extensions; *ext; ext++) {
200 if (!is_entrypoint_script_extension(*ext)) continue;
201 if (strcmp(dot, *ext) == 0) return true;
202 }
203 return false;
204}
205
206char *resolve_js_file(const char *filename) {
207 extern bool esm_is_url(const char *path);
208 if (!filename) return NULL;
209 if (esm_is_url(filename)) return strdup(filename);
210
211 struct stat st;
212 if (stat(filename, &st) == 0) {
213 if (S_ISREG(st.st_mode)) {
214 const char *slash = strrchr(filename, '/');
215 const char *base = slash ? slash + 1 : filename;
216 const char *dot = strrchr(base, '.');
217 if (dot && !has_js_extension(filename)) return NULL;
218 return strdup(filename);
219 }
220 if (!S_ISDIR(st.st_mode)) return NULL;
221
222 size_t len = strlen(filename);
223 int has_slash = (len > 0 && filename[len - 1] == '/');
224
225 for (const char *const *ext = module_resolve_extensions; *ext; ext++) {
226 if (!is_entrypoint_script_extension(*ext)) continue;
227 size_t ext_len = strlen(*ext);
228 char *index_path = try_oom(len + 7 + ext_len + 1);
229 sprintf(index_path, "%s%sindex%s", filename, has_slash ? "" : "/", *ext);
230 if (stat(index_path, &st) == 0 && S_ISREG(st.st_mode)) return index_path;
231 free(index_path);
232 }
233
234 return NULL;
235 }
236
237 if (has_js_extension(filename)) return NULL;
238 size_t base_len = strlen(filename);
239
240 for (const char *const *ext = module_resolve_extensions; *ext; ext++) {
241 if (!is_entrypoint_script_extension(*ext)) continue;
242 size_t ext_len = strlen(*ext);
243 char *test_path = try_oom(base_len + ext_len + 1);
244
245 memcpy(test_path, filename, base_len);
246 memcpy(test_path + base_len, *ext, ext_len + 1);
247
248 if (stat(test_path, &st) == 0 && S_ISREG(st.st_mode)) {
249 return test_path;
250 } free(test_path);
251 }
252
253 return NULL;
254}
255
256char *resolve_typescript_source_fallback(const char *filename) {
257 if (!filename) return NULL;
258
259 const char *mapped_ext = NULL;
260 size_t trim_len = 0;
261
262 size_t len = strlen(filename);
263 if (len > 3 && strcmp(filename + len - 3, ".js") == 0) {
264 mapped_ext = ".ts";
265 trim_len = 3;
266 } else if (len > 4 && strcmp(filename + len - 4, ".mjs") == 0) {
267 mapped_ext = ".mts";
268 trim_len = 4;
269 } else if (len > 4 && strcmp(filename + len - 4, ".cjs") == 0) {
270 mapped_ext = ".cts";
271 trim_len = 4;
272 } else return NULL;
273
274 size_t mapped_len = strlen(mapped_ext);
275 char *mapped = try_oom(len - trim_len + mapped_len + 1);
276 memcpy(mapped, filename, len - trim_len);
277 memcpy(mapped + len - trim_len, mapped_ext, mapped_len + 1);
278
279 return mapped;
280}
281
282typedef struct {
283 const char *repl; size_t repl_len; size_t *ri;
284 const char *matched; size_t matched_len;
285 const char *str; size_t str_len; size_t position;
286 const repl_capture_t *caps; int ncaptures;
287 char **buf; size_t *buf_len; size_t *buf_cap;
288} rt_ctx_t;
289
290static bool rt_append(rt_ctx_t *c, const char *data, size_t dlen) {
291 if (dlen == 0) return true;
292 if (*c->buf_len > SIZE_MAX - dlen - 1) return false;
293
294 if (*c->buf_len + dlen >= *c->buf_cap) {
295 size_t needed = *c->buf_len + dlen + 1;
296 size_t new_cap = needed * 2;
297 if (new_cap < needed) new_cap = needed;
298
299 char *next = realloc(*c->buf, new_cap);
300 if (!next) return false;
301 *c->buf = next;
302 *c->buf_cap = new_cap;
303 }
304
305 memcpy(*c->buf + *c->buf_len, data, dlen);
306 *c->buf_len += dlen;
307 return true;
308}
309
310static bool rt_dollar(rt_ctx_t *c) {
311 *c->ri += 2;
312 return rt_append(c, "$", 1);
313}
314
315static bool rt_match(rt_ctx_t *c) {
316 *c->ri += 2;
317 return rt_append(c, c->matched, c->matched_len);
318}
319
320static bool rt_prefix(rt_ctx_t *c) {
321 *c->ri += 2;
322 return rt_append(c, c->str, c->position);
323}
324
325static bool rt_suffix(rt_ctx_t *c) {
326 size_t after = c->position + c->matched_len;
327 bool ok = true;
328 if (after < c->str_len)
329 ok = rt_append(c, c->str + after, c->str_len - after);
330 *c->ri += 2;
331 return ok;
332}
333
334static bool rt_capture(rt_ctx_t *c) {
335 char nc = c->repl[*c->ri + 1];
336 int gn = nc - '0';
337 *c->ri += 2;
338
339 if (*c->ri < c->repl_len && c->repl[*c->ri] >= '0' && c->repl[*c->ri] <= '9') {
340 int two = gn * 10 + (c->repl[*c->ri] - '0');
341 if (two <= c->ncaptures) { gn = two; (*c->ri)++; }
342 }
343
344 if (gn > 0 && gn <= c->ncaptures && c->caps[gn - 1].ptr)
345 return rt_append(c, c->caps[gn - 1].ptr, c->caps[gn - 1].len);
346
347 if (gn == 0 || gn > c->ncaptures)
348 return rt_append(c, "$", 1) && rt_append(c, &nc, 1);
349
350 return true;
351}
352
353typedef bool (*rt_handler_t)(rt_ctx_t *);
354static rt_handler_t rt_dispatch[128];
355static bool rt_dispatch_init = false;
356
357static void rt_init_dispatch(void) {
358 if (rt_dispatch_init) return;
359 rt_dispatch['$'] = rt_dollar;
360 rt_dispatch['&'] = rt_match;
361 rt_dispatch['`'] = rt_prefix;
362 rt_dispatch['\''] = rt_suffix;
363 for (int i = '0'; i <= '9'; i++) rt_dispatch[i] = rt_capture;
364 rt_dispatch_init = true;
365}
366
367bool repl_template(
368 const char *repl, size_t repl_len,
369 const char *matched, size_t matched_len,
370 const char *str, size_t str_len, size_t position,
371 const repl_capture_t *caps, int ncaptures,
372 char **buf, size_t *buf_len, size_t *buf_cap
373) {
374 rt_init_dispatch();
375 rt_ctx_t c = {
376 repl, repl_len, NULL,
377 matched, matched_len,
378 str, str_len, position,
379 caps, ncaptures,
380 buf, buf_len, buf_cap,
381 };
382
383 for (size_t ri = 0; ri < repl_len; ) {
384 if (repl[ri] == '$' && ri + 1 < repl_len) {
385 unsigned char nc = (unsigned char)repl[ri + 1];
386 c.ri = &ri;
387 rt_handler_t h = nc < 128 ? rt_dispatch[nc] : NULL;
388 if (h) { if (!h(&c)) return false; continue; }
389 }
390 if (!rt_append(&c, &repl[ri], 1)) return false;
391 ri++;
392 }
393
394 return true;
395}
396
397void *try_oom(size_t size) {
398 void *p = malloc(size);
399 if (!p) {
400 crfprintf(stderr, msg.oom_fatal);
401 exit(EXIT_FAILURE);
402 } return p;
403}
404
405void cstr_free(cstr_buf_t *buf) {
406 if (buf->heap) free(buf->heap);
407}
408
409char *cstr_init(cstr_buf_t *buf, char *stack, size_t stack_size, const char *src, size_t len) {
410 if (len < stack_size) {
411 buf->ptr = stack;
412 buf->heap = NULL;
413 } else {
414 buf->heap = malloc(len + 1);
415 if (!buf->heap) return NULL;
416 buf->ptr = buf->heap;
417 }
418 memcpy(buf->ptr, src, len);
419 buf->ptr[len] = '\0';
420 return buf->ptr;
421}