diff options
author | Cary Clark <caryclark@skia.org> | 2018-05-16 07:07:07 -0400 |
---|---|---|
committer | Skia Commit-Bot <skia-commit-bot@chromium.org> | 2018-05-16 11:35:24 +0000 |
commit | 682c58da482155213e8cd2834b57bc6541e510a0 (patch) | |
tree | 63d666ea9ec0c2e765557e61e9ec5fc78ad9747f /tools/bookmaker | |
parent | 4c2a34e4804e5affa8447b590578a359bad2caf8 (diff) |
Documentation refresh
- add links to types within methods
- add check to see that all references and definitions match
- add style to tables to make them easier to read
- use https everywhere
- remove trailing spaces
- move overview inside class
- split class and struct in summary tables
- add missing #Line
- clean up SkImageInfo constant documentation
- work on SkColor documentation
- allow common phrases to take different parameters
- add more flexibility to generated tables
- tighten token parent requirements
- generalize deprecated and example interfaces
- detect inner constructors
R=caryclark@google.com
Docs-Preview: https://skia.org/?cl=121799
Bug: skia:6898
Change-Id: Ia75a23740b80259460916890b310e2a9f024962a
Reviewed-on: https://skia-review.googlesource.com/121799
Commit-Queue: Cary Clark <caryclark@skia.org>
Auto-Submit: Cary Clark <caryclark@skia.org>
Reviewed-by: Cary Clark <caryclark@skia.org>
Diffstat (limited to 'tools/bookmaker')
-rw-r--r-- | tools/bookmaker/bookmaker.cpp | 270 | ||||
-rw-r--r-- | tools/bookmaker/bookmaker.h | 274 | ||||
-rw-r--r-- | tools/bookmaker/definition.cpp | 48 | ||||
-rw-r--r-- | tools/bookmaker/includeWriter.cpp | 586 | ||||
-rw-r--r-- | tools/bookmaker/mdOut.cpp | 1286 | ||||
-rw-r--r-- | tools/bookmaker/parserCommon.cpp | 1 | ||||
-rw-r--r-- | tools/bookmaker/spellCheck.cpp | 48 |
7 files changed, 1808 insertions, 705 deletions
diff --git a/tools/bookmaker/bookmaker.cpp b/tools/bookmaker/bookmaker.cpp index 49e347e6ea..aaf3781974 100644 --- a/tools/bookmaker/bookmaker.cpp +++ b/tools/bookmaker/bookmaker.cpp @@ -29,18 +29,14 @@ DEFINE_string2(spellcheck, s, "", "Spell-check [once, all, mispelling]. (Require DEFINE_bool2(tokens, t, false, "Write bmh from include. (Requires -b -i)"); DEFINE_bool2(crosscheck, x, false, "Check bmh against includes. (Requires -b -i)"); // v is reserved for verbose +DEFINE_bool2(validate, V, false, "Validate that all anchor references have definitions. (Requires -r)"); DEFINE_bool2(skip, z, false, "Skip degenerate missed in legacy preprocessor."); -/* recipe for generating timestamps for existing doxygen comments -find include/core -type f -name '*.h' -print -exec git blame {} \; > ~/all.blame.txt +/* todos: + +add new markup to associate enum SaveLayerFlagsSet with typedef SaveLayerFlags, if needed. -todos: -add new markup to associate typedef SaveLayerFlags with Enum so that, for - documentation purposes, this enum is named rather than anonymous -check column 1 of subtopic tables to see that they start lowercase and don't have a trailing period -space table better for Constants should Return be on same line as 'Return Value'? -remove anonymous header, e.g. Enum SkPaint::::anonymous_2 #Member lost all formatting #List needs '# content ##', formatting consts like enum members need fully qualfied refs to make a valid link @@ -53,6 +49,45 @@ deprecated methods should be sorted down in md out, and show include "Deprecated rewrap text to fit in some number of columns #Literal is inflexible, making the entire #Code block link-less (see $Literal in SkImageInfo) would rather keep links for boby above #Literal, and/or make it a block and not a one-liner +add check to require #Const to contain #Code block if defining const or constexpr (enum consts have + #Code blocks inside the #Enum def +add spelling rule to look for x-bit but allow x bits + +There are a number of formatting bugs with ad hoc patches where a substitution doesn't keep +the space before or after, or the linefeeds before or after. The rules are not very good either. +Linefeeds in the bmh file are intended to be respected, but #Formula tends to start on a new line +even if the contents is intended to be inlined. Probably need to require it to be, e.g.: + + array length must be #Formula # (fXCount + 1) * (fYCount + 1) ##. + +where there is always a space between prior words and formula (i.e., between "be" and "(fXCount"; +and, an absense of a space after ## denotes no space between "+ 1)" and ".". These rules preserve +that # commands are always preceded by a whitespace character. Similarly, #PhraseDef/Ref +need to be inline or create new paragraphs. #phrase_ref# is sufficiently flexible that it can be +treated as a word without trailing whitespace, adapting the whitespace of its context. It also must +always have leading whitespace. + +It's awkward that phrase param is a child of the phrase def. Since phrase refs may also be children, +there is special case code to skip phrase def when looking for additional substitutions in the +phrase def. Could put it in the token list instead I guess, or make a definition subclass used +by phrase def with an additional slot... + + + +#Deprecated soon +## +should emit the text "To be deprecated soon." (right now you get just "soon") + +SkCanvas_ColorBehavior_kLegacy missing </table> in md out + +rearrange const out for md so that const / value / short description comes first in a table, +followed by more elaborate descriptions, examples, seealso. In md.cpp, look to see if #Subtopic +has #Const children. If so, generate a summary table first. +Or, only allow #Line and moderate text description in #Const. Put more verbose text, example, +seealso, in subsequent #SubTopic. Alpha_Type does this and it looks good. + +more spelling: x-value y-value + see head of selfCheck.cpp for additional todos */ @@ -67,6 +102,8 @@ see head of selfCheck.cpp for additional todos #define M(mt) (1LL << (int) MarkType::k##mt) #define M_D M(Description) #define M_CS M(Class) | M(Struct) +#define M_MD M(Method) | M(Define) +#define M_MDCM M_MD | M(Const) | M(Member) #define M_ST M(Subtopic) | M(Topic) #define M_CSST M_CS | M_ST #ifdef M_E @@ -84,35 +121,35 @@ see head of selfCheck.cpp for additional todos #define E_N Exemplary::kNo #define E_O Exemplary::kOptional +// ToDo: add column to denote which marks are one-liners BmhParser::MarkProps BmhParser::kMarkProps[] = { // names without formal definitions (e.g. Column) aren't included -// fill in other names once they're actually used { "", MarkType::kNone, R_Y, E_N, 0 } , { "A", MarkType::kAnchor, R_N, E_N, 0 } -, { "Alias", MarkType::kAlias, R_N, E_N, 0 } -, { "Bug", MarkType::kBug, R_N, E_N, 0 } -, { "Class", MarkType::kClass, R_Y, E_O, M_CSST | M(Root) } -, { "Code", MarkType::kCode, R_F, E_N, M_CSST | M_E | M(Method) | M(Define) | M(Typedef) } +, { "Alias", MarkType::kAlias, R_N, E_N, M_ST | M(Const) } +, { "Bug", MarkType::kBug, R_N, E_N, M_CSST | M_MDCM | M_E + | M(Example) | M(NoExample) } +, { "Class", MarkType::kClass, R_Y, E_O, M_CSST } +, { "Code", MarkType::kCode, R_F, E_N, M_CSST | M_E | M_MD | M(Typedef) } , { "", MarkType::kColumn, R_Y, E_N, M(Row) } , { "", MarkType::kComment, R_N, E_N, 0 } , { "Const", MarkType::kConst, R_Y, E_O, M_E | M_ST } , { "Define", MarkType::kDefine, R_O, E_Y, M_ST } , { "DefinedBy", MarkType::kDefinedBy, R_N, E_N, M(Method) } -, { "Deprecated", MarkType::kDeprecated, R_Y, E_N, 0 } +, { "Deprecated", MarkType::kDeprecated, R_Y, E_N, M_CS | M_MDCM | M_E } , { "Description", MarkType::kDescription, R_Y, E_N, M(Example) | M(NoExample) } -, { "Doxygen", MarkType::kDoxygen, R_Y, E_N, 0 } +, { "Details", MarkType::kDetails, R_N, E_N, M(Const) } , { "Duration", MarkType::kDuration, R_N, E_N, M(Example) | M(NoExample) } -, { "Enum", MarkType::kEnum, R_Y, E_O, M_CSST | M(Root) } -, { "EnumClass", MarkType::kEnumClass, R_Y, E_O, M_CSST | M(Root) } -, { "Example", MarkType::kExample, R_O, E_N, M_CSST | M_E | M(Method) | M(Const) | M(Define) } -, { "Experimental", MarkType::kExperimental, R_Y, E_N, 0 } -, { "External", MarkType::kExternal, R_Y, E_N, M(Root) } -, { "File", MarkType::kFile, R_N, E_N, M(Track) } -, { "Formula", MarkType::kFormula, R_F, E_N, - M(Column) | M_E | M_ST | M(Member) | M(Method) | M_D } +, { "Enum", MarkType::kEnum, R_Y, E_O, M_CSST } +, { "EnumClass", MarkType::kEnumClass, R_Y, E_O, M_CSST } +, { "Example", MarkType::kExample, R_O, E_N, M_CSST | M_E | M_MD } +, { "Experimental", MarkType::kExperimental, R_Y, E_N, M_CS | M_MDCM | M_E } +, { "External", MarkType::kExternal, R_Y, E_N, 0 } +, { "Formula", MarkType::kFormula, R_F, E_N, M(Column) | M(Description) + | M_E | M_ST | M_MDCM } , { "Function", MarkType::kFunction, R_O, E_N, M(Example) | M(NoExample) } , { "Height", MarkType::kHeight, R_N, E_N, M(Example) | M(NoExample) } -, { "Illustration", MarkType::kIllustration, R_N, E_N, M(Subtopic) } +, { "Illustration", MarkType::kIllustration, R_N, E_N, M_CSST | M_MD } , { "Image", MarkType::kImage, R_N, E_N, M(Example) | M(NoExample) } , { "In", MarkType::kIn, R_N, E_N, M_CSST | M_E | M(Method) | M(Typedef) } , { "Legend", MarkType::kLegend, R_Y, E_N, M(Table) } @@ -123,32 +160,31 @@ BmhParser::MarkProps BmhParser::kMarkProps[] = { , { "", MarkType::kMarkChar, R_N, E_N, 0 } , { "Member", MarkType::kMember, R_Y, E_N, M_CSST } , { "Method", MarkType::kMethod, R_Y, E_Y, M_CSST } -, { "NoExample", MarkType::kNoExample, R_N, E_N, M_CSST | M_E | M(Method) | M(Const) | M(Define) } +, { "NoExample", MarkType::kNoExample, R_N, E_N, M_CSST | M_E | M_MD } +, { "NoJustify", MarkType::kNoJustify, R_N, E_N, M(Const) | M(Member) } , { "Outdent", MarkType::kOutdent, R_N, E_N, M(Code) } , { "Param", MarkType::kParam, R_Y, E_N, M(Method) | M(Define) } , { "PhraseDef", MarkType::kPhraseDef, R_Y, E_N, M(Subtopic) } -, { "", MarkType::kPhraseRef, R_Y, E_N, 0 } +, { "", MarkType::kPhraseParam, R_Y, E_N, 0 } +, { "", MarkType::kPhraseRef, R_N, E_N, 0 } , { "Platform", MarkType::kPlatform, R_N, E_N, M(Example) | M(NoExample) } , { "Populate", MarkType::kPopulate, R_N, E_N, M(Subtopic) } -, { "Private", MarkType::kPrivate, R_N, E_N, 0 } +, { "Private", MarkType::kPrivate, R_Y, E_N, M_CSST | M_MDCM | M_E } , { "Return", MarkType::kReturn, R_Y, E_N, M(Method) } -, { "", MarkType::kRoot, R_Y, E_N, 0 } , { "", MarkType::kRow, R_Y, E_N, M(Table) | M(List) } -, { "SeeAlso", MarkType::kSeeAlso, R_C, E_N, M_CSST | M_E | M(Method) | M(Define) | M(Typedef) } +, { "SeeAlso", MarkType::kSeeAlso, R_C, E_N, M_CSST | M_E | M_MD | M(Typedef) } , { "Set", MarkType::kSet, R_N, E_N, M(Example) | M(NoExample) } , { "StdOut", MarkType::kStdOut, R_N, E_N, M(Example) | M(NoExample) } -, { "Struct", MarkType::kStruct, R_Y, E_O, M(Class) | M(Root) | M_ST } +, { "Struct", MarkType::kStruct, R_Y, E_O, M(Class) | M_ST } , { "Substitute", MarkType::kSubstitute, R_N, E_N, M_ST } , { "Subtopic", MarkType::kSubtopic, R_Y, E_Y, M_CSST } , { "Table", MarkType::kTable, R_Y, E_N, M(Method) | M_CSST | M_E } -, { "Template", MarkType::kTemplate, R_Y, E_N, 0 } +, { "Template", MarkType::kTemplate, R_Y, E_N, M_CSST } , { "", MarkType::kText, R_N, E_N, 0 } -, { "Time", MarkType::kTime, R_Y, E_N, M(Track) } , { "ToDo", MarkType::kToDo, R_N, E_N, 0 } -, { "Topic", MarkType::kTopic, R_Y, E_Y, M_CS | M(Root) | M(Topic) } -, { "Track", MarkType::kTrack, R_Y, E_N, M_E | M_ST } -, { "Typedef", MarkType::kTypedef, R_Y, E_N, M(Class) | M_ST } -, { "", MarkType::kUnion, R_Y, E_N, 0 } +, { "Topic", MarkType::kTopic, R_Y, E_Y, 0 } +, { "Typedef", MarkType::kTypedef, R_Y, E_N, M_CSST | M_E } +, { "Union", MarkType::kUnion, R_Y, E_N, M_CSST } , { "Volatile", MarkType::kVolatile, R_N, E_N, M(StdOut) } , { "Width", MarkType::kWidth, R_N, E_N, M(Example) | M(NoExample) } }; @@ -163,6 +199,7 @@ BmhParser::MarkProps BmhParser::kMarkProps[] = { #undef M_CSST #undef M_ST #undef M_CS +#undef M_MCD #undef M_D #undef M @@ -438,18 +475,46 @@ bool BmhParser::addDefinition(const char* defStart, bool hasEnd, MarkType markTy fParent->fChildren.push_back(definition); } break; + } else if (MarkType::kPhraseDef == markType) { + bool hasParams = '(' == this->next(); + fMarkup.emplace_front(markType, defStart, fLineCount, fParent, fMC); + definition = &fMarkup.front(); + definition->fName = typeNameBuilder[0]; + definition->fFiddle = fParent->fFiddle; + definition->fContentStart = fChar; + if (hasParams) { + char lastChar; + do { + const char* subEnd = this->anyOf(",)\n"); + if (!subEnd || '\n' == *subEnd) { + return this->reportError<bool>("unexpected phrase list end"); + } + fMarkup.emplace_front(MarkType::kPhraseParam, fChar, fLineCount, fParent, + fMC); + Definition* phraseParam = &fMarkup.front(); + phraseParam->fContentStart = fChar; + phraseParam->fContentEnd = subEnd; + phraseParam->fName = string(fChar, subEnd - fChar); + definition->fChildren.push_back(phraseParam); + this->skipTo(subEnd); + lastChar = this->next(); + phraseParam->fTerminator = fChar; + } while (')' != lastChar); + this->skipWhiteSpace(); + definition->fContentStart = fChar; + } + this->setAsParent(definition); + break; } // not one-liners case MarkType::kCode: case MarkType::kExample: - case MarkType::kExperimental: case MarkType::kFormula: case MarkType::kFunction: case MarkType::kLegend: case MarkType::kList: case MarkType::kPrivate: case MarkType::kTable: - case MarkType::kTrack: if (hasEnd) { definition = fParent; if (markType != fParent->fMarkType) { @@ -502,21 +567,22 @@ bool BmhParser::addDefinition(const char* defStart, bool hasEnd, MarkType markTy case MarkType::kAnchor: case MarkType::kBug: case MarkType::kDeprecated: + case MarkType::kDetails: case MarkType::kDuration: - case MarkType::kFile: + case MarkType::kExperimental: case MarkType::kHeight: case MarkType::kIllustration: case MarkType::kImage: case MarkType::kIn: case MarkType::kLine: case MarkType::kLiteral: + case MarkType::kNoJustify: case MarkType::kOutdent: case MarkType::kPlatform: case MarkType::kPopulate: case MarkType::kSeeAlso: case MarkType::kSet: case MarkType::kSubstitute: - case MarkType::kTime: case MarkType::kVolatile: case MarkType::kWidth: // todo : add check disallowing children? @@ -569,16 +635,37 @@ bool BmhParser::addDefinition(const char* defStart, bool hasEnd, MarkType markTy } fMarkup.emplace_front(MarkType::kText, start, fLineCount, definition, fMC); Definition* text = &fMarkup.front(); + if (!islower(start[0]) && (!isdigit(start[0]) + || MarkType::kConst != definition->fParent->fMarkType)) { + return this->reportError<bool>("expect lower case start"); + } + string contents = string(start, end - start); + if (string::npos != contents.find('.')) { + return this->reportError<bool>("expect phrase, not sentence"); + } + size_t firstSpace = contents.find(' '); + if (string::npos == firstSpace || 0 == firstSpace || 's' != start[firstSpace - 1]) { + if (MarkType::kMethod == fParent->fMarkType && "experimental" != contents + && "incomplete" != contents) { + return this->reportError<bool>( "expect phrase in third person present" + " tense (1st word should end in 's'"); + } + } text->fContentStart = start; text->fContentEnd = end; text->fTerminator = fChar; definition->fContentEnd = text->fContentEnd; definition->fTerminator = fChar; definition->fChildren.emplace_back(text); - } else if (MarkType::kDeprecated == markType) { + } else if (IncompleteAllowed(markType)) { this->skipSpace(); fParent->fDeprecated = true; - fParent->fToBeDeprecated = this->skipExact("soon"); + fParent->fDetails = + this->skipExact("soon") ? Definition::Details::kSoonToBe_Deprecated : + this->skipExact("testing") ? Definition::Details::kTestingOnly_Experiment : + this->skipExact("do not use") ? Definition::Details::kDoNotUse_Experiement : + this->skipExact("not ready") ? Definition::Details::kNotReady_Experiment : + Definition::Details::kNone; this->skipSpace(); if ('\n' != this->peek()) { return this->reportError<bool>("unexpected text after #Deprecated"); @@ -1210,8 +1297,8 @@ bool BmhParser::findDefinitions() { const char* defStart = fChar - 1; MarkType markType = this->getMarkType(MarkLookup::kRequire); bool hasEnd = this->hasEndToken(); - if (!hasEnd) { - MarkType parentType = fParent ? fParent->fMarkType : MarkType::kRoot; + if (!hasEnd && fParent) { + MarkType parentType = fParent->fMarkType; uint64_t parentMask = kMarkProps[(int) markType].fParentMask; if (parentMask && !(parentMask & (1LL << (int) parentType))) { return this->reportError<bool>("invalid parent"); @@ -1298,8 +1385,24 @@ bool BmhParser::findDefinitions() { const char* phraseNameStart = fChar; this->skipPhraseName(); string phraseKey = string(phraseNameStart, fChar - phraseNameStart); - if (fMC != this->next()) { - return this->reportError<bool>("expect # after phrase-name"); + char delimiter = this->next(); + vector<string> params; + vector<const char*> paramsLoc; + if (fMC != delimiter) { + if ('(' != delimiter) { + return this->reportError<bool>("expect # after phrase name"); + } + // phrase may take comma delimited parameter list + do { + const char* subEnd = this->anyOf(",)\n"); + if (!subEnd || '\n' == *subEnd) { + return this->reportError<bool>("unexpected phrase list end"); + } + params.push_back(string(fChar, subEnd - fChar)); + paramsLoc.push_back(fChar); + this->skipTo(subEnd); + + } while (')' != this->next()); } const char* start = phraseNameStart; SkASSERT('#' == start[-1]); @@ -1309,12 +1412,24 @@ bool BmhParser::findDefinitions() { } fMarkup.emplace_front(MarkType::kPhraseRef, start, fLineCount, fParent, fMC); Definition* markChar = &fMarkup.front(); + this->skipExact("#"); markChar->fContentStart = fChar; - this->skipToEndBracket('\n'); markChar->fContentEnd = fChar; markChar->fTerminator = fChar; markChar->fName = phraseKey; fParent->fChildren.push_back(markChar); + int paramLocIndex = 0; + for (auto param : params) { + const char* paramLoc = paramsLoc[paramLocIndex++]; + fMarkup.emplace_front(MarkType::kPhraseParam, paramLoc, fLineCount, fParent, + fMC); + Definition* phraseParam = &fMarkup.front(); + phraseParam->fContentStart = paramLoc; + phraseParam->fContentEnd = paramLoc + param.length(); + phraseParam->fTerminator = paramLoc + param.length(); + phraseParam->fName = param; + markChar->fChildren.push_back(phraseParam); + } } } char nextChar = this->next(); @@ -1384,13 +1499,14 @@ bool HackParser::hackFiles() { SkASSERT(!root->fParent); fStart = root->fStart; fChar = fStart; - fClassesAndStructs = nullptr; + fClasses = nullptr; fConstants = nullptr; fConstructors = nullptr; fMemberFunctions = nullptr; fMembers = nullptr; fOperators = nullptr; fRelatedFunctions = nullptr; + fStructs = nullptr; this->topicIter(root); fprintf(fOut, "%.*s", (int) (fEnd - fChar), fChar); fclose(fOut); @@ -1442,31 +1558,35 @@ string HackParser::searchTable(const Definition* tableHolder, const Definition* // returns true if topic has method void HackParser::topicIter(const Definition* topic) { - if (string::npos != topic->fName.find(MdOut::kClassesAndStructs)) { - SkASSERT(!fClassesAndStructs); - fClassesAndStructs = topic; + if (string::npos != topic->fName.find(SubtopicKeys::kClasses)) { + SkASSERT(!fClasses); + fClasses = topic; + } + if (string::npos != topic->fName.find(SubtopicKeys::kStructs)) { + SkASSERT(!fStructs); + fStructs = topic; } - if (string::npos != topic->fName.find(MdOut::kConstants)) { + if (string::npos != topic->fName.find(SubtopicKeys::kConstants)) { SkASSERT(!fConstants); fConstants = topic; } - if (string::npos != topic->fName.find(MdOut::kConstructors)) { + if (string::npos != topic->fName.find(SubtopicKeys::kConstructors)) { SkASSERT(!fConstructors); fConstructors = topic; } - if (string::npos != topic->fName.find(MdOut::kMemberFunctions)) { + if (string::npos != topic->fName.find(SubtopicKeys::kMemberFunctions)) { SkASSERT(!fMemberFunctions); fMemberFunctions = topic; } - if (string::npos != topic->fName.find(MdOut::kMembers)) { + if (string::npos != topic->fName.find(SubtopicKeys::kMembers)) { SkASSERT(!fMembers); fMembers = topic; } - if (string::npos != topic->fName.find(MdOut::kOperators)) { + if (string::npos != topic->fName.find(SubtopicKeys::kOperators)) { SkASSERT(!fOperators); fOperators = topic; } - if (string::npos != topic->fName.find(MdOut::kRelatedFunctions)) { + if (string::npos != topic->fName.find(SubtopicKeys::kRelatedFunctions)) { SkASSERT(!fRelatedFunctions); fRelatedFunctions = topic; } @@ -1539,8 +1659,11 @@ void HackParser::topicIter(const Definition* topic) { this->topicIter(child); break; case MarkType::kStruct: + this->addOneLiner(fStructs, child, hasLine, false); + this->topicIter(child); + break; case MarkType::kClass: - this->addOneLiner(fClassesAndStructs, child, hasLine, false); + this->addOneLiner(fClasses, child, hasLine, false); this->topicIter(child); break; case MarkType::kEnum: @@ -1822,6 +1945,7 @@ void TextParser::reportError(const char* errorStr) const { } void TextParser::reportWarning(const char* errorStr) const { + SkASSERT(fLine < fEnd); TextParser err(fFileName, fLine, fEnd, fLineCount); size_t lineLen = this->lineLength(); ptrdiff_t spaces = fChar - fLine; @@ -1892,7 +2016,13 @@ string TextParser::typedefName() { do { this->skipToWhiteSpace(); if (fChar < end && isspace(fChar[0])) { + const char* whiteStart = fChar; this->skipWhiteSpace(); + // FIXME: test should be for fMC + if ('#' == fChar[0]) { + end = whiteStart; + break; + } lastWord = fChar; } else { break; @@ -2051,8 +2181,6 @@ vector<string> BmhParser::typeName(MarkType markType, bool* checkEnd) { break; case MarkType::kCode: case MarkType::kDescription: - case MarkType::kDoxygen: - case MarkType::kExperimental: case MarkType::kExternal: case MarkType::kFormula: case MarkType::kFunction: @@ -2060,7 +2188,6 @@ vector<string> BmhParser::typeName(MarkType markType, bool* checkEnd) { case MarkType::kList: case MarkType::kNoExample: case MarkType::kPrivate: - case MarkType::kTrack: this->skipNoName(); break; case MarkType::kLine: @@ -2071,13 +2198,15 @@ vector<string> BmhParser::typeName(MarkType markType, bool* checkEnd) { case MarkType::kBug: // fixme: expect number case MarkType::kDefinedBy: case MarkType::kDeprecated: + case MarkType::kDetails: case MarkType::kDuration: - case MarkType::kFile: + case MarkType::kExperimental: case MarkType::kHeight: case MarkType::kIllustration: case MarkType::kImage: case MarkType::kIn: case MarkType::kLiteral: + case MarkType::kNoJustify: case MarkType::kOutdent: case MarkType::kPlatform: case MarkType::kPopulate: @@ -2085,7 +2214,6 @@ vector<string> BmhParser::typeName(MarkType markType, bool* checkEnd) { case MarkType::kSeeAlso: case MarkType::kSet: case MarkType::kSubstitute: - case MarkType::kTime: case MarkType::kToDo: case MarkType::kVolatile: case MarkType::kWidth: @@ -2104,12 +2232,22 @@ vector<string> BmhParser::typeName(MarkType markType, bool* checkEnd) { builder = this->typedefName(); break; case MarkType::kParam: - case MarkType::kPhraseDef: // fixme: expect camelCase for param builder = this->word("", ""); this->skipSpace(); *checkEnd = false; break; + case MarkType::kPhraseDef: { + const char* nameEnd = this->anyOf("(\n"); + builder = string(fChar, nameEnd - fChar); + this->skipLower(); + if (fChar != nameEnd) { + this->reportError("expect lower case only"); + break; + } + this->skipTo(nameEnd); + *checkEnd = false; + } break; case MarkType::kTable: this->skipNoName(); break; // unnamed @@ -2516,6 +2654,7 @@ int main(int argc, char** const argv) { } MdOut mdOut(bmhParser); mdOut.fDebugOut = FLAGS_stdout; + mdOut.fValidate = FLAGS_validate; if (!FLAGS_bmh.isEmpty() && mdOut.buildReferences(includeParser, FLAGS_bmh[0], FLAGS_ref[0])) { bmhParser.fWroteOut = true; @@ -2523,6 +2662,9 @@ int main(int argc, char** const argv) { if (!FLAGS_status.isEmpty() && mdOut.buildStatus(FLAGS_status[0], FLAGS_ref[0])) { bmhParser.fWroteOut = true; } + if (FLAGS_validate) { + mdOut.checkAnchors(); + } } if (!done && !FLAGS_spellcheck.isEmpty() && FLAGS_examples.isEmpty()) { if (!FLAGS_bmh.isEmpty()) { diff --git a/tools/bookmaker/bookmaker.h b/tools/bookmaker/bookmaker.h index 7e09d41afd..c755beec43 100644 --- a/tools/bookmaker/bookmaker.h +++ b/tools/bookmaker/bookmaker.h @@ -95,14 +95,13 @@ enum class MarkType { kDefinedBy, kDeprecated, kDescription, - kDoxygen, + kDetails, // used by #Const to specify #Subtopic details with examples and so on kDuration, kEnum, kEnumClass, kExample, kExperimental, kExternal, - kFile, kFormula, kFunction, kHeight, @@ -118,15 +117,16 @@ enum class MarkType { kMember, kMethod, kNoExample, + kNoJustify, // don't contribute this #Line to tabular comment measure, even if it fits kOutdent, kParam, kPhraseDef, + kPhraseParam, kPhraseRef, kPlatform, kPopulate, kPrivate, kReturn, - kRoot, kRow, kSeeAlso, kSet, @@ -137,16 +137,18 @@ enum class MarkType { kTable, kTemplate, kText, - kTime, kToDo, kTopic, - kTrack, kTypedef, kUnion, kVolatile, kWidth, }; +static inline bool IncompleteAllowed(MarkType markType) { + return MarkType::kDeprecated == markType || MarkType::kExperimental == markType; +} + enum { Last_MarkType = (int) MarkType::kWidth, }; @@ -299,21 +301,23 @@ public: return *loc; } + // either /n/n or /n# will stop parsing a typedef const char* doubleLF() const { - int count = 0; - const char* ptr = fChar; + const char* ptr = fChar - 1; const char* doubleStart = nullptr; - while (ptr < fEnd) { - if ('\n' == ptr[0]) { - if (++count == 1) { + while (++ptr < fEnd) { + if (!doubleStart) { + if ('\n' == ptr[0]) { doubleStart = ptr; - } else { - return doubleStart; } - } else if (' ' < ptr[0]) { - count = 0; + continue; + } + if ('\n' == ptr[0] || '#' == ptr[0]) { + return doubleStart; + } + if (' ' < ptr[0]) { + doubleStart = nullptr; } - ++ptr; } return nullptr; } @@ -476,6 +480,12 @@ public: return true; } + void skipLower() { + while (fChar < fEnd && (islower(fChar[0]) || '_' == fChar[0])) { + fChar++; + } + } + void skipToNonAlphaNum() { while (fChar < fEnd && (isalnum(fChar[0]) || '_' == fChar[0])) { fChar++; @@ -796,6 +806,19 @@ public: kOmitReturn, }; + enum class Details { + kNone, + kSoonToBe_Deprecated, + kTestingOnly_Experiment, + kDoNotUse_Experiement, + kNotReady_Experiment, + }; + + enum class DetailsType { + kPhrase, + kSentence, + }; + Definition() {} Definition(const char* start, const char* end, int line, Definition* parent, char mc) @@ -845,7 +868,6 @@ public: virtual ~Definition() {} virtual RootDefinition* asRoot() { SkASSERT(0); return nullptr; } - virtual const RootDefinition* asRoot() const { SkASSERT(0); return nullptr; } bool boilerplateIfDef(); bool boilerplateEndIf() { @@ -857,7 +879,7 @@ public: bool crossCheck(const Definition& includeToken) const; bool crossCheckInside(const char* start, const char* end, const Definition& includeToken) const; - const Definition* csParent() const { + Definition* csParent() { Definition* test = fParent; while (test) { if (MarkType::kStruct == test->fMarkType || MarkType::kClass == test->fMarkType) { @@ -875,12 +897,14 @@ public: const Definition* hasChild(MarkType markType) const; bool hasMatch(string name) const; const Definition* hasParam(string ref) const; + string incompleteMessage(DetailsType ) const; bool isClone() const { return fClone; } - Definition* iRootParent() { - Definition* test = fParent; + const Definition* iRootParent() const { + const Definition* test = fParent; while (test) { - if (Type::kKeyWord == test->fType && KeyWord::kClass == test->fKeyWord) { + if (Type::kKeyWord == test->fType + && (KeyWord::kClass == test->fKeyWord || KeyWord::kStruct == test->fKeyWord)) { return test; } test = test->fParent; @@ -923,6 +947,17 @@ public: fParentIndex = fParent ? (int) fParent->fTokens.size() : -1; } + const Definition* subtopicParent() const { + Definition* test = fParent; + while (test) { + if (MarkType::kTopic == test->fMarkType || MarkType::kSubtopic == test->fMarkType) { + return test; + } + test = test->fParent; + } + return nullptr; + } + const Definition* topicParent() const { Definition* test = fParent; while (test) { @@ -962,13 +997,29 @@ public: bool fDeprecated = false; bool fOperatorConst = false; bool fPrivate = false; - bool fShort = false; - bool fToBeDeprecated = false; + Details fDetails = Details::kNone; bool fMemberStart = false; bool fAnonymous = false; mutable bool fVisited = false; }; +class SubtopicKeys { +public: + static constexpr const char* kClasses = "Class"; + static constexpr const char* kConstants = "Constant"; + static constexpr const char* kConstructors = "Constructor"; + static constexpr const char* kDefines = "Define"; + static constexpr const char* kMemberFunctions = "Member_Function"; + static constexpr const char* kMembers = "Member"; + static constexpr const char* kOperators = "Operator"; + static constexpr const char* kOverview = "Overview"; + static constexpr const char* kRelatedFunctions = "Related_Function"; + static constexpr const char* kStructs = "Struct"; + static constexpr const char* kTypedefs = "Typedef"; + + static const char* kGeneratedSubtopics[]; +}; + class RootDefinition : public Definition { public: enum class AllowParens { @@ -976,6 +1027,15 @@ public: kYes, }; + struct SubtopicContents { + SubtopicContents() + : fShowClones(false) { + } + + vector<Definition*> fMembers; + bool fShowClones; + }; + RootDefinition() { } @@ -994,17 +1054,22 @@ public: } RootDefinition* asRoot() override { return this; } - const RootDefinition* asRoot() const override { return this; } void clearVisited(); bool dumpUnVisited(); - const Definition* find(string ref, AllowParens ) const; + Definition* find(string ref, AllowParens ); bool isRoot() const override { return true; } + + SubtopicContents& populator(const char* key) { + return fPopulators[key]; + } + RootDefinition* rootParent() override { return fRootParent; } const RootDefinition* rootParent() const override { return fRootParent; } void setRootParent(RootDefinition* rootParent) { fRootParent = rootParent; } unordered_map<string, RootDefinition*> fBranches; unordered_map<string, Definition> fLeaves; + unordered_map<string, SubtopicContents> fPopulators; private: RootDefinition* fRootParent = nullptr; }; @@ -1044,6 +1109,7 @@ public: ParserCommon() : TextParser() , fParent(nullptr) , fDebugOut(false) + , fValidate(false) { } @@ -1196,7 +1262,10 @@ public: int fPendingSpace; // one or two spaces should preceed the next string or block char fLastChar; // last written bool fDebugOut; // set true to write to std out + bool fValidate; // set true to check anchor defs and refs bool fOutdentNext; // set at end of embedded struct to prevent premature outdent + bool fWroteSomething; // used to detect empty content; an alternative source is preferable + private: typedef TextParser INHERITED; }; @@ -1238,6 +1307,7 @@ public: kFormula, // resolve methods as they are used, not as they are prototyped kLiteral, // output untouched kClone, // resolved, output, with references to clones as well + kSimple, // resolve simple words (used to resolve method declarations) }; enum class ExampleOptions { @@ -1283,14 +1353,13 @@ public: , { nullptr, MarkType::kDefinedBy } , { nullptr, MarkType::kDeprecated } , { nullptr, MarkType::kDescription } - , { nullptr, MarkType::kDoxygen } + , { nullptr, MarkType::kDetails } , { nullptr, MarkType::kDuration } , { &fEnumMap, MarkType::kEnum } , { &fClassMap, MarkType::kEnumClass } , { nullptr, MarkType::kExample } , { nullptr, MarkType::kExperimental } , { nullptr, MarkType::kExternal } - , { nullptr, MarkType::kFile } , { nullptr, MarkType::kFormula } , { nullptr, MarkType::kFunction } , { nullptr, MarkType::kHeight } @@ -1306,15 +1375,16 @@ public: , { nullptr, MarkType::kMember } , { &fMethodMap, MarkType::kMethod } , { nullptr, MarkType::kNoExample } + , { nullptr, MarkType::kNoJustify } , { nullptr, MarkType::kOutdent } , { nullptr, MarkType::kParam } , { nullptr, MarkType::kPhraseDef } + , { nullptr, MarkType::kPhraseParam } , { nullptr, MarkType::kPhraseRef } , { nullptr, MarkType::kPlatform } , { nullptr, MarkType::kPopulate } , { nullptr, MarkType::kPrivate } , { nullptr, MarkType::kReturn } - , { nullptr, MarkType::kRoot } , { nullptr, MarkType::kRow } , { nullptr, MarkType::kSeeAlso } , { nullptr, MarkType::kSet } @@ -1325,10 +1395,8 @@ public: , { nullptr, MarkType::kTable } , { nullptr, MarkType::kTemplate } , { nullptr, MarkType::kText } - , { nullptr, MarkType::kTime } , { nullptr, MarkType::kToDo } , { nullptr, MarkType::kTopic } - , { nullptr, MarkType::kTrack } , { &fTypedefMap, MarkType::kTypedef } , { nullptr, MarkType::kUnion } , { nullptr, MarkType::kVolatile } @@ -1484,14 +1552,13 @@ public: , { nullptr, MarkType::kDefinedBy } , { nullptr, MarkType::kDeprecated } , { nullptr, MarkType::kDescription } - , { nullptr, MarkType::kDoxygen } + , { nullptr, MarkType::kDetails } , { nullptr, MarkType::kDuration } , { &fIEnumMap, MarkType::kEnum } , { &fIEnumMap, MarkType::kEnumClass } , { nullptr, MarkType::kExample } , { nullptr, MarkType::kExperimental } , { nullptr, MarkType::kExternal } - , { nullptr, MarkType::kFile } , { nullptr, MarkType::kFormula } , { nullptr, MarkType::kFunction } , { nullptr, MarkType::kHeight } @@ -1507,15 +1574,16 @@ public: , { nullptr, MarkType::kMember } , { nullptr, MarkType::kMethod } , { nullptr, MarkType::kNoExample } + , { nullptr, MarkType::kNoJustify } , { nullptr, MarkType::kOutdent } , { nullptr, MarkType::kParam } , { nullptr, MarkType::kPhraseDef } + , { nullptr, MarkType::kPhraseParam } , { nullptr, MarkType::kPhraseRef } , { nullptr, MarkType::kPlatform } , { nullptr, MarkType::kPopulate } , { nullptr, MarkType::kPrivate } , { nullptr, MarkType::kReturn } - , { nullptr, MarkType::kRoot } , { nullptr, MarkType::kRow } , { nullptr, MarkType::kSeeAlso } , { nullptr, MarkType::kSet } @@ -1526,10 +1594,8 @@ public: , { nullptr, MarkType::kTable } , { &fITemplateMap, MarkType::kTemplate } , { nullptr, MarkType::kText } - , { nullptr, MarkType::kTime } , { nullptr, MarkType::kToDo } , { nullptr, MarkType::kTopic } - , { nullptr, MarkType::kTrack } , { &fITypedefMap, MarkType::kTypedef } , { &fIUnionMap, MarkType::kUnion } , { nullptr, MarkType::kVolatile } @@ -1905,6 +1971,13 @@ public: kOut, }; + enum class ItemState { + kNone, + kName, + kValue, + kComment, + }; + struct IterState { IterState (list<Definition>::iterator tIter, list<Definition>::iterator tIterEnd) : fDefIter(tIter) @@ -1937,6 +2010,23 @@ public: bool fWord; }; + struct Item { + string fName; + string fValue; + }; + + struct LastItem { + const char* fStart; + const char* fEnd; + }; + + struct ItemLength { + int fCurName; + int fCurValue; + int fLongestName; + int fLongestValue; + }; + IncludeWriter() : IncludeParser() { this->reset(); } @@ -1954,11 +2044,19 @@ public: return 0 == size; } + bool checkChildCommentLength(const Definition* parent, MarkType childType) const; + void checkEnumLengths(const Definition& child, string enumName, ItemLength* length) const; void constOut(const Definition* memberStart, const Definition& child, - const Definition* bmhConst); + const Definition* bmhConst); void descriptionOut(const Definition* def, SkipFirstLine , Phrase ); - void enumHeaderOut(const RootDefinition* root, const Definition& child); - void enumMembersOut(const RootDefinition* root, Definition& child); + void enumHeaderOut(RootDefinition* root, const Definition& child); + string enumMemberComment(const Definition* currentEnumItem, const Definition& child) const; + const Definition* enumMemberForComment(const Definition* currentEnumItem) const; + ItemState enumMemberName(const Definition& child, + const Definition* token, Item* , LastItem* , const Definition** enumItem); + void enumMemberOut(const Definition* currentEnumItem, const Definition& child, + const Item& , Preprocessor& ); + void enumMembersOut(Definition& child); bool enumPreprocessor(Definition* token, MemberPass pass, vector<IterState>& iterStack, IterState** iterState, Preprocessor* ); void enumSizeItems(const Definition& child); @@ -1970,7 +2068,8 @@ public: int lookupReference(const PunctuationState punctuation, const Word word, const int start, const int run, int lastWrite, const char last, const char* data); - void methodOut(const Definition* method, const Definition& child); + const Definition* matchMemberName(string matchName, const Definition& child) const; + void methodOut(Definition* method, const Definition& child); bool populate(Definition* def, ParentPair* parentPair, RootDefinition* root); bool populate(BmhParser& bmhParser); @@ -1993,7 +2092,6 @@ public: Definition* structMemberOut(const Definition* memberStart, const Definition& child); void structOut(const Definition* root, const Definition& child, const char* commentStart, const char* commentEnd); - void structSetMembersShort(const vector<Definition*>& bmhChildren); void structSizeMembers(const Definition& child); private: BmhParser* fBmhParser; @@ -2002,7 +2100,7 @@ private: const Definition* fBmhMethod; const Definition* fEnumDef; const Definition* fMethodDef; - const Definition* fBmhStructDef; + Definition* fBmhStructDef; const char* fContinuation; // used to construct paren-qualified method name int fAnonymousEnumCount; int fEnumItemValueTab; @@ -2120,13 +2218,14 @@ public: private: const BmhParser& fBmhParser; - const Definition* fClassesAndStructs; + const Definition* fClasses; const Definition* fConstants; const Definition* fConstructors; const Definition* fMemberFunctions; const Definition* fMembers; const Definition* fOperators; const Definition* fRelatedFunctions; + const Definition* fStructs; bool hackFiles(); typedef ParserCommon INHERITED; @@ -2134,25 +2233,21 @@ private: class MdOut : public ParserCommon { public: - MdOut(const BmhParser& bmh) : ParserCommon() + struct SubtopicDescriptions { + string fName; + string fOneLiner; + string fDetails; + }; + + MdOut(BmhParser& bmh) : ParserCommon() , fBmhParser(bmh) { this->reset(); + this->addPopulators(); } bool buildReferences(const IncludeParser& , const char* docDir, const char* mdOutDirOrFile); bool buildStatus(const char* docDir, const char* mdOutDir); - - static constexpr const char* kClassesAndStructs = "Class_or_Struct"; - static constexpr const char* kConstants = "Constant"; - static constexpr const char* kConstructors = "Constructor"; - static constexpr const char* kDefines = "Define"; - static constexpr const char* kMemberFunctions = "Member_Function"; - static constexpr const char* kMembers = "Member"; - static constexpr const char* kOperators = "Operator"; - static constexpr const char* kOverview = "Overview"; - static constexpr const char* kRelatedFunctions = "Related_Function"; - static constexpr const char* kSubtopics = "Overview_Subtopic"; - static constexpr const char* kTypedefs = "Typedef"; + void checkAnchors(); private: enum class TableState { @@ -2161,34 +2256,39 @@ private: kColumn, }; - struct TableContents { - TableContents() - : fShowClones(false) { - } - - string fDescription; - vector<const Definition*> fMembers; - bool fShowClones; + struct AnchorDef { + string fDef; + MarkType fMarkType; }; + void addPopulators(); string addReferences(const char* start, const char* end, BmhParser::Resolvable ); + string anchorDef(string def, string name); + string anchorLocalRef(string ref, string name); + string anchorRef(string def, string name); + bool buildRefFromFile(const char* fileName, const char* outDir); bool checkParamReturnBody(const Definition* def); - void childrenOut(const Definition* def, const char* contentStart); - const Definition* csParent() const; + Definition* checkParentsForMatch(Definition* test, string ref) const; + void childrenOut(Definition* def, const char* contentStart); + Definition* csParent(); const Definition* findParamType(); + string getMemberTypeName(const Definition* def, string* memberType); + static bool HasDetails(const Definition* def); + void htmlOut(string ); const Definition* isDefined(const TextParser& , string ref, BmhParser::Resolvable ); + const Definition* isDefinedByParent(RootDefinition* root, string ref); string linkName(const Definition* ) const; - string linkRef(string leadingSpaces, const Definition*, string ref, - BmhParser::Resolvable ) const; - void markTypeOut(Definition* ); + string linkRef(string leadingSpaces, const Definition*, string ref, BmhParser::Resolvable ); + void markTypeOut(Definition* , const Definition** prior); void mdHeaderOut(int depth) { mdHeaderOutLF(depth, 2); } void mdHeaderOutLF(int depth, int lf); - void overviewOut(); bool parseFromFile(const char* path) override { return true; } - void populateTables(const Definition* def); + void populateOne(Definition* def, + unordered_map<string, RootDefinition::SubtopicContents>& populator); + void populateTables(const Definition* def, RootDefinition* ); - TableContents& populator(const char* key) { + SubtopicDescriptions& populator(string key) { auto entry = fPopulators.find(key); // FIXME: this should have been detected earlier SkASSERT(fPopulators.end() != entry); @@ -2200,6 +2300,7 @@ private: fEnumClass = nullptr; fMethod = nullptr; fRoot = nullptr; + fSubtopic = nullptr; fLastParam = nullptr; fTableState = TableState::kNone; fAddRefFailed = false; @@ -2208,6 +2309,7 @@ private: fInList = false; fResolveAndIndent = false; fLiteralAndIndent = false; + fLastDef = nullptr; } BmhParser::Resolvable resolvable(const Definition* definition) const { @@ -2227,19 +2329,32 @@ private: } void resolveOut(const char* start, const char* end, BmhParser::Resolvable ); - void rowOut(const char * name, string description); - void subtopicOut(const TableContents& tableContents); - void subtopicsOut(); + void rowOut(const char * name, string description, bool literalName); + + void subtopicOut(string name); + void subtopicsOut(Definition* def); + void summaryOut(const Definition* def, MarkType , string name); + string tableDataCodeDef(const Definition* def); + string tableDataCodeDef(string def, string name); + string tableDataCodeLocalRef(string name); + string tableDataCodeLocalRef(string ref, string name); + string tableDataCodeRef(const Definition* ref); + string tableDataCodeRef(string ref, string name); - unordered_map<string, TableContents> fPopulators; vector<const Definition*> fClassStack; + unordered_map<string, vector<AnchorDef> > fAllAnchorDefs; + unordered_map<string, vector<string> > fAllAnchorRefs; - const BmhParser& fBmhParser; + BmhParser& fBmhParser; const Definition* fEnumClass; + const Definition* fLastDef; Definition* fMethod; - const RootDefinition* fRoot; + RootDefinition* fRoot; // used in generating populated tables; always struct or class + RootDefinition* fSubtopic; // used in resolving symbols const Definition* fLastParam; TableState fTableState; + unordered_map<string, SubtopicDescriptions> fPopulators; + unordered_map<string, string> fPhraseParams; bool fAddRefFailed; bool fHasFiddle; bool fInDescription; // FIXME: for now, ignore unfound camelCase in description since it may @@ -2247,6 +2362,8 @@ private: bool fInList; bool fLiteralAndIndent; bool fResolveAndIndent; + bool fOddRow; + bool fHasDetails; typedef ParserCommon INHERITED; }; @@ -2272,7 +2389,7 @@ public: } } - void skipToMethodEnd() { + void skipToMethodEnd(BmhParser::Resolvable resolvable) { if (this->eof()) { return; } @@ -2284,7 +2401,8 @@ public: return; } } - if (this->startsWith(fClassName.c_str()) || this->startsWith("operator")) { + if (BmhParser::Resolvable::kSimple != resolvable + && (this->startsWith(fClassName.c_str()) || this->startsWith("operator"))) { const char* ptr = this->anyOf("\n ("); if (ptr && '(' == *ptr) { this->skipToEndBracket(')'); diff --git a/tools/bookmaker/definition.cpp b/tools/bookmaker/definition.cpp index 06db96487e..9a6d0f6d49 100644 --- a/tools/bookmaker/definition.cpp +++ b/tools/bookmaker/definition.cpp @@ -338,6 +338,9 @@ bool Definition::boilerplateIfDef() { // fixme: this will need to be more complicated to handle all of Skia // for now, just handle paint -- maybe fiddle will loosen naming restrictions void Definition::setCanonicalFiddle() { + if (string::npos != fName.find("SkCanvas::SaveLayerRec")) { + SkDebugf(""); + } fMethodType = Definition::MethodType::kNone; size_t doubleColons = fName.find("::", 0); SkASSERT(string::npos != doubleColons); @@ -372,6 +375,15 @@ void Definition::setCanonicalFiddle() { size_t openParen = fName.find('(', doubleColons); if (string::npos == openParen) { result += fName.substr(doubleColons); + // see if it is a constructor -- if second to last delimited name equals last + size_t nextColons = fName.find("::", doubleColons); + if (string::npos != nextColons) { + nextColons += 2; + if (!strncmp(&fName[doubleColons], &fName[nextColons], + nextColons - doubleColons - 2)) { + fMethodType = Definition::MethodType::kConstructor; + } + } } else { size_t comma = fName.find(',', doubleColons); if (string::npos == comma) { @@ -677,7 +689,7 @@ string Definition::formatFunction(Format format) const { lastStart = saveStart; lastEnd = methodParser.fChar; indent = SkTMin(indent, (size_t) (limit - maxLine)); - // write string wtih trimmmed indent + // write string with trimmmed indent string methodStr; int written = 0; do { @@ -804,6 +816,32 @@ bool Definition::hasMatch(string name) const { return false; } +string Definition::incompleteMessage(DetailsType detailsType) const { + if (!IncompleteAllowed(fMarkType)) { + auto iter = std::find_if(fChildren.begin(), fChildren.end(), + [](const Definition* test) { return IncompleteAllowed(test->fMarkType); }); + SkASSERT(fChildren.end() != iter); + return (*iter)->incompleteMessage(detailsType); + } + string message = MarkType::kExperimental == fMarkType ? + "Experimental." : "Deprecated."; + if (Definition::Details::kDoNotUse_Experiement == fDetails) { + message += " Do not use."; + } else if (Definition::Details::kNotReady_Experiment == fDetails) { + message += " Not ready for general use."; + } else if (Definition::Details::kSoonToBe_Deprecated == fDetails) { + message += " Soon to be deprecated."; + } else if (Definition::Details::kTestingOnly_Experiment == fDetails) { + message += " For testing only."; + } + if (DetailsType::kPhrase == detailsType) { + message = message.substr(0, message.length() - 1); // remove trailing period + std::replace(message.begin(), message.end(), '.', ':'); + std::transform(message.begin(), message.end(), message.begin(), ::tolower); + } + return message; +} + bool Definition::isStructOrClass() const { if (MarkType::kStruct != fMarkType && MarkType::kClass != fMarkType) { return false; @@ -1084,7 +1122,7 @@ bool RootDefinition::dumpUnVisited() { return success; } -const Definition* RootDefinition::find(string ref, AllowParens allowParens) const { +Definition* RootDefinition::find(string ref, AllowParens allowParens) { const auto leafIter = fLeaves.find(ref); if (leafIter != fLeaves.end()) { return &leafIter->second; @@ -1098,12 +1136,12 @@ const Definition* RootDefinition::find(string ref, AllowParens allowParens) cons } const auto branchIter = fBranches.find(ref); if (branchIter != fBranches.end()) { - const RootDefinition* rootDef = branchIter->second; + RootDefinition* rootDef = branchIter->second; return rootDef; } - const Definition* result = nullptr; + Definition* result = nullptr; for (const auto& branch : fBranches) { - const RootDefinition* rootDef = branch.second; + RootDefinition* rootDef = branch.second; result = rootDef->find(ref, allowParens); if (result) { break; diff --git a/tools/bookmaker/includeWriter.cpp b/tools/bookmaker/includeWriter.cpp index e2ba6c67b8..c1a98cf786 100644 --- a/tools/bookmaker/includeWriter.cpp +++ b/tools/bookmaker/includeWriter.cpp @@ -7,6 +7,57 @@ #include "bookmaker.h" +bool IncludeWriter::checkChildCommentLength(const Definition* parent, MarkType childType) const { + bool oneMember = false; + for (auto& item : parent->fChildren) { + if (childType != item->fMarkType) { + continue; + } + oneMember = true; + int lineLen = 0; + for (auto& itemChild : item->fChildren) { + if (MarkType::kExperimental == itemChild->fMarkType) { + lineLen = sizeof("experimental") - 1; + break; + } + if (MarkType::kDeprecated == itemChild->fMarkType) { + lineLen = sizeof("deprecated") - 1; + // todo: look for 'soon' + break; + } + if (MarkType::kLine == itemChild->fMarkType) { + lineLen = itemChild->length(); + break; + } + } + if (!lineLen) { + item->reportError<void>("missing #Line"); + } + if (fEnumItemCommentTab + lineLen >= 100) { +// if too long, remove spaces until it fits, or wrap +// item->reportError<void>("#Line comment too long"); + } + } + return oneMember; +} + +void IncludeWriter::checkEnumLengths(const Definition& child, string enumName, ItemLength* length) const { + const Definition* enumItem = this->matchMemberName(enumName, child); + if (std::any_of(enumItem->fChildren.begin(), enumItem->fChildren.end(), + [](Definition* child){return MarkType::kNoJustify == child->fMarkType;})) { + return; + } + string comment = this->enumMemberComment(enumItem, child); + int lineLimit = 100 - fIndent - 7; // 7: , space //!< space + if (length->fCurValue) { + lineLimit -= 3; // space = space + } + if (length->fCurName + length->fCurValue + (int) comment.length() < lineLimit) { + length->fLongestName = SkTMax(length->fLongestName, length->fCurName); + length->fLongestValue = SkTMax(length->fLongestValue, length->fCurValue); + } +} + void IncludeWriter::constOut(const Definition* memberStart, const Definition& child, const Definition* bmhConst) { const char* bodyEnd = fDeferComment ? fDeferComment->fContentStart - 1 : @@ -33,7 +84,8 @@ void IncludeWriter::descriptionOut(const Definition* def, SkipFirstLine skipFirs bool breakOut = false; SkDEBUGCODE(bool wroteCode = false); if (def->fDeprecated) { - this->writeString(def->fToBeDeprecated ? "To be deprecated soon." : "Deprecated."); + string message = def->incompleteMessage(Definition::DetailsType::kSentence); + this->writeString(message); this->lfcr(); } for (auto prop : def->fChildren) { @@ -92,9 +144,10 @@ void IncludeWriter::descriptionOut(const Definition* def, SkipFirstLine skipFirs } } commentStart = prop->fContentStart; - if (def->fToBeDeprecated) { - commentStart += 4; // skip over "soon" // FIXME: this is awkward - } else if (MarkType::kBug == prop->fMarkType) { + if (' ' < commentStart[0]) { + commentStart = strchr(commentStart, '\n'); + } + if (MarkType::kBug == prop->fMarkType) { commentStart = prop->fContentEnd; } commentLen = (int) (prop->fContentEnd - commentStart); @@ -115,6 +168,9 @@ void IncludeWriter::descriptionOut(const Definition* def, SkipFirstLine skipFirs this->writeString("EXPERIMENTAL:"); this->writeSpace(); commentStart = prop->fContentStart; + if (' ' < commentStart[0]) { + commentStart = strchr(commentStart, '\n'); + } commentLen = (int) (prop->fContentEnd - commentStart); if (commentLen > 0) { if (Wrote::kNone != this->rewriteBlock(commentLen, commentStart, Phrase::kNo)) { @@ -202,7 +258,41 @@ void IncludeWriter::descriptionOut(const Definition* def, SkipFirstLine skipFirs return this->reportError<void>("missing phrase definition"); } Definition* phraseDef = iter->second; - this->rewriteBlock(phraseDef->length(), phraseDef->fContentStart, Phrase::kYes); + // TODO: given TextParser(commentStart, prop->fStart + up to #) return if + // it ends with two of more linefeeds, ignoring other whitespace + Phrase defIsPhrase = '\n' == prop->fStart[0] && '\n' == prop->fStart[-1] ? + Phrase::kNo : Phrase::kYes; + if (Phrase::kNo == defIsPhrase) { + this->lf(2); + } + const char* start = phraseDef->fContentStart; + int length = phraseDef->length(); + auto propParams = prop->fChildren.begin(); + // can this share code or logic with mdout somehow? + for (auto child : phraseDef->fChildren) { + if (MarkType::kPhraseParam == child->fMarkType) { + continue; + } + int localLength = child->fStart - start; + this->rewriteBlock(localLength, start, defIsPhrase); + start += localLength; + length -= localLength; + SkASSERT(propParams != prop->fChildren.end()); + if (fColumn > 0) { + this->writeSpace(); + } + this->writeString((*propParams)->fName); + localLength = child->fContentEnd - child->fStart; + start += localLength; + length -= localLength; + if (isspace(start[0])) { + this->writeSpace(); + } + defIsPhrase = Phrase::kYes; + } + if (length > 0) { + this->rewriteBlock(length, start, defIsPhrase); + } commentStart = prop->fContentStart; commentLen = (int) (def->fContentEnd - commentStart); } break; @@ -220,8 +310,7 @@ void IncludeWriter::descriptionOut(const Definition* def, SkipFirstLine skipFirs } } -void IncludeWriter::enumHeaderOut(const RootDefinition* root, - const Definition& child) { +void IncludeWriter::enumHeaderOut(RootDefinition* root, const Definition& child) { const Definition* enumDef = nullptr; const char* bodyEnd = fDeferComment ? fDeferComment->fContentStart - 1 : child.fContentStart; @@ -281,8 +370,7 @@ void IncludeWriter::enumHeaderOut(const RootDefinition* root, bool lastAnchor = false; SkDEBUGCODE(bool foundConst = false); for (auto test : enumDef->fChildren) { - if (MarkType::kCode == test->fMarkType) { - SkASSERT(!codeBlock); // FIXME: check enum for correct order earlier + if (MarkType::kCode == test->fMarkType && !codeBlock) { codeBlock = test; commentStart = codeBlock->fTerminator; continue; @@ -316,17 +404,23 @@ void IncludeWriter::enumHeaderOut(const RootDefinition* root, lastAnchor = false; } this->rewriteBlock((int) (commentEnd - commentStart), commentStart, Phrase::kNo); - if (MarkType::kAnchor == test->fMarkType) { + if (MarkType::kAnchor == test->fMarkType || MarkType::kCode == test->fMarkType) { bool newLine = commentEnd - commentStart > 1 && '\n' == commentEnd[-1] && '\n' == commentEnd[-2]; commentStart = test->fContentStart; - commentEnd = test->fChildren[0]->fStart; + commentEnd = MarkType::kAnchor == test->fMarkType ? test->fChildren[0]->fStart : + test->fContentEnd; if (newLine) { this->lf(2); } else { this->writeSpace(); } - this->rewriteBlock((int) (commentEnd - commentStart), commentStart, Phrase::kNo); + if (MarkType::kAnchor == test->fMarkType) { + this->rewriteBlock((int) (commentEnd - commentStart), commentStart, Phrase::kNo); + } else { + this->writeBlock((int) (commentEnd - commentStart), commentStart); + this->lf(2); + } lastAnchor = true; // this->writeSpace(); } commentStart = test->fTerminator; @@ -358,176 +452,189 @@ void IncludeWriter::enumHeaderOut(const RootDefinition* root, fEnumDef = enumDef; } -void IncludeWriter::enumMembersOut(const RootDefinition* root, Definition& child) { - // iterate through include tokens and find how much remains for 1 line comments - // put ones that fit on same line, ones that are too big on preceding line? - const Definition* currentEnumItem = nullptr; - const char* commentStart = nullptr; - const char* lastEnd = nullptr; - int commentLen = 0; - enum class State { - kNoItem, - kItemName, - kItemValue, - kItemComment, - }; - State state = State::kNoItem; +const Definition* IncludeWriter::enumMemberForComment(const Definition* currentEnumItem) const { + for (auto constItem : currentEnumItem->fChildren) { + if (MarkType::kLine == constItem->fMarkType + || MarkType::kExperimental == constItem->fMarkType + || MarkType::kDeprecated == constItem->fMarkType) { + return constItem; + } + } + SkASSERT(0); + return nullptr; +} + +string IncludeWriter::enumMemberComment(const Definition* currentEnumItem, + const Definition& child) const { + // #Const should always be followed by #Line, so description follows that + string shortComment; + for (auto constItem : currentEnumItem->fChildren) { + if (MarkType::kLine == constItem->fMarkType) { + shortComment = string(constItem->fContentStart, constItem->length()); + break; + } + if (IncompleteAllowed(constItem->fMarkType)) { + shortComment = constItem->incompleteMessage(Definition::DetailsType::kPhrase); + } + } + if (!shortComment.length()) { + currentEnumItem->reportError<void>("missing #Line or #Deprecated or #Experimental"); + } + return shortComment; +} + +IncludeWriter::ItemState IncludeWriter::enumMemberName( + const Definition& child, const Definition* token, Item* item, LastItem* last, + const Definition** currentEnumItem) { + TextParser parser(fFileName, last->fStart, last->fEnd, fLineCount); + parser.skipWhiteSpace(); + item->fName = string(parser.fChar, (int) (last->fEnd - parser.fChar)); + *currentEnumItem = this->matchMemberName(item->fName, child); + if (token) { + fStart = token->fContentEnd; + TextParser enumLine(token->fFileName, last->fEnd, token->fContentStart, token->fLineCount); + const char* end = enumLine.anyOf(",}="); + SkASSERT(end); + if ('=' == *end) { // write enum value + last->fEnd = token->fContentEnd; + item->fValue = string(token->fContentStart, (int) (last->fEnd - token->fContentStart)); + return ItemState::kValue; + } + } + return ItemState::kComment; +} + +void IncludeWriter::enumMemberOut(const Definition* currentEnumItem, const Definition& child, + const Item& item, Preprocessor& preprocessor) { + SkASSERT(currentEnumItem); + string shortComment = this->enumMemberComment(currentEnumItem, child); + int enumItemValueTab = + SkTMax((int) item.fName.length() + fIndent + 1, fEnumItemValueTab); // 1: , + int valueLength = item.fValue.length(); + int assignLength = valueLength ? valueLength + 3 : 0; // 3: space = space + int enumItemCommentTab = SkTMax(enumItemValueTab + assignLength, fEnumItemCommentTab); + int trimNeeded = enumItemCommentTab + shortComment.length() - (100 - (sizeof("//!< ") - 1)); + bool crAfterName = false; + if (trimNeeded > 0) { + if (item.fValue.length()) { + int valueSpare = SkTMin(trimNeeded, // 3 below: space = space + (int) (enumItemCommentTab - enumItemValueTab - item.fValue.length() - 3)); + SkASSERT(valueSpare >= 0); + trimNeeded -= valueSpare; + enumItemCommentTab -= valueSpare; + } + if (trimNeeded > 0) { + int nameSpare = SkTMin(trimNeeded, (int) (enumItemValueTab - item.fName.length() + - fIndent - 1)); // 1: , + SkASSERT(nameSpare >= 0); + trimNeeded -= nameSpare; + enumItemValueTab -= nameSpare; + enumItemCommentTab -= nameSpare; + } + if (trimNeeded > 0) { + crAfterName = true; + if (!valueLength) { + this->enumMemberForComment(currentEnumItem)->reportError<void>("comment too long"); + } else if (valueLength + fIndent + 8 + shortComment.length() > // 8: addtional indent + 100 - (sizeof(", //!< ") - 1)) { // -1: zero-terminated string + this->enumMemberForComment(currentEnumItem)->reportError<void>("comment 2 long"); + } // 2: = space + enumItemValueTab = fEnumItemValueTab + 2 // 2: , space + - SkTMax(0, fEnumItemValueTab + 2 + valueLength + 2 - fEnumItemCommentTab); + enumItemCommentTab = SkTMax(enumItemValueTab + valueLength + 2, fEnumItemCommentTab); + } + } + this->lfcr(); + this->writeString(item.fName); + int saveIndent = fIndent; + if (item.fValue.length()) { + if (!crAfterName) { + this->indentToColumn(enumItemValueTab); + } else { + this->writeSpace(); + } + this->writeString("="); + if (crAfterName) { + this->lfcr(); + fIndent = enumItemValueTab; + } else { + this->writeSpace(); + } + this->writeString(item.fValue); + } + this->writeString(","); + this->indentToColumn(enumItemCommentTab); + this->writeString("//!<"); + this->writeSpace(); + this->rewriteBlock(shortComment.length(), shortComment.c_str(), Phrase::kYes); + this->lfcr(); + fIndent = saveIndent; + if (preprocessor.fStart) { + SkASSERT(preprocessor.fEnd); + int saveIndent = fIndent; + fIndent = SkTMax(0, fIndent - 8); + this->lf(2); + this->writeBlock( + (int) (preprocessor.fEnd - preprocessor.fStart), preprocessor.fStart); + this->lfcr(); + fIndent = saveIndent; + preprocessor.reset(); + } +} + +// iterate through include tokens and find how much remains for 1 line comments +// put ones that fit on same line, ones that are too big wrap +void IncludeWriter::enumMembersOut(Definition& child) { + ItemState state = ItemState::kNone; + const Definition* currentEnumItem; + LastItem last = { nullptr, nullptr }; + auto brace = child.fChildren[0]; + if (KeyWord::kClass == brace->fKeyWord) { + brace = brace->fChildren[0]; + } + SkASSERT(Bracket::kBrace == brace->fBracket); vector<IterState> iterStack; - iterStack.emplace_back(child.fTokens.begin(), child.fTokens.end()); + iterStack.emplace_back(brace->fTokens.begin(), brace->fTokens.end()); IterState* iterState = &iterStack[0]; Preprocessor preprocessor; - for (int onePast = 0; onePast < 2; onePast += iterState->fDefIter == iterState->fDefEnd) { - Definition* token = onePast ? nullptr : &*iterState->fDefIter++; - if (this->enumPreprocessor(token, MemberPass::kOut, iterStack, &iterState, + Item item; + while (iterState->fDefIter != iterState->fDefEnd) { + auto& token = *iterState->fDefIter++; + if (this->enumPreprocessor(&token, MemberPass::kOut, iterStack, &iterState, &preprocessor)) { continue; } - if (token && State::kItemName == state) { - TextParser enumLine(token->fFileName, lastEnd, - token->fContentStart, token->fLineCount); - const char* end = enumLine.anyOf(",}="); - SkASSERT(end); - state = '=' == *end ? State::kItemValue : State::kItemComment; - if (State::kItemValue == state) { // write enum value - this->indentToColumn(fEnumItemValueTab); - this->writeString("="); - this->writeSpace(); - lastEnd = token->fContentEnd; - this->writeBlock((int) (lastEnd - token->fContentStart), - token->fContentStart); // write const value if any - continue; - } + if (ItemState::kName == state) { + state = this->enumMemberName(child, &token, &item, &last, ¤tEnumItem); } - if (token && State::kItemValue == state) { - TextParser valueEnd(token->fFileName, lastEnd, - token->fContentStart, token->fLineCount); + if (ItemState::kValue == state) { + TextParser valueEnd(token.fFileName, last.fEnd, token.fContentStart, token.fLineCount); const char* end = valueEnd.anyOf(",}"); if (!end) { // write expression continuation - if (' ' == lastEnd[0]) { - this->writeSpace(); - } - this->writeBlock((int) (token->fContentEnd - lastEnd), lastEnd); - continue; - } - } - if (State::kNoItem != state) { - this->writeString(","); - SkASSERT(currentEnumItem); - if (currentEnumItem->fShort) { - this->indentToColumn(fEnumItemCommentTab); - if (commentLen || currentEnumItem->fDeprecated) { - this->writeString("//!<"); - this->writeSpace(); - if (currentEnumItem->fDeprecated) { - this->writeString(child.fToBeDeprecated ? "to be deprecated soon" - : "deprecated"); - } else { - this->rewriteBlock(commentLen, commentStart, Phrase::kNo); - } - } - } - if (onePast) { - fIndent -= 4; - } - this->lfcr(); - if (preprocessor.fStart) { - SkASSERT(preprocessor.fEnd); - int saveIndent = fIndent; - fIndent = SkTMax(0, fIndent - 8); - this->lf(2); - this->writeBlock( - (int) (preprocessor.fEnd - preprocessor.fStart), preprocessor.fStart); - this->lfcr(); - fIndent = saveIndent; - preprocessor.reset(); - } - if (token && State::kItemValue == state) { - fStart = token->fContentStart; - } - state = State::kNoItem; - } - SkASSERT(State::kNoItem == state); - if (onePast) { - break; - } - SkASSERT(token); - string itemName; - if (!fEnumDef->isRoot()) { - itemName = root->fName + "::"; - if (KeyWord::kClass == child.fParent->fKeyWord) { - itemName += child.fParent->fName + "::"; - } - } - itemName += string(token->fContentStart, (int) (token->fContentEnd - token->fContentStart)); - for (auto& enumItem : fEnumDef->fChildren) { - if (MarkType::kConst != enumItem->fMarkType) { - continue; - } - if (itemName != enumItem->fName) { + item.fValue += string(last.fEnd, (int) (token.fContentEnd - last.fEnd)); continue; } - currentEnumItem = enumItem; - break; - } - SkASSERT(currentEnumItem); - // if description fits, it goes after item - commentStart = currentEnumItem->fContentStart; - const char* commentEnd; - if (currentEnumItem->fChildren.size() > 0) { - commentEnd = currentEnumItem->fChildren[0]->fStart; - } else { - commentEnd = currentEnumItem->fContentEnd; - } - TextParser enumComment(fFileName, commentStart, commentEnd, currentEnumItem->fLineCount); - bool isDeprecated = false; - if (enumComment.skipToLineStart()) { // skip const value - commentStart = enumComment.fChar; - commentLen = (int) (commentEnd - commentStart); - } else { - const Definition* childDef = currentEnumItem->fChildren[0]; - isDeprecated = MarkType::kDeprecated == childDef->fMarkType; - if (MarkType::kPrivate == childDef->fMarkType || isDeprecated) { - commentStart = childDef->fContentStart; - if (currentEnumItem->fToBeDeprecated) { - SkASSERT(isDeprecated); - commentStart += 4; // skip over "soon" // FIXME: this is awkward - } - commentLen = (int) (childDef->fContentEnd - commentStart); - } } - // FIXME: may assert here if there's no const value - // should have detected and errored on that earlier when enum fContentStart was set - SkASSERT((commentLen > 0 && commentLen < 1000) || isDeprecated); - if (!currentEnumItem->fShort) { - this->writeCommentHeader(); - fIndent += 4; - bool wroteLineFeed = false; - if (isDeprecated) { - this->writeString(currentEnumItem->fToBeDeprecated - ? "To be deprecated soon." : "Deprecated."); - } - TextParserSave save(this); - this->setForErrorReporting(currentEnumItem, commentStart); - wroteLineFeed = Wrote::kLF == - this->rewriteBlock(commentLen, commentStart, Phrase::kNo); - save.restore(); - fIndent -= 4; - if (wroteLineFeed || fColumn > 100 - 3 /* space * / */ ) { - this->lfcr(); - } else { - this->writeSpace(); - } - this->writeCommentTrailer(); + if (ItemState::kNone != state) { + this->enumMemberOut(currentEnumItem, child, item, preprocessor); + fStart = token.fContentStart; + state = ItemState::kNone; + last.fStart = nullptr; } - lastEnd = token->fContentEnd; - this->lfcr(); - if (',' == fStart[0]) { - ++fStart; + SkASSERT(ItemState::kNone == state); + if (!last.fStart) { + last.fStart = fStart; } - this->writeBlock((int) (lastEnd - fStart), fStart); // enum item name - fStart = token->fContentEnd; - state = State::kItemName; + last.fEnd = token.fContentEnd; + state = ItemState::kName; + } + if (ItemState::kName == state) { + state = this->enumMemberName(child, nullptr, &item, &last, ¤tEnumItem); + } + if (ItemState::kValue == state || ItemState::kComment == state) { + this->enumMemberOut(currentEnumItem, child, item, preprocessor); } + fIndent -= 4; } bool IncludeWriter::enumPreprocessor(Definition* token, MemberPass pass, @@ -590,18 +697,9 @@ bool IncludeWriter::enumPreprocessor(Definition* token, MemberPass pass, } void IncludeWriter::enumSizeItems(const Definition& child) { - enum class State { - kNoItem, - kItemName, - kItemValue, - kItemComment, - }; - State state = State::kNoItem; - int longestName = 0; - int longestValue = 0; - int valueLen = 0; + ItemState state = ItemState::kNone; + ItemLength lengths = { 0, 0, 0, 0 }; const char* lastEnd = nullptr; -// SkASSERT(child.fChildren.size() == 1 || child.fChildren.size() == 2); auto brace = child.fChildren[0]; if (KeyWord::kClass == brace->fKeyWord) { brace = brace->fChildren[0]; @@ -611,70 +709,85 @@ void IncludeWriter::enumSizeItems(const Definition& child) { iterStack.emplace_back(brace->fTokens.begin(), brace->fTokens.end()); IterState* iterState = &iterStack[0]; Preprocessor preprocessor; + string enumName; while (iterState->fDefIter != iterState->fDefEnd) { auto& token = *iterState->fDefIter++; if (this->enumPreprocessor(&token, MemberPass::kCount, iterStack, &iterState, &preprocessor)) { continue; } - if (State::kItemName == state) { - TextParser enumLine(token.fFileName, lastEnd, - token.fContentStart, token.fLineCount); + if (ItemState::kName == state) { + TextParser enumLine(token.fFileName, lastEnd, token.fContentStart, token.fLineCount); const char* end = enumLine.anyOf(",}="); SkASSERT(end); - state = '=' == *end ? State::kItemValue : State::kItemComment; - if (State::kItemValue == state) { - valueLen = (int) (token.fContentEnd - token.fContentStart); + state = '=' == *end ? ItemState::kValue : ItemState::kComment; + if (ItemState::kValue == state) { lastEnd = token.fContentEnd; + lengths.fCurValue = (int) (lastEnd - token.fContentStart); continue; } } - if (State::kItemValue == state) { - TextParser valueEnd(token.fFileName, lastEnd, - token.fContentStart, token.fLineCount); + if (ItemState::kValue == state) { + TextParser valueEnd(token.fFileName, lastEnd, token.fContentStart, token.fLineCount); const char* end = valueEnd.anyOf(",}"); if (!end) { // write expression continuation - valueLen += (int) (token.fContentEnd - lastEnd); + lengths.fCurValue += (int) (token.fContentEnd - lastEnd); continue; } } - if (State::kNoItem != state) { - longestValue = SkTMax(longestValue, valueLen); - state = State::kNoItem; + if (ItemState::kNone != state) { + this->checkEnumLengths(child, enumName, &lengths); + lengths.fCurValue = 0; + state = ItemState::kNone; } - SkASSERT(State::kNoItem == state); + SkASSERT(ItemState::kNone == state); lastEnd = token.fContentEnd; - longestName = SkTMax(longestName, (int) (lastEnd - token.fContentStart)); - state = State::kItemName; + lengths.fCurName = (int) (lastEnd - token.fContentStart); + enumName = string(token.fContentStart, lengths.fCurName); + state = ItemState::kName; } - if (State::kItemValue == state) { - longestValue = SkTMax(longestValue, valueLen); + if (ItemState::kNone != state) { + this->checkEnumLengths(child, enumName, &lengths); } - fEnumItemValueTab = longestName + fIndent + 1 /* space before = */ ; - if (longestValue) { - longestValue += 3; /* = space , */ + fEnumItemValueTab = lengths.fLongestName + fIndent + 1 /* 1: , */ ; + if (lengths.fLongestValue) { + lengths.fLongestValue += 3; // 3: space = space } - fEnumItemCommentTab = fEnumItemValueTab + longestValue + 1 /* space before //!< */ ; + fEnumItemCommentTab = fEnumItemValueTab + lengths.fLongestValue + 1 ; // 1: space before //!< // iterate through bmh children and see which comments fit on include lines - for (auto& enumItem : fEnumDef->fChildren) { - if (MarkType::kConst != enumItem->fMarkType) { + if (!this->checkChildCommentLength(fEnumDef, MarkType::kConst)) { + fEnumDef->reportError<void>("expected at least one #Const in #Enum"); + } +} + +const Definition* IncludeWriter::matchMemberName(string matchName, const Definition& child) const { + const Definition* parent = &child; + if (KeyWord::kEnum == child.fKeyWord && child.fChildren.size() > 0 + && KeyWord::kClass == child.fChildren[0]->fKeyWord) { + matchName = child.fChildren[0]->fName + "::" + matchName; + } + do { + if (KeyWord::kStruct == parent->fKeyWord || KeyWord::kClass == parent->fKeyWord) { + matchName = parent->fName + "::" + matchName; + } + } while ((parent = parent->fParent)); + const Definition* enumItem = nullptr; + for (auto testItem : fEnumDef->fChildren) { + if (MarkType::kConst != testItem->fMarkType) { continue; } - TextParser enumLine(enumItem); - enumLine.trimEnd(); - enumLine.skipToLineStart(); // skip const value - const char* commentStart = enumLine.fChar; - enumLine.skipLine(); - ptrdiff_t lineLen = enumLine.fChar - commentStart + 5 /* //!< space */ ; - if (!enumLine.eof()) { - enumLine.skipWhiteSpace(); + if (matchName != testItem->fName) { + continue; } - enumItem->fShort = enumLine.eof() && fEnumItemCommentTab + lineLen < 100; + enumItem = testItem; + break; } + SkASSERT(enumItem); + return enumItem; } // walk children and output complete method doxygen description -void IncludeWriter::methodOut(const Definition* method, const Definition& child) { +void IncludeWriter::methodOut(Definition* method, const Definition& child) { if (fPendingMethod) { fIndent -= 4; fPendingMethod = false; @@ -683,7 +796,7 @@ void IncludeWriter::methodOut(const Definition* method, const Definition& child) fMethodDef = &child; fContinuation = nullptr; fDeferComment = nullptr; - const Definition* csParent = method->csParent(); + Definition* csParent = method->csParent(); if (csParent && (0 == fIndent || fIndentNext)) { fIndent += 4; fIndentNext = false; @@ -745,7 +858,7 @@ void IncludeWriter::structOut(const Definition* root, const Definition& child, fIndent += 4; this->lfcr(); if (child.fDeprecated) { - this->writeString(child.fToBeDeprecated ? "to be deprecated soon" : "deprecated"); + this->writeString(child.incompleteMessage(Definition::DetailsType::kSentence)); } else { this->rewriteBlock((int)(commentEnd - commentStart), commentStart, Phrase::kNo); } @@ -805,6 +918,7 @@ Definition* IncludeWriter::structMemberOut(const Definition* memberStart, const if (!commentBlock) { return memberStart->reportError<Definition*>("member missing comment block"); } +#if 0 if (!commentBlock->fShort) { const char* commentStart = commentBlock->fContentStart; ptrdiff_t commentLen = commentBlock->fContentEnd - commentStart; @@ -831,6 +945,7 @@ Definition* IncludeWriter::structMemberOut(const Definition* memberStart, const } this->writeCommentTrailer(); } +#endif this->lfcr(); this->writeBlock((int) (child.fStart - memberStart->fContentStart), memberStart->fContentStart); @@ -852,7 +967,7 @@ Definition* IncludeWriter::structMemberOut(const Definition* memberStart, const valueStart->fContentStart); } this->writeString(";"); - if (commentBlock->fShort) { + /* if (commentBlock->fShort) */ { this->indentToColumn(fStructCommentTab); this->writeString("//!<"); this->writeSpace(); @@ -863,29 +978,6 @@ Definition* IncludeWriter::structMemberOut(const Definition* memberStart, const return valueEnd; } -// iterate through bmh children and see which comments fit on include lines -void IncludeWriter::structSetMembersShort(const vector<Definition*>& bmhChildren) { - for (auto memberDef : bmhChildren) { - if (MarkType::kMember != memberDef->fMarkType) { - continue; - } - string extract = fBmhParser->extractText(memberDef, BmhParser::TrimExtract::kYes); - bool multiline = string::npos != extract.find('\n'); - if (multiline) { - memberDef->fShort = false; - } else { - ptrdiff_t lineLen = extract.length() + 5 /* //!< space */ ; - memberDef->fShort = fStructCommentTab + lineLen < 100; - } - } - for (auto memberDef : bmhChildren) { - if (MarkType::kSubtopic != memberDef->fMarkType && MarkType::kTopic != memberDef->fMarkType) { - continue; - } - this->structSetMembersShort(memberDef->fChildren); - } -} - void IncludeWriter::structSizeMembers(const Definition& child) { int longestType = 0; Definition* typeStart = nullptr; @@ -996,8 +1088,9 @@ void IncludeWriter::structSizeMembers(const Definition& child) { fStructCommentTab += longestValue + 3 /* space = space */ ; fStructValueTab -= 1 /* ; */ ; } - // iterate through bmh children and see which comments fit on include lines - this->structSetMembersShort(fBmhStructDef->fChildren); + // iterate through struct to ensure that members' comments fit on line + // struct or class may not have any members + (void) this->checkChildCommentLength(fBmhStructDef, MarkType::kMember); } static bool find_start(const Definition* startDef, const char* start) { @@ -1026,8 +1119,8 @@ bool IncludeWriter::populate(Definition* def, ParentPair* prevPair, RootDefiniti // skip include comment // if there is a series of same named methods, write one set of comments, then write all methods string methodName; - const Definition* method = nullptr; - const Definition* clonedMethod = nullptr; + Definition* method = nullptr; + Definition* clonedMethod = nullptr; const Definition* memberStart = nullptr; const Definition* memberEnd = nullptr; fContinuation = nullptr; @@ -1285,8 +1378,8 @@ bool IncludeWriter::populate(Definition* def, ParentPair* prevPair, RootDefiniti if (fInStruct) { // try child; root+child; root->parent+child; etc. int trial = 0; - const RootDefinition* search = root; - const Definition* parent = search->fParent; + RootDefinition* search = root; + Definition* parent = search->fParent; do { string name; if (0 == trial) { @@ -1301,7 +1394,7 @@ bool IncludeWriter::populate(Definition* def, ParentPair* prevPair, RootDefiniti } fBmhStructDef = search->find(name, RootDefinition::AllowParens::kNo); } while (!fBmhStructDef && ++trial); - root = const_cast<RootDefinition*>(fBmhStructDef->asRoot()); + root = fBmhStructDef->asRoot(); SkASSERT(root); fIndent += 4; this->structSizeMembers(child); @@ -1472,7 +1565,7 @@ bool IncludeWriter::populate(Definition* def, ParentPair* prevPair, RootDefiniti (KeyWord::kClass == child.fParent->fKeyWord && child.fParent->fParent && KeyWord::kEnum == child.fParent->fParent->fKeyWord)) { SkASSERT(Bracket::kBrace == child.fBracket); - this->enumMembersOut(root, child); + this->enumMembersOut(*child.fParent); this->writeString("};"); this->lf(2); startDef = child.fParent; @@ -2095,6 +2188,7 @@ IncludeWriter::Wrote IncludeWriter::rewriteBlock(int size, const char* data, Phr case 'z': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': + case '%': // to do : ensure that preceding is a number case '-': switch (word) { case Word::kStart: 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); } diff --git a/tools/bookmaker/parserCommon.cpp b/tools/bookmaker/parserCommon.cpp index 072c996df3..0ce45dc1ff 100644 --- a/tools/bookmaker/parserCommon.cpp +++ b/tools/bookmaker/parserCommon.cpp @@ -154,6 +154,7 @@ bool ParserCommon::writeBlockTrim(int size, const char* data) { debug_out(size, data); } fprintf(fOut, "%.*s", size, data); + fWroteSomething = true; int added = 0; fLastChar = data[size - 1]; while (size > 0 && '\n' != data[--size]) { diff --git a/tools/bookmaker/spellCheck.cpp b/tools/bookmaker/spellCheck.cpp index 488ceacc7d..be17855f9b 100644 --- a/tools/bookmaker/spellCheck.cpp +++ b/tools/bookmaker/spellCheck.cpp @@ -42,12 +42,17 @@ private: kColumn, }; + enum class PrintCheck { + kWordsOnly, + kAllowNumbers, + }; + bool check(Definition* ); bool checkable(MarkType markType); - void childCheck(const Definition* def, const char* start); + void childCheck(Definition* def, const char* start); void leafCheck(const char* start, const char* end); bool parseFromFile(const char* path) override { return true; } - void printCheck(string str); + void printCheck(string str, PrintCheck); void reset() override { INHERITED::resetCommon(); @@ -168,7 +173,7 @@ bool SpellCheck::check(Definition* def) { case MarkType::kDescription: fInDescription = true; break; - case MarkType::kDoxygen: + case MarkType::kDetails: break; case MarkType::kDuration: break; @@ -182,8 +187,6 @@ bool SpellCheck::check(Definition* def) { break; case MarkType::kExternal: break; - case MarkType::kFile: - break; case MarkType::kFormula: fInFormula = true; break; @@ -223,6 +226,8 @@ bool SpellCheck::check(Definition* def) { } break; case MarkType::kNoExample: break; + case MarkType::kNoJustify: + break; case MarkType::kOutdent: break; case MarkType::kParam: { @@ -238,7 +243,13 @@ bool SpellCheck::check(Definition* def) { fInCode = true; this->wordCheck(paramParser.fChar - paramName, paramName); fInCode = false; - } break; + } break; + case MarkType::kPhraseDef: + break; + case MarkType::kPhraseParam: + break; + case MarkType::kPhraseRef: + break; case MarkType::kPlatform: break; case MarkType::kPopulate: @@ -271,7 +282,10 @@ bool SpellCheck::check(Definition* def) { case MarkType::kSubstitute: break; case MarkType::kSubtopic: - this->printCheck(printable); + // TODO: add a tag that allows subtopic labels in illustrations to skip spellcheck? + if (string::npos == fFileName.find("illustrations.bmh")) { + this->printCheck(printable, PrintCheck::kAllowNumbers); + } break; case MarkType::kTable: break; @@ -279,16 +293,11 @@ bool SpellCheck::check(Definition* def) { break; case MarkType::kText: break; - case MarkType::kTime: - break; case MarkType::kToDo: break; case MarkType::kTopic: - this->printCheck(printable); + this->printCheck(printable, PrintCheck::kWordsOnly); break; - case MarkType::kTrack: - // don't output children - return true; case MarkType::kTypedef: break; case MarkType::kUnion: @@ -348,11 +357,11 @@ bool SpellCheck::checkable(MarkType markType) { return BmhParser::Resolvable::kYes == fBmhParser.kMarkProps[(int) markType].fResolve; } -void SpellCheck::childCheck(const Definition* def, const char* start) { +void SpellCheck::childCheck(Definition* def, const char* start) { const char* end; fLineCount = def->fLineCount; if (def->isRoot()) { - fRoot = const_cast<RootDefinition*>(def->asRoot()); + fRoot = def->asRoot(); } for (auto& child : def->fChildren) { end = child->fStart; @@ -459,9 +468,16 @@ void SpellCheck::leafCheck(const char* start, const char* end) { } while (++chPtr <= end); } -void SpellCheck::printCheck(string str) { +void SpellCheck::printCheck(string str, PrintCheck allowed) { string word; for (std::stringstream stream(str); stream >> word; ) { + if (PrintCheck::kAllowNumbers == allowed && (std::isdigit(word.back()) || 'x' == word.back())) { + // allow ###x for RGB_888x + if ((size_t) std::count_if(word.begin(), word.end() - 1, + [](unsigned char c){ return std::isdigit(c); } ) == word.length() - 1) { + continue; + } + } wordCheck(word); } } |