CommandLine 2.0 库手册

简介

本文档介绍了 CommandLine 参数处理库。它将向您展示如何使用它以及它可以做什么。CommandLine 库使用声明式方法来指定您的程序接受的命令行选项。默认情况下,这些选项声明隐式地保存为声明的选项解析的值(当然,这 可以更改)。

尽管在许多不同的语言中都有大量的命令行参数解析库,但它们都不太符合我的需求。通过查看其他库的功能和问题,我将 CommandLine 库设计为具有以下功能

  1. 速度:CommandLine 库非常快速且占用资源少。库的解析时间与解析的参数数量成正比,而不是与识别的选项数量成正比。此外,命令行参数值被透明地捕获到用户定义的全局变量中,这些变量可以像任何其他变量一样访问(并且具有相同的性能)。

  2. 类型安全:作为 CommandLine 的用户,您不必担心记住您想要的参数类型(是 int?字符串?bool?枚举?)并不断地进行类型转换。这不仅有助于防止容易出错的结构,而且还可以使源代码更加简洁。

  3. 无需子类化:要使用 CommandLine,您只需实例化与您想要捕获的参数相对应的变量,而无需子类化解析器。这意味着您不必编写任何样板代码。

  4. 全局可访问:库可以指定在链接到该库的任何工具中自动启用的命令行参数。这是可能的,因为应用程序不必保留要传递给解析器的参数列表。这也使得支持动态加载的选项变得非常简单。

  5. 更简洁:CommandLine 直接支持枚举和其他类型,这意味着库中内置了更少的错误和更高的安全性。您不必担心您的整数命令行参数意外地被分配了对其枚举类型无效的值。

  6. 强大:CommandLine 库支持许多不同类型的参数,从简单的 布尔标志标量参数字符串整数枚举双精度浮点数),再到 参数列表。这是可能的,因为 CommandLine 是…

  7. 可扩展:向 CommandLine 添加新的参数类型非常简单。只需在声明命令行选项时指定您想要使用的解析器。自定义解析器 也没有问题。

  8. 节省劳力:CommandLine 库减少了您(用户)必须做的繁琐工作量。例如,它自动提供一个 -help 选项,显示您的工具可用的命令行选项。此外,它还为您完成大部分基本正确性检查。

  9. 功能强大:CommandLine 库可以处理在实际程序中经常发现的许多不同形式的选项。例如,位置参数,ls 风格的 分组 选项(以允许自然地处理 ‘ls -lad’),ld 风格的 前缀 选项(以解析 ‘-lmalloc -L/usr/lib’)和解释器风格的选项。

本文档有望让您快速轻松地开始在您的实用程序中使用 CommandLine。此外,它应该是一个简单的参考手册,用于了解事物是如何工作的。

快速入门指南

本手册的这一部分将引导您完成一个基本编译器工具的简单 CommandLine 化过程。旨在向您展示如何在您自己的程序中开始使用 CommandLine 库,并向您展示它可以做的一些很酷的事情。

首先,您需要将 CommandLine 头文件包含到您的程序中

#include "llvm/Support/CommandLine.h"

此外,您需要将此作为您的主程序的第一行添加

int main(int argc, char **argv) {
  cl::ParseCommandLineOptions(argc, argv);
  ...
}

… 它实际上解析了参数并填充了变量声明。

现在您已准备好支持命令行参数,我们需要告诉系统我们想要哪些参数以及它们的参数类型。CommandLine 库使用声明式语法来建模命令行参数,并使用捕获解析值的全局变量声明。这意味着对于您想要支持的每个命令行选项,都应该有一个全局变量声明来捕获结果。例如,在编译器中,我们希望支持 Unix 标准的 ‘-o <filename>’ 选项来指定输出位置。使用 CommandLine 库,这表示为

cl::opt<string> OutputFilename("o", cl::desc("Specify output filename"), cl::value_desc("filename"));

这声明了一个全局变量 “OutputFilename”,用于捕获 “o” 参数(第一个参数)的结果。我们通过使用 “cl::opt” 模板(而不是 “cl::list” 模板)来指定这是一个简单的标量选项,并告诉 CommandLine 库我们正在解析的数据类型是字符串。

第二个和第三个参数(是可选的)用于指定 “-help” 选项的输出内容。在这种情况下,我们得到一行如下所示的内容

USAGE: compiler [options]

OPTIONS:
  -h                - Alias for -help
  -help             - display available options (-help-hidden for more)
  -o <filename>     - Specify output filename

因为我们指定命令行选项应使用 string 数据类型进行解析,所以声明的变量可以自动用作真实字符串,在所有可以使用普通 C++ 字符串对象的上下文中均可使用。例如

...
std::ofstream Output(OutputFilename.c_str());
if (Output.good()) ...
...

您可以使用许多不同的选项来自定义命令行选项处理库,但上面的示例显示了这些选项的通用接口。选项可以按任何顺序指定,并使用 cl::desc(…) 等辅助函数指定,因此无需记住位置依赖关系。可用选项在参考指南中详细讨论。

继续该示例,我们希望我们的编译器接受输入文件名以及输出文件名,但我们不希望输入文件名使用连字符指定(即,不是 -filename.c)。为了支持这种风格的参数,CommandLine 库允许为程序指定位置参数。这些位置参数填充了非选项形式的命令行参数。我们像这样使用此功能

cl::opt<string> InputFilename(cl::Positional, cl::desc("<input file>"), cl::init("-"));

此声明指示第一个位置参数应被视为输入文件名。这里我们使用 cl::init 选项来为命令行选项指定初始值,如果未指定该选项,则使用该初始值(如果您未为选项指定 cl::init 修饰符,则使用数据类型的默认构造函数来初始化该值)。命令行选项默认为可选,因此如果我们希望用户始终指定输入文件名,我们将添加 cl::Required 标志,并且我们可以消除 cl::init 修饰符,如下所示

cl::opt<string> InputFilename(cl::Positional, cl::desc("<input file>"), cl::Required);

同样,CommandLine 库不要求按任何特定顺序指定选项,因此上面的声明等效于

cl::opt<string> InputFilename(cl::Positional, cl::Required, cl::desc("<input file>"));

通过简单地添加 cl::Required 标志,如果未指定参数,CommandLine 库将自动发出错误,这会将所有命令行选项验证代码从您的应用程序转移到库中。这只是一个示例,说明如何使用标志可以更改库的默认行为,基于每个选项。通过添加上述声明之一,-help 选项概要现在扩展为

USAGE: compiler [options] <input file>

OPTIONS:
  -h                - Alias for -help
  -help             - display available options (-help-hidden for more)
  -o <filename>     - Specify output filename

… 指示需要输入文件名。

布尔参数

