The models, scripts, and results of the benchmarks performed for a Half Reification Journal paper
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->paramCount(); i++) {
710 std::string param(fi->param(i)->id()->str().c_str(), fi->param(i)->id()->str().size());
711 if (allArgs.find(param) == allArgs.end()) {
712 std::cerr << "Warning: parameter " << *fi->param(i)->id()
713 << " not documented for function " << fi->id() << " at location " << fi->loc()
714 << "\n";
715 }
716 }
717
718 std::string sig;
719 {
720 GCLock lock;
721 std::vector<VarDecl*> params(fi->paramCount());
722 for (int i = 0; i < fi->paramCount(); i++) {
723 params[i] = fi->param(i);
724 }
725 auto* fi_c = new FunctionI(Location(), fi->id(), fi->ti(), params);
726 std::ostringstream oss_sig;
727 oss_sig << *fi_c;
728 sig = oss_sig.str();
729 sig.resize(sig.size() - 2);
730 }
731
732 std::ostringstream os;
733 os << "<div class='mzn-fundecl' id='" << HtmlDocOutput::make_html_id(sig) << "'>\n";
734 os << "<div class='mzn-fundecl-code'>";
735 os << "<a href='javascript:void(0)' onclick='revealMore(this)' "
736 "class='mzn-fundecl-more'>◀</a>";
737
738 std::ostringstream fs;
739 if (fi->ti()->type() == Type::ann()) {
740 fs << "annotation ";
741 os << "<span class='mzn-kw'>annotation</span> ";
742 } else if (fi->ti()->type() == Type::parbool()) {
743 fs << "test ";
744 os << "<span class='mzn-kw'>test</span> ";
745 } else if (fi->ti()->type() == Type::varbool()) {
746 fs << "predicate ";
747 os << "<span class='mzn-kw'>predicate</span> ";
748 } else {
749 fs << "function " << *fi->ti() << ": ";
750 os << "<span class='mzn-kw'>function</span> <span class='mzn-ti'>" << *fi->ti()
751 << "</span>: ";
752 }
753 fs << fi->id() << "(";
754 os << "<span class='mzn-fn-id'>" << fi->id() << "</span>(";
755 size_t align = fs.str().size();
756 for (unsigned int i = 0; i < fi->paramCount(); i++) {
757 fs << *fi->param(i)->ti() << ": " << *fi->param(i)->id();
758 if (i < fi->paramCount() - 1) {
759 fs << ", ";
760 }
761 }
762 bool splitArgs = (fs.str().size() > 70);
763 for (unsigned int i = 0; i < fi->paramCount(); i++) {
764 os << "<span class='mzn-ti'>" << *fi->param(i)->ti() << "</span>: "
765 << "<span class='mzn-id'>" << *fi->param(i)->id() << "</span>";
766 if (i < fi->paramCount() - 1) {
767 os << ",";
768 if (splitArgs) {
769 os << "\n";
770 for (auto j = static_cast<unsigned int>(align); (j--) != 0U;) {
771 os << " ";
772 }
773 } else {
774 os << " ";
775 }
776 }
777 }
778 os << ")";
779
780 if (fi->e() != nullptr) {
781 FunctionI* f_body = fi;
782 bool alias;
783 do {
784 alias = false;
785 Call* c = Expression::dynamicCast<Call>(f_body->e());
786 if ((c != nullptr) && c->argCount() == f_body->paramCount()) {
787 bool sameParams = true;
788 for (unsigned int i = 0; i < f_body->paramCount(); i++) {
789 Id* ident = c->arg(i)->dynamicCast<Id>();
790 if (ident == nullptr || ident->decl() != f_body->param(i) ||
791 ident->str() != c->decl()->param(i)->id()->str()) {
792 sameParams = false;
793 break;
794 }
795 }
796 if (sameParams) {
797 alias = true;
798 f_body = c->decl();
799 }
800 }
801 } while (alias);
802 if (f_body->e() != nullptr) {
803 std::ostringstream body_os;
804 Printer p(body_os, 70);
805 p.print(f_body->e());
806
807 std::string filename =
808 std::string(f_body->loc().filename().c_str(), f_body->loc().filename().size());
809 size_t lastSlash = filename.find_last_of('/');
810 if (lastSlash != std::string::npos) {
811 filename = filename.substr(lastSlash + 1, std::string::npos);
812 }
813 os << "<span class='mzn-fundecl-equals'> =</span>";
814 os << "\n<div class='mzn-fundecl-more-code'>";
815 os << "<div class='mzn-fundecl-body'>";
816 os << body_os.str();
817 os << "</div>\n";
818 os << "(standard decomposition from " << filename << ":" << f_body->loc().firstLine()
819 << ")";
820 os << "</div>";
821 }
822 }
823
824 os << "</div>\n<div class='mzn-fundecl-doc'>\n";
825
826 if (fi->id().c_str()[0] == '\'') {
827 std::string op = fi->id().substr(1, fi->id().size() - 2);
828 ;
829 const char* space = (op[0] >= 'a' ? " " : "");
830 if (fi->paramCount() == 2) {
831 os << "<p>Usage: <span class=\"mzn-arg\">" << *fi->param(0)->id() << space << op << space
832 << *fi->param(1)->id() << "</span></p>";
833 } else if (fi->paramCount() == 1) {
834 os << "<p>Usage: <span class=\"mzn-arg\">" << op << space << *fi->param(0)->id()
835 << "</span></p>";
836 }
837 }
838
839 std::string dshtml = addHTML(ds);
840
841 os << dshtml;
842 if (!params.empty()) {
843 os << "<div class='mzn-fundecl-params-heading'>Parameters</div>\n";
844 os << "<ul class='mzn-fundecl-params'>\n";
845 for (auto& param : params) {
846 os << "<li><span class='mzn-arg'>" << param.first << "</span>: " << param.second
847 << "</li>\n";
848 }
849 os << "</ul>\n";
850 }
851 os << "</div>";
852 os << "</div>";
853
854 HtmlDocOutput::DocItem di(HtmlDocOutput::DocItem::T_FUN,
855 std::string(fi->id().c_str(), fi->id().size()), sig, os.str());
856 HtmlDocOutput::add_to_group(_maingroup, group, di);
857 }
858 }
859};
860
861std::vector<HtmlDocument> HtmlPrinter::printHtml(EnvI& env, MiniZinc::Model* m,
862 const std::string& basename, int splitLevel,
863 bool includeStdLib, bool generateIndex) {
864 using namespace HtmlDocOutput;
865 Group g(basename, basename);
866 FunMap funMap;
867 CollectFunctionsVisitor fv(env, funMap, includeStdLib);
868 iter_items(fv, m);
869 PrintHtmlVisitor phv(env, g, funMap, includeStdLib);
870 iter_items(phv, m);
871
872 std::vector<HtmlDocument> ret;
873
874 struct SI {
875 Group* g;
876 Group* p;
877 int level;
878 int idx;
879 SI(Group* g0, Group* p0, int level0, int idx0) : g(g0), p(p0), level(level0), idx(idx0) {}
880 };
881
882 struct IndexEntry {
883 std::string id;
884 std::string sig;
885 std::string link;
886 std::string groupName;
887 IndexEntry(std::string id0, std::string sig0, std::string link0, std::string groupName0)
888 : id(std::move(id0)),
889 sig(std::move(sig0)),
890 link(std::move(link0)),
891 groupName(std::move(groupName0)) {
892 size_t spacepos = id.find_last_of(' ');
893 if (spacepos != std::string::npos) {
894 id = id.substr(spacepos + 1);
895 }
896 }
897 bool operator<(const IndexEntry& e) const {
898 if ((isalpha(id[0]) == 0) && (isalpha(e.id[0]) != 0)) {
899 return true;
900 }
901 return id == e.id ? groupName < e.groupName : id < e.id;
902 }
903 };
904 std::vector<IndexEntry> index;
905
906 std::vector<SI> stack;
907 stack.emplace_back(&g, nullptr, 0, 0);
908 while (!stack.empty()) {
909 Group& g = *stack.back().g;
910 int curLevel = stack.back().level;
911 int curIdx = stack.back().idx;
912 Group* p = stack.back().p;
913 stack.pop_back();
914 for (const auto& it : g.items) {
915 index.emplace_back(it.id, it.sig, g.fullPath, g.htmlName);
916 }
917 ret.emplace_back(g.fullPath, g.htmlName,
918 g.toHTML(curLevel, splitLevel, p, curIdx, basename, generateIndex));
919 if (curLevel < splitLevel) {
920 for (unsigned int i = 0; i < g.subgroups.m.size(); i++) {
921 stack.emplace_back(g.subgroups.m[i], &g, curLevel + 1, i);
922 }
923 }
924 }
925
926 if (generateIndex) {
927 std::sort(index.begin(), index.end());
928 std::ostringstream oss;
929 index.emplace_back("", "", "", "");
930
931 std::vector<std::string> idxSections;
932
933 if (!index.empty()) {
934 if (isalpha(index[0].id[0]) != 0) {
935 char idxSec_c = (char)toupper(index[0].id[0]);
936 std::string idxSec(&idxSec_c, 1);
937 oss << "<h3 id='Idx" << idxSec << "'>" << idxSec << "</h3>\n";
938 idxSections.push_back(idxSec);
939 } else {
940 oss << "<h3 id='IdxSymbols'>Symbols</h3>\n";
941 idxSections.emplace_back("Symbols");
942 }
943 }
944 oss << "<ul>\n";
945 std::string prevId = index.empty() ? "" : index[0].id;
946 std::vector<IndexEntry> curEntries;
947 for (auto ie : index) {
948 if (ie.id != prevId) {
949 oss << "<li>";
950 assert(!curEntries.empty());
951 IndexEntry& cur = curEntries[0];
952 if (curEntries.size() == 1) {
953 oss << cur.id << " <a href='" << cur.link << ".html#"
954 << HtmlDocOutput::make_html_id(cur.sig) << "'>"
955 << "(" << cur.groupName << ")</a>";
956 } else {
957 oss << cur.id << " (";
958 bool first = true;
959 for (const auto& i_ie : curEntries) {
960 if (first) {
961 first = false;
962 } else {
963 oss << ", ";
964 }
965 oss << "<a href='" << i_ie.link << ".html#" << HtmlDocOutput::make_html_id(i_ie.sig)
966 << "'>";
967 oss << i_ie.groupName << "</a>";
968 }
969 oss << ")";
970 }
971 oss << "</li>\n";
972 curEntries.clear();
973 }
974 if ((isalpha(ie.id[0]) != 0) && ie.id[0] != prevId[0]) {
975 char idxSec_c = (char)toupper(ie.id[0]);
976 std::string idxSec(&idxSec_c, 1);
977 oss << "</ul>\n<h3 id='Idx" << idxSec << "'>" << idxSec << "</h3><ul>";
978 idxSections.push_back(idxSec);
979 }
980 prevId = ie.id;
981 if (curEntries.empty() || curEntries.back().groupName != ie.groupName) {
982 curEntries.push_back(ie);
983 }
984 }
985 oss << "</ul>\n";
986
987 std::ostringstream oss_header;
988 oss_header << "<div class='mzn-group-level-0'>\n";
989 oss_header << "<div class='mzn-group-nav'>";
990 oss_header << "<a class='mzn-nav-up' href='" << g.getAnchor(0, 1) << "' title='" << g.htmlName
991 << "'>⇧</a> ";
992 bool first = true;
993 for (const auto& is : idxSections) {
994 if (first) {
995 first = false;
996 } else {
997 oss_header << " | ";
998 }
999 oss_header << "<a href='#Idx" << is << "'>" << is << "</a>";
1000 }
1001
1002 oss_header << "</div>";
1003
1004 oss_header << "<div class='mzn-group-name'>Index</div>\n";
1005
1006 HtmlDocument idx("doc-index", "Index", oss_header.str() + oss.str());
1007 ret.push_back(idx);
1008 }
1009 return ret;
1010}
1011
1012class PrintRSTVisitor : public ItemVisitor {
1013protected:
1014 EnvI& _env;
1015 HtmlDocOutput::Group& _maingroup;
1016 HtmlDocOutput::FunMap& _funmap;
1017 bool _includeStdLib;
1018
1019 static std::vector<std::string> replaceArgsRST(std::string& s) {
1020 std::vector<std::string> replacements;
1021 std::ostringstream oss;
1022 size_t lastpos = 0;
1023 size_t pos = std::min(s.find("\\a"), s.find("\\p"));
1024 size_t mathjax_open = s.find("\\(");
1025 size_t mathjax_close = s.rfind("\\)");
1026 while (pos != std::string::npos) {
1027 oss << s.substr(lastpos, pos - lastpos);
1028 size_t start = pos;
1029 while (start < s.size() && s[start] != ' ' && s[start] != '\t') {
1030 start++;
1031 }
1032 while (start < s.size() && (s[start] == ' ' || s[start] == '\t')) {
1033 start++;
1034 }
1035 size_t end = start + 1;
1036 while (end < s.size() && ((isalnum(s[end]) != 0) || s[end] == '_')) {
1037 end++;
1038 }
1039 bool needSpace = pos != 0 && s[pos - 1] != ' ' && s[pos - 1] != '\n';
1040 if (s[pos + 1] == 'a') {
1041 replacements.push_back(s.substr(start, end - start));
1042 if (pos >= mathjax_open && pos <= mathjax_close) {
1043 oss << "{\\bf " << replacements.back() << "}";
1044 } else {
1045 oss << (needSpace ? " " : "") << "``" << replacements.back() << "`` ";
1046 }
1047 } else {
1048 if (pos >= mathjax_open && pos <= mathjax_close) {
1049 oss << "{\\bf " << s.substr(start, end - start) << "}";
1050 } else {
1051 oss << (needSpace ? " " : "") << "``" << s.substr(start, end - start) << "`` ";
1052 }
1053 }
1054 lastpos = end;
1055 pos = std::min(s.find("\\a", lastpos), s.find("\\p", lastpos));
1056 }
1057 oss << s.substr(lastpos, std::string::npos);
1058 s = oss.str();
1059
1060 std::ostringstream oss2;
1061 pos = std::min(s.find("\\("), s.find("\\)"));
1062 lastpos = 0;
1063 while (pos != std::string::npos) {
1064 if (s[pos + 1] == ')') {
1065 // remove trailing whitespace
1066 std::string t = s.substr(lastpos, pos - lastpos);
1067 size_t t_end = t.find_last_not_of(' ');
1068 if (t_end != std::string::npos) {
1069 t_end++;
1070 }
1071 oss2 << t.substr(0, t_end);
1072 } else {
1073 oss2 << s.substr(lastpos, pos - lastpos);
1074 }
1075 lastpos = pos + 2;
1076 if (s[pos + 1] == '(') {
1077 oss2 << ":math:`";
1078 lastpos = s.find_first_not_of(' ', lastpos);
1079 } else {
1080 oss2 << "`";
1081 }
1082 pos = std::min(s.find("\\(", lastpos), s.find("\\)", lastpos));
1083 }
1084 oss2 << s.substr(lastpos, std::string::npos);
1085 s = oss2.str();
1086
1087 std::ostringstream oss3;
1088 pos = std::min(s.find("\\["), s.find("\\]"));
1089 lastpos = 0;
1090 while (pos != std::string::npos) {
1091 if (s[pos + 1] == ']') {
1092 // remove trailing whitespace
1093 std::string t = s.substr(lastpos, pos - lastpos);
1094 size_t t_end = t.find_last_not_of(' ');
1095 if (t_end != std::string::npos) {
1096 t_end++;
1097 }
1098 oss3 << t.substr(0, t_end);
1099 } else {
1100 oss3 << s.substr(lastpos, pos - lastpos);
1101 }
1102 lastpos = pos + 2;
1103 if (s[pos + 1] == '[') {
1104 oss3 << "``";
1105 lastpos = s.find_first_not_of(' ', lastpos);
1106 } else {
1107 oss3 << "``";
1108 }
1109 pos = std::min(s.find("\\[", lastpos), s.find("\\]", lastpos));
1110 }
1111 oss3 << s.substr(lastpos, std::string::npos);
1112 s = oss3.str();
1113 return replacements;
1114 }
1115
1116 static std::pair<std::string, std::string> extractArgLine(std::string& s, size_t n) {
1117 size_t start = n;
1118 while (start < s.size() && s[start] != ' ' && s[start] != '\t') {
1119 start++;
1120 }
1121 while (start < s.size() && (s[start] == ' ' || s[start] == '\t')) {
1122 start++;
1123 }
1124 size_t end = start + 1;
1125 while (end < s.size() && s[end] != ':') {
1126 end++;
1127 }
1128 std::string arg = s.substr(start, end - start);
1129 size_t doc_start = end + 1;
1130 while (end < s.size() && s[end] != '\n') {
1131 end++;
1132 }
1133 std::string ret = s.substr(doc_start, end - doc_start);
1134 replaceArgsRST(ret);
1135 s = s.substr(0, n) + s.substr(end, std::string::npos);
1136 return make_pair(arg, ret);
1137 }
1138
1139public:
1140 PrintRSTVisitor(EnvI& env, HtmlDocOutput::Group& mg, HtmlDocOutput::FunMap& fm,
1141 bool includeStdLib)
1142 : _env(env), _maingroup(mg), _funmap(fm), _includeStdLib(includeStdLib) {}
1143 bool enterModel(Model* m) {
1144 if (!_includeStdLib && m->filename() == "stdlib.mzn") {
1145 return false;
1146 }
1147 const std::string& dc = m->docComment();
1148 if (!dc.empty()) {
1149 size_t gpos = dc.find("@groupdef");
1150 while (gpos != std::string::npos) {
1151 size_t start = gpos;
1152 while (start < dc.size() && dc[start] != ' ' && dc[start] != '\t') {
1153 start++;
1154 }
1155 while (start < dc.size() && (dc[start] == ' ' || dc[start] == '\t')) {
1156 start++;
1157 }
1158 size_t end = start + 1;
1159 while (end < dc.size() && ((isalnum(dc[end]) != 0) || dc[end] == '_' || dc[end] == '.')) {
1160 end++;
1161 }
1162 std::string groupName = dc.substr(start, end - start);
1163 size_t doc_start = end + 1;
1164 while (end < dc.size() && dc[end] != '\n') {
1165 end++;
1166 }
1167 std::string groupHTMLName = dc.substr(doc_start, end - doc_start);
1168
1169 size_t next = dc.find("@groupdef", gpos + 1);
1170 std::string groupDesc = dc.substr(end, next == std::string::npos ? next : next - end);
1171 replaceArgsRST(groupDesc);
1172 HtmlDocOutput::set_group_desc(_maingroup, groupName, groupHTMLName, groupDesc);
1173 gpos = next;
1174 }
1175 }
1176 return true;
1177 }
1178 /// Visit variable declaration
1179 void vVarDeclI(VarDeclI* vdi) {
1180 if (Call* docstring = Expression::dynamicCast<Call>(
1181 get_annotation(vdi->e()->ann(), constants().ann.doc_comment))) {
1182 std::string ds = eval_string(_env, docstring->arg(0));
1183 std::string group("main");
1184 size_t group_idx = ds.find("@group");
1185 if (group_idx != std::string::npos) {
1186 group = HtmlDocOutput::extract_arg_word(ds, group_idx);
1187 }
1188 std::ostringstream os;
1189 std::string sig = vdi->e()->type().toString(_env) + " " +
1190 std::string(vdi->e()->id()->str().c_str(), vdi->e()->id()->str().size());
1191
1192 std::string myMainGroup = group.substr(0, group.find_first_of('.'));
1193 auto it = _maingroup.subgroups.find(myMainGroup);
1194 os << ".. index::\n";
1195 if (it != _maingroup.subgroups.m.end()) {
1196 os << " pair: " << (*it)->htmlName << "; " << *vdi->e()->id() << "\n\n";
1197 } else {
1198 std::cerr << "did not find " << myMainGroup << "\n";
1199 os << " single: " << *vdi->e()->id() << "\n\n";
1200 }
1201
1202 os << ".. code-block:: minizinc\n\n";
1203 if (vdi->e()->ti()->type() == Type::ann()) {
1204 os << " annotation " << *vdi->e()->id();
1205 } else {
1206 os << " " << *vdi->e()->ti() << ": " << *vdi->e()->id();
1207 }
1208 os << "\n\n";
1209 os << HtmlDocOutput::trim(ds) << "\n\n";
1210 GCLock lock;
1211 HtmlDocOutput::DocItem di(
1212 vdi->e()->type().isPar() ? HtmlDocOutput::DocItem::T_PAR : HtmlDocOutput::DocItem::T_VAR,
1213 sig, sig, os.str());
1214 HtmlDocOutput::add_to_group(_maingroup, group, di);
1215 }
1216 }
1217 /// Visit function item
1218 void vFunctionI(FunctionI* fi) {
1219 if (Call* docstring =
1220 Expression::dynamicCast<Call>(get_annotation(fi->ann(), constants().ann.doc_comment))) {
1221 std::string ds = eval_string(_env, docstring->arg(0));
1222 std::string group("main");
1223 size_t group_idx = ds.find("@group");
1224 if (group_idx != std::string::npos) {
1225 group = HtmlDocOutput::extract_arg_word(ds, group_idx);
1226 }
1227
1228 size_t param_idx = ds.find("@param");
1229 std::vector<std::pair<std::string, std::string> > params;
1230 while (param_idx != std::string::npos) {
1231 params.push_back(extractArgLine(ds, param_idx));
1232 param_idx = ds.find("@param");
1233 }
1234
1235 std::vector<std::string> args = replaceArgsRST(ds);
1236
1237 std::unordered_set<std::string> allArgs;
1238 for (auto& arg : args) {
1239 allArgs.insert(arg);
1240 }
1241 for (auto& param : params) {
1242 allArgs.insert(param.first);
1243 }
1244
1245 GCLock lock;
1246 for (unsigned int i = 0; i < fi->paramCount(); i++) {
1247 if (allArgs.find(std::string(fi->param(i)->id()->str().c_str(),
1248 fi->param(i)->id()->str().size())) == allArgs.end()) {
1249 std::cerr << "Warning: parameter " << *fi->param(i)->id()
1250 << " not documented for function " << fi->id() << " at location " << fi->loc()
1251 << "\n";
1252 }
1253 }
1254
1255 std::string sig;
1256 {
1257 GCLock lock;
1258 std::vector<VarDecl*> params(fi->paramCount());
1259 for (int i = 0; i < fi->paramCount(); i++) {
1260 params[i] = fi->param(i);
1261 }
1262 auto* fi_c = new FunctionI(Location(), fi->id(), fi->ti(), params);
1263 std::ostringstream oss_sig;
1264 oss_sig << *fi_c;
1265 sig = oss_sig.str();
1266 sig.resize(sig.size() - 2);
1267 }
1268
1269 std::ostringstream os;
1270 std::ostringstream fs;
1271 std::string myMainGroup = group.substr(0, group.find_first_of('.'));
1272 auto it = _maingroup.subgroups.find(myMainGroup);
1273 os << ".. index::\n";
1274 if (it != _maingroup.subgroups.m.end()) {
1275 os << " pair: " << (*it)->htmlName << "; " << fi->id() << "\n\n";
1276 } else {
1277 std::cerr << "did not find " << myMainGroup << "\n";
1278 os << " single: " << fi->id() << "\n\n";
1279 }
1280 os << ".. code-block:: minizinc\n\n";
1281
1282 if (fi->ti()->type() == Type::ann()) {
1283 fs << "annotation ";
1284 } else if (fi->ti()->type() == Type::parbool()) {
1285 fs << "test ";
1286 } else if (fi->ti()->type() == Type::varbool()) {
1287 fs << "predicate ";
1288 } else {
1289 fs << "function " << *fi->ti() << ": ";
1290 }
1291 fs << fi->id() << "(";
1292 os << " " << fs.str();
1293 size_t align = fs.str().size();
1294 for (unsigned int i = 0; i < fi->paramCount(); i++) {
1295 fs << *fi->param(i)->ti();
1296 std::ostringstream fid;
1297 fid << *fi->param(i)->id();
1298 if (!fid.str().empty()) {
1299 fs << ": " << *fi->param(i)->id();
1300 }
1301 if (i < fi->paramCount() - 1) {
1302 fs << ", ";
1303 }
1304 }
1305 bool splitArgs = (fs.str().size() > 70);
1306 for (unsigned int i = 0; i < fi->paramCount(); i++) {
1307 os << *fi->param(i)->ti();
1308 std::ostringstream fid;
1309 fid << *fi->param(i)->id();
1310 if (!fid.str().empty()) {
1311 os << ": " << *fi->param(i)->id();
1312 }
1313 if (i < fi->paramCount() - 1) {
1314 os << ",";
1315 if (splitArgs) {
1316 os << "\n ";
1317 for (auto j = static_cast<unsigned int>(align); (j--) != 0U;) {
1318 os << " ";
1319 }
1320 } else {
1321 os << " ";
1322 }
1323 }
1324 }
1325 os << ")";
1326
1327 os << "\n\n";
1328
1329 if (fi->id().c_str()[0] == '\'') {
1330 std::string op = fi->id().substr(1, fi->id().size() - 2);
1331 if (fi->paramCount() == 2) {
1332 os << "Usage: ``" << *fi->param(0)->id() << " " << op << " " << *fi->param(1)->id()
1333 << "``\n\n";
1334 } else if (fi->paramCount() == 1) {
1335 os << "Usage: ``" << op << " " << *fi->param(0)->id() << "``\n\n";
1336 }
1337 }
1338
1339 os << HtmlDocOutput::trim(ds) << "\n\n";
1340
1341 if (fi->e() != nullptr) {
1342 FunctionI* f_body = fi;
1343 bool alias;
1344 do {
1345 alias = false;
1346 Call* c = Expression::dynamicCast<Call>(f_body->e());
1347 if ((c != nullptr) && c->argCount() == f_body->paramCount()) {
1348 bool sameParams = true;
1349 for (unsigned int i = 0; i < f_body->paramCount(); i++) {
1350 Id* ident = c->arg(i)->dynamicCast<Id>();
1351 if (ident == nullptr || ident->decl() != f_body->param(i) ||
1352 ident->str() != c->decl()->param(i)->id()->str()) {
1353 sameParams = false;
1354 break;
1355 }
1356 }
1357 if (sameParams) {
1358 alias = true;
1359 f_body = c->decl();
1360 }
1361 }
1362 } while (alias);
1363 if (f_body->e() != nullptr) {
1364 std::string filename =
1365 std::string(f_body->loc().filename().c_str(), f_body->loc().filename().size());
1366 size_t filePos = filename.find("std/");
1367 if (filePos != std::string::npos) {
1368 filePos += 4;
1369 os << ".. only:: builder_html\n\n";
1370 os << " `More... <https://github.com/MiniZinc/libminizinc/blob/" << MZN_VERSION_MAJOR
1371 << "." << MZN_VERSION_MINOR << "." << MZN_VERSION_PATCH << "/share/minizinc/std/"
1372 << filename.substr(filePos, std::string::npos) << "#L" << f_body->loc().firstLine()
1373 << "-L" << f_body->loc().lastLine() << ">`__\n\n";
1374 }
1375 }
1376 }
1377
1378 if (!params.empty()) {
1379 os << "Parameters:\n\n";
1380 for (auto& param : params) {
1381 os << "- ``" << param.first << "``: " << param.second << "\n";
1382 }
1383 os << "\n";
1384 }
1385 os << "\n";
1386
1387 HtmlDocOutput::DocItem di(HtmlDocOutput::DocItem::T_FUN,
1388 std::string(fi->id().c_str(), fi->id().size()), sig, os.str());
1389 HtmlDocOutput::add_to_group(_maingroup, group, di);
1390 }
1391 }
1392};
1393
1394std::vector<HtmlDocument> RSTPrinter::printRST(EnvI& env, MiniZinc::Model* m,
1395 const std::string& basename, int splitLevel,
1396 bool includeStdLib, bool generateIndex) {
1397 using namespace HtmlDocOutput;
1398 Group g(basename, basename);
1399 FunMap funMap;
1400 CollectFunctionsVisitor fv(env, funMap, includeStdLib);
1401 iter_items(fv, m);
1402 PrintRSTVisitor prv(env, g, funMap, includeStdLib);
1403 iter_items(prv, m);
1404
1405 std::vector<HtmlDocument> ret;
1406
1407 std::ostringstream oss;
1408 oss << Group::rstHeading(g.htmlName, 0);
1409 oss << trim(g.desc) << "\n";
1410 oss << ".. toctree::\n\n";
1411 for (auto* sg : g.subgroups.m) {
1412 oss << " " << sg->fullPath << "\n";
1413 }
1414
1415 ret.emplace_back(g.fullPath, g.htmlName, oss.str());
1416
1417 for (auto& sg : g.subgroups.m) {
1418 ret.emplace_back(sg->fullPath, sg->htmlName, sg->toRST(0));
1419 }
1420 return ret;
1421}
1422
1423} // namespace MiniZinc