/* 扁平 JSON 解析 + 定长写入器。契约与「刻意不支持」的清单在 cmd_json.h。 */ #include "cmd_json.h" #include #include static const char *ws(const char *p) { while (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n') { p++; } return p; } /* 读一个 `"..."`,写进 out(容量 cap,含结尾 0)。返回引号之后的位置,失败 NULL。 只收可打印 ASCII 之外不做任何解释:反斜杠与控制字符一律失败。 */ static const char *str(const char *p, char *out, size_t cap) { if (*p != '"') { return NULL; } p++; size_t n = 0; while (*p != '"') { const unsigned char c = (unsigned char)*p; if (c == '\0' || c == '\\' || c < 0x20) { return NULL; } if (n + 1 >= cap) { return NULL; /* 太长:截断会把 "set_voltage_x" 读成别的东西 */ } out[n++] = (char)c; p++; } out[n] = '\0'; return p + 1; } static const char *num(const char *p, int64_t *v) { bool neg = false; if (*p == '-') { neg = true; p++; } if (*p < '0' || *p > '9') { return NULL; } if (*p == '0' && p[1] >= '0' && p[1] <= '9') { return NULL; /* 前导零:JSON 本身就不许,也防 "010" 被当成八进制理解 */ } int64_t acc = 0; int digits = 0; while (*p >= '0' && *p <= '9') { if (++digits > 18) { return NULL; } acc = acc * 10 + (*p - '0'); p++; } if (*p == '.' || *p == 'e' || *p == 'E') { return NULL; /* 浮点:见头文件 */ } *v = neg ? -acc : acc; return p; } static bool word(const char **pp, const char *w) { const size_t n = strlen(w); if (strncmp(*pp, w, n) != 0) { return false; } *pp += n; return true; } static const cj_field_t *find(const cj_obj_t *o, const char *key) { for (int i = 0; i < o->n; i++) { if (strcmp(o->f[i].key, key) == 0) { return &o->f[i]; } } return NULL; } bool cj_parse(const char *line, cj_obj_t *o) { memset(o, 0, sizeof(*o)); if (line == NULL) { return false; } const char *p = ws(line); if (*p++ != '{') { return false; } p = ws(p); if (*p == '}') { p++; } else { for (;;) { if (o->n >= CMD_JSON_MAX_FIELDS) { return false; } cj_field_t *f = &o->f[o->n]; p = str(p, f->key, sizeof(f->key)); if (p == NULL || f->key[0] == '\0' || find(o, f->key) != NULL) { return false; /* 解析失败 / 空键 / 重复键 */ } p = ws(p); if (*p++ != ':') { return false; } p = ws(p); if (*p == '"') { f->type = CJ_STR; p = str(p, f->s, sizeof(f->s)); } else if (word(&p, "true")) { f->type = CJ_BOOL; f->i = 1; } else if (word(&p, "false")) { f->type = CJ_BOOL; f->i = 0; } else { f->type = CJ_INT; p = num(p, &f->i); } if (p == NULL) { return false; } o->n++; p = ws(p); if (*p == ',') { p = ws(p + 1); continue; } if (*p == '}') { p++; break; } return false; } } return *ws(p) == '\0'; /* 右括号之后只许有空白:两条命令粘在一行里是分帧的 bug */ } int cj_get_int(const cj_obj_t *o, const char *key, int64_t *v) { const cj_field_t *f = find(o, key); if (f == NULL) { return 0; } if (f->type != CJ_INT) { return -1; } *v = f->i; return 1; } int cj_get_str(const cj_obj_t *o, const char *key, const char **s) { const cj_field_t *f = find(o, key); if (f == NULL) { return 0; } if (f->type != CJ_STR) { return -1; } *s = f->s; return 1; } int cj_get_bool(const cj_obj_t *o, const char *key, bool *v) { const cj_field_t *f = find(o, key); if (f == NULL) { return 0; } if (f->type != CJ_BOOL) { return -1; /* `"on":1` 不是布尔。收 1 的话 `"on":2` 该算什么就说不清了 */ } *v = f->i != 0; return 1; } void cj_w_init(cj_writer_t *w, char *buf, size_t cap) { w->buf = buf; w->cap = cap; w->pos = 0; w->overflow = (buf == NULL || cap == 0); if (!w->overflow) { buf[0] = '\0'; } } void cj_w(cj_writer_t *w, const char *fmt, ...) { if (w->overflow) { return; } va_list ap; va_start(ap, fmt); const int n = vsnprintf(w->buf + w->pos, w->cap - w->pos, fmt, ap); va_end(ap); if (n < 0 || (size_t)n >= w->cap - w->pos) { w->overflow = true; w->buf[w->pos] = '\0'; /* 把写了一半的那段撤掉 */ return; } w->pos += (size_t)n; }