• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2017 Google Inc.
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 
8 #include "bookmaker.h"
9 
constOut(const Definition * memberStart,const Definition & child,const Definition * bmhConst)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 
descriptionOut(const Definition * def,SkipFirstLine skipFirstLine)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 
enumHeaderOut(const RootDefinition * root,const Definition & child)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 
enumMembersOut(const RootDefinition * root,Definition & child)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 
enumSizeItems(const Definition & child)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
methodOut(const Definition * method,const Definition & child)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 
structOut(const Definition * root,const Definition & child,const char * commentStart,const char * commentEnd)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 
findMemberCommentBlock(const vector<Definition * > & bmhChildren,const string & name) const732 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 
structMemberOut(const Definition * memberStart,const Definition & child)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
structSetMembersShort(const vector<Definition * > & bmhChildren)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 
structSizeMembers(const Definition & child)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 
find_start(const Definition * startDef,const char * start)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 
populate(Definition * def,ParentPair * prevPair,RootDefinition * root)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 
populate(BmhParser & bmhParser)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
ConvertRef(const string str,bool first)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 
resolveMethod(const char * start,const char * end,bool first)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 
resolveRef(const char * start,const char * end,bool first,RefType * refType)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 
lookupMethod(const PunctuationState punctuation,const Word word,const int lastSpace,const int run,int lastWrite,const char * data,bool hasIndirection)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 
lookupReference(const PunctuationState punctuation,const Word word,const int start,const int run,int lastWrite,const char last,const char * data)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 */
rewriteBlock(int size,const char * data,Phrase phrase)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