This was kind of an annoying change to make since 0.11.0 has issues where it will point to the wrong srcloc on compile errors in generic code (which this 100% is) fortunately fixed in master. The motivation for this change is that the arg vector already contains 0-terminated strings, so we can avoid a lot of copies. This makes forwarding command-line arguments to C-functions that expect zero-terminated strings much more straightforward, and they also automatically decay to normal slices. Unfortunately, environment variable values are NOT zero-terminated, so they are currently copied with zero-termination. This seems to be the fault of Windows/WASI, both of which already are performing allocations (Windows to convert from UTF-16 to UTF-8, and WASI to get a copy of the environment). By duplicating the std EnvMap implementation, we could make a version that generates 0-terminated env vars without extra copies, but I'll skip on doing that for now.
337 lines
11 KiB
Zig
337 lines
11 KiB
Zig
const std = @import("std");
|
|
const StructField = std.builtin.Type.StructField;
|
|
|
|
/// Given a type and a struct literal of defaults to add, this function creates
|
|
/// a simulacrum type with additional defaults set on its fields.
|
|
///
|
|
/// This function cannot remove default values from fields, but it can add some
|
|
/// to fields that don't have them, and it can overwrite existing defaults
|
|
pub fn UpdateDefaults(comptime input: type, comptime defaults: anytype) type {
|
|
comptime {
|
|
const inputInfo = @typeInfo(input);
|
|
const fieldcount = switch (inputInfo) {
|
|
.Struct => |spec| blk: {
|
|
if (spec.decls.len > 0) {
|
|
@compileError("UpdateDefaults only works on structs " ++
|
|
"without decls due to limitations in @Type.");
|
|
}
|
|
break :blk spec.fields.len;
|
|
},
|
|
else => @compileError("can only add default value to struct type"),
|
|
};
|
|
|
|
var fields: [fieldcount]StructField = undefined;
|
|
for (inputInfo.Struct.fields, 0..) |field, idx| {
|
|
fields[idx] = .{
|
|
.name = field.name,
|
|
.field_type = field.field_type,
|
|
// the cast ostensibly does type checking for us. It also makes
|
|
// setting null defaults work, and it converts comptime_int to
|
|
// the appropriate type, which is nice for ergonomics. Not sure
|
|
// if it introduces weird edge cases. Probably it's fine?
|
|
.default_value = if (@hasField(@TypeOf(defaults), field.name))
|
|
@ptrCast(&@as(field.field_type, @field(defaults, field.name)))
|
|
else
|
|
field.default_value,
|
|
.is_comptime = field.is_comptime,
|
|
.alignment = field.alignment,
|
|
};
|
|
}
|
|
|
|
return @Type(.{ .Struct = .{
|
|
.layout = inputInfo.Struct.layout,
|
|
.backing_integer = inputInfo.Struct.backing_integer,
|
|
.fields = &fields,
|
|
.decls = inputInfo.Struct.decls,
|
|
.is_tuple = inputInfo.Struct.is_tuple,
|
|
} });
|
|
}
|
|
}
|
|
|
|
pub fn enumLength(comptime T: type) comptime_int {
|
|
return @typeInfo(T).Enum.fields.len;
|
|
}
|
|
|
|
pub fn partition(comptime T: type, input: []const T, wedge: []const []const T) [3][]const T {
|
|
var idx: usize = 0;
|
|
while (idx < input.len) : (idx += 1) {
|
|
for (wedge) |splitter| {
|
|
if (input.len - idx < splitter.len) continue;
|
|
if (std.mem.eql(T, input[idx .. idx + splitter.len], splitter)) {
|
|
return [3][]const T{
|
|
input[0..idx],
|
|
input[idx..(idx + splitter.len)],
|
|
input[(idx + splitter.len)..],
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
return [3][]const T{
|
|
input[0..],
|
|
input[input.len..],
|
|
input[input.len..],
|
|
};
|
|
}
|
|
|
|
pub fn ComptimeWriter(
|
|
comptime Context: type,
|
|
comptime writeFn: fn (comptime context: Context, comptime bytes: []const u8) error{}!usize,
|
|
) type {
|
|
return struct {
|
|
context: Context,
|
|
|
|
const Self = @This();
|
|
pub const Error = error{};
|
|
|
|
pub fn write(comptime self: Self, comptime bytes: []const u8) Error!usize {
|
|
return writeFn(self.context, bytes);
|
|
}
|
|
|
|
pub fn writeAll(comptime self: Self, comptime bytes: []const u8) Error!void {
|
|
var index: usize = 0;
|
|
while (index != bytes.len) {
|
|
index += try self.write(bytes[index..]);
|
|
}
|
|
}
|
|
|
|
pub fn print(comptime self: Self, comptime format: []const u8, args: anytype) Error!void {
|
|
return std.fmt.format(self, format, args) catch @compileError("woah");
|
|
}
|
|
|
|
pub fn writeByte(comptime self: Self, byte: u8) Error!void {
|
|
const array = [1]u8{byte};
|
|
return self.writeAll(&array);
|
|
}
|
|
|
|
pub fn writeByteNTimes(comptime self: Self, byte: u8, n: usize) Error!void {
|
|
var bytes: [256]u8 = undefined;
|
|
std.mem.set(u8, bytes[0..], byte);
|
|
|
|
var remaining: usize = n;
|
|
while (remaining > 0) {
|
|
const to_write = std.math.min(remaining, bytes.len);
|
|
try self.writeAll(bytes[0..to_write]);
|
|
remaining -= to_write;
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
pub const ComptimeSliceBuffer = struct {
|
|
buffer: []const u8 = &[_]u8{},
|
|
|
|
const Writer = ComptimeWriter(*@This(), appendslice);
|
|
|
|
pub fn writer(comptime self: *@This()) Writer {
|
|
return .{ .context = self };
|
|
}
|
|
|
|
fn appendslice(comptime self: *@This(), comptime bytes: []const u8) error{}!usize {
|
|
self.buffer = self.buffer ++ bytes;
|
|
return bytes.len;
|
|
}
|
|
};
|
|
|
|
pub fn SliceIterator(comptime T: type) type {
|
|
// could be expanded to use std.meta.Elem, perhaps
|
|
const ResultType = std.meta.Child(T);
|
|
|
|
return struct {
|
|
index: usize,
|
|
data: T,
|
|
|
|
pub const InitError = error{};
|
|
|
|
pub fn wrap(value: T) @This() {
|
|
return @This(){ .index = 0, .data = value };
|
|
}
|
|
|
|
pub fn next(self: *@This()) ?ResultType {
|
|
if (self.index == self.data.len) return null;
|
|
|
|
defer self.index += 1;
|
|
return self.data[self.index];
|
|
}
|
|
|
|
pub fn peek(self: *@This()) ?ResultType {
|
|
if (self.index == self.data.len) return null;
|
|
|
|
return self.data[self.index];
|
|
}
|
|
|
|
pub fn rewind(self: *@This()) void {
|
|
if (self.index == 0) return;
|
|
|
|
self.index -= 1;
|
|
}
|
|
|
|
pub fn skip(self: *@This()) void {
|
|
if (self.index == self.data.len) return;
|
|
|
|
self.index += 1;
|
|
}
|
|
};
|
|
}
|
|
|
|
pub fn MutatingZSplitter(comptime T: type) type {
|
|
return struct {
|
|
buffer: [:0]T,
|
|
delimiter: T,
|
|
index: ?usize = 0,
|
|
|
|
const Self = @This();
|
|
|
|
/// Returns a slice of the next field, or null if splitting is complete.
|
|
pub fn next(self: *Self) ?[:0]T {
|
|
const start = self.index orelse return null;
|
|
|
|
const end = if (std.mem.indexOfScalarPos(T, self.buffer, start, self.delimiter)) |delim_idx| blk: {
|
|
self.buffer[delim_idx] = 0;
|
|
self.index = delim_idx + 1;
|
|
break :blk delim_idx;
|
|
} else blk: {
|
|
self.index = null;
|
|
break :blk self.buffer.len;
|
|
};
|
|
|
|
return self.buffer[start..end :0];
|
|
}
|
|
|
|
/// Returns a slice of the remaining bytes. Does not affect iterator state.
|
|
pub fn rest(self: Self) [:0]T {
|
|
const end = self.buffer.len;
|
|
const start = self.index orelse end;
|
|
return self.buffer[start..end :0];
|
|
}
|
|
};
|
|
}
|
|
|
|
pub fn copyStruct(comptime T: type, source: T, field_overrides: anytype) T {
|
|
var result: T = undefined;
|
|
|
|
comptime inline for (@typeInfo(@TypeOf(field_overrides)).Struct.fields) |field| {
|
|
if (!@hasField(T, field.name)) @compileError("override contains bad field" ++ field);
|
|
};
|
|
|
|
inline for (comptime @typeInfo(T).Struct.fields) |field| {
|
|
if (comptime @hasField(@TypeOf(field_overrides), field.name))
|
|
@field(result, field.name) = @field(field_overrides, field.name)
|
|
else
|
|
@field(result, field.name) = @field(source, field.name);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/// Stores type-erased pointers to items in comptime extensible data structures,
|
|
/// which allows e.g. assembling a tuple through multiple calls rather than all
|
|
/// at once.
|
|
pub const TupleBuilder = struct {
|
|
pointers: []const *const anyopaque = &[0]*const anyopaque{},
|
|
types: []const type = &[0]type{},
|
|
|
|
pub fn add(comptime self: *@This(), comptime item: anytype) void {
|
|
self.pointers = self.pointers ++ &[_]*const anyopaque{@as(*const anyopaque, &item)};
|
|
self.types = self.types ++ &[_]type{@TypeOf(item)};
|
|
}
|
|
|
|
pub fn retrieve(comptime self: @This(), comptime index: comptime_int) self.types[index] {
|
|
return @as(*const self.types[index], @ptrCast(@alignCast(self.pointers[index]))).*;
|
|
}
|
|
|
|
pub fn realTuple(comptime self: @This()) self.TupleType() {
|
|
comptime {
|
|
var result: self.TupleType() = undefined;
|
|
var idx = 0;
|
|
while (idx < self.types.len) : (idx += 1) {
|
|
result[idx] = self.retrieve(idx);
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
|
|
pub fn TupleType(comptime self: @This()) type {
|
|
comptime {
|
|
var fields: [self.types.len]StructField = undefined;
|
|
for (self.types, 0..) |Type, idx| {
|
|
var num_buf: [128]u8 = undefined;
|
|
fields[idx] = .{
|
|
.name = std.fmt.bufPrint(&num_buf, "{d}", .{idx}) catch @compileError("failed to write field"),
|
|
.type = Type,
|
|
.default_value = null,
|
|
// TODO: is this the right thing to do?
|
|
.is_comptime = false,
|
|
.alignment = if (@sizeOf(Type) > 0) @alignOf(Type) else 0,
|
|
};
|
|
}
|
|
|
|
return @Type(.{ .Struct = .{
|
|
.layout = .Auto,
|
|
.fields = &fields,
|
|
.decls = &.{},
|
|
.is_tuple = true,
|
|
} });
|
|
}
|
|
}
|
|
};
|
|
|
|
test "add basic default" {
|
|
const Base = struct { a: u8 };
|
|
const Defaulted = UpdateDefaults(Base, .{ .a = 4 });
|
|
|
|
const value = Defaulted{};
|
|
try std.testing.expectEqual(@as(u8, 4), value.a);
|
|
}
|
|
|
|
test "overwrite basic default" {
|
|
const Base = struct { a: u8 = 0 };
|
|
const Defaulted = UpdateDefaults(Base, .{ .a = 1 });
|
|
|
|
const value = Defaulted{};
|
|
try std.testing.expectEqual(@as(u8, 1), value.a);
|
|
}
|
|
|
|
test "add string default" {
|
|
const Base = struct { a: []const u8 };
|
|
const Defaulted = UpdateDefaults(Base, .{ .a = "hello" });
|
|
|
|
const value = Defaulted{};
|
|
try std.testing.expectEqual(@as([]const u8, "hello"), value.a);
|
|
}
|
|
|
|
test "add null default" {
|
|
const Base = struct { a: ?u8 };
|
|
const Defaulted = UpdateDefaults(Base, .{ .a = null });
|
|
|
|
const value = Defaulted{};
|
|
try std.testing.expectEqual(@as(?u8, null), value.a);
|
|
}
|
|
|
|
test "add enum default" {
|
|
const Options = enum { good, bad };
|
|
const Base = struct { a: Options };
|
|
const Defaulted = UpdateDefaults(Base, .{ .a = .good });
|
|
|
|
const value = Defaulted{};
|
|
try std.testing.expectEqual(Options.good, value.a);
|
|
}
|
|
|
|
test "preserve existing default" {
|
|
const Base = struct { a: ?u8 = 2, b: u8 };
|
|
const Defaulted = UpdateDefaults(Base, .{ .b = 3 });
|
|
|
|
const value = Defaulted{};
|
|
try std.testing.expectEqual(@as(?u8, 2), value.a);
|
|
try std.testing.expectEqual(@as(?u8, 3), value.b);
|
|
}
|
|
|
|
test "add multiple defaults" {
|
|
const Base = struct { a: u8, b: i8, c: ?u8 };
|
|
const Defaulted = UpdateDefaults(Base, .{ .a = 3, .c = 2 });
|
|
|
|
const value = Defaulted{ .b = -1 };
|
|
try std.testing.expectEqual(@as(u8, 3), value.a);
|
|
try std.testing.expectEqual(@as(i8, -1), value.b);
|
|
try std.testing.expectEqual(@as(?u8, 2), value.c);
|
|
}
|