aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--LICENSE24
-rw-r--r--Makefile5
-rw-r--r--README.md80
-rw-r--r--include/container.h35
-rw-r--r--include/dynstring.h1
-rw-r--r--include/stringbuilder.h114
-rw-r--r--ste/.gitignore3
-rw-r--r--ste/README.md48
-rw-r--r--ste/example/Makefile14
-rw-r--r--ste/example/main.c15
-rw-r--r--ste/example/tmpl.c.ste11
-rw-r--r--ste/ste.c57
-rw-r--r--tests/sb.c37
13 files changed, 413 insertions, 31 deletions
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..fdddb29
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,24 @@
+This is free and unencumbered software released into the public domain.
+
+Anyone is free to copy, modify, publish, use, compile, sell, or
+distribute this software, either in source code form or as a compiled
+binary, for any purpose, commercial or non-commercial, and by any
+means.
+
+In jurisdictions that recognize copyright laws, the author or authors
+of this software dedicate any and all copyright interest in the
+software to the public domain. We make this dedication for the benefit
+of the public at large and to the detriment of our heirs and
+successors. We intend this dedication to be an overt act of
+relinquishment in perpetuity of all present and future rights to this
+software under copyright law.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+For more information, please refer to <https://unlicense.org>
diff --git a/Makefile b/Makefile
index 3a44550..bbcb337 100644
--- a/Makefile
+++ b/Makefile
@@ -45,3 +45,8 @@ json: tests/json.c include/jacson.h
$(CC) -o $@ tests/json.c $(CFLAGS)
./$@
rm $@
+
+stringbuilder: tests/sb.c include/stringbuilder.h
+ $(CC) -o $@ tests/sb.c $(CFLAGS)
+ ./$@
+ rm $@
diff --git a/README.md b/README.md
index 81c83da..93d6a75 100644
--- a/README.md
+++ b/README.md
@@ -1,37 +1,69 @@
-# What is this?
-This is just a collection of (usually single-purpos) header files that I use refularly in my code
+# What is this?
+This is just a collection of (usually single-purpose) header files that I use refularly in my code
# Common conventions
-* As these are header-only libraries, there is a macro in form `HEADER_NAME_IMPLEMENTATION` that has to be defined before including the header. A header can only be included once while this macro is defined, otherwise you get multiple definition errors.
-* Names starting with underscores are kind of private-use for this libraries, but they may be declared not `static` because they are used in macros that expose API.
+* As these are header-only libraries, there is a macro in form `HEADER_NAME_IMPLEMENTATION` that has to be defined
+ before including the header. A header can only be included once while this macro is defined, otherwise you get
+ multiple definition errors.
+* Names starting with underscores are kind of private-use for this libraries, but they may be declared not `static`
+ because they are used in macros that expose API.
* `snake_case`
* Function-like macros are not all-caps.
-* For now i only support GCC-based compilers. I will consider removing this dependency in future (e.g. getting rid of all `typeof` uses)
-# Descriptions for heders:
+* For now i only support GCC-based compilers. I will consider removing this dependency in future (e.g. getting rid of
+ all `typeof` uses)
+# Descriptions for heders:
## [`container.h`](include/container.h)
-* Summary: originally was several headers, but since `hash_map.h` used `dynarray.h`, which itself used `utility.h`, it was too complex to work with. So i combined everything in a single header.
-* How to use: Define `CONTAINER_IMPLEMENTATION` macro before including `container.h`. It's probably better to `#undef` it after inclusion as well. To use some functions (`__default_int_cmp` and alikes), define `CONTAINER_EXPOSE_HELPERS`.
+* Summary: originally was several headers, but since `hash_map.h` used `dynarray.h`, which itself used `utility.h`, it
+ was too complex to work with. So i combined everything in a single header.
+* How to use: Define `CONTAINER_IMPLEMENTATION` macro before including `container.h`. It's probably better to `#undef`
+ it after inclusion as well. To use some functions (`__default_int_cmp` and alikes), define `CONTAINER_EXPOSE_HELPERS`.
* Examples: See [tests](tests)
* Notes: if compiled in shared object, must be compiled with -fPIC
### Additional macros
-* SHRINK\_RESIGING\_ARRAY: Reallocate the array to smaller capacity when it's size becomes too small. Not impelemented at the moment.
+* SHRINK\_RESIGING\_ARRAY: Reallocate the array to smaller capacity when it's size becomes too small. Not impelemented
+ at the moment.
+* Disable parts of the library: `CONTAINER_DISABLE_HMAP` and `CONTAINER_DISABLE_HSET` will disable hash map and hash set
+ implementation correspondingly. `CONTAINER_DISABLE_LINKED_LIST` will disable linked list, and since hmap and hset
+ depend on it, will disable them as well. `CONTAINER_DISABLE_HASH` will disable both hash set and hash map.
+ `CONTAINER_DISABLE_ARRAY` will disable array implementation (and hmap and hset as a consequence).
+ `CONTAINER_DISABLE_UTILITY` will disable utilities, which will in turn disable everything else.
+ `CONTAINER_DISABLE_ALL` will not disable anything else but will leave you with unuseable header. Not useful, huh?
## [`rstypes.h`](include/rstypes.h)
* Summary: rust type aliases (like `u32`, `f128` e.t.c.)
-* How to use: if you don't need `i128` or `u128`, then just include the header. If you do need them, define macro `RS_TYPES_USE_128`.
+* How to use: if you don't need `i128` or `u128`, then just include the header. If you do need them, define macro
+ `RS_TYPES_USE_128`.
* Examples: See [tests](tests/types.c).
-* Notes: Not many compilers support 128-bit wide integers, and ones which DO support them tend to warn you that these are non ISO-C. That's why I've put them behind a macro. If they are not supported, their usage will fail with compilation error.
-## [`embed.h`](include/embed.h)
+* Notes: Not many compilers support 128-bit wide integers, and ones which DO support them tend to warn you that these
+ are non ISO-C. That's why I've put them behind a macro. If they are not supported, their usage will fail with
+ compilation error.
+## [`embed.h`](include/embed.h) \[\[NOT IN DEVELOPMENT\]\]
* Summary: Code generator for embedding resources directly into an executable.
-* How to use: It exposes C interface, so in order to use it, you will need to have a C program that builds resources for you. It has it's own repo, but i will be adding CLI to there soon.
-* Examples: see [this repo](https://github.com/justanothercatgirl/embed_test.c/)
+* How to use: It exposes C interface, so in order to use it, you will need to have a C program that builds resources for
+ you. It has it's own repo, but i will be adding CLI to there soon.
+* Examples: see [this repo](https://github.com/justanothercatgirl/embed_test.c/)
## [`jacson.h`](include/jacson.h)
* Summary: Spec-compliant json serializer and parser
-* How to use: define `JACSON_IMPLEMENTATION` macro. `CONTAINER_IMPLEMENTATION` MUST be defined as well; either somewhere earlier in the code before including `container.h` or just before `jacson.h`. this JSON implementation uses `array` and `hash_set`.
-* Macros: `JACSON_EXPORT_RSNPRINTF` will make symbols `__jacson_rsnprintf` and `__jacson_rsnputc` visible. These 2 functions allow for appending a string to a buffer obtained from malloc and resizing it (realloc) if needed. They're not visible by default because they do not have obvious friendly signature.
-## `build.h`
-nearest TODO for now.
-* Summary: a build system based on C. To compile something under it, you do something like `cc -o builder builder.c && ./builder`. The idea is stolen from [Tsoding](https://example.com)
-# Warning!
-Everything here is written by a relatively inexperienced student (me), so I guarantee basically nothing. Memory leaks? I am sorry for them, but also not responsible. Security? Haven't heard of that either. That's just how it is. But I try to make everything as good as possible, so you can use the code after a careful review.
-# License
-I did not decide on it yet, but I am inclined towards MIT or even public domain. Don't want to restrict the usage with LGPL, because it's not like this code is a big deal, anyone could write it. Also, I don't want to deal with license issues that I can create for my future self, so yeah, that's how it is. For now, public domain.
-
+* How to use: define `JACSON_IMPLEMENTATION` macro. `CONTAINER_IMPLEMENTATION` MUST be defined as well; either somewhere
+ earlier in the code before including `container.h` or just before `jacson.h`. this JSON implementation uses `array`
+ and `hash_set`.
+* Macros: `JACSON_EXPORT_RSNPRINTF` will make symbols `__jacson_rsnprintf` and `__jacson_rsnputc` visible. These 2
+ functions allow for appending a string to a buffer obtained from malloc and resizing it (realloc) if needed. They're
+ not visible by default because they do not have obvious friendly signature.
+## [`stringbuilder.h`](include/stringbuilder.h)
+* Summary: string builder. Quite simple.
+* How to use: include the header, define `STRINGBUILDER_IMPLEMENTATION`. Done.
+* Notes: Pretty small at the moment, but I plan on expanding it on-demand. The interfaces are called `jac_sb` despite
+ the header being called `stringbuilder.h`. You wouldn't want to type `struct stringbuilder` on your keyboard each
+ time, would you? but `sb.h` it too vague, it could be `sucking_balls.h`, `sizeable_breasts.h`, `system_breaker.h`
+ e.t.c.
+## `build.h`
+ TODO for now.
+* Summary: a build system based on C. To compile something under it, you do something like `cc -o builder builder.c &&
+ ./builder`. The idea is stolen from [Tsoding](https://example.com) # Warning! Everything here is written by a
+ relatively inexperienced student (me), so I guarantee basically nothing. Memory leaks? I am sorry for them, but also
+ not responsible. Security? Haven't heard of that either. That's just how it is. But I try to make everything as good
+ as possible, so you can use the code after a careful review. # License The [Unlicense license](https://unlicense.org).
+ Also known as [fuck google employees](https://opensource.google/documentation/reference/patching#forbidden) license
+ (nothing personal), equivalent to BSD0, CC0 or Public Domain.
+## `ste/`
+Please refer to [README](ste/READNE.md) in ste subdirectory.
+ <!--vim:tw=120-->
diff --git a/include/container.h b/include/container.h
index 5f40e4a..0234abe 100644
--- a/include/container.h
+++ b/include/container.h
@@ -1,6 +1,12 @@
+#ifndef CONTAINER_DISABLE_ALL
#ifndef JUSTANOTHERCATGIRL_HEADERS_CONTAINER
#define JUSTANOTHERCATGIRL_HEADERS_CONTAINER
+#if defined(CONTAINER_DISABLE_ARRAY) || defined(CONTAINER_DISABLE_ARRAY) || defined(CONTAINER_DISABLE_HASH)
+# define CONTAINER_DISABLE_HMAP
+# define CONTAINER_DISABLE_HSET
+#endif
+
#include <stdint.h>
#include <stdarg.h>
#include <stdio.h>
@@ -10,6 +16,7 @@
/* ----------------------------------------------------------------- */
/* -----------------UTILITY HEADER---------------------------------- */
/* ----------------------------------------------------------------- */
+#ifndef CONTAINER_DISABLE_UTILITY
#define stringify(val) _stringify_helper(val)
#define _stringify_helper(val) #val
#define string_concat_separator(first, ...) stringify(first) ";" string_concat_separator(__VA_ARGS__)
@@ -68,7 +75,7 @@ typedef void*(*memcpy_t)(void* restrict, const void*, size_t);
#else
# define _CONTAINER_STATIC static
#endif /* CONTAINER_EXPOSE_HELPERS */
-
+#endif /* CONTAINER_DISABLE_UTILITY */
/* ----------------------------------------------------------------- */
/* -------------------------ARRAY HEADER---------------------------- */
@@ -87,7 +94,7 @@ typedef void*(*memcpy_t)(void* restrict, const void*, size_t);
/* TODO: implement in operations that change the array size */
/* #define SHINK_RESIZING_ARRAY */
-
+#ifndef CONTAINER_DISABLE_ARRAY
/* size of the array header. Should not be used directly, unless you know what you are doing */
#define DYNARRAY_HEADER_SIZE sizeof(struct _dynarray_header)
@@ -154,11 +161,13 @@ void* array_copy(void* old);
/* Returns bool. Assumes that the array is sorted. */
char array_binary_search(void* array, void* element, qsort_cmp_t cmp);
struct linked_list array_to_ll(void* array);
+#endif /* CONTAINER_DISABLE_ARRAY */
/* ----------------------------------------------------------------- */
/* ----------------------LINKED LIST HEADER------------------------- */
/* ----------------------------------------------------------------- */
+#ifndef CONTAINER_DISABLE_LINKED_LIST
typedef void(*free_t)(void*);
struct __linked_list_meta {
size_t element_size;
@@ -292,11 +301,13 @@ void ll_set_free(struct linked_list* list, free_t new_free);
/* Creates a deep copy of the list, meaning that it must be free'd as any other list */
/* Uses `cpy` function to copy data to new location. If NULL, memcpy is used. */
struct linked_list ll_deep_copy(const struct linked_list* list, memcpy_t cpy);
+#endif /* CONTAINER_DISABLE_LINKED_LIST */
/* ----------------------------------------------------------------- */
/* ------------------------HASH MAP HEADER-------------------------- */
/* ----------------------------------------------------------------- */
+#ifndef CONTAINER_DISABLE_HMAP
#ifndef HMAP_MAX_BUCKET_SIZE
# define HMAP_MAX_BUCKET_SIZE 8
#endif
@@ -378,11 +389,13 @@ char hmapi_le(const struct hash_map_iter *a, const struct hash_map_iter* b);
char hmapi_ne(const struct hash_map_iter *a, const struct hash_map_iter* b);
/* 1 if `iter` is an end iterator, 0 otherwise */
char hmapi_end(const struct hash_map_iter* iter);
+#endif /* CONTAINER_DISABLE_HMAP */
/* ----------------------------------------------------------------- */
/* ------------------------HASH SET HEADER-------------------------- */
/* ----------------------------------------------------------------- */
+#ifndef CONTAINER_DISABLE_HSET
#ifndef HSET_MAX_BUCKET_SIZE
# define HSET_MAX_BUCKET_SIZE 8
#endif
@@ -454,12 +467,14 @@ char hseti_le(const struct hash_set_iter *a, const struct hash_set_iter* b);
char hseti_ne(const struct hash_set_iter *a, const struct hash_set_iter* b);
/* 1 if `iter` is an end iterator, 0 otherwise */
char hseti_end(const struct hash_set_iter* iter);
+#endif /* CONTAINER_DISABLE_HSET */
/* ------------------------------------------------------------------------- */
/* ------From now on, the rest of the header is implementation details------ */
/* -------------------the API and documentation end here-------------------- */
/* ------------------------------------------------------------------------- */
+#ifndef CONTAINER_DISABLE_ARRAY
enum _dynarray_header_idx {
_dah_idx_member_size = 0,
_dah_idx_capacity = 1,
@@ -481,6 +496,7 @@ void *_memshrink_array(void *dynarray);
void *_insert_to_index_dynarray(void *const dynarray, const void *const element, size_t el_size, size_t index);
void* _array_extend(void* array, void* buffer, size_t len);
void* _array_pop_at(void* array, size_t idx);
+#endif /* CONTAINER_DISABLE_ARRAY */
/* uncomment in dev mode so that LSP highlights the code */
/* #define CONTAINER_IMPLEMENTATION */
@@ -490,6 +506,7 @@ void* _array_pop_at(void* array, size_t idx);
/* ---------------------UTILITY IMPLEMENTATION---------------------- */
/* ----------------------------------------------------------------- */
+#ifndef CONTAINER_DISABLE_UTILITY
#ifndef __GNUC__
unsigned long __bit_scan_32(int32_t number) {
# ifdef _MSC_VER
@@ -572,12 +589,13 @@ const qsort_cmp_t __qsort_cmps[64] = {
0, 0, 0, __default_long_long_cmp,
};
#endif /* __GNUC__ */
-
+#endif /* CONTAINER_DISABLE_UTILITY */
/* ----------------------------------------------------------------- */
/* ----------------------ARRAY IMPLEMENTATION----------------------- */
/* ----------------------------------------------------------------- */
+#ifndef CONTAINER_DISABLE_ARRAY
void *_alloc_dynarray(size_t el_size, size_t len)
{
byte *data = (byte *)malloc(el_size * len + DYNARRAY_HEADER_SIZE);
@@ -699,10 +717,12 @@ struct linked_list array_to_ll(void* array) {
struct linked_list ret = ll_create_from_buffer(array_element_size(array), array, array_size(array));
return ret;
}
+#endif /* CONTAINER_DISABLE_ARRAY */
/* ----------------------------------------------------------------- */
/* ------------------LINKED LIST IMPLEMENTATION--------------------- */
/* ----------------------------------------------------------------- */
+#ifndef CONTAINER_DISABLE_LINKED_LIST
struct linked_list ll_create(size_t memb_size) {
struct __linked_list_meta meta = {
.assumed_size = 0,
@@ -1057,12 +1077,13 @@ struct linked_list ll_deep_copy(const struct linked_list* list, memcpy_t cpy) {
}
return ret;
}
-
+#endif /* CONTAINER_DISABLE_LINKED_LIST */
/* ----------------------------------------------------------------- */
/* ---------------------HASH MAP IMPLEMENTATION--------------------- */
/* ----------------------------------------------------------------- */
+#ifndef CONTAINER_DISABLE_HMAP
void __hmap_ll_custom_free(void* data) {
struct hmap_pair *pair = data;
free(pair->key);
@@ -1252,12 +1273,13 @@ char hmapi_ne(const struct hash_map_iter *a, const struct hash_map_iter* b) {
char hmapi_end(const struct hash_map_iter* iter) {
return iter->current_node == NULL || iter->bucket_pos == SIZE_MAX;
}
+#endif /* CONTAINER_DISABLE_HMAP */
/* ----------------------------------------------------------------- */
/* ---------------------HASH SET IMPLEMENTATION--------------------- */
/* ----------------------------------------------------------------- */
-
+#ifndef CONTAINER_DISABLE_HSET
struct hash_set hset_new(const size_t el_size, hset_equal_fn eq, hset_hash_fn hash) {
struct hash_set ret = {
.buckets = array_new(struct linked_list, HMAP_INIT_SIZE),
@@ -1416,8 +1438,9 @@ char hseti_ne(const struct hash_set_iter *a, const struct hash_set_iter* b) {
char hseti_end(const struct hash_set_iter* iter) {
return iter->current_node == NULL || iter->bucket_pos == SIZE_MAX;
}
+#endif /* CONTAINER_DISABLE_HSET */
#endif /* CONTAINER_IMPLEMENTATION */
#endif /* JUSTANOTHERCATGIRL_HEADERS_CONTAINER */
-
+#endif /* CONTAINER_DISABLE_ALL */
/* vim: set ts=8 noet: */
diff --git a/include/dynstring.h b/include/dynstring.h
deleted file mode 100644
index b3b23a0..0000000
--- a/include/dynstring.h
+++ /dev/null
@@ -1 +0,0 @@
-/* vim: set ts=8 noet: */
diff --git a/include/stringbuilder.h b/include/stringbuilder.h
new file mode 100644
index 0000000..e695082
--- /dev/null
+++ b/include/stringbuilder.h
@@ -0,0 +1,114 @@
+#ifndef JAC_STRINGBUILDER
+#define JAC_STRINGBUILDER
+
+#include <stddef.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+typedef struct jac_sb {
+ char *data;
+ size_t size;
+ size_t cap;
+} jac_sb;
+
+jac_sb jac_sb_empty(void);
+jac_sb jac_sb_new(size_t size);
+jac_sb jac_sb_from_buf(const char* buf);
+jac_sb jac_sb_from_buf_n(const char* buf, size_t size);
+
+void jac_sb_append_buf(jac_sb *sb, const char *c);
+void jac_sb_append_buf_n(jac_sb *sb, const char *c, size_t n);
+void jac_sb_putc(jac_sb *sb, char c);
+void jac_sb_snprintf(jac_sb *sb, const char* fmt, ...);
+void jac_sb_vsnprintf(jac_sb *sb, const char* fmt, va_list list);
+
+void jac_sb_free(jac_sb sb);
+
+#define STRINGBUILDER_IMPLEMENTATION
+#ifdef STRINGBUILDER_IMPLEMENTATION
+
+#define __SB_REALLOC(sb, n) \
+do { \
+ if (sb->size + n >= sb->cap) { \
+ size_t rsz = 1UL << \
+ (64 - __builtin_clzl((unsigned long)(sb->size+n))); \
+ sb->data = realloc(sb->data, rsz + 1); \
+ sb->cap = rsz; \
+ } \
+ sb->size += n; \
+} while(0) \
+
+jac_sb jac_sb_empty(void) {
+ return (jac_sb){.data = calloc(1, 1), .size = 0, .cap = 0};
+}
+jac_sb jac_sb_new(size_t size) {
+ return (jac_sb){.data = calloc(size + 1, 1), .size = 0, .cap = size};
+}
+jac_sb jac_sb_from_buf(const char* buf) {
+ size_t ln = strlen(buf);
+ jac_sb ret = {.data = malloc(ln + 1), .size = ln, .cap = ln};
+ memcpy(ret.data, buf, ln);
+ ret.data[ret.size] = '\0';
+ return ret;
+}
+jac_sb jac_sb_from_buf_n(const char* buf, size_t size) {
+ jac_sb ret = {.data = malloc(size + 1), .size = size, .cap = size};
+ memcpy(ret.data, buf, size);
+ ret.data[size] = '\0';
+ return ret;
+}
+
+void jac_sb_append_buf(jac_sb *sb, const char *c) {
+ jac_sb_append_buf_n(sb, c, strlen(c));
+}
+void jac_sb_append_buf_n(jac_sb *sb, const char *c, size_t n) {
+ __SB_REALLOC(sb, n);
+ memcpy(sb->data + sb->size - n, c, n);
+ sb->data[sb->size] = '\0';
+}
+void jac_sb_putc(jac_sb *sb, char c) {
+ if (sb->size + 1 >= sb->cap) {
+ sb->cap <<= 1;
+ sb->data = realloc(sb->data, sb->cap + 1);
+ }
+ sb->data[sb->size++] = c;
+ sb->data[sb->size] = '\0';
+}
+
+void jac_sb_snprintf(jac_sb *sb, const char* fmt, ...) {
+ va_list l;
+ va_start(l, fmt);
+ jac_sb_vsnprintf(sb, fmt, l);
+}
+void jac_sb_vsnprintf(jac_sb *sb, const char* fmt, va_list list) {
+ va_list ls1;
+ size_t ln;
+try_write:
+ /*cap: 4, size: 2
+ * | a | b | \0 | \0 | \0 |
+ */
+ va_copy(ls1, list);
+ ln = vsnprintf(sb->data + sb->size, sb->cap - sb->size + 1, fmt, ls1);
+ va_end(ls1);
+ if (ln >= sb->cap - sb->size + 1) {
+ sb->cap <<= 1;
+ sb->data = realloc(sb->data, sb->cap + 1);
+ goto try_write;
+ }
+ va_end(list);
+ sb->size += ln;
+}
+
+void jac_sb_free(jac_sb sb) {
+ free(sb.data);
+}
+
+
+#undef __SB_REALLOC
+#endif /* STRINGBUILDER_IMPLEMENTATION */
+
+#endif /* JAC_STRINGBUILDER */
+
+/* vim: set ts=8 noet: */
diff --git a/ste/.gitignore b/ste/.gitignore
new file mode 100644
index 0000000..953abb8
--- /dev/null
+++ b/ste/.gitignore
@@ -0,0 +1,3 @@
+tmpl.c
+main
+ste
diff --git a/ste/README.md b/ste/README.md
new file mode 100644
index 0000000..680f9a4
--- /dev/null
+++ b/ste/README.md
@@ -0,0 +1,48 @@
+# Simple Template Engine for C
+The idea is stolen from [Tsoding](https://github.com/tsoding).
+
+This is a template engine for the C programming language.
+A really simple one, to be honset. To see how it works, it's best to just check
+out examples, but in a nutshell: initially, everything is a string. After `%`
+sign, it becomes C source code. basically, `%` sign acts as a flip-flop switch
+between C and text. All of the text is wrapped inside the `OUT` macro, which
+must be defined in the source code (name may be customized via command-line
+arguments). To use the code, `#include generated.c` in the middle of function
+where you need this template.
+
+# Example
+`main.c`:
+```c
+#define STRINGBUILDER_IMPLEMENTATION
+#include "stringbuilder.h" /* refer to [c_headers](https://git.twistea.su/c_headers) */
+#include <stdio.h>
+
+int main(void) {
+ jac_sb sb = jac_sb_empty();
+ #define OUT(str) jac_sb_append_buf(&sb, str)
+ #define INT(i) jac_sb_snprintf(&sb, "%i", i); /* Note the semicolon */
+ #include "tmpl.c"
+ #undef OUT
+ printf("%s\n", sb.data);
+ jac_sb_free(sb);
+ return 727;
+}
+```
+
+template file (`tmpl.c.ste`):
+```html
+<!DOCTYPE html>
+<html>
+ <body>
+ %
+ for (int i = 0; i < 10; ++i) {
+ %<p>This is paragraph number % INT(i) %</p>%
+ }
+ %
+ </body>
+</html>
+```
+
+Build: `cc ste.c -o ste && ste tmpl.c.ste && cc main.c -o main`
+
+<!--vim:tw=80-->
diff --git a/ste/example/Makefile b/ste/example/Makefile
new file mode 100644
index 0000000..f8a6ef4
--- /dev/null
+++ b/ste/example/Makefile
@@ -0,0 +1,14 @@
+
+.SUFFIXES:
+
+all: main
+
+ste: ../ste.c
+ $(CC) -o $@ $<
+
+%.c: %.c.ste ste
+ ./ste -m OUTPUT $<
+
+main: main.c tmpl.c
+ $(CC) -o $@ $<
+
diff --git a/ste/example/main.c b/ste/example/main.c
new file mode 100644
index 0000000..18850d9
--- /dev/null
+++ b/ste/example/main.c
@@ -0,0 +1,15 @@
+#define STRINGBUILDER_IMPLEMENTATION
+#include "../../include/stringbuilder.h" /* refer to [c_headers](https://git.twistea.su/c_headers) */
+#include <stdio.h>
+
+int main(void) {
+ jac_sb sb = jac_sb_empty();
+ #define OUTPUT(str) jac_sb_append_buf(&sb, str)
+ #define INTEGER(i) jac_sb_snprintf(&sb, "%i", i); /* Note the semicolon */
+ # include "tmpl.c"
+ #undef OUTPUT
+ #undef INTEGER
+ printf("%s\n", sb.data);
+ jac_sb_free(sb);
+ return 727;
+}
diff --git a/ste/example/tmpl.c.ste b/ste/example/tmpl.c.ste
new file mode 100644
index 0000000..7f486cd
--- /dev/null
+++ b/ste/example/tmpl.c.ste
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+ <body>
+ %
+ for (int i = 0; i < 10; ++i) {
+ %<p>This is paragraph number % INTEGER(i) %</p>%
+ }
+ %
+ </body>
+</html>
+
diff --git a/ste/ste.c b/ste/ste.c
new file mode 100644
index 0000000..43252b9
--- /dev/null
+++ b/ste/ste.c
@@ -0,0 +1,57 @@
+#include <stdio.h>
+#include <string.h>
+
+void process_files(FILE* in, FILE* out, const char *macro) {
+ enum {MODESTR = 0, MODESRC = 1} a = MODESTR;
+ fprintf(out, "%s(\"", macro);
+ for (;;) {
+ int c = fgetc(in);
+ if (feof(in)) break;
+ if (c == '%') {
+ a = !a;
+ if (a == MODESTR) fprintf(out, " %s(\"", macro);
+ else fputs("\");", out);
+ continue;
+ }
+ if (a == MODESTR) fprintf(out, "\\x%02X", c);
+ else (fputc(c, out));
+ }
+ if (a == MODESTR) fputs("\");", out);
+}
+
+int main(int argc, char **argv) {
+ const char *macro = "OUT";
+ char *filename = NULL;
+ FILE *in, *out;
+
+ if (argc < 2) {
+ usage:
+ fprintf(stderr, "Usage: %s [-m name] <file.ste>\n", *argv);
+ return 1;
+ }
+
+ for (int i = 0; i < argc; ++i) {
+ if (!strcmp("-m", argv[i])) macro = argv[++i];
+ else filename = argv[i];
+ }
+ if (!filename || strlen(filename) <= 4) goto usage;
+
+ in = fopen(filename, "r");
+ for (char *fn = filename; fn[3]; ++fn) {
+ if (!strncmp(".ste", fn, 4)) {
+ *fn = '\0';
+ break;
+ }
+ }
+ out = fopen(filename, "w");
+ if (!in || !out) {
+ perror("fopen");
+ goto usage;
+ }
+
+ process_files(in, out, macro);
+
+ fclose(in);
+ fclose(out);
+ return 0;
+}
diff --git a/tests/sb.c b/tests/sb.c
new file mode 100644
index 0000000..8542a88
--- /dev/null
+++ b/tests/sb.c
@@ -0,0 +1,37 @@
+#define STRINGBUILDER_IMPLEMENTATION
+#include "../include/stringbuilder.h"
+
+#include <stdio.h>
+
+#define PRINTF(sb) printf("jac_sb { .data = \"%s\", .size = %zu, .cap = %zu }\n", sb.data, sb.size, sb.cap)
+
+int main() {
+ jac_sb sb = jac_sb_from_buf("Hello, world!");
+ PRINTF(sb);
+ for (size_t i = 0; i < 10; ++i) {
+ jac_sb_append_buf(&sb, " Actually this is a very long sentence that will probably cause several reallocations ");
+ jac_sb_append_buf(&sb, " Actually this is a very long sentence that will probably cause several reallocations ");
+ jac_sb_append_buf(&sb, " Actually this is a very long sentence that will probably cause several reallocations ");
+ jac_sb_append_buf(&sb, " Actually this is a very long sentence that will probably cause several reallocations ");
+ jac_sb_append_buf(&sb, " Actually this is a very long sentence that will probably cause several reallocations ");
+ jac_sb_append_buf(&sb, " Actually this is a very long sentence that will probably cause several reallocations ");
+ jac_sb_append_buf(&sb, " Actually this is a very long sentence that will probably cause several reallocations ");
+ jac_sb_append_buf(&sb, " Actually this is a very long sentence that will probably cause several reallocations ");
+ jac_sb_append_buf(&sb, " Actually this is a very long sentence that will probably cause several reallocations ");
+ jac_sb_append_buf(&sb, " Actually this is a very long sentence that will probably cause several reallocations ");
+ }
+ // PRINTF(sb);
+ jac_sb_free(sb);
+
+ sb = jac_sb_empty();
+ jac_sb_append_buf(&sb, "<!DOCTYPE html><html><body><ul>");
+ for (size_t i = 0; i < 50; ++i)
+ jac_sb_snprintf(&sb, "<li>element %i</li>\n", i);
+ jac_sb_append_buf(&sb, "</ul></body></html>");
+ for (size_t i = 0; i < 2048; ++i)
+ jac_sb_putc(&sb, 69);
+ PRINTF(sb);
+ if (sb.size != 3088 || sb.cap != 4096) return 1;
+ jac_sb_free(sb);
+ return 0;
+}