summaryrefslogtreecommitdiff
path: root/json
diff options
context:
space:
mode:
authorLLLL Colonq <llll@colonq>2026-07-09 23:51:55 -0400
committerLLLL Colonq <llll@colonq>2026-07-09 23:51:55 -0400
commit2bdcaf319b1d74ffbaccf08a58336f804761beab (patch)
treec21de6df74ec79b5574ad2b89fcc275d10847802 /json
Refactor into monorepo
Diffstat (limited to 'json')
-rw-r--r--json/.envrc1
-rw-r--r--json/.gitignore3
-rw-r--r--json/Makefile55
-rw-r--r--json/include/lcq/json.h63
-rw-r--r--json/packages.nix14
-rw-r--r--json/src/json.c289
-rw-r--r--json/src/main.c6
7 files changed, 431 insertions, 0 deletions
diff --git a/json/.envrc b/json/.envrc
new file mode 100644
index 0000000..c4b17d7
--- /dev/null
+++ b/json/.envrc
@@ -0,0 +1 @@
+use_flake
diff --git a/json/.gitignore b/json/.gitignore
new file mode 100644
index 0000000..e4551e3
--- /dev/null
+++ b/json/.gitignore
@@ -0,0 +1,3 @@
+./json
+build_*/
+.direnv/ \ No newline at end of file
diff --git a/json/Makefile b/json/Makefile
new file mode 100644
index 0000000..906f177
--- /dev/null
+++ b/json/Makefile
@@ -0,0 +1,55 @@
+CC ?= gcc
+AR ?= ar
+override CPPFLAGS += -MMD -MP
+override CFLAGS += -std=c99 -g -Ideps/ -Isrc/ -Iinclude/ -Wall -Wextra -Wpedantic -Wconversion -Wformat-security -Wshadow -Wpointer-arith -Wstrict-prototypes -Wmissing-prototypes -Wnull-dereference -Wfloat-equal -Wundef -Wpointer-arith -Wbad-function-cast -Wlogical-op -Wmissing-braces -Wcast-align -Wstrict-overflow=5 -ftrapv
+override LDFLAGS += -g -static
+
+BUILD = build_$(CC)
+
+SRCS := src/json.c
+CHK_SOURCES ?= $(SRCS)
+OBJECTS := $(SRCS:src/%.c=$(BUILD)/%.o)
+EXE := json
+LIB := libcolonq-json.a
+
+prefix ?= /usr/local
+exec_prefix ?= $(prefix)
+bindir ?= $(exec_prefix)/bin
+includedir ?= $(prefix)/include
+libdir ?= $(exec_prefix)/lib
+
+.PHONY: all clean install check-syntax
+
+all: $(EXE) $(LIB)
+
+$(EXE): $(BUILD)/main.o $(LIB)
+ $(CC) -o $@ $^ $(LDFLAGS)
+
+$(LIB): $(OBJECTS)
+ ar rcs $@ $^
+
+$(BUILD):
+ mkdir $(BUILD)/
+
+$(BUILD)/%.o: src/%.c | $(BUILD)
+ $(CC) $(CPPFLAGS) $(CFLAGS) -o $@ -c $<
+
+clean:
+ -rm $(EXE)
+ -rm $(LIB)
+ -rm -r $(BUILD)/
+
+TAGS: $(SRCS)
+ ctags --output-format=etags $^
+
+install: $(EXE) $(LIB)
+ mkdir -p $(DESTDIR)$(bindir) $(DESTDIR)$(libdir) $(DESTDIR)$(includedir)
+ install $(EXE) $(DESTDIR)$(bindir)/$(EXE)
+ install $(LIB) $(DESTDIR)$(libdir)/$(LIB)
+ cp -r include/* $(DESTDIR)$(includedir)
+
+check-syntax: TAGS
+ gcc $(CFLAGS) -fsyntax-only $(CHK_SOURCES)
+
+-include $(BUILD)/main.d
+-include $(OBJECTS:.o=.d)
diff --git a/json/include/lcq/json.h b/json/include/lcq/json.h
new file mode 100644
index 0000000..d5bc37e
--- /dev/null
+++ b/json/include/lcq/json.h
@@ -0,0 +1,63 @@
+#ifndef LCOLONQ_JSON_H
+#define LCOLONQ_JSON_H
+
+#include <lcq/prelude.h>
+
+typedef enum {
+ JSON_TOKEN__ERROR=-1,
+ JSON_TOKEN__START=0,
+ JSON_TOKEN_NUMBER_INT,
+ JSON_TOKEN_NUMBER_FLOAT,
+ JSON_TOKEN_STRING,
+ JSON_TOKEN_LSQUARE,
+ JSON_TOKEN_RSQUARE,
+ JSON_TOKEN_LCURLY,
+ JSON_TOKEN_RCURLY,
+ JSON_TOKEN_COMMA,
+ JSON_TOKEN_COLON,
+ JSON_TOKEN__END,
+} json_token;
+
+typedef struct {
+ u8 *buf;
+ i64 size;
+ i64 start, end;
+} json_lexer;
+
+char *json_token_name(json_token t);
+void json_lexer_new(json_lexer *l, u8 *buf, i64 size);
+json_token json_lex(json_lexer *l);
+
+typedef enum {
+ JSON_VALUE_TYPE__ERROR=-1,
+ JSON_VALUE_TYPE_NUMBER=1,
+ JSON_VALUE_TYPE_STRING,
+ JSON_VALUE_TYPE_ARRAY,
+ JSON_VALUE_TYPE_OBJECT,
+} json_value_type;
+
+typedef struct {
+ i64 idx;
+} json_value;
+
+typedef struct {
+ i64 idx;
+} json_array_iter;
+
+typedef struct {
+ json_lexer lex;
+ json_token next;
+ i64 curstart, curend;
+} json_parser;
+
+void json_parser_new(json_parser *p, u8 *buf, i64 size);
+json_value json_parse(json_parser *p);
+
+json_value_type json_value_get_type(json_parser *p, json_value v);
+char *json_value_as_string(json_parser *p, json_value v);
+i64 json_value_as_number(json_parser *p, json_value v);
+json_array_iter json_value_as_array(json_parser *p, json_value v);
+json_value json_array_iter_get(json_parser *p, json_array_iter i);
+bool json_array_iter_next(json_parser *p, json_array_iter *i);
+
+#endif
diff --git a/json/packages.nix b/json/packages.nix
new file mode 100644
index 0000000..7c3e692
--- /dev/null
+++ b/json/packages.nix
@@ -0,0 +1,14 @@
+pkgs: lcq: {
+ native = pkgs.pkgsMusl.stdenv.mkDerivation {
+ pname = "libcolonq-json";
+ version = "git";
+ src = ./.;
+ hardeningDisable = ["all"];
+ buildInputs = [
+ lcq.prelude.native
+ ];
+ installPhase = ''
+ make prefix=$out install
+ '';
+ };
+}
diff --git a/json/src/json.c b/json/src/json.c
new file mode 100644
index 0000000..cc85d51
--- /dev/null
+++ b/json/src/json.c
@@ -0,0 +1,289 @@
+#include <lcq/json.h>
+
+enum json_value_buf_type {
+ JSON_VALUE_BUF_TYPE_TERMINATOR=0x00,
+ JSON_VALUE_BUF_TYPE_NUMBER_ONEBYTE_POS=0x10,
+ JSON_VALUE_BUF_TYPE_NUMBER_MULTIBYTE_POS=0x20,
+ JSON_VALUE_BUF_TYPE_NUMBER_ONEBYTE_NEG=0x30,
+ JSON_VALUE_BUF_TYPE_NUMBER_MULTIBYTE_NEG=0x40,
+ JSON_VALUE_BUF_TYPE_STRING=0x50,
+ JSON_VALUE_BUF_TYPE_ARRAY=0x60,
+ JSON_VALUE_BUF_TYPE_OBJECT=0x70,
+ JSON_VALUE_BUF_TYPE_SEPARATOR=0x80,
+ JSON_VALUE_BUF_TYPE_SKIP=0xf0,
+};
+
+char *TOKEN_NAMES[] = {
+ [JSON_TOKEN_NUMBER_INT] = "int",
+ [JSON_TOKEN_NUMBER_FLOAT] = "float",
+ [JSON_TOKEN_STRING] = "string",
+ [JSON_TOKEN_LSQUARE] = "[",
+ [JSON_TOKEN_RSQUARE] = "]",
+ [JSON_TOKEN_LCURLY] = "{",
+ [JSON_TOKEN_RCURLY] = "}",
+ [JSON_TOKEN_COMMA] = ",",
+ [JSON_TOKEN_COLON] = ":",
+};
+char *json_token_name(json_token t) {
+ if (t <= JSON_TOKEN__START) return "error";
+ return TOKEN_NAMES[t];
+}
+
+void json_lexer_new(json_lexer *l, u8 *buf, i64 size) {
+ l->buf = buf;
+ l->size = size;
+ l->start = 0;
+ l->end = 0;
+}
+
+static bool lex_is_more_input(json_lexer *l) { return l && l->end < l->size; }
+static i64 lex_peek(json_lexer *l) { return l->buf[l->end]; }
+static i64 lex_advance(json_lexer *l) {
+ if (!lex_is_more_input(l)) return -1;
+ return l->buf[l->end++];
+}
+static bool lex_match(json_lexer *l, i64 x) {
+ if (lex_peek(l) == x) { lex_advance(l); return true; }
+ return false;
+}
+static bool lex_match_pred(json_lexer *l, bool (*f)(i64)) {
+ if (f(lex_peek(l))) { lex_advance(l); return true; }
+ return false;
+}
+
+static bool is_digit(i64 x) { return x >= '0' && x <= '9'; }
+static void consume_digits(json_lexer *l) { while (lex_match_pred(l, is_digit)); }
+static json_token consume_float(json_lexer *l) {
+ json_token ret = JSON_TOKEN_NUMBER_INT;
+ consume_digits(l);
+ if (lex_match(l, '.')) { consume_digits(l); ret = JSON_TOKEN_NUMBER_FLOAT; }
+ if (lex_match(l, 'e') || lex_match(l, 'E')) {
+ (void) (lex_match(l, '+') || lex_match(l, '-'));
+ consume_digits(l);
+ ret = JSON_TOKEN_NUMBER_FLOAT;
+ }
+ return ret;
+}
+
+json_token json_lex(json_lexer *l) {
+start:
+ l->start = l->end;
+ i64 cur = lex_advance(l);
+ switch (cur) {
+ case '[': return JSON_TOKEN_LSQUARE;
+ case ']': return JSON_TOKEN_RSQUARE;
+ case '{': return JSON_TOKEN_LCURLY;
+ case '}': return JSON_TOKEN_RCURLY;
+ case ',': return JSON_TOKEN_COMMA;
+ case ':': return JSON_TOKEN_COLON;
+ case '"': {
+ bool escaped = false;
+ while ((cur = lex_advance(l))) {
+ if (escaped) {
+ escaped = false;
+ } else {
+ if (cur == '"') break;
+ if (cur == '\\') escaped = true;
+ }
+ }
+ if (cur < -1) return JSON_TOKEN__ERROR;
+ return JSON_TOKEN_STRING;
+ }
+ case ' ': case '\t': case '\n': case '\r':
+ l->buf[l->start] = JSON_VALUE_BUF_TYPE_SKIP;
+ goto start;
+ case '-':
+ if (lex_match_pred(l, is_digit)) { return consume_float(l); }
+ return JSON_TOKEN__ERROR;
+ default:
+ if (is_digit(cur)) { return consume_float(l); }
+ return JSON_TOKEN__ERROR;
+ }
+}
+
+void json_parser_new(json_parser *p, u8 *buf, i64 size) {
+ json_lexer_new(&p->lex, buf, size);
+ p->curstart = 0;
+ p->curend = 0;
+ p->next = json_lex(&p->lex);
+}
+
+static json_token parse_advance(json_parser *p) {
+ if (p->next <= JSON_TOKEN__START) return p->next;
+ json_token ret = p->next;
+ p->curstart = p->lex.start;
+ p->curend = p->lex.end;
+ p->next = json_lex(&p->lex);
+ return ret;
+}
+static json_token parse_peek(json_parser *p) { return p->next; }
+
+static json_value val(i64 idx) { json_value ret; ret.idx = idx; return ret; }
+
+static json_value install_string(json_parser *p, i64 start, i64 end) {
+ p->lex.buf[start] = JSON_VALUE_BUF_TYPE_STRING;
+ p->lex.buf[end - 1] = JSON_VALUE_BUF_TYPE_TERMINATOR;
+ return val(start);
+}
+static json_value install_number_int(json_parser *p, i64 start, i64 end) {
+ i64 x = 0;
+ bool pos = true;
+ i64 i = start;
+ if (p->lex.buf[i] == '-') { pos = false; i += 1; }
+ for (; i < end; ++i) {
+ x *= 10;
+ x += p->lex.buf[i] - '0';
+ }
+ if (x < 16) {
+ p->lex.buf[start] =
+ (pos
+ ? JSON_VALUE_BUF_TYPE_NUMBER_ONEBYTE_POS
+ : JSON_VALUE_BUF_TYPE_NUMBER_ONEBYTE_NEG)
+ | (u8) x;
+ } else {
+ i64 off = 1;
+ for (; x > 0; off += 1, x >>= 8) {
+ p->lex.buf[start + off] = (u8) x & 0xff;
+ }
+ p->lex.buf[start] =
+ (pos
+ ? JSON_VALUE_BUF_TYPE_NUMBER_MULTIBYTE_POS
+ : JSON_VALUE_BUF_TYPE_NUMBER_MULTIBYTE_NEG)
+ | (u8) (off - 1);
+ }
+ return val(start);
+}
+static json_value install_array(json_parser *p, i64 start, i64 end) {
+ p->lex.buf[start] = JSON_VALUE_BUF_TYPE_ARRAY;
+ p->lex.buf[end - 1] = JSON_VALUE_BUF_TYPE_TERMINATOR;
+ return val(start);
+}
+
+json_value_type json_value_get_type(json_parser *p, json_value v) {
+ if (v.idx < 0 || v.idx >= p->lex.size) return JSON_VALUE_TYPE__ERROR;
+ enum json_value_buf_type bt = p->lex.buf[v.idx] & 0xf0;
+ switch (bt) {
+ case JSON_VALUE_BUF_TYPE_NUMBER_ONEBYTE_POS:
+ case JSON_VALUE_BUF_TYPE_NUMBER_MULTIBYTE_POS:
+ case JSON_VALUE_BUF_TYPE_NUMBER_ONEBYTE_NEG:
+ case JSON_VALUE_BUF_TYPE_NUMBER_MULTIBYTE_NEG:
+ return JSON_VALUE_TYPE_NUMBER;
+ case JSON_VALUE_BUF_TYPE_STRING: return JSON_VALUE_TYPE_STRING;
+ case JSON_VALUE_BUF_TYPE_ARRAY: return JSON_VALUE_TYPE_ARRAY;
+ case JSON_VALUE_BUF_TYPE_OBJECT: return JSON_VALUE_TYPE_OBJECT;
+ default: return JSON_VALUE_TYPE__ERROR;
+ }
+}
+char *json_value_as_string(json_parser *p, json_value v) {
+ if (json_value_get_type(p, v) != JSON_VALUE_TYPE_STRING) return NULL;
+ return (char *) &p->lex.buf[v.idx + 1];
+}
+i64 json_value_as_number(json_parser *p, json_value v) {
+ enum json_value_buf_type bt = p->lex.buf[v.idx] & 0xf0;
+ bool pos = true;
+ bool multibyte = true;
+ switch (bt) {
+ case JSON_VALUE_BUF_TYPE_NUMBER_ONEBYTE_POS:
+ multibyte = false;
+ // fallthrough
+ case JSON_VALUE_BUF_TYPE_NUMBER_MULTIBYTE_POS:
+ break;
+ case JSON_VALUE_BUF_TYPE_NUMBER_ONEBYTE_NEG:
+ multibyte = false;
+ // fallthrough
+ case JSON_VALUE_BUF_TYPE_NUMBER_MULTIBYTE_NEG:
+ pos = false;
+ break;
+ default: return 0;
+ }
+ if (multibyte) {
+ i64 x = 0;
+ i64 bs = p->lex.buf[v.idx] & 0x0f;
+ for (i64 off = 0; off < bs; ++off) {
+ x |= ((i64) p->lex.buf[v.idx + 1 + off]) << (off * 8);
+ }
+ return (i64) (pos ? 1 : -1) * x;
+ } else {
+ i64 x = p->lex.buf[v.idx] & 0x0f;
+ return (i64) (pos ? 1 : -1) * x;
+ }
+}
+json_array_iter json_value_as_array(json_parser *p, json_value v) {
+ json_array_iter ret;
+ if (json_value_get_type(p, v) != JSON_VALUE_TYPE_ARRAY) { ret.idx = -1; return ret; }
+ ret.idx = v.idx + 1;
+ return ret;
+}
+json_value json_array_iter_get(json_parser *p, json_array_iter i) {
+ if (i.idx < 0 || i.idx >= p->lex.size) return val(-1);
+ return val(i.idx);
+}
+bool json_array_iter_next(json_parser *p, json_array_iter *i) {
+ i64 nesting = 0;
+ bool in_string = false;
+ while (i->idx < p->lex.size) {
+ u8 b = p->lex.buf[i->idx];
+ enum json_value_buf_type bt = b & 0xf0;
+ i->idx += 1;
+ if (in_string) {
+ if (bt == JSON_VALUE_BUF_TYPE_TERMINATOR) in_string = false;
+ } else {
+ switch (bt) {
+ case JSON_VALUE_BUF_TYPE_STRING: in_string = true; break;
+ case JSON_VALUE_BUF_TYPE_NUMBER_MULTIBYTE_POS:
+ case JSON_VALUE_BUF_TYPE_NUMBER_MULTIBYTE_NEG:
+ i->idx += b & 0x0f;
+ break;
+ case JSON_VALUE_BUF_TYPE_ARRAY:
+ case JSON_VALUE_BUF_TYPE_OBJECT:
+ nesting += 1;
+ break;
+ case JSON_VALUE_BUF_TYPE_TERMINATOR:
+ if (nesting > 0) nesting -= 1;
+ else return false;
+ break;
+ case JSON_VALUE_BUF_TYPE_SEPARATOR:
+ if (nesting == 0) {
+ while ((p->lex.buf[i->idx] & 0xf0) == JSON_VALUE_BUF_TYPE_SKIP) i->idx++;
+ return true;
+ }
+ break;
+ default: break;
+ }
+ }
+ }
+ return false;
+}
+
+json_value json_parse(json_parser *p) {
+ json_token cur = parse_advance(p);
+ switch (cur) {
+ case JSON_TOKEN_STRING: return install_string(p, p->curstart, p->curend);
+ case JSON_TOKEN_NUMBER_INT: return install_number_int(p, p->curstart, p->curend);
+ case JSON_TOKEN_LSQUARE: {
+ i64 start = p->curstart;
+ while (true) {
+ json_parse(p);
+ cur = parse_advance(p);
+ if (cur == JSON_TOKEN_RSQUARE) break;
+ else if (cur == JSON_TOKEN_COMMA) p->lex.buf[p->curstart] = JSON_VALUE_BUF_TYPE_SEPARATOR;
+ else goto error;
+ }
+ return install_array(p, start, p->curend);
+ }
+ case JSON_TOKEN_LCURLY: {
+ i64 start = p->curstart;
+ while (true) {
+ json_parse(p);
+ cur = parse_advance(p);
+ if (cur == JSON_TOKEN_RCURLY) break;
+ else if (cur == JSON_TOKEN_COMMA || cur == JSON_TOKEN_COLON) p->lex.buf[p->curstart] = JSON_VALUE_BUF_TYPE_SEPARATOR;
+ else goto error;
+ }
+ return install_array(p, start, p->curend);
+ }
+ default: goto error;
+ }
+error:
+ return val(-1);
+}
diff --git a/json/src/main.c b/json/src/main.c
new file mode 100644
index 0000000..a7bf8c4
--- /dev/null
+++ b/json/src/main.c
@@ -0,0 +1,6 @@
+#include <stdio.h>
+
+int main(void) {
+ puts("hello computer");
+ return 0;
+}