blob: 4fdc206185132fb013474121ac1b3d9d344fc5d3 [file] [log] [blame]
Cary Clark8032b982017-07-28 11:04:54 -04001/*
2 * Copyright 2017 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8#include "bookmaker.h"
9
10#include "SkOSFile.h"
11#include "SkOSPath.h"
12
Cary Clark7cfcbca2018-01-04 16:11:51 -050013#define FPRINTF(...) \
14 if (fDebugOut) { \
15 SkDebugf(__VA_ARGS__); \
16 } \
17 fprintf(fOut, __VA_ARGS__)
18
Cary Clark682c58d2018-05-16 07:07:07 -040019const char* SubtopicKeys::kGeneratedSubtopics[] = {
20 kClasses, kConstants, kConstructors, kDefines,
21 kMemberFunctions, kMembers, kOperators, kRelatedFunctions, kStructs, kTypedefs,
22};
23
24const char* kConstTableStyle =
25"<style>" "\n"
26 ".td_const td, th { border: 2px solid #dddddd; text-align: left; padding: 8px; }" "\n"
27 ".tr_const tr:nth-child(even) { background-color: #f0f0f0; }" "\n"
28 ".td2_const td:first-child + td { text-align: center; }" "\n"
29"</style>" "\n";
30
31const char* kTableDeclaration = "<table style='border-collapse: collapse; width: 62.5em'>";
32
33#define kTD_Base "border: 2px solid #dddddd; padding: 8px; "
34#define kTH_Left "<th style='text-align: left; " kTD_Base "'>"
35#define kTH_Center "<th style='text-align: center; " kTD_Base "'>"
36
37string kTD_Left = " <td style='text-align: left; " kTD_Base "'>";
38string kTD_Center = " <td style='text-align: center; " kTD_Base "'>";
39string kTR_Dark = " <tr style='background-color: #f0f0f0; '>";
40
41const char* kAllConstTableHeader = " <tr>" kTH_Left "Const</th>" "\n"
42 kTH_Center "Value</th>" "\n"
43 kTH_Left "Description</th>" "</tr>";
44const char* kSubConstTableHeader = " <tr>" kTH_Left "Const</th>" "\n"
45 kTH_Center "Value</th>" "\n"
46 kTH_Left "Details</th>" "\n"
47 kTH_Left "Description</th>" "</tr>";
48const char* kAllMemberTableHeader = " <tr>" kTH_Left "Type</th>" "\n"
49 kTH_Left "Name</th>" "\n"
50 kTH_Left "Description</th>" "</tr>";
51const char* kSubMemberTableHeader = " <tr>" kTH_Left "Type</th>" "\n"
52 kTH_Left "Name</th>" "\n"
53 kTH_Left "Details</th>" "\n"
54 kTH_Left "Description</th>" "</tr>";
55const char* kTopicsTableHeader = " <tr>" kTH_Left "Topic</th>" "\n"
56 kTH_Left "Description</th>" "</tr>";
57
58static string html_file_name(string bmhFileName) {
59 SkASSERT("docs" == bmhFileName.substr(0, 4));
60 SkASSERT('\\' == bmhFileName[4] || '/' == bmhFileName[4]);
61 SkASSERT(".bmh" == bmhFileName.substr(bmhFileName.length() - 4));
62 string result = bmhFileName.substr(5, bmhFileName.length() - 4 - 5);
63 return result;
64}
65
66string MdOut::anchorDef(string str, string name) {
67 if (fValidate) {
68 string htmlName = html_file_name(fFileName);
69 vector<AnchorDef>& allDefs = fAllAnchorDefs[htmlName];
70 if (!std::any_of(allDefs.begin(), allDefs.end(),
71 [str](AnchorDef compare) { return compare.fDef == str; } )) {
72 MarkType markType = fLastDef->fMarkType;
73 if (MarkType::kMethod == markType
74 && std::any_of(fLastDef->fChildren.begin(), fLastDef->fChildren.end(),
75 [](const Definition* compare) {
76 return IncompleteAllowed(compare->fMarkType); } )) {
77 markType = MarkType::kDeprecated;
78 }
79 if (MarkType::kMethod == markType && fLastDef->fClone) {
80 markType = MarkType::kDeprecated; // TODO: hack to allow missing reference
81 }
82 allDefs.push_back( { str, markType } );
83 }
84 }
85 return "<a name='" + str + "'>" + name + "</a>";
86}
87
88string MdOut::anchorRef(string ref, string name) {
89 if (fValidate) {
90 string htmlName;
91 size_t hashIndex = ref.find('#');
92 if (string::npos != hashIndex && "https://" != ref.substr(0, 8)) {
93 if (0 == hashIndex) {
94 htmlName = html_file_name(fFileName);
95 } else {
96 htmlName = ref.substr(0, hashIndex);
97 }
98 vector<string>& allRefs = fAllAnchorRefs[htmlName];
99 string refPart = ref.substr(hashIndex + 1);
100 if (allRefs.end() == std::find(allRefs.begin(), allRefs.end(), refPart)) {
101 allRefs.push_back(refPart);
102 }
103 }
104 }
105 SkASSERT(string::npos != ref.find('#') || string::npos != ref.find("https://"));
106 return "<a href='" + ref + "'>" + name + "</a>";
107}
108
109string MdOut::anchorLocalRef(string ref, string name) {
110 return this->anchorRef("#" + ref, name);
111}
112
113string MdOut::tableDataCodeRef(string ref, string name) {
114 return kTD_Left + this->anchorRef(ref, "<code>" + name + "</code>") + "</td>";
115}
116
117string MdOut::tableDataCodeLocalRef(string ref, string name) {
118 return this->tableDataCodeRef("#" + ref, name);
119}
120
121string MdOut::tableDataCodeLocalRef(string name) {
122 return this->tableDataCodeLocalRef(name, name);
123}
124
125string MdOut::tableDataCodeRef(const Definition* ref) {
126 return this->tableDataCodeLocalRef(ref->fFiddle, ref->fName);
127}
128
129string MdOut::tableDataCodeDef(string def, string name) {
130 return kTD_Left + this->anchorDef(def, "<code>" + name + "</code>") + "</td>";
131}
132
133string MdOut::tableDataCodeDef(const Definition* def) {
134 return this->tableDataCodeDef(def->fFiddle, def->fName);
135}
136
137static string table_data_const(const Definition* def, const char** textStartPtr) {
138 TextParser parser(def);
139 SkAssertResult(parser.skipToEndBracket('\n'));
140 string constant = string(def->fContentStart, (int) (parser.fChar - def->fContentStart));
141 if (textStartPtr) {
142 *textStartPtr = parser.fChar;
143 }
144 return kTD_Center + constant + "</td>";
145}
146
147static string out_table_data_description_start() {
148 return kTD_Left;
149}
150
151static string out_table_data_description(string str) {
152 return kTD_Left + str + "</td>";
153}
154
155static string out_table_data_description(const Definition* def) {
156 return out_table_data_description(string(def->fContentStart,
157 (int) (def->fContentEnd - def->fContentStart)));
158}
159
160static string out_table_data_details(string details) {
161 return kTD_Left + details + "</td>";
162}
163
164#undef kConstTDBase
165#undef kTH_Left
166#undef kTH_Center
167
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400168static void add_ref(string leadingSpaces, string ref, string* result) {
Cary Clark8032b982017-07-28 11:04:54 -0400169 *result += leadingSpaces + ref;
170}
171
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400172static string preformat(string orig) {
Cary Clarkbc5697d2017-10-04 14:31:33 -0400173 string result;
174 for (auto c : orig) {
175 if ('<' == c) {
176 result += "&lt;";
177 } else if ('>' == c) {
178 result += "&gt;";
179 } else {
180 result += c;
181 }
182 }
183 return result;
184}
185
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400186static bool all_lower(string ref) {
Cary Clark2dc84ad2018-01-26 12:56:22 -0500187 for (auto ch : ref) {
188 if (!islower(ch)) {
189 return false;
190 }
191 }
192 return true;
193}
194
Cary Clark682c58d2018-05-16 07:07:07 -0400195// from https://stackoverflow.com/questions/3418231/replace-part-of-a-string-with-another-string
196void replace_all(string& str, const string& from, const string& to) {
197 SkASSERT(!from.empty());
198 size_t start_pos = 0;
199 while ((start_pos = str.find(from, start_pos)) != std::string::npos) {
200 str.replace(start_pos, from.length(), to);
201 start_pos += to.length(); // In case 'to' contains 'from', like replacing 'x' with 'yx'
202 }
203}
204
205// detail strings are preceded by an example comment to check readability
206void MdOut::addPopulators() {
207 fPopulators[SubtopicKeys::kClasses].fName = "Class Declarations";
208 fPopulators[SubtopicKeys::kClasses].fOneLiner = "embedded class members";
209 fPopulators[SubtopicKeys::kClasses].fDetails =
210 /* SkImageInfo */ "uses C++ classes to declare the public data"
211 " structures and interfaces.";
212 fPopulators[SubtopicKeys::kConstants].fName = "Constants";
213 fPopulators[SubtopicKeys::kConstants].fOneLiner = "enum and enum class, and their const values";
214 fPopulators[SubtopicKeys::kConstants].fDetails =
215 /* SkImageInfo */ "related constants are defined by <code>enum</code>,"
216 " <code>enum class</code>, <code>#define</code>, <code>const</code>,"
217 " and <code>constexpr</code>.";
218 fPopulators[SubtopicKeys::kConstructors].fName = "Constructors";
219 fPopulators[SubtopicKeys::kConstructors].fOneLiner = "functions that construct";
220 fPopulators[SubtopicKeys::kConstructors].fDetails =
221 /* SkImageInfo */ "can be constructed or initialized by these functions,"
222 " including C++ class constructors.";
223 fPopulators[SubtopicKeys::kDefines].fName = "Defines";
224 fPopulators[SubtopicKeys::kDefines].fOneLiner = "preprocessor definitions of functions, values";
225 fPopulators[SubtopicKeys::kDefines].fDetails =
226 /* SkImageInfo */ "uses preprocessor definitions to inline code and constants,"
227 " and to abstract platform-specific functionality.";
228 fPopulators[SubtopicKeys::kMemberFunctions].fName = "Functions";
229 fPopulators[SubtopicKeys::kMemberFunctions].fOneLiner = "global and class member functions";
230 fPopulators[SubtopicKeys::kMemberFunctions].fDetails =
231 /* SkImageInfo */ "member functions read and modify the structure properties.";
232 fPopulators[SubtopicKeys::kMembers].fName = "Members";
233 fPopulators[SubtopicKeys::kMembers].fOneLiner = "member values";
234 fPopulators[SubtopicKeys::kMembers].fDetails =
235 /* SkImageInfo */ "members may be read and written directly without using"
236 " a member function.";
237 fPopulators[SubtopicKeys::kOperators].fName = "Operators";
238 fPopulators[SubtopicKeys::kOperators].fOneLiner = "operator overloading methods";
239 fPopulators[SubtopicKeys::kOperators].fDetails =
240 /* SkImageInfo */ "operators inline class member functions with arithmetic"
241 " equivalents.";
242 fPopulators[SubtopicKeys::kRelatedFunctions].fName = "Related Functions";
243 fPopulators[SubtopicKeys::kRelatedFunctions].fOneLiner =
244 "similar member functions grouped together";
245 fPopulators[SubtopicKeys::kRelatedFunctions].fDetails =
246 /* SkImageInfo */ "global, <code>struct</code>, and <code>class</code> related member"
247 " functions share a topic.";
248 fPopulators[SubtopicKeys::kStructs].fName = "Struct Declarations";
249 fPopulators[SubtopicKeys::kStructs].fOneLiner = "embedded struct members";
250 fPopulators[SubtopicKeys::kStructs].fDetails =
251 /* SkImageInfo */ "uses C++ structs to declare the public data"
252 " structures and interfaces.";
253 fPopulators[SubtopicKeys::kTypedefs].fName = "Typedef Declarations";
254 fPopulators[SubtopicKeys::kTypedefs].fOneLiner = "types defined by other types";
255 fPopulators[SubtopicKeys::kTypedefs].fDetails =
256 /* SkImageInfo */ " <code>typedef</code> define a data type.";
257}
258
259Definition* MdOut::checkParentsForMatch(Definition* test, string ref) const {
260 bool isSubtopic = MarkType::kSubtopic == test->fMarkType
261 || MarkType::kTopic == test->fMarkType;
262 do {
263 if (!test->isRoot()) {
264 continue;
265 }
266 bool localTopic = MarkType::kSubtopic == test->fMarkType
267 || MarkType::kTopic == test->fMarkType;
268 if (localTopic != isSubtopic) {
269 continue;
270 }
271 string prefix(isSubtopic ? "_" : "::");
272 RootDefinition* root = test->asRoot();
273 string prefixed = root->fName + prefix + ref;
274 if (Definition* def = root->find(prefixed, RootDefinition::AllowParens::kYes)) {
275 return def;
276 }
277 } while ((test = test->fParent));
278 return nullptr;
279}
280
Cary Clark8032b982017-07-28 11:04:54 -0400281// FIXME: preserve inter-line spaces and don't add new ones
282string MdOut::addReferences(const char* refStart, const char* refEnd,
283 BmhParser::Resolvable resolvable) {
284 string result;
285 MethodParser t(fRoot ? fRoot->fName : string(), fFileName, refStart, refEnd, fLineCount);
286 bool lineStart = true;
287 string ref;
288 string leadingSpaces;
Cary Clarkd0530ba2017-09-14 11:25:39 -0400289 int distFromParam = 99;
Cary Clark8032b982017-07-28 11:04:54 -0400290 do {
Cary Clarkd0530ba2017-09-14 11:25:39 -0400291 ++distFromParam;
Cary Clark8032b982017-07-28 11:04:54 -0400292 const char* base = t.fChar;
293 t.skipWhiteSpace();
294 const char* wordStart = t.fChar;
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400295 if (BmhParser::Resolvable::kFormula == resolvable && !t.eof() && '"' == t.peek()) {
296 t.next();
297 t.skipToEndBracket('"');
298 t.next();
299 continue;
300 }
Cary Clark8032b982017-07-28 11:04:54 -0400301 t.skipToMethodStart();
302 const char* start = t.fChar;
303 if (wordStart < start) {
304 if (lineStart) {
305 lineStart = false;
306 } else {
307 wordStart = base;
308 }
309 result += string(wordStart, start - wordStart);
310 if ('\n' != result.back()) {
311 while (start > wordStart && '\n' == start[-1]) {
312 result += '\n';
313 --start;
314 }
315 }
316 }
317 if (lineStart) {
318 lineStart = false;
319 } else {
320 leadingSpaces = string(base, wordStart - base);
Cary Clarka560c472017-11-27 10:44:06 -0500321 }
Cary Clark682c58d2018-05-16 07:07:07 -0400322 t.skipToMethodEnd(resolvable);
Cary Clark8032b982017-07-28 11:04:54 -0400323 if (base == t.fChar) {
Cary Clark7cfcbca2018-01-04 16:11:51 -0500324 if (!t.eof() && '~' == base[0] && !isalnum(base[1])) {
325 t.next();
326 } else {
327 break;
328 }
Cary Clark8032b982017-07-28 11:04:54 -0400329 }
330 if (start >= t.fChar) {
331 continue;
332 }
333 if (!t.eof() && '"' == t.peek() && start > wordStart && '"' == start[-1]) {
334 continue;
335 }
336 ref = string(start, t.fChar - start);
Cary Clark186d08f2018-04-03 08:43:27 -0400337 if (const Definition* def = this->isDefined(t, ref, resolvable)) {
Cary Clark7cfcbca2018-01-04 16:11:51 -0500338 if (MarkType::kExternal == def->fMarkType) {
Cary Clark682c58d2018-05-16 07:07:07 -0400339 (void) this->anchorRef("undocumented#" + ref, ""); // for anchor validate
Cary Clark7cfcbca2018-01-04 16:11:51 -0500340 add_ref(leadingSpaces, ref, &result);
341 continue;
342 }
Cary Clark8032b982017-07-28 11:04:54 -0400343 SkASSERT(def->fFiddle.length());
Cary Clark682c58d2018-05-16 07:07:07 -0400344 if (BmhParser::Resolvable::kSimple != resolvable
345 && !t.eof() && '(' == t.peek() && t.strnchr(')', t.fEnd)) {
Cary Clark8032b982017-07-28 11:04:54 -0400346 if (!t.skipToEndBracket(')')) {
347 t.reportError("missing close paren");
Cary Clark566414e2018-03-12 08:26:45 -0400348 fAddRefFailed = true;
Cary Clark8032b982017-07-28 11:04:54 -0400349 return result;
350 }
351 t.next();
352 string fullRef = string(start, t.fChar - start);
353 // if _2 etc alternates are defined, look for paren match
354 // may ignore () if ref is all lower case
355 // otherwise flag as error
356 int suffix = '2';
357 bool foundMatch = false;
358 const Definition* altDef = def;
359 while (altDef && suffix <= '9') {
360 if ((foundMatch = altDef->paramsMatch(fullRef, ref))) {
361 def = altDef;
362 ref = fullRef;
363 break;
364 }
365 string altTest = ref + '_';
366 altTest += suffix++;
Cary Clark186d08f2018-04-03 08:43:27 -0400367 altDef = this->isDefined(t, altTest, BmhParser::Resolvable::kOut);
Cary Clark8032b982017-07-28 11:04:54 -0400368 }
369 if (suffix > '9') {
370 t.reportError("too many alts");
Cary Clark566414e2018-03-12 08:26:45 -0400371 fAddRefFailed = true;
Cary Clark8032b982017-07-28 11:04:54 -0400372 return result;
373 }
374 if (!foundMatch) {
Cary Clark186d08f2018-04-03 08:43:27 -0400375 if (!(def = this->isDefined(t, fullRef, resolvable))) {
376 if (BmhParser::Resolvable::kFormula == resolvable) {
377 // TODO: look for looser mapping -- if methods name match, look for
378 // unique mapping based on number of parameters
379 // for now, just look for function name match
380 def = this->isDefined(t, ref, resolvable);
381 }
382 if (!def && !result.size()) {
Cary Clark4c06f5e2017-08-04 12:48:24 -0400383 t.reportError("missing method");
Cary Clark566414e2018-03-12 08:26:45 -0400384 fAddRefFailed = true;
Cary Clark186d08f2018-04-03 08:43:27 -0400385 return result;
Cary Clark4c06f5e2017-08-04 12:48:24 -0400386 }
Cary Clark8032b982017-07-28 11:04:54 -0400387 }
388 ref = fullRef;
389 }
Cary Clark2dc84ad2018-01-26 12:56:22 -0500390 } else if (BmhParser::Resolvable::kClone != resolvable &&
391 all_lower(ref) && (t.eof() || '(' != t.peek())) {
392 add_ref(leadingSpaces, ref, &result);
393 continue;
394 }
Cary Clark186d08f2018-04-03 08:43:27 -0400395 if (!def) {
396 t.reportError("missing method");
397 fAddRefFailed = true;
398 return result;
399 }
Cary Clark2dc84ad2018-01-26 12:56:22 -0500400 result += linkRef(leadingSpaces, def, ref, resolvable);
Cary Clark8032b982017-07-28 11:04:54 -0400401 continue;
402 }
403 if (!t.eof() && '(' == t.peek()) {
404 if (!t.skipToEndBracket(')')) {
405 t.reportError("missing close paren");
Cary Clark566414e2018-03-12 08:26:45 -0400406 fAddRefFailed = true;
Cary Clark8032b982017-07-28 11:04:54 -0400407 return result;
408 }
409 t.next();
410 ref = string(start, t.fChar - start);
Cary Clark186d08f2018-04-03 08:43:27 -0400411 if (const Definition* def = this->isDefined(t, ref, BmhParser::Resolvable::kYes)) {
Cary Clark8032b982017-07-28 11:04:54 -0400412 SkASSERT(def->fFiddle.length());
Cary Clark2dc84ad2018-01-26 12:56:22 -0500413 result += linkRef(leadingSpaces, def, ref, resolvable);
Cary Clark8032b982017-07-28 11:04:54 -0400414 continue;
415 }
416 }
417// class, struct, and enum start with capitals
418// methods may start with upper (static) or lower (most)
419
420 // see if this should have been a findable reference
Ben Wagner63fd7602017-10-09 15:45:33 -0400421
Cary Clark8032b982017-07-28 11:04:54 -0400422 // look for Sk / sk / SK ..
Cary Clark5538c132018-06-14 12:28:14 -0400423 if (!ref.compare(0, 2, "Sk") && ref != "Skew" && ref != "Skews" && ref != "Skewing"
424 && ref != "Skip" && ref != "Skips") {
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400425 if (BmhParser::Resolvable::kOut != resolvable &&
426 BmhParser::Resolvable::kFormula != resolvable) {
Cary Clark1a8d7622018-03-05 13:26:16 -0500427 t.reportError("missed Sk prefixed");
Cary Clark566414e2018-03-12 08:26:45 -0400428 fAddRefFailed = true;
Cary Clark186d08f2018-04-03 08:43:27 -0400429 return result;
Cary Clark1a8d7622018-03-05 13:26:16 -0500430 }
Ben Wagner63fd7602017-10-09 15:45:33 -0400431 }
Cary Clark8032b982017-07-28 11:04:54 -0400432 if (!ref.compare(0, 2, "SK")) {
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400433 if (BmhParser::Resolvable::kOut != resolvable &&
434 BmhParser::Resolvable::kFormula != resolvable) {
Cary Clark8032b982017-07-28 11:04:54 -0400435 t.reportError("missed SK prefixed");
Cary Clark566414e2018-03-12 08:26:45 -0400436 fAddRefFailed = true;
Cary Clark186d08f2018-04-03 08:43:27 -0400437 return result;
Cary Clark8032b982017-07-28 11:04:54 -0400438 }
Ben Wagner63fd7602017-10-09 15:45:33 -0400439 }
Cary Clark8032b982017-07-28 11:04:54 -0400440 if (!isupper(start[0])) {
441 // TODO:
442 // look for all lowercase w/o trailing parens as mistaken method matches
443 // will also need to see if Example Description matches var in example
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400444 const Definition* def = nullptr;
Cary Clark8032b982017-07-28 11:04:54 -0400445 if (fMethod && (def = fMethod->hasParam(ref))) {
Cary Clark2dc84ad2018-01-26 12:56:22 -0500446 result += linkRef(leadingSpaces, def, ref, resolvable);
Cary Clarkd0530ba2017-09-14 11:25:39 -0400447 fLastParam = def;
448 distFromParam = 0;
Cary Clark8032b982017-07-28 11:04:54 -0400449 continue;
Ben Wagner63fd7602017-10-09 15:45:33 -0400450 } else if (!fInDescription && ref[0] != '0'
Cary Clark8032b982017-07-28 11:04:54 -0400451 && string::npos != ref.find_first_of("ABCDEFGHIJKLMNOPQRSTUVWXYZ")) {
452 // FIXME: see isDefined(); check to see if fXX is a member of xx.fXX
453 if (('f' != ref[0] && string::npos == ref.find("()"))
Cary Clarkbad5ad72017-08-03 17:14:08 -0400454// || '.' != t.backup(ref.c_str())
455 && ('k' != ref[0] && string::npos == ref.find("_Private"))) {
Cary Clarkbc5697d2017-10-04 14:31:33 -0400456 if ('.' == wordStart[0] && (distFromParam >= 1 && distFromParam <= 16)) {
Cary Clarkd0530ba2017-09-14 11:25:39 -0400457 const Definition* paramType = this->findParamType();
458 if (paramType) {
459 string fullName = paramType->fName + "::" + ref;
Cary Clarkac47b882018-01-11 10:35:44 -0500460 if (paramType->hasMatch(fullName)) {
Cary Clark2dc84ad2018-01-26 12:56:22 -0500461 result += linkRef(leadingSpaces, paramType, ref, resolvable);
Cary Clarkd0530ba2017-09-14 11:25:39 -0400462 continue;
463 }
464 }
465 }
Cary Clark682c58d2018-05-16 07:07:07 -0400466 if (BmhParser::Resolvable::kSimple != resolvable
467 && BmhParser::Resolvable::kOut != resolvable
468 && BmhParser::Resolvable::kFormula != resolvable) {
Cary Clark8032b982017-07-28 11:04:54 -0400469 t.reportError("missed camelCase");
Cary Clark566414e2018-03-12 08:26:45 -0400470 fAddRefFailed = true;
Cary Clark8032b982017-07-28 11:04:54 -0400471 return result;
472 }
473 }
474 }
475 add_ref(leadingSpaces, ref, &result);
476 continue;
477 }
478 auto topicIter = fBmhParser.fTopicMap.find(ref);
479 if (topicIter != fBmhParser.fTopicMap.end()) {
Cary Clark2dc84ad2018-01-26 12:56:22 -0500480 result += linkRef(leadingSpaces, topicIter->second, ref, resolvable);
Cary Clark8032b982017-07-28 11:04:54 -0400481 continue;
482 }
483 bool startsSentence = t.sentenceEnd(start);
484 if (!t.eof() && ' ' != t.peek()) {
Cary Clark2dc84ad2018-01-26 12:56:22 -0500485 add_ref(leadingSpaces, ref, &result);
Cary Clark8032b982017-07-28 11:04:54 -0400486 continue;
487 }
488 if (t.fChar + 1 >= t.fEnd || (!isupper(t.fChar[1]) && startsSentence)) {
Cary Clark2dc84ad2018-01-26 12:56:22 -0500489 add_ref(leadingSpaces, ref, &result);
Cary Clark8032b982017-07-28 11:04:54 -0400490 continue;
491 }
492 if (isupper(t.fChar[1]) && startsSentence) {
493 TextParser next(t.fFileName, &t.fChar[1], t.fEnd, t.fLineCount);
494 string nextWord(next.fChar, next.wordEnd() - next.fChar);
Cary Clark186d08f2018-04-03 08:43:27 -0400495 if (this->isDefined(t, nextWord, BmhParser::Resolvable::kYes)) {
Cary Clark2dc84ad2018-01-26 12:56:22 -0500496 add_ref(leadingSpaces, ref, &result);
Cary Clark8032b982017-07-28 11:04:54 -0400497 continue;
498 }
499 }
Cary Clark682c58d2018-05-16 07:07:07 -0400500 Definition* def = this->checkParentsForMatch(fSubtopic, ref);
501 if (!def) {
502 def = this->checkParentsForMatch(fRoot, ref);
503 }
504 if (def) {
505 result += this->linkRef(leadingSpaces, def, ref, resolvable);
506 continue;
507 }
508 if (BmhParser::Resolvable::kOut != resolvable &&
509 BmhParser::Resolvable::kFormula != resolvable) {
510 t.reportError("undefined reference");
511 fAddRefFailed = true;
512 } else {
513 add_ref(leadingSpaces, ref, &result);
Cary Clark8032b982017-07-28 11:04:54 -0400514 }
515 } while (!t.eof());
516 return result;
517}
518
Cary Clark186d08f2018-04-03 08:43:27 -0400519bool MdOut::buildReferences(const IncludeParser& includeParser, const char* docDir,
520 const char* mdFileOrPath) {
Cary Clark7cfcbca2018-01-04 16:11:51 -0500521 if (!sk_isdir(mdFileOrPath)) {
Cary Clark186d08f2018-04-03 08:43:27 -0400522 SkDebugf("must pass directory %s\n", mdFileOrPath);
523 SkDebugf("pass -i SkXXX.h to build references for a single include\n");
524 return false;
525 }
526 SkOSFile::Iter it(docDir, ".bmh");
527 for (SkString file; it.next(&file); ) {
528 if (!includeParser.references(file)) {
529 continue;
Cary Clark8032b982017-07-28 11:04:54 -0400530 }
Cary Clark186d08f2018-04-03 08:43:27 -0400531 SkString p = SkOSPath::Join(docDir, file.c_str());
532 if (!this->buildRefFromFile(p.c_str(), mdFileOrPath)) {
533 SkDebugf("failed to parse %s\n", p.c_str());
534 return false;
Cary Clark8032b982017-07-28 11:04:54 -0400535 }
536 }
537 return true;
538}
539
Cary Clark2f466242017-12-11 16:03:17 -0500540bool MdOut::buildStatus(const char* statusFile, const char* outDir) {
541 StatusIter iter(statusFile, ".bmh", StatusFilter::kInProgress);
542 for (string file; iter.next(&file); ) {
543 SkString p = SkOSPath::Join(iter.baseDir().c_str(), file.c_str());
544 const char* hunk = p.c_str();
545 if (!this->buildRefFromFile(hunk, outDir)) {
546 SkDebugf("failed to parse %s\n", hunk);
547 return false;
548 }
549 }
550 return true;
551}
552
Cary Clark8032b982017-07-28 11:04:54 -0400553bool MdOut::buildRefFromFile(const char* name, const char* outDir) {
Cary Clarkf895a422018-02-27 09:54:21 -0500554 if (!SkStrEndsWith(name, ".bmh")) {
555 return true;
556 }
557 if (SkStrEndsWith(name, "markup.bmh")) { // don't look inside this for now
558 return true;
559 }
560 if (SkStrEndsWith(name, "illustrations.bmh")) { // don't look inside this for now
561 return true;
562 }
Cary Clark8032b982017-07-28 11:04:54 -0400563 fFileName = string(name);
564 string filename(name);
565 if (filename.substr(filename.length() - 4) == ".bmh") {
566 filename = filename.substr(0, filename.length() - 4);
567 }
568 size_t start = filename.length();
569 while (start > 0 && (isalnum(filename[start - 1]) || '_' == filename[start - 1])) {
570 --start;
571 }
572 string match = filename.substr(start);
573 string header = match;
Cary Clark0edfbb72017-07-28 15:27:45 -0400574 filename = match + ".md";
Cary Clark8032b982017-07-28 11:04:54 -0400575 match += ".bmh";
576 fOut = nullptr;
Cary Clarkd0530ba2017-09-14 11:25:39 -0400577 string fullName;
Cary Clark2f466242017-12-11 16:03:17 -0500578
579 vector<string> keys;
580 keys.reserve(fBmhParser.fTopicMap.size());
581 for (const auto& it : fBmhParser.fTopicMap) {
582 keys.push_back(it.first);
583 }
584 std::sort(keys.begin(), keys.end());
585 for (auto key : keys) {
586 string s(key);
587 auto topicDef = fBmhParser.fTopicMap.at(s);
Cary Clark8032b982017-07-28 11:04:54 -0400588 if (topicDef->fParent) {
589 continue;
590 }
Cary Clark682c58d2018-05-16 07:07:07 -0400591 if (string::npos == topicDef->fFileName.rfind(match)) {
Cary Clark8032b982017-07-28 11:04:54 -0400592 continue;
593 }
594 if (!fOut) {
Cary Clarkd0530ba2017-09-14 11:25:39 -0400595 fullName = outDir;
Cary Clark8032b982017-07-28 11:04:54 -0400596 if ('/' != fullName.back()) {
597 fullName += '/';
598 }
599 fullName += filename;
Cary Clark7cfcbca2018-01-04 16:11:51 -0500600 fOut = fopen(filename.c_str(), "wb");
Cary Clark8032b982017-07-28 11:04:54 -0400601 if (!fOut) {
602 SkDebugf("could not open output file %s\n", fullName.c_str());
603 return false;
604 }
Cary Clark682c58d2018-05-16 07:07:07 -0400605 if (false) { // try inlining the style
606 FPRINTF("%s", kConstTableStyle);
607 }
Ben Wagner29380bd2017-10-09 14:43:00 -0400608 size_t underscorePos = header.find('_');
609 if (string::npos != underscorePos) {
Cary Clark0edfbb72017-07-28 15:27:45 -0400610 header.replace(underscorePos, 1, " ");
611 }
612 SkASSERT(string::npos == header.find('_'));
Cary Clark7cfcbca2018-01-04 16:11:51 -0500613 FPRINTF("%s", header.c_str());
Cary Clark8032b982017-07-28 11:04:54 -0400614 this->lfAlways(1);
Cary Clark7cfcbca2018-01-04 16:11:51 -0500615 FPRINTF("===");
Cary Clark8032b982017-07-28 11:04:54 -0400616 }
Cary Clark682c58d2018-05-16 07:07:07 -0400617 const Definition* prior = nullptr;
618 this->markTypeOut(topicDef, &prior);
Cary Clark8032b982017-07-28 11:04:54 -0400619 }
620 if (fOut) {
621 this->writePending();
622 fclose(fOut);
Cary Clark7cfcbca2018-01-04 16:11:51 -0500623 fflush(fOut);
624 if (this->writtenFileDiffers(filename, fullName)) {
625 fOut = fopen(fullName.c_str(), "wb");
626 int writtenSize;
627 const char* written = ReadToBuffer(filename, &writtenSize);
628 fwrite(written, 1, writtenSize, fOut);
629 fclose(fOut);
630 fflush(fOut);
631 SkDebugf("wrote updated %s\n", fullName.c_str());
632 }
633 remove(filename.c_str());
Cary Clark8032b982017-07-28 11:04:54 -0400634 fOut = nullptr;
635 }
Cary Clark566414e2018-03-12 08:26:45 -0400636 return !fAddRefFailed;
Cary Clark8032b982017-07-28 11:04:54 -0400637}
638
Cary Clark682c58d2018-05-16 07:07:07 -0400639static bool contains_referenced_child(const Definition* found, const vector<string>& refs) {
640 for (auto child : found->fChildren) {
641 if (refs.end() != std::find_if(refs.begin(), refs.end(),
642 [child](string def) { return child->fName == def; } )) {
643 return true;
644 }
645 if (contains_referenced_child(child, refs)) {
646 return true;
647 }
648 }
649 return false;
650}
651
652void MdOut::checkAnchors() {
653 int missing = 0;
654 for (auto bmhFile : fAllAnchorRefs) {
655 auto defIter = fAllAnchorDefs.find(bmhFile.first);
656 SkASSERT(fAllAnchorDefs.end() != defIter);
657 vector<AnchorDef>& allDefs = defIter->second;
658 std::sort(allDefs.begin(), allDefs.end(),
659 [](const AnchorDef& a, const AnchorDef& b) { return a.fDef < b.fDef; } );
660 std::sort(bmhFile.second.begin(), bmhFile.second.end());
661 auto allDefsIter = allDefs.begin();
662 auto allRefsIter = bmhFile.second.begin();
663 for (;;) {
664 bool allDefsEnded = allDefsIter == allDefs.end();
665 bool allRefsEnded = allRefsIter == bmhFile.second.end();
666 if (allDefsEnded && allRefsEnded) {
667 break;
668 }
669 if (allRefsEnded || (!allDefsEnded && allDefsIter->fDef < *allRefsIter)) {
670 if (MarkType::kParam != allDefsIter->fMarkType
671 && !IncompleteAllowed(allDefsIter->fMarkType)) {
672 // If undocumented but parent or child is referred to: good enough for now
673 bool goodEnough = false;
674 if ("undocumented" == defIter->first) {
675 auto iter = fBmhParser.fTopicMap.find(allDefsIter->fDef);
676 if (fBmhParser.fTopicMap.end() != iter) {
677 const Definition* found = iter->second;
678 if (string::npos != found->fFileName.find("undocumented")) {
679 const Definition* parent = found;
680 while ((parent = parent->fParent)) {
681 if (bmhFile.second.end() != std::find_if(bmhFile.second.begin(),
682 bmhFile.second.end(),
683 [parent](string def) {
684 return parent->fName == def; } )) {
685 goodEnough = true;
686 break;
687 }
688 }
689 if (!goodEnough) {
690 goodEnough = contains_referenced_child(found, bmhFile.second);
691 }
692 }
693 }
694 }
695 if (!goodEnough) {
696 SkDebugf("missing ref %s %s\n", defIter->first.c_str(),
697 allDefsIter->fDef.c_str());
698 missing++;
699 }
700 }
701 allDefsIter++;
702 } else if (allDefsEnded || (!allRefsEnded && allDefsIter->fDef > *allRefsIter)) {
703 if (fBmhParser.fExternals.end() == std::find_if(fBmhParser.fExternals.begin(),
704 fBmhParser.fExternals.end(), [allRefsIter](const RootDefinition& root) {
705 return *allRefsIter != root.fName; } )) {
706 SkDebugf("missing def %s %s\n", bmhFile.first.c_str(), allRefsIter->c_str());
707 missing++;
708 }
709 allRefsIter++;
710 } else {
711 SkASSERT(!allDefsEnded);
712 SkASSERT(!allRefsEnded);
713 SkASSERT(allDefsIter->fDef == *allRefsIter);
714 allDefsIter++;
715 allRefsIter++;
716 }
717 if (missing >= 10) {
718 missing = 0;
719 }
720 }
721 }
722}
723
Cary Clark566414e2018-03-12 08:26:45 -0400724bool MdOut::checkParamReturnBody(const Definition* def) {
Cary Clarka523d2d2017-08-30 08:58:10 -0400725 TextParser paramBody(def);
726 const char* descriptionStart = paramBody.fChar;
Cary Clark154beea2017-10-26 07:58:48 -0400727 if (!islower(descriptionStart[0]) && !isdigit(descriptionStart[0])) {
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400728 paramBody.skipToNonName();
Cary Clarka523d2d2017-08-30 08:58:10 -0400729 string ref = string(descriptionStart, paramBody.fChar - descriptionStart);
Cary Clark186d08f2018-04-03 08:43:27 -0400730 if (!this->isDefined(paramBody, ref, BmhParser::Resolvable::kYes)) {
Cary Clarka523d2d2017-08-30 08:58:10 -0400731 string errorStr = MarkType::kReturn == def->fMarkType ? "return" : "param";
732 errorStr += " description must start with lower case";
733 paramBody.reportError(errorStr.c_str());
Cary Clark566414e2018-03-12 08:26:45 -0400734 fAddRefFailed = true;
Cary Clarka523d2d2017-08-30 08:58:10 -0400735 return false;
736 }
737 }
738 if ('.' == paramBody.fEnd[-1]) {
739 paramBody.reportError("make param description a phrase; should not end with period");
Cary Clark566414e2018-03-12 08:26:45 -0400740 fAddRefFailed = true;
Cary Clarka523d2d2017-08-30 08:58:10 -0400741 return false;
742 }
743 return true;
744}
745
Cary Clark682c58d2018-05-16 07:07:07 -0400746void MdOut::childrenOut(Definition* def, const char* start) {
Cary Clark8032b982017-07-28 11:04:54 -0400747 const char* end;
748 fLineCount = def->fLineCount;
Cary Clark682c58d2018-05-16 07:07:07 -0400749 if (MarkType::kEnumClass == def->fMarkType) {
Cary Clarkbad5ad72017-08-03 17:14:08 -0400750 fEnumClass = def;
Cary Clark8032b982017-07-28 11:04:54 -0400751 }
Cary Clark154beea2017-10-26 07:58:48 -0400752 BmhParser::Resolvable resolvable = this->resolvable(def);
Cary Clark682c58d2018-05-16 07:07:07 -0400753 const Definition* prior = nullptr;
Cary Clark8032b982017-07-28 11:04:54 -0400754 for (auto& child : def->fChildren) {
Cary Clark682c58d2018-05-16 07:07:07 -0400755 if (MarkType::kPhraseParam == child->fMarkType) {
756 continue;
757 }
Cary Clark8032b982017-07-28 11:04:54 -0400758 end = child->fStart;
759 if (BmhParser::Resolvable::kNo != resolvable) {
760 this->resolveOut(start, end, resolvable);
761 }
Cary Clark682c58d2018-05-16 07:07:07 -0400762 this->markTypeOut(child, &prior);
Cary Clark8032b982017-07-28 11:04:54 -0400763 start = child->fTerminator;
764 }
765 if (BmhParser::Resolvable::kNo != resolvable) {
766 end = def->fContentEnd;
767 this->resolveOut(start, end, resolvable);
768 }
Cary Clarkbad5ad72017-08-03 17:14:08 -0400769 if (MarkType::kEnumClass == def->fMarkType) {
770 fEnumClass = nullptr;
771 }
Cary Clark8032b982017-07-28 11:04:54 -0400772}
773
Cary Clark682c58d2018-05-16 07:07:07 -0400774// output header for subtopic for all consts: name, value, short descriptions (#Line)
775// output link to in context #Const with moderate description
776void MdOut::summaryOut(const Definition* def, MarkType markType, string name) {
777 this->writePending();
778 SkASSERT(TableState::kNone == fTableState);
779 this->mdHeaderOut(3);
780 FPRINTF("%s", name.c_str());
781 this->lfAlways(2);
782 FPRINTF("%s", kTableDeclaration); // <table> with style info
783 this->lfAlways(1);
784 FPRINTF("%s", MarkType::kConst == markType ? kAllConstTableHeader : kAllMemberTableHeader);
785 this->lfAlways(1);
786 bool odd = true;
787 for (auto child : def->fChildren) {
788 if (markType != child->fMarkType) {
789 continue;
790 }
791 auto oneLiner = std::find_if(child->fChildren.begin(), child->fChildren.end(),
792 [](const Definition* test){ return MarkType::kLine == test->fMarkType; } );
793 if (child->fChildren.end() == oneLiner) {
794 child->reportError<void>("missing #Line");
795 continue;
796 }
797 FPRINTF("%s", odd ? kTR_Dark.c_str() : " <tr>");
798 this->lfAlways(1);
799 if (MarkType::kConst == markType) {
Cary Clarkffb3d682018-05-17 12:17:28 -0400800 FPRINTF("%s", tableDataCodeRef(child).c_str());
Cary Clark682c58d2018-05-16 07:07:07 -0400801 this->lfAlways(1);
Cary Clarkffb3d682018-05-17 12:17:28 -0400802 FPRINTF("%s", table_data_const(child, nullptr).c_str());
Cary Clark682c58d2018-05-16 07:07:07 -0400803 } else {
804 string memberType;
805 string memberName = this->getMemberTypeName(child, &memberType);
806 SkASSERT(MarkType::kMember == markType);
807 FPRINTF("%s", out_table_data_description(memberType).c_str());
808 this->lfAlways(1);
809 FPRINTF("%s", tableDataCodeLocalRef(memberName).c_str());
810 }
811 this->lfAlways(1);
812 FPRINTF("%s", out_table_data_description(*oneLiner).c_str());
813 this->lfAlways(1);
814 FPRINTF("%s", " </tr>");
815 this->lfAlways(1);
816 odd = !odd;
817 }
818 FPRINTF("</table>");
819 this->lfAlways(1);
820}
821
822Definition* MdOut::csParent() {
823 if (!fRoot) {
824 return nullptr;
825 }
826 Definition* csParent = fRoot->csParent();
Cary Clark08895c42018-02-01 09:37:32 -0500827 if (!csParent) {
828 const Definition* topic = fRoot;
829 while (topic && MarkType::kTopic != topic->fMarkType) {
830 topic = topic->fParent;
831 }
832 for (auto child : topic->fChildren) {
833 if (child->isStructOrClass() || MarkType::kTypedef == child->fMarkType) {
834 csParent = child;
835 break;
836 }
837 }
Cary Clark224c7002018-06-27 11:00:21 -0400838 SkASSERT(csParent || string::npos == fRoot->fFileName.find("Sk")
839 || string::npos != fRoot->fFileName.find("SkBlendMode_Reference.bmh"));
Cary Clark08895c42018-02-01 09:37:32 -0500840 }
841 return csParent;
842}
843
Cary Clarkd0530ba2017-09-14 11:25:39 -0400844const Definition* MdOut::findParamType() {
845 SkASSERT(fMethod);
846 TextParser parser(fMethod->fFileName, fMethod->fStart, fMethod->fContentStart,
847 fMethod->fLineCount);
848 string lastFull;
849 do {
850 parser.skipToAlpha();
851 if (parser.eof()) {
852 return nullptr;
853 }
854 const char* word = parser.fChar;
855 parser.skipFullName();
856 SkASSERT(!parser.eof());
857 string name = string(word, parser.fChar - word);
858 if (fLastParam->fName == name) {
Cary Clark186d08f2018-04-03 08:43:27 -0400859 const Definition* paramType = this->isDefined(parser, lastFull,
860 BmhParser::Resolvable::kOut);
Cary Clarkd0530ba2017-09-14 11:25:39 -0400861 return paramType;
862 }
863 if (isupper(name[0])) {
Ben Wagner63fd7602017-10-09 15:45:33 -0400864 lastFull = name;
Cary Clarkd0530ba2017-09-14 11:25:39 -0400865 }
866 } while (true);
867 return nullptr;
868}
869
Cary Clark682c58d2018-05-16 07:07:07 -0400870string MdOut::getMemberTypeName(const Definition* def, string* memberType) {
871 TextParser parser(def->fFileName, def->fStart, def->fContentStart,
872 def->fLineCount);
873 parser.skipExact("#Member");
874 parser.skipWhiteSpace();
875 const char* typeStart = parser.fChar;
876 const char* typeEnd = nullptr;
877 const char* nameStart = nullptr;
878 const char* nameEnd = nullptr;
879 do {
880 parser.skipToWhiteSpace();
881 if (nameStart) {
882 nameEnd = parser.fChar;
883 }
884 if (parser.eof()) {
885 break;
886 }
887 const char* spaceLoc = parser.fChar;
888 if (parser.skipWhiteSpace()) {
889 typeEnd = spaceLoc;
890 nameStart = parser.fChar;
891 }
892 } while (!parser.eof());
893 SkASSERT(typeEnd);
894 *memberType = string(typeStart, (int) (typeEnd - typeStart));
895 replace_all(*memberType, " ", "&nbsp;");
896 SkASSERT(nameStart);
897 SkASSERT(nameEnd);
898 return string(nameStart, (int) (nameEnd - nameStart));
899}
900
901bool MdOut::HasDetails(const Definition* def) {
902 for (auto child : def->fChildren) {
903 if (MarkType::kDetails == child->fMarkType) {
904 return true;
905 }
906 if (MdOut::HasDetails(child)) {
907 return true;
908 }
909 }
910 return false;
911}
912
913void MdOut::htmlOut(string s) {
914 SkASSERT(string::npos != s.find('<'));
915 FPRINTF("%s", s.c_str());
916}
917
918const Definition* MdOut::isDefinedByParent(RootDefinition* root, string ref) {
919 if (ref == root->fName) {
920 return root;
921 }
922 if (const Definition* definition = root->find(ref, RootDefinition::AllowParens::kYes)) {
923 return definition;
924 }
925 Definition* test = root;
926 bool isSubtopic = MarkType::kSubtopic == root->fMarkType
927 || MarkType::kTopic == root->fMarkType;
928 do {
929 if (!test->isRoot()) {
930 continue;
931 }
932 bool testIsSubtopic = MarkType::kSubtopic == test->fMarkType
933 || MarkType::kTopic == test->fMarkType;
934 if (isSubtopic != testIsSubtopic) {
935 continue;
936 }
937 RootDefinition* root = test->asRoot();
938 for (auto& leaf : root->fBranches) {
939 if (ref == leaf.first) {
940 return leaf.second;
941 }
942 const Definition* definition = leaf.second->find(ref,
943 RootDefinition::AllowParens::kYes);
944 if (definition) {
945 return definition;
946 }
947 }
948 string prefix = isSubtopic ? "_" : "::";
949 string prefixed = root->fName + prefix + ref;
950 if (Definition* definition = root->find(prefixed, RootDefinition::AllowParens::kYes)) {
951 return definition;
952 }
953 if (isSubtopic && isupper(prefixed[0])) {
954 auto topicIter = fBmhParser.fTopicMap.find(prefixed);
955 if (topicIter != fBmhParser.fTopicMap.end()) {
956 return topicIter->second;
957 }
958 }
959 if (isSubtopic) {
960 string fiddlePrefixed = root->fFiddle + "_" + ref;
961 auto topicIter = fBmhParser.fTopicMap.find(fiddlePrefixed);
962 if (topicIter != fBmhParser.fTopicMap.end()) {
963 return topicIter->second;
964 }
965 }
966 } while ((test = test->fParent));
967 return nullptr;
968}
969
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400970const Definition* MdOut::isDefined(const TextParser& parser, string ref,
Cary Clark186d08f2018-04-03 08:43:27 -0400971 BmhParser::Resolvable resolvable) {
Cary Clark8032b982017-07-28 11:04:54 -0400972 auto rootIter = fBmhParser.fClassMap.find(ref);
973 if (rootIter != fBmhParser.fClassMap.end()) {
974 return &rootIter->second;
975 }
976 auto typedefIter = fBmhParser.fTypedefMap.find(ref);
977 if (typedefIter != fBmhParser.fTypedefMap.end()) {
978 return &typedefIter->second;
979 }
980 auto enumIter = fBmhParser.fEnumMap.find(ref);
981 if (enumIter != fBmhParser.fEnumMap.end()) {
982 return &enumIter->second;
983 }
984 auto constIter = fBmhParser.fConstMap.find(ref);
985 if (constIter != fBmhParser.fConstMap.end()) {
986 return &constIter->second;
987 }
988 auto methodIter = fBmhParser.fMethodMap.find(ref);
989 if (methodIter != fBmhParser.fMethodMap.end()) {
990 return &methodIter->second;
991 }
992 auto aliasIter = fBmhParser.fAliasMap.find(ref);
993 if (aliasIter != fBmhParser.fAliasMap.end()) {
994 return aliasIter->second;
995 }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400996 auto defineIter = fBmhParser.fDefineMap.find(ref);
997 if (defineIter != fBmhParser.fDefineMap.end()) {
998 return &defineIter->second;
999 }
Cary Clark8032b982017-07-28 11:04:54 -04001000 for (const auto& external : fBmhParser.fExternals) {
1001 if (external.fName == ref) {
1002 return &external;
1003 }
1004 }
Cary Clark682c58d2018-05-16 07:07:07 -04001005 if (const Definition* definition = this->isDefinedByParent(fRoot, ref)) {
1006 return definition;
1007 }
1008 if (const Definition* definition = this->isDefinedByParent(fSubtopic, ref)) {
1009 return definition;
Cary Clark8032b982017-07-28 11:04:54 -04001010 }
1011 size_t doubleColon = ref.find("::");
1012 if (string::npos != doubleColon) {
1013 string className = ref.substr(0, doubleColon);
1014 auto classIter = fBmhParser.fClassMap.find(className);
1015 if (classIter != fBmhParser.fClassMap.end()) {
Cary Clark682c58d2018-05-16 07:07:07 -04001016 RootDefinition& classDef = classIter->second;
Cary Clarkce101242017-09-01 15:51:02 -04001017 const Definition* result = classDef.find(ref, RootDefinition::AllowParens::kYes);
Cary Clark8032b982017-07-28 11:04:54 -04001018 if (result) {
1019 return result;
1020 }
1021 }
1022
1023 }
1024 if (!ref.compare(0, 2, "SK") || !ref.compare(0, 3, "sk_")
1025 || (('k' == ref[0] || 'g' == ref[0] || 'f' == ref[0]) &&
1026 ref.length() > 1 && isupper(ref[1]))) {
1027 // try with a prefix
1028 if ('k' == ref[0]) {
Cary Clark682c58d2018-05-16 07:07:07 -04001029 for (auto& iter : fBmhParser.fEnumMap) {
Cary Clark2a8c48b2018-02-15 17:31:24 -05001030 auto def = iter.second.find(ref, RootDefinition::AllowParens::kYes);
1031 if (def) {
1032 return def;
Cary Clark8032b982017-07-28 11:04:54 -04001033 }
1034 }
Cary Clarkbad5ad72017-08-03 17:14:08 -04001035 if (fEnumClass) {
1036 string fullName = fEnumClass->fName + "::" + ref;
1037 for (auto child : fEnumClass->fChildren) {
1038 if (fullName == child->fName) {
1039 return child;
1040 }
1041 }
1042 }
1043 if (string::npos != ref.find("_Private")) {
1044 return nullptr;
1045 }
Cary Clark8032b982017-07-28 11:04:54 -04001046 }
1047 if ('f' == ref[0]) {
1048 // FIXME : find def associated with prior, e.g.: r.fX where 'SkPoint r' was earlier
1049 // need to have pushed last resolve on stack to do this
1050 // for now, just try to make sure that it's there and error if not
1051 if ('.' != parser.backup(ref.c_str())) {
1052 parser.reportError("fX member undefined");
Cary Clark566414e2018-03-12 08:26:45 -04001053 fAddRefFailed = true;
Cary Clark8032b982017-07-28 11:04:54 -04001054 return nullptr;
1055 }
1056 } else {
Cary Clark186d08f2018-04-03 08:43:27 -04001057 if (BmhParser::Resolvable::kOut != resolvable &&
1058 BmhParser::Resolvable::kFormula != resolvable) {
Cary Clark8032b982017-07-28 11:04:54 -04001059 parser.reportError("SK undefined");
Cary Clark566414e2018-03-12 08:26:45 -04001060 fAddRefFailed = true;
Cary Clark8032b982017-07-28 11:04:54 -04001061 }
1062 return nullptr;
1063 }
1064 }
1065 if (isupper(ref[0])) {
1066 auto topicIter = fBmhParser.fTopicMap.find(ref);
1067 if (topicIter != fBmhParser.fTopicMap.end()) {
1068 return topicIter->second;
1069 }
1070 size_t pos = ref.find('_');
1071 if (string::npos != pos) {
1072 // see if it is defined by another base class
1073 string className(ref, 0, pos);
1074 auto classIter = fBmhParser.fClassMap.find(className);
1075 if (classIter != fBmhParser.fClassMap.end()) {
Cary Clarkce101242017-09-01 15:51:02 -04001076 if (const Definition* definition = classIter->second.find(ref,
1077 RootDefinition::AllowParens::kYes)) {
Cary Clark8032b982017-07-28 11:04:54 -04001078 return definition;
1079 }
1080 }
1081 auto enumIter = fBmhParser.fEnumMap.find(className);
1082 if (enumIter != fBmhParser.fEnumMap.end()) {
Cary Clarkce101242017-09-01 15:51:02 -04001083 if (const Definition* definition = enumIter->second.find(ref,
1084 RootDefinition::AllowParens::kYes)) {
Cary Clark8032b982017-07-28 11:04:54 -04001085 return definition;
1086 }
1087 }
Cary Clark186d08f2018-04-03 08:43:27 -04001088 if (BmhParser::Resolvable::kOut != resolvable &&
1089 BmhParser::Resolvable::kFormula != resolvable) {
Cary Clark8032b982017-07-28 11:04:54 -04001090 parser.reportError("_ undefined");
Cary Clark566414e2018-03-12 08:26:45 -04001091 fAddRefFailed = true;
Cary Clark8032b982017-07-28 11:04:54 -04001092 }
1093 return nullptr;
1094 }
1095 }
1096 return nullptr;
1097}
1098
1099string MdOut::linkName(const Definition* ref) const {
1100 string result = ref->fName;
1101 size_t under = result.find('_');
1102 if (string::npos != under) {
1103 string classPart = result.substr(0, under);
1104 string namePart = result.substr(under + 1, result.length());
1105 if (fRoot && (fRoot->fName == classPart
1106 || (fRoot->fParent && fRoot->fParent->fName == classPart))) {
1107 result = namePart;
1108 }
1109 }
Cary Clark682c58d2018-05-16 07:07:07 -04001110 replace_all(result, "::", "_");
Cary Clark8032b982017-07-28 11:04:54 -04001111 return result;
1112}
1113
1114// for now, hard-code to html links
1115// def should not include SkXXX_
Cary Clark2d4bf5f2018-04-16 08:37:38 -04001116string MdOut::linkRef(string leadingSpaces, const Definition* def,
Cary Clark682c58d2018-05-16 07:07:07 -04001117 string ref, BmhParser::Resolvable resolvable) {
Cary Clark2a8c48b2018-02-15 17:31:24 -05001118 string buildup;
1119 string refName;
Cary Clark8032b982017-07-28 11:04:54 -04001120 const string* str = &def->fFiddle;
1121 SkASSERT(str->length() > 0);
Cary Clark2a8c48b2018-02-15 17:31:24 -05001122 string classPart = *str;
1123 bool globalEnumMember = false;
1124 if (MarkType::kAlias == def->fMarkType) {
1125 def = def->fParent;
1126 SkASSERT(def);
Cary Clark682c58d2018-05-16 07:07:07 -04001127 SkASSERT(MarkType::kSubtopic == def->fMarkType
1128 || MarkType::kTopic == def->fMarkType
1129 || MarkType::kConst == def->fMarkType);
Cary Clark8032b982017-07-28 11:04:54 -04001130 }
Cary Clark2a8c48b2018-02-15 17:31:24 -05001131 if (MarkType::kSubtopic == def->fMarkType) {
1132 const Definition* topic = def->topicParent();
1133 SkASSERT(topic);
1134 classPart = topic->fName;
1135 refName = def->fName;
1136 } else if (MarkType::kTopic == def->fMarkType) {
1137 refName = def->fName;
1138 } else {
1139 if ('k' == (*str)[0] && string::npos != str->find("_Sk")) {
1140 globalEnumMember = true;
1141 } else {
1142 SkASSERT("Sk" == str->substr(0, 2) || "SK" == str->substr(0, 2)
1143 // FIXME: kitchen sink catch below, need to do better
1144 || string::npos != def->fFileName.find("undocumented"));
1145 size_t under = str->find('_');
1146 classPart = string::npos != under ? str->substr(0, under) : *str;
Cary Clark8032b982017-07-28 11:04:54 -04001147 }
Cary Clark2a8c48b2018-02-15 17:31:24 -05001148 refName = def->fFiddle;
1149 }
1150 bool classMatch = fRoot->fFileName == def->fFileName;
Cary Clark8032b982017-07-28 11:04:54 -04001151 SkASSERT(fRoot);
1152 SkASSERT(fRoot->fFileName.length());
Cary Clark2a8c48b2018-02-15 17:31:24 -05001153 if (!classMatch) {
1154 string filename = def->fFileName;
Cary Clark8032b982017-07-28 11:04:54 -04001155 if (filename.substr(filename.length() - 4) == ".bmh") {
1156 filename = filename.substr(0, filename.length() - 4);
1157 }
1158 size_t start = filename.length();
1159 while (start > 0 && (isalnum(filename[start - 1]) || '_' == filename[start - 1])) {
1160 --start;
1161 }
Cary Clark2a8c48b2018-02-15 17:31:24 -05001162 buildup = filename.substr(start);
Cary Clark8cc16c72017-08-25 11:51:49 -04001163 }
Cary Clark2a8c48b2018-02-15 17:31:24 -05001164 buildup += "#" + refName;
Cary Clark8cc16c72017-08-25 11:51:49 -04001165 if (MarkType::kParam == def->fMarkType) {
1166 const Definition* parent = def->fParent;
1167 SkASSERT(MarkType::kMethod == parent->fMarkType);
1168 buildup = '#' + parent->fFiddle + '_' + ref;
Cary Clark8032b982017-07-28 11:04:54 -04001169 }
1170 string refOut(ref);
Cary Clark2a8c48b2018-02-15 17:31:24 -05001171 if (!globalEnumMember) {
1172 std::replace(refOut.begin(), refOut.end(), '_', ' ');
1173 }
Cary Clark8032b982017-07-28 11:04:54 -04001174 if (ref.length() > 2 && islower(ref[0]) && "()" == ref.substr(ref.length() - 2)) {
1175 refOut = refOut.substr(0, refOut.length() - 2);
1176 }
Cary Clark682c58d2018-05-16 07:07:07 -04001177 string result = leadingSpaces + this->anchorRef(buildup, refOut);
Cary Clark2dc84ad2018-01-26 12:56:22 -05001178 if (BmhParser::Resolvable::kClone == resolvable && MarkType::kMethod == def->fMarkType &&
1179 def->fCloned && !def->fClone) {
1180 bool found = false;
1181 string match = def->fName;
1182 if ("()" == match.substr(match.length() - 2)) {
1183 match = match.substr(0, match.length() - 2);
1184 }
1185 match += '_';
1186 auto classIter = fBmhParser.fClassMap.find(classPart);
1187 if (fBmhParser.fClassMap.end() != classIter) {
1188 for (char num = '2'; num <= '9'; ++num) {
1189 string clone = match + num;
1190 const auto& leafIter = classIter->second.fLeaves.find(clone);
1191 if (leafIter != classIter->second.fLeaves.end()) {
Cary Clark682c58d2018-05-16 07:07:07 -04001192 result += "<sup>" + this->anchorRef(buildup + "_" + num,
1193 string("[") + num + "]") + "</sup>";
Cary Clark2dc84ad2018-01-26 12:56:22 -05001194 found = true;
1195 }
1196 }
Cary Clark2dc84ad2018-01-26 12:56:22 -05001197 }
1198 if (!found) {
Cary Clarkab2621d2018-01-30 10:08:57 -05001199 SkDebugf(""); // convenient place to set a breakpoint
Cary Clark2dc84ad2018-01-26 12:56:22 -05001200 }
1201 }
1202 return result;
Cary Clark8032b982017-07-28 11:04:54 -04001203}
1204
Cary Clark682c58d2018-05-16 07:07:07 -04001205static bool writeTableEnd(MarkType markType, Definition* def, const Definition** prior) {
1206 return markType != def->fMarkType && *prior && markType == (*prior)->fMarkType;
1207}
1208
1209void MdOut::markTypeOut(Definition* def, const Definition** prior) {
Cary Clark8032b982017-07-28 11:04:54 -04001210 string printable = def->printableName();
1211 const char* textStart = def->fContentStart;
Cary Clark682c58d2018-05-16 07:07:07 -04001212 bool lookForOneLiner = false;
1213 // #Param and #Const don't have markers to say when the last is seen, so detect that by looking
1214 // for a change in type.
Cary Clark682c58d2018-05-16 07:07:07 -04001215 if (writeTableEnd(MarkType::kParam, def, prior) || writeTableEnd(MarkType::kConst, def, prior)
1216 || writeTableEnd(MarkType::kMember, def, prior)) {
Cary Clark8032b982017-07-28 11:04:54 -04001217 this->writePending();
Cary Clark7cfcbca2018-01-04 16:11:51 -05001218 FPRINTF("</table>");
Cary Clark8032b982017-07-28 11:04:54 -04001219 this->lf(2);
1220 fTableState = TableState::kNone;
1221 }
Cary Clark682c58d2018-05-16 07:07:07 -04001222 fLastDef = def;
Cary Clark8032b982017-07-28 11:04:54 -04001223 switch (def->fMarkType) {
1224 case MarkType::kAlias:
1225 break;
Cary Clark7cfcbca2018-01-04 16:11:51 -05001226 case MarkType::kAnchor: {
1227 if (fColumn > 0) {
1228 this->writeSpace();
1229 }
1230 this->writePending();
1231 TextParser parser(def);
1232 const char* start = parser.fChar;
Cary Clark682c58d2018-05-16 07:07:07 -04001233 parser.skipToEndBracket((string(" ") + def->fMC + " ").c_str());
Cary Clark7cfcbca2018-01-04 16:11:51 -05001234 string anchorText(start, parser.fChar - start);
Cary Clark682c58d2018-05-16 07:07:07 -04001235 parser.skipExact((string(" ") + def->fMC + " ").c_str());
Cary Clark7cfcbca2018-01-04 16:11:51 -05001236 string anchorLink(parser.fChar, parser.fEnd - parser.fChar);
Cary Clark682c58d2018-05-16 07:07:07 -04001237 this->htmlOut(anchorRef(anchorLink, anchorText));
Cary Clark7cfcbca2018-01-04 16:11:51 -05001238 } break;
Cary Clark8032b982017-07-28 11:04:54 -04001239 case MarkType::kBug:
1240 break;
1241 case MarkType::kClass:
Cary Clark682c58d2018-05-16 07:07:07 -04001242 case MarkType::kStruct: {
1243 fRoot = def->asRoot();
Cary Clark8032b982017-07-28 11:04:54 -04001244 this->mdHeaderOut(1);
Cary Clark682c58d2018-05-16 07:07:07 -04001245 if (MarkType::kStruct == def->fMarkType) {
1246 this->htmlOut(anchorDef(def->fFiddle, "Struct " + def->fName));
1247 } else {
1248 this->htmlOut(anchorDef(this->linkName(def), "Class " + def->fName));
1249 }
Cary Clark8032b982017-07-28 11:04:54 -04001250 this->lf(1);
Cary Clark682c58d2018-05-16 07:07:07 -04001251 if (string::npos != fRoot->fFileName.find("undocumented")) {
1252 break;
1253 }
1254 // if class or struct contains constants, and doesn't contain subtopic kConstant, add it
1255 // and add a child populate
1256 const Definition* subtopic = def->subtopicParent();
1257 const Definition* topic = def->topicParent();
1258 for (auto item : SubtopicKeys::kGeneratedSubtopics) {
1259 string subname;
1260 if (subtopic != topic) {
1261 subname = subtopic->fName + '_';
1262 }
1263 subname += item;
1264 if (fRoot->populator(item).fMembers.size()
1265 && !std::any_of(fRoot->fChildren.begin(), fRoot->fChildren.end(),
1266 [subname](const Definition* child) {
1267 return MarkType::kSubtopic == child->fMarkType
1268 && subname == child->fName;
1269 } )) {
1270 // generate subtopic
1271 this->mdHeaderOut(2);
1272 this->htmlOut(anchorDef(subname, item));
1273 this->lf(2);
1274 // generate populate
1275 this->subtopicOut(item);
1276 }
1277 }
1278 } break;
Cary Clark8032b982017-07-28 11:04:54 -04001279 case MarkType::kCode:
1280 this->lfAlways(2);
Cary Clark7cfcbca2018-01-04 16:11:51 -05001281 FPRINTF("<pre style=\"padding: 1em 1em 1em 1em;"
Cary Clarka560c472017-11-27 10:44:06 -05001282 "width: 62.5em; background-color: #f0f0f0\">");
Cary Clark8032b982017-07-28 11:04:54 -04001283 this->lf(1);
Cary Clark186d08f2018-04-03 08:43:27 -04001284 fResolveAndIndent = true;
Cary Clark8032b982017-07-28 11:04:54 -04001285 break;
1286 case MarkType::kColumn:
1287 this->writePending();
1288 if (fInList) {
Cary Clark7cfcbca2018-01-04 16:11:51 -05001289 FPRINTF(" <td>");
Cary Clark8032b982017-07-28 11:04:54 -04001290 } else {
Cary Clark7cfcbca2018-01-04 16:11:51 -05001291 FPRINTF("| ");
Cary Clark8032b982017-07-28 11:04:54 -04001292 }
1293 break;
1294 case MarkType::kComment:
1295 break;
Cary Clark682c58d2018-05-16 07:07:07 -04001296 case MarkType::kMember:
Cary Clark8032b982017-07-28 11:04:54 -04001297 case MarkType::kConst: {
Cary Clark682c58d2018-05-16 07:07:07 -04001298 bool isConst = MarkType::kConst == def->fMarkType;
1299 lookForOneLiner = false;
1300 fWroteSomething = false;
1301 // output consts for one parent with moderate descriptions
1302 // optional link to subtopic with longer descriptions, examples
Cary Clark8032b982017-07-28 11:04:54 -04001303 if (TableState::kNone == fTableState) {
Cary Clark682c58d2018-05-16 07:07:07 -04001304 SkASSERT(!*prior || (isConst && MarkType::kConst != (*prior)->fMarkType)
1305 || (!isConst && MarkType::kMember != (*prior)->fMarkType));
Cary Clark8032b982017-07-28 11:04:54 -04001306 this->mdHeaderOut(3);
Cary Clark682c58d2018-05-16 07:07:07 -04001307 FPRINTF("%s", this->fPopulators[isConst ? SubtopicKeys::kConstants :
1308 SubtopicKeys::kMembers].fName.c_str());
1309 this->lfAlways(2);
1310 FPRINTF("%s", kTableDeclaration);
Cary Clark8032b982017-07-28 11:04:54 -04001311 fTableState = TableState::kRow;
Cary Clark682c58d2018-05-16 07:07:07 -04001312 fOddRow = true;
1313 this->lfAlways(1);
1314 // look ahead to see if the details column has data or not
1315 fHasDetails = MdOut::HasDetails(def->fParent);
1316 FPRINTF("%s", fHasDetails ? \
1317 (isConst ? kSubConstTableHeader : kSubMemberTableHeader) : \
1318 (isConst ? kAllConstTableHeader : kAllMemberTableHeader));
1319 this->lfAlways(1);
Cary Clark8032b982017-07-28 11:04:54 -04001320 }
1321 if (TableState::kRow == fTableState) {
1322 this->writePending();
Cary Clark682c58d2018-05-16 07:07:07 -04001323 FPRINTF("%s", fOddRow ? kTR_Dark.c_str() : " <tr>");
1324 fOddRow = !fOddRow;
1325 this->lfAlways(1);
Cary Clark8032b982017-07-28 11:04:54 -04001326 fTableState = TableState::kColumn;
1327 }
1328 this->writePending();
Cary Clark682c58d2018-05-16 07:07:07 -04001329 if (isConst) {
1330 // TODO: if fHasDetails is true, could defer def and issue a ref instead
1331 // unclear if this is a good idea or not
1332 FPRINTF("%s", this->tableDataCodeDef(def).c_str());
1333 this->lfAlways(1);
1334 FPRINTF("%s", table_data_const(def, &textStart).c_str());
1335 } else {
1336 string memberType;
1337 string memberName = this->getMemberTypeName(def, &memberType);
1338 FPRINTF("%s", out_table_data_description(memberType).c_str());
1339 this->lfAlways(1);
1340 FPRINTF("%s", tableDataCodeDef(def->fFiddle, memberName).c_str());
1341 }
1342 this->lfAlways(1);
1343 if (fHasDetails) {
1344 string details;
1345 auto subtopic = std::find_if(def->fChildren.begin(), def->fChildren.end(),
1346 [](const Definition* test){
1347 return MarkType::kDetails == test->fMarkType; } );
1348 if (def->fChildren.end() != subtopic) {
1349 string subtopicName = string((*subtopic)->fContentStart,
1350 (int) ((*subtopic)->fContentEnd - (*subtopic)->fContentStart));
1351 const Definition* parentSubtopic = def->subtopicParent();
1352 SkASSERT(parentSubtopic);
1353 string fullName = parentSubtopic->fFiddle + '_' + subtopicName;
1354 if (fBmhParser.fTopicMap.end() == fBmhParser.fTopicMap.find(fullName)) {
1355 (*subtopic)->reportError<void>("missing #Details subtopic");
1356 }
Cary Clark80247e52018-07-11 16:18:41 -04001357 // subtopicName = parentSubtopic->fName + '_' + subtopicName;
Cary Clark682c58d2018-05-16 07:07:07 -04001358 string noUnderscores = subtopicName;
1359 replace_all(noUnderscores, "_", "&nbsp;");
1360 details = this->anchorLocalRef(subtopicName, noUnderscores) + "&nbsp;";
1361 }
1362 FPRINTF("%s", out_table_data_details(details).c_str());
1363 this->lfAlways(1);
1364 }
1365 lookForOneLiner = true; // if description is empty, use oneLiner data
1366 FPRINTF("%s", out_table_data_description_start().c_str()); // start of Description
1367 this->lfAlways(1);
Cary Clark8032b982017-07-28 11:04:54 -04001368 } break;
1369 case MarkType::kDefine:
Cary Clarkffb3d682018-05-17 12:17:28 -04001370 this->mdHeaderOut(2);
1371 this->htmlOut(anchorDef(def->fFiddle, "Define " + def->fName));
1372 this->lf(2);
Cary Clark8032b982017-07-28 11:04:54 -04001373 break;
Cary Clark8032b982017-07-28 11:04:54 -04001374 case MarkType::kDeprecated:
Cary Clark682c58d2018-05-16 07:07:07 -04001375 this->writeString("Deprecated.");
1376 this->lf(2);
Cary Clark8032b982017-07-28 11:04:54 -04001377 break;
1378 case MarkType::kDescription:
1379 fInDescription = true;
1380 this->writePending();
Cary Clark682c58d2018-05-16 07:07:07 -04001381 FPRINTF("%s", "<div>");
Cary Clark8032b982017-07-28 11:04:54 -04001382 break;
Cary Clark682c58d2018-05-16 07:07:07 -04001383 case MarkType::kDetails:
Cary Clark8032b982017-07-28 11:04:54 -04001384 break;
Cary Clarkac47b882018-01-11 10:35:44 -05001385 case MarkType::kDuration:
1386 break;
Cary Clark8032b982017-07-28 11:04:54 -04001387 case MarkType::kEnum:
1388 case MarkType::kEnumClass:
1389 this->mdHeaderOut(2);
Cary Clark682c58d2018-05-16 07:07:07 -04001390 this->htmlOut(anchorDef(def->fFiddle, "Enum " + def->fName));
Cary Clark8032b982017-07-28 11:04:54 -04001391 this->lf(2);
1392 break;
Cary Clark8032b982017-07-28 11:04:54 -04001393 case MarkType::kExample: {
1394 this->mdHeaderOut(3);
Cary Clark682c58d2018-05-16 07:07:07 -04001395 FPRINTF("%s", "Example\n"
Cary Clark8032b982017-07-28 11:04:54 -04001396 "\n");
1397 fHasFiddle = true;
Cary Clarka560c472017-11-27 10:44:06 -05001398 bool showGpu = false;
1399 bool gpuAndCpu = false;
Cary Clark8032b982017-07-28 11:04:54 -04001400 const Definition* platform = def->hasChild(MarkType::kPlatform);
1401 if (platform) {
1402 TextParser platParse(platform);
1403 fHasFiddle = !platParse.strnstr("!fiddle", platParse.fEnd);
Cary Clarka560c472017-11-27 10:44:06 -05001404 showGpu = platParse.strnstr("gpu", platParse.fEnd);
1405 if (showGpu) {
1406 gpuAndCpu = platParse.strnstr("cpu", platParse.fEnd);
1407 }
Cary Clark8032b982017-07-28 11:04:54 -04001408 }
Cary Clark08895c42018-02-01 09:37:32 -05001409 if (fHasFiddle) {
Cary Clark2f466242017-12-11 16:03:17 -05001410 SkASSERT(def->fHash.length() > 0);
Cary Clark7cfcbca2018-01-04 16:11:51 -05001411 FPRINTF("<div><fiddle-embed name=\"%s\"", def->fHash.c_str());
Cary Clarka560c472017-11-27 10:44:06 -05001412 if (showGpu) {
Cary Clark682c58d2018-05-16 07:07:07 -04001413 FPRINTF("%s", " gpu=\"true\"");
Cary Clarka560c472017-11-27 10:44:06 -05001414 if (gpuAndCpu) {
Cary Clark682c58d2018-05-16 07:07:07 -04001415 FPRINTF("%s", " cpu=\"true\"");
Cary Clarka560c472017-11-27 10:44:06 -05001416 }
1417 }
Cary Clark682c58d2018-05-16 07:07:07 -04001418 FPRINTF("%s", ">");
Cary Clark8032b982017-07-28 11:04:54 -04001419 } else {
Cary Clark2f466242017-12-11 16:03:17 -05001420 SkASSERT(def->fHash.length() == 0);
Cary Clark682c58d2018-05-16 07:07:07 -04001421 FPRINTF("%s", "<pre style=\"padding: 1em 1em 1em 1em; font-size: 13px"
Cary Clarka560c472017-11-27 10:44:06 -05001422 " width: 62.5em; background-color: #f0f0f0\">");
1423 this->lfAlways(1);
1424 if (def->fWrapper.length() > 0) {
Cary Clark7cfcbca2018-01-04 16:11:51 -05001425 FPRINTF("%s", def->fWrapper.c_str());
Cary Clarka560c472017-11-27 10:44:06 -05001426 }
Cary Clark186d08f2018-04-03 08:43:27 -04001427 fLiteralAndIndent = true;
Cary Clark8032b982017-07-28 11:04:54 -04001428 }
1429 } break;
1430 case MarkType::kExperimental:
Cary Clark682c58d2018-05-16 07:07:07 -04001431 writeString("Experimental.");
1432 this->lf(2);
Cary Clark8032b982017-07-28 11:04:54 -04001433 break;
1434 case MarkType::kExternal:
Cary Clark8032b982017-07-28 11:04:54 -04001435 break;
Cary Clark0d225392018-06-07 09:59:07 -04001436 case MarkType::kFile:
1437 break;
Cary Clark8032b982017-07-28 11:04:54 -04001438 case MarkType::kFormula:
1439 break;
1440 case MarkType::kFunction:
1441 break;
1442 case MarkType::kHeight:
1443 break;
Cary Clarkf895a422018-02-27 09:54:21 -05001444 case MarkType::kIllustration: {
1445 string illustName = "Illustrations_" + def->fParent->fFiddle;
Cary Clark224c7002018-06-27 11:00:21 -04001446 string number = string(def->fContentStart, def->length());
1447 if (number.length() && "1" != number) {
1448 illustName += "_" + number;
1449 }
Cary Clarkf895a422018-02-27 09:54:21 -05001450 auto illustIter = fBmhParser.fTopicMap.find(illustName);
1451 SkASSERT(fBmhParser.fTopicMap.end() != illustIter);
1452 Definition* illustDef = illustIter->second;
1453 SkASSERT(MarkType::kSubtopic == illustDef->fMarkType);
1454 SkASSERT(1 == illustDef->fChildren.size());
1455 Definition* illustExample = illustDef->fChildren[0];
1456 SkASSERT(MarkType::kExample == illustExample->fMarkType);
1457 string hash = illustExample->fHash;
1458 SkASSERT("" != hash);
1459 string title;
1460 this->writePending();
1461 FPRINTF("![%s](https://fiddle.skia.org/i/%s_raster.png \"%s\")",
1462 def->fName.c_str(), hash.c_str(), title.c_str());
1463 this->lf(2);
1464 } break;
Cary Clark8032b982017-07-28 11:04:54 -04001465 case MarkType::kImage:
1466 break;
Cary Clarkab2621d2018-01-30 10:08:57 -05001467 case MarkType::kIn:
1468 break;
Cary Clark8032b982017-07-28 11:04:54 -04001469 case MarkType::kLegend:
1470 break;
Cary Clarkab2621d2018-01-30 10:08:57 -05001471 case MarkType::kLine:
1472 break;
Cary Clark8032b982017-07-28 11:04:54 -04001473 case MarkType::kLink:
1474 break;
1475 case MarkType::kList:
1476 fInList = true;
Cary Clark682c58d2018-05-16 07:07:07 -04001477 fTableState = TableState::kRow;
Cary Clark8032b982017-07-28 11:04:54 -04001478 this->lfAlways(2);
Cary Clark682c58d2018-05-16 07:07:07 -04001479 FPRINTF("%s", "<table>");
Cary Clark8032b982017-07-28 11:04:54 -04001480 this->lf(1);
1481 break;
Cary Clark154beea2017-10-26 07:58:48 -04001482 case MarkType::kLiteral:
1483 break;
Cary Clark8032b982017-07-28 11:04:54 -04001484 case MarkType::kMarkChar:
1485 fBmhParser.fMC = def->fContentStart[0];
1486 break;
Cary Clark8032b982017-07-28 11:04:54 -04001487 case MarkType::kMethod: {
1488 string method_name = def->methodName();
Cary Clark78de7512018-02-07 07:27:09 -05001489 string formattedStr = def->formatFunction(Definition::Format::kIncludeReturn);
Cary Clark2dc84ad2018-01-26 12:56:22 -05001490 this->lfAlways(2);
Cary Clark682c58d2018-05-16 07:07:07 -04001491 this->htmlOut(anchorDef(def->fFiddle, ""));
Cary Clark2dc84ad2018-01-26 12:56:22 -05001492 if (!def->isClone()) {
Cary Clark8032b982017-07-28 11:04:54 -04001493 this->mdHeaderOutLF(2, 1);
Cary Clark7cfcbca2018-01-04 16:11:51 -05001494 FPRINTF("%s", method_name.c_str());
Cary Clark2dc84ad2018-01-26 12:56:22 -05001495 }
1496 this->lf(2);
Cary Clark8032b982017-07-28 11:04:54 -04001497
1498 // TODO: put in css spec that we can define somewhere else (if markup supports that)
Cary Clark154beea2017-10-26 07:58:48 -04001499 // TODO: 50em below should match limit = 80 in formatFunction()
Cary Clark8032b982017-07-28 11:04:54 -04001500 this->writePending();
Cary Clarkbc5697d2017-10-04 14:31:33 -04001501 string preformattedStr = preformat(formattedStr);
Cary Clark682c58d2018-05-16 07:07:07 -04001502 string references = this->addReferences(&preformattedStr.front(),
1503 &preformattedStr.back() + 1, BmhParser::Resolvable::kSimple);
1504 preformattedStr = references;
1505 this->htmlOut("<pre style=\"padding: 1em 1em 1em 1em; width: 62.5em;"
1506 "background-color: #f0f0f0\">\n" + preformattedStr + "\n" + "</pre>");
Cary Clark8032b982017-07-28 11:04:54 -04001507 this->lf(2);
1508 fTableState = TableState::kNone;
1509 fMethod = def;
1510 } break;
1511 case MarkType::kNoExample:
1512 break;
Cary Clark682c58d2018-05-16 07:07:07 -04001513 case MarkType::kNoJustify:
1514 break;
Cary Clark154beea2017-10-26 07:58:48 -04001515 case MarkType::kOutdent:
1516 break;
Cary Clark8032b982017-07-28 11:04:54 -04001517 case MarkType::kParam: {
1518 if (TableState::kNone == fTableState) {
Cary Clark682c58d2018-05-16 07:07:07 -04001519 SkASSERT(!*prior || MarkType::kParam != (*prior)->fMarkType);
Cary Clark8032b982017-07-28 11:04:54 -04001520 this->mdHeaderOut(3);
Cary Clark682c58d2018-05-16 07:07:07 -04001521 this->htmlOut(
Cary Clark8032b982017-07-28 11:04:54 -04001522 "Parameters\n"
1523 "\n"
1524 "<table>"
1525 );
1526 this->lf(1);
1527 fTableState = TableState::kRow;
1528 }
1529 if (TableState::kRow == fTableState) {
Cary Clark7cfcbca2018-01-04 16:11:51 -05001530 FPRINTF(" <tr>");
Cary Clark8032b982017-07-28 11:04:54 -04001531 this->lf(1);
1532 fTableState = TableState::kColumn;
1533 }
1534 TextParser paramParser(def->fFileName, def->fStart, def->fContentStart,
1535 def->fLineCount);
1536 paramParser.skipWhiteSpace();
1537 SkASSERT(paramParser.startsWith("#Param"));
1538 paramParser.next(); // skip hash
Cary Clark2d4bf5f2018-04-16 08:37:38 -04001539 paramParser.skipToNonName(); // skip Param
Cary Clark8032b982017-07-28 11:04:54 -04001540 paramParser.skipSpace();
1541 const char* paramName = paramParser.fChar;
1542 paramParser.skipToSpace();
Cary Clark8cc16c72017-08-25 11:51:49 -04001543 string paramNameStr(paramName, (int) (paramParser.fChar - paramName));
Cary Clarka523d2d2017-08-30 08:58:10 -04001544 if (!this->checkParamReturnBody(def)) {
Cary Clark682c58d2018-05-16 07:07:07 -04001545 *prior = def;
Cary Clarka523d2d2017-08-30 08:58:10 -04001546 return;
1547 }
Cary Clark8cc16c72017-08-25 11:51:49 -04001548 string refNameStr = def->fParent->fFiddle + "_" + paramNameStr;
Cary Clark682c58d2018-05-16 07:07:07 -04001549 this->htmlOut(" <td>" + anchorDef(refNameStr,
1550 "<code><strong>" + paramNameStr + "</strong></code>") + "</td>");
1551 this->lfAlways(1);
1552 FPRINTF(" <td>");
Cary Clark8032b982017-07-28 11:04:54 -04001553 } break;
Cary Clark682c58d2018-05-16 07:07:07 -04001554 case MarkType::kPhraseDef:
1555 // skip text and children
1556 *prior = def;
1557 return;
1558 case MarkType::kPhraseParam:
1559 SkDebugf(""); // convenient place to set a breakpoint
1560 break;
1561 case MarkType::kPhraseRef:
1562 if (fPhraseParams.end() != fPhraseParams.find(def->fName)) {
1563 if (fColumn > 0) {
1564 this->writeSpace();
1565 }
1566 this->writeString(fPhraseParams[def->fName]);
1567 if (isspace(def->fContentStart[0])) {
1568 this->writeSpace();
1569 }
1570 } else if (fBmhParser.fPhraseMap.end() == fBmhParser.fPhraseMap.find(def->fName)) {
1571 def->reportError<void>("missing phrase definition");
1572 fAddRefFailed = true;
1573 } else {
1574 if (fColumn) {
1575 SkASSERT(' ' >= def->fStart[0]);
1576 this->writeSpace();
1577 }
1578 Definition* phraseRef = fBmhParser.fPhraseMap.find(def->fName)->second;
1579 // def->fChildren are parameters to substitute phraseRef->fChildren,
1580 // phraseRef->fChildren has both param defines and references
1581 // def->fChildren must have the same number of entries as phaseRef->fChildren
1582 // which are kPhraseParam, and substitute one for one
1583 // Then, each kPhraseRef in phaseRef looks up the key and value
1584 fPhraseParams.clear();
1585 auto refKidsIter = phraseRef->fChildren.begin();
1586 for (auto child : def->fChildren) {
1587 if (MarkType::kPhraseParam != child->fMarkType) {
1588 // more work to do to support other types
1589 this->reportError("phrase ref child must be param");
1590 }
1591 do {
1592 if (refKidsIter == phraseRef->fChildren.end()) {
1593 this->reportError("phrase def missing param");
1594 break;
1595 }
1596 if (MarkType::kPhraseRef == (*refKidsIter)->fMarkType) {
1597 continue;
1598 }
1599 if (MarkType::kPhraseParam != (*refKidsIter)->fMarkType) {
1600 this->reportError("unexpected type in phrase def children");
1601 break;
1602 }
1603 fPhraseParams[(*refKidsIter)->fName] = child->fName;
1604 break;
1605 } while (true);
1606 }
1607 this->childrenOut(phraseRef, phraseRef->fContentStart);
1608 fPhraseParams.clear();
1609 if (' ' >= def->fContentStart[0] && !fPendingLF) {
1610 this->writeSpace();
1611 }
1612 }
1613 break;
Cary Clark8032b982017-07-28 11:04:54 -04001614 case MarkType::kPlatform:
1615 break;
Cary Clark08895c42018-02-01 09:37:32 -05001616 case MarkType::kPopulate: {
1617 SkASSERT(MarkType::kSubtopic == def->fParent->fMarkType);
1618 string name = def->fParent->fName;
Cary Clark682c58d2018-05-16 07:07:07 -04001619 if (string::npos != name.find(SubtopicKeys::kOverview)) {
1620 this->subtopicsOut(def->fParent);
Cary Clark08895c42018-02-01 09:37:32 -05001621 } else {
Cary Clark682c58d2018-05-16 07:07:07 -04001622 this->subtopicOut(name);
Cary Clark08895c42018-02-01 09:37:32 -05001623 }
1624 } break;
Cary Clark8032b982017-07-28 11:04:54 -04001625 case MarkType::kPrivate:
1626 break;
1627 case MarkType::kReturn:
1628 this->mdHeaderOut(3);
Cary Clark7cfcbca2018-01-04 16:11:51 -05001629 FPRINTF("Return Value");
Cary Clarka523d2d2017-08-30 08:58:10 -04001630 if (!this->checkParamReturnBody(def)) {
Cary Clark682c58d2018-05-16 07:07:07 -04001631 *prior = def;
Cary Clarka523d2d2017-08-30 08:58:10 -04001632 return;
1633 }
Cary Clark8032b982017-07-28 11:04:54 -04001634 this->lf(2);
1635 break;
1636 case MarkType::kRow:
1637 if (fInList) {
Cary Clark7cfcbca2018-01-04 16:11:51 -05001638 FPRINTF(" <tr>");
Cary Clark8032b982017-07-28 11:04:54 -04001639 this->lf(1);
1640 }
1641 break;
1642 case MarkType::kSeeAlso:
1643 this->mdHeaderOut(3);
Cary Clark7cfcbca2018-01-04 16:11:51 -05001644 FPRINTF("See Also");
Cary Clark8032b982017-07-28 11:04:54 -04001645 this->lf(2);
1646 break;
Cary Clark61dfc3a2018-01-03 08:37:53 -05001647 case MarkType::kSet:
1648 break;
Cary Clark8032b982017-07-28 11:04:54 -04001649 case MarkType::kStdOut: {
1650 TextParser code(def);
1651 this->mdHeaderOut(4);
Cary Clark682c58d2018-05-16 07:07:07 -04001652 FPRINTF(
Cary Clark8032b982017-07-28 11:04:54 -04001653 "Example Output\n"
1654 "\n"
1655 "~~~~");
1656 this->lfAlways(1);
1657 code.skipSpace();
1658 while (!code.eof()) {
1659 const char* end = code.trimmedLineEnd();
Cary Clark7cfcbca2018-01-04 16:11:51 -05001660 FPRINTF("%.*s\n", (int) (end - code.fChar), code.fChar);
Cary Clark8032b982017-07-28 11:04:54 -04001661 code.skipToLineStart();
1662 }
Cary Clark7cfcbca2018-01-04 16:11:51 -05001663 FPRINTF("~~~~");
Cary Clark8032b982017-07-28 11:04:54 -04001664 this->lf(2);
1665 } break;
Cary Clark8032b982017-07-28 11:04:54 -04001666 case MarkType::kSubstitute:
1667 break;
1668 case MarkType::kSubtopic:
Cary Clark682c58d2018-05-16 07:07:07 -04001669 fSubtopic = def->asRoot();
Cary Clark8032b982017-07-28 11:04:54 -04001670 this->mdHeaderOut(2);
Cary Clark682c58d2018-05-16 07:07:07 -04001671 if (SubtopicKeys::kOverview == def->fName) {
1672 this->writeString(def->fName);
1673 } else {
1674 this->htmlOut(anchorDef(def->fName, printable));
1675 }
Cary Clark8032b982017-07-28 11:04:54 -04001676 this->lf(2);
Cary Clark682c58d2018-05-16 07:07:07 -04001677 // if a subtopic child is const, generate short table of const name, value, line desc
1678 if (std::any_of(def->fChildren.begin(), def->fChildren.end(),
1679 [](Definition* child){return MarkType::kConst == child->fMarkType;})) {
1680 this->summaryOut(def, MarkType::kConst, fPopulators[SubtopicKeys::kConstants].fName);
1681 }
1682 // if a subtopic child is member, generate short table of const name, value, line desc
1683 if (std::any_of(def->fChildren.begin(), def->fChildren.end(),
1684 [](Definition* child){return MarkType::kMember == child->fMarkType;})) {
1685 this->summaryOut(def, MarkType::kMember, fPopulators[SubtopicKeys::kMembers].fName);
1686 }
Cary Clark8032b982017-07-28 11:04:54 -04001687 break;
1688 case MarkType::kTable:
1689 this->lf(2);
1690 break;
1691 case MarkType::kTemplate:
1692 break;
1693 case MarkType::kText:
1694 break;
Cary Clark8032b982017-07-28 11:04:54 -04001695 case MarkType::kToDo:
1696 break;
Cary Clark682c58d2018-05-16 07:07:07 -04001697 case MarkType::kTopic: {
1698 auto found = std::find_if(def->fChildren.begin(), def->fChildren.end(),
1699 [](Definition* test) { return test->isStructOrClass(); } );
1700 bool hasClassOrStruct = def->fChildren.end() != found;
1701 fRoot = hasClassOrStruct ? (*found)->asRoot() : def->asRoot();
1702 fSubtopic = def->asRoot();
1703 bool isUndocumented = string::npos != def->fFileName.find("undocumented");
1704 if (!isUndocumented) {
1705 this->populateTables(def, fRoot);
1706 }
Cary Clark8032b982017-07-28 11:04:54 -04001707 this->mdHeaderOut(1);
Cary Clark682c58d2018-05-16 07:07:07 -04001708 this->htmlOut(anchorDef(this->linkName(def), printable));
Cary Clark8032b982017-07-28 11:04:54 -04001709 this->lf(1);
Cary Clark682c58d2018-05-16 07:07:07 -04001710 } break;
Cary Clark8032b982017-07-28 11:04:54 -04001711 case MarkType::kTypedef:
Cary Clarkffb3d682018-05-17 12:17:28 -04001712 this->mdHeaderOut(2);
Cary Clark682c58d2018-05-16 07:07:07 -04001713 this->htmlOut(anchorDef(def->fFiddle, "Typedef " + def->fName));
1714 this->lf(1);
Cary Clark8032b982017-07-28 11:04:54 -04001715 break;
1716 case MarkType::kUnion:
1717 break;
1718 case MarkType::kVolatile:
1719 break;
1720 case MarkType::kWidth:
1721 break;
1722 default:
1723 SkDebugf("fatal error: MarkType::k%s unhandled in %s()\n",
Cary Clark2d4bf5f2018-04-16 08:37:38 -04001724 BmhParser::kMarkProps[(int) def->fMarkType].fName, __func__);
Cary Clark8032b982017-07-28 11:04:54 -04001725 SkASSERT(0); // handle everything
1726 break;
1727 }
1728 this->childrenOut(def, textStart);
1729 switch (def->fMarkType) { // post child work, at least for tables
Cary Clark7cfcbca2018-01-04 16:11:51 -05001730 case MarkType::kAnchor:
1731 if (fColumn > 0) {
1732 this->writeSpace();
1733 }
1734 break;
Cary Clark682c58d2018-05-16 07:07:07 -04001735 case MarkType::kClass:
1736 case MarkType::kStruct:
1737 if (TableState::kNone != fTableState) {
1738 this->writePending();
1739 FPRINTF("</table>");
1740 this->lf(2);
1741 fTableState = TableState::kNone;
1742 }
1743 if (def->csParent()) {
1744 fRoot = def->csParent()->asRoot();
1745 }
1746 break;
Cary Clark8032b982017-07-28 11:04:54 -04001747 case MarkType::kCode:
Cary Clark186d08f2018-04-03 08:43:27 -04001748 fIndent = 0;
1749 this->lf(1);
Cary Clark8032b982017-07-28 11:04:54 -04001750 this->writePending();
Cary Clark7cfcbca2018-01-04 16:11:51 -05001751 FPRINTF("</pre>");
Cary Clark8032b982017-07-28 11:04:54 -04001752 this->lf(2);
Cary Clark186d08f2018-04-03 08:43:27 -04001753 fResolveAndIndent = false;
Cary Clark8032b982017-07-28 11:04:54 -04001754 break;
1755 case MarkType::kColumn:
1756 if (fInList) {
1757 this->writePending();
Cary Clark7cfcbca2018-01-04 16:11:51 -05001758 FPRINTF("</td>");
Cary Clark682c58d2018-05-16 07:07:07 -04001759 this->lfAlways(1);
Cary Clark8032b982017-07-28 11:04:54 -04001760 } else {
Cary Clark7cfcbca2018-01-04 16:11:51 -05001761 FPRINTF(" ");
Cary Clark8032b982017-07-28 11:04:54 -04001762 }
1763 break;
1764 case MarkType::kDescription:
1765 this->writePending();
Cary Clark7cfcbca2018-01-04 16:11:51 -05001766 FPRINTF("</div>");
Cary Clark8032b982017-07-28 11:04:54 -04001767 fInDescription = false;
1768 break;
1769 case MarkType::kEnum:
1770 case MarkType::kEnumClass:
Cary Clark682c58d2018-05-16 07:07:07 -04001771 if (TableState::kNone != fTableState) {
1772 this->writePending();
1773 FPRINTF("</table>");
1774 this->lf(2);
1775 fTableState = TableState::kNone;
1776 }
Cary Clark8032b982017-07-28 11:04:54 -04001777 break;
1778 case MarkType::kExample:
1779 this->writePending();
1780 if (fHasFiddle) {
Cary Clark7cfcbca2018-01-04 16:11:51 -05001781 FPRINTF("</fiddle-embed></div>");
Cary Clark8032b982017-07-28 11:04:54 -04001782 } else {
Cary Clarka560c472017-11-27 10:44:06 -05001783 this->lfAlways(1);
1784 if (def->fWrapper.length() > 0) {
Cary Clark7cfcbca2018-01-04 16:11:51 -05001785 FPRINTF("}");
Cary Clarka560c472017-11-27 10:44:06 -05001786 this->lfAlways(1);
1787 }
Cary Clark7cfcbca2018-01-04 16:11:51 -05001788 FPRINTF("</pre>");
Cary Clark8032b982017-07-28 11:04:54 -04001789 }
1790 this->lf(2);
Cary Clark186d08f2018-04-03 08:43:27 -04001791 fLiteralAndIndent = false;
Cary Clark8032b982017-07-28 11:04:54 -04001792 break;
Cary Clark7cfcbca2018-01-04 16:11:51 -05001793 case MarkType::kLink:
1794 this->writeString("</a>");
1795 this->writeSpace();
1796 break;
Cary Clark8032b982017-07-28 11:04:54 -04001797 case MarkType::kList:
1798 fInList = false;
1799 this->writePending();
Cary Clark682c58d2018-05-16 07:07:07 -04001800 SkASSERT(TableState::kNone != fTableState);
Cary Clark7cfcbca2018-01-04 16:11:51 -05001801 FPRINTF("</table>");
Cary Clark8032b982017-07-28 11:04:54 -04001802 this->lf(2);
Cary Clark682c58d2018-05-16 07:07:07 -04001803 fTableState = TableState::kNone;
Cary Clark8032b982017-07-28 11:04:54 -04001804 break;
1805 case MarkType::kLegend: {
1806 SkASSERT(def->fChildren.size() == 1);
1807 const Definition* row = def->fChildren[0];
1808 SkASSERT(MarkType::kRow == row->fMarkType);
1809 size_t columnCount = row->fChildren.size();
1810 SkASSERT(columnCount > 0);
1811 this->writePending();
1812 for (size_t index = 0; index < columnCount; ++index) {
Cary Clark7cfcbca2018-01-04 16:11:51 -05001813 FPRINTF("| --- ");
Cary Clark8032b982017-07-28 11:04:54 -04001814 }
Cary Clark7cfcbca2018-01-04 16:11:51 -05001815 FPRINTF(" |");
Cary Clark8032b982017-07-28 11:04:54 -04001816 this->lf(1);
1817 } break;
1818 case MarkType::kMethod:
1819 fMethod = nullptr;
1820 this->lfAlways(2);
Cary Clark7cfcbca2018-01-04 16:11:51 -05001821 FPRINTF("---");
Cary Clark8032b982017-07-28 11:04:54 -04001822 this->lf(2);
1823 break;
1824 case MarkType::kConst:
Cary Clark682c58d2018-05-16 07:07:07 -04001825 case MarkType::kMember:
1826 if (lookForOneLiner && !fWroteSomething) {
1827 auto oneLiner = std::find_if(def->fChildren.begin(), def->fChildren.end(),
1828 [](const Definition* test){ return MarkType::kLine == test->fMarkType; } );
1829 if (def->fChildren.end() != oneLiner) {
1830 TextParser parser(*oneLiner);
1831 parser.skipWhiteSpace();
1832 parser.trimEnd();
1833 FPRINTF("%.*s", (int) (parser.fEnd - parser.fChar), parser.fChar);
1834 }
1835 lookForOneLiner = false;
1836 }
Cary Clark8032b982017-07-28 11:04:54 -04001837 case MarkType::kParam:
1838 SkASSERT(TableState::kColumn == fTableState);
1839 fTableState = TableState::kRow;
1840 this->writePending();
Cary Clark682c58d2018-05-16 07:07:07 -04001841 FPRINTF("</td>");
1842 this->lfAlways(1);
Cary Clark7cfcbca2018-01-04 16:11:51 -05001843 FPRINTF(" </tr>");
Cary Clark682c58d2018-05-16 07:07:07 -04001844 this->lfAlways(1);
Cary Clark8032b982017-07-28 11:04:54 -04001845 break;
1846 case MarkType::kReturn:
1847 case MarkType::kSeeAlso:
1848 this->lf(2);
1849 break;
1850 case MarkType::kRow:
1851 if (fInList) {
Cary Clark7cfcbca2018-01-04 16:11:51 -05001852 FPRINTF(" </tr>");
Cary Clark8032b982017-07-28 11:04:54 -04001853 } else {
Cary Clark7cfcbca2018-01-04 16:11:51 -05001854 FPRINTF("|");
Cary Clark8032b982017-07-28 11:04:54 -04001855 }
1856 this->lf(1);
1857 break;
Cary Clark8032b982017-07-28 11:04:54 -04001858 case MarkType::kTable:
1859 this->lf(2);
1860 break;
Cary Clark1a8d7622018-03-05 13:26:16 -05001861 case MarkType::kPhraseDef:
1862 break;
Cary Clark8032b982017-07-28 11:04:54 -04001863 case MarkType::kPrivate:
1864 break;
Cary Clark682c58d2018-05-16 07:07:07 -04001865 case MarkType::kSubtopic:
1866 SkASSERT(def);
1867 do {
1868 def = def->fParent;
1869 } while (def && MarkType::kTopic != def->fMarkType
1870 && MarkType::kSubtopic != def->fMarkType);
1871 SkASSERT(def);
1872 fSubtopic = def->asRoot();
1873 break;
1874 case MarkType::kTopic:
1875 fSubtopic = nullptr;
1876 break;
Cary Clark8032b982017-07-28 11:04:54 -04001877 default:
1878 break;
1879 }
Cary Clark682c58d2018-05-16 07:07:07 -04001880 *prior = def;
Cary Clark8032b982017-07-28 11:04:54 -04001881}
1882
1883void MdOut::mdHeaderOutLF(int depth, int lf) {
1884 this->lfAlways(lf);
1885 for (int index = 0; index < depth; ++index) {
Cary Clark7cfcbca2018-01-04 16:11:51 -05001886 FPRINTF("#");
Cary Clark8032b982017-07-28 11:04:54 -04001887 }
Cary Clark7cfcbca2018-01-04 16:11:51 -05001888 FPRINTF(" ");
Cary Clark8032b982017-07-28 11:04:54 -04001889}
1890
Cary Clark682c58d2018-05-16 07:07:07 -04001891void MdOut::populateOne(Definition* def,
1892 unordered_map<string, RootDefinition::SubtopicContents>& populator) {
1893 if (MarkType::kConst == def->fMarkType) {
1894 populator[SubtopicKeys::kConstants].fMembers.push_back(def);
Cary Clark78de7512018-02-07 07:27:09 -05001895 return;
1896 }
Cary Clark682c58d2018-05-16 07:07:07 -04001897 if (MarkType::kEnum == def->fMarkType || MarkType::kEnumClass == def->fMarkType) {
1898 populator[SubtopicKeys::kConstants].fMembers.push_back(def);
1899 return;
1900 }
1901 if (MarkType::kDefine == def->fMarkType) {
1902 populator[SubtopicKeys::kDefines].fMembers.push_back(def);
1903 return;
1904 }
1905 if (MarkType::kMember == def->fMarkType) {
1906 populator[SubtopicKeys::kMembers].fMembers.push_back(def);
1907 return;
1908 }
1909 if (MarkType::kTypedef == def->fMarkType) {
1910 populator[SubtopicKeys::kTypedefs].fMembers.push_back(def);
1911 return;
1912 }
1913 if (MarkType::kMethod != def->fMarkType) {
1914 return;
1915 }
1916 if (def->fClone) {
1917 return;
1918 }
1919 if (Definition::MethodType::kConstructor == def->fMethodType
1920 || Definition::MethodType::kDestructor == def->fMethodType) {
1921 populator[SubtopicKeys::kConstructors].fMembers.push_back(def);
1922 return;
1923 }
1924 if (Definition::MethodType::kOperator == def->fMethodType) {
1925 populator[SubtopicKeys::kOperators].fMembers.push_back(def);
1926 return;
1927 }
1928 populator[SubtopicKeys::kMemberFunctions].fMembers.push_back(def);
1929 const Definition* csParent = this->csParent();
1930 if (csParent) {
1931 if (0 == def->fName.find(csParent->fName + "::Make")
1932 || 0 == def->fName.find(csParent->fName + "::make")) {
1933 populator[SubtopicKeys::kConstructors].fMembers.push_back(def);
1934 return;
1935 }
1936 }
1937 for (auto item : def->fChildren) {
1938 if (MarkType::kIn == item->fMarkType) {
1939 string name(item->fContentStart, item->fContentEnd - item->fContentStart);
1940 populator[name].fMembers.push_back(def);
1941 populator[name].fShowClones = true;
1942 break;
1943 }
1944 }
1945}
1946
1947void MdOut::populateTables(const Definition* def, RootDefinition* root) {
Cary Clark08895c42018-02-01 09:37:32 -05001948 for (auto child : def->fChildren) {
Cary Clark682c58d2018-05-16 07:07:07 -04001949 if (MarkType::kSubtopic == child->fMarkType) {
Cary Clark78de7512018-02-07 07:27:09 -05001950 string name = child->fName;
Cary Clark682c58d2018-05-16 07:07:07 -04001951 bool builtInTopic = name == SubtopicKeys::kOverview;
1952 for (auto item : SubtopicKeys::kGeneratedSubtopics) {
1953 builtInTopic |= name == item;
Cary Clark08895c42018-02-01 09:37:32 -05001954 }
Cary Clark682c58d2018-05-16 07:07:07 -04001955 if (!builtInTopic) {
1956 string subname;
1957 const Definition* subtopic = child->subtopicParent();
1958 if (subtopic) {
1959 subname = subtopic->fName + '_';
1960 }
1961 builtInTopic = name == subname + SubtopicKeys::kOverview;
1962 for (auto item : SubtopicKeys::kGeneratedSubtopics) {
1963 builtInTopic |= name == subname + item;
1964 }
1965 if (!builtInTopic) {
1966 root->populator(SubtopicKeys::kRelatedFunctions).fMembers.push_back(child);
Cary Clark682c58d2018-05-16 07:07:07 -04001967 }
1968 }
1969 this->populateTables(child, root);
Cary Clark08895c42018-02-01 09:37:32 -05001970 continue;
1971 }
1972 if (child->isStructOrClass()) {
1973 if (fClassStack.size() > 0) {
Cary Clark682c58d2018-05-16 07:07:07 -04001974 root->populator(MarkType::kStruct != child->fMarkType ? SubtopicKeys::kClasses :
1975 SubtopicKeys::kStructs).fMembers.push_back(child);
Cary Clark08895c42018-02-01 09:37:32 -05001976 }
1977 fClassStack.push_back(child);
Cary Clark682c58d2018-05-16 07:07:07 -04001978 this->populateTables(child, child->asRoot());
Cary Clark08895c42018-02-01 09:37:32 -05001979 fClassStack.pop_back();
1980 continue;
1981 }
1982 if (MarkType::kEnum == child->fMarkType || MarkType::kEnumClass == child->fMarkType) {
Cary Clark682c58d2018-05-16 07:07:07 -04001983 this->populateTables(child, root);
Cary Clark08895c42018-02-01 09:37:32 -05001984 }
Cary Clark682c58d2018-05-16 07:07:07 -04001985 this->populateOne(child, root->fPopulators);
Cary Clark08895c42018-02-01 09:37:32 -05001986 }
Cary Clarkab2621d2018-01-30 10:08:57 -05001987}
1988
Cary Clark8032b982017-07-28 11:04:54 -04001989void MdOut::resolveOut(const char* start, const char* end, BmhParser::Resolvable resolvable) {
Cary Clark186d08f2018-04-03 08:43:27 -04001990 if ((BmhParser::Resolvable::kLiteral == resolvable || fLiteralAndIndent ||
1991 fResolveAndIndent) && end > start) {
1992 int linefeeds = 0;
Cary Clark154beea2017-10-26 07:58:48 -04001993 while ('\n' == *start) {
Cary Clark186d08f2018-04-03 08:43:27 -04001994 ++linefeeds;
Cary Clark154beea2017-10-26 07:58:48 -04001995 ++start;
1996 }
Cary Clark186d08f2018-04-03 08:43:27 -04001997 if (fResolveAndIndent && linefeeds) {
1998 this->lf(linefeeds);
1999 }
Cary Clark154beea2017-10-26 07:58:48 -04002000 const char* spaceStart = start;
2001 while (' ' == *start) {
2002 ++start;
2003 }
2004 if (start > spaceStart) {
Cary Clarka560c472017-11-27 10:44:06 -05002005 fIndent = start - spaceStart;
Cary Clark154beea2017-10-26 07:58:48 -04002006 }
Cary Clark186d08f2018-04-03 08:43:27 -04002007 }
2008 if (BmhParser::Resolvable::kLiteral == resolvable || fLiteralAndIndent) {
Cary Clark154beea2017-10-26 07:58:48 -04002009 this->writeBlockTrim(end - start, start);
2010 if ('\n' == end[-1]) {
2011 this->lf(1);
2012 }
2013 fIndent = 0;
2014 return;
2015 }
Cary Clark8032b982017-07-28 11:04:54 -04002016 // FIXME: this needs the markdown character present when the def was defined,
2017 // not the last markdown character the parser would have seen...
2018 while (fBmhParser.fMC == end[-1]) {
2019 --end;
2020 }
2021 if (start >= end) {
2022 return;
2023 }
2024 string resolved = this->addReferences(start, end, resolvable);
2025 trim_end_spaces(resolved);
2026 if (resolved.length()) {
2027 TextParser paragraph(fFileName, &*resolved.begin(), &*resolved.end(), fLineCount);
Cary Clark8032b982017-07-28 11:04:54 -04002028 while (!paragraph.eof()) {
Cary Clark186d08f2018-04-03 08:43:27 -04002029 while ('\n' == paragraph.peek()) {
2030 paragraph.next();
2031 if (paragraph.eof()) {
2032 return;
2033 }
2034 }
2035 const char* lineStart = paragraph.fChar;
Cary Clark8032b982017-07-28 11:04:54 -04002036 paragraph.skipWhiteSpace();
2037 const char* contentStart = paragraph.fChar;
Cary Clark186d08f2018-04-03 08:43:27 -04002038 if (fResolveAndIndent && contentStart > lineStart) {
2039 this->writePending();
2040 this->indentToColumn(contentStart - lineStart);
2041 }
Cary Clark8032b982017-07-28 11:04:54 -04002042 paragraph.skipToEndBracket('\n');
2043 ptrdiff_t lineLength = paragraph.fChar - contentStart;
2044 if (lineLength) {
Cary Clark7cfcbca2018-01-04 16:11:51 -05002045 while (lineLength && contentStart[lineLength - 1] <= ' ') {
2046 --lineLength;
2047 }
2048 string str(contentStart, lineLength);
2049 this->writeString(str.c_str());
Cary Clark682c58d2018-05-16 07:07:07 -04002050 fWroteSomething = !!lineLength;
Cary Clark8032b982017-07-28 11:04:54 -04002051 }
Cary Clark8032b982017-07-28 11:04:54 -04002052 if (paragraph.eof()) {
2053 break;
2054 }
2055 if ('\n' == paragraph.next()) {
Cary Clark7cfcbca2018-01-04 16:11:51 -05002056 int linefeeds = 1;
Cary Clark8032b982017-07-28 11:04:54 -04002057 if (!paragraph.eof() && '\n' == paragraph.peek()) {
2058 linefeeds = 2;
2059 }
2060 this->lf(linefeeds);
2061 }
2062 }
Cary Clark8032b982017-07-28 11:04:54 -04002063 }
2064}
Cary Clark08895c42018-02-01 09:37:32 -05002065
Cary Clark682c58d2018-05-16 07:07:07 -04002066void MdOut::rowOut(const char* name, string description, bool literalName) {
2067 FPRINTF("%s", fOddRow ? kTR_Dark.c_str() : " <tr>");
Cary Clark08895c42018-02-01 09:37:32 -05002068 this->lfAlways(1);
Cary Clark682c58d2018-05-16 07:07:07 -04002069 FPRINTF("%s", kTD_Left.c_str());
2070 if (literalName) {
2071 if (strlen(name)) {
2072 this->writeString(name);
2073 }
2074 } else {
2075 this->resolveOut(name, name + strlen(name), BmhParser::Resolvable::kYes);
2076 }
2077 FPRINTF("</td>");
2078 this->lfAlways(1);
2079 FPRINTF("%s", kTD_Left.c_str());
Cary Clark08895c42018-02-01 09:37:32 -05002080 this->resolveOut(&description.front(), &description.back() + 1, BmhParser::Resolvable::kYes);
Cary Clark682c58d2018-05-16 07:07:07 -04002081 FPRINTF("</td>");
2082 this->lfAlways(1);
2083 FPRINTF(" </tr>");
2084 this->lfAlways(1);
2085 fOddRow = !fOddRow;
Cary Clark08895c42018-02-01 09:37:32 -05002086}
2087
Cary Clark682c58d2018-05-16 07:07:07 -04002088void MdOut::subtopicsOut(Definition* def) {
2089 Definition* csParent = def->csParent();
2090 const Definition* subtopicParent = def->subtopicParent();
2091 const Definition* topicParent = def->topicParent();
2092 SkASSERT(subtopicParent);
2093 this->lfAlways(1);
2094 FPRINTF("%s", kTableDeclaration);
2095 this->lfAlways(1);
2096 FPRINTF("%s", kTopicsTableHeader);
2097 this->lfAlways(1);
2098 fOddRow = true;
2099 for (auto item : SubtopicKeys::kGeneratedSubtopics) {
2100 for (auto entry : fRoot->populator(item).fMembers) {
2101 if ((csParent && entry->csParent() == csParent)
2102 || entry->subtopicParent() == subtopicParent) {
2103 auto popItem = fPopulators.find(item);
2104 string description = popItem->second.fOneLiner;
2105 if (SubtopicKeys::kConstructors == item) {
2106 description += " " + fRoot->fName;
Cary Clark08895c42018-02-01 09:37:32 -05002107 }
Cary Clark682c58d2018-05-16 07:07:07 -04002108 string subtopic;
2109 if (subtopicParent != topicParent) {
2110 subtopic = subtopicParent->fName + '_';
2111 }
2112 string link = this->anchorLocalRef(subtopic + item, popItem->second.fName);
2113 this->rowOut(link.c_str(), description, true);
Cary Clark08895c42018-02-01 09:37:32 -05002114 break;
2115 }
2116 }
2117 }
Cary Clark682c58d2018-05-16 07:07:07 -04002118 FPRINTF("</table>");
2119 this->lfAlways(1);
Cary Clark08895c42018-02-01 09:37:32 -05002120}
2121
Cary Clark682c58d2018-05-16 07:07:07 -04002122void MdOut::subtopicOut(string name) {
Cary Clark682c58d2018-05-16 07:07:07 -04002123 const Definition* topicParent = fSubtopic ? fSubtopic->topicParent() : nullptr;
Cary Clark224c7002018-06-27 11:00:21 -04002124 Definition* csParent = this->csParent();
2125 if (!csParent) {
2126 auto csIter = std::find_if(topicParent->fChildren.begin(), topicParent->fChildren.end(),
2127 [](const Definition* def){ return MarkType::kEnum == def->fMarkType
2128 || MarkType::kEnumClass == def->fMarkType; } );
2129 SkASSERT(topicParent->fChildren.end() != csIter);
2130 csParent = *csIter;
2131 }
2132 SkASSERT(csParent);
Cary Clark682c58d2018-05-16 07:07:07 -04002133 this->lfAlways(1);
2134 if (fPopulators.end() != fPopulators.find(name)) {
2135 const SubtopicDescriptions& tableDescriptions = this->populator(name);
2136 this->anchorDef(name, tableDescriptions.fName);
2137 this->lfAlways(1);
2138 if (tableDescriptions.fDetails.length()) {
2139 string details = csParent->fName;
2140 details += " " + tableDescriptions.fDetails;
2141 this->writeString(details);
2142 this->lfAlways(1);
2143 }
2144 } else {
2145 this->anchorDef(name, name);
2146 this->lfAlways(1);
2147 }
2148 FPRINTF("%s", kTableDeclaration);
2149 this->lfAlways(1);
2150 FPRINTF("%s", kTopicsTableHeader);
2151 this->lfAlways(1);
2152 fOddRow = true;
Cary Clark08895c42018-02-01 09:37:32 -05002153 std::map<string, const Definition*> items;
Cary Clark682c58d2018-05-16 07:07:07 -04002154 const RootDefinition::SubtopicContents& tableContents = fRoot->populator(name.c_str());
2155 auto& data = tableContents.fMembers;
Cary Clark08895c42018-02-01 09:37:32 -05002156 for (auto entry : data) {
Cary Clark682c58d2018-05-16 07:07:07 -04002157 if (entry->csParent() != csParent && entry->topicParent() != topicParent) {
Cary Clark08895c42018-02-01 09:37:32 -05002158 continue;
2159 }
2160 size_t start = entry->fName.find_last_of("::");
Cary Clark224c7002018-06-27 11:00:21 -04002161 if (MarkType::kConst == entry->fMarkType && entry->fParent
2162 && MarkType::kEnumClass == entry->fParent->fMarkType
2163 && string::npos != start && start > 1) {
2164 start = entry->fName.substr(0, start - 1).rfind("::");
2165 }
Cary Clark682c58d2018-05-16 07:07:07 -04002166 string entryName = entry->fName.substr(string::npos == start ? 0 : start + 1);
2167 items[entryName] = entry;
Cary Clark08895c42018-02-01 09:37:32 -05002168 }
2169 for (auto entry : items) {
Cary Clark4855f782018-02-06 09:41:53 -05002170 if (entry.second->fDeprecated) {
2171 continue;
2172 }
Cary Clark08895c42018-02-01 09:37:32 -05002173 const Definition* oneLiner = nullptr;
2174 for (auto child : entry.second->fChildren) {
2175 if (MarkType::kLine == child->fMarkType) {
2176 oneLiner = child;
2177 break;
2178 }
2179 }
Cary Clarkedfe6702018-02-20 14:33:13 -05002180 if (!oneLiner) {
Cary Clark06c20f32018-03-20 15:53:27 -04002181 TextParser parser(entry.second->fFileName, entry.second->fStart,
2182 entry.second->fContentStart, entry.second->fLineCount);
2183 parser.reportError("missing #Line");
2184 continue;
Cary Clarkedfe6702018-02-20 14:33:13 -05002185 }
Cary Clark682c58d2018-05-16 07:07:07 -04002186 string keyName = entry.first;
2187 TextParser dummy(entry.second); // for reporting errors, which we won't do
2188 if (!this->isDefined(dummy, keyName, BmhParser::Resolvable::kOut)) {
2189 keyName = entry.second->fName;
2190 size_t doubleColon = keyName.find("::");
2191 SkASSERT(string::npos != doubleColon);
2192 keyName = keyName.substr(doubleColon + 2);
2193 }
2194 this->rowOut(keyName.c_str(), string(oneLiner->fContentStart,
2195 oneLiner->fContentEnd - oneLiner->fContentStart), false);
Cary Clark4855f782018-02-06 09:41:53 -05002196 if (tableContents.fShowClones && entry.second->fCloned) {
2197 int cloneNo = 2;
2198 string builder = entry.second->fName;
2199 if ("()" == builder.substr(builder.length() - 2)) {
2200 builder = builder.substr(0, builder.length() - 2);
2201 }
2202 builder += '_';
Cary Clark78de7512018-02-07 07:27:09 -05002203 this->rowOut("",
Cary Clark682c58d2018-05-16 07:07:07 -04002204 preformat(entry.second->formatFunction(Definition::Format::kOmitReturn)), true);
Cary Clark4855f782018-02-06 09:41:53 -05002205 do {
2206 string match = builder + to_string(cloneNo);
2207 auto child = csParent->findClone(match);
2208 if (!child) {
2209 break;
2210 }
Cary Clark682c58d2018-05-16 07:07:07 -04002211 this->rowOut("",
2212 preformat(child->formatFunction(Definition::Format::kOmitReturn)), true);
Cary Clark4855f782018-02-06 09:41:53 -05002213 } while (++cloneNo);
2214 }
Cary Clark08895c42018-02-01 09:37:32 -05002215 }
Cary Clark682c58d2018-05-16 07:07:07 -04002216 FPRINTF("</table>");
Cary Clark224c7002018-06-27 11:00:21 -04002217 this->lf(2);
Cary Clark08895c42018-02-01 09:37:32 -05002218}