959 lines
30 KiB
C
959 lines
30 KiB
C
/* json.c - 手写递归下降 JSON 解析器 + 构造器 + 序列化器 */
|
|
#include "json.h"
|
|
#include "i18n.h"
|
|
#include "util.h"
|
|
|
|
#include <ctype.h>
|
|
#include <math.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#define JSON_MAX_DEPTH 200
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* 构造 */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
static JsonValue *jv_new(JsonType t) {
|
|
JsonValue *v = xcalloc(1, sizeof(JsonValue));
|
|
v->type = t;
|
|
return v;
|
|
}
|
|
|
|
JsonValue *json_new_null(void) { return jv_new(JSON_NULL); }
|
|
JsonValue *json_new_array(void) { return jv_new(JSON_ARRAY); }
|
|
JsonValue *json_new_object(void) { return jv_new(JSON_OBJECT); }
|
|
|
|
JsonValue *json_new_bool(int b) {
|
|
JsonValue *v = jv_new(JSON_BOOL);
|
|
v->u.boolean = b ? 1 : 0;
|
|
return v;
|
|
}
|
|
|
|
JsonValue *json_new_number(double n) {
|
|
JsonValue *v = jv_new(JSON_NUMBER);
|
|
v->u.number = n;
|
|
return v;
|
|
}
|
|
|
|
JsonValue *json_new_string(const char *s) {
|
|
JsonValue *v = jv_new(JSON_STRING);
|
|
v->u.string = xstrdup(s ? s : "");
|
|
return v;
|
|
}
|
|
|
|
void json_array_insert(JsonValue *arr, size_t i, JsonValue *item) {
|
|
if (!arr || arr->type != JSON_ARRAY) {
|
|
json_free(item);
|
|
return;
|
|
}
|
|
if (i > arr->u.array.count)
|
|
i = arr->u.array.count;
|
|
if (arr->u.array.count == arr->u.array.cap) {
|
|
arr->u.array.cap = arr->u.array.cap ? arr->u.array.cap * 2 : 8;
|
|
arr->u.array.items = xrealloc(arr->u.array.items,
|
|
arr->u.array.cap * sizeof(JsonValue *));
|
|
}
|
|
memmove(arr->u.array.items + i + 1, arr->u.array.items + i,
|
|
(arr->u.array.count - i) * sizeof(JsonValue *));
|
|
arr->u.array.items[i] = item;
|
|
arr->u.array.count++;
|
|
}
|
|
|
|
void json_array_steal(JsonValue *dst, JsonValue *src) {
|
|
if (!dst || dst->type != JSON_ARRAY || !src || src->type != JSON_ARRAY)
|
|
return;
|
|
if (dst == src)
|
|
return;
|
|
for (size_t i = 0; i < src->u.array.count; i++) {
|
|
if (dst->u.array.count == dst->u.array.cap) {
|
|
dst->u.array.cap = dst->u.array.cap ? dst->u.array.cap * 2 : 8;
|
|
dst->u.array.items = xrealloc(
|
|
dst->u.array.items, dst->u.array.cap * sizeof(JsonValue *));
|
|
}
|
|
dst->u.array.items[dst->u.array.count++] = src->u.array.items[i];
|
|
}
|
|
free(src->u.array.items);
|
|
src->u.array.items = NULL;
|
|
src->u.array.count = 0;
|
|
src->u.array.cap = 0;
|
|
}
|
|
|
|
void json_array_remove(JsonValue *arr, size_t i) {
|
|
if (!arr || arr->type != JSON_ARRAY || i >= arr->u.array.count)
|
|
return;
|
|
json_free(arr->u.array.items[i]);
|
|
memmove(arr->u.array.items + i, arr->u.array.items + i + 1,
|
|
(arr->u.array.count - i - 1) * sizeof(JsonValue *));
|
|
arr->u.array.count--;
|
|
}
|
|
|
|
void json_array_push(JsonValue *arr, JsonValue *item) {
|
|
if (!arr || arr->type != JSON_ARRAY) {
|
|
json_free(item);
|
|
return;
|
|
}
|
|
if (arr->u.array.count == arr->u.array.cap) {
|
|
arr->u.array.cap = arr->u.array.cap ? arr->u.array.cap * 2 : 8;
|
|
arr->u.array.items = xrealloc(arr->u.array.items,
|
|
arr->u.array.cap * sizeof(JsonValue *));
|
|
}
|
|
arr->u.array.items[arr->u.array.count++] = item;
|
|
}
|
|
|
|
void json_object_remove(JsonValue *obj, const char *key) {
|
|
if (!obj || obj->type != JSON_OBJECT)
|
|
return;
|
|
for (size_t i = 0; i < obj->u.object.count; i++) {
|
|
if (strcmp(obj->u.object.members[i].key, key) == 0) {
|
|
free(obj->u.object.members[i].key);
|
|
json_free(obj->u.object.members[i].value);
|
|
memmove(&obj->u.object.members[i], &obj->u.object.members[i + 1],
|
|
(obj->u.object.count - i - 1) * sizeof(JsonMember));
|
|
obj->u.object.count--;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void json_object_set(JsonValue *obj, const char *key, JsonValue *value) {
|
|
if (!obj || obj->type != JSON_OBJECT) {
|
|
json_free(value);
|
|
return;
|
|
}
|
|
for (size_t i = 0; i < obj->u.object.count; i++) {
|
|
if (strcmp(obj->u.object.members[i].key, key) == 0) {
|
|
json_free(obj->u.object.members[i].value);
|
|
obj->u.object.members[i].value = value;
|
|
return;
|
|
}
|
|
}
|
|
if (obj->u.object.count == obj->u.object.cap) {
|
|
obj->u.object.cap = obj->u.object.cap ? obj->u.object.cap * 2 : 8;
|
|
obj->u.object.members = xrealloc(
|
|
obj->u.object.members, obj->u.object.cap * sizeof(JsonMember));
|
|
}
|
|
obj->u.object.members[obj->u.object.count].key = xstrdup(key);
|
|
obj->u.object.members[obj->u.object.count].value = value;
|
|
obj->u.object.count++;
|
|
}
|
|
|
|
void json_object_set_string(JsonValue *obj, const char *key,
|
|
const char *value) {
|
|
json_object_set(obj, key, value ? json_new_string(value) : json_new_null());
|
|
}
|
|
|
|
void json_object_set_number(JsonValue *obj, const char *key, double value) {
|
|
json_object_set(obj, key, json_new_number(value));
|
|
}
|
|
|
|
void json_object_set_bool(JsonValue *obj, const char *key, int value) {
|
|
json_object_set(obj, key, json_new_bool(value));
|
|
}
|
|
|
|
void json_object_set_null(JsonValue *obj, const char *key) {
|
|
json_object_set(obj, key, json_new_null());
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* 释放 */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
void json_free(JsonValue *v) {
|
|
if (!v)
|
|
return;
|
|
switch (v->type) {
|
|
case JSON_STRING:
|
|
free(v->u.string);
|
|
break;
|
|
case JSON_ARRAY:
|
|
for (size_t i = 0; i < v->u.array.count; i++)
|
|
json_free(v->u.array.items[i]);
|
|
free(v->u.array.items);
|
|
break;
|
|
case JSON_OBJECT:
|
|
for (size_t i = 0; i < v->u.object.count; i++) {
|
|
free(v->u.object.members[i].key);
|
|
json_free(v->u.object.members[i].value);
|
|
}
|
|
free(v->u.object.members);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
free(v);
|
|
}
|
|
|
|
JsonValue *json_clone(const JsonValue *v) {
|
|
if (!v)
|
|
return NULL;
|
|
switch (v->type) {
|
|
case JSON_NULL:
|
|
return json_new_null();
|
|
case JSON_BOOL:
|
|
return json_new_bool(v->u.boolean);
|
|
case JSON_NUMBER:
|
|
return json_new_number(v->u.number);
|
|
case JSON_STRING:
|
|
return json_new_string(v->u.string);
|
|
case JSON_ARRAY: {
|
|
JsonValue *a = json_new_array();
|
|
for (size_t i = 0; i < v->u.array.count; i++)
|
|
json_array_push(a, json_clone(v->u.array.items[i]));
|
|
return a;
|
|
}
|
|
case JSON_OBJECT: {
|
|
JsonValue *o = json_new_object();
|
|
for (size_t i = 0; i < v->u.object.count; i++)
|
|
json_object_set(o, v->u.object.members[i].key,
|
|
json_clone(v->u.object.members[i].value));
|
|
return o;
|
|
}
|
|
}
|
|
return json_new_null();
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* 读取 */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
JsonValue *json_object_get(const JsonValue *obj, const char *key) {
|
|
if (!obj || obj->type != JSON_OBJECT || !key)
|
|
return NULL;
|
|
for (size_t i = 0; i < obj->u.object.count; i++)
|
|
if (strcmp(obj->u.object.members[i].key, key) == 0)
|
|
return obj->u.object.members[i].value;
|
|
return NULL;
|
|
}
|
|
|
|
const char *json_string_value(const JsonValue *v) {
|
|
if (!v || v->type != JSON_STRING)
|
|
return NULL;
|
|
return v->u.string;
|
|
}
|
|
|
|
double json_number_value(const JsonValue *v, double def) {
|
|
if (!v || v->type != JSON_NUMBER)
|
|
return def;
|
|
return v->u.number;
|
|
}
|
|
|
|
int json_bool_value(const JsonValue *v, int def) {
|
|
if (!v)
|
|
return def;
|
|
if (v->type == JSON_BOOL)
|
|
return v->u.boolean;
|
|
if (v->type == JSON_NUMBER)
|
|
return v->u.number != 0;
|
|
return def;
|
|
}
|
|
|
|
size_t json_array_size(const JsonValue *v) {
|
|
if (!v || v->type != JSON_ARRAY)
|
|
return 0;
|
|
return v->u.array.count;
|
|
}
|
|
|
|
JsonValue *json_array_get(const JsonValue *v, size_t i) {
|
|
if (!v || v->type != JSON_ARRAY || i >= v->u.array.count)
|
|
return NULL;
|
|
return v->u.array.items[i];
|
|
}
|
|
|
|
const char *json_get_string(const JsonValue *obj, const char *key) {
|
|
return json_string_value(json_object_get(obj, key));
|
|
}
|
|
|
|
double json_get_number(const JsonValue *obj, const char *key, double def) {
|
|
return json_number_value(json_object_get(obj, key), def);
|
|
}
|
|
|
|
int json_get_bool(const JsonValue *obj, const char *key, int def) {
|
|
return json_bool_value(json_object_get(obj, key), def);
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* 解析器 */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
typedef struct {
|
|
const char *start; /* 原文起点, 用来算出行列号 */
|
|
const char *p;
|
|
const char *end;
|
|
char *err;
|
|
size_t errlen;
|
|
int depth;
|
|
int ui; /* 1 = 报错跟随界面语言 (见 jfmt) */
|
|
} Parser;
|
|
|
|
/* 取一条诊断的模板. ui 为 0 时固定用中文那份 —— 解析器的"母语"是中文,
|
|
* 而它同时出现在**给模型看的**工具参数报错里 (agent.c 把 json_parse 的
|
|
* 结论原样回填给模型), 那些不该随界面语言变, 见 README「界面语言」.
|
|
*
|
|
* 表里的 json.* 用的是 printf 转换 (%s/%c/%zu…)而不是 {name} —— 这里是
|
|
* 格式化转发, 不是占位符替换. 两种语言的转换符序列必须逐字一致,
|
|
* tests/test_i18n.c 会机械地比一遍. */
|
|
static const char *jfmt(int ui, const char *key) {
|
|
if (ui)
|
|
return t(key);
|
|
const char *zh = i18n_lookup(LIGENT_LANG_ZH, key);
|
|
return zh ? zh : key;
|
|
}
|
|
|
|
/* 只记录第一个错误; 带上行列号和出错处的原文片段,
|
|
* 否则用户只知道"语法错了"却不知道错在哪 —— 手写 JSON 时这几乎是必然的. */
|
|
LIGENT_PRINTF(2, 3) static void perr(Parser *ps, const char *fmt, ...) {
|
|
if (!ps->err || ps->errlen == 0 || ps->err[0] != '\0')
|
|
return;
|
|
|
|
size_t line = 1, col = 1;
|
|
for (const char *q = ps->start; q < ps->p && q < ps->end; q++) {
|
|
if (*q == '\n') {
|
|
line++;
|
|
col = 1;
|
|
} else
|
|
col++;
|
|
}
|
|
|
|
char detail[192];
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
vsnprintf(detail, sizeof detail, fmt, ap);
|
|
va_end(ap);
|
|
|
|
/* 出错位置开始的一小段原文 (控制字符替换掉, 避免把终端搞乱) */
|
|
char excerpt[40];
|
|
size_t n = 0;
|
|
for (const char *q = ps->p; q < ps->end && n < 24; q++) {
|
|
unsigned char c = (unsigned char)*q;
|
|
if (c == '\n')
|
|
break;
|
|
excerpt[n++] = c < 0x20 || c == 0x7f ? '?' : (char)c;
|
|
}
|
|
excerpt[n] = '\0';
|
|
|
|
/* 模板里是同序的 %zu/%zu/%s (+%s)—— 两种语言必须完全一致 */
|
|
if (n > 0)
|
|
snprintf(ps->err, ps->errlen, jfmt(ps->ui, "json.err.at_got"), line,
|
|
col, detail, excerpt);
|
|
else
|
|
snprintf(ps->err, ps->errlen, jfmt(ps->ui, "json.err.at_end"), line,
|
|
col, detail);
|
|
|
|
/* 最后统一净化: 错误信息里不允许出现原始控制字符.
|
|
* 像 "意外的字符 '%c'" 这种格式化会直接把控制字符带进消息本体,
|
|
* 原样打到终端上会把用户的屏幕搞乱. */
|
|
for (char *q = ps->err; *q; q++) {
|
|
unsigned char c = (unsigned char)*q;
|
|
if (c < 0x20 || c == 0x7f)
|
|
*q = '?';
|
|
}
|
|
}
|
|
|
|
static void skip_ws(Parser *ps) {
|
|
while (ps->p < ps->end) {
|
|
char c = *ps->p;
|
|
if (c == ' ' || c == '\t' || c == '\n' || c == '\r')
|
|
ps->p++;
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
|
|
static JsonValue *parse_value(Parser *ps);
|
|
|
|
/* 解析一个 JSON 字符串内容 (ps->p 指向起始双引号之后), 写入 sb */
|
|
static int parse_string_raw(Parser *ps, StrBuf *sb, char *errb, size_t errlen) {
|
|
(void)errb;
|
|
(void)errlen;
|
|
while (ps->p < ps->end) {
|
|
unsigned char c = (unsigned char)*ps->p++;
|
|
if (c == '"')
|
|
return 0;
|
|
if (c == '\\') {
|
|
if (ps->p >= ps->end) {
|
|
perr(ps, "%s",
|
|
jfmt(ps->ui, "json.err.string_escape_truncated"));
|
|
return -1;
|
|
}
|
|
char e = *ps->p++;
|
|
switch (e) {
|
|
case '"':
|
|
sb_putc(sb, '"');
|
|
break;
|
|
case '\\':
|
|
sb_putc(sb, '\\');
|
|
break;
|
|
case '/':
|
|
sb_putc(sb, '/');
|
|
break;
|
|
case 'b':
|
|
sb_putc(sb, '\b');
|
|
break;
|
|
case 'f':
|
|
sb_putc(sb, '\f');
|
|
break;
|
|
case 'n':
|
|
sb_putc(sb, '\n');
|
|
break;
|
|
case 'r':
|
|
sb_putc(sb, '\r');
|
|
break;
|
|
case 't':
|
|
sb_putc(sb, '\t');
|
|
break;
|
|
case 'u': {
|
|
unsigned int cp = 0;
|
|
if (ps->end - ps->p < 4) {
|
|
perr(ps, "%s", jfmt(ps->ui, "json.err.u_incomplete"));
|
|
return -1;
|
|
}
|
|
for (int i = 0; i < 4; i++) {
|
|
char h = ps->p[i];
|
|
cp <<= 4;
|
|
if (h >= '0' && h <= '9')
|
|
cp |= (unsigned)(h - '0');
|
|
else if (h >= 'a' && h <= 'f')
|
|
cp |= (unsigned)(h - 'a' + 10);
|
|
else if (h >= 'A' && h <= 'F')
|
|
cp |= (unsigned)(h - 'A' + 10);
|
|
else {
|
|
perr(ps, jfmt(ps->ui, "json.err.u_bad_hex"), h);
|
|
return -1;
|
|
}
|
|
}
|
|
ps->p += 4;
|
|
/* 代理对 */
|
|
if (cp >= 0xD800 && cp <= 0xDBFF && ps->end - ps->p >= 6 &&
|
|
ps->p[0] == '\\' && ps->p[1] == 'u') {
|
|
unsigned int lo = 0;
|
|
int ok = 1;
|
|
for (int i = 0; i < 4; i++) {
|
|
char h = ps->p[2 + i];
|
|
lo <<= 4;
|
|
if (h >= '0' && h <= '9')
|
|
lo |= (unsigned)(h - '0');
|
|
else if (h >= 'a' && h <= 'f')
|
|
lo |= (unsigned)(h - 'a' + 10);
|
|
else if (h >= 'A' && h <= 'F')
|
|
lo |= (unsigned)(h - 'A' + 10);
|
|
else {
|
|
ok = 0;
|
|
break;
|
|
}
|
|
}
|
|
if (ok && lo >= 0xDC00 && lo <= 0xDFFF) {
|
|
cp = 0x10000 + ((cp - 0xD800) << 10) + (lo - 0xDC00);
|
|
ps->p += 6;
|
|
}
|
|
}
|
|
/* 编码为 UTF-8 */
|
|
if (cp < 0x80) {
|
|
sb_putc(sb, (char)cp);
|
|
} else if (cp < 0x800) {
|
|
sb_putc(sb, (char)(0xC0 | (cp >> 6)));
|
|
sb_putc(sb, (char)(0x80 | (cp & 0x3F)));
|
|
} else if (cp < 0x10000) {
|
|
sb_putc(sb, (char)(0xE0 | (cp >> 12)));
|
|
sb_putc(sb, (char)(0x80 | ((cp >> 6) & 0x3F)));
|
|
sb_putc(sb, (char)(0x80 | (cp & 0x3F)));
|
|
} else {
|
|
sb_putc(sb, (char)(0xF0 | (cp >> 18)));
|
|
sb_putc(sb, (char)(0x80 | ((cp >> 12) & 0x3F)));
|
|
sb_putc(sb, (char)(0x80 | ((cp >> 6) & 0x3F)));
|
|
sb_putc(sb, (char)(0x80 | (cp & 0x3F)));
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
perr(ps, jfmt(ps->ui, "json.err.unknown_escape"), e);
|
|
return -1;
|
|
}
|
|
} else if (c < 0x20) {
|
|
perr(ps, jfmt(ps->ui, "json.err.raw_control"), c);
|
|
return -1;
|
|
} else {
|
|
sb_putc(sb, (char)c);
|
|
}
|
|
}
|
|
perr(ps, "%s", jfmt(ps->ui, "json.err.string_unterminated"));
|
|
return -1;
|
|
}
|
|
|
|
static JsonValue *parse_string(Parser *ps) {
|
|
StrBuf sb;
|
|
sb_init(&sb);
|
|
if (parse_string_raw(ps, &sb, NULL, 0) != 0) {
|
|
sb_free(&sb);
|
|
return NULL;
|
|
}
|
|
JsonValue *v = jv_new(JSON_STRING);
|
|
v->u.string = sb_detach(&sb);
|
|
return v;
|
|
}
|
|
|
|
/* 严格按 RFC 8259 扫描一个数字:
|
|
* -?(0|[1-9][0-9]*)(\.[0-9]+)?([eE][+-]?[0-9]+)?
|
|
*
|
|
* 以前这里是"能吃多少吃多少", 于是 01、5.、1e、1e+ 全都被当成合法数字 ——
|
|
* 而 strtod("1e") 会老老实实返回 1, 那个残缺的指数就被静默吞掉了.
|
|
* 配置文件和存档都是手写的, 把写错的数字当成另一个数继续用下去, 用户只会在
|
|
* 很久之后从一个完全不相干的现象里发现它. 所以语法必须自己严查一遍:
|
|
* strtod 只负责最后那步文本 -> double 的转换. */
|
|
static JsonValue *parse_number(Parser *ps) {
|
|
const char *start = ps->p;
|
|
|
|
if (ps->p < ps->end && *ps->p == '-')
|
|
ps->p++;
|
|
|
|
if (ps->p >= ps->end || !isdigit((unsigned char)*ps->p))
|
|
goto bad;
|
|
if (*ps->p == '0')
|
|
ps->p++; /* '0' 后面不能再跟数字 (01 非法) */
|
|
else
|
|
while (ps->p < ps->end && isdigit((unsigned char)*ps->p))
|
|
ps->p++;
|
|
|
|
if (ps->p < ps->end && *ps->p == '.') {
|
|
ps->p++;
|
|
if (ps->p >= ps->end || !isdigit((unsigned char)*ps->p))
|
|
goto bad;
|
|
while (ps->p < ps->end && isdigit((unsigned char)*ps->p))
|
|
ps->p++;
|
|
}
|
|
if (ps->p < ps->end && (*ps->p == 'e' || *ps->p == 'E')) {
|
|
ps->p++;
|
|
if (ps->p < ps->end && (*ps->p == '+' || *ps->p == '-'))
|
|
ps->p++;
|
|
if (ps->p >= ps->end || !isdigit((unsigned char)*ps->p))
|
|
goto bad;
|
|
while (ps->p < ps->end && isdigit((unsigned char)*ps->p))
|
|
ps->p++;
|
|
}
|
|
|
|
{
|
|
char *tmp = xstrndup(start, (size_t)(ps->p - start));
|
|
char *endp = NULL;
|
|
double d = strtod(tmp, &endp);
|
|
int all = (endp && *endp == '\0');
|
|
free(tmp);
|
|
if (!all)
|
|
goto bad; /* 语法扫过了 strtod 却不认: 不该发生 */
|
|
return json_new_number(d);
|
|
}
|
|
|
|
bad:
|
|
/* 回退到数字的起点再报错: 这样行列号指向"这个数字从哪开始",
|
|
* 而不是指向我们自己刚消费掉的那个字节. */
|
|
{
|
|
char *tmp = xstrndup(start, (size_t)(ps->p - start));
|
|
ps->p = start;
|
|
perr(ps, jfmt(ps->ui, "json.err.bad_number_got"), tmp);
|
|
free(tmp);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* 解析 ps->p 指向的 "key" */
|
|
static char *parse_key(Parser *ps) {
|
|
if (ps->p >= ps->end || *ps->p != '"') {
|
|
perr(ps, "%s", jfmt(ps->ui, "json.err.want_key"));
|
|
return NULL;
|
|
}
|
|
ps->p++;
|
|
StrBuf sb;
|
|
sb_init(&sb);
|
|
if (parse_string_raw(ps, &sb, NULL, 0) != 0) {
|
|
sb_free(&sb);
|
|
return NULL;
|
|
}
|
|
return sb_detach(&sb);
|
|
}
|
|
|
|
static JsonValue *parse_array(Parser *ps) {
|
|
JsonValue *arr = json_new_array();
|
|
ps->p++; /* '[' */
|
|
skip_ws(ps);
|
|
if (ps->p < ps->end && *ps->p == ']') {
|
|
ps->p++;
|
|
return arr;
|
|
}
|
|
for (;;) {
|
|
skip_ws(ps);
|
|
JsonValue *item = parse_value(ps);
|
|
if (!item) {
|
|
json_free(arr);
|
|
return NULL;
|
|
}
|
|
json_array_push(arr, item);
|
|
skip_ws(ps);
|
|
if (ps->p >= ps->end) {
|
|
perr(ps, "%s", jfmt(ps->ui, "json.err.array_unterminated"));
|
|
json_free(arr);
|
|
return NULL;
|
|
}
|
|
if (*ps->p == ',') {
|
|
ps->p++;
|
|
continue;
|
|
}
|
|
if (*ps->p == ']') {
|
|
ps->p++;
|
|
return arr;
|
|
}
|
|
perr(ps, jfmt(ps->ui, "json.err.want_comma_in_array"), *ps->p);
|
|
json_free(arr);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
static JsonValue *parse_object(Parser *ps) {
|
|
JsonValue *obj = json_new_object();
|
|
ps->p++; /* '{' */
|
|
skip_ws(ps);
|
|
if (ps->p < ps->end && *ps->p == '}') {
|
|
ps->p++;
|
|
return obj;
|
|
}
|
|
for (;;) {
|
|
skip_ws(ps);
|
|
char *key = parse_key(ps);
|
|
if (!key) {
|
|
json_free(obj);
|
|
return NULL;
|
|
}
|
|
skip_ws(ps);
|
|
if (ps->p >= ps->end || *ps->p != ':') {
|
|
perr(ps, jfmt(ps->ui, "json.err.want_colon"), key);
|
|
free(key);
|
|
json_free(obj);
|
|
return NULL;
|
|
}
|
|
ps->p++;
|
|
skip_ws(ps);
|
|
JsonValue *val = parse_value(ps);
|
|
if (!val) {
|
|
free(key);
|
|
json_free(obj);
|
|
return NULL;
|
|
}
|
|
json_object_set(obj, key, val);
|
|
free(key);
|
|
skip_ws(ps);
|
|
if (ps->p >= ps->end) {
|
|
perr(ps, "%s", jfmt(ps->ui, "json.err.object_unterminated"));
|
|
json_free(obj);
|
|
return NULL;
|
|
}
|
|
if (*ps->p == ',') {
|
|
ps->p++;
|
|
continue;
|
|
}
|
|
if (*ps->p == '}') {
|
|
ps->p++;
|
|
return obj;
|
|
}
|
|
perr(ps, jfmt(ps->ui, "json.err.want_comma_in_object"), *ps->p);
|
|
json_free(obj);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
static JsonValue *parse_value(Parser *ps) {
|
|
if (++ps->depth > JSON_MAX_DEPTH) {
|
|
perr(ps, jfmt(ps->ui, "json.err.too_deep"), JSON_MAX_DEPTH);
|
|
ps->depth--;
|
|
return NULL;
|
|
}
|
|
skip_ws(ps);
|
|
JsonValue *result = NULL;
|
|
if (ps->p >= ps->end) {
|
|
perr(ps, "%s", jfmt(ps->ui, "json.err.unexpected_eof"));
|
|
goto done;
|
|
}
|
|
switch (*ps->p) {
|
|
case '{':
|
|
result = parse_object(ps);
|
|
break;
|
|
case '[':
|
|
result = parse_array(ps);
|
|
break;
|
|
case '"':
|
|
ps->p++;
|
|
result = parse_string(ps);
|
|
break;
|
|
case 't':
|
|
if (ps->end - ps->p >= 4 && memcmp(ps->p, "true", 4) == 0) {
|
|
ps->p += 4;
|
|
result = json_new_bool(1);
|
|
} else
|
|
perr(ps, "%s", jfmt(ps->ui, "json.err.want_true"));
|
|
break;
|
|
case 'f':
|
|
if (ps->end - ps->p >= 5 && memcmp(ps->p, "false", 5) == 0) {
|
|
ps->p += 5;
|
|
result = json_new_bool(0);
|
|
} else
|
|
perr(ps, "%s", jfmt(ps->ui, "json.err.want_false"));
|
|
break;
|
|
case 'n':
|
|
if (ps->end - ps->p >= 4 && memcmp(ps->p, "null", 4) == 0) {
|
|
ps->p += 4;
|
|
result = json_new_null();
|
|
} else
|
|
perr(ps, "%s", jfmt(ps->ui, "json.err.want_null"));
|
|
break;
|
|
default:
|
|
if (*ps->p == '-' || isdigit((unsigned char)*ps->p))
|
|
result = parse_number(ps);
|
|
else
|
|
perr(ps, jfmt(ps->ui, "json.err.unexpected_char"), *ps->p);
|
|
break;
|
|
}
|
|
done:
|
|
ps->depth--;
|
|
return result;
|
|
}
|
|
|
|
/* ui != 0: 报错跟随界面语言; = 0: 固定中文 (给模型看的那份).
|
|
* 见上面 jfmt 的说明与 README「界面语言」. */
|
|
static JsonValue *parse_impl(const char *text, char *errbuf, size_t errlen,
|
|
int ui) {
|
|
if (errbuf && errlen)
|
|
errbuf[0] = '\0';
|
|
if (!text) {
|
|
if (errbuf && errlen)
|
|
snprintf(errbuf, errlen, "%s", jfmt(ui, "json.err.empty_input"));
|
|
return NULL;
|
|
}
|
|
Parser ps;
|
|
ps.start = text;
|
|
ps.p = text;
|
|
ps.end = text + strlen(text);
|
|
ps.err = errbuf;
|
|
ps.errlen = errlen;
|
|
ps.depth = 0;
|
|
ps.ui = ui;
|
|
|
|
JsonValue *v = parse_value(&ps);
|
|
if (!v)
|
|
return NULL;
|
|
skip_ws(&ps);
|
|
if (ps.p != ps.end) {
|
|
perr(&ps, jfmt(ps.ui, "json.err.trailing"), ps.p);
|
|
json_free(v);
|
|
return NULL;
|
|
}
|
|
return v;
|
|
}
|
|
|
|
JsonValue *json_parse(const char *text, char *errbuf, size_t errlen) {
|
|
return parse_impl(text, errbuf, errlen, 0);
|
|
}
|
|
|
|
JsonValue *json_parse_ui(const char *text, char *errbuf, size_t errlen) {
|
|
return parse_impl(text, errbuf, errlen, 1);
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* 序列化 */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
static void dump_string(StrBuf *sb, const char *s) {
|
|
size_t len = strlen(s), i = 0;
|
|
sb_putc(sb, '"');
|
|
while (i < len) {
|
|
unsigned char c = (unsigned char)s[i];
|
|
const char *esc = NULL;
|
|
switch (c) {
|
|
case '"':
|
|
esc = "\\\"";
|
|
break;
|
|
case '\\':
|
|
esc = "\\\\";
|
|
break;
|
|
case '\b':
|
|
esc = "\\b";
|
|
break;
|
|
case '\f':
|
|
esc = "\\f";
|
|
break;
|
|
case '\n':
|
|
esc = "\\n";
|
|
break;
|
|
case '\r':
|
|
esc = "\\r";
|
|
break;
|
|
case '\t':
|
|
esc = "\\t";
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (esc) {
|
|
sb_puts(sb, esc);
|
|
i++;
|
|
continue;
|
|
}
|
|
if (c < 0x20) {
|
|
sb_printf(sb, "\\u%04x", c);
|
|
i++;
|
|
continue;
|
|
}
|
|
if (c < 0x80) {
|
|
sb_putc(sb, (char)c);
|
|
i++;
|
|
continue;
|
|
}
|
|
|
|
/* 非 ASCII 字节: 只有构成**合法** UTF-8 序列时才能原样进 JSON 字符串.
|
|
* 以前这里是无条件 sb_putc, 于是工具输出里一个坏字节 (例如某条命令在
|
|
* 非 UTF-8 locale 下打出来的路径, 或者被截断的多字节字符)就会让**整个
|
|
* 请求体**不是合法 UTF-8, 服务端直接回
|
|
* 400 ... invalid unicode code point
|
|
* 更糟的是那段坏数据已经进了对话历史, 之后每次请求都会失败 ——
|
|
* 除非 /reset 把整轮对话丢掉.
|
|
*
|
|
* 兜在出口而不是只在入口清理, 是因为出口才是能保证"发出去的永远合法"
|
|
* 的那一层: 对话内容还可能来自存档、粘贴、/include 进来的文件……
|
|
* 这里统一把非法字节换成 U+FFFD, 既让请求成立, 也让模型看到"这里有个
|
|
* 无法解码的字符", 而不是收到一段乱码. */
|
|
size_t n = utf8_seq_len(s + i, len - i);
|
|
if (n == 0) {
|
|
sb_puts(sb, "\\ufffd");
|
|
i++;
|
|
continue;
|
|
}
|
|
/* U+FFFD 一律写成 \ufffd 转义. 它是上面替换坏字节时用的字符, 如果这里
|
|
* 让 EF BF BD 原样输出, dump→parse→dump 就不稳定了: 第一次是 \ufffd,
|
|
* 解析回来变成真的 U+FFFD, 第二次就原样吐三个字节.
|
|
* fuzz 的"dump 幂等"这条不变量就是被这个当场抓住的 —— 幂等是有价值的
|
|
* 性质, 它保证存档反复读写在字节层面也是收敛的. 给这一个字符定死一个
|
|
* 规范形式, 代价是真正合法的 U+FFFD 会多占 3 个字节, 可以忽略. */
|
|
if (n == 3 && (unsigned char)s[i] == 0xEF &&
|
|
(unsigned char)s[i + 1] == 0xBF &&
|
|
(unsigned char)s[i + 2] == 0xBD) {
|
|
sb_puts(sb, "\\ufffd");
|
|
i += 3;
|
|
continue;
|
|
}
|
|
sb_append(sb, s + i, n);
|
|
i += n;
|
|
}
|
|
sb_putc(sb, '"');
|
|
}
|
|
|
|
static void dump_number(StrBuf *sb, double n) {
|
|
if (isnan(n) || isinf(n)) {
|
|
sb_puts(sb, "null");
|
|
return;
|
|
}
|
|
char buf[64];
|
|
/* 先判范围, 再转换. double -> long long 在值超出范围时是 UB, 而 && 的
|
|
* 求值顺序保不住这个转换: 原来它写在条件最左边, 服务端回一个 1e300
|
|
* 就会踩到 (clang 的 UBSan 报 float-cast-overflow; gcc 不插桩这条转换,
|
|
* 所以 make SAN=1 抓不到). */
|
|
if (fabs(n) < 1e15 && n == (double)(long long)n)
|
|
snprintf(buf, sizeof buf, "%lld", (long long)n);
|
|
else
|
|
snprintf(buf, sizeof buf, "%.17g", n);
|
|
sb_puts(sb, buf);
|
|
}
|
|
|
|
static void dump_value(StrBuf *sb, const JsonValue *v, int indent, int pretty,
|
|
int level) {
|
|
if (!v) {
|
|
sb_puts(sb, "null");
|
|
return;
|
|
}
|
|
|
|
switch (v->type) {
|
|
case JSON_NULL:
|
|
sb_puts(sb, "null");
|
|
break;
|
|
case JSON_BOOL:
|
|
sb_puts(sb, v->u.boolean ? "true" : "false");
|
|
break;
|
|
case JSON_NUMBER:
|
|
dump_number(sb, v->u.number);
|
|
break;
|
|
case JSON_STRING:
|
|
dump_string(sb, v->u.string);
|
|
break;
|
|
|
|
case JSON_ARRAY:
|
|
if (v->u.array.count == 0) {
|
|
sb_puts(sb, "[]");
|
|
break;
|
|
}
|
|
sb_putc(sb, '[');
|
|
for (size_t i = 0; i < v->u.array.count; i++) {
|
|
if (i)
|
|
sb_putc(sb, ',');
|
|
if (pretty) {
|
|
sb_putc(sb, '\n');
|
|
for (int k = 0; k < (level + 1) * indent; k++)
|
|
sb_putc(sb, ' ');
|
|
}
|
|
dump_value(sb, v->u.array.items[i], indent, pretty, level + 1);
|
|
}
|
|
if (pretty) {
|
|
sb_putc(sb, '\n');
|
|
for (int k = 0; k < level * indent; k++)
|
|
sb_putc(sb, ' ');
|
|
}
|
|
sb_putc(sb, ']');
|
|
break;
|
|
|
|
case JSON_OBJECT:
|
|
if (v->u.object.count == 0) {
|
|
sb_puts(sb, "{}");
|
|
break;
|
|
}
|
|
sb_putc(sb, '{');
|
|
for (size_t i = 0; i < v->u.object.count; i++) {
|
|
if (i)
|
|
sb_putc(sb, ',');
|
|
if (pretty) {
|
|
sb_putc(sb, '\n');
|
|
for (int k = 0; k < (level + 1) * indent; k++)
|
|
sb_putc(sb, ' ');
|
|
}
|
|
dump_string(sb, v->u.object.members[i].key);
|
|
sb_putc(sb, ':');
|
|
if (pretty)
|
|
sb_putc(sb, ' ');
|
|
dump_value(sb, v->u.object.members[i].value, indent, pretty,
|
|
level + 1);
|
|
}
|
|
if (pretty) {
|
|
sb_putc(sb, '\n');
|
|
for (int k = 0; k < level * indent; k++)
|
|
sb_putc(sb, ' ');
|
|
}
|
|
sb_putc(sb, '}');
|
|
break;
|
|
}
|
|
}
|
|
|
|
char *json_dump(const JsonValue *v) {
|
|
StrBuf sb;
|
|
sb_init(&sb);
|
|
dump_value(&sb, v, 0, 0, 0);
|
|
return sb_detach(&sb);
|
|
}
|
|
|
|
char *json_dump_pretty(const JsonValue *v) {
|
|
StrBuf sb;
|
|
sb_init(&sb);
|
|
dump_value(&sb, v, 2, 1, 0);
|
|
return sb_detach(&sb);
|
|
}
|
|
|
|
char *json_quote_string(const char *s) {
|
|
StrBuf sb;
|
|
sb_init(&sb);
|
|
dump_string(&sb, s ? s : "");
|
|
return sb_detach(&sb);
|
|
}
|