MIRROR: javascript for 馃悳's, a tiny runtime with big ambitions
1#include <compat.h> // IWYU pragma: keep
2
3#include <ctype.h>
4#include <stdio.h>
5#include <stdlib.h>
6#include <string.h>
7
8#include "ant.h"
9#include "errors.h"
10#include "internal.h"
11#include "modules/symbol.h"
12
13#ifndef PATH_MAX
14#define PATH_MAX 4096
15#endif
16
17#ifdef _WIN32
18#define PATH_SEP '\\'
19#define PATH_SEP_STR "\\"
20#define PATH_DELIMITER ';'
21#else
22#define PATH_SEP '/'
23#define PATH_SEP_STR "/"
24#define PATH_DELIMITER ':'
25#endif
26
27typedef enum {
28 PATH_STYLE_POSIX = 1,
29 PATH_STYLE_WIN32 = 2,
30} path_style_t;
31
32static path_style_t path_host_style(void) {
33#ifdef _WIN32
34 return PATH_STYLE_WIN32;
35#else
36 return PATH_STYLE_POSIX;
37#endif
38}
39
40static path_style_t path_current_style(ant_t *js) {
41 ant_value_t data = js_get_slot(js->current_func, SLOT_DATA);
42 if (vtype(data) == T_NUM) {
43 int style = (int)js_getnum(data);
44 if (style == PATH_STYLE_WIN32) return PATH_STYLE_WIN32;
45 if (style == PATH_STYLE_POSIX) return PATH_STYLE_POSIX;
46 }
47 return path_host_style();
48}
49
50static bool path_style_is_windows(path_style_t style) {
51 return style == PATH_STYLE_WIN32;
52}
53
54static bool path_is_sep(path_style_t style, char ch) {
55 if (style == PATH_STYLE_WIN32) return ch == '\\' || ch == '/';
56 return ch == '/';
57}
58
59static char path_sep_char(path_style_t style) {
60 return style == PATH_STYLE_WIN32 ? '\\' : '/';
61}
62
63static const char *path_sep_str(path_style_t style) {
64 return style == PATH_STYLE_WIN32 ? "\\" : "/";
65}
66
67static const char *path_delimiter_str(path_style_t style) {
68 return style == PATH_STYLE_WIN32 ? ";" : ":";
69}
70
71static bool path_has_drive_letter(const char *path, size_t len) {
72 return len >= 2 && isalpha((unsigned char)path[0]) && path[1] == ':';
73}
74
75static bool path_is_absolute_style(path_style_t style, const char *path, size_t len) {
76 if (!path || len == 0) return false;
77 if (style == PATH_STYLE_WIN32) {
78 if (path_is_sep(style, path[0])) return true;
79 return len >= 3 && path_has_drive_letter(path, len) && path_is_sep(style, path[2]);
80 }
81 return path[0] == '/';
82}
83
84static char *path_normalize_separators(path_style_t style, const char *path, size_t len) {
85 char *result = malloc(len + 1);
86 if (!result) return NULL;
87
88 for (size_t i = 0; i < len; i++) {
89 if (style == PATH_STYLE_WIN32 && (path[i] == '\\' || path[i] == '/'))
90 result[i] = '\\';
91 else result[i] = path[i];
92 }
93
94 result[len] = '\0';
95 return result;
96}
97
98static size_t path_root_length(path_style_t style, const char *path, size_t len) {
99 if (!path || len == 0) return 0;
100 if (style != PATH_STYLE_WIN32) return path[0] == '/' ? 1 : 0;
101
102 if (len >= 2 && path_is_sep(style, path[0]) && path_is_sep(style, path[1])) {
103 size_t i = 2;
104 int parts = 0;
105
106 while (i < len) {
107 while (i < len && path_is_sep(style, path[i])) i++;
108
109 size_t start = i;
110 while (i < len && !path_is_sep(style, path[i])) i++;
111
112 if (i == start) continue;
113 parts++;
114
115 if (parts == 2) {
116 while (i < len && path_is_sep(style, path[i])) i++;
117 return i;
118 }}
119
120 return 2;
121 }
122
123 if (path_has_drive_letter(path, len))
124 return (len >= 3 && path_is_sep(style, path[2])) ? 3 : 2;
125
126 return path_is_sep(style, path[0]) ? 1 : 0;
127}
128
129static bool path_is_drive_relative(path_style_t style, const char *path, size_t len) {
130 return style == PATH_STYLE_WIN32 && path_has_drive_letter(path, len) && !(len >= 3 && path_is_sep(style, path[2]));
131}
132
133typedef struct {
134 char *from_norm;
135 char *to_norm;
136 char **from_segs;
137 char **to_segs;
138 size_t *from_lens;
139 size_t *to_lens;
140 int from_count;
141 int to_count;
142 size_t from_root_len;
143 size_t to_root_len;
144 int common;
145 char *result;
146 size_t result_cap;
147 size_t pos;
148} path_relative_ctx_t;
149
150static size_t path_trimmed_end(path_style_t style, const char *path, size_t len) {
151 size_t root_len = path_root_length(style, path, len);
152
153 while (len > root_len && path_is_sep(style, path[len - 1])) len--;
154 return len;
155}
156
157static size_t path_basename_start(path_style_t style, const char *path, size_t len) {
158 size_t root_len = path_root_length(style, path, len);
159 size_t end = path_trimmed_end(style, path, len);
160 size_t start = end;
161
162 while (start > root_len && !path_is_sep(style, path[start - 1])) start--;
163 return start;
164}
165
166static ant_value_t path_make_string(ant_t *js, const char *src, size_t len) {
167 return js_mkstr(js, src, len);
168}
169
170static ant_value_t builtin_path_basename(ant_t *js, ant_value_t *args, int nargs) {
171 path_style_t style = path_current_style(js);
172 if (nargs < 1) return js_mkerr(js, "basename() requires a path argument");
173 if (vtype(args[0]) != T_STR) return js_mkerr(js, "basename() path must be a string");
174
175 size_t path_len;
176 char *path = js_getstr(js, args[0], &path_len);
177 if (!path || path_len == 0) return js_mkstr(js, "", 0);
178
179 size_t end = path_trimmed_end(style, path, path_len);
180 size_t start = path_basename_start(style, path, path_len);
181 const char *base = path + start;
182 size_t base_len = end > start ? end - start : 0;
183
184 if (nargs >= 2 && vtype(args[1]) == T_STR) {
185 size_t ext_len;
186 char *ext = js_getstr(js, args[1], &ext_len);
187
188 if (ext && ext_len > 0 && base_len >= ext_len) {
189 if (memcmp(base + base_len - ext_len, ext, ext_len) == 0) base_len -= ext_len;
190 }
191 }
192
193 return path_make_string(js, base, base_len);
194}
195
196// path.dirname(path)
197static ant_value_t builtin_path_dirname(ant_t *js, ant_value_t *args, int nargs) {
198 path_style_t style = path_current_style(js);
199
200 if (nargs < 1) return js_mkerr(js, "dirname() requires a path argument");
201 if (vtype(args[0]) != T_STR) return js_mkerr(js, "dirname() path must be a string");
202
203 size_t path_len;
204 char *path = js_getstr(js, args[0], &path_len);
205
206 if (!path || path_len == 0) return js_mkstr(js, ".", 1);
207 char *normalized = path_normalize_separators(style, path, path_len);
208
209 size_t root_len = 0;
210 size_t end = 0;
211 size_t cut = 0;
212
213 if (!normalized) return js_mkerr(js, "Out of memory");
214 root_len = path_root_length(style, normalized, path_len);
215 end = path_trimmed_end(style, normalized, path_len);
216 cut = end;
217
218 while (cut > root_len && !path_is_sep(style, normalized[cut - 1])) cut--;
219 while (cut > root_len && path_is_sep(style, normalized[cut - 1])) cut--;
220
221 if (cut == 0) {
222 free(normalized);
223 return js_mkstr(js, ".", 1);
224 }
225
226 if (cut < root_len) cut = root_len;
227
228 ant_value_t result = path_make_string(js, normalized, cut);
229 free(normalized);
230 return result;
231}
232
233// path.extname(path)
234static ant_value_t builtin_path_extname(ant_t *js, ant_value_t *args, int nargs) {
235 path_style_t style = path_current_style(js);
236
237 if (nargs < 1) return js_mkerr(js, "extname() requires a path argument");
238 if (vtype(args[0]) != T_STR) return js_mkerr(js, "extname() path must be a string");
239
240 size_t path_len;
241 char *path = js_getstr(js, args[0], &path_len);
242 if (!path || path_len == 0) return js_mkstr(js, "", 0);
243
244 size_t start = path_basename_start(style, path, path_len);
245 size_t end = path_trimmed_end(style, path, path_len);
246 const char *base = path + start;
247 size_t base_len = end > start ? end - start : 0;
248
249 for (size_t i = base_len; i > 0; i--) {
250 if (base[i - 1] != '.') continue;
251 if (i - 1 == 0) break;
252 return path_make_string(js, base + i - 1, base_len - (i - 1));
253 }
254
255 return js_mkstr(js, "", 0);
256}
257
258static int is_dotdot(const char *seg, size_t len) {
259 return len == 2 && seg[0] == '.' && seg[1] == '.';
260}
261
262static char* normalize_path_full(path_style_t style, const char *path, size_t path_len) {
263 char *normalized = NULL;
264 char **segments = NULL;
265 size_t *seg_lens = NULL;
266
267 char *result = NULL;
268 char sep = path_sep_char(style);
269
270 size_t root_len = 0;
271 bool drive_relative = false;
272
273 if (path_len == 0) return strdup(".");
274
275 normalized = path_normalize_separators(style, path, path_len);
276 if (!normalized) goto fail;
277
278 segments = malloc(path_len * sizeof(char*));
279 seg_lens = malloc(path_len * sizeof(size_t));
280 if (!segments || !seg_lens) goto fail;
281
282 root_len = path_root_length(style, normalized, path_len);
283 drive_relative = path_is_drive_relative(style, normalized, path_len);
284 int seg_count = 0;
285
286 char *start = normalized + root_len;
287 char *seg_start = start;
288
289 for (char *p = start; ; p++) {
290 if (!path_is_sep(style, *p) && *p != '\0') continue;
291
292 size_t len = p - seg_start;
293 if (len == 0 || (len == 1 && seg_start[0] == '.')) goto next;
294
295 if (is_dotdot(seg_start, len)) {
296 if (seg_count > 0 && !is_dotdot(segments[seg_count - 1], seg_lens[seg_count - 1])) seg_count--;
297 else if (root_len == 0 || drive_relative) goto add_segment;
298 goto next;
299 }
300
301 add_segment:
302 segments[seg_count] = seg_start;
303 seg_lens[seg_count] = len;
304 seg_count++;
305
306 next:
307 if (*p == '\0') break;
308 seg_start = p + 1;
309 }
310
311 size_t result_len = root_len;
312 for (int i = 0; i < seg_count; i++) {
313 result_len += seg_lens[i];
314 if (i < seg_count - 1) result_len++;
315 }
316 if (result_len == 0) result_len = 1;
317
318 result = malloc(result_len + 1);
319 if (!result) goto fail;
320
321 size_t pos = 0;
322 if (root_len > 0) {
323 memcpy(result + pos, normalized, root_len);
324 pos += root_len;
325 }
326
327 for (int i = 0; i < seg_count; i++) {
328 if (pos > 0 && result[pos - 1] != sep && !(drive_relative && pos == root_len))
329 result[pos++] = sep;
330 memcpy(result + pos, segments[i], seg_lens[i]);
331 pos += seg_lens[i];
332 }
333
334 if (pos == 0) result[pos++] = '.';
335 if (path_len > root_len && path_is_sep(style, path[path_len - 1])
336 && pos > 0 && !path_is_sep(style, result[pos - 1])) {
337 result = realloc(result, pos + 2);
338 if (!result) goto fail;
339 result[pos++] = sep;
340 }
341
342 result[pos] = '\0';
343 free(segments);
344 free(seg_lens);
345 free(normalized);
346
347 return result;
348
349fail:
350 free(segments);
351 free(seg_lens);
352 free(normalized);
353 return NULL;
354}
355
356// path.normalize(path)
357static ant_value_t builtin_path_normalize(ant_t *js, ant_value_t *args, int nargs) {
358 path_style_t style = path_current_style(js);
359 if (nargs < 1) return js_mkerr(js, "normalize() requires a path argument");
360 if (vtype(args[0]) != T_STR) return js_mkerr(js, "normalize() path must be a string");
361
362 size_t path_len;
363 char *path = js_getstr(js, args[0], &path_len);
364 if (!path || path_len == 0) return js_mkstr(js, ".", 1);
365
366 char *result = normalize_path_full(style, path, path_len);
367 if (!result) return js_mkerr(js, "Out of memory");
368
369 ant_value_t ret = js_mkstr(js, result, strlen(result));
370 free(result);
371 return ret;
372}
373
374// path.join(...paths)
375static ant_value_t builtin_path_join(ant_t *js, ant_value_t *args, int nargs) {
376 path_style_t style = path_current_style(js);
377 char sep = path_sep_char(style);
378 if (nargs < 1) return js_mkstr(js, ".", 1);
379
380 size_t total_len = 0;
381 char **segments = malloc(nargs * sizeof(char*));
382 size_t *lengths = malloc(nargs * sizeof(size_t));
383
384 if (!segments || !lengths) {
385 free(segments);
386 free(lengths);
387 return js_mkerr(js, "Out of memory");
388 }
389
390 int valid_segments = 0;
391
392 for (int i = 0; i < nargs; i++) {
393 if (vtype(args[i]) == T_STR) {
394 segments[valid_segments] = js_getstr(js, args[i], &lengths[valid_segments]);
395 if (segments[valid_segments] && lengths[valid_segments] > 0) {
396 total_len += lengths[valid_segments] + 1; // +1 for separator
397 valid_segments++;
398 }}}
399
400 if (valid_segments == 0) {
401 free(segments);
402 free(lengths);
403 return js_mkstr(js, ".", 1);
404 }
405
406 char *result = malloc(total_len + 1);
407 if (!result) {
408 free(segments);
409 free(lengths);
410 return js_mkerr(js, "Out of memory");
411 }
412
413 size_t pos = 0;
414 for (int i = 0; i < valid_segments; i++) {
415 if (i > 0 && pos > 0 && result[pos - 1] != sep) {
416 result[pos++] = sep;
417 }
418
419 size_t start = 0;
420 if (i > 0 && path_is_sep(style, segments[i][0])) start = 1;
421
422 size_t seg_len = lengths[i];
423 while (seg_len > start + 1 && path_is_sep(style, segments[i][seg_len - 1])) seg_len--;
424
425 if (seg_len > start) {
426 memcpy(result + pos, segments[i] + start, seg_len - start);
427 pos += seg_len - start;
428 }
429 }
430
431 if (
432 valid_segments > 0 && lengths[valid_segments - 1] > 0
433 && path_is_sep(style, segments[valid_segments - 1][lengths[valid_segments - 1] - 1])
434 && pos > 0 && !path_is_sep(style, result[pos - 1])
435 ) result[pos++] = sep;
436
437 result[pos] = '\0';
438 char *normalized = normalize_path_full(style, result, pos);
439
440 free(result);
441 free(segments);
442 free(lengths);
443
444 if (!normalized) return js_mkerr(js, "Out of memory");
445 ant_value_t ret = js_mkstr(js, normalized, strlen(normalized));
446 free(normalized);
447
448 return ret;
449}
450
451// path.resolve(...paths)
452static ant_value_t builtin_path_resolve(ant_t *js, ant_value_t *args, int nargs) {
453 path_style_t style = path_current_style(js);
454
455 char cwd[PATH_MAX];
456 char *cwd_norm = NULL;
457 char *joined = NULL;
458 char *normalized = NULL;
459 char drive_prefix[3] = {0};
460
461 bool saw_absolute = false;
462 char sep = path_sep_char(style);
463
464 if (getcwd(cwd, sizeof(cwd)) == NULL)
465 return js_mkerr(js, "Failed to get current working directory");
466
467 cwd_norm = path_normalize_separators(style, cwd, strlen(cwd));
468 if (!cwd_norm) return js_mkerr(js, "Out of memory");
469
470 for (int i = nargs - 1; i >= 0; i--) {
471 char *next = NULL;
472 size_t seg_len = 0;
473 size_t joined_len = 0;
474 size_t next_len = 0;
475 char *segment = NULL;
476 size_t prefix_len = 0;
477
478 if (vtype(args[i]) != T_STR) continue;
479
480 segment = js_getstr(js, args[i], &seg_len);
481 if (!segment || seg_len == 0) continue;
482
483 if (style == PATH_STYLE_WIN32 && path_is_drive_relative(style, segment, seg_len)) {
484 if (drive_prefix[0] == '\0') {
485 drive_prefix[0] = segment[0];
486 drive_prefix[1] = ':';
487 drive_prefix[2] = '\0';
488 }
489 prefix_len = 2;
490 }
491
492 if (!joined) {
493 joined = strndup(segment + prefix_len, seg_len - prefix_len);
494 if (!joined) {
495 free(cwd_norm);
496 return js_mkerr(js, "Out of memory");
497 }
498 } else {
499 joined_len = strlen(joined);
500 next_len = (seg_len - prefix_len) + 1 + joined_len + 1;
501 next = malloc(next_len);
502 if (!next) {
503 free(cwd_norm);
504 free(joined);
505 return js_mkerr(js, "Out of memory");
506 }
507
508 snprintf(next, next_len, "%.*s%c%s", (int)(seg_len - prefix_len), segment + prefix_len, sep, joined);
509 free(joined);
510 joined = next;
511 }
512
513 if (path_is_absolute_style(style, segment, seg_len)) {
514 saw_absolute = true;
515 break;
516 }
517 }
518
519 if (!joined) {
520 if (style == PATH_STYLE_WIN32 && drive_prefix[0] != '\0') {
521 size_t cwd_len = strlen(cwd_norm);
522 joined = malloc(2 + cwd_len + 1);
523 if (!joined) {
524 free(cwd_norm);
525 return js_mkerr(js, "Out of memory");
526 }
527 snprintf(joined, 2 + cwd_len + 1, "%s%s", drive_prefix, cwd_norm);
528 } else {
529 joined = strdup(cwd_norm);
530 if (!joined) {
531 free(cwd_norm);
532 return js_mkerr(js, "Out of memory");
533 }
534 }
535 } else if (!saw_absolute) {
536 size_t cwd_len = strlen(cwd_norm);
537 size_t joined_len = strlen(joined);
538 size_t prefix_len = (style == PATH_STYLE_WIN32 && drive_prefix[0] != '\0') ? 2 : 0;
539 char *next = malloc(prefix_len + cwd_len + 1 + joined_len + 1);
540 if (!next) {
541 free(cwd_norm);
542 free(joined);
543 return js_mkerr(js, "Out of memory");
544 }
545
546 if (prefix_len > 0)
547 snprintf(next, prefix_len + cwd_len + 1 + joined_len + 1, "%s%s%c%s", drive_prefix, cwd_norm, sep, joined);
548 else snprintf(next, prefix_len + cwd_len + 1 + joined_len + 1, "%s%c%s", cwd_norm, sep, joined);
549 free(joined);
550 joined = next;
551 }
552
553 normalized = normalize_path_full(style, joined, strlen(joined));
554 free(cwd_norm);
555 free(joined);
556 if (!normalized) return js_mkerr(js, "Out of memory");
557
558 ant_value_t ret = js_mkstr(js, normalized, strlen(normalized));
559 free(normalized);
560 return ret;
561}
562
563// path.relative(from, to)
564static ant_value_t builtin_path_relative(ant_t *js, ant_value_t *args, int nargs) {
565 path_style_t style = path_current_style(js);
566 path_relative_ctx_t rel = {0};
567
568 char sep = path_sep_char(style);
569 if (nargs < 2) return js_mkerr(js, "relative() requires from and to arguments");
570 if (vtype(args[0]) != T_STR) return js_mkerr(js, "relative() from must be a string");
571 if (vtype(args[1]) != T_STR) return js_mkerr(js, "relative() to must be a string");
572
573 size_t from_len, to_len;
574 char *from = js_getstr(js, args[0], &from_len);
575 char *to = js_getstr(js, args[1], &to_len);
576
577 if (!from || !to) return js_mkerr(js, "Failed to get arguments");
578 if (from_len == to_len && strncmp(from, to, from_len) == 0) return js_mkstr(js, "", 0);
579
580 rel.from_norm = normalize_path_full(style, from, from_len);
581 rel.to_norm = normalize_path_full(style, to, to_len);
582 if (!rel.from_norm || !rel.to_norm) goto relative_fail;
583
584 rel.from_root_len = path_root_length(style, rel.from_norm, strlen(rel.from_norm));
585 rel.to_root_len = path_root_length(style, rel.to_norm, strlen(rel.to_norm));
586
587 if (style == PATH_STYLE_WIN32) {
588 if (rel.from_root_len != rel.to_root_len || strncasecmp(rel.from_norm, rel.to_norm, rel.from_root_len) != 0) {
589 ant_value_t out = js_mkstr(js, rel.to_norm, strlen(rel.to_norm));
590 free(rel.from_norm);
591 free(rel.to_norm);
592 return out;
593 }
594 } else if (rel.from_root_len != rel.to_root_len || strncmp(rel.from_norm, rel.to_norm, rel.from_root_len) != 0) {
595 ant_value_t out = js_mkstr(js, rel.to_norm, strlen(rel.to_norm));
596 free(rel.from_norm);
597 free(rel.to_norm);
598 return out;
599 }
600
601 rel.from_segs = malloc(strlen(rel.from_norm) * sizeof(char *));
602 rel.to_segs = malloc(strlen(rel.to_norm) * sizeof(char *));
603 rel.from_lens = malloc(strlen(rel.from_norm) * sizeof(size_t));
604 rel.to_lens = malloc(strlen(rel.to_norm) * sizeof(size_t));
605 if (!rel.from_segs || !rel.to_segs || !rel.from_lens || !rel.to_lens) goto relative_fail;
606
607 for (size_t i = rel.from_root_len, start = rel.from_root_len;; i++) {
608 if (rel.from_norm[i] != '\0' && !path_is_sep(style, rel.from_norm[i])) continue;
609 if (i > start) {
610 rel.from_segs[rel.from_count] = rel.from_norm + start;
611 rel.from_lens[rel.from_count++] = i - start;
612 }
613 if (rel.from_norm[i] == '\0') break;
614 start = i + 1;
615 }
616
617 for (size_t i = rel.to_root_len, start = rel.to_root_len;; i++) {
618 if (rel.to_norm[i] != '\0' && !path_is_sep(style, rel.to_norm[i])) continue;
619 if (i > start) {
620 rel.to_segs[rel.to_count] = rel.to_norm + start;
621 rel.to_lens[rel.to_count++] = i - start;
622 }
623 if (rel.to_norm[i] == '\0') break;
624 start = i + 1;
625 }
626
627 while (rel.common < rel.from_count && rel.common < rel.to_count) {
628 bool equal = false;
629 if (rel.from_lens[rel.common] == rel.to_lens[rel.common]) {
630 equal = style == PATH_STYLE_WIN32
631 ? strncasecmp(rel.from_segs[rel.common], rel.to_segs[rel.common], rel.from_lens[rel.common]) == 0
632 : strncmp(rel.from_segs[rel.common], rel.to_segs[rel.common], rel.from_lens[rel.common]) == 0;
633 }
634 if (!equal) break;
635 rel.common++;
636 }
637
638 rel.result_cap = strlen(rel.to_norm) + (size_t)(rel.from_count - rel.common) * 3 + 2;
639 rel.result = malloc(rel.result_cap);
640 if (!rel.result) goto relative_fail;
641
642 for (int i = rel.common; i < rel.from_count; i++) {
643 if (rel.pos > 0) rel.result[rel.pos++] = sep;
644 rel.result[rel.pos++] = '.';
645 rel.result[rel.pos++] = '.';
646 }
647
648 for (int i = rel.common; i < rel.to_count; i++) {
649 if (rel.pos > 0) rel.result[rel.pos++] = sep;
650 memcpy(rel.result + rel.pos, rel.to_segs[i], rel.to_lens[i]);
651 rel.pos += rel.to_lens[i];
652 }
653
654 if (rel.pos == 0) rel.result[rel.pos++] = '.';
655 rel.result[rel.pos] = '\0';
656
657 ant_value_t out = js_mkstr(js, rel.result, rel.pos);
658 free(rel.result);
659 free(rel.from_norm);
660 free(rel.to_norm);
661 free(rel.from_segs);
662 free(rel.to_segs);
663 free(rel.from_lens);
664 free(rel.to_lens);
665 return out;
666
667relative_fail:
668 free(rel.result);
669 free(rel.from_norm);
670 free(rel.to_norm);
671 free(rel.from_segs);
672 free(rel.to_segs);
673 free(rel.from_lens);
674 free(rel.to_lens);
675 return js_mkerr(js, "Out of memory");
676}
677
678// path.isAbsolute(path)
679static ant_value_t builtin_path_isAbsolute(ant_t *js, ant_value_t *args, int nargs) {
680 path_style_t style = path_current_style(js);
681 if (nargs < 1) return js_mkerr(js, "isAbsolute() requires a path argument");
682 if (vtype(args[0]) != T_STR) return js_mkerr(js, "isAbsolute() path must be a string");
683
684 size_t path_len;
685 char *path = js_getstr(js, args[0], &path_len);
686 if (!path || path_len == 0) return js_false;
687
688 return js_bool(path_is_absolute_style(style, path, path_len));
689}
690
691// path.parse(path)
692static ant_value_t builtin_path_parse(ant_t *js, ant_value_t *args, int nargs) {
693 path_style_t style = path_current_style(js);
694 if (nargs < 1) return js_mkerr(js, "parse() requires a path argument");
695 if (vtype(args[0]) != T_STR) return js_mkerr(js, "parse() path must be a string");
696
697 size_t path_len;
698 char *path = js_getstr(js, args[0], &path_len);
699 if (!path) return js_mkerr(js, "Failed to get path string");
700
701 ant_value_t result = js_mkobj(js);
702 char *normalized = path_normalize_separators(style, path, path_len);
703 size_t root_len = 0;
704 size_t start = 0;
705 size_t end = 0;
706 const char *base = NULL;
707 size_t base_len = 0;
708 if (!normalized) return js_mkerr(js, "Out of memory");
709
710 root_len = path_root_length(style, normalized, path_len);
711 js_set(js, result, "root", path_make_string(js, normalized, root_len));
712
713 end = path_trimmed_end(style, normalized, path_len);
714 start = path_basename_start(style, normalized, path_len);
715 base = normalized + start;
716 base_len = end > start ? end - start : 0;
717
718 if (start == 0) js_set(js, result, "dir", js_mkstr(js, ".", 1));
719 else {
720 size_t dir_len = start;
721 while (dir_len > root_len && path_is_sep(style, normalized[dir_len - 1])) dir_len--;
722 js_set(js, result, "dir", path_make_string(js, normalized, dir_len));
723 }
724
725 js_set(js, result, "base", path_make_string(js, base, base_len));
726
727 for (size_t i = base_len; i > 0; i--) {
728 if (base[i - 1] != '.') continue;
729 if (i - 1 == 0) break;
730 js_set(js, result, "ext", path_make_string(js, base + i - 1, base_len - (i - 1)));
731 js_set(js, result, "name", path_make_string(js, base, i - 1));
732 free(normalized);
733 return result;
734 }
735
736 js_set(js, result, "ext", js_mkstr(js, "", 0));
737 js_set(js, result, "name", path_make_string(js, base, base_len));
738 free(normalized);
739 return result;
740}
741
742// path.format(pathObject)
743static ant_value_t builtin_path_format(ant_t *js, ant_value_t *args, int nargs) {
744 path_style_t style = path_current_style(js);
745
746 char sep = path_sep_char(style);
747 if (nargs < 1) return js_mkerr(js, "format() requires a path object argument");
748 if (!is_special_object(args[0])) return js_mkerr(js, "format() argument must be an object");
749
750 ant_value_t obj = args[0];
751
752 ant_value_t dir_val = js_get(js, obj, "dir");
753 ant_value_t root_val = js_get(js, obj, "root");
754 ant_value_t base_val = js_get(js, obj, "base");
755 ant_value_t name_val = js_get(js, obj, "name");
756 ant_value_t ext_val = js_get(js, obj, "ext");
757
758 char result[PATH_MAX] = {0};
759 size_t pos = 0;
760
761 if (vtype(dir_val) == T_STR) {
762 size_t len;
763 char *str = js_getstr(js, dir_val, &len);
764 if (str && len > 0 && pos + len < PATH_MAX) {
765 memcpy(result + pos, str, len);
766 pos += len;
767 if (result[pos - 1] != sep && pos < PATH_MAX - 1) {
768 result[pos++] = sep;
769 }
770 }
771 } else if (vtype(root_val) == T_STR) {
772 size_t len;
773 char *str = js_getstr(js, root_val, &len);
774 if (str && len > 0 && pos + len < PATH_MAX) {
775 memcpy(result + pos, str, len);
776 pos += len;
777 }
778 }
779
780 if (vtype(base_val) == T_STR) {
781 size_t len;
782 char *str = js_getstr(js, base_val, &len);
783 if (str && len > 0 && pos + len < PATH_MAX) {
784 memcpy(result + pos, str, len);
785 pos += len;
786 }
787 } else {
788 if (vtype(name_val) == T_STR) {
789 size_t len;
790 char *str = js_getstr(js, name_val, &len);
791 if (str && len > 0 && pos + len < PATH_MAX) {
792 memcpy(result + pos, str, len);
793 pos += len;
794 }
795 }
796 if (vtype(ext_val) == T_STR) {
797 size_t len;
798 char *str = js_getstr(js, ext_val, &len);
799 if (str && len > 0 && pos + len < PATH_MAX) {
800 memcpy(result + pos, str, len);
801 pos += len;
802 }
803 }
804 }
805
806 return js_mkstr(js, result, pos);
807}
808
809typedef struct {
810 ant_value_t basename;
811 ant_value_t dirname;
812 ant_value_t extname;
813 ant_value_t join;
814 ant_value_t normalize;
815 ant_value_t resolve;
816 ant_value_t relative;
817 ant_value_t isAbsolute;
818 ant_value_t parse;
819 ant_value_t format;
820} path_api_t;
821
822static path_api_t path_build_api_for_style(ant_t *js, path_style_t style) {
823ant_value_t style_value = js_mknum((double)style);
824return (path_api_t){
825 .basename = js_heavy_mkfun(js, builtin_path_basename, style_value),
826 .dirname = js_heavy_mkfun(js, builtin_path_dirname, style_value),
827 .extname = js_heavy_mkfun(js, builtin_path_extname, style_value),
828 .join = js_heavy_mkfun(js, builtin_path_join, style_value),
829 .normalize = js_heavy_mkfun(js, builtin_path_normalize, style_value),
830 .resolve = js_heavy_mkfun(js, builtin_path_resolve, style_value),
831 .relative = js_heavy_mkfun(js, builtin_path_relative, style_value),
832 .isAbsolute = js_heavy_mkfun(js, builtin_path_isAbsolute, style_value),
833 .parse = js_heavy_mkfun(js, builtin_path_parse, style_value),
834 .format = js_heavy_mkfun(js, builtin_path_format, style_value)
835};}
836
837static void path_apply_api(ant_t *js, ant_value_t target, const path_api_t *api) {
838 js_set(js, target, "basename", api->basename);
839 js_set(js, target, "dirname", api->dirname);
840 js_set(js, target, "extname", api->extname);
841 js_set(js, target, "join", api->join);
842 js_set(js, target, "normalize", api->normalize);
843 js_set(js, target, "resolve", api->resolve);
844 js_set(js, target, "relative", api->relative);
845 js_set(js, target, "isAbsolute", api->isAbsolute);
846 js_set(js, target, "parse", api->parse);
847 js_set(js, target, "format", api->format);
848}
849
850static ant_value_t path_make_variant(ant_t *js, const path_api_t *api, path_style_t style) {
851 ant_value_t variant = js_mkobj(js);
852 path_apply_api(js, variant, api);
853
854 js_set(js, variant, "sep", js_mkstr(js, path_sep_str(style), 1));
855 js_set(js, variant, "delimiter", js_mkstr(js, path_delimiter_str(style), 1));
856 js_set(js, variant, "default", variant);
857
858 return variant;
859}
860
861ant_value_t path_library(ant_t *js) {
862 path_api_t api = path_build_api_for_style(js, path_host_style());
863 path_api_t posix_api = path_build_api_for_style(js, PATH_STYLE_POSIX);
864 path_api_t win32_api = path_build_api_for_style(js, PATH_STYLE_WIN32);
865
866 ant_value_t lib = path_make_variant(js, &api, path_host_style());
867 ant_value_t posix = path_make_variant(js, &posix_api, PATH_STYLE_POSIX);
868 ant_value_t win32 = path_make_variant(js, &win32_api, PATH_STYLE_WIN32);
869
870 js_set(js, lib, "posix", posix);
871 js_set(js, lib, "win32", win32);
872 js_set_sym(js, lib, get_toStringTag_sym(), js_mkstr(js, "path", 4));
873 js_set(js, lib, "default", lib);
874
875 return lib;
876}
877
878ant_value_t path_posix_library(ant_t *js) {
879 path_api_t api = path_build_api_for_style(js, PATH_STYLE_POSIX);
880 ant_value_t lib = path_make_variant(js, &api, PATH_STYLE_POSIX);
881 js_set_sym(js, lib, get_toStringTag_sym(), js_mkstr(js, "path", 4));
882 return lib;
883}
884
885ant_value_t path_win32_library(ant_t *js) {
886 path_api_t api = path_build_api_for_style(js, PATH_STYLE_WIN32);
887 ant_value_t lib = path_make_variant(js, &api, PATH_STYLE_WIN32);
888 js_set_sym(js, lib, get_toStringTag_sym(), js_mkstr(js, "path", 4));
889 return lib;
890}