Help converting u8 to string

Definitely, yes. And thanks for the challenge. This will stretch me. for sure. :+1:

1 Like

I’m trying to tackle this first, but have never used structs in Zig, so I hope you can tell me what I’m missing in this practice code:

const std = @import("std");

pub fn main() void {

	const Person = struct {
		name: []const u8,
		age: u7,

		pub fn init(nm: []const u8, a: u7) Person {
			return Person{
				.name = nm,
				.age = a,
			};
		}
	};

	const Ted: Person = Person.init("Theodore", 35);

	std.debug.print("{s} is {d} years old.\n", .{Ted.name, Ted.age});
}

Compiler (v0.14.0) tells me:

swk.zig:9:38: error: use of undeclared identifier 'Person'
  pub fn init(nm: []const u8, a: u7) Person {

Thanks in advance and apologies for the rudimentary question. I do hope this discussion will benefit other beginners as well.

You got that error because you declared your struct type within function scope, within function scope the const Person isn’t introduced as a known identifier until the const Person = struct { ... }; part is done completely, however within your struct you already try to use the name Person, to avoid this error, simply remember to declare struct/enum/union types within a container scope, there the name is immediately known so the struct can refer to its own name.

The file itself is also a struct so at the top of a file it is also container scope.

(You also could use @This() instead of Person to keep it in function scope, however that isn’t idiomatic)

const Person = struct {
	name: []const u8,
	age: u7,

	pub fn init(name: []const u8, age: u7) Person {
		return Person{
			.name = name,
			.age = age,
		};
	}
};

pub fn main() void {
    const Ted: Person = Person.init("Theodore", 35);
    std.debug.print("{s} is {d} years old.\n", .{Ted.name, Ted.age});
}

Also you don’t have to avoid the name and age as parameter names, those names don’t collide with/shadow the names of the fields, because accessing the fields always happens explicitly through dot access from some instance.

Also you can use decl literals and by convention types Person start with capital letters and instances ted with lowercase:

pub fn main() void {
    const ted: Person = .init("Theodore", 35);
    std.debug.print("{s} is {d} years old.\n", .{ted.name, ted.age});
}
2 Likes

Ah, understood. I was taking my cue from this example in the documentation. But I see now that this is a testing example, where the issue of scope isn’t in play:

// Functions in the struct's namespace can be called with dot syntax.
const Vec3 = struct {
    x: f32,
    y: f32,
    z: f32,

    pub fn init(x: f32, y: f32, z: f32) Vec3 {
        return Vec3{
            .x = x,
            .y = y,
            .z = z,
        };
    }

    pub fn dot(self: Vec3, other: Vec3) f32 {
        return self.x * other.x + self.y * other.y + self.z * other.z;
    }
};

Many thanks, hopefully this will get me back on track. Thanks, too, for the additional tips. :folded_hands:

I’ve tried to implement all of @milogreg’s suggestions (except for maxColumnWriter, which I’ll tackle next). No urgency, but would appreciate it if he or any other experienced hands could let me know if and how I’m barking up the wrong tree.

// xencode.zig: Human-readable plain-text encoding for binary files
// CLD rev. 2025-07-14_03:25

const std = @import("std");
const builtin = @import("builtin");
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
const ap_allocator = arena.allocator();
var tw: u8 = 65; // output text width
var twct: u8 = 1; // tw counter

const Config = struct {
	show_help: bool = false,
	printable: bool = false,
	xy_readable: bool = false,
	tx_width: u8 = 65,
	next_arg: u8 = 1,

	pub fn initFromArgs(args: []const []const u8) !Config {
		var show_help: bool = false;
		var printable: bool = false;
		var xy_readable: bool = false;
		var tx_width: u8 = 65;
		var next_arg: u8 = 1;
		while (next_arg < args.len) {
			if (
					std.mem.eql(u8, args[1], "-?") or
					std.mem.eql(u8, args[1], "/?") or
					std.mem.eql(u8, args[1], "-h") or
					std.mem.eql(u8, args[1], "--help")) {
				show_help = true;
				break;			
			}
			if (std.mem.eql(u8, args[next_arg], "-a") or
				std.mem.eql(u8, args[next_arg], "/a")) {
				printable = true;
				next_arg += 1;
				continue;
			}
			if (std.mem.eql(u8, args[next_arg], "-w") or
				std.mem.eql(u8, args[next_arg], "/w")) {
				if (next_arg + 1 <= args.len - 1) {
			    tx_width = try std.fmt.parseInt(u8, args[next_arg + 1], 10);
					next_arg += 2;
					continue;
				}
			}
			if (std.mem.eql(u8, args[next_arg], "-x") or
				std.mem.eql(u8, args[next_arg], "/x")) {
				xy_readable = true;
				next_arg += 1;
				continue;
			}
			break;
		}
		return Config {
			.show_help = show_help,
			.printable = printable,
			.xy_readable = xy_readable,
			.tx_width = tx_width,
			.next_arg = next_arg,
		};
	}
};

pub fn main() !void {
	defer arena.deinit();
	const args = try std.process.argsAlloc(ap_allocator);
	const conf: Config = try .initFromArgs(args);
	if (conf.show_help) {
		showHelp();
		return;
	}
	const OutputMode = enum {printable_only, normal, xy_readability_aids};
	var omode: OutputMode = .normal;
	if (conf.printable) omode = .printable_only;
	if (conf.xy_readable) omode = .xy_readability_aids;
	tw = conf.tx_width;

	var byte2: u16 = undefined;
	var byte3: u16 = undefined;
 	const tab: []const u8 = "{tab}";
	const crlf0: []const u8 = "[013+010]";
	const crlf: []const u8 = "[cr|lf]";
	const lbrc: []const u8 = "{091}";
	const rbrc: []const u8 = "{093}";
	const lguil: []const u8 = "{<}";
	const rguil: []const u8 = "{>}";
	const spce: []const u8 = "{032}";
	var ctr: u8 = 0;
	var n: u32 = 0;
	var use_file: bool = false;
	if (conf.next_arg > args.len - 1) {
		showHelp();
		return;
	}
	const file_in: []u8 = args[conf.next_arg];
	std.fs.cwd().access(file_in, .{}) catch |err| {
		switch (err) {
			error.FileNotFound => {
				std.debug.print("File not found: \"{s}\"\n", .{file_in});
				return;
			},
			else => unreachable,
		}
	};
	var file_ex: []u8 = undefined;
	if (conf.next_arg + 1 == args.len - 1) {
		file_ex = args[conf.next_arg + 1];
		use_file = true;
	}
	const data: []u8 = try fileRead(file_in);
	const file = if(use_file) try std.fs.cwd().createFile(file_ex, .{})
		else undefined;
	defer if(use_file) file.close();
	var buffered = std.io.bufferedWriter(if(use_file) file.writer()
		else std.io.getStdOut().writer());
	const out = buffered.writer();
	try out.writeAll("XPLeNCODE v2.0 (xencode.exe)");
	try addNewline(out);
	try out.writeAll("b-gin [UNTITLED]");
	try addNewline(out);
	while (n < data.len) {
		if (conf.printable) {
			if (data[n] > 32 and data[n] < 127) {
				try writeByteLnBk(data[n], out);
			}
			else if (data[n] == 32) {
				if (twct > 1 and twct < tw - 1) try writeByteLnBk(' ', out)
					else try writeAllLnBk(spce, out);	
			} else {
				try writeByteLnBk('.', out);
			}
			n += 1;
			continue;
		}
		ctr = 0;
		try switch (data[n]) {
			9 => {
				if(omode == .normal) try putCharNumBr(9, out);
				if(omode == .xy_readability_aids) {
					try writeAllLnBk(tab, out);
				} else {
					try writeByteLnBk('.', out);
				}
			},
			13 => {
				if (omode == .printable_only) {
					try writeByteLnBk('.', out);
					n += 1;
					continue;
				}
				if (data[n + 1] == 10) {
					if (omode == .xy_readability_aids) {
						try writeAllLnBk(crlf, out);
					} else {
						try writeAllLnBk(crlf0, out);
					}
					n += 1;
				} else {
					try putCharNumBr(data[n], out);
				}
			},
			32 => {
				if (twct > 1 and twct < tw - 1) try writeByteLnBk(' ', out)
					else try writeAllLnBk(spce, out);	
			},
			33...90, 92, 94...122, 124 => try writeByteLnBk(data[n], out),
			91 => {
				if (omode == .xy_readability_aids) try writeAllLnBk(lbrc, out)
					else try writeByteLnBk(data[n], out);
			},
			93 => {
				if (omode == .xy_readability_aids) try writeAllLnBk(rbrc, out)
					else try writeByteLnBk(data[n], out);
			},
			123 => putCharNumBr(data[n], out),
			125 => putCharNumBr(data[n], out),
			174 => {
				if (omode == .xy_readability_aids) try writeAllLnBk(lguil, out)
					else try putCharNumBr(data[n], out);
			},
			175 => {
				if (omode == .xy_readability_aids) try writeAllLnBk(rguil, out)
					else try putCharNumBr(data[n], out);
			},
			254 => { // Speedo charset
				if (omode != .xy_readability_aids) {
					try putCharNumBr(data[n], out);
					n += 1;
					continue;
				}
				if (data.len - n < 3) {
					try putCharNumBr(data[n], out);
					n += 1;
					continue;
				}
				byte2  = data[n + 1];
				byte3  = data[n + 2];
				if (byte3 < 2 or (byte3 < 3 and byte2 < 232)) {
					try putXySpeedoChar(byte2, byte3, out);
					n += 3;
				} else {
 				// Generic 3-byter [254+nnn+nnn]
					try put3byterBr(254, byte2, byte3, out);
					n += 3;
					continue;
				}
			},
			255 => {
				if (omode != .xy_readability_aids) {
					try putCharNumBr(data[n], out);
					n += 1;
					continue;
				}
				if (data.len - n < 3) {
					try putCharNumBr(data[n], out);
					n += 1;
					continue;
				}
				byte2  = data[n + 1];
				byte3  = data[n + 2];
				// 3-byte functions
				if (byte2 > 127 and byte2 < 131 and byte3 % 2 == 1) {
						try putXyFunc(byte2, byte3, out);
						n += 3;
						continue;
				}
				// Search wildcards
				else if ((byte2 == 192 and (byte3 == 145 or byte3 == 153 or byte3 == 155 or byte3 == 173 or byte3 == 174 or (byte3 >= 176 and byte3 <= 185) or byte3 == 193 or byte3 == 204 or byte3 == 206 or byte3 == 207 or byte3 == 211 or byte3 == 215 or byte3 == 216)) or (byte2 == 193 and (byte3 == 46 or byte3 == 47))) {
					try putXyWildcard(byte3, out);
					n += 3;
					continue;
				} else {
 				// Generic 3-byter [255+nnn+nnn]
					try put3byterBr(255, byte2, byte3, out);
					n += 3;
					continue;
				}
			},
			else => try putCharNumBr(data[n], out),
		};
		n += 1;
	}
	if (twct > 1) try addNewline(out);
	try out.writeAll("-nd");
	try addNewline(out);
	try out.writeAll("XPLeNCODE");
	try addNewline(out);
	try buffered.flush();
}

pub fn showHelp() void {
	std.debug.print("Human-readable Plain-text Encoding for Binary Files\n                              [CLD rev. 2025-07-14]\n\nUsage (optional arguments first):\nxencode [-a|-x] [-w <number>] file_in [file_out] | [-?]\n\nIf file_out is omitted, output is directed to stdout.\n\nOptions:\n  -a outputs Ascii 32-126 only (not decodable)\n  -x applies XyWrite readability aids to output\n  -w <number> changes text width of output to <number>\n     (default = 65 characters per line)\n  -? shows this help\n", .{});
}

pub fn fileRead(fname: []const u8) ![]u8 {
	const fileContents = try std.fs.cwd().readFileAlloc(
		ap_allocator,
		fname,
		std.math.maxInt(u32));
	return fileContents;
}

pub fn writeByteLnBk(byte: u8, wr: anytype) !void {
	try wr.writeByte(byte);
	twct += 1;
	if (twct > tw) {
		if (builtin.target.os.tag == .windows) try wr.writeByte(13);
		try wr.writeByte(10);
		twct = 1;
	}
}

pub fn writeAllLnBk(bytes: []const u8, wr: anytype) !void {
	for (bytes) |b| try writeByteLnBk(b, wr);
}

pub fn addNewline(wr: anytype) !void {
	if (builtin.target.os.tag == .windows) try wr.writeByte(13);
	try wr.writeByte(10);
}

pub fn putCharNumBr(char_in: u8, wr: anytype) !void {
	var buffer: [5]u8 = undefined;
	const charnum: []u8 = try std.fmt.bufPrint(
		&buffer,
		"{{{d:0>3}}}",
		.{char_in});
	try writeAllLnBk(charnum, wr);
}

pub fn put3byterBr(byte1: u16, byte2: u16, byte3: u16, wr: anytype) !void {
	var buffer: [13]u8 = undefined;
	const brace3: []u8 = try std.fmt.bufPrint(
		&buffer,
		"[{d:0>3}+{d:0>3}+{d:0>3}]", 
		.{byte1, byte2, byte3});
	try writeAllLnBk(brace3, wr);
}

pub fn putXyFunc(byte_2: u16, byte_3: u16, wr: anytype) !void {
	const index: u32 = (256 * (byte_2 % 128) / 2) + (byte_3 / 2);
	var afunc: [5]u8 = undefined;
	const func_no = [_]u8{'[', '2', '5', '5', '+', '1', '2', '9', '+',
		 '1', '6', '3', ']'};
	const func_nos = func_no[0..];
	const xyfuncs = [_]*const [2:0]u8{
"@0", "@1", "@2", "@3", "@4", "@5", "@6", "@7", "@8", "@9", "@A", "@B",
"@C", "@D", "@E", "@F", "@G", "@H", "@I", "@J", "@K", "@L", "@M", "@N",
"@O", "@P", "@Q", "@R", "@S", "@T", "@U", "@V", "@W", "@X", "@Y", "@Z",
"AD", "AS", "BF", "BK", "BS", "CC", "CD", "CH", "CI", "CL", "CM", "CN",
"CP", "CR", "CS", "CU", "DC", "DF", "GH", "DL", "DP", "DS", "DW", "EL",
"ER", "EX", "GT", "HM", "M0", "M1", "M2", "M3", "M4", "M5", "M6", "M7",
"M8", "MD", "MU", "MV", "NC", "NL", "NK", "NP", "NR", "NS", "NT", "NW",
"PC", "PD", "PL", "PP", "PR", "PS", "PT", "PU", "PW", "R0", "R1", "R2",
"R3", "R4", "R5", "R6", "R7", "R8", "R9", "RC", "RD", "RE", "RL", "RP",
"RS", "RV", "RW", "SD", "SH", "SI", "SK", "SM", "SN", "SS", "SU", "SV",
"TF", "TI", "TN", "TS", "UD", "WA", "WC", "WL", "WN", "WS", "WX", "WW",
"XC", "XD", "DT", "S1", "S2", "S3", "S4", "S5", "S6", "S7", "SP", "BC",
"LB", "LE", "NF", "PF", "TP", "BD", "MS", "NM", "LD", "LL", "LR", "LU",
"UP", "FF", "YD", "DO", "DX", "MK", "SO", "OP", "WZ", "NX", "SW", "FD",
"FM", "TL", "TR", "TE", "ED", "EE", "HC", "EC", "MC", "#1", "#2", "#3",
"#4", "#5", "#6", "#7", "#8", "#9", "$1", "$2", "$3", "$4", "$5", "$6",
"$7", "$8", "$9", "DR", "EN", "C0", "C1", "C2", "C3", "C4", "C5", "C6",
"C7", "C8", "C9", "EF", "IB", "NO", "NI", "CO", "$0", "LS", "XP", "WG",
"XM", "&0", "&1", "&2", "&3", "&4", "&5", "&6", "&7", "&8", "&9", "&A",
"&B", "&C", "&D", "&E", "&F", "&G", "&H", "&I", "&J", "&K", "&L", "&M",
"&N", "&O", "&P", "&Q", "&R", "&S", "&T", "&U", "&V", "&W", "&X", "&Y",
"&Z", "HL", "$A", "$B", "$C", "$D", "$E", "$F", "$G", "$H", "$I", "$J",
"$K", "$L", "$M", "$N", "$O", "$P", "$Q", "$R", "$S", "$T", "$U", "$V",
"$W", "$X", "$Y", "$Z", "XX", "H@", "VH", "MW", "QH", "DK", "SR", "SC",
"TG", "H1", "JH", "DZ", "DD", "DM", "LT", "RK", "NN", "MT", "ET", "ZT",
"T1", "TT", "<<", ">>", "IT", "SL", "SF", "FL", "FR", "FC", "SY", "ME",
"AC", "FS", "TW", "MI", "RO", "NB", "Q1", "Q2", "Q3", "Q4", "Q5", "Q6",
"Q7", "Q8", "TO", "IR", "AR", "AX", "DB", "DE", "HF", "SA", "OV", "TC",
"TB", "JM", "SG", "XH", "FT", "BX", "MN", "CB", "M9", "MZ", "ZZ", "RX",
"ST", "KF", "JC", "AK", "TM", "NU", "B4", "QP", "HG", "US", "XE", "ES",
"RB", "S-", "S+", "**", "BN", "RU", "CF", "UI", "XS", "EA", "BT", "KD",
"DN", "HI", "WH", "XN", "FX", "UN", "MX", "AZ", "BR", "HK", "#X", "??",
"BM", "JR", "XO", "XW", "TX", "LF", "LO", "BL", "XT", "WT", "IC", "CT",
"VB", "-D", "WD", "RM", "LM", "aL", "aR", "aB", "aE", "MP", "mN", "QL",
"QR", "MF"};
	if (index < xyfuncs.len) {
		afunc[0] = '[';
		afunc[1] = xyfuncs[index][0];
		afunc[2] = xyfuncs[index][1];
		if (afunc[1] == 'N' and afunc[2] == 'O') {
			try writeAllLnBk(func_nos, wr); // workaround for flaky func NO
			return;
		}
		afunc[3] = '_';
		afunc[4] = ']';
		try writeAllLnBk(afunc[0..], wr);
	}
}

pub fn putXyWildcard(byte_3: u16, wr: anytype) !void {
	var wild: [4]u8 = undefined;
	wild[0] = '[';
	wild[1] = 'w';
	var wild1: [5]u8 = undefined;
	wild1[0] = '[';
	wild1[1] = 'w';
	var wilddot = [_]u8{
		'[', '2', '5', '5', '+', '1', '9', '2', '+', '1', '7', '4', ']'};
	var c: u3 = 2;
	switch (byte_3) {
		46 => {wild[c] = '<'; c += 1;}, 
		47 => {wild[c] = '>'; c += 1;},
		145 => {wild1[c] = '1'; wild1[c + 1] = '3'; c += 2;},
		153 => {wild1[c] = '1'; wild1[c + 1] = '0'; c += 2;},
		155 => {wild[c] = 'C'; c += 1;}, 
		173 => {wild[c] = '-'; c += 1;}, 
		174 => {wild[c] = '.'; c += 1;}, 
		176 => {wild[c] = '0'; c += 1;}, 
		177 => {wild[c] = '1'; c += 1;}, 
		178 => {wild[c] = '2'; c += 1;}, 
		179 => {wild[c] = '3'; c += 1;}, 
		180 => {wild[c] = '4'; c += 1;}, 
		181 => {wild[c] = '5'; c += 1;}, 
		182 => {wild[c] = '6'; c += 1;}, 
		183 => {wild[c] = '7'; c += 1;}, 
		184 => {wild[c] = '8'; c += 1;}, 
		185 => {wild[c] = '9'; c += 1;}, 
		193 => {wild[c] = 'A'; c += 1;}, 
		204 => {wild[c] = 'L'; c += 1;}, 
		206 => {wild[c] = 'N'; c += 1;}, 
		207 => {wild[c] = 'O'; c += 1;}, 
		211 => {wild[c] = 'S'; c += 1;}, 
		215 => {wild[c] = 'W'; c += 1;}, 
		216 => {wild[c] = 'X'; c += 1;},
		else => wild[0] = 0,
	}
	if (wild[2] == '.') {
		try writeAllLnBk(wilddot[0..], wr);
		return;
	}
	if (c > 3) {
		wild1[c] = ']';
		try writeAllLnBk(wild1[0..], wr);
	} else {
		wild[c] = ']';
		try writeAllLnBk(wild[0..], wr);
	}
}

pub fn putXySpeedoChar(byte_2: u16, byte_3: u16, wr: anytype) !void {
	const index: u32 = (256 * (1 + byte_3)) + byte_2;
	var buffer: [5]u8 = undefined;
	const speedo: []u8 = try std.fmt.bufPrint(
		&buffer,
		"[{d}]", .{index});
	try writeAllLnBk(speedo, wr);
}

2 Likes

You’re on the right track, here’s a few notes:

  • The OutputMode type should be declared at the file scope or in Config’s scope, rather than in main.
  • Config should store an OutputMode instead of printable and xy_readable, and initFromArgs should determine the value of this OutputMode.
  • initFromArgs should just make a Config variable and populate/return that, rather than making new variables for each field.
  • This logic should be in initFromArgs, not main:
if (conf.next_arg > args.len - 1) {
    showHelp();
    return;
}
  • This is unrelated to the recent changes, but I think it’s worth pointing out: you shouldn’t use unreachable in places that are reachable. This is the code in question:
switch (err) {
    error.FileNotFound => {
        std.debug.print("File not found: \"{s}\"\n", .{file_in});
        return;
    },
    else => unreachable,
}
1 Like

I’ve tried to take in all of your comments. Updated code below.

With regard to your challenge to create a new writer that automatically inserts line breaks, I’m not exactly sure where to start. Currently, the core procedure is to write the output byte by byte, incrementing the horizontal write position each time; when the write position exceeds the max column width, insert a newline and reset the position counter to 1. So is the task to modify the writeByte or equivalent method of the current writer so that it does this? Because this is the only way I can think of to regulate the line width of the output. If there’s a better way, I’d be all ears.

// xencode.zig: Human-readable plain-text encoding for binary files
// CLD rev. 2025-07-15_03:22

const std = @import("std");
const builtin = @import("builtin");
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
const ap_allocator = arena.allocator();
var max_col: u8 = 65; // output text width
var col_ctr: u8 = 1; // column counter

const OutputMode = enum {
	show_help,
	printable_only,
	normal,
	xy_readability_aids,
};

const Config = struct {
	omode: OutputMode = .normal,
	tx_width: u8 = 65,
	next_arg: u8 = 1,

	pub fn initFromArgs(args: []const []const u8) !Config {
		var omode: OutputMode = .normal;
		var tx_width: u8 = 65;
		var next_arg: u8 = 1;
		while (next_arg < args.len) {
			if (
					std.mem.eql(u8, args[1], "-?") or
					std.mem.eql(u8, args[1], "/?") or
					std.mem.eql(u8, args[1], "-h") or
					std.mem.eql(u8, args[1], "--help")) {
				omode = .show_help;
				break;			
			}
			if (std.mem.eql(u8, args[next_arg], "-a") or
				std.mem.eql(u8, args[next_arg], "/a")) {
				omode = .printable_only;
				next_arg += 1;
				continue;
			}
			if (std.mem.eql(u8, args[next_arg], "-w") or
				std.mem.eql(u8, args[next_arg], "/w")) {
				if (next_arg + 1 <= args.len - 1) {
			    tx_width = try std.fmt.parseInt(u8, args[next_arg + 1], 10);
					next_arg += 2;
					continue;
				}
			}
			if (std.mem.eql(u8, args[next_arg], "-x") or
				std.mem.eql(u8, args[next_arg], "/x")) {
				omode = .xy_readability_aids;
				next_arg += 1;
				continue;
			}
			break;
		}
		if (next_arg > args.len - 1) omode = .show_help;
		return Config {
			.omode = omode,
			.tx_width = tx_width,
			.next_arg = next_arg,
		};
	}
};

pub fn main() !void {
	defer arena.deinit();
	const args = try std.process.argsAlloc(ap_allocator);
	const conf: Config = try .initFromArgs(args);
	const omode = conf.omode;
	if (omode == .show_help) {
		showHelp();
		return;
	}
	max_col = conf.tx_width;
	var byte2: u16 = undefined;
	var byte3: u16 = undefined;
 	const tab: []const u8 = "{tab}";
	const crlf0: []const u8 = "[013+010]";
	const crlf: []const u8 = "[cr|lf]";
	const lbrc: []const u8 = "{091}";
	const rbrc: []const u8 = "{093}";
	const lguil: []const u8 = "{<}";
	const rguil: []const u8 = "{>}";
	const spce: []const u8 = "{032}";
	var ctr: u8 = 0;
	var n: u32 = 0;
	var use_file: bool = false;
	const file_in: []u8 = args[conf.next_arg];
	std.fs.cwd().access(file_in, .{}) catch |err| {
		switch (err) {
			error.FileNotFound => {
				std.debug.print("File not found: \"{s}\"\n", .{file_in});
				return;
			},
			else => {
				std.debug.print("Error: {any}\n", .{err});
				return;
			}
		}
	};
	var file_ex: []u8 = undefined;
	if (conf.next_arg + 1 == args.len - 1) {
		file_ex = args[conf.next_arg + 1];
		use_file = true;
	}
	const data: []u8 = try fileRead(file_in);
	const file = if(use_file) try std.fs.cwd().createFile(file_ex, .{})
		else undefined;
	defer if(use_file) file.close();
	var buffered = std.io.bufferedWriter(if(use_file) file.writer()
		else std.io.getStdOut().writer());
	const out = buffered.writer();
	try out.writeAll("XPLeNCODE v2.0 (xencode.exe)");
	try putNewline(out);
	try out.writeAll("b-gin [UNTITLED]");
	try putNewline(out);
	while (n < data.len) {
		if (omode == .printable_only) {
			if (data[n] > 32 and data[n] < 127) {
				try writeByteLnBk(out, data[n]);
			}
			else if (data[n] == 32) {
				if (col_ctr > 1 and col_ctr < max_col - 1) try writeByteLnBk(out, ' ')
					else try writeAllLnBk(out, spce);	
			}
			else {
				try writeByteLnBk(out, '.');
			}
			n += 1;
			continue;
		}
		ctr = 0;
		try switch (data[n]) {
			9 => {
				if(omode == .normal) try putCharNumBr(out, 9);
				if(omode == .xy_readability_aids) {
					try writeAllLnBk(out, tab);
				} else {
					try writeByteLnBk(out, '.');
				}
			},
			13 => {
				if (omode == .printable_only) {
					try writeByteLnBk(out, '.');
					n += 1;
					continue;
				}
				if (data[n + 1] == 10) {
					if (omode == .xy_readability_aids) {
						try writeAllLnBk(out, crlf);
					} else {
						try writeAllLnBk(out, crlf0);
					}
					n += 1;
				} else {
					try putCharNumBr(out, data[n]);
				}
			},
			32 => {
				if (col_ctr > 1 and col_ctr < max_col - 1) try writeByteLnBk(out, ' ')
					else try writeAllLnBk(out, spce);	
			},
			33...90, 92, 94...122, 124 => try writeByteLnBk(out, data[n]),
			91 => {
				if (omode == .xy_readability_aids) try writeAllLnBk(out, lbrc)
					else try writeByteLnBk(out, data[n]);
			},
			93 => {
				if (omode == .xy_readability_aids) try writeAllLnBk(out, rbrc)
					else try writeByteLnBk(out, data[n]);
			},
			123 => putCharNumBr(out, data[n]),
			125 => putCharNumBr(out, data[n]),
			174 => {
				if (omode == .xy_readability_aids) try writeAllLnBk(out, lguil)
					else try putCharNumBr(out, data[n]);
			},
			175 => {
				if (omode == .xy_readability_aids) try writeAllLnBk(out, rguil)
					else try putCharNumBr(out, data[n]);
			},
			254 => { // Speedo charset
				if (omode != .xy_readability_aids) {
					try putCharNumBr(out, data[n]);
					n += 1;
					continue;
				}
				if (data.len - n < 3) {
					try putCharNumBr(out, data[n]);
					n += 1;
					continue;
				}
				byte2  = data[n + 1];
				byte3  = data[n + 2];
				if (byte3 < 2 or (byte3 < 3 and byte2 < 232)) {
					try putXySpeedoChar(out, byte2, byte3);
					n += 3;
				} else {
 				// Generic 3-byter [254+nnn+nnn]
					try put3byterBr(out, 254, byte2, byte3);
					n += 3;
					continue;
				}
			},
			255 => {
				if (omode != .xy_readability_aids) {
					try putCharNumBr(out, data[n]);
					n += 1;
					continue;
				}
				if (data.len - n < 3) {
					try putCharNumBr(out, data[n]);
					n += 1;
					continue;
				}
				byte2  = data[n + 1];
				byte3  = data[n + 2];
				// 3-byte functions
				if (byte2 > 127 and byte2 < 131 and byte3 % 2 == 1) {
						try putXyFunc(out, byte2, byte3);
						n += 3;
						continue;
				}
				// Search wildcards
				else if ((byte2 == 192 and (byte3 == 145 or byte3 == 153 or
					byte3 == 155 or byte3 == 173 or byte3 == 174 or
					(byte3 >= 176 and byte3 <= 185) or byte3 == 193 or
					byte3 == 204 or byte3 == 206 or byte3 == 207 or
					byte3 == 211 or byte3 == 215 or byte3 == 216)) or
					(byte2 == 193 and (byte3 == 46 or byte3 == 47))) {
					try putXyWildcard(out, byte3);
					n += 3;
					continue;
				} else {
 				// Generic 3-byter [255+nnn+nnn]
					try put3byterBr(out, 255, byte2, byte3);
					n += 3;
					continue;
				}
			},
			else => try putCharNumBr(out, data[n]),
		};
		n += 1;
	}
	if (col_ctr > 1) try putNewline(out);
	try out.writeAll("-nd");
	try putNewline(out);
	try out.writeAll("XPLeNCODE");
	try putNewline(out);
	try buffered.flush();
}

pub fn showHelp() void {
	const help = 
\\Human-readable Plain-text Encoding for Binary Files
\\                              [CLD rev. 2025-07-14]
\\
\\Usage (optional arguments first):
\\xencode [-a|-x] [-w <number>] file_in [file_out] | [-?]
\\
\\If file_out is omitted, output is directed to stdout.
\\
\\Options:
\\  -a outputs Ascii 32-126 only (not decodable)
\\  -x applies XyWrite readability aids to output
\\  -w <number> changes text width of output to <number>
\\     (default = 65 characters per line)
\\  -? shows this help
\\
;
	std.debug.print("{s}\n", .{help});
}

pub fn fileRead(fname: []const u8) ![]u8 {
	const fileContents = try std.fs.cwd().readFileAlloc(
		ap_allocator,
		fname,
		std.math.maxInt(u32));
	return fileContents;
}

pub fn writeByteLnBk(wr: anytype, byte: u8) !void {
	try wr.writeByte(byte); // assumes wr has method writeByte()
	col_ctr += 1;
	if (col_ctr > max_col) {
		if (builtin.target.os.tag == .windows) try wr.writeByte(13);
		try wr.writeByte(10);
		col_ctr = 1;
	}
}

pub fn writeAllLnBk(wr: anytype, bytes: []const u8) !void {
	for (bytes) |b| try writeByteLnBk(wr, b);
}

pub fn putNewline(wr: anytype) !void {
	if (builtin.target.os.tag == .windows) try wr.writeByte(13);
	try wr.writeByte(10);
}

pub fn putCharNumBr(wr: anytype, char_in: u8) !void {
	var buffer: [5]u8 = undefined;
	const charnum: []u8 = try std.fmt.bufPrint(
		&buffer,
		"{{{d:0>3}}}",
		.{char_in});
	try writeAllLnBk(wr, charnum);
}

pub fn put3byterBr(wr: anytype, byte1: u16, byte2: u16, byte3: u16) !void {
	var buffer: [13]u8 = undefined;
	const brace3: []u8 = try std.fmt.bufPrint(
		&buffer,
		"[{d:0>3}+{d:0>3}+{d:0>3}]", 
		.{byte1, byte2, byte3});
	try writeAllLnBk(wr, brace3);
}

pub fn putXyFunc(wr: anytype, byte_2: u16, byte_3: u16) !void {
	const index: u32 = (256 * (byte_2 % 128) / 2) + (byte_3 / 2);
	var afunc: [5]u8 = undefined;
	const func_no: []const u8 = "[255+129+163]";
	const xyfuncs = [_]*const [2:0]u8{
"@0", "@1", "@2", "@3", "@4", "@5", "@6", "@7", "@8", "@9", "@A", "@B",
"@C", "@D", "@E", "@F", "@G", "@H", "@I", "@J", "@K", "@L", "@M", "@N",
"@O", "@P", "@Q", "@R", "@S", "@T", "@U", "@V", "@W", "@X", "@Y", "@Z",
"AD", "AS", "BF", "BK", "BS", "CC", "CD", "CH", "CI", "CL", "CM", "CN",
"CP", "CR", "CS", "CU", "DC", "DF", "GH", "DL", "DP", "DS", "DW", "EL",
"ER", "EX", "GT", "HM", "M0", "M1", "M2", "M3", "M4", "M5", "M6", "M7",
"M8", "MD", "MU", "MV", "NC", "NL", "NK", "NP", "NR", "NS", "NT", "NW",
"PC", "PD", "PL", "PP", "PR", "PS", "PT", "PU", "PW", "R0", "R1", "R2",
"R3", "R4", "R5", "R6", "R7", "R8", "R9", "RC", "RD", "RE", "RL", "RP",
"RS", "RV", "RW", "SD", "SH", "SI", "SK", "SM", "SN", "SS", "SU", "SV",
"TF", "TI", "TN", "TS", "UD", "WA", "WC", "WL", "WN", "WS", "WX", "WW",
"XC", "XD", "DT", "S1", "S2", "S3", "S4", "S5", "S6", "S7", "SP", "BC",
"LB", "LE", "NF", "PF", "TP", "BD", "MS", "NM", "LD", "LL", "LR", "LU",
"UP", "FF", "YD", "DO", "DX", "MK", "SO", "OP", "WZ", "NX", "SW", "FD",
"FM", "TL", "TR", "TE", "ED", "EE", "HC", "EC", "MC", "#1", "#2", "#3",
"#4", "#5", "#6", "#7", "#8", "#9", "$1", "$2", "$3", "$4", "$5", "$6",
"$7", "$8", "$9", "DR", "EN", "C0", "C1", "C2", "C3", "C4", "C5", "C6",
"C7", "C8", "C9", "EF", "IB", "NO", "NI", "CO", "$0", "LS", "XP", "WG",
"XM", "&0", "&1", "&2", "&3", "&4", "&5", "&6", "&7", "&8", "&9", "&A",
"&B", "&C", "&D", "&E", "&F", "&G", "&H", "&I", "&J", "&K", "&L", "&M",
"&N", "&O", "&P", "&Q", "&R", "&S", "&T", "&U", "&V", "&W", "&X", "&Y",
"&Z", "HL", "$A", "$B", "$C", "$D", "$E", "$F", "$G", "$H", "$I", "$J",
"$K", "$L", "$M", "$N", "$O", "$P", "$Q", "$R", "$S", "$T", "$U", "$V",
"$W", "$X", "$Y", "$Z", "XX", "H@", "VH", "MW", "QH", "DK", "SR", "SC",
"TG", "H1", "JH", "DZ", "DD", "DM", "LT", "RK", "NN", "MT", "ET", "ZT",
"T1", "TT", "<<", ">>", "IT", "SL", "SF", "FL", "FR", "FC", "SY", "ME",
"AC", "FS", "TW", "MI", "RO", "NB", "Q1", "Q2", "Q3", "Q4", "Q5", "Q6",
"Q7", "Q8", "TO", "IR", "AR", "AX", "DB", "DE", "HF", "SA", "OV", "TC",
"TB", "JM", "SG", "XH", "FT", "BX", "MN", "CB", "M9", "MZ", "ZZ", "RX",
"ST", "KF", "JC", "AK", "TM", "NU", "B4", "QP", "HG", "US", "XE", "ES",
"RB", "S-", "S+", "**", "BN", "RU", "CF", "UI", "XS", "EA", "BT", "KD",
"DN", "HI", "WH", "XN", "FX", "UN", "MX", "AZ", "BR", "HK", "#X", "??",
"BM", "JR", "XO", "XW", "TX", "LF", "LO", "BL", "XT", "WT", "IC", "CT",
"VB", "-D", "WD", "RM", "LM", "aL", "aR", "aB", "aE", "MP", "mN", "QL",
"QR", "MF"};
	if (index < xyfuncs.len) {
		afunc[0] = '[';
		afunc[1] = xyfuncs[index][0];
		afunc[2] = xyfuncs[index][1];
		if (afunc[1] == 'N' and afunc[2] == 'O') {
			try writeAllLnBk(wr, func_no); // workaround for flaky func NO
			return;
		}
		afunc[3] = '_';
		afunc[4] = ']';
		try writeAllLnBk(wr, afunc[0..]);
	}
}

pub fn putXyWildcard(wr: anytype, byte_3: u16) !void {
	var wild: [4]u8 = undefined;
	wild[0] = '[';
	wild[1] = 'w';
	var wild1: [5]u8 = undefined;
	wild1[0] = '[';
	wild1[1] = 'w';
	const wilddot: []const u8 = "[255+192+174]";
	var c: u3 = 2;
	switch (byte_3) {
		46 => {wild[c] = '<'; c += 1;}, 
		47 => {wild[c] = '>'; c += 1;},
		145 => {wild1[c] = '1'; wild1[c + 1] = '3'; c += 2;},
		153 => {wild1[c] = '1'; wild1[c + 1] = '0'; c += 2;},
		155 => {wild[c] = 'C'; c += 1;}, 
		173 => {wild[c] = '-'; c += 1;}, 
		174 => {wild[c] = '.'; c += 1;}, 
		176 => {wild[c] = '0'; c += 1;}, 
		177 => {wild[c] = '1'; c += 1;}, 
		178 => {wild[c] = '2'; c += 1;}, 
		179 => {wild[c] = '3'; c += 1;}, 
		180 => {wild[c] = '4'; c += 1;}, 
		181 => {wild[c] = '5'; c += 1;}, 
		182 => {wild[c] = '6'; c += 1;}, 
		183 => {wild[c] = '7'; c += 1;}, 
		184 => {wild[c] = '8'; c += 1;}, 
		185 => {wild[c] = '9'; c += 1;}, 
		193 => {wild[c] = 'A'; c += 1;}, 
		204 => {wild[c] = 'L'; c += 1;}, 
		206 => {wild[c] = 'N'; c += 1;}, 
		207 => {wild[c] = 'O'; c += 1;}, 
		211 => {wild[c] = 'S'; c += 1;}, 
		215 => {wild[c] = 'W'; c += 1;}, 
		216 => {wild[c] = 'X'; c += 1;},
		else => wild[0] = 0,
	}
	if (wild[2] == '.') {
		try writeAllLnBk(wr, wilddot);
		return;
	}
	if (c > 3) {
		wild1[c] = ']';
		try writeAllLnBk(wr, wild1[0..]);
	} else {
		wild[c] = ']';
		try writeAllLnBk(wr, wild[0..]);
	}
}

pub fn putXySpeedoChar(wr: anytype, byte_2: u16, byte_3: u16) !void {
	const index: u32 = (256 * (1 + byte_3)) + byte_2;
	var buffer: [5]u8 = undefined;
	const speedo: []u8 = try std.fmt.bufPrint(
		&buffer,
		"[{d}]", .{index});
	try writeAllLnBk(wr, speedo);
}

You aren’t necessarily ‘modifying’ the existing writer, you are creating a layer on top of it that is responsible for the line break logic. The data will first be passed into the max-column writer, then will be passed into the backing writer to be written (possibly interleaved with some line breaks):

max_columns = 5

INPUT:             "ABCDEFG" -> max_column_writer
max_column_writer: "ABCDE"   -> writer
writer:            "ABCDE"   -> OUTPUT
max_column_writer: "\n"      -> writer
writer:            "\n"      -> OUTPUT
max_column_writer: "FG"      -> writer
writer:            "FG"      -> OUTPUT

Here’s a more complete scaffold for the code:

pub fn maxColumnWriter(writer: anytype, max_columns: u8) MaxColumnWriter(@TypeOf(writer)) {
    return .init(writer, max_columns);
}

pub fn MaxColumnWriter(comptime Writer: type) type {
    const WriteReturnType = @typeInfo(@TypeOf(Writer.write)).@"fn".return_type.?;
    const WriteError = @typeInfo(WriteReturnType).error_union.error_set;

    return struct {
        backing_writer: Writer,
        max_columns: u8,
        // ... other fields

        pub fn init(backing_writer: Writer, max_columns: u8) Self {
            return .{
                .backing_writer = backing_writer,
                .max_columns = max_columns,
                // ... initialize other fields
            };
        }

        const Self = @This();

        pub fn writer(self: *Self) std.io.GenericWriter(*Self, WriteError, write) {
            return .{ .context = self };
        }

        /// Write any number of bytes from `bytes`, and return the number of bytes written.
        fn write(self: *Self, bytes: []const u8) WriteError!usize {
            // ...
        }
    };
}

The main thing you need to implement is the write function, which will use the backing writer to write bytes and any needed line breaks.

Note that you shouldn’t include the number of line breaks in the byte count that write returns, as the byte count is just meant to communicate how many bytes from bytes were written.

Good lord, @milogreg, this is pellucid. I’m getting ready to travel, so it may be some time before I can follow through – but I have a concrete direction now. It’s been close to 60 years since I was an undergraduate, but I can still recognize an exceptional teacher when I encounter one. :waving_hand:

I’m amazed at the level of support this forum is willing to provide to a beginner like me. As a token of my appreciation, I’ve set up a monthly recurring donation to the Zig Software Foundation. On to version 1.0 – and beyond! :grinning_face:

6 Likes

I still plan to do the maxColumnsWriter exercise, but in the meantime I’ve been tinkering with what I have. I came up with this (recursive) function writeOut() which seems to accomplish the same thing with less code – not that less code is always the best code.

pub fn writeOut(wr: anytype, bytes: []const u8) !void {
	const diff: u64 = max_col - col_ctr;
	const b_len: u64 = bytes.len;
	if (b_len <= diff) {
		try wr.writeAll(bytes);
		col_ctr += b_len;
	}
	else {
		try wr.writeAll(bytes[0..1 + diff]);
		try wr.writeAll(newline);
		col_ctr = 1;
		try writeOut(wr, bytes[1 + diff..]);
	}
}

Current state of play:

// xencode.zig: Human-readable plain-text encoding for binary files
// CLD rev. 2025-07-17_13:46

const std = @import("std");
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
const ap_allocator = arena.allocator();
var max_col: u64 = 65; // output text width
var col_ctr: u64 = 1; // column counter

const OutputMode = enum {
	show_help,
	printable_only,
	normal,
	xy_readability_aids,
};

const Config = struct {
	omode: OutputMode = .normal,
	tx_width: u8 = 65,
	next_arg: u8 = 1,

	pub fn initFromArgs(args: []const []const u8) !Config {
		var omode: OutputMode = .normal;
		var tx_width: u8 = 65;
		var next_arg: u8 = 1;
		while (next_arg < args.len) {
			if (
					std.mem.eql(u8, args[1], "-?") or
					std.mem.eql(u8, args[1], "/?") or
					std.mem.eql(u8, args[1], "-h") or
					std.mem.eql(u8, args[1], "--help")) {
				omode = .show_help;
				break;			
			}
			if (std.mem.eql(u8, args[next_arg], "-a") or
				std.mem.eql(u8, args[next_arg], "/a")) {
				omode = .printable_only;
				next_arg += 1;
				continue;
			}
			if (std.mem.eql(u8, args[next_arg], "-w") or
				std.mem.eql(u8, args[next_arg], "/w")) {
				if (next_arg + 1 <= args.len - 1) {
			    tx_width = try std.fmt.parseInt(u8, args[next_arg + 1], 10);
					next_arg += 2;
					continue;
				}
			}
			if (std.mem.eql(u8, args[next_arg], "-x") or
				std.mem.eql(u8, args[next_arg], "/x")) {
				omode = .xy_readability_aids;
				next_arg += 1;
				continue;
			}
			break;
		}
		if (next_arg > args.len - 1) omode = .show_help;
		return Config {
			.omode = omode,
			.tx_width = tx_width,
			.next_arg = next_arg,
		};
	}
};

const Newline = struct {
	right_type: []const u8,

	pub fn init() Newline {
		const nl = [2]u8{13, 10};
		return Newline{
			.right_type = 
				if (@import("builtin").target.os.tag == .windows)
					nl[0..] else nl[1..]
		};
	}
};
const newline_type: Newline = .init();
const newline: []const u8 = newline_type.right_type;

pub fn main() !void {
	defer arena.deinit();
	const args = try std.process.argsAlloc(ap_allocator);
	const conf: Config = try .initFromArgs(args);
	const omode = conf.omode;
	if (omode == .show_help) {
		showHelp();
		return;
	}
	max_col = conf.tx_width;
	var byte2: u16 = undefined;
	var byte3: u16 = undefined;
 	const tab: []const u8 = "{tab}";
	const crlf0: []const u8 = "[013+010]";
	const crlf: []const u8 = "[cr|lf]";
	const lbrc: []const u8 = "{091}";
	const rbrc: []const u8 = "{093}";
	const lguil: []const u8 = "{<}";
	const rguil: []const u8 = "{>}";
	const spce: []const u8 = "{032}";
	var ctr: u8 = 0;
	var n: u32 = 0;
	var use_file: bool = false;
	const file_in: []u8 = args[conf.next_arg];
	std.fs.cwd().access(file_in, .{}) catch |err| {
		switch (err) {
			error.FileNotFound => {
				std.debug.print("File not found: \"{s}\"\n", .{file_in});
				return;
			},
			else => {
				std.debug.print("Error: {any}\n", .{err});
				return;
			}
		}
	};
	var file_ex: []u8 = undefined;
	if (conf.next_arg + 1 == args.len - 1) {
		file_ex = args[conf.next_arg + 1];
		use_file = true;
	}
	const data: []u8 = try fileRead(file_in);
	const file = if(use_file) try std.fs.cwd().createFile(file_ex, .{})
		else undefined;
	defer if(use_file) file.close();
	var buffered = std.io.bufferedWriter(if(use_file) file.writer()
		else std.io.getStdOut().writer());
	const writr = buffered.writer();
	try writr.writeAll("XPLeNCODE v2.0 (xencode.exe)");
	try writr.writeAll(newline);
	try writr.writeAll("b-gin [UNTITLED]");
	try writr.writeAll(newline);
	while (n < data.len) {
		if (omode == .printable_only) {
			if (data[n] > 32 and data[n] < 127) {
				try writeOut(writr, data[n..n + 1]);
			}
			else if (data[n] == 32) {
				if (col_ctr > 1 and col_ctr < max_col - 1) try writeOut(writr, " ")
					else try writeOut(writr, spce);	
			}
			else {
				try writeOut(writr, ".");
			}
			n += 1;
			continue;
		}
		ctr = 0;
		try switch (data[n]) {
			9 => {
				if(omode == .normal) try putCharNumBr(writr, 9);
				if(omode == .xy_readability_aids) {
					try writeOut(writr, tab);
				} else {
					try writeOut(writr, ".");
				}
			},
			13 => {
				if (omode == .printable_only) {
					try writeOut(writr, ".");
					n += 1;
					continue;
				}
				if (data[n + 1] == 10) {
					if (omode == .xy_readability_aids) {
						try writeOut(writr, crlf);
					} else {
						try writeOut(writr, crlf0);
					}
					n += 1;
				} else {
					try putCharNumBr(writr, data[n]);
				}
			},
			32 => {
				if (col_ctr > 1 and col_ctr < max_col - 1) try writeOut(writr, " ")
					else try writeOut(writr, spce);	
			},
			33...90, 92, 94...122, 124 => try writeOut(writr, data[n..n + 1]),
			91 => {
				if (omode == .xy_readability_aids) try writeOut(writr, lbrc)
					else try writeOut(writr, data[n..n + 1]);
			},
			93 => {
				if (omode == .xy_readability_aids) try writeOut(writr, rbrc)
					else try writeOut(writr, data[n..n + 1]);
			},
			123 => putCharNumBr(writr, data[n]),
			125 => putCharNumBr(writr, data[n]),
			174 => {
				if (omode == .xy_readability_aids) try writeOut(writr, lguil)
					else try putCharNumBr(writr, data[n]);
			},
			175 => {
				if (omode == .xy_readability_aids) try writeOut(writr, rguil)
					else try putCharNumBr(writr, data[n]);
			},
			254 => { // Speedo charset
				if (omode != .xy_readability_aids) {
					try putCharNumBr(writr, data[n]);
					n += 1;
					continue;
				}
				if (data.len - n < 3) {
					try putCharNumBr(writr, data[n]);
					n += 1;
					continue;
				}
				byte2  = data[n + 1];
				byte3  = data[n + 2];
				if (byte3 < 2 or (byte3 < 3 and byte2 < 232)) {
					try putXySpeedoChar(writr, byte2, byte3);
					n += 3;
				} else {
 				// Generic 3-byter [254+nnn+nnn]
					try put3byterBr(writr, 254, byte2, byte3);
					n += 3;
					continue;
				}
			},
			255 => {
				if (omode != .xy_readability_aids) {
					try putCharNumBr(writr, data[n]);
					n += 1;
					continue;
				}
				if (data.len - n < 3) {
					try putCharNumBr(writr, data[n]);
					n += 1;
					continue;
				}
				byte2  = data[n + 1];
				byte3  = data[n + 2];
				// 3-byte functions
				if (byte2 > 127 and byte2 < 131 and byte3 % 2 == 1) {
						try putXyFunc(writr, byte2, byte3);
						n += 3;
						continue;
				}
				// Search wildcards
				else if ((byte2 == 192 and (byte3 == 145 or byte3 == 153 or
					byte3 == 155 or byte3 == 173 or byte3 == 174 or
					(byte3 >= 176 and byte3 <= 185) or byte3 == 193 or
					byte3 == 204 or byte3 == 206 or byte3 == 207 or
					byte3 == 211 or byte3 == 215 or byte3 == 216)) or
					(byte2 == 193 and (byte3 == 46 or byte3 == 47))) {
					try putXyWildcard(writr, byte3);
					n += 3;
					continue;
				} else {
 				// Generic 3-byter [255+nnn+nnn]
					try put3byterBr(writr, 255, byte2, byte3);
					n += 3;
					continue;
				}
			},
			else => try putCharNumBr(writr, data[n]),
		};
		n += 1;
	}
	if (col_ctr > 1) try writr.writeAll(newline);
	try writr.writeAll("-nd");
	try writr.writeAll(newline);
	try writr.writeAll("XPLeNCODE");
	try writr.writeAll(newline);
	try buffered.flush();
}

pub fn showHelp() void {
	const help = 
\\Human-readable Plain-text Encoding for Binary Files
\\                              [CLD rev. 2025-07-14]
\\
\\Usage (optional arguments first):
\\xencode [-a|-x] [-w <number>] file_in [file_out] | [-?]
\\
\\If file_out is omitted, output is directed to stdout.
\\
\\Options:
\\  -a outputs Ascii 32-126 only (not decodable)
\\  -x applies XyWrite readability aids to output
\\  -w <number> changes text width of output to <number>
\\     (default = 65 characters per line)
\\  -? shows this help
\\
;
	std.debug.print("{s}\n", .{help});
}

pub fn fileRead(fname: []const u8) ![]u8 {
	const fileContents = try std.fs.cwd().readFileAlloc(
		ap_allocator,
		fname,
		std.math.maxInt(u32));
	return fileContents;
}

pub fn writeOut(wr: anytype, bytes: []const u8) !void {
	const diff: u64 = max_col - col_ctr;
	const b_len: u64 = bytes.len;
	if (b_len <= diff) {
		try wr.writeAll(bytes);
		col_ctr += b_len;
	}
	else {
		try wr.writeAll(bytes[0..1 + diff]);
		try wr.writeAll(newline);
		col_ctr = 1;
		try writeOut(wr, bytes[1 + diff..]);
	}
}

pub fn putCharNumBr(wr: anytype, char_in: u8) !void {
	var buffer: [5]u8 = undefined;
	const charnum: []u8 = try std.fmt.bufPrint(
		&buffer,
		"{{{d:0>3}}}",
		.{char_in});
	try writeOut(wr, charnum);
}

pub fn put3byterBr(wr: anytype, byte1: u16, byte2: u16, byte3: u16) !void {
	var buffer: [13]u8 = undefined;
	const brace3: []u8 = try std.fmt.bufPrint(
		&buffer,
		"[{d:0>3}+{d:0>3}+{d:0>3}]", 
		.{byte1, byte2, byte3});
	try writeOut(wr, brace3);
}

pub fn putXyFunc(wr: anytype, byte_2: u16, byte_3: u16) !void {
	const index: u32 = (256 * (byte_2 % 128) / 2) + (byte_3 / 2);
	var afunc: [5]u8 = undefined;
	const func_no: []const u8 = "[255+129+163]";
	const xyfuncs = [_]*const [2:0]u8{
"@0", "@1", "@2", "@3", "@4", "@5", "@6", "@7", "@8", "@9", "@A", "@B",
"@C", "@D", "@E", "@F", "@G", "@H", "@I", "@J", "@K", "@L", "@M", "@N",
"@O", "@P", "@Q", "@R", "@S", "@T", "@U", "@V", "@W", "@X", "@Y", "@Z",
"AD", "AS", "BF", "BK", "BS", "CC", "CD", "CH", "CI", "CL", "CM", "CN",
"CP", "CR", "CS", "CU", "DC", "DF", "GH", "DL", "DP", "DS", "DW", "EL",
"ER", "EX", "GT", "HM", "M0", "M1", "M2", "M3", "M4", "M5", "M6", "M7",
"M8", "MD", "MU", "MV", "NC", "NL", "NK", "NP", "NR", "NS", "NT", "NW",
"PC", "PD", "PL", "PP", "PR", "PS", "PT", "PU", "PW", "R0", "R1", "R2",
"R3", "R4", "R5", "R6", "R7", "R8", "R9", "RC", "RD", "RE", "RL", "RP",
"RS", "RV", "RW", "SD", "SH", "SI", "SK", "SM", "SN", "SS", "SU", "SV",
"TF", "TI", "TN", "TS", "UD", "WA", "WC", "WL", "WN", "WS", "WX", "WW",
"XC", "XD", "DT", "S1", "S2", "S3", "S4", "S5", "S6", "S7", "SP", "BC",
"LB", "LE", "NF", "PF", "TP", "BD", "MS", "NM", "LD", "LL", "LR", "LU",
"UP", "FF", "YD", "DO", "DX", "MK", "SO", "OP", "WZ", "NX", "SW", "FD",
"FM", "TL", "TR", "TE", "ED", "EE", "HC", "EC", "MC", "#1", "#2", "#3",
"#4", "#5", "#6", "#7", "#8", "#9", "$1", "$2", "$3", "$4", "$5", "$6",
"$7", "$8", "$9", "DR", "EN", "C0", "C1", "C2", "C3", "C4", "C5", "C6",
"C7", "C8", "C9", "EF", "IB", "NO", "NI", "CO", "$0", "LS", "XP", "WG",
"XM", "&0", "&1", "&2", "&3", "&4", "&5", "&6", "&7", "&8", "&9", "&A",
"&B", "&C", "&D", "&E", "&F", "&G", "&H", "&I", "&J", "&K", "&L", "&M",
"&N", "&O", "&P", "&Q", "&R", "&S", "&T", "&U", "&V", "&W", "&X", "&Y",
"&Z", "HL", "$A", "$B", "$C", "$D", "$E", "$F", "$G", "$H", "$I", "$J",
"$K", "$L", "$M", "$N", "$O", "$P", "$Q", "$R", "$S", "$T", "$U", "$V",
"$W", "$X", "$Y", "$Z", "XX", "H@", "VH", "MW", "QH", "DK", "SR", "SC",
"TG", "H1", "JH", "DZ", "DD", "DM", "LT", "RK", "NN", "MT", "ET", "ZT",
"T1", "TT", "<<", ">>", "IT", "SL", "SF", "FL", "FR", "FC", "SY", "ME",
"AC", "FS", "TW", "MI", "RO", "NB", "Q1", "Q2", "Q3", "Q4", "Q5", "Q6",
"Q7", "Q8", "TO", "IR", "AR", "AX", "DB", "DE", "HF", "SA", "OV", "TC",
"TB", "JM", "SG", "XH", "FT", "BX", "MN", "CB", "M9", "MZ", "ZZ", "RX",
"ST", "KF", "JC", "AK", "TM", "NU", "B4", "QP", "HG", "US", "XE", "ES",
"RB", "S-", "S+", "**", "BN", "RU", "CF", "UI", "XS", "EA", "BT", "KD",
"DN", "HI", "WH", "XN", "FX", "UN", "MX", "AZ", "BR", "HK", "#X", "??",
"BM", "JR", "XO", "XW", "TX", "LF", "LO", "BL", "XT", "WT", "IC", "CT",
"VB", "-D", "WD", "RM", "LM", "aL", "aR", "aB", "aE", "MP", "mN", "QL",
"QR", "MF"};
	if (index < xyfuncs.len) {
		afunc[0] = '[';
		afunc[1] = xyfuncs[index][0];
		afunc[2] = xyfuncs[index][1];
		if (afunc[1] == 'N' and afunc[2] == 'O') {
			try writeOut(wr, func_no); // workaround for flaky func NO
			return;
		}
		afunc[3] = '_';
		afunc[4] = ']';
		try writeOut(wr, afunc[0..]);
	}
}

pub fn putXyWildcard(wr: anytype, byte_3: u16) !void {
	var wild: [4]u8 = undefined;
	wild[0] = '[';
	wild[1] = 'w';
	var wild1: [5]u8 = undefined;
	wild1[0] = '[';
	wild1[1] = 'w';
	const wilddot: []const u8 = "[255+192+174]";
	var c: u3 = 2;
	switch (byte_3) {
		46 => {wild[c] = '<'; c += 1;}, 
		47 => {wild[c] = '>'; c += 1;},
		145 => {wild1[c] = '1'; wild1[c + 1] = '3'; c += 2;},
		153 => {wild1[c] = '1'; wild1[c + 1] = '0'; c += 2;},
		155 => {wild[c] = 'C'; c += 1;}, 
		173 => {wild[c] = '-'; c += 1;}, 
		174 => {wild[c] = '.'; c += 1;}, 
		176 => {wild[c] = '0'; c += 1;}, 
		177 => {wild[c] = '1'; c += 1;}, 
		178 => {wild[c] = '2'; c += 1;}, 
		179 => {wild[c] = '3'; c += 1;}, 
		180 => {wild[c] = '4'; c += 1;}, 
		181 => {wild[c] = '5'; c += 1;}, 
		182 => {wild[c] = '6'; c += 1;}, 
		183 => {wild[c] = '7'; c += 1;}, 
		184 => {wild[c] = '8'; c += 1;}, 
		185 => {wild[c] = '9'; c += 1;}, 
		193 => {wild[c] = 'A'; c += 1;}, 
		204 => {wild[c] = 'L'; c += 1;}, 
		206 => {wild[c] = 'N'; c += 1;}, 
		207 => {wild[c] = 'O'; c += 1;}, 
		211 => {wild[c] = 'S'; c += 1;}, 
		215 => {wild[c] = 'W'; c += 1;}, 
		216 => {wild[c] = 'X'; c += 1;},
		else => wild[0] = 0,
	}
	if (wild[2] == '.') {
		try writeOut(wr, wilddot);
		return;
	}
	if (c > 3) {
		wild1[c] = ']';
		try writeOut(wr, wild1[0..]);
	} else {
		wild[c] = ']';
		try writeOut(wr, wild[0..]);
	}
}

pub fn putXySpeedoChar(wr: anytype, byte_2: u16, byte_3: u16) !void {
	const index: u32 = (256 * (1 + byte_3)) + byte_2;
	var buffer: [5]u8 = undefined;
	const speedo: []u8 = try std.fmt.bufPrint(
		&buffer,
		"[{d}]", .{index});
	try writeOut(wr, speedo);
}

1 Like

Nice, that’s a pretty elegant way to do the newline insertion logic. With some slight modifications, writeOut would serve perfectly as MaxColumnsWriter’s write method.

Having the platform-specific newline as a global constant is also a smart idea. I’ll admit, I typically don’t even handle this \r\n vs \n case myself, even though I use Windows… so many things just work with \n!

Though, having a whole wrapper type + init is a little overkill. It would be more standard to just do one of the following:

const newline: []const u8 = if (@import("builtin").target.os.tag == .windows)
    "\r\n"
else
    "\n";
const newline: []const u8 = switch (@import("builtin").target.os.tag) {
    .windows => "\r\n",
    else => "\n",
};

I usually lean towards the latter option, but it’s a matter of preference. My rule of thumb is to use switch(tag) if there is at least one regular branch and a non-empty else branch, otherwise use if (tag == .my_tag).

Yes, this is why I was tinkering first: I wasn’t ready to write write.

I figured as much. Thanks for the options. I like the switch one.

Updated code. Used newline option #2 and trapped the ridiculously unlikely event that user sets max_cols to 0. (Didn’t bother with an error msg, just reset it to 65.)

// xencode.zig: Human-readable plain-text encoding for binary files
// CLD rev. 2025-07-17_15:15

const std = @import("std");
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
const ap_allocator = arena.allocator();
var max_col: u64 = 65; // output text width
var col_ctr: u64 = 1; // column counter

const OutputMode = enum {
	show_help,
	printable_only,
	normal,
	xy_readability_aids,
};

const Config = struct {
	omode: OutputMode = .normal,
	tx_width: u8 = 65,
	next_arg: u8 = 1,

	pub fn initFromArgs(args: []const []const u8) !Config {
		var omode: OutputMode = .normal;
		var tx_width: u8 = 65;
		var next_arg: u8 = 1;
		while (next_arg < args.len) {
			if (
					std.mem.eql(u8, args[1], "-?") or
					std.mem.eql(u8, args[1], "/?") or
					std.mem.eql(u8, args[1], "-h") or
					std.mem.eql(u8, args[1], "--help")) {
				omode = .show_help;
				break;
			}
			if (std.mem.eql(u8, args[next_arg], "-a") or
				std.mem.eql(u8, args[next_arg], "/a")) {
				omode = .printable_only;
				next_arg += 1;
				continue;
			}
			if (std.mem.eql(u8, args[next_arg], "-w") or
				std.mem.eql(u8, args[next_arg], "/w")) {
				if (next_arg + 1 <= args.len - 1) {
			    tx_width = try std.fmt.parseInt(u8, args[next_arg + 1], 10);
					if (tx_width < 1) tx_width = 65;
					next_arg += 2;
					continue;
				}
			}
			if (std.mem.eql(u8, args[next_arg], "-x") or
				std.mem.eql(u8, args[next_arg], "/x")) {
				omode = .xy_readability_aids;
				next_arg += 1;
				continue;
			}
			break;
		}
		if (next_arg > args.len - 1) omode = .show_help;
		return Config {
			.omode = omode,
			.tx_width = tx_width,
			.next_arg = next_arg,
		};
	}
};

const newline: []const u8 = switch (@import("builtin").target.os.tag) {
    .windows => "\r\n",
    else => "\n",
};

pub fn main() !void {
	defer arena.deinit();
	const args = try std.process.argsAlloc(ap_allocator);
	const conf: Config = try .initFromArgs(args);
	const omode = conf.omode;
	if (omode == .show_help) {
		showHelp();
		return;
	}
	max_col = conf.tx_width;
	var byte2: u16 = undefined;
	var byte3: u16 = undefined;
 	const tab: []const u8 = "{tab}";
	const crlf0: []const u8 = "[013+010]";
	const crlf: []const u8 = "[cr|lf]";
	const lbrc: []const u8 = "{091}";
	const rbrc: []const u8 = "{093}";
	const lguil: []const u8 = "{<}";
	const rguil: []const u8 = "{>}";
	const spce: []const u8 = "{032}";
	var ctr: u8 = 0;
	var n: u32 = 0;
	var use_file: bool = false;
	const file_in: []u8 = args[conf.next_arg];
	std.fs.cwd().access(file_in, .{}) catch |err| {
		switch (err) {
			error.FileNotFound => {
				std.debug.print("File not found: \"{s}\"\n", .{file_in});
				return;
			},
			else => {
				std.debug.print("Error: {any}\n", .{err});
				return;
			}
		}
	};
	var file_ex: []u8 = undefined;
	if (conf.next_arg + 1 == args.len - 1) {
		file_ex = args[conf.next_arg + 1];
		use_file = true;
	}
	const data: []u8 = try fileRead(file_in);
	const file = if(use_file) try std.fs.cwd().createFile(file_ex, .{})
		else undefined;
	defer if(use_file) file.close();
	var buffered = std.io.bufferedWriter(if(use_file) file.writer()
		else std.io.getStdOut().writer());
	const writr = buffered.writer();
	try writr.writeAll("XPLeNCODE v2.0 (xencode.exe)");
	try writr.writeAll(newline);
	try writr.writeAll("b-gin [UNTITLED]");
	try writr.writeAll(newline);
	while (n < data.len) {
		if (omode == .printable_only) {
			if (data[n] > 32 and data[n] < 127) {
				try writeOut(writr, data[n..n + 1]);
			}
			else if (data[n] == 32) {
				if (col_ctr > 1 and col_ctr < max_col - 1) try writeOut(writr, " ")
					else try writeOut(writr, spce);
			}
			else {
				try writeOut(writr, ".");
			}
			n += 1;
			continue;
		}
		ctr = 0;
		try switch (data[n]) {
			9 => {
				if(omode == .normal) try putCharNumBr(writr, 9);
				if(omode == .xy_readability_aids) {
					try writeOut(writr, tab);
				} else {
					try writeOut(writr, ".");
				}
			},
			13 => {
				if (omode == .printable_only) {
					try writeOut(writr, ".");
					n += 1;
					continue;
				}
				if (data[n + 1] == 10) {
					if (omode == .xy_readability_aids) {
						try writeOut(writr, crlf);
					} else {
						try writeOut(writr, crlf0);
					}
					n += 1;
				} else {
					try putCharNumBr(writr, data[n]);
				}
			},
			32 => {
				if (col_ctr > 1 and col_ctr < max_col - 1) try writeOut(writr, " ")
					else try writeOut(writr, spce);
			},
			33...90, 92, 94...122, 124 => try writeOut(writr, data[n..n + 1]),
			91 => {
				if (omode == .xy_readability_aids) try writeOut(writr, lbrc)
					else try writeOut(writr, data[n..n + 1]);
			},
			93 => {
				if (omode == .xy_readability_aids) try writeOut(writr, rbrc)
					else try writeOut(writr, data[n..n + 1]);
			},
			123 => putCharNumBr(writr, data[n]),
			125 => putCharNumBr(writr, data[n]),
			174 => {
				if (omode == .xy_readability_aids) try writeOut(writr, lguil)
					else try putCharNumBr(writr, data[n]);
			},
			175 => {
				if (omode == .xy_readability_aids) try writeOut(writr, rguil)
					else try putCharNumBr(writr, data[n]);
			},
			254 => { // Speedo charset
				if (omode != .xy_readability_aids) {
					try putCharNumBr(writr, data[n]);
					n += 1;
					continue;
				}
				if (data.len - n < 3) {
					try putCharNumBr(writr, data[n]);
					n += 1;
					continue;
				}
				byte2  = data[n + 1];
				byte3  = data[n + 2];
				if (byte3 < 2 or (byte3 < 3 and byte2 < 232)) {
					try putXySpeedoChar(writr, byte2, byte3);
					n += 3;
				} else {
 				// Generic 3-byter [254+nnn+nnn]
					try put3byterBr(writr, 254, byte2, byte3);
					n += 3;
					continue;
				}
			},
			255 => {
				if (omode != .xy_readability_aids) {
					try putCharNumBr(writr, data[n]);
					n += 1;
					continue;
				}
				if (data.len - n < 3) {
					try putCharNumBr(writr, data[n]);
					n += 1;
					continue;
				}
				byte2  = data[n + 1];
				byte3  = data[n + 2];
				// 3-byte functions
				if (byte2 > 127 and byte2 < 131 and byte3 % 2 == 1) {
						try putXyFunc(writr, byte2, byte3);
						n += 3;
						continue;
				}
				// Search wildcards
				else if ((byte2 == 192 and (byte3 == 145 or byte3 == 153 or
					byte3 == 155 or byte3 == 173 or byte3 == 174 or
					(byte3 >= 176 and byte3 <= 185) or byte3 == 193 or
					byte3 == 204 or byte3 == 206 or byte3 == 207 or
					byte3 == 211 or byte3 == 215 or byte3 == 216)) or
					(byte2 == 193 and (byte3 == 46 or byte3 == 47))) {
					try putXyWildcard(writr, byte3);
					n += 3;
					continue;
				} else {
 				// Generic 3-byter [255+nnn+nnn]
					try put3byterBr(writr, 255, byte2, byte3);
					n += 3;
					continue;
				}
			},
			else => try putCharNumBr(writr, data[n]),
		};
		n += 1;
	}
	if (col_ctr > 1) try writr.writeAll(newline);
	try writr.writeAll("-nd");
	try writr.writeAll(newline);
	try writr.writeAll("XPLeNCODE");
	try writr.writeAll(newline);
	try buffered.flush();
}

pub fn showHelp() void {
	const help =
\\Human-readable Plain-text Encoding for Binary Files
\\                              [CLD rev. 2025-07-14]
\\
\\Usage (optional arguments first):
\\xencode [-a|-x] [-w <number>] file_in [file_out] | [-?]
\\
\\If file_out is omitted, output is directed to stdout.
\\
\\Options:
\\  -a outputs Ascii 32-126 only (not decodable)
\\  -x applies XyWrite readability aids to output
\\  -w <number> changes text width of output to <number>
\\     (default = 65 characters per line)
\\  -? shows this help
\\
;
	std.debug.print("{s}\n", .{help});
}

pub fn fileRead(fname: []const u8) ![]u8 {
	const fileContents = try std.fs.cwd().readFileAlloc(
		ap_allocator,
		fname,
		std.math.maxInt(u32));
	return fileContents;
}

pub fn writeOut(wr: anytype, bytes: []const u8) !void {
	const diff: u64 = max_col - col_ctr;
	const b_len: u64 = bytes.len;
	if (b_len <= diff) {
		try wr.writeAll(bytes);
		col_ctr += b_len;
	}
	else {
		try wr.writeAll(bytes[0..1 + diff]);
		try wr.writeAll(newline);
		col_ctr = 1;
		try writeOut(wr, bytes[1 + diff..]);
	}
}

pub fn putCharNumBr(wr: anytype, char_in: u8) !void {
	var buffer: [5]u8 = undefined;
	const charnum: []u8 = try std.fmt.bufPrint(
		&buffer,
		"{{{d:0>3}}}",
		.{char_in});
	try writeOut(wr, charnum);
}

pub fn put3byterBr(wr: anytype, byte1: u16, byte2: u16, byte3: u16) !void {
	var buffer: [13]u8 = undefined;
	const brace3: []u8 = try std.fmt.bufPrint(
		&buffer,
		"[{d:0>3}+{d:0>3}+{d:0>3}]",
		.{byte1, byte2, byte3});
	try writeOut(wr, brace3);
}

pub fn putXyFunc(wr: anytype, byte_2: u16, byte_3: u16) !void {
	const index: u32 = (256 * (byte_2 % 128) / 2) + (byte_3 / 2);
	var afunc: [5]u8 = undefined;
	const func_no: []const u8 = "[255+129+163]";
	const xyfuncs = [_]*const [2:0]u8{
"@0", "@1", "@2", "@3", "@4", "@5", "@6", "@7", "@8", "@9", "@A", "@B",
"@C", "@D", "@E", "@F", "@G", "@H", "@I", "@J", "@K", "@L", "@M", "@N",
"@O", "@P", "@Q", "@R", "@S", "@T", "@U", "@V", "@W", "@X", "@Y", "@Z",
"AD", "AS", "BF", "BK", "BS", "CC", "CD", "CH", "CI", "CL", "CM", "CN",
"CP", "CR", "CS", "CU", "DC", "DF", "GH", "DL", "DP", "DS", "DW", "EL",
"ER", "EX", "GT", "HM", "M0", "M1", "M2", "M3", "M4", "M5", "M6", "M7",
"M8", "MD", "MU", "MV", "NC", "NL", "NK", "NP", "NR", "NS", "NT", "NW",
"PC", "PD", "PL", "PP", "PR", "PS", "PT", "PU", "PW", "R0", "R1", "R2",
"R3", "R4", "R5", "R6", "R7", "R8", "R9", "RC", "RD", "RE", "RL", "RP",
"RS", "RV", "RW", "SD", "SH", "SI", "SK", "SM", "SN", "SS", "SU", "SV",
"TF", "TI", "TN", "TS", "UD", "WA", "WC", "WL", "WN", "WS", "WX", "WW",
"XC", "XD", "DT", "S1", "S2", "S3", "S4", "S5", "S6", "S7", "SP", "BC",
"LB", "LE", "NF", "PF", "TP", "BD", "MS", "NM", "LD", "LL", "LR", "LU",
"UP", "FF", "YD", "DO", "DX", "MK", "SO", "OP", "WZ", "NX", "SW", "FD",
"FM", "TL", "TR", "TE", "ED", "EE", "HC", "EC", "MC", "#1", "#2", "#3",
"#4", "#5", "#6", "#7", "#8", "#9", "$1", "$2", "$3", "$4", "$5", "$6",
"$7", "$8", "$9", "DR", "EN", "C0", "C1", "C2", "C3", "C4", "C5", "C6",
"C7", "C8", "C9", "EF", "IB", "NO", "NI", "CO", "$0", "LS", "XP", "WG",
"XM", "&0", "&1", "&2", "&3", "&4", "&5", "&6", "&7", "&8", "&9", "&A",
"&B", "&C", "&D", "&E", "&F", "&G", "&H", "&I", "&J", "&K", "&L", "&M",
"&N", "&O", "&P", "&Q", "&R", "&S", "&T", "&U", "&V", "&W", "&X", "&Y",
"&Z", "HL", "$A", "$B", "$C", "$D", "$E", "$F", "$G", "$H", "$I", "$J",
"$K", "$L", "$M", "$N", "$O", "$P", "$Q", "$R", "$S", "$T", "$U", "$V",
"$W", "$X", "$Y", "$Z", "XX", "H@", "VH", "MW", "QH", "DK", "SR", "SC",
"TG", "H1", "JH", "DZ", "DD", "DM", "LT", "RK", "NN", "MT", "ET", "ZT",
"T1", "TT", "<<", ">>", "IT", "SL", "SF", "FL", "FR", "FC", "SY", "ME",
"AC", "FS", "TW", "MI", "RO", "NB", "Q1", "Q2", "Q3", "Q4", "Q5", "Q6",
"Q7", "Q8", "TO", "IR", "AR", "AX", "DB", "DE", "HF", "SA", "OV", "TC",
"TB", "JM", "SG", "XH", "FT", "BX", "MN", "CB", "M9", "MZ", "ZZ", "RX",
"ST", "KF", "JC", "AK", "TM", "NU", "B4", "QP", "HG", "US", "XE", "ES",
"RB", "S-", "S+", "**", "BN", "RU", "CF", "UI", "XS", "EA", "BT", "KD",
"DN", "HI", "WH", "XN", "FX", "UN", "MX", "AZ", "BR", "HK", "#X", "??",
"BM", "JR", "XO", "XW", "TX", "LF", "LO", "BL", "XT", "WT", "IC", "CT",
"VB", "-D", "WD", "RM", "LM", "aL", "aR", "aB", "aE", "MP", "mN", "QL",
"QR", "MF"};
	if (index < xyfuncs.len) {
		afunc[0] = '[';
		afunc[1] = xyfuncs[index][0];
		afunc[2] = xyfuncs[index][1];
		if (afunc[1] == 'N' and afunc[2] == 'O') {
			try writeOut(wr, func_no); // workaround for flaky func NO
			return;
		}
		afunc[3] = '_';
		afunc[4] = ']';
		try writeOut(wr, afunc[0..]);
	}
}

pub fn putXyWildcard(wr: anytype, byte_3: u16) !void {
	var wild: [4]u8 = undefined;
	wild[0] = '[';
	wild[1] = 'w';
	var wild1: [5]u8 = undefined;
	wild1[0] = '[';
	wild1[1] = 'w';
	const wilddot: []const u8 = "[255+192+174]";
	var c: u3 = 2;
	switch (byte_3) {
		46 => {wild[c] = '<'; c += 1;},
		47 => {wild[c] = '>'; c += 1;},
		145 => {wild1[c] = '1'; wild1[c + 1] = '3'; c += 2;},
		153 => {wild1[c] = '1'; wild1[c + 1] = '0'; c += 2;},
		155 => {wild[c] = 'C'; c += 1;},
		173 => {wild[c] = '-'; c += 1;},
		174 => {wild[c] = '.'; c += 1;},
		176 => {wild[c] = '0'; c += 1;},
		177 => {wild[c] = '1'; c += 1;},
		178 => {wild[c] = '2'; c += 1;},
		179 => {wild[c] = '3'; c += 1;},
		180 => {wild[c] = '4'; c += 1;},
		181 => {wild[c] = '5'; c += 1;},
		182 => {wild[c] = '6'; c += 1;},
		183 => {wild[c] = '7'; c += 1;},
		184 => {wild[c] = '8'; c += 1;},
		185 => {wild[c] = '9'; c += 1;},
		193 => {wild[c] = 'A'; c += 1;},
		204 => {wild[c] = 'L'; c += 1;},
		206 => {wild[c] = 'N'; c += 1;},
		207 => {wild[c] = 'O'; c += 1;},
		211 => {wild[c] = 'S'; c += 1;},
		215 => {wild[c] = 'W'; c += 1;},
		216 => {wild[c] = 'X'; c += 1;},
		else => wild[0] = 0,
	}
	if (wild[2] == '.') {
		try writeOut(wr, wilddot);
		return;
	}
	if (c > 3) {
		wild1[c] = ']';
		try writeOut(wr, wild1[0..]);
	} else {
		wild[c] = ']';
		try writeOut(wr, wild[0..]);
	}
}

pub fn putXySpeedoChar(wr: anytype, byte_2: u16, byte_3: u16) !void {
	const index: u32 = (256 * (1 + byte_3)) + byte_2;
	var buffer: [5]u8 = undefined;
	const speedo: []u8 = try std.fmt.bufPrint(
		&buffer,
		"[{d}]", .{index});
	try writeOut(wr, speedo);
}

I’m back after a break. I’ve simplified the subject code for learning purposes. Now it just writes random printable characters to a file or stdout, formatted according to variable MaxCols value (generated by the program). The maxColumnWrite function is similar to what I had earlier. I’m afraid I’m stumped as to how to integrate the MaxColumnWriter “scaffolding” into the program. I think the problem is that I don’t really understand what the various parts of the code are doing. Anyway, here’s the simplified program:

// Write random printable chars, formatted using
//   a (variable) maximum column width
// CLD rev. 2025-08-26
//
// Usage:
// writeChars [file_out]
// If file_out is omitted, output is written to stdout.
// ----------------------------------------------------
const std = @import("std");
var column_ctr: u64 = 0;
var bytes_written: u64 = 0;

pub fn maxColumnWrite(wr: anytype, bytes: []const u8, width: u8) !void {
	const diff: u64 = width - column_ctr;
	if (bytes.len > diff) {
		try wr.writeAll(bytes[0..diff]);
		bytes_written += diff;
		try wr.writeAll("\n");
		column_ctr = 0;
		try maxColumnWrite(wr, bytes[diff..], width);
	}
	else {
		try wr.writeAll(bytes);
		column_ctr += bytes.len;
		bytes_written += bytes.len;
	}
}

pub fn main() !void {
	var max_col_width: u8 = undefined; // generated randomly (see below)
	var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
	const ap_allocator = arena.allocator();
	defer arena.deinit();
	const args = try std.process.argsAlloc(ap_allocator);
	var use_file: bool = false;
	var file_ex: []u8 = undefined;
	if (args.len > 1) {
		file_ex = args[1];
		use_file = true;
	}
	const file = if(use_file) try std.fs.cwd().createFile(file_ex, .{})
		else undefined;
	defer if(use_file) file.close();
	var buffered = std.io.bufferedWriter(if(use_file) file.writer()
		else std.io.getStdOut().writer());
	const writr = buffered.writer();
	// _ = writr;

	// Generate a slice of random printable chars of variable length
	var seed: u64 = undefined;
	try std.posix.getrandom(std.mem.asBytes(&seed));
	var prng = std.Random.DefaultPrng.init(seed);
	const rand = prng.random();
	const m: u9 = rand.intRangeAtMost(u9, 200, 500);
	var slice: [500]u8 = undefined;
	for (0..m) |n| {
		slice[n] = rand.intRangeAtMost(u8, 35, 126);
		if (slice[n] == '\\') slice[n] = '_'; 
	}

	// Data and max column width
	const data = slice[0..m];
	max_col_width = rand.intRangeAtMost(u8, 45, 85);
	
	// Write report to file or stdout
	const report1 = try std.fmt.allocPrint(
		ap_allocator,
		"\nRaw data:\n{s}\nLength_in_bytes = {d}\n\nFormatted data (max_col_width = {d}):\n",
		.{data, data.len, max_col_width});
	defer ap_allocator.free(report1);
	try writr.writeAll(report1);
	try maxColumnWrite(writr, data, max_col_width);
	const report2 = try std.fmt.allocPrint(
		ap_allocator,
		"\nBytes written (ex-newlines) = {d}\n",
		.{bytes_written});
	defer ap_allocator.free(report2);
	try writr.writeAll(report2);
	try buffered.flush();
}

1 Like