xbintsc 0.2.4 → 0.2.6

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 (124) hide show
  1. package/dist/src/ast/declarations.d.ts +20 -1
  2. package/dist/src/ast/expressions.d.ts +6 -2
  3. package/dist/src/ast/kinds.d.ts +6 -1
  4. package/dist/src/ast/kinds.js +5 -0
  5. package/dist/src/ast/kinds.js.map +1 -1
  6. package/dist/src/ast/statements.d.ts +2 -1
  7. package/dist/src/binder/binder.js +73 -9
  8. package/dist/src/binder/binder.js.map +1 -1
  9. package/dist/src/cli/main.js +2 -4
  10. package/dist/src/cli/main.js.map +1 -1
  11. package/dist/src/codegen/generator/calls.d.ts +1 -1
  12. package/dist/src/codegen/generator/calls.js +57 -14
  13. package/dist/src/codegen/generator/calls.js.map +1 -1
  14. package/dist/src/codegen/generator/context.d.ts +1 -1
  15. package/dist/src/codegen/generator/expressions.js +39 -3
  16. package/dist/src/codegen/generator/expressions.js.map +1 -1
  17. package/dist/src/codegen/generator/module.js +73 -7
  18. package/dist/src/codegen/generator/module.js.map +1 -1
  19. package/dist/src/codegen/generator/state.js +16 -0
  20. package/dist/src/codegen/generator/state.js.map +1 -1
  21. package/dist/src/codegen/generator/statements.d.ts +4 -1
  22. package/dist/src/codegen/generator/statements.js +141 -10
  23. package/dist/src/codegen/generator/statements.js.map +1 -1
  24. package/dist/src/codegen/generator/tables.d.ts +3 -0
  25. package/dist/src/codegen/generator/tables.js +13 -0
  26. package/dist/src/codegen/generator/tables.js.map +1 -1
  27. package/dist/src/codegen/values.d.ts +18 -11
  28. package/dist/src/codegen/values.js +92 -15
  29. package/dist/src/codegen/values.js.map +1 -1
  30. package/dist/src/diagnostics/source.d.ts +1 -1
  31. package/dist/src/diagnostics/source.js +12 -7
  32. package/dist/src/diagnostics/source.js.map +1 -1
  33. package/dist/src/driver/compiler.d.ts +5 -0
  34. package/dist/src/driver/compiler.js +42 -2
  35. package/dist/src/driver/compiler.js.map +1 -1
  36. package/dist/src/driver/modules.js +29 -4
  37. package/dist/src/driver/modules.js.map +1 -1
  38. package/dist/src/extensions/node/child_process/index.d.ts +7 -0
  39. package/dist/src/extensions/node/child_process/index.js +19 -0
  40. package/dist/src/extensions/node/child_process/index.js.map +1 -0
  41. package/dist/src/extensions/node/crypto/index.d.ts +6 -0
  42. package/dist/src/extensions/node/crypto/index.js +18 -0
  43. package/dist/src/extensions/node/crypto/index.js.map +1 -0
  44. package/dist/src/extensions/node/index.js +6 -0
  45. package/dist/src/extensions/node/index.js.map +1 -1
  46. package/dist/src/extensions/node/module.d.ts +9 -1
  47. package/dist/src/extensions/node/module.js +14 -1
  48. package/dist/src/extensions/node/module.js.map +1 -1
  49. package/dist/src/extensions/node/url/index.d.ts +7 -0
  50. package/dist/src/extensions/node/url/index.js +21 -0
  51. package/dist/src/extensions/node/url/index.js.map +1 -0
  52. package/dist/src/lexer/scanner.js +4 -5
  53. package/dist/src/lexer/scanner.js.map +1 -1
  54. package/dist/src/lexer/token.d.ts +10 -0
  55. package/dist/src/lexer/token.js +69 -0
  56. package/dist/src/lexer/token.js.map +1 -1
  57. package/dist/src/parser/context.js +4 -16
  58. package/dist/src/parser/context.js.map +1 -1
  59. package/dist/src/parser/declarations.d.ts +2 -0
  60. package/dist/src/parser/declarations.js +146 -33
  61. package/dist/src/parser/declarations.js.map +1 -1
  62. package/dist/src/parser/expressions.js +20 -3
  63. package/dist/src/parser/expressions.js.map +1 -1
  64. package/dist/src/parser/helpers.js +2 -0
  65. package/dist/src/parser/helpers.js.map +1 -1
  66. package/dist/src/parser/modules.js +7 -2
  67. package/dist/src/parser/modules.js.map +1 -1
  68. package/dist/src/parser/statements.js +28 -3
  69. package/dist/src/parser/statements.js.map +1 -1
  70. package/dist/src/parser/types.js +5 -0
  71. package/dist/src/parser/types.js.map +1 -1
  72. package/dist/tests/codegen/llvm.test.js +16 -0
  73. package/dist/tests/codegen/llvm.test.js.map +1 -1
  74. package/dist/tests/driver/compiler.test.js +13 -1
  75. package/dist/tests/driver/compiler.test.js.map +1 -1
  76. package/dist/tests/e2e/compile.test.js +57 -0
  77. package/dist/tests/e2e/compile.test.js.map +1 -1
  78. package/dist/tests/parser/parser.test.js +53 -0
  79. package/dist/tests/parser/parser.test.js.map +1 -1
  80. package/package.json +1 -1
  81. package/runtime/ext_node/child_process/child_process.c +213 -0
  82. package/runtime/ext_node/crypto/crypto.c +177 -0
  83. package/runtime/ext_node/process/process.c +37 -3
  84. package/runtime/ext_node/url/url.c +85 -0
  85. package/runtime/rt.h +20 -0
  86. package/runtime/rt_internal.h +11 -0
  87. package/runtime/xt_builtins.c +23 -0
  88. package/runtime/xt_containers.c +136 -10
  89. package/runtime/xt_io.c +53 -0
  90. package/runtime/xt_regex.h +16 -6
  91. package/runtime/xt_stdlib.c +11 -2
  92. package/runtime/xt_stdlib2.c +250 -13
  93. package/runtime/xt_values.c +2 -1
  94. package/src/ast/declarations.ts +26 -1
  95. package/src/ast/expressions.ts +7 -2
  96. package/src/ast/kinds.ts +5 -0
  97. package/src/ast/statements.ts +2 -1
  98. package/src/binder/binder.ts +75 -9
  99. package/src/cli/main.ts +2 -3
  100. package/src/codegen/generator/calls.ts +59 -16
  101. package/src/codegen/generator/context.ts +1 -1
  102. package/src/codegen/generator/expressions.ts +39 -3
  103. package/src/codegen/generator/module.ts +79 -7
  104. package/src/codegen/generator/state.ts +16 -0
  105. package/src/codegen/generator/statements.ts +139 -11
  106. package/src/codegen/generator/tables.ts +14 -0
  107. package/src/codegen/values.ts +85 -16
  108. package/src/diagnostics/source.ts +12 -7
  109. package/src/driver/compiler.ts +44 -2
  110. package/src/driver/modules.ts +28 -3
  111. package/src/extensions/node/child_process/index.ts +24 -0
  112. package/src/extensions/node/crypto/index.ts +23 -0
  113. package/src/extensions/node/index.ts +6 -0
  114. package/src/extensions/node/module.ts +13 -1
  115. package/src/extensions/node/url/index.ts +26 -0
  116. package/src/lexer/scanner.ts +4 -4
  117. package/src/lexer/token.ts +73 -0
  118. package/src/parser/context.ts +4 -15
  119. package/src/parser/declarations.ts +140 -29
  120. package/src/parser/expressions.ts +20 -4
  121. package/src/parser/helpers.ts +2 -0
  122. package/src/parser/modules.ts +7 -2
  123. package/src/parser/statements.ts +27 -3
  124. package/src/parser/types.ts +5 -0
