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.
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/src/ast/expressions.d.ts +6 -2
- package/dist/src/ast/kinds.d.ts +1 -0
- package/dist/src/ast/kinds.js +1 -0
- package/dist/src/ast/kinds.js.map +1 -1
- package/dist/src/binder/binder.js +29 -0
- package/dist/src/binder/binder.js.map +1 -1
- package/dist/src/codegen/generator/calls.d.ts +1 -1
- package/dist/src/codegen/generator/calls.js +57 -14
- package/dist/src/codegen/generator/calls.js.map +1 -1
- package/dist/src/codegen/generator/context.d.ts +1 -1
- package/dist/src/codegen/generator/expressions.js +14 -3
- package/dist/src/codegen/generator/expressions.js.map +1 -1
- package/dist/src/codegen/generator/module.js +57 -2
- package/dist/src/codegen/generator/module.js.map +1 -1
- package/dist/src/codegen/generator/state.js +15 -0
- package/dist/src/codegen/generator/state.js.map +1 -1
- package/dist/src/codegen/generator/statements.js +25 -8
- package/dist/src/codegen/generator/statements.js.map +1 -1
- package/dist/src/codegen/generator/tables.d.ts +3 -0
- package/dist/src/codegen/generator/tables.js +12 -0
- package/dist/src/codegen/generator/tables.js.map +1 -1
- package/dist/src/codegen/values.d.ts +18 -11
- package/dist/src/codegen/values.js +92 -15
- package/dist/src/codegen/values.js.map +1 -1
- package/dist/src/diagnostics/source.d.ts +1 -1
- package/dist/src/diagnostics/source.js +12 -7
- package/dist/src/diagnostics/source.js.map +1 -1
- package/dist/src/driver/compiler.js +16 -2
- package/dist/src/driver/compiler.js.map +1 -1
- package/dist/src/extensions/node/child_process/index.d.ts +7 -0
- package/dist/src/extensions/node/child_process/index.js +19 -0
- package/dist/src/extensions/node/child_process/index.js.map +1 -0
- package/dist/src/extensions/node/crypto/index.d.ts +6 -0
- package/dist/src/extensions/node/crypto/index.js +18 -0
- package/dist/src/extensions/node/crypto/index.js.map +1 -0
- package/dist/src/extensions/node/index.js +6 -0
- package/dist/src/extensions/node/index.js.map +1 -1
- package/dist/src/extensions/node/module.d.ts +9 -1
- package/dist/src/extensions/node/module.js +14 -1
- package/dist/src/extensions/node/module.js.map +1 -1
- package/dist/src/extensions/node/url/index.d.ts +7 -0
- package/dist/src/extensions/node/url/index.js +21 -0
- package/dist/src/extensions/node/url/index.js.map +1 -0
- package/dist/src/lexer/scanner.js +4 -5
- package/dist/src/lexer/scanner.js.map +1 -1
- package/dist/src/parser/declarations.js +10 -9
- package/dist/src/parser/declarations.js.map +1 -1
- package/dist/src/parser/expressions.js +1 -1
- package/dist/src/parser/expressions.js.map +1 -1
- package/dist/tests/e2e/compile.test.js +36 -0
- package/dist/tests/e2e/compile.test.js.map +1 -1
- package/package.json +1 -1
- package/runtime/ext_node/child_process/child_process.c +213 -0
- package/runtime/ext_node/crypto/crypto.c +177 -0
- package/runtime/ext_node/process/process.c +37 -3
- package/runtime/ext_node/url/url.c +85 -0
- package/runtime/rt.h +20 -0
- package/runtime/rt_internal.h +11 -0
- package/runtime/xt_builtins.c +23 -0
- package/runtime/xt_containers.c +136 -10
- package/runtime/xt_io.c +53 -0
- package/runtime/xt_regex.h +16 -6
- package/runtime/xt_stdlib.c +11 -2
- package/runtime/xt_stdlib2.c +239 -13
- package/runtime/xt_values.c +2 -1
- package/src/ast/expressions.ts +7 -2
- package/src/ast/kinds.ts +1 -0
- package/src/binder/binder.ts +29 -0
- package/src/codegen/generator/calls.ts +59 -16
- package/src/codegen/generator/context.ts +1 -1
- package/src/codegen/generator/expressions.ts +14 -3
- package/src/codegen/generator/module.ts +60 -1
- package/src/codegen/generator/state.ts +15 -0
- package/src/codegen/generator/statements.ts +23 -8
- package/src/codegen/generator/tables.ts +13 -0
- package/src/codegen/values.ts +85 -16
- package/src/diagnostics/source.ts +12 -7
- package/src/driver/compiler.ts +15 -2
- package/src/extensions/node/child_process/index.ts +24 -0
- package/src/extensions/node/crypto/index.ts +23 -0
- package/src/extensions/node/index.ts +6 -0
- package/src/extensions/node/module.ts +13 -1
- package/src/extensions/node/url/index.ts +26 -0
- package/src/lexer/scanner.ts +4 -4
- package/src/parser/declarations.ts +10 -7
- package/src/parser/expressions.ts +1 -1
package/runtime/xt_stdlib2.c
CHANGED
|
@@ -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
|
-
|
|
1031
|
-
|
|
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 (
|
|
1039
|
-
|
|
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
|
-
|
|
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);
|
package/runtime/xt_values.c
CHANGED
|
@@ -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
|
-
|
|
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) {
|
package/src/ast/expressions.ts
CHANGED
|
@@ -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:
|
|
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
package/src/binder/binder.ts
CHANGED
|
@@ -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:
|
|
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:
|
|
266
|
-
if (args.length === 0) return { argc: 0, ptr: "null" };
|
|
267
|
-
const
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
428
|
+
this.emit(` call i64 @xt_array_spread(i64 ${array}, i64 ${spread})`);
|
|
406
429
|
} else {
|
|
407
430
|
const value = this.emitExpression(element);
|
|
408
|
-
|
|
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
|
|
420
|
-
|
|
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();
|
|
@@ -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(
|
|
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
|
-
|
|
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})`);
|