diff options
author | Cary Clark <caryclark@skia.org> | 2017-07-28 11:04:54 -0400 |
---|---|---|
committer | Skia Commit-Bot <skia-commit-bot@chromium.org> | 2017-07-28 15:30:38 +0000 |
commit | 8032b983faaa8c76f81bf3cf028e9c64f4635478 (patch) | |
tree | ed3be061ff02a99dab1b3e443d48b7f5c906417e /tools/bookmaker/includeParser.cpp | |
parent | acaa607328fb0dfac0894d4a2fcdead520e696b3 (diff) |
bookmaker initial checkin
bookmaker is a tool that generates documentation
backends from a canonical markup. Documentation for
bookmaker itself is evolving at docs/usingBookmaker.bmh,
which is visible online at skia.org/user/api/bmh_usingBookmaker
Change-Id: Ic76ddf29134895b5c2ebfbc84603e40ff08caf09
Reviewed-on: https://skia-review.googlesource.com/28000
Commit-Queue: Cary Clark <caryclark@google.com>
Reviewed-by: Cary Clark <caryclark@google.com>
Diffstat (limited to 'tools/bookmaker/includeParser.cpp')
-rw-r--r-- | tools/bookmaker/includeParser.cpp | 1733 |
1 files changed, 1733 insertions, 0 deletions
diff --git a/tools/bookmaker/includeParser.cpp b/tools/bookmaker/includeParser.cpp new file mode 100644 index 0000000000..089dcb3658 --- /dev/null +++ b/tools/bookmaker/includeParser.cpp @@ -0,0 +1,1733 @@ +/* + * 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" + +enum class KeyProperty { + kNone, + kClassSection, + kFunction, + kModifier, + kNumber, + kObject, + kPreprocessor, +}; + +struct IncludeKey { + const char* fName; + KeyWord fKeyWord; + KeyProperty fProperty; +}; + +const IncludeKey kKeyWords[] = { + { "", KeyWord::kNone, KeyProperty::kNone }, + { "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 }, + { "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 }, + { "uint32_t", KeyWord::kUint32_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) { + 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); + 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->keywordStart("Param"); + fprintf(fOut, "%s ", methodParam.c_str()); + this->keywordEnd(); + } + } + 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: + case KeyWord::kInclude: + 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; +} + +bool IncludeParser::crossCheck(BmhParser& bmhParser) { + string className = this->className(); + string classPrefix = className + "::"; + RootDefinition* root = &bmhParser.fClassMap[className]; + root->clearVisited(); + for (auto& classMapper : fIClassMap) { + if (className != classMapper.first + && classPrefix != classMapper.first.substr(0, classPrefix.length())) { + continue; + } + 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); + switch (token.fMarkType) { + case MarkType::kMethod: { + if (0 == token.fName.find("internal_") + || 0 == token.fName.find("Internal_") + || 0 == token.fName.find("legacy_") + || 0 == token.fName.find("temporary_")) { + continue; + } + const char* methodID = bmhParser.fMaps[(int) token.fMarkType].fName; + if (!def) { + string paramName = className + "::"; + paramName += string(token.fContentStart, + token.fContentEnd - token.fContentStart); + def = root->find(paramName); + 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); + } + } + 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); + } + if (!def && 0 == token.fName.find("SK_")) { + string incName = token.fName + "()"; + string macroName = className + "::" + incName; + def = root->find(macroName); + if (def) { + if (def->fName == incName) { + def->fVisited = true; + if ("SK_TO_STRING_NONVIRT" == token.fName) { + def = root->find(className + "::toString"); + if (def) { + def->fVisited = true; + } else { + SkDebugf("missing toString bmh: %s\n", fullName.c_str()); + } + } + break; + } else { + SkDebugf("method macro differs from bmh: %s\n", fullName.c_str()); + } + } + } + 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); + } + } + if (!def) { + SkDebugf("method missing from bmh: %s\n", fullName.c_str()); + break; + } + if (def->crossCheck(methodID, token)) { + def->fVisited = true; + } else { + SkDebugf("method differs from bmh: %s\n", fullName.c_str()); + } + } break; + case MarkType::kComment: + break; + 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.skipToNonAlphaNum(); + 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); + } + if (!def) { + SkDebugf("enum missing from bmh: %s\n", fullName.c_str()); + break; + } + } + def->fVisited = true; + for (auto& child : def->fChildren) { + if (MarkType::kCode == child->fMarkType) { + def = child; + break; + } + } + if (MarkType::kCode != def->fMarkType) { + SkDebugf("enum code missing from bmh: %s\n", fullName.c_str()); + break; + } + if (def->crossCheck(token)) { + def->fVisited = true; + } else { + SkDebugf("enum differs from bmh: %s\n", def->fName.c_str()); + } + for (auto& child : token.fChildren) { + string constName = className + "::" + child->fName; + def = root->find(constName); + if (!def) { + string innerName = classMapper.first + "::" + child->fName; + def = root->find(innerName); + } + if (!def) { + if (string::npos == child->fName.find("Legacy_")) { + SkDebugf("const missing from bmh: %s\n", constName.c_str()); + } + } else { + def->fVisited = true; + } + } + } break; + case MarkType::kMember: + if (def) { + def->fVisited = true; + } else { + SkDebugf("member missing from bmh: %s\n", fullName.c_str()); + } + break; + default: + SkASSERT(0); // unhandled + break; + } + } + } + if (!root->dumpUnVisited()) { + SkDebugf("some struct elements not found; struct finding in includeParser is missing\n"); + } + return true; +} + +IClassDefinition* IncludeParser::defineClass(const Definition& includeDef, + const 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) { + fprintf(fOut, "%s", + "# ------------------------------------------------------------------------------\n"); + fprintf(fOut, "" "\n"); + } + switch (token.fMarkType) { + case MarkType::kEnum: + fprintf(fOut, "#Enum %s" "\n", + token.fName.c_str()); + fprintf(fOut, "" "\n"); + fprintf(fOut, "#Code" "\n"); + fprintf(fOut, " enum %s {" "\n", + token.fName.c_str()); + for (auto& child : token.fChildren) { + fprintf(fOut, " %s %.*s" "\n", + child->fName.c_str(), child->length(), child->fContentStart); + } + fprintf(fOut, " };" "\n"); + fprintf(fOut, "##" "\n"); + fprintf(fOut, "" "\n"); + this->dumpComment(&token); + for (auto& child : token.fChildren) { + fprintf(fOut, "#Const %s", child->fName.c_str()); + 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; + } + fprintf(fOut, " %.*s", (int) (valEnd - val.fStart), val.fStart); + } else { + fprintf(fOut, " %.*s", + (int) (child->fContentEnd - child->fContentStart), + child->fContentStart); + } + } + fprintf(fOut, "" "\n"); + for (auto& token : child->fTokens) { + if (MarkType::kComment == token.fMarkType) { + this->dumpComment(&token); + } + } + fprintf(fOut, "##" "\n"); + } + fprintf(fOut, "" "\n"); + break; + case MarkType::kMethod: + fprintf(fOut, "#Method %.*s" "\n", + token.length(), token.fStart); + lfAlways(1); + this->dumpComment(&token); + break; + case MarkType::kMember: + this->keywordStart("Member"); + fprintf(fOut, "%.*s %s ", (int) (token.fContentEnd - token.fContentStart), + token.fContentStart, token.fName.c_str()); + lfAlways(1); + for (auto child : token.fChildren) { + fprintf(fOut, "%.*s", (int) (child->fContentEnd - child->fContentStart), + child->fContentStart); + lfAlways(1); + } + this->keywordEnd(); + continue; + break; + default: + SkASSERT(0); + } + this->lf(2); + fprintf(fOut, "#Example" "\n"); + fprintf(fOut, "##" "\n"); + fprintf(fOut, "" "\n"); + fprintf(fOut, "#ToDo incomplete ##" "\n"); + fprintf(fOut, "" "\n"); + fprintf(fOut, "##" "\n"); + fprintf(fOut, "" "\n"); + } +} +void IncludeParser::dumpComment(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); + if (MarkType::kMethod == token->fMarkType) { + methodName.fName = string(token->fContentStart, + (int) (token->fContentEnd - token->fContentStart)); + methodHasReturn = !methodParser.startsWith("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::kComment == child.fMarkType) { + 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->lfAlways(1); + } + this->keywordEnd(); + } else { + if (sawComment) { + this->nl(); + } + this->lf(2); + } + foundParams.emplace_back(piece); + this->keywordStart("Param"); + fprintf(fOut, "%s ", piece.c_str()); + fprintf(fOut, "%.*s", (int) (parser.fEnd - parser.fChar), parser.fChar); + this->lfAlways(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->lfAlways(1); + } + this->keywordEnd(); + } + this->checkForMissingParams(methodParams, foundParams); + sawParam = false; + sawComment = false; + multiline = false; + this->lf(2); + this->keywordStart("Return"); + fprintf(fOut, "%.*s ", (int) (parser.fEnd - parser.fChar), + parser.fChar); + this->lfAlways(1); + sawReturn = true; + parser.skipTo(parser.fEnd); + } else { + this->reportError("unexpected doxygen directive"); + } + } while (!parser.eof()); + } else { + if (sawComment) { + this->nl(); + } + this->lf(1); + fprintf(fOut, "%.*s ", child.length(), child.fContentStart); + sawComment = true; + if (sawParam || sawReturn) { + multiline = true; + } + } + } + } + if (sawParam || sawReturn) { + if (multiline) { + this->lfAlways(1); + } + this->keywordEnd(); + } + 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->keywordStart("Return"); + this->keywordEnd(); + } + } +} + + // dump equivalent markup +void IncludeParser::dumpTokens() { + string skClassName = this->className(); + string fileName = skClassName + ".bmh"; + fOut = fopen(fileName.c_str(), "wb"); + if (!fOut) { + SkDebugf("could not open output file %s\n", fileName.c_str()); + return; + } + string prefixName = skClassName.substr(0, 2); + string topicName = skClassName.length() > 2 && isupper(skClassName[2]) && + ("Sk" == prefixName || "Gr" == prefixName) ? skClassName.substr(2) : skClassName; + fprintf(fOut, "#Topic %s", topicName.c_str()); + this->lfAlways(2); + fprintf(fOut, "#Class %s", skClassName.c_str()); + this->lfAlways(2); + auto& classMap = fIClassMap[skClassName]; + auto& tokens = classMap.fTokens; + for (auto& token : tokens) { + if (Definition::Type::kMark != token.fType || MarkType::kComment != token.fMarkType) { + continue; + } + fprintf(fOut, "%.*s", (int) (token.fContentEnd - token.fContentStart), + token.fContentStart); + this->lfAlways(1); + } + this->lf(2); + string className(skClassName.substr(2)); + vector<string> sortedClasses; + size_t maxLen = 0; + for (const auto& oneClass : fIClassMap) { + if (skClassName + "::" != oneClass.first.substr(0, skClassName.length() + 2)) { + continue; + } + string structName = oneClass.first.substr(skClassName.length() + 2); + maxLen = SkTMax(maxLen, structName.length()); + sortedClasses.emplace_back(structName); + } + fprintf(fOut, "#Topic Overview"); + this->lfAlways(2); + fprintf(fOut, "#Subtopic %s_Structs", className.c_str()); + this->lfAlways(1); + fprintf(fOut, "#Table"); + this->lfAlways(1); + fprintf(fOut, "#Legend"); + this->lfAlways(1); + fprintf(fOut, "# %-*s # description ##", (int) maxLen, "struct"); + this->lfAlways(1); + fprintf(fOut, "#Legend ##"); + this->lfAlways(1); + fprintf(fOut, "#Table ##"); + this->lfAlways(1); + for (auto& name : sortedClasses) { + fprintf(fOut, "# %-*s # ##", (int) maxLen, name.c_str()); + this->lfAlways(1); + } + fprintf(fOut, "#Subtopic ##"); + this->lfAlways(2); + fprintf(fOut, "#Subtopic %s_Member_Functions", className.c_str()); + this->lfAlways(1); + fprintf(fOut, "#Table"); + this->lfAlways(1); + fprintf(fOut, "#Legend"); + this->lfAlways(1); + maxLen = 0; + vector<string> sortedNames; + for (const auto& token : classMap.fTokens) { + if (Definition::Type::kMark != token.fType || MarkType::kMethod != token.fMarkType) { + continue; + } + const string& name = token.fName; + if (name.substr(0, 7) == "android" || string::npos != name.find("nternal_")) { + continue; + } + if (name[name.length() - 2] == '_' && isdigit(name[name.length() - 1])) { + continue; + } + size_t paren = name.find('('); + size_t funcLen = string::npos == paren ? name.length() : paren; + maxLen = SkTMax(maxLen, funcLen); + sortedNames.emplace_back(name); + } + std::sort(sortedNames.begin(), sortedNames.end()); + fprintf(fOut, "# %-*s # description ##" "\n", + (int) maxLen, "function"); + fprintf(fOut, "#Legend ##" "\n"); + for (auto& name : sortedNames) { + size_t paren = name.find('('); + size_t funcLen = string::npos == paren ? name.length() : paren; + fprintf(fOut, "# %-*s # ##" "\n", + (int) maxLen, name.substr(0, funcLen).c_str()); + } + fprintf(fOut, "#Table ##" "\n"); + fprintf(fOut, "#Subtopic ##" "\n"); + fprintf(fOut, "" "\n"); + fprintf(fOut, "#Topic ##" "\n"); + fprintf(fOut, "" "\n"); + + for (auto& oneClass : fIClassMap) { + if (skClassName + "::" != oneClass.first.substr(0, skClassName.length() + 2)) { + continue; + } + string innerName = oneClass.first.substr(skClassName.length() + 2); + fprintf(fOut, "%s", + "# ------------------------------------------------------------------------------"); + this->lfAlways(2); + fprintf(fOut, "#Struct %s", innerName.c_str()); + this->lfAlways(2); + for (auto& token : oneClass.second.fTokens) { + if (Definition::Type::kMark != token.fType || MarkType::kComment != token.fMarkType) { + continue; + } + fprintf(fOut, "%.*s", (int) (token.fContentEnd - token.fContentStart), + token.fContentStart); + this->lfAlways(1); + } + this->lf(2); + this->dumpClassTokens(oneClass.second); + this->lf(2); + fprintf(fOut, "#Struct %s ##", innerName.c_str()); + this->lfAlways(2); + } + this->dumpClassTokens(classMap); + fprintf(fOut, "#Class %s ##" "\n", + skClassName.c_str()); + fprintf(fOut, "" "\n"); + fprintf(fOut, "#Topic %s ##" "\n", + topicName.c_str()); + fclose(fOut); +} + +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); + if (includeDef->fTokens.size() == 1) { + return true; // forward declaration only + } + // 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; + do { + if (iter == includeDef->fTokens.end()) { + return false; + } + 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 false; + } + IClassDefinition* markupDef = this->defineClass(*includeDef, nameStr); + if (!markupDef) { + return false; + } + markupDef->fStart = iter->fStart; + if (!this->findComments(*includeDef, markupDef)) { + return false; + } +// 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 lastPublic = publicIndex; + 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 (iter != includeDef->fTokens.end() + && (protectedLen != (size_t) (iter->fContentEnd - iter->fStart) + || strncmp(iter->fStart, protectedName, protectedLen)) + && (privateLen != (size_t) (iter->fContentEnd - iter->fStart) + || strncmp(iter->fStart, privateName, privateLen))) { + iter = std::next(iter); + ++lastPublic; + } + while (childIter != includeDef->fChildren.end() && (*childIter)->fParentIndex < lastPublic) { + Definition* child = *childIter; + if (!this->parseObject(child, markupDef)) { + return false; + } + childIter = std::next(childIter); + } + while (childIter != includeDef->fChildren.end()) { + (*childIter)->fPrivate = true; + childIter = std::next(childIter); + } + SkASSERT(fParent->fParent); + fParent = fParent->fParent; + return true; +} + +bool IncludeParser::parseComment(const 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(); + if (!parser.skipWord(kKeyWords[(int) markupDef->fKeyWord].fName)) { + return reportError<bool>("missing object type"); + } + if (!parser.skipWord(markupDef->fName.c_str())) { + 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); + parser.skipToEndBracket('\n'); + } + return true; +} + +bool IncludeParser::parseDefine() { + + return true; +} + +bool IncludeParser::parseEnum(Definition* child, Definition* markupDef) { + 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); + } + } + markupDef->fTokens.emplace_back(MarkType::kEnum, child->fContentStart, child->fContentEnd, + child->fLineCount, markupDef); + Definition* markupChild = &markupDef->fTokens.back(); + SkASSERT(KeyWord::kNone == markupChild->fKeyWord); + markupChild->fKeyWord = KeyWord::kEnum; + TextParser enumName(child); + enumName.skipExact("enum "); + const char* nameStart = enumName.fChar; + enumName.skipToSpace(); + markupChild->fName = markupDef->fName + "::" + + string(nameStart, (size_t) (enumName.fChar - nameStart)); + if (!this->findComments(*child, markupChild)) { + return false; + } + TextParser parser(child); + parser.skipToEndBracket('{'); + const char* dataEnd; + do { + parser.next(); + 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); + 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; + } + parser.skipToNonAlphaNum(); + string memberName(memberStart, parser.fChar); + parser.skipWhiteSpace(); + const char* dataStart = parser.fChar; + SkASSERT('=' == dataStart[0] || ',' == dataStart[0] || '}' == dataStart[0] + || '/' == dataStart[0]); + dataEnd = parser.anyOf(",}"); + markupChild->fTokens.emplace_back(MarkType::kMember, dataStart, dataEnd, parser.fLineCount, + markupChild); + Definition* member = &markupChild->fTokens.back(); + member->fName = memberName; + if (comment) { + member->fChildren.push_back(comment); + } + markupChild->fChildren.push_back(member); + parser.skipToEndBracket(dataEnd[0]); + } while (',' == dataEnd[0]); + for (size_t index = 1; index < child->fChildren.size(); ++index) { + const Definition* follower = child->fChildren[index]; + if (Definition::Type::kKeyWord == follower->fType) { + markupChild->fTokens.emplace_back(MarkType::kMember, follower->fContentStart, + follower->fContentEnd, follower->fLineCount, markupChild); + Definition* member = &markupChild->fTokens.back(); + member->fName = follower->fName; + markupChild->fChildren.push_back(member); + } + } + 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(const 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); + Definition* markupChild = &markupDef->fTokens.back(); + TextParser nameParser(child); + nameParser.skipToNonAlphaNum(); + string nameStr = string(child->fContentStart, nameParser.fChar - child->fContentStart); + IClassDefinition& classDef = fIClassMap[markupDef->fName]; + string uniqueName = this->uniqueName(classDef.fMethods, nameStr); + markupChild->fName = uniqueName; + 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', OneLine::kYes); + 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); + 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); + string nameStr(tokenIter->fStart, tokenIter->fContentEnd - tokenIter->fStart); + while (tokenIter != child->fParent->fTokens.begin()) { + auto testIter = std::prev(tokenIter); + switch (testIter->fType) { + case Definition::Type::kWord: + 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; + auto testIter = child->fParent->fTokens.begin(); + SkASSERT(child->fParentIndex > 0); + std::advance(testIter, child->fParentIndex - 1); + 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; + } + } + markupDef->fTokens.emplace_back(MarkType::kMethod, start, end, tokenIter->fLineCount, + markupDef); + 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; +} + +void IncludeParser::keywordEnd() { + fprintf(fOut, "##"); + this->lfAlways(1); +} + +void IncludeParser::keywordStart(const char* keyword) { + this->lf(1); + fprintf(fOut, "#%s ", keyword); +} + +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 this->reportError<bool>("failed to parse class"); + } + break; + case KeyWord::kEnum: + if (!this->parseEnum(child, markupDef)) { + return this->reportError<bool>("failed to parse enum"); + } + break; + case KeyWord::kStruct: + if (!this->parseClass(child, IsStruct::kYes)) { + return this->reportError<bool>("failed to parse struct"); + } + break; + case KeyWord::kTemplate: + if (!this->parseTemplate()) { + return this->reportError<bool>("failed to parse template"); + } + break; + case KeyWord::kTypedef: + if (!this->parseTypedef()) { + return this->reportError<bool>("failed to parse typedef"); + } + break; + case KeyWord::kUnion: + if (!this->parseUnion()) { + return this->reportError<bool>("failed to parse union"); + } + break; + default: + return this->reportError<bool>("unhandled keyword"); + } + break; + case Definition::Type::kBracket: + switch (child->fBracket) { + case Bracket::kParen: + if (!this->parseMethod(child, markupDef)) { + return this->reportError<bool>("failed to parse method"); + } + break; + case Bracket::kSlashSlash: + case Bracket::kSlashStar: + // comments are picked up by parsing objects first + break; + case Bracket::kPound: + // special-case the #xxx xxx_DEFINED entries + switch (child->fKeyWord) { + case KeyWord::kIfndef: + case KeyWord::kIfdef: + if (child->boilerplateIfDef(fParent)) { + if (!this->parseObjects(child, markupDef)) { + return false; + } + break; + } + goto preproError; + case KeyWord::kDefine: + if (child->boilerplateDef(fParent)) { + break; + } + goto preproError; + case KeyWord::kEndif: + if (child->boilerplateEndIf()) { + break; + } + case KeyWord::kInclude: + // ignored for now + break; + case KeyWord::kElse: + case KeyWord::kElif: + // todo: handle these + break; + default: + preproError: + return this->reportError<bool>("unhandled preprocessor"); + } + break; + case Bracket::kAngle: + // pick up templated function pieces when method is found + break; + default: + return this->reportError<bool>("unhandled bracket"); + } + break; + case Definition::Type::kWord: + if (MarkType::kMember != child->fMarkType) { + return this->reportError<bool>("unhandled word type"); + } + if (!this->parseMember(child, markupDef)) { + return this->reportError<bool>("unparsable member"); + } + break; + default: + return this->reportError<bool>("unhandled type"); + break; + } + return true; +} + +bool IncludeParser::parseTemplate() { + + return true; +} + +bool IncludeParser::parseTypedef() { + + 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 (KeyWord::kInclude == keyWord || KeyWord::kDefine == keyWord) { + this->popBracket(); + } + } 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()) { + this->popBracket(); + } + 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 ':': + case '(': + case '[': + case '{': { + if (fInCharCommentString) { + 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 (!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) { + bool deprecatedMacro = false; + if (')' == test) { + auto iter = fParent->fTokens.end(); + bool lookForWord = false; + while (fParent->fTokens.begin() != iter) { + --iter; + if (lookForWord) { + if (Definition::Type::kWord != iter->fType) { + break; + } + string word(iter->fContentStart, iter->length()); + if ("SK_ATTR_EXTERNALLY_DEPRECATED" == word) { + deprecatedMacro = true; + // remove macro paren (would confuse method parsing later) + fParent->fTokens.pop_back(); + fParent->fChildren.pop_back(); + } + break; + } + if (Definition::Type::kBracket != iter->fType) { + break; + } + if (Bracket::kParen != iter->fBracket) { + break; + } + lookForWord = true; + } + } + fInFunction = ')' == test && !deprecatedMacro; + } else { + fInFunction = '}' != test; + } + } 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::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 '&': + 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)) { + if (KeyWord::kEnum == fParent->fKeyWord) { + fInEnum = false; + } + this->popObject(); + } 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; + fParent->fChildren.push_back(member); + for (auto nameType = baseIter; nameType != namedIter; ++nameType) { + member->fChildren.push_back(&*nameType); + } + + } + } else if (fParent->fChildren.size() > 0) { + auto lastIter = fParent->fChildren.end(); + Definition* 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; + } + } + if (Definition::Type::kKeyWord == priorEnum->fType + && KeyWord::kEnum == priorEnum->fKeyWord) { + auto tokenWalker = fParent->fTokens.begin(); + std::advance(tokenWalker, priorEnum->fParentIndex); + SkASSERT(KeyWord::kEnum == tokenWalker->fKeyWord); + while (tokenWalker != fParent->fTokens.end()) { + std::advance(tokenWalker, 1); + 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; + } + } + 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 && 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.skipToNonAlphaNum(); + start->fName = string(nameStart, tp.fChar - nameStart); + start->fContentEnd = fChar; + priorEnum->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; + ++fChar; + return true; +} + +void IncludeParser::validate() const { + for (int index = 0; index <= (int) Last_MarkType; ++index) { + SkASSERT(fMaps[index].fMarkType == (MarkType) index); + } + IncludeParser::ValidateKeyWords(); +} |