blob: e0cf2714c5ba321f781e5f8a6083579afd90de46 [file] [log] [blame]
Cary Clark8032b982017-07-28 11:04:54 -04001/*
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
10void IncludeWriter::enumHeaderOut(const RootDefinition* root,
11 const Definition& child) {
12 const Definition* enumDef = nullptr;
13 const char* bodyEnd = fDeferComment ? fDeferComment->fContentStart - 1 :
14 child.fContentStart;
15 this->writeBlockTrim((int) (bodyEnd - fStart), fStart); // may write nothing
16 this->lf(2);
17 fDeferComment = nullptr;
18 fStart = child.fContentStart;
19 const auto& nameDef = child.fTokens.front();
20 string fullName;
21 if (nullptr != nameDef.fContentEnd) {
22 string enumName(nameDef.fContentStart,
23 (int) (nameDef.fContentEnd - nameDef.fContentStart));
24 fullName = root->fName + "::" + enumName;
25 enumDef = root->find(enumName);
26 if (!enumDef) {
27 enumDef = root->find(fullName);
28 }
29 SkASSERT(enumDef);
30 // child[0] should be #Code comment starts at child[0].fTerminator
31 // though skip until #Code is found (in case there's a #ToDo, etc)
32 // child[1] should be #Const comment ends at child[1].fStart
33 // comment becomes enum header (if any)
34 } else {
35 string enumName(root->fName);
36 enumName += "::_anonymous";
37 if (fAnonymousEnumCount > 1) {
38 enumName += '_' + to_string(fAnonymousEnumCount);
39 }
40 enumDef = root->find(enumName);
41 SkASSERT(enumDef);
42 ++fAnonymousEnumCount;
43 }
44 Definition* codeBlock = nullptr;
45 const char* commentStart = nullptr;
46 bool wroteHeader = false;
47 SkDEBUGCODE(bool foundConst = false);
48 for (auto test : enumDef->fChildren) {
49 if (MarkType::kCode == test->fMarkType) {
50 SkASSERT(!codeBlock); // FIXME: check enum for correct order earlier
51 codeBlock = test;
52 commentStart = codeBlock->fTerminator;
53 continue;
54 }
55 if (!codeBlock) {
56 continue;
57 }
58 const char* commentEnd = test->fStart;
59 if (!wroteHeader &&
60 !this->contentFree((int) (commentEnd - commentStart), commentStart)) {
61 this->writeCommentHeader();
62 this->writeString("\\enum");
63 this->writeSpace();
64 this->writeString(fullName.c_str());
65 fIndent += 4;
66 this->lfcr();
67 wroteHeader = true;
68 }
69 this->rewriteBlock((int) (commentEnd - commentStart), commentStart);
70 if (MarkType::kAnchor == test->fMarkType) {
71 commentStart = test->fContentStart;
72 commentEnd = test->fChildren[0]->fStart;
73 this->writeSpace();
74 this->rewriteBlock((int) (commentEnd - commentStart), commentStart);
75 this->writeSpace();
76 }
77 commentStart = test->fTerminator;
78 if (MarkType::kConst == test->fMarkType) {
79 SkASSERT(codeBlock); // FIXME: check enum for correct order earlier
80 SkDEBUGCODE(foundConst = true);
81 break;
82 }
83 }
84 SkASSERT(codeBlock);
85 SkASSERT(foundConst);
86 if (wroteHeader) {
87 fIndent -= 4;
88 this->lfcr();
89 this->writeCommentTrailer();
90 }
91 bodyEnd = child.fChildren[0]->fContentStart;
92 SkASSERT('{' == bodyEnd[0]);
93 ++bodyEnd;
94 this->lfcr();
95 this->writeBlock((int) (bodyEnd - fStart), fStart); // write include "enum Name {"
96 fIndent += 4;
97 this->singleLF();
98 fStart = bodyEnd;
99 fEnumDef = enumDef;
100}
101
102void IncludeWriter::enumMembersOut(const RootDefinition* root, const Definition& child) {
103 // iterate through include tokens and find how much remains for 1 line comments
104 // put ones that fit on same line, ones that are too big on preceding line?
105 const Definition* currentEnumItem = nullptr;
106 const char* commentStart = nullptr;
107 const char* lastEnd = nullptr;
108 int commentLen = 0;
109 enum class State {
110 kNoItem,
111 kItemName,
112 kItemValue,
113 kItemComment,
114 };
115 State state = State::kNoItem;
116 // can't use (auto& token : child.fTokens) 'cause we need state one past end
117 auto tokenIter = child.fTokens.begin();
118 for (int onePast = 0; onePast < 2; onePast += tokenIter == child.fTokens.end()) {
119 const Definition* token = onePast ? nullptr : &*tokenIter++;
120 if (token && Definition::Type::kBracket == token->fType) {
121 if (Bracket::kSlashSlash == token->fBracket) {
122 fStart = token->fContentEnd;
123 continue; // ignore old inline comments
124 }
125 if (Bracket::kSlashStar == token->fBracket) {
126 fStart = token->fContentEnd + 1;
127 continue; // ignore old inline comments
128 }
129 SkASSERT(0); // incomplete
130 }
131 if (token && Definition::Type::kWord != token->fType) {
132 SkASSERT(0); // incomplete
133 }
134 if (token && State::kItemName == state) {
135 TextParser enumLine(token->fFileName, lastEnd,
136 token->fContentStart, token->fLineCount);
137 const char* end = enumLine.anyOf(",}=");
138 SkASSERT(end);
139 state = '=' == *end ? State::kItemValue : State::kItemComment;
140 if (State::kItemValue == state) { // write enum value
141 this->indentToColumn(fEnumItemValueTab);
142 this->writeString("=");
143 this->writeSpace();
144 lastEnd = token->fContentEnd;
145 this->writeBlock((int) (lastEnd - token->fContentStart),
146 token->fContentStart); // write const value if any
147 continue;
148 }
149 }
150 if (token && State::kItemValue == state) {
151 TextParser valueEnd(token->fFileName, lastEnd,
152 token->fContentStart, token->fLineCount);
153 const char* end = valueEnd.anyOf(",}");
154 if (!end) { // write expression continuation
155 if (' ' == lastEnd[0]) {
156 this->writeSpace();
157 }
158 this->writeBlock((int) (token->fContentEnd - lastEnd), lastEnd);
159 continue;
160 }
161 }
162 if (State::kNoItem != state) {
163 this->writeString(",");
164 SkASSERT(currentEnumItem);
165 if (currentEnumItem->fShort) {
166 this->indentToColumn(fEnumItemCommentTab);
167 this->writeString("//!<");
168 this->writeSpace();
169 this->rewriteBlock(commentLen, commentStart);
170 }
171 if (onePast) {
172 fIndent -= 4;
173 }
174 this->lfcr();
175 if (token && State::kItemValue == state) {
176 fStart = token->fContentStart;
177 }
178 state = State::kNoItem;
179 }
180 SkASSERT(State::kNoItem == state);
181 if (onePast) {
182 break;
183 }
184 SkASSERT(token);
185 string itemName = root->fName + "::" + string(token->fContentStart,
186 (int) (token->fContentEnd - token->fContentStart));
187 for (auto& enumItem : fEnumDef->fChildren) {
188 if (MarkType::kConst != enumItem->fMarkType) {
189 continue;
190 }
191 if (itemName != enumItem->fName) {
192 continue;
193 }
194 currentEnumItem = enumItem;
195 break;
196 }
197 SkASSERT(currentEnumItem);
198 // if description fits, it goes after item
199 commentStart = currentEnumItem->fContentStart;
200 const char* commentEnd;
201 if (currentEnumItem->fChildren.size() > 0) {
202 commentEnd = currentEnumItem->fChildren[0]->fStart;
203 } else {
204 commentEnd = currentEnumItem->fContentEnd;
205 }
206 TextParser enumComment(fFileName, commentStart, commentEnd, currentEnumItem->fLineCount);
207 if (enumComment.skipToLineStart()) { // skip const value
208 commentStart = enumComment.fChar;
209 commentLen = (int) (commentEnd - commentStart);
210 } else {
211 const Definition* privateDef = currentEnumItem->fChildren[0];
212 SkASSERT(MarkType::kPrivate == privateDef->fMarkType);
213 commentStart = privateDef->fContentStart;
214 commentLen = (int) (privateDef->fContentEnd - privateDef->fContentStart);
215 }
216 SkASSERT(commentLen > 0 && commentLen < 1000);
217 if (!currentEnumItem->fShort) {
218 this->writeCommentHeader();
219 fIndent += 4;
220 bool wroteLineFeed = Wrote::kLF == this->rewriteBlock(commentLen, commentStart);
221 fIndent -= 4;
222 if (wroteLineFeed || fColumn > 100 - 3 /* space * / */ ) {
223 this->lfcr();
224 } else {
225 this->writeSpace();
226 }
227 this->writeCommentTrailer();
228 }
229 lastEnd = token->fContentEnd;
230 this->lfcr();
231 if (',' == fStart[0]) {
232 ++fStart;
233 }
234 this->writeBlock((int) (lastEnd - fStart), fStart); // enum item name
235 fStart = token->fContentEnd;
236 state = State::kItemName;
237 }
238}
239
240void IncludeWriter::enumSizeItems(const Definition& child) {
241 enum class State {
242 kNoItem,
243 kItemName,
244 kItemValue,
245 kItemComment,
246 };
247 State state = State::kNoItem;
248 int longestName = 0;
249 int longestValue = 0;
250 int valueLen = 0;
251 const char* lastEnd = nullptr;
252 SkASSERT(child.fChildren.size() == 1 || child.fChildren.size() == 2);
253 auto brace = child.fChildren[0];
254 SkASSERT(Bracket::kBrace == brace->fBracket);
255 for (auto& token : brace->fTokens) {
256 if (Definition::Type::kBracket == token.fType) {
257 if (Bracket::kSlashSlash == token.fBracket) {
258 continue; // ignore old inline comments
259 }
260 if (Bracket::kSlashStar == token.fBracket) {
261 continue; // ignore old inline comments
262 }
263 SkASSERT(0); // incomplete
264 }
265 if (Definition::Type::kWord != token.fType) {
266 SkASSERT(0); // incomplete
267 }
268 if (State::kItemName == state) {
269 TextParser enumLine(token.fFileName, lastEnd,
270 token.fContentStart, token.fLineCount);
271 const char* end = enumLine.anyOf(",}=");
272 SkASSERT(end);
273 state = '=' == *end ? State::kItemValue : State::kItemComment;
274 if (State::kItemValue == state) {
275 valueLen = (int) (token.fContentEnd - token.fContentStart);
276 lastEnd = token.fContentEnd;
277 continue;
278 }
279 }
280 if (State::kItemValue == state) {
281 TextParser valueEnd(token.fFileName, lastEnd,
282 token.fContentStart, token.fLineCount);
283 const char* end = valueEnd.anyOf(",}");
284 if (!end) { // write expression continuation
285 valueLen += (int) (token.fContentEnd - lastEnd);
286 continue;
287 }
288 }
289 if (State::kNoItem != state) {
290 longestValue = SkTMax(longestValue, valueLen);
291 state = State::kNoItem;
292 }
293 SkASSERT(State::kNoItem == state);
294 lastEnd = token.fContentEnd;
295 longestName = SkTMax(longestName, (int) (lastEnd - token.fContentStart));
296 state = State::kItemName;
297 }
298 if (State::kItemValue == state) {
299 longestValue = SkTMax(longestValue, valueLen);
300 }
301 fEnumItemValueTab = longestName + fIndent + 1 /* space before = */ ;
302 if (longestValue) {
303 longestValue += 3; /* = space , */
304 }
305 fEnumItemCommentTab = fEnumItemValueTab + longestValue + 1 /* space before //!< */ ;
306 // iterate through bmh children and see which comments fit on include lines
307 for (auto& enumItem : fEnumDef->fChildren) {
308 if (MarkType::kConst != enumItem->fMarkType) {
309 continue;
310 }
311 TextParser enumLine(enumItem);
312 enumLine.trimEnd();
313 enumLine.skipToLineStart(); // skip const value
314 const char* commentStart = enumLine.fChar;
315 enumLine.skipLine();
316 ptrdiff_t lineLen = enumLine.fChar - commentStart + 5 /* //!< space */ ;
317 if (!enumLine.eof()) {
318 enumLine.skipWhiteSpace();
319 }
320 enumItem->fShort = enumLine.eof() && fEnumItemCommentTab + lineLen < 100;
321 }
322}
323
324// walk children and output complete method doxygen description
Cary Clark579985c2017-07-31 11:48:27 -0400325void IncludeWriter::methodOut(const Definition* method, const Definition& child) {
326 fBmhMethod = method;
327 fMethodDef = &child;
Cary Clark8032b982017-07-28 11:04:54 -0400328 fContinuation = nullptr;
329 fDeferComment = nullptr;
330 if (0 == fIndent) {
331 fIndent = 4;
332 }
333 this->writeCommentHeader();
334 fIndent += 4;
335 const char* commentStart = method->fContentStart;
336 int commentLen = (int) (method->fContentEnd - commentStart);
337 bool breakOut = false;
338 for (auto methodProp : method->fChildren) {
339 switch (methodProp->fMarkType) {
340 case MarkType::kDefinedBy:
341 commentStart = methodProp->fTerminator;
342 break;
343 case MarkType::kDeprecated:
344 case MarkType::kPrivate:
345 commentLen = (int) (methodProp->fStart - commentStart);
346 if (commentLen > 0) {
347 SkASSERT(commentLen < 1000);
348 if (Wrote::kNone != this->rewriteBlock(commentLen, commentStart)) {
349 this->lfcr();
350 }
351 }
352 commentStart = methodProp->fContentStart;
353 commentLen = (int) (methodProp->fContentEnd - commentStart);
354 if (commentLen > 0) {
355 if (Wrote::kNone != this->rewriteBlock(commentLen, commentStart)) {
356 this->lfcr();
357 }
358 }
359 commentStart = methodProp->fTerminator;
360 commentLen = (int) (method->fContentEnd - commentStart);
361 break;
362 default:
363 commentLen = (int) (methodProp->fStart - commentStart);
364 breakOut = true;
365 }
366 if (breakOut) {
367 break;
368 }
369 }
370 SkASSERT(commentLen > 0 && commentLen < 1000);
371 this->rewriteBlock(commentLen, commentStart);
372 // compute indention column
373 size_t column = 0;
374 bool hasParmReturn = false;
375 for (auto methodPart : method->fChildren) {
376 if (MarkType::kParam == methodPart->fMarkType) {
377 column = SkTMax(column, methodPart->fName.length());
378 hasParmReturn = true;
379 } else if (MarkType::kReturn == methodPart->fMarkType) {
380 hasParmReturn = true;
381 }
382 }
383 if (hasParmReturn) {
384 this->lf(2);
385 column += fIndent + sizeof("@return ");
386 int saveIndent = fIndent;
387 for (auto methodPart : method->fChildren) {
388 const char* partStart = methodPart->fContentStart;
389 const char* partEnd = methodPart->fContentEnd;
390 if (MarkType::kParam == methodPart->fMarkType) {
391 this->writeString("@param");
392 this->writeSpace();
393 this->writeString(methodPart->fName.c_str());
394 } else if (MarkType::kReturn == methodPart->fMarkType) {
395 this->writeString("@return");
396 } else {
397 continue;
398 }
399 while ('\n' == partEnd[-1]) {
400 --partEnd;
401 }
402 while ('#' == partEnd[-1]) { // FIXME: so wrong; should not be before fContentEnd
403 --partEnd;
404 }
405 this->indentToColumn(column);
406 int partLen = (int) (partEnd - partStart);
407 SkASSERT(partLen > 0 && partLen < 200);
408 fIndent = column;
409 this->rewriteBlock(partLen, partStart);
410 fIndent = saveIndent;
411 this->lfcr();
412 }
413 } else {
414 this->lfcr();
415 }
416 fIndent -= 4;
417 this->lfcr();
418 this->writeCommentTrailer();
Cary Clark579985c2017-07-31 11:48:27 -0400419 fBmhMethod = nullptr;
420 fMethodDef = nullptr;
Cary Clark8032b982017-07-28 11:04:54 -0400421}
422
423void IncludeWriter::structOut(const Definition* root, const Definition& child,
424 const char* commentStart, const char* commentEnd) {
425 this->writeCommentHeader();
426 this->writeString("\\");
427 SkASSERT(MarkType::kClass == child.fMarkType || MarkType::kStruct == child.fMarkType);
428 this->writeString(MarkType::kClass == child.fMarkType ? "class" : "struct");
429 this->writeSpace();
430 this->writeString(child.fName.c_str());
431 fIndent += 4;
432 this->lfcr();
433 this->rewriteBlock((int) (commentEnd - commentStart), commentStart);
434 fIndent -= 4;
435 this->lfcr();
436 this->writeCommentTrailer();
437}
438
439void IncludeWriter::structMemberOut(const Definition* memberStart, const Definition& child) {
440 const char* commentStart = nullptr;
441 ptrdiff_t commentLen = 0;
442 string name(child.fContentStart, (int) (child.fContentEnd - child.fContentStart));
443 bool isShort;
444 for (auto memberDef : fStructDef->fChildren) {
445 if (memberDef->fName.length() - name.length() == memberDef->fName.find(name)) {
446 commentStart = memberDef->fContentStart;
447 commentLen = memberDef->fContentEnd - memberDef->fContentStart;
448 isShort = memberDef->fShort;
449 break;
450 }
451 }
452 if (!isShort) {
453 this->writeCommentHeader();
454 fIndent += 4;
455 bool wroteLineFeed = Wrote::kLF == this->rewriteBlock(commentLen, commentStart);
456 fIndent -= 4;
457 if (wroteLineFeed || fColumn > 100 - 3 /* space * / */ ) {
458 this->lfcr();
459 } else {
460 this->writeSpace();
461 }
462 this->writeCommentTrailer();
463 }
464 this->lfcr();
465 this->writeBlock((int) (memberStart->fContentEnd - memberStart->fContentStart),
466 memberStart->fContentStart);
467 this->indentToColumn(fStructMemberTab);
468 this->writeString(name.c_str());
469 this->writeString(";");
470 if (isShort) {
471 this->indentToColumn(fStructCommentTab);
472 this->writeString("//!<");
473 this->writeSpace();
474 this->rewriteBlock(commentLen, commentStart);
475 this->lfcr();
476 }
477}
478
479void IncludeWriter::structSizeMembers(Definition& child) {
480 int longestType = 0;
481 Definition* typeStart = nullptr;
482 int longestName = 0;
483 SkASSERT(child.fChildren.size() == 1 || child.fChildren.size() == 2);
484 bool inEnum = false;
485 auto brace = child.fChildren[0];
486 SkASSERT(Bracket::kBrace == brace->fBracket);
487 for (auto& token : brace->fTokens) {
488 if (Definition::Type::kBracket == token.fType) {
489 if (Bracket::kSlashSlash == token.fBracket) {
490 continue; // ignore old inline comments
491 }
492 if (Bracket::kSlashStar == token.fBracket) {
493 continue; // ignore old inline comments
494 }
495 if (Bracket::kParen == token.fBracket) {
496 break;
497 }
498 SkASSERT(0); // incomplete
499 }
500 if (Definition::Type::kKeyWord == token.fType) {
501 switch (token.fKeyWord) {
502 case KeyWord::kEnum:
503 inEnum = true;
504 break;
505 case KeyWord::kConst:
506 case KeyWord::kConstExpr:
507 case KeyWord::kStatic:
508 case KeyWord::kInt:
509 case KeyWord::kUint32_t:
510 case KeyWord::kSize_t:
511 case KeyWord::kFloat:
512 case KeyWord::kBool:
513 case KeyWord::kVoid:
514 if (!typeStart) {
515 typeStart = &token;
516 }
517 break;
518 default:
519 break;
520 }
521 continue;
522 }
523 if (Definition::Type::kPunctuation == token.fType) {
524 if (inEnum) {
525 SkASSERT(Punctuation::kSemicolon == token.fPunctuation);
526 inEnum = false;
527 }
528 continue;
529 }
530 if (Definition::Type::kWord != token.fType) {
531 SkASSERT(0); // incomplete
532 }
533 if (MarkType::kMember == token.fMarkType) {
534 TextParser typeStr(token.fFileName, typeStart->fContentStart, token.fContentStart,
535 token.fLineCount);
536 typeStr.trimEnd();
537 longestType = SkTMax(longestType, (int) (typeStr.fEnd - typeStart->fContentStart));
538 longestName = SkTMax(longestName, (int) (token.fContentEnd - token.fContentStart));
539 typeStart->fMemberStart = true;
540 typeStart = nullptr;
541 continue;
542 }
543 SkASSERT(MarkType::kNone == token.fMarkType);
544 if (!typeStart) {
545 typeStart = &token;
546 }
547 }
548 fStructMemberTab = longestType + fIndent + 1 /* space before name */ ;
549 fStructCommentTab = fStructMemberTab + longestName + 2 /* ; space */ ;
550 // iterate through bmh children and see which comments fit on include lines
551 for (auto& member : fStructDef->fChildren) {
552 if (MarkType::kMember != member->fMarkType) {
553 continue;
554 }
555 TextParser memberLine(member);
556 memberLine.trimEnd();
557 const char* commentStart = memberLine.fChar;
558 memberLine.skipLine();
559 ptrdiff_t lineLen = memberLine.fChar - commentStart + 5 /* //!< space */ ;
560 if (!memberLine.eof()) {
561 memberLine.skipWhiteSpace();
562 }
563 member->fShort = memberLine.eof() && fStructCommentTab + lineLen < 100;
564 }
565}
566
567bool IncludeWriter::populate(Definition* def, RootDefinition* root) {
568 // write bulk of original include up to class, method, enum, etc., excepting preceding comment
569 // find associated bmh object
570 // write any associated comments in Doxygen form
571 // skip include comment
572 // if there is a series of same named methods, write one set of comments, then write all methods
573 string methodName;
574 const Definition* method;
575 const Definition* clonedMethod = nullptr;
576 const Definition* memberStart = nullptr;
577 fContinuation = nullptr;
578 bool inStruct = false;
579 for (auto& child : def->fTokens) {
580 if (child.fPrivate) {
581 continue;
582 }
583 if (fContinuation) {
584 if (Definition::Type::kKeyWord == child.fType) {
585 if (KeyWord::kFriend == child.fKeyWord || KeyWord::kBool == child.fKeyWord) {
586 continue;
587 }
588 }
589 if (Definition::Type::kBracket == child.fType && Bracket::kParen == child.fBracket) {
590 if (!clonedMethod) {
591 continue;
592 }
593 int alternate = 1;
594 ptrdiff_t childLen = child.fContentEnd - child.fContentStart;
595 SkASSERT(')' == child.fContentStart[childLen]);
596 ++childLen;
597 do {
598 TextParser params(clonedMethod->fFileName, clonedMethod->fStart,
599 clonedMethod->fContentStart, clonedMethod->fLineCount);
600 params.skipToEndBracket('(');
601 if (params.fEnd - params.fChar >= childLen &&
602 !strncmp(params.fChar, child.fContentStart, childLen)) {
Cary Clark579985c2017-07-31 11:48:27 -0400603 this->methodOut(clonedMethod, child);
Cary Clark8032b982017-07-28 11:04:54 -0400604 break;
605 }
606 ++alternate;
607 string alternateMethod = methodName + '_' + to_string(alternate);
608 clonedMethod = root->find(alternateMethod);
609 } while (clonedMethod);
610 if (!clonedMethod) {
611 return this->reportError<bool>("cloned method not found");
612 }
613 clonedMethod = nullptr;
614 continue;
615 }
616 if (Definition::Type::kWord == child.fType) {
617 if (clonedMethod) {
618 continue;
619 }
620 size_t len = (size_t) (child.fContentEnd - child.fContentStart);
621 const char operatorStr[] = "operator";
622 size_t operatorLen = sizeof(operatorStr) - 1;
623 if (len >= operatorLen && !strncmp(child.fContentStart, operatorStr, operatorLen)) {
624 fContinuation = child.fContentEnd;
625 continue;
626 }
627 }
628 if (Definition::Type::kPunctuation == child.fType &&
629 (Punctuation::kSemicolon == child.fPunctuation ||
630 Punctuation::kLeftBrace == child.fPunctuation)) {
631 SkASSERT(fContinuation[0] == '(');
632 const char* continueEnd = child.fContentStart;
633 while (continueEnd > fContinuation && isspace(continueEnd[-1])) {
634 --continueEnd;
635 }
636 methodName += string(fContinuation, continueEnd - fContinuation);
637 method = root->find(methodName);
638 if (!method) {
639 fLineCount = child.fLineCount;
640 fclose(fOut); // so we can see what we've written so far
641 return this->reportError<bool>("method not found");
642 }
Cary Clark579985c2017-07-31 11:48:27 -0400643 this->methodOut(method, child);
Cary Clark8032b982017-07-28 11:04:54 -0400644 continue;
645 }
646 methodName += "()";
647 method = root->find(methodName);
648 if (MarkType::kDefinedBy == method->fMarkType) {
649 method = method->fParent;
650 }
651 if (method) {
Cary Clark579985c2017-07-31 11:48:27 -0400652 this->methodOut(method, child);
Cary Clark8032b982017-07-28 11:04:54 -0400653 continue;
654 }
655 fLineCount = child.fLineCount;
656 fclose(fOut); // so we can see what we've written so far
657 return this->reportError<bool>("method not found");
658 }
659 if (Bracket::kSlashSlash == child.fBracket || Bracket::kSlashStar == child.fBracket) {
660 if (!fDeferComment) {
661 fDeferComment = &child;
662 }
663 continue;
664 }
665 if (MarkType::kMethod == child.fMarkType) {
666 const char* bodyEnd = fDeferComment ? fDeferComment->fContentStart - 1 :
667 child.fContentStart;
668 // FIXME: roll end-trimming into writeBlockTrim call
669 while (fStart < bodyEnd && ' ' >= bodyEnd[-1]) {
670 --bodyEnd;
671 }
672 int blockSize = (int) (bodyEnd - fStart);
673 if (blockSize) {
674 this->writeBlock(blockSize, fStart);
675 }
676 fStart = child.fContentStart;
677 methodName = root->fName + "::" + child.fName;
678 fContinuation = child.fContentEnd;
679 method = root->find(methodName);
680 if (!method) {
681 continue;
682 }
683 if (method->fCloned) {
684 clonedMethod = method;
685 continue;
686 }
Cary Clark579985c2017-07-31 11:48:27 -0400687 this->methodOut(method, child);
Cary Clark8032b982017-07-28 11:04:54 -0400688 continue;
689 }
690 if (Definition::Type::kKeyWord == child.fType) {
691 const Definition* structDef = nullptr;
692 switch (child.fKeyWord) {
693 case KeyWord::kStruct:
694 // if struct contains members, compute their name and comment tabs
695 inStruct = fInStruct = child.fChildren.size() > 0;
696 if (fInStruct) {
697 fIndent += 4;
698 fStructDef = root->find(child.fName);
699 if (nullptr == structDef) {
700 fStructDef = root->find(root->fName + "::" + child.fName);
701 }
702 this->structSizeMembers(child);
703 fIndent -= 4;
704 }
705 case KeyWord::kClass:
706 if (child.fChildren.size() > 0) {
707 const char* bodyEnd = fDeferComment ? fDeferComment->fContentStart - 1 :
708 child.fContentStart;
709 this->writeBlock((int) (bodyEnd - fStart), fStart);
710 fStart = child.fContentStart;
711 if (child.fName == root->fName) {
712 if (Definition* parent = root->fParent) {
713 if (MarkType::kTopic == parent->fMarkType ||
714 MarkType::kSubtopic == parent->fMarkType) {
715 const char* commentStart = parent->fContentStart;
Cary Clark1eace2d2017-07-31 07:52:43 -0400716 for (auto child : parent->fChildren) {
717 if (MarkType::kClass == child->fMarkType) {
718 break;
719 }
720 commentStart = child->fTerminator;
721 }
Cary Clark8032b982017-07-28 11:04:54 -0400722 const char* commentEnd = root->fStart;
723 this->structOut(root, *root, commentStart, commentEnd);
724 } else {
725 SkASSERT(0); // incomplete
726 }
727 } else {
728 SkASSERT(0); // incomplete
729 }
730 } else {
731 structDef = root->find(child.fName);
732 if (nullptr == structDef) {
733 structDef = root->find(root->fName + "::" + child.fName);
734 }
735 Definition* codeBlock = nullptr;
736 Definition* nextBlock = nullptr;
737 for (auto test : structDef->fChildren) {
738 if (MarkType::kCode == test->fMarkType) {
739 SkASSERT(!codeBlock); // FIXME: check enum for correct order earlier
740 codeBlock = test;
741 continue;
742 }
743 if (codeBlock) {
744 nextBlock = test;
745 break;
746 }
747 }
748 SkASSERT(nextBlock); // FIXME: check enum for correct order earlier
749 const char* commentStart = codeBlock->fTerminator;
750 const char* commentEnd = nextBlock->fStart;
751 this->structOut(root, *structDef, commentStart, commentEnd);
752 }
753 fDeferComment = nullptr;
754 } else {
755 ; // empty forward reference, nothing to do here
756 }
757 break;
758 case KeyWord::kEnum: {
759 this->fInEnum = true;
760 this->enumHeaderOut(root, child);
761 this->enumSizeItems(child);
762 } break;
763 case KeyWord::kConst:
764 case KeyWord::kConstExpr:
765 case KeyWord::kStatic:
766 case KeyWord::kInt:
767 case KeyWord::kUint32_t:
768 case KeyWord::kSize_t:
769 case KeyWord::kFloat:
770 case KeyWord::kBool:
771 case KeyWord::kVoid:
772 if (!memberStart) {
773 memberStart = &child;
774 }
775 break;
776 case KeyWord::kPublic:
777 case KeyWord::kPrivate:
778 case KeyWord::kProtected:
779 case KeyWord::kFriend:
780 break;
781 default:
782 SkASSERT(0);
783 }
784 if (structDef) {
785 TextParser structName(&child);
786 SkAssertResult(structName.skipToEndBracket('{'));
787 fStart = structName.fChar + 1;
788 this->writeBlock((int) (fStart - child.fStart), child.fStart);
789 this->lf(2);
790 fIndent += 4;
791 if (!this->populate(&child, const_cast<Definition*>(structDef)->asRoot())) {
792 return false;
793 }
794 // output any remaining definitions at current indent level
795 const char* structEnd = child.fContentEnd;
796 SkAssertResult('}' == structEnd[-1]);
797 --structEnd;
798 this->writeBlock((int) (structEnd - fStart), fStart);
799 this->lf(2);
800 fStart = structEnd;
801 fIndent -= 4;
802 fContinuation = nullptr;
803 fDeferComment = nullptr;
804 } else {
805 if (!this->populate(&child, root)) {
806 return false;
807 }
808 }
809 continue;
810 }
811 if (Definition::Type::kBracket == child.fType) {
812 if (KeyWord::kEnum == child.fParent->fKeyWord) {
813 this->enumMembersOut(root, child);
814 this->writeString("};");
815 this->lf(2);
816 fStart = child.fParent->fContentEnd;
817 SkASSERT(';' == fStart[0]);
818 ++fStart;
819 fDeferComment = nullptr;
820 fInEnum = false;
821 continue;
822 }
823 fDeferComment = nullptr;
824 if (!this->populate(&child, root)) {
825 return false;
826 }
827 continue;
828 }
829 if (Definition::Type::kWord == child.fType) {
830 if (MarkType::kMember == child.fMarkType) {
831 this->structMemberOut(memberStart, child);
832 fStart = child.fContentEnd + 1;
833 fDeferComment = nullptr;
834 }
835 if (child.fMemberStart) {
836 memberStart = &child;
837 }
838 continue;
839 }
840 if (Definition::Type::kPunctuation == child.fType) {
841 if (Punctuation::kSemicolon == child.fPunctuation) {
842 memberStart = nullptr;
843 if (inStruct) {
844 fInStruct = false;
845 }
846 continue;
847 }
848 if (Punctuation::kLeftBrace == child.fPunctuation ||
849 Punctuation::kColon == child.fPunctuation ||
850 Punctuation::kAsterisk == child.fPunctuation
851 ) {
852 continue;
853 }
854 }
855 }
856 return true;
857}
858
859bool IncludeWriter::populate(BmhParser& bmhParser) {
860 bool allPassed = true;
861 for (auto& includeMapper : fIncludeMap) {
862 size_t lastSlash = includeMapper.first.rfind('/');
863 if (string::npos == lastSlash || lastSlash >= includeMapper.first.length() - 1) {
864 return this->reportError<bool>("malformed include name");
865 }
866 string fileName = includeMapper.first.substr(lastSlash + 1);
867 if (".h" != fileName.substr(fileName.length() - 2)) {
868 return this->reportError<bool>("expected fileName.h");
869 }
870 string skClassName = fileName.substr(0, fileName.length() - 2);
871 fOut = fopen(fileName.c_str(), "wb");
872 if (!fOut) {
873 SkDebugf("could not open output file %s\n", fileName.c_str());
874 return false;
875 }
876 if (bmhParser.fClassMap.end() == bmhParser.fClassMap.find(skClassName)) {
877 return this->reportError<bool>("could not find bmh class");
878 }
879 fBmhParser = &bmhParser;
880 RootDefinition* root = &bmhParser.fClassMap[skClassName];
881 fRootTopic = root->fParent;
882 root->clearVisited();
883 fStart = includeMapper.second.fContentStart;
884 fEnd = includeMapper.second.fContentEnd;
885 allPassed &= this->populate(&includeMapper.second, root);
886 this->writeBlock((int) (fEnd - fStart), fStart);
887 fIndent = 0;
888 this->lfcr();
889 this->writePending();
890 fclose(fOut);
891 }
892 return allPassed;
893}
894
895// change Xxx_Xxx to xxx xxx
896static string ConvertRef(const string str, bool first) {
897 string substitute;
898 for (char c : str) {
899 if ('_' == c) {
900 c = ' '; // change Xxx_Xxx to xxx xxx
901 } else if (isupper(c) && !first) {
902 c = tolower(c);
903 }
904 substitute += c;
905 first = false;
906 }
907 return substitute;
908}
909
Cary Clark8032b982017-07-28 11:04:54 -0400910string IncludeWriter::resolveMethod(const char* start, const char* end, bool first) {
911 string methodname(start, end - start);
Cary Clark579985c2017-07-31 11:48:27 -0400912 if (string::npos != methodname.find("()")) {
913 return "";
914 }
Cary Clark8032b982017-07-28 11:04:54 -0400915 string substitute;
916 auto rootDefIter = fBmhParser->fMethodMap.find(methodname);
917 if (fBmhParser->fMethodMap.end() != rootDefIter) {
918 substitute = methodname + "()";
919 } else {
Cary Clark1eace2d2017-07-31 07:52:43 -0400920 RootDefinition* parent = nullptr;
921 for (auto candidate : fRootTopic->fChildren) {
922 if (MarkType::kClass == candidate->fMarkType
923 || MarkType::kStruct == candidate->fMarkType) {
924 parent = candidate->asRoot();
925 break;
926 }
927 }
928 SkASSERT(parent);
Cary Clark8032b982017-07-28 11:04:54 -0400929 auto defRef = parent->find(parent->fName + "::" + methodname);
930 if (defRef && MarkType::kMethod == defRef->fMarkType) {
931 substitute = methodname + "()";
932 }
933 }
Cary Clark579985c2017-07-31 11:48:27 -0400934 if (fMethodDef && methodname == fMethodDef->fName) {
935 TextParser report(fBmhMethod);
936 report.reportError("method should not include references to itself");
937 return "";
938 }
Cary Clark8032b982017-07-28 11:04:54 -0400939 return substitute;
940}
941
942string IncludeWriter::resolveRef(const char* start, const char* end, bool first) {
943 // look up Xxx_Xxx
944 string undername(start, end - start);
945 SkASSERT(string::npos == undername.find(' '));
946 const Definition* rootDef = nullptr;
947 {
948 auto rootDefIter = fBmhParser->fTopicMap.find(undername);
949 if (fBmhParser->fTopicMap.end() != rootDefIter) {
950 rootDef = rootDefIter->second;
951 } else {
952 string prefixedName = fRootTopic->fName + '_' + undername;
953 rootDefIter = fBmhParser->fTopicMap.find(prefixedName);
954 if (fBmhParser->fTopicMap.end() != rootDefIter) {
955 rootDef = rootDefIter->second;
956 } else {
957 auto aliasIter = fBmhParser->fAliasMap.find(undername);
958 if (fBmhParser->fAliasMap.end() != aliasIter) {
959 rootDef = aliasIter->second->fParent;
960 } else if (!first) {
961 for (const auto& external : fBmhParser->fExternals) {
962 if (external.fName == undername) {
963 return external.fName;
964 }
965 }
966 SkDebugf("unfound: %s\n", undername.c_str());
Cary Clark579985c2017-07-31 11:48:27 -0400967 this->reportError("reference unfound");
968 return "";
Cary Clark8032b982017-07-28 11:04:54 -0400969 }
970 }
971 }
972 }
973 string substitute;
974 if (rootDef) {
975 for (auto child : rootDef->fChildren) {
976 if (MarkType::kSubstitute == child->fMarkType) {
977 substitute = string(child->fContentStart,
978 (int) (child->fContentEnd - child->fContentStart));
979 break;
980 }
981 if (MarkType::kClass == child->fMarkType ||
982 MarkType::kStruct == child->fMarkType ||
983 MarkType::kEnum == child->fMarkType ||
984 MarkType::kEnumClass == child->fMarkType) {
985 substitute = child->fName;
986 if (MarkType::kEnum == child->fMarkType && fInEnum) {
987 size_t parentClassEnd = substitute.find("::");
988 SkASSERT(string::npos != parentClassEnd);
989 substitute = substitute.substr(parentClassEnd + 2);
990 }
991 break;
992 }
993 }
994 if (!substitute.length()) {
995 auto parent = rootDef->fParent;
996 if (parent) {
997 if (MarkType::kClass == parent->fMarkType ||
998 MarkType::kStruct == parent->fMarkType ||
999 MarkType::kEnum == parent->fMarkType ||
1000 MarkType::kEnumClass == parent->fMarkType) {
1001 if (parent->fParent != fRootTopic) {
1002 substitute = parent->fName;
1003 size_t under = undername.find('_');
1004 SkASSERT(string::npos != under);
1005 string secondHalf(&undername[under], (size_t) (undername.length() - under));
1006 substitute += ConvertRef(secondHalf, false);
1007 } else {
1008 substitute += ConvertRef(undername, first);
1009 }
1010 }
1011 }
1012 }
1013 }
Cary Clark1eace2d2017-07-31 07:52:43 -04001014 // Ensure first word after period is capitalized if substitute is lower cased.
1015 if (first && isupper(start[0]) && substitute.length() > 0 && islower(substitute[0])) {
1016 substitute[0] = start[0];
1017 }
Cary Clark8032b982017-07-28 11:04:54 -04001018 return substitute;
1019}
Cary Clark579985c2017-07-31 11:48:27 -04001020
Cary Clark8032b982017-07-28 11:04:54 -04001021int IncludeWriter::lookupMethod(const PunctuationState punctuation, const Word word,
Cary Clark579985c2017-07-31 11:48:27 -04001022 const int lastSpace, const int run, int lastWrite, const char* data) {
1023 int wordStart = lastSpace;
1024 while (' ' >= data[wordStart]) {
1025 ++wordStart;
1026 }
1027 const int wordEnd = PunctuationState::kDelimiter == punctuation ||
Cary Clark8032b982017-07-28 11:04:54 -04001028 PunctuationState::kPeriod == punctuation ? run - 1 : run;
Cary Clark579985c2017-07-31 11:48:27 -04001029 string temp = this->resolveMethod(&data[wordStart], &data[wordEnd], Word::kFirst == word);
Cary Clark8032b982017-07-28 11:04:54 -04001030 if (temp.length()) {
Cary Clark579985c2017-07-31 11:48:27 -04001031 if (wordStart > lastWrite) {
1032 SkASSERT(data[wordStart - 1] >= ' ');
Cary Clark8032b982017-07-28 11:04:54 -04001033 if (' ' == data[lastWrite]) {
1034 this->writeSpace();
1035 }
Cary Clark579985c2017-07-31 11:48:27 -04001036 this->writeBlockTrim(wordStart - lastWrite, &data[lastWrite]);
1037 if (' ' == data[wordStart - 1]) {
Cary Clark8032b982017-07-28 11:04:54 -04001038 this->writeSpace();
1039 }
1040 }
1041 SkASSERT(temp[temp.length() - 1] > ' ');
1042 this->writeString(temp.c_str());
Cary Clark579985c2017-07-31 11:48:27 -04001043 lastWrite = wordEnd;
Cary Clark8032b982017-07-28 11:04:54 -04001044 }
1045 return lastWrite;
1046}
1047
1048int IncludeWriter::lookupReference(const PunctuationState punctuation, const Word word,
1049 const int start, const int run, int lastWrite, const char last, const char* data) {
1050 const int end = PunctuationState::kDelimiter == punctuation ||
1051 PunctuationState::kPeriod == punctuation ? run - 1 : run;
1052 string temp = this->resolveRef(&data[start], &data[end], Word::kFirst == word);
1053 if (!temp.length()) {
1054 if (Word::kFirst != word && '_' != last) {
1055 temp = string(&data[start], (size_t) (end - start));
1056 temp = ConvertRef(temp, false);
1057 }
1058 }
1059 if (temp.length()) {
1060 if (start > lastWrite) {
1061 SkASSERT(data[start - 1] >= ' ');
1062 if (' ' == data[lastWrite]) {
1063 this->writeSpace();
1064 }
1065 this->writeBlockTrim(start - lastWrite, &data[lastWrite]);
1066 if (' ' == data[start - 1]) {
1067 this->writeSpace();
1068 }
1069 }
1070 SkASSERT(temp[temp.length() - 1] > ' ');
1071 this->writeString(temp.c_str());
1072 lastWrite = end;
1073 }
1074 return lastWrite;
1075}
1076
1077/* returns true if rewriteBlock wrote linefeeds */
1078IncludeWriter::Wrote IncludeWriter::rewriteBlock(int size, const char* data) {
1079 bool wroteLineFeeds = false;
1080 while (size > 0 && data[0] <= ' ') {
1081 --size;
1082 ++data;
1083 }
1084 while (size > 0 && data[size - 1] <= ' ') {
1085 --size;
1086 }
1087 if (0 == size) {
1088 return Wrote::kNone;
1089 }
1090 int run = 0;
1091 Word word = Word::kStart;
1092 PunctuationState punctuation = PunctuationState::kStart;
1093 int start = 0;
1094 int lastWrite = 0;
1095 int lineFeeds = 0;
1096 int lastPrintable = 0;
Cary Clark579985c2017-07-31 11:48:27 -04001097 int lastSpace = -1;
Cary Clark8032b982017-07-28 11:04:54 -04001098 char c = 0;
1099 char last;
Cary Clark579985c2017-07-31 11:48:27 -04001100 bool embeddedSymbol = false;
Cary Clark8032b982017-07-28 11:04:54 -04001101 bool hasLower = false;
1102 bool hasUpper = false;
1103 bool hasSymbol = false;
1104 while (run < size) {
1105 last = c;
1106 c = data[run];
1107 SkASSERT(' ' <= c || '\n' == c);
1108 if (lineFeeds && ' ' < c) {
1109 if (lastPrintable >= lastWrite) {
1110 if (' ' == data[lastWrite]) {
1111 this->writeSpace();
1112 }
1113 this->writeBlock(lastPrintable - lastWrite + 1, &data[lastWrite]);
1114 }
1115 if (lineFeeds > 1) {
1116 this->lf(2);
1117 }
1118 this->lfcr(); // defer the indent until non-whitespace is seen
1119 lastWrite = run;
1120 lineFeeds = 0;
1121 }
1122 if (' ' < c) {
1123 lastPrintable = run;
1124 }
1125 switch (c) {
1126 case '\n':
1127 ++lineFeeds;
1128 wroteLineFeeds = true;
1129 case ' ':
1130 switch (word) {
1131 case Word::kStart:
1132 break;
1133 case Word::kUnderline:
1134 case Word::kCap:
1135 case Word::kFirst:
1136 if (!hasLower) {
1137 break;
1138 }
1139 lastWrite = this->lookupReference(punctuation, word, start, run,
1140 lastWrite, last, data);
1141 break;
1142 case Word::kMixed:
Cary Clark579985c2017-07-31 11:48:27 -04001143 if (hasUpper && hasLower && !hasSymbol && lastSpace > 0) {
1144 lastWrite = this->lookupMethod(punctuation, word, lastSpace, run,
1145 lastWrite, data);
Cary Clark8032b982017-07-28 11:04:54 -04001146 }
1147 break;
1148 default:
1149 SkASSERT(0);
1150 }
Cary Clark1eace2d2017-07-31 07:52:43 -04001151 punctuation = PunctuationState::kPeriod == punctuation ||
1152 (PunctuationState::kStart == punctuation && ' ' >= last) ?
1153 PunctuationState::kStart : PunctuationState::kSpace;
Cary Clark8032b982017-07-28 11:04:54 -04001154 word = Word::kStart;
Cary Clark579985c2017-07-31 11:48:27 -04001155 embeddedSymbol = false;
Cary Clark8032b982017-07-28 11:04:54 -04001156 hasLower = false;
1157 hasUpper = false;
1158 hasSymbol = false;
Cary Clark579985c2017-07-31 11:48:27 -04001159 lastSpace = run;
Cary Clark8032b982017-07-28 11:04:54 -04001160 break;
1161 case '.':
1162 switch (word) {
1163 case Word::kStart:
1164 punctuation = PunctuationState::kDelimiter;
1165 case Word::kCap:
1166 case Word::kFirst:
1167 case Word::kUnderline:
1168 case Word::kMixed:
1169 if (PunctuationState::kDelimiter == punctuation ||
1170 PunctuationState::kPeriod == punctuation) {
1171 word = Word::kMixed;
1172 }
1173 punctuation = PunctuationState::kPeriod;
1174 break;
1175 default:
1176 SkASSERT(0);
1177 }
Cary Clark579985c2017-07-31 11:48:27 -04001178 embeddedSymbol = true;
Cary Clark8032b982017-07-28 11:04:54 -04001179 break;
1180 case ',': case ';': case ':':
Cary Clark8032b982017-07-28 11:04:54 -04001181 switch (word) {
1182 case Word::kStart:
1183 punctuation = PunctuationState::kDelimiter;
1184 case Word::kCap:
1185 case Word::kFirst:
1186 case Word::kUnderline:
1187 case Word::kMixed:
1188 if (PunctuationState::kDelimiter == punctuation ||
1189 PunctuationState::kPeriod == punctuation) {
1190 word = Word::kMixed;
1191 }
1192 punctuation = PunctuationState::kDelimiter;
1193 break;
1194 default:
1195 SkASSERT(0);
1196 }
Cary Clark579985c2017-07-31 11:48:27 -04001197 embeddedSymbol = true;
Cary Clark8032b982017-07-28 11:04:54 -04001198 break;
1199 case '\'': // possessive apostrophe isn't treated as delimiting punctation
1200 case '=':
1201 case '!': // assumed not to be punctuation, but a programming symbol
1202 case '&': case '>': case '<': case '{': case '}': case '/': case '*':
1203 word = Word::kMixed;
Cary Clark579985c2017-07-31 11:48:27 -04001204 embeddedSymbol = true;
Cary Clark8032b982017-07-28 11:04:54 -04001205 break;
1206 case '(':
1207 if (' ' == last) {
1208 punctuation = PunctuationState::kDelimiter;
1209 } else {
1210 word = Word::kMixed;
1211 }
Cary Clark579985c2017-07-31 11:48:27 -04001212 embeddedSymbol = true;
Cary Clark8032b982017-07-28 11:04:54 -04001213 break;
1214 case ')': // assume word type has already been set
1215 punctuation = PunctuationState::kDelimiter;
Cary Clark579985c2017-07-31 11:48:27 -04001216 embeddedSymbol = true;
Cary Clark8032b982017-07-28 11:04:54 -04001217 break;
1218 case '_':
1219 switch (word) {
1220 case Word::kStart:
1221 word = Word::kMixed;
1222 break;
1223 case Word::kCap:
1224 case Word::kFirst:
1225 case Word::kUnderline:
1226 word = Word::kUnderline;
1227 break;
1228 case Word::kMixed:
1229 break;
1230 default:
1231 SkASSERT(0);
1232 }
Cary Clark579985c2017-07-31 11:48:27 -04001233 hasSymbol |= embeddedSymbol;
Cary Clark8032b982017-07-28 11:04:54 -04001234 break;
1235 case 'A': case 'B': case 'C': case 'D': case 'E':
1236 case 'F': case 'G': case 'H': case 'I': case 'J':
1237 case 'K': case 'L': case 'M': case 'N': case 'O':
1238 case 'P': case 'Q': case 'R': case 'S': case 'T':
1239 case 'U': case 'V': case 'W': case 'X': case 'Y':
1240 case 'Z':
1241 switch (word) {
1242 case Word::kStart:
1243 word = PunctuationState::kStart == punctuation ? Word::kFirst : Word::kCap;
1244 start = run;
1245 break;
1246 case Word::kCap:
1247 case Word::kFirst:
1248 if (!isupper(last)) {
1249 word = Word::kMixed;
1250 }
1251 break;
1252 case Word::kUnderline:
1253 // some word in Xxx_XXX_Xxx can be all upper, but all can't: XXX_XXX
1254 if ('_' != last && !isupper(last)) {
1255 word = Word::kMixed;
1256 }
1257 break;
1258 case Word::kMixed:
1259 break;
1260 default:
1261 SkASSERT(0);
1262 }
1263 hasUpper = true;
1264 if (PunctuationState::kPeriod == punctuation ||
1265 PunctuationState::kDelimiter == punctuation) {
1266 word = Word::kMixed;
Cary Clark8032b982017-07-28 11:04:54 -04001267 }
Cary Clark579985c2017-07-31 11:48:27 -04001268 hasSymbol |= embeddedSymbol;
Cary Clark8032b982017-07-28 11:04:54 -04001269 break;
1270 case 'a': case 'b': case 'c': case 'd': case 'e':
1271 case 'f': case 'g': case 'h': case 'i': case 'j':
1272 case 'k': case 'l': case 'm': case 'n': case 'o':
1273 case 'p': case 'q': case 'r': case 's': case 't':
1274 case 'u': case 'v': case 'w': case 'x': case 'y':
1275 case 'z':
1276 case '0': case '1': case '2': case '3': case '4':
1277 case '5': case '6': case '7': case '8': case '9':
1278 case '-':
1279 switch (word) {
1280 case Word::kStart:
1281 word = Word::kMixed;
1282 break;
1283 case Word::kMixed:
1284 case Word::kCap:
1285 case Word::kFirst:
1286 case Word::kUnderline:
1287 break;
1288 default:
1289 SkASSERT(0);
1290 }
1291 hasLower = true;
1292 punctuation = PunctuationState::kStart;
Cary Clark579985c2017-07-31 11:48:27 -04001293 hasSymbol |= embeddedSymbol;
Cary Clark8032b982017-07-28 11:04:54 -04001294 break;
1295 default:
1296 SkASSERT(0);
1297 }
1298 ++run;
1299 }
1300 if ((word == Word::kCap || word == Word::kFirst || word == Word::kUnderline) && hasLower) {
1301 lastWrite = this->lookupReference(punctuation, word, start, run, lastWrite, last, data);
Cary Clark579985c2017-07-31 11:48:27 -04001302 } else if (word == Word::kMixed && hasUpper && hasLower && !hasSymbol && lastSpace > 0) {
1303 lastWrite = this->lookupMethod(punctuation, word, lastSpace, run, lastWrite, data);
Cary Clark8032b982017-07-28 11:04:54 -04001304 }
1305 if (run > lastWrite) {
1306 if (' ' == data[lastWrite]) {
1307 this->writeSpace();
1308 }
1309 this->writeBlock(run - lastWrite, &data[lastWrite]);
1310 }
1311 return wroteLineFeeds ? Wrote::kLF : Wrote::kChars;
1312}