Files
bun.sh/src/string_joiner.zig
Dylan Conway 661355546a zig upgrade (#3667)
* upgrade

* more fixes

* Bump Zig

---------

Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
2023-07-18 01:20:20 -07:00

166 lines
4.3 KiB
Zig

/// Rope-like data structure for joining many small strings into one big string.
const std = @import("std");
const default_allocator = bun.default_allocator;
const bun = @import("root").bun;
const string = bun.string;
const Allocator = std.mem.Allocator;
const ObjectPool = @import("./pool.zig").ObjectPool;
const Joiner = @This();
const Joinable = struct {
offset: u31 = 0,
needs_deinit: bool = false,
allocator: Allocator = undefined,
slice: []const u8 = "",
pub const Pool = ObjectPool(Joinable, null, true, 4);
};
len: usize = 0,
use_pool: bool = true,
node_allocator: Allocator = undefined,
head: ?*Joinable.Pool.Node = null,
tail: ?*Joinable.Pool.Node = null,
/// Avoid an extra pass over the list when joining
watcher: Watcher = .{},
pub const Watcher = struct {
input: []const u8 = "",
estimated_count: u32 = 0,
needs_newline: bool = false,
};
pub fn done(this: *Joiner, allocator: Allocator) ![]u8 {
if (this.head == null) {
var out: []u8 = &[_]u8{};
return out;
}
var slice = try allocator.alloc(u8, this.len);
var remaining = slice;
var el_ = this.head;
while (el_) |join| {
const to_join = join.data.slice[join.data.offset..];
@memcpy(remaining[0..to_join.len], to_join);
remaining = remaining[@min(remaining.len, to_join.len)..];
var prev = join;
el_ = join.next;
if (prev.data.needs_deinit) {
prev.data.allocator.free(prev.data.slice);
prev.data = Joinable{};
}
if (this.use_pool) prev.release();
}
return slice[0 .. slice.len - remaining.len];
}
pub fn doneWithEnd(this: *Joiner, allocator: Allocator, end: []const u8) ![]u8 {
if (this.head == null and end.len == 0) {
return &[_]u8{};
}
if (this.head == null) {
var slice = try allocator.alloc(u8, end.len);
@memcpy(slice[0..end.len], end);
return slice;
}
var slice = try allocator.alloc(u8, this.len + end.len);
var remaining = slice;
var el_ = this.head;
while (el_) |join| {
const to_join = join.data.slice[join.data.offset..];
@memcpy(remaining[0..to_join.len], to_join);
remaining = remaining[@min(remaining.len, to_join.len)..];
var prev = join;
el_ = join.next;
if (prev.data.needs_deinit) {
prev.data.allocator.free(prev.data.slice);
prev.data = Joinable{};
}
if (this.use_pool) prev.release();
}
@memcpy(remaining[0..end.len], end);
remaining = remaining[@min(remaining.len, end.len)..];
return slice[0 .. slice.len - remaining.len];
}
pub fn lastByte(this: *const Joiner) u8 {
if (this.tail) |tail| {
const slice = tail.data.slice[tail.data.offset..];
return if (slice.len > 0) slice[slice.len - 1] else 0;
}
return 0;
}
pub fn push(this: *Joiner, slice: string) void {
this.append(slice, 0, null);
}
pub fn ensureNewlineAtEnd(this: *Joiner) void {
if (this.watcher.needs_newline) {
this.watcher.needs_newline = false;
this.push("\n");
}
}
pub fn append(this: *Joiner, slice: string, offset: u32, allocator: ?Allocator) void {
const data = slice[offset..];
this.len += @as(u32, @truncate(data.len));
var new_tail = if (this.use_pool)
Joinable.Pool.get(default_allocator)
else
(this.node_allocator.create(Joinable.Pool.Node) catch unreachable);
this.watcher.estimated_count += @intFromBool(
this.watcher.input.len > 0 and
bun.strings.contains(data, this.watcher.input),
);
this.watcher.needs_newline = this.watcher.input.len > 0 and data.len > 0 and
data[data.len - 1] != '\n';
new_tail.* = .{
.allocator = default_allocator,
.data = Joinable{
.offset = @as(u31, @truncate(offset)),
.allocator = allocator orelse undefined,
.needs_deinit = allocator != null,
.slice = slice,
},
};
var tail = this.tail orelse {
this.tail = new_tail;
this.head = new_tail;
return;
};
tail.next = new_tail;
this.tail = new_tail;
}
pub fn contains(this: *const Joiner, slice: string) bool {
var el = this.head;
while (el) |node| {
el = node.next;
if (bun.strings.contains(node.data.slice[node.data.offset..], slice)) return true;
}
return false;
}