/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */ /* * Main authors: * Guido Tack */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include #include #include #include #include #include #include #include #include namespace MiniZinc { namespace HtmlDocOutput { // Trim leading space: // - always trim first line completely // - second line defines the base indentation std::string trim(const std::string& s0) { std::string s = s0; // remove carriage returns size_t j = 0; for (size_t i = 0; i < s.size(); i++) { if (s[i] != '\r') { s[j++] = s[i]; } } s.resize(j); size_t first_line_indent = s.find_first_not_of(" \t"); if (first_line_indent == std::string::npos) { return ""; } size_t first_nl = s.find('\n'); std::ostringstream oss; if (first_line_indent == first_nl) { // first line is empty oss << "\n"; } else { // strip first line size_t end_of_first_line = first_nl == std::string::npos ? std::string::npos : first_nl - first_line_indent + 1; oss << s.substr(first_line_indent, end_of_first_line); } if (first_nl == std::string::npos) { return oss.str(); } size_t unindent = s.find_first_not_of(" \t", first_nl + 1); if (unindent == std::string::npos) { return oss.str(); } size_t pos = s.find('\n', first_nl + 1); if (unindent == 0 || unindent > pos) { oss << s.substr(first_nl + 1, std::string::npos); return oss.str(); } size_t lastpos = unindent; while (pos != std::string::npos) { oss << s.substr(lastpos, pos - lastpos) << "\n"; size_t next_indent = s.find_first_not_of(" \t", pos + 1); if (next_indent == std::string::npos || next_indent - (pos + 1) < unindent) { lastpos = next_indent; } else { lastpos = pos + 1 + unindent; } pos = (lastpos == std::string::npos ? lastpos : s.find('\n', lastpos)); } if (lastpos != std::string::npos) { oss << s.substr(lastpos, std::string::npos); } return oss.str(); } class DocItem { public: enum DocType { T_PAR = 0, T_VAR = 1, T_FUN = 2 }; DocItem(const DocType& t0, std::string id0, std::string sig0, std::string doc0) : t(t0), id(std::move(id0)), sig(std::move(sig0)), doc(std::move(doc0)) {} DocType t; std::string id; std::string sig; std::string doc; }; typedef std::unordered_map FunMap; class Group; class GroupMap { public: typedef std::vector Map; Map m; ~GroupMap(); Map::iterator find(const std::string& n); }; class Group { public: Group(std::string name0, std::string fullPath0) : name(std::move(name0)), fullPath(std::move(fullPath0)) {} std::string name; std::string fullPath; std::string desc; std::string htmlName; GroupMap subgroups; std::vector items; std::string getAnchor(int level, int indivFileLevel) const { if (level < indivFileLevel) { return fullPath + ".html"; } return "#" + fullPath; } std::string toHTML(int level, int indivFileLevel, Group* parent, unsigned int idx, const std::string& basename, bool generateIndex) { std::ostringstream oss; int realLevel = (level < indivFileLevel) ? 0 : level - indivFileLevel; oss << "
\n"; if (parent != nullptr) { oss << "
"; if (idx > 0) { oss << " "; } oss << " "; if (idx < parent->subgroups.m.size() - 1) { oss << " "; } if (generateIndex) { oss << "Index\n"; } if (!items.empty()) { oss << "reveal " "all\n"; oss << "hide " "all\n"; } oss << "
"; } if (!htmlName.empty()) { oss << "\n"; oss << "
\n" << desc << "
\n"; } if (!subgroups.m.empty()) { oss << "

Sections:

\n"; oss << "
    \n"; for (auto& it : subgroups.m) { oss << "
  • " << it->htmlName << "\n"; if (it->htmlName.empty()) { std::cerr << "Warning: undocumented group " << it->fullPath << "\n"; } } oss << "
\n"; if (parent == nullptr && generateIndex) { oss << "

Index

\n"; } if (!items.empty()) { oss << "

Declarations in this section:

