/*-*- 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/alg/alg.h"
#include "libc/bits/bits.h"
#include "libc/sysv/consts/map.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/prot.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, "</code>");
        if (pre) fprintf(f, "</pre>");
        if (ul0 || ul2) fprintf(f, "</ul>");
        if (ol0 || ol2) fprintf(f, "</ol>");
        return;
      case '&':
        fprintf(f, "&amp;");
        bol = false;
        break;
      case '<':
        fprintf(f, "&lt;");
        bol = false;
        break;
      case '>':
        fprintf(f, "&gt;");
        bol = false;
        break;
      case '"':
        fprintf(f, "&quot;");
        bol = false;
        break;
      case '\'':
        fprintf(f, "&apos;");
        bol = false;
        break;
      case '`':
        if (!pre && !bt1 && !bt2 && *s != '`') {
          fprintf(f, "<code>");
          bt1 = true;
        } else if (!pre && !bt1 && !bt2 && *s == '`') {
          fprintf(f, "<code>");
          bt2 = true;
          ++s;
        } else if (bt1) {
          fprintf(f, "</code>");
          bt1 = false;
        } else if (bt2 && *s == '`') {
          fprintf(f, "</code>");
          bt2 = false;
          ++s;
        } else {
          fprintf(f, "`");
        }
        bol = false;
        break;
      case '\n':
        if (!pre && !ul0 && !ul2 && !ol0 && !ol2 && *s == '\n') {
          fprintf(f, "\n<p>");
          bol = true;
        } else if (pre && s[0] != '\n') {
          if (s[0] != ' ' || s[1] != ' ' || s[2] != ' ' || s[3] != ' ') {
            fprintf(f, "</pre>\n");
            pre = false;
            bol = true;
          } else {
            fprintf(f, "\n");
            bol = false;
            s += 4;
          }
        } else if (ul0 && s[0] == '-' && s[1] == ' ') {
          fprintf(f, "\n<li>");
          s += 2;
          bol = false;
        } else if (ul2 && s[0] == ' ' && s[1] == ' ' && s[2] == '-' &&
                   s[3] == ' ') {
          fprintf(f, "\n<li>");
          s += 4;
          bol = false;
        } else if (ul0 && s[0] != '\n' && (s[0] != ' ' || s[1] != ' ')) {
          fprintf(f, "\n</ul>\n");
          bol = true;
          ul0 = false;
        } else if (ul2 && s[0] != '\n' &&
                   (s[0] != ' ' || s[1] != ' ' || s[2] != ' ' || s[3] != ' ')) {
          fprintf(f, "\n</ul>\n");
          bol = true;
          ul2 = false;
        } else if (ol0 && ('0' <= s[0] && s[0] <= '9') && s[1] == '.' &&
                   s[2] == ' ') {
          fprintf(f, "\n<li>");
          s += 3;
          bol = false;
        } else if (ol2 && s[0] == ' ' && s[1] == ' ' &&
                   ('0' <= s[2] && s[2] <= '9') && s[3] == '.' && s[3] == ' ') {
          fprintf(f, "\n<li>");
          s += 5;
          bol = false;
        } else if (ol0 && s[0] != '\n' && (s[0] != ' ' || s[1] != ' ')) {
          fprintf(f, "\n</ol>\n");
          bol = true;
          ol0 = false;
        } else if (ol2 && s[0] != '\n' &&
                   (s[0] != ' ' || s[1] != ' ' || s[2] != ' ' || s[3] != ' ')) {
          fprintf(f, "\n</ol>\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, "<ul><li>");
          ++s;
        } else {
          fprintf(f, "-");
        }
        bol = false;
        break;
      case '1':
        if (bol && !ol0 && !ol2 && !ul0 && !ul2 && s[0] == '.' && s[1] == ' ') {
          ol0 = true;
          fprintf(f, "<ol><li>");
          s += 2;
        } else {
          fprintf(f, "1");
        }
        bol = false;
        break;
      case ' ':
        if (bol && !pre && s[0] == ' ' && s[1] == ' ' && s[2] == ' ') {
          pre = true;
          fprintf(f, "<pre>");
          s += 3;
        } else if (bol && !ul0 && !ul2 && !ol0 && !ol2 && s[0] == ' ' &&
                   s[1] == '-' && s[2] == ' ') {
          ul2 = true;
          fprintf(f, "<ul><li>");
          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, "<ol><li>");
          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, "\
<!doctype html>\n\
<html lang=\"en\">\n\
<meta charset=\"utf-8\">\n\
<script async src=\"https://www.googletagmanager.com/gtag/js?id=UA-43182592-5\"></script>\n\
<script>window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag('js', new Date()); gtag('config', 'UA-43182592-5');</script>\n\
<title>Cosmopolitan C Library</title>\n\
<meta name=\"viewport\" content=\"width=1024\">\n\
<link rel=\"canonical\" href=\"https://justine.lol/cosmopolitan/documentation.html\">\n\
<link rel=\"stylesheet\" href=\"https://fonts.googleapis.com/css2?family=Roboto&display=swap\">\n\
<link rel=\"stylesheet\" href=\"style.css\">\n\
<style>\n\
  .nav {\n\
    margin-bottom: 0;\n\
  }\n\
  .toc {\n\
    overflow-x: auto;\n\
  }\n\
  .toc a {\n\
    text-decoration: none;\n\
  }\n\
  h3 a {\n\
    color: inherit;\n\
    text-decoration: none;\n\
  }\n\
  pre {\n\
    margin-left: 0;\n\
    padding: 12px;\n\
    background: #f6f6f6;\n\
    width: 100%;\n\
    overflow-x: auto;\n\
    border-radius: 5px;\n\
  }\n\
  code {\n\
    padding: 2px 4px;\n\
    background: #e4e6e8;\n\
    border-radius: 3px;\n\
  }\n\
  hr {\n\
    height: 1px;\n\
    margin-bottom: 16px;\n\
    color: #d6d9dc;\n\
    background: #d6d9dc;\n\
    border: 0;\n\
  }\n\
  .category {\n\
    font-weight: bold;\n\
  }\n\
  .tagname {\n\
    font-size: 12pt;\n\
    font-weight: bold;\n\
  }\n\
  .tag {\n\
    margin-top: .5em;\n\
  }\n\
  .tag dl {\n\
    margin-top: .5em;\n\
    margin-bottom: .5em;\n\
    margin-left: 1em;\n\
  }\n\
</style>\n\
\n\
<header>\n\
  <img width=\"196\" height=\"105\"\n\
       src=\"//storage.googleapis.com/justine/cosmopolitan/cosmopolitan.png\"\n\
       title=\"cosmopolitan honeybadger\"\n\
       alt=\"honeybadger\">\n\
  <h1>cosmopolitan libc</h1>\n\
  <span>build-once run-anywhere c without devops</span>\n\
</header>\n\
\n\
<nav class=\"nav\">\n\
  <ul>\n\
    <li><a href=\"index.html\">Intro</a>\n\
    <li><a href=\"download.html\">Download</a>\n\
    <li><a class=\"active\" href=\"documentation.html\">Documentation</a>\n\
    <li><a href=\"sources.html\">Sources</a>\n\
    <li><a href=\"https://github.com/jart/cosmopolitan\">GitHub</a>\n\
    <li><a href=\"license.html\">License</a>\n\
    <li class=\"right\"><a href=\"../index.html\">» jart's web page</a>\n\
  </ul>\n\
</nav>\n\
\n\
<table class=\"dox\" width=\"960\">\n\
<tr>\n\
<td width=\"283\" valign=\"top\" class=\"toc\">\n\
");

  /* // lefthand index: objects */
  /* fprintf(f, "<p><span class=\"category\">macro objects</span>\n"); */
  /* fprintf(f, "<p>\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, "<a href=\"#%s\">%s</a><br>\n", m->name, m->name); */
  /* } */

  /* // lefthand index: functions */
  /* fprintf(f, "<p><span class=\"category\">macro functions</span>\n"); */
  /* fprintf(f, "<p>\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, "<a href=\"#%s\">%s</a><br>\n", m->name, m->name); */
  /* } */

  // lefthand index: objects
  fprintf(f, "<p><span class=\"category\">objects</span>\n");
  fprintf(f, "<p>\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, "<a href=\"#%s\">%s</a><br>\n", o->name, o->name);
  }

  // lefthand index: functions
  fprintf(f, "<p><span class=\"category\">functions</span>\n");
  fprintf(f, "<p>\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, "<a href=\"#%s\">%s</a><br>\n", o->name, o->name);
  }

  // righthand contents
  fprintf(f, "<td width=\"667\" valign=\"top\">\n");
  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, "<hr>");
      fprintf(f, "<div id=\"%s\" class=\"api\">\n", o->name);
      fprintf(f, "<h3><a href=\"#%s\">%s</a></h3>", o->name, o->name);

      // title
      if (o->javadown && *o->javadown->title) {
        fprintf(f, "<p>");
        PrintText(f, o->javadown->title);
        fprintf(f, "\n");
      }

      // text
      if (o->javadown && *o->javadown->text) {
        fprintf(f, "<p>");
        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, "<div class=\"tag\">\n");
        fprintf(f, "<span class=\"tagname\">@param</span>\n");
        fprintf(f, "<dl>\n");
        if (o->params.n) {
          for (j = 0; j < o->params.n; ++j) {
            fprintf(f, "<dt>");
            PrintText(f, o->params.p[j].type);
            fprintf(f, " <em>");
            PrintText(f, o->params.p[j].name);
            fprintf(f, "</em>\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, "<dd>");
                  PrintText(f, o->javadown->tags.p[k].text + strlen(prefix));
                  fprintf(f, "\n");
                  break;
                }
              }
              free(prefix);
            }
          }
        } else {
          for (k = 0; k < o->javadown->tags.n; ++k) {
            if (!strcmp(o->javadown->tags.p[k].tag, "param")) {
              fprintf(f, "<dd>");
              PrintText(f, o->javadown->tags.p[k].text);
              fprintf(f, "\n");
              break;
            }
          }
        }
        if (o->is_variadic) {
          fprintf(f, "<dt>...\n");
        }
        fprintf(f, "</dl>\n");
        fprintf(f, "</div>\n");  // .tag
      }

      // return
      if (o->is_function) {
        fprintf(f, "<div class=\"tag\">\n");
        if (IsNoReturn(o)) {
          fprintf(f, "<span class=\"tagname\">@noreturn</span>\n");
        } else {
          fprintf(f, "<span class=\"tagname\">@return</span>\n");
          was_outputted = false;
          fprintf(f, "<dl>\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, "<dt>");
                PrintText(f, o->type);
                was_outputted = true;
              }
              fprintf(f, "\n<dd>");
              PrintText(f, o->javadown->tags.p[k].text);
              fprintf(f, "\n");
            }
          }
          if (!was_outputted) {
            fprintf(f, "<dt>");
            PrintText(f, o->type);
          }
          fprintf(f, "</dl>\n");
          fprintf(f, "</div>\n");  // .tag
        }
      }

      // type
      if (!o->is_function) {
        fprintf(f, "<div class=\"tag\">\n");
        fprintf(f, "<span class=\"tagname\">@type</span>\n");
        fprintf(f, "<dl>\n");
        fprintf(f, "<dt>");
        PrintText(f, o->type);
        fprintf(f, "</dl>\n");
        fprintf(f, "</div>\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, "<div class=\"tag\">\n");
          fprintf(f, "<span class=\"tagname\">@");
          PrintText(f, o->javadown->tags.p[k].tag);
          fprintf(f, "</span>\n");
          if (*o->javadown->tags.p[k].text) {
            PrintText(f, o->javadown->tags.p[k].text);
            fprintf(f, "\n");
          }
          fprintf(f, "</div>\n");  // .tag
        }
      }

      // sauce
      if (strcmp(o->path, "missingno.c")) {
        fprintf(f, "<div class=\"tag\">\n");
        fprintf(f,
                "<span class=\"tagname\">@see</span> <a "
                "href=\"https://github.com/jart/cosmopolitan/blob/master/"
                "%s#L%d\">%s</a>",
                o->path, o->line, o->path);
        fprintf(f, "</div>\n");  // .tag
      }

      fprintf(f, "</div>\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, "<hr>");
      fprintf(f, "<div id=\"%s\" class=\"api\">\n", m->name);
      fprintf(f, "<h3><a href=\"#%s\">%s</a></h3>", m->name, m->name);
      // title
      if (m->javadown && *m->javadown->title) {
        fprintf(f, "<p>");
        PrintText(f, m->javadown->title);
        fprintf(f, "\n");
      }
      // text
      if (m->javadown && *m->javadown->text) {
        fprintf(f, "<p>");
        PrintText(f, m->javadown->text);
        fprintf(f, "\n");
      }
      // parameters
      if (!m->is_objlike && (m->params.n || HasTag(m->javadown, "param"))) {
        fprintf(f, "<div class=\"tag\">\n");
        fprintf(f, "<span class=\"tagname\">@param</span>\n");
        fprintf(f, "<dl>\n");
        if (m->params.n) {
          for (j = 0; j < m->params.n; ++j) {
            fprintf(f, "<dt>");
            fprintf(f, "<em>");
            PrintText(f, m->params.p[j].name);
            fprintf(f, "</em>\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, "<dd>");
                  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, "<dd>");
              PrintText(f, m->javadown->tags.p[k].text);
              fprintf(f, "\n");
              break;
            }
          }
        }
        fprintf(f, "</dl>\n");
        fprintf(f, "</div>\n");  // .tag
      }
      fprintf(f, "</div>\n"); /* class=".api" */
    }
  }
  fprintf(f, "</table>\n");

  // footer
  fprintf(f, "\
\n\
<footer>\n\
  <p>\n\
    <div style=\"float:right;text-align:right\">\n\
      Free Libre &amp; Open Source<br>\n\
      <a href=\"https://github.com/jart\">github.com/jart/cosmopolitan</a>\n\
    </div>\n\
    Feedback<br>\n\
    jtunney@gmail.com\n\
  </p>\n\
  <div style=\"clear:both\"></div>\n\
</footer>\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);
}