除了输入和输出文件名之外,我们还希望编译器示例支持三个布尔标志:“-f” 以强制将二进制输出写入终端,“--quiet” 以启用静默模式,以及 “-q” 以向后兼容我们的一些用户。我们可以通过声明布尔类型的选项来支持这些,如下所示

cl::opt<bool> Force ("f", cl::desc("Enable binary output on terminals"));
cl::opt<bool> Quiet ("quiet", cl::desc("Don't print informational messages"));
cl::opt<bool> Quiet2("q", cl::desc("Don't print informational messages"), cl::Hidden);

这会执行您期望的操作:它声明了三个布尔变量(“Force”、“Quiet” 和 “Quiet2”)以识别这些选项。请注意,“-q” 选项使用 “cl::Hidden” 标志指定。此修饰符阻止它被标准 “-help” 输出显示(请注意,它仍然在 “-help-hidden” 输出中显示)。

CommandLine 库对不同的数据类型使用不同的解析器。例如,在字符串情况下,传递给选项的参数会按字面复制到字符串变量的内容中… 然而,在布尔情况下,我们显然不能这样做,因此我们必须使用更智能的解析器。在布尔解析器的情况下,它不允许任何选项(在这种情况下,它将 true 值分配给变量),或者它允许指定值 “true” 或 “false”,允许以下任何输入

compiler -f          # No value, 'Force' == true
compiler -f=true     # Value specified, 'Force' == true
compiler -f=TRUE     # Value specified, 'Force' == true
compiler -f=FALSE    # Value specified, 'Force' == false

… 你明白了。 bool 解析器 只是将字符串值转换为布尔值,并拒绝像 ‘compiler -f=foo’ 这样的内容。 类似地,floatdoubleint 解析器的工作方式与您期望的一样,使用 ‘strtol’ 和 ‘strtod’ C 库调用将字符串值解析为指定的数据类型。

使用上面的声明,“compiler -help” 发出此内容

USAGE: compiler [options] <input file>

OPTIONS:
  -f     - Enable binary output on terminals
  -o     - Override output filename
  -quiet - Don't print informational messages
  -help  - display available options (-help-hidden for more)

并且 “compiler -help-hidden” 打印此内容

USAGE: compiler [options] <input file>

OPTIONS:
  -f     - Enable binary output on terminals
  -o     - Override output filename
  -q     - Don't print informational messages
  -quiet - Don't print informational messages
  -help  - display available options (-help-hidden for more)

这个简短的示例向您展示了如何使用 ‘cl::opt’ 类来解析简单的标量命令行参数。除了简单的标量参数之外,CommandLine 库还提供了基元来支持 CommandLine 选项别名选项列表

参数别名

到目前为止,该示例运行良好,除了我们现在需要像这样检查静默条件这一事实

...
  if (!Quiet && !Quiet2) printInformationalMessage(...);
...

… 这真是太麻烦了!我们可以使用 “cl::alias” 类使 “-q” 选项成为 “-quiet” 选项的别名,而不是为同一条件定义两个值,而是提供值本身

cl::opt<bool> Force ("f", cl::desc("Overwrite output files"));
cl::opt<bool> Quiet ("quiet", cl::desc("Don't print informational messages"));
cl::alias     QuietA("q", cl::desc("Alias for -quiet"), cl::aliasopt(Quiet));

第三行(这是我们从上面修改的唯一一行)定义了一个 “-q” 别名,每当指定该别名时,它都会更新 “Quiet” 变量(由 cl::aliasopt 修饰符指定)。由于别名不保存状态,因此程序现在唯一需要查询的是 Quiet 变量。别名的另一个优点是它们会自动从 -help 输出中隐藏自己(尽管同样,它们仍然在 -help-hidden 输出 中可见)。

现在应用程序代码可以简单地使用

...
  if (!Quiet) printInformationalMessage(...);
...

… 这要好得多! “cl::alias” 可用于为任何变量类型指定备用名称,并且用途广泛。

从一组可能性中选择一个替代方案

到目前为止,我们已经了解了 CommandLine 库如何处理内置类型,如 std::stringboolint,但它如何处理它不了解的内容,如枚举或 ‘int*’ 呢?

答案是它使用表驱动的通用解析器(除非您指定自己的解析器,如扩展指南中所述)。此解析器将文字字符串映射到所需的任何类型,并要求您告诉它此映射应该是什么。

假设我们想为我们的优化器添加四个优化级别,使用标准标志 “-g”、“-O0”、“-O1” 和 “-O2”。我们可以使用上面的布尔选项轻松实现这一点,但此策略存在几个问题

  1. 用户可以一次指定多个选项,例如,“compiler -O3 -O2”。 CommandLine 库将无法为我们捕获此错误输入。

  2. 我们将不得不测试 4 个不同的变量,以查看设置了哪些变量。

  3. 这没有映射到我们想要的数字级别… 因此我们无法轻松查看是否启用了某个级别 >= “-O1”。

为了应对这些问题,我们可以使用枚举值,并让 CommandLine 库直接使用适当的级别填充它,如下所示

enum OptLevel {
  g, O1, O2, O3
};

cl::opt<OptLevel> OptimizationLevel(cl::desc("Choose optimization level:"),
  cl::values(
    clEnumVal(g , "No optimizations, enable debugging"),
    clEnumVal(O1, "Enable trivial optimizations"),
    clEnumVal(O2, "Enable default optimizations"),
    clEnumVal(O3, "Enable expensive optimizations")));

...
  if (OptimizationLevel >= O2) doPartialRedundancyElimination(...);
...

此声明定义了一个 “OptLevel” 枚举类型的变量 “OptimizationLevel”。此变量可以分配声明中列出的任何值。CommandLine 库强制用户只能指定一个选项,并确保只能指定有效的枚举值。“clEnumVal” 宏确保命令行参数与枚举值匹配。添加此选项后,我们的帮助输出现在是

USAGE: compiler [options] <input file>

OPTIONS:
  Choose optimization level:
    -g          - No optimizations, enable debugging
    -O1         - Enable trivial optimizations
    -O2         - Enable default optimizations
    -O3         - Enable expensive optimizations
  -f            - Enable binary output on terminals
  -help         - display available options (-help-hidden for more)
  -o <filename> - Specify output filename
  -quiet        - Don't print informational messages

在这种情况下,标志名称直接对应于枚举名称有点尴尬,因为我们可能不希望在我们的程序中有一个名为 “g” 的枚举定义。因此,我们可以选择像这样编写此示例

enum OptLevel {
  Debug, O1, O2, O3
};

cl::opt<OptLevel> OptimizationLevel(cl::desc("Choose optimization level:"),
  cl::values(
   clEnumValN(Debug, "g", "No optimizations, enable debugging"),
    clEnumVal(O1        , "Enable trivial optimizations"),
    clEnumVal(O2        , "Enable default optimizations"),
    clEnumVal(O3        , "Enable expensive optimizations")));

