Browse Source

json: add rudimentary JSON parser library

K. Lange 10 months ago
parent
commit
bd92f00a40
4 changed files with 596 additions and 1 deletions
  1. 109 0
      apps/json-test.c
  2. 53 0
      base/usr/include/toaru/json.h
  3. 432 0
      lib/json.c
  4. 2 1
      util/auto-dep.py

+ 109 - 0
apps/json-test.c

@@ -0,0 +1,109 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <toaru/json.h>
+#include <toaru/hashmap.h>
+#include <toaru/list.h>
+
+typedef struct JSON_Value Value;
+
+int main(int argc, char * argv[]) {
+
+	{
+		Value * result = json_parse("\"foo bar baz\"");
+		assert(result && result->type == JSON_TYPE_STRING);
+		assert(strcmp(result->string, "foo bar baz") == 0);
+	}
+
+	{
+		Value * result = json_parse("\"foo \\nbar baz\"");
+		assert(result && result->type == JSON_TYPE_STRING);
+		assert(strcmp(result->string, "foo \nbar baz") == 0);
+	}
+
+	{
+		Value * result = json_parse("-123");
+		assert(result && result->type == JSON_TYPE_NUMBER);
+		assert(fabs(result->number - (-123.0) < 0.0001));
+	}
+
+	{
+		Value * result = json_parse("2e3");
+		assert(result && result->type == JSON_TYPE_NUMBER);
+		assert(fabs(result->number - (2000.0) < 0.0001));
+	}
+
+	{
+		Value * result = json_parse("0.124");
+		assert(result && result->type == JSON_TYPE_NUMBER);
+		assert(fabs(result->number - (0.124) < 0.0001));
+	}
+
+	{
+		Value * result = json_parse("[ 1, 2, 3 ]");
+		assert(result && result->type == JSON_TYPE_ARRAY);
+		assert(result->array->length == 3);
+
+		assert(fabs(((Value *)list_dequeue(result->array)->value)->number - 1.0) < 0.0001);
+		assert(fabs(((Value *)list_dequeue(result->array)->value)->number - 2.0) < 0.0001);
+		assert(fabs(((Value *)list_dequeue(result->array)->value)->number - 3.0) < 0.0001);
+	}
+
+	{
+		Value * result = json_parse("true");
+		assert(result && result->type == JSON_TYPE_BOOL);
+		assert(result->boolean == 1);
+	}
+
+	{
+		Value * result = json_parse("false");
+		assert(result && result->type == JSON_TYPE_BOOL);
+		assert(result->boolean == 0);
+	}
+
+	{
+		Value * result = json_parse("null");
+		assert(result && result->type == JSON_TYPE_NULL);
+	}
+
+	{
+		Value * result = json_parse("torbs");
+		assert(!result);
+	}
+
+	{
+		Value * result = json_parse("{\"foo\": \"bar\", \"bix\": 123}");
+		assert(result && result->type == JSON_TYPE_OBJECT);
+
+		hashmap_t * hash = result->object;
+		assert(hashmap_get(hash, "foo"));
+		assert(((Value *)hashmap_get(hash, "foo"))->type == JSON_TYPE_STRING);
+		assert(strcmp(((Value *)hashmap_get(hash, "foo"))->string, "bar") == 0);
+		assert(((Value *)hashmap_get(hash, "bix"))->type == JSON_TYPE_NUMBER);
+		assert(fabs(((Value *)hashmap_get(hash, "bix"))->number - 123.0) < 0.00001);
+	}
+
+	{
+		FILE * f = fopen("/opt/demo.json","r");
+
+		char str[1024];
+		fgets(str, 1024, f);
+
+		Value * result = json_parse(str);
+		assert(result && result->type == JSON_TYPE_OBJECT);
+
+		Value * _main = JSON_KEY(result,"main");
+		Value * conditions = (JSON_KEY(result,"weather") && JSON_KEY(result,"weather")->array->length > 0) ?
+			JSON_IND(JSON_KEY(result,"weather"),0) : NULL;
+
+		fprintf(stdout, "temp=%lf\n", JSON_KEY(_main,"temp")->number);
+		fprintf(stdout, "temp_r=%d\n", (int)JSON_KEY(_main,"temp")->number);
+		fprintf(stdout, "conditions=%s\n", conditions ? JSON_KEY(conditions,"main")->string : "");
+		fprintf(stdout, "icon=%s\n", conditions ? JSON_KEY(conditions,"icon")->string : "");
+		fprintf(stderr, "humidity=%d\n", (int)JSON_KEY(_main,"humidity")->number);
+		fprintf(stderr, "clouds=%d\n", (int)JSON_KEY(JSON_KEY(result,"clouds"),"all") ? JSON_KEY(JSON_KEY(result,"clouds"),"all")->number : 0);
+		fprintf(stderr, "city=%s\n", "Tokyo");
+	}
+
+	return 0;
+}

