NOCLIP/source/meta.zig
torque 390a1ba4fd
parser: parse into 0-terminated strings
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.
2023-08-27 13:53:14 -07:00

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);
}