/* * 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& methodParams, const vector& 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("expected parent"); } if (Definition::Type::kBracket != poundDef->fType) { return reportError("expected bracket"); } if (Bracket::kPound != poundDef->fBracket) { return reportError("expected preprocessor"); } if (KeyWord::kNone != poundDef->fKeyWord) { return reportError("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("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("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 #include 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("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& map = fIClassMap; IClassDefinition& markupDef = map[className]; if (markupDef.fStart) { typedef IClassDefinition* IClassDefPtr; return INHERITED::reportError("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 methodParams; vector foundParams; Definition methodName; TextParser methodParser(token.fFileName, token.fContentStart, token.fContentEnd, token.fLineCount); bool debugCode = methodParser.skipExact("SkDEBUGCODE("); if (MarkType::kMethod == token.fMarkType) { methodName.fName = debugCode ? token.fName : string(token.fContentStart, (int) (token.fContentEnd - token.fContentStart)); methodHasReturn = !methodParser.startsWith("void ") && !methodParser.startsWith("static void ") && !methodParser.strnchr('~', methodParser.fEnd); const char* paren = methodParser.strnchr('(', methodParser.fEnd); const char* nextEnd = paren; do { string paramName; methodParser.fChar = nextEnd + 1; methodParser.skipSpace(); if (!methodName.nextMethodParam(&methodParser, &nextEnd, ¶mName)) { continue; } methodParams.push_back(paramName); } while (')' != nextEnd[0]); } for (const auto& child : token.fTokens) { if (Definition::Type::kMark == child.fType && MarkType::kMember == child.fMarkType) { break; } if (Definition::Type::kMark == child.fType && MarkType::kComment == child.fMarkType) { if (child.fPrivate) { break; } if ('@' == child.fContentStart[0]) { TextParser parser(&child); do { parser.next(); if (parser.startsWith("param ")) { parser.skipWord("param"); const char* parmStart = parser.fChar; parser.skipToSpace(); string parmName = string(parmStart, (int) (parser.fChar - parmStart)); parser.skipWhiteSpace(); do { size_t nextComma = parmName.find(','); string piece; if (string::npos == nextComma) { piece = parmName; parmName = ""; } else { piece = parmName.substr(0, nextComma); parmName = parmName.substr(nextComma + 1); } if (sawParam) { if (multiline) { this->lf(1); } this->writeEndTag(); } else { if (sawComment) { this->nl(); } this->lf(2); } foundParams.emplace_back(piece); this->writeTag("Param", piece); this->writeSpace(2); this->writeBlock(parser.fEnd - parser.fChar, parser.fChar); this->lf(1); sawParam = true; sawComment = false; } while (parmName.length()); parser.skipTo(parser.fEnd); } else if (parser.startsWith("return ") || parser.startsWith("returns ")) { parser.skipWord("return"); if ('s' == parser.peek()) { parser.next(); } if (sawParam) { if (multiline) { this->lf(1); } this->writeEndTag(); } this->checkForMissingParams(methodParams, foundParams); sawParam = false; sawComment = false; multiline = false; this->lf(2); this->writeTag("Return"); this->writeSpace(2); this->writeBlock(parser.fEnd - parser.fChar, parser.fChar); this->lf(1); sawReturn = true; parser.skipTo(parser.fEnd); } else { this->reportError("unexpected doxygen directive"); } } while (!parser.eof()); } else if (child.length() > 1) { const char* start = child.fContentStart; ptrdiff_t length = child.fContentEnd - start; SkASSERT(length >= 0); while (length && '/' == start[0]) { start += 1; --length; } while (length && '/' == start[length - 1]) { length -= 1; if (length && '*' == start[length - 1]) { length -= 1; } } if (length) { this->lfAlways(sawComment || sawParam || sawReturn ? 1 : 2); if (sawParam || sawReturn) { this->indentToColumn(8); } this->writeBlock(length, start); this->writeSpace(); sawComment = true; if (sawParam || sawReturn) { multiline = true; } } } } } if (sawParam || sawReturn) { if (multiline) { this->lf(1); } this->writeEndTag(); } if (!sawReturn) { if (!sawParam) { if (sawComment) { this->nl(); } this->lf(2); } this->checkForMissingParams(methodParams, foundParams); } if (methodHasReturn != sawReturn) { if (!methodHasReturn) { this->reportError("unexpected doxygen return"); } else { if (sawComment) { this->nl(); } this->lf(2); this->writeIncompleteTag("Return"); } } } void IncludeParser::dumpCommonTail(const Definition& token) { this->lf(2); this->writeTag("Example"); this->lf(1); this->writeString("// incomplete"); this->lf(1); this->writeEndTag(); this->lf(2); this->writeTag("SeeAlso"); this->writeSpace(); this->writeString("incomplete"); this->lf(2); this->writeEndTag(BmhParser::kMarkProps[(int) token.fMarkType].fName); this->lf(2); } void IncludeParser::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 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("invalid mark type"); } string name = this->uniqueName(*mapIter->fInclude, typeName); Definition& markupDef = *(*mapIter->fInclude)[name]; if (markupDef.fStart) { return reportError("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("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("unexpected end"); } if ('{' == iter->fStart[0] && Definition::Type::kPunctuation == iter->fType) { break; } } while (static_cast(iter = std::next(iter)), true); if (Punctuation::kLeftBrace != iter->fPunctuation) { return iter->reportError("expected left brace"); } IClassDefinition* markupDef = this->defineClass(*includeDef, nameStr); if (!markupDef) { return iter->reportError("expected markup definition"); } markupDef->fStart = iter->fStart; if (!this->findComments(*includeDef, markupDef)) { return iter->reportError("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("expected parent is file"); } string filename = markupDef->fileName(); if (!parser.skipWord(filename.c_str())) { return reportError("missing object type"); } } else if (parser.skipExact("fn")) { SkASSERT(0); // incomplete } else { if (!parser.skipWord(kKeyWords[(int) markupDef->fKeyWord].fName)) { return reportError("missing object type"); } if (!parser.skipWord(markupDef->fName.c_str()) && KeyWord::kEnum != markupDef->fKeyWord) { return reportError("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 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("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("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("failed to parse const or constexpr"); } break; case KeyWord::kEnum: if (!this->parseEnum(child, markupDef)) { return child->reportError("failed to parse enum"); } break; case KeyWord::kStruct: if (!this->parseClass(child, IsStruct::kYes)) { return child->reportError("failed to parse struct"); } break; case KeyWord::kTemplate: if (!this->parseTemplate(child, markupDef)) { return child->reportError("failed to parse template"); } break; case KeyWord::kTypedef: if (!this->parseTypedef(child, markupDef)) { return child->reportError("failed to parse typedef"); } break; case KeyWord::kUnion: if (!this->parseUnion()) { return child->reportError("failed to parse union"); } break; default: return child->reportError("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("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("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("expected operator overload"); } } break; default: return child->reportError("unhandled bracket"); } break; case Definition::Type::kWord: if (MarkType::kMember != child->fMarkType) { return child->reportError("unhandled word type"); } if (!this->parseMember(child, markupDef)) { return child->reportError("unparsable member"); } break; default: return child->reportError("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("malformed char"); } if (fInString) { return reportError("malformed string"); } if (!this->checkForWord()) { return false; } if (Bracket::kPound == this->topBracket()) { KeyWord keyWord = fParent->fKeyWord; if (KeyWord::kNone == keyWord) { return this->reportError("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("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("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("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("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("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 ] [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::iterator baseIter = fParent->fTokens.end(); list::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("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("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; }