• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 "bmhParser.h"
9 #include "includeParser.h"
10 #include "mdOut.h"
11 
12 #include "SkOSFile.h"
13 #include "SkOSPath.h"
14 
15 class SubtopicKeys {
16 public:
17     static constexpr const char* kClasses = "Classes";
18     static constexpr const char* kConstants = "Constants";
19     static constexpr const char* kConstructors = "Constructors";
20     static constexpr const char* kDefines = "Defines";
21     static constexpr const char* kMemberFunctions = "Member_Functions";
22     static constexpr const char* kMembers = "Members";
23     static constexpr const char* kOperators = "Operators";
24     static constexpr const char* kOverview = "Overview";
25     static constexpr const char* kRelatedFunctions = "Related_Functions";
26     static constexpr const char* kStructs = "Structs";
27     static constexpr const char* kTypedefs = "Typedefs";
28 
29     static const char* kGeneratedSubtopics[];
30 };
31 
32 const char* SubtopicKeys::kGeneratedSubtopics[] = {
33     kConstants, kDefines, kTypedefs, kMembers, kClasses, kStructs, kConstructors,
34     kOperators, kMemberFunctions, kRelatedFunctions
35 };
36 
37 const char* kConstTableStyle =
38 "<style>"                                                                                      "\n"
39     ".td_const td, th { border: 2px solid #dddddd; text-align: left; padding: 8px; }"          "\n"
40     ".tr_const tr:nth-child(even) { background-color: #f0f0f0; }"                              "\n"
41     ".td2_const td:first-child + td { text-align: center; }"                                   "\n"
42 "</style>"                                                                                     "\n";
43 
44 const char* kTableDeclaration = "<table style='border-collapse: collapse; width: 62.5em'>";
45 
46 #define kTD_Base         "border: 2px solid #dddddd; padding: 8px; "
47 #define kTH_Left         "<th style='text-align: left; "   kTD_Base "'>"
48 #define kTH_Center       "<th style='text-align: center; " kTD_Base "'>"
49 
50 string kTD_Left    = "    <td style='text-align: left; "   kTD_Base "'>";
51 string kTD_Center  = "    <td style='text-align: center; " kTD_Base "'>";
52 string kTR_Dark    =   "  <tr style='background-color: #f0f0f0; '>";
53 
54 const char* kAllConstTableHeader =  "  <tr>" kTH_Left   "Const</th>"                            "\n"
55                                              kTH_Center "Value</th>"                            "\n"
56                                              kTH_Left   "Description</th>" "</tr>";
57 const char* kSubConstTableHeader =  "  <tr>" kTH_Left   "Const</th>"                            "\n"
58                                              kTH_Center "Value</th>"                            "\n"
59                                              kTH_Left   "Details</th>"                          "\n"
60                                              kTH_Left   "Description</th>" "</tr>";
61 const char* kAllMemberTableHeader = "  <tr>" kTH_Left   "Type</th>"                             "\n"
62                                              kTH_Left   "Member</th>"                           "\n"
63                                              kTH_Left   "Description</th>" "</tr>";
64 const char* kSubMemberTableHeader = "  <tr>" kTH_Left   "Type</th>"                             "\n"
65                                              kTH_Left   "Member</th>"                           "\n"
66                                              kTH_Left   "Details</th>"                          "\n"
67                                              kTH_Left   "Description</th>" "</tr>";
68 const char* kTopicsTableHeader    = "  <tr>" kTH_Left   "Topic</th>"                            "\n"
69                                              kTH_Left   "Description</th>" "</tr>";
70 
anchorDef(string str,string name)71 string MdOut::anchorDef(string str, string name) {
72     if (fValidate) {
73         string htmlName = ParserCommon::HtmlFileName(fFileName);
74         vector<AnchorDef>& allDefs = fAllAnchorDefs[htmlName];
75         if (!std::any_of(allDefs.begin(), allDefs.end(),
76                 [str](AnchorDef compare) { return compare.fDef == str; } )) {
77             MarkType markType = fLastDef->fMarkType;
78             if (MarkType::kMethod == markType && fLastDef->fClone) {
79                 SkASSERT(0);  // incomplete
80             }
81             allDefs.push_back( { str, markType } );
82         }
83     }
84     return "<a name='" + str + "'>" + name + "</a>";
85 }
86 
anchorRef(string ref,string name)87 string MdOut::anchorRef(string ref, string name) {
88     if (fValidate) {
89         string htmlName;
90         size_t hashIndex = ref.find('#');
91         if (string::npos != hashIndex && "https://" != ref.substr(0, 8)) {
92             if (0 == hashIndex) {
93                 htmlName = ParserCommon::HtmlFileName(fFileName);
94             } else {
95                 htmlName = ref.substr(0, hashIndex);
96             }
97             vector<string>& allRefs = fAllAnchorRefs[htmlName];
98             string refPart = ref.substr(hashIndex + 1);
99             if (allRefs.end() == std::find(allRefs.begin(), allRefs.end(), refPart)) {
100                 allRefs.push_back(refPart);
101             }
102         }
103     }
104     SkASSERT(string::npos != ref.find('#') || string::npos != ref.find("https://"));
105     return "<a href='" + ref + "'>" + name + "</a>";
106 }
107 
anchorLocalRef(string ref,string name)108 string MdOut::anchorLocalRef(string ref, string name) {
109     return this->anchorRef("#" + ref, name);
110 }
111 
tableDataCodeRef(string ref,string name)112 string MdOut::tableDataCodeRef(string ref, string name) {
113     return kTD_Left + this->anchorRef(ref, "<code>" + name + "</code>") + "</td>";
114 }
115 
tableDataCodeLocalRef(string ref,string name)116 string MdOut::tableDataCodeLocalRef(string ref, string name) {
117     return this->tableDataCodeRef("#" + ref, name);
118 }
119 
tableDataCodeLocalRef(string name)120 string MdOut::tableDataCodeLocalRef(string name) {
121     return this->tableDataCodeLocalRef(name, name);
122 }
123 
tableDataCodeRef(const Definition * ref)124 string MdOut::tableDataCodeRef(const Definition* ref) {
125     return this->tableDataCodeLocalRef(ref->fFiddle, ref->fName);
126 }
127 
tableDataCodeDef(string def,string name)128 string MdOut::tableDataCodeDef(string def, string name) {
129     return kTD_Left + this->anchorDef(def, "<code>" + name + "</code>") + "</td>";
130 }
131 
tableDataCodeDef(const Definition * def)132 string MdOut::tableDataCodeDef(const Definition* def) {
133     return this->tableDataCodeDef(def->fFiddle, def->fName);
134 }
135 
table_data_const(const Definition * def,const char ** textStartPtr)136 static string table_data_const(const Definition* def, const char** textStartPtr) {
137     TextParser parser(def);
138     SkAssertResult(parser.skipToEndBracket('\n'));
139     string constant = string(def->fContentStart, (int) (parser.fChar - def->fContentStart));
140     if (textStartPtr) {
141         *textStartPtr = parser.fChar;
142     }
143     return kTD_Center + constant + "</td>";
144 }
145 
out_table_data_description_start()146 static string out_table_data_description_start() {
147     return kTD_Left;
148 }
149 
out_table_data_description(string str)150 static string out_table_data_description(string str) {
151     return kTD_Left + str + "</td>";
152 }
153 
out_table_data_description(const Definition * def)154 static string out_table_data_description(const Definition* def) {
155     return out_table_data_description(string(def->fContentStart,
156             (int) (def->fContentEnd - def->fContentStart)));
157 }
158 
out_table_data_details(string details)159 static string out_table_data_details(string details) {
160     return kTD_Left + details + "</td>";
161 }
162 
163 #undef kConstTDBase
164 #undef kTH_Center
165 
preformat(string orig)166 static string preformat(string orig) {
167     string result;
168     for (auto c : orig) {
169         if ('<' == c) {
170           result += "&lt;";
171         } else if ('>' == c) {
172           result += "&gt;";
173         } else {
174             result += c;
175         }
176     }
177     return result;
178 }
179 
180 // from https://stackoverflow.com/questions/3418231/replace-part-of-a-string-with-another-string
replace_all(string & str,const string & from,const string & to)181 void replace_all(string& str, const string& from, const string& to) {
182     SkASSERT(!from.empty());
183     size_t start_pos = 0;
184     while ((start_pos = str.find(from, start_pos)) != std::string::npos) {
185         str.replace(start_pos, from.length(), to);
186         start_pos += to.length(); // In case 'to' contains 'from', like replacing 'x' with 'yx'
187     }
188 }
189 
190 // detail strings are preceded by an example comment to check readability
addPopulators()191 void MdOut::addPopulators() {
192     auto populator = [this](string key, string singular, string plural, string oneLiner,
193             string details) -> void {
194         fPopulators[key].fSingular = singular;
195         fPopulators[key].fPlural = plural;
196         fPopulators[key].fOneLiner = oneLiner;
197         fPopulators[key].fDetails = details;
198     };
199     populator(SubtopicKeys::kClasses, "Class", "Class Declarations",
200             "embedded class members",
201             /* SkImageInfo */ "uses <code>class</code> to declare the public data structures"
202                               " and interfaces.");
203     populator(SubtopicKeys::kConstants, "Constant", "Constants",
204             "enum and enum class, and their const values",
205             /* SkImageInfo */ "defines related constants are using <code>enum</code>,"
206                               " <code>enum class</code>,  <code>#define</code>,"
207                               " <code>const</code>, and <code>constexpr</code>.");
208     populator(SubtopicKeys::kConstructors, "Constructor", "Constructors",
209             "functions that construct",
210             /* SkImageInfo */ "can be constructed or initialized by these functions,"
211                               " including <code>class</code> constructors.");
212     populator(SubtopicKeys::kDefines, "Define", "Defines",
213             "preprocessor definitions of functions, values",
214             /* SkImageInfo */ "uses preprocessor definitions to inline code and constants,"
215                               " and to abstract platform-specific functionality.");
216     populator(SubtopicKeys::kMemberFunctions, "Member Function", "Member Functions",
217             "static and local functions",
218             /* SkImageInfo */ "uses member functions to read and modify structure properties.");
219     populator(SubtopicKeys::kMembers, "Member", "Members",
220             "member values",
221             /* SkImageInfo */ "contains members that may be read and written directly without using"
222                               " a member function.");
223     populator(SubtopicKeys::kOperators, "Operator", "Operators",
224             "operator overloading functions",
225             /* SkImageInfo */ "defines member functions with arithmetic equivalents.");
226     populator(SubtopicKeys::kRelatedFunctions, "Related Function", "Related Functions",
227             "similar functions grouped together",
228             /* SkImageInfo */ "defines related functions that share a topic.");
229     populator(SubtopicKeys::kStructs, "Struct", "Struct Declarations",
230             "embedded struct members",
231             /* SkImageInfo */ "uses <code>struct</code> to declare the public data"
232                               " structures and interfaces.");
233     populator(SubtopicKeys::kTypedefs, "Typedef", "Typedef Declarations",
234             "types defined in terms of other types",
235             /* SkImageInfo */ "uses <code>typedef</code> to define a data type.");
236 }
237 
checkParentsForMatch(Definition * test,string ref) const238 Definition* MdOut::checkParentsForMatch(Definition* test, string ref) const {
239     bool isSubtopic = MarkType::kSubtopic == test->fMarkType
240             || MarkType::kTopic == test->fMarkType;
241     do {
242         if (!test->isRoot()) {
243             continue;
244         }
245         bool localTopic = MarkType::kSubtopic == test->fMarkType
246                 || MarkType::kTopic == test->fMarkType;
247         if (localTopic != isSubtopic) {
248             continue;
249         }
250         string prefix(isSubtopic ? "_" : "::");
251         RootDefinition* root = test->asRoot();
252         string prefixed = root->fName + prefix + ref;
253         if (Definition* def = root->find(prefixed, RootDefinition::AllowParens::kYes)) {
254             return def;
255         }
256     } while ((test = test->fParent));
257     return nullptr;
258 }
259 
260 struct BraceState {
BraceStateBraceState261     BraceState(RootDefinition* root, string name, const char* ch, KeyWord last, KeyWord keyWord,
262             int count)
263         : fRoot(root)
264         , fName(name)
265         , fChar(ch)
266         , fLastKey(last)
267         , fKeyWord(keyWord)
268         , fBraceCount(count) {
269     }
270 
271     RootDefinition* fRoot;
272     string fName;
273     const char* fChar;
274     KeyWord fLastKey;
275     KeyWord fKeyWord;
276     int fBraceCount;
277 };
278 
hasWordSpace(string wordSpace) const279 bool MdOut::DefinedState::hasWordSpace(string wordSpace) const {
280     if (!fNames->fRefMap.size()) {
281         return false;
282     }
283     for (const NameMap* names = fNames; names; names = names->fParent) {
284         if (names->fRefMap.end() != names->fRefMap.find(wordSpace)) {
285             return true;
286         }
287     }
288     return false;
289 }
290 
phraseContinues(string phrase,string * priorWord,string * priorLink) const291 bool MdOut::DefinedState::phraseContinues(string phrase, string* priorWord,
292         string* priorLink) const {
293     for (const NameMap* names = fNames; names; names = names->fParent) {
294         if (names->fRefMap.end() != names->fRefMap.find(phrase + ' ')) {
295             *priorWord = phrase;
296             return true;
297         }
298         if (names->fRefMap.end() != names->fRefMap.find(phrase)) {
299             *priorWord = phrase;
300             auto linkIter = names->fLinkMap.find(phrase);
301             *priorLink = names->fLinkMap.end() == linkIter ? "" : linkIter->second;
302             return true;
303         }
304     }
305     return false;
306 }
307 
setLink()308 void MdOut::DefinedState::setLink() {
309     fLink = "";
310     fPriorDef = nullptr;
311     // TODO: operators have complicated parsing possibilities; handle the easiest for now
312     // TODO: constructors also have complicated parsing possibilities; handle the easiest
313     bool isOperator = "operator" == fPriorWord;
314     if (((fRoot && fRoot->isStructOrClass() && fRoot->fName == fPriorWord) || isOperator)
315             && '(' == fSeparator.back()) {
316         SkASSERT(fSubtopic);
317         TextParser parser(fSubtopic->fFileName, fSeparatorStart, fRefEnd, fSubtopic->fLineCount);
318         parser.skipToEndBracket('(');
319         const char* parenStart = parser.fChar;
320         parser.skipToBalancedEndBracket('(', ')');
321         (void) parser.skipExact(" const");
322         string methodName = fPriorWord + fSeparator
323                 + string(parenStart + 1, parser.fChar - parenStart - 1);
324         string testLink;
325         if (this->findLink(methodName, &testLink, false)) {
326             // consume only if we find it
327             if (isOperator) {
328                 fPriorWord += fSeparator.substr(0, fSeparator.length() - 1);  // strip paren
329                 fPriorSeparator = "(";
330             }
331             fWord = "";
332             fPriorLink = testLink;
333             fEnd = parenStart + 1;
334             return;
335         }
336     }
337     // look to see if text following ref is method qualifier
338     else if ((Resolvable::kYes == fResolvable || Resolvable::kClone == fResolvable)
339             && "(" == fSeparator && "" != fPriorLink) {
340         TextParser parser(fLastDef->fFileName, fSeparatorStart, fRefEnd, fLastDef->fLineCount);
341         parser.skipToBalancedEndBracket('(', ')');
342         string fullMethod = fPriorWord + string(parser.fStart, parser.fChar - parser.fStart);
343         string trimmed = trim_inline_spaces(fullMethod);
344         string testLink;
345         if (findLink(trimmed, &testLink, false)) {
346             fMethodName = fullMethod;
347             fWord = trimmed;
348             fLink = testLink;
349             fEnd = parser.fChar;
350             this->backup();
351             return;
352         }
353     }
354     if ("." == fSeparator || "->" == fSeparator || "()." == fSeparator || "()->" == fSeparator) {
355         bool foundField = fWord.length() >= 2 && (('f' == fWord[0] && isupper(fWord[1]))
356                 || "()" == fWord.substr(fWord.length() - 2)
357                 || (fEnd + 2 <= fRefEnd && "()" == string(fEnd, 2)));
358         if (foundField) {
359             if (fMethod && fNames->fRefMap.end() != fNames->fRefMap.find(fPriorWord)) {
360         // find prior fWord's type in fMethod
361                 TextParser parser(fMethod);
362                 SkAssertResult(parser.containsWord(fPriorWord.c_str(), parser.fEnd,
363                         &parser.fChar));
364         // look up class or struct; trival lookup only class/struct [& * const]
365                 while (parser.back(" ") || parser.back("&") || parser.back("*")
366                         || parser.back("const"))
367                     ;
368                 const char* structEnd = parser.fChar;
369                 parser.backupWord();
370                 if (structEnd != parser.fChar) {
371                     string structName(parser.fChar, structEnd - parser.fChar);
372                     if ("SkVector" == structName) {
373                         // TODO: populate global refmap with typedefs as well as structs
374                         structName = "SkPoint";
375                     } else if ("SkIVector" == structName) {
376                         structName = "SkIPoint";
377                     }
378                     structName += "::" + fWord;
379         // look for fWord as member of class or struct
380                     auto defIter = fGlobals->fRefMap.find(structName);
381                     if (fGlobals->fRefMap.end() == defIter) {
382                         structName += "()";
383                         defIter = fGlobals->fRefMap.find(structName);
384                     }
385                     if (fGlobals->fRefMap.end() != defIter) {
386                         // example: dstInfo.width()
387                         auto structIter = fGlobals->fLinkMap.find(structName);
388                         SkASSERT(fGlobals->fLinkMap.end() != structIter);
389                         fLink = structIter->second;
390                         fPriorDef = defIter->second;
391                         return;
392                     } else {
393                         SkDebugf("probably missing struct or class member in bmh: ");
394                         SkDebugf("%s\n", structName.c_str());
395                     }
396                 }
397             }
398             auto& parentRefMap = fNames->fParent->fRefMap;
399             auto priorIter = parentRefMap.find(fPriorWord);
400             if (parentRefMap.end() == priorIter) {
401                 priorIter = parentRefMap.find(fPriorWord + "()");
402             }
403             if (parentRefMap.end() != priorIter) {
404                 Definition* priorDef = priorIter->second;
405                 if (priorDef) {
406                     TextParser parser(priorDef->fFileName, priorDef->fStart,
407                             priorDef->fContentStart, priorDef->fLineCount);
408                     parser.skipExact("#Method ");
409                     parser.skipSpace();
410                     parser.skipExact("const ");  // optional
411                     parser.skipSpace();
412                     const char* start = parser.fChar;
413                     parser.skipToNonAlphaNum();
414                     string structName(start, parser.fChar - start);
415                     structName += "::" + fWord;
416                     auto defIter = fGlobals->fRefMap.find(structName);
417                     if (fGlobals->fRefMap.end() != defIter) {
418                         // example: imageInfo().width()
419                         auto globalIter = fGlobals->fLinkMap.find(structName);
420                         SkASSERT(fGlobals->fLinkMap.end() != globalIter);
421                         fLink = globalIter->second;
422                         fPriorDef = defIter->second;
423                         return;
424                     }
425                 }
426             }
427         } else {
428             string fullRef = fPriorWord + fSeparator + fWord;
429             if (this->findLink(fullRef, &fLink, false)) {
430                 return;
431             }
432             if (Resolvable::kCode != fResolvable) {
433                 SkDebugf("probably missing () after function:");
434                 const char* debugStart = fEnd - 20 < fRefStart ? fRefStart : fEnd - 20;
435                 const char* debugEnd = fEnd + 10 > fRefEnd ? fRefEnd : fEnd + 10;
436                 SkDebugf("%.*s\n", debugEnd - debugStart, debugStart);
437                 SkDebugf(""); // convenient place to set a breakpoint
438             }
439         }
440     }
441     // example: SkCanvas::restoreToCount
442     if ("::" == fSeparator) {
443         string fullRef = fPriorWord + "::" + fWord;
444         if (this->findLink(fullRef, &fLink, fAddParens)) {
445             return;
446         }
447     }
448     // look in parent fNames and above for match
449     if (fNames) {
450         if (this->findLink(fWord, &fLink, (Resolvable::kClone == fResolvable && fAddParens)
451                 || (Resolvable::kCode == fResolvable && '(' == fEnd[0]))) {
452             return;
453         }
454     }
455     // example : sqrt as in "sqrt(x * x + y * y)"
456     // example : erase in seeAlso
457     if (Resolvable::kClone == fResolvable || (fEnd + 1 < fRefEnd && '(' == fEnd[0])) {
458         if ((fAddParens || '~' == fWord.front()) && this->findLink(fWord + "()", &fLink, false)) {
459             return;
460         }
461     }
462     // example: Color_Type
463     if (this->findLink(fWord, &fLink, fBmhParser->fAliasMap)) {
464         return;
465     }
466     if (Resolvable::kInclude != fResolvable && string::npos != fWord.find('_')) {
467         // example: Blend_Mode
468         if (this->findLink(fWord, &fLink, fBmhParser->fTopicMap)) {
469             return;
470         }
471         if (fSubtopic) {
472             // example: Fake_Bold
473             if (fSubtopic->fName == fWord) {
474                 fLink = '#' + fSubtopic->fFiddle;
475                 fPriorDef = fSubtopic;
476                 return;
477             }
478             const Definition* rootTopic = fSubtopic->subtopicParent();
479             if (rootTopic) {
480                 if (rootTopic->fFiddle == fWord) {
481                     fLink = '#' + rootTopic->fFiddle;
482                     fPriorDef = rootTopic;
483                     return;
484                 }
485                 string globName = rootTopic->fFiddle + '_' + fWord;
486                 if (this->findLink(globName, &fLink, fBmhParser->fTopicMap)) {
487                     return;
488                 }
489             }
490         }
491         if (fRoot) {
492             string test = fRoot->fName + "::" + fWord;
493             auto rootIter = fRoot->fLeaves.find(test);
494             // example: restoreToCount in subtopic State_Stack
495             if (fRoot->fLeaves.end() != rootIter) {
496                 fLink = '#' + rootIter->second.fFiddle;
497                 fPriorDef = &rootIter->second;
498                 return;
499             }
500         }
501     }
502     if (isupper(fWord[0]) && string::npos != fWord.find('_')) {
503         const Definition* topical = fSubtopic;
504         do {
505             string subtopic = topical->fName + '_' + fWord;
506             // example: Stroke_Width
507             if (this->findLink(subtopic, &fLink, fBmhParser->fTopicMap)) {
508                 return;
509             }
510         } while ((topical = topical->topicParent()));
511     }
512     // treat hex constants as known words
513     if (fSeparator.size() > 0 && '0' == fSeparator.back() && 'x' == fWord[0]) {
514         bool allHex = true;
515         for (size_t index = 1; index < fWord.size(); ++index) {
516             char c = fWord[index];
517             if (('0' > c || '9' < c) && ('A' > c || 'F' < c)) {
518                 allHex = false;
519                 break;
520             }
521         }
522         if (allHex) {
523             return;
524         }
525     }
526     // treat floating constants as known words
527     if ("e" == fWord) {
528         if (std::all_of(fSeparator.begin(), fSeparator.end(), [](char c) {
529             return isdigit(c) || '.' == c || '-' == c || ' ' >= c;
530         })) {
531             return;
532         }
533     }
534     // stop short of parsing example; just look to see if it contains fWord in description
535     if (fLastDef && MarkType::kDescription == fLastDef->fMarkType) {
536         Definition* example = fLastDef->fParent;
537         if (MarkType::kExample == example->fMarkType) {
538             // example text is blocked by last child before std out, if it exists
539             const char* exStart = example->fChildren.back()->fContentEnd;
540             const char* exEnd = example->fContentEnd;
541             if (MarkType::kStdOut == example->fChildren.back()->fMarkType) {
542                 exStart = example->fChildren[example->fChildren.size() - 2]->fContentEnd;
543                 exEnd = example->fChildren.back()->fContentStart;
544             }
545             // maybe need a general function that searches block text excluding children
546             TextParser exParse(example->fFileName, exStart, exEnd, example->fLineCount);
547             if (exParse.containsWord(fWord.c_str(), exParse.fEnd, nullptr)) {
548                 return;
549             }
550         }
551     }
552     // example: (x1, y1) after arcTo(SkScalar x1, ...
553     if (Resolvable::kYes == fResolvable && "" != fSeparator
554             && ('(' == fSeparator.back() || ',' == fSeparator[0])
555             && string::npos != fMethodName.find(fWord)) {
556         return;
557     }
558     // example: <sup>  (skip html)
559     if (Resolvable::kYes == fResolvable && fEnd + 1 < fRefEnd && '>' == fEnd[0] && "" != fSeparator
560             && ('<' == fSeparator.back() || (fSeparator.size() >= 2
561             && "</" == fSeparator.substr(fSeparator.size() - 2)))) {
562         return;
563     }
564     bool paramName = islower(fWord[0]) && (Resolvable::kCode == fResolvable
565             || Resolvable::kClone == fResolvable);
566     // TODO: can probably resolve formulae, but need a way for formula to define new reference
567     // for example: Given: #Formula # Sa ## as source Alpha,
568     // for example: where #Formula # m = Da > 0 ? Dc / Da : 0 ##;
569     if (!fInProgress && Resolvable::kSimple != fResolvable
570             && !paramName && Resolvable::kFormula != fResolvable) {
571         // example: Coons as in "Coons patch"
572         bool withSpace = fEnd + 1 < fRefEnd && ' ' == fEnd[0]
573                 && fGlobals->fRefMap.end() != fGlobals->fRefMap.find(fWord + ' ');
574         if (!withSpace && (Resolvable::kInclude == fResolvable ? !fInMatrix :
575                 '"' != fPriorSeparator.back() || '"' != fSeparator.back())) {
576             SkDebugf("word %s not found\n", fWord.c_str());
577             fBmhParser->fGlobalNames.fRefMap[fWord] = nullptr;
578         }
579     }
580 }
581 
582 
addReferences(const char * refStart,const char * refEnd,Resolvable resolvable)583 string MdOut::addReferences(const char* refStart, const char* refEnd, Resolvable resolvable) {
584     DefinedState s(*this, refStart, refEnd, resolvable);
585     string result;
586     const char* start = refStart;
587     do {
588         s.fSeparatorStart = start;
589         start = s.skipWhiteSpace();
590         s.skipParens();
591         string separator = s.nextSeparator(start);
592         if (fDebugWriteCodeBlock) {
593             SkDebugf("%s", separator.c_str());
594         }
595         result += separator;
596         if (s.findEnd(start)) {
597             break;
598         }
599         s.fWord = string(start, s.fEnd - start);
600         if ("TODO" == s.fWord) {
601             while('\n' != *s.fEnd++)
602                 ;
603             start = s.fEnd;
604             continue;
605         }
606         s.setLower();
607         if (s.setPriorSpaceWord(&start)) {
608             continue;
609         }
610         s.setLink();
611         string link = "" == s.fPriorLink ? s.fPriorWord :
612                 this->anchorRef(s.fPriorLink, s.fPriorWord);
613         if (fDebugWriteCodeBlock) {
614             SkDebugf("%s", link.c_str());
615         }
616         result += link;
617         start = s.nextWord();
618     } while (true);
619     string finalLink = "" == s.fPriorLink ? s.fPriorWord :
620             this->anchorRef(s.fPriorLink, s.fPriorWord);
621     if (fDebugWriteCodeBlock) {
622         SkDebugf("%s", finalLink.c_str());
623     }
624     result += finalLink;
625     if (fDebugWriteCodeBlock) {
626         SkDebugf("%s", s.fPriorSeparator.c_str());
627     }
628     result += s.fPriorSeparator;
629     return result;
630 }
631 
buildReferences(const char * docDir,const char * mdFileOrPath)632 bool MdOut::buildReferences(const char* docDir, const char* mdFileOrPath) {
633     if (!sk_isdir(mdFileOrPath)) {
634         SkDebugf("must pass directory %s\n", mdFileOrPath);
635         SkDebugf("pass -i SkXXX.h to build references for a single include\n");
636         return false;
637     }
638     fInProgress = true;
639     SkOSFile::Iter it(docDir, ".bmh");
640     for (SkString file; it.next(&file); ) {
641         if (!fIncludeParser.references(file)) {
642             continue;
643         }
644         SkString p = SkOSPath::Join(docDir, file.c_str());
645         if (!this->buildRefFromFile(p.c_str(), mdFileOrPath)) {
646             SkDebugf("failed to parse %s\n", p.c_str());
647             return false;
648         }
649     }
650     return true;
651 }
652 
buildStatus(const char * statusFile,const char * outDir)653 bool MdOut::buildStatus(const char* statusFile, const char* outDir) {
654     StatusIter iter(statusFile, ".bmh", StatusFilter::kInProgress);
655     StatusFilter filter;
656     for (string file; iter.next(&file, &filter); ) {
657         SkString p = SkOSPath::Join(iter.baseDir().c_str(), file.c_str());
658         const char* hunk = p.c_str();
659         fInProgress = StatusFilter::kInProgress == filter;
660         if (!this->buildRefFromFile(hunk, outDir)) {
661             SkDebugf("failed to parse %s\n", hunk);
662             return false;
663         }
664     }
665     return true;
666 }
667 
buildRefFromFile(const char * name,const char * outDir)668 bool MdOut::buildRefFromFile(const char* name, const char* outDir) {
669     if (!SkStrEndsWith(name, ".bmh")) {
670         return true;
671     }
672     if (SkStrEndsWith(name, "markup.bmh")) {  // don't look inside this for now
673         return true;
674     }
675     if (SkStrEndsWith(name, "illustrations.bmh")) {  // don't look inside this for now
676         return true;
677     }
678     if (SkStrEndsWith(name, "undocumented.bmh")) {  // don't look inside this for now
679         return true;
680     }
681     fFileName = string(name);
682     string filename(name);
683     if (filename.substr(filename.length() - 4) == ".bmh") {
684         filename = filename.substr(0, filename.length() - 4);
685     }
686     size_t start = filename.length();
687     while (start > 0 && (isalnum(filename[start - 1]) || '_' == filename[start - 1])) {
688         --start;
689     }
690     string match = filename.substr(start);
691     string header = match;
692     filename = match + ".md";
693     match += ".bmh";
694     fOut = nullptr;
695     string fullName;
696 
697     vector<string> keys;
698     keys.reserve(fBmhParser.fTopicMap.size());
699     for (const auto& it : fBmhParser.fTopicMap) {
700         keys.push_back(it.first);
701     }
702     std::sort(keys.begin(), keys.end());
703     for (auto key : keys) {
704         string s(key);
705         auto topicDef = fBmhParser.fTopicMap.at(s);
706         if (topicDef->fParent) {
707             continue;
708         }
709         if (string::npos == topicDef->fFileName.rfind(match)) {
710             continue;
711         }
712         if (!fOut) {
713             fullName = outDir;
714             if ('/' != fullName.back()) {
715                 fullName += '/';
716             }
717             fullName += filename;
718             fOut = fopen(filename.c_str(), "wb");
719             if (!fOut) {
720                 SkDebugf("could not open output file %s\n", fullName.c_str());
721                 return false;
722             }
723             if (false) {    // try inlining the style
724                 FPRINTF("%s", kConstTableStyle);
725             }
726             size_t underscorePos = header.find('_');
727             if (string::npos != underscorePos) {
728                 header.replace(underscorePos, 1, " ");
729             }
730             SkASSERT(string::npos == header.find('_'));
731             this->writeString(header);
732             this->lfAlways(1);
733             this->writeString("===");
734             this->lfAlways(1);
735         }
736         const Definition* prior = nullptr;
737         this->markTypeOut(topicDef, &prior);
738     }
739     if (fOut) {
740         this->writePending();
741         fclose(fOut);
742         fflush(fOut);
743         if (ParserCommon::WrittenFileDiffers(fullName, filename)) {
744             ParserCommon::CopyToFile(fullName, filename);
745             SkDebugf("wrote %s\n", fullName.c_str());
746         } else {
747             remove(filename.c_str());
748         }
749         fOut = nullptr;
750     }
751     return !fAddRefFailed;
752 }
753 
contains_referenced_child(const Definition * found,const vector<string> & refs)754 static bool contains_referenced_child(const Definition* found, const vector<string>& refs) {
755     for (auto child : found->fChildren) {
756         if (refs.end() != std::find_if(refs.begin(), refs.end(),
757                     [child](string def) { return child->fName == def; } )) {
758             return true;
759         }
760         if (contains_referenced_child(child, refs)) {
761             return true;
762         }
763     }
764     return false;
765 }
766 
checkAnchors()767 void MdOut::checkAnchors() {
768     int missing = 0;
769     for (auto bmhFile : fAllAnchorRefs) {
770         auto defIter = fAllAnchorDefs.find(bmhFile.first);
771         SkASSERT(fAllAnchorDefs.end() != defIter);
772         vector<AnchorDef>& allDefs = defIter->second;
773         std::sort(allDefs.begin(), allDefs.end(),
774                 [](const AnchorDef& a, const AnchorDef& b) { return a.fDef < b.fDef; } );
775         std::sort(bmhFile.second.begin(), bmhFile.second.end());
776         auto allDefsIter = allDefs.begin();
777         auto allRefsIter = bmhFile.second.begin();
778         for (;;) {
779             bool allDefsEnded = allDefsIter == allDefs.end();
780             bool allRefsEnded = allRefsIter == bmhFile.second.end();
781             if (allDefsEnded && allRefsEnded) {
782                 break;
783             }
784             if (allRefsEnded || (!allDefsEnded && allDefsIter->fDef < *allRefsIter)) {
785                 if (MarkType::kParam != allDefsIter->fMarkType) {
786                     // If undocumented but parent or child is referred to: good enough for now
787                     bool goodEnough = false;
788                     if ("undocumented" == defIter->first) {
789                         auto iter = fBmhParser.fTopicMap.find(allDefsIter->fDef);
790                         if (fBmhParser.fTopicMap.end() != iter) {
791                             const Definition* found = iter->second;
792                             if (string::npos != found->fFileName.find("undocumented")) {
793                                 const Definition* parent = found;
794                                 while ((parent = parent->fParent)) {
795                                     if (bmhFile.second.end() != std::find_if(bmhFile.second.begin(),
796                                             bmhFile.second.end(),
797                                             [parent](string def) {
798                                             return parent->fName == def; } )) {
799                                         goodEnough = true;
800                                         break;
801                                     }
802                                 }
803                                 if (!goodEnough) {
804                                     goodEnough = contains_referenced_child(found, bmhFile.second);
805                                 }
806                             }
807                         }
808                     }
809                     if (!goodEnough) {
810                         SkDebugf("missing ref %s %s\n", defIter->first.c_str(),
811                                 allDefsIter->fDef.c_str());
812                         missing++;
813                     }
814                 }
815                 allDefsIter++;
816             } else if (allDefsEnded || (!allRefsEnded && allDefsIter->fDef > *allRefsIter)) {
817                 if (fBmhParser.fExternals.end() == std::find_if(fBmhParser.fExternals.begin(),
818                         fBmhParser.fExternals.end(), [allRefsIter](const RootDefinition& root) {
819                         return *allRefsIter != root.fName; } )) {
820                     SkDebugf("missing def %s %s\n", bmhFile.first.c_str(), allRefsIter->c_str());
821                     missing++;
822                 }
823                 allRefsIter++;
824             } else {
825                 SkASSERT(!allDefsEnded);
826                 SkASSERT(!allRefsEnded);
827                 SkASSERT(allDefsIter->fDef == *allRefsIter);
828                 allDefsIter++;
829                 allRefsIter++;
830             }
831             if (missing >= 10) {
832                 missing = 0;
833             }
834         }
835     }
836 }
837 
checkParamReturnBody(const Definition * def)838 bool MdOut::checkParamReturnBody(const Definition* def) {
839     TextParser paramBody(def);
840     const char* descriptionStart = paramBody.fChar;
841     if (!islower(descriptionStart[0]) && !isdigit(descriptionStart[0])) {
842         paramBody.skipToNonName();
843         string ref = string(descriptionStart, paramBody.fChar - descriptionStart);
844         if (!std::all_of(ref.begin(), ref.end(), [](char c) { return isupper(c); })
845                 && !this->isDefined(paramBody, Resolvable::kYes)) {
846             string errorStr = MarkType::kReturn == def->fMarkType ? "return" : "param";
847             errorStr += " description must start with lower case";
848             paramBody.reportError(errorStr.c_str());
849             fAddRefFailed = true;
850             return false;
851         }
852     }
853     if ('.' == paramBody.fEnd[-1]) {
854         paramBody.reportError("make param description a phrase; should not end with period");
855         fAddRefFailed = true;
856         return false;
857     }
858     return true;
859 }
860 
childrenOut(Definition * def,const char * start)861 void MdOut::childrenOut(Definition* def, const char* start) {
862     const char* end;
863     fLineCount = def->fLineCount;
864     if (MarkType::kEnumClass == def->fMarkType) {
865         fEnumClass = def;
866     }
867     Resolvable resolvable = this->resolvable(def);
868     const Definition* prior = nullptr;
869     for (auto& child : def->fChildren) {
870         if (MarkType::kPhraseParam == child->fMarkType) {
871             continue;
872         }
873         end = child->fStart;
874         if (Resolvable::kNo != resolvable) {
875             if (def->isStructOrClass() || MarkType::kEnumClass == def->fMarkType) {
876                 fNames = &def->asRoot()->fNames;
877             }
878             this->resolveOut(start, end, resolvable);
879         }
880         this->markTypeOut(child, &prior);
881         start = child->fTerminator;
882     }
883     if (Resolvable::kNo != resolvable) {
884         end = def->fContentEnd;
885         if (MarkType::kFormula == def->fMarkType && ' ' == start[0]) {
886             this->writeSpace();
887         }
888         this->resolveOut(start, end, resolvable);
889     }
890     if (MarkType::kEnumClass == def->fMarkType) {
891         fEnumClass = nullptr;
892     }
893 }
894 
895 // output header for subtopic for all consts: name, value, short descriptions (#Line)
896 // output link to in context #Const with moderate description
summaryOut(const Definition * def,MarkType markType,string name)897 void MdOut::summaryOut(const Definition* def, MarkType markType, string name) {
898     this->writePending();
899     SkASSERT(TableState::kNone == fTableState);
900     this->mdHeaderOut(3);
901     FPRINTF("%s", name.c_str());
902     this->lfAlways(2);
903     FPRINTF("%s", kTableDeclaration);  // <table> with style info
904     this->lfAlways(1);
905     FPRINTF("%s", MarkType::kConst == markType ? kAllConstTableHeader : kAllMemberTableHeader);
906     this->lfAlways(1);
907     bool odd = true;
908     for (auto child : def->fChildren) {
909         if (markType != child->fMarkType) {
910             continue;
911         }
912         auto oneLiner = std::find_if(child->fChildren.begin(), child->fChildren.end(),
913                 [](const Definition* test){ return MarkType::kLine == test->fMarkType; } );
914         if (child->fChildren.end() == oneLiner) {
915             child->reportError<void>("missing #Line");
916             continue;
917         }
918         FPRINTF("%s", odd ? kTR_Dark.c_str() : "  <tr>");
919         this->lfAlways(1);
920         if (MarkType::kConst == markType) {
921             FPRINTF("%s", tableDataCodeRef(child).c_str());
922             this->lfAlways(1);
923             FPRINTF("%s", table_data_const(child, nullptr).c_str());
924         } else {
925             string memberType;
926             string memberName = this->getMemberTypeName(child, &memberType);
927             SkASSERT(MarkType::kMember == markType);
928             FPRINTF("%s", out_table_data_description(memberType).c_str());
929             this->lfAlways(1);
930             FPRINTF("%s", tableDataCodeLocalRef(memberName).c_str());
931         }
932         this->lfAlways(1);
933         FPRINTF("%s", out_table_data_description(*oneLiner).c_str());
934         this->lfAlways(1);
935         FPRINTF("%s", "  </tr>");
936         this->lfAlways(1);
937         odd = !odd;
938     }
939     FPRINTF("</table>");
940     this->lfAlways(1);
941 }
942 
csParent()943 Definition* MdOut::csParent() {
944     if (!fRoot) {
945         return nullptr;
946     }
947     Definition* csParent = fRoot->csParent();
948     if (!csParent) {
949         const Definition* topic = fRoot;
950         while (topic && MarkType::kTopic != topic->fMarkType) {
951             topic = topic->fParent;
952         }
953         for (auto child : topic->fChildren) {
954             if (child->isStructOrClass() || MarkType::kTypedef == child->fMarkType) {
955                 csParent = child;
956                 break;
957             }
958         }
959         SkASSERT(csParent || string::npos == fRoot->fFileName.find("Sk")
960                 || string::npos != fRoot->fFileName.find("SkBlendMode_Reference.bmh"));
961     }
962     return csParent;
963 }
964 
findLink(string word,string * linkPtr,bool addParens)965 bool MdOut::DefinedState::findLink(string word, string* linkPtr, bool addParens) {
966     const NameMap* names = fNames;
967     do {
968         auto localIter = names->fRefMap.find(word);
969         if (names->fRefMap.end() != localIter) {
970             if ((fPriorDef = localIter->second)) {
971                 auto linkIter = names->fLinkMap.find(word);
972                 SkAssertResult(names->fLinkMap.end() != linkIter);
973                 *linkPtr = linkIter->second;
974             }
975             return true;
976         }
977         if (!names->fParent && isupper(word[0])) {
978             SkASSERT(names == &fBmhParser->fGlobalNames);
979             string lower = (char) tolower(word[0]) + word.substr(1);
980             auto globalIter = names->fRefMap.find(lower);
981             if (names->fRefMap.end() != globalIter) {
982                 if ((fPriorDef = globalIter->second)) {
983                     auto lowerIter = names->fLinkMap.find(lower);
984                     SkAssertResult(names->fLinkMap.end() != lowerIter);
985                     *linkPtr = lowerIter->second;
986                 }
987                 return true;
988             }
989         }
990         if (addParens) {
991             string parenWord = word + "()";
992             auto paramIter = names->fRefMap.find(parenWord);
993             if (names->fRefMap.end() != paramIter) {
994                 if ((fPriorDef = paramIter->second)) {
995                     auto parenIter = names->fLinkMap.find(parenWord);
996                     SkAssertResult(names->fLinkMap.end() != parenIter);
997                     *linkPtr = parenIter->second;
998                 }
999                 return true;
1000             }
1001         }
1002     } while ((names = names->fParent));
1003     return false;
1004 }
1005 
findLink(string word,string * linkPtr,unordered_map<string,Definition * > & map)1006 bool MdOut::DefinedState::findLink(string word, string* linkPtr,
1007         unordered_map<string, Definition*>& map) {
1008     auto mapIter = map.find(word);
1009     if (map.end() != mapIter) {
1010         if ((fPriorDef = mapIter->second)) {
1011             *linkPtr = '#' + mapIter->second->fFiddle;
1012         }
1013         return true;
1014     }
1015     return false;
1016 }
1017 
findParamType()1018 const Definition* MdOut::findParamType() {
1019     SkASSERT(fMethod);
1020     TextParser parser(fMethod->fFileName, fMethod->fStart, fMethod->fContentStart,
1021             fMethod->fLineCount);
1022     string lastFull;
1023     do {
1024         parser.skipToAlpha();
1025         if (parser.eof()) {
1026             return nullptr;
1027         }
1028         const char* word = parser.fChar;
1029         parser.skipFullName();
1030         SkASSERT(!parser.eof());
1031         string name = string(word, parser.fChar - word);
1032         if (fLastParam->fName == name) {
1033             const Definition* paramType = this->isDefined(parser, Resolvable::kOut);
1034             return paramType;
1035         }
1036         if (isupper(name[0])) {
1037             lastFull = name;
1038         }
1039     } while (true);
1040     return nullptr;
1041 }
1042 
getMemberTypeName(const Definition * def,string * memberType)1043 string MdOut::getMemberTypeName(const Definition* def, string* memberType) {
1044     TextParser parser(def->fFileName, def->fStart, def->fContentStart,
1045             def->fLineCount);
1046     parser.skipExact("#Member");
1047     parser.skipWhiteSpace();
1048     const char* typeStart = parser.fChar;
1049     const char* typeEnd = nullptr;
1050     const char* nameStart = nullptr;
1051     const char* nameEnd = nullptr;
1052     do {
1053         parser.skipToWhiteSpace();
1054         if (nameStart) {
1055             nameEnd = parser.fChar;
1056         }
1057         if (parser.eof()) {
1058             break;
1059         }
1060         const char* spaceLoc = parser.fChar;
1061         if (parser.skipWhiteSpace()) {
1062             typeEnd = spaceLoc;
1063             nameStart = parser.fChar;
1064         }
1065     } while (!parser.eof());
1066     SkASSERT(typeEnd);
1067     *memberType = string(typeStart, (int) (typeEnd - typeStart));
1068     replace_all(*memberType, " ", "&nbsp;");
1069     SkASSERT(nameStart);
1070     SkASSERT(nameEnd);
1071     return string(nameStart, (int) (nameEnd - nameStart));
1072 }
1073 
HasDetails(const Definition * def)1074 bool MdOut::HasDetails(const Definition* def) {
1075     for (auto child : def->fChildren) {
1076         if (MarkType::kDetails == child->fMarkType) {
1077             return true;
1078         }
1079         if (MdOut::HasDetails(child)) {
1080             return true;
1081         }
1082     }
1083     return false;
1084 }
1085 
htmlOut(string s)1086 void MdOut::htmlOut(string s) {
1087     SkASSERT(string::npos != s.find('<'));
1088     FPRINTF("%s", s.c_str());
1089 }
1090 
isDefined(const TextParser & parser,Resolvable resolvable)1091 const Definition* MdOut::isDefined(const TextParser& parser, Resolvable resolvable) {
1092     DefinedState s(*this, parser.fStart, parser.fEnd, resolvable);
1093     const char* start = parser.fStart;
1094     do {
1095         s.fSeparatorStart = start;
1096         start = s.skipWhiteSpace();
1097         s.skipParens();
1098         (void) s.nextSeparator(start);
1099         if (s.findEnd(start)) {
1100             return nullptr;
1101         }
1102         s.fWord = string(start, s.fEnd - start);
1103         s.setLower();
1104     } while (s.setPriorSpaceWord(&start));
1105     s.setLink();
1106     return s.fPriorDef;
1107 }
1108 
linkName(const Definition * ref) const1109 string MdOut::linkName(const Definition* ref) const {
1110     string result = ref->fName;
1111     size_t under = result.find('_');
1112     if (string::npos != under) {
1113         string classPart = result.substr(0, under);
1114         string namePart = result.substr(under + 1, result.length());
1115         if (fRoot && (fRoot->fName == classPart
1116                 || (fRoot->fParent && fRoot->fParent->fName == classPart))) {
1117             result = namePart;
1118         }
1119     }
1120     replace_all(result, "::", "_");
1121     return result;
1122 }
1123 
writeTableEnd(MarkType markType,Definition * def,const Definition ** prior)1124 static bool writeTableEnd(MarkType markType, Definition* def, const Definition** prior) {
1125     return markType != def->fMarkType && *prior && markType == (*prior)->fMarkType;
1126 }
1127 
1128 // Recursively build string with declarative code. Skip structs, classes, that
1129 // have been built directly.
addCodeBlock(const Definition * def,string & result) const1130 void MdOut::addCodeBlock(const Definition* def, string& result) const {
1131     const Definition* last = nullptr;
1132     bool wroteFunction = false;
1133     for (auto member : def->fChildren) {
1134         const Definition* prior = last;
1135         const char* priorTerminator = nullptr;
1136         if (prior) {
1137             priorTerminator = prior->fTerminator ? prior->fTerminator : prior->fContentEnd;
1138         }
1139         last = member;
1140         if (KeyWord::kIfndef == member->fKeyWord) {
1141             this->addCodeBlock(member, result);
1142             continue;
1143         }
1144         if (KeyWord::kClass == member->fKeyWord || KeyWord::kStruct == member->fKeyWord
1145                 || KeyWord::kTemplate == member->fKeyWord) {
1146             if (!member->fChildren.size()) {
1147                 continue;
1148             }
1149             // todo: Make sure this was written non-elided somewhere else
1150             // todo: provide indent value?
1151             string block = fIncludeParser.elidedCodeBlock(*member);
1152             // add italic link for elided body
1153             size_t brace = block.find('{');
1154             if (string::npos != brace) {
1155                 string name = member->fName;
1156                 if ("" == name) {
1157                     for (auto child : member->fChildren) {
1158                         if ("" != (name = child->fName)) {
1159                             break;
1160                         }
1161                     }
1162                 }
1163                 SkASSERT("" != name);
1164                 string body = "\n    // <i>" + name + " interface</i>";
1165                 block = block.substr(0, brace + 1) + body + block.substr(brace + 1);
1166             }
1167             this->stringAppend(result, block);
1168             continue;
1169         }
1170         if (KeyWord::kEnum == member->fKeyWord) {
1171             if (member->fChildren.empty()) {
1172                 continue;
1173             }
1174             auto tokenIter = member->fTokens.begin();
1175             if (KeyWord::kEnum == member->fKeyWord && KeyWord::kClass == tokenIter->fKeyWord) {
1176                 tokenIter = tokenIter->fTokens.begin();
1177             }
1178             while (Definition::Type::kWord != tokenIter->fType) {
1179                 std::advance(tokenIter, 1);
1180             }
1181             const auto& token = *tokenIter;
1182             string name = string(token.fContentStart, token.length());
1183             SkASSERT(name.length() > 0);
1184             MarkType markType = KeyWord::kClass == member->fKeyWord
1185                     || KeyWord::kStruct == member->fKeyWord ? MarkType::kClass : MarkType::kEnum;
1186             // find bmh def or just find name of class / struct / enum ? (what if enum is nameless?)
1187             if (wroteFunction) {
1188                 this->stringAppend(result, '\n');
1189                 wroteFunction = false;
1190             }
1191             this->stringAppend(result,
1192                     fIncludeParser.codeBlock(markType, name, fInProgress));
1193             this->stringAppend(result, '\n');
1194             continue;
1195         }
1196         // Global function declarations are not preparsed very well;
1197         // make do by using the prior position to find the start
1198         if (Bracket::kParen == member->fBracket && prior) {
1199             TextParser function(member->fFileName, priorTerminator, member->fTerminator + 1,
1200                     member->fLineCount);
1201             this->stringAppend(result,
1202                     fIncludeParser.writeCodeBlock(function, MarkType::kFunction, 0));
1203             this->stringAppend(result, ";\n");
1204             wroteFunction = true;
1205             continue;
1206         }
1207         if (KeyWord::kTypedef == member->fKeyWord) {
1208             this->stringAppend(result, member);
1209             this->stringAppend(result, ";\n");
1210             continue;
1211         }
1212         if (KeyWord::kDefine == member->fKeyWord) {
1213             string body(member->fContentStart, member->length());
1214             if (string::npos != body.find('(')) {
1215                 this->stringAppend(result, body);
1216                 this->stringAppend(result, '\n');
1217             }
1218             continue;
1219         }
1220         if (KeyWord::kConstExpr == member->fKeyWord) {
1221             this->stringAppend(result, member);
1222             auto nextMember = def->fTokens.begin();
1223             unsigned tokenPos = member->fParentIndex + 1;
1224             SkASSERT(tokenPos < def->fTokens.size());
1225             std::advance(nextMember, tokenPos);
1226             while (member->fContentEnd >= nextMember->fContentStart) {
1227                 std::advance(nextMember, 1);
1228                 SkASSERT(++tokenPos < def->fTokens.size());
1229             }
1230             while (Punctuation::kSemicolon != nextMember->fPunctuation) {
1231                 std::advance(nextMember, 1);
1232                 SkASSERT(++tokenPos < def->fTokens.size());
1233             }
1234             TextParser between(member->fFileName, member->fContentEnd,
1235                     nextMember->fContentStart, member->fLineCount);
1236             between.skipWhiteSpace();
1237             if ('=' == between.peek()) {
1238                 this->stringAppend(result, ' ');
1239                 string middle(between.fChar, nextMember->fContentStart);
1240                 this->stringAppend(result, middle);
1241                 last = nullptr;
1242             } else {
1243                 SkAssertResult(';' == between.peek());
1244             }
1245             this->stringAppend(result, ';');
1246             this->stringAppend(result, '\n');
1247             continue;
1248         }
1249     }
1250 }
1251 
markTypeOut(Definition * def,const Definition ** prior)1252 void MdOut::markTypeOut(Definition* def, const Definition** prior) {
1253     string printable = def->printableName();
1254     const char* textStart = def->fContentStart;
1255     bool lookForOneLiner = false;
1256     // #Param and #Const don't have markers to say when the last is seen, so detect that by looking
1257     // for a change in type.
1258     if (writeTableEnd(MarkType::kParam, def, prior) || writeTableEnd(MarkType::kConst, def, prior)
1259                 || writeTableEnd(MarkType::kMember, def, prior)) {
1260         this->writePending();
1261         FPRINTF("</table>");
1262         this->lf(2);
1263         fTableState = TableState::kNone;
1264     }
1265     fLastDef = def;
1266     NameMap paramMap;
1267     switch (def->fMarkType) {
1268         case MarkType::kAlias:
1269             break;
1270         case MarkType::kAnchor: {
1271             if (fColumn > 0) {
1272                 this->writeSpace();
1273             }
1274             this->writePending();
1275             TextParser parser(def);
1276             const char* start = parser.fChar;
1277             parser.skipToEndBracket((string(" ") + def->fMC + " ").c_str());
1278             string anchorText(start, parser.fChar - start);
1279             parser.skipExact((string(" ") + def->fMC + " ").c_str());
1280             string anchorLink(parser.fChar, parser.fEnd - parser.fChar);
1281             this->htmlOut(anchorRef(anchorLink, anchorText));
1282             } break;
1283         case MarkType::kBug:
1284             break;
1285         case MarkType::kClass:
1286         case MarkType::kStruct:
1287             fRoot = def->asRoot();
1288             this->lfAlways(2);
1289             if (MarkType::kStruct == def->fMarkType) {
1290                 this->htmlOut(anchorDef(def->fFiddle, ""));
1291             } else {
1292                 this->htmlOut(anchorDef(this->linkName(def), ""));
1293             }
1294             this->lfAlways(2);
1295             FPRINTF("---");
1296             this->lf(2);
1297             break;
1298         case MarkType::kCode:
1299             this->lfAlways(2);
1300             FPRINTF("<pre style=\"padding: 1em 1em 1em 1em;"
1301                     "width: 62.5em; background-color: #f0f0f0\">");
1302             this->lf(1);
1303             fResolveAndIndent = true;
1304             break;
1305         case MarkType::kColumn:
1306             this->writePending();
1307             if (fInList) {
1308                 FPRINTF("    <td>");
1309             } else {
1310                 FPRINTF("| ");
1311             }
1312             break;
1313         case MarkType::kComment:
1314             break;
1315         case MarkType::kMember:
1316         case MarkType::kConst: {
1317             bool isConst = MarkType::kConst == def->fMarkType;
1318             lookForOneLiner = false;
1319             fWroteSomething = false;
1320         // output consts for one parent with moderate descriptions
1321         // optional link to subtopic with longer descriptions, examples
1322             if (TableState::kNone == fTableState) {
1323                 SkASSERT(!*prior || (isConst && MarkType::kConst != (*prior)->fMarkType)
1324                         || (!isConst && MarkType::kMember != (*prior)->fMarkType));
1325                 if (isConst) {
1326                     this->mdHeaderOut(3);
1327                     this->writeString(this->fPopulators[SubtopicKeys::kConstants].fPlural);
1328                     this->lfAlways(2);
1329                 }
1330                 FPRINTF("%s", kTableDeclaration);
1331                 fTableState = TableState::kRow;
1332                 fOddRow = true;
1333                 this->lfAlways(1);
1334                 // look ahead to see if the details column has data or not
1335                 fHasDetails = MdOut::HasDetails(def->fParent);
1336                 FPRINTF("%s", fHasDetails ? \
1337                         (isConst ? kSubConstTableHeader : kSubMemberTableHeader) : \
1338                         (isConst ? kAllConstTableHeader : kAllMemberTableHeader));
1339                 this->lfAlways(1);
1340             }
1341             if (TableState::kRow == fTableState) {
1342                 this->writePending();
1343                 FPRINTF("%s", fOddRow ? kTR_Dark.c_str() : "  <tr>");
1344                 fOddRow = !fOddRow;
1345                 this->lfAlways(1);
1346                 fTableState = TableState::kColumn;
1347             }
1348             this->writePending();
1349             if (isConst) {
1350                 // TODO: if fHasDetails is true, could defer def and issue a ref instead
1351                 // unclear if this is a good idea or not
1352                 FPRINTF("%s", this->tableDataCodeDef(def).c_str());
1353                 this->lfAlways(1);
1354                 FPRINTF("%s", table_data_const(def, &textStart).c_str());
1355             } else {
1356                 string memberType;
1357                 string memberName = this->getMemberTypeName(def, &memberType);
1358                 FPRINTF("%s", out_table_data_description(memberType).c_str());
1359                 this->lfAlways(1);
1360                 FPRINTF("%s", tableDataCodeDef(def->fFiddle, memberName).c_str());
1361             }
1362             this->lfAlways(1);
1363             if (fHasDetails) {
1364                 string details;
1365                 auto subtopic = std::find_if(def->fChildren.begin(), def->fChildren.end(),
1366                         [](const Definition* test){
1367                         return MarkType::kDetails == test->fMarkType; } );
1368                 if (def->fChildren.end() != subtopic) {
1369                     string subtopicName = string((*subtopic)->fContentStart,
1370                             (int) ((*subtopic)->fContentEnd - (*subtopic)->fContentStart));
1371                     const Definition* parentSubtopic = def->subtopicParent();
1372                     SkASSERT(parentSubtopic);
1373                     string fullName = parentSubtopic->fFiddle + '_' + subtopicName;
1374                     if (fBmhParser.fTopicMap.end() == fBmhParser.fTopicMap.find(fullName)) {
1375                         (*subtopic)->reportError<void>("missing #Details subtopic");
1376                     }
1377              //       subtopicName = parentSubtopic->fName + '_' + subtopicName;
1378                     string noUnderscores = subtopicName;
1379                     replace_all(noUnderscores, "_", "&nbsp;");
1380                     details = this->anchorLocalRef(subtopicName, noUnderscores) + "&nbsp;";
1381                 }
1382                 FPRINTF("%s", out_table_data_details(details).c_str());
1383                 this->lfAlways(1);
1384             }
1385             lookForOneLiner = true;  // if description is empty, use oneLiner data
1386             FPRINTF("%s", out_table_data_description_start().c_str()); // start of Description
1387             this->lfAlways(1);
1388         } break;
1389         case MarkType::kDescription:
1390             fInDescription = true;
1391             this->writePending();
1392             FPRINTF("%s", "<div>");
1393             break;
1394         case MarkType::kDetails:
1395             break;
1396         case MarkType::kDuration:
1397             break;
1398         case MarkType::kDefine:
1399         case MarkType::kEnum:
1400         case MarkType::kEnumClass:
1401             this->lfAlways(2);
1402             this->htmlOut(anchorDef(def->fFiddle, ""));
1403             this->lfAlways(2);
1404             FPRINTF("---");
1405             this->lf(2);
1406             break;
1407         case MarkType::kExample: {
1408             this->mdHeaderOut(3);
1409             FPRINTF("%s", "Example\n"
1410                             "\n");
1411             fHasFiddle = true;
1412             bool showGpu = false;
1413             bool gpuAndCpu = false;
1414             const Definition* platform = def->hasChild(MarkType::kPlatform);
1415             if (platform) {
1416                 TextParser platParse(platform);
1417                 fHasFiddle = !platParse.strnstr("!fiddle", platParse.fEnd);
1418                 showGpu = platParse.strnstr("gpu", platParse.fEnd);
1419                 if (showGpu) {
1420                     gpuAndCpu = platParse.strnstr("cpu", platParse.fEnd);
1421                 }
1422             }
1423             if (fHasFiddle) {
1424                 SkASSERT(def->fHash.length() > 0);
1425                 FPRINTF("<div><fiddle-embed name=\"%s\"", def->fHash.c_str());
1426                 if (showGpu) {
1427                     FPRINTF("%s", " gpu=\"true\"");
1428                     if (gpuAndCpu) {
1429                         FPRINTF("%s", " cpu=\"true\"");
1430                     }
1431                 }
1432                 FPRINTF("%s", ">");
1433             } else {
1434                 SkASSERT(def->fHash.length() == 0);
1435                 FPRINTF("%s", "<pre style=\"padding: 1em 1em 1em 1em; font-size: 13px"
1436                         " width: 62.5em; background-color: #f0f0f0\">");
1437                 this->lfAlways(1);
1438                 if (def->fWrapper.length() > 0) {
1439                     FPRINTF("%s", def->fWrapper.c_str());
1440                 }
1441                 fLiteralAndIndent = true;
1442             }
1443             } break;
1444         case MarkType::kExternal:
1445             break;
1446         case MarkType::kFile:
1447             break;
1448         case MarkType::kFilter:
1449             break;
1450         case MarkType::kFormula:
1451             break;
1452         case MarkType::kFunction:
1453             break;
1454         case MarkType::kHeight:
1455             break;
1456         case MarkType::kIllustration: {
1457             string illustName = "Illustrations_" + def->fParent->fFiddle;
1458             string number = string(def->fContentStart, def->length());
1459             if (number.length() && "1" != number) {
1460                 illustName += "_" + number;
1461             }
1462             auto illustIter = fBmhParser.fTopicMap.find(illustName);
1463             SkASSERT(fBmhParser.fTopicMap.end() != illustIter);
1464             Definition* illustDef = illustIter->second;
1465             SkASSERT(MarkType::kSubtopic == illustDef->fMarkType);
1466             SkASSERT(1 == illustDef->fChildren.size());
1467             Definition* illustExample = illustDef->fChildren[0];
1468             SkASSERT(MarkType::kExample == illustExample->fMarkType);
1469             string hash = illustExample->fHash;
1470             SkASSERT("" != hash);
1471             string title;
1472             this->writePending();
1473             FPRINTF("![%s](https://fiddle.skia.org/i/%s_raster.png \"%s\")",
1474                     def->fName.c_str(), hash.c_str(), title.c_str());
1475             this->lf(2);
1476         } break;
1477         case MarkType::kImage:
1478             break;
1479         case MarkType::kIn:
1480             break;
1481         case MarkType::kLegend:
1482             break;
1483         case MarkType::kLine:
1484             break;
1485         case MarkType::kLink:
1486             break;
1487         case MarkType::kList:
1488             fInList = true;
1489             fTableState = TableState::kRow;
1490             this->lfAlways(2);
1491             FPRINTF("%s", "<table>");
1492             this->lf(1);
1493             break;
1494         case MarkType::kLiteral:
1495             break;
1496         case MarkType::kMarkChar:
1497             fBmhParser.fMC = def->fContentStart[0];
1498             break;
1499         case MarkType::kMethod: {
1500             this->lfAlways(2);
1501 			if (false && !def->isClone()) {
1502                 string method_name = def->methodName();
1503                 this->mdHeaderOutLF(2, 1);
1504                 this->htmlOut(this->anchorDef(def->fFiddle, method_name));
1505 			} else {
1506                 this->htmlOut(this->anchorDef(def->fFiddle, ""));
1507             }
1508             this->lfAlways(2);
1509             FPRINTF("---");
1510 			this->lf(2);
1511 
1512             // TODO: put in css spec that we can define somewhere else (if markup supports that)
1513             // TODO: 50em below should match limit = 80 in formatFunction()
1514             this->writePending();
1515             string formattedStr = def->formatFunction(Definition::Format::kIncludeReturn);
1516             string preformattedStr = preformat(formattedStr);
1517             string references = this->addReferences(&preformattedStr.front(),
1518                     &preformattedStr.back() + 1, Resolvable::kSimple);
1519             preformattedStr = references;
1520             this->htmlOut("<pre style=\"padding: 1em 1em 1em 1em; width: 62.5em;"
1521                     "background-color: #f0f0f0\">\n" + preformattedStr + "\n" + "</pre>");
1522             this->lf(2);
1523             fTableState = TableState::kNone;
1524             fMethod = def;
1525             Definition* iMethod = fIncludeParser.findMethod(*def);
1526             if (iMethod) {
1527                 fMethod = iMethod;
1528                 paramMap.fParent = &fBmhParser.fGlobalNames;
1529                 paramMap.setParams(def, iMethod);
1530                 fNames = &paramMap;
1531             }
1532             } break;
1533         case MarkType::kNoExample:
1534             break;
1535         case MarkType::kNoJustify:
1536             break;
1537         case MarkType::kOutdent:
1538             break;
1539         case MarkType::kParam: {
1540             TextParser paramParser(def->fFileName, def->fStart, def->fContentStart,
1541                     def->fLineCount);
1542             paramParser.skipWhiteSpace();
1543             SkASSERT(paramParser.startsWith("#Param"));
1544             paramParser.next(); // skip hash
1545             paramParser.skipToNonName(); // skip Param
1546             this->parameterHeaderOut(paramParser, prior, def);
1547         } break;
1548         case MarkType::kPhraseDef:
1549             // skip text and children
1550             *prior = def;
1551             return;
1552         case MarkType::kPhraseParam:
1553             SkDebugf(""); // convenient place to set a breakpoint
1554             break;
1555         case MarkType::kPhraseRef:
1556             if (fPhraseParams.end() != fPhraseParams.find(def->fName)) {
1557                 if (fColumn > 0) {
1558                     this->writeSpace();
1559                 }
1560                 this->writeString(fPhraseParams[def->fName]);
1561                 if (isspace(def->fContentStart[0])) {
1562                     this->writeSpace();
1563                 }
1564             } else if (fBmhParser.fPhraseMap.end() == fBmhParser.fPhraseMap.find(def->fName)) {
1565                 def->reportError<void>("missing phrase definition");
1566                 fAddRefFailed = true;
1567             } else {
1568                 if (fColumn) {
1569                     SkASSERT(' ' >= def->fStart[0]);
1570                     this->writeSpace();
1571                 }
1572                 Definition* phraseRef = fBmhParser.fPhraseMap.find(def->fName)->second;
1573                 // def->fChildren are parameters to substitute phraseRef->fChildren,
1574                 // phraseRef->fChildren has both param defines and references
1575                 // def->fChildren must have the same number of entries as phaseRef->fChildren
1576                 // which are kPhraseParam, and substitute one for one
1577                 // Then, each kPhraseRef in phaseRef looks up the key and value
1578                 fPhraseParams.clear();
1579                 auto refKidsIter = phraseRef->fChildren.begin();
1580                 for (auto child : def->fChildren) {
1581                     if (MarkType::kPhraseParam != child->fMarkType) {
1582                         // more work to do to support other types
1583                         this->reportError("phrase ref child must be param");
1584                     }
1585                     do {
1586                         if (refKidsIter == phraseRef->fChildren.end()) {
1587                             this->reportError("phrase def missing param");
1588                             break;
1589                         }
1590                         if (MarkType::kPhraseRef == (*refKidsIter)->fMarkType) {
1591                             continue;
1592                         }
1593                         if (MarkType::kPhraseParam != (*refKidsIter)->fMarkType) {
1594                             this->reportError("unexpected type in phrase def children");
1595                             break;
1596                         }
1597                         fPhraseParams[(*refKidsIter)->fName] = child->fName;
1598                         break;
1599                     } while (true);
1600                 }
1601                 this->childrenOut(phraseRef, phraseRef->fContentStart);
1602                 fPhraseParams.clear();
1603                 if (' ' >= def->fContentStart[0] && !fPendingLF) {
1604                     this->writeSpace();
1605                 }
1606             }
1607             break;
1608         case MarkType::kPlatform:
1609             break;
1610         case MarkType::kPopulate: {
1611             Definition* parent = def->fParent;
1612             SkASSERT(parent);
1613             if (MarkType::kCode == parent->fMarkType) {
1614                 auto inDef = std::find_if(parent->fChildren.begin(), parent->fChildren.end(),
1615                         [](const Definition* child) { return MarkType::kIn == child->fMarkType; });
1616                 if (parent->fChildren.end() != inDef) {
1617                     auto filterDef = std::find_if(parent->fChildren.begin(),
1618                             parent->fChildren.end(), [](const Definition* child) {
1619                             return MarkType::kFilter == child->fMarkType; });
1620                     SkASSERT(parent->fChildren.end() != filterDef);
1621                     string codeBlock = fIncludeParser.filteredBlock(
1622                             string((*inDef)->fContentStart, (*inDef)->length()),
1623                             string((*filterDef)->fContentStart, (*filterDef)->length()));
1624                     this->resolveOut(codeBlock.c_str(), codeBlock.c_str() + codeBlock.length(),
1625                             this->resolvable(parent));
1626                     break;
1627                 }
1628                 // find include matching code parent
1629                 Definition* grand = parent->fParent;
1630                 SkASSERT(grand);
1631                 if (MarkType::kClass == grand->fMarkType
1632                         || MarkType::kStruct == grand->fMarkType
1633                         || MarkType::kEnum == grand->fMarkType
1634                         || MarkType::kEnumClass == grand->fMarkType
1635                         || MarkType::kTypedef == grand->fMarkType
1636                         || MarkType::kDefine == grand->fMarkType) {
1637                     string codeBlock = fIncludeParser.codeBlock(*grand, fInProgress);
1638                     this->resolveOut(codeBlock.c_str(), codeBlock.c_str() + codeBlock.length(),
1639                             this->resolvable(parent));
1640                 } else if (MarkType::kTopic == grand->fMarkType) {
1641                     // use bmh file name to find include file name
1642                     size_t start = grand->fFileName.rfind("Sk");
1643                     SkASSERT(start != string::npos);
1644                     size_t end = grand->fFileName.rfind("_Reference");
1645                     SkASSERT(end != string::npos && end > start);
1646                     string incName(grand->fFileName.substr(start, end - start));
1647                     const Definition* includeDef = fIncludeParser.include(incName + ".h");
1648                     SkASSERT(includeDef);
1649                     string codeBlock;
1650                     this->addCodeBlock(includeDef, codeBlock);
1651                     this->resolveOut(codeBlock.c_str(), codeBlock.c_str() + codeBlock.length(),
1652                             this->resolvable(parent));
1653                 } else {
1654                     SkASSERT(MarkType::kSubtopic == grand->fMarkType);
1655                     auto inTag = std::find_if(grand->fChildren.begin(), grand->fChildren.end(),
1656                             [](Definition* child){return MarkType::kIn == child->fMarkType;});
1657                     SkASSERT(grand->fChildren.end() != inTag);
1658                     auto filterTag = std::find_if(grand->fChildren.begin(), grand->fChildren.end(),
1659                             [](Definition* child){return MarkType::kFilter == child->fMarkType;});
1660                     SkASSERT(grand->fChildren.end() != filterTag);
1661                     string inContents((*inTag)->fContentStart, (*inTag)->length());
1662                     string filterContents((*filterTag)->fContentStart, (*filterTag)->length());
1663                     string filteredBlock = fIncludeParser.filteredBlock(inContents, filterContents);
1664                     this->resolveOut(filteredBlock.c_str(), filteredBlock.c_str()
1665                             + filteredBlock.length(), this->resolvable(parent));
1666                 }
1667             } else {
1668                 SkASSERT(MarkType::kMethod == parent->fMarkType);
1669                 // retrieve parameters, return, description from include
1670                 Definition* iMethod = fIncludeParser.findMethod(*parent);
1671                 if (!iMethod) {  // deprecated or 'in progress' functions should not include populate
1672                     SkDebugf("#Populate found in deprecated or missing method %s\n", def->fName.c_str());
1673                     def->fParent->reportError<void>("Remove #Method");
1674                 }
1675                 bool wroteParam = false;
1676                 SkASSERT(fMethod == iMethod);
1677                 for (auto& entry : iMethod->fTokens) {
1678                     if (MarkType::kComment != entry.fMarkType) {
1679                         continue;
1680                     }
1681                     TextParser parser(&entry);
1682                     if (parser.skipExact("@param ")) { // write parameters, if any
1683                         this->parameterHeaderOut(parser, prior, def);
1684                         this->resolveOut(parser.fChar, parser.fEnd,
1685                                 Resolvable::kInclude);
1686                         this->parameterTrailerOut();
1687                         wroteParam = true;
1688                         continue;
1689                     }
1690                     if (wroteParam) {
1691                         this->writePending();
1692                         FPRINTF("</table>");
1693                         this->lf(2);
1694                         fTableState = TableState::kNone;
1695                         wroteParam = false;
1696                     }
1697                     if (parser.skipExact("@return ")) { // write return, if any
1698                         this->returnHeaderOut(prior, def);
1699                         this->resolveOut(parser.fChar, parser.fEnd,
1700                                 Resolvable::kInclude);
1701                         this->lf(2);
1702                         continue;
1703                     }
1704                     if (1 == entry.length() && '/' == entry.fContentStart[0]) {
1705                         continue;
1706                     }
1707                     if ("/!< " == string(entry.fContentStart, entry.length()).substr(0, 4)) {
1708                         continue;
1709                     }
1710                     const char* backwards = entry.fContentStart;
1711                     while (' ' == *--backwards)
1712                         ;
1713                     if ('\n' == backwards[0] && '\n' == backwards[-1]) {
1714                         this->lf(2);
1715                     }
1716                     this->resolveOut(entry.fContentStart, entry.fContentEnd,
1717                             Resolvable::kInclude);  // write description
1718                     this->lf(1);
1719                 }
1720             }
1721             } break;
1722         case MarkType::kReturn:
1723             this->returnHeaderOut(prior, def);
1724             break;
1725         case MarkType::kRow:
1726             if (fInList) {
1727                 FPRINTF("  <tr>");
1728                 this->lf(1);
1729             }
1730             break;
1731         case MarkType::kSeeAlso:
1732             this->mdHeaderOut(3);
1733             FPRINTF("See Also");
1734             this->lf(2);
1735             break;
1736         case MarkType::kSet:
1737             break;
1738         case MarkType::kStdOut: {
1739             TextParser code(def);
1740             this->mdHeaderOut(4);
1741             FPRINTF(
1742                     "Example Output\n"
1743                     "\n"
1744                     "~~~~");
1745             this->lfAlways(1);
1746             code.skipSpace();
1747             while (!code.eof()) {
1748                 const char* end = code.trimmedLineEnd();
1749                 FPRINTF("%.*s\n", (int) (end - code.fChar), code.fChar);
1750                 code.skipToLineStart();
1751             }
1752             FPRINTF("~~~~");
1753             this->lf(2);
1754             } break;
1755         case MarkType::kSubstitute:
1756             break;
1757         case MarkType::kSubtopic:
1758             fSubtopic = def->asRoot();
1759             if (false && SubtopicKeys::kOverview == def->fName) {
1760                 this->writeString(def->fName);
1761             } else {
1762                 this->lfAlways(2);
1763                 this->htmlOut(anchorDef(def->fName, ""));
1764             }
1765             if (std::any_of(def->fChildren.begin(), def->fChildren.end(),
1766                     [](Definition* child) {
1767                     return MarkType::kSeeAlso == child->fMarkType
1768                     || MarkType::kExample == child->fMarkType
1769                     || MarkType::kNoExample == child->fMarkType;
1770             })) {
1771                 this->lfAlways(2);
1772                 FPRINTF("---");
1773             }
1774             this->lf(2);
1775 #if 0
1776             // if a subtopic child is const, generate short table of const name, value, line desc
1777             if (std::any_of(def->fChildren.begin(), def->fChildren.end(),
1778                     [](Definition* child){return MarkType::kConst == child->fMarkType;})) {
1779                 this->summaryOut(def, MarkType::kConst, fPopulators[SubtopicKeys::kConstants].fPlural);
1780             }
1781 #endif
1782             // if a subtopic child is member, generate short table of const name, value, line desc
1783             if (std::any_of(def->fChildren.begin(), def->fChildren.end(),
1784                     [](Definition* child){return MarkType::kMember == child->fMarkType;})) {
1785                 this->summaryOut(def, MarkType::kMember, fPopulators[SubtopicKeys::kMembers].fPlural);
1786             }
1787             break;
1788         case MarkType::kTable:
1789             this->lf(2);
1790             break;
1791         case MarkType::kTemplate:
1792             break;
1793         case MarkType::kText:
1794             if (def->fParent && MarkType::kFormula == def->fParent->fMarkType) {
1795                 if (fColumn > 0) {
1796                     this->writeSpace();
1797                 }
1798                 this->writePending();
1799                 this->htmlOut("<code>");
1800                 this->resolveOut(def->fContentStart, def->fContentEnd,
1801                         Resolvable::kFormula);
1802                 this->htmlOut("</code>");
1803             }
1804             break;
1805         case MarkType::kToDo:
1806             break;
1807         case MarkType::kTopic: {
1808             auto found = std::find_if(def->fChildren.begin(), def->fChildren.end(),
1809                     [](Definition* test) { return test->isStructOrClass(); } );
1810             bool hasClassOrStruct = def->fChildren.end() != found;
1811             fRoot = hasClassOrStruct ? (*found)->asRoot() : def->asRoot();
1812             fSubtopic = def->asRoot();
1813             bool isUndocumented = string::npos != def->fFileName.find("undocumented");
1814             if (!isUndocumented) {
1815                 this->populateTables(def, fRoot);
1816             }
1817 //            this->mdHeaderOut(1);
1818 //            this->htmlOut(anchorDef(this->linkName(def), printable));
1819 //            this->lf(1);
1820             } break;
1821         case MarkType::kTypedef:
1822             this->lfAlways(2);
1823             this->htmlOut(anchorDef(def->fFiddle, ""));
1824             this->lfAlways(2);
1825             FPRINTF("---");
1826             this->lf(2);
1827             break;
1828         case MarkType::kUnion:
1829             break;
1830         case MarkType::kVolatile:
1831             break;
1832         case MarkType::kWidth:
1833             break;
1834         default:
1835             SkDebugf("fatal error: MarkType::k%s unhandled in %s()\n",
1836                     BmhParser::kMarkProps[(int) def->fMarkType].fName, __func__);
1837             SkASSERT(0); // handle everything
1838             break;
1839     }
1840     this->childrenOut(def, textStart);
1841     switch (def->fMarkType) {  // post child work, at least for tables
1842         case MarkType::kAnchor:
1843             if (fColumn > 0) {
1844                 this->writeSpace();
1845             }
1846             break;
1847         case MarkType::kClass:
1848         case MarkType::kStruct:
1849             if (TableState::kNone != fTableState) {
1850                 this->writePending();
1851                 FPRINTF("</table>");
1852                 this->lf(2);
1853                 fTableState = TableState::kNone;
1854             }
1855             if (def->csParent()) {
1856                 fRoot = def->csParent()->asRoot();
1857             }
1858             break;
1859         case MarkType::kCode:
1860             fIndent = 0;
1861             this->lf(1);
1862             this->writePending();
1863             FPRINTF("</pre>");
1864             this->lf(2);
1865             fResolveAndIndent = false;
1866             break;
1867         case MarkType::kColumn:
1868             if (fInList) {
1869                 this->writePending();
1870                 FPRINTF("</td>");
1871                 this->lfAlways(1);
1872             } else {
1873                 FPRINTF(" ");
1874             }
1875             break;
1876         case MarkType::kDescription:
1877             this->writePending();
1878             FPRINTF("</div>");
1879             fInDescription = false;
1880             break;
1881         case MarkType::kEnum:
1882         case MarkType::kEnumClass:
1883             if (TableState::kNone != fTableState) {
1884                 this->writePending();
1885                 FPRINTF("</table>");
1886                 this->lf(2);
1887                 fTableState = TableState::kNone;
1888             }
1889             break;
1890         case MarkType::kExample:
1891             this->writePending();
1892             if (fHasFiddle) {
1893                 FPRINTF("</fiddle-embed></div>");
1894             } else {
1895                 this->lfAlways(1);
1896                 if (def->fWrapper.length() > 0) {
1897                     FPRINTF("}");
1898                     this->lfAlways(1);
1899                 }
1900                 FPRINTF("</pre>");
1901             }
1902             this->lf(2);
1903             fLiteralAndIndent = false;
1904             break;
1905         case MarkType::kLink:
1906             this->writeString("</a>");
1907             this->writeSpace();
1908             break;
1909         case MarkType::kList:
1910             fInList = false;
1911             this->writePending();
1912             SkASSERT(TableState::kNone != fTableState);
1913             FPRINTF("</table>");
1914             this->lf(2);
1915             fTableState = TableState::kNone;
1916             break;
1917         case MarkType::kLegend: {
1918             SkASSERT(def->fChildren.size() == 1);
1919             const Definition* row = def->fChildren[0];
1920             SkASSERT(MarkType::kRow == row->fMarkType);
1921             size_t columnCount = row->fChildren.size();
1922             SkASSERT(columnCount > 0);
1923             this->writePending();
1924             for (size_t index = 0; index < columnCount; ++index) {
1925                 FPRINTF("| --- ");
1926             }
1927             FPRINTF(" |");
1928             this->lf(1);
1929             } break;
1930         case MarkType::kMethod:
1931             fMethod = nullptr;
1932             fNames = fNames->fParent;
1933             break;
1934         case MarkType::kConst:
1935         case MarkType::kMember:
1936             if (lookForOneLiner && !fWroteSomething) {
1937                 auto oneLiner = std::find_if(def->fChildren.begin(), def->fChildren.end(),
1938                         [](const Definition* test){ return MarkType::kLine == test->fMarkType; } );
1939                 if (def->fChildren.end() != oneLiner) {
1940                     TextParser parser(*oneLiner);
1941                     parser.skipWhiteSpace();
1942                     parser.trimEnd();
1943                     FPRINTF("%.*s", (int) (parser.fEnd - parser.fChar), parser.fChar);
1944                 }
1945                 lookForOneLiner = false;
1946             }
1947         case MarkType::kParam:
1948             this->parameterTrailerOut();
1949             break;
1950         case MarkType::kReturn:
1951         case MarkType::kSeeAlso:
1952             this->lf(2);
1953             break;
1954         case MarkType::kRow:
1955             if (fInList) {
1956                 FPRINTF("  </tr>");
1957             } else {
1958                 FPRINTF("|");
1959             }
1960             this->lf(1);
1961             break;
1962         case MarkType::kTable:
1963             this->lf(2);
1964             break;
1965         case MarkType::kPhraseDef:
1966             break;
1967         case MarkType::kSubtopic:
1968             SkASSERT(def);
1969             do {
1970                 def = def->fParent;
1971             } while (def && MarkType::kTopic != def->fMarkType
1972                     && MarkType::kSubtopic != def->fMarkType);
1973             SkASSERT(def);
1974             fSubtopic = def->asRoot();
1975             break;
1976         case MarkType::kTopic:
1977             fSubtopic = nullptr;
1978             break;
1979         default:
1980             break;
1981     }
1982     *prior = def;
1983 }
1984 
mdHeaderOutLF(int depth,int lf)1985 void MdOut::mdHeaderOutLF(int depth, int lf) {
1986     this->lfAlways(lf);
1987     for (int index = 0; index < depth; ++index) {
1988         FPRINTF("#");
1989     }
1990     FPRINTF(" ");
1991 }
1992 
parameterHeaderOut(TextParser & paramParser,const Definition ** prior,Definition * def)1993 void MdOut::parameterHeaderOut(TextParser& paramParser, const Definition** prior, Definition* def) {
1994     if (TableState::kNone == fTableState) {
1995         SkASSERT(!*prior || MarkType::kParam != (*prior)->fMarkType);
1996         this->mdHeaderOut(3);
1997         this->htmlOut(
1998                 "Parameters\n"
1999                 "\n"
2000                 "<table>"
2001                 );
2002         this->lf(1);
2003         fTableState = TableState::kRow;
2004     }
2005     if (TableState::kRow == fTableState) {
2006         FPRINTF("  <tr>");
2007         this->lf(1);
2008         fTableState = TableState::kColumn;
2009     }
2010     paramParser.skipSpace();
2011     const char* paramName = paramParser.fChar;
2012     paramParser.skipToSpace();
2013     string paramNameStr(paramName, (int) (paramParser.fChar - paramName));
2014     if (MarkType::kPopulate != def->fMarkType && !this->checkParamReturnBody(def)) {
2015         *prior = def;
2016         return;
2017     }
2018     string refNameStr = def->fParent->fFiddle + "_" + paramNameStr;
2019     this->htmlOut("    <td>" + this->anchorDef(refNameStr,
2020             "<code><strong>" + paramNameStr + "</strong></code>") + "</td>");
2021     this->lfAlways(1);
2022     FPRINTF("    <td>");
2023 }
2024 
parameterTrailerOut()2025 void MdOut::parameterTrailerOut() {
2026     SkASSERT(TableState::kColumn == fTableState);
2027     fTableState = TableState::kRow;
2028     this->writePending();
2029     FPRINTF("</td>");
2030     this->lfAlways(1);
2031     FPRINTF("  </tr>");
2032     this->lfAlways(1);
2033 }
2034 
populateOne(Definition * def,unordered_map<string,RootDefinition::SubtopicContents> & populator)2035 void MdOut::populateOne(Definition* def,
2036         unordered_map<string, RootDefinition::SubtopicContents>& populator) {
2037     if (MarkType::kConst == def->fMarkType) {
2038         populator[SubtopicKeys::kConstants].fMembers.push_back(def);
2039         return;
2040     }
2041     if (MarkType::kEnum == def->fMarkType || MarkType::kEnumClass == def->fMarkType) {
2042         populator[SubtopicKeys::kConstants].fMembers.push_back(def);
2043         return;
2044     }
2045     if (MarkType::kDefine == def->fMarkType) {
2046         populator[SubtopicKeys::kDefines].fMembers.push_back(def);
2047         return;
2048     }
2049     if (MarkType::kMember == def->fMarkType) {
2050         populator[SubtopicKeys::kMembers].fMembers.push_back(def);
2051         return;
2052     }
2053     if (MarkType::kTypedef == def->fMarkType) {
2054         populator[SubtopicKeys::kTypedefs].fMembers.push_back(def);
2055         return;
2056     }
2057     if (MarkType::kMethod != def->fMarkType) {
2058         return;
2059     }
2060     if (def->fClone) {
2061         return;
2062     }
2063     if (Definition::MethodType::kConstructor == def->fMethodType
2064             || Definition::MethodType::kDestructor == def->fMethodType) {
2065         populator[SubtopicKeys::kConstructors].fMembers.push_back(def);
2066         return;
2067     }
2068     if (Definition::MethodType::kOperator == def->fMethodType) {
2069         populator[SubtopicKeys::kOperators].fMembers.push_back(def);
2070         return;
2071     }
2072     populator[SubtopicKeys::kMemberFunctions].fMembers.push_back(def);
2073     const Definition* csParent = this->csParent();
2074     if (csParent) {
2075         if (0 == def->fName.find(csParent->fName + "::Make")
2076                 || 0 == def->fName.find(csParent->fName + "::make")) {
2077             populator[SubtopicKeys::kConstructors].fMembers.push_back(def);
2078             return;
2079         }
2080     }
2081     for (auto item : def->fChildren) {
2082         if (MarkType::kIn == item->fMarkType) {
2083             string name(item->fContentStart, item->fContentEnd - item->fContentStart);
2084             populator[name].fMembers.push_back(def);
2085             populator[name].fShowClones = true;
2086             break;
2087         }
2088     }
2089 }
2090 
populateTables(const Definition * def,RootDefinition * root)2091 void MdOut::populateTables(const Definition* def, RootDefinition* root) {
2092     for (auto child : def->fChildren) {
2093         if (MarkType::kSubtopic == child->fMarkType) {
2094             string name = child->fName;
2095             bool builtInTopic = name == SubtopicKeys::kOverview;
2096             for (auto item : SubtopicKeys::kGeneratedSubtopics) {
2097                 builtInTopic |= name == item;
2098             }
2099             if (!builtInTopic) {
2100                 string subname;
2101                 const Definition* subtopic = child->subtopicParent();
2102                 if (subtopic) {
2103                     subname = subtopic->fName + '_';
2104                 }
2105                 builtInTopic = name == subname + SubtopicKeys::kOverview;
2106                 for (auto item : SubtopicKeys::kGeneratedSubtopics) {
2107                     builtInTopic |= name == subname + item;
2108                 }
2109                 if (!builtInTopic) {
2110                     root->populator(SubtopicKeys::kRelatedFunctions).fMembers.push_back(child);
2111                 }
2112             }
2113             this->populateTables(child, root);
2114             continue;
2115         }
2116         if (child->isStructOrClass()) {
2117             if (fClassStack.size() > 0) {
2118                 root->populator(MarkType::kStruct != child->fMarkType ? SubtopicKeys::kClasses :
2119                         SubtopicKeys::kStructs).fMembers.push_back(child);
2120             }
2121             fClassStack.push_back(child);
2122             this->populateTables(child, child->asRoot());
2123             fClassStack.pop_back();
2124             continue;
2125         }
2126         if (MarkType::kEnum == child->fMarkType || MarkType::kEnumClass == child->fMarkType) {
2127             this->populateTables(child, root);
2128         }
2129         this->populateOne(child, root->fPopulators);
2130     }
2131 }
2132 
resolveOut(const char * start,const char * end,Resolvable resolvable)2133 void MdOut::resolveOut(const char* start, const char* end, Resolvable resolvable) {
2134     if ((Resolvable::kLiteral == resolvable || fLiteralAndIndent ||
2135             fResolveAndIndent) && end > start) {
2136         int linefeeds = 0;
2137         while ('\n' == *start) {
2138             ++linefeeds;
2139             ++start;
2140         }
2141         if (fResolveAndIndent && linefeeds) {
2142             this->lf(linefeeds);
2143         }
2144         const char* spaceStart = start;
2145         while (' ' == *start) {
2146             ++start;
2147         }
2148         if (start > spaceStart) {
2149             fIndent = start - spaceStart;
2150         }
2151     }
2152     if (Resolvable::kLiteral == resolvable || fLiteralAndIndent) {
2153         this->writeBlockTrim(end - start, start);
2154         if ('\n' == end[-1]) {
2155             this->lf(1);
2156         }
2157         fIndent = 0;
2158         return;
2159     }
2160     // FIXME: this needs the markdown character present when the def was defined,
2161     // not the last markdown character the parser would have seen...
2162     while (fBmhParser.fMC == end[-1]) {
2163         --end;
2164     }
2165     if (start >= end) {
2166         return;
2167     }
2168     string resolved = this->addReferences(start, end, resolvable);
2169     trim_end_spaces(resolved);
2170     if (resolved.length()) {
2171         TextParser paragraph(fFileName, &*resolved.begin(), &*resolved.end(), fLineCount);
2172         while (!paragraph.eof()) {
2173             while ('\n' == paragraph.peek()) {
2174                 paragraph.next();
2175                 if (paragraph.eof()) {
2176                     return;
2177                 }
2178             }
2179             const char* lineStart = paragraph.fChar;
2180             paragraph.skipWhiteSpace();
2181             const char* contentStart = paragraph.fChar;
2182             if (fResolveAndIndent && contentStart > lineStart) {
2183                 this->writePending();
2184                 this->indentToColumn(contentStart - lineStart);
2185             }
2186             paragraph.skipToEndBracket('\n');
2187             ptrdiff_t lineLength = paragraph.fChar - contentStart;
2188             if (lineLength) {
2189                 while (lineLength && contentStart[lineLength - 1] <= ' ') {
2190                     --lineLength;
2191                 }
2192                 string str(contentStart, lineLength);
2193                 this->writeString(str.c_str());
2194                 fWroteSomething = !!lineLength;
2195             }
2196             if (paragraph.eof()) {
2197                 break;
2198             }
2199             if ('\n' == paragraph.next()) {
2200                 int linefeeds = 1;
2201                 if (!paragraph.eof() && '\n' == paragraph.peek()) {
2202                     linefeeds = 2;
2203                 }
2204                 this->lf(linefeeds);
2205             }
2206         }
2207     }
2208 }
2209 
returnHeaderOut(const Definition ** prior,Definition * def)2210 void MdOut::returnHeaderOut(const Definition** prior, Definition* def) {
2211     this->mdHeaderOut(3);
2212     FPRINTF("Return Value");
2213     if (MarkType::kPopulate != def->fMarkType && !this->checkParamReturnBody(def)) {
2214         *prior = def;
2215         return;
2216     }
2217     this->lf(2);
2218 }
2219 
rowOut(string col1,const Definition * col2)2220 void MdOut::rowOut(string col1, const Definition* col2) {
2221     FPRINTF("%s", fOddRow ? kTR_Dark.c_str() : "  <tr>");
2222     this->lfAlways(1);
2223     FPRINTF("%s", kTD_Left.c_str());
2224     if ("" != col1) {
2225         this->writeString(col1);
2226     }
2227     FPRINTF("</td>");
2228     this->lfAlways(1);
2229     FPRINTF("%s", kTD_Left.c_str());
2230     TextParser parser(col2->fFileName, col2->fStart, col2->fContentStart, col2->fLineCount);
2231     parser.skipExact("#Method");
2232     parser.skipSpace();
2233     parser.trimEnd();
2234     string methodName(parser.fChar, parser.fEnd - parser.fChar);
2235     this->htmlOut(this->anchorRef("#" + col2->fFiddle, methodName));
2236     this->htmlOut("</td>");
2237     this->lfAlways(1);
2238     FPRINTF("  </tr>");
2239     this->lfAlways(1);
2240     fOddRow = !fOddRow;
2241 }
2242 
rowOut(const char * name,string description,bool literalName)2243 void MdOut::rowOut(const char* name, string description, bool literalName) {
2244     FPRINTF("%s", fOddRow ? kTR_Dark.c_str() : "  <tr>");
2245     this->lfAlways(1);
2246     FPRINTF("%s", kTD_Left.c_str());
2247     if (literalName) {
2248         if (strlen(name)) {
2249             this->writeString(name);
2250         }
2251     } else {
2252         this->resolveOut(name, name + strlen(name), Resolvable::kYes);
2253     }
2254     FPRINTF("</td>");
2255     this->lfAlways(1);
2256     FPRINTF("%s", kTD_Left.c_str());
2257     this->resolveOut(&description.front(), &description.back() + 1, Resolvable::kYes);
2258     FPRINTF("</td>");
2259     this->lfAlways(1);
2260     FPRINTF("  </tr>");
2261     this->lfAlways(1);
2262     fOddRow = !fOddRow;
2263 }
2264 
subtopicsOut(Definition * def)2265 void MdOut::subtopicsOut(Definition* def) {
2266     Definition* csParent = def->csParent();
2267     const Definition* subtopicParent = def->subtopicParent();
2268     const Definition* topicParent = def->topicParent();
2269     SkASSERT(subtopicParent);
2270     this->lfAlways(1);
2271     FPRINTF("%s", kTableDeclaration);
2272     this->lfAlways(1);
2273     FPRINTF("%s", kTopicsTableHeader);
2274     this->lfAlways(1);
2275     fOddRow = true;
2276     for (auto item : SubtopicKeys::kGeneratedSubtopics) {
2277         if (SubtopicKeys::kMemberFunctions == item) {
2278             continue;
2279         }
2280         for (auto entry : fRoot->populator(item).fMembers) {
2281             if ((csParent && entry->csParent() == csParent)
2282                     || entry->subtopicParent() == subtopicParent) {
2283                 if (SubtopicKeys::kRelatedFunctions == item) {
2284                     (void) subtopicRowOut(entry->fName, entry); // report all errors
2285                     continue;
2286                 }
2287                 auto popItem = fPopulators.find(item);
2288                 string description = popItem->second.fOneLiner;
2289                 if (SubtopicKeys::kConstructors == item) {
2290                     description += " " + fRoot->fName;
2291                 }
2292                 string subtopic;
2293                 if (subtopicParent != topicParent) {
2294                     subtopic = subtopicParent->fName + '_';
2295                 }
2296                 string link = this->anchorLocalRef(subtopic + item, popItem->second.fPlural);
2297                 this->rowOut(link.c_str(), description, true);
2298                 break;
2299             }
2300         }
2301     }
2302     FPRINTF("</table>");
2303     this->lfAlways(1);
2304 }
2305 
subtopicOut(string name)2306 void MdOut::subtopicOut(string name) {
2307     const Definition* topicParent = fSubtopic ? fSubtopic->topicParent() : nullptr;
2308     Definition* csParent = fRoot && fRoot->isStructOrClass() ? fRoot : this->csParent();
2309     if (!csParent) {
2310         auto csIter = std::find_if(topicParent->fChildren.begin(), topicParent->fChildren.end(),
2311                 [](const Definition* def){ return MarkType::kEnum == def->fMarkType
2312                 || MarkType::kEnumClass == def->fMarkType; } );
2313         SkASSERT(topicParent->fChildren.end() != csIter);
2314         csParent = *csIter;
2315     }
2316     SkASSERT(csParent);
2317     this->lfAlways(1);
2318     if (fPopulators.end() != fPopulators.find(name)) {
2319         const SubtopicDescriptions& tableDescriptions = this->populator(name);
2320         this->anchorDef(name, tableDescriptions.fPlural);
2321         this->lfAlways(1);
2322         if (tableDescriptions.fDetails.length()) {
2323             string details = csParent->fName;
2324             details += " " + tableDescriptions.fDetails;
2325             this->writeString(details);
2326             this->lfAlways(1);
2327         }
2328     } else {
2329         this->anchorDef(name, name);
2330         this->lfAlways(1);
2331     }
2332     if (SubtopicKeys::kMembers == name) {
2333         return; // members output their own table
2334     }
2335     const RootDefinition::SubtopicContents& tableContents = fRoot->populator(name.c_str());
2336     if (SubtopicKeys::kTypedefs == name && fSubtopic && MarkType::kTopic == fSubtopic->fMarkType) {
2337         topicParent = fSubtopic;
2338     }
2339     this->subtopicOut(name, tableContents.fMembers, csParent, topicParent,
2340             tableContents.fShowClones);
2341 }
2342 
subtopicOut(string key,const vector<Definition * > & data,const Definition * csParent,const Definition * topicParent,bool showClones)2343 void MdOut::subtopicOut(string key, const vector<Definition*>& data, const Definition* csParent,
2344         const Definition* topicParent, bool showClones) {
2345     this->writeString(kTableDeclaration);
2346     this->lfAlways(1);
2347     this->writeSubtopicTableHeader(key);
2348     this->lfAlways(1);
2349     fOddRow = true;
2350     std::map<string, const Definition*> items;
2351     for (auto entry : data) {
2352         if (!BmhParser::IsExemplary(entry)) {
2353             continue;
2354         }
2355         if (entry->csParent() != csParent && entry->topicParent() != topicParent) {
2356             continue;
2357         }
2358         size_t start = entry->fName.find_last_of("::");
2359         if (MarkType::kConst == entry->fMarkType && entry->fParent
2360                 && MarkType::kEnumClass == entry->fParent->fMarkType
2361                 && string::npos != start && start > 1) {
2362             start = entry->fName.substr(0, start - 1).rfind("::");
2363         }
2364         string entryName = entry->fName.substr(string::npos == start ? 0 : start + 1);
2365         items[entryName] = entry;
2366     }
2367     for (auto entry : items) {
2368         if (!this->subtopicRowOut(entry.first, entry.second)) {
2369             return;
2370         }
2371         if (showClones && entry.second->fCloned) {
2372             int cloneNo = 2;
2373             string builder = entry.second->fName;
2374             if ("()" == builder.substr(builder.length() - 2)) {
2375                 builder = builder.substr(0, builder.length() - 2);
2376             }
2377             builder += '_';
2378             this->rowOut("overloads", entry.second);
2379             do {
2380                 string match = builder + to_string(cloneNo);
2381                 auto child = csParent->findClone(match);
2382                 if (!child) {
2383                     break;
2384                 }
2385                 this->rowOut("", child);
2386             } while (++cloneNo);
2387         }
2388     }
2389     FPRINTF("</table>");
2390     this->lf(2);
2391 }
2392 
subtopicRowOut(string keyName,const Definition * entry)2393 bool MdOut::subtopicRowOut(string keyName, const Definition* entry) {
2394     const Definition* oneLiner = nullptr;
2395     for (auto child : entry->fChildren) {
2396         if (MarkType::kLine == child->fMarkType) {
2397             oneLiner = child;
2398             break;
2399         }
2400     }
2401     if (!oneLiner) {
2402         TextParser parser(entry->fFileName, entry->fStart,
2403                 entry->fContentStart, entry->fLineCount);
2404         return parser.reportError<bool>("missing #Line");
2405     }
2406     TextParser dummy(entry); // for reporting errors, which we won't do
2407     if (!this->isDefined(dummy, Resolvable::kOut)) {
2408         keyName = entry->fName;
2409         size_t doubleColon = keyName.find("::");
2410         SkASSERT(string::npos != doubleColon);
2411         keyName = keyName.substr(doubleColon + 2);
2412     }
2413     this->rowOut(keyName.c_str(), string(oneLiner->fContentStart,
2414             oneLiner->fContentEnd - oneLiner->fContentStart), false);
2415     return true;
2416 }
2417 
writeSubtopicTableHeader(string key)2418 void MdOut::writeSubtopicTableHeader(string key) {
2419     this->htmlOut("<tr>");
2420     this->htmlOut(kTH_Left);
2421     if (fPopulators.end() != fPopulators.find(key)) {
2422         this->writeString(fPopulators[key].fSingular);
2423     } else {
2424         this->writeString("Function");
2425     }
2426     this->htmlOut("</th>");
2427     this->lf(1);
2428     this->htmlOut(kTH_Left);
2429     this->writeString("Description");
2430     this->htmlOut("</th>");
2431     this->htmlOut("</tr>");
2432 }
2433 
2434 #undef kTH_Left
2435