Home | History | Annotate | Download | only in bookmaker
      1 /*
      2  * Copyright 2017 Google Inc.
      3  *
      4  * Use of this source code is governed by a BSD-style license that can be
      5  * found in the LICENSE file.
      6  */
      7 
      8 #include "bookmaker.h"
      9 
     10 #include "SkOSFile.h"
     11 #include "SkOSPath.h"
     12 
     13 #define FPRINTF(...)                \
     14     if (fDebugOut) {                \
     15         SkDebugf(__VA_ARGS__);      \
     16     }                               \
     17     fprintf(fOut, __VA_ARGS__)
     18 
     19 static void add_ref(const string& leadingSpaces, const string& ref, string* result) {
     20     *result += leadingSpaces + ref;
     21 }
     22 
     23 static string preformat(const string& orig) {
     24     string result;
     25     for (auto c : orig) {
     26         if ('<' == c) {
     27           result += "&lt;";
     28         } else if ('>' == c) {
     29           result += "&gt;";
     30         } else {
     31             result += c;
     32         }
     33     }
     34     return result;
     35 }
     36 
     37 static bool all_lower(const string& ref) {
     38 	for (auto ch : ref) {
     39 		if (!islower(ch)) {
     40 			return false;
     41 		}
     42 	}
     43 	return true;
     44 }
     45 
     46 // FIXME: preserve inter-line spaces and don't add new ones
     47 string MdOut::addReferences(const char* refStart, const char* refEnd,
     48         BmhParser::Resolvable resolvable) {
     49     string result;
     50     MethodParser t(fRoot ? fRoot->fName : string(), fFileName, refStart, refEnd, fLineCount);
     51     bool lineStart = true;
     52     string ref;
     53     string leadingSpaces;
     54     int distFromParam = 99;
     55     do {
     56         ++distFromParam;
     57         const char* base = t.fChar;
     58         t.skipWhiteSpace();
     59         const char* wordStart = t.fChar;
     60         t.skipToMethodStart();
     61         const char* start = t.fChar;
     62         if (wordStart < start) {
     63             if (lineStart) {
     64                 lineStart = false;
     65             } else {
     66                 wordStart = base;
     67             }
     68             result += string(wordStart, start - wordStart);
     69             if ('\n' != result.back()) {
     70                 while (start > wordStart && '\n' == start[-1]) {
     71                     result += '\n';
     72                     --start;
     73                 }
     74             }
     75         }
     76         if (lineStart) {
     77             lineStart = false;
     78         } else {
     79             leadingSpaces = string(base, wordStart - base);
     80         }
     81         t.skipToMethodEnd();
     82         if (base == t.fChar) {
     83             if (!t.eof() && '~' == base[0] && !isalnum(base[1])) {
     84                 t.next();
     85             } else {
     86                 break;
     87             }
     88         }
     89         if (start >= t.fChar) {
     90             continue;
     91         }
     92         if (!t.eof() && '"' == t.peek() && start > wordStart && '"' == start[-1]) {
     93             continue;
     94         }
     95         ref = string(start, t.fChar - start);
     96         if (const Definition* def = this->isDefined(t, ref,
     97                 BmhParser::Resolvable::kOut != resolvable)) {
     98             if (MarkType::kExternal == def->fMarkType) {
     99                 add_ref(leadingSpaces, ref, &result);
    100                 continue;
    101             }
    102             SkASSERT(def->fFiddle.length());
    103             if (!t.eof() && '(' == t.peek() && t.strnchr(')', t.fEnd)) {
    104                 if (!t.skipToEndBracket(')')) {
    105                     t.reportError("missing close paren");
    106                     return result;
    107                 }
    108                 t.next();
    109                 string fullRef = string(start, t.fChar - start);
    110                 // if _2 etc alternates are defined, look for paren match
    111                 // may ignore () if ref is all lower case
    112                 // otherwise flag as error
    113                 int suffix = '2';
    114                 bool foundMatch = false;
    115                 const Definition* altDef = def;
    116                 while (altDef && suffix <= '9') {
    117                     if ((foundMatch = altDef->paramsMatch(fullRef, ref))) {
    118                         def = altDef;
    119                         ref = fullRef;
    120                         break;
    121                     }
    122                     string altTest = ref + '_';
    123                     altTest += suffix++;
    124                     altDef = this->isDefined(t, altTest, false);
    125                 }
    126                 if (suffix > '9') {
    127                     t.reportError("too many alts");
    128                     return result;
    129                 }
    130                 if (!foundMatch) {
    131                     if (!(def = this->isDefined(t, fullRef,
    132                             BmhParser::Resolvable::kOut != resolvable))) {
    133                         if (!result.size()) {
    134                             t.reportError("missing method");
    135                         }
    136                         return result;
    137                     }
    138                     ref = fullRef;
    139                 }
    140 			} else if (BmhParser::Resolvable::kClone != resolvable &&
    141 					all_lower(ref) && (t.eof() || '(' != t.peek())) {
    142 				add_ref(leadingSpaces, ref, &result);
    143 				continue;
    144 			}
    145 			result += linkRef(leadingSpaces, def, ref, resolvable);
    146             continue;
    147         }
    148         if (!t.eof() && '(' == t.peek()) {
    149             if (!t.skipToEndBracket(')')) {
    150                 t.reportError("missing close paren");
    151                 return result;
    152             }
    153             t.next();
    154             ref = string(start, t.fChar - start);
    155             if (const Definition* def = this->isDefined(t, ref, true)) {
    156                 SkASSERT(def->fFiddle.length());
    157 				result += linkRef(leadingSpaces, def, ref, resolvable);
    158                 continue;
    159             }
    160         }
    161 // class, struct, and enum start with capitals
    162 // methods may start with upper (static) or lower (most)
    163 
    164         // see if this should have been a findable reference
    165 
    166             // look for Sk / sk / SK ..
    167         if (!ref.compare(0, 2, "Sk") && ref != "Skew" && ref != "Skews" &&
    168               ref != "Skip" && ref != "Skips") {
    169             t.reportError("missed Sk prefixed");
    170             return result;
    171         }
    172         if (!ref.compare(0, 2, "SK")) {
    173             if (BmhParser::Resolvable::kOut != resolvable) {
    174                 t.reportError("missed SK prefixed");
    175             }
    176             return result;
    177         }
    178         if (!isupper(start[0])) {
    179             // TODO:
    180             // look for all lowercase w/o trailing parens as mistaken method matches
    181             // will also need to see if Example Description matches var in example
    182             const Definition* def;
    183             if (fMethod && (def = fMethod->hasParam(ref))) {
    184 				result += linkRef(leadingSpaces, def, ref, resolvable);
    185                 fLastParam = def;
    186                 distFromParam = 0;
    187                 continue;
    188             } else if (!fInDescription && ref[0] != '0'
    189                     && string::npos != ref.find_first_of("ABCDEFGHIJKLMNOPQRSTUVWXYZ")) {
    190                 // FIXME: see isDefined(); check to see if fXX is a member of xx.fXX
    191                 if (('f' != ref[0] && string::npos == ref.find("()"))
    192 //                        || '.' != t.backup(ref.c_str())
    193                         && ('k' != ref[0] && string::npos == ref.find("_Private"))) {
    194                     if ('.' == wordStart[0] && (distFromParam >= 1 && distFromParam <= 16)) {
    195                         const Definition* paramType = this->findParamType();
    196                         if (paramType) {
    197                             string fullName = paramType->fName + "::" + ref;
    198                             if (paramType->hasMatch(fullName)) {
    199 								result += linkRef(leadingSpaces, paramType, ref, resolvable);
    200                                 continue;
    201                             }
    202                         }
    203                     }
    204                     if (BmhParser::Resolvable::kOut != resolvable) {
    205                         t.reportError("missed camelCase");
    206                         return result;
    207                     }
    208                 }
    209             }
    210             add_ref(leadingSpaces, ref, &result);
    211             continue;
    212         }
    213         auto topicIter = fBmhParser.fTopicMap.find(ref);
    214         if (topicIter != fBmhParser.fTopicMap.end()) {
    215 			result += linkRef(leadingSpaces, topicIter->second, ref, resolvable);
    216             continue;
    217         }
    218         bool startsSentence = t.sentenceEnd(start);
    219         if (!t.eof() && ' ' != t.peek()) {
    220 			add_ref(leadingSpaces, ref, &result);
    221             continue;
    222         }
    223         if (t.fChar + 1 >= t.fEnd || (!isupper(t.fChar[1]) && startsSentence)) {
    224 			add_ref(leadingSpaces, ref, &result);
    225             continue;
    226         }
    227         if (isupper(t.fChar[1]) && startsSentence) {
    228             TextParser next(t.fFileName, &t.fChar[1], t.fEnd, t.fLineCount);
    229             string nextWord(next.fChar, next.wordEnd() - next.fChar);
    230             if (this->isDefined(t, nextWord, true)) {
    231 				add_ref(leadingSpaces, ref, &result);
    232                 continue;
    233             }
    234         }
    235         const Definition* test = fRoot;
    236         do {
    237             if (!test->isRoot()) {
    238                 continue;
    239             }
    240             for (string prefix : { "_", "::" } ) {
    241                 const RootDefinition* root = test->asRoot();
    242                 string prefixed = root->fName + prefix + ref;
    243                 if (const Definition* def = root->find(prefixed,
    244                         RootDefinition::AllowParens::kYes)) {
    245 					result += linkRef(leadingSpaces, def, ref, resolvable);
    246                     goto found;
    247                 }
    248             }
    249         } while ((test = test->fParent));
    250     found:
    251         if (!test) {
    252             if (BmhParser::Resolvable::kOut != resolvable) {
    253                 t.reportError("undefined reference");
    254             }
    255         }
    256     } while (!t.eof());
    257     return result;
    258 }
    259 
    260 
    261 
    262 bool MdOut::buildReferences(const char* docDir, const char* mdFileOrPath) {
    263     if (!sk_isdir(mdFileOrPath)) {
    264         SkString mdFile = SkOSPath::Basename(mdFileOrPath);
    265         SkString bmhFile = SkOSPath::Join(docDir, mdFile.c_str());
    266         bmhFile.remove(bmhFile.size() - 3, 3);
    267         bmhFile += ".bmh";
    268         SkString mdPath = SkOSPath::Dirname(mdFileOrPath);
    269         if (!this->buildRefFromFile(bmhFile.c_str(), mdPath.c_str())) {
    270             SkDebugf("failed to parse %s\n", mdFileOrPath);
    271             return false;
    272         }
    273     } else {
    274         SkOSFile::Iter it(docDir, ".bmh");
    275         for (SkString file; it.next(&file); ) {
    276             SkString p = SkOSPath::Join(docDir, file.c_str());
    277             if (!this->buildRefFromFile(p.c_str(), mdFileOrPath)) {
    278                 SkDebugf("failed to parse %s\n", p.c_str());
    279                 return false;
    280             }
    281         }
    282     }
    283     return true;
    284 }
    285 
    286 bool MdOut::buildStatus(const char* statusFile, const char* outDir) {
    287     StatusIter iter(statusFile, ".bmh", StatusFilter::kInProgress);
    288     for (string file; iter.next(&file); ) {
    289         SkString p = SkOSPath::Join(iter.baseDir().c_str(), file.c_str());
    290         const char* hunk = p.c_str();
    291         if (!this->buildRefFromFile(hunk, outDir)) {
    292             SkDebugf("failed to parse %s\n", hunk);
    293             return false;
    294         }
    295     }
    296     return true;
    297 }
    298 
    299 bool MdOut::buildRefFromFile(const char* name, const char* outDir) {
    300     if (!SkStrEndsWith(name, ".bmh")) {
    301         return true;
    302     }
    303     if (SkStrEndsWith(name, "markup.bmh")) {  // don't look inside this for now
    304         return true;
    305     }
    306     if (SkStrEndsWith(name, "illustrations.bmh")) {  // don't look inside this for now
    307         return true;
    308     }
    309     fFileName = string(name);
    310     string filename(name);
    311     if (filename.substr(filename.length() - 4) == ".bmh") {
    312         filename = filename.substr(0, filename.length() - 4);
    313     }
    314     size_t start = filename.length();
    315     while (start > 0 && (isalnum(filename[start - 1]) || '_' == filename[start - 1])) {
    316         --start;
    317     }
    318     string match = filename.substr(start);
    319     string header = match;
    320     filename = match + ".md";
    321     match += ".bmh";
    322     fOut = nullptr;
    323     string fullName;
    324 
    325     vector<string> keys;
    326     keys.reserve(fBmhParser.fTopicMap.size());
    327     for (const auto& it : fBmhParser.fTopicMap) {
    328         keys.push_back(it.first);
    329     }
    330     std::sort(keys.begin(), keys.end());
    331     for (auto key : keys) {
    332         string s(key);
    333         auto topicDef = fBmhParser.fTopicMap.at(s);
    334         if (topicDef->fParent) {
    335             continue;
    336         }
    337         if (!topicDef->isRoot()) {
    338             return this->reportError<bool>("expected root topic");
    339         }
    340         fRoot = topicDef->asRoot();
    341         if (string::npos == fRoot->fFileName.rfind(match)) {
    342             continue;
    343         }
    344         if (!fOut) {
    345             fullName = outDir;
    346             if ('/' != fullName.back()) {
    347                 fullName += '/';
    348             }
    349             fullName += filename;
    350             fOut = fopen(filename.c_str(), "wb");
    351             if (!fOut) {
    352                 SkDebugf("could not open output file %s\n", fullName.c_str());
    353                 return false;
    354             }
    355             size_t underscorePos = header.find('_');
    356             if (string::npos != underscorePos) {
    357                 header.replace(underscorePos, 1, " ");
    358             }
    359             SkASSERT(string::npos == header.find('_'));
    360             FPRINTF("%s", header.c_str());
    361             this->lfAlways(1);
    362             FPRINTF("===");
    363         }
    364         fPopulators.clear();
    365         fPopulators[kClassesAndStructs].fDescription = "embedded struct and class members";
    366         fPopulators[kConstants].fDescription = "enum and enum class, const values";
    367         fPopulators[kConstructors].fDescription = "functions that construct";
    368         fPopulators[kMemberFunctions].fDescription = "static functions and member methods";
    369         fPopulators[kMembers].fDescription = "member values";
    370         fPopulators[kOperators].fDescription = "operator overloading methods";
    371         fPopulators[kRelatedFunctions].fDescription = "similar methods grouped together";
    372         fPopulators[kSubtopics].fDescription = "";
    373         this->populateTables(fRoot);
    374         this->markTypeOut(topicDef);
    375     }
    376     if (fOut) {
    377         this->writePending();
    378         fclose(fOut);
    379         fflush(fOut);
    380         if (this->writtenFileDiffers(filename, fullName)) {
    381             fOut = fopen(fullName.c_str(), "wb");
    382             int writtenSize;
    383             const char* written = ReadToBuffer(filename, &writtenSize);
    384             fwrite(written, 1, writtenSize, fOut);
    385             fclose(fOut);
    386             fflush(fOut);
    387             SkDebugf("wrote updated %s\n", fullName.c_str());
    388         }
    389         remove(filename.c_str());
    390         fOut = nullptr;
    391     }
    392     return true;
    393 }
    394 
    395 bool MdOut::checkParamReturnBody(const Definition* def) const {
    396     TextParser paramBody(def);
    397     const char* descriptionStart = paramBody.fChar;
    398     if (!islower(descriptionStart[0]) && !isdigit(descriptionStart[0])) {
    399         paramBody.skipToNonAlphaNum();
    400         string ref = string(descriptionStart, paramBody.fChar - descriptionStart);
    401         if (!this->isDefined(paramBody, ref, true)) {
    402             string errorStr = MarkType::kReturn == def->fMarkType ? "return" : "param";
    403             errorStr += " description must start with lower case";
    404             paramBody.reportError(errorStr.c_str());
    405             return false;
    406         }
    407     }
    408     if ('.' == paramBody.fEnd[-1]) {
    409         paramBody.reportError("make param description a phrase; should not end with period");
    410         return false;
    411     }
    412     return true;
    413 }
    414 
    415 void MdOut::childrenOut(const Definition* def, const char* start) {
    416     const char* end;
    417     fLineCount = def->fLineCount;
    418     if (def->isRoot()) {
    419         fRoot = const_cast<RootDefinition*>(def->asRoot());
    420     } else if (MarkType::kEnumClass == def->fMarkType) {
    421         fEnumClass = def;
    422     }
    423     BmhParser::Resolvable resolvable = this->resolvable(def);
    424     for (auto& child : def->fChildren) {
    425         end = child->fStart;
    426         if (BmhParser::Resolvable::kNo != resolvable) {
    427             this->resolveOut(start, end, resolvable);
    428         }
    429         this->markTypeOut(child);
    430         start = child->fTerminator;
    431     }
    432     if (BmhParser::Resolvable::kNo != resolvable) {
    433         end = def->fContentEnd;
    434         this->resolveOut(start, end, resolvable);
    435     }
    436     if (MarkType::kEnumClass == def->fMarkType) {
    437         fEnumClass = nullptr;
    438     }
    439 }
    440 
    441 const Definition* MdOut::csParent() const {
    442     const Definition* csParent = fRoot->csParent();
    443     if (!csParent) {
    444         const Definition* topic = fRoot;
    445         while (topic && MarkType::kTopic != topic->fMarkType) {
    446             topic = topic->fParent;
    447         }
    448         for (auto child : topic->fChildren) {
    449             if (child->isStructOrClass() || MarkType::kTypedef == child->fMarkType) {
    450                 csParent = child;
    451                 break;
    452             }
    453         }
    454         SkASSERT(csParent || string::npos == fRoot->fFileName.find("Sk"));
    455     }
    456     return csParent;
    457 }
    458 
    459 const Definition* MdOut::findParamType() {
    460     SkASSERT(fMethod);
    461     TextParser parser(fMethod->fFileName, fMethod->fStart, fMethod->fContentStart,
    462             fMethod->fLineCount);
    463     string lastFull;
    464     do {
    465         parser.skipToAlpha();
    466         if (parser.eof()) {
    467             return nullptr;
    468         }
    469         const char* word = parser.fChar;
    470         parser.skipFullName();
    471         SkASSERT(!parser.eof());
    472         string name = string(word, parser.fChar - word);
    473         if (fLastParam->fName == name) {
    474             const Definition* paramType = this->isDefined(parser, lastFull, false);
    475             return paramType;
    476         }
    477         if (isupper(name[0])) {
    478             lastFull = name;
    479         }
    480     } while (true);
    481     return nullptr;
    482 }
    483 
    484 const Definition* MdOut::isDefined(const TextParser& parser, const string& ref, bool report) const {
    485     auto rootIter = fBmhParser.fClassMap.find(ref);
    486     if (rootIter != fBmhParser.fClassMap.end()) {
    487         return &rootIter->second;
    488     }
    489     auto typedefIter = fBmhParser.fTypedefMap.find(ref);
    490     if (typedefIter != fBmhParser.fTypedefMap.end()) {
    491         return &typedefIter->second;
    492     }
    493     auto enumIter = fBmhParser.fEnumMap.find(ref);
    494     if (enumIter != fBmhParser.fEnumMap.end()) {
    495         return &enumIter->second;
    496     }
    497     auto constIter = fBmhParser.fConstMap.find(ref);
    498     if (constIter != fBmhParser.fConstMap.end()) {
    499         return &constIter->second;
    500     }
    501     auto methodIter = fBmhParser.fMethodMap.find(ref);
    502     if (methodIter != fBmhParser.fMethodMap.end()) {
    503         return &methodIter->second;
    504     }
    505     auto aliasIter = fBmhParser.fAliasMap.find(ref);
    506     if (aliasIter != fBmhParser.fAliasMap.end()) {
    507         return aliasIter->second;
    508     }
    509     for (const auto& external : fBmhParser.fExternals) {
    510         if (external.fName == ref) {
    511             return &external;
    512         }
    513     }
    514     if (fRoot) {
    515         if (ref == fRoot->fName) {
    516             return fRoot;
    517         }
    518         if (const Definition* definition = fRoot->find(ref, RootDefinition::AllowParens::kYes)) {
    519             return definition;
    520         }
    521         const Definition* test = fRoot;
    522         do {
    523             if (!test->isRoot()) {
    524                 continue;
    525             }
    526             const RootDefinition* root = test->asRoot();
    527             for (auto& leaf : root->fBranches) {
    528                 if (ref == leaf.first) {
    529                     return leaf.second;
    530                 }
    531                 const Definition* definition = leaf.second->find(ref,
    532                         RootDefinition::AllowParens::kYes);
    533                 if (definition) {
    534                     return definition;
    535                 }
    536             }
    537             for (string prefix : { "::", "_" } ) {
    538                 string prefixed = root->fName + prefix + ref;
    539                 if (const Definition* definition = root->find(prefixed,
    540                         RootDefinition::AllowParens::kYes)) {
    541                     return definition;
    542                 }
    543                 if (isupper(prefixed[0])) {
    544                     auto topicIter = fBmhParser.fTopicMap.find(prefixed);
    545                     if (topicIter != fBmhParser.fTopicMap.end()) {
    546                         return topicIter->second;
    547                     }
    548                 }
    549             }
    550             string fiddlePrefixed = root->fFiddle + "_" + ref;
    551             auto topicIter = fBmhParser.fTopicMap.find(fiddlePrefixed);
    552             if (topicIter != fBmhParser.fTopicMap.end()) {
    553                 return topicIter->second;
    554             }
    555         } while ((test = test->fParent));
    556     }
    557     size_t doubleColon = ref.find("::");
    558     if (string::npos != doubleColon) {
    559         string className = ref.substr(0, doubleColon);
    560         auto classIter = fBmhParser.fClassMap.find(className);
    561         if (classIter != fBmhParser.fClassMap.end()) {
    562             const RootDefinition& classDef = classIter->second;
    563             const Definition* result = classDef.find(ref, RootDefinition::AllowParens::kYes);
    564             if (result) {
    565                 return result;
    566             }
    567         }
    568 
    569     }
    570     if (!ref.compare(0, 2, "SK") || !ref.compare(0, 3, "sk_")
    571             || (('k' == ref[0] || 'g' == ref[0] || 'f' == ref[0]) &&
    572                 ref.length() > 1 && isupper(ref[1]))) {
    573         // try with a prefix
    574         if ('k' == ref[0]) {
    575             for (auto const& iter : fBmhParser.fEnumMap) {
    576                 auto def = iter.second.find(ref, RootDefinition::AllowParens::kYes);
    577                 if (def) {
    578                     return def;
    579                 }
    580             }
    581             if (fEnumClass) {
    582                 string fullName = fEnumClass->fName + "::" + ref;
    583                 for (auto child : fEnumClass->fChildren) {
    584                     if (fullName == child->fName) {
    585                         return child;
    586                     }
    587                 }
    588             }
    589             if (string::npos != ref.find("_Private")) {
    590                 return nullptr;
    591             }
    592         }
    593         if ('f' == ref[0]) {
    594             // FIXME : find def associated with prior, e.g.: r.fX where 'SkPoint r' was earlier
    595                 // need to have pushed last resolve on stack to do this
    596                 // for now, just try to make sure that it's there and error if not
    597             if ('.' != parser.backup(ref.c_str())) {
    598                 parser.reportError("fX member undefined");
    599                 return nullptr;
    600             }
    601         } else {
    602             if (report) {
    603                 parser.reportError("SK undefined");
    604             }
    605             return nullptr;
    606         }
    607     }
    608     if (isupper(ref[0])) {
    609         auto topicIter = fBmhParser.fTopicMap.find(ref);
    610         if (topicIter != fBmhParser.fTopicMap.end()) {
    611             return topicIter->second;
    612         }
    613         size_t pos = ref.find('_');
    614         if (string::npos != pos) {
    615             // see if it is defined by another base class
    616             string className(ref, 0, pos);
    617             auto classIter = fBmhParser.fClassMap.find(className);
    618             if (classIter != fBmhParser.fClassMap.end()) {
    619                 if (const Definition* definition = classIter->second.find(ref,
    620                         RootDefinition::AllowParens::kYes)) {
    621                     return definition;
    622                 }
    623             }
    624             auto enumIter = fBmhParser.fEnumMap.find(className);
    625             if (enumIter != fBmhParser.fEnumMap.end()) {
    626                 if (const Definition* definition = enumIter->second.find(ref,
    627                         RootDefinition::AllowParens::kYes)) {
    628                     return definition;
    629                 }
    630             }
    631             if (report) {
    632                 parser.reportError("_ undefined");
    633             }
    634             return nullptr;
    635         }
    636     }
    637     return nullptr;
    638 }
    639 
    640 string MdOut::linkName(const Definition* ref) const {
    641     string result = ref->fName;
    642     size_t under = result.find('_');
    643     if (string::npos != under) {
    644         string classPart = result.substr(0, under);
    645         string namePart = result.substr(under + 1, result.length());
    646         if (fRoot && (fRoot->fName == classPart
    647                 || (fRoot->fParent && fRoot->fParent->fName == classPart))) {
    648             result = namePart;
    649         }
    650     }
    651     return result;
    652 }
    653 
    654 // for now, hard-code to html links
    655 // def should not include SkXXX_
    656 string MdOut::linkRef(const string& leadingSpaces, const Definition* def,
    657         const string& ref, BmhParser::Resolvable resolvable) const {
    658     string buildup;
    659     string refName;
    660     const string* str = &def->fFiddle;
    661     SkASSERT(str->length() > 0);
    662     string classPart = *str;
    663     bool globalEnumMember = false;
    664     if (MarkType::kAlias == def->fMarkType) {
    665         def = def->fParent;
    666         SkASSERT(def);
    667         SkASSERT(MarkType::kSubtopic == def->fMarkType ||MarkType::kTopic == def->fMarkType);
    668     }
    669     if (MarkType::kSubtopic == def->fMarkType) {
    670         const Definition* topic = def->topicParent();
    671         SkASSERT(topic);
    672         classPart = topic->fName;
    673         refName = def->fName;
    674     } else if (MarkType::kTopic == def->fMarkType) {
    675         refName = def->fName;
    676     } else {
    677         if ('k' == (*str)[0] && string::npos != str->find("_Sk")) {
    678             globalEnumMember = true;
    679         } else {
    680             SkASSERT("Sk" == str->substr(0, 2) || "SK" == str->substr(0, 2)
    681                     // FIXME: kitchen sink catch below, need to do better
    682                     || string::npos != def->fFileName.find("undocumented"));
    683             size_t under = str->find('_');
    684             classPart = string::npos != under ? str->substr(0, under) : *str;
    685         }
    686         refName = def->fFiddle;
    687     }
    688     bool classMatch = fRoot->fFileName == def->fFileName;
    689     SkASSERT(fRoot);
    690     SkASSERT(fRoot->fFileName.length());
    691     if (!classMatch) {
    692         string filename = def->fFileName;
    693         if (filename.substr(filename.length() - 4) == ".bmh") {
    694             filename = filename.substr(0, filename.length() - 4);
    695         }
    696         size_t start = filename.length();
    697         while (start > 0 && (isalnum(filename[start - 1]) || '_' == filename[start - 1])) {
    698             --start;
    699         }
    700         buildup = filename.substr(start);
    701     }
    702     buildup += "#" + refName;
    703     if (MarkType::kParam == def->fMarkType) {
    704         const Definition* parent = def->fParent;
    705         SkASSERT(MarkType::kMethod == parent->fMarkType);
    706         buildup = '#' + parent->fFiddle + '_' + ref;
    707     }
    708     string refOut(ref);
    709     if (!globalEnumMember) {
    710         std::replace(refOut.begin(), refOut.end(), '_', ' ');
    711     }
    712     if (ref.length() > 2 && islower(ref[0]) && "()" == ref.substr(ref.length() - 2)) {
    713         refOut = refOut.substr(0, refOut.length() - 2);
    714     }
    715     string result = leadingSpaces + "<a href=\"" + buildup + "\">" + refOut + "</a>";
    716 	if (BmhParser::Resolvable::kClone == resolvable && MarkType::kMethod == def->fMarkType &&
    717 			def->fCloned && !def->fClone) {
    718 		bool found = false;
    719 		string match = def->fName;
    720 		if ("()" == match.substr(match.length() - 2)) {
    721 			match = match.substr(0, match.length() - 2);
    722 		}
    723 		match += '_';
    724 		auto classIter = fBmhParser.fClassMap.find(classPart);
    725 		if (fBmhParser.fClassMap.end() != classIter) {
    726 			for (char num = '2'; num <= '9'; ++num) {
    727 				string clone = match + num;
    728 				const auto& leafIter = classIter->second.fLeaves.find(clone);
    729 				if (leafIter != classIter->second.fLeaves.end()) {
    730 					result += "<sup><a href=\"" + buildup + "_" + num + "\">[" + num + "]</a></sup>";
    731 					found = true;
    732 				}
    733 			}
    734 		}
    735 		if (!found) {
    736 			SkDebugf("");  // convenient place to set a breakpoint
    737 		}
    738 	}
    739 	return result;
    740 }
    741 
    742 void MdOut::markTypeOut(Definition* def) {
    743     string printable = def->printableName();
    744     const char* textStart = def->fContentStart;
    745     if (MarkType::kParam != def->fMarkType && MarkType::kConst != def->fMarkType &&
    746             (!def->fParent || MarkType::kConst != def->fParent->fMarkType) &&
    747             TableState::kNone != fTableState) {
    748         this->writePending();
    749         FPRINTF("</table>");
    750         this->lf(2);
    751         fTableState = TableState::kNone;
    752     }
    753     switch (def->fMarkType) {
    754         case MarkType::kAlias:
    755             break;
    756         case MarkType::kAnchor: {
    757             if (fColumn > 0) {
    758                 this->writeSpace();
    759             }
    760             this->writePending();
    761             TextParser parser(def);
    762             const char* start = parser.fChar;
    763             parser.skipToEndBracket(" # ");
    764             string anchorText(start, parser.fChar - start);
    765             parser.skipExact(" # ");
    766             string anchorLink(parser.fChar, parser.fEnd - parser.fChar);
    767             FPRINTF("<a href=\"%s\">%s", anchorLink.c_str(), anchorText.c_str());
    768             } break;
    769         case MarkType::kBug:
    770             break;
    771         case MarkType::kClass:
    772             this->mdHeaderOut(1);
    773             FPRINTF("<a name=\"%s\"></a> Class %s", this->linkName(def).c_str(),
    774                     def->fName.c_str());
    775             this->lf(1);
    776             break;
    777         case MarkType::kCode:
    778             this->lfAlways(2);
    779             FPRINTF("<pre style=\"padding: 1em 1em 1em 1em;"
    780                     "width: 62.5em; background-color: #f0f0f0\">");
    781             this->lf(1);
    782             break;
    783         case MarkType::kColumn:
    784             this->writePending();
    785             if (fInList) {
    786                 FPRINTF("    <td>");
    787             } else {
    788                 FPRINTF("| ");
    789             }
    790             break;
    791         case MarkType::kComment:
    792             break;
    793         case MarkType::kConst: {
    794             if (TableState::kNone == fTableState) {
    795                 this->mdHeaderOut(3);
    796                 FPRINTF("Constants\n"
    797                         "\n"
    798                         "<table>");
    799                 fTableState = TableState::kRow;
    800                 this->lf(1);
    801             }
    802             if (TableState::kRow == fTableState) {
    803                 this->writePending();
    804                 FPRINTF("  <tr>");
    805                 this->lf(1);
    806                 fTableState = TableState::kColumn;
    807             }
    808             this->writePending();
    809             FPRINTF("    <td><a name=\"%s\"> <code><strong>%s </strong></code> </a></td>",
    810                     def->fFiddle.c_str(), def->fName.c_str());
    811             const char* lineEnd = strchr(textStart, '\n');
    812             SkASSERT(lineEnd < def->fTerminator);
    813             SkASSERT(lineEnd > textStart);
    814             SkASSERT((int) (lineEnd - textStart) == lineEnd - textStart);
    815             FPRINTF("<td>%.*s</td>", (int) (lineEnd - textStart), textStart);
    816             FPRINTF("<td>");
    817             textStart = lineEnd;
    818         } break;
    819         case MarkType::kDefine:
    820             break;
    821         case MarkType::kDefinedBy:
    822             break;
    823         case MarkType::kDeprecated:
    824             break;
    825         case MarkType::kDescription:
    826             fInDescription = true;
    827             this->writePending();
    828             FPRINTF("<div>");
    829             break;
    830         case MarkType::kDoxygen:
    831             break;
    832         case MarkType::kDuration:
    833             break;
    834         case MarkType::kEnum:
    835         case MarkType::kEnumClass:
    836             this->mdHeaderOut(2);
    837             FPRINTF("<a name=\"%s\"></a> Enum %s", def->fFiddle.c_str(), def->fName.c_str());
    838             this->lf(2);
    839             break;
    840         case MarkType::kExample: {
    841             this->mdHeaderOut(3);
    842             FPRINTF("Example\n"
    843                             "\n");
    844             fHasFiddle = true;
    845             bool showGpu = false;
    846             bool gpuAndCpu = false;
    847             const Definition* platform = def->hasChild(MarkType::kPlatform);
    848             if (platform) {
    849                 TextParser platParse(platform);
    850                 fHasFiddle = !platParse.strnstr("!fiddle", platParse.fEnd);
    851                 showGpu = platParse.strnstr("gpu", platParse.fEnd);
    852                 if (showGpu) {
    853                     gpuAndCpu = platParse.strnstr("cpu", platParse.fEnd);
    854                 }
    855             }
    856             if (fHasFiddle) {
    857                 SkASSERT(def->fHash.length() > 0);
    858                 FPRINTF("<div><fiddle-embed name=\"%s\"", def->fHash.c_str());
    859                 if (showGpu) {
    860                     FPRINTF(" gpu=\"true\"");
    861                     if (gpuAndCpu) {
    862                         FPRINTF(" cpu=\"true\"");
    863                     }
    864                 }
    865                 FPRINTF(">");
    866             } else {
    867                 SkASSERT(def->fHash.length() == 0);
    868                 FPRINTF("<pre style=\"padding: 1em 1em 1em 1em; font-size: 13px"
    869                         " width: 62.5em; background-color: #f0f0f0\">");
    870                 this->lfAlways(1);
    871                 if (def->fWrapper.length() > 0) {
    872                     FPRINTF("%s", def->fWrapper.c_str());
    873                 }
    874                 fRespectLeadingSpace = true;
    875             }
    876             } break;
    877         case MarkType::kExperimental:
    878             break;
    879         case MarkType::kExternal:
    880             break;
    881         case MarkType::kFile:
    882             break;
    883         case MarkType::kFormula:
    884             break;
    885         case MarkType::kFunction:
    886             break;
    887         case MarkType::kHeight:
    888             break;
    889         case MarkType::kIllustration: {
    890             string illustName = "Illustrations_" + def->fParent->fFiddle;
    891             auto illustIter = fBmhParser.fTopicMap.find(illustName);
    892             SkASSERT(fBmhParser.fTopicMap.end() != illustIter);
    893             Definition* illustDef = illustIter->second;
    894             SkASSERT(MarkType::kSubtopic == illustDef->fMarkType);
    895             SkASSERT(1 == illustDef->fChildren.size());
    896             Definition* illustExample = illustDef->fChildren[0];
    897             SkASSERT(MarkType::kExample == illustExample->fMarkType);
    898             string hash = illustExample->fHash;
    899             SkASSERT("" != hash);
    900             string title;
    901             this->writePending();
    902             FPRINTF("![%s](https://fiddle.skia.org/i/%s_raster.png \"%s\")",
    903                     def->fName.c_str(), hash.c_str(), title.c_str());
    904             this->lf(2);
    905         } break;
    906         case MarkType::kImage:
    907             break;
    908         case MarkType::kIn:
    909             break;
    910         case MarkType::kLegend:
    911             break;
    912         case MarkType::kLine:
    913             break;
    914         case MarkType::kLink:
    915             break;
    916         case MarkType::kList:
    917             fInList = true;
    918             this->lfAlways(2);
    919             FPRINTF("<table>");
    920             this->lf(1);
    921             break;
    922         case MarkType::kLiteral:
    923             break;
    924         case MarkType::kMarkChar:
    925             fBmhParser.fMC = def->fContentStart[0];
    926             break;
    927         case MarkType::kMember: {
    928             TextParser tp(def->fFileName, def->fStart, def->fContentStart, def->fLineCount);
    929             tp.skipExact("#Member");
    930             tp.skipWhiteSpace();
    931             const char* end = tp.trimmedBracketEnd('\n');
    932             this->lfAlways(2);
    933             FPRINTF("<a name=\"%s\"> <code><strong>%.*s</strong></code> </a>",
    934                     def->fFiddle.c_str(), (int) (end - tp.fChar), tp.fChar);
    935             this->lf(2);
    936             } break;
    937         case MarkType::kMethod: {
    938             string method_name = def->methodName();
    939             string formattedStr = def->formatFunction(Definition::Format::kIncludeReturn);
    940 
    941 			this->lfAlways(2);
    942 			FPRINTF("<a name=\"%s\"></a>", def->fFiddle.c_str());
    943 			if (!def->isClone()) {
    944                 this->mdHeaderOutLF(2, 1);
    945                 FPRINTF("%s", method_name.c_str());
    946 			}
    947 			this->lf(2);
    948 
    949             // TODO: put in css spec that we can define somewhere else (if markup supports that)
    950             // TODO: 50em below should match limit = 80 in formatFunction()
    951             this->writePending();
    952             string preformattedStr = preformat(formattedStr);
    953             FPRINTF("<pre style=\"padding: 1em 1em 1em 1em;"
    954                                     "width: 62.5em; background-color: #f0f0f0\">\n"
    955                             "%s\n"
    956                             "</pre>",  preformattedStr.c_str());
    957             this->lf(2);
    958             fTableState = TableState::kNone;
    959             fMethod = def;
    960             } break;
    961         case MarkType::kNoExample:
    962             break;
    963         case MarkType::kOutdent:
    964             break;
    965         case MarkType::kParam: {
    966             if (TableState::kNone == fTableState) {
    967                 this->mdHeaderOut(3);
    968                 fprintf(fOut,
    969                         "Parameters\n"
    970                         "\n"
    971                         "<table>"
    972                         );
    973                 this->lf(1);
    974                 fTableState = TableState::kRow;
    975             }
    976             if (TableState::kRow == fTableState) {
    977                 FPRINTF("  <tr>");
    978                 this->lf(1);
    979                 fTableState = TableState::kColumn;
    980             }
    981             TextParser paramParser(def->fFileName, def->fStart, def->fContentStart,
    982                     def->fLineCount);
    983             paramParser.skipWhiteSpace();
    984             SkASSERT(paramParser.startsWith("#Param"));
    985             paramParser.next(); // skip hash
    986             paramParser.skipToNonAlphaNum(); // skip Param
    987             paramParser.skipSpace();
    988             const char* paramName = paramParser.fChar;
    989             paramParser.skipToSpace();
    990             string paramNameStr(paramName, (int) (paramParser.fChar - paramName));
    991             if (!this->checkParamReturnBody(def)) {
    992                 return;
    993             }
    994             string refNameStr = def->fParent->fFiddle + "_" + paramNameStr;
    995             fprintf(fOut,
    996                     "    <td><a name=\"%s\"> <code><strong>%s </strong></code> </a></td> <td>",
    997                     refNameStr.c_str(), paramNameStr.c_str());
    998         } break;
    999         case MarkType::kPlatform:
   1000             break;
   1001         case MarkType::kPopulate: {
   1002             SkASSERT(MarkType::kSubtopic == def->fParent->fMarkType);
   1003             string name = def->fParent->fName;
   1004             if (kSubtopics == name) {
   1005                 this->subtopicsOut();
   1006             } else {
   1007                 this->subtopicOut(this->populator(name.c_str()));
   1008             }
   1009             } break;
   1010         case MarkType::kPrivate:
   1011             break;
   1012         case MarkType::kReturn:
   1013             this->mdHeaderOut(3);
   1014             FPRINTF("Return Value");
   1015             if (!this->checkParamReturnBody(def)) {
   1016                 return;
   1017             }
   1018             this->lf(2);
   1019             break;
   1020         case MarkType::kRow:
   1021             if (fInList) {
   1022                 FPRINTF("  <tr>");
   1023                 this->lf(1);
   1024             }
   1025             break;
   1026         case MarkType::kSeeAlso:
   1027             this->mdHeaderOut(3);
   1028             FPRINTF("See Also");
   1029             this->lf(2);
   1030             break;
   1031         case MarkType::kSet:
   1032             break;
   1033         case MarkType::kStdOut: {
   1034             TextParser code(def);
   1035             this->mdHeaderOut(4);
   1036             fprintf(fOut,
   1037                     "Example Output\n"
   1038                     "\n"
   1039                     "~~~~");
   1040             this->lfAlways(1);
   1041             code.skipSpace();
   1042             while (!code.eof()) {
   1043                 const char* end = code.trimmedLineEnd();
   1044                 FPRINTF("%.*s\n", (int) (end - code.fChar), code.fChar);
   1045                 code.skipToLineStart();
   1046             }
   1047             FPRINTF("~~~~");
   1048             this->lf(2);
   1049             } break;
   1050         case MarkType::kStruct:
   1051             fRoot = def->asRoot();
   1052             this->mdHeaderOut(1);
   1053             FPRINTF("<a name=\"%s\"></a> Struct %s", def->fFiddle.c_str(), def->fName.c_str());
   1054             this->lf(1);
   1055             break;
   1056         case MarkType::kSubstitute:
   1057             break;
   1058         case MarkType::kSubtopic:
   1059             this->mdHeaderOut(2);
   1060             FPRINTF("<a name=\"%s\"></a> %s", def->fName.c_str(), printable.c_str());
   1061             this->lf(2);
   1062             break;
   1063         case MarkType::kTable:
   1064             this->lf(2);
   1065             break;
   1066         case MarkType::kTemplate:
   1067             break;
   1068         case MarkType::kText:
   1069             break;
   1070         case MarkType::kTime:
   1071             break;
   1072         case MarkType::kToDo:
   1073             break;
   1074         case MarkType::kTopic:
   1075             this->mdHeaderOut(1);
   1076             FPRINTF("<a name=\"%s\"></a> %s", this->linkName(def).c_str(),
   1077                     printable.c_str());
   1078             this->lf(1);
   1079             break;
   1080         case MarkType::kTrack:
   1081             // don't output children
   1082             return;
   1083         case MarkType::kTypedef:
   1084             break;
   1085         case MarkType::kUnion:
   1086             break;
   1087         case MarkType::kVolatile:
   1088             break;
   1089         case MarkType::kWidth:
   1090             break;
   1091         default:
   1092             SkDebugf("fatal error: MarkType::k%s unhandled in %s()\n",
   1093                     fBmhParser.fMaps[(int) def->fMarkType].fName, __func__);
   1094             SkASSERT(0); // handle everything
   1095             break;
   1096     }
   1097     this->childrenOut(def, textStart);
   1098     switch (def->fMarkType) {  // post child work, at least for tables
   1099         case MarkType::kAnchor:
   1100             if (fColumn > 0) {
   1101                 this->writeSpace();
   1102             }
   1103             break;
   1104         case MarkType::kCode:
   1105             this->writePending();
   1106             FPRINTF("</pre>");
   1107             this->lf(2);
   1108             break;
   1109         case MarkType::kColumn:
   1110             if (fInList) {
   1111                 this->writePending();
   1112                 FPRINTF("</td>");
   1113                 this->lf(1);
   1114             } else {
   1115                 FPRINTF(" ");
   1116             }
   1117             break;
   1118         case MarkType::kDescription:
   1119             this->writePending();
   1120             FPRINTF("</div>");
   1121             fInDescription = false;
   1122             break;
   1123         case MarkType::kEnum:
   1124         case MarkType::kEnumClass:
   1125             this->lfAlways(2);
   1126             break;
   1127         case MarkType::kExample:
   1128             this->writePending();
   1129             if (fHasFiddle) {
   1130                 FPRINTF("</fiddle-embed></div>");
   1131             } else {
   1132                 this->lfAlways(1);
   1133                 if (def->fWrapper.length() > 0) {
   1134                     FPRINTF("}");
   1135                     this->lfAlways(1);
   1136                 }
   1137                 FPRINTF("</pre>");
   1138             }
   1139             this->lf(2);
   1140             fRespectLeadingSpace = false;
   1141             break;
   1142         case MarkType::kLink:
   1143             this->writeString("</a>");
   1144             this->writeSpace();
   1145             break;
   1146         case MarkType::kList:
   1147             fInList = false;
   1148             this->writePending();
   1149             FPRINTF("</table>");
   1150             this->lf(2);
   1151             break;
   1152         case MarkType::kLegend: {
   1153             SkASSERT(def->fChildren.size() == 1);
   1154             const Definition* row = def->fChildren[0];
   1155             SkASSERT(MarkType::kRow == row->fMarkType);
   1156             size_t columnCount = row->fChildren.size();
   1157             SkASSERT(columnCount > 0);
   1158             this->writePending();
   1159             for (size_t index = 0; index < columnCount; ++index) {
   1160                 FPRINTF("| --- ");
   1161             }
   1162             FPRINTF(" |");
   1163             this->lf(1);
   1164             } break;
   1165         case MarkType::kMethod:
   1166             fMethod = nullptr;
   1167             this->lfAlways(2);
   1168             FPRINTF("---");
   1169             this->lf(2);
   1170             break;
   1171         case MarkType::kConst:
   1172         case MarkType::kParam:
   1173             SkASSERT(TableState::kColumn == fTableState);
   1174             fTableState = TableState::kRow;
   1175             this->writePending();
   1176             FPRINTF("</td>\n");
   1177             FPRINTF("  </tr>");
   1178             this->lf(1);
   1179             break;
   1180         case MarkType::kReturn:
   1181         case MarkType::kSeeAlso:
   1182             this->lf(2);
   1183             break;
   1184         case MarkType::kRow:
   1185             if (fInList) {
   1186                 FPRINTF("  </tr>");
   1187             } else {
   1188                 FPRINTF("|");
   1189             }
   1190             this->lf(1);
   1191             break;
   1192         case MarkType::kStruct:
   1193             fRoot = fRoot->rootParent();
   1194             break;
   1195         case MarkType::kTable:
   1196             this->lf(2);
   1197             break;
   1198         case MarkType::kPrivate:
   1199             break;
   1200         default:
   1201             break;
   1202     }
   1203 }
   1204 
   1205 void MdOut::mdHeaderOutLF(int depth, int lf) {
   1206     this->lfAlways(lf);
   1207     for (int index = 0; index < depth; ++index) {
   1208         FPRINTF("#");
   1209     }
   1210     FPRINTF(" ");
   1211 }
   1212 
   1213 void MdOut::populateTables(const Definition* def) {
   1214     const Definition* csParent = this->csParent();
   1215     if (!csParent) {
   1216         return;
   1217     }
   1218     for (auto child : def->fChildren) {
   1219         if (MarkType::kTopic == child->fMarkType || MarkType::kSubtopic == child->fMarkType) {
   1220             string name = child->fName;
   1221             bool builtInTopic = name == kClassesAndStructs || name == kConstants
   1222                     || name == kConstructors || name == kMemberFunctions || name == kMembers
   1223                     || name == kOperators || name == kOverview || name == kRelatedFunctions
   1224                     || name == kSubtopics;
   1225             if (!builtInTopic && child->fName != kOverview) {
   1226                 this->populator(kRelatedFunctions).fMembers.push_back(child);
   1227             }
   1228             this->populateTables(child);
   1229             continue;
   1230         }
   1231         if (child->isStructOrClass()) {
   1232             if (fClassStack.size() > 0) {
   1233                 this->populator(kClassesAndStructs).fMembers.push_back(child);
   1234             }
   1235             fClassStack.push_back(child);
   1236             this->populateTables(child);
   1237             fClassStack.pop_back();
   1238             continue;
   1239         }
   1240         if (MarkType::kEnum == child->fMarkType || MarkType::kEnumClass == child->fMarkType) {
   1241             this->populator(kConstants).fMembers.push_back(child);
   1242             continue;
   1243         }
   1244         if (MarkType::kMember == child->fMarkType) {
   1245             this->populator(kMembers).fMembers.push_back(child);
   1246             continue;
   1247         }
   1248         if (MarkType::kMethod != child->fMarkType) {
   1249             continue;
   1250         }
   1251         if (child->fClone) {
   1252             continue;
   1253         }
   1254         if (Definition::MethodType::kConstructor == child->fMethodType
   1255                 || Definition::MethodType::kDestructor == child->fMethodType) {
   1256             this->populator(kConstructors).fMembers.push_back(child);
   1257             continue;
   1258         }
   1259         if (Definition::MethodType::kOperator == child->fMethodType) {
   1260             this->populator(kOperators).fMembers.push_back(child);
   1261             continue;
   1262         }
   1263         this->populator(kMemberFunctions).fMembers.push_back(child);
   1264         if (csParent && (0 == child->fName.find(csParent->fName + "::Make")
   1265                 || 0 == child->fName.find(csParent->fName + "::make"))) {
   1266             this->populator(kConstructors).fMembers.push_back(child);
   1267             continue;
   1268         }
   1269         for (auto item : child->fChildren) {
   1270             if (MarkType::kIn == item->fMarkType) {
   1271                 string name(item->fContentStart, item->fContentEnd - item->fContentStart);
   1272                 fPopulators[name].fMembers.push_back(child);
   1273                 fPopulators[name].fShowClones = true;
   1274                 break;
   1275             }
   1276         }
   1277     }
   1278 }
   1279 
   1280 void MdOut::resolveOut(const char* start, const char* end, BmhParser::Resolvable resolvable) {
   1281     if ((BmhParser::Resolvable::kLiteral == resolvable || fRespectLeadingSpace) && end > start) {
   1282         while ('\n' == *start) {
   1283             ++start;
   1284         }
   1285         const char* spaceStart = start;
   1286         while (' ' == *start) {
   1287             ++start;
   1288         }
   1289         if (start > spaceStart) {
   1290             fIndent = start - spaceStart;
   1291         }
   1292         this->writeBlockTrim(end - start, start);
   1293         if ('\n' == end[-1]) {
   1294             this->lf(1);
   1295         }
   1296         fIndent = 0;
   1297         return;
   1298     }
   1299     // FIXME: this needs the markdown character present when the def was defined,
   1300     // not the last markdown character the parser would have seen...
   1301     while (fBmhParser.fMC == end[-1]) {
   1302         --end;
   1303     }
   1304     if (start >= end) {
   1305         return;
   1306     }
   1307     string resolved = this->addReferences(start, end, resolvable);
   1308     trim_end_spaces(resolved);
   1309     if (resolved.length()) {
   1310         TextParser paragraph(fFileName, &*resolved.begin(), &*resolved.end(), fLineCount);
   1311         TextParser original(fFileName, start, end, fLineCount);
   1312         while (!original.eof() && '\n' == original.peek()) {
   1313             original.next();
   1314         }
   1315         original.skipSpace();
   1316         while (!paragraph.eof()) {
   1317             paragraph.skipWhiteSpace();
   1318             const char* contentStart = paragraph.fChar;
   1319             paragraph.skipToEndBracket('\n');
   1320             ptrdiff_t lineLength = paragraph.fChar - contentStart;
   1321             if (lineLength) {
   1322                 while (lineLength && contentStart[lineLength - 1] <= ' ') {
   1323                     --lineLength;
   1324                 }
   1325                 string str(contentStart, lineLength);
   1326                 this->writeString(str.c_str());
   1327             }
   1328 #if 0
   1329             int linefeeds = 0;
   1330             while (lineLength > 0 && '\n' == contentStart[--lineLength]) {
   1331 
   1332                 ++linefeeds;
   1333             }
   1334             if (lineLength > 0) {
   1335                 this->nl();
   1336             }
   1337             fLinefeeds += linefeeds;
   1338 #endif
   1339             if (paragraph.eof()) {
   1340                 break;
   1341             }
   1342             if ('\n' == paragraph.next()) {
   1343                 int linefeeds = 1;
   1344                 if (!paragraph.eof() && '\n' == paragraph.peek()) {
   1345                     linefeeds = 2;
   1346                 }
   1347                 this->lf(linefeeds);
   1348             }
   1349         }
   1350 #if 0
   1351         while (end > start && end[0] == '\n') {
   1352             FPRINTF("\n");
   1353             --end;
   1354         }
   1355 #endif
   1356     }
   1357 }
   1358 
   1359 void MdOut::rowOut(const char* name, const string& description) {
   1360     this->lfAlways(1);
   1361     FPRINTF("| ");
   1362     this->resolveOut(name, name + strlen(name), BmhParser::Resolvable::kYes);
   1363     FPRINTF(" | ");
   1364     this->resolveOut(&description.front(), &description.back() + 1, BmhParser::Resolvable::kYes);
   1365     FPRINTF(" |");
   1366     this->lf(1);
   1367 }
   1368 
   1369 void MdOut::subtopicsOut() {
   1370     const Definition* csParent = this->csParent();
   1371     SkASSERT(csParent);
   1372     this->rowOut("name", "description");
   1373     this->rowOut("---", "---");
   1374     for (auto item : { kClassesAndStructs, kConstants, kConstructors, kMemberFunctions,
   1375             kMembers, kOperators, kRelatedFunctions } ) {
   1376         for (auto entry : this->populator(item).fMembers) {
   1377             if (entry->csParent() == csParent) {
   1378                 string description = fPopulators.find(item)->second.fDescription;
   1379                 if (kConstructors == item) {
   1380                     description += " " + csParent->fName;
   1381                 }
   1382                 this->rowOut(item, description);
   1383                 break;
   1384             }
   1385         }
   1386     }
   1387 }
   1388 
   1389 void MdOut::subtopicOut(const TableContents& tableContents) {
   1390     const auto& data = tableContents.fMembers;
   1391     const Definition* csParent = this->csParent();
   1392     SkASSERT(csParent);
   1393     fRoot = csParent->asRoot();
   1394     this->rowOut("name", "description");
   1395     this->rowOut("---", "---");
   1396     std::map<string, const Definition*> items;
   1397     for (auto entry : data) {
   1398         if (entry->csParent() != csParent) {
   1399             continue;
   1400         }
   1401         size_t start = entry->fName.find_last_of("::");
   1402         string name = entry->fName.substr(string::npos == start ? 0 : start + 1);
   1403         items[name] = entry;
   1404     }
   1405     for (auto entry : items) {
   1406         if (entry.second->fDeprecated) {
   1407             continue;
   1408         }
   1409         const Definition* oneLiner = nullptr;
   1410         for (auto child : entry.second->fChildren) {
   1411             if (MarkType::kLine == child->fMarkType) {
   1412                 oneLiner = child;
   1413                 break;
   1414             }
   1415         }
   1416         if (!oneLiner) {
   1417             SkDebugf(""); // convenient place to set a breakpoint
   1418         }
   1419         // TODO: detect this earlier? throw error here?
   1420         SkASSERT(oneLiner);
   1421         this->rowOut(entry.first.c_str(), string(oneLiner->fContentStart,
   1422             oneLiner->fContentEnd - oneLiner->fContentStart));
   1423         if (tableContents.fShowClones && entry.second->fCloned) {
   1424             int cloneNo = 2;
   1425             string builder = entry.second->fName;
   1426             if ("()" == builder.substr(builder.length() - 2)) {
   1427                 builder = builder.substr(0, builder.length() - 2);
   1428             }
   1429             builder += '_';
   1430             this->rowOut("",
   1431                     preformat(entry.second->formatFunction(Definition::Format::kOmitReturn)));
   1432             do {
   1433                 string match = builder + to_string(cloneNo);
   1434                 auto child = csParent->findClone(match);
   1435                 if (!child) {
   1436                     break;
   1437                 }
   1438                 this->rowOut("", preformat(child->formatFunction(Definition::Format::kOmitReturn)));
   1439             } while (++cloneNo);
   1440         }
   1441     }
   1442 }
   1443