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 "SkOSPath.h"
9 
10 #include "definition.h"
11 #include "textParser.h"
12 
13 #ifdef CONST
14 #undef CONST
15 #endif
16 
17 #ifdef FRIEND
18 #undef FRIEND
19 #endif
20 
21 #ifdef BLANK
22 #undef BLANK
23 #endif
24 
25 #ifdef ANY
26 #undef ANY
27 #endif
28 
29 #ifdef DEFOP
30 #undef DEFOP
31 #endif
32 
33 #define CONST 1
34 #define STATIC 2
35 #define BLANK  0
36 #define ANY -1
37 #define DEFOP Definition::Operator
38 
39 enum class OpType : int8_t {
40     kNone,
41     kVoid,
42     kBool,
43     kChar,
44     kInt,
45     kScalar,
46     kSizeT,
47     kThis,
48     kAny,
49 };
50 
51 enum class OpMod : int8_t {
52     kNone,
53     kArray,
54     kMove,
55     kPointer,
56     kReference,
57     kAny,
58 };
59 
60 const struct OperatorParser {
61     DEFOP fOperator;
62     const char* fSymbol;
63     const char* fName;
64     int8_t fFriend;
65     OpType fReturnType;
66     OpMod fReturnMod;
67     int8_t fConstMethod;
68     struct Param {
69         int8_t fConst;
70         OpType fType;
71         OpMod fMod;
72     } fParams[2];
73 } opData[] = {
74     { DEFOP::kUnknown, "??", "???",    BLANK,  OpType::kNone,   OpMod::kNone,         BLANK,
75                                     { } },
76     { DEFOP::kAdd,     "+",  "add",    BLANK,  OpType::kThis,   OpMod::kNone,         BLANK,
77                                     {{ CONST,  OpType::kThis,   OpMod::kReference, },
78                                      { CONST,  OpType::kThis,   OpMod::kReference, }}},
79     { DEFOP::kAddTo,   "+=", "addto",  BLANK,  OpType::kVoid,   OpMod::kNone,         BLANK,
80                                     {{ CONST,  OpType::kThis,   OpMod::kReference, }}},
81     { DEFOP::kAddTo,   "+=", "addto1", BLANK,  OpType::kThis,   OpMod::kReference,    BLANK,
82                                     {{ CONST,  OpType::kThis,   OpMod::kReference, }}},
83     { DEFOP::kAddTo,   "+=", "addto2", BLANK,  OpType::kThis,   OpMod::kReference,    BLANK,
84                                     {{ CONST,  OpType::kChar,   OpMod::kArray, }}},
85     { DEFOP::kAddTo,   "+=", "addto3", BLANK,  OpType::kThis,   OpMod::kReference,    BLANK,
86                                     {{ CONST,  OpType::kChar,   OpMod::kNone, }}},
87     { DEFOP::kArray,   "[]", "array",  BLANK,  OpType::kScalar, OpMod::kNone,         CONST,
88                                     {{ BLANK,  OpType::kInt,    OpMod::kNone, }}},
89     { DEFOP::kArray,   "[]", "array1", BLANK,  OpType::kScalar, OpMod::kReference,    BLANK,
90                                     {{ BLANK,  OpType::kInt,    OpMod::kNone, }}},
91     { DEFOP::kArray,   "[]", "array2", BLANK,  OpType::kChar,   OpMod::kNone,         CONST,
92                                     {{ BLANK,  OpType::kSizeT,  OpMod::kNone, }}},
93     { DEFOP::kArray,   "[]", "array3", BLANK,  OpType::kChar,   OpMod::kReference,    BLANK,
94                                     {{ BLANK,  OpType::kSizeT,  OpMod::kNone, }}},
95     { DEFOP::kCast,    "()", "cast",   BLANK,  OpType::kAny,    OpMod::kAny,          ANY,
96                                     {{ ANY,    OpType::kAny,    OpMod::kAny,  }}},
97     { DEFOP::kCopy,    "=",  "copy",   BLANK,  OpType::kThis,   OpMod::kReference,    BLANK,
98                                     {{ CONST,  OpType::kThis,   OpMod::kReference, }}},
99     { DEFOP::kCopy,    "=",  "copy1",  BLANK,  OpType::kThis,   OpMod::kReference,    BLANK,
100                                     {{ CONST,  OpType::kChar,   OpMod::kArray, }}},
101     { DEFOP::kDelete,  "delete", "delete",  BLANK,  OpType::kVoid,   OpMod::kNone,    BLANK,
102                                     {{ BLANK,  OpType::kVoid,   OpMod::kPointer, }}},
103     { DEFOP::kDereference, "->", "deref",  ANY,  OpType::kThis, OpMod::kPointer,      CONST,
104                                     { } },
105     { DEFOP::kDereference, "*", "deref", BLANK,  OpType::kThis, OpMod::kReference,    CONST,
106                                     { } },
107     { DEFOP::kEqual,   "==", "equal",  BLANK,  OpType::kBool,   OpMod::kNone,         BLANK,
108                                     {{ CONST,  OpType::kThis,   OpMod::kReference, },
109                                      { CONST,  OpType::kThis,   OpMod::kReference, }}},
110     { DEFOP::kEqual,   "==", "equal1",  BLANK,  OpType::kBool,   OpMod::kNone,         CONST,
111                                     {{ CONST,  OpType::kThis,   OpMod::kReference, }}},
112     { DEFOP::kEqual,   "==", "equal2", ANY,    OpType::kBool,   OpMod::kNone,         BLANK,
113                                     {{ CONST,  OpType::kThis,   OpMod::kReference, },
114                                      { CONST,  OpType::kThis,   OpMod::kReference, }}},
115     { DEFOP::kMinus,   "-",  "minus",  BLANK,  OpType::kThis,   OpMod::kNone,         CONST,
116                                     { } },
117     { DEFOP::kMove,    "=",  "move",   BLANK,  OpType::kThis,   OpMod::kReference,    BLANK,
118                                     {{ BLANK,  OpType::kThis,   OpMod::kMove, }}},
119     { DEFOP::kMultiply, "*", "multiply", BLANK, OpType::kThis, OpMod::kNone,         CONST,
120                                     {{ BLANK,  OpType::kScalar, OpMod::kNone, }}},
121     { DEFOP::kMultiply, "*", "multiply1", BLANK, OpType::kThis, OpMod::kNone,         CONST,
122                                     {{ CONST,  OpType::kThis,   OpMod::kReference, }}},
123     { DEFOP::kMultiply, "*", "multiply2", BLANK, OpType::kThis, OpMod::kNone,         BLANK,
124                                     {{ CONST,  OpType::kThis,   OpMod::kReference, },
125                                      { CONST,  OpType::kThis,   OpMod::kReference, }}},
126     { DEFOP::kMultiplyBy, "*=", "multiplyby", BLANK,  OpType::kThis, OpMod::kReference, BLANK,
127                                     {{ BLANK,  OpType::kScalar, OpMod::kNone, }}},
128     { DEFOP::kNew,     "new", "new",   BLANK,  OpType::kVoid,   OpMod::kPointer,      BLANK,
129                                     {{ BLANK,  OpType::kSizeT,  OpMod::kNone, }}},
130     { DEFOP::kNotEqual, "!=", "notequal", BLANK, OpType::kBool,   OpMod::kNone,      BLANK,
131                                     {{ CONST,  OpType::kThis,   OpMod::kReference, },
132                                      { CONST,  OpType::kThis,   OpMod::kReference, }}},
133     { DEFOP::kNotEqual, "!=", "notequal1", BLANK,  OpType::kBool,   OpMod::kNone,     CONST,
134                                     {{ CONST,  OpType::kThis,   OpMod::kReference, }}},
135     { DEFOP::kNotEqual, "!=", "notequal2", ANY, OpType::kBool,   OpMod::kNone,     BLANK,
136                                     {{ CONST,  OpType::kThis,   OpMod::kReference, },
137                                      { CONST,  OpType::kThis,   OpMod::kReference, }}},
138     { DEFOP::kSubtract, "-", "subtract", BLANK, OpType::kThis, OpMod::kNone,         BLANK,
139                                     {{ CONST,  OpType::kThis,   OpMod::kReference, },
140                                      { CONST,  OpType::kThis,   OpMod::kReference, }}},
141     { DEFOP::kSubtractFrom, "-=", "subtractfrom",  BLANK,  OpType::kVoid,   OpMod::kNone, BLANK,
142                                     {{ CONST,  OpType::kThis,   OpMod::kReference, }}},
143 };
144 
lookup_type(string typeWord,string name)145 OpType lookup_type(string typeWord, string name) {
146     if (typeWord == name || (typeWord == "SkIVector" && name == "SkIPoint")
147                          || (typeWord == "SkVector" && name == "SkPoint")) {
148         return OpType::kThis;
149     }
150     if ("float" == typeWord || "double" == typeWord) {
151         return OpType::kScalar;
152     }
153     const char* keyWords[] = { "void", "bool", "char", "int", "SkScalar", "size_t" };
154     for (unsigned i = 0; i < SK_ARRAY_COUNT(keyWords); ++i) {
155         if (typeWord == keyWords[i]) {
156             return (OpType) (i + 1);
157         }
158     }
159     return OpType::kNone;
160 }
161 
lookup_mod(TextParser & iParser)162 OpMod lookup_mod(TextParser& iParser) {
163     OpMod mod = OpMod::kNone;
164     if ('&' == iParser.peek()) {
165         mod = OpMod::kReference;
166         iParser.next();
167         if ('&' == iParser.peek()) {
168             mod = OpMod::kMove;
169             iParser.next();
170         }
171     } else if ('*' == iParser.peek()) {
172         mod = OpMod::kPointer;
173         iParser.next();
174     }
175     iParser.skipWhiteSpace();
176     return mod;
177 }
178 
parseOperator(size_t doubleColons,string & result)179 bool Definition::parseOperator(size_t doubleColons, string& result) {
180     const char operatorStr[] = "operator";
181     size_t opPos = fName.find(operatorStr, doubleColons);
182     if (string::npos == opPos) {
183         return false;
184     }
185     string className(fName, 0, doubleColons - 2);
186     TextParser iParser(fFileName, fStart, fContentStart, fLineCount);
187     SkAssertResult(iParser.skipWord("#Method"));
188     iParser.skipWhiteSpace();
189     bool isStatic = iParser.skipExact("static");
190     iParser.skipWhiteSpace();
191     bool returnsConst = iParser.skipExact("const");
192     if (returnsConst) {
193         SkASSERT(0);  // incomplete
194     }
195     SkASSERT(isStatic == false || returnsConst == false);
196     iParser.skipWhiteSpace();
197     const char* returnTypeStart = iParser.fChar;
198     iParser.skipToNonName();
199     SkASSERT(iParser.fChar > returnTypeStart);
200     string returnType(returnTypeStart, iParser.fChar - returnTypeStart);
201     OpType returnOpType = lookup_type(returnType, className);
202     iParser.skipWhiteSpace();
203     OpMod returnMod = lookup_mod(iParser);
204     SkAssertResult(iParser.skipExact("operator"));
205     iParser.skipWhiteSpace();
206     fMethodType = Definition::MethodType::kOperator;
207     TextParserSave save(&iParser);
208     for (auto parser : opData) {
209         save.restore();
210         if (!iParser.skipExact(parser.fSymbol)) {
211             continue;
212         }
213         iParser.skipWhiteSpace();
214         if ('(' != iParser.peek()) {
215             continue;
216         }
217         if (parser.fFriend != ANY && (parser.fFriend == STATIC) != isStatic) {
218             continue;
219         }
220         if (parser.fReturnType != OpType::kAny && parser.fReturnType != returnOpType) {
221             continue;
222         }
223         if (parser.fReturnMod != OpMod::kAny && parser.fReturnMod != returnMod) {
224             continue;
225         }
226         iParser.next();  // skip '('
227         iParser.skipWhiteSpace();
228         int parserCount = (parser.fParams[0].fType != OpType::kNone) +
229             (parser.fParams[1].fType != OpType::kNone);
230         bool countsMatch = true;
231         for (int pIndex = 0; pIndex < 2; ++pIndex) {
232             if (')' == iParser.peek()) {
233                 countsMatch = pIndex == parserCount;
234                 break;
235             }
236             if (',' == iParser.peek()) {
237                 iParser.next();
238                 iParser.skipWhiteSpace();
239             }
240             bool paramConst = iParser.skipExact("const");
241             if (parser.fParams[pIndex].fConst != ANY &&
242                     paramConst != (parser.fParams[pIndex].fConst == CONST)) {
243                 countsMatch = false;
244                 break;
245             }
246             iParser.skipWhiteSpace();
247             const char* paramStart = iParser.fChar;
248             iParser.skipToNonName();
249             SkASSERT(iParser.fChar > paramStart);
250             string paramType(paramStart, iParser.fChar - paramStart);
251             OpType paramOpType = lookup_type(paramType, className);
252             if (parser.fParams[pIndex].fType != OpType::kAny &&
253                     parser.fParams[pIndex].fType != paramOpType) {
254                 countsMatch = false;
255                 break;
256             }
257             iParser.skipWhiteSpace();
258             OpMod paramMod = lookup_mod(iParser);
259             if (parser.fParams[pIndex].fMod != OpMod::kAny &&
260                     parser.fParams[pIndex].fMod != paramMod) {
261                 countsMatch = false;
262                 break;
263             }
264             iParser.skipToNonName();
265             if ('[' == iParser.peek()) {
266                 paramMod = OpMod::kArray;
267                 SkAssertResult(iParser.skipExact("[]"));
268             }
269             iParser.skipWhiteSpace();
270         }
271         if (!countsMatch) {
272             continue;
273         }
274         if (')' != iParser.peek()) {
275             continue;
276         }
277         iParser.next();
278         bool constMethod = iParser.skipExact(" const");
279         if (parser.fConstMethod != ANY && (parser.fConstMethod == CONST) != constMethod) {
280             continue;
281         }
282         result += parser.fName;
283         result += "_operator";
284         fOperator = parser.fOperator;
285         fOperatorConst = constMethod;
286         return true;
287     }
288     SkASSERT(0); // incomplete
289     return false;
290 }
291 
292 #undef CONST
293 #undef FRIEND
294 #undef BLANK
295 #undef DEFOP
296 
boilerplateIfDef()297 bool Definition::boilerplateIfDef() {
298     const Definition& label = fTokens.front();
299     if (Type::kWord != label.fType) {
300         return false;
301     }
302     fName = string(label.fContentStart, label.fContentEnd - label.fContentStart);
303     return true;
304 }
305 
306 
307 // fixme: this will need to be more complicated to handle all of Skia
308 // for now, just handle paint -- maybe fiddle will loosen naming restrictions
setCanonicalFiddle()309 void Definition::setCanonicalFiddle() {
310     fMethodType = Definition::MethodType::kNone;
311     size_t doubleColons = fName.rfind("::");
312     SkASSERT(string::npos != doubleColons);
313     string base = fName.substr(0, doubleColons);
314     string result = base + "_";
315     doubleColons += 2;
316     if (string::npos != fName.find('~', doubleColons)) {
317         fMethodType = Definition::MethodType::kDestructor;
318         result += "destructor";
319     } else if (!this->parseOperator(doubleColons, result)) {
320         bool isMove = string::npos != fName.find("&&", doubleColons);
321         size_t parens = fName.find("()", doubleColons);
322         if (string::npos != parens) {
323             string methodName = fName.substr(doubleColons, parens - doubleColons);
324             do {
325                 size_t nextDouble = methodName.find("::");
326                 if (string::npos == nextDouble) {
327                     break;
328                 }
329                 base = methodName.substr(0, nextDouble);
330                 result += base + '_';
331                 methodName = methodName.substr(nextDouble + 2);
332                 doubleColons += nextDouble + 2;
333             } while (true);
334             if (base == methodName) {
335                 fMethodType = Definition::MethodType::kConstructor;
336                 result += "empty_constructor";
337             } else {
338                 result += fName.substr(doubleColons, fName.length() - doubleColons - 2);
339             }
340         } else {
341             size_t openParen = fName.find('(', doubleColons);
342             if (string::npos == openParen) {
343                 result += fName.substr(doubleColons);
344                 // see if it is a constructor -- if second to last delimited name equals last
345                 size_t nextColons = fName.find("::", doubleColons);
346                 if (string::npos != nextColons) {
347                     nextColons += 2;
348                     if (!strncmp(&fName[doubleColons], &fName[nextColons],
349                             nextColons - doubleColons - 2)) {
350                         fMethodType = Definition::MethodType::kConstructor;
351                     }
352                 }
353             } else {
354                 size_t comma = fName.find(',', doubleColons);
355                 if (string::npos == comma) {
356                     result += isMove ? "move_" : "copy_";
357                 }
358                 fMethodType = Definition::MethodType::kConstructor;
359                 // name them by their param types,
360                 //   e.g. SkCanvas__int_int_const_SkSurfaceProps_star
361                 // TODO: move forward until parens are balanced and terminator =,)
362                 TextParser params("", &fName[openParen] + 1, &*fName.end(), 0);
363                 bool underline = false;
364                 while (!params.eof()) {
365 //                    SkDEBUGCODE(const char* end = params.anyOf("(),="));  // unused for now
366 //                    SkASSERT(end[0] != '(');  // fixme: put off handling nested parentheseses
367                     if (params.startsWith("const") || params.startsWith("int")
368                             || params.startsWith("Sk")) {
369                         const char* wordStart = params.fChar;
370                         params.skipToNonName();
371                         if (underline) {
372                             result += '_';
373                         } else {
374                             underline = true;
375                         }
376                         result += string(wordStart, params.fChar - wordStart);
377                     } else {
378                         params.skipToNonName();
379                     }
380                     if (!params.eof() && '*' == params.peek()) {
381                         if (underline) {
382                             result += '_';
383                         } else {
384                             underline = true;
385                         }
386                         result += "star";
387                         params.next();
388                         params.skipSpace();
389                     }
390                     params.skipToAlpha();
391                 }
392             }
393         }
394     }
395     fFiddle = Definition::NormalizedName(result);
396 }
397 
space_pad(string * str)398 static void space_pad(string* str) {
399     size_t len = str->length();
400     if (len == 0) {
401         return;
402     }
403     char last = (*str)[len - 1];
404     if ('~' == last || ' ' >= last) {
405         return;
406     }
407     *str += ' ';
408 }
409 
410 //start here;
411 // see if it possible to abstract this a little bit so it can
412 // additionally be used to find params and return in method prototype that
413 // does not have corresponding doxygen comments
checkMethod() const414 bool Definition::checkMethod() const {
415     SkASSERT(MarkType::kMethod == fMarkType);
416     // if method returns a value, look for a return child
417     // for each parameter, look for a corresponding child
418     const char* end = fContentStart;
419     while (end > fStart && ' ' >= end[-1]) {
420         --end;
421     }
422     TextParser methodParser(fFileName, fStart, end, fLineCount);
423     methodParser.skipWhiteSpace();
424     SkASSERT(methodParser.startsWith("#Method"));
425     methodParser.skipName("#Method");
426     methodParser.skipSpace();
427     string name = this->methodName();
428     if (MethodType::kNone == fMethodType && name.length() > 2 &&
429             "()" == name.substr(name.length() - 2)) {
430         name = name.substr(0, name.length() - 2);
431     }
432     bool expectReturn = this->methodHasReturn(name, &methodParser);
433     bool foundReturn = false;
434     bool foundPopulate = false;
435     for (auto& child : fChildren) {
436         foundPopulate |= MarkType::kPopulate == child->fMarkType;
437         if (MarkType::kReturn != child->fMarkType) {
438             if (MarkType::kParam == child->fMarkType) {
439                 child->fVisited = false;
440             }
441             continue;
442         }
443         if (!expectReturn) {
444             return methodParser.reportError<bool>("no #Return expected");
445         }
446         if (foundReturn) {
447             return methodParser.reportError<bool>("multiple #Return markers");
448         }
449         foundReturn = true;
450     }
451     if (expectReturn && !foundReturn && !foundPopulate) {
452         return methodParser.reportError<bool>("missing #Return marker");
453     }
454     const char* paren = methodParser.strnchr('(', methodParser.fEnd);
455     if (!paren) {
456         return methodParser.reportError<bool>("missing #Method function definition");
457     }
458     const char* nextEnd = paren;
459     do {
460         string paramName;
461         methodParser.fChar = nextEnd + 1;
462         methodParser.skipSpace();
463         if (!this->nextMethodParam(&methodParser, &nextEnd, &paramName)) {
464             continue;
465         }
466         bool foundParam = false;
467         for (auto& child : fChildren) {
468             if (MarkType::kParam != child->fMarkType) {
469                 continue;
470             }
471             if (paramName != child->fName) {
472                 continue;
473             }
474             if (child->fVisited) {
475                 return methodParser.reportError<bool>("multiple #Method param with same name");
476             }
477             child->fVisited = true;
478             if (foundParam) {
479                 TextParser paramError(child);
480                 return methodParser.reportError<bool>("multiple #Param with same name");
481             }
482             foundParam = true;
483 
484         }
485         if (!foundParam && !foundPopulate) {
486             return methodParser.reportError<bool>("no #Param found");
487         }
488         if (')' == nextEnd[0]) {
489             break;
490         }
491     } while (')' != nextEnd[0]);
492     for (auto& child : fChildren) {
493         if (MarkType::kParam != child->fMarkType) {
494             continue;
495         }
496         if (!child->fVisited) {
497             TextParser paramError(child);
498             return paramError.reportError<bool>("#Param without param in #Method");
499         }
500     }
501     // check after end of #Line and before next child for description
502     const char* descStart = fContentStart;
503     const char* descEnd = nullptr;
504     const Definition* defEnd = nullptr;
505     const Definition* priorDef = nullptr;
506     bool incomplete = false;
507     for (auto& child : fChildren) {
508         if (MarkType::kAnchor == child->fMarkType) {
509             continue;
510         }
511         if (MarkType::kCode == child->fMarkType) {
512             priorDef = child;
513             continue;
514         }
515         if (MarkType::kFormula == child->fMarkType) {
516             continue;
517         }
518         if (MarkType::kLine == child->fMarkType) {
519             SkASSERT(child->fChildren.size() > 0);
520             TextParser childDesc(child->fChildren[0]);
521             incomplete |= childDesc.startsWith("incomplete");
522         }
523         if (MarkType::kList == child->fMarkType) {
524             priorDef = child;
525             continue;
526         }
527         if (MarkType::kMarkChar == child->fMarkType) {
528             continue;
529         }
530         if (MarkType::kPhraseRef == child->fMarkType) {
531             continue;
532         }
533         TextParser emptyCheck(fFileName, descStart, child->fStart, child->fLineCount);
534         if (!emptyCheck.eof() && emptyCheck.skipWhiteSpace()) {
535             descStart = emptyCheck.fChar;
536             emptyCheck.trimEnd();
537             defEnd = priorDef;
538             descEnd = emptyCheck.fEnd;
539             break;
540         }
541         descStart = child->fTerminator;
542         priorDef = nullptr;
543     }
544     if (!descEnd) {
545         return incomplete || foundPopulate ? true :
546                 methodParser.reportError<bool>("missing description");
547     }
548     TextParser description(fFileName, descStart, descEnd, fLineCount);
549     // expect first word capitalized and pluralized. expect a trailing period
550     SkASSERT(descStart < descEnd);
551     if (!isupper(descStart[0])) {
552         description.reportWarning("expected capital");
553     } else if ('.' != descEnd[-1]) {
554         if (!defEnd || defEnd->fTerminator != descEnd) {
555             if (!incomplete) {
556                 description.reportWarning("expected period");
557             }
558         }
559     } else {
560         if (!description.startsWith("For use by Android")) {
561             description.skipToSpace();
562             if (',' == description.fChar[-1]) {
563                 --description.fChar;
564             }
565             if ('s' != description.fChar[-1]) {
566                 if (!incomplete) {
567                     description.reportWarning("expected plural");
568                 }
569             }
570         }
571     }
572     return true;
573 }
574 
crossCheck2(const Definition & includeToken) const575 bool Definition::crossCheck2(const Definition& includeToken) const {
576     TextParser parser(fFileName, fStart, fContentStart, fLineCount);
577     parser.skipExact("#");
578     bool isMethod = parser.skipName("Method");
579     const char* contentEnd;
580     if (isMethod) {
581         contentEnd = fContentStart;
582     } else if (parser.skipName("DefinedBy")) {
583         contentEnd = fContentEnd;
584         while (parser.fChar < contentEnd && ' ' >= contentEnd[-1]) {
585             --contentEnd;
586         }
587         if (parser.fChar < contentEnd - 1 && ')' == contentEnd[-1] && '(' == contentEnd[-2]) {
588             contentEnd -= 2;
589         }
590     } else {
591         return parser.reportError<bool>("unexpected crosscheck marktype");
592     }
593     return crossCheckInside(parser.fChar, contentEnd, includeToken);
594 }
595 
crossCheck(const Definition & includeToken) const596 bool Definition::crossCheck(const Definition& includeToken) const {
597     return crossCheckInside(fContentStart, fContentEnd, includeToken);
598 }
599 
methodEnd() const600 const char* Definition::methodEnd() const {
601     const char defaultTag[] = " = default";
602     size_t tagSize = sizeof(defaultTag) - 1;
603     const char* tokenEnd = fContentEnd - tagSize;
604     if (tokenEnd <= fContentStart || strncmp(tokenEnd, defaultTag, tagSize)) {
605         tokenEnd = fContentEnd;
606     }
607     return tokenEnd;
608 }
609 
SkipImplementationWords(TextParser & inc)610 bool Definition::SkipImplementationWords(TextParser& inc) {
611     if (inc.startsWith("SK_API")) {
612         inc.skipWord("SK_API");
613     }
614     if (inc.startsWith("inline")) {
615         inc.skipWord("inline");
616     }
617     if (inc.startsWith("friend")) {
618         inc.skipWord("friend");
619     }
620     if (inc.startsWith("SK_API")) {
621         inc.skipWord("SK_API");
622     }
623     return inc.skipExact("SkDEBUGCODE(");
624 }
625 
crossCheckInside(const char * start,const char * end,const Definition & includeToken) const626 bool Definition::crossCheckInside(const char* start, const char* end,
627         const Definition& includeToken) const {
628     TextParser def(fFileName, start, end, fLineCount);
629     const char* tokenEnd = includeToken.methodEnd();
630     TextParser inc("", includeToken.fContentStart, tokenEnd, 0);
631     if (inc.startsWith("static")) {
632         def.skipWhiteSpace();
633         if (!def.startsWith("static")) {
634             return false;
635         }
636         inc.skipWord("static");
637         def.skipWord("static");
638     }
639     (void) Definition::SkipImplementationWords(inc);
640     do {
641         bool defEof;
642         bool incEof;
643         do {
644             defEof = def.eof() || !def.skipWhiteSpace();
645             incEof = inc.eof() || !inc.skipWhiteSpace();
646             if (!incEof && '/' == inc.peek() && (defEof || '/' != def.peek())) {
647                 inc.next();
648                 if ('*' == inc.peek()) {
649                     inc.skipToEndBracket("*/");
650                     inc.next();
651                 } else if ('/' == inc.peek()) {
652                     inc.skipToEndBracket('\n');
653                 }
654             } else if (!incEof && '#' == inc.peek() && (defEof || '#' != def.peek())) {
655                 inc.next();
656                 if (inc.startsWith("if")) {
657                     inc.skipToEndBracket("\n");
658                 } else if (inc.startsWith("endif")) {
659                     inc.skipToEndBracket("\n");
660                 } else {
661                     SkASSERT(0); // incomplete
662                     return false;
663                 }
664             } else {
665                 break;
666             }
667             inc.next();
668         } while (true);
669         if (defEof || incEof) {
670             if (defEof == incEof || (!defEof && ';' == def.peek())) {
671                 return true;
672             }
673             return false;  // allow setting breakpoint on failure
674         }
675         char defCh;
676         do {
677             defCh = def.next();
678             if (inc.skipExact("SK_WARN_UNUSED_RESULT")) {
679                 inc.skipSpace();
680             }
681             char incCh = inc.next();
682             if (' ' >= defCh && ' ' >= incCh) {
683                 break;
684             }
685             if (defCh != incCh) {
686                 if ('_' != defCh || ' ' != incCh || !fOperatorConst || !def.startsWith("const")) {
687                     return false;
688                 }
689             }
690             if (';' == defCh) {
691                 return true;
692             }
693         } while (!def.eof() && !inc.eof());
694     } while (true);
695     return false;
696 }
697 
formatFunction(Format format) const698 string Definition::formatFunction(Format format) const {
699     const char* end = fContentStart;
700     while (end > fStart && ' ' >= end[-1]) {
701         --end;
702     }
703     TextParser methodParser(fFileName, fStart, end, fLineCount);
704     methodParser.skipWhiteSpace();
705     SkASSERT(methodParser.startsWith("#Method"));
706     methodParser.skipName("#Method");
707     methodParser.skipSpace();
708     const char* lastStart = methodParser.fChar;
709     const int limit = 100;  // todo: allow this to be set by caller or in global or something
710     string name = this->methodName();
711     const char* nameInParser = methodParser.strnstr(name.c_str(), methodParser.fEnd);
712     methodParser.skipTo(nameInParser);
713     const char* lastEnd = methodParser.fChar;
714     if (Format::kOmitReturn == format) {
715         lastStart = lastEnd;
716     }
717     const char* paren = methodParser.strnchr('(', methodParser.fEnd);
718     size_t indent;
719     if (paren) {
720         indent = (size_t) (paren - lastStart) + 1;
721     } else {
722         indent = (size_t) (lastEnd - lastStart);
723     }
724     // trim indent so longest line doesn't exceed box width
725     TextParserSave savePlace(&methodParser);
726     const char* saveStart = lastStart;
727     ptrdiff_t maxLine = 0;
728     do {
729         const char* nextStart = lastEnd;
730         const char* delimiter = methodParser.anyOf(",)");
731         const char* nextEnd = delimiter ? delimiter : methodParser.fEnd;
732         if (delimiter) {
733             while (nextStart < nextEnd && ' ' >= nextStart[0]) {
734                 ++nextStart;
735             }
736         }
737         while (nextEnd > nextStart && ' ' >= nextEnd[-1]) {
738             --nextEnd;
739         }
740         if (delimiter) {
741             nextEnd += 1;
742             delimiter += 1;
743         }
744         if (lastEnd > lastStart) {
745             maxLine = SkTMax(maxLine, lastEnd - lastStart);
746         }
747         if (delimiter) {
748             methodParser.skipTo(delimiter);
749         }
750         lastStart = nextStart;
751         lastEnd = nextEnd;
752     } while (lastStart < lastEnd);
753     savePlace.restore();
754     lastStart = saveStart;
755     lastEnd = methodParser.fChar;
756     indent = SkTMin(indent, (size_t) (limit - maxLine));
757     // write string with trimmmed indent
758     string methodStr;
759     int written = 0;
760     do {
761         const char* nextStart = lastEnd;
762 //        SkASSERT(written < limit);
763         const char* delimiter = methodParser.anyOf(",)");
764         const char* nextEnd = delimiter ? delimiter : methodParser.fEnd;
765         if (delimiter) {
766             while (nextStart < nextEnd && ' ' >= nextStart[0]) {
767                 ++nextStart;
768             }
769         }
770         while (nextEnd > nextStart && ' ' >= nextEnd[-1]) {
771             --nextEnd;
772         }
773         if (delimiter) {
774             nextEnd += 1;
775             delimiter += 1;
776         }
777         if (lastEnd > lastStart) {
778             if (lastStart[0] != ' ') {
779                 space_pad(&methodStr);
780             }
781             string addon(lastStart, (size_t) (lastEnd - lastStart));
782             if (" const" == addon) {
783                 addon = "const";
784             }
785             methodStr += addon;
786             written += addon.length();
787         }
788         if (delimiter) {
789             if (nextEnd - nextStart >= (ptrdiff_t) (limit - written)) {
790                 written = indent;
791                 if (Format::kIncludeReturn == format) {
792                     methodStr += '\n';
793                     methodStr += string(indent, ' ');
794                 }
795             }
796             methodParser.skipTo(delimiter);
797         }
798         lastStart = nextStart;
799         lastEnd = nextEnd;
800     } while (lastStart < lastEnd);
801     return methodStr;
802 }
803 
fiddleName() const804 string Definition::fiddleName() const {
805     string result;
806     size_t start = 0;
807     string parent;
808     const Definition* parentDef = this;
809     while ((parentDef = parentDef->fParent)) {
810         if (MarkType::kClass == parentDef->fMarkType || MarkType::kStruct == parentDef->fMarkType) {
811             parent = parentDef->fFiddle;
812             break;
813         }
814     }
815     if (parent.length() && 0 == fFiddle.compare(0, parent.length(), parent)) {
816         start = parent.length();
817         while (start < fFiddle.length() && '_' == fFiddle[start]) {
818             ++start;
819         }
820     }
821     size_t end = fFiddle.find_first_of('(', start);
822     return fFiddle.substr(start, end - start);
823 }
824 
fileName() const825 string Definition::fileName() const {
826     size_t nameStart = fFileName.rfind(SkOSPath::SEPARATOR);
827     if (SkOSPath::SEPARATOR != '/') {
828         size_t altNameStart = fFileName.rfind('/');
829         nameStart = string::npos == nameStart ? altNameStart :
830                 string::npos != altNameStart && altNameStart > nameStart ? altNameStart : nameStart;
831     }
832     SkASSERT(string::npos != nameStart);
833     string baseFile = fFileName.substr(nameStart + 1);
834     return baseFile;
835 }
836 
findClone(string match) const837 const Definition* Definition::findClone(string match) const {
838     for (auto child : fChildren) {
839         if (!child->fClone) {
840             continue;
841         }
842         if (match == child->fName) {
843             return child;
844         }
845         auto inner = child->findClone(match);
846         if (inner) {
847             return inner;
848         }
849     }
850     return nullptr;
851 }
852 
hasChild(MarkType markType) const853 const Definition* Definition::hasChild(MarkType markType) const {
854     for (auto iter : fChildren) {
855         if (markType == iter->fMarkType) {
856             return iter;
857         }
858     }
859     return nullptr;
860 }
861 
hasParam(string ref)862 Definition* Definition::hasParam(string ref) {
863     SkASSERT(MarkType::kMethod == fMarkType);
864     for (auto iter : fChildren) {
865         if (MarkType::kParam != iter->fMarkType) {
866             continue;
867         }
868         if (iter->fName == ref) {
869             return &*iter;
870         }
871     }
872     for (auto& iter : fTokens) {
873         if (MarkType::kComment != iter.fMarkType) {
874             continue;
875         }
876         TextParser parser(&iter);
877         if (!parser.skipExact("@param ")) {
878             continue;
879         }
880         if (parser.skipExact(ref.c_str()) && ' ' == parser.peek()) {
881             return &iter;
882         }
883     }
884     return nullptr;
885 }
886 
hasMatch(string name) const887 bool Definition::hasMatch(string name) const {
888     for (auto child : fChildren) {
889         if (name == child->fName) {
890             return true;
891         }
892         if (child->hasMatch(name)) {
893             return true;
894         }
895     }
896     return false;
897 }
898 
isStructOrClass() const899 bool Definition::isStructOrClass() const {
900     if (MarkType::kStruct != fMarkType && MarkType::kClass != fMarkType) {
901         return false;
902     }
903     if (string::npos != fFileName.find("undocumented.bmh")) {
904         return false;
905     }
906     return true;
907 }
908 
methodHasReturn(string name,TextParser * methodParser) const909 bool Definition::methodHasReturn(string name, TextParser* methodParser) const {
910     if (methodParser->skipExact("static")) {
911         methodParser->skipWhiteSpace();
912     }
913     if (methodParser->skipExact("virtual")) {
914         methodParser->skipWhiteSpace();
915     }
916     const char* lastStart = methodParser->fChar;
917     const char* nameInParser = methodParser->strnstr(name.c_str(), methodParser->fEnd);
918     methodParser->skipTo(nameInParser);
919     const char* lastEnd = methodParser->fChar;
920     const char* returnEnd = lastEnd;
921     while (returnEnd > lastStart && ' ' == returnEnd[-1]) {
922         --returnEnd;
923     }
924     bool expectReturn = 4 != returnEnd - lastStart || strncmp("void", lastStart, 4);
925     if (MethodType::kNone != fMethodType && MethodType::kOperator != fMethodType && !expectReturn) {
926         return methodParser->reportError<bool>("unexpected void");
927     }
928     switch (fMethodType) {
929         case MethodType::kNone:
930         case MethodType::kOperator:
931             // either is fine
932             break;
933         case MethodType::kConstructor:
934             expectReturn = true;
935             break;
936         case MethodType::kDestructor:
937             expectReturn = false;
938             break;
939     }
940     return expectReturn;
941 }
942 
methodName() const943 string Definition::methodName() const {
944     string result;
945     size_t start = 0;
946     string parent;
947     const Definition* parentDef = this;
948     while ((parentDef = parentDef->fParent)) {
949         if (MarkType::kClass == parentDef->fMarkType || MarkType::kStruct == parentDef->fMarkType) {
950             parent = parentDef->fName;
951             break;
952         }
953     }
954     if (parent.length() && 0 == fName.compare(0, parent.length(), parent)) {
955         start = parent.length();
956         while (start < fName.length() && ':' == fName[start]) {
957             ++start;
958         }
959     }
960     if (fClone) {
961         int lastUnder = fName.rfind('_');
962         return fName.substr(start, (size_t) (lastUnder - start));
963     }
964     size_t end = fName.find_first_of('(', start);
965     if (string::npos == end) {
966         return fName.substr(start);
967     }
968     return fName.substr(start, end - start);
969 }
970 
nextMethodParam(TextParser * methodParser,const char ** nextEndPtr,string * paramName) const971 bool Definition::nextMethodParam(TextParser* methodParser, const char** nextEndPtr,
972         string* paramName) const {
973     int parenCount = 0;
974     TextParserSave saveState(methodParser);
975     while (true) {
976         if (methodParser->eof()) {
977             return methodParser->reportError<bool>("#Method function missing close paren");
978         }
979         char ch = methodParser->peek();
980         if ('(' == ch || '{' == ch) {
981             ++parenCount;
982         }
983         if (parenCount == 0 && (')' == ch || ',' == ch)) {
984             *nextEndPtr = methodParser->fChar;
985             break;
986         }
987         if (')' == ch || '}' == ch) {
988             if (0 > --parenCount) {
989                 return this->reportError<bool>("mismatched parentheses");
990             }
991         }
992         methodParser->next();
993     }
994     saveState.restore();
995     const char* nextEnd = *nextEndPtr;
996     const char* paramEnd = nextEnd;
997     const char* assign = methodParser->strnstr(" = ", paramEnd);
998     if (assign) {
999         paramEnd = assign;
1000     }
1001     const char* closeBracket = methodParser->strnstr("]", paramEnd);
1002     if (closeBracket) {
1003         const char* openBracket = methodParser->strnstr("[", paramEnd);
1004         if (openBracket && openBracket < closeBracket) {
1005             while (openBracket < --closeBracket && isdigit(closeBracket[0]))
1006                 ;
1007             if (openBracket == closeBracket) {
1008                 paramEnd = openBracket;
1009             }
1010         }
1011     }
1012     const char* function = methodParser->strnstr(")(", paramEnd);
1013     if (function) {
1014         paramEnd = function;
1015     }
1016     while (paramEnd > methodParser->fChar && ' ' == paramEnd[-1]) {
1017         --paramEnd;
1018     }
1019     const char* paramStart = paramEnd;
1020     while (paramStart > methodParser->fChar && isalnum(paramStart[-1])) {
1021         --paramStart;
1022     }
1023     if (paramStart > methodParser->fChar && paramStart >= paramEnd) {
1024         return methodParser->reportError<bool>("#Method missing param name");
1025     }
1026     *paramName = string(paramStart, paramEnd - paramStart);
1027     if (!paramName->length()) {
1028         if (')' != nextEnd[0]) {
1029             return methodParser->reportError<bool>("#Method malformed param");
1030         }
1031         return false;
1032     }
1033     return true;
1034 }
1035 
NormalizedName(string name)1036 string Definition::NormalizedName(string name) {
1037     string normalizedName = name;
1038     std::replace(normalizedName.begin(), normalizedName.end(), '-', '_');
1039     do {
1040         size_t doubleColon = normalizedName.find("::", 0);
1041         if (string::npos == doubleColon) {
1042             break;
1043         }
1044         normalizedName = normalizedName.substr(0, doubleColon)
1045             + '_' + normalizedName.substr(doubleColon + 2);
1046     } while (true);
1047     return normalizedName;
1048 }
1049 
unpreformat(string orig)1050 static string unpreformat(string orig) {
1051     string result;
1052     int amp = 0;
1053     for (auto c : orig) {
1054         switch (amp) {
1055         case 0:
1056             if ('&' == c) {
1057                 amp = 1;
1058             } else {
1059                 amp = 0;
1060                 result += c;
1061             }
1062             break;
1063         case 1:
1064             if ('l' == c) {
1065                 amp = 2;
1066             } else if ('g' == c) {
1067                 amp = 3;
1068             } else {
1069                 amp = 0;
1070                 result += "&";
1071                 result += c;
1072             }
1073             break;
1074         case 2:
1075             if ('t' == c) {
1076                 amp = 4;
1077             } else {
1078                 amp = 0;
1079                 result += "&l";
1080                 result += c;
1081             }
1082             break;
1083         case 3:
1084             if ('t' == c) {
1085                 amp = 5;
1086             } else {
1087                 amp = 0;
1088                 result += "&g";
1089                 result += c;
1090             }
1091             break;
1092         case 4:
1093             if (';' == c) {
1094                 result += '<';
1095             } else {
1096                 result += "&lt";
1097                 result += c;
1098             }
1099             amp = 0;
1100             break;
1101         case 5:
1102             if (';' == c) {
1103                 result += '>';
1104             } else {
1105                 result += "&gt";
1106                 result += c;
1107             }
1108             amp = 0;
1109             break;
1110         }
1111     }
1112     return result;
1113 }
1114 
paramsMatch(string matchFormatted,string name) const1115 bool Definition::paramsMatch(string matchFormatted, string name) const {
1116     string match = unpreformat(matchFormatted);
1117     TextParser def(fFileName, fStart, fContentStart, fLineCount);
1118     const char* dName = def.strnstr(name.c_str(), fContentStart);
1119     if (!dName) {
1120         return false;
1121     }
1122     def.skipTo(dName);
1123     TextParser m(fFileName, &match.front(), &match.back() + 1, fLineCount);
1124     const char* mName = m.strnstr(name.c_str(), m.fEnd);
1125     if (!mName) {
1126         return false;
1127     }
1128     m.skipTo(mName);
1129     while (!def.eof() && ')' != def.peek() && !m.eof() && ')' != m.peek()) {
1130         const char* ds = def.fChar;
1131         const char* ms = m.fChar;
1132         const char* de = def.anyOf(") \n");
1133         const char* me = m.anyOf(") \n");
1134         def.skipTo(de);
1135         m.skipTo(me);
1136         if (def.fChar - ds != m.fChar - ms) {
1137             return false;
1138         }
1139         if (strncmp(ds, ms, (int) (def.fChar - ds))) {
1140             return false;
1141         }
1142         def.skipWhiteSpace();
1143         m.skipWhiteSpace();
1144     }
1145     return !def.eof() && ')' == def.peek() && !m.eof() && ')' == m.peek();
1146 }
1147 
1148 
trimEnd()1149 void Definition::trimEnd() {
1150     while (fContentEnd > fContentStart && ' ' >= fContentEnd[-1]) {
1151         --fContentEnd;
1152     }
1153 }
1154 
clearVisited()1155 void RootDefinition::clearVisited() {
1156     fVisited = false;
1157     for (auto& leaf : fLeaves) {
1158         leaf.second.fVisited = false;
1159     }
1160     for (auto& branch : fBranches) {
1161         branch.second->clearVisited();
1162     }
1163 }
1164 
dumpUnVisited()1165 bool RootDefinition::dumpUnVisited() {
1166     bool success = true;
1167     for (auto& leaf : fLeaves) {
1168         if (!leaf.second.fVisited) {
1169             // FIXME: bugs requiring long tail fixes, suppressed here:
1170             // SkBitmap::validate() is wrapped in SkDEBUGCODE in .h and not parsed
1171             if ("SkBitmap::validate()" == leaf.first) {
1172                 continue;
1173             }
1174             // FIXME: end of long tail bugs
1175             SkDebugf("defined in bmh but missing in include: %s\n", leaf.first.c_str());
1176             success = false;
1177         }
1178     }
1179     for (auto& branch : fBranches) {
1180         success &= branch.second->dumpUnVisited();
1181     }
1182     return success;
1183 }
1184 
find(string ref,AllowParens allowParens)1185 Definition* RootDefinition::find(string ref, AllowParens allowParens) {
1186     const auto leafIter = fLeaves.find(ref);
1187     if (leafIter != fLeaves.end()) {
1188         return &leafIter->second;
1189     }
1190     if (AllowParens::kYes == allowParens) {
1191         size_t leftParen = ref.find('(');
1192         if (string::npos == leftParen
1193                 || (leftParen + 1 < ref.length() && ')' != ref[leftParen + 1])) {
1194             string withParens = ref + "()";
1195             const auto parensIter = fLeaves.find(withParens);
1196             if (parensIter != fLeaves.end()) {
1197                 return &parensIter->second;
1198             }
1199         }
1200         if (string::npos != leftParen) {
1201             string name = ref.substr(0, leftParen);
1202             size_t posInDefName = fName.find(name);
1203             if (string::npos != posInDefName && posInDefName > 2
1204                     && "::" == fName.substr(posInDefName - 2, 2)) {
1205                 string fullRef = fName + "::" + ref;
1206                 const auto fullIter = fLeaves.find(fullRef);
1207                 if (fullIter != fLeaves.end()) {
1208                     return &fullIter->second;
1209                 }
1210             }
1211         }
1212     }
1213     const auto branchIter = fBranches.find(ref);
1214     if (branchIter != fBranches.end()) {
1215         RootDefinition* rootDef = branchIter->second;
1216         return rootDef;
1217     }
1218     Definition* result = nullptr;
1219     for (const auto& branch : fBranches) {
1220         RootDefinition* rootDef = branch.second;
1221         result = rootDef->find(ref, allowParens);
1222         if (result) {
1223             break;
1224         }
1225     }
1226     return result;
1227 }
1228