synthetic: make bytes generation more flexible (#9204)

pull/9864/head
Mitchell Hashimoto 2025-12-10 12:59:46 -08:00 committed by GitHub
commit af05397219
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 130 additions and 53 deletions

View File

@ -1,4 +1,4 @@
/// Generates bytes. //! Generates bytes.
const Bytes = @This(); const Bytes = @This();
const std = @import("std"); const std = @import("std");
@ -7,9 +7,7 @@ const Generator = @import("Generator.zig");
/// Random number generator. /// Random number generator.
rand: std.Random, rand: std.Random,
/// The minimum and maximum length of the generated bytes. The maximum /// The minimum and maximum length of the generated bytes.
/// length will be capped to the length of the buffer passed in if the
/// buffer length is smaller.
min_len: usize = 1, min_len: usize = 1,
max_len: usize = std.math.maxInt(usize), max_len: usize = std.math.maxInt(usize),
@ -18,23 +16,79 @@ max_len: usize = std.math.maxInt(usize),
/// side effect of the generator, not an intended use case. /// side effect of the generator, not an intended use case.
alphabet: ?[]const u8 = null, alphabet: ?[]const u8 = null,
/// Predefined alphabets. /// Generate an alphabet given a function that returns true/false for a
pub const Alphabet = struct { /// given byte.
pub const ascii = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()_+-=[]{}|;':\\\",./<>?`~"; pub fn generateAlphabet(comptime func: fn (u8) bool) []const u8 {
}; @setEvalBranchQuota(3000);
var count = 0;
for (0..256) |c| {
if (func(c)) count += 1;
}
var alphabet: [count]u8 = undefined;
var i = 0;
for (0..256) |c| {
if (func(c)) {
alphabet[i] = c;
i += 1;
}
}
const result = alphabet;
return &result;
}
pub fn generator(self: *Bytes) Generator { pub fn generator(self: *Bytes) Generator {
return .init(self, next); return .init(self, next);
} }
pub fn next(self: *Bytes, writer: *std.Io.Writer, max_len: usize) Generator.Error!void { /// Return a copy of the Bytes, but with a new alphabet.
std.debug.assert(max_len >= 1); pub fn newAlphabet(self: *const Bytes, new_alphabet: ?[]const u8) Bytes {
const len = @min( return .{
self.rand.intRangeAtMostBiased(usize, self.min_len, self.max_len), .rand = self.rand,
max_len, .alphabet = new_alphabet,
); .min_len = self.min_len,
.max_len = self.max_len,
};
}
/// Return a copy of the Bytes, but with a new min_len. The new min
/// len cannot be more than the previous max_len.
pub fn atLeast(self: *const Bytes, new_min_len: usize) Bytes {
return .{
.rand = self.rand,
.alphabet = self.alphabet,
.min_len = @min(self.max_len, new_min_len),
.max_len = self.max_len,
};
}
/// Return a copy of the Bytes, but with a new max_len. The new max_len cannot
/// be more the previous max_len.
pub fn atMost(self: *const Bytes, new_max_len: usize) Bytes {
return .{
.rand = self.rand,
.alphabet = self.alphabet,
.min_len = @min(self.min_len, @min(self.max_len, new_max_len)),
.max_len = @min(self.max_len, new_max_len),
};
}
pub fn next(self: *const Bytes, writer: *std.Io.Writer, max_len: usize) std.Io.Writer.Error!void {
_ = try self.atMost(max_len).write(writer);
}
pub fn format(self: *const Bytes, writer: *std.Io.Writer) std.Io.Writer.Error!void {
_ = try self.write(writer);
}
/// Write some random data and return the number of bytes written.
pub fn write(self: *const Bytes, writer: *std.Io.Writer) std.Io.Writer.Error!usize {
std.debug.assert(self.min_len >= 1);
std.debug.assert(self.max_len >= self.min_len);
const len = self.rand.intRangeAtMostBiased(usize, self.min_len, self.max_len);
var buf: [8]u8 = undefined; var buf: [8]u8 = undefined;
var remaining = len; var remaining = len;
while (remaining > 0) { while (remaining > 0) {
const data = buf[0..@min(remaining, buf.len)]; const data = buf[0..@min(remaining, buf.len)];
@ -45,6 +99,8 @@ pub fn next(self: *Bytes, writer: *std.Io.Writer, max_len: usize) Generator.Erro
try writer.writeAll(data); try writer.writeAll(data);
remaining -= data.len; remaining -= data.len;
} }
return len;
} }
test "bytes" { test "bytes" {
@ -52,9 +108,11 @@ test "bytes" {
var prng = std.Random.DefaultPrng.init(0); var prng = std.Random.DefaultPrng.init(0);
var buf: [256]u8 = undefined; var buf: [256]u8 = undefined;
var writer: std.Io.Writer = .fixed(&buf); var writer: std.Io.Writer = .fixed(&buf);
var v: Bytes = .{ .rand = prng.random() }; var v: Bytes = .{
v.min_len = buf.len; .rand = prng.random(),
v.max_len = buf.len; .min_len = buf.len,
.max_len = buf.len,
};
const gen = v.generator(); const gen = v.generator();
try gen.next(&writer, buf.len); try gen.next(&writer, buf.len);
try testing.expectEqual(buf.len, writer.buffered().len); try testing.expectEqual(buf.len, writer.buffered().len);

View File

@ -35,19 +35,26 @@ p_valid: f64 = 1.0,
p_valid_kind: std.enums.EnumArray(ValidKind, f64) = .initFill(1.0), p_valid_kind: std.enums.EnumArray(ValidKind, f64) = .initFill(1.0),
p_invalid_kind: std.enums.EnumArray(InvalidKind, f64) = .initFill(1.0), p_invalid_kind: std.enums.EnumArray(InvalidKind, f64) = .initFill(1.0),
/// The alphabet for random bytes (omitting 0x1B and 0x07). fn checkKvAlphabet(c: u8) bool {
const bytes_alphabet: []const u8 = alphabet: { return switch (c) {
var alphabet: [256]u8 = undefined; std.ascii.control_code.esc, std.ascii.control_code.bel, ';', '=' => false,
for (0..alphabet.len) |i| { else => std.ascii.isPrint(c),
if (i == 0x1B or i == 0x07) { };
alphabet[i] = @intCast(i + 1); }
} else {
alphabet[i] = @intCast(i); /// The alphabet for random bytes in OSC key/value pairs (omitting 0x1B,
} /// 0x07, ';', '=').
} pub const kv_alphabet = Bytes.generateAlphabet(checkKvAlphabet);
const result = alphabet;
break :alphabet &result; fn checkOscAlphabet(c: u8) bool {
}; return switch (c) {
std.ascii.control_code.esc, std.ascii.control_code.bel => false,
else => true,
};
}
/// The alphabet for random bytes in OSCs (omitting 0x1B and 0x07).
pub const osc_alphabet = Bytes.generateAlphabet(checkOscAlphabet);
pub fn generator(self: *Osc) Generator { pub fn generator(self: *Osc) Generator {
return .init(self, next); return .init(self, next);
@ -99,35 +106,43 @@ fn nextUnwrapped(self: *Osc, writer: *std.Io.Writer, max_len: usize) Generator.E
fn nextUnwrappedValidExact(self: *const Osc, writer: *std.Io.Writer, k: ValidKind, max_len: usize) Generator.Error!void { fn nextUnwrappedValidExact(self: *const Osc, writer: *std.Io.Writer, k: ValidKind, max_len: usize) Generator.Error!void {
switch (k) { switch (k) {
.change_window_title => { .change_window_title => change_window_title: {
try writer.writeAll("0;"); // Set window title if (max_len < 3) break :change_window_title;
var bytes_gen = self.bytes(); try writer.print("0;{f}", .{self.bytes().atMost(max_len - 3)}); // Set window title
try bytes_gen.next(writer, max_len - 2);
}, },
.prompt_start => { .prompt_start => prompt_start: {
if (max_len < 4) break :prompt_start;
var remaining = max_len;
try writer.writeAll("133;A"); // Start prompt try writer.writeAll("133;A"); // Start prompt
remaining -= 4;
// aid // aid
if (self.rand.boolean()) { if (self.rand.boolean()) aid: {
var bytes_gen = self.bytes(); if (remaining < 6) break :aid;
bytes_gen.max_len = 16;
try writer.writeAll(";aid="); try writer.writeAll(";aid=");
try bytes_gen.next(writer, max_len); remaining -= 5;
remaining -= try self.bytes().newAlphabet(kv_alphabet).atMost(@min(16, remaining)).write(writer);
} }
// redraw // redraw
if (self.rand.boolean()) { if (self.rand.boolean()) redraw: {
if (remaining < 9) break :redraw;
try writer.writeAll(";redraw="); try writer.writeAll(";redraw=");
if (self.rand.boolean()) { if (self.rand.boolean()) {
try writer.writeAll("1"); try writer.writeAll("1");
} else { } else {
try writer.writeAll("0"); try writer.writeAll("0");
} }
remaining -= 9;
} }
}, },
.prompt_end => try writer.writeAll("133;B"), // End prompt .prompt_end => prompt_end: {
if (max_len < 4) break :prompt_end;
try writer.writeAll("133;B"); // End prompt
},
} }
} }
@ -139,14 +154,11 @@ fn nextUnwrappedInvalidExact(
) Generator.Error!void { ) Generator.Error!void {
switch (k) { switch (k) {
.random => { .random => {
var bytes_gen = self.bytes(); try self.bytes().atMost(max_len).format(writer);
try bytes_gen.next(writer, max_len);
}, },
.good_prefix => { .good_prefix => {
try writer.writeAll("133;"); try writer.print("133;{f}", .{self.bytes().atMost(max_len - 4)});
var bytes_gen = self.bytes();
try bytes_gen.next(writer, max_len - 4);
}, },
} }
} }
@ -154,7 +166,7 @@ fn nextUnwrappedInvalidExact(
fn bytes(self: *const Osc) Bytes { fn bytes(self: *const Osc) Bytes {
return .{ return .{
.rand = self.rand, .rand = self.rand,
.alphabet = bytes_alphabet, .alphabet = osc_alphabet,
}; };
} }

View File

@ -3,12 +3,21 @@ const Ascii = @This();
const std = @import("std"); const std = @import("std");
const assert = std.debug.assert; const assert = std.debug.assert;
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const synthetic = @import("../main.zig"); const Bytes = @import("../Bytes.zig");
const log = std.log.scoped(.@"terminal-stream-bench"); const log = std.log.scoped(.@"terminal-stream-bench");
pub const Options = struct {}; pub const Options = struct {};
fn checkAsciiAlphabet(c: u8) bool {
return switch (c) {
' ' => false,
else => std.ascii.isPrint(c),
};
}
pub const ascii = Bytes.generateAlphabet(checkAsciiAlphabet);
/// Create a new terminal stream handler for the given arguments. /// Create a new terminal stream handler for the given arguments.
pub fn create( pub fn create(
alloc: Allocator, alloc: Allocator,
@ -23,12 +32,10 @@ pub fn destroy(self: *Ascii, alloc: Allocator) void {
alloc.destroy(self); alloc.destroy(self);
} }
pub fn run(self: *Ascii, writer: *std.Io.Writer, rand: std.Random) !void { pub fn run(_: *Ascii, writer: *std.Io.Writer, rand: std.Random) !void {
_ = self; var gen: Bytes = .{
var gen: synthetic.Bytes = .{
.rand = rand, .rand = rand,
.alphabet = synthetic.Bytes.Alphabet.ascii, .alphabet = ascii,
}; };
while (true) { while (true) {