endianness/more prog mode
This commit is contained in:
parent
6a5bdf1e5e
commit
ebef9c52e4
8 changed files with 146 additions and 53 deletions
|
|
@ -635,15 +635,35 @@ Navigation:
|
||||||
value updates on Enter (or live as you type - TBD)
|
value updates on Enter (or live as you type - TBD)
|
||||||
- When bit grid is focused: left/right/up/down navigate, Space toggles
|
- When bit grid is focused: left/right/up/down navigate, Space toggles
|
||||||
- Tab cycles between Standard/Programmer mode
|
- Tab cycles between Standard/Programmer mode
|
||||||
- Ctrl-W cycles bit width (8/16/32/64)
|
- Ctrl-W cycles bit width (8/16/32/64/128)
|
||||||
- Ctrl-E toggles endianness display (LE/BE)
|
- Ctrl-E toggles endianness display (LE/BE)
|
||||||
|
|
||||||
|
Bit width is a non-destructive display lens:
|
||||||
|
- The stored value keeps full precision (u128). Cycling the width never
|
||||||
|
truncates it, so narrowing to a smaller width and then widening again
|
||||||
|
restores the hidden upper bits.
|
||||||
|
- The DEC/HEX/OCT/BIN/ASCII rows and the bit grid show the value masked to
|
||||||
|
the current width.
|
||||||
|
- While the stored value has bits set beyond the current width, a warning
|
||||||
|
line is shown ("value exceeds N bits - showing low bits (widen to
|
||||||
|
restore)"). It is a warning, not an error: the value is still usable.
|
||||||
|
- Explicit value edits (typing a decimal digit, toggling a bit, editing a
|
||||||
|
hex/oct/bin digit) commit to the current width, clearing any hidden upper
|
||||||
|
bits. Only width cycling is non-destructive; deliberately editing at a
|
||||||
|
narrow width means you are now working at that width.
|
||||||
|
|
||||||
Endianness:
|
Endianness:
|
||||||
- Default: little-endian (matches x86-64)
|
- Toggle with Ctrl-E; header shows current setting (`Endian: LE` / `Endian: BE`)
|
||||||
- Toggle with Ctrl-E
|
- Default: big-endian. The HEX row's primary job is to show the value in hex,
|
||||||
- Affects: HEX byte display order, ASCII interpretation order
|
so by default it reads MSB-first and matches DEC/OCT/BIN. Little-endian is
|
||||||
- Does NOT affect: the underlying u64 value, DEC display, BIN display
|
the opt-in "how it sits in x86-64 memory" view.
|
||||||
- Header shows current setting: `Endian: LE` or `Endian: BE`
|
- Affects: HEX byte display order and ASCII byte order only. In LE the display
|
||||||
|
bytes are reversed (e.g. 0xDEADBEEF -> `EF BE AD DE`).
|
||||||
|
- Does NOT affect: the underlying value, DEC, OCT, or BIN display, nor the
|
||||||
|
clipboard `raw` form of HEX (which stays the canonical MSB-first number).
|
||||||
|
- Rationale for BE default (revised from an earlier LE default): defaulting to
|
||||||
|
LE would reverse the HEX row so it no longer matches the number shown in the
|
||||||
|
DEC/BIN rows directly beside it, which is surprising in a calculator context.
|
||||||
|
|
||||||
Text/ASCII literals:
|
Text/ASCII literals:
|
||||||
- Single-quoted strings in expressions: `'ELF'`, `'RIFF'`, `'a'`
|
- Single-quoted strings in expressions: `'ELF'`, `'RIFF'`, `'a'`
|
||||||
|
|
|
||||||
|
|
@ -263,7 +263,11 @@ Subcommands deferred until Phase 2 engine work is done.
|
||||||
- Focused field is visually highlighted (label gets inverse color)
|
- Focused field is visually highlighted (label gets inverse color)
|
||||||
- Removed Ctrl-B (field navigation replaces it)
|
- Removed Ctrl-B (field navigation replaces it)
|
||||||
- Expression field works as before (full programmer-mode expression evaluation)
|
- Expression field works as before (full programmer-mode expression evaluation)
|
||||||
- Endianness indicator in header (display only, toggle not yet implemented)
|
- ASCII row under HEX (read-only, xxd-style, aligned column-for-column)
|
||||||
|
- Endianness toggle (Ctrl-E): flips HEX and ASCII byte order, header shows LE/BE.
|
||||||
|
Default big-endian so HEX reads as the number (matching DEC/OCT/BIN); LE is the
|
||||||
|
opt-in x86-64 memory-layout view. DEC/OCT/BIN and the hex `raw`/clipboard form
|
||||||
|
are unaffected. Covered by formatter unit tests (BE and LE for hex and ascii).
|
||||||
- Verify: can arrow between fields, type hex value and see all fields update, toggle bits
|
- Verify: can arrow between fields, type hex value and see all fields update, toggle bits
|
||||||
|
|
||||||
### Task 5.3.2: Implement text/ASCII literal parsing [DONE]
|
### Task 5.3.2: Implement text/ASCII literal parsing [DONE]
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const types = @import("types.zig");
|
const types = @import("types.zig");
|
||||||
const BitWidth = types.BitWidth;
|
const BitWidth = types.BitWidth;
|
||||||
|
const Endianness = types.Endianness;
|
||||||
|
|
||||||
/// A formatted value with both display and clipboard representations.
|
/// A formatted value with both display and clipboard representations.
|
||||||
pub const FormattedValue = struct {
|
pub const FormattedValue = struct {
|
||||||
|
|
@ -55,13 +56,15 @@ pub fn formatFloat(buf: []u8, value: f64) FormattedValue {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Format an integer for programmer mode hex display.
|
/// Format an integer for programmer mode hex display.
|
||||||
/// Display: "FF FF FF FF" (space per byte)
|
/// Display: "FF FF FF FF" (space per byte), byte order per `endian`.
|
||||||
/// Raw: "0xFFFFFFFF" (no separators, with prefix)
|
/// Raw: "0xFFFFFFFF" (no separators, canonical MSB-first value regardless of
|
||||||
pub fn formatHex(buf: []u8, value: u128, bit_width: BitWidth) FormattedValue {
|
/// endian, since raw is the number itself and copies to the clipboard as such).
|
||||||
|
pub fn formatHex(buf: []u8, value: u128, bit_width: BitWidth, endian: Endianness) FormattedValue {
|
||||||
const width = bit_width.bits();
|
const width = bit_width.bits();
|
||||||
const hex_digits: usize = @as(usize, width) / 4;
|
const hex_digits: usize = @as(usize, width) / 4;
|
||||||
|
const num_bytes: usize = @as(usize, width) / 8;
|
||||||
|
|
||||||
// Write raw first: "0x" + hex digits
|
// Write raw first: "0x" + hex digits, canonical MSB-first.
|
||||||
buf[0] = '0';
|
buf[0] = '0';
|
||||||
buf[1] = 'x';
|
buf[1] = 'x';
|
||||||
var pos: usize = 2;
|
var pos: usize = 2;
|
||||||
|
|
@ -74,17 +77,20 @@ pub fn formatHex(buf: []u8, value: u128, bit_width: BitWidth) FormattedValue {
|
||||||
}
|
}
|
||||||
const raw = buf[0..pos];
|
const raw = buf[0..pos];
|
||||||
|
|
||||||
// Write display after raw: hex digits with space per byte (every 2 digits)
|
// Write display after raw: one byte at a time in `endian` order,
|
||||||
|
// space-separated. Only the byte order swaps; nibble order within a
|
||||||
|
// byte is always high-then-low.
|
||||||
const display_start = pos;
|
const display_start = pos;
|
||||||
i = 0;
|
var k: usize = 0;
|
||||||
while (i < hex_digits) : (i += 1) {
|
while (k < num_bytes) : (k += 1) {
|
||||||
if (i > 0 and i % 2 == 0) {
|
if (k > 0) {
|
||||||
buf[pos] = ' ';
|
buf[pos] = ' ';
|
||||||
pos += 1;
|
pos += 1;
|
||||||
}
|
}
|
||||||
const shift_amt: u7 = @intCast((hex_digits - 1 - i) * 4);
|
const byte = byteAt(value, num_bytes, k, endian);
|
||||||
const nibble: u4 = @intCast((value >> shift_amt) & 0xF);
|
buf[pos] = hexDigit(@intCast(byte >> 4));
|
||||||
buf[pos] = hexDigit(nibble);
|
pos += 1;
|
||||||
|
buf[pos] = hexDigit(@intCast(byte & 0xF));
|
||||||
pos += 1;
|
pos += 1;
|
||||||
}
|
}
|
||||||
const display = buf[display_start..pos];
|
const display = buf[display_start..pos];
|
||||||
|
|
@ -92,6 +98,15 @@ pub fn formatHex(buf: []u8, value: u128, bit_width: BitWidth) FormattedValue {
|
||||||
return .{ .display = display, .raw = raw };
|
return .{ .display = display, .raw = raw };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the byte to display at position `k` (0 = leftmost) for a value of
|
||||||
|
/// `num_bytes` width, honoring endianness. Big-endian shows the most
|
||||||
|
/// significant byte first; little-endian shows the least significant first.
|
||||||
|
fn byteAt(value: u128, num_bytes: usize, k: usize, endian: Endianness) u8 {
|
||||||
|
const msb_index: usize = if (endian == .big) k else num_bytes - 1 - k;
|
||||||
|
const shift_amt: u7 = @intCast((num_bytes - 1 - msb_index) * 8);
|
||||||
|
return @intCast((value >> shift_amt) & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
/// Format an integer for programmer mode binary display.
|
/// Format an integer for programmer mode binary display.
|
||||||
/// Display: "1111 0000 1010 1100" (space per nibble)
|
/// Display: "1111 0000 1010 1100" (space per nibble)
|
||||||
/// Raw: "0b1111000010101100" (no separators)
|
/// Raw: "0b1111000010101100" (no separators)
|
||||||
|
|
@ -170,23 +185,21 @@ pub fn formatOctal(buf: []u8, value: u128, bit_width: BitWidth) FormattedValue {
|
||||||
return .{ .display = display, .raw = raw };
|
return .{ .display = display, .raw = raw };
|
||||||
}
|
}
|
||||||
/// Format an integer's bytes as an ASCII representation for programmer mode.
|
/// Format an integer's bytes as an ASCII representation for programmer mode.
|
||||||
/// One glyph per byte, most-significant byte first (matching the hex byte view).
|
/// One glyph per byte in `endian` order (matching the hex byte view).
|
||||||
/// Printable bytes (0x20-0x7E) render as themselves; every other byte renders
|
/// Printable bytes (0x20-0x7E) render as themselves; every other byte renders
|
||||||
/// as '.', matching the convention used by `xxd` and similar hex dumps.
|
/// as '.', matching the convention used by `xxd` and similar hex dumps.
|
||||||
///
|
///
|
||||||
/// Display: " . . . a s c i i" - each byte is a leading space + glyph,
|
/// Display: " . . . a s c i i" - each byte is a leading space + glyph,
|
||||||
/// space-separated, so the row lines up column-for-column under formatHex.
|
/// space-separated, so the row lines up column-for-column under formatHex.
|
||||||
/// Raw: "...ascii" - contiguous glyphs, clipboard-friendly.
|
/// Raw: "...ascii" - contiguous glyphs, clipboard-friendly.
|
||||||
pub fn formatAscii(buf: []u8, value: u128, bit_width: BitWidth) FormattedValue {
|
pub fn formatAscii(buf: []u8, value: u128, bit_width: BitWidth, endian: Endianness) FormattedValue {
|
||||||
const num_bytes: usize = @as(usize, bit_width.bits()) / 8;
|
const num_bytes: usize = @as(usize, bit_width.bits()) / 8;
|
||||||
|
|
||||||
// Raw: contiguous glyphs, MSB first.
|
// Raw: contiguous glyphs, in display (endian) order.
|
||||||
var pos: usize = 0;
|
var pos: usize = 0;
|
||||||
var k: usize = 0;
|
var k: usize = 0;
|
||||||
while (k < num_bytes) : (k += 1) {
|
while (k < num_bytes) : (k += 1) {
|
||||||
const shift_amt: u7 = @intCast((num_bytes - 1 - k) * 8);
|
buf[pos] = asciiGlyph(byteAt(value, num_bytes, k, endian));
|
||||||
const byte: u8 = @intCast((value >> shift_amt) & 0xFF);
|
|
||||||
buf[pos] = asciiGlyph(byte);
|
|
||||||
pos += 1;
|
pos += 1;
|
||||||
}
|
}
|
||||||
const raw = buf[0..pos];
|
const raw = buf[0..pos];
|
||||||
|
|
@ -202,9 +215,7 @@ pub fn formatAscii(buf: []u8, value: u128, bit_width: BitWidth) FormattedValue {
|
||||||
}
|
}
|
||||||
buf[pos] = ' ';
|
buf[pos] = ' ';
|
||||||
pos += 1;
|
pos += 1;
|
||||||
const shift_amt: u7 = @intCast((num_bytes - 1 - k) * 8);
|
buf[pos] = asciiGlyph(byteAt(value, num_bytes, k, endian));
|
||||||
const byte: u8 = @intCast((value >> shift_amt) & 0xFF);
|
|
||||||
buf[pos] = asciiGlyph(byte);
|
|
||||||
pos += 1;
|
pos += 1;
|
||||||
}
|
}
|
||||||
const display = buf[display_start..pos];
|
const display = buf[display_start..pos];
|
||||||
|
|
@ -361,39 +372,61 @@ test "formatFloat: zero" {
|
||||||
|
|
||||||
test "formatHex: 8-bit" {
|
test "formatHex: 8-bit" {
|
||||||
var buf: [256]u8 = undefined;
|
var buf: [256]u8 = undefined;
|
||||||
const result = formatHex(&buf, 0xFF, .bits8);
|
const result = formatHex(&buf, 0xFF, .bits8, .big);
|
||||||
try testing.expectEqualStrings("FF", result.display);
|
try testing.expectEqualStrings("FF", result.display);
|
||||||
try testing.expectEqualStrings("0xFF", result.raw);
|
try testing.expectEqualStrings("0xFF", result.raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "formatHex: 16-bit" {
|
test "formatHex: 16-bit" {
|
||||||
var buf: [256]u8 = undefined;
|
var buf: [256]u8 = undefined;
|
||||||
const result = formatHex(&buf, 0xABCD, .bits16);
|
const result = formatHex(&buf, 0xABCD, .bits16, .big);
|
||||||
try testing.expectEqualStrings("AB CD", result.display);
|
try testing.expectEqualStrings("AB CD", result.display);
|
||||||
try testing.expectEqualStrings("0xABCD", result.raw);
|
try testing.expectEqualStrings("0xABCD", result.raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "formatHex: 32-bit with grouping" {
|
test "formatHex: 32-bit with grouping" {
|
||||||
var buf: [256]u8 = undefined;
|
var buf: [256]u8 = undefined;
|
||||||
const result = formatHex(&buf, 0xDEADBEEF, .bits32);
|
const result = formatHex(&buf, 0xDEADBEEF, .bits32, .big);
|
||||||
try testing.expectEqualStrings("DE AD BE EF", result.display);
|
try testing.expectEqualStrings("DE AD BE EF", result.display);
|
||||||
try testing.expectEqualStrings("0xDEADBEEF", result.raw);
|
try testing.expectEqualStrings("0xDEADBEEF", result.raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "formatHex: 64-bit with grouping" {
|
test "formatHex: 64-bit with grouping" {
|
||||||
var buf: [256]u8 = undefined;
|
var buf: [256]u8 = undefined;
|
||||||
const result = formatHex(&buf, 0xDEAD_BEEF_CAFE_BABE, .bits64);
|
const result = formatHex(&buf, 0xDEAD_BEEF_CAFE_BABE, .bits64, .big);
|
||||||
try testing.expectEqualStrings("DE AD BE EF CA FE BA BE", result.display);
|
try testing.expectEqualStrings("DE AD BE EF CA FE BA BE", result.display);
|
||||||
try testing.expectEqualStrings("0xDEADBEEFCAFEBABE", result.raw);
|
try testing.expectEqualStrings("0xDEADBEEFCAFEBABE", result.raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "formatHex: zero 32-bit" {
|
test "formatHex: zero 32-bit" {
|
||||||
var buf: [256]u8 = undefined;
|
var buf: [256]u8 = undefined;
|
||||||
const result = formatHex(&buf, 0, .bits32);
|
const result = formatHex(&buf, 0, .bits32, .big);
|
||||||
try testing.expectEqualStrings("00 00 00 00", result.display);
|
try testing.expectEqualStrings("00 00 00 00", result.display);
|
||||||
try testing.expectEqualStrings("0x00000000", result.raw);
|
try testing.expectEqualStrings("0x00000000", result.raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "formatHex: little-endian reverses byte display" {
|
||||||
|
var buf: [256]u8 = undefined;
|
||||||
|
const result = formatHex(&buf, 0xDEADBEEF, .bits32, .little);
|
||||||
|
// Bytes shown least-significant first (x86 memory order)
|
||||||
|
try testing.expectEqualStrings("EF BE AD DE", result.display);
|
||||||
|
// Raw stays the canonical number
|
||||||
|
try testing.expectEqualStrings("0xDEADBEEF", result.raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "formatHex: little-endian 64-bit" {
|
||||||
|
var buf: [256]u8 = undefined;
|
||||||
|
const result = formatHex(&buf, 0xDEAD_BEEF_CAFE_BABE, .bits64, .little);
|
||||||
|
try testing.expectEqualStrings("BE BA FE CA EF BE AD DE", result.display);
|
||||||
|
try testing.expectEqualStrings("0xDEADBEEFCAFEBABE", result.raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "formatHex: little-endian 8-bit is identical (single byte)" {
|
||||||
|
var buf: [256]u8 = undefined;
|
||||||
|
const result = formatHex(&buf, 0xFF, .bits8, .little);
|
||||||
|
try testing.expectEqualStrings("FF", result.display);
|
||||||
|
}
|
||||||
|
|
||||||
test "formatBinary: 8-bit" {
|
test "formatBinary: 8-bit" {
|
||||||
var buf: [256]u8 = undefined;
|
var buf: [256]u8 = undefined;
|
||||||
const result = formatBinary(&buf, 0xFF, .bits8);
|
const result = formatBinary(&buf, 0xFF, .bits8);
|
||||||
|
|
@ -491,41 +524,48 @@ test "formatFloat: regular float" {
|
||||||
test "formatAscii: printable word 64-bit" {
|
test "formatAscii: printable word 64-bit" {
|
||||||
var buf: [128]u8 = undefined;
|
var buf: [128]u8 = undefined;
|
||||||
// 0x0000006173636969 -> bytes 00 00 00 61 73 63 69 69 -> "...ascii"
|
// 0x0000006173636969 -> bytes 00 00 00 61 73 63 69 69 -> "...ascii"
|
||||||
const result = formatAscii(&buf, 0x0000006173636969, .bits64);
|
const result = formatAscii(&buf, 0x0000006173636969, .bits64, .big);
|
||||||
try testing.expectEqualStrings("...ascii", result.raw);
|
try testing.expectEqualStrings("...ascii", result.raw);
|
||||||
// Display aligns one glyph per byte, space-separated
|
// Display aligns one glyph per byte, space-separated
|
||||||
try testing.expectEqualStrings(" . . . a s c i i", result.display);
|
try testing.expectEqualStrings(" . . . a s c i i", result.display);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "formatAscii: little-endian reverses byte order" {
|
||||||
|
var buf: [128]u8 = undefined;
|
||||||
|
// Same value, little-endian: bytes reversed -> "iicsa..." with trailing dots
|
||||||
|
const result = formatAscii(&buf, 0x0000006173636969, .bits64, .little);
|
||||||
|
try testing.expectEqualStrings("iicsa...", result.raw);
|
||||||
|
}
|
||||||
|
|
||||||
test "formatAscii: 8-bit printable" {
|
test "formatAscii: 8-bit printable" {
|
||||||
var buf: [128]u8 = undefined;
|
var buf: [128]u8 = undefined;
|
||||||
const result = formatAscii(&buf, 0x41, .bits8);
|
const result = formatAscii(&buf, 0x41, .bits8, .big);
|
||||||
try testing.expectEqualStrings("A", result.raw);
|
try testing.expectEqualStrings("A", result.raw);
|
||||||
try testing.expectEqualStrings(" A", result.display);
|
try testing.expectEqualStrings(" A", result.display);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "formatAscii: 8-bit non-printable becomes dot" {
|
test "formatAscii: 8-bit non-printable becomes dot" {
|
||||||
var buf: [128]u8 = undefined;
|
var buf: [128]u8 = undefined;
|
||||||
const result = formatAscii(&buf, 0x00, .bits8);
|
const result = formatAscii(&buf, 0x00, .bits8, .big);
|
||||||
try testing.expectEqualStrings(".", result.raw);
|
try testing.expectEqualStrings(".", result.raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "formatAscii: control and high bytes become dots" {
|
test "formatAscii: control and high bytes become dots" {
|
||||||
var buf: [128]u8 = undefined;
|
var buf: [128]u8 = undefined;
|
||||||
// 0x1B (ESC) and 0xFF are both non-printable
|
// 0x1B (ESC) and 0xFF are both non-printable
|
||||||
const result = formatAscii(&buf, 0x1BFF, .bits16);
|
const result = formatAscii(&buf, 0x1BFF, .bits16, .big);
|
||||||
try testing.expectEqualStrings("..", result.raw);
|
try testing.expectEqualStrings("..", result.raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "formatAscii: boundary bytes 0x20 and 0x7E are printable" {
|
test "formatAscii: boundary bytes 0x20 and 0x7E are printable" {
|
||||||
var buf: [128]u8 = undefined;
|
var buf: [128]u8 = undefined;
|
||||||
// 0x20 = space, 0x7E = '~'
|
// 0x20 = space, 0x7E = '~'
|
||||||
const result = formatAscii(&buf, 0x207E, .bits16);
|
const result = formatAscii(&buf, 0x207E, .bits16, .big);
|
||||||
try testing.expectEqualStrings(" ~", result.raw);
|
try testing.expectEqualStrings(" ~", result.raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "formatAscii: 0x7F is non-printable" {
|
test "formatAscii: 0x7F is non-printable" {
|
||||||
var buf: [128]u8 = undefined;
|
var buf: [128]u8 = undefined;
|
||||||
const result = formatAscii(&buf, 0x7F, .bits8);
|
const result = formatAscii(&buf, 0x7F, .bits8, .big);
|
||||||
try testing.expectEqualStrings(".", result.raw);
|
try testing.expectEqualStrings(".", result.raw);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -105,7 +105,10 @@ pub const Value = union(enum) {
|
||||||
pub const ProgrammerConfig = struct {
|
pub const ProgrammerConfig = struct {
|
||||||
bit_width: BitWidth = .bits64,
|
bit_width: BitWidth = .bits64,
|
||||||
signedness: Signedness = .signed,
|
signedness: Signedness = .signed,
|
||||||
display_endian: Endianness = .little,
|
/// Byte order for the HEX and ASCII rows only. Defaults to big-endian so
|
||||||
|
/// the HEX row reads as the number itself (matching DEC/OCT/BIN); the
|
||||||
|
/// little-endian view (x86 memory layout) is available via the toggle.
|
||||||
|
display_endian: Endianness = .big,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// All possible engine errors.
|
/// All possible engine errors.
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@ fn formatStandardMultiBase(buf: []u8, dec_display: []const u8, value: f64) CliRe
|
||||||
var hex_buf: [256]u8 = undefined;
|
var hex_buf: [256]u8 = undefined;
|
||||||
var oct_buf: [256]u8 = undefined;
|
var oct_buf: [256]u8 = undefined;
|
||||||
var bin_buf: [512]u8 = undefined;
|
var bin_buf: [512]u8 = undefined;
|
||||||
const hex = engine.formatter.formatHex(&hex_buf, int_val, bw);
|
const hex = engine.formatter.formatHex(&hex_buf, int_val, bw, .big);
|
||||||
const oct = engine.formatter.formatOctal(&oct_buf, int_val, bw);
|
const oct = engine.formatter.formatOctal(&oct_buf, int_val, bw);
|
||||||
const bin = engine.formatter.formatBinary(&bin_buf, int_val, bw);
|
const bin = engine.formatter.formatBinary(&bin_buf, int_val, bw);
|
||||||
|
|
||||||
|
|
@ -127,7 +127,7 @@ fn formatProgrammerResult(buf: []u8, result: engine.types.Integer, config: engin
|
||||||
var oct_buf: [256]u8 = undefined;
|
var oct_buf: [256]u8 = undefined;
|
||||||
var bin_buf: [512]u8 = undefined;
|
var bin_buf: [512]u8 = undefined;
|
||||||
|
|
||||||
const hex = engine.formatter.formatHex(&hex_buf, value, config.bit_width);
|
const hex = engine.formatter.formatHex(&hex_buf, value, config.bit_width, config.display_endian);
|
||||||
const dec = engine.formatter.formatDecimalUnsigned(&dec_buf, value);
|
const dec = engine.formatter.formatDecimalUnsigned(&dec_buf, value);
|
||||||
const sdec = engine.formatter.formatDecimalSigned(&sdec_buf, signed);
|
const sdec = engine.formatter.formatDecimalSigned(&sdec_buf, signed);
|
||||||
const oct = engine.formatter.formatOctal(&oct_buf, value, config.bit_width);
|
const oct = engine.formatter.formatOctal(&oct_buf, value, config.bit_width);
|
||||||
|
|
|
||||||
23
src/tui.zig
23
src/tui.zig
|
|
@ -195,7 +195,11 @@ pub const App = struct {
|
||||||
.bits64 => .bits128,
|
.bits64 => .bits128,
|
||||||
.bits128 => .bits8,
|
.bits128 => .bits8,
|
||||||
};
|
};
|
||||||
self.prog_value &= self.prog_config.bit_width.mask();
|
// NOTE: prog_value is intentionally NOT masked here. Width is a
|
||||||
|
// display lens over the full value, so narrowing then widening
|
||||||
|
// restores the hidden upper bits. The display masks to width and
|
||||||
|
// a warning is shown while the value does not fit (see
|
||||||
|
// drawProgrammerMode). Explicit value edits still commit to width.
|
||||||
if (self.bit_cursor >= self.prog_config.bit_width.bits()) {
|
if (self.bit_cursor >= self.prog_config.bit_width.bits()) {
|
||||||
self.bit_cursor = @intCast(self.prog_config.bit_width.bits() - 1);
|
self.bit_cursor = @intCast(self.prog_config.bit_width.bits() - 1);
|
||||||
}
|
}
|
||||||
|
|
@ -203,6 +207,16 @@ pub const App = struct {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ctrl-E: toggle display endianness (programmer mode)
|
||||||
|
if (self.mode == .programmer and key.matches('e', .{ .ctrl = true })) {
|
||||||
|
self.prog_config.display_endian = switch (self.prog_config.display_endian) {
|
||||||
|
.little => .big,
|
||||||
|
.big => .little,
|
||||||
|
};
|
||||||
|
ctx.redraw = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// VALUE ZONE key handling
|
// VALUE ZONE key handling
|
||||||
if (self.value_zone_active) {
|
if (self.value_zone_active) {
|
||||||
self.handleValueZoneKey(key);
|
self.handleValueZoneKey(key);
|
||||||
|
|
@ -382,7 +396,10 @@ pub const App = struct {
|
||||||
},
|
},
|
||||||
.dec_unsigned, .dec_signed => {
|
.dec_unsigned, .dec_signed => {
|
||||||
if (char >= '0' and char <= '9') {
|
if (char >= '0' and char <= '9') {
|
||||||
self.prog_value = ((self.prog_value *% 10) +% (char - '0')) & self.prog_config.bit_width.mask();
|
// Operate on the in-width portion so hidden upper bits do
|
||||||
|
// not corrupt the arithmetic; the edit commits to width.
|
||||||
|
const m = self.prog_config.bit_width.mask();
|
||||||
|
self.prog_value = (((self.prog_value & m) *% 10) +% (char - '0')) & m;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
.expression => {},
|
.expression => {},
|
||||||
|
|
@ -455,7 +472,7 @@ pub const App = struct {
|
||||||
var hex_buf: [256]u8 = undefined;
|
var hex_buf: [256]u8 = undefined;
|
||||||
var oct_buf: [256]u8 = undefined;
|
var oct_buf: [256]u8 = undefined;
|
||||||
var bin_buf: [512]u8 = undefined;
|
var bin_buf: [512]u8 = undefined;
|
||||||
const hex = engine.formatter.formatHex(&hex_buf, int_val, bw);
|
const hex = engine.formatter.formatHex(&hex_buf, int_val, bw, .big);
|
||||||
const oct = engine.formatter.formatOctal(&oct_buf, int_val, bw);
|
const oct = engine.formatter.formatOctal(&oct_buf, int_val, bw);
|
||||||
const bin = engine.formatter.formatBinary(&bin_buf, int_val, bw);
|
const bin = engine.formatter.formatBinary(&bin_buf, int_val, bw);
|
||||||
details = .{
|
details = .{
|
||||||
|
|
|
||||||
|
|
@ -34,11 +34,11 @@ pub fn drawHelp(surface: *vxfw.Surface, width: u16, height: u16) void {
|
||||||
draw.writeStr(surface, row, 2, "Programmer Mode", .{ .fg = C.purple, .bold = true });
|
draw.writeStr(surface, row, 2, "Programmer Mode", .{ .fg = C.purple, .bold = true });
|
||||||
row += 1;
|
row += 1;
|
||||||
const prog_keys = [_][2][]const u8{
|
const prog_keys = [_][2][]const u8{
|
||||||
.{ "Ctrl-B", "Toggle bit grid focus" },
|
.{ "`", "Toggle input / value zone" },
|
||||||
.{ "Ctrl-W", "Cycle bit width (8/16/32/64)" },
|
.{ "Ctrl-W", "Cycle bit width (8/16/32/64/128)" },
|
||||||
|
.{ "Ctrl-E", "Toggle endianness (BE / LE)" },
|
||||||
.{ "Space", "Toggle bit (in grid)" },
|
.{ "Space", "Toggle bit (in grid)" },
|
||||||
.{ "Arrows", "Navigate bits (in grid)" },
|
.{ "Arrows", "Navigate fields and bits (value zone)" },
|
||||||
.{ "Esc", "Return to input (from grid)" },
|
|
||||||
};
|
};
|
||||||
for (prog_keys) |kv| {
|
for (prog_keys) |kv| {
|
||||||
if (row >= height -| 4) break;
|
if (row >= height -| 4) break;
|
||||||
|
|
|
||||||
|
|
@ -16,12 +16,21 @@ pub fn drawProgrammerMode(app: *tui.App, surface: *vxfw.Surface, width: u16, hei
|
||||||
|
|
||||||
// Config display
|
// Config display
|
||||||
var config_buf: [80]u8 = undefined;
|
var config_buf: [80]u8 = undefined;
|
||||||
const config_str = std.fmt.bufPrint(&config_buf, "Bits: {d} Signed: {s} Endian: LE", .{
|
const config_str = std.fmt.bufPrint(&config_buf, "Bits: {d} Signed: {s} Endian: {s}", .{
|
||||||
bw.bits(),
|
bw.bits(),
|
||||||
if (app.prog_config.signedness == .signed) "yes" else "no",
|
if (app.prog_config.signedness == .signed) "yes" else "no",
|
||||||
|
if (app.prog_config.display_endian == .little) "LE" else "BE",
|
||||||
}) catch "Bits: ??";
|
}) catch "Bits: ??";
|
||||||
draw.writeStr(surface, 2, 2, config_str, .{ .fg = C.muted });
|
draw.writeStr(surface, 2, 2, config_str, .{ .fg = C.muted });
|
||||||
|
|
||||||
|
// Truncation warning: the stored value has bits beyond the current width,
|
||||||
|
// so the display below shows only the low bits. Widening restores them.
|
||||||
|
if (app.prog_value & ~bw.mask() != 0) {
|
||||||
|
var warn_buf: [96]u8 = undefined;
|
||||||
|
const warn = std.fmt.bufPrint(&warn_buf, "! value exceeds {d} bits - showing low bits (widen to restore)", .{bw.bits()}) catch "! value exceeds width";
|
||||||
|
draw.writeStr(surface, 3, 2, warn, .{ .fg = C.pink, .bold = true });
|
||||||
|
}
|
||||||
|
|
||||||
// Bit grid
|
// Bit grid
|
||||||
const grid_start: u16 = 4;
|
const grid_start: u16 = 4;
|
||||||
drawBitGrid(app, surface, grid_start, val, bw);
|
drawBitGrid(app, surface, grid_start, val, bw);
|
||||||
|
|
@ -53,7 +62,7 @@ pub fn drawProgrammerMode(app: *tui.App, surface: *vxfw.Surface, width: u16, hei
|
||||||
|
|
||||||
// HEX
|
// HEX
|
||||||
var hex_buf: [256]u8 = undefined;
|
var hex_buf: [256]u8 = undefined;
|
||||||
const hex = engine.formatter.formatHex(&hex_buf, val, bw);
|
const hex = engine.formatter.formatHex(&hex_buf, val, bw, app.prog_config.display_endian);
|
||||||
const hex_style: vaxis.Style = if (focused == .hex)
|
const hex_style: vaxis.Style = if (focused == .hex)
|
||||||
.{ .fg = C.bg, .bg = C.green, .bold = true }
|
.{ .fg = C.bg, .bg = C.green, .bold = true }
|
||||||
else
|
else
|
||||||
|
|
@ -67,7 +76,7 @@ pub fn drawProgrammerMode(app: *tui.App, surface: *vxfw.Surface, width: u16, hei
|
||||||
|
|
||||||
// ASCII (derived, read-only): one glyph per byte, aligned under HEX.
|
// ASCII (derived, read-only): one glyph per byte, aligned under HEX.
|
||||||
var ascii_buf: [128]u8 = undefined;
|
var ascii_buf: [128]u8 = undefined;
|
||||||
const ascii = engine.formatter.formatAscii(&ascii_buf, val, bw);
|
const ascii = engine.formatter.formatAscii(&ascii_buf, val, bw, app.prog_config.display_endian);
|
||||||
draw.writeStr(surface, base_start + 3, 2, "ASCII:", .{ .fg = C.cyan });
|
draw.writeStr(surface, base_start + 3, 2, "ASCII:", .{ .fg = C.cyan });
|
||||||
draw.writeStr(surface, base_start + 3, 11, ascii.display, .{ .fg = C.orange });
|
draw.writeStr(surface, base_start + 3, 11, ascii.display, .{ .fg = C.orange });
|
||||||
|
|
||||||
|
|
@ -111,9 +120,9 @@ pub fn drawProgrammerMode(app: *tui.App, surface: *vxfw.Surface, width: u16, hei
|
||||||
app.drawInput(surface, height -| 2);
|
app.drawInput(surface, height -| 2);
|
||||||
draw.fillRow(surface, height -| 1, ' ', .{ .fg = C.muted, .bg = C.bg });
|
draw.fillRow(surface, height -| 1, ' ', .{ .fg = C.muted, .bg = C.bg });
|
||||||
const status = if (focused == .bits)
|
const status = if (focused == .bits)
|
||||||
"Arrows:nav | Space:toggle | Up/Down:field | Ctrl-W:width | Tab:mode | Ctrl-C:quit"
|
"Arrows:nav | Space:toggle | Up/Down:field | Ctrl-W:width | Ctrl-E:endian | Tab:mode | Ctrl-C:quit"
|
||||||
else
|
else
|
||||||
"Up/Down:field | Enter:set value | Ctrl-W:width | Tab:mode | ?:help | Ctrl-C:quit";
|
"Up/Down:field | Enter:set value | Ctrl-W:width | Ctrl-E:endian | Tab:mode | ?:help | Ctrl-C:quit";
|
||||||
draw.writeStr(surface, height -| 1, 1, status, .{ .fg = C.muted, .bg = C.bg });
|
draw.writeStr(surface, height -| 1, 1, status, .{ .fg = C.muted, .bg = C.bg });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue