Skip to content

Commit 80c250a

Browse files
committed
synthetic: make bytes generation more flexible
1 parent 3d837cb commit 80c250a

File tree

3 files changed

+158
-53
lines changed

3 files changed

+158
-53
lines changed

src/synthetic/Bytes.zig

Lines changed: 145 additions & 19 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,54 +7,180 @@ 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.
13-
min_len: usize = 1,
14-
max_len: usize = std.math.maxInt(usize),
10+
/// The minimum and maximum length of the generated bytes.
11+
_min_len: usize = 1,
12+
_max_len: usize = std.math.maxInt(usize),
1513

1614
/// The possible bytes that can be generated. If a byte is duplicated
1715
/// in the alphabet, it will be more likely to be generated. That's a
1816
/// side effect of the generator, not an intended use case.
19-
alphabet: ?[]const u8 = null,
17+
_alphabet: ?[]const u8 = null,
2018

2119
/// Predefined alphabets.
2220
pub const Alphabet = struct {
23-
pub const ascii = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()_+-=[]{}|;':\\\",./<>?`~";
21+
fn _generate(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+
}
38+
39+
fn _ascii(c: u8) bool {
40+
return switch (c) {
41+
' ' => false,
42+
else => std.ascii.isPrint(c),
43+
};
44+
}
45+
46+
pub const ascii = _generate(_ascii);
47+
pub const alphanumeric = _generate(std.ascii.isAlphanumeric);
48+
pub const alphabetic = _generate(std.ascii.isAlphabetic);
49+
50+
fn _kv(c: u8) bool {
51+
return switch (c) {
52+
std.ascii.control_code.esc, std.ascii.control_code.bel, ';', '=' => false,
53+
else => std.ascii.isPrint(c),
54+
};
55+
}
56+
57+
/// The alphabet for random bytes in OSC key/value pairs (omitting 0x1B,
58+
/// 0x07, ';', '=').
59+
pub const kv = _generate(_kv);
60+
61+
fn _osc(c: u8) bool {
62+
return switch (c) {
63+
std.ascii.control_code.esc, std.ascii.control_code.bel => false,
64+
else => true,
65+
};
66+
}
67+
68+
/// The alphabet for random bytes in OSCs (omitting 0x1B and 0x07).
69+
pub const osc = _generate(_osc);
70+
71+
pub const Keys = keys: {
72+
const decls = @typeInfo(Alphabet).@"struct".decls;
73+
var count = 0;
74+
for (decls) |decl| {
75+
if (std.mem.eql(u8, decl.name, "Keys")) continue;
76+
if (decl.name[0] == '_') continue;
77+
count += 1;
78+
}
79+
var fields: [count]std.builtin.Type.EnumField = undefined;
80+
var i = 0;
81+
for (decls) |decl| {
82+
if (std.mem.eql(u8, decl.name, "Keys")) continue;
83+
if (decl.name[0] == '_') continue;
84+
fields[i].name = decl.name;
85+
fields[i].value = i;
86+
i += 1;
87+
}
88+
break :keys @Type(
89+
.{
90+
.@"enum" = .{
91+
.fields = &fields,
92+
.decls = &.{},
93+
.is_exhaustive = true,
94+
.tag_type = std.math.IntFittingRange(0, count),
95+
},
96+
},
97+
);
98+
};
2499
};
25100

