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