+ 53 - 0
base/usr/include/toaru/json.h

@@ -0,0 +1,53 @@
+#pragma once
+
+#include <_cheader.h>
+#include <toaru/hashmap.h>
+#include <toaru/list.h>
+
+_Begin_C_Header
+
+#define JSON_TYPE_OBJECT 0
+#define JSON_TYPE_ARRAY  1
+#define JSON_TYPE_STRING 2
+#define JSON_TYPE_NUMBER 3
+#define JSON_TYPE_BOOL   4
+#define JSON_TYPE_NULL   5
+
+struct JSON_Value {
+	int type;
+	union {
+		char * string;
+		double number;
+		list_t * array;
+		hashmap_t * object;
+		int boolean;
+	};
+};
+
+#define JSON_KEY(v,k) ((struct JSON_Value *)(hashmap_get(v->object,k)))
+#define JSON_IND(v,i) ((struct JSON_Value *)(list_index(v->array,i)))
+
+/**
+ * json_free
+ *
+ * Free a struct JSON_Value, and its contents recursively if it's an array,
+ * object, string, etc.
+ */
+extern void json_free(struct JSON_Value *);
+
+/**
+ * json_parse
+ *
+ * Parse a string into a JSON_Value
+ */
+extern struct JSON_Value * json_parse(const char *);
+
+/**
+ * json_parse_file
+ *
+ * Open a file path and parse its contents as JSON
+ * (Convenience function)
+ */
+extern struct JSON_Value * json_parse_file(const char * filename);
+
+_End_C_Header

+ 432 - 0
lib/json.c

