blob: cbb34a2a4f063e92b95e057bb1d8869ea48731b9 [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();
Cary Clarkf05bdda2017-08-24 12:59:48 -0400433 if (inc.startsWith("if")) {
434 inc.skipToEndBracket("\n");
435 } else if (inc.startsWith("endif")) {
436 inc.skipToEndBracket("\n");
437 } else {
438 SkASSERT(0); // incomplete
439 return false;
440 }
Cary Clark8032b982017-07-28 11:04:54 -0400441 } else {
442 break;
443 }
444 inc.next();
445 } while (true);
446 if (defEof || incEof) {
Cary Clarkf05bdda2017-08-24 12:59:48 -0400447 if (defEof == incEof || (!defEof && ';' == def.peek())) {
448 return true;
449 }
450 return false; // allow setting breakpoint on failure
Cary Clark8032b982017-07-28 11:04:54 -0400451 }
452 char defCh;
453 do {
454 defCh = def.next();
455 char incCh = inc.next();
456 if (' ' >= defCh && ' ' >= incCh) {
457 break;
458 }
459 if (defCh != incCh) {
460 return false;
461 }
462 if (';' == defCh) {
463 return true;
464 }
465 } while (!def.eof() && !inc.eof());
466 } while (true);
467 return false;
468}
469
470string Definition::formatFunction() const {
471 const char* end = fContentStart;
472 while (end > fStart && ' ' >= end[-1]) {
473 --end;
474 }
475 TextParser methodParser(fFileName, fStart, end, fLineCount);
476 methodParser.skipWhiteSpace();
477 SkASSERT(methodParser.startsWith("#Method"));
478 methodParser.skipName("#Method");
479 methodParser.skipSpace();
480 const char* lastStart = methodParser.fChar;
481 const int limit = 80; // todo: allow this to be set by caller or in global or something
482 string methodStr;
483 string name = this->methodName();
484 const char* nameInParser = methodParser.strnstr(name.c_str(), methodParser.fEnd);
485 methodParser.skipTo(nameInParser);
486 const char* lastEnd = methodParser.fChar;
487 const char* paren = methodParser.strnchr('(', methodParser.fEnd);
488 size_t indent;
489 if (paren) {
490 indent = (size_t) (paren - lastStart) + 1;
491 } else {
492 indent = (size_t) (lastEnd - lastStart);
493 }
494 int written = 0;
495 do {
496 const char* nextStart = lastEnd;
497 SkASSERT(written < limit);
498 const char* delimiter = methodParser.anyOf(",)");
499 const char* nextEnd = delimiter ? delimiter : methodParser.fEnd;
500 if (delimiter) {
501 while (nextStart < nextEnd && ' ' >= nextStart[0]) {
502 ++nextStart;
503 }
504 }
505 while (nextEnd > nextStart && ' ' >= nextEnd[-1]) {
506 --nextEnd;
507 }
508 if (delimiter) {
509 nextEnd += 1;
510 delimiter += 1;
511 }
512 if (lastEnd > lastStart) {
513 if (lastStart[0] != ' ') {
514 space_pad(&methodStr);
515 }
516 methodStr += string(lastStart, (size_t) (lastEnd - lastStart));
517 written += (size_t) (lastEnd - lastStart);
518 }
519 if (delimiter) {
520 if (nextEnd - nextStart >= (ptrdiff_t) (limit - written)) {
521 written = indent;
522 methodStr += '\n';
523 methodStr += string(indent, ' ');
524 }
525 methodParser.skipTo(delimiter);
526 }
527 lastStart = nextStart;
528 lastEnd = nextEnd;
529 } while (lastStart < lastEnd);
530 return methodStr;
531}
532
533string Definition::fiddleName() const {
534 string result;
535 size_t start = 0;
536 string parent;
537 const Definition* parentDef = this;
538 while ((parentDef = parentDef->fParent)) {
539 if (MarkType::kClass == parentDef->fMarkType || MarkType::kStruct == parentDef->fMarkType) {
540 parent = parentDef->fFiddle;
541 break;
542 }
543 }
544 if (parent.length() && 0 == fFiddle.compare(0, parent.length(), parent)) {
545 start = parent.length();
546 while (start < fFiddle.length() && '_' == fFiddle[start]) {
547 ++start;
548 }
549 }
550 size_t end = fFiddle.find_first_of('(', start);
551 return fFiddle.substr(start, end - start);
552}
553
554const Definition* Definition::hasChild(MarkType markType) const {
555 for (auto iter : fChildren) {
556 if (markType == iter->fMarkType) {
557 return iter;
558 }
559 }
560 return nullptr;
561}
562
563const Definition* Definition::hasParam(const string& ref) const {
564 SkASSERT(MarkType::kMethod == fMarkType);
565 for (auto iter : fChildren) {
566 if (MarkType::kParam != iter->fMarkType) {
567 continue;
568 }
569 if (iter->fName == ref) {
570 return &*iter;
571 }
572
573 }
574 return nullptr;
575}
576
577bool Definition::methodHasReturn(const string& name, TextParser* methodParser) const {
578 const char* lastStart = methodParser->fChar;
579 const char* nameInParser = methodParser->strnstr(name.c_str(), methodParser->fEnd);
580 methodParser->skipTo(nameInParser);
581 const char* lastEnd = methodParser->fChar;
582 const char* returnEnd = lastEnd;
583 while (returnEnd > lastStart && ' ' == returnEnd[-1]) {
584 --returnEnd;
585 }
586 bool expectReturn = 4 != returnEnd - lastStart || strncmp("void", lastStart, 4);
587 if (MethodType::kNone != fMethodType && !expectReturn) {
588 return methodParser->reportError<bool>("unexpected void");
589 }
590 switch (fMethodType) {
591 case MethodType::kNone:
592 case MethodType::kOperator:
593 // either is fine
594 break;
595 case MethodType::kConstructor:
596 expectReturn = true;
597 break;
598 case MethodType::kDestructor:
599 expectReturn = false;
600 break;
601 }
602 return expectReturn;
603}
604
605string Definition::methodName() const {
606 string result;
607 size_t start = 0;
608 string parent;
609 const Definition* parentDef = this;
610 while ((parentDef = parentDef->fParent)) {
611 if (MarkType::kClass == parentDef->fMarkType || MarkType::kStruct == parentDef->fMarkType) {
612 parent = parentDef->fName;
613 break;
614 }
615 }
616 if (parent.length() && 0 == fName.compare(0, parent.length(), parent)) {
617 start = parent.length();
618 while (start < fName.length() && ':' == fName[start]) {
619 ++start;
620 }
621 }
622 if (fClone) {
623 int lastUnder = fName.rfind('_');
624 return fName.substr(start, (size_t) (lastUnder - start));
625 }
626 size_t end = fName.find_first_of('(', start);
627 if (string::npos == end) {
628 return fName.substr(start);
629 }
630 return fName.substr(start, end - start);
631}
632
633bool Definition::nextMethodParam(TextParser* methodParser, const char** nextEndPtr,
634 string* paramName) const {
635 *nextEndPtr = methodParser->anyOf(",)");
636 const char* nextEnd = *nextEndPtr;
637 if (!nextEnd) {
638 return methodParser->reportError<bool>("#Method function missing close paren");
639 }
640 const char* paramEnd = nextEnd;
641 const char* assign = methodParser->strnstr(" = ", paramEnd);
642 if (assign) {
643 paramEnd = assign;
644 }
645 const char* closeBracket = methodParser->strnstr("]", paramEnd);
646 if (closeBracket) {
647 const char* openBracket = methodParser->strnstr("[", paramEnd);
648 if (openBracket && openBracket < closeBracket) {
649 while (openBracket < --closeBracket && isdigit(closeBracket[0]))
650 ;
651 if (openBracket == closeBracket) {
652 paramEnd = openBracket;
653 }
654 }
655 }
656 while (paramEnd > methodParser->fChar && ' ' == paramEnd[-1]) {
657 --paramEnd;
658 }
659 const char* paramStart = paramEnd;
660 while (paramStart > methodParser->fChar && isalnum(paramStart[-1])) {
661 --paramStart;
662 }
663 if (paramStart > methodParser->fChar && paramStart >= paramEnd) {
664 return methodParser->reportError<bool>("#Method missing param name");
665 }
666 *paramName = string(paramStart, paramEnd - paramStart);
667 if (!paramName->length()) {
668 if (')' != nextEnd[0]) {
669 return methodParser->reportError<bool>("#Method malformed param");
670 }
671 return false;
672 }
673 return true;
674}
675
676 bool ParserCommon::parseFile(const char* fileOrPath, const char* suffix) {
677 if (!sk_isdir(fileOrPath)) {
678 if (!this->parseFromFile(fileOrPath)) {
679 SkDebugf("failed to parse %s\n", fileOrPath);
680 return false;
681 }
682 } else {
683 SkOSFile::Iter it(fileOrPath, suffix);
684 for (SkString file; it.next(&file); ) {
685 SkString p = SkOSPath::Join(fileOrPath, file.c_str());
686 const char* hunk = p.c_str();
687 if (!SkStrEndsWith(hunk, suffix)) {
688 continue;
689 }
690 if (!this->parseFromFile(hunk)) {
691 SkDebugf("failed to parse %s\n", hunk);
692 return false;
693 }
694 }
695 }
696 return true;
697}
698
699bool Definition::paramsMatch(const string& match, const string& name) const {
700 TextParser def(fFileName, fStart, fContentStart, fLineCount);
701 const char* dName = def.strnstr(name.c_str(), fContentStart);
702 if (!dName) {
703 return false;
704 }
705 def.skipTo(dName);
706 TextParser m(fFileName, &match.front(), &match.back() + 1, fLineCount);
707 const char* mName = m.strnstr(name.c_str(), m.fEnd);
708 if (!mName) {
709 return false;
710 }
711 m.skipTo(mName);
712 while (!def.eof() && ')' != def.peek() && !m.eof() && ')' != m.peek()) {
713 const char* ds = def.fChar;
714 const char* ms = m.fChar;
715 const char* de = def.anyOf(") \n");
716 const char* me = m.anyOf(") \n");
717 def.skipTo(de);
718 m.skipTo(me);
719 if (def.fChar - ds != m.fChar - ms) {
720 return false;
721 }
722 if (strncmp(ds, ms, (int) (def.fChar - ds))) {
723 return false;
724 }
725 def.skipWhiteSpace();
726 m.skipWhiteSpace();
727 }
728 return !def.eof() && ')' == def.peek() && !m.eof() && ')' == m.peek();
729}
730
731void RootDefinition::clearVisited() {
732 fVisited = false;
733 for (auto& leaf : fLeaves) {
734 leaf.second.fVisited = false;
735 }
736 for (auto& branch : fBranches) {
737 branch.second->clearVisited();
738 }
739}
740
741bool RootDefinition::dumpUnVisited() {
742 bool allStructElementsFound = true;
743 for (auto& leaf : fLeaves) {
744 if (!leaf.second.fVisited) {
745 // TODO: parse embedded struct in includeParser phase, then remove this condition
746 size_t firstColon = leaf.first.find("::");
747 size_t lastColon = leaf.first.rfind("::");
748 if (firstColon != lastColon) { // struct, two sets
749 allStructElementsFound = false;
750 continue;
751 }
752 SkDebugf("defined in bmh but missing in include: %s\n", leaf.first.c_str());
753 }
754 }
755 for (auto& branch : fBranches) {
756 allStructElementsFound &= branch.second->dumpUnVisited();
757 }
758 return allStructElementsFound;
759}
760
761const Definition* RootDefinition::find(const string& ref) const {
762 const auto leafIter = fLeaves.find(ref);
763 if (leafIter != fLeaves.end()) {
764 return &leafIter->second;
765 }
766 const auto branchIter = fBranches.find(ref);
767 if (branchIter != fBranches.end()) {
768 const RootDefinition* rootDef = branchIter->second;
769 return rootDef;
770 }
771 const Definition* result = nullptr;
772 for (const auto& branch : fBranches) {
773 const RootDefinition* rootDef = branch.second;
774 result = rootDef->find(ref);
775 if (result) {
776 break;
777 }
778 }
779 return result;
780}
781
782/*
783 class contains named struct, enum, enum-member, method, topic, subtopic
784 everything contained by class is uniquely named
785 contained names may be reused by other classes
786 method contains named parameters
787 parameters may be reused in other methods
788 */
789
790bool BmhParser::addDefinition(const char* defStart, bool hasEnd, MarkType markType,
791 const vector<string>& typeNameBuilder) {
792 Definition* definition = nullptr;
793 switch (markType) {
794 case MarkType::kComment:
795 if (!this->skipToDefinitionEnd(markType)) {
796 return false;
797 }
798 return true;
799 // these types may be referred to by name
800 case MarkType::kClass:
801 case MarkType::kStruct:
802 case MarkType::kConst:
803 case MarkType::kEnum:
804 case MarkType::kEnumClass:
805 case MarkType::kMember:
806 case MarkType::kMethod:
807 case MarkType::kTypedef: {
808 if (!typeNameBuilder.size()) {
809 return this->reportError<bool>("unnamed markup");
810 }
811 if (typeNameBuilder.size() > 1) {
812 return this->reportError<bool>("expected one name only");
813 }
814 const string& name = typeNameBuilder[0];
815 if (nullptr == fRoot) {
816 fRoot = this->findBmhObject(markType, name);
817 fRoot->fFileName = fFileName;
818 definition = fRoot;
819 } else {
820 if (nullptr == fParent) {
821 return this->reportError<bool>("expected parent");
822 }
823 if (fParent == fRoot && hasEnd) {
824 RootDefinition* rootParent = fRoot->rootParent();
825 if (rootParent) {
826 fRoot = rootParent;
827 }
828 definition = fParent;
829 } else {
830 if (!hasEnd && fRoot->find(name)) {
831 return this->reportError<bool>("duplicate symbol");
832 }
833 if (MarkType::kStruct == markType || MarkType::kClass == markType) {
834 // if class or struct, build fRoot hierarchy
835 // and change isDefined to search all parents of fRoot
836 SkASSERT(!hasEnd);
837 RootDefinition* childRoot = new RootDefinition;
838 (fRoot->fBranches)[name] = childRoot;
839 childRoot->setRootParent(fRoot);
840 childRoot->fFileName = fFileName;
841 fRoot = childRoot;
842 definition = fRoot;
843 } else {
844 definition = &fRoot->fLeaves[name];
845 }
846 }
847 }
848 if (hasEnd) {
849 Exemplary hasExample = Exemplary::kNo;
850 bool hasExcluder = false;
851 for (auto child : definition->fChildren) {
852 if (MarkType::kExample == child->fMarkType) {
853 hasExample = Exemplary::kYes;
854 }
855 hasExcluder |= MarkType::kPrivate == child->fMarkType
856 || MarkType::kDeprecated == child->fMarkType
857 || MarkType::kExperimental == child->fMarkType
858 || MarkType::kNoExample == child->fMarkType;
859 }
860 if (fMaps[(int) markType].fExemplary != hasExample
861 && fMaps[(int) markType].fExemplary != Exemplary::kOptional) {
862 if (string::npos == fFileName.find("undocumented")
863 && !hasExcluder) {
864 hasExample == Exemplary::kNo ?
865 this->reportWarning("missing example") :
866 this->reportWarning("unexpected example");
867 }
868
869 }
870 if (MarkType::kMethod == markType) {
871 if (fCheckMethods && !definition->checkMethod()) {
872 return false;
873 }
874 }
875 if (!this->popParentStack(definition)) {
876 return false;
877 }
878 } else {
879 definition->fStart = defStart;
880 this->skipSpace();
881 definition->fFileName = fFileName;
882 definition->fContentStart = fChar;
883 definition->fLineCount = fLineCount;
884 definition->fClone = fCloned;
885 if (MarkType::kConst == markType) {
886 // todo: require that fChar points to def on same line as markup
887 // additionally add definition to class children if it is not already there
888 if (definition->fParent != fRoot) {
889// fRoot->fChildren.push_back(definition);
890 }
891 }
892 definition->fName = name;
893 if (MarkType::kMethod == markType) {
894 if (string::npos != name.find(':', 0)) {
895 definition->setCanonicalFiddle();
896 } else {
897 definition->fFiddle = name;
898 }
899 } else {
900 definition->fFiddle = normalized_name(name);
901 }
902 definition->fMarkType = markType;
903 this->setAsParent(definition);
904 }
905 } break;
906 case MarkType::kTopic:
907 case MarkType::kSubtopic:
908 SkASSERT(1 == typeNameBuilder.size());
909 if (!hasEnd) {
910 if (!typeNameBuilder.size()) {
911 return this->reportError<bool>("unnamed topic");
912 }
913 fTopics.emplace_front(markType, defStart, fLineCount, fParent);
914 RootDefinition* rootDefinition = &fTopics.front();
915 definition = rootDefinition;
916 definition->fFileName = fFileName;
917 definition->fContentStart = fChar;
918 definition->fName = typeNameBuilder[0];
919 Definition* parent = fParent;
920 while (parent && MarkType::kTopic != parent->fMarkType
921 && MarkType::kSubtopic != parent->fMarkType) {
922 parent = parent->fParent;
923 }
924 definition->fFiddle = parent ? parent->fFiddle + '_' : "";
925 definition->fFiddle += normalized_name(typeNameBuilder[0]);
926 this->setAsParent(definition);
927 }
928 {
929 const string& fullTopic = hasEnd ? fParent->fFiddle : definition->fFiddle;
930 Definition* defPtr = fTopicMap[fullTopic];
931 if (hasEnd) {
932 if (!definition) {
933 definition = defPtr;
934 } else if (definition != defPtr) {
935 return this->reportError<bool>("mismatched topic");
936 }
937 } else {
938 if (nullptr != defPtr) {
939 return this->reportError<bool>("already declared topic");
940 }
941 fTopicMap[fullTopic] = definition;
942 }
943 }
944 if (hasEnd) {
945 if (!this->popParentStack(definition)) {
946 return false;
947 }
948 }
949 break;
950 // these types are children of parents, but are not in named maps
951 case MarkType::kDefinedBy: {
952 string prefixed(fRoot->fName);
953 const char* start = fChar;
954 string name(start, this->trimmedBracketEnd(fMC, OneLine::kYes) - start);
955 prefixed += "::" + name;
956 this->skipToEndBracket(fMC);
957 const auto leafIter = fRoot->fLeaves.find(prefixed);
958 if (fRoot->fLeaves.end() != leafIter) {
959 this->reportError<bool>("DefinedBy already defined");
960 }
961 definition = &fRoot->fLeaves[prefixed];
962 definition->fParent = fParent;
963 definition->fStart = defStart;
964 definition->fContentStart = start;
965 definition->fName = name;
966 definition->fFiddle = normalized_name(name);
967 definition->fContentEnd = fChar;
968 this->skipToEndBracket('\n');
969 definition->fTerminator = fChar;
970 definition->fMarkType = markType;
971 definition->fLineCount = fLineCount;
972 fParent->fChildren.push_back(definition);
973 } break;
974 case MarkType::kDescription:
975 case MarkType::kStdOut:
976 // may be one-liner
977 case MarkType::kBug:
978 case MarkType::kNoExample:
979 case MarkType::kParam:
980 case MarkType::kReturn:
981 case MarkType::kToDo:
982 if (hasEnd) {
983 if (markType == fParent->fMarkType) {
984 definition = fParent;
985 if (MarkType::kBug == markType || MarkType::kReturn == markType
986 || MarkType::kToDo == markType) {
987 this->skipNoName();
988 }
989 if (!this->popParentStack(fParent)) { // if not one liner, pop
990 return false;
991 }
Cary Clark579985c2017-07-31 11:48:27 -0400992 if (MarkType::kParam == markType || MarkType::kReturn == markType) {
993 const char* parmEndCheck = definition->fContentEnd;
994 while (parmEndCheck < definition->fTerminator) {
995 if (fMC == parmEndCheck[0]) {
996 break;
997 }
998 if (' ' < parmEndCheck[0]) {
999 this->reportError<bool>(
1000 "use full end marker on multiline #Param and #Return");
1001 }
1002 ++parmEndCheck;
1003 }
1004 }
Cary Clark8032b982017-07-28 11:04:54 -04001005 } else {
1006 fMarkup.emplace_front(markType, defStart, fLineCount, fParent);
1007 definition = &fMarkup.front();
1008 definition->fName = typeNameBuilder[0];
1009 definition->fFiddle = normalized_name(typeNameBuilder[0]);
1010 definition->fContentStart = fChar;
1011 definition->fContentEnd = this->trimmedBracketEnd(fMC, OneLine::kYes);
1012 this->skipToEndBracket(fMC);
1013 SkAssertResult(fMC == this->next());
1014 SkAssertResult(fMC == this->next());
1015 definition->fTerminator = fChar;
1016 fParent->fChildren.push_back(definition);
1017 }
1018 break;
1019 }
1020 // not one-liners
1021 case MarkType::kCode:
1022 case MarkType::kDeprecated:
1023 case MarkType::kExample:
1024 case MarkType::kExperimental:
1025 case MarkType::kFormula:
1026 case MarkType::kFunction:
1027 case MarkType::kLegend:
1028 case MarkType::kList:
1029 case MarkType::kPrivate:
1030 case MarkType::kTable:
1031 case MarkType::kTrack:
1032 if (hasEnd) {
1033 definition = fParent;
1034 if (markType != fParent->fMarkType) {
1035 return this->reportError<bool>("end element mismatch");
1036 } else if (!this->popParentStack(fParent)) {
1037 return false;
1038 }
1039 if (MarkType::kExample == markType) {
1040 if (definition->fChildren.size() == 0) {
1041 TextParser emptyCheck(definition);
1042 if (emptyCheck.eof() || !emptyCheck.skipWhiteSpace()) {
1043 return this->reportError<bool>("missing example body");
1044 }
1045 }
1046 }
1047 } else {
1048 fMarkup.emplace_front(markType, defStart, fLineCount, fParent);
1049 definition = &fMarkup.front();
1050 definition->fContentStart = fChar;
1051 definition->fName = typeNameBuilder[0];
1052 definition->fFiddle = fParent->fFiddle;
1053 char suffix = '\0';
1054 bool tryAgain;
1055 do {
1056 tryAgain = false;
1057 for (const auto& child : fParent->fChildren) {
1058 if (child->fFiddle == definition->fFiddle) {
1059 if (MarkType::kExample != child->fMarkType) {
1060 continue;
1061 }
1062 if ('\0' == suffix) {
1063 suffix = 'a';
1064 } else if (++suffix > 'z') {
1065 return reportError<bool>("too many examples");
1066 }
1067 definition->fFiddle = fParent->fFiddle + '_';
1068 definition->fFiddle += suffix;
1069 tryAgain = true;
1070 break;
1071 }
1072 }
1073 } while (tryAgain);
1074 this->setAsParent(definition);
1075 }
1076 break;
1077 // always treated as one-liners (can't detect misuse easily)
1078 case MarkType::kAlias:
1079 case MarkType::kAnchor:
1080 case MarkType::kDefine:
1081 case MarkType::kError:
1082 case MarkType::kFile:
1083 case MarkType::kHeight:
1084 case MarkType::kImage:
1085 case MarkType::kPlatform:
1086 case MarkType::kSeeAlso:
1087 case MarkType::kSubstitute:
1088 case MarkType::kTime:
1089 case MarkType::kVolatile:
1090 case MarkType::kWidth:
1091 if (hasEnd) {
1092 return this->reportError<bool>("one liners omit end element");
1093 }
1094 fMarkup.emplace_front(markType, defStart, fLineCount, fParent);
1095 definition = &fMarkup.front();
1096 definition->fName = typeNameBuilder[0];
1097 definition->fFiddle = normalized_name(typeNameBuilder[0]);
1098 definition->fContentStart = fChar;
1099 definition->fContentEnd = this->trimmedBracketEnd('\n', OneLine::kYes);
1100 definition->fTerminator = this->lineEnd() - 1;
1101 fParent->fChildren.push_back(definition);
1102 if (MarkType::kAnchor == markType) {
1103 this->skipToEndBracket(fMC);
1104 fMarkup.emplace_front(MarkType::kLink, fChar, fLineCount, definition);
1105 SkAssertResult(fMC == this->next());
1106 this->skipWhiteSpace();
1107 Definition* link = &fMarkup.front();
1108 link->fContentStart = fChar;
1109 link->fContentEnd = this->trimmedBracketEnd(fMC, OneLine::kYes);
1110 this->skipToEndBracket(fMC);
1111 SkAssertResult(fMC == this->next());
1112 SkAssertResult(fMC == this->next());
1113 link->fTerminator = fChar;
1114 definition->fContentEnd = link->fContentEnd;
1115 definition->fTerminator = fChar;
1116 definition->fChildren.emplace_back(link);
1117 } else if (MarkType::kAlias == markType) {
1118 this->skipWhiteSpace();
1119 const char* start = fChar;
1120 this->skipToNonAlphaNum();
1121 string alias(start, fChar - start);
1122 if (fAliasMap.end() != fAliasMap.find(alias)) {
1123 return this->reportError<bool>("duplicate alias");
1124 }
1125 fAliasMap[alias] = definition;
1126 }
1127 break;
1128 case MarkType::kExternal:
1129 (void) this->collectExternals(); // FIXME: detect errors in external defs?
1130 break;
1131 default:
1132 SkASSERT(0); // fixme : don't let any types be invisible
1133 return true;
1134 }
1135 if (fParent) {
1136 SkASSERT(definition);
1137 SkASSERT(definition->fName.length() > 0);
1138 }
1139 return true;
1140}
1141
1142bool BmhParser::childOf(MarkType markType) const {
1143 auto childError = [this](MarkType markType) -> bool {
1144 string errStr = "expected ";
1145 errStr += fMaps[(int) markType].fName;
1146 errStr += " parent";
1147 return this->reportError<bool>(errStr.c_str());
1148 };
1149
1150 if (markType == fParent->fMarkType) {
1151 return true;
1152 }
1153 if (this->hasEndToken()) {
1154 if (!fParent->fParent) {
1155 return this->reportError<bool>("expected grandparent");
1156 }
1157 if (markType == fParent->fParent->fMarkType) {
1158 return true;
1159 }
1160 }
1161 return childError(markType);
1162}
1163
1164string BmhParser::className(MarkType markType) {
1165 string builder;
1166 const Definition* parent = this->parentSpace();
1167 if (parent && (parent != fParent || MarkType::kClass != markType)) {
1168 builder += parent->fName;
1169 }
1170 const char* end = this->lineEnd();
1171 const char* mc = this->strnchr(fMC, end);
1172 if (mc) {
1173 this->skipSpace();
1174 const char* wordStart = fChar;
1175 this->skipToNonAlphaNum();
1176 const char* wordEnd = fChar;
1177 if (mc + 1 < fEnd && fMC == mc[1]) { // if ##
1178 if (markType != fParent->fMarkType) {
1179 return this->reportError<string>("unbalanced method");
1180 }
1181 if (builder.length() > 0 && wordEnd > wordStart) {
1182 if (builder != fParent->fName) {
1183 builder += "::";
1184 builder += string(wordStart, wordEnd - wordStart);
1185 if (builder != fParent->fName) {
1186 return this->reportError<string>("name mismatch");
1187 }
1188 }
1189 }
1190 this->skipLine();
1191 return fParent->fName;
1192 }
1193 fChar = mc;
1194 this->next();
1195 }
1196 this->skipWhiteSpace();
1197 if (MarkType::kEnum == markType && fChar >= end) {
1198 fAnonymous = true;
1199 builder += "::_anonymous";
1200 return uniqueRootName(builder, markType);
1201 }
1202 builder = this->word(builder, "::");
1203 return builder;
1204}
1205
1206bool BmhParser::collectExternals() {
1207 do {
1208 this->skipWhiteSpace();
1209 if (this->eof()) {
1210 break;
1211 }
1212 if (fMC == this->peek()) {
1213 this->next();
1214 if (this->eof()) {
1215 break;
1216 }
1217 if (fMC == this->peek()) {
1218 this->skipLine();
1219 break;
1220 }
1221 if (' ' >= this->peek()) {
1222 this->skipLine();
1223 continue;
1224 }
1225 if (this->startsWith(fMaps[(int) MarkType::kExternal].fName)) {
1226 this->skipToNonAlphaNum();
1227 continue;
1228 }
1229 }
1230 this->skipToAlpha();
1231 const char* wordStart = fChar;
1232 this->skipToNonAlphaNum();
1233 if (fChar - wordStart > 0) {
1234 fExternals.emplace_front(MarkType::kExternal, wordStart, fChar, fLineCount, fParent);
1235 RootDefinition* definition = &fExternals.front();
1236 definition->fFileName = fFileName;
1237 definition->fName = string(wordStart ,fChar - wordStart);
1238 definition->fFiddle = normalized_name(definition->fName);
1239 }
1240 } while (!this->eof());
1241 return true;
1242}
1243
1244int BmhParser::endHashCount() const {
1245 const char* end = fLine + this->lineLength();
1246 int count = 0;
1247 while (fLine < end && fMC == *--end) {
1248 count++;
1249 }
1250 return count;
1251}
1252
1253// FIXME: some examples may produce different output on different platforms
1254// if the text output can be different, think of how to author that
1255
1256bool BmhParser::findDefinitions() {
1257 bool lineStart = true;
1258 fParent = nullptr;
1259 while (!this->eof()) {
1260 if (this->peek() == fMC) {
1261 this->next();
1262 if (this->peek() == fMC) {
1263 this->next();
1264 if (!lineStart && ' ' < this->peek()) {
1265 return this->reportError<bool>("expected definition");
1266 }
1267 if (this->peek() != fMC) {
1268 vector<string> parentName;
1269 parentName.push_back(fParent->fName);
1270 if (!this->addDefinition(fChar - 1, true, fParent->fMarkType, parentName)) {
1271 return false;
1272 }
1273 } else {
1274 SkAssertResult(this->next() == fMC);
1275 fMC = this->next(); // change markup character
1276 if (' ' >= fMC) {
1277 return this->reportError<bool>("illegal markup character");
1278 }
1279 fMarkup.emplace_front(MarkType::kMarkChar, fChar - 1, fLineCount, fParent);
1280 Definition* markChar = &fMarkup.front();
1281 markChar->fContentStart = fChar - 1;
1282 this->skipToEndBracket('\n');
1283 markChar->fContentEnd = fChar;
1284 markChar->fTerminator = fChar;
1285 fParent->fChildren.push_back(markChar);
1286 }
1287 } else if (this->peek() >= 'A' && this->peek() <= 'Z') {
1288 const char* defStart = fChar - 1;
1289 MarkType markType = this->getMarkType(MarkLookup::kRequire);
1290 bool hasEnd = this->hasEndToken();
1291 if (!hasEnd) {
1292 MarkType parentType = fParent ? fParent->fMarkType : MarkType::kRoot;
1293 uint64_t parentMask = fMaps[(int) markType].fParentMask;
1294 if (parentMask && !(parentMask & (1LL << (int) parentType))) {
1295 return this->reportError<bool>("invalid parent");
1296 }
1297 }
1298 if (!this->skipName(fMaps[(int) markType].fName)) {
1299 return this->reportError<bool>("illegal markup character");
1300 }
1301 if (!this->skipSpace()) {
1302 return this->reportError<bool>("unexpected end");
1303 }
1304 bool expectEnd = true;
1305 vector<string> typeNameBuilder = this->typeName(markType, &expectEnd);
1306 if (fCloned && MarkType::kMethod != markType && MarkType::kExample != markType
1307 && !fAnonymous) {
1308 return this->reportError<bool>("duplicate name");
1309 }
1310 if (hasEnd && expectEnd) {
1311 SkASSERT(fMC != this->peek());
1312 }
1313 if (!this->addDefinition(defStart, hasEnd, markType, typeNameBuilder)) {
1314 return false;
1315 }
1316 continue;
1317 } else if (this->peek() == ' ') {
1318 if (!fParent || (MarkType::kTable != fParent->fMarkType
1319 && MarkType::kLegend != fParent->fMarkType
1320 && MarkType::kList != fParent->fMarkType)) {
1321 int endHashes = this->endHashCount();
1322 if (endHashes <= 1) { // one line comment
1323 if (fParent) {
1324 fMarkup.emplace_front(MarkType::kComment, fChar - 1, fLineCount, fParent);
1325 Definition* comment = &fMarkup.front();
1326 comment->fContentStart = fChar - 1;
1327 this->skipToEndBracket('\n');
1328 comment->fContentEnd = fChar;
1329 comment->fTerminator = fChar;
1330 fParent->fChildren.push_back(comment);
1331 } else {
1332 fChar = fLine + this->lineLength() - 1;
1333 }
1334 } else { // table row
1335 if (2 != endHashes) {
1336 string errorStr = "expect ";
1337 errorStr += fMC;
1338 errorStr += fMC;
1339 return this->reportError<bool>(errorStr.c_str());
1340 }
1341 if (!fParent || MarkType::kTable != fParent->fMarkType) {
1342 return this->reportError<bool>("missing table");
1343 }
1344 }
1345 } else {
1346 bool parentIsList = MarkType::kList == fParent->fMarkType;
1347 // fixme? no nested tables for now
1348 const char* colStart = fChar - 1;
1349 fMarkup.emplace_front(MarkType::kRow, colStart, fLineCount, fParent);
1350 Definition* row = &fMarkup.front();
1351 this->skipWhiteSpace();
1352 row->fContentStart = fChar;
1353 this->setAsParent(row);
1354 const char* lineEnd = this->lineEnd();
1355 do {
1356 fMarkup.emplace_front(MarkType::kColumn, colStart, fLineCount, fParent);
1357 Definition* column = &fMarkup.front();
1358 column->fContentStart = fChar;
1359 column->fContentEnd = this->trimmedBracketEnd(fMC,
1360 parentIsList ? OneLine::kNo : OneLine::kYes);
1361 this->skipToEndBracket(fMC);
1362 colStart = fChar;
1363 SkAssertResult(fMC == this->next());
1364 if (fMC == this->peek()) {
1365 this->next();
1366 }
1367 column->fTerminator = fChar;
1368 fParent->fChildren.push_back(column);
1369 this->skipSpace();
1370 } while (fChar < lineEnd && '\n' != this->peek());
1371 if (!this->popParentStack(fParent)) {
1372 return false;
1373 }
1374 const Definition* lastCol = row->fChildren.back();
1375 row->fContentEnd = lastCol->fContentEnd;
1376 }
1377 }
1378 }
1379 lineStart = this->next() == '\n';
1380 }
1381 if (fParent) {
1382 return this->reportError<bool>("mismatched end");
1383 }
1384 return true;
1385}
1386
1387MarkType BmhParser::getMarkType(MarkLookup lookup) const {
1388 for (int index = 0; index <= Last_MarkType; ++index) {
1389 int typeLen = strlen(fMaps[index].fName);
1390 if (typeLen == 0) {
1391 continue;
1392 }
1393 if (fChar + typeLen >= fEnd || fChar[typeLen] > ' ') {
1394 continue;
1395 }
1396 int chCompare = strncmp(fChar, fMaps[index].fName, typeLen);
1397 if (chCompare < 0) {
1398 goto fail;
1399 }
1400 if (chCompare == 0) {
1401 return (MarkType) index;
1402 }
1403 }
1404fail:
1405 if (MarkLookup::kRequire == lookup) {
1406 return this->reportError<MarkType>("unknown mark type");
1407 }
1408 return MarkType::kNone;
1409}
1410
1411bool HackParser::hackFiles() {
1412 string filename(fFileName);
1413 size_t len = filename.length() - 1;
1414 while (len > 0 && (isalnum(filename[len]) || '_' == filename[len] || '.' == filename[len])) {
1415 --len;
1416 }
1417 filename = filename.substr(len + 1);
1418 // remove trailing period from #Param and #Return
1419 FILE* out = fopen(filename.c_str(), "wb");
1420 if (!out) {
1421 SkDebugf("could not open output file %s\n", filename.c_str());
1422 return false;
1423 }
1424 const char* start = fStart;
1425 do {
1426 const char* match = this->strnchr('#', fEnd);
1427 if (!match) {
1428 break;
1429 }
1430 this->skipTo(match);
1431 this->next();
1432 if (!this->startsWith("Param") && !this->startsWith("Return")) {
1433 continue;
1434 }
1435 const char* end = this->strnstr("##", fEnd);
1436 while (true) {
1437 TextParser::Save lastPeriod(this);
1438 this->next();
1439 if (!this->skipToEndBracket('.', end)) {
1440 lastPeriod.restore();
1441 break;
1442 }
1443 }
1444 if ('.' == this->peek()) {
1445 fprintf(out, "%.*s", (int) (fChar - start), start);
1446 this->next();
1447 start = fChar;
1448 }
1449 } while (!this->eof());
1450 fprintf(out, "%.*s", (int) (fEnd - start), start);
1451 fclose(out);
1452 return true;
1453}
1454
1455bool BmhParser::hasEndToken() const {
1456 const char* last = fLine + this->lineLength();
1457 while (last > fLine && ' ' >= *--last)
1458 ;
1459 if (--last < fLine) {
1460 return false;
1461 }
1462 return last[0] == fMC && last[1] == fMC;
1463}
1464
1465string BmhParser::memberName() {
1466 const char* wordStart;
1467 const char* prefixes[] = { "static", "const" };
1468 do {
1469 this->skipSpace();
1470 wordStart = fChar;
1471 this->skipToNonAlphaNum();
1472 } while (this->anyOf(wordStart, prefixes, SK_ARRAY_COUNT(prefixes)));
1473 if ('*' == this->peek()) {
1474 this->next();
1475 }
1476 return this->className(MarkType::kMember);
1477}
1478
1479string BmhParser::methodName() {
1480 if (this->hasEndToken()) {
1481 if (!fParent || !fParent->fName.length()) {
1482 return this->reportError<string>("missing parent method name");
1483 }
1484 SkASSERT(fMC == this->peek());
1485 this->next();
1486 SkASSERT(fMC == this->peek());
1487 this->next();
1488 SkASSERT(fMC != this->peek());
1489 return fParent->fName;
1490 }
1491 string builder;
1492 const char* end = this->lineEnd();
1493 const char* paren = this->strnchr('(', end);
1494 if (!paren) {
1495 return this->reportError<string>("missing method name and reference");
1496 }
1497 const char* nameStart = paren;
1498 char ch;
1499 bool expectOperator = false;
1500 bool isConstructor = false;
1501 const char* nameEnd = nullptr;
1502 while (nameStart > fChar && ' ' != (ch = *--nameStart)) {
1503 if (!isalnum(ch) && '_' != ch) {
1504 if (nameEnd) {
1505 break;
1506 }
1507 expectOperator = true;
1508 continue;
1509 }
1510 if (!nameEnd) {
1511 nameEnd = nameStart + 1;
1512 }
1513 }
1514 if (!nameEnd) {
1515 return this->reportError<string>("unexpected method name char");
1516 }
1517 if (' ' == nameStart[0]) {
1518 ++nameStart;
1519 }
1520 if (nameEnd <= nameStart) {
1521 return this->reportError<string>("missing method name");
1522 }
1523 if (nameStart >= paren) {
1524 return this->reportError<string>("missing method name length");
1525 }
1526 string name(nameStart, nameEnd - nameStart);
1527 bool allLower = true;
1528 for (int index = 0; index < (int) (nameEnd - nameStart); ++index) {
1529 if (!islower(nameStart[index])) {
1530 allLower = false;
1531 break;
1532 }
1533 }
1534 if (expectOperator && "operator" != name) {
1535 return this->reportError<string>("expected operator");
1536 }
1537 const Definition* parent = this->parentSpace();
1538 if (parent && parent->fName.length() > 0) {
1539 if (parent->fName == name) {
1540 isConstructor = true;
1541 } else if ('~' == name[0]) {
1542 if (parent->fName != name.substr(1)) {
1543 return this->reportError<string>("expected destructor");
1544 }
1545 isConstructor = true;
1546 }
1547 builder = parent->fName + "::";
1548 }
1549 if (isConstructor || expectOperator) {
1550 paren = this->strnchr(')', end) + 1;
1551 }
1552 builder.append(nameStart, paren - nameStart);
1553 if (!expectOperator && allLower) {
1554 builder.append("()");
1555 }
1556 int parens = 0;
1557 while (fChar < end || parens > 0) {
1558 if ('(' == this->peek()) {
1559 ++parens;
1560 } else if (')' == this->peek()) {
1561 --parens;
1562 }
1563 this->next();
1564 }
1565 TextParser::Save saveState(this);
1566 this->skipWhiteSpace();
1567 if (this->startsWith("const")) {
1568 this->skipName("const");
1569 } else {
1570 saveState.restore();
1571 }
1572// this->next();
1573 return uniqueRootName(builder, MarkType::kMethod);
1574}
1575
1576const Definition* BmhParser::parentSpace() const {
1577 Definition* parent = nullptr;
1578 Definition* test = fParent;
1579 while (test) {
1580 if (MarkType::kClass == test->fMarkType ||
1581 MarkType::kEnumClass == test->fMarkType ||
1582 MarkType::kStruct == test->fMarkType) {
1583 parent = test;
1584 break;
1585 }
1586 test = test->fParent;
1587 }
1588 return parent;
1589}
1590
1591bool BmhParser::popParentStack(Definition* definition) {
1592 if (!fParent) {
1593 return this->reportError<bool>("missing parent");
1594 }
1595 if (definition != fParent) {
1596 return this->reportError<bool>("definition end is not parent");
1597 }
1598 if (!definition->fStart) {
1599 return this->reportError<bool>("definition missing start");
1600 }
1601 if (definition->fContentEnd) {
1602 return this->reportError<bool>("definition already ended");
1603 }
1604 definition->fContentEnd = fLine - 1;
1605 definition->fTerminator = fChar;
1606 fParent = definition->fParent;
1607 if (!fParent || (MarkType::kTopic == fParent->fMarkType && !fParent->fParent)) {
1608 fRoot = nullptr;
1609 }
1610 return true;
1611}
1612
1613TextParser::TextParser(const Definition* definition) :
1614 TextParser(definition->fFileName, definition->fContentStart, definition->fContentEnd,
1615 definition->fLineCount) {
1616}
1617
1618void TextParser::reportError(const char* errorStr) const {
1619 this->reportWarning(errorStr);
1620 SkDebugf(""); // convenient place to set a breakpoint
1621}
1622
1623void TextParser::reportWarning(const char* errorStr) const {
1624 TextParser err(fFileName, fLine, fEnd, fLineCount);
1625 size_t lineLen = this->lineLength();
1626 ptrdiff_t spaces = fChar - fLine;
1627 while (spaces > 0 && (size_t) spaces > lineLen) {
1628 ++err.fLineCount;
1629 err.fLine += lineLen;
1630 spaces -= lineLen;
1631 lineLen = err.lineLength();
1632 }
Cary Clarkbad5ad72017-08-03 17:14:08 -04001633 SkDebugf("\n%s(%zd): error: %s\n", fFileName.c_str(), err.fLineCount, errorStr);
Cary Clark8032b982017-07-28 11:04:54 -04001634 if (0 == lineLen) {
1635 SkDebugf("[blank line]\n");
1636 } else {
1637 while (lineLen > 0 && '\n' == err.fLine[lineLen - 1]) {
1638 --lineLen;
1639 }
1640 SkDebugf("%.*s\n", (int) lineLen, err.fLine);
1641 SkDebugf("%*s^\n", (int) spaces, "");
1642 }
1643}
1644
1645bool BmhParser::skipNoName() {
1646 if ('\n' == this->peek()) {
1647 this->next();
1648 return true;
1649 }
1650 this->skipWhiteSpace();
1651 if (fMC != this->peek()) {
1652 return this->reportError<bool>("expected end mark");
1653 }
1654 this->next();
1655 if (fMC != this->peek()) {
1656 return this->reportError<bool>("expected end mark");
1657 }
1658 this->next();
1659 return true;
1660}
1661
1662bool BmhParser::skipToDefinitionEnd(MarkType markType) {
1663 if (this->eof()) {
1664 return this->reportError<bool>("missing end");
1665 }
1666 const char* start = fLine;
1667 int startLineCount = fLineCount;
1668 int stack = 1;
1669 ptrdiff_t lineLen;
1670 bool foundEnd = false;
1671 do {
1672 lineLen = this->lineLength();
1673 if (fMC != *fChar++) {
1674 continue;
1675 }
1676 if (fMC == *fChar) {
1677 continue;
1678 }
1679 if (' ' == *fChar) {
1680 continue;
1681 }
1682 MarkType nextType = this->getMarkType(MarkLookup::kAllowUnknown);
1683 if (markType != nextType) {
1684 continue;
1685 }
1686 bool hasEnd = this->hasEndToken();
1687 if (hasEnd) {
1688 if (!--stack) {
1689 foundEnd = true;
1690 continue;
1691 }
1692 } else {
1693 ++stack;
1694 }
1695 } while ((void) ++fLineCount, (void) (fLine += lineLen), (void) (fChar = fLine),
1696 !this->eof() && !foundEnd);
1697 if (foundEnd) {
1698 return true;
1699 }
1700 fLineCount = startLineCount;
1701 fLine = start;
1702 fChar = start;
1703 return this->reportError<bool>("unbalanced stack");
1704}
1705
1706vector<string> BmhParser::topicName() {
1707 vector<string> result;
1708 this->skipWhiteSpace();
1709 const char* lineEnd = fLine + this->lineLength();
1710 const char* nameStart = fChar;
1711 while (fChar < lineEnd) {
1712 char ch = this->next();
1713 SkASSERT(',' != ch);
1714 if ('\n' == ch) {
1715 break;
1716 }
1717 if (fMC == ch) {
1718 break;
1719 }
1720 }
1721 if (fChar - 1 > nameStart) {
1722 string builder(nameStart, fChar - nameStart - 1);
1723 trim_start_end(builder);
1724 result.push_back(builder);
1725 }
1726 if (fChar < lineEnd && fMC == this->peek()) {
1727 this->next();
1728 }
1729 return result;
1730}
1731
1732// typeName parsing rules depend on mark type
1733vector<string> BmhParser::typeName(MarkType markType, bool* checkEnd) {
1734 fAnonymous = false;
1735 fCloned = false;
1736 vector<string> result;
1737 string builder;
1738 if (fParent) {
1739 builder = fParent->fName;
1740 }
1741 switch (markType) {
1742 case MarkType::kEnum:
1743 // enums may be nameless
1744 case MarkType::kConst:
1745 case MarkType::kEnumClass:
1746 case MarkType::kClass:
1747 case MarkType::kStruct:
1748 case MarkType::kTypedef:
1749 // expect name
1750 builder = this->className(markType);
1751 break;
1752 case MarkType::kExample:
1753 // check to see if one already exists -- if so, number this one
1754 builder = this->uniqueName(string(), markType);
1755 this->skipNoName();
1756 break;
1757 case MarkType::kCode:
1758 case MarkType::kDeprecated:
1759 case MarkType::kDescription:
1760 case MarkType::kDoxygen:
1761 case MarkType::kExperimental:
1762 case MarkType::kExternal:
1763 case MarkType::kFormula:
1764 case MarkType::kFunction:
1765 case MarkType::kLegend:
1766 case MarkType::kList:
1767 case MarkType::kNoExample:
1768 case MarkType::kPrivate:
1769 case MarkType::kTrack:
1770 this->skipNoName();
1771 break;
1772 case MarkType::kAlias:
1773 case MarkType::kAnchor:
1774 case MarkType::kBug: // fixme: expect number
1775 case MarkType::kDefine:
1776 case MarkType::kDefinedBy:
1777 case MarkType::kError:
1778 case MarkType::kFile:
1779 case MarkType::kHeight:
1780 case MarkType::kImage:
1781 case MarkType::kPlatform:
1782 case MarkType::kReturn:
1783 case MarkType::kSeeAlso:
1784 case MarkType::kSubstitute:
1785 case MarkType::kTime:
1786 case MarkType::kToDo:
1787 case MarkType::kVolatile:
1788 case MarkType::kWidth:
1789 *checkEnd = false; // no name, may have text body
1790 break;
1791 case MarkType::kStdOut:
1792 this->skipNoName();
1793 break; // unnamed
1794 case MarkType::kMember:
1795 builder = this->memberName();
1796 break;
1797 case MarkType::kMethod:
1798 builder = this->methodName();
1799 break;
1800 case MarkType::kParam:
1801 // fixme: expect camelCase
1802 builder = this->word("", "");
1803 this->skipSpace();
1804 *checkEnd = false;
1805 break;
1806 case MarkType::kTable:
1807 this->skipNoName();
1808 break; // unnamed
1809 case MarkType::kSubtopic:
1810 case MarkType::kTopic:
1811 // fixme: start with cap, allow space, hyphen, stop on comma
1812 // one topic can have multiple type names delineated by comma
1813 result = this->topicName();
1814 if (result.size() == 0 && this->hasEndToken()) {
1815 break;
1816 }
1817 return result;
1818 default:
1819 // fixme: don't allow silent failures
1820 SkASSERT(0);
1821 }
1822 result.push_back(builder);
1823 return result;
1824}
1825
1826string BmhParser::uniqueName(const string& base, MarkType markType) {
1827 string builder(base);
1828 if (!builder.length()) {
1829 builder = fParent->fName;
1830 }
1831 if (!fParent) {
1832 return builder;
1833 }
1834 int number = 2;
1835 string numBuilder(builder);
1836 do {
1837 for (const auto& iter : fParent->fChildren) {
1838 if (markType == iter->fMarkType) {
1839 if (iter->fName == numBuilder) {
Cary Clark8032b982017-07-28 11:04:54 -04001840 fCloned = true;
1841 numBuilder = builder + '_' + to_string(number);
1842 goto tryNext;
1843 }
1844 }
1845 }
1846 break;
1847tryNext: ;
1848 } while (++number);
1849 return numBuilder;
1850}
1851
1852string BmhParser::uniqueRootName(const string& base, MarkType markType) {
1853 auto checkName = [markType](const Definition& def, const string& numBuilder) -> bool {
1854 return markType == def.fMarkType && def.fName == numBuilder;
1855 };
1856
1857 string builder(base);
1858 if (!builder.length()) {
1859 builder = fParent->fName;
1860 }
1861 int number = 2;
1862 string numBuilder(builder);
1863 Definition* cloned = nullptr;
1864 do {
1865 if (fRoot) {
1866 for (auto& iter : fRoot->fBranches) {
1867 if (checkName(*iter.second, numBuilder)) {
1868 cloned = iter.second;
1869 goto tryNext;
1870 }
1871 }
1872 for (auto& iter : fRoot->fLeaves) {
1873 if (checkName(iter.second, numBuilder)) {
1874 cloned = &iter.second;
1875 goto tryNext;
1876 }
1877 }
1878 } else if (fParent) {
1879 for (auto& iter : fParent->fChildren) {
1880 if (checkName(*iter, numBuilder)) {
1881 cloned = &*iter;
1882 goto tryNext;
1883 }
1884 }
1885 }
1886 break;
1887tryNext: ;
1888 if ("()" == builder.substr(builder.length() - 2)) {
1889 builder = builder.substr(0, builder.length() - 2);
1890 }
1891 if (MarkType::kMethod == markType) {
1892 cloned->fCloned = true;
1893 }
1894 fCloned = true;
1895 numBuilder = builder + '_' + to_string(number);
1896 } while (++number);
1897 return numBuilder;
1898}
1899
1900void BmhParser::validate() const {
1901 for (int index = 0; index <= (int) Last_MarkType; ++index) {
1902 SkASSERT(fMaps[index].fMarkType == (MarkType) index);
1903 }
1904 const char* last = "";
1905 for (int index = 0; index <= (int) Last_MarkType; ++index) {
1906 const char* next = fMaps[index].fName;
1907 if (!last[0]) {
1908 last = next;
1909 continue;
1910 }
1911 if (!next[0]) {
1912 continue;
1913 }
1914 SkASSERT(strcmp(last, next) < 0);
1915 last = next;
1916 }
1917}
1918
1919string BmhParser::word(const string& prefix, const string& delimiter) {
1920 string builder(prefix);
1921 this->skipWhiteSpace();
1922 const char* lineEnd = fLine + this->lineLength();
1923 const char* nameStart = fChar;
1924 while (fChar < lineEnd) {
1925 char ch = this->next();
1926 if (' ' >= ch) {
1927 break;
1928 }
1929 if (',' == ch) {
1930 return this->reportError<string>("no comma needed");
1931 break;
1932 }
1933 if (fMC == ch) {
1934 return builder;
1935 }
1936 if (!isalnum(ch) && '_' != ch && ':' != ch && '-' != ch) {
1937 return this->reportError<string>("unexpected char");
1938 }
1939 if (':' == ch) {
1940 // expect pair, and expect word to start with Sk
1941 if (nameStart[0] != 'S' || nameStart[1] != 'k') {
1942 return this->reportError<string>("expected Sk");
1943 }
1944 if (':' != this->peek()) {
1945 return this->reportError<string>("expected ::");
1946 }
1947 this->next();
1948 } else if ('-' == ch) {
1949 // expect word not to start with Sk or kX where X is A-Z
1950 if (nameStart[0] == 'k' && nameStart[1] >= 'A' && nameStart[1] <= 'Z') {
1951 return this->reportError<string>("didn't expected kX");
1952 }
1953 if (nameStart[0] == 'S' && nameStart[1] == 'k') {
1954 return this->reportError<string>("expected Sk");
1955 }
1956 }
1957 }
1958 if (prefix.size()) {
1959 builder += delimiter;
1960 }
1961 builder.append(nameStart, fChar - nameStart - 1);
1962 return builder;
1963}
1964
1965// pass one: parse text, collect definitions
1966// pass two: lookup references
1967
1968DEFINE_string2(bmh, b, "", "A path to a *.bmh file or a directory.");
1969DEFINE_string2(examples, e, "", "File of fiddlecli input, usually fiddle.json (For now, disables -r -f -s)");
Cary Clarkf05bdda2017-08-24 12:59:48 -04001970DEFINE_string2(fiddle, f, "", "File of fiddlecli output, usually fiddleout.json.");
Cary Clark8032b982017-07-28 11:04:54 -04001971DEFINE_string2(include, i, "", "A path to a *.h file or a directory.");
1972DEFINE_bool2(hack, k, false, "Do a find/replace hack to update all *.bmh files. (Requires -b)");
1973DEFINE_bool2(populate, p, false, "Populate include from bmh. (Requires -b -i)");
1974DEFINE_string2(ref, r, "", "Resolve refs and write bmh_*.md files to path. (Requires -b)");
1975DEFINE_bool2(spellcheck, s, false, "Spell-check. (Requires -b)");
1976DEFINE_bool2(tokens, t, false, "Output include tokens. (Requires -i)");
1977DEFINE_bool2(crosscheck, x, false, "Check bmh against includes. (Requires -b -i)");
1978
1979static bool dump_examples(FILE* fiddleOut, const Definition& def, bool* continuation) {
1980 if (MarkType::kExample == def.fMarkType) {
1981 string result;
1982 if (!def.exampleToScript(&result)) {
1983 return false;
1984 }
1985 if (result.length() > 0) {
1986 if (*continuation) {
1987 fprintf(fiddleOut, ",\n");
1988 } else {
1989 *continuation = true;
1990 }
1991 fprintf(fiddleOut, "%s", result.c_str());
1992 }
1993 return true;
1994 }
1995 for (auto& child : def.fChildren ) {
1996 if (!dump_examples(fiddleOut, *child, continuation)) {
1997 return false;
1998 }
1999 }
2000 return true;
2001}
2002
2003static int count_children(const Definition& def, MarkType markType) {
2004 int count = 0;
2005 if (markType == def.fMarkType) {
2006 ++count;
2007 }
2008 for (auto& child : def.fChildren ) {
2009 count += count_children(*child, markType);
2010 }
2011 return count;
2012}
2013
2014int main(int argc, char** const argv) {
2015 BmhParser bmhParser;
2016 bmhParser.validate();
2017
2018 SkCommandLineFlags::SetUsage(
2019 "Common Usage: bookmaker -i path/to/include.h -t\n"
2020 " bookmaker -b path/to/bmh_files -e fiddle.json\n"
2021 " ~/go/bin/fiddlecli --input fiddle.json --output fiddleout.json\n"
2022 " bookmaker -b path/to/bmh_files -f fiddleout.json -r path/to/md_files\n"
2023 " bookmaker -b path/to/bmh_files -i path/to/include.h -x\n"
2024 " bookmaker -b path/to/bmh_files -i path/to/include.h -p\n");
2025 bool help = false;
2026 for (int i = 1; i < argc; i++) {
2027 if (0 == strcmp("-h", argv[i]) || 0 == strcmp("--help", argv[i])) {
2028 help = true;
2029 for (int j = i + 1; j < argc; j++) {
2030 if (SkStrStartsWith(argv[j], '-')) {
2031 break;
2032 }
2033 help = false;
2034 }
2035 break;
2036 }
2037 }
2038 if (!help) {
2039 SkCommandLineFlags::Parse(argc, argv);
2040 } else {
2041 SkCommandLineFlags::PrintUsage();
2042 const char* commands[] = { "", "-h", "bmh", "-h", "examples", "-h", "include", "-h", "fiddle",
2043 "-h", "ref", "-h", "tokens",
2044 "-h", "crosscheck", "-h", "populate", "-h", "spellcheck" };
2045 SkCommandLineFlags::Parse(SK_ARRAY_COUNT(commands), (char**) commands);
2046 return 0;
2047 }
2048 if (FLAGS_bmh.isEmpty() && FLAGS_include.isEmpty()) {
2049 SkDebugf("requires -b or -i\n");
2050 SkCommandLineFlags::PrintUsage();
2051 return 1;
2052 }
2053 if (FLAGS_bmh.isEmpty() && !FLAGS_examples.isEmpty()) {
2054 SkDebugf("-e requires -b\n");
2055 SkCommandLineFlags::PrintUsage();
2056 return 1;
2057 }
2058 if (FLAGS_hack) {
2059 if (FLAGS_bmh.isEmpty()) {
2060 SkDebugf("-k or --hack requires -b\n");
2061 SkCommandLineFlags::PrintUsage();
2062 return 1;
2063 }
2064 HackParser hacker;
2065 if (!hacker.parseFile(FLAGS_bmh[0], ".bmh")) {
2066 SkDebugf("hack failed\n");
2067 return -1;
2068 }
2069 SkDebugf("hack success\n");
2070 return 0;
2071 }
2072 if ((FLAGS_include.isEmpty() || FLAGS_bmh.isEmpty()) && FLAGS_populate) {
2073 SkDebugf("-r requires -b -i\n");
2074 SkCommandLineFlags::PrintUsage();
2075 return 1;
2076 }
2077 if (FLAGS_bmh.isEmpty() && !FLAGS_ref.isEmpty()) {
2078 SkDebugf("-r requires -b\n");
2079 SkCommandLineFlags::PrintUsage();
2080 return 1;
2081 }
2082 if (FLAGS_bmh.isEmpty() && FLAGS_spellcheck) {
2083 SkDebugf("-s requires -b\n");
2084 SkCommandLineFlags::PrintUsage();
2085 return 1;
2086 }
2087 if (FLAGS_include.isEmpty() && FLAGS_tokens) {
2088 SkDebugf("-t requires -i\n");
2089 SkCommandLineFlags::PrintUsage();
2090 return 1;
2091 }
2092 if ((FLAGS_include.isEmpty() || FLAGS_bmh.isEmpty()) && FLAGS_crosscheck) {
2093 SkDebugf("-x requires -b -i\n");
2094 SkCommandLineFlags::PrintUsage();
2095 return 1;
2096 }
2097 if (!FLAGS_bmh.isEmpty()) {
2098 if (!bmhParser.parseFile(FLAGS_bmh[0], ".bmh")) {
2099 return -1;
2100 }
2101 }
2102 bool done = false;
2103 if (!FLAGS_include.isEmpty()) {
2104 if (FLAGS_tokens || FLAGS_crosscheck) {
2105 IncludeParser includeParser;
2106 includeParser.validate();
2107 if (!includeParser.parseFile(FLAGS_include[0], ".h")) {
2108 return -1;
2109 }
2110 if (FLAGS_tokens) {
2111 includeParser.dumpTokens();
2112 done = true;
2113 } else if (FLAGS_crosscheck) {
2114 if (!includeParser.crossCheck(bmhParser)) {
2115 return -1;
2116 }
2117 done = true;
2118 }
2119 } else if (FLAGS_populate) {
2120 IncludeWriter includeWriter;
2121 includeWriter.validate();
2122 if (!includeWriter.parseFile(FLAGS_include[0], ".h")) {
2123 return -1;
2124 }
2125 if (!includeWriter.populate(bmhParser)) {
2126 return -1;
2127 }
2128 done = true;
2129 }
2130 }
2131 FiddleParser fparser(&bmhParser);
2132 if (!done && !FLAGS_fiddle.isEmpty() && FLAGS_examples.isEmpty()) {
2133 if (!fparser.parseFile(FLAGS_fiddle[0], ".txt")) {
2134 return -1;
2135 }
2136 }
2137 if (!done && !FLAGS_ref.isEmpty() && FLAGS_examples.isEmpty()) {
2138 MdOut mdOut(bmhParser);
2139 mdOut.buildReferences(FLAGS_bmh[0], FLAGS_ref[0]);
2140 }
2141 if (!done && FLAGS_spellcheck && FLAGS_examples.isEmpty()) {
2142 bmhParser.spellCheck(FLAGS_bmh[0]);
2143 done = true;
2144 }
2145 int examples = 0;
2146 int methods = 0;
2147 int topics = 0;
2148 FILE* fiddleOut;
2149 if (!done && !FLAGS_examples.isEmpty()) {
2150 fiddleOut = fopen(FLAGS_examples[0], "wb");
2151 if (!fiddleOut) {
2152 SkDebugf("could not open output file %s\n", FLAGS_examples[0]);
2153 return -1;
2154 }
2155 fprintf(fiddleOut, "{\n");
2156 bool continuation = false;
2157 for (const auto& topic : bmhParser.fTopicMap) {
2158 if (topic.second->fParent) {
2159 continue;
2160 }
2161 dump_examples(fiddleOut, *topic.second, &continuation);
2162 }
2163 fprintf(fiddleOut, "\n}\n");
2164 fclose(fiddleOut);
2165 }
2166 for (const auto& topic : bmhParser.fTopicMap) {
2167 if (topic.second->fParent) {
2168 continue;
2169 }
2170 examples += count_children(*topic.second, MarkType::kExample);
2171 methods += count_children(*topic.second, MarkType::kMethod);
2172 topics += count_children(*topic.second, MarkType::kSubtopic);
2173 topics += count_children(*topic.second, MarkType::kTopic);
2174 }
2175 SkDebugf("topics=%d classes=%d methods=%d examples=%d\n",
2176 bmhParser.fTopicMap.size(), bmhParser.fClassMap.size(),
2177 methods, examples);
2178 return 0;
2179}