884dd7d428
Command line runs without error for SkBitmap, SkPath, SkRect, SkIRect, SkPixmap, SkCanvas. Docs-Preview: https://skia.org/?cl=57112 TBR: caryclark@google.com Bug: skia:6898 Change-Id: I73b69ae8ffdf0a1e6bc187dc8a9dfb28f7766faa Reviewed-on: https://skia-review.googlesource.com/57112 Reviewed-by: Cary Clark <caryclark@skia.org> Commit-Queue: Cary Clark <caryclark@skia.org>
1719 lines
68 KiB
C++
1719 lines
68 KiB
C++
/*
|
|
* Copyright 2017 Google Inc.
|
|
*
|
|
* Use of this source code is governed by a BSD-style license that can be
|
|
* found in the LICENSE file.
|
|
*/
|
|
|
|
#include "bookmaker.h"
|
|
|
|
void IncludeWriter::descriptionOut(const Definition* def) {
|
|
const char* commentStart = def->fContentStart;
|
|
int commentLen = (int) (def->fContentEnd - commentStart);
|
|
bool breakOut = false;
|
|
for (auto prop : def->fChildren) {
|
|
switch (prop->fMarkType) {
|
|
case MarkType::kDefinedBy:
|
|
commentStart = prop->fTerminator;
|
|
break;
|
|
case MarkType::kDeprecated:
|
|
case MarkType::kPrivate:
|
|
commentLen = (int) (prop->fStart - commentStart);
|
|
if (commentLen > 0) {
|
|
SkASSERT(commentLen < 1000);
|
|
if (Wrote::kNone != this->rewriteBlock(commentLen, commentStart, Phrase::kNo)) {
|
|
this->lfcr();
|
|
}
|
|
}
|
|
commentStart = prop->fContentStart;
|
|
commentLen = (int) (prop->fContentEnd - commentStart);
|
|
if (commentLen > 0) {
|
|
if (Wrote::kNone != this->rewriteBlock(commentLen, commentStart, Phrase::kNo)) {
|
|
this->lfcr();
|
|
}
|
|
}
|
|
commentStart = prop->fTerminator;
|
|
commentLen = (int) (def->fContentEnd - commentStart);
|
|
break;
|
|
case MarkType::kExperimental:
|
|
this->writeString("EXPERIMENTAL:");
|
|
this->writeSpace();
|
|
commentStart = prop->fContentStart;
|
|
commentLen = (int) (prop->fContentEnd - commentStart);
|
|
if (commentLen > 0) {
|
|
if (Wrote::kNone != this->rewriteBlock(commentLen, commentStart, Phrase::kNo)) {
|
|
this->lfcr();
|
|
}
|
|
}
|
|
commentStart = prop->fTerminator;
|
|
commentLen = (int) (def->fContentEnd - commentStart);
|
|
break;
|
|
case MarkType::kFormula:
|
|
commentLen = prop->fStart - commentStart;
|
|
if (commentLen > 0) {
|
|
if (Wrote::kNone != this->rewriteBlock(commentLen, commentStart, Phrase::kNo)) {
|
|
this->lfcr();
|
|
}
|
|
}
|
|
this->writeBlock(prop->length(), prop->fContentStart);
|
|
commentStart = prop->fTerminator;
|
|
commentLen = (int) (def->fContentEnd - commentStart);
|
|
if ('\n' == commentStart[0] && '\n' == commentStart[1]) {
|
|
this->lf(2);
|
|
}
|
|
break;
|
|
case MarkType::kToDo:
|
|
commentLen = (int) (prop->fStart - commentStart);
|
|
if (commentLen > 0) {
|
|
SkASSERT(commentLen < 1000);
|
|
if (Wrote::kNone != this->rewriteBlock(commentLen, commentStart, Phrase::kNo)) {
|
|
this->lfcr();
|
|
}
|
|
}
|
|
commentStart = prop->fTerminator;
|
|
commentLen = (int) (def->fContentEnd - commentStart);
|
|
break;
|
|
case MarkType::kList:
|
|
commentLen = prop->fStart - commentStart;
|
|
if (commentLen > 0) {
|
|
if (Wrote::kNone != this->rewriteBlock(commentLen, commentStart,
|
|
Phrase::kNo)) {
|
|
this->lfcr();
|
|
}
|
|
}
|
|
for (auto row : prop->fChildren) {
|
|
SkASSERT(MarkType::kRow == row->fMarkType);
|
|
for (auto column : row->fChildren) {
|
|
SkASSERT(MarkType::kColumn == column->fMarkType);
|
|
this->writeString("-");
|
|
this->writeSpace();
|
|
this->descriptionOut(column);
|
|
this->lf(1);
|
|
}
|
|
}
|
|
commentStart = prop->fTerminator;
|
|
commentLen = (int) (def->fContentEnd - commentStart);
|
|
if ('\n' == commentStart[0] && '\n' == commentStart[1]) {
|
|
this->lf(2);
|
|
}
|
|
break;
|
|
default:
|
|
commentLen = (int) (prop->fStart - commentStart);
|
|
breakOut = true;
|
|
}
|
|
if (breakOut) {
|
|
break;
|
|
}
|
|
}
|
|
SkASSERT(commentLen > 0 && commentLen < 1500);
|
|
this->rewriteBlock(commentLen, commentStart, Phrase::kNo);
|
|
}
|
|
|
|
void IncludeWriter::enumHeaderOut(const RootDefinition* root,
|
|
const Definition& child) {
|
|
const Definition* enumDef = nullptr;
|
|
const char* bodyEnd = fDeferComment ? fDeferComment->fContentStart - 1 :
|
|
child.fContentStart;
|
|
this->writeBlockTrim((int) (bodyEnd - fStart), fStart); // may write nothing
|
|
this->lf(2);
|
|
fDeferComment = nullptr;
|
|
fStart = child.fContentStart;
|
|
const auto& nameDef = child.fTokens.front();
|
|
string fullName;
|
|
if (nullptr != nameDef.fContentEnd) {
|
|
TextParser enumClassCheck(&nameDef);
|
|
const char* start = enumClassCheck.fStart;
|
|
size_t len = (size_t) (enumClassCheck.fEnd - start);
|
|
bool enumClass = enumClassCheck.skipExact("class ");
|
|
if (enumClass) {
|
|
start = enumClassCheck.fChar;
|
|
const char* end = enumClassCheck.anyOf(" \n;{");
|
|
len = (size_t) (end - start);
|
|
}
|
|
string enumName(start, len);
|
|
if (enumClass) {
|
|
child.fChildren[0]->fName = enumName;
|
|
}
|
|
fullName = root->fName + "::" + enumName;
|
|
enumDef = root->find(enumName, RootDefinition::AllowParens::kNo);
|
|
if (!enumDef) {
|
|
enumDef = root->find(fullName, RootDefinition::AllowParens::kNo);
|
|
}
|
|
SkASSERT(enumDef);
|
|
// child[0] should be #Code comment starts at child[0].fTerminator
|
|
// though skip until #Code is found (in case there's a #ToDo, etc)
|
|
// child[1] should be #Const comment ends at child[1].fStart
|
|
// comment becomes enum header (if any)
|
|
} else {
|
|
string enumName(root->fName);
|
|
enumName += "::_anonymous";
|
|
if (fAnonymousEnumCount > 1) {
|
|
enumName += '_' + to_string(fAnonymousEnumCount);
|
|
}
|
|
enumDef = root->find(enumName, RootDefinition::AllowParens::kNo);
|
|
SkASSERT(enumDef);
|
|
++fAnonymousEnumCount;
|
|
}
|
|
Definition* codeBlock = nullptr;
|
|
const char* commentStart = nullptr;
|
|
bool wroteHeader = false;
|
|
bool lastAnchor = false;
|
|
SkDEBUGCODE(bool foundConst = false);
|
|
for (auto test : enumDef->fChildren) {
|
|
if (MarkType::kCode == test->fMarkType) {
|
|
SkASSERT(!codeBlock); // FIXME: check enum for correct order earlier
|
|
codeBlock = test;
|
|
commentStart = codeBlock->fTerminator;
|
|
continue;
|
|
}
|
|
if (!codeBlock) {
|
|
continue;
|
|
}
|
|
const char* commentEnd = test->fStart;
|
|
if (!wroteHeader &&
|
|
!this->contentFree((int) (commentEnd - commentStart), commentStart)) {
|
|
this->writeCommentHeader();
|
|
this->writeString("\\enum");
|
|
if (fullName.length() > 0) {
|
|
this->writeSpace();
|
|
this->writeString(fullName.c_str());
|
|
}
|
|
fIndent += 4;
|
|
this->lfcr();
|
|
wroteHeader = true;
|
|
}
|
|
if (lastAnchor) {
|
|
if (commentEnd - commentStart > 1) {
|
|
SkASSERT('\n' == commentStart[0]);
|
|
if (' ' == commentStart[1]) {
|
|
this->writeSpace();
|
|
}
|
|
}
|
|
lastAnchor = false;
|
|
}
|
|
this->rewriteBlock((int) (commentEnd - commentStart), commentStart, Phrase::kNo);
|
|
if (MarkType::kAnchor == test->fMarkType) {
|
|
commentStart = test->fContentStart;
|
|
commentEnd = test->fChildren[0]->fStart;
|
|
this->writeSpace();
|
|
this->rewriteBlock((int) (commentEnd - commentStart), commentStart, Phrase::kNo);
|
|
lastAnchor = true; // this->writeSpace();
|
|
}
|
|
commentStart = test->fTerminator;
|
|
if (MarkType::kConst == test->fMarkType) {
|
|
SkASSERT(codeBlock); // FIXME: check enum for correct order earlier
|
|
SkDEBUGCODE(foundConst = true);
|
|
break;
|
|
}
|
|
}
|
|
SkASSERT(codeBlock);
|
|
SkASSERT(foundConst);
|
|
if (wroteHeader) {
|
|
fIndent -= 4;
|
|
this->lfcr();
|
|
this->writeCommentTrailer();
|
|
}
|
|
Definition* braceHolder = child.fChildren[0];
|
|
if (KeyWord::kClass == braceHolder->fKeyWord) {
|
|
braceHolder = braceHolder->fChildren[0];
|
|
}
|
|
bodyEnd = braceHolder->fContentStart;
|
|
SkASSERT('{' == bodyEnd[0]);
|
|
++bodyEnd;
|
|
this->lfcr();
|
|
this->writeBlock((int) (bodyEnd - fStart), fStart); // write include "enum Name {"
|
|
fIndent += 4;
|
|
this->singleLF();
|
|
fStart = bodyEnd;
|
|
fEnumDef = enumDef;
|
|
}
|
|
|
|
void IncludeWriter::enumMembersOut(const RootDefinition* root, Definition& child) {
|
|
// iterate through include tokens and find how much remains for 1 line comments
|
|
// put ones that fit on same line, ones that are too big on preceding line?
|
|
const Definition* currentEnumItem = nullptr;
|
|
const char* commentStart = nullptr;
|
|
const char* lastEnd = nullptr;
|
|
int commentLen = 0;
|
|
enum class State {
|
|
kNoItem,
|
|
kItemName,
|
|
kItemValue,
|
|
kItemComment,
|
|
};
|
|
State state = State::kNoItem;
|
|
vector<IterState> iterStack;
|
|
iterStack.emplace_back(child.fTokens.begin(), child.fTokens.end());
|
|
IterState* iterState = &iterStack[0];
|
|
bool preprocessorWord = false;
|
|
const char* preprocessStart = nullptr;
|
|
const char* preprocessEnd = nullptr;
|
|
for (int onePast = 0; onePast < 2; onePast += iterState->fDefIter == iterState->fDefEnd) {
|
|
Definition* token = onePast ? nullptr : &*iterState->fDefIter++;
|
|
if (token && Definition::Type::kBracket == token->fType) {
|
|
if (Bracket::kSlashSlash == token->fBracket) {
|
|
fStart = token->fContentEnd;
|
|
continue; // ignore old inline comments
|
|
}
|
|
if (Bracket::kSlashStar == token->fBracket) {
|
|
fStart = token->fContentEnd + 1;
|
|
continue; // ignore old inline comments
|
|
}
|
|
if (Bracket::kPound == token->fBracket) { // preprocessor wraps member
|
|
preprocessStart = token->fContentStart;
|
|
if (KeyWord::kIf == token->fKeyWord || KeyWord::kIfdef == token->fKeyWord) {
|
|
iterStack.emplace_back(token->fTokens.begin(), token->fTokens.end());
|
|
iterState = &iterStack.back();
|
|
preprocessorWord = true;
|
|
} else if (KeyWord::kEndif == token->fKeyWord) {
|
|
iterStack.pop_back();
|
|
iterState = &iterStack.back();
|
|
preprocessEnd = token->fContentEnd;
|
|
} else {
|
|
SkASSERT(0); // incomplete
|
|
}
|
|
continue;
|
|
}
|
|
SkASSERT(0); // incomplete
|
|
}
|
|
if (token && Definition::Type::kWord != token->fType) {
|
|
SkASSERT(0); // incomplete
|
|
}
|
|
if (preprocessorWord) {
|
|
preprocessorWord = false;
|
|
preprocessEnd = token->fContentEnd;
|
|
continue;
|
|
}
|
|
if (token && State::kItemName == state) {
|
|
TextParser enumLine(token->fFileName, lastEnd,
|
|
token->fContentStart, token->fLineCount);
|
|
const char* end = enumLine.anyOf(",}=");
|
|
SkASSERT(end);
|
|
state = '=' == *end ? State::kItemValue : State::kItemComment;
|
|
if (State::kItemValue == state) { // write enum value
|
|
this->indentToColumn(fEnumItemValueTab);
|
|
this->writeString("=");
|
|
this->writeSpace();
|
|
lastEnd = token->fContentEnd;
|
|
this->writeBlock((int) (lastEnd - token->fContentStart),
|
|
token->fContentStart); // write const value if any
|
|
continue;
|
|
}
|
|
}
|
|
if (token && State::kItemValue == state) {
|
|
TextParser valueEnd(token->fFileName, lastEnd,
|
|
token->fContentStart, token->fLineCount);
|
|
const char* end = valueEnd.anyOf(",}");
|
|
if (!end) { // write expression continuation
|
|
if (' ' == lastEnd[0]) {
|
|
this->writeSpace();
|
|
}
|
|
this->writeBlock((int) (token->fContentEnd - lastEnd), lastEnd);
|
|
continue;
|
|
}
|
|
}
|
|
if (State::kNoItem != state) {
|
|
this->writeString(",");
|
|
SkASSERT(currentEnumItem);
|
|
if (currentEnumItem->fShort) {
|
|
this->indentToColumn(fEnumItemCommentTab);
|
|
this->writeString("//!<");
|
|
this->writeSpace();
|
|
this->rewriteBlock(commentLen, commentStart, Phrase::kNo);
|
|
}
|
|
if (onePast) {
|
|
fIndent -= 4;
|
|
}
|
|
this->lfcr();
|
|
if (preprocessStart) {
|
|
SkASSERT(preprocessEnd);
|
|
int saveIndent = fIndent;
|
|
fIndent = SkTMax(0, fIndent - 8);
|
|
this->lf(2);
|
|
this->writeBlock((int) (preprocessEnd - preprocessStart), preprocessStart);
|
|
this->lfcr();
|
|
fIndent = saveIndent;
|
|
preprocessStart = nullptr;
|
|
preprocessEnd = nullptr;
|
|
}
|
|
if (token && State::kItemValue == state) {
|
|
fStart = token->fContentStart;
|
|
}
|
|
state = State::kNoItem;
|
|
}
|
|
SkASSERT(State::kNoItem == state);
|
|
if (onePast) {
|
|
break;
|
|
}
|
|
SkASSERT(token);
|
|
string itemName = root->fName + "::";
|
|
if (KeyWord::kClass == child.fParent->fKeyWord) {
|
|
itemName += child.fParent->fName + "::";
|
|
}
|
|
itemName += string(token->fContentStart, (int) (token->fContentEnd - token->fContentStart));
|
|
for (auto& enumItem : fEnumDef->fChildren) {
|
|
if (MarkType::kConst != enumItem->fMarkType) {
|
|
continue;
|
|
}
|
|
if (itemName != enumItem->fName) {
|
|
continue;
|
|
}
|
|
currentEnumItem = enumItem;
|
|
break;
|
|
}
|
|
SkASSERT(currentEnumItem);
|
|
// if description fits, it goes after item
|
|
commentStart = currentEnumItem->fContentStart;
|
|
const char* commentEnd;
|
|
if (currentEnumItem->fChildren.size() > 0) {
|
|
commentEnd = currentEnumItem->fChildren[0]->fStart;
|
|
} else {
|
|
commentEnd = currentEnumItem->fContentEnd;
|
|
}
|
|
TextParser enumComment(fFileName, commentStart, commentEnd, currentEnumItem->fLineCount);
|
|
if (enumComment.skipToLineStart()) { // skip const value
|
|
commentStart = enumComment.fChar;
|
|
commentLen = (int) (commentEnd - commentStart);
|
|
} else {
|
|
const Definition* privateDef = currentEnumItem->fChildren[0];
|
|
SkASSERT(MarkType::kPrivate == privateDef->fMarkType);
|
|
commentStart = privateDef->fContentStart;
|
|
commentLen = (int) (privateDef->fContentEnd - privateDef->fContentStart);
|
|
}
|
|
// FIXME: may assert here if there's no const value
|
|
// should have detected and errored on that earlier when enum fContentStart was set
|
|
SkASSERT(commentLen > 0 && commentLen < 1000);
|
|
if (!currentEnumItem->fShort) {
|
|
this->writeCommentHeader();
|
|
fIndent += 4;
|
|
bool wroteLineFeed = Wrote::kLF ==
|
|
this->rewriteBlock(commentLen, commentStart, Phrase::kNo);
|
|
fIndent -= 4;
|
|
if (wroteLineFeed || fColumn > 100 - 3 /* space * / */ ) {
|
|
this->lfcr();
|
|
} else {
|
|
this->writeSpace();
|
|
}
|
|
this->writeCommentTrailer();
|
|
}
|
|
lastEnd = token->fContentEnd;
|
|
this->lfcr();
|
|
if (',' == fStart[0]) {
|
|
++fStart;
|
|
}
|
|
this->writeBlock((int) (lastEnd - fStart), fStart); // enum item name
|
|
fStart = token->fContentEnd;
|
|
state = State::kItemName;
|
|
}
|
|
}
|
|
|
|
void IncludeWriter::enumSizeItems(const Definition& child) {
|
|
enum class State {
|
|
kNoItem,
|
|
kItemName,
|
|
kItemValue,
|
|
kItemComment,
|
|
};
|
|
State state = State::kNoItem;
|
|
int longestName = 0;
|
|
int longestValue = 0;
|
|
int valueLen = 0;
|
|
const char* lastEnd = nullptr;
|
|
SkASSERT(child.fChildren.size() == 1 || child.fChildren.size() == 2);
|
|
auto brace = child.fChildren[0];
|
|
if (KeyWord::kClass == brace->fKeyWord) {
|
|
brace = brace->fChildren[0];
|
|
}
|
|
SkASSERT(Bracket::kBrace == brace->fBracket);
|
|
vector<IterState> iterStack;
|
|
iterStack.emplace_back(brace->fTokens.begin(), brace->fTokens.end());
|
|
IterState* iterState = &iterStack[0];
|
|
bool preprocessorWord = false;
|
|
while (iterState->fDefIter != iterState->fDefEnd) {
|
|
auto& token = *iterState->fDefIter++;
|
|
if (Definition::Type::kBracket == token.fType) {
|
|
if (Bracket::kSlashSlash == token.fBracket) {
|
|
continue; // ignore old inline comments
|
|
}
|
|
if (Bracket::kSlashStar == token.fBracket) {
|
|
continue; // ignore old inline comments
|
|
}
|
|
if (Bracket::kPound == token.fBracket) { // preprocessor wraps member
|
|
if (KeyWord::kIf == token.fKeyWord || KeyWord::kIfdef == token.fKeyWord) {
|
|
iterStack.emplace_back(token.fTokens.begin(), token.fTokens.end());
|
|
iterState = &iterStack.back();
|
|
preprocessorWord = true;
|
|
} else if (KeyWord::kEndif == token.fKeyWord) {
|
|
iterStack.pop_back();
|
|
iterState = &iterStack.back();
|
|
} else {
|
|
SkASSERT(0); // incomplete
|
|
}
|
|
continue;
|
|
}
|
|
SkASSERT(0); // incomplete
|
|
}
|
|
if (Definition::Type::kWord != token.fType) {
|
|
SkASSERT(0); // incomplete
|
|
}
|
|
if (preprocessorWord) {
|
|
preprocessorWord = false;
|
|
continue;
|
|
}
|
|
if (State::kItemName == state) {
|
|
TextParser enumLine(token.fFileName, lastEnd,
|
|
token.fContentStart, token.fLineCount);
|
|
const char* end = enumLine.anyOf(",}=");
|
|
SkASSERT(end);
|
|
state = '=' == *end ? State::kItemValue : State::kItemComment;
|
|
if (State::kItemValue == state) {
|
|
valueLen = (int) (token.fContentEnd - token.fContentStart);
|
|
lastEnd = token.fContentEnd;
|
|
continue;
|
|
}
|
|
}
|
|
if (State::kItemValue == state) {
|
|
TextParser valueEnd(token.fFileName, lastEnd,
|
|
token.fContentStart, token.fLineCount);
|
|
const char* end = valueEnd.anyOf(",}");
|
|
if (!end) { // write expression continuation
|
|
valueLen += (int) (token.fContentEnd - lastEnd);
|
|
continue;
|
|
}
|
|
}
|
|
if (State::kNoItem != state) {
|
|
longestValue = SkTMax(longestValue, valueLen);
|
|
state = State::kNoItem;
|
|
}
|
|
SkASSERT(State::kNoItem == state);
|
|
lastEnd = token.fContentEnd;
|
|
longestName = SkTMax(longestName, (int) (lastEnd - token.fContentStart));
|
|
state = State::kItemName;
|
|
}
|
|
if (State::kItemValue == state) {
|
|
longestValue = SkTMax(longestValue, valueLen);
|
|
}
|
|
fEnumItemValueTab = longestName + fIndent + 1 /* space before = */ ;
|
|
if (longestValue) {
|
|
longestValue += 3; /* = space , */
|
|
}
|
|
fEnumItemCommentTab = fEnumItemValueTab + longestValue + 1 /* space before //!< */ ;
|
|
// iterate through bmh children and see which comments fit on include lines
|
|
for (auto& enumItem : fEnumDef->fChildren) {
|
|
if (MarkType::kConst != enumItem->fMarkType) {
|
|
continue;
|
|
}
|
|
TextParser enumLine(enumItem);
|
|
enumLine.trimEnd();
|
|
enumLine.skipToLineStart(); // skip const value
|
|
const char* commentStart = enumLine.fChar;
|
|
enumLine.skipLine();
|
|
ptrdiff_t lineLen = enumLine.fChar - commentStart + 5 /* //!< space */ ;
|
|
if (!enumLine.eof()) {
|
|
enumLine.skipWhiteSpace();
|
|
}
|
|
enumItem->fShort = enumLine.eof() && fEnumItemCommentTab + lineLen < 100;
|
|
}
|
|
}
|
|
|
|
// walk children and output complete method doxygen description
|
|
void IncludeWriter::methodOut(const Definition* method, const Definition& child) {
|
|
fBmhMethod = method;
|
|
fMethodDef = &child;
|
|
fContinuation = nullptr;
|
|
fDeferComment = nullptr;
|
|
if (0 == fIndent) {
|
|
fIndent = 4;
|
|
}
|
|
this->writeCommentHeader();
|
|
fIndent += 4;
|
|
this->descriptionOut(method);
|
|
// compute indention column
|
|
size_t column = 0;
|
|
bool hasParmReturn = false;
|
|
for (auto methodPart : method->fChildren) {
|
|
if (MarkType::kParam == methodPart->fMarkType) {
|
|
column = SkTMax(column, methodPart->fName.length());
|
|
hasParmReturn = true;
|
|
} else if (MarkType::kReturn == methodPart->fMarkType) {
|
|
hasParmReturn = true;
|
|
}
|
|
}
|
|
if (hasParmReturn) {
|
|
this->lf(2);
|
|
column += fIndent + sizeof("@return ");
|
|
int saveIndent = fIndent;
|
|
for (auto methodPart : method->fChildren) {
|
|
const char* partStart = methodPart->fContentStart;
|
|
const char* partEnd = methodPart->fContentEnd;
|
|
if (MarkType::kParam == methodPart->fMarkType) {
|
|
this->writeString("@param");
|
|
this->writeSpace();
|
|
this->writeString(methodPart->fName.c_str());
|
|
} else if (MarkType::kReturn == methodPart->fMarkType) {
|
|
this->writeString("@return");
|
|
} else {
|
|
continue;
|
|
}
|
|
while ('\n' == partEnd[-1]) {
|
|
--partEnd;
|
|
}
|
|
while ('#' == partEnd[-1]) { // FIXME: so wrong; should not be before fContentEnd
|
|
--partEnd;
|
|
}
|
|
this->indentToColumn(column);
|
|
int partLen = (int) (partEnd - partStart);
|
|
// FIXME : detect this earlier; assert if #Return is empty
|
|
SkASSERT(partLen > 0 && partLen < 200);
|
|
fIndent = column;
|
|
this->rewriteBlock(partLen, partStart, Phrase::kYes);
|
|
fIndent = saveIndent;
|
|
this->lfcr();
|
|
}
|
|
} else {
|
|
this->lfcr();
|
|
}
|
|
fIndent -= 4;
|
|
this->lfcr();
|
|
this->writeCommentTrailer();
|
|
fBmhMethod = nullptr;
|
|
fMethodDef = nullptr;
|
|
fWroteMethod = true;
|
|
}
|
|
|
|
void IncludeWriter::structOut(const Definition* root, const Definition& child,
|
|
const char* commentStart, const char* commentEnd) {
|
|
this->writeCommentHeader();
|
|
this->writeString("\\");
|
|
SkASSERT(MarkType::kClass == child.fMarkType || MarkType::kStruct == child.fMarkType);
|
|
this->writeString(MarkType::kClass == child.fMarkType ? "class" : "struct");
|
|
this->writeSpace();
|
|
this->writeString(child.fName.c_str());
|
|
fIndent += 4;
|
|
this->lfcr();
|
|
this->rewriteBlock((int) (commentEnd - commentStart), commentStart, Phrase::kNo);
|
|
fIndent -= 4;
|
|
this->lfcr();
|
|
this->writeCommentTrailer();
|
|
}
|
|
|
|
Definition* IncludeWriter::structMemberOut(const Definition* memberStart, const Definition& child) {
|
|
const char* blockStart = !fWroteMethod && fDeferComment ? fLastComment->fContentEnd : fStart;
|
|
const char* blockEnd = fWroteMethod && fDeferComment ? fDeferComment->fStart - 1 :
|
|
memberStart->fStart;
|
|
this->writeBlockTrim((int) (blockEnd - blockStart), blockStart);
|
|
fWroteMethod = false;
|
|
const char* commentStart = nullptr;
|
|
ptrdiff_t commentLen = 0;
|
|
string name(child.fContentStart, (int) (child.fContentEnd - child.fContentStart));
|
|
bool isShort;
|
|
Definition* commentBlock = nullptr;
|
|
for (auto memberDef : fStructDef->fChildren) {
|
|
if (memberDef->fName.length() - name.length() == memberDef->fName.find(name)) {
|
|
commentStart = memberDef->fContentStart;
|
|
commentLen = memberDef->fContentEnd - commentStart;
|
|
isShort = memberDef->fShort;
|
|
commentBlock = memberDef;
|
|
SkASSERT(!isShort || memberDef->fChildren.size() == 0);
|
|
break;
|
|
}
|
|
}
|
|
if (!isShort) {
|
|
this->writeCommentHeader();
|
|
bool wroteLineFeed = false;
|
|
fIndent += 4;
|
|
for (auto child : commentBlock->fChildren) {
|
|
commentLen = child->fStart - commentStart;
|
|
wroteLineFeed |= Wrote::kLF == this->rewriteBlock(commentLen, commentStart, Phrase::kNo);
|
|
if (MarkType::kFormula == child->fMarkType) {
|
|
this->writeSpace();
|
|
this->writeBlock((int) (child->fContentEnd - child->fContentStart),
|
|
child->fContentStart);
|
|
}
|
|
commentStart = child->fTerminator;
|
|
}
|
|
commentLen = commentBlock->fContentEnd - commentStart;
|
|
wroteLineFeed |= Wrote::kLF == this->rewriteBlock(commentLen, commentStart, Phrase::kNo);
|
|
fIndent -= 4;
|
|
if (wroteLineFeed || fColumn > 100 - 3 /* space * / */ ) {
|
|
this->lfcr();
|
|
} else {
|
|
this->writeSpace();
|
|
}
|
|
this->writeCommentTrailer();
|
|
}
|
|
this->lfcr();
|
|
this->writeBlock((int) (child.fStart - memberStart->fContentStart),
|
|
memberStart->fContentStart);
|
|
this->indentToColumn(fStructMemberTab);
|
|
this->writeString(name.c_str());
|
|
auto tokenIter = child.fParent->fTokens.begin();
|
|
std::advance(tokenIter, child.fParentIndex + 1);
|
|
Definition* valueStart = &*tokenIter;
|
|
while (Definition::Type::kPunctuation != tokenIter->fType) {
|
|
std::advance(tokenIter, 1);
|
|
SkASSERT(child.fParent->fTokens.end() != tokenIter);
|
|
}
|
|
Definition* valueEnd = &*tokenIter;
|
|
if (valueStart != valueEnd) {
|
|
this->indentToColumn(fStructValueTab);
|
|
this->writeString("=");
|
|
this->writeSpace();
|
|
this->writeBlock((int) (valueEnd->fStart - valueStart->fContentStart),
|
|
valueStart->fContentStart);
|
|
}
|
|
this->writeString(";");
|
|
if (isShort) {
|
|
this->indentToColumn(fStructCommentTab);
|
|
this->writeString("//!<");
|
|
this->writeSpace();
|
|
this->rewriteBlock(commentLen, commentStart, Phrase::kNo);
|
|
this->lfcr();
|
|
}
|
|
return valueEnd;
|
|
}
|
|
|
|
void IncludeWriter::structSizeMembers(const Definition& child) {
|
|
int longestType = 0;
|
|
Definition* typeStart = nullptr;
|
|
int longestName = 0;
|
|
int longestValue = 0;
|
|
SkASSERT(child.fChildren.size() == 1 || child.fChildren.size() == 2);
|
|
bool inEnum = false;
|
|
bool inMethod = false;
|
|
bool inMember = false;
|
|
auto brace = child.fChildren[0];
|
|
SkASSERT(Bracket::kBrace == brace->fBracket);
|
|
for (auto& token : brace->fTokens) {
|
|
if (Definition::Type::kBracket == token.fType) {
|
|
if (Bracket::kSlashSlash == token.fBracket) {
|
|
continue; // ignore old inline comments
|
|
}
|
|
if (Bracket::kSlashStar == token.fBracket) {
|
|
continue; // ignore old inline comments
|
|
}
|
|
if (Bracket::kParen == token.fBracket) {
|
|
if (inMethod) {
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
SkASSERT(0); // incomplete
|
|
}
|
|
if (Definition::Type::kKeyWord == token.fType) {
|
|
switch (token.fKeyWord) {
|
|
case KeyWord::kEnum:
|
|
inEnum = true;
|
|
break;
|
|
case KeyWord::kConst:
|
|
case KeyWord::kConstExpr:
|
|
case KeyWord::kStatic:
|
|
case KeyWord::kInt:
|
|
case KeyWord::kUint8_t:
|
|
case KeyWord::kUint16_t:
|
|
case KeyWord::kUint32_t:
|
|
case KeyWord::kUint64_t:
|
|
case KeyWord::kSize_t:
|
|
case KeyWord::kFloat:
|
|
case KeyWord::kBool:
|
|
case KeyWord::kVoid:
|
|
if (!typeStart) {
|
|
typeStart = &token;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
continue;
|
|
}
|
|
if (Definition::Type::kPunctuation == token.fType) {
|
|
if (inEnum) {
|
|
SkASSERT(Punctuation::kSemicolon == token.fPunctuation);
|
|
inEnum = false;
|
|
}
|
|
if (inMethod) {
|
|
if (Punctuation::kColon == token.fPunctuation) {
|
|
inMethod = false;
|
|
} else if (Punctuation::kLeftBrace == token.fPunctuation) {
|
|
inMethod = false;
|
|
} else if (Punctuation::kSemicolon == token.fPunctuation) {
|
|
inMethod = false;
|
|
} else {
|
|
SkASSERT(0); // incomplete
|
|
}
|
|
}
|
|
if (inMember) {
|
|
SkASSERT(Punctuation::kSemicolon == token.fPunctuation);
|
|
typeStart = nullptr;
|
|
inMember = false;
|
|
}
|
|
continue;
|
|
}
|
|
if (Definition::Type::kWord != token.fType) {
|
|
SkASSERT(0); // incomplete
|
|
}
|
|
if (MarkType::kMember == token.fMarkType) {
|
|
TextParser typeStr(token.fFileName, typeStart->fContentStart, token.fContentStart,
|
|
token.fLineCount);
|
|
typeStr.trimEnd();
|
|
longestType = SkTMax(longestType, (int) (typeStr.fEnd - typeStr.fStart));
|
|
longestName = SkTMax(longestName, (int) (token.fContentEnd - token.fContentStart));
|
|
typeStart->fMemberStart = true;
|
|
inMember = true;
|
|
continue;
|
|
}
|
|
if (MarkType::kMethod == token.fMarkType) {
|
|
inMethod = true;
|
|
continue;
|
|
}
|
|
SkASSERT(MarkType::kNone == token.fMarkType);
|
|
if (typeStart) {
|
|
if (inMember) {
|
|
longestValue =
|
|
SkTMax(longestValue, (int) (token.fContentEnd - token.fContentStart));
|
|
}
|
|
} else {
|
|
typeStart = &token;
|
|
}
|
|
}
|
|
fStructMemberTab = longestType + fIndent + 1 /* space before name */ ;
|
|
fStructValueTab = fStructMemberTab + longestName + 2 /* space ; */ ;
|
|
fStructCommentTab = fStructValueTab;
|
|
if (longestValue) {
|
|
fStructCommentTab += longestValue + 3 /* space = space */ ;
|
|
fStructValueTab -= 1 /* ; */ ;
|
|
}
|
|
// iterate through bmh children and see which comments fit on include lines
|
|
for (auto& member : fStructDef->fChildren) {
|
|
if (MarkType::kMember != member->fMarkType) {
|
|
continue;
|
|
}
|
|
TextParser memberLine(member);
|
|
memberLine.trimEnd();
|
|
const char* commentStart = memberLine.fChar;
|
|
memberLine.skipLine();
|
|
ptrdiff_t lineLen = memberLine.fChar - commentStart + 5 /* //!< space */ ;
|
|
if (!memberLine.eof()) {
|
|
memberLine.skipWhiteSpace();
|
|
}
|
|
member->fShort = memberLine.eof() && fStructCommentTab + lineLen < 100;
|
|
}
|
|
}
|
|
|
|
bool IncludeWriter::populate(Definition* def, ParentPair* prevPair, RootDefinition* root) {
|
|
ParentPair pair = { def, prevPair };
|
|
// write bulk of original include up to class, method, enum, etc., excepting preceding comment
|
|
// find associated bmh object
|
|
// write any associated comments in Doxygen form
|
|
// skip include comment
|
|
// if there is a series of same named methods, write one set of comments, then write all methods
|
|
string methodName;
|
|
const Definition* method;
|
|
const Definition* clonedMethod = nullptr;
|
|
const Definition* memberStart = nullptr;
|
|
const Definition* memberEnd = nullptr;
|
|
fContinuation = nullptr;
|
|
bool inStruct = false;
|
|
bool inConstructor = false;
|
|
bool inInline = false;
|
|
for (auto& child : def->fTokens) {
|
|
if (memberEnd) {
|
|
if (memberEnd != &child) {
|
|
continue;
|
|
}
|
|
fStart = child.fContentStart + 1;
|
|
memberEnd = nullptr;
|
|
}
|
|
if (child.fPrivate) {
|
|
if (MarkType::kMethod == child.fMarkType) {
|
|
inInline = true;
|
|
}
|
|
continue;
|
|
}
|
|
if (inInline) {
|
|
if (Definition::Type::kKeyWord == child.fType) {
|
|
SkASSERT(MarkType::kMethod != child.fMarkType);
|
|
continue;
|
|
}
|
|
if (Definition::Type::kPunctuation == child.fType) {
|
|
if (Punctuation::kLeftBrace == child.fPunctuation) {
|
|
inInline = false;
|
|
} else {
|
|
SkASSERT(Punctuation::kAsterisk == child.fPunctuation);
|
|
}
|
|
continue;
|
|
}
|
|
if (Definition::Type::kWord == child.fType) {
|
|
string name(child.fContentStart, child.fContentEnd - child.fContentStart);
|
|
SkASSERT(string::npos != name.find("::"));
|
|
continue;
|
|
}
|
|
if (Definition::Type::kBracket == child.fType) {
|
|
SkASSERT(Bracket::kParen == child.fBracket);
|
|
continue;
|
|
}
|
|
}
|
|
if (fContinuation) {
|
|
if (Definition::Type::kKeyWord == child.fType) {
|
|
if (KeyWord::kFriend == child.fKeyWord ||
|
|
KeyWord::kSK_API == child.fKeyWord) {
|
|
continue;
|
|
}
|
|
const IncludeKey& includeKey = kKeyWords[(int) child.fKeyWord];
|
|
if (KeyProperty::kNumber == includeKey.fProperty) {
|
|
continue;
|
|
}
|
|
}
|
|
if (Definition::Type::kBracket == child.fType && Bracket::kParen == child.fBracket) {
|
|
if (!clonedMethod) {
|
|
if (inConstructor) {
|
|
fContinuation = child.fContentStart;
|
|
}
|
|
continue;
|
|
}
|
|
int alternate = 1;
|
|
ptrdiff_t childLen = child.fContentEnd - child.fContentStart;
|
|
SkASSERT(')' == child.fContentStart[childLen]);
|
|
++childLen;
|
|
do {
|
|
TextParser params(clonedMethod->fFileName, clonedMethod->fStart,
|
|
clonedMethod->fContentStart, clonedMethod->fLineCount);
|
|
params.skipToEndBracket('(');
|
|
if (params.startsWith(child.fContentStart, childLen)) {
|
|
this->methodOut(clonedMethod, child);
|
|
break;
|
|
}
|
|
++alternate;
|
|
string alternateMethod = methodName + '_' + to_string(alternate);
|
|
clonedMethod = root->find(alternateMethod, RootDefinition::AllowParens::kNo);
|
|
} while (clonedMethod);
|
|
if (!clonedMethod) {
|
|
return this->reportError<bool>("cloned method not found");
|
|
}
|
|
clonedMethod = nullptr;
|
|
continue;
|
|
}
|
|
if (Definition::Type::kWord == child.fType) {
|
|
if (clonedMethod) {
|
|
continue;
|
|
}
|
|
size_t len = (size_t) (child.fContentEnd - child.fContentStart);
|
|
const char operatorStr[] = "operator";
|
|
size_t operatorLen = sizeof(operatorStr) - 1;
|
|
if (len >= operatorLen && !strncmp(child.fContentStart, operatorStr, operatorLen)) {
|
|
fContinuation = child.fContentEnd;
|
|
continue;
|
|
}
|
|
}
|
|
if (Definition::Type::kPunctuation == child.fType &&
|
|
(Punctuation::kSemicolon == child.fPunctuation ||
|
|
Punctuation::kLeftBrace == child.fPunctuation ||
|
|
(Punctuation::kColon == child.fPunctuation && inConstructor))) {
|
|
SkASSERT(fContinuation[0] == '(');
|
|
const char* continueEnd = child.fContentStart;
|
|
while (continueEnd > fContinuation && isspace(continueEnd[-1])) {
|
|
--continueEnd;
|
|
}
|
|
methodName += string(fContinuation, continueEnd - fContinuation);
|
|
method = root->find(methodName, RootDefinition::AllowParens::kNo);
|
|
if (!method) {
|
|
fLineCount = child.fLineCount;
|
|
return this->reportError<bool>("method not found");
|
|
}
|
|
this->methodOut(method, child);
|
|
continue;
|
|
}
|
|
if (Definition::Type::kPunctuation == child.fType &&
|
|
Punctuation::kAsterisk == child.fPunctuation &&
|
|
clonedMethod) {
|
|
continue;
|
|
}
|
|
if (inConstructor) {
|
|
continue;
|
|
}
|
|
method = root->find(methodName + "()", RootDefinition::AllowParens::kNo);
|
|
if (method && MarkType::kDefinedBy == method->fMarkType) {
|
|
method = method->fParent;
|
|
}
|
|
if (method) {
|
|
if (method->fCloned) {
|
|
clonedMethod = method;
|
|
continue;
|
|
}
|
|
this->methodOut(method, child);
|
|
continue;
|
|
}
|
|
fLineCount = child.fLineCount;
|
|
return this->reportError<bool>("method not found");
|
|
}
|
|
if (Bracket::kSlashSlash == child.fBracket || Bracket::kSlashStar == child.fBracket) {
|
|
if (!fDeferComment) {
|
|
fDeferComment = &child;
|
|
}
|
|
fLastComment = &child;
|
|
continue;
|
|
}
|
|
if (MarkType::kMethod == child.fMarkType) {
|
|
if (this->internalName(child)) {
|
|
continue;
|
|
}
|
|
const char* bodyEnd = fDeferComment ? fDeferComment->fContentStart - 1 :
|
|
child.fContentStart;
|
|
// FIXME: roll end-trimming into writeBlockTrim call
|
|
while (fStart < bodyEnd && ' ' >= bodyEnd[-1]) {
|
|
--bodyEnd;
|
|
}
|
|
int blockSize = (int) (bodyEnd - fStart);
|
|
if (blockSize) {
|
|
this->writeBlock(blockSize, fStart);
|
|
}
|
|
fStart = child.fContentStart;
|
|
methodName = root->fName + "::" + child.fName;
|
|
inConstructor = root->fName == child.fName;
|
|
fContinuation = child.fContentEnd;
|
|
method = root->find(methodName, RootDefinition::AllowParens::kNo);
|
|
if (!method) {
|
|
continue;
|
|
}
|
|
if (method->fCloned) {
|
|
clonedMethod = method;
|
|
continue;
|
|
}
|
|
this->methodOut(method, child);
|
|
if (fAttrDeprecated) {
|
|
fStart = fAttrDeprecated->fContentStart;
|
|
fAttrDeprecated = nullptr;
|
|
}
|
|
continue;
|
|
}
|
|
if (Definition::Type::kKeyWord == child.fType) {
|
|
const Definition* structDef = nullptr;
|
|
switch (child.fKeyWord) {
|
|
case KeyWord::kStruct:
|
|
case KeyWord::kClass:
|
|
// if struct contains members, compute their name and comment tabs
|
|
if (child.fChildren.size() > 0) {
|
|
const ParentPair* testPair = &pair;
|
|
while ((testPair = testPair->fPrev)) {
|
|
if (KeyWord::kClass == testPair->fParent->fKeyWord) {
|
|
inStruct = fInStruct = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (fInStruct) {
|
|
fIndent += 4;
|
|
fStructDef = root->find(child.fName, RootDefinition::AllowParens::kNo);
|
|
if (nullptr == structDef) {
|
|
fStructDef = root->find(root->fName + "::" + child.fName,
|
|
RootDefinition::AllowParens::kNo);
|
|
}
|
|
this->structSizeMembers(child);
|
|
fIndent -= 4;
|
|
}
|
|
if (child.fChildren.size() > 0) {
|
|
const char* bodyEnd = fDeferComment ? fDeferComment->fContentStart - 1 :
|
|
child.fContentStart;
|
|
this->writeBlockTrim((int) (bodyEnd - fStart), fStart);
|
|
fStart = child.fContentStart;
|
|
if (child.fName == root->fName) {
|
|
if (Definition* parent = root->fParent) {
|
|
if (MarkType::kTopic == parent->fMarkType ||
|
|
MarkType::kSubtopic == parent->fMarkType) {
|
|
const char* commentStart = root->fContentStart;
|
|
const char* commentEnd = root->fChildren[0]->fStart;
|
|
this->structOut(root, *root, commentStart, commentEnd);
|
|
} else {
|
|
SkASSERT(0); // incomplete
|
|
}
|
|
} else {
|
|
SkASSERT(0); // incomplete
|
|
}
|
|
} else {
|
|
structDef = root->find(child.fName, RootDefinition::AllowParens::kNo);
|
|
if (nullptr == structDef) {
|
|
structDef = root->find(root->fName + "::" + child.fName,
|
|
RootDefinition::AllowParens::kNo);
|
|
}
|
|
if (!structDef) {
|
|
this->lf(2);
|
|
fIndent = 0;
|
|
this->writeBlock((int) (fStart - bodyEnd), bodyEnd);
|
|
this->lfcr();
|
|
continue;
|
|
}
|
|
Definition* codeBlock = nullptr;
|
|
SkDEBUGCODE(Definition* nextBlock = nullptr);
|
|
for (auto test : structDef->fChildren) {
|
|
if (MarkType::kCode == test->fMarkType) {
|
|
SkASSERT(!codeBlock); // FIXME: check enum for correct order earlier
|
|
codeBlock = test;
|
|
continue;
|
|
}
|
|
if (codeBlock) {
|
|
SkDEBUGCODE(nextBlock = test);
|
|
break;
|
|
}
|
|
}
|
|
// FIXME: trigger error earlier if inner #Struct or #Class is missing #Code
|
|
SkASSERT(nextBlock); // FIXME: check enum for correct order earlier
|
|
const char* commentStart = structDef->fContentStart;
|
|
const char* commentEnd = codeBlock->fStart;
|
|
this->structOut(root, *structDef, commentStart, commentEnd);
|
|
}
|
|
fDeferComment = nullptr;
|
|
} else {
|
|
; // empty forward reference, nothing to do here
|
|
}
|
|
break;
|
|
case KeyWord::kEnum: {
|
|
fInEnum = true;
|
|
this->enumHeaderOut(root, child);
|
|
this->enumSizeItems(child);
|
|
} break;
|
|
case KeyWord::kConst:
|
|
case KeyWord::kConstExpr:
|
|
case KeyWord::kStatic:
|
|
case KeyWord::kInt:
|
|
case KeyWord::kUint8_t:
|
|
case KeyWord::kUint16_t:
|
|
case KeyWord::kUint32_t:
|
|
case KeyWord::kUint64_t:
|
|
case KeyWord::kUnsigned:
|
|
case KeyWord::kSize_t:
|
|
case KeyWord::kFloat:
|
|
case KeyWord::kBool:
|
|
case KeyWord::kVoid:
|
|
if (!memberStart) {
|
|
memberStart = &child;
|
|
}
|
|
break;
|
|
case KeyWord::kPublic:
|
|
case KeyWord::kPrivate:
|
|
case KeyWord::kProtected:
|
|
case KeyWord::kFriend:
|
|
case KeyWord::kInline:
|
|
case KeyWord::kSK_API:
|
|
case KeyWord::kTypedef:
|
|
break;
|
|
default:
|
|
SkASSERT(0);
|
|
}
|
|
if (structDef) {
|
|
TextParser structName(&child);
|
|
SkAssertResult(structName.skipToEndBracket('{'));
|
|
fStart = structName.fChar + 1;
|
|
this->writeBlock((int) (fStart - child.fStart), child.fStart);
|
|
this->lf(2);
|
|
fIndent += 4;
|
|
if (!this->populate(&child, &pair, const_cast<Definition*>(structDef)->asRoot())) {
|
|
return false;
|
|
}
|
|
// output any remaining definitions at current indent level
|
|
const char* structEnd = child.fContentEnd;
|
|
SkAssertResult('}' == structEnd[-1]);
|
|
--structEnd;
|
|
this->writeBlockTrim((int) (structEnd - fStart), fStart);
|
|
this->lf(2);
|
|
fStart = structEnd;
|
|
fIndent -= 4;
|
|
fContinuation = nullptr;
|
|
fDeferComment = nullptr;
|
|
} else if (KeyWord::kUint8_t == child.fKeyWord) {
|
|
continue;
|
|
} else {
|
|
if (fInEnum && KeyWord::kClass == child.fChildren[0]->fKeyWord) {
|
|
if (!this->populate(child.fChildren[0], &pair, root)) {
|
|
return false;
|
|
}
|
|
} else if (!this->populate(&child, &pair, root)) {
|
|
return false;
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
if (Definition::Type::kBracket == child.fType) {
|
|
if (KeyWord::kEnum == child.fParent->fKeyWord ||
|
|
(KeyWord::kClass == child.fParent->fKeyWord && child.fParent->fParent &&
|
|
KeyWord::kEnum == child.fParent->fParent->fKeyWord)) {
|
|
SkASSERT(Bracket::kBrace == child.fBracket);
|
|
this->enumMembersOut(root, child);
|
|
this->writeString("};");
|
|
this->lf(2);
|
|
fStart = child.fParent->fContentEnd;
|
|
SkASSERT(';' == fStart[0]);
|
|
++fStart;
|
|
fDeferComment = nullptr;
|
|
fInEnum = false;
|
|
continue;
|
|
}
|
|
if (fAttrDeprecated) {
|
|
continue;
|
|
}
|
|
fDeferComment = nullptr;
|
|
if (!this->populate(&child, &pair, root)) {
|
|
return false;
|
|
}
|
|
continue;
|
|
}
|
|
if (Definition::Type::kWord == child.fType) {
|
|
if (MarkType::kMember == child.fMarkType) {
|
|
if (!memberStart) {
|
|
auto iter = def->fTokens.begin();
|
|
std::advance(iter, child.fParentIndex - 1);
|
|
memberStart = &*iter;
|
|
if (!fStructDef) {
|
|
SkASSERT(KeyWord::kStruct == def->fParent->fKeyWord);
|
|
fStructDef = def->fParent;
|
|
this->structSizeMembers(*fStructDef);
|
|
}
|
|
}
|
|
memberEnd = this->structMemberOut(memberStart, child);
|
|
fStart = child.fContentEnd + 1;
|
|
fDeferComment = nullptr;
|
|
}
|
|
if (child.fMemberStart) {
|
|
memberStart = &child;
|
|
}
|
|
const char attrDeprecated[] = "SK_ATTR_DEPRECATED";
|
|
const size_t attrDeprecatedLen = sizeof(attrDeprecated) - 1;
|
|
if (attrDeprecatedLen == child.fContentEnd - child.fContentStart &&
|
|
!strncmp(attrDeprecated, child.fStart, attrDeprecatedLen)) {
|
|
fAttrDeprecated = &child;
|
|
}
|
|
continue;
|
|
}
|
|
if (Definition::Type::kPunctuation == child.fType) {
|
|
if (Punctuation::kSemicolon == child.fPunctuation) {
|
|
memberStart = nullptr;
|
|
if (inStruct) {
|
|
fInStruct = false;
|
|
}
|
|
continue;
|
|
}
|
|
if (Punctuation::kLeftBrace == child.fPunctuation ||
|
|
Punctuation::kColon == child.fPunctuation ||
|
|
Punctuation::kAsterisk == child.fPunctuation
|
|
) {
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool IncludeWriter::populate(BmhParser& bmhParser) {
|
|
bool allPassed = true;
|
|
for (auto& includeMapper : fIncludeMap) {
|
|
size_t lastSlash = includeMapper.first.rfind('/');
|
|
if (string::npos == lastSlash) {
|
|
lastSlash = includeMapper.first.rfind('\\');
|
|
}
|
|
if (string::npos == lastSlash || lastSlash >= includeMapper.first.length() - 1) {
|
|
return this->reportError<bool>("malformed include name");
|
|
}
|
|
string fileName = includeMapper.first.substr(lastSlash + 1);
|
|
if (".h" != fileName.substr(fileName.length() - 2)) {
|
|
return this->reportError<bool>("expected fileName.h");
|
|
}
|
|
string skClassName = fileName.substr(0, fileName.length() - 2);
|
|
fOut = fopen(fileName.c_str(), "wb");
|
|
if (!fOut) {
|
|
SkDebugf("could not open output file %s\n", fileName.c_str());
|
|
return false;
|
|
}
|
|
if (bmhParser.fClassMap.end() == bmhParser.fClassMap.find(skClassName)) {
|
|
return this->reportError<bool>("could not find bmh class");
|
|
}
|
|
fBmhParser = &bmhParser;
|
|
RootDefinition* root = &bmhParser.fClassMap[skClassName];
|
|
fRootTopic = root->fParent;
|
|
root->clearVisited();
|
|
fStart = includeMapper.second.fContentStart;
|
|
fEnd = includeMapper.second.fContentEnd;
|
|
allPassed &= this->populate(&includeMapper.second, nullptr, root);
|
|
this->writeBlock((int) (fEnd - fStart), fStart);
|
|
fIndent = 0;
|
|
this->lfcr();
|
|
this->writePending();
|
|
fclose(fOut);
|
|
SkDebugf("wrote %s\n", fileName.c_str());
|
|
}
|
|
return allPassed;
|
|
}
|
|
|
|
// change Xxx_Xxx to xxx xxx
|
|
static string ConvertRef(const string str, bool first) {
|
|
string substitute;
|
|
for (char c : str) {
|
|
if ('_' == c) {
|
|
c = ' '; // change Xxx_Xxx to xxx xxx
|
|
} else if (isupper(c) && !first) {
|
|
c = tolower(c);
|
|
}
|
|
substitute += c;
|
|
first = false;
|
|
}
|
|
return substitute;
|
|
}
|
|
|
|
string IncludeWriter::resolveMethod(const char* start, const char* end, bool first) {
|
|
string methodname(start, end - start);
|
|
if (string::npos != methodname.find("()")) {
|
|
return "";
|
|
}
|
|
string substitute;
|
|
auto rootDefIter = fBmhParser->fMethodMap.find(methodname);
|
|
if (fBmhParser->fMethodMap.end() != rootDefIter) {
|
|
substitute = methodname + "()";
|
|
} else {
|
|
RootDefinition* parent = nullptr;
|
|
for (auto candidate : fRootTopic->fChildren) {
|
|
if (MarkType::kClass == candidate->fMarkType
|
|
|| MarkType::kStruct == candidate->fMarkType) {
|
|
parent = candidate->asRoot();
|
|
break;
|
|
}
|
|
}
|
|
SkASSERT(parent);
|
|
auto defRef = parent->find(parent->fName + "::" + methodname,
|
|
RootDefinition::AllowParens::kNo);
|
|
if (defRef && MarkType::kMethod == defRef->fMarkType) {
|
|
substitute = methodname + "()";
|
|
}
|
|
}
|
|
if (fMethodDef && methodname == fMethodDef->fName) {
|
|
TextParser report(fBmhMethod);
|
|
report.reportError("method should not include references to itself");
|
|
return "";
|
|
}
|
|
if (fBmhMethod) {
|
|
for (auto child : fBmhMethod->fChildren) {
|
|
if (MarkType::kParam != child->fMarkType) {
|
|
continue;
|
|
}
|
|
if (methodname == child->fName) {
|
|
return "";
|
|
}
|
|
}
|
|
}
|
|
return substitute;
|
|
}
|
|
|
|
string IncludeWriter::resolveRef(const char* start, const char* end, bool first,
|
|
RefType* refType) {
|
|
// look up Xxx_Xxx
|
|
string undername(start, end - start);
|
|
for (const auto& external : fBmhParser->fExternals) {
|
|
if (external.fName == undername) {
|
|
*refType = RefType::kExternal;
|
|
return external.fName;
|
|
}
|
|
}
|
|
*refType = RefType::kNormal;
|
|
SkASSERT(string::npos == undername.find(' '));
|
|
const Definition* rootDef = nullptr;
|
|
{
|
|
auto rootDefIter = fBmhParser->fTopicMap.find(undername);
|
|
if (fBmhParser->fTopicMap.end() != rootDefIter) {
|
|
rootDef = rootDefIter->second;
|
|
} else {
|
|
string prefixedName = fRootTopic->fName + '_' + undername;
|
|
rootDefIter = fBmhParser->fTopicMap.find(prefixedName);
|
|
if (fBmhParser->fTopicMap.end() != rootDefIter) {
|
|
rootDef = rootDefIter->second;
|
|
} else if (fStructDef) {
|
|
string localPrefix = fStructDef->fFiddle + '_' + undername;
|
|
rootDefIter = fBmhParser->fTopicMap.find(localPrefix);
|
|
if (fBmhParser->fTopicMap.end() != rootDefIter) {
|
|
rootDef = rootDefIter->second;
|
|
}
|
|
} else {
|
|
auto aliasIter = fBmhParser->fAliasMap.find(undername);
|
|
if (fBmhParser->fAliasMap.end() != aliasIter) {
|
|
rootDef = aliasIter->second->fParent;
|
|
} else if (!first) {
|
|
SkDebugf("unfound: %s\n", undername.c_str());
|
|
this->reportError("reference unfound");
|
|
return "";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
string substitute;
|
|
if (rootDef) {
|
|
for (auto child : rootDef->fChildren) {
|
|
if (MarkType::kSubstitute == child->fMarkType) {
|
|
substitute = string(child->fContentStart,
|
|
(int) (child->fContentEnd - child->fContentStart));
|
|
break;
|
|
}
|
|
}
|
|
if (!substitute.length()) {
|
|
for (auto child : rootDef->fChildren) {
|
|
if (MarkType::kClass == child->fMarkType ||
|
|
MarkType::kStruct == child->fMarkType ||
|
|
(MarkType::kEnum == child->fMarkType && !child->fAnonymous) ||
|
|
MarkType::kEnumClass == child->fMarkType) {
|
|
substitute = child->fName;
|
|
if (MarkType::kEnum == child->fMarkType && fInEnum) {
|
|
size_t parentClassEnd = substitute.find("::");
|
|
SkASSERT(string::npos != parentClassEnd);
|
|
substitute = substitute.substr(parentClassEnd + 2);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!substitute.length()) {
|
|
auto parent = rootDef->fParent;
|
|
if (parent) {
|
|
if (MarkType::kClass == parent->fMarkType ||
|
|
MarkType::kStruct == parent->fMarkType ||
|
|
(MarkType::kEnum == parent->fMarkType && !parent->fAnonymous) ||
|
|
MarkType::kEnumClass == parent->fMarkType) {
|
|
if (parent->fParent != fRootTopic) {
|
|
substitute = parent->fName;
|
|
size_t under = undername.find('_');
|
|
SkASSERT(string::npos != under);
|
|
string secondHalf(&undername[under], (size_t) (undername.length() - under));
|
|
substitute += ConvertRef(secondHalf, false);
|
|
} else {
|
|
substitute += ConvertRef(undername, first);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Ensure first word after period is capitalized if substitute is lower cased.
|
|
if (first && isupper(start[0]) && substitute.length() > 0 && islower(substitute[0])) {
|
|
substitute[0] = start[0];
|
|
}
|
|
return substitute;
|
|
}
|
|
|
|
int IncludeWriter::lookupMethod(const PunctuationState punctuation, const Word word,
|
|
const int lastSpace, const int run, int lastWrite, const char* data,
|
|
bool hasIndirection) {
|
|
int wordStart = lastSpace;
|
|
while (' ' >= data[wordStart]) {
|
|
++wordStart;
|
|
}
|
|
const int wordEnd = PunctuationState::kDelimiter == punctuation ||
|
|
PunctuationState::kPeriod == punctuation ? run - 1 : run;
|
|
string temp;
|
|
if (hasIndirection && '(' != data[wordEnd - 1] && ')' != data[wordEnd - 1]) {
|
|
// FIXME: hard-coded to assume a.b or a->b is a.b() or a->b().
|
|
// need to check class a for member b to see if this is so
|
|
TextParser parser(fFileName, &data[wordStart], &data[wordEnd], fLineCount);
|
|
const char* indirection = parser.anyOf(".>");
|
|
if (&data[wordEnd] <= &indirection[2] || 'f' != indirection[1] ||
|
|
!isupper(indirection[2])) {
|
|
temp = string(&data[wordStart], wordEnd - wordStart) + "()";
|
|
}
|
|
} else {
|
|
temp = this->resolveMethod(&data[wordStart], &data[wordEnd], Word::kFirst == word);
|
|
}
|
|
if (temp.length()) {
|
|
if (wordStart > lastWrite) {
|
|
SkASSERT(data[wordStart - 1] >= ' ');
|
|
if (' ' == data[lastWrite]) {
|
|
this->writeSpace();
|
|
}
|
|
this->writeBlockTrim(wordStart - lastWrite, &data[lastWrite]);
|
|
if (' ' == data[wordStart - 1]) {
|
|
this->writeSpace();
|
|
}
|
|
}
|
|
SkASSERT(temp[temp.length() - 1] > ' ');
|
|
this->writeString(temp.c_str());
|
|
lastWrite = wordEnd;
|
|
}
|
|
return lastWrite;
|
|
}
|
|
|
|
int IncludeWriter::lookupReference(const PunctuationState punctuation, const Word word,
|
|
const int start, const int run, int lastWrite, const char last, const char* data) {
|
|
const int end = PunctuationState::kDelimiter == punctuation ||
|
|
PunctuationState::kPeriod == punctuation ? run - 1 : run;
|
|
RefType refType = RefType::kUndefined;
|
|
string resolved = string(&data[start], (size_t) (end - start));
|
|
string temp = this->resolveRef(&data[start], &data[end], Word::kFirst == word, &refType);
|
|
if (!temp.length()) {
|
|
if (Word::kFirst != word && '_' != last) {
|
|
temp = ConvertRef(resolved, false);
|
|
}
|
|
}
|
|
if (temp.length()) {
|
|
if (start > lastWrite) {
|
|
SkASSERT(data[start - 1] >= ' ');
|
|
if (' ' == data[lastWrite]) {
|
|
this->writeSpace();
|
|
}
|
|
this->writeBlockTrim(start - lastWrite, &data[lastWrite]);
|
|
if (' ' == data[start - 1]) {
|
|
this->writeSpace();
|
|
}
|
|
}
|
|
SkASSERT(temp[temp.length() - 1] > ' ');
|
|
this->writeString(temp.c_str());
|
|
lastWrite = end;
|
|
}
|
|
return lastWrite;
|
|
}
|
|
|
|
/* returns true if rewriteBlock wrote linefeeds */
|
|
IncludeWriter::Wrote IncludeWriter::rewriteBlock(int size, const char* data, Phrase phrase) {
|
|
bool wroteLineFeeds = false;
|
|
while (size > 0 && data[0] <= ' ') {
|
|
--size;
|
|
++data;
|
|
}
|
|
while (size > 0 && data[size - 1] <= ' ') {
|
|
--size;
|
|
}
|
|
if (0 == size) {
|
|
return Wrote::kNone;
|
|
}
|
|
int run = 0;
|
|
Word word = Word::kStart;
|
|
PunctuationState punctuation = Phrase::kNo == phrase ?
|
|
PunctuationState::kStart : PunctuationState::kSpace;
|
|
int start = 0;
|
|
int lastWrite = 0;
|
|
int lineFeeds = 0;
|
|
int lastPrintable = 0;
|
|
int lastSpace = -1;
|
|
char c = 0;
|
|
char last;
|
|
bool embeddedIndirection = false;
|
|
bool embeddedSymbol = false;
|
|
bool hasLower = false;
|
|
bool hasUpper = false;
|
|
bool hasIndirection = false;
|
|
bool hasSymbol = false;
|
|
while (run < size) {
|
|
last = c;
|
|
c = data[run];
|
|
SkASSERT(' ' <= c || '\n' == c);
|
|
if (lineFeeds && ' ' < c) {
|
|
if (lastPrintable >= lastWrite) {
|
|
if (' ' == data[lastWrite]) {
|
|
this->writeSpace();
|
|
}
|
|
this->writeBlock(lastPrintable - lastWrite + 1, &data[lastWrite]);
|
|
}
|
|
if (lineFeeds > 1) {
|
|
this->lf(2);
|
|
}
|
|
this->lfcr(); // defer the indent until non-whitespace is seen
|
|
lastWrite = run;
|
|
lineFeeds = 0;
|
|
}
|
|
if (' ' < c) {
|
|
lastPrintable = run;
|
|
}
|
|
switch (c) {
|
|
case '\n':
|
|
++lineFeeds;
|
|
wroteLineFeeds = true;
|
|
case ' ':
|
|
switch (word) {
|
|
case Word::kStart:
|
|
break;
|
|
case Word::kUnderline:
|
|
case Word::kCap:
|
|
case Word::kFirst:
|
|
if (!hasLower) {
|
|
break;
|
|
}
|
|
lastWrite = this->lookupReference(punctuation, word, start, run,
|
|
lastWrite, last, data);
|
|
break;
|
|
case Word::kMixed:
|
|
if (hasUpper && hasLower && !hasSymbol && lastSpace > 0) {
|
|
lastWrite = this->lookupMethod(punctuation, word, lastSpace, run,
|
|
lastWrite, data, hasIndirection && !hasSymbol);
|
|
}
|
|
break;
|
|
default:
|
|
SkASSERT(0);
|
|
}
|
|
punctuation = PunctuationState::kPeriod == punctuation ||
|
|
(PunctuationState::kStart == punctuation && ' ' >= last) ?
|
|
PunctuationState::kStart : PunctuationState::kSpace;
|
|
word = Word::kStart;
|
|
embeddedIndirection = false;
|
|
embeddedSymbol = false;
|
|
hasLower = false;
|
|
hasUpper = false;
|
|
hasIndirection = false;
|
|
hasSymbol = false;
|
|
lastSpace = run;
|
|
break;
|
|
case '.':
|
|
switch (word) {
|
|
case Word::kStart:
|
|
punctuation = PunctuationState::kDelimiter;
|
|
case Word::kCap:
|
|
case Word::kFirst:
|
|
case Word::kUnderline:
|
|
case Word::kMixed:
|
|
if (PunctuationState::kDelimiter == punctuation ||
|
|
PunctuationState::kPeriod == punctuation) {
|
|
word = Word::kMixed;
|
|
}
|
|
punctuation = PunctuationState::kPeriod;
|
|
break;
|
|
default:
|
|
SkASSERT(0);
|
|
}
|
|
embeddedIndirection = true;
|
|
break;
|
|
case ',': case ';': case ':':
|
|
switch (word) {
|
|
case Word::kStart:
|
|
punctuation = PunctuationState::kDelimiter;
|
|
case Word::kCap:
|
|
case Word::kFirst:
|
|
case Word::kUnderline:
|
|
case Word::kMixed:
|
|
if (PunctuationState::kDelimiter == punctuation ||
|
|
PunctuationState::kPeriod == punctuation) {
|
|
word = Word::kMixed;
|
|
}
|
|
punctuation = PunctuationState::kDelimiter;
|
|
break;
|
|
default:
|
|
SkASSERT(0);
|
|
}
|
|
embeddedSymbol = true;
|
|
break;
|
|
case '>':
|
|
if ('-' == last) {
|
|
embeddedIndirection = true;
|
|
break;
|
|
}
|
|
case '\'': // possessive apostrophe isn't treated as delimiting punctation
|
|
case '\"': // quote is passed straight through
|
|
case '=':
|
|
case '!': // assumed not to be punctuation, but a programming symbol
|
|
case '&': case '<': case '{': case '}': case '/': case '*': case '[': case ']':
|
|
word = Word::kMixed;
|
|
embeddedSymbol = true;
|
|
break;
|
|
case '(':
|
|
if (' ' == last) {
|
|
punctuation = PunctuationState::kDelimiter;
|
|
} else {
|
|
word = Word::kMixed;
|
|
}
|
|
embeddedSymbol = true;
|
|
break;
|
|
case ')': // assume word type has already been set
|
|
punctuation = PunctuationState::kDelimiter;
|
|
embeddedSymbol = true;
|
|
break;
|
|
case '_':
|
|
switch (word) {
|
|
case Word::kStart:
|
|
word = Word::kMixed;
|
|
break;
|
|
case Word::kCap:
|
|
case Word::kFirst:
|
|
case Word::kUnderline:
|
|
word = Word::kUnderline;
|
|
break;
|
|
case Word::kMixed:
|
|
break;
|
|
default:
|
|
SkASSERT(0);
|
|
}
|
|
hasSymbol |= embeddedSymbol;
|
|
break;
|
|
case '+':
|
|
// hackery to allow C++
|
|
SkASSERT('C' == last || '+' == last); // FIXME: don't allow + outside of #Formula
|
|
break;
|
|
case 'A': case 'B': case 'C': case 'D': case 'E':
|
|
case 'F': case 'G': case 'H': case 'I': case 'J':
|
|
case 'K': case 'L': case 'M': case 'N': case 'O':
|
|
case 'P': case 'Q': case 'R': case 'S': case 'T':
|
|
case 'U': case 'V': case 'W': case 'X': case 'Y':
|
|
case 'Z':
|
|
switch (word) {
|
|
case Word::kStart:
|
|
word = PunctuationState::kStart == punctuation ? Word::kFirst : Word::kCap;
|
|
start = run;
|
|
break;
|
|
case Word::kCap:
|
|
case Word::kFirst:
|
|
if (!isupper(last)) {
|
|
word = Word::kMixed;
|
|
}
|
|
break;
|
|
case Word::kUnderline:
|
|
// some word in Xxx_XXX_Xxx can be all upper, but all can't: XXX_XXX
|
|
if ('_' != last && !isupper(last)) {
|
|
word = Word::kMixed;
|
|
}
|
|
break;
|
|
case Word::kMixed:
|
|
break;
|
|
default:
|
|
SkASSERT(0);
|
|
}
|
|
hasUpper = true;
|
|
if (PunctuationState::kPeriod == punctuation ||
|
|
PunctuationState::kDelimiter == punctuation) {
|
|
word = Word::kMixed;
|
|
}
|
|
hasIndirection |= embeddedIndirection;
|
|
hasSymbol |= embeddedSymbol;
|
|
break;
|
|
case 'a': case 'b': case 'c': case 'd': case 'e':
|
|
case 'f': case 'g': case 'h': case 'i': case 'j':
|
|
case 'k': case 'l': case 'm': case 'n': case 'o':
|
|
case 'p': case 'q': case 'r': case 's': case 't':
|
|
case 'u': case 'v': case 'w': case 'x': case 'y':
|
|
case 'z':
|
|
case '0': case '1': case '2': case '3': case '4':
|
|
case '5': case '6': case '7': case '8': case '9':
|
|
case '-':
|
|
switch (word) {
|
|
case Word::kStart:
|
|
word = Word::kMixed;
|
|
break;
|
|
case Word::kMixed:
|
|
case Word::kCap:
|
|
case Word::kFirst:
|
|
case Word::kUnderline:
|
|
break;
|
|
default:
|
|
SkASSERT(0);
|
|
}
|
|
hasLower = true;
|
|
punctuation = PunctuationState::kStart;
|
|
hasIndirection |= embeddedIndirection;
|
|
hasSymbol |= embeddedSymbol;
|
|
break;
|
|
default:
|
|
SkASSERT(0);
|
|
}
|
|
++run;
|
|
}
|
|
if ((word == Word::kCap || word == Word::kFirst || word == Word::kUnderline) && hasLower) {
|
|
lastWrite = this->lookupReference(punctuation, word, start, run, lastWrite, last, data);
|
|
} else if (word == Word::kMixed && hasUpper && hasLower && !hasSymbol && lastSpace > 0) {
|
|
lastWrite = this->lookupMethod(punctuation, word, lastSpace, run, lastWrite, data,
|
|
hasIndirection && !hasSymbol);
|
|
}
|
|
if (run > lastWrite) {
|
|
if (' ' == data[lastWrite]) {
|
|
this->writeSpace();
|
|
}
|
|
this->writeBlock(run - lastWrite, &data[lastWrite]);
|
|
}
|
|
return wroteLineFeeds ? Wrote::kLF : Wrote::kChars;
|
|
}
|