@stacksjs/zig-dtsx 0.9.12 → 0.9.14
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 +28 -0
- package/build.zig +1 -1
- package/package.json +2 -2
- package/src/char_utils.zig +78 -12
- package/src/emitter.zig +324 -179
- package/src/extractors.zig +724 -404
- package/src/lib.zig +35 -8
- package/src/main.zig +108 -77
- package/src/scan_loop.zig +101 -65
- package/src/scanner.zig +293 -106
- package/src/type_inference.zig +215 -130
- package/test/zig-dtsx.test.ts +5 -1
- package/zig-out/bin/zig-dtsx +0 -0
- package/zig-out/bin/zig-dtsx.exe +0 -0
package/src/extractors.zig
CHANGED
|
@@ -29,22 +29,17 @@ pub fn extractLeadingComments(s: *Scanner, decl_start: usize) ?[]const []const u
|
|
|
29
29
|
const pu: usize = @intCast(p);
|
|
30
30
|
// Check for block comment ending with */
|
|
31
31
|
if (p >= 1 and s.source[pu] == ch.CH_SLASH and s.source[pu - 1] == ch.CH_STAR) {
|
|
32
|
-
// Find matching
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
has_block_comment = true;
|
|
44
|
-
p = start - 1;
|
|
45
|
-
while (p >= 0 and ch.isWhitespace(s.source[@intCast(p)])) p -= 1;
|
|
46
|
-
continue;
|
|
47
|
-
}
|
|
32
|
+
// Find matching `/*` opener — std.mem.lastIndexOf walks bytes via
|
|
33
|
+
// the optimized scanner path. Was a manual byte-by-byte loop walking
|
|
34
|
+
// backwards, which is O(N) for long block comments.
|
|
35
|
+
const search_end = pu - 1;
|
|
36
|
+
const found = std.mem.lastIndexOf(u8, s.source[0..search_end], "/*");
|
|
37
|
+
if (found) |su| {
|
|
38
|
+
comments.append(s.source[su .. pu + 1]) catch {};
|
|
39
|
+
has_block_comment = true;
|
|
40
|
+
p = @as(isize, @intCast(su)) - 1;
|
|
41
|
+
while (p >= 0 and ch.isWhitespace(s.source[@intCast(p)])) p -= 1;
|
|
42
|
+
continue;
|
|
48
43
|
}
|
|
49
44
|
break;
|
|
50
45
|
}
|
|
@@ -78,19 +73,23 @@ pub fn extractLeadingComments(s: *Scanner, decl_start: usize) ?[]const []const u
|
|
|
78
73
|
}
|
|
79
74
|
}
|
|
80
75
|
|
|
76
|
+
// Single-line short-circuit: skip reverse + join when there's only one comment.
|
|
77
|
+
if (single_lines.items.len == 1) {
|
|
78
|
+
comments.append(single_lines.items[0]) catch {};
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
|
|
81
82
|
// Reverse and join with newlines
|
|
82
83
|
std.mem.reverse([]const u8, single_lines.items);
|
|
83
|
-
var total_len: usize =
|
|
84
|
-
for (single_lines.items
|
|
85
|
-
total_len += line.len;
|
|
86
|
-
if (i < single_lines.items.len - 1) total_len += 1;
|
|
87
|
-
}
|
|
84
|
+
var total_len: usize = single_lines.items.len - 1; // newlines between
|
|
85
|
+
for (single_lines.items) |line| total_len += line.len;
|
|
88
86
|
const joined = s.allocator.alloc(u8, total_len) catch break;
|
|
89
87
|
var offset: usize = 0;
|
|
88
|
+
const last_idx = single_lines.items.len - 1;
|
|
90
89
|
for (single_lines.items, 0..) |line, i| {
|
|
91
90
|
@memcpy(joined[offset .. offset + line.len], line);
|
|
92
91
|
offset += line.len;
|
|
93
|
-
if (i <
|
|
92
|
+
if (i < last_idx) {
|
|
94
93
|
joined[offset] = '\n';
|
|
95
94
|
offset += 1;
|
|
96
95
|
}
|
|
@@ -151,7 +150,8 @@ pub fn extractImport(s: *Scanner, start: usize) Declaration {
|
|
|
151
150
|
}
|
|
152
151
|
}
|
|
153
152
|
|
|
154
|
-
// Extract source module
|
|
153
|
+
// Extract source module — use indexOfChar (single-byte SIMD scan) for the
|
|
154
|
+
// closing quote instead of indexOf with a 1-char needle.
|
|
155
155
|
var module_src: []const u8 = "";
|
|
156
156
|
{
|
|
157
157
|
const from_idx = ch.indexOf(text, "from ", 0);
|
|
@@ -161,9 +161,7 @@ pub fn extractImport(s: *Scanner, start: usize) Declaration {
|
|
|
161
161
|
if (mi < text.len) {
|
|
162
162
|
const q = text[mi];
|
|
163
163
|
if (q == ch.CH_SQUOTE or q == ch.CH_DQUOTE) {
|
|
164
|
-
|
|
165
|
-
const end_idx = ch.indexOf(text, q_str, mi + 1);
|
|
166
|
-
if (end_idx) |ei| {
|
|
164
|
+
if (ch.indexOfChar(text, q, mi + 1)) |ei| {
|
|
167
165
|
module_src = text[mi + 1 .. ei];
|
|
168
166
|
}
|
|
169
167
|
}
|
|
@@ -173,9 +171,7 @@ pub fn extractImport(s: *Scanner, start: usize) Declaration {
|
|
|
173
171
|
while (mi < text.len and text[mi] != ch.CH_SQUOTE and text[mi] != ch.CH_DQUOTE) mi += 1;
|
|
174
172
|
if (mi < text.len) {
|
|
175
173
|
const q = text[mi];
|
|
176
|
-
|
|
177
|
-
const end_idx = ch.indexOf(text, q_str, mi + 1);
|
|
178
|
-
if (end_idx) |ei| {
|
|
174
|
+
if (ch.indexOfChar(text, q, mi + 1)) |ei| {
|
|
179
175
|
module_src = text[mi + 1 .. ei];
|
|
180
176
|
}
|
|
181
177
|
}
|
|
@@ -240,9 +236,9 @@ pub fn extractImport(s: *Scanner, start: usize) Declaration {
|
|
|
240
236
|
}
|
|
241
237
|
}
|
|
242
238
|
|
|
243
|
-
// Named imports
|
|
239
|
+
// Named imports — splitScalar is faster than splitSequence for a 1-byte separator.
|
|
244
240
|
const named_part = import_part[bs + 1 .. be];
|
|
245
|
-
var iter = std.mem.
|
|
241
|
+
var iter = std.mem.splitScalar(u8, named_part, ',');
|
|
246
242
|
while (iter.next()) |raw_item| {
|
|
247
243
|
const trimmed = ch.sliceTrimmed(raw_item, 0, raw_item.len);
|
|
248
244
|
if (trimmed.len == 0) continue;
|
|
@@ -266,14 +262,15 @@ pub fn extractImport(s: *Scanner, start: usize) Declaration {
|
|
|
266
262
|
}
|
|
267
263
|
}
|
|
268
264
|
|
|
265
|
+
// toOwnedSlice trims unused capacity — important for non-arena callers.
|
|
269
266
|
parsed_import = .{
|
|
270
267
|
.default_name = default_name,
|
|
271
|
-
.named_items = named_items.items,
|
|
268
|
+
.named_items = named_items.toOwnedSlice() catch named_items.items,
|
|
272
269
|
.source = module_src,
|
|
273
270
|
.is_type_only = is_type_only,
|
|
274
271
|
.is_namespace = is_namespace,
|
|
275
272
|
.namespace_name = namespace_name,
|
|
276
|
-
.resolved_items = resolved_items.items,
|
|
273
|
+
.resolved_items = resolved_items.toOwnedSlice() catch resolved_items.items,
|
|
277
274
|
};
|
|
278
275
|
}
|
|
279
276
|
|
|
@@ -302,8 +299,9 @@ pub fn extractGenerics(s: *Scanner) []const u8 {
|
|
|
302
299
|
const start = s.pos;
|
|
303
300
|
_ = s.findMatchingClose(ch.CH_LANGLE, ch.CH_RANGLE);
|
|
304
301
|
const raw = s.source[start..s.pos];
|
|
305
|
-
// Normalize multi-line generics to single line
|
|
306
|
-
|
|
302
|
+
// Normalize multi-line generics to single line. indexOfChar is the
|
|
303
|
+
// single-byte SIMD path; the previous code used the multi-byte indexOf.
|
|
304
|
+
if (ch.indexOfChar(raw, '\n', 0) != null) {
|
|
307
305
|
// Direct alloc: output ≤ input length (whitespace collapsed)
|
|
308
306
|
const buf = s.allocator.alloc(u8, raw.len) catch return raw;
|
|
309
307
|
var pos: usize = 0;
|
|
@@ -395,15 +393,18 @@ fn endsWithWord(text: []const u8, word: []const u8) bool {
|
|
|
395
393
|
|
|
396
394
|
/// Check if string is a numeric literal
|
|
397
395
|
pub fn isNumericLiteral(v: []const u8) bool {
|
|
396
|
+
if (v.len == 0) return false;
|
|
398
397
|
var i: usize = 0;
|
|
399
|
-
if (
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
398
|
+
if (v[0] == '-') {
|
|
399
|
+
i = 1;
|
|
400
|
+
if (i >= v.len) return false;
|
|
401
|
+
}
|
|
402
|
+
if (!ch.isDigit(v[i])) return false;
|
|
403
|
+
while (i < v.len and ch.isDigit(v[i])) i += 1;
|
|
403
404
|
if (i < v.len and v[i] == '.') {
|
|
404
405
|
i += 1;
|
|
405
|
-
if (i >= v.len or v[i]
|
|
406
|
-
while (i < v.len and v[i]
|
|
406
|
+
if (i >= v.len or !ch.isDigit(v[i])) return false;
|
|
407
|
+
while (i < v.len and ch.isDigit(v[i])) i += 1;
|
|
407
408
|
}
|
|
408
409
|
return i == v.len;
|
|
409
410
|
}
|
|
@@ -411,27 +412,40 @@ pub fn isNumericLiteral(v: []const u8) bool {
|
|
|
411
412
|
/// Infer type from a default value expression (simple cases)
|
|
412
413
|
pub fn inferTypeFromDefault(value: []const u8) []const u8 {
|
|
413
414
|
const v = std.mem.trim(u8, value, " \t\r\n");
|
|
414
|
-
if (
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
415
|
+
if (v.len == 0) return "unknown";
|
|
416
|
+
// Dispatch on first byte to avoid running multiple checks for cases that
|
|
417
|
+
// can't possibly match. e.g. a value starting with `[` is never `true`.
|
|
418
|
+
return switch (v[0]) {
|
|
419
|
+
't' => if (std.mem.eql(u8, v, "true")) "boolean" else "unknown",
|
|
420
|
+
'f' => if (std.mem.eql(u8, v, "false")) "boolean" else "unknown",
|
|
421
|
+
'[' => "unknown[]",
|
|
422
|
+
'{' => "Record<string, unknown>",
|
|
423
|
+
'\'', '"' => if (v.len >= 2 and v[v.len - 1] == v[0]) "string" else "unknown",
|
|
424
|
+
'-', '0'...'9' => if (isNumericLiteral(v)) "number" else "unknown",
|
|
425
|
+
else => "unknown",
|
|
426
|
+
};
|
|
420
427
|
}
|
|
421
428
|
|
|
422
429
|
/// Infer literal type from initializer value (for const-like / static readonly)
|
|
423
430
|
pub fn inferLiteralType(value: []const u8) []const u8 {
|
|
424
431
|
const v = std.mem.trim(u8, value, " \t\r\n");
|
|
425
|
-
if (
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
432
|
+
if (v.len == 0) return "unknown";
|
|
433
|
+
return switch (v[0]) {
|
|
434
|
+
't' => if (std.mem.eql(u8, v, "true")) v else "unknown",
|
|
435
|
+
'f' => if (std.mem.eql(u8, v, "false")) v else "unknown",
|
|
436
|
+
'\'', '"' => if (v.len >= 2 and v[v.len - 1] == v[0]) v else "unknown",
|
|
437
|
+
'-', '0'...'9' => if (isNumericLiteral(v)) v else "unknown",
|
|
438
|
+
else => "unknown",
|
|
439
|
+
};
|
|
429
440
|
}
|
|
430
441
|
|
|
431
442
|
/// Extract type from `as Type` assertion in initializer
|
|
432
443
|
pub fn extractAssertion(init_text: []const u8) ?[]const u8 {
|
|
433
444
|
if (ch.endsWith(init_text, "as const")) return null;
|
|
434
|
-
//
|
|
445
|
+
// Cheap pre-check: if ` as ` doesn't appear anywhere, skip the entire walk.
|
|
446
|
+
if (ch.indexOf(init_text, " as ", 0) == null) return null;
|
|
447
|
+
|
|
448
|
+
// Only find " as " at depth 0 (not inside nested brackets/braces/parens).
|
|
435
449
|
var last_as: ?usize = null;
|
|
436
450
|
var depth: isize = 0;
|
|
437
451
|
var in_str: u8 = 0;
|
|
@@ -453,7 +467,11 @@ pub fn extractAssertion(init_text: []const u8) ?[]const u8 {
|
|
|
453
467
|
depth += 1;
|
|
454
468
|
} else if (c == ')' or c == ']' or c == '}' or c == '>') {
|
|
455
469
|
depth -= 1;
|
|
456
|
-
} else if (depth == 0 and
|
|
470
|
+
} else if (depth == 0 and c == ' ' and i + 4 <= init_text.len and
|
|
471
|
+
init_text[i + 1] == 'a' and init_text[i + 2] == 's' and init_text[i + 3] == ' ')
|
|
472
|
+
{
|
|
473
|
+
// Inline 4-byte check is faster than std.mem.eql for a fixed-length
|
|
474
|
+
// needle, and gates the comparison on the leading space first.
|
|
457
475
|
last_as = i;
|
|
458
476
|
}
|
|
459
477
|
i += 1;
|
|
@@ -502,7 +520,16 @@ pub fn buildDtsParams(s: *Scanner, raw_params: []const u8) []const u8 {
|
|
|
502
520
|
}
|
|
503
521
|
}
|
|
504
522
|
if (can_passthrough and colons >= commas + 1) {
|
|
505
|
-
// Check for parameter modifiers
|
|
523
|
+
// Check for parameter modifiers. First-byte fast-fail: param
|
|
524
|
+
// modifiers start with one of `p`, `r`, or `o`. If `inner` doesn't
|
|
525
|
+
// even contain any of those bytes, skip the per-modifier loop
|
|
526
|
+
// entirely — saves ~5 full string scans on every parameter list
|
|
527
|
+
// that lacks modifiers (the common case).
|
|
528
|
+
const has_p = ch.indexOfChar(inner, 'p', 0) != null;
|
|
529
|
+
const has_r = ch.indexOfChar(inner, 'r', 0) != null;
|
|
530
|
+
const has_o = ch.indexOfChar(inner, 'o', 0) != null;
|
|
531
|
+
if (!has_p and !has_r and !has_o) return raw_params;
|
|
532
|
+
|
|
506
533
|
var has_modifier = false;
|
|
507
534
|
for (types.PARAM_MODIFIERS) |mod| {
|
|
508
535
|
if (ch.indexOf(inner, mod, 0)) |mod_idx| {
|
|
@@ -602,10 +629,15 @@ pub fn buildSingleDtsParam(s: *Scanner, raw: []const u8) []const u8 {
|
|
|
602
629
|
p = std.mem.trim(u8, p[di..], " \t\r\n");
|
|
603
630
|
}
|
|
604
631
|
|
|
605
|
-
// Strip parameter modifiers
|
|
632
|
+
// Strip parameter modifiers — fast-fail by first byte. The 5 PARAM_MODIFIERS
|
|
633
|
+
// ("public", "protected", "private", "readonly", "override") all start with
|
|
634
|
+
// 'p', 'r', or 'o', so we can reject most bytes in O(1) without iterating
|
|
635
|
+
// the full list.
|
|
606
636
|
var stripped = true;
|
|
607
|
-
while (stripped) {
|
|
637
|
+
while (stripped and p.len > 0) {
|
|
608
638
|
stripped = false;
|
|
639
|
+
const c0 = p[0];
|
|
640
|
+
if (c0 != 'p' and c0 != 'r' and c0 != 'o') break;
|
|
609
641
|
for (types.PARAM_MODIFIERS) |mod| {
|
|
610
642
|
if (p.len > mod.len and ch.startsWith(p, mod) and !ch.isIdentChar(p[mod.len])) {
|
|
611
643
|
p = std.mem.trim(u8, p[mod.len..], " \t\r\n");
|
|
@@ -695,14 +727,17 @@ pub fn buildSingleDtsParam(s: *Scanner, raw: []const u8) []const u8 {
|
|
|
695
727
|
}
|
|
696
728
|
const opt_marker: []const u8 = if (is_optional and !is_rest) "?" else "";
|
|
697
729
|
|
|
698
|
-
// Build result
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
730
|
+
// Build result — direct alloc (no ArrayList overhead)
|
|
731
|
+
const rest_prefix: []const u8 = if (is_rest) "..." else "";
|
|
732
|
+
const total = rest_prefix.len + name.len + opt_marker.len + 2 + param_type.len;
|
|
733
|
+
const result_buf = s.allocator.alloc(u8, total) catch return "unknown: unknown";
|
|
734
|
+
var rp: usize = 0;
|
|
735
|
+
if (is_rest) { @memcpy(result_buf[rp..][0..3], "..."); rp += 3; }
|
|
736
|
+
@memcpy(result_buf[rp..][0..name.len], name); rp += name.len;
|
|
737
|
+
@memcpy(result_buf[rp..][0..opt_marker.len], opt_marker); rp += opt_marker.len;
|
|
738
|
+
@memcpy(result_buf[rp..][0..2], ": "); rp += 2;
|
|
739
|
+
@memcpy(result_buf[rp..][0..param_type.len], param_type); rp += param_type.len;
|
|
740
|
+
return result_buf[0..rp];
|
|
706
741
|
}
|
|
707
742
|
|
|
708
743
|
/// Clean a destructured pattern by stripping default values and rest operators.
|
|
@@ -710,6 +745,14 @@ pub fn buildSingleDtsParam(s: *Scanner, raw: []const u8) []const u8 {
|
|
|
710
745
|
/// Also handles multiline patterns like:
|
|
711
746
|
/// "{\n name,\n headers = { ... },\n}" → "{\n name,\n headers,\n}"
|
|
712
747
|
fn cleanDestructuredPattern(alloc: std.mem.Allocator, pattern: []const u8) []const u8 {
|
|
748
|
+
// Fast path: if there's nothing to clean (no defaults, no rest operators,
|
|
749
|
+
// no newlines), the pattern is already in DTS-friendly form. The previous
|
|
750
|
+
// code always allocated and walked the entire pattern.
|
|
751
|
+
const has_eq = ch.indexOfChar(pattern, '=', 0) != null;
|
|
752
|
+
const has_dots = ch.indexOf(pattern, "...", 0) != null;
|
|
753
|
+
const has_nl = ch.indexOfChar(pattern, '\n', 0) != null;
|
|
754
|
+
if (!has_eq and !has_dots and !has_nl) return pattern;
|
|
755
|
+
|
|
713
756
|
var result = std.array_list.Managed(u8).init(alloc);
|
|
714
757
|
result.ensureTotalCapacity(pattern.len) catch {};
|
|
715
758
|
var i: usize = 0;
|
|
@@ -791,8 +834,9 @@ fn cleanDestructuredPattern(alloc: std.mem.Allocator, pattern: []const u8) []con
|
|
|
791
834
|
|
|
792
835
|
const cleaned = result.toOwnedSlice() catch return pattern;
|
|
793
836
|
|
|
794
|
-
// If the pattern contains newlines, try to collapse to single line if short enough
|
|
795
|
-
|
|
837
|
+
// If the pattern contains newlines, try to collapse to single line if short enough.
|
|
838
|
+
// indexOfChar is the SIMD-optimized single-byte search.
|
|
839
|
+
if (ch.indexOfChar(cleaned, '\n', 0) != null) {
|
|
796
840
|
// Collapse newlines and extra whitespace to single spaces
|
|
797
841
|
var collapsed = std.array_list.Managed(u8).init(alloc);
|
|
798
842
|
collapsed.ensureTotalCapacity(cleaned.len) catch {};
|
|
@@ -888,23 +932,30 @@ pub fn extractFunction(s: *Scanner, decl_start: usize, is_exported: bool, is_asy
|
|
|
888
932
|
const dts_params = buildDtsParams(s, raw_params);
|
|
889
933
|
const func_name = if (name.len > 0) name else "default";
|
|
890
934
|
|
|
891
|
-
// Build DTS text
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
935
|
+
// Build DTS text — single alloc + memcpy is cheaper than ArrayList init +
|
|
936
|
+
// multiple appendSlice + toOwnedSlice for the fixed-shape function header.
|
|
937
|
+
const export_prefix: []const u8 = if (is_exported) "export " else "";
|
|
938
|
+
const declare_kw = "declare function ";
|
|
939
|
+
const colon_sep = ": ";
|
|
940
|
+
const total = export_prefix.len + declare_kw.len + func_name.len + generics.len +
|
|
941
|
+
dts_params.len + colon_sep.len + return_type.len + 1; // +1 for ';'
|
|
942
|
+
const dts_text = blk: {
|
|
943
|
+
const buf = s.allocator.alloc(u8, total) catch break :blk @as([]const u8, "");
|
|
944
|
+
var tp: usize = 0;
|
|
945
|
+
@memcpy(buf[tp..][0..export_prefix.len], export_prefix); tp += export_prefix.len;
|
|
946
|
+
@memcpy(buf[tp..][0..declare_kw.len], declare_kw); tp += declare_kw.len;
|
|
947
|
+
@memcpy(buf[tp..][0..func_name.len], func_name); tp += func_name.len;
|
|
948
|
+
@memcpy(buf[tp..][0..generics.len], generics); tp += generics.len;
|
|
949
|
+
@memcpy(buf[tp..][0..dts_params.len], dts_params); tp += dts_params.len;
|
|
950
|
+
@memcpy(buf[tp..][0..colon_sep.len], colon_sep); tp += colon_sep.len;
|
|
951
|
+
@memcpy(buf[tp..][0..return_type.len], return_type); tp += return_type.len;
|
|
952
|
+
buf[tp] = ';';
|
|
953
|
+
break :blk @as([]const u8, buf);
|
|
954
|
+
};
|
|
904
955
|
const comments = extractLeadingComments(s, decl_start);
|
|
905
956
|
|
|
906
957
|
if (has_body) {
|
|
907
|
-
s.
|
|
958
|
+
s.putFuncBodyIndex(s.declarations.items.len);
|
|
908
959
|
}
|
|
909
960
|
|
|
910
961
|
return .{
|
|
@@ -987,6 +1038,55 @@ pub fn extractVariable(s: *Scanner, decl_start: usize, kind: []const u8, is_expo
|
|
|
987
1038
|
const init_start = s.pos;
|
|
988
1039
|
var depth: isize = 0;
|
|
989
1040
|
while (s.pos < s.len) {
|
|
1041
|
+
// SIMD fast-skip: bulk-skip bytes that can't be structural
|
|
1042
|
+
if (depth > 0) {
|
|
1043
|
+
while (s.pos + 16 <= s.len) {
|
|
1044
|
+
const chunk: @Vector(16, u8) = s.source[s.pos..][0..16].*;
|
|
1045
|
+
const interesting = (chunk == @as(@Vector(16, u8), @splat(ch.CH_LPAREN))) |
|
|
1046
|
+
(chunk == @as(@Vector(16, u8), @splat(ch.CH_RPAREN))) |
|
|
1047
|
+
(chunk == @as(@Vector(16, u8), @splat(ch.CH_LBRACE))) |
|
|
1048
|
+
(chunk == @as(@Vector(16, u8), @splat(ch.CH_RBRACE))) |
|
|
1049
|
+
(chunk == @as(@Vector(16, u8), @splat(ch.CH_LBRACKET))) |
|
|
1050
|
+
(chunk == @as(@Vector(16, u8), @splat(ch.CH_RBRACKET))) |
|
|
1051
|
+
(chunk == @as(@Vector(16, u8), @splat(ch.CH_LANGLE))) |
|
|
1052
|
+
(chunk == @as(@Vector(16, u8), @splat(ch.CH_RANGLE))) |
|
|
1053
|
+
(chunk == @as(@Vector(16, u8), @splat(ch.CH_SQUOTE))) |
|
|
1054
|
+
(chunk == @as(@Vector(16, u8), @splat(ch.CH_DQUOTE))) |
|
|
1055
|
+
(chunk == @as(@Vector(16, u8), @splat(ch.CH_BACKTICK))) |
|
|
1056
|
+
(chunk == @as(@Vector(16, u8), @splat(ch.CH_SLASH)));
|
|
1057
|
+
if (!@reduce(.Or, interesting)) {
|
|
1058
|
+
s.pos += 16;
|
|
1059
|
+
} else {
|
|
1060
|
+
break;
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
} else {
|
|
1064
|
+
while (s.pos + 16 <= s.len) {
|
|
1065
|
+
const chunk: @Vector(16, u8) = s.source[s.pos..][0..16].*;
|
|
1066
|
+
const interesting = (chunk == @as(@Vector(16, u8), @splat(ch.CH_LPAREN))) |
|
|
1067
|
+
(chunk == @as(@Vector(16, u8), @splat(ch.CH_RPAREN))) |
|
|
1068
|
+
(chunk == @as(@Vector(16, u8), @splat(ch.CH_LBRACE))) |
|
|
1069
|
+
(chunk == @as(@Vector(16, u8), @splat(ch.CH_RBRACE))) |
|
|
1070
|
+
(chunk == @as(@Vector(16, u8), @splat(ch.CH_LBRACKET))) |
|
|
1071
|
+
(chunk == @as(@Vector(16, u8), @splat(ch.CH_RBRACKET))) |
|
|
1072
|
+
(chunk == @as(@Vector(16, u8), @splat(ch.CH_LANGLE))) |
|
|
1073
|
+
(chunk == @as(@Vector(16, u8), @splat(ch.CH_RANGLE))) |
|
|
1074
|
+
(chunk == @as(@Vector(16, u8), @splat(ch.CH_SQUOTE))) |
|
|
1075
|
+
(chunk == @as(@Vector(16, u8), @splat(ch.CH_DQUOTE))) |
|
|
1076
|
+
(chunk == @as(@Vector(16, u8), @splat(ch.CH_BACKTICK))) |
|
|
1077
|
+
(chunk == @as(@Vector(16, u8), @splat(ch.CH_SLASH))) |
|
|
1078
|
+
(chunk == @as(@Vector(16, u8), @splat(ch.CH_SEMI))) |
|
|
1079
|
+
(chunk == @as(@Vector(16, u8), @splat(ch.CH_COMMA))) |
|
|
1080
|
+
(chunk == @as(@Vector(16, u8), @splat(ch.CH_LF))) |
|
|
1081
|
+
(chunk == @as(@Vector(16, u8), @splat(ch.CH_CR)));
|
|
1082
|
+
if (!@reduce(.Or, interesting)) {
|
|
1083
|
+
s.pos += 16;
|
|
1084
|
+
} else {
|
|
1085
|
+
break;
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
if (s.pos >= s.len) break;
|
|
990
1090
|
if (s.skipNonCode()) continue;
|
|
991
1091
|
const ic = s.source[s.pos];
|
|
992
1092
|
if (ic == ch.CH_LPAREN or ic == ch.CH_LBRACE or ic == ch.CH_LBRACKET or ic == ch.CH_LANGLE) {
|
|
@@ -1028,17 +1128,23 @@ pub fn extractVariable(s: *Scanner, decl_start: usize, kind: []const u8, is_expo
|
|
|
1028
1128
|
const comments = extractLeadingComments(s, decl_start);
|
|
1029
1129
|
const final_type = if (type_annotation.len > 0) type_annotation else "unknown";
|
|
1030
1130
|
|
|
1031
|
-
// Build DTS text
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1131
|
+
// Build DTS text — direct alloc (no ArrayList overhead).
|
|
1132
|
+
const export_prefix: []const u8 = if (is_exported) "export " else "";
|
|
1133
|
+
const declare_kw = "declare ";
|
|
1134
|
+
const total = export_prefix.len + declare_kw.len + kind.len + 1 + name.len + 2 + final_type.len + 1;
|
|
1135
|
+
const text_buf = blk: {
|
|
1136
|
+
const buf = s.allocator.alloc(u8, total) catch break :blk @as([]const u8, "");
|
|
1137
|
+
var tp: usize = 0;
|
|
1138
|
+
@memcpy(buf[tp..][0..export_prefix.len], export_prefix); tp += export_prefix.len;
|
|
1139
|
+
@memcpy(buf[tp..][0..declare_kw.len], declare_kw); tp += declare_kw.len;
|
|
1140
|
+
@memcpy(buf[tp..][0..kind.len], kind); tp += kind.len;
|
|
1141
|
+
buf[tp] = ' '; tp += 1;
|
|
1142
|
+
@memcpy(buf[tp..][0..name.len], name); tp += name.len;
|
|
1143
|
+
@memcpy(buf[tp..][0..2], ": "); tp += 2;
|
|
1144
|
+
@memcpy(buf[tp..][0..final_type.len], final_type); tp += final_type.len;
|
|
1145
|
+
buf[tp] = ';';
|
|
1146
|
+
break :blk @as([]const u8, buf);
|
|
1147
|
+
};
|
|
1042
1148
|
|
|
1043
1149
|
// Store the variable kind in modifiers
|
|
1044
1150
|
const mods = s.allocator.alloc([]const u8, 1) catch null;
|
|
@@ -1049,7 +1155,7 @@ pub fn extractVariable(s: *Scanner, decl_start: usize, kind: []const u8, is_expo
|
|
|
1049
1155
|
results.append(.{
|
|
1050
1156
|
.kind = .variable_decl,
|
|
1051
1157
|
.name = name,
|
|
1052
|
-
.text =
|
|
1158
|
+
.text = text_buf,
|
|
1053
1159
|
.is_exported = is_exported,
|
|
1054
1160
|
.modifiers = mods,
|
|
1055
1161
|
.type_annotation = type_annotation,
|
|
@@ -1102,26 +1208,33 @@ pub fn extractInterface(s: *Scanner, decl_start: usize, is_exported: bool) Decla
|
|
|
1102
1208
|
const raw_body = if (s.pos < s.len and s.source[s.pos] == ch.CH_LBRACE) s.extractBraceBlock() else "{}";
|
|
1103
1209
|
const body = cleanBraceBlock(s, raw_body);
|
|
1104
1210
|
|
|
1105
|
-
// Build DTS text
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1211
|
+
// Build DTS text — direct alloc + memcpy for better cache behavior on the
|
|
1212
|
+
// many-interfaces hot path.
|
|
1213
|
+
const export_prefix: []const u8 = if (is_exported) "export " else "";
|
|
1214
|
+
const declare_kw = "declare interface ";
|
|
1215
|
+
const extends_kw: []const u8 = if (extends_clause.len > 0) " extends " else "";
|
|
1216
|
+
const total = export_prefix.len + declare_kw.len + name.len + generics.len +
|
|
1217
|
+
extends_kw.len + extends_clause.len + 1 + body.len;
|
|
1218
|
+
const text = blk: {
|
|
1219
|
+
const buf = s.allocator.alloc(u8, total) catch break :blk @as([]const u8, "");
|
|
1220
|
+
var tp: usize = 0;
|
|
1221
|
+
@memcpy(buf[tp..][0..export_prefix.len], export_prefix); tp += export_prefix.len;
|
|
1222
|
+
@memcpy(buf[tp..][0..declare_kw.len], declare_kw); tp += declare_kw.len;
|
|
1223
|
+
@memcpy(buf[tp..][0..name.len], name); tp += name.len;
|
|
1224
|
+
@memcpy(buf[tp..][0..generics.len], generics); tp += generics.len;
|
|
1225
|
+
@memcpy(buf[tp..][0..extends_kw.len], extends_kw); tp += extends_kw.len;
|
|
1226
|
+
@memcpy(buf[tp..][0..extends_clause.len], extends_clause); tp += extends_clause.len;
|
|
1227
|
+
buf[tp] = ' '; tp += 1;
|
|
1228
|
+
@memcpy(buf[tp..][0..body.len], body); tp += body.len;
|
|
1229
|
+
break :blk @as([]const u8, buf);
|
|
1230
|
+
};
|
|
1118
1231
|
|
|
1119
1232
|
const comments = extractLeadingComments(s, decl_start);
|
|
1120
1233
|
|
|
1121
1234
|
return .{
|
|
1122
1235
|
.kind = .interface_decl,
|
|
1123
1236
|
.name = name,
|
|
1124
|
-
.text = text
|
|
1237
|
+
.text = text,
|
|
1125
1238
|
.is_exported = is_exported,
|
|
1126
1239
|
.extends_clause = extends_clause,
|
|
1127
1240
|
.generics = generics,
|
|
@@ -1167,21 +1280,29 @@ pub fn extractTypeAlias(s: *Scanner, decl_start: usize, is_exported: bool) Decla
|
|
|
1167
1280
|
const type_body = s.sliceTrimmed(type_start, s.pos);
|
|
1168
1281
|
if (s.pos < s.len and s.source[s.pos] == ch.CH_SEMI) s.pos += 1;
|
|
1169
1282
|
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
text
|
|
1176
|
-
|
|
1177
|
-
|
|
1283
|
+
// Direct alloc — fixed-shape "[export ]type name<G> = body".
|
|
1284
|
+
const export_prefix: []const u8 = if (is_exported) "export " else "";
|
|
1285
|
+
const type_kw = "type ";
|
|
1286
|
+
const eq_sep = " = ";
|
|
1287
|
+
const total = export_prefix.len + type_kw.len + name.len + generics.len + eq_sep.len + type_body.len;
|
|
1288
|
+
const text = blk: {
|
|
1289
|
+
const buf = s.allocator.alloc(u8, total) catch break :blk @as([]const u8, "");
|
|
1290
|
+
var tp: usize = 0;
|
|
1291
|
+
@memcpy(buf[tp..][0..export_prefix.len], export_prefix); tp += export_prefix.len;
|
|
1292
|
+
@memcpy(buf[tp..][0..type_kw.len], type_kw); tp += type_kw.len;
|
|
1293
|
+
@memcpy(buf[tp..][0..name.len], name); tp += name.len;
|
|
1294
|
+
@memcpy(buf[tp..][0..generics.len], generics); tp += generics.len;
|
|
1295
|
+
@memcpy(buf[tp..][0..eq_sep.len], eq_sep); tp += eq_sep.len;
|
|
1296
|
+
@memcpy(buf[tp..][0..type_body.len], type_body); tp += type_body.len;
|
|
1297
|
+
break :blk @as([]const u8, buf);
|
|
1298
|
+
};
|
|
1178
1299
|
|
|
1179
1300
|
const comments = extractLeadingComments(s, decl_start);
|
|
1180
1301
|
|
|
1181
1302
|
return .{
|
|
1182
1303
|
.kind = .type_decl,
|
|
1183
1304
|
.name = name,
|
|
1184
|
-
.text = text
|
|
1305
|
+
.text = text,
|
|
1185
1306
|
.is_exported = is_exported,
|
|
1186
1307
|
.generics = generics,
|
|
1187
1308
|
.leading_comments = comments,
|
|
@@ -1289,32 +1410,40 @@ pub fn extractClass(s: *Scanner, decl_start: usize, is_exported: bool, is_abstra
|
|
|
1289
1410
|
s.skipWhitespaceAndComments();
|
|
1290
1411
|
const class_body = buildClassBodyDts(s);
|
|
1291
1412
|
|
|
1292
|
-
// Build DTS text
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
if (
|
|
1296
|
-
|
|
1297
|
-
if (
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1413
|
+
// Build DTS text — direct alloc + memcpy.
|
|
1414
|
+
const export_prefix: []const u8 = if (is_exported) "export " else "";
|
|
1415
|
+
const declare_kw = "declare ";
|
|
1416
|
+
const abstract_kw: []const u8 = if (is_abstract) "abstract " else "";
|
|
1417
|
+
const class_kw = "class ";
|
|
1418
|
+
const extends_sep: []const u8 = if (extends_clause.len > 0) " extends " else "";
|
|
1419
|
+
const implements_sep: []const u8 = if (implements_text.len > 0) " implements " else "";
|
|
1420
|
+
const total = export_prefix.len + declare_kw.len + abstract_kw.len + class_kw.len +
|
|
1421
|
+
name.len + generics.len + extends_sep.len + extends_clause.len +
|
|
1422
|
+
implements_sep.len + implements_text.len + 1 + class_body.len;
|
|
1423
|
+
const text = blk: {
|
|
1424
|
+
const buf = s.allocator.alloc(u8, total) catch break :blk @as([]const u8, "");
|
|
1425
|
+
var tp: usize = 0;
|
|
1426
|
+
@memcpy(buf[tp..][0..export_prefix.len], export_prefix); tp += export_prefix.len;
|
|
1427
|
+
@memcpy(buf[tp..][0..declare_kw.len], declare_kw); tp += declare_kw.len;
|
|
1428
|
+
@memcpy(buf[tp..][0..abstract_kw.len], abstract_kw); tp += abstract_kw.len;
|
|
1429
|
+
@memcpy(buf[tp..][0..class_kw.len], class_kw); tp += class_kw.len;
|
|
1430
|
+
@memcpy(buf[tp..][0..name.len], name); tp += name.len;
|
|
1431
|
+
@memcpy(buf[tp..][0..generics.len], generics); tp += generics.len;
|
|
1432
|
+
@memcpy(buf[tp..][0..extends_sep.len], extends_sep); tp += extends_sep.len;
|
|
1433
|
+
@memcpy(buf[tp..][0..extends_clause.len], extends_clause); tp += extends_clause.len;
|
|
1434
|
+
@memcpy(buf[tp..][0..implements_sep.len], implements_sep); tp += implements_sep.len;
|
|
1435
|
+
@memcpy(buf[tp..][0..implements_text.len], implements_text); tp += implements_text.len;
|
|
1436
|
+
buf[tp] = ' '; tp += 1;
|
|
1437
|
+
@memcpy(buf[tp..][0..class_body.len], class_body); tp += class_body.len;
|
|
1438
|
+
break :blk @as([]const u8, buf);
|
|
1439
|
+
};
|
|
1311
1440
|
|
|
1312
1441
|
const comments = extractLeadingComments(s, decl_start);
|
|
1313
1442
|
|
|
1314
1443
|
return .{
|
|
1315
1444
|
.kind = .class_decl,
|
|
1316
1445
|
.name = name,
|
|
1317
|
-
.text = text
|
|
1446
|
+
.text = text,
|
|
1318
1447
|
.is_exported = is_exported,
|
|
1319
1448
|
.extends_clause = extends_clause,
|
|
1320
1449
|
.generics = generics,
|
|
@@ -1362,33 +1491,42 @@ fn buildClassBodyDts(s: *Scanner) []const u8 {
|
|
|
1362
1491
|
|
|
1363
1492
|
while (true) {
|
|
1364
1493
|
s.skipWhitespaceAndComments();
|
|
1365
|
-
if (s.
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1494
|
+
if (s.pos >= s.len) break;
|
|
1495
|
+
// First-byte dispatch — most class members have at most one or two
|
|
1496
|
+
// modifiers. The previous code ran up to 10 matchWord calls per
|
|
1497
|
+
// member iteration; this lets us reject most bytes immediately.
|
|
1498
|
+
const mc = s.source[s.pos];
|
|
1499
|
+
switch (mc) {
|
|
1500
|
+
'p' => {
|
|
1501
|
+
if (s.matchWord("private")) { is_private = true; s.pos += 7; }
|
|
1502
|
+
else if (s.matchWord("protected")) { is_protected = true; s.pos += 9; }
|
|
1503
|
+
else if (s.matchWord("public")) { s.pos += 6; }
|
|
1504
|
+
else break;
|
|
1505
|
+
},
|
|
1506
|
+
's' => {
|
|
1507
|
+
if (s.matchWord("static")) { is_static = true; s.pos += 6; }
|
|
1508
|
+
else break;
|
|
1509
|
+
},
|
|
1510
|
+
'a' => {
|
|
1511
|
+
if (s.matchWord("abstract")) { is_abstract = true; s.pos += 8; }
|
|
1512
|
+
else if (s.matchWord("accessor")) { s.pos += 8; }
|
|
1513
|
+
else if (s.matchWord("async")) { is_async = true; s.pos += 5; }
|
|
1514
|
+
else break;
|
|
1515
|
+
},
|
|
1516
|
+
'r' => {
|
|
1517
|
+
if (s.matchWord("readonly")) { is_readonly = true; s.pos += 8; }
|
|
1518
|
+
else break;
|
|
1519
|
+
},
|
|
1520
|
+
'o' => {
|
|
1521
|
+
if (s.matchWord("override")) { s.pos += 8; }
|
|
1522
|
+
else break;
|
|
1523
|
+
},
|
|
1524
|
+
'd' => {
|
|
1525
|
+
if (s.matchWord("declare")) { s.pos += 7; }
|
|
1526
|
+
else break;
|
|
1527
|
+
},
|
|
1528
|
+
else => break,
|
|
1529
|
+
}
|
|
1392
1530
|
}
|
|
1393
1531
|
|
|
1394
1532
|
s.skipWhitespaceAndComments();
|
|
@@ -1402,14 +1540,24 @@ fn buildClassBodyDts(s: *Scanner) []const u8 {
|
|
|
1402
1540
|
continue;
|
|
1403
1541
|
}
|
|
1404
1542
|
|
|
1405
|
-
// Build modifier prefix
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1543
|
+
// Build modifier prefix — direct alloc + memcpy beats ArrayList for the
|
|
1544
|
+
// ≤4 known fixed-length tokens. Saves an ArrayList init + toOwnedSlice
|
|
1545
|
+
// shrink per class member.
|
|
1546
|
+
const proto_len: usize = 2 +
|
|
1547
|
+
@as(usize, if (is_protected) "protected ".len else 0) +
|
|
1548
|
+
@as(usize, if (is_static) "static ".len else 0) +
|
|
1549
|
+
@as(usize, if (is_abstract) "abstract ".len else 0) +
|
|
1550
|
+
@as(usize, if (is_readonly) "readonly ".len else 0);
|
|
1551
|
+
const prefix = blk: {
|
|
1552
|
+
const pbuf = s.allocator.alloc(u8, proto_len) catch break :blk @as([]const u8, " ");
|
|
1553
|
+
pbuf[0] = ' '; pbuf[1] = ' ';
|
|
1554
|
+
var pp: usize = 2;
|
|
1555
|
+
if (is_protected) { @memcpy(pbuf[pp..][0.."protected ".len], "protected "); pp += "protected ".len; }
|
|
1556
|
+
if (is_static) { @memcpy(pbuf[pp..][0.."static ".len], "static "); pp += "static ".len; }
|
|
1557
|
+
if (is_abstract) { @memcpy(pbuf[pp..][0.."abstract ".len], "abstract "); pp += "abstract ".len; }
|
|
1558
|
+
if (is_readonly) { @memcpy(pbuf[pp..][0.."readonly ".len], "readonly "); pp += "readonly ".len; }
|
|
1559
|
+
break :blk @as([]const u8, pbuf);
|
|
1560
|
+
};
|
|
1413
1561
|
|
|
1414
1562
|
// Detect member type
|
|
1415
1563
|
if (s.matchWord("constructor")) {
|
|
@@ -1429,11 +1577,14 @@ fn buildClassBodyDts(s: *Scanner) []const u8 {
|
|
|
1429
1577
|
}
|
|
1430
1578
|
|
|
1431
1579
|
const dts_params = buildDtsParams(s, raw_params);
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1580
|
+
// Direct alloc — fixed-shape " constructor<params>;".
|
|
1581
|
+
const ctor_prefix = " constructor";
|
|
1582
|
+
const ctor_total = ctor_prefix.len + dts_params.len + 1;
|
|
1583
|
+
const ctor_buf = s.allocator.alloc(u8, ctor_total) catch break;
|
|
1584
|
+
@memcpy(ctor_buf[0..ctor_prefix.len], ctor_prefix);
|
|
1585
|
+
@memcpy(ctor_buf[ctor_prefix.len..][0..dts_params.len], dts_params);
|
|
1586
|
+
ctor_buf[ctor_total - 1] = ';';
|
|
1587
|
+
members.append(ctor_buf) catch {};
|
|
1437
1588
|
} else if (s.matchWord("get") and isAccessorFollowed(s)) {
|
|
1438
1589
|
s.pos += 3;
|
|
1439
1590
|
s.skipWhitespaceAndComments();
|
|
@@ -1453,14 +1604,19 @@ fn buildClassBodyDts(s: *Scanner) []const u8 {
|
|
|
1453
1604
|
} else if (s.pos < s.len and s.source[s.pos] == ch.CH_SEMI) {
|
|
1454
1605
|
s.pos += 1;
|
|
1455
1606
|
}
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1607
|
+
// Direct alloc — fixed shape "<prefix>get <name>(): <ret>;".
|
|
1608
|
+
const get_kw = "get ";
|
|
1609
|
+
const get_sep = "(): ";
|
|
1610
|
+
const get_total = prefix.len + get_kw.len + member_name.len + get_sep.len + ret_type.len + 1;
|
|
1611
|
+
const get_buf = s.allocator.alloc(u8, get_total) catch break;
|
|
1612
|
+
var gp: usize = 0;
|
|
1613
|
+
@memcpy(get_buf[gp..][0..prefix.len], prefix); gp += prefix.len;
|
|
1614
|
+
@memcpy(get_buf[gp..][0..get_kw.len], get_kw); gp += get_kw.len;
|
|
1615
|
+
@memcpy(get_buf[gp..][0..member_name.len], member_name); gp += member_name.len;
|
|
1616
|
+
@memcpy(get_buf[gp..][0..get_sep.len], get_sep); gp += get_sep.len;
|
|
1617
|
+
@memcpy(get_buf[gp..][0..ret_type.len], ret_type); gp += ret_type.len;
|
|
1618
|
+
get_buf[gp] = ';';
|
|
1619
|
+
members.append(get_buf) catch {};
|
|
1464
1620
|
} else if (s.matchWord("set") and isAccessorFollowed(s)) {
|
|
1465
1621
|
s.pos += 3;
|
|
1466
1622
|
s.skipWhitespaceAndComments();
|
|
@@ -1478,13 +1634,17 @@ fn buildClassBodyDts(s: *Scanner) []const u8 {
|
|
|
1478
1634
|
s.pos += 1;
|
|
1479
1635
|
}
|
|
1480
1636
|
const dts_params = buildDtsParams(s, raw_params);
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1637
|
+
// Direct alloc — fixed shape "<prefix>set <name><params>;".
|
|
1638
|
+
const set_kw = "set ";
|
|
1639
|
+
const set_total = prefix.len + set_kw.len + member_name.len + dts_params.len + 1;
|
|
1640
|
+
const set_buf = s.allocator.alloc(u8, set_total) catch break;
|
|
1641
|
+
var sp: usize = 0;
|
|
1642
|
+
@memcpy(set_buf[sp..][0..prefix.len], prefix); sp += prefix.len;
|
|
1643
|
+
@memcpy(set_buf[sp..][0..set_kw.len], set_kw); sp += set_kw.len;
|
|
1644
|
+
@memcpy(set_buf[sp..][0..member_name.len], member_name); sp += member_name.len;
|
|
1645
|
+
@memcpy(set_buf[sp..][0..dts_params.len], dts_params); sp += dts_params.len;
|
|
1646
|
+
set_buf[sp] = ';';
|
|
1647
|
+
members.append(set_buf) catch {};
|
|
1488
1648
|
} else {
|
|
1489
1649
|
// Regular method or property
|
|
1490
1650
|
const is_generator = s.source[s.pos] == ch.CH_STAR;
|
|
@@ -1503,22 +1663,31 @@ fn buildClassBodyDts(s: *Scanner) []const u8 {
|
|
|
1503
1663
|
|
|
1504
1664
|
if (members.items.len == 0) return "{}";
|
|
1505
1665
|
|
|
1506
|
-
// Join members —
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
var
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1666
|
+
// Join members — direct alloc + memcpy. Total = "{\n" + members joined by
|
|
1667
|
+
// '\n' + "\n}". Previously used ArrayList with per-member appendSlice + a
|
|
1668
|
+
// boundary conditional inside the loop.
|
|
1669
|
+
var total_len: usize = 4; // "{\n" (2) + "\n}" (2)
|
|
1670
|
+
for (members.items) |m| total_len += m.len;
|
|
1671
|
+
total_len += members.items.len - 1; // newlines between
|
|
1672
|
+
const buf = s.allocator.alloc(u8, total_len) catch return "{}";
|
|
1673
|
+
buf[0] = '{'; buf[1] = '\n';
|
|
1674
|
+
var rp: usize = 2;
|
|
1675
|
+
@memcpy(buf[rp..][0..members.items[0].len], members.items[0]);
|
|
1676
|
+
rp += members.items[0].len;
|
|
1677
|
+
for (members.items[1..]) |m| {
|
|
1678
|
+
buf[rp] = '\n'; rp += 1;
|
|
1679
|
+
@memcpy(buf[rp..][0..m.len], m);
|
|
1680
|
+
rp += m.len;
|
|
1515
1681
|
}
|
|
1516
|
-
|
|
1517
|
-
|
|
1682
|
+
buf[rp] = '\n'; rp += 1;
|
|
1683
|
+
buf[rp] = '}'; rp += 1;
|
|
1684
|
+
return buf[0..rp];
|
|
1518
1685
|
}
|
|
1519
1686
|
|
|
1520
1687
|
fn isAccessorFollowed(s: *const Scanner) bool {
|
|
1521
|
-
|
|
1688
|
+
// Both "get" and "set" are 3 chars — the caller already verified one matched.
|
|
1689
|
+
// Inspect the byte at +3 directly instead of calling matchWord again.
|
|
1690
|
+
const next_after = s.peekAfterWord("get"); // length is what matters; "get".len == "set".len
|
|
1522
1691
|
return next_after != 0 and (ch.isIdentStart(next_after) or next_after == ch.CH_LBRACKET or next_after == ch.CH_HASH);
|
|
1523
1692
|
}
|
|
1524
1693
|
|
|
@@ -1584,17 +1753,23 @@ fn handleMethodOrPropertyAfterName(s: *Scanner, member_name: []const u8, mod_pre
|
|
|
1584
1753
|
const opt_mark: []const u8 = if (is_optional) "?" else "";
|
|
1585
1754
|
const gen_text: []const u8 = if (is_generator) "*" else "";
|
|
1586
1755
|
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
member.
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1756
|
+
// Direct alloc — class methods are emitted in tight loops, so the
|
|
1757
|
+
// ArrayList overhead added up to a non-trivial fraction of class
|
|
1758
|
+
// emission time on member-heavy classes.
|
|
1759
|
+
const meth_total = mod_prefix.len + gen_text.len + member_name.len + opt_mark.len +
|
|
1760
|
+
generics.len + dts_params.len + 2 + ret_type.len + 1;
|
|
1761
|
+
const meth_buf = s.allocator.alloc(u8, meth_total) catch return;
|
|
1762
|
+
var mp: usize = 0;
|
|
1763
|
+
@memcpy(meth_buf[mp..][0..mod_prefix.len], mod_prefix); mp += mod_prefix.len;
|
|
1764
|
+
@memcpy(meth_buf[mp..][0..gen_text.len], gen_text); mp += gen_text.len;
|
|
1765
|
+
@memcpy(meth_buf[mp..][0..member_name.len], member_name); mp += member_name.len;
|
|
1766
|
+
@memcpy(meth_buf[mp..][0..opt_mark.len], opt_mark); mp += opt_mark.len;
|
|
1767
|
+
@memcpy(meth_buf[mp..][0..generics.len], generics); mp += generics.len;
|
|
1768
|
+
@memcpy(meth_buf[mp..][0..dts_params.len], dts_params); mp += dts_params.len;
|
|
1769
|
+
@memcpy(meth_buf[mp..][0..2], ": "); mp += 2;
|
|
1770
|
+
@memcpy(meth_buf[mp..][0..ret_type.len], ret_type); mp += ret_type.len;
|
|
1771
|
+
meth_buf[mp] = ';';
|
|
1772
|
+
members.append(meth_buf) catch {};
|
|
1598
1773
|
} else if (next_ch == ch.CH_COLON or next_ch == ch.CH_EQUAL or next_ch == ch.CH_SEMI or next_ch == ch.CH_RBRACE or next_ch == ch.CH_LF or next_ch == ch.CH_CR) {
|
|
1599
1774
|
// Property
|
|
1600
1775
|
var prop_type: []const u8 = "";
|
|
@@ -1661,14 +1836,17 @@ fn handleMethodOrPropertyAfterName(s: *Scanner, member_name: []const u8, mod_pre
|
|
|
1661
1836
|
}
|
|
1662
1837
|
|
|
1663
1838
|
const opt_mark: []const u8 = if (is_optional) "?" else "";
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1839
|
+
// Direct alloc — fixed shape "<prefix><name>[?]: <type>;".
|
|
1840
|
+
const prop_total = mod_prefix.len + member_name.len + opt_mark.len + 2 + prop_type.len + 1;
|
|
1841
|
+
const prop_buf = s.allocator.alloc(u8, prop_total) catch return;
|
|
1842
|
+
var pp: usize = 0;
|
|
1843
|
+
@memcpy(prop_buf[pp..][0..mod_prefix.len], mod_prefix); pp += mod_prefix.len;
|
|
1844
|
+
@memcpy(prop_buf[pp..][0..member_name.len], member_name); pp += member_name.len;
|
|
1845
|
+
@memcpy(prop_buf[pp..][0..opt_mark.len], opt_mark); pp += opt_mark.len;
|
|
1846
|
+
@memcpy(prop_buf[pp..][0..2], ": "); pp += 2;
|
|
1847
|
+
@memcpy(prop_buf[pp..][0..prop_type.len], prop_type); pp += prop_type.len;
|
|
1848
|
+
prop_buf[pp] = ';';
|
|
1849
|
+
members.append(prop_buf) catch {};
|
|
1672
1850
|
} else {
|
|
1673
1851
|
s.skipClassMember();
|
|
1674
1852
|
}
|
|
@@ -1717,27 +1895,31 @@ fn extractParamProperties(s: *Scanner, raw_params: []const u8, members: *std.arr
|
|
|
1717
1895
|
params.append(std.mem.trim(u8, inner[start..], " \t\r\n")) catch {};
|
|
1718
1896
|
|
|
1719
1897
|
for (params.items) |param| {
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1898
|
+
if (param.len == 0) continue;
|
|
1899
|
+
// First-byte fast-fail — only constructor params starting with 'p' or
|
|
1900
|
+
// 'r' can carry an access modifier, so reject everything else without
|
|
1901
|
+
// running the four startsWith / contains scans.
|
|
1902
|
+
const first = param[0];
|
|
1903
|
+
if (first != 'p' and first != 'r') continue;
|
|
1904
|
+
|
|
1905
|
+
const has_public = first == 'p' and (ch.startsWith(param, "public ") or ch.startsWith(param, "public\t"));
|
|
1906
|
+
const has_protected = first == 'p' and (ch.startsWith(param, "protected ") or ch.startsWith(param, "protected\t"));
|
|
1907
|
+
const has_private = first == 'p' and (ch.startsWith(param, "private ") or ch.startsWith(param, "private\t"));
|
|
1908
|
+
const has_readonly = (first == 'r' and (ch.startsWith(param, "readonly ") or ch.startsWith(param, "readonly\t"))) or ch.contains(param, " readonly ");
|
|
1724
1909
|
|
|
1725
1910
|
if (!has_public and !has_protected and !has_private and !has_readonly) continue;
|
|
1726
1911
|
if (has_private) continue;
|
|
1727
1912
|
|
|
1728
1913
|
var p = param;
|
|
1729
|
-
var mods = std.array_list.Managed([]const u8).init(s.allocator);
|
|
1730
1914
|
if (has_public) {
|
|
1731
1915
|
var si: usize = 6;
|
|
1732
1916
|
while (si < p.len and ch.isWhitespace(p[si])) si += 1;
|
|
1733
1917
|
p = p[si..];
|
|
1734
|
-
mods.append("public") catch {};
|
|
1735
1918
|
}
|
|
1736
1919
|
if (has_protected) {
|
|
1737
1920
|
var si: usize = 9;
|
|
1738
1921
|
while (si < p.len and ch.isWhitespace(p[si])) si += 1;
|
|
1739
1922
|
p = p[si..];
|
|
1740
|
-
mods.append("protected") catch {};
|
|
1741
1923
|
}
|
|
1742
1924
|
if (has_readonly) {
|
|
1743
1925
|
if (ch.indexOf(p, "readonly ", 0)) |ri| {
|
|
@@ -1751,24 +1933,45 @@ fn extractParamProperties(s: *Scanner, raw_params: []const u8, members: *std.arr
|
|
|
1751
1933
|
@memcpy(new_p[before.len..], after);
|
|
1752
1934
|
p = new_p;
|
|
1753
1935
|
}
|
|
1754
|
-
mods.append("readonly") catch {};
|
|
1755
|
-
}
|
|
1756
|
-
|
|
1757
|
-
// Build mod text
|
|
1758
|
-
var mod_text = std.array_list.Managed(u8).init(s.allocator);
|
|
1759
|
-
for (mods.items, 0..) |m, i| {
|
|
1760
|
-
mod_text.appendSlice(m) catch {};
|
|
1761
|
-
if (i < mods.items.len - 1) mod_text.append(' ') catch {};
|
|
1762
1936
|
}
|
|
1763
|
-
if (mods.items.len > 0) mod_text.append(' ') catch {};
|
|
1764
1937
|
|
|
1938
|
+
// Compute total mod_text length up front — at most 3 modifier tokens
|
|
1939
|
+
// are possible (public/protected, readonly) plus separators. Direct
|
|
1940
|
+
// alloc avoids the ArrayList init + per-modifier append + toOwnedSlice
|
|
1941
|
+
// cost the previous code paid for every parameter property.
|
|
1765
1942
|
const dts_param = buildSingleDtsParam(s, p);
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1943
|
+
const pub_token: []const u8 = if (has_public) "public" else "";
|
|
1944
|
+
const prot_token: []const u8 = if (has_protected) "protected" else "";
|
|
1945
|
+
const ro_token: []const u8 = if (has_readonly) "readonly" else "";
|
|
1946
|
+
const num_mods: usize = @as(usize, if (has_public) 1 else 0) +
|
|
1947
|
+
@as(usize, if (has_protected) 1 else 0) +
|
|
1948
|
+
@as(usize, if (has_readonly) 1 else 0);
|
|
1949
|
+
const mod_text_len = pub_token.len + prot_token.len + ro_token.len +
|
|
1950
|
+
(if (num_mods > 0) num_mods else 0); // separators + trailing space when any modifier present
|
|
1951
|
+
|
|
1952
|
+
const m_total = 2 + mod_text_len + dts_param.len + 1;
|
|
1953
|
+
const mbuf = s.allocator.alloc(u8, m_total) catch continue;
|
|
1954
|
+
var mp: usize = 0;
|
|
1955
|
+
mbuf[mp] = ' '; mp += 1;
|
|
1956
|
+
mbuf[mp] = ' '; mp += 1;
|
|
1957
|
+
var first_mod = true;
|
|
1958
|
+
if (has_public) {
|
|
1959
|
+
@memcpy(mbuf[mp..][0..pub_token.len], pub_token); mp += pub_token.len;
|
|
1960
|
+
first_mod = false;
|
|
1961
|
+
}
|
|
1962
|
+
if (has_protected) {
|
|
1963
|
+
if (!first_mod) { mbuf[mp] = ' '; mp += 1; }
|
|
1964
|
+
@memcpy(mbuf[mp..][0..prot_token.len], prot_token); mp += prot_token.len;
|
|
1965
|
+
first_mod = false;
|
|
1966
|
+
}
|
|
1967
|
+
if (has_readonly) {
|
|
1968
|
+
if (!first_mod) { mbuf[mp] = ' '; mp += 1; }
|
|
1969
|
+
@memcpy(mbuf[mp..][0..ro_token.len], ro_token); mp += ro_token.len;
|
|
1970
|
+
}
|
|
1971
|
+
if (num_mods > 0) { mbuf[mp] = ' '; mp += 1; }
|
|
1972
|
+
@memcpy(mbuf[mp..][0..dts_param.len], dts_param); mp += dts_param.len;
|
|
1973
|
+
mbuf[mp] = ';'; mp += 1;
|
|
1974
|
+
members.append(mbuf[0..mp]) catch {};
|
|
1772
1975
|
}
|
|
1773
1976
|
}
|
|
1774
1977
|
|
|
@@ -1814,15 +2017,22 @@ pub fn extractModule(s: *Scanner, decl_start: usize, is_exported: bool, keyword:
|
|
|
1814
2017
|
else
|
|
1815
2018
|
"{}";
|
|
1816
2019
|
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
text
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
2020
|
+
// Direct alloc — fixed-shape "[export ]declare <keyword> <name> <body>".
|
|
2021
|
+
const export_prefix: []const u8 = if (is_exported) "export " else "";
|
|
2022
|
+
const declare_kw = "declare ";
|
|
2023
|
+
const total = export_prefix.len + declare_kw.len + keyword.len + 1 + name.len + 1 + body.len;
|
|
2024
|
+
const text = blk: {
|
|
2025
|
+
const buf = s.allocator.alloc(u8, total) catch break :blk @as([]const u8, "");
|
|
2026
|
+
var tp: usize = 0;
|
|
2027
|
+
@memcpy(buf[tp..][0..export_prefix.len], export_prefix); tp += export_prefix.len;
|
|
2028
|
+
@memcpy(buf[tp..][0..declare_kw.len], declare_kw); tp += declare_kw.len;
|
|
2029
|
+
@memcpy(buf[tp..][0..keyword.len], keyword); tp += keyword.len;
|
|
2030
|
+
buf[tp] = ' '; tp += 1;
|
|
2031
|
+
@memcpy(buf[tp..][0..name.len], name); tp += name.len;
|
|
2032
|
+
buf[tp] = ' '; tp += 1;
|
|
2033
|
+
@memcpy(buf[tp..][0..body.len], body); tp += body.len;
|
|
2034
|
+
break :blk @as([]const u8, buf);
|
|
2035
|
+
};
|
|
1826
2036
|
|
|
1827
2037
|
const comments = extractLeadingComments(s, decl_start);
|
|
1828
2038
|
const is_ambient = name.len > 0 and (name[0] == '\'' or name[0] == '"');
|
|
@@ -1830,7 +2040,7 @@ pub fn extractModule(s: *Scanner, decl_start: usize, is_exported: bool, keyword:
|
|
|
1830
2040
|
return .{
|
|
1831
2041
|
.kind = .module_decl,
|
|
1832
2042
|
.name = name,
|
|
1833
|
-
.text = text
|
|
2043
|
+
.text = text,
|
|
1834
2044
|
.is_exported = is_exported,
|
|
1835
2045
|
.source_module = if (is_ambient and name.len > 2) name[1 .. name.len - 1] else "",
|
|
1836
2046
|
.leading_comments = comments,
|
|
@@ -1859,22 +2069,28 @@ pub fn buildNamespaceBodyDts(s: *Scanner, indent: []const u8) []const u8 {
|
|
|
1859
2069
|
continue;
|
|
1860
2070
|
}
|
|
1861
2071
|
|
|
2072
|
+
// First-byte gate for "export"/"declare" — avoids running matchWord on
|
|
2073
|
+
// every byte that can't possibly start either keyword.
|
|
1862
2074
|
var has_export = false;
|
|
1863
|
-
if (s.matchWord("export")) {
|
|
2075
|
+
if (s.pos < s.len and s.source[s.pos] == 'e' and s.matchWord("export")) {
|
|
1864
2076
|
has_export = true;
|
|
1865
2077
|
s.pos += 6;
|
|
1866
2078
|
s.skipWhitespaceAndComments();
|
|
1867
2079
|
}
|
|
1868
|
-
if (s.matchWord("declare")) {
|
|
2080
|
+
if (s.pos < s.len and s.source[s.pos] == 'd' and s.matchWord("declare")) {
|
|
1869
2081
|
s.pos += 7;
|
|
1870
2082
|
s.skipWhitespaceAndComments();
|
|
1871
2083
|
}
|
|
1872
2084
|
|
|
1873
2085
|
const prefix: []const u8 = if (has_export) "export " else "";
|
|
1874
2086
|
|
|
1875
|
-
|
|
2087
|
+
// First-byte gate: only check `function`/`async function` when the next
|
|
2088
|
+
// byte could start either. Saves matchWord on every other dispatch path.
|
|
2089
|
+
const dispatch_byte: u8 = if (s.pos < s.len) s.source[s.pos] else 0;
|
|
2090
|
+
if ((dispatch_byte == 'f' and s.matchWord("function")) or
|
|
2091
|
+
(dispatch_byte == 'a' and s.matchWord("async") and s.peekAfterKeyword("async", "function"))) {
|
|
1876
2092
|
var is_async = false;
|
|
1877
|
-
if (s.matchWord("async")) {
|
|
2093
|
+
if (dispatch_byte == 'a' and s.matchWord("async")) {
|
|
1878
2094
|
is_async = true;
|
|
1879
2095
|
s.pos += 5;
|
|
1880
2096
|
s.skipWhitespaceAndComments();
|
|
@@ -1901,37 +2117,53 @@ pub fn buildNamespaceBodyDts(s: *Scanner, indent: []const u8) []const u8 {
|
|
|
1901
2117
|
s.pos += 1;
|
|
1902
2118
|
}
|
|
1903
2119
|
const dts_params = buildDtsParams(s, raw_params);
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
2120
|
+
// Direct alloc — fixed shape "<indent><prefix>function <name><generics><params>: <ret>;".
|
|
2121
|
+
const fn_kw = "function ";
|
|
2122
|
+
const colon_sep = ": ";
|
|
2123
|
+
const fn_total = indent.len + prefix.len + fn_kw.len + fname.len +
|
|
2124
|
+
generics.len + dts_params.len + colon_sep.len + ret_type.len + 1;
|
|
2125
|
+
const fn_buf = s.allocator.alloc(u8, fn_total) catch continue;
|
|
2126
|
+
var fp: usize = 0;
|
|
2127
|
+
@memcpy(fn_buf[fp..][0..indent.len], indent); fp += indent.len;
|
|
2128
|
+
@memcpy(fn_buf[fp..][0..prefix.len], prefix); fp += prefix.len;
|
|
2129
|
+
@memcpy(fn_buf[fp..][0..fn_kw.len], fn_kw); fp += fn_kw.len;
|
|
2130
|
+
@memcpy(fn_buf[fp..][0..fname.len], fname); fp += fname.len;
|
|
2131
|
+
@memcpy(fn_buf[fp..][0..generics.len], generics); fp += generics.len;
|
|
2132
|
+
@memcpy(fn_buf[fp..][0..dts_params.len], dts_params); fp += dts_params.len;
|
|
2133
|
+
@memcpy(fn_buf[fp..][0..colon_sep.len], colon_sep); fp += colon_sep.len;
|
|
2134
|
+
@memcpy(fn_buf[fp..][0..ret_type.len], ret_type); fp += ret_type.len;
|
|
2135
|
+
fn_buf[fp] = ';';
|
|
2136
|
+
lines.append(fn_buf) catch {};
|
|
1915
2137
|
} else if (s.matchWord("const") or s.matchWord("let") or s.matchWord("var")) {
|
|
1916
|
-
|
|
2138
|
+
// First-char dispatch — previously matchWord ran twice more here.
|
|
2139
|
+
const kw: []const u8 = switch (s.source[s.pos]) {
|
|
2140
|
+
'c' => "const",
|
|
2141
|
+
'l' => "let",
|
|
2142
|
+
else => "var",
|
|
2143
|
+
};
|
|
1917
2144
|
s.pos += kw.len;
|
|
1918
2145
|
s.skipWhitespaceAndComments();
|
|
1919
2146
|
|
|
1920
2147
|
// Check for const enum
|
|
1921
|
-
|
|
2148
|
+
// kw is one of "const", "let", "var" — check by first byte instead of mem.eql.
|
|
2149
|
+
if (kw[0] == 'c' and s.matchWord("enum")) {
|
|
1922
2150
|
s.pos += 4;
|
|
1923
2151
|
s.skipWhitespaceAndComments();
|
|
1924
2152
|
const ce_name = s.readIdent();
|
|
1925
2153
|
s.skipWhitespaceAndComments();
|
|
1926
2154
|
const ce_body = if (s.pos < s.len and s.source[s.pos] == ch.CH_LBRACE) s.extractBraceBlock() else "{}";
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
2155
|
+
// Direct alloc — fixed shape "<indent><prefix>const enum <name> <body>".
|
|
2156
|
+
const ce_kw = "const enum ";
|
|
2157
|
+
const ce_total = indent.len + prefix.len + ce_kw.len + ce_name.len + 1 + ce_body.len;
|
|
2158
|
+
const ce_buf = s.allocator.alloc(u8, ce_total) catch continue;
|
|
2159
|
+
var cp: usize = 0;
|
|
2160
|
+
@memcpy(ce_buf[cp..][0..indent.len], indent); cp += indent.len;
|
|
2161
|
+
@memcpy(ce_buf[cp..][0..prefix.len], prefix); cp += prefix.len;
|
|
2162
|
+
@memcpy(ce_buf[cp..][0..ce_kw.len], ce_kw); cp += ce_kw.len;
|
|
2163
|
+
@memcpy(ce_buf[cp..][0..ce_name.len], ce_name); cp += ce_name.len;
|
|
2164
|
+
ce_buf[cp] = ' '; cp += 1;
|
|
2165
|
+
@memcpy(ce_buf[cp..][0..ce_body.len], ce_body); cp += ce_body.len;
|
|
2166
|
+
lines.append(ce_buf) catch {};
|
|
1935
2167
|
continue;
|
|
1936
2168
|
}
|
|
1937
2169
|
|
|
@@ -1989,7 +2221,9 @@ pub fn buildNamespaceBodyDts(s: *Scanner, indent: []const u8) []const u8 {
|
|
|
1989
2221
|
const as_type = extractAssertion(init_text);
|
|
1990
2222
|
if (as_type) |t| {
|
|
1991
2223
|
vtype = t;
|
|
1992
|
-
} else if (
|
|
2224
|
+
} else if (kw[0] == 'c') {
|
|
2225
|
+
// kw is "const" — first-byte check is sufficient since it's
|
|
2226
|
+
// one of "const" / "let" / "var".
|
|
1993
2227
|
vtype = inferLiteralType(init_text);
|
|
1994
2228
|
} else {
|
|
1995
2229
|
vtype = inferTypeFromDefault(init_text);
|
|
@@ -1997,16 +2231,19 @@ pub fn buildNamespaceBodyDts(s: *Scanner, indent: []const u8) []const u8 {
|
|
|
1997
2231
|
}
|
|
1998
2232
|
if (vtype.len == 0) vtype = "unknown";
|
|
1999
2233
|
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2234
|
+
// Direct alloc — fixed shape "<indent><prefix><kw> <name>: <type>;".
|
|
2235
|
+
const var_total = indent.len + prefix.len + kw.len + 1 + vname.len + 2 + vtype.len + 1;
|
|
2236
|
+
const var_buf = s.allocator.alloc(u8, var_total) catch continue;
|
|
2237
|
+
var vp: usize = 0;
|
|
2238
|
+
@memcpy(var_buf[vp..][0..indent.len], indent); vp += indent.len;
|
|
2239
|
+
@memcpy(var_buf[vp..][0..prefix.len], prefix); vp += prefix.len;
|
|
2240
|
+
@memcpy(var_buf[vp..][0..kw.len], kw); vp += kw.len;
|
|
2241
|
+
var_buf[vp] = ' '; vp += 1;
|
|
2242
|
+
@memcpy(var_buf[vp..][0..vname.len], vname); vp += vname.len;
|
|
2243
|
+
@memcpy(var_buf[vp..][0..2], ": "); vp += 2;
|
|
2244
|
+
@memcpy(var_buf[vp..][0..vtype.len], vtype); vp += vtype.len;
|
|
2245
|
+
var_buf[vp] = ';';
|
|
2246
|
+
lines.append(var_buf) catch {};
|
|
2010
2247
|
} else if (s.matchWord("interface")) {
|
|
2011
2248
|
s.pos += 9;
|
|
2012
2249
|
s.skipWhitespaceAndComments();
|
|
@@ -2024,16 +2261,21 @@ pub fn buildNamespaceBodyDts(s: *Scanner, indent: []const u8) []const u8 {
|
|
|
2024
2261
|
ext = s.source[ext_start..s.pos];
|
|
2025
2262
|
}
|
|
2026
2263
|
const body = cleanBraceBlock(s, s.extractBraceBlock());
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2264
|
+
// Direct alloc — fixed shape "<indent><prefix>interface <name><gen><ext> <body>".
|
|
2265
|
+
const if_kw = "interface ";
|
|
2266
|
+
const if_total = indent.len + prefix.len + if_kw.len + iname.len +
|
|
2267
|
+
generics.len + ext.len + 1 + body.len;
|
|
2268
|
+
const if_buf = s.allocator.alloc(u8, if_total) catch continue;
|
|
2269
|
+
var ifp: usize = 0;
|
|
2270
|
+
@memcpy(if_buf[ifp..][0..indent.len], indent); ifp += indent.len;
|
|
2271
|
+
@memcpy(if_buf[ifp..][0..prefix.len], prefix); ifp += prefix.len;
|
|
2272
|
+
@memcpy(if_buf[ifp..][0..if_kw.len], if_kw); ifp += if_kw.len;
|
|
2273
|
+
@memcpy(if_buf[ifp..][0..iname.len], iname); ifp += iname.len;
|
|
2274
|
+
@memcpy(if_buf[ifp..][0..generics.len], generics); ifp += generics.len;
|
|
2275
|
+
@memcpy(if_buf[ifp..][0..ext.len], ext); ifp += ext.len;
|
|
2276
|
+
if_buf[ifp] = ' '; ifp += 1;
|
|
2277
|
+
@memcpy(if_buf[ifp..][0..body.len], body); ifp += body.len;
|
|
2278
|
+
lines.append(if_buf) catch {};
|
|
2037
2279
|
} else if (s.matchWord("type")) {
|
|
2038
2280
|
s.pos += 4;
|
|
2039
2281
|
s.skipWhitespaceAndComments();
|
|
@@ -2061,15 +2303,21 @@ pub fn buildNamespaceBodyDts(s: *Scanner, indent: []const u8) []const u8 {
|
|
|
2061
2303
|
}
|
|
2062
2304
|
const type_body = s.sliceTrimmed(ts, s.pos);
|
|
2063
2305
|
if (s.pos < s.len and s.source[s.pos] == ch.CH_SEMI) s.pos += 1;
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2306
|
+
// Direct alloc — fixed shape "<indent><prefix>type <name><gen> = <body>".
|
|
2307
|
+
const ty_kw = "type ";
|
|
2308
|
+
const eq_sep = " = ";
|
|
2309
|
+
const ty_total = indent.len + prefix.len + ty_kw.len + tname.len +
|
|
2310
|
+
generics.len + eq_sep.len + type_body.len;
|
|
2311
|
+
const ty_buf = s.allocator.alloc(u8, ty_total) catch continue;
|
|
2312
|
+
var typ: usize = 0;
|
|
2313
|
+
@memcpy(ty_buf[typ..][0..indent.len], indent); typ += indent.len;
|
|
2314
|
+
@memcpy(ty_buf[typ..][0..prefix.len], prefix); typ += prefix.len;
|
|
2315
|
+
@memcpy(ty_buf[typ..][0..ty_kw.len], ty_kw); typ += ty_kw.len;
|
|
2316
|
+
@memcpy(ty_buf[typ..][0..tname.len], tname); typ += tname.len;
|
|
2317
|
+
@memcpy(ty_buf[typ..][0..generics.len], generics); typ += generics.len;
|
|
2318
|
+
@memcpy(ty_buf[typ..][0..eq_sep.len], eq_sep); typ += eq_sep.len;
|
|
2319
|
+
@memcpy(ty_buf[typ..][0..type_body.len], type_body); typ += type_body.len;
|
|
2320
|
+
lines.append(ty_buf) catch {};
|
|
2073
2321
|
}
|
|
2074
2322
|
} else if (s.matchWord("enum")) {
|
|
2075
2323
|
// Handle enum inside namespace
|
|
@@ -2078,32 +2326,39 @@ pub fn buildNamespaceBodyDts(s: *Scanner, indent: []const u8) []const u8 {
|
|
|
2078
2326
|
const ename = s.readIdent();
|
|
2079
2327
|
s.skipWhitespaceAndComments();
|
|
2080
2328
|
const ebody = if (s.pos < s.len and s.source[s.pos] == ch.CH_LBRACE) s.extractBraceBlock() else "{}";
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2329
|
+
// Direct alloc — fixed shape "<indent><prefix>enum <name> <body>".
|
|
2330
|
+
const en_kw = "enum ";
|
|
2331
|
+
const en_total = indent.len + prefix.len + en_kw.len + ename.len + 1 + ebody.len;
|
|
2332
|
+
const en_buf = s.allocator.alloc(u8, en_total) catch continue;
|
|
2333
|
+
var ep: usize = 0;
|
|
2334
|
+
@memcpy(en_buf[ep..][0..indent.len], indent); ep += indent.len;
|
|
2335
|
+
@memcpy(en_buf[ep..][0..prefix.len], prefix); ep += prefix.len;
|
|
2336
|
+
@memcpy(en_buf[ep..][0..en_kw.len], en_kw); ep += en_kw.len;
|
|
2337
|
+
@memcpy(en_buf[ep..][0..ename.len], ename); ep += ename.len;
|
|
2338
|
+
en_buf[ep] = ' '; ep += 1;
|
|
2339
|
+
@memcpy(en_buf[ep..][0..ebody.len], ebody); ep += ebody.len;
|
|
2340
|
+
lines.append(en_buf) catch {};
|
|
2089
2341
|
} else if (s.matchWord("namespace") or s.matchWord("module")) {
|
|
2090
|
-
//
|
|
2091
|
-
const ns_kw: []const u8 = if (s.
|
|
2342
|
+
// First-char dispatch — previously matchWord ran twice more.
|
|
2343
|
+
const ns_kw: []const u8 = if (s.source[s.pos] == 'n') "namespace" else "module";
|
|
2092
2344
|
s.pos += ns_kw.len;
|
|
2093
2345
|
s.skipWhitespaceAndComments();
|
|
2094
2346
|
const ns_name = s.readIdent();
|
|
2095
2347
|
s.skipWhitespaceAndComments();
|
|
2096
2348
|
// Use same indent level for nested namespace body (matches TS behavior)
|
|
2097
2349
|
const ns_body = if (s.pos < s.len and s.source[s.pos] == ch.CH_LBRACE) buildNamespaceBodyDts(s, indent) else "{}";
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2350
|
+
// Direct alloc — fixed shape "<indent><prefix><ns_kw> <name> <body>".
|
|
2351
|
+
const ns_total = indent.len + prefix.len + ns_kw.len + 1 + ns_name.len + 1 + ns_body.len;
|
|
2352
|
+
const ns_buf = s.allocator.alloc(u8, ns_total) catch continue;
|
|
2353
|
+
var nsp: usize = 0;
|
|
2354
|
+
@memcpy(ns_buf[nsp..][0..indent.len], indent); nsp += indent.len;
|
|
2355
|
+
@memcpy(ns_buf[nsp..][0..prefix.len], prefix); nsp += prefix.len;
|
|
2356
|
+
@memcpy(ns_buf[nsp..][0..ns_kw.len], ns_kw); nsp += ns_kw.len;
|
|
2357
|
+
ns_buf[nsp] = ' '; nsp += 1;
|
|
2358
|
+
@memcpy(ns_buf[nsp..][0..ns_name.len], ns_name); nsp += ns_name.len;
|
|
2359
|
+
ns_buf[nsp] = ' '; nsp += 1;
|
|
2360
|
+
@memcpy(ns_buf[nsp..][0..ns_body.len], ns_body); nsp += ns_body.len;
|
|
2361
|
+
lines.append(ns_buf) catch {};
|
|
2107
2362
|
} else if (s.matchWord("class") or s.matchWord("abstract")) {
|
|
2108
2363
|
// Handle class inside namespace
|
|
2109
2364
|
var is_abs = false;
|
|
@@ -2119,31 +2374,46 @@ pub fn buildNamespaceBodyDts(s: *Scanner, indent: []const u8) []const u8 {
|
|
|
2119
2374
|
s.skipWhitespaceAndComments();
|
|
2120
2375
|
const cgen = extractGenerics(s);
|
|
2121
2376
|
s.skipWhitespaceAndComments();
|
|
2122
|
-
// Skip extends/implements
|
|
2123
|
-
|
|
2124
|
-
|
|
2377
|
+
// Skip extends/implements — first-byte dispatch saves redundant
|
|
2378
|
+
// matchWord calls (the previous code ran matchWord up to 4× per
|
|
2379
|
+
// outer iteration just to determine the keyword length).
|
|
2380
|
+
while (true) {
|
|
2381
|
+
if (s.pos >= s.len) break;
|
|
2382
|
+
const c0 = s.source[s.pos];
|
|
2383
|
+
var kw_len: usize = 0;
|
|
2384
|
+
if (c0 == 'e' and s.matchWord("extends")) kw_len = 7
|
|
2385
|
+
else if (c0 == 'i' and s.matchWord("implements")) kw_len = 10
|
|
2386
|
+
else break;
|
|
2125
2387
|
s.pos += kw_len;
|
|
2126
2388
|
s.skipWhitespaceAndComments();
|
|
2127
2389
|
var depth: isize = 0;
|
|
2128
2390
|
while (s.pos < s.len) {
|
|
2129
2391
|
if (s.skipNonCode()) continue;
|
|
2130
2392
|
const tc = s.source[s.pos];
|
|
2131
|
-
if (tc == ch.CH_LANGLE) depth += 1
|
|
2393
|
+
if (tc == ch.CH_LANGLE) depth += 1
|
|
2394
|
+
else if (tc == ch.CH_RANGLE and !s.isArrowGT()) depth -= 1
|
|
2395
|
+
else if (depth == 0 and (tc == ch.CH_LBRACE or (tc == 'i' and s.matchWord("implements")))) break;
|
|
2132
2396
|
s.pos += 1;
|
|
2133
2397
|
}
|
|
2134
2398
|
}
|
|
2135
2399
|
s.skipWhitespaceAndComments();
|
|
2136
2400
|
const cbody = if (s.pos < s.len and s.source[s.pos] == ch.CH_LBRACE) buildClassBodyDts(s) else "{}";
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2401
|
+
// Direct alloc — "<indent><prefix>[abstract ]class <name><gen> <body>".
|
|
2402
|
+
const abs_kw: []const u8 = if (is_abs) "abstract " else "";
|
|
2403
|
+
const cl_kw = "class ";
|
|
2404
|
+
const cl_total = indent.len + prefix.len + abs_kw.len + cl_kw.len +
|
|
2405
|
+
cname.len + cgen.len + 1 + cbody.len;
|
|
2406
|
+
const cl_buf = s.allocator.alloc(u8, cl_total) catch continue;
|
|
2407
|
+
var cp: usize = 0;
|
|
2408
|
+
@memcpy(cl_buf[cp..][0..indent.len], indent); cp += indent.len;
|
|
2409
|
+
@memcpy(cl_buf[cp..][0..prefix.len], prefix); cp += prefix.len;
|
|
2410
|
+
@memcpy(cl_buf[cp..][0..abs_kw.len], abs_kw); cp += abs_kw.len;
|
|
2411
|
+
@memcpy(cl_buf[cp..][0..cl_kw.len], cl_kw); cp += cl_kw.len;
|
|
2412
|
+
@memcpy(cl_buf[cp..][0..cname.len], cname); cp += cname.len;
|
|
2413
|
+
@memcpy(cl_buf[cp..][0..cgen.len], cgen); cp += cgen.len;
|
|
2414
|
+
cl_buf[cp] = ' '; cp += 1;
|
|
2415
|
+
@memcpy(cl_buf[cp..][0..cbody.len], cbody); cp += cbody.len;
|
|
2416
|
+
lines.append(cl_buf) catch {};
|
|
2147
2417
|
} else {
|
|
2148
2418
|
s.skipToStatementEnd();
|
|
2149
2419
|
}
|
|
@@ -2155,12 +2425,16 @@ pub fn buildNamespaceBodyDts(s: *Scanner, indent: []const u8) []const u8 {
|
|
|
2155
2425
|
var def_text = s.sliceTrimmed(def_start, s.pos);
|
|
2156
2426
|
if (def_text.len > 0 and def_text[def_text.len - 1] == ';') def_text = def_text[0 .. def_text.len - 1];
|
|
2157
2427
|
if (def_text.len > 0) {
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2428
|
+
// Direct alloc — fixed shape "<indent>export default <text>;".
|
|
2429
|
+
const def_kw = "export default ";
|
|
2430
|
+
const def_total = indent.len + def_kw.len + def_text.len + 1;
|
|
2431
|
+
const def_buf = s.allocator.alloc(u8, def_total) catch continue;
|
|
2432
|
+
var dp: usize = 0;
|
|
2433
|
+
@memcpy(def_buf[dp..][0..indent.len], indent); dp += indent.len;
|
|
2434
|
+
@memcpy(def_buf[dp..][0..def_kw.len], def_kw); dp += def_kw.len;
|
|
2435
|
+
@memcpy(def_buf[dp..][0..def_text.len], def_text); dp += def_text.len;
|
|
2436
|
+
def_buf[dp] = ';';
|
|
2437
|
+
lines.append(def_buf) catch {};
|
|
2164
2438
|
}
|
|
2165
2439
|
} else {
|
|
2166
2440
|
s.skipToStatementEnd();
|
|
@@ -2168,14 +2442,23 @@ pub fn buildNamespaceBodyDts(s: *Scanner, indent: []const u8) []const u8 {
|
|
|
2168
2442
|
}
|
|
2169
2443
|
|
|
2170
2444
|
if (lines.items.len == 0) return "{}";
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
for (lines.items
|
|
2174
|
-
|
|
2175
|
-
|
|
2445
|
+
// Direct alloc — pre-compute total and emit in one pass.
|
|
2446
|
+
var total_len: usize = 4; // "{\n" + "\n}"
|
|
2447
|
+
for (lines.items) |line| total_len += line.len;
|
|
2448
|
+
total_len += lines.items.len - 1; // newlines between
|
|
2449
|
+
const buf = s.allocator.alloc(u8, total_len) catch return "{}";
|
|
2450
|
+
buf[0] = '{'; buf[1] = '\n';
|
|
2451
|
+
var rp: usize = 2;
|
|
2452
|
+
@memcpy(buf[rp..][0..lines.items[0].len], lines.items[0]);
|
|
2453
|
+
rp += lines.items[0].len;
|
|
2454
|
+
for (lines.items[1..]) |line| {
|
|
2455
|
+
buf[rp] = '\n'; rp += 1;
|
|
2456
|
+
@memcpy(buf[rp..][0..line.len], line);
|
|
2457
|
+
rp += line.len;
|
|
2176
2458
|
}
|
|
2177
|
-
|
|
2178
|
-
|
|
2459
|
+
buf[rp] = '\n'; rp += 1;
|
|
2460
|
+
buf[rp] = '}'; rp += 1;
|
|
2461
|
+
return buf[0..rp];
|
|
2179
2462
|
}
|
|
2180
2463
|
|
|
2181
2464
|
// ========================================================================
|
|
@@ -2184,6 +2467,17 @@ pub fn buildNamespaceBodyDts(s: *Scanner, indent: []const u8) []const u8 {
|
|
|
2184
2467
|
|
|
2185
2468
|
/// Strip trailing inline comments from a line (respecting strings)
|
|
2186
2469
|
fn stripTrailingInlineComment(line: []const u8) []const u8 {
|
|
2470
|
+
// Fast path: most lines have no `/` at all — `indexOfChar` SIMD-scans for it
|
|
2471
|
+
// in a single pass. If absent, we know there's no inline comment and can
|
|
2472
|
+
// skip the string-state walking entirely.
|
|
2473
|
+
const slash_idx = ch.indexOfChar(line, '/', 0);
|
|
2474
|
+
if (slash_idx == null) {
|
|
2475
|
+
// Trim trailing whitespace.
|
|
2476
|
+
var end = line.len;
|
|
2477
|
+
while (end > 0 and (line[end - 1] == ' ' or line[end - 1] == '\t' or line[end - 1] == '\r')) end -= 1;
|
|
2478
|
+
return line[0..end];
|
|
2479
|
+
}
|
|
2480
|
+
|
|
2187
2481
|
var in_string: u8 = 0;
|
|
2188
2482
|
var i: usize = 0;
|
|
2189
2483
|
while (i < line.len) {
|
|
@@ -2241,8 +2535,18 @@ pub fn cleanBraceBlock(s: *Scanner, raw: []const u8) []const u8 {
|
|
|
2241
2535
|
if (!needs_reindent) return raw;
|
|
2242
2536
|
}
|
|
2243
2537
|
|
|
2244
|
-
// Check if there are any comment markers
|
|
2245
|
-
|
|
2538
|
+
// Check if there are any comment markers — single scan for '/' and only do
|
|
2539
|
+
// the multi-byte verification if found.
|
|
2540
|
+
const has_comments = blk: {
|
|
2541
|
+
var i: usize = 0;
|
|
2542
|
+
while (ch.indexOfChar(raw, '/', i)) |slash_idx| {
|
|
2543
|
+
if (slash_idx + 1 < raw.len and (raw[slash_idx + 1] == '/' or raw[slash_idx + 1] == '*')) {
|
|
2544
|
+
break :blk true;
|
|
2545
|
+
}
|
|
2546
|
+
i = slash_idx + 1;
|
|
2547
|
+
}
|
|
2548
|
+
break :blk false;
|
|
2549
|
+
};
|
|
2246
2550
|
|
|
2247
2551
|
// Split raw text by newlines and process line by line
|
|
2248
2552
|
const est_lines = @max(raw.len / 30, 4);
|
|
@@ -2294,7 +2598,8 @@ pub fn cleanBraceBlock(s: *Scanner, raw: []const u8) []const u8 {
|
|
|
2294
2598
|
// Compute indent
|
|
2295
2599
|
var iw: usize = 0;
|
|
2296
2600
|
while (iw < cleaned_line.len and (cleaned_line[iw] == ' ' or cleaned_line[iw] == '\t')) iw += 1;
|
|
2297
|
-
|
|
2601
|
+
// Single-byte literal check — `std.mem.eql` is overkill for a 1-char comparison.
|
|
2602
|
+
if (!(ct.len == 1 and (ct[0] == '{' or ct[0] == '}'))) {
|
|
2298
2603
|
if (iw < min_indent) min_indent = iw;
|
|
2299
2604
|
}
|
|
2300
2605
|
indent_cache.append(iw) catch {};
|
|
@@ -2314,7 +2619,8 @@ pub fn cleanBraceBlock(s: *Scanner, raw: []const u8) []const u8 {
|
|
|
2314
2619
|
|
|
2315
2620
|
var iw: usize = 0;
|
|
2316
2621
|
while (iw < cleaned_line.len and (cleaned_line[iw] == ' ' or cleaned_line[iw] == '\t')) iw += 1;
|
|
2317
|
-
|
|
2622
|
+
// Single-byte literal check — `std.mem.eql` is overkill for a 1-char comparison.
|
|
2623
|
+
if (!(ct.len == 1 and (ct[0] == '{' or ct[0] == '}'))) {
|
|
2318
2624
|
if (iw < min_indent) min_indent = iw;
|
|
2319
2625
|
}
|
|
2320
2626
|
indent_cache.append(iw) catch {};
|
|
@@ -2350,9 +2656,9 @@ pub fn cleanBraceBlock(s: *Scanner, raw: []const u8) []const u8 {
|
|
|
2350
2656
|
}
|
|
2351
2657
|
|
|
2352
2658
|
const ct = ch.sliceTrimmed(line, 0, line.len);
|
|
2353
|
-
if (
|
|
2354
|
-
|
|
2355
|
-
pos +=
|
|
2659
|
+
if (ct.len == 1 and ct[0] == '{') {
|
|
2660
|
+
buf[pos] = '{';
|
|
2661
|
+
pos += 1;
|
|
2356
2662
|
continue;
|
|
2357
2663
|
}
|
|
2358
2664
|
|
|
@@ -2381,78 +2687,92 @@ pub fn cleanBraceBlock(s: *Scanner, raw: []const u8) []const u8 {
|
|
|
2381
2687
|
|
|
2382
2688
|
/// Handle `declare ...` after `declare` keyword
|
|
2383
2689
|
pub fn handleDeclare(s: *Scanner, stmt_start: usize, is_exported: bool) void {
|
|
2384
|
-
|
|
2690
|
+
// First-byte dispatch — most declare keywords are uniquely identified by
|
|
2691
|
+
// their first byte, so we avoid running the full matchWord scan against
|
|
2692
|
+
// unrelated keywords (each matchWord costs a length+boundary check).
|
|
2693
|
+
if (s.pos >= s.len) return;
|
|
2694
|
+
const c0 = s.source[s.pos];
|
|
2695
|
+
if (c0 == 'f' and s.matchWord("function")) {
|
|
2385
2696
|
const decl = extractFunction(s, stmt_start, is_exported, false, false);
|
|
2386
2697
|
if (decl) |d| s.declarations.append(d) catch {};
|
|
2387
|
-
} else if (
|
|
2388
|
-
s.
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2698
|
+
} else if (c0 == 'a') {
|
|
2699
|
+
if (s.matchWord("async")) {
|
|
2700
|
+
s.pos += 5;
|
|
2701
|
+
s.skipWhitespaceAndComments();
|
|
2702
|
+
if (s.matchWord("function")) {
|
|
2703
|
+
const decl = extractFunction(s, stmt_start, is_exported, true, false);
|
|
2704
|
+
if (decl) |d| s.declarations.append(d) catch {};
|
|
2705
|
+
}
|
|
2706
|
+
} else if (s.matchWord("abstract")) {
|
|
2707
|
+
s.pos += 8;
|
|
2708
|
+
s.skipWhitespaceAndComments();
|
|
2709
|
+
if (s.matchWord("class")) {
|
|
2710
|
+
const decl = extractClass(s, stmt_start, is_exported, true);
|
|
2711
|
+
s.declarations.append(decl) catch {};
|
|
2712
|
+
}
|
|
2393
2713
|
}
|
|
2394
|
-
} else if (
|
|
2395
|
-
const decl = extractClass(s, stmt_start, is_exported, false);
|
|
2396
|
-
s.declarations.append(decl) catch {};
|
|
2397
|
-
} else if (s.matchWord("abstract")) {
|
|
2398
|
-
s.pos += 8;
|
|
2399
|
-
s.skipWhitespaceAndComments();
|
|
2714
|
+
} else if (c0 == 'c') {
|
|
2400
2715
|
if (s.matchWord("class")) {
|
|
2401
|
-
const decl = extractClass(s, stmt_start, is_exported,
|
|
2716
|
+
const decl = extractClass(s, stmt_start, is_exported, false);
|
|
2402
2717
|
s.declarations.append(decl) catch {};
|
|
2718
|
+
} else if (s.matchWord("const")) {
|
|
2719
|
+
const saved_pos = s.pos;
|
|
2720
|
+
s.pos += 5;
|
|
2721
|
+
s.skipWhitespaceAndComments();
|
|
2722
|
+
if (s.matchWord("enum")) {
|
|
2723
|
+
s.pos = saved_pos + 5;
|
|
2724
|
+
s.skipWhitespaceAndComments();
|
|
2725
|
+
const decl = extractEnum(s, stmt_start, is_exported, true);
|
|
2726
|
+
s.declarations.append(decl) catch {};
|
|
2727
|
+
} else if (is_exported) {
|
|
2728
|
+
s.pos = saved_pos;
|
|
2729
|
+
const decls = extractVariable(s, stmt_start, "const", true);
|
|
2730
|
+
for (decls) |d| s.declarations.append(d) catch {};
|
|
2731
|
+
} else {
|
|
2732
|
+
s.skipToStatementEnd();
|
|
2733
|
+
}
|
|
2403
2734
|
}
|
|
2404
|
-
} else if (s.matchWord("interface")) {
|
|
2735
|
+
} else if (c0 == 'i' and s.matchWord("interface")) {
|
|
2405
2736
|
const decl = extractInterface(s, stmt_start, is_exported);
|
|
2406
2737
|
s.declarations.append(decl) catch {};
|
|
2407
|
-
} else if (s.matchWord("type")) {
|
|
2738
|
+
} else if (c0 == 't' and s.matchWord("type")) {
|
|
2408
2739
|
const decl = extractTypeAlias(s, stmt_start, is_exported);
|
|
2409
2740
|
s.declarations.append(decl) catch {};
|
|
2410
|
-
} else if (s.matchWord("enum")) {
|
|
2741
|
+
} else if (c0 == 'e' and s.matchWord("enum")) {
|
|
2411
2742
|
const decl = extractEnum(s, stmt_start, is_exported, false);
|
|
2412
2743
|
s.declarations.append(decl) catch {};
|
|
2413
|
-
} else if (s.matchWord("
|
|
2414
|
-
const saved_pos = s.pos;
|
|
2415
|
-
s.pos += 5;
|
|
2416
|
-
s.skipWhitespaceAndComments();
|
|
2417
|
-
if (s.matchWord("enum")) {
|
|
2418
|
-
s.pos = saved_pos + 5;
|
|
2419
|
-
s.skipWhitespaceAndComments();
|
|
2420
|
-
const decl = extractEnum(s, stmt_start, is_exported, true);
|
|
2421
|
-
s.declarations.append(decl) catch {};
|
|
2422
|
-
} else if (is_exported) {
|
|
2423
|
-
s.pos = saved_pos;
|
|
2424
|
-
const decls = extractVariable(s, stmt_start, "const", true);
|
|
2425
|
-
for (decls) |d| s.declarations.append(d) catch {};
|
|
2426
|
-
} else {
|
|
2427
|
-
s.skipToStatementEnd();
|
|
2428
|
-
}
|
|
2429
|
-
} else if (s.matchWord("let") or s.matchWord("var")) {
|
|
2744
|
+
} else if ((c0 == 'l' and s.matchWord("let")) or (c0 == 'v' and s.matchWord("var"))) {
|
|
2430
2745
|
if (is_exported) {
|
|
2431
|
-
|
|
2746
|
+
// First-char dispatch — saves a redundant matchWord call.
|
|
2747
|
+
const kind: []const u8 = if (c0 == 'l') "let" else "var";
|
|
2432
2748
|
const decls = extractVariable(s, stmt_start, kind, true);
|
|
2433
2749
|
for (decls) |d| s.declarations.append(d) catch {};
|
|
2434
2750
|
} else {
|
|
2435
2751
|
s.skipToStatementEnd();
|
|
2436
2752
|
}
|
|
2437
|
-
} else if (s.matchWord("module")) {
|
|
2753
|
+
} else if (c0 == 'm' and s.matchWord("module")) {
|
|
2438
2754
|
const decl = extractModule(s, stmt_start, is_exported, "module");
|
|
2439
2755
|
s.declarations.append(decl) catch {};
|
|
2440
|
-
} else if (s.matchWord("namespace")) {
|
|
2756
|
+
} else if (c0 == 'n' and s.matchWord("namespace")) {
|
|
2441
2757
|
const decl = extractModule(s, stmt_start, is_exported, "namespace");
|
|
2442
2758
|
s.declarations.append(decl) catch {};
|
|
2443
|
-
} else if (s.matchWord("global")) {
|
|
2759
|
+
} else if (c0 == 'g' and s.matchWord("global")) {
|
|
2444
2760
|
s.pos += 6;
|
|
2445
2761
|
s.skipWhitespaceAndComments();
|
|
2446
2762
|
const body = if (s.pos < s.len and s.source[s.pos] == ch.CH_LBRACE) buildNamespaceBodyDts(s, " ") else "{}";
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2763
|
+
// Direct alloc — fixed prefix + body slice.
|
|
2764
|
+
const prefix = "declare global ";
|
|
2765
|
+
const text_buf = blk: {
|
|
2766
|
+
const buf = s.allocator.alloc(u8, prefix.len + body.len) catch break :blk @as([]const u8, "");
|
|
2767
|
+
@memcpy(buf[0..prefix.len], prefix);
|
|
2768
|
+
@memcpy(buf[prefix.len..][0..body.len], body);
|
|
2769
|
+
break :blk @as([]const u8, buf);
|
|
2770
|
+
};
|
|
2451
2771
|
const comments = extractLeadingComments(s, stmt_start);
|
|
2452
2772
|
s.declarations.append(.{
|
|
2453
2773
|
.kind = .module_decl,
|
|
2454
2774
|
.name = "global",
|
|
2455
|
-
.text =
|
|
2775
|
+
.text = text_buf,
|
|
2456
2776
|
.is_exported = false,
|
|
2457
2777
|
.leading_comments = comments,
|
|
2458
2778
|
.start = stmt_start,
|