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 std = @import("std");
@ -7,9 +7,7 @@ const Generator = @import("Generator.zig");
/// Random number generator.
rand: std.Random,
/// The minimum and maximum length of the generated bytes. The maximum
/// length will be capped to the length of the buffer passed in if the
/// buffer length is smaller.
/// The minimum and maximum length of the generated bytes.
min_len: usize = 1,
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.
alphabet: ?[]const u8 = null,
/// Predefined alphabets.
pub const Alphabet = struct {
pub const ascii = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()_+-=[]{}|;':\\\",./<>?`~";
};
/// Generate an alphabet given a function that returns true/false for a
/// given byte.
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 {
return .init(self, next);
}
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),
max_len,
);
/// Return a copy of the Bytes, but with a new alphabet.
pub fn newAlphabet(self: *const Bytes, new_alphabet: ?[]const u8) Bytes {
return .{
.rand = self.rand,
.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 remaining = len;
while (remaining > 0) {
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);
remaining -= data.len;
}
return len;
}
test "bytes" {
@ -52,9 +108,11 @@ test "bytes" {
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;
var v: Bytes = .{
.rand = prng.random(),
.min_len = buf.len,
.max_len = buf.len,
};
const gen = v.generator();
try gen.next(&writer, buf.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_invalid_kind: std.enums.EnumArray(InvalidKind, f64) = .initFill(1.0),
/// The alphabet for random bytes (omitting 0x1B and 0x07).
const bytes_alphabet: []const u8 = alphabet: {
var alphabet: [256]u8 = undefined;
for (0..alphabet.len) |i| {
if (i == 0x1B or i == 0x07) {
alphabet[i] = @intCast(i + 1);
} else {
alphabet[i] = @intCast(i);
}
}
const result = alphabet;
break :alphabet &result;
fn checkKvAlphabet(c: u8) bool {
return switch (c) {
std.ascii.control_code.esc, std.ascii.control_code.bel, ';', '=' => false,
else => std.ascii.isPrint(c),
};
}
/// The alphabet for random bytes in OSC key/value pairs (omitting 0x1B,
/// 0x07, ';', '=').
pub const kv_alphabet = Bytes.generateAlphabet(checkKvAlphabet);
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 {
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 {
switch (k) {
.change_window_title => {
try writer.writeAll("0;"); // Set window title
var bytes_gen = self.bytes();
try bytes_gen.next(writer, max_len - 2);
.change_window_title => change_window_title: {
if (max_len < 3) break :change_window_title;
try writer.print("0;{f}", .{self.bytes().atMost(max_len - 3)}); // Set window title
},
.prompt_start => {
.prompt_start => prompt_start: {
if (max_len < 4) break :prompt_start;
var remaining = max_len;
try writer.writeAll("133;A"); // Start prompt
remaining -= 4;
// aid
if (self.rand.boolean()) {
var bytes_gen = self.bytes();
bytes_gen.max_len = 16;
if (self.rand.boolean()) aid: {
if (remaining < 6) break :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
if (self.rand.boolean()) {
if (self.rand.boolean()) redraw: {
if (remaining < 9) break :redraw;
try writer.writeAll(";redraw=");
if (self.rand.boolean()) {
try writer.writeAll("1");
} else {
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 {
switch (k) {
.random => {
var bytes_gen = self.bytes();
try bytes_gen.next(writer, max_len);
try self.bytes().atMost(max_len).format(writer);
},
.good_prefix => {
try writer.writeAll("133;");
var bytes_gen = self.bytes();
try bytes_gen.next(writer, max_len - 4);
try writer.print("133;{f}", .{self.bytes().atMost(max_len - 4)});
},
}
}
@ -154,7 +166,7 @@ fn nextUnwrappedInvalidExact(
fn bytes(self: *const Osc) Bytes {
return .{
.rand = self.rand,
.alphabet = bytes_alphabet,
.alphabet = osc_alphabet,
};
}

View File

@ -3,12 +3,21 @@ const Ascii = @This();
const std = @import("std");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const synthetic = @import("../main.zig");
const Bytes = @import("../Bytes.zig");
const log = std.log.scoped(.@"terminal-stream-bench");
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.
pub fn create(
alloc: Allocator,
@ -23,12 +32,10 @@ pub fn destroy(self: *Ascii, alloc: Allocator) void {
alloc.destroy(self);
}
pub fn run(self: *Ascii, writer: *std.Io.Writer, rand: std.Random) !void {
_ = self;
var gen: synthetic.Bytes = .{
pub fn run(_: *Ascii, writer: *std.Io.Writer, rand: std.Random) !void {
var gen: Bytes = .{
.rand = rand,
.alphabet = synthetic.Bytes.Alphabet.ascii,
.alphabet = ascii,
};
while (true) {