4dc5a45405
Preparation for generating bookmaker files for all remaining interfaces Standardize enum and enum classes by including a comma after the last entry. Replace flatten-related #define in public interfaces with their equivalent. The motivation is to give documentation something to refer to. An alternative would be to move part or all of this out of the public interface; something I can work on in a follow-up CL. R=reed@google.com,bsalomon@google.com Bug: skia:6898 Change-Id: I4b865f6ec3d8f5d31e50448fef7d2714510302f0 Reviewed-on: https://skia-review.googlesource.com/129312 Reviewed-by: Brian Salomon <bsalomon@google.com> Commit-Queue: Cary Clark <caryclark@skia.org>
2665 lines
103 KiB
C++
2665 lines
103 KiB
C++
/*
|
|
* Copyright 2017 Google Inc.
|
|
*
|
|
* Use of this source code is governed by a BSD-style license that can be
|
|
* found in the LICENSE file.
|
|
*/
|
|
|
|
#include "bookmaker.h"
|
|
#include "SkOSFile.h"
|
|
#include "SkOSPath.h"
|
|
|
|
const char IncludeParser::gAttrDeprecated[] = "SK_ATTR_DEPRECATED";
|
|
const size_t IncludeParser::kAttrDeprecatedLen = sizeof(gAttrDeprecated) - 1;
|
|
|
|
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 },
|
|
{ "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 },
|
|
{ "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 },
|
|
{ "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 && 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
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::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;
|
|
this->addDefinition(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;
|
|
}
|
|
|
|
#include <sstream>
|
|
#include <iostream>
|
|
|
|
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;
|
|
}
|
|
RootDefinition* root = &finder->second;
|
|
root->clearVisited();
|
|
}
|
|
for (auto& classMapper : fIClassMap) {
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
auto& classMap = classMapper.second;
|
|
auto& tokens = classMap.fTokens;
|
|
for (const auto& token : tokens) {
|
|
if (token.fPrivate) {
|
|
continue;
|
|
}
|
|
string fullName = classMapper.first + "::" + token.fName;
|
|
const Definition* def = root->find(fullName, RootDefinition::AllowParens::kYes);
|
|
switch (token.fMarkType) {
|
|
case MarkType::kMethod: {
|
|
if (this->isInternalName(token)) {
|
|
continue;
|
|
}
|
|
if (!def) {
|
|
string paramName = className + "::";
|
|
paramName += string(token.fContentStart,
|
|
token.fContentEnd - token.fContentStart);
|
|
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;
|
|
string constructorName = className + "::";
|
|
constructorName += string(token.fContentStart + skip,
|
|
token.fContentEnd - 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 (gAttrDeprecated == token.fName) {
|
|
fAttrDeprecated = &token;
|
|
break;
|
|
}
|
|
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 (!root->fDeprecated) {
|
|
SkDebugf("method missing from bmh: %s\n", fullName.c_str());
|
|
fFailed = true;
|
|
}
|
|
break;
|
|
}
|
|
if (def->crossCheck2(token)) {
|
|
def->fVisited = true;
|
|
if (MarkType::kDefinedBy == def->fMarkType) {
|
|
def->fParent->fVisited = true;
|
|
}
|
|
if (token.fDeprecated && !def->fDeprecated) {
|
|
fFailed = !def->reportError<bool>("expect bmh to be marked deprecated");
|
|
}
|
|
} 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 anonName = className + "::" + string(lastUnderscore,
|
|
wordEnd - lastUnderscore) + 's';
|
|
def = root->find(anonName, RootDefinition::AllowParens::kYes);
|
|
}
|
|
if (!def) {
|
|
if (!root->fDeprecated) {
|
|
SkDebugf("enum missing from bmh: %s\n", fullName.c_str());
|
|
fFailed = true;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
def->fVisited = true;
|
|
for (auto& child : def->fChildren) {
|
|
if (MarkType::kCode == child->fMarkType) {
|
|
def = child;
|
|
break;
|
|
}
|
|
}
|
|
if (MarkType::kCode != def->fMarkType) {
|
|
if (!root->fDeprecated) {
|
|
SkDebugf("enum code missing from bmh: %s\n", fullName.c_str());
|
|
fFailed = true;
|
|
}
|
|
break;
|
|
}
|
|
if (def->crossCheck(token)) {
|
|
def->fVisited = true;
|
|
} else {
|
|
SkDebugf("enum differs from bmh: %s\n", def->fName.c_str());
|
|
fFailed = true;
|
|
}
|
|
for (auto& child : token.fChildren) {
|
|
string constName = MarkType::kEnumClass == token.fMarkType ?
|
|
fullName : className;
|
|
constName += "::" + child->fName;
|
|
def = root->find(constName, RootDefinition::AllowParens::kYes);
|
|
if (!def) {
|
|
string innerName = classMapper.first + "::" + child->fName;
|
|
def = root->find(innerName, RootDefinition::AllowParens::kYes);
|
|
}
|
|
if (!def) {
|
|
if (string::npos == child->fName.find("Legacy_")) {
|
|
if (!root->fDeprecated) {
|
|
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 if (!root->fDeprecated) {
|
|
SkDebugf("member missing from bmh: %s\n", fullName.c_str());
|
|
fFailed = true;
|
|
}
|
|
break;
|
|
case MarkType::kTypedef:
|
|
if (def) {
|
|
def->fVisited = true;
|
|
} else if (!root->fDeprecated) {
|
|
SkDebugf("typedef missing from bmh: %s\n", fullName.c_str());
|
|
fFailed = true;
|
|
}
|
|
break;
|
|
case MarkType::kConst:
|
|
if (def) {
|
|
def->fVisited = true;
|
|
} else if (!root->fDeprecated) {
|
|
SkDebugf("const missing from bmh: %s\n", fullName.c_str());
|
|
fFailed = true;
|
|
}
|
|
break;
|
|
default:
|
|
SkASSERT(0); // unhandled
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
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;
|
|
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;
|
|
fParent = &markupDef;
|
|
return &markupDef;
|
|
}
|
|
|
|
void IncludeParser::dumpClassTokens(IClassDefinition& classDef) {
|
|
auto& tokens = classDef.fTokens;
|
|
for (auto& token : tokens) {
|
|
if (Definition::Type::kMark == token.fType && MarkType::kComment == token.fMarkType) {
|
|
continue;
|
|
}
|
|
if (MarkType::kMember != token.fMarkType) {
|
|
this->writeBlockSeparator();
|
|
}
|
|
switch (token.fMarkType) {
|
|
case MarkType::kEnum:
|
|
case MarkType::kEnumClass:
|
|
this->dumpEnum(token, token.fName);
|
|
break;
|
|
case MarkType::kMethod:
|
|
this->dumpMethod(token, classDef.fName);
|
|
break;
|
|
case MarkType::kMember:
|
|
this->dumpMember(token);
|
|
continue;
|
|
break;
|
|
default:
|
|
SkASSERT(0);
|
|
}
|
|
this->dumpCommonTail(token);
|
|
}
|
|
}
|
|
void IncludeParser::dumpComment(const Definition& token) {
|
|
fLineCount = token.fLineCount;
|
|
fChar = fLine = token.fContentStart;
|
|
fEnd = token.fContentEnd;
|
|
bool sawParam = false;
|
|
bool multiline = false;
|
|
bool sawReturn = false;
|
|
bool sawComment = false;
|
|
bool methodHasReturn = false;
|
|
vector<string> methodParams;
|
|
vector<string> foundParams;
|
|
Definition methodName;
|
|
TextParser methodParser(token.fFileName, token.fContentStart, token.fContentEnd,
|
|
token.fLineCount);
|
|
bool debugCode = methodParser.skipExact("SkDEBUGCODE(");
|
|
if (MarkType::kMethod == token.fMarkType) {
|
|
methodName.fName = debugCode ? token.fName : string(token.fContentStart,
|
|
(int) (token.fContentEnd - token.fContentStart));
|
|
methodHasReturn = !methodParser.startsWith("void ")
|
|
&& !methodParser.startsWith("static void ")
|
|
&& !methodParser.strnchr('~', methodParser.fEnd);
|
|
const char* paren = methodParser.strnchr('(', methodParser.fEnd);
|
|
const char* nextEnd = paren;
|
|
do {
|
|
string paramName;
|
|
methodParser.fChar = nextEnd + 1;
|
|
methodParser.skipSpace();
|
|
if (!methodName.nextMethodParam(&methodParser, &nextEnd, ¶mName)) {
|
|
continue;
|
|
}
|
|
methodParams.push_back(paramName);
|
|
} while (')' != nextEnd[0]);
|
|
}
|
|
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.fContentStart[0]) {
|
|
TextParser parser(&child);
|
|
do {
|
|
parser.next();
|
|
if (parser.startsWith("param ")) {
|
|
parser.skipWord("param");
|
|
const char* parmStart = parser.fChar;
|
|
parser.skipToSpace();
|
|
string parmName = string(parmStart, (int) (parser.fChar - parmStart));
|
|
parser.skipWhiteSpace();
|
|
do {
|
|
size_t nextComma = parmName.find(',');
|
|
string piece;
|
|
if (string::npos == nextComma) {
|
|
piece = parmName;
|
|
parmName = "";
|
|
} else {
|
|
piece = parmName.substr(0, nextComma);
|
|
parmName = parmName.substr(nextComma + 1);
|
|
}
|
|
if (sawParam) {
|
|
if (multiline) {
|
|
this->lf(1);
|
|
}
|
|
this->writeEndTag();
|
|
} else {
|
|
if (sawComment) {
|
|
this->nl();
|
|
}
|
|
this->lf(2);
|
|
}
|
|
foundParams.emplace_back(piece);
|
|
this->writeTag("Param", piece);
|
|
this->writeSpace(2);
|
|
this->writeBlock(parser.fEnd - parser.fChar, parser.fChar);
|
|
this->lf(1);
|
|
sawParam = true;
|
|
sawComment = false;
|
|
} while (parmName.length());
|
|
parser.skipTo(parser.fEnd);
|
|
} else if (parser.startsWith("return ") || parser.startsWith("returns ")) {
|
|
parser.skipWord("return");
|
|
if ('s' == parser.peek()) {
|
|
parser.next();
|
|
}
|
|
if (sawParam) {
|
|
if (multiline) {
|
|
this->lf(1);
|
|
}
|
|
this->writeEndTag();
|
|
}
|
|
this->checkForMissingParams(methodParams, foundParams);
|
|
sawParam = false;
|
|
sawComment = false;
|
|
multiline = false;
|
|
this->lf(2);
|
|
this->writeTag("Return");
|
|
this->writeSpace(2);
|
|
this->writeBlock(parser.fEnd - parser.fChar, parser.fChar);
|
|
this->lf(1);
|
|
sawReturn = true;
|
|
parser.skipTo(parser.fEnd);
|
|
} else {
|
|
this->reportError("unexpected doxygen directive");
|
|
}
|
|
} while (!parser.eof());
|
|
} else 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->lfAlways(sawComment || sawParam || sawReturn ? 1 : 2);
|
|
if (sawParam || sawReturn) {
|
|
this->indentToColumn(8);
|
|
}
|
|
this->writeBlock(length, start);
|
|
this->writeSpace();
|
|
sawComment = true;
|
|
if (sawParam || sawReturn) {
|
|
multiline = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (sawParam || sawReturn) {
|
|
if (multiline) {
|
|
this->lf(1);
|
|
}
|
|
this->writeEndTag();
|
|
}
|
|
if (!sawReturn) {
|
|
if (!sawParam) {
|
|
if (sawComment) {
|
|
this->nl();
|
|
}
|
|
this->lf(2);
|
|
}
|
|
this->checkForMissingParams(methodParams, foundParams);
|
|
}
|
|
if (methodHasReturn != sawReturn) {
|
|
if (!methodHasReturn) {
|
|
this->reportError("unexpected doxygen return");
|
|
} else {
|
|
if (sawComment) {
|
|
this->nl();
|
|
}
|
|
this->lf(2);
|
|
this->writeIncompleteTag("Return");
|
|
}
|
|
}
|
|
}
|
|
|
|
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::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) {
|
|
this->writeTag("Enum", name);
|
|
this->lf(2);
|
|
this->writeTag("Code");
|
|
this->lfAlways(1);
|
|
this->indentToColumn(4);
|
|
this->writeString("enum");
|
|
this->writeSpace();
|
|
if ("_anonymous" != token.fName.substr(0, 10)) {
|
|
this->writeString(token.fName);
|
|
this->writeSpace();
|
|
}
|
|
this->writeString("{");
|
|
this->lfAlways(1);
|
|
for (auto& child : token.fChildren) {
|
|
this->indentToColumn(8);
|
|
this->writeString(child->fName);
|
|
if (child->length()) {
|
|
this->writeSpace();
|
|
this->writeBlock(child->length(), child->fContentStart);
|
|
}
|
|
if (',' != fLastChar) {
|
|
this->writeString(",");
|
|
}
|
|
this->lfAlways(1);
|
|
}
|
|
this->indentToColumn(4);
|
|
this->writeString("};");
|
|
this->lf(1);
|
|
this->writeString("##");
|
|
this->lf(2);
|
|
this->dumpComment(token);
|
|
for (auto& child : token.fChildren) {
|
|
// start here;
|
|
// get comments before
|
|
// or after const values
|
|
this->writeTag("Const");
|
|
this->writeSpace();
|
|
this->writeString(child->fName);
|
|
TextParser val(child);
|
|
if (!val.eof()) {
|
|
if ('=' == val.fStart[0] || ',' == val.fStart[0]) {
|
|
val.next();
|
|
val.skipSpace();
|
|
const char* valEnd = val.anyOf(",\n");
|
|
if (!valEnd) {
|
|
valEnd = val.fEnd;
|
|
}
|
|
this->writeSpace();
|
|
this->writeBlock(valEnd - val.fStart, val.fStart);
|
|
} else {
|
|
this->writeSpace();
|
|
this->writeDefinition(*child);
|
|
}
|
|
}
|
|
this->lf(1);
|
|
for (auto comment : child->fChildren) {
|
|
if (MarkType::kComment == comment->fMarkType) {
|
|
TextParser parser(comment);
|
|
parser.skipExact("*");
|
|
parser.skipExact("*");
|
|
while (!parser.eof() && parser.skipWhiteSpace()) {
|
|
parser.skipExact("*");
|
|
parser.skipWhiteSpace();
|
|
const char* start = parser.fChar;
|
|
parser.skipToEndBracket('\n');
|
|
this->lf(1);
|
|
this->writeBlock(parser.fChar - start, start);
|
|
}
|
|
}
|
|
}
|
|
this->writeEndTag();
|
|
}
|
|
this->lf(2);
|
|
}
|
|
|
|
bool IncludeParser::dumpGlobals() {
|
|
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";
|
|
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->writeTag("Alias", topicName + "_Reference");
|
|
this->lf(2);
|
|
this->writeTag("Subtopic", "Overview");
|
|
fIndent += 4;
|
|
this->writeTag("Subtopic", "Subtopic");
|
|
fIndent += 4;
|
|
this->writeTag("Populate");
|
|
fIndent -= 4;
|
|
this->writeEndTag();
|
|
fIndent -= 4;
|
|
this->writeEndTag();
|
|
this->lf(2);
|
|
if (!fIDefineMap.empty()) {
|
|
this->writeTag("Subtopic", "Define");
|
|
this->writeTag("Populate");
|
|
this->writeEndTag();
|
|
this->lf(2);
|
|
}
|
|
if (!fIFunctionMap.empty()) {
|
|
this->writeTag("Subtopic", "Function");
|
|
this->writeTag("Populate");
|
|
this->writeEndTag();
|
|
this->lf(2);
|
|
}
|
|
if (!fIEnumMap.empty()) {
|
|
this->writeTag("Subtopic", "Enum");
|
|
this->writeTag("Populate");
|
|
this->writeEndTag();
|
|
this->lf(2);
|
|
}
|
|
if (!fITemplateMap.empty()) {
|
|
this->writeTag("Subtopic", "Template");
|
|
this->writeTag("Populate");
|
|
this->writeEndTag();
|
|
this->lf(2);
|
|
}
|
|
if (!fITypedefMap.empty()) {
|
|
this->writeTag("Subtopic", "Typedef");
|
|
this->writeTag("Populate");
|
|
this->writeEndTag();
|
|
this->lf(2);
|
|
}
|
|
if (!fIUnionMap.empty()) {
|
|
this->writeTag("Subtopic", "Union");
|
|
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) {
|
|
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:
|
|
this->dumpMethod(*def, globalsName);
|
|
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);
|
|
}
|
|
this->writeEndTag("Topic", topicName);
|
|
this->lfAlways(1);
|
|
fclose(fOut);
|
|
SkDebugf("wrote %s\n", fileName.c_str());
|
|
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::isOperator(const Definition& token) {
|
|
return "operator" == token.fName.substr(0, 8);
|
|
}
|
|
|
|
void IncludeParser::dumpMethod(const Definition& token, string className) {
|
|
this->writeTag("Method");
|
|
this->writeSpace();
|
|
|
|
string name = string(token.fStart ? token.fStart : token.fContentStart,
|
|
token.length());
|
|
if (this->isOperator(token)) {
|
|
string spaceConst(" const");
|
|
size_t constPos = name.rfind(spaceConst);
|
|
if (name.length() - spaceConst.length() == constPos) {
|
|
name = name.substr(0, constPos) + "_const";
|
|
}
|
|
}
|
|
this->writeString(name);
|
|
string inType;
|
|
if (this->isConstructor(token, className)) {
|
|
inType = "Constructor";
|
|
} else if (this->isOperator(token)) {
|
|
inType = "Operator";
|
|
} else {
|
|
inType = "incomplete";
|
|
}
|
|
this->writeTag("In", inType);
|
|
this->writeTag("Line");
|
|
this->writeSpace(1);
|
|
this->writeString("#");
|
|
this->writeSpace(1);
|
|
this->writeString("incomplete");
|
|
this->writeSpace(1);
|
|
this->writeString("##");
|
|
this->lf(2);
|
|
this->dumpComment(token);
|
|
}
|
|
|
|
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() {
|
|
if (!this->dumpGlobals()) {
|
|
return false;
|
|
}
|
|
for (const auto& member : fIClassMap) {
|
|
if (string::npos != member.first.find("::")) {
|
|
continue;
|
|
}
|
|
if (!this->dumpTokens(member.first)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// dump equivalent markup
|
|
bool IncludeParser::dumpTokens(string skClassName) {
|
|
string fileName = skClassName + "_Reference.bmh";
|
|
fOut = fopen(fileName.c_str(), "wb");
|
|
if (!fOut) {
|
|
SkDebugf("could not open output file %s\n", fileName.c_str());
|
|
return false;
|
|
}
|
|
string prefixName = skClassName.substr(0, 2);
|
|
string topicName = skClassName.length() > 2 && isupper(skClassName[2]) &&
|
|
("Sk" == prefixName || "Gr" == prefixName) ? skClassName.substr(2) : skClassName;
|
|
this->writeTagNoLF("Topic", topicName);
|
|
this->writeTag("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);
|
|
bool hasClass = false;
|
|
bool hasConst = !fIEnumMap.empty();
|
|
bool hasConstructor = false;
|
|
bool hasMember = false;
|
|
bool hasOperator = false;
|
|
for (const auto& oneClass : fIClassMap) {
|
|
if (skClassName + "::" != oneClass.first.substr(0, skClassName.length() + 2)) {
|
|
continue;
|
|
}
|
|
hasClass = true;
|
|
break;
|
|
}
|
|
for (const auto& token : classMap.fTokens) {
|
|
if (Definition::Type::kMark != token.fType || MarkType::kMethod != token.fMarkType) {
|
|
continue;
|
|
}
|
|
if (this->isInternalName(token)) {
|
|
continue;
|
|
}
|
|
if (this->isConstructor(token, skClassName)) {
|
|
hasConstructor = true;
|
|
continue;
|
|
}
|
|
if (this->isOperator(token)) {
|
|
hasOperator = true;
|
|
continue;
|
|
}
|
|
if (this->isClone(token)) {
|
|
continue;
|
|
}
|
|
hasMember = true;
|
|
}
|
|
this->writeTag("Subtopic", "Overview");
|
|
fIndent += 4;
|
|
this->writeTag("Subtopic", "Subtopic");
|
|
fIndent += 4;
|
|
this->writeTag("Populate");
|
|
fIndent -= 4;
|
|
this->writeEndTag();
|
|
fIndent -= 4;
|
|
this->writeEndTag();
|
|
this->lf(2);
|
|
|
|
if (hasClass) {
|
|
this->writeTag("Subtopic", "Class_or_Struct");
|
|
this->writeTag("Populate");
|
|
this->writeEndTag();
|
|
this->lf(2);
|
|
}
|
|
if (hasConst) {
|
|
this->writeTag("Subtopic", "Constant");
|
|
this->writeTag("Populate");
|
|
this->writeEndTag();
|
|
this->lf(2);
|
|
}
|
|
if (hasConstructor) {
|
|
this->writeTag("Subtopic", "Constructor");
|
|
this->writeTag("Populate");
|
|
this->writeEndTag();
|
|
this->lf(2);
|
|
}
|
|
if (hasOperator) {
|
|
this->writeTag("Subtopic", "Operator");
|
|
this->writeTag("Populate");
|
|
this->writeEndTag();
|
|
this->lf(2);
|
|
}
|
|
if (hasMember) {
|
|
this->writeTag("Subtopic", "Member_Function");
|
|
this->writeTag("Populate");
|
|
this->writeEndTag();
|
|
this->lf(2);
|
|
}
|
|
for (auto& oneEnum : fIEnumMap) {
|
|
this->writeBlockSeparator();
|
|
this->dumpEnum(*oneEnum.second, oneEnum.first);
|
|
this->lf(2);
|
|
this->writeTag("Example");
|
|
this->lfcr();
|
|
this->writeString("// incomplete");
|
|
this->writeEndTag();
|
|
this->lf(2);
|
|
this->writeTag("SeeAlso", "incomplete");
|
|
this->lf(2);
|
|
this->writeEndTag("Enum", oneEnum.first);
|
|
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->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;
|
|
}
|
|
|
|
bool IncludeParser::findComments(const Definition& includeDef, Definition* markupDef) {
|
|
// add comment preceding class, if any
|
|
const 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)) {
|
|
return false;
|
|
}
|
|
commentIter = std::next(commentIter);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// caller calls reportError, so just return false 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);
|
|
}
|
|
string nameStr(iter->fStart, iter->fContentEnd - iter->fStart);
|
|
includeDef->fName = nameStr;
|
|
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 (1 != includeDef->fChildren.size()) {
|
|
// return false; // fix me: SkCanvasClipVisitor isn't correctly parsed
|
|
// }
|
|
includeDef = includeDef->fChildren.front();
|
|
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 = std::next(iter);
|
|
++publicIndex;
|
|
}
|
|
}
|
|
auto childIter = includeDef->fChildren.begin();
|
|
while (childIter != includeDef->fChildren.end() && (*childIter)->fParentIndex < publicIndex) {
|
|
(*childIter)->fPrivate = true;
|
|
childIter = std::next(childIter);
|
|
}
|
|
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);
|
|
while (childIter != includeDef->fChildren.end()) {
|
|
Definition* child = *childIter;
|
|
while (child->fParentIndex > keyIndex && iter != includeDef->fTokens.end()) {
|
|
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;
|
|
}
|
|
} else {
|
|
child->fPrivate = true;
|
|
}
|
|
fLastObject = child;
|
|
childIter = std::next(childIter);
|
|
}
|
|
SkASSERT(fParent->fParent);
|
|
fParent = fParent->fParent;
|
|
return true;
|
|
}
|
|
|
|
bool IncludeParser::parseComment(string filename, const char* start, const char* end,
|
|
int lineCount, Definition* markupDef) {
|
|
TextParser parser(filename, start, end, lineCount);
|
|
// parse doxygen if present
|
|
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;
|
|
}
|
|
|
|
bool IncludeParser::parseConst(Definition* child, Definition* markupDef) {
|
|
// todo: hard code to constexpr for now
|
|
TextParser constParser(child);
|
|
if (!constParser.skipExact("static")) {
|
|
return false;
|
|
}
|
|
constParser.skipWhiteSpace();
|
|
if (!constParser.skipExact("constexpr")) {
|
|
return false;
|
|
}
|
|
constParser.skipWhiteSpace();
|
|
const char* typeStart = constParser.fChar;
|
|
constParser.skipToSpace();
|
|
KeyWord constType = FindKey(typeStart, constParser.fChar);
|
|
if (KeyWord::kNone == constType) {
|
|
// todo: this could be a non-keyword, ... do we need to look for type?
|
|
return false;
|
|
}
|
|
constParser.skipWhiteSpace();
|
|
const char* nameStart = constParser.fChar;
|
|
constParser.skipToSpace();
|
|
string nameStr = string(nameStart, constParser.fChar - nameStart);
|
|
if (!markupDef) {
|
|
fGlobals.emplace_back(MarkType::kConst, child->fContentStart, child->fContentEnd,
|
|
child->fLineCount, fParent, '\0');
|
|
Definition* globalMarkupChild = &fGlobals.back();
|
|
string globalUniqueName = this->uniqueName(fIConstMap, nameStr);
|
|
globalMarkupChild->fName = globalUniqueName;
|
|
if (!this->findComments(*child, globalMarkupChild)) {
|
|
return false;
|
|
}
|
|
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 = nameStr;
|
|
markupChild->fTerminator = markupChild->fContentEnd;
|
|
IClassDefinition& classDef = fIClassMap[markupDef->fName];
|
|
classDef.fConsts[nameStr] = 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;
|
|
}
|
|
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);
|
|
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;
|
|
}
|
|
classDef.fDefines[nameStr] = markupChild;
|
|
return true;
|
|
}
|
|
|
|
bool IncludeParser::parseEnum(Definition* child, Definition* markupDef) {
|
|
TextParser parser(child);
|
|
parser.skipToEndBracket('{');
|
|
if (parser.eof()) {
|
|
return true; // if enum is a forward declaration, do nothing
|
|
}
|
|
parser.next();
|
|
string nameStr;
|
|
if (child->fTokens.size() > 0) {
|
|
auto token = child->fTokens.begin();
|
|
if (Definition::Type::kKeyWord == token->fType && KeyWord::kClass == token->fKeyWord) {
|
|
token = token->fTokens.begin();
|
|
}
|
|
if (Definition::Type::kWord == token->fType) {
|
|
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;
|
|
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;
|
|
TextParser enumName(child);
|
|
enumName.skipExact("enum ");
|
|
enumName.skipWhiteSpace();
|
|
if (enumName.skipExact("class ")) {
|
|
enumName.skipWhiteSpace();
|
|
markupChild->fMarkType = MarkType::kEnumClass;
|
|
}
|
|
const char* nameStart = enumName.fChar;
|
|
enumName.skipToSpace();
|
|
if (markupDef) {
|
|
markupChild->fName = markupDef->fName + "::" +
|
|
string(nameStart, (size_t) (enumName.fChar - nameStart));
|
|
}
|
|
if (!this->findComments(*child, markupChild)) {
|
|
return false;
|
|
}
|
|
const char* dataEnd;
|
|
do {
|
|
parser.skipWhiteSpace();
|
|
if ('}' == parser.peek()) {
|
|
break;
|
|
}
|
|
Definition* comment = nullptr;
|
|
// note that comment, if any, can be before or after (on the same line, though) as member
|
|
if ('#' == parser.peek()) {
|
|
// fixme: handle preprecessor, but just skip it for now
|
|
parser.skipToLineStart();
|
|
}
|
|
while (parser.startsWith("/*") || parser.startsWith("//")) {
|
|
parser.next();
|
|
const char* start = parser.fChar;
|
|
const char* end;
|
|
if ('*' == parser.peek()) {
|
|
end = parser.strnstr("*/", parser.fEnd);
|
|
parser.fChar = end;
|
|
parser.next();
|
|
parser.next();
|
|
} else {
|
|
end = parser.trimmedLineEnd();
|
|
parser.skipToLineStart();
|
|
}
|
|
markupChild->fTokens.emplace_back(MarkType::kComment, start, end, parser.fLineCount,
|
|
markupChild, '\0');
|
|
comment = &markupChild->fTokens.back();
|
|
comment->fTerminator = end;
|
|
if (!this->parseComment(parser.fFileName, start, end, parser.fLineCount, comment)) {
|
|
return false;
|
|
}
|
|
parser.skipWhiteSpace();
|
|
}
|
|
parser.skipWhiteSpace();
|
|
const char* memberStart = parser.fChar;
|
|
if ('}' == memberStart[0]) {
|
|
break;
|
|
}
|
|
// if there's comment on same the line as member def, output first as if it was before
|
|
|
|
parser.skipToNonName();
|
|
string memberName(memberStart, parser.fChar);
|
|
if (parser.eof() || !parser.skipWhiteSpace()) {
|
|
return this->reportError<bool>("enum member must end with comma 1");
|
|
}
|
|
const char* dataStart = parser.fChar;
|
|
if ('=' == parser.peek()) {
|
|
parser.skipToEndBracket(',');
|
|
}
|
|
if (!parser.eof() && '#' == parser.peek()) {
|
|
// fixme: handle preprecessor, but just skip it for now
|
|
continue;
|
|
}
|
|
if (parser.eof() || ',' != parser.peek()) {
|
|
return this->reportError<bool>("enum member must end with comma 2");
|
|
}
|
|
dataEnd = parser.fChar;
|
|
const char* start = parser.anyOf("/\n");
|
|
SkASSERT(start);
|
|
parser.skipTo(start);
|
|
if ('/' == parser.next()) {
|
|
char slashStar = parser.next();
|
|
if ('/' == slashStar || '*' == slashStar) {
|
|
TextParserSave save(&parser);
|
|
char doxCheck = parser.next();
|
|
if ((slashStar != doxCheck && '!' != doxCheck) || '<' != parser.next()) {
|
|
save.restore();
|
|
}
|
|
}
|
|
parser.skipWhiteSpace();
|
|
const char* commentStart = parser.fChar;
|
|
if ('/' == slashStar) {
|
|
parser.skipToEndBracket('\n');
|
|
} else {
|
|
parser.skipToEndBracket("*/");
|
|
}
|
|
SkASSERT(!parser.eof());
|
|
const char* commentEnd = parser.fChar;
|
|
markupChild->fTokens.emplace_back(MarkType::kComment, commentStart, commentEnd,
|
|
parser.fLineCount, markupChild, '\0');
|
|
comment = &markupChild->fTokens.back();
|
|
comment->fTerminator = commentEnd;
|
|
}
|
|
markupChild->fTokens.emplace_back(MarkType::kMember, dataStart, dataEnd, parser.fLineCount,
|
|
markupChild, '\0');
|
|
Definition* member = &markupChild->fTokens.back();
|
|
member->fName = memberName;
|
|
if (comment) {
|
|
member->fChildren.push_back(comment);
|
|
comment->fPrivate = true;
|
|
}
|
|
markupChild->fChildren.push_back(member);
|
|
} while (true);
|
|
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;
|
|
// 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);
|
|
markupChild->fName = uniqueName;
|
|
classDef.fEnums[uniqueName] = markupChild;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool IncludeParser::parseInclude(string name) {
|
|
fParent = &fIncludeMap[name];
|
|
fParent->fName = name;
|
|
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;
|
|
markupChild->fTerminator = markupChild->fContentEnd;
|
|
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;
|
|
tokenIter->fMarkType = MarkType::kMethod;
|
|
tokenIter->fPrivate = string::npos != nameStr.find("::");
|
|
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;
|
|
while (testIter != child->fParent->fTokens.end()) {
|
|
testIter = std::next(testIter);
|
|
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 (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;
|
|
}
|
|
fIFunctionMap[globalUniqueName] = globalMarkupChild;
|
|
return true;
|
|
}
|
|
markupDef->fTokens.emplace_back(MarkType::kMethod, start, end, tokenIter->fLineCount,
|
|
markupDef, '\0');
|
|
Definition* markupChild = &markupDef->fTokens.back();
|
|
// do find instead -- I wonder if there is a way to prevent this in c++
|
|
IClassDefinition& classDef = fIClassMap[markupDef->fName];
|
|
SkASSERT(classDef.fStart);
|
|
string uniqueName = this->uniqueName(classDef.fMethods, nameStr);
|
|
markupChild->fName = uniqueName;
|
|
if (!this->findComments(*child, markupChild)) {
|
|
return false;
|
|
}
|
|
classDef.fMethods[uniqueName] = markupChild;
|
|
return true;
|
|
}
|
|
|
|
bool IncludeParser::parseObjects(Definition* parent, Definition* markupDef) {
|
|
for (auto& child : parent->fChildren) {
|
|
if (!this->parseObject(child, markupDef)) {
|
|
return false;
|
|
}
|
|
}
|
|
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:
|
|
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;
|
|
default:
|
|
return child->reportError<bool>("unhandled keyword");
|
|
}
|
|
break;
|
|
case Definition::Type::kBracket:
|
|
switch (child->fBracket) {
|
|
case Bracket::kParen:
|
|
if (fLastObject) {
|
|
TextParser checkDeprecated(child->fFileName, fLastObject->fTerminator + 1,
|
|
child->fStart, fLastObject->fLineCount);
|
|
if (!checkDeprecated.eof()) {
|
|
checkDeprecated.skipWhiteSpace();
|
|
if (checkDeprecated.startsWith(gAttrDeprecated)) {
|
|
fAttrDeprecated = child;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
{
|
|
auto tokenIter = child->fParent->fTokens.begin();
|
|
std::advance(tokenIter, child->fParentIndex);
|
|
tokenIter = std::prev(tokenIter);
|
|
TextParser previousToken(&*tokenIter);
|
|
if (previousToken.startsWith(gAttrDeprecated)) {
|
|
fAttrDeprecated = &*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 (!this->parseMethod(child, markupDef)) {
|
|
return child->reportError<bool>("failed to parse method");
|
|
}
|
|
if (fAttrDeprecated) {
|
|
Definition* lastMethod = &markupDef->fTokens.back();
|
|
lastMethod->fDeprecated = true;
|
|
fAttrDeprecated = nullptr;
|
|
}
|
|
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:
|
|
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;
|
|
}
|
|
fITypedefMap[globalUniqueName] = globalMarkupChild;
|
|
return true;
|
|
}
|
|
markupDef->fTokens.emplace_back(MarkType::kTypedef, 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];
|
|
classDef.fTypedefs[nameStr] = markupChild;
|
|
return true;
|
|
}
|
|
|
|
bool IncludeParser::parseUnion() {
|
|
|
|
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 (!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);
|
|
break;
|
|
case ')':
|
|
case ']':
|
|
case '}': {
|
|
if (fInCharCommentString) {
|
|
break;
|
|
}
|
|
if (fInDefine && fInBrace) {
|
|
break;
|
|
}
|
|
if (!fInBrace) {
|
|
if (!this->checkForWord()) {
|
|
return false;
|
|
}
|
|
}
|
|
bool popBraceParent = fInBrace == fParent;
|
|
if ((')' == test ? Bracket::kParen :
|
|
']' == test ? Bracket::kSquare : Bracket::kBrace) == this->topBracket()) {
|
|
this->popBracket();
|
|
if (!fInFunction) {
|
|
fInFunction = ')' == test;
|
|
} else {
|
|
fInFunction = '}' != test;
|
|
}
|
|
} else if (')' == test && Bracket::kDebugCode == this->topBracket()) {
|
|
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()) {
|
|
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 '-':
|
|
case '!':
|
|
if (fInCharCommentString || fInBrace) {
|
|
break;
|
|
}
|
|
if (!this->checkForWord()) {
|
|
return false;
|
|
}
|
|
break;
|
|
case ';':
|
|
if (fInCharCommentString || fInBrace) {
|
|
break;
|
|
}
|
|
if (!this->checkForWord()) {
|
|
return false;
|
|
}
|
|
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;
|
|
}
|
|
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--; ) {
|
|
if (tokenIter->fLineCount == fLineCount) {
|
|
if ('f' == tokenIter->fStart[0] && isupper(tokenIter->fStart[1])) {
|
|
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);
|
|
start->fContentEnd = fChar;
|
|
priorEnum->fChildren.emplace_back(start);
|
|
fPriorEnum = priorEnum;
|
|
}
|
|
}
|
|
} else { // check for static constexpr not following an enum
|
|
// find first token on line
|
|
auto backTokenWalker = fParent->fTokens.end();
|
|
while (fParent->fTokens.begin() != backTokenWalker
|
|
&& (fParent->fTokens.end() == backTokenWalker
|
|
|| backTokenWalker->fStart > fLine)) {
|
|
std::advance(backTokenWalker, -1);
|
|
}
|
|
if (fParent->fTokens.end() != backTokenWalker
|
|
&& backTokenWalker->fStart < fLine) {
|
|
std::advance(backTokenWalker, 1);
|
|
}
|
|
// look for static constexpr
|
|
Definition* start = &*backTokenWalker;
|
|
bool foundExpected = true;
|
|
for (KeyWord expected : {KeyWord::kStatic, KeyWord::kConstExpr}){
|
|
const Definition* test = &*backTokenWalker;
|
|
if (expected != test->fKeyWord) {
|
|
foundExpected = false;
|
|
break;
|
|
}
|
|
if (backTokenWalker == fParent->fTokens.end()) {
|
|
break;
|
|
}
|
|
std::advance(backTokenWalker, 1);
|
|
}
|
|
if (foundExpected) {
|
|
std::advance(backTokenWalker, 1);
|
|
const char* nameStart = backTokenWalker->fStart;
|
|
std::advance(backTokenWalker, 1);
|
|
TextParser parser(fFileName, nameStart, backTokenWalker->fStart, fLineCount);
|
|
parser.skipToNonAlphaNum();
|
|
start->fMarkType = MarkType::kConst;
|
|
start->fName = string(nameStart, parser.fChar - nameStart);
|
|
start->fContentEnd = backTokenWalker->fContentEnd;
|
|
fParent->fChildren.emplace_back(start);
|
|
}
|
|
}
|
|
}
|
|
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 {
|
|
for (int index = 0; index <= (int) Last_MarkType; ++index) {
|
|
SkASSERT(fMaps[index].fMarkType == (MarkType) index);
|
|
}
|
|
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;
|
|
}
|
|
// TODO incomplete: probably need to look in other places for class-less includes like SkColor.h
|
|
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 = SkOSPath::Join(docs, baseName.c_str());
|
|
remove(fullName.c_str());
|
|
}
|