aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/all.h65
-rw-r--r--src/bfc.c30
-rw-r--r--src/codegen.c148
-rw-r--r--src/ivec.c119
-rw-r--r--src/ivec.h18
-rw-r--r--src/lexer.c72
-rw-r--r--src/node_vec.c114
-rw-r--r--src/node_vec.h26
-rw-r--r--src/parser.c152
-rw-r--r--src/str.c119
-rw-r--r--src/str.h22
11 files changed, 885 insertions, 0 deletions
diff --git a/src/all.h b/src/all.h
new file mode 100644
index 0000000..c5a983c
--- /dev/null
+++ b/src/all.h
@@ -0,0 +1,65 @@
+#pragma once
+#include "ivec.h"
+#include "node_vec.h"
+#include "str.h"
+#include <stdbool.h>
+#include <stddef.h>
+
+// Lexer
+
+typedef enum {
+ LEX_PINC,
+ LEX_PDEC,
+ LEX_VINC,
+ LEX_VDEC,
+ LEX_OUT,
+ LEX_IN,
+ LEX_LB,
+ LEX_RB,
+ LEX_EOF
+} lex_tok_e;
+
+typedef struct {
+ str *src;
+ size_t pos;
+ ivec tokens;
+} lexer_t;
+
+lexer_t lex_init(str *src);
+void lex_run(lexer_t *lx);
+void lex_free(lexer_t *lx);
+
+// Parser/AST
+
+typedef struct ast_node ast_node_t;
+
+typedef enum {
+ AST_INC,
+ AST_DEC,
+ AST_PTR_INC,
+ AST_PTR_DEC,
+ AST_OUT,
+ AST_IN,
+ AST_LOOP
+} ast_type_e;
+
+struct ast_node {
+ ast_type_e type;
+ int val;
+
+ node_vec children; // vec<ast_node_t*>
+ ast_node_t *next;
+};
+
+typedef struct {
+ lexer_t *lx;
+ size_t pos;
+} parser_t;
+
+parser_t parser_init(lexer_t *lx);
+ast_node_t *parse_program(parser_t *p);
+void ast_free(ast_node_t *node);
+
+// codegen
+
+str gen_code(ast_node_t *ast);
diff --git a/src/bfc.c b/src/bfc.c
new file mode 100644
index 0000000..2e369cf
--- /dev/null
+++ b/src/bfc.c
@@ -0,0 +1,30 @@
+#include "all.h"
+#include "str.h"
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+int main(void) {
+ str input = {0};
+ str_append_fread(&input, stdin);
+
+ lexer_t lexer = lex_init(&input);
+ lex_run(&lexer);
+
+ parser_t p = parser_init(&lexer);
+
+ ast_node_t *ast = parse_program(&p);
+
+ // lex_free(&lexer);
+ // parser_free(&p);
+
+ str c_code = gen_code(ast);
+
+ ast_free(ast);
+
+ printf("%s", c_code.data);
+
+ free_str(&input);
+ return 0;
+}
diff --git a/src/codegen.c b/src/codegen.c
new file mode 100644
index 0000000..e4972db
--- /dev/null
+++ b/src/codegen.c
@@ -0,0 +1,148 @@
+#include "all.h"
+#include "str.h"
+
+static void emit_includes(str *out) {
+ str_append(out, "#include <stdio.h>\n");
+ str_append(out, "#include <stdlib.h>\n");
+ str_append(out, "#include <string.h>\n\n");
+}
+
+static void emit_runtime(str *out) {
+ str_append(
+ out,
+ "typedef struct {\n"
+ " unsigned char *data;\n"
+ " size_t size;\n"
+ "} tape_t;\n\n"
+
+ "static void tape_init(tape_t *t, size_t initial) {\n"
+ " t->data = calloc(initial, 1);\n"
+ " t->size = initial;\n"
+ "}\n\n"
+
+ "static void tape_grow_right(tape_t *t, size_t needed) {\n"
+ " size_t new_size = t->size;\n"
+ " while (new_size <= needed) new_size *= 2;\n"
+ " unsigned char *new_data = calloc(new_size, 1);\n"
+ " memcpy(new_data, t->data, t->size);\n"
+ " free(t->data);\n"
+ " t->data = new_data;\n"
+ " t->size = new_size;\n"
+ "}\n\n"
+
+ "static void tape_grow_left(tape_t *t, size_t add, size_t *ptr) {\n"
+ " size_t new_size = t->size + add;\n"
+ " unsigned char *new_data = calloc(new_size, 1);\n"
+ " memcpy(new_data + add, t->data, t->size);\n"
+ " free(t->data);\n"
+ " t->data = new_data;\n"
+ " t->size = new_size;\n"
+ " *ptr += add;\n"
+ "}\n\n"
+
+ "static void tape_move_right(tape_t *t, size_t *ptr, size_t amount) {\n"
+ " *ptr += amount;\n"
+ " if (*ptr >= t->size)\n"
+ " tape_grow_right(t, *ptr);\n"
+ "}\n\n"
+
+ "static void tape_move_left(tape_t *t, size_t *ptr, size_t amount) {\n"
+ " if (amount > *ptr) {\n"
+ " size_t need = amount - *ptr;\n"
+ " size_t add = t->size;\n"
+ " while (add <= need)\n"
+ " add *= 2;\n"
+ " tape_grow_left(t, add, ptr);\n"
+ " }\n"
+ " *ptr -= amount;\n"
+ "}\n\n");
+}
+
+static void emit_prelude(str *out) {
+ str_append(out, "int main(void)\n"
+ "{\n"
+ " tape_t tape;\n"
+ " tape_init(&tape, 32);\n"
+ " size_t ptr = tape.size / 2;\n\n");
+}
+
+static void emit_epilogue(str *out) {
+ str_append(out, " free(tape.data);\n"
+ " return 0;\n"
+ "}\n");
+}
+
+static void emit_indent(str *out, int indent) {
+ for (int i = 0; i < indent; i++)
+ str_append(out, " ");
+}
+
+void emit_node(str *out, ast_node_t *node, int indent) {
+ for (; node; node = node->next) {
+ switch (node->type) {
+ case AST_INC:
+ emit_indent(out, indent);
+ str_append(out, "tape.data[ptr] += ");
+ str_appendf(out, "%d;\n", node->val);
+ break;
+
+ case AST_DEC:
+ emit_indent(out, indent);
+ str_append(out, "tape.data[ptr] -= ");
+ str_appendf(out, "%d;\n", node->val);
+ break;
+
+ case AST_PTR_INC:
+ emit_indent(out, indent);
+ str_append(out, "tape_move_right(&tape, &ptr, ");
+ str_appendf(out, "%d", node->val);
+ str_append(out, ");\n");
+ break;
+
+ case AST_PTR_DEC:
+ emit_indent(out, indent);
+ str_append(out, "tape_move_left(&tape, &ptr, ");
+ str_appendf(out, "%d", node->val);
+ str_append(out, ");\n");
+ break;
+
+ case AST_OUT:
+ emit_indent(out, indent);
+ str_append(out, "putchar((int)tape.data[ptr]);\n");
+ break;
+
+ case AST_IN:
+ emit_indent(out, indent);
+ str_append(out,
+ "{ int ch = getchar();\n"
+ " tape.data[ptr] = (ch == EOF) ? 0 : (unsigned char)ch; }\n");
+ break;
+
+ case AST_LOOP:
+ emit_indent(out, indent);
+ str_append(out, "while (tape.data[ptr]) {\n");
+
+ for (int i = 0; i < node->children.len; i++) {
+ emit_node(out, node->children._data[i], indent + 1);
+ }
+
+ emit_indent(out, indent);
+ str_append(out, "}\n");
+ break;
+ }
+ }
+}
+
+str gen_code(ast_node_t *ast) {
+ str out = {0};
+
+ emit_includes(&out);
+ emit_runtime(&out);
+ emit_prelude(&out);
+
+ emit_node(&out, ast, 1);
+
+ emit_epilogue(&out);
+
+ return out;
+}
diff --git a/src/ivec.c b/src/ivec.c
new file mode 100644
index 0000000..9c0dd95
--- /dev/null
+++ b/src/ivec.c
@@ -0,0 +1,119 @@
+#include "ivec.h"
+#include <stdio.h>
+#include <stdlib.h>
+
+void iv_free(ivec *v) {
+ free(v->_data);
+ v->_data = NULL;
+ v->_cap = 0;
+ v->len = 0;
+}
+
+static void _iv_ensure_init(ivec *v) {
+ if (v->_data)
+ return;
+
+ v->_cap = 16;
+ v->len = 0;
+
+ v->_data = malloc(v->_cap * sizeof(long long));
+ if (!v->_data) {
+ perror("_v_ensure_init.malloc");
+ abort();
+ }
+}
+
+static void _iv_ensure_capacity(ivec *v) {
+ _iv_ensure_init(v);
+
+ if (v->len >= v->_cap) {
+ v->_cap = (v->_cap == 0) ? 16 : v->_cap * 2;
+
+ long long *tmp = realloc(v->_data, v->_cap * sizeof(long long));
+ if (!tmp) {
+ perror("_v_ensure_capacity.realloc");
+ abort();
+ }
+
+ v->_data = tmp;
+ }
+}
+
+size_t iv_push(ivec *v, long long data) {
+ _iv_ensure_capacity(v);
+
+ v->_data[v->len++] = data;
+ return v->len - 1;
+}
+
+// getter
+long long iv_get(ivec *v, size_t idx) {
+ _iv_ensure_init(v);
+
+ if (idx < v->len) {
+ return v->_data[idx];
+ }
+
+ return 0;
+}
+
+// O(n) shift delete
+long long iv_rem_shift(ivec *v, size_t idx) {
+ _iv_ensure_init(v);
+
+ if (idx >= v->len)
+ return 0;
+
+ int old = v->_data[idx];
+
+ for (size_t j = idx; j + 1 < v->len; j++) {
+ v->_data[j] = v->_data[j + 1];
+ }
+
+ v->len--;
+ return old;
+}
+
+// O(1) swap delete
+long long iv_rem(ivec *v, size_t idx) {
+ _iv_ensure_init(v);
+
+ if (idx >= v->len)
+ return 0;
+
+ int old = v->_data[idx];
+ v->_data[idx] = v->_data[--v->len];
+ return old;
+}
+
+// search
+long long iv_has(ivec *v, long long x,
+ int (*comp)(const long long, const long long)) {
+ _iv_ensure_init(v);
+
+ if (!comp)
+ return 0;
+
+ for (size_t i = 0; i < v->len; i++) {
+ if (comp(v->_data[i], x) == 0) {
+ return v->_data[i];
+ }
+ }
+
+ return 0;
+}
+
+void iv_shrink(ivec *v) {
+ _iv_ensure_init(v);
+
+ size_t new_cap = v->len ? v->len : 1;
+
+ long long *tmp = realloc(v->_data, new_cap * sizeof(int)); // FIX
+ if (!tmp) {
+ perror("v_shrink.realloc");
+ abort();
+ }
+
+ v->_cap = new_cap;
+ v->_data = tmp;
+}
diff --git a/src/ivec.h b/src/ivec.h
new file mode 100644
index 0000000..8526171
--- /dev/null
+++ b/src/ivec.h
@@ -0,0 +1,18 @@
+#pragma once
+
+#include <stddef.h>
+
+typedef struct {
+ long long *_data;
+ size_t _cap;
+ size_t len;
+} ivec;
+
+void iv_free(ivec *v);
+size_t iv_push(ivec *v, long long data);
+long long iv_get(ivec *v, size_t idx);
+long long iv_rem_shift(ivec *v, size_t idx);
+long long iv_rem(ivec *v, size_t idx);
+long long iv_has(ivec *v, long long x,
+ int (*comp)(const long long, const long long));
+void iv_shrink(ivec *v);
diff --git a/src/lexer.c b/src/lexer.c
new file mode 100644
index 0000000..4f1fcd0
--- /dev/null
+++ b/src/lexer.c
@@ -0,0 +1,72 @@
+#include "all.h"
+#include "ivec.h"
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+lexer_t lex_init(str *src) {
+ lexer_t lx = {0};
+
+ lx.src = src;
+ lx.pos = 0;
+ lx.tokens = (ivec){0};
+
+ return lx;
+}
+
+void lex_run(lexer_t *lx) {
+ for (size_t i = 0; i < lx->src->length; i++) {
+
+ char c = lx->src->data[i];
+ lex_tok_e tok;
+
+ switch (c) {
+ case '+':
+ tok = LEX_PINC;
+ break;
+ case '-':
+ tok = LEX_PDEC;
+ break;
+ case '>':
+ tok = LEX_VINC;
+ break;
+ case '<':
+ tok = LEX_VDEC;
+ break;
+ case '.':
+ tok = LEX_OUT;
+ break;
+ case ',':
+ tok = LEX_IN;
+ break;
+ case '[':
+ tok = LEX_LB;
+ break;
+ case ']':
+ tok = LEX_RB;
+ break;
+ default:
+ continue;
+ }
+
+ iv_push(&lx->tokens, tok);
+ }
+
+ iv_push(&lx->tokens, LEX_EOF);
+}
+
+void lex_free(lexer_t *lx) {
+ if (!lx)
+ return;
+
+ iv_free(&lx->tokens);
+
+ if (lx->src) {
+ free(lx->src->data);
+ free(lx->src);
+ }
+
+ lx->src = NULL;
+ lx->pos = 0;
+}
diff --git a/src/node_vec.c b/src/node_vec.c
new file mode 100644
index 0000000..f327585
--- /dev/null
+++ b/src/node_vec.c
@@ -0,0 +1,114 @@
+#include "node_vec.h"
+#include "all.h"
+#include <stdio.h>
+#include <stdlib.h>
+
+void node_vec_free(node_vec *v) {
+ free(v->_data);
+ v->_data = NULL;
+ v->_cap = 0;
+ v->len = 0;
+}
+
+static void _node_vec_init(node_vec *v) {
+ if (v->_data)
+ return;
+
+ v->_cap = 16;
+ v->len = 0;
+
+ v->_data = malloc(v->_cap * sizeof(ast_node_t *));
+ if (!v->_data) {
+ perror("_node_vec_init.malloc");
+ abort();
+ }
+}
+
+static void _node_vec_ensure(node_vec *v) {
+ _node_vec_init(v);
+
+ if (v->len >= v->_cap) {
+ v->_cap = (v->_cap == 0) ? 16 : v->_cap * 2;
+
+ ast_node_t **tmp = realloc(v->_data, v->_cap * sizeof(ast_node_t *));
+ if (!tmp) {
+ perror("_node_vec_ensure.realloc");
+ abort();
+ }
+
+ v->_data = tmp;
+ }
+}
+
+size_t node_vec_push(node_vec *v, ast_node_t *data) {
+ _node_vec_ensure(v);
+
+ v->_data[v->len++] = data;
+ return v->len - 1;
+}
+
+ast_node_t *node_vec_get(node_vec *v, size_t idx) {
+ _node_vec_init(v);
+
+ if (idx < v->len)
+ return v->_data[idx];
+
+ return NULL;
+}
+
+ast_node_t *node_vec_rem_shift(node_vec *v, size_t idx) {
+ _node_vec_init(v);
+
+ if (idx >= v->len)
+ return NULL;
+
+ ast_node_t *old = v->_data[idx];
+
+ for (size_t j = idx; j + 1 < v->len; j++) {
+ v->_data[j] = v->_data[j + 1];
+ }
+
+ v->len--;
+ return old;
+}
+
+ast_node_t *node_vec_rem(node_vec *v, size_t idx) {
+ _node_vec_init(v);
+
+ if (idx >= v->len)
+ return NULL;
+
+ ast_node_t *old = v->_data[idx];
+ v->_data[idx] = v->_data[--v->len];
+ return old;
+}
+
+ast_node_t *node_vec_has(node_vec *v, ast_node_t *x,
+ int (*comp)(const ast_node_t *, const ast_node_t *)) {
+ _node_vec_init(v);
+
+ if (!comp)
+ return NULL;
+
+ for (size_t i = 0; i < v->len; i++) {
+ if (comp(v->_data[i], x) == 0)
+ return v->_data[i];
+ }
+
+ return NULL;
+}
+
+void node_vec_shrink(node_vec *v) {
+ _node_vec_init(v);
+
+ size_t new_cap = v->len ? v->len : 1;
+
+ ast_node_t **tmp = realloc(v->_data, new_cap * sizeof(ast_node_t *));
+ if (!tmp) {
+ perror("node_vec_shrink.realloc");
+ abort();
+ }
+
+ v->_cap = new_cap;
+ v->_data = tmp;
+}
diff --git a/src/node_vec.h b/src/node_vec.h
new file mode 100644
index 0000000..8421d32
--- /dev/null
+++ b/src/node_vec.h
@@ -0,0 +1,26 @@
+#pragma once
+
+#include <stddef.h>
+
+typedef struct ast_node ast_node_t;
+
+typedef struct {
+ ast_node_t **_data;
+ size_t _cap;
+ size_t len;
+} node_vec;
+
+void node_vec_free(node_vec *v);
+
+size_t node_vec_push(node_vec *v, ast_node_t *data);
+
+ast_node_t *node_vec_get(node_vec *v, size_t idx);
+
+ast_node_t *node_vec_rem_shift(node_vec *v, size_t idx);
+
+ast_node_t *node_vec_rem(node_vec *v, size_t idx);
+
+ast_node_t *node_vec_has(node_vec *v, ast_node_t *x,
+ int (*comp)(const ast_node_t *, const ast_node_t *));
+
+void node_vec_shrink(node_vec *v);
diff --git a/src/parser.c b/src/parser.c
new file mode 100644
index 0000000..855b94e
--- /dev/null
+++ b/src/parser.c
@@ -0,0 +1,152 @@
+#include "all.h"
+#include "ivec.h"
+#include "node_vec.h"
+#include <stdint.h>
+#include <stdlib.h>
+
+static inline lex_tok_e peek(parser_t *p) {
+ return iv_get(&p->lx->tokens, p->pos);
+}
+
+static inline lex_tok_e advance(parser_t *p) {
+ return iv_get(&p->lx->tokens, p->pos++);
+}
+
+static ast_node_t *new_node(ast_type_e type, int val) {
+ ast_node_t *n = malloc(sizeof(ast_node_t));
+ n->type = type;
+ n->val = val;
+ n->next = NULL;
+ n->children = (node_vec){0};
+ return n;
+}
+
+static ast_node_t *parse_block(parser_t *p);
+
+static ast_node_t *parse_single(parser_t *p) {
+ lex_tok_e t = peek(p);
+
+ switch (t) {
+
+ case LEX_PINC: {
+ int count = 0;
+ while (peek(p) == LEX_PINC) {
+ advance(p);
+ count++;
+ }
+ return new_node(AST_INC, count);
+ }
+
+ case LEX_PDEC: {
+ int count = 0;
+ while (peek(p) == LEX_PDEC) {
+ advance(p);
+ count++;
+ }
+ return new_node(AST_DEC, count);
+ }
+
+ case LEX_VINC: {
+ int count = 0;
+ while (peek(p) == LEX_VINC) {
+ advance(p);
+ count++;
+ }
+ return new_node(AST_PTR_INC, count);
+ }
+
+ case LEX_VDEC: {
+ int count = 0;
+ while (peek(p) == LEX_VDEC) {
+ advance(p);
+ count++;
+ }
+ return new_node(AST_PTR_DEC, count);
+ }
+
+ case LEX_OUT:
+ advance(p);
+ return new_node(AST_OUT, 1);
+
+ case LEX_IN:
+ advance(p);
+ return new_node(AST_IN, 1);
+
+ case LEX_LB:
+ advance(p); // consume '['
+ return parse_block(p);
+
+ case LEX_RB:
+ return NULL;
+
+ case LEX_EOF:
+ return NULL;
+
+ default:
+ advance(p);
+ return NULL;
+ }
+}
+
+static ast_node_t *parse_block(parser_t *p) {
+ ast_node_t *loop = new_node(AST_LOOP, 1);
+
+ while (1) {
+ lex_tok_e t = peek(p);
+
+ if (t == LEX_EOF) {
+ break;
+ }
+
+ if (t == LEX_RB) {
+ advance(p); // consume ']'
+ break;
+ }
+
+ ast_node_t *child = parse_single(p);
+ if (child) {
+ node_vec_push(&loop->children, child);
+ }
+ }
+
+ return loop;
+}
+
+parser_t parser_init(lexer_t *lx) { return (parser_t){.lx = lx, .pos = 0}; }
+
+ast_node_t *parse_program(parser_t *p) {
+ ast_node_t *head = NULL;
+ ast_node_t *tail = NULL;
+
+ while (peek(p) != LEX_EOF) {
+
+ ast_node_t *node = parse_single(p);
+ if (!node)
+ continue;
+
+ if (!head) {
+ head = tail = node;
+ } else {
+ tail->next = node;
+ tail = node;
+ }
+ }
+
+ return head;
+}
+
+void ast_free(ast_node_t *node) {
+ if (!node)
+ return;
+
+ if (node->next)
+ ast_free(node->next);
+
+ for (size_t i = 0; i < node->children.len; i++) {
+ ast_free(node->children._data[i]);
+ }
+
+ node_vec_free(&node->children);
+
+ free(node);
+}
diff --git a/src/str.c b/src/str.c
new file mode 100644
index 0000000..ddca742
--- /dev/null
+++ b/src/str.c
@@ -0,0 +1,119 @@
+#include "str.h"
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define STARTING_SIZE 256
+#define READ_CHUNK_SIZE 1024
+
+static void str_ensure_init(str *s) {
+ if (s->data)
+ return;
+
+ s->capacity = STARTING_SIZE;
+ s->length = 0;
+ s->data = malloc(s->capacity);
+ if (!s->data)
+ abort();
+
+ s->data[0] = '\0';
+}
+
+static void str_ensure_capacity(str *s, size_t extra) {
+ str_ensure_init(s);
+
+ size_t needed = s->length + extra + 1; // +1 for '\0'
+ if (needed <= s->capacity)
+ return;
+
+ while (s->capacity < needed)
+ s->capacity *= 2;
+
+ char *new_data = realloc(s->data, s->capacity);
+ if (!new_data)
+ abort();
+
+ s->data = new_data;
+}
+
+void free_str(str *s) {
+ free(s->data);
+ s->data = NULL;
+ s->length = 0;
+ s->capacity = 0;
+}
+
+void str_append(str *s, const char *from) {
+ str_append_len(s, from, strlen(from));
+}
+
+void str_append_c(str *s, char c) { str_append_len(s, &c, 1); }
+
+void str_append_len(str *s, const char *from, size_t len) {
+ str_ensure_capacity(s, len);
+
+ memcpy(s->data + s->length, from, len);
+ s->length += len;
+ s->data[s->length] = '\0';
+}
+
+void str_append_read(str *s, int fd) {
+ char buf[READ_CHUNK_SIZE];
+ ssize_t n;
+
+ while ((n = read(fd, buf, sizeof(buf))) > 0) {
+ str_append_len(s, buf, (size_t)n);
+ }
+}
+
+void str_append_fread(str *s, FILE *file) {
+ char buf[READ_CHUNK_SIZE];
+ size_t n;
+
+ while ((n = fread(buf, 1, sizeof(buf), file)) > 0) {
+ str_append_len(s, buf, n);
+ }
+}
+
+void str_append_fread_len(str *s, size_t size, FILE *file) {
+ char buf[READ_CHUNK_SIZE];
+
+ while (size > 0) {
+ size_t chunk = size > sizeof(buf) ? sizeof(buf) : size;
+ size_t n = fread(buf, 1, chunk, file);
+
+ if (n == 0)
+ break;
+
+ str_append_len(s, buf, n);
+ size -= n;
+
+ if (n < chunk)
+ break;
+ }
+}
+
+void str_appendf(str *s, const char *fmt, ...) {
+ va_list args;
+ va_start(args, fmt);
+
+ va_list copy;
+ va_copy(copy, args);
+ int needed = vsnprintf(NULL, 0, fmt, copy);
+ va_end(copy);
+
+ if (needed < 0) {
+ va_end(args);
+ return;
+ }
+
+ str_ensure_capacity(s, (size_t)needed);
+
+ vsnprintf(s->data + s->length, s->capacity - s->length, fmt, args);
+ s->length += (size_t)needed;
+
+ va_end(args);
+}
diff --git a/src/str.h b/src/str.h
new file mode 100644
index 0000000..416a846
--- /dev/null
+++ b/src/str.h
@@ -0,0 +1,22 @@
+#pragma once
+
+#include <stddef.h>
+#include <stdio.h>
+
+typedef struct {
+ char *data;
+ size_t length;
+ size_t capacity;
+} str;
+
+void free_str(str *s);
+
+void str_append(str *s, const char *from);
+void str_append_c(str *s, char c);
+void str_append_len(str *s, const char *from, size_t len);
+
+void str_append_read(str *s, int fd);
+void str_append_fread(str *s, FILE *file);
+void str_append_fread_len(str *s, size_t size, FILE *file);
+
+void str_appendf(str *s, const char *fmt, ...);