5c11d5c084
Verified that running bookmaker produces an identical fiddle.json before and after this change. Bug: skia: Change-Id: I1aa7477348a5f8c362201199b130508c2818116f Reviewed-on: https://skia-review.googlesource.com/c/188303 Commit-Queue: Brian Osman <brianosman@google.com> Reviewed-by: Florin Malita <fmalita@chromium.org>
3650 lines
137 KiB
C++
3650 lines
137 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 "SkOSFile.h"
|
|
#include "SkOSPath.h"
|
|
|
|
#include "bmhParser.h"
|
|
#include "includeParser.h"
|
|
#include <map>
|
|
|
|
const IncludeKey kKeyWords[] = {
|
|
{ "", KeyWord::kNone, KeyProperty::kNone },
|
|
{ "SK_API", KeyWord::kSK_API, KeyProperty::kModifier },
|
|
{ "SK_BEGIN_REQUIRE_DENSE", KeyWord::kSK_BEGIN_REQUIRE_DENSE, KeyProperty::kModifier },
|
|
{ "alignas", KeyWord::kAlignAs, KeyProperty::kModifier },
|
|
{ "bool", KeyWord::kBool, KeyProperty::kNumber },
|
|
{ "char", KeyWord::kChar, KeyProperty::kNumber },
|
|
{ "class", KeyWord::kClass, KeyProperty::kObject },
|
|
{ "const", KeyWord::kConst, KeyProperty::kModifier },
|
|
{ "constexpr", KeyWord::kConstExpr, KeyProperty::kModifier },
|
|
{ "define", KeyWord::kDefine, KeyProperty::kPreprocessor },
|
|
{ "double", KeyWord::kDouble, KeyProperty::kNumber },
|
|
{ "elif", KeyWord::kElif, KeyProperty::kPreprocessor },
|
|
{ "else", KeyWord::kElse, KeyProperty::kPreprocessor },
|
|
{ "endif", KeyWord::kEndif, KeyProperty::kPreprocessor },
|
|
{ "enum", KeyWord::kEnum, KeyProperty::kObject },
|
|
{ "error", KeyWord::kError, KeyProperty::kPreprocessor },
|
|
{ "float", KeyWord::kFloat, KeyProperty::kNumber },
|
|
{ "friend", KeyWord::kFriend, KeyProperty::kModifier },
|
|
{ "if", KeyWord::kIf, KeyProperty::kPreprocessor },
|
|
{ "ifdef", KeyWord::kIfdef, KeyProperty::kPreprocessor },
|
|
{ "ifndef", KeyWord::kIfndef, KeyProperty::kPreprocessor },
|
|
{ "include", KeyWord::kInclude, KeyProperty::kPreprocessor },
|
|
{ "inline", KeyWord::kInline, KeyProperty::kModifier },
|
|
{ "int", KeyWord::kInt, KeyProperty::kNumber },
|
|
{ "operator", KeyWord::kOperator, KeyProperty::kFunction },
|
|
{ "private", KeyWord::kPrivate, KeyProperty::kClassSection },
|
|
{ "protected", KeyWord::kProtected, KeyProperty::kClassSection },
|
|
{ "public", KeyWord::kPublic, KeyProperty::kClassSection },
|
|
{ "signed", KeyWord::kSigned, KeyProperty::kNumber },
|
|
{ "size_t", KeyWord::kSize_t, KeyProperty::kNumber },
|
|
{ "static", KeyWord::kStatic, KeyProperty::kModifier },
|
|
{ "struct", KeyWord::kStruct, KeyProperty::kObject },
|
|
{ "template", KeyWord::kTemplate, KeyProperty::kObject },
|
|
{ "typedef", KeyWord::kTypedef, KeyProperty::kObject },
|
|
{ "typename", KeyWord::kTypename, KeyProperty::kObject },
|
|
{ "uint16_t", KeyWord::kUint16_t, KeyProperty::kNumber },
|
|
{ "uint32_t", KeyWord::kUint32_t, KeyProperty::kNumber },
|
|
{ "uint64_t", KeyWord::kUint64_t, KeyProperty::kNumber },
|
|
{ "uint8_t", KeyWord::kUint8_t, KeyProperty::kNumber },
|
|
{ "uintptr_t", KeyWord::kUintPtr_t, KeyProperty::kNumber },
|
|
{ "union", KeyWord::kUnion, KeyProperty::kObject },
|
|
{ "unsigned", KeyWord::kUnsigned, KeyProperty::kNumber },
|
|
{ "using", KeyWord::kUsing, KeyProperty::kObject },
|
|
{ "void", KeyWord::kVoid, KeyProperty::kNumber },
|
|
};
|
|
|
|
const size_t kKeyWordCount = SK_ARRAY_COUNT(kKeyWords);
|
|
|
|
KeyWord IncludeParser::FindKey(const char* start, const char* end) {
|
|
int ch = 0;
|
|
for (size_t index = 0; index < kKeyWordCount; ) {
|
|
if (start[ch] > kKeyWords[index].fName[ch]) {
|
|
++index;
|
|
if (ch > 0 && (index == kKeyWordCount ||
|
|
kKeyWords[index - 1].fName[ch - 1] < kKeyWords[index].fName[ch - 1])) {
|
|
return KeyWord::kNone;
|
|
}
|
|
continue;
|
|
}
|
|
if (start[ch] < kKeyWords[index].fName[ch]) {
|
|
return KeyWord::kNone;
|
|
}
|
|
++ch;
|
|
if (start + ch >= end) {
|
|
if (end - start < (int) strlen(kKeyWords[index].fName)) {
|
|
return KeyWord::kNone;
|
|
}
|
|
return kKeyWords[index].fKeyWord;
|
|
}
|
|
}
|
|
return KeyWord::kNone;
|
|
}
|
|
|
|
void IncludeParser::ValidateKeyWords() {
|
|
for (size_t index = 1; index < kKeyWordCount; ++index) {
|
|
SkASSERT((int) kKeyWords[index - 1].fKeyWord + 1
|
|
== (int) kKeyWords[index].fKeyWord);
|
|
SkASSERT(0 > strcmp(kKeyWords[index - 1].fName, kKeyWords[index].fName));
|
|
}
|
|
}
|
|
|
|
void IncludeParser::addKeyword(KeyWord keyWord) {
|
|
fParent->fTokens.emplace_back(keyWord, fIncludeWord, fChar, fLineCount, fParent, '\0');
|
|
fIncludeWord = nullptr;
|
|
if (KeyProperty::kObject == kKeyWords[(int) keyWord].fProperty) {
|
|
Definition* def = &fParent->fTokens.back();
|
|
this->addDefinition(def);
|
|
if (KeyWord::kEnum == fParent->fKeyWord) {
|
|
fInEnum = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool looks_like_method(const TextParser& tp) {
|
|
TextParser t(tp.fFileName, tp.fLine, tp.fChar, tp.fLineCount);
|
|
t.skipSpace();
|
|
if (!t.skipExact("struct") && !t.skipExact("class") && !t.skipExact("enum class")
|
|
&& !t.skipExact("enum")) {
|
|
return true;
|
|
}
|
|
t.skipSpace();
|
|
if (t.skipExact("SK_API")) {
|
|
t.skipSpace();
|
|
}
|
|
if (!isupper(t.peek())) {
|
|
return true;
|
|
}
|
|
return nullptr != t.strnchr('(', t.fEnd);
|
|
}
|
|
|
|
static bool looks_like_forward_declaration(const TextParser& tp) {
|
|
TextParser t(tp.fFileName, tp.fChar, tp.lineEnd(), tp.fLineCount);
|
|
t.skipSpace();
|
|
if (!t.skipExact("struct") && !t.skipExact("class") && !t.skipExact("enum class")
|
|
&& !t.skipExact("enum")) {
|
|
return false;
|
|
}
|
|
t.skipSpace();
|
|
if (t.skipExact("SK_API")) {
|
|
t.skipSpace();
|
|
}
|
|
if (!isupper(t.peek())) {
|
|
return false;
|
|
}
|
|
t.skipToNonAlphaNum();
|
|
if (t.eof() || ';' != t.next()) {
|
|
return false;
|
|
}
|
|
if (t.eof() || '\n' != t.next()) {
|
|
return false;
|
|
}
|
|
return t.eof();
|
|
}
|
|
|
|
static bool looks_like_constructor(const TextParser& tp) {
|
|
TextParser t(tp.fFileName, tp.fLine, tp.lineEnd(), tp.fLineCount);
|
|
t.skipSpace();
|
|
if (!isupper(t.peek())) {
|
|
if (':' == t.next() && ' ' >= t.peek()) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
t.skipToNonAlphaNum();
|
|
if ('(' != t.peek()) {
|
|
return false;
|
|
}
|
|
if (!t.skipToEndBracket(')')) {
|
|
return false;
|
|
}
|
|
SkAssertResult(')' == t.next());
|
|
t.skipSpace();
|
|
return tp.fChar == t.fChar;
|
|
}
|
|
|
|
static bool looks_like_class_decl(const TextParser& tp) {
|
|
TextParser t(tp.fFileName, tp.fLine, tp.fChar, tp.fLineCount);
|
|
t.skipSpace();
|
|
if (!t.skipExact("class")) {
|
|
return false;
|
|
}
|
|
t.skipSpace();
|
|
if (t.skipExact("SK_API")) {
|
|
t.skipSpace();
|
|
}
|
|
if (!isupper(t.peek())) {
|
|
return false;
|
|
}
|
|
t.skipToNonAlphaNum();
|
|
return !t.skipToEndBracket('(');
|
|
}
|
|
|
|
static bool looks_like_const(const TextParser& tp) {
|
|
TextParser t(tp.fFileName, tp.fChar, tp.lineEnd(), tp.fLineCount);
|
|
if (!t.startsWith("static constexpr ")) {
|
|
return false;
|
|
}
|
|
if (t.skipToEndBracket(" k")) {
|
|
SkAssertResult(t.skipExact(" k"));
|
|
} else if (t.skipToEndBracket(" SK_")) {
|
|
SkAssertResult(t.skipExact(" SK_"));
|
|
} else {
|
|
return false;
|
|
}
|
|
if (!isupper(t.peek())) {
|
|
return false;
|
|
}
|
|
return t.skipToEndBracket(" = ");
|
|
}
|
|
|
|
static bool looks_like_member(const TextParser& tp) {
|
|
TextParser t(tp.fFileName, tp.fChar, tp.lineEnd(), tp.fLineCount);
|
|
const char* end = t.anyOf("(;");
|
|
if (!end || '(' == *end) {
|
|
return false;
|
|
}
|
|
bool foundMember = false;
|
|
do {
|
|
const char* next = t.anyOf(" ;");
|
|
if (';' == *next) {
|
|
break;
|
|
}
|
|
t.skipTo(next);
|
|
t.skipSpace();
|
|
foundMember = 'f' == t.fChar[0] && isupper(t.fChar[1]);
|
|
} while (true);
|
|
return foundMember;
|
|
}
|
|
|
|
static void skip_constructor_initializers(TextParser& t) {
|
|
SkAssertResult(':' == t.next());
|
|
do {
|
|
t.skipWhiteSpace();
|
|
t.skipToNonAlphaNum();
|
|
t.skipWhiteSpace();
|
|
if ('{' == t.peek()) {
|
|
t.skipToBalancedEndBracket('{', '}');
|
|
}
|
|
do {
|
|
const char* limiter = t.anyOf("(,{");
|
|
t.skipTo(limiter);
|
|
if ('(' != t.peek()) {
|
|
break;
|
|
}
|
|
t.skipToBalancedEndBracket('(', ')');
|
|
} while (true);
|
|
if ('{' == t.peek()) {
|
|
return;
|
|
}
|
|
SkAssertResult(',' == t.next());
|
|
} while (true);
|
|
}
|
|
|
|
static const char kInline[] = "inline ";
|
|
static const char kSK_API[] = "SK_API ";
|
|
static const char kSK_WARN_UNUSED_RESULT[] = "SK_WARN_UNUSED_RESULT ";
|
|
|
|
bool IncludeParser::advanceInclude(TextParser& i) {
|
|
if (!i.skipWhiteSpace(&fCheck.fIndent, &fCheck.fWriteReturn)) {
|
|
return false;
|
|
}
|
|
if (fCheck.fPrivateBrace) {
|
|
if (i.startsWith("};")) {
|
|
if (fCheck.fPrivateBrace == fCheck.fBraceCount) {
|
|
fCheck.fPrivateBrace = 0;
|
|
fCheck.fDoubleReturn = true;
|
|
} else {
|
|
i.skipExact("};");
|
|
fCheck.fBraceCount -= 1;
|
|
}
|
|
return false;
|
|
}
|
|
if (i.startsWith("public:")) {
|
|
if (fCheck.fBraceCount <= fCheck.fPrivateBrace) {
|
|
fCheck.fPrivateBrace = 0;
|
|
if (fCheck.fPrivateProtected) {
|
|
i.skipExact("public:");
|
|
}
|
|
} else {
|
|
i.skipExact("public:");
|
|
}
|
|
} else {
|
|
fCheck.fBraceCount += i.skipToLineBalance('{', '}');
|
|
}
|
|
return false;
|
|
} else if (i.startsWith("};")) {
|
|
fCheck.fDoubleReturn = 2;
|
|
}
|
|
if (i.skipExact(kInline)) {
|
|
fCheck.fSkipInline = true;
|
|
return false;
|
|
}
|
|
if (i.skipExact(kSK_API)) {
|
|
fCheck.fSkipAPI = true;
|
|
return false;
|
|
}
|
|
if (i.skipExact(kSK_WARN_UNUSED_RESULT)) {
|
|
fCheck.fSkipWarnUnused = true;
|
|
return false;
|
|
}
|
|
if (i.skipExact("SK_ATTR_DEPRECATED")) {
|
|
i.skipToLineStart(&fCheck.fIndent, &fCheck.fWriteReturn);
|
|
return false;
|
|
}
|
|
if (i.skipExact("SkDEBUGCODE")) {
|
|
i.skipWhiteSpace();
|
|
if ('(' != i.peek()) {
|
|
i.reportError("expected open paren");
|
|
}
|
|
TextParserSave save(&i);
|
|
SkAssertResult(i.skipToBalancedEndBracket('(', ')'));
|
|
fCheck.fInDebugCode = i.fChar - 1;
|
|
save.restore();
|
|
SkAssertResult('(' == i.next());
|
|
}
|
|
if ('{' == i.peek()) {
|
|
if (looks_like_method(i)) {
|
|
fCheck.fState = CheckCode::State::kMethod;
|
|
bool inBalance = false;
|
|
TextParser paren(i.fFileName, i.fStart, i.fEnd, i.fLineCount);
|
|
paren.skipToEndBracket('(');
|
|
paren.skipToBalancedEndBracket('(', ')');
|
|
inBalance = i.fChar < paren.fChar;
|
|
if (!inBalance) {
|
|
if (!i.skipToBalancedEndBracket('{', '}')) {
|
|
i.reportError("unbalanced open brace");
|
|
}
|
|
i.skipToLineStart(&fCheck.fIndent, &fCheck.fWriteReturn);
|
|
return false;
|
|
}
|
|
} else if (looks_like_class_decl(i)) {
|
|
fCheck.fState = CheckCode::State::kClassDeclaration;
|
|
fCheck.fPrivateBrace = fCheck.fBraceCount + 1;
|
|
fCheck.fPrivateProtected = false;
|
|
}
|
|
}
|
|
if (':' == i.peek() && looks_like_constructor(i)) {
|
|
fCheck.fState = CheckCode::State::kConstructor;
|
|
skip_constructor_initializers(i);
|
|
return false;
|
|
}
|
|
if ('#' == i.peek()) {
|
|
i.skipToLineStart(&fCheck.fIndent, &fCheck.fWriteReturn);
|
|
return false;
|
|
}
|
|
if (i.startsWith("//")) {
|
|
i.skipToLineStart(&fCheck.fIndent, &fCheck.fWriteReturn);
|
|
return false;
|
|
}
|
|
if (i.startsWith("/*")) {
|
|
i.skipToEndBracket("*/");
|
|
i.skipToLineStart(&fCheck.fIndent, &fCheck.fWriteReturn);
|
|
return false;
|
|
}
|
|
if (looks_like_forward_declaration(i)) {
|
|
fCheck.fState = CheckCode::State::kForwardDeclaration;
|
|
i.skipToLineStart(&fCheck.fIndent, &fCheck.fWriteReturn);
|
|
return false;
|
|
}
|
|
if (i.skipExact("private:") || i.skipExact("protected:")) {
|
|
if (!fCheck.fBraceCount) {
|
|
i.reportError("expect private in brace");
|
|
}
|
|
fCheck.fPrivateBrace = fCheck.fBraceCount;
|
|
fCheck.fPrivateProtected = true;
|
|
return false;
|
|
}
|
|
const char* funcEnd = i.anyOf("(\n");
|
|
if (funcEnd && '(' == funcEnd[0] && '_' == *i.anyOf("_(")
|
|
&& (i.contains("internal_", funcEnd, nullptr)
|
|
|| i.contains("private_", funcEnd, nullptr)
|
|
|| i.contains("legacy_", funcEnd, nullptr)
|
|
|| i.contains("temporary_", funcEnd, nullptr))) {
|
|
i.skipTo(funcEnd);
|
|
if (!i.skipToBalancedEndBracket('(', ')')) {
|
|
i.reportError("unbalanced open parent");
|
|
}
|
|
i.skipSpace();
|
|
i.skipExact("const ");
|
|
i.skipSpace();
|
|
if (';' == i.peek()) {
|
|
i.next();
|
|
}
|
|
fCheck.fState = CheckCode::State::kNone;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void IncludeParser::codeBlockAppend(string& result, string s) const {
|
|
for (char c : s) {
|
|
this->codeBlockAppend(result, c);
|
|
}
|
|
}
|
|
|
|
void IncludeParser::codeBlockAppend(string& result, char ch) const {
|
|
if (Elided::kYes == fElided && fCheck.fBraceCount) {
|
|
return;
|
|
}
|
|
this->stringAppend(result, ch);
|
|
}
|
|
|
|
void IncludeParser::codeBlockSpaces(string& result, int indent) const {
|
|
if (!indent) {
|
|
return;
|
|
}
|
|
if (Elided::kYes == fElided && fCheck.fBraceCount) {
|
|
return;
|
|
}
|
|
SkASSERT(indent > 0);
|
|
if (fDebugWriteCodeBlock) {
|
|
SkDebugf("%*c", indent, ' ');
|
|
}
|
|
result.append(indent, ' ');
|
|
}
|
|
|
|
string IncludeParser::writeCodeBlock(const Definition& iDef) {
|
|
if (MarkType::kComment == iDef.fMarkType) {
|
|
return "";
|
|
}
|
|
if (iDef.fUndocumented) {
|
|
return "";
|
|
}
|
|
TextParser i(&iDef);
|
|
(void) i.skipExact("SkDEBUGCODE(");
|
|
if (MarkType::kConst == iDef.fMarkType && !i.fEnd) {
|
|
// TODO: end should have been set earlier
|
|
auto iter = iDef.fParent->fTokens.begin();
|
|
std::advance(iter, iDef.fParentIndex + 1);
|
|
SkASSERT(iter != iDef.fParent->fTokens.end());
|
|
i.fEnd = iter->fContentStart;
|
|
}
|
|
const char* loc;
|
|
if (MarkType::kMember == iDef.fMarkType) {
|
|
const char* parentEnd = iDef.fParent->fContentEnd;
|
|
TextParser newEnd(&iDef);
|
|
newEnd.fEnd = parentEnd;
|
|
const char* memberEnd = newEnd.anyOf(",};");
|
|
if (memberEnd && (';' == memberEnd[0] || ',' == memberEnd[0])) {
|
|
i.fEnd = memberEnd + 1;
|
|
}
|
|
}
|
|
if (i.contains("//", i.fEnd, &loc)) {
|
|
i.fEnd = loc;
|
|
}
|
|
if (i.contains("/*", i.fEnd, &loc)) {
|
|
i.fEnd = loc;
|
|
}
|
|
if (i.contains("{", i.fEnd, &loc)) {
|
|
bool inBalance = false;
|
|
if (MarkType::kMethod == iDef.fMarkType) {
|
|
TextParser paren(&iDef);
|
|
paren.skipToEndBracket('(');
|
|
paren.skipToBalancedEndBracket('(', ')');
|
|
inBalance = loc < paren.fChar;
|
|
}
|
|
if (!inBalance) {
|
|
i.fEnd = loc + 1;
|
|
while (i.fEnd < iDef.fContentEnd && ' ' >= i.fEnd[0]) {
|
|
++i.fEnd;
|
|
}
|
|
}
|
|
}
|
|
while (i.fEnd > i.fStart && ' ' == i.fEnd[-1]) {
|
|
--i.fEnd;
|
|
}
|
|
const char* before = iDef.fContentStart;
|
|
while (' ' == *--before)
|
|
;
|
|
int startIndent = iDef.fContentStart - before - 1;
|
|
bool saveDebugWriteBlock = fDebugWriteCodeBlock;
|
|
fDebugWriteCodeBlock = false;
|
|
string result = writeCodeBlock(i, iDef.fMarkType, startIndent);
|
|
fDebugWriteCodeBlock = saveDebugWriteBlock;
|
|
if (!result.empty()) {
|
|
if (MarkType::kNone != fPreviousMarkType && iDef.fMarkType != fPreviousMarkType
|
|
&& ((MarkType::kEnum != fPreviousMarkType
|
|
&& MarkType::kEnumClass != fPreviousMarkType)
|
|
|| MarkType::kMember != iDef.fMarkType)
|
|
&& (MarkType::kMember != fPreviousMarkType
|
|
|| iDef.fParent == fPreviousDef->fParent)) {
|
|
result = "\n" + result;
|
|
}
|
|
if (fDebugWriteCodeBlock) {
|
|
SkDebugf("%s", result.c_str());
|
|
}
|
|
fPreviousDef = &iDef;
|
|
fPreviousMarkType = iDef.fMarkType;
|
|
}
|
|
for (auto& token : iDef.fTokens) {
|
|
result += this->writeCodeBlock(token);
|
|
}
|
|
if (MarkType::kEnum == iDef.fMarkType || MarkType::kEnumClass == iDef.fMarkType
|
|
|| MarkType::kStruct == iDef.fMarkType || MarkType::kClass == iDef.fMarkType) {
|
|
this->codeBlockSpaces(result, startIndent);
|
|
this->codeBlockAppend(result, "};\n\n");
|
|
}
|
|
return result;
|
|
}
|
|
|
|
string IncludeParser::writeCodeBlock(TextParser& i, MarkType markType, int startIndent) {
|
|
string result;
|
|
char last;
|
|
int lastIndent = 0;
|
|
bool lastDoubleMeUp = false;
|
|
fCheck.reset();
|
|
if (MarkType::kDefine == markType) {
|
|
result = "#define ";
|
|
} else {
|
|
this->codeBlockSpaces(result, startIndent);
|
|
}
|
|
do {
|
|
if (!this->advanceInclude(i)) {
|
|
continue;
|
|
}
|
|
do {
|
|
last = i.peek();
|
|
SkASSERT(' ' < last);
|
|
if (fCheck.fInDebugCode == i.fChar) {
|
|
fCheck.fInDebugCode = nullptr;
|
|
i.next(); // skip close paren
|
|
break;
|
|
}
|
|
if (CheckCode::State::kMethod == fCheck.fState) {
|
|
this->codeBlockAppend(result, ';');
|
|
fCheck.fState = CheckCode::State::kNone;
|
|
}
|
|
if (fCheck.fWriteReturn) {
|
|
this->codeBlockAppend(result, '\n');
|
|
bool doubleMeUp = i.startsWith("typedef ") || looks_like_const(i)
|
|
|| (!strncmp("struct ", i.fStart, 7) && looks_like_member(i));
|
|
if ((!--fCheck.fDoubleReturn && !i.startsWith("};")) || i.startsWith("enum ")
|
|
|| i.startsWith("typedef ") || doubleMeUp || fCheck.fTypedefReturn
|
|
|| (fCheck.fIndent && (i.startsWith("class ") || i.startsWith("struct ")))) {
|
|
if (lastIndent > 0 && (!doubleMeUp || !lastDoubleMeUp)) {
|
|
this->codeBlockAppend(result, '\n');
|
|
}
|
|
fCheck.fTypedefReturn = false;
|
|
lastDoubleMeUp = doubleMeUp;
|
|
}
|
|
if (doubleMeUp) {
|
|
fCheck.fTypedefReturn = true;
|
|
}
|
|
lastIndent = fCheck.fIndent;
|
|
}
|
|
if (fCheck.fIndent) {
|
|
size_t indent = fCheck.fIndent;
|
|
if (fCheck.fSkipInline && indent > sizeof(kInline)) {
|
|
indent -= sizeof(kInline) - 1;
|
|
}
|
|
if (fCheck.fSkipAPI && indent > sizeof(kSK_API)) {
|
|
indent -= sizeof(kSK_API) - 1;
|
|
}
|
|
if (fCheck.fSkipWarnUnused && indent > sizeof(kSK_WARN_UNUSED_RESULT)) {
|
|
indent -= sizeof(kSK_WARN_UNUSED_RESULT) - 1;
|
|
}
|
|
|
|
this->codeBlockSpaces(result, indent);
|
|
}
|
|
this->codeBlockAppend(result, last);
|
|
fCheck.fWriteReturn = false;
|
|
fCheck.fIndent = 0;
|
|
fCheck.fBraceCount += '{' == last;
|
|
fCheck.fBraceCount -= '}' == last;
|
|
if (';' == last) {
|
|
fCheck.fSkipInline = false;
|
|
fCheck.fSkipAPI = false;
|
|
fCheck.fSkipWarnUnused = false;
|
|
}
|
|
if (fCheck.fBraceCount < 0) {
|
|
i.reportError("unbalanced close brace");
|
|
return result;
|
|
}
|
|
i.next();
|
|
} while (!i.eof() && ' ' < i.peek() && !i.startsWith("//"));
|
|
} while (!i.eof());
|
|
if (CheckCode::State::kMethod == fCheck.fState) {
|
|
this->codeBlockAppend(result, ';');
|
|
}
|
|
bool elided = Elided::kYes == fElided;
|
|
bool elidedTemplate = elided && !strncmp(i.fStart, "template ", 9);
|
|
bool elidedTClass = elidedTemplate && MarkType::kClass == markType;
|
|
bool statementEnd = !result.empty() && (MarkType::kMethod == markType
|
|
|| MarkType::kTypedef == markType || '}' == result.back());
|
|
bool semiEnd = !result.empty() && (',' == result.back() || ';' == result.back());
|
|
if (fCheck.fWriteReturn || elidedTClass) {
|
|
this->codeBlockAppend(result, '\n');
|
|
}
|
|
if (elided && ((MarkType::kFunction != markType && lastIndent > startIndent) || elidedTClass)) {
|
|
this->codeBlockAppend(result, '}');
|
|
statementEnd = true;
|
|
}
|
|
if (elided || statementEnd) {
|
|
this->codeBlockAppend(result, ";\n");
|
|
} else if (elidedTemplate || semiEnd) {
|
|
this->codeBlockAppend(result, '\n');
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void IncludeParser::checkForMissingParams(const vector<string>& methodParams,
|
|
const vector<string>& foundParams) {
|
|
for (auto& methodParam : methodParams) {
|
|
bool found = false;
|
|
for (auto& foundParam : foundParams) {
|
|
if (methodParam == foundParam) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found) {
|
|
this->writeIncompleteTag("Param", methodParam, 2);
|
|
}
|
|
}
|
|
for (auto& foundParam : foundParams) {
|
|
bool found = false;
|
|
for (auto& methodParam : methodParams) {
|
|
if (methodParam == foundParam) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found) {
|
|
this->reportError("doxygen param does not match method declaration");
|
|
}
|
|
}
|
|
}
|
|
|
|
bool IncludeParser::checkForWord() {
|
|
if (!fIncludeWord) {
|
|
return true;
|
|
}
|
|
KeyWord keyWord = FindKey(fIncludeWord, fChar);
|
|
if (KeyWord::kClass == keyWord || KeyWord::kStruct == keyWord) {
|
|
Bracket bracket = this->topBracket();
|
|
if (Bracket::kParen == bracket) {
|
|
return true;
|
|
}
|
|
}
|
|
if (KeyWord::kNone != keyWord) {
|
|
if (KeyProperty::kPreprocessor != kKeyWords[(int) keyWord].fProperty) {
|
|
this->addKeyword(keyWord);
|
|
return true;
|
|
}
|
|
} else {
|
|
this->addWord();
|
|
return true;
|
|
}
|
|
Definition* poundDef = fParent;
|
|
if (!fParent) {
|
|
return reportError<bool>("expected parent");
|
|
}
|
|
if (Definition::Type::kBracket != poundDef->fType) {
|
|
return reportError<bool>("expected bracket");
|
|
}
|
|
if (Bracket::kPound != poundDef->fBracket) {
|
|
return reportError<bool>("expected preprocessor");
|
|
}
|
|
if (KeyWord::kNone != poundDef->fKeyWord) {
|
|
return reportError<bool>("already found keyword");
|
|
}
|
|
poundDef->fKeyWord = keyWord;
|
|
fIncludeWord = nullptr;
|
|
switch (keyWord) {
|
|
// these do not link to other # directives
|
|
case KeyWord::kDefine:
|
|
if (!fInBrace) {
|
|
SkASSERT(!fInDefine);
|
|
fInDefine = true;
|
|
}
|
|
case KeyWord::kInclude:
|
|
case KeyWord::kError:
|
|
break;
|
|
// these start a # directive link
|
|
case KeyWord::kIf:
|
|
case KeyWord::kIfdef:
|
|
case KeyWord::kIfndef:
|
|
break;
|
|
// these continue a # directive link
|
|
case KeyWord::kElif:
|
|
case KeyWord::kElse:
|
|
this->popObject(); // pop elif
|
|
if (Bracket::kPound != fParent->fBracket) {
|
|
return this->reportError<bool>("expected preprocessor directive");
|
|
}
|
|
this->popBracket(); // pop if
|
|
poundDef->fParent = fParent;
|
|
fParent = poundDef; // push elif back
|
|
break;
|
|
// this ends a # directive link
|
|
case KeyWord::kEndif:
|
|
// FIXME : should this be calling popBracket() instead?
|
|
this->popObject(); // pop endif
|
|
if (Bracket::kPound != fParent->fBracket) {
|
|
return this->reportError<bool>("expected preprocessor directive");
|
|
}
|
|
this->popBracket(); // pop if/else
|
|
break;
|
|
default:
|
|
SkASSERT(0);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
string IncludeParser::className() const {
|
|
string name(fParent->fName);
|
|
size_t slash = name.find_last_of("/");
|
|
if (string::npos == slash) {
|
|
slash = name.find_last_of("\\");
|
|
}
|
|
SkASSERT(string::npos != slash);
|
|
string result = name.substr(slash);
|
|
result = result.substr(1, result.size() - 3);
|
|
return result;
|
|
}
|
|
|
|
void IncludeParser::writeCodeBlock() {
|
|
fElided = Elided::kNo;
|
|
for (auto& classMapper : fIClassMap) {
|
|
fPreviousMarkType = MarkType::kNone;
|
|
fPreviousDef = nullptr;
|
|
classMapper.second.fCode = this->writeCodeBlock(classMapper.second);
|
|
}
|
|
for (auto& enumMapper : fIEnumMap) {
|
|
fPreviousMarkType = MarkType::kNone;
|
|
fPreviousDef = nullptr;
|
|
enumMapper.second->fCode = this->writeCodeBlock(*enumMapper.second);
|
|
}
|
|
for (auto& typedefMapper : fITypedefMap) {
|
|
fPreviousMarkType = MarkType::kNone;
|
|
fPreviousDef = nullptr;
|
|
typedefMapper.second->fCode = this->writeCodeBlock(*typedefMapper.second);
|
|
}
|
|
for (auto& defineMapper : fIDefineMap) {
|
|
fPreviousMarkType = MarkType::kNone;
|
|
fPreviousDef = nullptr;
|
|
defineMapper.second->fCode = this->writeCodeBlock(*defineMapper.second);
|
|
}
|
|
}
|
|
|
|
void IncludeParser::checkName(Definition* def) {
|
|
SkASSERT(!def->fName.empty());
|
|
TextParser parser(def->fFileName, &def->fName.front(), &def->fName.back() + 1, def->fLineCount);
|
|
const vector<string> skipWords = { "deprecated", "experimental", "internal", "private",
|
|
"legacy", "temporary" };
|
|
if (!parser.anyWord(skipWords, 0).empty()) {
|
|
def->fUndocumented = true;
|
|
}
|
|
}
|
|
|
|
#include <sstream>
|
|
#include <iostream>
|
|
|
|
void IncludeParser::checkTokens(list<Definition>& tokens, string key, string className,
|
|
RootDefinition* root, BmhParser& bmhParser) {
|
|
for (const auto& token : tokens) {
|
|
if (token.fPrivate) {
|
|
continue;
|
|
}
|
|
string fullName = key + "::" + token.fName;
|
|
const Definition* def = nullptr;
|
|
if (root) {
|
|
def = root->find(fullName, RootDefinition::AllowParens::kYes);
|
|
}
|
|
switch (token.fMarkType) {
|
|
case MarkType::kMethod: {
|
|
if (this->isInternalName(token)) {
|
|
continue;
|
|
}
|
|
if (!root) {
|
|
if (token.fUndocumented) {
|
|
break;
|
|
}
|
|
auto methIter = bmhParser.fMethodMap.find(token.fName);
|
|
if (bmhParser.fMethodMap.end() != methIter) {
|
|
def = &methIter->second;
|
|
if (def->crossCheck2(token)) {
|
|
def->fVisited = true;
|
|
} else {
|
|
this->suggestFix(Suggest::kMethodDiffers, token, root, def);
|
|
fFailed = true;
|
|
}
|
|
} else {
|
|
this->suggestFix(Suggest::kMethodMissing, token, root, nullptr);
|
|
fFailed = true;
|
|
}
|
|
break;
|
|
}
|
|
if (!def) {
|
|
string paramName = className + "::";
|
|
paramName += string(token.fContentStart,
|
|
token.fContentEnd - token.fContentStart);
|
|
if (string::npos != paramName.find('\n')) {
|
|
paramName.erase(std::remove(paramName.begin(), paramName.end(), '\n'),
|
|
paramName.end());
|
|
}
|
|
def = root->find(paramName, RootDefinition::AllowParens::kYes);
|
|
if (!def && 0 == token.fName.find("operator")) {
|
|
string operatorName = className + "::";
|
|
TextParser oper("", token.fStart, token.fContentEnd, 0);
|
|
const char* start = oper.strnstr("operator", token.fContentEnd);
|
|
SkASSERT(start);
|
|
oper.skipTo(start);
|
|
oper.skipToEndBracket('(');
|
|
int parens = 0;
|
|
do {
|
|
if ('(' == oper.peek()) {
|
|
++parens;
|
|
} else if (')' == oper.peek()) {
|
|
--parens;
|
|
}
|
|
} while (!oper.eof() && oper.next() && parens > 0);
|
|
operatorName += string(start, oper.fChar - start);
|
|
def = root->find(operatorName, RootDefinition::AllowParens::kYes);
|
|
}
|
|
}
|
|
if (!def) {
|
|
int skip = !strncmp(token.fContentStart, "explicit ", 9) ? 9 : 0;
|
|
skip = !strncmp(token.fContentStart, "virtual ", 8) ? 8 : skip;
|
|
const char* tokenEnd = token.methodEnd();
|
|
string constructorName = className + "::";
|
|
constructorName += string(token.fContentStart + skip,
|
|
tokenEnd - token.fContentStart - skip);
|
|
def = root->find(constructorName, RootDefinition::AllowParens::kYes);
|
|
}
|
|
if (!def && 0 == token.fName.find("SK_")) {
|
|
string incName = token.fName + "()";
|
|
string macroName = className + "::" + incName;
|
|
def = root->find(macroName, RootDefinition::AllowParens::kYes);
|
|
if (def) {
|
|
if (def->fName == incName) {
|
|
def->fVisited = true;
|
|
if ("SK_TO_STRING_NONVIRT" == token.fName) {
|
|
def = root->find(className + "::toString",
|
|
RootDefinition::AllowParens::kYes);
|
|
if (def) {
|
|
def->fVisited = true;
|
|
} else {
|
|
SkDebugf("missing toString bmh: %s\n", fullName.c_str());
|
|
fFailed = true;
|
|
}
|
|
}
|
|
break;
|
|
} else {
|
|
SkDebugf("method macro differs from bmh: %s\n", fullName.c_str());
|
|
fFailed = true;
|
|
}
|
|
}
|
|
}
|
|
if (!def) {
|
|
bool allLower = true;
|
|
for (size_t index = 0; index < token.fName.length(); ++index) {
|
|
if (!islower(token.fName[index])) {
|
|
allLower = false;
|
|
break;
|
|
}
|
|
}
|
|
if (allLower) {
|
|
string lowerName = className + "::" + token.fName + "()";
|
|
def = root->find(lowerName, RootDefinition::AllowParens::kYes);
|
|
}
|
|
}
|
|
if (!def) {
|
|
if (0 == token.fName.find("SkDEBUGCODE")) {
|
|
break;
|
|
}
|
|
}
|
|
if (!def) {
|
|
// simple method names inside nested classes have a bug and are missing trailing parens
|
|
string withParens = fullName + "()"; // FIXME: this shouldn't be necessary
|
|
def = root->find(withParens, RootDefinition::AllowParens::kNo);
|
|
}
|
|
if (!def) {
|
|
if (!token.fUndocumented) {
|
|
this->suggestFix(Suggest::kMethodMissing, token, root, nullptr);
|
|
fFailed = true;
|
|
}
|
|
break;
|
|
}
|
|
if (token.fUndocumented) {
|
|
// we can't report an error yet; if bmh documents this unnecessarily,
|
|
// we'll detect that later. It may be that def points to similar
|
|
// documented function.
|
|
break;
|
|
}
|
|
if (def->crossCheck2(token)) {
|
|
def->fVisited = true;
|
|
} else {
|
|
SkDebugf("method differs from bmh: %s\n", fullName.c_str());
|
|
fFailed = true;
|
|
}
|
|
} break;
|
|
case MarkType::kComment:
|
|
break;
|
|
case MarkType::kEnumClass:
|
|
case MarkType::kEnum: {
|
|
if (!def) {
|
|
// work backwards from first word to deduce #Enum name
|
|
TextParser firstMember("", token.fStart, token.fContentEnd, 0);
|
|
SkAssertResult(firstMember.skipName("enum"));
|
|
SkAssertResult(firstMember.skipToEndBracket('{'));
|
|
firstMember.next();
|
|
firstMember.skipWhiteSpace();
|
|
SkASSERT('k' == firstMember.peek());
|
|
const char* savePos = firstMember.fChar;
|
|
firstMember.skipToNonName();
|
|
const char* wordEnd = firstMember.fChar;
|
|
firstMember.fChar = savePos;
|
|
const char* lastUnderscore = nullptr;
|
|
do {
|
|
if (!firstMember.skipToEndBracket('_')) {
|
|
break;
|
|
}
|
|
if (firstMember.fChar > wordEnd) {
|
|
break;
|
|
}
|
|
lastUnderscore = firstMember.fChar;
|
|
} while (firstMember.next());
|
|
if (lastUnderscore) {
|
|
++lastUnderscore;
|
|
string enumName(lastUnderscore, wordEnd - lastUnderscore);
|
|
if (root) {
|
|
string anonName = className + "::" + enumName + 's';
|
|
def = root->find(anonName, RootDefinition::AllowParens::kYes);
|
|
} else {
|
|
auto enumIter = bmhParser.fEnumMap.find(enumName);
|
|
if (bmhParser.fEnumMap.end() != enumIter) {
|
|
RootDefinition* rootDef = &enumIter->second;
|
|
def = rootDef;
|
|
}
|
|
}
|
|
}
|
|
if (!def && !root) {
|
|
auto enumIter = bmhParser.fEnumMap.find(token.fName);
|
|
if (bmhParser.fEnumMap.end() != enumIter) {
|
|
def = &enumIter->second;
|
|
}
|
|
if (!def) {
|
|
auto enumClassIter = bmhParser.fClassMap.find(token.fName);
|
|
if (bmhParser.fClassMap.end() != enumClassIter) {
|
|
def = &enumClassIter->second;
|
|
}
|
|
}
|
|
}
|
|
if (!def) {
|
|
if (!token.fUndocumented) {
|
|
SkDebugf("enum missing from bmh: %s\n", fullName.c_str());
|
|
fFailed = true;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
def->fVisited = true;
|
|
bool hasCode = false;
|
|
bool hasPopulate = true;
|
|
for (auto& child : def->fChildren) {
|
|
if (MarkType::kCode == child->fMarkType) {
|
|
hasPopulate = std::any_of(child->fChildren.begin(),
|
|
child->fChildren.end(), [](auto grandChild){
|
|
return MarkType::kPopulate == grandChild->fMarkType; });
|
|
if (!hasPopulate) {
|
|
def = child;
|
|
}
|
|
hasCode = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!hasCode && !root) {
|
|
const Definition* topic = def->topicParent();
|
|
hasCode = std::any_of(topic->fChildren.begin(), topic->fChildren.end(),
|
|
[](Definition* def){ return MarkType::kCode == def->fMarkType
|
|
&& def->fChildren.size() > 0 && MarkType::kPopulate ==
|
|
def->fChildren.front()->fMarkType; });
|
|
}
|
|
if (!hasCode) {
|
|
SkDebugf("enum code missing from bmh: %s\n", fullName.c_str());
|
|
fFailed = true;
|
|
break;
|
|
}
|
|
if (!hasPopulate) {
|
|
if (def->crossCheck(token)) {
|
|
def->fVisited = true;
|
|
} else {
|
|
SkDebugf("enum differs from bmh: %s\n", def->fName.c_str());
|
|
fFailed = true;
|
|
}
|
|
}
|
|
for (auto& member : token.fTokens) {
|
|
if (MarkType::kMember != member.fMarkType) {
|
|
continue;
|
|
}
|
|
string constName = MarkType::kEnumClass == token.fMarkType ?
|
|
fullName : className;
|
|
if (root) {
|
|
constName += "::" + member.fName;
|
|
def = root->find(constName, RootDefinition::AllowParens::kYes);
|
|
} else {
|
|
auto enumMapper = bmhParser.fEnumMap.find(token.fName);
|
|
if (bmhParser.fEnumMap.end() != enumMapper) {
|
|
auto& enumDoc = enumMapper->second;
|
|
auto memberIter = enumDoc.fLeaves.find(member.fName);
|
|
if (enumDoc.fLeaves.end() != memberIter) {
|
|
def = &memberIter->second;
|
|
}
|
|
}
|
|
}
|
|
if (!def) {
|
|
string innerName = key + "::" + member.fName;
|
|
def = root->find(innerName, RootDefinition::AllowParens::kYes);
|
|
}
|
|
if (!def) {
|
|
if (!member.fUndocumented) {
|
|
SkDebugf("const missing from bmh: %s\n", constName.c_str());
|
|
fFailed = true;
|
|
}
|
|
} else {
|
|
def->fVisited = true;
|
|
}
|
|
}
|
|
} break;
|
|
case MarkType::kMember:
|
|
if (def) {
|
|
def->fVisited = true;
|
|
} else {
|
|
SkDebugf("member missing from bmh: %s\n", fullName.c_str());
|
|
fFailed = true;
|
|
}
|
|
break;
|
|
case MarkType::kTypedef:
|
|
if (!def && !root) {
|
|
auto typedefIter = bmhParser.fTypedefMap.find(token.fName);
|
|
if (bmhParser.fTypedefMap.end() != typedefIter) {
|
|
def = &typedefIter->second;
|
|
}
|
|
}
|
|
if (def) {
|
|
def->fVisited = true;
|
|
} else {
|
|
SkDebugf("typedef missing from bmh: %s\n", fullName.c_str());
|
|
fFailed = true;
|
|
}
|
|
break;
|
|
case MarkType::kConst:
|
|
if (!def && !root) {
|
|
auto constIter = bmhParser.fConstMap.find(token.fName);
|
|
if (bmhParser.fConstMap.end() != constIter) {
|
|
def = &constIter->second;
|
|
}
|
|
}
|
|
if (def) {
|
|
def->fVisited = true;
|
|
} else {
|
|
if (!token.fUndocumented) {
|
|
SkDebugf("const missing from bmh: %s\n", fullName.c_str());
|
|
fFailed = true;
|
|
}
|
|
}
|
|
break;
|
|
case MarkType::kDefine:
|
|
// TODO: incomplete
|
|
break;
|
|
default:
|
|
SkASSERT(0); // unhandled
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool IncludeParser::crossCheck(BmhParser& bmhParser) {
|
|
for (auto& classMapper : fIClassMap) {
|
|
string className = classMapper.first;
|
|
auto finder = bmhParser.fClassMap.find(className);
|
|
if (bmhParser.fClassMap.end() == finder) {
|
|
SkASSERT(string::npos != className.find("::"));
|
|
continue;
|
|
}
|
|
}
|
|
for (auto& classMapper : fIClassMap) {
|
|
if (classMapper.second.fUndocumented) {
|
|
continue;
|
|
}
|
|
string className = classMapper.first;
|
|
std::istringstream iss(className);
|
|
string classStr;
|
|
string classBase;
|
|
RootDefinition* root = nullptr;
|
|
while (std::getline(iss, classStr, ':')) {
|
|
if (root) {
|
|
if (!classStr.length()) {
|
|
continue;
|
|
}
|
|
classBase += "::" + classStr;
|
|
auto finder = root->fBranches.find(classBase);
|
|
if (root->fBranches.end() != finder) {
|
|
root = finder->second;
|
|
} else {
|
|
SkASSERT(0);
|
|
}
|
|
} else {
|
|
classBase = classStr;
|
|
auto finder = bmhParser.fClassMap.find(classBase);
|
|
if (bmhParser.fClassMap.end() != finder) {
|
|
root = &finder->second;
|
|
} else {
|
|
SkASSERT(0);
|
|
}
|
|
}
|
|
}
|
|
this->checkTokens(classMapper.second.fTokens, classMapper.first, className, root,
|
|
bmhParser);
|
|
}
|
|
this->checkTokens(fGlobals, "", "", nullptr, bmhParser);
|
|
int crossChecks = 0;
|
|
string firstCheck;
|
|
for (auto& classMapper : fIClassMap) {
|
|
string className = classMapper.first;
|
|
auto finder = bmhParser.fClassMap.find(className);
|
|
if (bmhParser.fClassMap.end() == finder) {
|
|
continue;
|
|
}
|
|
RootDefinition* root = &finder->second;
|
|
if (!root->dumpUnVisited()) {
|
|
fFailed = true;
|
|
}
|
|
if (crossChecks) {
|
|
SkDebugf(".");
|
|
} else {
|
|
SkDebugf("cross-check");
|
|
firstCheck = className;
|
|
}
|
|
++crossChecks;
|
|
}
|
|
if (crossChecks) {
|
|
if (1 == crossChecks) {
|
|
SkDebugf(" %s", firstCheck.c_str());
|
|
}
|
|
SkDebugf("\n");
|
|
}
|
|
bmhParser.fWroteOut = true;
|
|
return !fFailed;
|
|
}
|
|
|
|
IClassDefinition* IncludeParser::defineClass(const Definition& includeDef,
|
|
string name) {
|
|
string className;
|
|
const Definition* test = fParent;
|
|
while (Definition::Type::kFileType != test->fType) {
|
|
if (Definition::Type::kMark == test->fType && KeyWord::kClass == test->fKeyWord) {
|
|
className = test->fName + "::";
|
|
break;
|
|
}
|
|
test = test->fParent;
|
|
}
|
|
className += name;
|
|
unordered_map<string, IClassDefinition>& map = fIClassMap;
|
|
IClassDefinition& markupDef = map[className];
|
|
if (markupDef.fStart) {
|
|
typedef IClassDefinition* IClassDefPtr;
|
|
return INHERITED::reportError<IClassDefPtr>("class already defined");
|
|
}
|
|
markupDef.fFileName = fFileName;
|
|
markupDef.fStart = includeDef.fStart;
|
|
markupDef.fContentStart = includeDef.fStart;
|
|
markupDef.fName = className;
|
|
this->checkName(&markupDef);
|
|
markupDef.fContentEnd = includeDef.fContentEnd;
|
|
markupDef.fTerminator = includeDef.fTerminator;
|
|
markupDef.fParent = fParent;
|
|
markupDef.fLineCount = fLineCount;
|
|
markupDef.fMarkType = KeyWord::kStruct == includeDef.fKeyWord ?
|
|
MarkType::kStruct : MarkType::kClass;
|
|
markupDef.fKeyWord = includeDef.fKeyWord;
|
|
markupDef.fType = Definition::Type::kMark;
|
|
auto tokenIter = includeDef.fParent->fTokens.begin();
|
|
SkASSERT(includeDef.fParentIndex > 0);
|
|
std::advance(tokenIter, includeDef.fParentIndex - 1);
|
|
const Definition* priorComment = &*tokenIter;
|
|
markupDef.fUndocumented = priorComment->fUndocumented;
|
|
fParent = &markupDef;
|
|
return &markupDef;
|
|
}
|
|
|
|
void IncludeParser::dumpClassTokens(IClassDefinition& classDef) {
|
|
auto& tokens = classDef.fTokens;
|
|
bool wroteTail = true;
|
|
for (auto& token : tokens) {
|
|
if (Definition::Type::kMark == token.fType && MarkType::kComment == token.fMarkType) {
|
|
continue;
|
|
}
|
|
if (wroteTail && MarkType::kMember != token.fMarkType) {
|
|
this->writeBlockSeparator();
|
|
}
|
|
switch (token.fMarkType) {
|
|
case MarkType::kConst:
|
|
this->dumpConst(token, classDef.fName);
|
|
break;
|
|
case MarkType::kEnum:
|
|
case MarkType::kEnumClass:
|
|
this->dumpEnum(token, token.fName);
|
|
break;
|
|
case MarkType::kMethod:
|
|
if (!this->dumpMethod(token, classDef.fName)) {
|
|
wroteTail = false;
|
|
continue;
|
|
}
|
|
break;
|
|
case MarkType::kMember:
|
|
this->dumpMember(token);
|
|
continue;
|
|
break;
|
|
case MarkType::kTypedef:
|
|
this->dumpTypedef(token, classDef.fName);
|
|
break;
|
|
default:
|
|
SkASSERT(0);
|
|
}
|
|
this->dumpCommonTail(token);
|
|
wroteTail = true;
|
|
}
|
|
}
|
|
void IncludeParser::dumpComment(const Definition& token) {
|
|
fLineCount = token.fLineCount;
|
|
fChar = fLine = token.fContentStart;
|
|
fEnd = token.fContentEnd;
|
|
if (MarkType::kMethod == token.fMarkType) {
|
|
this->lf(2);
|
|
this->writeTag("Populate");
|
|
this->lf(2);
|
|
return;
|
|
}
|
|
for (const auto& child : token.fTokens) {
|
|
if (Definition::Type::kMark == child.fType && MarkType::kMember == child.fMarkType) {
|
|
break;
|
|
}
|
|
if (Definition::Type::kMark == child.fType && MarkType::kComment == child.fMarkType) {
|
|
if (child.fPrivate) {
|
|
break;
|
|
}
|
|
if (child.length() > 1) {
|
|
const char* start = child.fContentStart;
|
|
ptrdiff_t length = child.fContentEnd - start;
|
|
SkASSERT(length >= 0);
|
|
while (length && '/' == start[0]) {
|
|
start += 1;
|
|
--length;
|
|
}
|
|
while (length && '/' == start[length - 1]) {
|
|
length -= 1;
|
|
if (length && '*' == start[length - 1]) {
|
|
length -= 1;
|
|
}
|
|
}
|
|
if (length) {
|
|
this->lf(2);
|
|
if ("!< " == string(start, length).substr(0, 3)) {
|
|
return;
|
|
}
|
|
this->writeBlock(length, start);
|
|
this->lf(2);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void IncludeParser::dumpCommonTail(const Definition& token) {
|
|
this->lf(2);
|
|
this->writeTag("Example");
|
|
this->lf(1);
|
|
this->writeString("// incomplete");
|
|
this->lf(1);
|
|
this->writeEndTag();
|
|
this->lf(2);
|
|
this->writeTag("SeeAlso");
|
|
this->writeSpace();
|
|
this->writeString("incomplete");
|
|
this->lf(2);
|
|
this->writeEndTag(BmhParser::kMarkProps[(int) token.fMarkType].fName);
|
|
this->lf(2);
|
|
}
|
|
|
|
void IncludeParser::dumpConst(const Definition& token, string className) {
|
|
this->writeTag("Const");
|
|
this->writeSpace();
|
|
this->writeString(token.fName);
|
|
this->writeTagTable("Line", "incomplete");
|
|
this->lf(2);
|
|
this->dumpComment(token);
|
|
}
|
|
|
|
void IncludeParser::dumpDefine(const Definition& token) {
|
|
this->writeTag("Define", token.fName);
|
|
this->lf(2);
|
|
this->writeTag("Code");
|
|
this->lfAlways(1);
|
|
this->writeString("###$");
|
|
this->lfAlways(1);
|
|
this->indentToColumn(4);
|
|
this->writeBlock(token.fTerminator - token.fStart, token.fStart);
|
|
this->lf(1);
|
|
this->indentToColumn(0);
|
|
this->writeString("$$$#");
|
|
|
|
this->writeEndTag();
|
|
this->lf(2);
|
|
this->dumpComment(token);
|
|
for (auto& child : token.fTokens) {
|
|
if (MarkType::kComment == child.fMarkType) {
|
|
continue;
|
|
}
|
|
this->writeTag("Param", child.fName);
|
|
this->writeSpace();
|
|
this->writeString("incomplete");
|
|
this->writeSpace();
|
|
this->writeString("##");
|
|
this->lf(1);
|
|
}
|
|
}
|
|
|
|
void IncludeParser::dumpEnum(const Definition& token, string name) {
|
|
string tagType(MarkType::kEnum == token.fMarkType ? "Enum" : "EnumClass");
|
|
this->writeTag(tagType.c_str(), token.fName);
|
|
this->lf(2);
|
|
this->writeTag("Code");
|
|
this->writeTag("Populate");
|
|
this->writeEndTag();
|
|
this->lf(2);
|
|
this->dumpComment(token);
|
|
string prior;
|
|
for (auto& child : token.fTokens) {
|
|
if (MarkType::kComment == child.fMarkType) {
|
|
prior = string(child.fContentStart, child.length());
|
|
}
|
|
if (MarkType::kMember != child.fMarkType) {
|
|
continue;
|
|
}
|
|
this->writeTag("Const");
|
|
this->writeSpace();
|
|
this->writeString(child.fName);
|
|
this->writeSpace(2);
|
|
this->writeString("0 # incomplete; replace '0' with member value");
|
|
this->lf(1);
|
|
this->writeTagNoLF("Line", "#");
|
|
this->writeSpace();
|
|
if ("/!< " == prior.substr(0, 4)) {
|
|
this->writeString(prior.substr(4));
|
|
} else {
|
|
this->writeString("incomplete");
|
|
}
|
|
this->writeSpace();
|
|
this->writeString("##");
|
|
this->lf(1);
|
|
this->writeString("# incomplete; add description or delete");
|
|
this->writeEndTag();
|
|
}
|
|
this->lf(2);
|
|
this->writeString("# incomplete; add description or delete");
|
|
this->lf(2);
|
|
}
|
|
|
|
bool IncludeParser::dumpGlobals(string* globalFileName, long int* globalTell) {
|
|
bool hasGlobals = !fIDefineMap.empty() || !fIFunctionMap.empty() || !fIEnumMap.empty()
|
|
|| !fITemplateMap.empty()|| !fITypedefMap.empty() || !fIUnionMap.empty();
|
|
if (!hasGlobals) {
|
|
return true;
|
|
}
|
|
size_t lastBSlash = fFileName.rfind('\\');
|
|
size_t lastSlash = fFileName.rfind('/');
|
|
size_t lastDotH = fFileName.rfind(".h");
|
|
SkASSERT(string::npos != lastDotH);
|
|
if (string::npos != lastBSlash && (string::npos == lastSlash
|
|
|| lastBSlash < lastSlash)) {
|
|
lastSlash = lastBSlash;
|
|
} else if (string::npos == lastSlash) {
|
|
lastSlash = -1;
|
|
}
|
|
lastSlash += 1;
|
|
string globalsName = fFileName.substr(lastSlash, lastDotH - lastSlash);
|
|
string fileName = globalsName + "_Reference.bmh";
|
|
*globalFileName = fileName;
|
|
fOut = fopen(fileName.c_str(), "wb");
|
|
if (!fOut) {
|
|
SkDebugf("could not open output file %s\n", globalsName.c_str());
|
|
return false;
|
|
}
|
|
string prefixName = globalsName.substr(0, 2);
|
|
string topicName = globalsName.length() > 2 && isupper(globalsName[2]) &&
|
|
("Sk" == prefixName || "Gr" == prefixName) ? globalsName.substr(2) : globalsName;
|
|
this->writeTagNoLF("Topic", topicName);
|
|
this->writeEndTag("Alias", topicName + "_Reference");
|
|
this->lf(2);
|
|
if (!fIDefineMap.empty() || !fIFunctionMap.empty() || !fIEnumMap.empty()
|
|
|| !fITemplateMap.empty() || !fITypedefMap.empty() || !fIUnionMap.empty()) {
|
|
this->writeTag("Code");
|
|
this->writeTag("Populate");
|
|
this->writeEndTag();
|
|
this->lf(2);
|
|
}
|
|
std::map<int, Definition*> sortedDefs;
|
|
for (const auto& entry : fIDefineMap) {
|
|
sortedDefs[entry.second->fLineCount] = entry.second;
|
|
}
|
|
for (const auto& entry : fIFunctionMap) {
|
|
sortedDefs[entry.second->fLineCount] = entry.second;
|
|
}
|
|
for (const auto& entry : fIEnumMap) {
|
|
if (string::npos == entry.first.find("::")) {
|
|
sortedDefs[entry.second->fLineCount] = entry.second;
|
|
}
|
|
}
|
|
for (const auto& entry : fITemplateMap) {
|
|
sortedDefs[entry.second->fLineCount] = entry.second;
|
|
}
|
|
for (const auto& entry : fITypedefMap) {
|
|
sortedDefs[entry.second->fLineCount] = entry.second;
|
|
}
|
|
for (const auto& entry : fIUnionMap) {
|
|
sortedDefs[entry.second->fLineCount] = entry.second;
|
|
}
|
|
for (const auto& entry : sortedDefs) {
|
|
const Definition* def = entry.second;
|
|
this->writeBlockSeparator();
|
|
switch (def->fMarkType) {
|
|
case MarkType::kDefine:
|
|
this->dumpDefine(*def);
|
|
break;
|
|
case MarkType::kMethod:
|
|
if (!this->dumpMethod(*def, globalsName)) {
|
|
continue;
|
|
}
|
|
break;
|
|
case MarkType::kEnum:
|
|
case MarkType::kEnumClass:
|
|
this->dumpEnum(*def, globalsName);
|
|
break;
|
|
case MarkType::kTemplate:
|
|
SkASSERT(0); // incomplete
|
|
break;
|
|
case MarkType::kTypedef: {
|
|
this->writeTag("Typedef");
|
|
this->writeSpace();
|
|
TextParser parser(def);
|
|
if (!parser.skipExact("typedef")) {
|
|
return false;
|
|
}
|
|
if (!parser.skipSpace()) {
|
|
return false;
|
|
}
|
|
this->writeBlock(parser.fEnd - parser.fChar, parser.fChar);
|
|
this->lf(2);
|
|
this->dumpComment(*def);
|
|
this->writeEndTag(BmhParser::kMarkProps[(int) entry.second->fMarkType].fName);
|
|
this->lf(2);
|
|
} continue;
|
|
case MarkType::kUnion:
|
|
SkASSERT(0); // incomplete
|
|
break;
|
|
default:
|
|
SkASSERT(0);
|
|
}
|
|
this->dumpCommonTail(*def);
|
|
}
|
|
*globalTell = ftell(fOut);
|
|
this->writeEndTag("Topic", topicName);
|
|
this->lfAlways(1);
|
|
// fclose(fOut); // defer closing in case class needs to be also written here
|
|
return true;
|
|
}
|
|
|
|
bool IncludeParser::isClone(const Definition& token) {
|
|
string name = token.fName;
|
|
return name[name.length() - 2] == '_' && isdigit(name[name.length() - 1]);
|
|
}
|
|
|
|
bool IncludeParser::isConstructor(const Definition& token, string className) {
|
|
string name = token.fName;
|
|
return 0 == name.find(className) || '~' == name[0];
|
|
}
|
|
|
|
bool IncludeParser::isInternalName(const Definition& token) {
|
|
string name = token.fName;
|
|
// exception for this SkCanvas function .. for now
|
|
if (0 == token.fName.find("androidFramework_setDeviceClipRestriction")) {
|
|
return false;
|
|
}
|
|
return name.substr(0, 7) == "android"
|
|
|| 0 == token.fName.find("internal_")
|
|
|| 0 == token.fName.find("Internal_")
|
|
|| 0 == token.fName.find("legacy_")
|
|
|| 0 == token.fName.find("temporary_")
|
|
|| 0 == token.fName.find("private_");
|
|
}
|
|
|
|
bool IncludeParser::isMember(const Definition& token) const {
|
|
if ('f' == token.fStart[0] && isupper(token.fStart[1])) {
|
|
return true;
|
|
}
|
|
if (!islower(token.fStart[0])) {
|
|
return false;
|
|
}
|
|
// make an exception for SkTextBlob::RunBuffer, sole struct with members not in fXxxx format
|
|
if (string::npos != token.fFileName.find("SkTextBlob.h")) {
|
|
const Definition* structToken = token.fParent;
|
|
if (!structToken) {
|
|
return false;
|
|
}
|
|
if (KeyWord::kStruct != structToken->fKeyWord) {
|
|
structToken = token.fParent->fParent;
|
|
if (!structToken) {
|
|
return false;
|
|
}
|
|
if (KeyWord::kStruct != structToken->fKeyWord) {
|
|
return false;
|
|
}
|
|
}
|
|
SkASSERT(structToken->fTokens.size() > 0);
|
|
const Definition& child = structToken->fTokens.front();
|
|
string structName(child.fContentStart, child.length());
|
|
if ("RunBuffer" != structName) {
|
|
return false;
|
|
}
|
|
string tokenName(token.fContentStart, token.length());
|
|
string allowed[] = { "glyphs", "pos", "utf8text", "clusters" };
|
|
for (auto allow : allowed) {
|
|
if (allow == tokenName) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool IncludeParser::isOperator(const Definition& token) {
|
|
return "operator" == token.fName.substr(0, 8);
|
|
}
|
|
|
|
bool IncludeParser::dumpMethod(const Definition& token, string className) {
|
|
if (std::any_of(token.fTokens.begin(), token.fTokens.end(),
|
|
[=](const Definition& def) { return MarkType::kComment == def.fMarkType
|
|
&& this->isUndocumentable(def.fFileName, def.fContentStart, def.fContentEnd,
|
|
def.fLineCount); } )) {
|
|
return false;
|
|
}
|
|
this->writeTag("Method");
|
|
this->writeSpace();
|
|
|
|
string name = string(token.fStart ? token.fStart : token.fContentStart,
|
|
token.length());
|
|
this->writeBlock((int) name.size(), name.c_str());
|
|
string inType;
|
|
if (this->isConstructor(token, className)) {
|
|
inType = "Constructor";
|
|
} else if (this->isOperator(token)) {
|
|
inType = "Operator";
|
|
} else {
|
|
inType = "incomplete";
|
|
}
|
|
this->writeTag("In", inType);
|
|
this->writeTagTable("Line", "incomplete");
|
|
this->lf(2);
|
|
this->dumpComment(token);
|
|
return true;
|
|
}
|
|
|
|
void IncludeParser::dumpMember(const Definition& token) {
|
|
this->writeTag("Member");
|
|
this->writeSpace();
|
|
this->writeDefinition(token, token.fName, 2);
|
|
lf(1);
|
|
for (auto child : token.fChildren) {
|
|
this->writeDefinition(*child);
|
|
}
|
|
this->writeEndTag();
|
|
lf(2);
|
|
}
|
|
|
|
bool IncludeParser::dumpTokens() {
|
|
string globalFileName;
|
|
long int globalTell = 0;
|
|
if (!this->dumpGlobals(&globalFileName, &globalTell)) {
|
|
return false;
|
|
}
|
|
for (const auto& member : fIClassMap) {
|
|
if (string::npos != member.first.find("::")) {
|
|
continue;
|
|
}
|
|
if (!this->dumpTokens(member.first, globalFileName, &globalTell)) {
|
|
return false;
|
|
}
|
|
}
|
|
if (globalTell) {
|
|
fclose(fOut);
|
|
SkDebugf("wrote %s\n", globalFileName.c_str());
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// dump equivalent markup
|
|
bool IncludeParser::dumpTokens(string skClassName, string globalFileName, long int* globalTell) {
|
|
string fileName = skClassName + "_Reference.bmh";
|
|
if (globalFileName != fileName) {
|
|
fOut = fopen(fileName.c_str(), "wb");
|
|
if (!fOut) {
|
|
SkDebugf("could not open output file %s\n", fileName.c_str());
|
|
return false;
|
|
}
|
|
} else {
|
|
fseek(fOut, *globalTell, SEEK_SET);
|
|
this->lf(2);
|
|
this->writeBlockSeparator();
|
|
*globalTell = 0;
|
|
}
|
|
string prefixName = skClassName.substr(0, 2);
|
|
string topicName = skClassName.length() > 2 && isupper(skClassName[2]) &&
|
|
("Sk" == prefixName || "Gr" == prefixName) ? skClassName.substr(2) : skClassName;
|
|
if (globalFileName != fileName) {
|
|
this->writeTagNoLF("Topic", topicName);
|
|
this->writeEndTag("Alias", topicName + "_Reference");
|
|
this->lf(2);
|
|
}
|
|
auto& classMap = fIClassMap[skClassName];
|
|
SkASSERT(KeyWord::kClass == classMap.fKeyWord || KeyWord::kStruct == classMap.fKeyWord);
|
|
const char* containerType = KeyWord::kClass == classMap.fKeyWord ? "Class" : "Struct";
|
|
this->writeTag(containerType, skClassName);
|
|
this->lf(2);
|
|
auto& tokens = classMap.fTokens;
|
|
for (auto& token : tokens) {
|
|
if (Definition::Type::kMark != token.fType || MarkType::kComment != token.fMarkType) {
|
|
continue;
|
|
}
|
|
this->writeDefinition(token);
|
|
this->lf(1);
|
|
}
|
|
this->lf(2);
|
|
this->writeTag("Code");
|
|
this->writeTag("Populate");
|
|
this->writeEndTag();
|
|
this->lf(2);
|
|
for (auto& oneClass : fIClassMap) {
|
|
if (skClassName + "::" != oneClass.first.substr(0, skClassName.length() + 2)) {
|
|
continue;
|
|
}
|
|
string innerName = oneClass.first.substr(skClassName.length() + 2);
|
|
this->writeBlockSeparator();
|
|
KeyWord keyword = oneClass.second.fKeyWord;
|
|
SkASSERT(KeyWord::kClass == keyword || KeyWord::kStruct == keyword);
|
|
const char* containerType = KeyWord::kClass == keyword ? "Class" : "Struct";
|
|
this->writeTag(containerType, innerName);
|
|
this->writeTagTable("Line", "incomplete");
|
|
this->lf(2);
|
|
this->writeTag("Code");
|
|
this->writeEndTag("ToDo", "fill this in manually");
|
|
this->writeEndTag();
|
|
this->lf(2);
|
|
for (auto& token : oneClass.second.fTokens) {
|
|
if (Definition::Type::kMark != token.fType || MarkType::kComment != token.fMarkType) {
|
|
continue;
|
|
}
|
|
this->writeDefinition(token);
|
|
}
|
|
this->lf(2);
|
|
this->dumpClassTokens(oneClass.second);
|
|
this->lf(2);
|
|
this->writeEndTag(containerType, innerName);
|
|
this->lf(2);
|
|
}
|
|
this->dumpClassTokens(classMap);
|
|
this->writeEndTag(containerType, skClassName);
|
|
this->lf(2);
|
|
this->writeEndTag("Topic", topicName);
|
|
this->lfAlways(1);
|
|
fclose(fOut);
|
|
SkDebugf("wrote %s\n", fileName.c_str());
|
|
return true;
|
|
}
|
|
|
|
void IncludeParser::dumpTypedef(const Definition& token, string className) {
|
|
this->writeTag("Typedef");
|
|
this->writeSpace();
|
|
this->writeString(token.fName);
|
|
this->writeTagTable("Line", "incomplete");
|
|
this->lf(2);
|
|
this->dumpComment(token);
|
|
}
|
|
|
|
string IncludeParser::elidedCodeBlock(const Definition& iDef) {
|
|
SkASSERT(KeyWord::kStruct == iDef.fKeyWord || KeyWord::kClass == iDef.fKeyWord
|
|
|| KeyWord::kTemplate == iDef.fKeyWord);
|
|
TextParser i(&iDef);
|
|
fElided = Elided::kYes;
|
|
MarkType markType = MarkType::kClass;
|
|
if (KeyWord::kTemplate == iDef.fKeyWord) { // may be function
|
|
for (auto child : iDef.fChildren) {
|
|
if (MarkType::kMethod == child->fMarkType) {
|
|
markType = MarkType::kFunction;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return this->writeCodeBlock(i, markType, 0);
|
|
}
|
|
|
|
string IncludeParser::filteredBlock(string inContents, string filterContents) {
|
|
string result;
|
|
const unordered_map<string, Definition*>* mapPtr = nullptr;
|
|
if ("Constant" == inContents) {
|
|
mapPtr = &fIConstMap;
|
|
} else {
|
|
SkASSERT(0); // only Constant supported for now
|
|
}
|
|
vector<Definition*> consts;
|
|
for (auto entry : *mapPtr) {
|
|
if (string::npos == entry.first.find(filterContents)) {
|
|
continue;
|
|
}
|
|
consts.push_back(entry.second);
|
|
}
|
|
std::sort(consts.begin(), consts.end(), [](Definition* def1, Definition* def2) {
|
|
return def1->fLineCount < def2->fLineCount;
|
|
} );
|
|
for (auto oneConst : consts) {
|
|
result += this->writeCodeBlock(*oneConst);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool IncludeParser::findCommentAfter(const Definition& includeDef, Definition* markupDef) {
|
|
this->checkName(markupDef);
|
|
const Definition* parent = includeDef.fParent;
|
|
int index = includeDef.fParentIndex;
|
|
auto wordIter = parent->fTokens.begin();
|
|
std::advance(wordIter, index);
|
|
SkASSERT(&*wordIter == &includeDef);
|
|
size_t commentLine = 0;
|
|
do {
|
|
wordIter = std::next(wordIter);
|
|
if (parent->fTokens.end() == wordIter) {
|
|
break;
|
|
}
|
|
commentLine = wordIter->fLineCount;
|
|
} while (Punctuation::kSemicolon != wordIter->fPunctuation);
|
|
wordIter = std::next(wordIter);
|
|
if (parent->fTokens.end() != wordIter && Bracket::kSlashSlash == wordIter->fBracket
|
|
&& wordIter->fLineCount == commentLine) {
|
|
return this->parseComment(wordIter->fFileName, wordIter->fContentStart,
|
|
wordIter->fContentEnd, wordIter->fLineCount, markupDef, &markupDef->fUndocumented);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool IncludeParser::findComments(const Definition& includeDef, Definition* markupDef) {
|
|
this->checkName(markupDef);
|
|
// add comment preceding class, if any
|
|
Definition* parent = includeDef.fParent;
|
|
int index = includeDef.fParentIndex;
|
|
auto wordIter = parent->fTokens.begin();
|
|
std::advance(wordIter, index);
|
|
SkASSERT(&*wordIter == &includeDef);
|
|
while (parent->fTokens.begin() != wordIter) {
|
|
auto testIter = std::prev(wordIter);
|
|
if (Definition::Type::kWord != testIter->fType
|
|
&& Definition::Type::kKeyWord != testIter->fType
|
|
&& (Definition::Type::kBracket != testIter->fType
|
|
|| Bracket::kAngle != testIter->fBracket)
|
|
&& (Definition::Type::kPunctuation != testIter->fType
|
|
|| Punctuation::kAsterisk != testIter->fPunctuation)) {
|
|
break;
|
|
}
|
|
wordIter = testIter;
|
|
}
|
|
auto commentIter = wordIter;
|
|
while (parent->fTokens.begin() != commentIter) {
|
|
auto testIter = std::prev(commentIter);
|
|
bool isComment = Definition::Type::kBracket == testIter->fType
|
|
&& (Bracket::kSlashSlash == testIter->fBracket
|
|
|| Bracket::kSlashStar == testIter->fBracket);
|
|
if (!isComment) {
|
|
break;
|
|
}
|
|
commentIter = testIter;
|
|
}
|
|
while (commentIter != wordIter) {
|
|
if (!this->parseComment(commentIter->fFileName, commentIter->fContentStart,
|
|
commentIter->fContentEnd, commentIter->fLineCount, markupDef,
|
|
&markupDef->fUndocumented)) {
|
|
return false;
|
|
}
|
|
commentIter->fUndocumented = markupDef->fUndocumented;
|
|
commentIter = std::next(commentIter);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
Definition* IncludeParser::findIncludeObject(const Definition& includeDef, MarkType markType,
|
|
string typeName) {
|
|
typedef Definition* DefinitionPtr;
|
|
auto mapIter = std::find_if(fMaps.begin(), fMaps.end(),
|
|
[markType](DefinitionMap& defMap){ return markType == defMap.fMarkType; } );
|
|
if (mapIter == fMaps.end()) {
|
|
return nullptr;
|
|
}
|
|
if (mapIter->fInclude->end() == mapIter->fInclude->find(typeName)) {
|
|
return reportError<DefinitionPtr>("invalid mark type");
|
|
}
|
|
string name = this->uniqueName(*mapIter->fInclude, typeName);
|
|
Definition& markupDef = *(*mapIter->fInclude)[name];
|
|
if (markupDef.fStart) {
|
|
return reportError<DefinitionPtr>("definition already defined");
|
|
}
|
|
markupDef.fFileName = fFileName;
|
|
markupDef.fStart = includeDef.fStart;
|
|
markupDef.fContentStart = includeDef.fStart;
|
|
this->checkName(&markupDef);
|
|
markupDef.fName = name;
|
|
markupDef.fContentEnd = includeDef.fContentEnd;
|
|
markupDef.fTerminator = includeDef.fTerminator;
|
|
markupDef.fParent = fParent;
|
|
markupDef.fLineCount = includeDef.fLineCount;
|
|
markupDef.fMarkType = markType;
|
|
markupDef.fKeyWord = includeDef.fKeyWord;
|
|
markupDef.fType = Definition::Type::kMark;
|
|
return &markupDef;
|
|
}
|
|
|
|
Definition* IncludeParser::findMethod(const Definition& bmhDef) {
|
|
auto doubleColon = bmhDef.fName.rfind("::");
|
|
if (string::npos == doubleColon) {
|
|
const auto& iGlobalMethod = fIFunctionMap.find(bmhDef.fName);
|
|
SkASSERT(fIFunctionMap.end() != iGlobalMethod);
|
|
return iGlobalMethod->second;
|
|
}
|
|
string className = bmhDef.fName.substr(0, doubleColon);
|
|
const auto& iClass = fIClassMap.find(className);
|
|
if (fIClassMap.end() == iClass) {
|
|
return nullptr;
|
|
}
|
|
string methodName = bmhDef.fName.substr(doubleColon + 2);
|
|
auto& iTokens = iClass->second.fTokens;
|
|
const auto& iMethod = std::find_if(iTokens.begin(), iTokens.end(),
|
|
[methodName](Definition& token) {
|
|
return MarkType::kMethod == token.fMarkType
|
|
&& !token.fUndocumented
|
|
&& (methodName == token.fName
|
|
|| methodName == token.fName + "()"); } );
|
|
if (iTokens.end() != iMethod) {
|
|
return &*iMethod;
|
|
}
|
|
size_t subClassPos = className.rfind("::");
|
|
if (string::npos != subClassPos) {
|
|
className = className.substr(subClassPos + 2);
|
|
}
|
|
// match may be constructor; compare strings to see if this is so
|
|
if (string::npos == methodName.find('(')) {
|
|
return nullptr;
|
|
}
|
|
auto stripper = [](string s) -> string {
|
|
bool last = false;
|
|
string result;
|
|
for (char c : s) {
|
|
if (' ' >= c) {
|
|
if (!last) {
|
|
last = true;
|
|
result += ' ';
|
|
}
|
|
continue;
|
|
}
|
|
result += c;
|
|
last = false;
|
|
}
|
|
return result;
|
|
};
|
|
string strippedMethodName = stripper(methodName);
|
|
if (strippedMethodName == methodName) {
|
|
strippedMethodName = "";
|
|
}
|
|
const auto& cMethod = std::find_if(iTokens.begin(), iTokens.end(),
|
|
[className, methodName, stripper, strippedMethodName](Definition& token) {
|
|
if (MarkType::kMethod != token.fMarkType) {
|
|
return false;
|
|
}
|
|
if (token.fUndocumented) {
|
|
return false;
|
|
}
|
|
TextParser parser(&token);
|
|
const char* match = parser.strnstr(className.c_str(), parser.fEnd);
|
|
if (!match) {
|
|
return false;
|
|
}
|
|
parser.skipTo(match);
|
|
parser.skipExact(className.c_str());
|
|
if ('(' != parser.peek()) {
|
|
return false;
|
|
}
|
|
parser.skipToBalancedEndBracket('(', ')');
|
|
string iMethodName(match, parser.fChar - match);
|
|
if (methodName == iMethodName) {
|
|
return true;
|
|
}
|
|
if (strippedMethodName.empty()) {
|
|
return false;
|
|
}
|
|
string strippedIName = stripper(iMethodName);
|
|
return strippedIName == strippedMethodName;
|
|
} );
|
|
SkAssertResult(iTokens.end() != cMethod);
|
|
return &*cMethod;
|
|
}
|
|
|
|
Definition* IncludeParser::parentBracket(Definition* parent) const {
|
|
while (parent && Definition::Type::kBracket != parent->fType) {
|
|
parent = parent->fParent;
|
|
}
|
|
return parent;
|
|
}
|
|
|
|
Bracket IncludeParser::grandParentBracket() const {
|
|
Definition* parent = parentBracket(fParent);
|
|
parent = parentBracket(parent ? parent->fParent : nullptr);
|
|
return parent ? parent->fBracket : Bracket::kNone;
|
|
}
|
|
|
|
bool IncludeParser::inAlignAs() const {
|
|
if (fParent->fTokens.size() < 2) {
|
|
return false;
|
|
}
|
|
auto reverseIter = fParent->fTokens.end();
|
|
bool checkForBracket = true;
|
|
while (fParent->fTokens.begin() != reverseIter) {
|
|
std::advance(reverseIter, -1);
|
|
if (checkForBracket) {
|
|
if (Definition::Type::kBracket != reverseIter->fType) {
|
|
return false;
|
|
}
|
|
if (Bracket::kParen != reverseIter->fBracket) {
|
|
return false;
|
|
}
|
|
checkForBracket = false;
|
|
continue;
|
|
}
|
|
if (Definition::Type::kKeyWord != reverseIter->fType) {
|
|
return false;
|
|
}
|
|
return KeyWord::kAlignAs == reverseIter->fKeyWord;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
const Definition* IncludeParser::include(string match) const {
|
|
for (auto& entry : fIncludeMap) {
|
|
if (string::npos == entry.first.find(match)) {
|
|
continue;
|
|
}
|
|
return &entry.second;
|
|
}
|
|
SkASSERT(0);
|
|
return nullptr;
|
|
}
|
|
|
|
// caller just returns, so report error here
|
|
bool IncludeParser::parseClass(Definition* includeDef, IsStruct isStruct) {
|
|
SkASSERT(includeDef->fTokens.size() > 0);
|
|
// parse class header
|
|
auto iter = includeDef->fTokens.begin();
|
|
if (!strncmp(iter->fStart, "SK_API", iter->fContentEnd - iter->fStart)) {
|
|
// todo : documentation is ignoring this for now
|
|
iter = std::next(iter);
|
|
}
|
|
bool hasAlignAs = iter->fKeyWord == KeyWord::kAlignAs;
|
|
if (hasAlignAs) {
|
|
iter = std::next(iter);
|
|
if (Definition::Type::kBracket != iter->fType || Bracket::kParen != iter->fBracket) {
|
|
return includeDef->reportError<bool>("expected alignas argument");
|
|
}
|
|
iter = std::next(iter);
|
|
}
|
|
string nameStr(iter->fStart, iter->fContentEnd - iter->fStart);
|
|
includeDef->fName = nameStr;
|
|
this->checkName(includeDef);
|
|
iter = std::next(iter);
|
|
if (iter == includeDef->fTokens.end()) {
|
|
return true; // forward declaration only
|
|
}
|
|
do {
|
|
if (iter == includeDef->fTokens.end()) {
|
|
return includeDef->reportError<bool>("unexpected end");
|
|
}
|
|
if ('{' == iter->fStart[0] && Definition::Type::kPunctuation == iter->fType) {
|
|
break;
|
|
}
|
|
} while (static_cast<void>(iter = std::next(iter)), true);
|
|
if (Punctuation::kLeftBrace != iter->fPunctuation) {
|
|
return iter->reportError<bool>("expected left brace");
|
|
}
|
|
IClassDefinition* markupDef = this->defineClass(*includeDef, nameStr);
|
|
if (!markupDef) {
|
|
return iter->reportError<bool>("expected markup definition");
|
|
}
|
|
markupDef->fStart = iter->fStart;
|
|
if (!this->findComments(*includeDef, markupDef)) {
|
|
return iter->reportError<bool>("find comments failed");
|
|
}
|
|
if (markupDef->fUndocumented) {
|
|
includeDef->fUndocumented = true;
|
|
}
|
|
// if (1 != includeDef->fChildren.size()) {
|
|
// return false; // fix me: SkCanvasClipVisitor isn't correctly parsed
|
|
// }
|
|
auto includeDefIter = includeDef->fChildren.begin();
|
|
if (hasAlignAs) {
|
|
SkASSERT(includeDef->fChildren.end() != includeDefIter);
|
|
SkASSERT(Bracket::kParen == (*includeDefIter)->fBracket);
|
|
std::advance(includeDefIter, 1);
|
|
}
|
|
if (includeDef->fChildren.end() != includeDefIter
|
|
&& Bracket::kAngle == (*includeDefIter)->fBracket) {
|
|
std::advance(includeDefIter, 1);
|
|
}
|
|
includeDef = *includeDefIter;
|
|
SkASSERT(Bracket::kBrace == includeDef->fBracket);
|
|
iter = includeDef->fTokens.begin();
|
|
// skip until public
|
|
int publicIndex = 0;
|
|
if (IsStruct::kNo == isStruct) {
|
|
const char* publicName = kKeyWords[(int) KeyWord::kPublic].fName;
|
|
size_t publicLen = strlen(publicName);
|
|
while (iter != includeDef->fTokens.end()
|
|
&& (publicLen != (size_t) (iter->fContentEnd - iter->fStart)
|
|
|| strncmp(iter->fStart, publicName, publicLen))) {
|
|
iter->fPrivate = true;
|
|
iter = std::next(iter);
|
|
++publicIndex;
|
|
}
|
|
}
|
|
int keyIndex = publicIndex;
|
|
KeyWord currentKey = KeyWord::kPublic;
|
|
const char* publicName = kKeyWords[(int) KeyWord::kPublic].fName;
|
|
size_t publicLen = strlen(publicName);
|
|
const char* protectedName = kKeyWords[(int) KeyWord::kProtected].fName;
|
|
size_t protectedLen = strlen(protectedName);
|
|
const char* privateName = kKeyWords[(int) KeyWord::kPrivate].fName;
|
|
size_t privateLen = strlen(privateName);
|
|
auto childIter = includeDef->fChildren.begin();
|
|
while (includeDef->fChildren.end() != childIter && (*childIter)->fPrivate) {
|
|
std::advance(childIter, 1);
|
|
}
|
|
while (childIter != includeDef->fChildren.end()) {
|
|
Definition* child = *childIter;
|
|
while (child->fParentIndex > keyIndex && iter != includeDef->fTokens.end()) {
|
|
iter->fPrivate = KeyWord::kPublic != currentKey;
|
|
const char* testStart = iter->fStart;
|
|
size_t testLen = (size_t) (iter->fContentEnd - testStart);
|
|
iter = std::next(iter);
|
|
++keyIndex;
|
|
if (publicLen == testLen && !strncmp(testStart, publicName, testLen)) {
|
|
currentKey = KeyWord::kPublic;
|
|
break;
|
|
}
|
|
if (protectedLen == testLen && !strncmp(testStart, protectedName, testLen)) {
|
|
currentKey = KeyWord::kProtected;
|
|
break;
|
|
}
|
|
if (privateLen == testLen && !strncmp(testStart, privateName, testLen)) {
|
|
currentKey = KeyWord::kPrivate;
|
|
break;
|
|
}
|
|
}
|
|
fLastObject = nullptr;
|
|
if (KeyWord::kPublic == currentKey) {
|
|
if (!this->parseObject(child, markupDef)) {
|
|
return false;
|
|
}
|
|
}
|
|
fLastObject = child;
|
|
childIter = std::next(childIter);
|
|
}
|
|
while (iter != includeDef->fTokens.end()) {
|
|
iter->fPrivate = KeyWord::kPublic != currentKey;
|
|
iter = std::next(iter);
|
|
}
|
|
SkASSERT(fParent->fParent);
|
|
fParent = fParent->fParent;
|
|
return true;
|
|
}
|
|
|
|
bool IncludeParser::isUndocumentable(string filename, const char* start, const char* end,
|
|
int lineCount) {
|
|
TextParser parser(filename, start, end, lineCount);
|
|
const vector<string> skipWords = { "deprecated", "experimental", "private" };
|
|
const vector<string> butNot = { "to be deprecated", "may be deprecated" };
|
|
const vector<string> alsoNot = { "todo" };
|
|
string match = parser.anyWord(skipWords, 0);
|
|
if ("" != match) {
|
|
if (parser.anyWord(alsoNot, 0).empty()
|
|
&& ("deprecated" != match || parser.anyWord(butNot, 2).empty())) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool IncludeParser::parseComment(string filename, const char* start, const char* end,
|
|
int lineCount, Definition* markupDef, bool* undocumentedPtr) {
|
|
if (this->isUndocumentable(filename, start, end, lineCount)) {
|
|
*undocumentedPtr = true;
|
|
}
|
|
// parse doxygen if present
|
|
TextParser parser(filename, start, end, lineCount);
|
|
if (parser.startsWith("**")) {
|
|
parser.next();
|
|
parser.next();
|
|
parser.skipWhiteSpace();
|
|
if ('\\' == parser.peek()) {
|
|
parser.next();
|
|
// Doxygen tag may be "file" or "fn" in addition to "class", "enum", "struct"
|
|
if (parser.skipExact("file")) {
|
|
if (Definition::Type::kFileType != fParent->fType) {
|
|
return reportError<bool>("expected parent is file");
|
|
}
|
|
string filename = markupDef->fileName();
|
|
if (!parser.skipWord(filename.c_str())) {
|
|
return reportError<bool>("missing object type");
|
|
}
|
|
} else if (parser.skipExact("fn")) {
|
|
SkASSERT(0); // incomplete
|
|
} else {
|
|
if (!parser.skipWord(kKeyWords[(int) markupDef->fKeyWord].fName)) {
|
|
return reportError<bool>("missing object type");
|
|
}
|
|
if (!parser.skipWord(markupDef->fName.c_str()) &&
|
|
KeyWord::kEnum != markupDef->fKeyWord) {
|
|
return reportError<bool>("missing object name");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// remove leading '*' if present
|
|
Definition* parent = markupDef->fTokens.size() ? &markupDef->fTokens.back() : markupDef;
|
|
while (!parser.eof() && parser.skipWhiteSpace()) {
|
|
while ('*' == parser.peek()) {
|
|
parser.next();
|
|
if (parser.eof()) {
|
|
break;
|
|
}
|
|
parser.skipWhiteSpace();
|
|
}
|
|
if (parser.eof()) {
|
|
break;
|
|
}
|
|
const char* lineEnd = parser.trimmedLineEnd();
|
|
markupDef->fTokens.emplace_back(MarkType::kComment, parser.fChar, lineEnd,
|
|
parser.fLineCount, parent, '\0');
|
|
parser.skipToEndBracket('\n');
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
find comment either in front of or after the const def and then extract if the
|
|
const is undocumented
|
|
*/
|
|
bool IncludeParser::parseConst(Definition* child, Definition* markupDef) {
|
|
if (!markupDef) {
|
|
fGlobals.emplace_back(MarkType::kConst, child->fContentStart, child->fContentEnd,
|
|
child->fLineCount, fParent, '\0');
|
|
Definition* globalMarkupChild = &fGlobals.back();
|
|
string globalUniqueName = this->uniqueName(fIConstMap, child->fName);
|
|
globalMarkupChild->fName = globalUniqueName;
|
|
if (!this->findComments(*child, globalMarkupChild)) {
|
|
return false;
|
|
}
|
|
if (!this->findCommentAfter(*child, globalMarkupChild)) {
|
|
return false;
|
|
}
|
|
if (globalMarkupChild->fUndocumented) {
|
|
child->fUndocumented = true;
|
|
} else {
|
|
fIConstMap[globalUniqueName] = globalMarkupChild;
|
|
}
|
|
return true;
|
|
}
|
|
markupDef->fTokens.emplace_back(MarkType::kConst, child->fContentStart, child->fContentEnd,
|
|
child->fLineCount, markupDef, '\0');
|
|
Definition* markupChild = &markupDef->fTokens.back();
|
|
markupChild->fName = child->fName;
|
|
markupChild->fTerminator = markupChild->fContentEnd;
|
|
IClassDefinition& classDef = fIClassMap[markupDef->fName];
|
|
classDef.fConsts[child->fName] = markupChild;
|
|
if (!this->findComments(*child, markupChild)) {
|
|
return false;
|
|
}
|
|
if (!this->findCommentAfter(*child, markupChild)) {
|
|
return false;
|
|
}
|
|
if (markupChild->fUndocumented) {
|
|
child->fUndocumented = true;
|
|
} else {
|
|
fIConstMap[child->fName] = markupChild;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool IncludeParser::parseDefine(Definition* child, Definition* markupDef) {
|
|
TextParser parser(child);
|
|
if (!parser.skipExact("#define")) {
|
|
return false;
|
|
}
|
|
if (!parser.skipSpace()) {
|
|
return false;
|
|
}
|
|
const char* nameStart = parser.fChar;
|
|
parser.skipToNonAlphaNum(); // FIXME: just want to skip isalnum() and '_'
|
|
if (parser.eof()) {
|
|
return true; // do nothing if #define doesn't define anything
|
|
}
|
|
string nameStr(nameStart, parser.fChar - nameStart);
|
|
struct Param {
|
|
const char* fStart;
|
|
const char* fEnd;
|
|
};
|
|
vector<Param> params;
|
|
if ('(' == parser.peek()) {
|
|
parser.next();
|
|
if (!parser.skipSpace()) {
|
|
return false;
|
|
}
|
|
do {
|
|
const char* paramStart = parser.fChar;
|
|
if (!parser.skipExact("...")) {
|
|
parser.skipToNonAlphaNum();
|
|
}
|
|
if (parser.eof()) {
|
|
return false;
|
|
}
|
|
params.push_back({paramStart, parser.fChar});
|
|
if (!parser.skipSpace()) {
|
|
return false;
|
|
}
|
|
if (')' == parser.peek()) {
|
|
parser.next();
|
|
break;
|
|
}
|
|
if (',' != parser.next()) {
|
|
return false;
|
|
}
|
|
if (!parser.skipSpace()) {
|
|
return false;
|
|
}
|
|
} while (true);
|
|
}
|
|
if (!parser.skipSpace()) {
|
|
return false;
|
|
}
|
|
if (!markupDef) {
|
|
fGlobals.emplace_back(MarkType::kDefine, nameStart, child->fContentEnd,
|
|
child->fLineCount, fParent, '\0');
|
|
Definition* globalMarkupChild = &fGlobals.back();
|
|
string globalUniqueName = this->uniqueName(fIDefineMap, nameStr);
|
|
globalMarkupChild->fName = globalUniqueName;
|
|
globalMarkupChild->fTerminator = child->fContentEnd;
|
|
if (!this->findComments(*child, globalMarkupChild)) {
|
|
return false;
|
|
}
|
|
if (!globalMarkupChild->fUndocumented) {
|
|
fIDefineMap[globalUniqueName] = globalMarkupChild;
|
|
}
|
|
for (Param param : params) {
|
|
globalMarkupChild->fTokens.emplace_back(MarkType::kParam, param.fStart, param.fEnd,
|
|
child->fLineCount, globalMarkupChild, '\0');
|
|
Definition* paramChild = &globalMarkupChild->fTokens.back();
|
|
paramChild->fName = string(param.fStart, param.fEnd - param.fStart);
|
|
this->checkName(paramChild);
|
|
paramChild->fTerminator = param.fEnd;
|
|
}
|
|
return true;
|
|
}
|
|
markupDef->fTokens.emplace_back(MarkType::kDefine, child->fContentStart, child->fContentEnd,
|
|
child->fLineCount, markupDef, '\0');
|
|
Definition* markupChild = &markupDef->fTokens.back();
|
|
markupChild->fName = nameStr;
|
|
markupChild->fTerminator = markupChild->fContentEnd;
|
|
IClassDefinition& classDef = fIClassMap[markupDef->fName];
|
|
if (!this->findComments(*child, markupChild)) {
|
|
return false;
|
|
}
|
|
if (markupChild->fUndocumented) {
|
|
child->fUndocumented = true;
|
|
} else {
|
|
classDef.fDefines[nameStr] = markupChild;
|
|
fIDefineMap[nameStr] = markupChild;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool IncludeParser::parseEnum(Definition* child, Definition* markupDef) {
|
|
if (!child->fTokens.size()) {
|
|
return true; // if enum is a forward declaration, do nothing
|
|
}
|
|
bool isEnumClass = false;
|
|
Definition* parent = child;
|
|
auto token = parent->fTokens.begin();
|
|
if (Definition::Type::kKeyWord == token->fType && KeyWord::kClass == token->fKeyWord) {
|
|
isEnumClass = true;
|
|
parent = &*token;
|
|
token = parent->fTokens.begin();
|
|
}
|
|
SkASSERT(Definition::Type::kWord == token->fType);
|
|
string nameStr = string(token->fStart, token->fContentEnd - token->fStart);
|
|
Definition* markupChild;
|
|
if (!markupDef) {
|
|
fGlobals.emplace_back(MarkType::kEnum, child->fContentStart, child->fContentEnd,
|
|
child->fLineCount, fParent, '\0');
|
|
markupChild = &fGlobals.back();
|
|
string globalUniqueName = this->uniqueName(fIEnumMap, nameStr);
|
|
markupChild->fName = globalUniqueName;
|
|
markupChild->fTerminator = child->fContentEnd;
|
|
if (!markupChild->fUndocumented) {
|
|
fIEnumMap[globalUniqueName] = markupChild;
|
|
}
|
|
} else {
|
|
markupDef->fTokens.emplace_back(MarkType::kEnum, child->fContentStart, child->fContentEnd,
|
|
child->fLineCount, markupDef, '\0');
|
|
markupChild = &markupDef->fTokens.back();
|
|
}
|
|
SkASSERT(KeyWord::kNone == markupChild->fKeyWord);
|
|
markupChild->fKeyWord = KeyWord::kEnum;
|
|
if (isEnumClass) {
|
|
markupChild->fMarkType = MarkType::kEnumClass;
|
|
}
|
|
if (markupDef) {
|
|
markupChild->fName = markupDef->fName + "::" + nameStr;
|
|
}
|
|
if (!this->findComments(*child, markupChild)) {
|
|
return false;
|
|
}
|
|
if (markupChild->fUndocumented) {
|
|
child->fUndocumented = true;
|
|
}
|
|
if (!this->parseEnumConst(token, parent->fTokens.end(), markupChild)) {
|
|
return false;
|
|
}
|
|
for (auto outsideMember : child->fChildren) {
|
|
if (Definition::Type::kBracket == outsideMember->fType) {
|
|
continue;
|
|
}
|
|
SkASSERT(Definition::Type::kKeyWord == outsideMember->fType);
|
|
if (KeyWord::kClass == outsideMember->fKeyWord) {
|
|
continue;
|
|
}
|
|
SkASSERT(KeyWord::kStatic == outsideMember->fKeyWord);
|
|
markupChild->fTokens.emplace_back(MarkType::kMember, outsideMember->fContentStart,
|
|
outsideMember->fContentEnd, outsideMember->fLineCount, markupChild, '\0');
|
|
Definition* member = &markupChild->fTokens.back();
|
|
member->fName = outsideMember->fName;
|
|
this->checkName(member);
|
|
// FIXME: ? add comment as well ?
|
|
markupChild->fChildren.push_back(member);
|
|
}
|
|
if (markupDef) {
|
|
IClassDefinition& classDef = fIClassMap[markupDef->fName];
|
|
SkASSERT(classDef.fStart);
|
|
string uniqueName = this->uniqueName(classDef.fEnums, nameStr);
|
|
string fullName = markupChild->fName;
|
|
markupChild->fName = uniqueName;
|
|
classDef.fEnums[uniqueName] = markupChild;
|
|
if (!markupChild->fUndocumented) {
|
|
fIEnumMap[fullName] = markupChild;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool IncludeParser::parseOneEnumConst(list<Definition>& constList,
|
|
Definition* markupChild, bool skipWord) {
|
|
auto memberIter = constList.begin();
|
|
const auto memberIterEnd = constList.end();
|
|
if (skipWord) {
|
|
SkASSERT(Definition::Type::kWord == memberIter->fType);
|
|
memberIter = std::next(memberIter);
|
|
SkASSERT(memberIterEnd != memberIter);
|
|
}
|
|
// token array has parse atoms; child array has comments
|
|
bool undocumented = false;
|
|
while (memberIterEnd != memberIter) {
|
|
while (Bracket::kSlashStar == memberIter->fBracket) {
|
|
if (!this->parseComment(memberIter->fFileName, memberIter->fContentStart,
|
|
memberIter->fContentEnd, memberIter->fLineCount, markupChild, &undocumented)) {
|
|
return false;
|
|
}
|
|
memberIter = std::next(memberIter);
|
|
if (memberIterEnd == memberIter) {
|
|
return false;
|
|
}
|
|
}
|
|
if (Bracket::kPound == memberIter->fBracket) {
|
|
KeyWord keyWord = memberIter->fKeyWord;
|
|
bool sawIf = KeyWord::kIfdef == keyWord || KeyWord::kIf == keyWord
|
|
|| KeyWord::kElif == keyWord;
|
|
if (sawIf || KeyWord::kElse == keyWord) {
|
|
if (!parseOneEnumConst(memberIter->fTokens, markupChild, sawIf)) {
|
|
return false;
|
|
}
|
|
} else {
|
|
SkASSERT(KeyWord::kEndif == keyWord || KeyWord::kError == keyWord);
|
|
}
|
|
memberIter = std::next(memberIter);
|
|
if (memberIterEnd == memberIter) {
|
|
break;
|
|
}
|
|
continue;
|
|
}
|
|
while (Definition::Type::kWord != memberIter->fType) {
|
|
memberIter = std::next(memberIter);
|
|
if (memberIterEnd == memberIter) {
|
|
return false;
|
|
}
|
|
}
|
|
auto memberStart = memberIter;
|
|
Definition* memberEnd = nullptr;
|
|
const char* last;
|
|
do {
|
|
last = memberIter->fContentEnd;
|
|
memberIter = std::next(memberIter);
|
|
if (memberIterEnd == memberIter) {
|
|
break;
|
|
}
|
|
memberEnd = &*memberIter;
|
|
} while (string::npos == string(last, memberIter->fContentStart).find(','));
|
|
if (!memberEnd) {
|
|
return false;
|
|
}
|
|
if (memberIterEnd != memberIter && Bracket::kSlashSlash == memberIter->fBracket) {
|
|
if (!this->parseComment(memberIter->fFileName, memberIter->fContentStart,
|
|
memberIter->fContentEnd, memberIter->fLineCount, markupChild, &undocumented)) {
|
|
return false;
|
|
}
|
|
memberIter = std::next(memberIter);
|
|
}
|
|
markupChild->fTokens.emplace_back(MarkType::kMember, memberStart->fContentStart,
|
|
memberEnd->fContentEnd, memberStart->fLineCount, markupChild, '\0');
|
|
Definition* markupMember = &markupChild->fTokens.back();
|
|
string name = string(memberStart->fContentStart, memberStart->length());
|
|
memberStart->fName = name;
|
|
markupMember->fName = name;
|
|
this->checkName(markupMember);
|
|
memberStart->fUndocumented = markupMember->fUndocumented;
|
|
memberStart->fMarkType = MarkType::kMember;
|
|
undocumented = false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool IncludeParser::parseEnumConst(list<Definition>::iterator& tokenIter,
|
|
const list<Definition>::iterator& tokenEnd, Definition* markupChild) {
|
|
SkASSERT(Definition::Type::kWord == tokenIter->fType); // should be enum name
|
|
tokenIter = std::next(tokenIter);
|
|
SkASSERT(tokenEnd != tokenIter);
|
|
if (Definition::Type::kKeyWord == tokenIter->fType) {
|
|
SkASSERT((unsigned) tokenIter->fKeyWord < SK_ARRAY_COUNT(kKeyWords));
|
|
SkASSERT(KeyProperty::kNumber == kKeyWords[(int) tokenIter->fKeyWord].fProperty);
|
|
tokenIter = std::next(tokenIter);
|
|
SkASSERT(tokenEnd != tokenIter);
|
|
}
|
|
SkASSERT(Punctuation::kLeftBrace == tokenIter->fPunctuation);
|
|
tokenIter = std::next(tokenIter);
|
|
SkASSERT(tokenEnd != tokenIter);
|
|
SkASSERT(Bracket::kBrace == tokenIter->fBracket);
|
|
return parseOneEnumConst(tokenIter->fTokens, markupChild, false);
|
|
}
|
|
|
|
bool IncludeParser::parseInclude(string name) {
|
|
fParent = &fIncludeMap[name];
|
|
fParent->fName = name;
|
|
this->checkName(fParent);
|
|
fParent->fFileName = fFileName;
|
|
fParent->fType = Definition::Type::kFileType;
|
|
fParent->fContentStart = fChar;
|
|
fParent->fContentEnd = fEnd;
|
|
// parse include file into tree
|
|
while (fChar < fEnd) {
|
|
if (!this->parseChar()) {
|
|
return false;
|
|
}
|
|
}
|
|
// parse tree and add named objects to maps
|
|
fParent = &fIncludeMap[name];
|
|
if (!this->parseObjects(fParent, nullptr)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool IncludeParser::parseMember(Definition* child, Definition* markupDef) {
|
|
const char* typeStart = child->fChildren[0]->fContentStart;
|
|
markupDef->fTokens.emplace_back(MarkType::kMember, typeStart, child->fContentStart,
|
|
child->fLineCount, markupDef, '\0');
|
|
Definition* markupChild = &markupDef->fTokens.back();
|
|
TextParser nameParser(child);
|
|
nameParser.skipToNonName();
|
|
string nameStr = string(child->fContentStart, nameParser.fChar - child->fContentStart);
|
|
IClassDefinition& classDef = fIClassMap[markupDef->fName];
|
|
string uniqueName = this->uniqueName(classDef.fMethods, nameStr);
|
|
markupChild->fName = uniqueName;
|
|
this->checkName(markupChild);
|
|
markupChild->fTerminator = markupChild->fContentEnd;
|
|
if (!markupChild->fUndocumented) {
|
|
classDef.fMembers[uniqueName] = markupChild;
|
|
}
|
|
if (child->fParentIndex >= 2) {
|
|
auto comment = child->fParent->fTokens.begin();
|
|
std::advance(comment, child->fParentIndex - 2);
|
|
if (Definition::Type::kBracket == comment->fType
|
|
&& (Bracket::kSlashStar == comment->fBracket
|
|
|| Bracket::kSlashSlash == comment->fBracket)) {
|
|
TextParser parser(&*comment);
|
|
do {
|
|
parser.skipToAlpha();
|
|
if (parser.eof()) {
|
|
break;
|
|
}
|
|
const char* start = parser.fChar;
|
|
const char* end = parser.trimmedBracketEnd('\n');
|
|
if (Bracket::kSlashStar == comment->fBracket) {
|
|
const char* commentEnd = parser.strnstr("*/", end);
|
|
if (commentEnd) {
|
|
end = commentEnd;
|
|
}
|
|
}
|
|
markupDef->fTokens.emplace_back(MarkType::kComment, start, end, child->fLineCount,
|
|
markupDef, '\0');
|
|
Definition* commentChild = &markupDef->fTokens.back();
|
|
markupChild->fChildren.emplace_back(commentChild);
|
|
parser.skipTo(end);
|
|
} while (!parser.eof());
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool IncludeParser::parseMethod(Definition* child, Definition* markupDef) {
|
|
auto tokenIter = child->fParent->fTokens.begin();
|
|
std::advance(tokenIter, child->fParentIndex);
|
|
tokenIter = std::prev(tokenIter);
|
|
const char* nameEnd = tokenIter->fContentEnd;
|
|
bool addConst = false;
|
|
auto operatorCheck = tokenIter;
|
|
if ('[' == tokenIter->fStart[0] || '*' == tokenIter->fStart[0]) {
|
|
operatorCheck = std::prev(tokenIter);
|
|
}
|
|
if (KeyWord::kOperator == operatorCheck->fKeyWord) {
|
|
auto closeParen = std::next(tokenIter);
|
|
SkASSERT(Definition::Type::kBracket == closeParen->fType &&
|
|
'(' == closeParen->fContentStart[0]);
|
|
nameEnd = closeParen->fContentEnd + 1;
|
|
closeParen = std::next(closeParen);
|
|
if (Definition::Type::kKeyWord == closeParen->fType &&
|
|
KeyWord::kConst == closeParen->fKeyWord) {
|
|
addConst = true;
|
|
}
|
|
tokenIter = operatorCheck;
|
|
}
|
|
string nameStr(tokenIter->fStart, nameEnd - tokenIter->fStart);
|
|
if (addConst) {
|
|
nameStr += " const";
|
|
}
|
|
while (tokenIter != child->fParent->fTokens.begin()) {
|
|
auto testIter = std::prev(tokenIter);
|
|
switch (testIter->fType) {
|
|
case Definition::Type::kWord:
|
|
if (testIter == child->fParent->fTokens.begin() &&
|
|
(KeyWord::kIfdef == child->fParent->fKeyWord ||
|
|
KeyWord::kIfndef == child->fParent->fKeyWord ||
|
|
KeyWord::kIf == child->fParent->fKeyWord)) {
|
|
std::next(tokenIter);
|
|
break;
|
|
}
|
|
goto keepGoing;
|
|
case Definition::Type::kKeyWord: {
|
|
KeyProperty keyProperty = kKeyWords[(int) testIter->fKeyWord].fProperty;
|
|
if (KeyProperty::kNumber == keyProperty || KeyProperty::kModifier == keyProperty) {
|
|
goto keepGoing;
|
|
}
|
|
} break;
|
|
case Definition::Type::kBracket:
|
|
if (Bracket::kAngle == testIter->fBracket) {
|
|
goto keepGoing;
|
|
}
|
|
break;
|
|
case Definition::Type::kPunctuation:
|
|
if (Punctuation::kSemicolon == testIter->fPunctuation
|
|
|| Punctuation::kLeftBrace == testIter->fPunctuation
|
|
|| Punctuation::kColon == testIter->fPunctuation) {
|
|
break;
|
|
}
|
|
keepGoing:
|
|
tokenIter = testIter;
|
|
continue;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
tokenIter->fName = nameStr; // simple token stream, OK if name is duplicate
|
|
tokenIter->fMarkType = MarkType::kMethod;
|
|
tokenIter->fPrivate = string::npos != nameStr.find("::")
|
|
&& KeyWord::kTemplate != child->fParent->fKeyWord;
|
|
this->checkName(&*tokenIter);
|
|
auto testIter = child->fParent->fTokens.begin();
|
|
SkASSERT(child->fParentIndex > 0);
|
|
std::advance(testIter, child->fParentIndex - 1);
|
|
if (tokenIter->fParent && KeyWord::kIfdef == tokenIter->fParent->fKeyWord &&
|
|
0 == tokenIter->fParentIndex) {
|
|
tokenIter = std::next(tokenIter);
|
|
}
|
|
const char* start = tokenIter->fContentStart;
|
|
const char* end = tokenIter->fContentEnd;
|
|
const char kDebugCodeStr[] = "SkDEBUGCODE";
|
|
const size_t kDebugCodeLen = sizeof(kDebugCodeStr) - 1;
|
|
if (end - start == kDebugCodeLen && !strncmp(start, kDebugCodeStr, kDebugCodeLen)) {
|
|
std::advance(testIter, 1);
|
|
start = testIter->fContentStart + 1;
|
|
end = testIter->fContentEnd - 1;
|
|
} else {
|
|
end = testIter->fContentEnd;
|
|
do {
|
|
std::advance(testIter, 1);
|
|
if (testIter == child->fParent->fTokens.end()) {
|
|
break;
|
|
}
|
|
switch (testIter->fType) {
|
|
case Definition::Type::kPunctuation:
|
|
SkASSERT(Punctuation::kSemicolon == testIter->fPunctuation
|
|
|| Punctuation::kLeftBrace == testIter->fPunctuation
|
|
|| Punctuation::kColon == testIter->fPunctuation);
|
|
end = testIter->fStart;
|
|
break;
|
|
case Definition::Type::kKeyWord: {
|
|
KeyProperty keyProperty = kKeyWords[(int) testIter->fKeyWord].fProperty;
|
|
if (KeyProperty::kNumber == keyProperty || KeyProperty::kModifier == keyProperty) {
|
|
continue;
|
|
}
|
|
} break;
|
|
default:
|
|
continue;
|
|
}
|
|
break;
|
|
} while (true);
|
|
}
|
|
while (end > start && ' ' >= end[-1]) {
|
|
--end;
|
|
}
|
|
if (!markupDef) {
|
|
auto parentIter = child->fParent->fTokens.begin();
|
|
SkASSERT(child->fParentIndex > 0);
|
|
std::advance(parentIter, child->fParentIndex - 1);
|
|
Definition* methodName = &*parentIter;
|
|
TextParser nameParser(methodName);
|
|
if (nameParser.skipToEndBracket(':') && nameParser.startsWith("::")) {
|
|
return true; // expect this is inline class definition outside of class
|
|
}
|
|
fGlobals.emplace_back(MarkType::kMethod, start, end, tokenIter->fLineCount,
|
|
fParent, '\0');
|
|
Definition* globalMarkupChild = &fGlobals.back();
|
|
string globalUniqueName = this->uniqueName(fIFunctionMap, nameStr);
|
|
globalMarkupChild->fName = globalUniqueName;
|
|
if (!this->findComments(*child, globalMarkupChild)) {
|
|
return false;
|
|
}
|
|
if (globalMarkupChild->fUndocumented) {
|
|
child->fUndocumented = true;
|
|
} else {
|
|
fIFunctionMap[globalUniqueName] = globalMarkupChild;
|
|
}
|
|
return true;
|
|
}
|
|
markupDef->fTokens.emplace_back(MarkType::kMethod, start, end, tokenIter->fLineCount,
|
|
markupDef, '\0');
|
|
Definition* markupChild = &markupDef->fTokens.back();
|
|
{
|
|
auto mapIter = fIClassMap.find(markupDef->fName);
|
|
SkASSERT(fIClassMap.end() != mapIter);
|
|
IClassDefinition& classDef = mapIter->second;
|
|
SkASSERT(classDef.fStart);
|
|
string uniqueName = this->uniqueName(classDef.fMethods, nameStr);
|
|
markupChild->fName = uniqueName;
|
|
if (!this->findComments(*child, markupChild)) {
|
|
return false;
|
|
}
|
|
if (markupChild->fUndocumented) {
|
|
tokenIter->fUndocumented = true;
|
|
} else {
|
|
classDef.fMethods[uniqueName] = markupChild;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool IncludeParser::parseObjects(Definition* parent, Definition* markupDef) {
|
|
fPriorObject = nullptr;
|
|
for (auto child : parent->fChildren) {
|
|
if (!this->parseObject(child, markupDef)) {
|
|
return false;
|
|
}
|
|
fPriorObject = child;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool IncludeParser::parseObject(Definition* child, Definition* markupDef) {
|
|
// set up for error reporting
|
|
fLine = fChar = child->fStart;
|
|
fEnd = child->fContentEnd;
|
|
// todo: put original line number in child as well
|
|
switch (child->fType) {
|
|
case Definition::Type::kKeyWord:
|
|
switch (child->fKeyWord) {
|
|
case KeyWord::kClass:
|
|
if (!this->parseClass(child, IsStruct::kNo)) {
|
|
return false;
|
|
}
|
|
break;
|
|
case KeyWord::kStatic:
|
|
case KeyWord::kConst:
|
|
case KeyWord::kConstExpr:
|
|
if (!this->parseConst(child, markupDef)) {
|
|
return child->reportError<bool>("failed to parse const or constexpr");
|
|
}
|
|
break;
|
|
case KeyWord::kEnum:
|
|
if (!this->parseEnum(child, markupDef)) {
|
|
return child->reportError<bool>("failed to parse enum");
|
|
}
|
|
break;
|
|
case KeyWord::kStruct:
|
|
if (!this->parseClass(child, IsStruct::kYes)) {
|
|
return child->reportError<bool>("failed to parse struct");
|
|
}
|
|
break;
|
|
case KeyWord::kTemplate:
|
|
if (!this->parseTemplate(child, markupDef)) {
|
|
return child->reportError<bool>("failed to parse template");
|
|
}
|
|
break;
|
|
case KeyWord::kTypedef:
|
|
if (!this->parseTypedef(child, markupDef)) {
|
|
return child->reportError<bool>("failed to parse typedef");
|
|
}
|
|
break;
|
|
case KeyWord::kUnion:
|
|
if (!this->parseUnion()) {
|
|
return child->reportError<bool>("failed to parse union");
|
|
}
|
|
break;
|
|
case KeyWord::kUsing:
|
|
if (!this->parseUsing()) {
|
|
return child->reportError<bool>("failed to parse using");
|
|
}
|
|
break;
|
|
default:
|
|
return child->reportError<bool>("unhandled keyword");
|
|
}
|
|
break;
|
|
case Definition::Type::kBracket:
|
|
switch (child->fBracket) {
|
|
case Bracket::kParen:
|
|
{
|
|
auto tokenIter = child->fParent->fTokens.begin();
|
|
std::advance(tokenIter, child->fParentIndex);
|
|
tokenIter = std::prev(tokenIter);
|
|
TextParser previousToken(&*tokenIter);
|
|
if (this->isMember(*tokenIter)) {
|
|
break;
|
|
}
|
|
if (Bracket::kPound == child->fParent->fBracket &&
|
|
KeyWord::kIf == child->fParent->fKeyWord) {
|
|
// TODO: this will skip methods named defined() -- for the
|
|
// moment there aren't any
|
|
if (previousToken.startsWith("defined")) {
|
|
break;
|
|
}
|
|
}
|
|
if (previousToken.startsWith("sizeof") && 6 == previousToken.lineLength()) {
|
|
break;
|
|
}
|
|
}
|
|
if (fPriorObject && MarkType::kConst == fPriorObject->fMarkType) {
|
|
break;
|
|
}
|
|
if (!this->parseMethod(child, markupDef)) {
|
|
return child->reportError<bool>("failed to parse method");
|
|
}
|
|
break;
|
|
case Bracket::kSlashSlash:
|
|
case Bracket::kSlashStar:
|
|
// comments are picked up by parsing objects first
|
|
break;
|
|
case Bracket::kPound:
|
|
// special-case the #xxx xxx_DEFINED entries
|
|
switch (child->fKeyWord) {
|
|
case KeyWord::kIf:
|
|
case KeyWord::kIfndef:
|
|
case KeyWord::kIfdef:
|
|
if (child->boilerplateIfDef()) {
|
|
if (!this->parseObjects(child, markupDef)) {
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
goto preproError;
|
|
case KeyWord::kDefine:
|
|
if (this->parseDefine(child, markupDef)) {
|
|
break;
|
|
}
|
|
goto preproError;
|
|
case KeyWord::kEndif:
|
|
if (child->boilerplateEndIf()) {
|
|
break;
|
|
}
|
|
case KeyWord::kError:
|
|
case KeyWord::kInclude:
|
|
// ignored for now
|
|
break;
|
|
case KeyWord::kElse:
|
|
if (!this->parseObjects(child, markupDef)) {
|
|
return false;
|
|
}
|
|
break;
|
|
case KeyWord::kElif:
|
|
// todo: handle these
|
|
break;
|
|
default:
|
|
preproError:
|
|
return child->reportError<bool>("unhandled preprocessor");
|
|
}
|
|
break;
|
|
case Bracket::kAngle:
|
|
// pick up templated function pieces when method is found
|
|
break;
|
|
case Bracket::kDebugCode:
|
|
if (!this->parseObjects(child, markupDef)) {
|
|
return false;
|
|
}
|
|
break;
|
|
case Bracket::kSquare: {
|
|
// check to see if parent is operator, the only case we handle so far
|
|
auto prev = child->fParent->fTokens.begin();
|
|
std::advance(prev, child->fParentIndex - 1);
|
|
if (KeyWord::kOperator != prev->fKeyWord) {
|
|
return child->reportError<bool>("expected operator overload");
|
|
}
|
|
} break;
|
|
default:
|
|
return child->reportError<bool>("unhandled bracket");
|
|
}
|
|
break;
|
|
case Definition::Type::kWord:
|
|
if (MarkType::kMember != child->fMarkType) {
|
|
return child->reportError<bool>("unhandled word type");
|
|
}
|
|
if (!this->parseMember(child, markupDef)) {
|
|
return child->reportError<bool>("unparsable member");
|
|
}
|
|
break;
|
|
default:
|
|
return child->reportError<bool>("unhandled type");
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool IncludeParser::parseTemplate(Definition* child, Definition* markupDef) {
|
|
return this->parseObjects(child, markupDef);
|
|
}
|
|
|
|
bool IncludeParser::parseTypedef(Definition* child, Definition* markupDef) {
|
|
TextParser typedefParser(child);
|
|
typedefParser.skipExact("typedef");
|
|
typedefParser.skipWhiteSpace();
|
|
string nameStr = typedefParser.typedefName();
|
|
if (!markupDef) {
|
|
fGlobals.emplace_back(MarkType::kTypedef, child->fContentStart, child->fContentEnd,
|
|
child->fLineCount, fParent, '\0');
|
|
Definition* globalMarkupChild = &fGlobals.back();
|
|
string globalUniqueName = this->uniqueName(fITypedefMap, nameStr);
|
|
globalMarkupChild->fName = globalUniqueName;
|
|
if (!this->findComments(*child, globalMarkupChild)) {
|
|
return false;
|
|
}
|
|
if (globalMarkupChild->fUndocumented) {
|
|
child->fUndocumented = true;
|
|
} else {
|
|
fITypedefMap[globalUniqueName] = globalMarkupChild;
|
|
}
|
|
child->fName = nameStr;
|
|
return true;
|
|
}
|
|
markupDef->fTokens.emplace_back(MarkType::kTypedef, child->fContentStart, child->fContentEnd,
|
|
child->fLineCount, markupDef, '\0');
|
|
Definition* markupChild = &markupDef->fTokens.back();
|
|
markupChild->fName = nameStr;
|
|
this->checkName(markupChild);
|
|
markupChild->fTerminator = markupChild->fContentEnd;
|
|
IClassDefinition& classDef = fIClassMap[markupDef->fName];
|
|
classDef.fTypedefs[nameStr] = markupChild;
|
|
child->fName = markupDef->fName + "::" + nameStr;
|
|
this->checkName(child);
|
|
fITypedefMap[child->fName] = markupChild;
|
|
return true;
|
|
}
|
|
|
|
bool IncludeParser::parseUnion() {
|
|
// incomplete
|
|
return true;
|
|
}
|
|
|
|
bool IncludeParser::parseUsing() {
|
|
// incomplete
|
|
return true;
|
|
}
|
|
|
|
bool IncludeParser::parseChar() {
|
|
char test = *fChar;
|
|
if ('\\' == fPrev) {
|
|
if ('\n' == test) {
|
|
// ++fLineCount;
|
|
fLine = fChar + 1;
|
|
}
|
|
goto done;
|
|
}
|
|
switch (test) {
|
|
case '\n':
|
|
// ++fLineCount;
|
|
fLine = fChar + 1;
|
|
if (fInChar) {
|
|
return reportError<bool>("malformed char");
|
|
}
|
|
if (fInString) {
|
|
return reportError<bool>("malformed string");
|
|
}
|
|
if (!this->checkForWord()) {
|
|
return false;
|
|
}
|
|
if (Bracket::kPound == this->topBracket()) {
|
|
KeyWord keyWord = fParent->fKeyWord;
|
|
if (KeyWord::kNone == keyWord) {
|
|
return this->reportError<bool>("unhandled preprocessor directive");
|
|
}
|
|
if (fInDefine) {
|
|
SkASSERT(KeyWord::kDefine == keyWord);
|
|
fInDefine = false;
|
|
}
|
|
if (KeyWord::kInclude == keyWord || KeyWord::kDefine == keyWord || KeyWord::kError == keyWord) {
|
|
this->popBracket();
|
|
}
|
|
if (fInBrace) {
|
|
SkASSERT(KeyWord::kDefine == fInBrace->fKeyWord);
|
|
fInBrace = nullptr;
|
|
}
|
|
} else if (Bracket::kSlashSlash == this->topBracket()) {
|
|
this->popBracket();
|
|
}
|
|
break;
|
|
case '*':
|
|
if (!fInCharCommentString && '/' == fPrev) {
|
|
this->pushBracket(Bracket::kSlashStar);
|
|
}
|
|
if (!this->checkForWord()) {
|
|
return false;
|
|
}
|
|
if (!fInCharCommentString) {
|
|
this->addPunctuation(Punctuation::kAsterisk);
|
|
}
|
|
break;
|
|
case '/':
|
|
if ('*' == fPrev) {
|
|
if (!fInCharCommentString) {
|
|
return reportError<bool>("malformed closing comment");
|
|
}
|
|
if (Bracket::kSlashStar == this->topBracket()) {
|
|
TextParserSave save(this);
|
|
this->next(); // include close in bracket
|
|
this->popBracket();
|
|
save.restore(); // put things back so nothing is skipped
|
|
}
|
|
break;
|
|
}
|
|
if (!fInCharCommentString && '/' == fPrev) {
|
|
this->pushBracket(Bracket::kSlashSlash);
|
|
break;
|
|
}
|
|
if (!this->checkForWord()) {
|
|
return false;
|
|
}
|
|
break;
|
|
case '\'':
|
|
if (Bracket::kChar == this->topBracket()) {
|
|
this->popBracket();
|
|
} else if (!fInComment && !fInString) {
|
|
if (fIncludeWord) {
|
|
return this->reportError<bool>("word then single-quote");
|
|
}
|
|
this->pushBracket(Bracket::kChar);
|
|
}
|
|
break;
|
|
case '\"':
|
|
if (Bracket::kString == this->topBracket()) {
|
|
this->popBracket();
|
|
} else if (!fInComment && !fInChar) {
|
|
if (fIncludeWord) {
|
|
return this->reportError<bool>("word then double-quote");
|
|
}
|
|
this->pushBracket(Bracket::kString);
|
|
}
|
|
break;
|
|
case '(':
|
|
if (fIncludeWord && fChar - fIncludeWord >= 10 &&
|
|
!strncmp("SkDEBUGCODE", fIncludeWord, 10)) {
|
|
this->pushBracket(Bracket::kDebugCode);
|
|
break;
|
|
}
|
|
case ':':
|
|
case '[':
|
|
case '{': {
|
|
if (fInCharCommentString) {
|
|
break;
|
|
}
|
|
if (fInDefine && fInBrace) {
|
|
break;
|
|
}
|
|
if (':' == test && (fInBrace || ':' == fChar[-1] || ':' == fChar[1])) {
|
|
break;
|
|
}
|
|
if (fConstExpr) {
|
|
fConstExpr->fContentEnd = fParent->fTokens.back().fContentEnd;
|
|
fConstExpr = nullptr;
|
|
}
|
|
if (!fInBrace) {
|
|
if (!this->checkForWord()) {
|
|
return false;
|
|
}
|
|
if (':' == test && !fInFunction) {
|
|
break;
|
|
}
|
|
if ('{' == test) {
|
|
this->addPunctuation(Punctuation::kLeftBrace);
|
|
} else if (':' == test) {
|
|
this->addPunctuation(Punctuation::kColon);
|
|
}
|
|
}
|
|
if (fInBrace && '{' == test && Definition::Type::kBracket == fInBrace->fType
|
|
&& Bracket::kColon == fInBrace->fBracket) {
|
|
Definition* braceParent = fParent->fParent;
|
|
braceParent->fChildren.pop_back();
|
|
braceParent->fTokens.pop_back();
|
|
fParent = braceParent;
|
|
fInBrace = nullptr;
|
|
}
|
|
this->pushBracket(
|
|
'(' == test ? Bracket::kParen :
|
|
'[' == test ? Bracket::kSquare :
|
|
'{' == test ? Bracket::kBrace :
|
|
Bracket::kColon);
|
|
if (!fInBrace
|
|
&& ('{' == test || (':' == test && ' ' >= fChar[1]))
|
|
&& fInFunction) {
|
|
fInBrace = fParent;
|
|
}
|
|
} break;
|
|
case '<':
|
|
if (fInCharCommentString || fInBrace) {
|
|
break;
|
|
}
|
|
if (!this->checkForWord()) {
|
|
return false;
|
|
}
|
|
if (fInEnum) {
|
|
break;
|
|
}
|
|
this->pushBracket(Bracket::kAngle);
|
|
// this angle bracket may be an operator or may be a bracket
|
|
// wait for balancing close angle, if any, to decide
|
|
break;
|
|
case ')':
|
|
case ']':
|
|
case '}': {
|
|
if (fInCharCommentString) {
|
|
break;
|
|
}
|
|
if (fInDefine && fInBrace) {
|
|
break;
|
|
}
|
|
if (!fInBrace) {
|
|
if (!this->checkForWord()) {
|
|
return false;
|
|
}
|
|
}
|
|
bool popBraceParent = fInBrace == fParent;
|
|
Bracket match = ')' == test ? Bracket::kParen :
|
|
']' == test ? Bracket::kSquare : Bracket::kBrace;
|
|
if (match == this->topBracket()) {
|
|
this->popBracket();
|
|
if (!fInFunction) {
|
|
fInFunction = ')' == test && !this->inAlignAs();
|
|
} else {
|
|
fInFunction = '}' != test;
|
|
}
|
|
} else if (')' == test && Bracket::kDebugCode == this->topBracket()) {
|
|
this->popBracket();
|
|
} else if (Bracket::kAngle == this->topBracket()
|
|
&& match == this->grandParentBracket()) {
|
|
this->popBracket();
|
|
this->popBracket();
|
|
} else {
|
|
return reportError<bool>("malformed close bracket");
|
|
}
|
|
if (popBraceParent) {
|
|
Definition* braceParent = fInBrace->fParent;
|
|
braceParent->fChildren.pop_back();
|
|
braceParent->fTokens.pop_back();
|
|
fInBrace = nullptr;
|
|
}
|
|
} break;
|
|
case '>':
|
|
if (fInCharCommentString || fInBrace) {
|
|
break;
|
|
}
|
|
if (!this->checkForWord()) {
|
|
return false;
|
|
}
|
|
if (fInEnum) {
|
|
break;
|
|
}
|
|
if (Bracket::kPound == this->topBracket()) {
|
|
break;
|
|
}
|
|
if (Bracket::kAngle == this->topBracket()) {
|
|
// looks like angle pair are braces, not operators
|
|
this->popBracket();
|
|
} else {
|
|
return reportError<bool>("malformed close angle bracket");
|
|
}
|
|
break;
|
|
case '#': {
|
|
if (fInCharCommentString || fInBrace) {
|
|
break;
|
|
}
|
|
SkASSERT(!fIncludeWord); // don't expect this, curious if it is triggered
|
|
this->pushBracket(Bracket::kPound);
|
|
break;
|
|
}
|
|
case ' ':
|
|
if (fInDefine && !fInBrace && Bracket::kPound == this->topBracket()) {
|
|
SkASSERT(KeyWord::kDefine == fParent->fKeyWord);
|
|
fInBrace = fParent;
|
|
// delimiting brackets are space ... unescaped-linefeed
|
|
}
|
|
case '&':
|
|
case ',':
|
|
case '+':
|
|
case '-':
|
|
case '!':
|
|
if (fInCharCommentString || fInBrace) {
|
|
break;
|
|
}
|
|
if (!this->checkForWord()) {
|
|
return false;
|
|
}
|
|
break;
|
|
case '=':
|
|
if (fInCharCommentString || fInBrace) {
|
|
break;
|
|
}
|
|
if (!this->checkForWord()) {
|
|
return false;
|
|
}
|
|
if (!fParent->fTokens.size()) {
|
|
break;
|
|
}
|
|
{
|
|
const Definition& lastToken = fParent->fTokens.back();
|
|
if (lastToken.fType != Definition::Type::kWord) {
|
|
break;
|
|
}
|
|
string name(lastToken.fContentStart, lastToken.length());
|
|
if ("SK_" != name.substr(0, 3) && 'k' != name[0]) {
|
|
break;
|
|
}
|
|
// find token on start of line
|
|
auto lineIter = fParent->fTokens.end();
|
|
do {
|
|
if (fParent->fTokens.begin() == lineIter) {
|
|
break;
|
|
}
|
|
--lineIter;
|
|
} while (lineIter->fContentStart > fLine);
|
|
if (lineIter->fContentStart < fLine && fParent->fTokens.end() != lineIter) {
|
|
++lineIter;
|
|
}
|
|
Definition* lineStart = &*lineIter;
|
|
// walk tokens looking for [template <typename T>] [static] [const | constexpr]
|
|
bool sawConst = false;
|
|
bool sawStatic = false;
|
|
bool sawTemplate = false;
|
|
bool sawType = false;
|
|
while (&lastToken != &*lineIter) {
|
|
if (KeyWord::kTemplate == lineIter->fKeyWord) {
|
|
if (sawConst || sawStatic || sawTemplate) {
|
|
sawConst = false;
|
|
break;
|
|
}
|
|
if (&lastToken == &*++lineIter) {
|
|
break;
|
|
}
|
|
if (KeyWord::kTypename != lineIter->fKeyWord) {
|
|
break;
|
|
}
|
|
if (&lastToken == &*++lineIter) {
|
|
break;
|
|
}
|
|
if (Definition::Type::kWord != lineIter->fType) {
|
|
break;
|
|
}
|
|
sawTemplate = true;
|
|
} else if (KeyWord::kStatic == lineIter->fKeyWord) {
|
|
if (sawConst || sawStatic) {
|
|
sawConst = false;
|
|
break;
|
|
}
|
|
sawStatic = true;
|
|
} else if (KeyWord::kConst == lineIter->fKeyWord
|
|
|| KeyWord::kConstExpr == lineIter->fKeyWord) {
|
|
if (sawConst) {
|
|
sawConst = false;
|
|
break;
|
|
}
|
|
sawConst = true;
|
|
} else {
|
|
if (sawType) {
|
|
sawType = false;
|
|
break;
|
|
}
|
|
if (Definition::Type::kKeyWord == lineIter->fType
|
|
&& KeyProperty::kNumber
|
|
== kKeyWords[(int) lineIter->fKeyWord].fProperty) {
|
|
sawType = true;
|
|
} else if (Definition::Type::kWord == lineIter->fType) {
|
|
string typeName(lineIter->fContentStart, lineIter->length());
|
|
if ("Sk" != name.substr(0, 2)) {
|
|
sawType = true;
|
|
}
|
|
}
|
|
}
|
|
++lineIter;
|
|
}
|
|
if (sawType && sawConst) {
|
|
// if found, name first
|
|
lineStart->fName = name;
|
|
lineStart->fMarkType = MarkType::kConst;
|
|
this->checkName(lineStart);
|
|
fParent->fChildren.emplace_back(lineStart);
|
|
fConstExpr = lineStart;
|
|
}
|
|
}
|
|
break;
|
|
case ';':
|
|
if (fInCharCommentString || fInBrace) {
|
|
break;
|
|
}
|
|
if (!this->checkForWord()) {
|
|
return false;
|
|
}
|
|
if (fConstExpr) {
|
|
fConstExpr->fContentEnd = fParent->fTokens.back().fContentEnd;
|
|
fConstExpr = nullptr;
|
|
}
|
|
if (Definition::Type::kKeyWord == fParent->fType
|
|
&& KeyProperty::kObject == (kKeyWords[(int) fParent->fKeyWord].fProperty)) {
|
|
bool parentIsClass = KeyWord::kClass == fParent->fKeyWord;
|
|
if (parentIsClass && fParent->fParent &&
|
|
KeyWord::kEnum == fParent->fParent->fKeyWord) {
|
|
this->popObject();
|
|
}
|
|
if (KeyWord::kEnum == fParent->fKeyWord) {
|
|
fInEnum = false;
|
|
}
|
|
parentIsClass |= KeyWord::kStruct == fParent->fKeyWord;
|
|
this->popObject();
|
|
if (parentIsClass && fParent && KeyWord::kTemplate == fParent->fKeyWord) {
|
|
this->popObject();
|
|
}
|
|
fPriorEnum = nullptr;
|
|
} else if (Definition::Type::kBracket == fParent->fType
|
|
&& fParent->fParent && Definition::Type::kKeyWord == fParent->fParent->fType
|
|
&& KeyWord::kStruct == fParent->fParent->fKeyWord) {
|
|
list<Definition>::iterator baseIter = fParent->fTokens.end();
|
|
list<Definition>::iterator namedIter = fParent->fTokens.end();
|
|
for (auto tokenIter = fParent->fTokens.end();
|
|
fParent->fTokens.begin() != tokenIter; ) {
|
|
--tokenIter;
|
|
if (tokenIter->fLineCount == fLineCount) {
|
|
if (this->isMember(*tokenIter)) {
|
|
if (namedIter != fParent->fTokens.end()) {
|
|
return reportError<bool>("found two named member tokens");
|
|
}
|
|
namedIter = tokenIter;
|
|
}
|
|
baseIter = tokenIter;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
// FIXME: if a member definition spans multiple lines, this won't work
|
|
if (namedIter != fParent->fTokens.end()) {
|
|
if (baseIter == namedIter) {
|
|
return this->reportError<bool>("expected type before named token");
|
|
}
|
|
Definition* member = &*namedIter;
|
|
member->fMarkType = MarkType::kMember;
|
|
if (!member->fTerminator) {
|
|
member->fTerminator = member->fContentEnd;
|
|
}
|
|
fParent->fChildren.push_back(member);
|
|
for (auto nameType = baseIter; nameType != namedIter; ++nameType) {
|
|
member->fChildren.push_back(&*nameType);
|
|
}
|
|
}
|
|
fPriorEnum = nullptr;
|
|
} else if (fParent->fChildren.size() > 0) {
|
|
auto lastIter = fParent->fChildren.end();
|
|
Definition* priorEnum = fPriorEnum;
|
|
fPriorEnum = nullptr;
|
|
if (!priorEnum) {
|
|
while (fParent->fChildren.begin() != lastIter) {
|
|
std::advance(lastIter, -1);
|
|
priorEnum = *lastIter;
|
|
if (Definition::Type::kBracket != priorEnum->fType ||
|
|
(Bracket::kSlashSlash != priorEnum->fBracket
|
|
&& Bracket::kSlashStar != priorEnum->fBracket)) {
|
|
break;
|
|
}
|
|
}
|
|
fPriorIndex = priorEnum->fParentIndex;
|
|
}
|
|
if (Definition::Type::kKeyWord == priorEnum->fType
|
|
&& KeyWord::kEnum == priorEnum->fKeyWord) {
|
|
auto tokenWalker = fParent->fTokens.begin();
|
|
std::advance(tokenWalker, fPriorIndex);
|
|
while (tokenWalker != fParent->fTokens.end()) {
|
|
std::advance(tokenWalker, 1);
|
|
++fPriorIndex;
|
|
if (Punctuation::kSemicolon == tokenWalker->fPunctuation) {
|
|
break;
|
|
}
|
|
}
|
|
while (tokenWalker != fParent->fTokens.end()) {
|
|
std::advance(tokenWalker, 1);
|
|
const Definition* test = &*tokenWalker;
|
|
if (Definition::Type::kBracket != test->fType ||
|
|
(Bracket::kSlashSlash != test->fBracket
|
|
&& Bracket::kSlashStar != test->fBracket)) {
|
|
break;
|
|
}
|
|
}
|
|
auto saveTokenWalker = tokenWalker;
|
|
Definition* start = &*tokenWalker;
|
|
bool foundExpected = true;
|
|
for (KeyWord expected : {KeyWord::kStatic, KeyWord::kConstExpr, KeyWord::kInt}){
|
|
const Definition* test = &*tokenWalker;
|
|
if (expected != test->fKeyWord) {
|
|
foundExpected = false;
|
|
break;
|
|
}
|
|
if (tokenWalker == fParent->fTokens.end()) {
|
|
break;
|
|
}
|
|
std::advance(tokenWalker, 1);
|
|
}
|
|
if (!foundExpected) {
|
|
foundExpected = true;
|
|
tokenWalker = saveTokenWalker;
|
|
for (KeyWord expected : {KeyWord::kStatic, KeyWord::kConst, KeyWord::kNone}){
|
|
const Definition* test = &*tokenWalker;
|
|
if (expected != test->fKeyWord) {
|
|
foundExpected = false;
|
|
break;
|
|
}
|
|
if (tokenWalker == fParent->fTokens.end()) {
|
|
break;
|
|
}
|
|
if (KeyWord::kNone != expected) {
|
|
std::advance(tokenWalker, 1);
|
|
}
|
|
}
|
|
if (foundExpected) {
|
|
auto nameToken = priorEnum->fTokens.begin();
|
|
string enumName = string(nameToken->fContentStart,
|
|
nameToken->fContentEnd - nameToken->fContentStart);
|
|
const Definition* test = &*tokenWalker;
|
|
string constType = string(test->fContentStart,
|
|
test->fContentEnd - test->fContentStart);
|
|
if (enumName != constType) {
|
|
foundExpected = false;
|
|
} else {
|
|
std::advance(tokenWalker, 1);
|
|
}
|
|
}
|
|
}
|
|
if (foundExpected && tokenWalker != fParent->fTokens.end()) {
|
|
const char* nameStart = tokenWalker->fStart;
|
|
std::advance(tokenWalker, 1);
|
|
if (tokenWalker != fParent->fTokens.end()) {
|
|
TextParser tp(fFileName, nameStart, tokenWalker->fStart, fLineCount);
|
|
tp.skipToNonName();
|
|
start->fName = string(nameStart, tp.fChar - nameStart);
|
|
this->checkName(start);
|
|
start->fContentEnd = fChar;
|
|
priorEnum->fChildren.emplace_back(start);
|
|
fPriorEnum = priorEnum;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
this->addPunctuation(Punctuation::kSemicolon);
|
|
fInFunction = false;
|
|
break;
|
|
case '~':
|
|
if (fInEnum) {
|
|
break;
|
|
}
|
|
case '0': case '1': case '2': case '3': case '4':
|
|
case '5': case '6': case '7': case '8': case '9':
|
|
// TODO: don't want to parse numbers, but do need to track for enum defs
|
|
// 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 '_':
|
|
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':
|
|
if (fInCharCommentString || fInBrace) {
|
|
break;
|
|
}
|
|
if (!fIncludeWord) {
|
|
fIncludeWord = fChar;
|
|
}
|
|
break;
|
|
}
|
|
done:
|
|
fPrev = test;
|
|
this->next();
|
|
return true;
|
|
}
|
|
|
|
void IncludeParser::validate() const {
|
|
IncludeParser::ValidateKeyWords();
|
|
}
|
|
|
|
bool IncludeParser::references(const SkString& file) const {
|
|
// if includes weren't passed one at a time, assume all references are valid
|
|
if (fIncludeMap.empty()) {
|
|
return true;
|
|
}
|
|
SkASSERT(file.endsWith(".bmh") );
|
|
string root(file.c_str(), file.size() - 4);
|
|
string kReference("_Reference");
|
|
if (string::npos != root.find(kReference)) {
|
|
root = root.substr(0, root.length() - kReference.length());
|
|
}
|
|
if (fIClassMap.end() != fIClassMap.find(root)) {
|
|
return true;
|
|
}
|
|
if (fIStructMap.end() != fIStructMap.find(root)) {
|
|
return true;
|
|
}
|
|
if (fIEnumMap.end() != fIEnumMap.find(root)) {
|
|
return true;
|
|
}
|
|
if (fITypedefMap.end() != fITypedefMap.find(root)) {
|
|
return true;
|
|
}
|
|
if (fIFunctionMap.end() != fIFunctionMap.find(root)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void IncludeParser::RemoveFile(const char* docs, const char* includes) {
|
|
if (!sk_isdir(includes)) {
|
|
IncludeParser::RemoveOneFile(docs, includes);
|
|
} else {
|
|
SkOSFile::Iter it(includes, ".h");
|
|
for (SkString file; it.next(&file); ) {
|
|
SkString p = SkOSPath::Join(includes, file.c_str());
|
|
const char* hunk = p.c_str();
|
|
if (!SkStrEndsWith(hunk, ".h")) {
|
|
continue;
|
|
}
|
|
IncludeParser::RemoveOneFile(docs, hunk);
|
|
}
|
|
}
|
|
}
|
|
|
|
void IncludeParser::RemoveOneFile(const char* docs, const char* includesFile) {
|
|
const char* lastForward = strrchr(includesFile, '/');
|
|
const char* lastBackward = strrchr(includesFile, '\\');
|
|
const char* last = lastForward > lastBackward ? lastForward : lastBackward;
|
|
if (!last) {
|
|
last = includesFile;
|
|
} else {
|
|
last += 1;
|
|
}
|
|
SkString baseName(last);
|
|
SkASSERT(baseName.endsWith(".h"));
|
|
baseName.remove(baseName.size() - 2, 2);
|
|
baseName.append("_Reference.bmh");
|
|
SkString fullName = docs ? SkOSPath::Join(docs, baseName.c_str()) : baseName;
|
|
remove(fullName.c_str());
|
|
}
|
|
|
|
static const char kMethodMissingStr[] =
|
|
"If the method requires documentation, add to "
|
|
"%s at minimum:\n" // path to bmh file
|
|
"\n"
|
|
"#Method %s\n" // method declaration less implementation details
|
|
"#In SomeSubtopicName\n"
|
|
"#Line # add a one line description here ##\n"
|
|
"#Populate\n"
|
|
"#NoExample\n"
|
|
"// or better yet, use #Example and put C++ code here\n"
|
|
"##\n"
|
|
"#SeeAlso optional related symbols\n"
|
|
"#Method ##\n"
|
|
"\n"
|
|
"Add to %s, at minimum:\n" // path to include
|
|
"\n"
|
|
"/** (description) Starts with present tense action verb\n"
|
|
" and end with a period.\n"
|
|
"%s" // @param, @return if needed go here
|
|
"*/\n"
|
|
"%s ...\n" // method declaration
|
|
"\n"
|
|
"If the method does not require documentation,\n"
|
|
"add \"private\" or \"experimental\", as in:\n"
|
|
"\n"
|
|
"/** Experimental, do not use. And so on...\n"
|
|
"*/\n"
|
|
"%s ...\n" // method declaration
|
|
"\n"
|
|
;
|
|
|
|
// bDef does not have #Populate
|
|
static const char kMethodDiffersNoPopStr[] =
|
|
"In %s:\n" // path to bmh file
|
|
"#Method %s\n" // method declaration less implementation details
|
|
"does not match doxygen comment of:\n"
|
|
"%s.\n" // method declaration
|
|
"\n"
|
|
;
|
|
|
|
static const char kMethodDiffersStr[] =
|
|
"In %s:\n" // path to include
|
|
"%s\n" // method declaration
|
|
"does not match doxygen comment.\n"
|
|
"\n"
|
|
;
|
|
|
|
void IncludeParser::suggestFix(Suggest suggest, const Definition& iDef,
|
|
const RootDefinition* root, const Definition* bDef) {
|
|
string methodNameStr(iDef.fContentStart, iDef.length());
|
|
const char* methodName = methodNameStr.c_str();
|
|
TextParser lessImplParser(&iDef);
|
|
if (lessImplParser.skipExact("static")) {
|
|
lessImplParser.skipWhiteSpace();
|
|
}
|
|
// TODO : handle debug wrapper
|
|
/* bool inDebugWrapper = */ Definition::SkipImplementationWords(lessImplParser);
|
|
string lessImplStr(lessImplParser.fChar, lessImplParser.fEnd - lessImplParser.fChar);
|
|
const char* methodNameLessImpl = lessImplStr.c_str();
|
|
// return result, if any is substr from 0 to location of iDef.fName
|
|
size_t namePos = methodNameStr.find(iDef.fName);
|
|
SkASSERT(string::npos != namePos);
|
|
size_t funcEnd = namePos;
|
|
while (funcEnd > 0 && ' ' >= methodNameStr[funcEnd - 1]) {
|
|
funcEnd -= 1;
|
|
}
|
|
string funcRet = methodNameStr.substr(0, funcEnd);
|
|
// parameters, if any, are delimited by () and separate by ,
|
|
TextParser parser(&iDef);
|
|
parser.fChar += namePos + iDef.fName.length();
|
|
const char* start = parser.fChar;
|
|
vector<string> paramStrs;
|
|
if ('(' == start[0]) {
|
|
parser.skipToBalancedEndBracket('(', ')');
|
|
TextParser params(&iDef);
|
|
params.fChar = start + 1;
|
|
params.fEnd = parser.fChar;
|
|
while (!params.eof()) {
|
|
const char* paramEnd = params.anyOf("=,)");
|
|
const char* paramStart = paramEnd;
|
|
while (paramStart > params.fChar && ' ' >= paramStart[-1]) {
|
|
paramStart -= 1;
|
|
}
|
|
while (paramStart > params.fChar && (isalnum(paramStart[-1])
|
|
|| '_' == paramStart[-1])) {
|
|
paramStart -= 1;
|
|
}
|
|
string param(paramStart, paramEnd - paramStart);
|
|
paramStrs.push_back(param);
|
|
params.fChar = params.anyOf(",)") + 1;
|
|
}
|
|
}
|
|
string bmhFile = root ? root->fFileName : bDef ? bDef->fFileName : "a *.bmh file";
|
|
bool hasFuncReturn = "" != funcRet && "void" != funcRet;
|
|
switch(suggest) {
|
|
case Suggest::kMethodMissing: {
|
|
// if include @param, @return are missing, request them as well
|
|
string paramDox;
|
|
bool firstParam = true;
|
|
for (auto paramStr : paramStrs) {
|
|
if (firstParam) {
|
|
paramDox += "\n";
|
|
firstParam = false;
|
|
}
|
|
paramDox += " @param " + paramStr + " descriptive phrase\n";
|
|
}
|
|
if (hasFuncReturn) {
|
|
paramDox += "\n";
|
|
paramDox += " @return descriptive phrase\n";
|
|
}
|
|
SkDebugf(kMethodMissingStr, bmhFile.c_str(), methodNameLessImpl, iDef.fFileName.c_str(),
|
|
paramDox.c_str(), methodName, methodName);
|
|
} break;
|
|
case Suggest::kMethodDiffers: {
|
|
bool hasPop = std::any_of(bDef->fChildren.begin(), bDef->fChildren.end(),
|
|
[](Definition* def) { return MarkType::kPopulate == def->fMarkType; });
|
|
if (!hasPop) {
|
|
SkDebugf(kMethodDiffersNoPopStr, bmhFile.c_str(), methodNameLessImpl, methodName);
|
|
}
|
|
SkDebugf(kMethodDiffersStr, iDef.fFileName.c_str(), methodName);
|
|
} break;
|
|
default:
|
|
SkASSERT(0);
|
|
}
|
|
}
|
|
|
|
Bracket IncludeParser::topBracket() const {
|
|
Definition* parent = this->parentBracket(fParent);
|
|
return parent ? parent->fBracket : Bracket::kNone;
|
|
}
|