Skip to content

Instantly share code, notes, and snippets.

@louisswarren
Last active July 1, 2024 07:23
Show Gist options
  • Save louisswarren/f0706c1e48615bf4ab3a8776a8eb3010 to your computer and use it in GitHub Desktop.
Save louisswarren/f0706c1e48615bf4ab3a8776a8eb3010 to your computer and use it in GitHub Desktop.
Template generation for C
main
templates.h
template_*.h
*.o
#include <stdio.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
typedef int64_t i64;
typedef struct {
const char *p;
ptrdiff_t n;
} slice;
#define S(X) ((slice){X, sizeof(X) - 1})
typedef struct {
char *start;
ptrdiff_t len;
ptrdiff_t cap;
} Arena;
void
push(Arena *buf, const void *p, ptrdiff_t n)
{
assert(buf->len + n < buf->cap);
memcpy(buf->start + buf->len, p, n);
buf->len += n;
}
void
push_int(Arena *buf, i64 n)
{
char b[32];
char *p = b + sizeof(b) - 1;
int sign = n < 0 ? -1 : 1;
do {
*(p--) = (char)('0' + (n % 10) * sign);
n /= 10;
} while (n);
if (sign < 0) {
*p = '-';
} else {
++p;
}
push(buf, p, b + sizeof(b) - p);
}
#define template(F, A, ...) \
template_##F##_raw(A, &(struct template_##F##_args){__VA_ARGS__})
#include "templates.h"
int
main(void)
{
Arena a = {NULL, 0, 1 << 20};
a.start = malloc(a.cap);
if (!a.start)
return 1;
template(webpage_html, &a,
.title = S("Hello, world!"),
.num = INT64_MIN,
.body = S("This is a test"));
fwrite(a.start, 1, a.len, stdout);
return 0;
}
CFLAGS = -Wall -Warray-bounds=2 -Wcast-align=strict -Wcast-qual -Wconversion -Wno-sign-conversion -Wdangling-else -Wdate-time -Wdouble-promotion -Wextra -Wfloat-conversion -Wformat-overflow=2 -Wformat-signedness -Wformat-truncation=2 -Wformat=2 -Winit-self -Wjump-misses-init -Wlogical-op -Wmissing-include-dirs -Wnested-externs -Wnull-dereference -Wpacked -Wpedantic -Wredundant-decls -Wshadow -Wshift-negative-value -Wshift-overflow=2 -Wstrict-aliasing -Wstrict-overflow=2 -Wstrict-prototypes -Wstringop-overflow=4 -Wstringop-truncation -Wswitch-default -Wswitch-enum -Wuninitialized -Wunsafe-loop-optimizations -Wunused -Wuse-after-free=3 -Wwrite-strings -fanalyzer -fmax-errors=2 -pedantic-errors
template_html = $(shell find . -name 'template_*.html')
template_o = $(patsubst ./template_%.html,./template_%.o,$(template_html))
template_h = $(patsubst ./template_%.html,./template_%.h,$(template_html))
.PHONY: default
default: main
./$<
.PHONY: test
test: out.txt
diff expected.txt $<
out.txt: main
./$< > $@
main: main.o $(template_o)
main.o: main.c templates.h $(template_h)
templates.h: $(template_h)
for x in $^; do echo "#include \"$$x\""; done > $@
template_%.o: template_%.html
ld -z noexecstack -r -b binary -o $@ $<
template_%.h: tempp.py template_%.html
python3 $^ > $@
.PHONY: clean
clean:
rm -f main templates.h template_*.h *.o
<html>
<head>
<title><{title}></title>
</head>
<body>
<h1>Number <{num:d}></h1>
<p>
<{body}>
</p>
<p>
Escape a template by putting a minus sign inside like <{-this}>.
</p>
</body>
</html>
import itertools
import re
import sys
from collections import namedtuple
class Parameter(namedtuple('Parameter', (
'raw',
'ident',
'ctype',
'start',
'end',
'disabled'
))):
pass
def print_extern_include(file_ident):
print("extern const char");
print(f"\t_binary_{file_ident}_start[],")
print(f"\t_binary_{file_ident}_end[];")
print()
def print_arg_struct(file_ident, params):
print(f"struct {file_ident}_args", "{")
param_types = set((p.ctype, p.ident) for p in params if not p.disabled)
for ctype, ident in sorted(param_types):
print(f"\t{ctype} {ident};")
print("};")
print()
def print_template_function(file_ident, params, content_len):
src = f"_binary_{file_ident}_start"
last = 0
print("void")
print(f"{file_ident}_raw(Arena *buf, const struct {file_ident}_args *args)")
print("{")
for p in params:
if last < p.start:
print(f"\tpush(buf, {src} + {last}, {p.start - last});")
if p.disabled:
lit = '"' + p.raw[:2] + p.raw[3:] + '"'
print(f"\tpush(buf, {lit}, sizeof({lit}) - 1);")
elif p.ctype == 'slice':
print(f"\tpush(buf, args->{p.ident}.p, args->{p.ident}.n);")
elif p.ctype == 'int64_t':
print(f"\tpush_int(buf, args->{p.ident});")
last = p.end
print(f"\tpush(buf, {src} + {last}, {content_len - last});")
print("}")
def make_header(filename):
file_ident = ''.join(c if c.isalnum() else '_' for c in filename)
with open(filename) as f:
contents = f.read()
print_extern_include(file_ident)
r = re.compile(flags=(re.ASCII | re.VERBOSE), pattern=r'''
<[{]
(?P<disable>[-]+)?
(?P<ident>\w+)
(:(?P<ftype>\w+))?
[}]>
''')
typedict = {
None: 'slice',
'd': 'int64_t',
's': 'slice',
}
params = [
Parameter(
raw=m.group(0),
ctype=typedict[m.groupdict()['ftype']],
ident=m.groupdict()['ident'],
start=m.start(),
end=m.end(),
disabled=m.groupdict()['disable'] is not None,
)
for m in r.finditer(contents)]
print_arg_struct(file_ident, params)
print_template_function(file_ident, params, len(contents))
if __name__ == '__main__':
filename = sys.argv[1]
make_header(filename)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment