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, ¤tEnumItem);
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, ¤tEnumItem);
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