diff --git a/apps/json-test.c b/apps/json-test.c new file mode 100644 index 00000000..5bfeebde --- /dev/null +++ b/apps/json-test.c @@ -0,0 +1,109 @@ +#include +#include +#include +#include +#include +#include + +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; +} diff --git a/base/usr/include/toaru/json.h b/base/usr/include/toaru/json.h new file mode 100644 index 00000000..6f106605 --- /dev/null +++ b/base/usr/include/toaru/json.h @@ -0,0 +1,53 @@ +#pragma once + +#include <_cheader.h> +#include +#include + +_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 diff --git a/lib/json.c b/lib/json.c new file mode 100644 index 00000000..504684d5 --- /dev/null +++ b/lib/json.c @@ -0,0 +1,432 @@ +#include +#include +#include +#include + +#include +#include +#include + +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; +} diff --git a/util/auto-dep.py b/util/auto-dep.py index 380b22bd..dbe00f04 100755 --- a/util/auto-dep.py +++ b/util/auto-dep.py @@ -24,7 +24,8 @@ class Classifier(object): '': (None, '-ltoaru_rline', ['']), '': (None, '-ltoaru_rline_exp', ['']), '': (None, '-ltoaru_confreader', ['']), - '': (None, '-ltoaru_markup', ['']), + '': (None, '-ltoaru_markup', ['']), + '': (None, '-ltoaru_json', ['']), '': (None, '-ltoaru_yutani', ['', '', '', '', '']), '': (None, '-ltoaru_decorations', ['', '', '', '']), '': (None, '-ltoaru_termemu', ['']),