mirror of
https://git.kappach.at/lda/Parsee.git
synced 2025-04-20 19:06:09 +02:00
I need to make it so that NetBSD targets get their own correctness/build checks in CI. I also had to do some hacks with Cytoplasm, will make a PR for those soon.
502 lines
14 KiB
C
502 lines
14 KiB
C
/* aya.c - Generates a documentation page for a Parsee function
|
|
* ============================================================
|
|
* Not hdoc because I hate hdoc!!!!!!111!!!!!!1!!!!11111!!!
|
|
* TODO: Aya is currently incomplete. Sorry, not sorry.
|
|
*
|
|
* Under CC0, as its a rather useful example of a KappaChat tool.
|
|
* See LICENSE for more information about Parsee's licensing. */
|
|
|
|
#include <Cytoplasm/HeaderParser.h>
|
|
#include <Cytoplasm/HashMap.h>
|
|
#include <Cytoplasm/Memory.h>
|
|
#include <Cytoplasm/Stream.h>
|
|
#include <Cytoplasm/Args.h>
|
|
#include <Cytoplasm/Log.h>
|
|
#include <Cytoplasm/Str.h>
|
|
#include <Cytoplasm/Uri.h>
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
|
|
typedef struct AyadocComment {
|
|
/* TODO: Descriptions are a bit more advanced than a raw string.
|
|
* Only because you have to deal with the {value} format. */
|
|
char *description;
|
|
|
|
/* Some notes are more advanced (see Returns, Modifies, See-Also, ...) */
|
|
HashMap *notes;
|
|
} AyadocComment;
|
|
|
|
static void
|
|
FreeAyadoc(AyadocComment *comment)
|
|
{
|
|
char *key, *value;
|
|
if (!comment)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Free(comment->description);
|
|
while (HashMapIterate(comment->notes, &key, (void **) &value))
|
|
{
|
|
Free(value);
|
|
}
|
|
HashMapFree(comment->notes);
|
|
|
|
Free(comment);
|
|
}
|
|
static AyadocComment *
|
|
ParseAyadoc(char *raw)
|
|
{
|
|
AyadocComment *ret;
|
|
char *start, *line_break;
|
|
bool end_of_text = false;
|
|
bool parsing_notes = false;
|
|
if (!raw)
|
|
{
|
|
return NULL;
|
|
}
|
|
ret = Malloc(sizeof(*ret));
|
|
ret->notes = HashMapCreate();
|
|
ret->description = NULL;
|
|
|
|
start = raw;
|
|
while (!end_of_text)
|
|
{
|
|
char *line_content = NULL, *temporary = NULL;
|
|
char *index_ptr;
|
|
if (!(line_break = strchr(start, '\n')))
|
|
{
|
|
end_of_text = true;
|
|
|
|
/* Points to the EOF. I am using strchr because we need to
|
|
* defer the {end_of_text}. */
|
|
line_break = start + strlen(start);
|
|
}
|
|
|
|
if (start >= line_break)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (!strncmp(start, " *", 2) || !strncmp(start, "*", 1))
|
|
{
|
|
start += 1 + (*start == ' ');
|
|
while (start && isspace((int) *start))
|
|
{
|
|
start++;
|
|
}
|
|
}
|
|
|
|
line_content = StrDuplicate("");
|
|
for (index_ptr = start; index_ptr < line_break; index_ptr++)
|
|
{
|
|
char char_buffer[2] = { *index_ptr, '\0' };
|
|
|
|
temporary = line_content;
|
|
line_content = StrConcat(2, line_content, char_buffer);
|
|
Free(temporary);
|
|
}
|
|
|
|
if (!strncmp(line_content, "---", 3))
|
|
{
|
|
parsing_notes = true;
|
|
|
|
goto line_end;
|
|
}
|
|
|
|
if (parsing_notes)
|
|
{
|
|
char *del = strchr(line_content, ':');
|
|
char *val = del ? del + 1 : NULL;
|
|
if (del && strlen(del) >= 1)
|
|
{
|
|
while (*val && isspace((int) *val))
|
|
{
|
|
val++;
|
|
}
|
|
}
|
|
if (del)
|
|
{
|
|
*del = '\0';
|
|
}
|
|
HashMapSet(ret->notes, line_content, StrDuplicate(val));
|
|
goto line_end;
|
|
}
|
|
|
|
temporary = ret->description;
|
|
ret->description = StrConcat(3, ret->description, line_content, "\n");
|
|
Free(temporary);
|
|
line_end:
|
|
Free(line_content);
|
|
start = line_break + 1;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
HandleReturnValue(Stream *out, AyadocComment *aya, HeaderDeclaration d, char *field)
|
|
{
|
|
char *tag_del = NULL;
|
|
if (StrEquals(field, "NOTHING"))
|
|
{
|
|
StreamPrintf(out, "<span class='rets-none'>nothing</span>");
|
|
return;
|
|
}
|
|
else if (StrEquals(field, "NORETURN"))
|
|
{
|
|
StreamPrintf(out, "<span class='rets-exit'>doesn't return in the thread</span>");
|
|
return;
|
|
}
|
|
|
|
if ((tag_del = strchr(field, '[')))
|
|
{
|
|
/* We're free to edit field, as it is in the heap */
|
|
*tag_del = '\0';
|
|
}
|
|
|
|
(void) aya;
|
|
(void) d;
|
|
|
|
/* Already write the field */
|
|
StreamPrintf(out, " <span class='rets-span'>%s</span>", field);
|
|
|
|
if (tag_del)
|
|
{
|
|
/* Locate the last ]. */
|
|
char *end_tags = strrchr(++tag_del, ']');
|
|
if (!end_tags)
|
|
{
|
|
end_tags = tag_del + strlen(tag_del);
|
|
}
|
|
|
|
*end_tags = '\0';
|
|
if (StrEquals(tag_del, "LA:HEAP") ||
|
|
StrEquals(tag_del, "HEAP"))
|
|
{
|
|
StreamPrintf(out, " <span class='rets-la'>(stored in the heap)</span>");
|
|
return;
|
|
}
|
|
else if (!strncmp(tag_del, "LA:", 3))
|
|
{
|
|
tag_del += 3;
|
|
}
|
|
StreamPrintf(out, " <span class='rets-la'>(stored along %s)</span>", tag_del);
|
|
}
|
|
}
|
|
static void
|
|
HandleSeeValue(Stream *out, AyadocComment *aya, HeaderDeclaration d, char *field)
|
|
{
|
|
Uri *uri = UriParse(field);
|
|
if (uri)
|
|
{
|
|
StreamPrintf(out,
|
|
"<a class='aya-see-field' href='%s'>%s</a>",
|
|
field, uri->host
|
|
);
|
|
UriFree(uri);
|
|
return;
|
|
}
|
|
StreamPrintf(out,
|
|
"<a href='#fdiv-%s' class='aya-see-field'>%s</a>",
|
|
field, field
|
|
);
|
|
(void) aya;
|
|
(void) d;
|
|
}
|
|
#define SplitBy(sym, cb, del_str) \
|
|
char *start_of_arg = val, *separator = NULL; \
|
|
bool end_of_field = false; \
|
|
\
|
|
while (!end_of_field) \
|
|
{ \
|
|
char *temporary, *field = NULL, *index; \
|
|
char *last_char; \
|
|
while (*start_of_arg && isspace((int) *start_of_arg)) \
|
|
{ \
|
|
start_of_arg++; \
|
|
} \
|
|
if (!(separator = strchr(start_of_arg, sym))) \
|
|
{ \
|
|
end_of_field = true; \
|
|
separator = start_of_arg + strlen(start_of_arg); \
|
|
} \
|
|
if (start_of_arg >= separator) \
|
|
{ \
|
|
break; \
|
|
} \
|
|
\
|
|
/* Trim out spaces */ \
|
|
last_char = separator - 1; \
|
|
while (*last_char && isspace((int) *last_char)) \
|
|
{ \
|
|
last_char--; \
|
|
} \
|
|
\
|
|
field = StrDuplicate(""); \
|
|
for (index = start_of_arg; index <= last_char; index++) \
|
|
{ \
|
|
char buffer[2] = { *index, '\0' }; \
|
|
\
|
|
temporary = field; \
|
|
field = StrConcat(2, field, buffer); \
|
|
Free(temporary); \
|
|
} \
|
|
\
|
|
/* Process the field */ \
|
|
cb(out, ayadoc, decl, field); \
|
|
\
|
|
if (!end_of_field) \
|
|
{ \
|
|
StreamPrintf(out, del_str); \
|
|
} \
|
|
\
|
|
Free(field); \
|
|
start_of_arg = separator + 1; \
|
|
}
|
|
|
|
static void
|
|
GenerateReturns(Stream *out, AyadocComment *ayadoc, HeaderDeclaration decl, char *val)
|
|
{
|
|
SplitBy('|', HandleReturnValue, " <strong>or</strong> ");
|
|
}
|
|
static void
|
|
GenerateSee(Stream *out, AyadocComment *ayadoc, HeaderDeclaration decl, char *val)
|
|
{
|
|
SplitBy(',', HandleSeeValue, ", ");
|
|
}
|
|
#undef SplitBy
|
|
|
|
static void
|
|
GenerateHTML(Stream *out, AyadocComment *ayadoc, HeaderDeclaration decl)
|
|
{
|
|
if (!out || !ayadoc)
|
|
{
|
|
return;
|
|
}
|
|
|
|
StreamPrintf(out, "<div id='fdiv-%s'>", decl.name);
|
|
{
|
|
StreamPrintf(out, "<hr/>");
|
|
|
|
StreamPrintf(out, "<h3>Signature</h3>");
|
|
StreamPrintf(out, "<div id='sdiv-%s'>", decl.name);
|
|
{
|
|
char *attr, *value;
|
|
StreamPrintf(out, "<code>");
|
|
{
|
|
StreamPrintf(out, "<span class='aya-return'>");
|
|
StreamPrintf(out, "%s", decl.returnType);
|
|
StreamPrintf(out, "</span><br/>");
|
|
}
|
|
{
|
|
size_t i, args = ArraySize(decl.args);
|
|
StreamPrintf(out, "<span class='aya-name'>");
|
|
StreamPrintf(out, "<strong>%s</strong>", decl.name);
|
|
StreamPrintf(out, "</span>");
|
|
StreamPrintf(out, "(");
|
|
|
|
for (i = 0; i < args; i++)
|
|
{
|
|
char *arg = ArrayGet(decl.args, i);
|
|
|
|
StreamPrintf(out, "<span class='aya-arg'>");
|
|
StreamPrintf(out, "%s", arg);
|
|
StreamPrintf(out, "</span>");
|
|
|
|
if (i != args - 1)
|
|
{
|
|
StreamPrintf(out, ", ");
|
|
}
|
|
}
|
|
StreamPrintf(out, ");");
|
|
}
|
|
StreamPrintf(out, "</code>");
|
|
|
|
StreamPrintf(out, "<h3>Description</h3>");
|
|
/* TODO: Pretty-print {}s by resolving from context.
|
|
* We don't make the input HTML-safe, aside from newlines as
|
|
* BR tags. */
|
|
StreamPrintf(
|
|
out,
|
|
"<p class='aya-desc'>%s</p>",
|
|
ayadoc->description ?
|
|
ayadoc->description :
|
|
"<span class='aya-ndesc'>"
|
|
"(no description given)"
|
|
"</span>"
|
|
);
|
|
|
|
/* Extra fields */
|
|
while (HashMapIterate(ayadoc->notes, &attr, (void **) &value))
|
|
{
|
|
if (StrEquals(attr, "Returns"))
|
|
{
|
|
StreamPrintf(out, "<div class='aya-pret' id='pret-%s'>", decl.name);
|
|
StreamPrintf(out, "<h2>Returns</h2>");
|
|
GenerateReturns(out, ayadoc, decl, value);
|
|
StreamPrintf(out, "</div>");
|
|
StreamFlush(out);
|
|
continue;
|
|
}
|
|
else if (StrEquals(attr, "See-Also"))
|
|
{
|
|
StreamPrintf(out, "<div class='aya-see' id='see-%s'>", decl.name);
|
|
StreamPrintf(out, "<h2>See also</h2>");
|
|
GenerateSee(out, ayadoc, decl, value);
|
|
StreamPrintf(out, "</div>");
|
|
StreamFlush(out);
|
|
continue;
|
|
}
|
|
else if (StrEquals(attr, "Thrasher"))
|
|
{
|
|
StreamPrintf(out, "<p class='aya-free' id='free-%s'>", decl.name);
|
|
StreamPrintf(out, "<strong>This function may be destroyed with ");
|
|
StreamPrintf(out, "<code><a href='#fdiv-%s'>%s</a></code>", value, value);
|
|
StreamPrintf(out, "</strong></p>");
|
|
StreamFlush(out);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
}
|
|
StreamPrintf(out, "</div>");
|
|
}
|
|
StreamPrintf(out, "</div>");
|
|
StreamFlush(out);
|
|
}
|
|
|
|
int
|
|
Main(Array *args, HashMap *env)
|
|
{
|
|
ArgParseState state;
|
|
int flag;
|
|
|
|
char *header = NULL, *xhtml = NULL, *css = NULL;
|
|
char *project = "Ayaya!";
|
|
Stream *input, *output;
|
|
bool help = false;
|
|
|
|
ArgParseStateInit(&state);
|
|
while ((flag = ArgParse(&state, args, "i:o:p:C:h")) != -1)
|
|
{
|
|
switch (flag)
|
|
{
|
|
case 'i':
|
|
header = state.optArg;
|
|
break;
|
|
case 'o':
|
|
xhtml = state.optArg;
|
|
break;
|
|
case 'C':
|
|
css = state.optArg;
|
|
break;
|
|
case 'p':
|
|
project = state.optArg;
|
|
break;
|
|
case 'h':
|
|
help = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!header || !xhtml || help)
|
|
{
|
|
Log(help ? LOG_INFO : LOG_ERR,
|
|
"Usage: %s "
|
|
"-i [C header file] "
|
|
"-o [Generated Aya HTML] "
|
|
"-p {project name} "
|
|
"-C {CSS stylefile}",
|
|
ArrayGet(args, 0)
|
|
);
|
|
return EXIT_FAILURE;
|
|
}
|
|
input = StreamOpen(header, "r");
|
|
output = StreamOpen(xhtml, "w");
|
|
|
|
StreamPrintf(output, "<!DOCTYPE html>");
|
|
StreamPrintf(output, "<html>");
|
|
|
|
StreamPrintf(output, "<head>");
|
|
if (css)
|
|
{
|
|
Stream *css_stream = StreamOpen(css, "r");
|
|
StreamPrintf(output, "<style>");
|
|
StreamCopy(css_stream, output);
|
|
StreamPrintf(output, "</style>");
|
|
StreamClose(css_stream);
|
|
}
|
|
StreamPrintf(output, "</head>");
|
|
|
|
StreamPrintf(output, "<body>");
|
|
StreamPrintf(output, "<center><h1>%s: %s</h1></center>", project, header);
|
|
StreamPrintf(output, "<br/>");
|
|
/* TODO */
|
|
{
|
|
AyadocComment *comm = NULL;
|
|
HeaderExpr expression = { 0 };
|
|
while (true)
|
|
{
|
|
HeaderDeclaration decl;
|
|
bool raw_put = false;
|
|
|
|
HeaderParse(input, &expression);
|
|
if (expression.type == HP_EOF)
|
|
{
|
|
break;
|
|
}
|
|
|
|
switch (expression.type)
|
|
{
|
|
case HP_COMMENT:
|
|
if (strncmp(expression.data.text, "*", 1) &&
|
|
strncmp(expression.data.text, "-*", 2))
|
|
{
|
|
break;
|
|
}
|
|
if (!strncmp(expression.data.text, "-*", 2))
|
|
{
|
|
raw_put = true;
|
|
}
|
|
if (comm)
|
|
{
|
|
FreeAyadoc(comm);
|
|
}
|
|
comm = ParseAyadoc(expression.data.text + !!raw_put);
|
|
if (raw_put)
|
|
{
|
|
StreamPrintf(output, "<p>");
|
|
StreamPrintf(output, comm->description);
|
|
StreamPrintf(output, "</p>");
|
|
|
|
FreeAyadoc(comm);
|
|
comm = NULL;
|
|
}
|
|
break;
|
|
case HP_DECLARATION:
|
|
if (!comm)
|
|
{
|
|
break;
|
|
}
|
|
decl = expression.data.declaration;
|
|
GenerateHTML(output, comm, decl);
|
|
FreeAyadoc(comm);
|
|
comm = NULL;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
StreamPrintf(output, "</body>");
|
|
|
|
StreamPrintf(output, "</html>");
|
|
StreamFlush(output);
|
|
|
|
StreamClose(output);
|
|
StreamClose(input);
|
|
(void) env;
|
|
return EXIT_SUCCESS;
|
|
}
|