forked from latticeware/Cytoplasm
568 lines
15 KiB
C
568 lines
15 KiB
C
/*
|
|
* 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 <HeaderParser.h>
|
|
|
|
#include <HashMap.h>
|
|
#include <Str.h>
|
|
#include <Memory.h>
|
|
#include <Args.h>
|
|
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <stdlib.h>
|
|
#include <time.h>
|
|
|
|
#include <errno.h>
|
|
|
|
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;
|
|
}
|