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(...)`
This commit is contained in:
olemorud
2023-04-23 22:34:24 +02:00
parent 4b91c62750
commit 9a88d1cbdc
9 changed files with 313 additions and 302 deletions

View File

@@ -10,22 +10,24 @@ COMPILER ?= gcc
CC := gcc CC := gcc
# -fsanitize={address,undefined} # -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.release := -O3 -march=native -DNDEBUG
CFLAGS.gcc := ${CFLAGS.gcc.${BUILD}} -Iinclude -W{all,extra,error} -fstack-protector-all -std=gnu11 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.release=-O3 -march=native -DNDEBUG
CFLAGS.clang=-Wextra -Wall -Wpedantic -fstack-protector-all ${CFLAGS.clang.${BUILD}} CFLAGS.clang=-Wextra -Wall -Wpedantic -fstack-protector-all ${CFLAGS.clang.${BUILD}}
CFLAGS := ${CFLAGS.${COMPILER}} CFLAGS := ${CFLAGS.${COMPILER}}
LD_PRELOAD:=
# ==== end set compiler flags ==== # ==== end set compiler flags ====
BUILD_DIR := bin/${BUILD} BUILD_DIR := bin/${BUILD}
OBJ_DIR := .obj/${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)) OBJS := $(patsubst %,$(OBJ_DIR)/%,$(_OBJS))

View File

@@ -1,22 +0,0 @@
#ifndef _obj_H
#define _obj_H
#include <stdbool.h>
#include <stddef.h>
#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

41
include/json_value.h Normal file
View File

@@ -0,0 +1,41 @@
#ifndef _JSON_VALUE_H
#define _JSON_VALUE_H
#include <stdbool.h> // 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

View File

@@ -2,31 +2,10 @@
#ifndef _PARSE_H #ifndef _PARSE_H
#define _PARSE_H #define _PARSE_H
#include "json_obj.h" #include "json_value.h"
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
enum json_type { object, #include <stdio.h> // FILE*
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;
};
};
struct json_value parse_json_value(FILE* fp); struct json_value parse_json_value(FILE* fp);
void print_json(struct json_value val, int indent);
#endif #endif

View File

@@ -3,6 +3,9 @@
#define _UTIL_H #define _UTIL_H
#include <stddef.h> #include <stddef.h>
#include <stdio.h>
__attribute__((__noreturn__)) void err_ctx(int exit_code, FILE* fp, const char* format, ...);
void* malloc_or_die(size_t size); void* malloc_or_die(size_t size);
void* realloc_or_die(void* ptr, size_t size); void* realloc_or_die(void* ptr, size_t size);

View File

@@ -5,9 +5,17 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include "json_obj.h" #include "json_value.h"
#include "util.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 djb2 string hash
credits: Daniel J. Bernstein 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("<unknown>");
}
}
void print_json(struct json_value val, int indent)
{
print_json_value(val, 0, indent);
}

View File

@@ -1,5 +1,5 @@
#include "json_obj.h" #include "json_value.h"
#include "parse.h" #include "parse.h"
#include "util.h" #include "util.h"

View File

@@ -1,22 +1,19 @@
#include "parse.h" #include "parse.h"
#include "json_value.h"
#include "util.h"
#include <ctype.h> // isspace, isdigit #include <ctype.h> // isspace, isdigit
#include <err.h> // err, errx #include <err.h> // err, errx
#include <stdarg.h> // va_list
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> // exit, EXIT_SUCCESS, EXIT_FAILURE #include <stdlib.h> // exit, EXIT_SUCCESS, EXIT_FAILURE
#include <string.h> // strcmp #include <string.h> // strcmp
#include "json_obj.h"
#include "util.h"
#define EARLY_EOF 202 #define EARLY_EOF 202
#define MALLOC_DIE 201 #define MALLOC_DIE 201
#define UNEXPECTED_CHAR 200 #define UNEXPECTED_CHAR 200
#define ERROR_CONTEXT_LEN 80
char* read_string(FILE* fp); char* read_string(FILE* fp);
obj_t* read_object(FILE* fp); obj_t* read_object(FILE* fp);
void discard_whitespace(FILE* fp); void discard_whitespace(FILE* fp);
@@ -24,104 +21,94 @@ bool read_boolean(FILE* fp);
void read_null(FILE* fp); void read_null(FILE* fp);
double read_number(FILE* fp); double read_number(FILE* fp);
struct json_value** read_array(FILE* fp); struct json_value** read_array(FILE* fp);
void err_ctx(int exit_code, FILE* fp, const char* format, ...); void discard_whitespace(FILE* fp);
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);
/* /*
Prints parser errors with surrounding context and Consumes the next whitespace character in a file stream
terminates the program. and discards them.
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, ...) void discard_whitespace(FILE* fp)
{ {
va_list args; int c;
static char context[ERROR_CONTEXT_LEN];
va_start(args, format); while (isspace(c = fgetc(fp))) {
fputc('\n', stderr); if (c == EOF)
vfprintf(stderr, format, args); err_ctx(EARLY_EOF, fp, "(%s) unexpected EOF", __func__);
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); ungetc(c, fp);
} }
/* /*
A JSON string is a sequence of zero or more Unicode characters, wrapped in Consumes the next JSON value in a file stream and returns a
double quotes, using backslash escapes. corresponding json_value. See `json_value.h` for details.
A character is represented as a single character JSON string. A JSON string A JSON value can be a JSON string in double quotes, or a JSON number,
is very much like a C or Java string. 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) char* read_string(FILE* fp)
{ {
@@ -269,7 +256,7 @@ struct json_value** read_array(FILE* fp)
break; break;
case ']': case ']':
output[i] = NULL; output[i++] = NULL;
return realloc_or_die(output, i * sizeof(struct json_value*)); return realloc_or_die(output, i * sizeof(struct json_value*));
case ',': case ',':
@@ -336,7 +323,6 @@ bool read_boolean(FILE* fp)
err_ctx(UNEXPECTED_CHAR, fp, "(%s) unexpected symbol", __func__); 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, A JSON number is very much like a C or Java number,
except that the octal and hexadecimal formats are not used. except that the octal and hexadecimal formats are not used.
@@ -351,155 +337,3 @@ double read_number(FILE* fp)
return n; 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("<unknown>");
}
}
void print_json(struct json_value val, int indent)
{
print_json_value(val, 0, indent);
}

View File

@@ -1,11 +1,14 @@
#include "util.h" #include "util.h"
#include <err.h> #include <err.h> // err
#include <errno.h> #include <errno.h> // errno
#include <execinfo.h> #include <execinfo.h> // backtrace
#include <stdio.h> #include <stdarg.h> // va_list
#include <stdlib.h> #include <stdio.h> // fprintf
#include <stdlib.h> // malloc, realloc, calloc
#define ERROR_CONTEXT_LEN 80
void* malloc_or_die(size_t size) 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"); 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); exit(EXIT_FAILURE);
} }
@@ -65,3 +68,76 @@ void print_trace()
free(strings); free(strings);
#endif #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);
}