so://phie

ductape

Static template generator for C
git clone git://soophie.de/ductape

blob: cce2790830dc5584907ad11bce08c14a6b46053a (11483 bytes)[doc][blame][raw]

1// Copyright (C) 2026 Sophie Sitzler

2//

3// This program is free software: you can redistribute it and/or modify

4// it under the terms of the GNU General Public License as published by

5// the Free Software Foundation, either version 3 of the License, or

6// (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 of

10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the

11// GNU General Public License for more details.

12//

13// You should have received a copy of the GNU General Public License

14// along with this program. If not, see <https://www.gnu.org/licenses/>.

15

16// @file main.c

17// @desc The ductape compiler.

18

19#include <stdio.h>

20#include <stdlib.h>

21#include <ctype.h>

22

23#define BUF_IMPL

24// @include <buf.h>

25#include <buf.h>

26

27#define DUC_BUILDPATH "DUC_BUILDPATH"

28#define DUC_HEADERPATH "DUC_HEADERPATH"

29#define DUC_LIBPATH "DUC_LIBPATH"

30

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_SPLIT

35#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"

39

40#define DUC_TMP_C_PATH ".duc_tmp.c"

41#define DUC_TMP_SO_PATH ".duc_tmp.so"

42

43#define DUC_CMD_COMPILE "cc -shared -fPIC -o %s %s %s"

44

45// @enum duc_err_t

46typedef 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;

55

56// @enum duc_mode_t

57typedef enum {

58 DUC_MODE_HEADER,

59 DUC_MODE_MODULE,

60 __DUC_MODE__COUNT,

61} duc_mode_t;

62

63// @func duc_error_str

64void 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}

87

88// @func duc_sig_hash

89size_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}

103

104// @func duc_render

105duc_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_SPLIT

115 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}

243

244// @func duc_file_read

245duc_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}

259

260// @func duc_file_read_binary

261duc_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}

274

275// @func duc_file_write

276duc_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}

287

288// @func duc_compile_file

289duc_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}

360

361// @func main

362int 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}