Lazy documentation parser for C
git clone git://soophie.de/cdoc
blob: a6095c809645822fdf968e5b713544f04ab9e871 (12296 bytes)[doc][blame][raw]
1
/*2
* Copyright (C) 2026 Sophie Sitzler3
*4
* This program is free software: you can redistribute it and/or modify5
* it under the terms of the GNU General Public License as published by6
* the Free Software Foundation, either version 3 of the License, or7
* (at your option) any later version.8
*9
* This program is distributed in the hope that it will be useful,10
* but WITHOUT ANY WARRANTY; without even the implied warranty of11
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12
* GNU General Public License for more details.13
*14
* You should have received a copy of the GNU General Public License15
* along with this program. If not, see <https://www.gnu.org/licenses/>.16
*/18
// @file cdoc.h19
// @desc Lazy documentation parser for C21
#pragma once23
#include <stdlib.h>25
// @enum cdoc_type_t26
// @groupdef cdoc_type_t27
typedef enum {28
CDOC_TYPE_NONE = 0,29
CDOC_TYPE_FILE = 1,30
CDOC_TYPE_IMPL = 2,31
CDOC_TYPE_TYPE = 3,32
CDOC_TYPE_INCLUDE = 4,33
CDOC_TYPE_MACRO = 5,34
CDOC_TYPE_VAR = 6,35
CDOC_TYPE_ENUM = 7,36
CDOC_TYPE_STRUCT = 8,37
CDOC_TYPE_UNION = 9,38
CDOC_TYPE_FUNCTION = 10,39
__CDOC_TYPE_COUNT = 11,40
} cdoc_type_t;42
// @struct cdoc_ref_t43
// @groupdef cdoc_ref_t44
typedef struct {45
cdoc_type_t type;46
char *symbol;47
char *description;48
char *signature;49
char *obsolete;50
char *note;51
char *ret;52
size_t line;53
bool is_internal;54
char **param_list;55
size_t param_list_len;56
char *groupdef;57
char *group;58
} cdoc_ref_t;60
// @struct cdoc_t61
// @groupdef cdoc_t62
typedef struct {63
cdoc_ref_t *ref_list;64
size_t ref_list_len;65
} cdoc_t;67
// @macro CDOC_REF_INIT68
// @group cdoc_ref_t69
#define CDOC_REF_INIT (cdoc_ref_t) { CDOC_TYPE_NONE, NULL, NULL, NULL, NULL, NULL, NULL, 0, false, NULL, 0, NULL, NULL }71
// @macro CDOC_INIT72
// @group cdoc_t73
#define CDOC_INIT (cdoc_t) { NULL, 0 }75
// @func cdoc_type_str76
// @group cdoc_type_t77
const char* cdoc_type_str(cdoc_type_t type);79
// @func cdoc_type_name80
// @group cdoc_type_t81
const char* cdoc_type_name(cdoc_type_t type);83
// @func cdoc_is_doc84
bool cdoc_is_doc(char *line);86
// @func cdoc_symbol_get87
char *cdoc_symbol_get(char *line);89
// @func cdoc_parse_desc90
char *cdoc_parse_desc(char *value);92
// @func cdoc_parse93
// @group cdoc_t94
cdoc_t cdoc_parse(char *content);96
// @func cdoc_free97
// @group cdoc_t98
void cdoc_free(cdoc_t *self);100
#ifdef CDOC_IMPL102
// @include <buf.h>103
#include <buf.h>105
// @impl cdoc_type_str106
const char* cdoc_type_str(cdoc_type_t type) {107
switch (type) {108
case __CDOC_TYPE_COUNT:109
case CDOC_TYPE_NONE:110
return NULL;111
case CDOC_TYPE_FILE:112
return "file";113
case CDOC_TYPE_IMPL:114
return "impl";115
case CDOC_TYPE_TYPE:116
return "type";117
case CDOC_TYPE_INCLUDE:118
return "include";119
case CDOC_TYPE_MACRO:120
return "macro";121
case CDOC_TYPE_VAR:122
return "var";123
case CDOC_TYPE_ENUM:124
return "enum";125
case CDOC_TYPE_STRUCT:126
return "struct";127
case CDOC_TYPE_UNION:128
return "union";129
case CDOC_TYPE_FUNCTION:130
return "func";131
}132
return NULL;133
}135
// @impl cdoc_type_name136
const char* cdoc_type_name(cdoc_type_t type) {137
switch (type) {138
case __CDOC_TYPE_COUNT:139
case CDOC_TYPE_NONE:140
return NULL;141
case CDOC_TYPE_FILE:142
return "file";143
case CDOC_TYPE_IMPL:144
return "impl";145
case CDOC_TYPE_TYPE:146
return "type";147
case CDOC_TYPE_INCLUDE:148
return "include";149
case CDOC_TYPE_MACRO:150
return "macro";151
case CDOC_TYPE_VAR:152
return "variable";153
case CDOC_TYPE_ENUM:154
return "enum";155
case CDOC_TYPE_STRUCT:156
return "struct";157
case CDOC_TYPE_UNION:158
return "union";159
case CDOC_TYPE_FUNCTION:160
return "function";161
}162
return NULL;163
}165
// @impl cdoc_escape_str166
char *cdoc_escape_str(char *value) {167
if (value == NULL) return NULL;168
buf_t buf= BUF_NULL;169
buf_write(&buf, value);170
buf_repall(&buf, "&", "&");171
buf_repall(&buf, "<", "<");172
buf_repall(&buf, ">", ">");173
buf_repall(&buf, "\"", """);174
buf_repall(&buf, "'", "'");175
buf_write_len(&buf, "\0", 1);176
return (char *) buf.bytes;177
}179
// @impl cdoc_parse180
char *cdoc_parse_desc(char *value) {181
if (value == NULL) return NULL;182
buf_t buf = BUF_NULL;183
size_t len = strlen(value);184
size_t set = 0;185
for (size_t i = 0; i < len; i++) {186
char c = value[i];187
if (c == '{') {188
buf_write_len(&buf, value + set, i - set);189
set = i + 1;190
}191
if (c == '}') {192
buf_write(&buf, "<a href=\"#");193
buf_write_len(&buf, value + set, i - set);194
buf_write(&buf, "\">");195
buf_write_len(&buf, value + set, i - set);196
buf_write(&buf, "</a>");197
set = i + 1;198
}199
}200
if (set < len) {201
buf_write_len(&buf, value + set, len - set);202
}203
buf_write_len(&buf, "\0", 1);204
return (char *) buf.bytes;205
}207
// @impl cdoc_is_doc208
bool cdoc_is_doc(char *line) {209
return strncmp(line, "// @", 4) == 0;210
}212
// @impl cdoc_symbol_get213
char *cdoc_symbol_get(char *line) {214
if (cdoc_is_doc(line)) {215
char *op = line + 4;216
for (size_t i = 0; i < __CDOC_TYPE_COUNT; i++) {217
cdoc_type_t type = (cdoc_type_t) i;218
if (type == CDOC_TYPE_NONE) {219
continue;220
}221
if (type == CDOC_TYPE_FILE) {222
continue;223
}224
const char *str = cdoc_type_str(type);225
if (str && strncmp(op, str, strlen(str)) == 0) {226
char *value = strstr(op, " ") + 1;227
if (value) {228
size_t offset = value - op;229
size_t value_len = strlen(line) - offset - 4;230
return strndup(value, value_len);231
}232
}233
}234
}235
return NULL;236
}238
// @impl cdoc_parse239
cdoc_t cdoc_parse(char *content) {240
cdoc_t doc = CDOC_INIT;241
size_t i = 0;242
size_t ln = 0;243
size_t sol = 0;244
size_t ln_sig = 0;245
bool is_doc = false;246
bool is_signature = false;247
cdoc_ref_t ref = CDOC_REF_INIT;248
size_t content_len = strlen(content);249
while (i < content_len) {250
char c = content[i];251
if (c == '\n') {252
size_t ln_len = i - sol;253
char *line = content + sol;254
if (strncmp(line, "// @", 4) == 0) {255
is_doc = true;256
bool is_op = false;257
char *op = line + 4;258
for (size_t i = 0; i < __CDOC_TYPE_COUNT; i++) {259
cdoc_type_t type = (cdoc_type_t) i;260
const char *str = cdoc_type_str(type);261
if (str && strncmp(op, str, strlen(str)) == 0) {262
ref.type = type;263
is_op = true;264
break;265
}266
}267
if (is_op) {268
char *value = strstr(op, " ") + 1;269
if (value) {270
size_t offset = value - op;271
size_t value_len = ln_len - offset - 4;272
ref.symbol = strndup(value, value_len);273
}274
}275
if (strncmp(op, "desc", 4) == 0) {276
char *value = strstr(op, " ") + 1;277
if (value) {278
size_t offset = value - op;279
size_t value_len = ln_len - offset - 4;280
ref.description = strndup(value, value_len);281
}282
}283
if (strncmp(op, "obsolete", 8) == 0) {284
char *value = strstr(op, " ") + 1;285
if (value) {286
size_t offset = value - op;287
size_t value_len = ln_len - offset - 4;288
ref.obsolete = strndup(value, value_len);289
}290
}291
if (strncmp(op, "note", 4) == 0) {292
char *value = strstr(op, " ") + 1;293
if (value) {294
size_t offset = value - op;295
size_t value_len = ln_len - offset - 4;296
ref.note = strndup(value, value_len);297
}298
}299
if (strncmp(op, "return", 6) == 0) {300
char *value = strstr(op, " ") + 1;301
if (value) {302
size_t offset = value - op;303
size_t value_len = ln_len - offset - 4;304
ref.ret = strndup(value, value_len);305
}306
}307
if (strncmp(op, "internal", 8) == 0) {308
ref.is_internal = true;309
}310
if (strncmp(op, "param", 5) == 0) {311
char *value = strstr(op, " ") + 1;312
if (value) {313
ref.param_list = realloc(ref.param_list, sizeof(char *) * (ref.param_list_len + 1));314
size_t offset = value - op;315
size_t value_len = ln_len - offset - 4;316
ref.param_list[ref.param_list_len] = strndup(value, value_len);317
ref.param_list_len++;318
}319
}320
if (strncmp(op, "groupdef", 8) == 0) {321
char *value = strstr(op, " ") + 1;322
if (value) {323
size_t offset = value - op;324
size_t value_len = ln_len - offset - 4;325
ref.groupdef = strndup(value, value_len);326
}327
}328
else if (strncmp(op, "group", 5) == 0) {329
char *value = strstr(op, " ") + 1;330
if (value) {331
size_t offset = value - op;332
size_t value_len = ln_len - offset - 4;333
ref.group = strndup(value, value_len);334
}335
}336
}337
else if (is_doc) {338
is_signature = true;339
is_doc = false;340
ref.line = ln + 1;341
}342
if (is_signature) {343
char *value = line;344
size_t value_len = ln_len;345
size_t signature_len = 0;346
if (value) {347
if (ref.signature) {348
signature_len = strlen(ref.signature);349
}350
ref.signature = realloc(ref.signature, sizeof(char) * (signature_len + value_len + 2));351
memcpy(ref.signature + signature_len, value, value_len);352
ref.signature[signature_len + value_len] = '\n';353
ref.signature[signature_len + value_len + 1] = '\0';354
}355
bool is_end = true;356
if (ref.type == CDOC_TYPE_MACRO) {357
if (ref.signature[strlen(ref.signature) - 2] == '\\') {358
is_end = false;359
}360
}361
if (ref.type == CDOC_TYPE_VAR) {362
if (ln_sig == 0 && ref.signature[strlen(ref.signature) - 2] == '{') {363
is_end = false;364
}365
if (ln_sig > 0 && ref.signature[signature_len] != '}') {366
is_end = false;367
}368
}369
if (ref.type == CDOC_TYPE_ENUM) {370
if (ref.signature[signature_len] != '}') {371
is_end = false;372
}373
}374
if (ref.type == CDOC_TYPE_STRUCT) {375
if (ln_sig == 0 && ref.signature[strlen(ref.signature) - 2] == '{') {376
is_end = false;377
}378
if (ln_sig > 0 && ref.signature[signature_len] != '}') {379
is_end = false;380
}381
}382
if (ref.type == CDOC_TYPE_UNION) {383
if (ref.signature[signature_len] != '}') {384
is_end = false;385
}386
}387
if (ref.type == CDOC_TYPE_FUNCTION) {388
if (ln_sig == 0 && ref.signature[strlen(ref.signature) - 2] == '{') {389
buf_t buf = BUF_NULL;390
buf_write(&buf, ref.signature);391
free(ref.signature);392
buf_repall(&buf, "{", "{ ... }");393
buf_write_len(&buf, "\0", 1);394
ref.signature = (char *) buf.bytes;395
}396
}397
ln_sig++;398
if (is_end) {399
is_signature = false;400
ln_sig = 0;401
if (ref.type != CDOC_TYPE_NONE) {402
doc.ref_list = realloc(doc.ref_list, sizeof(cdoc_ref_t) * (doc.ref_list_len + 1));403
memcpy(&doc.ref_list[doc.ref_list_len], &ref, sizeof(cdoc_ref_t));404
doc.ref_list_len++;405
ref = CDOC_REF_INIT;406
}407
}408
}409
sol = i + 1;410
ln++;411
}412
i++;413
}414
return doc;415
}417
// @impl cdoc_free418
void cdoc_free(cdoc_t *self) {419
if (self->ref_list && self->ref_list_len > 0) {420
for (size_t i = 0; i < self->ref_list_len; i++) {421
cdoc_ref_t *ref = &self->ref_list[i];422
if (ref->symbol) free(ref->symbol);423
if (ref->description) free(ref->description);424
if (ref->obsolete) free(ref->obsolete);425
if (ref->note) free(ref->note);426
if (ref->ret) free(ref->ret);427
if (ref->signature) free(ref->signature);428
if (ref->param_list) {429
for (size_t i = 0; i < ref->param_list_len; i++) {430
free(ref->param_list[i]);431
}432
free(ref->param_list);433
}434
if (ref->groupdef) free(ref->groupdef);435
if (ref->group) free(ref->group);436
}437
free(self->ref_list);438
self->ref_list = NULL;439
self->ref_list_len = 0;440
}441
}443
#endif // CDOC_IMPL