Home | History | Annotate | Download | only in bookmaker
      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 
    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 
    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 
    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 
    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
    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 
    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
    414 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 
    575 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 
    596 bool Definition::crossCheck(const Definition& includeToken) const {
    597     return crossCheckInside(fContentStart, fContentEnd, includeToken);
    598 }
    599 
    600 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 
    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 
    626 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 
    698 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 
    804 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 
    825 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 
    837 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 
    853 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 
    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 
    887 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 
    899 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 
    909 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 
    943 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 
    971 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 
   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 
   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 
   1115 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 
   1149 void Definition::trimEnd() {
   1150     while (fContentEnd > fContentStart && ' ' >= fContentEnd[-1]) {
   1151         --fContentEnd;
   1152     }
   1153 }
   1154 
   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 
   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 
   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