xbintsc 0.2.5 → 0.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/dist/src/ast/expressions.d.ts +6 -2
  4. package/dist/src/ast/kinds.d.ts +1 -0
  5. package/dist/src/ast/kinds.js +1 -0
  6. package/dist/src/ast/kinds.js.map +1 -1
  7. package/dist/src/binder/binder.js +29 -0
  8. package/dist/src/binder/binder.js.map +1 -1
  9. package/dist/src/codegen/generator/calls.d.ts +1 -1
  10. package/dist/src/codegen/generator/calls.js +57 -14
  11. package/dist/src/codegen/generator/calls.js.map +1 -1
  12. package/dist/src/codegen/generator/context.d.ts +1 -1
  13. package/dist/src/codegen/generator/expressions.js +14 -3
  14. package/dist/src/codegen/generator/expressions.js.map +1 -1
  15. package/dist/src/codegen/generator/module.js +57 -2
  16. package/dist/src/codegen/generator/module.js.map +1 -1
  17. package/dist/src/codegen/generator/state.js +15 -0
  18. package/dist/src/codegen/generator/state.js.map +1 -1
  19. package/dist/src/codegen/generator/statements.js +25 -8
  20. package/dist/src/codegen/generator/statements.js.map +1 -1
  21. package/dist/src/codegen/generator/tables.d.ts +3 -0
  22. package/dist/src/codegen/generator/tables.js +12 -0
  23. package/dist/src/codegen/generator/tables.js.map +1 -1
  24. package/dist/src/codegen/values.d.ts +18 -11
  25. package/dist/src/codegen/values.js +92 -15
  26. package/dist/src/codegen/values.js.map +1 -1
  27. package/dist/src/diagnostics/source.d.ts +1 -1
  28. package/dist/src/diagnostics/source.js +12 -7
  29. package/dist/src/diagnostics/source.js.map +1 -1
  30. package/dist/src/driver/compiler.js +16 -2
  31. package/dist/src/driver/compiler.js.map +1 -1
  32. package/dist/src/extensions/node/child_process/index.d.ts +7 -0
  33. package/dist/src/extensions/node/child_process/index.js +19 -0
  34. package/dist/src/extensions/node/child_process/index.js.map +1 -0
  35. package/dist/src/extensions/node/crypto/index.d.ts +6 -0
  36. package/dist/src/extensions/node/crypto/index.js +18 -0
  37. package/dist/src/extensions/node/crypto/index.js.map +1 -0
  38. package/dist/src/extensions/node/index.js +6 -0
  39. package/dist/src/extensions/node/index.js.map +1 -1
  40. package/dist/src/extensions/node/module.d.ts +9 -1
  41. package/dist/src/extensions/node/module.js +14 -1
  42. package/dist/src/extensions/node/module.js.map +1 -1
  43. package/dist/src/extensions/node/url/index.d.ts +7 -0
  44. package/dist/src/extensions/node/url/index.js +21 -0
  45. package/dist/src/extensions/node/url/index.js.map +1 -0
  46. package/dist/src/lexer/scanner.js +4 -5
  47. package/dist/src/lexer/scanner.js.map +1 -1
  48. package/dist/src/parser/declarations.js +10 -9
  49. package/dist/src/parser/declarations.js.map +1 -1
  50. package/dist/src/parser/expressions.js +1 -1
  51. package/dist/src/parser/expressions.js.map +1 -1
  52. package/dist/tests/e2e/compile.test.js +36 -0
  53. package/dist/tests/e2e/compile.test.js.map +1 -1
  54. package/package.json +1 -1
  55. package/runtime/ext_node/child_process/child_process.c +213 -0
  56. package/runtime/ext_node/crypto/crypto.c +177 -0
  57. package/runtime/ext_node/process/process.c +37 -3
  58. package/runtime/ext_node/url/url.c +85 -0
  59. package/runtime/rt.h +20 -0
  60. package/runtime/rt_internal.h +11 -0
  61. package/runtime/xt_builtins.c +23 -0
  62. package/runtime/xt_containers.c +136 -10
  63. package/runtime/xt_io.c +53 -0
  64. package/runtime/xt_regex.h +16 -6
  65. package/runtime/xt_stdlib.c +11 -2
  66. package/runtime/xt_stdlib2.c +239 -13
  67. package/runtime/xt_values.c +2 -1
  68. package/src/ast/expressions.ts +7 -2
  69. package/src/ast/kinds.ts +1 -0
  70. package/src/binder/binder.ts +29 -0
  71. package/src/codegen/generator/calls.ts +59 -16
  72. package/src/codegen/generator/context.ts +1 -1
  73. package/src/codegen/generator/expressions.ts +14 -3
  74. package/src/codegen/generator/module.ts +60 -1
  75. package/src/codegen/generator/state.ts +15 -0
  76. package/src/codegen/generator/statements.ts +23 -8
  77. package/src/codegen/generator/tables.ts +13 -0
  78. package/src/codegen/values.ts +85 -16
  79. package/src/diagnostics/source.ts +12 -7
  80. package/src/driver/compiler.ts +15 -2
  81. package/src/extensions/node/child_process/index.ts +24 -0
  82. package/src/extensions/node/crypto/index.ts +23 -0
  83. package/src/extensions/node/index.ts +6 -0
  84. package/src/extensions/node/module.ts +13 -1
  85. package/src/extensions/node/url/index.ts +26 -0
  86. package/src/lexer/scanner.ts +4 -4
  87. package/src/parser/declarations.ts +10 -7
  88. package/src/parser/expressions.ts +1 -1
