blob: 48e7c5fa5ac09eff692fef88d2f570418060e9f7 [file] [log] [blame]
/*
* 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;
}
<