blob: eca72963b09d1d991178f60f34af80fe2ff6855b [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 "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 },
{ "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 },
{ "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::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;
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);
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 (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 (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::kConst:
this->dumpConst(token, classDef.fName);
break;
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;
case MarkType::kTypedef:
this->dumpTypedef(token, classDef.fName);
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, &paramName)) {
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::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) {
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(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);
this->writeTag("Subtopic", "Overview");
this->writeTag("Populate");
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);
}
*globalTell = ftell(fOut);
this->writeEndTag("Topic", topicName);
this->lfAlways(1);
// fclose(fOut); // defer closing in case class needs to be also written here
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::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);
}
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->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);
}
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);
}
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);
bool hasClass = false;
bool hasConst = !fIEnumMap.empty();
bool hasConstructor = false;
bool hasMember = false;
bool hasOperator = false;
bool hasStruct = false;
for (const auto& oneClass : fIClassMap) {
if (skClassName + "::" != oneClass.first.substr(0, skClassName.length() + 2)) {
continue;
}
hasClass = true;
break;
}
for (const auto& oneStruct : fIStructMap) {
if (skClassName + "::" != oneStruct.first.substr(0, skClassName.length() + 2)) {
continue;
}
hasStruct = 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");
this->writeTag("Populate");
this->writeEndTag();
this->lf(2);
if (hasClass) {
this->writeTag("Subtopic", "Class");
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);
}
if (hasStruct) {
this->writeTag("Subtopic", "Struct");
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->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);
}
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;
}
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;
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::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;
}
// 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;
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
// }
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::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) {
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;
}
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;
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 parser.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 parser.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; // simple token stream, OK if name is duplicate
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();
// TODO: I wonder if there is a way to prevent looking up by operator[] (creating empty) ?
{
auto mapIter = fIClassMap.find(markupDef->fName);
SkASSERT(fIClassMap.end() != mapIter);
IClassDefinition& classDef = mapIter->second;
SkASSERT(classDef.fStart);
string uniqueName = this->uniqueName(classDef.fMethods, nameStr);
markupChild->fName = uniqueName;
if (!this->findComments(*child, markupChild)) {
return false;
}
classDef.fMethods[uniqueName] = markupChild;
}
return true;
}
bool IncludeParser::parseObjects(Definition* parent, Definition* markupDef) {
fPriorObject = nullptr;
for (auto child : parent->fChildren) {
if (!this->parseObject(child, markupDef)) {
return false;
}
fPriorObject = child;
}
return true;
}
bool IncludeParser::parseObject(Definition* child, Definition* markupDef) {
// set up for error reporting
fLine = fChar = child->fStart;
fEnd = child->fContentEnd;
// todo: put original line number in child as well
switch (child->fType) {
case Definition::Type::kKeyWord:
switch (child->fKeyWord) {
case KeyWord::kClass:
if (!this->parseClass(child, IsStruct::kNo)) {
return false;
}
break;
case KeyWord::kStatic:
case KeyWord::kConst:
case KeyWord::kConstExpr:
if (!this->parseConst(child, markupDef)) {
return child->reportError<bool>("failed to parse const or constexpr");
}
break;
case KeyWord::kEnum:
if (!this->parseEnum(child, markupDef)) {
return child->reportError<bool>("failed to parse enum");
}
break;
case KeyWord::kStruct:
if (!this->parseClass(child, IsStruct::kYes)) {
return child->reportError<bool>("failed to parse struct");
}
break;
case KeyWord::kTemplate:
if (!this->parseTemplate(child, markupDef)) {
return child->reportError<bool>("failed to parse template");
}
break;
case KeyWord::kTypedef:
if (!this->parseTypedef(child, markupDef)) {
return child->reportError<bool>("failed to parse typedef");
}
break;
case KeyWord::kUnion:
if (!this->parseUnion()) {
return child->reportError<bool>("failed to parse union");
}
break;
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 (this->isMember(*tokenIter)) {
break;
}
if (Bracket::kPound == child->fParent->fBracket &&
KeyWord::kIf == child->fParent->fKeyWord) {
// TODO: this will skip methods named defined() -- for the
// moment there aren't any
if (previousToken.startsWith("defined")) {
break;
}
}
if (previousToken.startsWith("sizeof") && 6 == previousToken.lineLength()) {
break;
}
}
if (fPriorObject && MarkType::kConst == fPriorObject->fMarkType) {
break;
}
if (!this->parseMethod(child, markupDef)) {
return child->reportError<bool>("failed to parse method");
}
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;
child->fName = nameStr;
return true;
}
markupDef->fTokens.emplace_back(MarkType::kTypedef, child->fContentStart, child->fContentEnd,
child->fLineCount, markupDef, '\0');
Definition* markupChild = &markupDef->fTokens.back();
markupChild->fName = nameStr;
markupChild->fTerminator = markupChild->fContentEnd;
IClassDefinition& classDef = fIClassMap[markupDef->fName];
classDef.fTypedefs[nameStr] = markupChild;
child->fName = markupDef->fName + "::" + nameStr;
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 (fConstExpr) {
fConstExpr->fContentEnd = fParent->fTokens.back().fContentEnd;
fConstExpr = nullptr;
}
if (!fInBrace) {
if (!this->checkForWord()) {
return false;
}
if (':' == test && !fInFunction) {
break;
}
if ('{' == test) {
this->addPunctuation(Punctuation::kLeftBrace);
} else if (':' == test) {
this->addPunctuation(Punctuation::kColon);
}
}
if (fInBrace && '{' == test && Definition::Type::kBracket == fInBrace->fType
&& Bracket::kColon == fInBrace->fBracket) {
Definition* braceParent = fParent->fParent;
braceParent->fChildren.pop_back();
braceParent->fTokens.pop_back();
fParent = braceParent;
fInBrace = nullptr;
}
this->pushBracket(
'(' == test ? Bracket::kParen :
'[' == test ? Bracket::kSquare :
'{' == test ? Bracket::kBrace :
Bracket::kColon);
if (!fInBrace
&& ('{' == test || (':' == test && ' ' >= fChar[1]))
&& fInFunction) {
fInBrace = fParent;
}
} break;
case '<':
if (fInCharCommentString || fInBrace) {
break;
}
if (!this->checkForWord()) {
return false;
}
if (fInEnum) {
break;
}
this->pushBracket(Bracket::kAngle);
// this angle bracket may be an operator or may be a bracket
// wait for balancing close angle, if any, to decide
break;
case ')':
case ']':
case '}': {
if (fInCharCommentString) {
break;
}
if (fInDefine && fInBrace) {
break;
}
if (!fInBrace) {
if (!this->checkForWord()) {
return false;
}
}
bool popBraceParent = fInBrace == fParent;
Bracket match = ')' == test ? Bracket::kParen :
']' == test ? Bracket::kSquare : Bracket::kBrace;
if (match == this->topBracket()) {
this->popBracket();
if (!fInFunction) {
fInFunction = ')' == test && !this->inAlignAs();
} else {
fInFunction = '}' != test;
}
} else if (')' == test && Bracket::kDebugCode == this->topBracket()) {
this->popBracket();
} else if (Bracket::kAngle == this->topBracket()
&& match == this->grandParentBracket()) {
this->popBracket();
this->popBracket();
} else {
return reportError<bool>("malformed close bracket");
}
if (popBraceParent) {
Definition* braceParent = fInBrace->fParent;
braceParent->fChildren.pop_back();
braceParent->fTokens.pop_back();
fInBrace = nullptr;
}
} break;
case '>':
if (fInCharCommentString || fInBrace) {
break;
}
if (!this->checkForWord()) {
return false;
}
if (fInEnum) {
break;
}
if (Bracket::kPound == this->topBracket()) {
break;
}
if (Bracket::kAngle == this->topBracket()) {
// looks like angle pair are braces, not operators
this->popBracket();
} else {
return reportError<bool>("malformed close angle bracket");
}
break;
case '#': {
if (fInCharCommentString || fInBrace) {
break;
}
SkASSERT(!fIncludeWord); // don't expect this, curious if it is triggered
this->pushBracket(Bracket::kPound);
break;
}
case ' ':
if (fInDefine && !fInBrace && Bracket::kPound == this->topBracket()) {
SkASSERT(KeyWord::kDefine == fParent->fKeyWord);
fInBrace = fParent;
// delimiting brackets are space ... unescaped-linefeed
}
case '&':
case ',':
case '+':
case '-':
case '!':
if (fInCharCommentString || fInBrace) {
break;
}
if (!this->checkForWord()) {
return false;
}
break;
case '=':
if (fInCharCommentString || fInBrace) {
break;
}
if (!this->checkForWord()) {
return false;
}
if (!fParent->fTokens.size()) {
break;
}
{
const Definition& lastToken = fParent->fTokens.back();
if (lastToken.fType != Definition::Type::kWord) {
break;
}
string name(lastToken.fContentStart, lastToken.length());
if ("SK_" != name.substr(0, 3) && 'k' != name[0]) {
break;
}
// find token on start of line
auto lineIter = fParent->fTokens.end();
do {
if (fParent->fTokens.begin() == lineIter) {
break;
}
--lineIter;
} while (lineIter->fContentStart > fLine);
if (lineIter->fContentStart < fLine && fParent->fTokens.end() != lineIter) {
++lineIter;
}
Definition* lineStart = &*lineIter;
// walk tokens looking for [template <typename T>] [static] [const | constexpr]
bool sawConst = false;
bool sawStatic = false;
bool sawTemplate = false;
bool sawType = false;
while (&lastToken != &*lineIter) {
if (KeyWord::kTemplate == lineIter->fKeyWord) {
if (sawConst || sawStatic || sawTemplate) {
sawConst = false;
break;
}
if (&lastToken == &*++lineIter) {
break;
}
if (KeyWord::kTypename != lineIter->fKeyWord) {
break;
}
if (&lastToken == &*++lineIter) {
break;
}
if (Definition::Type::kWord != lineIter->fType) {
break;
}
sawTemplate = true;
} else if (KeyWord::kStatic == lineIter->fKeyWord) {
if (sawConst || sawStatic) {
sawConst = false;
break;
}
sawStatic = true;
} else if (KeyWord::kConst == lineIter->fKeyWord
|| KeyWord::kConstExpr == lineIter->fKeyWord) {
if (sawConst) {
sawConst = false;
break;
}
sawConst = true;
} else {
if (sawType) {
sawType = false;
break;
}
if (Definition::Type::kKeyWord == lineIter->fType
&& KeyProperty::kNumber
== kKeyWords[(int) lineIter->fKeyWord].fProperty) {
sawType = true;
} else if (Definition::Type::kWord == lineIter->fType) {
string typeName(lineIter->fContentStart, lineIter->length());
if ("Sk" != name.substr(0, 2)) {
sawType = true;
}
}
}
++lineIter;
}
if (sawType && sawConst) {
// if found, name first
lineStart->fName = name;
lineStart->fMarkType = MarkType::kConst;
fParent->fChildren.emplace_back(lineStart);
fConstExpr = lineStart;
}
}
break;
case ';':
if (fInCharCommentString || fInBrace) {
break;
}
if (!this->checkForWord()) {
return false;
}
if (fConstExpr) {
fConstExpr->fContentEnd = fParent->fTokens.back().fContentEnd;
fConstExpr = nullptr;
}
if (Definition::Type::kKeyWord == fParent->fType
&& KeyProperty::kObject == (kKeyWords[(int) fParent->fKeyWord].fProperty)) {
bool parentIsClass = KeyWord::kClass == fParent->fKeyWord;
if (parentIsClass && fParent->fParent &&
KeyWord::kEnum == fParent->fParent->fKeyWord) {
this->popObject();
}
if (KeyWord::kEnum == fParent->fKeyWord) {
fInEnum = false;
}
this->popObject();
if (parentIsClass && fParent && KeyWord::kTemplate == fParent->fKeyWord) {
this->popObject();
}
fPriorEnum = nullptr;
} else if (Definition::Type::kBracket == fParent->fType
&& fParent->fParent && Definition::Type::kKeyWord == fParent->fParent->fType
&& KeyWord::kStruct == fParent->fParent->fKeyWord) {
list<Definition>::iterator baseIter = fParent->fTokens.end();
list<Definition>::iterator namedIter = fParent->fTokens.end();
for (auto tokenIter = fParent->fTokens.end();
fParent->fTokens.begin() != tokenIter; ) {
--tokenIter;
if (tokenIter->fLineCount == fLineCount) {
if (this->isMember(*tokenIter)) {
if (namedIter != fParent->fTokens.end()) {
return reportError<bool>("found two named member tokens");
}
namedIter = tokenIter;
}
baseIter = tokenIter;
} else {
break;
}
}
// FIXME: if a member definition spans multiple lines, this won't work
if (namedIter != fParent->fTokens.end()) {
if (baseIter == namedIter) {
return this->reportError<bool>("expected type before named token");
}
Definition* member = &*namedIter;
member->fMarkType = MarkType::kMember;
if (!member->fTerminator) {
member->fTerminator = member->fContentEnd;
}
fParent->fChildren.push_back(member);
for (auto nameType = baseIter; nameType != namedIter; ++nameType) {
member->fChildren.push_back(&*nameType);
}
}
fPriorEnum = nullptr;
} else if (fParent->fChildren.size() > 0) {
auto lastIter = fParent->fChildren.end();
Definition* priorEnum = fPriorEnum;
fPriorEnum = nullptr;
if (!priorEnum) {
while (fParent->fChildren.begin() != lastIter) {
std::advance(lastIter, -1);
priorEnum = *lastIter;
if (Definition::Type::kBracket != priorEnum->fType ||
(Bracket::kSlashSlash != priorEnum->fBracket
&& Bracket::kSlashStar != priorEnum->fBracket)) {
break;
}
}
fPriorIndex = priorEnum->fParentIndex;
}
if (Definition::Type::kKeyWord == priorEnum->fType
&& KeyWord::kEnum == priorEnum->fKeyWord) {
auto tokenWalker = fParent->fTokens.begin();
std::advance(tokenWalker, fPriorIndex);
while (tokenWalker != fParent->fTokens.end()) {
std::advance(tokenWalker, 1);
++fPriorIndex;
if (Punctuation::kSemicolon == tokenWalker->fPunctuation) {
break;
}
}
while (tokenWalker != fParent->fTokens.end()) {
std::advance(tokenWalker, 1);
const Definition* test = &*tokenWalker;
if (Definition::Type::kBracket != test->fType ||
(Bracket::kSlashSlash != test->fBracket
&& Bracket::kSlashStar != test->fBracket)) {
break;
}
}
auto saveTokenWalker = tokenWalker;
Definition* start = &*tokenWalker;
bool foundExpected = true;
for (KeyWord expected : {KeyWord::kStatic, KeyWord::kConstExpr, KeyWord::kInt}){
const Definition* test = &*tokenWalker;
if (expected != test->fKeyWord) {
foundExpected = false;
break;
}
if (tokenWalker == fParent->fTokens.end()) {
break;
}
std::advance(tokenWalker, 1);
}
if (!foundExpected) {
foundExpected = true;
tokenWalker = saveTokenWalker;
for (KeyWord expected : {KeyWord::kStatic, KeyWord::kConst, KeyWord::kNone}){
const Definition* test = &*tokenWalker;
if (expected != test->fKeyWord) {
foundExpected = false;
break;
}
if (tokenWalker == fParent->fTokens.end()) {
break;
}
if (KeyWord::kNone != expected) {
std::advance(tokenWalker, 1);
}
}
if (foundExpected) {
auto nameToken = priorEnum->fTokens.begin();
string enumName = string(nameToken->fContentStart,
nameToken->fContentEnd - nameToken->fContentStart);
const Definition* test = &*tokenWalker;
string constType = string(test->fContentStart,
test->fContentEnd - test->fContentStart);
if (enumName != constType) {
foundExpected = false;
} else {
std::advance(tokenWalker, 1);
}
}
}
if (foundExpected && tokenWalker != fParent->fTokens.end()) {
const char* nameStart = tokenWalker->fStart;
std::advance(tokenWalker, 1);
if (tokenWalker != fParent->fTokens.end()) {
TextParser tp(fFileName, nameStart, tokenWalker->fStart, fLineCount);
tp.skipToNonName();
start->fName = string(nameStart, tp.fChar - nameStart);
start->fContentEnd = fChar;
priorEnum->fChildren.emplace_back(start);
fPriorEnum = priorEnum;
}
}
}
}
this->addPunctuation(Punctuation::kSemicolon);
fInFunction = false;
break;
case '~':
if (fInEnum) {
break;
}
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
// TODO: don't want to parse numbers, but do need to track for enum defs
// break;
case 'A': case 'B': case 'C': case 'D': case 'E':
case 'F': case 'G': case 'H': case 'I': case 'J':
case 'K': case 'L': case 'M': case 'N': case 'O':
case 'P': case 'Q': case 'R': case 'S': case 'T':
case 'U': case 'V': case 'W': case 'X': case 'Y':
case 'Z': case '_':
case 'a': case 'b': case 'c': case 'd': case 'e':
case 'f': case 'g': case 'h': case 'i': case 'j':
case 'k': case 'l': case 'm': case 'n': case 'o':
case 'p': case 'q': case 'r': case 's': case 't':
case 'u': case 'v': case 'w': case 'x': case 'y':
case 'z':
if (fInCharCommentString || fInBrace) {
break;
}
if (!fIncludeWord) {
fIncludeWord = fChar;
}
break;
}
done:
fPrev = test;
this->next();
return true;
}
void IncludeParser::validate() const {
IncludeParser::ValidateKeyWords();
}
bool IncludeParser::references(const SkString& file) const {
// if includes weren't passed one at a time, assume all references are valid
if (fIncludeMap.empty()) {
return true;
}
SkASSERT(file.endsWith(".bmh") );
string root(file.c_str(), file.size() - 4);
string kReference("_Reference");
if (string::npos != root.find(kReference)) {
root = root.substr(0, root.length() - kReference.length());
}
if (fIClassMap.end() != fIClassMap.find(root)) {
return true;
}
if (fIStructMap.end() != fIStructMap.find(root)) {
return true;
}
if (fIEnumMap.end() != fIEnumMap.find(root)) {
return true;
}
if (fIFunctionMap.end() != fIFunctionMap.find(root)) {
return true;
}
return false;
}
void IncludeParser::RemoveFile(const char* docs, const char* includes) {
if (!sk_isdir(includes)) {
IncludeParser::RemoveOneFile(docs, includes);
} else {
SkOSFile::Iter it(includes, ".h");
for (SkString file; it.next(&file); ) {
SkString p = SkOSPath::Join(includes, file.c_str());
const char* hunk = p.c_str();
if (!SkStrEndsWith(hunk, ".h")) {
continue;
}
IncludeParser::RemoveOneFile(docs, hunk);
}
}
}
void IncludeParser::RemoveOneFile(const char* docs, const char* includesFile) {
const char* lastForward = strrchr(includesFile, '/');
const char* lastBackward = strrchr(includesFile, '\\');
const char* last = lastForward > lastBackward ? lastForward : lastBackward;
if (!last) {
last = includesFile;
} else {
last += 1;
}
SkString baseName(last);
SkASSERT(baseName.endsWith(".h"));
baseName.remove(baseName.size() - 2, 2);
baseName.append("_Reference.bmh");
SkString fullName = SkOSPath::Join(docs, baseName.c_str());
remove(fullName.c_str());
}
Bracket IncludeParser::topBracket() const {
Definition* parent = this->parentBracket(fParent);
return parent ? parent->fBracket : Bracket::kNone;
}