Skip to content

Commit 8cce9ac

Browse files
committed
synthetic: make bytes generation more flexible
1 parent 3d837cb commit 8cce9ac

File tree

3 files changed

+130
-53
lines changed

3 files changed

+130
-53
lines changed

src/synthetic/Bytes.zig

Lines changed: 75 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/// Generates bytes.
1+
//! Generates bytes.
22
const Bytes = @This();
33

44
const std = @import("std");
@@ -7,9 +7,7 @@ const Generator = @import("Generator.zig");
77
/// Random number generator.
88
rand: std.Random,
99

10-
/// The minimum and maximum length of the generated bytes. The maximum
11-
/// length will be capped to the length of the buffer passed in if the
12-
/// buffer length is smaller.
10+
/// The minimum and maximum length of the generated bytes.
1311
min_len: usize = 1,
1412
max_len: usize = std.math.maxInt(usize),
1513

@@ -18,23 +16,79 @@ max_len: usize = std.math.maxInt(usize),
1816
/// side effect of the generator, not an intended use case.
1917
alphabet: ?[]const u8 = null,
2018

21-
/// Predefined alphabets.
22-
pub const Alphabet = struct {
23-
pub const ascii = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()_+-=[]{}|;':\\\",./<>?`~";
24-
};
19+
/// Generate an alphabet given a function that returns true/false for a
20+
/// given byte.
21+
pub fn generateAlphabet(comptime func: fn (u8) bool) []const u8 {
22+
@setEvalBranchQuota(3000);
23+
var count = 0;
24+
for (0..256) |c| {
25+
if (func(c)) count += 1;
26+
}
27+
var alphabet: [count]u8 = undefined;
28+
var i = 0;
29+
for (0..256) |c| {
30+
if (func(c)) {
31+
alphabet[i] = c;
32+
i += 1;
33+
}
34+
}
35+
const result = alphabet;
36+
return &result;
37+
}
2538

2639
pub fn generator(self: *Bytes) Generator {
2740
return .init(self, next);
2841
}
2942

