/
nv-lang
/
nova
Обзор
Документация
Войти
/
nv-lang
/
nova
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
main
std/src/runtime/read_buffer.nv
454 строки
16 KB
Evgeniy Golovin
merge: comment-hygiene-2 — чистка комментариев std (batch 1-20) + линт-свип 330→13
01 авг 2026, 18:13
01 авг 2026, 18:13
bb8b33d
Код
Авторство
О чём код?
// Plan 91.12 (D126 retract): ReadBuffer реализован полностью на Nova. // Тип: cursor-record `{ ro data []u8, mut pos int }` — view + offset. // Ранее: external type + C runtime (Nova_ReadBuffer + ~60 inline C функций // с per-width macros). // Теперь: Nova record; все методы на Nova-body над []u8 primitives. // // API contract (Plan 177 / D325 — единый fallible-контракт): // ReadBuffer.from(b []u8) -> Self // @position, @remaining, @has_remaining, @remaining_bytes // @read_X -> Result[T, ReadBufferError], X ∈ // { byte, bytes, u8, i8, u16_le, u16_be, u32_le, u32_be, u64_le, u64_be, // i16_le, i16_be, i32_le, i32_be, i64_le, i64_be, // f32_le, f32_be, f64_le, f64_be, char, str } // // D325: одна Result-форма на операцию — bare Fail-form близнецы // (`try_read_X`/bare `read_X`-throws) РЕТРАКТИРОВАНЫ. throw на call-site = // `!!`, проброс = `?`, Result→Option = `.ok()`. #no_prelude module runtime.read_buffer // [M-ptr-raw-access-contract-and-unaligned] item 3: f64/f32 `.from_bits()` // used below is no longer a compiler intrinsic — plain Nova-body method in // std/runtime/numeric.nv (like char.from/u8.try_from in // std/runtime/char.nv). `#no_prelude` files must import it explicitly. import std.runtime.numeric // ─── Type ─── export type ReadBuffer { ro data []u8 mut pos int } // ─── Constructor ─── // Создать ReadBuffer из []u8 (view + position 0). Data копируется как // readonly handle — slice-view внутри cursor'а без отдельной alloc'и // сверх ReadBuffer struct. Param `b` readonly by default (D176 amend). // // [M-static-conv-array-record-mono-cc-fail] (2026-07-17, НЕ мигрирован): // канон §1а требует `[]u8 @to_readbuffer()` (метод на источнике) вместо // static `.from`, но эта форма — extension-метод на `[]u8`, ВОЗВРАЩАЮЩИЙ // user-record (`ReadBuffer { data: @, ... }`) — воспроизводимо ломает // линковку CC-стадии в spec_tests/conformance/read_nav.nv: `lld-link: // undefined symbol: Nova_NovaArray_nova_int_method_to_readbuffer` (mono- // коллектор мономорфизирует `to_readbuffer` для ЛЮБОЙ `[]T`-инстанциации, // встреченной в compile unit — не только `[]u8`, — и падает на T=int). // Существующие `[]u8 @to_str()`/`@to_str_unchecked()` (string/core.nv) // НЕ триггерят баг — тело у них не строит user-record через `{ field: @ }`. // Оставлено как static `.from` (W_STATIC_CONVERSION лint-находка сохранена // НАМЕРЕННО) до отдельного фикса mono-коллектора; см. отчёт задачи // fix-runtime-lint-debt. НЕ мигрировать call-сайты обратно без этого фикса. // nova:allow W_STATIC_CONVERSION -- rename заблокирован [M-static-conv-array-record-mono-cc-fail] (mono-баг extension-на-[]u8-с-record-телом); вернуть to_* после фикса export fn ReadBuffer.from(b []u8) -> Self => { data: b, pos: 0 } // ─── Cursor metadata ─── // Текущий offset cursor'а в байтах. export fn ReadBuffer @position() -> int => @pos // Сколько байт осталось до конца буфера. export fn ReadBuffer @remaining() -> int => @data.len() - @pos // True если осталось ≥ n байт. export fn ReadBuffer @has_remaining(n int) -> bool requires n >= 0 { @data.len() - @pos >= n } // Скопировать оставшиеся байты как []u8 (deep copy через slice + append). export fn ReadBuffer @remaining_bytes() -> []u8 { ro rem = @data.len() - @pos mut out []u8 = []u8.new(cap: rem.max(0)) if rem > 0 { out.append(@data[@pos..]) } out } // ─── Result-returning read primitives (D325) ─── // // Все @read_X — Result-форма. Каждый сначала проверяет // `@remaining() >= N`, читает N байт, advance'ит cursor, возвращает Ok. // При недостатке байт — Err(UnexpectedEnd { wanted, available }). // 1 byte. Result-форма. export fn ReadBuffer mut @read_byte() -> Result[u8, ReadBufferError] { if @data.len() - @pos < 1 { return Err(UnexpectedEnd { wanted: 1, available: @data.len() - @pos }) } ro v = @data[@pos] @pos += 1 Ok(v) } // n байт. Result-форма (Ok(bytes-copy) или Err). export fn ReadBuffer mut @read_bytes(n int) -> Result[[]u8, ReadBufferError] { if n < 0 || @data.len() - @pos < n { return Err(UnexpectedEnd { wanted: n, available: @data.len() - @pos }) } // Copy сегмент в свежий []u8 — caller владеет результатом независимо // от lifetime backing buffer'а. mut out []u8 = []u8.new(cap: n.max(0)) if n > 0 { out.append(@data[@pos..(@pos + n)]) } @pos += n Ok(out) } // 1 byte unsigned. export fn ReadBuffer mut @read_u8() -> Result[u8, ReadBufferError] { if @data.len() - @pos < 1 { return Err(UnexpectedEnd { wanted: 1, available: @data.len() - @pos }) } ro v = @data[@pos] @pos += 1 Ok(v) } // 1 byte signed (bit-pattern preserved через u8→i8 cast). export fn ReadBuffer mut @read_i8() -> Result[i8, ReadBufferError] { if @data.len() - @pos < 1 { return Err(UnexpectedEnd { wanted: 1, available: @data.len() - @pos }) } ro v = @data[@pos] @pos += 1 Ok(v as i8) } // ── 16-bit ── // u16 little-endian. export fn ReadBuffer mut @read_u16_le() -> Result[u16, ReadBufferError] { if @data.len() - @pos < 2 { return Err(UnexpectedEnd { wanted: 2, available: @data.len() - @pos }) } ro p = @pos ro b0 = @data[p] as int ro b1 = @data[p + 1] as int @pos = p + 2 Ok((b0 | (b1 << 8)) as u16) } // u16 big-endian. export fn ReadBuffer mut @read_u16_be() -> Result[u16, ReadBufferError] { if @data.len() - @pos < 2 { return Err(UnexpectedEnd { wanted: 2, available: @data.len() - @pos }) } ro p = @pos ro b0 = @data[p] as int ro b1 = @data[p + 1] as int @pos = p + 2 Ok(((b0 << 8) | b1) as u16) } // i16 little-endian (sign-extended). export fn ReadBuffer mut @read_i16_le() -> Result[i16, ReadBufferError] { if @data.len() - @pos < 2 { return Err(UnexpectedEnd { wanted: 2, available: @data.len() - @pos }) } ro p = @pos ro b0 = @data[p] as int ro b1 = @data[p + 1] as int @pos = p + 2 ro raw = b0 | (b1 << 8) ro sx = if raw >= 0x8000 { raw - 0x10000 } else { raw } Ok(sx as i16) } // i16 big-endian (sign-extended). export fn ReadBuffer mut @read_i16_be() -> Result[i16, ReadBufferError] { if @data.len() - @pos < 2 { return Err(UnexpectedEnd { wanted: 2, available: @data.len() - @pos }) } ro p = @pos ro b0 = @data[p] as int ro b1 = @data[p + 1] as int @pos = p + 2 ro raw = (b0 << 8) | b1 ro sx = if raw >= 0x8000 { raw - 0x10000 } else { raw } Ok(sx as i16) } // ── 32-bit ── // u32 little-endian. export fn ReadBuffer mut @read_u32_le() -> Result[u32, ReadBufferError] { if @data.len() - @pos < 4 { return Err(UnexpectedEnd { wanted: 4, available: @data.len() - @pos }) } ro p = @pos ro b0 = @data[p] as int ro b1 = @data[p + 1] as int ro b2 = @data[p + 2] as int ro b3 = @data[p + 3] as int @pos = p + 4 Ok((b0 | (b1 << 8) | (b2 << 16) | (b3 << 24)) as u32) } // u32 big-endian. export fn ReadBuffer mut @read_u32_be() -> Result[u32, ReadBufferError] { if @data.len() - @pos < 4 { return Err(UnexpectedEnd { wanted: 4, available: @data.len() - @pos }) } ro p = @pos ro b0 = @data[p] as int ro b1 = @data[p + 1] as int ro b2 = @data[p + 2] as int ro b3 = @data[p + 3] as int @pos = p + 4 Ok(((b0 << 24) | (b1 << 16) | (b2 << 8) | b3) as u32) } // i32 little-endian (sign-extended). export fn ReadBuffer mut @read_i32_le() -> Result[i32, ReadBufferError] { if @data.len() - @pos < 4 { return Err(UnexpectedEnd { wanted: 4, available: @data.len() - @pos }) } ro p = @pos ro b0 = @data[p] as int ro b1 = @data[p + 1] as int ro b2 = @data[p + 2] as int ro b3 = @data[p + 3] as int @pos = p + 4 ro raw = b0 | (b1 << 8) | (b2 << 16) | (b3 << 24) ro sx = if raw >= 0x80000000 { raw - 0x100000000 } else { raw } Ok(sx as i32) } // i32 big-endian (sign-extended). export fn ReadBuffer mut @read_i32_be() -> Result[i32, ReadBufferError] { if @data.len() - @pos < 4 { return Err(UnexpectedEnd { wanted: 4, available: @data.len() - @pos }) } ro p = @pos ro b0 = @data[p] as int ro b1 = @data[p + 1] as int ro b2 = @data[p + 2] as int ro b3 = @data[p + 3] as int @pos = p + 4 ro raw = (b0 << 24) | (b1 << 16) | (b2 << 8) | b3 ro sx = if raw >= 0x80000000 { raw - 0x100000000 } else { raw } Ok(sx as i32) } // ── 64-bit ── // // Для 64-bit no separate sign-extension нужен — int (i64) уже native // 64-bit signed; u64 cast from i64 preserves bit-pattern. // u64 little-endian. export fn ReadBuffer mut @read_u64_le() -> Result[u64, ReadBufferError] { if @data.len() - @pos < 8 { return Err(UnexpectedEnd { wanted: 8, available: @data.len() - @pos }) } ro p = @pos ro b0 = @data[p] as int ro b1 = @data[p + 1] as int ro b2 = @data[p + 2] as int ro b3 = @data[p + 3] as int ro b4 = @data[p + 4] as int ro b5 = @data[p + 5] as int ro b6 = @data[p + 6] as int ro b7 = @data[p + 7] as int @pos = p + 8 Ok((b0 | (b1 << 8) | (b2 << 16) | (b3 << 24) | (b4 << 32) | (b5 << 40) | (b6 << 48) | (b7 << 56)) as u64) } // u64 big-endian. export fn ReadBuffer mut @read_u64_be() -> Result[u64, ReadBufferError] { if @data.len() - @pos < 8 { return Err(UnexpectedEnd { wanted: 8, available: @data.len() - @pos }) } ro p = @pos ro b0 = @data[p] as int ro b1 = @data[p + 1] as int ro b2 = @data[p + 2] as int ro b3 = @data[p + 3] as int ro b4 = @data[p + 4] as int ro b5 = @data[p + 5] as int ro b6 = @data[p + 6] as int ro b7 = @data[p + 7] as int @pos = p + 8 Ok(((b0 << 56) | (b1 << 48) | (b2 << 40) | (b3 << 32) | (b4 << 24) | (b5 << 16) | (b6 << 8) | b7) as u64) } // i64 little-endian — без sign-extension (int = i64 native). export fn ReadBuffer mut @read_i64_le() -> Result[i64, ReadBufferError] { if @data.len() - @pos < 8 { return Err(UnexpectedEnd { wanted: 8, available: @data.len() - @pos }) } ro p = @pos ro b0 = @data[p] as int ro b1 = @data[p + 1] as int ro b2 = @data[p + 2] as int ro b3 = @data[p + 3] as int ro b4 = @data[p + 4] as int ro b5 = @data[p + 5] as int ro b6 = @data[p + 6] as int ro b7 = @data[p + 7] as int @pos = p + 8 Ok((b0 | (b1 << 8) | (b2 << 16) | (b3 << 24) | (b4 << 32) | (b5 << 40) | (b6 << 48) | (b7 << 56)) as i64) } // i64 big-endian. export fn ReadBuffer mut @read_i64_be() -> Result[i64, ReadBufferError] { if @data.len() - @pos < 8 { return Err(UnexpectedEnd { wanted: 8, available: @data.len() - @pos }) } ro p = @pos ro b0 = @data[p] as int ro b1 = @data[p + 1] as int ro b2 = @data[p + 2] as int ro b3 = @data[p + 3] as int ro b4 = @data[p + 4] as int ro b5 = @data[p + 5] as int ro b6 = @data[p + 6] as int ro b7 = @data[p + 7] as int @pos = p + 8 Ok(((b0 << 56) | (b1 << 48) | (b2 << 40) | (b3 << 32) | (b4 << 24) | (b5 << 16) | (b6 << 8) | b7) as i64) } // ── Float (IEEE 754) ── // // Все float reads делегируют в unsigned int того же размера + f{32,64}.from_bits. // f32 little-endian IEEE 754. export fn ReadBuffer mut @read_f32_le() -> Result[f32, ReadBufferError] => match @read_u32_le() { Ok(u) => Ok(f32.from_bits(u)) Err(e) => Err(e) } // f32 big-endian IEEE 754. export fn ReadBuffer mut @read_f32_be() -> Result[f32, ReadBufferError] => match @read_u32_be() { Ok(u) => Ok(f32.from_bits(u)) Err(e) => Err(e) } // f64 little-endian IEEE 754. export fn ReadBuffer mut @read_f64_le() -> Result[f64, ReadBufferError] => match @read_u64_le() { Ok(u) => Ok(f64.from_bits(u)) Err(e) => Err(e) } // f64 big-endian IEEE 754. export fn ReadBuffer mut @read_f64_be() -> Result[f64, ReadBufferError] => match @read_u64_be() { Ok(u) => Ok(f64.from_bits(u)) Err(e) => Err(e) } // ─── UTF-8 codepoint reads ─── // // `read_char` / `read_str` — same UTF-8 walk pattern что // _nova_rb_decode_utf8_one в C runtime (read_buffer.h:376-396). // // Возможные ошибки: // UnexpectedEnd — буфер пуст / неполный sequence на хвосте // (InvalidUtf8 — bootstrap ограничение: ReadBufferError имеет только // UnexpectedEnd variant; invalid UTF-8 трактуется как UnexpectedEnd // per существующая семантика test fixtures). // Helper: decode один UTF-8 codepoint, не advance'ит pos. // Returns Result[(cp, consumed_bytes), ReadBufferError]. fn ReadBuffer @decode_utf8_at(p int) -> Result[(int, int), ReadBufferError] { ro avail = @data.len() - p if avail <= 0 { return Err(UnexpectedEnd { wanted: 1, available: 0 }) } ro b0 = @data[p] as int if b0 < 0x80 { return Ok((b0, 1)) } // [M-router-handler-mut-capture-escape-soundness] §2 field-launder // энфорс (срочный пакет звучности, 2026-08-01) flagged `tup.0`/`tup.1` // below: the if-expr tuple's element type doesn't resolve through // `infer_expr_type` (no arm for tuple-typed if-branches), so the // checker's stack-value predicate saw `None` and — combined with `tup` // being `ro`-bound (L1) — conservatively rejected the read as // "not provably fully-stack". The tuple genuinely IS `(int, int)` // (fully-stack); `tup` binding itself is never reassigned, so `mut` // here is a no-op besides opting `tup` out of the L1-ro-source check // (mut-bound locals are never added to `ro_binding_names`). mut tup = if (b0 & 0xE0) == 0xC0 { (2, b0 & 0x1F) } else if (b0 & 0xF0) == 0xE0 { (3, b0 & 0x0F) } else if (b0 & 0xF8) == 0xF0 { (4, b0 & 0x07) } else { // Invalid UTF-8 leader byte — bootstrap: report как UnexpectedEnd. return Err(UnexpectedEnd { wanted: 1, available: avail }) } ro need = tup.0 mut cp = tup.1 if avail < need { return Err(UnexpectedEnd { wanted: need, available: avail }) } for i in 1..need { ro bi = @data[p + i] as int if (bi & 0xC0) != 0x80 { // Invalid continuation byte — bootstrap: report как UnexpectedEnd. return Err(UnexpectedEnd { wanted: need, available: i }) } cp = (cp << 6) | (bi & 0x3F) } Ok((cp, need)) } // Один codepoint (UTF-8 1-4 байта). Result-форма. // Plan 34 Ф.5.2 / D54: `int as char` запрещён; используем n.to_char(). // UTF-8 decoder уже валидирует range — from.Err() unreachable, но // нужно для well-typedness; surface как UnexpectedEnd для consistency. export fn ReadBuffer mut @read_char() -> Result[char, ReadBufferError] => match @decode_utf8_at(@pos) { Ok(tup) => match (tup.0).to_char() { Ok(c) => { @pos += tup.1 Ok(c) } Err(_) => Err(UnexpectedEnd { wanted: tup.1, available: 0 }) } Err(e) => Err(e) } // n codepoint'ов как str. Result-форма (Ok(str) или Err(UnexpectedEnd)). export fn ReadBuffer mut @read_str(n int) -> Result[str, ReadBufferError] { if n < 0 { return Err(UnexpectedEnd { wanted: 0, available: @data.len() - @pos }) } ro start = @pos mut walked = start for i in 0..n { match @decode_utf8_at(walked) { Ok(tup) => { walked += tup.1 } Err(e) => return Err(e) } } // Copy bytes [start..walked] в свежий str. Слайс — zero-copy view, // from_bytes_unchecked делает свою копию. ro bytes_slice = @data[start..walked] @pos = walked Ok(unsafe { bytes_slice.to_str_unchecked() }) }