...
  if (OptimizationLevel == Debug) outputDebugInfo(...);
...

通过使用 “clEnumValN” 宏而不是 “clEnumVal”,我们可以直接指定标志应获取的名称。通常,直接映射很好,但有时您不能或不想保留映射,这时您将使用它。

命名替代方案

另一种有用的参数形式是命名替代方案样式。我们将在我们的编译器中使用这种样式来指定可以使用的不同调试级别。我们不希望每个调试级别都是其自身的开关,而是希望支持以下选项,其中一次只能指定一个选项:“--debug-level=none”、“--debug-level=quick”、“--debug-level=detailed”。为此,我们使用与我们的优化级别标志完全相同的格式,但我们还指定一个选项名称。对于这种情况,代码如下所示

enum DebugLev {
  nodebuginfo, quick, detailed
};

// Enable Debug Options to be specified on the command line
cl::opt<DebugLev> DebugLevel("debug_level", cl::desc("Set the debugging level:"),
  cl::values(
    clEnumValN(nodebuginfo, "none", "disable debug information"),
     clEnumVal(quick,               "enable quick debug information"),
     clEnumVal(detailed,            "enable detailed debug information")));

此定义定义了一个 “enum DebugLev” 类型的枚举命令行变量,其工作方式与以前完全相同。这里的区别仅在于向您的程序用户公开的界面以及 “-help” 选项的帮助输出

USAGE: compiler [options] <input file>

OPTIONS:
  Choose optimization level:
    -g          - No optimizations, enable debugging
    -O1         - Enable trivial optimizations
    -O2         - Enable default optimizations
    -O3         - Enable expensive optimizations
  -debug_level  - Set the debugging level:
    =none       - disable debug information
    =quick      - enable quick debug information
    =detailed   - enable detailed debug information
  -f            - Enable binary output on terminals
  -help         - display available options (-help-hidden for more)
  -o <filename> - Specify output filename
  -quiet        - Don't print informational messages

同样,调试级别声明和优化级别声明之间唯一的结构差异是调试级别声明包含一个选项名称("debug_level"),这会自动更改库处理参数的方式。CommandLine 库同时支持这两种形式,以便您可以选择最适合您的应用程序的形式。

解析选项列表

现在我们已经解决了标准的日常参数类型,让我们变得有点疯狂。假设我们希望我们的优化器接受要执行的优化列表,允许重复项。例如,我们可能想要运行:“compiler -dce -instsimplify -inline -dce -strip”。在这种情况下,参数的顺序和出现次数非常重要。这就是 “cl::list” 模板的用途。首先,从定义您想要执行的优化的枚举开始

enum Opts {
  // 'inline' is a C++ keyword, so name it 'inlining'
  dce, instsimplify, inlining, strip
};

然后定义您的 “cl::list” 变量

cl::list<Opts> OptimizationList(cl::desc("Available Optimizations:"),
  cl::values(
    clEnumVal(dce               , "Dead Code Elimination"),
    clEnumVal(instsimplify      , "Instruction Simplification"),
   clEnumValN(inlining, "inline", "Procedure Integration"),
    clEnumVal(strip             , "Strip Symbols")));

这定义了一个概念上类型为 “std::vector<enum Opts>” 的变量。因此,您可以使用标准向量方法访问它

for (unsigned i = 0; i != OptimizationList.size(); ++i)
  switch (OptimizationList[i])
     ...

… 迭代指定的选项列表。

请注意,“cl::list” 模板是完全通用的,可以与您可以使用 “cl::opt” 模板的任何数据类型或其他参数一起使用。使用列表的一种特别有用的方法是将所有位置参数一起捕获,如果可能指定多个位置参数。例如,在链接器的情况下,链接器接受多个 ‘.o’ 文件,并且需要将它们捕获到列表中。这自然被指定为

...
cl::list<std::string> InputFilenames(cl::Positional, cl::desc("<Input files>"), cl::OneOrMore);
...

此变量的工作方式与 “vector<string>” 对象完全相同。因此,访问列表很简单,就像上面一样。在此示例中,我们使用了 cl::OneOrMore 修饰符来通知 CommandLine 库,如果用户未在我们的命令行中指定任何 .o 文件,则会出错。同样,这只是减少了我们必须进行的检查量。

将选项收集为一组标志

除了在列表中收集选项集之外,还可以将枚举值的信息收集到位向量中。cl::bits 类使用的表示形式是 unsigned 整数。枚举值由枚举的序数值位位置中的 0/1 表示。1 表示指定了枚举,0 表示未指定。当解析每个指定值时,结果枚举的位会在选项的位向量中设置

bits |= 1 << (unsigned)enum;

多次指定的选项是冗余的。第一个之后的任何实例都将被丢弃。

重新处理上面的列表示例,我们可以用 cl::bits 替换 cl::list

cl::bits<Opts> OptimizationBits(cl::desc("Available Optimizations:"),
  cl::values(
    clEnumVal(dce               , "Dead Code Elimination"),
    clEnumVal(instsimplify      , "Instruction Simplification"),
   clEnumValN(inlining, "inline", "Procedure Integration"),
    clEnumVal(strip             , "Strip Symbols")));

要测试是否指定了 instsimplify,我们可以使用 cl:bits::isSet 函数

if (OptimizationBits.isSet(instsimplify)) {
  ...
}

也可以使用 cl::bits::getBits 函数获取原始位向量

unsigned bits = OptimizationBits.getBits();

最后,如果使用外部存储,则指定的位置必须是 type unsigned。在所有其他方面,cl::bits 选项等效于 cl::list 选项。

向帮助输出添加自由格式文本

随着我们的程序不断增长和变得更加成熟,我们可能会决定将有关其功能的摘要信息放入帮助输出中。帮助输出的样式类似于 Unix man 页面,提供有关程序的简洁信息。然而,Unix man 页面通常包含有关程序功能的描述。要将其添加到您的 CommandLine 程序中,只需将第三个参数传递给 main 中的 cl::ParseCommandLineOptions 调用。然后,此附加参数将作为程序的概述信息打印,允许您包含您想要的任何其他信息。例如

int main(int argc, char **argv) {
  cl::ParseCommandLineOptions(argc, argv, " CommandLine compiler example\n\n"
                              "  This program blah blah blah...\n");
  ...
}

将产生帮助输出

**OVERVIEW: CommandLine compiler example

  This program blah blah blah...**

USAGE: compiler [options] <input file>

OPTIONS:
  ...
  -help             - display available options (-help-hidden for more)
  -o <filename>     - Specify output filename

将选项分组到类别中

如果我们的程序有大量选项,我们的工具用户可能难以浏览 -help 的输出。为了缓解这个问题,我们可以将我们的选项放入类别中。这可以通过声明选项类别(cl::OptionCategory 对象),然后使用 cl::cat 选项属性将我们的选项放入这些类别中来完成。例如

