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
19static void add_ref(const string& leadingSpaces, const string& ref, string* result) {
20    *result += leadingSpaces + ref;
21}
22
23static 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
37static 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
47string 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
260bool MdOut::buildReferences(const char* docDir, const char* mdFileOrPath) {
261    if (!sk_isdir(mdFileOrPath)) {
262        SkString mdFile = SkOSPath::Basename(mdFileOrPath);
263        SkString bmhFile = SkOSPath::Join(docDir, mdFile.c_str());
264        bmhFile.remove(bmhFile.size() - 3, 3);
265        bmhFile += ".bmh";
266        SkString mdPath = SkOSPath::Dirname(mdFileOrPath);
267        if (!this->buildRefFromFile(bmhFile.c_str(), mdPath.c_str())) {
268            SkDebugf("failed to parse %s\n", mdFileOrPath);
269            return false;
270        }
271    } else {
272        SkOSFile::Iter it(docDir, ".bmh");
273        for (SkString file; it.next(&file); ) {
274            SkString p = SkOSPath::Join(docDir, file.c_str());
275            const char* hunk = p.c_str();
276            if (!SkStrEndsWith(hunk, ".bmh")) {
277                continue;
278            }
279            if (SkStrEndsWith(hunk, "markup.bmh")) {  // don't look inside this for now
280                continue;
281            }
282            if (!this->buildRefFromFile(hunk, mdFileOrPath)) {
283                SkDebugf("failed to parse %s\n", hunk);
284                return false;
285            }
286        }
287    }
288    return true;
289}
290
291bool MdOut::buildStatus(const char* statusFile, const char* outDir) {
292    StatusIter iter(statusFile, ".bmh", StatusFilter::kInProgress);
293    for (string file; iter.next(&file); ) {
294        SkString p = SkOSPath::Join(iter.baseDir().c_str(), file.c_str());
295        const char* hunk = p.c_str();
296        if (!this->buildRefFromFile(hunk, outDir)) {
297            SkDebugf("failed to parse %s\n", hunk);
298            return false;
299        }
300    }
301    return true;
302}
303
304bool MdOut::buildRefFromFile(const char* name, const char* outDir) {
305    fFileName = string(name);
306    string filename(name);
307    if (filename.substr(filename.length() - 4) == ".bmh") {
308        filename = filename.substr(0, filename.length() - 4);
309    }
310    size_t start = filename.length();
311    while (start > 0 && (isalnum(filename[start - 1]) || '_' == filename[start - 1])) {
312        --start;
313    }
314    string match = filename.substr(start);
315    string header = match;
316    filename = match + ".md";
317    match += ".bmh";
318    fOut = nullptr;
319    string fullName;
320
321    vector<string> keys;
322    keys.reserve(fBmhParser.fTopicMap.size());
323    for (const auto& it : fBmhParser.fTopicMap) {
324        keys.push_back(it.first);
325    }
326    std::sort(keys.begin(), keys.end());
327    for (auto key : keys) {
328        string s(key);
329        auto topicDef = fBmhParser.fTopicMap.at(s);
330        if (topicDef->fParent) {
331            continue;
332        }
333        if (!topicDef->isRoot()) {
334            return this->reportError<bool>("expected root topic");
335        }
336        fRoot = topicDef->asRoot();
337        if (string::npos == fRoot->fFileName.rfind(match)) {
338            continue;
339        }
340        if (!fOut) {
341            fullName = outDir;
342            if ('/' != fullName.back()) {
343                fullName += '/';
344            }
345            fullName += filename;
346            fOut = fopen(filename.c_str(), "wb");
347            if (!fOut) {
348                SkDebugf("could not open output file %s\n", fullName.c_str());
349                return false;
350            }
351            size_t underscorePos = header.find('_');
352            if (string::npos != underscorePos) {
353                header.replace(underscorePos, 1, " ");
354            }
355            SkASSERT(string::npos == header.find('_'));
356            FPRINTF("%s", header.c_str());
357            this->lfAlways(1);
358            FPRINTF("===");
359        }
360        fPopulators[kClassesAndStructs].fDescription = "embedded struct and class members";
361        fPopulators[kConstants].fDescription = "enum and enum class, const values";
362        fPopulators[kConstructors].fDescription = "functions that construct";
363        fPopulators[kMemberFunctions].fDescription = "static functions and member methods";
364        fPopulators[kMembers].fDescription = "member values";
365        fPopulators[kOperators].fDescription = "operator overloading methods";
366        fPopulators[kRelatedFunctions].fDescription = "similar methods grouped together";
367        fPopulators[kSubtopics].fDescription = "";
368        this->populateTables(fRoot);
369        this->markTypeOut(topicDef);
370    }
371    if (fOut) {
372        this->writePending();
373        fclose(fOut);
374        fflush(fOut);
375        if (this->writtenFileDiffers(filename, fullName)) {
376            fOut = fopen(fullName.c_str(), "wb");
377            int writtenSize;
378            const char* written = ReadToBuffer(filename, &writtenSize);
379            fwrite(written, 1, writtenSize, fOut);
380            fclose(fOut);
381            fflush(fOut);
382            SkDebugf("wrote updated %s\n", fullName.c_str());
383        }
384        remove(filename.c_str());
385        fOut = nullptr;
386    }
387    return true;
388}
389
390bool MdOut::checkParamReturnBody(const Definition* def) const {
391    TextParser paramBody(def);
392    const char* descriptionStart = paramBody.fChar;
393    if (!islower(descriptionStart[0]) && !isdigit(descriptionStart[0])) {
394        paramBody.skipToNonAlphaNum();
395        string ref = string(descriptionStart, paramBody.fChar - descriptionStart);
396        if (!this->isDefined(paramBody, ref, true)) {
397            string errorStr = MarkType::kReturn == def->fMarkType ? "return" : "param";
398            errorStr += " description must start with lower case";
399            paramBody.reportError(errorStr.c_str());
400            return false;
401        }
402    }
403    if ('.' == paramBody.fEnd[-1]) {
404        paramBody.reportError("make param description a phrase; should not end with period");
405        return false;
406    }
407    return true;
408}
409
410void MdOut::childrenOut(const Definition* def, const char* start) {
411    const char* end;
412    fLineCount = def->fLineCount;
413    if (def->isRoot()) {
414        fRoot = const_cast<RootDefinition*>(def->asRoot());
415    } else if (MarkType::kEnumClass == def->fMarkType) {
416        fEnumClass = def;
417    }
418    BmhParser::Resolvable resolvable = this->resolvable(def);
419    for (auto& child : def->fChildren) {
420        end = child->fStart;
421        if (BmhParser::Resolvable::kNo != resolvable) {
422            this->resolveOut(start, end, resolvable);
423        }
424        this->markTypeOut(child);
425        start = child->fTerminator;
426    }
427    if (BmhParser::Resolvable::kNo != resolvable) {
428        end = def->fContentEnd;
429        this->resolveOut(start, end, resolvable);
430    }
431    if (MarkType::kEnumClass == def->fMarkType) {
432        fEnumClass = nullptr;
433    }
434}
435
436const Definition* MdOut::csParent() const {
437    const Definition* csParent = fRoot->csParent();
438    if (!csParent) {
439        const Definition* topic = fRoot;
440        while (topic && MarkType::kTopic != topic->fMarkType) {
441            topic = topic->fParent;
442        }
443        for (auto child : topic->fChildren) {
444            if (child->isStructOrClass() || MarkType::kTypedef == child->fMarkType) {
445                csParent = child;
446                break;
447            }
448        }
449        SkASSERT(csParent || string::npos == fRoot->fFileName.find("Sk"));
450    }
451    return csParent;
452}
453
454const Definition* MdOut::findParamType() {
455    SkASSERT(fMethod);
456    TextParser parser(fMethod->fFileName, fMethod->fStart, fMethod->fContentStart,
457            fMethod->fLineCount);
458    string lastFull;
459    do {
460        parser.skipToAlpha();
461        if (parser.eof()) {
462            return nullptr;
463        }
464        const char* word = parser.fChar;
465        parser.skipFullName();
466        SkASSERT(!parser.eof());
467        string name = string(word, parser.fChar - word);
468        if (fLastParam->fName == name) {
469            const Definition* paramType = this->isDefined(parser, lastFull, false);
470            return paramType;
471        }
472        if (isupper(name[0])) {
473            lastFull = name;
474        }
475    } while (true);
476    return nullptr;
477}
478
479const Definition* MdOut::isDefined(const TextParser& parser, const string& ref, bool report) const {
480    auto rootIter = fBmhParser.fClassMap.find(ref);
481    if (rootIter != fBmhParser.fClassMap.end()) {
482        return &rootIter->second;
483    }
484    auto typedefIter = fBmhParser.fTypedefMap.find(ref);
485    if (typedefIter != fBmhParser.fTypedefMap.end()) {
486        return &typedefIter->second;
487    }
488    auto enumIter = fBmhParser.fEnumMap.find(ref);
489    if (enumIter != fBmhParser.fEnumMap.end()) {
490        return &enumIter->second;
491    }
492    auto constIter = fBmhParser.fConstMap.find(ref);
493    if (constIter != fBmhParser.fConstMap.end()) {
494        return &constIter->second;
495    }
496    auto methodIter = fBmhParser.fMethodMap.find(ref);
497    if (methodIter != fBmhParser.fMethodMap.end()) {
498        return &methodIter->second;
499    }
500    auto aliasIter = fBmhParser.fAliasMap.find(ref);
501    if (aliasIter != fBmhParser.fAliasMap.end()) {
502        return aliasIter->second;
503    }
504    for (const auto& external : fBmhParser.fExternals) {
505        if (external.fName == ref) {
506            return &external;
507        }
508    }
509    if (fRoot) {
510        if (ref == fRoot->fName) {
511            return fRoot;
512        }
513        if (const Definition* definition = fRoot->find(ref, RootDefinition::AllowParens::kYes)) {
514            return definition;
515        }
516        const Definition* test = fRoot;
517        do {
518            if (!test->isRoot()) {
519                continue;
520            }
521            const RootDefinition* root = test->asRoot();
522            for (auto& leaf : root->fBranches) {
523                if (ref == leaf.first) {
524                    return leaf.second;
525                }
526                const Definition* definition = leaf.second->find(ref,
527                        RootDefinition::AllowParens::kYes);
528                if (definition) {
529                    return definition;
530                }
531            }
532            for (string prefix : { "::", "_" } ) {
533                string prefixed = root->fName + prefix + ref;
534                if (const Definition* definition = root->find(prefixed,
535                        RootDefinition::AllowParens::kYes)) {
536                    return definition;
537                }
538                if (isupper(prefixed[0])) {
539                    auto topicIter = fBmhParser.fTopicMap.find(prefixed);
540                    if (topicIter != fBmhParser.fTopicMap.end()) {
541                        return topicIter->second;
542                    }
543                }
544            }
545            string fiddlePrefixed = root->fFiddle + "_" + ref;
546            auto topicIter = fBmhParser.fTopicMap.find(fiddlePrefixed);
547            if (topicIter != fBmhParser.fTopicMap.end()) {
548                return topicIter->second;
549            }
550        } while ((test = test->fParent));
551    }
552    size_t doubleColon = ref.find("::");
553    if (string::npos != doubleColon) {
554        string className = ref.substr(0, doubleColon);
555        auto classIter = fBmhParser.fClassMap.find(className);
556        if (classIter != fBmhParser.fClassMap.end()) {
557            const RootDefinition& classDef = classIter->second;
558            const Definition* result = classDef.find(ref, RootDefinition::AllowParens::kYes);
559            if (result) {
560                return result;
561            }
562        }
563
564    }
565    if (!ref.compare(0, 2, "SK") || !ref.compare(0, 3, "sk_")
566            || (('k' == ref[0] || 'g' == ref[0] || 'f' == ref[0]) &&
567                ref.length() > 1 && isupper(ref[1]))) {
568        // try with a prefix
569        if ('k' == ref[0]) {
570            for (auto const& iter : fBmhParser.fEnumMap) {
571                if (iter.second.find(ref, RootDefinition::AllowParens::kYes)) {
572                    return &iter.second;
573                }
574            }
575            if (fEnumClass) {
576                string fullName = fEnumClass->fName + "::" + ref;
577                for (auto child : fEnumClass->fChildren) {
578                    if (fullName == child->fName) {
579                        return child;
580                    }
581                }
582            }
583            if (string::npos != ref.find("_Private")) {
584                return nullptr;
585            }
586        }
587        if ('f' == ref[0]) {
588            // FIXME : find def associated with prior, e.g.: r.fX where 'SkPoint r' was earlier
589                // need to have pushed last resolve on stack to do this
590                // for now, just try to make sure that it's there and error if not
591            if ('.' != parser.backup(ref.c_str())) {
592                parser.reportError("fX member undefined");
593                return nullptr;
594            }
595        } else {
596            if (report) {
597                parser.reportError("SK undefined");
598            }
599            return nullptr;
600        }
601    }
602    if (isupper(ref[0])) {
603        auto topicIter = fBmhParser.fTopicMap.find(ref);
604        if (topicIter != fBmhParser.fTopicMap.end()) {
605            return topicIter->second;
606        }
607        size_t pos = ref.find('_');
608        if (string::npos != pos) {
609            // see if it is defined by another base class
610            string className(ref, 0, pos);
611            auto classIter = fBmhParser.fClassMap.find(className);
612            if (classIter != fBmhParser.fClassMap.end()) {
613                if (const Definition* definition = classIter->second.find(ref,
614                        RootDefinition::AllowParens::kYes)) {
615                    return definition;
616                }
617            }
618            auto enumIter = fBmhParser.fEnumMap.find(className);
619            if (enumIter != fBmhParser.fEnumMap.end()) {
620                if (const Definition* definition = enumIter->second.find(ref,
621                        RootDefinition::AllowParens::kYes)) {
622                    return definition;
623                }
624            }
625            if (report) {
626                parser.reportError("_ undefined");
627            }
628            return nullptr;
629        }
630    }
631    return nullptr;
632}
633
634string MdOut::linkName(const Definition* ref) const {
635    string result = ref->fName;
636    size_t under = result.find('_');
637    if (string::npos != under) {
638        string classPart = result.substr(0, under);
639        string namePart = result.substr(under + 1, result.length());
640        if (fRoot && (fRoot->fName == classPart
641                || (fRoot->fParent && fRoot->fParent->fName == classPart))) {
642            result = namePart;
643        }
644    }
645    return result;
646}
647
648// for now, hard-code to html links
649// def should not include SkXXX_
650string MdOut::linkRef(const string& leadingSpaces, const Definition* def,
651        const string& ref, BmhParser::Resolvable resolvable) const {
652	string buildup;
653    const string* str = &def->fFiddle;
654    SkASSERT(str->length() > 0);
655    size_t under = str->find('_');
656    const Definition* curRoot = fRoot;
657    string classPart = string::npos != under ? str->substr(0, under) : *str;
658    bool classMatch = curRoot->fName == classPart;
659    while (curRoot->fParent) {
660        curRoot = curRoot->fParent;
661        classMatch |= curRoot->fName == classPart;
662    }
663    const Definition* defRoot;
664    const Definition* temp = def;
665    do {
666        defRoot = temp;
667        if (!(temp = temp->fParent)) {
668            break;
669        }
670        classMatch |= temp != defRoot && temp->fName == classPart;
671    } while (true);
672    string namePart = string::npos != under ? str->substr(under + 1, str->length()) : *str;
673    SkASSERT(fRoot);
674    SkASSERT(fRoot->fFileName.length());
675    if (classMatch) {
676        buildup = "#";
677        if (*str != classPart && "Sk" == classPart.substr(0, 2)) {
678            buildup += classPart + "_";
679        }
680        buildup += namePart;
681    } else {
682        string filename = defRoot->asRoot()->fFileName;
683        if (filename.substr(filename.length() - 4) == ".bmh") {
684            filename = filename.substr(0, filename.length() - 4);
685        }
686        size_t start = filename.length();
687        while (start > 0 && (isalnum(filename[start - 1]) || '_' == filename[start - 1])) {
688            --start;
689        }
690        buildup = filename.substr(start) + "#" + (classMatch ? namePart : *str);
691    }
692    if (MarkType::kParam == def->fMarkType) {
693        const Definition* parent = def->fParent;
694        SkASSERT(MarkType::kMethod == parent->fMarkType);
695        buildup = '#' + parent->fFiddle + '_' + ref;
696    }
697    string refOut(ref);
698    std::replace(refOut.begin(), refOut.end(), '_', ' ');
699    if (ref.length() > 2 && islower(ref[0]) && "()" == ref.substr(ref.length() - 2)) {
700        refOut = refOut.substr(0, refOut.length() - 2);
701    }
702    string result = leadingSpaces + "<a href=\"" + buildup + "\">" + refOut + "</a>";
703	if (BmhParser::Resolvable::kClone == resolvable && MarkType::kMethod == def->fMarkType &&
704			def->fCloned && !def->fClone) {
705		bool found = false;
706		string match = def->fName;
707		if ("()" == match.substr(match.length() - 2)) {
708			match = match.substr(0, match.length() - 2);
709		}
710		match += '_';
711		auto classIter = fBmhParser.fClassMap.find(classPart);
712		if (fBmhParser.fClassMap.end() != classIter) {
713			for (char num = '2'; num <= '9'; ++num) {
714				string clone = match + num;
715				const auto& leafIter = classIter->second.fLeaves.find(clone);
716				if (leafIter != classIter->second.fLeaves.end()) {
717					result += "<sup><a href=\"" + buildup + "_" + num + "\">[" + num + "]</a></sup>";
718					found = true;
719				}
720			}
721
722		}
723		if (!found) {
724			SkDebugf("");  // convenient place to set a breakpoint
725		}
726	}
727	return result;
728}
729
730void MdOut::markTypeOut(Definition* def) {
731    string printable = def->printableName();
732    const char* textStart = def->fContentStart;
733    if (MarkType::kParam != def->fMarkType && MarkType::kConst != def->fMarkType &&
734            (!def->fParent || MarkType::kConst != def->fParent->fMarkType) &&
735            TableState::kNone != fTableState) {
736        this->writePending();
737        FPRINTF("</table>");
738        this->lf(2);
739        fTableState = TableState::kNone;
740    }
741    switch (def->fMarkType) {
742        case MarkType::kAlias:
743            break;
744        case MarkType::kAnchor: {
745            if (fColumn > 0) {
746                this->writeSpace();
747            }
748            this->writePending();
749            TextParser parser(def);
750            const char* start = parser.fChar;
751            parser.skipToEndBracket(" # ");
752            string anchorText(start, parser.fChar - start);
753            parser.skipExact(" # ");
754            string anchorLink(parser.fChar, parser.fEnd - parser.fChar);
755            FPRINTF("<a href=\"%s\">%s", anchorLink.c_str(), anchorText.c_str());
756            } break;
757        case MarkType::kBug:
758            break;
759        case MarkType::kClass:
760            this->mdHeaderOut(1);
761            FPRINTF("<a name=\"%s\"></a> Class %s", this->linkName(def).c_str(),
762                    def->fName.c_str());
763            this->lf(1);
764            break;
765        case MarkType::kCode:
766            this->lfAlways(2);
767            FPRINTF("<pre style=\"padding: 1em 1em 1em 1em;"
768                    "width: 62.5em; background-color: #f0f0f0\">");
769            this->lf(1);
770            break;
771        case MarkType::kColumn:
772            this->writePending();
773            if (fInList) {
774                FPRINTF("    <td>");
775            } else {
776                FPRINTF("| ");
777            }
778            break;
779        case MarkType::kComment:
780            break;
781        case MarkType::kConst: {
782            if (TableState::kNone == fTableState) {
783                this->mdHeaderOut(3);
784                FPRINTF("Constants\n"
785                        "\n"
786                        "<table>");
787                fTableState = TableState::kRow;
788                this->lf(1);
789            }
790            if (TableState::kRow == fTableState) {
791                this->writePending();
792                FPRINTF("  <tr>");
793                this->lf(1);
794                fTableState = TableState::kColumn;
795            }
796            this->writePending();
797            FPRINTF("    <td><a name=\"%s\"> <code><strong>%s </strong></code> </a></td>",
798                    def->fFiddle.c_str(), def->fName.c_str());
799            const char* lineEnd = strchr(textStart, '\n');
800            SkASSERT(lineEnd < def->fTerminator);
801            SkASSERT(lineEnd > textStart);
802            SkASSERT((int) (lineEnd - textStart) == lineEnd - textStart);
803            FPRINTF("<td>%.*s</td>", (int) (lineEnd - textStart), textStart);
804            FPRINTF("<td>");
805            textStart = lineEnd;
806        } break;
807        case MarkType::kDefine:
808            break;
809        case MarkType::kDefinedBy:
810            break;
811        case MarkType::kDeprecated:
812            break;
813        case MarkType::kDescription:
814            fInDescription = true;
815            this->writePending();
816            FPRINTF("<div>");
817            break;
818        case MarkType::kDoxygen:
819            break;
820        case MarkType::kDuration:
821            break;
822        case MarkType::kEnum:
823        case MarkType::kEnumClass:
824            this->mdHeaderOut(2);
825            FPRINTF("<a name=\"%s\"></a> Enum %s", def->fFiddle.c_str(), def->fName.c_str());
826            this->lf(2);
827            break;
828        case MarkType::kExample: {
829            this->mdHeaderOut(3);
830            FPRINTF("Example\n"
831                            "\n");
832            fHasFiddle = true;
833            bool showGpu = false;
834            bool gpuAndCpu = false;
835            const Definition* platform = def->hasChild(MarkType::kPlatform);
836            if (platform) {
837                TextParser platParse(platform);
838                fHasFiddle = !platParse.strnstr("!fiddle", platParse.fEnd);
839                showGpu = platParse.strnstr("gpu", platParse.fEnd);
840                if (showGpu) {
841                    gpuAndCpu = platParse.strnstr("cpu", platParse.fEnd);
842                }
843            }
844            if (fHasFiddle) {
845                SkASSERT(def->fHash.length() > 0);
846                FPRINTF("<div><fiddle-embed name=\"%s\"", def->fHash.c_str());
847                if (showGpu) {
848                    FPRINTF(" gpu=\"true\"");
849                    if (gpuAndCpu) {
850                        FPRINTF(" cpu=\"true\"");
851                    }
852                }
853                FPRINTF(">");
854            } else {
855                SkASSERT(def->fHash.length() == 0);
856                FPRINTF("<pre style=\"padding: 1em 1em 1em 1em; font-size: 13px"
857                        " width: 62.5em; background-color: #f0f0f0\">");
858                this->lfAlways(1);
859                if (def->fWrapper.length() > 0) {
860                    FPRINTF("%s", def->fWrapper.c_str());
861                }
862                fRespectLeadingSpace = true;
863            }
864            } break;
865        case MarkType::kExperimental:
866            break;
867        case MarkType::kExternal:
868            break;
869        case MarkType::kFile:
870            break;
871        case MarkType::kFormula:
872            break;
873        case MarkType::kFunction:
874            break;
875        case MarkType::kHeight:
876            break;
877        case MarkType::kImage:
878            break;
879        case MarkType::kIn:
880            break;
881        case MarkType::kLegend:
882            break;
883        case MarkType::kLine:
884            break;
885        case MarkType::kLink:
886            break;
887        case MarkType::kList:
888            fInList = true;
889            this->lfAlways(2);
890            FPRINTF("<table>");
891            this->lf(1);
892            break;
893        case MarkType::kLiteral:
894            break;
895        case MarkType::kMarkChar:
896            fBmhParser.fMC = def->fContentStart[0];
897            break;
898        case MarkType::kMember: {
899            TextParser tp(def->fFileName, def->fStart, def->fContentStart, def->fLineCount);
900            tp.skipExact("#Member");
901            tp.skipWhiteSpace();
902            const char* end = tp.trimmedBracketEnd('\n');
903            this->lfAlways(2);
904            FPRINTF("<a name=\"%s\"> <code><strong>%.*s</strong></code> </a>",
905                    def->fFiddle.c_str(), (int) (end - tp.fChar), tp.fChar);
906            this->lf(2);
907            } break;
908        case MarkType::kMethod: {
909            string method_name = def->methodName();
910            string formattedStr = def->formatFunction();
911
912			this->lfAlways(2);
913			FPRINTF("<a name=\"%s\"></a>", def->fFiddle.c_str());
914			if (!def->isClone()) {
915                this->mdHeaderOutLF(2, 1);
916                FPRINTF("%s", method_name.c_str());
917			}
918			this->lf(2);
919
920            // TODO: put in css spec that we can define somewhere else (if markup supports that)
921            // TODO: 50em below should match limit = 80 in formatFunction()
922            this->writePending();
923            string preformattedStr = preformat(formattedStr);
924            FPRINTF("<pre style=\"padding: 1em 1em 1em 1em;"
925                                    "width: 62.5em; background-color: #f0f0f0\">\n"
926                            "%s\n"
927                            "</pre>",  preformattedStr.c_str());
928            this->lf(2);
929            fTableState = TableState::kNone;
930            fMethod = def;
931            } break;
932        case MarkType::kNoExample:
933            break;
934        case MarkType::kOutdent:
935            break;
936        case MarkType::kParam: {
937            if (TableState::kNone == fTableState) {
938                this->mdHeaderOut(3);
939                fprintf(fOut,
940                        "Parameters\n"
941                        "\n"
942                        "<table>"
943                        );
944                this->lf(1);
945                fTableState = TableState::kRow;
946            }
947            if (TableState::kRow == fTableState) {
948                FPRINTF("  <tr>");
949                this->lf(1);
950                fTableState = TableState::kColumn;
951            }
952            TextParser paramParser(def->fFileName, def->fStart, def->fContentStart,
953                    def->fLineCount);
954            paramParser.skipWhiteSpace();
955            SkASSERT(paramParser.startsWith("#Param"));
956            paramParser.next(); // skip hash
957            paramParser.skipToNonAlphaNum(); // skip Param
958            paramParser.skipSpace();
959            const char* paramName = paramParser.fChar;
960            paramParser.skipToSpace();
961            string paramNameStr(paramName, (int) (paramParser.fChar - paramName));
962            if (!this->checkParamReturnBody(def)) {
963                return;
964            }
965            string refNameStr = def->fParent->fFiddle + "_" + paramNameStr;
966            fprintf(fOut,
967                    "    <td><a name=\"%s\"> <code><strong>%s </strong></code> </a></td> <td>",
968                    refNameStr.c_str(), paramNameStr.c_str());
969        } break;
970        case MarkType::kPlatform:
971            break;
972        case MarkType::kPopulate: {
973            SkASSERT(MarkType::kSubtopic == def->fParent->fMarkType);
974            string name = def->fParent->fName;
975            if (kSubtopics == name) {
976                this->subtopicsOut();
977            } else {
978                SkASSERT(kClassesAndStructs == name || kConstants == name || kConstructors == name
979                        || kMemberFunctions == name || kMembers == name || kOperators == name
980                        || kRelatedFunctions == name);
981                this->subtopicOut(this->populator(name.c_str()));
982            }
983            } break;
984        case MarkType::kPrivate:
985            break;
986        case MarkType::kReturn:
987            this->mdHeaderOut(3);
988            FPRINTF("Return Value");
989            if (!this->checkParamReturnBody(def)) {
990                return;
991            }
992            this->lf(2);
993            break;
994        case MarkType::kRow:
995            if (fInList) {
996                FPRINTF("  <tr>");
997                this->lf(1);
998            }
999            break;
1000        case MarkType::kSeeAlso:
1001            this->mdHeaderOut(3);
1002            FPRINTF("See Also");
1003            this->lf(2);
1004            break;
1005        case MarkType::kSet:
1006            break;
1007        case MarkType::kStdOut: {
1008            TextParser code(def);
1009            this->mdHeaderOut(4);
1010            fprintf(fOut,
1011                    "Example Output\n"
1012                    "\n"
1013                    "~~~~");
1014            this->lfAlways(1);
1015            code.skipSpace();
1016            while (!code.eof()) {
1017                const char* end = code.trimmedLineEnd();
1018                FPRINTF("%.*s\n", (int) (end - code.fChar), code.fChar);
1019                code.skipToLineStart();
1020            }
1021            FPRINTF("~~~~");
1022            this->lf(2);
1023            } break;
1024        case MarkType::kStruct:
1025            fRoot = def->asRoot();
1026            this->mdHeaderOut(1);
1027            FPRINTF("<a name=\"%s\"></a> Struct %s", def->fFiddle.c_str(), def->fName.c_str());
1028            this->lf(1);
1029            break;
1030        case MarkType::kSubstitute:
1031            break;
1032        case MarkType::kSubtopic:
1033            this->mdHeaderOut(2);
1034            FPRINTF("<a name=\"%s\"></a> %s", def->fName.c_str(), printable.c_str());
1035            this->lf(2);
1036            break;
1037        case MarkType::kTable:
1038            this->lf(2);
1039            break;
1040        case MarkType::kTemplate:
1041            break;
1042        case MarkType::kText:
1043            break;
1044        case MarkType::kTime:
1045            break;
1046        case MarkType::kToDo:
1047            break;
1048        case MarkType::kTopic:
1049            this->mdHeaderOut(1);
1050            FPRINTF("<a name=\"%s\"></a> %s", this->linkName(def).c_str(),
1051                    printable.c_str());
1052            this->lf(1);
1053            break;
1054        case MarkType::kTrack:
1055            // don't output children
1056            return;
1057        case MarkType::kTypedef:
1058            break;
1059        case MarkType::kUnion:
1060            break;
1061        case MarkType::kVolatile:
1062            break;
1063        case MarkType::kWidth:
1064            break;
1065        default:
1066            SkDebugf("fatal error: MarkType::k%s unhandled in %s()\n",
1067                    fBmhParser.fMaps[(int) def->fMarkType].fName, __func__);
1068            SkASSERT(0); // handle everything
1069            break;
1070    }
1071    this->childrenOut(def, textStart);
1072    switch (def->fMarkType) {  // post child work, at least for tables
1073        case MarkType::kAnchor:
1074            if (fColumn > 0) {
1075                this->writeSpace();
1076            }
1077            break;
1078        case MarkType::kCode:
1079            this->writePending();
1080            FPRINTF("</pre>");
1081            this->lf(2);
1082            break;
1083        case MarkType::kColumn:
1084            if (fInList) {
1085                this->writePending();
1086                FPRINTF("</td>");
1087                this->lf(1);
1088            } else {
1089                FPRINTF(" ");
1090            }
1091            break;
1092        case MarkType::kDescription:
1093            this->writePending();
1094            FPRINTF("</div>");
1095            fInDescription = false;
1096            break;
1097        case MarkType::kEnum:
1098        case MarkType::kEnumClass:
1099            this->lfAlways(2);
1100            break;
1101        case MarkType::kExample:
1102            this->writePending();
1103            if (fHasFiddle) {
1104                FPRINTF("</fiddle-embed></div>");
1105            } else {
1106                this->lfAlways(1);
1107                if (def->fWrapper.length() > 0) {
1108                    FPRINTF("}");
1109                    this->lfAlways(1);
1110                }
1111                FPRINTF("</pre>");
1112            }
1113            this->lf(2);
1114            fRespectLeadingSpace = false;
1115            break;
1116        case MarkType::kLink:
1117            this->writeString("</a>");
1118            this->writeSpace();
1119            break;
1120        case MarkType::kList:
1121            fInList = false;
1122            this->writePending();
1123            FPRINTF("</table>");
1124            this->lf(2);
1125            break;
1126        case MarkType::kLegend: {
1127            SkASSERT(def->fChildren.size() == 1);
1128            const Definition* row = def->fChildren[0];
1129            SkASSERT(MarkType::kRow == row->fMarkType);
1130            size_t columnCount = row->fChildren.size();
1131            SkASSERT(columnCount > 0);
1132            this->writePending();
1133            for (size_t index = 0; index < columnCount; ++index) {
1134                FPRINTF("| --- ");
1135            }
1136            FPRINTF(" |");
1137            this->lf(1);
1138            } break;
1139        case MarkType::kMethod:
1140            fMethod = nullptr;
1141            this->lfAlways(2);
1142            FPRINTF("---");
1143            this->lf(2);
1144            break;
1145        case MarkType::kConst:
1146        case MarkType::kParam:
1147            SkASSERT(TableState::kColumn == fTableState);
1148            fTableState = TableState::kRow;
1149            this->writePending();
1150            FPRINTF("</td>\n");
1151            FPRINTF("  </tr>");
1152            this->lf(1);
1153            break;
1154        case MarkType::kReturn:
1155        case MarkType::kSeeAlso:
1156            this->lf(2);
1157            break;
1158        case MarkType::kRow:
1159            if (fInList) {
1160                FPRINTF("  </tr>");
1161            } else {
1162                FPRINTF("|");
1163            }
1164            this->lf(1);
1165            break;
1166        case MarkType::kStruct:
1167            fRoot = fRoot->rootParent();
1168            break;
1169        case MarkType::kTable:
1170            this->lf(2);
1171            break;
1172        case MarkType::kPrivate:
1173            break;
1174        default:
1175            break;
1176    }
1177}
1178
1179void MdOut::mdHeaderOutLF(int depth, int lf) {
1180    this->lfAlways(lf);
1181    for (int index = 0; index < depth; ++index) {
1182        FPRINTF("#");
1183    }
1184    FPRINTF(" ");
1185}
1186
1187void MdOut::populateTables(const Definition* def) {
1188    const Definition* csParent = this->csParent();
1189    for (auto child : def->fChildren) {
1190        if (MarkType::kTopic == child->fMarkType || MarkType::kSubtopic == child->fMarkType) {
1191            bool legacyTopic = fPopulators.end() != fPopulators.find(child->fName);
1192            if (!legacyTopic && child->fName != kOverview) {
1193                this->populator(kRelatedFunctions).push_back(child);
1194            }
1195            this->populateTables(child);
1196            continue;
1197        }
1198        if (child->isStructOrClass()) {
1199            if (fClassStack.size() > 0) {
1200                this->populator(kClassesAndStructs).push_back(child);
1201            }
1202            fClassStack.push_back(child);
1203            this->populateTables(child);
1204            fClassStack.pop_back();
1205            continue;
1206        }
1207        if (MarkType::kEnum == child->fMarkType || MarkType::kEnumClass == child->fMarkType) {
1208            this->populator(kConstants).push_back(child);
1209            continue;
1210        }
1211        if (MarkType::kMember == child->fMarkType) {
1212            this->populator(kMembers).push_back(child);
1213            continue;
1214        }
1215        if (MarkType::kMethod != child->fMarkType) {
1216            continue;
1217        }
1218        if (child->fClone) {
1219            continue;
1220        }
1221        if (Definition::MethodType::kConstructor == child->fMethodType
1222                || Definition::MethodType::kDestructor == child->fMethodType) {
1223            this->populator(kConstructors).push_back(child);
1224            continue;
1225        }
1226        if (Definition::MethodType::kOperator == child->fMethodType) {
1227            this->populator(kOperators).push_back(child);
1228            continue;
1229        }
1230        this->populator(kMemberFunctions).push_back(child);
1231        if (csParent && (0 == child->fName.find(csParent->fName + "::Make")
1232                || 0 == child->fName.find(csParent->fName + "::make"))) {
1233            this->populator(kConstructors).push_back(child);
1234        }
1235    }
1236}
1237
1238void MdOut::resolveOut(const char* start, const char* end, BmhParser::Resolvable resolvable) {
1239    if ((BmhParser::Resolvable::kLiteral == resolvable || fRespectLeadingSpace) && end > start) {
1240        while ('\n' == *start) {
1241            ++start;
1242        }
1243        const char* spaceStart = start;
1244        while (' ' == *start) {
1245            ++start;
1246        }
1247        if (start > spaceStart) {
1248            fIndent = start - spaceStart;
1249        }
1250        this->writeBlockTrim(end - start, start);
1251        if ('\n' == end[-1]) {
1252            this->lf(1);
1253        }
1254        fIndent = 0;
1255        return;
1256    }
1257    // FIXME: this needs the markdown character present when the def was defined,
1258    // not the last markdown character the parser would have seen...
1259    while (fBmhParser.fMC == end[-1]) {
1260        --end;
1261    }
1262    if (start >= end) {
1263        return;
1264    }
1265    string resolved = this->addReferences(start, end, resolvable);
1266    trim_end_spaces(resolved);
1267    if (resolved.length()) {
1268        TextParser paragraph(fFileName, &*resolved.begin(), &*resolved.end(), fLineCount);
1269        TextParser original(fFileName, start, end, fLineCount);
1270        while (!original.eof() && '\n' == original.peek()) {
1271            original.next();
1272        }
1273        original.skipSpace();
1274        while (!paragraph.eof()) {
1275            paragraph.skipWhiteSpace();
1276            const char* contentStart = paragraph.fChar;
1277            paragraph.skipToEndBracket('\n');
1278            ptrdiff_t lineLength = paragraph.fChar - contentStart;
1279            if (lineLength) {
1280                while (lineLength && contentStart[lineLength - 1] <= ' ') {
1281                    --lineLength;
1282                }
1283                string str(contentStart, lineLength);
1284                this->writeString(str.c_str());
1285            }
1286#if 0
1287            int linefeeds = 0;
1288            while (lineLength > 0 && '\n' == contentStart[--lineLength]) {
1289
1290                ++linefeeds;
1291            }
1292            if (lineLength > 0) {
1293                this->nl();
1294            }
1295            fLinefeeds += linefeeds;
1296#endif
1297            if (paragraph.eof()) {
1298                break;
1299            }
1300            if ('\n' == paragraph.next()) {
1301                int linefeeds = 1;
1302                if (!paragraph.eof() && '\n' == paragraph.peek()) {
1303                    linefeeds = 2;
1304                }
1305                this->lf(linefeeds);
1306            }
1307        }
1308#if 0
1309        while (end > start && end[0] == '\n') {
1310            FPRINTF("\n");
1311            --end;
1312        }
1313#endif
1314    }
1315}
1316
1317void MdOut::rowOut(const char* name, const string& description) {
1318    this->lfAlways(1);
1319    FPRINTF("| ");
1320    this->resolveOut(name, name + strlen(name), BmhParser::Resolvable::kYes);
1321    FPRINTF(" | ");
1322    this->resolveOut(&description.front(), &description.back() + 1, BmhParser::Resolvable::kYes);
1323    FPRINTF(" |");
1324    this->lf(1);
1325}
1326
1327void MdOut::subtopicsOut() {
1328    const Definition* csParent = this->csParent();
1329    SkASSERT(csParent);
1330    this->rowOut("name", "description");
1331    this->rowOut("---", "---");
1332    for (auto item : { kClassesAndStructs, kConstants, kConstructors, kMemberFunctions,
1333            kMembers, kOperators, kRelatedFunctions } ) {
1334        for (auto entry : this->populator(item)) {
1335            if (entry->csParent() == csParent) {
1336                string description = fPopulators.find(item)->second.fDescription;
1337                if (kConstructors == item) {
1338                    description += " " + csParent->fName;
1339                }
1340                this->rowOut(item, description);
1341                break;
1342            }
1343        }
1344    }
1345}
1346
1347void MdOut::subtopicOut(vector<const Definition*>& data) {
1348    const Definition* csParent = this->csParent();
1349    SkASSERT(csParent);
1350    fRoot = csParent->asRoot();
1351    this->rowOut("name", "description");
1352    this->rowOut("---", "---");
1353    std::map<string, const Definition*> items;
1354    for (auto entry : data) {
1355        if (entry->csParent() != csParent) {
1356            continue;
1357        }
1358        size_t start = entry->fName.find_last_of("::");
1359        string name = entry->fName.substr(string::npos == start ? 0 : start + 1);
1360        items[name] = entry;
1361    }
1362    for (auto entry : items) {
1363        const Definition* oneLiner = nullptr;
1364        for (auto child : entry.second->fChildren) {
1365            if (MarkType::kLine == child->fMarkType) {
1366                oneLiner = child;
1367                break;
1368            }
1369        }
1370        SkASSERT(oneLiner);
1371        this->rowOut(entry.first.c_str(), string(oneLiner->fContentStart,
1372            oneLiner->fContentEnd - oneLiner->fContentStart));
1373    }
1374}
1375