authorgravatar for hello@nektro.netMeghan Denny <hello@nektro.net> 2026-01-22 16:11:00 -08:00
committergravatar for hello@nektro.netMeghan Denny <hello@nektro.net> 2026-01-22 16:11:00 -08:00
log9392e9e3186ef5f84d2a37432a0116fbd96a803d
tree3e9519c022e8c42b084278158be4aa5cf6ed257a
parente5ac23bf48c1d0ef54b95490723a0419b68abc6c

move ArrayLists into single StaticManyArrayList


1 files changed, 77 insertions(+), 38 deletions(-)

url.zig+77-38
...@@ -78,27 +78,22 @@ pub const URL = struct {...@@ -78,27 +78,22 @@ pub const URL = struct {
7878
79 var href = ManyArrayList(8 + 7, u8).init(alloc);79 var href = ManyArrayList(8 + 7, u8).init(alloc);
80 defer href.deinit();80 defer href.deinit();
81 // scheme
81 // :82 // :
82 // //83 // //
83 var username = std.ArrayList(u8).init(alloc);84 // username
84 defer username.deinit();
85 // :85 // :
86 var password = std.ArrayList(u8).init(alloc);86 // password
87 defer password.deinit();
88 // @87 // @
89 var host: Host = .{ .name = "" };88 var host: Host = .{ .name = "" };
90 defer if (host == .name) alloc.free(host.name);89 defer if (host == .name) alloc.free(host.name);
91 // :90 // :
92 var port = std.ArrayList(u8).init(alloc);91 // port
93 defer port.deinit();92 // path
94 var path = std.ArrayList(u8).init(alloc);
95 defer path.deinit();
96 // ?93 // ?
97 var query = std.ArrayList(u8).init(alloc);94 // query
98 defer query.deinit();
99 // #95 // #
100 var fragment = std.ArrayList(u8).init(alloc);96 // fragment
101 defer fragment.deinit();
10297
103 // 9.98 // 9.
104 while (true) {99 while (true) {
...@@ -172,7 +167,7 @@ pub const URL = struct {...@@ -172,7 +167,7 @@ pub const URL = struct {
172 }167 }
173 // 9. Otherwise, set url’s path to the empty string and set state to opaque path state.168 // 9. Otherwise, set url’s path to the empty string and set state to opaque path state.
174 else {169 else {
175 path.clearRetainingCapacity();170 href.clear(10);
176 state = .opaque_path;171 state = .opaque_path;
177 }172 }
178 }173 }
...@@ -248,18 +243,18 @@ pub const URL = struct {...@@ -248,18 +243,18 @@ pub const URL = struct {
248 // try query.appendSlice(base.?.query.?);243 // try query.appendSlice(base.?.query.?);
249 // 2. If c is U+003F (?), then set url’s query to the empty string, and state to query state.244 // 2. If c is U+003F (?), then set url’s query to the empty string, and state to query state.
250 if (c == '?') {245 if (c == '?') {
251 query.clearRetainingCapacity();246 href.clear(12);
252 state = .query;247 state = .query;
253 }248 }
254 // 3. Otherwise, if c is U+0023 (#), set url’s fragment to the empty string and state to fragment state.249 // 3. Otherwise, if c is U+0023 (#), set url’s fragment to the empty string and state to fragment state.
255 else if (c == '#') {250 else if (c == '#') {
256 fragment.clearRetainingCapacity();251 href.clear(14);
257 state = .fragment;252 state = .fragment;
258 }253 }
259 // 4. Otherwise, if c is not the EOF code point:254 // 4. Otherwise, if c is not the EOF code point:
260 else if (i < length) {255 else if (i < length) {
261 // 1. Set url’s query to null.256 // 1. Set url’s query to null.
262 query.clearRetainingCapacity();257 href.clear(12);
263 // 2. Shorten url’s path.258 // 2. Shorten url’s path.
264 @panic("TODO");259 @panic("TODO");
265 // 3. Set state to path state and decrease pointer by 1.260 // 3. Set state to path state and decrease pointer by 1.
...@@ -380,6 +375,7 @@ pub const URL = struct {...@@ -380,6 +375,7 @@ pub const URL = struct {
380 // 4. If host is failure, then return failure.375 // 4. If host is failure, then return failure.
381 const h = try parseHost(alloc, buffer.items, !isSchemeSpecial(href.items(0)));376 const h = try parseHost(alloc, buffer.items, !isSchemeSpecial(href.items(0)));
382 // 5. Set url’s host to host, buffer to the empty string, and state to port state.377 // 5. Set url’s host to host, buffer to the empty string, and state to port state.
378 // try href.set(7, h);
383 host = h;379 host = h;
384 buffer.clearRetainingCapacity();380 buffer.clearRetainingCapacity();
385 state = .port;381 state = .port;
...@@ -395,11 +391,12 @@ pub const URL = struct {...@@ -395,11 +391,12 @@ pub const URL = struct {
395 // 1. If url is special and buffer is the empty string, host-missing validation error, return failure.391 // 1. If url is special and buffer is the empty string, host-missing validation error, return failure.
396 if (isSchemeSpecial(href.items(0)) and buffer.items.len == 0) return error.InvalidURL392 if (isSchemeSpecial(href.items(0)) and buffer.items.len == 0) return error.InvalidURL
397 // 2. Otherwise, if state override is given, buffer is the empty string, and either url includes credentials or url’s port is non-null, then return failure.393 // 2. Otherwise, if state override is given, buffer is the empty string, and either url includes credentials or url’s port is non-null, then return failure.
398 else if (state_override != null and buffer.items.len == 0 and (username.items.len > 0 or password.items.len > 0)) return error.InvalidURL;394 else if (state_override != null and buffer.items.len == 0 and (href.items(3).len > 0 or href.items(5).len > 0)) return error.InvalidURL;
399 // 3. Let host be the result of host parsing buffer with url is not special.395 // 3. Let host be the result of host parsing buffer with url is not special.
400 // 4. If host is failure, then return failure.396 // 4. If host is failure, then return failure.
401 const h = try parseHost(alloc, buffer.items, !isSchemeSpecial(href.items(0)));397 const h = try parseHost(alloc, buffer.items, !isSchemeSpecial(href.items(0)));
402 // 5. Set url’s host to host, buffer to the empty string, and state to path start state.398 // 5. Set url’s host to host, buffer to the empty string, and state to path start state.
399 // try href.set(7, h);
403 host = h;400 host = h;
404 buffer.clearRetainingCapacity();401 buffer.clearRetainingCapacity();
405 state = .path_start;402 state = .path_start;
...@@ -433,7 +430,7 @@ pub const URL = struct {...@@ -433,7 +430,7 @@ pub const URL = struct {
433 // 2. If port is not a 16-bit unsigned integer, port-out-of-range validation error, return failure.430 // 2. If port is not a 16-bit unsigned integer, port-out-of-range validation error, return failure.
434 const p = std.fmt.parseInt(u16, buffer.items, 10) catch return error.InvalidURL;431 const p = std.fmt.parseInt(u16, buffer.items, 10) catch return error.InvalidURL;
435 // 3. Set url’s port to null, if port is url’s scheme’s default port; otherwise to port.432 // 3. Set url’s port to null, if port is url’s scheme’s default port; otherwise to port.
436 if (schemeDefaultPort(href.items(0)) != p) try port.writer().print("{d}", .{p});433 if (schemeDefaultPort(href.items(0)) != p) try href.print(9, "{d}", .{p});
437 // 4. Set buffer to the empty string.434 // 4. Set buffer to the empty string.
438 buffer.clearRetainingCapacity();435 buffer.clearRetainingCapacity();
439 // 5. If state override is given, then return.436 // 5. If state override is given, then return.
...@@ -456,7 +453,7 @@ pub const URL = struct {...@@ -456,7 +453,7 @@ pub const URL = struct {
456 // 1. Set url’s scheme to "file".453 // 1. Set url’s scheme to "file".
457 try href.set(0, "file");454 try href.set(0, "file");
458 // 2. Set url’s host to the empty string.455 // 2. Set url’s host to the empty string.
459 host = .{ .name = "" };456 href.clear(7);
460 // 3. If c is U+002F (/) or U+005C (\), then:457 // 3. If c is U+002F (/) or U+005C (\), then:
461 if (c == '/' or c == '\\') {458 if (c == '/' or c == '\\') {
462 // 1. If c is U+005C (\), invalid-reverse-solidus validation error.459 // 1. If c is U+005C (\), invalid-reverse-solidus validation error.
...@@ -472,18 +469,18 @@ pub const URL = struct {...@@ -472,18 +469,18 @@ pub const URL = struct {
472 // try query.appendSlice(base.?.query.?);469 // try query.appendSlice(base.?.query.?);
473 // 2. If c is U+003F (?), then set url’s query to the empty string and state to query state.470 // 2. If c is U+003F (?), then set url’s query to the empty string and state to query state.
474 if (c == '?') {471 if (c == '?') {
475 query.clearRetainingCapacity();472 href.clear(12);
476 state = .query;473 state = .query;
477 }474 }
478 // 3. Otherwise, if c is U+0023 (#), set url’s fragment to the empty string and state to fragment state.475 // 3. Otherwise, if c is U+0023 (#), set url’s fragment to the empty string and state to fragment state.
479 else if (c == '#') {476 else if (c == '#') {
480 fragment.clearRetainingCapacity();477 href.clear(14);
481 state = .fragment;478 state = .fragment;
482 }479 }
483 // 4. Otherwise, if c is not the EOF code point:480 // 4. Otherwise, if c is not the EOF code point:
484 else if (i < length) {481 else if (i < length) {
485 // 1. Set url’s query to null.482 // 1. Set url’s query to null.
486 query.clearRetainingCapacity();483 href.clear(12);
487 // 2. If the code point substring from pointer to the end of input does not start with a Windows drive letter, then shorten url’s path.484 // 2. If the code point substring from pointer to the end of input does not start with a Windows drive letter, then shorten url’s path.
488 // 3. Otherwise:485 // 3. Otherwise:
489 // This is a (platform-independent) Windows drive letter quirk.486 // This is a (platform-independent) Windows drive letter quirk.
...@@ -546,7 +543,7 @@ pub const URL = struct {...@@ -546,7 +543,7 @@ pub const URL = struct {
546 // 2. Otherwise, if buffer is the empty string, then:543 // 2. Otherwise, if buffer is the empty string, then:
547 else if (buffer.items.len == 0) {544 else if (buffer.items.len == 0) {
548 // 1. Set url’s host to the empty string.545 // 1. Set url’s host to the empty string.
549 host = .{ .name = "" };546 href.clear(7);
550 // 2. If state override is given, then return.547 // 2. If state override is given, then return.
551 if (state_override != null) break;548 if (state_override != null) break;
552 // 3. Set state to path start state.549 // 3. Set state to path start state.
...@@ -563,6 +560,7 @@ pub const URL = struct {...@@ -563,6 +560,7 @@ pub const URL = struct {
563 h = .{ .name = "" };560 h = .{ .name = "" };
564 }561 }
565 // 4. Set url’s host to host.562 // 4. Set url’s host to host.
563 // try href.set(7, h);
566 host = h;564 host = h;
567 // 5. If state override is given, then return.565 // 5. If state override is given, then return.
568 if (state_override != null) break;566 if (state_override != null) break;
...@@ -590,12 +588,12 @@ pub const URL = struct {...@@ -590,12 +588,12 @@ pub const URL = struct {
590 }588 }
591 // 2. Otherwise, if state override is not given and c is U+003F (?), set url’s query to the empty string and state to query state.589 // 2. Otherwise, if state override is not given and c is U+003F (?), set url’s query to the empty string and state to query state.
592 else if (state_override == null and c == '?') {590 else if (state_override == null and c == '?') {
593 query.clearRetainingCapacity();591 href.clear(12);
594 state = .query;592 state = .query;
595 }593 }
596 // 3. Otherwise, if state override is not given and c is U+0023 (#), set url’s fragment to the empty string and state to fragment state.594 // 3. Otherwise, if state override is not given and c is U+0023 (#), set url’s fragment to the empty string and state to fragment state.
597 else if (state_override == null and c == '#') {595 else if (state_override == null and c == '#') {
598 fragment.clearRetainingCapacity();596 href.clear(14);
599 state = .fragment;597 state = .fragment;
600 }598 }
601 // 4. Otherwise, if c is not the EOF code point:599 // 4. Otherwise, if c is not the EOF code point:
...@@ -638,22 +636,22 @@ pub const URL = struct {...@@ -638,22 +636,22 @@ pub const URL = struct {
638 else if (!isSingleDotPathSeg(buffer.items)) {636 else if (!isSingleDotPathSeg(buffer.items)) {
639 // 1. If url’s scheme is "file", url’s path is empty, and buffer is a Windows drive letter, then replace the second code point in buffer with U+003A (:).637 // 1. If url’s scheme is "file", url’s path is empty, and buffer is a Windows drive letter, then replace the second code point in buffer with U+003A (:).
640 // > This is a (platform-independent) Windows drive letter quirk.638 // > This is a (platform-independent) Windows drive letter quirk.
641 if (std.mem.eql(u8, href.items(0), "file") and path.items.len == 0 and isWindowsDriveLetter(buffer.items)) {639 if (std.mem.eql(u8, href.items(0), "file") and href.items(10).len == 0 and isWindowsDriveLetter(buffer.items)) {
642 buffer.items[1] = ':';640 buffer.items[1] = ':';
643 }641 }
644 // 2. Append buffer to url’s path.642 // 2. Append buffer to url’s path.
645 try path.appendSlice(buffer.items);643 try href.appendSlice(10, buffer.items);
646 }644 }
647 // 5. Set buffer to the empty string.645 // 5. Set buffer to the empty string.
648 buffer.clearRetainingCapacity();646 buffer.clearRetainingCapacity();
649 // 6. If c is U+003F (?), then set url’s query to the empty string and state to query state.647 // 6. If c is U+003F (?), then set url’s query to the empty string and state to query state.
650 if (c == '?') {648 if (c == '?') {
651 query.clearRetainingCapacity();649 href.clear(12);
652 state = .query;650 state = .query;
653 }651 }
654 // 7. If c is U+0023 (#), then set url’s fragment to the empty string and state to fragment state.652 // 7. If c is U+0023 (#), then set url’s fragment to the empty string and state to fragment state.
655 if (c == '#') {653 if (c == '#') {
656 fragment.clearRetainingCapacity();654 href.clear(14);
657 state = .fragment;655 state = .fragment;
658 }656 }
659 }657 }
...@@ -676,12 +674,12 @@ pub const URL = struct {...@@ -676,12 +674,12 @@ pub const URL = struct {
676 .opaque_path => {674 .opaque_path => {
677 // 1. If c is U+003F (?), then set url’s query to the empty string and state to query state.675 // 1. If c is U+003F (?), then set url’s query to the empty string and state to query state.
678 if (c == '?') {676 if (c == '?') {
679 query.clearRetainingCapacity();677 href.clear(12);
680 state = .query;678 state = .query;
681 }679 }
682 // 2. Otherwise, if c is U+0023 (#), then set url’s fragment to the empty string and state to fragment state.680 // 2. Otherwise, if c is U+0023 (#), then set url’s fragment to the empty string and state to fragment state.
683 else if (c == '#') {681 else if (c == '#') {
684 fragment.clearRetainingCapacity();682 href.clear(14);
685 state = .fragment;683 state = .fragment;
686 }684 }
687 // 3. Otherwise, if c is U+0020 SPACE:685 // 3. Otherwise, if c is U+0020 SPACE:
...@@ -689,11 +687,11 @@ pub const URL = struct {...@@ -689,11 +687,11 @@ pub const URL = struct {
689 // 1. If remaining starts with U+003F (?) or U+003F (#), then append "%20" to url’s path.687 // 1. If remaining starts with U+003F (?) or U+003F (#), then append "%20" to url’s path.
690 const rem = inputl.items[i + l(c) ..];688 const rem = inputl.items[i + l(c) ..];
691 if (std.mem.startsWith(u8, rem, "?") or std.mem.startsWith(u8, rem, "#")) {689 if (std.mem.startsWith(u8, rem, "?") or std.mem.startsWith(u8, rem, "#")) {
692 try path.appendSlice("%20");690 try href.appendSlice(10, "%20");
693 }691 }
694 // 2. Otherwise, append U+0020 SPACE to url’s path.692 // 2. Otherwise, append U+0020 SPACE to url’s path.
695 else {693 else {
696 try path.append(' ');694 try href.appendSlice(10, " ");
697 }695 }
698 }696 }
699 // 4. Otherwise, if c is not the EOF code point:697 // 4. Otherwise, if c is not the EOF code point:
...@@ -703,7 +701,7 @@ pub const URL = struct {...@@ -703,7 +701,7 @@ pub const URL = struct {
703 // 2. If c is U+0025 (%) and remaining does not start with two ASCII hex digits, invalid-URL-unit validation error.701 // 2. If c is U+0025 (%) and remaining does not start with two ASCII hex digits, invalid-URL-unit validation error.
704 {}702 {}
705 // 3. UTF-8 percent-encode c using the C0 control percent-encode set and append the result to url’s path.703 // 3. UTF-8 percent-encode c using the C0 control percent-encode set and append the result to url’s path.
706 try percentEncodeScalarAL(&path, inputl.items[i..][0..l(c)], is_c0control_percent_char);704 try percentEncodeScalarML(&href, 10, inputl.items[i..][0..l(c)], is_c0control_percent_char);
707 }705 }
708 },706 },
709 .query => {707 .query => {
...@@ -720,15 +718,15 @@ pub const URL = struct {...@@ -720,15 +718,15 @@ pub const URL = struct {
720 // 2. Percent-encode after encoding, with encoding, buffer, and queryPercentEncodeSet, and append the result to url’s query.718 // 2. Percent-encode after encoding, with encoding, buffer, and queryPercentEncodeSet, and append the result to url’s query.
721 // > This operation cannot be invoked code-point-for-code-point due to the stateful ISO-2022-JP encoder.719 // > This operation cannot be invoked code-point-for-code-point due to the stateful ISO-2022-JP encoder.
722 if (isSchemeSpecial(href.items(0))) {720 if (isSchemeSpecial(href.items(0))) {
723 try percentEncodeAL(&query, buffer.items, is_special_query_percent_char);721 try percentEncodeML(&href, 12, buffer.items, is_special_query_percent_char);
724 } else {722 } else {
725 try percentEncodeAL(&query, buffer.items, is_query_percent_char);723 try percentEncodeML(&href, 12, buffer.items, is_query_percent_char);
726 }724 }
727 // 3. Set buffer to the empty string.725 // 3. Set buffer to the empty string.
728 buffer.clearRetainingCapacity();726 buffer.clearRetainingCapacity();
729 // 4. If c is U+0023 (#), then set url’s fragment to the empty string and state to fragment state.727 // 4. If c is U+0023 (#), then set url’s fragment to the empty string and state to fragment state.
730 if (c == '#') {728 if (c == '#') {
731 fragment.clearRetainingCapacity();729 href.clear(14);
732 state = .fragment;730 state = .fragment;
733 }731 }
734 }732 }
...@@ -750,7 +748,7 @@ pub const URL = struct {...@@ -750,7 +748,7 @@ pub const URL = struct {
750 // 2. If c is U+0025 (%) and remaining does not start with two ASCII hex digits, invalid-URL-unit validation error.748 // 2. If c is U+0025 (%) and remaining does not start with two ASCII hex digits, invalid-URL-unit validation error.
751 {}749 {}
752 // 3. UTF-8 percent-encode c using the fragment percent-encode set and append the result to url’s fragment.750 // 3. UTF-8 percent-encode c using the fragment percent-encode set and append the result to url’s fragment.
753 try percentEncodeScalarAL(&fragment, inputl.items[i..][0..l(c)], is_fragment_percent_char);751 try percentEncodeScalarML(&href, 14, inputl.items[i..][0..l(c)], is_fragment_percent_char);
754 }752 }
755 },753 },
756 }754 }
...@@ -1381,6 +1379,29 @@ fn percentEncodeAL(list: *std.ArrayList(u8), input: []const u8, comptime set: fn...@@ -1381,6 +1379,29 @@ fn percentEncodeAL(list: *std.ArrayList(u8), input: []const u8, comptime set: fn
1381 }1379 }
1382 }1380 }
1383}1381}
1382fn percentEncodeScalarML(list: *ManyArrayList(15, u8), n: usize, cp: []const u8, comptime set: fn (u8) bool) !void {
1383 if (set(cp[0])) {
1384 for (cp) |b| {
1385 try list.appendSlice(n, &.{'%'});
1386 try list.print(n, "{d:0<2}", .{b});
1387 }
1388 } else {
1389 try list.appendSlice(n, &.{cp[0]});
1390 }
1391}
1392fn percentEncodeML(list: *ManyArrayList(15, u8), n: usize, input: []const u8, comptime set: fn (u8) bool) !void {
1393 var it = std.unicode.Utf8View.initUnchecked(input).iterator();
1394 while (it.nextCodepointSlice()) |sl| {
1395 if (set(sl[0])) {
1396 for (sl) |b| {
1397 try list.appendSlice(n, &.{'%'});
1398 try list.print(n, "{d:0<2}", .{b});
1399 }
1400 } else {
1401 try list.appendSlice(n, sl);
1402 }
1403 }
1404}
13841405
1385pub fn ManyArrayList(N: usize, T: type) type {1406pub fn ManyArrayList(N: usize, T: type) type {
1386 return struct {1407 return struct {
...@@ -1409,5 +1430,23 @@ pub fn ManyArrayList(N: usize, T: type) type {...@@ -1409,5 +1430,23 @@ pub fn ManyArrayList(N: usize, T: type) type {
1409 const len = self.lengths[n];1430 const len = self.lengths[n];
1410 return self.list.items[real_n..][0..len];1431 return self.list.items[real_n..][0..len];
1411 }1432 }
1433
1434 pub fn clear(self: *@This(), n: usize) void {
1435 const real_n = extras.sum(usize, self.lengths[0..n]);
1436 self.list.replaceRangeAssumeCapacity(real_n, self.lengths[n], &.{});
1437 self.lengths[n] = 0;
1438 }
1439
1440 pub fn print(self: *@This(), n: usize, comptime fmt: []const u8, args: anytype) !void {
1441 var buffer: [64]u8 = undefined;
1442 const slice = std.fmt.bufPrint(&buffer, fmt, args) catch unreachable;
1443 return self.appendSlice(n, slice);
1444 }
1445
1446 pub fn appendSlice(self: *@This(), n: usize, slice: []const T) !void {
1447 const real_n = extras.sum(usize, self.lengths[0 .. n + 1]);
1448 try self.list.insertSlice(real_n, slice);
1449 self.lengths[n] += slice.len;
1450 }
1412 };1451 };
1413}1452}