@@ -0,0 +1,432 @@
+#include <stdio.h>
+#include <stdint.h>
+#include <ctype.h>
+#include <math.h>
+
+#include <toaru/hashmap.h>
+#include <toaru/list.h>
+#include <toaru/json.h>
+
+typedef struct JSON_Value Value;
+
+/* Internal usage */
+struct JSON_Context {
+	const char * string;
+	int c;
+	const char * error;
+};
+
+void json_free(Value * v) {
+	if (v->type == JSON_TYPE_STRING) {
+		free(v->string);
+	}
+	if (v->type == JSON_TYPE_OBJECT) {
+		hashmap_free(v->object);
+		free(v->object);
+	}
+	if (v->type == JSON_TYPE_ARRAY) {
+		foreach(node, v->array) {
+			json_free(node->value);
+		}
+		list_free(v->array);
+		free(v->array);
+	}
+	free(v);
+}
+
+static Value * value(struct JSON_Context * ctx);
+
+static int peek(struct JSON_Context * ctx) {
+	return ctx->string[ctx->c];
+}
+
+static void advance(struct JSON_Context * ctx) {
+	ctx->c++;
+}
+
+static void whitespace(struct JSON_Context * ctx) {
+	while (1) {
+		int ch = peek(ctx);
+		if (ch == ' ' || ch == '\r' || ch == '\n' || ch == '\t') {
+			advance(ctx);
+		} else {
+			break;
+		}
+	}
+}
+
+static Value * string(struct JSON_Context * ctx) {
+	if (peek(ctx) != '"') return NULL;
+	advance(ctx);
+
+	int size = 4;
+	char * tmp = malloc(4);
+	tmp[0] = 0;
+	int used = 0;
+
+#define add(c) do { \
+	if (used + 1 == size) { \
+		size *= 2; \
+		tmp = realloc(tmp, size); \
+	} \
+	tmp[used] = c; \
+	tmp[used+1] = 0; \
+	used++; \
+} while (0)
+
+	while (1) {
+		int ch = peek(ctx);
+		if (ch == 0) goto string_error;
+		if (ch == '"') {
+			break;
+		} else if (ch == '\\') {
+			advance(ctx);
+			ch = peek(ctx);
+			if (ch == '"') add('"');
+			else if (ch == '\\') add('\\');
+			else if (ch == '/') add('/');
+			else if (ch == 'b') add('\b');
+			else if (ch == 'f') add('\f');
+			else if (ch == 'n') add('\n');
+			else if (ch == 'r') add('\r');
+			else if (ch == 't') add('\t');
+			else if (ch == 'u') {
+				/* Parse hex */
+				advance(ctx);
+				char hex_digits[5];
+				if (!isxdigit(peek(ctx))) goto string_error;
+				hex_digits[0] = peek(ctx); advance(ctx);
+				if (!isxdigit(peek(ctx))) goto string_error;
+				hex_digits[1] = peek(ctx); advance(ctx);
+				if (!isxdigit(peek(ctx))) goto string_error;
+				hex_digits[2] = peek(ctx); advance(ctx);
+				if (!isxdigit(peek(ctx))) goto string_error;
+				hex_digits[3] = peek(ctx); /* will be advanced later */
+				hex_digits[4] = 0;
+
+				uint32_t val = strtoul(hex_digits, NULL, 16);
+				if (val < 0x0080) {
+					add(val);
+				} else if (val < 0x0800) {
+					add(0xC0 | (val >> 6));
+					add(0x80 | (val & 0x3F));
+				} else {
+					add(0xE0 | (val >> 12));
+					add(0x80 | ((val >> 6) & 0x3F));
+					add(0x80 | (val & 0x3F));
+				}
+			} else {
+				goto string_error;
+			}
+			advance(ctx);
+		} else {
+			add(ch);
+			advance(ctx);
+		}
+	}
+
+	if (peek(ctx) != '"') {
+		ctx->error = "Unexpected EOF?";
+		goto string_error;
+	}
+	advance(ctx);
+
+	Value * out = malloc(sizeof(Value));
+	out->type = JSON_TYPE_STRING;
+	out->string = strdup(tmp);
+	free(tmp);
+
+	return out;
+
+string_error:
+	free(tmp);
+	return NULL;
+}
+
+static Value * object(struct JSON_Context * ctx) {
+	if (peek(ctx) != '{') {
+		ctx->error = "Expected { (internal error)";
+		return NULL;
+	}
+	advance(ctx);
+
+	Value * out;
+	hashmap_t * output = hashmap_create(10);
+	output->hash_val_free = (void (*)(void *))json_free;
+	whitespace(ctx);
+
+	if (peek(ctx) == '}') {
+		advance(ctx);
+		goto _object_done;
+	}
+
+	while (1) {
+		whitespace(ctx);
+		Value * s = string(ctx);
+
+		if (!s) {
+			ctx->error = "Expected string";
+			break;
+		}
+
+		whitespace(ctx);
+
+		if (peek(ctx) != ':') {
+			ctx->error = "Expected :";
+			break;
+		}
+		advance(ctx);
+
+		Value * v = value(ctx);
+
+		hashmap_set(output, s->string, v);
+		json_free(s);
+
+		if (peek(ctx) == '}') {
+			advance(ctx);
+			goto _object_done;
+		}
+
+		if (peek(ctx) != ',') {
+			ctx->error = "Expected , or {";
+			break;
+		}
+
+		advance(ctx);
+	}
+
+	hashmap_free(output);
+	return NULL;
+
+_object_done:
+	out = malloc(sizeof(Value));
+	out->type = JSON_TYPE_OBJECT;
+	out->object = output;
+	return out;
+}
+
+static Value * boolean(struct JSON_Context * ctx) {
+	int value = -1;
+	if (peek(ctx) == 't') {
+		advance(ctx);
+		if (peek(ctx) != 'r') { ctx->error = "Invalid literal while parsing bool"; return NULL; }
+		advance(ctx);
+		if (peek(ctx) != 'u') { ctx->error = "Invalid literal while parsing bool"; return NULL; }
+		advance(ctx);
+		if (peek(ctx) != 'e') { ctx->error = "Invalid literal while parsing bool"; return NULL; }
+		advance(ctx);
+		value = 1;
+	} else if (peek(ctx) == 'f') {
+		advance(ctx);
+		if (peek(ctx) != 'a') { ctx->error = "Invalid literal while parsing bool"; return NULL; }
+		advance(ctx);
+		if (peek(ctx) != 'l') { ctx->error = "Invalid literal while parsing bool"; return NULL; }
+		advance(ctx);
+		if (peek(ctx) != 's') { ctx->error = "Invalid literal while parsing bool"; return NULL; }
+		advance(ctx);
+		if (peek(ctx) != 'e') { ctx->error = "Invalid literal while parsing bool"; return NULL; }
+		advance(ctx);
+		value = 0;
+	} else { ctx->error = "Invalid literal while parsing bool"; return NULL; }
+
+	Value * out = malloc(sizeof(Value));
+	out->type = JSON_TYPE_BOOL;
+	out->boolean = value;
+	return out;
+}
+
+static Value * null(struct JSON_Context * ctx) {
+	if (peek(ctx) != 'n') { ctx->error = "Invalid literal while parsing null"; return NULL; }
+	advance(ctx);
+	if (peek(ctx) != 'u') { ctx->error = "Invalid literal while parsing null"; return NULL; }
+	advance(ctx);
+	if (peek(ctx) != 'l') { ctx->error = "Invalid literal while parsing null"; return NULL; }
+	advance(ctx);
+	if (peek(ctx) != 'l') { ctx->error = "Invalid literal while parsing null"; return NULL; }
+	advance(ctx);
+
+	Value * out = malloc(sizeof(Value));
+	out->type = JSON_TYPE_NULL;
+	return out;
+}
+
+static Value * number(struct JSON_Context * ctx) {
+
+	double value = 0;
+	int sign = 1;
+	if (peek(ctx) == '-') {
+		/* Negative */
+		sign = -1;
+		advance(ctx);
+	}
+
+	if (peek(ctx) == '0') {
+		advance(ctx);
+	} else if (isdigit(peek(ctx))) {
+		/* Read any digit */
+		value = peek(ctx) - '0';
+		advance(ctx);
+		while (isdigit(peek(ctx))) {
+			value *= 10;
+			value += peek(ctx) - '0';
+			advance(ctx);
+		}
+	} else {
+		ctx->error = "Expected digit";
+		return NULL;
+	}
+
+	if (peek(ctx) == '.') {
+		/* Read fractional part */
+		advance(ctx);
+
+		double multiplier = 0.1;
+		/* read at least one digit */
+		if (!isdigit(peek(ctx))) {
+			ctx->error = "Expected digit";
+			return NULL;
+		}
+		while (isdigit(peek(ctx))) {
+			value += multiplier * (peek(ctx) - '0');
+			multiplier *= 0.1;
+			advance(ctx);
+		}
+	}
+
+	if (peek(ctx) == 'e' || peek(ctx) == 'E') {
+		/* Read exponent */
+		int exp_sign = 1;
+		advance(ctx);
+		if (peek(ctx) == '+') advance(ctx);
+		else if (peek(ctx) == '-') {
+			exp_sign = -1;
+			advance(ctx);
+		}
+
+		/* read digits */
+		if (!isdigit(peek(ctx))) {
+			ctx->error = "Expected digit";
+			return NULL;
+		}
+		double exp = peek(ctx) - '0';
+		advance(ctx);
+		while (isdigit(peek(ctx))) {
+			exp *= 10;
+			exp += peek(ctx) - '0';
+			advance(ctx);
+		}
+
+		value = value * pow(10.0,exp * exp_sign);
+	}
+
+	Value * out = malloc(sizeof(Value));
+	out->type = JSON_TYPE_NUMBER;
+	out->number = value * sign;
+
+	return out;
+}
+
+static Value * array(struct JSON_Context * ctx) {
+	if (peek(ctx) != '[') return NULL;
+	advance(ctx);
+
+	whitespace(ctx);
+
+	list_t * output = list_create();
+	Value * out;
+
+	if (peek(ctx) == ']') {
+		advance(ctx);
+		goto _array_done;
+	}
+
+	while (1) {
+		Value * next = value(ctx);
+
+		if (!next) break;
+
+		list_insert(output, next);
+
+		if (peek(ctx) == ']') {
+			advance(ctx);
+			goto _array_done;
+		}
+		if (peek(ctx) != ',') {
+			ctx->error = "Expected ,";
+			break;
+		}
+
+		advance(ctx);
+	}
+
+	/* uh oh */
+	foreach(node, output) {
+		json_free(node->value);
+	}
+	list_free(output);
+	free(output);
+	return NULL;
+
+_array_done:
+	out = malloc(sizeof(Value));
+	out->type = JSON_TYPE_ARRAY;
+	out->array = output;
+	return out;
+}
+
+#define WHITE(c) { \
+	Value * out = c; \
+	whitespace(ctx); \
+	return out; \
+}
+
+static Value * value(struct JSON_Context * ctx) {
+	whitespace(ctx);
+	if (peek(ctx) == '"') WHITE(string(ctx))
+	else if (peek(ctx) == '{') WHITE(object(ctx))
+	else if (peek(ctx) == '[') WHITE(array(ctx))
+	else if (peek(ctx) == '-' || isdigit(peek(ctx))) WHITE(number(ctx))
+	else if (peek(ctx) == 't') WHITE(boolean(ctx))
+	else if (peek(ctx) == 'f') WHITE(boolean(ctx))
+	else if (peek(ctx) == 'n') WHITE(null(ctx))
+	ctx->error = "Unexpected value";
+	return NULL;
+}
+
+Value * json_parse(const char * str) {
+	struct JSON_Context ctx;
+	ctx.string = str;
+	ctx.c = 0;
+	ctx.error = NULL;
+	Value * out = value(&ctx);
+#if 0
+	if (!out) {
+		fprintf(stderr, "JSON parse error at %d (%c)\n", ctx.c, ctx.string[ctx.c]);
+		fprintf(stderr, "%s\n", ctx.error);
+		fprintf(stderr, "%s\n", ctx.string);
+		for (int i = 0; i < ctx.c; ++i) { fprintf(stderr, " "); }
+		fprintf(stderr, "^\n");
+	}
+#endif
+	return out;
+}
+
+Value * json_parse_file(const char * filename) {
+	FILE * f = fopen(filename, "r");
+
+	if (!f) return NULL;
+
+	fseek(f, 0, SEEK_END);
+	size_t size = ftell(f);
+	fseek(f, 0, SEEK_SET);
+
+	char * tmp = malloc(size + 1);
+	fread(tmp, size, 1, f);
+	tmp[size] = 0;
+
+	fclose(f);
+
+	Value * out = json_parse(tmp);
+	free(tmp);
+	return out;
+}

