this repo has no description
1/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */
2
3/*
4 * Main authors:
5 * Guido Tack <guido.tack@monash.edu>
6 */
7
8/* This Source Code Form is subject to the terms of the Mozilla Public
9 * License, v. 2.0. If a copy of the MPL was not distributed with this
10 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
11
12#include <minizinc/config.hh>
13#include <minizinc/copy.hh>
14#include <minizinc/eval_par.hh>
15#include <minizinc/htmlprinter.hh>
16#include <minizinc/model.hh>
17#include <minizinc/prettyprinter.hh>
18
19#include <cctype>
20#include <sstream>
21#include <utility>
22
23namespace MiniZinc {
24
25namespace HtmlDocOutput {
26
27// Trim leading space:
28// - always trim first line completely
29// - second line defines the base indentation
30std::string trim(const std::string& s0) {
31 std::string s = s0;
32 // remove carriage returns
33 size_t j = 0;
34 for (size_t i = 0; i < s.size(); i++) {
35 if (s[i] != '\r') {
36 s[j++] = s[i];
37 }
38 }
39 s.resize(j);
40 size_t first_line_indent = s.find_first_not_of(" \t");
41 if (first_line_indent == std::string::npos) {
42 return "";
43 }
44 size_t first_nl = s.find('\n');
45 std::ostringstream oss;
46 if (first_line_indent == first_nl) {
47 // first line is empty
48 oss << "\n";
49 } else {
50 // strip first line
51 size_t end_of_first_line =
52 first_nl == std::string::npos ? std::string::npos : first_nl - first_line_indent + 1;
53 oss << s.substr(first_line_indent, end_of_first_line);
54 }
55 if (first_nl == std::string::npos) {
56 return oss.str();
57 }
58 size_t unindent = s.find_first_not_of(" \t", first_nl + 1);
59 if (unindent == std::string::npos) {
60 return oss.str();
61 }
62 size_t pos = s.find('\n', first_nl + 1);
63 if (unindent == 0 || unindent > pos) {
64 oss << s.substr(first_nl + 1, std::string::npos);
65 return oss.str();
66 }
67 size_t lastpos = unindent;
68 while (pos != std::string::npos) {
69 oss << s.substr(lastpos, pos - lastpos) << "\n";
70 size_t next_indent = s.find_first_not_of(" \t", pos + 1);
71 if (next_indent == std::string::npos || next_indent - (pos + 1) < unindent) {
72 lastpos = next_indent;
73 } else {
74 lastpos = pos + 1 + unindent;
75 }
76 pos = (lastpos == std::string::npos ? lastpos : s.find('\n', lastpos));
77 }
78 if (lastpos != std::string::npos) {
79 oss << s.substr(lastpos, std::string::npos);
80 }
81 return oss.str();
82}
83
84class DocItem {
85public:
86 enum DocType { T_PAR = 0, T_VAR = 1, T_FUN = 2 };
87 DocItem(const DocType& t0, std::string id0, std::string sig0, std::string doc0)
88 : t(t0), id(std::move(id0)), sig(std::move(sig0)), doc(std::move(doc0)) {}
89 DocType t;
90 std::string id;
91 std::string sig;
92 std::string doc;
93};
94
95typedef std::unordered_map<FunctionI*, std::string> FunMap;
96
97class Group;
98
99class GroupMap {
100public:
101 typedef std::vector<Group*> Map;
102 Map m;
103 ~GroupMap();
104 Map::iterator find(const std::string& n);
105};
106
107class Group {
108public:
109 Group(std::string name0, std::string fullPath0)
110 : name(std::move(name0)), fullPath(std::move(fullPath0)) {}
111 std::string name;
112 std::string fullPath;
113 std::string desc;
114 std::string htmlName;
115 GroupMap subgroups;
116 std::vector<DocItem> items;
117
118 std::string getAnchor(int level, int indivFileLevel) const {
119 if (level < indivFileLevel) {
120 return fullPath + ".html";
121 }
122 return "#" + fullPath;
123 }
124
125 std::string toHTML(int level, int indivFileLevel, Group* parent, unsigned int idx,
126 const std::string& basename, bool generateIndex) {
127 std::ostringstream oss;
128
129 int realLevel = (level < indivFileLevel) ? 0 : level - indivFileLevel;
130 oss << "<div class='mzn-group-level-" << realLevel << "'>\n";
131 if (parent != nullptr) {
132 oss << "<div class='mzn-group-nav'>";
133 if (idx > 0) {
134 oss << "<a class='mzn-nav-prev' href='"
135 << parent->subgroups.m[idx - 1]->getAnchor(level - 1, indivFileLevel) << "' title='"
136 << parent->subgroups.m[idx - 1]->htmlName << "'>⇐</a> ";
137 }
138 oss << "<a class='mzn-nav-up' href='" << parent->getAnchor(level - 1, indivFileLevel)
139 << "' title='" << parent->htmlName << "'>⇧</a> ";
140 if (idx < parent->subgroups.m.size() - 1) {
141 oss << "<a class='mzn-nav-next' href='"
142 << parent->subgroups.m[idx + 1]->getAnchor(level - 1, indivFileLevel) << "' title='"
143 << parent->subgroups.m[idx + 1]->htmlName << "'>⇒</a> ";
144 }
145 if (generateIndex) {
146 oss << "<a href='doc-index.html'>Index</a>\n";
147 }
148 if (!items.empty()) {
149 oss << "<a href='javascript:void(0)' onclick='revealAll()' class='mzn-nav-text'>reveal "
150 "all</a>\n";
151 oss << "<a href='javascript:void(0)' onclick='hideAll()' class='mzn-nav-text'>hide "
152 "all</a>\n";
153 }
154 oss << "</div>";
155 }
156 if (!htmlName.empty()) {
157 oss << "<div class='mzn-group-name'><a name='" << fullPath << "'>" << htmlName
158 << "</a></div>\n";
159 oss << "<div class='mzn-group-desc'>\n" << desc << "</div>\n";
160 }
161
162 if (!subgroups.m.empty()) {
163 oss << "<p>Sections:</p>\n";
164 oss << "<ul>\n";
165 for (auto& it : subgroups.m) {
166 oss << "<li><a href='" << it->getAnchor(level, indivFileLevel) << "'>" << it->htmlName
167 << "</a>\n";
168
169 if (it->htmlName.empty()) {
170 std::cerr << "Warning: undocumented group " << it->fullPath << "\n";
171 }
172 }
173 oss << "</ul>\n";
174 if (parent == nullptr && generateIndex) {
175 oss << "<p><a href='doc-index.html'>Index</a></p>\n";
176 }
177 if (!items.empty()) {
178 oss << "<p>Declarations in this section:</p>\n";
179 }
180 }
181
182 struct SortById {
183 bool operator()(const DocItem& i0, const DocItem& i1) {
184 return i0.t < i1.t || (i0.t == i1.t && i0.id < i1.id);
185 }
186 } _cmp;
187 std::stable_sort(items.begin(), items.end(), _cmp);
188
189 int cur_t = -1;
190 const char* dt[] = {"par", "var", "fun"};
191 const char* dt_desc[] = {"Parameters", "Variables", "Functions and Predicates"};
192 for (const auto& item : items) {
193 if (item.t != cur_t) {
194 if (cur_t != -1) {
195 oss << "</div>\n";
196 }
197 cur_t = item.t;
198 oss << "<div class='mzn-decl-type-" << dt[cur_t] << "'>\n";
199 oss << "<div class='mzn-decl-type-heading'>" << dt_desc[cur_t] << "</div>\n";
200 }
201 oss << item.doc;
202 }
203 if (cur_t != -1) {
204 oss << "</div>\n";
205 }
206
207 if (level >= indivFileLevel) {
208 for (unsigned int i = 0; i < subgroups.m.size(); i++) {
209 oss << subgroups.m[i]->toHTML(level + 1, indivFileLevel, this, i, basename, generateIndex);
210 }
211 }
212
213 oss << "</div>";
214 return oss.str();
215 }
216
217 static std::string rstHeading(const std::string& s, int level) {
218 std::vector<char> levelChar({'#', '=', '-', '^', '+', '"'});
219 std::ostringstream oss;
220 oss << s << "\n";
221 for (int i = 0; i < s.size(); i++) {
222 oss << levelChar[level];
223 }
224 oss << "\n\n";
225 return oss.str();
226 }
227
228 std::string toRST(int level) {
229 std::ostringstream oss;
230 if (!htmlName.empty()) {
231 if (level == 0) {
232 oss << ".. _ch-lib-" << name << ":\n\n";
233 }
234 oss << rstHeading(htmlName, level);
235 oss << HtmlDocOutput::trim(desc) << "\n\n";
236 }
237 for (auto& i : subgroups.m) {
238 oss << i->toRST(level + 1);
239 }
240 if (!items.empty()) {
241 if (!subgroups.m.empty()) {
242 oss << rstHeading("Other declarations", level + 1);
243 }
244 struct SortById {
245 bool operator()(const DocItem& i0, const DocItem& i1) {
246 return i0.t < i1.t || (i0.t == i1.t && i0.id < i1.id);
247 }
248 } _cmp;
249 std::stable_sort(items.begin(), items.end(), _cmp);
250
251 int cur_t = -1;
252 int nHeadings = 0;
253 for (const auto& item : items) {
254 if (item.t != cur_t) {
255 cur_t = item.t;
256 nHeadings++;
257 }
258 }
259 cur_t = -1;
260 const char* dt_desc[] = {"Parameters", "Variables", "Functions and Predicates"};
261 for (const auto& item : items) {
262 if (item.t != cur_t) {
263 cur_t = item.t;
264 if (nHeadings > 1) {
265 oss << rstHeading(dt_desc[cur_t], subgroups.m.empty() ? level + 1 : level + 2);
266 }
267 }
268 oss << item.doc;
269 }
270 }
271 return oss.str();
272 }
273};
274
275GroupMap::~GroupMap() {
276 for (auto& it : m) {
277 delete it;
278 }
279}
280GroupMap::Map::iterator GroupMap::find(const std::string& n) {
281 for (auto it = m.begin(); it != m.end(); ++it) {
282 if ((*it)->name == n) {
283 return it;
284 }
285 }
286 return m.end();
287}
288
289void add_to_group(Group& gm, const std::string& group, DocItem& di) {
290 std::vector<std::string> subgroups;
291 size_t lastpos = 0;
292 size_t pos = group.find('.');
293 while (pos != std::string::npos) {
294 subgroups.push_back(group.substr(lastpos, pos - lastpos));
295 lastpos = pos + 1;
296 pos = group.find('.', lastpos);
297 }
298 subgroups.push_back(group.substr(lastpos, std::string::npos));
299
300 GroupMap* cgm = &gm.subgroups;
301 std::string gpath(gm.fullPath);
302 for (unsigned int i = 0; i < subgroups.size(); i++) {
303 gpath += "-";
304 gpath += subgroups[i];
305 if (cgm->find(subgroups[i]) == cgm->m.end()) {
306 cgm->m.push_back(new Group(subgroups[i], gpath));
307 }
308 Group& g = **cgm->find(subgroups[i]);
309 if (i == subgroups.size() - 1) {
310 g.items.push_back(di);
311 } else {
312 cgm = &g.subgroups;
313 }
314 }
315}
316
317void set_group_desc(Group& maingroup, const std::string& group, const std::string& htmlName,
318 const std::string& s) {
319 if (group == "MAIN") {
320 if (!maingroup.htmlName.empty()) {
321 std::cerr << "Warning: two descriptions for group `" << group << "'\n";
322 }
323 maingroup.htmlName = htmlName;
324 maingroup.desc = s;
325 return;
326 }
327
328 std::vector<std::string> subgroups;
329 size_t lastpos = 0;
330 size_t pos = group.find('.');
331 while (pos != std::string::npos) {
332 subgroups.push_back(group.substr(lastpos, pos - lastpos));
333 lastpos = pos + 1;
334 pos = group.find('.', lastpos);
335 }
336 subgroups.push_back(group.substr(lastpos, std::string::npos));
337
338 GroupMap* cgm = &maingroup.subgroups;
339 std::string gpath(maingroup.fullPath);
340 for (unsigned int i = 0; i < subgroups.size(); i++) {
341 gpath += "-";
342 gpath += subgroups[i];
343 if (cgm->find(subgroups[i]) == cgm->m.end()) {
344 cgm->m.push_back(new Group(subgroups[i], gpath));
345 }
346 Group& g = **cgm->find(subgroups[i]);
347 if (i == subgroups.size() - 1) {
348 if (!g.htmlName.empty()) {
349 std::cerr << "Warning: two descriptions for group `" << group << "'\n";
350 }
351 g.htmlName = htmlName;
352 g.desc = s;
353 } else {
354 cgm = &g.subgroups;
355 }
356 }
357}
358
359std::string extract_arg_word(std::string& s, size_t n) {
360 size_t start = n;
361 while (start < s.size() && s[start] != ' ' && s[start] != '\t') {
362 start++;
363 }
364 while (start < s.size() && (s[start] == ' ' || s[start] == '\t')) {
365 start++;
366 }
367 size_t end = start + 1;
368 while (end < s.size() && ((isalnum(s[end]) != 0) || s[end] == '_' || s[end] == '.')) {
369 end++;
370 }
371 std::string ret = s.substr(start, end - start);
372 s = s.substr(end, std::string::npos);
373 return ret;
374}
375
376std::string make_html_id(const std::string& ident) {
377 std::ostringstream oss;
378 oss << "I";
379 bool prevWasSym = false;
380 for (char i : ident) {
381 bool isSym = true;
382 switch (i) {
383 case '!':
384 oss << "-ex";
385 break;
386 case '=':
387 oss << "-eq";
388 break;
389 case '*':
390 oss << "-as";
391 break;
392 case '+':
393 oss << "-pl";
394 break;
395 case '-':
396 oss << "-mi";
397 break;
398 case '>':
399 oss << "-gr";
400 break;
401 case '<':
402 oss << "-lt";
403 break;
404 case '/':
405 oss << "-dv";
406 break;
407 case '\\':
408 oss << "-bs";
409 break;
410 case '~':
411 oss << "-tl";
412 break;
413 case '\'':
414 oss << "-tk";
415 break;
416 case ' ':
417 case '\t':
418 case '\n':
419 break;
420 case ':':
421 oss << "-cl";
422 break;
423 case '[':
424 oss << "-bo";
425 break;
426 case ']':
427 oss << "-bc";
428 break;
429 case '$':
430 oss << "-dd";
431 break;
432 case '(':
433 oss << "-po";
434 break;
435 case ')':
436 oss << "-pc";
437 break;
438 case ',':
439 oss << "-cm";
440 break;
441 default:
442 oss << (prevWasSym ? "-" : "") << i;
443 isSym = false;
444 break;
445 }
446 prevWasSym = isSym;
447 }
448 return oss.str();
449}
450
451} // namespace HtmlDocOutput
452
453class CollectFunctionsVisitor : public ItemVisitor {
454protected:
455 EnvI& _env;
456 HtmlDocOutput::FunMap& _funmap;
457 bool _includeStdLib;
458
459public:
460 CollectFunctionsVisitor(EnvI& env, HtmlDocOutput::FunMap& funmap, bool includeStdLib)
461 : _env(env), _funmap(funmap), _includeStdLib(includeStdLib) {}
462 bool enterModel(Model* m) const { return _includeStdLib || m->filename() != "stdlib.mzn"; }
463 void vFunctionI(FunctionI* fi) {
464 if (Call* docstring =
465 Expression::dynamicCast<Call>(get_annotation(fi->ann(), constants().ann.doc_comment))) {
466 std::string ds = eval_string(_env, docstring->arg(0));
467 std::string group("main");
468 size_t group_idx = ds.find("@group");
469 if (group_idx != std::string::npos) {
470 group = HtmlDocOutput::extract_arg_word(ds, group_idx);
471 }
472 _funmap.insert(std::make_pair(fi, group));
473 }
474 }
475};
476
477class PrintHtmlVisitor : public ItemVisitor {
478protected:
479 EnvI& _env;
480 HtmlDocOutput::Group& _maingroup;
481 HtmlDocOutput::FunMap& _funmap;
482 bool _includeStdLib;
483
484 static std::vector<std::string> replaceArgs(std::string& s) {
485 std::vector<std::string> replacements;
486 std::ostringstream oss;
487 size_t lastpos = 0;
488 size_t pos = std::min(s.find("\\a"), s.find("\\p"));
489 size_t mathjax_open = s.find("\\(");
490 size_t mathjax_close = s.rfind("\\)");
491 if (pos == std::string::npos) {
492 return replacements;
493 }
494 while (pos != std::string::npos) {
495 oss << s.substr(lastpos, pos - lastpos);
496 size_t start = pos;
497 while (start < s.size() && s[start] != ' ' && s[start] != '\t') {
498 start++;
499 }
500 while (start < s.size() && (s[start] == ' ' || s[start] == '\t')) {
501 start++;
502 }
503 size_t end = start + 1;
504 while (end < s.size() && ((isalnum(s[end]) != 0) || s[end] == '_')) {
505 end++;
506 }
507 if (s[pos + 1] == 'a') {
508 replacements.push_back(s.substr(start, end - start));
509 if (pos >= mathjax_open && pos <= mathjax_close) {
510 oss << "{\\bf " << replacements.back() << "}";
511 } else {
512 oss << "<span class='mzn-arg'>" << replacements.back() << "</span>";
513 }
514 } else {
515 if (pos >= mathjax_open && pos <= mathjax_close) {
516 oss << "{\\bf " << s.substr(start, end - start) << "}";
517 } else {
518 oss << "<span class='mzn-parm'>" << s.substr(start, end - start) << "</span>";
519 }
520 }
521 lastpos = end;
522 pos = std::min(s.find("\\a", lastpos), s.find("\\p", lastpos));
523 }
524 oss << s.substr(lastpos, std::string::npos);
525 s = oss.str();
526 return replacements;
527 }
528
529 static std::pair<std::string, std::string> extractArgLine(std::string& s, size_t n) {
530 size_t start = n;
531 while (start < s.size() && s[start] != ' ' && s[start] != '\t') {
532 start++;
533 }
534 while (start < s.size() && (s[start] == ' ' || s[start] == '\t')) {
535 start++;
536 }
537 size_t end = start + 1;
538 while (end < s.size() && s[end] != ':') {
539 end++;
540 }
541 std::string arg = s.substr(start, end - start);
542 size_t doc_start = end + 1;
543 while (end < s.size() && s[end] != '\n') {
544 end++;
545 }
546 std::string ret = s.substr(doc_start, end - doc_start);
547 replaceArgs(ret);
548 s = s.substr(0, n) + s.substr(end, std::string::npos);
549 return make_pair(arg, ret);
550 }
551
552 static std::string addHTML(const std::string& s) {
553 std::ostringstream oss;
554 size_t lastpos = 0;
555 size_t pos = s.find('\n');
556 bool inUl = false;
557 oss << "<p>\n";
558 while (pos != std::string::npos) {
559 oss << s.substr(lastpos, pos - lastpos);
560 size_t next = std::min(s.find('\n', pos + 1), s.find('-', pos + 1));
561 if (next == std::string::npos) {
562 lastpos = pos + 1;
563 break;
564 }
565 bool allwhite = true;
566 for (size_t cur = pos + 1; cur < next; cur++) {
567 if (s[cur] != ' ' && s[cur] != '\t') {
568 allwhite = false;
569 break;
570 }
571 }
572 if (allwhite) {
573 if (s[next] == '-') {
574 if (!inUl) {
575 oss << "<ul>\n";
576 inUl = true;
577 }
578 oss << "<li>";
579 } else {
580 if (inUl) {
581 oss << "</ul>\n";
582 inUl = false;
583 } else {
584 oss << "</p><p>\n";
585 }
586 }
587 lastpos = next + 1;
588 pos = s.find('\n', lastpos);
589 } else {
590 lastpos = pos + 1;
591 if (s[pos] == '\n') {
592 oss << " ";
593 }
594 if (s[next] == '-') {
595 pos = s.find('\n', next + 1);
596 } else {
597 pos = next;
598 }
599 }
600 }
601 oss << s.substr(lastpos, std::string::npos);
602 if (inUl) {
603 oss << "</ul>\n";
604 }
605 oss << "</p>\n";
606 return oss.str();
607 }
608
609public:
610 PrintHtmlVisitor(EnvI& env, HtmlDocOutput::Group& mg, HtmlDocOutput::FunMap& fm,
611 bool includeStdLib)
612 : _env(env), _maingroup(mg), _funmap(fm), _includeStdLib(includeStdLib) {}
613 bool enterModel(Model* m) {
614 if (!_includeStdLib && m->filename() == "stdlib.mzn") {
615 return false;
616 }
617 const std::string& dc = m->docComment();
618 if (!dc.empty()) {
619 size_t gpos = dc.find("@groupdef");
620 while (gpos != std::string::npos) {
621 size_t start = gpos;
622 while (start < dc.size() && dc[start] != ' ' && dc[start] != '\t') {
623 start++;
624 }
625 while (start < dc.size() && (dc[start] == ' ' || dc[start] == '\t')) {
626 start++;
627 }
628 size_t end = start + 1;
629 while (end < dc.size() && ((isalnum(dc[end]) != 0) || dc[end] == '_' || dc[end] == '.')) {
630 end++;
631 }
632 std::string groupName = dc.substr(start, end - start);
633 size_t doc_start = end + 1;
634 while (end < dc.size() && dc[end] != '\n') {
635 end++;
636 }
637 std::string groupHTMLName = dc.substr(doc_start, end - doc_start);
638
639 size_t next = dc.find("@groupdef", gpos + 1);
640 HtmlDocOutput::set_group_desc(
641 _maingroup, groupName, groupHTMLName,
642 addHTML(dc.substr(end, next == std::string::npos ? next : next - end)));
643 gpos = next;
644 }
645 }
646 return true;
647 }
648 /// Visit variable declaration
649 void vVarDeclI(VarDeclI* vdi) {
650 if (Call* docstring = Expression::dynamicCast<Call>(
651 get_annotation(vdi->e()->ann(), constants().ann.doc_comment))) {
652 std::string ds = eval_string(_env, docstring->arg(0));
653 std::string group("main");
654 size_t group_idx = ds.find("@group");
655 if (group_idx != std::string::npos) {
656 group = HtmlDocOutput::extract_arg_word(ds, group_idx);
657 }
658
659 std::ostringstream os;
660 std::string sig =
661 vdi->e()->type().toString(_env) + " " + std::string(vdi->e()->id()->str().c_str());
662 os << "<div class='mzn-vardecl' id='" << HtmlDocOutput::make_html_id(sig) << "'>\n";
663 os << "<div class='mzn-vardecl-code'>\n";
664 if (vdi->e()->ti()->type() == Type::ann()) {
665 os << "<span class='mzn-kw'>annotation</span> ";
666 os << "<span class='mzn-fn-id'>" << *vdi->e()->id() << "</span>";
667 } else {
668 os << *vdi->e()->ti() << ": " << *vdi->e()->id();
669 }
670 os << "</div><div class='mzn-vardecl-doc'>\n";
671 os << addHTML(ds);
672 os << "</div></div>";
673 GCLock lock;
674 HtmlDocOutput::DocItem di(
675 vdi->e()->type().isPar() ? HtmlDocOutput::DocItem::T_PAR : HtmlDocOutput::DocItem::T_VAR,
676 sig, sig, os.str());
677 HtmlDocOutput::add_to_group(_maingroup, group, di);
678 }
679 }
680 /// Visit function item
681 void vFunctionI(FunctionI* fi) {
682 if (Call* docstring =
683 Expression::dynamicCast<Call>(get_annotation(fi->ann(), constants().ann.doc_comment))) {
684 std::string ds = eval_string(_env, docstring->arg(0));
685 std::string group("main");
686 size_t group_idx = ds.find("@group");
687 if (group_idx != std::string::npos) {
688 group = HtmlDocOutput::extract_arg_word(ds, group_idx);
689 }
690
691 size_t param_idx = ds.find("@param");
692 std::vector<std::pair<std::string, std::string> > params;
693 while (param_idx != std::string::npos) {
694 params.push_back(extractArgLine(ds, param_idx));
695 param_idx = ds.find("@param");
696 }
697
698 std::vector<std::string> args = replaceArgs(ds);
699
700 std::unordered_set<std::string> allArgs;
701 for (auto& arg : args) {
702 allArgs.insert(arg);
703 }
704 for (auto& param : params) {
705 allArgs.insert(param.first);
706 }
707
708 GCLock lock;
709 for (unsigned int i = 0; i < fi->params().size(); i++) {
710 std::string param(fi->params()[i]->id()->str().c_str(),
711 fi->params()[i]->id()->str().size());
712 if (allArgs.find(param) == allArgs.end()) {
713 std::cerr << "Warning: parameter " << *fi->params()[i]->id()
714 << " not documented for function " << fi->id() << " at location " << fi->loc()
715 << "\n";
716 }
717 }
718
719 std::string sig;
720 {
721 GCLock lock;
722 auto* fi_c = new FunctionI(Location(), fi->id(), fi->ti(), fi->params());
723 std::ostringstream oss_sig;
724 oss_sig << *fi_c;
725 sig = oss_sig.str();
726 sig.resize(sig.size() - 2);
727 }
728
729 std::ostringstream os;
730 os << "<div class='mzn-fundecl' id='" << HtmlDocOutput::make_html_id(sig) << "'>\n";
731 os << "<div class='mzn-fundecl-code'>";
732 os << "<a href='javascript:void(0)' onclick='revealMore(this)' "
733 "class='mzn-fundecl-more'>◀</a>";
734
735 std::ostringstream fs;
736 if (fi->ti()->type() == Type::ann()) {
737 fs << "annotation ";
738 os << "<span class='mzn-kw'>annotation</span> ";
739 } else if (fi->ti()->type() == Type::parbool()) {
740 fs << "test ";
741 os << "<span class='mzn-kw'>test</span> ";
742 } else if (fi->ti()->type() == Type::varbool()) {
743 fs << "predicate ";
744 os << "<span class='mzn-kw'>predicate</span> ";
745 } else {
746 fs << "function " << *fi->ti() << ": ";
747 os << "<span class='mzn-kw'>function</span> <span class='mzn-ti'>" << *fi->ti()
748 << "</span>: ";
749 }
750 fs << fi->id() << "(";
751 os << "<span class='mzn-fn-id'>" << fi->id() << "</span>(";
752 size_t align = fs.str().size();
753 for (unsigned int i = 0; i < fi->params().size(); i++) {
754 fs << *fi->params()[i]->ti() << ": " << *fi->params()[i]->id();
755 if (i < fi->params().size() - 1) {
756 fs << ", ";
757 }
758 }
759 bool splitArgs = (fs.str().size() > 70);
760 for (unsigned int i = 0; i < fi->params().size(); i++) {
761 os << "<span class='mzn-ti'>" << *fi->params()[i]->ti() << "</span>: "
762 << "<span class='mzn-id'>" << *fi->params()[i]->id() << "</span>";
763 if (i < fi->params().size() - 1) {
764 os << ",";
765 if (splitArgs) {
766 os << "\n";
767 for (auto j = static_cast<unsigned int>(align); (j--) != 0U;) {
768 os << " ";
769 }
770 } else {
771 os << " ";
772 }
773 }
774 }
775 os << ")";
776
777 if (fi->e() != nullptr) {
778 FunctionI* f_body = fi;
779 bool alias;
780 do {
781 alias = false;
782 Call* c = Expression::dynamicCast<Call>(f_body->e());
783 if ((c != nullptr) && c->argCount() == f_body->params().size()) {
784 bool sameParams = true;
785 for (unsigned int i = 0; i < f_body->params().size(); i++) {
786 Id* ident = c->arg(i)->dynamicCast<Id>();
787 if (ident == nullptr || ident->decl() != f_body->params()[i] ||
788 ident->str() != c->decl()->params()[i]->id()->str()) {
789 sameParams = false;
790 break;
791 }
792 }
793 if (sameParams) {
794 alias = true;
795 f_body = c->decl();
796 }
797 }
798 } while (alias);
799 if (f_body->e() != nullptr) {
800 std::ostringstream body_os;
801 Printer p(body_os, 70);
802 p.print(f_body->e());
803
804 std::string filename =
805 std::string(f_body->loc().filename().c_str(), f_body->loc().filename().size());
806 size_t lastSlash = filename.find_last_of('/');
807 if (lastSlash != std::string::npos) {
808 filename = filename.substr(lastSlash + 1, std::string::npos);
809 }
810 os << "<span class='mzn-fundecl-equals'> =</span>";
811 os << "\n<div class='mzn-fundecl-more-code'>";
812 os << "<div class='mzn-fundecl-body'>";
813 os << body_os.str();
814 os << "</div>\n";
815 os << "(standard decomposition from " << filename << ":" << f_body->loc().firstLine()
816 << ")";
817 os << "</div>";
818 }
819 }
820
821 os << "</div>\n<div class='mzn-fundecl-doc'>\n";
822
823 if (fi->id().c_str()[0] == '\'') {
824 std::string op = fi->id().substr(1, fi->id().size() - 2);
825 ;
826 const char* space = (op[0] >= 'a' ? " " : "");
827 if (fi->params().size() == 2) {
828 os << "<p>Usage: <span class=\"mzn-arg\">" << *fi->params()[0]->id() << space << op
829 << space << *fi->params()[1]->id() << "</span></p>";
830 } else if (fi->params().size() == 1) {
831 os << "<p>Usage: <span class=\"mzn-arg\">" << op << space << *fi->params()[0]->id()
832 << "</span></p>";
833 }
834 }
835
836 std::string dshtml = addHTML(ds);
837
838 os << dshtml;
839 if (!params.empty()) {
840 os << "<div class='mzn-fundecl-params-heading'>Parameters</div>\n";
841 os << "<ul class='mzn-fundecl-params'>\n";
842 for (auto& param : params) {
843 os << "<li><span class='mzn-arg'>" << param.first << "</span>: " << param.second
844 << "</li>\n";
845 }
846 os << "</ul>\n";
847 }
848 os << "</div>";
849 os << "</div>";
850
851 HtmlDocOutput::DocItem di(HtmlDocOutput::DocItem::T_FUN,
852 std::string(fi->id().c_str(), fi->id().size()), sig, os.str());
853 HtmlDocOutput::add_to_group(_maingroup, group, di);
854 }
855 }
856};
857
858std::vector<HtmlDocument> HtmlPrinter::printHtml(EnvI& env, MiniZinc::Model* m,
859 const std::string& basename, int splitLevel,
860 bool includeStdLib, bool generateIndex) {
861 using namespace HtmlDocOutput;
862 Group g(basename, basename);
863 FunMap funMap;
864 CollectFunctionsVisitor fv(env, funMap, includeStdLib);
865 iter_items(fv, m);
866 PrintHtmlVisitor phv(env, g, funMap, includeStdLib);
867 iter_items(phv, m);
868
869 std::vector<HtmlDocument> ret;
870
871 struct SI {
872 Group* g;
873 Group* p;
874 int level;
875 int idx;
876 SI(Group* g0, Group* p0, int level0, int idx0) : g(g0), p(p0), level(level0), idx(idx0) {}
877 };
878
879 struct IndexEntry {
880 std::string id;
881 std::string sig;
882 std::string link;
883 std::string groupName;
884 IndexEntry(std::string id0, std::string sig0, std::string link0, std::string groupName0)
885 : id(std::move(id0)),
886 sig(std::move(sig0)),
887 link(std::move(link0)),
888 groupName(std::move(groupName0)) {
889 size_t spacepos = id.find_last_of(' ');
890 if (spacepos != std::string::npos) {
891 id = id.substr(spacepos + 1);
892 }
893 }
894 bool operator<(const IndexEntry& e) const {
895 if ((isalpha(id[0]) == 0) && (isalpha(e.id[0]) != 0)) {
896 return true;
897 }
898 return id == e.id ? groupName < e.groupName : id < e.id;
899 }
900 };
901 std::vector<IndexEntry> index;
902
903 std::vector<SI> stack;
904 stack.emplace_back(&g, nullptr, 0, 0);
905 while (!stack.empty()) {
906 Group& g = *stack.back().g;
907 int curLevel = stack.back().level;
908 int curIdx = stack.back().idx;
909 Group* p = stack.back().p;
910 stack.pop_back();
911 for (const auto& it : g.items) {
912 index.emplace_back(it.id, it.sig, g.fullPath, g.htmlName);
913 }
914 ret.emplace_back(g.fullPath, g.htmlName,
915 g.toHTML(curLevel, splitLevel, p, curIdx, basename, generateIndex));
916 if (curLevel < splitLevel) {
917 for (unsigned int i = 0; i < g.subgroups.m.size(); i++) {
918 stack.emplace_back(g.subgroups.m[i], &g, curLevel + 1, i);
919 }
920 }
921 }
922
923 if (generateIndex) {
924 std::sort(index.begin(), index.end());
925 std::ostringstream oss;
926 index.emplace_back("", "", "", "");
927
928 std::vector<std::string> idxSections;
929
930 if (!index.empty()) {
931 if (isalpha(index[0].id[0]) != 0) {
932 char idxSec_c = (char)toupper(index[0].id[0]);
933 std::string idxSec(&idxSec_c, 1);
934 oss << "<h3 id='Idx" << idxSec << "'>" << idxSec << "</h3>\n";
935 idxSections.push_back(idxSec);
936 } else {
937 oss << "<h3 id='IdxSymbols'>Symbols</h3>\n";
938 idxSections.emplace_back("Symbols");
939 }
940 }
941 oss << "<ul>\n";
942 std::string prevId = index.empty() ? "" : index[0].id;
943 std::vector<IndexEntry> curEntries;
944 for (auto ie : index) {
945 if (ie.id != prevId) {
946 oss << "<li>";
947 assert(!curEntries.empty());
948 IndexEntry& cur = curEntries[0];
949 if (curEntries.size() == 1) {
950 oss << cur.id << " <a href='" << cur.link << ".html#"
951 << HtmlDocOutput::make_html_id(cur.sig) << "'>"
952 << "(" << cur.groupName << ")</a>";
953 } else {
954 oss << cur.id << " (";
955 bool first = true;
956 for (const auto& i_ie : curEntries) {
957 if (first) {
958 first = false;
959 } else {
960 oss << ", ";
961 }
962 oss << "<a href='" << i_ie.link << ".html#" << HtmlDocOutput::make_html_id(i_ie.sig)
963 << "'>";
964 oss << i_ie.groupName << "</a>";
965 }
966 oss << ")";
967 }
968 oss << "</li>\n";
969 curEntries.clear();
970 }
971 if ((isalpha(ie.id[0]) != 0) && ie.id[0] != prevId[0]) {
972 char idxSec_c = (char)toupper(ie.id[0]);
973 std::string idxSec(&idxSec_c, 1);
974 oss << "</ul>\n<h3 id='Idx" << idxSec << "'>" << idxSec << "</h3><ul>";
975 idxSections.push_back(idxSec);
976 }
977 prevId = ie.id;
978 if (curEntries.empty() || curEntries.back().groupName != ie.groupName) {
979 curEntries.push_back(ie);
980 }
981 }
982 oss << "</ul>\n";
983
984 std::ostringstream oss_header;
985 oss_header << "<div class='mzn-group-level-0'>\n";
986 oss_header << "<div class='mzn-group-nav'>";
987 oss_header << "<a class='mzn-nav-up' href='" << g.getAnchor(0, 1) << "' title='" << g.htmlName
988 << "'>⇧</a> ";
989 bool first = true;
990 for (const auto& is : idxSections) {
991 if (first) {
992 first = false;
993 } else {
994 oss_header << " | ";
995 }
996 oss_header << "<a href='#Idx" << is << "'>" << is << "</a>";
997 }
998
999 oss_header << "</div>";
1000
1001 oss_header << "<div class='mzn-group-name'>Index</div>\n";
1002
1003 HtmlDocument idx("doc-index", "Index", oss_header.str() + oss.str());
1004 ret.push_back(idx);
1005 }
1006 return ret;
1007}
1008
1009class PrintRSTVisitor : public ItemVisitor {
1010protected:
1011 EnvI& _env;
1012 HtmlDocOutput::Group& _maingroup;
1013 HtmlDocOutput::FunMap& _funmap;
1014 bool _includeStdLib;
1015
1016 static std::vector<std::string> replaceArgsRST(std::string& s) {
1017 std::vector<std::string> replacements;
1018 std::ostringstream oss;
1019 size_t lastpos = 0;
1020 size_t pos = std::min(s.find("\\a"), s.find("\\p"));
1021 size_t mathjax_open = s.find("\\(");
1022 size_t mathjax_close = s.rfind("\\)");
1023 while (pos != std::string::npos) {
1024 oss << s.substr(lastpos, pos - lastpos);
1025 size_t start = pos;
1026 while (start < s.size() && s[start] != ' ' && s[start] != '\t') {
1027 start++;
1028 }
1029 while (start < s.size() && (s[start] == ' ' || s[start] == '\t')) {
1030 start++;
1031 }
1032 size_t end = start + 1;
1033 while (end < s.size() && ((isalnum(s[end]) != 0) || s[end] == '_')) {
1034 end++;
1035 }
1036 bool needSpace = pos != 0 && s[pos - 1] != ' ' && s[pos - 1] != '\n';
1037 if (s[pos + 1] == 'a') {
1038 replacements.push_back(s.substr(start, end - start));
1039 if (pos >= mathjax_open && pos <= mathjax_close) {
1040 oss << "{\\bf " << replacements.back() << "}";
1041 } else {
1042 oss << (needSpace ? " " : "") << "``" << replacements.back() << "`` ";
1043 }
1044 } else {
1045 if (pos >= mathjax_open && pos <= mathjax_close) {
1046 oss << "{\\bf " << s.substr(start, end - start) << "}";
1047 } else {
1048 oss << (needSpace ? " " : "") << "``" << s.substr(start, end - start) << "`` ";
1049 }
1050 }
1051 lastpos = end;
1052 pos = std::min(s.find("\\a", lastpos), s.find("\\p", lastpos));
1053 }
1054 oss << s.substr(lastpos, std::string::npos);
1055 s = oss.str();
1056
1057 std::ostringstream oss2;
1058 pos = std::min(s.find("\\("), s.find("\\)"));
1059 lastpos = 0;
1060 while (pos != std::string::npos) {
1061 if (s[pos + 1] == ')') {
1062 // remove trailing whitespace
1063 std::string t = s.substr(lastpos, pos - lastpos);
1064 size_t t_end = t.find_last_not_of(' ');
1065 if (t_end != std::string::npos) {
1066 t_end++;
1067 }
1068 oss2 << t.substr(0, t_end);
1069 } else {
1070 oss2 << s.substr(lastpos, pos - lastpos);
1071 }
1072 lastpos = pos + 2;
1073 if (s[pos + 1] == '(') {
1074 oss2 << ":math:`";
1075 lastpos = s.find_first_not_of(' ', lastpos);
1076 } else {
1077 oss2 << "`";
1078 }
1079 pos = std::min(s.find("\\(", lastpos), s.find("\\)", lastpos));
1080 }
1081 oss2 << s.substr(lastpos, std::string::npos);
1082 s = oss2.str();
1083
1084 std::ostringstream oss3;
1085 pos = std::min(s.find("\\["), s.find("\\]"));
1086 lastpos = 0;
1087 while (pos != std::string::npos) {
1088 if (s[pos + 1] == ']') {
1089 // remove trailing whitespace
1090 std::string t = s.substr(lastpos, pos - lastpos);
1091 size_t t_end = t.find_last_not_of(' ');
1092 if (t_end != std::string::npos) {
1093 t_end++;
1094 }
1095 oss3 << t.substr(0, t_end);
1096 } else {
1097 oss3 << s.substr(lastpos, pos - lastpos);
1098 }
1099 lastpos = pos + 2;
1100 if (s[pos + 1] == '[') {
1101 oss3 << "``";
1102 lastpos = s.find_first_not_of(' ', lastpos);
1103 } else {
1104 oss3 << "``";
1105 }
1106 pos = std::min(s.find("\\[", lastpos), s.find("\\]", lastpos));
1107 }
1108 oss3 << s.substr(lastpos, std::string::npos);
1109 s = oss3.str();
1110 return replacements;
1111 }
1112
1113 static std::pair<std::string, std::string> extractArgLine(std::string& s, size_t n) {
1114 size_t start = n;
1115 while (start < s.size() && s[start] != ' ' && s[start] != '\t') {
1116 start++;
1117 }
1118 while (start < s.size() && (s[start] == ' ' || s[start] == '\t')) {
1119 start++;
1120 }
1121 size_t end = start + 1;
1122 while (end < s.size() && s[end] != ':') {
1123 end++;
1124 }
1125 std::string arg = s.substr(start, end - start);
1126 size_t doc_start = end + 1;
1127 while (end < s.size() && s[end] != '\n') {
1128 end++;
1129 }
1130 std::string ret = s.substr(doc_start, end - doc_start);
1131 replaceArgsRST(ret);
1132 s = s.substr(0, n) + s.substr(end, std::string::npos);
1133 return make_pair(arg, ret);
1134 }
1135
1136public:
1137 PrintRSTVisitor(EnvI& env, HtmlDocOutput::Group& mg, HtmlDocOutput::FunMap& fm,
1138 bool includeStdLib)
1139 : _env(env), _maingroup(mg), _funmap(fm), _includeStdLib(includeStdLib) {}
1140 bool enterModel(Model* m) {
1141 if (!_includeStdLib && m->filename() == "stdlib.mzn") {
1142 return false;
1143 }
1144 const std::string& dc = m->docComment();
1145 if (!dc.empty()) {
1146 size_t gpos = dc.find("@groupdef");
1147 while (gpos != std::string::npos) {
1148 size_t start = gpos;
1149 while (start < dc.size() && dc[start] != ' ' && dc[start] != '\t') {
1150 start++;
1151 }
1152 while (start < dc.size() && (dc[start] == ' ' || dc[start] == '\t')) {
1153 start++;
1154 }
1155 size_t end = start + 1;
1156 while (end < dc.size() && ((isalnum(dc[end]) != 0) || dc[end] == '_' || dc[end] == '.')) {
1157 end++;
1158 }
1159 std::string groupName = dc.substr(start, end - start);
1160 size_t doc_start = end + 1;
1161 while (end < dc.size() && dc[end] != '\n') {
1162 end++;
1163 }
1164 std::string groupHTMLName = dc.substr(doc_start, end - doc_start);
1165
1166 size_t next = dc.find("@groupdef", gpos + 1);
1167 std::string groupDesc = dc.substr(end, next == std::string::npos ? next : next - end);
1168 replaceArgsRST(groupDesc);
1169 HtmlDocOutput::set_group_desc(_maingroup, groupName, groupHTMLName, groupDesc);
1170 gpos = next;
1171 }
1172 }
1173 return true;
1174 }
1175 /// Visit variable declaration
1176 void vVarDeclI(VarDeclI* vdi) {
1177 if (Call* docstring = Expression::dynamicCast<Call>(
1178 get_annotation(vdi->e()->ann(), constants().ann.doc_comment))) {
1179 std::string ds = eval_string(_env, docstring->arg(0));
1180 std::string group("main");
1181 size_t group_idx = ds.find("@group");
1182 if (group_idx != std::string::npos) {
1183 group = HtmlDocOutput::extract_arg_word(ds, group_idx);
1184 }
1185 std::ostringstream os;
1186 std::string sig = vdi->e()->type().toString(_env) + " " +
1187 std::string(vdi->e()->id()->str().c_str(), vdi->e()->id()->str().size());
1188
1189 std::string myMainGroup = group.substr(0, group.find_first_of('.'));
1190 auto it = _maingroup.subgroups.find(myMainGroup);
1191 os << ".. index::\n";
1192 if (it != _maingroup.subgroups.m.end()) {
1193 os << " pair: " << (*it)->htmlName << "; " << *vdi->e()->id() << "\n\n";
1194 } else {
1195 std::cerr << "did not find " << myMainGroup << "\n";
1196 os << " single: " << *vdi->e()->id() << "\n\n";
1197 }
1198
1199 os << ".. code-block:: minizinc\n\n";
1200 if (vdi->e()->ti()->type() == Type::ann()) {
1201 os << " annotation " << *vdi->e()->id();
1202 } else {
1203 os << " " << *vdi->e()->ti() << ": " << *vdi->e()->id();
1204 }
1205 os << "\n\n";
1206 os << HtmlDocOutput::trim(ds) << "\n\n";
1207 GCLock lock;
1208 HtmlDocOutput::DocItem di(
1209 vdi->e()->type().isPar() ? HtmlDocOutput::DocItem::T_PAR : HtmlDocOutput::DocItem::T_VAR,
1210 sig, sig, os.str());
1211 HtmlDocOutput::add_to_group(_maingroup, group, di);
1212 }
1213 }
1214 /// Visit function item
1215 void vFunctionI(FunctionI* fi) {
1216 if (Call* docstring =
1217 Expression::dynamicCast<Call>(get_annotation(fi->ann(), constants().ann.doc_comment))) {
1218 std::string ds = eval_string(_env, docstring->arg(0));
1219 std::string group("main");
1220 size_t group_idx = ds.find("@group");
1221 if (group_idx != std::string::npos) {
1222 group = HtmlDocOutput::extract_arg_word(ds, group_idx);
1223 }
1224
1225 size_t param_idx = ds.find("@param");
1226 std::vector<std::pair<std::string, std::string> > params;
1227 while (param_idx != std::string::npos) {
1228 params.push_back(extractArgLine(ds, param_idx));
1229 param_idx = ds.find("@param");
1230 }
1231
1232 std::vector<std::string> args = replaceArgsRST(ds);
1233
1234 std::unordered_set<std::string> allArgs;
1235 for (auto& arg : args) {
1236 allArgs.insert(arg);
1237 }
1238 for (auto& param : params) {
1239 allArgs.insert(param.first);
1240 }
1241
1242 GCLock lock;
1243 for (unsigned int i = 0; i < fi->params().size(); i++) {
1244 if (allArgs.find(std::string(fi->params()[i]->id()->str().c_str(),
1245 fi->params()[i]->id()->str().size())) == allArgs.end()) {
1246 std::cerr << "Warning: parameter " << *fi->params()[i]->id()
1247 << " not documented for function " << fi->id() << " at location " << fi->loc()
1248 << "\n";
1249 }
1250 }
1251
1252 std::string sig;
1253 {
1254 GCLock lock;
1255 auto* fi_c = new FunctionI(Location(), fi->id(), fi->ti(), fi->params());
1256 std::ostringstream oss_sig;
1257 oss_sig << *fi_c;
1258 sig = oss_sig.str();
1259 sig.resize(sig.size() - 2);
1260 }
1261
1262 std::ostringstream os;
1263 std::ostringstream fs;
1264 std::string myMainGroup = group.substr(0, group.find_first_of('.'));
1265 auto it = _maingroup.subgroups.find(myMainGroup);
1266 os << ".. index::\n";
1267 if (it != _maingroup.subgroups.m.end()) {
1268 os << " pair: " << (*it)->htmlName << "; " << fi->id() << "\n\n";
1269 } else {
1270 std::cerr << "did not find " << myMainGroup << "\n";
1271 os << " single: " << fi->id() << "\n\n";
1272 }
1273 os << ".. code-block:: minizinc\n\n";
1274
1275 if (fi->ti()->type() == Type::ann()) {
1276 fs << "annotation ";
1277 } else if (fi->ti()->type() == Type::parbool()) {
1278 fs << "test ";
1279 } else if (fi->ti()->type() == Type::varbool()) {
1280 fs << "predicate ";
1281 } else {
1282 fs << "function " << *fi->ti() << ": ";
1283 }
1284 fs << fi->id() << "(";
1285 os << " " << fs.str();
1286 size_t align = fs.str().size();
1287 for (unsigned int i = 0; i < fi->params().size(); i++) {
1288 fs << *fi->params()[i]->ti();
1289 std::ostringstream fid;
1290 fid << *fi->params()[i]->id();
1291 if (!fid.str().empty()) {
1292 fs << ": " << *fi->params()[i]->id();
1293 }
1294 if (i < fi->params().size() - 1) {
1295 fs << ", ";
1296 }
1297 }
1298 bool splitArgs = (fs.str().size() > 70);
1299 for (unsigned int i = 0; i < fi->params().size(); i++) {
1300 os << *fi->params()[i]->ti();
1301 std::ostringstream fid;
1302 fid << *fi->params()[i]->id();
1303 if (!fid.str().empty()) {
1304 os << ": " << *fi->params()[i]->id();
1305 }
1306 if (i < fi->params().size() - 1) {
1307 os << ",";
1308 if (splitArgs) {
1309 os << "\n ";
1310 for (auto j = static_cast<unsigned int>(align); (j--) != 0U;) {
1311 os << " ";
1312 }
1313 } else {
1314 os << " ";
1315 }
1316 }
1317 }
1318 os << ")";
1319
1320 os << "\n\n";
1321
1322 if (fi->id().c_str()[0] == '\'') {
1323 std::string op = fi->id().substr(1, fi->id().size() - 2);
1324 if (fi->params().size() == 2) {
1325 os << "Usage: ``" << *fi->params()[0]->id() << " " << op << " " << *fi->params()[1]->id()
1326 << "``\n\n";
1327 } else if (fi->params().size() == 1) {
1328 os << "Usage: ``" << op << " " << *fi->params()[0]->id() << "``\n\n";
1329 }
1330 }
1331
1332 os << HtmlDocOutput::trim(ds) << "\n\n";
1333
1334 if (fi->e() != nullptr) {
1335 FunctionI* f_body = fi;
1336 bool alias;
1337 do {
1338 alias = false;
1339 Call* c = Expression::dynamicCast<Call>(f_body->e());
1340 if ((c != nullptr) && c->argCount() == f_body->params().size()) {
1341 bool sameParams = true;
1342 for (unsigned int i = 0; i < f_body->params().size(); i++) {
1343 Id* ident = c->arg(i)->dynamicCast<Id>();
1344 if (ident == nullptr || ident->decl() != f_body->params()[i] ||
1345 ident->str() != c->decl()->params()[i]->id()->str()) {
1346 sameParams = false;
1347 break;
1348 }
1349 }
1350 if (sameParams) {
1351 alias = true;
1352 f_body = c->decl();
1353 }
1354 }
1355 } while (alias);
1356 if (f_body->e() != nullptr) {
1357 std::string filename =
1358 std::string(f_body->loc().filename().c_str(), f_body->loc().filename().size());
1359 size_t filePos = filename.find("std/");
1360 if (filePos != std::string::npos) {
1361 filePos += 4;
1362 os << ".. only:: builder_html\n\n";
1363 os << " `More... <https://github.com/MiniZinc/libminizinc/blob/" << MZN_VERSION_MAJOR
1364 << "." << MZN_VERSION_MINOR << "." << MZN_VERSION_PATCH << "/share/minizinc/std/"
1365 << filename.substr(filePos, std::string::npos) << "#L" << f_body->loc().firstLine()
1366 << "-L" << f_body->loc().lastLine() << ">`__\n\n";
1367 }
1368 }
1369 }
1370
1371 if (!params.empty()) {
1372 os << "Parameters:\n\n";
1373 for (auto& param : params) {
1374 os << "- ``" << param.first << "``: " << param.second << "\n";
1375 }
1376 os << "\n";
1377 }
1378 os << "\n";
1379
1380 HtmlDocOutput::DocItem di(HtmlDocOutput::DocItem::T_FUN,
1381 std::string(fi->id().c_str(), fi->id().size()), sig, os.str());
1382 HtmlDocOutput::add_to_group(_maingroup, group, di);
1383 }
1384 }
1385};
1386
1387std::vector<HtmlDocument> RSTPrinter::printRST(EnvI& env, MiniZinc::Model* m,
1388 const std::string& basename, int splitLevel,
1389 bool includeStdLib, bool generateIndex) {
1390 using namespace HtmlDocOutput;
1391 Group g(basename, basename);
1392 FunMap funMap;
1393 CollectFunctionsVisitor fv(env, funMap, includeStdLib);
1394 iter_items(fv, m);
1395 PrintRSTVisitor prv(env, g, funMap, includeStdLib);
1396 iter_items(prv, m);
1397
1398 std::vector<HtmlDocument> ret;
1399
1400 std::ostringstream oss;
1401 oss << Group::rstHeading(g.htmlName, 0);
1402 oss << trim(g.desc) << "\n";
1403 oss << ".. toctree::\n\n";
1404 for (auto* sg : g.subgroups.m) {
1405 oss << " " << sg->fullPath << "\n";
1406 }
1407
1408 ret.emplace_back(g.fullPath, g.htmlName, oss.str());
1409
1410 for (auto& sg : g.subgroups.m) {
1411 ret.emplace_back(sg->fullPath, sg->htmlName, sg->toRST(0));
1412 }
1413 return ret;
1414}
1415
1416} // namespace MiniZinc