/
nv-lang
/
nova
Обзор
Документация
Войти
/
nv-lang
/
nova
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
main
std/src/encoding/serde/serde.nv
363 строки
19 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/encoding/serde/serde.nv — format-agnostic Serialize/Deserialize ядро. // // Typed serde-слой: record/скаляр/контейнер <-> wire-format через // format-agnostic data-model. Компилятор синтезирует `@serialize`/ // `.deserialize` для record'ов с `#impl(Serialize + Deserialize)` (та же // auto-derive-машина, что `Equal`/`Clone`/`Hash`/`Debug`) — тела здесь // НЕ пишутся руками для user-record'ов; это ФАЙЛ-ядро протоколов + скаляр/ // контейнер-conformance (как Debug для Option/Result в protocols.nv). // // Backend (JSON) — в peer-файле json.nv. PURE codec: без I/O-эффекта. // // Decisions: // - data-model (12 кейсов) + протоколы Serialize/Deserialize/ // Serializer/Deserializer, generic-bound нотейшн `[S Serializer]`. // - record-auto-derive-контракт (компилерный синтез, opt-in `#impl`). // - data-model <-> синтез mapping (int=i64-wide widen/range-check). // - soundness — numeric точное-целое-check, depth-guard, str-map-keys. // - Result-everywhere; SerError/DeError = `value`-record. module encoding.serde // ────────────────────────────────────────────────────────────────────────── // Ошибки. `value`-record — маленькие копируемые структуры, текут по значению // через Result-error-канал (тот же паттерн, что IoError). // ────────────────────────────────────────────────────────────────────────── /// Category of a serialization error (narrow — serialization rarely fails). /// OPEN kind → a wildcard arm is required on `match`. #unstable export type SerErrorKind enum | NonFiniteFloat // NaN/Inf в JSON (RFC 8259) | SerDepthLimit // serialize-сторона depth-guard | SerLossyInteger(str) // |v| > 2^53 — не lossless в f64-wire; симметрично DeError.LossyInteger (уник. имя — избегает коллизии вариантов) | SerCustom(str) | SerOther(str) /// Serialization error (D325 R5 — a single structural type, OPEN kind). #unstable export type SerError value { ro kind SerErrorKind, ro path str } /// Constructor; `path` is optional (defaults to ""). #unstable export fn SerError.new(kind SerErrorKind, path str = "") -> SerError => { kind, path } /// Human-readable description. #unstable export fn SerError @to_str() -> str { ro base = match @kind { NonFiniteFloat => "non-finite float not representable in JSON" SerDepthLimit => "serialize recursion depth limit exceeded" SerLossyInteger(v) => "integer ${v} is not losslessly representable in a JSON number" SerCustom(m) => m SerOther(m) => m } if @path.is_empty() { base } else { "at ${@path}: ${base}" } } /// Category of a deserialization error (rich — deser is the main error source). /// OPEN kind → a wildcard arm is required. #unstable export type DeErrorKind enum | UnexpectedType { expected str, found str } // ждали int, нашли string | MissingField(str) // required-поле отсутствует | UnknownField(str) // strict-by-default; opt-out #serde(allow_unknown) | UnknownVariant { name str, expected []str } // enum tag не совпал (sum-deser) | NoVariantMatched // untagged — ни один вариант | OutOfRange(str) // int не влез в i64/u64 | LossyInteger(str) // f64 не точное целое / |v|>=2^53 | DepthLimitExceeded // deser-сторона depth-guard | Syntax(str) // обёртка ParseJsonError (line/col в тексте) | Custom(str) | Other(str) /// Deserialization error: `kind` + a JSONPath-style `path` («$.users[2].email»). /// D325 R5 — a single structural type; a `value`-record (keystone). #unstable export type DeError value { ro kind DeErrorKind, ro path str } /// Constructor; `path` is optional (defaults to ""). #unstable export fn DeError.new(kind DeErrorKind, path str = "") -> DeError => { kind, path } /// Type-mismatch helper. #unstable export fn DeError.unexpected(expected str, found str) -> DeError => { kind: UnexpectedType { expected, found }, path: "" } /// Human-readable description with path/expected/found (serde+zod-class). #unstable export fn DeError @to_str() -> str { ro base = match @kind { UnexpectedType { expected, found } => "expected ${expected}, found ${found}" MissingField(f) => "missing required field `${f}`" UnknownField(f) => "unknown field `${f}`" UnknownVariant { name, expected } => "unknown enum variant `${name}` (expected one of ${expected.len()})" NoVariantMatched => "no enum variant matched (untagged)" OutOfRange(v) => "number ${v} out of range" LossyInteger(v) => "number ${v} is not a lossless integer" DepthLimitExceeded => "deserialize recursion depth limit exceeded" Syntax(m) => "syntax error: ${m}" Custom(m) => m Other(m) => m } if @path.is_empty() { base } else { "at ${@path}: ${base}" } } // ────────────────────────────────────────────────────────────────────────── // Data-model — format-agnostic ядро (12 кейсов). Абстрактный // словарь Serialize/Serializer: `bool int uint float str bytes option unit // seq map struct enum`. Протоколы НЕ строят промежуточное data-model-дерево // (push прямо в Serializer); контракт визит-методов фиксируют сигнатуры // Serializer/Deserializer ниже. Отдельным enum data-model НЕ объявляется — // имена вариантов (`Option`/`Struct`/`Map`/`Enum`/…) столкнулись бы с // встроенными типами; словарь живёт в сигнатурах методов, не в enum. // ────────────────────────────────────────────────────────────────────────── // ────────────────────────────────────────────────────────────────────────── // Serializer — push-протокол (backend-точка расширения). Единый // mutable stack-machine: составные обрамляются begin/key/end; backend владеет // стеком (format-agnostic). Каждый метод fallible (non-finite float). // ────────────────────────────────────────────────────────────────────────── #unstable export type Serializer protocol { // ── скаляры (терминалы) ── mut @serialize_bool(v bool) -> Result[(), SerError] mut @serialize_int(v int) -> Result[(), SerError] mut @serialize_uint(v u64) -> Result[(), SerError] mut @serialize_float(v f64) -> Result[(), SerError] mut @serialize_str(v str) -> Result[(), SerError] mut @serialize_bytes(v []u8) -> Result[(), SerError] // JSON: base64-строка mut @serialize_unit() -> Result[(), SerError] // () mut @serialize_none() -> Result[(), SerError] // Option::None -> null // ── составные (stack-framing) ── mut @begin_struct(name str, len int) -> Result[(), SerError] mut @struct_field(key str) -> Result[(), SerError] // key уже rename-resolved mut @end_struct() -> Result[(), SerError] mut @begin_seq(len int) -> Result[(), SerError] mut @end_seq() -> Result[(), SerError] mut @begin_map(len int) -> Result[(), SerError] mut @map_key(key str) -> Result[(), SerError] // ключ только str mut @end_map() -> Result[(), SerError] } // ────────────────────────────────────────────────────────────────────────── // Deserializer — driven-pull. Единый cursor; скаляры — прямой pull, // составные — под-cursor через `enter_*` (возвращает Self, позиционированный // на дочернем значении). Sub-deserializer'ы только ЧИТАЮТ → нет write-back, // поэтому владение тривиально (keyed-access модель, Swift KeyedDecodingContainer). // ────────────────────────────────────────────────────────────────────────── #unstable export type Deserializer protocol { // ── скаляры (прямой pull) ── mut @deser_bool() -> Result[bool, DeError] mut @deser_int() -> Result[int, DeError] // точное-целое-check mut @deser_uint() -> Result[u64, DeError] // negative/range-check mut @deser_float() -> Result[f64, DeError] mut @deser_str() -> Result[str, DeError] mut @deser_bytes() -> Result[[]u8, DeError] // JSON: base64-decode mut @is_null() -> Result[bool, DeError] mut @is_str() -> Result[bool, DeError] // externally-tagged sum: unit-variant = bare string // ── struct-доступ (под-cursor по ключу) ── mut @enter_field(key str) -> Result[Self, DeError] // MissingField если нет mut @enter_field_or_null(key str) -> Result[Self, DeError] // null-cursor если нет (Option-поля) // Точная presence-проверка (БЕЗ конфликта с JSON `null`, в отличие от // `enter_field_or_null`) — используется синтезатором для `default`/`alias` // -fallback-цепочки (has_field(name)? до enter_field(name), а не догадка // по null-курсору). mut @has_field(key str) -> Result[bool, DeError] // ── seq-доступ ── mut @seq_len() -> Result[int, DeError] mut @enter_index(i int) -> Result[Self, DeError] // ── map-доступ (ключи только str) ── mut @map_keys() -> Result[[]str, DeError] mut @enter_key(key str) -> Result[Self, DeError] } // ────────────────────────────────────────────────────────────────────────── // Serialize / Deserialize — контракт типа. `deserialize` статический. // ────────────────────────────────────────────────────────────────────────── /// Mirror push-contract. The compiler synthesizes it for a `#impl(Serialize)` record. #unstable export type Serialize protocol { @serialize[S Serializer](mut s S) -> Result[(), SerError] } /// Construction of a value from any format. The compiler synthesizes it for /// a `#impl(Deserialize)` record. #unstable export type Deserialize protocol { .deserialize[D Deserializer](mut d D) -> Result[Self, DeError] } // ────────────────────────────────────────────────────────────────────────── // Скаляр-conformance. Скаляры получают Serialize/Deserialize через `.nv`-методы // на примитивах (методы на примитивах легальны); синтез record'а зовёт их // единообразно (`@field.serialize(s)` / `T.deserialize(d)`). // ────────────────────────────────────────────────────────────────────────── fn bool @serialize[S Serializer](mut s S) -> Result[(), SerError] => s.serialize_bool(@) fn bool.deserialize[D Deserializer](mut d D) -> Result[bool, DeError] => d.deser_bool() fn int @serialize[S Serializer](mut s S) -> Result[(), SerError] => s.serialize_int(@) fn int.deserialize[D Deserializer](mut d D) -> Result[int, DeError] => d.deser_int() fn u64 @serialize[S Serializer](mut s S) -> Result[(), SerError] => s.serialize_uint(@) fn u64.deserialize[D Deserializer](mut d D) -> Result[u64, DeError] => d.deser_uint() fn f64 @serialize[S Serializer](mut s S) -> Result[(), SerError] => s.serialize_float(@) fn f64.deserialize[D Deserializer](mut d D) -> Result[f64, DeError] => d.deser_float() fn str @serialize[S Serializer](mut s S) -> Result[(), SerError] => s.serialize_str(@) fn str.deserialize[D Deserializer](mut d D) -> Result[str, DeError] => d.deser_str() // ── narrow numeric scalars (int i64-wide widen on ser, range-check on // deser). The record synthesizer emits the SER direction inline as a direct // `s.serialize_int/uint/float(@f as WIDE)?` (a primitive receiver does not // dispatch a user `@serialize`), and the DESER direction as a static // `T.deserialize(sub)?` with an explicit `ro f: T` annotation so the `?` // lowers to the narrow C type. deser_int/deser_uint already enforce the // exact-integer / non-negative wire invariants; the per-type // checks reject values that overflow the narrower target (`OutOfRange`) — // loss-free or typed, never silent. fn i8.deserialize[D Deserializer](mut d D) -> Result[i8, DeError] { ro n = d.deser_int()? if n < -128 || n > 127 { Err(DeError.new(OutOfRange(n.to_str()))) } else { Ok(n as i8) } } fn i16.deserialize[D Deserializer](mut d D) -> Result[i16, DeError] { ro n = d.deser_int()? if n < -32768 || n > 32767 { Err(DeError.new(OutOfRange(n.to_str()))) } else { Ok(n as i16) } } fn i32.deserialize[D Deserializer](mut d D) -> Result[i32, DeError] { ro n = d.deser_int()? if n < -2147483648 || n > 2147483647 { Err(DeError.new(OutOfRange(n.to_str()))) } else { Ok(n as i32) } } fn i64.deserialize[D Deserializer](mut d D) -> Result[i64, DeError] { ro n = d.deser_int()? Ok(n as i64) } fn u8.deserialize[D Deserializer](mut d D) -> Result[u8, DeError] { ro n = d.deser_uint()? if n > 255 { Err(DeError.new(OutOfRange(n.to_str()))) } else { Ok(n as u8) } } fn u16.deserialize[D Deserializer](mut d D) -> Result[u16, DeError] { ro n = d.deser_uint()? if n > 65535 { Err(DeError.new(OutOfRange(n.to_str()))) } else { Ok(n as u16) } } fn u32.deserialize[D Deserializer](mut d D) -> Result[u32, DeError] { ro n = d.deser_uint()? if n > 4294967295 { Err(DeError.new(OutOfRange(n.to_str()))) } else { Ok(n as u32) } } fn uint.deserialize[D Deserializer](mut d D) -> Result[uint, DeError] { ro n = d.deser_uint()? Ok(n as uint) } fn f32.deserialize[D Deserializer](mut d D) -> Result[f32, DeError] { ro n = d.deser_float()? Ok(n as f32) } // ────────────────────────────────────────────────────────────────────────── // Контейнер-conformance. Выразимо `.nv`-impl'ами (прецедент Debug для // Option/Result в protocols.nv), НЕ compiler-side mono: generic-receiver + // generic-static-dispatch `T.deserialize(d)` работают (проверено эмпирически). // ────────────────────────────────────────────────────────────────────────── /// Option[T]: Some(v) -> v; None -> serialize_none (JSON null). Deser: null -> None. #unstable fn Option[T Serialize] @serialize[S Serializer](mut s S) -> Result[(), SerError] { match @ { None => s.serialize_none() Some(v) => v.serialize(s) } } #unstable fn Option[T Deserialize].deserialize[D Deserializer](mut d D) -> Result[Option[T], DeError] { if d.is_null()? { Ok(None) } else { ro v = T.deserialize(d)? Ok(Some(v)) } } /// Option[T] deserialization as a FREE function. The built-in `Option` does /// not dispatch user static methods (`Option[str].deserialize` falls into the /// generic fallback), but a free-fn turbofish monomorphizes — the record /// synthesizer calls THIS for Option fields: `deser_option[IT](sub)`. /// null → None; otherwise Some(IT). #unstable export fn deser_option[T Deserialize, D Deserializer](mut d D) -> Result[Option[T], DeError] { if d.is_null()? { Ok(None) } else { ro v = T.deserialize(d)? Ok(Some(v)) } } /// []T → data-model Seq. Deser: length + a per-index sub-cursor. #unstable fn[T Serialize] []T @serialize[S Serializer](mut s S) -> Result[(), SerError] { s.begin_seq(@len())? for v in @ { v.serialize(s)? } s.end_seq() } #unstable fn[T Deserialize] []T.deserialize[D Deserializer](mut d D) -> Result[[]T, DeError] { ro n = d.seq_len()? mut out []T = [] for i in 0..n { mut sub = d.enter_index(i)? out.push(T.deserialize(sub)?) } Ok(out) } /// HashMap[str, V] → data-model Map (string-only keys, Q16). Deser: keys + per-key. #unstable fn HashMap[str, V Serialize] @serialize[S Serializer](mut s S) -> Result[(), SerError] { s.begin_map(@len())? mut keys = @keys().collect() keys.sort() // детерминированный вывод (golden-тесты) for k in keys { s.map_key(k)? match @get(k) { Some(v) => v.serialize(s)? None => {} } } s.end_map() } #unstable fn HashMap[str, V Deserialize].deserialize[D Deserializer](mut d D) -> Result[HashMap[str, V], DeError] { ro keys = d.map_keys()? mut out = HashMap[str, V].new() for k in keys { mut sub = d.enter_key(k)? out.insert(k, V.deserialize(sub)?) } Ok(out) }