brintos

brintos / llvm-project-archived public Read only

0
0
Text · 15.3 KiB · d779c84 Raw
459 lines · cpp
1//===-- ClangOptionDocEmitter.cpp - Documentation for command line flags --===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7// FIXME: Once this has stabilized, consider moving it to LLVM.8//9//===----------------------------------------------------------------------===//10 11#include "TableGenBackends.h"12#include "llvm/ADT/STLExtras.h"13#include "llvm/ADT/StringSwitch.h"14#include "llvm/ADT/Twine.h"15#include "llvm/TableGen/Error.h"16#include "llvm/TableGen/Record.h"17#include "llvm/TableGen/TableGenBackend.h"18#include <cctype>19#include <cstring>20#include <map>21 22using namespace llvm;23 24namespace {25struct DocumentedOption {26  const Record *Option;27  std::vector<const Record *> Aliases;28};29struct DocumentedGroup;30struct Documentation {31  std::vector<DocumentedGroup> Groups;32  std::vector<DocumentedOption> Options;33 34  bool empty() {35    return Groups.empty() && Options.empty();36  }37};38struct DocumentedGroup : Documentation {39  const Record *Group;40};41 42static bool hasFlag(const Record *Option, StringRef OptionFlag,43                    StringRef FlagsField) {44  for (const Record *Flag : Option->getValueAsListOfDefs(FlagsField))45    if (Flag->getName() == OptionFlag)46      return true;47  if (const DefInit *DI = dyn_cast<DefInit>(Option->getValueInit("Group")))48    for (const Record *Flag : DI->getDef()->getValueAsListOfDefs(FlagsField))49      if (Flag->getName() == OptionFlag)50        return true;51  return false;52}53 54static bool isOptionVisible(const Record *Option, const Record *DocInfo) {55  for (StringRef IgnoredFlag : DocInfo->getValueAsListOfStrings("IgnoreFlags"))56    if (hasFlag(Option, IgnoredFlag, "Flags"))57      return false;58  for (StringRef Mask : DocInfo->getValueAsListOfStrings("VisibilityMask"))59    if (hasFlag(Option, Mask, "Visibility"))60      return true;61  return false;62}63 64// Reorganize the records into a suitable form for emitting documentation.65Documentation extractDocumentation(const RecordKeeper &Records,66                                   const Record *DocInfo) {67  Documentation Result;68 69  // Build the tree of groups. The root in the tree is the fake option group70  // (Record*)nullptr, which contains all top-level groups and options.71  std::map<const Record *, std::vector<const Record *>> OptionsInGroup;72  std::map<const Record *, std::vector<const Record *>> GroupsInGroup;73  std::map<const Record *, std::vector<const Record *>> Aliases;74 75  std::map<std::string, const Record *> OptionsByName;76  for (const Record *R : Records.getAllDerivedDefinitions("Option"))77    OptionsByName[std::string(R->getValueAsString("Name"))] = R;78 79  auto Flatten = [](const Record *R) {80    return R->getValue("DocFlatten") && R->getValueAsBit("DocFlatten");81  };82 83  auto SkipFlattened = [&](const Record *R) -> const Record * {84    while (R && Flatten(R)) {85      auto *G = dyn_cast<DefInit>(R->getValueInit("Group"));86      if (!G)87        return nullptr;88      R = G->getDef();89    }90    return R;91  };92 93  for (const Record *R : Records.getAllDerivedDefinitions("OptionGroup")) {94    if (Flatten(R))95      continue;96 97    const Record *Group = nullptr;98    if (auto *G = dyn_cast<DefInit>(R->getValueInit("Group")))99      Group = SkipFlattened(G->getDef());100    GroupsInGroup[Group].push_back(R);101  }102 103  for (const Record *R : Records.getAllDerivedDefinitions("Option")) {104    if (auto *A = dyn_cast<DefInit>(R->getValueInit("Alias"))) {105      Aliases[A->getDef()].push_back(R);106      continue;107    }108 109    // Pretend no-X and Xno-Y options are aliases of X and XY.110    std::string Name = std::string(R->getValueAsString("Name"));111    if (Name.size() >= 4) {112      if (Name.substr(0, 3) == "no-") {113        if (const Record *Opt = OptionsByName[Name.substr(3)]) {114          Aliases[Opt].push_back(R);115          continue;116        }117      }118      if (Name.substr(1, 3) == "no-") {119        if (const Record *Opt = OptionsByName[Name[0] + Name.substr(4)]) {120          Aliases[Opt].push_back(R);121          continue;122        }123      }124    }125 126    const Record *Group = nullptr;127    if (auto *G = dyn_cast<DefInit>(R->getValueInit("Group")))128      Group = SkipFlattened(G->getDef());129    OptionsInGroup[Group].push_back(R);130  }131 132  auto CompareByName = [](const Record *A, const Record *B) {133    return A->getValueAsString("Name") < B->getValueAsString("Name");134  };135 136  auto CompareByLocation = [](const Record *A, const Record *B) {137    return A->getLoc()[0].getPointer() < B->getLoc()[0].getPointer();138  };139 140  auto DocumentationForOption = [&](const Record *R) -> DocumentedOption {141    auto &A = Aliases[R];142    sort(A, CompareByName);143    return {R, std::move(A)};144  };145 146  std::function<Documentation(const Record *)> DocumentationForGroup =147      [&](const Record *R) -> Documentation {148    Documentation D;149 150    auto &Groups = GroupsInGroup[R];151    sort(Groups, CompareByLocation);152    for (const Record *G : Groups) {153      D.Groups.emplace_back();154      D.Groups.back().Group = G;155      Documentation &Base = D.Groups.back();156      Base = DocumentationForGroup(G);157      if (Base.empty())158        D.Groups.pop_back();159    }160 161    auto &Options = OptionsInGroup[R];162    sort(Options, CompareByName);163    for (const Record *O : Options)164      if (isOptionVisible(O, DocInfo))165        D.Options.push_back(DocumentationForOption(O));166 167    return D;168  };169 170  return DocumentationForGroup(nullptr);171}172 173// Get the first and successive separators to use for an OptionKind.174std::pair<StringRef,StringRef> getSeparatorsForKind(const Record *OptionKind) {175  return StringSwitch<std::pair<StringRef, StringRef>>(OptionKind->getName())176      .Cases({"KIND_JOINED", "KIND_JOINED_OR_SEPARATE",177              "KIND_JOINED_AND_SEPARATE", "KIND_REMAINING_ARGS_JOINED"},178             {"", " "})179      .Case("KIND_COMMAJOINED", {"", ","})180      .Default({" ", " "});181}182 183const unsigned UnlimitedArgs = unsigned(-1);184 185// Get the number of arguments expected for an option, or -1 if any number of186// arguments are accepted.187unsigned getNumArgsForKind(const Record *OptionKind, const Record *Option) {188  return StringSwitch<unsigned>(OptionKind->getName())189      .Cases({"KIND_JOINED", "KIND_JOINED_OR_SEPARATE", "KIND_SEPARATE"}, 1)190      .Cases({"KIND_REMAINING_ARGS", "KIND_REMAINING_ARGS_JOINED",191              "KIND_COMMAJOINED"},192             UnlimitedArgs)193      .Case("KIND_JOINED_AND_SEPARATE", 2)194      .Case("KIND_MULTIARG", Option->getValueAsInt("NumArgs"))195      .Default(0);196}197 198std::string escapeRST(StringRef Str) {199  std::string Out;200  for (auto K : Str) {201    if (StringRef("`*|[]\\").count(K))202      Out.push_back('\\');203    Out.push_back(K);204  }205  return Out;206}207 208StringRef getSphinxOptionID(StringRef OptionName) {209  return OptionName.take_while([](char C) { return isalnum(C) || C == '-'; });210}211 212bool canSphinxCopeWithOption(const Record *Option) {213  // HACK: Work arond sphinx's inability to cope with punctuation-only options214  // such as /? by suppressing them from the option list.215  for (char C : Option->getValueAsString("Name"))216    if (isalnum(C))217      return true;218  return false;219}220 221void emitHeading(int Depth, std::string Heading, raw_ostream &OS) {222  assert(Depth < 8 && "groups nested too deeply");223  OS << Heading << '\n'224     << std::string(Heading.size(), "=~-_'+<>"[Depth]) << "\n";225}226 227/// Get the value of field \p Primary, if possible. If \p Primary does not228/// exist, get the value of \p Fallback and escape it for rST emission.229std::string getRSTStringWithTextFallback(const Record *R, StringRef Primary,230                                         StringRef Fallback) {231  for (auto Field : {Primary, Fallback}) {232    if (auto *V = R->getValue(Field)) {233      StringRef Value;234      if (auto *SV = dyn_cast_or_null<StringInit>(V->getValue()))235        Value = SV->getValue();236      if (!Value.empty())237        return Field == Primary ? Value.str() : escapeRST(Value);238    }239  }240  return std::string(StringRef());241}242 243void emitOptionWithArgs(StringRef Prefix, const Record *Option,244                        ArrayRef<StringRef> Args, raw_ostream &OS) {245  OS << Prefix << escapeRST(Option->getValueAsString("Name"));246 247  std::pair<StringRef, StringRef> Separators =248      getSeparatorsForKind(Option->getValueAsDef("Kind"));249 250  StringRef Separator = Separators.first;251  for (auto Arg : Args) {252    OS << Separator << escapeRST(Arg);253    Separator = Separators.second;254  }255}256 257constexpr StringLiteral DefaultMetaVarName = "<arg>";258 259void emitOptionName(StringRef Prefix, const Record *Option, raw_ostream &OS) {260  // Find the arguments to list after the option.261  unsigned NumArgs = getNumArgsForKind(Option->getValueAsDef("Kind"), Option);262  bool HasMetaVarName = !Option->isValueUnset("MetaVarName");263 264  std::vector<std::string> Args;265  if (HasMetaVarName)266    Args.push_back(std::string(Option->getValueAsString("MetaVarName")));267  else if (NumArgs == 1)268    Args.push_back(DefaultMetaVarName.str());269 270  // Fill up arguments if this option didn't provide a meta var name or it271  // supports an unlimited number of arguments. We can't see how many arguments272  // already are in a meta var name, so assume it has right number. This is273  // needed for JoinedAndSeparate options so that there arent't too many274  // arguments.275  if (!HasMetaVarName || NumArgs == UnlimitedArgs) {276    while (Args.size() < NumArgs) {277      Args.push_back(("<arg" + Twine(Args.size() + 1) + ">").str());278      // Use '--args <arg1> <arg2>...' if any number of args are allowed.279      if (Args.size() == 2 && NumArgs == UnlimitedArgs) {280        Args.back() += "...";281        break;282      }283    }284  }285 286  emitOptionWithArgs(Prefix, Option,287                     std::vector<StringRef>(Args.begin(), Args.end()), OS);288 289  auto AliasArgs = Option->getValueAsListOfStrings("AliasArgs");290  if (!AliasArgs.empty()) {291    const Record *Alias = Option->getValueAsDef("Alias");292    OS << " (equivalent to ";293    emitOptionWithArgs(294        Alias->getValueAsListOfStrings("Prefixes").front(), Alias,295        AliasArgs, OS);296    OS << ")";297  }298}299 300bool emitOptionNames(const Record *Option, raw_ostream &OS, bool EmittedAny) {301  for (auto &Prefix : Option->getValueAsListOfStrings("Prefixes")) {302    if (EmittedAny)303      OS << ", ";304    emitOptionName(Prefix, Option, OS);305    EmittedAny = true;306  }307  return EmittedAny;308}309 310template <typename Fn>311void forEachOptionName(const DocumentedOption &Option, const Record *DocInfo,312                       Fn F) {313  F(Option.Option);314 315  for (auto *Alias : Option.Aliases)316    if (isOptionVisible(Alias, DocInfo) &&317        canSphinxCopeWithOption(Option.Option))318      F(Alias);319}320 321void emitOption(const DocumentedOption &Option, const Record *DocInfo,322                raw_ostream &OS) {323  if (Option.Option->getValueAsDef("Kind")->getName() == "KIND_UNKNOWN" ||324      Option.Option->getValueAsDef("Kind")->getName() == "KIND_INPUT")325    return;326  if (!canSphinxCopeWithOption(Option.Option))327    return;328 329  // HACK: Emit a different program name with each option to work around330  // sphinx's inability to cope with options that differ only by punctuation331  // (eg -ObjC vs -ObjC++, -G vs -G=).332  std::vector<std::string> SphinxOptionIDs;333  forEachOptionName(Option, DocInfo, [&](const Record *Option) {334    for (auto &Prefix : Option->getValueAsListOfStrings("Prefixes"))335      SphinxOptionIDs.push_back(std::string(getSphinxOptionID(336          (Prefix + Option->getValueAsString("Name")).str())));337  });338  assert(!SphinxOptionIDs.empty() && "no flags for option");339  static std::map<std::string, int> NextSuffix;340  int SphinxWorkaroundSuffix = NextSuffix[*llvm::max_element(341      SphinxOptionIDs, [&](const std::string &A, const std::string &B) {342        return NextSuffix[A] < NextSuffix[B];343      })];344  for (auto &S : SphinxOptionIDs)345    NextSuffix[S] = SphinxWorkaroundSuffix + 1;346 347  std::string Program = DocInfo->getValueAsString("Program").lower();348  if (SphinxWorkaroundSuffix)349    OS << ".. program:: " << Program << SphinxWorkaroundSuffix << "\n";350 351  // Emit the names of the option.352  OS << ".. option:: ";353  bool EmittedAny = false;354  forEachOptionName(Option, DocInfo, [&](const Record *Option) {355    EmittedAny = emitOptionNames(Option, OS, EmittedAny);356  });357  if (SphinxWorkaroundSuffix)358    OS << "\n.. program:: " << Program;359  OS << "\n\n";360 361  // Emit the description, if we have one.362  const Record *R = Option.Option;363  std::string Description;364 365  // Prefer a program specific help string.366  // This is a list of (visibilities, string) pairs.367  for (const Record *VisibilityHelp :368       R->getValueAsListOfDefs("HelpTextsForVariants")) {369    // This is a list of visibilities.370    ArrayRef<const Init *> Visibilities =371        VisibilityHelp->getValueAsListInit("Visibilities")->getElements();372 373    // See if any of the program's visibilities are in the list.374    for (StringRef DocInfoMask :375         DocInfo->getValueAsListOfStrings("VisibilityMask")) {376      for (const Init *Visibility : Visibilities) {377        if (Visibility->getAsUnquotedString() == DocInfoMask) {378          // Use the first one we find.379          Description = escapeRST(VisibilityHelp->getValueAsString("Text"));380          break;381        }382      }383      if (!Description.empty())384        break;385    }386 387    if (!Description.empty())388      break;389  }390 391  // If there's not a program specific string, use the default one.392  if (Description.empty())393    Description = getRSTStringWithTextFallback(R, "DocBrief", "HelpText");394 395  if (!isa<UnsetInit>(R->getValueInit("Values"))) {396    if (!Description.empty() && Description.back() != '.')397      Description.push_back('.');398 399    StringRef MetaVarName;400    if (!isa<UnsetInit>(R->getValueInit("MetaVarName")))401      MetaVarName = R->getValueAsString("MetaVarName");402    else403      MetaVarName = DefaultMetaVarName;404 405    SmallVector<StringRef> Values;406    SplitString(R->getValueAsString("Values"), Values, ",");407    Description += (" " + MetaVarName + " must be '").str();408    if (Values.size() > 1) {409      Description += join(Values.begin(), Values.end() - 1, "', '");410      Description += "' or '";411    }412    Description += (Values.back() + "'.").str();413  }414 415  if (!Description.empty())416    OS << Description << "\n\n";417}418 419void emitDocumentation(int Depth, const Documentation &Doc,420                       const Record *DocInfo, raw_ostream &OS);421 422void emitGroup(int Depth, const DocumentedGroup &Group, const Record *DocInfo,423               raw_ostream &OS) {424  emitHeading(Depth,425              getRSTStringWithTextFallback(Group.Group, "DocName", "Name"), OS);426 427  // Emit the description, if we have one.428  std::string Description =429      getRSTStringWithTextFallback(Group.Group, "DocBrief", "HelpText");430  if (!Description.empty())431    OS << Description << "\n\n";432 433  // Emit contained options and groups.434  emitDocumentation(Depth + 1, Group, DocInfo, OS);435}436 437void emitDocumentation(int Depth, const Documentation &Doc,438                       const Record *DocInfo, raw_ostream &OS) {439  for (auto &O : Doc.Options)440    emitOption(O, DocInfo, OS);441  for (auto &G : Doc.Groups)442    emitGroup(Depth, G, DocInfo, OS);443}444 445}  // namespace446 447void clang::EmitClangOptDocs(const RecordKeeper &Records, raw_ostream &OS) {448  const Record *DocInfo = Records.getDef("GlobalDocumentation");449  if (!DocInfo) {450    PrintFatalError("The GlobalDocumentation top-level definition is missing, "451                    "no documentation will be generated.");452    return;453  }454  OS << DocInfo->getValueAsString("Intro") << "\n";455  OS << ".. program:: " << DocInfo->getValueAsString("Program").lower() << "\n";456 457  emitDocumentation(0, extractDocumentation(Records, DocInfo), DocInfo, OS);458}459