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 += "<"; 28 } else if ('>' == c) { 29 result += ">"; 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