/
nv-lang
/
nova
Обзор
Документация
Войти
/
nv-lang
/
nova
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
main
std/src/encoding/csv.nv
133 строки
5 KB
Evgeniy Golovin
docs(endocs): encoding — translate csv/hex/ini/toml /// docs to English
01 авг 2026, 04:06
01 авг 2026, 04:06
7761c5e
Код
Авторство
О чём код?
// stdlib/csv.nv — CSV parser per RFC 4180. // https://www.rfc-editor.org/rfc/rfc4180 module encoding.csv /// Errors while parsing CSV (unterminated quote, unexpected char). #stable(since = "0.1") export type ParseCsvError enum | UnterminatedQuote { line int } | UnexpectedChar { line int, col int, char char } /// Parse a CSV document into `[][]str` (records of fields). Supports quoted fields /// (`"..."`) with the escape `""` → `"`. #stable(since = "0.1") export fn Csv.parse(input str) Fail[ParseCsvError] -> [][]str { // [M-lint-findings-static-conversion] mut records [][]str = [] mut current_record []str = [] consume current_field = StringBuilder.new() mut in_quote = false mut line = 1 // CSV-поля — произвольный UTF-8 (не ASCII-only), а нужен рандомный // 1-symbol lookahead (`i+1`) — материализуем `[]char` ОДИН раз (push-loop, // не `.collect()` — крашит кодоген на CharsIter, Block 2 precedent) // вместо ретрактированного `input.chars().nth(i)` O(n) скана на каждой // итерации (D260-амендмент). mut ic []char = []char.new(cap: input.byte_len()) for c in input.chars() { ic.push(c) } mut i = 0 while i < ic.len() { ro c = ic[i] if in_quote { if c == '"' { // Lookahead: "" → escaped quote, иначе закрытие quote ro next = ic.get(i + 1) if next == Some('"') { current_field.append('"') i += 2 } else { in_quote = false i += 1 } } else { if c == '\n' { line += 1 } current_field.append(c) i += 1 } } else { if c == '"' { in_quote = true i += 1 } else if c == ',' { current_record.push(current_field.into_str()) consume current_field = StringBuilder.new() i += 1 } else if c == '\n' { current_record.push(current_field.into_str()) consume current_field = StringBuilder.new() records.push(current_record) current_record = [] line += 1 i += 1 } else if c == '\r' { // CRLF: пропускаем \r, обработка на \n i += 1 } else { current_field.append(c) i += 1 } } } if in_quote { ro _ = current_field.into_str() throw UnterminatedQuote { line } } // Финальная запись (если последняя строка без \n) ro tail = current_field.into_str() if tail.byte_len() > 0 || current_record.len() > 0 { current_record.push(tail) records.push(current_record) } records } // Сериализация [][]str → str. /// Encode `[][]str` back into a CSV string. Auto-quotes fields with `,`/`"`/`\n`. #stable(since = "0.1") export fn Csv.encode(records [][]str) -> str { consume buf = StringBuilder.new(cap: records.len() * 64) for record in records { mut first = true for field in record { if !first { buf.append(',') } first = false if needs_quoting(field) { buf.append('"') for c in field.chars() { if c == '"' { buf.append('"') buf.append('"') } else { buf.append(c) } } buf.append('"') } else { buf.append(field) } } buf.append('\n') } buf } // [M-closure-trailing-scalar-coercion-no-typecheck] follow-up (2026-07-10): // a LEADING `||` on a continuation line is NOT an OR-continuation in Nova — // it is ALSO the zero-arg closure-literal syntax, and the parser deliberately // does not extend newline-tolerance across a line-initial `||` (same class // of miscompile as `std/encoding/toml.nv`'s `is_bare_key_char`, closed the // same day the checker gained `E_CLOSURE_SCALAR_RETURN`). Fix: trailing `||` // at END of each line — a TRAILING binary operator before a newline IS a // legal continuation. fn needs_quoting(field str) -> bool { field.contains(",") || field.contains("\"") || field.contains("\n") || field.contains("\r") }