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