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 "bookmaker.h"
9 
10 #ifdef SK_BUILD_FOR_WIN
11 #include <Windows.h>
12 #endif
13 
14 DEFINE_string2(status, a, "", "File containing status of documentation. (Use in place of -b -i)");
15 DEFINE_string2(bmh, b, "", "Path to a *.bmh file or a directory.");
16 DEFINE_bool2(catalog, c, false, "Write example catalog.htm. (Requires -b -f -r)");
17 DEFINE_string2(examples, e, "", "File of fiddlecli input, usually fiddle.json (For now, disables -r -f -s)");
18 DEFINE_string2(fiddle, f, "", "File of fiddlecli output, usually fiddleout.json.");
19 DEFINE_bool2(hack, H, false, "Do a find/replace hack to update all *.bmh files. (Requires -b)");
20 // h is reserved for help
21 DEFINE_string2(include, i, "", "Path to a *.h file or a directory.");
22 DEFINE_bool2(selfcheck, k, false, "Check bmh against itself. (Requires -b)");
23 DEFINE_bool2(stdout, o, false, "Write file out to standard out.");
24 DEFINE_bool2(populate, p, false, "Populate include from bmh. (Requires -b -i)");
25 // q is reserved for quiet
26 DEFINE_string2(ref, r, "", "Resolve refs and write *.md files to path. (Requires -b -f)");
27 DEFINE_string2(spellcheck, s, "", "Spell-check [once, all, mispelling]. (Requires -b)");
28 DEFINE_bool2(tokens, t, false, "Write bmh from include. (Requires -b -i)");
29 DEFINE_bool2(crosscheck, x, false, "Check bmh against includes. (Requires -b -i)");
30 // v is reserved for verbose
31 DEFINE_bool2(skip, z, false, "Skip degenerate missed in legacy preprocessor.");
32 
33 /*  recipe for generating timestamps for existing doxygen comments
34 find include/core -type f -name '*.h' -print -exec git blame {} \; > ~/all.blame.txt
35 
36 todos:
37 add new markup to associate typedef SaveLayerFlags with Enum so that, for
38       documentation purposes, this enum is named rather than anonymous
39 check column 1 of subtopic tables to see that they start lowercase and don't have a trailing period
40 space table better for Constants
41 should Return be on same line as 'Return Value'?
42 remove anonymous header, e.g. Enum SkPaint::::anonymous_2
43 #Member lost all formatting
44 #List needs '# content ##', formatting
45 consts like enum members need fully qualfied refs to make a valid link
46 enum comments should be disallowed unless after #Enum and before first #Const
47     ... or, should look for enum comments in other places
48 trouble with aliases, plurals
49     need to keep first letter of includeWriter @param / @return lowercase
50     Quad -> quad, Quads -> quads
51 see head of selfCheck.cpp for additional todos
52  */
53 
54 /*
55   class contains named struct, enum, enum-member, method, topic, subtopic
56      everything contained by class is uniquely named
57      contained names may be reused by other classes
58   method contains named parameters
59      parameters may be reused in other methods
60  */
61 
addDefinition(const char * defStart,bool hasEnd,MarkType markType,const vector<string> & typeNameBuilder)62 bool BmhParser::addDefinition(const char* defStart, bool hasEnd, MarkType markType,
63         const vector<string>& typeNameBuilder) {
64     Definition* definition = nullptr;
65     switch (markType) {
66         case MarkType::kComment:
67             if (!this->skipToDefinitionEnd(markType)) {
68                 return false;
69             }
70             return true;
71         // these types may be referred to by name
72         case MarkType::kClass:
73         case MarkType::kStruct:
74         case MarkType::kConst:
75         case MarkType::kEnum:
76         case MarkType::kEnumClass:
77         case MarkType::kMember:
78         case MarkType::kMethod:
79         case MarkType::kTypedef: {
80             if (!typeNameBuilder.size()) {
81                 return this->reportError<bool>("unnamed markup");
82             }
83             if (typeNameBuilder.size() > 1) {
84                 return this->reportError<bool>("expected one name only");
85             }
86             const string& name = typeNameBuilder[0];
87             if (nullptr == fRoot) {
88                 fRoot = this->findBmhObject(markType, name);
89                 fRoot->fFileName = fFileName;
90                 definition = fRoot;
91             } else {
92                 if (nullptr == fParent) {
93                     return this->reportError<bool>("expected parent");
94                 }
95                 if (fParent == fRoot && hasEnd) {
96                     RootDefinition* rootParent = fRoot->rootParent();
97                     if (rootParent) {
98                         fRoot = rootParent;
99                     }
100                     definition = fParent;
101                 } else {
102                     if (!hasEnd && fRoot->find(name, RootDefinition::AllowParens::kNo)) {
103                         return this->reportError<bool>("duplicate symbol");
104                     }
105                     if (MarkType::kStruct == markType || MarkType::kClass == markType) {
106                         // if class or struct, build fRoot hierarchy
107                         // and change isDefined to search all parents of fRoot
108                         SkASSERT(!hasEnd);
109                         RootDefinition* childRoot = new RootDefinition;
110                         (fRoot->fBranches)[name] = childRoot;
111                         childRoot->setRootParent(fRoot);
112                         childRoot->fFileName = fFileName;
113                         fRoot = childRoot;
114                         definition = fRoot;
115                     } else {
116                         definition = &fRoot->fLeaves[name];
117                     }
118                 }
119             }
120             if (hasEnd) {
121                 Exemplary hasExample = Exemplary::kNo;
122                 bool hasExcluder = false;
123                 for (auto child : definition->fChildren) {
124                      if (MarkType::kExample == child->fMarkType) {
125                         hasExample = Exemplary::kYes;
126                      }
127                      hasExcluder |= MarkType::kPrivate == child->fMarkType
128                             || MarkType::kDeprecated == child->fMarkType
129                             || MarkType::kExperimental == child->fMarkType
130                             || MarkType::kNoExample == child->fMarkType;
131                 }
132                 if (fMaps[(int) markType].fExemplary != hasExample
133                         && fMaps[(int) markType].fExemplary != Exemplary::kOptional) {
134                     if (string::npos == fFileName.find("undocumented")
135                             && !hasExcluder) {
136                         hasExample == Exemplary::kNo ?
137                                 this->reportWarning("missing example") :
138                                 this->reportWarning("unexpected example");
139                     }
140 
141                 }
142                 if (MarkType::kMethod == markType) {
143                     if (fCheckMethods && !definition->checkMethod()) {
144                         return false;
145                     }
146                 }
147                 if (!this->popParentStack(definition)) {
148                     return false;
149                 }
150             } else {
151                 definition->fStart = defStart;
152                 this->skipSpace();
153                 definition->fFileName = fFileName;
154                 definition->fContentStart = fChar;
155                 definition->fLineCount = fLineCount;
156                 definition->fClone = fCloned;
157                 if (MarkType::kConst == markType) {
158                     // todo: require that fChar points to def on same line as markup
159                     // additionally add definition to class children if it is not already there
160                     if (definition->fParent != fRoot) {
161 //                        fRoot->fChildren.push_back(definition);
162                     }
163                 }
164                 definition->fName = name;
165                 if (MarkType::kMethod == markType) {
166                     if (string::npos != name.find(':', 0)) {
167                         definition->setCanonicalFiddle();
168                     } else {
169                         definition->fFiddle = name;
170                     }
171                 } else {
172                     definition->fFiddle = Definition::NormalizedName(name);
173                 }
174                 definition->fMarkType = markType;
175                 definition->fAnonymous = fAnonymous;
176                 this->setAsParent(definition);
177             }
178             } break;
179         case MarkType::kTopic:
180         case MarkType::kSubtopic:
181             SkASSERT(1 == typeNameBuilder.size());
182             if (!hasEnd) {
183                 if (!typeNameBuilder.size()) {
184                     return this->reportError<bool>("unnamed topic");
185                 }
186                 fTopics.emplace_front(markType, defStart, fLineCount, fParent);
187                 RootDefinition* rootDefinition = &fTopics.front();
188                 definition = rootDefinition;
189                 definition->fFileName = fFileName;
190                 definition->fContentStart = fChar;
191                 definition->fName = typeNameBuilder[0];
192                 Definition* parent = fParent;
193                 while (parent && MarkType::kTopic != parent->fMarkType
194                         && MarkType::kSubtopic != parent->fMarkType) {
195                     parent = parent->fParent;
196                 }
197                 definition->fFiddle = parent ? parent->fFiddle + '_' : "";
198                 definition->fFiddle += Definition::NormalizedName(typeNameBuilder[0]);
199                 this->setAsParent(definition);
200             }
201             {
202                 SkASSERT(hasEnd ? fParent : definition);
203                 string fullTopic = hasEnd ? fParent->fFiddle : definition->fFiddle;
204                 Definition* defPtr = fTopicMap[fullTopic];
205                 if (hasEnd) {
206                     if (!definition) {
207                         definition = defPtr;
208                     } else if (definition != defPtr) {
209                         return this->reportError<bool>("mismatched topic");
210                     }
211                 } else {
212                     if (nullptr != defPtr) {
213                         return this->reportError<bool>("already declared topic");
214                     }
215                     fTopicMap[fullTopic] = definition;
216                 }
217             }
218             if (hasEnd) {
219                 if (!this->popParentStack(definition)) {
220                     return false;
221                 }
222             }
223             break;
224         // these types are children of parents, but are not in named maps
225         case MarkType::kDefinedBy: {
226             string prefixed(fRoot->fName);
227             const char* start = fChar;
228             string name(start, this->trimmedBracketEnd(fMC) - start);
229             prefixed += "::" + name;
230             this->skipToEndBracket(fMC);
231             const auto leafIter = fRoot->fLeaves.find(prefixed);
232             if (fRoot->fLeaves.end() != leafIter) {
233                 this->reportError<bool>("DefinedBy already defined");
234             }
235             definition = &fRoot->fLeaves[prefixed];
236             definition->fParent = fParent;
237             definition->fStart = defStart;
238             definition->fContentStart = start;
239             definition->fName = name;
240             definition->fFiddle = Definition::NormalizedName(name);
241             definition->fContentEnd = fChar;
242             this->skipToEndBracket('\n');
243             definition->fTerminator = fChar;
244             definition->fMarkType = markType;
245             definition->fLineCount = fLineCount;
246             fParent->fChildren.push_back(definition);
247             } break;
248         case MarkType::kDescription:
249         case MarkType::kStdOut:
250         // may be one-liner
251         case MarkType::kBug:
252         case MarkType::kNoExample:
253         case MarkType::kParam:
254         case MarkType::kReturn:
255         case MarkType::kToDo:
256             if (hasEnd) {
257                 if (markType == fParent->fMarkType) {
258                     definition = fParent;
259                     if (MarkType::kBug == markType || MarkType::kReturn == markType
260                             || MarkType::kToDo == markType) {
261                         this->skipNoName();
262                     }
263                     if (!this->popParentStack(fParent)) { // if not one liner, pop
264                         return false;
265                     }
266                     if (MarkType::kParam == markType || MarkType::kReturn == markType) {
267                         if (!this->checkParamReturn(definition)) {
268                             return false;
269                         }
270                     }
271                 } else {
272                     fMarkup.emplace_front(markType, defStart, fLineCount, fParent);
273                     definition = &fMarkup.front();
274                     definition->fName = typeNameBuilder[0];
275                     definition->fFiddle = fParent->fFiddle;
276                     definition->fContentStart = fChar;
277                     definition->fContentEnd = this->trimmedBracketEnd(fMC);
278                     this->skipToEndBracket(fMC);
279                     SkAssertResult(fMC == this->next());
280                     SkAssertResult(fMC == this->next());
281                     definition->fTerminator = fChar;
282                     fParent->fChildren.push_back(definition);
283                 }
284                 break;
285             }
286         // not one-liners
287         case MarkType::kCode:
288         case MarkType::kDeprecated:
289         case MarkType::kExample:
290         case MarkType::kExperimental:
291         case MarkType::kFormula:
292         case MarkType::kFunction:
293         case MarkType::kLegend:
294         case MarkType::kList:
295         case MarkType::kPrivate:
296         case MarkType::kTable:
297         case MarkType::kTrack:
298             if (hasEnd) {
299                 definition = fParent;
300                 if (markType != fParent->fMarkType) {
301                     return this->reportError<bool>("end element mismatch");
302                 } else if (!this->popParentStack(fParent)) {
303                     return false;
304                 }
305                 if (MarkType::kExample == markType) {
306                     if (definition->fChildren.size() == 0) {
307                         TextParser emptyCheck(definition);
308                         if (emptyCheck.eof() || !emptyCheck.skipWhiteSpace()) {
309                             return this->reportError<bool>("missing example body");
310                         }
311                     }
312                     definition->setWrapper();
313                 }
314             } else {
315                 fMarkup.emplace_front(markType, defStart, fLineCount, fParent);
316                 definition = &fMarkup.front();
317                 definition->fContentStart = fChar;
318                 definition->fName = typeNameBuilder[0];
319                 definition->fFiddle = fParent->fFiddle;
320                 char suffix = '\0';
321                 bool tryAgain;
322                 do {
323                     tryAgain = false;
324                     for (const auto& child : fParent->fChildren) {
325                         if (child->fFiddle == definition->fFiddle) {
326                             if (MarkType::kExample != child->fMarkType) {
327                                 continue;
328                             }
329                             if ('\0' == suffix) {
330                                 suffix = 'a';
331                             } else if (++suffix > 'z') {
332                                 return reportError<bool>("too many examples");
333                             }
334                             definition->fFiddle = fParent->fFiddle + '_';
335                             definition->fFiddle += suffix;
336                             tryAgain = true;
337                             break;
338                         }
339                     }
340                 } while (tryAgain);
341                 this->setAsParent(definition);
342             }
343             break;
344             // always treated as one-liners (can't detect misuse easily)
345         case MarkType::kAlias:
346         case MarkType::kAnchor:
347         case MarkType::kDefine:
348         case MarkType::kDuration:
349         case MarkType::kFile:
350         case MarkType::kHeight:
351         case MarkType::kImage:
352 		case MarkType::kIn:
353 		case MarkType::kLine:
354 		case MarkType::kLiteral:
355         case MarkType::kOutdent:
356         case MarkType::kPlatform:
357         case MarkType::kPopulate:
358         case MarkType::kSeeAlso:
359         case MarkType::kSet:
360         case MarkType::kSubstitute:
361         case MarkType::kTime:
362         case MarkType::kVolatile:
363         case MarkType::kWidth:
364             if (hasEnd && MarkType::kAnchor != markType && MarkType::kLine != markType) {
365                 return this->reportError<bool>("one liners omit end element");
366             } else if (!hasEnd && MarkType::kAnchor == markType) {
367                 return this->reportError<bool>("anchor line must have end element last");
368             }
369             fMarkup.emplace_front(markType, defStart, fLineCount, fParent);
370             definition = &fMarkup.front();
371             definition->fName = typeNameBuilder[0];
372             definition->fFiddle = Definition::NormalizedName(typeNameBuilder[0]);
373             definition->fContentStart = fChar;
374             definition->fContentEnd = this->trimmedBracketEnd('\n');
375             definition->fTerminator = this->lineEnd() - 1;
376             fParent->fChildren.push_back(definition);
377             if (MarkType::kAnchor == markType) {
378                 this->skipToEndBracket(fMC);
379                 fMarkup.emplace_front(MarkType::kLink, fChar, fLineCount, definition);
380                 SkAssertResult(fMC == this->next());
381                 this->skipWhiteSpace();
382                 Definition* link = &fMarkup.front();
383                 link->fContentStart = fChar;
384                 link->fContentEnd = this->trimmedBracketEnd(fMC);
385                 this->skipToEndBracket(fMC);
386                 SkAssertResult(fMC == this->next());
387                 SkAssertResult(fMC == this->next());
388                 link->fTerminator = fChar;
389                 definition->fContentEnd = link->fContentEnd;
390                 definition->fTerminator = fChar;
391                 definition->fChildren.emplace_back(link);
392             } else if (MarkType::kAlias == markType) {
393                 this->skipWhiteSpace();
394                 const char* start = fChar;
395                 this->skipToNonAlphaNum();
396                 string alias(start, fChar - start);
397                 if (fAliasMap.end() != fAliasMap.find(alias)) {
398                     return this->reportError<bool>("duplicate alias");
399                 }
400                 fAliasMap[alias] = definition;
401 			}
402 			else if (MarkType::kLine == markType) {
403 				const char* nextLF = this->strnchr('\n', this->fEnd);
404 				const char* start = fChar;
405 				const char* end = this->trimmedBracketEnd(fMC);
406 				this->skipToEndBracket(fMC, nextLF);
407 				if (fMC != this->next() || fMC != this->next()) {
408 					return this->reportError<bool>("expected ## to delineate line");
409 				}
410 				fMarkup.emplace_front(MarkType::kText, start, fLineCount, definition);
411 				Definition* text = &fMarkup.front();
412 				text->fContentStart = start;
413 				text->fContentEnd = end;
414 				text->fTerminator = fChar;
415 				definition->fContentEnd = text->fContentEnd;
416 				definition->fTerminator = fChar;
417 				definition->fChildren.emplace_back(text);
418 			}
419             break;
420         case MarkType::kExternal:
421             (void) this->collectExternals();  // FIXME: detect errors in external defs?
422             break;
423         default:
424             SkASSERT(0);  // fixme : don't let any types be invisible
425             return true;
426     }
427     if (fParent) {
428         SkASSERT(definition);
429         SkASSERT(definition->fName.length() > 0);
430     }
431     return true;
432 }
433 
reportDuplicates(const Definition & def,const string & dup) const434 void BmhParser::reportDuplicates(const Definition& def, const string& dup) const {
435     if (MarkType::kExample == def.fMarkType && dup == def.fFiddle) {
436         TextParser reporter(&def);
437         reporter.reportError("duplicate example name");
438     }
439     for (auto& child : def.fChildren ) {
440         reportDuplicates(*child, dup);
441     }
442 }
443 
find_examples(const Definition & def,vector<string> * exampleNames)444 static void find_examples(const Definition& def, vector<string>* exampleNames) {
445     if (MarkType::kExample == def.fMarkType) {
446         exampleNames->push_back(def.fFiddle);
447     }
448     for (auto& child : def.fChildren ) {
449         find_examples(*child, exampleNames);
450     }
451 }
452 
checkExamples() const453 bool BmhParser::checkExamples() const {
454     vector<string> exampleNames;
455     for (const auto& topic : fTopicMap) {
456         if (topic.second->fParent) {
457             continue;
458         }
459         find_examples(*topic.second, &exampleNames);
460     }
461     std::sort(exampleNames.begin(), exampleNames.end());
462     string* last = nullptr;
463     string reported;
464     bool checkOK = true;
465     for (auto& nameIter : exampleNames) {
466         if (last && *last == nameIter && reported != *last) {
467             reported = *last;
468             SkDebugf("%s\n", reported.c_str());
469             for (const auto& topic : fTopicMap) {
470                 if (topic.second->fParent) {
471                     continue;
472                 }
473                 this->reportDuplicates(*topic.second, reported);
474             }
475             checkOK = false;
476         }
477         last = &nameIter;
478     }
479     return checkOK;
480 }
481 
checkParamReturn(const Definition * definition) const482 bool BmhParser::checkParamReturn(const Definition* definition) const {
483     const char* parmEndCheck = definition->fContentEnd;
484     while (parmEndCheck < definition->fTerminator) {
485         if (fMC == parmEndCheck[0]) {
486             break;
487         }
488         if (' ' < parmEndCheck[0]) {
489             this->reportError<bool>(
490                     "use full end marker on multiline #Param and #Return");
491         }
492         ++parmEndCheck;
493     }
494     return true;
495 }
496 
childOf(MarkType markType) const497 bool BmhParser::childOf(MarkType markType) const {
498     auto childError = [this](MarkType markType) -> bool {
499         string errStr = "expected ";
500         errStr += fMaps[(int) markType].fName;
501         errStr += " parent";
502         return this->reportError<bool>(errStr.c_str());
503     };
504 
505     if (markType == fParent->fMarkType) {
506         return true;
507     }
508     if (this->hasEndToken()) {
509         if (!fParent->fParent) {
510             return this->reportError<bool>("expected grandparent");
511         }
512         if (markType == fParent->fParent->fMarkType) {
513             return true;
514         }
515     }
516     return childError(markType);
517 }
518 
className(MarkType markType)519 string BmhParser::className(MarkType markType) {
520     const char* end = this->lineEnd();
521     const char* mc = this->strnchr(fMC, end);
522     string classID;
523     TextParser::Save savePlace(this);
524     this->skipSpace();
525     const char* wordStart = fChar;
526     this->skipToNonAlphaNum();
527     const char* wordEnd = fChar;
528     classID = string(wordStart, wordEnd - wordStart);
529     if (!mc) {
530         savePlace.restore();
531     }
532     string builder;
533     const Definition* parent = this->parentSpace();
534     if (parent && parent->fName != classID) {
535         builder += parent->fName;
536     }
537     if (mc) {
538         if (mc + 1 < fEnd && fMC == mc[1]) {  // if ##
539             if (markType != fParent->fMarkType) {
540                 return this->reportError<string>("unbalanced method");
541             }
542             if (builder.length() > 0 && classID.size() > 0) {
543                 if (builder != fParent->fName) {
544                     builder += "::";
545                     builder += classID;
546                     if (builder != fParent->fName) {
547                         return this->reportError<string>("name mismatch");
548                     }
549                 }
550             }
551             this->skipLine();
552             return fParent->fName;
553         }
554         fChar = mc;
555         this->next();
556     }
557     this->skipWhiteSpace();
558     if (MarkType::kEnum == markType && fChar >= end) {
559         fAnonymous = true;
560         builder += "::_anonymous";
561         return uniqueRootName(builder, markType);
562     }
563     builder = this->word(builder, "::");
564     return builder;
565 }
566 
collectExternals()567 bool BmhParser::collectExternals() {
568     do {
569         this->skipWhiteSpace();
570         if (this->eof()) {
571             break;
572         }
573         if (fMC == this->peek()) {
574             this->next();
575             if (this->eof()) {
576                 break;
577             }
578             if (fMC == this->peek()) {
579                 this->skipLine();
580                 break;
581             }
582             if (' ' >= this->peek()) {
583                 this->skipLine();
584                 continue;
585             }
586             if (this->startsWith(fMaps[(int) MarkType::kExternal].fName)) {
587                 this->skipToNonAlphaNum();
588                 continue;
589             }
590         }
591         this->skipToAlpha();
592         const char* wordStart = fChar;
593         this->skipToNonAlphaNum();
594         if (fChar - wordStart > 0) {
595             fExternals.emplace_front(MarkType::kExternal, wordStart, fChar, fLineCount, fParent);
596             RootDefinition* definition = &fExternals.front();
597             definition->fFileName = fFileName;
598             definition->fName = string(wordStart ,fChar - wordStart);
599             definition->fFiddle = Definition::NormalizedName(definition->fName);
600         }
601     } while (!this->eof());
602     return true;
603 }
604 
dump_examples(FILE * fiddleOut,const Definition & def,bool * continuation)605 static bool dump_examples(FILE* fiddleOut, const Definition& def, bool* continuation) {
606     if (MarkType::kExample == def.fMarkType) {
607         string result;
608         if (!def.exampleToScript(&result, Definition::ExampleOptions::kAll)) {
609             return false;
610         }
611         if (result.length() > 0) {
612             result += "\n";
613             result += "}";
614             if (*continuation) {
615                 fprintf(fiddleOut, ",\n");
616             } else {
617                 *continuation = true;
618             }
619             fprintf(fiddleOut, "%s", result.c_str());
620         }
621         return true;
622     }
623     for (auto& child : def.fChildren ) {
624         if (!dump_examples(fiddleOut, *child, continuation)) {
625             return false;
626         }
627     }
628     return true;
629 }
630 
dumpExamples(const char * fiddleJsonFileName) const631 bool BmhParser::dumpExamples(const char* fiddleJsonFileName) const {
632     FILE* fiddleOut = fopen(fiddleJsonFileName, "wb");
633     if (!fiddleOut) {
634         SkDebugf("could not open output file %s\n", fiddleJsonFileName);
635         return false;
636     }
637     fprintf(fiddleOut, "{\n");
638     bool continuation = false;
639     for (const auto& topic : fTopicMap) {
640         if (topic.second->fParent) {
641             continue;
642         }
643         dump_examples(fiddleOut, *topic.second, &continuation);
644     }
645     fprintf(fiddleOut, "\n}\n");
646     fclose(fiddleOut);
647     SkDebugf("wrote %s\n", fiddleJsonFileName);
648     return true;
649 }
650 
endHashCount() const651 int BmhParser::endHashCount() const {
652     const char* end = fLine + this->lineLength();
653     int count = 0;
654     while (fLine < end && fMC == *--end) {
655         count++;
656     }
657     return count;
658 }
659 
endTableColumn(const char * end,const char * terminator)660 bool BmhParser::endTableColumn(const char* end, const char* terminator) {
661     if (!this->popParentStack(fParent)) {
662         return false;
663     }
664     fWorkingColumn->fContentEnd = end;
665     fWorkingColumn->fTerminator = terminator;
666     fColStart = fChar - 1;
667     this->skipSpace();
668     fTableState = TableState::kColumnStart;
669     return true;
670 }
671 
672 // FIXME: some examples may produce different output on different platforms
673 // if the text output can be different, think of how to author that
674 
findDefinitions()675 bool BmhParser::findDefinitions() {
676     bool lineStart = true;
677     const char* lastChar = nullptr;
678     const char* lastMC = nullptr;
679     fParent = nullptr;
680     while (!this->eof()) {
681         if (this->peek() == fMC) {
682             lastMC = fChar;
683             this->next();
684             if (this->peek() == fMC) {
685                 this->next();
686                 if (!lineStart && ' ' < this->peek()) {
687                     return this->reportError<bool>("expected definition");
688                 }
689                 if (this->peek() != fMC) {
690                     if (MarkType::kColumn == fParent->fMarkType) {
691                         SkASSERT(TableState::kColumnEnd == fTableState);
692                         if (!this->endTableColumn(lastChar, lastMC)) {
693                             return false;
694                         }
695                         SkASSERT(fRow);
696                         if (!this->popParentStack(fParent)) {
697                             return false;
698                         }
699                         fRow->fContentEnd = fWorkingColumn->fContentEnd;
700                         fWorkingColumn = nullptr;
701                         fRow = nullptr;
702                         fTableState = TableState::kNone;
703                     } else {
704                         vector<string> parentName;
705                         parentName.push_back(fParent->fName);
706                         if (!this->addDefinition(fChar - 1, true, fParent->fMarkType, parentName)) {
707                             return false;
708                         }
709                     }
710                 } else {
711                     SkAssertResult(this->next() == fMC);
712                     fMC = this->next();  // change markup character
713                     if (' ' >= fMC) {
714                         return this->reportError<bool>("illegal markup character");
715                     }
716                     fMarkup.emplace_front(MarkType::kMarkChar, fChar - 1, fLineCount, fParent);
717                     Definition* markChar = &fMarkup.front();
718                     markChar->fContentStart = fChar - 1;
719                     this->skipToEndBracket('\n');
720                     markChar->fContentEnd = fChar;
721                     markChar->fTerminator = fChar;
722                     fParent->fChildren.push_back(markChar);
723                 }
724             } else if (this->peek() >= 'A' && this->peek() <= 'Z') {
725                 const char* defStart = fChar - 1;
726                 MarkType markType = this->getMarkType(MarkLookup::kRequire);
727                 bool hasEnd = this->hasEndToken();
728                 if (!hasEnd) {
729                     MarkType parentType = fParent ? fParent->fMarkType : MarkType::kRoot;
730                     uint64_t parentMask = fMaps[(int) markType].fParentMask;
731                     if (parentMask && !(parentMask & (1LL << (int) parentType))) {
732                         return this->reportError<bool>("invalid parent");
733                     }
734                 }
735                 if (!this->skipName(fMaps[(int) markType].fName)) {
736                     return this->reportError<bool>("illegal markup character");
737                 }
738                 if (!this->skipSpace()) {
739                     return this->reportError<bool>("unexpected end");
740                 }
741                 bool expectEnd = true;
742                 vector<string> typeNameBuilder = this->typeName(markType, &expectEnd);
743                 if (fCloned && MarkType::kMethod != markType && MarkType::kExample != markType
744                         && !fAnonymous) {
745                     return this->reportError<bool>("duplicate name");
746                 }
747                 if (hasEnd && expectEnd) {
748                     SkASSERT(fMC != this->peek());
749                 }
750                 if (!this->addDefinition(defStart, hasEnd, markType, typeNameBuilder)) {
751                     return false;
752                 }
753                 continue;
754             } else if (this->peek() == ' ') {
755                 if (!fParent || (MarkType::kTable != fParent->fMarkType
756                         && MarkType::kLegend != fParent->fMarkType
757                         && MarkType::kList != fParent->fMarkType
758 						&& MarkType::kLine != fParent->fMarkType)) {
759                     int endHashes = this->endHashCount();
760                     if (endHashes <= 1) {
761                         if (fParent) {
762                             if (TableState::kColumnEnd == fTableState) {
763                                 if (!this->endTableColumn(lastChar, lastMC)) {
764                                     return false;
765                                 }
766                             } else {  // one line comment
767                                 fMarkup.emplace_front(MarkType::kComment, fChar - 1, fLineCount,
768                                         fParent);
769                                 Definition* comment = &fMarkup.front();
770                                 comment->fContentStart = fChar - 1;
771                                 this->skipToEndBracket('\n');
772                                 comment->fContentEnd = fChar;
773                                 comment->fTerminator = fChar;
774                                 fParent->fChildren.push_back(comment);
775                             }
776                         } else {
777                             fChar = fLine + this->lineLength() - 1;
778                         }
779                     } else {  // table row
780                         if (2 != endHashes) {
781                             string errorStr = "expect ";
782                             errorStr += fMC;
783                             errorStr += fMC;
784                             return this->reportError<bool>(errorStr.c_str());
785                         }
786                         if (!fParent || MarkType::kTable != fParent->fMarkType) {
787                             return this->reportError<bool>("missing table");
788                         }
789                     }
790                 } else if (TableState::kNone == fTableState) {
791                     // fixme? no nested tables for now
792                     fColStart = fChar - 1;
793                     fMarkup.emplace_front(MarkType::kRow, fColStart, fLineCount, fParent);
794                     fRow = &fMarkup.front();
795                     fRow->fName = fParent->fName;
796                     this->skipWhiteSpace();
797                     fRow->fContentStart = fChar;
798                     this->setAsParent(fRow);
799                     fTableState = TableState::kColumnStart;
800                 }
801                 if (TableState::kColumnStart == fTableState) {
802                     fMarkup.emplace_front(MarkType::kColumn, fColStart, fLineCount, fParent);
803                     fWorkingColumn = &fMarkup.front();
804                     fWorkingColumn->fName = fParent->fName;
805                     fWorkingColumn->fContentStart = fChar;
806                     this->setAsParent(fWorkingColumn);
807                     fTableState = TableState::kColumnEnd;
808                     continue;
809                 }
810             }
811         }
812         char nextChar = this->next();
813         lineStart = nextChar == '\n';
814         if (' ' < nextChar) {
815             lastChar = fChar;
816         }
817     }
818     if (fParent) {
819         return fParent->reportError<bool>("mismatched end");
820     }
821     return true;
822 }
823 
getMarkType(MarkLookup lookup) const824 MarkType BmhParser::getMarkType(MarkLookup lookup) const {
825     for (int index = 0; index <= Last_MarkType; ++index) {
826         int typeLen = strlen(fMaps[index].fName);
827         if (typeLen == 0) {
828             continue;
829         }
830         if (fChar + typeLen >= fEnd || fChar[typeLen] > ' ') {
831             continue;
832         }
833         int chCompare = strncmp(fChar, fMaps[index].fName, typeLen);
834         if (chCompare < 0) {
835             goto fail;
836         }
837         if (chCompare == 0) {
838             return (MarkType) index;
839         }
840     }
841 fail:
842     if (MarkLookup::kRequire == lookup) {
843         return this->reportError<MarkType>("unknown mark type");
844     }
845     return MarkType::kNone;
846 }
847 
848     // write #In to show containing #Topic
849 	// write #Line with one liner from Member_Functions, Constructors, Operators if method,
850 	//    from Constants if enum, otherwise from #Subtopic containing match
hackFiles()851 bool HackParser::hackFiles() {
852     string filename(fFileName);
853     size_t len = filename.length() - 1;
854     while (len > 0 && (isalnum(filename[len]) || '_' == filename[len] || '.' == filename[len])) {
855         --len;
856     }
857     filename = filename.substr(len + 1);
858     if (filename.substr(0, 2) != "Sk") {
859         return true;
860     }
861     size_t under = filename.find('_');
862     SkASSERT(under);
863     string className = filename.substr(0, under);
864     fOut = fopen(filename.c_str(), "wb");
865     if (!fOut) {
866         SkDebugf("could not open output file %s\n", filename.c_str());
867         return false;
868     }
869     auto mapEntry = fBmhParser.fClassMap.find(className);
870     SkASSERT(fBmhParser.fClassMap.end() != mapEntry);
871     const Definition* classMarkup = &mapEntry->second;
872     const Definition* root = classMarkup->fParent;
873     SkASSERT(root);
874     SkASSERT(root->fTerminator);
875     SkASSERT('\n' == root->fTerminator[0]);
876     SkASSERT(!root->fParent);
877     fStart = root->fStart;
878     fChar = fStart;
879     fClassesAndStructs = nullptr;
880     fConstants = nullptr;
881     fConstructors = nullptr;
882     fMemberFunctions = nullptr;
883     fMembers = nullptr;
884     fOperators = nullptr;
885     fRelatedFunctions = nullptr;
886     this->topicIter(root);
887     fprintf(fOut, "%.*s", (int) (fEnd - fChar), fChar);
888     fclose(fOut);
889     if (this->writtenFileDiffers(filename, root->fFileName)) {
890         SkDebugf("wrote %s\n", filename.c_str());
891     } else {
892         remove(filename.c_str());
893     }
894     return true;
895 }
896 
searchTable(const Definition * tableHolder,const Definition * match)897 string HackParser::searchTable(const Definition* tableHolder, const Definition* match) {
898     if (!tableHolder) {
899         return "";
900     }
901     string bestMatch;
902     string result;
903     for (auto table : tableHolder->fChildren) {
904         if (MarkType::kTable == table->fMarkType) {
905             for (auto row : table->fChildren) {
906                 if (MarkType::kRow == row->fMarkType) {
907                     const Definition* col0 = row->fChildren[0];
908                     size_t len = col0->fContentEnd - col0->fContentStart;
909                     string method = string(col0->fContentStart, len);
910                     if (len - 2 == method.find("()") && islower(method[0])
911                             && Definition::MethodType::kOperator != match->fMethodType) {
912                         method = method.substr(0, len - 2);
913                     }
914                     if (string::npos == match->fName.find(method)) {
915                         continue;
916                     }
917                     if (bestMatch.length() < method.length()) {
918                         bestMatch = method;
919                         const Definition * col1 = row->fChildren[1];
920                         if (col1->fContentEnd <= col1->fContentStart) {
921                             SkASSERT(string::npos != col1->fFileName.find("SkImageInfo"));
922                             result = "incomplete";
923                         } else {
924                             result = string(col1->fContentStart, col1->fContentEnd -
925                                     col1->fContentStart);
926                         }
927                     }
928                 }
929             }
930         }
931     }
932     return result;
933 }
934 
935 // returns true if topic has method
topicIter(const Definition * topic)936 void HackParser::topicIter(const Definition* topic) {
937     if (string::npos != topic->fName.find(MdOut::kClassesAndStructs)) {
938         SkASSERT(!fClassesAndStructs);
939         fClassesAndStructs = topic;
940     }
941     if (string::npos != topic->fName.find(MdOut::kConstants)) {
942         SkASSERT(!fConstants);
943         fConstants = topic;
944     }
945     if (string::npos != topic->fName.find(MdOut::kConstructors)) {
946         SkASSERT(!fConstructors);
947         fConstructors = topic;
948     }
949     if (string::npos != topic->fName.find(MdOut::kMemberFunctions)) {
950         SkASSERT(!fMemberFunctions);
951         fMemberFunctions = topic;
952     }
953     if (string::npos != topic->fName.find(MdOut::kMembers)) {
954         SkASSERT(!fMembers);
955         fMembers = topic;
956     }
957     if (string::npos != topic->fName.find(MdOut::kOperators)) {
958         SkASSERT(!fOperators);
959         fOperators = topic;
960     }
961     if (string::npos != topic->fName.find(MdOut::kRelatedFunctions)) {
962         SkASSERT(!fRelatedFunctions);
963         fRelatedFunctions = topic;
964     }
965     for (auto child : topic->fChildren) {
966         string oneLiner;
967         bool hasIn = false;
968         bool hasLine = false;
969         for (auto part : child->fChildren) {
970             hasIn |= MarkType::kIn == part->fMarkType;
971             hasLine |= MarkType::kLine == part->fMarkType;
972         }
973         switch (child->fMarkType) {
974             case MarkType::kMethod: {
975                 if (Definition::MethodType::kOperator == child->fMethodType) {
976                     SkDebugf("");
977                 }
978                 hasIn |= MarkType::kTopic != topic->fMarkType &&
979                         MarkType::kSubtopic != topic->fMarkType;  // don't write #In if parent is class
980                 hasLine |= child->fClone;
981                 if (!hasLine) {
982                     // find member_functions, add entry 2nd column text to #Line
983                     for (auto tableHolder : { fMemberFunctions, fConstructors, fOperators }) {
984                         if (!tableHolder) {
985                             continue;
986                         }
987                         if (Definition::MethodType::kConstructor == child->fMethodType
988                                 && fConstructors != tableHolder) {
989                             continue;
990                         }
991                         if (Definition::MethodType::kOperator == child->fMethodType
992                                 && fOperators != tableHolder) {
993                             continue;
994                         }
995                         string temp = this->searchTable(tableHolder, child);
996                         if ("" != temp) {
997                             SkASSERT("" == oneLiner || temp == oneLiner);
998                             oneLiner = temp;
999                         }
1000                     }
1001                     if ("" == oneLiner) {
1002                         const Definition* csParent = child->csParent();
1003                         if (!csParent || !csParent->csParent()) {
1004                             SkDebugf("");
1005                         }
1006     #ifdef SK_DEBUG
1007                         const Definition* rootParent = topic;
1008                         while (rootParent->fParent && MarkType::kClass != rootParent->fMarkType
1009                                  && MarkType::kStruct != rootParent->fMarkType) {
1010                             rootParent = rootParent->fParent;
1011                         }
1012     #endif
1013                         SkASSERT(rootParent);
1014                         SkASSERT(MarkType::kClass == rootParent->fMarkType
1015                                 || MarkType::kStruct == rootParent->fMarkType);
1016                         hasLine = true;
1017                     }
1018                 }
1019 
1020                 if (hasIn && hasLine) {
1021                     continue;
1022                 }
1023                 const char* start = fChar;
1024                 const char* end = child->fContentStart;
1025                 fprintf(fOut, "%.*s", (int) (end - start), start);
1026                 fChar = end;
1027                 // write to method markup header end
1028                 if (!hasIn) {
1029                     fprintf(fOut, "\n#In %s", topic->fName.c_str());
1030                 }
1031                 if (!hasLine) {
1032                     fprintf(fOut, "\n#Line # %s ##", oneLiner.c_str());
1033                 }
1034                 } break;
1035             case MarkType::kTopic:
1036             case MarkType::kSubtopic:
1037                 this->addOneLiner(fRelatedFunctions, child, hasLine, true);
1038                 this->topicIter(child);
1039                 break;
1040             case MarkType::kStruct:
1041             case MarkType::kClass:
1042                 this->addOneLiner(fClassesAndStructs, child, hasLine, false);
1043                 this->topicIter(child);
1044                 break;
1045             case MarkType::kEnum:
1046             case MarkType::kEnumClass:
1047                 this->addOneLiner(fConstants, child, hasLine, true);
1048                 break;
1049             case MarkType::kMember:
1050                 this->addOneLiner(fMembers, child, hasLine, false);
1051                 break;
1052             default:
1053                 ;
1054         }
1055     }
1056 }
1057 
addOneLiner(const Definition * defTable,const Definition * child,bool hasLine,bool lfAfter)1058 void HackParser::addOneLiner(const Definition* defTable, const Definition* child, bool hasLine,
1059         bool lfAfter) {
1060     if (hasLine) {
1061         return;
1062     }
1063     string oneLiner = this->searchTable(defTable, child);
1064     if ("" == oneLiner) {
1065         return;
1066     }
1067     const char* start = fChar;
1068     const char* end = child->fContentStart;
1069     fprintf(fOut, "%.*s", (int) (end - start), start);
1070     fChar = end;
1071     if (!lfAfter) {
1072         fprintf(fOut, "\n");
1073     }
1074     fprintf(fOut, "#Line # %s ##", oneLiner.c_str());
1075     if (lfAfter) {
1076         fprintf(fOut, "\n");
1077     }
1078 }
1079 
hasEndToken() const1080 bool BmhParser::hasEndToken() const {
1081     const char* last = fLine + this->lineLength();
1082     while (last > fLine && ' ' >= *--last)
1083         ;
1084     if (--last < fLine) {
1085         return false;
1086     }
1087     return last[0] == fMC && last[1] == fMC;
1088 }
1089 
memberName()1090 string BmhParser::memberName() {
1091     const char* wordStart;
1092     const char* prefixes[] = { "static", "const" };
1093     do {
1094         this->skipSpace();
1095         wordStart = fChar;
1096         this->skipToNonAlphaNum();
1097     } while (this->anyOf(wordStart, prefixes, SK_ARRAY_COUNT(prefixes)));
1098     if ('*' == this->peek()) {
1099         this->next();
1100     }
1101     return this->className(MarkType::kMember);
1102 }
1103 
methodName()1104 string BmhParser::methodName() {
1105     if (this->hasEndToken()) {
1106         if (!fParent || !fParent->fName.length()) {
1107             return this->reportError<string>("missing parent method name");
1108         }
1109         SkASSERT(fMC == this->peek());
1110         this->next();
1111         SkASSERT(fMC == this->peek());
1112         this->next();
1113         SkASSERT(fMC != this->peek());
1114         return fParent->fName;
1115     }
1116     string builder;
1117     const char* end = this->lineEnd();
1118     const char* paren = this->strnchr('(', end);
1119     if (!paren) {
1120         return this->reportError<string>("missing method name and reference");
1121     }
1122     const char* nameStart = paren;
1123     char ch;
1124     bool expectOperator = false;
1125     bool isConstructor = false;
1126     const char* nameEnd = nullptr;
1127     while (nameStart > fChar && ' ' != (ch = *--nameStart)) {
1128         if (!isalnum(ch) && '_' != ch) {
1129             if (nameEnd) {
1130                 break;
1131             }
1132             expectOperator = true;
1133             continue;
1134         }
1135         if (!nameEnd) {
1136             nameEnd = nameStart + 1;
1137         }
1138     }
1139     if (!nameEnd) {
1140          return this->reportError<string>("unexpected method name char");
1141     }
1142     if (' ' == nameStart[0]) {
1143         ++nameStart;
1144     }
1145     if (nameEnd <= nameStart) {
1146         return this->reportError<string>("missing method name");
1147     }
1148     if (nameStart >= paren) {
1149         return this->reportError<string>("missing method name length");
1150     }
1151     string name(nameStart, nameEnd - nameStart);
1152     bool allLower = true;
1153     for (int index = 0; index < (int) (nameEnd - nameStart); ++index) {
1154         if (!islower(nameStart[index])) {
1155             allLower = false;
1156             break;
1157         }
1158     }
1159     if (expectOperator && "operator" != name) {
1160          return this->reportError<string>("expected operator");
1161     }
1162     const Definition* parent = this->parentSpace();
1163     if (parent && parent->fName.length() > 0) {
1164         if (parent->fName == name) {
1165             isConstructor = true;
1166         } else if ('~' == name[0]) {
1167             if (parent->fName != name.substr(1)) {
1168                  return this->reportError<string>("expected destructor");
1169             }
1170             isConstructor = true;
1171         }
1172         builder = parent->fName + "::";
1173     }
1174     bool addConst = false;
1175     if (isConstructor || expectOperator) {
1176         paren = this->strnchr(')', end) + 1;
1177         TextParser::Save saveState(this);
1178         this->skipTo(paren);
1179         if (this->skipExact("_const")) {
1180             addConst = true;
1181         }
1182         saveState.restore();
1183     }
1184     builder.append(nameStart, paren - nameStart);
1185     if (addConst) {
1186         builder.append("_const");
1187     }
1188     if (!expectOperator && allLower) {
1189         builder.append("()");
1190     }
1191     int parens = 0;
1192     while (fChar < end || parens > 0) {
1193         if ('(' == this->peek()) {
1194             ++parens;
1195         } else if (')' == this->peek()) {
1196             --parens;
1197         }
1198         this->next();
1199     }
1200     TextParser::Save saveState(this);
1201     this->skipWhiteSpace();
1202     if (this->startsWith("const")) {
1203         this->skipName("const");
1204     } else {
1205         saveState.restore();
1206     }
1207 //    this->next();
1208     return uniqueRootName(builder, MarkType::kMethod);
1209 }
1210 
parentSpace() const1211 const Definition* BmhParser::parentSpace() const {
1212     Definition* parent = nullptr;
1213     Definition* test = fParent;
1214     while (test) {
1215         if (MarkType::kClass == test->fMarkType ||
1216                 MarkType::kEnumClass == test->fMarkType ||
1217                 MarkType::kStruct == test->fMarkType) {
1218             parent = test;
1219             break;
1220         }
1221         test = test->fParent;
1222     }
1223     return parent;
1224 }
1225 
popParentStack(Definition * definition)1226 bool BmhParser::popParentStack(Definition* definition) {
1227     if (!fParent) {
1228         return this->reportError<bool>("missing parent");
1229     }
1230     if (definition != fParent) {
1231         return this->reportError<bool>("definition end is not parent");
1232     }
1233     if (!definition->fStart) {
1234         return this->reportError<bool>("definition missing start");
1235     }
1236     if (definition->fContentEnd) {
1237         return this->reportError<bool>("definition already ended");
1238     }
1239     definition->fContentEnd = fLine - 1;
1240     definition->fTerminator = fChar;
1241     fParent = definition->fParent;
1242     if (!fParent || (MarkType::kTopic == fParent->fMarkType && !fParent->fParent)) {
1243         fRoot = nullptr;
1244     }
1245     return true;
1246 }
1247 
TextParser(const Definition * definition)1248 TextParser::TextParser(const Definition* definition) :
1249     TextParser(definition->fFileName, definition->fContentStart, definition->fContentEnd,
1250         definition->fLineCount) {
1251 }
1252 
reportError(const char * errorStr) const1253 void TextParser::reportError(const char* errorStr) const {
1254     this->reportWarning(errorStr);
1255     SkDebugf("");  // convenient place to set a breakpoint
1256 }
1257 
reportWarning(const char * errorStr) const1258 void TextParser::reportWarning(const char* errorStr) const {
1259     TextParser err(fFileName, fLine, fEnd, fLineCount);
1260     size_t lineLen = this->lineLength();
1261     ptrdiff_t spaces = fChar - fLine;
1262     while (spaces > 0 && (size_t) spaces > lineLen) {
1263         ++err.fLineCount;
1264         err.fLine += lineLen;
1265         spaces -= lineLen;
1266         lineLen = err.lineLength();
1267     }
1268 	string fileName;
1269 #ifdef SK_BUILD_FOR_WIN
1270 	TCHAR pathChars[MAX_PATH];
1271 	DWORD pathLen = GetCurrentDirectory(MAX_PATH, pathChars);
1272 	for (DWORD index = 0; index < pathLen; ++index) {
1273 		fileName += pathChars[index] == (char)pathChars[index] ? (char)pathChars[index] : '?';
1274 	}
1275 	fileName += '\\';
1276 #endif
1277 	fileName += fFileName;
1278     SkDebugf("\n%s(%zd): error: %s\n", fileName.c_str(), err.fLineCount, errorStr);
1279     if (0 == lineLen) {
1280         SkDebugf("[blank line]\n");
1281     } else {
1282         while (lineLen > 0 && '\n' == err.fLine[lineLen - 1]) {
1283             --lineLen;
1284         }
1285         SkDebugf("%.*s\n", (int) lineLen, err.fLine);
1286         SkDebugf("%*s^\n", (int) spaces, "");
1287     }
1288 }
1289 
typedefName()1290 string TextParser::typedefName() {
1291     // look for typedef as one of three forms:
1292     // typedef return-type (*NAME)(params);
1293     // typedef alias NAME;
1294     // typedef std::function<alias> NAME;
1295     string builder;
1296     const char* end = this->doubleLF();
1297     if (!end) {
1298        end = fEnd;
1299     }
1300     const char* altEnd = this->strnstr("#Typedef ##", end);
1301     if (altEnd) {
1302         end = this->strnchr('\n', end);
1303     }
1304     if (!end) {
1305         return this->reportError<string>("missing typedef std::function end bracket >");
1306     }
1307     bool stdFunction = this->startsWith("std::function");
1308     if (stdFunction) {
1309         if (!this->skipToEndBracket('>')) {
1310             return this->reportError<string>("missing typedef std::function end bracket >");
1311         }
1312         this->next();
1313         this->skipWhiteSpace();
1314         builder += string(fChar, end - fChar);
1315     } else {
1316         const char* paren = this->strnchr('(', end);
1317         if (!paren) {
1318             const char* lastWord = nullptr;
1319             do {
1320                 this->skipToWhiteSpace();
1321                 if (fChar < end && isspace(fChar[0])) {
1322                     this->skipWhiteSpace();
1323                     lastWord = fChar;
1324                 } else {
1325                     break;
1326                 }
1327             } while (true);
1328             if (!lastWord) {
1329                 return this->reportError<string>("missing typedef name");
1330             }
1331             builder += string(lastWord, end - lastWord);
1332         } else {
1333             this->skipTo(paren);
1334             this->next();
1335             if ('*' != this->next()) {
1336                 return this->reportError<string>("missing typedef function asterisk");
1337             }
1338             const char* nameStart = fChar;
1339             if (!this->skipToEndBracket(')')) {
1340                 return this->reportError<string>("missing typedef function )");
1341             }
1342             builder += string(nameStart, fChar - nameStart);
1343             if (!this->skipToEndBracket('(')) {
1344                 return this->reportError<string>("missing typedef params (");
1345             }
1346             if (! this->skipToEndBracket(')')) {
1347                 return this->reportError<string>("missing typedef params )");
1348             }
1349             this->skipTo(end);
1350         }
1351     }
1352     return builder;
1353 }
1354 
skipNoName()1355 bool BmhParser::skipNoName() {
1356     if ('\n' == this->peek()) {
1357         this->next();
1358         return true;
1359     }
1360     this->skipWhiteSpace();
1361     if (fMC != this->peek()) {
1362         return this->reportError<bool>("expected end mark");
1363     }
1364     this->next();
1365     if (fMC != this->peek()) {
1366         return this->reportError<bool>("expected end mark");
1367     }
1368     this->next();
1369     return true;
1370 }
1371 
skipToDefinitionEnd(MarkType markType)1372 bool BmhParser::skipToDefinitionEnd(MarkType markType) {
1373     if (this->eof()) {
1374         return this->reportError<bool>("missing end");
1375     }
1376     const char* start = fLine;
1377     int startLineCount = fLineCount;
1378     int stack = 1;
1379     ptrdiff_t lineLen;
1380     bool foundEnd = false;
1381     do {
1382         lineLen = this->lineLength();
1383         if (fMC != *fChar++) {
1384             continue;
1385         }
1386         if (fMC == *fChar) {
1387             continue;
1388         }
1389         if (' ' == *fChar) {
1390             continue;
1391         }
1392         MarkType nextType = this->getMarkType(MarkLookup::kAllowUnknown);
1393         if (markType != nextType) {
1394             continue;
1395         }
1396         bool hasEnd = this->hasEndToken();
1397         if (hasEnd) {
1398             if (!--stack) {
1399                 foundEnd = true;
1400                 continue;
1401             }
1402         } else {
1403             ++stack;
1404         }
1405     } while ((void) ++fLineCount, (void) (fLine += lineLen), (void) (fChar = fLine),
1406             !this->eof() && !foundEnd);
1407     if (foundEnd) {
1408         return true;
1409     }
1410     fLineCount = startLineCount;
1411     fLine = start;
1412     fChar = start;
1413     return this->reportError<bool>("unbalanced stack");
1414 }
1415 
skipToString()1416 bool BmhParser::skipToString() {
1417 	this->skipSpace();
1418 	if (fMC != this->peek()) {
1419 		return this->reportError<bool>("expected end mark");
1420 	}
1421 	this->next();
1422 	this->skipSpace();
1423 	// body is text from here to double fMC
1424 		// no single fMC allowed, no linefeed allowed
1425 	return true;
1426 }
1427 
topicName()1428 vector<string> BmhParser::topicName() {
1429     vector<string> result;
1430     this->skipWhiteSpace();
1431     const char* lineEnd = fLine + this->lineLength();
1432     const char* nameStart = fChar;
1433     while (fChar < lineEnd) {
1434         char ch = this->next();
1435         SkASSERT(',' != ch);
1436         if ('\n' == ch) {
1437             break;
1438         }
1439         if (fMC == ch) {
1440             break;
1441         }
1442     }
1443     if (fChar - 1 > nameStart) {
1444         string builder(nameStart, fChar - nameStart - 1);
1445         trim_start_end(builder);
1446         result.push_back(builder);
1447     }
1448     if (fChar < lineEnd && fMC == this->peek()) {
1449         this->next();
1450     }
1451     return result;
1452 }
1453 
1454 // typeName parsing rules depend on mark type
typeName(MarkType markType,bool * checkEnd)1455 vector<string> BmhParser::typeName(MarkType markType, bool* checkEnd) {
1456     fAnonymous = false;
1457     fCloned = false;
1458     vector<string> result;
1459     string builder;
1460     if (fParent) {
1461         builder = fParent->fName;
1462     }
1463     switch (markType) {
1464         case MarkType::kEnum:
1465             // enums may be nameless
1466         case MarkType::kConst:
1467         case MarkType::kEnumClass:
1468         case MarkType::kClass:
1469         case MarkType::kStruct:
1470             // expect name
1471             builder = this->className(markType);
1472             break;
1473         case MarkType::kExample:
1474             // check to see if one already exists -- if so, number this one
1475             builder = this->uniqueName(string(), markType);
1476             this->skipNoName();
1477             break;
1478         case MarkType::kCode:
1479         case MarkType::kDeprecated:
1480         case MarkType::kDescription:
1481         case MarkType::kDoxygen:
1482         case MarkType::kExperimental:
1483         case MarkType::kExternal:
1484         case MarkType::kFormula:
1485         case MarkType::kFunction:
1486         case MarkType::kLegend:
1487         case MarkType::kList:
1488         case MarkType::kNoExample:
1489         case MarkType::kPrivate:
1490         case MarkType::kTrack:
1491             this->skipNoName();
1492             break;
1493 		case MarkType::kLine:
1494 			this->skipToString();
1495 			break;
1496         case MarkType::kAlias:
1497         case MarkType::kAnchor:
1498         case MarkType::kBug:  // fixme: expect number
1499         case MarkType::kDefine:
1500         case MarkType::kDefinedBy:
1501         case MarkType::kDuration:
1502         case MarkType::kFile:
1503         case MarkType::kHeight:
1504         case MarkType::kImage:
1505 		case MarkType::kIn:
1506         case MarkType::kLiteral:
1507         case MarkType::kOutdent:
1508         case MarkType::kPlatform:
1509         case MarkType::kPopulate:
1510         case MarkType::kReturn:
1511         case MarkType::kSeeAlso:
1512         case MarkType::kSet:
1513         case MarkType::kSubstitute:
1514         case MarkType::kTime:
1515         case MarkType::kToDo:
1516         case MarkType::kVolatile:
1517         case MarkType::kWidth:
1518             *checkEnd = false;  // no name, may have text body
1519             break;
1520         case MarkType::kStdOut:
1521             this->skipNoName();
1522             break;  // unnamed
1523         case MarkType::kMember:
1524             builder = this->memberName();
1525             break;
1526         case MarkType::kMethod:
1527             builder = this->methodName();
1528             break;
1529         case MarkType::kTypedef:
1530             builder = this->typedefName();
1531             break;
1532         case MarkType::kParam:
1533            // fixme: expect camelCase
1534             builder = this->word("", "");
1535             this->skipSpace();
1536             *checkEnd = false;
1537             break;
1538         case MarkType::kTable:
1539             this->skipNoName();
1540             break;  // unnamed
1541         case MarkType::kSubtopic:
1542         case MarkType::kTopic:
1543             // fixme: start with cap, allow space, hyphen, stop on comma
1544             // one topic can have multiple type names delineated by comma
1545             result = this->topicName();
1546             if (result.size() == 0 && this->hasEndToken()) {
1547                 break;
1548             }
1549             return result;
1550         default:
1551             // fixme: don't allow silent failures
1552             SkASSERT(0);
1553     }
1554     result.push_back(builder);
1555     return result;
1556 }
1557 
typedefName()1558 string BmhParser::typedefName() {
1559     if (this->hasEndToken()) {
1560         if (!fParent || !fParent->fName.length()) {
1561             return this->reportError<string>("missing parent typedef name");
1562         }
1563         SkASSERT(fMC == this->peek());
1564         this->next();
1565         SkASSERT(fMC == this->peek());
1566         this->next();
1567         SkASSERT(fMC != this->peek());
1568         return fParent->fName;
1569     }
1570     string builder;
1571     const Definition* parent = this->parentSpace();
1572     if (parent && parent->fName.length() > 0) {
1573         builder = parent->fName + "::";
1574     }
1575     builder += TextParser::typedefName();
1576     return uniqueRootName(builder, MarkType::kTypedef);
1577 }
1578 
uniqueName(const string & base,MarkType markType)1579 string BmhParser::uniqueName(const string& base, MarkType markType) {
1580     string builder(base);
1581     if (!builder.length()) {
1582         builder = fParent->fName;
1583     }
1584     if (!fParent) {
1585         return builder;
1586     }
1587     int number = 2;
1588     string numBuilder(builder);
1589     do {
1590         for (const auto& iter : fParent->fChildren) {
1591             if (markType == iter->fMarkType) {
1592                 if (iter->fName == numBuilder) {
1593                     fCloned = true;
1594                     numBuilder = builder + '_' + to_string(number);
1595                     goto tryNext;
1596                 }
1597             }
1598         }
1599         break;
1600 tryNext: ;
1601     } while (++number);
1602     return numBuilder;
1603 }
1604 
uniqueRootName(const string & base,MarkType markType)1605 string BmhParser::uniqueRootName(const string& base, MarkType markType) {
1606     auto checkName = [markType](const Definition& def, const string& numBuilder) -> bool {
1607         return markType == def.fMarkType && def.fName == numBuilder;
1608     };
1609 
1610     string builder(base);
1611     if (!builder.length()) {
1612         builder = fParent->fName;
1613     }
1614     int number = 2;
1615     string numBuilder(builder);
1616     Definition* cloned = nullptr;
1617     do {
1618         if (fRoot) {
1619             for (auto& iter : fRoot->fBranches) {
1620                 if (checkName(*iter.second, numBuilder)) {
1621                     cloned = iter.second;
1622                     goto tryNext;
1623                 }
1624             }
1625             for (auto& iter : fRoot->fLeaves) {
1626                 if (checkName(iter.second, numBuilder)) {
1627                     cloned = &iter.second;
1628                     goto tryNext;
1629                 }
1630             }
1631         } else if (fParent) {
1632             for (auto& iter : fParent->fChildren) {
1633                 if (checkName(*iter, numBuilder)) {
1634                     cloned = &*iter;
1635                     goto tryNext;
1636                 }
1637             }
1638         }
1639         break;
1640 tryNext: ;
1641         if ("()" == builder.substr(builder.length() - 2)) {
1642             builder = builder.substr(0, builder.length() - 2);
1643         }
1644         if (MarkType::kMethod == markType) {
1645             cloned->fCloned = true;
1646         }
1647         fCloned = true;
1648         numBuilder = builder + '_' + to_string(number);
1649     } while (++number);
1650     return numBuilder;
1651 }
1652 
validate() const1653 void BmhParser::validate() const {
1654     for (int index = 0; index <= (int) Last_MarkType; ++index) {
1655         SkASSERT(fMaps[index].fMarkType == (MarkType) index);
1656     }
1657     const char* last = "";
1658     for (int index = 0; index <= (int) Last_MarkType; ++index) {
1659         const char* next = fMaps[index].fName;
1660         if (!last[0]) {
1661             last = next;
1662             continue;
1663         }
1664         if (!next[0]) {
1665             continue;
1666         }
1667         SkASSERT(strcmp(last, next) < 0);
1668         last = next;
1669     }
1670 }
1671 
word(const string & prefix,const string & delimiter)1672 string BmhParser::word(const string& prefix, const string& delimiter) {
1673     string builder(prefix);
1674     this->skipWhiteSpace();
1675     const char* lineEnd = fLine + this->lineLength();
1676     const char* nameStart = fChar;
1677     while (fChar < lineEnd) {
1678         char ch = this->next();
1679         if (' ' >= ch) {
1680             break;
1681         }
1682         if (',' == ch) {
1683             return this->reportError<string>("no comma needed");
1684             break;
1685         }
1686         if (fMC == ch) {
1687             return builder;
1688         }
1689         if (!isalnum(ch) && '_' != ch && ':' != ch && '-' != ch) {
1690             return this->reportError<string>("unexpected char");
1691         }
1692         if (':' == ch) {
1693             // expect pair, and expect word to start with Sk
1694             if (nameStart[0] != 'S' || nameStart[1] != 'k') {
1695                 return this->reportError<string>("expected Sk");
1696             }
1697             if (':' != this->peek()) {
1698                 return this->reportError<string>("expected ::");
1699             }
1700             this->next();
1701         } else if ('-' == ch) {
1702             // expect word not to start with Sk or kX where X is A-Z
1703             if (nameStart[0] == 'k' && nameStart[1] >= 'A' && nameStart[1] <= 'Z') {
1704                 return this->reportError<string>("didn't expected kX");
1705             }
1706             if (nameStart[0] == 'S' && nameStart[1] == 'k') {
1707                 return this->reportError<string>("expected Sk");
1708             }
1709         }
1710     }
1711     if (prefix.size()) {
1712         builder += delimiter;
1713     }
1714     builder.append(nameStart, fChar - nameStart - 1);
1715     return builder;
1716 }
1717 
1718 // pass one: parse text, collect definitions
1719 // pass two: lookup references
1720 
count_children(const Definition & def,MarkType markType)1721 static int count_children(const Definition& def, MarkType markType) {
1722     int count = 0;
1723     if (markType == def.fMarkType) {
1724         ++count;
1725     }
1726     for (auto& child : def.fChildren ) {
1727         count += count_children(*child, markType);
1728     }
1729     return count;
1730 }
1731 
main(int argc,char ** const argv)1732 int main(int argc, char** const argv) {
1733     BmhParser bmhParser(FLAGS_skip);
1734     bmhParser.validate();
1735 
1736     SkCommandLineFlags::SetUsage(
1737         "Common Usage: bookmaker -b path/to/bmh_files -i path/to/include.h -t\n"
1738         "              bookmaker -b path/to/bmh_files -e fiddle.json\n"
1739         "              ~/go/bin/fiddlecli --input fiddle.json --output fiddleout.json\n"
1740         "              bookmaker -b path/to/bmh_files -f fiddleout.json -r path/to/md_files\n"
1741         "              bookmaker -a path/to/status.json -x\n"
1742         "              bookmaker -a path/to/status.json -p\n");
1743     bool help = false;
1744     for (int i = 1; i < argc; i++) {
1745         if (0 == strcmp("-h", argv[i]) || 0 == strcmp("--help", argv[i])) {
1746             help = true;
1747             for (int j = i + 1; j < argc; j++) {
1748                 if (SkStrStartsWith(argv[j], '-')) {
1749                     break;
1750                 }
1751                 help = false;
1752             }
1753             break;
1754         }
1755     }
1756     if (!help) {
1757         SkCommandLineFlags::Parse(argc, argv);
1758     } else {
1759         SkCommandLineFlags::PrintUsage();
1760         const char* const commands[] = { "", "-h", "bmh", "-h", "examples", "-h", "include",
1761             "-h", "fiddle", "-h", "ref", "-h", "status", "-h", "tokens",
1762             "-h", "crosscheck", "-h", "populate", "-h", "spellcheck" };
1763         SkCommandLineFlags::Parse(SK_ARRAY_COUNT(commands), commands);
1764         return 0;
1765     }
1766     if (FLAGS_bmh.isEmpty() && FLAGS_include.isEmpty() && FLAGS_status.isEmpty()) {
1767         SkDebugf("requires at least one of: -b -i -a\n");
1768         SkCommandLineFlags::PrintUsage();
1769         return 1;
1770     }
1771     if (!FLAGS_bmh.isEmpty() && !FLAGS_status.isEmpty()) {
1772         SkDebugf("requires -b or -a but not both\n");
1773         SkCommandLineFlags::PrintUsage();
1774         return 1;
1775     }
1776     if (!FLAGS_include.isEmpty() && !FLAGS_status.isEmpty()) {
1777         SkDebugf("requires -i or -a but not both\n");
1778         SkCommandLineFlags::PrintUsage();
1779         return 1;
1780     }
1781     if (FLAGS_bmh.isEmpty() && FLAGS_status.isEmpty() && FLAGS_catalog) {
1782          SkDebugf("-c requires -b or -a\n");
1783         SkCommandLineFlags::PrintUsage();
1784         return 1;
1785     }
1786     if ((FLAGS_fiddle.isEmpty() || FLAGS_ref.isEmpty()) && FLAGS_catalog) {
1787         SkDebugf("-c requires -f -r\n");
1788         SkCommandLineFlags::PrintUsage();
1789         return 1;
1790     }
1791     if (FLAGS_bmh.isEmpty() && FLAGS_status.isEmpty() && !FLAGS_examples.isEmpty()) {
1792         SkDebugf("-e requires -b or -a\n");
1793         SkCommandLineFlags::PrintUsage();
1794         return 1;
1795     }
1796     if ((FLAGS_include.isEmpty() || FLAGS_bmh.isEmpty()) && FLAGS_status.isEmpty() &&
1797             FLAGS_populate) {
1798         SkDebugf("-p requires -b -i or -a\n");
1799         SkCommandLineFlags::PrintUsage();
1800         return 1;
1801     }
1802     if (FLAGS_bmh.isEmpty() && FLAGS_status.isEmpty() && !FLAGS_ref.isEmpty()) {
1803         SkDebugf("-r requires -b or -a\n");
1804         SkCommandLineFlags::PrintUsage();
1805         return 1;
1806     }
1807     if (FLAGS_bmh.isEmpty() && FLAGS_status.isEmpty() && !FLAGS_spellcheck.isEmpty()) {
1808         SkDebugf("-s requires -b or -a\n");
1809         SkCommandLineFlags::PrintUsage();
1810         return 1;
1811     }
1812     if ((FLAGS_include.isEmpty() || FLAGS_bmh.isEmpty()) && FLAGS_tokens) {
1813         SkDebugf("-t requires -b -i\n");
1814         SkCommandLineFlags::PrintUsage();
1815         return 1;
1816     }
1817     if ((FLAGS_include.isEmpty() || FLAGS_bmh.isEmpty()) && FLAGS_status.isEmpty() &&
1818             FLAGS_crosscheck) {
1819         SkDebugf("-x requires -b -i or -a\n");
1820         SkCommandLineFlags::PrintUsage();
1821         return 1;
1822     }
1823     bmhParser.reset();
1824     if (!FLAGS_bmh.isEmpty()) {
1825         if (FLAGS_tokens)  {
1826             IncludeParser::RemoveFile(FLAGS_bmh[0], FLAGS_include[0]);
1827         }
1828         if (!bmhParser.parseFile(FLAGS_bmh[0], ".bmh")) {
1829             return -1;
1830         }
1831     } else if (!FLAGS_status.isEmpty()) {
1832         if (!bmhParser.parseStatus(FLAGS_status[0], ".bmh", StatusFilter::kInProgress)) {
1833             return -1;
1834         }
1835     }
1836     if (FLAGS_hack) {
1837         if (FLAGS_bmh.isEmpty()) {
1838             SkDebugf("-k or --hack requires -b\n");
1839             SkCommandLineFlags::PrintUsage();
1840             return 1;
1841         }
1842         HackParser hacker(bmhParser);
1843         if (!hacker.parseFile(FLAGS_bmh[0], ".bmh")) {
1844             SkDebugf("hack failed\n");
1845             return -1;
1846         }
1847         return 0;
1848     }
1849     if (FLAGS_selfcheck && !SelfCheck(bmhParser)) {
1850         return -1;
1851     }
1852     bool done = false;
1853     if (!FLAGS_include.isEmpty() && FLAGS_tokens) {
1854         IncludeParser includeParser;
1855         includeParser.validate();
1856         if (!includeParser.parseFile(FLAGS_include[0], ".h")) {
1857             return -1;
1858         }
1859         if (FLAGS_tokens) {
1860             includeParser.fDebugOut = FLAGS_stdout;
1861             if (includeParser.dumpTokens(FLAGS_bmh[0])) {
1862                 bmhParser.fWroteOut = true;
1863             }
1864             done = true;
1865         }
1866     } else if (!FLAGS_include.isEmpty() || !FLAGS_status.isEmpty()) {
1867         if (FLAGS_crosscheck) {
1868             IncludeParser includeParser;
1869             includeParser.validate();
1870             if (!FLAGS_include.isEmpty() &&
1871                     !includeParser.parseFile(FLAGS_include[0], ".h")) {
1872                 return -1;
1873             }
1874             if (!FLAGS_status.isEmpty() && !includeParser.parseStatus(FLAGS_status[0], ".h",
1875                     StatusFilter::kCompleted)) {
1876                 return -1;
1877             }
1878             if (!includeParser.crossCheck(bmhParser)) {
1879                 return -1;
1880             }
1881             done = true;
1882         } else if (FLAGS_populate) {
1883             IncludeWriter includeWriter;
1884             includeWriter.validate();
1885             if (!FLAGS_include.isEmpty() &&
1886                     !includeWriter.parseFile(FLAGS_include[0], ".h")) {
1887                 return -1;
1888             }
1889             if (!FLAGS_status.isEmpty() && !includeWriter.parseStatus(FLAGS_status[0], ".h",
1890                     StatusFilter::kCompleted)) {
1891                 return -1;
1892             }
1893             includeWriter.fDebugOut = FLAGS_stdout;
1894             if (!includeWriter.populate(bmhParser)) {
1895                 return -1;
1896             }
1897             bmhParser.fWroteOut = true;
1898             done = true;
1899         }
1900     }
1901     if (!done && !FLAGS_fiddle.isEmpty() && FLAGS_examples.isEmpty()) {
1902         FiddleParser fparser(&bmhParser);
1903         if (!fparser.parseFile(FLAGS_fiddle[0], ".txt")) {
1904             return -1;
1905         }
1906     }
1907     if (!done && FLAGS_catalog && FLAGS_examples.isEmpty()) {
1908         Catalog cparser(&bmhParser);
1909         cparser.fDebugOut = FLAGS_stdout;
1910         if (!FLAGS_bmh.isEmpty() && !cparser.openCatalog(FLAGS_bmh[0], FLAGS_ref[0])) {
1911             return -1;
1912         }
1913         if (!FLAGS_status.isEmpty() && !cparser.openStatus(FLAGS_status[0], FLAGS_ref[0])) {
1914             return -1;
1915         }
1916         if (!cparser.parseFile(FLAGS_fiddle[0], ".txt")) {
1917             return -1;
1918         }
1919         if (!cparser.closeCatalog()) {
1920             return -1;
1921         }
1922         bmhParser.fWroteOut = true;
1923         done = true;
1924     }
1925     if (!done && !FLAGS_ref.isEmpty() && FLAGS_examples.isEmpty()) {
1926         MdOut mdOut(bmhParser);
1927         mdOut.fDebugOut = FLAGS_stdout;
1928         if (!FLAGS_bmh.isEmpty() && mdOut.buildReferences(FLAGS_bmh[0], FLAGS_ref[0])) {
1929             bmhParser.fWroteOut = true;
1930         }
1931         if (!FLAGS_status.isEmpty() && mdOut.buildStatus(FLAGS_status[0], FLAGS_ref[0])) {
1932             bmhParser.fWroteOut = true;
1933         }
1934     }
1935     if (!done && !FLAGS_spellcheck.isEmpty() && FLAGS_examples.isEmpty()) {
1936         if (!FLAGS_bmh.isEmpty()) {
1937             bmhParser.spellCheck(FLAGS_bmh[0], FLAGS_spellcheck);
1938         }
1939         if (!FLAGS_status.isEmpty()) {
1940             bmhParser.spellStatus(FLAGS_status[0], FLAGS_spellcheck);
1941         }
1942         bmhParser.fWroteOut = true;
1943         done = true;
1944     }
1945     int examples = 0;
1946     int methods = 0;
1947     int topics = 0;
1948     if (!done && !FLAGS_examples.isEmpty()) {
1949         // check to see if examples have duplicate names
1950         if (!bmhParser.checkExamples()) {
1951             return -1;
1952         }
1953         bmhParser.fDebugOut = FLAGS_stdout;
1954         if (!bmhParser.dumpExamples(FLAGS_examples[0])) {
1955             return -1;
1956         }
1957         return 0;
1958     }
1959     if (!bmhParser.fWroteOut) {
1960         for (const auto& topic : bmhParser.fTopicMap) {
1961             if (topic.second->fParent) {
1962                 continue;
1963             }
1964             examples += count_children(*topic.second, MarkType::kExample);
1965             methods += count_children(*topic.second, MarkType::kMethod);
1966             topics += count_children(*topic.second, MarkType::kSubtopic);
1967             topics += count_children(*topic.second, MarkType::kTopic);
1968         }
1969         SkDebugf("topics=%d classes=%d methods=%d examples=%d\n",
1970                 bmhParser.fTopicMap.size(), bmhParser.fClassMap.size(),
1971                 methods, examples);
1972     }
1973     return 0;
1974 }
1975