authorgravatar for 47059999+Im-Beast@users.noreply.github.comMateusz Franik <47059999+Im-Beast@users.noreply.github.com> 2024-07-12 10:59:09+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2024-07-12 01:59:09-07:00
log5068576f23334b555210ae97757fb76ad07d0ba1
treee5e736b1d484a7dcf04024ad775186643afab8f9
parentdef7b01d2c3b5d21675be8938b366a263dbab586

feat: implement string (escaped) identifiers (#54)

String identifiers allow you to use any text in an identifier name, such as keywords, or any unicode codepoints aside from null (0). Identifiers still must be at least one character long; these requirements and syntax are taken from the Zig compiler. --------- Co-authored-by: dave caruso <me@paperdave.net>

4 files changed, 341 insertions(+), 53 deletions(-)

build.zig.zon+1-1
......@@ -2,7 +2,7 @@
22 .name = "",
33 .version = "0.0.0",
44
5 .minimum_zig_version = "0.13.0-dev.348+c564a16a0",
5 .minimum_zig_version = "0.13.0",
66
77 .dependencies = .{
88 .zg = .{
src/core/JsLink.zig+3-1
......@@ -446,9 +446,11 @@ pub fn canPrintWithoutEscape(c: u21) bool {
446446
447447pub fn indexOfNeedsEscape(slice: []const u8) ?usize {
448448 var it = std.unicode.Wtf8Iterator{ .bytes = slice, .i = 0 };
449 var i: usize = 0;
449450 while (it.nextCodepoint()) |c| {
450451 if (!canPrintWithoutEscape(c))
451 return it.i;
452 return i;
453 i = it.i;
452454 }
453455 return null;
454456}
src/core/Report.zig+235-23
......@@ -58,19 +58,32 @@ pub const Err = union(enum) {
5858 node: NodeIndex,
5959
6060 fn build(data: @This(), ctx: *MsgContext) !void {
61 try ctx.msgNode(data.node, "unknown identifier \"{s}\"", .{
62 ctx.ast.basicNodeSlice(data.node),
61 try ctx.msgNode(data.node, "unknown identifier {}", .{
62 formatId(ctx.ast.basicNodeSlice(data.node)),
6363 });
6464 }
6565 },
66 invalid_string_identifier: struct {
67 node: NodeIndex,
68
69 fn build(data: @This(), ctx: *MsgContext) !void {
70 const slice = ctx.ast.basicNodeSlice(data.node);
71 if (slice.len == 0) {
72 try ctx.msgNode(data.node, "identifier cannot be empty", .{});
73 } else {
74 // TODO: make this point at the null escape?
75 try ctx.msgNode(data.node, "identifier cannot include null bytes", .{});
76 }
77 }
78 },
6679 invalid_builtin: struct {
6780 node: NodeIndex,
6881
6982 fn build(data: @This(), ctx: *MsgContext) !void {
7083 try ctx.msgToken(
7184 ctx.ast.nodeToken(data.node),
72 "unknown built-in function \"{s}\"",
73 .{ctx.ast.basicNodeSlice(data.node)},
85 "unknown built-in function {}",
86 .{formatId(ctx.ast.basicNodeSlice(data.node))},
7487 );
7588 }
7689 },
......@@ -90,8 +103,8 @@ pub const Err = union(enum) {
90103 prev: NodeIndex,
91104
92105 fn build(data: @This(), ctx: *MsgContext) !void {
93 try ctx.msgToken(ctx.ast.fnDeclIdToken(data.node), "re-declaration of \"{s}\"", .{
94 ctx.ast.fullFnDecl(data.node).id,
106 try ctx.msgToken(ctx.ast.fnDeclIdToken(data.node), "re-declaration of {}", .{
107 formatId(ctx.ast.fullFnDecl(data.node).id),
95108 });
96109 try ctx.msgToken(ctx.ast.fnDeclIdToken(data.prev), "also declared here", .{});
97110 // TODO: be able to store a full list of other declarations
......@@ -103,8 +116,8 @@ pub const Err = union(enum) {
103116 prev: NodeIndex,
104117
105118 fn build(data: @This(), ctx: *MsgContext) !void {
106 try ctx.msgToken(ctx.ast.fnDeclIdToken(data.node), "re-declaration of \"{s}\"", .{
107 ctx.ast.fullParamDecl(data.node).id,
119 try ctx.msgToken(ctx.ast.fnDeclIdToken(data.node), "re-declaration of {}", .{
120 formatId(ctx.ast.fullParamDecl(data.node).id),
108121 });
109122 try ctx.msgToken(ctx.ast.nodeToken(data.prev), "also declared here", .{});
110123 // TODO: be able to store a full list of other declarations
......@@ -128,8 +141,8 @@ pub const Err = union(enum) {
128141 fn build(data: @This(), ctx: *MsgContext) !void {
129142 try ctx.msg(
130143 .{ .two_tokens = ctx.ast.nodeToken(data.node) + 1 },
131 "unknown block label \"{s}\"",
132 .{ctx.ast.tokenSlice(ctx.ast.nodeToken(data.node) + 2)},
144 "unknown block label {}",
145 .{formatId(ctx.ast.tokenSlice(ctx.ast.nodeToken(data.node) + 2))},
133146 );
134147 }
135148 },
......@@ -139,8 +152,8 @@ pub const Err = union(enum) {
139152 fn build(data: @This(), ctx: *MsgContext) !void {
140153 try ctx.msgToken(
141154 ctx.ast.nodeToken(data.node) + 1,
142 "declaration shadows reserved built-in \"{s}\"",
143 .{ctx.ast.fullVarDecl(data.node).id},
155 "declaration shadows reserved built-in {}",
156 .{formatId(ctx.ast.fullVarDecl(data.node).id)},
144157 );
145158 }
146159 },
......@@ -218,8 +231,8 @@ pub const Err = union(enum) {
218231 fn build(data: @This(), ctx: *MsgContext) !void {
219232 try ctx.msgNode(
220233 data.node,
221 "cannot re-assign built-in \"{s}\"",
222 .{ctx.ast.basicNodeSlice(data.node)},
234 "cannot re-assign built-in {}",
235 .{formatId(ctx.ast.basicNodeSlice(data.node))},
223236 );
224237 }
225238 },
......@@ -236,9 +249,9 @@ pub const Err = union(enum) {
236249 else => unreachable, // not a valid symbol node
237250 };
238251
239 try ctx.msgToken(ctx.ast.idToken(data.node), "cannot assign to {s} \"{s}\"", .{
252 try ctx.msgToken(ctx.ast.idToken(data.node), "cannot assign to {s} {}", .{
240253 @tagName(data.kind),
241 id,
254 formatId(id),
242255 });
243256 try ctx.msgToken(ctx.ast.idToken(data.decl), "declared here", .{});
244257 }
......@@ -297,12 +310,12 @@ pub const Warn = union(enum) {
297310 else => unreachable, // not a valid symbol node
298311 };
299312
300 try ctx.msgToken(ctx.ast.idToken(data.node), "unused {s} \"{s}\"", .{
313 try ctx.msgToken(ctx.ast.idToken(data.node), "unused {s} {}", .{
301314 @tagName(data.kind),
302 id,
315 formatId(id),
303316 });
304317
305 try ctx.msgAlone("if this was intentional, discard it with \"_ = {s};\"", .{id});
318 try ctx.msgAlone("if this was intentional, discard it with \"_ = {};\"", .{formatIdCode(id)});
306319 }
307320 },
308321 not_mutated_var: struct {
......@@ -314,8 +327,8 @@ pub const Warn = union(enum) {
314327 else => unreachable, // not a valid variable symbol node
315328 };
316329
317 try ctx.msgToken(ctx.ast.idToken(data.node), "variable \"{s}\" not mutated", .{
318 id,
330 try ctx.msgToken(ctx.ast.idToken(data.node), "variable {} not mutated", .{
331 formatId(id),
319332 });
320333 }
321334 },
......@@ -787,9 +800,10 @@ pub const MsgContext = struct {
787800 components: ArrayListUnmanaged(Component),
788801
789802 pub fn msg(m: *MsgContext, highlight: Highlight, comptime fmt: []const u8, args: anytype) !void {
790 const text = try std.fmt.allocPrint(m.arena, fmt, args);
803 var arr: std.ArrayListUnmanaged(u8) = .{};
804 try std.fmt.format(arr.writer(m.arena), fmt, args);
791805 try m.components.append(m.arena, .{
792 .message_utf8 = text,
806 .message_utf8 = try arr.toOwnedSlice(m.arena),
793807 .location = highlight.loc(m.ast),
794808 });
795809 }
......@@ -936,6 +950,202 @@ pub const Operand = enum(u32) {
936950 }
937951};
938952
953/// Non-allocating formatter for taking an id token slice (raw), normalizing it
954/// into it's canonical form.
955fn formatId(id: []const u8) std.fmt.Formatter(formatIdImpl) {
956 return .{ .data = id };
957}
958
959fn formatIdImpl(
960 id: []const u8,
961 comptime format: []const u8,
962 _: std.fmt.FormatOptions,
963 writer: anytype,
964) !void {
965 comptime assert(format.len == 0);
966
967 if (id[0] != '@') {
968 // no work needed
969 try writer.writeByte('"');
970 try writer.writeAll(id);
971 try writer.writeByte('"');
972
973 return;
974 }
975
976 // determine if the entire ID can be printed without escpaing
977 const inner = id[2 .. id.len - 1];
978 const needs_encode = brk: {
979 var i: usize = 0;
980 while (i < inner.len) {
981 switch (inner[i]) {
982 'a'...'z',
983 'A'...'Z',
984 '0'...'9',
985 '_',
986 '$',
987 => {
988 i += 1;
989 continue;
990 },
991 else => break :brk true,
992 0x80...0xFF => |first_byte| {
993 const len = std.unicode.utf8ByteSequenceLength(first_byte) catch
994 unreachable; // Invalid UTF-8
995 const codepoint = std.unicode.utf8Decode(inner[i..][0..len]) catch
996 unreachable; // Invalid UTF-8
997 if (!unicode_props.isIdContinue(codepoint))
998 break :brk true;
999 },
1000 }
1001 }
1002 break :brk false;
1003 };
1004
1005 if (needs_encode) {
1006 try writer.writeAll("@\"");
1007 } else {
1008 try writer.writeByte('"');
1009 }
1010
1011 // TODO: these two are "reasonable upper bounds" and not scientifically proven
1012 var decode_buf: [4096 * 2]u8 = undefined;
1013 //var encode_buf: [4096 * 3]u8 = undefined;
1014
1015 var it = std.mem.window(u8, inner, 4096, 4096);
1016 while (it.next()) |chunk| {
1017 const decoded = Tokenizer.decodeStringBuf(chunk, &decode_buf);
1018 if (needs_encode) {
1019 // TODO: This code is kind of duplicated, but there are small
1020 // differences in the string formatters. Ideally these are merged
1021 // under a common function that takes an argument of the 'mode' to
1022 // print the string in.
1023 const JSLink = @import("JsLink.zig");
1024 const indexOfNeedsEscape = JSLink.indexOfNeedsEscape;
1025 const canPrintWithoutEscape = JSLink.canPrintWithoutEscape;
1026
1027 var text = decoded;
1028 inner: while (text.len > 0) {
1029 const width = std.unicode.utf8ByteSequenceLength(text[0]) catch
1030 unreachable; // string is encoded incorrectly
1031
1032 const c: u21 = std.unicode.wtf8Decode(text[0..width]) catch
1033 unreachable; // string is encoded incorrectly
1034
1035 if (canPrintWithoutEscape(c)) {
1036 if (indexOfNeedsEscape(text)) |j| {
1037 try writer.writeAll(text[0..j]);
1038 text = text[j..];
1039 continue :inner;
1040 } else {
1041 try writer.writeAll(text);
1042 break :inner;
1043 }
1044 }
1045
1046 text = text[width..];
1047
1048 switch (c) {
1049 '\n' => {
1050 try writer.writeAll("\\n");
1051 },
1052 std.ascii.control_code.cr => {
1053 try writer.writeAll("\\r");
1054 },
1055 '\\' => {
1056 try writer.writeAll("\\\\");
1057 },
1058 '"' => {
1059 try writer.writeAll("\\\"");
1060 },
1061 '\t' => {
1062 try writer.writeAll("\\t");
1063 },
1064 else => {
1065 const hex_chars = "0123456789ABCDEF";
1066
1067 if (c < 0xFF) {
1068 try writer.writeAll(&[_]u8{
1069 '\\',
1070 'x',
1071 hex_chars[(c >> 4) & 0xF],
1072 hex_chars[c & 0xF],
1073 });
1074 } else if (c < 0xFFFF) {
1075 try writer.writeAll(&[_]u8{
1076 '\\',
1077 'u',
1078 '{',
1079 '0',
1080 'x',
1081 hex_chars[(c >> 12) & 0xF],
1082 hex_chars[(c >> 8) & 0xF],
1083 hex_chars[(c >> 4) & 0xF],
1084 hex_chars[c & 0xF],
1085 '}',
1086 });
1087 } else {
1088 const first_high_surrogate = 0xD800;
1089 const first_low_surrogate = 0xDC00;
1090
1091 const k = c - 0x10000;
1092 const lo = @as(usize, @intCast(first_high_surrogate + ((k >> 10) & 0x3FF)));
1093 const hi = @as(usize, @intCast(first_low_surrogate + (k & 0x3FF)));
1094
1095 try writer.writeAll(&[_]u8{
1096 '\\',
1097 'u',
1098 '{',
1099 hex_chars[lo >> 12],
1100 hex_chars[(lo >> 8) & 15],
1101 hex_chars[(lo >> 4) & 15],
1102 hex_chars[lo & 15],
1103 '}',
1104 '\\',
1105 'u',
1106 '{',
1107 '0',
1108 'x',
1109 hex_chars[hi >> 12],
1110 hex_chars[(hi >> 8) & 15],
1111 hex_chars[(hi >> 4) & 15],
1112 hex_chars[hi & 15],
1113 '}',
1114 });
1115 }
1116 },
1117 }
1118 }
1119 } else {
1120 try writer.writeAll(decoded);
1121 }
1122 }
1123
1124 try writer.writeByte('"');
1125}
1126
1127fn formatIdCode(id: []const u8) std.fmt.Formatter(formatIdCodeImpl) {
1128 return .{ .data = id };
1129}
1130
1131fn formatIdCodeImpl(
1132 id: []const u8,
1133 comptime format: []const u8,
1134 _: std.fmt.FormatOptions,
1135 writer: anytype,
1136) !void {
1137 comptime assert(format.len == 0);
1138 if (id[0] != '@') {
1139 // no decoding needed
1140 try writer.writeAll(id);
1141 return;
1142 }
1143
1144 try writer.writeAll("@\"");
1145
1146 try writer.writeByte('"');
1147}
1148
9391149const Report = @This();
9401150const std = @import("std");
9411151const ArrayListUnmanaged = std.ArrayListUnmanaged;
......@@ -944,6 +1154,8 @@ const assert = std.debug.assert;
9441154
9451155const builtin = @import("builtin");
9461156
1157const unicode_props = @import("unicode_props.zig");
1158
9471159const Tokenizer = @import("Tokenizer.zig");
9481160const ByteOffset = Tokenizer.ByteOffset;
9491161
src/core/Transform.zig+102-28
......@@ -6,6 +6,7 @@ string_bytes: std.ArrayListUnmanaged(u8) = .{},
66extra: ExtraStore = .{},
77
88scratch: std.ArrayListUnmanaged(u32) = .{},
9id_decode_buf: std.ArrayList(u8),
910
1011// TODO: refactor this away
1112// The new home for this memory should be pre-allocated on scratch,
......@@ -30,12 +31,15 @@ pub fn transform(
3031 const trace = tracer.trace(@src());
3132 defer trace.end();
3233
34 var sfa = std.heap.stackFallback(4096, gpa);
35
3336 var t: Transform = .{
3437 .ast = ast,
3538 .gpa = gpa,
3639 .report = .{ .gpa = gpa, .current_file = file },
40 .id_decode_buf = std.ArrayList(u8).initCapacity(sfa.get(), 4096) catch unreachable,
3741 };
38 errdefer t.deinit();
42 defer t.deinit();
3943
4044 try t.string_bytes.append(t.gpa, 0);
4145
......@@ -53,7 +57,6 @@ pub fn transform(
5357
5458 try t.insts.setCapacity(t.gpa, t.insts.len);
5559
56 defer t.deinit();
5760 return Teg{
5861 .insts = t.insts.toOwnedSlice(),
5962 .string_bytes = try t.string_bytes.toOwnedSlice(t.gpa),
......@@ -70,6 +73,7 @@ pub fn deinit(t: *Transform) void {
7073 t.string_bytes.deinit(t.gpa);
7174 t.insts.deinit(t.gpa);
7275 t.extra.deinit(t.gpa);
76 t.id_decode_buf.deinit();
7377 t.* = undefined;
7478}
7579
......@@ -124,8 +128,13 @@ const Scope = struct {
124128 }
125129 }
126130
127 fn deinit(s: *Scope, gpa: Allocator) void {
128 s.symbols.deinit(gpa);
131 fn deinit(s: *Scope, t: *Transform) void {
132 for (s.symbols.keys()) |k| {
133 if (!isSliceInBuffer(u8, t.ast.utf8_source, k)) {
134 t.gpa.free(k);
135 }
136 }
137 s.symbols.deinit(t.gpa);
129138 s.* = undefined;
130139 }
131140};
......@@ -338,8 +347,12 @@ fn previsitModuleStatement(t: *Transform, node: NodeIndex) Allocator.Error!void
338347 switch (tag) {
339348 .fn_decl => {
340349 const fn_decl = t.ast.fullFnDecl(node);
341 const gop = try t.scope().symbols.getOrPut(t.gpa, fn_decl.id);
350 var id, const id_allocated = try t.decodeIdentifierExtra(fn_decl.id);
351 if (id_allocated)
352 id = try t.gpa.dupe(u8, id);
353 const gop = try t.scope().symbols.getOrPut(t.gpa, id);
342354 if (gop.found_existing) {
355 t.gpa.free(id);
343356 try t.report.err(.duplicate_function, .{
344357 .node = node,
345358 .prev = gop.value_ptr.node,
......@@ -448,7 +461,7 @@ fn transformExpressionToBlock(
448461 switch (node_tag) {
449462 .id => {
450463 // Lower builtin ids without using a block
451 const id = t.ast.basicNodeSlice(node);
464 const id = try t.decodeIdentifier(t.ast.basicNodeSlice(node));
452465 if (BuiltInIds.get(id)) |ref| {
453466 return ref;
454467 }
......@@ -544,10 +557,15 @@ fn transformParamDecl(t: *Transform, decl: BlockContext, param: NodeIndex) Alloc
544557 const param_decl = t.ast.fullParamDecl(param);
545558 const value = try t.transformExpressionToBlock(decl, param_decl.type, false);
546559
560 var id, const id_allocated = try t.decodeIdentifierExtra(param_decl.id);
561 if (id_allocated)
562 id = try t.gpa.dupe(u8, id);
563
547564 // add `sym` into the scope so that the next param's type
548565 // can use this param's value, if known at compile time.
549 const sym = try t.scope().symbols.getOrPut(t.gpa, param_decl.id);
566 const sym = try t.scope().symbols.getOrPut(t.gpa, id);
550567 if (sym.found_existing) {
568 if (id_allocated) t.gpa.free(id);
551569 try t.report.err(.duplicate_param, .{
552570 .node = param,
553571 .prev = sym.value_ptr.node,
......@@ -685,13 +703,14 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc
685703 => unreachable, // not an expression
686704
687705 .id => {
688 const id = t.ast.basicNodeSlice(node);
689 if (BuiltInIds.get(id)) |ref| {
690 return ref;
706 const id = try t.decodeIdentifier(t.ast.basicNodeSlice(node));
707 if (id.len == 0 or mem.containsAtLeast(u8, id, 1, &[1]u8{'\x00'})) {
708 try t.report.err(.invalid_string_identifier, .{ .node = node });
709 return .void;
691710 }
692711
693 if (mem.startsWith(u8, id, "@")) {
694 @panic("TODO: string identifiers");
712 if (BuiltInIds.get(id)) |ref| {
713 return ref;
695714 }
696715
697716 const lookup = t.lookupId(id) orelse {
......@@ -1178,16 +1197,17 @@ fn transformAssignmentTarget(t: *Transform, decl: BlockContext, node: NodeIndex)
11781197 => unreachable, // not an expression
11791198
11801199 .id => {
1181 const id = t.ast.basicNodeSlice(node);
1200 const id = try t.decodeIdentifier(t.ast.basicNodeSlice(node));
11821201 assert(!mem.eql(u8, id, "_")); // discards are not assignment targets
11831202
1184 if (BuiltInIds.has(id)) {
1185 try t.report.err(.assign_builtin, .{ .node = node });
1203 if (id.len == 0 or mem.indexOfScalar(u8, id, '\x00') != null) {
1204 try t.report.err(.invalid_string_identifier, .{ .node = node });
11861205 return null;
11871206 }
11881207
1189 if (mem.startsWith(u8, id, "@")) {
1190 @panic("TODO: string identifiers");
1208 if (BuiltInIds.has(id)) {
1209 try t.report.err(.assign_builtin, .{ .node = node });
1210 return null;
11911211 }
11921212
11931213 const lookup = t.lookupId(id) orelse {
......@@ -1251,23 +1271,30 @@ fn transformBlockStatement(
12511271 .var_decl => {
12521272 const var_decl = t.ast.fullVarDecl(node);
12531273
1254 if (mem.eql(u8, var_decl.id, "_")) {
1274 var id, const id_allocated = try t.decodeIdentifierExtra(var_decl.id);
1275 if (id.len == 0 or mem.indexOfScalar(u8, id, '\x00') != null) {
1276 try t.report.err(.invalid_string_identifier, .{ .node = node });
1277 return .void;
1278 }
1279
1280 if (mem.eql(u8, id, "_")) {
12551281 try t.report.warn(.invalid_discard, .{ .node = node });
12561282
12571283 const ref = switch (t.ast.nodeTag(var_decl.init)) {
12581284 .id => {
1259 const id = t.ast.basicNodeSlice(node);
1260 if (BuiltInIds.has(id)) {
1261 try t.report.warn(.discard_builtin, .{ .node = node });
1285 const inner_id = try t.decodeIdentifier(t.ast.basicNodeSlice(var_decl.init));
1286 if (inner_id.len == 0 or mem.indexOfScalar(u8, inner_id, '\x00') != null) {
1287 try t.report.err(.invalid_string_identifier, .{ .node = var_decl.init });
12621288 return .void;
12631289 }
12641290
1265 if (mem.startsWith(u8, id, "@")) {
1266 @panic("TODO: string identifiers");
1291 if (BuiltInIds.has(inner_id)) {
1292 try t.report.warn(.discard_builtin, .{ .node = var_decl.init });
1293 return .void;
12671294 }
12681295
1269 const lookup = t.lookupId(id) orelse {
1270 try t.report.err(.unknown_identifier, .{ .node = node });
1296 const lookup = t.lookupId(inner_id) orelse {
1297 try t.report.err(.unknown_identifier, .{ .node = var_decl.init });
12711298 return .void;
12721299 };
12731300
......@@ -1288,7 +1315,16 @@ fn transformBlockStatement(
12881315 });
12891316 }
12901317
1291 if (BuiltInIds.has(var_decl.id)) {
1318 if (id_allocated) {
1319 // consider arena?
1320 id = try t.gpa.dupe(u8, id);
1321 } else {
1322 // assert that the memory is in the source code. this is how
1323 // we can identify later if it is owned or not.
1324 assert(isSliceInBuffer(u8, t.ast.utf8_source, id));
1325 }
1326
1327 if (BuiltInIds.has(id)) {
12921328 try t.report.err(.shadow_builtin_variable, .{ .node = node });
12931329 return .void;
12941330 }
......@@ -1324,10 +1360,11 @@ fn transformBlockStatement(
13241360 }),
13251361 });
13261362
1327 const gop = try t.scope().symbols.getOrPut(t.gpa, var_decl.id);
1363 const gop = try t.scope().symbols.getOrPut(t.gpa, id);
13281364
13291365 // if re-declaring, apply unused warnings now
13301366 if (gop.found_existing) {
1367 if (id_allocated) t.gpa.free(id);
13311368 try gop.value_ptr.addWarnings(t);
13321369 }
13331370
......@@ -1435,7 +1472,7 @@ fn pushScope(t: *Transform) !void {
14351472}
14361473
14371474fn popScope(t: *Transform) void {
1438 t.scope().deinit(t.gpa);
1475 t.scope().deinit(t);
14391476 t.scopes.items[t.scopes.items.len - 1] = undefined;
14401477 t.scopes.items.len -= 1;
14411478}
......@@ -1552,6 +1589,43 @@ fn addDecodedSlicedString(t: *Transform, escaped: []const u8) !SlicedString {
15521589 return t.addSlicedString(decoded);
15531590}
15541591
1592/// Decodes an identifier, storing it into a buffer reused across the transform
1593/// pass Memory is essentially free for this operation, at the cost of the
1594/// return value only being valid until the next call.
1595fn decodeIdentifierExtra(t: *Transform, raw_token: []const u8) !struct { []const u8, bool } {
1596 assert(raw_token.len > 0);
1597
1598 if (raw_token[0] != '@') {
1599 return .{ raw_token, false };
1600 }
1601
1602 assert(raw_token[1] == '"');
1603 assert(raw_token[raw_token.len - 1] == '"');
1604
1605 const inner = raw_token[2 .. raw_token.len - 1];
1606 const len = Tokenizer.decodeStringCount(inner);
1607
1608 try t.id_decode_buf.ensureTotalCapacityPrecise(len);
1609 @memset(t.id_decode_buf.items, undefined);
1610 t.id_decode_buf.items.len = len;
1611
1612 return .{
1613 Tokenizer.decodeStringBuf(inner, t.id_decode_buf.items),
1614 true,
1615 };
1616}
1617
1618fn decodeIdentifier(t: *Transform, raw_token: []const u8) ![]const u8 {
1619 return (try t.decodeIdentifierExtra(raw_token)).@"0";
1620}
1621
1622fn isSliceInBuffer(comptime T: type, haystack: []const T, needle: []const T) bool {
1623 return (@intFromPtr(haystack.ptr) <=
1624 @intFromPtr(needle.ptr) and
1625 (@intFromPtr(needle.ptr) + needle.len * @sizeOf(T)) <=
1626 (@intFromPtr(haystack.ptr) + haystack.len * @sizeOf(T)));
1627}
1628
15551629const Transform = @This();
15561630const std = @import("std");
15571631const mem = std.mem;