From 9a88d1cbdc00f540b792cc7c8c5e1633c2f27bd9 Mon Sep 17 00:00:00 2001 From: olemorud Date: Sun, 23 Apr 2023 22:34:24 +0200 Subject: [PATCH] Refactor Move json_value and obj_t to same file - Rename json_obj.* -> json_value.* - Move `print_json_value(...)` to json_value.* Move `err_ctx(...)` to util.* parse.h now only exposes `parse_json_value(...)` --- Makefile | 8 +- include/json_obj.h | 22 --- include/json_value.h | 41 ++++ include/parse.h | 25 +-- include/util.h | 3 + src/{json_obj.c => json_value.c} | 100 +++++++++- src/main.c | 2 +- src/parse.c | 326 ++++++++----------------------- src/util.c | 88 ++++++++- 9 files changed, 313 insertions(+), 302 deletions(-) delete mode 100644 include/json_obj.h create mode 100644 include/json_value.h rename src/{json_obj.c => json_value.c} (54%) diff --git a/Makefile b/Makefile index 0613b6d..f2531cc 100644 --- a/Makefile +++ b/Makefile @@ -10,22 +10,24 @@ COMPILER ?= gcc CC := gcc # -fsanitize={address,undefined} -CFLAGS.gcc.debug := -Og -ggdb -fanalyzer -DBACKTRACE -rdynamic +CFLAGS.gcc.debug := -Og -ggdb -fanalyzer -DBACKTRACE -rdynamic -fsanitize=address CFLAGS.gcc.release := -O3 -march=native -DNDEBUG CFLAGS.gcc := ${CFLAGS.gcc.${BUILD}} -Iinclude -W{all,extra,error} -fstack-protector-all -std=gnu11 -CFLAGS.clang.debug=-O0 -ggdb -DBACKTRACE +CFLAGS.clang.debug=-O0 -g3 -DBACKTRACE -rdynamic CFLAGS.clang.release=-O3 -march=native -DNDEBUG CFLAGS.clang=-Wextra -Wall -Wpedantic -fstack-protector-all ${CFLAGS.clang.${BUILD}} CFLAGS := ${CFLAGS.${COMPILER}} +LD_PRELOAD:= + # ==== end set compiler flags ==== BUILD_DIR := bin/${BUILD} OBJ_DIR := .obj/${BUILD} -_OBJS := main.o parse.o json_obj.o util.o +_OBJS := main.o parse.o json_value.o util.o OBJS := $(patsubst %,$(OBJ_DIR)/%,$(_OBJS)) diff --git a/include/json_obj.h b/include/json_obj.h deleted file mode 100644 index 613dfee..0000000 --- a/include/json_obj.h +++ /dev/null @@ -1,22 +0,0 @@ - -#ifndef _obj_H -#define _obj_H - -#include -#include - -#define OBJ_SIZE 1024 - -typedef struct obj_entry { - char const* key; - struct json_value* val; - struct obj_entry* next; -} * __p_obj_entry; - -typedef __p_obj_entry obj_t[OBJ_SIZE]; - -void* obj_at(obj_t m, char* const key); -bool obj_insert(obj_t m, char* const key, struct json_value* value); -void obj_delete(obj_t m); - -#endif diff --git a/include/json_value.h b/include/json_value.h new file mode 100644 index 0000000..2ae5dd1 --- /dev/null +++ b/include/json_value.h @@ -0,0 +1,41 @@ + +#ifndef _JSON_VALUE_H +#define _JSON_VALUE_H + +#include // bool + +#define OBJ_SIZE 64 + +typedef struct obj_entry { + char const* key; + struct json_value* val; + struct obj_entry* next; +} * __p_obj_entry; + +typedef __p_obj_entry obj_t[OBJ_SIZE]; + +enum json_type { object, + array, + string, + number, + boolean, + null }; + +struct json_value { + enum json_type type; + union { + obj_t* object; + struct json_value** array; + char* string; + bool boolean; + double number; + }; +}; + +void* obj_at(obj_t m, char* const key); +bool obj_insert(obj_t m, char* const key, struct json_value* value); +void obj_delete(obj_t m); + +void print_json(struct json_value val, int indent); + +#endif diff --git a/include/parse.h b/include/parse.h index 145f564..68bcfba 100644 --- a/include/parse.h +++ b/include/parse.h @@ -2,31 +2,10 @@ #ifndef _PARSE_H #define _PARSE_H -#include "json_obj.h" -#include -#include -#include +#include "json_value.h" -enum json_type { object, - array, - string, - number, - boolean, - null }; - -struct json_value { - enum json_type type; - union { - obj_t* object; - struct json_value** array; - char* string; - bool boolean; - double number; - }; -}; +#include // FILE* struct json_value parse_json_value(FILE* fp); -void print_json(struct json_value val, int indent); - #endif diff --git a/include/util.h b/include/util.h index 310b9a3..2f8e23c 100644 --- a/include/util.h +++ b/include/util.h @@ -3,6 +3,9 @@ #define _UTIL_H #include +#include + +__attribute__((__noreturn__)) void err_ctx(int exit_code, FILE* fp, const char* format, ...); void* malloc_or_die(size_t size); void* realloc_or_die(void* ptr, size_t size); diff --git a/src/json_obj.c b/src/json_value.c similarity index 54% rename from src/json_obj.c rename to src/json_value.c index 55cdeb9..fab0796 100644 --- a/src/json_obj.c +++ b/src/json_value.c @@ -5,9 +5,17 @@ #include #include -#include "json_obj.h" +#include "json_value.h" #include "util.h" +bool obj_insert(obj_t m, char* const key, struct json_value* value); +size_t obj_hash(char const* str); +void obj_delete(obj_t m); +void print_array(struct json_value** arr, int cur_indent, int indent_amount); +void print_json_value(struct json_value val, int cur_indent, int indent_amount); +void print_object(obj_t obj, int cur_indent, int indent_amount); +void* obj_at(obj_t m, char* const key); + /* djb2 string hash credits: Daniel J. Bernstein @@ -128,3 +136,93 @@ void obj_delete(obj_t m) } } } + +void add_indent(int n) +{ + for (int i = 0; i < n; i++) + putchar(' '); +} + +void print_object(obj_t obj, int cur_indent, int indent_amount) +{ + putchar('{'); + + bool first = true; + + for (size_t i = 0; i < OBJ_SIZE; i++) { + struct obj_entry* e = obj[i]; + + while (e != NULL) { + if (!first) + putchar(','); + + first = false; + + putchar('\n'); + add_indent(cur_indent); + printf("\"%s\": ", e->key); + print_json_value(*(e->val), cur_indent + indent_amount, indent_amount); + + e = e->next; + } + } + + putchar('\n'); + add_indent(cur_indent - indent_amount * 2); + putchar('}'); +} + +void print_array(struct json_value** arr, int cur_indent, int indent_amount) +{ + putchar('['); + + if (arr[0] == NULL) { + putchar(']'); + return; + } + + for (size_t i = 0; arr[i+1] != NULL; i++) { + putchar('\n'); + add_indent(cur_indent); + print_json_value(*arr[i], cur_indent + indent_amount, indent_amount); + + if (arr[i + 1] != NULL) + putchar(','); + } + + putchar('\n'); + add_indent(cur_indent - indent_amount * 2); + putchar(']'); +} + +void print_json_value(struct json_value val, int cur_indent, + int indent_amount) +{ + switch (val.type) { + case string: + printf("\"%s\"", val.string); + break; + case number: + printf("%lf", val.number); + break; + case boolean: + printf("%s", val.boolean ? "true" : "false"); + break; + case null: + printf("null"); + break; + case object: + print_object(*val.object, cur_indent + indent_amount, indent_amount); + break; + case array: + print_array(val.array, cur_indent + indent_amount, indent_amount); + break; + default: + printf(""); + } +} + +void print_json(struct json_value val, int indent) +{ + print_json_value(val, 0, indent); +} diff --git a/src/main.c b/src/main.c index 326b2e9..4e72d66 100644 --- a/src/main.c +++ b/src/main.c @@ -1,5 +1,5 @@ -#include "json_obj.h" +#include "json_value.h" #include "parse.h" #include "util.h" diff --git a/src/parse.c b/src/parse.c index 9356cdb..6ea29e1 100644 --- a/src/parse.c +++ b/src/parse.c @@ -1,22 +1,19 @@ #include "parse.h" +#include "json_value.h" +#include "util.h" + #include // isspace, isdigit #include // err, errx -#include // va_list #include #include // exit, EXIT_SUCCESS, EXIT_FAILURE #include // strcmp -#include "json_obj.h" -#include "util.h" - #define EARLY_EOF 202 #define MALLOC_DIE 201 #define UNEXPECTED_CHAR 200 -#define ERROR_CONTEXT_LEN 80 - char* read_string(FILE* fp); obj_t* read_object(FILE* fp); void discard_whitespace(FILE* fp); @@ -24,104 +21,94 @@ bool read_boolean(FILE* fp); void read_null(FILE* fp); double read_number(FILE* fp); struct json_value** read_array(FILE* fp); -void err_ctx(int exit_code, FILE* fp, const char* format, ...); - -void print_object(obj_t obj, int cur_indent, int indent_amount); -void print_json_value(struct json_value val, int cur_indent, int indent_amount); -void print_array(struct json_value** arr, int cur_indent, int indent_amount); - -// define as a macro to make debugging smoother -#define discard_whitespace(fp) \ - do { \ - int c; \ - while (isspace(c = fgetc(fp))) { \ - if (c == EOF) \ - err_ctx(EARLY_EOF, fp, "(%s) unexpected EOF", __func__); \ - } \ - ungetc(c, fp); \ - } while (0); +void discard_whitespace(FILE* fp); /* - Prints parser errors with surrounding context and - terminates the program. - - exit_code - code to exit with - fp - file causing parser errors - format - error message format string - ... - format string arguments + Consumes the next whitespace character in a file stream + and discards them. */ -__attribute__((__noreturn__)) void err_ctx(int exit_code, FILE* fp, const char* format, ...) +void discard_whitespace(FILE* fp) { - va_list args; - static char context[ERROR_CONTEXT_LEN]; + int c; - va_start(args, format); - fputc('\n', stderr); - vfprintf(stderr, format, args); - fprintf(stderr, " (at index %zu)\n", ftell(fp)); - va_end(args); - - if (fseek(fp, -(ERROR_CONTEXT_LEN / 2), SEEK_CUR) == 0) { - size_t n_read = fread(context, sizeof(char), ERROR_CONTEXT_LEN, fp); - - fprintf(stderr, "\ncontext:\n"); - - int arrow_offset = 0; - size_t i; - for (i = 0; i < ERROR_CONTEXT_LEN / 2 && i < n_read; i++) { - switch (context[i]) { - case '\n': - fprintf(stderr, "\\n"); - arrow_offset += 2; - break; - case '\r': - fprintf(stderr, "\\r"); - arrow_offset += 2; - break; - case '\t': - fprintf(stderr, "\\t"); - arrow_offset += 2; - break; - default: - fputc(context[i], stderr); - arrow_offset += 1; - break; - } - } - - for (; i < ERROR_CONTEXT_LEN && i < n_read; i++) { - switch (context[i]) { - case '\n': - fprintf(stderr, "\\n"); - break; - case '\t': - fprintf(stderr, "\\t"); - break; - default: - fputc(context[i], stderr); - break; - } - } - - fputc('\n', stderr); - for (int i = 0; i < arrow_offset - 2; i++) - fputc(' ', stderr); - - fputc('^', stderr); - fputc('\n', stderr); + while (isspace(c = fgetc(fp))) { + if (c == EOF) + err_ctx(EARLY_EOF, fp, "(%s) unexpected EOF", __func__); } - exit(exit_code); + ungetc(c, fp); } /* - A JSON string is a sequence of zero or more Unicode characters, wrapped in - double quotes, using backslash escapes. + Consumes the next JSON value in a file stream and returns a + corresponding json_value. See `json_value.h` for details. - A character is represented as a single character JSON string. A JSON string - is very much like a C or Java string. + A JSON value can be a JSON string in double quotes, or a JSON number, + or true or false or null, or a JOSN object or a JSON array. - Consumes a JSON string from a file stream and returns a corresponding char* + These structures can be nested. +*/ +struct json_value parse_json_value(FILE* fp) +{ + discard_whitespace(fp); + int c = fgetc(fp); + + struct json_value result = { 0 }; + + switch (c) { + case EOF: + err_ctx(EARLY_EOF, fp, "(%s) unexpected EOF", __func__); + + case '{': + result.type = object; + result.object = read_object(fp); + break; + + case '"': + result.type = string; + result.string = read_string(fp); + break; + + case '[': + result.type = array; + result.array = read_array(fp); + break; + + case 't': + case 'f': + ungetc(c, fp); + result.type = boolean; + result.boolean = read_boolean(fp); + break; + + case 'n': + ungetc(c, fp); + read_null(fp); + result.type = null; + result.number = 0L; + break; + + default: + if (isdigit(c)) { + result.type = number; + result.number = read_number(fp); + } else { + ungetc(c, fp); + err_ctx(UNEXPECTED_CHAR, fp, "(%s) unexpected symbol %c", __func__, c); + } + } + + return result; +} + +/* + A JSON string is a sequence of zero or more Unicode characters, wrapped in + double quotes, using backslash escapes. + + A character is represented as a single character JSON string. A JSON string + is very much like a C or Java string. + + Consumes a JSON string from a file stream and returns a corresponding char* */ char* read_string(FILE* fp) { @@ -269,7 +256,7 @@ struct json_value** read_array(FILE* fp) break; case ']': - output[i] = NULL; + output[i++] = NULL; return realloc_or_die(output, i * sizeof(struct json_value*)); case ',': @@ -336,7 +323,6 @@ bool read_boolean(FILE* fp) err_ctx(UNEXPECTED_CHAR, fp, "(%s) unexpected symbol", __func__); } -// TODO: fix int overflow /* A JSON number is very much like a C or Java number, except that the octal and hexadecimal formats are not used. @@ -351,155 +337,3 @@ double read_number(FILE* fp) return n; } - -/* - Consumes the next JSON value in a file stream and returns a - corresponding json_value - - A JSON value can be a JSON string in double quotes, or a JSON number, - or true or false or null, or a JOSN object or a JSON array. - These structures can be nested. -*/ -struct json_value parse_json_value(FILE* fp) -{ - discard_whitespace(fp); - int c = fgetc(fp); - - struct json_value result = { 0 }; - - switch (c) { - case EOF: - err_ctx(EARLY_EOF, fp, "(%s) unexpected EOF", __func__); - - case '{': - result.type = object; - result.object = read_object(fp); - break; - - case '"': - result.type = string; - result.string = read_string(fp); - break; - - case '[': - result.type = array; - result.array = read_array(fp); - break; - - case 't': - case 'f': - ungetc(c, fp); - result.type = boolean; - result.boolean = read_boolean(fp); - break; - - case 'n': - ungetc(c, fp); - read_null(fp); - result.type = null; - result.number = 0L; - break; - - default: - if (isdigit(c)) { - result.type = number; - result.number = read_number(fp); - } else { - ungetc(c, fp); - err_ctx(UNEXPECTED_CHAR, fp, "(%s) unexpected symbol %c", __func__, c); - } - } - - return result; -} - -void add_indent(int n) -{ - for (int i = 0; i < n; i++) - putchar(' '); -} - -void print_object(obj_t obj, int cur_indent, int indent_amount) -{ - putchar('{'); - - bool first = true; - - for (size_t i = 0; i < OBJ_SIZE; i++) { - struct obj_entry* e = obj[i]; - - while (e != NULL) { - if (!first) - putchar(','); - - first = false; - - putchar('\n'); - add_indent(cur_indent); - printf("\"%s\": ", e->key); - print_json_value(*(e->val), cur_indent + indent_amount, indent_amount); - - e = e->next; - } - } - - putchar('\n'); - add_indent(cur_indent - indent_amount * 2); - putchar('}'); -} - -void print_array(struct json_value** arr, int cur_indent, int indent_amount) -{ - putchar('['); - - if (arr[0] == NULL) { - putchar(']'); - return; - } - - size_t i; - - for (i = 0; arr[i + 1] != NULL; i++) { - putchar('\n'); - add_indent(cur_indent); - print_json_value(*arr[i], cur_indent + indent_amount, indent_amount); - if (arr[i + 1] != NULL) - putchar(','); - } - - putchar('\n'); - add_indent(cur_indent - indent_amount * 2); - putchar(']'); -} - -void print_json_value(struct json_value val, int cur_indent, - int indent_amount) -{ - switch (val.type) { - case string: - printf("\"%s\"", val.string); - break; - case number: - printf("%lf", val.number); - break; - case boolean: - printf("%s", val.boolean ? "true" : "false"); - break; - case null: - printf("null"); - break; - case object: - print_object(*val.object, cur_indent + indent_amount, indent_amount); - break; - case array: - print_array(val.array, cur_indent + indent_amount, indent_amount); - break; - default: - printf(""); - } -} - -void print_json(struct json_value val, int indent) -{ - print_json_value(val, 0, indent); -} diff --git a/src/util.c b/src/util.c index a51da78..1785bc5 100644 --- a/src/util.c +++ b/src/util.c @@ -1,11 +1,14 @@ #include "util.h" -#include -#include -#include -#include -#include +#include // err +#include // errno +#include // backtrace +#include // va_list +#include // fprintf +#include // malloc, realloc, calloc + +#define ERROR_CONTEXT_LEN 80 void* malloc_or_die(size_t size) { @@ -27,7 +30,7 @@ void* realloc_or_die(void* ptr, size_t size) err(errno, "realloc_or_die failed"); } - fprintf(stderr, "\nrealloc_or_die returned NULL, ptr: %p size: %zu\n", ptr, size); + fprintf(stderr, "\nrealloc_or_die returned NULL but errno is 0, ptr: %p size: %zu\n", ptr, size); exit(EXIT_FAILURE); } @@ -65,3 +68,76 @@ void print_trace() free(strings); #endif } + +/* + Prints parser errors with surrounding context and + terminates the program. + + exit_code - code to exit with + fp - file causing parser errors + format - error message format string + ... - format string arguments +*/ +__attribute__((__noreturn__)) void err_ctx(int exit_code, FILE* fp, const char* format, ...) +{ + va_list args; + static char context[ERROR_CONTEXT_LEN]; + + va_start(args, format); + fputc('\n', stderr); + vfprintf(stderr, format, args); + fprintf(stderr, " (at index %zu)\n", ftell(fp)); + va_end(args); + + if (fseek(fp, -(ERROR_CONTEXT_LEN / 2), SEEK_CUR) == 0) { + size_t n_read = fread(context, sizeof(char), ERROR_CONTEXT_LEN, fp); + + fprintf(stderr, "\ncontext:\n"); + + int arrow_offset = 0; + size_t i; + for (i = 0; i < ERROR_CONTEXT_LEN / 2 && i < n_read; i++) { + switch (context[i]) { + case '\n': + fprintf(stderr, "\\n"); + arrow_offset += 2; + break; + case '\r': + fprintf(stderr, "\\r"); + arrow_offset += 2; + break; + case '\t': + fprintf(stderr, "\\t"); + arrow_offset += 2; + break; + default: + fputc(context[i], stderr); + arrow_offset += 1; + break; + } + } + + for (; i < ERROR_CONTEXT_LEN && i < n_read; i++) { + switch (context[i]) { + case '\n': + fprintf(stderr, "\\n"); + break; + case '\t': + fprintf(stderr, "\\t"); + break; + default: + fputc(context[i], stderr); + break; + } + } + + fputc('\n', stderr); + for (int i = 0; i < arrow_offset - 2; i++) + fputc(' ', stderr); + + fputc('^', stderr); + fputc('\n', stderr); + } + + exit(exit_code); +}