allow consumers to control allocation more granularly
All checks were successful
Generic zig build / build (push) Successful in 21s

This commit is contained in:
Emil Lerch 2026-05-27 13:38:38 -07:00
parent 12b755660e
commit e6b6691a05
Signed by: lobo
GPG key ID: A7B62D657EF764F8
2 changed files with 29 additions and 8 deletions

View file

@ -74,7 +74,7 @@ pub fn main(init: std.process.Init) !void {
if (std.mem.eql(u8, format, "srf")) {
var reader = std.Io.Reader.fixed(data.items);
const records = try srf.parse(&reader, allocator, .{ .alloc_strings = false });
const records = try srf.parse(&reader, allocator, .{ .parse_allocator = .none });
defer records.deinit();
} else if (std.mem.eql(u8, format, "jsonl")) {
var lines = std.mem.splitScalar(u8, data.items, '\n');

View file

@ -1057,10 +1057,29 @@ pub const ParseOptions = struct {
diagnostics: ?*Diagnostics = null,
/// By default, the parser will copy data so it is safe to free the original
/// This will impose about 8% overhead, but be safer. If you do not require
/// this safety, set alloc_strings to false. Setting this to false is the
/// equivalent of the "Leaky" parsing functions of std.json
alloc_strings: bool = true,
/// buffer or use with streaming readers. This will impose about 8% overhead,
/// and ties the lifetime of any strings to the deinit() method. For
/// fixed buffer parsing, consider using .none, which will not allocate
/// strings. More complex use cases can use their own allocator for control
/// over string lifetime
parse_allocator: ParseAllocator = .parse_arena,
};
pub const ParseAllocator = union(enum) {
/// No allocator. Lifetime of any strings parsed is tied to the underlying
/// data passed to the reader. This is most appropriate when the caller
/// uses a fixed buffer, and is equivalent of the "Leaky" parsing
/// functions of std.json
none,
/// Use the arena allocator created by the parser to copy any strings.
/// This ties the lifetime of any data parsed to the parser deinit()
/// function. Imposes about 8% overhead compared to "none".
parse_arena,
/// Parser will use the caller-supplied allocator, providing the most
/// flexibility over lifetime. Overhead will be contingent on the allocator
/// used. If the allocator is an arena allocator, assume 8% overhead over
/// "none". It is likely a fixed buffer allocator would be somewhat less.
allocator: std.mem.Allocator,
};
const Directive = union(enum) {
@ -1409,9 +1428,11 @@ pub fn iterator(reader: *std.Io.Reader, allocator: std.mem.Allocator, options: P
}
inline fn dupe(allocator: std.mem.Allocator, options: ParseOptions, data: []const u8) ParseError![]const u8 {
if (options.alloc_strings)
return try allocator.dupe(u8, data);
return data;
switch (options.parse_allocator) {
.none => return data,
.parse_arena => return try allocator.dupe(u8, data),
.allocator => |a| return try a.dupe(u8, data),
}
}
/// Logs a parse error to diagnostics. Note that the allocator provided should
/// *NOT* be an arena, as the message must outlive the parse results, which will