mirror of
https://github.com/mapbox/tippecanoe.git
synced 2025-02-02 01:08:14 +00:00
f67e97cd08
The idea is that you can free any element once you are done with it, even if it is part of an array or hash that isn't completely read yet. Removing array elements is reasonably well defined because nothing else depends on them. Removing a hash key or value causes it to be replaced by a null unless the other side of the component has already been nulled out, in which case the whole pair is removed.
560 lines
11 KiB
C
560 lines
11 KiB
C
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <ctype.h>
|
|
#include <string.h>
|
|
#include <stdarg.h>
|
|
#include "jsonpull.h"
|
|
|
|
typedef enum json_expect {
|
|
JSON_ITEM, JSON_COMMA, JSON_COLON, JSON_KEY, JSON_VALUE,
|
|
} json_expect;
|
|
|
|
static json_pull *json_init() {
|
|
json_pull *j = malloc(sizeof(json_pull));
|
|
j->error = NULL;
|
|
j->line = 1;
|
|
j->root = NULL;
|
|
j->container = NULL;
|
|
return j;
|
|
}
|
|
|
|
static int read_file(json_pull *j) {
|
|
int c = fgetc(j->source);
|
|
if (c == '\n') {
|
|
j->line++;
|
|
}
|
|
return c;
|
|
}
|
|
|
|
static int peek_file(json_pull *j) {
|
|
int c = getc(j->source);
|
|
ungetc(c, j->source);
|
|
return c;
|
|
}
|
|
|
|
json_pull *json_begin_file(FILE *f) {
|
|
json_pull *j = json_init();
|
|
|
|
j->read = read_file;
|
|
j->peek = peek_file;
|
|
j->source = f;
|
|
|
|
return j;
|
|
}
|
|
|
|
static int read_string(json_pull *j) {
|
|
char *cp = j->source;
|
|
if (*cp == '\0') {
|
|
return EOF;
|
|
}
|
|
int c = (unsigned char) *cp;
|
|
cp++;
|
|
j->source = cp;
|
|
if (c == '\n') {
|
|
j->line++;
|
|
}
|
|
return c;
|
|
}
|
|
|
|
static int peek_string(json_pull *p) {
|
|
char *cp = p->source;
|
|
if (*cp == '\0') {
|
|
return EOF;
|
|
}
|
|
return (unsigned char) *cp;
|
|
}
|
|
|
|
json_pull *json_begin_string(char *s) {
|
|
json_pull *j = json_init();
|
|
|
|
j->read = read_string;
|
|
j->peek = peek_string;
|
|
j->source = s;
|
|
|
|
return j;
|
|
}
|
|
|
|
#define SIZE_FOR(i) (((i) + 31) & ~31)
|
|
|
|
static json_object *add_object(json_pull *j, json_type type) {
|
|
json_object *o = malloc(sizeof(struct json_object));
|
|
o->type = type;
|
|
o->parent = j->container;
|
|
o->array = NULL;
|
|
o->keys = NULL;
|
|
o->values = NULL;
|
|
o->length = 0;
|
|
|
|
json_object *c = j->container;
|
|
|
|
if (c != NULL) {
|
|
if (c->type == JSON_ARRAY) {
|
|
if (c->expect == JSON_ITEM) {
|
|
if (SIZE_FOR(c->length + 1) != SIZE_FOR(c->length)) {
|
|
c->array = realloc(c->array, SIZE_FOR(c->length + 1) * sizeof(json_object *));
|
|
}
|
|
|
|
c->array[c->length++] = o;
|
|
c->expect = JSON_COMMA;
|
|
} else {
|
|
j->error = "Expected a comma, not a list item";
|
|
free(c);
|
|
return NULL;
|
|
}
|
|
} else if (c->type == JSON_HASH) {
|
|
if (c->expect == JSON_VALUE) {
|
|
c->values[c->length - 1] = o;
|
|
c->expect = JSON_COMMA;
|
|
} else if (c->expect == JSON_KEY) {
|
|
if (type != JSON_STRING) {
|
|
j->error = "Hash key is not a string";
|
|
free(c);
|
|
return NULL;
|
|
}
|
|
|
|
if (SIZE_FOR(c->length + 1) != SIZE_FOR(c->length)) {
|
|
c->keys = realloc(c->keys, SIZE_FOR(c->length + 1) * sizeof(json_object *));
|
|
c->values = realloc(c->values, SIZE_FOR(c->length + 1) * sizeof(json_object *));
|
|
}
|
|
|
|
c->keys[c->length] = o;
|
|
c->values[c->length] = NULL;
|
|
c->length++;
|
|
c->expect = JSON_COLON;
|
|
} else {
|
|
j->error = "Expected a comma or colon";
|
|
free(c);
|
|
return NULL;
|
|
}
|
|
}
|
|
} else {
|
|
j->root = o;
|
|
}
|
|
|
|
return o;
|
|
}
|
|
|
|
json_object *json_hash_get(json_object *o, char *s) {
|
|
if (o == NULL || o->type != JSON_HASH) {
|
|
return NULL;
|
|
}
|
|
|
|
int i;
|
|
for (i = 0; i < o->length; i++) {
|
|
if (o->keys[i] != NULL && o->keys[i]->type == JSON_STRING) {
|
|
if (strcmp(o->keys[i]->string, s) == 0) {
|
|
return o->values[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
struct string {
|
|
char *buf;
|
|
int n;
|
|
int nalloc;
|
|
};
|
|
|
|
static void string_init(struct string *s) {
|
|
s->nalloc = 500;
|
|
s->buf = malloc(s->nalloc);
|
|
s->n = 0;
|
|
s->buf[0] = '\0';
|
|
}
|
|
|
|
static void string_append(struct string *s, char c) {
|
|
if (s->n + 2 >= s->nalloc) {
|
|
s->nalloc += 500;
|
|
s->buf = realloc(s->buf, s->nalloc);
|
|
}
|
|
|
|
s->buf[s->n++] = c;
|
|
s->buf[s->n] = '\0';
|
|
}
|
|
|
|
static void string_free(struct string *s) {
|
|
free(s->buf);
|
|
}
|
|
|
|
json_object *json_parse(json_pull *j) {
|
|
int c;
|
|
again:
|
|
/////////////////////////// Whitespace
|
|
|
|
do {
|
|
c = j->read(j);
|
|
if (c == EOF) {
|
|
if (j->container != NULL) {
|
|
j->error = "Reached EOF without all containers being closed";
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
} while (c == ' ' || c == '\t' || c == '\r' || c == '\n');
|
|
|
|
/////////////////////////// Arrays
|
|
|
|
if (c == '[') {
|
|
json_object *o = add_object(j, JSON_ARRAY);
|
|
if (o == NULL) {
|
|
return NULL;
|
|
}
|
|
j->container = o;
|
|
j->container->expect = JSON_ITEM;
|
|
goto again;
|
|
} else if (c == ']') {
|
|
if (j->container == NULL) {
|
|
j->error = "Found ] at top level";
|
|
return NULL;
|
|
}
|
|
|
|
if (j->container->type != JSON_ARRAY) {
|
|
j->error = "Found ] not in an array";
|
|
return NULL;
|
|
}
|
|
|
|
if (j->container->expect != JSON_COMMA) {
|
|
if (! (j->container->expect == JSON_ITEM && j->container->length == 0)) {
|
|
j->error = "Found ] without final element";
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
json_object *ret = j->container;
|
|
j->container = ret->parent;
|
|
return ret;
|
|
}
|
|
|
|
/////////////////////////// Hashes
|
|
|
|
if (c == '{') {
|
|
json_object *o = add_object(j, JSON_HASH);
|
|
if (o == NULL) {
|
|
return NULL;
|
|
}
|
|
j->container = o;
|
|
j->container->expect = JSON_KEY;
|
|
goto again;
|
|
} else if (c == '}') {
|
|
if (j->container == NULL) {
|
|
j->error = "Found } at top level";
|
|
return NULL;
|
|
}
|
|
|
|
if (j->container->type != JSON_HASH) {
|
|
j->error = "Found } not in a hash";
|
|
return NULL;
|
|
}
|
|
|
|
if (j->container->expect != JSON_COMMA) {
|
|
if (! (j->container->expect == JSON_KEY && j->container->length == 0)) {
|
|
j->error = "Found } without final element";
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
json_object *ret = j->container;
|
|
j->container = ret->parent;
|
|
return ret;
|
|
}
|
|
|
|
/////////////////////////// Null
|
|
|
|
if (c == 'n') {
|
|
if (j->read(j) != 'u' || j->read(j) != 'l' || j->read(j) != 'l') {
|
|
j->error = "Found misspelling of null";
|
|
return NULL;
|
|
}
|
|
|
|
return add_object(j, JSON_NULL);
|
|
}
|
|
|
|
/////////////////////////// True
|
|
|
|
if (c == 't') {
|
|
if (j->read(j) != 'r' || j->read(j) != 'u' || j->read(j) != 'e') {
|
|
j->error = "Found misspelling of true";
|
|
return NULL;
|
|
}
|
|
|
|
return add_object(j, JSON_TRUE);
|
|
}
|
|
|
|
/////////////////////////// False
|
|
|
|
if (c == 'f') {
|
|
if (j->read(j) != 'a' || j->read(j) != 'l' || j->read(j) != 's' || j->read(j) != 'e') {
|
|
j->error = "Found misspelling of false";
|
|
return NULL;
|
|
}
|
|
|
|
return add_object(j, JSON_FALSE);
|
|
}
|
|
|
|
/////////////////////////// Comma
|
|
|
|
if (c == ',') {
|
|
if (j->container == NULL) {
|
|
j->error = "Found comma at top level";
|
|
return NULL;
|
|
}
|
|
|
|
if (j->container->expect != JSON_COMMA) {
|
|
j->error = "Found unexpected comma";
|
|
return NULL;
|
|
}
|
|
|
|
if (j->container->type == JSON_HASH) {
|
|
j->container->expect = JSON_KEY;
|
|
} else {
|
|
j->container->expect = JSON_ITEM;
|
|
}
|
|
|
|
goto again;
|
|
}
|
|
|
|
/////////////////////////// Colon
|
|
|
|
if (c == ':') {
|
|
if (j->container == NULL) {
|
|
j->error = "Found colon at top level";
|
|
return NULL;
|
|
}
|
|
|
|
if (j->container->expect != JSON_COLON) {
|
|
j->error = "Found unexpected colon";
|
|
return NULL;
|
|
}
|
|
|
|
j->container->expect = JSON_VALUE;
|
|
goto again;
|
|
}
|
|
|
|
/////////////////////////// Numbers
|
|
|
|
if (c == '-' || (c >= '0' && c <= '9')) {
|
|
struct string val;
|
|
string_init(&val);
|
|
|
|
if (c == '-') {
|
|
string_append(&val, c);
|
|
c = j->read(j);
|
|
}
|
|
|
|
if (c == '0') {
|
|
string_append(&val, c);
|
|
} else if (c >= '1' && c <= '9') {
|
|
string_append(&val, c);
|
|
c = j->peek(j);
|
|
|
|
while (c >= '0' && c <= '9') {
|
|
string_append(&val, j->read(j));
|
|
c = j->peek(j);
|
|
}
|
|
}
|
|
|
|
if (j->peek(j) == '.') {
|
|
string_append(&val, j->read(j));
|
|
|
|
c = j->peek(j);
|
|
while (c >= '0' && c <= '9') {
|
|
string_append(&val, j->read(j));
|
|
c = j->peek(j);
|
|
}
|
|
}
|
|
|
|
c = j->peek(j);
|
|
if (c == 'e' || c == 'E') {
|
|
string_append(&val, j->read(j));
|
|
|
|
c = j->peek(j);
|
|
if (c == '+' || c == '-') {
|
|
string_append(&val, j->read(j));
|
|
}
|
|
|
|
c = j->peek(j);
|
|
if (c < '0' || c > '9') {
|
|
j->error = "Exponent without digits";
|
|
return NULL;
|
|
}
|
|
while (c >= '0' && c <= '9') {
|
|
string_append(&val, j->read(j));
|
|
c = j->peek(j);
|
|
}
|
|
}
|
|
|
|
json_object *n = add_object(j, JSON_NUMBER);
|
|
if (n != NULL) {
|
|
n->number = atof(val.buf);
|
|
}
|
|
string_free(&val);
|
|
return n;
|
|
}
|
|
|
|
/////////////////////////// Strings
|
|
|
|
if (c == '"') {
|
|
struct string val;
|
|
string_init(&val);
|
|
|
|
while ((c = j->read(j)) != EOF) {
|
|
if (c == '"') {
|
|
break;
|
|
} else if (c == '\\') {
|
|
c = j->read(j);
|
|
|
|
if (c == '"') {
|
|
string_append(&val, '"');
|
|
} else if (c == '\\') {
|
|
string_append(&val, '\\');
|
|
} else if (c == '/') {
|
|
string_append(&val, '/');
|
|
} else if (c == 'b') {
|
|
string_append(&val, '\b');
|
|
} else if (c == 'f') {
|
|
string_append(&val, '\f');
|
|
} else if (c == 'n') {
|
|
string_append(&val, '\n');
|
|
} else if (c == 'r') {
|
|
string_append(&val, '\r');
|
|
} else if (c == 't') {
|
|
string_append(&val, '\t');
|
|
} else if (c == 'u') {
|
|
char hex[5] = "aaaa";
|
|
int i;
|
|
for (i = 0; i < 4; i++) {
|
|
hex[i] = j->read(j);
|
|
}
|
|
unsigned long ch = strtoul(hex, NULL, 16);
|
|
if (ch <= 0x7F) {
|
|
string_append(&val, ch);
|
|
} else if (ch <= 0x7FF) {
|
|
string_append(&val, 0xC0 | (ch >> 6));
|
|
string_append(&val, 0x80 | (ch & 0x3F));
|
|
} else {
|
|
string_append(&val, 0xE0 | (ch >> 12));
|
|
string_append(&val, 0x80 | ((ch >> 6) & 0x3F));
|
|
string_append(&val, 0x80 | (ch & 0x3F));
|
|
}
|
|
} else {
|
|
j->error = "Found backslash followed by unknown character";
|
|
return NULL;
|
|
}
|
|
} else {
|
|
string_append(&val, c);
|
|
}
|
|
}
|
|
|
|
json_object *s = add_object(j, JSON_STRING);
|
|
if (s != NULL) {
|
|
s->string = val.buf;
|
|
s->length = val.n;
|
|
}
|
|
return s;
|
|
}
|
|
|
|
j->error = "Found unexpected character";
|
|
return NULL;
|
|
}
|
|
|
|
static json_object *fabricate_null(json_object *parent) {
|
|
json_object *o = malloc(sizeof(struct json_object));
|
|
o->type = JSON_NULL;
|
|
o->parent = parent;
|
|
o->array = NULL;
|
|
o->keys = NULL;
|
|
o->values = NULL;
|
|
o->length = 0;
|
|
return o;
|
|
}
|
|
|
|
void json_free(json_object *o) {
|
|
int i;
|
|
|
|
if (o == NULL) {
|
|
return;
|
|
}
|
|
|
|
// Free any data linked from here
|
|
|
|
if (o->type == JSON_ARRAY) {
|
|
json_object **a = o->array;
|
|
int n = o->length;
|
|
|
|
o->array = NULL;
|
|
o->length = 0;
|
|
|
|
for (i = 0; i < n; i++) {
|
|
json_free(a[i]);
|
|
}
|
|
|
|
free(a);
|
|
} else if (o->type == JSON_HASH) {
|
|
json_object **k = o->keys;
|
|
json_object **v = o->values;
|
|
int n = o->length;
|
|
|
|
o->keys = NULL;
|
|
o->values = NULL;
|
|
o->length = 0;
|
|
|
|
for (i = 0; i < n; i++) {
|
|
json_free(k[i]);
|
|
json_free(v[i]);
|
|
}
|
|
|
|
free(k);
|
|
free(v);
|
|
} else if (o->type == JSON_STRING) {
|
|
free(o->string);
|
|
}
|
|
|
|
// Expunge references to this as an array element
|
|
// or a hash key or value.
|
|
|
|
if (o->parent != NULL) {
|
|
if (o->parent->type == JSON_ARRAY) {
|
|
for (i = 0; i < o->parent->length; i++) {
|
|
if (o->parent->array[i] == o) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (i < o->parent->length) {
|
|
memmove(o->parent->array + i, o->parent->array + i + 1, o->parent->length - i - 1);
|
|
o->parent->length--;
|
|
}
|
|
}
|
|
|
|
if (o->parent->type == JSON_HASH) {
|
|
for (i = 0; i < o->parent->length; i++) {
|
|
if (o->parent->keys[i] == o) {
|
|
o->parent->keys[i] = fabricate_null(o->parent);
|
|
break;
|
|
}
|
|
if (o->parent->values[i] == o) {
|
|
o->parent->values[i] = fabricate_null(o->parent);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (i < o->parent->length) {
|
|
if (o->parent->keys[i] != NULL && o->parent->keys[i]->type == JSON_NULL) {
|
|
if (o->parent->values[i] != NULL && o->parent->values[i]->type == JSON_NULL) {
|
|
free(o->parent->keys[i]);
|
|
free(o->parent->values[i]);
|
|
|
|
memmove(o->parent->keys + i, o->parent->keys + i + 1, o->parent->length - i - 1);
|
|
memmove(o->parent->values + i, o->parent->values + i + 1, o->parent->length - i - 1);
|
|
o->parent->length--;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
free(o);
|
|
}
|