762 lines
22 KiB
C
762 lines
22 KiB
C
// calogJson.c -- calog JSON library (see calogJson.h). A self-contained recursive-descent
|
|
// parser and a serializer, both bridging JSON text and calog's value/aggregate model. No
|
|
// shared state: the two natives are pure functions of their arguments.
|
|
|
|
#define _POSIX_C_SOURCE 200809L
|
|
|
|
#include "calogJson.h"
|
|
|
|
#include <errno.h>
|
|
#include <math.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#define JSON_BUF_INITIAL 64
|
|
|
|
// A growable output byte buffer for serialization and for decoded string bodies.
|
|
typedef struct JsonBufT {
|
|
char *bytes;
|
|
size_t length;
|
|
size_t cap;
|
|
} JsonBufT;
|
|
|
|
// A read cursor over the source text (binary-safe: bounded by end, not a NUL).
|
|
typedef struct JsonParseT {
|
|
const char *cur;
|
|
const char *end;
|
|
} JsonParseT;
|
|
|
|
static int32_t jsonBufEnsure(JsonBufT *buf, size_t extra);
|
|
static void jsonBufFree(JsonBufT *buf);
|
|
static int32_t jsonBufPutBytes(JsonBufT *buf, const char *bytes, size_t length);
|
|
static int32_t jsonBufPutChar(JsonBufT *buf, char c);
|
|
static int32_t jsonEncode(JsonBufT *buf, const CalogValueT *value, int32_t depth);
|
|
static int32_t jsonEncodeAgg(JsonBufT *buf, const CalogAggT *agg, int32_t depth);
|
|
static int32_t jsonEncodeKey(JsonBufT *buf, const CalogValueT *key);
|
|
static int32_t jsonEncodeReal(JsonBufT *buf, double r);
|
|
static int32_t jsonEncodeString(JsonBufT *buf, const char *bytes, int64_t length);
|
|
static int32_t jsonHexQuad(JsonParseT *p, uint32_t *out);
|
|
static int32_t jsonParseArray(JsonParseT *p, CalogValueT *out, int32_t depth);
|
|
static int32_t jsonParseLiteral(JsonParseT *p, CalogValueT *out);
|
|
static int32_t jsonParseNative(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
|
static int32_t jsonParseNumber(JsonParseT *p, CalogValueT *out);
|
|
static int32_t jsonParseObject(JsonParseT *p, CalogValueT *out, int32_t depth);
|
|
static int32_t jsonParseString(JsonParseT *p, CalogValueT *out);
|
|
static int32_t jsonParseUnicode(JsonParseT *p, JsonBufT *buf);
|
|
static int32_t jsonParseValue(JsonParseT *p, CalogValueT *out, int32_t depth);
|
|
static int32_t jsonPutUtf8(JsonBufT *buf, uint32_t cp);
|
|
static void jsonSkipWs(JsonParseT *p);
|
|
static int32_t jsonStringifyNative(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
|
|
|
|
|
int32_t calogJsonRegister(CalogT *calog) {
|
|
calogRegisterInline(calog, "jsonParse", jsonParseNative, NULL);
|
|
calogRegisterInline(calog, "jsonStringify", jsonStringifyNative, NULL);
|
|
return calogOkE;
|
|
}
|
|
|
|
|
|
static int32_t jsonBufEnsure(JsonBufT *buf, size_t extra) {
|
|
size_t need;
|
|
size_t cap;
|
|
char *grown;
|
|
|
|
need = buf->length + extra;
|
|
if (need <= buf->cap) {
|
|
return calogOkE;
|
|
}
|
|
cap = (buf->cap == 0) ? JSON_BUF_INITIAL : buf->cap;
|
|
while (cap < need) {
|
|
cap *= CALOG_GROWTH_FACTOR;
|
|
}
|
|
grown = (char *)realloc(buf->bytes, cap);
|
|
if (grown == NULL) {
|
|
return calogErrOomE;
|
|
}
|
|
buf->bytes = grown;
|
|
buf->cap = cap;
|
|
return calogOkE;
|
|
}
|
|
|
|
|
|
static void jsonBufFree(JsonBufT *buf) {
|
|
free(buf->bytes);
|
|
buf->bytes = NULL;
|
|
buf->length = 0;
|
|
buf->cap = 0;
|
|
}
|
|
|
|
|
|
static int32_t jsonBufPutBytes(JsonBufT *buf, const char *bytes, size_t length) {
|
|
int32_t status;
|
|
|
|
if (length == 0) {
|
|
return calogOkE;
|
|
}
|
|
status = jsonBufEnsure(buf, length);
|
|
if (status != calogOkE) {
|
|
return status;
|
|
}
|
|
memcpy(buf->bytes + buf->length, bytes, length);
|
|
buf->length += length;
|
|
return calogOkE;
|
|
}
|
|
|
|
|
|
static int32_t jsonBufPutChar(JsonBufT *buf, char c) {
|
|
return jsonBufPutBytes(buf, &c, 1);
|
|
}
|
|
|
|
|
|
static int32_t jsonEncode(JsonBufT *buf, const CalogValueT *value, int32_t depth) {
|
|
char tmp[32];
|
|
int n;
|
|
|
|
if (depth >= CALOG_MAX_DEPTH) {
|
|
return calogErrDepthE;
|
|
}
|
|
switch (value->type) {
|
|
case calogNilE:
|
|
return jsonBufPutBytes(buf, "null", 4);
|
|
case calogBoolE:
|
|
return value->as.b ? jsonBufPutBytes(buf, "true", 4) : jsonBufPutBytes(buf, "false", 5);
|
|
case calogIntE:
|
|
n = snprintf(tmp, sizeof(tmp), "%lld", (long long)value->as.i);
|
|
return jsonBufPutBytes(buf, tmp, (size_t)n);
|
|
case calogRealE:
|
|
return jsonEncodeReal(buf, value->as.r);
|
|
case calogStringE:
|
|
return jsonEncodeString(buf, value->as.s.bytes, value->as.s.length);
|
|
case calogAggE:
|
|
return jsonEncodeAgg(buf, value->as.agg, depth);
|
|
case calogFnE:
|
|
return calogErrTypeE;
|
|
default:
|
|
return calogErrTypeE;
|
|
}
|
|
}
|
|
|
|
|
|
static int32_t jsonEncodeAgg(JsonBufT *buf, const CalogAggT *agg, int32_t depth) {
|
|
int64_t index;
|
|
int32_t status;
|
|
|
|
// A map (or a hybrid that carries keyed pairs) serializes as a JSON object; a pure
|
|
// sequence serializes as a JSON array. In the hybrid case the array elements are emitted
|
|
// under their integer indices so nothing is silently dropped.
|
|
if (agg->pairCount > 0 || agg->kind == calogMapE) {
|
|
bool first;
|
|
first = true;
|
|
status = jsonBufPutChar(buf, '{');
|
|
if (status != calogOkE) {
|
|
return status;
|
|
}
|
|
for (index = 0; index < agg->arrayCount; index++) {
|
|
char tmp[32];
|
|
int n;
|
|
if (!first) {
|
|
status = jsonBufPutChar(buf, ',');
|
|
if (status != calogOkE) {
|
|
return status;
|
|
}
|
|
}
|
|
first = false;
|
|
n = snprintf(tmp, sizeof(tmp), "\"%lld\":", (long long)index);
|
|
status = jsonBufPutBytes(buf, tmp, (size_t)n);
|
|
if (status != calogOkE) {
|
|
return status;
|
|
}
|
|
status = jsonEncode(buf, &agg->array[index], depth + 1);
|
|
if (status != calogOkE) {
|
|
return status;
|
|
}
|
|
}
|
|
for (index = 0; index < agg->pairCount; index++) {
|
|
if (!first) {
|
|
status = jsonBufPutChar(buf, ',');
|
|
if (status != calogOkE) {
|
|
return status;
|
|
}
|
|
}
|
|
first = false;
|
|
status = jsonEncodeKey(buf, &agg->pairs[index].key);
|
|
if (status != calogOkE) {
|
|
return status;
|
|
}
|
|
status = jsonBufPutChar(buf, ':');
|
|
if (status != calogOkE) {
|
|
return status;
|
|
}
|
|
status = jsonEncode(buf, &agg->pairs[index].value, depth + 1);
|
|
if (status != calogOkE) {
|
|
return status;
|
|
}
|
|
}
|
|
return jsonBufPutChar(buf, '}');
|
|
}
|
|
status = jsonBufPutChar(buf, '[');
|
|
if (status != calogOkE) {
|
|
return status;
|
|
}
|
|
for (index = 0; index < agg->arrayCount; index++) {
|
|
if (index > 0) {
|
|
status = jsonBufPutChar(buf, ',');
|
|
if (status != calogOkE) {
|
|
return status;
|
|
}
|
|
}
|
|
status = jsonEncode(buf, &agg->array[index], depth + 1);
|
|
if (status != calogOkE) {
|
|
return status;
|
|
}
|
|
}
|
|
return jsonBufPutChar(buf, ']');
|
|
}
|
|
|
|
|
|
static int32_t jsonEncodeKey(JsonBufT *buf, const CalogValueT *key) {
|
|
char tmp[32];
|
|
int n;
|
|
|
|
// JSON object keys must be strings; coerce a scalar key to its string form.
|
|
switch (key->type) {
|
|
case calogStringE:
|
|
return jsonEncodeString(buf, key->as.s.bytes, key->as.s.length);
|
|
case calogIntE:
|
|
n = snprintf(tmp, sizeof(tmp), "\"%lld\"", (long long)key->as.i);
|
|
return jsonBufPutBytes(buf, tmp, (size_t)n);
|
|
case calogBoolE:
|
|
return key->as.b ? jsonBufPutBytes(buf, "\"true\"", 6) : jsonBufPutBytes(buf, "\"false\"", 7);
|
|
case calogRealE:
|
|
n = snprintf(tmp, sizeof(tmp), "\"%.17g\"", key->as.r);
|
|
return jsonBufPutBytes(buf, tmp, (size_t)n);
|
|
default:
|
|
return calogErrTypeE;
|
|
}
|
|
}
|
|
|
|
|
|
static int32_t jsonEncodeReal(JsonBufT *buf, double r) {
|
|
char tmp[40];
|
|
int n;
|
|
|
|
if (!isfinite(r)) {
|
|
return jsonBufPutBytes(buf, "null", 4);
|
|
}
|
|
n = snprintf(tmp, sizeof(tmp), "%.15g", r);
|
|
if (strtod(tmp, NULL) != r) {
|
|
n = snprintf(tmp, sizeof(tmp), "%.17g", r);
|
|
}
|
|
return jsonBufPutBytes(buf, tmp, (size_t)n);
|
|
}
|
|
|
|
|
|
static int32_t jsonEncodeString(JsonBufT *buf, const char *bytes, int64_t length) {
|
|
int64_t index;
|
|
int32_t status;
|
|
|
|
status = jsonBufPutChar(buf, '"');
|
|
if (status != calogOkE) {
|
|
return status;
|
|
}
|
|
for (index = 0; index < length; index++) {
|
|
unsigned char c;
|
|
c = (unsigned char)bytes[index];
|
|
switch (c) {
|
|
case '"':
|
|
status = jsonBufPutBytes(buf, "\\\"", 2);
|
|
break;
|
|
case '\\':
|
|
status = jsonBufPutBytes(buf, "\\\\", 2);
|
|
break;
|
|
case '\b':
|
|
status = jsonBufPutBytes(buf, "\\b", 2);
|
|
break;
|
|
case '\f':
|
|
status = jsonBufPutBytes(buf, "\\f", 2);
|
|
break;
|
|
case '\n':
|
|
status = jsonBufPutBytes(buf, "\\n", 2);
|
|
break;
|
|
case '\r':
|
|
status = jsonBufPutBytes(buf, "\\r", 2);
|
|
break;
|
|
case '\t':
|
|
status = jsonBufPutBytes(buf, "\\t", 2);
|
|
break;
|
|
default:
|
|
if (c < 0x20) {
|
|
char esc[8];
|
|
int n;
|
|
n = snprintf(esc, sizeof(esc), "\\u%04x", (unsigned)c);
|
|
status = jsonBufPutBytes(buf, esc, (size_t)n);
|
|
} else {
|
|
status = jsonBufPutChar(buf, (char)c);
|
|
}
|
|
break;
|
|
}
|
|
if (status != calogOkE) {
|
|
return status;
|
|
}
|
|
}
|
|
return jsonBufPutChar(buf, '"');
|
|
}
|
|
|
|
|
|
static int32_t jsonHexQuad(JsonParseT *p, uint32_t *out) {
|
|
uint32_t value;
|
|
int i;
|
|
|
|
value = 0;
|
|
for (i = 0; i < 4; i++) {
|
|
int digit;
|
|
if (p->cur >= p->end) {
|
|
return calogErrArgE;
|
|
}
|
|
if (*p->cur >= '0' && *p->cur <= '9') {
|
|
digit = *p->cur - '0';
|
|
} else if (*p->cur >= 'a' && *p->cur <= 'f') {
|
|
digit = *p->cur - 'a' + 10;
|
|
} else if (*p->cur >= 'A' && *p->cur <= 'F') {
|
|
digit = *p->cur - 'A' + 10;
|
|
} else {
|
|
return calogErrArgE;
|
|
}
|
|
value = (value << 4) | (uint32_t)digit;
|
|
p->cur++;
|
|
}
|
|
*out = value;
|
|
return calogOkE;
|
|
}
|
|
|
|
|
|
static int32_t jsonParseArray(JsonParseT *p, CalogValueT *out, int32_t depth) {
|
|
CalogAggT *agg;
|
|
int32_t status;
|
|
|
|
calogValueNil(out);
|
|
status = calogAggCreate(&agg, calogListE);
|
|
if (status != calogOkE) {
|
|
return status;
|
|
}
|
|
p->cur++; // consume '['
|
|
jsonSkipWs(p);
|
|
if (p->cur < p->end && *p->cur == ']') {
|
|
p->cur++;
|
|
calogValueAgg(out, agg);
|
|
return calogOkE;
|
|
}
|
|
for (;;) {
|
|
CalogValueT element;
|
|
status = jsonParseValue(p, &element, depth + 1);
|
|
if (status != calogOkE) {
|
|
calogAggFree(agg);
|
|
return status;
|
|
}
|
|
status = calogAggPush(agg, &element);
|
|
if (status != calogOkE) {
|
|
calogValueFree(&element);
|
|
calogAggFree(agg);
|
|
return status;
|
|
}
|
|
jsonSkipWs(p);
|
|
if (p->cur >= p->end) {
|
|
calogAggFree(agg);
|
|
return calogErrArgE;
|
|
}
|
|
if (*p->cur == ',') {
|
|
p->cur++;
|
|
continue;
|
|
}
|
|
if (*p->cur == ']') {
|
|
p->cur++;
|
|
break;
|
|
}
|
|
calogAggFree(agg);
|
|
return calogErrArgE;
|
|
}
|
|
calogValueAgg(out, agg);
|
|
return calogOkE;
|
|
}
|
|
|
|
|
|
static int32_t jsonParseLiteral(JsonParseT *p, CalogValueT *out) {
|
|
size_t remaining;
|
|
|
|
calogValueNil(out);
|
|
remaining = (size_t)(p->end - p->cur);
|
|
if (remaining >= 4 && memcmp(p->cur, "true", 4) == 0) {
|
|
calogValueBool(out, true);
|
|
p->cur += 4;
|
|
return calogOkE;
|
|
}
|
|
if (remaining >= 5 && memcmp(p->cur, "false", 5) == 0) {
|
|
calogValueBool(out, false);
|
|
p->cur += 5;
|
|
return calogOkE;
|
|
}
|
|
if (remaining >= 4 && memcmp(p->cur, "null", 4) == 0) {
|
|
calogValueNil(out);
|
|
p->cur += 4;
|
|
return calogOkE;
|
|
}
|
|
return calogErrArgE;
|
|
}
|
|
|
|
|
|
static int32_t jsonParseNative(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
|
|
JsonParseT parse;
|
|
int32_t status;
|
|
|
|
(void)userData;
|
|
calogValueNil(result);
|
|
if (argCount != 1 || args[0].type != calogStringE) {
|
|
return calogFail(result, calogErrArgE, "jsonParse expects (text)");
|
|
}
|
|
parse.cur = args[0].as.s.bytes;
|
|
parse.end = args[0].as.s.bytes + args[0].as.s.length;
|
|
status = jsonParseValue(&parse, result, 0);
|
|
if (status != calogOkE) {
|
|
calogValueFree(result);
|
|
return calogFail(result, status, "jsonParse: invalid JSON");
|
|
}
|
|
jsonSkipWs(&parse);
|
|
if (parse.cur != parse.end) {
|
|
calogValueFree(result);
|
|
return calogFail(result, calogErrArgE, "jsonParse: trailing characters after JSON value");
|
|
}
|
|
return calogOkE;
|
|
}
|
|
|
|
|
|
static int32_t jsonParseNumber(JsonParseT *p, CalogValueT *out) {
|
|
const char *start;
|
|
char *stop;
|
|
bool isReal;
|
|
|
|
calogValueNil(out);
|
|
start = p->cur;
|
|
isReal = false;
|
|
if (p->cur < p->end && *p->cur == '-') {
|
|
p->cur++;
|
|
}
|
|
while (p->cur < p->end) {
|
|
char c;
|
|
c = *p->cur;
|
|
if (c >= '0' && c <= '9') {
|
|
p->cur++;
|
|
} else if (c == '.' || c == 'e' || c == 'E' || c == '+' || c == '-') {
|
|
isReal = true;
|
|
p->cur++;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
if (p->cur == start) {
|
|
return calogErrArgE;
|
|
}
|
|
if (isReal) {
|
|
double r;
|
|
r = strtod(start, &stop);
|
|
if (stop != p->cur) {
|
|
return calogErrArgE;
|
|
}
|
|
calogValueReal(out, r);
|
|
return calogOkE;
|
|
}
|
|
{
|
|
long long i;
|
|
errno = 0;
|
|
i = strtoll(start, &stop, 10);
|
|
if (stop != p->cur) {
|
|
return calogErrArgE;
|
|
}
|
|
if (errno == ERANGE) {
|
|
// An integer literal that overflows int64 becomes a real, rather than silently
|
|
// saturating to LLONG_MAX/MIN.
|
|
double r;
|
|
r = strtod(start, &stop);
|
|
if (stop != p->cur) {
|
|
return calogErrArgE;
|
|
}
|
|
calogValueReal(out, r);
|
|
return calogOkE;
|
|
}
|
|
calogValueInt(out, (int64_t)i);
|
|
return calogOkE;
|
|
}
|
|
}
|
|
|
|
|
|
static int32_t jsonParseObject(JsonParseT *p, CalogValueT *out, int32_t depth) {
|
|
CalogAggT *agg;
|
|
int32_t status;
|
|
|
|
calogValueNil(out);
|
|
status = calogAggCreate(&agg, calogMapE);
|
|
if (status != calogOkE) {
|
|
return status;
|
|
}
|
|
p->cur++; // consume '{'
|
|
jsonSkipWs(p);
|
|
if (p->cur < p->end && *p->cur == '}') {
|
|
p->cur++;
|
|
calogValueAgg(out, agg);
|
|
return calogOkE;
|
|
}
|
|
for (;;) {
|
|
CalogValueT key;
|
|
CalogValueT value;
|
|
jsonSkipWs(p);
|
|
if (p->cur >= p->end || *p->cur != '"') {
|
|
calogAggFree(agg);
|
|
return calogErrArgE;
|
|
}
|
|
status = jsonParseString(p, &key);
|
|
if (status != calogOkE) {
|
|
calogAggFree(agg);
|
|
return status;
|
|
}
|
|
jsonSkipWs(p);
|
|
if (p->cur >= p->end || *p->cur != ':') {
|
|
calogValueFree(&key);
|
|
calogAggFree(agg);
|
|
return calogErrArgE;
|
|
}
|
|
p->cur++; // consume ':'
|
|
status = jsonParseValue(p, &value, depth + 1);
|
|
if (status != calogOkE) {
|
|
calogValueFree(&key);
|
|
calogAggFree(agg);
|
|
return status;
|
|
}
|
|
status = calogAggSet(agg, &key, &value);
|
|
if (status != calogOkE) {
|
|
calogValueFree(&key);
|
|
calogValueFree(&value);
|
|
calogAggFree(agg);
|
|
return status;
|
|
}
|
|
jsonSkipWs(p);
|
|
if (p->cur >= p->end) {
|
|
calogAggFree(agg);
|
|
return calogErrArgE;
|
|
}
|
|
if (*p->cur == ',') {
|
|
p->cur++;
|
|
continue;
|
|
}
|
|
if (*p->cur == '}') {
|
|
p->cur++;
|
|
break;
|
|
}
|
|
calogAggFree(agg);
|
|
return calogErrArgE;
|
|
}
|
|
calogValueAgg(out, agg);
|
|
return calogOkE;
|
|
}
|
|
|
|
|
|
static int32_t jsonParseString(JsonParseT *p, CalogValueT *out) {
|
|
JsonBufT buf;
|
|
int32_t status;
|
|
|
|
calogValueNil(out);
|
|
buf.bytes = NULL;
|
|
buf.length = 0;
|
|
buf.cap = 0;
|
|
p->cur++; // consume opening '"'
|
|
while (p->cur < p->end) {
|
|
unsigned char c;
|
|
c = (unsigned char)*p->cur;
|
|
if (c == '"') {
|
|
p->cur++;
|
|
status = calogValueString(out, (buf.bytes != NULL) ? buf.bytes : "", (int64_t)buf.length);
|
|
jsonBufFree(&buf);
|
|
return status;
|
|
}
|
|
if (c == '\\') {
|
|
char e;
|
|
p->cur++;
|
|
if (p->cur >= p->end) {
|
|
jsonBufFree(&buf);
|
|
return calogErrArgE;
|
|
}
|
|
e = *p->cur;
|
|
switch (e) {
|
|
case '"':
|
|
status = jsonBufPutChar(&buf, '"');
|
|
break;
|
|
case '\\':
|
|
status = jsonBufPutChar(&buf, '\\');
|
|
break;
|
|
case '/':
|
|
status = jsonBufPutChar(&buf, '/');
|
|
break;
|
|
case 'b':
|
|
status = jsonBufPutChar(&buf, '\b');
|
|
break;
|
|
case 'f':
|
|
status = jsonBufPutChar(&buf, '\f');
|
|
break;
|
|
case 'n':
|
|
status = jsonBufPutChar(&buf, '\n');
|
|
break;
|
|
case 'r':
|
|
status = jsonBufPutChar(&buf, '\r');
|
|
break;
|
|
case 't':
|
|
status = jsonBufPutChar(&buf, '\t');
|
|
break;
|
|
case 'u':
|
|
status = jsonParseUnicode(p, &buf);
|
|
break;
|
|
default:
|
|
jsonBufFree(&buf);
|
|
return calogErrArgE;
|
|
}
|
|
if (status != calogOkE) {
|
|
jsonBufFree(&buf);
|
|
return status;
|
|
}
|
|
if (e != 'u') {
|
|
p->cur++;
|
|
}
|
|
continue;
|
|
}
|
|
if (c < 0x20) {
|
|
jsonBufFree(&buf);
|
|
return calogErrArgE;
|
|
}
|
|
status = jsonBufPutChar(&buf, (char)c);
|
|
if (status != calogOkE) {
|
|
jsonBufFree(&buf);
|
|
return status;
|
|
}
|
|
p->cur++;
|
|
}
|
|
jsonBufFree(&buf);
|
|
return calogErrArgE;
|
|
}
|
|
|
|
|
|
static int32_t jsonParseUnicode(JsonParseT *p, JsonBufT *buf) {
|
|
uint32_t cp;
|
|
int32_t status;
|
|
|
|
p->cur++; // consume 'u'
|
|
status = jsonHexQuad(p, &cp);
|
|
if (status != calogOkE) {
|
|
return status;
|
|
}
|
|
if (cp >= 0xD800 && cp <= 0xDBFF) {
|
|
uint32_t lo;
|
|
if (p->cur + 1 >= p->end || p->cur[0] != '\\' || p->cur[1] != 'u') {
|
|
return calogErrArgE;
|
|
}
|
|
p->cur += 2; // consume "\u"
|
|
status = jsonHexQuad(p, &lo);
|
|
if (status != calogOkE) {
|
|
return status;
|
|
}
|
|
if (lo < 0xDC00 || lo > 0xDFFF) {
|
|
return calogErrArgE;
|
|
}
|
|
cp = 0x10000 + ((cp - 0xD800) << 10) + (lo - 0xDC00);
|
|
} else if (cp >= 0xDC00 && cp <= 0xDFFF) {
|
|
return calogErrArgE; // an unpaired low surrogate is not a valid code point
|
|
}
|
|
return jsonPutUtf8(buf, cp);
|
|
}
|
|
|
|
|
|
static int32_t jsonParseValue(JsonParseT *p, CalogValueT *out, int32_t depth) {
|
|
calogValueNil(out);
|
|
if (depth >= CALOG_MAX_DEPTH) {
|
|
return calogErrDepthE;
|
|
}
|
|
jsonSkipWs(p);
|
|
if (p->cur >= p->end) {
|
|
return calogErrArgE;
|
|
}
|
|
switch (*p->cur) {
|
|
case '{':
|
|
return jsonParseObject(p, out, depth);
|
|
case '[':
|
|
return jsonParseArray(p, out, depth);
|
|
case '"':
|
|
return jsonParseString(p, out);
|
|
case 't':
|
|
case 'f':
|
|
case 'n':
|
|
return jsonParseLiteral(p, out);
|
|
default:
|
|
return jsonParseNumber(p, out);
|
|
}
|
|
}
|
|
|
|
|
|
static int32_t jsonPutUtf8(JsonBufT *buf, uint32_t cp) {
|
|
char bytes[4];
|
|
size_t count;
|
|
|
|
if (cp < 0x80) {
|
|
bytes[0] = (char)cp;
|
|
count = 1;
|
|
} else if (cp < 0x800) {
|
|
bytes[0] = (char)(0xC0 | (cp >> 6));
|
|
bytes[1] = (char)(0x80 | (cp & 0x3F));
|
|
count = 2;
|
|
} else if (cp < 0x10000) {
|
|
bytes[0] = (char)(0xE0 | (cp >> 12));
|
|
bytes[1] = (char)(0x80 | ((cp >> 6) & 0x3F));
|
|
bytes[2] = (char)(0x80 | (cp & 0x3F));
|
|
count = 3;
|
|
} else {
|
|
bytes[0] = (char)(0xF0 | (cp >> 18));
|
|
bytes[1] = (char)(0x80 | ((cp >> 12) & 0x3F));
|
|
bytes[2] = (char)(0x80 | ((cp >> 6) & 0x3F));
|
|
bytes[3] = (char)(0x80 | (cp & 0x3F));
|
|
count = 4;
|
|
}
|
|
return jsonBufPutBytes(buf, bytes, count);
|
|
}
|
|
|
|
|
|
static void jsonSkipWs(JsonParseT *p) {
|
|
while (p->cur < p->end) {
|
|
char c;
|
|
c = *p->cur;
|
|
if (c == ' ' || c == '\t' || c == '\n' || c == '\r') {
|
|
p->cur++;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static int32_t jsonStringifyNative(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
|
|
JsonBufT buf;
|
|
int32_t status;
|
|
|
|
(void)userData;
|
|
calogValueNil(result);
|
|
if (argCount != 1) {
|
|
return calogFail(result, calogErrArgE, "jsonStringify expects (value)");
|
|
}
|
|
buf.bytes = NULL;
|
|
buf.length = 0;
|
|
buf.cap = 0;
|
|
status = jsonEncode(&buf, &args[0], 0);
|
|
if (status != calogOkE) {
|
|
jsonBufFree(&buf);
|
|
return calogFail(result, status, "jsonStringify: value is not JSON-serializable");
|
|
}
|
|
status = calogValueString(result, (buf.bytes != NULL) ? buf.bytes : "", (int64_t)buf.length);
|
|
jsonBufFree(&buf);
|
|
return status;
|
|
}
|