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 += "<";
171 } else if ('>' == c) {
172 result += ">";
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, " ", " ");
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, "_", " ");
1380 details = this->anchorLocalRef(subtopicName, noUnderscores) + " ";
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 = ¶mMap;
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