commit d2182a433c06ed9100e1d3d167d29dd161da307a Author: ktkk Date: Wed Apr 2 12:52:45 2025 +0000 Initial commit diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..8392d15 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f5adaf3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +# Nix +result* + +# Misc +.direnv diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..bc7edb2 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,18 @@ +cmake_minimum_required(VERSION 3.20) + +project( + json + LANGUAGES + C +) + +set(CMAKE_C_STANDARD 23) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) + +add_executable( + ${PROJECT_NAME} + main.c + json.c +) + diff --git a/README.md b/README.md new file mode 100644 index 0000000..3ee6924 --- /dev/null +++ b/README.md @@ -0,0 +1,6 @@ +# Json + +A simple json stringifying library written in C out of frustration. + +See `main.c` for a usage example. + diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..a47bfd9 --- /dev/null +++ b/flake.lock @@ -0,0 +1,61 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1743315132, + "narHash": "sha256-6hl6L/tRnwubHcA4pfUUtk542wn2Om+D4UnDhlDW9BE=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "52faf482a3889b7619003c0daec593a1912fddc1", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..4cc5a94 --- /dev/null +++ b/flake.nix @@ -0,0 +1,42 @@ +{ + description = "C Template"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = + { + nixpkgs + , flake-utils + , ... + }: + flake-utils.lib.eachDefaultSystem (system: + let + pkgs = nixpkgs.legacyPackages.${system}; + pname = "hello-world"; #package name + version = "0.0.1"; + src = ./.; + buildInputs = with pkgs; [ + ]; + nativeBuildInputs = with pkgs; [ + cmake + ninja + pkg-config + clang-tools + ctags + ]; + in + { + devShells.default = pkgs.mkShell.override { stdenv = pkgs.clangStdenv; } { + inherit buildInputs nativeBuildInputs; + }; + + packages.default = pkgs.stdenv.mkDerivation { + inherit buildInputs nativeBuildInputs pname version src; + }; + + formatter = pkgs.alejandra; + }); +} diff --git a/json.c b/json.c new file mode 100644 index 0000000..a98052f --- /dev/null +++ b/json.c @@ -0,0 +1,193 @@ +#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) +{ +} + +void begin_value(Json* json); +void end_value(Json* json); +void push_indentation(Json* json); +void pop_indentation(Json* json); +void indent(Json* json); + +void json_begin_array(Json* json) +{ + begin_value(json); + putc('[', json->stream); + push_indentation(json); + json->next_punctuation = NONE; +} + +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: + UNREACHABLE(); + } + putc(']', json->stream); + end_value(json); +} + +void json_begin_object(Json* json) +{ + begin_value(json); + putc('{', json->stream); + push_indentation(json); + json->next_punctuation = NONE; +} + +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: + UNREACHABLE(); + } + putc('}', json->stream); + end_value(json); +} + +void json_add_object_field(Json* json, const char* key) +{ + begin_value(json); + fprintf(json->stream, "\"%s\"", key); + json->next_punctuation = COLON; +} + +void json_add_string(Json* json, const char* value) +{ + begin_value(json); + fprintf(json->stream, "\"%s\"", value); + end_value(json); +} + +void json_add_long(Json* json, long value) +{ + begin_value(json); + fprintf(json->stream, "%ld", value); + end_value(json); +} + +void json_add_double(Json* json, double value) +{ + begin_value(json); + fprintf(json->stream, "%f", value); + end_value(json); +} + +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); +} + +void json_add_null(Json* json) +{ + begin_value(json); + fputs("null", json->stream); + end_value(json); +} + +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; + } +} + +void end_value(Json* json) +{ + json->next_punctuation = COMMA; +} + +void push_indentation(Json* json) +{ + json->indent_level += 1; +} + +void pop_indentation(Json* json) +{ + json->indent_level -= 1; +} + +void indent(Json* json) +{ + char indent_char = ' '; + int n; + switch (json->whitespace) { + case MINIFIED: + 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); + } +} + diff --git a/json.h b/json.h new file mode 100644 index 0000000..aa3a316 --- /dev/null +++ b/json.h @@ -0,0 +1,45 @@ +#ifndef JSON_H +#define JSON_H + +#include + +typedef enum Whitespace { + MINIFIED, + INDENT_1, + INDENT_2, + INDENT_3, + INDENT_4, + INDENT_8, + INDENT_TAB, +} Whitespace; + +typedef enum Punctuation { + BEGINNING, + NONE, + COMMA, + COLON, +} Punctuation; + +typedef struct Json { + FILE* stream; + Whitespace whitespace; + int indent_level; + Punctuation next_punctuation; +} Json; + +Json json_init(FILE* stream, Whitespace whitespace); +void json_free(Json* json); + +void json_begin_array(Json* json); +void json_end_array(Json* json); +void json_begin_object(Json* json); +void json_end_object(Json* json); +void json_add_object_field(Json* json, const char* key); +void json_add_string(Json* json, const char* value); +void json_add_long(Json* json, long value); +void json_add_double(Json* json, double value); +void json_add_bool(Json* json, bool value); +void json_add_null(Json* json); + +#endif // JSON_H + diff --git a/main.c b/main.c new file mode 100644 index 0000000..3a8a6f6 --- /dev/null +++ b/main.c @@ -0,0 +1,50 @@ +#include + +#include "json.h" + +typedef struct Info { + int id; + const char* description; + bool enabled; +} Info; + +#define INFO_LEN 2 +static const Info infos[INFO_LEN] = { + (Info){ + .id = 0, + .description = "test", + .enabled = true, + }, + (Info){ + .id = 1, + .description = "Hello, World!", + .enabled = false, + }, +}; + +int main() +{ + auto json = json_init(stdout, INDENT_2); + + json_begin_array(&json); + for (int i = 0; i < INFO_LEN; i++) { + auto info = infos[i]; + + json_begin_object(&json); + { + json_add_object_field(&json, "id"); + json_add_long(&json, info.id); + + json_add_object_field(&json, "description"); + json_add_string(&json, info.description); + + json_add_object_field(&json, "enabled"); + json_add_bool(&json, info.enabled); + } + json_end_object(&json); + } + json_end_array(&json); + + json_free(&json); +} +