/* * Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation files * (the "Software"), to deal in the Software without restriction, * including without limitation the rights to use, copy, modify, merge, * publish, distribute, sublicense, and/or sell copies of the Software, * and to permit persons to whom the Software is furnished to do so, * subject to the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include typedef struct DocDecl { char docs[HEADER_EXPR_MAX]; HeaderDeclaration decl; } DocDecl; typedef struct DocTypedef { char docs[HEADER_EXPR_MAX]; char text[HEADER_EXPR_MAX]; } DocTypedef; typedef struct DocGlobal { char docs[HEADER_EXPR_MAX]; HeaderGlobal global; } DocGlobal; static void ParseMainBlock(HashMap * registers, Array * descr, char *comment) { char *line = strtok(comment, "\n"); while (line) { while (*line && (isspace(*line) || *line == '*')) { line++; } if (!*line) { goto next; } if (*line == '@') { int i = 0; line++; while (!isspace(line[i])) { i++; } line[i] = '\0'; Free(HashMapSet(registers, line, StrDuplicate(line + i + 1))); } else { ArrayAdd(descr, StrDuplicate(line)); } next: line = strtok(NULL, "\n"); } } int Main(Array * args) { HeaderExpr expr; size_t i; char *key; char *val; int exit = EXIT_SUCCESS; ArgParseState arg; HashMap *registers = HashMapCreate(); Array *descr = ArrayCreate(); Array *declarations = ArrayCreate(); DocDecl *decl = NULL; Array *typedefs = ArrayCreate(); DocTypedef *type = NULL; Array *globals = ArrayCreate(); DocGlobal *global = NULL; char comment[HEADER_EXPR_MAX]; int isDocumented = 0; Stream *in = NULL; Stream *out = NULL; int opt; ArgParseStateInit(&arg); while ((opt = ArgParse(&arg, args, "i:o:D:")) != -1) { switch (opt) { case 'i': if (in) { break; } if (StrEquals(arg.optArg, "-")) { in = StreamStdin(); } else { int len = strlen(arg.optArg); in = StreamOpen(arg.optArg, "r"); if (!in) { StreamPrintf(StreamStderr(), "Error: %s:%s", arg.optArg, strerror(errno)); exit = EXIT_FAILURE; goto finish; } while (arg.optArg[len - 1] != '.') { arg.optArg[len - 1] = '\0'; len--; } arg.optArg[len - 1] = '\0'; len--; HashMapSet(registers, "Nm", StrDuplicate(arg.optArg)); } break; case 'o': if (out) { break; } if (StrEquals(arg.optArg, "-")) { out = StreamStdout(); } else { out = StreamOpen(arg.optArg, "w"); if (!out) { StreamPrintf(StreamStderr(), "Error: %s:%s", arg.optArg, strerror(errno)); exit = EXIT_FAILURE; goto finish; } } break; case 'D': val = arg.optArg; while (*val && *val != '=') { val++; } if (!*val || *val != '=') { StreamPrintf(StreamStderr(), "Bad register definition: %s", arg.optArg); exit = EXIT_FAILURE; goto finish; } *val = '\0'; val++; HashMapSet(registers, arg.optArg, StrDuplicate(val)); break; } } if (!in) { in = StreamStdin(); } if (!out) { out = StreamStdout(); } memset(&expr, 0, sizeof(expr)); while (1) { HeaderParse(in, &expr); switch (expr.type) { case HP_PREPROCESSOR_DIRECTIVE: /* Ignore */ break; case HP_EOF: /* Done parsing */ goto last; case HP_PARSE_ERROR: case HP_SYNTAX_ERROR: StreamPrintf(StreamStderr(), "Parse Error: (line %d) %s\n", expr.data.error.lineNo, expr.data.error.msg); exit = EXIT_FAILURE; goto finish; case HP_COMMENT: if (expr.data.text[0] != '*') { break; } if (strncmp(expr.data.text, "**", 2) == 0) { ParseMainBlock(registers, descr, expr.data.text); } else { strncpy(comment, expr.data.text, sizeof(comment)); isDocumented = 1; } break; case HP_TYPEDEF: if (HashMapGet(registers, "ignore-typedefs")) { break; } if (!isDocumented) { StreamPrintf(StreamStderr(), "Error: Undocumented typedef:\n%s\n", expr.data.text); exit = EXIT_FAILURE; goto finish; } else { type = Malloc(sizeof(DocTypedef)); strncpy(type->docs, comment, sizeof(type->docs)); strncpy(type->text, expr.data.text, sizeof(type->text)); ArrayAdd(typedefs, type); isDocumented = 0; } break; case HP_DECLARATION: if (!isDocumented) { StreamPrintf(StreamStderr(), "Error: %s() is undocumented.\n", expr.data.declaration.name); exit = EXIT_FAILURE; goto finish; } else { decl = Malloc(sizeof(DocDecl)); decl->decl = expr.data.declaration; decl->decl.args = ArrayCreate(); strncpy(decl->docs, comment, sizeof(decl->docs)); for (i = 0; i < ArraySize(expr.data.declaration.args); i++) { ArrayAdd(decl->decl.args, StrDuplicate(ArrayGet(expr.data.declaration.args, i))); } ArrayAdd(declarations, decl); isDocumented = 0; } break; case HP_GLOBAL: if (!isDocumented) { StreamPrintf(StreamStderr(), "Error: Global %s is undocumented.\n", expr.data.global.name); exit = EXIT_FAILURE; goto finish; } else { global = Malloc(sizeof(DocGlobal)); global->global = expr.data.global; strncpy(global->docs, comment, sizeof(global->docs)); ArrayAdd(globals, global); isDocumented = 0; } break; case HP_UNKNOWN: if (HashMapGet(registers, "suppress-warnings")) { break; } StreamPrintf(StreamStderr(), "Warning: Unknown expression: %s\n", expr.data.text); break; default: StreamPrintf(StreamStderr(), "Unknown header type: %d\n", expr.type); StreamPrintf(StreamStderr(), "This is a programming error.\n"); exit = EXIT_FAILURE; goto finish; } } last: val = HashMapGet(registers, "Nm"); if (!val) { HashMapSet(registers, "Nm", StrDuplicate("Unnamed")); } val = HashMapGet(registers, "Dd"); if (!val) { time_t currentTime; struct tm *timeInfo; char tsBuf[1024]; currentTime = time(NULL); timeInfo = localtime(¤tTime); strftime(tsBuf, sizeof(tsBuf), "%B %d %Y", timeInfo); val = tsBuf; } StreamPrintf(out, ".Dd $%s: %s $\n", "Mdocdate", val); val = HashMapGet(registers, "Os"); if (val) { StreamPrintf(out, ".Os %s\n", val); } val = HashMapGet(registers, "Nm"); StreamPrintf(out, ".Dt %s 3\n", val); StreamPrintf(out, ".Sh NAME\n"); StreamPrintf(out, ".Nm %s\n", val); val = HashMapGet(registers, "Nd"); if (!val) { val = "No Description."; } StreamPrintf(out, ".Nd %s\n", val); StreamPrintf(out, ".Sh SYNOPSIS\n"); val = HashMapGet(registers, "Nm"); StreamPrintf(out, ".In %s.h\n", val); for (i = 0; i < ArraySize(declarations); i++) { size_t j; decl = ArrayGet(declarations, i); StreamPrintf(out, ".Ft %s\n", decl->decl.returnType); StreamPrintf(out, ".Fn %s ", decl->decl.name); for (j = 0; j < ArraySize(decl->decl.args); j++) { StreamPrintf(out, "\"%s\" ", ArrayGet(decl->decl.args, j)); } StreamPutc(out, '\n'); } if (ArraySize(globals)) { StreamPrintf(out, ".Sh GLOBALS\n"); for (i = 0; i < ArraySize(globals); i++) { char *line; global = ArrayGet(globals, i); StreamPrintf(out, ".Ss %s %s\n", global->global.type, global->global.name); line = strtok(global->docs, "\n"); while (line) { while (*line && (isspace(*line) || *line == '*')) { line++; } if (*line) { StreamPrintf(out, "%s\n", line); } line = strtok(NULL, "\n"); } } } if (ArraySize(typedefs)) { StreamPrintf(out, ".Sh TYPE DECLARATIONS\n"); for (i = 0; i < ArraySize(typedefs); i++) { char *line; type = ArrayGet(typedefs, i); StreamPrintf(out, ".Bd -literal -offset indent\n"); StreamPrintf(out, "%s\n", type->text); StreamPrintf(out, ".Ed\n.Pp\n"); line = strtok(type->docs, "\n"); while (line) { while (*line && (isspace(*line) || *line == '*')) { line++; } if (*line) { StreamPrintf(out, "%s\n", line); } line = strtok(NULL, "\n"); } } } StreamPrintf(out, ".Sh DESCRIPTION\n"); for (i = 0; i < ArraySize(descr); i++) { StreamPrintf(out, "%s\n", ArrayGet(descr, i)); } for (i = 0; i < ArraySize(declarations); i++) { size_t j; char *line; decl = ArrayGet(declarations, i); StreamPrintf(out, ".Ss %s %s(", decl->decl.returnType, decl->decl.name); for (j = 0; j < ArraySize(decl->decl.args); j++) { StreamPrintf(out, "%s", ArrayGet(decl->decl.args, j)); if (j < ArraySize(decl->decl.args) - 1) { StreamPuts(out, ", "); } } StreamPuts(out, ")\n"); line = strtok(decl->docs, "\n"); while (line) { while (*line && (isspace(*line) || *line == '*')) { line++; } if (*line) { StreamPrintf(out, "%s\n", line); } line = strtok(NULL, "\n"); } } val = HashMapGet(registers, "Xr"); if (val) { char *xr = strtok(val, " "); StreamPrintf(out, ".Sh SEE ALSO\n"); while (xr) { if (*xr) { StreamPrintf(out, ".Xr %s 3 ", xr); } xr = strtok(NULL, " "); if (xr) { StreamPutc(out, ','); } StreamPutc(out, '\n'); } } finish: if (in != StreamStdin()) { StreamClose(in); } if (out != StreamStdout()) { StreamClose(out); } for (i = 0; i < ArraySize(declarations); i++) { size_t j; decl = ArrayGet(declarations, i); for (j = 0; j < ArraySize(decl->decl.args); j++) { Free(ArrayGet(decl->decl.args, j)); } ArrayFree(decl->decl.args); Free(decl); } ArrayFree(declarations); for (i = 0; i < ArraySize(typedefs); i++) { Free(ArrayGet(typedefs, i)); } ArrayFree(typedefs); for (i = 0; i < ArraySize(globals); i++) { Free(ArrayGet(globals, i)); } ArrayFree(globals); for (i = 0; i < ArraySize(descr); i++) { Free(ArrayGet(descr, i)); } ArrayFree(descr); while (HashMapIterate(registers, &key, (void **) &val)) { Free(val); } HashMapFree(registers); return exit; }