/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
╞══════════════════════════════════════════════════════════════════════════════╡
│ Copyright 2020 Justine Alexandra Roberts Tunney │
│ │
│ Permission to use, copy, modify, and/or distribute this software for │
│ any purpose with or without fee is hereby granted, provided that the │
│ above copyright notice and this permission notice appear in all copies. │
│ │
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
│ PERFORMANCE OF THIS SOFTWARE. │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/intrin/bits.h"
#include "libc/mem/alg.h"
#include "libc/sysv/consts/map.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/prot.h"
#include "libc/x/xasprintf.h"
#include "third_party/chibicc/chibicc.h"
#define APPEND(L) L.p = realloc(L.p, ++L.n * sizeof(*L.p))
struct Dox {
unsigned char *p;
struct Freelist {
size_t n;
void **p;
} freelist;
struct Set {
size_t n;
struct SetEntry {
unsigned h;
char *s;
} * p;
} names;
struct DoxObjects {
size_t n;
struct DoxObject {
bool ignore;
char *type;
char *name;
char *path;
int line;
bool is_function;
bool is_variadic;
bool is_weak;
bool is_inline;
bool is_noreturn;
bool is_destructor;
bool is_constructor;
bool is_force_align_arg_pointer;
bool is_no_caller_saved_registers;
char *visibility;
struct Javadown *javadown;
struct DoxObjectParams {
size_t n;
struct DoxObjectParam {
char *type;
char *name;
} * p;
} params;
} * p;
} objects;
struct DoxMacros {
size_t n;
struct DoxMacro {
bool ignore;
char *name;
char *path;
int line;
bool is_objlike;
char *va_args_name;
struct Javadown *javadown;
struct DoxMacroParams {
size_t n;
struct DoxMacroParam {
char *name;
} * p;
} params;
} * p;
} macros;
struct DoxIndex {
size_t n;
struct DoxIndexEntry {
enum DoxIndexType {
kObject,
kMacro,
} t;
int i;
} * p;
} index;
};
static unsigned Hash(const void *p, unsigned long n) {
unsigned h, i;
for (h = i = 0; i < n; i++) {
h += ((unsigned char *)p)[i];
h *= 0x9e3779b1;
}
return MAX(1, h);
}
static struct Dox *NewDox(void) {
return calloc(1, sizeof(struct Dox));
}
static void FreeDox(struct Dox *dox) {
int i;
if (dox) {
for (i = 0; i < dox->freelist.n; ++i) {
free(dox->freelist.p[i]);
}
free(dox->names.p);
free(dox->freelist.p);
free(dox->objects.p);
free(dox->macros.p);
free(dox->index.p);
free(dox);
}
}
static void *FreeLater(struct Dox *dox, void *p) {
APPEND(dox->freelist);
dox->freelist.p[dox->freelist.n - 1] = p;
return p;
}
static int DeserializeInt(struct Dox *dox) {
int x;
x = (unsigned)dox->p[0] << 000 | (unsigned)dox->p[1] << 010 |
(unsigned)dox->p[2] << 020 | (unsigned)dox->p[3] << 030;
dox->p += 4;
return x;
}
static char *DeserializeStr(struct Dox *dox) {
char *s;
size_t n;
n = DeserializeInt(dox);
s = malloc(n + 1);
memcpy(s, dox->p, n);
s[n] = '\0';
dox->p += n;
return FreeLater(dox, s);
}
static struct Javadown *DeserializeJavadown(struct Dox *dox) {
int i;
bool present;
struct Javadown *jd;
if (DeserializeInt(dox)) {
jd = FreeLater(dox, calloc(1, sizeof(struct Javadown)));
jd->isfileoverview = DeserializeInt(dox);
jd->title = DeserializeStr(dox);
jd->text = DeserializeStr(dox);
jd->tags.n = DeserializeInt(dox);
jd->tags.p = FreeLater(dox, malloc(jd->tags.n * sizeof(*jd->tags.p)));
for (i = 0; i < jd->tags.n; ++i) {
jd->tags.p[i].tag = DeserializeStr(dox);
jd->tags.p[i].text = DeserializeStr(dox);
}
return jd;
} else {
return NULL;
}
}
static void DeserializeObject(struct Dox *dox, struct DoxObject *o) {
int i;
o->ignore = false;
o->type = DeserializeStr(dox);
o->name = DeserializeStr(dox);
o->path = DeserializeStr(dox);
o->line = DeserializeInt(dox);
o->is_function = DeserializeInt(dox);
o->is_variadic = DeserializeInt(dox);
o->is_weak = DeserializeInt(dox);
o->is_inline = DeserializeInt(dox);
o->is_noreturn = DeserializeInt(dox);
o->is_destructor = DeserializeInt(dox);
o->is_constructor = DeserializeInt(dox);
o->is_force_align_arg_pointer = DeserializeInt(dox);
o->is_no_caller_saved_registers = DeserializeInt(dox);
o->visibility = DeserializeStr(dox);
o->javadown = DeserializeJavadown(dox);
o->params.n = DeserializeInt(dox);
o->params.p = FreeLater(dox, malloc(o->params.n * sizeof(*o->params.p)));
for (i = 0; i < o->params.n; ++i) {
o->params.p[i].type = DeserializeStr(dox);
o->params.p[i].name = DeserializeStr(dox);
}
}
static void DeserializeMacro(struct Dox *dox, struct DoxMacro *m) {
int i;
m->ignore = false;
m->name = DeserializeStr(dox);
m->path = DeserializeStr(dox);
m->line = DeserializeInt(dox);
m->is_objlike = DeserializeInt(dox);
m->va_args_name = DeserializeStr(dox);
m->javadown = DeserializeJavadown(dox);
m->params.n = DeserializeInt(dox);
m->params.p = FreeLater(dox, malloc(m->params.n * sizeof(*m->params.p)));
for (i = 0; i < m->params.n; ++i) {
m->params.p[i].name = DeserializeStr(dox);
}
}
static void DeserializeDox(struct Dox *dox, const char *path) {
int i, j, n;
i = dox->objects.n;
n = DeserializeInt(dox);
dox->objects.p =
realloc(dox->objects.p, (dox->objects.n + n) * sizeof(*dox->objects.p));
for (j = 0; j < n; ++j) {
DeserializeObject(dox, dox->objects.p + i + j);
}
i = dox->macros.n;
dox->objects.n += n;
n = DeserializeInt(dox);
dox->macros.p =
realloc(dox->macros.p, (dox->macros.n + n) * sizeof(*dox->macros.p));
for (j = 0; j < n; ++j) {
DeserializeMacro(dox, dox->macros.p + i + j);
}
dox->macros.n += n;
CHECK_EQ(31337, DeserializeInt(dox));
}
static void ReadDox(struct Dox *dox, const StringArray *files) {
int i, fd;
void *map;
struct stat st;
for (i = 0; i < files->len; ++i) {
CHECK_NE(-1, (fd = open(files->data[i], O_RDONLY)));
CHECK_NE(-1, fstat(fd, &st));
if (st.st_size) {
CHECK_NE(MAP_FAILED,
(map = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0)));
dox->p = map;
DeserializeDox(dox, files->data[i]);
munmap(map, st.st_size);
}
close(fd);
}
}
static bool AddSet(struct Set *set, char *s) {
unsigned i, h, k;
h = Hash(s, strlen(s));
k = 0;
for (k = 0;; ++k) {
i = (h + k + ((k + 1) >> 1)) & (set->n - 1);
if (!set->p[i].h) {
set->p[i].h = h;
set->p[i].s = s;
return true;
}
if (h == set->p[i].h && !strcmp(s, set->p[i].s)) {
return false;
}
}
}
static int CompareDoxIndexEntry(const void *p1, const void *p2, void *arg) {
struct Dox *dox;
const char *s1, *s2;
struct DoxIndexEntry *a, *b;
dox = arg, a = p1, b = p2;
s1 = a->t == kObject ? dox->objects.p[a->i].name : dox->macros.p[a->i].name;
s2 = b->t == kObject ? dox->objects.p[b->i].name : dox->macros.p[b->i].name;
while (*s1 == '_') ++s1;
while (*s2 == '_') ++s2;
return strcasecmp(s1, s2);
}
static void IndexDox(struct Dox *dox) {
size_t i, j, n;
dox->names.n = _roundup2pow(dox->objects.n + dox->macros.n) << 1;
dox->names.p = calloc(dox->names.n, sizeof(*dox->names.p));
n = 0;
for (i = 0; i < dox->objects.n; ++i) {
if (AddSet(&dox->names, dox->objects.p[i].name)) {
++n;
} else {
dox->objects.p[i].ignore = true;
}
}
for (i = 0; i < dox->macros.n; ++i) {
if (AddSet(&dox->names, dox->macros.p[i].name)) {
++n;
} else {
dox->macros.p[i].ignore = true;
}
}
dox->index.n = n;
dox->index.p = malloc(n * sizeof(*dox->index.p));
j = 0;
for (i = 0; i < dox->objects.n; ++i) {
if (dox->objects.p[i].ignore) continue;
dox->index.p[j].t = kObject;
dox->index.p[j].i = i;
++j;
}
for (i = 0; i < dox->macros.n; ++i) {
if (dox->macros.p[i].ignore) continue;
dox->index.p[j].t = kMacro;
dox->index.p[j].i = i;
++j;
}
CHECK_EQ(n, j);
qsort_r(dox->index.p, dox->index.n, sizeof(*dox->index.p),
CompareDoxIndexEntry, dox);
}
/**
* Escapes HTML entities and interprets basic Markdown syntax.
*/
static void PrintText(FILE *f, const char *s) {
int c;
bool bol, pre, ul0, ul2, ol0, ol2, bt1, bt2;
for (bt1 = bt2 = ul2 = ul0 = ol2 = ol0 = pre = false, bol = true;;) {
switch ((c = *s++)) {
case '\0':
if (bt1 || bt2) fprintf(f, "");
if (pre) fprintf(f, "");
if (ul0 || ul2) fprintf(f, "");
if (ol0 || ol2) fprintf(f, "");
return;
case '&':
fprintf(f, "&");
bol = false;
break;
case '<':
fprintf(f, "<");
bol = false;
break;
case '>':
fprintf(f, ">");
bol = false;
break;
case '"':
fprintf(f, """);
bol = false;
break;
case '\'':
fprintf(f, "'");
bol = false;
break;
case '`':
if (!pre && !bt1 && !bt2 && *s != '`') {
fprintf(f, "");
bt1 = true;
} else if (!pre && !bt1 && !bt2 && *s == '`') {
fprintf(f, "");
bt2 = true;
++s;
} else if (bt1) {
fprintf(f, "
");
bt1 = false;
} else if (bt2 && *s == '`') {
fprintf(f, "
");
bt2 = false;
++s;
} else {
fprintf(f, "`");
}
bol = false;
break;
case '\n':
if (!pre && !ul0 && !ul2 && !ol0 && !ol2 && *s == '\n') {
fprintf(f, "\n
");
bol = true;
} else if (pre && s[0] != '\n') {
if (s[0] != ' ' || s[1] != ' ' || s[2] != ' ' || s[3] != ' ') {
fprintf(f, "\n");
pre = false;
bol = true;
} else {
fprintf(f, "\n");
bol = false;
s += 4;
}
} else if (ul0 && s[0] == '-' && s[1] == ' ') {
fprintf(f, "\n
");
s += 2;
bol = false;
} else if (ul2 && s[0] == ' ' && s[1] == ' ' && s[2] == '-' &&
s[3] == ' ') {
fprintf(f, "\n ");
s += 4;
bol = false;
} else if (ul0 && s[0] != '\n' && (s[0] != ' ' || s[1] != ' ')) {
fprintf(f, "\n\n");
bol = true;
ul0 = false;
} else if (ul2 && s[0] != '\n' &&
(s[0] != ' ' || s[1] != ' ' || s[2] != ' ' || s[3] != ' ')) {
fprintf(f, "\n\n");
bol = true;
ul2 = false;
} else if (ol0 && ('0' <= s[0] && s[0] <= '9') && s[1] == '.' &&
s[2] == ' ') {
fprintf(f, "\n ");
s += 3;
bol = false;
} else if (ol2 && s[0] == ' ' && s[1] == ' ' &&
('0' <= s[2] && s[2] <= '9') && s[3] == '.' && s[3] == ' ') {
fprintf(f, "\n ");
s += 5;
bol = false;
} else if (ol0 && s[0] != '\n' && (s[0] != ' ' || s[1] != ' ')) {
fprintf(f, "\n\n");
bol = true;
ol0 = false;
} else if (ol2 && s[0] != '\n' &&
(s[0] != ' ' || s[1] != ' ' || s[2] != ' ' || s[3] != ' ')) {
fprintf(f, "\n\n");
bol = true;
ol2 = false;
} else {
fprintf(f, "\n");
bol = true;
}
break;
case '-':
if (bol && !ul0 && !ul2 && !ol0 && !ol2 && s[0] == ' ') {
ul0 = true;
fprintf(f, "");
++s;
} else {
fprintf(f, "-");
}
bol = false;
break;
case '1':
if (bol && !ol0 && !ol2 && !ul0 && !ul2 && s[0] == '.' && s[1] == ' ') {
ol0 = true;
fprintf(f, "");
s += 2;
} else {
fprintf(f, "1");
}
bol = false;
break;
case ' ':
if (bol && !pre && s[0] == ' ' && s[1] == ' ' && s[2] == ' ') {
pre = true;
fprintf(f, "");
s += 3;
} else if (bol && !ul0 && !ul2 && !ol0 && !ol2 && s[0] == ' ' &&
s[1] == '-' && s[2] == ' ') {
ul2 = true;
fprintf(f, " ");
s += 3;
} else if (bol && !ul0 && !ul2 && !ol0 && !ol2 && s[0] == ' ' &&
('0' <= s[1] && s[1] <= '9') && s[2] == '.' && s[3] == ' ') {
ol2 = true;
fprintf(f, "");
s += 4;
} else {
fprintf(f, " ");
}
bol = false;
break;
default:
fprintf(f, "%c", c);
bol = false;
break;
}
}
}
static bool HasTag(struct Javadown *jd, const char *tag) {
int k;
if (jd) {
for (k = 0; k < jd->tags.n; ++k) {
if (!strcmp(jd->tags.p[k].tag, tag)) {
return true;
}
}
}
return false;
}
static bool IsNoReturn(struct DoxObject *o) {
return o->is_noreturn || HasTag(o->javadown, "noreturn");
}
static void PrintDox(struct Dox *dox, FILE *f) {
int i, j, k;
char *prefix;
bool was_outputted;
struct DoxMacro *m;
struct DoxObject *o;
// header
fprintf(f, "\
\n\
\n\
\n\
Cosmopolitan C Library \n\
\n\
\n\
\n\
\n\
\n\
\n\
\n\
\n\
\n\
\n\
\n\
\n\
\n\
\n\
\n\
\n\
\n\
\n\
");
/* // lefthand index: objects */
/* fprintf(f, "macro objects \n"); */
/* fprintf(f, "
\n"); */
/* for (i = 0; i < dox->index.n; ++i) { */
/* if (dox->index.p[i].t != kMacro) continue; */
/* m = dox->macros.p + dox->index.p[i].i; */
/* if (m->ignore) continue; */
/* if (!m->is_objlike) continue; */
/* fprintf(f, "%s \n", m->name, m->name); */
/* } */
/* // lefthand index: functions */
/* fprintf(f, "
macro functions \n"); */
/* fprintf(f, "
\n"); */
/* for (i = 0; i < dox->index.n; ++i) { */
/* if (dox->index.p[i].t != kMacro) continue; */
/* m = dox->macros.p + dox->index.p[i].i; */
/* if (m->ignore) continue; */
/* if (m->is_objlike) continue; */
/* fprintf(f, "%s \n", m->name, m->name); */
/* } */
// lefthand index: objects
fprintf(f, "
objects \n");
fprintf(f, "
\n");
for (i = 0; i < dox->index.n; ++i) {
if (dox->index.p[i].t != kObject) continue;
o = dox->objects.p + dox->index.p[i].i;
if (o->ignore) continue;
if (o->is_function) continue;
fprintf(f, "%s \n", o->name, o->name);
}
// lefthand index: functions
fprintf(f, "
functions \n");
fprintf(f, "
\n");
for (i = 0; i < dox->index.n; ++i) {
if (dox->index.p[i].t != kObject) continue;
o = dox->objects.p + dox->index.p[i].i;
if (o->ignore) continue;
if (!o->is_function) continue;
fprintf(f, "%s \n", o->name, o->name);
}
fprintf(f, "
\n\n");
// jump to top button
fprintf(f, "\
\n\
Top of the page \n\
\n\
\n\
");
// right hand column
fprintf(f, "
\n");
// search bar
fprintf(f, "\
\n\
\n\
\n\n");
// righthand contents
for (i = 0; i < dox->index.n; ++i) {
if (dox->index.p[i].t == kObject) {
o = dox->objects.p + dox->index.p[i].i;
if (o->ignore) continue;
fprintf(f, "\n");
if (i) fprintf(f, " ");
fprintf(f, "\n", o->name);
fprintf(f, "
", o->name, o->name);
// title
if (o->javadown && *o->javadown->title) {
fprintf(f, "
");
PrintText(f, o->javadown->title);
fprintf(f, "\n");
}
// text
if (o->javadown && *o->javadown->text) {
fprintf(f, "
");
PrintText(f, o->javadown->text);
fprintf(f, "\n");
}
// parameters
if (o->is_function &&
(o->is_variadic || o->params.n || HasTag(o->javadown, "param"))) {
fprintf(f, "
\n");
fprintf(f, "
@param \n");
fprintf(f, "
\n");
if (o->params.n) {
for (j = 0; j < o->params.n; ++j) {
fprintf(f, "");
PrintText(f, o->params.p[j].type);
fprintf(f, " ");
PrintText(f, o->params.p[j].name);
fprintf(f, " \n");
if (o->javadown) {
prefix = xasprintf("%s ", o->params.p[j].name);
for (k = 0; k < o->javadown->tags.n; ++k) {
if (!strcmp(o->javadown->tags.p[k].tag, "param") &&
_startswith(o->javadown->tags.p[k].text, prefix)) {
fprintf(f, " ");
PrintText(f, o->javadown->tags.p[k].text + strlen(prefix));
fprintf(f, "\n");
break;
}
}
free(prefix);
}
}
} else if (o->javadown) {
for (k = 0; k < o->javadown->tags.n; ++k) {
if (!strcmp(o->javadown->tags.p[k].tag, "param")) {
fprintf(f, "");
PrintText(f, o->javadown->tags.p[k].text);
fprintf(f, "\n");
break;
}
}
}
if (o->is_variadic) {
fprintf(f, " ...\n");
}
fprintf(f, " \n");
fprintf(f, "
\n"); // .tag
}
// return
if (o->is_function) {
fprintf(f, "
\n");
if (IsNoReturn(o)) {
fprintf(f, "
@noreturn \n");
} else {
fprintf(f, "
@return \n");
was_outputted = false;
fprintf(f, "
\n");
if (o->javadown) {
for (k = 0; k < o->javadown->tags.n; ++k) {
if (strcmp(o->javadown->tags.p[k].tag, "return")) continue;
if (!was_outputted) {
fprintf(f, "");
PrintText(f, o->type);
was_outputted = true;
}
fprintf(f, "\n ");
PrintText(f, o->javadown->tags.p[k].text);
fprintf(f, "\n");
}
}
if (!was_outputted) {
fprintf(f, " ");
PrintText(f, o->type);
}
fprintf(f, " \n");
}
fprintf(f, "
\n"); // .tag
}
// type
if (!o->is_function) {
fprintf(f, "
\n");
fprintf(f, "
@type \n");
fprintf(f, "
\n");
fprintf(f, "");
PrintText(f, o->type);
fprintf(f, " \n");
fprintf(f, "
\n"); // .tag
}
// tags
if (o->javadown) {
for (k = 0; k < o->javadown->tags.n; ++k) {
if (!strcmp(o->javadown->tags.p[k].tag, "param")) continue;
if (!strcmp(o->javadown->tags.p[k].tag, "return")) continue;
if (!strcmp(o->javadown->tags.p[k].tag, "noreturn")) continue;
fprintf(f, "
\n");
fprintf(f, "@");
PrintText(f, o->javadown->tags.p[k].tag);
fprintf(f, " \n");
if (*o->javadown->tags.p[k].text) {
PrintText(f, o->javadown->tags.p[k].text);
fprintf(f, "\n");
}
fprintf(f, "
\n"); // .tag
}
}
// sauce
if (strcmp(o->path, "missingno.c")) {
fprintf(f, "
\n");
fprintf(f,
"
@see %s ",
o->path, o->line, o->path);
fprintf(f, "
\n"); // .tag
}
fprintf(f, "
\n"); /* class=".api" */
} else {
continue;
m = dox->macros.p + dox->index.p[i].i;
if (m->ignore) continue;
fprintf(f, "\n");
if (i) fprintf(f, " ");
fprintf(f, "\n", m->name);
fprintf(f, "
", m->name, m->name);
// title
if (m->javadown && *m->javadown->title) {
fprintf(f, "
");
PrintText(f, m->javadown->title);
fprintf(f, "\n");
}
// text
if (m->javadown && *m->javadown->text) {
fprintf(f, "
");
PrintText(f, m->javadown->text);
fprintf(f, "\n");
}
// parameters
if (!m->is_objlike && (m->params.n || HasTag(m->javadown, "param"))) {
fprintf(f, "
\n");
fprintf(f, "
@param \n");
fprintf(f, "
\n");
if (m->params.n) {
for (j = 0; j < m->params.n; ++j) {
fprintf(f, "");
fprintf(f, "");
PrintText(f, m->params.p[j].name);
fprintf(f, " \n");
if (m->javadown) {
prefix = xasprintf("%s ", m->params.p[j].name);
for (k = 0; k < m->javadown->tags.n; ++k) {
if (!strcmp(m->javadown->tags.p[k].tag, "param") &&
_startswith(m->javadown->tags.p[k].text, prefix)) {
fprintf(f, " ");
PrintText(f, m->javadown->tags.p[k].text + strlen(prefix));
fprintf(f, "\n");
break;
}
}
free(prefix);
}
}
} else {
for (k = 0; k < m->javadown->tags.n; ++k) {
if (!strcmp(m->javadown->tags.p[k].tag, "param")) {
fprintf(f, "");
PrintText(f, m->javadown->tags.p[k].text);
fprintf(f, "\n");
break;
}
}
}
fprintf(f, " \n");
fprintf(f, "
\n"); // .tag
}
fprintf(f, "
\n"); /* class=".api" */
}
}
fprintf(f, " \n");
// footer
fprintf(f, "\
\n\
\n\
");
}
/**
* Merges documentation data and outputs HTML.
*/
void drop_dox(const StringArray *files, const char *path) {
FILE *f;
struct Dox *dox;
dox = NewDox();
ReadDox(dox, files);
IndexDox(dox);
f = fopen(path, "w");
PrintDox(dox, f);
fclose(f);
FreeDox(dox);
}