30-
pub fn next(self: *Bytes, writer: *std.Io.Writer, max_len: usize) Generator.Error!void {
31-
std.debug.assert(max_len >= 1);
32-
const len = @min(
33-
self.rand.intRangeAtMostBiased(usize, self.min_len, self.max_len),
34-
max_len,
35-
);
43+
/// Return a copy of the Bytes, but with a new alphabet.
44+
pub fn newAlphabet(self: *const Bytes, new_alphabet: ?[]const u8) Bytes {
45+
return .{
46+
.rand = self.rand,
47+
.alphabet = new_alphabet,
48+
.min_len = self.min_len,
49+
.max_len = self.max_len,
50+
};
51+
}
52+
53+
/// Return a copy of the Bytes, but with a new min_len. The new min
54+
/// len cannot be more than the previous max_len.
55+
pub fn atLeast(self: *const Bytes, new_min_len: usize) Bytes {
56+
return .{
57+
.rand = self.rand,
58+
.alphabet = self.alphabet,
59+
.min_len = @min(self.max_len, new_min_len),
60+
.max_len = self.max_len,
61+
};
62+
}
63+
64+
/// Return a copy of the Bytes, but with a new max_len. The new max_len cannot
65+
/// be more the previous max_len.
66+
pub fn atMost(self: *const Bytes, new_max_len: usize) Bytes {
67+
return .{
68+
.rand = self.rand,
69+
.alphabet = self.alphabet,
70+
.min_len = @min(self.min_len, @min(self.max_len, new_max_len)),
71+
.max_len = @min(self.max_len, new_max_len),
72+
};
73+
}
74+
75+
pub fn next(self: *const Bytes, writer: *std.Io.Writer, max_len: usize) std.Io.Writer.Error!void {
76+
_ = try self.atMost(max_len).write(writer);
77+
}
78+
79+
pub fn format(self: *const Bytes, writer: *std.Io.Writer) std.Io.Writer.Error!void {
80+
_ = try self.write(writer);
81+
}
82+
83+
/// Write some random data and return the number of bytes written.
84+
pub fn write(self: *const Bytes, writer: *std.Io.Writer) std.Io.Writer.Error!usize {
85+
std.debug.assert(self.min_len >= 1);
86+
std.debug.assert(self.max_len >= self.min_len);
87+
88+
const len = self.rand.intRangeAtMostBiased(usize, self.min_len, self.max_len);
3689

3790
var buf: [8]u8 = undefined;
91+
3892
var remaining = len;
3993
while (remaining > 0) {
4094
const data = buf[0..@min(remaining, buf.len)];
@@ -45,16 +99,20 @@ pub fn next(self: *Bytes, writer: *std.Io.Writer, max_len: usize) Generator.Erro
4599
try writer.writeAll(data);
46100
remaining -= data.len;
47101
}
102+
103+
return len;
48104
}
49105

50106
test "bytes" {
51107
const testing = std.testing;
52108
var prng = std.Random.DefaultPrng.init(0);
53109
var buf: [256]u8 = undefined;
54110
var writer: std.Io.Writer = .fixed(&buf);
55-
var v: Bytes = .{ .rand = prng.random() };
56-
v.min_len = buf.len;
57-
v.max_len = buf.len;
111+
var v: Bytes = .{
112+
.rand = prng.random(),
113+
.min_len = buf.len,
114+
.max_len = buf.len,
115+
};
58116
const gen = v.generator();
59117
try gen.next(&writer, buf.len);
60118
try testing.expectEqual(buf.len, writer.buffered().len);

src/synthetic/Osc.zig

Lines changed: 42 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -35,19 +35,26 @@ p_valid: f64 = 1.0,
3535
p_valid_kind: std.enums.EnumArray(ValidKind, f64) = .initFill(1.0),
3636
p_invalid_kind: std.enums.EnumArray(InvalidKind, f64) = .initFill(1.0),
3737

38-
/// The alphabet for random bytes (omitting 0x1B and 0x07).
39-
const bytes_alphabet: []const u8 = alphabet: {
40-
var alphabet: [256]u8 = undefined;
41-
for (0..alphabet.len) |i| {
42-
if (i == 0x1B or i == 0x07) {
43-
alphabet[i] = @intCast(i + 1);
44-
} else {
45-
alphabet[i] = @intCast(i);
46-
}
47-
}
48-
const result = alphabet;
49-
break :alphabet &result;
50-
};
38+
fn checkKvAlphabet(c: u8) bool {
39+
return switch (c) {
40+
std.ascii.control_code.esc, std.ascii.control_code.bel, ';', '=' => false,
41+
else => std.ascii.isPrint(c),
42+
};
43+
}
44+
45+
/// The alphabet for random bytes in OSC key/value pairs (omitting 0x1B,
46+
/// 0x07, ';', '=').
47+
pub const kv_alphabet = Bytes.generateAlphabet(checkKvAlphabet);
48+
49+
fn checkOscAlphabet(c: u8) bool {
50+
return switch (c) {
51+
std.ascii.control_code.esc, std.ascii.control_code.bel => false,
52+
else => true,
53+
};
54+
}
55+
56+
/// The alphabet for random bytes in OSCs (omitting 0x1B and 0x07).
57+
pub const osc_alphabet = Bytes.generateAlphabet(checkOscAlphabet);
5158

5259
pub fn generator(self: *Osc) Generator {
5360
return .init(self, next);
@@ -99,35 +106,43 @@ fn nextUnwrapped(self: *Osc, writer: *std.Io.Writer, max_len: usize) Generator.E
99106

100107
fn nextUnwrappedValidExact(self: *const Osc, writer: *std.Io.Writer, k: ValidKind, max_len: usize) Generator.Error!void {
101108
switch (k) {
102-
.change_window_title => {
103-
try writer.writeAll("0;"); // Set window title
104-
var bytes_gen = self.bytes();
105-
try bytes_gen.next(writer, max_len - 2);
109+
.change_window_title => change_window_title: {
110+
if (max_len < 3) break :change_window_title;
111+
try writer.print("0;{f}", .{self.bytes().atMost(max_len - 3)}); // Set window title
106112
},
107113

108-
.prompt_start => {
114+
.prompt_start => prompt_start: {
115+
if (max_len < 4) break :prompt_start;
116+
var remaining = max_len;
117+
109118
try writer.writeAll("133;A"); // Start prompt
119+
remaining -= 4;
110120

111121
// aid
112-
if (self.rand.boolean()) {
113-
var bytes_gen = self.bytes();
114-
bytes_gen.max_len = 16;
122+
if (self.rand.boolean()) aid: {
123+
if (remaining < 6) break :aid;
115124
try writer.writeAll(";aid=");
116-
try bytes_gen.next(writer, max_len);
125+
remaining -= 5;
126+
remaining -= try self.bytes().newAlphabet(kv_alphabet).atMost(@min(16, remaining)).write(writer);
117127
}
118128

119129
// redraw
120-
if (self.rand.boolean()) {
130+
if (self.rand.boolean()) redraw: {
131+
if (remaining < 9) break :redraw;
121132
try writer.writeAll(";redraw=");
122133
if (self.rand.boolean()) {
123134
try writer.writeAll("1");
124135
} else {
125136
try writer.writeAll("0");
126137
}
138+
remaining -= 9;
127139
}
128140
},
129141

130-
.prompt_end => try writer.writeAll("133;B"), // End prompt
142+
.prompt_end => prompt_end: {
143+
if (max_len < 4) break :prompt_end;
144+
try writer.writeAll("133;B"); // End prompt
145+
},
131146
}
132147
}
133148

@@ -139,22 +154,19 @@ fn nextUnwrappedInvalidExact(
139154
) Generator.Error!void {
140155
switch (k) {
141156
.random => {
142-
var bytes_gen = self.bytes();
143-
try bytes_gen.next(writer, max_len);
157+
try self.bytes().atMost(max_len).format(writer);
144158
},
145159

146160
.good_prefix => {
147-
try writer.writeAll("133;");
148-
var bytes_gen = self.bytes();
149-
try bytes_gen.next(writer, max_len - 4);
161+
try writer.print("133;{f}", .{self.bytes().atMost(max_len - 4)});
150162
},
151163
}
152164
}
153165

154166
fn bytes(self: *const Osc) Bytes {
155167
return .{
156168
.rand = self.rand,
157-
.alphabet = bytes_alphabet,
169+
.alphabet = osc_alphabet,
158170
};
159171
}
160172

src/synthetic/cli/Ascii.zig

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,21 @@ const Ascii = @This();
33
const std = @import("std");
44
const assert = std.debug.assert;
55
const Allocator = std.mem.Allocator;
6-
const synthetic = @import("../main.zig");
6+
const Bytes = @import("../Bytes.zig");
77

88
const log = std.log.scoped(.@"terminal-stream-bench");
99

1010
pub const Options = struct {};
1111

12+
fn checkAsciiAlphabet(c: u8) bool {
13+
return switch (c) {
14+
' ' => false,
15+
else => std.ascii.isPrint(c),
16+
};
17+
}
18+
19+
pub const ascii = Bytes.generateAlphabet(checkAsciiAlphabet);
20+
1221
/// Create a new terminal stream handler for the given arguments.
1322
pub fn create(
1423
alloc: Allocator,
@@ -23,12 +32,10 @@ pub fn destroy(self: *Ascii, alloc: Allocator) void {
2332
alloc.destroy(self);
2433
}
2534

26-
pub fn run(self: *Ascii, writer: *std.Io.Writer, rand: std.Random) !void {
27-
_ = self;
28-
29-
var gen: synthetic.Bytes = .{
35+
pub fn run(_: *Ascii, writer: *std.Io.Writer, rand: std.Random) !void {
36+
var gen: Bytes = .{
3037
.rand = rand,
31-
.alphabet = synthetic.Bytes.Alphabet.ascii,
38+
.alphabet = ascii,
3239
};
3340

3441
while (true) {

0 commit comments

Comments
 (0)