synthetic: use std.Io.Writer for more of the interface (#9038)
parent
03e71e86a4
commit
37b3c27020
|
|
@ -27,27 +27,35 @@ pub fn generator(self: *Bytes) Generator {
|
|||
return .init(self, next);
|
||||
}
|
||||
|
||||
pub fn next(self: *Bytes, buf: []u8) Generator.Error![]const u8 {
|
||||
pub fn next(self: *Bytes, writer: *std.Io.Writer, max_len: usize) Generator.Error!void {
|
||||
std.debug.assert(max_len >= 1);
|
||||
const len = @min(
|
||||
self.rand.intRangeAtMostBiased(usize, self.min_len, self.max_len),
|
||||
buf.len,
|
||||
max_len,
|
||||
);
|
||||
|
||||
const result = buf[0..len];
|
||||
self.rand.bytes(result);
|
||||
if (self.alphabet) |alphabet| {
|
||||
for (result) |*byte| byte.* = alphabet[byte.* % alphabet.len];
|
||||
var buf: [8]u8 = undefined;
|
||||
var remaining = len;
|
||||
while (remaining > 0) {
|
||||
const data = buf[0..@min(remaining, buf.len)];
|
||||
self.rand.bytes(data);
|
||||
if (self.alphabet) |alphabet| {
|
||||
for (data) |*byte| byte.* = alphabet[byte.* % alphabet.len];
|
||||
}
|
||||
try writer.writeAll(data);
|
||||
remaining -= data.len;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
test "bytes" {
|
||||
const testing = std.testing;
|
||||
var prng = std.Random.DefaultPrng.init(0);
|
||||
var buf: [256]u8 = undefined;
|
||||
var writer: std.Io.Writer = .fixed(&buf);
|
||||
var v: Bytes = .{ .rand = prng.random() };
|
||||
v.min_len = buf.len;
|
||||
v.max_len = buf.len;
|
||||
const gen = v.generator();
|
||||
const result = try gen.next(&buf);
|
||||
try testing.expect(result.len > 0);
|
||||
try gen.next(&writer, buf.len);
|
||||
try testing.expectEqual(buf.len, writer.buffered().len);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,27 +6,27 @@ const assert = std.debug.assert;
|
|||
|
||||
/// For generators, this is the only error that is allowed to be
|
||||
/// returned by the next function.
|
||||
pub const Error = error{NoSpaceLeft};
|
||||
pub const Error = error{WriteFailed};
|
||||
|
||||
/// The vtable for the generator.
|
||||
ptr: *anyopaque,
|
||||
nextFn: *const fn (ptr: *anyopaque, buf: []u8) Error![]const u8,
|
||||
nextFn: *const fn (ptr: *anyopaque, *std.Io.Writer, usize) Error!void,
|
||||
|
||||
/// Create a new generator from a pointer and a function pointer.
|
||||
/// This usually is only called by generator implementations, not
|
||||
/// generator users.
|
||||
pub fn init(
|
||||
pointer: anytype,
|
||||
comptime nextFn: fn (ptr: @TypeOf(pointer), buf: []u8) Error![]const u8,
|
||||
comptime nextFn: fn (ptr: @TypeOf(pointer), *std.Io.Writer, usize) Error!void,
|
||||
) Generator {
|
||||
const Ptr = @TypeOf(pointer);
|
||||
assert(@typeInfo(Ptr) == .pointer); // Must be a pointer
|
||||
assert(@typeInfo(Ptr).pointer.size == .one); // Must be a single-item pointer
|
||||
assert(@typeInfo(@typeInfo(Ptr).pointer.child) == .@"struct"); // Must point to a struct
|
||||
const gen = struct {
|
||||
fn next(ptr: *anyopaque, buf: []u8) Error![]const u8 {
|
||||
fn next(ptr: *anyopaque, writer: *std.Io.Writer, max_len: usize) Error!void {
|
||||
const self: Ptr = @ptrCast(@alignCast(ptr));
|
||||
return try nextFn(self, buf);
|
||||
try nextFn(self, writer, max_len);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -37,6 +37,6 @@ pub fn init(
|
|||
}
|
||||
|
||||
/// Get the next value from the generator. Returns the data written.
|
||||
pub fn next(self: Generator, buf: []u8) Error![]const u8 {
|
||||
return try self.nextFn(self.ptr, buf);
|
||||
pub fn next(self: Generator, writer: *std.Io.Writer, max_size: usize) Error!void {
|
||||
try self.nextFn(self.ptr, writer, max_size);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,6 +53,9 @@ pub fn generator(self: *Osc) Generator {
|
|||
return .init(self, next);
|
||||
}
|
||||
|
||||
const osc = std.fmt.comptimePrint("{c}]", .{std.ascii.control_code.esc});
|
||||
const st = std.fmt.comptimePrint("{c}", .{std.ascii.control_code.bel});
|
||||
|
||||
/// Get the next OSC request in bytes. The generated OSC request will
|
||||
/// have the prefix `ESC ]` and the terminator `BEL` (0x07).
|
||||
///
|
||||
|
|
@ -63,23 +66,22 @@ pub fn generator(self: *Osc) Generator {
|
|||
///
|
||||
/// The buffer must be at least 3 bytes long to accommodate the
|
||||
/// prefix and terminator.
|
||||
pub fn next(self: *Osc, buf: []u8) Generator.Error![]const u8 {
|
||||
if (buf.len < 3) return error.NoSpaceLeft;
|
||||
const unwrapped = try self.nextUnwrapped(buf[2 .. buf.len - 1]);
|
||||
buf[0] = 0x1B; // ESC
|
||||
buf[1] = ']';
|
||||
buf[unwrapped.len + 2] = 0x07; // BEL
|
||||
return buf[0 .. unwrapped.len + 3];
|
||||
pub fn next(self: *Osc, writer: *std.Io.Writer, max_len: usize) Generator.Error!void {
|
||||
assert(max_len >= 3);
|
||||
try writer.writeAll(osc);
|
||||
try self.nextUnwrapped(writer, max_len - (osc.len + st.len));
|
||||
try writer.writeAll(st);
|
||||
}
|
||||
|
||||
fn nextUnwrapped(self: *Osc, buf: []u8) Generator.Error![]const u8 {
|
||||
fn nextUnwrapped(self: *Osc, writer: *std.Io.Writer, max_len: usize) Generator.Error!void {
|
||||
return switch (self.chooseValidity()) {
|
||||
.valid => valid: {
|
||||
const Indexer = @TypeOf(self.p_valid_kind).Indexer;
|
||||
const idx = self.rand.weightedIndex(f64, &self.p_valid_kind.values);
|
||||
break :valid try self.nextUnwrappedValidExact(
|
||||
buf,
|
||||
writer,
|
||||
Indexer.keyForIndex(idx),
|
||||
max_len,
|
||||
);
|
||||
},
|
||||
|
||||
|
|
@ -87,70 +89,64 @@ fn nextUnwrapped(self: *Osc, buf: []u8) Generator.Error![]const u8 {
|
|||
const Indexer = @TypeOf(self.p_invalid_kind).Indexer;
|
||||
const idx = self.rand.weightedIndex(f64, &self.p_invalid_kind.values);
|
||||
break :invalid try self.nextUnwrappedInvalidExact(
|
||||
buf,
|
||||
writer,
|
||||
Indexer.keyForIndex(idx),
|
||||
max_len,
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
fn nextUnwrappedValidExact(self: *const Osc, buf: []u8, k: ValidKind) Generator.Error![]const u8 {
|
||||
var fbs = std.io.fixedBufferStream(buf);
|
||||
fn nextUnwrappedValidExact(self: *const Osc, writer: *std.Io.Writer, k: ValidKind, max_len: usize) Generator.Error!void {
|
||||
switch (k) {
|
||||
.change_window_title => {
|
||||
try fbs.writer().writeAll("0;"); // Set window title
|
||||
try writer.writeAll("0;"); // Set window title
|
||||
var bytes_gen = self.bytes();
|
||||
const title = try bytes_gen.next(fbs.buffer[fbs.pos..]);
|
||||
try fbs.seekBy(@intCast(title.len));
|
||||
try bytes_gen.next(writer, max_len - 2);
|
||||
},
|
||||
|
||||
.prompt_start => {
|
||||
try fbs.writer().writeAll("133;A"); // Start prompt
|
||||
try writer.writeAll("133;A"); // Start prompt
|
||||
|
||||
// aid
|
||||
if (self.rand.boolean()) {
|
||||
var bytes_gen = self.bytes();
|
||||
bytes_gen.max_len = 16;
|
||||
try fbs.writer().writeAll(";aid=");
|
||||
const aid = try bytes_gen.next(fbs.buffer[fbs.pos..]);
|
||||
try fbs.seekBy(@intCast(aid.len));
|
||||
try writer.writeAll(";aid=");
|
||||
try bytes_gen.next(writer, max_len);
|
||||
}
|
||||
|
||||
// redraw
|
||||
if (self.rand.boolean()) {
|
||||
try fbs.writer().writeAll(";redraw=");
|
||||
try writer.writeAll(";redraw=");
|
||||
if (self.rand.boolean()) {
|
||||
try fbs.writer().writeAll("1");
|
||||
try writer.writeAll("1");
|
||||
} else {
|
||||
try fbs.writer().writeAll("0");
|
||||
try writer.writeAll("0");
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
.prompt_end => try fbs.writer().writeAll("133;B"), // End prompt
|
||||
.prompt_end => try writer.writeAll("133;B"), // End prompt
|
||||
}
|
||||
|
||||
return fbs.getWritten();
|
||||
}
|
||||
|
||||
fn nextUnwrappedInvalidExact(
|
||||
self: *const Osc,
|
||||
buf: []u8,
|
||||
writer: *std.Io.Writer,
|
||||
k: InvalidKind,
|
||||
) Generator.Error![]const u8 {
|
||||
max_len: usize,
|
||||
) Generator.Error!void {
|
||||
switch (k) {
|
||||
.random => {
|
||||
var bytes_gen = self.bytes();
|
||||
return try bytes_gen.next(buf);
|
||||
try bytes_gen.next(writer, max_len);
|
||||
},
|
||||
|
||||
.good_prefix => {
|
||||
var fbs = std.io.fixedBufferStream(buf);
|
||||
try fbs.writer().writeAll("133;");
|
||||
try writer.writeAll("133;");
|
||||
var bytes_gen = self.bytes();
|
||||
const data = try bytes_gen.next(fbs.buffer[fbs.pos..]);
|
||||
try fbs.seekBy(@intCast(data.len));
|
||||
return fbs.getWritten();
|
||||
try bytes_gen.next(writer, max_len - 4);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -177,11 +173,21 @@ const Validity = enum { valid, invalid };
|
|||
const test_seed = 0xC0FFEEEEEEEEEEEE;
|
||||
|
||||
test "OSC generator" {
|
||||
const testing = std.testing;
|
||||
var prng = std.Random.DefaultPrng.init(test_seed);
|
||||
var buf: [4096]u8 = undefined;
|
||||
var v: Osc = .{ .rand = prng.random() };
|
||||
const gen = v.generator();
|
||||
for (0..50) |_| _ = try gen.next(&buf);
|
||||
var buf: [256]u8 = undefined;
|
||||
{
|
||||
var v: Osc = .{
|
||||
.rand = prng.random(),
|
||||
};
|
||||
const gen = v.generator();
|
||||
for (0..50) |_| {
|
||||
var writer: std.Io.Writer = .fixed(&buf);
|
||||
try gen.next(&writer, buf.len);
|
||||
const result = writer.buffered();
|
||||
try testing.expect(result.len > 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test "OSC generator valid" {
|
||||
|
|
@ -195,7 +201,9 @@ test "OSC generator valid" {
|
|||
.p_valid = 1.0,
|
||||
};
|
||||
for (0..50) |_| {
|
||||
const seq = try gen.next(&buf);
|
||||
var writer: std.Io.Writer = .fixed(&buf);
|
||||
try gen.next(&writer, buf.len);
|
||||
const seq = writer.buffered();
|
||||
var parser: terminal.osc.Parser = .init();
|
||||
for (seq[2 .. seq.len - 1]) |c| parser.next(c);
|
||||
try testing.expect(parser.end(null) != null);
|
||||
|
|
@ -213,7 +221,9 @@ test "OSC generator invalid" {
|
|||
.p_valid = 0.0,
|
||||
};
|
||||
for (0..50) |_| {
|
||||
const seq = try gen.next(&buf);
|
||||
var writer: std.Io.Writer = .fixed(&buf);
|
||||
try gen.next(&writer, buf.len);
|
||||
const seq = writer.buffered();
|
||||
var parser: terminal.osc.Parser = .init();
|
||||
for (seq[2 .. seq.len - 1]) |c| parser.next(c);
|
||||
try testing.expect(parser.end(null) == null);
|
||||
|
|
|
|||
|
|
@ -41,13 +41,12 @@ pub fn generator(self: *Utf8) Generator {
|
|||
return .init(self, next);
|
||||
}
|
||||
|
||||
pub fn next(self: *Utf8, buf: []u8) Generator.Error![]const u8 {
|
||||
pub fn next(self: *Utf8, writer: *std.Io.Writer, max_len: usize) Generator.Error!void {
|
||||
const len = @min(
|
||||
self.rand.intRangeAtMostBiased(usize, self.min_len, self.max_len),
|
||||
buf.len,
|
||||
max_len,
|
||||
);
|
||||
|
||||
const result = buf[0..len];
|
||||
var rem: usize = len;
|
||||
while (rem > 0) {
|
||||
// Pick a utf8 byte count to generate.
|
||||
|
|
@ -75,9 +74,11 @@ pub fn next(self: *Utf8, buf: []u8) Generator.Error![]const u8 {
|
|||
assert(std.unicode.utf8CodepointSequenceLength(
|
||||
cp,
|
||||
) catch unreachable == @intFromEnum(utf8_len));
|
||||
rem -= std.unicode.utf8Encode(
|
||||
|
||||
var buf: [4]u8 = undefined;
|
||||
const l = std.unicode.utf8Encode(
|
||||
cp,
|
||||
result[result.len - rem ..],
|
||||
&buf,
|
||||
) catch |err| switch (err) {
|
||||
// Impossible because our generation above is hardcoded to
|
||||
// produce a valid range. If not, a bug.
|
||||
|
|
@ -86,18 +87,22 @@ pub fn next(self: *Utf8, buf: []u8) Generator.Error![]const u8 {
|
|||
// Possible, in which case we redo the loop and encode nothing.
|
||||
error.Utf8CannotEncodeSurrogateHalf => continue,
|
||||
};
|
||||
try writer.writeAll(buf[0..l]);
|
||||
rem -= l;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
test "utf8" {
|
||||
const testing = std.testing;
|
||||
var prng = std.Random.DefaultPrng.init(0);
|
||||
var buf: [256]u8 = undefined;
|
||||
var writer: std.Io.Writer = .fixed(&buf);
|
||||
var v: Utf8 = .{ .rand = prng.random() };
|
||||
v.min_len = buf.len;
|
||||
v.max_len = buf.len;
|
||||
const gen = v.generator();
|
||||
const result = try gen.next(&buf);
|
||||
try testing.expect(result.len > 0);
|
||||
try gen.next(&writer, buf.len);
|
||||
const result = writer.buffered();
|
||||
try testing.expectEqual(256, result.len);
|
||||
try testing.expect(std.unicode.utf8ValidateSlice(result));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -100,7 +100,9 @@ fn mainActionImpl(
|
|||
try impl.run(writer, rand);
|
||||
|
||||
// Always flush
|
||||
try writer.flush();
|
||||
writer.flush() catch |err| switch (err) {
|
||||
error.WriteFailed => return,
|
||||
};
|
||||
}
|
||||
|
||||
test {
|
||||
|
|
|
|||
|
|
@ -31,10 +31,8 @@ pub fn run(self: *Ascii, writer: *std.Io.Writer, rand: std.Random) !void {
|
|||
.alphabet = synthetic.Bytes.Alphabet.ascii,
|
||||
};
|
||||
|
||||
var buf: [1024]u8 = undefined;
|
||||
while (true) {
|
||||
const data = try gen.next(&buf);
|
||||
writer.writeAll(data) catch |err| {
|
||||
gen.next(writer, 1024) catch |err| {
|
||||
const Error = error{ WriteFailed, BrokenPipe } || @TypeOf(err);
|
||||
switch (@as(Error, err)) {
|
||||
error.BrokenPipe => return, // stdout closed
|
||||
|
|
|
|||
|
|
@ -37,14 +37,11 @@ pub fn run(self: *Osc, writer: *std.Io.Writer, rand: std.Random) !void {
|
|||
|
||||
var buf: [1024]u8 = undefined;
|
||||
while (true) {
|
||||
const data = try gen.next(&buf);
|
||||
writer.writeAll(data) catch |err| {
|
||||
const Error = error{ WriteFailed, BrokenPipe } || @TypeOf(err);
|
||||
switch (@as(Error, err)) {
|
||||
error.BrokenPipe => return, // stdout closed
|
||||
error.WriteFailed => return, // fixed buffer full
|
||||
else => return err,
|
||||
}
|
||||
var fixed: std.Io.Writer = .fixed(&buf);
|
||||
try gen.next(&fixed, buf.len);
|
||||
const data = fixed.buffered();
|
||||
writer.writeAll(data) catch |err| switch (err) {
|
||||
error.WriteFailed => return,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,10 +30,8 @@ pub fn run(self: *Utf8, writer: *std.Io.Writer, rand: std.Random) !void {
|
|||
.rand = rand,
|
||||
};
|
||||
|
||||
var buf: [1024]u8 = undefined;
|
||||
while (true) {
|
||||
const data = try gen.next(&buf);
|
||||
writer.writeAll(data) catch |err| {
|
||||
gen.next(writer, 1024) catch |err| {
|
||||
const Error = error{ WriteFailed, BrokenPipe } || @TypeOf(err);
|
||||
switch (@as(Error, err)) {
|
||||
error.BrokenPipe => return, // stdout closed
|
||||
|
|
|
|||
Loading…
Reference in New Issue