+ 2 - 1
util/auto-dep.py

@@ -24,7 +24,8 @@ class Classifier(object):
         '<toaru/rline.h>':       (None, '-ltoaru_rline',       ['<toaru/kbd.h>']),
         '<toaru/rline_exp.h>':   (None, '-ltoaru_rline_exp',   ['<toaru/rline.h>']),
         '<toaru/confreader.h>':  (None, '-ltoaru_confreader',  ['<toaru/hashmap.h>']),
-        '<toaru/markup.h>':      (None, '-ltoaru_markup',       ['<toaru/hashmap.h>']),
+        '<toaru/markup.h>':      (None, '-ltoaru_markup',      ['<toaru/hashmap.h>']),
+        '<toaru/json.h>':        (None, '-ltoaru_json',        ['<toaru/hashmap.h>']),
         '<toaru/yutani.h>':      (None, '-ltoaru_yutani',      ['<toaru/kbd.h>', '<toaru/list.h>', '<toaru/pex.h>', '<toaru/graphics.h>', '<toaru/hashmap.h>']),
         '<toaru/decorations.h>': (None, '-ltoaru_decorations', ['<toaru/menu.h>', '<toaru/sdf.h>', '<toaru/graphics.h>', '<toaru/yutani.h>']),
         '<toaru/termemu.h>':     (None, '-ltoaru_termemu',     ['<toaru/graphics.h>']),