/
nv-lang
/
nova
Обзор
Документация
Войти
/
nv-lang
/
nova
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
main
std/src/runtime/string/parse.nv
241 строка
10 KB
Evgeniy Golovin
fix(checker): №253 — ok_or(bare-variant)? Try loses context, misresolves
01 авг 2026, 21:47
01 авг 2026, 21:47
c3e3251
Код
Авторство
О чём код?
// Co-equal file of module `runtime.string` (folder = one module). // Role: primitive parse engines (private) + `str @to_*` conversion surface. // // `str` Nova-body methods. #no_prelude breaks the prelude->string->prelude // import cycle. #no_prelude module runtime.string import std.prelude.core.{Option, Some, None, Result, Ok, Err} /// Error variants for the integer parse engines. Exported so callers can match on them. export type ParseIntError enum Empty | InvalidDigit | Overflow | InvalidRadix // Conversions live as METHODS ON THE SOURCE (`s.to_int()`, mirroring // `s.to_str()`/`to_str_lossy()` elsewhere), not static constructors on the // target type. The digit-scanning logic itself is a PRIVATE engine (no // `export`) reused by every `to_*` wrapper below — one engine per // signedness domain, not duplicated per width. Radix stays available as an // optional keyword arg (default 10) so hex/octal/binary parsing // (`s.to_int(radix: 16)`) keeps working without a second public name. /// Signed decimal/radix-N engine (`int`/i64 domain, two's-complement 64-bit /// range). `radix` valid range: 2..=36. Empty / `-`/`+` without digits → /// `Err(Empty)`. Invalid digit → `Err(InvalidDigit)`. i64 overflow → /// `Err(Overflow)`. radix out of range → `Err(InvalidRadix)`. /// PRIVATE — not part of the public surface; reused /// by `@to_int`/`@to_i64` below. fn str @parse_int_core(radix int) -> Result[int, ParseIntError] { if radix < 2 || radix > 36 { return Err(InvalidRadix) } ro bytes = @bytes() ro n = @byte_len() if n == 0 { return Err(Empty) } mut neg = false mut start = 0 if bytes[0] == '-' { neg = true start = 1 } else if bytes[0] == '+' { start = 1 } if start >= n { return Err(Empty) } mut acc = 0 for j in start..n { ro c = bytes[j] as int mut d = -1 if c >= '0' && c <= '9' { d = c - '0' } else if c >= 'a' && c <= 'z' { d = c - 'a' + 10 } else if c >= 'A' && c <= 'Z' { d = c - 'A' + 10 } if d < 0 || d >= radix { return Err(InvalidDigit) } if acc > (9223372036854775807 - d) / radix { return Err(Overflow) } acc = acc * radix + d } Ok(if neg { -acc } else { acc }) } /// Unsigned decimal/radix-N engine (`uint`/u64 domain, full 64-bit unsigned /// range). Same contract as `@parse_int_core` except there is no sign: a /// leading `-` is not special-cased — it simply fails the digit scan at /// position 0 and falls out through the generic `Err(InvalidDigit)` path /// ("`-` ⇒ InvalidDigit"; no dedicated branch needed). PRIVATE — /// reused by `@to_u64`/`@to_u32`/`@to_u8` below. fn str @parse_uint_core(radix int) -> Result[uint, ParseIntError] { if radix < 2 || radix > 36 { return Err(InvalidRadix) } ro bytes = @bytes() ro n = @byte_len() if n == 0 { return Err(Empty) } mut start = 0 if bytes[0] == '+' { start = 1 } if start >= n { return Err(Empty) } mut acc uint = 0 ro uradix = radix as uint for j in start..n { ro c = bytes[j] as int mut d = -1 if c >= '0' && c <= '9' { d = c - '0' } else if c >= 'a' && c <= 'z' { d = c - 'a' + 10 } else if c >= 'A' && c <= 'Z' { d = c - 'A' + 10 } if d < 0 || d >= radix { return Err(InvalidDigit) } ro ud = d as uint if acc > (uint.MAX - ud) / uradix { return Err(Overflow) } acc = acc * uradix + ud } Ok(acc) } /// Error variants for `str.to_f64()`. Exported so callers can match on them. export type ParseFloatError enum Empty | Invalid // Plain FFI, no compiler name-knowledge: `str_parse_f64` is a // regular `extern "C" fn`, out-param convention (bool ok + `*out` // value). The compiler knows NOTHING about float parsing; it just lowers // an ordinary FFI call. extern "C" fn str_parse_f64(s str, out *mut f64) -> bool /// Strict parse — delegates to `str_parse_f64` (conv.h, `strtod`-based; /// leading whitespace, `+`/`-`, decimal, exponent, `"nan"`/`"inf"` literals). /// Empty string → `Err(Empty)` (checked here so it's distinguishable from /// other malformed syntax, which `strtod` cannot tell apart on its own — /// reported as `Err(Invalid)`). #stable(since = "0.1") export fn str @to_f64() -> Result[f64, ParseFloatError] { if @is_empty() { return Err(Empty) } mut v = 0.0 if unsafe { str_parse_f64(@, &v) } { Ok(v) } else { Err(Invalid) } } // ── `str @to_bool()` ─ // // Error variants for `str.to_bool()`. Exported so callers can match on them. export type ParseBoolError enum Empty | Invalid /// Strict `bool` parse — lowercase-only `"true"`/`"false"`, mirrors Rust /// `str::parse::<bool>` (no case-insensitive fallback, no `"1"`/`"0"`/ /// `"yes"`/`"no"` aliases). Empty string → `Err(Empty)` — same /// distinguishable-from-generic-garbage split as `@to_f64`/`@to_int` above; /// anything else that isn't exactly `"true"` or `"false"` → `Err(Invalid)` /// (mirrors `@to_f64`'s `Invalid` for "well-formed-but-not-this" garbage). #stable(since = "0.1") export fn str @to_bool() -> Result[bool, ParseBoolError] { if @is_empty() { return Err(Empty) } if @ == "true" { return Ok(true) } if @ == "false" { return Ok(false) } Err(Invalid) } // ── `str @to_char()` — exactly one codepoint ── // // Error variants for `str.to_char()`. Exported so callers can match on them. // Deliberately NOT `CharFromError` (std/runtime/char.nv, `(cp int).to_char()` // domain) — that error means "int value outside the Unicode scalar range / // surrogate", which cannot occur here (a `str`'s bytes are always valid // UTF-8 by construction — every decoded codepoint is already a legal scalar // value). The only failure modes for str→char are "no codepoints" and "more // than one" — a different domain, so a sibling type (not a reused double; // error types are not reused across domains). Variant split mirrors Rust's // `core::char::ParseCharError` (`EmptyString`/`TooManyChars`) renamed to // this module's `Empty`/`...` convention (`ParseIntError`/`ParseFloatError` // above both use `Empty` for the same "nothing there" case). export type ParseCharError enum Empty | TooManyChars /// Parses exactly one Unicode codepoint — not byte (`"é".to_char()` /// succeeds even though `é` is 2 UTF-8 bytes; `"🎈".to_char()` succeeds as /// one 4-byte codepoint). Empty string → `Err(Empty)`. More than one /// codepoint → `Err(TooManyChars)`. #stable(since = "0.1") export fn str @to_char() -> Result[char, ParseCharError] { mut it = @chars() ro c = it.next().ok_or(Empty)? match it.next() { None => Ok(c) Some(_) => Err(TooManyChars) } } // `str @to_*` integer conversion surface — the full SignedInts/UnsignedInts // set: `to_int`, `to_i64`, `to_u64`, `to_u32`, `to_u8`, `to_i8`, `to_i16`, // `to_i32`, `to_u16`, `to_uint`. Per-method overloads on `str` (a // type-set-bounded generic whose receiver is the type-set parameter in a // path call hits a codegen gap, so the per-method form is used instead). // // The narrow-width wrappers range-check the wide engine result // (`int`/`uint` domain) before narrowing. `to_int`/`to_i64`/`to_u64`/ // `to_uint` need no post-check — the engine's own overflow guard already // covers their whole range (`uint` is the same width as the // `@parse_uint_core` engine itself, no narrowing at all). /// Decimal (default) or radix-N (`radix: 2..=36`) parse into `int`. #stable(since = "0.1") export fn str @to_int(radix int = 10) -> Result[int, ParseIntError] => @parse_int_core(radix) /// Decimal (default) or radix-N parse into `i64`. Same 64-bit range as /// `int` on this platform — no range-check needed beyond the engine's own. #stable(since = "0.1") export fn str @to_i64(radix int = 10) -> Result[i64, ParseIntError] => @parse_int_core(radix).map(|v| v as i64) /// Decimal (default) or radix-N parse into `u64`. Same range as `uint` on /// this platform — no range-check needed beyond the engine's own. #stable(since = "0.1") export fn str @to_u64(radix int = 10) -> Result[u64, ParseIntError] => @parse_uint_core(radix).map(|v| v as u64) /// Decimal (default) or radix-N parse into `u32`, range-checked (unsigned: /// a leading `-` is `Err(InvalidDigit)`, not negation). #stable(since = "0.1") export fn str @to_u32(radix int = 10) -> Result[u32, ParseIntError] { ro v = @parse_uint_core(radix)? if v > (u32.MAX as uint) { return Err(Overflow) } Ok(v as u32) } /// Decimal (default) or radix-N parse into `u8`, range-checked. #stable(since = "0.1") export fn str @to_u8(radix int = 10) -> Result[u8, ParseIntError] { ro v = @parse_uint_core(radix)? if v > (u8.MAX as uint) { return Err(Overflow) } Ok(v as u8) } // Остаток SignedInts/UnsignedInts-набора. Тот же движок // (`@parse_int_core`/`@parse_uint_core`), тот же range-check-после-разбора // паттерн, что `to_u32`/`to_u8`. /// Decimal (default) or radix-N parse into `i8`, range-checked. #stable(since = "0.1") export fn str @to_i8(radix int = 10) -> Result[i8, ParseIntError] { ro v = @parse_int_core(radix)? if v > (i8.MAX as int) || v < (i8.MIN as int) { return Err(Overflow) } Ok(v as i8) } /// Decimal (default) or radix-N parse into `i16`, range-checked. #stable(since = "0.1") export fn str @to_i16(radix int = 10) -> Result[i16, ParseIntError] { ro v = @parse_int_core(radix)? if v > (i16.MAX as int) || v < (i16.MIN as int) { return Err(Overflow) } Ok(v as i16) } /// Decimal (default) or radix-N parse into `i32`, range-checked. #stable(since = "0.1") export fn str @to_i32(radix int = 10) -> Result[i32, ParseIntError] { ro v = @parse_int_core(radix)? if v > (i32.MAX as int) || v < (i32.MIN as int) { return Err(Overflow) } Ok(v as i32) } /// Decimal (default) or radix-N parse into `u16`, range-checked (unsigned: /// a leading `-` is `Err(InvalidDigit)`, not negation). #stable(since = "0.1") export fn str @to_u16(radix int = 10) -> Result[u16, ParseIntError] { ro v = @parse_uint_core(radix)? if v > (u16.MAX as uint) { return Err(Overflow) } Ok(v as u16) } /// Decimal (default) or radix-N parse into `uint`. Same range as the /// `@parse_uint_core` engine itself (`uint` IS that engine's domain) — no /// range-check needed, mirrors `@to_int` above. #stable(since = "0.1") export fn str @to_uint(radix int = 10) -> Result[uint, ParseIntError] => @parse_uint_core(radix)