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