cl::OptionCategory StageSelectionCat("Stage Selection Options",
                                     "These control which stages are run.");

cl::opt<bool> Preprocessor("E",cl::desc("Run preprocessor stage."),
                           cl::cat(StageSelectionCat));

cl::opt<bool> NoLink("c",cl::desc("Run all stages except linking."),
                     cl::cat(StageSelectionCat));

如果声明了选项类别,则 -help 的输出将变为分类的。输出看起来像

OVERVIEW: This is a small program to demo the LLVM CommandLine API
USAGE: Sample [options]

OPTIONS:

  General options:

    -help              - Display available options (-help-hidden for more)
    -help-list         - Display list of available options (-help-list-hidden for more)


  Stage Selection Options:
  These control which stages are run.

    -E                 - Run preprocessor stage.
    -c                 - Run all stages except linking.

除了在声明选项类别时 -help 的行为发生更改之外,命令行选项 -help-list 变为可见,它将以未分类列表的形式打印命令行选项。

请注意,未显式分类的选项将放置在 cl::getGeneralCategory() 类别中。

参考指南

现在您已经了解了如何使用 CommandLine 库的基础知识,本节将为您提供调整命令行选项工作方式所需的详细信息,以及有关更“高级”命令行选项处理功能的信息。

位置参数

位置参数是那些未命名且未使用连字符指定的参数。当仅通过位置指定选项时,应使用位置参数。例如,标准 Unix grep 工具接受正则表达式参数,以及可选的要搜索的文件名(如果未指定文件名,则默认为标准输入)。使用 CommandLine 库,这将指定为

cl::opt<string> Regex   (cl::Positional, cl::desc("<regular expression>"), cl::Required);
cl::opt<string> Filename(cl::Positional, cl::desc("<input file>"), cl::init("-"));

给定这两个选项声明,我们的 grep 替换的 -help 输出将如下所示

USAGE: spiffygrep [options] <regular expression> <input file>

OPTIONS:
  -help - display available options (-help-hidden for more)

… 并且生成的程序可以像标准 grep 工具一样使用。

位置参数按其构造顺序排序。这意味着命令行选项将根据它们在 .cpp 文件中列出的顺序进行排序,但如果位置参数在多个 .cpp 文件中定义,则不会定义排序。解决此问题的方法很简单,即在一个 .cpp 文件中定义所有位置参数。

使用连字符指定位置选项

有时您可能想要为您的位置参数指定一个以连字符开头的value(例如,在文件中搜索‘-foo’)。起初,您将很难做到这一点,因为它会尝试查找名为‘-foo’的参数,并且会失败(单引号也无法解决问题)。请注意,系统 grep 也存在同样的问题

$ spiffygrep '-foo' test.txt
Unknown command line argument '-foo'.  Try: spiffygrep -help'

$ grep '-foo' test.txt
grep: illegal option -- f
grep: illegal option -- o
grep: illegal option -- o
Usage: grep -hblcnsviw pattern file . . .

对于这个问题,您的工具和系统版本的解决方案是相同的:使用 ‘--’ 标记。当用户在命令行上指定 ‘--’ 时,它是在告诉程序,‘--’ 之后的所有选项都应被视为位置参数,而不是选项。因此,我们可以像这样使用它

$ spiffygrep -- -foo test.txt
  ...output...

使用 getPosition() 确定绝对位置

有时,一个选项可能会影响或修改另一个选项的含义。例如,考虑 gcc-x LANG 选项。这告诉 gcc 忽略后续位置参数的后缀,并强制将文件解释为包含 LANG 语言的源代码。为了正确处理这种情况,您需要知道每个参数的绝对位置,特别是列表中的参数,以便可以正确应用它们的交互。这对于像 -llibname 这样的选项也很有用,它实际上是一个以破折号开头的位置参数。

因此,一般来说,问题是您有两个以某种方式交互的 cl::list 变量。为了确保正确的交互,您可以使用 cl::list::getPosition(optnum) 方法。此方法返回 cl::listoptnum 项的绝对位置(在命令行上找到的位置)。

用法的惯用方法如下

static cl::list<std::string> Files(cl::Positional, cl::OneOrMore);
static cl::list<std::string> Libraries("l");

int main(int argc, char**argv) {
  // ...
  std::vector<std::string>::iterator fileIt = Files.begin();
  std::vector<std::string>::iterator libIt  = Libraries.begin();
  unsigned libPos = 0, filePos = 0;
  while ( 1 ) {
    if ( libIt != Libraries.end() )
      libPos = Libraries.getPosition( libIt - Libraries.begin() );
    else
      libPos = 0;
    if ( fileIt != Files.end() )
      filePos = Files.getPosition( fileIt - Files.begin() );
    else
      filePos = 0;

    if ( filePos != 0 && (libPos == 0 || filePos < libPos) ) {
      // Source File Is next
      ++fileIt;
    }
    else if ( libPos != 0 && (filePos == 0 || libPos < filePos) ) {
      // Library is next
      ++libIt;
    }
    else
      break; // we're done with the list
  }
}

请注意,出于兼容性原因,cl::opt 也支持 unsigned getPosition() 选项,该选项将提供该选项的绝对位置。您可以将与两个列表相同的方法应用于 cl::optcl::list 选项。

cl::ConsumeAfter 修饰符

cl::ConsumeAfter 格式化选项 用于构建使用 “解释器风格” 选项处理的程序。使用这种风格的选项处理,在最后一个位置参数之后指定的所有参数都被视为特殊的解释器参数,这些参数不会被命令行参数解释。

作为一个具体的例子,假设我们正在开发标准 Unix Bourne shell (/bin/sh) 的替代品。要运行 /bin/sh,首先您要指定 shell 本身的选项(例如 -x,它会打开跟踪输出),然后指定要运行的脚本的名称,然后指定脚本的参数。这些脚本的参数由 Bourne shell 命令行选项处理器解析,但不被解释为 shell 本身的选项。使用 CommandLine 库,我们会将其指定为

cl::opt<string> Script(cl::Positional, cl::desc("<input script>"), cl::init("-"));
cl::list<string>  Argv(cl::ConsumeAfter, cl::desc("<program arguments>..."));
cl::opt<bool>    Trace("x", cl::desc("Enable trace output"));

这会自动提供帮助输出

USAGE: spiffysh [options] <input script> <program arguments>...

OPTIONS:
  -help - display available options (-help-hidden for more)
  -x    - Enable trace output

