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
addDefinition(const char * defStart,bool hasEnd,MarkType markType,const vector<string> & typeNameBuilder,HasTag hasTag)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
reportDuplicates(const Definition & def,const string & dup) const472 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
find_fiddle(Definition * def,string name)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
findExample(string name) const496 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
find_examples(const Definition & def,vector<string> * exampleNames)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
checkEndMarker(MarkType markType,string match) const518 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
checkExamples() const551 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
checkParamReturn(const Definition * definition) const580 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
childOf(MarkType markType) const595 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
className(MarkType markType)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
collectExternals()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
dump_examples(FILE * fiddleOut,const Definition & def,bool * continuation)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
dumpExamples(const char * fiddleJsonFileName) const729 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
endHashCount() const749 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
endTableColumn(const char * end,const char * terminator)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
findDefinitions()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
getMarkType(MarkLookup lookup) const924 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
hackFiles()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
searchTable(const Definition * tableHolder,const Definition * match)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
topicIter(const Definition * topic)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
addOneLiner(const Definition * defTable,const Definition * child,bool hasLine,bool lfAfter)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
hasEndToken() const1173 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
memberName()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
methodName()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
parentSpace() const1304 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
popParentStack(Definition * definition)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
TextParser(const Definition * definition)1341 TextParser::TextParser(const Definition* definition) :
1342 TextParser(definition->fFileName, definition->fContentStart, definition->fContentEnd,
1343 definition->fLineCount) {
1344 }
1345
ReportFilename(string file)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
reportError(const char * errorStr) const1360 void TextParser::reportError(const char* errorStr) const {
1361 this->reportWarning(errorStr);
1362 SkDebugf(""); // convenient place to set a breakpoint
1363 }
1364
reportWarning(const char * errorStr) const1365 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
typedefName()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
skipNoName()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
skipToDefinitionEnd(MarkType markType)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
skipToString()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
topicName()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
typeName(MarkType markType,bool * checkEnd)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
typedefName()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
uniqueName(const string & base,MarkType markType)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
uniqueRootName(const string & base,MarkType markType)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
validate() const1762 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
word(const string & prefix,const string & delimiter)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
count_children(const Definition & def,MarkType markType)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
main(int argc,char ** const argv)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