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