\n"; } } struct SortById { bool operator()(const DocItem& i0, const DocItem& i1) { return i0.t < i1.t || (i0.t == i1.t && i0.id < i1.id); } } _cmp; std::stable_sort(items.begin(), items.end(), _cmp); int cur_t = -1; const char* dt[] = {"par", "var", "fun"}; const char* dt_desc[] = {"Parameters", "Variables", "Functions and Predicates"}; for (const auto& item : items) { if (item.t != cur_t) { if (cur_t != -1) { oss << "
\n"; } cur_t = item.t; oss << "
\n"; oss << "
" << dt_desc[cur_t] << "
\n"; } oss << item.doc; } if (cur_t != -1) { oss << "
\n"; } if (level >= indivFileLevel) { for (unsigned int i = 0; i < subgroups.m.size(); i++) { oss << subgroups.m[i]->toHTML(level + 1, indivFileLevel, this, i, basename, generateIndex); } } oss << ""; return oss.str(); } static std::string rstHeading(const std::string& s, int level) { std::vector levelChar({'#', '=', '-', '^', '+', '"'}); std::ostringstream oss; oss << s << "\n"; for (int i = 0; i < s.size(); i++) { oss << levelChar[level]; } oss << "\n\n"; return oss.str(); } std::string toRST(int level) { std::ostringstream oss; if (!htmlName.empty()) { if (level == 0) { oss << ".. _ch-lib-" << name << ":\n\n"; } oss << rstHeading(htmlName, level); oss << HtmlDocOutput::trim(desc) << "\n\n"; } for (auto& i : subgroups.m) { oss << i->toRST(level + 1); } if (!items.empty()) { if (!subgroups.m.empty()) { oss << rstHeading("Other declarations", level + 1); } struct SortById { bool operator()(const DocItem& i0, const DocItem& i1) { return i0.t < i1.t || (i0.t == i1.t && i0.id < i1.id); } } _cmp; std::stable_sort(items.begin(), items.end(), _cmp); int cur_t = -1; int nHeadings = 0; for (const auto& item : items) { if (item.t != cur_t) { cur_t = item.t; nHeadings++; } } cur_t = -1; const char* dt_desc[] = {"Parameters", "Variables", "Functions and Predicates"}; for (const auto& item : items) { if (item.t != cur_t) { cur_t = item.t; if (nHeadings > 1) { oss << rstHeading(dt_desc[cur_t], subgroups.m.empty() ? level + 1 : level + 2); } } oss << item.doc; } } return oss.str(); } }; GroupMap::~GroupMap() { for (auto& it : m) { delete it; } } GroupMap::Map::iterator GroupMap::find(const std::string& n) { for (auto it = m.begin(); it != m.end(); ++it) { if ((*it)->name == n) { return it; } } return m.end(); } void add_to_group(Group& gm, const std::string& group, DocItem& di) { std::vector subgroups; size_t lastpos = 0; size_t pos = group.find('.'); while (pos != std::string::npos) { subgroups.push_back(group.substr(lastpos, pos - lastpos)); lastpos = pos + 1; pos = group.find('.', lastpos); } subgroups.push_back(group.substr(lastpos, std::string::npos)); GroupMap* cgm = &gm.subgroups; std::string gpath(gm.fullPath); for (unsigned int i = 0; i < subgroups.size(); i++) { gpath += "-"; gpath += subgroups[i]; if (cgm->find(subgroups[i]) == cgm->m.end()) { cgm->m.push_back(new Group(subgroups[i], gpath)); } Group& g = **cgm->find(subgroups[i]); if (i == subgroups.size() - 1) { g.items.push_back(di); } else { cgm = &g.subgroups; } } } void set_group_desc(Group& maingroup, const std::string& group, const std::string& htmlName, const std::string& s) { if (group == "MAIN") { if (!maingroup.htmlName.empty()) { std::cerr << "Warning: two descriptions for group `" << group << "'\n"; } maingroup.htmlName = htmlName; maingroup.desc = s; return; } std::vector subgroups; size_t lastpos = 0; size_t pos = group.find('.'); while (pos != std::string::npos) { subgroups.push_back(group.substr(lastpos, pos - lastpos)); lastpos = pos + 1; pos = group.find('.', lastpos); } subgroups.push_back(group.substr(lastpos, std::string::npos)); GroupMap* cgm = &maingroup.subgroups; std::string gpath(maingroup.fullPath); for (unsigned int i = 0; i < subgroups.size(); i++) { gpath += "-"; gpath += subgroups[i]; if (cgm->find(subgroups[i]) == cgm->m.end()) { cgm->m.push_back(new Group(subgroups[i], gpath)); } Group& g = **cgm->find(subgroups[i]); if (i == subgroups.size() - 1) { if (!g.htmlName.empty()) { std::cerr << "Warning: two descriptions for group `" << group << "'\n"; } g.htmlName = htmlName; g.desc = s; } else { cgm = &g.subgroups; } } } std::string extract_arg_word(std::string& s, size_t n) { size_t start = n; while (start < s.size() && s[start] != ' ' && s[start] != '\t') { start++; } while (start < s.size() && (s[start] == ' ' || s[start] == '\t')) { start++; } size_t end = start + 1; while (end < s.size() && ((isalnum(s[end]) != 0) || s[end] == '_' || s[end] == '.')) { end++; } std::string ret = s.substr(start, end - start); s = s.substr(end, std::string::npos); return ret; } std::string make_html_id(const std::string& ident) { std::ostringstream oss; oss << "I"; bool prevWasSym = false; for (char i : ident) { bool isSym = true; switch (i) { case '!': oss << "-ex"; break; case '=': oss << "-eq"; break; case '*': oss << "-as"; break; case '+': oss << "-pl"; break; case '-': oss << "-mi"; break; case '>': oss << "-gr"; break; case '<': oss << "-lt"; break; case '/': oss << "-dv"; break; case '\\': oss << "-bs"; break; case '~': oss << "-tl"; break; case '\'': oss << "-tk"; break; case ' ': case '\t': case '\n': break; case ':': oss << "-cl"; break; case '[': oss << "-bo"; break; case ']': oss << "-bc"; break; case '$': oss << "-dd"; break; case '(': oss << "-po"; break; case ')': oss << "-pc"; break; case ',': oss << "-cm"; break; default: oss << (prevWasSym ? "-" : "") << i; isSym = false; break; } prevWasSym = isSym; } return oss.str(); } } // namespace HtmlDocOutput class CollectFunctionsVisitor : public ItemVisitor { protected: EnvI& _env; HtmlDocOutput::FunMap& _funmap; bool _includeStdLib; public: CollectFunctionsVisitor(EnvI& env, HtmlDocOutput::FunMap& funmap, bool includeStdLib) : _env(env), _funmap(funmap), _includeStdLib(includeStdLib) {} bool enterModel(Model* m) const { return _includeStdLib || m->filename() != "stdlib.mzn"; } void vFunctionI(FunctionI* fi) { if (Call* docstring = Expression::dynamicCast(get_annotation(fi->ann(), constants().ann.doc_comment))) { std::string ds = eval_string(_env, docstring->arg(0)); std::string group("main"); size_t group_idx = ds.find("@group"); if (group_idx != std::string::npos) { group = HtmlDocOutput::extract_arg_word(ds, group_idx); } _funmap.insert(std::make_pair(fi, group)); } } }; class PrintHtmlVisitor : public ItemVisitor { protected: EnvI& _env; HtmlDocOutput::Group& _maingroup; HtmlDocOutput::FunMap& _funmap; bool _includeStdLib; static std::vector replaceArgs(std::string& s) { std::vector replacements; std::ostringstream oss; size_t lastpos = 0; size_t pos = std::min(s.find("\\a"), s.find("\\p")); size_t mathjax_open = s.find("\\("); size_t mathjax_close = s.rfind("\\)"); if (pos == std::string::npos) { return replacements; } while (pos != std::string::npos) { oss << s.substr(lastpos, pos - lastpos); size_t start = pos; while (start < s.size() && s[start] != ' ' && s[start] != '\t') { start++; } while (start < s.size() && (s[start] == ' ' || s[start] == '\t')) { start++; } size_t end = start + 1; while (end < s.size() && ((isalnum(s[end]) != 0) || s[end] == '_')) { end++; } if (s[pos + 1] == 'a') { replacements.push_back(s.substr(start, end - start)); if (pos >= mathjax_open && pos <= mathjax_close) { oss << "{\\bf " << replacements.back() << "}"; } else { oss << "" << replacements.back() << ""; } } else { if (pos >= mathjax_open && pos <= mathjax_close) { oss << "{\\bf " << s.substr(start, end - start) << "}"; } else { oss << "" << s.substr(start, end - start) << ""; } } lastpos = end; pos = std::min(s.find("\\a", lastpos), s.find("\\p", lastpos)); } oss << s.substr(lastpos, std::string::npos); s = oss.str(); return replacements; } static std::pair extractArgLine(std::string& s, size_t n) { size_t start = n; while (start < s.size() && s[start] != ' ' && s[start] != '\t') { start++; } while (start < s.size() && (s[start] == ' ' || s[start] == '\t')) { start++; } size_t end = start + 1; while (end < s.size() && s[end] != ':') { end++; } std::string arg = s.substr(start, end - start); size_t doc_start = end + 1; while (end < s.size() && s[end] != '\n') { end++; } std::string ret = s.substr(doc_start, end - doc_start); replaceArgs(ret); s = s.substr(0, n) + s.substr(end, std::string::npos); return make_pair(arg, ret); } static std::string addHTML(const std::string& s) { std::ostringstream oss; size_t lastpos = 0; size_t pos = s.find('\n'); bool inUl = false; oss << "

\n"; while (pos != std::string::npos) { oss << s.substr(lastpos, pos - lastpos); size_t next = std::min(s.find('\n', pos + 1), s.find('-', pos + 1)); if (next == std::string::npos) { lastpos = pos + 1; break; } bool allwhite = true; for (size_t cur = pos + 1; cur < next; cur++) { if (s[cur] != ' ' && s[cur] != '\t') { allwhite = false; break; } } if (allwhite) { if (s[next] == '-') { if (!inUl) { oss << "

    \n"; inUl = true; } oss << "
  • "; } else { if (inUl) { oss << "
\n"; inUl = false; } else { oss << "

\n"; } } lastpos = next + 1; pos = s.find('\n', lastpos); } else { lastpos = pos + 1; if (s[pos] == '\n') { oss << " "; } if (s[next] == '-') { pos = s.find('\n', next + 1); } else { pos = next; } } } oss << s.substr(lastpos, std::string::npos); if (inUl) { oss << "\n"; } oss << "

\n"; return oss.str(); } public: PrintHtmlVisitor(EnvI& env, HtmlDocOutput::Group& mg, HtmlDocOutput::FunMap& fm, bool includeStdLib) : _env(env), _maingroup(mg), _funmap(fm), _includeStdLib(includeStdLib) {} bool enterModel(Model* m) { if (!_includeStdLib && m->filename() == "stdlib.mzn") { return false; } const std::string& dc = m->docComment(); if (!dc.empty()) { size_t gpos = dc.find("@groupdef"); while (gpos != std::string::npos) { size_t start = gpos; while (start < dc.size() && dc[start] != ' ' && dc[start] != '\t') { start++; } while (start < dc.size() && (dc[start] == ' ' || dc[start] == '\t')) { start++; } size_t end = start + 1; while (end < dc.size() && ((isalnum(dc[end]) != 0) || dc[end] == '_' || dc[end] == '.')) { end++; } std::string groupName = dc.substr(start, end - start); size_t doc_start = end + 1; while (end < dc.size() && dc[end] != '\n') { end++; } std::string groupHTMLName = dc.substr(doc_start, end - doc_start); size_t next = dc.find("@groupdef", gpos + 1); HtmlDocOutput::set_group_desc( _maingroup, groupName, groupHTMLName, addHTML(dc.substr(end, next == std::string::npos ? next : next - end))); gpos = next; } } return true; } /// Visit variable declaration void vVarDeclI(VarDeclI* vdi) { if (Call* docstring = Expression::dynamicCast( get_annotation(vdi->e()->ann(), constants().ann.doc_comment))) { std::string ds = eval_string(_env, docstring->arg(0)); std::string group("main"); size_t group_idx = ds.find("@group"); if (group_idx != std::string::npos) { group = HtmlDocOutput::extract_arg_word(ds, group_idx); } std::ostringstream os; std::string sig = vdi->e()->type().toString(_env) + " " + std::string(vdi->e()->id()->str().c_str()); os << "
\n"; os << "
\n"; if (vdi->e()->ti()->type() == Type::ann()) { os << "annotation "; os << "" << *vdi->e()->id() << ""; } else { os << *vdi->e()->ti() << ": " << *vdi->e()->id(); } os << "
\n"; os << addHTML(ds); os << "
"; GCLock lock; HtmlDocOutput::DocItem di( vdi->e()->type().isPar() ? HtmlDocOutput::DocItem::T_PAR : HtmlDocOutput::DocItem::T_VAR, sig, sig, os.str()); HtmlDocOutput::add_to_group(_maingroup, group, di); } } /// Visit function item void vFunctionI(FunctionI* fi) { if (Call* docstring = Expression::dynamicCast(get_annotation(fi->ann(), constants().ann.doc_comment))) { std::string ds = eval_string(_env, docstring->arg(0)); std::string group("main"); size_t group_idx = ds.find("@group"); if (group_idx != std::string::npos) { group = HtmlDocOutput::extract_arg_word(ds, group_idx); } size_t param_idx = ds.find("@param"); std::vector > params; while (param_idx != std::string::npos) { params.push_back(extractArgLine(ds, param_idx)); param_idx = ds.find("@param"); } std::vector args = replaceArgs(ds); std::unordered_set allArgs; for (auto& arg : args) { allArgs.insert(arg); } for (auto& param : params) { allArgs.insert(param.first); } GCLock lock; for (unsigned int i = 0; i < fi->params().size(); i++) { std::string param(fi->params()[i]->id()->str().c_str(), fi->params()[i]->id()->str().size()); if (allArgs.find(param) == allArgs.end()) { std::cerr << "Warning: parameter " << *fi->params()[i]->id() << " not documented for function " << fi->id() << " at location " << fi->loc() << "\n"; } } std::string sig; { GCLock lock; auto* fi_c = new FunctionI(Location(), fi->id(), fi->ti(), fi->params()); std::ostringstream oss_sig; oss_sig << *fi_c; sig = oss_sig.str(); sig.resize(sig.size() - 2); } std::ostringstream os; os << "
\n"; os << "
"; os << ""; std::ostringstream fs; if (fi->ti()->type() == Type::ann()) { fs << "annotation "; os << "annotation "; } else if (fi->ti()->type() == Type::parbool()) { fs << "test "; os << "test "; } else if (fi->ti()->type() == Type::varbool()) { fs << "predicate "; os << "predicate "; } else { fs << "function " << *fi->ti() << ": "; os << "function " << *fi->ti() << ": "; } fs << fi->id() << "("; os << "" << fi->id() << "("; size_t align = fs.str().size(); for (unsigned int i = 0; i < fi->params().size(); i++) { fs << *fi->params()[i]->ti() << ": " << *fi->params()[i]->id(); if (i < fi->params().size() - 1) { fs << ", "; } } bool splitArgs = (fs.str().size() > 70); for (unsigned int i = 0; i < fi->params().size(); i++) { os << "" << *fi->params()[i]->ti() << ": " << "" << *fi->params()[i]->id() << ""; if (i < fi->params().size() - 1) { os << ","; if (splitArgs) { os << "\n"; for (auto j = static_cast(align); (j--) != 0U;) { os << " "; } } else { os << " "; } } } os << ")"; if (fi->e() != nullptr) { FunctionI* f_body = fi; bool alias; do { alias = false; Call* c = Expression::dynamicCast(f_body->e()); if ((c != nullptr) && c->argCount() == f_body->params().size()) { bool sameParams = true; for (unsigned int i = 0; i < f_body->params().size(); i++) { Id* ident = c->arg(i)->dynamicCast(); if (ident == nullptr || ident->decl() != f_body->params()[i] || ident->str() != c->decl()->params()[i]->id()->str()) { sameParams = false; break; } } if (sameParams) { alias = true; f_body = c->decl(); } } } while (alias); if (f_body->e() != nullptr) { std::ostringstream body_os; Printer p(body_os, 70); p.print(f_body->e()); std::string filename = std::string(f_body->loc().filename().c_str(), f_body->loc().filename().size()); size_t lastSlash = filename.find_last_of('/'); if (lastSlash != std::string::npos) { filename = filename.substr(lastSlash + 1, std::string::npos); } os << " ="; os << "\n
"; os << "
"; os << body_os.str(); os << "
\n"; os << "(standard decomposition from " << filename << ":" << f_body->loc().firstLine() << ")"; os << "
"; } } os << "
\n
\n"; if (fi->id().c_str()[0] == '\'') { std::string op = fi->id().substr(1, fi->id().size() - 2); ; const char* space = (op[0] >= 'a' ? " " : ""); if (fi->params().size() == 2) { os << "

Usage: " << *fi->params()[0]->id() << space << op << space << *fi->params()[1]->id() << "

"; } else if (fi->params().size() == 1) { os << "

Usage: " << op << space << *fi->params()[0]->id() << "

"; } } std::string dshtml = addHTML(ds); os << dshtml; if (!params.empty()) { os << "
Parameters
\n"; os << "
    \n"; for (auto& param : params) { os << "
  • " << param.first << ": " << param.second << "
  • \n"; } os << "
\n"; } os << "
"; os << "
"; HtmlDocOutput::DocItem di(HtmlDocOutput::DocItem::T_FUN, std::string(fi->id().c_str(), fi->id().size()), sig, os.str()); HtmlDocOutput::add_to_group(_maingroup, group, di); } } }; std::vector HtmlPrinter::printHtml(EnvI& env, MiniZinc::Model* m, const std::string& basename, int splitLevel, bool includeStdLib, bool generateIndex) { using namespace HtmlDocOutput; Group g(basename, basename); FunMap funMap; CollectFunctionsVisitor fv(env, funMap, includeStdLib); iter_items(fv, m); PrintHtmlVisitor phv(env, g, funMap, includeStdLib); iter_items(phv, m); std::vector ret; struct SI { Group* g; Group* p; int level; int idx; SI(Group* g0, Group* p0, int level0, int idx0) : g(g0), p(p0), level(level0), idx(idx0) {} }; struct IndexEntry { std::string id; std::string sig; std::string link; std::string groupName; IndexEntry(std::string id0, std::string sig0, std::string link0, std::string groupName0) : id(std::move(id0)), sig(std::move(sig0)), link(std::move(link0)), groupName(std::move(groupName0)) { size_t spacepos = id.find_last_of(' '); if (spacepos != std::string::npos) { id = id.substr(spacepos + 1); } } bool operator<(const IndexEntry& e) const { if ((isalpha(id[0]) == 0) && (isalpha(e.id[0]) != 0)) { return true; } return id == e.id ? groupName < e.groupName : id < e.id; } }; std::vector index; std::vector stack; stack.emplace_back(&g, nullptr, 0, 0); while (!stack.empty()) { Group& g = *stack.back().g; int curLevel = stack.back().level; int curIdx = stack.back().idx; Group* p = stack.back().p; stack.pop_back(); for (const auto& it : g.items) { index.emplace_back(it.id, it.sig, g.fullPath, g.htmlName); } ret.emplace_back(g.fullPath, g.htmlName, g.toHTML(curLevel, splitLevel, p, curIdx, basename, generateIndex)); if (curLevel < splitLevel) { for (unsigned int i = 0; i < g.subgroups.m.size(); i++) { stack.emplace_back(g.subgroups.m[i], &g, curLevel + 1, i); } } } if (generateIndex) { std::sort(index.begin(), index.end()); std::ostringstream oss; index.emplace_back("", "", "", ""); std::vector idxSections; if (!index.empty()) { if (isalpha(index[0].id[0]) != 0) { char idxSec_c = (char)toupper(index[0].id[0]); std::string idxSec(&idxSec_c, 1); oss << "

" << idxSec << "

\n"; idxSections.push_back(idxSec); } else { oss << "

Symbols

\n"; idxSections.emplace_back("Symbols"); } } oss << "
    \n"; std::string prevId = index.empty() ? "" : index[0].id; std::vector curEntries; for (auto ie : index) { if (ie.id != prevId) { oss << "
  • "; assert(!curEntries.empty()); IndexEntry& cur = curEntries[0]; if (curEntries.size() == 1) { oss << cur.id << " " << "(" << cur.groupName << ")"; } else { oss << cur.id << " ("; bool first = true; for (const auto& i_ie : curEntries) { if (first) { first = false; } else { oss << ", "; } oss << ""; oss << i_ie.groupName << ""; } oss << ")"; } oss << "
  • \n"; curEntries.clear(); } if ((isalpha(ie.id[0]) != 0) && ie.id[0] != prevId[0]) { char idxSec_c = (char)toupper(ie.id[0]); std::string idxSec(&idxSec_c, 1); oss << "
\n

" << idxSec << "

    "; idxSections.push_back(idxSec); } prevId = ie.id; if (curEntries.empty() || curEntries.back().groupName != ie.groupName) { curEntries.push_back(ie); } } oss << "
\n"; std::ostringstream oss_header; oss_header << "
\n"; oss_header << "
"; oss_header << " "; bool first = true; for (const auto& is : idxSections) { if (first) { first = false; } else { oss_header << " | "; } oss_header << "" << is << ""; } oss_header << "
"; oss_header << "
Index
\n"; HtmlDocument idx("doc-index", "Index", oss_header.str() + oss.str()); ret.push_back(idx); } return ret; } class PrintRSTVisitor : public ItemVisitor { protected: EnvI& _env; HtmlDocOutput::Group& _maingroup; HtmlDocOutput::FunMap& _funmap; bool _includeStdLib; static std::vector replaceArgsRST(std::string& s) { std::vector replacements; std::ostringstream oss; size_t lastpos = 0; size_t pos = std::min(s.find("\\a"), s.find("\\p")); size_t mathjax_open = s.find("\\("); size_t mathjax_close = s.rfind("\\)"); while (pos != std::string::npos) { oss << s.substr(lastpos, pos - lastpos); size_t start = pos; while (start < s.size() && s[start] != ' ' && s[start] != '\t') { start++; } while (start < s.size() && (s[start] == ' ' || s[start] == '\t')) { start++; } size_t end = start + 1; while (end < s.size() && ((isalnum(s[end]) != 0) || s[end] == '_')) { end++; } bool needSpace = pos != 0 && s[pos - 1] != ' ' && s[pos - 1] != '\n'; if (s[pos + 1] == 'a') { replacements.push_back(s.substr(start, end - start)); if (pos >= mathjax_open && pos <= mathjax_close) { oss << "{\\bf " << replacements.back() << "}"; } else { oss << (needSpace ? " " : "") << "``" << replacements.back() << "`` "; } } else { if (pos >= mathjax_open && pos <= mathjax_close) { oss << "{\\bf " << s.substr(start, end - start) << "}"; } else { oss << (needSpace ? " " : "") << "``" << s.substr(start, end - start) << "`` "; } } lastpos = end; pos = std::min(s.find("\\a", lastpos), s.find("\\p", lastpos)); } oss << s.substr(lastpos, std::string::npos); s = oss.str(); std::ostringstream oss2; pos = std::min(s.find("\\("), s.find("\\)")); lastpos = 0; while (pos != std::string::npos) { if (s[pos + 1] == ')') { // remove trailing whitespace std::string t = s.substr(lastpos, pos - lastpos); size_t t_end = t.find_last_not_of(' '); if (t_end != std::string::npos) { t_end++; } oss2 << t.substr(0, t_end); } else { oss2 << s.substr(lastpos, pos - lastpos); } lastpos = pos + 2; if (s[pos + 1] == '(') { oss2 << ":math:`"; lastpos = s.find_first_not_of(' ', lastpos); } else { oss2 << "`"; } pos = std::min(s.find("\\(", lastpos), s.find("\\)", lastpos)); } oss2 << s.substr(lastpos, std::string::npos); s = oss2.str(); std::ostringstream oss3; pos = std::min(s.find("\\["), s.find("\\]")); lastpos = 0; while (pos != std::string::npos) { if (s[pos + 1] == ']') { // remove trailing whitespace std::string t = s.substr(lastpos, pos - lastpos); size_t t_end = t.find_last_not_of(' '); if (t_end != std::string::npos) { t_end++; } oss3 << t.substr(0, t_end); } else { oss3 << s.substr(lastpos, pos - lastpos); } lastpos = pos + 2; if (s[pos + 1] == '[') { oss3 << "``"; lastpos = s.find_first_not_of(' ', lastpos); } else { oss3 << "``"; } pos = std::min(s.find("\\[", lastpos), s.find("\\]", lastpos)); } oss3 << s.substr(lastpos, std::string::npos); s = oss3.str(); return replacements; } static std::pair extractArgLine(std::string& s, size_t n) { size_t start = n; while (start < s.size() && s[start] != ' ' && s[start] != '\t') { start++; } while (start < s.size() && (s[start] == ' ' || s[start] == '\t')) { start++; } size_t end = start + 1; while (end < s.size() && s[end] != ':') { end++; } std::string arg = s.substr(start, end - start); size_t doc_start = end + 1; while (end < s.size() && s[end] != '\n') { end++; } std::string ret = s.substr(doc_start, end - doc_start); replaceArgsRST(ret); s = s.substr(0, n) + s.substr(end, std::string::npos); return make_pair(arg, ret); } public: PrintRSTVisitor(EnvI& env, HtmlDocOutput::Group& mg, HtmlDocOutput::FunMap& fm, bool includeStdLib) : _env(env), _maingroup(mg), _funmap(fm), _includeStdLib(includeStdLib) {} bool enterModel(Model* m) { if (!_includeStdLib && m->filename() == "stdlib.mzn") { return false; } const std::string& dc = m->docComment(); if (!dc.empty()) { size_t gpos = dc.find("@groupdef"); while (gpos != std::string::npos) { size_t start = gpos; while (start < dc.size() && dc[start] != ' ' && dc[start] != '\t') { start++; } while (start < dc.size() && (dc[start] == ' ' || dc[start] == '\t')) { start++; } size_t end = start + 1; while (end < dc.size() && ((isalnum(dc[end]) != 0) || dc[end] == '_' || dc[end] == '.')) { end++; } std::string groupName = dc.substr(start, end - start); size_t doc_start = end + 1; while (end < dc.size() && dc[end] != '\n') { end++; } std::string groupHTMLName = dc.substr(doc_start, end - doc_start); size_t next = dc.find("@groupdef", gpos + 1); std::string groupDesc = dc.substr(end, next == std::string::npos ? next : next - end); replaceArgsRST(groupDesc); HtmlDocOutput::set_group_desc(_maingroup, groupName, groupHTMLName, groupDesc); gpos = next; } } return true; } /// Visit variable declaration void vVarDeclI(VarDeclI* vdi) { if (Call* docstring = Expression::dynamicCast( get_annotation(vdi->e()->ann(), constants().ann.doc_comment))) { std::string ds = eval_string(_env, docstring->arg(0)); std::string group("main"); size_t group_idx = ds.find("@group"); if (group_idx != std::string::npos) { group = HtmlDocOutput::extract_arg_word(ds, group_idx); } std::ostringstream os; std::string sig = vdi->e()->type().toString(_env) + " " + std::string(vdi->e()->id()->str().c_str(), vdi->e()->id()->str().size()); std::string myMainGroup = group.substr(0, group.find_first_of('.')); auto it = _maingroup.subgroups.find(myMainGroup); os << ".. index::\n"; if (it != _maingroup.subgroups.m.end()) { os << " pair: " << (*it)->htmlName << "; " << *vdi->e()->id() << "\n\n"; } else { std::cerr << "did not find " << myMainGroup << "\n"; os << " single: " << *vdi->e()->id() << "\n\n"; } os << ".. code-block:: minizinc\n\n"; if (vdi->e()->ti()->type() == Type::ann()) { os << " annotation " << *vdi->e()->id(); } else { os << " " << *vdi->e()->ti() << ": " << *vdi->e()->id(); } os << "\n\n"; os << HtmlDocOutput::trim(ds) << "\n\n"; GCLock lock; HtmlDocOutput::DocItem di( vdi->e()->type().isPar() ? HtmlDocOutput::DocItem::T_PAR : HtmlDocOutput::DocItem::T_VAR, sig, sig, os.str()); HtmlDocOutput::add_to_group(_maingroup, group, di); } } /// Visit function item void vFunctionI(FunctionI* fi) { if (Call* docstring = Expression::dynamicCast(get_annotation(fi->ann(), constants().ann.doc_comment))) { std::string ds = eval_string(_env, docstring->arg(0)); std::string group("main"); size_t group_idx = ds.find("@group"); if (group_idx != std::string::npos) { group = HtmlDocOutput::extract_arg_word(ds, group_idx); } size_t param_idx = ds.find("@param"); std::vector > params; while (param_idx != std::string::npos) { params.push_back(extractArgLine(ds, param_idx)); param_idx = ds.find("@param"); } std::vector args = replaceArgsRST(ds); std::unordered_set allArgs; for (auto& arg : args) { allArgs.insert(arg); } for (auto& param : params) { allArgs.insert(param.first); } GCLock lock; for (unsigned int i = 0; i < fi->params().size(); i++) { if (allArgs.find(std::string(fi->params()[i]->id()->str().c_str(), fi->params()[i]->id()->str().size())) == allArgs.end()) { std::cerr << "Warning: parameter " << *fi->params()[i]->id() << " not documented for function " << fi->id() << " at location " << fi->loc() << "\n"; } } std::string sig; { GCLock lock; auto* fi_c = new FunctionI(Location(), fi->id(), fi->ti(), fi->params()); std::ostringstream oss_sig; oss_sig << *fi_c; sig = oss_sig.str(); sig.resize(sig.size() - 2); } std::ostringstream os; std::ostringstream fs; std::string myMainGroup = group.substr(0, group.find_first_of('.')); auto it = _maingroup.subgroups.find(myMainGroup); os << ".. index::\n"; if (it != _maingroup.subgroups.m.end()) { os << " pair: " << (*it)->htmlName << "; " << fi->id() << "\n\n"; } else { std::cerr << "did not find " << myMainGroup << "\n"; os << " single: " << fi->id() << "\n\n"; } os << ".. code-block:: minizinc\n\n"; if (fi->ti()->type() == Type::ann()) { fs << "annotation "; } else if (fi->ti()->type() == Type::parbool()) { fs << "test "; } else if (fi->ti()->type() == Type::varbool()) { fs << "predicate "; } else { fs << "function " << *fi->ti() << ": "; } fs << fi->id() << "("; os << " " << fs.str(); size_t align = fs.str().size(); for (unsigned int i = 0; i < fi->params().size(); i++) { fs << *fi->params()[i]->ti(); std::ostringstream fid; fid << *fi->params()[i]->id(); if (!fid.str().empty()) { fs << ": " << *fi->params()[i]->id(); } if (i < fi->params().size() - 1) { fs << ", "; } } bool splitArgs = (fs.str().size() > 70); for (unsigned int i = 0; i < fi->params().size(); i++) { os << *fi->params()[i]->ti(); std::ostringstream fid; fid << *fi->params()[i]->id(); if (!fid.str().empty()) { os << ": " << *fi->params()[i]->id(); } if (i < fi->params().size() - 1) { os << ","; if (splitArgs) { os << "\n "; for (auto j = static_cast(align); (j--) != 0U;) { os << " "; } } else { os << " "; } } } os << ")"; os << "\n\n"; if (fi->id().c_str()[0] == '\'') { std::string op = fi->id().substr(1, fi->id().size() - 2); if (fi->params().size() == 2) { os << "Usage: ``" << *fi->params()[0]->id() << " " << op << " " << *fi->params()[1]->id() << "``\n\n"; } else if (fi->params().size() == 1) { os << "Usage: ``" << op << " " << *fi->params()[0]->id() << "``\n\n"; } } os << HtmlDocOutput::trim(ds) << "\n\n"; if (fi->e() != nullptr) { FunctionI* f_body = fi; bool alias; do { alias = false; Call* c = Expression::dynamicCast(f_body->e()); if ((c != nullptr) && c->argCount() == f_body->params().size()) { bool sameParams = true; for (unsigned int i = 0; i < f_body->params().size(); i++) { Id* ident = c->arg(i)->dynamicCast(); if (ident == nullptr || ident->decl() != f_body->params()[i] || ident->str() != c->decl()->params()[i]->id()->str()) { sameParams = false; break; } } if (sameParams) { alias = true; f_body = c->decl(); } } } while (alias); if (f_body->e() != nullptr) { std::string filename = std::string(f_body->loc().filename().c_str(), f_body->loc().filename().size()); size_t filePos = filename.find("std/"); if (filePos != std::string::npos) { filePos += 4; os << ".. only:: builder_html\n\n"; os << " `More... loc().firstLine() << "-L" << f_body->loc().lastLine() << ">`__\n\n"; } } } if (!params.empty()) { os << "Parameters:\n\n"; for (auto& param : params) { os << "- ``" << param.first << "``: " << param.second << "\n"; } os << "\n"; } os << "\n"; HtmlDocOutput::DocItem di(HtmlDocOutput::DocItem::T_FUN, std::string(fi->id().c_str(), fi->id().size()), sig, os.str()); HtmlDocOutput::add_to_group(_maingroup, group, di); } } }; std::vector RSTPrinter::printRST(EnvI& env, MiniZinc::Model* m, const std::string& basename, int splitLevel, bool includeStdLib, bool generateIndex) { using namespace HtmlDocOutput; Group g(basename, basename); FunMap funMap; CollectFunctionsVisitor fv(env, funMap, includeStdLib); iter_items(fv, m); PrintRSTVisitor prv(env, g, funMap, includeStdLib); iter_items(prv, m); std::vector ret; std::ostringstream oss; oss << Group::rstHeading(g.htmlName, 0); oss << trim(g.desc) << "\n"; oss << ".. toctree::\n\n"; for (auto* sg : g.subgroups.m) { oss << " " << sg->fullPath << "\n"; } ret.emplace_back(g.fullPath, g.htmlName, oss.str()); for (auto& sg : g.subgroups.m) { ret.emplace_back(sg->fullPath, sg->htmlName, sg->toRST(0)); } return ret; } } // namespace MiniZinc