DMS-XS-Parser

 view release on metacpan or  search on metacpan

vendor/dms-c/dms.c  view on Meta::CPAN

    if (c == 0) { set_err(p, "expected key"); return NULL; }
    return parse_bare_key_str(p);
}

static char *parse_bare_key_str(parser *p) {
    size_t start = p->pos;
    const char *src = p->src;
    size_t end = p->len;
    size_t pos = p->pos;
    /* ASCII fast path: ~6 chars/key × 50k keys is the bench's biggest
     * utf8_decode caller. Stepping byte-by-byte here is ~10× faster. */
    while (pos < end) {
        unsigned char b0 = (unsigned char)src[pos];
        if (b0 < 0x80) {
            if (!is_bare_key_char_ascii(b0)) break;
            pos++;
            continue;
        }
        /* Non-ASCII: fall through to the multi-byte decoder. */
        size_t clen;
        int cp = utf8_decode(src + pos, end - pos, &clen);
        if (cp < 0 || !is_bare_key_char_cp(cp)) break;
        pos += clen;
    }
    p->pos = pos;
    if (pos == start) { set_err(p, "expected key"); return NULL; }
    return xstrndup(src + start, pos - start);
}

/* ---------- string parsers ---------- */

static char *parse_basic_string_str(parser *p) {
    int sl = p->line;
    size_t sls = p->line_start, sp = p->pos;
    p->pos++; /* opening " */
    buf b; buf_init(&b);
    while (1) {
        if (eof(p)) { set_err_at(p, sl, sls, sp, "unterminated string"); free(b.data); return NULL; }
        char c = peek(p);
        if (c == '\n' || c == '\r') { set_err(p, "strings cannot span lines"); free(b.data); return NULL; }
        if (c == '"') { p->pos++; /* SPEC §Unicode normalization: re-NFC after escape decoding. */ if (!b.data) { return nfc_string(xstrdup("")); } return nfc_string(b.data); }
        if (c == '\\') {
            p->pos++;
            if (eof(p)) { set_err(p, "unterminated escape"); free(b.data); return NULL; }
            char esc = p->src[p->pos++];
            char out[5];
            int outlen;
            unsigned cp;
            switch (esc) {
                case '"': buf_push(&b, '"'); break;
                case '\\': buf_push(&b, '\\'); break;
                case 'n': buf_push(&b, '\n'); break;
                case 't': buf_push(&b, '\t'); break;
                case 'r': buf_push(&b, '\r'); break;
                case 'b': buf_push(&b, '\b'); break;
                case 'f': buf_push(&b, '\f'); break;
                case 'u':
                case 'U': {
                    int n = (esc == 'u') ? 4 : 8;
                    if ((int)(p->len - p->pos) < n) {
                        set_err(p, "expected %d hex digits in unicode escape", n);
                        free(b.data); return NULL;
                    }
                    cp = 0;
                    for (int i = 0; i < n; i++) {
                        char h = p->src[p->pos + i];
                        int d;
                        if (h >= '0' && h <= '9') d = h - '0';
                        else if (h >= 'a' && h <= 'f') d = h - 'a' + 10;
                        else if (h >= 'A' && h <= 'F') d = h - 'A' + 10;
                        else { set_err(p, "invalid hex in unicode escape"); free(b.data); return NULL; }
                        cp = (cp << 4) | d;
                    }
                    p->pos += n;
                    /* SPEC: U+0000 is forbidden anywhere in DMS source, including via
                       escape decoding. `�` / `\U00000000` must not slip through. */
                    if (cp == 0) {
                        set_err(p, "\\u0000 escape forbidden"); free(b.data); return NULL;
                    }
                    /* SPEC §basic-string escapes: surrogate codepoints (U+D800..U+DFFF)
                       are not valid Unicode scalars and are a parse error in `\uXXXX` /
                       `\UXXXXXXXX` escapes. */
                    if (cp >= 0xD800 && cp <= 0xDFFF) {
                        set_err(p, "surrogate codepoint U+%04X in escape", cp); free(b.data); return NULL;
                    }
                    if (cp > 0x10FFFF) {
                        set_err(p, "unicode escape is not a scalar value"); free(b.data); return NULL;
                    }
                    outlen = utf8_encode(cp, out);
                    buf_push_str(&b, out, outlen);
                    break;
                }
                default:
                    set_err(p, "invalid escape '\\%c'", esc);
                    free(b.data); return NULL;
            }
        } else {
            /* preserve raw bytes */
            buf_push(&b, c);
            p->pos++;
        }
    }
}

static char *parse_literal_string_str(parser *p) {
    int sl = p->line;
    size_t sls = p->line_start, sp = p->pos;
    p->pos++; /* opening ' */
    buf b; buf_init(&b);
    while (1) {
        if (eof(p)) { set_err_at(p, sl, sls, sp, "unterminated string"); free(b.data); return NULL; }
        char c = peek(p);
        if (c == '\n' || c == '\r') { set_err(p, "strings cannot span lines"); free(b.data); return NULL; }
        if (c == '\'') { p->pos++; if (!b.data) return xstrdup(""); return b.data; }
        buf_push(&b, c);
        p->pos++;
    }
}

static dms_value *parse_basic_string(parser *p) {
    char *s = parse_basic_string_str(p);
    if (!s) return NULL;
    dms_value *v = new_value(DMS_STRING);
    v->u.s = s;
    return v;
}

static dms_value *parse_literal_string(parser *p) {
    char *s = parse_literal_string_str(p);
    if (!s) return NULL;
    dms_value *v = new_value(DMS_STRING);
    v->u.s = s;
    /* Record original form (Literal). Basic-string form is the emitter
       default and not recorded. */
    if (p->record_forms) {
        dms_string_form *sf = (dms_string_form *)calloc(1, sizeof(dms_string_form));
        sf->kind = DMS_STRING_LITERAL;
        record_form_string(p, sf);
    }
    return v;
}

/* ---------- numbers / datetimes ---------- */

static bool valid_underscores(const char *s) {
    size_t n = strlen(s);
    if (n == 0) return true;



( run in 0.498 second using v1.01-cache-2.11-cpan-9581c071862 )