• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2017 Google Inc.
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 
8 #include "bookmaker.h"
9 #include "SkOSPath.h"
10 
count_indent(const string & text,size_t test,size_t end)11 static size_t count_indent(const string& text, size_t test, size_t end) {
12     size_t result = test;
13     while (test < end) {
14         if (' ' != text[test]) {
15             break;
16         }
17         ++test;
18     }
19     return test - result;
20 }
21 
add_code(const string & text,int pos,int end,size_t outIndent,size_t textIndent,string & example)22 static void add_code(const string& text, int pos, int end,
23         size_t outIndent, size_t textIndent, string& example) {
24     do {
25          // fix this to move whole paragraph in, out, but preserve doc indent
26         int nextIndent = count_indent(text, pos, end);
27         size_t len = text.find('\n', pos);
28         if (string::npos == len) {
29             len = end;
30         }
31         if ((size_t) (pos + nextIndent) < len) {
32             size_t indent = outIndent + nextIndent;
33             SkASSERT(indent >= textIndent);
34             indent -= textIndent;
35             for (size_t index = 0; index < indent; ++index) {
36                 example += ' ';
37             }
38             pos += nextIndent;
39             while ((size_t) pos < len) {
40                 example += '"' == text[pos] ? "\\\"" :
41                     '\\' == text[pos] ? "\\\\" :
42                     text.substr(pos, 1);
43                 ++pos;
44             }
45             example += "\\n";
46         } else {
47             pos += nextIndent;
48         }
49         if ('\n' == text[pos]) {
50             ++pos;
51         }
52     } while (pos < end);
53 }
54 
55 #ifdef CONST
56 #undef CONST
57 #endif
58 
59 #ifdef FRIEND
60 #undef FRIEND
61 #endif
62 
63 #ifdef BLANK
64 #undef BLANK
65 #endif
66 
67 #ifdef ANY
68 #undef ANY
69 #endif
70 
71 #ifdef DEFOP
72 #undef DEFOP
73 #endif
74 
75 #define CONST 1
76 #define STATIC 2
77 #define BLANK  0
78 #define ANY -1
79 #define DEFOP Definition::Operator
80 
81 enum class OpType : int8_t {
82     kNone,
83     kVoid,
84     kBool,
85     kChar,
86     kFloat,
87     kInt,
88     kScalar,
89     kSizeT,
90     kThis,
91     kAny,
92 };
93 
94 enum class OpMod : int8_t {
95     kNone,
96     kArray,
97     kMove,
98     kPointer,
99     kReference,
100     kAny,
101 };
102 
103 const struct OperatorParser {
104     DEFOP fOperator;
105     const char* fSymbol;
106     const char* fName;
107     int8_t fFriend;
108     OpType fReturnType;
109     OpMod fReturnMod;
110     int8_t fConstMethod;
111     struct Param {
112         int8_t fConst;
113         OpType fType;
114         OpMod fMod;
115     } fParams[2];
116 } opData[] = {
117     { DEFOP::kUnknown, "??", "???",    BLANK,  OpType::kNone,   OpMod::kNone,         BLANK,
118                                     { } },
119     { DEFOP::kAdd,     "+",  "add",    BLANK,  OpType::kThis,   OpMod::kNone,         BLANK,
120                                     {{ CONST,  OpType::kThis,   OpMod::kReference, },
121                                      { CONST,  OpType::kThis,   OpMod::kReference, }}},
122     { DEFOP::kAddTo,   "+=", "addto",  BLANK,  OpType::kVoid,   OpMod::kNone,         BLANK,
123                                     {{ CONST,  OpType::kThis,   OpMod::kReference, }}},
124     { DEFOP::kAddTo,   "+=", "addto1", BLANK,  OpType::kThis,   OpMod::kReference,    BLANK,
125                                     {{ CONST,  OpType::kThis,   OpMod::kReference, }}},
126     { DEFOP::kAddTo,   "+=", "addto2", BLANK,  OpType::kThis,   OpMod::kReference,    BLANK,
127                                     {{ CONST,  OpType::kChar,   OpMod::kArray, }}},
128     { DEFOP::kAddTo,   "+=", "addto3", BLANK,  OpType::kThis,   OpMod::kReference,    BLANK,
129                                     {{ CONST,  OpType::kChar,   OpMod::kNone, }}},
130     { DEFOP::kArray,   "[]", "array",  BLANK,  OpType::kScalar, OpMod::kNone,         CONST,
131                                     {{ BLANK,  OpType::kInt,    OpMod::kNone, }}},
132     { DEFOP::kArray,   "[]", "array1", BLANK,  OpType::kScalar, OpMod::kReference,    BLANK,
133                                     {{ BLANK,  OpType::kInt,    OpMod::kNone, }}},
134     { DEFOP::kArray,   "[]", "array2", BLANK,  OpType::kChar,   OpMod::kNone,         CONST,
135                                     {{ BLANK,  OpType::kSizeT,  OpMod::kNone, }}},
136     { DEFOP::kArray,   "[]", "array3", BLANK,  OpType::kChar,   OpMod::kReference,    BLANK,
137                                     {{ BLANK,  OpType::kSizeT,  OpMod::kNone, }}},
138     { DEFOP::kCast,    "()", "cast",   BLANK,  OpType::kAny,    OpMod::kAny,          ANY,
139                                     {{ ANY,    OpType::kAny,    OpMod::kAny,  }}},
140     { DEFOP::kCopy,    "=",  "copy",   BLANK,  OpType::kThis,   OpMod::kReference,    BLANK,
141                                     {{ CONST,  OpType::kThis,   OpMod::kReference, }}},
142     { DEFOP::kCopy,    "=",  "copy1",  BLANK,  OpType::kThis,   OpMod::kReference,    BLANK,
143                                     {{ CONST,  OpType::kChar,   OpMod::kArray, }}},
144     { DEFOP::kDelete,  "delete", "delete",  BLANK,  OpType::kVoid,   OpMod::kNone,    BLANK,
145                                     {{ BLANK,  OpType::kVoid,   OpMod::kPointer, }}},
146     { DEFOP::kDereference, "->", "deref",  ANY,  OpType::kThis, OpMod::kPointer,      CONST,
147                                     { } },
148     { DEFOP::kDereference, "*", "deref", BLANK,  OpType::kThis, OpMod::kReference,    CONST,
149                                     { } },
150     { DEFOP::kEqual,   "==", "equal",  BLANK,  OpType::kBool,   OpMod::kNone,         BLANK,
151                                     {{ CONST,  OpType::kThis,   OpMod::kReference, },
152                                      { CONST,  OpType::kThis,   OpMod::kReference, }}},
153     { DEFOP::kEqual,   "==", "equal1",  BLANK,  OpType::kBool,   OpMod::kNone,         CONST,
154                                     {{ CONST,  OpType::kThis,   OpMod::kReference, }}},
155     { DEFOP::kEqual,   "==", "equal2", ANY,    OpType::kBool,   OpMod::kNone,         BLANK,
156                                     {{ CONST,  OpType::kThis,   OpMod::kReference, },
157                                      { CONST,  OpType::kThis,   OpMod::kReference, }}},
158     { DEFOP::kMinus,   "-",  "minus",  BLANK,  OpType::kThis,   OpMod::kNone,         CONST,
159                                     { } },
160     { DEFOP::kMove,    "=",  "move",   BLANK,  OpType::kThis,   OpMod::kReference,    BLANK,
161                                     {{ BLANK,  OpType::kThis,   OpMod::kMove, }}},
162     { DEFOP::kMultiply, "*", "multiply", BLANK, OpType::kThis, OpMod::kNone,         CONST,
163                                     {{ BLANK,  OpType::kScalar, OpMod::kNone, }}},
164     { DEFOP::kMultiply, "*", "multiply1", BLANK, OpType::kThis, OpMod::kNone,         BLANK,
165                                     {{ CONST,  OpType::kThis,   OpMod::kReference, },
166                                      { CONST,  OpType::kThis,   OpMod::kReference, }}},
167     { DEFOP::kMultiplyBy, "*=", "multiplyby", BLANK,  OpType::kThis, OpMod::kReference, BLANK,
168                                     {{ BLANK,  OpType::kScalar, OpMod::kNone, }}},
169     { DEFOP::kNew,     "new", "new",   BLANK,  OpType::kVoid,   OpMod::kPointer,      BLANK,
170                                     {{ BLANK,  OpType::kSizeT,  OpMod::kNone, }}},
171     { DEFOP::kNotEqual, "!=", "notequal", BLANK, OpType::kBool,   OpMod::kNone,      BLANK,
172                                     {{ CONST,  OpType::kThis,   OpMod::kReference, },
173                                      { CONST,  OpType::kThis,   OpMod::kReference, }}},
174     { DEFOP::kNotEqual, "!=", "notequal1", BLANK,  OpType::kBool,   OpMod::kNone,     CONST,
175                                     {{ CONST,  OpType::kThis,   OpMod::kReference, }}},
176     { DEFOP::kNotEqual, "!=", "notequal2", ANY, OpType::kBool,   OpMod::kNone,     BLANK,
177                                     {{ CONST,  OpType::kThis,   OpMod::kReference, },
178                                      { CONST,  OpType::kThis,   OpMod::kReference, }}},
179     { DEFOP::kSubtract, "-", "subtract", BLANK, OpType::kThis, OpMod::kNone,         BLANK,
180                                     {{ CONST,  OpType::kThis,   OpMod::kReference, },
181                                      { CONST,  OpType::kThis,   OpMod::kReference, }}},
182     { DEFOP::kSubtractFrom, "-=", "subtractfrom",  BLANK,  OpType::kVoid,   OpMod::kNone, BLANK,
183                                     {{ CONST,  OpType::kThis,   OpMod::kReference, }}},
184 };
185 
lookup_type(const string & typeWord,const string & name)186 OpType lookup_type(const string& typeWord, const string& name) {
187     if (typeWord == name || (typeWord == "SkIVector" && name == "SkIPoint")
188                          || (typeWord == "SkVector" && name == "SkPoint")) {
189         return OpType::kThis;
190     }
191     const char* keyWords[] = { "void", "bool", "char", "float", "int", "SkScalar", "size_t" };
192     for (unsigned i = 0; i < SK_ARRAY_COUNT(keyWords); ++i) {
193         if (typeWord == keyWords[i]) {
194             return (OpType) (i + 1);
195         }
196     }
197     return OpType::kNone;
198 }
199 
lookup_mod(TextParser & iParser)200 OpMod lookup_mod(TextParser& iParser) {
201     OpMod mod = OpMod::kNone;
202     if ('&' == iParser.peek()) {
203         mod = OpMod::kReference;
204         iParser.next();
205         if ('&' == iParser.peek()) {
206             mod = OpMod::kMove;
207             iParser.next();
208         }
209     } else if ('*' == iParser.peek()) {
210         mod = OpMod::kPointer;
211         iParser.next();
212     }
213     iParser.skipWhiteSpace();
214     return mod;
215 }
216 
parseOperator(size_t doubleColons,string & result)217 bool Definition::parseOperator(size_t doubleColons, string& result) {
218     const char operatorStr[] = "operator";
219     size_t opPos = fName.find(operatorStr, doubleColons);
220     if (string::npos == opPos) {
221         return false;
222     }
223     string className(fName, 0, doubleColons - 2);
224     TextParser iParser(fFileName, fStart, fContentStart, fLineCount);
225     SkAssertResult(iParser.skipWord("#Method"));
226     iParser.skipExact("SK_API");
227     iParser.skipWhiteSpace();
228     bool isStatic = iParser.skipExact("static");
229     iParser.skipWhiteSpace();
230     iParser.skipExact("SK_API");
231     iParser.skipWhiteSpace();
232     bool returnsConst = iParser.skipExact("const");
233     if (returnsConst) {
234         SkASSERT(0);  // incomplete
235     }
236     SkASSERT(isStatic == false || returnsConst == false);
237     iParser.skipWhiteSpace();
238     const char* returnTypeStart = iParser.fChar;
239     iParser.skipToNonAlphaNum();
240     SkASSERT(iParser.fChar > returnTypeStart);
241     string returnType(returnTypeStart, iParser.fChar - returnTypeStart);
242     OpType returnOpType = lookup_type(returnType, className);
243     iParser.skipWhiteSpace();
244     OpMod returnMod = lookup_mod(iParser);
245     SkAssertResult(iParser.skipExact("operator"));
246     iParser.skipWhiteSpace();
247     fMethodType = Definition::MethodType::kOperator;
248     TextParser::Save save(&iParser);
249     for (auto parser : opData) {
250         save.restore();
251         if (!iParser.skipExact(parser.fSymbol)) {
252             continue;
253         }
254         iParser.skipWhiteSpace();
255         if ('(' != iParser.peek()) {
256             continue;
257         }
258         if (parser.fFriend != ANY && (parser.fFriend == STATIC) != isStatic) {
259             continue;
260         }
261         if (parser.fReturnType != OpType::kAny && parser.fReturnType != returnOpType) {
262             continue;
263         }
264         if (parser.fReturnMod != OpMod::kAny && parser.fReturnMod != returnMod) {
265             continue;
266         }
267         iParser.next();  // skip '('
268         iParser.skipWhiteSpace();
269         int parserCount = (parser.fParams[0].fType != OpType::kNone) +
270             (parser.fParams[1].fType != OpType::kNone);
271         bool countsMatch = true;
272         for (int pIndex = 0; pIndex < 2; ++pIndex) {
273             if (')' == iParser.peek()) {
274                 countsMatch = pIndex == parserCount;
275                 break;
276             }
277             if (',' == iParser.peek()) {
278                 iParser.next();
279                 iParser.skipWhiteSpace();
280             }
281             bool paramConst = iParser.skipExact("const");
282             if (parser.fParams[pIndex].fConst != ANY &&
283                     paramConst != (parser.fParams[pIndex].fConst == CONST)) {
284                 countsMatch = false;
285                 break;
286             }
287             iParser.skipWhiteSpace();
288             const char* paramStart = iParser.fChar;
289             iParser.skipToNonAlphaNum();
290             SkASSERT(iParser.fChar > paramStart);
291             string paramType(paramStart, iParser.fChar - paramStart);
292             OpType paramOpType = lookup_type(paramType, className);
293             if (parser.fParams[pIndex].fType != OpType::kAny &&
294                     parser.fParams[pIndex].fType != paramOpType) {
295                 countsMatch = false;
296                 break;
297             }
298             iParser.skipWhiteSpace();
299             OpMod paramMod = lookup_mod(iParser);
300             if (parser.fParams[pIndex].fMod != OpMod::kAny &&
301                     parser.fParams[pIndex].fMod != paramMod) {
302                 countsMatch = false;
303                 break;
304             }
305             iParser.skipToNonAlphaNum();
306             if ('[' == iParser.peek()) {
307                 paramMod = OpMod::kArray;
308                 SkAssertResult(iParser.skipExact("[]"));
309             }
310             iParser.skipWhiteSpace();
311         }
312         if (!countsMatch) {
313             continue;
314         }
315         if (')' != iParser.peek()) {
316             continue;
317         }
318         iParser.next();
319         bool constMethod = iParser.skipExact("_const");
320         if (parser.fConstMethod != ANY && (parser.fConstMethod == CONST) != constMethod) {
321             continue;
322         }
323         result += parser.fName;
324         result += "_operator";
325         fOperator = parser.fOperator;
326         fOperatorConst = constMethod;
327         return true;
328     }
329     SkASSERT(0); // incomplete
330     return false;
331 #if 0
332     if ('!' == fName[opPos]) {
333         SkASSERT('=' == fName[opPos + 1]);
334         result += "not_equal_operator";
335     } else if ('=' == fName[opPos]) {
336         if ('(' == fName[opPos + 1]) {
337             result += isMove ? "move_" : "copy_";
338             result += "assignment_operator";
339         } else {
340             SkASSERT('=' == fName[opPos + 1]);
341             result += "equal_operator";
342         }
343     } else if ('[' == fName[opPos]) {
344         result += "subscript_operator";
345         const char* end = fContentStart;
346         while (end > fStart && ' ' >= end[-1]) {
347             --end;
348         }
349         string constCheck(fStart, end - fStart);
350         size_t constPos = constCheck.rfind("const");
351         if (constCheck.length() == constPos + 5) {
352             result += "_const";
353         }
354     } else if ('*' == fName[opPos]) {
355         result += "multiply_operator";
356     } else if ('-' == fName[opPos]) {
357         result += "subtract_operator";
358     } else if ('+' == fName[opPos]) {
359         result += "add_operator";
360     } else {
361         SkASSERT(0);  // todo: incomplete
362     }
363 #endif
364     return true;
365 }
366 
367 #undef CONST
368 #undef FRIEND
369 #undef BLANK
370 #undef DEFOP
371 
boilerplateIfDef(Definition * parent)372 bool Definition::boilerplateIfDef(Definition* parent) {
373     const Definition& label = fTokens.front();
374     if (Type::kWord != label.fType) {
375         return false;
376     }
377     fName = string(label.fContentStart, label.fContentEnd - label.fContentStart);
378     return true;
379 }
380 
381 // todo: this is matching #ifndef SkXXX_DEFINED for no particular reason
382 // it doesn't do anything useful with arbitrary input, e.g. #ifdef SK_SUPPORT_LEGACY_CANVAS_HELPERS
383 // also doesn't know what to do with SK_REQUIRE_LOCAL_VAR()
boilerplateDef(Definition * parent)384 bool Definition::boilerplateDef(Definition* parent) {
385     if (!this->boilerplateIfDef(parent)) {
386         return false;
387     }
388     const char* s = fName.c_str();
389     const char* e = strchr(s, '_');
390     return true; // fixme: if this is trying to do something useful with define, do it here
391     if (!e) {
392         return false;
393     }
394     string prefix(s, e - s);
395     const char* inName = strstr(parent->fName.c_str(), prefix.c_str());
396     if (!inName) {
397         return false;
398     }
399     if ('/' != inName[-1] && '\\' != inName[-1]) {
400         return false;
401     }
402     if (strcmp(inName + prefix.size(), ".h")) {
403         return false;
404     }
405     return true;
406 }
407 
408 // fixme: this will need to be more complicated to handle all of Skia
409 // for now, just handle paint -- maybe fiddle will loosen naming restrictions
setCanonicalFiddle()410 void Definition::setCanonicalFiddle() {
411     fMethodType = Definition::MethodType::kNone;
412     size_t doubleColons = fName.find("::", 0);
413     SkASSERT(string::npos != doubleColons);
414     string base = fName.substr(0, doubleColons);
415     string result = base + "_";
416     doubleColons += 2;
417     if (string::npos != fName.find('~', doubleColons)) {
418         fMethodType = Definition::MethodType::kDestructor;
419         result += "destructor";
420     } else if (!this->parseOperator(doubleColons, result)) {
421         bool isMove = string::npos != fName.find("&&", doubleColons);
422         size_t parens = fName.find("()", doubleColons);
423         if (string::npos != parens) {
424             string methodName = fName.substr(doubleColons, parens - doubleColons);
425             do {
426                 size_t nextDouble = methodName.find("::");
427                 if (string::npos == nextDouble) {
428                     break;
429                 }
430                 base = methodName.substr(0, nextDouble);
431                 result += base + '_';
432                 methodName = methodName.substr(nextDouble + 2);
433                 doubleColons += nextDouble + 2;
434             } while (true);
435             if (base == methodName) {
436                 fMethodType = Definition::MethodType::kConstructor;
437                 result += "empty_constructor";
438             } else {
439                 result += fName.substr(doubleColons, fName.length() - doubleColons - 2);
440             }
441         } else {
442             size_t openParen = fName.find('(', doubleColons);
443             if (string::npos == openParen) {
444                 result += fName.substr(doubleColons);
445             } else {
446                 size_t comma = fName.find(',', doubleColons);
447                 if (string::npos == comma) {
448                     result += isMove ? "move_" : "copy_";
449                 }
450                 fMethodType = Definition::MethodType::kConstructor;
451                 // name them by their param types,
452                 //   e.g. SkCanvas__int_int_const_SkSurfaceProps_star
453                 // TODO: move forward until parens are balanced and terminator =,)
454                 TextParser params("", &fName[openParen] + 1, &*fName.end(), 0);
455                 bool underline = false;
456                 while (!params.eof()) {
457 //                    SkDEBUGCODE(const char* end = params.anyOf("(),="));  // unused for now
458 //                    SkASSERT(end[0] != '(');  // fixme: put off handling nested parentheseses
459                     if (params.startsWith("const") || params.startsWith("int")
460                             || params.startsWith("Sk")) {
461                         const char* wordStart = params.fChar;
462                         params.skipToNonAlphaNum();
463                         if (underline) {
464                             result += '_';
465                         } else {
466                             underline = true;
467                         }
468                         result += string(wordStart, params.fChar - wordStart);
469                     } else {
470                         params.skipToNonAlphaNum();
471                     }
472                     if (!params.eof() && '*' == params.peek()) {
473                         if (underline) {
474                             result += '_';
475                         } else {
476                             underline = true;
477                         }
478                         result += "star";
479                         params.next();
480                         params.skipSpace();
481                     }
482                     params.skipToAlpha();
483                 }
484             }
485         }
486     }
487     fFiddle = Definition::NormalizedName(result);
488 }
489 
setWrapper()490 void Definition::setWrapper() {
491     const char drawWrapper[] = "void draw(SkCanvas* canvas) {";
492     const char drawNoCanvas[] = "void draw(SkCanvas* ) {";
493     string text = this->extractText(Definition::TrimExtract::kNo);
494     size_t nonSpace = 0;
495     while (nonSpace < text.length() && ' ' >= text[nonSpace]) {
496         ++nonSpace;
497     }
498     bool hasFunc = !text.compare(nonSpace, sizeof(drawWrapper) - 1, drawWrapper);
499     bool noCanvas = !text.compare(nonSpace, sizeof(drawNoCanvas) - 1, drawNoCanvas);
500     bool hasCanvas = string::npos != text.find("SkCanvas canvas");
501     SkASSERT(!hasFunc || !noCanvas);
502     bool preprocessor = text[0] == '#';
503     bool wrapCode = !hasFunc && !noCanvas && !preprocessor;
504     if (wrapCode) {
505         fWrapper = hasCanvas ? string(drawNoCanvas) : string(drawWrapper);
506     }
507 }
508 
exampleToScript(string * result,ExampleOptions exampleOptions) const509 bool Definition::exampleToScript(string* result, ExampleOptions exampleOptions) const {
510     bool hasFiddle = true;
511     const Definition* platform = this->hasChild(MarkType::kPlatform);
512     if (platform) {
513         TextParser platParse(platform);
514         hasFiddle = !platParse.strnstr("!fiddle", platParse.fEnd);
515     }
516     if (!hasFiddle) {
517         *result = "";
518         return true;
519     }
520     string text = this->extractText(Definition::TrimExtract::kNo);
521     bool textOut = string::npos != text.find("SkDebugf(")
522             || string::npos != text.find("dump(")
523             || string::npos != text.find("dumpHex(");
524     string heightStr = "256";
525     string widthStr = "256";
526     string normalizedName(fFiddle);
527     string code;
528     string imageStr = "0";
529     string srgbStr = "false";
530     string durationStr = "0";
531     for (auto const& iter : fChildren) {
532         switch (iter->fMarkType) {
533             case MarkType::kDuration:
534                 durationStr = string(iter->fContentStart, iter->fContentEnd - iter->fContentStart);
535                 break;
536             case MarkType::kHeight:
537                 heightStr = string(iter->fContentStart, iter->fContentEnd - iter->fContentStart);
538                 break;
539             case MarkType::kWidth:
540                 widthStr = string(iter->fContentStart, iter->fContentEnd - iter->fContentStart);
541                 break;
542             case MarkType::kDescription:
543                 // ignore for now
544                 break;
545             case MarkType::kFunction: {
546                 // emit this, but don't wrap this in draw()
547                 string funcText(iter->fContentStart, iter->fContentEnd - iter->fContentStart);
548                 size_t pos = 0;
549                 while (pos < funcText.length() && ' ' > funcText[pos]) {
550                     ++pos;
551                 }
552                 size_t indent = count_indent(funcText, pos, funcText.length());
553                 add_code(funcText, pos, funcText.length(), 0, indent, code);
554                 code += "\\n";
555                 } break;
556             case MarkType::kComment:
557                 break;
558             case MarkType::kImage:
559                 imageStr = string(iter->fContentStart, iter->fContentEnd - iter->fContentStart);
560                 break;
561             case MarkType::kToDo:
562                 break;
563             case MarkType::kBug:
564             case MarkType::kMarkChar:
565             case MarkType::kPlatform:
566                 // ignore for now
567                 break;
568             case MarkType::kSet:
569                 if ("sRGB" == string(iter->fContentStart,
570                                      iter->fContentEnd - iter->fContentStart)) {
571                     srgbStr = "true";
572                 } else {
573                     SkASSERT(0);   // more work to do
574                     return false;
575                 }
576                 break;
577             case MarkType::kStdOut:
578                 textOut = true;
579                 break;
580             default:
581                 SkASSERT(0);  // more coding to do
582         }
583     }
584     string animatedStr = "0" != durationStr ? "true" : "false";
585     string textOutStr = textOut ? "true" : "false";
586     size_t pos = 0;
587     while (pos < text.length() && ' ' > text[pos]) {
588         ++pos;
589     }
590     size_t end = text.length();
591     size_t outIndent = 0;
592     size_t textIndent = count_indent(text, pos, end);
593     if (fWrapper.length() > 0) {
594         code += fWrapper;
595         code += "\\n";
596         outIndent = 4;
597     }
598     add_code(text, pos, end, outIndent, textIndent, code);
599     if (fWrapper.length() > 0) {
600         code += "}";
601     }
602     string example = "\"" + normalizedName + "\": {\n";
603     size_t nameStart = fFileName.find(SkOSPath::SEPARATOR, 0);
604     SkASSERT(string::npos != nameStart);
605     string baseFile = fFileName.substr(nameStart + 1, fFileName.length() - nameStart - 5);
606     if (ExampleOptions::kText == exampleOptions) {
607         example += "    \"code\": \"" + code + "\",\n";
608         example += "    \"hash\": \"" + fHash + "\",\n";
609         example += "    \"file\": \"" + baseFile + "\",\n";
610         example += "    \"name\": \"" + fName + "\",";
611     } else {
612         example += "    \"code\": \"" + code + "\",\n";
613         if (ExampleOptions::kPng == exampleOptions) {
614             example += "    \"width\": " + widthStr + ",\n";
615             example += "    \"height\": " + heightStr + ",\n";
616             example += "    \"hash\": \"" + fHash + "\",\n";
617             example += "    \"file\": \"" + baseFile + "\",\n";
618             example += "    \"name\": \"" + fName + "\"\n";
619             example += "}";
620        } else {
621             example += "    \"options\": {\n";
622             example += "        \"width\": " + widthStr + ",\n";
623             example += "        \"height\": " + heightStr + ",\n";
624             example += "        \"source\": " + imageStr + ",\n";
625             example += "        \"srgb\": " + srgbStr + ",\n";
626             example += "        \"f16\": false,\n";
627             example += "        \"textOnly\": " + textOutStr + ",\n";
628             example += "        \"animated\": " + animatedStr + ",\n";
629             example += "        \"duration\": " + durationStr + "\n";
630             example += "    },\n";
631             example += "    \"fast\": true";
632         }
633     }
634     *result = example;
635     return true;
636 }
637 
extractText(TrimExtract trimExtract) const638 string Definition::extractText(TrimExtract trimExtract) const {
639     string result;
640     TextParser parser(fFileName, fContentStart, fContentEnd, fLineCount);
641     int childIndex = 0;
642     char mc = '#';
643     while (parser.fChar < parser.fEnd) {
644         if (TrimExtract::kYes == trimExtract && !parser.skipWhiteSpace()) {
645             break;
646         }
647         if (parser.next() == mc) {
648             if (parser.next() == mc) {
649                 if (parser.next() == mc) {
650                     mc = parser.next();
651                 }
652             } else {
653                 // fixme : more work to do if # style comment is in text
654                 // if in method definition, could be alternate method name
655                 --parser.fChar;
656                 if (' ' < parser.fChar[0]) {
657                     if (islower(parser.fChar[0])) {
658                         result += '\n';
659                         parser.skipLine();
660                     } else {
661                         SkASSERT(isupper(parser.fChar[0]));
662                         parser.skipTo(fChildren[childIndex]->fTerminator);
663                         if (mc == parser.fChar[0] && mc == parser.fChar[1]) {
664                             parser.next();
665                             parser.next();
666                         }
667                         childIndex++;
668                     }
669                 } else {
670                     parser.skipLine();
671                 }
672                 continue;
673             }
674         } else {
675             --parser.fChar;
676         }
677         const char* end = parser.fEnd;
678         const char* mark = parser.strnchr(mc, end);
679         if (mark) {
680             end = mark;
681         }
682         string fragment(parser.fChar, end - parser.fChar);
683         trim_end(fragment);
684         if (TrimExtract::kYes == trimExtract) {
685             trim_start(fragment);
686             if (result.length()) {
687                 result += '\n';
688                 result += '\n';
689             }
690         }
691         if (TrimExtract::kYes == trimExtract || has_nonwhitespace(fragment)) {
692             result += fragment;
693         }
694         parser.skipTo(end);
695     }
696     return result;
697 }
698 
space_pad(string * str)699 static void space_pad(string* str) {
700     size_t len = str->length();
701     if (len == 0) {
702         return;
703     }
704     char last = (*str)[len - 1];
705     if ('~' == last || ' ' >= last) {
706         return;
707     }
708     *str += ' ';
709 }
710 
711 //start here;
712 // see if it possible to abstract this a little bit so it can
713 // additionally be used to find params and return in method prototype that
714 // does not have corresponding doxygen comments
checkMethod() const715 bool Definition::checkMethod() const {
716     SkASSERT(MarkType::kMethod == fMarkType);
717     // if method returns a value, look for a return child
718     // for each parameter, look for a corresponding child
719     const char* end = fContentStart;
720     while (end > fStart && ' ' >= end[-1]) {
721         --end;
722     }
723     TextParser methodParser(fFileName, fStart, end, fLineCount);
724     methodParser.skipWhiteSpace();
725     SkASSERT(methodParser.startsWith("#Method"));
726     methodParser.skipName("#Method");
727     methodParser.skipSpace();
728     string name = this->methodName();
729     if (MethodType::kNone == fMethodType && name.length() > 2 &&
730             "()" == name.substr(name.length() - 2)) {
731         name = name.substr(0, name.length() - 2);
732     }
733     bool expectReturn = this->methodHasReturn(name, &methodParser);
734     bool foundReturn = false;
735     bool foundException = false;
736     for (auto& child : fChildren) {
737         foundException |= MarkType::kDeprecated == child->fMarkType
738                 || MarkType::kExperimental == child->fMarkType;
739         if (MarkType::kReturn != child->fMarkType) {
740             if (MarkType::kParam == child->fMarkType) {
741                 child->fVisited = false;
742             }
743             continue;
744         }
745         if (!expectReturn) {
746             return methodParser.reportError<bool>("no #Return expected");
747         }
748         if (foundReturn) {
749             return methodParser.reportError<bool>("multiple #Return markers");
750         }
751         foundReturn = true;
752     }
753     if (expectReturn && !foundReturn && !foundException) {
754         return methodParser.reportError<bool>("missing #Return marker");
755     }
756     const char* paren = methodParser.strnchr('(', methodParser.fEnd);
757     if (!paren) {
758         return methodParser.reportError<bool>("missing #Method function definition");
759     }
760     const char* nextEnd = paren;
761     do {
762         string paramName;
763         methodParser.fChar = nextEnd + 1;
764         methodParser.skipSpace();
765         if (!this->nextMethodParam(&methodParser, &nextEnd, &paramName)) {
766             continue;
767         }
768         bool foundParam = false;
769         for (auto& child : fChildren) {
770             if (MarkType::kParam != child->fMarkType) {
771                 continue;
772             }
773             if (paramName != child->fName) {
774                 continue;
775             }
776             if (child->fVisited) {
777                 return methodParser.reportError<bool>("multiple #Method param with same name");
778             }
779             child->fVisited = true;
780             if (foundParam) {
781                 TextParser paramError(child);
782                 return methodParser.reportError<bool>("multiple #Param with same name");
783             }
784             foundParam = true;
785 
786         }
787         if (!foundParam && !foundException) {
788             return methodParser.reportError<bool>("no #Param found");
789         }
790         if (')' == nextEnd[0]) {
791             break;
792         }
793     } while (')' != nextEnd[0]);
794     for (auto& child : fChildren) {
795         if (MarkType::kParam != child->fMarkType) {
796             continue;
797         }
798         if (!child->fVisited) {
799             TextParser paramError(child);
800             return paramError.reportError<bool>("#Param without param in #Method");
801         }
802     }
803     return true;
804 }
805 
crossCheck2(const Definition & includeToken) const806 bool Definition::crossCheck2(const Definition& includeToken) const {
807     TextParser parser(fFileName, fStart, fContentStart, fLineCount);
808     parser.skipExact("#");
809     bool isMethod = parser.skipName("Method");
810     const char* contentEnd;
811     if (isMethod) {
812         contentEnd = fContentStart;
813     } else if (parser.skipName("DefinedBy")) {
814         contentEnd = fContentEnd;
815         while (parser.fChar < contentEnd && ' ' >= contentEnd[-1]) {
816             --contentEnd;
817         }
818         if (parser.fChar < contentEnd - 1 && ')' == contentEnd[-1] && '(' == contentEnd[-2]) {
819             contentEnd -= 2;
820         }
821     } else {
822         return parser.reportError<bool>("unexpected crosscheck marktype");
823     }
824     return crossCheckInside(parser.fChar, contentEnd, includeToken);
825 }
826 
crossCheck(const Definition & includeToken) const827 bool Definition::crossCheck(const Definition& includeToken) const {
828     return crossCheckInside(fContentStart, fContentEnd, includeToken);
829 }
830 
crossCheckInside(const char * start,const char * end,const Definition & includeToken) const831 bool Definition::crossCheckInside(const char* start, const char* end,
832         const Definition& includeToken) const {
833     TextParser def(fFileName, start, end, fLineCount);
834     TextParser inc("", includeToken.fContentStart, includeToken.fContentEnd, 0);
835     if (inc.startsWith("SK_API")) {
836         inc.skipWord("SK_API");
837     }
838     if (inc.startsWith("friend")) {
839         inc.skipWord("friend");
840     }
841     if (inc.startsWith("SK_API")) {
842         inc.skipWord("SK_API");
843     }
844     inc.skipExact("SkDEBUGCODE(");
845     do {
846         bool defEof;
847         bool incEof;
848         do {
849             defEof = def.eof() || !def.skipWhiteSpace();
850             incEof = inc.eof() || !inc.skipWhiteSpace();
851             if (!incEof && '/' == inc.peek() && (defEof || '/' != def.peek())) {
852                 inc.next();
853                 if ('*' == inc.peek()) {
854                     inc.skipToEndBracket("*/");
855                     inc.next();
856                 } else if ('/' == inc.peek()) {
857                     inc.skipToEndBracket('\n');
858                 }
859             } else if (!incEof && '#' == inc.peek() && (defEof || '#' != def.peek())) {
860                 inc.next();
861                 if (inc.startsWith("if")) {
862                     inc.skipToEndBracket("\n");
863                 } else if (inc.startsWith("endif")) {
864                     inc.skipToEndBracket("\n");
865                 } else {
866                     SkASSERT(0); // incomplete
867                     return false;
868                 }
869             } else {
870                 break;
871             }
872             inc.next();
873         } while (true);
874         if (defEof || incEof) {
875             if (defEof == incEof || (!defEof && ';' == def.peek())) {
876                 return true;
877             }
878             return false;  // allow setting breakpoint on failure
879         }
880         char defCh;
881         do {
882             defCh = def.next();
883             char incCh = inc.next();
884             if (' ' >= defCh && ' ' >= incCh) {
885                 break;
886             }
887             if (defCh != incCh) {
888                 if ('_' != defCh || ' ' != incCh || !fOperatorConst || !def.startsWith("const")) {
889                     return false;
890                 }
891             }
892             if (';' == defCh) {
893                 return true;
894             }
895         } while (!def.eof() && !inc.eof());
896     } while (true);
897     return false;
898 }
899 
formatFunction(Format format) const900 string Definition::formatFunction(Format format) const {
901     const char* end = fContentStart;
902     while (end > fStart && ' ' >= end[-1]) {
903         --end;
904     }
905     TextParser methodParser(fFileName, fStart, end, fLineCount);
906     methodParser.skipWhiteSpace();
907     SkASSERT(methodParser.startsWith("#Method"));
908     methodParser.skipName("#Method");
909     methodParser.skipSpace();
910     const char* lastStart = methodParser.fChar;
911     const int limit = 100;  // todo: allow this to be set by caller or in global or something
912     string name = this->methodName();
913     const char* nameInParser = methodParser.strnstr(name.c_str(), methodParser.fEnd);
914     methodParser.skipTo(nameInParser);
915     const char* lastEnd = methodParser.fChar;
916     if (Format::kOmitReturn == format) {
917         lastStart = lastEnd;
918     }
919     const char* paren = methodParser.strnchr('(', methodParser.fEnd);
920     size_t indent;
921     if (paren) {
922         indent = (size_t) (paren - lastStart) + 1;
923     } else {
924         indent = (size_t) (lastEnd - lastStart);
925     }
926     // trim indent so longest line doesn't exceed box width
927     TextParser::Save savePlace(&methodParser);
928     const char* saveStart = lastStart;
929     ptrdiff_t maxLine = 0;
930     do {
931         const char* nextStart = lastEnd;
932         const char* delimiter = methodParser.anyOf(",)");
933         const char* nextEnd = delimiter ? delimiter : methodParser.fEnd;
934         if (delimiter) {
935             while (nextStart < nextEnd && ' ' >= nextStart[0]) {
936                 ++nextStart;
937             }
938         }
939         while (nextEnd > nextStart && ' ' >= nextEnd[-1]) {
940             --nextEnd;
941         }
942         if (delimiter) {
943             nextEnd += 1;
944             delimiter += 1;
945         }
946         if (lastEnd > lastStart) {
947             maxLine = SkTMax(maxLine, lastEnd - lastStart);
948         }
949         if (delimiter) {
950             methodParser.skipTo(delimiter);
951         }
952         lastStart = nextStart;
953         lastEnd = nextEnd;
954     } while (lastStart < lastEnd);
955     savePlace.restore();
956     lastStart = saveStart;
957     lastEnd = methodParser.fChar;
958     indent = SkTMin(indent, (size_t) (limit - maxLine));
959     // write string wtih trimmmed indent
960     string methodStr;
961     int written = 0;
962     do {
963         const char* nextStart = lastEnd;
964         SkASSERT(written < limit);
965         const char* delimiter = methodParser.anyOf(",)");
966         const char* nextEnd = delimiter ? delimiter : methodParser.fEnd;
967         if (delimiter) {
968             while (nextStart < nextEnd && ' ' >= nextStart[0]) {
969                 ++nextStart;
970             }
971         }
972         while (nextEnd > nextStart && ' ' >= nextEnd[-1]) {
973             --nextEnd;
974         }
975         if (delimiter) {
976             nextEnd += 1;
977             delimiter += 1;
978         }
979         if (lastEnd > lastStart) {
980             if (lastStart[0] != ' ') {
981                 space_pad(&methodStr);
982             }
983             methodStr += string(lastStart, (size_t) (lastEnd - lastStart));
984             written += (size_t) (lastEnd - lastStart);
985         }
986         if (delimiter) {
987             if (nextEnd - nextStart >= (ptrdiff_t) (limit - written)) {
988                 written = indent;
989                 if (Format::kIncludeReturn == format) {
990                     methodStr += '\n';
991                     methodStr += string(indent, ' ');
992                 }
993             }
994             methodParser.skipTo(delimiter);
995         }
996         lastStart = nextStart;
997         lastEnd = nextEnd;
998     } while (lastStart < lastEnd);
999     return methodStr;
1000 }
1001 
fiddleName() const1002 string Definition::fiddleName() const {
1003     string result;
1004     size_t start = 0;
1005     string parent;
1006     const Definition* parentDef = this;
1007     while ((parentDef = parentDef->fParent)) {
1008         if (MarkType::kClass == parentDef->fMarkType || MarkType::kStruct == parentDef->fMarkType) {
1009             parent = parentDef->fFiddle;
1010             break;
1011         }
1012     }
1013     if (parent.length() && 0 == fFiddle.compare(0, parent.length(), parent)) {
1014         start = parent.length();
1015         while (start < fFiddle.length() && '_' == fFiddle[start]) {
1016             ++start;
1017         }
1018     }
1019     size_t end = fFiddle.find_first_of('(', start);
1020     return fFiddle.substr(start, end - start);
1021 }
1022 
findClone(string match) const1023 const Definition* Definition::findClone(string match) const {
1024     for (auto child : fChildren) {
1025         if (!child->fClone) {
1026             continue;
1027         }
1028         if (match == child->fName) {
1029             return child;
1030         }
1031         auto inner = child->findClone(match);
1032         if (inner) {
1033             return inner;
1034         }
1035     }
1036     return nullptr;
1037 }
1038 
hasChild(MarkType markType) const1039 const Definition* Definition::hasChild(MarkType markType) const {
1040     for (auto iter : fChildren) {
1041         if (markType == iter->fMarkType) {
1042             return iter;
1043         }
1044     }
1045     return nullptr;
1046 }
1047 
hasParam(const string & ref) const1048 const Definition* Definition::hasParam(const string& ref) const {
1049     SkASSERT(MarkType::kMethod == fMarkType);
1050     for (auto iter : fChildren) {
1051         if (MarkType::kParam != iter->fMarkType) {
1052             continue;
1053         }
1054         if (iter->fName == ref) {
1055             return &*iter;
1056         }
1057 
1058     }
1059     return nullptr;
1060 }
1061 
hasMatch(const string & name) const1062 bool Definition::hasMatch(const string& name) const {
1063     for (auto child : fChildren) {
1064         if (name == child->fName) {
1065             return true;
1066         }
1067         if (child->hasMatch(name)) {
1068             return true;
1069         }
1070     }
1071     return false;
1072 }
1073 
isStructOrClass() const1074 bool Definition::isStructOrClass() const {
1075     if (MarkType::kStruct != fMarkType && MarkType::kClass != fMarkType) {
1076         return false;
1077     }
1078     if (string::npos != fFileName.find("undocumented.bmh")) {
1079         return false;
1080     }
1081     return true;
1082 }
1083 
methodHasReturn(const string & name,TextParser * methodParser) const1084 bool Definition::methodHasReturn(const string& name, TextParser* methodParser) const {
1085     if (methodParser->skipExact("static")) {
1086         methodParser->skipWhiteSpace();
1087     }
1088     const char* lastStart = methodParser->fChar;
1089     const char* nameInParser = methodParser->strnstr(name.c_str(), methodParser->fEnd);
1090     methodParser->skipTo(nameInParser);
1091     const char* lastEnd = methodParser->fChar;
1092     const char* returnEnd = lastEnd;
1093     while (returnEnd > lastStart && ' ' == returnEnd[-1]) {
1094         --returnEnd;
1095     }
1096     bool expectReturn = 4 != returnEnd - lastStart || strncmp("void", lastStart, 4);
1097     if (MethodType::kNone != fMethodType && MethodType::kOperator != fMethodType && !expectReturn) {
1098         return methodParser->reportError<bool>("unexpected void");
1099     }
1100     switch (fMethodType) {
1101         case MethodType::kNone:
1102         case MethodType::kOperator:
1103             // either is fine
1104             break;
1105         case MethodType::kConstructor:
1106             expectReturn = true;
1107             break;
1108         case MethodType::kDestructor:
1109             expectReturn = false;
1110             break;
1111     }
1112     return expectReturn;
1113 }
1114 
methodName() const1115 string Definition::methodName() const {
1116     string result;
1117     size_t start = 0;
1118     string parent;
1119     const Definition* parentDef = this;
1120     while ((parentDef = parentDef->fParent)) {
1121         if (MarkType::kClass == parentDef->fMarkType || MarkType::kStruct == parentDef->fMarkType) {
1122             parent = parentDef->fName;
1123             break;
1124         }
1125     }
1126     if (parent.length() && 0 == fName.compare(0, parent.length(), parent)) {
1127         start = parent.length();
1128         while (start < fName.length() && ':' == fName[start]) {
1129             ++start;
1130         }
1131     }
1132     if (fClone) {
1133         int lastUnder = fName.rfind('_');
1134         return fName.substr(start, (size_t) (lastUnder - start));
1135     }
1136     size_t end = fName.find_first_of('(', start);
1137     if (string::npos == end) {
1138         return fName.substr(start);
1139     }
1140     return fName.substr(start, end - start);
1141 }
1142 
nextMethodParam(TextParser * methodParser,const char ** nextEndPtr,string * paramName) const1143 bool Definition::nextMethodParam(TextParser* methodParser, const char** nextEndPtr,
1144         string* paramName) const {
1145     int parenCount = 0;
1146     TextParser::Save saveState(methodParser);
1147     while (true) {
1148         if (methodParser->eof()) {
1149             return methodParser->reportError<bool>("#Method function missing close paren");
1150         }
1151         char ch = methodParser->peek();
1152         if ('(' == ch) {
1153             ++parenCount;
1154         }
1155         if (parenCount == 0 && (')' == ch || ',' == ch)) {
1156             *nextEndPtr = methodParser->fChar;
1157             break;
1158         }
1159         if (')' == ch) {
1160             if (0 > --parenCount) {
1161                 return this->reportError<bool>("mismatched parentheses");
1162             }
1163         }
1164         methodParser->next();
1165     }
1166     saveState.restore();
1167     const char* nextEnd = *nextEndPtr;
1168     const char* paramEnd = nextEnd;
1169     const char* assign = methodParser->strnstr(" = ", paramEnd);
1170     if (assign) {
1171         paramEnd = assign;
1172     }
1173     const char* closeBracket = methodParser->strnstr("]", paramEnd);
1174     if (closeBracket) {
1175         const char* openBracket = methodParser->strnstr("[", paramEnd);
1176         if (openBracket && openBracket < closeBracket) {
1177             while (openBracket < --closeBracket && isdigit(closeBracket[0]))
1178                 ;
1179             if (openBracket == closeBracket) {
1180                 paramEnd = openBracket;
1181             }
1182         }
1183     }
1184     const char* function = methodParser->strnstr(")(", paramEnd);
1185     if (function) {
1186         paramEnd = function;
1187     }
1188     while (paramEnd > methodParser->fChar && ' ' == paramEnd[-1]) {
1189         --paramEnd;
1190     }
1191     const char* paramStart = paramEnd;
1192     while (paramStart > methodParser->fChar && isalnum(paramStart[-1])) {
1193         --paramStart;
1194     }
1195     if (paramStart > methodParser->fChar && paramStart >= paramEnd) {
1196         return methodParser->reportError<bool>("#Method missing param name");
1197     }
1198     *paramName = string(paramStart, paramEnd - paramStart);
1199     if (!paramName->length()) {
1200         if (')' != nextEnd[0]) {
1201             return methodParser->reportError<bool>("#Method malformed param");
1202         }
1203         return false;
1204     }
1205     return true;
1206 }
1207 
NormalizedName(string name)1208 string Definition::NormalizedName(string name) {
1209     string normalizedName = name;
1210     std::replace(normalizedName.begin(), normalizedName.end(), '-', '_');
1211     do {
1212         size_t doubleColon = normalizedName.find("::", 0);
1213         if (string::npos == doubleColon) {
1214             break;
1215         }
1216         normalizedName = normalizedName.substr(0, doubleColon)
1217             + '_' + normalizedName.substr(doubleColon + 2);
1218     } while (true);
1219     return normalizedName;
1220 }
1221 
unpreformat(const string & orig)1222 static string unpreformat(const string& orig) {
1223     string result;
1224     int amp = 0;
1225     for (auto c : orig) {
1226         switch (amp) {
1227         case 0:
1228             if ('&' == c) {
1229                 amp = 1;
1230             } else {
1231                 amp = 0;
1232                 result += c;
1233             }
1234             break;
1235         case 1:
1236             if ('l' == c) {
1237                 amp = 2;
1238             } else if ('g' == c) {
1239                 amp = 3;
1240             } else {
1241                 amp = 0;
1242                 result += "&";
1243                 result += c;
1244             }
1245             break;
1246         case 2:
1247             if ('t' == c) {
1248                 amp = 4;
1249             } else {
1250                 amp = 0;
1251                 result += "&l";
1252                 result += c;
1253             }
1254             break;
1255         case 3:
1256             if ('t' == c) {
1257                 amp = 5;
1258             } else {
1259                 amp = 0;
1260                 result += "&g";
1261                 result += c;
1262             }
1263             break;
1264         case 4:
1265             if (';' == c) {
1266                 result += '<';
1267             } else {
1268                 result += "&lt";
1269                 result += c;
1270             }
1271             amp = 0;
1272             break;
1273         case 5:
1274             if (';' == c) {
1275                 result += '>';
1276             } else {
1277                 result += "&gt";
1278                 result += c;
1279             }
1280             amp = 0;
1281             break;
1282         }
1283     }
1284     return result;
1285 }
1286 
paramsMatch(const string & matchFormatted,const string & name) const1287 bool Definition::paramsMatch(const string& matchFormatted, const string& name) const {
1288     string match = unpreformat(matchFormatted);
1289     TextParser def(fFileName, fStart, fContentStart, fLineCount);
1290     const char* dName = def.strnstr(name.c_str(), fContentStart);
1291     if (!dName) {
1292         return false;
1293     }
1294     def.skipTo(dName);
1295     TextParser m(fFileName, &match.front(), &match.back() + 1, fLineCount);
1296     const char* mName = m.strnstr(name.c_str(), m.fEnd);
1297     if (!mName) {
1298         return false;
1299     }
1300     m.skipTo(mName);
1301     while (!def.eof() && ')' != def.peek() && !m.eof() && ')' != m.peek()) {
1302         const char* ds = def.fChar;
1303         const char* ms = m.fChar;
1304         const char* de = def.anyOf(") \n");
1305         const char* me = m.anyOf(") \n");
1306         def.skipTo(de);
1307         m.skipTo(me);
1308         if (def.fChar - ds != m.fChar - ms) {
1309             return false;
1310         }
1311         if (strncmp(ds, ms, (int) (def.fChar - ds))) {
1312             return false;
1313         }
1314         def.skipWhiteSpace();
1315         m.skipWhiteSpace();
1316     }
1317     return !def.eof() && ')' == def.peek() && !m.eof() && ')' == m.peek();
1318 }
1319 
clearVisited()1320 void RootDefinition::clearVisited() {
1321     fVisited = false;
1322     for (auto& leaf : fLeaves) {
1323         leaf.second.fVisited = false;
1324     }
1325     for (auto& branch : fBranches) {
1326         branch.second->clearVisited();
1327     }
1328 }
1329 
dumpUnVisited()1330 bool RootDefinition::dumpUnVisited() {
1331     bool success = true;
1332     for (auto& leaf : fLeaves) {
1333         if (!leaf.second.fVisited) {
1334             // FIXME: bugs requiring long tail fixes, suppressed here:
1335             // SkBitmap::validate() is wrapped in SkDEBUGCODE in .h and not parsed
1336             if ("SkBitmap::validate()" == leaf.first) {
1337                 continue;
1338             }
1339             // SkPath::pathRefIsValid in #ifdef ; prefer to remove chrome dependency to fix
1340             if ("SkPath::pathRefIsValid" == leaf.first) {
1341                 continue;
1342             }
1343             // FIXME: end of long tail bugs
1344             SkDebugf("defined in bmh but missing in include: %s\n", leaf.first.c_str());
1345             success = false;
1346         }
1347     }
1348     for (auto& branch : fBranches) {
1349         success &= branch.second->dumpUnVisited();
1350     }
1351     return success;
1352 }
1353 
find(const string & ref,AllowParens allowParens) const1354 const Definition* RootDefinition::find(const string& ref, AllowParens allowParens) const {
1355     const auto leafIter = fLeaves.find(ref);
1356     if (leafIter != fLeaves.end()) {
1357         return &leafIter->second;
1358     }
1359     if (AllowParens::kYes == allowParens && string::npos == ref.find("()")) {
1360         string withParens = ref + "()";
1361         const auto parensIter = fLeaves.find(withParens);
1362         if (parensIter != fLeaves.end()) {
1363             return &parensIter->second;
1364         }
1365     }
1366     const auto branchIter = fBranches.find(ref);
1367     if (branchIter != fBranches.end()) {
1368         const RootDefinition* rootDef = branchIter->second;
1369         return rootDef;
1370     }
1371     const Definition* result = nullptr;
1372     for (const auto& branch : fBranches) {
1373         const RootDefinition* rootDef = branch.second;
1374         result = rootDef->find(ref, allowParens);
1375         if (result) {
1376             break;
1377         }
1378     }
1379     return result;
1380 }
1381