CommandLine 2.0 函式庫手冊

簡介

本文檔描述了 CommandLine 參數處理函式庫。它將向您展示如何使用它,以及它可以做什麼。CommandLine 函式庫使用宣告式方法來指定您的程式接受的命令列選項。默認情況下,這些選項宣告隱式地保存為宣告的選項解析的值(當然,這可以更改)。

儘管有很多用許多不同語言編寫的命令列參數解析函式庫,但沒有一個適合我的需求。通過查看其他函式庫的功能和問題,我設計了 CommandLine 函式庫以具有以下功能

  1. 速度:CommandLine 函式庫速度非常快且佔用資源少。函式庫的解析時間與解析的參數數量成正比,而不是與識別的選項數量成正比。此外,命令列參數值會以透明方式擷取到使用者定義的全域變數中,您可以像存取任何其他變數一樣存取這些變數(且效能相同)。

  2. 類型安全:身為 CommandLine 的使用者,您不需要記住想要的參數類型(是 int?字串?布林值?列舉?)並不斷進行轉換。這不僅有助於防止容易出錯的結構,還能讓原始程式碼更加簡潔。

  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

… 你懂的。 布林值剖析器 只會將字串值轉換為布林值,並拒絕像「compiler -f=foo」這樣的東西。同樣地, 浮點數雙精度浮點數整數 剖析器會像您預期的那樣運作,使用「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();

最後,如果使用了外部儲存體,則指定的位置必須是 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 檔案中定義所有位置引數即可。

使用連字號指定位置選項

有時您可能想要為您的位置引數指定一個以連字號開頭的值(例如,在檔案中搜尋「-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 選項。

內部與外部儲存

預設情況下,所有命令列選項都會自動保留它們從命令列解析的值。這在常見情況下非常方便,尤其是與在使用它們的文件中定義命令列選項的功能相結合時。這稱為內部儲存模型。

然而,有時將命令列選項處理程式碼與解析值的儲存分開是很好的。例如,假設我們有一個“-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));

在上面的範例中,我們在 cl::opt 範本中指定“true”作為第二個引數,表示範本不應保留值本身的副本。除此之外,我們還指定了 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 屬性會指定將已解析命令列選項的值儲存在何處。如需詳細資訊,請參閱 內部與外部儲存 一節。

  • 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,它將選項字串(例如 -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」類型的值,則命令列程式庫會使用 parser<type> 的實例。因此,自訂選項處理是使用「parser」類別的特化來指定的。

命令列程式庫提供了以下內建解析器特化,這些特化足以應付大多數應用程式。但是,它也可以擴展以使用新的資料類型和新的資料解釋方式。有關此類程式庫擴展的更多詳細資訊,請參閱撰寫自訂解析器

  • 泛型 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::DebugFlag 以及由 lib/IR/PassManager.cpp 檔案匯出的 llvm::TimePassesIsEnabled 旗標。

動態新增命令列選項