std1.0.0-beta-7 SafeC docs

Source serial/json.sc

View in the repository

// SafeC Standard Library — JSON serialization implementation (see json.h)
#pragma once
#include <std/serial/json.h>
#include <std/serial/value.h>
#include <std/collections/string.h>
#include <std/mem.h>
// String::push_int/push_float/parse_int/parse_float (both used heavily
// below) are declared in string.h but only *defined* in these two files —
// string.sc itself doesn't pull them in (see std/collections/string.sc's
// own includes), so anything that actually calls them, like this file,
// needs to.
#include <std/fmt.h>
#include <std/convert.h>
#include <std/encoding/utf8.h>

namespace std {

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

static char json_hex_digit_(int v) {
    if (v < 10) { return (char)((int)'0' + v); }
    return (char)((int)'a' + (v - 10));
}

// Appends s[run_start, i) as one push_n_() call — batching runs of bytes
// that need no escaping avoids a push_char() (and reserve_() check) per
// byte, which matters since most string content has no special chars.
static void json_flush_run_(&String out, const char* s, unsigned long run_start, unsigned long i) {
    if (i > run_start) {
        unsafe { out.push_n_(s + run_start, i - run_start); }
    }
}

static void json_append_escaped_(&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 == '"' || c == '\\' || c == '\n' || c == '\r' || c == '\t' || (int)(unsigned char)c < 32) {
            json_flush_run_(out, s, run_start, i);
            if (c == '"') {
                unsafe { out.push("\\\""); }
            } else if (c == '\\') {
                unsafe { out.push("\\\\"); }
            } else if (c == '\n') {
                unsafe { out.push("\\n"); }
            } else if (c == '\r') {
                unsafe { out.push("\\r"); }
            } else if (c == '\t') {
                unsafe { out.push("\\t"); }
            } else {
                unsafe {
                    out.push("\\u00");
                    out.push_char(json_hex_digit_(((int)c >> 4) & 0xF));
                    out.push_char(json_hex_digit_((int)c & 0xF));
                }
            }
            i = i + 1UL;
            run_start = i;
        } else {
            i = i + 1UL;
        }
    }
    json_flush_run_(out, s, run_start, i);
    unsafe { out.push_char('"'); }
}

// push_float always emits a fixed number of decimals ('3.140000') — trim
// the trailing zeros (and a now-bare trailing '.') so ordinary values read
// the way a human (or another JSON parser's float formatter) would write
// them, without losing precision push_float actually computed.
static void json_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);
    }
}

void json_write(const &Value v, &String out) {
    int kind;
    unsafe { kind = v.kind; }
    if (kind == VAL_NULL) {
        unsafe { out.push("null"); }
    } else if (kind == VAL_BOOL) {
        int b;
        unsafe { b = v.bool_val; }
        unsafe { out.push(b != 0 ? "true" : "false"); }
    } else if (kind == VAL_INT) {
        long long n;
        unsafe { n = v.int_val; }
        unsafe { out.push_int(n); }
    } else if (kind == VAL_FLOAT) {
        double f;
        unsafe { f = v.float_val; }
        json_append_float_(out, f);
    } else if (kind == VAL_STRING) {
        const char* s;
        unsafe {
            if (v.str_val != (char*)0) { s = (const char*)v.str_val; }
            else { s = (const char*)""; }
        }
        json_append_escaped_(out, s);
    } else if (kind == VAL_ARRAY) {
        unsafe { out.push_char('['); }
        unsigned long n;
        unsafe { n = v.arr_val.length(); }
        unsigned long i = 0UL;
        while (i < n) {
            if (i > 0UL) { unsafe { out.push_char(','); } }
            &Value elem;
            unsafe { elem = v.arr_val.at(i); }
            json_write(elem, out);
            i = i + 1UL;
        }
        unsafe { out.push_char(']'); }
    } else if (kind == VAL_OBJECT) {
        unsafe { out.push_char('{'); }
        unsigned long n;
        unsafe { n = v.obj_val.length(); }
        unsigned long i = 0UL;
        while (i < n) {
            if (i > 0UL) { unsafe { out.push_char(','); } }
            struct ObjectEntry* e;
            unsafe { e = v.obj_val.at(i); }
            const char* key;
            unsafe { key = (const char*)e->key; }
            json_append_escaped_(out, key);
            unsafe { out.push_char(':'); }
            &Value val;
            unsafe { val = e->val; }
            json_write(val, out);
            i = i + 1UL;
        }
        unsafe { out.push_char('}'); }
    }
}

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

// ── Parser ────────────────────────────────────────────────────────────────
// Plain recursive descent over a NUL-terminated C string. No exceptions in
// this language, so failure propagates via a shared 'ok' flag every
// sub-parser checks before doing any work — once it's 0, every remaining
// call in the current parse becomes a cheap no-op that returns Value::null().

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

