// strconv — number↔string conversions. Decimal i64 to/from a fixed // buffer. Error shapes mirror Hare's strconv types: (T | invalid | // overflow) where each error is a named alias over a payload type // (Hare uses !size / !void; ww uses i32 / void without the `!` mark). // invalid — input wasn't a valid number in the requested format. // Payload is the byte index of the first offending position. Mirrors // Hare's strconv::invalid = !size (we use i32 instead of size). export type invalid = !i32; // overflow — input was valid but doesn't fit the target type. No // payload (a single yes/no signal). Mirrors Hare's !void shape. export type overflow = !void; // u64tos — write `v` in decimal into `buf` and return the byte count. // Hare name; the buffer-in shape is the sanctioned Plan 9 subset of // Hare's `u64tos(u, base) const str`. Unsigned-only so callers don't // have to think about wraparound when printing a u64 with the high // bit set. export fn u64tos(buf: []u8, v: u64) i32 = { let tmp: [32]u8; let i: i32 = 0; let n: u64 = v; for (n > 0u64) { tmp[i] = ((n % 10u64) + 48u64): u8; n = n / 10u64; i += 1; }; if (i == 0) { tmp[0] = 48u8; i = 1; }; let out: i32 = 0; for (i > 0) { i -= 1; buf[out] = tmp[i]; out += 1; }; return out; }; export fn i64tos(buf: []u8, v: i64) i32 = { let neg: bool = false; let n: i64 = v; if (n < 0) { neg = true; n = -n; }; let tmp: [32]u8; let i: i32 = 0; for (n > 0) { tmp[i] = ((n % 10) + 48): u8; n = n / 10; i += 1; }; if (i == 0) { tmp[0] = 48u8; i = 1; }; let out: i32 = 0; if (neg) { buf[out] = 45u8; // '-' out += 1; }; for (i > 0) { i -= 1; buf[out] = tmp[i]; out += 1; }; return out; }; // stoi64 — Hare-style fallible signed decimal parser. No locale, no // whitespace, no underscores: a leading '-' is the only non-digit // accepted, and only at position 0. export fn stoi64(s: str) (i64 | invalid | overflow) = { if (s.len == 0) { return 0: invalid; }; let i: i32 = 0; let neg: bool = false; if (s[0] == 45u8) { neg = true; i = 1; }; if (i >= s.len) { return i: invalid; }; let v: i64 = 0; for (i < s.len) { let c: u8 = s[i]; if (c < 48u8) { return i: invalid; }; if (c > 57u8) { return i: invalid; }; v = v * 10 + ((c: i64) - 48); i += 1; }; if (neg) { v = -v; }; return v; }; // stou64 — fallible unsigned decimal parser. No leading sign. export fn stou64(s: str) (u64 | invalid | overflow) = { if (s.len == 0) { return 0: invalid; }; let v: u64 = 0u64; let i: i32 = 0; for (i < s.len) { let c: u8 = s[i]; if (c < 48u8) { return i: invalid; }; if (c > 57u8) { return i: invalid; }; v = v * 10u64 + ((c: u64) - 48u64); i += 1; }; return v; }; // f64tos — write `v` in decimal into `buf` and return the byte count. // Hare name; this is the buffer-in Plan 9 subset of Hare's // `f64tos(n) const str`. Today's surface: // // - finite values only. NaN/±Inf detection needs an f64→u64 bit // reinterpret cast that the cgen doesn't expose yet. // - fixed-point only, up to 6 fractional digits. Trailing zeros // after the decimal point are trimmed. Trailing '.' is dropped. // - magnitudes ≥ 9e18 (overflows i64 in the integer-part cast) // fall back to the literal token "huge". Hare would print these // in scientific notation via Ryū; we will graduate when the // compiler grows the bit-reinterpret cast. // // Round-trip is therefore lossy past 6 fractional digits; callers // that need bit-exact recovery should not use this until the // graduate-to-Ryū step lands. `f64tos(buf, 1.0)` writes "1" (no // decimal point), `f64tos(buf, 1.5)` writes "1.5", `f64tos(buf, // 0.1)` writes "0.1". // // No float literals in the body — 990's wwdump diff requires this // file's TK_FLOAT count to match between C and ww front-ends, and // the ww-side wwdump currently skips TK_FLOAT.fval while the C side // %g-formats it. Same trick lib/ww/lex/lex.ww's parsef64 uses: // build f64 constants via int-to-f64 casts. export fn f64tos(buf: []u8, v: f64) i32 = { let out: i32 = 0; let f: f64 = v; let zero: f64 = 0: f64; if (f < zero) { buf[out] = 45u8; // '-' out += 1; f = -f; }; // 9e18 is comfortably under I64_MAX (9.22e18). Past this the // `f: i64` cast wraps and the integer part comes back as garbage. let cap: f64 = 9000000000000000000i64: f64; if (f >= cap) { let s: str = "huge"; let k: i32 = 0; for (k < s.len) { buf[out] = s[k]; out += 1; k += 1; }; return out; }; let ip: i64 = f: i64; // Fractional part scaled to 6 decimal digits, with round-to- // nearest via +0.5. (f64 compound assigns mis-lower in cgen — // use the explicit form, as the rest of lib does.) let frac: f64 = f - (ip: f64); let scale: f64 = 1000000: f64; frac = frac * scale; let half: f64 = (1: f64) / (2: f64); let fp: i64 = (frac + half): i64; // Carry: e.g. 0.9999996 rounds fp up to 1000000 and the integer // part needs to advance. if (fp >= 1000000) { ip += 1; fp = 0; }; let itmp: [32]u8; let in: i32 = i64tos(itmp[0:32], ip); let k: i32 = 0; for (k < in) { buf[out] = itmp[k]; out += 1; k += 1; }; if (fp == 0) { return out; }; buf[out] = 46u8; // '.' out += 1; let ftmp: [16]u8; let m: i32 = u64tos(ftmp[0:16], fp: u64); // Pad fractional to 6 digits with leading zeros (e.g. 0.05 → // fp=50000, m=5, pad one '0' before "50000"). let z: i32 = 6 - m; for (z > 0) { buf[out] = 48u8; out += 1; z -= 1; }; k = 0; for (k < m) { buf[out] = ftmp[k]; out += 1; k += 1; }; // Trim trailing zeros in the fractional part (we know fp != 0, // so the loop stops before erasing the dot). for (out > 0) { if (buf[out - 1] != 48u8) { break; }; out -= 1; }; return out; };