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