Files
ww/lib/encoding/hex/hex.ww
Hojun-Cho a8561c03a0 lib/encoding/hex+test: Hare port (encode / decode / sizes)
Replaces the 19-line placeholder. Five entrypoints per
ref/hare/encoding/hex/hex.ha + README:13:

- invalid (!void) — mirrors errors::invalid (hex.ha:175 decodestr).
  base32's !i32 is a pre-existing in-tree divergence; hex doesn't
  carry it forward.
- encodedsize(n) = n*2 — derived from hex.ha:46-55 encode_writer
  lowercase 2-chars-per-byte.
- decodedsize(n) = n/2 — inverse; hex.ha:158.
- encode(dst, src) i32 — lowercase output per README:13 + hex.ha:91.
- decode(dst, src) (i32 | invalid) — accepts lower / upper / mixed;
  returns invalid on odd length (hex.ha:154) or non-hex char
  (hex.ha:161-163).

Deferred (cite-and-defer, same pattern as base32/base64):
- newencoder / newdecoder — Hare's io::handle stream API; ww has no
  io::handle integration yet.
- encodestr / decodestr — allocator-returning sum-result; needs
  os.alloc-backed memio dynamic, not wired.
- dump — hexdump-with-ASCII view; needs io::handle + fmt::fprintf
  into a write sink.

Test: lib/encoding/hex/hextest.ww 13 rows — sizes, encode_basic
(Hare's CAFEBABEDEADF00D verbatim), encode_zero / encode_ff /
encode_empty (nibble corners + sign-extend + table off-by-one),
decode_{lower,upper,mixed} (case acceptance), decode_{empty,
odd_length,bad_char,bad_char_mid} (error paths), roundtrip_all_bytes
(0..255 full nibble+shift family — Class B exerciser).

Driver test/wcc/979_hex_run.c slotted between 978_intdiv_signed and
980_memio_run.
2026-05-17 07:07:57 +09:00

79 lines
2.7 KiB
Plaintext

// encoding/hex — RFC 4648 base16 (hexadecimal) encode/decode,
// buffer-based.
//
// Mirrors Hare's encoding::hex surface, modulo Hare's stream-based
// encoder/decoder and memio-backed allocator helpers. ww ships the
// in-memory subset only — same shape as lib/encoding/base32: caller
// provides dst, fn returns the byte count or invalid.
//
// Output is always lowercase per ref/hare/encoding/hex/README:13;
// decode accepts both upper- and lower-case per the same line.
// invalid — input wasn't a valid hex sequence. Mirrors the
// `errors::invalid` (!void) that ref/hare/encoding/hex/hex.ha:175
// returns from decodestr. lib/encoding/base32's local !i32 spelling
// is a pre-existing divergence; this module follows Hare.
export type invalid = !void;
// encodedsize — bytes required to encode `n` source bytes.
// ref/hare/encoding/hex/hex.ha:91 writes 2 chars per input byte
// unconditionally.
export fn encodedsize(n: i32) i32 = { return n * 2; };
// decodedsize — bytes produced when decoding `n` encoded bytes.
// `n` must be even or decode returns invalid.
export fn decodedsize(n: i32) i32 = { return n / 2; };
// 4-bit value to lowercase hex char. '0'..'9' is +48; 'a'..'f' is
// +87 (= 'a' - 10).
fn nibble(v: u8) u8 = {
if (v < 10u8) { return v + 48u8; };
return v + 87u8;
};
// Hex char to 4-bit value, accepting upper or lower case. Returns
// 255 on invalid char (sentinel; '=' isn't legal in hex).
fn denibble(c: u8) u8 = {
if (c >= 48u8) { if (c <= 57u8) { return c - 48u8; }; }; // '0'..'9'
if (c >= 65u8) { if (c <= 70u8) { return c - 55u8; }; }; // 'A'..'F'
if (c >= 97u8) { if (c <= 102u8) { return c - 87u8; }; }; // 'a'..'f'
return 255u8;
};
// encode — encode `src` into `dst` as lowercase hex pairs. Returns
// bytes written. `dst` must hold at least encodedsize(src.len)
// bytes. Mirrors ref/hare/encoding/hex/hex.ha:91 `encode(out, in)`
// (sans the io::handle wrapper).
export fn encode(dst: []u8, src: []u8) i32 = {
let i: i32 = 0;
let j: i32 = 0;
for (i < src.len) {
let b: u8 = src[i];
dst[j] = nibble(b >> 4u8);
dst[j + 1] = nibble(b & 15u8);
i += 1;
j += 2;
};
return j;
};
// decode — decode hex pairs from `src` into `dst`. Returns count
// of bytes written or invalid on odd-length input or non-hex char.
// Mirrors ref/hare/encoding/hex/hex.ha:175 `decodestr(s)` (sans
// the allocator return).
export fn decode(dst: []u8, src: []u8) (i32 | invalid) = {
if ((src.len & 1) != 0) { let e: invalid; return e; };
let i: i32 = 0;
let j: i32 = 0;
for (i < src.len) {
let hi: u8 = denibble(src[i]);
let lo: u8 = denibble(src[i + 1]);
if (hi == 255u8) { let e: invalid; return e; };
if (lo == 255u8) { let e: invalid; return e; };
dst[j] = (hi << 4u8) | lo;
i += 2;
j += 1;
};
return j;
};