std1.0.0-beta-7 SafeC docs

Source serial/csv.sc

View in the repository

// SafeC Standard Library — CSV serialization implementation (see csv.h)
#pragma once
#include <std/serial/csv.h>
#include <std/serial/value.h>
#include <std/collections/string.h>
#include <std/mem.h>
// String::push_int/push_float (used by csv_append_field_ below) are
// declared in string.h but only *defined* in these two files — see
// json.sc's identical comment for why both are needed here too.
#include <std/fmt.h>
#include <std/convert.h>

namespace std {

// ── Writer ──────────────────────────────────────────────────────────────────

static int csv_needs_quoting_(const char* s) {
    unsigned long i = 0UL;
    while (1) {
        char c;
        unsafe { c = s[i]; }
        if (c == (char)0) { return 0; }
        if (c == ',' || c == '"' || c == '\n' || c == '\r') { return 1; }
        i = i + 1UL;
    }
}

static void csv_append_quoted_(&String out, const char* s) {
    unsafe { out.push_char('"'); }
    unsigned long i = 0UL;
    unsigned long run_start = 0UL;
    while (1) {
        char c;
        unsafe { c = s[i]; }
        if (c == (char)0) { break; }
        if (c == '"') {
            if (i > run_start) { unsafe { out.push_n_(s + run_start, i - run_start); } }
            unsafe { out.push("\"\""); } // escape: one quote becomes two
            i = i + 1UL;
            run_start = i;
        } else {
            i = i + 1UL;
        }
    }
    if (i > run_start) { unsafe { out.push_n_(s + run_start, i - run_start); } }
    unsafe { out.push_char('"'); }
}

// Same trailing-zero-trim behavior as json.sc's json_append_float_ (kept
// as a separate copy rather than shared, matching html.sc/xml.sc each
// having their own small helpers instead of a cross-format-backend
// dependency between the format modules themselves).
static void csv_append_float_(&String out, double v) {
    unsafe {
        unsigned long before = out.length();
        out.push_float(v, 6);
        unsigned long end = out.length();
        while (end > before) {
            int c = out.char_at(end - 1UL);
            if (c != (int)'0') { break; }
            end = end - 1UL;
        }
        if (end > before) {
            int c = out.char_at(end - 1UL);
            if (c == (int)'.') { end = end - 1UL; }
        }
        out.truncate(end);
    }
}

static void csv_append_field_(&String out, const struct Value* field) {
    int kind;
    unsafe { kind = field->kind; }
    if (kind == VAL_NULL) {
        return; // empty field
    } else if (kind == VAL_STRING) {
        const char* s;
        unsafe {
            if (field->str_val != (char*)0) { s = (const char*)field->str_val; }
            else { s = (const char*)""; }
        }
        if (csv_needs_quoting_(s)) { csv_append_quoted_(out, s); }
        else { unsafe { out.push(s); } }
    } else if (kind == VAL_INT) {
        long long n;
        unsafe { n = field->int_val; }
        unsafe { out.push_int(n); }
    } else if (kind == VAL_FLOAT) {
        double f;
        unsafe { f = field->float_val; }
        csv_append_float_(out, f);
    } else if (kind == VAL_BOOL) {
        int b;
        unsafe { b = field->bool_val; }
        unsafe { out.push(b != 0 ? "true" : "false"); }
    }
    // VAL_ARRAY/VAL_OBJECT fields aren't meaningful in a CSV cell — silently
    // written as nothing, same as VAL_NULL, rather than erroring: a caller
    // building rows programmatically shouldn't have a malformed nested
    // value abort the whole document.
}

void csv_write(const &Value v, &String out) {
    unsigned long rowCount;
    unsafe { rowCount = v.arr_val.length(); }
    unsigned long r = 0UL;
    while (r < rowCount) {
        if (r > 0UL) { unsafe { out.push_char('\n'); } }
        struct Value* row;
        unsafe { row = v.arr_val.at(r); }
        unsigned long fieldCount;
        unsafe { fieldCount = row->arr_val.length(); }
        unsigned long f = 0UL;
        while (f < fieldCount) {
            if (f > 0UL) { unsafe { out.push_char(','); } }
            struct Value* field;
            unsafe { field = row->arr_val.at(f); }
            csv_append_field_(out, field);
            f = f + 1UL;
        }
        r = r + 1UL;
    }
}

inline struct String value_to_csv(const &Value v) {
    struct String out = String::new();
    csv_write(v, &out);
    return out;
}

// ── Parser ────────────────────────────────────────────────────────────────

struct CsvCursor_ {
    const char* text;
    unsigned long pos;
    int ok;
};

static char csv_peek_(struct CsvCursor_* c) {
    unsafe { return c->text[c->pos]; }
}

// Parses one field starting at the cursor, stopping at (but not consuming)
// the following ',' / '\n' / '\r' / end-of-text.
static struct Value csv_parse_field_(struct CsvCursor_* c) {
    struct String out = String::new();
    if (csv_peek_(c) == '"') {
        unsafe { c->pos = c->pos + 1UL; } // consume opening quote
        while (1) {
            char ch = csv_peek_(c);
            if (ch == (char)0) {
                unsafe { c->ok = 0; } // unterminated quoted field
                break;
            }
            if (ch == '"') {
                char next;
                unsafe { next = c->text[c->pos + 1UL]; }
                if (next == '"') {
                    unsafe { out.push_char('"'); c->pos = c->pos + 2UL; }
                    continue;
                }
                unsafe { c->pos = c->pos + 1UL; } // consume closing quote
                break;
            }
            unsafe { out.push_char(ch); c->pos = c->pos + 1UL; }
        }
    } else {
        while (1) {
            char ch = csv_peek_(c);
            if (ch == (char)0 || ch == ',' || ch == '\n' || ch == '\r') { break; }
            unsafe { out.push_char(ch); c->pos = c->pos + 1UL; }
        }
    }
    struct Value v = Value::from_str(out.as_ptr());
    out.free();
    return v;
}

static struct Value csv_parse_row_(struct CsvCursor_* c) {
    struct Value row = Value::array();
    while (1) {
        struct Value field = csv_parse_field_(c);
        row.array_push(field);
        int stillOk;
        unsafe { stillOk = c->ok; }
        if (stillOk == 0) { break; }
        char ch = csv_peek_(c);
        if (ch == ',') { unsafe { c->pos = c->pos + 1UL; } continue; }
        break; // '\n', '\r', or end of text ends the row
    }
    return row;
}

struct Value csv_parse(const &char text, ?&int ok) {
    struct Value rows = Value::array();
    struct CsvCursor_ c;
    c.text = text;
    c.pos = 0UL;
    c.ok = 1;
    while (1) {
        char first = csv_peek_(&c);
        if (first == (char)0) { break; } // no trailing phantom row after EOF
        struct Value row = csv_parse_row_(&c);
        rows.array_push(row);
        if (c.ok == 0) { break; }
        char ch = csv_peek_(&c);
        if (ch == '\r') { unsafe { c.pos = c.pos + 1UL; } ch = csv_peek_(&c); }
        if (ch == '\n') { unsafe { c.pos = c.pos + 1UL; } }
    }
    if (ok != (int*)0) { unsafe { *ok = c.ok; } }
    return rows;
}

struct Csv Csv::new() {
    struct Csv f;
    f.unused_ = 0;
    return f;
}

void Csv::write(const &Value v, &String out) const { csv_write(v, out); }

struct Value Csv::parse(const &char text, ?&int ok) const { return csv_parse(text, ok); }

} // namespace std