static char json_peek_(&JsonCursor_ c) {
    unsafe { return c->text[c->pos]; }
}

static void json_skip_ws_(&JsonCursor_ c) {
    while (1) {
        char ch = json_peek_(c);
        if (ch != ' ' && ch != '\t' && ch != '\n' && ch != '\r') { break; }
        unsafe { c->pos = c->pos + 1UL; }
    }
}

static int json_eat_(&JsonCursor_ c, char expected) {
    if (json_peek_(c) != expected) { unsafe { c->ok = 0; } return 0; }
    unsafe { c->pos = c->pos + 1UL; }
    return 1;
}

// Matches a literal keyword ("true"/"false"/"null") starting at the cursor.
static int json_eat_literal_(&JsonCursor_ c, const char* word) {
    unsigned long start;
    unsafe { start = c->pos; }
    unsigned long i = 0UL;
    while (1) {
        char want;
        unsafe { want = word[i]; }
        if (want == (char)0) { break; }
        if (json_peek_(c) != want) { unsafe { c->pos = start; } return 0; }
        unsafe { c->pos = c->pos + 1UL; }
        i = i + 1UL;
    }
    return 1;
}

static struct Value json_parse_value_(&JsonCursor_ c);

static unsigned int json_read_hex4_(&JsonCursor_ c) {
    unsigned int val = 0u;
    int k = 0;
    while (k < 4) {
        char hc = json_peek_(c);
        c.pos = c.pos + 1UL;
        unsigned int digit = 0u;
        if (hc >= '0' && hc <= '9') { digit = (unsigned int)(hc - '0'); }
        else if (hc >= 'a' && hc <= 'f') { digit = (unsigned int)(hc - 'a' + 10); }
        else if (hc >= 'A' && hc <= 'F') { digit = (unsigned int)(hc - 'A' + 10); }
        else { c.ok = 0; }
        val = val * 16u + digit;
        k = k + 1;
    }
    return val;
}

