xbintsc 0.3.81 → 0.3.87
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 +12 -2
- package/README.zh-CN.md +12 -4
- package/dist/src/ast/common.d.ts +23 -0
- package/dist/src/cli/main.js +68 -24
- package/dist/src/cli/main.js.map +1 -1
- package/dist/src/codegen/generator/calls/invocation.js +5 -1
- package/dist/src/codegen/generator/calls/invocation.js.map +1 -1
- package/dist/src/codegen/generator/context.d.ts +50 -0
- package/dist/src/codegen/generator/context.js +124 -13
- package/dist/src/codegen/generator/context.js.map +1 -1
- package/dist/src/codegen/generator/debug.d.ts +33 -0
- package/dist/src/codegen/generator/debug.js +97 -0
- package/dist/src/codegen/generator/debug.js.map +1 -0
- package/dist/src/codegen/generator/expressions/assignment.js +9 -5
- package/dist/src/codegen/generator/expressions/assignment.js.map +1 -1
- package/dist/src/codegen/generator/expressions/operators.js +35 -9
- package/dist/src/codegen/generator/expressions/operators.js.map +1 -1
- package/dist/src/codegen/generator/expressions/primary.js +5 -2
- package/dist/src/codegen/generator/expressions/primary.js.map +1 -1
- package/dist/src/codegen/generator/generator.d.ts +3 -1
- package/dist/src/codegen/generator/generator.js +3 -1
- package/dist/src/codegen/generator/generator.js.map +1 -1
- package/dist/src/codegen/generator/module.d.ts +1 -0
- package/dist/src/codegen/generator/module.js +76 -6
- package/dist/src/codegen/generator/module.js.map +1 -1
- package/dist/src/codegen/generator/numeric.d.ts +26 -0
- package/dist/src/codegen/generator/numeric.js +127 -0
- package/dist/src/codegen/generator/numeric.js.map +1 -0
- package/dist/src/codegen/generator/state.d.ts +17 -0
- package/dist/src/codegen/generator/state.js +5 -0
- package/dist/src/codegen/generator/state.js.map +1 -1
- package/dist/src/codegen/generator/statements/control-flow.js +1 -1
- package/dist/src/codegen/generator/statements/control-flow.js.map +1 -1
- package/dist/src/codegen/generator/statements/dispatch.js +3 -0
- package/dist/src/codegen/generator/statements/dispatch.js.map +1 -1
- package/dist/src/codegen/generator/statements/variables.js +8 -2
- package/dist/src/codegen/generator/statements/variables.js.map +1 -1
- package/dist/src/codegen/generator/tables.d.ts +7 -0
- package/dist/src/codegen/generator/tables.js +25 -0
- package/dist/src/codegen/generator/tables.js.map +1 -1
- package/dist/src/driver/bundler/merge.js +41 -5
- package/dist/src/driver/bundler/merge.js.map +1 -1
- package/dist/src/driver/compiler.d.ts +22 -3
- package/dist/src/driver/compiler.js +43 -57
- package/dist/src/driver/compiler.js.map +1 -1
- package/dist/src/driver/fingerprint.d.ts +27 -0
- package/dist/src/driver/fingerprint.js +88 -0
- package/dist/src/driver/fingerprint.js.map +1 -0
- package/dist/src/driver/toolchain-provider.d.ts +9 -2
- package/dist/src/driver/toolchain-provider.js +15 -15
- package/dist/src/driver/toolchain-provider.js.map +1 -1
- package/dist/src/driver/toolchain.d.ts +58 -1
- package/dist/src/driver/toolchain.js +169 -22
- package/dist/src/driver/toolchain.js.map +1 -1
- package/dist/src/parser/expressions.js +20 -1
- package/dist/src/parser/expressions.js.map +1 -1
- package/dist/tests/cli/main.test.js +41 -1
- package/dist/tests/cli/main.test.js.map +1 -1
- package/dist/tests/codegen/expressions.test.js +14 -3
- package/dist/tests/codegen/expressions.test.js.map +1 -1
- package/dist/tests/codegen/llvm.test.js +23 -0
- package/dist/tests/codegen/llvm.test.js.map +1 -1
- package/dist/tests/driver/toolchain-provider.test.js +49 -2
- package/dist/tests/driver/toolchain-provider.test.js.map +1 -1
- package/dist/tests/driver/toolchain.test.js +62 -3
- package/dist/tests/driver/toolchain.test.js.map +1 -1
- package/dist/tests/e2e/child-process.test.d.ts +10 -0
- package/dist/tests/e2e/child-process.test.js +59 -0
- package/dist/tests/e2e/child-process.test.js.map +1 -0
- package/dist/tests/e2e/compile-objects-classes.test.js +47 -0
- package/dist/tests/e2e/compile-objects-classes.test.js.map +1 -1
- package/dist/tests/e2e/error-stack.test.d.ts +9 -0
- package/dist/tests/e2e/error-stack.test.js +94 -0
- package/dist/tests/e2e/error-stack.test.js.map +1 -0
- package/dist/tests/e2e/string-concat.test.d.ts +9 -0
- package/dist/tests/e2e/string-concat.test.js +102 -0
- package/dist/tests/e2e/string-concat.test.js.map +1 -0
- package/dist/tests/helpers.d.ts +9 -0
- package/dist/tests/helpers.js +26 -3
- package/dist/tests/helpers.js.map +1 -1
- package/dist/tests/parser/expressions.test.js +7 -0
- package/dist/tests/parser/expressions.test.js.map +1 -1
- package/dist/tests/version.test.d.ts +1 -0
- package/dist/tests/version.test.js +24 -0
- package/dist/tests/version.test.js.map +1 -0
- package/doc/DESIGN.md +20 -12
- package/doc/ai/cli.md +2 -0
- package/doc/ai/contributing.md +7 -1
- package/doc/ai/language-support.md +4 -4
- package/doc/ai/troubleshooting.md +39 -2
- package/doc/ai/zh-CN/cli.md +2 -0
- package/doc/ai/zh-CN/contributing.md +6 -2
- package/doc/ai/zh-CN/language-support.md +4 -4
- package/doc/ai/zh-CN/troubleshooting.md +35 -2
- package/doc/implemented.md +3 -3
- package/doc/requirements.md +13 -4
- package/doc/unimplemented.md +11 -10
- package/doc/zh-CN/DESIGN.md +15 -9
- package/doc/zh-CN/implemented.md +3 -3
- package/doc/zh-CN/requirements.md +12 -3
- package/doc/zh-CN/unimplemented.md +10 -9
- package/package.json +3 -1
- package/runtime/ext_node/buffer/parts/construction.inc +2 -2
- package/runtime/ext_node/buffer/parts/methods.inc +1 -1
- package/runtime/ext_node/buffer/parts/search.inc +1 -1
- package/runtime/ext_node/child_process/child_process.c +25 -0
- package/runtime/ext_node/fs/fd_ops.c +1 -1
- package/runtime/rt.h +17 -1
- package/runtime/rt_internal.h +47 -4
- package/runtime/xt_alloc.c +14 -2
- package/runtime/xt_bigint/conversions.inc +1 -1
- package/runtime/xt_builtins.c +136 -14
- package/runtime/xt_containers/access.inc +17 -16
- package/runtime/xt_containers/objects.inc +4 -3
- package/runtime/xt_io/exception.inc +171 -2
- package/runtime/xt_io/inspect.inc +15 -13
- package/runtime/xt_io.c +5 -0
- package/runtime/xt_stdlib/arrays.inc +2 -2
- package/runtime/xt_stdlib/dispatch.inc +2 -2
- package/runtime/xt_stdlib/strings.inc +33 -21
- package/runtime/xt_stdlib2/array-statics.inc +2 -1
- package/runtime/xt_stdlib2/arrays.inc +1 -1
- package/runtime/xt_stdlib2/error.inc +22 -6
- package/runtime/xt_stdlib2/json.inc +3 -2
- package/runtime/xt_stdlib2/number-string-statics.inc +3 -1
- package/runtime/xt_stdlib2/regexp-match.inc +48 -33
- package/runtime/xt_stdlib2/strings.inc +23 -14
- package/runtime/xt_symbol.c +1 -1
- package/runtime/xt_values/comparison.inc +2 -2
- package/runtime/xt_values/constructors.inc +10 -5
- package/runtime/xt_values/conversions.inc +4 -3
- package/runtime/xt_values/strings.inc +78 -4
- package/scripts/bench.ts +172 -0
- package/scripts/build-runtime.ts +2 -1
- package/scripts/check-version.ts +53 -0
- package/src/ast/common.ts +23 -0
- package/src/cli/main.ts +68 -26
- package/src/codegen/generator/calls/invocation.ts +5 -0
- package/src/codegen/generator/context.ts +129 -18
- package/src/codegen/generator/debug.ts +132 -0
- package/src/codegen/generator/expressions/assignment.ts +9 -5
- package/src/codegen/generator/expressions/operators.ts +47 -8
- package/src/codegen/generator/expressions/primary.ts +5 -2
- package/src/codegen/generator/generator.ts +18 -2
- package/src/codegen/generator/module.ts +78 -6
- package/src/codegen/generator/numeric.ts +158 -0
- package/src/codegen/generator/state.ts +22 -0
- package/src/codegen/generator/statements/control-flow.ts +1 -1
- package/src/codegen/generator/statements/dispatch.ts +3 -0
- package/src/codegen/generator/statements/variables.ts +8 -2
- package/src/codegen/generator/tables.ts +26 -0
- package/src/driver/bundler/merge.ts +39 -5
- package/src/driver/compiler.ts +62 -55
- package/src/driver/fingerprint.ts +85 -0
- package/src/driver/toolchain-provider.ts +17 -16
- package/src/driver/toolchain.ts +210 -21
- package/src/parser/expressions.ts +20 -1
|
@@ -9,13 +9,16 @@ static xt_value xt_string_pad(xt_value value, xt_value lengthValue, xt_value pad
|
|
|
9
9
|
if (!pad || pad->length == 0) return value;
|
|
10
10
|
size_t total = (size_t)target;
|
|
11
11
|
xt_string *out = xt_string_alloc(total);
|
|
12
|
+
char *mutable = xt_string_mutable(out);
|
|
13
|
+
const char *text = xt_string_bytes(s);
|
|
14
|
+
const char *padBytes = xt_string_bytes(pad);
|
|
12
15
|
size_t needed = total - s->length;
|
|
13
16
|
if (start) {
|
|
14
|
-
for (size_t i = 0; i < needed; i++)
|
|
15
|
-
memcpy(
|
|
17
|
+
for (size_t i = 0; i < needed; i++) mutable[i] = padBytes[i % pad->length];
|
|
18
|
+
memcpy(mutable + needed, text, s->length);
|
|
16
19
|
} else {
|
|
17
|
-
memcpy(
|
|
18
|
-
for (size_t i = 0; i < needed; i++)
|
|
20
|
+
memcpy(mutable, text, s->length);
|
|
21
|
+
for (size_t i = 0; i < needed; i++) mutable[s->length + i] = padBytes[i % pad->length];
|
|
19
22
|
}
|
|
20
23
|
return XT_FROM_PTR(XT_TAG_STRING, out);
|
|
21
24
|
}
|
|
@@ -23,11 +26,12 @@ static xt_value xt_string_pad(xt_value value, xt_value lengthValue, xt_value pad
|
|
|
23
26
|
static xt_value xt_string_trim_side(xt_value value, int start) {
|
|
24
27
|
xt_string *s = xt_as_string(value);
|
|
25
28
|
if (!s) return value;
|
|
29
|
+
const char *bytes = xt_string_bytes(s);
|
|
26
30
|
uint32_t begin = 0;
|
|
27
31
|
uint32_t end = s->length;
|
|
28
|
-
if (start) { while (begin < end && isspace((unsigned char)
|
|
29
|
-
else { while (end > begin && isspace((unsigned char)
|
|
30
|
-
return xt_string_new(
|
|
32
|
+
if (start) { while (begin < end && isspace((unsigned char)bytes[begin])) begin++; }
|
|
33
|
+
else { while (end > begin && isspace((unsigned char)bytes[end - 1])) end--; }
|
|
34
|
+
return xt_string_new(bytes + begin, end - begin);
|
|
31
35
|
}
|
|
32
36
|
|
|
33
37
|
static xt_value xt_string_at(xt_value value, xt_value indexValue) {
|
|
@@ -36,7 +40,7 @@ static xt_value xt_string_at(xt_value value, xt_value indexValue) {
|
|
|
36
40
|
int32_t index = indexValue == XT_UNDEFINED ? 0 : xt_to_int32(indexValue);
|
|
37
41
|
if (index < 0) index += (int32_t)s->length;
|
|
38
42
|
if (index < 0 || (uint32_t)index >= s->length) return XT_UNDEFINED;
|
|
39
|
-
return xt_string_new(s
|
|
43
|
+
return xt_string_new(xt_string_bytes(s) + index, 1);
|
|
40
44
|
}
|
|
41
45
|
|
|
42
46
|
static xt_value xt_string_replace_all(xt_value value, xt_value searchValue, xt_value replacementValue) {
|
|
@@ -46,20 +50,23 @@ static xt_value xt_string_replace_all(xt_value value, xt_value searchValue, xt_v
|
|
|
46
50
|
xt_string *search = xt_as_string(xt_to_string(searchValue));
|
|
47
51
|
xt_string *replacement = xt_as_string(xt_to_string(replacementValue));
|
|
48
52
|
if (!search || search->length == 0) return value;
|
|
53
|
+
const char *text = xt_string_bytes(s);
|
|
54
|
+
const char *needle = xt_string_bytes(search);
|
|
55
|
+
const char *repl = xt_string_bytes(replacement);
|
|
49
56
|
size_t capacity = s->length + replacement->length * 4 + 16;
|
|
50
57
|
char *out = (char *)malloc(capacity);
|
|
51
58
|
if (!out) abort();
|
|
52
59
|
size_t length = 0;
|
|
53
60
|
uint32_t i = 0;
|
|
54
61
|
while (i < s->length) {
|
|
55
|
-
if (i + search->length <= s->length && memcmp(
|
|
62
|
+
if (i + search->length <= s->length && memcmp(text + i, needle, search->length) == 0) {
|
|
56
63
|
if (length + replacement->length + 2 > capacity) { capacity = (length + replacement->length + 2) * 2; out = (char *)realloc(out, capacity); if (!out) abort(); }
|
|
57
|
-
memcpy(out + length,
|
|
64
|
+
memcpy(out + length, repl, replacement->length);
|
|
58
65
|
length += replacement->length;
|
|
59
66
|
i += search->length;
|
|
60
67
|
} else {
|
|
61
68
|
if (length + 2 > capacity) { capacity *= 2; out = (char *)realloc(out, capacity); if (!out) abort(); }
|
|
62
|
-
out[length++] =
|
|
69
|
+
out[length++] = text[i++];
|
|
63
70
|
}
|
|
64
71
|
}
|
|
65
72
|
xt_value result = xt_string_new(out, length);
|
|
@@ -106,17 +113,19 @@ xt_value xt_ext_string_method(xt_value target, const char *method, int32_t argc,
|
|
|
106
113
|
if (limit > (int32_t)s->length) limit = (int32_t)s->length;
|
|
107
114
|
int32_t nlen = (int32_t)needle->length;
|
|
108
115
|
if (nlen == 0) return xt_number((double)(limit < 0 ? 0 : limit));
|
|
116
|
+
const char *text = xt_string_bytes(s);
|
|
117
|
+
const char *need = xt_string_bytes(needle);
|
|
109
118
|
int32_t i = limit;
|
|
110
119
|
if (i > (int32_t)s->length - nlen) i = (int32_t)s->length - nlen;
|
|
111
120
|
for (; i >= 0; i--) {
|
|
112
|
-
if (memcmp(
|
|
121
|
+
if (memcmp(text + i, need, (size_t)nlen) == 0) return xt_number((double)i);
|
|
113
122
|
}
|
|
114
123
|
return xt_number(-1);
|
|
115
124
|
}
|
|
116
125
|
if (strcmp(method, "localeCompare") == 0) {
|
|
117
126
|
xt_string *a = xt_as_string(target);
|
|
118
127
|
xt_string *b = xt_as_string(xt_to_string(xt_arg_at(argc, argv, 0)));
|
|
119
|
-
int cmp = strcmp(a ? a
|
|
128
|
+
int cmp = strcmp(a ? xt_string_bytes(a) : "", b ? xt_string_bytes(b) : "");
|
|
120
129
|
return xt_number(cmp < 0 ? -1 : cmp > 0 ? 1 : 0);
|
|
121
130
|
}
|
|
122
131
|
if (strcmp(method, "codePointAt") == 0) {
|
|
@@ -124,7 +133,7 @@ xt_value xt_ext_string_method(xt_value target, const char *method, int32_t argc,
|
|
|
124
133
|
if (!s) return XT_UNDEFINED;
|
|
125
134
|
int32_t index = xt_to_int32(xt_arg_at(argc, argv, 0));
|
|
126
135
|
if (index < 0 || (uint32_t)index >= s->length) return XT_UNDEFINED;
|
|
127
|
-
return xt_number((double)(unsigned char)s
|
|
136
|
+
return xt_number((double)(unsigned char)xt_string_bytes(s)[index]);
|
|
128
137
|
}
|
|
129
138
|
if (strcmp(method, "valueOf") == 0 || strcmp(method, "toString") == 0) return target;
|
|
130
139
|
*handled = 0;
|
package/runtime/xt_symbol.c
CHANGED
|
@@ -98,7 +98,7 @@ xt_value xt_symbol_to_string(xt_value value) {
|
|
|
98
98
|
char *buffer = (char *)malloc(8 + length);
|
|
99
99
|
if (!buffer) abort();
|
|
100
100
|
memcpy(buffer, "Symbol(", 7);
|
|
101
|
-
if (description) memcpy(buffer + 7, description
|
|
101
|
+
if (description) memcpy(buffer + 7, xt_string_bytes(description), length);
|
|
102
102
|
buffer[7 + length] = ')';
|
|
103
103
|
xt_value result = xt_string_new(buffer, 8 + length);
|
|
104
104
|
free(buffer);
|
|
@@ -33,7 +33,7 @@ xt_value xt_lt(xt_value a, xt_value b) {
|
|
|
33
33
|
xt_string *x = xt_as_string(a);
|
|
34
34
|
xt_string *y = xt_as_string(b);
|
|
35
35
|
size_t n = x->length < y->length ? x->length : y->length;
|
|
36
|
-
int cmp = memcmp(x
|
|
36
|
+
int cmp = memcmp(xt_string_bytes(x), xt_string_bytes(y), n);
|
|
37
37
|
if (cmp == 0) return xt_bool(x->length < y->length);
|
|
38
38
|
return xt_bool(cmp < 0);
|
|
39
39
|
}
|
|
@@ -48,7 +48,7 @@ xt_value xt_le(xt_value a, xt_value b) {
|
|
|
48
48
|
xt_string *x = xt_as_string(a);
|
|
49
49
|
xt_string *y = xt_as_string(b);
|
|
50
50
|
size_t n = x->length < y->length ? x->length : y->length;
|
|
51
|
-
int cmp = memcmp(x
|
|
51
|
+
int cmp = memcmp(xt_string_bytes(x), xt_string_bytes(y), n);
|
|
52
52
|
if (cmp == 0) return xt_bool(x->length <= y->length);
|
|
53
53
|
return xt_bool(cmp < 0);
|
|
54
54
|
}
|
|
@@ -10,19 +10,22 @@ xt_value xt_number(double d) { return xt_from_double(d); }
|
|
|
10
10
|
xt_string *xt_string_alloc(size_t length) {
|
|
11
11
|
xt_string *s = (xt_string *)xt_alloc(sizeof(xt_string), XT_OBJECT_KIND_STRING);
|
|
12
12
|
s->length = (uint32_t)length;
|
|
13
|
+
/* Keep the terminator in the allocation: many consumers hand `flat` to libc. */
|
|
13
14
|
s->capacity = (uint32_t)length + 1;
|
|
14
|
-
s->
|
|
15
|
-
if (!s->
|
|
15
|
+
s->flat = (char *)malloc(s->capacity);
|
|
16
|
+
if (!s->flat) {
|
|
16
17
|
fprintf(stderr, "xbintsc: out of memory allocating string\n");
|
|
17
18
|
abort();
|
|
18
19
|
}
|
|
19
|
-
s->
|
|
20
|
+
s->flat[length] = '\0';
|
|
21
|
+
s->left = NULL;
|
|
22
|
+
s->right = NULL;
|
|
20
23
|
return s;
|
|
21
24
|
}
|
|
22
25
|
|
|
23
26
|
xt_value xt_string_new(const char *data, size_t len) {
|
|
24
27
|
xt_string *s = xt_string_alloc(len);
|
|
25
|
-
if (len > 0) memcpy(s->
|
|
28
|
+
if (len > 0) memcpy(s->flat, data, len);
|
|
26
29
|
return XT_FROM_PTR(XT_TAG_STRING, s);
|
|
27
30
|
}
|
|
28
31
|
|
|
@@ -30,9 +33,11 @@ xt_value xt_string_from_cstr(const char *data) { return xt_string_new(data, strl
|
|
|
30
33
|
|
|
31
34
|
xt_string *xt_as_string(xt_value v);
|
|
32
35
|
|
|
36
|
+
char *xt_string_mutable(xt_string *s) { return s ? s->flat : NULL; }
|
|
37
|
+
|
|
33
38
|
const char *xt_string_data(xt_value value) {
|
|
34
39
|
xt_string *s = xt_as_string(value);
|
|
35
|
-
return s ? s
|
|
40
|
+
return s ? xt_string_bytes(s) : NULL;
|
|
36
41
|
}
|
|
37
42
|
|
|
38
43
|
int32_t xt_string_length_value(xt_value value) {
|
|
@@ -24,8 +24,9 @@ double xt_to_number(xt_value v) {
|
|
|
24
24
|
if (XT_IS_BIGINT(v)) return xt_bigint_to_double_value(v);
|
|
25
25
|
if (XT_IS_STRING(v)) {
|
|
26
26
|
xt_string *s = xt_as_string(v);
|
|
27
|
-
const char *
|
|
28
|
-
const char *
|
|
27
|
+
const char *bytes = xt_string_bytes(s);
|
|
28
|
+
const char *begin = bytes;
|
|
29
|
+
const char *end = bytes + s->length;
|
|
29
30
|
while (begin < end && isspace((unsigned char)*begin)) begin++;
|
|
30
31
|
while (end > begin && isspace((unsigned char)end[-1])) end--;
|
|
31
32
|
size_t length = (size_t)(end - begin);
|
|
@@ -83,7 +84,7 @@ xt_value xt_to_string(xt_value v) {
|
|
|
83
84
|
out = (char *)realloc(out, capacity);
|
|
84
85
|
}
|
|
85
86
|
if (i > 0) out[length++] = ',';
|
|
86
|
-
memcpy(out + length, str
|
|
87
|
+
memcpy(out + length, xt_string_bytes(str), str->length);
|
|
87
88
|
length += str->length;
|
|
88
89
|
out[length] = '\0';
|
|
89
90
|
}
|
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
/* Strings */
|
|
3
3
|
/* ------------------------------------------------------------------------- */
|
|
4
4
|
|
|
5
|
+
/* Concatenations up to this many bytes are copied eagerly. The copy is cheaper
|
|
6
|
+
* than a cons node for the short strings that dominate everyday code, and a
|
|
7
|
+
* loop that keeps appending crosses the threshold quickly, after which every
|
|
8
|
+
* append is O(1). */
|
|
9
|
+
#define XT_STRING_CONS_THRESHOLD 32
|
|
10
|
+
|
|
5
11
|
xt_string *xt_as_string(xt_value v) {
|
|
6
12
|
if (XT_IS_STRING(v)) return (xt_string *)XT_GET_PTR(v);
|
|
7
13
|
return NULL;
|
|
@@ -9,14 +15,82 @@ xt_string *xt_as_string(xt_value v) {
|
|
|
9
15
|
|
|
10
16
|
int32_t xt_string_length(xt_string *s) { return (int32_t)s->length; }
|
|
11
17
|
|
|
18
|
+
const char *xt_string_bytes(xt_string *s) {
|
|
19
|
+
if (!s) return NULL;
|
|
20
|
+
if (s->flat) return s->flat;
|
|
21
|
+
|
|
22
|
+
/* The tree is traversed depth-first without recursion: a loop that appends
|
|
23
|
+
* 100k times produces a 100k-deep left spine, which would overflow the C
|
|
24
|
+
* stack. Right children are pushed so the leftmost leaf is emitted first. */
|
|
25
|
+
size_t total = (size_t)s->length;
|
|
26
|
+
char *out = (char *)malloc(total + 1);
|
|
27
|
+
if (!out) {
|
|
28
|
+
fprintf(stderr, "xbintsc: out of memory flattening string\n");
|
|
29
|
+
abort();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
size_t capacity = 64;
|
|
33
|
+
size_t count = 0;
|
|
34
|
+
xt_string **stack = (xt_string **)malloc(sizeof(xt_string *) * capacity);
|
|
35
|
+
if (!stack) {
|
|
36
|
+
fprintf(stderr, "xbintsc: out of memory flattening string\n");
|
|
37
|
+
abort();
|
|
38
|
+
}
|
|
39
|
+
stack[count++] = s;
|
|
40
|
+
|
|
41
|
+
size_t offset = 0;
|
|
42
|
+
while (count > 0) {
|
|
43
|
+
xt_string *node = stack[--count];
|
|
44
|
+
if (node->flat) {
|
|
45
|
+
memcpy(out + offset, node->flat, node->length);
|
|
46
|
+
offset += node->length;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (count + 2 > capacity) {
|
|
50
|
+
while (count + 2 > capacity) capacity *= 2;
|
|
51
|
+
xt_string **grown = (xt_string **)realloc(stack, sizeof(xt_string *) * capacity);
|
|
52
|
+
if (!grown) {
|
|
53
|
+
fprintf(stderr, "xbintsc: out of memory flattening string\n");
|
|
54
|
+
abort();
|
|
55
|
+
}
|
|
56
|
+
stack = grown;
|
|
57
|
+
}
|
|
58
|
+
if (node->right) stack[count++] = node->right;
|
|
59
|
+
if (node->left) stack[count++] = node->left;
|
|
60
|
+
}
|
|
61
|
+
free(stack);
|
|
62
|
+
|
|
63
|
+
out[offset] = '\0';
|
|
64
|
+
s->flat = out;
|
|
65
|
+
s->capacity = (uint32_t)(total + 1);
|
|
66
|
+
s->left = NULL;
|
|
67
|
+
s->right = NULL;
|
|
68
|
+
return s->flat;
|
|
69
|
+
}
|
|
70
|
+
|
|
12
71
|
static xt_value xt_string_concat(xt_string *a, xt_string *b) {
|
|
72
|
+
if (a->length == 0) return XT_FROM_PTR(XT_TAG_STRING, b);
|
|
73
|
+
if (b->length == 0) return XT_FROM_PTR(XT_TAG_STRING, a);
|
|
13
74
|
size_t total = (size_t)a->length + (size_t)b->length;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
75
|
+
if (total <= XT_STRING_CONS_THRESHOLD) {
|
|
76
|
+
xt_string *out = xt_string_alloc(total);
|
|
77
|
+
memcpy(out->flat, xt_string_bytes(a), a->length);
|
|
78
|
+
memcpy(out->flat + a->length, xt_string_bytes(b), b->length);
|
|
79
|
+
return XT_FROM_PTR(XT_TAG_STRING, out);
|
|
80
|
+
}
|
|
81
|
+
xt_string *out = (xt_string *)xt_alloc(sizeof(xt_string), XT_OBJECT_KIND_STRING);
|
|
82
|
+
out->length = (uint32_t)total;
|
|
83
|
+
out->capacity = 0;
|
|
84
|
+
out->flat = NULL;
|
|
85
|
+
out->left = a;
|
|
86
|
+
out->right = b;
|
|
17
87
|
return XT_FROM_PTR(XT_TAG_STRING, out);
|
|
18
88
|
}
|
|
19
89
|
|
|
20
90
|
int xt_string_equals(xt_string *a, xt_string *b) {
|
|
21
|
-
|
|
91
|
+
/* Interned literals (property names above all) are shared, so identity is the
|
|
92
|
+
* common case and skips the length, flattening and byte comparison entirely. */
|
|
93
|
+
if (a == b) return 1;
|
|
94
|
+
if (a->length != b->length) return 0;
|
|
95
|
+
return memcmp(xt_string_bytes(a), xt_string_bytes(b), a->length) == 0;
|
|
22
96
|
}
|
package/scripts/bench.ts
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Build and time a small, fixed benchmark suite.
|
|
4
|
+
*
|
|
5
|
+
* The numbers are wall-clock times on the current machine, so they are only
|
|
6
|
+
* useful for comparing revisions of the compiler (or the same revision against
|
|
7
|
+
* Node): run `npm run bench` before and after a change to the runtime or
|
|
8
|
+
* codegen. Each program is compiled with the normal driver (cache bypassed) and
|
|
9
|
+
* executed several times; the table reports the fastest run to reduce noise.
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* npm run bench # every benchmark, 3 runs, with Node for scale
|
|
13
|
+
* npm run bench -- --runs 5 # more runs (minimum is reported)
|
|
14
|
+
* npm run bench -- --filter prop
|
|
15
|
+
* npm run bench -- --no-node # skip the Node comparison
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { spawnSync } from "node:child_process";
|
|
19
|
+
import { mkdirSync, statSync, writeFileSync } from "node:fs";
|
|
20
|
+
import { dirname, join, resolve } from "node:path";
|
|
21
|
+
import { fileURLToPath } from "node:url";
|
|
22
|
+
import { build } from "../src/driver/index.js";
|
|
23
|
+
|
|
24
|
+
const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
25
|
+
const outDir = join(root, "build", "bench");
|
|
26
|
+
const cacheDir = join(root, "build", "bench-cache");
|
|
27
|
+
|
|
28
|
+
interface Benchmark {
|
|
29
|
+
readonly name: string;
|
|
30
|
+
/** Plain JavaScript so the same source can run under Node. */
|
|
31
|
+
readonly source: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const BENCHMARKS: readonly Benchmark[] = [
|
|
35
|
+
{
|
|
36
|
+
name: "numeric",
|
|
37
|
+
source: `
|
|
38
|
+
let total = 0;
|
|
39
|
+
for (let i = 0; i < 50000000; i++) total = total + i;
|
|
40
|
+
console.log(total);
|
|
41
|
+
`,
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: "property",
|
|
45
|
+
source: `
|
|
46
|
+
const object = { x: 0 };
|
|
47
|
+
for (let i = 0; i < 5000000; i++) object.x = object.x + 1;
|
|
48
|
+
console.log(object.x);
|
|
49
|
+
`,
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: "array",
|
|
53
|
+
source: `
|
|
54
|
+
const values = [];
|
|
55
|
+
for (let i = 0; i < 3000000; i++) values[i] = i;
|
|
56
|
+
let total = 0;
|
|
57
|
+
for (let i = 0; i < 3000000; i++) total = total + values[i];
|
|
58
|
+
console.log(total);
|
|
59
|
+
`,
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
name: "string-concat",
|
|
63
|
+
source: `
|
|
64
|
+
let text = "";
|
|
65
|
+
for (let i = 0; i < 100000; i++) text = text + "a";
|
|
66
|
+
console.log(text.length);
|
|
67
|
+
`,
|
|
68
|
+
},
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
interface Options {
|
|
72
|
+
readonly runs: number;
|
|
73
|
+
readonly filter?: string;
|
|
74
|
+
readonly node: boolean;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function parseOptions(argv: readonly string[]): Options {
|
|
78
|
+
let runs = 3;
|
|
79
|
+
let filter: string | undefined;
|
|
80
|
+
let node = true;
|
|
81
|
+
for (let index = 0; index < argv.length; index++) {
|
|
82
|
+
const arg = argv[index]!;
|
|
83
|
+
if (arg === "--no-node") node = false;
|
|
84
|
+
else if (arg === "--runs") runs = Math.max(1, Number(argv[++index]) || 1);
|
|
85
|
+
else if (arg.startsWith("--runs=")) runs = Math.max(1, Number(arg.slice("--runs=".length)) || 1);
|
|
86
|
+
else if (arg === "--filter") filter = argv[++index];
|
|
87
|
+
else if (arg.startsWith("--filter=")) filter = arg.slice("--filter=".length);
|
|
88
|
+
else if (arg === "--help" || arg === "-h") {
|
|
89
|
+
process.stdout.write(
|
|
90
|
+
"usage: npm run bench [-- --runs N] [--filter <substr>] [--no-node]\n",
|
|
91
|
+
);
|
|
92
|
+
process.exit(0);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return { runs, filter, node };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function minTimeMs(runs: number, execute: () => void): number {
|
|
99
|
+
let best = Number.POSITIVE_INFINITY;
|
|
100
|
+
for (let run = 0; run < runs; run++) {
|
|
101
|
+
const start = process.hrtime.bigint();
|
|
102
|
+
execute();
|
|
103
|
+
const elapsed = Number(process.hrtime.bigint() - start) / 1e6;
|
|
104
|
+
if (elapsed < best) best = elapsed;
|
|
105
|
+
}
|
|
106
|
+
return best;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function formatMs(value: number | undefined): string {
|
|
110
|
+
return value === undefined ? "-" : `${value.toFixed(1)} ms`;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function main(): number {
|
|
114
|
+
const options = parseOptions(process.argv.slice(2));
|
|
115
|
+
mkdirSync(outDir, { recursive: true });
|
|
116
|
+
mkdirSync(cacheDir, { recursive: true });
|
|
117
|
+
const benchmarks = BENCHMARKS.filter(
|
|
118
|
+
(benchmark) => options.filter === undefined || benchmark.name.includes(options.filter),
|
|
119
|
+
);
|
|
120
|
+
if (benchmarks.length === 0) {
|
|
121
|
+
process.stderr.write(`xbintsc: no benchmark matches '${options.filter}'\n`);
|
|
122
|
+
return 1;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const rows: { name: string; ours?: number; node?: number; bytes?: number }[] = [];
|
|
126
|
+
for (const benchmark of benchmarks) {
|
|
127
|
+
const entry = join(outDir, `${benchmark.name}.ts`);
|
|
128
|
+
writeFileSync(entry, benchmark.source);
|
|
129
|
+
const result = build(entry, {
|
|
130
|
+
emit: "exe",
|
|
131
|
+
outDir: join(outDir, "bin"),
|
|
132
|
+
cacheDir,
|
|
133
|
+
force: true,
|
|
134
|
+
});
|
|
135
|
+
const errors = result.diagnostics.filter((diagnostic) => diagnostic.category === "error");
|
|
136
|
+
if (errors.length > 0) {
|
|
137
|
+
for (const error of errors) process.stderr.write(`xbintsc: ${error.message}\n`);
|
|
138
|
+
return 1;
|
|
139
|
+
}
|
|
140
|
+
const bytes = statSync(result.outputPath).size;
|
|
141
|
+
const ours = minTimeMs(options.runs, () => {
|
|
142
|
+
const executed = spawnSync(result.outputPath, [], { stdio: "ignore" });
|
|
143
|
+
if (executed.status !== 0) throw new Error(`${benchmark.name} exited with ${executed.status}`);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
let nodeTime: number | undefined;
|
|
147
|
+
if (options.node) {
|
|
148
|
+
const script = join(outDir, `${benchmark.name}.mjs`);
|
|
149
|
+
writeFileSync(script, benchmark.source);
|
|
150
|
+
nodeTime = minTimeMs(options.runs, () => {
|
|
151
|
+
const executed = spawnSync(process.execPath, [script], { stdio: "ignore" });
|
|
152
|
+
if (executed.status !== 0) throw new Error(`${benchmark.name} (node) exited with ${executed.status}`);
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
rows.push({ name: benchmark.name, ours, node: nodeTime, bytes });
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const header = ["benchmark", "xbintsc", "node", "size"];
|
|
159
|
+
process.stdout.write(
|
|
160
|
+
`${header[0]!.padEnd(16)}${header[1]!.padStart(12)}${header[2]!.padStart(12)}${header[3]!.padStart(12)}\n`,
|
|
161
|
+
);
|
|
162
|
+
for (const row of rows) {
|
|
163
|
+
const size = row.bytes === undefined ? "-" : `${Math.round(row.bytes / 1024)} KB`;
|
|
164
|
+
process.stdout.write(
|
|
165
|
+
`${row.name.padEnd(16)}${formatMs(row.ours).padStart(12)}${formatMs(row.node).padStart(12)}${size.padStart(12)}\n`,
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
process.stdout.write(`\nfastest of ${options.runs} run(s) on ${process.platform}-${process.arch}\n`);
|
|
169
|
+
return 0;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
process.exit(main());
|
package/scripts/build-runtime.ts
CHANGED
|
@@ -21,6 +21,7 @@ import { fileURLToPath } from "node:url";
|
|
|
21
21
|
import { RUNTIME_SOURCES } from "../src/driver/compiler.js";
|
|
22
22
|
import { platformSlug } from "../src/driver/paths.js";
|
|
23
23
|
import { resolveToolchain } from "../src/driver/toolchain-provider.js";
|
|
24
|
+
import { RUNTIME_CFLAGS } from "../src/driver/toolchain.js";
|
|
24
25
|
import { nodeExtension } from "../src/extensions/node/index.js";
|
|
25
26
|
|
|
26
27
|
const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
@@ -72,7 +73,7 @@ function compile(sources: readonly string[], extension: string): string[] {
|
|
|
72
73
|
const object = join(objDir, `${extension}_${index}_${basename(source, ".c")}.o`);
|
|
73
74
|
const result = spawnSync(
|
|
74
75
|
clang,
|
|
75
|
-
[
|
|
76
|
+
[...RUNTIME_CFLAGS, "-c", source, "-o", object, `-I${runtimeDir}`],
|
|
76
77
|
{ stdio: "inherit", env: childEnv },
|
|
77
78
|
);
|
|
78
79
|
if (result.status !== 0) {
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Verify that `COMPILER_VERSION` (reported by `xbintsc version` / `doctor` and
|
|
4
|
+
* embedded in the incremental cache key) matches the version in `package.json`
|
|
5
|
+
* (which names the release archives).
|
|
6
|
+
*
|
|
7
|
+
* Release automation runs this before tagging. A mismatch means an upgraded
|
|
8
|
+
* compiler can reuse binaries produced by the previous one while presenting the
|
|
9
|
+
* wrong version to bug reports.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { readFileSync } from "node:fs";
|
|
13
|
+
import { dirname, join, resolve } from "node:path";
|
|
14
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
15
|
+
|
|
16
|
+
/** Read the version literal from the compiler source. */
|
|
17
|
+
export function compilerVersionOf(root: string): string {
|
|
18
|
+
const text = readFileSync(join(root, "src", "driver", "compiler.ts"), "utf8");
|
|
19
|
+
const match = /export const COMPILER_VERSION = "([^"]+)"/.exec(text);
|
|
20
|
+
if (!match) throw new Error("COMPILER_VERSION is not declared in src/driver/compiler.ts");
|
|
21
|
+
return match[1]!;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Read the published version from package.json. */
|
|
25
|
+
export function packageVersionOf(root: string): string {
|
|
26
|
+
const parsed = JSON.parse(readFileSync(join(root, "package.json"), "utf8")) as { version?: string };
|
|
27
|
+
if (!parsed.version) throw new Error("package.json has no version field");
|
|
28
|
+
return parsed.version;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Returns a human-readable mismatch message, or `undefined` when in sync. */
|
|
32
|
+
export function checkCompilerVersion(root: string): string | undefined {
|
|
33
|
+
const packageVersion = packageVersionOf(root);
|
|
34
|
+
const compilerVersion = compilerVersionOf(root);
|
|
35
|
+
if (packageVersion === compilerVersion) return undefined;
|
|
36
|
+
return (
|
|
37
|
+
`COMPILER_VERSION is "${compilerVersion}" but package.json is "${packageVersion}".\n` +
|
|
38
|
+
'Update COMPILER_VERSION in src/driver/compiler.ts (it keys the incremental cache), or fix package.json.'
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const invokedDirectly =
|
|
43
|
+
process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href;
|
|
44
|
+
|
|
45
|
+
if (invokedDirectly) {
|
|
46
|
+
const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
47
|
+
const mismatch = checkCompilerVersion(root);
|
|
48
|
+
if (mismatch) {
|
|
49
|
+
process.stderr.write(`xbintsc: version mismatch\n${mismatch}\n`);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
process.stdout.write(`xbintsc: version ${compilerVersionOf(root)} matches package.json\n`);
|
|
53
|
+
}
|
package/src/ast/common.ts
CHANGED
|
@@ -207,4 +207,27 @@ export interface SourceFileNode extends Node {
|
|
|
207
207
|
readonly statements: Statement[];
|
|
208
208
|
readonly fileName: string;
|
|
209
209
|
readonly text: string;
|
|
210
|
+
/**
|
|
211
|
+
* For a bundled program: the original module each node was parsed from.
|
|
212
|
+
*
|
|
213
|
+
* The bundler merges every module into one `SourceFile`, but the nodes keep
|
|
214
|
+
* their module-local `start`/`end` offsets. Diagnostics must therefore report
|
|
215
|
+
* the module a node came from, not the entry file, or a cross-module error
|
|
216
|
+
* shows the wrong file and a nonsensical excerpt. Absent for a single-file
|
|
217
|
+
* compile, where `fileName` is the only source.
|
|
218
|
+
*/
|
|
219
|
+
readonly origins?: Map<object, string>;
|
|
220
|
+
/**
|
|
221
|
+
* For a bundled program: the original text of every module, keyed like
|
|
222
|
+
* {@link origins}. A node's `start` is relative to its module's text, so
|
|
223
|
+
* computing a frame's line number needs that text rather than the merged
|
|
224
|
+
* concatenation in `text`.
|
|
225
|
+
*/
|
|
226
|
+
readonly moduleTexts?: Map<string, string>;
|
|
227
|
+
/**
|
|
228
|
+
* Bundler-generated symbol prefixes (`m0_`, `m1_`, …). Display names (stack
|
|
229
|
+
* frames, symbol suffixes) strip them so a bundled `fail()` still shows as
|
|
230
|
+
* `fail`, not `m1_fail`.
|
|
231
|
+
*/
|
|
232
|
+
readonly modulePrefixes?: readonly string[];
|
|
210
233
|
}
|