/
nv-lang
/
nova
Обзор
Документация
Войти
/
nv-lang
/
nova
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
main
std/src/prelude/embed.nv
149 строк
8 KB
Evgeniy Golovin
fix(221.1): №254 — bound-check + specificity для Next[T]/Iter[I], Iter-делегаты, T-binding, разворот 16 обходов
03 авг 2026, 14:37
03 авг 2026, 14:37
ef341bd
Код
Авторство
О чём код?
// std/prelude/embed.nv — file-based source of truth для prelude-типа // `EmbeddedDir`/`EmbeddedEntry`. Plan 210 (D412-амендмент): `embed_dir("dir")` // — компайл-тайм интринсик, вшивающий ВСЮ папку (рекурсивно) в бинарь. // // `embed_dir("dir")` резолвер (`compiler-codegen/src/embed_resolve.rs`, // `try_replace_embed_dir`) переписывает вызов В КОМПАЙЛ-ТАЙМ в // `EmbeddedDir.new([EmbeddedEntry{path:"…", data: x"…"}, …])` (отсортировано // по path). Оба типа обязаны быть prelude-видимы — синтезированный код // ссылается на них по имени (см. std/prelude.nv re-export). // // Payload'ы (`data`) материализуются через существующую `HexBlobLit`-арм // (`emit_c.rs` 28579) — zero-copy вид над `static const uint8_t // nova_blob_*[]` (.rodata), БЕЗ копии. Эта декларация НЕ меняет emit_c — // `EmbeddedDir.new`/`@get`/`@paths`/`@len`/`@has`/`@entries` — обычные // Nova-методы (static dispatch), типы выводятся штатно. // // Plan 210 Ф.7.4 (Go-паритет+, 2026-07-17, D412-амендмент): `@merge` — // чистая Nova-body функция (0 правок компилятора), сливающая два // EmbeddedDir в один (отсортированная склейка, panic на дубль пути). // `glob:`/`hidden:` (Ф.7.1/Ф.7.3) и `embed_str` (Ф.7.2) — резолвер-стороннее // расширение (`embed_resolve.rs`), не касается ЭТОГО файла. module prelude.embed /// One embedded record: a relative POSIX path + file bytes (a zero-copy /// view over static rodata). Constructed ONLY by the compiler /// (`embed_dir`) — carries no invariants by itself, the field is public. #stable(since = "0.1") export type EmbeddedEntry { path str // относительный POSIX-путь от embed-корня, case-sensitive data []u8 // содержимое файла, нулевая копия (вид над .rodata) } /// Immutable embedded folder: a path→bytes map. Entries are sorted by /// `path` (the invariant is guarded by `EmbeddedDir.new` + the priv field — /// D412 amendment, Plan 210). Zero copy of the payloads. #stable(since = "0.1") export type EmbeddedDir { priv entries []EmbeddedEntry // ОТСОРТИРОВАНЫ по path (бинарный поиск); priv (D281) — // извне только через EmbeddedDir.new → инвариант ненарушим } /// The only public constructor (the same one `embed_dir` synthesizes). /// Convention "constructors = Type.new" (precedent `Vec.new`). /// Requires sorting by `path` (UTF-8 bytewise == the order of `str.compare`); /// verify in O(N), violation → panic (an honest error, not a silent None in get). /// Stores a DEFENSIVE shallow copy of the input (loop-push of the same /// pointers to records, NOT `.clone()` — that needs a `T Clone` bound, /// unneeded for EmbeddedEntry) — post-construction mutation of the source /// vector cannot break the invariant (records are immutable; fields have no `mut`). /// Publicness is INTENTIONAL: building your OWN (non-embedded) catalog is /// legal in tests/mocks — the invariant is still guarded by verify. export fn EmbeddedDir.new(entries []EmbeddedEntry) -> Self { mut i = 1 while i < entries.len() { if entries[i - 1].path.compare(entries[i].path) >= 0 { panic("EmbeddedDir.new: entries must be sorted by path, unique") } i += 1 } mut own = entries.collect() // защитная мелкая копия (алиас-защита) Self { entries: own } } /// Number of embedded files. export fn EmbeddedDir @len() -> int => @entries.len() /// All embedded paths (in deterministic sorted order). export fn EmbeddedDir @paths() -> []str { mut ps []str = [] for e in @entries { ps.push(e.path) } ps } /// Whether a file exists at the path. export fn EmbeddedDir @has(path str) -> bool => @get(path).is_some() /// Iteration of (path, data) pairs without a double lookup — an explicit /// property-read of the priv field (precedent `Vec @ptr()`). Returns `ro` /// — a read-only view (L2, precedent `str @bytes() -> ro []u8`): mutating /// the result = compile error → the invariant cannot leak via the output alias. export fn EmbeddedDir @entries() -> ro []EmbeddedEntry => @entries /// File bytes at the path, `None` if absent. Binary search over the sorted /// entries (O(log N)); relies on the sorted invariant. The key is the exact /// byte form: no leading `./`, `..` is not normalized — `get("./app.js")` /// honestly gives None. The data is a view over .rodata: do NOT mutate /// (see [M-d412-blob-view-mut-write], inherited from D412); for mutation — /// `.clone()`. export fn EmbeddedDir @get(path str) -> Option[[]u8] { mut lo = 0 mut hi = @entries.len() while lo < hi { ro mid = lo + (hi - lo) / 2 ro e = @entries[mid] // одно чтение на итерацию ro c = e.path.compare(path) // str.compare (D178), <0/0/>0 if c == 0 { return Some(e.data) } if c < 0 { lo = mid + 1 } else { hi = mid } } None } /// Plan 210 Ф.7.4 (Go parity+, 2026-07-17): merge `self` with `other` into /// a new `EmbeddedDir` — a sorted splice of two already-sorted tables /// (O(N+M) merge, like a mergesort step), NOT concat + re-sort. /// A matching path in BOTH inputs — `panic` (the same contract as /// `EmbeddedDir.new` on an unsorted/duplicated input — a conflict of two /// embedded trees with the same relative path is a programmer error, not a /// runtime "take any version" case). Typical case: /// `embed_dir("frontend").merge(embed_dir("generated_assets"))` — assemble /// one `EmbeddedDir` from SEVERAL embedding directories. export fn EmbeddedDir @merge(other EmbeddedDir) -> EmbeddedDir { ro a = @entries ro b = other.entries() mut out []EmbeddedEntry = [] mut i = 0 mut j = 0 while i < a.len() && j < b.len() { ro c = a[i].path.compare(b[j].path) if c < 0 { // Merge-interleave (mergesort-шаг), НЕ contiguous copy — false- // positive для W_MANUAL_SLICE_COPY (эвристика видит `push(x[i])` // в цикле, не отличая alternating merge от straight drain: какой // из двух источников следующий определяет сравнение путей, не // порядок исходного массива). Обход (прецедент d145 — вынести // индексацию в локаль): `av` рвёт синтаксический матч // `push(<obj>[<ident>])`, оставляя семантику неизменной. ro av = a[i] out.push(av) i += 1 } else if c > 0 { ro bv = b[j] out.push(bv) j += 1 } else { panic("EmbeddedDir.merge: duplicate path `${a[i].path}` present in both directories") } } // Contiguous drain остатка (один из входов исчерпан) — канон // W_MANUAL_SLICE_COPY: `[]T`-вид среза (D262) + `.append`, не поэлементный // цикл (nv-coding-style §18а). if i < a.len() { out.append(a[i..]) } if j < b.len() { out.append(b[j..]) } EmbeddedDir.new(out) }