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 <chrono>
9 #include <ctime>
10 
11 #include "bmhParser.h"
12 #include "includeWriter.h"
13 
checkChildCommentLength(const Definition * parent,MarkType childType) const14 bool IncludeWriter::checkChildCommentLength(const Definition* parent, MarkType childType) const {
15     bool oneMember = false;
16     for (auto& item : parent->fChildren) {
17         if (childType != item->fMarkType) {
18             continue;
19         }
20         oneMember = true;
21         int lineLen = 0;
22         for (auto& itemChild : item->fChildren) {
23             if (MarkType::kLine == itemChild->fMarkType) {
24                 lineLen = itemChild->length();
25                 break;
26             }
27         }
28         if (!lineLen) {
29             item->reportError<void>("missing #Line");
30         }
31         if (fEnumItemCommentTab + lineLen >= 100) {
32 // if too long, remove spaces until it fits, or wrap
33 //            item->reportError<void>("#Line comment too long");
34         }
35     }
36     return oneMember;
37 }
38 
checkEnumLengths(const Definition & child,string enumName,ItemLength * length) const39 void IncludeWriter::checkEnumLengths(const Definition& child, string enumName, ItemLength* length) const {
40     const Definition* enumItem = this->matchMemberName(enumName, child);
41     if (std::any_of(enumItem->fChildren.begin(), enumItem->fChildren.end(),
42             [](Definition* child){return MarkType::kNoJustify == child->fMarkType;})) {
43         return;
44     }
45     string comment = this->enumMemberComment(enumItem, child);
46     int lineLimit = 100 - fIndent - 7; // 7: , space //!< space
47     if (length->fCurValue) {
48         lineLimit -= 3; // space = space
49     }
50     if (length->fCurName + length->fCurValue + (int) comment.length() < lineLimit) {
51         length->fLongestName = SkTMax(length->fLongestName, length->fCurName);
52         length->fLongestValue = SkTMax(length->fLongestValue, length->fCurValue);
53     }
54 }
55 
constOut(const Definition * memberStart,const Definition * bmhConst)56 void IncludeWriter::constOut(const Definition* memberStart, const Definition* bmhConst) {
57     const char* bodyEnd = fDeferComment ? fDeferComment->fContentStart - 1 :
58         memberStart->fContentStart;
59     this->firstBlockTrim((int) (bodyEnd - fStart), fStart);  // may write nothing
60     this->lf(2);
61     this->indentDeferred(IndentKind::kConstOut);
62     if (fStructEnded) {
63         fIndent = fICSStack.size() * 4;
64         fStructEnded = false;
65     }
66     // comment may be legitimately empty; typedef may not have separate comment (for now)
67     fReturnOnWrite = true;
68     bool commentHasLength = this->descriptionOut(bmhConst, SkipFirstLine::kYes, Phrase::kNo);
69     fReturnOnWrite = false;
70     if (commentHasLength) {
71         this->writeCommentHeader();
72         fIndent += 4;
73         if (!this->descriptionOut(bmhConst, SkipFirstLine::kYes, Phrase::kNo)) {
74             return memberStart->reportError<void>("expected description for const");
75         }
76         fIndent -= 4;
77         this->writeCommentTrailer(OneLine::kNo);
78     }
79     this->setStart(memberStart->fContentStart, memberStart);
80 }
81 
descriptionOut(const Definition * def,SkipFirstLine skipFirstLine,Phrase phrase)82 bool IncludeWriter::descriptionOut(const Definition* def, SkipFirstLine skipFirstLine,
83             Phrase phrase) {
84     bool wroteSomething = false;
85     const char* commentStart = def->fContentStart;
86     if (SkipFirstLine::kYes == skipFirstLine) {
87         TextParser parser(def);
88         SkAssertResult(parser.skipLine());
89         commentStart = parser.fChar;
90     }
91     int commentLen = (int) (def->fContentEnd - commentStart);
92     bool breakOut = false;
93     SkDEBUGCODE(bool wroteCode = false);
94     const Definition* lastDescription = def;
95     for (auto prop : def->fChildren) {
96         fLastDescription = lastDescription;
97         lastDescription = prop;
98         switch (prop->fMarkType) {
99             case MarkType::kCode: {
100                 bool literal = false;
101                 bool literalOutdent = false;
102                 commentLen = (int) (prop->fStart - commentStart);
103                 if (commentLen > 0) {
104                     SkASSERT(commentLen < 1000);
105                     if (Wrote::kNone != this->rewriteBlock(commentLen, commentStart, Phrase::kNo)) {
106                         if (fReturnOnWrite) {
107                             return true;
108                         }
109                         this->lf(2);
110                         wroteSomething = true;
111                     }
112                 }
113                 size_t childSize = prop->fChildren.size();
114                 if (childSize) {
115                     if (MarkType::kLiteral == prop->fChildren[0]->fMarkType) {
116                         SkASSERT(1 == childSize || 2 == childSize);  // incomplete
117                         SkASSERT(1 == childSize || MarkType::kOutdent == prop->fChildren[1]->fMarkType);
118                         commentStart = prop->fChildren[childSize - 1]->fContentStart;
119                         literal = true;
120                         literalOutdent = 2 == childSize &&
121                                 MarkType::kOutdent == prop->fChildren[1]->fMarkType;
122                     }
123                 }
124                 commentLen = (int) (prop->fContentEnd - commentStart);
125                 SkASSERT(commentLen > 0);
126                 if (literal) {
127                     if (!fReturnOnWrite && !literalOutdent) {
128                         fIndent += 4;
129                     }
130                     wroteSomething |= this->writeBlockIndent(commentLen, commentStart, false);
131                     if (fReturnOnWrite) {
132                         return true;
133                     }
134                     if (!fReturnOnWrite) {
135                         this->lf(2);
136                         if (!literalOutdent) {
137                             fIndent -= 4;
138                         }
139                     }
140                     SkDEBUGCODE(wroteCode = true);
141                 }
142                 commentStart = prop->fTerminator;
143                 } break;
144             case MarkType::kBug: {
145                 if (fReturnOnWrite) {
146                     return true;
147                 }
148                 string bugstr("(see skbug.com/" + string(prop->fContentStart,
149                     prop->fContentEnd - prop->fContentStart) + ')');
150                 this->writeString(bugstr);
151                 this->lfcr();
152                 wroteSomething = true;
153             }
154             case MarkType::kFormula: {
155                 commentLen = prop->fStart - commentStart;
156                 if (commentLen > 0) {
157                     if (Wrote::kNone != this->rewriteBlock(commentLen, commentStart, Phrase::kNo)) {
158                         if (fReturnOnWrite) {
159                             return true;
160                         }
161                         if (commentLen > 1 && '\n' == prop->fStart[-1]) {
162                             this->lf(1);
163                         } else {
164                             this->writeSpace();
165                         }
166                         wroteSomething = true;
167                     }
168                 }
169                 int saveIndent = fIndent;
170                 if (fIndent < fColumn + 1) {
171                     fIndent = fColumn + 1;
172                 }
173                 wroteSomething |= this->writeBlockIndent(prop->length(), prop->fContentStart, true);
174                 fIndent = saveIndent;
175                 if (wroteSomething && fReturnOnWrite) {
176                     return true;
177                 }
178                 commentStart = prop->fTerminator;
179                 commentLen = (int) (def->fContentEnd - commentStart);
180                 if (!fReturnOnWrite) {
181                     if (commentLen > 1 && ' ' == commentStart[0] && !fLinefeeds) {
182                         this->writeSpace();
183                     }
184                 }
185                 } break;
186             case MarkType::kDetails:
187             case MarkType::kIn:
188             case MarkType::kLine:
189             case MarkType::kToDo:
190                 commentLen = (int) (prop->fStart - commentStart);
191                 if (commentLen > 0) {
192                     SkASSERT(commentLen < 1000);
193                     if (Wrote::kNone != this->rewriteBlock(commentLen, commentStart, Phrase::kNo)) {
194                         if (fReturnOnWrite) {
195                             return true;
196                         }
197                         this->lfcr();
198                         wroteSomething = true;
199                     }
200                 }
201                 commentStart = prop->fTerminator;
202                 commentLen = (int) (def->fContentEnd - commentStart);
203                 break;
204             case MarkType::kList:
205                 commentLen = prop->fStart - commentStart;
206                 if (commentLen > 0) {
207                     if (Wrote::kNone != this->rewriteBlock(commentLen, commentStart,
208                             Phrase::kNo)) {
209                         if (fReturnOnWrite) {
210                             return true;
211                         }
212                         this->lfcr();
213                         wroteSomething = true;
214                     }
215                 }
216                 for (auto row : prop->fChildren) {
217                     SkASSERT(MarkType::kRow == row->fMarkType);
218                     for (auto column : row->fChildren) {
219                         SkASSERT(MarkType::kColumn == column->fMarkType);
220                         if (fReturnOnWrite) {
221                             return true;
222                         }
223                         this->writeString("-");
224                         this->writeSpace();
225                         wroteSomething |= this->descriptionOut(column, SkipFirstLine::kNo, Phrase::kNo);
226                         this->lf(1);
227                     }
228                 }
229                 commentStart = prop->fTerminator;
230                 commentLen = (int) (def->fContentEnd - commentStart);
231                 if ('\n' == commentStart[0] && '\n' == commentStart[1]) {
232                     this->lf(2);
233                 }
234                 break;
235             case MarkType::kPhraseRef: {
236                 commentLen = prop->fStart - commentStart;
237                 if (commentLen > 0) {
238                     if (fReturnOnWrite) {
239                         return true;
240                     }
241                     this->rewriteBlock(commentLen, commentStart, Phrase::kNo);
242                     // ince we don't do line wrapping, always insert LF before phrase
243                     this->lfcr();   // TODO: remove this once rewriteBlock rewraps paragraphs
244                     wroteSomething = true;
245                 }
246                 auto iter = fBmhParser->fPhraseMap.find(prop->fName);
247                 if (fBmhParser->fPhraseMap.end() == iter) {
248                     return this->reportError<bool>("missing phrase definition");
249                 }
250                 Definition* phraseDef = iter->second;
251                 // TODO: given TextParser(commentStart, prop->fStart + up to #) return if
252                 // it ends with two of more linefeeds, ignoring other whitespace
253                 Phrase defIsPhrase = '\n' == prop->fStart[0] && '\n' == prop->fStart[-1] ?
254                         Phrase::kNo : Phrase::kYes;
255                 if (Phrase::kNo == defIsPhrase) {
256                     this->lf(2);
257                 }
258                 const char* start = phraseDef->fContentStart;
259                 int length = phraseDef->length();
260                 auto propParams = prop->fChildren.begin();
261                 // can this share code or logic with mdout somehow?
262                 for (auto child : phraseDef->fChildren) {
263                     if (MarkType::kPhraseParam == child->fMarkType) {
264                         continue;
265                     }
266                     int localLength = child->fStart - start;
267                     if (fReturnOnWrite) {
268                         return true;
269                     }
270                     this->rewriteBlock(localLength, start, defIsPhrase);
271                     start += localLength;
272                     length -= localLength;
273                     SkASSERT(propParams != prop->fChildren.end());
274                     if (fColumn > 0) {
275                         this->writeSpace();
276                     }
277                     this->writeString((*propParams)->fName);
278                     localLength = child->fContentEnd - child->fStart;
279                     start += localLength;
280                     length -= localLength;
281                     if (isspace(start[0])) {
282                         this->writeSpace();
283                     }
284                     defIsPhrase = Phrase::kYes;
285                     wroteSomething = true;
286                 }
287                 if (length > 0) {
288                     if (fReturnOnWrite) {
289                         return true;
290                     }
291                     this->rewriteBlock(length, start, defIsPhrase);
292                 }
293                 commentStart = prop->fContentStart;
294                 commentLen = (int) (def->fContentEnd - commentStart);
295                 if (!fReturnOnWrite) {
296                     if ('\n' == commentStart[0] && '\n' == commentStart[1]) {
297                         this->lf(2);
298                     }
299                 }
300                 } break;
301             default:
302                 commentLen = (int) (prop->fStart - commentStart);
303                 breakOut = true;
304         }
305         if (breakOut) {
306             break;
307         }
308     }
309     if (!breakOut) {
310         commentLen = (int) (def->fContentEnd - commentStart);
311     }
312     SkASSERT(wroteCode || (commentLen > 0 && commentLen < 1500));
313     if (commentLen > 0) {
314         if (Wrote::kNone != this->rewriteBlock(commentLen, commentStart, phrase)) {
315             if (fReturnOnWrite) {
316                 return true;
317             }
318             wroteSomething = true;
319         }
320     }
321     SkASSERT(!fReturnOnWrite || !wroteSomething);
322     return wroteSomething;
323 }
324 
enumHeaderOut(RootDefinition * root,const Definition & child)325 void IncludeWriter::enumHeaderOut(RootDefinition* root, const Definition& child) {
326     const Definition* enumDef = nullptr;
327     const char* bodyEnd = fDeferComment ? fDeferComment->fContentStart - 1 :
328             child.fContentStart;
329     this->firstBlockTrim((int) (bodyEnd - fStart), fStart);  // may write nothing
330     this->lf(2);
331     this->indentDeferred(IndentKind::kEnumHeader);
332     fDeferComment = nullptr;
333     this->setStart(child.fContentStart, &child);
334     const auto& nameDef = child.fTokens.front();
335     string fullName;
336     if (nullptr != nameDef.fContentEnd) {
337         TextParser enumClassCheck(&nameDef);
338         const char* start = enumClassCheck.fStart;
339         size_t len = (size_t) (enumClassCheck.fEnd - start);
340         bool enumClass = enumClassCheck.skipExact("class ");
341         if (enumClass) {
342             start = enumClassCheck.fChar;
343             const char* end = enumClassCheck.anyOf(" \n;{");
344             len = (size_t) (end - start);
345         }
346         string enumName(start, len);
347         if (enumClass) {
348             child.fChildren[0]->fName = enumName;
349         }
350         fullName = root->fName + "::" + enumName;
351         enumDef = root->find(enumName, RootDefinition::AllowParens::kNo);
352         if (!enumDef) {
353             enumDef = root->find(fullName, RootDefinition::AllowParens::kNo);
354         }
355         if (!enumDef) {
356             auto mapEntry = fBmhParser->fEnumMap.find(enumName);
357             if (fBmhParser->fEnumMap.end() != mapEntry) {
358                 enumDef = &mapEntry->second;
359             }
360         }
361         if (!enumDef && enumName == root->fName) {
362             enumDef = root;
363         }
364         SkASSERT(enumDef);
365         // child[0] should be #Code comment starts at child[0].fTerminator
366             // though skip until #Code is found (in case there's a #ToDo, etc)
367         // child[1] should be #Const comment ends at child[1].fStart
368         // comment becomes enum header (if any)
369     } else {
370         string enumName(root->fName);
371         enumName += "::_anonymous";
372         if (fAnonymousEnumCount > 1) {
373             enumName += '_' + to_string(fAnonymousEnumCount);
374         }
375         enumDef = root->find(enumName, RootDefinition::AllowParens::kNo);
376         SkASSERT(enumDef);
377         ++fAnonymousEnumCount;
378     }
379     Definition* codeBlock = nullptr;
380     const char* commentStart = nullptr;
381     bool firstCodeBlocks = true;
382     bool wroteHeader = false;
383     bool lastAnchor = false;
384 //    SkDEBUGCODE(bool foundConst = false);
385     for (auto test : enumDef->fChildren) {
386         if (MarkType::kCode == test->fMarkType && firstCodeBlocks) {
387             codeBlock = test;
388             commentStart = codeBlock->fTerminator;
389             continue;
390         } else if (codeBlock) {
391             firstCodeBlocks = false;
392         }
393         if (!codeBlock) {
394             continue;
395         }
396         const char* commentEnd = test->fStart;
397         if (!wroteHeader &&
398                 !this->contentFree((int) (commentEnd - commentStart), commentStart)) {
399             if (fIndentNext) {
400                 // FIXME: how can I tell where fIdentNext gets cleared?
401                 this->indentIn(IndentKind::kEnumChild);
402             }
403             this->writeCommentHeader();
404             this->writeString("\\enum");
405             if (fullName.length() > 0) {
406                 this->writeSpace();
407                 this->writeString(fullName.c_str());
408             }
409             this->indentIn(IndentKind::kEnumChild2);
410             this->lfcr();
411             wroteHeader = true;
412         }
413         if (lastAnchor) {
414             if (commentEnd - commentStart > 1) {
415                 SkASSERT('\n' == commentStart[0]);
416                 if (' ' == commentStart[1]) {
417                     this->writeSpace();
418                 }
419             }
420             lastAnchor = false;
421         }
422         this->rewriteBlock((int) (commentEnd - commentStart), commentStart, Phrase::kNo);
423         if (MarkType::kAnchor == test->fMarkType || MarkType::kCode == test->fMarkType) {
424             bool newLine = commentEnd - commentStart > 1 &&
425                 '\n' == commentEnd[-1] && '\n' == commentEnd[-2];
426             commentStart = test->fContentStart;
427             commentEnd = MarkType::kAnchor == test->fMarkType ? test->fChildren[0]->fStart :
428                     test->fContentEnd;
429             if (newLine) {
430                 this->lf(2);
431             } else {
432                 this->writeSpace();
433             }
434             if (MarkType::kAnchor == test->fMarkType) {
435                 this->rewriteBlock((int) (commentEnd - commentStart), commentStart, Phrase::kNo);
436             } else {
437                 this->writeBlock((int) (commentEnd - commentStart), commentStart);
438                 this->lf(2);
439             }
440             lastAnchor = true;   // this->writeSpace();
441         }
442         commentStart = test->fTerminator;
443         if (MarkType::kConst == test->fMarkType) {
444             SkASSERT(codeBlock);  // FIXME: check enum for correct order earlier
445 //            SkDEBUGCODE(foundConst = true);
446             break;
447         }
448     }
449  //   SkASSERT(codeBlock);
450  //   SkASSERT(foundConst);
451     if (wroteHeader) {
452         this->indentOut();
453         this->lfcr();
454         this->writeCommentTrailer(OneLine::kNo);
455     }
456     Definition* braceHolder = child.fChildren[0];
457     if (KeyWord::kClass == braceHolder->fKeyWord) {
458         braceHolder = braceHolder->fChildren[0];
459     }
460     bodyEnd = braceHolder->fContentStart;
461     SkASSERT('{' == bodyEnd[0]);
462     ++bodyEnd;
463     this->lfcr();
464     this->writeBlock((int) (bodyEnd - fStart), fStart); // write include "enum Name {"
465     this->indentIn(IndentKind::kEnumHeader2);
466     this->singleLF();
467     this->setStart(bodyEnd, braceHolder);
468     fEnumDef = enumDef;
469 }
470 
enumMemberForComment(const Definition * currentEnumItem) const471 const Definition* IncludeWriter::enumMemberForComment(const Definition* currentEnumItem) const {
472     for (auto constItem : currentEnumItem->fChildren) {
473         if (MarkType::kLine == constItem->fMarkType) {
474             return constItem;
475         }
476     }
477     SkASSERT(0);
478     return nullptr;
479 }
480 
enumMemberComment(const Definition * currentEnumItem,const Definition & child) const481 string IncludeWriter::enumMemberComment(const Definition* currentEnumItem,
482         const Definition& child) const {
483     // #Const should always be followed by #Line, so description follows that
484     string shortComment;
485     for (auto constItem : currentEnumItem->fChildren) {
486         if (MarkType::kLine == constItem->fMarkType) {
487             shortComment = string(constItem->fContentStart, constItem->length());
488             break;
489         }
490     }
491     if (!shortComment.length()) {
492         currentEnumItem->reportError<void>("missing #Line or #Deprecated or #Experimental");
493     }
494     return shortComment;
495 }
496 
enumMemberName(const Definition & child,const Definition * token,Item * item,LastItem * last,const Definition ** currentEnumItem)497 IncludeWriter::ItemState IncludeWriter::enumMemberName(
498         const Definition& child, const Definition* token, Item* item, LastItem* last,
499         const Definition** currentEnumItem) {
500     TextParser parser(fFileName, last->fStart, last->fEnd, fLineCount);
501     parser.skipWhiteSpace();
502     item->fName = string(parser.fChar, (int) (last->fEnd - parser.fChar));
503     *currentEnumItem = this->matchMemberName(item->fName, child);
504     if (token) {
505         this->setStart(token->fContentEnd, token);
506         TextParser enumLine(token->fFileName, last->fEnd, token->fContentStart, token->fLineCount);
507         const char* end = enumLine.anyOf(",}=");
508         SkASSERT(end);
509         if ('=' == *end) {  // write enum value
510             last->fEnd = token->fContentEnd;
511             item->fValue = string(token->fContentStart, (int) (last->fEnd - token->fContentStart));
512             return ItemState::kValue;
513         }
514     }
515     return ItemState::kComment;
516 }
517 
enumMemberOut(const Definition * currentEnumItem,const Definition & child,const Item & item,Preprocessor & preprocessor)518 void IncludeWriter::enumMemberOut(const Definition* currentEnumItem, const Definition& child,
519         const Item& item, Preprocessor& preprocessor) {
520     SkASSERT(currentEnumItem);
521     string shortComment = this->enumMemberComment(currentEnumItem, child);
522     int enumItemValueTab =
523             SkTMax((int) item.fName.length() + fIndent + 1, fEnumItemValueTab); // 1: ,
524     int valueLength = item.fValue.length();
525     int assignLength = valueLength ? valueLength + 3 : 0; // 3: space = space
526     int enumItemCommentTab = SkTMax(enumItemValueTab + assignLength, fEnumItemCommentTab);
527     int trimNeeded = enumItemCommentTab + shortComment.length() - (100 - (sizeof("//!< ") - 1));
528     bool crAfterName = false;
529     if (trimNeeded > 0) {
530         if (item.fValue.length()) {
531             int valueSpare = SkTMin(trimNeeded,                  // 3 below: space = space
532                     (int) (enumItemCommentTab - enumItemValueTab - item.fValue.length() - 3));
533             SkASSERT(valueSpare >= 0);
534             trimNeeded -= valueSpare;
535             enumItemCommentTab -= valueSpare;
536         }
537         if (trimNeeded > 0) {
538             int nameSpare = SkTMin(trimNeeded, (int) (enumItemValueTab - item.fName.length()
539                     - fIndent - 1));  // 1: ,
540             SkASSERT(nameSpare >= 0);
541             trimNeeded -= nameSpare;
542             enumItemValueTab -= nameSpare;
543             enumItemCommentTab -= nameSpare;
544         }
545         if (trimNeeded > 0) {
546             crAfterName = true;
547             if (!valueLength) {
548                 this->enumMemberForComment(currentEnumItem)->reportError<void>("comment too long");
549             } else if (valueLength + fIndent + 8 + shortComment.length() > // 8: addtional indent
550                     100 - (sizeof(", //!< ") - 1)) { // -1: zero-terminated string
551                 this->enumMemberForComment(currentEnumItem)->reportError<void>("comment 2 long");
552             }                                    // 2: = space
553             enumItemValueTab = fEnumItemValueTab +  2                 // 2: , space
554                     - SkTMax(0, fEnumItemValueTab + 2 + valueLength +    2 - fEnumItemCommentTab);
555             enumItemCommentTab = SkTMax(enumItemValueTab + valueLength + 2, fEnumItemCommentTab);
556         }
557     }
558     this->lfcr();
559     this->writeString(item.fName);
560     int saveIndent = fIndent;
561     if (item.fValue.length()) {
562         if (!crAfterName) {
563             this->indentToColumn(enumItemValueTab);
564         } else {
565             this->writeSpace();
566         }
567         this->writeString("=");
568         if (crAfterName) {
569             this->lfcr();
570             fIndent = enumItemValueTab;
571         } else {
572             this->writeSpace();
573         }
574         this->writeString(item.fValue);
575     }
576     this->writeString(",");
577     this->indentToColumn(enumItemCommentTab);
578     this->writeString("//!<");
579     this->writeSpace();
580     this->rewriteBlock(shortComment.length(), shortComment.c_str(), Phrase::kYes);
581     this->lfcr();
582     fIndent = saveIndent;
583     if (preprocessor.fStart) {
584         SkASSERT(preprocessor.fEnd);
585         int saveIndent = fIndent;
586         fIndent = SkTMax(0, fIndent - 8);
587         this->lf(2);
588         this->writeBlock(
589                 (int) (preprocessor.fEnd - preprocessor.fStart), preprocessor.fStart);
590         this->lfcr();
591         fIndent = saveIndent;
592         preprocessor.reset();
593     }
594 }
595 
596 // iterate through include tokens and find how much remains for 1 line comments
597 // put ones that fit on same line, ones that are too big wrap
enumMembersOut(Definition & child)598 void IncludeWriter::enumMembersOut(Definition& child) {
599     ItemState state = ItemState::kNone;
600     const Definition* currentEnumItem = nullptr;
601     LastItem last = { nullptr, nullptr };
602     auto brace = child.fChildren[0];
603     if (KeyWord::kClass == brace->fKeyWord) {
604         brace = brace->fChildren[0];
605     }
606     SkASSERT(Bracket::kBrace == brace->fBracket);
607     vector<IterState> iterStack;
608     iterStack.emplace_back(brace->fTokens.begin(), brace->fTokens.end());
609     IterState* iterState = &iterStack[0];
610     Preprocessor preprocessor;
611     Item item;
612     while (iterState->fDefIter != iterState->fDefEnd) {
613         auto& token = *iterState->fDefIter++;
614         if (this->enumPreprocessor(&token, MemberPass::kOut, iterStack, &iterState,
615                 &preprocessor)) {
616             continue;
617         }
618         if (ItemState::kName == state) {
619             state = this->enumMemberName(child, &token, &item, &last, &currentEnumItem);
620         }
621         if (ItemState::kValue == state) {
622             TextParser valueEnd(token.fFileName, last.fEnd, token.fContentStart, token.fLineCount);
623             const char* end = valueEnd.anyOf(",}");
624             if (!end) {  // write expression continuation
625                 item.fValue += string(last.fEnd, (int) (token.fContentEnd - last.fEnd));
626                 continue;
627             }
628         }
629         if (ItemState::kNone != state && currentEnumItem) {
630             this->enumMemberOut(currentEnumItem, child, item, preprocessor);
631             item.reset();
632             this->setStartBack(token.fContentStart, &token);
633             state = ItemState::kNone;
634             last.fStart = nullptr;
635         }
636         SkASSERT(ItemState::kNone == state || !currentEnumItem);
637         if (!last.fStart) {
638             last.fStart = fStart;
639         }
640         last.fEnd = token.fContentEnd;
641         state = ItemState::kName;
642     }
643     if (ItemState::kName == state) {
644         state = this->enumMemberName(child, nullptr, &item, &last, &currentEnumItem);
645     }
646     if ((ItemState::kValue == state || ItemState::kComment == state) && currentEnumItem) {
647         this->enumMemberOut(currentEnumItem, child, item, preprocessor);
648     }
649     this->indentOut();
650 }
651 
enumPreprocessor(Definition * token,MemberPass pass,vector<IterState> & iterStack,IterState ** iterState,Preprocessor * preprocessor)652 bool IncludeWriter::enumPreprocessor(Definition* token, MemberPass pass,
653         vector<IterState>& iterStack, IterState** iterState, Preprocessor* preprocessor) {
654     if (token && Definition::Type::kBracket == token->fType) {
655         if (Bracket::kSlashSlash == token->fBracket) {
656             if (MemberPass::kOut == pass) {
657                 this->setStart(token->fContentEnd, token);
658             }
659             return true;  // ignore old inline comments
660         }
661         if (Bracket::kSlashStar == token->fBracket) {
662             if (MemberPass::kOut == pass) {
663                 this->setStart(token->fContentEnd + 1, token);
664             }
665             return true;  // ignore old inline comments
666         }
667         if (Bracket::kPound == token->fBracket) {  // preprocessor wraps member
668             preprocessor->fDefinition = token;
669             preprocessor->fStart = token->fContentStart;
670             if (KeyWord::kIf == token->fKeyWord || KeyWord::kIfdef == token->fKeyWord) {
671                 iterStack.emplace_back(token->fTokens.begin(), token->fTokens.end());
672                 *iterState = &iterStack.back();
673                 preprocessor->fWord = true;
674             } else if (KeyWord::kEndif == token->fKeyWord || KeyWord::kElif == token->fKeyWord
675                     || KeyWord::kElse == token->fKeyWord) {
676                 iterStack.pop_back();
677                 *iterState = &iterStack.back();
678                 preprocessor->fEnd = token->fContentEnd;
679                 if (KeyWord::kElif == token->fKeyWord) {
680                     iterStack.emplace_back(token->fTokens.begin(), token->fTokens.end());
681                     *iterState = &iterStack.back();
682                     preprocessor->fWord = true;
683                 }
684             } else {
685                 SkASSERT(0); // incomplete
686             }
687             return true;
688         }
689         if (preprocessor->fDefinition) {
690             if (Bracket::kParen == token->fBracket) {
691                 preprocessor->fEnd = token->fContentEnd;
692                 SkASSERT(')' == *preprocessor->fEnd);
693                 ++preprocessor->fEnd;
694                 return true;
695             }
696             SkASSERT(0);  // incomplete
697         }
698         return true;
699     }
700     if (token && Definition::Type::kWord != token->fType) {
701         SkASSERT(0); // incomplete
702     }
703     if (preprocessor->fWord) {
704         preprocessor->fWord = false;
705         preprocessor->fEnd = token->fContentEnd;
706         return true;
707     }
708     return false;
709 }
710 
enumSizeItems(const Definition & child)711 void IncludeWriter::enumSizeItems(const Definition& child) {
712     ItemState state = ItemState::kNone;
713     ItemLength lengths = { 0, 0, 0, 0 };
714     const char* lastEnd = nullptr;
715     auto brace = child.fChildren[0];
716     if (KeyWord::kClass == brace->fKeyWord) {
717         brace = brace->fChildren[0];
718     }
719     SkASSERT(Bracket::kBrace == brace->fBracket);
720     vector<IterState> iterStack;
721     iterStack.emplace_back(brace->fTokens.begin(), brace->fTokens.end());
722     IterState* iterState = &iterStack[0];
723     Preprocessor preprocessor;
724     string enumName;
725     bool undocumented = false;
726     while (iterState->fDefIter != iterState->fDefEnd) {
727         auto& token = *iterState->fDefIter++;
728         if (this->enumPreprocessor(&token, MemberPass::kCount, iterStack, &iterState,
729                 &preprocessor)) {
730             continue;
731         }
732         if (ItemState::kName == state) {
733             TextParser enumLine(token.fFileName, lastEnd, token.fContentStart, token.fLineCount);
734             const char* end = enumLine.anyOf(",}=");
735             SkASSERT(end);
736             state = '=' == *end ? ItemState::kValue : ItemState::kComment;
737             if (ItemState::kValue == state) {
738                 lastEnd = token.fContentEnd;
739                 lengths.fCurValue = (int) (lastEnd - token.fContentStart);
740                 continue;
741             }
742         }
743         if (ItemState::kValue == state) {
744             TextParser valueEnd(token.fFileName, lastEnd, token.fContentStart, token.fLineCount);
745             const char* end = valueEnd.anyOf(",}");
746             if (!end) {  // write expression continuation
747                 lengths.fCurValue += (int) (token.fContentEnd - lastEnd);
748                 continue;
749             }
750         }
751         if (ItemState::kNone != state) {
752             if (!undocumented) {
753                 this->checkEnumLengths(child, enumName, &lengths);
754             }
755             lengths.fCurValue = 0;
756             state = ItemState::kNone;
757         }
758         SkASSERT(ItemState::kNone == state);
759         lastEnd = token.fContentEnd;
760         lengths.fCurName = (int) (lastEnd - token.fContentStart);
761         enumName = string(token.fContentStart, lengths.fCurName);
762         undocumented = token.fUndocumented;
763         state = ItemState::kName;
764     }
765     if (ItemState::kNone != state && !undocumented) {
766         this->checkEnumLengths(child, enumName, &lengths);
767     }
768     fEnumItemValueTab = lengths.fLongestName + fIndent + 1 /* 1: , */ ;
769     if (lengths.fLongestValue) {
770         lengths.fLongestValue += 3; // 3: space = space
771     }
772     fEnumItemCommentTab = fEnumItemValueTab + lengths.fLongestValue + 1 ; // 1: space before //!<
773     // iterate through bmh children and see which comments fit on include lines
774     if (!this->checkChildCommentLength(fEnumDef, MarkType::kConst)) {
775         fEnumDef->reportError<void>("expected at least one #Const in #Enum");
776     }
777 }
778 
matchMemberName(string matchName,const Definition & child) const779 const Definition* IncludeWriter::matchMemberName(string matchName, const Definition& child) const {
780     const Definition* parent = &child;
781     if (KeyWord::kEnum == child.fKeyWord && child.fChildren.size() > 0
782             && KeyWord::kClass == child.fChildren[0]->fKeyWord) {
783         matchName = child.fChildren[0]->fName + "::" + matchName;
784     }
785     do {
786         if (KeyWord::kStruct == parent->fKeyWord || KeyWord::kClass == parent->fKeyWord) {
787             matchName = parent->fName + "::" + matchName;
788         }
789     } while ((parent = parent->fParent));
790     const Definition* enumItem = nullptr;
791     for (auto testItem : fEnumDef->fChildren) {
792         if (MarkType::kConst != testItem->fMarkType) {
793             continue;
794         }
795         if (matchName != testItem->fName) {
796             continue;
797         }
798         enumItem = testItem;
799         break;
800     }
801     return enumItem;    // returns nullptr if matchName is undocumented
802 }
803 
804 // walk children and output complete method doxygen description
methodOut(Definition * method,const Definition & child)805 void IncludeWriter::methodOut(Definition* method, const Definition& child) {
806     if (fPendingMethod) {
807         this->indentOut();
808         fPendingMethod = false;
809     }
810     fBmhMethod = method;
811     fMethodDef = &child;
812     fContinuation = nullptr;
813     fDeferComment = nullptr;
814     Definition* csParent = method->csParent();
815     if (csParent && (0 == fIndent || fIndentNext)) {
816         this->indentIn(IndentKind::kMethodOut);
817         fIndentNext = false;
818     }
819     if (method->fChildren.end() != std::find_if(method->fChildren.begin(), method->fChildren.end(),
820             [](const Definition* def) { return MarkType::kPopulate == def->fMarkType; } )) {
821         std::list<Definition>::iterator iter;
822         const Definition* childPtr = &child;
823         SkDEBUGCODE(bool sawMethod = false);
824         do {
825             int commentIndex = childPtr->fParentIndex;
826             iter = childPtr->fParent->fTokens.begin();
827             std::advance(iter, commentIndex);
828             SkDEBUGCODE(sawMethod |= MarkType::kMethod == iter->fMarkType);
829             while (--commentIndex >= 0) {
830                 std::advance(iter, -1);
831                 if (Bracket::kSlashStar == iter->fBracket) {
832                     SkASSERT(sawMethod);
833                     break;
834                 }
835                 SkASSERT(!sawMethod);
836                 SkDEBUGCODE(sawMethod |= MarkType::kMethod == iter->fMarkType);
837             }
838             if (MarkType::kMethod != iter->fMarkType) {
839                 break;
840             }
841             childPtr = childPtr->fParent;
842             SkDEBUGCODE(sawMethod = true);
843         } while (true);
844         SkASSERT(Bracket::kSlashSlash == iter->fBracket || Bracket::kSlashStar == iter->fBracket);
845         this->lf(2);
846         this->writeString("/");
847         this->writeBlock(iter->length(), iter->fContentStart);
848         this->lfcr();
849     } else {
850         this->writeCommentHeader();
851         fIndent += 4;
852         this->descriptionOut(method, SkipFirstLine::kNo, Phrase::kNo);
853         // compute indention column
854         size_t column = 0;
855         bool hasParmReturn = false;
856         for (auto methodPart : method->fChildren) {
857             if (MarkType::kParam == methodPart->fMarkType) {
858                 column = SkTMax(column, methodPart->fName.length());
859                 hasParmReturn = true;
860             } else if (MarkType::kReturn == methodPart->fMarkType) {
861                 hasParmReturn = true;
862             }
863         }
864         if (hasParmReturn) {
865             this->lf(2);
866             column += fIndent + sizeof("@return ");
867             int saveIndent = fIndent;
868             for (auto methodPart : method->fChildren) {
869                 if (MarkType::kParam == methodPart->fMarkType) {
870                     this->writeString("@param");
871                     this->writeSpace();
872                     this->writeString(methodPart->fName.c_str());
873                 } else if (MarkType::kReturn == methodPart->fMarkType) {
874                     this->writeString("@return");
875                 } else {
876                     continue;
877                 }
878                 this->indentToColumn(column);
879                 fIndent = column;
880                 this->descriptionOut(methodPart, SkipFirstLine::kNo, Phrase::kYes);
881                 fIndent = saveIndent;
882                 this->lfcr();
883             }
884         } else {
885             this->lfcr();
886         }
887         fIndent -= 4;
888         this->lfcr();
889         this->writeCommentTrailer(OneLine::kNo);
890     }
891     fBmhMethod = nullptr;
892     fMethodDef = nullptr;
893     fEnumDef = nullptr;
894     fWroteMethod = true;
895 }
896 
structOut(const Definition * root,const Definition & child,const char * commentStart,const char * commentEnd)897 void IncludeWriter::structOut(const Definition* root, const Definition& child,
898         const char* commentStart, const char* commentEnd) {
899     this->writeCommentHeader();
900     this->writeString("\\");
901     SkASSERT(MarkType::kClass == child.fMarkType || MarkType::kStruct == child.fMarkType);
902     this->writeString(MarkType::kClass == child.fMarkType ? "class" : "struct");
903     this->writeSpace();
904     this->writeString(child.fName.c_str());
905     fIndent += 4;
906     this->lfcr();
907     this->rewriteBlock((int)(commentEnd - commentStart), commentStart, Phrase::kNo);
908     fIndent -= 4;
909     this->lfcr();
910     this->writeCommentTrailer(OneLine::kNo);
911 }
912 
findEnumSubtopic(string undername,const Definition ** rootDefPtr) const913 bool IncludeWriter::findEnumSubtopic(string undername, const Definition** rootDefPtr) const {
914     const Definition* subtopic = fEnumDef->fParent;
915     string subcheck = subtopic->fFiddle + '_' + undername;
916     auto iter = fBmhParser->fTopicMap.find(subcheck);
917     if (iter == fBmhParser->fTopicMap.end()) {
918         return false;
919     }
920     *rootDefPtr = iter->second;
921     return true;
922 }
923 
findMemberCommentBlock(const vector<Definition * > & bmhChildren,string name) const924 Definition* IncludeWriter::findMemberCommentBlock(const vector<Definition*>& bmhChildren,
925         string name) const {
926     for (auto memberDef : bmhChildren) {
927         if (MarkType::kMember != memberDef->fMarkType) {
928             continue;
929         }
930         string match = memberDef->fName;
931         // if match.endsWith(name) ...
932         if (match.length() >= name.length() &&
933                 0 == match.compare(match.length() - name.length(), name.length(), name)) {
934             return memberDef;
935         }
936     }
937     for (auto memberDef : bmhChildren) {
938         if (MarkType::kSubtopic != memberDef->fMarkType && MarkType::kTopic != memberDef->fMarkType) {
939             continue;
940         }
941         Definition* result = this->findMemberCommentBlock(memberDef->fChildren, name);
942         if (result) {
943             return result;
944         }
945     }
946     return nullptr;
947 }
948 
findMethod(string name,RootDefinition * root) const949 Definition* IncludeWriter::findMethod(string name, RootDefinition* root) const {
950     if (root) {
951         return root->find(name, RootDefinition::AllowParens::kNo);
952     }
953     auto methodIter = fBmhParser->fMethodMap.find(name);
954     if (fBmhParser->fMethodMap.end() == methodIter) {
955         return nullptr;
956     }
957     return &methodIter->second;
958 }
959 
960 
firstBlock(int size,const char * data)961 void IncludeWriter::firstBlock(int size, const char* data) {
962      SkAssertResult(this->firstBlockTrim(size, data));
963 }
964 
firstBlockTrim(int size,const char * data)965 bool IncludeWriter::firstBlockTrim(int size, const char* data) {
966     bool result = this->writeBlockTrim(size, data);
967     if (fFirstWrite) {
968         auto fileInfo = std::find_if(fRootTopic->fChildren.begin(), fRootTopic->fChildren.end(),
969                 [](const Definition* def){ return MarkType::kFile == def->fMarkType; } );
970         if (fRootTopic->fChildren.end() != fileInfo) {
971             this->writeCommentHeader();
972             this->writeString("\\file");
973             this->writeSpace();
974             size_t lastSlash = fFileName.rfind('/');
975             if (string::npos == lastSlash) {
976                 lastSlash = fFileName.rfind('\\');
977             }
978             string fileName = fFileName.substr(lastSlash + 1);
979             this->writeString(fileName);
980             this->lf(2);
981             fIndent += 4;
982             this->descriptionOut(*fileInfo, SkipFirstLine::kNo, Phrase::kNo);
983             fIndent -= 4;
984             this->writeCommentTrailer(OneLine::kNo);
985         }
986         fFirstWrite = false;
987     }
988     return result;
989 }
990 
setStart(const char * start,const Definition * def)991 void IncludeWriter::setStart(const char* start, const Definition* def) {
992     SkASSERT(start >= fStart);
993     this->setStartBack(start, def);
994 }
995 
setStartBack(const char * start,const Definition * def)996 void IncludeWriter::setStartBack(const char* start, const Definition* def) {
997     fStartSetter = def;
998     fStart = start;
999 }
1000 
structMemberOut(const Definition * memberStart,const Definition & child)1001 Definition* IncludeWriter::structMemberOut(const Definition* memberStart, const Definition& child) {
1002     const char* blockStart = !fWroteMethod && fDeferComment ? fDeferComment->fContentEnd : fStart;
1003     const char* blockEnd = fWroteMethod && fDeferComment ? fDeferComment->fStart - 1 :
1004             memberStart->fStart;
1005     this->firstBlockTrim((int) (blockEnd - blockStart), blockStart);
1006     this->indentDeferred(IndentKind::kStructMember);
1007     fWroteMethod = false;
1008     string name(child.fContentStart, (int) (child.fContentEnd - child.fContentStart));
1009     Definition* commentBlock = this->findMemberCommentBlock(fBmhStructDef->fChildren, name);
1010     if (!commentBlock) {
1011         return memberStart->reportError<Definition*>("member missing comment block 2");
1012     }
1013     auto lineIter = std::find_if(commentBlock->fChildren.begin(), commentBlock->fChildren.end(),
1014         [](const Definition* def){ return MarkType::kLine == def->fMarkType; } );
1015     SkASSERT(commentBlock->fChildren.end() != lineIter);
1016     const Definition* lineDef = *lineIter;
1017     if (fStructMemberLength > 100) {
1018         this->writeCommentHeader();
1019         this->writeSpace();
1020         this->rewriteBlock(lineDef->length(), lineDef->fContentStart, Phrase::kYes);
1021         this->writeCommentTrailer(OneLine::kYes);
1022     }
1023     this->lfcr();
1024     this->writeBlock((int) (child.fStart - memberStart->fContentStart),
1025             memberStart->fContentStart);
1026     this->indentToColumn(fStructMemberTab);
1027     this->writeString(name.c_str());
1028     auto tokenIter = child.fParent->fTokens.begin();
1029     std::advance(tokenIter, child.fParentIndex + 1);
1030     Definition* valueStart = &*tokenIter;
1031     while (Definition::Type::kPunctuation != tokenIter->fType) {
1032         std::advance(tokenIter, 1);
1033         SkASSERT(child.fParent->fTokens.end() != tokenIter);
1034     }
1035     Definition* valueEnd = &*tokenIter;
1036     if (valueStart != valueEnd) {
1037         this->indentToColumn(fStructValueTab);
1038         this->writeString("=");
1039         this->writeSpace();
1040         this->writeBlock((int) (valueEnd->fStart - valueStart->fContentStart),
1041                 valueStart->fContentStart);
1042     }
1043     this->writeString(";");
1044     if (fStructMemberLength <= 100) {
1045         this->indentToColumn(fStructCommentTab);
1046         this->writeString("//!<");
1047         this->writeSpace();
1048         this->rewriteBlock(lineDef->length(), lineDef->fContentStart, Phrase::kYes);
1049     }
1050     this->lf(1);
1051     return valueEnd;
1052 }
1053 
1054 // const and constexpr and #define aren't contained in a braces like struct and enum.
1055 // use a bmh subtopic to group like ones together, then measure them in the include as if
1056 // they were formally linked together
constSizeMembers(const RootDefinition * root)1057 void IncludeWriter::constSizeMembers(const RootDefinition* root) {
1058     // fBmhConst->fParent is subtopic containing all grouped const expressions
1059     // fConstDef is token of const include name, hopefully on same line as const start
1060     string rootPrefix = root ? root->fName + "::" : "";
1061     const Definition* test = fConstDef;
1062     int tokenIndex = test->fParentIndex;
1063     int longestName = 0;
1064     int longestValue = 0;
1065     int longestComment = 0;
1066     const Definition* subtopic = fBmhConst->fParent;
1067     SkASSERT(subtopic);
1068     SkASSERT(MarkType::kSubtopic == subtopic->fMarkType);
1069     // back up to first token on line
1070     size_t lineCount = test->fLineCount;
1071     const Definition* last;
1072     auto tokenIter = test->fParent->fTokens.begin();
1073     std::advance(tokenIter, tokenIndex);
1074     do {
1075         last = test;
1076         std::advance(tokenIter, -1);
1077         test = &*tokenIter;
1078         SkASSERT(test->fParentIndex == --tokenIndex);
1079     } while (lineCount == test->fLineCount);
1080     test = last;
1081     for (auto child : subtopic->fChildren) {
1082         if (MarkType::kConst != child->fMarkType) {
1083             continue;
1084         }
1085         // expect found name to be on the left of assign
1086         // expect assign
1087         // expect semicolon
1088         // no parens, no braces
1089         while (rootPrefix + test->fName != child->fName) {
1090             std::advance(tokenIter, 1);
1091             test = &*tokenIter;
1092             SkASSERT(lineCount >= test->fLineCount);
1093         }
1094         ++lineCount;
1095         TextParser constText(test);
1096         const char* nameEnd = constText.trimmedBracketEnd('=');
1097         SkAssertResult(constText.skipToEndBracket('='));
1098         const char* valueEnd = constText.trimmedBracketEnd(';');
1099         auto lineIter = std::find_if(child->fChildren.begin(), child->fChildren.end(),
1100                 [](const Definition* def){ return MarkType::kLine == def->fMarkType; });
1101         SkASSERT(child->fChildren.end() != lineIter);
1102         longestName = SkTMax(longestName, (int) (nameEnd - constText.fStart));
1103         longestValue = SkTMax(longestValue, (int) (valueEnd - constText.fChar));
1104         longestComment = SkTMax(longestComment, (*lineIter)->length());
1105     }
1106     // write fStructValueTab, fStructCommentTab
1107     fConstValueTab = longestName + fIndent + 1;
1108     fConstCommentTab = fConstValueTab + longestValue + 2;
1109     fConstLength = fConstCommentTab + longestComment + (int) sizeof("//!<");
1110 }
1111 
defineOut(const Definition & def)1112 bool IncludeWriter::defineOut(const Definition& def) {
1113     if (def.fTokens.size() < 1) {
1114         return false;
1115     }
1116     auto& child = def.fTokens.front();
1117     string name(child.fContentStart, child.length());
1118     auto defIter = fBmhParser->fDefineMap.find(name);
1119     if (fBmhParser->fDefineMap.end() == defIter) {
1120         return false;
1121     }
1122     const Definition& bmhDef = defIter->second;
1123     this->constOut(&def, &bmhDef);
1124     return true;
1125 }
1126 
structSizeMembers(const Definition & child)1127 void IncludeWriter::structSizeMembers(const Definition& child) {
1128     int longestType = 0;
1129     Definition* typeStart = nullptr;
1130     int longestName = 0;
1131     int longestValue = 0;
1132     int longestComment = 0;
1133     SkASSERT(child.fChildren.size() == 1 || child.fChildren.size() == 2);
1134     bool inEnum = false;
1135     bool inMethod = false;
1136     bool inMember = false;
1137     auto brace = child.fChildren[0];
1138     SkASSERT(Bracket::kBrace == brace->fBracket);
1139     for (auto& token : brace->fTokens) {
1140         if (Definition::Type::kBracket == token.fType) {
1141             if (Bracket::kSlashSlash == token.fBracket) {
1142                 continue;  // ignore old inline comments
1143             }
1144             if (Bracket::kSlashStar == token.fBracket) {
1145                 continue;  // ignore old inline comments
1146             }
1147             if (Bracket::kParen == token.fBracket) {
1148                 if (inMethod) {
1149                     continue;
1150                 }
1151                 break;
1152             }
1153             if (Bracket::kAngle == token.fBracket) {
1154                 // in template param
1155                 continue;
1156             }
1157             SkASSERT(0); // incomplete
1158         }
1159         if (Definition::Type::kKeyWord == token.fType) {
1160             switch (token.fKeyWord) {
1161                 case KeyWord::kEnum:
1162                     inEnum = true;
1163                     break;
1164                 case KeyWord::kConst:
1165                 case KeyWord::kConstExpr:
1166                 case KeyWord::kStatic:
1167                 case KeyWord::kInt:
1168                 case KeyWord::kUint8_t:
1169                 case KeyWord::kUint16_t:
1170                 case KeyWord::kUint32_t:
1171                 case KeyWord::kUint64_t:
1172                 case KeyWord::kUintPtr_t:
1173                 case KeyWord::kUnsigned:
1174                 case KeyWord::kSize_t:
1175                 case KeyWord::kFloat:
1176                 case KeyWord::kBool:
1177                 case KeyWord::kChar:
1178                 case KeyWord::kVoid:
1179                     if (!typeStart) {
1180                         typeStart = &token;
1181                     }
1182                     break;
1183                 default:
1184                     break;
1185             }
1186             continue;
1187         }
1188         if (Definition::Type::kPunctuation == token.fType) {
1189             if (inEnum) {
1190                 SkASSERT(Punctuation::kSemicolon == token.fPunctuation);
1191                 inEnum = false;
1192             }
1193             if (inMethod) {
1194                 if (Punctuation::kColon == token.fPunctuation) {
1195                     inMethod = false;
1196                 } else if (Punctuation::kLeftBrace == token.fPunctuation) {
1197                     inMethod = false;
1198                 } else if (Punctuation::kSemicolon == token.fPunctuation) {
1199                     inMethod = false;
1200                 } else if (Punctuation::kAsterisk == token.fPunctuation) {
1201                     inMethod = false;
1202                 } else {
1203                     SkASSERT(0);  // incomplete
1204                 }
1205             }
1206             if (inMember) {
1207                 SkASSERT(Punctuation::kSemicolon == token.fPunctuation);
1208                 typeStart = nullptr;
1209                 inMember = false;
1210             }
1211             continue;
1212         }
1213         if (Definition::Type::kWord != token.fType) {
1214             SkASSERT(0); // incomplete
1215         }
1216         if (MarkType::kMember == token.fMarkType) {
1217             TextParser typeStr(token.fFileName, typeStart->fContentStart, token.fContentStart,
1218                     token.fLineCount);
1219             typeStr.trimEnd();
1220             longestType = SkTMax(longestType, (int) (typeStr.fEnd - typeStr.fStart));
1221             longestName = SkTMax(longestName, (int) (token.fContentEnd - token.fContentStart));
1222             typeStart->fMemberStart = true;
1223             inMember = true;
1224             string tokenName(token.fContentStart, (int) (token.fContentEnd - token.fContentStart));
1225             Definition* commentBlock = this->findMemberCommentBlock(fBmhStructDef->fChildren,
1226                     tokenName);
1227             if (!commentBlock) {
1228                 return token.reportError<void>("member missing comment block 1");
1229             }
1230             auto lineIter = std::find_if(commentBlock->fChildren.begin(),
1231                     commentBlock->fChildren.end(),
1232                     [](const Definition* def){ return MarkType::kLine == def->fMarkType; } );
1233             SkASSERT(commentBlock->fChildren.end() != lineIter);
1234             const Definition* lineDef = *lineIter;
1235             longestComment = SkTMax(longestComment, lineDef->length());
1236             continue;
1237         }
1238         if (MarkType::kMethod == token.fMarkType) {
1239             inMethod = true;
1240             continue;
1241         }
1242         SkASSERT(MarkType::kNone == token.fMarkType);
1243         if (typeStart) {
1244             if (inMember) {
1245                 longestValue =
1246                         SkTMax(longestValue, (int) (token.fContentEnd - token.fContentStart));
1247             }
1248         } else {
1249             typeStart = &token;
1250         }
1251     }
1252     fStructMemberTab = longestType + fIndent + 1 /* space before name */ ;
1253     fStructValueTab = fStructMemberTab + longestName + 2 /* space ; */ ;
1254     fStructCommentTab = fStructValueTab;
1255     if (longestValue) {
1256         fStructCommentTab += longestValue + 3 /* space = space */ ;
1257         fStructValueTab -= 1 /* ; */ ;
1258     }
1259     fStructMemberLength = fStructCommentTab + longestComment;
1260     // iterate through struct to ensure that members' comments fit on line
1261     // struct or class may not have any members
1262     (void) this->checkChildCommentLength(fBmhStructDef, MarkType::kMember);
1263 }
1264 
find_start(const Definition * startDef,const char * start)1265 static bool find_start(const Definition* startDef, const char* start) {
1266     for (const auto& child : startDef->fTokens) {
1267         if (child.fContentStart == start) {
1268             return MarkType::kMethod == child.fMarkType;
1269         }
1270         if (child.fContentStart >= start) {
1271             break;
1272         }
1273         if (find_start(&child, start)) {
1274             return true;
1275         }
1276     }
1277     return false;
1278 }
1279 
populate(Definition * def,ParentPair * prevPair,RootDefinition * root)1280 bool IncludeWriter::populate(Definition* def, ParentPair* prevPair, RootDefinition* root) {
1281     if (!def->fTokens.size()) {
1282         return true;
1283     }
1284     ParentPair pair = { def, prevPair };
1285     // write bulk of original include up to class, method, enum, etc., excepting preceding comment
1286     // find associated bmh object
1287     // write any associated comments in Doxygen form
1288     // skip include comment
1289     // if there is a series of same named methods, write one set of comments, then write all methods
1290     string methodName;
1291     Definition* method = nullptr;
1292     Definition* clonedMethod = nullptr;
1293     const Definition* memberStart = nullptr;
1294     const Definition* memberEnd = nullptr;
1295     fContinuation = nullptr;
1296     bool inStruct = false;
1297     bool inConstructor = false;
1298     bool inInline = false;
1299     bool eatOperator = false;
1300     bool sawConst = false;
1301     bool staticOnly = false;
1302     bool sawTypedef = false;
1303     Definition* deferredTypedefComment = nullptr;
1304     const Definition* requireDense = nullptr;
1305     const Definition* startDef = nullptr;
1306     for (auto& child : def->fTokens) {
1307         if (KeyWord::kInline == child.fKeyWord) {
1308             continue;
1309         }
1310         if (KeyWord::kOperator == child.fKeyWord && method &&
1311                 Definition::MethodType::kOperator == method->fMethodType) {
1312             eatOperator = true;
1313             continue;
1314         }
1315         if (eatOperator) {
1316             if (Bracket::kSquare == child.fBracket || Bracket::kParen == child.fBracket) {
1317                 continue;
1318             }
1319             eatOperator = false;
1320             fContinuation = nullptr;
1321             if (KeyWord::kConst == child.fKeyWord) {
1322                 continue;
1323             }
1324         }
1325         if (memberEnd) {
1326             if (memberEnd != &child) {
1327                 continue;
1328             }
1329             startDef = &child;
1330             this->setStart(child.fContentStart + 1, &child);
1331             memberEnd = nullptr;
1332         }
1333         if (child.fPrivate) {
1334             if (MarkType::kMethod == child.fMarkType) {
1335                 inInline = true;
1336             }
1337             continue;
1338         }
1339         if (inInline) {
1340             if (Definition::Type::kKeyWord == child.fType) {
1341                 SkASSERT(MarkType::kMethod != child.fMarkType);
1342                 continue;
1343             }
1344             if (Definition::Type::kPunctuation == child.fType) {
1345                 if (Punctuation::kLeftBrace == child.fPunctuation) {
1346                     inInline = false;
1347                 } else {
1348                     SkASSERT(Punctuation::kAsterisk == child.fPunctuation);
1349                 }
1350                 continue;
1351             }
1352             if (Definition::Type::kWord == child.fType) {
1353                 string name(child.fContentStart, child.fContentEnd - child.fContentStart);
1354                 SkASSERT(string::npos != name.find("::"));
1355                 continue;
1356             }
1357             if (Definition::Type::kBracket == child.fType) {
1358                 SkASSERT(Bracket::kParen == child.fBracket);
1359                 continue;
1360             }
1361         }
1362         if (fContinuation) {
1363             if (Definition::Type::kKeyWord == child.fType) {
1364                 if (KeyWord::kFriend == child.fKeyWord ||
1365                         KeyWord::kSK_API == child.fKeyWord) {
1366                     continue;
1367                 }
1368                 const IncludeKey& includeKey = kKeyWords[(int) child.fKeyWord];
1369                 if (KeyProperty::kNumber == includeKey.fProperty) {
1370                     continue;
1371                 }
1372             }
1373             if (Definition::Type::kBracket == child.fType) {
1374                 if (Bracket::kAngle == child.fBracket) {
1375                     continue;
1376                 }
1377                 if (Bracket::kParen == child.fBracket) {
1378                     if (!clonedMethod) {
1379                         if (inConstructor) {
1380                             fContinuation = child.fContentStart;
1381                         }
1382                         continue;
1383                     }
1384                     int alternate = 1;
1385                     ptrdiff_t childLen = child.fContentEnd - child.fContentStart;
1386                     SkASSERT(')' == child.fContentStart[childLen]);
1387                     ++childLen;
1388                     do {
1389                         TextParser params(clonedMethod->fFileName, clonedMethod->fStart,
1390                             clonedMethod->fContentStart, clonedMethod->fLineCount);
1391                         params.skipToEndBracket('(');
1392                         if (params.startsWith(child.fContentStart, childLen)) {
1393                             this->methodOut(clonedMethod, child);
1394                             sawConst = false;
1395                             break;
1396                         }
1397                         ++alternate;
1398                         string alternateMethod = methodName + '_' + to_string(alternate);
1399                        clonedMethod = this->findMethod(alternateMethod, root);
1400                     } while (clonedMethod);
1401                     if (!clonedMethod) {
1402                         return child.reportError<bool>("cloned method not found");
1403                     }
1404                     clonedMethod = nullptr;
1405                     continue;
1406                 }
1407             }
1408             if (Definition::Type::kWord == child.fType) {
1409                 if (clonedMethod) {
1410                     continue;
1411                 }
1412                 size_t len = (size_t) (child.fContentEnd - child.fContentStart);
1413                 const char operatorStr[] = "operator";
1414                 size_t operatorLen = sizeof(operatorStr) - 1;
1415                 if (len >= operatorLen && !strncmp(child.fContentStart, operatorStr, operatorLen)) {
1416                     fContinuation = child.fContentEnd;
1417                     continue;
1418                 }
1419             }
1420             if (Definition::Type::kPunctuation == child.fType &&
1421                     (Punctuation::kSemicolon == child.fPunctuation ||
1422                     Punctuation::kLeftBrace == child.fPunctuation ||
1423                     (Punctuation::kColon == child.fPunctuation && inConstructor))) {
1424                 SkASSERT(fContinuation[0] == '(');
1425                 const char* continueEnd = child.fContentStart;
1426                 while (continueEnd > fContinuation && isspace(continueEnd[-1])) {
1427                     --continueEnd;
1428                 }
1429                 const char defaultTag[] = " = default";
1430                 size_t tagSize = sizeof(defaultTag) - 1;
1431                 const char* tokenEnd = continueEnd - tagSize;
1432                 if (tokenEnd <= fContinuation || strncmp(tokenEnd, defaultTag, tagSize)) {
1433                     tokenEnd = continueEnd;
1434                 }
1435                 methodName += string(fContinuation, tokenEnd - fContinuation);
1436                 if (string::npos != methodName.find('\n')) {
1437                     methodName.erase(std::remove(methodName.begin(), methodName.end(), '\n'),
1438                                     methodName.end());
1439                 }
1440                 method = this->findMethod(methodName, root);
1441                 if (!method) {
1442                     return child.reportError<bool>("method not found");
1443                 }
1444                 this->methodOut(method, child);
1445                 sawConst = false;
1446                 continue;
1447             }
1448             if (Definition::Type::kPunctuation == child.fType &&
1449                     Punctuation::kAsterisk == child.fPunctuation &&
1450                     clonedMethod) {
1451                 continue;
1452             }
1453             if (inConstructor) {
1454                 continue;
1455             }
1456             method = this->findMethod(methodName + "()", root);
1457             if (method) {
1458                 if (method->fCloned) {
1459                     clonedMethod = method;
1460                     continue;
1461                 }
1462                 this->methodOut(method, child);
1463                 sawConst = false;
1464                 continue;
1465             }
1466             if (KeyWord::kTemplate == child.fParent->fKeyWord) {
1467                 // incomplete; no support to template specialization in public includes
1468                 fContinuation = nullptr;
1469                 continue;
1470             }
1471             return child.reportError<bool>("method not found");
1472         }
1473         if (Bracket::kSlashSlash == child.fBracket || Bracket::kSlashStar == child.fBracket) {
1474             if (!fDeferComment) {
1475                 fDeferComment = &child;
1476             }
1477             continue;
1478         }
1479         if (MarkType::kMethod == child.fMarkType) {
1480             if (this->isInternalName(child)) {
1481                 continue;
1482             }
1483             if (child.fUndocumented) {
1484                 continue;
1485             }
1486             if (KeyWord::kTemplate == child.fParent->fKeyWord) {
1487                 // todo: support template specializations
1488                 continue;
1489             }
1490             const char* bodyEnd = fDeferComment ? fDeferComment->fContentStart - 1 :
1491                     child.fContentStart;
1492             if (Definition::Type::kBracket == def->fType && Bracket::kDebugCode == def->fBracket) {
1493                 auto tokenIter = def->fParent->fTokens.begin();
1494                 std::advance(tokenIter, def->fParentIndex - 1);
1495                 Definition* prior = &*tokenIter;
1496                 if (Definition::Type::kBracket == def->fType &&
1497                         Bracket::kSlashStar == prior->fBracket) {
1498                     bodyEnd = prior->fContentStart - 1;
1499                 }
1500             }
1501             // FIXME: roll end-trimming into writeBlockTrim call
1502             while (fStart < bodyEnd && ' ' >= bodyEnd[-1]) {
1503                 --bodyEnd;
1504             }
1505             int blockSize = (int) (bodyEnd - fStart);
1506             SkASSERT(blockSize >= 0);
1507             if (blockSize) {
1508                 string debugstr(fStart, blockSize);
1509                 this->writeBlock(blockSize, fStart);
1510             }
1511             startDef = &child;
1512             this->setStart(child.fContentStart, &child);
1513             auto mapFind = fBmhParser->fMethodMap.find(child.fName);
1514             if (fBmhParser->fMethodMap.end() != mapFind) {
1515                 inConstructor = false;
1516                 method = &mapFind->second;
1517                 methodName = child.fName;
1518             } else if (root) {
1519                 methodName = root->fName + "::" + child.fName;
1520                 size_t lastName = root->fName.rfind(':');
1521                 lastName = string::npos == lastName ? 0 : lastName + 1;
1522                 inConstructor = root->fName.substr(lastName) == child.fName;
1523                 method = root->find(methodName, RootDefinition::AllowParens::kNo);
1524             }
1525             fContinuation = child.fContentEnd;
1526             if (!method) {
1527                 continue;
1528             }
1529             if (method->fCloned) {
1530                 clonedMethod = method;
1531                 continue;
1532             }
1533             this->methodOut(method, child);
1534             sawConst = false;
1535             continue;
1536         }
1537         if (Definition::Type::kKeyWord == child.fType) {
1538             if (child.fUndocumented) {
1539                 continue;
1540             }
1541             switch (child.fKeyWord) {
1542                 case KeyWord::kStruct:
1543                 case KeyWord::kClass:
1544                     fICSStack.push_back(&child);
1545                     fStructEnded = false;
1546                     fStructMemberTab = 0;
1547                     // if struct contains members, compute their name and comment tabs
1548                     if (child.fChildren.size() > 0) {
1549                         const ParentPair* testPair = &pair;
1550                         while ((testPair = testPair->fPrev)) {
1551                             if (KeyWord::kClass == testPair->fParent->fKeyWord) {
1552                                 inStruct = fInStruct = true;
1553                                 break;
1554                             }
1555                         }
1556                     }
1557                     if (fInStruct) {
1558                         // try child; root+child; root->parent+child; etc.
1559                         int trial = 0;
1560                         RootDefinition* search = root;
1561                         Definition* parent = search->fParent;
1562                         do {
1563                             string name;
1564                             if (0 == trial) {
1565                                 name = child.fName;
1566                             } else if (1 == trial) {
1567                                 name = root->fName + "::" + child.fName;
1568                             } else if (2 == trial) {
1569                                 name = root->fName;
1570                             } else {
1571                                 SkASSERT(parent);
1572                                 name = parent->fName + "::" + child.fName;
1573                                 search = parent->asRoot();
1574                                 parent = search->fParent;
1575                             }
1576                             fBmhStructDef = search->find(name, RootDefinition::AllowParens::kNo);
1577                         } while (!fBmhStructDef && ++trial);
1578                         root = fBmhStructDef->asRoot();
1579                         SkASSERT(root);
1580                         fIndent += 4;
1581                         this->structSizeMembers(child);
1582                         fIndent -= 4;
1583                         SkASSERT(!fIndentNext);
1584                         fIndentNext = true;
1585                     }
1586                     if (child.fChildren.size() > 0) {
1587                         const char* bodyEnd = fDeferComment ? fDeferComment->fContentStart - 1 :
1588                                 child.fContentStart;
1589                         this->writeBlockTrim((int) (bodyEnd - fStart), fStart);
1590                         if (fPendingMethod) {
1591                             if (fIndent >= 4) {
1592                                 this->indentOut();
1593                             }
1594                             fPendingMethod = false;
1595                         }
1596                         startDef = requireDense ? requireDense : &child;
1597                         if (requireDense) {
1598                             startDef = requireDense;
1599                             this->setStart(requireDense->fContentStart, requireDense);
1600                         } else {
1601                             startDef = &child;
1602                             this->setStart(child.fContentStart, &child);
1603                         }
1604                         requireDense = nullptr;
1605                         if (!fInStruct && (!root || child.fName != root->fName)) {
1606                             root = &fBmhParser->fClassMap[child.fName];
1607                             fRootTopic = root->fParent;
1608                             SkASSERT(!root->fVisited);
1609                             root->clearVisited();
1610 #if 0
1611     // this seems better balanced; but real problem is probably fInStruct
1612                             if (fIndentStack.size() > 0) {
1613                                 this->indentOut();
1614                             }
1615                             SkASSERT(!fIndent);
1616 #else
1617                             fIndent = 0;
1618 #endif
1619                             fBmhStructDef = root;
1620                         }
1621                         if (child.fName == root->fName) {
1622                             if (Definition* parent = root->fParent) {
1623                                 if (MarkType::kTopic == parent->fMarkType ||
1624                                         MarkType::kSubtopic == parent->fMarkType) {
1625                                     const char* commentStart = root->fContentStart;
1626                                     unsigned index = 0;
1627                                     const char* commentEnd = root->fChildren[0]->fStart;
1628                                     int line = 1;
1629                                     do {
1630                                         TextParser parser(root->fFileName, commentStart, commentEnd, line);
1631                                         if (!parser.eof()) {
1632                                             parser.skipWhiteSpace();
1633                                         }
1634                                         if (!parser.eof()) {
1635                                             break;
1636                                         }
1637                                         commentStart = root->fChildren[index]->fTerminator;
1638                                         ++index;
1639                                         SkASSERT(index < root->fChildren.size());
1640                                         commentEnd = root->fChildren[index]->fStart;
1641                                     } while (true);
1642                                     this->structOut(root, *root, commentStart, commentEnd);
1643                                 } else {
1644                                     SkASSERT(0); // incomplete
1645                                 }
1646                             } else {
1647                                 SkASSERT(0); // incomplete
1648                             }
1649                         } else {
1650                             SkASSERT(fInStruct);
1651                             Definition* priorBlock = fBmhStructDef;
1652                             Definition* codeBlock = nullptr;
1653                             Definition* nextBlock = nullptr;
1654                             for (auto test : fBmhStructDef->fChildren) {
1655                                 if (MarkType::kCode == test->fMarkType) {
1656                                     SkASSERT(!codeBlock);  // FIXME: check enum earlier
1657                                     codeBlock = test;
1658                                     continue;
1659                                 }
1660                                 if (codeBlock) {
1661                                     nextBlock = test;
1662                                     break;
1663                                 }
1664                                 priorBlock = test;
1665                             }
1666                       // FIXME: trigger error earlier if inner #Struct or #Class is missing #Code
1667                             SkASSERT(codeBlock);
1668                             SkASSERT(nextBlock);  // FIXME: check enum for correct order earlier
1669                             const char* commentStart = codeBlock->fTerminator;
1670                             const char* commentEnd = nextBlock->fStart;
1671                     // FIXME: trigger error if #Code is present but comment is before it earlier
1672                             SkASSERT(priorBlock); // code always preceded by #Line (I think)
1673                             TextParser priorComment(priorBlock->fFileName,
1674                                     priorBlock->fTerminator, codeBlock->fStart,
1675                                     priorBlock->fLineCount);
1676                             priorComment.trimEnd();
1677                             if (!priorComment.eof()) {
1678                                 return priorBlock->reportError<bool>(
1679                                         "expect no comment before #Code");
1680                             }
1681                             TextParser nextComment(codeBlock->fFileName, commentStart,
1682                                     commentEnd, codeBlock->fLineCount);
1683                             nextComment.trimEnd();
1684                             if (!priorComment.eof()) {
1685                                 return priorBlock->reportError<bool>(
1686                                         "expect comment after #Code");
1687                             }
1688                             if (!nextComment.eof()) {
1689 
1690                             }
1691                             fIndentNext = true;
1692                             this->structOut(root, *fBmhStructDef, commentStart, commentEnd);
1693                         }
1694                         fDeferComment = nullptr;
1695                     } else {
1696                        // empty forward reference
1697                         bool writeTwo = '\n' == child.fContentStart[-1]
1698                                 && '\n' == child.fContentStart[-2];
1699                         if (writeTwo) {
1700                             const char* bodyEnd = fDeferComment ? fDeferComment->fContentStart - 1 :
1701                                     child.fContentStart;
1702                             this->writeBlockTrim((int) (bodyEnd - fStart), fStart);
1703                             this->lf(writeTwo ? 2 : 1);
1704                             fIndent = 0;
1705                             this->writeBlockTrim(child.length() + 1, child.fContentStart);
1706                             writeTwo = '\n' == child.fContentEnd[1]
1707                                     && '\n' == child.fContentStart[2];
1708                             this->lf(writeTwo ? 2 : 1);
1709                             fStart = child.fContentEnd + 1;
1710                             fDeferComment = nullptr;
1711                         }
1712                     }
1713                     break;
1714                 case KeyWord::kEnum: {
1715                     fInEnum = true;
1716                     this->enumHeaderOut(root, child);
1717                     this->enumSizeItems(child);
1718                 } break;
1719                 case KeyWord::kConst:
1720                 case KeyWord::kConstExpr:
1721                     sawConst = !memberStart || staticOnly;
1722                     if (!memberStart) {
1723                         memberStart = &child;
1724                         staticOnly = true;
1725                     }
1726                     if (MarkType::kConst == child.fMarkType) {
1727                         auto constIter = fBmhParser->fConstMap.find(child.fName);
1728                         if (fBmhParser->fConstMap.end() != constIter) {
1729                             const RootDefinition& bmhConst = constIter->second;
1730                             this->constOut(&child, &bmhConst);
1731                             fDeferComment = nullptr;
1732                         }
1733                     }
1734                     break;
1735                 case KeyWord::kStatic:
1736                     if (!memberStart) {
1737                         memberStart = &child;
1738                         staticOnly = true;
1739                     }
1740                     break;
1741                 case KeyWord::kInt:
1742                 case KeyWord::kUint8_t:
1743                 case KeyWord::kUint16_t:
1744                 case KeyWord::kUint32_t:
1745                 case KeyWord::kUint64_t:
1746                 case KeyWord::kUintPtr_t:
1747                 case KeyWord::kUnsigned:
1748                 case KeyWord::kSize_t:
1749                 case KeyWord::kFloat:
1750                 case KeyWord::kBool:
1751                 case KeyWord::kChar:
1752                 case KeyWord::kVoid:
1753                     staticOnly = false;
1754                     if (!memberStart) {
1755                         memberStart = &child;
1756                     }
1757                     break;
1758                 case KeyWord::kAlignAs:
1759                 case KeyWord::kPublic:
1760                 case KeyWord::kPrivate:
1761                 case KeyWord::kProtected:
1762                 case KeyWord::kFriend:
1763                 case KeyWord::kInline:
1764                 case KeyWord::kSK_API:
1765                 case KeyWord::kTemplate:
1766                 case KeyWord::kUsing:
1767                     break;
1768                 case KeyWord::kTypedef:
1769                     SkASSERT(!memberStart);
1770                     memberStart = &child;
1771                     deferredTypedefComment = fDeferComment;
1772                     sawTypedef = true;
1773                     break;
1774                 case KeyWord::kSK_BEGIN_REQUIRE_DENSE:
1775                     requireDense = &child;
1776                     break;
1777                 default:
1778                     SkASSERT(0);
1779             }
1780             if (KeyWord::kUint8_t == child.fKeyWord || KeyWord::kUint32_t == child.fKeyWord) {
1781                 continue;
1782             } else {
1783                 if (fInEnum && child.fChildren.size() > 0
1784                         && KeyWord::kClass == child.fChildren[0]->fKeyWord) {
1785                     if (!this->populate(child.fChildren[0], &pair, root)) {
1786                         return false;
1787                     }
1788                 } else {
1789                     if (!this->populate(&child, &pair, root)) {
1790                         return false;
1791                     }
1792                     if (KeyWord::kClass == child.fKeyWord || KeyWord::kStruct == child.fKeyWord) {
1793                         fICSStack.pop_back();
1794                         fStructEnded = true;
1795                         if (fInStruct) {
1796                             fInStruct = false;
1797                             do {
1798                                 SkASSERT(root);
1799                                 root = const_cast<RootDefinition*>(root->fParent->asRoot());
1800                             } while (MarkType::kTopic == root->fMarkType ||
1801                                     MarkType::kSubtopic == root->fMarkType);
1802 #if 0
1803                         }
1804                         if (MarkType::kStruct == root->fMarkType ||
1805                                 MarkType::kClass == root->fMarkType) {
1806 #else
1807                             SkASSERT(MarkType::kStruct == root->fMarkType ||
1808                                     MarkType::kClass == root->fMarkType);
1809 #endif
1810                             fPendingMethod = false;
1811                             if (startDef) {
1812                                 fPendingMethod = find_start(startDef, fStart);
1813                             }
1814                             fOutdentNext = !fPendingMethod;
1815                         }
1816                     }
1817                 }
1818             }
1819             continue;
1820         }
1821         if (Definition::Type::kBracket == child.fType) {
1822             if (KeyWord::kEnum == child.fParent->fKeyWord ||
1823                     (KeyWord::kClass == child.fParent->fKeyWord && child.fParent->fParent &&
1824                     KeyWord::kEnum == child.fParent->fParent->fKeyWord)) {
1825                 SkASSERT(Bracket::kBrace == child.fBracket);
1826                 this->enumMembersOut(*child.fParent);
1827                 this->writeString("};");
1828                 this->lf(2);
1829                 startDef = child.fParent;
1830                 this->setStart(child.fParent->fContentEnd, child.fParent);
1831                 SkASSERT(';' == fStart[0]);
1832                 ++fStart;
1833                 fDeferComment = nullptr;
1834                 fInEnum = false;
1835                 if (fIndentNext) {
1836 //                    fIndent -= 4;
1837                     fIndentNext = false;
1838                 }
1839                 continue;
1840             }
1841             if (KeyWord::kDefine == child.fKeyWord && this->defineOut(child)) {
1842                 fDeferComment = nullptr;
1843                 continue;
1844             }
1845             fDeferComment = nullptr;
1846             if (KeyWord::kClass == def->fKeyWord || KeyWord::kStruct == def->fKeyWord) {
1847                 fIndentNext = true;
1848             }
1849             if (!this->populate(&child, &pair, root)) {
1850                 return false;
1851             }
1852             if (KeyWord::kClass == def->fKeyWord || KeyWord::kStruct == def->fKeyWord) {
1853                 if (def->iRootParent() && (!fStartSetter
1854                         || MarkType::kMethod != fStartSetter->fMarkType)) {
1855                     this->setStart(child.fContentEnd, &child);
1856                     fDeferComment = nullptr;
1857                 }
1858             }
1859             continue;
1860         }
1861         if (Definition::Type::kWord == child.fType) {
1862             if (MarkType::kMember == child.fMarkType) {
1863                 if (!memberStart) {
1864                     auto iter = def->fTokens.begin();
1865                     std::advance(iter, child.fParentIndex - 1);
1866                     memberStart = &*iter;
1867                     staticOnly = false;
1868                 }
1869                 if (!fStructMemberTab) {
1870                     SkASSERT(KeyWord::kStruct == def->fParent->fKeyWord);
1871                     fIndent += 4;
1872                     this->structSizeMembers(*def->fParent);
1873                     fIndent -= 4;
1874                     fIndentNext = true;
1875                 }
1876                 SkASSERT(fBmhStructDef);
1877                 memberEnd = this->structMemberOut(memberStart, child);
1878                 startDef = &child;
1879                 this->setStart(child.fContentEnd + 1, &child);
1880                 fDeferComment = nullptr;
1881             } else if (MarkType::kNone == child.fMarkType && sawConst && fEnumDef) {
1882                 const Definition* bmhConst = nullptr;
1883                 string match;
1884                 if (root) {
1885                     match = root->fName + "::";
1886                 }
1887                 match += string(child.fContentStart, child.fContentEnd - child.fContentStart);
1888                 for (auto enumChild : fEnumDef->fChildren) {
1889                     if (MarkType::kConst == enumChild->fMarkType && enumChild->fName == match) {
1890                         bmhConst = enumChild;
1891                         break;
1892                     }
1893                 }
1894                 if (bmhConst) {
1895                     this->constOut(memberStart, bmhConst);
1896                     fDeferComment = nullptr;
1897                     sawConst = false;
1898                 }
1899             } else if (MarkType::kNone == child.fMarkType && sawConst && !fEnumDef) {
1900                 string match;
1901                 if (root) {
1902                     match = root->fName + "::";
1903                     match += string(child.fContentStart, child.fContentEnd - child.fContentStart);
1904                     auto bmhClassIter = fBmhParser->fClassMap.find(root->fName);
1905                     if (fBmhParser->fClassMap.end() != bmhClassIter) {
1906                         RootDefinition& bmhClass = bmhClassIter->second;
1907                         auto constIter = std::find_if(bmhClass.fLeaves.begin(), bmhClass.fLeaves.end(),
1908                                 [match](std::pair<const string, Definition>& leaf){ return match == leaf.second.fName; } );
1909                         if (bmhClass.fLeaves.end() != constIter) {
1910                             const Definition& bmhConst = constIter->second;
1911                             if (MarkType::kConst == bmhConst.fMarkType
1912                                     && MarkType::kSubtopic == bmhConst.fParent->fMarkType) {
1913                                 fBmhConst = &bmhConst;
1914                                 fConstDef = &child;
1915                             }
1916                         }
1917                     }
1918                 }
1919             }
1920             if (child.fMemberStart) {
1921                 memberStart = &child;
1922                 staticOnly = false;
1923             }
1924             continue;
1925         }
1926         if (Definition::Type::kPunctuation == child.fType) {
1927             if (Punctuation::kSemicolon == child.fPunctuation) {
1928                 if (sawConst && fBmhConst) {  // find bmh documentation. Parent must be subtopic.
1929                     const Definition* subtopic = fBmhConst->fParent;
1930                     SkASSERT(subtopic);
1931                     SkASSERT(MarkType::kSubtopic == subtopic->fMarkType);
1932                     auto firstConst = std::find_if(subtopic->fChildren.begin(),
1933                             subtopic->fChildren.end(),
1934                             [](const Definition* def){ return MarkType::kConst == def->fMarkType;});
1935                     SkASSERT(firstConst != subtopic->fChildren.end());
1936                     bool constIsFirst = *firstConst == fBmhConst;
1937                     if (constIsFirst) {  // If first #Const child, output subtopic description.
1938                         this->constOut(memberStart, subtopic);
1939                         // find member / value / comment tabs
1940                         // look for a one-to-one correspondence between bmh and include
1941                         this->constSizeMembers(root);
1942                         fDeferComment = nullptr;
1943                     }
1944                     // after const code, output #Line description as short comment
1945                     auto lineIter = std::find_if(fBmhConst->fChildren.begin(),
1946                             fBmhConst->fChildren.end(),
1947                             [](const Definition* def){ return MarkType::kLine == def->fMarkType; });
1948                     SkASSERT(fBmhConst->fChildren.end() != lineIter);
1949                     const Definition* lineDef = *lineIter;
1950                     if (fConstLength > 100) {
1951                         this->writeCommentHeader();
1952                         this->writeSpace();
1953                         this->rewriteBlock(lineDef->length(), lineDef->fContentStart, Phrase::kYes);
1954                         this->writeCommentTrailer(OneLine::kYes);
1955                     }
1956                     this->lfcr();
1957                     TextParser constText(memberStart);
1958                     const char* nameEnd = constText.trimmedBracketEnd('=');
1959                     SkAssertResult(constText.skipToEndBracket('='));
1960                     const char* valueEnd = constText.trimmedBracketEnd(';');
1961                     this->writeBlock((int) (nameEnd - memberStart->fContentStart),
1962                             memberStart->fContentStart);
1963                     this->indentToColumn(fConstValueTab);
1964                     this->writeBlock((int) (valueEnd - constText.fChar), constText.fChar);
1965                     this->writeString(";");
1966                     if (fConstLength <= 100) {
1967                         this->indentToColumn(fConstCommentTab);
1968                         this->writeString("//!<");
1969                         this->writeSpace();
1970                         this->rewriteBlock(lineDef->length(), lineDef->fContentStart, Phrase::kYes);
1971                     }
1972                     this->setStart(child.fContentStart + 1, &child);
1973                     fDeferComment = nullptr;
1974                     fBmhConst = nullptr;
1975                     sawConst = false;
1976                 } else if (sawTypedef) {
1977                     const Definition* bmhTypedef = nullptr;
1978                     if (root) {
1979                         SkDEBUGCODE(auto classIter = fBmhParser->fClassMap.find(root->fName));
1980                         SkASSERT(fBmhParser->fClassMap.end() != classIter);
1981                         RootDefinition& classDef = fBmhParser->fClassMap[root->fName];
1982                         auto leafIter = classDef.fLeaves.find(memberStart->fName);
1983                         if (classDef.fLeaves.end() != leafIter) {
1984                             bmhTypedef = &leafIter->second;
1985                         }
1986                     }
1987                     if (!bmhTypedef) {
1988                         auto typedefIter = fBmhParser->fTypedefMap.find(memberStart->fName);
1989                         SkASSERT(fBmhParser->fTypedefMap.end() != typedefIter);
1990                         bmhTypedef = &typedefIter->second;
1991                     }
1992                     fDeferComment = deferredTypedefComment;
1993                     this->constOut(memberStart, bmhTypedef);
1994                     fDeferComment = nullptr;
1995                     sawTypedef = false;
1996                 }
1997                 memberStart = nullptr;
1998                 staticOnly = false;
1999                 if (inStruct) {
2000                     fInStruct = false;
2001                 }
2002                 continue;
2003             }
2004             if (Punctuation::kLeftBrace == child.fPunctuation ||
2005                     Punctuation::kColon == child.fPunctuation ||
2006                     Punctuation::kAsterisk == child.fPunctuation
2007                 ) {
2008                 continue;
2009             }
2010         }
2011     }
2012     return true;
2013 }
2014 
populate(BmhParser & bmhParser)2015 bool IncludeWriter::populate(BmhParser& bmhParser) {
2016     bool allPassed = true;
2017     for (auto& includeMapper : fIncludeMap) {
2018         size_t lastSlash = includeMapper.first.rfind('/');
2019         if (string::npos == lastSlash) {
2020             lastSlash = includeMapper.first.rfind('\\');
2021         }
2022         if (string::npos == lastSlash || lastSlash >= includeMapper.first.length() - 1) {
2023             return this->reportError<bool>("malformed include name");
2024         }
2025         string fileName = includeMapper.first.substr(lastSlash + 1);
2026         if (".h" != fileName.substr(fileName.length() - 2)) {
2027             return this->reportError<bool>("expected fileName.h");
2028         }
2029         string skClassName = fileName.substr(0, fileName.length() - 2);
2030         this->reset();
2031         fOut = fopen(fileName.c_str(), "wb");
2032         if (!fOut) {
2033             SkDebugf("could not open output file %s\n", fileName.c_str());
2034             return false;
2035         }
2036         RootDefinition* root =
2037                 bmhParser.fClassMap.end() == bmhParser.fClassMap.find(skClassName) ?
2038                 nullptr : &bmhParser.fClassMap[skClassName];
2039         fBmhParser = &bmhParser;
2040         if (root) {
2041             fRootTopic = root->fParent;
2042             root->clearVisited();
2043         } else {
2044             SkASSERT("Sk" == skClassName.substr(0, 2));
2045             string topicName = skClassName.substr(2);
2046             auto topicIter = bmhParser.fTopicMap.find(topicName);
2047             SkASSERT(bmhParser.fTopicMap.end() != topicIter);
2048             fRootTopic = topicIter->second->asRoot();
2049             fFirstWrite = true;   // write file information after includes
2050         }
2051         fFileName = includeMapper.second.fFileName;
2052         this->setStartBack(includeMapper.second.fContentStart, &includeMapper.second);
2053         fEnd = includeMapper.second.fContentEnd;
2054         fAnonymousEnumCount = 1;
2055         this->writeHeader(includeMapper);
2056         allPassed &= this->populate(&includeMapper.second, nullptr, root);
2057         this->writeBlock((int) (fEnd - fStart), fStart);
2058 #if 0
2059         if (fIndentStack.size() > 0) {
2060             this->indentOut();
2061         }
2062         SkASSERT(!fIndent);
2063 #else
2064         fIndent = 0;
2065 #endif
2066         this->lfcr();
2067         this->writePending();
2068         fclose(fOut);
2069         fflush(fOut);
2070         size_t slash = fFileName.find_last_of('/');
2071         if (string::npos == slash) {
2072             slash = 0;
2073         }
2074         size_t back = fFileName.find_last_of('\\');
2075         if (string::npos == back) {
2076             back = 0;
2077         }
2078         string dir = fFileName.substr(0, SkTMax(slash, back) + 1);
2079         string readname = dir + fileName;
2080         if (ParserCommon::WrittenFileDiffers(fileName, readname)) {
2081             SkDebugf("wrote updated %s\n", fileName.c_str());
2082         } else {
2083             remove(fileName.c_str());
2084         }
2085     }
2086     return allPassed;
2087 }
2088 
resolveMethod(const char * start,const char * end,bool first)2089 string IncludeWriter::resolveMethod(const char* start, const char* end, bool first) {
2090     string methodname(start, end - start);
2091     if (string::npos != methodname.find("()")) {
2092         return "";
2093     }
2094     string substitute;
2095     auto rootDefIter = fBmhParser->fMethodMap.find(methodname);
2096     if (fBmhParser->fMethodMap.end() != rootDefIter) {
2097         substitute = methodname + "()";
2098     } else {
2099         RootDefinition* parent = nullptr;
2100         for (auto candidate : fRootTopic->fChildren) {
2101             if (MarkType::kClass == candidate->fMarkType
2102                     || MarkType::kStruct == candidate->fMarkType) {
2103                 parent = candidate->asRoot();
2104                 break;
2105             }
2106         }
2107         if (parent) {
2108             auto defRef = parent->find(parent->fName + "::" + methodname,
2109                     RootDefinition::AllowParens::kNo);
2110             if (defRef && MarkType::kMethod == defRef->fMarkType) {
2111                 substitute = methodname + "()";
2112             } else {
2113                 auto defineIter = fBmhParser->fDefineMap.find(methodname);
2114                 if (fBmhParser->fDefineMap.end() != defineIter) {
2115                     const RootDefinition& defineDef = defineIter->second;
2116                     auto codeIter = std::find_if(defineDef.fChildren.begin(),
2117                             defineDef.fChildren.end(),
2118                             [](Definition* child){ return MarkType::kCode == child->fMarkType; } );
2119                     if (defineDef.fChildren.end() != codeIter) {
2120                         const Definition* codeDef = *codeIter;
2121                         string codeContents(codeDef->fContentStart, codeDef->length());
2122                         size_t namePos = codeContents.find(methodname);
2123                         if (string::npos != namePos) {
2124                             size_t parenPos = namePos + methodname.length();
2125                             if (parenPos < codeContents.length() && '(' == codeContents[parenPos]) {
2126                                 substitute = methodname + "()";
2127                             }
2128                         }
2129                     }
2130                 }
2131             }
2132         }
2133     }
2134     if (fMethodDef && methodname == fMethodDef->fName) {
2135         TextParser report(fBmhMethod);
2136         report.reportError("method should not include references to itself");
2137         return "";
2138     }
2139     if (fBmhMethod) {
2140         for (auto child : fBmhMethod->fChildren) {
2141             if (MarkType::kParam != child->fMarkType) {
2142                 continue;
2143             }
2144             if (methodname == child->fName) {
2145                 return "";
2146             }
2147         }
2148     }
2149     return substitute;
2150 }
2151 
resolveAlias(const Definition * def)2152 string IncludeWriter::resolveAlias(const Definition* def) {
2153     for (auto child : def->fChildren) {
2154         if (MarkType::kSubstitute == child->fMarkType) {
2155             return string(child->fContentStart, (int) (child->fContentEnd - child->fContentStart));
2156         }
2157         if (MarkType::kAlias == child->fMarkType && def->fName == child->fName) {
2158             return this->resolveAlias(child);
2159         }
2160     }
2161     return "";
2162 }
2163 
resolveRef(const char * start,const char * end,bool first,RefType * refType)2164 string IncludeWriter::resolveRef(const char* start, const char* end, bool first,
2165         RefType* refType) {
2166         // look up Xxx_Xxx
2167     string undername(start, end - start);
2168     for (const auto& external : fBmhParser->fExternals) {
2169         if (external.fName == undername) {
2170             *refType = RefType::kExternal;
2171             return external.fName;
2172         }
2173     }
2174     *refType = RefType::kNormal;
2175     SkASSERT(string::npos == undername.find(' '));
2176     const Definition* rootDef = nullptr;
2177     string substitute;
2178     {
2179         auto rootDefIter = fBmhParser->fTopicMap.find(undername);
2180         if (fBmhParser->fTopicMap.end() != rootDefIter) {
2181             rootDef = rootDefIter->second;
2182         } else {
2183             string prefixedName = fRootTopic->fName + '_' + undername;
2184             rootDefIter = fBmhParser->fTopicMap.find(prefixedName);
2185             if (fBmhParser->fTopicMap.end() != rootDefIter) {
2186                 rootDef = rootDefIter->second;
2187             } else if (fBmhStructDef) {
2188                 string localPrefix = fBmhStructDef->fFiddle + '_' + undername;
2189                 rootDefIter = fBmhParser->fTopicMap.find(localPrefix);
2190                 if (fBmhParser->fTopicMap.end() != rootDefIter) {
2191                     rootDef = rootDefIter->second;
2192                 }
2193                 if (!rootDef) {
2194                     size_t doubleColon = fBmhStructDef->fName.rfind("::");
2195                     if (string::npos != doubleColon && undername
2196                             == fBmhStructDef->fName.substr(doubleColon + 2)) {
2197                         substitute = fBmhStructDef->fName;
2198                     }
2199                 }
2200             }
2201             if (!rootDef && fEnumDef && "Sk" + prefixedName == fEnumDef->fFiddle) {
2202                 rootDef = fEnumDef;
2203             }
2204             if (!rootDef && !substitute.length()) {
2205                 auto aliasIter = fBmhParser->fAliasMap.find(undername);
2206                 if (fBmhParser->fAliasMap.end() != aliasIter) {
2207                     rootDef = aliasIter->second;
2208                 } else if (fInEnum && fEnumDef && this->findEnumSubtopic(undername, &rootDef)) {
2209                 } else if (!first) {
2210                     this->fChar = start;
2211                     this->fLine = start;
2212                     this->fEnd = end;
2213                     this->reportError("reference unfound");
2214                     return "";
2215                 }
2216             }
2217         }
2218     }
2219     if (rootDef) {
2220         MarkType rootType = rootDef->fMarkType;
2221         if (MarkType::kSubtopic == rootType || MarkType::kTopic == rootType
2222                 || MarkType::kAlias == rootType) {
2223             substitute = this->resolveAlias(rootDef);
2224         }
2225         if (!substitute.length()) {
2226             string match = rootDef->fName;
2227             size_t index;
2228             while (string::npos != (index = match.find('_'))) {
2229                 match.erase(index, 1);
2230             }
2231             string skmatch = "Sk" + match;
2232             auto parent = MarkType::kAlias == rootType ? rootDef->fParent : rootDef;
2233             for (auto child : parent->fChildren) {
2234                 // there may be more than one
2235                 // prefer the one mostly closely matching in text
2236                 if ((MarkType::kClass == child->fMarkType ||
2237                     MarkType::kStruct == child->fMarkType ||
2238                     MarkType::kTypedef == child->fMarkType ||
2239                     (MarkType::kEnum == child->fMarkType && !child->fAnonymous) ||
2240                     MarkType::kEnumClass == child->fMarkType) && (match == child->fName ||
2241                     skmatch == child->fName)) {
2242                     substitute = child->fName;
2243                     break;
2244                 }
2245             }
2246         }
2247         if (!substitute.length()) {
2248             for (auto child : rootDef->fChildren) {
2249                 if (MarkType::kSubstitute == child->fMarkType) {
2250                     substitute = string(child->fContentStart, child->length());
2251                     break;
2252                 }
2253                 // there may be more than one
2254                 // if so, it's a bug since it's unknown which is the right one
2255                 if (MarkType::kClass == child->fMarkType ||
2256                         MarkType::kStruct == child->fMarkType ||
2257                         (MarkType::kEnum == child->fMarkType && !child->fAnonymous) ||
2258                         MarkType::kEnumClass == child->fMarkType) {
2259                     SkASSERT("" == substitute);
2260                     substitute = child->fName;
2261                     if (MarkType::kEnum == child->fMarkType) {
2262                         size_t parentClassEnd = substitute.find("::");
2263                         SkASSERT(string::npos != parentClassEnd);
2264                         string subEnd = substitute.substr(parentClassEnd + 2);
2265                         if (fInEnum) {
2266                             substitute = subEnd;
2267                         }
2268                         if (subEnd == undername) {
2269                             break;
2270                         }
2271                     }
2272                 }
2273             }
2274         }
2275         if (!substitute.length()) {
2276             const Definition* parent = rootDef;
2277             do {
2278                 parent = parent->fParent;
2279             } while (parent && (MarkType::kSubtopic == parent->fMarkType
2280                         || MarkType::kTopic == parent->fMarkType));
2281             if (parent) {
2282                 if (MarkType::kClass == parent->fMarkType ||
2283                         MarkType::kStruct == parent->fMarkType ||
2284                         (MarkType::kEnum == parent->fMarkType && !parent->fAnonymous) ||
2285                         MarkType::kEnumClass == parent->fMarkType) {
2286                     if (parent->fParent != fRootTopic) {
2287                         substitute = parent->fName;
2288                         substitute += ' ';
2289                         substitute += ParserCommon::ConvertRef(rootDef->fName, false);
2290                     } else {
2291                         size_t underpos = undername.find('_');
2292                         if (string::npos != underpos) {
2293                             string parentName = undername.substr(0, underpos);
2294                             string skName = "Sk" + parentName;
2295                             if (skName == parent->fName) {
2296                                 SkASSERT(start >= fLastDescription->fContentStart);
2297                                 string lastDescription = string(fLastDescription->fContentStart,
2298                                         (int) (start - fLastDescription->fContentStart));
2299                                 size_t lineStart = lastDescription.rfind('\n');
2300                                 SkASSERT(string::npos != lineStart);
2301                                 fLine = fLastDescription->fContentStart + lineStart + 1;
2302                                 fChar = start;
2303                                 fEnd = end;
2304                                 return this->reportError<string>("remove underline");
2305                             }
2306                         }
2307                         substitute += ParserCommon::ConvertRef(undername, first);
2308                     }
2309                 }
2310             }
2311         }
2312     }
2313     // Ensure first word after period is capitalized if substitute is lower cased.
2314     if (first && isupper(start[0]) && substitute.length() > 0 && islower(substitute[0])) {
2315         substitute[0] = start[0];
2316     }
2317     return substitute;
2318 }
2319 
lookupMethod(const PunctuationState punctuation,const Word word,const int lastSpace,const int run,int lastWrite,const char * data,bool hasIndirection)2320 int IncludeWriter::lookupMethod(const PunctuationState punctuation, const Word word,
2321         const int lastSpace, const int run, int lastWrite, const char* data,
2322         bool hasIndirection) {
2323     int wordStart = lastSpace;
2324     while (' ' >= data[wordStart]) {
2325         ++wordStart;
2326     }
2327     const int wordEnd = PunctuationState::kDelimiter == punctuation ||
2328             PunctuationState::kParen == punctuation ||
2329             PunctuationState::kPeriod == punctuation ? run - 1 : run;
2330     string temp;
2331     if (hasIndirection && '(' != data[wordEnd - 1] && ')' != data[wordEnd - 1]) {
2332         // FIXME: hard-coded to assume a.b or a->b is a.b() or a->b().
2333         // need to check class a for member b to see if this is so
2334         TextParser parser(fFileName, &data[wordStart], &data[wordEnd], fLineCount);
2335         const char* indirection = parser.anyOf(".>");
2336         if (&data[wordEnd] <= &indirection[2] || 'f' != indirection[1] ||
2337                 !isupper(indirection[2])) {
2338             temp = string(&data[wordStart], wordEnd - wordStart) + "()";
2339         }
2340     } else {
2341         temp = this->resolveMethod(&data[wordStart], &data[wordEnd], Word::kFirst == word);
2342     }
2343     if (temp.length()) {
2344         if (wordStart > lastWrite) {
2345             SkASSERT(data[wordStart - 1] >= ' ');
2346             if (' ' == data[lastWrite]) {
2347                 this->writeSpace();
2348             }
2349             this->firstBlockTrim(wordStart - lastWrite, &data[lastWrite]);
2350             if (' ' == data[wordStart - 1]) {
2351                 this->writeSpace();
2352             }
2353         }
2354         SkASSERT(temp[temp.length() - 1] > ' ');
2355         this->writeString(temp.c_str());
2356         lastWrite = wordEnd;
2357     }
2358     return lastWrite;
2359 }
2360 
lookupReference(const PunctuationState punctuation,const Word word,const int start,const int run,int lastWrite,const char last,const char * data)2361 int IncludeWriter::lookupReference(const PunctuationState punctuation, const Word word,
2362         const int start, const int run, int lastWrite, const char last, const char* data) {
2363     const int end = PunctuationState::kDelimiter == punctuation ||
2364             PunctuationState::kParen == punctuation ||
2365             PunctuationState::kPeriod == punctuation ? run - 1 : run;
2366     RefType refType = RefType::kUndefined;
2367     string resolved = string(&data[start], (size_t) (end - start));
2368     string temp = this->resolveRef(&data[start], &data[end], Word::kFirst == word, &refType);
2369     if (!temp.length()) {
2370         if (Word::kFirst != word && '_' != last) {
2371             temp = ParserCommon::ConvertRef(resolved, false);
2372         }
2373     }
2374     if (temp.length()) {
2375         if (start > lastWrite) {
2376             SkASSERT(data[start - 1] >= ' ');
2377             if (' ' == data[lastWrite]) {
2378                 this->writeSpace();
2379             }
2380             this->firstBlockTrim(start - lastWrite, &data[lastWrite]);
2381             if (' ' == data[start - 1]) {
2382                 this->writeSpace();
2383             }
2384         }
2385         SkASSERT(temp[temp.length() - 1] > ' ');
2386         this->writeString(temp.c_str());
2387         lastWrite = end;
2388     }
2389     return lastWrite;
2390 }
2391 
2392 /* returns true if rewriteBlock wrote linefeeds */
rewriteBlock(int size,const char * data,Phrase phrase)2393 IncludeWriter::Wrote IncludeWriter::rewriteBlock(int size, const char* data, Phrase phrase) {
2394     bool wroteLineFeeds = false;
2395     while (size > 0 && data[0] <= ' ') {
2396         --size;
2397         ++data;
2398     }
2399     while (size > 0 && data[size - 1] <= ' ') {
2400         --size;
2401     }
2402     if (0 == size) {
2403         return Wrote::kNone;
2404     }
2405     if (fReturnOnWrite) {
2406         return Wrote::kChars;
2407     }
2408     int run = 0;
2409     Word word = Word::kStart;
2410     PunctuationState punctuation = Phrase::kNo == phrase ?
2411             PunctuationState::kStart : PunctuationState::kSpace;
2412     int start = 0;
2413     int lastWrite = 0;
2414     int lineFeeds = 0;
2415     int lastPrintable = 0;
2416     int lastSpace = -1;
2417     char c = 0;
2418     char last = 0;
2419     bool embeddedIndirection = false;
2420     bool embeddedSymbol = false;
2421     bool hasLower = false;
2422     bool hasUpper = false;
2423     bool hasIndirection = false;
2424     bool hasSymbol = false;
2425     while (run < size) {
2426         last = c;
2427         c = data[run];
2428         SkASSERT(' ' <= c || '\n' == c);
2429         if (lineFeeds && ' ' < c) {
2430             if (lastPrintable >= lastWrite) {
2431                 if (' ' == data[lastWrite]) {
2432                     this->writeSpace();
2433                     lastWrite++;
2434                 }
2435                 this->writeBlock(lastPrintable - lastWrite + 1, &data[lastWrite]);
2436             }
2437             if (lineFeeds > 1) {
2438                 this->lf(2);
2439             }
2440             this->lfcr(); // defer the indent until non-whitespace is seen
2441             lastWrite = run;
2442             lineFeeds = 0;
2443         }
2444         if (' ' < c) {
2445             lastPrintable = run;
2446         }
2447         switch (c) {
2448             case '\n':
2449                 ++lineFeeds;
2450                 wroteLineFeeds = true;
2451             case ' ':
2452                 switch (word) {
2453                     case Word::kStart:
2454                         break;
2455                     case Word::kUnderline:
2456                     case Word::kCap:
2457                     case Word::kFirst:
2458                         if (!hasLower) {
2459                             break;
2460                         }
2461                         lastWrite = this->lookupReference(punctuation, word, start, run,
2462                                 lastWrite, last, data);
2463                         break;
2464                     case Word::kMixed:
2465                         if (hasUpper && hasLower && !hasSymbol && lastSpace > 0) {
2466                             lastWrite = this->lookupMethod(punctuation, word, lastSpace, run,
2467                                     lastWrite, data, hasIndirection);
2468                         }
2469                         break;
2470                     default:
2471                         SkASSERT(0);
2472                 }
2473                 punctuation = PunctuationState::kPeriod == punctuation ||
2474                         (PunctuationState::kStart == punctuation && ' ' >= last) ?
2475                         PunctuationState::kStart : PunctuationState::kSpace;
2476                 word = Word::kStart;
2477                 embeddedIndirection = false;
2478                 embeddedSymbol = false;
2479                 hasLower = false;
2480                 hasUpper = false;
2481                 hasIndirection = false;
2482                 hasSymbol = false;
2483                 lastSpace = run;
2484                 break;
2485             case '.': case ',': case ';': case ':': case ')':
2486                 switch (word) {
2487                     case Word::kStart:
2488                         punctuation = PunctuationState::kDelimiter;
2489                     case Word::kCap:
2490                     case Word::kFirst:
2491                     case Word::kUnderline:
2492                     case Word::kMixed:
2493                         if (PunctuationState::kDelimiter == punctuation ||
2494                                 PunctuationState::kPeriod == punctuation) {
2495                             word = Word::kMixed;
2496                         }
2497                         punctuation = '.' == c ? PunctuationState::kPeriod :
2498                                 PunctuationState::kDelimiter;
2499                         break;
2500                     default:
2501                         SkASSERT(0);
2502                 }
2503                 ('.' == c ? embeddedIndirection : embeddedSymbol) = true;
2504                 break;
2505             case '>':
2506                 if ('-' == last) {
2507                     embeddedIndirection = true;
2508                     break;
2509                 }
2510             case '\'': // possessive apostrophe isn't treated as delimiting punctation
2511             case '\"': // quote is passed straight through
2512             case '=':
2513             case '!':  // assumed not to be punctuation, but a programming symbol
2514             case '&': case '<': case '{': case '}': case '/': case '*': case '[': case ']':
2515                 word = Word::kMixed;
2516                 embeddedSymbol = true;
2517                 break;
2518             case '(':
2519                 if (' ' == last) {
2520                     punctuation = PunctuationState::kParen;
2521                 } else {
2522                     word = Word::kMixed;
2523                 }
2524                 embeddedSymbol = true;
2525                 break;
2526             case '_':
2527                 switch (word) {
2528                     case Word::kStart:
2529                         word = Word::kMixed;
2530                         break;
2531                     case Word::kCap:
2532                     case Word::kFirst:
2533                     case Word::kUnderline:
2534                         word = Word::kUnderline;
2535                         break;
2536                     case Word::kMixed:
2537                         break;
2538                     default:
2539                         SkASSERT(0);
2540                 }
2541                 hasSymbol |= embeddedSymbol;
2542                 break;
2543             case '+':
2544                 // hackery to allow C++
2545                 SkASSERT('C' == last || '+' == last);  // FIXME: don't allow + outside of #Formula
2546                 break;
2547             case 'A': case 'B': case 'C': case 'D': case 'E':
2548             case 'F': case 'G': case 'H': case 'I': case 'J':
2549             case 'K': case 'L': case 'M': case 'N': case 'O':
2550             case 'P': case 'Q': case 'R': case 'S': case 'T':
2551             case 'U': case 'V': case 'W': case 'X': case 'Y':
2552             case 'Z':
2553                 switch (word) {
2554                     case Word::kStart:
2555                         word = PunctuationState::kStart == punctuation ? Word::kFirst : Word::kCap;
2556                         start = run;
2557                         break;
2558                     case Word::kCap:
2559                     case Word::kFirst:
2560                         if (!isupper(last) && '~' != last) {
2561                             word = Word::kMixed;
2562                         }
2563                         break;
2564                     case Word::kUnderline:
2565                         // some word in Xxx_XXX_Xxx can be all upper, but all can't: XXX_XXX
2566                         if ('_' != last && !isupper(last)) {
2567                             word = Word::kMixed;
2568                         }
2569                         break;
2570                     case Word::kMixed:
2571                         break;
2572                     default:
2573                         SkASSERT(0);
2574                 }
2575                 hasUpper = true;
2576                 if (PunctuationState::kPeriod == punctuation ||
2577                         PunctuationState::kDelimiter == punctuation) {
2578                     word = Word::kMixed;
2579                 }
2580                 hasIndirection |= embeddedIndirection;
2581                 hasSymbol |= embeddedSymbol;
2582                 break;
2583             case 'a': case 'b': case 'c': case 'd': case 'e':
2584             case 'f': case 'g': case 'h': case 'i': case 'j':
2585             case 'k': case 'l': case 'm': case 'n': case 'o':
2586             case 'p': case 'q': case 'r': case 's': case 't':
2587             case 'u': case 'v': case 'w': case 'x': case 'y':
2588             case 'z':
2589             case '0': case '1': case '2': case '3': case '4':
2590             case '5': case '6': case '7': case '8': case '9':
2591             case '%':  // to do : ensure that preceding is a number
2592             case '-':
2593                 switch (word) {
2594                     case Word::kStart:
2595                         word = Word::kMixed;
2596                         break;
2597                     case Word::kMixed:
2598                     case Word::kCap:
2599                     case Word::kFirst:
2600                     case Word::kUnderline:
2601                         break;
2602                     default:
2603                         SkASSERT(0);
2604                 }
2605                 hasLower = true;
2606                 punctuation = PunctuationState::kStart;
2607                 hasIndirection |= embeddedIndirection;
2608                 hasSymbol |= embeddedSymbol;
2609                 break;
2610             case '~':
2611                 SkASSERT(Word::kStart == word);
2612                 word = PunctuationState::kStart == punctuation ? Word::kFirst : Word::kCap;
2613                 start = run;
2614                 hasUpper = true;
2615                 hasIndirection |= embeddedIndirection;
2616                 hasSymbol |= embeddedSymbol;
2617                 break;
2618             default:
2619                 SkASSERT(0);
2620         }
2621         ++run;
2622     }
2623     if ((word == Word::kCap || word == Word::kFirst || word == Word::kUnderline) && hasLower) {
2624         lastWrite = this->lookupReference(punctuation, word, start, run, lastWrite, last, data);
2625     } else if (word == Word::kMixed && hasUpper && hasLower && !hasSymbol && lastSpace > 0) {
2626         lastWrite = this->lookupMethod(punctuation, word, lastSpace, run, lastWrite, data,
2627                 hasIndirection && !hasSymbol);
2628     }
2629     if (run > lastWrite) {
2630         if (' ' == data[lastWrite]) {
2631             this->writeSpace();
2632         }
2633         this->writeBlock(run - lastWrite, &data[lastWrite]);
2634     }
2635     return wroteLineFeeds ? Wrote::kLF : Wrote::kChars;
2636 }
2637 
paddedString(int num)2638 static string paddedString(int num) {
2639     auto padded = std::to_string(num);
2640     padded.insert(0, 2U - std::min(string::size_type(2), padded.length()), '0');
2641     return padded;
2642 }
2643 
writeHeader(std::pair<const string,Definition> & include)2644 bool IncludeWriter::writeHeader(std::pair<const string, Definition>& include) {
2645     std::chrono::system_clock::time_point now = std::chrono::system_clock::now();
2646     time_t tt = std::chrono::system_clock::to_time_t(now);
2647     tm local_tm = *localtime(&tt);
2648 
2649     // find end of copyright header
2650     fChar = fStart;
2651     this->skipWhiteSpace();
2652     if (!this->skipExact(
2653             "/*\n"
2654             " * Copyright ")) {
2655         return this->reportError<bool>("copyright mismatch 1");
2656     }
2657     const char* date = fChar;
2658     this->skipToSpace();
2659     string yearStr(date, fChar - date);
2660     int year = stoi(yearStr);
2661     if (year < 2005 || year > local_tm.tm_year + 1900) {
2662         return this->reportError<bool>("copyright year out of range");
2663     }
2664     this->skipSpace();
2665     const char android[] = "The Android Open Source Project";
2666     const char google[] = "Google Inc.";
2667     if (this->startsWith(android)) {
2668         this->skipExact(android);
2669     } else if (!this->skipExact(google)) {
2670         return this->reportError<bool>("copyright mismatch 2");
2671     }
2672     if (!this->skipExact(
2673             "\n"
2674             " *\n"
2675             " * Use of this source code is governed by a BSD-style license that can be\n"
2676             " * found in the LICENSE file.\n"
2677             " */\n"
2678             "\n"
2679             )) {
2680         return this->reportError<bool>("copyright mismatch 2");
2681     }
2682     this->writeBlock(fChar - fStart, fStart);
2683     this->lf(2);
2684     this->writeString("/* Generated by tools/bookmaker from");
2685     this->writeSpace();
2686     string includeName = include.first;
2687     std::replace(includeName.begin(), includeName.end(), '\\', '/');
2688     this->writeString(includeName);
2689     this->writeSpace();
2690     this->writeString("and");
2691     this->writeSpace();
2692     string bmhName = fRootTopic->fFileName;
2693     std::replace(bmhName.begin(), bmhName.end(), '\\', '/');
2694     this->writeString(bmhName);
2695     this->lfcr();
2696     fIndent = 3;
2697     string dateTimeStr = std::to_string(local_tm.tm_year + 1900) + "-"
2698             + paddedString(local_tm.tm_mon + 1) + "-"
2699             + paddedString(local_tm.tm_mday) + " "
2700             + paddedString(local_tm.tm_hour) + ":"
2701             + paddedString(local_tm.tm_min) + ":"
2702             + paddedString(local_tm.tm_sec);
2703     this->writeString("on");
2704     this->writeSpace();
2705     this->writeString(dateTimeStr);
2706     this->writeString(". Additional documentation and examples can be found at:");
2707     this->lfcr();
2708     this->writeString("https://skia.org/user/api/");
2709     size_t bmhPageStart = bmhName.rfind('/');
2710     size_t bmhPageEnd = bmhName.rfind('.');
2711     if (string::npos == bmhPageStart || string::npos == bmhPageEnd) {
2712         return this->reportError<bool>("badly formed bmh page name");
2713     }
2714     ++bmhPageStart;
2715     string bmhPage = bmhName.substr(bmhPageStart, bmhPageEnd - bmhPageStart);
2716     this->writeString(bmhPage);
2717     this->lf(2);
2718     this->writeString("You may edit either file directly. Structural changes to public interfaces require");
2719     this->lfcr();
2720     this->writeString("editing both files. After editing");
2721     this->writeSpace();
2722     this->writeString(bmhName);
2723     this->writeSpace();
2724     this->writeString(", run:");
2725     this->lfcr();
2726     fIndent += 4;
2727     this->writeString("bookmaker -b docs -i");
2728     this->writeSpace();
2729     this->writeString(includeName);
2730     this->writeSpace();
2731     this->writeString("-p");
2732     this->lfcr();
2733     fIndent -= 4;
2734     this->writeString("to create an updated version of this file.");
2735     this->lfcr();
2736     fIndent = 1;
2737     this->writeString("*/");
2738     this->lf(2);
2739     fIndent = 0;
2740     if (this->startsWith("/* Generated by tools/bookmaker from")) {
2741         this->skipToEndBracket("*/");
2742         if (!this->skipExact("*/\n\n")) {
2743             return this->reportError<bool>("malformed generated comment");
2744         }
2745     }
2746     fStart = fChar;
2747 
2748     return true;
2749 }
2750