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
14DEFINE_string2(status, a, "", "File containing status of documentation. (Use in place of -b -i)");
15DEFINE_string2(bmh, b, "", "Path to a *.bmh file or a directory.");
16DEFINE_bool2(catalog, c, false, "Write example catalog.htm. (Requires -b -f -r)");
17DEFINE_string2(examples, e, "", "File of fiddlecli input, usually fiddle.json (For now, disables -r -f -s)");
18DEFINE_string2(fiddle, f, "", "File of fiddlecli output, usually fiddleout.json.");
19DEFINE_bool2(hack, H, false, "Do a find/replace hack to update all *.bmh files. (Requires -b)");
20// h is reserved for help
21DEFINE_string2(include, i, "", "Path to a *.h file or a directory.");
22DEFINE_bool2(selfcheck, k, false, "Check bmh against itself. (Requires -b)");
23DEFINE_bool2(stdout, o, false, "Write file out to standard out.");
24DEFINE_bool2(populate, p, false, "Populate include from bmh. (Requires -b -i)");
25// q is reserved for quiet
26DEFINE_string2(ref, r, "", "Resolve refs and write *.md files to path. (Requires -b -f)");
27DEFINE_string2(spellcheck, s, "", "Spell-check [once, all, mispelling]. (Requires -b)");
28DEFINE_bool2(tokens, t, false, "Write bmh from include. (Requires -b -i)");
29DEFINE_bool2(crosscheck, x, false, "Check bmh against includes. (Requires -b -i)");
30// v is reserved for verbose
31DEFINE_bool2(skip, z, false, "Skip degenerate missed in legacy preprocessor.");
32
33/*  recipe for generating timestamps for existing doxygen comments
34find include/core -type f -name '*.h' -print -exec git blame {} \; > ~/all.blame.txt
35
36todos:
37add new markup to associate typedef SaveLayerFlags with Enum so that, for
38      documentation purposes, this enum is named rather than anonymous
39check column 1 of subtopic tables to see that they start lowercase and don't have a trailing period
40space table better for Constants
41should Return be on same line as 'Return Value'?
42remove anonymous header, e.g. Enum SkPaint::::anonymous_2
43#Member lost all formatting
44#List needs '# content ##', formatting
45consts like enum members need fully qualfied refs to make a valid link
46enum comments should be disallowed unless after #Enum and before first #Const
47    ... or, should look for enum comments in other places
48trouble with aliases, plurals
49    need to keep first letter of includeWriter @param / @return lowercase
50    Quad -> quad, Quads -> quads
51deprecated methods should be sorted down in md out, and show include "Deprecated." text body.
52see 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
63bool 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
472void 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
483static 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
496Definition* 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
509static 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
518bool 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
551bool 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
580bool 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
595bool 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
617string 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
665bool 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
703static 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
729bool 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
749int 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
758bool 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
773bool 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
924MarkType 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    }
941fail:
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
951bool 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
997string 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
1036void 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
1151void 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
1173bool 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
1183string 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
1197string 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
1304const 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
1319bool 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
1341TextParser::TextParser(const Definition* definition) :
1342    TextParser(definition->fFileName, definition->fContentStart, definition->fContentEnd,
1343        definition->fLineCount) {
1344}
1345
1346string 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
1360void TextParser::reportError(const char* errorStr) const {
1361    this->reportWarning(errorStr);
1362    SkDebugf("");  // convenient place to set a breakpoint
1363}
1364
1365void 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
1388string 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
1453bool 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
1470bool 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
1514bool 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
1526vector<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
1553vector<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
1657string 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
1678string 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;
1703tryNext: ;
1704    } while (++number);
1705    return numBuilder;
1706}
1707
1708string 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;
1743tryNext: ;
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
1762void 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
1781string 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
1830static 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
1841int 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