26101
pub fn generator(self: *Bytes) Generator {
27102
return .init(self, next);
28103
}
29104

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-
);
105+
pub fn init(rand: std.Random) Bytes {
106+
return .{
107+
.rand = rand,
108+
};
109+
}
110+
111+
pub fn next(self: *const Bytes, writer: *std.Io.Writer, max_len: usize) std.Io.Writer.Error!void {
112+
try self.atMost(max_len).format(writer);
113+
}
114+
115+
/// Return a copy of the Bytes, but with a new alphabet.
116+
pub fn alphabet(self: *const Bytes, comptime key: Alphabet.Keys) Bytes {
117+
return .{
118+
._alphabet = @field(Alphabet, @tagName(key)),
119+
._min_len = self._min_len,
120+
._max_len = self._max_len,
121+
.rand = self.rand,
122+
};
123+
}
124+
125+
/// Return a copy of the Bytes, but with a new min_len. The new min
126+
/// len cannot be more than the previous max_len.
127+
pub fn atLeast(self: *const Bytes, min_len: usize) Bytes {
128+
return .{
129+
.rand = self.rand,
130+
._alphabet = self._alphabet,
131+
._min_len = @min(self._max_len, min_len),
132+
._max_len = self._max_len,
133+
};
134+
}
135+
136+
/// Return a copy of the Bytes, but with a new max_len. The new max_len cannot
137+
/// be more the previous max_len.
138+
pub fn atMost(self: *const Bytes, max_len: usize) Bytes {
139+
return .{
140+
.rand = self.rand,
141+
._alphabet = self._alphabet,
142+
._min_len = @min(self._min_len, @min(self._max_len, max_len)),
143+
._max_len = @min(self._max_len, max_len),
144+
};
145+
}
146+
147+
pub fn format(self: *const Bytes, writer: *std.Io.Writer) std.Io.Writer.Error!void {
148+
_ = try self.write(writer);
149+
}
150+
151+
/// Write some random data and return the number of bytes written.
152+
pub fn write(self: *const Bytes, writer: *std.Io.Writer) std.Io.Writer.Error!usize {
153+
std.debug.assert(self._min_len >= 1);
154+
std.debug.assert(self._max_len >= self._min_len);
155+
156+
const len = self.rand.intRangeAtMostBiased(usize, self._min_len, self._max_len);
36157

37158
var buf: [8]u8 = undefined;
159+
38160
var remaining = len;
39161
while (remaining > 0) {
40162
const data = buf[0..@min(remaining, buf.len)];
41163
self.rand.bytes(data);
42-
if (self.alphabet) |alphabet| {
43-
for (data) |*byte| byte.* = alphabet[byte.* % alphabet.len];
164+
if (self._alphabet) |_alphabet| {
165+
for (data) |*byte| byte.* = _alphabet[byte.* % _alphabet.len];
44166
}
45167
try writer.writeAll(data);
46168
remaining -= data.len;
47169
}
170+
171+
return len;
48172
}
49173

50174
test "bytes" {
51175
const testing = std.testing;
52176
var prng = std.Random.DefaultPrng.init(0);
53177
var buf: [256]u8 = undefined;
54178
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;
179+
var v: Bytes = .{
180+
.rand = prng.random(),
181+
._min_len = buf.len,
182+
._max_len = buf.len,
183+
};
58184
const gen = v.generator();
59185
try gen.next(&writer, buf.len);
60186
try testing.expectEqual(buf.len, writer.buffered().len);

src/synthetic/Osc.zig

Lines changed: 12 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -35,20 +35,6 @@ 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-
};
51-
5238
pub fn generator(self: *Osc) Generator {
5339
return .init(self, next);
5440
}
@@ -100,30 +86,32 @@ fn nextUnwrapped(self: *Osc, writer: *std.Io.Writer, max_len: usize) Generator.E
10086
fn nextUnwrappedValidExact(self: *const Osc, writer: *std.Io.Writer, k: ValidKind, max_len: usize) Generator.Error!void {
10187
switch (k) {
10288
.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);
89+
try writer.print("0;{f}", .{self.bytes().atMost(max_len - 3)}); // Set window title
10690
},
10791

10892
.prompt_start => {
93+
var remaining = max_len;
94+
10995
try writer.writeAll("133;A"); // Start prompt
96+
remaining -= 4;
11097

11198
// aid
11299
if (self.rand.boolean()) {
113-
var bytes_gen = self.bytes();
114-
bytes_gen.max_len = 16;
115100
try writer.writeAll(";aid=");
116-
try bytes_gen.next(writer, max_len);
101+
remaining -= 5;
102+
remaining -= try self.bytes().alphabet(.kv).atMost(@min(16, remaining)).write(writer);
117103
}
118104

119105
// redraw
120-
if (self.rand.boolean()) {
106+
if (self.rand.boolean()) redraw: {
107+
if (remaining < 9) break :redraw;
121108
try writer.writeAll(";redraw=");
122109
if (self.rand.boolean()) {
123110
try writer.writeAll("1");
124111
} else {
125112
try writer.writeAll("0");
126113
}
114+
remaining -= 9;
127115
}
128116
},
129117

@@ -139,23 +127,17 @@ fn nextUnwrappedInvalidExact(
139127
) Generator.Error!void {
140128
switch (k) {
141129
.random => {
142-
var bytes_gen = self.bytes();
143-
try bytes_gen.next(writer, max_len);
130+
try self.bytes().atMost(max_len).format(writer);
144131
},
145132

146133
.good_prefix => {
147-
try writer.writeAll("133;");
148-
var bytes_gen = self.bytes();
149-
try bytes_gen.next(writer, max_len - 4);
134+
try writer.print("133;{f}", .{self.bytes().atMost(max_len - 4)});
150135
},
151136
}
152137
}
153138

154139
fn bytes(self: *const Osc) Bytes {
155-
return .{
156-
.rand = self.rand,
157-
.alphabet = bytes_alphabet,
158-
};
140+
return Bytes.init(self.rand).alphabet(.osc);
159141
}
160142

161143
/// Choose whether to generate a valid or invalid OSC request based

src/synthetic/cli/Ascii.zig

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,7 @@ pub fn destroy(self: *Ascii, alloc: Allocator) void {
2626
pub fn run(self: *Ascii, writer: *std.Io.Writer, rand: std.Random) !void {
2727
_ = self;
2828

29-
var gen: synthetic.Bytes = .{
30-
.rand = rand,
31-
.alphabet = synthetic.Bytes.Alphabet.ascii,
32-
};
29+
var gen = synthetic.Bytes.init(rand).alphabet(.ascii);
3330

3431
while (true) {
3532
gen.next(writer, 1024) catch |err| {

0 commit comments

Comments
 (0)