For a text file parser it would be nice to have a std.Io.Reader that keeps track of the line and the column number. It could then be used for diagnostics.
As a template I used std.Io.Reader.Hashed from the standard library. And I think I have found a solution that works. But I am not 100% confident, and I would appreciate if someone with a good knowledge of the new Io.Reader interface has time to have a look at the code.
I found it a bit tricky (especially for the column) because the vtable.stream function is sometimes called to fill the reader buffer, and sometimes to write to the destination. I may well have missed a case.
This is the code (copied from here):
/// Track position, line number and column.
const TextPosition = struct {
in: *Reader,
reader: Reader,
pos: usize = 0,
nl_pos: usize = 0,
nl_prebuf: usize = 0,
nl_count: usize = 0,
pub fn init(in: *Reader, buffer: []u8) TextPosition {
return .{
.in = in,
.reader = .{
.vtable = &.{ .stream = stream },
.buffer = buffer,
.seek = 0,
.end = 0,
},
};
}
fn stream(r: *Reader, w: *Writer, limit: Limit) Reader.StreamError!usize {
const t: *TextPosition = @alignCast(@fieldParentPtr("reader", r));
const data = limit.slice(try w.writableSliceGreedy(1));
var vec: [1][]u8 = .{data};
const n = try t.in.readVec(&vec);
if (data.ptr == r.buffer.ptr) t.nl_prebuf = t.nl_pos;
for (data[0..n]) |c| {
t.pos += 1;
if (c == '\n') {
t.nl_count += 1;
t.nl_pos = t.pos;
}
}
w.advance(n);
return n;
}
fn position(t: *const TextPosition) usize {
return t.pos - t.reader.bufferedLen();
}
/// 0-based
fn line(t: *const TextPosition) usize {
const r = &t.reader;
return t.nl_count - std.mem.count(u8, r.buffer[r.seek..r.end], "\n");
}
/// 0-based
fn column(t: *const TextPosition) usize {
const r = &t.reader;
if (r.bufferedLen() == 0)
return t.pos - t.nl_pos;
if (std.mem.lastIndexOfScalar(u8, r.buffer[0..r.seek], '\n')) |i| {
return r.seek - i - 1;
} else {
return t.pos - t.nl_prebuf - r.bufferedLen();
}
}
};