在运行时,如果我们像这样运行我们的新 shell 替代品 ‘`spiffysh -x test.sh -a -x -y bar’,Trace 变量将被设置为 true,Script 变量将被设置为 “test.sh”,并且 Argv 列表将包含 ["-a", -x", -y", "bar"],因为它们是在最后一个位置参数(即脚本名称)之后指定的。

cl::ConsumeAfter 选项在指定时有几个限制。例如,每个程序只能指定一个 cl::ConsumeAfter,必须至少指定一个 位置参数,不能有任何 cl::list 位置参数,并且 cl::ConsumeAfter 选项应为 cl::list 选项。

内部存储 vs 外部存储

默认情况下,所有命令行选项都会自动保存它们从命令行解析的值。这在常见情况下非常方便,特别是当与在文件中定义命令行选项的能力相结合时。这称为内部存储模型。

然而,有时将命令行选项处理代码与解析值的存储分开会更好。例如,假设我们有一个 ‘-debug’ 选项,我们希望用它来在整个程序体中启用调试信息。在这种情况下,控制调试代码的布尔值应该是全局可访问的(例如,在一个头文件中),但命令行选项处理代码不应暴露给所有这些客户端(需要许多 .cpp 文件 #include CommandLine.h)。

为此,请在您的 .h 文件中设置您的选项,例如这样

// DebugFlag.h - Get access to the '-debug' command line option
//

// DebugFlag - This boolean is set to true if the '-debug' command line option
// is specified.  This should probably not be referenced directly, instead, use
// the DEBUG macro below.
//
extern bool DebugFlag;

// DEBUG macro - This macro should be used by code to emit debug information.
// In the '-debug' option is specified on the command line, and if this is a
// debug build, then the code specified as the option to the macro will be
// executed.  Otherwise it will not be.
#ifdef NDEBUG
#define LLVM_DEBUG(X)
#else
#define LLVM_DEBUG(X) do { if (DebugFlag) { X; } } while (0)
#endif

这允许客户端愉快地使用 LLVM_DEBUG() 宏,或者如果他们愿意,可以显式使用 DebugFlag。现在我们只需要能够在设置选项时设置 DebugFlag 布尔值。为此,我们将一个额外的参数传递给我们的命令行参数处理器,并指定在哪里使用 cl::location 属性填充。

bool DebugFlag;                  // the actual value
static cl::opt<bool, true>       // The parser
Debug("debug", cl::desc("Enable debug output"), cl::Hidden, cl::location(DebugFlag));

在上面的示例中,我们将 “true” 指定为 cl::opt 模板的第二个参数,表明该模板不应维护值本身的副本。除此之外,我们还指定了 cl::location 属性,以便自动设置 DebugFlag

选项属性

本节介绍您可以在选项上指定的基本属性。

  • 选项名称属性(对于所有选项都是必需的,除了 位置选项)指定选项名称是什么。此选项在简单的双引号中指定

    cl::opt<bool> Quiet("quiet");
    
  • cl::desc 属性指定选项的描述,该描述将显示在程序的 -help 输出中。此属性支持多行描述,行之间用 ‘n’ 分隔。

  • cl::value_desc 属性指定一个字符串,该字符串可用于微调命令行选项的 -help 输出。请点击此处查看示例。

  • cl::init 属性为 标量 选项指定初始值。如果未指定此属性,则命令行选项值默认为类型默认构造函数创建的值。

    警告

    如果您为选项同时指定 cl::initcl::location,则必须先指定 cl::location,以便当命令行解析器看到 cl::init 时,它知道将初始值放在哪里。(如果您没有将它们放在正确的顺序中,您将在运行时收到错误。)

  • cl::location 属性指定在使用外部存储时,存储解析的命令行选项值的位置。有关更多信息,请参阅关于内部存储 vs 外部存储的部分。

  • cl::aliasopt 属性指定 cl::alias 选项是哪个选项的别名。

  • cl::values 属性指定通用解析器要使用的字符串到值的映射。它采用 (选项, 值, 描述) 三元组列表,这些三元组指定选项名称、映射到的值以及 -help 中显示的描述。由于通用解析器最常用于枚举值,因此通常可以使用两个宏

    1. clEnumVal 宏用作指定枚举三元组的一种简单方法。此宏自动使选项名称与枚举名称相同。宏的第一个选项是枚举,第二个是命令行选项的描述。

    2. clEnumValN 宏用于指定选项名称与枚举名称不相同的宏选项。对于此宏,第一个参数是枚举值,第二个是标志名称,第二个是描述。

    如果您尝试将 cl::values 与不支持它的解析器一起使用,您将收到编译时错误。

  • cl::multi_val 属性指定此选项具有多个值(例如:-sectalign segname sectname sectvalue)。此属性采用一个无符号参数 - 选项的值的数量。此属性仅对 cl::list 选项有效(如果您尝试将其与其他选项类型一起使用,则会编译错误)。允许在多值选项上使用所有常用的修饰符(显然,除了 cl::ValueDisallowed 之外)。

  • cl::cat 属性指定选项所属的选项类别。类别应为 cl::OptionCategory 对象。

  • cl::callback 属性指定在看到选项时调用的回调函数,并且可以用于设置其他选项,例如选项 B 隐含选项 A。如果选项是 cl::list,并且还指定了 cl::CommaSeparated,则回调将为每个值触发一次。这可以用于验证组合或选择性地设置其他选项。

    cl::opt<bool> OptA("a", cl::desc("option a"));
    cl::opt<bool> OptB(
        "b", cl::desc("option b -- This option turns on option a"),
        cl::callback([&](const bool &) { OptA = true; }));
    cl::list<std::string, cl::list<std::string>> List(
      "list",
      cl::desc("option list -- This option turns on options a when "
               "'foo' is included in list"),
      cl::CommaSeparated,
      cl::callback([&](const std::string &Str) {
        if (Str == "foo")
          OptA = true;
      }));
    

选项修饰符

选项修饰符是您传递到 cl::optcl::list 构造函数中的标志和表达式。这些修饰符使您能够调整选项的解析方式以及如何生成 -help 输出以很好地适应您的应用程序。

这些选项分为五个主要类别

  1. -help 输出中隐藏选项

  2. 控制所需和允许的出现次数

  3. 控制是否必须指定值

  4. 控制其他格式化选项

  5. 其他选项修饰符

不可能为单个选项指定来自同一类别的两个选项(您将收到运行时错误),除非是杂项类别中的选项。CommandLine 库为所有这些设置指定了在实践中最有用和最常见的默认值,这意味着您通常不必担心这些。

-help 输出中隐藏选项

cl::NotHiddencl::Hiddencl::ReallyHidden 修饰符用于控制选项是否出现在已编译程序的 -help-help-hidden 输出中

  • cl::NotHidden 修饰符(这是 cl::optcl::list 选项的默认值)指示该选项将出现在两个帮助列表中。

  • cl::Hidden 修饰符(这是 cl::alias 选项的默认值)指示该选项不应出现在 -help 输出中,但应出现在 -help-hidden 输出中。

  • cl::ReallyHidden 修饰符指示该选项不应出现在任何帮助输出中。

控制所需和允许的出现次数

这组选项用于控制在程序的命令行中允许(或需要)指定选项的次数。为此设置指定值允许 CommandLine 库为您进行错误检查。

此选项组的允许值是

  • cl::Optional 修饰符(这是 cl::optcl::alias 类的默认值)指示您的程序将允许指定零次或一次选项。

  • cl::ZeroOrMore 修饰符(这是 cl::list 类的默认值)指示您的程序将允许指定选项零次或多次。

  • cl::Required 修饰符指示必须精确指定一次指定的选项。

  • cl::OneOrMore 修饰符指示必须至少指定一次选项。

  • cl::ConsumeAfter 修饰符在 位置参数部分 中进行了描述。

如果未指定选项,则选项的值等于 cl::init 属性指定的值。如果未指定 cl::init 属性,则使用数据类型的默认构造函数初始化选项值。

如果对于 cl::opt 类的选项多次指定了选项,则仅保留最后一个值。

控制是否必须指定值

这组选项用于控制选项是否允许存在值。在 CommandLine 库的情况下,值要么用等号指定(例如 ‘-index-depth=17’),要么作为尾随字符串指定(例如 ‘-o a.out’)。

此选项组的允许值是

  • cl::ValueOptional 修饰符(这是 bool 类型选项的默认值)指定可以接受具有值,也可以不具有值。布尔参数可以通过仅出现在命令行上来启用,或者它可以具有显式的 ‘-foo=true’。如果以这种模式指定选项,则在没有等号的情况下提供值是非法的。因此 ‘-foo true’ 是非法的。要获得此行为,您必须使用 cl::ValueRequired 修饰符。

  • cl::ValueRequired 修饰符(这是除 使用通用解析器的未命名替代方案 之外的所有其他类型的默认值)指定必须提供值。此模式通知命令行库,如果未通过等号提供选项,则提供的下一个参数必须是值。这允许像 ‘-o a.out’ 这样的操作工作。

  • cl::ValueDisallowed 修饰符(这是 使用通用解析器的未命名替代方案 的默认值)指示用户指定值是运行时错误。可以提供此选项以禁止用户为布尔选项提供选项(例如 ‘-foo=true’)。

一般来说,此选项组的默认值的工作方式就像您希望它们工作的方式一样。如上所述,您可以为布尔参数指定 cl::ValueDisallowed 修饰符以限制您的命令行解析器。这些选项在扩展库时最有用。

控制其他格式化选项

格式化选项组用于指定命令行选项具有特殊功能,并且在其他方面与其他命令行参数不同。与往常一样,您最多只能指定这些参数中的一个。

  • cl::NormalFormatting 修饰符(这是所有选项的默认值)指定此选项是 “正常的”。

  • cl::Positional 修饰符指定这是一个位置参数,它没有与之关联的命令行选项。有关更多信息,请参阅位置参数部分。

  • cl::ConsumeAfter 修饰符指定此选项用于捕获 “解释器风格” 参数。有关更多信息,请参阅本节

  • cl::Prefix 修饰符指定此选项是其值的前缀。对于 ‘Prefix’ 选项,等号不会将值与指定的选项名称分开。相反,值是前缀之后的所有内容,包括任何等号(如果存在)。这对于处理链接器工具中的奇怪参数(如 -lmalloc-L/usr/lib)或编译器工具中的 -DNAME=value 非常有用。在这里,‘l’、‘D’ 和 ‘L’ 选项是正常的字符串(或列表)选项,添加了 cl::Prefix 修饰符,以允许 CommandLine 库识别它们。请注意,cl::Prefix 选项不得指定 cl::ValueDisallowed 修饰符。

控制选项分组

cl::Grouping 修饰符可以与除 cl::Positional 之外的任何格式化类型组合使用。它用于实现 Unix 风格的工具(如 ls),这些工具具有许多单字母参数,但只需要一个破折号。例如,‘ls -labF’ 命令实际上启用了四个不同的选项,所有这些选项都是单字母的。

请注意,只有当 cl::Grouping 选项单独使用或在组的末尾使用时,它们才能具有值。对于 cl::ValueRequired,如果此类选项在组中的其他位置使用,则会发生运行时错误。

CommandLine 库不限制您如何使用 cl::Prefixcl::Grouping 修饰符,但有可能指定模糊的参数设置。因此,可能有多个字母选项是前缀或分组选项,它们仍然可以按设计工作。

为此,CommandLine 库使用贪婪算法将输入选项解析为(可能多个)前缀和分组选项。该策略基本上如下所示

parse(string OrigInput) {

1. string Input = OrigInput;
2. if (isOption(Input)) return getOption(Input).parse();  // Normal option
3. while (!Input.empty() && !isOption(Input)) Input.pop_back();  // Remove the last letter
4. while (!Input.empty()) {
     string MaybeValue = OrigInput.substr(Input.length())
     if (getOption(Input).isPrefix())
       return getOption(Input).parse(MaybeValue)
     if (!MaybeValue.empty() && MaybeValue[0] == '=')
       return getOption(Input).parse(MaybeValue.substr(1))
     if (!getOption(Input).isGrouping())
       return error()
     getOption(Input).parse()
     Input = OrigInput = MaybeValue
     while (!Input.empty() && !isOption(Input)) Input.pop_back();
     if (!Input.empty() && !getOption(Input).isGrouping())
       return error()
   }
5. if (!OrigInput.empty()) error();

}

杂项选项修饰符

杂项选项修饰符是唯一可以指定来自该集合的多个标志的标志:它们不是互斥的。这些标志指定修改选项的布尔属性。

  • cl::CommaSeparated 修饰符指示为选项的值指定的任何逗号都应用于将值拆分为选项的多个值。例如,当指定 cl::CommaSeparated 时,这两个选项是等效的:“-foo=a -foo=b -foo=c” 和 “-foo=a,b,c”。此选项仅在允许选项接受一个或多个值的情况下使用才有意义(即,它是一个 cl::list 选项)。

  • cl::DefaultOption 修饰符用于指定选项是一个默认选项,可以被应用程序特定的解析器覆盖。例如,-help 别名 -h 就是以这种方式注册的,因此它可以被需要将 -h 选项用于其他目的的应用程序覆盖,无论是作为常规选项还是作为另一个选项的别名。

  • cl::PositionalEatsArgs 修饰符(仅适用于位置参数,并且仅对列表有意义)指示位置参数应消耗其后的任何字符串(包括以 “-” 开头的字符串),直到另一个识别的位置参数为止。例如,如果您有两个 “吃掉” 位置参数 “pos1” 和 “pos2”,则字符串 “-pos1 -foo -bar baz -pos2 -bork” 将导致 “-foo -bar -baz” 字符串应用于 “-pos1” 选项,而 “-bork” 字符串应用于 “-pos2” 选项。

  • cl::Sink 修饰符用于处理未知选项。如果至少有一个指定了 cl::Sink 修饰符的选项,则解析器会将无法识别的选项字符串作为值传递给它,而不是发出错误信号。与 cl::CommaSeparated 一样,此修饰符仅对 cl::list 选项有意义。

响应文件

某些系统,例如 Microsoft Windows 的某些变体和一些较旧的 Unix 系统,对命令行长度的限制相对较低。因此,习惯上使用所谓的 “响应文件” 来规避此限制。这些文件在命令行上提及(使用 “@file” 语法)。程序读取这些文件并将内容插入 argv,从而绕过命令行长度限制。

顶层类和函数

尽管具有所有内置的灵活性,但 CommandLine 选项库实际上只包含一个函数 cl::ParseCommandLineOptions 和三个主要类:cl::optcl::listcl::alias。本节详细描述这三个类。

cl::getRegisteredOptions 函数

cl::getRegisteredOptions 函数旨在为程序员提供对声明的非位置命令行选项的访问权限,以便可以在调用 cl::ParseCommandLineOptions 之前修改它们在 -help 中的显示方式。请注意,不应在任何静态初始化期间调用此方法,因为它无法保证所有选项都已初始化。因此,它应从 main 中调用。

此函数可用于访问在工具编写者可能无法直接访问的库中声明的选项。

该函数检索一个 StringMap,该 StringMap 将选项字符串(例如 -help)映射到 Option*

以下是如何使用该函数的示例

using namespace llvm;
int main(int argc, char **argv) {
  cl::OptionCategory AnotherCategory("Some options");

  StringMap<cl::Option*> &Map = cl::getRegisteredOptions();

  //Unhide useful option and put it in a different category
  assert(Map.count("print-all-options") > 0);
  Map["print-all-options"]->setHiddenFlag(cl::NotHidden);
  Map["print-all-options"]->setCategory(AnotherCategory);

  //Hide an option we don't want to see
  assert(Map.count("enable-no-infs-fp-math") > 0);
  Map["enable-no-infs-fp-math"]->setHiddenFlag(cl::Hidden);

  //Change --version to --show-version
  assert(Map.count("version") > 0);
  Map["version"]->setArgStr("show-version");

  //Change --help description
  assert(Map.count("help") > 0);
  Map["help"]->setDescription("Shows help");

  cl::ParseCommandLineOptions(argc, argv, "This is a small program to demo the LLVM CommandLine API");
  ...
}

cl::ParseCommandLineOptions 函数

cl::ParseCommandLineOptions 函数旨在直接从 main 调用,并用于在 argcargv 可用后填充所有命令行选项变量的值。

cl::ParseCommandLineOptions 函数需要两个参数(argcargv),但也可能接受可选的第三个参数,该参数保存 额外的文本,以便在调用 -help 选项时发出。

cl::SetVersionPrinter 函数

cl::SetVersionPrinter 函数旨在直接从 main 函数中调用,且 cl::ParseCommandLineOptions 之前调用。它的使用是可选的。它只是安排一个函数来响应 --version 选项的调用,而不是让 CommandLine 库打印出 LLVM 的常用版本字符串。这对于那些不是 LLVM 的一部分但希望使用 CommandLine 功能的程序非常有用。此类程序应仅定义一个不带参数且返回 void 的小型函数,并打印出适合该程序的任何版本信息。将该函数的地址传递给 cl::SetVersionPrinter,以便安排在用户给出 --version 选项时调用它。

cl::opt

cl::opt 类是用于表示标量命令行选项的类,也是最常用的类。它是一个模板类,最多可以接受三个参数(除了第一个参数外,所有参数都有默认值)

namespace cl {
  template <class DataType, bool ExternalStorage = false,
            class ParserClass = parser<DataType> >
  class opt;
}

第一个模板参数指定命令行参数的底层数据类型,并用于选择默认的解析器实现。第二个模板参数用于指定选项是否应包含选项的存储空间(默认情况),或者是否应使用外部存储空间来包含为选项解析的值(有关更多信息,请参见内部存储与外部存储)。

第三个模板参数指定要使用的解析器。默认值根据选项的底层数据类型选择 parser 类的实例化。通常,此默认值适用于大多数应用程序,因此仅在使用自定义解析器时才使用此选项。

cl::list

cl::list 类是用于表示命令行选项列表的类。它也是一个模板类,最多可以接受三个参数

namespace cl {
  template <class DataType, class Storage = bool,
            class ParserClass = parser<DataType> >
  class list;
}

此类的工作方式与 cl::opt 类完全相同,不同之处在于第二个参数是外部存储的类型,而不是布尔值。对于此类,标记类型 bool 用于指示应使用内部存储。

cl::bits

cl::bits 类是用于以位向量形式表示命令行选项列表的类。它也是一个模板类,最多可以接受三个参数

namespace cl {
  template <class DataType, class Storage = bool,
            class ParserClass = parser<DataType> >
  class bits;
}

此类的工作方式与 cl::list 类完全相同,不同之处在于,如果使用外部存储,则第二个参数的类型必须为 unsigned

cl::alias

cl::alias 类是一个非模板类,用于为其他参数形成别名。

namespace cl {
  class alias;
}

应使用 cl::aliasopt 属性来指定此别名所针对的选项。别名参数默认为 cl::Hidden,并使用别名选项的解析器来执行从字符串到数据的转换。

cl::extrahelp

cl::extrahelp 类是一个非模板类,允许为 -help 选项打印额外的帮助文本。

namespace cl {
  struct extrahelp;
}

要使用 extrahelp,只需使用 const char* 参数构造一个。传递给构造函数的文本将逐字打印在帮助消息的底部。请注意,可以使用多个 cl::extrahelp,但不建议这样做。如果您的工具需要打印额外的帮助信息,请将所有帮助信息放入单个 cl::extrahelp 实例中。

例如

cl::extrahelp("\nADDITIONAL HELP:\n\n  This is the extra help\n");

cl::OptionCategory

cl::OptionCategory 类是一个用于声明选项类别的简单类。

namespace cl {
  class OptionCategory;
}

选项类别必须具有名称,并且可以选择性地具有描述,这些名称和描述作为 const char* 传递给构造函数。

请注意,在解析选项之前(例如静态地)声明选项类别并将其与选项关联将更改 -help 的输出,从未分类变为已分类。如果声明了选项类别但未将其与选项关联,则该选项类别将从 -help 的输出中隐藏。

内置解析器

解析器控制如何将从命令行获取的字符串值转换为类型化的值,以便在 C++ 程序中使用。默认情况下,如果命令行选项指定它使用 “type” 类型的值,则 CommandLine 库使用 parser<type> 的实例。因此,自定义选项处理通过 “parser” 类的特化来指定。

CommandLine 库提供了以下内置解析器特化,这些特化足以满足大多数应用程序的需求。但是,它也可以扩展以处理新的数据类型和解释相同数据的新方法。有关此类库扩展的更多详细信息,请参见编写自定义解析器

  • 通用 parser<t> 解析器可以通过使用 cl::values 属性将字符串值映射到任何数据类型,该属性指定映射信息。此解析器最常见的用途是解析枚举值,这使您可以将 CommandLine 库用于所有错误检查,以确保仅指定有效的枚举值(而不是接受任意字符串)。尽管如此,通用解析器类仍可用于任何数据类型。

  • parser<bool> 特化用于将布尔字符串转换为布尔值。当前接受的字符串为 “true”、“TRUE”、“True”、“1”、“false”、“FALSE”、“False” 和 “0”。

  • parser<boolOrDefault> 特化用于值是布尔值的情况,但我们也需要知道是否指定了该选项。 boolOrDefault 是一个具有 3 个值的枚举,BOU_UNSET、BOU_TRUE 和 BOU_FALSE。此解析器接受与 ``parser<bool>`` 相同的字符串。

  • parser<string> 特化只是将解析后的字符串存储到指定的字符串值中。不执行数据的转换或修改。

  • parser<int> 特化使用 C strtol 函数来解析字符串输入。因此,它将接受一个十进制数(带有可选的 ‘+’ 或 ‘-’ 前缀),该十进制数必须以非零数字开头。它接受八进制数(以 ‘0’ 前缀数字标识)和十六进制数(以 ‘0x’ 或 ‘0X’ 前缀标识)。

  • parser<double>parser<float> 特化使用标准 C strtod 函数将浮点字符串转换为浮点值。因此,支持广泛的字符串格式,包括指数表示法(例如:1.7e15)并正确支持区域设置。

扩展指南

尽管 CommandLine 库已经内置了许多功能(如前所述),但其真正的优势之一在于其可扩展性。本节讨论 CommandLine 库在底层的工作原理,并说明如何进行一些简单、常见的扩展。

编写自定义解析器

最简单和最常见的扩展之一是使用自定义解析器。正如前面讨论的那样,解析器是 CommandLine 库的一部分,它将用户的字符串输入转换为特定的解析数据类型,并在过程中验证输入。

有两种使用新解析器的方法

  1. 为您的自定义数据类型特化 cl::parser 模板。

    此方法的优点是,每当用户使用您的数据类型的值类型定义选项时,您的自定义数据类型的用户将自动使用您的自定义解析器。此方法的缺点是,如果您的基本数据类型已经是受支持的类型,则此方法不起作用。

  2. 编写一个独立的类,并从需要它的选项中显式使用它。

    在您希望使用特殊语法为不太特殊的数据类型解析选项的情况下,此方法非常有效。此方法的缺点是,您的解析器的用户必须意识到他们正在使用您的解析器而不是内置解析器。

为了指导讨论,我们将讨论一个自定义解析器,该解析器接受文件大小,并在数字大小后指定可选单位。例如,我们希望将 “102kb”、“41M”、“1G” 解析为适当的整数值。在这种情况下,我们要解析成的底层数据类型是 “unsigned”。我们选择上面的方法 #2,因为我们不想将其作为所有 unsigned 选项的默认值。

首先,我们声明新的 FileSizeParser

struct FileSizeParser : public cl::parser<unsigned> {
  // parse - Return true on error.
  bool parse(cl::Option &O, StringRef ArgName, const std::string &ArgValue,
             unsigned &Val);
};

我们的新类继承自 cl::parser 模板类,以填充默认的样板代码。我们为其提供了我们解析成的数据类型,即 parse 方法的最后一个参数,以便我们的自定义解析器的客户端知道要将什么对象类型传递给 parse 方法。(在这里,我们声明我们解析为 “unsigned” 变量。)

对于大多数目的而言,自定义解析器中必须实现的唯一方法是 parse 方法。每当调用选项时,都会调用 parse 方法,传入选项本身、选项名称、要解析的字符串以及对返回值的引用。如果要解析的字符串格式不正确,解析器应输出错误消息并返回 true。否则,它应返回 false 并将 “Val” 设置为解析后的值。在我们的示例中,我们将 parse 实现为

bool FileSizeParser::parse(cl::Option &O, StringRef ArgName,
                           const std::string &Arg, unsigned &Val) {
  const char *ArgStart = Arg.c_str();
  char *End;

  // Parse integer part, leaving 'End' pointing to the first non-integer char
  Val = (unsigned)strtol(ArgStart, &End, 0);

  while (1) {
    switch (*End++) {
    case 0: return false;   // No error
    case 'i':               // Ignore the 'i' in KiB if people use that
    case 'b': case 'B':     // Ignore B suffix
      break;

    case 'g': case 'G': Val *= 1024*1024*1024; break;
    case 'm': case 'M': Val *= 1024*1024;      break;
    case 'k': case 'K': Val *= 1024;           break;

    default:
      // Print an error message if unrecognized character!
      return O.error("'" + Arg + "' value invalid for file size argument!");
    }
  }
}

此函数为我们感兴趣的字符串类型实现了一个非常简单的解析器。尽管它有一些漏洞(例如,它允许 “123KKK”),但对于此示例来说已经足够好了。请注意,我们使用选项本身来打印错误消息(error 方法始终返回 true),以便获得友好的错误消息(如下所示)。现在我们有了解析器类,我们可以像这样使用它

static cl::opt<unsigned, false, FileSizeParser>
MFS("max-file-size", cl::desc("Maximum file size to accept"),
    cl::value_desc("size"));

这会将以下内容添加到我们程序的输出中

OPTIONS:
  -help                 - display available options (-help-hidden for more)
  ...
  -max-file-size=<size> - Maximum file size to accept

我们可以测试我们的解析现在是否正确工作(测试程序仅打印 max-file-size 参数值)

$ ./test
MFS: 0
$ ./test -max-file-size=123MB
MFS: 128974848
$ ./test -max-file-size=3G
MFS: 3221225472
$ ./test -max-file-size=dog
-max-file-size option: 'dog' value invalid for file size argument!

看起来它可以工作。我们收到的错误消息很好且有帮助,并且我们似乎接受合理的文件大小。这总结了“自定义解析器”教程。

利用外部存储

几个 LLVM 库定义了静态 cl::opt 实例,这些实例将自动包含在与该库链接的任何程序中。这是一个特性。但是,有时需要在库外部知道命令行选项的值。在这些情况下,库会或应该提供外部存储位置,该位置可供库的用户访问。这方面的示例包括 lib/Support/Debug.cpp 文件导出的 llvm::DebugFlaglib/IR/PassManager.cpp 文件导出的 llvm::TimePassesIsEnabled 标志。

动态添加命令行选项