/
nv-lang
/
nova
Обзор
Документация
Войти
/
nv-lang
/
nova
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
main
std/src/unicode/case.nv
185 строк
8 KB
Evgeniy Golovin
std(naming): collections/hashmap → hash_map — единственный нарушитель snake_case-правила модулей
06 авг 2026, 12:48
06 авг 2026, 12:48
0b0a260
Код
Авторство
О чём код?
// Plan 152.4.4 (D253): Unicode case folding + case mapping (locale-independent). // // Opt-in `std.unicode` peer (NOT prelude — table cost paid only by importers). // Co-equal with `case_data.nv` (generated tables) in the `std.unicode` // folder-module, so the `*_DATA` constants are visible here directly. Reuses peer // helpers: parse_cp_keyed_table / parse_cp_list / str_to_cps / cps_to_str // (cp_utils.nv), parse_flat / in_ranges2 (ranges.nv). // // fold_case(s) — full case folding (CaseFolding C+F), for caseless matching. // to_uppercase(s) — full Unicode uppercase (multi-codepoint: ß→SS, fi→FI, …). // to_lowercase(s) — full Unicode lowercase incl. the Final_Sigma context rule. // // "Locale-independent" (D253): SpecialCasing language entries (tr/az/lt) and // Turkic folding (status T) are excluded by the generator. The only context rule // in the language-neutral subset — Final_Sigma — is applied here. Title-casing // needs UAX #29 word boundaries ([M-152-word-boundaries]) and is deferred. // // Note: case folding is NOT normalization. For full caseless matching of // canonically-equivalent text, normalize first (UAX #15) then fold. module std.unicode import std.collections.hash_map.{HashMap} import std.collections.vec.{Vec} // ── lazy-static case tables (built once on first touch) ── ro fold_map HashMap[u32, str] = parse_cp_keyed_table(FOLD_DATA) ro lower_map HashMap[u32, str] = parse_cp_keyed_table(LOWER_DATA) ro upper_map HashMap[u32, str] = parse_cp_keyed_table(UPPER_DATA) ro title_map HashMap[u32, str] = parse_cp_keyed_table(TITLE_DATA) ro cased_flat []u32 = parse_flat(CASED_DATA) ro caseign_flat []u32 = parse_flat(CASE_IGNORABLE_DATA) fn is_cased(cp u32) -> bool => in_ranges2(cased_flat, cp) fn is_case_ignorable(cp u32) -> bool => in_ranges2(caseign_flat, cp) // Greek sigma — the one context-dependent lowercase in the no-locale subset. const CAP_SIGMA u32 = 0x3A3 const SMALL_SIGMA u32 = 0x3C3 const SMALL_FINAL_SIGMA u32 = 0x3C2 // Single-codepoint sequence (identity mapping). fn single(cp u32) -> []u32 { mut out []u32 = []u32.new(cap: 1) out.push(cp) } // Per-codepoint table lookup → mapped sequence, or [cp] when unmapped. Used by // `to_lowercase` here and by `to_titlecase` (words.nv); the `Some` arm decodes // the packed mapping, the `None` arm is the identity codepoint. fn lower_one(cp u32) -> []u32 { match lower_map.get(cp) { Some(seq) => parse_cp_list(seq), None => single(cp) } } // Per-codepoint titlecase mapping (used by `to_titlecase` in words.nv for the // first cased char of each word; rest of the word goes through lower_one). fn title_one(cp u32) -> []u32 { match title_map.get(cp) { Some(seq) => parse_cp_list(seq), None => single(cp) } } // Per-codepoint uppercase mapping → mapped sequence, or [cp] when unmapped // (symmetric with lower_one). Reused by char_to_uppercase (category.nv) for the // code-point-level case API. Multi-codepoint expansions apply (ß → [S, S]). fn upper_one(cp u32) -> []u32 { match upper_map.get(cp) { Some(seq) => parse_cp_list(seq), None => single(cp) } } /// Full case folding (UCD CaseFolding status C+F) — for caseless matching. /// Idempotent on its own output. Multi-codepoint folds apply (ß → "ss"). export fn fold_case(s str) -> str { mut out []u32 = []u32.new(cap: s.byte_len()) for c in s.chars() { // `None` arm is a fluent `out.push(..)` tail (typed `Vec*`); the `Some` // arm yields unit — the `match` coerces to unit in statement position // ([M-codegen-fluent-tail-if-unify], now fixed in codegen). match fold_map.get(c as u32) { Some(seq) => { for d in parse_cp_list(seq) { out.push(d) } } None => out.push(c as u32) } } cps_to_str(out) } /// Full Unicode uppercase (locale-independent). Multi-codepoint expansions apply /// (ß → "SS", fi → "FI", ff → "FF", ʼn → "ʼN", …). No Turkic/Lithuanian tailoring. export fn to_uppercase(s str) -> str { mut out []u32 = []u32.new(cap: s.byte_len()) for c in s.chars() { match upper_map.get(c as u32) { Some(seq) => { for d in parse_cp_list(seq) { out.push(d) } } None => out.push(c as u32) } } cps_to_str(out) } /// Full Unicode lowercase (locale-independent), including the Final_Sigma context /// rule (Greek Σ → ς word-finally, σ otherwise). İ (U+0130) → "i̇" via the /// unconditional mapping; no Turkic locale tailoring. export fn to_lowercase(s str) -> str { ro cps = str_to_cps(s) ro n = cps.len() mut out []u32 = []u32.new(cap: n) for i in 0..n { ro cp = cps[i] if cp == CAP_SIGMA { // Σ → ς word-finally, σ otherwise — fluent `out.push(..)` tail in the // `then` branch, unit-yielding `else` (the if coerces to unit). out.push(if is_final_sigma(cps, i) { SMALL_FINAL_SIGMA } else { SMALL_SIGMA }) } else { match lower_map.get(cp) { Some(seq) => { for d in parse_cp_list(seq) { out.push(d) } } None => out.push(cp) } } } cps_to_str(out) } // ── str case methods (Plan 91.18 Ф.5) ── // Available opt-in under `import std.unicode` (different from ASCII runtime.string variants). /// Full case folding as a str method (for caseless matching). export fn str @fold_case() -> str => fold_case(@) /// Full Unicode uppercase as a str method (multi-codepoint: ß→SS, fi→FI). export fn str @to_upper() -> str => to_uppercase(@) /// Full Unicode lowercase as a str method (includes Final_Sigma context rule). export fn str @to_lower() -> str => to_lowercase(@) /// Unicode title-casing as a str method (requires word-boundary segmentation). export fn str @to_title() -> str => to_titlecase(@) /// Unicode full-case-fold equality for `str` (opt-in, requires `import std.unicode`). /// Handles multi-codepoint folds: "STRASSE".eq_ignore_case("straße") == true. /// For ASCII-only input prefer `eq_ignore_ascii_case` (no table overhead). export fn str @eq_ignore_case(other str) -> bool => fold_case(@) == fold_case(other) /// Unicode full-case-fold equality for `char` (opt-in, requires `import std.unicode`). /// Converts each codepoint to a one-char str, then delegates to `fold_case`. /// For ASCII-only input prefer `char.eq_ignore_ascii_case` (no table overhead). export fn char @eq_ignore_case(other char) -> bool => fold_case(@to_str()) == fold_case(other.to_str()) // Final_Sigma (UAX / SpecialCasing): the Σ at index `i` lowercases to ς iff it is // preceded by (a cased char, then zero+ case-ignorable) AND NOT followed by // (zero+ case-ignorable, then a cased char). fn is_final_sigma(cps []u32, i int) -> bool { // before: a cased char must precede (skipping case-ignorable runs). mut j = i - 1 mut before_cased = false mut scan = true while scan && j >= 0 { ro c = cps[j] if is_case_ignorable(c) { j -= 1 } else if is_cased(c) { before_cased = true scan = false } else { scan = false } } if before_cased { // after: a cased char must NOT follow (skipping case-ignorable runs). ro n = cps.len() mut k = i + 1 mut after_cased = false scan = true while scan && k < n { ro c = cps[k] if is_case_ignorable(c) { k += 1 } else if is_cased(c) { after_cased = true scan = false } else { scan = false } } !after_cased } else { false } }