Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 75 additions & 17 deletions src/synthetic/Bytes.zig
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/// Generates bytes.
//! Generates bytes.
const Bytes = @This();

const std = @import("std");
Expand All @@ -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),

Expand All @@ -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)];
Expand All @@ -45,16 +99,20 @@ 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" {
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;
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);
Expand Down
72 changes: 42 additions & 30 deletions src/synthetic/Osc.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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
},
}
}

Expand All @@ -139,22 +154,19 @@ 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)});
},
}
}

fn bytes(self: *const Osc) Bytes {
return .{
.rand = self.rand,
.alphabet = bytes_alphabet,
.alphabet = osc_alphabet,
};
}

Expand Down
19 changes: 13 additions & 6 deletions src/synthetic/cli/Ascii.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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) {
Expand Down