@@ -100,6 +100,29 @@ xt_value xt_boolean_ctor(int32_t argc, xt_value *argv) {
100
100
  return xt_bool(xt_truthy(argv[0]));
101
101
  }
102
102
 
103
+ /*
104
+ * Builtin function values. Global constructors like `Boolean` are usually
105
+ * called directly, but JavaScript also allows passing them around
106
+ * (`arr.filter(Boolean)`). These trampolines adapt the uniform `(argc, argv)`
107
+ * builtin ABI to the closure ABI so the codegen can wrap them with
108
+ * `xt_closure_new`.
109
+ */
110
+ #define XT_BUILTIN_TRAMPOLINE(name, target) \
111
+ xt_value name(xt_value thisValue, xt_value env, int32_t argc, \
112
+ xt_value *argv) { \
113
+ (void)thisValue; \
114
+ (void)env; \
115
+ return target(argc, argv); \
116
+ }
117
+
118
+ XT_BUILTIN_TRAMPOLINE(xt_builtin_value_boolean, xt_boolean_ctor)
119
+ XT_BUILTIN_TRAMPOLINE(xt_builtin_value_number, xt_number_ctor)
120
+ XT_BUILTIN_TRAMPOLINE(xt_builtin_value_string, xt_string_ctor)
121
+ XT_BUILTIN_TRAMPOLINE(xt_builtin_value_parseInt, xt_parse_int)
122
+ XT_BUILTIN_TRAMPOLINE(xt_builtin_value_parseFloat, xt_parse_float)
123
+ XT_BUILTIN_TRAMPOLINE(xt_builtin_value_isNaN, xt_is_nan)
124
+ XT_BUILTIN_TRAMPOLINE(xt_builtin_value_isFinite, xt_is_finite)
125
+
103
126
  xt_value xt_in(xt_value key, xt_value value) {
104
127
  if (XT_IS_OBJECT(value)) return xt_object_has(value, key);
105
128
  if (XT_IS_ARRAY(value)) {
@@ -63,16 +63,40 @@ xt_property *xt_object_find_property(xt_object *obj, xt_string *key) {
63
63
  return obj ? xt_object_find(obj, key) : NULL;
64
64
  }
65
65
 
66
+ static int xt_key_is_prototype(xt_value key) {
67
+ xt_string *k = xt_as_string(xt_to_string(key));
68
+ return k && k->length == 9 && memcmp(k->data, "prototype", 9) == 0;
69
+ }
70
+
66
71
  xt_value xt_object_get(xt_value value, xt_value key) {
72
+ /* A function's `.prototype` lives in a dedicated field (set by
73
+ `xt_function_set_prototype`), not in its property bag. Expose it as a
74
+ normal property so `Class.prototype` returns the object instances use. */
75
+ if (XT_IS_FUNCTION(value) && xt_key_is_prototype(key)) {
76
+ return ((xt_function *)XT_GET_PTR(value))->prototype;
77
+ }
67
78
  xt_object *obj = xt_as_object(value);
68
79
  if (!obj) return XT_UNDEFINED;
80
+ /* Only plain objects (and function property bags) use the `xt_object`
81
+ layout. Containers such as Map/Set/Date/RegExp/Promise/Array must not be
82
+ interpreted as property tables, or property lookup reads past the end of
83
+ their real representation. */
84
+ if (obj->header.kind != XT_OBJECT_KIND_OBJECT) return XT_UNDEFINED;
69
85
  xt_value keyString = xt_to_string(key);
70
86
  xt_string *k = xt_as_string(keyString);
71
87
  /* Walk the prototype chain like JavaScript property lookup. */
72
88
  xt_object *cur = obj;
73
89
  while (cur) {
74
90
  xt_property *prop = xt_object_find(cur, k);
75
- if (prop) return prop->value;
91
+ if (prop) {
92
+ /* Invoke accessors with the original receiver as `this`, so getters
93
+ inherited from a prototype still see the instance. */
94
+ if (prop->getter != XT_UNDEFINED && XT_IS_FUNCTION(prop->getter)) {
95
+ return xt_call_with_this(prop->getter, value, 0, NULL);
96
+ }
97
+ if (prop->getter != XT_UNDEFINED) return XT_UNDEFINED;
98
+ return prop->value;
99
+ }
76
100
  if (!XT_IS_OBJECT(cur->prototype)) break;
77
101
  cur = (xt_object *)XT_GET_PTR(cur->prototype);
78
102
  }
@@ -80,22 +104,82 @@ xt_value xt_object_get(xt_value value, xt_value key) {
80
104
  }
81
105
 
82
106
  xt_value xt_object_set(xt_value value, xt_value key, xt_value newValue) {
107
+ if (XT_IS_FUNCTION(value) && xt_key_is_prototype(key)) {
108
+ return xt_function_set_prototype(value, newValue);
109
+ }
83
110
  xt_object *obj = xt_as_object(value);
84
111
  if (!obj) return newValue;
112
+ if (obj->header.kind != XT_OBJECT_KIND_OBJECT) return newValue;
85
113
  if (obj->frozen) return newValue;
86
114
  xt_value keyString = xt_to_string(key);
87
- xt_property *prop = xt_object_find(obj, xt_as_string(keyString));
115
+ xt_string *k = xt_as_string(keyString);
116
+ xt_property *prop = xt_object_find(obj, k);
88
117
  if (prop) {
118
+ if (prop->setter != XT_UNDEFINED) {
119
+ if (XT_IS_FUNCTION(prop->setter)) {
120
+ xt_value argument = newValue;
121
+ xt_call_with_this(prop->setter, value, 1, &argument);
122
+ }
123
+ return newValue;
124
+ }
125
+ if (prop->getter != XT_UNDEFINED) return newValue; /* accessor without setter */
89
126
  prop->value = newValue;
90
127
  return newValue;
91
128
  }
129
+ /* No own property: an inherited accessor still intercepts the assignment
130
+ (JavaScript assigns through prototype setters instead of shadowing). */
131
+ xt_object *ancestor = XT_IS_OBJECT(obj->prototype) ? (xt_object *)XT_GET_PTR(obj->prototype) : NULL;
132
+ while (ancestor) {
133
+ xt_property *inherited = xt_object_find(ancestor, k);
134
+ if (inherited) {
135
+ if (inherited->setter != XT_UNDEFINED) {
136
+ if (XT_IS_FUNCTION(inherited->setter)) {
137
+ xt_value argument = newValue;
138
+ xt_call_with_this(inherited->setter, value, 1, &argument);
139
+ }
140
+ return newValue;
141
+ }
142
+ if (inherited->getter != XT_UNDEFINED) return newValue; /* getter-only accessor */
143
+ break; /* plain inherited data property: create an own shadowing property */
144
+ }
145
+ ancestor = XT_IS_OBJECT(ancestor->prototype) ? (xt_object *)XT_GET_PTR(ancestor->prototype) : NULL;
146
+ }
92
147
  xt_object_reserve(obj, obj->count + 1);
93
- obj->properties[obj->count].key = xt_as_string(keyString);
148
+ obj->properties[obj->count].key = k;
94
149
  obj->properties[obj->count].value = newValue;
150
+ obj->properties[obj->count].getter = XT_UNDEFINED;
151
+ obj->properties[obj->count].setter = XT_UNDEFINED;
95
152
  obj->count++;
96
153
  return newValue;
97
154
  }
98
155
 
156
+ static xt_property *xt_object_ensure_property(xt_object *obj, xt_value keyString) {
157
+ xt_property *prop = xt_object_find(obj, xt_as_string(keyString));
158
+ if (prop) return prop;
159
+ xt_object_reserve(obj, obj->count + 1);
160
+ prop = &obj->properties[obj->count];
161
+ prop->key = xt_as_string(keyString);
162
+ prop->value = XT_UNDEFINED;
163
+ prop->getter = XT_UNDEFINED;
164
+ prop->setter = XT_UNDEFINED;
165
+ obj->count++;
166
+ return prop;
167
+ }
168
+
169
+ xt_value xt_object_define_getter(xt_value value, xt_value key, xt_value getter) {
170
+ xt_object *obj = xt_as_object(value);
171
+ if (!obj) return value;
172
+ xt_object_ensure_property(obj, xt_to_string(key))->getter = getter;
173
+ return value;
174
+ }
175
+
176
+ xt_value xt_object_define_setter(xt_value value, xt_value key, xt_value setter) {
177
+ xt_object *obj = xt_as_object(value);
178
+ if (!obj) return value;
179
+ xt_object_ensure_property(obj, xt_to_string(key))->setter = setter;
180
+ return value;
181
+ }
182
+
99
183
  xt_value xt_object_get_prototype(xt_value value) {
100
184
  xt_object *obj = xt_as_object(value);
101
185
  if (obj) return obj->prototype;
@@ -248,21 +332,47 @@ xt_value xt_array_length(xt_value value) {
248
332
  }
249
333
 
250
334
  xt_value xt_array_spread(xt_value target, xt_value source) {
251
- if (!XT_IS_ARRAY(target) || !XT_IS_ARRAY(source)) return target;
252
- xt_array *dst = (xt_array *)XT_GET_PTR(target);
253
- xt_array *src = (xt_array *)XT_GET_PTR(source);
254
- xt_array_reserve(dst, dst->length + src->length);
255
- for (uint32_t i = 0; i < src->length; i++) dst->items[dst->length++] = src->items[i];
335
+ if (!XT_IS_ARRAY(target)) return target;
336
+ if (XT_IS_ARRAY(source)) {
337
+ xt_array *dst = (xt_array *)XT_GET_PTR(target);
338
+ xt_array *src = (xt_array *)XT_GET_PTR(source);
339
+ xt_array_reserve(dst, dst->length + src->length);
340
+ for (uint32_t i = 0; i < src->length; i++) dst->items[dst->length++] = src->items[i];
341
+ return target;
342
+ }
343
+ /* Spreading a string/Map/Set iterates exactly like `for...of`. */
344
+ int32_t n = xt_to_int32(xt_iter_length(source));
345
+ for (int32_t i = 0; i < n; i++) {
346
+ xt_array_push(target, xt_iter_value(source, xt_number((double)i)));
347
+ }
256
348
  return target;
257
349
  }
258
350
 
351
+ int32_t xt_array_size(xt_value value) {
352
+ if (!XT_IS_ARRAY(value)) return 0;
353
+ return (int32_t)((xt_array *)XT_GET_PTR(value))->length;
354
+ }
355
+
356
+ xt_value *xt_array_items(xt_value value) {
357
+ if (!XT_IS_ARRAY(value)) return NULL;
358
+ return ((xt_array *)XT_GET_PTR(value))->items;
359
+ }
360
+
259
361
 
260
362
  /* ------------------------------------------------------------------------- */
261
363
  /* Generic member access */
262
364
  /* ------------------------------------------------------------------------- */
263
365
 
264
366
  xt_value xt_get(xt_value target, xt_value key) {
265
- if (XT_IS_ARRAY(target)) return xt_array_get(target, key);
367
+ if (XT_IS_ARRAY(target)) {
368
+ /* `arr.length` must read the array's length, never an element. */
369
+ if (XT_IS_STRING(key)) {
370
+ xt_string *k = xt_as_string(key);
371
+ if (k->length == 6 && memcmp(k->data, "length", 6) == 0)
372
+ return xt_number((double)((xt_array *)XT_GET_PTR(target))->length);
373
+ }
374
+ return xt_array_get(target, key);
375
+ }
266
376
  if (XT_IS_FUNCTION(target)) return xt_object_get(target, key);
267
377
  if (XT_IS_OBJECT(target)) {
268
378
  /* Map/Set expose `.size`; promises, dates and regexps have their own
@@ -277,6 +387,7 @@ xt_value xt_get(xt_value target, xt_value key) {
277
387
  }
278
388
  }
279
389
  }
390
+ if (obj->header.kind == XT_OBJECT_KIND_REGEXP) return xt_regexp_get_property(target, key);
280
391
  return xt_object_get(target, key);
281
392
  }
282
393
  if (XT_IS_STRING(target)) {
@@ -294,7 +405,22 @@ xt_value xt_get(xt_value target, xt_value key) {
294
405
  }
295
406
 
296
407
  xt_value xt_set(xt_value target, xt_value key, xt_value value) {
297
- if (XT_IS_ARRAY(target)) return xt_array_set(target, key, value);
408
+ if (XT_IS_ARRAY(target)) {
409
+ /* Assigning `arr.length` truncates or extends the array. */
410
+ if (XT_IS_STRING(key)) {
411
+ xt_string *k = xt_as_string(key);
412
+ if (k->length == 6 && memcmp(k->data, "length", 6) == 0) {
413
+ xt_array *array = (xt_array *)XT_GET_PTR(target);
414
+ int32_t n = xt_to_int32(value);
415
+ if (n < 0) n = 0;
416
+ xt_array_reserve(array, (uint32_t)n);
417
+ for (uint32_t j = array->length; j < (uint32_t)n; j++) array->items[j] = XT_UNDEFINED;
418
+ array->length = (uint32_t)n;
419
+ return value;
420
+ }
421
+ }
422
+ return xt_array_set(target, key, value);
423
+ }
298
424
  if (XT_IS_FUNCTION(target)) return xt_object_set(target, key, value);
299
425
  if (XT_IS_OBJECT(target)) return xt_object_set(target, key, value);
300
426
  return value;
package/runtime/xt_io.c CHANGED
@@ -69,6 +69,59 @@ void xt_set_program_args(int32_t argc, char **argv) {
69
69
  xt_program_argv = argv;
70
70
  }
71
71
 
72
+ /*
73
+ * `import.meta` support. The compiler resolves module URLs at build time, but
74
+ * a self-hosted binary only knows where *it* lives, so `import.meta.url` is the
75
+ * executable's own `file://` URL (and `dirname`/`filename` its path parts).
76
+ * This lets the compiler locate an adjacent `runtime/` directory at run time.
77
+ */
78
+ static xt_value xt_file_url(const char *path) {
79
+ char url[4300];
80
+ size_t j = 0;
81
+ const char *prefix = "file://";
82
+ for (; *prefix; prefix++) url[j++] = *prefix;
83
+ #if defined(_WIN32)
84
+ url[j++] = '/';
85
+ #endif
86
+ for (const char *p = path; *p && j < sizeof(url) - 1; p++) url[j++] = (*p == '\\') ? '/' : *p;
87
+ url[j] = 0;
88
+ return xt_string_from_cstr(url);
89
+ }
90
+
91
+ xt_value xt_import_meta(xt_value name) {
92
+ const char *prop = xt_string_data(name);
93
+ const char *raw = (xt_program_argv && xt_program_argv[0]) ? xt_program_argv[0] : "";
94
+ char resolved[4096];
95
+ resolved[0] = 0;
96
+ if (raw[0]) {
97
+ #if defined(_WIN32)
98
+ if (_fullpath(resolved, raw, sizeof(resolved)) == NULL) resolved[0] = 0;
99
+ #else
100
+ if (realpath(raw, resolved) == NULL) resolved[0] = 0;
101
+ #endif
102
+ }
103
+ const char *abs = resolved[0] ? resolved : raw;
104
+ if (prop && strcmp(prop, "dirname") == 0) {
105
+ char dir[4096];
106
+ size_t n = strlen(abs);
107
+ if (n >= sizeof(dir)) n = sizeof(dir) - 1;
108
+ memcpy(dir, abs, n);
109
+ dir[n] = 0;
110
+ char *slash = strrchr(dir, '/');
111
+ #if defined(_WIN32)
112
+ char *back = strrchr(dir, '\\');
113
+ if (back && (!slash || back > slash)) slash = back;
114
+ #endif
115
+ if (slash) {
116
+ if (slash == dir) slash[1] = 0;
117
+ else *slash = 0;
118
+ }
119
+ return xt_string_from_cstr(dir);
120
+ }
121
+ if (prop && strcmp(prop, "filename") == 0) return xt_string_from_cstr(abs);
122
+ return xt_file_url(abs);
123
+ }
124
+
72
125
  void *xt_try_enter(void) {
73
126
  xt_try_frame *frame = (xt_try_frame *)calloc(1, sizeof(xt_try_frame));
74
127
  if (!frame) abort();
@@ -567,9 +567,13 @@ static int xt_re_match_alt(xt_re_alt *alt, const xt_re_input *in, size_t pos,
567
567
  return 0;
568
568
  }
569
569
 
570
- static int xt_re_accept(void *ctx, size_t pos) {
571
- (void)ctx;
572
- (void)pos;
570
+ typedef struct {
571
+ size_t end;
572
+ } xt_re_match_result;
573
+
574
+ static int xt_re_record_match(void *ctx, size_t pos) {
575
+ xt_re_match_result *result = (xt_re_match_result *)ctx;
576
+ result->end = pos;
573
577
  return 1;
574
578
  }
575
579
 
@@ -597,15 +601,21 @@ static int regexec(const regex_t *preg, const char *string, size_t nmatch,
597
601
  regmatch_t pmatch[], int eflags) {
598
602
  xt_re_input in;
599
603
  size_t start;
600
- (void)nmatch;
601
- (void)pmatch;
602
604
  (void)eflags;
603
605
  if (!preg || !preg->root || !string) return 1;
604
606
  in.text = string;
605
607
  in.len = strlen(string);
606
608
  in.icase = preg->icase;
607
609
  for (start = 0; start <= in.len; start++) {
608
- if (xt_re_match_alt(preg->root, &in, start, xt_re_accept, NULL)) return 0;
610
+ xt_re_match_result result;
611
+ result.end = 0;
612
+ if (xt_re_match_alt(preg->root, &in, start, xt_re_record_match, &result)) {
613
+ if (nmatch > 0 && pmatch) {
614
+ pmatch[0].rm_so = (int)start;
615
+ pmatch[0].rm_eo = (int)result.end;
616
+ }
617
+ return 0;
618
+ }
609
619
  }
610
620
  return 1;
611
621
  }
@@ -327,6 +327,7 @@ static xt_value xt_string_trim(xt_value value) {
327
327
  }
328
328
 
329
329
  static xt_value xt_string_replace(xt_value value, xt_value searchValue, xt_value replacementValue) {
330
+ if (xt_is_regexp(searchValue)) return xt_regexp_replace(value, searchValue, replacementValue);
330
331
  xt_string *s = xt_as_string(value);
331
332
  if (!s) return value;
332
333
  xt_string *search = xt_as_string(xt_to_string(searchValue));
@@ -456,13 +457,21 @@ xt_value xt_call_method(xt_value target, xt_value name, int32_t argc, xt_value *
456
457
  if (XT_IS_FUNCTION(fn)) return xt_call_with_this(fn, target, argc, argv);
457
458
  xt_value result = xt_ext_object_method(target, method, argc, argv, &handled);
458
459
  if (handled) return result;
459
- xt_throw(xt_string_from_cstr("TypeError: target does not have a callable property of that name"));
460
+ {
461
+ char message[256];
462
+ snprintf(message, sizeof(message), "TypeError: object has no callable method '%s'", method);
463
+ xt_throw(xt_string_from_cstr(message));
464
+ }
460
465
  return XT_UNDEFINED;
461
466
  }
462
467
  if (XT_IS_FUNCTION(target)) {
463
468
  xt_value fn = xt_object_get(target, name);
464
469
  if (XT_IS_FUNCTION(fn)) return xt_call_with_this(fn, target, argc, argv);
465
- xt_throw(xt_string_from_cstr("TypeError: target does not have a callable property of that name"));
470
+ {
471
+ char message[256];
472
+ snprintf(message, sizeof(message), "TypeError: function has no callable method '%s'", method);
473
+ xt_throw(xt_string_from_cstr(message));
474
+ }
466
475
  return XT_UNDEFINED;
467
476
  }
468
477
  if (XT_IS_ARRAY(target)) {
@@ -294,6 +294,7 @@ static xt_value xt_string_at(xt_value value, xt_value indexValue) {
294
294
  }
295
295
 
296
296
  static xt_value xt_string_replace_all(xt_value value, xt_value searchValue, xt_value replacementValue) {
297
+ if (xt_is_regexp(searchValue)) return xt_regexp_replace(value, searchValue, replacementValue);
297
298
  xt_string *s = xt_as_string(value);
298
299
  if (!s) return value;
299
300
  xt_string *search = xt_as_string(xt_to_string(searchValue));
@@ -856,6 +857,35 @@ int32_t xt_set_size(xt_value value) {
856
857
  return (int32_t)((xt_set_object *)XT_GET_PTR(value))->count;
857
858
  }
858
859
 
860
+ /* -- for...of iteration --------------------------------------------------- */
861
+
862
+ /* `for...of` lowers to a length check plus an index lookup. Arrays and strings
863
+ index directly; Maps yield `[key, value]` pairs and Sets yield their values. */
864
+ xt_value xt_iter_length(xt_value value) {
865
+ if (xt_is_map(value)) return xt_number((double)xt_map_size(value));
866
+ if (xt_is_set(value)) return xt_number((double)xt_set_size(value));
867
+ return xt_array_length(value);
868
+ }
869
+
870
+ xt_value xt_iter_value(xt_value value, xt_value index) {
871
+ if (xt_is_map(value)) {
872
+ xt_map *map = (xt_map *)XT_GET_PTR(value);
873
+ int64_t i = (int64_t)xt_to_int32(index);
874
+ if (i < 0 || i >= (int64_t)map->count) return XT_UNDEFINED;
875
+ xt_value pair = xt_array_new(0, NULL);
876
+ xt_array_push(pair, map->keys[i]);
877
+ xt_array_push(pair, map->values[i]);
878
+ return pair;
879
+ }
880
+ if (xt_is_set(value)) {
881
+ xt_set_object *set = (xt_set_object *)XT_GET_PTR(value);
882
+ int64_t i = (int64_t)xt_to_int32(index);
883
+ if (i < 0 || i >= (int64_t)set->count) return XT_UNDEFINED;
884
+ return set->values[i];
885
+ }
886
+ return xt_get(value, index);
887
+ }
888
+
859
889
  static int32_t xt_set_index(xt_set_object *set, xt_value key) {
860
890
  for (uint32_t i = 0; i < set->count; i++) if (xt_value_equals(set->values[i], key)) return (int32_t)i;
861
891
  return -1;
@@ -1016,18 +1046,232 @@ xt_value xt_regexp_ctor(int32_t argc, xt_value *argv) {
1016
1046
  return XT_FROM_PTR(XT_TAG_OBJECT, regexp);
1017
1047
  }
1018
1048
 
1019
- static xt_value xt_regexp_test(xt_value target, xt_value input) {
1020
- xt_regexp *regexp = (xt_regexp *)XT_GET_PTR(target);
1049
+ xt_value xt_error_ctor(int32_t argc, xt_value *argv) {
1050
+ xt_value error = xt_object_new();
1051
+ xt_value name = xt_arg_at(argc, argv, 1);
1052
+ xt_value message = xt_arg_at(argc, argv, 0);
1053
+ xt_object_set(error, xt_string_from_cstr("name"),
1054
+ name == XT_UNDEFINED ? xt_string_from_cstr("Error") : name);
1055
+ xt_object_set(error, xt_string_from_cstr("message"),
1056
+ message == XT_UNDEFINED ? xt_string_from_cstr("") : message);
1057
+ return error;
1058
+ }
1059
+
1060
+ /* `super(message)` inside a subclass of the builtin `Error`: initialize the
1061
+ * already-allocated instance instead of creating a fresh object. */
1062
+ xt_value xt_error_init(xt_value thisValue, int32_t argc, xt_value *argv) {
1063
+ xt_value name = xt_arg_at(argc, argv, 1);
1064
+ xt_value message = xt_arg_at(argc, argv, 0);
1065
+ xt_object_set(thisValue, xt_string_from_cstr("name"),
1066
+ name == XT_UNDEFINED ? xt_string_from_cstr("Error") : name);
1067
+ xt_object_set(thisValue, xt_string_from_cstr("message"),
1068
+ message == XT_UNDEFINED ? xt_string_from_cstr("") : message);
1069
+ return thisValue;
1070
+ }
1071
+
1072
+ /* Compile `regexp` and search `input` starting at byte offset `start`.
1073
+ * Returns 1 and fills `matchStart`/`matchEnd` (absolute offsets into `input`)
1074
+ * when a match is found, otherwise 0. */
1075
+
1076
+ static int xt_regex_hex_digit(char c) {
1077
+ if (c >= '0' && c <= '9') return c - '0';
1078
+ if (c >= 'a' && c <= 'f') return c - 'a' + 10;
1079
+ if (c >= 'A' && c <= 'F') return c - 'A' + 10;
1080
+ return -1;
1081
+ }
1082
+
1083
+ /* JavaScript regexes use escapes POSIX ERE does not understand (`\r`, `\n`,
1084
+ * `\d`, `\w`, `\s`, `(?:...)`, ...). Rewrite the common ones into their POSIX
1085
+ * equivalents before handing the pattern to the engine. Returns a malloc'd,
1086
+ * NUL-terminated string the caller must free. */
1087
+ static char *xt_regexp_translate_js(const char *pattern) {
1088
+ size_t length = strlen(pattern);
1089
+ size_t capacity = length * 16 + 16;
1090
+ char *out = (char *)malloc(capacity);
1091
+ if (!out) abort();
1092
+ size_t written = 0;
1093
+ for (size_t i = 0; i < length; i++) {
1094
+ char c = pattern[i];
1095
+ const char *replacement = NULL;
1096
+ char literal = 0;
1097
+ int consume = 0;
1098
+ if (c == '\\') {
1099
+ char next = (i + 1 < length) ? pattern[i + 1] : '\0';
1100
+ switch (next) {
1101
+ case 'r': literal = '\r'; consume = 1; break;
1102
+ case 'n': literal = '\n'; consume = 1; break;
1103
+ case 't': literal = '\t'; consume = 1; break;
1104
+ case 'f': literal = '\f'; consume = 1; break;
1105
+ case 'v': literal = '\v'; consume = 1; break;
1106
+ case 'd': replacement = "[0-9]"; consume = 1; break;
1107
+ case 'D': replacement = "[^0-9]"; consume = 1; break;
1108
+ case 'w': replacement = "[A-Za-z0-9_]"; consume = 1; break;
1109
+ case 'W': replacement = "[^A-Za-z0-9_]"; consume = 1; break;
1110
+ case 's': replacement = "[ \t\n\r\f\v]"; consume = 1; break;
1111
+ case 'S': replacement = "[^ \t\n\r\f\v]"; consume = 1; break;
1112
+ case 'x': {
1113
+ int hi = (i + 2 < length) ? xt_regex_hex_digit(pattern[i + 2]) : -1;
1114
+ int lo = (i + 3 < length) ? xt_regex_hex_digit(pattern[i + 3]) : -1;
1115
+ if (hi >= 0 && lo >= 0) { literal = (char)(hi * 16 + lo); consume = 3; }
1116
+ break;
1117
+ }
1118
+ default: break;
1119
+ }
1120
+ } else if (c == '(' && i + 2 < length && pattern[i + 1] == '?' && pattern[i + 2] == ':') {
1121
+ /* POSIX ERE has no non-capturing groups; a capturing group matches the
1122
+ * same text, only the capture bookkeeping differs. */
1123
+ literal = '(';
1124
+ consume = 2;
1125
+ }
1126
+ if (replacement) {
1127
+ size_t addition = strlen(replacement);
1128
+ if (written + addition + 1 > capacity) { capacity = (written + addition) * 2 + 16; out = (char *)realloc(out, capacity); if (!out) abort(); }
1129
+ memcpy(out + written, replacement, addition);
1130
+ written += addition;
1131
+ } else if (consume || literal) {
1132
+ if (written + 2 > capacity) { capacity = capacity * 2 + 16; out = (char *)realloc(out, capacity); if (!out) abort(); }
1133
+ out[written++] = literal;
1134
+ } else {
1135
+ if (written + 2 > capacity) { capacity = capacity * 2 + 16; out = (char *)realloc(out, capacity); if (!out) abort(); }
1136
+ out[written++] = c;
1137
+ }
1138
+ i += (size_t)consume;
1139
+ }
1140
+ out[written] = '\0';
1141
+ return out;
1142
+ }
1143
+
1144
+ int xt_regexp_find(xt_value regexpValue, xt_value input, int32_t start, int32_t *matchStart, int32_t *matchEnd) {
1145
+ xt_regexp *regexp = (xt_regexp *)XT_GET_PTR(regexpValue);
1021
1146
  xt_string *pattern = xt_as_string(regexp->source);
1022
1147
  xt_string *text = xt_as_string(xt_to_string(input));
1148
+ if (!pattern || !text) return 0;
1149
+ if (start < 0) start = 0;
1150
+ if ((uint32_t)start > text->length) return 0;
1023
1151
  int flags = REG_EXTENDED;
1024
1152
  xt_string *f = xt_as_string(regexp->flags);
1025
1153
  if (f) for (uint32_t i = 0; i < f->length; i++) if (f->data[i] == 'i') flags |= REG_ICASE;
1154
+ char *translated = xt_regexp_translate_js(pattern->data);
1155
+ if (!translated) return 0;
1026
1156
  regex_t compiled;
1027
- if (!pattern || !text || regcomp(&compiled, pattern->data, flags) != 0) return XT_FALSE;
1028
- int ok = regexec(&compiled, text->data, 0, NULL, 0) == 0;
1157
+ if (regcomp(&compiled, translated, flags) != 0) {
1158
+ free(translated);
1159
+ return 0;
1160
+ }
1161
+ free(translated);
1162
+ regmatch_t match;
1163
+ match.rm_so = 0;
1164
+ match.rm_eo = 0;
1165
+ int ok = regexec(&compiled, text->data + start, 1, &match, 0) == 0;
1029
1166
  regfree(&compiled);
1030
- return xt_bool(ok);
1167
+ if (!ok) return 0;
1168
+ if (matchStart) *matchStart = start + (int32_t)match.rm_so;
1169
+ if (matchEnd) *matchEnd = start + (int32_t)match.rm_eo;
1170
+ return 1;
1171
+ }
1172
+
1173
+ static int xt_regexp_is_global(xt_value regexpValue) {
1174
+ xt_regexp *regexp = (xt_regexp *)XT_GET_PTR(regexpValue);
1175
+ xt_string *f = xt_as_string(regexp->flags);
1176
+ if (!f) return 0;
1177
+ for (uint32_t i = 0; i < f->length; i++) if (f->data[i] == 'g') return 1;
1178
+ return 0;
1179
+ }
1180
+
1181
+ static xt_value xt_regexp_test(xt_value target, xt_value input) {
1182
+ return xt_bool(xt_regexp_find(target, input, 0, NULL, NULL));
1183
+ }
1184
+
1185
+ /* JavaScript-style match object: `[0]` is the matched text, `index` is the
1186
+ * match offset and `input` is the searched string. */
1187
+ xt_value xt_regexp_exec(xt_value target, xt_value input) {
1188
+ int32_t start = 0;
1189
+ int32_t end = 0;
1190
+ if (!xt_regexp_find(target, input, 0, &start, &end)) return XT_NULL;
1191
+ xt_value textValue = xt_to_string(input);
1192
+ xt_string *text = xt_as_string(textValue);
1193
+ xt_value out = xt_object_new();
1194
+ xt_object_set(out, xt_string_from_cstr("0"), xt_string_new(text->data + start, (int64_t)(end - start)));
1195
+ xt_object_set(out, xt_string_from_cstr("index"), xt_number((double)start));
1196
+ xt_object_set(out, xt_string_from_cstr("input"), textValue);
1197
+ xt_object_set(out, xt_string_from_cstr("length"), xt_number(1));
1198
+ return out;
1199
+ }
1200
+
1201
+ /* `String.prototype.replace`/`replaceAll` with a RegExp search value. */
1202
+ xt_value xt_regexp_replace(xt_value value, xt_value regexpValue, xt_value replacementValue) {
1203
+ xt_string *text = xt_as_string(xt_to_string(value));
1204
+ if (!text) return value;
1205
+ xt_string *replacement = xt_as_string(xt_to_string(replacementValue));
1206
+ if (!replacement) return value;
1207
+ int global = xt_regexp_is_global(regexpValue);
1208
+ size_t capacity = text->length + replacement->length + 16;
1209
+ char *out = (char *)malloc(capacity);
1210
+ if (!out) abort();
1211
+ size_t length = 0;
1212
+ int32_t cursor = 0;
1213
+ int replaced = 0;
1214
+ for (;;) {
1215
+ int32_t start = 0;
1216
+ int32_t end = 0;
1217
+ if (!xt_regexp_find(regexpValue, value, cursor, &start, &end)) break;
1218
+ size_t prefix = (size_t)(start - cursor);
1219
+ size_t needed = length + prefix + replacement->length + 2;
1220
+ if (needed > capacity) {
1221
+ capacity = needed * 2;
1222
+ out = (char *)realloc(out, capacity);
1223
+ if (!out) abort();
1224
+ }
1225
+ memcpy(out + length, text->data + cursor, prefix);
1226
+ length += prefix;
1227
+ memcpy(out + length, replacement->data, replacement->length);
1228
+ length += replacement->length;
1229
+ replaced = 1;
1230
+ if (end > start) {
1231
+ cursor = end;
1232
+ } else {
1233
+ /* Zero-width match: copy one byte so the scan always progresses. */
1234
+ if (cursor >= (int32_t)text->length) break;
1235
+ out[length++] = text->data[cursor++];
1236
+ }
1237
+ if (!global) break;
1238
+ }
1239
+ if (!replaced) {
1240
+ free(out);
1241
+ return value;
1242
+ }
1243
+ size_t tail = text->length - (size_t)cursor;
1244
+ if (length + tail + 1 > capacity) {
1245
+ capacity = length + tail + 1;
1246
+ out = (char *)realloc(out, capacity);
1247
+ if (!out) abort();
1248
+ }
1249
+ memcpy(out + length, text->data + cursor, tail);
1250
+ length += tail;
1251
+ xt_value result = xt_string_new(out, (int64_t)length);
1252
+ free(out);
1253
+ return result;
1254
+ }
1255
+
1256
+ /* Property access (`re.source`, `re.flags`, `re.global`, ...). */
1257
+ xt_value xt_regexp_get_property(xt_value target, xt_value key) {
1258
+ xt_regexp *regexp = (xt_regexp *)XT_GET_PTR(target);
1259
+ if (!XT_IS_STRING(key)) return XT_UNDEFINED;
1260
+ xt_string *k = xt_as_string(key);
1261
+ xt_string *f = xt_as_string(regexp->flags);
1262
+ int flag = 0;
1263
+ if (k->length == 6 && memcmp(k->data, "source", 6) == 0) return regexp->source;
1264
+ if (k->length == 5 && memcmp(k->data, "flags", 5) == 0) return regexp->flags;
1265
+ if (k->length == 9 && memcmp(k->data, "lastIndex", 9) == 0) return xt_number(0);
1266
+ if (k->length == 6 && memcmp(k->data, "global", 6) == 0) flag = 'g';
1267
+ else if (k->length == 10 && memcmp(k->data, "ignoreCase", 10) == 0) flag = 'i';
1268
+ else if (k->length == 9 && memcmp(k->data, "multiline", 9) == 0) flag = 'm';
1269
+ else if (k->length == 6 && memcmp(k->data, "sticky", 6) == 0) flag = 'y';
1270
+ if (flag) {
1271
+ if (f) for (uint32_t i = 0; i < f->length; i++) if (f->data[i] == flag) return XT_TRUE;
1272
+ return XT_FALSE;
1273
+ }
1274
+ return XT_UNDEFINED;
1031
1275
  }
1032
1276
 
1033
1277
  xt_value xt_ext_container_method(xt_value target, const char *method, int32_t argc, xt_value *argv, int *handled) {
@@ -1039,14 +1283,7 @@ xt_value xt_ext_container_method(xt_value target, const char *method, int32_t ar
1039
1283
  xt_regexp *regexp = (xt_regexp *)obj;
1040
1284
  *handled = 1;
1041
1285
  if (strcmp(method, "test") == 0) return xt_regexp_test(target, xt_arg_at(argc, argv, 0));
1042
- if (strcmp(method, "exec") == 0) {
1043
- if (xt_truthy(xt_regexp_test(target, xt_arg_at(argc, argv, 0)))) {
1044
- xt_value out = xt_array_new(0, NULL);
1045
- xt_array_push(out, xt_arg_at(argc, argv, 0));
1046
- return out;
1047
- }
1048
- return XT_NULL;
1049
- }
1286
+ if (strcmp(method, "exec") == 0) return xt_regexp_exec(target, xt_arg_at(argc, argv, 0));
1050
1287
  if (strcmp(method, "toString") == 0) {
1051
1288
  xt_value out = xt_string_from_cstr("/");
1052
1289
  out = xt_add(out, regexp->source);
@@ -117,7 +117,8 @@ int xt_truthy(xt_value v) {
117
117
  if (v == XT_UNDEFINED || v == XT_NULL) return 0;
118
118
  if (v == XT_FALSE) return 0;
119
119
  if (v == XT_TRUE) return 1;
120
- return 1; /* objects, arrays, functions and non-empty strings are truthy */
120
+ if (XT_IS_STRING(v)) return xt_as_string(v)->length != 0;
121
+ return 1; /* objects, arrays and functions are truthy */
121
122
  }
122
123
 
123
124
  double xt_to_number(xt_value v) {