#include "json.h" #include #define UNREACHABLE() assert(false) Json json_init(FILE* stream, Whitespace whitespace) { return (Json){ .stream = stream, .whitespace = whitespace, .indent_level = 0, .next_punctuation = BEGINNING, }; } void json_free(Json* json) { // `json_free` just exists for symmetry with `json_init`. // It does nothing, for now. } void begin_value(Json* json); void end_value(Json* json); void push_indentation(Json* json); void pop_indentation(Json* json); void indent(Json* json); /// Begin a JSON array value. void json_begin_array(Json* json) { begin_value(json); putc('[', json->stream); push_indentation(json); json->next_punctuation = NONE; } /// End a JSON array value. void json_end_array(Json* json) { pop_indentation(json); switch (json->next_punctuation) { case NONE: break; case COMMA: indent(json); break; case BEGINNING: case COLON: // The only way to get here is by either: // - calling `json_end_array` right after `json_init` // - calling `json_end_array` right after `json_add_object_field` // - setting `next_punctuation` explicitly UNREACHABLE(); } putc(']', json->stream); end_value(json); } /// Begin a JSON object value. void json_begin_object(Json* json) { begin_value(json); putc('{', json->stream); push_indentation(json); json->next_punctuation = NONE; } /// End a JSON object value. void json_end_object(Json* json) { pop_indentation(json); switch (json->next_punctuation) { case NONE: break; case COMMA: indent(json); break; case BEGINNING: case COLON: // The only way to get here is by either: // - calling `json_end_object` right after `json_init` // - calling `json_end_object` right after `json_add_object_field` // - settings `next_punctuation` explicitly UNREACHABLE(); } putc('}', json->stream); end_value(json); } /// Add a JSON object field key. /// This should always be followed by adding a JSON value. void json_add_object_field(Json* json, const char* key) { begin_value(json); fprintf(json->stream, "\"%s\"", key); json->next_punctuation = COLON; } /// Add a JSON string value. void json_add_string(Json* json, const char* value) { begin_value(json); fprintf(json->stream, "\"%s\"", value); end_value(json); } /// Add a JSON long value. void json_add_long(Json* json, long value) { begin_value(json); fprintf(json->stream, "%ld", value); end_value(json); } /// Add a JSON double value. void json_add_double(Json* json, double value) { begin_value(json); fprintf(json->stream, "%f", value); end_value(json); } /// Add a JSON bool value. void json_add_bool(Json* json, bool value) { begin_value(json); const char* s; if (value) { s = "true"; } else { s = "false"; } fputs(s, json->stream); end_value(json); } /// Add a JSON null value void json_add_null(Json* json) { begin_value(json); fputs("null", json->stream); end_value(json); } /// Begin a new JSON value. /// Appends the appropriate punctuation to the stream. void begin_value(Json* json) { switch (json->next_punctuation) { case BEGINNING: break; case NONE: indent(json); break; case COMMA: fputc(',', json->stream); indent(json); break; case COLON: putc(':', json->stream); if (json->whitespace != MINIFIED) { fputc(' ', json->stream); } break; } } /// End a JSON value. void end_value(Json* json) { json->next_punctuation = COMMA; } /// Increment the indent level by one. void push_indentation(Json* json) { json->indent_level += 1; } /// Decrement the indent level by one. void pop_indentation(Json* json) { json->indent_level -= 1; } /// Append a newline and whitespace for the next line, /// except when `whitespace` is set to `MINIFIED`. void indent(Json* json) { char indent_char = ' '; int n; switch (json->whitespace) { case MINIFIED: // In minified mode, we don't want newlines or whitespace. return; case INDENT_1: n = 1 * json->indent_level; break; case INDENT_2: n = 2 * json->indent_level; break; case INDENT_3: n = 3 * json->indent_level; break; case INDENT_4: n = 4 * json->indent_level; break; case INDENT_8: n = 8 * json->indent_level; break; case INDENT_TAB: indent_char = '\t'; n = json->indent_level; break; } putc('\n', json->stream); for (int i = 0; i < n; i++) { putc(indent_char, json->stream); } }