first version

This commit is contained in:
overflowerror 2021-04-29 20:39:48 +02:00
parent b4995a65ba
commit 92ef70c668
8 changed files with 1513 additions and 0 deletions

41
Makefile Normal file
View file

@ -0,0 +1,41 @@
CC = gcc
CFLAGS = -std=c99 -Wall -D_POSIX_C_SOURCE=201112L -D_XOPEN_SOURCE=500 -D_GNU_SOURCE -static -g
LD = gcc
LDFLAGS =
AR = ar
ARFLAGS = rcs
BIN_NAME = demo
LIB_NAME = libcjson.a
OBJS = obj/base.o obj/parse.o obj/query.o obj/stringify.o
DEPS = $(OBJS:%.o=%.d)
all: $(BIN_NAME) $(LIB_NAME) test
$(BIN_NAME): obj/demo.o $(OBJS)
$(LD) $(LDFLAGS) -o $@ $^
$(LIB_NAME): CFLAGS += -fPIC
$(LIB_NAME): $(OBJS)
$(AR) $(ARFLAGS) $@ $^
test: obj/test.o $(OBJS)
$(LD) $(LDFLAGS) -o $@ $^
-include $(DEPS)
obj/%.o: src/%.c obj
$(CC) $(CFLAGS) -MMD -c -o $@ $<
obj:
@mkdir -p obj
clean:
@echo "Cleaning up..."
@rm -f obj/*.o
@rm -f obj/*.d
@rm -f test
@rm -f $(BIN_NAME)
@rm -f $(LIB_NAME)

237
src/base.c Normal file
View file

@ -0,0 +1,237 @@
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <stdarg.h>
#include <string.h>
#include "json.h"
void json_free_r(jsonValue_t* value) {
jsonArray_t array;
jsonObject_t object;
switch(value->type) {
case JSON_NULL:
break;
case JSON_ARRAY:
array = value->value.array;
for (int i = 0; i < array.size; i++) {
json_free_r(&(array.entries[i]));
}
free(array.entries);
break;
case JSON_OBJECT:
object = value->value.object;
for (int i = 0; i < object.size; i++) {
free(object.entries[i].key);
json_free_r(&(object.entries[i].value));
}
free(object.entries);
break;
case JSON_STRING:
free(value->value.string);
break;
default:
break;
}
}
void json_free(jsonValue_t* value) {
if (value == NULL)
return;
json_free_r(value);
free(value);
}
jsonValue_t* json_value() {
jsonValue_t* value = malloc(sizeof(jsonValue_t));
return value;
}
jsonValue_t* json_null() {
jsonValue_t* value = json_value();
value->type = JSON_NULL;
return value;
}
jsonValue_t* json_double(double d) {
jsonValue_t* value = json_value();
value->type = JSON_DOUBLE;
value->value.real = d;
return value;
}
jsonValue_t* json_long(long l) {
jsonValue_t* value = json_value();
value->type = JSON_LONG;
value->value.integer = l;
return value;
}
jsonValue_t* json_bool(bool b) {
jsonValue_t* value = json_value();
value->type = JSON_BOOL;
value->value.boolean = b;
return value;
}
jsonValue_t* json_string(const char* s) {
jsonValue_t* value = json_value();
value->type = JSON_STRING;
value->value.string = strdup(s);
return value;
}
jsonValue_t* json_array(bool freeAfterwards, size_t size, ...) {
jsonValue_t* value = json_value();
value->type = JSON_ARRAY;
value->value.array.size = size;
value->value.array.entries = malloc(sizeof(jsonValue_t) * size);
va_list ap;
va_start(ap, size);
for (size_t i = 0; i < size; i++) {
jsonValue_t* entry = va_arg(ap, jsonValue_t*);
value->value.array.entries[i] = *entry;
if (freeAfterwards) {
free(entry);
}
}
va_end(ap);
return value;
}
jsonValue_t* json_object(bool freeAfterwards, size_t size, ...) {
jsonValue_t* value = json_value();
value->type = JSON_OBJECT;
value->value.object.size = size;
value->value.object.entries = malloc(sizeof(jsonObjectEntry_t) * size);
va_list ap;
va_start(ap, size);
for (size_t i = 0; i < size; i++) {
const char* key = va_arg(ap, const char*);
jsonValue_t* entry = va_arg(ap, jsonValue_t*);
value->value.object.entries[i].key = strdup(key);
value->value.object.entries[i].value = *entry;
if (freeAfterwards) {
free(entry);
}
}
va_end(ap);
return value;
}
void print_repeat(int i, char c) {
for (int j = 0; j < i; j++) {
printf("%c", c);
}
}
void json_print_r(jsonValue_t* value, int indent) {
print_repeat(indent, '\t');
if (value == NULL) {
printf("[invalid]\n");
return;
}
switch(value->type) {
case JSON_ARRAY:
printf("array:\n");
for (size_t i = 0; i < value->value.array.size; i++) {
json_print_r(&value->value.array.entries[i], indent + 1);
}
break;
case JSON_OBJECT:
printf("object:\n");
for (size_t i = 0; i < value->value.object.size; i++) {
print_repeat(indent + 1, '\t');
printf("%s:\n", value->value.object.entries[i].key);
json_print_r(&value->value.object.entries[i].value, indent + 2);
}
break;
case JSON_DOUBLE:
printf("number: %lf\n", value->value.real);
break;
case JSON_LONG:
printf("number: %lld\n", value->value.integer);
break;
case JSON_STRING:
printf("string: \"%s\"\n", value->value.string);
break;
case JSON_BOOL:
printf("bool: %s\n", value->value.boolean ? "true" : "false");
break;
case JSON_NULL:
printf("null\n");
break;
}
}
void json_print(jsonValue_t* value) {
json_print_r(value, 0);
}
jsonValue_t json_clone_r(jsonValue_t value) {
jsonValue_t clone = value;
switch(value.type) {
case JSON_STRING:
clone.value.string = strdup(value.value.string);
break;
case JSON_ARRAY:
clone.value.array.size = value.value.array.size;
clone.value.array.entries = malloc(sizeof(jsonValue_t) * clone.value.array.size);
if (clone.value.array.entries == NULL) {
fprintf(stderr, "fuu\n");
exit(1);
}
for (size_t i = 0; i < clone.value.array.size; i++) {
clone.value.array.entries[i] = json_clone_r(value.value.array.entries[i]);
}
break;
case JSON_OBJECT:
clone.value.object.size = value.value.object.size;
clone.value.object.entries = malloc(sizeof(jsonObjectEntry_t) * clone.value.object.size);
if (clone.value.object.entries == NULL) {
fprintf(stderr, "fuu\n");
exit(1);
}
for (size_t i = 0; i < clone.value.object.size; i++) {
clone.value.object.entries[i].key = strdup(value.value.object.entries[i].key);
clone.value.object.entries[i].value = json_clone_r(value.value.object.entries[i].value);
}
break;
default:
break;
}
return clone;
}
jsonValue_t* json_clone(jsonValue_t* value) {
jsonValue_t* clone = malloc(sizeof(jsonValue_t));
if (clone == NULL) {
fprintf(stderr, "fuu\n");
exit(1);
}
*clone = json_clone_r(*value);
return clone;
}

57
src/demo.c Normal file
View file

@ -0,0 +1,57 @@
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <stdarg.h>
#include "json.h"
char* strclone(const char* string) {
char* clone = malloc(strlen(string) + 1);
if (clone == NULL) {
fprintf(stderr, "Fuck!\n");
exit(1);
}
strcpy(clone, string);
return clone;
}
int main() {
jsonValue_t* value = json_array(true, 4,
json_string("Hello"),
json_string("World"),
json_null(),
json_object(true, 3,
"okay", json_bool(true),
"pi", json_double(3.1415),
"leet", json_long(1337)
)
);
json_print(value);
printf("\n");
char* string = json_stringify(value);
printf("%s\n", string);
free(string);
printf("\n\n");
jsonValue_t* tmp = json_query(value, ".[3].okay");
json_print(tmp);
json_free(tmp);
json_free(value);
printf("\n\n");
value = json_parse("{ \"foo\": \"bar\", \"foobar\": [ 1337, 3.1415, null, false] }");
json_print(value);
json_free(value);
return 0;
}

65
src/json.h Normal file
View file

@ -0,0 +1,65 @@
#ifndef JSON_H
#define JSON_H
#include <stdbool.h>
typedef enum {
JSON_ARRAY,
JSON_OBJECT,
JSON_DOUBLE,
JSON_LONG,
JSON_STRING,
JSON_BOOL,
JSON_NULL,
} jsonValueType_t;
typedef struct {
size_t size;
struct jsonObjectEntry* entries;
} jsonObject_t;
typedef struct {
size_t size;
struct jsonValue* entries;
} jsonArray_t;
typedef struct jsonValue {
jsonValueType_t type;
union {
bool boolean;
double real;
long long integer;
char* string;
jsonObject_t object;
jsonArray_t array;
} value;
} jsonValue_t;
typedef struct jsonObjectEntry {
char* key;
struct jsonValue value;
} jsonObjectEntry_t;
void json_free(jsonValue_t* value);
jsonValue_t* json_value();
jsonValue_t* json_null();
jsonValue_t* json_double(double d);
jsonValue_t* json_long(long l);
jsonValue_t* json_bool(bool b);
jsonValue_t* json_string(const char* s);
jsonValue_t* json_array(bool freeAfterwards, size_t size, ...);
jsonValue_t* json_object(bool freeAfterwards, size_t size, ...);
void json_print(jsonValue_t* value);
jsonValue_t* json_clone(jsonValue_t* value);
jsonValue_t* json_object_get(jsonValue_t* value, const char* key);
jsonValue_t* json_array_get(jsonValue_t* value, size_t i);
jsonValue_t* json_query(jsonValue_t* value, const char* query);
char* json_stringify(jsonValue_t* value);
jsonValue_t* json_parse(const char* string);
#endif

463
src/parse.c Normal file
View file

@ -0,0 +1,463 @@
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include "json.h"
extern void json_free_r(jsonValue_t* value);
struct parserToken {
size_t length;
char* token;
};
#define EMPTY_PARSER_TOKEN ((struct parserToken) { .length = 0, .token = NULL})
#define PARSER_TOKEN_CHUNK_SIZE (1024)
void addToParserToken(struct parserToken* token, char c) {
if (token->length % PARSER_TOKEN_CHUNK_SIZE == 0) {
token->token = realloc(token->token, sizeof(char) * (token->length / PARSER_TOKEN_CHUNK_SIZE + 1) * PARSER_TOKEN_CHUNK_SIZE);
}
token->token[token->length++] = c;
}
void freeParserToken(struct parserToken* token) {
if (token->token != NULL) {
free(token->token);
}
}
typedef struct {
bool okay;
const char* errorFormat;
size_t index;
size_t line;
jsonValue_t value;
} jsonParsedValue_t;
#define JSON_PARSER_STATE_IDLE (0)
#define JSON_PARSER_STATE_STRING (10)
#define JSON_PARSER_STATE_ARRAY (20)
#define JSON_PARSER_STATE_OBJECT (30)
#define JSON_PARSER_STATE_LONG (40)
#define JSON_PARSER_STATE_DOUBLE (50)
jsonParsedValue_t json_parse_long(jsonParsedValue_t value, struct parserToken token) {
addToParserToken(&token, '\0');
char* endptr;
long long l = strtoll(token.token, &endptr, 10);
if (*endptr != '\0') {
value.index -= strlen(token.token) - (endptr - token.token);
value.errorFormat = "line %ld: illegal character '%c'\n";
} else {
value.okay = true;
value.value.type = JSON_LONG;
value.value.value.integer = l;
}
return value;
}
jsonParsedValue_t json_parse_double(jsonParsedValue_t value, struct parserToken token) {
addToParserToken(&token, '\0');
char* endptr;
double d = strtod(token.token, &endptr);
if (*endptr != '\0') {
value.index -= strlen(token.token) - (endptr - token.token);
value.errorFormat = "line %ld: illegal character '%c'";
} else {
value.okay = true;
value.value.type = JSON_DOUBLE;
value.value.value.real = d;
}
return value;
}
jsonParsedValue_t json_parse_r(const char* string, size_t index, size_t line, size_t length) {
jsonParsedValue_t value;
value.okay = false;
value.line = line;
int state = JSON_PARSER_STATE_IDLE;
struct parserToken token = EMPTY_PARSER_TOKEN;
bool escaped = false;
bool readyForNext = true;
char* key = NULL;
for (; index < length; index++) {
char c = string[index];
if (c == '\n') {
value.line++;
}
if (state != JSON_PARSER_STATE_STRING && (c == ' ' || c == '\t' || c == '\n')) {
continue;
}
switch(state) {
case JSON_PARSER_STATE_IDLE:
switch(c) {
case '"':
state = JSON_PARSER_STATE_STRING;
value.value.type = JSON_STRING;
break;
case '[':
state = JSON_PARSER_STATE_ARRAY;
value.value.type = JSON_ARRAY;
value.value.value.array.size = 0;
value.value.value.array.entries = NULL;
break;
case '{':
state = JSON_PARSER_STATE_OBJECT;
value.value.type = JSON_OBJECT;
value.value.value.object.size = 0;
value.value.value.object.entries = NULL;
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case '-':
addToParserToken(&token, c);
state = JSON_PARSER_STATE_LONG;
break;
default:
if (length - index >= strlen("null") && strncmp("null", string + index, strlen("null")) == 0) {
value.okay = true;
value.index = index + strlen("null");
value.value.type = JSON_NULL;
} else if (length - index >= strlen("true") && strncmp("true", string + index, strlen("true")) == 0) {
value.okay = true;
value.index = index + strlen("true");
value.value.type = JSON_BOOL;
value.value.value.boolean = true;
} else if (length - index >= strlen("false") && strncmp("false", string + index, strlen("false")) == 0) {
value.okay = true;
value.index = index + strlen("false");
value.value.type = JSON_BOOL;
value.value.value.boolean = false;
} else {
value.index = index;
value.errorFormat = "illegal character in line %d: '%c'";
}
return value;
}
break;
case JSON_PARSER_STATE_STRING:
if (!escaped && c == '"') {
addToParserToken(&token, '\0');
value.okay = true;
value.index = index + 1;
value.value.value.string = strdup(token.token);
freeParserToken(&token);
return value;
}
if (!escaped && c == '\\') {
escaped = true;
break;
} else if (escaped) {
switch(c) {
case 'b':
addToParserToken(&token, '\b');
break;
case 'f':
addToParserToken(&token, '\f');
break;
case 'n':
addToParserToken(&token, '\n');
break;
case 'r':
addToParserToken(&token, '\r');
break;
case 't':
addToParserToken(&token, '\t');
break;
case 'u':
value.okay = false;
value.index = index;
value.errorFormat = "line %ld: \\u-syntax is not supported";
return value;
case '"':
case '\\':
case '/':
addToParserToken(&token, c);
break;
default:
addToParserToken(&token, '\\');
addToParserToken(&token, c);
break;
}
escaped = false;
} else {
switch(c) {
case '\b':
case '\f':
case '\n':
case '\r':
case '\t':
value.okay = false;
value.index = index;
value.errorFormat = "line %ld: control characters are not allowed in json strings";
return value;
default:
addToParserToken(&token, c);
break;
}
}
break;
case JSON_PARSER_STATE_LONG:
if (c >= '0' && c <= '9') {
addToParserToken(&token, c);
} else if (c == '.' || c == 'e') {
addToParserToken(&token, c);
state = JSON_PARSER_STATE_DOUBLE;
} else {
value.index = index;
value = json_parse_long(value, token);
freeParserToken(&token);
return value;
}
break;
case JSON_PARSER_STATE_DOUBLE:
if ((c >= '0' && c <= '9') || c == '.' || c == '+' || c == '-' || c == 'e') {
addToParserToken(&token, c);
} else {
value.index = index;
value = json_parse_double(value, token);
freeParserToken(&token);
return value;
}
break;
case JSON_PARSER_STATE_ARRAY:
{
if (c == ']') {
value.index = index + 1;
value.okay = true;
return value;
}
if (c == ',') {
if (readyForNext) {
json_free_r(&value.value);
value.errorFormat = "line %ld: unexpected '%c'";
value.index = index;
return value;
}
readyForNext = true;
break;
}
if (!readyForNext) {
json_free_r(&value.value);
value.errorFormat = "line %ld: unexpected '%c'; ',' or ']' expected";
value.index = index;
return value;
}
jsonParsedValue_t entry = json_parse_r(string, index, value.line, length);
if (!entry.okay) {
json_free_r(&value.value);
return entry;
}
jsonValue_t* entries = realloc(value.value.value.array.entries, sizeof(jsonValue_t) * (value.value.value.array.size + 1));
if (entries == NULL) {
json_free_r(&value.value);
value.errorFormat = "allocation for array failed";
return value;
}
entries[value.value.value.array.size++] = entry.value;
value.value.value.array.entries = entries;
index = entry.index - 1;
readyForNext = false;
}
break;
case JSON_PARSER_STATE_OBJECT:
{
if (c == '}') {
value.index = index + 1;
value.okay = true;
return value;
}
if (c == ',') {
if (readyForNext) {
json_free_r(&value.value);
value.errorFormat = "line %ld: unexpected ','";
value.index = index;
return value;
}
if (key != NULL) {
json_free_r(&value.value);
value.errorFormat = "line %ld: unexpected ','; ':' expected";
value.index = index;
return value;
}
readyForNext = true;
break;
}
if (c == ':') {
if (readyForNext) {
json_free_r(&value.value);
value.errorFormat = "line %ld: unexpected character ':'";
value.index = index;
return value;
}
if (key == NULL) {
json_free_r(&value.value);
value.errorFormat = "line %ld: unexpected ':'; key is missing";
value.index = index;
return value;
}
readyForNext = true;
break;
}
if (!readyForNext) {
json_free_r(&value.value);
value.errorFormat = "line %ld: unexpected '%c'; ',' or '}' expected";
value.index = index;
return value;
}
jsonParsedValue_t entry = json_parse_r(string, index, value.line, length);
if (!entry.okay) {
json_free_r(&value.value);
return entry;
}
if (key == NULL) {
if (entry.value.type != JSON_STRING) {
json_free_r(&value.value);
json_free_r(&entry.value);
value.errorFormat = "line %ld: key is missing";
value.index = index;
return value;
}
key = entry.value.value.string;
} else {
jsonObjectEntry_t* entries = realloc(value.value.value.array.entries, sizeof(jsonObjectEntry_t) * (value.value.value.object.size + 1));
if (entries == NULL) {
json_free_r(&value.value);
value.errorFormat = "allocation for object failed";
return value;
}
entries[value.value.value.object.size].key = key;
entries[value.value.value.object.size++].value = entry.value;
value.value.value.object.entries = entries;
key = NULL;
}
index = entry.index - 1;
readyForNext = false;
}
break;
default:
value.index = index;
value.errorFormat = "illegal state in line %ld";
return value;
}
}
if (state == JSON_PARSER_STATE_LONG) {
value.index = index;
value = json_parse_long(value, token);
freeParserToken(&token);
return value;
}
if (state == JSON_PARSER_STATE_DOUBLE) {
value.index = index;
value = json_parse_double(value, token);
freeParserToken(&token);
return value;
}
freeParserToken(&token);
value.index = index - 1;
value.errorFormat = "unexpected end of input on line %ld";
return value;
}
jsonValue_t* json_parse(const char* string) {
size_t length = strlen(string);
jsonParsedValue_t parsedValue = json_parse_r(string, 0, 1, length);
if (!parsedValue.okay) {
printf("%ld\n", parsedValue.index);
printf(parsedValue.errorFormat, parsedValue.line, string[parsedValue.index]);
printf("\n");
return NULL;
}
if (length != parsedValue.index) {
printf("unexptected character '%c' in line %ld\n", string[parsedValue.index], parsedValue.line);
return NULL;
}
jsonValue_t* value = malloc(sizeof(jsonValue_t));
*value = parsedValue.value;
return value;
}

118
src/query.c Normal file
View file

@ -0,0 +1,118 @@
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include "json.h"
jsonValue_t* json_object_get(jsonValue_t* value, const char* key) {
if (value->type != JSON_OBJECT)
return NULL;
for (size_t i = 0; i < value->value.object.size; i++) {
if (strcmp(value->value.object.entries[i].key, key) == 0) {
return json_clone(&value->value.object.entries[i].value);
}
}
return json_null();
}
jsonValue_t* json_array_get(jsonValue_t* value, size_t i) {
if (value->type != JSON_ARRAY)
return NULL;
if (value->value.array.size <= i) {
return json_null();
}
return json_clone(&value->value.array.entries[i]);
}
jsonValue_t* json_query(jsonValue_t* value, const char* query) {
#define JSON_QUERY_BUFFER_SIZE (1024)
char buffer[JSON_QUERY_BUFFER_SIZE];
value = json_clone(value);
while(true) {
if (query[0] == '\0')
break;
if (query[0] != '.') {
json_free(value);
return NULL;
}
size_t length;
for (length = 1; query[length] != '\0' && query[length] != '.'; length++);
if (length >= JSON_QUERY_BUFFER_SIZE) {
json_free(value);
return NULL;
}
memcpy(buffer, query, length);
buffer[length] = '\0';
query = query + length;
if (strcmp(buffer, ".") == 0) {
continue;
}
char* _buffer = buffer + 1;
length--;
switch(value->type) {
case JSON_ARRAY:
if (_buffer[0] != '[' || _buffer[length - 1] != ']') {
json_free(value);
return NULL;
}
_buffer[length - 1] = '\0';
_buffer = _buffer + 1;
char* endptr;
long long index = strtoll(_buffer, &endptr, 10);
if (*endptr != '\0') {
json_free(value);
return NULL;
}
if (index < 0) {
json_free(value);
return NULL;
}
jsonValue_t* arrayEntry = json_array_get(value, index);
json_free(value);
value = arrayEntry;
break;
case JSON_OBJECT:
if (_buffer[0] == '"') {
if (_buffer[length - 1] != '"') {
json_free(value);
return NULL;
}
_buffer[length - 1] = '\0';
_buffer = _buffer + 1;
}
jsonValue_t* objectEntry = json_object_get(value, _buffer);
json_free(value);
value = objectEntry;
break;
default:
json_free(value);
return NULL;
}
if (value == NULL)
return NULL;
}
return value;
}

170
src/stringify.c Normal file
View file

@ -0,0 +1,170 @@
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "json.h"
size_t string_escaped_length(const char* string) {
size_t length = 0;
char c;
size_t i;
for (i = 0, c = string[i]; c != '\0'; c = string[++i]) {
switch(c) {
case '\\':
case '"':
case '/':
case '\b':
case '\f':
case '\n':
case '\r':
case '\t':
length += 2;
break;
default:
length += 1;
break;
}
}
return length;
}
size_t json_length(jsonValue_t* value) {
size_t result = 0;
switch (value->type) {
case JSON_NULL:
return 4;
case JSON_STRING:
return 2 + string_escaped_length(value->value.string);
case JSON_DOUBLE:
return snprintf(NULL, 0, "%lf", value->value.real);
case JSON_LONG:
return snprintf(NULL, 0, "%lld", value->value.integer);
case JSON_BOOL:
return 5;
case JSON_ARRAY:
result += 2;
for (size_t i = 0; i < value->value.array.size; i++) {
result += json_length(&(value->value.array.entries[i])) + 1;
}
return result;
case JSON_OBJECT:
result += 2;
for (size_t i = 0; i < value->value.object.size; i++) {
result += strlen(value->value.object.entries[i].key) + 2 + 1;
result += json_length(&(value->value.object.entries[i].value)) + 1;
}
return result;
default:
return 0;
}
}
size_t json_write_string(char* target, size_t maxSize, const char* source) {
#define JSON_WRITE_STRING_ADD(c) target[size++] = c; if (size == maxSize) return size;
size_t size = 0;
JSON_WRITE_STRING_ADD('"');
size_t i;
char c;
for (i = 0, c = source[i]; c != '\0'; c = source[++i]) {
switch(c) {
case '\\':
JSON_WRITE_STRING_ADD('\\');
JSON_WRITE_STRING_ADD('\\');
break;
case '"':
JSON_WRITE_STRING_ADD('\\');
JSON_WRITE_STRING_ADD('"');
break;
case '/':
JSON_WRITE_STRING_ADD('\\');
JSON_WRITE_STRING_ADD('/');
break;
case '\b':
JSON_WRITE_STRING_ADD('\\');
JSON_WRITE_STRING_ADD('b');
break;
case '\f':
JSON_WRITE_STRING_ADD('\\');
JSON_WRITE_STRING_ADD('f');
break;
case '\n':
JSON_WRITE_STRING_ADD('\\');
JSON_WRITE_STRING_ADD('n');
break;
case '\r':
JSON_WRITE_STRING_ADD('\\');
JSON_WRITE_STRING_ADD('r');
break;
case '\t':
JSON_WRITE_STRING_ADD('\\');
JSON_WRITE_STRING_ADD('t');
break;
default:
JSON_WRITE_STRING_ADD(c);
break;
}
}
JSON_WRITE_STRING_ADD('"');
return size;
}
size_t json_stringify_r(char* string, size_t index, size_t totalSize, jsonValue_t* value) {
switch(value->type) {
case JSON_NULL:
return snprintf(string + index, totalSize - index, "null") + index;
case JSON_STRING:
return json_write_string(string + index, totalSize - index, value->value.string) + index;
case JSON_DOUBLE:
return snprintf(string + index, totalSize - index, "%lf", value->value.real) + index;
case JSON_LONG:
return snprintf(string + index, totalSize - index, "%lld", value->value.integer) + index;
case JSON_BOOL:
return snprintf(string + index, totalSize - index, "%s", value->value.boolean ? "true" : "false") + index;
case JSON_ARRAY:
index += snprintf(string + index, totalSize - index, "[");
for (size_t i = 0; i < value->value.array.size; i++) {
index = json_stringify_r(string, index, totalSize, &(value->value.array.entries[i]));
index += snprintf(string + index, totalSize - index, ",");
}
index--;
index += snprintf(string + index, totalSize - index, "]");
return index;
case JSON_OBJECT:
index += snprintf(string + index, totalSize - index, "{");
for (size_t i = 0; i < value->value.object.size; i++) {
index += json_write_string(string + index, totalSize - index, value->value.object.entries[i].key);
index += snprintf(string + index, totalSize - index, ":");
index = json_stringify_r(string, index, totalSize, &(value->value.object.entries[i].value));
index += snprintf(string + index, totalSize - index, ",");
}
index--; // replace last , with }
index += snprintf(string + index, totalSize - index, "}");
return index;
default:
return index;
}
}
char* json_stringify(jsonValue_t* value) {
size_t size = json_length(value) + 1;
char* string = malloc(size);
if (string == NULL)
return NULL;
json_stringify_r(string, 0, size, value);
return string;
}

362
src/test.c Normal file
View file

@ -0,0 +1,362 @@
#include <stdio.h>
#include <alloca.h>
#include <string.h>
#include <errno.h>
#include <stdbool.h>
#include <stdlib.h>
#include "json.h"
bool global = true;
bool overall = true;
void checkBool(bool ok, const char* check) {
const char* result;
if (ok) {
result = "[ OK ]";
} else {
result = "[FAILED]";
global = false;
}
printf("%s:%*s%s\n", check, (int) (30 - strlen(check)), "", result);
}
void checkInt(long long value, long long compare, const char* check) {
checkBool(value == compare, check);
}
void checkDouble(double value, double compare, const char* check) {
checkBool(value == compare, check);
}
void checkString(const char* value, const char* compare, const char* check) {
checkBool(strcmp(value, compare) == 0, check);
}
void checkVoid(const void* value, const void* compare, const char* check) {
checkBool(value == compare, check);
}
void checkNull(void* value, const char* check) {
checkBool(value != NULL, check);
}
void showError() {
fprintf(stderr, "Error: %s\n", strerror(errno));
}
/*bool hasData(int fd) {
int tmp = poll(&(struct pollfd){ .fd = fd, .events = POLLIN }, 1, 10);
return tmp == 1;
}*/
void test(const char* name, void (*testFunction)()) {
printf("%s\n", name);
printf("%.*s\n", (int) strlen(name),
"===================================");
testFunction();
if (!global)
overall = false;
printf("%s: %s\n\n", name, global ? "OK" : "FAILED");
global = true;
}
void header(const char* text) {
printf("\n");
printf("=======================================\n");
printf("== %s\n", text);
printf("=======================================\n");
}
void testDouble() {
double d;
d = 3.1415926;
jsonValue_t* v = json_double(d);
checkNull(v, "result is not null");
checkInt(v->type, JSON_DOUBLE, "type is correct");
checkDouble(v->value.real, d, "value is correct");
char* string = json_stringify(v);
size_t size = snprintf(NULL, 0, "%lf", d);
char* compare = alloca(size + 1);
sprintf(compare, "%lf", d);
checkString(string, compare, "stringify");
free(string);
json_free(v);
}
void testLong() {
long long l;
l = 1337;
jsonValue_t* v = json_long(l);
checkNull(v, "result is not null");
checkInt(v->type, JSON_LONG, "type is correct");
checkInt(v->value.integer, l, "value is correct");
char* string = json_stringify(v);
size_t size = snprintf(NULL, 0, "%lld", l);
char* compare = alloca(size + 1);
sprintf(compare, "%lld", l);
checkString(string, compare, "stringify");
free(string);
json_free(v);
}
void testBool() {
bool b;
b = true;
jsonValue_t* v = json_bool(b);
checkNull(v, "result is not null");
checkInt(v->type, JSON_BOOL, "type is correct");
checkBool(v->value.boolean, "value is correct");
char* string = json_stringify(v);
char* compare = "true";
checkString(string, compare, "stringify");
free(string);
json_free(v);
b = false;
v = json_bool(b);
checkNull(v, "result is not null");
checkInt(v->type, JSON_BOOL, "type is correct");
checkBool(!v->value.boolean, "value is correct");
string = json_stringify(v);
compare = "false";
checkString(string, compare, "stringify");
free(string);
json_free(v);
}
void testString() {
const char* s = "foobar";
jsonValue_t* v = json_string(s);
checkNull(v, "result is not null");
checkInt(v->type, JSON_STRING, "type is correct");
char* string = json_stringify(v);
char* compare = alloca(strlen(s) + 2 + 1);
sprintf(compare, "\"%s\"", s);
checkString(string, compare, "stringify");
free(string);
json_free(v);
}
void testNull() {
jsonValue_t* v = json_null();
checkNull(v, "result is not null");
checkInt(v->type, JSON_NULL, "type is correct");
char* string = json_stringify(v);
char* compare = "null";
checkString(string, compare, "stringify");
free(string);
json_free(v);
}
void testArray() {
jsonValue_t* value = json_array(true, 4,
json_string("Hello"),
json_string("World"),
json_null(),
json_object(true, 3,
"okay", json_bool(true),
"pi", json_double(3.1415),
"leet", json_long(1337)
)
);
checkNull(value, "result is not null");
checkInt(value->type, JSON_ARRAY, "type is correct");
checkInt(value->value.array.size, 4, "array length is correct");
checkInt(value->value.array.entries[0].type, JSON_STRING, "[0] type is correct");
checkInt(value->value.array.entries[1].type, JSON_STRING, "[1] type is correct");
checkInt(value->value.array.entries[2].type, JSON_NULL, "[2] type is correct");
checkInt(value->value.array.entries[3].type, JSON_OBJECT, "[3] type is correct");
char* string = json_stringify(value);
// the two zeros are technically not wrong but kinda hard to remove => let's keep them
char* compare = "[\"Hello\",\"World\",null,{\"okay\":true,\"pi\":3.141500,\"leet\":1337}]";
checkString(string, compare, "stringify");
free(string);
json_free(value);
}
void testObject() {
jsonValue_t* value = json_object(true, 3,
"foo", json_string("bar"),
"number", json_long(42),
"list", json_array(true, 3,
json_bool(true),
json_double(3.1415),
json_null()
)
);
checkNull(value, "result is not null");
checkInt(value->type, JSON_OBJECT, "type is correct");
checkInt(value->value.object.size, 3, "object length is correct");
checkString(value->value.object.entries[0].key, "foo", "[0] key is correct");
checkString(value->value.object.entries[1].key, "number", "[1] key is correct");
checkString(value->value.object.entries[2].key, "list", "[2] key is correct");
checkInt(value->value.object.entries[0].value.type, JSON_STRING, "[0] type is correct");
checkInt(value->value.object.entries[1].value.type, JSON_LONG, "[1] type is correct");
checkInt(value->value.object.entries[2].value.type, JSON_ARRAY, "[2] type is correct");
char* string = json_stringify(value);
char* compare = "{\"foo\":\"bar\",\"number\":42,\"list\":[true,3.141500,null]}";
checkString(string, compare, "stringify");
free(string);
json_free(value);
}
void testParse() {
jsonValue_t* value = json_parse("{ \"foo\": \"bar\", \"foobar\": [ 1337, 3.1415, null, false] }");
checkNull(value, "result is not null");
checkInt(value->type, JSON_OBJECT, "type is correct");
checkInt(value->value.object.size, 2, "object length is correct");
checkString(value->value.object.entries[0].key, "foo", "[0] key is correct");
checkString(value->value.object.entries[1].key, "foobar", "[1] key is correct");
checkInt(value->value.object.entries[0].value.type, JSON_STRING, "[0] type is correct");
checkInt(value->value.object.entries[1].value.type, JSON_ARRAY, "[1] type is correct");
checkString(value->value.object.entries[0].value.value.string, "bar", "[0] value is correct");
checkInt(value->value.object.entries[1].value.value.array.size, 4, "[0] array length is correct");
checkInt(value->value.object.entries[1].value.value.array.entries[0].type, JSON_LONG, "[0] type is correct");
checkInt(value->value.object.entries[1].value.value.array.entries[1].type, JSON_DOUBLE, "[1] type is correct");
checkInt(value->value.object.entries[1].value.value.array.entries[2].type, JSON_NULL, "[2] type is correct");
checkInt(value->value.object.entries[1].value.value.array.entries[3].type, JSON_BOOL, "[3] type is correct");
checkInt(value->value.object.entries[1].value.value.array.entries[0].value.integer, 1337, "[0][0] value is correct");
checkDouble(value->value.object.entries[1].value.value.array.entries[1].value.real, 3.1415, "[0][1] value is correct");
checkBool(!value->value.object.entries[1].value.value.array.entries[3].value.boolean, "[0][3] value is correct");
json_free(value);
}
void testQuery() {
jsonValue_t* value = json_array(true, 4,
json_string("Hello"),
json_string("World"),
json_null(),
json_object(true, 3,
"okay", json_bool(true),
"pi", json_double(3.1415),
"leet", json_long(1337)
)
);
jsonValue_t* tmp;
tmp = json_query(value, ".[0]");
checkNull(tmp, "in array, not null");
checkInt(tmp->type, JSON_STRING, "in array, type");
checkString(tmp->value.string, "Hello", "in array, value");
json_free(tmp);
tmp = json_query(value, ".[3].okay");
checkNull(tmp, "in obj in array, not null");
checkInt(tmp->type, JSON_BOOL, "in obj in array, type");
checkBool(tmp->value.boolean, "in obj in array, value");
json_free(tmp);
tmp = json_query(value, ".[4]");
checkNull(tmp, "not in array, not null");
checkInt(tmp->type, JSON_NULL, "not in array, type");
json_free(tmp);
tmp = json_query(value, ".[3].foobar");
checkNull(tmp, "not in obj in array, not null");
checkInt(tmp->type, JSON_NULL, "not in obj in array, type");
json_free(tmp);
json_free(value);
}
void testClone() {
jsonValue_t* value = json_array(true, 4,
json_string("Hello"),
json_string("World"),
json_null(),
json_object(true, 3,
"okay", json_bool(true),
"pi", json_double(3.1415),
"leet", json_long(1337)
)
);
jsonValue_t* cloned = json_clone(value);
checkNull(cloned, "clone not null");
checkBool(((long long) value) != ((long long) cloned), "different addr");
jsonValue_t* valueMember = &(value->value.array.entries[3]);
jsonValue_t* cloneMember = &(cloned->value.array.entries[3]);
checkNull(cloneMember, "member clone not null");
checkBool(((long long) valueMember) != ((long long) cloneMember), "member different addr");
json_free(cloned);
json_free(value);
}
int main(int argc, char** argv) {
header("Types");
test("double", &testDouble);
test("long", &testLong);
test("bool", &testBool);
test("string", &testString);
test("null", &testNull);
test("array", &testArray);
test("array", &testObject);
header("Functionality");
test("parse", &testParse);
test("query", &testQuery);
test("clone", &testClone);
printf("\nOverall: %s\n", overall ? "OK" : "FAILED");
}