Static template generator for C
git clone git://soophie.de/ductape
blob: cce2790830dc5584907ad11bce08c14a6b46053a (11483 bytes)[doc][blame][raw]
1
// Copyright (C) 2026 Sophie Sitzler2
//3
// This program is free software: you can redistribute it and/or modify4
// it under the terms of the GNU General Public License as published by5
// the Free Software Foundation, either version 3 of the License, or6
// (at your option) any later version.7
//8
// This program is distributed in the hope that it will be useful,9
// but WITHOUT ANY WARRANTY; without even the implied warranty of10
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the11
// GNU General Public License for more details.12
//13
// You should have received a copy of the GNU General Public License14
// along with this program. If not, see <https://www.gnu.org/licenses/>.16
// @file main.c17
// @desc The ductape compiler.19
#include <stdio.h>20
#include <stdlib.h>21
#include <ctype.h>23
#define BUF_IMPL24
// @include <buf.h>25
#include <buf.h>27
#define DUC_BUILDPATH "DUC_BUILDPATH"28
#define DUC_HEADERPATH "DUC_HEADERPATH"29
#define DUC_LIBPATH "DUC_LIBPATH"31
#define DUC_SYM_SIG_HASH "__DUC_SIG_HASH_"32
#define DUC_SYM_UID "__DUC_UID_"33
#define DUC_SYM_SPLIT "%"34
#define DUC_SYM_SPLIT_ESCAPE DUC_SYM_SPLIT DUC_SYM_SPLIT35
#define DUC_SYM_WRITE "DUC_WRITE"36
#define DUC_SYM_TEMPLDEF "DUC_TEMPLDEF"37
#define DUC_SYM_ENDTEMPL "DUC_ENDTEMPL"38
#define DUC_SYM_TEMPLDEF_HEADER "__DUC_TEMPLDEF_HEADER"40
#define DUC_TMP_C_PATH ".duc_tmp.c"41
#define DUC_TMP_SO_PATH ".duc_tmp.so"43
#define DUC_CMD_COMPILE "cc -shared -fPIC -o %s %s %s"45
// @enum duc_err_t46
typedef enum {47
DUC_ERR_OK,48
DUC_ERR_NO_ARGS,49
DUC_ERR_NO_FILE,50
DUC_ERR_NO_WRITE,51
DUC_ERR_UNCLOSED_CODE,52
DUC_ERR_NO_COMPILE,53
__DUC_ERR__COUNT,54
} duc_err_t;56
// @enum duc_mode_t57
typedef enum {58
DUC_MODE_HEADER,59
DUC_MODE_MODULE,60
__DUC_MODE__COUNT,61
} duc_mode_t;63
// @func duc_error_str64
void duc_error_str(duc_err_t err) {65
switch (err) {66
case DUC_ERR_OK:67
break;68
case DUC_ERR_NO_ARGS:69
fprintf(stderr, "Usage: ductape <files...>\n");70
break;71
case DUC_ERR_NO_FILE:72
fprintf(stderr, "Error: No file found!\n");73
break;74
case DUC_ERR_NO_WRITE:75
fprintf(stderr, "Error: Can't write to file!\n");76
break;77
case DUC_ERR_UNCLOSED_CODE:78
fprintf(stderr, "Error: Unclosed code block!\n");79
break;80
case DUC_ERR_NO_COMPILE:81
fprintf(stderr, "Error: Failed to compile!\n");82
break;83
case __DUC_ERR__COUNT:84
break;85
}86
}88
// @func duc_sig_hash89
size_t duc_sig_hash(char *str, size_t len) {90
size_t hash = 5381;91
size_t i = 0;92
while (i < len) {93
unsigned char c = (unsigned) str[i];94
if (c == ' ' || c == '\t' || c == '\r' || c == '\n') {95
i++;96
continue;97
}98
hash = ((hash << 5) + hash) + c;99
i++;100
}101
return hash;102
}104
// @func duc_render105
duc_err_t duc_render(duc_mode_t mode, buf_t *out, char *content, char **uid) {106
size_t len = strlen(content);107
size_t set = 0;108
size_t is_code = 0;109
size_t is_templ = 0;110
size_t is_templ_begin = 0;111
size_t hash = 0;112
for (size_t i = 0; i < len; i++) {113
if (content[i] == *DUC_SYM_SPLIT) {114
// ignore escaped DUC_SYM_SPLIT115
if (i + 1 < len && content[i + 1] == *DUC_SYM_SPLIT) {116
i += 1;117
continue;118
}119
if (is_code) {120
for (size_t j = 0; j < i - set; j++) {121
if (strncmp(content + set + j, DUC_SYM_TEMPLDEF, strlen(DUC_SYM_TEMPLDEF)) == 0) {122
is_templ = 1;123
is_templ_begin = 1;124
hash = duc_sig_hash(content + set, i - set);125
bool is_arg = false;126
bool is_uid = false;127
size_t uid_set = 0;128
for (size_t k = j; k < i - set; k++) {129
char c = content[set + k];130
if (c == '(') {131
is_arg = true;132
}133
if (is_arg && isalpha(c)) {134
is_uid = true;135
is_arg = false;136
uid_set = k;137
}138
if (is_uid && (!isalpha(c) && !isdigit(c) && c != '_')) {139
is_uid = false;140
*uid = strndup(content + set + uid_set, k - uid_set);141
break;142
}143
}144
}145
if (strncmp(content + set + j, DUC_SYM_ENDTEMPL, strlen(DUC_SYM_ENDTEMPL)) == 0) {146
is_templ = 0;147
}148
}149
if (mode == DUC_MODE_MODULE) {150
buf_write_len(out, content + set, i - set);151
buf_write(out, "\n");152
if (is_templ_begin) {153
is_templ_begin = 0;154
buf_write(out, "(void) ctx;");155
}156
}157
if (mode == DUC_MODE_HEADER && is_templ == 1) {158
is_templ = 0;159
buf_t templdef = BUF_NULL;160
buf_write_len(&templdef, content + set, i - set);161
buf_write(&templdef, "\n");162
buf_repall(&templdef, DUC_SYM_TEMPLDEF, DUC_SYM_TEMPLDEF_HEADER);163
buf_concat(out, templdef);164
buf_free(&templdef);165
}166
}167
else {168
const size_t STRING_LIMIT = 4095;169
size_t str_len = i - set;170
size_t rem_len = str_len;171
size_t hex_len = 0;172
size_t idx = 0;173
while (rem_len > 0) {174
if (rem_len > STRING_LIMIT) {175
hex_len = STRING_LIMIT;176
}177
else {178
hex_len = str_len - idx;179
}180
rem_len -= hex_len;181
buf_t hex = BUF_NULL;182
buf_write_len(&hex, content + set + idx, hex_len);183
buf_repall(&hex, DUC_SYM_SPLIT_ESCAPE, DUC_SYM_SPLIT);184
buf_to_hex(&hex);185
if (mode == DUC_MODE_MODULE && is_templ == 1) {186
buf_write(out, DUC_SYM_WRITE "(\"");187
buf_concat(out, hex);188
buf_write(out, "\")\n");189
}190
buf_free(&hex);191
idx += hex_len;192
}193
}194
is_code = !is_code;195
set = i + 1;196
}197
}198
buf_t meta = BUF_NULL;199
buf_write(&meta, "#include <dlfcn.h>\n");200
buf_write(&meta, "#include <string.h>\n");201
buf_write(&meta, "#include <stdlib.h>\n");202
buf_write(&meta, "#include <ductape.h>\n");203
buf_writef(&meta, "const char " DUC_SYM_UID "%s[] = \"%s\";\n", *uid, *uid);204
buf_writef(&meta, "const size_t " DUC_SYM_SIG_HASH "%s = %ld;\n", *uid, hash);205
buf_concat(&meta, *out);206
buf_free(out);207
*out = BUF_NULL;208
buf_concat(out, meta);209
buf_free(&meta);210
if (is_code) {211
return DUC_ERR_UNCLOSED_CODE;212
}213
else {214
const size_t STRING_LIMIT = 4095;215
size_t str_len = len - set;216
size_t rem_len = str_len;217
size_t hex_len = 0;218
size_t idx = 0;219
while (rem_len > 0) {220
if (rem_len > STRING_LIMIT) {221
hex_len = STRING_LIMIT;222
}223
else {224
hex_len = str_len - idx;225
}226
rem_len -= hex_len;227
buf_t hex = BUF_NULL;228
buf_write_len(&hex, content + set + idx, hex_len);229
buf_repall(&hex, DUC_SYM_SPLIT_ESCAPE, DUC_SYM_SPLIT);230
buf_to_hex(&hex);231
if (mode == DUC_MODE_MODULE && is_templ == 1) {232
buf_write(out, DUC_SYM_WRITE "(\"");233
buf_concat(out, hex);234
buf_write(out, "\")\n");235
}236
buf_free(&hex);237
idx += hex_len;238
}239
}240
buf_repall(out, DUC_SYM_SPLIT_ESCAPE, DUC_SYM_SPLIT);241
return DUC_ERR_OK;242
}244
// @func duc_file_read245
duc_err_t duc_file_read(char **content, const char *filename) {246
FILE *fp = fopen(filename, "r");247
if (fp == NULL) {248
return DUC_ERR_NO_FILE;249
}250
fseek(fp, 0, SEEK_END);251
size_t len = ftell(fp);252
fseek(fp, 0, SEEK_SET);253
*content = calloc(len + 1, sizeof(char));254
fread(*content, sizeof(char), len, fp);255
(*content)[len] = '\0';256
fclose(fp);257
return DUC_ERR_OK;258
}260
// @func duc_file_read_binary261
duc_err_t duc_file_read_binary(char **binary, size_t *len, const char *filename) {262
FILE *fp = fopen(filename, "rb");263
if (fp == NULL) {264
return DUC_ERR_NO_FILE;265
}266
fseek(fp, 0, SEEK_END);267
*len = ftell(fp);268
fseek(fp, 0, SEEK_SET);269
*binary = calloc(*len, sizeof(char));270
fread(*binary, sizeof(char), *len, fp);271
fclose(fp);272
return DUC_ERR_OK;273
}275
// @func duc_file_write276
duc_err_t duc_file_write(const char *content, size_t len, const char *filename) {277
FILE *fp = fopen(filename, "w");278
if (fp == NULL) {279
return DUC_ERR_NO_FILE;280
}281
if (fwrite(content, sizeof(char), len, fp) != len) {282
return DUC_ERR_NO_WRITE;283
}284
fclose(fp);285
return DUC_ERR_OK;286
}288
// @func duc_compile_file289
duc_err_t duc_compile_file(duc_mode_t mode, char *filepath, char **uid) {290
duc_err_t err;291
char *content = NULL;292
err = duc_file_read(&content, filepath);293
if (err != DUC_ERR_OK) {294
duc_error_str(err);295
return err;296
}297
buf_t template = BUF_NULL;298
err = duc_render(mode, &template, content, uid);299
free(content);300
if (err != DUC_ERR_OK || *uid == NULL) {301
buf_free(&template);302
duc_error_str(err);303
return err;304
}305
switch (mode) {306
case DUC_MODE_HEADER: {307
char *path = getenv(DUC_HEADERPATH);308
if (path == NULL) {309
path = "./";310
}311
buf_t filename = BUF_NULL;312
buf_writef(&filename, "%s%s.h", path, *uid);313
buf_terminate(&filename);314
duc_file_write((char *) template.bytes, template.len, (char *) filename.bytes);315
buf_free(&filename);316
buf_free(&template);317
return DUC_ERR_OK;318
}319
case DUC_MODE_MODULE: {320
err = duc_file_write((char *) template.bytes, template.len, DUC_TMP_C_PATH);321
buf_free(&template);322
char *lib_path = getenv(DUC_LIBPATH);323
if (lib_path == NULL) {324
lib_path = "";325
}326
char cmd[500];327
snprintf(cmd, sizeof(cmd), DUC_CMD_COMPILE, DUC_TMP_SO_PATH, lib_path, DUC_TMP_C_PATH);328
int rc = system(cmd);329
remove(DUC_TMP_C_PATH);330
if (rc != 0) {331
err = DUC_ERR_NO_COMPILE;332
duc_error_str(err);333
return err;334
}335
char *binary = NULL;336
size_t len = 0;337
err = duc_file_read_binary(&binary, &len, DUC_TMP_SO_PATH);338
remove(DUC_TMP_SO_PATH);339
if (err != DUC_ERR_OK) {340
duc_error_str(err);341
return err;342
}343
char *path = getenv(DUC_BUILDPATH);344
if (path == NULL) {345
path = "./";346
}347
buf_t filename = BUF_NULL;348
buf_writef(&filename, "%s%s.so", path, *uid);349
buf_terminate(&filename);350
duc_file_write(binary, len, (char *) filename.bytes);351
buf_free(&filename);352
free(binary);353
return DUC_ERR_OK;354
}355
case __DUC_MODE__COUNT:356
break;357
}358
return DUC_ERR_OK;359
}361
// @func main362
int main(int argc, char **argv) {363
if (argc <= 1) {364
return DUC_ERR_NO_ARGS;365
}366
char *headerpath = getenv(DUC_HEADERPATH);367
char *buildpath = getenv(DUC_BUILDPATH);368
char *libpath = getenv(DUC_LIBPATH);369
fprintf(stdout, "[ductape] Compiling templates...\n");370
fprintf(stdout, " ." DUC_HEADERPATH " = '%s'\n", headerpath ? headerpath : "./");371
fprintf(stdout, " ." DUC_BUILDPATH " = '%s'\n", buildpath ? buildpath : "./");372
if (libpath) {373
fprintf(stdout, " ." DUC_LIBPATH " = '%s'\n", libpath);374
}375
duc_err_t err = DUC_ERR_OK;376
for (size_t i = 1; i < (size_t) argc; i++) {377
char *filepath = argv[i];378
duc_err_t header_err = DUC_ERR_OK;379
char *uid = NULL;380
if ((header_err = duc_compile_file(DUC_MODE_HEADER, filepath, &uid)) != DUC_ERR_OK) {381
err = header_err;382
}383
if (uid) {384
free(uid);385
}386
uid = NULL;387
duc_err_t module_err = DUC_ERR_OK;388
if ((module_err = duc_compile_file(DUC_MODE_MODULE, filepath, &uid)) != DUC_ERR_OK) {389
err = module_err;390
}391
fprintf(stdout, "-- compile '%s' | %s [%s]\n", uid, filepath, header_err == DUC_ERR_OK && module_err == DUC_ERR_OK ? "OK" : "FAILED");392
if (uid) {393
free(uid);394
}395
}396
return err;397
}