blob: 78b0364b066c8f25625cf42fee36d0ec5720f51f [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) {
Cary Clarkbad5ad72017-08-03 17:14:08 -040022 TextParser enumClassCheck(&nameDef);
23 const char* start = enumClassCheck.fStart;
24 size_t len = (size_t) (enumClassCheck.fEnd - start);
25 bool enumClass = enumClassCheck.skipExact("class ");
26 if (enumClass) {
27 start = enumClassCheck.fChar;
28 const char* end = enumClassCheck.anyOf(" \n;{");
29 len = (size_t) (end - start);
30 }
31 string enumName(start, len);
32 if (enumClass) {
33 child.fChildren[0]->fName = enumName;
34 }
Cary Clark8032b982017-07-28 11:04:54 -040035 fullName = root->fName + "::" + enumName;
Cary Clarkce101242017-09-01 15:51:02 -040036 enumDef = root->find(enumName, RootDefinition::AllowParens::kNo);
Cary Clark8032b982017-07-28 11:04:54 -040037 if (!enumDef) {
Cary Clarkce101242017-09-01 15:51:02 -040038 enumDef = root->find(fullName, RootDefinition::AllowParens::kNo);
Cary Clark8032b982017-07-28 11:04:54 -040039 }
40 SkASSERT(enumDef);
41 // child[0] should be #Code comment starts at child[0].fTerminator
42 // though skip until #Code is found (in case there's a #ToDo, etc)
43 // child[1] should be #Const comment ends at child[1].fStart
44 // comment becomes enum header (if any)
45 } else {
46 string enumName(root->fName);
47 enumName += "::_anonymous";
48 if (fAnonymousEnumCount > 1) {
49 enumName += '_' + to_string(fAnonymousEnumCount);
50 }
Cary Clarkce101242017-09-01 15:51:02 -040051 enumDef = root->find(enumName, RootDefinition::AllowParens::kNo);
Cary Clark8032b982017-07-28 11:04:54 -040052 SkASSERT(enumDef);
53 ++fAnonymousEnumCount;
54 }
55 Definition* codeBlock = nullptr;
56 const char* commentStart = nullptr;
57 bool wroteHeader = false;
58 SkDEBUGCODE(bool foundConst = false);
59 for (auto test : enumDef->fChildren) {
60 if (MarkType::kCode == test->fMarkType) {
61 SkASSERT(!codeBlock); // FIXME: check enum for correct order earlier
62 codeBlock = test;
63 commentStart = codeBlock->fTerminator;
64 continue;
65 }
66 if (!codeBlock) {
67 continue;
68 }
69 const char* commentEnd = test->fStart;
70 if (!wroteHeader &&
71 !this->contentFree((int) (commentEnd - commentStart), commentStart)) {
72 this->writeCommentHeader();
73 this->writeString("\\enum");
Cary Clarkbad5ad72017-08-03 17:14:08 -040074 if (fullName.length() > 0) {
75 this->writeSpace();
76 this->writeString(fullName.c_str());
77 }
Cary Clark8032b982017-07-28 11:04:54 -040078 fIndent += 4;
79 this->lfcr();
80 wroteHeader = true;
81 }
82 this->rewriteBlock((int) (commentEnd - commentStart), commentStart);
83 if (MarkType::kAnchor == test->fMarkType) {
84 commentStart = test->fContentStart;
85 commentEnd = test->fChildren[0]->fStart;
86 this->writeSpace();
87 this->rewriteBlock((int) (commentEnd - commentStart), commentStart);
88 this->writeSpace();
89 }
90 commentStart = test->fTerminator;
91 if (MarkType::kConst == test->fMarkType) {
92 SkASSERT(codeBlock); // FIXME: check enum for correct order earlier
93 SkDEBUGCODE(foundConst = true);
94 break;
95 }
96 }
97 SkASSERT(codeBlock);
98 SkASSERT(foundConst);
99 if (wroteHeader) {
100 fIndent -= 4;
101 this->lfcr();
102 this->writeCommentTrailer();
103 }
Cary Clarkbad5ad72017-08-03 17:14:08 -0400104 Definition* braceHolder = child.fChildren[0];
105 if (KeyWord::kClass == braceHolder->fKeyWord) {
106 braceHolder = braceHolder->fChildren[0];
107 }
108 bodyEnd = braceHolder->fContentStart;
Cary Clark8032b982017-07-28 11:04:54 -0400109 SkASSERT('{' == bodyEnd[0]);
110 ++bodyEnd;
111 this->lfcr();
112 this->writeBlock((int) (bodyEnd - fStart), fStart); // write include "enum Name {"
113 fIndent += 4;
114 this->singleLF();
115 fStart = bodyEnd;
116 fEnumDef = enumDef;
117}
118
Cary Clarkbad5ad72017-08-03 17:14:08 -0400119void IncludeWriter::enumMembersOut(const RootDefinition* root, Definition& child) {
Cary Clark8032b982017-07-28 11:04:54 -0400120 // iterate through include tokens and find how much remains for 1 line comments
121 // put ones that fit on same line, ones that are too big on preceding line?
122 const Definition* currentEnumItem = nullptr;
123 const char* commentStart = nullptr;
124 const char* lastEnd = nullptr;
125 int commentLen = 0;
126 enum class State {
127 kNoItem,
128 kItemName,
129 kItemValue,
130 kItemComment,
131 };
132 State state = State::kNoItem;
Cary Clarkbad5ad72017-08-03 17:14:08 -0400133 vector<IterState> iterStack;
134 iterStack.emplace_back(child.fTokens.begin(), child.fTokens.end());
135 IterState* iterState = &iterStack[0];
136 bool preprocessorWord = false;
137 const char* preprocessStart = nullptr;
138 const char* preprocessEnd = nullptr;
139 for (int onePast = 0; onePast < 2; onePast += iterState->fDefIter == iterState->fDefEnd) {
140 Definition* token = onePast ? nullptr : &*iterState->fDefIter++;
Cary Clark8032b982017-07-28 11:04:54 -0400141 if (token && Definition::Type::kBracket == token->fType) {
142 if (Bracket::kSlashSlash == token->fBracket) {
143 fStart = token->fContentEnd;
144 continue; // ignore old inline comments
145 }
146 if (Bracket::kSlashStar == token->fBracket) {
147 fStart = token->fContentEnd + 1;
148 continue; // ignore old inline comments
149 }
Cary Clarkbad5ad72017-08-03 17:14:08 -0400150 if (Bracket::kPound == token->fBracket) { // preprocessor wraps member
151 preprocessStart = token->fContentStart;
152 if (KeyWord::kIf == token->fKeyWord || KeyWord::kIfdef == token->fKeyWord) {
153 iterStack.emplace_back(token->fTokens.begin(), token->fTokens.end());
154 iterState = &iterStack.back();
155 preprocessorWord = true;
156 } else if (KeyWord::kEndif == token->fKeyWord) {
157 iterStack.pop_back();
158 iterState = &iterStack.back();
159 preprocessEnd = token->fContentEnd;
160 } else {
161 SkASSERT(0); // incomplete
162 }
163 continue;
164 }
Cary Clark8032b982017-07-28 11:04:54 -0400165 SkASSERT(0); // incomplete
166 }
167 if (token && Definition::Type::kWord != token->fType) {
168 SkASSERT(0); // incomplete
169 }
Cary Clarkbad5ad72017-08-03 17:14:08 -0400170 if (preprocessorWord) {
171 preprocessorWord = false;
172 preprocessEnd = token->fContentEnd;
173 continue;
174 }
Cary Clark8032b982017-07-28 11:04:54 -0400175 if (token && State::kItemName == state) {
176 TextParser enumLine(token->fFileName, lastEnd,
177 token->fContentStart, token->fLineCount);
178 const char* end = enumLine.anyOf(",}=");
179 SkASSERT(end);
180 state = '=' == *end ? State::kItemValue : State::kItemComment;
181 if (State::kItemValue == state) { // write enum value
182 this->indentToColumn(fEnumItemValueTab);
183 this->writeString("=");
184 this->writeSpace();
185 lastEnd = token->fContentEnd;
186 this->writeBlock((int) (lastEnd - token->fContentStart),
187 token->fContentStart); // write const value if any
188 continue;
189 }
190 }
191 if (token && State::kItemValue == state) {
192 TextParser valueEnd(token->fFileName, lastEnd,
193 token->fContentStart, token->fLineCount);
194 const char* end = valueEnd.anyOf(",}");
195 if (!end) { // write expression continuation
196 if (' ' == lastEnd[0]) {
197 this->writeSpace();
198 }
199 this->writeBlock((int) (token->fContentEnd - lastEnd), lastEnd);
200 continue;
201 }
202 }
203 if (State::kNoItem != state) {
204 this->writeString(",");
205 SkASSERT(currentEnumItem);
206 if (currentEnumItem->fShort) {
207 this->indentToColumn(fEnumItemCommentTab);
208 this->writeString("//!<");
209 this->writeSpace();
210 this->rewriteBlock(commentLen, commentStart);
211 }
212 if (onePast) {
213 fIndent -= 4;
214 }
215 this->lfcr();
Cary Clarkbad5ad72017-08-03 17:14:08 -0400216 if (preprocessStart) {
217 SkASSERT(preprocessEnd);
218 int saveIndent = fIndent;
219 fIndent = SkTMax(0, fIndent - 8);
220 this->lf(2);
221 this->writeBlock((int) (preprocessEnd - preprocessStart), preprocessStart);
222 this->lfcr();
223 fIndent = saveIndent;
224 preprocessStart = nullptr;
225 preprocessEnd = nullptr;
226 }
Cary Clark8032b982017-07-28 11:04:54 -0400227 if (token && State::kItemValue == state) {
228 fStart = token->fContentStart;
229 }
230 state = State::kNoItem;
231 }
232 SkASSERT(State::kNoItem == state);
233 if (onePast) {
234 break;
235 }
236 SkASSERT(token);
Cary Clarkbad5ad72017-08-03 17:14:08 -0400237 string itemName = root->fName + "::";
238 if (KeyWord::kClass == child.fParent->fKeyWord) {
239 itemName += child.fParent->fName + "::";
240 }
241 itemName += string(token->fContentStart, (int) (token->fContentEnd - token->fContentStart));
Cary Clark8032b982017-07-28 11:04:54 -0400242 for (auto& enumItem : fEnumDef->fChildren) {
243 if (MarkType::kConst != enumItem->fMarkType) {
244 continue;
245 }
246 if (itemName != enumItem->fName) {
247 continue;
248 }
249 currentEnumItem = enumItem;
250 break;
251 }
252 SkASSERT(currentEnumItem);
253 // if description fits, it goes after item
254 commentStart = currentEnumItem->fContentStart;
255 const char* commentEnd;
256 if (currentEnumItem->fChildren.size() > 0) {
257 commentEnd = currentEnumItem->fChildren[0]->fStart;
258 } else {
259 commentEnd = currentEnumItem->fContentEnd;
260 }
261 TextParser enumComment(fFileName, commentStart, commentEnd, currentEnumItem->fLineCount);
262 if (enumComment.skipToLineStart()) { // skip const value
263 commentStart = enumComment.fChar;
264 commentLen = (int) (commentEnd - commentStart);
265 } else {
266 const Definition* privateDef = currentEnumItem->fChildren[0];
267 SkASSERT(MarkType::kPrivate == privateDef->fMarkType);
268 commentStart = privateDef->fContentStart;
269 commentLen = (int) (privateDef->fContentEnd - privateDef->fContentStart);
270 }
Cary Clark73fa9722017-08-29 17:36:51 -0400271 // FIXME: may assert here if there's no const value
272 // should have detected and errored on that earlier when enum fContentStart was set
Cary Clark8032b982017-07-28 11:04:54 -0400273 SkASSERT(commentLen > 0 && commentLen < 1000);
274 if (!currentEnumItem->fShort) {
275 this->writeCommentHeader();
276 fIndent += 4;
277 bool wroteLineFeed = Wrote::kLF == this->rewriteBlock(commentLen, commentStart);
278 fIndent -= 4;
279 if (wroteLineFeed || fColumn > 100 - 3 /* space * / */ ) {
280 this->lfcr();
281 } else {
282 this->writeSpace();
283 }
284 this->writeCommentTrailer();
285 }
286 lastEnd = token->fContentEnd;
287 this->lfcr();
288 if (',' == fStart[0]) {
289 ++fStart;
290 }
291 this->writeBlock((int) (lastEnd - fStart), fStart); // enum item name
292 fStart = token->fContentEnd;
293 state = State::kItemName;
294 }
295}
296
297void IncludeWriter::enumSizeItems(const Definition& child) {
298 enum class State {
299 kNoItem,
300 kItemName,
301 kItemValue,
302 kItemComment,
303 };
304 State state = State::kNoItem;
305 int longestName = 0;
306 int longestValue = 0;
307 int valueLen = 0;
308 const char* lastEnd = nullptr;
309 SkASSERT(child.fChildren.size() == 1 || child.fChildren.size() == 2);
310 auto brace = child.fChildren[0];
Cary Clarkbad5ad72017-08-03 17:14:08 -0400311 if (KeyWord::kClass == brace->fKeyWord) {
312 brace = brace->fChildren[0];
313 }
Cary Clark8032b982017-07-28 11:04:54 -0400314 SkASSERT(Bracket::kBrace == brace->fBracket);
Cary Clarkbad5ad72017-08-03 17:14:08 -0400315 vector<IterState> iterStack;
316 iterStack.emplace_back(brace->fTokens.begin(), brace->fTokens.end());
317 IterState* iterState = &iterStack[0];
318 bool preprocessorWord = false;
319 while (iterState->fDefIter != iterState->fDefEnd) {
320 auto& token = *iterState->fDefIter++;
Cary Clark8032b982017-07-28 11:04:54 -0400321 if (Definition::Type::kBracket == token.fType) {
322 if (Bracket::kSlashSlash == token.fBracket) {
323 continue; // ignore old inline comments
324 }
325 if (Bracket::kSlashStar == token.fBracket) {
326 continue; // ignore old inline comments
327 }
Cary Clarkbad5ad72017-08-03 17:14:08 -0400328 if (Bracket::kPound == token.fBracket) { // preprocessor wraps member
329 if (KeyWord::kIf == token.fKeyWord || KeyWord::kIfdef == token.fKeyWord) {
330 iterStack.emplace_back(token.fTokens.begin(), token.fTokens.end());
331 iterState = &iterStack.back();
332 preprocessorWord = true;
333 } else if (KeyWord::kEndif == token.fKeyWord) {
334 iterStack.pop_back();
335 iterState = &iterStack.back();
336 } else {
337 SkASSERT(0); // incomplete
338 }
339 continue;
340 }
Cary Clark8032b982017-07-28 11:04:54 -0400341 SkASSERT(0); // incomplete
342 }
343 if (Definition::Type::kWord != token.fType) {
344 SkASSERT(0); // incomplete
345 }
Cary Clarkbad5ad72017-08-03 17:14:08 -0400346 if (preprocessorWord) {
347 preprocessorWord = false;
348 continue;
349 }
Cary Clark8032b982017-07-28 11:04:54 -0400350 if (State::kItemName == state) {
351 TextParser enumLine(token.fFileName, lastEnd,
352 token.fContentStart, token.fLineCount);
353 const char* end = enumLine.anyOf(",}=");
354 SkASSERT(end);
355 state = '=' == *end ? State::kItemValue : State::kItemComment;
356 if (State::kItemValue == state) {
357 valueLen = (int) (token.fContentEnd - token.fContentStart);
358 lastEnd = token.fContentEnd;
359 continue;
360 }
361 }
362 if (State::kItemValue == state) {
363 TextParser valueEnd(token.fFileName, lastEnd,
364 token.fContentStart, token.fLineCount);
365 const char* end = valueEnd.anyOf(",}");
366 if (!end) { // write expression continuation
367 valueLen += (int) (token.fContentEnd - lastEnd);
368 continue;
369 }
370 }
371 if (State::kNoItem != state) {
372 longestValue = SkTMax(longestValue, valueLen);
373 state = State::kNoItem;
374 }
375 SkASSERT(State::kNoItem == state);
376 lastEnd = token.fContentEnd;
377 longestName = SkTMax(longestName, (int) (lastEnd - token.fContentStart));
378 state = State::kItemName;
379 }
380 if (State::kItemValue == state) {
381 longestValue = SkTMax(longestValue, valueLen);
382 }
383 fEnumItemValueTab = longestName + fIndent + 1 /* space before = */ ;
384 if (longestValue) {
385 longestValue += 3; /* = space , */
386 }
387 fEnumItemCommentTab = fEnumItemValueTab + longestValue + 1 /* space before //!< */ ;
388 // iterate through bmh children and see which comments fit on include lines
389 for (auto& enumItem : fEnumDef->fChildren) {
390 if (MarkType::kConst != enumItem->fMarkType) {
391 continue;
392 }
393 TextParser enumLine(enumItem);
394 enumLine.trimEnd();
395 enumLine.skipToLineStart(); // skip const value
396 const char* commentStart = enumLine.fChar;
397 enumLine.skipLine();
398 ptrdiff_t lineLen = enumLine.fChar - commentStart + 5 /* //!< space */ ;
399 if (!enumLine.eof()) {
400 enumLine.skipWhiteSpace();
401 }
402 enumItem->fShort = enumLine.eof() && fEnumItemCommentTab + lineLen < 100;
403 }
404}
405
406// walk children and output complete method doxygen description
Cary Clark579985c2017-07-31 11:48:27 -0400407void IncludeWriter::methodOut(const Definition* method, const Definition& child) {
408 fBmhMethod = method;
409 fMethodDef = &child;
Cary Clark8032b982017-07-28 11:04:54 -0400410 fContinuation = nullptr;
411 fDeferComment = nullptr;
412 if (0 == fIndent) {
413 fIndent = 4;
414 }
415 this->writeCommentHeader();
416 fIndent += 4;
417 const char* commentStart = method->fContentStart;
418 int commentLen = (int) (method->fContentEnd - commentStart);
419 bool breakOut = false;
420 for (auto methodProp : method->fChildren) {
421 switch (methodProp->fMarkType) {
422 case MarkType::kDefinedBy:
423 commentStart = methodProp->fTerminator;
424 break;
425 case MarkType::kDeprecated:
426 case MarkType::kPrivate:
427 commentLen = (int) (methodProp->fStart - commentStart);
428 if (commentLen > 0) {
429 SkASSERT(commentLen < 1000);
430 if (Wrote::kNone != this->rewriteBlock(commentLen, commentStart)) {
431 this->lfcr();
432 }
433 }
434 commentStart = methodProp->fContentStart;
435 commentLen = (int) (methodProp->fContentEnd - commentStart);
436 if (commentLen > 0) {
437 if (Wrote::kNone != this->rewriteBlock(commentLen, commentStart)) {
438 this->lfcr();
439 }
440 }
441 commentStart = methodProp->fTerminator;
442 commentLen = (int) (method->fContentEnd - commentStart);
Cary Clarkbad5ad72017-08-03 17:14:08 -0400443 break;
444 case MarkType::kExperimental:
445 this->writeString("EXPERIMENTAL:");
446 this->writeSpace();
447 commentStart = methodProp->fContentStart;
448 commentLen = (int) (methodProp->fContentEnd - commentStart);
449 if (commentLen > 0) {
450 if (Wrote::kNone != this->rewriteBlock(commentLen, commentStart)) {
451 this->lfcr();
452 }
453 }
454 commentStart = methodProp->fTerminator;
455 commentLen = (int) (method->fContentEnd - commentStart);
456 break;
457 case MarkType::kToDo:
458 commentLen = (int) (methodProp->fStart - commentStart);
459 if (commentLen > 0) {
460 SkASSERT(commentLen < 1000);
461 if (Wrote::kNone != this->rewriteBlock(commentLen, commentStart)) {
462 this->lfcr();
463 }
464 }
465 commentStart = methodProp->fTerminator;
466 commentLen = (int) (method->fContentEnd - commentStart);
467 break;
Cary Clark8032b982017-07-28 11:04:54 -0400468 default:
469 commentLen = (int) (methodProp->fStart - commentStart);
470 breakOut = true;
471 }
472 if (breakOut) {
473 break;
474 }
475 }
Cary Clarkbad5ad72017-08-03 17:14:08 -0400476 SkASSERT(commentLen > 0 && commentLen < 1500);
Cary Clark8032b982017-07-28 11:04:54 -0400477 this->rewriteBlock(commentLen, commentStart);
478 // compute indention column
479 size_t column = 0;
480 bool hasParmReturn = false;
481 for (auto methodPart : method->fChildren) {
482 if (MarkType::kParam == methodPart->fMarkType) {
483 column = SkTMax(column, methodPart->fName.length());
484 hasParmReturn = true;
485 } else if (MarkType::kReturn == methodPart->fMarkType) {
486 hasParmReturn = true;
487 }
488 }
489 if (hasParmReturn) {
490 this->lf(2);
491 column += fIndent + sizeof("@return ");
492 int saveIndent = fIndent;
493 for (auto methodPart : method->fChildren) {
494 const char* partStart = methodPart->fContentStart;
495 const char* partEnd = methodPart->fContentEnd;
496 if (MarkType::kParam == methodPart->fMarkType) {
497 this->writeString("@param");
498 this->writeSpace();
499 this->writeString(methodPart->fName.c_str());
500 } else if (MarkType::kReturn == methodPart->fMarkType) {
501 this->writeString("@return");
502 } else {
503 continue;
504 }
505 while ('\n' == partEnd[-1]) {
506 --partEnd;
507 }
508 while ('#' == partEnd[-1]) { // FIXME: so wrong; should not be before fContentEnd
509 --partEnd;
510 }
511 this->indentToColumn(column);
512 int partLen = (int) (partEnd - partStart);
Cary Clark73fa9722017-08-29 17:36:51 -0400513 // FIXME : detect this earlier; assert if #Return is empty
Cary Clark8032b982017-07-28 11:04:54 -0400514 SkASSERT(partLen > 0 && partLen < 200);
515 fIndent = column;
516 this->rewriteBlock(partLen, partStart);
517 fIndent = saveIndent;
518 this->lfcr();
519 }
520 } else {
521 this->lfcr();
522 }
523 fIndent -= 4;
524 this->lfcr();
525 this->writeCommentTrailer();
Cary Clark579985c2017-07-31 11:48:27 -0400526 fBmhMethod = nullptr;
527 fMethodDef = nullptr;
Cary Clark8032b982017-07-28 11:04:54 -0400528}
529
530void IncludeWriter::structOut(const Definition* root, const Definition& child,
531 const char* commentStart, const char* commentEnd) {
532 this->writeCommentHeader();
533 this->writeString("\\");
534 SkASSERT(MarkType::kClass == child.fMarkType || MarkType::kStruct == child.fMarkType);
535 this->writeString(MarkType::kClass == child.fMarkType ? "class" : "struct");
536 this->writeSpace();
537 this->writeString(child.fName.c_str());
538 fIndent += 4;
539 this->lfcr();
540 this->rewriteBlock((int) (commentEnd - commentStart), commentStart);
541 fIndent -= 4;
542 this->lfcr();
543 this->writeCommentTrailer();
544}
545
Cary Clarkbad5ad72017-08-03 17:14:08 -0400546Definition* IncludeWriter::structMemberOut(const Definition* memberStart, const Definition& child) {
547 const char* blockStart = fDeferComment ? fLastComment->fContentEnd : fStart;
548 this->writeBlockTrim((int) (memberStart->fStart - blockStart), blockStart);
Cary Clark8032b982017-07-28 11:04:54 -0400549 const char* commentStart = nullptr;
550 ptrdiff_t commentLen = 0;
551 string name(child.fContentStart, (int) (child.fContentEnd - child.fContentStart));
552 bool isShort;
Cary Clarkbad5ad72017-08-03 17:14:08 -0400553 Definition* commentBlock = nullptr;
Cary Clark8032b982017-07-28 11:04:54 -0400554 for (auto memberDef : fStructDef->fChildren) {
555 if (memberDef->fName.length() - name.length() == memberDef->fName.find(name)) {
556 commentStart = memberDef->fContentStart;
Cary Clarkbad5ad72017-08-03 17:14:08 -0400557 commentLen = memberDef->fContentEnd - commentStart;
Cary Clark8032b982017-07-28 11:04:54 -0400558 isShort = memberDef->fShort;
Cary Clarkbad5ad72017-08-03 17:14:08 -0400559 commentBlock = memberDef;
560 SkASSERT(!isShort || memberDef->fChildren.size() == 0);
Cary Clark8032b982017-07-28 11:04:54 -0400561 break;
562 }
563 }
564 if (!isShort) {
565 this->writeCommentHeader();
Cary Clarkbad5ad72017-08-03 17:14:08 -0400566 bool wroteLineFeed = false;
Cary Clark8032b982017-07-28 11:04:54 -0400567 fIndent += 4;
Cary Clarkbad5ad72017-08-03 17:14:08 -0400568 for (auto child : commentBlock->fChildren) {
569 commentLen = child->fStart - commentStart;
570 wroteLineFeed |= Wrote::kLF == this->rewriteBlock(commentLen, commentStart);
571 if (MarkType::kFormula == child->fMarkType) {
572 this->writeSpace();
573 this->writeBlock((int) (child->fContentEnd - child->fContentStart),
574 child->fContentStart);
575 }
576 commentStart = child->fTerminator;
577 }
578 commentLen = commentBlock->fContentEnd - commentStart;
579 wroteLineFeed |= Wrote::kLF == this->rewriteBlock(commentLen, commentStart);
Cary Clark8032b982017-07-28 11:04:54 -0400580 fIndent -= 4;
581 if (wroteLineFeed || fColumn > 100 - 3 /* space * / */ ) {
582 this->lfcr();
583 } else {
584 this->writeSpace();
585 }
586 this->writeCommentTrailer();
587 }
588 this->lfcr();
Cary Clarkbad5ad72017-08-03 17:14:08 -0400589 this->writeBlock((int) (child.fStart - memberStart->fContentStart),
Cary Clark8032b982017-07-28 11:04:54 -0400590 memberStart->fContentStart);
591 this->indentToColumn(fStructMemberTab);
592 this->writeString(name.c_str());
Cary Clarkbad5ad72017-08-03 17:14:08 -0400593 auto tokenIter = child.fParent->fTokens.begin();
594 std::advance(tokenIter, child.fParentIndex + 1);
595 Definition* valueStart = &*tokenIter;
596 while (Definition::Type::kPunctuation != tokenIter->fType) {
597 std::advance(tokenIter, 1);
598 SkASSERT(child.fParent->fTokens.end() != tokenIter);
599 }
600 Definition* valueEnd = &*tokenIter;
601 if (valueStart != valueEnd) {
602 this->indentToColumn(fStructValueTab);
603 this->writeString("=");
604 this->writeSpace();
605 this->writeBlock((int) (valueEnd->fStart - valueStart->fContentStart),
606 valueStart->fContentStart);
607 }
Cary Clark8032b982017-07-28 11:04:54 -0400608 this->writeString(";");
609 if (isShort) {
610 this->indentToColumn(fStructCommentTab);
611 this->writeString("//!<");
612 this->writeSpace();
613 this->rewriteBlock(commentLen, commentStart);
614 this->lfcr();
615 }
Cary Clarkbad5ad72017-08-03 17:14:08 -0400616 return valueEnd;
Cary Clark8032b982017-07-28 11:04:54 -0400617}
618
619void IncludeWriter::structSizeMembers(Definition& child) {
620 int longestType = 0;
621 Definition* typeStart = nullptr;
622 int longestName = 0;
Cary Clarkbad5ad72017-08-03 17:14:08 -0400623 int longestValue = 0;
Cary Clark8032b982017-07-28 11:04:54 -0400624 SkASSERT(child.fChildren.size() == 1 || child.fChildren.size() == 2);
625 bool inEnum = false;
Cary Clarkbad5ad72017-08-03 17:14:08 -0400626 bool inMethod = false;
627 bool inMember = false;
Cary Clark8032b982017-07-28 11:04:54 -0400628 auto brace = child.fChildren[0];
629 SkASSERT(Bracket::kBrace == brace->fBracket);
630 for (auto& token : brace->fTokens) {
631 if (Definition::Type::kBracket == token.fType) {
632 if (Bracket::kSlashSlash == token.fBracket) {
633 continue; // ignore old inline comments
634 }
635 if (Bracket::kSlashStar == token.fBracket) {
636 continue; // ignore old inline comments
637 }
638 if (Bracket::kParen == token.fBracket) {
Cary Clarkbad5ad72017-08-03 17:14:08 -0400639 if (inMethod) {
640 continue;
641 }
Cary Clark8032b982017-07-28 11:04:54 -0400642 break;
643 }
644 SkASSERT(0); // incomplete
645 }
646 if (Definition::Type::kKeyWord == token.fType) {
647 switch (token.fKeyWord) {
648 case KeyWord::kEnum:
649 inEnum = true;
650 break;
651 case KeyWord::kConst:
652 case KeyWord::kConstExpr:
653 case KeyWord::kStatic:
654 case KeyWord::kInt:
655 case KeyWord::kUint32_t:
656 case KeyWord::kSize_t:
657 case KeyWord::kFloat:
658 case KeyWord::kBool:
659 case KeyWord::kVoid:
660 if (!typeStart) {
661 typeStart = &token;
662 }
663 break;
664 default:
665 break;
666 }
667 continue;
668 }
669 if (Definition::Type::kPunctuation == token.fType) {
670 if (inEnum) {
671 SkASSERT(Punctuation::kSemicolon == token.fPunctuation);
672 inEnum = false;
673 }
Cary Clarkbad5ad72017-08-03 17:14:08 -0400674 if (inMethod) {
675 if (Punctuation::kColon == token.fPunctuation) {
676 inMethod = false;
677 } else if (Punctuation::kLeftBrace == token.fPunctuation) {
678 inMethod = false;
Cary Clark73fa9722017-08-29 17:36:51 -0400679 } else if (Punctuation::kSemicolon == token.fPunctuation) {
680 inMethod = false;
Cary Clarkbad5ad72017-08-03 17:14:08 -0400681 } else {
682 SkASSERT(0); // incomplete
683 }
684 }
685 if (inMember) {
686 SkASSERT(Punctuation::kSemicolon == token.fPunctuation);
687 typeStart = nullptr;
688 inMember = false;
689 }
Cary Clark8032b982017-07-28 11:04:54 -0400690 continue;
691 }
692 if (Definition::Type::kWord != token.fType) {
693 SkASSERT(0); // incomplete
694 }
695 if (MarkType::kMember == token.fMarkType) {
696 TextParser typeStr(token.fFileName, typeStart->fContentStart, token.fContentStart,
697 token.fLineCount);
698 typeStr.trimEnd();
Cary Clarkbad5ad72017-08-03 17:14:08 -0400699 longestType = SkTMax(longestType, (int) (typeStr.fEnd - typeStr.fStart));
Cary Clark8032b982017-07-28 11:04:54 -0400700 longestName = SkTMax(longestName, (int) (token.fContentEnd - token.fContentStart));
701 typeStart->fMemberStart = true;
Cary Clarkbad5ad72017-08-03 17:14:08 -0400702 inMember = true;
703 continue;
704 }
705 if (MarkType::kMethod == token.fMarkType) {
706 inMethod = true;
Cary Clark8032b982017-07-28 11:04:54 -0400707 continue;
708 }
709 SkASSERT(MarkType::kNone == token.fMarkType);
Cary Clarkbad5ad72017-08-03 17:14:08 -0400710 if (typeStart) {
711 if (inMember) {
712 longestValue =
713 SkTMax(longestValue, (int) (token.fContentEnd - token.fContentStart));
714 }
715 } else {
Cary Clark8032b982017-07-28 11:04:54 -0400716 typeStart = &token;
717 }
718 }
719 fStructMemberTab = longestType + fIndent + 1 /* space before name */ ;
Cary Clarkbad5ad72017-08-03 17:14:08 -0400720 fStructValueTab = fStructMemberTab + longestName + 2 /* space ; */ ;
721 fStructCommentTab = fStructValueTab;
722 if (longestValue) {
723 fStructCommentTab += longestValue + 3 /* space = space */ ;
724 fStructValueTab -= 1 /* ; */ ;
725 }
Cary Clark8032b982017-07-28 11:04:54 -0400726 // iterate through bmh children and see which comments fit on include lines
727 for (auto& member : fStructDef->fChildren) {
728 if (MarkType::kMember != member->fMarkType) {
729 continue;
730 }
731 TextParser memberLine(member);
732 memberLine.trimEnd();
733 const char* commentStart = memberLine.fChar;
734 memberLine.skipLine();
735 ptrdiff_t lineLen = memberLine.fChar - commentStart + 5 /* //!< space */ ;
736 if (!memberLine.eof()) {
737 memberLine.skipWhiteSpace();
738 }
739 member->fShort = memberLine.eof() && fStructCommentTab + lineLen < 100;
740 }
741}
742
Cary Clark73fa9722017-08-29 17:36:51 -0400743bool IncludeWriter::populate(Definition* def, ParentPair* prevPair, RootDefinition* root) {
744 ParentPair pair = { def, prevPair };
Cary Clark8032b982017-07-28 11:04:54 -0400745 // write bulk of original include up to class, method, enum, etc., excepting preceding comment
746 // find associated bmh object
747 // write any associated comments in Doxygen form
748 // skip include comment
749 // if there is a series of same named methods, write one set of comments, then write all methods
750 string methodName;
751 const Definition* method;
752 const Definition* clonedMethod = nullptr;
753 const Definition* memberStart = nullptr;
Cary Clarkbad5ad72017-08-03 17:14:08 -0400754 const Definition* memberEnd = nullptr;
Cary Clark8032b982017-07-28 11:04:54 -0400755 fContinuation = nullptr;
756 bool inStruct = false;
Cary Clarkbad5ad72017-08-03 17:14:08 -0400757 bool inConstructor = false;
Cary Clark8032b982017-07-28 11:04:54 -0400758 for (auto& child : def->fTokens) {
Cary Clarkbad5ad72017-08-03 17:14:08 -0400759 if (memberEnd) {
760 if (memberEnd != &child) {
761 continue;
762 }
763 fStart = child.fContentStart + 1;
764 memberEnd = nullptr;
765 }
Cary Clark8032b982017-07-28 11:04:54 -0400766 if (child.fPrivate) {
767 continue;
768 }
769 if (fContinuation) {
770 if (Definition::Type::kKeyWord == child.fType) {
Cary Clark73fa9722017-08-29 17:36:51 -0400771 if (KeyWord::kFriend == child.fKeyWord || KeyWord::kBool == child.fKeyWord ||
772 KeyWord::kSK_API == child.fKeyWord) {
Cary Clark8032b982017-07-28 11:04:54 -0400773 continue;
774 }
775 }
776 if (Definition::Type::kBracket == child.fType && Bracket::kParen == child.fBracket) {
777 if (!clonedMethod) {
Cary Clarkbad5ad72017-08-03 17:14:08 -0400778 if (inConstructor) {
779 fContinuation = child.fContentStart;
780 }
Cary Clark8032b982017-07-28 11:04:54 -0400781 continue;
782 }
783 int alternate = 1;
784 ptrdiff_t childLen = child.fContentEnd - child.fContentStart;
785 SkASSERT(')' == child.fContentStart[childLen]);
786 ++childLen;
787 do {
788 TextParser params(clonedMethod->fFileName, clonedMethod->fStart,
789 clonedMethod->fContentStart, clonedMethod->fLineCount);
790 params.skipToEndBracket('(');
Cary Clarkbad5ad72017-08-03 17:14:08 -0400791 if (params.startsWith(child.fContentStart, childLen)) {
Cary Clark579985c2017-07-31 11:48:27 -0400792 this->methodOut(clonedMethod, child);
Cary Clark8032b982017-07-28 11:04:54 -0400793 break;
794 }
795 ++alternate;
796 string alternateMethod = methodName + '_' + to_string(alternate);
Cary Clarkce101242017-09-01 15:51:02 -0400797 clonedMethod = root->find(alternateMethod, RootDefinition::AllowParens::kNo);
Cary Clark8032b982017-07-28 11:04:54 -0400798 } while (clonedMethod);
799 if (!clonedMethod) {
800 return this->reportError<bool>("cloned method not found");
801 }
802 clonedMethod = nullptr;
803 continue;
804 }
805 if (Definition::Type::kWord == child.fType) {
806 if (clonedMethod) {
807 continue;
808 }
809 size_t len = (size_t) (child.fContentEnd - child.fContentStart);
810 const char operatorStr[] = "operator";
811 size_t operatorLen = sizeof(operatorStr) - 1;
812 if (len >= operatorLen && !strncmp(child.fContentStart, operatorStr, operatorLen)) {
813 fContinuation = child.fContentEnd;
814 continue;
815 }
816 }
817 if (Definition::Type::kPunctuation == child.fType &&
818 (Punctuation::kSemicolon == child.fPunctuation ||
819 Punctuation::kLeftBrace == child.fPunctuation)) {
820 SkASSERT(fContinuation[0] == '(');
821 const char* continueEnd = child.fContentStart;
822 while (continueEnd > fContinuation && isspace(continueEnd[-1])) {
823 --continueEnd;
824 }
825 methodName += string(fContinuation, continueEnd - fContinuation);
Cary Clarkce101242017-09-01 15:51:02 -0400826 method = root->find(methodName, RootDefinition::AllowParens::kNo);
Cary Clark8032b982017-07-28 11:04:54 -0400827 if (!method) {
828 fLineCount = child.fLineCount;
829 fclose(fOut); // so we can see what we've written so far
830 return this->reportError<bool>("method not found");
831 }
Cary Clark579985c2017-07-31 11:48:27 -0400832 this->methodOut(method, child);
Cary Clark8032b982017-07-28 11:04:54 -0400833 continue;
834 }
Cary Clarkbad5ad72017-08-03 17:14:08 -0400835 if (inConstructor) {
836 continue;
837 }
Cary Clark8032b982017-07-28 11:04:54 -0400838 methodName += "()";
Cary Clarkce101242017-09-01 15:51:02 -0400839 method = root->find(methodName, RootDefinition::AllowParens::kNo);
Cary Clarkbad5ad72017-08-03 17:14:08 -0400840 if (method && MarkType::kDefinedBy == method->fMarkType) {
Cary Clark8032b982017-07-28 11:04:54 -0400841 method = method->fParent;
842 }
843 if (method) {
Cary Clark579985c2017-07-31 11:48:27 -0400844 this->methodOut(method, child);
Cary Clark8032b982017-07-28 11:04:54 -0400845 continue;
846 }
847 fLineCount = child.fLineCount;
848 fclose(fOut); // so we can see what we've written so far
849 return this->reportError<bool>("method not found");
850 }
851 if (Bracket::kSlashSlash == child.fBracket || Bracket::kSlashStar == child.fBracket) {
852 if (!fDeferComment) {
853 fDeferComment = &child;
854 }
Cary Clarkbad5ad72017-08-03 17:14:08 -0400855 fLastComment = &child;
Cary Clark8032b982017-07-28 11:04:54 -0400856 continue;
857 }
858 if (MarkType::kMethod == child.fMarkType) {
Cary Clarkbad5ad72017-08-03 17:14:08 -0400859 if (this->internalName(child)) {
860 continue;
861 }
Cary Clark8032b982017-07-28 11:04:54 -0400862 const char* bodyEnd = fDeferComment ? fDeferComment->fContentStart - 1 :
863 child.fContentStart;
864 // FIXME: roll end-trimming into writeBlockTrim call
865 while (fStart < bodyEnd && ' ' >= bodyEnd[-1]) {
866 --bodyEnd;
867 }
868 int blockSize = (int) (bodyEnd - fStart);
869 if (blockSize) {
870 this->writeBlock(blockSize, fStart);
871 }
872 fStart = child.fContentStart;
873 methodName = root->fName + "::" + child.fName;
Cary Clarkbad5ad72017-08-03 17:14:08 -0400874 inConstructor = root->fName == child.fName;
Cary Clark8032b982017-07-28 11:04:54 -0400875 fContinuation = child.fContentEnd;
Cary Clarkce101242017-09-01 15:51:02 -0400876 method = root->find(methodName, RootDefinition::AllowParens::kNo);
Cary Clark8032b982017-07-28 11:04:54 -0400877 if (!method) {
878 continue;
879 }
880 if (method->fCloned) {
881 clonedMethod = method;
882 continue;
883 }
Cary Clark579985c2017-07-31 11:48:27 -0400884 this->methodOut(method, child);
Cary Clark73fa9722017-08-29 17:36:51 -0400885 if (fAttrDeprecated) {
886 fStart = fAttrDeprecated->fContentStart;
887 fAttrDeprecated = nullptr;
888 }
Cary Clark8032b982017-07-28 11:04:54 -0400889 continue;
890 }
891 if (Definition::Type::kKeyWord == child.fType) {
892 const Definition* structDef = nullptr;
893 switch (child.fKeyWord) {
894 case KeyWord::kStruct:
Cary Clark73fa9722017-08-29 17:36:51 -0400895 case KeyWord::kClass:
Cary Clark8032b982017-07-28 11:04:54 -0400896 // if struct contains members, compute their name and comment tabs
Cary Clark73fa9722017-08-29 17:36:51 -0400897 if (child.fChildren.size() > 0) {
898 const ParentPair* testPair = &pair;
899 while ((testPair = testPair->fPrev)) {
900 if (KeyWord::kClass == testPair->fParent->fKeyWord) {
901 inStruct = fInStruct = true;
902 break;
903 }
904 }
905 }
Cary Clark8032b982017-07-28 11:04:54 -0400906 if (fInStruct) {
907 fIndent += 4;
Cary Clarkce101242017-09-01 15:51:02 -0400908 fStructDef = root->find(child.fName, RootDefinition::AllowParens::kNo);
Cary Clark8032b982017-07-28 11:04:54 -0400909 if (nullptr == structDef) {
Cary Clarkce101242017-09-01 15:51:02 -0400910 fStructDef = root->find(root->fName + "::" + child.fName,
911 RootDefinition::AllowParens::kNo);
Cary Clark8032b982017-07-28 11:04:54 -0400912 }
913 this->structSizeMembers(child);
914 fIndent -= 4;
915 }
Cary Clark8032b982017-07-28 11:04:54 -0400916 if (child.fChildren.size() > 0) {
917 const char* bodyEnd = fDeferComment ? fDeferComment->fContentStart - 1 :
918 child.fContentStart;
Cary Clark73fa9722017-08-29 17:36:51 -0400919 this->writeBlockTrim((int) (bodyEnd - fStart), fStart);
Cary Clark8032b982017-07-28 11:04:54 -0400920 fStart = child.fContentStart;
921 if (child.fName == root->fName) {
922 if (Definition* parent = root->fParent) {
923 if (MarkType::kTopic == parent->fMarkType ||
924 MarkType::kSubtopic == parent->fMarkType) {
925 const char* commentStart = parent->fContentStart;
Cary Clark1eace2d2017-07-31 07:52:43 -0400926 for (auto child : parent->fChildren) {
927 if (MarkType::kClass == child->fMarkType) {
928 break;
929 }
930 commentStart = child->fTerminator;
931 }
Cary Clark8032b982017-07-28 11:04:54 -0400932 const char* commentEnd = root->fStart;
933 this->structOut(root, *root, commentStart, commentEnd);
934 } else {
935 SkASSERT(0); // incomplete
936 }
937 } else {
938 SkASSERT(0); // incomplete
939 }
940 } else {
Cary Clarkce101242017-09-01 15:51:02 -0400941 structDef = root->find(child.fName, RootDefinition::AllowParens::kNo);
Cary Clark8032b982017-07-28 11:04:54 -0400942 if (nullptr == structDef) {
Cary Clarkce101242017-09-01 15:51:02 -0400943 structDef = root->find(root->fName + "::" + child.fName,
944 RootDefinition::AllowParens::kNo);
Cary Clark8032b982017-07-28 11:04:54 -0400945 }
Cary Clarkbad5ad72017-08-03 17:14:08 -0400946 if (!structDef) {
947 this->lf(2);
948 fIndent = 0;
949 this->writeBlock((int) (fStart - bodyEnd), bodyEnd);
950 this->lfcr();
951 continue;
952 }
Cary Clark8032b982017-07-28 11:04:54 -0400953 Definition* codeBlock = nullptr;
954 Definition* nextBlock = nullptr;
955 for (auto test : structDef->fChildren) {
956 if (MarkType::kCode == test->fMarkType) {
957 SkASSERT(!codeBlock); // FIXME: check enum for correct order earlier
958 codeBlock = test;
959 continue;
960 }
961 if (codeBlock) {
962 nextBlock = test;
963 break;
964 }
965 }
Cary Clark73fa9722017-08-29 17:36:51 -0400966 // FIXME: trigger error earlier if inner #Struct or #Class is missing #Code
Cary Clark8032b982017-07-28 11:04:54 -0400967 SkASSERT(nextBlock); // FIXME: check enum for correct order earlier
968 const char* commentStart = codeBlock->fTerminator;
969 const char* commentEnd = nextBlock->fStart;
970 this->structOut(root, *structDef, commentStart, commentEnd);
971 }
972 fDeferComment = nullptr;
973 } else {
974 ; // empty forward reference, nothing to do here
975 }
976 break;
977 case KeyWord::kEnum: {
Cary Clarkbad5ad72017-08-03 17:14:08 -0400978 fInEnum = true;
Cary Clark8032b982017-07-28 11:04:54 -0400979 this->enumHeaderOut(root, child);
980 this->enumSizeItems(child);
981 } break;
982 case KeyWord::kConst:
983 case KeyWord::kConstExpr:
984 case KeyWord::kStatic:
985 case KeyWord::kInt:
986 case KeyWord::kUint32_t:
Cary Clark73fa9722017-08-29 17:36:51 -0400987 case KeyWord::kUnsigned:
Cary Clark8032b982017-07-28 11:04:54 -0400988 case KeyWord::kSize_t:
989 case KeyWord::kFloat:
990 case KeyWord::kBool:
991 case KeyWord::kVoid:
992 if (!memberStart) {
993 memberStart = &child;
994 }
995 break;
996 case KeyWord::kPublic:
997 case KeyWord::kPrivate:
998 case KeyWord::kProtected:
999 case KeyWord::kFriend:
Cary Clark73fa9722017-08-29 17:36:51 -04001000 case KeyWord::kInline:
1001 case KeyWord::kSK_API:
Cary Clarkbad5ad72017-08-03 17:14:08 -04001002 case KeyWord::kTypedef:
Cary Clark8032b982017-07-28 11:04:54 -04001003 break;
1004 default:
1005 SkASSERT(0);
1006 }
1007 if (structDef) {
1008 TextParser structName(&child);
1009 SkAssertResult(structName.skipToEndBracket('{'));
1010 fStart = structName.fChar + 1;
1011 this->writeBlock((int) (fStart - child.fStart), child.fStart);
1012 this->lf(2);
1013 fIndent += 4;
Cary Clark73fa9722017-08-29 17:36:51 -04001014 if (!this->populate(&child, &pair, const_cast<Definition*>(structDef)->asRoot())) {
Cary Clark8032b982017-07-28 11:04:54 -04001015 return false;
1016 }
1017 // output any remaining definitions at current indent level
1018 const char* structEnd = child.fContentEnd;
1019 SkAssertResult('}' == structEnd[-1]);
1020 --structEnd;
Cary Clarkbad5ad72017-08-03 17:14:08 -04001021 this->writeBlockTrim((int) (structEnd - fStart), fStart);
Cary Clark8032b982017-07-28 11:04:54 -04001022 this->lf(2);
1023 fStart = structEnd;
1024 fIndent -= 4;
1025 fContinuation = nullptr;
1026 fDeferComment = nullptr;
1027 } else {
Cary Clarkbad5ad72017-08-03 17:14:08 -04001028 if (fInEnum && KeyWord::kClass == child.fChildren[0]->fKeyWord) {
Cary Clark73fa9722017-08-29 17:36:51 -04001029 if (!this->populate(child.fChildren[0], &pair, root)) {
Cary Clarkbad5ad72017-08-03 17:14:08 -04001030 return false;
1031 }
Cary Clark73fa9722017-08-29 17:36:51 -04001032 } else if (!this->populate(&child, &pair, root)) {
Cary Clark8032b982017-07-28 11:04:54 -04001033 return false;
1034 }
1035 }
1036 continue;
1037 }
1038 if (Definition::Type::kBracket == child.fType) {
Cary Clarkbad5ad72017-08-03 17:14:08 -04001039 if (KeyWord::kEnum == child.fParent->fKeyWord ||
1040 (KeyWord::kClass == child.fParent->fKeyWord && child.fParent->fParent &&
1041 KeyWord::kEnum == child.fParent->fParent->fKeyWord)) {
Cary Clark8032b982017-07-28 11:04:54 -04001042 this->enumMembersOut(root, child);
1043 this->writeString("};");
1044 this->lf(2);
1045 fStart = child.fParent->fContentEnd;
1046 SkASSERT(';' == fStart[0]);
1047 ++fStart;
1048 fDeferComment = nullptr;
1049 fInEnum = false;
1050 continue;
Cary Clark73fa9722017-08-29 17:36:51 -04001051 }
1052 if (fAttrDeprecated) {
1053 continue;
1054 }
Cary Clark8032b982017-07-28 11:04:54 -04001055 fDeferComment = nullptr;
Cary Clark73fa9722017-08-29 17:36:51 -04001056 if (!this->populate(&child, &pair, root)) {
Cary Clark8032b982017-07-28 11:04:54 -04001057 return false;
1058 }
1059 continue;
1060 }
1061 if (Definition::Type::kWord == child.fType) {
1062 if (MarkType::kMember == child.fMarkType) {
Cary Clarkbad5ad72017-08-03 17:14:08 -04001063 memberEnd = this->structMemberOut(memberStart, child);
Cary Clark8032b982017-07-28 11:04:54 -04001064 fStart = child.fContentEnd + 1;
1065 fDeferComment = nullptr;
1066 }
1067 if (child.fMemberStart) {
1068 memberStart = &child;
1069 }
Cary Clark73fa9722017-08-29 17:36:51 -04001070 const char attrDeprecated[] = "SK_ATTR_DEPRECATED";
1071 const size_t attrDeprecatedLen = sizeof(attrDeprecated) - 1;
1072 if (attrDeprecatedLen == child.fContentEnd - child.fContentStart &&
1073 !strncmp(attrDeprecated, child.fStart, attrDeprecatedLen)) {
1074 fAttrDeprecated = &child;
1075 }
Cary Clark8032b982017-07-28 11:04:54 -04001076 continue;
1077 }
1078 if (Definition::Type::kPunctuation == child.fType) {
1079 if (Punctuation::kSemicolon == child.fPunctuation) {
1080 memberStart = nullptr;
1081 if (inStruct) {
1082 fInStruct = false;
1083 }
1084 continue;
1085 }
1086 if (Punctuation::kLeftBrace == child.fPunctuation ||
1087 Punctuation::kColon == child.fPunctuation ||
1088 Punctuation::kAsterisk == child.fPunctuation
1089 ) {
1090 continue;
1091 }
1092 }
1093 }
1094 return true;
1095}
1096
1097bool IncludeWriter::populate(BmhParser& bmhParser) {
1098 bool allPassed = true;
1099 for (auto& includeMapper : fIncludeMap) {
1100 size_t lastSlash = includeMapper.first.rfind('/');
1101 if (string::npos == lastSlash || lastSlash >= includeMapper.first.length() - 1) {
1102 return this->reportError<bool>("malformed include name");
1103 }
1104 string fileName = includeMapper.first.substr(lastSlash + 1);
1105 if (".h" != fileName.substr(fileName.length() - 2)) {
1106 return this->reportError<bool>("expected fileName.h");
1107 }
1108 string skClassName = fileName.substr(0, fileName.length() - 2);
1109 fOut = fopen(fileName.c_str(), "wb");
1110 if (!fOut) {
1111 SkDebugf("could not open output file %s\n", fileName.c_str());
1112 return false;
1113 }
1114 if (bmhParser.fClassMap.end() == bmhParser.fClassMap.find(skClassName)) {
1115 return this->reportError<bool>("could not find bmh class");
1116 }
1117 fBmhParser = &bmhParser;
1118 RootDefinition* root = &bmhParser.fClassMap[skClassName];
1119 fRootTopic = root->fParent;
1120 root->clearVisited();
1121 fStart = includeMapper.second.fContentStart;
1122 fEnd = includeMapper.second.fContentEnd;
Cary Clark73fa9722017-08-29 17:36:51 -04001123 allPassed &= this->populate(&includeMapper.second, nullptr, root);
Cary Clark8032b982017-07-28 11:04:54 -04001124 this->writeBlock((int) (fEnd - fStart), fStart);
1125 fIndent = 0;
1126 this->lfcr();
1127 this->writePending();
1128 fclose(fOut);
1129 }
1130 return allPassed;
1131}
1132
1133// change Xxx_Xxx to xxx xxx
1134static string ConvertRef(const string str, bool first) {
1135 string substitute;
1136 for (char c : str) {
1137 if ('_' == c) {
1138 c = ' '; // change Xxx_Xxx to xxx xxx
1139 } else if (isupper(c) && !first) {
1140 c = tolower(c);
1141 }
1142 substitute += c;
1143 first = false;
1144 }
1145 return substitute;
1146}
1147
Cary Clark8032b982017-07-28 11:04:54 -04001148string IncludeWriter::resolveMethod(const char* start, const char* end, bool first) {
1149 string methodname(start, end - start);
Cary Clark579985c2017-07-31 11:48:27 -04001150 if (string::npos != methodname.find("()")) {
1151 return "";
1152 }
Cary Clark8032b982017-07-28 11:04:54 -04001153 string substitute;
1154 auto rootDefIter = fBmhParser->fMethodMap.find(methodname);
1155 if (fBmhParser->fMethodMap.end() != rootDefIter) {
1156 substitute = methodname + "()";
1157 } else {
Cary Clark1eace2d2017-07-31 07:52:43 -04001158 RootDefinition* parent = nullptr;
1159 for (auto candidate : fRootTopic->fChildren) {
1160 if (MarkType::kClass == candidate->fMarkType
1161 || MarkType::kStruct == candidate->fMarkType) {
1162 parent = candidate->asRoot();
1163 break;
1164 }
1165 }
1166 SkASSERT(parent);
Cary Clarkce101242017-09-01 15:51:02 -04001167 auto defRef = parent->find(parent->fName + "::" + methodname,
1168 RootDefinition::AllowParens::kNo);
Cary Clark8032b982017-07-28 11:04:54 -04001169 if (defRef && MarkType::kMethod == defRef->fMarkType) {
1170 substitute = methodname + "()";
1171 }
1172 }
Cary Clark579985c2017-07-31 11:48:27 -04001173 if (fMethodDef && methodname == fMethodDef->fName) {
1174 TextParser report(fBmhMethod);
1175 report.reportError("method should not include references to itself");
1176 return "";
1177 }
Cary Clark8032b982017-07-28 11:04:54 -04001178 return substitute;
1179}
1180
Cary Clarkce101242017-09-01 15:51:02 -04001181string IncludeWriter::resolveRef(const char* start, const char* end, bool first,
1182 RefType* refType) {
Cary Clark8032b982017-07-28 11:04:54 -04001183 // look up Xxx_Xxx
1184 string undername(start, end - start);
Cary Clarkce101242017-09-01 15:51:02 -04001185 for (const auto& external : fBmhParser->fExternals) {
1186 if (external.fName == undername) {
1187 *refType = RefType::kExternal;
1188 return external.fName;
1189 }
1190 }
1191 *refType = RefType::kNormal;
Cary Clark8032b982017-07-28 11:04:54 -04001192 SkASSERT(string::npos == undername.find(' '));
1193 const Definition* rootDef = nullptr;
1194 {
1195 auto rootDefIter = fBmhParser->fTopicMap.find(undername);
1196 if (fBmhParser->fTopicMap.end() != rootDefIter) {
1197 rootDef = rootDefIter->second;
1198 } else {
1199 string prefixedName = fRootTopic->fName + '_' + undername;
1200 rootDefIter = fBmhParser->fTopicMap.find(prefixedName);
1201 if (fBmhParser->fTopicMap.end() != rootDefIter) {
1202 rootDef = rootDefIter->second;
Cary Clarkbad5ad72017-08-03 17:14:08 -04001203 } else if (fStructDef) {
1204 string localPrefix = fStructDef->fFiddle + '_' + undername;
1205 rootDefIter = fBmhParser->fTopicMap.find(localPrefix);
1206 if (fBmhParser->fTopicMap.end() != rootDefIter) {
1207 rootDef = rootDefIter->second;
1208 }
Cary Clark8032b982017-07-28 11:04:54 -04001209 } else {
1210 auto aliasIter = fBmhParser->fAliasMap.find(undername);
1211 if (fBmhParser->fAliasMap.end() != aliasIter) {
1212 rootDef = aliasIter->second->fParent;
1213 } else if (!first) {
Cary Clark8032b982017-07-28 11:04:54 -04001214 SkDebugf("unfound: %s\n", undername.c_str());
Cary Clark579985c2017-07-31 11:48:27 -04001215 this->reportError("reference unfound");
1216 return "";
Cary Clark8032b982017-07-28 11:04:54 -04001217 }
1218 }
1219 }
1220 }
1221 string substitute;
1222 if (rootDef) {
1223 for (auto child : rootDef->fChildren) {
1224 if (MarkType::kSubstitute == child->fMarkType) {
1225 substitute = string(child->fContentStart,
1226 (int) (child->fContentEnd - child->fContentStart));
1227 break;
1228 }
1229 if (MarkType::kClass == child->fMarkType ||
1230 MarkType::kStruct == child->fMarkType ||
1231 MarkType::kEnum == child->fMarkType ||
1232 MarkType::kEnumClass == child->fMarkType) {
1233 substitute = child->fName;
1234 if (MarkType::kEnum == child->fMarkType && fInEnum) {
1235 size_t parentClassEnd = substitute.find("::");
1236 SkASSERT(string::npos != parentClassEnd);
1237 substitute = substitute.substr(parentClassEnd + 2);
1238 }
1239 break;
1240 }
1241 }
1242 if (!substitute.length()) {
1243 auto parent = rootDef->fParent;
1244 if (parent) {
1245 if (MarkType::kClass == parent->fMarkType ||
1246 MarkType::kStruct == parent->fMarkType ||
1247 MarkType::kEnum == parent->fMarkType ||
1248 MarkType::kEnumClass == parent->fMarkType) {
1249 if (parent->fParent != fRootTopic) {
1250 substitute = parent->fName;
1251 size_t under = undername.find('_');
1252 SkASSERT(string::npos != under);
1253 string secondHalf(&undername[under], (size_t) (undername.length() - under));
1254 substitute += ConvertRef(secondHalf, false);
1255 } else {
1256 substitute += ConvertRef(undername, first);
1257 }
1258 }
1259 }
1260 }
1261 }
Cary Clark1eace2d2017-07-31 07:52:43 -04001262 // Ensure first word after period is capitalized if substitute is lower cased.
1263 if (first && isupper(start[0]) && substitute.length() > 0 && islower(substitute[0])) {
1264 substitute[0] = start[0];
1265 }
Cary Clark8032b982017-07-28 11:04:54 -04001266 return substitute;
1267}
Cary Clark579985c2017-07-31 11:48:27 -04001268
Cary Clark8032b982017-07-28 11:04:54 -04001269int IncludeWriter::lookupMethod(const PunctuationState punctuation, const Word word,
Cary Clark579985c2017-07-31 11:48:27 -04001270 const int lastSpace, const int run, int lastWrite, const char* data) {
1271 int wordStart = lastSpace;
1272 while (' ' >= data[wordStart]) {
1273 ++wordStart;
1274 }
1275 const int wordEnd = PunctuationState::kDelimiter == punctuation ||
Cary Clark8032b982017-07-28 11:04:54 -04001276 PunctuationState::kPeriod == punctuation ? run - 1 : run;
Cary Clark579985c2017-07-31 11:48:27 -04001277 string temp = this->resolveMethod(&data[wordStart], &data[wordEnd], Word::kFirst == word);
Cary Clark8032b982017-07-28 11:04:54 -04001278 if (temp.length()) {
Cary Clark579985c2017-07-31 11:48:27 -04001279 if (wordStart > lastWrite) {
1280 SkASSERT(data[wordStart - 1] >= ' ');
Cary Clark8032b982017-07-28 11:04:54 -04001281 if (' ' == data[lastWrite]) {
1282 this->writeSpace();
1283 }
Cary Clark579985c2017-07-31 11:48:27 -04001284 this->writeBlockTrim(wordStart - lastWrite, &data[lastWrite]);
1285 if (' ' == data[wordStart - 1]) {
Cary Clark8032b982017-07-28 11:04:54 -04001286 this->writeSpace();
1287 }
1288 }
1289 SkASSERT(temp[temp.length() - 1] > ' ');
1290 this->writeString(temp.c_str());
Cary Clark579985c2017-07-31 11:48:27 -04001291 lastWrite = wordEnd;
Cary Clark8032b982017-07-28 11:04:54 -04001292 }
1293 return lastWrite;
1294}
1295
1296int IncludeWriter::lookupReference(const PunctuationState punctuation, const Word word,
1297 const int start, const int run, int lastWrite, const char last, const char* data) {
1298 const int end = PunctuationState::kDelimiter == punctuation ||
1299 PunctuationState::kPeriod == punctuation ? run - 1 : run;
Cary Clarkce101242017-09-01 15:51:02 -04001300 RefType refType = RefType::kUndefined;
1301 string resolved = string(&data[start], (size_t) (end - start));
1302 string temp = this->resolveRef(&data[start], &data[end], Word::kFirst == word, &refType);
Cary Clark8032b982017-07-28 11:04:54 -04001303 if (!temp.length()) {
1304 if (Word::kFirst != word && '_' != last) {
Cary Clarkce101242017-09-01 15:51:02 -04001305 temp = ConvertRef(resolved, false);
Cary Clark8032b982017-07-28 11:04:54 -04001306 }
1307 }
1308 if (temp.length()) {
1309 if (start > lastWrite) {
1310 SkASSERT(data[start - 1] >= ' ');
1311 if (' ' == data[lastWrite]) {
1312 this->writeSpace();
1313 }
1314 this->writeBlockTrim(start - lastWrite, &data[lastWrite]);
1315 if (' ' == data[start - 1]) {
1316 this->writeSpace();
1317 }
1318 }
1319 SkASSERT(temp[temp.length() - 1] > ' ');
1320 this->writeString(temp.c_str());
1321 lastWrite = end;
1322 }
1323 return lastWrite;
1324}
1325
1326/* returns true if rewriteBlock wrote linefeeds */
1327IncludeWriter::Wrote IncludeWriter::rewriteBlock(int size, const char* data) {
1328 bool wroteLineFeeds = false;
1329 while (size > 0 && data[0] <= ' ') {
1330 --size;
1331 ++data;
1332 }
1333 while (size > 0 && data[size - 1] <= ' ') {
1334 --size;
1335 }
1336 if (0 == size) {
1337 return Wrote::kNone;
1338 }
1339 int run = 0;
1340 Word word = Word::kStart;
1341 PunctuationState punctuation = PunctuationState::kStart;
1342 int start = 0;
1343 int lastWrite = 0;
1344 int lineFeeds = 0;
1345 int lastPrintable = 0;
Cary Clark579985c2017-07-31 11:48:27 -04001346 int lastSpace = -1;
Cary Clark8032b982017-07-28 11:04:54 -04001347 char c = 0;
1348 char last;
Cary Clark579985c2017-07-31 11:48:27 -04001349 bool embeddedSymbol = false;
Cary Clark8032b982017-07-28 11:04:54 -04001350 bool hasLower = false;
1351 bool hasUpper = false;
1352 bool hasSymbol = false;
1353 while (run < size) {
1354 last = c;
1355 c = data[run];
1356 SkASSERT(' ' <= c || '\n' == c);
1357 if (lineFeeds && ' ' < c) {
1358 if (lastPrintable >= lastWrite) {
1359 if (' ' == data[lastWrite]) {
1360 this->writeSpace();
1361 }
1362 this->writeBlock(lastPrintable - lastWrite + 1, &data[lastWrite]);
1363 }
1364 if (lineFeeds > 1) {
1365 this->lf(2);
1366 }
1367 this->lfcr(); // defer the indent until non-whitespace is seen
1368 lastWrite = run;
1369 lineFeeds = 0;
1370 }
1371 if (' ' < c) {
1372 lastPrintable = run;
1373 }
1374 switch (c) {
1375 case '\n':
1376 ++lineFeeds;
1377 wroteLineFeeds = true;
1378 case ' ':
1379 switch (word) {
1380 case Word::kStart:
1381 break;
1382 case Word::kUnderline:
1383 case Word::kCap:
1384 case Word::kFirst:
1385 if (!hasLower) {
1386 break;
1387 }
1388 lastWrite = this->lookupReference(punctuation, word, start, run,
1389 lastWrite, last, data);
1390 break;
1391 case Word::kMixed:
Cary Clark579985c2017-07-31 11:48:27 -04001392 if (hasUpper && hasLower && !hasSymbol && lastSpace > 0) {
1393 lastWrite = this->lookupMethod(punctuation, word, lastSpace, run,
1394 lastWrite, data);
Cary Clark8032b982017-07-28 11:04:54 -04001395 }
1396 break;
1397 default:
1398 SkASSERT(0);
1399 }
Cary Clark1eace2d2017-07-31 07:52:43 -04001400 punctuation = PunctuationState::kPeriod == punctuation ||
1401 (PunctuationState::kStart == punctuation && ' ' >= last) ?
1402 PunctuationState::kStart : PunctuationState::kSpace;
Cary Clark8032b982017-07-28 11:04:54 -04001403 word = Word::kStart;
Cary Clark579985c2017-07-31 11:48:27 -04001404 embeddedSymbol = false;
Cary Clark8032b982017-07-28 11:04:54 -04001405 hasLower = false;
1406 hasUpper = false;
1407 hasSymbol = false;
Cary Clark579985c2017-07-31 11:48:27 -04001408 lastSpace = run;
Cary Clark8032b982017-07-28 11:04:54 -04001409 break;
1410 case '.':
1411 switch (word) {
1412 case Word::kStart:
1413 punctuation = PunctuationState::kDelimiter;
1414 case Word::kCap:
1415 case Word::kFirst:
1416 case Word::kUnderline:
1417 case Word::kMixed:
1418 if (PunctuationState::kDelimiter == punctuation ||
1419 PunctuationState::kPeriod == punctuation) {
1420 word = Word::kMixed;
1421 }
1422 punctuation = PunctuationState::kPeriod;
1423 break;
1424 default:
1425 SkASSERT(0);
1426 }
Cary Clark579985c2017-07-31 11:48:27 -04001427 embeddedSymbol = true;
Cary Clark8032b982017-07-28 11:04:54 -04001428 break;
1429 case ',': case ';': case ':':
Cary Clark8032b982017-07-28 11:04:54 -04001430 switch (word) {
1431 case Word::kStart:
1432 punctuation = PunctuationState::kDelimiter;
1433 case Word::kCap:
1434 case Word::kFirst:
1435 case Word::kUnderline:
1436 case Word::kMixed:
1437 if (PunctuationState::kDelimiter == punctuation ||
1438 PunctuationState::kPeriod == punctuation) {
1439 word = Word::kMixed;
1440 }
1441 punctuation = PunctuationState::kDelimiter;
1442 break;
1443 default:
1444 SkASSERT(0);
1445 }
Cary Clark579985c2017-07-31 11:48:27 -04001446 embeddedSymbol = true;
Cary Clark8032b982017-07-28 11:04:54 -04001447 break;
1448 case '\'': // possessive apostrophe isn't treated as delimiting punctation
Cary Clarkce101242017-09-01 15:51:02 -04001449 case '\"': // quote is passed straight through
Cary Clark8032b982017-07-28 11:04:54 -04001450 case '=':
1451 case '!': // assumed not to be punctuation, but a programming symbol
Cary Clark73fa9722017-08-29 17:36:51 -04001452 case '&': case '>': case '<': case '{': case '}': case '/': case '*': case '[': case ']':
Cary Clark8032b982017-07-28 11:04:54 -04001453 word = Word::kMixed;
Cary Clark579985c2017-07-31 11:48:27 -04001454 embeddedSymbol = true;
Cary Clark8032b982017-07-28 11:04:54 -04001455 break;
1456 case '(':
1457 if (' ' == last) {
1458 punctuation = PunctuationState::kDelimiter;
1459 } else {
1460 word = Word::kMixed;
1461 }
Cary Clark579985c2017-07-31 11:48:27 -04001462 embeddedSymbol = true;
Cary Clark8032b982017-07-28 11:04:54 -04001463 break;
1464 case ')': // assume word type has already been set
1465 punctuation = PunctuationState::kDelimiter;
Cary Clark579985c2017-07-31 11:48:27 -04001466 embeddedSymbol = true;
Cary Clark8032b982017-07-28 11:04:54 -04001467 break;
1468 case '_':
1469 switch (word) {
1470 case Word::kStart:
1471 word = Word::kMixed;
1472 break;
1473 case Word::kCap:
1474 case Word::kFirst:
1475 case Word::kUnderline:
1476 word = Word::kUnderline;
1477 break;
1478 case Word::kMixed:
1479 break;
1480 default:
1481 SkASSERT(0);
1482 }
Cary Clark579985c2017-07-31 11:48:27 -04001483 hasSymbol |= embeddedSymbol;
Cary Clark8032b982017-07-28 11:04:54 -04001484 break;
Cary Clark73fa9722017-08-29 17:36:51 -04001485 case '+':
1486 // hackery to allow C++
1487 SkASSERT('C' == last || '+' == last); // FIXME: don't allow + outside of #Formula
1488 break;
Cary Clark8032b982017-07-28 11:04:54 -04001489 case 'A': case 'B': case 'C': case 'D': case 'E':
1490 case 'F': case 'G': case 'H': case 'I': case 'J':
1491 case 'K': case 'L': case 'M': case 'N': case 'O':
1492 case 'P': case 'Q': case 'R': case 'S': case 'T':
1493 case 'U': case 'V': case 'W': case 'X': case 'Y':
1494 case 'Z':
1495 switch (word) {
1496 case Word::kStart:
1497 word = PunctuationState::kStart == punctuation ? Word::kFirst : Word::kCap;
1498 start = run;
1499 break;
1500 case Word::kCap:
1501 case Word::kFirst:
1502 if (!isupper(last)) {
1503 word = Word::kMixed;
1504 }
1505 break;
1506 case Word::kUnderline:
1507 // some word in Xxx_XXX_Xxx can be all upper, but all can't: XXX_XXX
1508 if ('_' != last && !isupper(last)) {
1509 word = Word::kMixed;
1510 }
1511 break;
1512 case Word::kMixed:
1513 break;
1514 default:
1515 SkASSERT(0);
1516 }
1517 hasUpper = true;
1518 if (PunctuationState::kPeriod == punctuation ||
1519 PunctuationState::kDelimiter == punctuation) {
1520 word = Word::kMixed;
Cary Clark8032b982017-07-28 11:04:54 -04001521 }
Cary Clark579985c2017-07-31 11:48:27 -04001522 hasSymbol |= embeddedSymbol;
Cary Clark8032b982017-07-28 11:04:54 -04001523 break;
1524 case 'a': case 'b': case 'c': case 'd': case 'e':
1525 case 'f': case 'g': case 'h': case 'i': case 'j':
1526 case 'k': case 'l': case 'm': case 'n': case 'o':
1527 case 'p': case 'q': case 'r': case 's': case 't':
1528 case 'u': case 'v': case 'w': case 'x': case 'y':
1529 case 'z':
1530 case '0': case '1': case '2': case '3': case '4':
1531 case '5': case '6': case '7': case '8': case '9':
1532 case '-':
1533 switch (word) {
1534 case Word::kStart:
1535 word = Word::kMixed;
1536 break;
1537 case Word::kMixed:
1538 case Word::kCap:
1539 case Word::kFirst:
1540 case Word::kUnderline:
1541 break;
1542 default:
1543 SkASSERT(0);
1544 }
1545 hasLower = true;
1546 punctuation = PunctuationState::kStart;
Cary Clark579985c2017-07-31 11:48:27 -04001547 hasSymbol |= embeddedSymbol;
Cary Clark8032b982017-07-28 11:04:54 -04001548 break;
1549 default:
1550 SkASSERT(0);
1551 }
1552 ++run;
1553 }
1554 if ((word == Word::kCap || word == Word::kFirst || word == Word::kUnderline) && hasLower) {
1555 lastWrite = this->lookupReference(punctuation, word, start, run, lastWrite, last, data);
Cary Clark579985c2017-07-31 11:48:27 -04001556 } else if (word == Word::kMixed && hasUpper && hasLower && !hasSymbol && lastSpace > 0) {
1557 lastWrite = this->lookupMethod(punctuation, word, lastSpace, run, lastWrite, data);
Cary Clark8032b982017-07-28 11:04:54 -04001558 }
1559 if (run > lastWrite) {
1560 if (' ' == data[lastWrite]) {
1561 this->writeSpace();
1562 }
1563 this->writeBlock(run - lastWrite, &data[lastWrite]);
1564 }
1565 return wroteLineFeeds ? Wrote::kLF : Wrote::kChars;
1566}