@@ -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;
@@ -1027,18 +1057,221 @@ xt_value xt_error_ctor(int32_t argc, xt_value *argv) {
1027
1057
  return error;
1028
1058
  }
1029
1059
 
1030
- static xt_value xt_regexp_test(xt_value target, xt_value input) {
1031
- xt_regexp *regexp = (xt_regexp *)XT_GET_PTR(target);
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);
1032
1146
  xt_string *pattern = xt_as_string(regexp->source);
1033
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;
1034
1151
  int flags = REG_EXTENDED;
1035
1152
  xt_string *f = xt_as_string(regexp->flags);
1036
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;
1037
1156
  regex_t compiled;
1038
- if (!pattern || !text || regcomp(&compiled, pattern->data, flags) != 0) return XT_FALSE;
1039
- 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;
1040
1166
  regfree(&compiled);
1041
- 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;
1042
1275
  }
1043
1276
 
1044
1277
  xt_value xt_ext_container_method(xt_value target, const char *method, int32_t argc, xt_value *argv, int *handled) {
@@ -1050,14 +1283,7 @@ xt_value xt_ext_container_method(xt_value target, const char *method, int32_t ar
1050
1283
  xt_regexp *regexp = (xt_regexp *)obj;
1051
1284
  *handled = 1;
1052
1285
  if (strcmp(method, "test") == 0) return xt_regexp_test(target, xt_arg_at(argc, argv, 0));
1053
- if (strcmp(method, "exec") == 0) {
1054
- if (xt_truthy(xt_regexp_test(target, xt_arg_at(argc, argv, 0)))) {
1055
- xt_value out = xt_array_new(0, NULL);
1056
- xt_array_push(out, xt_arg_at(argc, argv, 0));
1057
- return out;
1058
- }
1059
- return XT_NULL;
1060
- }
1286
+ if (strcmp(method, "exec") == 0) return xt_regexp_exec(target, xt_arg_at(argc, argv, 0));
1061
1287
  if (strcmp(method, "toString") == 0) {
1062
1288
  xt_value out = xt_string_from_cstr("/");
1063
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) {
@@ -40,7 +40,7 @@ export interface NumericLiteral extends Node {
40
40
  export interface BigIntLiteral extends Node {
41
41
  readonly kind: SyntaxKind.BigIntLiteral;
42
42
  readonly text: string;
43
- readonly value: bigint;
43
+ readonly value: number;
44
44
  }
45
45
 
46
46
  export interface StringLiteral extends Node {
@@ -127,7 +127,12 @@ export interface ObjectLiteralExpression extends Node {
127
127
  readonly properties: ObjectLiteralElementLike[];
128
128
  }
129
129
 
130
- export type PropertyName = Identifier | StringLiteral | NumericLiteral | PrivateIdentifier;
130
+ export type PropertyName = Identifier | StringLiteral | NumericLiteral | PrivateIdentifier | ComputedPropertyName;
131
+
132
+ export interface ComputedPropertyName extends Node {
133
+ readonly kind: SyntaxKind.ComputedPropertyName;
134
+ readonly expression: Expression;
135
+ }
131
136
 
132
137
  export interface FunctionExpression extends Node {
133
138
  readonly kind: SyntaxKind.FunctionExpression;
package/src/ast/kinds.ts CHANGED
@@ -62,6 +62,7 @@ export const enum SyntaxKind {
62
62
  ArrayBindingPattern = 46,
63
63
  ObjectBindingPattern = 47,
64
64
  BindingElement = 48,
65
+ ComputedPropertyName = 49,
65
66
 
66
67
  // Statements
67
68
  ExpressionStatement = 60,
@@ -454,6 +454,35 @@ class Binder {
454
454
  case SyntaxKind.Identifier:
455
455
  this.reference(node as Identifier, scope);
456
456
  return;
457
+ case SyntaxKind.PropertyAccessExpression:
458
+ // `obj.name` reads a property, not a variable named `name`.
459
+ this.bindNode((node as unknown as { expression: Expression }).expression, scope);
460
+ return;
461
+ case SyntaxKind.ElementAccessExpression: {
462
+ const access = node as unknown as { expression: Expression; argumentExpression: Expression };
463
+ this.bindNode(access.expression, scope);
464
+ this.bindNode(access.argumentExpression, scope);
465
+ return;
466
+ }
467
+ case SyntaxKind.PropertyAssignment: {
468
+ // `{ key: value }` / `{ key() {} }`: only the value is an expression,
469
+ // except a computed key (`{ [expr]: value }`) whose expression is real.
470
+ const property = node as unknown as { name: PropertyName; initializer: Expression };
471
+ if (property.name.kind === SyntaxKind.ComputedPropertyName) {
472
+ this.bindNode((property.name as unknown as { expression: Expression }).expression, scope);
473
+ }
474
+ this.bindNode(property.initializer, scope);
475
+ return;
476
+ }
477
+ case SyntaxKind.ShorthandPropertyAssignment:
478
+ this.reference((node as unknown as { name: Identifier }).name, scope);
479
+ return;
480
+ case SyntaxKind.LabeledStatement:
481
+ this.bindNode((node as unknown as { statement: Node }).statement, scope);
482
+ return;
483
+ case SyntaxKind.BreakStatement:
484
+ case SyntaxKind.ContinueStatement:
485
+ return;
457
486
  case SyntaxKind.ThisKeyword:
458
487
  this.current.usesThis = true;
459
488
  return;
@@ -14,6 +14,7 @@ import {
14
14
  type Identifier,
15
15
  type ObjectLiteralExpression,
16
16
  type PropertyAccessExpression,
17
+ type SpreadElement,
17
18
  } from "../../ast/nodes.js";
18
19
  import { SymbolKind, type FunctionInfo } from "../../binder/binder.js";
19
20
  import type { ModuleExport } from "../../extensions/registry.js";
@@ -35,7 +36,7 @@ export interface CallMethods {
35
36
  emitCall(this: Generator, node: CallExpression): string;
36
37
  tryEmitBuiltinCall(this: Generator, node: CallExpression, callee: PropertyAccessExpression): string | undefined;
37
38
  runtimeCall(this: Generator, name: string, args: readonly string[]): string;
38
- emitArguments(this: Generator, args: readonly Expression[]): { argc: number; ptr: string };
39
+ emitArguments(this: Generator, args: readonly Expression[]): { argc: string; ptr: string };
39
40
  emitDelete(this: Generator, node: DeleteExpression): string;
40
41
  emitPropertyAccess(this: Generator, node: PropertyAccessExpression): string;
41
42
  emitElementAccess(this: Generator, node: ElementAccessExpression): string;
@@ -262,17 +263,38 @@ export const callMethods: CallMethods = {
262
263
  return result;
263
264
  },
264
265
 
265
- emitArguments(args: readonly Expression[]): { argc: number; ptr: string } {
266
- if (args.length === 0) return { argc: 0, ptr: "null" };
267
- const ptr = `%args${this.current.allocas.length}`;
268
- this.current.allocas.push(`${ptr} = alloca i64, i32 ${args.length}`);
269
- for (let index = 0; index < args.length; index++) {
270
- const value = this.emitExpression(args[index]!);
271
- const slot = this.reg();
272
- this.emit(` ${slot} = getelementptr i64, i64* ${ptr}, i32 ${index}`);
273
- this.emit(` store i64 ${value}, i64* ${slot}`);
266
+ emitArguments(args: readonly Expression[]): { argc: string; ptr: string } {
267
+ if (args.length === 0) return { argc: "0", ptr: "null" };
268
+ const hasSpread = args.some((element) => element.kind === SyntaxKind.SpreadElement);
269
+ if (!hasSpread) {
270
+ const ptr = `%args${this.current.allocas.length}`;
271
+ this.current.allocas.push(`${ptr} = alloca i64, i32 ${args.length}`);
272
+ for (let index = 0; index < args.length; index++) {
273
+ const value = this.emitExpression(args[index]!);
274
+ const slot = this.reg();
275
+ this.emit(` ${slot} = getelementptr i64, i64* ${ptr}, i32 ${index}`);
276
+ this.emit(` store i64 ${value}, i64* ${slot}`);
277
+ }
278
+ return { argc: String(args.length), ptr };
279
+ }
280
+ // At least one `...spread`: accumulate the argument list in a runtime
281
+ // array, which grows to whatever length the spread produces.
282
+ const array = this.reg();
283
+ this.emit(` ${array} = call i64 @xt_array_new(i32 0, i64* null)`);
284
+ for (const element of args) {
285
+ if (element.kind === SyntaxKind.SpreadElement) {
286
+ const value = this.emitExpression((element as SpreadElement).expression);
287
+ this.emit(` call i64 @xt_array_spread(i64 ${array}, i64 ${value})`);
288
+ } else {
289
+ const value = this.emitExpression(element);
290
+ this.emit(` call i64 @xt_array_push(i64 ${array}, i64 ${value})`);
291
+ }
274
292
  }
275
- return { argc: args.length, ptr };
293
+ const argc = this.reg();
294
+ this.emit(` ${argc} = call i32 @xt_array_size(i64 ${array})`);
295
+ const ptr = this.reg();
296
+ this.emit(` ${ptr} = call i64* @xt_array_items(i64 ${array})`);
297
+ return { argc, ptr };
276
298
  },
277
299
 
278
300
  emitDelete(node: DeleteExpression): string {
@@ -398,14 +420,15 @@ export const callMethods: CallMethods = {
398
420
  this.emit(` ${result} = call i64 @xt_array_new(i32 ${args.argc}, i64* ${args.ptr})`);
399
421
  return result;
400
422
  }
401
- let array = this.runtimeCall("xt_array_new", [`0`, `null`]);
423
+ const array = this.reg();
424
+ this.emit(` ${array} = call i64 @xt_array_new(i32 0, i64* null)`);
402
425
  for (const element of node.elements) {
403
426
  if (element.kind === SyntaxKind.SpreadElement) {
404
427
  const spread = this.emitExpression((element as { expression: Expression }).expression);
405
- array = this.runtimeCall("xt_array_spread", [array, spread]);
428
+ this.emit(` call i64 @xt_array_spread(i64 ${array}, i64 ${spread})`);
406
429
  } else {
407
430
  const value = this.emitExpression(element);
408
- array = this.runtimeCall("xt_array_push", [array, value]);
431
+ this.emit(` call i64 @xt_array_push(i64 ${array}, i64 ${value})`);
409
432
  }
410
433
  }
411
434
  return array;
@@ -416,8 +439,15 @@ export const callMethods: CallMethods = {
416
439
  this.emit(` ${object} = call i64 @xt_object_new()`);
417
440
  for (const property of node.properties) {
418
441
  if (property.kind === SyntaxKind.PropertyAssignment) {
419
- const name = propertyNameText(property.name);
420
- const key = this.stringValue(name);
442
+ const nameNode = property.name;
443
+ let key: string;
444
+ if (nameNode.kind === SyntaxKind.ComputedPropertyName) {
445
+ const keyValue = this.emitExpression((nameNode as { expression: Expression }).expression);
446
+ key = this.reg();
447
+ this.emit(` ${key} = call i64 @xt_to_string(i64 ${keyValue})`);
448
+ } else {
449
+ key = this.stringValue(propertyNameText(nameNode));
450
+ }
421
451
  const value = this.emitExpression(property.initializer);
422
452
  this.emit(` call i64 @xt_set(i64 ${object}, i64 ${key}, i64 ${value})`);
423
453
  } else if (property.kind === SyntaxKind.ShorthandPropertyAssignment) {
@@ -482,6 +512,19 @@ export const callMethods: CallMethods = {
482
512
 
483
513
  emitSuperConstructor(node: CallExpression): string {
484
514
  const thisValue = this.emitThis();
515
+ const classInfo = this.current.fn.classInfo;
516
+ const parent = classInfo?.parentExpression;
517
+ const unboundBuiltin =
518
+ parent !== undefined &&
519
+ parent.kind === SyntaxKind.Identifier &&
520
+ (parent as Identifier).text === "Error" &&
521
+ !this.binding.symbolOfIdentifier.get(parent as Identifier);
522
+ if (unboundBuiltin) {
523
+ const args = this.emitArguments(node.arguments);
524
+ const result = this.reg();
525
+ this.emit(` ${result} = call i64 @xt_error_init(i64 ${thisValue}, i32 ${args.argc}, i64* ${args.ptr})`);
526
+ return result;
527
+ }
485
528
  const proto = this.reg();
486
529
  this.emit(` ${proto} = call i64 @xt_object_get_prototype(i64 ${thisValue})`);
487
530
  const parentProto = this.reg();
@@ -117,7 +117,7 @@ export class GeneratorContext {
117
117
  return ptr;
118
118
  }
119
119
 
120
- const(hex: bigint): string {
120
+ const(hex: string): string {
121
121
  return i64(hex);
122
122
  }
123
123
 
@@ -33,7 +33,7 @@ import {
33
33
  import { SymbolKind, type SymbolInfo } from "../../binder/binder.js";
34
34
  import { DiagnosticCode } from "../../diagnostics/diagnostic.js";
35
35
  import { i64, numberLiteral, XT_FALSE, XT_NULL, XT_TRUE, XT_UNDEFINED } from "../values.js";
36
- import { BINARY_RUNTIME, compoundToBinary, isAssignmentOperator, CTOR_FUNCTIONS } from "./tables.js";
36
+ import { BINARY_RUNTIME, compoundToBinary, isAssignmentOperator, BUILTIN_FUNCTION_VALUES, CTOR_FUNCTIONS } from "./tables.js";
37
37
  import type { Generator } from "./generator.js";
38
38
 
39
39
  export interface ExpressionMethods {
@@ -64,7 +64,7 @@ export const expressionMethods: ExpressionMethods = {
64
64
  return numberLiteral(value);
65
65
  }
66
66
  case SyntaxKind.BigIntLiteral:
67
- return numberLiteral(Number((node as { value: bigint }).value));
67
+ return numberLiteral((node as { value: number }).value);
68
68
  case SyntaxKind.StringLiteral:
69
69
  return this.stringValue((node as { value: string }).value);
70
70
  case SyntaxKind.NoSubstitutionTemplateLiteral:
@@ -197,7 +197,17 @@ export const expressionMethods: ExpressionMethods = {
197
197
  this.emit(` ${rest} = call i64 @xt_rest_args(i32 ${argc}, i64* ${argv}, i32 0)`);
198
198
  return rest;
199
199
  }
200
- default:
200
+ default: {
201
+ const builtinValue = BUILTIN_FUNCTION_VALUES[identifier.text];
202
+ if (builtinValue) {
203
+ // A global like `Boolean` used as a value (`arr.filter(Boolean)`):
204
+ // wrap its runtime trampoline in a closure object.
205
+ const cast = this.reg();
206
+ this.emit(` ${cast} = bitcast i64 (i64, i64, i32, i64*)* @${builtinValue} to i8*`);
207
+ const closure = this.reg();
208
+ this.emit(` ${closure} = call i64 @xt_closure_new(i8* ${cast}, i32 0, i64* null)`);
209
+ return closure;
210
+ }
201
211
  this.diagnostics.error(
202
212
  DiagnosticCode.CannotFindName,
203
213
  `Cannot find name '${identifier.text}'`,
@@ -205,6 +215,7 @@ export const expressionMethods: ExpressionMethods = {
205
215
  this.sourceFile.fileName,
206
216
  );
207
217
  return i64(XT_UNDEFINED);
218
+ }
208
219
  }
209
220
  },
210
221
 
@@ -4,6 +4,7 @@
4
4
  */
5
5
 
6
6
  import {
7
+ ModifierKind,
7
8
  SyntaxKind,
8
9
  type ArrowFunction,
9
10
  type Block,
@@ -15,8 +16,20 @@ import type { ClassInfo, FunctionInfo, SymbolInfo } from "../../binder/binder.js
15
16
  import { SymbolKind } from "../../binder/binder.js";
16
17
  import { i64, XT_UNDEFINED } from "../values.js";
17
18
  import { RUNTIME_DECLARATIONS, type FunctionState } from "./state.js";
19
+ import { propertyNameText } from "./tables.js";
18
20
  import type { Generator } from "./generator.js";
19
21
 
22
+ /** Accessibility/`readonly` parameter modifiers make a constructor parameter a property. */
23
+ function isParameterProperty(parameter: Parameter): boolean {
24
+ return parameter.modifiers.some(
25
+ (modifier) =>
26
+ modifier.modifierKind === ModifierKind.Public ||
27
+ modifier.modifierKind === ModifierKind.Private ||
28
+ modifier.modifierKind === ModifierKind.Protected ||
29
+ modifier.modifierKind === ModifierKind.Readonly,
30
+ );
31
+ }
32
+
20
33
  export interface ModuleMethods {
21
34
  run(this: Generator): string;
22
35
  emitFunction(this: Generator, fn: FunctionInfo): void;
@@ -120,6 +133,31 @@ export const moduleMethods: ModuleMethods = {
120
133
  this.emit(` store i64 ${inherited}, i64* %saved.this`);
121
134
  }
122
135
 
136
+ // A derived class with no explicit constructor gets an implicit one that
137
+ // forwards every argument to the parent constructor (`super(...arguments)`).
138
+ if (fn.isConstructor && fn.classInfo?.parentExpression) {
139
+ const explicitBody = (fn.node as { body?: Block }).body;
140
+ const parent = fn.classInfo.parentExpression;
141
+ const unboundBuiltin =
142
+ parent.kind === SyntaxKind.Identifier &&
143
+ (parent as Identifier).text === "Error" &&
144
+ !this.binding.symbolOfIdentifier.get(parent as Identifier);
145
+ if (!explicitBody && !unboundBuiltin) {
146
+ const parentValue = this.emitExpression(parent);
147
+ const parentProto = this.reg();
148
+ this.emit(` ${parentProto} = call i64 @xt_function_get_prototype(i64 ${parentValue})`);
149
+ const ctorKey = this.stringValue("__ctor");
150
+ const parentCtor = this.reg();
151
+ this.emit(` ${parentCtor} = call i64 @xt_get(i64 ${parentProto}, i64 ${ctorKey})`);
152
+ const superThis = this.emitThis();
153
+ this.emit(` call i64 @xt_call_with_this(i64 ${parentCtor}, i64 ${superThis}, i32 %argc, i64* %argv)`);
154
+ } else if (!explicitBody && unboundBuiltin) {
155
+ // `extends Error` with no explicit constructor: initialise message/name.
156
+ const superThis = this.emitThis();
157
+ this.emit(` call i64 @xt_error_init(i64 ${superThis}, i32 %argc, i64* %argv)`);
158
+ }
159
+ }
160
+
123
161
  // Instance field initializers run before the constructor body.
124
162
  if (fn.isConstructor && fn.classInfo) {
125
163
  const thisValue = this.emitThis();
@@ -131,6 +169,20 @@ export const moduleMethods: ModuleMethods = {
131
169
  }
132
170
  }
133
171
 
172
+ // Constructor parameter properties (`constructor(private readonly x: T) {}`)
173
+ // copy the incoming argument onto `this`.
174
+ if (fn.isConstructor) {
175
+ const thisValue = this.emitThis();
176
+ for (let index = 0; index < fn.params.length; index++) {
177
+ const parameter = parameterNodes[index];
178
+ if (!parameter || !isParameterProperty(parameter)) continue;
179
+ const symbol = fn.params[index]!;
180
+ const key = this.stringValue(propertyNameText(parameter.name));
181
+ const value = this.readSlot(symbol);
182
+ this.emit(` call i64 @xt_set(i64 ${thisValue}, i64 ${key}, i64 ${value})`);
183
+ }
184
+ }
185
+
134
186
  // Body.
135
187
  if (fn.node.kind === SyntaxKind.SourceFile) {
136
188
  this.emitStatements(this.sourceFile.statements);
@@ -220,7 +272,14 @@ export const moduleMethods: ModuleMethods = {
220
272
  for (const method of classInfo.methods) {
221
273
  const fnValue = this.emitClosureValue(method);
222
274
  const key = this.stringValue(method.name);
223
- this.emit(` call i64 @xt_set(i64 ${proto}, i64 ${key}, i64 ${fnValue})`);
275
+ const accessor = (method.node as { accessor?: "get" | "set" }).accessor;
276
+ if (accessor === "get") {
277
+ this.emit(` call i64 @xt_object_define_getter(i64 ${proto}, i64 ${key}, i64 ${fnValue})`);
278
+ } else if (accessor === "set") {
279
+ this.emit(` call i64 @xt_object_define_setter(i64 ${proto}, i64 ${key}, i64 ${fnValue})`);
280
+ } else {
281
+ this.emit(` call i64 @xt_set(i64 ${proto}, i64 ${key}, i64 ${fnValue})`);
282
+ }
224
283
  }
225
284
  const ctor = this.emitClosureValue(classInfo.ctor!);
226
285
  this.emit(` call i64 @xt_function_set_prototype(i64 ${ctor}, i64 ${proto})`);