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 "bookmaker.h"
      9 
     10 void IncludeWriter::constOut(const Definition* memberStart, const Definition& child,
     11     const Definition* bmhConst) {
     12     const char* bodyEnd = fDeferComment ? fDeferComment->fContentStart - 1 :
     13         memberStart->fContentStart;
     14     this->writeBlockTrim((int) (bodyEnd - fStart), fStart);  // may write nothing
     15     this->lf(2);
     16     this->writeCommentHeader();
     17     fIndent += 4;
     18     this->descriptionOut(bmhConst, SkipFirstLine::kYes);
     19     fIndent -= 4;
     20     this->writeCommentTrailer();
     21     fStart = memberStart->fContentStart;
     22 }
     23 
     24 void IncludeWriter::descriptionOut(const Definition* def, SkipFirstLine skipFirstLine) {
     25     const char* commentStart = def->fContentStart;
     26     if (SkipFirstLine::kYes == skipFirstLine) {
     27         TextParser parser(def);
     28         SkAssertResult(parser.skipLine());
     29         commentStart = parser.fChar;
     30     }
     31     int commentLen = (int) (def->fContentEnd - commentStart);
     32     bool breakOut = false;
     33     SkDEBUGCODE(bool wroteCode = false);
     34     if (def->fDeprecated) {
     35         this->writeString(def->fToBeDeprecated ? "To be deprecated soon." : "Deprecated.");
     36         this->lfcr();
     37     }
     38     for (auto prop : def->fChildren) {
     39         switch (prop->fMarkType) {
     40             case MarkType::kCode: {
     41                 bool literal = false;
     42                 bool literalOutdent = false;
     43                 commentLen = (int) (prop->fStart - commentStart);
     44                 if (commentLen > 0) {
     45                     SkASSERT(commentLen < 1000);
     46                     if (Wrote::kNone != this->rewriteBlock(commentLen, commentStart, Phrase::kNo)) {
     47                         this->lf(2);
     48                     }
     49                 }
     50                 size_t childSize = prop->fChildren.size();
     51                 if (childSize) {
     52                     SkASSERT(1 == childSize || 2 == childSize);  // incomplete
     53                     SkASSERT(MarkType::kLiteral == prop->fChildren[0]->fMarkType);
     54                     SkASSERT(1 == childSize || MarkType::kOutdent == prop->fChildren[1]->fMarkType);
     55                     commentStart = prop->fChildren[childSize - 1]->fContentStart;
     56                     literal = true;
     57                     literalOutdent = 2 == childSize &&
     58                             MarkType::kOutdent == prop->fChildren[1]->fMarkType;
     59                 }
     60                 commentLen = (int) (prop->fContentEnd - commentStart);
     61                 SkASSERT(commentLen > 0);
     62                 if (literal) {
     63                     if (!literalOutdent) {
     64                         fIndent += 4;
     65                     }
     66                     this->writeBlockIndent(commentLen, commentStart);
     67                     this->lf(2);
     68                     if (!literalOutdent) {
     69                         fIndent -= 4;
     70                     }
     71                     commentStart = prop->fTerminator;
     72                     SkDEBUGCODE(wroteCode = true);
     73                 }
     74                 } break;
     75             case MarkType::kDefinedBy:
     76                 commentStart = prop->fTerminator;
     77                 break;
     78             case MarkType::kBug: {
     79                 string bugstr("(see skbug.com/" + string(prop->fContentStart,
     80                     prop->fContentEnd - prop->fContentStart) + ')');
     81                 this->writeString(bugstr);
     82                 this->lfcr();
     83             }
     84             case MarkType::kDeprecated:
     85             case MarkType::kPrivate:
     86                 commentLen = (int) (prop->fStart - commentStart);
     87                 if (commentLen > 0) {
     88                     SkASSERT(commentLen < 1000);
     89                     if (Wrote::kNone != this->rewriteBlock(commentLen, commentStart, Phrase::kNo)) {
     90                         this->lfcr();
     91                     }
     92                 }
     93                 commentStart = prop->fContentStart;
     94                 if (def->fToBeDeprecated) {
     95                     commentStart += 4; // skip over "soon" // FIXME: this is awkward
     96                 } else if (MarkType::kBug == prop->fMarkType) {
     97                     commentStart = prop->fContentEnd;
     98                 }
     99                 commentLen = (int) (prop->fContentEnd - commentStart);
    100                 if (commentLen > 0) {
    101                     this->writeBlockIndent(commentLen, commentStart);
    102                     if ('\n' != commentStart[commentLen - 1] && '\n' == commentStart[commentLen]) {
    103                         this->lfcr();
    104                     }
    105                 }
    106                 commentStart = prop->fTerminator;
    107                 commentLen = (int) (def->fContentEnd - commentStart);
    108                 break;
    109             case MarkType::kExperimental:
    110                 this->writeString("EXPERIMENTAL:");
    111                 this->writeSpace();
    112                 commentStart = prop->fContentStart;
    113                 commentLen = (int) (prop->fContentEnd - commentStart);
    114                 if (commentLen > 0) {
    115                     if (Wrote::kNone != this->rewriteBlock(commentLen, commentStart, Phrase::kNo)) {
    116                         this->lfcr();
    117                     }
    118                 }
    119                 commentStart = prop->fTerminator;
    120                 commentLen = (int) (def->fContentEnd - commentStart);
    121                 break;
    122             case MarkType::kFormula: {
    123                 commentLen = prop->fStart - commentStart;
    124                 if (commentLen > 0) {
    125                     if (Wrote::kNone != this->rewriteBlock(commentLen, commentStart, Phrase::kNo)) {
    126                         if (commentLen > 1 && '\n' == prop->fStart[-1] &&
    127                                 '\n' == prop->fStart[-2]) {
    128                             this->lf(1);
    129                         } else {
    130                             this->writeSpace();
    131                         }
    132                     }
    133                 }
    134                 int saveIndent = fIndent;
    135                 if (fIndent < fColumn + 1) {
    136                     fIndent = fColumn + 1;
    137                 }
    138                 this->writeBlockIndent(prop->length(), prop->fContentStart);
    139                 fIndent = saveIndent;
    140                 commentStart = prop->fTerminator;
    141                 commentLen = (int) (def->fContentEnd - commentStart);
    142                 if (commentLen > 1 && '\n' == commentStart[0] && '\n' == commentStart[1]) {
    143                     this->lf(2);
    144                 } else {
    145                     SkASSERT('\n' == prop->fTerminator[0]);
    146                     if ('.' != prop->fTerminator[1] && !fLinefeeds) {
    147                         this->writeSpace();
    148                     }
    149                 }
    150                 } break;
    151             case MarkType::kIn:
    152             case MarkType::kLine:
    153             case MarkType::kToDo:
    154                 commentLen = (int) (prop->fStart - commentStart);
    155                 if (commentLen > 0) {
    156                     SkASSERT(commentLen < 1000);
    157                     if (Wrote::kNone != this->rewriteBlock(commentLen, commentStart, Phrase::kNo)) {
    158                         this->lfcr();
    159                     }
    160                 }
    161                 commentStart = prop->fTerminator;
    162                 commentLen = (int) (def->fContentEnd - commentStart);
    163                 break;
    164             case MarkType::kList:
    165                 commentLen = prop->fStart - commentStart;
    166                 if (commentLen > 0) {
    167                     if (Wrote::kNone != this->rewriteBlock(commentLen, commentStart,
    168                             Phrase::kNo)) {
    169                         this->lfcr();
    170                     }
    171                 }
    172                 for (auto row : prop->fChildren) {
    173                     SkASSERT(MarkType::kRow == row->fMarkType);
    174                     for (auto column : row->fChildren) {
    175                         SkASSERT(MarkType::kColumn == column->fMarkType);
    176                         this->writeString("-");
    177                         this->writeSpace();
    178                         this->descriptionOut(column, SkipFirstLine::kNo);
    179                         this->lf(1);
    180                     }
    181                 }
    182                 commentStart = prop->fTerminator;
    183                 commentLen = (int) (def->fContentEnd - commentStart);
    184                 if ('\n' == commentStart[0] && '\n' == commentStart[1]) {
    185                     this->lf(2);
    186                 }
    187                 break;
    188             default:
    189                 commentLen = (int) (prop->fStart - commentStart);
    190                 breakOut = true;
    191         }
    192         if (breakOut) {
    193             break;
    194         }
    195     }
    196     SkASSERT(wroteCode || (commentLen > 0 && commentLen < 1500) || def->fDeprecated);
    197     if (commentLen > 0) {
    198         this->rewriteBlock(commentLen, commentStart, Phrase::kNo);
    199     }
    200 }
    201 
    202 void IncludeWriter::enumHeaderOut(const RootDefinition* root,
    203         const Definition& child) {
    204     const Definition* enumDef = nullptr;
    205     const char* bodyEnd = fDeferComment ? fDeferComment->fContentStart - 1 :
    206             child.fContentStart;
    207     this->writeBlockTrim((int) (bodyEnd - fStart), fStart);  // may write nothing
    208     this->lf(2);
    209     if (fIndentNext) {
    210         fIndent += 4;
    211         fIndentNext = false;
    212     }
    213     fDeferComment = nullptr;
    214     fStart = child.fContentStart;
    215     const auto& nameDef = child.fTokens.front();
    216     string fullName;
    217     if (nullptr != nameDef.fContentEnd) {
    218         TextParser enumClassCheck(&nameDef);
    219         const char* start = enumClassCheck.fStart;
    220         size_t len = (size_t) (enumClassCheck.fEnd - start);
    221         bool enumClass = enumClassCheck.skipExact("class ");
    222         if (enumClass) {
    223             start = enumClassCheck.fChar;
    224             const char* end = enumClassCheck.anyOf(" \n;{");
    225             len = (size_t) (end - start);
    226         }
    227         string enumName(start, len);
    228         if (enumClass) {
    229             child.fChildren[0]->fName = enumName;
    230         }
    231         fullName = root->fName + "::" + enumName;
    232         enumDef = root->find(enumName, RootDefinition::AllowParens::kNo);
    233         if (!enumDef) {
    234             enumDef = root->find(fullName, RootDefinition::AllowParens::kNo);
    235         }
    236         SkASSERT(enumDef);
    237         // child[0] should be #Code comment starts at child[0].fTerminator
    238             // though skip until #Code is found (in case there's a #ToDo, etc)
    239         // child[1] should be #Const comment ends at child[1].fStart
    240         // comment becomes enum header (if any)
    241     } else {
    242         string enumName(root->fName);
    243         enumName += "::_anonymous";
    244         if (fAnonymousEnumCount > 1) {
    245             enumName += '_' + to_string(fAnonymousEnumCount);
    246         }
    247         enumDef = root->find(enumName, RootDefinition::AllowParens::kNo);
    248         SkASSERT(enumDef);
    249         ++fAnonymousEnumCount;
    250     }
    251     Definition* codeBlock = nullptr;
    252     const char* commentStart = nullptr;
    253     bool wroteHeader = false;
    254     bool lastAnchor = false;
    255     SkDEBUGCODE(bool foundConst = false);
    256     for (auto test : enumDef->fChildren) {
    257         if (MarkType::kCode == test->fMarkType) {
    258             SkASSERT(!codeBlock);  // FIXME: check enum for correct order earlier
    259             codeBlock = test;
    260             commentStart = codeBlock->fTerminator;
    261             continue;
    262         }
    263         if (!codeBlock) {
    264             continue;
    265         }
    266         const char* commentEnd = test->fStart;
    267         if (!wroteHeader &&
    268                 !this->contentFree((int) (commentEnd - commentStart), commentStart)) {
    269             if (fIndentNext) {
    270                 fIndent += 4;
    271             }
    272             this->writeCommentHeader();
    273             this->writeString("\\enum");
    274             if (fullName.length() > 0) {
    275                 this->writeSpace();
    276                 this->writeString(fullName.c_str());
    277             }
    278             fIndent += 4;
    279             this->lfcr();
    280             wroteHeader = true;
    281         }
    282         if (lastAnchor) {
    283             if (commentEnd - commentStart > 1) {
    284                 SkASSERT('\n' == commentStart[0]);
    285                 if (' ' == commentStart[1]) {
    286                     this->writeSpace();
    287                 }
    288             }
    289             lastAnchor = false;
    290         }
    291         this->rewriteBlock((int) (commentEnd - commentStart), commentStart, Phrase::kNo);
    292         if (MarkType::kAnchor == test->fMarkType) {
    293             bool newLine = commentEnd - commentStart > 1 &&
    294                 '\n' == commentEnd[-1] && '\n' == commentEnd[-2];
    295             commentStart = test->fContentStart;
    296             commentEnd = test->fChildren[0]->fStart;
    297             if (newLine) {
    298                 this->lf(2);
    299             } else {
    300                 this->writeSpace();
    301             }
    302             this->rewriteBlock((int) (commentEnd - commentStart), commentStart, Phrase::kNo);
    303             lastAnchor = true;   // this->writeSpace();
    304         }
    305         commentStart = test->fTerminator;
    306         if (MarkType::kConst == test->fMarkType) {
    307             SkASSERT(codeBlock);  // FIXME: check enum for correct order earlier
    308             SkDEBUGCODE(foundConst = true);
    309             break;
    310         }
    311     }
    312     SkASSERT(codeBlock);
    313     SkASSERT(foundConst);
    314     if (wroteHeader) {
    315         fIndent -= 4;
    316         this->lfcr();
    317         this->writeCommentTrailer();
    318     }
    319     Definition* braceHolder = child.fChildren[0];
    320     if (KeyWord::kClass == braceHolder->fKeyWord) {
    321         braceHolder = braceHolder->fChildren[0];
    322     }
    323     bodyEnd = braceHolder->fContentStart;
    324     SkASSERT('{' == bodyEnd[0]);
    325     ++bodyEnd;
    326     this->lfcr();
    327     this->writeBlock((int) (bodyEnd - fStart), fStart); // write include "enum Name {"
    328     fIndent += 4;
    329     this->singleLF();
    330     fStart = bodyEnd;
    331     fEnumDef = enumDef;
    332 }
    333 
    334 void IncludeWriter::enumMembersOut(const RootDefinition* root, Definition& child) {
    335     // iterate through include tokens and find how much remains for 1 line comments
    336     // put ones that fit on same line, ones that are too big on preceding line?
    337     const Definition* currentEnumItem = nullptr;
    338     const char* commentStart = nullptr;
    339     const char* lastEnd = nullptr;
    340     int commentLen = 0;
    341     enum class State {
    342         kNoItem,
    343         kItemName,
    344         kItemValue,
    345         kItemComment,
    346     };
    347     State state = State::kNoItem;
    348     vector<IterState> iterStack;
    349     iterStack.emplace_back(child.fTokens.begin(), child.fTokens.end());
    350     IterState* iterState = &iterStack[0];
    351     bool preprocessorWord = false;
    352     const char* preprocessStart = nullptr;
    353     const char* preprocessEnd = nullptr;
    354     for (int onePast = 0; onePast < 2; onePast += iterState->fDefIter == iterState->fDefEnd) {
    355         Definition* token = onePast ? nullptr : &*iterState->fDefIter++;
    356         if (token && Definition::Type::kBracket == token->fType) {
    357             if (Bracket::kSlashSlash == token->fBracket) {
    358                 fStart = token->fContentEnd;
    359                 continue;  // ignore old inline comments
    360             }
    361             if (Bracket::kSlashStar == token->fBracket) {
    362                 fStart = token->fContentEnd + 1;
    363                 continue;  // ignore old inline comments
    364             }
    365             if (Bracket::kPound == token->fBracket) {  // preprocessor wraps member
    366                 preprocessStart = token->fContentStart;
    367                 if (KeyWord::kIf == token->fKeyWord || KeyWord::kIfdef == token->fKeyWord) {
    368                     iterStack.emplace_back(token->fTokens.begin(), token->fTokens.end());
    369                     iterState = &iterStack.back();
    370                     preprocessorWord = true;
    371                 } else if (KeyWord::kEndif == token->fKeyWord) {
    372                     iterStack.pop_back();
    373                     iterState = &iterStack.back();
    374                     preprocessEnd = token->fContentEnd;
    375                 } else {
    376                     SkASSERT(0); // incomplete
    377                 }
    378                 continue;
    379             }
    380             SkASSERT(0); // incomplete
    381         }
    382         if (token && Definition::Type::kWord != token->fType) {
    383             SkASSERT(0); // incomplete
    384         }
    385         if (preprocessorWord) {
    386             preprocessorWord = false;
    387             preprocessEnd = token->fContentEnd;
    388             continue;
    389         }
    390         if (token && State::kItemName == state) {
    391             TextParser enumLine(token->fFileName, lastEnd,
    392                     token->fContentStart, token->fLineCount);
    393             const char* end = enumLine.anyOf(",}=");
    394             SkASSERT(end);
    395             state = '=' == *end ? State::kItemValue : State::kItemComment;
    396             if (State::kItemValue == state) {  // write enum value
    397                 this->indentToColumn(fEnumItemValueTab);
    398                 this->writeString("=");
    399                 this->writeSpace();
    400                 lastEnd = token->fContentEnd;
    401                 this->writeBlock((int) (lastEnd - token->fContentStart),
    402                         token->fContentStart); // write const value if any
    403                 continue;
    404             }
    405         }
    406         if (token && State::kItemValue == state) {
    407             TextParser valueEnd(token->fFileName, lastEnd,
    408                     token->fContentStart, token->fLineCount);
    409             const char* end = valueEnd.anyOf(",}");
    410             if (!end) {  // write expression continuation
    411                 if (' ' == lastEnd[0]) {
    412                     this->writeSpace();
    413                 }
    414                 this->writeBlock((int) (token->fContentEnd - lastEnd), lastEnd);
    415                 continue;
    416             }
    417         }
    418         if (State::kNoItem != state) {
    419             this->writeString(",");
    420             SkASSERT(currentEnumItem);
    421             if (currentEnumItem->fShort) {
    422                 this->indentToColumn(fEnumItemCommentTab);
    423                 if (commentLen || currentEnumItem->fDeprecated) {
    424                     this->writeString("//!<");
    425                     this->writeSpace();
    426                     if (currentEnumItem->fDeprecated) {
    427                         this->writeString(child.fToBeDeprecated ? "to be deprecated soon"
    428                                 : "deprecated");
    429                     } else {
    430                         this->rewriteBlock(commentLen, commentStart, Phrase::kNo);
    431                     }
    432                 }
    433             }
    434             if (onePast) {
    435                 fIndent -= 4;
    436             }
    437             this->lfcr();
    438             if (preprocessStart) {
    439                 SkASSERT(preprocessEnd);
    440                 int saveIndent = fIndent;
    441                 fIndent = SkTMax(0, fIndent - 8);
    442                 this->lf(2);
    443                 this->writeBlock((int) (preprocessEnd - preprocessStart), preprocessStart);
    444                 this->lfcr();
    445                 fIndent = saveIndent;
    446                 preprocessStart = nullptr;
    447                 preprocessEnd = nullptr;
    448             }
    449             if (token && State::kItemValue == state) {
    450                 fStart = token->fContentStart;
    451             }
    452             state = State::kNoItem;
    453         }
    454         SkASSERT(State::kNoItem == state);
    455         if (onePast) {
    456             break;
    457         }
    458         SkASSERT(token);
    459         string itemName = root->fName + "::";
    460         if (KeyWord::kClass == child.fParent->fKeyWord) {
    461             itemName += child.fParent->fName + "::";
    462         }
    463         itemName += string(token->fContentStart, (int) (token->fContentEnd - token->fContentStart));
    464         for (auto& enumItem : fEnumDef->fChildren) {
    465             if (MarkType::kConst != enumItem->fMarkType) {
    466                 continue;
    467             }
    468             if (itemName != enumItem->fName) {
    469                 continue;
    470             }
    471             currentEnumItem = enumItem;
    472             break;
    473         }
    474         SkASSERT(currentEnumItem);
    475         // if description fits, it goes after item
    476         commentStart = currentEnumItem->fContentStart;
    477         const char* commentEnd;
    478         if (currentEnumItem->fChildren.size() > 0) {
    479             commentEnd = currentEnumItem->fChildren[0]->fStart;
    480         } else {
    481             commentEnd = currentEnumItem->fContentEnd;
    482         }
    483         TextParser enumComment(fFileName, commentStart, commentEnd, currentEnumItem->fLineCount);
    484         bool isDeprecated = false;
    485         if (enumComment.skipToLineStart()) {  // skip const value
    486             commentStart = enumComment.fChar;
    487             commentLen = (int) (commentEnd - commentStart);
    488         } else {
    489             const Definition* childDef = currentEnumItem->fChildren[0];
    490             isDeprecated = MarkType::kDeprecated == childDef->fMarkType;
    491             if (MarkType::kPrivate == childDef->fMarkType || isDeprecated) {
    492                 commentStart = childDef->fContentStart;
    493                 if (currentEnumItem->fToBeDeprecated) {
    494                     SkASSERT(isDeprecated);
    495                     commentStart += 4; // skip over "soon" // FIXME: this is awkward
    496                 }
    497                 commentLen = (int) (childDef->fContentEnd - commentStart);
    498             }
    499         }
    500         // FIXME: may assert here if there's no const value
    501         // should have detected and errored on that earlier when enum fContentStart was set
    502         SkASSERT((commentLen > 0 && commentLen < 1000) || isDeprecated);
    503         if (!currentEnumItem->fShort) {
    504             this->writeCommentHeader();
    505             fIndent += 4;
    506             bool wroteLineFeed = false;
    507             if (isDeprecated) {
    508                 this->writeString(currentEnumItem->fToBeDeprecated
    509                         ? "To be deprecated soon." : "Deprecated.");
    510             }
    511             wroteLineFeed  = Wrote::kLF ==
    512                 this->rewriteBlock(commentLen, commentStart, Phrase::kNo);
    513             fIndent -= 4;
    514             if (wroteLineFeed || fColumn > 100 - 3 /* space * / */ ) {
    515                 this->lfcr();
    516             } else {
    517                 this->writeSpace();
    518             }
    519             this->writeCommentTrailer();
    520         }
    521         lastEnd = token->fContentEnd;
    522         this->lfcr();
    523         if (',' == fStart[0]) {
    524             ++fStart;
    525         }
    526         this->writeBlock((int) (lastEnd - fStart), fStart);  // enum item name
    527         fStart = token->fContentEnd;
    528         state = State::kItemName;
    529     }
    530 }
    531 
    532 void IncludeWriter::enumSizeItems(const Definition& child) {
    533     enum class State {
    534         kNoItem,
    535         kItemName,
    536         kItemValue,
    537         kItemComment,
    538     };
    539     State state = State::kNoItem;
    540     int longestName = 0;
    541     int longestValue = 0;
    542     int valueLen = 0;
    543     const char* lastEnd = nullptr;
    544 //    SkASSERT(child.fChildren.size() == 1 || child.fChildren.size() == 2);
    545     auto brace = child.fChildren[0];
    546     if (KeyWord::kClass == brace->fKeyWord) {
    547         brace = brace->fChildren[0];
    548     }
    549     SkASSERT(Bracket::kBrace == brace->fBracket);
    550     vector<IterState> iterStack;
    551     iterStack.emplace_back(brace->fTokens.begin(), brace->fTokens.end());
    552     IterState* iterState = &iterStack[0];
    553     bool preprocessorWord = false;
    554     while (iterState->fDefIter != iterState->fDefEnd) {
    555         auto& token = *iterState->fDefIter++;
    556         if (Definition::Type::kBracket == token.fType) {
    557             if (Bracket::kSlashSlash == token.fBracket) {
    558                 continue;  // ignore old inline comments
    559             }
    560             if (Bracket::kSlashStar == token.fBracket) {
    561                 continue;  // ignore old inline comments
    562             }
    563             if (Bracket::kPound == token.fBracket) {  // preprocessor wraps member
    564                 if (KeyWord::kIf == token.fKeyWord || KeyWord::kIfdef == token.fKeyWord) {
    565                     iterStack.emplace_back(token.fTokens.begin(), token.fTokens.end());
    566                     iterState = &iterStack.back();
    567                     preprocessorWord = true;
    568                 } else if (KeyWord::kEndif == token.fKeyWord) {
    569                     iterStack.pop_back();
    570                     iterState = &iterStack.back();
    571                 } else {
    572                     SkASSERT(0); // incomplete
    573                 }
    574                 continue;
    575             }
    576             SkASSERT(0); // incomplete
    577         }
    578         if (Definition::Type::kWord != token.fType) {
    579             SkASSERT(0); // incomplete
    580         }
    581         if (preprocessorWord) {
    582             preprocessorWord = false;
    583             continue;
    584         }
    585         if (State::kItemName == state) {
    586             TextParser enumLine(token.fFileName, lastEnd,
    587                     token.fContentStart, token.fLineCount);
    588             const char* end = enumLine.anyOf(",}=");
    589             SkASSERT(end);
    590             state = '=' == *end ? State::kItemValue : State::kItemComment;
    591             if (State::kItemValue == state) {
    592                 valueLen = (int) (token.fContentEnd - token.fContentStart);
    593                 lastEnd = token.fContentEnd;
    594                 continue;
    595             }
    596         }
    597         if (State::kItemValue == state) {
    598             TextParser valueEnd(token.fFileName, lastEnd,
    599                     token.fContentStart, token.fLineCount);
    600             const char* end = valueEnd.anyOf(",}");
    601             if (!end) {  // write expression continuation
    602                 valueLen += (int) (token.fContentEnd - lastEnd);
    603                 continue;
    604             }
    605         }
    606         if (State::kNoItem != state) {
    607             longestValue = SkTMax(longestValue, valueLen);
    608             state = State::kNoItem;
    609         }
    610         SkASSERT(State::kNoItem == state);
    611         lastEnd = token.fContentEnd;
    612         longestName = SkTMax(longestName, (int) (lastEnd - token.fContentStart));
    613         state = State::kItemName;
    614     }
    615     if (State::kItemValue == state) {
    616         longestValue = SkTMax(longestValue, valueLen);
    617     }
    618     fEnumItemValueTab = longestName + fIndent + 1 /* space before = */ ;
    619     if (longestValue) {
    620         longestValue += 3; /* = space , */
    621     }
    622     fEnumItemCommentTab = fEnumItemValueTab + longestValue + 1 /* space before //!< */ ;
    623     // iterate through bmh children and see which comments fit on include lines
    624     for (auto& enumItem : fEnumDef->fChildren) {
    625         if (MarkType::kConst != enumItem->fMarkType) {
    626             continue;
    627         }
    628         TextParser enumLine(enumItem);
    629         enumLine.trimEnd();
    630         enumLine.skipToLineStart(); // skip const value
    631         const char* commentStart = enumLine.fChar;
    632         enumLine.skipLine();
    633         ptrdiff_t lineLen = enumLine.fChar - commentStart + 5 /* //!< space */ ;
    634         if (!enumLine.eof()) {
    635             enumLine.skipWhiteSpace();
    636         }
    637         enumItem->fShort = enumLine.eof() && fEnumItemCommentTab + lineLen < 100;
    638     }
    639 }
    640 
    641 // walk children and output complete method doxygen description
    642 void IncludeWriter::methodOut(const Definition* method, const Definition& child) {
    643     if (fPendingMethod) {
    644         fIndent -= 4;
    645         fPendingMethod = false;
    646     }
    647     fBmhMethod = method;
    648     fMethodDef = &child;
    649     fContinuation = nullptr;
    650     fDeferComment = nullptr;
    651     if (0 == fIndent || fIndentNext) {
    652         fIndent += 4;
    653         fIndentNext = false;
    654     }
    655     this->writeCommentHeader();
    656     fIndent += 4;
    657     this->descriptionOut(method, SkipFirstLine::kNo);
    658     // compute indention column
    659     size_t column = 0;
    660     bool hasParmReturn = false;
    661     for (auto methodPart : method->fChildren) {
    662         if (MarkType::kParam == methodPart->fMarkType) {
    663             column = SkTMax(column, methodPart->fName.length());
    664             hasParmReturn = true;
    665         } else if (MarkType::kReturn == methodPart->fMarkType) {
    666             hasParmReturn = true;
    667         }
    668     }
    669     if (hasParmReturn) {
    670         this->lf(2);
    671         column += fIndent + sizeof("@return ");
    672         int saveIndent = fIndent;
    673         for (auto methodPart : method->fChildren) {
    674             const char* partStart = methodPart->fContentStart;
    675             const char* partEnd = methodPart->fContentEnd;
    676             if (MarkType::kParam == methodPart->fMarkType) {
    677                 this->writeString("@param");
    678                 this->writeSpace();
    679                 this->writeString(methodPart->fName.c_str());
    680             } else if (MarkType::kReturn == methodPart->fMarkType) {
    681                 this->writeString("@return");
    682             } else {
    683                 continue;
    684             }
    685             while ('\n' == partEnd[-1]) {
    686                 --partEnd;
    687             }
    688             while ('#' == partEnd[-1]) { // FIXME: so wrong; should not be before fContentEnd
    689                 --partEnd;
    690             }
    691             this->indentToColumn(column);
    692             int partLen = (int) (partEnd - partStart);
    693             // FIXME : detect this earlier; assert if #Return is empty
    694             SkASSERT(partLen > 0 && partLen < 300);  // may assert if param desc is especially long
    695             fIndent = column;
    696             this->rewriteBlock(partLen, partStart, Phrase::kYes);
    697             fIndent = saveIndent;
    698             this->lfcr();
    699         }
    700     } else {
    701         this->lfcr();
    702     }
    703     fIndent -= 4;
    704     this->lfcr();
    705     this->writeCommentTrailer();
    706     fBmhMethod = nullptr;
    707     fMethodDef = nullptr;
    708     fEnumDef = nullptr;
    709     fWroteMethod = true;
    710 }
    711 
    712 void IncludeWriter::structOut(const Definition* root, const Definition& child,
    713         const char* commentStart, const char* commentEnd) {
    714     this->writeCommentHeader();
    715     this->writeString("\\");
    716     SkASSERT(MarkType::kClass == child.fMarkType || MarkType::kStruct == child.fMarkType);
    717     this->writeString(MarkType::kClass == child.fMarkType ? "class" : "struct");
    718     this->writeSpace();
    719     this->writeString(child.fName.c_str());
    720     fIndent += 4;
    721     this->lfcr();
    722     if (child.fDeprecated) {
    723         this->writeString(child.fToBeDeprecated ? "to be deprecated soon" : "deprecated");
    724     } else {
    725         this->rewriteBlock((int)(commentEnd - commentStart), commentStart, Phrase::kNo);
    726     }
    727     fIndent -= 4;
    728     this->lfcr();
    729     this->writeCommentTrailer();
    730 }
    731 
    732 Definition* IncludeWriter::findMemberCommentBlock(const vector<Definition*>& bmhChildren,
    733         const string& name) const {
    734     for (auto memberDef : bmhChildren) {
    735         if (MarkType::kMember != memberDef->fMarkType) {
    736             continue;
    737         }
    738         string match = memberDef->fName;
    739         // if match.endsWith(name) ...
    740         if (match.length() >= name.length() &&
    741                 0 == match.compare(match.length() - name.length(), name.length(), name)) {
    742             return memberDef;
    743         }
    744     }
    745     for (auto memberDef : bmhChildren) {
    746         if (MarkType::kSubtopic != memberDef->fMarkType && MarkType::kTopic != memberDef->fMarkType) {
    747             continue;
    748         }
    749         Definition* result = this->findMemberCommentBlock(memberDef->fChildren, name);
    750         if (result) {
    751             return result;
    752         }
    753     }
    754     return nullptr;
    755 }
    756 
    757 Definition* IncludeWriter::structMemberOut(const Definition* memberStart, const Definition& child) {
    758     const char* blockStart = !fWroteMethod && fDeferComment ? fLastComment->fContentEnd : fStart;
    759     const char* blockEnd = fWroteMethod && fDeferComment ? fDeferComment->fStart - 1 :
    760             memberStart->fStart;
    761     this->writeBlockTrim((int) (blockEnd - blockStart), blockStart);
    762     if (fIndentNext) {
    763         fIndent += 4;
    764         fIndentNext = false;
    765     }
    766     fWroteMethod = false;
    767     string name(child.fContentStart, (int) (child.fContentEnd - child.fContentStart));
    768     Definition* commentBlock = this->findMemberCommentBlock(fBmhStructDef->fChildren, name);
    769     if (!commentBlock) {
    770         return memberStart->reportError<Definition*>("member missing comment block");
    771     }
    772     if (!commentBlock->fShort) {
    773         const char* commentStart = commentBlock->fContentStart;
    774         ptrdiff_t commentLen = commentBlock->fContentEnd - commentStart;
    775         this->writeCommentHeader();
    776         bool wroteLineFeed = false;
    777         fIndent += 4;
    778         for (auto child : commentBlock->fChildren) {
    779             commentLen = child->fStart - commentStart;
    780             wroteLineFeed |= Wrote::kLF == this->rewriteBlock(commentLen, commentStart, Phrase::kNo);
    781             if (MarkType::kFormula == child->fMarkType) {
    782                 this->writeSpace();
    783                 this->writeBlock((int) (child->fContentEnd - child->fContentStart),
    784                         child->fContentStart);
    785             }
    786             commentStart = child->fTerminator;
    787         }
    788         commentLen = commentBlock->fContentEnd - commentStart;
    789         wroteLineFeed |= Wrote::kLF == this->rewriteBlock(commentLen, commentStart, Phrase::kNo);
    790         fIndent -= 4;
    791         if (wroteLineFeed || fColumn > 100 - 3 /* space * / */ ) {
    792             this->lfcr();
    793         } else {
    794             this->writeSpace();
    795         }
    796         this->writeCommentTrailer();
    797     }
    798     this->lfcr();
    799     this->writeBlock((int) (child.fStart - memberStart->fContentStart),
    800             memberStart->fContentStart);
    801     this->indentToColumn(fStructMemberTab);
    802     this->writeString(name.c_str());
    803     auto tokenIter = child.fParent->fTokens.begin();
    804     std::advance(tokenIter, child.fParentIndex + 1);
    805     Definition* valueStart = &*tokenIter;
    806     while (Definition::Type::kPunctuation != tokenIter->fType) {
    807         std::advance(tokenIter, 1);
    808         SkASSERT(child.fParent->fTokens.end() != tokenIter);
    809     }
    810     Definition* valueEnd = &*tokenIter;
    811     if (valueStart != valueEnd) {
    812         this->indentToColumn(fStructValueTab);
    813         this->writeString("=");
    814         this->writeSpace();
    815         this->writeBlock((int) (valueEnd->fStart - valueStart->fContentStart),
    816                 valueStart->fContentStart);
    817     }
    818     this->writeString(";");
    819     if (commentBlock->fShort) {
    820         this->indentToColumn(fStructCommentTab);
    821         this->writeString("//!<");
    822         this->writeSpace();
    823         string extract = commentBlock->extractText(Definition::TrimExtract::kYes);
    824         this->rewriteBlock(extract.length(), &extract.front(), Phrase::kNo);
    825     }
    826     this->lf(2);
    827     return valueEnd;
    828 }
    829 
    830 // iterate through bmh children and see which comments fit on include lines
    831 void IncludeWriter::structSetMembersShort(const vector<Definition*>& bmhChildren) {
    832     for (auto memberDef : bmhChildren) {
    833         if (MarkType::kMember != memberDef->fMarkType) {
    834             continue;
    835         }
    836         string extract = memberDef->extractText(Definition::TrimExtract::kYes);
    837         bool multiline = string::npos != extract.find('\n');
    838         if (multiline) {
    839             memberDef->fShort = false;
    840         } else {
    841             ptrdiff_t lineLen = extract.length() + 5 /* //!< space */ ;
    842             memberDef->fShort = fStructCommentTab + lineLen < 100;
    843         }
    844     }
    845     for (auto memberDef : bmhChildren) {
    846         if (MarkType::kSubtopic != memberDef->fMarkType && MarkType::kTopic != memberDef->fMarkType) {
    847             continue;
    848         }
    849         this->structSetMembersShort(memberDef->fChildren);
    850     }
    851 }
    852 
    853 void IncludeWriter::structSizeMembers(const Definition& child) {
    854     int longestType = 0;
    855     Definition* typeStart = nullptr;
    856     int longestName = 0;
    857     int longestValue = 0;
    858     SkASSERT(child.fChildren.size() == 1 || child.fChildren.size() == 2);
    859     bool inEnum = false;
    860     bool inMethod = false;
    861     bool inMember = false;
    862     auto brace = child.fChildren[0];
    863     SkASSERT(Bracket::kBrace == brace->fBracket);
    864     for (auto& token : brace->fTokens) {
    865         if (Definition::Type::kBracket == token.fType) {
    866             if (Bracket::kSlashSlash == token.fBracket) {
    867                 continue;  // ignore old inline comments
    868             }
    869             if (Bracket::kSlashStar == token.fBracket) {
    870                 continue;  // ignore old inline comments
    871             }
    872             if (Bracket::kParen == token.fBracket) {
    873                 if (inMethod) {
    874                     continue;
    875                 }
    876                 break;
    877             }
    878             SkASSERT(0); // incomplete
    879         }
    880         if (Definition::Type::kKeyWord == token.fType) {
    881             switch (token.fKeyWord) {
    882                 case KeyWord::kEnum:
    883                     inEnum = true;
    884                     break;
    885                 case KeyWord::kConst:
    886                 case KeyWord::kConstExpr:
    887                 case KeyWord::kStatic:
    888                 case KeyWord::kInt:
    889                 case KeyWord::kUint8_t:
    890                 case KeyWord::kUint16_t:
    891                 case KeyWord::kUint32_t:
    892                 case KeyWord::kUint64_t:
    893                 case KeyWord::kSize_t:
    894                 case KeyWord::kFloat:
    895                 case KeyWord::kBool:
    896                 case KeyWord::kVoid:
    897                     if (!typeStart) {
    898                         typeStart = &token;
    899                     }
    900                     break;
    901                 default:
    902                     break;
    903             }
    904             continue;
    905         }
    906         if (Definition::Type::kPunctuation == token.fType) {
    907             if (inEnum) {
    908                 SkASSERT(Punctuation::kSemicolon == token.fPunctuation);
    909                 inEnum = false;
    910             }
    911             if (inMethod) {
    912                 if (Punctuation::kColon == token.fPunctuation) {
    913                     inMethod = false;
    914                 } else if (Punctuation::kLeftBrace == token.fPunctuation) {
    915                     inMethod = false;
    916                 } else if (Punctuation::kSemicolon == token.fPunctuation) {
    917                     inMethod = false;
    918                 } else {
    919                     SkASSERT(0);  // incomplete
    920                 }
    921             }
    922             if (inMember) {
    923                 SkASSERT(Punctuation::kSemicolon == token.fPunctuation);
    924                 typeStart = nullptr;
    925                 inMember = false;
    926             }
    927             continue;
    928         }
    929         if (Definition::Type::kWord != token.fType) {
    930             SkASSERT(0); // incomplete
    931         }
    932         if (MarkType::kMember == token.fMarkType) {
    933             TextParser typeStr(token.fFileName, typeStart->fContentStart, token.fContentStart,
    934                     token.fLineCount);
    935             typeStr.trimEnd();
    936             longestType = SkTMax(longestType, (int) (typeStr.fEnd - typeStr.fStart));
    937             longestName = SkTMax(longestName, (int) (token.fContentEnd - token.fContentStart));
    938             typeStart->fMemberStart = true;
    939             inMember = true;
    940             continue;
    941         }
    942         if (MarkType::kMethod == token.fMarkType) {
    943             inMethod = true;
    944             continue;
    945         }
    946         SkASSERT(MarkType::kNone == token.fMarkType);
    947         if (typeStart) {
    948             if (inMember) {
    949                 longestValue =
    950                         SkTMax(longestValue, (int) (token.fContentEnd - token.fContentStart));
    951             }
    952         } else {
    953             typeStart = &token;
    954         }
    955     }
    956     fStructMemberTab = longestType + fIndent + 1 /* space before name */ ;
    957     fStructValueTab = fStructMemberTab + longestName + 2 /* space ; */ ;
    958     fStructCommentTab = fStructValueTab;
    959     if (longestValue) {
    960         fStructCommentTab += longestValue + 3 /* space = space */ ;
    961         fStructValueTab -= 1 /* ; */ ;
    962     }
    963     // iterate through bmh children and see which comments fit on include lines
    964     this->structSetMembersShort(fBmhStructDef->fChildren);
    965 }
    966 
    967 static bool find_start(const Definition* startDef, const char* start) {
    968     for (const auto& child : startDef->fTokens) {
    969         if (child.fContentStart == start) {
    970             return MarkType::kMethod == child.fMarkType;
    971         }
    972         if (child.fContentStart >= start) {
    973             break;
    974         }
    975         if (find_start(&child, start)) {
    976             return true;
    977         }
    978     }
    979     return false;
    980 }
    981 
    982 bool IncludeWriter::populate(Definition* def, ParentPair* prevPair, RootDefinition* root) {
    983     if (!def->fTokens.size()) {
    984         return true;
    985     }
    986     ParentPair pair = { def, prevPair };
    987     // write bulk of original include up to class, method, enum, etc., excepting preceding comment
    988     // find associated bmh object
    989     // write any associated comments in Doxygen form
    990     // skip include comment
    991     // if there is a series of same named methods, write one set of comments, then write all methods
    992     string methodName;
    993     const Definition* method = nullptr;
    994     const Definition* clonedMethod = nullptr;
    995     const Definition* memberStart = nullptr;
    996     const Definition* memberEnd = nullptr;
    997     fContinuation = nullptr;
    998     bool inStruct = false;
    999     bool inConstructor = false;
   1000     bool inInline = false;
   1001     bool eatOperator = false;
   1002     bool sawConst = false;
   1003     bool staticOnly = false;
   1004     const Definition* requireDense = nullptr;
   1005     const Definition* startDef = nullptr;
   1006     for (auto& child : def->fTokens) {
   1007         if (KeyWord::kOperator == child.fKeyWord && method &&
   1008                 Definition::MethodType::kOperator == method->fMethodType) {
   1009             eatOperator = true;
   1010             continue;
   1011         }
   1012         if (eatOperator) {
   1013             if (Bracket::kSquare == child.fBracket || Bracket::kParen == child.fBracket) {
   1014                 continue;
   1015             }
   1016             eatOperator = false;
   1017             fContinuation = nullptr;
   1018             if (KeyWord::kConst == child.fKeyWord) {
   1019                 continue;
   1020             }
   1021         }
   1022         if (memberEnd) {
   1023             if (memberEnd != &child) {
   1024                 continue;
   1025             }
   1026             startDef = &child;
   1027             fStart = child.fContentStart + 1;
   1028             memberEnd = nullptr;
   1029         }
   1030         if (child.fPrivate) {
   1031             if (MarkType::kMethod == child.fMarkType) {
   1032                 inInline = true;
   1033             }
   1034             continue;
   1035         }
   1036         if (inInline) {
   1037             if (Definition::Type::kKeyWord == child.fType) {
   1038                 SkASSERT(MarkType::kMethod != child.fMarkType);
   1039                 continue;
   1040             }
   1041             if (Definition::Type::kPunctuation == child.fType) {
   1042                 if (Punctuation::kLeftBrace == child.fPunctuation) {
   1043                     inInline = false;
   1044                 } else {
   1045                     SkASSERT(Punctuation::kAsterisk == child.fPunctuation);
   1046                 }
   1047                 continue;
   1048             }
   1049             if (Definition::Type::kWord == child.fType) {
   1050                 string name(child.fContentStart, child.fContentEnd - child.fContentStart);
   1051                 SkASSERT(string::npos != name.find("::"));
   1052                 continue;
   1053             }
   1054             if (Definition::Type::kBracket == child.fType) {
   1055                 SkASSERT(Bracket::kParen == child.fBracket);
   1056                 continue;
   1057             }
   1058         }
   1059         if (fContinuation) {
   1060             if (Definition::Type::kKeyWord == child.fType) {
   1061                 if (KeyWord::kFriend == child.fKeyWord ||
   1062                         KeyWord::kSK_API == child.fKeyWord) {
   1063                     continue;
   1064                 }
   1065                 const IncludeKey& includeKey = kKeyWords[(int) child.fKeyWord];
   1066                 if (KeyProperty::kNumber == includeKey.fProperty) {
   1067                     continue;
   1068                 }
   1069             }
   1070             if (Definition::Type::kBracket == child.fType) {
   1071                 if (Bracket::kAngle == child.fBracket) {
   1072                     continue;
   1073                 }
   1074                 if (Bracket::kParen == child.fBracket) {
   1075                     if (!clonedMethod) {
   1076                         if (inConstructor) {
   1077                             fContinuation = child.fContentStart;
   1078                         }
   1079                         continue;
   1080                     }
   1081                     int alternate = 1;
   1082                     ptrdiff_t childLen = child.fContentEnd - child.fContentStart;
   1083                     SkASSERT(')' == child.fContentStart[childLen]);
   1084                     ++childLen;
   1085                     do {
   1086                         TextParser params(clonedMethod->fFileName, clonedMethod->fStart,
   1087                             clonedMethod->fContentStart, clonedMethod->fLineCount);
   1088                         params.skipToEndBracket('(');
   1089                         if (params.startsWith(child.fContentStart, childLen)) {
   1090                             this->methodOut(clonedMethod, child);
   1091                             break;
   1092                         }
   1093                         ++alternate;
   1094                         string alternateMethod = methodName + '_' + to_string(alternate);
   1095                         clonedMethod = root->find(alternateMethod,
   1096                                 RootDefinition::AllowParens::kNo);
   1097                     } while (clonedMethod);
   1098                     if (!clonedMethod) {
   1099                         return this->reportError<bool>("cloned method not found");
   1100                     }
   1101                     clonedMethod = nullptr;
   1102                     continue;
   1103                 }
   1104             }
   1105             if (Definition::Type::kWord == child.fType) {
   1106                 if (clonedMethod) {
   1107                     continue;
   1108                 }
   1109                 size_t len = (size_t) (child.fContentEnd - child.fContentStart);
   1110                 const char operatorStr[] = "operator";
   1111                 size_t operatorLen = sizeof(operatorStr) - 1;
   1112                 if (len >= operatorLen && !strncmp(child.fContentStart, operatorStr, operatorLen)) {
   1113                     fContinuation = child.fContentEnd;
   1114                     continue;
   1115                 }
   1116             }
   1117             if (Definition::Type::kPunctuation == child.fType &&
   1118                     (Punctuation::kSemicolon == child.fPunctuation ||
   1119                     Punctuation::kLeftBrace == child.fPunctuation ||
   1120                     (Punctuation::kColon == child.fPunctuation && inConstructor))) {
   1121                 SkASSERT(fContinuation[0] == '(');
   1122                 const char* continueEnd = child.fContentStart;
   1123                 while (continueEnd > fContinuation && isspace(continueEnd[-1])) {
   1124                     --continueEnd;
   1125                 }
   1126                 methodName += string(fContinuation, continueEnd - fContinuation);
   1127                 method = root->find(methodName, RootDefinition::AllowParens::kNo);
   1128                 if (!method) {
   1129                     if (fBmhStructDef && fBmhStructDef->fDeprecated) {
   1130                         fContinuation = nullptr;
   1131                         continue;
   1132                     }
   1133                     fLineCount = child.fLineCount;
   1134                     return this->reportError<bool>("method not found");
   1135                 }
   1136                 this->methodOut(method, child);
   1137                 continue;
   1138             }
   1139             if (Definition::Type::kPunctuation == child.fType &&
   1140                     Punctuation::kAsterisk == child.fPunctuation &&
   1141                     clonedMethod) {
   1142                 continue;
   1143             }
   1144             if (inConstructor) {
   1145                 continue;
   1146             }
   1147             method = root->find(methodName + "()", RootDefinition::AllowParens::kNo);
   1148             if (method && MarkType::kDefinedBy == method->fMarkType) {
   1149                 method = method->fParent;
   1150             }
   1151             if (method) {
   1152                 if (method->fCloned) {
   1153                     clonedMethod = method;
   1154                     continue;
   1155                 }
   1156                 this->methodOut(method, child);
   1157                 continue;
   1158             } else if (fBmhStructDef && fBmhStructDef->fDeprecated) {
   1159                 fContinuation = nullptr;
   1160                 continue;
   1161             }
   1162             fLineCount = child.fLineCount;
   1163             return this->reportError<bool>("method not found");
   1164         }
   1165         if (Bracket::kSlashSlash == child.fBracket || Bracket::kSlashStar == child.fBracket) {
   1166             if (!fDeferComment) {
   1167                 fDeferComment = &child;
   1168             }
   1169             fLastComment = &child;
   1170             continue;
   1171         }
   1172         if (MarkType::kMethod == child.fMarkType) {
   1173             if (this->internalName(child)) {
   1174                 continue;
   1175             }
   1176             const char* bodyEnd = fDeferComment ? fDeferComment->fContentStart - 1 :
   1177                     fAttrDeprecated ? fAttrDeprecated->fContentStart - 1 :
   1178                     child.fContentStart;
   1179             if (Definition::Type::kBracket == def->fType && Bracket::kDebugCode == def->fBracket) {
   1180                 auto tokenIter = def->fParent->fTokens.begin();
   1181                 std::advance(tokenIter, def->fParentIndex - 1);
   1182                 Definition* prior = &*tokenIter;
   1183                 if (Definition::Type::kBracket == def->fType &&
   1184                         Bracket::kSlashStar == prior->fBracket) {
   1185                     bodyEnd = prior->fContentStart - 1;
   1186                 }
   1187             }
   1188             // FIXME: roll end-trimming into writeBlockTrim call
   1189             while (fStart < bodyEnd && ' ' >= bodyEnd[-1]) {
   1190                 --bodyEnd;
   1191             }
   1192             int blockSize = (int) (bodyEnd - fStart);
   1193             if (blockSize) {
   1194                 string debugstr(fStart, blockSize);
   1195                 this->writeBlock(blockSize, fStart);
   1196             }
   1197             startDef = &child;
   1198             fStart = child.fContentStart;
   1199             methodName = root->fName + "::" + child.fName;
   1200             inConstructor = root->fName == child.fName;
   1201             fContinuation = child.fContentEnd;
   1202             method = root->find(methodName, RootDefinition::AllowParens::kNo);
   1203 //            if (!method) {
   1204 //                method = root->find(methodName + "()", RootDefinition::AllowParens::kNo);
   1205 //            }
   1206             if (!method) {
   1207                 continue;
   1208             }
   1209             if (method->fCloned) {
   1210                 clonedMethod = method;
   1211                 continue;
   1212             }
   1213             this->methodOut(method, child);
   1214             if (fAttrDeprecated) {
   1215                 startDef = fAttrDeprecated;
   1216                 fStart = fAttrDeprecated->fContentStart;
   1217                 fAttrDeprecated = nullptr;
   1218             }
   1219             continue;
   1220         }
   1221         if (Definition::Type::kKeyWord == child.fType) {
   1222             if (fIndentNext) {
   1223     // too soon
   1224 #if 0  // makes struct Lattice indent when it oughtn't
   1225                 if (KeyWord::kEnum == child.fKeyWord) {
   1226                     fIndent += 4;
   1227                 }
   1228                 if (KeyWord::kPublic != child.fKeyWord) {
   1229                     fIndentNext = false;
   1230                 }
   1231 #endif
   1232             }
   1233             switch (child.fKeyWord) {
   1234                 case KeyWord::kStruct:
   1235                 case KeyWord::kClass:
   1236                     fStructMemberTab = 0;
   1237                     // if struct contains members, compute their name and comment tabs
   1238                     if (child.fChildren.size() > 0) {
   1239                         const ParentPair* testPair = &pair;
   1240                         while ((testPair = testPair->fPrev)) {
   1241                             if (KeyWord::kClass == testPair->fParent->fKeyWord) {
   1242                                 inStruct = fInStruct = true;
   1243                                 break;
   1244                             }
   1245                         }
   1246                     }
   1247                     if (fInStruct) {
   1248                         // try child; root+child; root->parent+child; etc.
   1249                         int trial = 0;
   1250                         const RootDefinition* search = root;
   1251                         const Definition* parent = search->fParent;
   1252                         do {
   1253                             string name;
   1254                             if (0 == trial) {
   1255                                 name = child.fName;
   1256                             } else if (1 == trial) {
   1257                                 name = root->fName + "::" + child.fName;
   1258                             } else {
   1259                                 SkASSERT(parent);
   1260                                 name = parent->fName + "::" + child.fName;
   1261                                 search = parent->asRoot();
   1262                                 parent = search->fParent;
   1263                             }
   1264                             fBmhStructDef = search->find(name, RootDefinition::AllowParens::kNo);
   1265                         } while (!fBmhStructDef && ++trial);
   1266                         root = const_cast<RootDefinition*>(fBmhStructDef->asRoot());
   1267                         SkASSERT(root);
   1268                         fIndent += 4;
   1269                         this->structSizeMembers(child);
   1270                         fIndent -= 4;
   1271                         SkASSERT(!fIndentNext);
   1272                         fIndentNext = true;
   1273                     }
   1274                     if (child.fChildren.size() > 0) {
   1275                         const char* bodyEnd = fDeferComment ? fDeferComment->fContentStart - 1 :
   1276                                 child.fContentStart;
   1277                         this->writeBlockTrim((int) (bodyEnd - fStart), fStart);
   1278                         if (fPendingMethod) {
   1279                             fIndent -= 4;
   1280                             fPendingMethod = false;
   1281                         }
   1282                         startDef = requireDense ? requireDense : &child;
   1283                         fStart = requireDense ? requireDense->fContentStart : child.fContentStart;
   1284                         requireDense = nullptr;
   1285                         if (!fInStruct && child.fName != root->fName) {
   1286                             root = &fBmhParser->fClassMap[child.fName];
   1287                             fRootTopic = root->fParent;
   1288                             SkASSERT(!root->fVisited);
   1289                             root->clearVisited();
   1290                             fIndent = 0;
   1291                             fBmhStructDef = root;
   1292                         }
   1293                         if (child.fName == root->fName) {
   1294                             if (Definition* parent = root->fParent) {
   1295                                 if (MarkType::kTopic == parent->fMarkType ||
   1296                                         MarkType::kSubtopic == parent->fMarkType) {
   1297                                     const char* commentStart = root->fContentStart;
   1298                                     const char* commentEnd = root->fChildren[0]->fStart;
   1299                                     this->structOut(root, *root, commentStart, commentEnd);
   1300                                 } else {
   1301                                     SkASSERT(0); // incomplete
   1302                                 }
   1303                             } else {
   1304                                 SkASSERT(0); // incomplete
   1305                             }
   1306                         } else {
   1307                             SkASSERT(fInStruct);
   1308                 #if 0
   1309                             fBmhStructDef = root->find(child.fName, RootDefinition::AllowParens::kNo);
   1310                             if (nullptr == fBmhStructDef) {
   1311                                 fBmhStructDef = root->find(root->fName + "::" + child.fName,
   1312                                         RootDefinition::AllowParens::kNo);
   1313                             }
   1314                             if (!fBmhStructDef) {
   1315                                 this->lf(2);
   1316                                 fIndent = 0;
   1317                                 this->writeBlock((int) (fStart - bodyEnd), bodyEnd);
   1318                                 this->lfcr();
   1319                                 continue;
   1320                             }
   1321                 #endif
   1322                             Definition* codeBlock = nullptr;
   1323                             Definition* nextBlock = nullptr;
   1324                             for (auto test : fBmhStructDef->fChildren) {
   1325                                 if (MarkType::kCode == test->fMarkType) {
   1326                                     SkASSERT(!codeBlock);  // FIXME: check enum for correct order earlier
   1327                                     codeBlock = test;
   1328                                     continue;
   1329                                 }
   1330                                 if (codeBlock) {
   1331                                     nextBlock = test;
   1332                                     break;
   1333                                 }
   1334                             }
   1335                             // FIXME: trigger error earlier if inner #Struct or #Class is missing #Code
   1336                             if (!fBmhStructDef->fDeprecated) {
   1337                                 SkASSERT(codeBlock);
   1338                                 SkASSERT(nextBlock);  // FIXME: check enum for correct order earlier
   1339                                 const char* commentStart = codeBlock->fTerminator;
   1340                                 const char* commentEnd = nextBlock->fStart;
   1341                                 fIndentNext = true;
   1342                                 this->structOut(root, *fBmhStructDef, commentStart, commentEnd);
   1343                             }
   1344                         }
   1345                         fDeferComment = nullptr;
   1346                     } else {
   1347                        ; // empty forward reference, nothing to do here
   1348                     }
   1349                     break;
   1350                 case KeyWord::kEnum: {
   1351                     fInEnum = true;
   1352                     this->enumHeaderOut(root, child);
   1353                     this->enumSizeItems(child);
   1354                 } break;
   1355                 case KeyWord::kConst:
   1356                 case KeyWord::kConstExpr:
   1357                     sawConst = !memberStart || staticOnly;
   1358                     if (!memberStart) {
   1359                         memberStart = &child;
   1360                         staticOnly = true;
   1361                     }
   1362                     break;
   1363                 case KeyWord::kStatic:
   1364                     if (!memberStart) {
   1365                         memberStart = &child;
   1366                         staticOnly = true;
   1367                     }
   1368                     break;
   1369                 case KeyWord::kInt:
   1370                 case KeyWord::kUint8_t:
   1371                 case KeyWord::kUint16_t:
   1372                 case KeyWord::kUint32_t:
   1373                 case KeyWord::kUint64_t:
   1374                 case KeyWord::kUnsigned:
   1375                 case KeyWord::kSize_t:
   1376                 case KeyWord::kFloat:
   1377                 case KeyWord::kBool:
   1378                 case KeyWord::kChar:
   1379                 case KeyWord::kVoid:
   1380                     staticOnly = false;
   1381                     if (!memberStart) {
   1382                         memberStart = &child;
   1383                     }
   1384                     break;
   1385                 case KeyWord::kPublic:
   1386                 case KeyWord::kPrivate:
   1387                 case KeyWord::kProtected:
   1388                 case KeyWord::kFriend:
   1389                 case KeyWord::kInline:
   1390                 case KeyWord::kSK_API:
   1391                 case KeyWord::kTypedef:
   1392                     break;
   1393                 case KeyWord::kSK_BEGIN_REQUIRE_DENSE:
   1394                     requireDense = &child;
   1395                     break;
   1396                 default:
   1397                     SkASSERT(0);
   1398             }
   1399             if (KeyWord::kUint8_t == child.fKeyWord) {
   1400                 continue;
   1401             } else {
   1402                 if (fInEnum && KeyWord::kClass == child.fChildren[0]->fKeyWord) {
   1403                     if (!this->populate(child.fChildren[0], &pair, root)) {
   1404                         return false;
   1405                     }
   1406                 } else {
   1407                     if (!this->populate(&child, &pair, root)) {
   1408                         return false;
   1409                     }
   1410                     if (KeyWord::kClass == child.fKeyWord || KeyWord::kStruct == child.fKeyWord) {
   1411                         if (fInStruct) {
   1412                             fInStruct = false;
   1413                             do {
   1414                                 SkASSERT(root);
   1415                                 root = const_cast<RootDefinition*>(root->fParent->asRoot());
   1416                             } while (MarkType::kTopic == root->fMarkType ||
   1417                                     MarkType::kSubtopic == root->fMarkType);
   1418                             SkASSERT(MarkType::kStruct == root->fMarkType ||
   1419                             MarkType::kClass == root->fMarkType);
   1420                             fPendingMethod = false;
   1421                             if (startDef) {
   1422                                 fPendingMethod = find_start(startDef, fStart);
   1423                             }
   1424                             fOutdentNext = !fPendingMethod;
   1425                         }
   1426                     }
   1427                 }
   1428             }
   1429             continue;
   1430         }
   1431         if (Definition::Type::kBracket == child.fType) {
   1432             if (KeyWord::kEnum == child.fParent->fKeyWord ||
   1433                     (KeyWord::kClass == child.fParent->fKeyWord && child.fParent->fParent &&
   1434                     KeyWord::kEnum == child.fParent->fParent->fKeyWord)) {
   1435                 SkASSERT(Bracket::kBrace == child.fBracket);
   1436                 this->enumMembersOut(root, child);
   1437                 this->writeString("};");
   1438                 this->lf(2);
   1439                 startDef = child.fParent;
   1440                 fStart = child.fParent->fContentEnd;
   1441                 SkASSERT(';' == fStart[0]);
   1442                 ++fStart;
   1443                 fDeferComment = nullptr;
   1444                 fInEnum = false;
   1445                 if (fIndentNext) {
   1446 //                    fIndent -= 4;
   1447                     fIndentNext = false;
   1448                 }
   1449                 continue;
   1450             }
   1451             if (fAttrDeprecated) {
   1452                 continue;
   1453             }
   1454             fDeferComment = nullptr;
   1455             if (KeyWord::kClass == def->fKeyWord || KeyWord::kStruct == def->fKeyWord) {
   1456                 fIndentNext = true;
   1457             }
   1458             if (!this->populate(&child, &pair, root)) {
   1459                 return false;
   1460             }
   1461             continue;
   1462         }
   1463         if (Definition::Type::kWord == child.fType) {
   1464             if (MarkType::kMember == child.fMarkType) {
   1465                 if (!memberStart) {
   1466                     auto iter = def->fTokens.begin();
   1467                     std::advance(iter, child.fParentIndex - 1);
   1468                     memberStart = &*iter;
   1469                     staticOnly = false;
   1470                     if (!fStructMemberTab) {
   1471                         SkASSERT(KeyWord::kStruct == def->fParent->fKeyWord);
   1472                         fIndent += 4;
   1473                         this->structSizeMembers(*def->fParent);
   1474                         fIndent -= 4;
   1475 //                        SkASSERT(!fIndentNext);
   1476                         fIndentNext = true;
   1477                     }
   1478                 }
   1479                 SkASSERT(fBmhStructDef);
   1480                 if (!fBmhStructDef->fDeprecated) {
   1481                     memberEnd = this->structMemberOut(memberStart, child);
   1482                     startDef = &child;
   1483                     fStart = child.fContentEnd + 1;
   1484                     fDeferComment = nullptr;
   1485                 }
   1486             } else if (MarkType::kNone == child.fMarkType && sawConst
   1487                     && fEnumDef && !fEnumDef->fDeprecated) {
   1488                 const Definition* bmhConst = nullptr;
   1489                 string match;
   1490                 if (root) {
   1491                     match = root->fName + "::";
   1492                 }
   1493                 match += string(child.fContentStart, child.fContentEnd - child.fContentStart);
   1494                 for (auto enumChild : fEnumDef->fChildren) {
   1495                     if (MarkType::kConst == enumChild->fMarkType && enumChild->fName == match) {
   1496                         bmhConst = enumChild;
   1497                         break;
   1498                     }
   1499                 }
   1500                 if (bmhConst) {
   1501                     this->constOut(memberStart, child, bmhConst);
   1502                     fDeferComment = nullptr;
   1503                     sawConst = false;
   1504                 }
   1505             }
   1506             if (child.fMemberStart) {
   1507                 memberStart = &child;
   1508                 staticOnly = false;
   1509             }
   1510             const char attrDeprecated[] = "SK_ATTR_DEPRECATED";
   1511             const size_t attrDeprecatedLen = sizeof(attrDeprecated) - 1;
   1512             if (attrDeprecatedLen == child.fContentEnd - child.fContentStart &&
   1513                     !strncmp(attrDeprecated, child.fStart, attrDeprecatedLen)) {
   1514                 fAttrDeprecated = &child;
   1515             }
   1516             continue;
   1517         }
   1518         if (Definition::Type::kPunctuation == child.fType) {
   1519             if (Punctuation::kSemicolon == child.fPunctuation) {
   1520                 memberStart = nullptr;
   1521                 sawConst = false;
   1522                 staticOnly = false;
   1523                 if (inStruct) {
   1524                     fInStruct = false;
   1525                 }
   1526                 continue;
   1527             }
   1528             if (Punctuation::kLeftBrace == child.fPunctuation ||
   1529                     Punctuation::kColon == child.fPunctuation ||
   1530                     Punctuation::kAsterisk == child.fPunctuation
   1531                 ) {
   1532                 continue;
   1533             }
   1534         }
   1535     }
   1536     return true;
   1537 }
   1538 
   1539 bool IncludeWriter::populate(BmhParser& bmhParser) {
   1540     bool allPassed = true;
   1541     for (auto& includeMapper : fIncludeMap) {
   1542         size_t lastSlash = includeMapper.first.rfind('/');
   1543         if (string::npos == lastSlash) {
   1544             lastSlash = includeMapper.first.rfind('\\');
   1545         }
   1546         if (string::npos == lastSlash || lastSlash >= includeMapper.first.length() - 1) {
   1547             return this->reportError<bool>("malformed include name");
   1548         }
   1549         string fileName = includeMapper.first.substr(lastSlash + 1);
   1550         if (".h" != fileName.substr(fileName.length() - 2)) {
   1551             return this->reportError<bool>("expected fileName.h");
   1552         }
   1553         string skClassName = fileName.substr(0, fileName.length() - 2);
   1554         fOut = fopen(fileName.c_str(), "wb");
   1555         if (!fOut) {
   1556             SkDebugf("could not open output file %s\n", fileName.c_str());
   1557             return false;
   1558         }
   1559         if (bmhParser.fClassMap.end() == bmhParser.fClassMap.find(skClassName)) {
   1560             return this->reportError<bool>("could not find bmh class");
   1561         }
   1562         fBmhParser = &bmhParser;
   1563         RootDefinition* root = &bmhParser.fClassMap[skClassName];
   1564         fRootTopic = root->fParent;
   1565         root->clearVisited();
   1566         fStart = includeMapper.second.fContentStart;
   1567         fEnd = includeMapper.second.fContentEnd;
   1568         fAnonymousEnumCount = 1;
   1569         allPassed &= this->populate(&includeMapper.second, nullptr, root);
   1570         this->writeBlock((int) (fEnd - fStart), fStart);
   1571         fIndent = 0;
   1572         this->lfcr();
   1573         this->writePending();
   1574         fclose(fOut);
   1575         fflush(fOut);
   1576         size_t slash = fFileName.find_last_of('/');
   1577         if (string::npos == slash) {
   1578             slash = 0;
   1579         }
   1580         size_t back = fFileName.find_last_of('\\');
   1581         if (string::npos == back) {
   1582             back = 0;
   1583         }
   1584         string dir = fFileName.substr(0, SkTMax(slash, back) + 1);
   1585         string readname = dir + fileName;
   1586         if (this->writtenFileDiffers(fileName, readname)) {
   1587             SkDebugf("wrote updated %s\n", fileName.c_str());
   1588         } else {
   1589             remove(fileName.c_str());
   1590         }
   1591     }
   1592     return allPassed;
   1593 }
   1594 
   1595 // change Xxx_Xxx to xxx xxx
   1596 static string ConvertRef(const string str, bool first) {
   1597     string substitute;
   1598     for (char c : str) {
   1599         if ('_' == c) {
   1600             c = ' ';  // change Xxx_Xxx to xxx xxx
   1601         } else if (isupper(c) && !first) {
   1602             c = tolower(c);
   1603         }
   1604         substitute += c;
   1605         first = false;
   1606     }
   1607     return substitute;
   1608 }
   1609 
   1610 string IncludeWriter::resolveMethod(const char* start, const char* end, bool first) {
   1611     string methodname(start, end - start);
   1612     if (string::npos != methodname.find("()")) {
   1613         return "";
   1614     }
   1615     string substitute;
   1616     auto rootDefIter = fBmhParser->fMethodMap.find(methodname);
   1617     if (fBmhParser->fMethodMap.end() != rootDefIter) {
   1618         substitute = methodname + "()";
   1619     } else {
   1620         RootDefinition* parent = nullptr;
   1621         for (auto candidate : fRootTopic->fChildren) {
   1622             if (MarkType::kClass == candidate->fMarkType
   1623                     || MarkType::kStruct == candidate->fMarkType) {
   1624                 parent = candidate->asRoot();
   1625                 break;
   1626             }
   1627         }
   1628         SkASSERT(parent);
   1629         auto defRef = parent->find(parent->fName + "::" + methodname,
   1630                 RootDefinition::AllowParens::kNo);
   1631         if (defRef && MarkType::kMethod == defRef->fMarkType) {
   1632             substitute = methodname + "()";
   1633         }
   1634     }
   1635     if (fMethodDef && methodname == fMethodDef->fName) {
   1636         TextParser report(fBmhMethod);
   1637         report.reportError("method should not include references to itself");
   1638         return "";
   1639     }
   1640     if (fBmhMethod) {
   1641         for (auto child : fBmhMethod->fChildren) {
   1642             if (MarkType::kParam != child->fMarkType) {
   1643                 continue;
   1644             }
   1645             if (methodname == child->fName) {
   1646                 return "";
   1647             }
   1648         }
   1649     }
   1650     return substitute;
   1651 }
   1652 
   1653 string IncludeWriter::resolveRef(const char* start, const char* end, bool first,
   1654         RefType* refType) {
   1655         // look up Xxx_Xxx
   1656     string undername(start, end - start);
   1657     for (const auto& external : fBmhParser->fExternals) {
   1658         if (external.fName == undername) {
   1659             *refType = RefType::kExternal;
   1660             return external.fName;
   1661         }
   1662     }
   1663     *refType = RefType::kNormal;
   1664     SkASSERT(string::npos == undername.find(' '));
   1665     const Definition* rootDef = nullptr;
   1666     string substitute;
   1667     {
   1668         auto rootDefIter = fBmhParser->fTopicMap.find(undername);
   1669         if (fBmhParser->fTopicMap.end() != rootDefIter) {
   1670             rootDef = rootDefIter->second;
   1671         } else {
   1672             string prefixedName = fRootTopic->fName + '_' + undername;
   1673             rootDefIter = fBmhParser->fTopicMap.find(prefixedName);
   1674             if (fBmhParser->fTopicMap.end() != rootDefIter) {
   1675                 rootDef = rootDefIter->second;
   1676             } else if (fBmhStructDef) {
   1677                 string localPrefix = fBmhStructDef->fFiddle + '_' + undername;
   1678                 rootDefIter = fBmhParser->fTopicMap.find(localPrefix);
   1679                 if (fBmhParser->fTopicMap.end() != rootDefIter) {
   1680                     rootDef = rootDefIter->second;
   1681                 }
   1682                 if (!rootDef) {
   1683                     size_t doubleColon = fBmhStructDef->fName.rfind("::");
   1684                     if (string::npos != doubleColon && undername
   1685                             == fBmhStructDef->fName.substr(doubleColon + 2)) {
   1686                         substitute = fBmhStructDef->fName;
   1687                     }
   1688                 }
   1689             }
   1690             if (!rootDef && !substitute.length()) {
   1691                 auto aliasIter = fBmhParser->fAliasMap.find(undername);
   1692                 if (fBmhParser->fAliasMap.end() != aliasIter) {
   1693                     rootDef = aliasIter->second;
   1694                 } else if (!first) {
   1695                     SkDebugf("unfound: %s\n", undername.c_str());
   1696                     this->reportError("reference unfound");
   1697                     return "";
   1698                 }
   1699             }
   1700         }
   1701     }
   1702     if (rootDef) {
   1703         MarkType rootType = rootDef->fMarkType;
   1704         bool isTopic = MarkType::kSubtopic == rootType || MarkType::kTopic == rootType;
   1705         auto substituteParent = MarkType::kAlias == rootType ? rootDef->fParent :
   1706                 isTopic ? rootDef : nullptr;
   1707         if (substituteParent) {
   1708             for (auto child : substituteParent->fChildren) {
   1709                 if (MarkType::kSubstitute == child->fMarkType) {
   1710                     substitute = string(child->fContentStart,
   1711                             (int) (child->fContentEnd - child->fContentStart));
   1712                     break;
   1713                 }
   1714             }
   1715         }
   1716         if (!substitute.length()) {
   1717             string match = rootDef->fName;
   1718             size_t index;
   1719             while (string::npos != (index = match.find('_'))) {
   1720                 match.erase(index, 1);
   1721             }
   1722             string skmatch = "Sk" + match;
   1723             auto parent = substituteParent ? substituteParent : rootDef;
   1724             for (auto child : parent->fChildren) {
   1725                 // there may be more than one
   1726                 // prefer the one mostly closely matching in text
   1727                 if ((MarkType::kClass == child->fMarkType ||
   1728                     MarkType::kStruct == child->fMarkType ||
   1729                     (MarkType::kEnum == child->fMarkType && !child->fAnonymous) ||
   1730                     MarkType::kEnumClass == child->fMarkType) && (match == child->fName ||
   1731                     skmatch == child->fName)) {
   1732                     substitute = child->fName;
   1733                     break;
   1734                 }
   1735             }
   1736         }
   1737         if (!substitute.length()) {
   1738             for (auto child : rootDef->fChildren) {
   1739                 // there may be more than one
   1740                 // if so, it's a bug since it's unknown which is the right one
   1741                 if (MarkType::kClass == child->fMarkType ||
   1742                         MarkType::kStruct == child->fMarkType ||
   1743                         (MarkType::kEnum == child->fMarkType && !child->fAnonymous) ||
   1744                         MarkType::kEnumClass == child->fMarkType) {
   1745                     SkASSERT("" == substitute);
   1746                     substitute = child->fName;
   1747                     if (MarkType::kEnum == child->fMarkType) {
   1748                         size_t parentClassEnd = substitute.find("::");
   1749                         SkASSERT(string::npos != parentClassEnd);
   1750                         string subEnd = substitute.substr(parentClassEnd + 2);
   1751                         if (fInEnum) {
   1752                             substitute = subEnd;
   1753                         }
   1754                         if (subEnd == undername) {
   1755                             break;
   1756                         }
   1757                     }
   1758                 }
   1759             }
   1760         }
   1761         if (!substitute.length()) {
   1762             const Definition* parent = rootDef;
   1763             do {
   1764                 parent = parent->fParent;
   1765             } while (parent && (MarkType::kSubtopic == parent->fMarkType
   1766                         || MarkType::kTopic == parent->fMarkType));
   1767             if (parent) {
   1768                 if (MarkType::kClass == parent->fMarkType ||
   1769                         MarkType::kStruct == parent->fMarkType ||
   1770                         (MarkType::kEnum == parent->fMarkType && !parent->fAnonymous) ||
   1771                         MarkType::kEnumClass == parent->fMarkType) {
   1772                     if (parent->fParent != fRootTopic) {
   1773                         substitute = parent->fName;
   1774                         substitute += ' ';
   1775                         substitute += ConvertRef(rootDef->fName, false);
   1776                     } else {
   1777                         substitute += ConvertRef(undername, first);
   1778                     }
   1779                 }
   1780             }
   1781         }
   1782     }
   1783     // Ensure first word after period is capitalized if substitute is lower cased.
   1784     if (first && isupper(start[0]) && substitute.length() > 0 && islower(substitute[0])) {
   1785         substitute[0] = start[0];
   1786     }
   1787     return substitute;
   1788 }
   1789 
   1790 int IncludeWriter::lookupMethod(const PunctuationState punctuation, const Word word,
   1791         const int lastSpace, const int run, int lastWrite, const char* data,
   1792         bool hasIndirection) {
   1793     int wordStart = lastSpace;
   1794     while (' ' >= data[wordStart]) {
   1795         ++wordStart;
   1796     }
   1797     const int wordEnd = PunctuationState::kDelimiter == punctuation ||
   1798             PunctuationState::kParen == punctuation ||
   1799             PunctuationState::kPeriod == punctuation ? run - 1 : run;
   1800     string temp;
   1801     if (hasIndirection && '(' != data[wordEnd - 1] && ')' != data[wordEnd - 1]) {
   1802         // FIXME: hard-coded to assume a.b or a->b is a.b() or a->b().
   1803         // need to check class a for member b to see if this is so
   1804         TextParser parser(fFileName, &data[wordStart], &data[wordEnd], fLineCount);
   1805         const char* indirection = parser.anyOf(".>");
   1806         if (&data[wordEnd] <= &indirection[2] || 'f' != indirection[1] ||
   1807                 !isupper(indirection[2])) {
   1808             temp = string(&data[wordStart], wordEnd - wordStart) + "()";
   1809         }
   1810     } else {
   1811         temp = this->resolveMethod(&data[wordStart], &data[wordEnd], Word::kFirst == word);
   1812     }
   1813     if (temp.length()) {
   1814         if (wordStart > lastWrite) {
   1815             SkASSERT(data[wordStart - 1] >= ' ');
   1816             if (' ' == data[lastWrite]) {
   1817                 this->writeSpace();
   1818             }
   1819             this->writeBlockTrim(wordStart - lastWrite, &data[lastWrite]);
   1820             if (' ' == data[wordStart - 1]) {
   1821                 this->writeSpace();
   1822             }
   1823         }
   1824         SkASSERT(temp[temp.length() - 1] > ' ');
   1825         this->writeString(temp.c_str());
   1826         lastWrite = wordEnd;
   1827     }
   1828     return lastWrite;
   1829 }
   1830 
   1831 int IncludeWriter::lookupReference(const PunctuationState punctuation, const Word word,
   1832         const int start, const int run, int lastWrite, const char last, const char* data) {
   1833     const int end = PunctuationState::kDelimiter == punctuation ||
   1834             PunctuationState::kParen == punctuation ||
   1835             PunctuationState::kPeriod == punctuation ? run - 1 : run;
   1836     RefType refType = RefType::kUndefined;
   1837     string resolved = string(&data[start], (size_t) (end - start));
   1838     string temp = this->resolveRef(&data[start], &data[end], Word::kFirst == word, &refType);
   1839     if (!temp.length()) {
   1840         if (Word::kFirst != word && '_' != last) {
   1841             temp = ConvertRef(resolved, false);
   1842         }
   1843     }
   1844     if (temp.length()) {
   1845         if (start > lastWrite) {
   1846             SkASSERT(data[start - 1] >= ' ');
   1847             if (' ' == data[lastWrite]) {
   1848                 this->writeSpace();
   1849             }
   1850             this->writeBlockTrim(start - lastWrite, &data[lastWrite]);
   1851             if (' ' == data[start - 1]) {
   1852                 this->writeSpace();
   1853             }
   1854         }
   1855         SkASSERT(temp[temp.length() - 1] > ' ');
   1856         this->writeString(temp.c_str());
   1857         lastWrite = end;
   1858     }
   1859     return lastWrite;
   1860 }
   1861 
   1862 /* returns true if rewriteBlock wrote linefeeds */
   1863 IncludeWriter::Wrote IncludeWriter::rewriteBlock(int size, const char* data, Phrase phrase) {
   1864     bool wroteLineFeeds = false;
   1865     while (size > 0 && data[0] <= ' ') {
   1866         --size;
   1867         ++data;
   1868     }
   1869     while (size > 0 && data[size - 1] <= ' ') {
   1870         --size;
   1871     }
   1872     if (0 == size) {
   1873         return Wrote::kNone;
   1874     }
   1875     int run = 0;
   1876     Word word = Word::kStart;
   1877     PunctuationState punctuation = Phrase::kNo == phrase ?
   1878             PunctuationState::kStart : PunctuationState::kSpace;
   1879     int start = 0;
   1880     int lastWrite = 0;
   1881     int lineFeeds = 0;
   1882     int lastPrintable = 0;
   1883     int lastSpace = -1;
   1884     char c = 0;
   1885     char last = 0;
   1886     bool embeddedIndirection = false;
   1887     bool embeddedSymbol = false;
   1888     bool hasLower = false;
   1889     bool hasUpper = false;
   1890     bool hasIndirection = false;
   1891     bool hasSymbol = false;
   1892     while (run < size) {
   1893         last = c;
   1894         c = data[run];
   1895         SkASSERT(' ' <= c || '\n' == c);
   1896         if (lineFeeds && ' ' < c) {
   1897             if (lastPrintable >= lastWrite) {
   1898                 if (' ' == data[lastWrite]) {
   1899                     this->writeSpace();
   1900                     lastWrite++;
   1901                 }
   1902                 this->writeBlock(lastPrintable - lastWrite + 1, &data[lastWrite]);
   1903             }
   1904             if (lineFeeds > 1) {
   1905                 this->lf(2);
   1906             }
   1907             this->lfcr(); // defer the indent until non-whitespace is seen
   1908             lastWrite = run;
   1909             lineFeeds = 0;
   1910         }
   1911         if (' ' < c) {
   1912             lastPrintable = run;
   1913         }
   1914         switch (c) {
   1915             case '\n':
   1916                 ++lineFeeds;
   1917                 wroteLineFeeds = true;
   1918             case ' ':
   1919                 switch (word) {
   1920                     case Word::kStart:
   1921                         break;
   1922                     case Word::kUnderline:
   1923                     case Word::kCap:
   1924                     case Word::kFirst:
   1925                         if (!hasLower) {
   1926                             break;
   1927                         }
   1928                         lastWrite = this->lookupReference(punctuation, word, start, run,
   1929                                 lastWrite, last, data);
   1930                         break;
   1931                     case Word::kMixed:
   1932                         if (hasUpper && hasLower && !hasSymbol && lastSpace > 0) {
   1933                             lastWrite = this->lookupMethod(punctuation, word, lastSpace, run,
   1934                                     lastWrite, data, hasIndirection);
   1935                         }
   1936                         break;
   1937                     default:
   1938                         SkASSERT(0);
   1939                 }
   1940                 punctuation = PunctuationState::kPeriod == punctuation ||
   1941                         (PunctuationState::kStart == punctuation && ' ' >= last) ?
   1942                         PunctuationState::kStart : PunctuationState::kSpace;
   1943                 word = Word::kStart;
   1944                 embeddedIndirection = false;
   1945                 embeddedSymbol = false;
   1946                 hasLower = false;
   1947                 hasUpper = false;
   1948                 hasIndirection = false;
   1949                 hasSymbol = false;
   1950                 lastSpace = run;
   1951                 break;
   1952             case '.': case ',': case ';': case ':': case ')':
   1953                 switch (word) {
   1954                     case Word::kStart:
   1955                         punctuation = PunctuationState::kDelimiter;
   1956                     case Word::kCap:
   1957                     case Word::kFirst:
   1958                     case Word::kUnderline:
   1959                     case Word::kMixed:
   1960                         if (PunctuationState::kDelimiter == punctuation ||
   1961                                 PunctuationState::kPeriod == punctuation) {
   1962                             word = Word::kMixed;
   1963                         }
   1964                         punctuation = '.' == c ? PunctuationState::kPeriod :
   1965                                 PunctuationState::kDelimiter;
   1966                         break;
   1967                     default:
   1968                         SkASSERT(0);
   1969                 }
   1970                 ('.' == c ? embeddedIndirection : embeddedSymbol) = true;
   1971                 break;
   1972             case '>':
   1973                 if ('-' == last) {
   1974                     embeddedIndirection = true;
   1975                     break;
   1976                 }
   1977             case '\'': // possessive apostrophe isn't treated as delimiting punctation
   1978             case '\"': // quote is passed straight through
   1979             case '=':
   1980             case '!':  // assumed not to be punctuation, but a programming symbol
   1981             case '&': case '<': case '{': case '}': case '/': case '*': case '[': case ']':
   1982                 word = Word::kMixed;
   1983                 embeddedSymbol = true;
   1984                 break;
   1985             case '(':
   1986                 if (' ' == last) {
   1987                     punctuation = PunctuationState::kParen;
   1988                 } else {
   1989                     word = Word::kMixed;
   1990                 }
   1991                 embeddedSymbol = true;
   1992                 break;
   1993             case '_':
   1994                 switch (word) {
   1995                     case Word::kStart:
   1996                         word = Word::kMixed;
   1997                         break;
   1998                     case Word::kCap:
   1999                     case Word::kFirst:
   2000                     case Word::kUnderline:
   2001                         word = Word::kUnderline;
   2002                         break;
   2003                     case Word::kMixed:
   2004                         break;
   2005                     default:
   2006                         SkASSERT(0);
   2007                 }
   2008                 hasSymbol |= embeddedSymbol;
   2009                 break;
   2010             case '+':
   2011                 // hackery to allow C++
   2012                 SkASSERT('C' == last || '+' == last);  // FIXME: don't allow + outside of #Formula
   2013                 break;
   2014             case 'A': case 'B': case 'C': case 'D': case 'E':
   2015             case 'F': case 'G': case 'H': case 'I': case 'J':
   2016             case 'K': case 'L': case 'M': case 'N': case 'O':
   2017             case 'P': case 'Q': case 'R': case 'S': case 'T':
   2018             case 'U': case 'V': case 'W': case 'X': case 'Y':
   2019             case 'Z':
   2020                 switch (word) {
   2021                     case Word::kStart:
   2022                         word = PunctuationState::kStart == punctuation ? Word::kFirst : Word::kCap;
   2023                         start = run;
   2024                         break;
   2025                     case Word::kCap:
   2026                     case Word::kFirst:
   2027                         if (!isupper(last) && '~' != last) {
   2028                             word = Word::kMixed;
   2029                         }
   2030                         break;
   2031                     case Word::kUnderline:
   2032                         // some word in Xxx_XXX_Xxx can be all upper, but all can't: XXX_XXX
   2033                         if ('_' != last && !isupper(last)) {
   2034                             word = Word::kMixed;
   2035                         }
   2036                         break;
   2037                     case Word::kMixed:
   2038                         break;
   2039                     default:
   2040                         SkASSERT(0);
   2041                 }
   2042                 hasUpper = true;
   2043                 if (PunctuationState::kPeriod == punctuation ||
   2044                         PunctuationState::kDelimiter == punctuation) {
   2045                     word = Word::kMixed;
   2046                 }
   2047                 hasIndirection |= embeddedIndirection;
   2048                 hasSymbol |= embeddedSymbol;
   2049                 break;
   2050             case 'a': case 'b': case 'c': case 'd': case 'e':
   2051             case 'f': case 'g': case 'h': case 'i': case 'j':
   2052             case 'k': case 'l': case 'm': case 'n': case 'o':
   2053             case 'p': case 'q': case 'r': case 's': case 't':
   2054             case 'u': case 'v': case 'w': case 'x': case 'y':
   2055             case 'z':
   2056             case '0': case '1': case '2': case '3': case '4':
   2057             case '5': case '6': case '7': case '8': case '9':
   2058             case '-':
   2059                 switch (word) {
   2060                     case Word::kStart:
   2061                         word = Word::kMixed;
   2062                         break;
   2063                     case Word::kMixed:
   2064                     case Word::kCap:
   2065                     case Word::kFirst:
   2066                     case Word::kUnderline:
   2067                         break;
   2068                     default:
   2069                         SkASSERT(0);
   2070                 }
   2071                 hasLower = true;
   2072                 punctuation = PunctuationState::kStart;
   2073                 hasIndirection |= embeddedIndirection;
   2074                 hasSymbol |= embeddedSymbol;
   2075                 break;
   2076             case '~':
   2077                 SkASSERT(Word::kStart == word);
   2078                 word = PunctuationState::kStart == punctuation ? Word::kFirst : Word::kCap;
   2079                 start = run;
   2080                 hasUpper = true;
   2081                 hasIndirection |= embeddedIndirection;
   2082                 hasSymbol |= embeddedSymbol;
   2083                 break;
   2084             default:
   2085                 SkASSERT(0);
   2086         }
   2087         ++run;
   2088     }
   2089     if ((word == Word::kCap || word == Word::kFirst || word == Word::kUnderline) && hasLower) {
   2090         lastWrite = this->lookupReference(punctuation, word, start, run, lastWrite, last, data);
   2091     } else if (word == Word::kMixed && hasUpper && hasLower && !hasSymbol && lastSpace > 0) {
   2092         lastWrite = this->lookupMethod(punctuation, word, lastSpace, run, lastWrite, data,
   2093                 hasIndirection && !hasSymbol);
   2094     }
   2095     if (run > lastWrite) {
   2096         if (' ' == data[lastWrite]) {
   2097             this->writeSpace();
   2098         }
   2099         this->writeBlock(run - lastWrite, &data[lastWrite]);
   2100     }
   2101     return wroteLineFeeds ? Wrote::kLF : Wrote::kChars;
   2102 }
   2103