so://phie

cdoc

Lazy documentation parser for C
git clone git://soophie.de/cdoc

blob: a6095c809645822fdf968e5b713544f04ab9e871 (12296 bytes)[doc][blame][raw]

1/*

2 * Copyright (C) 2026 Sophie Sitzler

3 *

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

5 * it under the terms of the GNU General Public License as published by

6 * the Free Software Foundation, either version 3 of the License, or

7 * (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 of

11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the

12 * GNU General Public License for more details.

13 *

14 * You should have received a copy of the GNU General Public License

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

16 */

17

18// @file cdoc.h

19// @desc Lazy documentation parser for C

20

21#pragma once

22

23#include <stdlib.h>

24

25// @enum cdoc_type_t

26// @groupdef cdoc_type_t

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

41

42// @struct cdoc_ref_t

43// @groupdef cdoc_ref_t

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

59

60// @struct cdoc_t

61// @groupdef cdoc_t

62typedef struct {

63 cdoc_ref_t *ref_list;

64 size_t ref_list_len;

65} cdoc_t;

66

67// @macro CDOC_REF_INIT

68// @group cdoc_ref_t

69#define CDOC_REF_INIT (cdoc_ref_t) { CDOC_TYPE_NONE, NULL, NULL, NULL, NULL, NULL, NULL, 0, false, NULL, 0, NULL, NULL }

70

71// @macro CDOC_INIT

72// @group cdoc_t

73#define CDOC_INIT (cdoc_t) { NULL, 0 }

74

75// @func cdoc_type_str

76// @group cdoc_type_t

77const char* cdoc_type_str(cdoc_type_t type);

78

79// @func cdoc_type_name

80// @group cdoc_type_t

81const char* cdoc_type_name(cdoc_type_t type);

82

83// @func cdoc_is_doc

84bool cdoc_is_doc(char *line);

85

86// @func cdoc_symbol_get

87char *cdoc_symbol_get(char *line);

88

89// @func cdoc_parse_desc

90char *cdoc_parse_desc(char *value);

91

92// @func cdoc_parse

93// @group cdoc_t

94cdoc_t cdoc_parse(char *content);

95

96// @func cdoc_free

97// @group cdoc_t

98void cdoc_free(cdoc_t *self);

99

100#ifdef CDOC_IMPL

101

102// @include <buf.h>

103#include <buf.h>

104

105// @impl cdoc_type_str

106const 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}

134

135// @impl cdoc_type_name

136const 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}

164

165// @impl cdoc_escape_str

166char *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, "&", "&amp;");

171 buf_repall(&buf, "<", "&lt;");

172 buf_repall(&buf, ">", "&gt;");

173 buf_repall(&buf, "\"", "&quot;");

174 buf_repall(&buf, "'", "&apos;");

175 buf_write_len(&buf, "\0", 1);

176 return (char *) buf.bytes;

177}

178

179// @impl cdoc_parse

180char *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}

206

207// @impl cdoc_is_doc

208bool cdoc_is_doc(char *line) {

209 return strncmp(line, "// @", 4) == 0;

210}

211

212// @impl cdoc_symbol_get

213char *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}

237

238// @impl cdoc_parse

239cdoc_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}

416

417// @impl cdoc_free

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

442

443#endif // CDOC_IMPL