shared: add minimal JSON tokenizer
This commit is contained in:
@@ -0,0 +1,409 @@
|
||||
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
|
||||
|
||||
/***
|
||||
This file is part of systemd.
|
||||
|
||||
Copyright 2014 Lennart Poettering
|
||||
|
||||
systemd is free software; you can redistribute it and/or modify it
|
||||
under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation; either version 2.1 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
systemd is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with systemd; If not, see <http://www.gnu.org/licenses/>.
|
||||
***/
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "macro.h"
|
||||
#include "log.h"
|
||||
#include "util.h"
|
||||
#include "utf8.h"
|
||||
#include "json.h"
|
||||
|
||||
enum {
|
||||
STATE_NULL,
|
||||
STATE_VALUE,
|
||||
STATE_VALUE_POST,
|
||||
};
|
||||
|
||||
static void inc_lines(unsigned *line, const char *s, size_t n) {
|
||||
const char *p = s;
|
||||
|
||||
if (!line)
|
||||
return;
|
||||
|
||||
for (;;) {
|
||||
const char *f;
|
||||
|
||||
f = memchr(p, '\n', n);
|
||||
if (!f)
|
||||
return;
|
||||
|
||||
n -= (f - p) + 1;
|
||||
p = f + 1;
|
||||
(*line)++;
|
||||
}
|
||||
}
|
||||
|
||||
static int json_parse_string(const char **p, char **ret) {
|
||||
_cleanup_free_ char *s = NULL;
|
||||
size_t n = 0, allocated = 0;
|
||||
const char *c;
|
||||
|
||||
assert(p);
|
||||
assert(*p);
|
||||
assert(ret);
|
||||
|
||||
c = *p;
|
||||
|
||||
if (*c != '"')
|
||||
return -EINVAL;
|
||||
|
||||
c++;
|
||||
|
||||
for (;;) {
|
||||
int len;
|
||||
|
||||
/* Check for EOF */
|
||||
if (*c == 0)
|
||||
return -EINVAL;
|
||||
|
||||
/* Check for control characters 0x00..0x1f */
|
||||
if (*c > 0 && *c < ' ')
|
||||
return -EINVAL;
|
||||
|
||||
/* Check for control character 0x7f */
|
||||
if (*c == 0x7f)
|
||||
return -EINVAL;
|
||||
|
||||
if (*c == '"') {
|
||||
if (!s) {
|
||||
s = strdup("");
|
||||
if (!s)
|
||||
return -ENOMEM;
|
||||
} else
|
||||
s[n] = 0;
|
||||
|
||||
*p = c + 1;
|
||||
|
||||
*ret = s;
|
||||
s = NULL;
|
||||
return JSON_STRING;
|
||||
}
|
||||
|
||||
if (*c == '\\') {
|
||||
char ch = 0;
|
||||
c++;
|
||||
|
||||
if (*c == 0)
|
||||
return -EINVAL;
|
||||
|
||||
if (IN_SET(*c, '"', '\\', '/'))
|
||||
ch = *c;
|
||||
else if (*c == 'b')
|
||||
ch = '\b';
|
||||
else if (*c == 'f')
|
||||
ch = '\f';
|
||||
else if (*c == 'n')
|
||||
ch = '\n';
|
||||
else if (*c == 'r')
|
||||
ch = '\r';
|
||||
else if (*c == 't')
|
||||
ch = '\t';
|
||||
else if (*c == 'u') {
|
||||
int aa, bb, cc, dd;
|
||||
uint16_t x;
|
||||
|
||||
aa = unhexchar(c[1]);
|
||||
if (aa < 0)
|
||||
return -EINVAL;
|
||||
|
||||
bb = unhexchar(c[2]);
|
||||
if (bb < 0)
|
||||
return -EINVAL;
|
||||
|
||||
cc = unhexchar(c[3]);
|
||||
if (cc < 0)
|
||||
return -EINVAL;
|
||||
|
||||
dd = unhexchar(c[4]);
|
||||
if (dd < 0)
|
||||
return -EINVAL;
|
||||
|
||||
|
||||
x = ((uint16_t) aa << 12) |
|
||||
((uint16_t) bb << 8) |
|
||||
((uint16_t) cc << 4) |
|
||||
((uint16_t) dd);
|
||||
|
||||
if (x <= 0)
|
||||
return -EINVAL;
|
||||
|
||||
if (!GREEDY_REALLOC(s, allocated, n + 4))
|
||||
return -ENOMEM;
|
||||
|
||||
n += utf8_encode_unichar(x, s + n);
|
||||
c += 5;
|
||||
continue;
|
||||
} else
|
||||
return -EINVAL;
|
||||
|
||||
if (!GREEDY_REALLOC(s, allocated, n + 2))
|
||||
return -ENOMEM;
|
||||
|
||||
s[n++] = ch;
|
||||
c ++;
|
||||
continue;
|
||||
}
|
||||
|
||||
len = utf8_encoded_valid_unichar(c);
|
||||
if (len < 0)
|
||||
return len;
|
||||
|
||||
if (!GREEDY_REALLOC(s, allocated, n + len + 1))
|
||||
return -ENOMEM;
|
||||
|
||||
memcpy(s + n, c, len);
|
||||
n += len;
|
||||
c += len;
|
||||
}
|
||||
}
|
||||
|
||||
static int json_parse_number(const char **p, union json_value *ret) {
|
||||
bool negative = false, exponent_negative = false, is_double = false;
|
||||
double x = 0.0, y = 0.0, exponent = 0.0, shift = 1.0;
|
||||
intmax_t i = 0;
|
||||
const char *c;
|
||||
|
||||
assert(p);
|
||||
assert(*p);
|
||||
assert(ret);
|
||||
|
||||
c = *p;
|
||||
|
||||
if (*c == '-') {
|
||||
negative = true;
|
||||
c++;
|
||||
}
|
||||
|
||||
if (*c == '0')
|
||||
c++;
|
||||
else {
|
||||
if (!strchr("123456789", *c) || *c == 0)
|
||||
return -EINVAL;
|
||||
|
||||
do {
|
||||
if (!is_double) {
|
||||
int64_t t;
|
||||
|
||||
t = 10 * i + (*c - '0');
|
||||
if (t < i) /* overflow */
|
||||
is_double = false;
|
||||
else
|
||||
i = t;
|
||||
}
|
||||
|
||||
x = 10.0 * x + (*c - '0');
|
||||
c++;
|
||||
} while (strchr("0123456789", *c) && *c != 0);
|
||||
}
|
||||
|
||||
if (*c == '.') {
|
||||
is_double = true;
|
||||
c++;
|
||||
|
||||
if (!strchr("0123456789", *c) || *c == 0)
|
||||
return -EINVAL;
|
||||
|
||||
do {
|
||||
y = 10.0 * y + (*c - '0');
|
||||
shift = 10.0 * shift;
|
||||
c++;
|
||||
} while (strchr("0123456789", *c) && *c != 0);
|
||||
}
|
||||
|
||||
if (*c == 'e' || *c == 'E') {
|
||||
is_double = true;
|
||||
c++;
|
||||
|
||||
if (*c == '-') {
|
||||
exponent_negative = true;
|
||||
c++;
|
||||
} else if (*c == '+')
|
||||
c++;
|
||||
|
||||
if (!strchr("0123456789", *c) || *c == 0)
|
||||
return -EINVAL;
|
||||
|
||||
do {
|
||||
exponent = 10.0 * exponent + (*c - '0');
|
||||
c++;
|
||||
} while (strchr("0123456789", *c) && *c != 0);
|
||||
}
|
||||
|
||||
if (*c != 0)
|
||||
return -EINVAL;
|
||||
|
||||
*p = c;
|
||||
|
||||
if (is_double) {
|
||||
ret->real = ((negative ? -1.0 : 1.0) * (x + (y / shift))) * exp10((exponent_negative ? -1.0 : 1.0) * exponent);
|
||||
return JSON_REAL;
|
||||
} else {
|
||||
ret->integer = negative ? -i : i;
|
||||
return JSON_INTEGER;
|
||||
}
|
||||
}
|
||||
|
||||
int json_tokenize(
|
||||
const char **p,
|
||||
char **ret_string,
|
||||
union json_value *ret_value,
|
||||
void **state,
|
||||
unsigned *line) {
|
||||
|
||||
const char *c;
|
||||
int t;
|
||||
int r;
|
||||
|
||||
assert(p);
|
||||
assert(*p);
|
||||
assert(ret_string);
|
||||
assert(ret_value);
|
||||
assert(state);
|
||||
|
||||
t = PTR_TO_INT(*state);
|
||||
c = *p;
|
||||
|
||||
if (t == STATE_NULL) {
|
||||
if (line)
|
||||
*line = 1;
|
||||
t = STATE_VALUE;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
const char *b;
|
||||
|
||||
b = c + strspn(c, WHITESPACE);
|
||||
if (*b == 0)
|
||||
return JSON_END;
|
||||
|
||||
inc_lines(line, c, b - c);
|
||||
c = b;
|
||||
|
||||
switch (t) {
|
||||
|
||||
case STATE_VALUE:
|
||||
|
||||
if (*c == '{') {
|
||||
*ret_string = NULL;
|
||||
*ret_value = JSON_VALUE_NULL;
|
||||
*p = c + 1;
|
||||
*state = INT_TO_PTR(STATE_VALUE);
|
||||
return JSON_OBJECT_OPEN;
|
||||
|
||||
} else if (*c == '}') {
|
||||
*ret_string = NULL;
|
||||
*ret_value = JSON_VALUE_NULL;
|
||||
*p = c + 1;
|
||||
*state = INT_TO_PTR(STATE_VALUE_POST);
|
||||
return JSON_OBJECT_CLOSE;
|
||||
|
||||
} else if (*c == '[') {
|
||||
*ret_string = NULL;
|
||||
*ret_value = JSON_VALUE_NULL;
|
||||
*p = c + 1;
|
||||
*state = INT_TO_PTR(STATE_VALUE);
|
||||
return JSON_ARRAY_OPEN;
|
||||
|
||||
} else if (*c == ']') {
|
||||
*ret_string = NULL;
|
||||
*ret_value = JSON_VALUE_NULL;
|
||||
*p = c + 1;
|
||||
*state = INT_TO_PTR(STATE_VALUE_POST);
|
||||
return JSON_ARRAY_CLOSE;
|
||||
|
||||
} else if (*c == '"') {
|
||||
r = json_parse_string(&c, ret_string);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
*ret_value = JSON_VALUE_NULL;
|
||||
*p = c;
|
||||
*state = INT_TO_PTR(STATE_VALUE_POST);
|
||||
return r;
|
||||
|
||||
} else if (strchr("-0123456789", *c)) {
|
||||
r = json_parse_number(&c, ret_value);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
*ret_string = NULL;
|
||||
*p = c;
|
||||
*state = INT_TO_PTR(STATE_VALUE_POST);
|
||||
return r;
|
||||
|
||||
} else if (startswith(c, "true")) {
|
||||
*ret_string = NULL;
|
||||
ret_value->boolean = true;
|
||||
*p = c + 4;
|
||||
*state = INT_TO_PTR(STATE_VALUE_POST);
|
||||
return JSON_BOOLEAN;
|
||||
|
||||
} else if (startswith(c, "false")) {
|
||||
*ret_string = NULL;
|
||||
ret_value->boolean = false;
|
||||
*p = c + 5;
|
||||
*state = INT_TO_PTR(STATE_VALUE_POST);
|
||||
return JSON_BOOLEAN;
|
||||
|
||||
} else if (startswith(c, "null")) {
|
||||
*ret_string = NULL;
|
||||
*ret_value = JSON_VALUE_NULL;
|
||||
*p = c + 4;
|
||||
*state = INT_TO_PTR(STATE_VALUE_POST);
|
||||
return JSON_NULL;
|
||||
|
||||
} else
|
||||
return -EINVAL;
|
||||
|
||||
case STATE_VALUE_POST:
|
||||
|
||||
if (*c == ':') {
|
||||
*ret_string = NULL;
|
||||
*ret_value = JSON_VALUE_NULL;
|
||||
*p = c + 1;
|
||||
*state = INT_TO_PTR(STATE_VALUE);
|
||||
return JSON_COLON;
|
||||
} else if (*c == ',') {
|
||||
*ret_string = NULL;
|
||||
*ret_value = JSON_VALUE_NULL;
|
||||
*p = c + 1;
|
||||
*state = INT_TO_PTR(STATE_VALUE);
|
||||
return JSON_COMMA;
|
||||
} else if (*c == '}') {
|
||||
*ret_string = NULL;
|
||||
*ret_value = JSON_VALUE_NULL;
|
||||
*p = c + 1;
|
||||
*state = INT_TO_PTR(STATE_VALUE_POST);
|
||||
return JSON_OBJECT_CLOSE;
|
||||
} else if (*c == ']') {
|
||||
*ret_string = NULL;
|
||||
*ret_value = JSON_VALUE_NULL;
|
||||
*p = c + 1;
|
||||
*state = INT_TO_PTR(STATE_VALUE_POST);
|
||||
return JSON_ARRAY_CLOSE;
|
||||
} else
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user