/
nv-lang
/
nova
Обзор
Документация
Войти
/
nv-lang
/
nova
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
main
std/src/data/semver_range.nv
168 строк
5 KB
Evgeniy Golovin
merge: comment-hygiene-2 — чистка комментариев std (batch 1-20) + линт-свип 330→13
01 авг 2026, 18:13
01 авг 2026, 18:13
bb8b33d
Код
Авторство
О чём код?
// stdlib/semver_range.nv — semver constraint expressions. // // Парсит выражения версионных требований: // 1.2.3 — exact match // ^1.2.3 — compatible (>=1.2.3 <2.0.0) // ~1.2.3 — patch-level (>=1.2.3 <1.3.0) // >=1.0.0 — comparator // >1.0.0 <2.0.0 — and-список через space // 1.2.3 || 2.0.0 — or-список через || // * — any version // // Используется в D78 SAT-resolver для package dependencies. // // API: // s.to_version_req() -> Result[VersionReq, ParseRangeError] // req.matches(version Version) -> bool module data.semver_range // Plan 35 Ф.1: cross-file resolve. Version тип определён в semver. import std.data.semver /// One comparator in a version requirement (`>=1.2.3`, `<2.0.0`, `=1.0.0`). #stable(since = "0.1") export type Comparator enum | Eq(Version) | Gt(Version) | Ge(Version) | Lt(Version) | Le(Version) /// An AND-group of comparators: all must match (e.g. `>1.0.0 <2.0.0`). #stable(since = "0.1") export type ComparatorSet { comparators []Comparator } /// Version requirement: an OR-list of AND-groups. Empty sets + any_match=true → "*". /// /// # Examples /// ```nova /// let req = "^1.2.3".to_version_req()!! // >=1.2.3 <2.0.0 /// assert(req.matches("1.5.0".to_version()!!)) /// ``` #stable(since = "0.1") export type VersionReq { sets []ComparatorSet any_match bool } /// Errors when parsing a version requirement string. #stable(since = "0.1") export type ParseRangeError enum | EmptyInput | InvalidComparator { value str } | InvalidVersion { value str } /// Parse version requirement expression. /// /// Syntax: `1.2.3` (exact), `^1.2.3` (compatible), `~1.2.3` (patch), /// `>=1.0.0` (lower bound), `>1.0.0 <2.0.0` (AND), `1.0.0 || 2.0.0` (OR), /// `*` (any). /// /// The static `VersionReq.parse(s)` was retracted (nv-coding-style §1а, /// "fifth door") — conversion lives on the source (`s.to_version_req()`), /// like `s.to_version()` in semver.nv. #stable(since = "0.1") export fn str @to_version_req() -> Result[VersionReq, ParseRangeError] { ro trimmed = @trim_ascii() if trimmed.byte_len() == 0 { return Err(EmptyInput) } if trimmed == "*" { return Ok({ sets: [], any_match: true }) } // Split by || mut sets []ComparatorSet = [] ro or_groups = trimmed.split("||") for group in or_groups { ro comps = parse_comparator_set(group.trim_ascii())? sets.push({ comparators: comps }) } Ok({ sets, any_match: false }) } fn parse_comparator_set(s str) -> Result[[]Comparator, ParseRangeError] { mut out []Comparator = [] // Split by whitespace ro tokens = s.split(" ") for tok in tokens { ro t = tok.trim_ascii() if t.byte_len() == 0 { continue } ro comps = parse_one_comparator(t)? for c in comps { out.push(c) } } Ok(out) } // Returns []Comparator потому что ^1.2.3 → 2 comparators. fn parse_one_comparator(s str) -> Result[[]Comparator, ParseRangeError] { if s.starts_with("^") { // ^1.2.3 → >=1.2.3 <2.0.0 ro v = parse_version(s[1..])? ro upper = Version.new(v.major + 1, 0, 0) return Ok([Ge(v), Lt(upper)]) } if s.starts_with("~") { // ~1.2.3 → >=1.2.3 <1.3.0 ro v = parse_version(s[1..])? ro upper = Version.new(v.major, v.minor + 1, 0) return Ok([Ge(v), Lt(upper)]) } if s.starts_with(">=") { return Ok([Ge(parse_version(s[2..])?)]) } if s.starts_with("<=") { return Ok([Le(parse_version(s[2..])?)]) } if s.starts_with(">") { return Ok([Gt(parse_version(s[1..])?)]) } if s.starts_with("<") { return Ok([Lt(parse_version(s[1..])?)]) } if s.starts_with("=") { return Ok([Eq(parse_version(s[1..])?)]) } Ok([Eq(parse_version(s)?)]) } fn parse_version(s str) -> Result[Version, ParseRangeError] => // `to_version()` возвращает Result (D325) — конвертируем Err в наш // ParseRangeError честным match. match s.to_version() { Ok(v) => Ok(v) Err(_) => Err(InvalidVersion { value: s }) } // ────────────────────────────────────────────────────────────────────────── // Matching // ────────────────────────────────────────────────────────────────────────── /// True if `v` matches the requirement. Short-circuits on the first matching OR-set. #stable(since = "0.1") export fn VersionReq @matches(v Version) -> bool { if @any_match { return true } for set in @sets { if set_matches(set, v) { return true } } false } fn set_matches(set ComparatorSet, v Version) -> bool { for c in set.comparators { if !comparator_matches(c, v) { return false } } true } fn comparator_matches(c Comparator, v Version) -> bool => match c { Eq(req) => v.equal(req) Gt(req) => v > req Ge(req) => v >= req Lt(req) => v < req Le(req) => v <= req } // Тесты — см. peer-файл semver_range_test.nv (module data.semver_range_test).