diff options
Diffstat (limited to 'json/src/json.c')
| -rw-r--r-- | json/src/json.c | 289 |
1 files changed, 289 insertions, 0 deletions
diff --git a/json/src/json.c b/json/src/json.c new file mode 100644 index 0000000..cc85d51 --- /dev/null +++ b/json/src/json.c @@ -0,0 +1,289 @@ +#include <lcq/json.h> + +enum json_value_buf_type { + JSON_VALUE_BUF_TYPE_TERMINATOR=0x00, + JSON_VALUE_BUF_TYPE_NUMBER_ONEBYTE_POS=0x10, + JSON_VALUE_BUF_TYPE_NUMBER_MULTIBYTE_POS=0x20, + JSON_VALUE_BUF_TYPE_NUMBER_ONEBYTE_NEG=0x30, + JSON_VALUE_BUF_TYPE_NUMBER_MULTIBYTE_NEG=0x40, + JSON_VALUE_BUF_TYPE_STRING=0x50, + JSON_VALUE_BUF_TYPE_ARRAY=0x60, + JSON_VALUE_BUF_TYPE_OBJECT=0x70, + JSON_VALUE_BUF_TYPE_SEPARATOR=0x80, + JSON_VALUE_BUF_TYPE_SKIP=0xf0, +}; + +char *TOKEN_NAMES[] = { + [JSON_TOKEN_NUMBER_INT] = "int", + [JSON_TOKEN_NUMBER_FLOAT] = "float", + [JSON_TOKEN_STRING] = "string", + [JSON_TOKEN_LSQUARE] = "[", + [JSON_TOKEN_RSQUARE] = "]", + [JSON_TOKEN_LCURLY] = "{", + [JSON_TOKEN_RCURLY] = "}", + [JSON_TOKEN_COMMA] = ",", + [JSON_TOKEN_COLON] = ":", +}; +char *json_token_name(json_token t) { + if (t <= JSON_TOKEN__START) return "error"; + return TOKEN_NAMES[t]; +} + +void json_lexer_new(json_lexer *l, u8 *buf, i64 size) { + l->buf = buf; + l->size = size; + l->start = 0; + l->end = 0; +} + +static bool lex_is_more_input(json_lexer *l) { return l && l->end < l->size; } +static i64 lex_peek(json_lexer *l) { return l->buf[l->end]; } +static i64 lex_advance(json_lexer *l) { + if (!lex_is_more_input(l)) return -1; + return l->buf[l->end++]; +} +static bool lex_match(json_lexer *l, i64 x) { + if (lex_peek(l) == x) { lex_advance(l); return true; } + return false; +} +static bool lex_match_pred(json_lexer *l, bool (*f)(i64)) { + if (f(lex_peek(l))) { lex_advance(l); return true; } + return false; +} + +static bool is_digit(i64 x) { return x >= '0' && x <= '9'; } +static void consume_digits(json_lexer *l) { while (lex_match_pred(l, is_digit)); } +static json_token consume_float(json_lexer *l) { + json_token ret = JSON_TOKEN_NUMBER_INT; + consume_digits(l); + if (lex_match(l, '.')) { consume_digits(l); ret = JSON_TOKEN_NUMBER_FLOAT; } + if (lex_match(l, 'e') || lex_match(l, 'E')) { + (void) (lex_match(l, '+') || lex_match(l, '-')); + consume_digits(l); + ret = JSON_TOKEN_NUMBER_FLOAT; + } + return ret; +} + +json_token json_lex(json_lexer *l) { +start: + l->start = l->end; + i64 cur = lex_advance(l); + switch (cur) { + case '[': return JSON_TOKEN_LSQUARE; + case ']': return JSON_TOKEN_RSQUARE; + case '{': return JSON_TOKEN_LCURLY; + case '}': return JSON_TOKEN_RCURLY; + case ',': return JSON_TOKEN_COMMA; + case ':': return JSON_TOKEN_COLON; + case '"': { + bool escaped = false; + while ((cur = lex_advance(l))) { + if (escaped) { + escaped = false; + } else { + if (cur == '"') break; + if (cur == '\\') escaped = true; + } + } + if (cur < -1) return JSON_TOKEN__ERROR; + return JSON_TOKEN_STRING; + } + case ' ': case '\t': case '\n': case '\r': + l->buf[l->start] = JSON_VALUE_BUF_TYPE_SKIP; + goto start; + case '-': + if (lex_match_pred(l, is_digit)) { return consume_float(l); } + return JSON_TOKEN__ERROR; + default: + if (is_digit(cur)) { return consume_float(l); } + return JSON_TOKEN__ERROR; + } +} + +void json_parser_new(json_parser *p, u8 *buf, i64 size) { + json_lexer_new(&p->lex, buf, size); + p->curstart = 0; + p->curend = 0; + p->next = json_lex(&p->lex); +} + +static json_token parse_advance(json_parser *p) { + if (p->next <= JSON_TOKEN__START) return p->next; + json_token ret = p->next; + p->curstart = p->lex.start; + p->curend = p->lex.end; + p->next = json_lex(&p->lex); + return ret; +} +static json_token parse_peek(json_parser *p) { return p->next; } + +static json_value val(i64 idx) { json_value ret; ret.idx = idx; return ret; } + +static json_value install_string(json_parser *p, i64 start, i64 end) { + p->lex.buf[start] = JSON_VALUE_BUF_TYPE_STRING; + p->lex.buf[end - 1] = JSON_VALUE_BUF_TYPE_TERMINATOR; + return val(start); +} +static json_value install_number_int(json_parser *p, i64 start, i64 end) { + i64 x = 0; + bool pos = true; + i64 i = start; + if (p->lex.buf[i] == '-') { pos = false; i += 1; } + for (; i < end; ++i) { + x *= 10; + x += p->lex.buf[i] - '0'; + } + if (x < 16) { + p->lex.buf[start] = + (pos + ? JSON_VALUE_BUF_TYPE_NUMBER_ONEBYTE_POS + : JSON_VALUE_BUF_TYPE_NUMBER_ONEBYTE_NEG) + | (u8) x; + } else { + i64 off = 1; + for (; x > 0; off += 1, x >>= 8) { + p->lex.buf[start + off] = (u8) x & 0xff; + } + p->lex.buf[start] = + (pos + ? JSON_VALUE_BUF_TYPE_NUMBER_MULTIBYTE_POS + : JSON_VALUE_BUF_TYPE_NUMBER_MULTIBYTE_NEG) + | (u8) (off - 1); + } + return val(start); +} +static json_value install_array(json_parser *p, i64 start, i64 end) { + p->lex.buf[start] = JSON_VALUE_BUF_TYPE_ARRAY; + p->lex.buf[end - 1] = JSON_VALUE_BUF_TYPE_TERMINATOR; + return val(start); +} + +json_value_type json_value_get_type(json_parser *p, json_value v) { + if (v.idx < 0 || v.idx >= p->lex.size) return JSON_VALUE_TYPE__ERROR; + enum json_value_buf_type bt = p->lex.buf[v.idx] & 0xf0; + switch (bt) { + case JSON_VALUE_BUF_TYPE_NUMBER_ONEBYTE_POS: + case JSON_VALUE_BUF_TYPE_NUMBER_MULTIBYTE_POS: + case JSON_VALUE_BUF_TYPE_NUMBER_ONEBYTE_NEG: + case JSON_VALUE_BUF_TYPE_NUMBER_MULTIBYTE_NEG: + return JSON_VALUE_TYPE_NUMBER; + case JSON_VALUE_BUF_TYPE_STRING: return JSON_VALUE_TYPE_STRING; + case JSON_VALUE_BUF_TYPE_ARRAY: return JSON_VALUE_TYPE_ARRAY; + case JSON_VALUE_BUF_TYPE_OBJECT: return JSON_VALUE_TYPE_OBJECT; + default: return JSON_VALUE_TYPE__ERROR; + } +} +char *json_value_as_string(json_parser *p, json_value v) { + if (json_value_get_type(p, v) != JSON_VALUE_TYPE_STRING) return NULL; + return (char *) &p->lex.buf[v.idx + 1]; +} +i64 json_value_as_number(json_parser *p, json_value v) { + enum json_value_buf_type bt = p->lex.buf[v.idx] & 0xf0; + bool pos = true; + bool multibyte = true; + switch (bt) { + case JSON_VALUE_BUF_TYPE_NUMBER_ONEBYTE_POS: + multibyte = false; + // fallthrough + case JSON_VALUE_BUF_TYPE_NUMBER_MULTIBYTE_POS: + break; + case JSON_VALUE_BUF_TYPE_NUMBER_ONEBYTE_NEG: + multibyte = false; + // fallthrough + case JSON_VALUE_BUF_TYPE_NUMBER_MULTIBYTE_NEG: + pos = false; + break; + default: return 0; + } + if (multibyte) { + i64 x = 0; + i64 bs = p->lex.buf[v.idx] & 0x0f; + for (i64 off = 0; off < bs; ++off) { + x |= ((i64) p->lex.buf[v.idx + 1 + off]) << (off * 8); + } + return (i64) (pos ? 1 : -1) * x; + } else { + i64 x = p->lex.buf[v.idx] & 0x0f; + return (i64) (pos ? 1 : -1) * x; + } +} +json_array_iter json_value_as_array(json_parser *p, json_value v) { + json_array_iter ret; + if (json_value_get_type(p, v) != JSON_VALUE_TYPE_ARRAY) { ret.idx = -1; return ret; } + ret.idx = v.idx + 1; + return ret; +} +json_value json_array_iter_get(json_parser *p, json_array_iter i) { + if (i.idx < 0 || i.idx >= p->lex.size) return val(-1); + return val(i.idx); +} +bool json_array_iter_next(json_parser *p, json_array_iter *i) { + i64 nesting = 0; + bool in_string = false; + while (i->idx < p->lex.size) { + u8 b = p->lex.buf[i->idx]; + enum json_value_buf_type bt = b & 0xf0; + i->idx += 1; + if (in_string) { + if (bt == JSON_VALUE_BUF_TYPE_TERMINATOR) in_string = false; + } else { + switch (bt) { + case JSON_VALUE_BUF_TYPE_STRING: in_string = true; break; + case JSON_VALUE_BUF_TYPE_NUMBER_MULTIBYTE_POS: + case JSON_VALUE_BUF_TYPE_NUMBER_MULTIBYTE_NEG: + i->idx += b & 0x0f; + break; + case JSON_VALUE_BUF_TYPE_ARRAY: + case JSON_VALUE_BUF_TYPE_OBJECT: + nesting += 1; + break; + case JSON_VALUE_BUF_TYPE_TERMINATOR: + if (nesting > 0) nesting -= 1; + else return false; + break; + case JSON_VALUE_BUF_TYPE_SEPARATOR: + if (nesting == 0) { + while ((p->lex.buf[i->idx] & 0xf0) == JSON_VALUE_BUF_TYPE_SKIP) i->idx++; + return true; + } + break; + default: break; + } + } + } + return false; +} + +json_value json_parse(json_parser *p) { + json_token cur = parse_advance(p); + switch (cur) { + case JSON_TOKEN_STRING: return install_string(p, p->curstart, p->curend); + case JSON_TOKEN_NUMBER_INT: return install_number_int(p, p->curstart, p->curend); + case JSON_TOKEN_LSQUARE: { + i64 start = p->curstart; + while (true) { + json_parse(p); + cur = parse_advance(p); + if (cur == JSON_TOKEN_RSQUARE) break; + else if (cur == JSON_TOKEN_COMMA) p->lex.buf[p->curstart] = JSON_VALUE_BUF_TYPE_SEPARATOR; + else goto error; + } + return install_array(p, start, p->curend); + } + case JSON_TOKEN_LCURLY: { + i64 start = p->curstart; + while (true) { + json_parse(p); + cur = parse_advance(p); + if (cur == JSON_TOKEN_RCURLY) break; + else if (cur == JSON_TOKEN_COMMA || cur == JSON_TOKEN_COLON) p->lex.buf[p->curstart] = JSON_VALUE_BUF_TYPE_SEPARATOR; + else goto error; + } + return install_array(p, start, p->curend); + } + default: goto error; + } +error: + return val(-1); +} |
