diff options
Diffstat (limited to 'tools/bookmaker/mdOut.cpp')
-rw-r--r-- | tools/bookmaker/mdOut.cpp | 1286 |
1 files changed, 990 insertions, 296 deletions
diff --git a/tools/bookmaker/mdOut.cpp b/tools/bookmaker/mdOut.cpp index db6a8cf7d6..7f07bc2d3e 100644 --- a/tools/bookmaker/mdOut.cpp +++ b/tools/bookmaker/mdOut.cpp @@ -16,6 +16,155 @@ } \ fprintf(fOut, __VA_ARGS__) +const char* SubtopicKeys::kGeneratedSubtopics[] = { + kClasses, kConstants, kConstructors, kDefines, + kMemberFunctions, kMembers, kOperators, kRelatedFunctions, kStructs, kTypedefs, +}; + +const char* kConstTableStyle = +"<style>" "\n" + ".td_const td, th { border: 2px solid #dddddd; text-align: left; padding: 8px; }" "\n" + ".tr_const tr:nth-child(even) { background-color: #f0f0f0; }" "\n" + ".td2_const td:first-child + td { text-align: center; }" "\n" +"</style>" "\n"; + +const char* kTableDeclaration = "<table style='border-collapse: collapse; width: 62.5em'>"; + +#define kTD_Base "border: 2px solid #dddddd; padding: 8px; " +#define kTH_Left "<th style='text-align: left; " kTD_Base "'>" +#define kTH_Center "<th style='text-align: center; " kTD_Base "'>" + +string kTD_Left = " <td style='text-align: left; " kTD_Base "'>"; +string kTD_Center = " <td style='text-align: center; " kTD_Base "'>"; +string kTR_Dark = " <tr style='background-color: #f0f0f0; '>"; + +const char* kAllConstTableHeader = " <tr>" kTH_Left "Const</th>" "\n" + kTH_Center "Value</th>" "\n" + kTH_Left "Description</th>" "</tr>"; +const char* kSubConstTableHeader = " <tr>" kTH_Left "Const</th>" "\n" + kTH_Center "Value</th>" "\n" + kTH_Left "Details</th>" "\n" + kTH_Left "Description</th>" "</tr>"; +const char* kAllMemberTableHeader = " <tr>" kTH_Left "Type</th>" "\n" + kTH_Left "Name</th>" "\n" + kTH_Left "Description</th>" "</tr>"; +const char* kSubMemberTableHeader = " <tr>" kTH_Left "Type</th>" "\n" + kTH_Left "Name</th>" "\n" + kTH_Left "Details</th>" "\n" + kTH_Left "Description</th>" "</tr>"; +const char* kTopicsTableHeader = " <tr>" kTH_Left "Topic</th>" "\n" + kTH_Left "Description</th>" "</tr>"; + +static string html_file_name(string bmhFileName) { + SkASSERT("docs" == bmhFileName.substr(0, 4)); + SkASSERT('\\' == bmhFileName[4] || '/' == bmhFileName[4]); + SkASSERT(".bmh" == bmhFileName.substr(bmhFileName.length() - 4)); + string result = bmhFileName.substr(5, bmhFileName.length() - 4 - 5); + return result; +} + +string MdOut::anchorDef(string str, string name) { + if (fValidate) { + string htmlName = html_file_name(fFileName); + vector<AnchorDef>& allDefs = fAllAnchorDefs[htmlName]; + if (!std::any_of(allDefs.begin(), allDefs.end(), + [str](AnchorDef compare) { return compare.fDef == str; } )) { + MarkType markType = fLastDef->fMarkType; + if (MarkType::kMethod == markType + && std::any_of(fLastDef->fChildren.begin(), fLastDef->fChildren.end(), + [](const Definition* compare) { + return IncompleteAllowed(compare->fMarkType); } )) { + markType = MarkType::kDeprecated; + } + if (MarkType::kMethod == markType && fLastDef->fClone) { + markType = MarkType::kDeprecated; // TODO: hack to allow missing reference + } + allDefs.push_back( { str, markType } ); + } + } + return "<a name='" + str + "'>" + name + "</a>"; +} + +string MdOut::anchorRef(string ref, string name) { + if (fValidate) { + string htmlName; + size_t hashIndex = ref.find('#'); + if (string::npos != hashIndex && "https://" != ref.substr(0, 8)) { + if (0 == hashIndex) { + htmlName = html_file_name(fFileName); + } else { + htmlName = ref.substr(0, hashIndex); + } + vector<string>& allRefs = fAllAnchorRefs[htmlName]; + string refPart = ref.substr(hashIndex + 1); + if (allRefs.end() == std::find(allRefs.begin(), allRefs.end(), refPart)) { + allRefs.push_back(refPart); + } + } + } + SkASSERT(string::npos != ref.find('#') || string::npos != ref.find("https://")); + return "<a href='" + ref + "'>" + name + "</a>"; +} + +string MdOut::anchorLocalRef(string ref, string name) { + return this->anchorRef("#" + ref, name); +} + +string MdOut::tableDataCodeRef(string ref, string name) { + return kTD_Left + this->anchorRef(ref, "<code>" + name + "</code>") + "</td>"; +} + +string MdOut::tableDataCodeLocalRef(string ref, string name) { + return this->tableDataCodeRef("#" + ref, name); +} + +string MdOut::tableDataCodeLocalRef(string name) { + return this->tableDataCodeLocalRef(name, name); +} + +string MdOut::tableDataCodeRef(const Definition* ref) { + return this->tableDataCodeLocalRef(ref->fFiddle, ref->fName); +} + +string MdOut::tableDataCodeDef(string def, string name) { + return kTD_Left + this->anchorDef(def, "<code>" + name + "</code>") + "</td>"; +} + +string MdOut::tableDataCodeDef(const Definition* def) { + return this->tableDataCodeDef(def->fFiddle, def->fName); +} + +static string table_data_const(const Definition* def, const char** textStartPtr) { + TextParser parser(def); + SkAssertResult(parser.skipToEndBracket('\n')); + string constant = string(def->fContentStart, (int) (parser.fChar - def->fContentStart)); + if (textStartPtr) { + *textStartPtr = parser.fChar; + } + return kTD_Center + constant + "</td>"; +} + +static string out_table_data_description_start() { + return kTD_Left; +} + +static string out_table_data_description(string str) { + return kTD_Left + str + "</td>"; +} + +static string out_table_data_description(const Definition* def) { + return out_table_data_description(string(def->fContentStart, + (int) (def->fContentEnd - def->fContentStart))); +} + +static string out_table_data_details(string details) { + return kTD_Left + details + "</td>"; +} + +#undef kConstTDBase +#undef kTH_Left +#undef kTH_Center + static void add_ref(string leadingSpaces, string ref, string* result) { *result += leadingSpaces + ref; } @@ -43,6 +192,92 @@ static bool all_lower(string ref) { return true; } +// from https://stackoverflow.com/questions/3418231/replace-part-of-a-string-with-another-string +void replace_all(string& str, const string& from, const string& to) { + SkASSERT(!from.empty()); + size_t start_pos = 0; + while ((start_pos = str.find(from, start_pos)) != std::string::npos) { + str.replace(start_pos, from.length(), to); + start_pos += to.length(); // In case 'to' contains 'from', like replacing 'x' with 'yx' + } +} + +// detail strings are preceded by an example comment to check readability +void MdOut::addPopulators() { + fPopulators[SubtopicKeys::kClasses].fName = "Class Declarations"; + fPopulators[SubtopicKeys::kClasses].fOneLiner = "embedded class members"; + fPopulators[SubtopicKeys::kClasses].fDetails = + /* SkImageInfo */ "uses C++ classes to declare the public data" + " structures and interfaces."; + fPopulators[SubtopicKeys::kConstants].fName = "Constants"; + fPopulators[SubtopicKeys::kConstants].fOneLiner = "enum and enum class, and their const values"; + fPopulators[SubtopicKeys::kConstants].fDetails = + /* SkImageInfo */ "related constants are defined by <code>enum</code>," + " <code>enum class</code>, <code>#define</code>, <code>const</code>," + " and <code>constexpr</code>."; + fPopulators[SubtopicKeys::kConstructors].fName = "Constructors"; + fPopulators[SubtopicKeys::kConstructors].fOneLiner = "functions that construct"; + fPopulators[SubtopicKeys::kConstructors].fDetails = + /* SkImageInfo */ "can be constructed or initialized by these functions," + " including C++ class constructors."; + fPopulators[SubtopicKeys::kDefines].fName = "Defines"; + fPopulators[SubtopicKeys::kDefines].fOneLiner = "preprocessor definitions of functions, values"; + fPopulators[SubtopicKeys::kDefines].fDetails = + /* SkImageInfo */ "uses preprocessor definitions to inline code and constants," + " and to abstract platform-specific functionality."; + fPopulators[SubtopicKeys::kMemberFunctions].fName = "Functions"; + fPopulators[SubtopicKeys::kMemberFunctions].fOneLiner = "global and class member functions"; + fPopulators[SubtopicKeys::kMemberFunctions].fDetails = + /* SkImageInfo */ "member functions read and modify the structure properties."; + fPopulators[SubtopicKeys::kMembers].fName = "Members"; + fPopulators[SubtopicKeys::kMembers].fOneLiner = "member values"; + fPopulators[SubtopicKeys::kMembers].fDetails = + /* SkImageInfo */ "members may be read and written directly without using" + " a member function."; + fPopulators[SubtopicKeys::kOperators].fName = "Operators"; + fPopulators[SubtopicKeys::kOperators].fOneLiner = "operator overloading methods"; + fPopulators[SubtopicKeys::kOperators].fDetails = + /* SkImageInfo */ "operators inline class member functions with arithmetic" + " equivalents."; + fPopulators[SubtopicKeys::kRelatedFunctions].fName = "Related Functions"; + fPopulators[SubtopicKeys::kRelatedFunctions].fOneLiner = + "similar member functions grouped together"; + fPopulators[SubtopicKeys::kRelatedFunctions].fDetails = + /* SkImageInfo */ "global, <code>struct</code>, and <code>class</code> related member" + " functions share a topic."; + fPopulators[SubtopicKeys::kStructs].fName = "Struct Declarations"; + fPopulators[SubtopicKeys::kStructs].fOneLiner = "embedded struct members"; + fPopulators[SubtopicKeys::kStructs].fDetails = + /* SkImageInfo */ "uses C++ structs to declare the public data" + " structures and interfaces."; + fPopulators[SubtopicKeys::kTypedefs].fName = "Typedef Declarations"; + fPopulators[SubtopicKeys::kTypedefs].fOneLiner = "types defined by other types"; + fPopulators[SubtopicKeys::kTypedefs].fDetails = + /* SkImageInfo */ " <code>typedef</code> define a data type."; +} + +Definition* MdOut::checkParentsForMatch(Definition* test, string ref) const { + bool isSubtopic = MarkType::kSubtopic == test->fMarkType + || MarkType::kTopic == test->fMarkType; + do { + if (!test->isRoot()) { + continue; + } + bool localTopic = MarkType::kSubtopic == test->fMarkType + || MarkType::kTopic == test->fMarkType; + if (localTopic != isSubtopic) { + continue; + } + string prefix(isSubtopic ? "_" : "::"); + RootDefinition* root = test->asRoot(); + string prefixed = root->fName + prefix + ref; + if (Definition* def = root->find(prefixed, RootDefinition::AllowParens::kYes)) { + return def; + } + } while ((test = test->fParent)); + return nullptr; +} + // FIXME: preserve inter-line spaces and don't add new ones string MdOut::addReferences(const char* refStart, const char* refEnd, BmhParser::Resolvable resolvable) { @@ -84,7 +319,7 @@ string MdOut::addReferences(const char* refStart, const char* refEnd, } else { leadingSpaces = string(base, wordStart - base); } - t.skipToMethodEnd(); + t.skipToMethodEnd(resolvable); if (base == t.fChar) { if (!t.eof() && '~' == base[0] && !isalnum(base[1])) { t.next(); @@ -99,13 +334,18 @@ string MdOut::addReferences(const char* refStart, const char* refEnd, continue; } ref = string(start, t.fChar - start); + if (fMethod && "SkSurface::MakeRenderTarget" == fMethod->fName && "is" == ref) { + SkDebugf(""); + } if (const Definition* def = this->isDefined(t, ref, resolvable)) { if (MarkType::kExternal == def->fMarkType) { + (void) this->anchorRef("undocumented#" + ref, ""); // for anchor validate add_ref(leadingSpaces, ref, &result); continue; } SkASSERT(def->fFiddle.length()); - if (!t.eof() && '(' == t.peek() && t.strnchr(')', t.fEnd)) { + if (BmhParser::Resolvable::kSimple != resolvable + && !t.eof() && '(' == t.peek() && t.strnchr(')', t.fEnd)) { if (!t.skipToEndBracket(')')) { t.reportError("missing close paren"); fAddRefFailed = true; @@ -226,8 +466,9 @@ string MdOut::addReferences(const char* refStart, const char* refEnd, } } } - if (BmhParser::Resolvable::kOut != resolvable && - BmhParser::Resolvable::kFormula != resolvable) { + if (BmhParser::Resolvable::kSimple != resolvable + && BmhParser::Resolvable::kOut != resolvable + && BmhParser::Resolvable::kFormula != resolvable) { t.reportError("missed camelCase"); fAddRefFailed = true; return result; @@ -259,37 +500,25 @@ string MdOut::addReferences(const char* refStart, const char* refEnd, continue; } } - const Definition* test = fRoot; - do { - if (!test->isRoot()) { - continue; - } - for (string prefix : { "_", "::" } ) { - const RootDefinition* root = test->asRoot(); - string prefixed = root->fName + prefix + ref; - if (const Definition* def = root->find(prefixed, - RootDefinition::AllowParens::kYes)) { - result += linkRef(leadingSpaces, def, ref, resolvable); - goto found; - } - } - } while ((test = test->fParent)); - found: - if (!test) { - if (BmhParser::Resolvable::kOut != resolvable && - BmhParser::Resolvable::kFormula != resolvable) { - t.reportError("undefined reference"); - fAddRefFailed = true; - } else { - add_ref(leadingSpaces, ref, &result); - } + Definition* def = this->checkParentsForMatch(fSubtopic, ref); + if (!def) { + def = this->checkParentsForMatch(fRoot, ref); + } + if (def) { + result += this->linkRef(leadingSpaces, def, ref, resolvable); + continue; + } + if (BmhParser::Resolvable::kOut != resolvable && + BmhParser::Resolvable::kFormula != resolvable) { + t.reportError("undefined reference"); + fAddRefFailed = true; + } else { + add_ref(leadingSpaces, ref, &result); } } while (!t.eof()); return result; } - - bool MdOut::buildReferences(const IncludeParser& includeParser, const char* docDir, const char* mdFileOrPath) { if (!sk_isdir(mdFileOrPath)) { @@ -362,12 +591,7 @@ bool MdOut::buildRefFromFile(const char* name, const char* outDir) { if (topicDef->fParent) { continue; } - if (!topicDef->isRoot()) { - fAddRefFailed = true; - return this->reportError<bool>("expected root topic"); - } - fRoot = topicDef->asRoot(); - if (string::npos == fRoot->fFileName.rfind(match)) { + if (string::npos == topicDef->fFileName.rfind(match)) { continue; } if (!fOut) { @@ -381,6 +605,9 @@ bool MdOut::buildRefFromFile(const char* name, const char* outDir) { SkDebugf("could not open output file %s\n", fullName.c_str()); return false; } + if (false) { // try inlining the style + FPRINTF("%s", kConstTableStyle); + } size_t underscorePos = header.find('_'); if (string::npos != underscorePos) { header.replace(underscorePos, 1, " "); @@ -390,19 +617,8 @@ bool MdOut::buildRefFromFile(const char* name, const char* outDir) { this->lfAlways(1); FPRINTF("==="); } - fPopulators.clear(); - fPopulators[kClassesAndStructs].fDescription = "embedded struct and class members"; - fPopulators[kConstants].fDescription = "enum and enum class, const values"; - fPopulators[kConstructors].fDescription = "functions that construct"; - fPopulators[kDefines].fDescription = "preprocessor definitions of functions, values"; - fPopulators[kMemberFunctions].fDescription = "static functions and member methods"; - fPopulators[kMembers].fDescription = "member values"; - fPopulators[kOperators].fDescription = "operator overloading methods"; - fPopulators[kRelatedFunctions].fDescription = "similar methods grouped together"; - fPopulators[kSubtopics].fDescription = ""; - fPopulators[kTypedefs].fDescription = "types defined by other types"; - this->populateTables(fRoot); - this->markTypeOut(topicDef); + const Definition* prior = nullptr; + this->markTypeOut(topicDef, &prior); } if (fOut) { this->writePending(); @@ -423,6 +639,91 @@ bool MdOut::buildRefFromFile(const char* name, const char* outDir) { return !fAddRefFailed; } +static bool contains_referenced_child(const Definition* found, const vector<string>& refs) { + for (auto child : found->fChildren) { + if (refs.end() != std::find_if(refs.begin(), refs.end(), + [child](string def) { return child->fName == def; } )) { + return true; + } + if (contains_referenced_child(child, refs)) { + return true; + } + } + return false; +} + +void MdOut::checkAnchors() { + int missing = 0; + for (auto bmhFile : fAllAnchorRefs) { + auto defIter = fAllAnchorDefs.find(bmhFile.first); + SkASSERT(fAllAnchorDefs.end() != defIter); + vector<AnchorDef>& allDefs = defIter->second; + std::sort(allDefs.begin(), allDefs.end(), + [](const AnchorDef& a, const AnchorDef& b) { return a.fDef < b.fDef; } ); + std::sort(bmhFile.second.begin(), bmhFile.second.end()); + auto allDefsIter = allDefs.begin(); + auto allRefsIter = bmhFile.second.begin(); + for (;;) { + bool allDefsEnded = allDefsIter == allDefs.end(); + bool allRefsEnded = allRefsIter == bmhFile.second.end(); + if (allDefsEnded && allRefsEnded) { + break; + } + if (allRefsEnded || (!allDefsEnded && allDefsIter->fDef < *allRefsIter)) { + if (MarkType::kParam != allDefsIter->fMarkType + && !IncompleteAllowed(allDefsIter->fMarkType)) { + // If undocumented but parent or child is referred to: good enough for now + bool goodEnough = false; + if ("undocumented" == defIter->first) { + auto iter = fBmhParser.fTopicMap.find(allDefsIter->fDef); + if (fBmhParser.fTopicMap.end() != iter) { + const Definition* found = iter->second; + if (string::npos != found->fFileName.find("undocumented")) { + const Definition* parent = found; + while ((parent = parent->fParent)) { + if (bmhFile.second.end() != std::find_if(bmhFile.second.begin(), + bmhFile.second.end(), + [parent](string def) { + return parent->fName == def; } )) { + goodEnough = true; + break; + } + } + if (!goodEnough) { + goodEnough = contains_referenced_child(found, bmhFile.second); + } + } + } + } + if (!goodEnough) { + SkDebugf("missing ref %s %s\n", defIter->first.c_str(), + allDefsIter->fDef.c_str()); + missing++; + } + } + allDefsIter++; + } else if (allDefsEnded || (!allRefsEnded && allDefsIter->fDef > *allRefsIter)) { + if (fBmhParser.fExternals.end() == std::find_if(fBmhParser.fExternals.begin(), + fBmhParser.fExternals.end(), [allRefsIter](const RootDefinition& root) { + return *allRefsIter != root.fName; } )) { + SkDebugf("missing def %s %s\n", bmhFile.first.c_str(), allRefsIter->c_str()); + missing++; + } + allRefsIter++; + } else { + SkASSERT(!allDefsEnded); + SkASSERT(!allRefsEnded); + SkASSERT(allDefsIter->fDef == *allRefsIter); + allDefsIter++; + allRefsIter++; + } + if (missing >= 10) { + missing = 0; + } + } + } +} + bool MdOut::checkParamReturnBody(const Definition* def) { TextParser paramBody(def); const char* descriptionStart = paramBody.fChar; @@ -445,21 +746,23 @@ bool MdOut::checkParamReturnBody(const Definition* def) { return true; } -void MdOut::childrenOut(const Definition* def, const char* start) { +void MdOut::childrenOut(Definition* def, const char* start) { const char* end; fLineCount = def->fLineCount; - if (def->isRoot()) { - fRoot = const_cast<RootDefinition*>(def->asRoot()); - } else if (MarkType::kEnumClass == def->fMarkType) { + if (MarkType::kEnumClass == def->fMarkType) { fEnumClass = def; } BmhParser::Resolvable resolvable = this->resolvable(def); + const Definition* prior = nullptr; for (auto& child : def->fChildren) { + if (MarkType::kPhraseParam == child->fMarkType) { + continue; + } end = child->fStart; if (BmhParser::Resolvable::kNo != resolvable) { this->resolveOut(start, end, resolvable); } - this->markTypeOut(child); + this->markTypeOut(child, &prior); start = child->fTerminator; } if (BmhParser::Resolvable::kNo != resolvable) { @@ -471,8 +774,59 @@ void MdOut::childrenOut(const Definition* def, const char* start) { } } -const Definition* MdOut::csParent() const { - const Definition* csParent = fRoot->csParent(); +// output header for subtopic for all consts: name, value, short descriptions (#Line) +// output link to in context #Const with moderate description +void MdOut::summaryOut(const Definition* def, MarkType markType, string name) { + this->writePending(); + SkASSERT(TableState::kNone == fTableState); + this->mdHeaderOut(3); + FPRINTF("%s", name.c_str()); + this->lfAlways(2); + FPRINTF("%s", kTableDeclaration); // <table> with style info + this->lfAlways(1); + FPRINTF("%s", MarkType::kConst == markType ? kAllConstTableHeader : kAllMemberTableHeader); + this->lfAlways(1); + bool odd = true; + for (auto child : def->fChildren) { + if (markType != child->fMarkType) { + continue; + } + auto oneLiner = std::find_if(child->fChildren.begin(), child->fChildren.end(), + [](const Definition* test){ return MarkType::kLine == test->fMarkType; } ); + if (child->fChildren.end() == oneLiner) { + child->reportError<void>("missing #Line"); + continue; + } + FPRINTF("%s", odd ? kTR_Dark.c_str() : " <tr>"); + this->lfAlways(1); + if (MarkType::kConst == markType) { + FPRINTF("%s", tableDataCodeRef(def).c_str()); + this->lfAlways(1); + FPRINTF("%s", table_data_const(def, nullptr).c_str()); + } else { + string memberType; + string memberName = this->getMemberTypeName(child, &memberType); + SkASSERT(MarkType::kMember == markType); + FPRINTF("%s", out_table_data_description(memberType).c_str()); + this->lfAlways(1); + FPRINTF("%s", tableDataCodeLocalRef(memberName).c_str()); + } + this->lfAlways(1); + FPRINTF("%s", out_table_data_description(*oneLiner).c_str()); + this->lfAlways(1); + FPRINTF("%s", " </tr>"); + this->lfAlways(1); + odd = !odd; + } + FPRINTF("</table>"); + this->lfAlways(1); +} + +Definition* MdOut::csParent() { + if (!fRoot) { + return nullptr; + } + Definition* csParent = fRoot->csParent(); if (!csParent) { const Definition* topic = fRoot; while (topic && MarkType::kTopic != topic->fMarkType) { @@ -515,6 +869,106 @@ const Definition* MdOut::findParamType() { return nullptr; } +string MdOut::getMemberTypeName(const Definition* def, string* memberType) { + TextParser parser(def->fFileName, def->fStart, def->fContentStart, + def->fLineCount); + parser.skipExact("#Member"); + parser.skipWhiteSpace(); + const char* typeStart = parser.fChar; + const char* typeEnd = nullptr; + const char* nameStart = nullptr; + const char* nameEnd = nullptr; + do { + parser.skipToWhiteSpace(); + if (nameStart) { + nameEnd = parser.fChar; + } + if (parser.eof()) { + break; + } + const char* spaceLoc = parser.fChar; + if (parser.skipWhiteSpace()) { + typeEnd = spaceLoc; + nameStart = parser.fChar; + } + } while (!parser.eof()); + SkASSERT(typeEnd); + *memberType = string(typeStart, (int) (typeEnd - typeStart)); + replace_all(*memberType, " ", " "); + SkASSERT(nameStart); + SkASSERT(nameEnd); + return string(nameStart, (int) (nameEnd - nameStart)); +} + +bool MdOut::HasDetails(const Definition* def) { + for (auto child : def->fChildren) { + if (MarkType::kDetails == child->fMarkType) { + return true; + } + if (MdOut::HasDetails(child)) { + return true; + } + } + return false; +} + +void MdOut::htmlOut(string s) { + SkASSERT(string::npos != s.find('<')); + FPRINTF("%s", s.c_str()); +} + +const Definition* MdOut::isDefinedByParent(RootDefinition* root, string ref) { + if (ref == root->fName) { + return root; + } + if (const Definition* definition = root->find(ref, RootDefinition::AllowParens::kYes)) { + return definition; + } + Definition* test = root; + bool isSubtopic = MarkType::kSubtopic == root->fMarkType + || MarkType::kTopic == root->fMarkType; + do { + if (!test->isRoot()) { + continue; + } + bool testIsSubtopic = MarkType::kSubtopic == test->fMarkType + || MarkType::kTopic == test->fMarkType; + if (isSubtopic != testIsSubtopic) { + continue; + } + RootDefinition* root = test->asRoot(); + for (auto& leaf : root->fBranches) { + if (ref == leaf.first) { + return leaf.second; + } + const Definition* definition = leaf.second->find(ref, + RootDefinition::AllowParens::kYes); + if (definition) { + return definition; + } + } + string prefix = isSubtopic ? "_" : "::"; + string prefixed = root->fName + prefix + ref; + if (Definition* definition = root->find(prefixed, RootDefinition::AllowParens::kYes)) { + return definition; + } + if (isSubtopic && isupper(prefixed[0])) { + auto topicIter = fBmhParser.fTopicMap.find(prefixed); + if (topicIter != fBmhParser.fTopicMap.end()) { + return topicIter->second; + } + } + if (isSubtopic) { + string fiddlePrefixed = root->fFiddle + "_" + ref; + auto topicIter = fBmhParser.fTopicMap.find(fiddlePrefixed); + if (topicIter != fBmhParser.fTopicMap.end()) { + return topicIter->second; + } + } + } while ((test = test->fParent)); + return nullptr; +} + const Definition* MdOut::isDefined(const TextParser& parser, string ref, BmhParser::Resolvable resolvable) { auto rootIter = fBmhParser.fClassMap.find(ref); @@ -550,55 +1004,18 @@ const Definition* MdOut::isDefined(const TextParser& parser, string ref, return &external; } } - if (fRoot) { - if (ref == fRoot->fName) { - return fRoot; - } - if (const Definition* definition = fRoot->find(ref, RootDefinition::AllowParens::kYes)) { - return definition; - } - const Definition* test = fRoot; - do { - if (!test->isRoot()) { - continue; - } - const RootDefinition* root = test->asRoot(); - for (auto& leaf : root->fBranches) { - if (ref == leaf.first) { - return leaf.second; - } - const Definition* definition = leaf.second->find(ref, - RootDefinition::AllowParens::kYes); - if (definition) { - return definition; - } - } - for (string prefix : { "::", "_" } ) { - string prefixed = root->fName + prefix + ref; - if (const Definition* definition = root->find(prefixed, - RootDefinition::AllowParens::kYes)) { - return definition; - } - if (isupper(prefixed[0])) { - auto topicIter = fBmhParser.fTopicMap.find(prefixed); - if (topicIter != fBmhParser.fTopicMap.end()) { - return topicIter->second; - } - } - } - string fiddlePrefixed = root->fFiddle + "_" + ref; - auto topicIter = fBmhParser.fTopicMap.find(fiddlePrefixed); - if (topicIter != fBmhParser.fTopicMap.end()) { - return topicIter->second; - } - } while ((test = test->fParent)); + if (const Definition* definition = this->isDefinedByParent(fRoot, ref)) { + return definition; + } + if (const Definition* definition = this->isDefinedByParent(fSubtopic, ref)) { + return definition; } size_t doubleColon = ref.find("::"); if (string::npos != doubleColon) { string className = ref.substr(0, doubleColon); auto classIter = fBmhParser.fClassMap.find(className); if (classIter != fBmhParser.fClassMap.end()) { - const RootDefinition& classDef = classIter->second; + RootDefinition& classDef = classIter->second; const Definition* result = classDef.find(ref, RootDefinition::AllowParens::kYes); if (result) { return result; @@ -611,7 +1028,7 @@ const Definition* MdOut::isDefined(const TextParser& parser, string ref, ref.length() > 1 && isupper(ref[1]))) { // try with a prefix if ('k' == ref[0]) { - for (auto const& iter : fBmhParser.fEnumMap) { + for (auto& iter : fBmhParser.fEnumMap) { auto def = iter.second.find(ref, RootDefinition::AllowParens::kYes); if (def) { return def; @@ -692,13 +1109,14 @@ string MdOut::linkName(const Definition* ref) const { result = namePart; } } + replace_all(result, "::", "_"); return result; } // for now, hard-code to html links // def should not include SkXXX_ string MdOut::linkRef(string leadingSpaces, const Definition* def, - string ref, BmhParser::Resolvable resolvable) const { + string ref, BmhParser::Resolvable resolvable) { string buildup; string refName; const string* str = &def->fFiddle; @@ -708,7 +1126,9 @@ string MdOut::linkRef(string leadingSpaces, const Definition* def, if (MarkType::kAlias == def->fMarkType) { def = def->fParent; SkASSERT(def); - SkASSERT(MarkType::kSubtopic == def->fMarkType ||MarkType::kTopic == def->fMarkType); + SkASSERT(MarkType::kSubtopic == def->fMarkType + || MarkType::kTopic == def->fMarkType + || MarkType::kConst == def->fMarkType); } if (MarkType::kSubtopic == def->fMarkType) { const Definition* topic = def->topicParent(); @@ -756,7 +1176,7 @@ string MdOut::linkRef(string leadingSpaces, const Definition* def, if (ref.length() > 2 && islower(ref[0]) && "()" == ref.substr(ref.length() - 2)) { refOut = refOut.substr(0, refOut.length() - 2); } - string result = leadingSpaces + "<a href=\"" + buildup + "\">" + refOut + "</a>"; + string result = leadingSpaces + this->anchorRef(buildup, refOut); if (BmhParser::Resolvable::kClone == resolvable && MarkType::kMethod == def->fMarkType && def->fCloned && !def->fClone) { bool found = false; @@ -771,7 +1191,8 @@ string MdOut::linkRef(string leadingSpaces, const Definition* def, string clone = match + num; const auto& leafIter = classIter->second.fLeaves.find(clone); if (leafIter != classIter->second.fLeaves.end()) { - result += "<sup><a href=\"" + buildup + "_" + num + "\">[" + num + "]</a></sup>"; + result += "<sup>" + this->anchorRef(buildup + "_" + num, + string("[") + num + "]") + "</sup>"; found = true; } } @@ -783,19 +1204,27 @@ string MdOut::linkRef(string leadingSpaces, const Definition* def, return result; } -void MdOut::markTypeOut(Definition* def) { +static bool writeTableEnd(MarkType markType, Definition* def, const Definition** prior) { + return markType != def->fMarkType && *prior && markType == (*prior)->fMarkType; +} + +void MdOut::markTypeOut(Definition* def, const Definition** prior) { string printable = def->printableName(); const char* textStart = def->fContentStart; - if (MarkType::kParam != def->fMarkType && MarkType::kConst != def->fMarkType && - (!def->fParent || MarkType::kConst != def->fParent->fMarkType) && - TableState::kNone != fTableState && - (MarkType::kPhraseRef != def->fMarkType || !def->fParent || - MarkType::kParam != def->fParent->fMarkType)) { + bool lookForOneLiner = false; + // #Param and #Const don't have markers to say when the last is seen, so detect that by looking + // for a change in type. + if (MarkType::kStruct == def->fMarkType) { + SkDebugf(""); + } + if (writeTableEnd(MarkType::kParam, def, prior) || writeTableEnd(MarkType::kConst, def, prior) + || writeTableEnd(MarkType::kMember, def, prior)) { this->writePending(); FPRINTF("</table>"); this->lf(2); fTableState = TableState::kNone; } + fLastDef = def; switch (def->fMarkType) { case MarkType::kAlias: break; @@ -806,20 +1235,52 @@ void MdOut::markTypeOut(Definition* def) { this->writePending(); TextParser parser(def); const char* start = parser.fChar; - parser.skipToEndBracket(" # "); + parser.skipToEndBracket((string(" ") + def->fMC + " ").c_str()); string anchorText(start, parser.fChar - start); - parser.skipExact(" # "); + parser.skipExact((string(" ") + def->fMC + " ").c_str()); string anchorLink(parser.fChar, parser.fEnd - parser.fChar); - FPRINTF("<a href=\"%s\">%s", anchorLink.c_str(), anchorText.c_str()); + this->htmlOut(anchorRef(anchorLink, anchorText)); } break; case MarkType::kBug: break; case MarkType::kClass: + case MarkType::kStruct: { + fRoot = def->asRoot(); this->mdHeaderOut(1); - FPRINTF("<a name=\"%s\"></a> Class %s", this->linkName(def).c_str(), - def->fName.c_str()); + if (MarkType::kStruct == def->fMarkType) { + this->htmlOut(anchorDef(def->fFiddle, "Struct " + def->fName)); + } else { + this->htmlOut(anchorDef(this->linkName(def), "Class " + def->fName)); + } this->lf(1); - break; + if (string::npos != fRoot->fFileName.find("undocumented")) { + break; + } + // if class or struct contains constants, and doesn't contain subtopic kConstant, add it + // and add a child populate + const Definition* subtopic = def->subtopicParent(); + const Definition* topic = def->topicParent(); + for (auto item : SubtopicKeys::kGeneratedSubtopics) { + string subname; + if (subtopic != topic) { + subname = subtopic->fName + '_'; + } + subname += item; + if (fRoot->populator(item).fMembers.size() + && !std::any_of(fRoot->fChildren.begin(), fRoot->fChildren.end(), + [subname](const Definition* child) { + return MarkType::kSubtopic == child->fMarkType + && subname == child->fName; + } )) { + // generate subtopic + this->mdHeaderOut(2); + this->htmlOut(anchorDef(subname, item)); + this->lf(2); + // generate populate + this->subtopicOut(item); + } + } + } break; case MarkType::kCode: this->lfAlways(2); FPRINTF("<pre style=\"padding: 1em 1em 1em 1em;" @@ -837,56 +1298,105 @@ void MdOut::markTypeOut(Definition* def) { break; case MarkType::kComment: break; + case MarkType::kMember: case MarkType::kConst: { + bool isConst = MarkType::kConst == def->fMarkType; + lookForOneLiner = false; + fWroteSomething = false; + // output consts for one parent with moderate descriptions + // optional link to subtopic with longer descriptions, examples if (TableState::kNone == fTableState) { + SkASSERT(!*prior || (isConst && MarkType::kConst != (*prior)->fMarkType) + || (!isConst && MarkType::kMember != (*prior)->fMarkType)); this->mdHeaderOut(3); - FPRINTF("Constants\n" - "\n" - "<table>"); + FPRINTF("%s", this->fPopulators[isConst ? SubtopicKeys::kConstants : + SubtopicKeys::kMembers].fName.c_str()); + this->lfAlways(2); + FPRINTF("%s", kTableDeclaration); fTableState = TableState::kRow; - this->lf(1); + fOddRow = true; + this->lfAlways(1); + // look ahead to see if the details column has data or not + fHasDetails = MdOut::HasDetails(def->fParent); + FPRINTF("%s", fHasDetails ? \ + (isConst ? kSubConstTableHeader : kSubMemberTableHeader) : \ + (isConst ? kAllConstTableHeader : kAllMemberTableHeader)); + this->lfAlways(1); } if (TableState::kRow == fTableState) { this->writePending(); - FPRINTF(" <tr>"); - this->lf(1); + FPRINTF("%s", fOddRow ? kTR_Dark.c_str() : " <tr>"); + fOddRow = !fOddRow; + this->lfAlways(1); fTableState = TableState::kColumn; } this->writePending(); - FPRINTF(" <td><a name=\"%s\"> <code><strong>%s </strong></code> </a></td>", - def->fFiddle.c_str(), def->fName.c_str()); - const char* lineEnd = strchr(textStart, '\n'); - SkASSERT(lineEnd < def->fTerminator); - SkASSERT(lineEnd > textStart); - SkASSERT((int) (lineEnd - textStart) == lineEnd - textStart); - FPRINTF("<td>%.*s</td>", (int) (lineEnd - textStart), textStart); - FPRINTF("<td>"); - textStart = lineEnd; + if (isConst) { + // TODO: if fHasDetails is true, could defer def and issue a ref instead + // unclear if this is a good idea or not + FPRINTF("%s", this->tableDataCodeDef(def).c_str()); + this->lfAlways(1); + FPRINTF("%s", table_data_const(def, &textStart).c_str()); + } else { + string memberType; + string memberName = this->getMemberTypeName(def, &memberType); + FPRINTF("%s", out_table_data_description(memberType).c_str()); + this->lfAlways(1); + FPRINTF("%s", tableDataCodeDef(def->fFiddle, memberName).c_str()); + } + this->lfAlways(1); + if (fHasDetails) { + string details; + auto subtopic = std::find_if(def->fChildren.begin(), def->fChildren.end(), + [](const Definition* test){ + return MarkType::kDetails == test->fMarkType; } ); + if (def->fChildren.end() != subtopic) { + string subtopicName = string((*subtopic)->fContentStart, + (int) ((*subtopic)->fContentEnd - (*subtopic)->fContentStart)); + const Definition* parentSubtopic = def->subtopicParent(); + SkASSERT(parentSubtopic); + string fullName = parentSubtopic->fFiddle + '_' + subtopicName; + if (fBmhParser.fTopicMap.end() == fBmhParser.fTopicMap.find(fullName)) { + (*subtopic)->reportError<void>("missing #Details subtopic"); + } + subtopicName = parentSubtopic->fName + '_' + subtopicName; + string noUnderscores = subtopicName; + replace_all(noUnderscores, "_", " "); + details = this->anchorLocalRef(subtopicName, noUnderscores) + " "; + } + FPRINTF("%s", out_table_data_details(details).c_str()); + this->lfAlways(1); + } + lookForOneLiner = true; // if description is empty, use oneLiner data + FPRINTF("%s", out_table_data_description_start().c_str()); // start of Description + this->lfAlways(1); } break; case MarkType::kDefine: break; case MarkType::kDefinedBy: break; case MarkType::kDeprecated: + this->writeString("Deprecated."); + this->lf(2); break; case MarkType::kDescription: fInDescription = true; this->writePending(); - FPRINTF("<div>"); + FPRINTF("%s", "<div>"); break; - case MarkType::kDoxygen: + case MarkType::kDetails: break; case MarkType::kDuration: break; case MarkType::kEnum: case MarkType::kEnumClass: this->mdHeaderOut(2); - FPRINTF("<a name=\"%s\"></a> Enum %s", def->fFiddle.c_str(), def->fName.c_str()); + this->htmlOut(anchorDef(def->fFiddle, "Enum " + def->fName)); this->lf(2); break; case MarkType::kExample: { this->mdHeaderOut(3); - FPRINTF("Example\n" + FPRINTF("%s", "Example\n" "\n"); fHasFiddle = true; bool showGpu = false; @@ -904,15 +1414,15 @@ void MdOut::markTypeOut(Definition* def) { SkASSERT(def->fHash.length() > 0); FPRINTF("<div><fiddle-embed name=\"%s\"", def->fHash.c_str()); if (showGpu) { - FPRINTF(" gpu=\"true\""); + FPRINTF("%s", " gpu=\"true\""); if (gpuAndCpu) { - FPRINTF(" cpu=\"true\""); + FPRINTF("%s", " cpu=\"true\""); } } - FPRINTF(">"); + FPRINTF("%s", ">"); } else { SkASSERT(def->fHash.length() == 0); - FPRINTF("<pre style=\"padding: 1em 1em 1em 1em; font-size: 13px" + FPRINTF("%s", "<pre style=\"padding: 1em 1em 1em 1em; font-size: 13px" " width: 62.5em; background-color: #f0f0f0\">"); this->lfAlways(1); if (def->fWrapper.length() > 0) { @@ -922,10 +1432,11 @@ void MdOut::markTypeOut(Definition* def) { } } break; case MarkType::kExperimental: + writeString("Experimental."); + this->lf(2); break; case MarkType::kExternal: - break; - case MarkType::kFile: + SkDebugf(""); break; case MarkType::kFormula: break; @@ -962,8 +1473,9 @@ void MdOut::markTypeOut(Definition* def) { break; case MarkType::kList: fInList = true; + fTableState = TableState::kRow; this->lfAlways(2); - FPRINTF("<table>"); + FPRINTF("%s", "<table>"); this->lf(1); break; case MarkType::kLiteral: @@ -971,22 +1483,11 @@ void MdOut::markTypeOut(Definition* def) { case MarkType::kMarkChar: fBmhParser.fMC = def->fContentStart[0]; break; - case MarkType::kMember: { - TextParser tp(def->fFileName, def->fStart, def->fContentStart, def->fLineCount); - tp.skipExact("#Member"); - tp.skipWhiteSpace(); - const char* end = tp.trimmedBracketEnd('\n'); - this->lfAlways(2); - FPRINTF("<a name=\"%s\"> <code><strong>%.*s</strong></code> </a>", - def->fFiddle.c_str(), (int) (end - tp.fChar), tp.fChar); - this->lf(2); - } break; case MarkType::kMethod: { string method_name = def->methodName(); string formattedStr = def->formatFunction(Definition::Format::kIncludeReturn); - this->lfAlways(2); - FPRINTF("<a name=\"%s\"></a>", def->fFiddle.c_str()); + this->htmlOut(anchorDef(def->fFiddle, "")); if (!def->isClone()) { this->mdHeaderOutLF(2, 1); FPRINTF("%s", method_name.c_str()); @@ -997,22 +1498,26 @@ void MdOut::markTypeOut(Definition* def) { // TODO: 50em below should match limit = 80 in formatFunction() this->writePending(); string preformattedStr = preformat(formattedStr); - FPRINTF("<pre style=\"padding: 1em 1em 1em 1em;" - "width: 62.5em; background-color: #f0f0f0\">\n" - "%s\n" - "</pre>", preformattedStr.c_str()); + string references = this->addReferences(&preformattedStr.front(), + &preformattedStr.back() + 1, BmhParser::Resolvable::kSimple); + preformattedStr = references; + this->htmlOut("<pre style=\"padding: 1em 1em 1em 1em; width: 62.5em;" + "background-color: #f0f0f0\">\n" + preformattedStr + "\n" + "</pre>"); this->lf(2); fTableState = TableState::kNone; fMethod = def; } break; case MarkType::kNoExample: break; + case MarkType::kNoJustify: + break; case MarkType::kOutdent: break; case MarkType::kParam: { if (TableState::kNone == fTableState) { + SkASSERT(!*prior || MarkType::kParam != (*prior)->fMarkType); this->mdHeaderOut(3); - fprintf(fOut, + this->htmlOut( "Parameters\n" "\n" "<table>" @@ -1036,22 +1541,84 @@ void MdOut::markTypeOut(Definition* def) { paramParser.skipToSpace(); string paramNameStr(paramName, (int) (paramParser.fChar - paramName)); if (!this->checkParamReturnBody(def)) { + *prior = def; return; } string refNameStr = def->fParent->fFiddle + "_" + paramNameStr; - fprintf(fOut, - " <td><a name=\"%s\"> <code><strong>%s </strong></code> </a></td> <td>", - refNameStr.c_str(), paramNameStr.c_str()); + this->htmlOut(" <td>" + anchorDef(refNameStr, + "<code><strong>" + paramNameStr + "</strong></code>") + "</td>"); + this->lfAlways(1); + FPRINTF(" <td>"); } break; + case MarkType::kPhraseDef: + // skip text and children + *prior = def; + return; + case MarkType::kPhraseParam: + SkDebugf(""); // convenient place to set a breakpoint + break; + case MarkType::kPhraseRef: + if (fPhraseParams.end() != fPhraseParams.find(def->fName)) { + if (fColumn > 0) { + this->writeSpace(); + } + this->writeString(fPhraseParams[def->fName]); + if (isspace(def->fContentStart[0])) { + this->writeSpace(); + } + } else if (fBmhParser.fPhraseMap.end() == fBmhParser.fPhraseMap.find(def->fName)) { + def->reportError<void>("missing phrase definition"); + fAddRefFailed = true; + } else { + if (fColumn) { + SkASSERT(' ' >= def->fStart[0]); + this->writeSpace(); + } + Definition* phraseRef = fBmhParser.fPhraseMap.find(def->fName)->second; + // def->fChildren are parameters to substitute phraseRef->fChildren, + // phraseRef->fChildren has both param defines and references + // def->fChildren must have the same number of entries as phaseRef->fChildren + // which are kPhraseParam, and substitute one for one + // Then, each kPhraseRef in phaseRef looks up the key and value + fPhraseParams.clear(); + auto refKidsIter = phraseRef->fChildren.begin(); + for (auto child : def->fChildren) { + if (MarkType::kPhraseParam != child->fMarkType) { + // more work to do to support other types + this->reportError("phrase ref child must be param"); + } + do { + if (refKidsIter == phraseRef->fChildren.end()) { + this->reportError("phrase def missing param"); + break; + } + if (MarkType::kPhraseRef == (*refKidsIter)->fMarkType) { + continue; + } + if (MarkType::kPhraseParam != (*refKidsIter)->fMarkType) { + this->reportError("unexpected type in phrase def children"); + break; + } + fPhraseParams[(*refKidsIter)->fName] = child->fName; + break; + } while (true); + } + this->childrenOut(phraseRef, phraseRef->fContentStart); + fPhraseParams.clear(); + if (' ' >= def->fContentStart[0] && !fPendingLF) { + this->writeSpace(); + } + } + break; case MarkType::kPlatform: break; case MarkType::kPopulate: { SkASSERT(MarkType::kSubtopic == def->fParent->fMarkType); string name = def->fParent->fName; - if (kSubtopics == name) { - this->subtopicsOut(); + if (string::npos != name.find(SubtopicKeys::kOverview)) { + this->subtopicsOut(def->fParent); } else { - this->subtopicOut(this->populator(name.c_str())); + this->subtopicOut(name); } } break; case MarkType::kPrivate: @@ -1060,6 +1627,7 @@ void MdOut::markTypeOut(Definition* def) { this->mdHeaderOut(3); FPRINTF("Return Value"); if (!this->checkParamReturnBody(def)) { + *prior = def; return; } this->lf(2); @@ -1080,7 +1648,7 @@ void MdOut::markTypeOut(Definition* def) { case MarkType::kStdOut: { TextParser code(def); this->mdHeaderOut(4); - fprintf(fOut, + FPRINTF( "Example Output\n" "\n" "~~~~"); @@ -1094,18 +1662,27 @@ void MdOut::markTypeOut(Definition* def) { FPRINTF("~~~~"); this->lf(2); } break; - case MarkType::kStruct: - fRoot = def->asRoot(); - this->mdHeaderOut(1); - FPRINTF("<a name=\"%s\"></a> Struct %s", def->fFiddle.c_str(), def->fName.c_str()); - this->lf(1); - break; case MarkType::kSubstitute: break; case MarkType::kSubtopic: + fSubtopic = def->asRoot(); this->mdHeaderOut(2); - FPRINTF("<a name=\"%s\"></a> %s", def->fName.c_str(), printable.c_str()); + if (SubtopicKeys::kOverview == def->fName) { + this->writeString(def->fName); + } else { + this->htmlOut(anchorDef(def->fName, printable)); + } this->lf(2); + // if a subtopic child is const, generate short table of const name, value, line desc + if (std::any_of(def->fChildren.begin(), def->fChildren.end(), + [](Definition* child){return MarkType::kConst == child->fMarkType;})) { + this->summaryOut(def, MarkType::kConst, fPopulators[SubtopicKeys::kConstants].fName); + } + // if a subtopic child is member, generate short table of const name, value, line desc + if (std::any_of(def->fChildren.begin(), def->fChildren.end(), + [](Definition* child){return MarkType::kMember == child->fMarkType;})) { + this->summaryOut(def, MarkType::kMember, fPopulators[SubtopicKeys::kMembers].fName); + } break; case MarkType::kTable: this->lf(2); @@ -1114,20 +1691,26 @@ void MdOut::markTypeOut(Definition* def) { break; case MarkType::kText: break; - case MarkType::kTime: - break; case MarkType::kToDo: break; - case MarkType::kTopic: + case MarkType::kTopic: { + auto found = std::find_if(def->fChildren.begin(), def->fChildren.end(), + [](Definition* test) { return test->isStructOrClass(); } ); + bool hasClassOrStruct = def->fChildren.end() != found; + fRoot = hasClassOrStruct ? (*found)->asRoot() : def->asRoot(); + fSubtopic = def->asRoot(); + bool isUndocumented = string::npos != def->fFileName.find("undocumented"); + if (!isUndocumented) { + this->populateTables(def, fRoot); + } this->mdHeaderOut(1); - FPRINTF("<a name=\"%s\"></a> %s", this->linkName(def).c_str(), - printable.c_str()); + this->htmlOut(anchorDef(this->linkName(def), printable)); this->lf(1); - break; - case MarkType::kTrack: - // don't output children - return; + } break; case MarkType::kTypedef: + this->mdHeaderOut(1); + this->htmlOut(anchorDef(def->fFiddle, "Typedef " + def->fName)); + this->lf(1); break; case MarkType::kUnion: break; @@ -1135,39 +1718,36 @@ void MdOut::markTypeOut(Definition* def) { break; case MarkType::kWidth: break; - case MarkType::kPhraseDef: - // skip text and children - return; - case MarkType::kPhraseRef: - if (fBmhParser.fPhraseMap.end() == fBmhParser.fPhraseMap.find(def->fName)) { - def->reportError<void>("missing phrase definition"); - fAddRefFailed = true; - } else { - if (fColumn && ' ' >= def->fStart[0]) { - this->writeSpace(); - } - Definition* phraseRef = fBmhParser.fPhraseMap.find(def->fName)->second; - this->childrenOut(phraseRef, phraseRef->fContentStart); - if (' ' >= def->fContentStart[0]) { - this->writeSpace(); - } - } - break; default: SkDebugf("fatal error: MarkType::k%s unhandled in %s()\n", BmhParser::kMarkProps[(int) def->fMarkType].fName, __func__); SkASSERT(0); // handle everything break; } - TableState saveState = fTableState; + // TableState saveState = fTableState; + if (def->fLineCount >= 533 && string::npos != def->fFileName.find("SkSurface")) { + SkDebugf(""); + } this->childrenOut(def, textStart); - fTableState = saveState; + // fTableState = saveState; switch (def->fMarkType) { // post child work, at least for tables case MarkType::kAnchor: if (fColumn > 0) { this->writeSpace(); } break; + case MarkType::kClass: + case MarkType::kStruct: + if (TableState::kNone != fTableState) { + this->writePending(); + FPRINTF("</table>"); + this->lf(2); + fTableState = TableState::kNone; + } + if (def->csParent()) { + fRoot = def->csParent()->asRoot(); + } + break; case MarkType::kCode: fIndent = 0; this->lf(1); @@ -1180,7 +1760,7 @@ void MdOut::markTypeOut(Definition* def) { if (fInList) { this->writePending(); FPRINTF("</td>"); - this->lf(1); + this->lfAlways(1); } else { FPRINTF(" "); } @@ -1192,7 +1772,12 @@ void MdOut::markTypeOut(Definition* def) { break; case MarkType::kEnum: case MarkType::kEnumClass: - this->lfAlways(2); + if (TableState::kNone != fTableState) { + this->writePending(); + FPRINTF("</table>"); + this->lf(2); + fTableState = TableState::kNone; + } break; case MarkType::kExample: this->writePending(); @@ -1216,8 +1801,10 @@ void MdOut::markTypeOut(Definition* def) { case MarkType::kList: fInList = false; this->writePending(); + SkASSERT(TableState::kNone != fTableState); FPRINTF("</table>"); this->lf(2); + fTableState = TableState::kNone; break; case MarkType::kLegend: { SkASSERT(def->fChildren.size() == 1); @@ -1239,13 +1826,26 @@ void MdOut::markTypeOut(Definition* def) { this->lf(2); break; case MarkType::kConst: + case MarkType::kMember: + if (lookForOneLiner && !fWroteSomething) { + auto oneLiner = std::find_if(def->fChildren.begin(), def->fChildren.end(), + [](const Definition* test){ return MarkType::kLine == test->fMarkType; } ); + if (def->fChildren.end() != oneLiner) { + TextParser parser(*oneLiner); + parser.skipWhiteSpace(); + parser.trimEnd(); + FPRINTF("%.*s", (int) (parser.fEnd - parser.fChar), parser.fChar); + } + lookForOneLiner = false; + } case MarkType::kParam: SkASSERT(TableState::kColumn == fTableState); fTableState = TableState::kRow; this->writePending(); - FPRINTF("</td>\n"); + FPRINTF("</td>"); + this->lfAlways(1); FPRINTF(" </tr>"); - this->lf(1); + this->lfAlways(1); break; case MarkType::kReturn: case MarkType::kSeeAlso: @@ -1259,9 +1859,6 @@ void MdOut::markTypeOut(Definition* def) { } this->lf(1); break; - case MarkType::kStruct: - fRoot = fRoot->rootParent(); - break; case MarkType::kTable: this->lf(2); break; @@ -1269,9 +1866,22 @@ void MdOut::markTypeOut(Definition* def) { break; case MarkType::kPrivate: break; + case MarkType::kSubtopic: + SkASSERT(def); + do { + def = def->fParent; + } while (def && MarkType::kTopic != def->fMarkType + && MarkType::kSubtopic != def->fMarkType); + SkASSERT(def); + fSubtopic = def->asRoot(); + break; + case MarkType::kTopic: + fSubtopic = nullptr; + break; default: break; } + *prior = def; } void MdOut::mdHeaderOutLF(int depth, int lf) { @@ -1282,76 +1892,103 @@ void MdOut::mdHeaderOutLF(int depth, int lf) { FPRINTF(" "); } -void MdOut::populateTables(const Definition* def) { - const Definition* csParent = this->csParent(); - if (!csParent) { +void MdOut::populateOne(Definition* def, + unordered_map<string, RootDefinition::SubtopicContents>& populator) { + if (MarkType::kConst == def->fMarkType) { + populator[SubtopicKeys::kConstants].fMembers.push_back(def); return; } + if (MarkType::kEnum == def->fMarkType || MarkType::kEnumClass == def->fMarkType) { + populator[SubtopicKeys::kConstants].fMembers.push_back(def); + return; + } + if (MarkType::kDefine == def->fMarkType) { + populator[SubtopicKeys::kDefines].fMembers.push_back(def); + return; + } + if (MarkType::kMember == def->fMarkType) { + populator[SubtopicKeys::kMembers].fMembers.push_back(def); + return; + } + if (MarkType::kTypedef == def->fMarkType) { + populator[SubtopicKeys::kTypedefs].fMembers.push_back(def); + return; + } + if (MarkType::kMethod != def->fMarkType) { + return; + } + if (def->fClone) { + return; + } + if (Definition::MethodType::kConstructor == def->fMethodType + || Definition::MethodType::kDestructor == def->fMethodType) { + populator[SubtopicKeys::kConstructors].fMembers.push_back(def); + return; + } + if (Definition::MethodType::kOperator == def->fMethodType) { + populator[SubtopicKeys::kOperators].fMembers.push_back(def); + return; + } + populator[SubtopicKeys::kMemberFunctions].fMembers.push_back(def); + const Definition* csParent = this->csParent(); + if (csParent) { + if (0 == def->fName.find(csParent->fName + "::Make") + || 0 == def->fName.find(csParent->fName + "::make")) { + populator[SubtopicKeys::kConstructors].fMembers.push_back(def); + return; + } + } + for (auto item : def->fChildren) { + if (MarkType::kIn == item->fMarkType) { + string name(item->fContentStart, item->fContentEnd - item->fContentStart); + populator[name].fMembers.push_back(def); + populator[name].fShowClones = true; + break; + } + } +} + +void MdOut::populateTables(const Definition* def, RootDefinition* root) { for (auto child : def->fChildren) { - if (MarkType::kTopic == child->fMarkType || MarkType::kSubtopic == child->fMarkType) { + if (MarkType::kSubtopic == child->fMarkType) { string name = child->fName; - bool builtInTopic = name == kClassesAndStructs || name == kConstants - || name == kConstructors || name == kDefines || name == kMemberFunctions - || name == kMembers || name == kOperators || name == kOverview - || name == kRelatedFunctions || name == kSubtopics || name == kTypedefs; - if (!builtInTopic && child->fName != kOverview) { - this->populator(kRelatedFunctions).fMembers.push_back(child); - } - this->populateTables(child); + bool builtInTopic = name == SubtopicKeys::kOverview; + for (auto item : SubtopicKeys::kGeneratedSubtopics) { + builtInTopic |= name == item; + } + if (!builtInTopic) { + string subname; + const Definition* subtopic = child->subtopicParent(); + if (subtopic) { + subname = subtopic->fName + '_'; + } + builtInTopic = name == subname + SubtopicKeys::kOverview; + for (auto item : SubtopicKeys::kGeneratedSubtopics) { + builtInTopic |= name == subname + item; + } + if (!builtInTopic) { + root->populator(SubtopicKeys::kRelatedFunctions).fMembers.push_back(child); + } else { + SkDebugf(""); + } + } + this->populateTables(child, root); continue; } if (child->isStructOrClass()) { if (fClassStack.size() > 0) { - this->populator(kClassesAndStructs).fMembers.push_back(child); + root->populator(MarkType::kStruct != child->fMarkType ? SubtopicKeys::kClasses : + SubtopicKeys::kStructs).fMembers.push_back(child); } fClassStack.push_back(child); - this->populateTables(child); + this->populateTables(child, child->asRoot()); fClassStack.pop_back(); continue; } if (MarkType::kEnum == child->fMarkType || MarkType::kEnumClass == child->fMarkType) { - this->populator(kConstants).fMembers.push_back(child); - continue; - } - if (MarkType::kDefine == child->fMarkType) { - this->populator(kDefines).fMembers.push_back(child); - } - if (MarkType::kMember == child->fMarkType) { - this->populator(kMembers).fMembers.push_back(child); - continue; - } - if (MarkType::kTypedef == child->fMarkType) { - this->populator(kTypedefs).fMembers.push_back(child); - } - if (MarkType::kMethod != child->fMarkType) { - continue; - } - if (child->fClone) { - continue; - } - if (Definition::MethodType::kConstructor == child->fMethodType - || Definition::MethodType::kDestructor == child->fMethodType) { - this->populator(kConstructors).fMembers.push_back(child); - continue; - } - if (Definition::MethodType::kOperator == child->fMethodType) { - this->populator(kOperators).fMembers.push_back(child); - continue; - } - this->populator(kMemberFunctions).fMembers.push_back(child); - if (csParent && (0 == child->fName.find(csParent->fName + "::Make") - || 0 == child->fName.find(csParent->fName + "::make"))) { - this->populator(kConstructors).fMembers.push_back(child); - continue; - } - for (auto item : child->fChildren) { - if (MarkType::kIn == item->fMarkType) { - string name(item->fContentStart, item->fContentEnd - item->fContentStart); - fPopulators[name].fMembers.push_back(child); - fPopulators[name].fShowClones = true; - break; - } + this->populateTables(child, root); } + this->populateOne(child, root->fPopulators); } } @@ -1416,6 +2053,7 @@ void MdOut::resolveOut(const char* start, const char* end, BmhParser::Resolvable } string str(contentStart, lineLength); this->writeString(str.c_str()); + fWroteSomething = !!lineLength; } if (paragraph.eof()) { break; @@ -1431,51 +2069,96 @@ void MdOut::resolveOut(const char* start, const char* end, BmhParser::Resolvable } } -void MdOut::rowOut(const char* name, string description) { +void MdOut::rowOut(const char* name, string description, bool literalName) { + FPRINTF("%s", fOddRow ? kTR_Dark.c_str() : " <tr>"); this->lfAlways(1); - FPRINTF("| "); - this->resolveOut(name, name + strlen(name), BmhParser::Resolvable::kYes); - FPRINTF(" | "); + FPRINTF("%s", kTD_Left.c_str()); + if (literalName) { + if (strlen(name)) { + this->writeString(name); + } + } else { + this->resolveOut(name, name + strlen(name), BmhParser::Resolvable::kYes); + } + FPRINTF("</td>"); + this->lfAlways(1); + FPRINTF("%s", kTD_Left.c_str()); this->resolveOut(&description.front(), &description.back() + 1, BmhParser::Resolvable::kYes); - FPRINTF(" |"); - this->lf(1); + FPRINTF("</td>"); + this->lfAlways(1); + FPRINTF(" </tr>"); + this->lfAlways(1); + fOddRow = !fOddRow; } -void MdOut::subtopicsOut() { - const Definition* csParent = this->csParent(); - SkASSERT(csParent); - this->rowOut("name", "description"); - this->rowOut("---", "---"); - for (auto item : { kClassesAndStructs, kConstants, kConstructors, kDefines, - kMemberFunctions, kMembers, kOperators, kRelatedFunctions, kTypedefs } ) { - for (auto entry : this->populator(item).fMembers) { - if (entry->csParent() == csParent) { - string description = fPopulators.find(item)->second.fDescription; - if (kConstructors == item) { - description += " " + csParent->fName; +void MdOut::subtopicsOut(Definition* def) { + Definition* csParent = def->csParent(); + const Definition* subtopicParent = def->subtopicParent(); + const Definition* topicParent = def->topicParent(); + SkASSERT(subtopicParent); + this->lfAlways(1); + FPRINTF("%s", kTableDeclaration); + this->lfAlways(1); + FPRINTF("%s", kTopicsTableHeader); + this->lfAlways(1); + fOddRow = true; + for (auto item : SubtopicKeys::kGeneratedSubtopics) { + for (auto entry : fRoot->populator(item).fMembers) { + if ((csParent && entry->csParent() == csParent) + || entry->subtopicParent() == subtopicParent) { + auto popItem = fPopulators.find(item); + string description = popItem->second.fOneLiner; + if (SubtopicKeys::kConstructors == item) { + description += " " + fRoot->fName; + } + string subtopic; + if (subtopicParent != topicParent) { + subtopic = subtopicParent->fName + '_'; } - this->rowOut(item, description); + string link = this->anchorLocalRef(subtopic + item, popItem->second.fName); + this->rowOut(link.c_str(), description, true); break; } } } + FPRINTF("</table>"); + this->lfAlways(1); } -void MdOut::subtopicOut(const TableContents& tableContents) { - const auto& data = tableContents.fMembers; - const Definition* csParent = this->csParent(); +void MdOut::subtopicOut(string name) { + Definition* csParent = this->csParent(); SkASSERT(csParent); - fRoot = csParent->asRoot(); - this->rowOut("name", "description"); - this->rowOut("---", "---"); + const Definition* topicParent = fSubtopic ? fSubtopic->topicParent() : nullptr; + this->lfAlways(1); + if (fPopulators.end() != fPopulators.find(name)) { + const SubtopicDescriptions& tableDescriptions = this->populator(name); + this->anchorDef(name, tableDescriptions.fName); + this->lfAlways(1); + if (tableDescriptions.fDetails.length()) { + string details = csParent->fName; + details += " " + tableDescriptions.fDetails; + this->writeString(details); + this->lfAlways(1); + } + } else { + this->anchorDef(name, name); + this->lfAlways(1); + } + FPRINTF("%s", kTableDeclaration); + this->lfAlways(1); + FPRINTF("%s", kTopicsTableHeader); + this->lfAlways(1); + fOddRow = true; std::map<string, const Definition*> items; + const RootDefinition::SubtopicContents& tableContents = fRoot->populator(name.c_str()); + auto& data = tableContents.fMembers; for (auto entry : data) { - if (entry->csParent() != csParent) { + if (entry->csParent() != csParent && entry->topicParent() != topicParent) { continue; } size_t start = entry->fName.find_last_of("::"); - string name = entry->fName.substr(string::npos == start ? 0 : start + 1); - items[name] = entry; + string entryName = entry->fName.substr(string::npos == start ? 0 : start + 1); + items[entryName] = entry; } for (auto entry : items) { if (entry.second->fDeprecated) { @@ -1494,8 +2177,16 @@ void MdOut::subtopicOut(const TableContents& tableContents) { parser.reportError("missing #Line"); continue; } - this->rowOut(entry.first.c_str(), string(oneLiner->fContentStart, - oneLiner->fContentEnd - oneLiner->fContentStart)); + string keyName = entry.first; + TextParser dummy(entry.second); // for reporting errors, which we won't do + if (!this->isDefined(dummy, keyName, BmhParser::Resolvable::kOut)) { + keyName = entry.second->fName; + size_t doubleColon = keyName.find("::"); + SkASSERT(string::npos != doubleColon); + keyName = keyName.substr(doubleColon + 2); + } + this->rowOut(keyName.c_str(), string(oneLiner->fContentStart, + oneLiner->fContentEnd - oneLiner->fContentStart), false); if (tableContents.fShowClones && entry.second->fCloned) { int cloneNo = 2; string builder = entry.second->fName; @@ -1504,15 +2195,18 @@ void MdOut::subtopicOut(const TableContents& tableContents) { } builder += '_'; this->rowOut("", - preformat(entry.second->formatFunction(Definition::Format::kOmitReturn))); + preformat(entry.second->formatFunction(Definition::Format::kOmitReturn)), true); do { string match = builder + to_string(cloneNo); auto child = csParent->findClone(match); if (!child) { break; } - this->rowOut("", preformat(child->formatFunction(Definition::Format::kOmitReturn))); + this->rowOut("", + preformat(child->formatFunction(Definition::Format::kOmitReturn)), true); } while (++cloneNo); } } + FPRINTF("</table>"); + this->lfAlways(1); } |