diff options
Diffstat (limited to 'src/board_memory.zig')
| -rw-r--r-- | src/board_memory.zig | 69 |
1 files changed, 51 insertions, 18 deletions
diff --git a/src/board_memory.zig b/src/board_memory.zig index 853ae5da..f99fc106 100644 --- a/src/board_memory.zig +++ b/src/board_memory.zig @@ -85,20 +85,41 @@ pub const Error = error{ ExtraArgument, }; -/// hex (`0x4ff40000`), decimal (`1341718528`), and — for free, from base 0 — -/// binary and octal. A bare `4ff40000` is deliberately NOT hex: it is a -/// legal-looking decimal number, so guessing the base would make one typo -/// silently address somewhere else entirely. +/// EVERY literal these three words take is HEX, with or without an `0x`, and there is no way to +/// write a decimal one. +/// +/// This replaces base-0 parsing, which accepted `0x4ff40000` and `1341390848` and refused a bare +/// `4ff40000` on the grounds that guessing between hex and decimal would make one typo address +/// somewhere else entirely. That reasoning was sound and the conclusion was still wrong: the +/// ambiguity it protected against is not a real one. Every address anybody has ever typed at these +/// three words is hex - it came off a datasheet, a linker map, or a previous dump's own output, all +/// of which print hex - so the base was never in doubt, and demanding `0x` on every one of them was +/// a toll on the common case to guard a case that does not arise. +/// +/// The COUNTS go with them, and that is the part worth stating out loud rather than leaving as a +/// surprise: `Hexdump 4ff40000 100` shows 0x100 bytes, which is 256, not one hundred. One rule for +/// every literal in the word is worth more than two rules that each fit their argument better, +/// because the second kind is the sort of thing you have to remember at the moment you are already +/// concentrating on something else. Everything these words PRINT is hex too, including the clamp +/// notes, so a number can go back in where it came out. +fn parseHex(comptime T: type, tok: []const u8, bad: Error) Error!T { + // `parseInt` only honours an `0x` when its base is 0, so with base 16 the prefix has to come off + // here. A bare `0x` leaves nothing behind and `parseInt` rejects the empty string, which is the + // answer that wants giving. + const body = if (tok.len > 2 and tok[0] == '0' and (tok[1] | 0x20) == 'x') tok[2..] else tok; + return std.fmt.parseInt(T, body, 16) catch bad; +} + fn parseAddr(tok: []const u8) Error!u32 { - return std.fmt.parseInt(u32, tok, 0) catch return Error.BadAddress; + return parseHex(u32, tok, Error.BadAddress); } fn parseCount(tok: []const u8) Error!u64 { - return std.fmt.parseInt(u64, tok, 0) catch return Error.BadCount; + return parseHex(u64, tok, Error.BadCount); } fn parseValue(tok: []const u8) Error!u32 { - return std.fmt.parseInt(u32, tok, 0) catch return Error.BadValue; + return parseHex(u32, tok, Error.BadValue); } /// A 32-bit peripheral or RAM read that the compiler may neither elide, @@ -152,12 +173,14 @@ fn extent(addr: u32, requested: u64, unit: u32, cap: u32) Extent { fn writeNote(w: *std.Io.Writer, e: Extent, requested: u64, unit_name: []const u8) !void { switch (e.limit orelse return) { + // Hex, like everything else these words read and print, so the number in a clamp note can go + // straight back into the command that produced it. .console => try w.print( - "clamped: {d} {s} requested, {d} shown ({d}-byte cap, one 115200-baud console)\n", + "clamped: 0x{x} {s} requested, 0x{x} shown (0x{x}-byte cap, one 115200-baud console)\n", .{ requested, unit_name, e.count, max_bytes }, ), .space => try w.print( - "clamped: {d} {s} requested, {d} shown (the 32-bit address space ends at 0x100000000)\n", + "clamped: 0x{x} {s} requested, 0x{x} shown (the 32-bit address space ends at 0x100000000)\n", .{ requested, unit_name, e.count }, ), } @@ -192,24 +215,34 @@ test "a clamp note is written exactly when something was clamped" { try writeNote(&w, extent(0x4ff40000, 99_999, 4, max_words), 99_999, "words"); try std.testing.expectEqualStrings( - "clamped: 99999 words requested, 1024 shown (4096-byte cap, one 115200-baud console)\n", + "clamped: 0x1869f words requested, 0x400 shown (0x1000-byte cap, one 115200-baud console)\n", w.buffered(), ); w = .fixed(&buf); try writeNote(&w, extent(0xfffffff0, 64, 1, max_bytes), 64, "bytes"); try std.testing.expectEqualStrings( - "clamped: 64 bytes requested, 16 shown (the 32-bit address space ends at 0x100000000)\n", + "clamped: 0x40 bytes requested, 0x10 shown (the 32-bit address space ends at 0x100000000)\n", w.buffered(), ); } -test "an address is hex or decimal, and a bare hex-looking token is decimal" { - try std.testing.expectEqual(0x4ff40000, parseAddr("0x4ff40000")); - try std.testing.expectEqual(0x4ff40000, parseAddr("1341390848")); - // a bare hex-looking token is a decimal number, never a guess - try std.testing.expectError(Error.BadAddress, parseAddr("4ff40000")); +test "every literal is hex, with or without the prefix" { + const eq = std.testing.expectEqual; + // the prefix is optional, never required, and never changes the answer + try eq(0x4ff40000, parseAddr("0x4ff40000")); + try eq(0x4ff40000, parseAddr("4ff40000")); + try eq(0x4ff40000, parseAddr("0X4FF40000")); + try eq(0x4ff40000, parseAddr("4FF40000")); + // a token that looks decimal is hex too - the whole point, and the thing to remember + try eq(0x100, parseCount("100")); + try eq(0x256, parseCount("256")); + try eq(0xdeadbeef, parseValue("deadbeef")); + // and the refusals still refuse try std.testing.expectError(Error.BadAddress, parseAddr("0x100000000")); + try std.testing.expectError(Error.BadAddress, parseAddr("0x")); + try std.testing.expectError(Error.BadAddress, parseAddr("nope")); + try std.testing.expectError(Error.BadAddress, parseAddr("12g4")); try std.testing.expectError(Error.BadCount, parseCount("-1")); try std.testing.expectError(Error.BadValue, parseValue("0x1_0000_0000")); } @@ -221,7 +254,7 @@ test "an address is hex or decimal, and a bare hex-looking token is decimal" { pub fn peek(p: *Pardes, id: usize, argument: []const u8) !void { var it = std.mem.tokenizeAny(u8, argument, " \t\r\n"); const addr = try parseAddr(it.next() orelse return Error.MissingAddress); - const requested = if (it.next()) |tok| try parseCount(tok) else 1; + const requested = if (it.next()) |tok| try parseCount(tok) else 0x1; if (it.next() != null) return Error.ExtraArgument; if (addr % 4 != 0) return Error.MisalignedAddress; @@ -271,7 +304,7 @@ pub fn poke(p: *Pardes, id: usize, argument: []const u8) !void { pub fn hexdump(p: *Pardes, id: usize, argument: []const u8) !void { var it = std.mem.tokenizeAny(u8, argument, " \t\r\n"); const addr = try parseAddr(it.next() orelse return Error.MissingAddress); - const requested = if (it.next()) |tok| try parseCount(tok) else 256; + const requested = if (it.next()) |tok| try parseCount(tok) else 0x100; if (it.next() != null) return Error.ExtraArgument; const e = extent(addr, requested, 1, max_bytes); |