static struct Value json_parse_string_raw_(&JsonCursor_ c) {
    // Caller already confirmed the opening '"'.
    struct String out = String::new();
    json_eat_(c, '"');
    while (1) {
        int stillOk;
        unsafe { stillOk = c->ok; }
        if (stillOk == 0) { break; }
        char ch = json_peek_(c);
        if (ch == (char)0) { unsafe { c->ok = 0; } break; }
        if (ch == '"') { unsafe { c->pos = c->pos + 1UL; } break; }
        if (ch == '\\') {
            unsafe { c->pos = c->pos + 1UL; }
            char esc = json_peek_(c);
            unsafe { c->pos = c->pos + 1UL; }
            if (esc == 'n') { unsafe { out.push_char('\n'); } }
            else if (esc == 't') { unsafe { out.push_char('\t'); } }
            else if (esc == 'r') { unsafe { out.push_char('\r'); } }
            else if (esc == '"') { unsafe { out.push_char('"'); } }
            else if (esc == '\\') { unsafe { out.push_char('\\'); } }
            else if (esc == '/') { unsafe { out.push_char('/'); } }
            else if (esc == 'b') { unsafe { out.push_char((char)8); } }
            else if (esc == 'f') { unsafe { out.push_char((char)12); } }
            else if (esc == 'u') {
                // \uXXXX → UTF-8, combining a UTF-16 surrogate pair
                // (\uD83D\uDE00) into one astral code point.
                unsigned int cp = json_read_hex4_(c);
                if (cp >= 0xD800u && cp <= 0xDBFFu && json_peek_(c) == '\\') {
                    unsigned long save;
                    unsafe { save = c->pos; c->pos = c->pos + 1UL; }
                    if (json_peek_(c) == 'u') {
                        unsafe { c->pos = c->pos + 1UL; }
                        unsigned int lo = json_read_hex4_(c);
                        if (lo >= 0xDC00u && lo <= 0xDFFFu) {
                            cp = 0x10000u + ((cp - 0xD800u) << 10) + (lo - 0xDC00u);
                        } else {
                            unsafe { c->pos = save; }
                        }
                    } else {
                        unsafe { c->pos = save; }
                    }
                }
                char enc[4];
                int n; unsafe { n = utf8_encode(cp, (char*)enc); }
                unsafe { out.push_n_((const char*)enc, (unsigned long)n); }
            } else {
                unsafe { c->ok = 0; }
            }
        } else {
            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 json_parse_number_(&JsonCursor_ c) {
    unsigned long start;
    unsafe { start = c->pos; }
    int isFloat = 0;
    if (json_peek_(c) == '-') { unsafe { c->pos = c->pos + 1UL; } }
    while (1) {
        char ch = json_peek_(c);
        if (ch >= '0' && ch <= '9') { unsafe { c->pos = c->pos + 1UL; } continue; }
        if (ch == '.' || ch == 'e' || ch == 'E' || ch == '+' || ch == '-') {
            isFloat = 1;
            unsafe { c->pos = c->pos + 1UL; }
            continue;
        }
        break;
    }
    unsigned long len;
    unsafe { len = c->pos - start; }
    if (len == 0UL) { unsafe { c->ok = 0; } return Value::null(); }

    struct String tok = String::new();
    struct String numStr;
    unsafe { numStr = String::from_n_(c->text + start, len); }
    tok.push_str(&numStr);
    numStr.free();
    int ok = 0;
    struct Value v;
    if (isFloat) {
        double f = tok.parse_float(&ok);
        v = Value::from_float(f);
    } else {
        long long n = tok.parse_int(&ok);
        v = Value::from_int(n);
    }
    tok.free();
    if (ok == 0) { unsafe { c->ok = 0; } }
    return v;
}

static struct Value json_parse_array_(&JsonCursor_ c) {
    struct Value arr = Value::array();
    json_eat_(c, '[');
    json_skip_ws_(c);
    if (json_peek_(c) == ']') { unsafe { c->pos = c->pos + 1UL; } return arr; }
    while (1) {
        int stillOk;
        unsafe { stillOk = c->ok; }
        if (stillOk == 0) { break; }
        json_skip_ws_(c);
        struct Value elem = json_parse_value_(c);
        arr.array_push(elem);
        json_skip_ws_(c);
        char ch = json_peek_(c);
        if (ch == ',') { unsafe { c->pos = c->pos + 1UL; continue; } }
        if (ch == ']') { unsafe { c->pos = c->pos + 1UL; break; } }
        unsafe { c->ok = 0; }
        break;
    }
    return arr;
}

static struct Value json_parse_object_(&JsonCursor_ c) {
    struct Value obj = Value::object();
    json_eat_(c, '{');
    json_skip_ws_(c);
    if (json_peek_(c) == '}') { unsafe { c->pos = c->pos + 1UL; } return obj; }
    while (1) {
        int stillOk;
        unsafe { stillOk = c->ok; }
        if (stillOk == 0) { break; }
        json_skip_ws_(c);
        if (json_peek_(c) != '"') { unsafe { c->ok = 0; } break; }
        struct Value keyVal = json_parse_string_raw_(c);
        json_skip_ws_(c);
        json_eat_(c, ':');
        json_skip_ws_(c);
        struct Value val = json_parse_value_(c);
        unsafe { obj.object_set(keyVal.as_string(), val); }
        keyVal.free();
        json_skip_ws_(c);
        char ch = json_peek_(c);
        if (ch == ',') { unsafe { c->pos = c->pos + 1UL; continue; } }
        if (ch == '}') { unsafe { c->pos = c->pos + 1UL; break; } }
        unsafe { c->ok = 0; }
        break;
    }
    return obj;
}

static struct Value json_parse_value_(&JsonCursor_ c) {
    json_skip_ws_(c);
    int stillOk2;
    unsafe { stillOk2 = c->ok; }
    if (stillOk2 == 0) { return Value::null(); }
    char ch = json_peek_(c);
    if (ch == '{') { return json_parse_object_(c); }
    if (ch == '[') { return json_parse_array_(c); }
    if (ch == '"') { return json_parse_string_raw_(c); }
    if (ch == 't') {
        if (json_eat_literal_(c, "true")) { return Value::from_bool(1); }
        unsafe { c->ok = 0; } return Value::null();
    }
    if (ch == 'f') {
        if (json_eat_literal_(c, "false")) { return Value::from_bool(0); }
        unsafe { c->ok = 0; } return Value::null();
    }
    if (ch == 'n') {
        if (json_eat_literal_(c, "null")) { return Value::null(); }
        unsafe { c->ok = 0; } return Value::null();
    }
    if (ch == '-' || (ch >= '0' && ch <= '9')) { return json_parse_number_(c); }
    unsafe { c->ok = 0; }
    return Value::null();
}

struct Value json_parse(const &char text, ?&int ok) {
    struct JsonCursor_ c;
    c.text = text;
    c.pos = 0UL;
    c.ok = 1;
    struct Value v = json_parse_value_(&c);
    if (ok != (int*)0) { unsafe { *ok = c.ok; } }
    if (c.ok == 0) {
        v.free();
        return Value::null();
    }
    return v;
}

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

void Json::write(const &Value v, &String out) const { json_write(v, out); }

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

} // namespace std