├── chrono ├── README.md ├── Makefile ├── tests.c ├── chrono.h └── chrono.c ├── vm ├── Makefile ├── tests.c ├── vm.h ├── vm.c └── README.md ├── .github └── FUNDING.yml ├── dsl ├── Makefile ├── benchmarks.c ├── tests.c ├── dsl.h ├── README.md └── dsl.c ├── fix ├── Makefile ├── benchmarks.c ├── fix.h ├── tests.c ├── fix.c └── README.md ├── set ├── Makefile ├── tests.c ├── set.h ├── set.c └── README.md ├── task ├── Makefile ├── task.c ├── task.h ├── tests.c └── README.md ├── list ├── Makefile ├── tests.c ├── list.h ├── list.c └── README.md ├── slog ├── Makefile ├── tests.c ├── slog.h ├── slog.c └── README.md ├── dynamic ├── Makefile ├── tests.c ├── dynamic.h ├── dynamic.c └── README.md ├── error ├── Makefile ├── tests.c ├── error.h ├── error.c └── README.md ├── macro ├── Makefile ├── macro.c ├── tests.c ├── macro.h └── README.md ├── vector ├── Makefile ├── tests.c ├── vector.h ├── vector.c └── README.md ├── malloc1 ├── Makefile ├── tests.c ├── malloc1.h ├── malloc1.c └── README.md ├── malloc2 ├── Makefile ├── malloc2.h ├── benchmarks.c ├── tests.c ├── malloc2.c └── README.md ├── reflect ├── Makefile ├── tests.c ├── reflect.h ├── reflect.c └── README.md ├── stream1 ├── Makefile ├── tests.c ├── stream1.h ├── stream1.c └── README.md ├── .gitignore ├── benchmarks.c ├── epub ├── metadata.yml ├── README.md ├── book_data.json ├── styles.css └── build_ebook.py ├── tests.c ├── Makefile └── README.md /chrono/README.md: -------------------------------------------------------------------------------- 1 | ## 2 | -------------------------------------------------------------------------------- /vm/Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS+=-c -I.. 2 | 3 | ../build/vm.o: vm.h vm.c 4 | $(CC) $(CFLAGS) vm.c -o ../build/vm.o 5 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: codr7 4 | liberapay: andreas7 5 | -------------------------------------------------------------------------------- /dsl/Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS+=-c -I.. 2 | 3 | ../build/dsl.o: dsl.h dsl.c 4 | $(CC) $(CFLAGS) dsl.c -o ../build/dsl.o 5 | -------------------------------------------------------------------------------- /fix/Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS+=-c -I.. 2 | 3 | ../build/fix.o: fix.h fix.c 4 | $(CC) $(CFLAGS) fix.c -o ../build/fix.o 5 | -------------------------------------------------------------------------------- /set/Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS+=-c -I.. 2 | 3 | ../build/set.o: set.h set.c 4 | $(CC) $(CFLAGS) set.c -o ../build/set.o 5 | -------------------------------------------------------------------------------- /task/Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS+=-c -I.. 2 | 3 | ../build/task.o: task.c 4 | $(CC) $(CFLAGS) task.c -o ../build/task.o 5 | -------------------------------------------------------------------------------- /list/Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS+=-c 2 | 3 | ../build/list.o: list.h list.c 4 | $(CC) $(CFLAGS) list.c -o ../build/list.o 5 | -------------------------------------------------------------------------------- /slog/Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS+=-c -I.. 2 | 3 | ../build/slog.o: slog.h slog.c 4 | $(CC) $(CFLAGS) slog.c -o ../build/slog.o 5 | -------------------------------------------------------------------------------- /dynamic/Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS+=-c -I.. 2 | 3 | ../build/dynamic.o: dynamic.c 4 | $(CC) $(CFLAGS) dynamic.c -o ../build/dynamic.o 5 | -------------------------------------------------------------------------------- /error/Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS+=-c -I.. 2 | 3 | ../build/error.o: error.h error.c 4 | $(CC) $(CFLAGS) error.c -o ../build/error.o 5 | -------------------------------------------------------------------------------- /macro/Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS+=-c -I.. 2 | 3 | ../build/macro.o: macro.h macro.c 4 | $(CC) $(CFLAGS) macro.c -o ../build/macro.o 5 | -------------------------------------------------------------------------------- /chrono/Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS+=-c -I.. 2 | 3 | ../build/chrono.o: chrono.h chrono.c 4 | $(CC) $(CFLAGS) chrono.c -o ../build/chrono.o 5 | -------------------------------------------------------------------------------- /vector/Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS+=-c -I.. 2 | 3 | ../build/vector.o: vector.h vector.c 4 | $(CC) $(CFLAGS) vector.c -o ../build/vector.o 5 | -------------------------------------------------------------------------------- /malloc1/Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS+=-c -I.. 2 | 3 | ../build/malloc1.o: malloc1.h malloc1.c 4 | $(CC) $(CFLAGS) malloc1.c -o ../build/malloc1.o 5 | -------------------------------------------------------------------------------- /malloc2/Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS+=-c -I.. 2 | 3 | ../build/malloc2.o: malloc2.h malloc2.c 4 | $(CC) $(CFLAGS) malloc2.c -o ../build/malloc2.o 5 | -------------------------------------------------------------------------------- /reflect/Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS+=-c -I.. 2 | 3 | ../build/reflect.o: reflect.h reflect.c 4 | $(CC) $(CFLAGS) reflect.c -o ../build/reflect.o 5 | -------------------------------------------------------------------------------- /stream1/Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS+=-c -I.. 2 | 3 | ../build/stream1.o: stream1.h stream1.c 4 | $(CC) $(CFLAGS) stream1.c -o ../build/stream1.o 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | 3 | # ignore epub and .md files generated within sub-directories of the epub/ folder 4 | epub/*/**/*.epub 5 | epub/*/**/*.md -------------------------------------------------------------------------------- /chrono/tests.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "chrono.h" 3 | 4 | void chrono_tests() { 5 | hc_time_t t = hc_now(); 6 | const int ns = 1000; 7 | assert(hc_sleep(ns) == 0); 8 | assert(hc_time_ns(&t) >= ns); 9 | } 10 | -------------------------------------------------------------------------------- /macro/macro.c: -------------------------------------------------------------------------------- 1 | #include "macro.h" 2 | 3 | size_t hc_alignof(size_t size) { 4 | const size_t max = _Alignof(max_align_t); 5 | if (size >= max) { return max; } 6 | size_t v = 1; 7 | for (size_t nv = 1; nv <= size; v = nv, nv = v << 1); 8 | return v; 9 | } 10 | -------------------------------------------------------------------------------- /benchmarks.c: -------------------------------------------------------------------------------- 1 | #include "error/error.h" 2 | 3 | #include "dsl/benchmarks.c" 4 | #include "fix/benchmarks.c" 5 | #include "malloc2/benchmarks.c" 6 | 7 | int main() { 8 | fix_benchmarks(); 9 | malloc2_benchmarks(); 10 | dsl_benchmarks(); 11 | 12 | hc_errors_deinit(); 13 | return 0; 14 | } 15 | -------------------------------------------------------------------------------- /error/tests.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "error.h" 7 | 8 | void error_tests() { 9 | void on_catch(struct hc_error *e) { 10 | assert(hc_streq("E123", e->message) == 0); 11 | } 12 | 13 | hc_catch(on_catch) { 14 | hc_throw("E123 Going %s", "Down!"); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /stream1/tests.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "stream1.h" 4 | 5 | void stream1_tests() { 6 | struct hc_memory_stream s; 7 | hc_memory_stream_init(&s, &hc_malloc_default); 8 | hc_defer(hc_stream_deinit(&s.stream)); 9 | hc_printf(&s.stream, "%s%d", "foo", 42); 10 | assert(strcmp("foo42", hc_memory_stream_string(&s)) == 0); 11 | } 12 | -------------------------------------------------------------------------------- /reflect/tests.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "reflect.h" 3 | 4 | void reflect_tests() { 5 | struct hc_value v; 6 | hc_value_init(&v, &HC_STRING)->as_string = strdup("foo"); 7 | hc_defer(hc_value_deinit(&v)); 8 | struct hc_value c; 9 | hc_value_copy(&c, &v); 10 | hc_defer(hc_value_deinit(&c)); 11 | assert(strcmp(c.as_string, v.as_string) == 0); 12 | } 13 | -------------------------------------------------------------------------------- /epub/metadata.yml: -------------------------------------------------------------------------------- 1 | --- 2 | title: 3 | - type: main 4 | text: Hacktical C 5 | creator: 6 | - role: author 7 | text: Andreas Codr7 8 | identifier: 9 | # randomly generated URN; arbitrary but should be kept consistent so updates to 10 | # the book are recognized as the same book. 11 | - scheme: URN 12 | text: urn:uuid:4eb54e25-66bf-4f42-9ee3-16fd2e7e1de5 13 | rights: CC BY-NC-ND 4.0 14 | ... -------------------------------------------------------------------------------- /macro/tests.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "macro.h" 3 | 4 | void macro_tests() { 5 | int hc_id(foo, bar) = 42; 6 | assert(foobar == 42); 7 | 8 | assert(hc_min(7, 42) == 7); 9 | assert(hc_max(7.0, 42.0) == 42.0); 10 | 11 | { 12 | int foo = 0; 13 | 14 | { 15 | hc_defer(assert(foo++ == 1)); 16 | hc_defer(assert(foo++ == 0)); 17 | } 18 | 19 | assert(foo == 2); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /dynamic/tests.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "dynamic.h" 3 | 4 | void dynamic_tests() { 5 | const char *out = "/var/tmp/libtest.so"; 6 | 7 | hc_compile("#include \n" 8 | "int test() { return 42; }", 9 | out, 10 | .cflags = (const char *[]){"-Wall", 11 | "-fsanitize=undefined", 12 | NULL}); 13 | 14 | struct hc_dlib lib; 15 | hc_dlib_init(&lib, out); 16 | hc_defer(hc_dlib_deinit(&lib)); 17 | int (*fn)() = hc_dlib_find(&lib, "test"); 18 | assert(fn() == 42); 19 | } 20 | -------------------------------------------------------------------------------- /vm/tests.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "vm.h" 4 | #include "malloc1/malloc1.h" 5 | 6 | static void emit_tests() { 7 | struct hc_vm vm; 8 | hc_vm_init(&vm, &hc_malloc_default); 9 | hc_defer(hc_vm_deinit(&vm)); 10 | struct hc_push_op op; 11 | hc_value_init(&op.value, &HC_FIX)->as_fix = hc_fix(0, 42); 12 | hc_vm_emit(&vm, &HC_PUSH, &op); 13 | hc_vm_eval(&vm, 0, -1); 14 | assert(vm.stack.length == 1); 15 | assert(hc_vm_pop(&vm)->as_fix == op.value.as_fix); 16 | } 17 | 18 | void vm_tests() { 19 | emit_tests(); 20 | } 21 | -------------------------------------------------------------------------------- /fix/benchmarks.c: -------------------------------------------------------------------------------- 1 | #include "chrono/chrono.h" 2 | #include "fix.h" 3 | 4 | void fix_benchmarks() { 5 | hc_time_t t; 6 | const int n = 100000; 7 | 8 | t = hc_now(); 9 | double dv = 0; 10 | 11 | for (int i = 0; i < n; i++) { 12 | dv += 0.001; 13 | } 14 | 15 | hc_time_print(&t, "double: "); 16 | 17 | t = hc_now(); 18 | hc_fix_t fv = hc_fix(3, 0); 19 | hc_fix_t fd = hc_fix(3, 1); 20 | 21 | for (int i = 0; i < n; i++) { 22 | fv = hc_fix_add(fv, fd); 23 | } 24 | 25 | hc_time_print(&t, "fix: "); 26 | } 27 | -------------------------------------------------------------------------------- /list/tests.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "list.h" 3 | 4 | struct list_item { 5 | struct hc_list ls; 6 | int value; 7 | }; 8 | 9 | void list_tests() { 10 | struct hc_list head; 11 | hc_list_init(&head); 12 | 13 | const int n = 10; 14 | struct list_item items[n]; 15 | 16 | for (int i = 0; i < n; i++) { 17 | items[i].value = i; 18 | hc_list_push_back(&head, &items[i].ls); 19 | } 20 | 21 | int i = 0; 22 | 23 | hc_list_do(&head, il) { 24 | assert(hc_baseof(il, struct list_item, ls)->value == i++); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /epub/README.md: -------------------------------------------------------------------------------- 1 | # Hacktical C - epub 2 | 3 | This directory contains utilities for generating an ebook version of this book in EPUB format. 4 | 5 | It collects chapter contents from each chapter's README and supplemental code source files. 6 | 7 | ## Dependencies 8 | 9 | * python 10 | - minimum: v3.9 11 | - recommended: v3.13 12 | * [pandoc](https://pandoc.org) 13 | 14 | ## Building 15 | 16 | `python ./build_ebook.py` 17 | 18 | The build utilities are designed to be runnable with no arguments and default options, but also support a number of overrides for experimentation and other non-default uses. 19 | 20 | Use `python ./build_ebook.py -h` to get a list of these additional configuration options and overrides. 21 | -------------------------------------------------------------------------------- /fix/fix.h: -------------------------------------------------------------------------------- 1 | #ifndef HACKTICAL_FIX_H 2 | #define HACKTICAL_FIX_H 3 | 4 | #include 5 | 6 | #define HC_FIX_EXP 3 7 | #define HC_FIX_HDR (HC_FIX_EXP+1) 8 | 9 | #define HC_FIX_MAX_EXP 7 10 | 11 | struct hc_stream; 12 | 13 | typedef uint64_t hc_fix_t; 14 | 15 | uint32_t hc_scale(uint8_t exp); 16 | hc_fix_t hc_fix(uint8_t exp, int64_t val); 17 | 18 | uint8_t hc_fix_exp(hc_fix_t x); 19 | int64_t hc_fix_val(hc_fix_t x); 20 | double hc_fix_double(hc_fix_t x); 21 | 22 | hc_fix_t hc_fix_add(hc_fix_t x, hc_fix_t y); 23 | hc_fix_t hc_fix_sub(hc_fix_t x, hc_fix_t y); 24 | hc_fix_t hc_fix_mul(hc_fix_t x, hc_fix_t y); 25 | hc_fix_t hc_fix_div(hc_fix_t x, hc_fix_t y); 26 | 27 | int64_t hc_fix_int(hc_fix_t x); 28 | int64_t hc_fix_frac(hc_fix_t x); 29 | 30 | void hc_fix_print(const hc_fix_t v, struct hc_stream *out); 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /slog/tests.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "slog.h" 5 | #include "malloc1/malloc1.h" 6 | 7 | void slog_tests() { 8 | struct hc_memory_stream out; 9 | hc_memory_stream_init(&out, &hc_malloc_default); 10 | 11 | struct hc_slog_stream s; 12 | hc_slog_stream_init(&s, &out.stream, .close_out=true); 13 | 14 | hc_slog_do(&s) { 15 | hc_slog_context_do(hc_slog_string("string", "abc")) { 16 | hc_time_t t = hc_time(2025, 4, 13, 1, 40, 0); 17 | 18 | hc_slog_write(hc_slog_bool("bool", true), 19 | hc_slog_int("int", 42), 20 | hc_slog_time("time", t)); 21 | } 22 | } 23 | 24 | assert(strcmp("string=\"abc\", " 25 | "bool=true, " 26 | "int=42, " 27 | "time=2025-04-13T01:40:00\n", 28 | hc_memory_stream_string(&out)) == 0); 29 | 30 | hc_slog_deinit(&s); 31 | } 32 | -------------------------------------------------------------------------------- /chrono/chrono.h: -------------------------------------------------------------------------------- 1 | #ifndef HACKTICAL_CHRONO_H 2 | #define HACKTICAL_CHRONO_H 3 | 4 | #include 5 | #include 6 | 7 | #define HC_TIME_FORMAT "%Y-%m-%dT%H:%M:%S" 8 | 9 | struct hc_stream; 10 | 11 | struct hc_time { 12 | struct timespec value; 13 | }; 14 | 15 | typedef struct hc_time hc_time_t; 16 | 17 | hc_time_t hc_now(); 18 | 19 | hc_time_t hc_time(int year, 20 | int month, 21 | int day, 22 | int hour, 23 | int minute, 24 | int second); 25 | 26 | uint64_t hc_time_ns(const hc_time_t *t); 27 | void hc_time_print(const hc_time_t *t, const char *m); 28 | char *hc_time_sprintf(const hc_time_t *t, const char *spec); 29 | 30 | void hc_time_printf(const hc_time_t *t, 31 | const char *spec, 32 | struct hc_stream *out); 33 | 34 | uint64_t hc_sleep(uint64_t ns); 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /task/task.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "task.h" 4 | 5 | struct hc_task *hc_task_init(struct hc_task *t, 6 | struct hc_task_list *tl, 7 | hc_task_body body) { 8 | t->body = body; 9 | t->state = 0; 10 | t->done = false; 11 | hc_list_push_back(&tl->tasks, &t->list); 12 | return t; 13 | } 14 | 15 | struct hc_task_list *hc_task_list_init(struct hc_task_list *tl) { 16 | hc_list_init(&tl->tasks); 17 | return tl; 18 | } 19 | 20 | void hc_task_list_run(const struct hc_task_list *tl) { 21 | bool all_done = false; 22 | 23 | while (!all_done) { 24 | all_done = true; 25 | 26 | hc_list_do(&tl->tasks, i) { 27 | struct hc_task *t = hc_baseof(i, struct hc_task, list); 28 | 29 | if (!t->done) { 30 | t->body(t); 31 | all_done = false; 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /task/task.h: -------------------------------------------------------------------------------- 1 | #ifndef HACKTICAL_TASK_H 2 | #define HACKTICAL_TASK_H 3 | 4 | #include 5 | #include "list/list.h" 6 | 7 | #define hc_task_yield(task) \ 8 | do { \ 9 | task->state = __LINE__; \ 10 | return; \ 11 | case __LINE__:; \ 12 | } while (0) 13 | 14 | struct hc_task; 15 | 16 | typedef void (*hc_task_body)(struct hc_task *); 17 | 18 | struct hc_task { 19 | struct hc_list list; 20 | hc_task_body body; 21 | int state; 22 | bool done; 23 | }; 24 | 25 | struct hc_task_list { 26 | struct hc_list tasks; 27 | }; 28 | 29 | struct hc_task *hc_task_init(struct hc_task *t, 30 | struct hc_task_list *tl, 31 | hc_task_body body); 32 | 33 | struct hc_task_list *hc_task_list_init(struct hc_task_list *tl); 34 | void hc_task_list_run(const struct hc_task_list *tl); 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /dsl/benchmarks.c: -------------------------------------------------------------------------------- 1 | #include "chrono/chrono.h" 2 | #include "dsl.h" 3 | 4 | void dsl_benchmarks() { 5 | hc_time_t t; 6 | const int n = 100000; 7 | 8 | char buf[32]; 9 | t = hc_now(); 10 | 11 | for (int i = 0; i < n; i++) { 12 | sprintf(buf, "abc %s def", "ghi"); 13 | } 14 | 15 | hc_time_print(&t, "sprintf: "); 16 | 17 | struct hc_dsl dsl; 18 | hc_dsl_init(&dsl, &hc_malloc_default); 19 | hc_defer(hc_dsl_deinit(&dsl)); 20 | struct hc_memory_stream out; 21 | hc_memory_stream_init(&out, &hc_malloc_default); 22 | hc_defer(hc_stream_deinit(&out.stream)); 23 | dsl.out = &out.stream; 24 | hc_dsl_set_string(&dsl, "foo", "ghi"); 25 | hc_dsl_eval(&dsl, "abc $(print foo) def"); 26 | 27 | t = hc_now(); 28 | 29 | for (int i = 0; i < n; i++) { 30 | hc_vector_clear(&out.data); 31 | hc_vm_eval(&dsl.vm, 0, -1); 32 | } 33 | 34 | hc_time_print(&t, "dsl: "); 35 | } 36 | -------------------------------------------------------------------------------- /tests.c: -------------------------------------------------------------------------------- 1 | #include "error/error.h" 2 | 3 | #include "chrono/tests.c" 4 | #include "dsl/tests.c" 5 | #include "dynamic/tests.c" 6 | #include "error/tests.c" 7 | #include "fix/tests.c" 8 | #include "list/tests.c" 9 | #include "macro/tests.c" 10 | #include "malloc1/tests.c" 11 | #include "malloc2/tests.c" 12 | #include "reflect/tests.c" 13 | #include "set/tests.c" 14 | #include "slog/tests.c" 15 | #include "stream1/tests.c" 16 | #include "task/tests.c" 17 | #include "vector/tests.c" 18 | #include "vm/tests.c" 19 | 20 | int main() { 21 | chrono_tests(); 22 | dsl_tests(); 23 | dynamic_tests(); 24 | error_tests(); 25 | fix_tests(); 26 | list_tests(); 27 | macro_tests(); 28 | malloc1_tests(); 29 | malloc2_tests(); 30 | reflect_tests(); 31 | set_tests(); 32 | slog_tests(); 33 | stream1_tests(); 34 | task_tests(); 35 | vector_tests(); 36 | vm_tests(); 37 | 38 | hc_errors_deinit(); 39 | return 0; 40 | } 41 | -------------------------------------------------------------------------------- /malloc2/malloc2.h: -------------------------------------------------------------------------------- 1 | #ifndef HACKTICAL_MALLOC2_H 2 | #define HACKTICAL_MALLOC2_H 3 | 4 | #include 5 | #include 6 | 7 | #include "list/list.h" 8 | #include "malloc1/malloc1.h" 9 | #include "set/set.h" 10 | 11 | /* Memo */ 12 | 13 | struct hc_memo_alloc { 14 | struct hc_malloc malloc; 15 | struct hc_malloc *source; 16 | struct hc_set memo; 17 | }; 18 | 19 | struct hc_memo_alloc *hc_memo_alloc_init(struct hc_memo_alloc *a, 20 | struct hc_malloc *source); 21 | 22 | void hc_memo_alloc_deinit(struct hc_memo_alloc *a); 23 | 24 | /* Slab */ 25 | 26 | struct hc_slab_alloc { 27 | struct hc_malloc malloc; 28 | struct hc_malloc *source; 29 | struct hc_list slabs; 30 | size_t slab_size; 31 | }; 32 | 33 | struct hc_slab_alloc *hc_slab_alloc_init(struct hc_slab_alloc *a, 34 | struct hc_malloc *source, 35 | size_t slab_size); 36 | 37 | void hc_slab_alloc_deinit(struct hc_slab_alloc *a); 38 | 39 | #endif 40 | -------------------------------------------------------------------------------- /malloc1/tests.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "malloc1.h" 3 | 4 | void malloc1_tests() { 5 | assert(hc_align(0, 4) == 0); 6 | assert(hc_align(1, 4) == 4); 7 | assert(hc_align(3, 4) == 4); 8 | assert(hc_align(4, 4) == 4); 9 | assert(hc_align(5, 4) == 8); 10 | 11 | const int s = 1024; 12 | struct hc_bump_alloc a; 13 | hc_bump_alloc_init(&a, &hc_malloc_default, s); 14 | hc_defer(hc_bump_alloc_deinit(&a)); 15 | assert(a.size == s); 16 | assert(a.offset == 0); 17 | 18 | int *ip = hc_acquire(&a.malloc, sizeof(int)); 19 | *ip = 42; 20 | 21 | long *lp = hc_acquire(&a.malloc, sizeof(long)); 22 | *lp = 42L; 23 | 24 | assert(a.offset >= sizeof(int) + sizeof(long)); 25 | bool caught = false; 26 | 27 | void on_catch(struct hc_error *e) { 28 | assert(hc_streq(e->message, HC_NO_MEMORY) == 0); 29 | caught = true; 30 | } 31 | 32 | hc_catch(on_catch) { 33 | hc_acquire(&a.malloc, s); 34 | assert(false); 35 | } 36 | 37 | assert(caught); 38 | } 39 | -------------------------------------------------------------------------------- /set/tests.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "set.h" 4 | 5 | struct map_item { 6 | int k, v; 7 | }; 8 | 9 | static enum hc_order cmp(const void *x, const void *y) { 10 | return hc_cmp(*(const int *)x, *(const int *)y); 11 | } 12 | 13 | static const void *key(const void *x) { 14 | return &((const struct map_item *)x)->k; 15 | } 16 | 17 | void set_tests() { 18 | int n = 10; 19 | struct hc_set s; 20 | hc_set_init(&s, &hc_malloc_default, sizeof(struct map_item), cmp); 21 | s.key = key; 22 | 23 | for (int i = 0; i < n; i++) { 24 | struct map_item *it = hc_set_add(&s, &i, false); 25 | *it = (struct map_item){.k = i, .v = i}; 26 | } 27 | 28 | assert(hc_set_length(&s) == n); 29 | 30 | for (int i = 0; i < n; i++) { 31 | struct map_item *it = hc_set_find(&s, &i); 32 | assert(it); 33 | assert(it->k == i); 34 | assert(it->v == i); 35 | } 36 | 37 | hc_set_clear(&s); 38 | assert(hc_set_length(&s) == 0); 39 | hc_set_deinit(&s); 40 | } 41 | -------------------------------------------------------------------------------- /list/list.h: -------------------------------------------------------------------------------- 1 | #ifndef HACKTICAL_LIST_H 2 | #define HACKTICAL_LIST_H 3 | 4 | #include 5 | #include "../macro/macro.h" 6 | 7 | #define _hc_list_do(l, i, _list, _next) \ 8 | __auto_type _list = l; \ 9 | for (struct hc_list *i = _list->next, *_next = i->next; \ 10 | i != _list; \ 11 | i = _next, _next = i->next) 12 | 13 | #define hc_list_do(l, i) \ 14 | _hc_list_do(l, i, hc_unique(list), hc_unique(next)) 15 | 16 | struct hc_list { 17 | struct hc_list *prev, *next; 18 | }; 19 | 20 | void hc_list_init(struct hc_list *l); 21 | bool hc_list_nil(const struct hc_list *l); 22 | struct hc_list *hc_list_delete(struct hc_list *l); 23 | 24 | void hc_list_push_front(struct hc_list *l, struct hc_list *it); 25 | struct hc_list *hc_list_pop_front(struct hc_list *l); 26 | struct hc_list *hc_list_peek_front(struct hc_list *l); 27 | 28 | void hc_list_push_back(struct hc_list *l, struct hc_list *it); 29 | struct hc_list *hc_list_pop_back(struct hc_list *l); 30 | struct hc_list *hc_list_peek_back(struct hc_list *l); 31 | void hc_list_shift_back(struct hc_list *l); 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /set/set.h: -------------------------------------------------------------------------------- 1 | #ifndef HACKTICAL_SET_H 2 | #define HACKTICAL_SET_H 3 | 4 | #include 5 | #include "vector/vector.h" 6 | 7 | #define hc_cmp(x, y) ({ \ 8 | __auto_type _x = x; \ 9 | __auto_type _y = y; \ 10 | (_x < _y) ? HC_LT : ((_x > _y) ? HC_GT : HC_EQ); \ 11 | }) 12 | 13 | struct hc_malloc; 14 | 15 | enum hc_order {HC_LT = -1, HC_EQ = 0, HC_GT = 1}; 16 | 17 | typedef enum hc_order (*hc_cmp_t)(const void *, const void *); 18 | 19 | typedef const void *(*hc_set_key_t)(const void *); 20 | 21 | struct hc_set { 22 | struct hc_vector items; 23 | hc_cmp_t cmp; 24 | hc_set_key_t key; 25 | }; 26 | 27 | struct hc_set *hc_set_init(struct hc_set *s, 28 | struct hc_malloc *malloc, 29 | size_t item_size, 30 | hc_cmp_t cmp); 31 | 32 | void hc_set_deinit(struct hc_set *s); 33 | size_t hc_set_index(const struct hc_set *s, const void *key, bool *ok); 34 | size_t hc_set_length(const struct hc_set *s); 35 | void *hc_set_find(struct hc_set *s, const void *key); 36 | void *hc_set_add(struct hc_set *s, const void *key, bool force); 37 | void hc_set_clear(struct hc_set *s); 38 | 39 | #endif 40 | -------------------------------------------------------------------------------- /dynamic/dynamic.h: -------------------------------------------------------------------------------- 1 | #ifndef HACKTICAL_DYNAMIC_H 2 | #define HACKTICAL_DYNAMIC_H 3 | 4 | #include 5 | #include 6 | 7 | struct hc_proc { 8 | int pid; 9 | int in; 10 | }; 11 | 12 | #define hc_proc_init(p, ...) \ 13 | _hc_proc_init(p, {__VA_ARGS__, NULL}) 14 | 15 | struct hc_proc *_hc_proc_init(struct hc_proc *p, char *cmd[]); 16 | void hc_proc_wait(struct hc_proc *p); 17 | void hc_proc_deinit(struct hc_proc *p); 18 | 19 | struct hc_compile_opts { 20 | const char *cc; 21 | const char **cflags; 22 | }; 23 | 24 | #define hc_compile(code, out, ...) \ 25 | _hc_compile(code, out, (struct hc_compile_opts){ \ 26 | .cc = "/usr/bin/gcc", \ 27 | .cflags = (const char *[]){NULL}, \ 28 | ##__VA_ARGS__ \ 29 | }) 30 | 31 | void _hc_compile(const char *code, 32 | const char *out, 33 | struct hc_compile_opts opts); 34 | 35 | struct hc_dlib { 36 | void *handle; 37 | }; 38 | 39 | struct hc_dlib *hc_dlib_init(struct hc_dlib *lib, const char *path); 40 | struct hc_dlib *hc_dlib_deinit(struct hc_dlib *lib); 41 | void *hc_dlib_find(const struct hc_dlib *lib, const char *s); 42 | 43 | char *hc_vsprintf(const char *format, va_list args); 44 | 45 | #endif 46 | -------------------------------------------------------------------------------- /malloc1/malloc1.h: -------------------------------------------------------------------------------- 1 | #ifndef HACKTICAL_MALLOC1_H 2 | #define HACKTICAL_MALLOC1_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #define _hc_acquire(m, _m, s) ({ \ 9 | struct hc_malloc *_m = m; \ 10 | assert(_m->acquire); \ 11 | _m->acquire(_m, s); \ 12 | }) 13 | 14 | #define hc_acquire(m, s) \ 15 | _hc_acquire(m, hc_unique(malloc_m), s) 16 | 17 | #define _hc_release(m, _m, p) do { \ 18 | struct hc_malloc *_m = m; \ 19 | assert(_m->release); \ 20 | _m->release(_m, p); \ 21 | } while (0) 22 | 23 | #define hc_release(m, p) \ 24 | _hc_release(m, hc_unique(malloc_m), p) 25 | 26 | struct hc_malloc { 27 | void *(*acquire)(struct hc_malloc *, size_t); 28 | void (*release)(struct hc_malloc *, void *); 29 | }; 30 | 31 | extern struct hc_malloc hc_malloc_default; 32 | 33 | /* Bump */ 34 | 35 | struct hc_bump_alloc { 36 | struct hc_malloc malloc; 37 | struct hc_malloc *source; 38 | size_t size, offset; 39 | uint8_t *memory; 40 | }; 41 | 42 | void hc_bump_alloc_init(struct hc_bump_alloc *a, 43 | struct hc_malloc *source, 44 | size_t size); 45 | 46 | void hc_bump_alloc_deinit(struct hc_bump_alloc *a); 47 | 48 | #endif 49 | -------------------------------------------------------------------------------- /task/tests.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "task.h" 4 | 5 | struct my_task { 6 | struct hc_task task; 7 | int *value; 8 | }; 9 | 10 | static void producer(struct hc_task *task) { 11 | int *value = hc_baseof(task, struct my_task, task)->value; 12 | 13 | switch (task->state) { 14 | case 0: 15 | assert(*value == 0); 16 | (*value)++; 17 | hc_task_yield(task); 18 | assert(*value == 2); 19 | (*value)++; 20 | } 21 | 22 | task->done = true; 23 | } 24 | 25 | static void consumer(struct hc_task *task) { 26 | int *value = hc_baseof(task, struct my_task, task)->value; 27 | 28 | switch (task->state) { 29 | case 0: 30 | assert(*value == 1); 31 | (*value)++; 32 | hc_task_yield(task); 33 | assert(*value == 3); 34 | (*value)++; 35 | } 36 | 37 | task->done = true; 38 | } 39 | 40 | void task_tests() { 41 | struct hc_task_list tl; 42 | hc_task_list_init(&tl); 43 | 44 | int value = 0; 45 | 46 | struct my_task pt = {.value = &value}; 47 | hc_task_init(&pt.task, &tl, &producer); 48 | 49 | struct my_task ct = {.value = &value}; 50 | hc_task_init(&ct.task, &tl, &consumer); 51 | 52 | hc_task_list_run(&tl); 53 | assert(value == 4); 54 | } 55 | -------------------------------------------------------------------------------- /error/error.h: -------------------------------------------------------------------------------- 1 | #ifndef HACKTICAL_ERROR_H 2 | #define HACKTICAL_ERROR_H 3 | 4 | #include 5 | #include 6 | #include "macro/macro.h" 7 | 8 | #define HC_NO_MEMORY "NO_MEMORY" 9 | #define HC_INVALID_SIZE "INVALID_SIZE" 10 | 11 | #define hc_throw(m, ...) do { \ 12 | struct hc_error *_e = \ 13 | hc_error_new("Error in '%s', line %d:\n" m "\n", \ 14 | __FILE__, __LINE__, ##__VA_ARGS__); \ 15 | _hc_throw(_e); \ 16 | } while(0) 17 | 18 | #define _hc_catch(_e, _f, h) \ 19 | jmp_buf _e; \ 20 | bool _f = true; \ 21 | if (setjmp(_e)) { \ 22 | h(hc_error); \ 23 | hc_error_free(hc_error); \ 24 | } else for (hc_catch_push(_e); _f; _f = false, hc_catch_pop()) \ 25 | 26 | #define hc_catch(h) \ 27 | _hc_catch(hc_unique(env), hc_unique(flag), h) 28 | 29 | void hc_catch_push(jmp_buf h); 30 | void hc_catch_pop(); 31 | void hc_errors_deinit(); 32 | 33 | struct hc_error { 34 | char *message; 35 | }; 36 | 37 | 38 | extern __thread struct hc_error *hc_error; 39 | struct hc_error *hc_error_new(const char *message, ...); 40 | void _hc_throw(struct hc_error *e); 41 | void hc_error_free(struct hc_error *e); 42 | 43 | bool hc_streq(const char *l, const char *r); 44 | 45 | #endif 46 | -------------------------------------------------------------------------------- /vector/tests.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "vector.h" 4 | 5 | void vector_tests() { 6 | struct hc_vector v; 7 | hc_vector_init(&v, &hc_malloc_default, sizeof(int)); 8 | const int n = 100; 9 | 10 | for (int i = 0; i < n; i++) { 11 | *(int *)hc_vector_push(&v) = i; 12 | } 13 | 14 | { 15 | int i = 0; 16 | 17 | hc_vector_do(&v, it) { 18 | assert(*(int *)it == i++); 19 | } 20 | } 21 | 22 | assert(v.length == n); 23 | 24 | for (int i = 0; i < n; i++) { 25 | assert(*(int *)hc_vector_get(&v, i) == i); 26 | } 27 | 28 | assert(*(int *)hc_vector_pop(&v) == n-1); 29 | assert(*(int *)hc_vector_peek(&v) == n-2); 30 | assert(v.length == n-1); 31 | 32 | for (int i = 0; i < n-1; i++) { 33 | assert(*(int *)hc_vector_get(&v, i) == i); 34 | } 35 | 36 | assert(hc_vector_delete(&v, 0, 1)); 37 | assert(v.length == n-2); 38 | 39 | for (int i = 1; i < n-1; i++) { 40 | assert(*(int *)hc_vector_get(&v, i-1) == i); 41 | } 42 | 43 | (*(int *)hc_vector_insert(&v, 0, 1) = 0); 44 | assert(v.length == n-1); 45 | 46 | for (int i = 0; i < n-1; i++) { 47 | assert(*(int *)hc_vector_get(&v, i) == i); 48 | } 49 | 50 | hc_vector_clear(&v); 51 | assert(v.length == 0); 52 | 53 | hc_vector_deinit(&v); 54 | } 55 | -------------------------------------------------------------------------------- /reflect/reflect.h: -------------------------------------------------------------------------------- 1 | #ifndef HACKTICAL_REFLECT_H 2 | #define HACKTICAL_REFLECT_H 3 | 4 | #include 5 | 6 | #include "chrono/chrono.h" 7 | #include "fix/fix.h" 8 | 9 | struct hc_value; 10 | 11 | struct hc_type { 12 | const char *name; 13 | 14 | void (*copy)(struct hc_value *dst, struct hc_value *src); 15 | void (*deinit)(struct hc_value *); 16 | void (*print)(const struct hc_value *, struct hc_stream *out); 17 | void (*write)(const struct hc_value *, struct hc_stream *out); 18 | }; 19 | 20 | struct hc_value { 21 | const struct hc_type *type; 22 | 23 | union { 24 | bool as_bool; 25 | hc_fix_t as_fix; 26 | int as_int; 27 | void *as_other; 28 | char *as_string; 29 | hc_time_t as_time; 30 | }; 31 | }; 32 | 33 | struct hc_value *hc_value_init(struct hc_value *v, const struct hc_type *t); 34 | void hc_value_deinit(struct hc_value *v); 35 | struct hc_value *hc_value_copy(struct hc_value *dst, struct hc_value *src); 36 | void hc_value_print(struct hc_value *v, struct hc_stream *out); 37 | void hc_value_write(struct hc_value *v, struct hc_stream *out); 38 | 39 | extern const struct hc_type HC_BOOL; 40 | extern const struct hc_type HC_FIX; 41 | extern const struct hc_type HC_INT; 42 | extern const struct hc_type HC_STRING; 43 | extern const struct hc_type HC_TIME; 44 | 45 | #endif 46 | -------------------------------------------------------------------------------- /vector/vector.h: -------------------------------------------------------------------------------- 1 | #ifndef HACKTICAL_VECTOR_H 2 | #define HACKTICAL_VECTOR_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include "macro/macro.h" 8 | 9 | #define _hc_vector_do(v, _v, var) \ 10 | struct hc_vector *_v = v; \ 11 | for (void *var = _v->start; \ 12 | var < (void *)_v->end; \ 13 | var += _v->item_size) 14 | 15 | #define hc_vector_do(v, var) \ 16 | _hc_vector_do(v, hc_unique(vector), var) 17 | 18 | struct hc_malloc; 19 | 20 | struct hc_vector { 21 | size_t item_size, capacity, length; 22 | uint8_t *start, *end; 23 | struct hc_malloc *malloc; 24 | }; 25 | 26 | struct hc_vector *hc_vector_init(struct hc_vector *v, 27 | struct hc_malloc *malloc, 28 | size_t item_size); 29 | 30 | void hc_vector_deinit(struct hc_vector *v); 31 | void hc_vector_grow(struct hc_vector *v, size_t capacity); 32 | void hc_vector_clear(struct hc_vector *v); 33 | void *hc_vector_get(struct hc_vector *v, size_t i); 34 | const void *hc_vector_get_const(const struct hc_vector *v, size_t i); 35 | void *hc_vector_push(struct hc_vector *v); 36 | void *hc_vector_peek(struct hc_vector *v); 37 | void *hc_vector_pop(struct hc_vector *v); 38 | void *hc_vector_insert(struct hc_vector *v, size_t i, size_t n); 39 | bool hc_vector_delete(struct hc_vector *v, size_t i, size_t n); 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /malloc2/benchmarks.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "chrono/chrono.h" 4 | #include "malloc2.h" 5 | 6 | #define N 1000 7 | #define MAX_SIZE 64 8 | #define GET_SIZE() ((rand() % MAX_SIZE) + 1) 9 | 10 | static void run_malloc() { 11 | int *ps[N]; 12 | hc_time_t t = hc_now(); 13 | 14 | for (int i = 0; i < N; i++) { 15 | ps[i] = malloc(GET_SIZE()); 16 | } 17 | 18 | for (int i = 0; i < N; i++) { 19 | free(ps[i]); 20 | } 21 | 22 | hc_time_print(&t, "malloc: "); 23 | } 24 | 25 | static void run_bump() { 26 | struct hc_bump_alloc a; 27 | hc_bump_alloc_init(&a, &hc_malloc_default, N * MAX_SIZE); 28 | hc_time_t t = hc_now(); 29 | 30 | for (int i = 0; i < N; i++) { 31 | hc_acquire(&a.malloc, GET_SIZE()); 32 | } 33 | 34 | hc_bump_alloc_deinit(&a); 35 | hc_time_print(&t, "bump: "); 36 | } 37 | 38 | static void run_slab() { 39 | struct hc_slab_alloc a; 40 | hc_slab_alloc_init(&a, &hc_malloc_default, N); 41 | hc_time_t t = hc_now(); 42 | 43 | for (int i = 0; i < N; i++) { 44 | hc_acquire(&a.malloc, GET_SIZE()); 45 | } 46 | 47 | hc_slab_alloc_deinit(&a); 48 | hc_time_print(&t, "slab: "); 49 | } 50 | 51 | void malloc2_benchmarks() { 52 | const int s = time(NULL); 53 | 54 | srand(s); 55 | run_malloc(); 56 | 57 | srand(s); 58 | run_bump(); 59 | 60 | srand(s); 61 | run_slab(); 62 | } 63 | -------------------------------------------------------------------------------- /malloc2/tests.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "malloc2.h" 3 | 4 | static void memo_tests() { 5 | struct hc_memo_alloc a; 6 | hc_memo_alloc_init(&a, &hc_malloc_default); 7 | 8 | int *ip1 = hc_acquire(&a.malloc, sizeof(int)); 9 | 10 | long *lp = hc_acquire(&a.malloc, sizeof(long)); 11 | assert((int *)lp != ip1); 12 | *lp = 42; 13 | 14 | hc_release(&a.malloc, ip1); 15 | int *ip2 = hc_acquire(&a.malloc, sizeof(int)); 16 | assert(ip2 == ip1); 17 | *ip2 = 42; 18 | 19 | int *ip3 = hc_acquire(&a.malloc, sizeof(int)); 20 | assert(ip3 != ip1); 21 | *ip3 = 42; 22 | 23 | hc_release(&a.malloc, lp); 24 | hc_release(&a.malloc, ip2); 25 | hc_release(&a.malloc, ip3); 26 | 27 | hc_memo_alloc_deinit(&a); 28 | } 29 | 30 | static void slab_tests() { 31 | struct hc_slab_alloc a; 32 | hc_slab_alloc_init(&a, &hc_malloc_default, 2 * sizeof(int)); 33 | assert(a.slab_size == 2 * sizeof(int)); 34 | 35 | const int *p1 = hc_acquire(&a.malloc, sizeof(int)); 36 | const int *p2 = hc_acquire(&a.malloc, sizeof(int)); 37 | assert(p2 == p1 + 1); 38 | 39 | const int *p3 = hc_acquire(&a.malloc, sizeof(int)); 40 | assert(p3 > p2 + 1); 41 | 42 | const int *p4 = hc_acquire(&a.malloc, 10 * sizeof(int)); 43 | assert(p4 > p3 + 1); 44 | 45 | hc_slab_alloc_deinit(&a); 46 | } 47 | 48 | void malloc2_tests() { 49 | memo_tests(); 50 | slab_tests(); 51 | } 52 | -------------------------------------------------------------------------------- /epub/book_data.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "chapter_root": "macro", 4 | "code_supplements": ["tests.c", "macro.h"] 5 | }, 6 | { 7 | "chapter_root": "fix", 8 | "code_supplements": ["benchmarks.c", "fix.h", "fix.c"] 9 | }, 10 | { 11 | "chapter_root": "list", 12 | "code_supplements": ["tests.c", "list.h", "list.c"] 13 | }, 14 | { 15 | "chapter_root": "task", 16 | "code_supplements": ["tests.c", "task.h", "task.c"] 17 | }, 18 | { 19 | "chapter_root": "malloc1", 20 | "code_supplements": ["tests.c", "malloc1.h", "malloc1.c"] 21 | }, 22 | { 23 | "chapter_root": "vector", 24 | "code_supplements": ["tests.c", "vector.h", "vector.c"] 25 | }, 26 | { 27 | "chapter_root": "error", 28 | "code_supplements": ["tests.c", "error.h", "error.c"] 29 | }, 30 | { 31 | "chapter_root": "set", 32 | "code_supplements": ["tests.c", "set.h", "set.c"] 33 | }, 34 | { 35 | "chapter_root": "malloc2", 36 | "code_supplements": ["tests.c", "malloc2.h", "malloc2.c"] 37 | }, 38 | { 39 | "chapter_root": "dynamic", 40 | "code_supplements": ["tests.c", "dynamic.h", "dynamic.c"] 41 | }, 42 | { 43 | "chapter_root": "stream1", 44 | "code_supplements": ["tests.c", "stream1.h", "stream1.c"] 45 | }, 46 | { 47 | "chapter_root": "slog", 48 | "code_supplements": ["tests.c", "slog.h", "slog.c"] 49 | } 50 | ] -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | export CC=ccache gcc 2 | export CFLAGS=-g -O2 -flto -Wall -Wno-override-init-side-effects -fsanitize=bounds,undefined -I. -lm 3 | export LDFLAGS= 4 | 5 | CHAPTERS=build/chrono.o build/dsl.o build/dynamic.o build/error.o build/fix.o build/list.o build/macro.o build/malloc1.o build/malloc2.o build/reflect.o build/set.o build/slog.o build/stream1.o build/task.o build/vector.o build/vm.o 6 | 7 | all: clean build/test build/benchmark 8 | 9 | build/test: tests.c $(CHAPTERS) 10 | $(CC) $(CFLAGS) tests.c $(CHAPTERS) -o build/test 11 | valgrind build/test 12 | 13 | build/benchmark: benchmarks.c $(CHAPTERS) 14 | $(CC) $(CFLAGS) benchmarks.c $(CHAPTERS) -o build/benchmark 15 | build/benchmark 16 | 17 | build/dsl.o: 18 | $(MAKE) -C dsl 19 | 20 | build/dynamic.o: 21 | $(MAKE) -C dynamic 22 | 23 | build/error.o: 24 | $(MAKE) -C error 25 | 26 | build/fix.o: 27 | $(MAKE) -C fix 28 | 29 | build/list.o: 30 | $(MAKE) -C list 31 | 32 | build/macro.o: 33 | $(MAKE) -C macro 34 | 35 | build/malloc1.o: 36 | $(MAKE) -C malloc1 37 | 38 | build/malloc2.o: 39 | $(MAKE) -C malloc2 40 | 41 | build/reflect.o: 42 | $(MAKE) -C reflect 43 | 44 | build/set.o: 45 | $(MAKE) -C set 46 | 47 | build/slog.o: 48 | $(MAKE) -C slog 49 | 50 | build/stream1.o: 51 | $(MAKE) -C stream1 52 | 53 | build/task.o: 54 | $(MAKE) -C task 55 | 56 | build/chrono.o: 57 | $(MAKE) -C chrono 58 | 59 | build/vector.o: 60 | $(MAKE) -C vector 61 | 62 | build/vm.o: 63 | $(MAKE) -C vm 64 | 65 | clean: 66 | rm -f build/* 67 | -------------------------------------------------------------------------------- /fix/tests.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "fix.h" 5 | 6 | static void test_add() { 7 | assert(hc_fix_add(hc_fix(2, 175), hc_fix(2, 25)) == 8 | hc_fix(2, 200)); 9 | 10 | assert(hc_fix_add(hc_fix(2, 175), hc_fix(2, -25)) == 11 | hc_fix(2, 150)); 12 | } 13 | 14 | static void test_div() { 15 | assert(hc_fix_div(hc_fix(2, 150), hc_fix(0, 2)) == 16 | hc_fix(2, 75)); 17 | 18 | assert(hc_fix_div(hc_fix(2, 150), hc_fix(0, -2)) == 19 | hc_fix(2, -75)); 20 | 21 | assert(hc_fix_div(hc_fix(2, -150), hc_fix(0, -2)) == 22 | hc_fix(2, 75)); 23 | } 24 | 25 | static void test_mul() { 26 | assert(hc_fix_mul(hc_fix(2, 150), hc_fix(1, 5)) == 27 | hc_fix(2, 75)); 28 | 29 | assert(hc_fix_mul(hc_fix(2, 150), hc_fix(1, -5)) == 30 | hc_fix(2, -75)); 31 | 32 | assert(hc_fix_mul(hc_fix(2, -150), hc_fix(1, -5)) == 33 | hc_fix(2, 75)); 34 | } 35 | 36 | static void test_new() { 37 | hc_fix_t x = hc_fix(2, -125); 38 | assert(hc_fix_exp(x) == 2); 39 | assert(hc_fix_val(x) == -125); 40 | assert(hc_fix_int(x) == -1); 41 | assert(hc_fix_frac(x) == -25); 42 | assert(hc_fix_double(x) == -1.25); 43 | } 44 | 45 | static void test_sub() { 46 | assert(hc_fix_sub(hc_fix(2, 175), hc_fix(2, 25)) == 47 | hc_fix(2, 150)); 48 | 49 | assert(hc_fix_sub(hc_fix(2, 175), hc_fix(2, -25)) == 50 | hc_fix(2, 200)); 51 | } 52 | 53 | void fix_tests() { 54 | test_add(); 55 | test_div(); 56 | test_mul(); 57 | test_new(); 58 | test_sub(); 59 | } 60 | -------------------------------------------------------------------------------- /list/list.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "list.h" 3 | 4 | void hc_list_init(struct hc_list *l) { 5 | l->prev = l->next = l; 6 | } 7 | 8 | bool hc_list_nil(const struct hc_list *l) { 9 | return l->prev == l && l->next == l; 10 | } 11 | 12 | struct hc_list *hc_list_delete(struct hc_list *l) { 13 | l->prev->next = l->next; 14 | l->next->prev = l->prev; 15 | return l; 16 | } 17 | 18 | void hc_list_push_front(struct hc_list *l, struct hc_list *it) { 19 | hc_list_push_back(l->next, it); 20 | } 21 | 22 | struct hc_list *hc_list_pop_front(struct hc_list *l) { 23 | struct hc_list *it = l->next; 24 | return (it == l) ? NULL : hc_list_delete(it); 25 | } 26 | 27 | struct hc_list *hc_list_peek_front(struct hc_list *l) { 28 | struct hc_list *it = l->next; 29 | return (it == l) ? NULL : it; 30 | } 31 | 32 | void hc_list_push_back(struct hc_list *l, struct hc_list *it) { 33 | it->prev = l->prev; 34 | l->prev->next = it; 35 | it->next = l; 36 | l->prev = it; 37 | } 38 | 39 | struct hc_list *hc_list_pop_back(struct hc_list *l) { 40 | struct hc_list *it = l->prev; 41 | return (it == l) ? NULL : hc_list_delete(it); 42 | } 43 | 44 | struct hc_list *hc_list_peek_back(struct hc_list *l) { 45 | struct hc_list *it = l->prev; 46 | return (it == l) ? NULL : it; 47 | } 48 | 49 | void hc_list_shift_back(struct hc_list *l) { 50 | l->next->prev = l->prev; 51 | l->prev->next = l->next; 52 | l->prev = l->next; 53 | l->next = l->next->next; 54 | l->prev->next = l; 55 | l->next->prev = l; 56 | } 57 | -------------------------------------------------------------------------------- /malloc1/malloc1.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "error/error.h" 4 | #include "macro/macro.h" 5 | #include "malloc1.h" 6 | 7 | static void *default_acquire(struct hc_malloc *m, size_t size) { 8 | return malloc(size); 9 | } 10 | 11 | static void default_release(struct hc_malloc *m, void *p) { 12 | free(p); 13 | } 14 | 15 | struct hc_malloc hc_malloc_default = {.acquire = default_acquire, 16 | .release = default_release}; 17 | 18 | __thread struct hc_malloc *hc_mallocp = NULL; 19 | 20 | /* Bump */ 21 | 22 | static void *bump_acquire(struct hc_malloc *a, size_t size) { 23 | if (size <= 0) { 24 | hc_throw(HC_INVALID_SIZE); 25 | } 26 | 27 | struct hc_bump_alloc *ba = hc_baseof(a, struct hc_bump_alloc, malloc); 28 | 29 | if (ba->size - ba->offset < size) { 30 | hc_throw(HC_NO_MEMORY); 31 | } 32 | 33 | uint8_t *p = ba->memory + ba->offset; 34 | uint8_t *pa = hc_align(p, size); 35 | ba->offset = ba->offset + pa - p + size; 36 | return pa; 37 | } 38 | 39 | static void bump_release(struct hc_malloc *a, void *p) { 40 | //Do nothing 41 | } 42 | 43 | void hc_bump_alloc_init(struct hc_bump_alloc *a, 44 | struct hc_malloc *source, 45 | size_t size) { 46 | a->malloc.acquire = bump_acquire; 47 | a->malloc.release = bump_release; 48 | a->source = source; 49 | a->size = size; 50 | a->offset = 0; 51 | a->memory = hc_acquire(source, size); 52 | } 53 | 54 | void hc_bump_alloc_deinit(struct hc_bump_alloc *a) { 55 | hc_release(a->source, a->memory); 56 | } 57 | -------------------------------------------------------------------------------- /dsl/tests.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "dsl.h" 4 | 5 | static void read_call_tests() { 6 | struct hc_sloc sloc = hc_sloc("read_call_tests", 0, 0); 7 | 8 | struct hc_list out; 9 | hc_list_init(&out); 10 | hc_defer(hc_forms_free(&out)); 11 | 12 | const char *s = "( foo bar )"; 13 | const char *in = s; 14 | assert(hc_read_expr(&in, &out, &sloc)); 15 | 16 | struct hc_form *f = hc_baseof(out.next, struct hc_form, owner); 17 | assert(f->type == &HC_CALL_FORM); 18 | } 19 | 20 | static void read_id_tests() { 21 | struct hc_sloc sloc = hc_sloc("read_id_tests", 0, 0); 22 | 23 | struct hc_list out; 24 | hc_list_init(&out); 25 | hc_defer(hc_forms_free(&out)); 26 | 27 | const char *s = "foo"; 28 | const char *in = s; 29 | assert(hc_read_expr(&in, &out, &sloc)); 30 | 31 | struct hc_form *f = hc_baseof(out.next, struct hc_form, owner); 32 | assert(f->type == &HC_ID_FORM); 33 | } 34 | 35 | static void eval_tests() { 36 | struct hc_dsl dsl; 37 | hc_dsl_init(&dsl, &hc_malloc_default); 38 | hc_defer(hc_dsl_deinit(&dsl)); 39 | struct hc_memory_stream out; 40 | hc_memory_stream_init(&out, &hc_malloc_default); 41 | hc_defer(hc_stream_deinit(&out.stream)); 42 | dsl.out = &out.stream; 43 | hc_dsl_set_string(&dsl, "foo", "ghi"); 44 | hc_dsl_eval(&dsl, "abc $(print (upcase foo)) def"); 45 | assert(strcmp("abc GHI def", hc_memory_stream_string(&out)) == 0); 46 | } 47 | 48 | void dsl_tests() { 49 | read_id_tests(); 50 | read_call_tests(); 51 | eval_tests(); 52 | } 53 | -------------------------------------------------------------------------------- /set/set.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "set.h" 3 | 4 | struct hc_set *hc_set_init(struct hc_set *s, 5 | struct hc_malloc *malloc, 6 | const size_t item_size, 7 | hc_cmp_t cmp) { 8 | hc_vector_init(&s->items, malloc, item_size); 9 | s->cmp = cmp; 10 | s->key = NULL; 11 | return s; 12 | } 13 | 14 | void hc_set_deinit(struct hc_set *s) { 15 | hc_vector_deinit(&s->items); 16 | } 17 | 18 | size_t hc_set_index(const struct hc_set *s, const void *key, bool *ok) { 19 | size_t min = 0, max = s->items.length; 20 | 21 | while (min < max) { 22 | const size_t i = (min+max)/2; 23 | const void *v = hc_vector_get_const(&s->items, i); 24 | const void *k = s->key ? s->key(v) : v; 25 | 26 | switch (s->cmp(key, k)) { 27 | case HC_LT: 28 | max = i; 29 | break; 30 | case HC_GT: 31 | min = i+1; 32 | break; 33 | default: 34 | if (ok) { 35 | *ok = true; 36 | } 37 | 38 | return i; 39 | } 40 | } 41 | 42 | return min; 43 | } 44 | 45 | size_t hc_set_length(const struct hc_set *s) { 46 | return s->items.length; 47 | } 48 | 49 | void *hc_set_find(struct hc_set *s, const void *key) { 50 | bool ok = false; 51 | const size_t i = hc_set_index(s, key, &ok); 52 | return ok ? hc_vector_get(&s->items, i) : NULL; 53 | } 54 | 55 | void *hc_set_add(struct hc_set *s, const void *key, const bool force) { 56 | bool ok = false; 57 | const size_t i = hc_set_index(s, key, &ok); 58 | 59 | if (ok && !force) { 60 | return NULL; 61 | } 62 | 63 | return hc_vector_insert(&s->items, i, 1); 64 | } 65 | 66 | void hc_set_clear(struct hc_set *s) { 67 | hc_vector_clear(&s->items); 68 | } 69 | -------------------------------------------------------------------------------- /vm/vm.h: -------------------------------------------------------------------------------- 1 | #ifndef HACKTICAL_VM_H 2 | #define HACKTICAL_VM_H 3 | 4 | #include "list/list.h" 5 | #include "malloc1/malloc1.h" 6 | #include "reflect/reflect.h" 7 | #include "set/set.h" 8 | #include "stream1/stream1.h" 9 | #include "vector/vector.h" 10 | 11 | struct hc_sloc { 12 | char source[32]; 13 | char out[64]; 14 | int row; 15 | int col; 16 | }; 17 | 18 | struct hc_sloc hc_sloc(const char *source, int row, int col); 19 | const char *hc_sloc_string(struct hc_sloc *sloc); 20 | 21 | struct hc_vm { 22 | struct hc_vector stack; 23 | struct hc_vector ops; 24 | struct hc_vector code; 25 | }; 26 | 27 | void hc_vm_init(struct hc_vm *vm, struct hc_malloc *malloc); 28 | void hc_vm_deinit(struct hc_vm *vm); 29 | 30 | struct hc_value *hc_vm_push(struct hc_vm *vm); 31 | struct hc_value *hc_vm_peek(struct hc_vm *vm); 32 | struct hc_value *hc_vm_pop(struct hc_vm *vm); 33 | 34 | struct hc_op; 35 | 36 | size_t hc_vm_emit(struct hc_vm *vm, 37 | const struct hc_op *op, 38 | const void *data); 39 | 40 | void hc_vm_eval(struct hc_vm *vm, size_t start_pc, size_t end_pc); 41 | 42 | extern const struct hc_type HC_VM_FUN; 43 | typedef void (*hc_vm_fun_t)(struct hc_vm *, struct hc_sloc); 44 | 45 | typedef uint8_t *(*hc_op_eval_t)(struct hc_vm *, uint8_t *); 46 | 47 | struct hc_op { 48 | const char *name; 49 | 50 | size_t align; 51 | size_t size; 52 | 53 | hc_op_eval_t eval; 54 | void (*deinit)(uint8_t *); 55 | }; 56 | 57 | struct hc_call_op { 58 | hc_vm_fun_t target; 59 | struct hc_sloc sloc; 60 | }; 61 | 62 | extern const struct hc_op HC_CALL; 63 | 64 | struct hc_push_op { 65 | struct hc_value value; 66 | }; 67 | 68 | extern const struct hc_op HC_PUSH; 69 | 70 | #endif 71 | -------------------------------------------------------------------------------- /error/error.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "error.h" 7 | #include "malloc1/malloc1.h" 8 | #include "vector/vector.h" 9 | 10 | __thread struct hc_error *hc_error = NULL; 11 | 12 | static struct hc_vector *handlers() { 13 | static bool init = true; 14 | static __thread struct hc_vector handlers; 15 | 16 | if (init) { 17 | hc_vector_init(&handlers, &hc_malloc_default, sizeof(jmp_buf)); 18 | init = false; 19 | } 20 | 21 | return &handlers; 22 | } 23 | 24 | void hc_catch_push(jmp_buf h) { 25 | memcpy((jmp_buf *)hc_vector_push(handlers()), h, sizeof(jmp_buf)); 26 | } 27 | 28 | void hc_catch_pop() { 29 | hc_vector_pop(handlers()); 30 | } 31 | 32 | void hc_errors_deinit() { 33 | hc_vector_deinit(handlers()); 34 | } 35 | 36 | void _hc_throw(struct hc_error *e) { 37 | struct hc_vector *hs = handlers(); 38 | 39 | if (!hs->length) { 40 | fputs(e->message, stderr); 41 | hc_error_free(e); 42 | abort(); 43 | } 44 | 45 | jmp_buf t; 46 | memcpy(t, *(jmp_buf *)hc_vector_pop(hs), sizeof(jmp_buf)); 47 | hc_error = e; 48 | longjmp(t, 1); 49 | } 50 | 51 | struct hc_error *hc_error_new(const char *message, ...) { 52 | va_list args; 53 | va_start(args, message); 54 | 55 | va_list tmp_args; 56 | va_copy(tmp_args, args); 57 | int len = vsnprintf(NULL, 0, message, tmp_args); 58 | va_end(tmp_args); 59 | 60 | if (len < 0) { 61 | vfprintf(stderr, message, args); 62 | abort(); 63 | } 64 | 65 | len++; 66 | struct hc_error *e = malloc(sizeof(struct hc_error)); 67 | e->message = malloc(len); 68 | vsnprintf(e->message, len, message, args); 69 | va_end(args); 70 | return e; 71 | } 72 | 73 | void hc_error_free(struct hc_error *e) { 74 | free(e->message); 75 | free(e); 76 | } 77 | 78 | bool hc_streq(const char *l, const char *r) { 79 | for (; *l && *l == *r; l++, r++); 80 | return *l == *r; 81 | } 82 | -------------------------------------------------------------------------------- /stream1/stream1.h: -------------------------------------------------------------------------------- 1 | #ifndef HACKTICAL_STREAM1_H 2 | #define HACKTICAL_STREAM1_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "vector/vector.h" 10 | 11 | struct hc_stream { 12 | size_t (*read)(struct hc_stream *, uint8_t *, size_t); 13 | size_t (*write)(struct hc_stream *, const uint8_t *, size_t); 14 | void (*deinit)(struct hc_stream *); 15 | }; 16 | 17 | size_t hc_read(struct hc_stream *s, uint8_t *data, size_t n); 18 | size_t hc_write(struct hc_stream *s, const uint8_t *data, size_t n); 19 | 20 | char *hc_gets(struct hc_stream *s, struct hc_malloc *malloc); 21 | char hc_getc(struct hc_stream *s); 22 | size_t hc_putc(struct hc_stream *s, char data); 23 | size_t hc_puts(struct hc_stream *s, const char *data); 24 | 25 | size_t hc_vprintf(struct hc_stream *s, 26 | const char *spec, 27 | va_list args); 28 | 29 | size_t hc_printf(struct hc_stream *s, const char *spec, ...); 30 | 31 | void hc_stream_deinit(struct hc_stream *s); 32 | 33 | struct hc_file_stream_opts { 34 | bool close_file; 35 | }; 36 | 37 | struct hc_file_stream { 38 | struct hc_stream stream; 39 | FILE *file; 40 | struct hc_file_stream_opts opts; 41 | }; 42 | 43 | #define hc_file_stream_init(s, f, ...) \ 44 | _hc_file_stream_init(s, f, (struct hc_file_stream_opts){ \ 45 | .close_file = false, \ 46 | ##__VA_ARGS__ \ 47 | }) 48 | 49 | extern struct hc_stream hc_file_stream; 50 | 51 | struct hc_file_stream *_hc_file_stream_init(struct hc_file_stream *s, 52 | FILE *file, 53 | struct hc_file_stream_opts opts); 54 | 55 | struct hc_stream *hc_stdout(); 56 | 57 | struct hc_memory_stream { 58 | struct hc_stream stream; 59 | struct hc_vector data; 60 | size_t rpos; 61 | }; 62 | 63 | extern struct hc_stream hc_memory_stream; 64 | 65 | struct hc_memory_stream *hc_memory_stream_init(struct hc_memory_stream *s, 66 | struct hc_malloc *malloc); 67 | 68 | const char *hc_memory_stream_string(struct hc_memory_stream *s); 69 | 70 | #endif 71 | -------------------------------------------------------------------------------- /macro/macro.h: -------------------------------------------------------------------------------- 1 | #ifndef HACKTICAL_MACRO_H 2 | #define HACKTICAL_MACRO_H 3 | 4 | #include 5 | #include 6 | 7 | #define hc_abs(x) ({ \ 8 | __auto_type _x = x; \ 9 | _x < 0 ? -x : x; \ 10 | }) \ 11 | 12 | #define hc_align(base, size) ({ \ 13 | __auto_type _base = base; \ 14 | __auto_type _size = hc_alignof(size); \ 15 | __auto_type _rest = (ptrdiff_t)_base % _size; \ 16 | (_rest) ? _base + _size - _rest : _base; \ 17 | }) \ 18 | 19 | size_t hc_alignof(size_t size); 20 | 21 | #define _hc_array(t, a, n, ...) \ 22 | t a[] = { __VA_ARGS__ }; \ 23 | const size_t n = sizeof(a) / sizeof(t) 24 | 25 | #define hc_array(t, p, ...) \ 26 | _hc_array(t, hc_id(p, _a), hc_id(p, _n), ##__VA_ARGS__) 27 | 28 | #define _hc_baseof(p, t, m, _p) ({ \ 29 | uint8_t *_p = (uint8_t *)(p); \ 30 | _p ? ((t *)(_p - offsetof(t, m))) : NULL; \ 31 | }) 32 | 33 | #define hc_baseof(p, t, m) \ 34 | _hc_baseof(p, t, m, hc_unique(pointer)) 35 | 36 | #define hc_bitmask(v, bc) \ 37 | (v & ((1 << bc) - 1)) 38 | 39 | #define hc_const(x) ({ \ 40 | sizeof(int) == \ 41 | sizeof(*(8 ? ((void *)((long)(x) * 0l)) : (int *)8)) \ 42 | }) 43 | 44 | #define _hc_defer(_d, _v, ...) \ 45 | void _d(int *) { __VA_ARGS__; } \ 46 | int _v __attribute__ ((__cleanup__(_d))) 47 | 48 | #define hc_defer(...) \ 49 | _hc_defer(hc_unique(defer_d), hc_unique(defer_v), ##__VA_ARGS__) 50 | 51 | #define _hc_id(x, y) \ 52 | x ## y 53 | 54 | #define hc_id(x, y) \ 55 | _hc_id(x, y) 56 | 57 | #define hc_max(x, y) ({ \ 58 | __auto_type _x = x; \ 59 | __auto_type _y = y; \ 60 | _x > _y ? _x : _y; \ 61 | }) \ 62 | 63 | #define hc_min(x, y) ({ \ 64 | __auto_type _x = x; \ 65 | __auto_type _y = y; \ 66 | _x < _y ? _x : _y; \ 67 | }) \ 68 | 69 | #define hc_sign(x) \ 70 | (x < 0 ? -1 : 1) \ 71 | 72 | #define hc_unique(x) \ 73 | hc_id(x, __COUNTER__) 74 | 75 | #endif 76 | -------------------------------------------------------------------------------- /chrono/chrono.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "chrono.h" 7 | #include "error/error.h" 8 | #include "stream1/stream1.h" 9 | 10 | hc_time_t hc_now() { 11 | hc_time_t t; 12 | 13 | if (!timespec_get(&t.value, TIME_UTC)) { 14 | hc_throw("Failed getting time: %d", errno); 15 | } 16 | 17 | return t; 18 | } 19 | 20 | hc_time_t hc_time(int year, 21 | int month, 22 | int day, 23 | int hour, 24 | int minute, 25 | int second) { 26 | struct tm t = {0}; 27 | t.tm_year = year - 1900; 28 | t.tm_mon = month - 1; 29 | t.tm_mday = day; 30 | t.tm_hour = hour; 31 | t.tm_min = minute; 32 | t.tm_sec = second; 33 | 34 | hc_time_t result = {0}; 35 | result.value.tv_sec = timegm(&t); 36 | return result; 37 | } 38 | 39 | uint64_t hc_time_ns(const hc_time_t *t) { 40 | const struct timespec now = hc_now().value; 41 | 42 | return 43 | (now.tv_sec - t->value.tv_sec) * 1000000000 + 44 | (now.tv_nsec - t->value.tv_nsec); 45 | } 46 | 47 | void hc_time_print(const hc_time_t *t, const char *m) { 48 | printf("%s%" PRIu64 "ns\n", m, hc_time_ns(t)); 49 | } 50 | 51 | char *hc_time_sprintf(const hc_time_t *t, const char *spec) { 52 | struct tm tm; 53 | gmtime_r(&(t->value.tv_sec), &tm); 54 | size_t len = 8; 55 | char *result = malloc(len); 56 | 57 | for (;;) { 58 | const size_t n = strftime(result, len, spec, &tm); 59 | 60 | if (n) { 61 | result[n] = 0; 62 | break; 63 | } 64 | 65 | len *= 2; 66 | free(result); 67 | result = malloc(len); 68 | } 69 | 70 | return result; 71 | } 72 | 73 | void hc_time_printf(const hc_time_t *t, 74 | const char *spec, 75 | struct hc_stream *out) { 76 | char *s = hc_time_sprintf(t, "%Y-%m-%dT%H:%M:%S"); 77 | hc_puts(out, s); 78 | free(s); 79 | } 80 | 81 | uint64_t hc_sleep(uint64_t ns) { 82 | struct timespec t = {0}; 83 | t.tv_nsec = ns; 84 | 85 | switch (nanosleep(&t, &t)) { 86 | case 0: 87 | break; 88 | case EINTR: 89 | return t.tv_nsec; 90 | default: 91 | hc_throw("Failed sleeping: %d", errno); 92 | } 93 | 94 | return 0; 95 | } 96 | -------------------------------------------------------------------------------- /fix/fix.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "fix.h" 5 | #include "macro/macro.h" 6 | #include "stream1/stream1.h" 7 | 8 | uint32_t hc_scale(const uint8_t exp) { 9 | static const uint32_t scale[HC_FIX_MAX_EXP+1] = { 10 | 1, 11 | 10, 12 | 100, 13 | 1000, 14 | 10000, 15 | 100000, 16 | 1000000, 17 | 10000000}; 18 | 19 | assert(exp <= HC_FIX_MAX_EXP); 20 | return scale[exp]; 21 | } 22 | 23 | hc_fix_t hc_fix(const uint8_t exp, const int64_t val) { 24 | return (hc_fix_t)hc_bitmask(exp, HC_FIX_EXP) + 25 | (hc_fix_t)(((val < 0) ? 1 : 0) << HC_FIX_EXP) + 26 | (hc_fix_t)(hc_abs(val) << HC_FIX_HDR); 27 | } 28 | 29 | uint8_t hc_fix_exp(const hc_fix_t x) { 30 | return hc_bitmask(x, HC_FIX_EXP); 31 | } 32 | 33 | int64_t hc_fix_val(const hc_fix_t x) { 34 | const int64_t v = x >> HC_FIX_HDR; 35 | return ((x >> HC_FIX_EXP) & 1) ? -v : v; 36 | } 37 | 38 | int64_t hc_fix_int(const hc_fix_t x) { 39 | return hc_fix_val(x) / hc_scale(hc_fix_exp(x)); 40 | } 41 | 42 | int64_t hc_fix_frac(const hc_fix_t x) { 43 | const int64_t xv = hc_fix_val(x); 44 | const uint32_t xs = hc_scale(hc_fix_exp(x)); 45 | return xv - (xv / xs) * xs; 46 | } 47 | 48 | double hc_fix_double(const hc_fix_t x) { 49 | return hc_fix_val(x) / (double)hc_scale(hc_fix_exp(x)); 50 | } 51 | 52 | hc_fix_t hc_fix_add(const hc_fix_t x, const hc_fix_t y) { 53 | const uint8_t xe = hc_fix_exp(x); 54 | const uint8_t ye = hc_fix_exp(y); 55 | 56 | if (xe == ye) { 57 | return hc_fix(xe, hc_fix_val(x) + hc_fix_val(y)); 58 | } 59 | 60 | return hc_fix(xe, hc_fix_val(x) + 61 | hc_fix_val(y) * hc_scale(xe) / hc_scale(ye)); 62 | } 63 | 64 | hc_fix_t hc_fix_sub(const hc_fix_t x, const hc_fix_t y) { 65 | const uint8_t xe = hc_fix_exp(x); 66 | const uint8_t ye = hc_fix_exp(y); 67 | 68 | if (xe == ye) { 69 | return hc_fix(xe, hc_fix_val(x) - hc_fix_val(y)); 70 | } 71 | 72 | return hc_fix(xe, hc_fix_val(x) - 73 | hc_fix_val(y) * hc_scale(xe) / hc_scale(ye)); 74 | } 75 | 76 | hc_fix_t hc_fix_mul(const hc_fix_t x, const hc_fix_t y) { 77 | return hc_fix(hc_fix_exp(x), hc_fix_val(x) * 78 | hc_fix_val(y) / hc_scale(hc_fix_exp(y))); 79 | } 80 | 81 | hc_fix_t hc_fix_div(const hc_fix_t x, const hc_fix_t y) { 82 | return hc_fix(hc_fix_exp(x), hc_fix_val(x) / 83 | hc_fix_val(y) / hc_scale(hc_fix_exp(y))); 84 | } 85 | 86 | void hc_fix_print(const hc_fix_t v, struct hc_stream *out) { 87 | hc_printf(out, 88 | "%" PRId64 ".%" PRId64, 89 | hc_fix_int(v), 90 | hc_fix_frac(v)); 91 | } 92 | -------------------------------------------------------------------------------- /reflect/reflect.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "reflect.h" 6 | #include "stream1/stream1.h" 7 | 8 | struct hc_value *hc_value_init(struct hc_value *v, const struct hc_type *t) { 9 | v->type = t; 10 | return v; 11 | } 12 | 13 | void hc_value_deinit(struct hc_value *v) { 14 | if (v->type->deinit) { 15 | v->type->deinit(v); 16 | } 17 | } 18 | 19 | struct hc_value *hc_value_copy(struct hc_value *dst, struct hc_value *src) { 20 | const struct hc_type *t = src->type; 21 | 22 | if (t->copy) { 23 | dst->type = t; 24 | t->copy(dst, src); 25 | } else { 26 | *dst = *src; 27 | } 28 | 29 | return dst; 30 | } 31 | 32 | void hc_value_print(struct hc_value *v, struct hc_stream *out) { 33 | if (v->type->print) { 34 | v->type->print(v, out); 35 | } else { 36 | hc_value_write(v, out); 37 | } 38 | } 39 | 40 | void hc_value_write(struct hc_value *v, struct hc_stream *out) { 41 | assert(v->type->write); 42 | v->type->write(v, out); 43 | } 44 | 45 | static void bool_write(const struct hc_value *v, struct hc_stream *out) { 46 | hc_puts(out, v->as_bool ? "true" : "false"); 47 | } 48 | 49 | const struct hc_type HC_BOOL = { 50 | .name = "Bool", 51 | .copy = NULL, 52 | .write = bool_write 53 | }; 54 | 55 | static void fix_write(const struct hc_value *v, struct hc_stream *out) { 56 | hc_fix_print(v->as_fix, out); 57 | } 58 | 59 | const struct hc_type HC_FIX = { 60 | .name = "Fix", 61 | .copy = NULL, 62 | .write = fix_write 63 | }; 64 | 65 | static void int_write(const struct hc_value *v, struct hc_stream *out) { 66 | hc_printf(out, "%d", v->as_int); 67 | } 68 | 69 | const struct hc_type HC_INT = { 70 | .name = "Int", 71 | .copy = NULL, 72 | .write = int_write 73 | }; 74 | 75 | static void string_copy(struct hc_value *dst, struct hc_value *src) { 76 | dst->as_string = strdup(src->as_string); 77 | } 78 | 79 | static void string_deinit(struct hc_value *v) { 80 | free(v->as_string); 81 | } 82 | 83 | static void string_print(const struct hc_value *v, struct hc_stream *out) { 84 | hc_puts(out, v->as_string); 85 | } 86 | 87 | static void string_write(const struct hc_value *v, struct hc_stream *out) { 88 | hc_putc(out, '"'); 89 | string_print(v, out); 90 | hc_putc(out, '"'); 91 | } 92 | 93 | const struct hc_type HC_STRING = { 94 | .name = "String", 95 | .copy = string_copy, 96 | .deinit = string_deinit, 97 | .print = string_print, 98 | .write = string_write 99 | }; 100 | 101 | static void time_write(const struct hc_value *v, struct hc_stream *out) { 102 | hc_time_printf(&v->as_time, HC_TIME_FORMAT, out); 103 | } 104 | 105 | const struct hc_type HC_TIME = { 106 | .name = "Time", 107 | .copy = NULL, 108 | .write = time_write 109 | }; 110 | -------------------------------------------------------------------------------- /vector/vector.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "vector.h" 6 | #include "malloc1/malloc1.h" 7 | 8 | static void grow(struct hc_vector *v) { 9 | hc_vector_grow(v, v->capacity ? v->capacity*2 : 2); 10 | } 11 | 12 | struct hc_vector *hc_vector_init(struct hc_vector *v, 13 | struct hc_malloc *malloc, 14 | const size_t item_size) { 15 | v->malloc = malloc; 16 | v->item_size = item_size; 17 | v->capacity = 0; 18 | v->length = 0; 19 | v->start = v->end = NULL; 20 | return v; 21 | } 22 | 23 | void hc_vector_deinit(struct hc_vector *v) { 24 | if (v->start) { hc_release(v->malloc, v->start); } 25 | } 26 | 27 | void hc_vector_grow(struct hc_vector *v, const size_t capacity) { 28 | v->capacity = capacity; 29 | size_t size = v->item_size * (v->capacity+1); 30 | uint8_t *new_start = hc_acquire(v->malloc, size); 31 | 32 | if (v->start) { 33 | memmove(new_start, v->start, v->length * v->item_size); 34 | hc_release(v->malloc, v->start); 35 | } 36 | 37 | v->start = new_start; 38 | v->end = v->start + v->item_size*v->length; 39 | } 40 | 41 | void hc_vector_clear(struct hc_vector *v) { 42 | v->length = 0; 43 | v->end = v->start; 44 | } 45 | 46 | void *hc_vector_get(struct hc_vector *v, const size_t i) { 47 | return v->start ? v->start + v->item_size*i : NULL; 48 | } 49 | 50 | const void *hc_vector_get_const(const struct hc_vector *v, const size_t i) { 51 | return v->start ? v->start + v->item_size*i : NULL; 52 | } 53 | 54 | void *hc_vector_push(struct hc_vector *v) { 55 | if (v->length == v->capacity) { grow(v); } 56 | void *p = v->end; 57 | v->end += v->item_size; 58 | v->length++; 59 | return p; 60 | } 61 | 62 | void *hc_vector_peek(struct hc_vector *v) { 63 | return v->length ? v->end - v->item_size : NULL; 64 | } 65 | 66 | void *hc_vector_pop(struct hc_vector *v) { 67 | if (!v->length) { return NULL; } 68 | v->end -= v->item_size; 69 | v->length--; 70 | return v->end; 71 | } 72 | 73 | void *hc_vector_insert(struct hc_vector *v, const size_t i, const size_t n) { 74 | const size_t m = v->length+n; 75 | if (m > v->capacity) { hc_vector_grow(v, m); } 76 | uint8_t *const p = hc_vector_get(v, i); 77 | 78 | if (i < v->length) { 79 | memmove(p + v->item_size*n, p, (v->length - i) * v->item_size); 80 | } 81 | 82 | v->length += n; 83 | v->end += n*v->item_size; 84 | return p; 85 | } 86 | 87 | bool hc_vector_delete(struct hc_vector *v, const size_t i, const size_t n) { 88 | const size_t m = i+n; 89 | assert(m <= v->length); 90 | 91 | if (m < v->length) { 92 | uint8_t *const p = hc_vector_get(v, i); 93 | memmove(p, p + n*v->item_size, i + (v->length-n) * v->item_size); 94 | } 95 | 96 | v->length -= n; 97 | v->end -= n*v->item_size; 98 | return true; 99 | } 100 | -------------------------------------------------------------------------------- /macro/README.md: -------------------------------------------------------------------------------- 1 | ## Macros 2 | Before we move on, macros deserve a closer look. Many developers never get past the initial stage of utilizing their most basic features; which is a shame, because macros enable many powerful techniques. 3 | 4 | It's often useful to pass arguments through an additional layer of macros to force expansion. `hc_id()` is used to concatenate identifiers and utilizes this technique to expand arguments before concatenating using `##`. 5 | 6 | ```C 7 | #define _hc_id(x, y) 8 | x ## y 9 | 10 | #define hc_id(x, y) 11 | _hc_id(x, y) 12 | ``` 13 | 14 | Example: 15 | ```C 16 | int hc_id(foo, bar) = 42; 17 | assert(foobar == 42); 18 | ``` 19 | 20 | `hc_unique()` generates unique identifiers with specified prefix. It uses a system macro named `__COUNTER__` for the unique part, this macro increases its value on each expansion. 21 | 22 | ```C 23 | #define hc_unique(x) 24 | hc_id(x, __COUNTER__) 25 | ``` 26 | 27 | Since it doesn't make much sense to generate unique identifiers that are never used, this macro is mostly used to generate arguments for macros where the generated id can be reused. 28 | 29 | `hc_defer()` is used to register scoped destructors, it uses `hc_unique()` to generate a name for the temporary variable to which the `__cleanup__` attribute is applied, as well as for the destructor trampoline. Registered destructors are executed in reverse order. 30 | 31 | `__VA__ARGS__` represents the argument list in macros that take a variable number of arguments. It is sometimes written `##__VA_ARGS__`, which drops the preceding `,` if the argument list is empty. 32 | 33 | ```C 34 | #define _hc_defer(_d, _v, ...) 35 | void _d(int *) { __VA_ARGS__; } 36 | int _v __attribute__ ((__cleanup__(_d))) 37 | 38 | #define hc_defer(...) 39 | _hc_defer(hc_unique(defer_d), hc_unique(defer_v), ##__VA_ARGS__) 40 | ``` 41 | Example: 42 | ```C 43 | int foo = 0; 44 | 45 | { 46 | hc_defer(assert(foo++ == 1)); 47 | hc_defer(assert(foo++ == 0)); 48 | } 49 | 50 | assert(foo == 2); 51 | ``` 52 | 53 | One common problem that is best avoided in macro context is double expansion of macro arguments. What it means is that macros simply paste arguments as is; if the argument has an effect, the effect will be repeated for every expansion by default. That's the reason we're assigning temporaries when there is a need to refer to a macro argument multiple times within the body. 54 | 55 | `__auto_type` allows inferring types from macro arguments, this may be used to define generic macros such as `hc_min()`. 56 | 57 | Enclosing the macro body in `({`/`})` enables multi-line expressions. 58 | 59 | ```C 60 | #define hc_min(x, y) ({ 61 | __auto_type _x = x; 62 | __auto_type _y = y; 63 | _x < _y ? _x : _y; 64 | }) 65 | ``` 66 | 67 | Example: 68 | ```C 69 | assert(hc_min(7, 42) == 7); 70 | ``` 71 | 72 | Passing arrays as function arguments is slightly problematic in C due to the fact that they decay into pointers, which means the length is lost in the call. `hc_array` takes a type and a prefix, and defines the array and its length as `[prefix]_a`/`[prefix]_n`. 73 | 74 | ```C 75 | #define _hc_array(t, a, n, ...) 76 | t a[] = { __VA_ARGS__ }; 77 | const size_t n = sizeof(a) / sizeof(t) 78 | 79 | #define hc_array(t, p, ...) 80 | _hc_array(t, hc_id(p, _a), hc_id(p, _n), ##__VA_ARGS__) 81 | ``` -------------------------------------------------------------------------------- /slog/slog.h: -------------------------------------------------------------------------------- 1 | #ifndef HACKTICAL_SLOG_H 2 | #define HACKTICAL_SLOG_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "chrono/chrono.h" 9 | #include "reflect/reflect.h" 10 | #include "stream1/stream1.h" 11 | 12 | #define __hc_slog_do(s, _ps) \ 13 | for (struct hc_slog *_ps = hc_slog(); \ 14 | _ps && (_hc_slog = (s)); \ 15 | _hc_slog = _ps, _ps = NULL) 16 | 17 | #define _hc_slog_do(s) \ 18 | __hc_slog_do((s), hc_unique(ps)) 19 | 20 | #define hc_slog_do(s) \ 21 | _hc_slog_do(&(s)->slog) 22 | 23 | struct hc_slog; 24 | 25 | struct hc_slog_field { 26 | char *name; 27 | struct hc_value value; 28 | }; 29 | 30 | struct hc_slog { 31 | void (*deinit)(struct hc_slog *); 32 | void (*write)(struct hc_slog *, size_t, struct hc_slog_field *[]); 33 | }; 34 | 35 | struct hc_slog_stream_opts { 36 | bool close_out; 37 | }; 38 | 39 | struct hc_slog_stream { 40 | struct hc_slog slog; 41 | struct hc_stream *out; 42 | struct hc_slog_stream_opts opts; 43 | }; 44 | 45 | extern __thread struct hc_slog *_hc_slog; 46 | struct hc_slog *hc_slog(); 47 | 48 | struct hc_slog_field; 49 | 50 | struct hc_slog_field *hc_slog_bool(const char *name, bool value); 51 | struct hc_slog_field *hc_slog_int(const char *name, int value); 52 | struct hc_slog_field *hc_slog_string(const char *name, const char *value); 53 | struct hc_slog_field *hc_slog_time(const char *name, hc_time_t value); 54 | 55 | #define hc_slog_deinit(s) \ 56 | _hc_slog_deinit(&(s)->slog) 57 | 58 | #define _hc_slog_write(s, ...) do { \ 59 | hc_array(struct hc_slog_field *, fs, ##__VA_ARGS__); \ 60 | __hc_slog_write((s), fs_n, fs_a); \ 61 | } while (0) 62 | 63 | #define hc_slog_write(...) \ 64 | _hc_slog_write(hc_slog(), ##__VA_ARGS__) 65 | 66 | #define hc_slog_stream_init(s, out, ...) \ 67 | _hc_slog_stream_init(s, out, (struct hc_slog_stream_opts){ \ 68 | .close_out = false, \ 69 | ##__VA_ARGS__ \ 70 | }) 71 | 72 | struct hc_slog_stream *_hc_slog_stream_init(struct hc_slog_stream *s, 73 | struct hc_stream *out, 74 | struct hc_slog_stream_opts opts); 75 | 76 | void _hc_slog_deinit(struct hc_slog *s); 77 | 78 | void __hc_slog_write(struct hc_slog *s, 79 | size_t n, 80 | struct hc_slog_field *fields[]); 81 | 82 | #define __hc_slog_context_do(_c, _fs, _a, _n, ...) \ 83 | struct hc_slog_context _c; \ 84 | hc_array(struct hc_slog_field *, _fs, ##__VA_ARGS__); \ 85 | hc_slog_context_init(&_c, _n, _a); \ 86 | hc_defer(hc_slog_deinit(&_c)); \ 87 | hc_slog_do(&_c) 88 | 89 | #define _hc_slog_context_do(_c, _fs, ...) \ 90 | __hc_slog_context_do(_c, \ 91 | _fs, \ 92 | hc_id(_fs, _a), \ 93 | hc_id(_fs, _n), \ 94 | ##__VA_ARGS__) 95 | 96 | #define hc_slog_context_do(...) \ 97 | _hc_slog_context_do(hc_unique(slog_c), \ 98 | hc_unique(slog_fs), \ 99 | ##__VA_ARGS__) 100 | 101 | struct hc_slog_context { 102 | struct hc_slog slog; 103 | struct hc_slog *parent; 104 | size_t length; 105 | struct hc_slog_field **fields; 106 | }; 107 | 108 | struct hc_slog_context *hc_slog_context_init(struct hc_slog_context *c, 109 | size_t length, 110 | struct hc_slog_field *fields[]); 111 | 112 | #endif 113 | -------------------------------------------------------------------------------- /dsl/dsl.h: -------------------------------------------------------------------------------- 1 | #ifndef HACKTICAL_DSL_H 2 | #define HACKTICAL_DSL_H 3 | 4 | #include "vm/vm.h" 5 | 6 | enum hc_order hc_strcmp(const char *x, const char *y); 7 | char *hc_upcase(char *s); 8 | 9 | struct hc_dsl { 10 | struct hc_set env; 11 | struct hc_stream *out; 12 | 13 | struct hc_vm vm; 14 | }; 15 | 16 | void hc_dsl_init(struct hc_dsl *dsl, struct hc_malloc *malloc); 17 | void hc_dsl_deinit(struct hc_dsl *dsl); 18 | 19 | struct hc_value* hc_dsl_getenv(struct hc_dsl *dsl, const char *key); 20 | 21 | struct hc_value *hc_dsl_setenv(struct hc_dsl *dsl, 22 | const char *key, 23 | const struct hc_type *type); 24 | 25 | void hc_dsl_set_fun(struct hc_dsl *dsl, const char *key, hc_vm_fun_t val); 26 | void hc_dsl_set_string(struct hc_dsl *dsl, const char *key, const char *val); 27 | void hc_dsl_eval(struct hc_dsl *dsl, const char *in); 28 | 29 | struct hc_form; 30 | 31 | enum hc_dsl_parsed { 32 | HC_DSL_CALL = 1, HC_DSL_ID, HC_DSL_TEXT 33 | }; 34 | 35 | struct hc_form_type { 36 | void (*emit)(struct hc_form *, struct hc_dsl *); 37 | void (*print)(const struct hc_form *, struct hc_stream *); 38 | struct hc_value *(*value)(const struct hc_form *, struct hc_dsl *); 39 | void (*free)(struct hc_form *); 40 | }; 41 | 42 | struct hc_form { 43 | const struct hc_form_type *type; 44 | struct hc_sloc sloc; 45 | struct hc_list owner; 46 | }; 47 | 48 | void hc_form_init(struct hc_form *f, 49 | const struct hc_form_type *type, 50 | struct hc_sloc sloc, 51 | struct hc_list *owner); 52 | 53 | void hc_form_emit(struct hc_form *f, struct hc_dsl *dsl); 54 | void hc_form_print(struct hc_form *f, struct hc_stream *out); 55 | struct hc_value *hc_form_value(const struct hc_form *f, struct hc_dsl *dsl); 56 | void hc_form_free(struct hc_form *f); 57 | 58 | extern const struct hc_form_type HC_CALL_FORM; 59 | 60 | struct hc_call { 61 | struct hc_form form; 62 | struct hc_form *target; 63 | struct hc_list args; 64 | }; 65 | 66 | void hc_call_init(struct hc_call *f, 67 | struct hc_sloc sloc, 68 | struct hc_list *owner, 69 | struct hc_form *target); 70 | 71 | extern const struct hc_form_type HC_ID_FORM; 72 | 73 | struct hc_id { 74 | struct hc_form form; 75 | char *name; 76 | }; 77 | 78 | void hc_id_init(struct hc_id *f, 79 | struct hc_sloc sloc, 80 | struct hc_list *owner, 81 | const char *name); 82 | 83 | extern const struct hc_form_type HC_LITERAL; 84 | 85 | struct hc_literal { 86 | struct hc_form form; 87 | struct hc_value value; 88 | }; 89 | 90 | void hc_literal_init(struct hc_literal *f, 91 | struct hc_sloc sloc, 92 | struct hc_list *owner); 93 | 94 | void hc_skip_ws(const char **in, struct hc_sloc *sloc); 95 | 96 | void hc_read_call(const char **in, 97 | struct hc_list *out, 98 | struct hc_sloc *sloc); 99 | 100 | bool hc_read_expr(const char **in, 101 | struct hc_list *out, 102 | struct hc_sloc *sloc); 103 | 104 | void hc_read_id(const char **in, 105 | struct hc_list *out, 106 | struct hc_sloc *sloc); 107 | 108 | bool hc_read_next(const char **in, 109 | struct hc_list *out, 110 | struct hc_sloc *sloc); 111 | 112 | bool hc_read_text(const char **in, 113 | struct hc_list *out, 114 | struct hc_sloc *sloc); 115 | 116 | void hc_forms_emit(struct hc_list *in, struct hc_dsl *dsl); 117 | void hc_forms_free(struct hc_list *in); 118 | 119 | #endif 120 | -------------------------------------------------------------------------------- /vector/README.md: -------------------------------------------------------------------------------- 1 | ## Vectors 2 | A vector is a dynamically allocated array that automagically changes its size as needed. Items are stored in a single block of memory, just like a regular array; on top of that it takes care of the book keeping when adding/removing items. It's by far the most common collection type in today's mainstream programming languages. 3 | 4 | Rather than storing pointers to values, we'll expose the memory directly to enable value based access; this means that the vector needs to know the size of its items. We also cache the start and end of the currently used, aligned memory block. 5 | 6 | ```C 7 | struct hc_vector { 8 | size_t item_size, capacity, length; 9 | uint8_t *start, *end; 10 | struct hc_malloc *malloc; 11 | }; 12 | ``` 13 | 14 | Example: 15 | ```C 16 | struct hc_vector v; 17 | hc_vector_init(&v, &hc_malloc_default, sizeof(int)); 18 | hc_vector_grow(&v, 10); 19 | 20 | const int n = 10; 21 | 22 | for (int i = 0; i < n; i++) { 23 | *(int *)hc_vector_push(&v) = i; 24 | } 25 | ``` 26 | 27 | The `hc_vector_grow()` call in the preceding example is not strictly needed, but helps reduce allocations; without it the vector would need to duble the size of its memory block 3 times (allocating 2, 4, 8 and finally 16*32 bytes) to store 10 integers. 28 | 29 | ```C 30 | void hc_vector_grow(struct hc_vector *v, int capacity) { 31 | v->capacity = capacity; 32 | size_t size = v->item_size * (v->capacity+1); 33 | uint8_t *new_start = hc_acquire(v->malloc, size); 34 | 35 | if (v->start) { 36 | memmove(new_start, v->start, v->length * v->item_size); 37 | hc_release(v->malloc, v->start); 38 | } 39 | 40 | v->start = new_start; 41 | v->end = v->start + v->item_size*v->length; 42 | } 43 | ``` 44 | 45 | A macro is provided to simplify looping. 46 | 47 | ```C 48 | #define _hc_vector_do(v, _v, var) 49 | struct hc_vector *_v = v; 50 | for (void *var = _v->start; 51 | var < (void *)_v->end; 52 | var += _v->item_size) 53 | 54 | #define hc_vector_do(v, var) 55 | _hc_vector_do(v, hc_unique(vector), var) 56 | ``` 57 | 58 | Example: 59 | ```C 60 | hc_vector_do(&v, it) { 61 | int v = *(int *)it; 62 | ... 63 | } 64 | ``` 65 | 66 | Alternatively you can use `hc_vector_get()` with a manual loop, which is slightly slower since it needs to call a function and calculate the pointer for every iteration. 67 | 68 | ```C 69 | for (int i = 0; i < n; i++) { 70 | int v = *(int *)hc_vector_get(&v, i); 71 | ... 72 | } 73 | ``` 74 | 75 | While not the primary use case for vectors, it's sometimes useful to be able to insert/delete blocks of items. `hc_vector_insert()` moves the tail if needed and returns a pointer to the start of the inserted block. 76 | 77 | ```C 78 | void *hc_vector_insert(struct hc_vector *v, int i, int n) { 79 | const int m = v->length+n; 80 | if (m > v->capacity) { hc_vector_grow(v, m); } 81 | uint8_t *const p = hc_vector_get(v, i); 82 | 83 | if (i < v->length) { 84 | memmove(p + v->item_size*n, p, (v->length - i) * v->item_size); 85 | } 86 | 87 | v->length += n; 88 | v->end += n*v->item_size; 89 | return p; 90 | } 91 | ``` 92 | 93 | `hc_vector_delete()` similarly moves the tail if needed. 94 | 95 | ```C 96 | void hc_vector_delete(struct hc_vector *v, int i, int n) { 97 | const int m = i+n; 98 | assert(v->length <= m); 99 | 100 | if (m < v->length) { 101 | uint8_t *const p = hc_vector_get(v, i); 102 | memmove(p, p + n*v->item_size, i + (v->length-n) * v->item_size); 103 | } 104 | 105 | v->length -= n; 106 | v->end -= n*v->item_size; 107 | return true; 108 | } 109 | ``` -------------------------------------------------------------------------------- /task/README.md: -------------------------------------------------------------------------------- 1 | ## Lightweight Concurrent Tasks 2 | Concurrent tasks allows keeping the flow of control intact where it would otherwise get lost in the noise of a solution based on discrete events. Note that concurrent means interleaved, not parallel; system threads would add a lot of complexity and overhead in comparison. C lacks built in support for coroutines, but the same effect can be achieved without breaking any rules. 3 | 4 | This is what a task looks like. 5 | 6 | ```C 7 | struct hc_task { 8 | struct hc_list list; 9 | hc_task_body body; 10 | int state; 11 | bool done; 12 | }; 13 | ``` 14 | 15 | The body is simply a regular pointer to a function taking a `struct hc_task *`-argument. 16 | 17 | ```C 18 | typedef void (*hc_task_body)(struct hc_task *); 19 | ``` 20 | 21 | We'll also need a strategy to track of a list of tasks, a kind of scheduler. 22 | 23 | ```C 24 | struct hc_task_list { 25 | struct hc_list tasks; 26 | }; 27 | ``` 28 | 29 | To keep it simple, we'll simply keep running tasks until all are `done`. One obvious improvement would be to keep running tasks on a separate list. 30 | 31 | ```C 32 | void hc_task_list_run(struct hc_task_list *tl) { 33 | bool all_done = false; 34 | 35 | while (!all_done) { 36 | all_done = true; 37 | 38 | hc_list_do(&tl->tasks, i) { 39 | struct hc_task *t = hc_baseof(i, struct hc_task, list); 40 | 41 | if (!t->done) { 42 | t->body(t); 43 | all_done = false; 44 | } 45 | } 46 | } 47 | } 48 | ``` 49 | 50 | We now have all the pieces needed to define our tasks. For this example, we'll use a simple counter to represent the data being produced/consumed. 51 | 52 | ```C 53 | struct my_task { 54 | struct hc_task task; 55 | int *value; 56 | }; 57 | 58 | void producer(struct hc_task *task); 59 | void consumer(struct hc_task *task); 60 | 61 | int main() { 62 | struct hc_task_list tl; 63 | hc_task_list_init(&tl); 64 | 65 | int value = 0; 66 | 67 | struct my_task pt = {.value = &value}; 68 | hc_task_init(&pt.task, &tl, &producer); 69 | 70 | struct my_task ct = {.value = &value}; 71 | hc_task_init(&ct.task, &tl, &consumer); 72 | 73 | hc_task_list_run(&tl); 74 | } 75 | ``` 76 | 77 | The producer increases the counter and then yields control to the consumer, execution resumes on the next line following `hc_task_yield()` on next call. 78 | 79 | ```C 80 | void producer(struct hc_task *task) { 81 | int *value = hc_baseof(task, struct my_task, task)->value; 82 | 83 | switch (task->state) { 84 | case 0: 85 | (*value)++; 86 | hc_task_yield(task); 87 | (*value)++; 88 | } 89 | 90 | task->done = true; 91 | } 92 | ``` 93 | 94 | The consumer decreases the counter in a similar fashion. 95 | 96 | ```C 97 | static void consumer(struct hc_task *task) { 98 | int *value = hc_baseof(task, struct my_task, task)->value; 99 | 100 | switch (task->state) { 101 | case 0: 102 | (*value)--; 103 | hc_task_yield(task); 104 | (*value)--; 105 | } 106 | 107 | task->done = true; 108 | } 109 | ``` 110 | 111 | `hc_task_yield()` is implemented as a macro that updates the task state and adds a matching `case`. The `__LINE__` macro expands to the current source code line number. 112 | 113 | ```C 114 | #define hc_task_yield(task) 115 | do { 116 | task->state = __LINE__; 117 | return; 118 | case __LINE__:; 119 | } while (0) 120 | ``` 121 | 122 | The reason this works as well as it does is because C allows `case` to appear at any nesting level within a `switch`; the discovery of this feature is often credited to [Tom Duff](https://en.wikipedia.org/wiki/Duff%27s_device). 123 | 124 | ### Limitations 125 | Since we're skipping around inside the task's function body, any local variables that span calls to `hc_task_yield()` need to be placed inside `struct my_task`. -------------------------------------------------------------------------------- /list/README.md: -------------------------------------------------------------------------------- 1 | ## Intrusive Doubly Linked Lists 2 | Linked lists have a somewhat sketchy reputation these days. Which is a shame, because it's a very flexible and cheap technique for keeping track of a sequence of values. 3 | 4 | Singly linked lists are tricky to rearrange and remove items from since you always need access to the previous item, which can only be obtained by starting from the head. Doubly linked lists solve this problem, since you have direct access to both the previous and next item. 5 | 6 | The other common objection is memory locality. Since list nodes are separate from the actual values, iterating means chasing pointers at every step. Modern computer architectures derive a lot of their speed from caching large blocks of data, as opposed to repeatedly accessing main memory; which makes ordinary linked lists slow compared to arrays. 7 | 8 | Intrusive means that the list's infrastructure is stored inside the values. This means no extra allocations for list nodes and potentially no pointer chasing if values are allocated as a single block of memory. 9 | 10 | At this point you may wonder: what is the point of a list of values stored in a single block of memory? The point is that the allocation strategy has little to do with what sequences we choose to put values in. One common strategy in C is to allocate a bunch of values as a single block of memory, as opposed to asking the memory allocator for one value at a time; which reduces memory fragmentation. 11 | 12 | This is what a list node looks like; as promised there is no trace of the value, just the links. 13 | 14 | ```C 15 | struct hc_list { 16 | struct hc_list *prev, *next; 17 | }; 18 | ``` 19 | 20 | And this is a value which is prepared for inclusion in two lists (at a time). 21 | 22 | ```C 23 | struct my_item { 24 | struct hc_list one_list; 25 | struct hc_list another_list; 26 | int value; 27 | }; 28 | ``` 29 | 30 | We can now allocate as many items as we need in one block, and add them to a maximum of two lists at a time. List roots are simply nodes that are not part of any values. 31 | 32 | ```C 33 | struct hc_list one_list, another_list; 34 | hc_list_init(&one_list); 35 | hc_list_init(&another_list); 36 | 37 | const int n = 10; 38 | struct my_item items[n]; 39 | 40 | for (int i = 0; i < n; i++) { 41 | items[i].value = i; 42 | hc_list_push_back(&one_list, &items[i].one_list); 43 | hc_list_push_back(&another_list, &items[i].another_list); 44 | } 45 | ``` 46 | 47 | Iterating a list means starting from the head and stepping through the links until we reach the head again. Since this is something we'll likely do a lot, extracting the pattern as a macro makes sense. Note that simply switching links form `next` to `prev` allows iterating the list in reverse, which is sometimes useful. 48 | 49 | ```C 50 | #define _hc_list_do(l, i, _list, _next) 51 | __auto_type _list = l; 52 | for (struct hc_list *i = _list->next, *_next = i->next; 53 | i != _list; 54 | i = _next, _next = i->next) 55 | 56 | #define hc_list_do(l, i) 57 | _hc_list_do(l, i, hc_unique(list), hc_unique(next)) 58 | ``` 59 | 60 | Example: 61 | ```C 62 | hc_list_do(&one_list, i) { 63 | struct my_item it = hc_baseof(i, struct my_item, one_list); 64 | ... 65 | } 66 | ``` 67 | 68 | We use the following macro to reach out from `struct hc_list` to its `struct my_item`-container. 69 | 70 | ```C 71 | #define _hc_baseof(p, t, m, _p) ({ 72 | uint8_t *_p = (uint8_t *)(p); 73 | _p ? ((t *)(_p - offsetof(t, m))) : NULL; 74 | }) 75 | 76 | #define hc_baseof(p, t, m) 77 | _hc_baseof(p, t, m, hc_unique(pointer)) 78 | ``` 79 | 80 | To remove an item from a list; we don't even need access to the list root, the item is enough. 81 | 82 | ```C 83 | struct hc_list *hc_list_delete(struct hc_list *l) { 84 | l->prev->next = l->next; 85 | l->next->prev = l->prev; 86 | return l; 87 | } 88 | ``` 89 | 90 | Example: 91 | ```C 92 | hc_list_delete(&items[0].one_list); 93 | ``` -------------------------------------------------------------------------------- /malloc1/README.md: -------------------------------------------------------------------------------- 1 | ## Composable Memory Allocators - Part 1 2 | Writing your own memory allocator is a common rite of passage for novice C programmers. Which makes sense, since one of the defining features of C is direct memory access. 3 | 4 | Here we're going to explore a composable design that allows conveniently custom tailoring the allocation strategy. Individual allocators follow the Unix-principle of doing one thing well. 5 | 6 | Allocators are required to support the following API: 7 | 8 | ```C 9 | struct hc_malloc { 10 | void *(*acquire)(struct hc_malloc *, size_t); 11 | void (*release)(struct hc_malloc *, void *); 12 | }; 13 | ``` 14 | 15 | The default/root allocator delegates to `malloc`/`free`. 16 | 17 | ```C 18 | void *default_acquire(struct hc_malloc *m, size_t size) { 19 | return malloc(size); 20 | } 21 | 22 | void default_release(struct hc_malloc *m, void *p) { 23 | free(p); 24 | } 25 | 26 | struct hc_malloc hc_malloc_default = { 27 | .acquire = default_acquire, 28 | .release = default_release 29 | }; 30 | ``` 31 | 32 | ```C 33 | #define _hc_acquire(m, _m, s) ({ 34 | struct hc_malloc *_m = m; 35 | assert(_m->acquire); 36 | _m->acquire(_m, s); 37 | }) 38 | 39 | #define hc_acquire(m, s) 40 | _hc_acquire(m, hc_unique(malloc_m), s) 41 | 42 | #define _hc_release(m, _m, p) do { 43 | struct hc_malloc *_m = m; 44 | assert(_m->release); 45 | _m->release(_m, p); 46 | } while (0) 47 | 48 | #define hc_release(m, p) 49 | _hc_release(m, hc_unique(malloc_m), p) 50 | ``` 51 | 52 | ### Alignment 53 | Before we dive into the first real implementation, alignment deserves a brief discussion. The short story is that the CPU requires data to be aligned to size multiples, meaning the start address is required to be a multiple of the size (up to `_Alignof(max_align_t)`). Since this is something we're going to do now and then, `hc_align()` is provided to simplify the process. 54 | 55 | ```C 56 | #define hc_align(base, size) ({ 57 | __auto_type _base = base; 58 | __auto_type _size = hc_alignof(size); 59 | __auto_type _rest = (ptrdiff_t)_base % _size; 60 | (_rest) ? _base + _size - _rest : _base; 61 | }) 62 | 63 | size_t hc_alignof(size_t size) { 64 | const size_t max = _Alignof(max_align_t); 65 | if (size >= max) { return max; } 66 | size_t v = 1; 67 | for (size_t nv = 1; nv <= size; v = nv, nv = v << 1); 68 | return v; 69 | } 70 | ``` 71 | 72 | ### Bump Allocation 73 | 74 | A bump allocator consists of a fixed block of memory, a size and an offset. 75 | 76 | ```C 77 | struct hc_bump_alloc { 78 | struct hc_malloc malloc; 79 | struct hc_malloc *source; 80 | size_t size, offset; 81 | uint8_t *memory; 82 | }; 83 | 84 | void hc_bump_alloc_init(struct hc_bump_alloc *a, 85 | struct hc_malloc *source, 86 | size_t size) { 87 | a->malloc.acquire = bump_acquire; 88 | a->malloc.release = bump_release; 89 | a->source = source; 90 | a->size = size; 91 | a->offset = 0; 92 | a->memory = hc_acquire(source, size); 93 | } 94 | 95 | void hc_bump_alloc_deinit(struct hc_bump_alloc *a) { 96 | hc_release(a->source, a->memory); 97 | } 98 | ``` 99 | 100 | `acquire()` bumps the offset to a correctly aligned address plus the requested size. 101 | 102 | ```C 103 | void *bump_acquire(struct hc_malloc *m, size_t size) { 104 | if (size <= 0) { 105 | hc_throw(HC_INVALID_SIZE, "Invalid size"); 106 | } 107 | 108 | struct hc_bump_alloc *ba = hc_baseof(a, struct hc_bump_alloc, malloc); 109 | 110 | if (ba->size - ba->offset < size) { 111 | hc_throw(HC_NO_MEMORY, "Out of memory"); 112 | } 113 | 114 | uint8_t *p = ba->memory + ba->offset; 115 | uint8_t *pa = hc_align(p, size); 116 | ba->offset = ba->offset + pa - p + size; 117 | return pa; 118 | } 119 | ``` 120 | 121 | `release()` is a no op. 122 | 123 | ```C 124 | void bump_release(struct hc_malloc *m, void *p) { 125 | // Do nothing 126 | } 127 | ``` 128 | 129 | Continued in [Part 2](https://github.com/codr7/hacktical-c/tree/main/malloc2). -------------------------------------------------------------------------------- /dynamic/dynamic.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "dynamic.h" 10 | #include "error/error.h" 11 | #include "malloc1/malloc1.h" 12 | #include "vector/vector.h" 13 | 14 | struct hc_proc *_hc_proc_init(struct hc_proc *p, char *cmd[]) { 15 | int fds[2]; 16 | 17 | if (pipe(fds) == -1) { 18 | hc_throw("Failed creating pipe: %d", errno); 19 | } 20 | 21 | pid_t child_pid = fork(); 22 | 23 | switch (child_pid) { 24 | case 0: { 25 | if (close(fds[1]) == -1) { 26 | hc_throw("Failed closing pipe writer: %d", errno); 27 | } 28 | 29 | if (dup2(fds[0], 0) == -1) { 30 | hc_throw("Failed rebinding stdin: %d", errno); 31 | } 32 | 33 | char *const env[] = {"PATH=/bin:/sbin", NULL}; 34 | 35 | if (execve(cmd[0], cmd, env) == -1) { 36 | hc_throw("Failed to execve '%s': %d", cmd[0], errno); 37 | } 38 | } 39 | case -1: 40 | hc_throw("Failed forking process: %d", errno); 41 | default: 42 | if (close(fds[0]) == -1) { 43 | hc_throw("Failed closing pipe reader: %d", errno); 44 | } 45 | 46 | p->pid = child_pid; 47 | p->in = fds[1]; 48 | break; 49 | } 50 | 51 | return p; 52 | } 53 | 54 | static void close_in(struct hc_proc *p) { 55 | if (p->in != -1 && close(p->in) == -1) { 56 | hc_throw("Failed closing stdin: %d", errno); 57 | } 58 | } 59 | 60 | void hc_proc_wait(struct hc_proc *p) { 61 | close_in(p); 62 | 63 | if (waitpid(p->pid, NULL, 0) == -1) { 64 | hc_throw("Failed waiting for child process to exit: %d", errno); 65 | } 66 | } 67 | 68 | void hc_proc_deinit(struct hc_proc *p) { 69 | close_in(p); 70 | } 71 | 72 | static void free_cmd(char **in) { 73 | for (char **s = in; *s; s++) { 74 | free(*s); 75 | } 76 | } 77 | 78 | void _hc_compile(const char *code, 79 | const char *out, 80 | const struct hc_compile_opts opts) { 81 | hc_array(const char *, pre, 82 | opts.cc, "-shared", "-fpic", "-o", out, "-xc"); 83 | 84 | int n = pre_n + 2; 85 | for (int i = 0; opts.cflags[i]; i++, n++); 86 | char *cmd[n]; 87 | int i = 0; 88 | 89 | for (; i < pre_n; i++) { 90 | cmd[i] = strdup(pre_a[i]); 91 | } 92 | 93 | for (; i < n - 2; i++) { 94 | cmd[i] = strdup(opts.cflags[i - pre_n]); 95 | } 96 | 97 | cmd[i++] = strdup("-"); 98 | cmd[i] = NULL; 99 | hc_defer(free_cmd(cmd)); 100 | 101 | struct hc_proc child; 102 | _hc_proc_init(&child, cmd); 103 | hc_defer(hc_proc_deinit(&child)); 104 | FILE *in = fdopen(child.in, "w"); 105 | 106 | if (!in) { 107 | hc_throw("Failed opening stdin stream: %d", errno); 108 | } 109 | 110 | child.in = -1; 111 | hc_defer(hc_proc_wait(&child)); 112 | hc_defer(fclose(in)); 113 | 114 | if (fputs(code, in) == EOF) { 115 | hc_throw("Failed writing code: %d", errno); 116 | } 117 | } 118 | 119 | struct hc_dlib *hc_dlib_init(struct hc_dlib *lib, const char *path) { 120 | lib->handle = dlopen(path, RTLD_NOW); 121 | 122 | if (!lib->handle) { 123 | hc_throw("Error opening dynamic library '%s': %s", path, dlerror()); 124 | } 125 | 126 | return lib; 127 | } 128 | 129 | struct hc_dlib *hc_dlib_deinit(struct hc_dlib *lib) { 130 | if (dlclose(lib->handle) != 0) { 131 | hc_throw("Failed closing dynamic library: ", dlerror()); 132 | } 133 | 134 | return lib; 135 | } 136 | 137 | void *hc_dlib_find(const struct hc_dlib *lib, const char *s) { 138 | dlerror(); 139 | void *v = dlsym(lib->handle, s); 140 | char *e = dlerror(); 141 | 142 | if (e) { 143 | hc_throw("Symbol '%s' not found: %s", e); 144 | } 145 | 146 | return v; 147 | } 148 | 149 | char *hc_vsprintf(const char *format, va_list args) { 150 | va_list tmp_args; 151 | va_copy(tmp_args, args); 152 | int len = vsnprintf(NULL, 0, format, tmp_args); 153 | va_end(tmp_args); 154 | 155 | if (len < 0) { 156 | hc_throw("Formatting '%s' failed: %d", format, errno); 157 | } 158 | 159 | len++; 160 | char *out = malloc(len); 161 | vsnprintf(out, len, format, args); 162 | return out; 163 | } 164 | -------------------------------------------------------------------------------- /malloc2/malloc2.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "error/error.h" 4 | #include "macro/macro.h" 5 | #include "malloc2.h" 6 | 7 | /* Memo */ 8 | 9 | struct memo { 10 | size_t size; 11 | uint8_t data[]; 12 | }; 13 | 14 | static void *memo_acquire(struct hc_malloc *a, size_t size) { 15 | struct hc_memo_alloc *ma = hc_baseof(a, struct hc_memo_alloc, malloc); 16 | 17 | if (hc_set_length(&ma->memo)) { 18 | bool ok = false; 19 | size_t i = hc_set_index(&ma->memo, &size, &ok); 20 | 21 | if (ok) { 22 | struct hc_vector *is = &ma->memo.items; 23 | struct memo *m = *(struct memo **)hc_vector_get(is, i); 24 | hc_vector_delete(is, i, 1); 25 | return m->data; 26 | } 27 | } 28 | 29 | struct memo *m = hc_acquire(ma->source, sizeof(struct memo) + size); 30 | m->size = size; 31 | return m->data; 32 | } 33 | 34 | static void memo_release(struct hc_malloc *a, void *p) { 35 | struct hc_memo_alloc *ma = hc_baseof(a, struct hc_memo_alloc, malloc); 36 | struct memo *m = hc_baseof(p, struct memo, data); 37 | *(struct memo **)hc_set_add(&ma->memo, &m->size, true) = m; 38 | } 39 | 40 | static enum hc_order memo_cmp(const void *l, const void *r) { 41 | return hc_cmp(*(size_t *)l, *(size_t *)r); 42 | } 43 | 44 | static const void *memo_key(const void *p) { 45 | struct memo *m = *(struct memo **)p; 46 | return &m->size; 47 | } 48 | 49 | struct hc_memo_alloc *hc_memo_alloc_init(struct hc_memo_alloc *a, 50 | struct hc_malloc *source) { 51 | a->malloc.acquire = memo_acquire; 52 | a->malloc.release = memo_release; 53 | a->source = source; 54 | hc_set_init(&a->memo, &hc_malloc_default, sizeof(struct memo *), memo_cmp); 55 | a->memo.key = memo_key; 56 | return a; 57 | } 58 | 59 | void hc_memo_alloc_deinit(struct hc_memo_alloc *a) { 60 | hc_vector_do(&a->memo.items, _m) { 61 | struct memo *m = *(struct memo **)_m; 62 | hc_release(a->source, m); 63 | } 64 | 65 | hc_set_deinit(&a->memo); 66 | } 67 | 68 | /* Slab */ 69 | 70 | struct slab { 71 | struct hc_list slabs; 72 | uint8_t *next; 73 | uint8_t memory[]; 74 | }; 75 | 76 | static struct slab *add_slab(struct hc_slab_alloc *a, const size_t size) { 77 | struct slab *s = hc_acquire(a->source, sizeof(struct slab) + size); 78 | hc_list_push_front(&a->slabs, &s->slabs); 79 | s->next = s->memory; 80 | return s; 81 | } 82 | 83 | static struct slab *get_slab(struct hc_slab_alloc *a, const size_t size) { 84 | if (size > a->slab_size) { 85 | return add_slab(a, size); 86 | } 87 | 88 | struct slab *result = NULL; 89 | 90 | hc_list_do(&a->slabs, sl) { 91 | struct slab *s = hc_baseof(sl, struct slab, slabs); 92 | uint8_t *p = hc_align(s->next, size); 93 | 94 | if (p + size > s->memory + a->slab_size) { 95 | break; 96 | } 97 | 98 | result = s; 99 | } 100 | 101 | return result ? result : add_slab(a, a->slab_size); 102 | } 103 | 104 | static void *slab_acquire(struct hc_malloc *a, const size_t size) { 105 | struct hc_slab_alloc *sa = hc_baseof(a, struct hc_slab_alloc, malloc); 106 | struct slab *s = get_slab(sa, size); 107 | uint8_t *p = hc_align(s->next, size); 108 | s->next = p + size; 109 | 110 | while (s->slabs.next != &s->slabs) { 111 | struct slab *ns = hc_baseof(s->slabs.next, struct slab, slabs); 112 | 113 | if (ns->next - ns->memory > s->next - s->memory) { 114 | hc_list_shift_back(&s->slabs); 115 | } else { 116 | break; 117 | } 118 | } 119 | 120 | return p; 121 | } 122 | 123 | static void slab_release(struct hc_malloc *a, void *p) { 124 | // Do nothing 125 | } 126 | 127 | struct hc_slab_alloc *hc_slab_alloc_init(struct hc_slab_alloc *a, 128 | struct hc_malloc *source, 129 | const size_t slab_size) { 130 | a->malloc.acquire = slab_acquire; 131 | a->malloc.release = slab_release; 132 | a->source = source; 133 | hc_list_init(&a->slabs); 134 | a->slab_size = slab_size; 135 | return a; 136 | } 137 | 138 | void hc_slab_alloc_deinit(struct hc_slab_alloc *a) { 139 | hc_list_do(&a->slabs, _s) { 140 | struct slab *s = hc_baseof(_s, struct slab, slabs); 141 | hc_release(a->source, s); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /set/README.md: -------------------------------------------------------------------------------- 1 | ## Ordered Sets and Maps 2 | Besides [lists](https://github.com/codr7/hacktical-c/tree/main/list) and [vectors](https://github.com/codr7/hacktical-c/tree/main/vector), some kind of mapping/lookup functionality is often needed. The design described here is based on binary searched vectors. 3 | 4 | Most people would instinctively reach for hash tables, and typically spend the next few months researching optimal hash algorithms and table designs. Most hash tables need to be resized at some point, leading to GC-like dips in performance. And no matter what hash algorithms you use, you will eventually run into issues with values clustering on a minority of buckets. 5 | 6 | A binary searched vector is as simple as it gets and performs pretty well while being more predictable, and since they don't need any infrastructure, they're comparably cheap to create. The case you want to avoid with a binary searched set is inserting items in reverse order, since that maximises the amount of work performed. 7 | 8 | What we're actually going to build is an ordered (multi-)set; but since it's value based, maps are easily implemented on top with low overhead. 9 | 10 | Example: 11 | ```C 12 | struct map_item { 13 | int k, v; 14 | }; 15 | 16 | enum hc_order cmp(const void *x, const void *y) { 17 | return hc_cmp(*(const int *)x, *(const int *)y); 18 | } 19 | 20 | const void *key(const void *x) { 21 | return &((const struct map_item *)x)->k; 22 | } 23 | 24 | const int n = 10; 25 | struct hc_set s; 26 | hc_set_init(&s, &hc_malloc_default, sizeof(struct map_item), cmp); 27 | 28 | for (int i = 0; i < n; i++) { 29 | struct map_item *it = hc_set_add(&s, &i, false); 30 | *it = (struct map_item){.k = i, .v = i}; 31 | } 32 | 33 | for (int i = 0; i < n; i++) { 34 | struct map_item *it = hc_set_find(&s, &i); 35 | assert(it); 36 | assert(it->k == i); 37 | assert(it->v == i); 38 | } 39 | 40 | hc_set_deinit(&s); 41 | ``` 42 | 43 | A custom enum and convenience macro for comparisons is provided. 44 | 45 | ```C 46 | #define hc_cmp(x, y) ({ 47 | __auto_type _x = x; 48 | __auto_type _y = y; 49 | (_x < _y) ? HC_LT : ((_x > _y) ? HC_GT : HC_EQ); 50 | }) 51 | 52 | enum hc_order {HC_LT = -1, HC_EQ = 0, HC_GT = 1}; 53 | 54 | typedef enum hc_order (*hc_cmp_t)(const void *, const void *); 55 | ``` 56 | 57 | Besides a comparator for items, sets also support an optional key accessor. 58 | 59 | ```C 60 | struct hc_set { 61 | struct hc_vector items; 62 | hc_cmp_t cmp; 63 | hc_set_key_t key; 64 | }; 65 | 66 | struct hc_set *hc_set_init(struct hc_set *s, 67 | struct hc_malloc *malloc, 68 | size_t item_size, 69 | hc_cmp_t cmp) { 70 | hc_vector_init(&s->items, malloc, item_size); 71 | s->cmp = cmp; 72 | s->key = NULL; 73 | return s; 74 | } 75 | 76 | void hc_set_deinit(struct hc_set *s) { 77 | hc_vector_deinit(&s->items); 78 | } 79 | ``` 80 | 81 | Most of the weight is pulled by `hc_set_index()`; which returns the index for a key, and optionally sets a flag if the key is in the set. 82 | 83 | ```C 84 | size_t hc_set_index(struct hc_set *s, void *key, bool *ok) { 85 | size_t min = 0, max = s->items.length; 86 | 87 | while (min < max) { 88 | const size_t i = (min+max)/2; 89 | const void *v = hc_vector_get(&s->items, i); 90 | const void *k = s->key ? s->key(v) : v; 91 | 92 | switch (s->cmp(key, k)) { 93 | case HC_LT: 94 | max = i; 95 | break; 96 | case HC_GT: 97 | min = i+1; 98 | break; 99 | default: 100 | if (ok) { 101 | *ok = true; 102 | } 103 | 104 | return i; 105 | } 106 | } 107 | 108 | return min; 109 | } 110 | ``` 111 | 112 | `hc_set_add()` adds an item with the specified key, provided it's not already in the set or the `force`-flag is `true`. A pointer to the added item is returned. 113 | 114 | ```C 115 | void *hc_set_add(struct hc_set *s, void *key, bool force) { 116 | bool ok = false; 117 | const size_t i = hc_set_index(s, key, &ok); 118 | 119 | if (ok && !force) { 120 | return NULL; 121 | } 122 | 123 | return hc_vector_insert(&s->items, i, 1); 124 | 125 | } 126 | ``` 127 | 128 | `hc_set_find()` returns the item with the specified key if found, otherwise `NULL`. 129 | 130 | ```C 131 | void *hc_set_find(struct hc_set *s, void *key) { 132 | bool ok = false; 133 | const size_t i = hc_set_index(s, key, &ok); 134 | return ok ? hc_vector_get(&s->items, i) : NULL; 135 | } 136 | ``` -------------------------------------------------------------------------------- /vm/vm.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "error/error.h" 8 | #include "macro/macro.h" 9 | #include "vm.h" 10 | 11 | struct hc_sloc hc_sloc(const char *source, const int row, const int col) { 12 | struct hc_sloc s = {.source = {0}, .row = row, .col = col}; 13 | assert(strlen(source) < sizeof(s.source)); 14 | strcpy(s.source, source); 15 | return s; 16 | } 17 | 18 | const char *hc_sloc_string(struct hc_sloc *sloc) { 19 | snprintf(sloc->out, sizeof(sloc->out), "'%s'; row %d, column %d", 20 | sloc->source, sloc->row, sloc->col); 21 | return sloc->out; 22 | } 23 | 24 | 25 | void hc_vm_init(struct hc_vm *vm, struct hc_malloc *malloc) { 26 | hc_vector_init(&vm->stack, malloc, sizeof(struct hc_value)); 27 | hc_vector_init(&vm->ops, malloc, sizeof(const struct hc_op *)); 28 | hc_vector_init(&vm->code, malloc, sizeof(hc_op_eval_t)); 29 | } 30 | 31 | static size_t op_items(const struct hc_op *op, 32 | uint8_t *p, 33 | struct hc_vm *vm) { 34 | const size_t s = op->size + hc_align(p, op->align) - p; 35 | return ceil(s / (double)vm->code.item_size); 36 | } 37 | 38 | static void deinit_stack(struct hc_vm *vm) { 39 | hc_vector_do(&vm->stack, v) { 40 | hc_value_deinit(v); 41 | } 42 | 43 | hc_vector_deinit(&vm->stack); 44 | } 45 | 46 | static void deinit_ops(struct hc_vm *vm) { 47 | uint8_t *p = vm->code.start; 48 | 49 | hc_vector_do(&vm->ops, _op) { 50 | const struct hc_op *op = *(const struct hc_op **)_op; 51 | p += sizeof(hc_op_eval_t); 52 | 53 | if (op->deinit) { 54 | op->deinit(hc_align(p, op->align)); 55 | } 56 | 57 | p += op_items(op, p, vm) * vm->code.item_size; 58 | } 59 | 60 | hc_vector_deinit(&vm->ops); 61 | } 62 | 63 | void hc_vm_deinit(struct hc_vm *vm) { 64 | deinit_stack(vm); 65 | deinit_ops(vm); 66 | hc_vector_deinit(&vm->code); 67 | } 68 | 69 | struct hc_value *hc_vm_push(struct hc_vm *vm) { 70 | return hc_vector_push(&vm->stack); 71 | } 72 | 73 | struct hc_value *hc_vm_peek(struct hc_vm *vm) { 74 | return hc_vector_peek(&vm->stack); 75 | } 76 | 77 | struct hc_value *hc_vm_pop(struct hc_vm *vm) { 78 | return hc_vector_pop(&vm->stack); 79 | } 80 | 81 | size_t hc_vm_emit(struct hc_vm *vm, 82 | const struct hc_op *op, 83 | const void *data) { 84 | *(const struct hc_op **)hc_vector_push(&vm->ops) = op; 85 | const size_t pc = vm->code.length; 86 | *(hc_op_eval_t *)hc_vector_push(&vm->code) = op->eval; 87 | 88 | uint8_t *const p = hc_vector_insert(&vm->code, 89 | vm->code.length, 90 | op_items(op, vm->code.end, vm)); 91 | 92 | memcpy(hc_align(p, op->align), data, op->size); 93 | return pc; 94 | } 95 | 96 | void hc_vm_eval(struct hc_vm *vm, 97 | const size_t start_pc, 98 | const size_t end_pc) { 99 | const uint8_t *const ep = (end_pc == -1) 100 | ? vm->code.end 101 | : hc_vector_get(&vm->code, end_pc); 102 | 103 | for (uint8_t *p = hc_vector_get(&vm->code, start_pc); 104 | p != ep; 105 | p = (*(hc_op_eval_t *)p)(vm, p + vm->code.item_size)); 106 | } 107 | 108 | static void fun_print(const struct hc_value *v, struct hc_stream *out) { 109 | hc_printf(out, "%p", v->as_other); 110 | } 111 | 112 | const struct hc_type HC_VM_FUN = { 113 | .name = "VM/Fun", 114 | .copy = NULL, 115 | .print = fun_print 116 | }; 117 | 118 | static uint8_t *call_eval(struct hc_vm *vm, uint8_t *data) { 119 | struct hc_call_op *op = (void *)hc_align(data, alignof(struct hc_call_op)); 120 | op->target(vm, op->sloc); 121 | return (uint8_t *)op + sizeof(struct hc_call_op); 122 | } 123 | 124 | const struct hc_op HC_CALL = (struct hc_op){ 125 | .name = "call", 126 | .align = alignof(struct hc_call_op), 127 | .size = sizeof(struct hc_call_op), 128 | .eval = call_eval, 129 | .deinit = NULL 130 | }; 131 | 132 | static void push_deinit(uint8_t *data) { 133 | struct hc_push_op *op = (void *)data; 134 | hc_value_deinit(&op->value); 135 | } 136 | 137 | static uint8_t *push_eval(struct hc_vm *vm, uint8_t *data) { 138 | struct hc_push_op *op = (void *)hc_align(data, alignof(struct hc_push_op)); 139 | hc_value_copy(hc_vm_push(vm), &op->value); 140 | return (uint8_t *)op + sizeof(struct hc_push_op); 141 | } 142 | 143 | const struct hc_op HC_PUSH = (struct hc_op){ 144 | .name = "push", 145 | .align = alignof(struct hc_push_op), 146 | .size = sizeof(struct hc_push_op), 147 | .eval = push_eval, 148 | .deinit = push_deinit 149 | }; 150 | -------------------------------------------------------------------------------- /fix/README.md: -------------------------------------------------------------------------------- 1 | ## Fixed-Point Arithmetic 2 | Fixed-point is a method of representing fractional values by storing a fixed number of digits of the fractional part, accomplished by multiplying with a fixed power of ten. This means they can be stored and processed much like regular integers. Floating point values are more complicated and inconsistent to deal with. As a result it's often recommended to use fixed-points when dealing with numbers that need to be exact, for instance time and money. 3 | 4 | Fixed-points are normally implemented by simply multiplying all values by a static power of ten. The design described here trades some performance for flexibility and safety by remembering the exponent used to create the value and supporting operations on values with different exponents. All arithmetic operations use the precision of the left hand argument for their result. 5 | 6 | We use unsigned 64-bit integers; 3 bits for the exponent (power of 10), 1 for sign and the remaining 60 bits for the value. 7 | 8 | ```C 9 | #define HC_FIX_EXP 3 10 | #define HC_FIX_HDR (HC_FIX_EXP+1) 11 | 12 | #define HC_FIX_MAX_EXP 7 13 | 14 | typedef uint64_t hc_fix_t; 15 | ``` 16 | 17 | The constructor takes an exponent and a pre-scaled, signed value. 18 | 19 | ```C 20 | hc_fix_t hc_fix(uint8_t exp, int64_t val) { 21 | return (hc_fix_t)hc_bitmask(exp, HC_FIX_EXP) + 22 | (hc_fix_t)(((val < 0) ? 1 : 0) << HC_FIX_EXP) + 23 | (hc_fix_t)(hc_abs(val) << HC_FIX_HDR); 24 | } 25 | ``` 26 | 27 | Example: 28 | ```C 29 | hc_fix_t x = hc_fix(2, -125); 30 | assert(hc_fix_exp(x) == 2); 31 | assert(hc_fix_val(x) == -125); 32 | ``` 33 | 34 | `hc_scale()` converts exponents to multipliers. 35 | 36 | ```C 37 | uint32_t hc_scale(const uint8_t exp) { 38 | static const uint32_t scale[HC_FIX_MAX_EXP+1] = { 39 | 1, 40 | 10, 41 | 100, 42 | 1000, 43 | 10000, 44 | 100000, 45 | 1000000, 46 | 10000000}; 47 | 48 | assert(exp <= HC_FIX_MAX_EXP); 49 | return scale[exp]; 50 | } 51 | ``` 52 | 53 | `hc_fix_int()` and `hc_fix_frac()` return signed integer and fractional parts respectively. 54 | 55 | ```C 56 | int64_t hc_fix_int(hc_fix_t x) { 57 | return hc_fix_val(x) / hc_scale(hc_fix_exp(x)); 58 | } 59 | 60 | int64_t hc_fix_frac(hc_fix_t x) { 61 | const int64_t xv = hc_fix_val(x); 62 | const uint32_t xs = hc_scale(hc_fix_exp(x)); 63 | return xv - (xv / xs) * xs; 64 | } 65 | ``` 66 | 67 | Example: 68 | ```C 69 | const hc_fix_t x = hc_fix(2, -125); 70 | assert(hc_fix_int(x) == -1); 71 | assert(hc_fix_frac(x) == -25); 72 | ``` 73 | 74 | `hc_fix_double()` converts to signed double precision floating point. 75 | 76 | ```C 77 | double hc_fix_t_double(hc_fix_t x) { 78 | return hc_fix_val(x) / (double)hc_scale(hc_fix_exp(x)); 79 | } 80 | ``` 81 | 82 | Example: 83 | ```C 84 | hc_fix_t x = hc_fix(2, -125); 85 | assert(hc_fix_double(x) == -1.25); 86 | ``` 87 | 88 | Addition and subtraction are identical except for the operator, the left hand side exponent is preserved. A fast path is provided for the case when both values share the same exponent. 89 | 90 | ```C 91 | hc_fix_t hc_fix_add(hc_fix_t x, hc_fix_t y) { 92 | const uint8_t xe = hc_fix_exp(x); 93 | const uint8_t ye = hc_fix_exp(y); 94 | 95 | if (xe == ye) { 96 | return hc_fix(xe, hc_fix_val(x) + hc_fix_val(y)); 97 | } 98 | 99 | return hc_fix(xe, hc_fix_val(x) + 100 | hc_fix_val(y) * hc_scale(xe) / hc_scale(ye)); 101 | } 102 | ``` 103 | 104 | ```C 105 | hc_fix_t hc_fix_sub(hc_fix_t x, hc_fix_t y) { 106 | const uint8_t xe = hc_fix_exp(x); 107 | const uint8_t ye = hc_fix_exp(y); 108 | 109 | if (xe == ye) { 110 | return hc_fix(xe, hc_fix_val(x) - hc_fix_val(y)); 111 | } 112 | 113 | return hc_fix(xe, hc_fix_val(x) - 114 | hc_fix_val(y) * hc_scale(xe) / hc_scale(ye)); 115 | } 116 | ``` 117 | 118 | Example: 119 | ```C 120 | hc_fix_t x = hc_fix(2, 175); 121 | hc_fix_t y = hc_fix(2, 25); 122 | assert(hc_fix_add(x, y) == hc_fix(2, 200)); 123 | ``` 124 | 125 | Multiplication and division are likewise identical except for the operator. 126 | 127 | ```C 128 | hc_fix_t hc_fix_mul(hc_fix_t x, hc_fix_t y) { 129 | return hc_fix(hc_fix_exp(x), hc_fix_val(x) * 130 | hc_fix_val(y) / hc_scale(hc_fix_exp(y))); 131 | } 132 | ``` 133 | 134 | ```C 135 | hc_fix_t hc_fix_div(hc_fix_t x, hc_fix_t y) { 136 | return hc_fix(hc_fix_exp(x), hc_fix_val(x) / 137 | hc_fix_val(y) / hc_scale(hc_fix_exp(y))); 138 | } 139 | ``` 140 | 141 | Example: 142 | ```C 143 | hc_fix_t x = hc_fix(2, 150); 144 | hc_fix_t y = hc_fix(1, 5); 145 | assert(hc_fix_mul(x, y) == hc_fix(2, 75)); 146 | ``` -------------------------------------------------------------------------------- /reflect/README.md: -------------------------------------------------------------------------------- 1 | ## Reflection 2 | Reflection is the ability of a program to examine and introspect its own structure and behavior. Since C lacks built-in support, we're going to add a thin layer of types and values on top. By itself, the functionality described here doesn't look very useful, but it enables implementing [other](https://github.com/codr7/hacktical-c/tree/main/slog) [ideas](https://github.com/codr7/hacktical-c/tree/main/dsl). 3 | 4 | Example: 5 | ```C 6 | struct hc_value v; 7 | hc_value_init(&v, &HC_STRING)->as_string = strdup("foo"); 8 | hc_defer(hc_value_deinit(&v)); 9 | struct hc_value c; 10 | hc_value_copy(&c, &v); 11 | hc_defer(hc_value_deinit(&c)); 12 | assert(strcmp(c.as_string, v.as_string) == 0); 13 | ``` 14 | 15 | A type contains a name and a set of basic operations we want to be able to perform polymorphically. 16 | 17 | ```C 18 | struct hc_type { 19 | const char *name; 20 | 21 | void (*copy)(struct hc_value *dst, struct hc_value *src); 22 | void (*deinit)(struct hc_value *); 23 | void (*print)(const struct hc_value *, struct hc_stream *out); 24 | void (*write)(const struct hc_value *, struct hc_stream *out); 25 | }; 26 | ``` 27 | 28 | Values are implemented as tagged unions, with the option to store other kinds of values as `void *`. 29 | 30 | ```C 31 | struct hc_value { 32 | const struct hc_type *type; 33 | 34 | union { 35 | bool as_bool; 36 | hc_fix_t as_fix; 37 | int as_int; 38 | void *as_other; 39 | char *as_string; 40 | hc_time_t as_time; 41 | }; 42 | }; 43 | ``` 44 | 45 | `deinit` is optional. 46 | 47 | ```C 48 | void hc_value_deinit(struct hc_value *v) { 49 | if (v->type->deinit) { 50 | v->type->deinit(v); 51 | } 52 | } 53 | ``` 54 | 55 | `copy` is optional and defaults to bit-wise. 56 | 57 | ```C 58 | struct hc_value *hc_value_copy(struct hc_value *dst, struct hc_value *src) { 59 | const struct hc_type *t = src->type; 60 | 61 | if (t->copy) { 62 | dst->type = t; 63 | t->copy(dst, src); 64 | } else { 65 | *dst = *src; 66 | } 67 | 68 | return dst; 69 | } 70 | ``` 71 | 72 | We'll add both raw (`write`) and formatted (`print`) output, formatted defaults to raw. 73 | 74 | ```C 75 | void hc_value_print(struct hc_value *v, struct hc_stream *out) { 76 | if (v->type->print) { 77 | v->type->print(v, out); 78 | } else { 79 | hc_value_write(v, out); 80 | } 81 | } 82 | ``` 83 | 84 | The standard types are defined as constants. 85 | 86 | Booleans: 87 | ```C 88 | static void bool_write(const struct hc_value *v, struct hc_stream *out) { 89 | hc_puts(out, v->as_bool ? "true" : "false"); 90 | } 91 | 92 | const struct hc_type HC_BOOL = { 93 | .name = "Bool", 94 | .copy = NULL, 95 | .write = bool_write 96 | }; 97 | ``` 98 | 99 | [Fixed-points](https://github.com/codr7/hacktical-c/tree/main/fix): 100 | 101 | ```C 102 | static void fix_write(const struct hc_value *v, struct hc_stream *out) { 103 | hc_fix_print(v->as_fix, out); 104 | } 105 | 106 | const struct hc_type HC_FIX = { 107 | .name = "Fix", 108 | .copy = NULL, 109 | .write = fix_write 110 | }; 111 | ``` 112 | 113 | Integers: 114 | 115 | ```C 116 | static void int_write(const struct hc_value *v, struct hc_stream *out) { 117 | hc_printf(out, "%d", v->as_int); 118 | } 119 | 120 | const struct hc_type HC_INT = { 121 | .name = "Int", 122 | .copy = NULL, 123 | .write = int_write 124 | }; 125 | ``` 126 | 127 | The string type stands out in two ways; values are dynamically allocated, and it uses different syntax suitable for programmatic reading when being written to a stream. 128 | 129 | ```C 130 | static void string_copy(struct hc_value *dst, struct hc_value *src) { 131 | dst->as_string = strdup(src->as_string); 132 | } 133 | 134 | static void string_deinit(struct hc_value *v) { 135 | free(v->as_string); 136 | } 137 | 138 | static void string_print(const struct hc_value *v, struct hc_stream *out) { 139 | hc_puts(out, v->as_string); 140 | } 141 | 142 | static void string_write(const struct hc_value *v, struct hc_stream *out) { 143 | hc_putc(out, '"'); 144 | string_print(v, out); 145 | hc_putc(out, '"'); 146 | } 147 | 148 | const struct hc_type HC_STRING = { 149 | .name = "String", 150 | .copy = string_copy, 151 | .deinit = string_deinit, 152 | .print = string_print, 153 | .write = string_write 154 | }; 155 | ``` 156 | [Time](https://github.com/codr7/hacktical-c/tree/main/chrono): 157 | 158 | ```C 159 | static void time_write(const struct hc_value *v, struct hc_stream *out) { 160 | hc_time_printf(&v->as_time, HC_TIME_FORMAT, out); 161 | } 162 | 163 | const struct hc_type HC_TIME = { 164 | .name = "Time", 165 | .copy = NULL, 166 | .write = time_write 167 | }; 168 | ``` -------------------------------------------------------------------------------- /error/README.md: -------------------------------------------------------------------------------- 1 | ## Exceptions 2 | How to best deal with errors is something we're still very much figuring out in software. But it is clear to me that there will always be a place for different strategies depending on needs. 3 | 4 | The main issue with exceptions is that they transfer control in difficult to predict ways, and the main advantage is that they allow dealing with errors at the correct level without manually propagating them. 5 | 6 | C lacks native exception support, so we're going to roll our own using `setjmp` and `longjmp`. `setjmp` saves the current execution context into a variable of type `jmp_buf` and returns `0` the first time, and `longjmp` restores it which causes a second return from `setjmp` with the specified value. 7 | 8 | Example: 9 | ```C 10 | void on_catch(struct hc_error *e) { 11 | printf(e->message); 12 | free(e); 13 | } 14 | 15 | hc_catch(on_catch) { 16 | hc_throw("E123 Going %s", "Down!"); 17 | } 18 | ``` 19 | 20 | Output: 21 | ``` 22 | Error in 'error/tests.c', line 14: 23 | E123 Going Down! 24 | ``` 25 | 26 | We use a `for`-loop to push/pop handlers around the catch body, a neat trick for whenever you need to do something after a user defined block of code in macro context. 27 | 28 | ```C 29 | #define _hc_catch(_e, _f, h) 30 | jmp_buf _e; 31 | bool _f = true; 32 | if (setjmp(_e)) { 33 | h(hc_error); 34 | } else for (hc_catch_push(_e); _f; _f = false, hc_catch_pop()) 35 | 36 | #define hc_catch(h) 37 | _hc_catch(hc_unique(env), hc_unique(flag), h) 38 | ``` 39 | 40 | A static thread-local `struct hc_vector` stores currently active handlers. Note that the only way to deinitialize it is by manually calling `hc_errors_deinit()` at the end of every thread. 41 | 42 | ```C 43 | static struct hc_vector *handlers() { 44 | static bool init = true; 45 | static __thread struct hc_vector handlers; 46 | 47 | if (init) { 48 | hc_vector_init(&handlers, &hc_malloc_default, sizeof(jmp_buf)); 49 | init = false; 50 | } 51 | 52 | return &handlers; 53 | } 54 | 55 | void hc_catch_push(jmp_buf h) { 56 | memcpy((jmp_buf *)hc_vector_push(handlers()), h, sizeof(jmp_buf)); 57 | } 58 | 59 | void hc_catch_pop() { 60 | hc_vector_pop(handlers()); 61 | } 62 | 63 | void hc_errors_deinit() { 64 | hc_vector_deinit(handlers()); 65 | } 66 | ``` 67 | 68 | `hc_throw()` takes a `printf`-compatable format and argument list. We're taking advantage of the fact that adjacent string literals are automagically concatenated by the compiler to add context. 69 | 70 | ```C 71 | #define hc_throw(m, ...) { 72 | struct hc_error *e = hc_error_new("Failure in '%s', line %d:\n" m "\n", 73 | __FILE__, __LINE__, ##__VA_ARGS__); 74 | _hc_throw(e); 75 | } do while(0) 76 | 77 | void _hc_throw(struct hc_error *e) { 78 | struct hc_vector *hs = handlers(); 79 | 80 | if (!hs->length) { 81 | fputs(e->message, stderr); 82 | abort(); 83 | } 84 | 85 | jmp_buf t; 86 | memcpy(t, *(jmp_buf *)hc_vector_peek(hs), sizeof(jmp_buf)); 87 | hc_error = e; 88 | longjmp(t, 1); 89 | } 90 | ``` 91 | 92 | Errors are defined as follows. 93 | 94 | ```C 95 | struct hc_error { 96 | char *message[]; 97 | }; 98 | ``` 99 | 100 | ### Vararg Functions 101 | From this point on we're going to be defining a fair amount of vararg functions. Like many other features, they require a tiny bit more discipline in C compared to most other languages. 102 | 103 | `va_start` initializes a vararg, it expects the final argument in second position. A `va_list` can only be consumed once, since each call to `va_arg()` modifies the list so that the next call returns the next argument. Due to this it's not possible to retrieve an argument more than once. In the following example we use `va_copy` to get around the problem by duplicating the list. 104 | 105 | Back in error land; calling `vsnprintf` with a `NULL` argument returns the message length, which we need to allocate memory for the error. 106 | 107 | ```C 108 | struct hc_error *hc_error_new(const char *message, ...) { 109 | struct hc_error *e = malloc(sizeof(struct hc_error)); 110 | va_list args; 111 | va_start(args, message); 112 | 113 | va_list tmp_args; 114 | va_copy(tmp_args, args); 115 | int len = vsnprintf(NULL, 0, message, tmp_args); 116 | va_end(tmp_args); 117 | 118 | if (len < 0) { 119 | vfprintf(stderr, message, args); 120 | abort(); 121 | } 122 | 123 | len++; 124 | e->message = malloc(len); 125 | vsnprintf(e->message, len, message, args); 126 | va_end(args); 127 | return e; 128 | } 129 | ``` 130 | 131 | ### Cleaning Up 132 | Keep in mind that any code that interacts with exceptions has to use `hc_defer()` or similar functionality to clean up properly. -------------------------------------------------------------------------------- /epub/styles.css: -------------------------------------------------------------------------------- 1 | /* This defines styles and classes used in the book */ 2 | @page { 3 | margin: 10px; 4 | } 5 | html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, 6 | blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, 7 | ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, 8 | fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, 9 | article, aside, canvas, details, embed, figure, figcaption, footer, header, 10 | hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video, ol, 11 | ul, li, dl, dt, dd { 12 | margin: 0; 13 | padding: 0; 14 | border: 0; 15 | font-size: 100%; 16 | vertical-align: baseline; 17 | } 18 | html { 19 | line-height: 1.2; 20 | font-family: Georgia, serif; 21 | color: #1a1a1a; 22 | } 23 | p { 24 | text-indent: 0; 25 | margin: 1em 0; 26 | widows: 2; 27 | orphans: 2; 28 | } 29 | a, a:visited { 30 | color: #1a1a1a; 31 | } 32 | img { 33 | max-width: 100%; 34 | } 35 | sup { 36 | vertical-align: super; 37 | font-size: smaller; 38 | } 39 | sub { 40 | vertical-align: sub; 41 | font-size: smaller; 42 | } 43 | h1 { 44 | margin: 3em 0 0 0; 45 | font-size: 2em; 46 | page-break-before: always; 47 | line-height: 150%; 48 | } 49 | h2 { 50 | margin: 1.5em 0 0 0; 51 | font-size: 1.5em; 52 | line-height: 135%; 53 | } 54 | h3 { 55 | margin: 1.3em 0 0 0; 56 | font-size: 1.3em; 57 | } 58 | h4 { 59 | margin: 1.2em 0 0 0; 60 | font-size: 1.2em; 61 | } 62 | h5 { 63 | margin: 1.1em 0 0 0; 64 | font-size: 1.1em; 65 | } 66 | h6 { 67 | font-size: 1em; 68 | } 69 | h1, h2, h3, h4, h5, h6 { 70 | text-indent: 0; 71 | text-align: left; 72 | font-weight: bold; 73 | page-break-after: avoid; 74 | page-break-inside: avoid; 75 | } 76 | 77 | ol, ul { 78 | margin: 1em 0 0 1.7em; 79 | } 80 | li > ol, li > ul { 81 | margin-top: 0; 82 | } 83 | blockquote { 84 | margin: 1em 0 1em 1.7em; 85 | } 86 | code { 87 | font-family: Menlo, Monaco, 'Lucida Console', Consolas, monospace; 88 | font-size: 80%; 89 | margin: 0; 90 | hyphens: manual; 91 | background-color: #eee; 92 | } 93 | pre { 94 | margin: 1em 0; 95 | overflow: auto; 96 | } 97 | pre code { 98 | padding: 0; 99 | overflow: visible; 100 | overflow-wrap: normal; 101 | } 102 | .sourceCode { 103 | background-color: #eee; 104 | overflow: visible; 105 | font-size: 90%; 106 | } 107 | hr { 108 | background-color: #1a1a1a; 109 | border: none; 110 | height: 1px; 111 | margin: 1em 0; 112 | } 113 | table { 114 | margin: 1em 0; 115 | border-collapse: collapse; 116 | width: 100%; 117 | overflow-x: auto; 118 | display: block; 119 | } 120 | table caption { 121 | margin-bottom: 0.75em; 122 | } 123 | tbody { 124 | margin-top: 0.5em; 125 | border-top: 1px solid #1a1a1a; 126 | border-bottom: 1px solid #1a1a1a; 127 | } 128 | th, td { 129 | padding: 0.25em 0.5em 0.25em 0.5em; 130 | } 131 | th { 132 | border-top: 1px solid #1a1a1a; 133 | } 134 | header { 135 | margin-bottom: 4em; 136 | text-align: center; 137 | } 138 | #TOC li { 139 | list-style: none; 140 | } 141 | #TOC ul { 142 | padding-left: 1.3em; 143 | } 144 | #TOC > ul { 145 | padding-left: 0; 146 | } 147 | #TOC a:not(:hover) { 148 | text-decoration: none; 149 | } 150 | code { 151 | white-space: pre-wrap; 152 | } 153 | span.smallcaps { 154 | font-variant: small-caps; 155 | } 156 | 157 | /* This is the most compatible CSS, but it only allows two columns: */ 158 | div.column { 159 | display: inline-block; 160 | vertical-align: top; 161 | width: 50%; 162 | } 163 | /* If you can rely on CSS3 support, use this instead: */ 164 | /* div.columns { 165 | display: flex; 166 | gap: min(4vw, 1.5em); 167 | } 168 | div.column { 169 | flex: auto; 170 | overflow-x: auto; 171 | } */ 172 | 173 | div.hanging-indent { 174 | margin-left: 1.5em; 175 | text-indent: -1.5em; 176 | } 177 | ul.task-list { 178 | list-style: none; 179 | } 180 | ul.task-list li input[type="checkbox"] { 181 | width: 0.8em; 182 | margin: 0 0.8em 0.2em -1.6em; 183 | vertical-align: middle; 184 | } 185 | .display.math { 186 | display: block; 187 | text-align: center; 188 | margin: 0.5rem auto; 189 | } 190 | 191 | /* For title, author, and date on the cover page */ 192 | h1.title { } 193 | p.author { } 194 | p.date { } 195 | 196 | nav#toc ol, nav#landmarks ol { 197 | padding: 0; 198 | margin-left: 1em; 199 | } 200 | nav#toc ol li, nav#landmarks ol li { 201 | list-style-type: none; 202 | margin: 0; 203 | padding: 0; 204 | } 205 | a.footnote-ref { 206 | vertical-align: super; 207 | } 208 | em, em em em, em em em em em { 209 | font-style: italic; 210 | } 211 | em em, em em em em { 212 | font-style: normal; 213 | } 214 | q { 215 | quotes: "“" "”" "‘" "’"; 216 | } 217 | @media screen { /* Workaround for iBooks issue; see #6242 */ 218 | .sourceCode { 219 | overflow: visible !important; 220 | white-space: pre-wrap !important; 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /slog/slog.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "error/error.h" 7 | #include "macro/macro.h" 8 | #include "malloc1/malloc1.h" 9 | #include "slog.h" 10 | 11 | __thread struct hc_slog *_hc_slog = NULL; 12 | 13 | struct hc_slog *hc_slog() { 14 | if (_hc_slog != NULL) { 15 | return _hc_slog; 16 | } 17 | 18 | static __thread bool init = true; 19 | static __thread struct hc_slog_stream s; 20 | 21 | if (init) { 22 | hc_slog_stream_init(&s, hc_stdout()); 23 | init = false; 24 | } 25 | 26 | return &s.slog; 27 | } 28 | 29 | static void field_deinit(struct hc_slog_field *f) { 30 | free(f->name); 31 | hc_value_deinit(&f->value); 32 | } 33 | 34 | static void slog_write(struct hc_slog *s, 35 | const size_t n, 36 | struct hc_slog_field *fields[]) { 37 | assert(s->write); 38 | s->write(s, n, fields); 39 | } 40 | 41 | void __hc_slog_write(struct hc_slog *s, 42 | const size_t n, 43 | struct hc_slog_field *fields[]) { 44 | slog_write(s, n, fields); 45 | 46 | for(size_t i = 0; i < n; i++) { 47 | struct hc_slog_field *f = fields[i]; 48 | field_deinit(f); 49 | free(f); 50 | } 51 | } 52 | 53 | void _hc_slog_deinit(struct hc_slog *s) { 54 | if (s->deinit) { 55 | s->deinit(s); 56 | } 57 | } 58 | 59 | static struct hc_value *field_init(struct hc_slog_field *f, 60 | const char *name, 61 | const struct hc_type *type) { 62 | f->name = strdup(name); 63 | hc_value_init(&f->value, type); 64 | return &f->value; 65 | } 66 | 67 | struct hc_slog_field *hc_slog_bool(const char *name, const bool value) { 68 | struct hc_slog_field *f = malloc(sizeof(struct hc_slog_field)); 69 | field_init(f, name, &HC_BOOL)->as_bool = value; 70 | return f; 71 | } 72 | 73 | struct hc_slog_field *hc_slog_int(const char *name, const int value) { 74 | struct hc_slog_field *f = malloc(sizeof(struct hc_slog_field)); 75 | field_init(f, name, &HC_INT)->as_int = value; 76 | return f; 77 | } 78 | 79 | struct hc_slog_field *hc_slog_string(const char *name, const char *value) { 80 | struct hc_slog_field *f = malloc(sizeof(struct hc_slog_field)); 81 | field_init(f, name, &HC_STRING)->as_string = strdup(value); 82 | return f; 83 | } 84 | 85 | struct hc_slog_field *hc_slog_time(const char *name, const hc_time_t value) { 86 | struct hc_slog_field *f = malloc(sizeof(struct hc_slog_field)); 87 | field_init(f, name, &HC_TIME)->as_time = value; 88 | return f; 89 | } 90 | 91 | void stream_deinit(struct hc_slog *s) { 92 | struct hc_slog_stream *ss = hc_baseof(s, struct hc_slog_stream, slog); 93 | if (ss->opts.close_out) { hc_stream_deinit(ss->out); } 94 | } 95 | 96 | static void field_write(struct hc_slog_field *f, struct hc_stream *out) { 97 | hc_puts(out, f->name); 98 | hc_putc(out, '='); 99 | hc_value_write(&f->value, out); 100 | } 101 | 102 | static void stream_write(struct hc_slog *s, 103 | const size_t n, 104 | struct hc_slog_field *fields[]) { 105 | struct hc_slog_stream *ss = hc_baseof(s, struct hc_slog_stream, slog); 106 | 107 | for(size_t i = 0; i < n; i++) { 108 | struct hc_slog_field *f = fields[i]; 109 | if (i) { hc_puts(ss->out, ", "); } 110 | field_write(f, ss->out); 111 | } 112 | 113 | hc_putc(ss->out, '\n'); 114 | } 115 | 116 | struct hc_slog_stream *_hc_slog_stream_init(struct hc_slog_stream *s, 117 | struct hc_stream *out, 118 | const struct hc_slog_stream_opts opts) { 119 | s->slog.deinit = stream_deinit; 120 | s->slog.write = stream_write; 121 | s->out = out; 122 | s->opts = opts; 123 | return s; 124 | } 125 | 126 | static void context_deinit(struct hc_slog *s) { 127 | struct hc_slog_context *sc = hc_baseof(s, struct hc_slog_context, slog); 128 | 129 | for (size_t i = 0; i < sc->length; i++) { 130 | struct hc_slog_field *f = sc->fields[i]; 131 | field_deinit(f); 132 | free(f); 133 | } 134 | 135 | free(sc->fields); 136 | } 137 | 138 | static void context_write(struct hc_slog *s, 139 | const size_t n, 140 | struct hc_slog_field *fields[]) { 141 | struct hc_slog_context *c = hc_baseof(s, struct hc_slog_context, slog); 142 | struct hc_slog_field *fs[c->length + n]; 143 | memcpy(fs, c->fields, sizeof(struct hc_slog_field *) * c->length); 144 | memcpy(fs + c->length, fields, sizeof(struct hc_slog_field *) * n); 145 | slog_write(c->parent, c->length + n, fs); 146 | } 147 | 148 | struct hc_slog_context *hc_slog_context_init(struct hc_slog_context *c, 149 | size_t length, 150 | struct hc_slog_field *fields[]) { 151 | c->slog.deinit = context_deinit; 152 | c->slog.write = context_write; 153 | c->parent = hc_slog(); 154 | c->length = length; 155 | size_t s = sizeof(struct hc_slog_field *) * length; 156 | c->fields = malloc(s); 157 | memcpy(c->fields, fields, s); 158 | return c; 159 | } 160 | -------------------------------------------------------------------------------- /stream1/stream1.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "dynamic/dynamic.h" 7 | #include "error/error.h" 8 | #include "malloc1/malloc1.h" 9 | #include "macro/macro.h" 10 | #include "stream1.h" 11 | 12 | size_t hc_read(struct hc_stream *s, uint8_t *data, const size_t n) { 13 | assert(s->read); 14 | return s->read(s, data, n); 15 | } 16 | 17 | size_t hc_write(struct hc_stream *s, const uint8_t *data, const size_t n) { 18 | assert(s->write); 19 | return s->write(s, data, n); 20 | } 21 | 22 | char hc_getc(struct hc_stream *s) { 23 | char c = 0; 24 | return hc_read(s, (uint8_t *)&c, 1) ? c : 0; 25 | } 26 | 27 | char *hc_gets(struct hc_stream *s, struct hc_malloc *malloc) { 28 | struct hc_vector out; 29 | hc_vector_init(&out, malloc, 1); 30 | 31 | for (;;) { 32 | char c = hc_getc(s); 33 | 34 | if (c == EOF) { 35 | break; 36 | } 37 | 38 | *(char *)hc_vector_push(&out) = c; 39 | 40 | if (c == '\n') { 41 | break; 42 | } 43 | } 44 | 45 | 46 | *(char *)hc_vector_push(&out) = 0; 47 | return (char *)out.start; 48 | } 49 | 50 | size_t hc_putc(struct hc_stream *s, const char data) { 51 | const uint8_t d[2] = {data, 0}; 52 | return hc_write(s, d, 1); 53 | } 54 | 55 | size_t hc_puts(struct hc_stream *s, const char *data) { 56 | return hc_write(s, (const uint8_t *)data, strlen(data)); 57 | } 58 | 59 | size_t hc_vprintf(struct hc_stream *s, 60 | const char *spec, 61 | va_list args) { 62 | char *data = hc_vsprintf(spec, args); 63 | hc_defer(free(data)); 64 | return hc_write(s, (uint8_t *)data, strlen(data)); 65 | } 66 | 67 | size_t hc_printf(struct hc_stream *s, const char *spec, ...) { 68 | va_list args; 69 | va_start(args, spec); 70 | hc_defer(va_end(args)); 71 | return hc_vprintf(s, spec, args); 72 | } 73 | 74 | void hc_stream_deinit(struct hc_stream *s) { 75 | assert(s->deinit); 76 | s->deinit(s); 77 | } 78 | 79 | size_t file_read(struct hc_stream *s, uint8_t *data, const size_t n) { 80 | struct hc_file_stream *fs = hc_baseof(s, struct hc_file_stream, stream); 81 | assert(fs->file); 82 | return fread(data, n, 1, fs->file); 83 | } 84 | 85 | size_t file_write(struct hc_stream *s, const uint8_t *data, const size_t n) { 86 | struct hc_file_stream *fs = hc_baseof(s, struct hc_file_stream, stream); 87 | assert(fs->file); 88 | return fwrite(data, n, 1, fs->file); 89 | } 90 | 91 | void file_deinit(struct hc_stream *s) { 92 | struct hc_file_stream *fs = hc_baseof(s, struct hc_file_stream, stream); 93 | 94 | if (fs->opts.close_file) { 95 | assert(fs->file); 96 | 97 | if (fclose(fs->file) == EOF) { 98 | hc_throw("Failed closing file"); 99 | } 100 | 101 | fs->file = NULL; 102 | } 103 | } 104 | 105 | struct hc_file_stream *_hc_file_stream_init(struct hc_file_stream *s, 106 | FILE *file, 107 | const struct hc_file_stream_opts opts) { 108 | s->stream = (struct hc_stream){ 109 | .read = file_read, 110 | .write = file_write, 111 | .deinit = file_deinit, 112 | }; 113 | 114 | s->file = file; 115 | s->opts = opts; 116 | return s; 117 | }; 118 | 119 | struct hc_stream *hc_stdout() { 120 | static __thread bool init = true; 121 | static __thread struct hc_file_stream s; 122 | 123 | if (init) { 124 | hc_file_stream_init(&s, stdout); 125 | init = false; 126 | } 127 | 128 | return &s.stream; 129 | } 130 | 131 | size_t memory_read(struct hc_stream *s, uint8_t *data, size_t n) { 132 | struct hc_memory_stream *ms = hc_baseof(s, struct hc_memory_stream, stream); 133 | 134 | if (ms->rpos + n > ms->data.length) { 135 | n = ms->data.length - ms->rpos; 136 | } 137 | 138 | memcpy(data, ms->data.start + ms->rpos, n); 139 | ms->rpos += n; 140 | return n; 141 | } 142 | 143 | size_t memory_write(struct hc_stream *s, 144 | const uint8_t *data, 145 | const size_t n) { 146 | struct hc_memory_stream *ms = hc_baseof(s, struct hc_memory_stream, stream); 147 | uint8_t *const dst = hc_vector_insert(&ms->data, ms->data.length, n); 148 | memcpy(dst, data, n); 149 | return n; 150 | } 151 | 152 | void memory_deinit(struct hc_stream *s) { 153 | struct hc_memory_stream *ms = hc_baseof(s, struct hc_memory_stream, stream); 154 | hc_vector_deinit(&ms->data); 155 | } 156 | 157 | struct hc_memory_stream *hc_memory_stream_init(struct hc_memory_stream *s, 158 | struct hc_malloc *malloc) { 159 | s->stream = (struct hc_stream){ 160 | .read = memory_read, 161 | .write = memory_write, 162 | .deinit = memory_deinit, 163 | }; 164 | 165 | hc_vector_init(&s->data, malloc, 1); 166 | s->rpos = 0; 167 | return s; 168 | } 169 | 170 | const char *hc_memory_stream_string(struct hc_memory_stream *s) { 171 | if (!s->data.length || (*(s->data.end-1))) { 172 | *(uint8_t *)hc_vector_push(&s->data) = 0; 173 | } 174 | 175 | return (const char *)s->data.start; 176 | } 177 | -------------------------------------------------------------------------------- /dynamic/README.md: -------------------------------------------------------------------------------- 1 | ## Dynamic Compilation 2 | To implement dynamic compilation, we'll have to cast a few non-trivial Unix spells and sacrifice some portability. It's not that these features aren't available on other platforms; rather that they're implemented in slightly different ways, using different names. 3 | 4 | Example: 5 | ```C 6 | const char *out = "/var/tmp/libtest.so"; 7 | 8 | hc_compile("#include \n" 9 | "int test() { return 42; }", 10 | out, 11 | .cflags = (const char *[]){"-Wall", "-fsanitize=undefined", NULL}); 12 | 13 | struct hc_dlib lib; 14 | hc_dlib_init(&lib, out); 15 | hc_defer(hc_dlib_deinit(&lib)); 16 | int (*fn)() = hc_dlib_find(&lib, "test"); 17 | assert(fn() == 42); 18 | ``` 19 | 20 | The star of the show is `hc_compile()` which allows dynamically creating shared libraries from source code. 21 | 22 | ```C 23 | struct hc_compile_opts { 24 | const char *cc; 25 | const char **cflags; 26 | }; 27 | 28 | #define hc_compile(code, out, ...) 29 | _hc_compile(code, out, (struct hc_compile_opts){ 30 | .cc = "/usr/bin/gcc", 31 | .cflags = (const char *[]){NULL}, 32 | ##__VA_ARGS__ 33 | }) 34 | 35 | void _hc_compile(const char *code, 36 | const char *out, 37 | const struct hc_compile_opts opts) { 38 | hc_array(const char *, pre, 39 | opts.cc, "-shared", "-fpic", "-o", out, "-xc"); 40 | 41 | int n = pre_n + 2; 42 | for (int i = 0; opts.cflags[i]; i++, n++); 43 | char *cmd[n]; 44 | int i = 0; 45 | 46 | for (; i < pre_n; i++) { 47 | cmd[i] = strdup(pre_a[i]); 48 | } 49 | 50 | for (; i < n - 2; i++) { 51 | cmd[i] = strdup(opts.cflags[i - pre_n]); 52 | } 53 | 54 | cmd[i++] = strdup("-"); 55 | cmd[i] = NULL; 56 | hc_defer(free_cmd(cmd)); 57 | 58 | struct hc_proc child; 59 | _hc_proc_init(&child, cmd); 60 | hc_defer(hc_proc_deinit(&child)); 61 | FILE *stdin = fdopen(child.stdin, "w"); 62 | 63 | if (!stdin) { 64 | hc_throw("Failed opening stdin stream: %d", errno); 65 | } 66 | 67 | child.stdin = -1; 68 | hc_defer(hc_proc_wait(&child)); 69 | hc_defer(fclose(stdin)); 70 | 71 | if (fputs(code, stdin) == EOF) { 72 | hc_throw("Failed writing code: %d", errno); 73 | } 74 | } 75 | 76 | void free_cmd(char **in) { 77 | for (char **s = in; *s; s++) { 78 | free(*s); 79 | } 80 | } 81 | ``` 82 | 83 | Starting an external process with a pipe attached to `stdin` gets a tiny bit involved, but goes someting like this: 84 | 85 | We begin by creating a `pipe()` and `fork()`-ing a new process. 86 | 87 | In the child process, we close the writer, attach the reader to `stdin` using `dup2()` and execute the specified command using `execve()`. 88 | 89 | In the parent we close the reader and initialize the result struct with the PID and writer. 90 | 91 | ```C 92 | struct hc_proc { 93 | int pid; 94 | int stdin; 95 | }; 96 | 97 | #define hc_proc_init(p, ...) \ 98 | _hc_proc_init(p, {__VA_ARGS__, NULL}) 99 | 100 | struct hc_proc *_hc_proc_init(struct hc_proc *p, char *cmd[]) { 101 | int fds[2]; 102 | 103 | if (pipe(fds) == -1) { 104 | hc_throw("Failed creating pipe: %d", errno); 105 | } 106 | 107 | pid_t child_pid = fork(); 108 | 109 | switch (child_pid) { 110 | case 0: { 111 | if (close(fds[1]) == -1) { 112 | hc_throw("Failed closing pipe writer: %d", errno); 113 | } 114 | 115 | if (dup2(fds[0], 0) == -1) { 116 | hc_throw("Failed rebinding stdin: %d", errno); 117 | } 118 | 119 | char *const env[] = {"PATH=/bin:/sbin", NULL}; 120 | 121 | if (execve(cmd[0], cmd, env) == -1) { 122 | hc_throw("Failed to execve '%s': %d", cmd[0], errno); 123 | } 124 | } 125 | case -1: 126 | hc_throw("Failed forking process: %d", errno); 127 | default: 128 | if (close(fds[0]) == -1) { 129 | hc_throw("Failed closing pipe reader: %d", errno); 130 | } 131 | 132 | p->pid = child_pid; 133 | p->stdin = fds[1]; 134 | break; 135 | } 136 | 137 | return p; 138 | } 139 | ``` 140 | 141 | `struct hc_dlib` handles loading shared libraries and looking up symbols. 142 | 143 | ```C 144 | struct hc_dlib { 145 | void *handle; 146 | }; 147 | 148 | struct hc_dlib *hc_dlib_init(struct hc_dlib *lib, const char *path) { 149 | lib->handle = dlopen(path, RTLD_NOW); 150 | 151 | if (!lib->handle) { 152 | hc_throw("Error opening dynamic library '%s': %s", path, dlerror()); 153 | } 154 | 155 | return lib; 156 | } 157 | 158 | struct hc_dlib *hc_dlib_deinit(struct hc_dlib *lib) { 159 | if (dlclose(lib->handle) != 0) { 160 | hc_throw("Failed closing dynamic library: ", dlerror()); 161 | } 162 | 163 | return lib; 164 | } 165 | 166 | void *hc_dlib_find(const struct hc_dlib *lib, const char *s) { 167 | dlerror(); 168 | void *v = dlsym(lib->handle, s); 169 | char *e = dlerror(); 170 | 171 | if (e) { 172 | hc_throw("Symbol '%s' not found: %s", e); 173 | } 174 | 175 | return v; 176 | } 177 | ``` -------------------------------------------------------------------------------- /vm/README.md: -------------------------------------------------------------------------------- 1 | ## Virtual Machines 2 | A virtual machine (VM) is a machine emulated using software; commonly used to implement programming languages and emulators. 3 | 4 | Virtual machines come in two main flavors; stack based like Forth, Java or Python; and register based like Lua or Erlang. 5 | 6 | Stack based machines use smaller instructions, since the stack takes care of addressing; on the other hand they require evaluating more operations to reorder values on the stack. Register based machines keep values in slots and use wider instructions that contain the addresses they operate on, on the other hand they need to allocate registers. 7 | 8 | Here we will build a simple stack based machine. For reasons that will be explained shortly, we'll store the operations and the corresponding code to be evaluated separately. 9 | 10 | ```C 11 | struct hc_vm { 12 | struct hc_vector stack; 13 | struct hc_vector ops; 14 | struct hc_vector code; 15 | }; 16 | 17 | void hc_vm_init(struct hc_vm *vm, struct hc_malloc *malloc) { 18 | hc_vector_init(&vm->stack, malloc, sizeof(struct hc_value)); 19 | hc_vector_init(&vm->ops, malloc, sizeof(const struct hc_op *)); 20 | hc_vector_init(&vm->code, malloc, sizeof(hc_op_eval_t)); 21 | } 22 | ``` 23 | 24 | Each operation contains a name, alignment and size, and function pointers for evaluation and cleanup. 25 | 26 | ```C 27 | typedef uint8_t *(*hc_op_eval_t)(struct hc_vm *, uint8_t *); 28 | 29 | struct hc_op { 30 | const char *name; 31 | 32 | size_t align; 33 | size_t size; 34 | 35 | hc_op_eval_t eval; 36 | void (*deinit)(uint8_t *); 37 | }; 38 | ``` 39 | 40 | The evaluation loop is by far the most performance critical part of a virtual machine, since it executes code for each and every operation. The following implementation has the added advantage of not statically limiting the set of available operations, which enables extending the machine from user code. 41 | 42 | ```C 43 | void hc_vm_eval(struct hc_vm *vm, 44 | size_t start_pc, 45 | size_t end_pc) { 46 | uint8_t *ep = (end_pc == -1) 47 | ? vm->code.end 48 | : hc_vector_get(&vm->code, end_pc); 49 | 50 | for (uint8_t *p = hc_vector_get(&vm->code, start_pc); 51 | p != ep; 52 | p = (*(hc_op_eval_t *)p)(vm, p + vm->code.item_size)); 53 | } 54 | ``` 55 | 56 | This brings us back to the question of why we store evaluated code separately. The evaluation loop is very dependent on memory locality, which means we want to store the minimum amount of data that's absolutely needed to evaluate an operation quickly. This allows the CPU to cache larger chunks of the code in one go. 57 | 58 | `hc_vm_emit()` adds operations to `ops` and `code`, aligning the `code`-part separately (`code` itself is aligned by `sizeof(hc_eval_fn_t)`). 59 | 60 | ```C 61 | size_t hc_vm_emit(struct hc_vm *vm, 62 | struct hc_op *op, 63 | void *data) { 64 | *(struct hc_op **)hc_vector_push(&vm->ops) = op; 65 | size_t pc = vm->code.length; 66 | *(hc_op_eval_t *)hc_vector_push(&vm->code) = op->eval; 67 | 68 | uint8_t *p = hc_vector_insert(&vm->code, 69 | vm->code.length, 70 | op_items(op, vm->code.end, vm)); 71 | 72 | memcpy(hc_align(p, op->align), data, op->size); 73 | return pc; 74 | } 75 | 76 | size_t op_items(const struct hc_op *op, 77 | uint8_t *p, 78 | struct hc_vm *vm) { 79 | const size_t s = op->size + hc_align(p, op->align) - p; 80 | return ceil(s / (double)vm->code.item_size); 81 | } 82 | ``` 83 | 84 | The `push` operation pushes a [value](https://github.com/codr7/hacktical-c/tree/main/reflect) on the stack. 85 | 86 | ```C 87 | struct hc_push_op { 88 | struct hc_value value; 89 | }; 90 | 91 | static void push_deinit(uint8_t *data) { 92 | struct hc_push_op *op = (void *)data; 93 | hc_value_deinit(&op->value); 94 | } 95 | 96 | static uint8_t *push_eval(struct hc_vm *vm, uint8_t *data) { 97 | struct hc_push_op *op = (void *)hc_align(data, alignof(struct hc_push_op)); 98 | hc_value_copy(hc_vm_push(vm), &op->value); 99 | return (uint8_t *)op + sizeof(struct hc_push_op); 100 | } 101 | 102 | const struct hc_op HC_PUSH = (struct hc_op){ 103 | .name = "push", 104 | .align = alignof(struct hc_push_op), 105 | .size = sizeof(struct hc_push_op), 106 | .eval = push_eval, 107 | .deinit = push_deinit 108 | }; 109 | ``` 110 | 111 | The `call` operation is used to call C functions. 112 | 113 | ```C 114 | typedef void (*hc_vm_fun_t)(struct hc_vm *, struct hc_sloc); 115 | 116 | struct hc_call_op { 117 | hc_vm_fun_t target; 118 | struct hc_sloc sloc; 119 | }; 120 | 121 | static uint8_t *call_eval(struct hc_vm *vm, uint8_t *data) { 122 | struct hc_call_op *op = (void *)hc_align(data, alignof(struct hc_call_op)); 123 | op->target(vm, op->sloc); 124 | return (uint8_t *)op + sizeof(struct hc_call_op); 125 | } 126 | 127 | const struct hc_op HC_CALL = (struct hc_op){ 128 | .name = "call", 129 | .align = alignof(struct hc_call_op), 130 | .size = sizeof(struct hc_call_op), 131 | .eval = call_eval, 132 | .deinit = NULL 133 | }; 134 | ``` 135 | 136 | Calls include the source location where the call was made to enable better error reporting. 137 | 138 | ```C 139 | struct hc_sloc { 140 | char source[32]; 141 | char out[64]; 142 | int row; 143 | int col; 144 | }; 145 | 146 | struct hc_sloc hc_sloc(const char *source, const int row, const int col) { 147 | struct hc_sloc s = {.source = {0}, .row = row, .col = col}; 148 | assert(strlen(source) < sizeof(s.source)); 149 | strcpy(s.source, source); 150 | return s; 151 | } 152 | 153 | const char *hc_sloc_string(struct hc_sloc *sloc) { 154 | snprintf(sloc->out, sizeof(sloc->out), "'%s'; row %d, column %d", 155 | sloc->source, sloc->row, sloc->col); 156 | 157 | return sloc->out; 158 | } 159 | ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hacktical C 2 | A practical hacker's guide to the C programming language. 3 | 4 | *In memory of [Dennis Ritchie](https://en.wikipedia.org/wiki/Dennis_Ritchie), 5 | one of the greatest hackers this world has known.* 6 | 7 | ## About the book 8 | This book assumes basic programming knowledge. We're not going to spend a lot of time and space on explaining features that don't behave differently in important ways compared to other mainstream languages. Instead we're going to focus on practical techniques for making the most out of the power and flexibility C offers. 9 | 10 | Keep in mind it's very much a work in progress, mind the gaps, and please report any issues you come across or suggestions for improvements in the repo. 11 | 12 | ## About the author 13 | Programmers tend to fall in to categories, with very different motivations. I've always identified as a hacker. I like solving tricky problems, tend to interpret rules as challenges, and prefer using powerful tools that don't get in my way. To me, software is all about making a positive change in the real world. 14 | 15 | I've been writing code for fun on a mostly daily basis since I got a Commodore 64 for Christmas in 1985, professionally in different roles/companies since 1998. 16 | 17 | I started out with Basic on the Commodore, went on to learn Assembler on an Amiga, Pascal on PC; C++, Modula-3, Prolog, Ruby, Python, Perl, JavaScript, Common Lisp, Java, Erlang, Forth, Haskell, SmallTalk, Clojure, Go, C#, Swift, Kotlin. 18 | 19 | For a long time, I didn't pay much attention to C; the language felt primitive, especially compared to C++. But gradually over time, I realized that the worst enemy in software is complexity, and started taking C more seriously. 20 | 21 | Since then I've written plenty of C; and along the way I've picked up a number of techniques that helped me make the most out of the language and appreciate it for its strengths. 22 | 23 | This book is an attempt to synthesize that experience. 24 | 25 | ## License 26 | The content of this book is openly licensed via [CC BY-NC-ND 4.0](https://creativecommons.org/licenses/by-nc-nd/4.0/). 27 | 28 | ## Support 29 | I've decided to release the book using an open license to benefit as many as possible, because I believe knowledge should be shared freely. 30 | 31 | But I also believe in compensation for creators; and the less economic pressure I have to deal with, the more time and energy I can put into the project. 32 | 33 | The repository is set up for sponsoring via Stripe and Liberapay, alternatively you may use BTC (bitcoin:18k7kMcvPSSSzQtJ6hY5xxCt5U5p45rbuh) or ETH (0x776001F33F6Fc07ce9FF70187D5c034DCb429811). 34 | 35 | ## Why C? 36 | The reason I believe C is and always will be important is that it stands in a class of its own as a mostly portable assembler, offering similar levels of freedom. 37 | 38 | C doesn't try very hard to prevent you from making mistakes. It has few opinions about your code and happily assumes that you know exactly what you're doing. Freedom with responsibility. 39 | 40 | These days; many programmers would recommend choosing a stricter language, regardless of the problem being solved. Most of those programmers wouldn't trust themselves with the kind of freedom C offers, and many haven't bothered to learn the language properly. 41 | 42 | Since the foundation of the digital revolution, including the Internet was built using C; it gets the blame for many problems that are more due to our immaturity in designing and building complicated software than about programming languages. 43 | 44 | The truth is that any reasonably complicated software system created by humans will have bugs, regardless of what technology was used to create it. Using a stricter language helps with reducing some classes of bugs, at the cost of reduced flexibility and increased effort in expressing a solution. 45 | 46 | Programmers like to say that you should pick 'the right tool for the job'; what many fail to grasp is that the only people who have the capability to decide which tools are right, are the people writing the code. Much effort has been wasted on arguing and bullying programmers into picking tools other people prefer. 47 | 48 | ## Building 49 | The makefile requires `gcc`, `ccache` and `valgrind` to do its thing. 50 | 51 | ``` 52 | git clone https://github.com/codr7/hacktical-c.git 53 | cd hacktical-c 54 | mkdir build 55 | make 56 | ``` 57 | 58 | ## Platforms 59 | Since Unix is all about C, and Linux is currently the best supported Unix out there; that's the platform I would recommend. There's also Free/Open/NetBSD, which have less support but are equally enjoyable from my experience. 60 | 61 | All of the above support [valgrind](https://valgrind.org/), which makes coding C a lot safer and more productive. 62 | 63 | Microsoft has unfortunately chosen to neglect C for a long time, its compilers dragging far behind the rest of the pack. Windows does however offer a way of running Linux in the form of WSL2, which works very well from my experience. 64 | 65 | macOS keeps diverging from its Unix roots, which makes coding C in that environment increasingly frustrating. 66 | 67 | ## Portability 68 | The code in this book uses several GNU extensions that are not yet in the C standard; cleanup attributes, multi-line expressions and nested functions specifically. 69 | 70 | Except for nested functions, extensions used in the code should work just as well in `clang`. 71 | 72 | I can think of one feature, `hc_defer()`, which would currently be absolutely impossible without extensions. 73 | 74 | ## Chapters 75 | The content is arranged to form a natural progression, where later chapters build on concepts that have already been introduced. That being said; feel free to skip around, just be prepared to backtrack to fill in blanks. 76 | 77 | - [Macros](https://github.com/codr7/hacktical-c/tree/main/macro) 78 | - [Fixed-Point Arithmetic](https://github.com/codr7/hacktical-c/tree/main/fix) 79 | - [Intrusive Doubly Linked Lists](https://github.com/codr7/hacktical-c/tree/main/list) 80 | - [Lightweight Concurrent Tasks](https://github.com/codr7/hacktical-c/tree/main/task) 81 | - [Composable Memory Allocators - Part 1](https://github.com/codr7/hacktical-c/tree/main/malloc1) 82 | - [Vectors](https://github.com/codr7/hacktical-c/tree/main/vector) 83 | - [Exceptions](https://github.com/codr7/hacktical-c/tree/main/error) 84 | - [Ordered Sets and Maps](https://github.com/codr7/hacktical-c/tree/main/set) 85 | - [Composable Memory Allocators - Part 2](https://github.com/codr7/hacktical-c/tree/main/malloc2) 86 | - [Dynamic Compilation](https://github.com/codr7/hacktical-c/tree/main/dynamic) 87 | - [Extensible Streams - Part 1](https://github.com/codr7/hacktical-c/tree/main/stream1) 88 | - [Reflection](https://github.com/codr7/hacktical-c/tree/main/reflect) 89 | - [Structured Logs](https://github.com/codr7/hacktical-c/tree/main/slog) 90 | - [Virtual Machines](https://github.com/codr7/hacktical-c/tree/main/vm) 91 | - [Domain Specific Languages](https://github.com/codr7/hacktical-c/tree/main/dsl) 92 | 93 | The entire book may be exported in `ePub`-format by following these [instructions](./epub/README.md). -------------------------------------------------------------------------------- /stream1/README.md: -------------------------------------------------------------------------------- 1 | ## Extensible Streams - Part 1 2 | C++'s stream implementation may have missed the target in many ways, that doesn't mean extensible stream APIs in general are a bad idea. The problem we're trying to solve is providing an interface where one end doesn't need to know what's on the other end of a stream. C's standard library leaves a lot to wish for; there are extensions for custom `FILE *`-streams, but with spotty support. 3 | 4 | Example: 5 | ```C 6 | struct hc_memory_stream s; 7 | hc_memory_stream_init(&s); 8 | hc_defer(hc_stream_deinit(&s.stream)); 9 | hc_printf(&s.stream, "%s%d", "foo", 42); 10 | assert(strcmp("foo42", hc_memory_stream_string(&s)) == 0); 11 | ``` 12 | 13 | We'll start with defining the interface for a stream. 14 | 15 | ```C 16 | struct hc_stream { 17 | size_t (*read)(struct hc_stream *, uint8_t *, size_t); 18 | size_t (*write)(struct hc_stream *, const uint8_t *, size_t); 19 | void (*deinit)(struct hc_stream *); 20 | }; 21 | ``` 22 | 23 | `deinit`, `read` & `write` delegate to respective stored function pointer. 24 | 25 | ```C 26 | size_t hc_read(struct hc_stream *s, uint8_t *data, const size_t n) { 27 | assert(s->read); 28 | return s->read(s, data, n); 29 | } 30 | 31 | size_t hc_write(struct hc_stream *s, 32 | const uint8_t *data, 33 | const size_t n) { 34 | assert(s->write); 35 | return s->write(s, data, n); 36 | } 37 | ``` 38 | 39 | `getc` is trivial to implement using `read`. 40 | 41 | ```C 42 | char hc_getc(struct hc_stream *s) { 43 | char c = 0; 44 | return hc_read(s, (uint8_t *)&c, 1) ? c : 0; 45 | } 46 | ``` 47 | 48 | Which in turn allows us to easily implement `gets`, using a [vector](https://github.com/codr7/hacktical-c/tree/main/vector) as buffer. 49 | 50 | ```C 51 | char *hc_gets(struct hc_stream *s, struct hc_malloc *malloc) { 52 | struct hc_vector out; 53 | hc_vector_init(&out, malloc, 1); 54 | 55 | for (;;) { 56 | char c = hc_getc(s); 57 | 58 | if (c == EOF) { 59 | break; 60 | } 61 | 62 | *(char *)hc_vector_push(&out) = c; 63 | 64 | if (c == '\n') { 65 | break; 66 | } 67 | } 68 | 69 | 70 | *(char *)hc_vector_push(&out) = 0; 71 | return (char *)out.start; 72 | } 73 | ``` 74 | 75 | `putc` and `puts` delegate to `write`. 76 | 77 | ```C 78 | size_t hc_putc(struct hc_stream *s, const char data) { 79 | const uint8_t d[2] = {data, 0}; 80 | return hc_write(s, d, 1); 81 | } 82 | 83 | size_t hc_puts(struct hc_stream *s, const char *data) { 84 | return hc_write(s, (const uint8_t *)data, strlen(data)); 85 | } 86 | ``` 87 | 88 | `vprintf` uses a temporary buffer to format the message. 89 | 90 | ```C 91 | size_t hc_vprintf(struct hc_stream *s, 92 | const char *spec, 93 | va_list args) { 94 | char *data = hc_vsprintf(spec, args); 95 | hc_defer(free(data)); 96 | return hc_write(s, data, strlen(data)); 97 | } 98 | 99 | size_t hc_printf(struct hc_stream *s, const char *spec, ...) { 100 | va_list args; 101 | va_start(args, spec); 102 | hc_defer(va_end(args)); 103 | return hc_vprintf(s, spec, args); 104 | } 105 | ``` 106 | 107 | ```C 108 | void hc_stream_deinit(struct hc_stream *s) { 109 | assert(s->deinit); 110 | s->deinit(s); 111 | } 112 | ``` 113 | 114 | We begin with file streams. 115 | 116 | ```C 117 | struct hc_file_stream_opts { 118 | bool close_file; 119 | }; 120 | 121 | struct hc_file_stream { 122 | struct hc_stream stream; 123 | FILE *file; 124 | struct hc_file_stream_opts opts; 125 | }; 126 | 127 | #define hc_file_stream_init(s, f, ...) 128 | _hc_file_stream_init(s, f, (struct hc_file_stream_opts){ 129 | .close_file = false, 130 | ##__VA_ARGS__ 131 | }) 132 | 133 | struct hc_file_stream *_hc_file_stream_init(struct hc_file_stream *s, 134 | FILE *file, 135 | struct hc_file_stream_opts opts) { 136 | s->stream = (struct hc_stream){ 137 | .deinit = file_deinit, 138 | .read = file_read, 139 | .write = file_write, 140 | }; 141 | 142 | s->file = file; 143 | s->opts = opts; 144 | return s; 145 | }; 146 | ``` 147 | 148 | `read()`/`write()` delegate to `stdio` 149 | 150 | ```C 151 | size_t file_read(struct hc_stream *s, uint8_t *data, size_t n) { 152 | struct hc_file_stream *fs = hc_baseof(s, struct hc_file_stream, stream); 153 | return fread(data, n, 1, fs->file); 154 | } 155 | 156 | size_t file_write(struct hc_stream *s, const uint8_t *data, size_t n) { 157 | struct hc_file_stream *fs = hc_baseof(s, struct hc_file_stream, stream); 158 | return fwrite(data, n, 1, fs->file); 159 | } 160 | ``` 161 | 162 | If `close_file` is `true`, the file is closed with the stream. 163 | 164 | ```C 165 | void file_deinit(struct hc_stream *s) { 166 | struct hc_file_stream *fs = hc_baseof(s, struct hc_file_stream, stream); 167 | 168 | if (fs->close_file) { 169 | assert(fs->file); 170 | 171 | if (fclose(fs->file) == EOF) { 172 | hc_throw(0, "Failed closing file"); 173 | } 174 | 175 | fs->file = NULL; 176 | } 177 | } 178 | ``` 179 | 180 | Next up is memory streams. We'll use a [vector](https://github.com/codr7/hacktical-c/tree/main/vector) to manage the stream data and add a separate variable to track the current read position. 181 | 182 | ```C 183 | struct hc_memory_stream { 184 | struct hc_stream stream; 185 | struct hc_vector data; 186 | size_t rpos; 187 | }; 188 | 189 | struct hc_memory_stream *hc_memory_stream_init(struct hc_memory_stream *s, 190 | struct hc_malloc *malloc) { 191 | s->stream = (struct hc_stream){ 192 | .deinit = memory_deinit, 193 | .read = memory_read, 194 | .write = memory_write, 195 | }; 196 | 197 | hc_vector_init(&s->data, malloc, 1); 198 | s->rpos = 0; 199 | return s; 200 | } 201 | ``` 202 | 203 | `read()` clamps the number of read bytes to the length of `data - rpos`, uses `memcpy()` to copy data, and finally updates `rpos`. 204 | 205 | ```C 206 | size_t memory_read(struct hc_stream *s, uint8_t *data, size_t n) { 207 | struct hc_memory_stream *ms = hc_baseof(s, struct hc_memory_stream, stream); 208 | 209 | if (ms->rpos + n > ms->data.length) { 210 | n = ms->data.length - ms->rpos; 211 | } 212 | 213 | memcpy(data, ms->data.start + ms->rpos, n); 214 | ms->rpos += n; 215 | return n; 216 | } 217 | ``` 218 | 219 | `write()` inserts a block of `n` bytes at the end of the vector and uses `memcpy()` to copy data. 220 | 221 | ```C 222 | size_t memory_write(struct hc_stream *s, const uint8_t *data, size_t n) { 223 | struct hc_memory_stream *ms = hc_baseof(s, struct hc_memory_stream, stream); 224 | uint8_t *const dst = hc_vector_insert(&ms->data, ms->data.length, n); 225 | memcpy(dst, data, n); 226 | return n; 227 | } 228 | ``` 229 | 230 | The stream data is freed in `deinit()`. 231 | 232 | ```C 233 | void memory_deinit(struct hc_stream *s) { 234 | struct hc_memory_stream *ms = hc_baseof(s, struct hc_memory_stream, stream); 235 | hc_vector_deinit(&ms->data); 236 | } 237 | ``` -------------------------------------------------------------------------------- /malloc2/README.md: -------------------------------------------------------------------------------- 1 | ## Composable Memory Allocators - Part 2 2 | Welcome back to memory allocator land. 3 | 4 | We now have enough features in place to implement more elaborate allocators. 5 | 6 | It might make sense to give [Part 1](https://github.com/codr7/hacktical-c/tree/main/malloc1) a quick scan before diving in. 7 | 8 | ### Recycling Memory 9 | Memory recycling is a common requirement, we'll design the feature as a separate allocator that can be plugged in at any point. Since we have no idea what kind of memory we'll be recycling, we'll tag each allocation with its size. 10 | 11 | Example: 12 | ```C 13 | struct hc_memo_alloc a; 14 | hc_memo_alloc_init(&a, &hc_malloc_default); 15 | const int *p = hc_acquire(&a.malloc, sizeof(int)); 16 | hc_release(&a.malloc, p); 17 | assert(hc_acquire(&a.malloc, sizeof(int)) == p); 18 | ``` 19 | 20 | A [set](https://github.com/codr7/hacktical-c/tree/main/set) keyed on size is used to track allocations. 21 | 22 | ```C 23 | struct hc_memo_alloc { 24 | struct hc_malloc malloc; 25 | struct hc_malloc *source; 26 | struct hc_set memo; 27 | }; 28 | 29 | struct hc_memo_alloc *hc_memo_alloc_init(struct hc_memo_alloc *a, 30 | struct hc_malloc *source) { 31 | a->malloc.acquire = memo_acquire; 32 | a->malloc.release = memo_release; 33 | a->source = source; 34 | hc_set_init(&a->memo, &hc_malloc_default, sizeof(struct memo *), memo_cmp); 35 | a->memo.key = memo_key; 36 | } 37 | 38 | void hc_memo_alloc_deinit(struct hc_memo_alloc *a) { 39 | hc_vector_do(&a->memo.items, _m) { 40 | struct memo *m = *(struct memo **)_m; 41 | hc_release(a->source, m); 42 | } 43 | 44 | hc_set_deinit(&a->memo); 45 | } 46 | ``` 47 | 48 | Each allocation consists of a size and the data. 49 | 50 | ```C 51 | struct memo { 52 | size_t size; 53 | uint8_t data[]; 54 | }; 55 | 56 | enum hc_order memo_cmp(const void *l, const void *r) { 57 | return hc_cmp(*(size_t *)l, *(size_t *)r); 58 | } 59 | 60 | const void *memo_key(const void *p) { 61 | struct memo *m = *(struct memo **)p; 62 | return &m->size; 63 | } 64 | ``` 65 | 66 | `acquire` first checks for recycled allocations of the correct size, and delegates to the source allocator if none was found. 67 | 68 | ```C 69 | void *memo_acquire(struct hc_malloc *a, size_t size) { 70 | struct hc_memo_alloc *ma = hc_baseof(a, struct hc_memo_alloc, malloc); 71 | 72 | if (hc_set_length(&ma->memo)) { 73 | bool ok = false; 74 | size_t i = hc_set_index(&ma->memo, &size, &ok); 75 | 76 | if (ok) { 77 | struct hc_vector *is = &ma->memo.items; 78 | struct memo *m = *(struct memo **)hc_vector_get(is, i); 79 | hc_vector_delete(is, i, 1); 80 | return m->data; 81 | } 82 | } 83 | 84 | struct memo *m = hc_acquire(ma->source, sizeof(struct memo) + size); 85 | m->size = size; 86 | return m->data; 87 | } 88 | ``` 89 | 90 | `release` registers the allocation for recycling. 91 | 92 | ```C 93 | void memo_release(struct hc_malloc *a, void *p) { 94 | struct hc_memo_alloc *ma = hc_baseof(a, struct hc_memo_alloc, malloc); 95 | struct memo *m = hc_baseof(p, struct memo, data); 96 | *(struct memo **)hc_set_add(&ma->memo, &m->size, true) = m; 97 | } 98 | ``` 99 | 100 | ### Slab Allocation 101 | Slab allocators acquire memory in fixed size blocks, or slabs. They're commonly used in combination with a fixed allocation size, where each slab contains the same number of slots. We're going to introduce a tiny bit of flexibility by allowing different allocation sizes. 102 | 103 | Example: 104 | ```C 105 | struct hc_slab_alloc a; 106 | hc_slab_alloc_init(&a, &hc_malloc_default, 2 * sizeof(int)); 107 | 108 | // Same slab 109 | const int *p1 = hc_acquire(&a.malloc, sizeof(int)); 110 | const int *p2 = hc_acquire(&a.malloc, sizeof(int)); 111 | assert(p2 == p1 + 1); 112 | 113 | // New slab 114 | const int *p3 = hc_acquire(&a.malloc, sizeof(int)); 115 | assert(p3 > p2 + 1); 116 | ``` 117 | 118 | We'll use a `struct hc_list` to keep track of our slabs. 119 | 120 | ```C 121 | struct hc_slab_alloc { 122 | struct hc_malloc malloc; 123 | struct hc_malloc *source; 124 | struct hc_list slabs; 125 | size_t slab_size; 126 | }; 127 | 128 | struct hc_slab_alloc *hc_slab_alloc_init(struct hc_slab_alloc *a, 129 | struct hc_malloc *source, 130 | size_t slab_size) { 131 | a->malloc.acquire = slab_acquire; 132 | a->malloc.release = slab_release; 133 | a->source = source; 134 | hc_list_init(&a->slabs); 135 | a->slab_size = slab_size; 136 | return a; 137 | } 138 | 139 | void hc_slab_alloc_deinit(struct hc_slab_alloc *a) { 140 | hc_list_do(&a->slabs, _s) { 141 | struct slab *s = hc_baseof(_s, struct slab, slabs); 142 | hc_release(a->source, s); 143 | } 144 | } 145 | ``` 146 | 147 | Slabs are defined as dynamically sized structs that keep track of used/available memory. 148 | 149 | ```C 150 | struct slab { 151 | struct hc_list slabs; 152 | uint8_t *next; 153 | uint8_t memory[]; 154 | }; 155 | ``` 156 | 157 | Slabs are ordered by available memory, descending. Finding a slab means moving down the list until we reach a slab that can't fit the allocation and returning the previous slab. Allocation sizes that exceed the specified slab size always return new custom sized slabs. 158 | 159 | ```C 160 | struct slab *get_slab(struct hc_slab_alloc *a, const size_t size) { 161 | if (size > a->slab_size) { 162 | return add_slab(a, size); 163 | } 164 | 165 | struct slab *result = NULL; 166 | 167 | hc_list_do(&a->slabs, sl) { 168 | struct slab *s = hc_baseof(sl, struct slab, slabs); 169 | uint8_t *p = hc_align(s->next, size); 170 | 171 | if (p + size > s->memory + a->slab_size) { 172 | break; 173 | } 174 | 175 | result = s; 176 | } 177 | 178 | return result ? result : add_slab(a, a->slab_size); 179 | } 180 | ``` 181 | 182 | If no suitable slabs are found, a new one is added. 183 | 184 | ```C 185 | struct slab *add_slab(struct hc_slab_alloc *a, size_t size) { 186 | struct slab *s = hc_acquire(a->source, 187 | sizeof(struct slab) + 188 | size); 189 | 190 | hc_list_push_front(&a->slabs, &s->slabs); 191 | s->next = s->memory; 192 | return s; 193 | } 194 | ``` 195 | 196 | `acquire()` gets a slab and adjusts it's position in the list before returning a pointer to the allocation. 197 | 198 | ```C 199 | void *slab_acquire(struct hc_malloc *a, const size_t size) { 200 | struct hc_slab_alloc *sa = hc_baseof(a, struct hc_slab_alloc, malloc); 201 | 202 | struct slab *s = get_slab(sa, size); 203 | uint8_t *p = hc_align(s->next, size); 204 | s->next = p + size; 205 | 206 | while (s->slabs.next != &s->slabs) { 207 | struct slab *ns = hc_baseof(s->slabs.next, struct slab, slabs); 208 | 209 | if (ns->next - ns->memory > s->next - s->memory) { 210 | hc_list_shift_back(&s->slabs); 211 | } else { 212 | break; 213 | } 214 | } 215 | 216 | return p; 217 | } 218 | ``` 219 | 220 | `release()` is a no op. 221 | 222 | ```C 223 | void slab_release(struct hc_malloc *a, void *p) { 224 | // Do nothing 225 | } 226 | ``` -------------------------------------------------------------------------------- /slog/README.md: -------------------------------------------------------------------------------- 1 | ## Structured Logs 2 | Logging is one of those things that deserve more focus than it usually gets. A well designed log helps alot with resolving unforseen issues that pop up in production. I prefer my logs structured by name/value pairs since this increases their usefulness by making them convenient to work with programatically. 3 | 4 | Example: 5 | ```C 6 | struct hc_slog_stream s; 7 | hc_slog_stream_init(&s, &hc_stdout(), true); 8 | 9 | hc_slog_do(&s) { 10 | hc_slog_context_do(hc_slog_string("foo", "bar")) { 11 | hc_slog_write(hc_slog_time("baz", hc_time(2025, 4, 13, 1, 40, 0))); 12 | } 13 | } 14 | ``` 15 | ``` 16 | foo="bar", baz=2025-04-13T1:40:00 17 | ``` 18 | 19 | Let's start with the interface. 20 | 21 | ```C 22 | struct hc_slog { 23 | void (*deinit)(struct hc_slog *); 24 | void (*write)(struct hc_slog *, size_t, struct hc_slog_field []); 25 | }; 26 | ``` 27 | 28 | A field consists of a name and a [value](https://github.com/codr7/hacktical-c/tree/main/reflect). 29 | 30 | ```C 31 | struct hc_slog_field { 32 | char *name; 33 | struct hc_value value; 34 | }; 35 | ``` 36 | 37 | Each thread has a default log, which defaults to `stdout`. 38 | 39 | ```C 40 | __thread struct hc_slog *_hc_slog = NULL; 41 | 42 | struct hc_slog *hc_slog() { 43 | if (_hc_slog != NULL) { 44 | return _hc_slog; 45 | } 46 | 47 | static __thread bool init = true; 48 | static __thread struct hc_slog_stream s; 49 | 50 | if (init) { 51 | hc_slog_stream_init(&s, hc_stdout(), false); 52 | init = false; 53 | } 54 | 55 | return &s.slog; 56 | } 57 | ``` 58 | 59 | A few macros to simplify typical use: 60 | 61 | ```C 62 | #define __hc_slog_do(s, _ps) 63 | for (struct hc_slog *_ps = hc_slog(); 64 | _ps && (_hc_slog = (s)); 65 | _hc_slog = _ps, _ps = NULL) 66 | 67 | #define _hc_slog_do(s) 68 | __hc_slog_do((s), hc_unique(ps)) 69 | 70 | #define hc_slog_do(s) 71 | _hc_slog_do(&(s)->slog) 72 | 73 | #define hc_slog_deinit(s) 74 | _hc_slog_deinit(&(s)->slog) 75 | ``` 76 | 77 | We'll add a layer of convenience functions for defining fields: 78 | 79 | ```C 80 | static struct hc_value *field_init(struct hc_slog_field *f, 81 | const char *name, 82 | const struct hc_type *type) { 83 | f->name = strdup(name); 84 | hc_value_init(&f->value, type); 85 | return &f->value; 86 | } 87 | 88 | struct hc_slog_field *hc_slog_bool(const char *name, const bool value) { 89 | struct hc_slog_field *f = malloc(sizeof(struct hc_slog_field)); 90 | field_init(f, name, &HC_BOOL)->as_bool = value; 91 | return f; 92 | } 93 | 94 | struct hc_slog_field *hc_slog_int(const char *name, const int value) { 95 | struct hc_slog_field *f = malloc(sizeof(struct hc_slog_field)); 96 | field_init(f, name, &HC_INT)->as_int = value; 97 | return f; 98 | } 99 | 100 | struct hc_slog_field *hc_slog_string(const char *name, const char *value) { 101 | struct hc_slog_field *f = malloc(sizeof(struct hc_slog_field)); 102 | field_init(f, name, &HC_STRING)->as_string = strdup(value); 103 | return f; 104 | } 105 | 106 | struct hc_slog_field *hc_slog_time(const char *name, const hc_time_t value) { 107 | struct hc_slog_field *f = malloc(sizeof(struct hc_slog_field)); 108 | field_init(f, name, &HC_TIME)->as_time = value; 109 | return f; 110 | } 111 | ``` 112 | 113 | `hc_slog_write()` converts its arguments to be passed as length/array to the function. 114 | 115 | ```C 116 | #define _hc_slog_write(s, ...) do { 117 | hc_array(struct hc_slog_field *, fs, ##__VA_ARGS__); 118 | __hc_slog_write((s), fs_n, fs_a); 119 | } while (0) 120 | 121 | #define hc_slog_write(...) 122 | _hc_slog_write(hc_slog(), ##__VA_ARGS__) 123 | ``` 124 | 125 | The primary kind of log writes fields to a [stream](https://github.com/codr7/hacktical-c/tree/main/stream1). 126 | 127 | ```C 128 | struct hc_slog_stream_opts { 129 | bool close_out; 130 | }; 131 | 132 | struct hc_slog_stream { 133 | struct hc_slog slog; 134 | struct hc_stream *out; 135 | struct hc_slog_stream_opts opts; 136 | }; 137 | 138 | void stream_deinit(struct hc_slog *s) { 139 | struct hc_slog_stream *ss = hc_baseof(s, struct hc_slog_stream, slog); 140 | if (ss->opts.close_out) { hc_stream_deinit(ss->out); } 141 | } 142 | 143 | void stream_write(struct hc_slog *s, 144 | const size_t n, 145 | struct hc_slog_field *fields[]) { 146 | struct hc_slog_stream *ss = hc_baseof(s, struct hc_slog_stream, slog); 147 | 148 | for(size_t i = 0; i < n; i++) { 149 | struct hc_slog_field *f = fields[i]; 150 | if (i) { hc_puts(ss->out, ", "); } 151 | field_write(f, ss->out); 152 | } 153 | 154 | hc_putc(ss->out, '\n'); 155 | } 156 | 157 | #define hc_slog_stream_init(s, out, ...) 158 | _hc_slog_stream_init(s, out, (struct hc_slog_stream_opts){ 159 | .close_out = false, 160 | ##__VA_ARGS__ 161 | }) 162 | 163 | struct hc_slog_stream *hc_slog_stream_init(struct hc_slog_stream *s, 164 | struct hc_stream *out, 165 | struct hc_slog_stream_opts opts) { 166 | s->slog.deinit = stream_deinit; 167 | s->slog.write = stream_write; 168 | s->out = out; 169 | s->opts = opts; 170 | return s; 171 | } 172 | ``` 173 | 174 | Contexts are implemented as just another kind of log, which traps write calls and delegate them to the parent log prefixed with its own fields. 175 | 176 | ```C 177 | #define __hc_slog_context_do(_c, _fs, _a, _n, ...) 178 | struct hc_slog_context _c; 179 | hc_array(struct hc_slog_field *, _fs, ##__VA_ARGS__); 180 | hc_slog_context_init(&_c, _n, _a); 181 | hc_defer(hc_slog_deinit(&_c)); 182 | hc_slog_do(&_c) 183 | 184 | #define _hc_slog_context_do(_c, _fs, ...) 185 | __hc_slog_context_do(_c, 186 | _fs, 187 | hc_id(_fs, _a), 188 | hc_id(_fs, _n), 189 | ##__VA_ARGS__) 190 | 191 | #define hc_slog_context_do(...) 192 | _hc_slog_context_do(hc_unique(slog_c), 193 | hc_unique(slog_fs), 194 | ##__VA_ARGS__) 195 | 196 | struct hc_slog_context { 197 | struct hc_slog slog; 198 | struct hc_slog *parent; 199 | size_t length; 200 | struct hc_slog_field **fields; 201 | }; 202 | 203 | static void context_deinit(struct hc_slog *s) { 204 | struct hc_slog_context *sc = hc_baseof(s, struct hc_slog_context, slog); 205 | 206 | for (size_t i = 0; i < sc->length; i++) { 207 | struct hc_slog_field *f = sc->fields[i]; 208 | field_deinit(f); 209 | free(f); 210 | } 211 | 212 | free(sc->fields); 213 | } 214 | 215 | static void context_write(struct hc_slog *s, 216 | const size_t n, 217 | struct hc_slog_field *fields[]) { 218 | struct hc_slog_context *c = hc_baseof(s, struct hc_slog_context, slog); 219 | struct hc_slog_field *fs[c->length + n]; 220 | memcpy(fs, c->fields, sizeof(struct hc_slog_field *) * c->length); 221 | memcpy(fs + c->length, fields, sizeof(struct hc_slog_field *) * n); 222 | slog_write(c->parent, c->length + n, fs); 223 | } 224 | 225 | struct hc_slog_context *hc_slog_context_init(struct hc_slog_context *c, 226 | size_t length, 227 | struct hc_slog_field *fields[]) { 228 | c->slog.deinit = context_deinit; 229 | c->slog.write = context_write; 230 | c->parent = hc_slog(); 231 | c->length = length; 232 | size_t s = sizeof(struct hc_slog_field *) * length; 233 | c->fields = malloc(s); 234 | memcpy(c->fields, fields, s); 235 | return c; 236 | } 237 | ``` -------------------------------------------------------------------------------- /dsl/README.md: -------------------------------------------------------------------------------- 1 | ## Domain Specific Languages 2 | A domain specific language (DSL) is a tiny embedded language specialized at solving a specific category of problems. Once you start looking for them, they're everywhere; `printf` formats and regular expressions are two obvious examples. 3 | 4 | We're going to build a template engine that allows splicing runtime evaluated expressions into strings. Templates are compiled into operations for the [virtual machine](https://github.com/codr7/hacktical-c/tree/main/vm) we built in previous chapter. 5 | 6 | Example: 7 | ```C 8 | struct hc_dsl dsl; 9 | hc_dsl_init(&dsl, &hc_malloc_default); 10 | hc_defer(hc_dsl_deinit(&dsl)); 11 | struct hc_memory_stream out; 12 | hc_memory_stream_init(&out, &hc_malloc_default); 13 | hc_defer(hc_stream_deinit(&out->stream)); 14 | dsl.out = &out.stream; 15 | hc_dsl_set_string(&dsl, "foo", "ghi"); 16 | hc_dsl_eval(&dsl, "abc $(print (upcase foo)) def"); 17 | assert(strcmp("abc GHI def", hc_memory_stream_string(&out)) == 0); 18 | ``` 19 | 20 | Our DSL consists of an environment, a standard output and a [vm](https://github.com/codr7/hacktical-c/tree/main/vm). 21 | 22 | ```C 23 | struct hc_dsl { 24 | struct hc_set env; 25 | struct hc_stream *out; 26 | 27 | struct hc_vm vm; 28 | }; 29 | 30 | void hc_dsl_init(struct hc_dsl *dsl) { 31 | hc_set_init(&dsl->env, malloc, sizeof(struct env_item), env_cmp); 32 | dsl->env.key = env_key; 33 | dsl->out = hc_stdout(); 34 | 35 | hc_vm_init(vm, &hc_malloc_default); 36 | hc_dsl_set_fun(vm, "print", lib_print); 37 | hc_dsl_set_fun(vm, "upcase", lib_upcase); 38 | } 39 | 40 | enum hc_order env_cmp(const void *x, const void *y) { 41 | return hc_strcmp(*(const char **)x, *(const char **)y); 42 | } 43 | 44 | const void *env_key(const void *x) { 45 | return &((const struct env_item *)x)->key; 46 | } 47 | ``` 48 | 49 | `print` pops a value from the stack and prints it to `vm.stdout`. 50 | 51 | ```C 52 | void lib_print(struct hc_vm *vm, const struct hc_sloc sloc) { 53 | struct hc_value *v = hc_vm_pop(vm); 54 | struct hc_dsl *dsl = hc_baseof(vm, struct hc_dsl, vm); 55 | hc_value_print(v, dsl->out); 56 | hc_value_deinit(v); 57 | } 58 | ``` 59 | 60 | While `upcase` transforms the top value on the stack to uppercase. 61 | 62 | ```C 63 | void lib_upcase(struct hc_vm *vm, const struct hc_sloc sloc) { 64 | struct hc_value *v = hc_vm_peek(vm); 65 | 66 | if (v->type != &HC_STRING) { 67 | hc_throw("Error in %s: Expected string (%s)", 68 | hc_sloc_string(&sloc), v->type->name); 69 | } 70 | 71 | hc_upcase(v->as_string); 72 | } 73 | 74 | char *hc_upcase(char *s) { 75 | while (*s) { 76 | *s = toupper(*s); 77 | s++; 78 | } 79 | 80 | return s; 81 | } 82 | ``` 83 | 84 | The only missing piece of the puzzle at this point is transforming template code into [VM](https://github.com/codr7/hacktical-c/tree/main/vm) operations, aka. syntax. 85 | 86 | ```C 87 | void hc_dsl_eval(struct hc_dsl *dsl, const char *in) { 88 | struct hc_list forms; 89 | hc_list_init(&forms); 90 | hc_defer(hc_forms_free(&forms)); 91 | struct hc_sloc sloc = hc_sloc("eval", 0, 0); 92 | while (hc_read_next(&in, &forms, &sloc)); 93 | const size_t pc = dsl->vm.code.length; 94 | hc_forms_emit(&forms, dsl); 95 | hc_vm_eval(&dsl->vm, pc, -1); 96 | } 97 | ``` 98 | 99 | The top layer of our parser simply checks for `$` and uses that decide what do next. 100 | 101 | ```C 102 | bool hc_read_next(const char **in, 103 | struct hc_list *out, 104 | struct hc_sloc *sloc) { 105 | if (**in == '$') { 106 | (*in)++; 107 | hc_read_call(in, out, sloc); 108 | return true; 109 | } 110 | 111 | return hc_read_text(in, out, sloc); 112 | } 113 | ``` 114 | 115 | A call consists of a target and optional arguments. 116 | 117 | ```C 118 | void hc_read_call(const char **in, 119 | struct hc_list *out, 120 | struct hc_sloc *sloc) { 121 | struct hc_sloc floc = *sloc; 122 | 123 | if (**in != '(') { 124 | hc_throw("Error in %s: Invalid call syntax", 125 | hc_sloc_string(sloc)); 126 | } 127 | 128 | (*in)++; 129 | sloc->col++; 130 | hc_skip_ws(in, sloc); 131 | 132 | if (!hc_read_expr(in, out, sloc)) { 133 | hc_throw("Error in %s: Missing call target", 134 | hc_sloc_string(sloc)); 135 | } 136 | 137 | struct hc_form *t = hc_baseof(hc_list_pop_back(out), 138 | struct hc_form, 139 | owner); 140 | 141 | hc_list_init(&t->owner); 142 | struct hc_call *f = malloc(sizeof(struct hc_call)); 143 | hc_call_init(f, floc, out, t); 144 | 145 | for (bool done = false; !done;) { 146 | hc_skip_ws(in, sloc); 147 | 148 | switch (**in) { 149 | case 0: 150 | hc_form_free(f); 151 | 152 | hc_throw("Error in %s: Open call form", 153 | hc_sloc_string(sloc)); 154 | case ')': 155 | (*in)++; 156 | sloc->col++; 157 | done = true; 158 | continue; 159 | default: 160 | break; 161 | } 162 | 163 | if (!hc_read_expr(in, &f->args, sloc)) { 164 | hc_form_free(f); 165 | 166 | hc_throw("Error in %s: Invalid call syntax", 167 | hc_sloc_string(sloc)); 168 | } 169 | } 170 | } 171 | ``` 172 | 173 | When emitted, calls get the value of the target and emits arguments if any followed by a `HC_CALL``-operation. 174 | 175 | ```C 176 | static void call_emit(struct hc_form *_f, struct hc_vm *vm) { 177 | struct hc_call *f = hc_baseof(_f, struct hc_call, form); 178 | struct hc_value *t = hc_form_value(f->target, vm); 179 | 180 | if (!t) { 181 | hc_throw("Error in %s: Missing call target", 182 | hc_sloc_string(&_f->sloc)); 183 | } 184 | 185 | if (t->type != &HC_VM_FUN) { 186 | hc_throw("Error in %s: '%s' isn't callable", 187 | hc_sloc_string(&_f->sloc), 188 | t->type->name); 189 | } 190 | 191 | hc_list_do(&f->args, a) { 192 | hc_form_emit(hc_baseof(a, struct hc_form, owner), vm); 193 | } 194 | 195 | hc_vm_emit(vm, 196 | &HC_CALL, 197 | &(struct hc_call_op){ 198 | .target = t->as_other, 199 | .sloc = _f->sloc 200 | }); 201 | } 202 | ``` 203 | 204 | `hc_skip_ws()` simply skips forward as long as the current char is some kind of whitespace. 205 | 206 | ```C 207 | void hc_skip_ws(const char **in, struct hc_sloc *sloc) { 208 | for (;; (*in)++) { 209 | switch (**in) { 210 | case ' ': 211 | case '\t': 212 | sloc->col++; 213 | break; 214 | case '\n': 215 | sloc->row++; 216 | sloc->col = 0; 217 | break; 218 | default: 219 | return; 220 | } 221 | } 222 | } 223 | ``` 224 | 225 | `hc_read_expr()` handles anything allowed inside `$()`, which means another call or an identifier. 226 | 227 | ```C 228 | bool hc_read_expr(const char **in, 229 | struct hc_list *out, 230 | struct hc_sloc *sloc) { 231 | const char c = **in; 232 | 233 | switch (c) { 234 | case '(': 235 | hc_read_call(in, out, sloc); 236 | return true; 237 | default: 238 | if (isalpha(c)) { 239 | hc_read_id(in, out, sloc); 240 | return true; 241 | } 242 | 243 | break; 244 | } 245 | 246 | return false; 247 | } 248 | ``` 249 | 250 | Identifiers are required to start with an alphabetic char; following that, anything except whitespace and parens is allowed. 251 | 252 | ```C 253 | void hc_read_id(const char **in, 254 | struct hc_list *out, 255 | struct hc_sloc *sloc) { 256 | struct hc_sloc floc = *sloc; 257 | struct hc_memory_stream buf; 258 | hc_memory_stream_init(&buf, &hc_malloc_default); 259 | hc_defer(hc_stream_deinit(&buf.stream)); 260 | char c = 0; 261 | 262 | while ((c = **in)) { 263 | if (isspace(c) || c == '(' || c == ')') { 264 | break; 265 | } 266 | 267 | hc_putc(&buf.stream, c); 268 | sloc->col++; 269 | (*in)++; 270 | } 271 | 272 | struct hc_id *f = malloc(sizeof(struct hc_id)); 273 | hc_id_init(f, floc, out, hc_memory_stream_string(&buf)); 274 | } 275 | ``` 276 | 277 | Identifiers get their values from `dsl.env` and emit an operation to push it on the stack. 278 | 279 | ```C 280 | void id_emit(struct hc_form *_f, struct hc_dsl *dsl) { 281 | struct hc_id *f = hc_baseof(_f, struct hc_id, form); 282 | struct hc_value *v = hc_dsl_getenv(dsl, f->name); 283 | 284 | if (!v) { 285 | hc_throw("Error in %s: Unknown identifier '%s'", 286 | hc_sloc_string(&_f->sloc), f->name); 287 | } 288 | 289 | struct hc_push_op op; 290 | hc_value_copy(&op.value, v); 291 | hc_vm_emit(&dsl->vm, &HC_PUSH, &op); 292 | } 293 | ``` 294 | 295 | The text parser keeps going until a `$` is found or until it reaches the end of the string, it then constructs a `print` call with the text as argument. 296 | 297 | ```C 298 | bool hc_read_text(const char **in, 299 | struct hc_list *out, 300 | struct hc_sloc *sloc) { 301 | struct hc_sloc floc = *sloc; 302 | const char *start = *in; 303 | 304 | while (**in && **in != '$') { 305 | if (**in == '\n') { 306 | sloc->row++; 307 | } else { 308 | sloc->col++; 309 | } 310 | 311 | (*in)++; 312 | } 313 | 314 | size_t n = *in - start; 315 | 316 | if (n) { 317 | struct hc_value v; 318 | hc_value_init(&v, &HC_STRING)->as_string = strndup(start, n); 319 | struct hc_literal *vf = malloc(sizeof(struct hc_literal)); 320 | hc_literal_init(vf, floc, out); 321 | vf->value = v; 322 | struct hc_id *t = malloc(sizeof(struct hc_literal)); 323 | hc_id_init(t, floc, NULL, "print"); 324 | struct hc_call *c = malloc(sizeof(struct hc_call)); 325 | hc_call_init(c, floc, out, &t->form); 326 | return true; 327 | } 328 | 329 | return false; 330 | } 331 | ``` -------------------------------------------------------------------------------- /epub/build_ebook.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=missing-module-docstring 2 | # pylint: disable=missing-function-docstring 3 | # pylint: disable=missing-class-docstring 4 | # pylint: disable=line-too-long 5 | # pylint: disable=consider-using-f-string 6 | 7 | import argparse 8 | import dataclasses 9 | import json 10 | import logging 11 | import os 12 | import pathlib 13 | import re 14 | import subprocess 15 | import sys 16 | 17 | @dataclasses.dataclass 18 | class BuildConfig: 19 | """ 20 | the collection of cli options used to configure this build 21 | """ 22 | output_dir: pathlib.Path 23 | output_int_dir: pathlib.Path 24 | output_epub_path: pathlib.Path 25 | epub_metadata_path: pathlib.Path 26 | css_path: pathlib.Path 27 | chapters_data_json: pathlib.Path 28 | chapters_root: pathlib.Path 29 | 30 | @dataclasses.dataclass 31 | class ChapterData: 32 | """ 33 | the files which are used to populate each section of the book 34 | """ 35 | chapter_root: pathlib.Path 36 | code_supplements: list[pathlib.Path] 37 | 38 | PANDOC_EXE_NAME = "pandoc" 39 | 40 | def main() -> None: 41 | """ 42 | build the ebook 43 | """ 44 | logging.basicConfig( 45 | encoding='utf-8', 46 | level=logging.INFO, 47 | format='[%(asctime)s] [%(levelname)s] %(message)s', 48 | datefmt='%I:%M:%S') 49 | 50 | verify_pandoc_installed() 51 | 52 | parser = argparse.ArgumentParser( 53 | prog='build_ebook', 54 | description='script to collate and format markdown book chapters into an EPUB ebook') 55 | parser.add_argument( 56 | '--chapters-root', 57 | default=None, 58 | help='The directory where chapter data is stored. If none provided / is used') 59 | parser.add_argument( 60 | '-o', '--output-epub-name', 61 | default='HackticalC.epub', 62 | help='The name of the generated EPUB book file') 63 | parser.add_argument( 64 | '--output-directory', 65 | default=None, 66 | help='The directory where generated output is placed. If none provided /epub/output used') 67 | parser.add_argument( 68 | '-v', '--verbose', 69 | default=False, 70 | action='store_true', 71 | help='whether to log output verbosely or not') 72 | 73 | # setup the log output 74 | parsed_args = parser.parse_args() 75 | if parsed_args.verbose: 76 | logging.getLogger().setLevel(logging.DEBUG) 77 | 78 | # parse our build configuration 79 | config = parse_config(parsed_args) 80 | logging.debug("output directory: %s", config.output_dir) 81 | logging.debug("intermediate output directory: %s", config.output_int_dir) 82 | logging.debug("output epub path: %s", config.output_epub_path) 83 | logging.debug("epub metadata path: %s", config.epub_metadata_path) 84 | logging.debug("book css path: %s", config.css_path) 85 | logging.debug("chapters data json path: %s", config.chapters_data_json) 86 | logging.debug("chapters root: %s", config.chapters_root) 87 | 88 | # ensure our output directories exist 89 | os.makedirs(config.output_dir, exist_ok=True) 90 | logging.debug("setup %s", config.output_dir) 91 | os.makedirs(config.output_int_dir, exist_ok=True) 92 | logging.debug("setup %s", config.output_int_dir) 93 | 94 | # read the book data from our JSON 95 | with open(config.chapters_data_json, encoding="utf8") as f: 96 | chapter_data = parse_chapter_data_from_json_blob(f.read()) 97 | 98 | # expand the paths to absolute paths for easier debugging 99 | chapter_data = [ expand_chapter_data_paths(config.chapters_root, ch) for ch in chapter_data ] 100 | logging.debug("chapter data: %s", chapter_data) 101 | 102 | # take the original source chapters and slightly reformat them for nicer ebook presentation 103 | formatted_chapter_file_paths: list[pathlib.Path] = [] 104 | for (i, ch) in enumerate(chapter_data): 105 | (chapter_main, chapter_supplement) = reformat_chapter_data(config.output_int_dir, i+1, ch) 106 | formatted_chapter_file_paths.append(chapter_main) 107 | if chapter_supplement is not None: 108 | formatted_chapter_file_paths.append(chapter_supplement) 109 | logging.debug("formatted chapters files: %s", formatted_chapter_file_paths) 110 | 111 | 112 | # throw the reformatted source .md files through pandoc to generate the ebook 113 | try: 114 | pandoc_args = [ 115 | PANDOC_EXE_NAME, 116 | "-o", str(config.output_epub_path), 117 | "--css", str(config.css_path), 118 | "--toc", # auto-generate ToC 119 | "--metadata-file", str(config.epub_metadata_path), 120 | ] + [str(p) for p in formatted_chapter_file_paths] 121 | logging.debug("generating %s: cmd=%s", config.output_epub_path, pandoc_args) 122 | 123 | subprocess.run(pandoc_args, check=True) 124 | logging.info("generated %s", config.output_epub_path) 125 | except subprocess.CalledProcessError as ex: 126 | logging.error("Pandoc failed! retcode=%d", ex.returncode) 127 | logging.error(" cmd: %s", ex.cmd) 128 | logging.error(" args: %s", ex.args) 129 | logging.error(" stdout: %s", ex.stdout) 130 | logging.error(" stderr: %s", ex.stderr) 131 | 132 | def parse_config(args: argparse.Namespace) -> BuildConfig: 133 | """ 134 | read the cli args and default build settings into a build config 135 | """ 136 | if args.output_directory is not None: 137 | output_dir = pathlib.Path(args.output_directory) 138 | else: 139 | output_dir = pathlib.Path(os.path.abspath(__file__)).parent / "output" 140 | 141 | if args.chapters_root is not None: 142 | chapters_root = pathlib.Path(args.chapters_root).resolve() 143 | else: 144 | chapters_root = pathlib.Path(os.path.abspath(__file__)).parent.parent 145 | 146 | # normalize our paths to nicer, "full" paths without relative indirection for ease of debugging 147 | script_dir = pathlib.Path(os.path.abspath(__file__)).parent.resolve() 148 | output_dir = output_dir.resolve() 149 | output_int_dir = output_dir / "int" 150 | output_epub_path = output_dir / args.output_epub_name 151 | epub_metadata_path = script_dir / "metadata.yml" 152 | book_css_path = script_dir / "styles.css" 153 | chapters_data_json_path = script_dir / "book_data.json" 154 | chapters_root = chapters_root.resolve() 155 | 156 | return BuildConfig( 157 | output_dir=output_dir, 158 | output_int_dir=output_int_dir, 159 | output_epub_path=output_epub_path, 160 | epub_metadata_path=epub_metadata_path, 161 | css_path=book_css_path, 162 | chapters_data_json=chapters_data_json_path, 163 | chapters_root=chapters_root) 164 | 165 | def parse_chapter_data_from_json_blob(json_file_data: str) -> list[ChapterData]: 166 | """ 167 | read the book data json blob to get all of the info we need to read the source chapter files 168 | """ 169 | chapters_data = json.loads(json_file_data) 170 | 171 | if not isinstance(chapters_data, list): 172 | raise RuntimeError("Chapter data JSON malformed: expected JSON list") 173 | 174 | parsed_chapters_data: list[ChapterData] = [] 175 | for chapter_data in chapters_data: 176 | if not isinstance(chapter_data, dict): 177 | raise RuntimeError("Chapter data JSON malformed: each entry in list must be chapter data object") 178 | 179 | if "chapter_root" not in chapter_data: 180 | raise RuntimeError("Chapter data JSON malformed: missing required 'chapter_root' in chapter") 181 | 182 | chapter_root = chapter_data["chapter_root"] 183 | if not isinstance(chapter_root, str): 184 | raise RuntimeError(f"Chapter data JSON malformed: 'chapter_root' is {type(chapter_root)}; expected str") 185 | 186 | code_supplements = chapter_data.get("code_supplements", []) 187 | for (i, supp)in enumerate(code_supplements): 188 | if not isinstance(supp, str): 189 | raise RuntimeError(f"Chapter data JSON malformed: code_supplements[{i}] was {type(supp)}; expected str") 190 | 191 | parsed_data = ChapterData( 192 | chapter_root=pathlib.Path(chapter_root), 193 | code_supplements=[ pathlib.Path(supp) for supp in code_supplements ]) 194 | 195 | logging.debug("parsed chapter data #%d: %s", len(parsed_chapters_data), parsed_data) 196 | parsed_chapters_data.append(parsed_data) 197 | return parsed_chapters_data 198 | 199 | def expand_chapter_data_paths(chapters_root: pathlib.Path, data: ChapterData) -> ChapterData: 200 | """ 201 | build the actual paths to each of the chapter files from the chapter root 202 | """ 203 | new_chapter_root = chapters_root / data.chapter_root 204 | return ChapterData( 205 | chapter_root=new_chapter_root, 206 | code_supplements=[ new_chapter_root/supp for supp in data.code_supplements ]) 207 | 208 | CHAPTER_TITLE_LINE_RE = re.compile(r"^[#]+\s+(\S+.*)") 209 | def reformat_chapter_data( 210 | output_int_path: pathlib.Path, 211 | chapter_number: int, 212 | chapter_data: ChapterData 213 | ) -> tuple[pathlib.Path, pathlib.Path|None]: 214 | """ 215 | reformat the chapter data for nicer presentation in the ebook 216 | - make the each chapter heading an H1 tag 217 | - collate the supplemental code files into their own "appendix" chapter 218 | """ 219 | 220 | logging.debug("formatting chapter %d: %s", chapter_number, str(chapter_data.chapter_root)) 221 | 222 | main_chapter_file = chapter_data.chapter_root / "README.md" 223 | with open(main_chapter_file, encoding="utf8") as f: 224 | reformatted_chapter_content_lines = [ line.rstrip() for line in f.readlines() ] 225 | if len(reformatted_chapter_content_lines) < 1: 226 | raise RuntimeError(f"missing title line in {main_chapter_file}") 227 | chapter_title_line_match = CHAPTER_TITLE_LINE_RE.match(reformatted_chapter_content_lines[0]) 228 | if chapter_title_line_match is None: 229 | raise RuntimeError(f"first line isn't title line in {main_chapter_file}: {reformatted_chapter_content_lines[0]}") 230 | 231 | section_name: str = chapter_title_line_match.group(1) 232 | new_chapter_title_line = "# Ch %d. %s" % (chapter_number, section_name) 233 | 234 | reformatted_chapter_content_lines[0] = new_chapter_title_line 235 | 236 | section_name_file_id = section_name.replace(" ", "_") 237 | chapter_file_prefix = f"CHAPTER_{chapter_number:02d}_{section_name_file_id}" 238 | reformatted_chapter_file_path = output_int_path / f"{chapter_file_prefix}_MAIN.md" 239 | 240 | write_file_lines(reformatted_chapter_file_path, reformatted_chapter_content_lines) 241 | logging.debug("generated %s", reformatted_chapter_file_path) 242 | 243 | if len(chapter_data.code_supplements) > 0: 244 | supplement_chapter_title = f"# Supplements: {section_name}" 245 | new_chapter_supplement_path = output_int_path / f"{chapter_file_prefix}_SUPP.md" 246 | 247 | supplement_chapter_lines = [] 248 | 249 | supplement_chapter_lines.append(supplement_chapter_title) 250 | for supp in chapter_data.code_supplements: 251 | supplement_chapter_lines.append("") 252 | 253 | supplement_chapter_lines.append(f"## {supp.name}") # add the supp sub-section title line 254 | supplement_chapter_lines.append("") 255 | 256 | # add a codeblock for each of the injected code supplement files 257 | supplement_chapter_lines.append("```C") 258 | with open(supp, encoding="utf8") as f: 259 | supplement_chapter_lines += ( line.rstrip() for line in f.readlines() ) 260 | supplement_chapter_lines.append("```") 261 | 262 | write_file_lines(new_chapter_supplement_path, supplement_chapter_lines) 263 | logging.debug("generated %s", new_chapter_supplement_path) 264 | 265 | else: 266 | new_chapter_supplement_path = None 267 | 268 | return (reformatted_chapter_file_path, new_chapter_supplement_path) 269 | 270 | def write_file_lines(filepath: pathlib.Path, lines: list[str], line_separator='\n', encoding="utf8"): 271 | """ 272 | a quick helper to add normalized line endings and write out a collection of lines to a file 273 | """ 274 | with open(filepath, "w", encoding=encoding) as f: 275 | f.writelines(line + line_separator for line in lines) 276 | 277 | def verify_pandoc_installed() -> None: 278 | """ 279 | ensure pandoc is available and working 280 | """ 281 | pandoc_args = [ PANDOC_EXE_NAME, "--version" ] 282 | try: 283 | subprocess.run(pandoc_args, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 284 | logging.debug("verified pandoc installation") 285 | except subprocess.CalledProcessError as ex: 286 | logging.error("Pandoc installation issue! retcode=%d", ex.returncode) 287 | logging.error(" cmd: %s", ex.cmd) 288 | logging.error(" args: %s", ex.args) 289 | logging.error(" stdout: %s", ex.stdout) 290 | logging.error(" stderr: %s", ex.stderr) 291 | sys.exit() 292 | # pylint: disable=broad-exception-caught 293 | except Exception as ex: 294 | logging.error("Pandoc installation issue! %r", ex) 295 | logging.error(" tested with %s", pandoc_args) 296 | sys.exit() 297 | 298 | if __name__ == "__main__": 299 | main() 300 | -------------------------------------------------------------------------------- /dsl/dsl.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "dsl.h" 7 | #include "error/error.h" 8 | #include "list/list.h" 9 | 10 | enum hc_order hc_strcmp(const char *x, const char *y) { 11 | const int result = strcmp(x, y); 12 | if (!result) { return HC_EQ; } 13 | return (result < 0) ? HC_LT : HC_GT; 14 | } 15 | 16 | char *hc_upcase(char *s) { 17 | while (*s) { 18 | *s = toupper(*s); 19 | s++; 20 | } 21 | 22 | return s; 23 | } 24 | 25 | static void lib_print(struct hc_vm *vm, struct hc_sloc sloc) { 26 | struct hc_value *v = hc_vm_pop(vm); 27 | struct hc_dsl *dsl = hc_baseof(vm, struct hc_dsl, vm); 28 | hc_value_print(v, dsl->out); 29 | hc_value_deinit(v); 30 | } 31 | 32 | static void lib_upcase(struct hc_vm *vm, struct hc_sloc sloc) { 33 | struct hc_value *v = hc_vm_peek(vm); 34 | 35 | if (v->type != &HC_STRING) { 36 | hc_throw("Error in %s: Expected string (%s)", 37 | hc_sloc_string(&sloc), v->type->name); 38 | } 39 | 40 | hc_upcase(v->as_string); 41 | } 42 | 43 | struct env_item { 44 | char *key; 45 | struct hc_value value; 46 | }; 47 | 48 | static enum hc_order env_cmp(const void *x, const void *y) { 49 | return hc_strcmp(*(const char **)x, *(const char **)y); 50 | } 51 | 52 | static const void *env_key(const void *x) { 53 | return &((const struct env_item *)x)->key; 54 | } 55 | 56 | void hc_dsl_init(struct hc_dsl *dsl, struct hc_malloc *malloc) { 57 | hc_set_init(&dsl->env, malloc, sizeof(struct env_item), env_cmp); 58 | dsl->env.key = env_key; 59 | dsl->out = hc_stdout(); 60 | 61 | hc_vm_init(&dsl->vm, &hc_malloc_default); 62 | hc_dsl_set_fun(dsl, "print", lib_print); 63 | hc_dsl_set_fun(dsl, "upcase", lib_upcase); 64 | } 65 | 66 | static void deinit_env(struct hc_set *env) { 67 | hc_vector_do(&env->items, _it) { 68 | struct env_item *it = _it; 69 | free(it->key); 70 | hc_value_deinit(&it->value); 71 | } 72 | 73 | hc_set_deinit(env); 74 | } 75 | 76 | void hc_dsl_deinit(struct hc_dsl *dsl) { 77 | deinit_env(&dsl->env); 78 | hc_vm_deinit(&dsl->vm); 79 | } 80 | 81 | struct hc_value *hc_dsl_getenv(struct hc_dsl *dsl, const char *key) { 82 | struct env_item *it = hc_set_find(&dsl->env, &key); 83 | return it ? &it->value : NULL; 84 | } 85 | 86 | struct hc_value *hc_dsl_setenv(struct hc_dsl *dsl, 87 | const char *key, 88 | const struct hc_type *type) { 89 | struct env_item *it = hc_set_add(&dsl->env, &key, false); 90 | it->key = strdup(key); 91 | hc_value_init(&it->value, type); 92 | return &it->value; 93 | } 94 | 95 | void hc_dsl_set_fun(struct hc_dsl *dsl, const char *key, hc_vm_fun_t val) { 96 | hc_dsl_setenv(dsl, key, &HC_VM_FUN)->as_other = val; 97 | } 98 | 99 | void hc_dsl_set_string(struct hc_dsl *dsl, const char *key, const char *val) { 100 | hc_dsl_setenv(dsl, key, &HC_STRING)->as_string = strdup(val); 101 | } 102 | 103 | void hc_dsl_eval(struct hc_dsl *dsl, const char *in) { 104 | struct hc_list forms; 105 | hc_list_init(&forms); 106 | hc_defer(hc_forms_free(&forms)); 107 | struct hc_sloc sloc = hc_sloc("eval", 0, 0); 108 | while (hc_read_next(&in, &forms, &sloc)); 109 | const size_t pc = dsl->vm.code.length; 110 | hc_forms_emit(&forms, dsl); 111 | hc_vm_eval(&dsl->vm, pc, -1); 112 | } 113 | 114 | void hc_form_init(struct hc_form *f, 115 | const struct hc_form_type *t, 116 | const struct hc_sloc sloc, 117 | struct hc_list *owner) { 118 | f->type = t; 119 | f->sloc = sloc; 120 | 121 | if (owner) { 122 | hc_list_push_back(owner, &f->owner); 123 | } else { 124 | hc_list_init(&f->owner); 125 | } 126 | } 127 | 128 | void hc_form_emit(struct hc_form *f, struct hc_dsl *dsl) { 129 | assert(f->type->emit); 130 | f->type->emit(f, dsl); 131 | } 132 | 133 | void hc_form_print(struct hc_form *f, struct hc_stream *out) { 134 | assert(f->type->print); 135 | f->type->print(f, out); 136 | } 137 | 138 | struct hc_value *hc_form_value(const struct hc_form *f, struct hc_dsl *dsl) { 139 | return f->type->value ? f->type->value(f, dsl) : NULL; 140 | } 141 | 142 | void hc_form_free(struct hc_form *f) { 143 | hc_list_delete(&f->owner); 144 | assert(f->type->free); 145 | f->type->free(f); 146 | } 147 | 148 | static void call_emit(struct hc_form *_f, struct hc_dsl *dsl) { 149 | struct hc_call *f = hc_baseof(_f, struct hc_call, form); 150 | struct hc_value *t = hc_form_value(f->target, dsl); 151 | 152 | if (!t) { 153 | hc_throw("Error in %s: Missing call target", 154 | hc_sloc_string(&_f->sloc)); 155 | } 156 | 157 | if (t->type != &HC_VM_FUN) { 158 | hc_throw("Error in %s: '%s' isn't callable", 159 | hc_sloc_string(&_f->sloc), 160 | t->type->name); 161 | } 162 | 163 | hc_list_do(&f->args, a) { 164 | hc_form_emit(hc_baseof(a, struct hc_form, owner), dsl); 165 | } 166 | 167 | hc_vm_emit(&dsl->vm, 168 | &HC_CALL, 169 | &(struct hc_call_op){ 170 | .target = t->as_other, 171 | .sloc = _f->sloc 172 | }); 173 | } 174 | 175 | static void call_print(const struct hc_form *_f, struct hc_stream *out) { 176 | struct hc_call *f = hc_baseof(_f, struct hc_call, form); 177 | hc_putc(out, '('); 178 | hc_form_print(f->target, out); 179 | 180 | hc_list_do(&f->args, i) { 181 | hc_putc(out, ' '); 182 | hc_form_print(hc_baseof(i, struct hc_form, owner), out); 183 | } 184 | 185 | hc_putc(out, ')'); 186 | } 187 | 188 | static void call_free(struct hc_form *_f) { 189 | struct hc_call *f = hc_baseof(_f, struct hc_call, form); 190 | hc_form_free(f->target); 191 | 192 | hc_list_do(&f->args, i) { 193 | hc_form_free(hc_baseof(i, struct hc_form, owner)); 194 | } 195 | 196 | free(f); 197 | } 198 | 199 | const struct hc_form_type HC_CALL_FORM = { 200 | .emit = call_emit, 201 | .print = call_print, 202 | .value = NULL, 203 | .free = call_free 204 | }; 205 | 206 | void hc_call_init(struct hc_call *f, 207 | const struct hc_sloc sloc, 208 | struct hc_list *owner, 209 | struct hc_form *target) { 210 | hc_form_init(&f->form, &HC_CALL_FORM, sloc, owner); 211 | f->target = target; 212 | hc_list_init(&f->args); 213 | } 214 | 215 | static void id_emit(struct hc_form *_f, struct hc_dsl *dsl) { 216 | struct hc_id *f = hc_baseof(_f, struct hc_id, form); 217 | struct hc_value *v = hc_dsl_getenv(dsl, f->name); 218 | 219 | if (!v) { 220 | hc_throw("Error in %s: Unknown identifier '%s'", 221 | hc_sloc_string(&_f->sloc), f->name); 222 | } 223 | 224 | struct hc_push_op op; 225 | hc_value_copy(&op.value, v); 226 | hc_vm_emit(&dsl->vm, &HC_PUSH, &op); 227 | } 228 | 229 | static void id_print(const struct hc_form *_f, struct hc_stream *out) { 230 | struct hc_id *f = hc_baseof(_f, struct hc_id, form); 231 | hc_puts(out, f->name); 232 | } 233 | 234 | static struct hc_value *id_value(const struct hc_form *_f, 235 | struct hc_dsl *dsl) { 236 | struct hc_id *f = hc_baseof(_f, struct hc_id, form); 237 | return hc_dsl_getenv(dsl, f->name); 238 | } 239 | 240 | static void id_free(struct hc_form *_f) { 241 | struct hc_id *f = hc_baseof(_f, struct hc_id, form); 242 | free(f->name); 243 | free(f); 244 | } 245 | 246 | const struct hc_form_type HC_ID_FORM = { 247 | .emit = id_emit, 248 | .print = id_print, 249 | .value = id_value, 250 | .free = id_free 251 | }; 252 | 253 | void hc_id_init(struct hc_id *f, 254 | const struct hc_sloc sloc, 255 | struct hc_list *owner, 256 | const char *name) { 257 | hc_form_init(&f->form, &HC_ID_FORM, sloc, owner); 258 | f->name = strdup(name); 259 | } 260 | 261 | static void literal_emit(struct hc_form *_f, struct hc_dsl *dsl) { 262 | struct hc_literal *f = hc_baseof(_f, struct hc_literal, form); 263 | struct hc_push_op op; 264 | hc_value_copy(&op.value, &f->value); 265 | hc_vm_emit(&dsl->vm, &HC_PUSH, &op); 266 | } 267 | 268 | static void literal_print(const struct hc_form *_f, struct hc_stream *out) { 269 | struct hc_literal *f = hc_baseof(_f, struct hc_literal, form); 270 | hc_value_write(&f->value, out); 271 | } 272 | 273 | static struct hc_value *literal_value(const struct hc_form *_f, 274 | struct hc_dsl *dsl) { 275 | struct hc_literal *f = hc_baseof(_f, struct hc_literal, form); 276 | return &f->value; 277 | } 278 | 279 | static void literal_free(struct hc_form *_f) { 280 | struct hc_literal *f = hc_baseof(_f, struct hc_literal, form); 281 | hc_value_deinit(&f->value); 282 | free(f); 283 | } 284 | 285 | const struct hc_form_type HC_LITERAL = { 286 | .emit = literal_emit, 287 | .print = literal_print, 288 | .value = literal_value, 289 | .free = literal_free, 290 | }; 291 | 292 | void hc_literal_init(struct hc_literal *f, 293 | const struct hc_sloc sloc, 294 | struct hc_list *owner) { 295 | hc_form_init(&f->form, &HC_LITERAL, sloc, owner); 296 | } 297 | 298 | void hc_skip_ws(const char **in, struct hc_sloc *sloc) { 299 | for (;; (*in)++) { 300 | switch (**in) { 301 | case ' ': 302 | case '\t': 303 | sloc->col++; 304 | break; 305 | case '\n': 306 | sloc->row++; 307 | sloc->col = 0; 308 | break; 309 | default: 310 | return; 311 | } 312 | } 313 | } 314 | 315 | void hc_read_call(const char **in, 316 | struct hc_list *out, 317 | struct hc_sloc *sloc) { 318 | struct hc_sloc floc = *sloc; 319 | 320 | if (**in != '(') { 321 | hc_throw("Error in %s: Invalid call syntax", 322 | hc_sloc_string(sloc)); 323 | } 324 | 325 | (*in)++; 326 | sloc->col++; 327 | hc_skip_ws(in, sloc); 328 | 329 | if (!hc_read_expr(in, out, sloc)) { 330 | hc_throw("Error in %s: Missing call target", 331 | hc_sloc_string(sloc)); 332 | } 333 | 334 | struct hc_form *t = hc_baseof(hc_list_pop_back(out), 335 | struct hc_form, 336 | owner); 337 | 338 | struct hc_call *f = malloc(sizeof(struct hc_call)); 339 | hc_list_init(&t->owner); 340 | hc_call_init(f, floc, out, t); 341 | 342 | for (bool done = false; !done;) { 343 | hc_skip_ws(in, sloc); 344 | 345 | switch (**in) { 346 | case 0: 347 | hc_form_free(&f->form); 348 | 349 | hc_throw("Error in %s: Open call form", 350 | hc_sloc_string(sloc)); 351 | case ')': 352 | (*in)++; 353 | sloc->col++; 354 | done = true; 355 | continue; 356 | default: 357 | break; 358 | } 359 | 360 | if (!hc_read_expr(in, &f->args, sloc)) { 361 | hc_form_free(&f->form); 362 | 363 | hc_throw("Error in %s: Invalid call syntax", 364 | hc_sloc_string(sloc)); 365 | } 366 | } 367 | } 368 | 369 | bool hc_read_expr(const char **in, 370 | struct hc_list *out, 371 | struct hc_sloc *sloc) { 372 | const char c = **in; 373 | 374 | switch (c) { 375 | case '(': 376 | hc_read_call(in, out, sloc); 377 | return true; 378 | default: 379 | if (isalpha(c)) { 380 | hc_read_id(in, out, sloc); 381 | return true; 382 | } 383 | 384 | break; 385 | } 386 | 387 | return false; 388 | } 389 | 390 | void hc_read_id(const char **in, 391 | struct hc_list *out, 392 | struct hc_sloc *sloc) { 393 | struct hc_sloc floc = *sloc; 394 | struct hc_memory_stream buf; 395 | hc_memory_stream_init(&buf, &hc_malloc_default); 396 | hc_defer(hc_stream_deinit(&buf.stream)); 397 | char c = 0; 398 | 399 | while ((c = **in)) { 400 | if (isspace(c) || c == '(' || c == ')') { 401 | break; 402 | } 403 | 404 | hc_putc(&buf.stream, c); 405 | sloc->col++; 406 | (*in)++; 407 | } 408 | 409 | struct hc_id *f = malloc(sizeof(struct hc_id)); 410 | hc_id_init(f, floc, out, hc_memory_stream_string(&buf)); 411 | } 412 | 413 | bool hc_read_next(const char **in, 414 | struct hc_list *out, 415 | struct hc_sloc *sloc) { 416 | if (**in == '$') { 417 | (*in)++; 418 | hc_read_call(in, out, sloc); 419 | return true; 420 | } 421 | 422 | return hc_read_text(in, out, sloc); 423 | } 424 | 425 | bool hc_read_text(const char **in, 426 | struct hc_list *out, 427 | struct hc_sloc *sloc) { 428 | struct hc_sloc floc = *sloc; 429 | const char *start = *in; 430 | 431 | while (**in && **in != '$') { 432 | if (**in == '\n') { 433 | sloc->row++; 434 | } else { 435 | sloc->col++; 436 | } 437 | 438 | (*in)++; 439 | } 440 | 441 | size_t n = *in - start; 442 | 443 | if (n) { 444 | struct hc_value v; 445 | hc_value_init(&v, &HC_STRING)->as_string = strndup(start, n); 446 | struct hc_literal *vf = malloc(sizeof(struct hc_literal)); 447 | hc_literal_init(vf, floc, out); 448 | vf->value = v; 449 | struct hc_id *t = malloc(sizeof(struct hc_literal)); 450 | hc_id_init(t, floc, NULL, "print"); 451 | struct hc_call *c = malloc(sizeof(struct hc_call)); 452 | hc_call_init(c, floc, out, &t->form); 453 | return true; 454 | } 455 | 456 | return false; 457 | } 458 | 459 | void hc_forms_free(struct hc_list *in) { 460 | hc_list_do(in, i) { 461 | hc_form_free(hc_baseof(i, struct hc_form, owner)); 462 | } 463 | } 464 | 465 | void hc_forms_emit(struct hc_list *in, struct hc_dsl *dsl) { 466 | hc_list_do(in, i) { 467 | hc_form_emit(hc_baseof(i, struct hc_form, owner), dsl); 468 | } 469 | } 470 | --------------------------------------------------------------------------------