[llvm] [LLVM][Option] Support multiline option parsing (PR #171214)
Danny Mösch via llvm-commits
llvm-commits at lists.llvm.org
Tue Jan 6 09:36:19 PST 2026
https://github.com/SimplyDanny updated https://github.com/llvm/llvm-project/pull/171214
>From 8e9a43abfea934f23c8c18f3c3e82374d6cd2b26 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Danny=20M=C3=B6sch?= <danny.moesch at icloud.com>
Date: Mon, 8 Dec 2025 23:07:49 +0100
Subject: [PATCH] [LLVM][Option] Support multiline option parsing
Consider arguments as belonging even if they are defined on
(multiple) lines after the introducing argument. Empty lines
between arguments are ignored as well.
---
llvm/lib/Option/OptTable.cpp | 2 +-
llvm/lib/Option/Option.cpp | 107 +++++++++++++-----
llvm/unittests/Option/OptionParsingTest.cpp | 117 ++++++++++++++++++++
3 files changed, 196 insertions(+), 30 deletions(-)
diff --git a/llvm/lib/Option/OptTable.cpp b/llvm/lib/Option/OptTable.cpp
index 065036cedc2ae..4e8e59db8335c 100644
--- a/llvm/lib/Option/OptTable.cpp
+++ b/llvm/lib/Option/OptTable.cpp
@@ -537,7 +537,7 @@ InputArgList OptTable::internalParseArgs(
MissingArgIndex = MissingArgCount = 0;
unsigned Index = 0, End = ArgArr.size();
while (Index < End) {
- // Ingore nullptrs, they are response file's EOL markers
+ // Ignore nullptrs, they are response file's EOL markers.
if (Args.getArgString(Index) == nullptr) {
++Index;
continue;
diff --git a/llvm/lib/Option/Option.cpp b/llvm/lib/Option/Option.cpp
index 738f75bb41e68..d77f9fac64596 100644
--- a/llvm/lib/Option/Option.cpp
+++ b/llvm/lib/Option/Option.cpp
@@ -109,6 +109,36 @@ bool Option::matches(OptSpecifier Opt) const {
return false;
}
+/// Describes how to treat `nullptr` argument strings produced for
+/// newlines and empty lines when advancing through the argument
+/// list.
+enum class NextArgStringBehavior {
+ /// Skip over `nullptr` entries and return the next non-null
+ /// argument string, if any.
+ SkipEmpty,
+ /// Treat a `nullptr` entry as a hard boundary and stop
+ /// advancing. This is used for options that should not
+ /// consume arguments past such boundaries (for example,
+ /// RemainingArgs* options that must not cross @file
+ /// segments).
+ StopAtNull,
+};
+
+/// Advances to and returns the next argument string according to the given
+/// behavior.
+static const char *advanceToNextArgString(
+ const ArgList &Args, unsigned &Index,
+ NextArgStringBehavior Behavior = NextArgStringBehavior::SkipEmpty) {
+ while (Index <= Args.getNumInputArgStrings()) {
+ if (const char *ArgStr = Args.getArgString(Index - 1))
+ return ArgStr;
+ if (Behavior == NextArgStringBehavior::StopAtNull)
+ return nullptr;
+ ++Index;
+ }
+ return nullptr;
+}
+
std::unique_ptr<Arg> Option::acceptInternal(const ArgList &Args,
StringRef Spelling,
unsigned &Index) const {
@@ -152,31 +182,41 @@ std::unique_ptr<Arg> Option::acceptInternal(const ArgList &Args,
return A;
}
- case SeparateClass:
+ case SeparateClass: {
// Matches iff this is an exact match.
if (SpellingSize != ArgStringSize)
return nullptr;
+ unsigned StartIndex = Index;
Index += 2;
- if (Index > Args.getNumInputArgStrings() ||
- Args.getArgString(Index - 1) == nullptr)
+
+ const char *ArgString = advanceToNextArgString(Args, Index);
+ if (!ArgString)
return nullptr;
- return std::make_unique<Arg>(*this, Spelling, Index - 2,
- Args.getArgString(Index - 1));
+ return std::make_unique<Arg>(*this, Spelling, StartIndex, ArgString);
+ }
case MultiArgClass: {
// Matches iff this is an exact match.
if (SpellingSize != ArgStringSize)
return nullptr;
- Index += 1 + getNumArgs();
- if (Index > Args.getNumInputArgStrings())
- return nullptr;
+ unsigned StartIndex = Index;
+ Index += 2;
+
+ SmallVector<const char *, 4> Values;
+ for (unsigned i = 0; i < getNumArgs(); ++i) {
+ const char *ArgString = advanceToNextArgString(Args, Index);
+ if (!ArgString)
+ return nullptr;
+ Values.push_back(ArgString);
+ if (i + 1 < getNumArgs())
+ ++Index;
+ }
- auto A = std::make_unique<Arg>(*this, Spelling, Index - 1 - getNumArgs(),
- Args.getArgString(Index - getNumArgs()));
+ auto A = std::make_unique<Arg>(*this, Spelling, StartIndex, Values[0]);
for (unsigned i = 1; i != getNumArgs(); ++i)
- A->getValues().push_back(Args.getArgString(Index - getNumArgs() + i));
+ A->getValues().push_back(Values[i]);
return A;
}
case JoinedOrSeparateClass: {
@@ -187,32 +227,39 @@ std::unique_ptr<Arg> Option::acceptInternal(const ArgList &Args,
}
// Otherwise it must be separate.
+ unsigned StartIndex = Index;
Index += 2;
- if (Index > Args.getNumInputArgStrings() ||
- Args.getArgString(Index - 1) == nullptr)
+
+ const char *ArgString = advanceToNextArgString(Args, Index);
+ if (!ArgString)
return nullptr;
- return std::make_unique<Arg>(*this, Spelling, Index - 2,
- Args.getArgString(Index - 1));
+ return std::make_unique<Arg>(*this, Spelling, StartIndex, ArgString);
}
- case JoinedAndSeparateClass:
+ case JoinedAndSeparateClass: {
// Always matches.
+ unsigned StartIndex = Index;
+ const char *JoinedValue = Args.getArgString(StartIndex) + SpellingSize;
Index += 2;
- if (Index > Args.getNumInputArgStrings() ||
- Args.getArgString(Index - 1) == nullptr)
+
+ const char *SeparateValue = advanceToNextArgString(Args, Index);
+ if (!SeparateValue)
return nullptr;
- return std::make_unique<Arg>(*this, Spelling, Index - 2,
- Args.getArgString(Index - 2) + SpellingSize,
- Args.getArgString(Index - 1));
+ return std::make_unique<Arg>(*this, Spelling, StartIndex, JoinedValue,
+ SeparateValue);
+ }
case RemainingArgsClass: {
// Matches iff this is an exact match.
if (SpellingSize != ArgStringSize)
return nullptr;
- auto A = std::make_unique<Arg>(*this, Spelling, Index++);
- while (Index < Args.getNumInputArgStrings() &&
- Args.getArgString(Index) != nullptr)
- A->getValues().push_back(Args.getArgString(Index++));
+ auto A = std::make_unique<Arg>(*this, Spelling, Index);
+ Index += 2;
+ while (const char *ArgString = advanceToNextArgString(
+ Args, Index, NextArgStringBehavior::StopAtNull)) {
+ A->getValues().push_back(ArgString);
+ Index++;
+ }
return A;
}
case RemainingArgsJoinedClass: {
@@ -221,10 +268,12 @@ std::unique_ptr<Arg> Option::acceptInternal(const ArgList &Args,
// An inexact match means there is a joined arg.
A->getValues().push_back(Args.getArgString(Index) + SpellingSize);
}
- Index++;
- while (Index < Args.getNumInputArgStrings() &&
- Args.getArgString(Index) != nullptr)
- A->getValues().push_back(Args.getArgString(Index++));
+ Index += 2;
+ while (const char *ArgString = advanceToNextArgString(
+ Args, Index, NextArgStringBehavior::StopAtNull)) {
+ A->getValues().push_back(ArgString);
+ Index++;
+ }
return A;
}
diff --git a/llvm/unittests/Option/OptionParsingTest.cpp b/llvm/unittests/Option/OptionParsingTest.cpp
index 3da015e343eb9..10ed60d476621 100644
--- a/llvm/unittests/Option/OptionParsingTest.cpp
+++ b/llvm/unittests/Option/OptionParsingTest.cpp
@@ -559,3 +559,120 @@ USAGE: usage
multiple lines in it
)");
}
+
+TYPED_TEST(OptTableTest, ResponseFileNullptrMarkers) {
+ TypeParam T;
+ unsigned MAI, MAC;
+
+ // This simulates a response file with:
+ // -C
+ // value
+ const char *Args1[] = {"-C", nullptr, "value"};
+ InputArgList AL = T.ParseArgs(Args1, MAI, MAC);
+ EXPECT_TRUE(AL.hasArg(OPT_C));
+ EXPECT_EQ("value", AL.getLastArgValue(OPT_C));
+ EXPECT_EQ(0U, MAC);
+
+ // This simulates blank lines in a response file:
+ // -C
+ //
+ //
+ // value
+ const char *Args2[] = {"-C", nullptr, nullptr, nullptr, "value"};
+ InputArgList AL2 = T.ParseArgs(Args2, MAI, MAC);
+ EXPECT_TRUE(AL2.hasArg(OPT_C));
+ EXPECT_EQ("value", AL2.getLastArgValue(OPT_C));
+ EXPECT_EQ(0U, MAC);
+
+ // This simulates a response file with -E takes 2 arguments:
+ // -E
+ // arg1
+ // arg2
+ const char *Args3[] = {"-E", nullptr, "arg1", nullptr, "arg2"};
+ InputArgList AL3 = T.ParseArgs(Args3, MAI, MAC);
+ EXPECT_TRUE(AL3.hasArg(OPT_E));
+ std::vector<std::string> EValues = AL3.getAllArgValues(OPT_E);
+ ASSERT_EQ(2U, EValues.size());
+ EXPECT_EQ("arg1", EValues[0]);
+ EXPECT_EQ("arg2", EValues[1]);
+ EXPECT_EQ(0U, MAC);
+
+ // -F can be either joined or separate, test the separate case:
+ // -F
+ // value
+ const char *Args4[] = {"-F", nullptr, "value"};
+ InputArgList AL4 = T.ParseArgs(Args4, MAI, MAC);
+ EXPECT_TRUE(AL4.hasArg(OPT_F));
+ EXPECT_EQ("value", AL4.getLastArgValue(OPT_F));
+ EXPECT_EQ(0U, MAC);
+
+ // Test that missing argument is still detected correctly with missing
+ // arguments at the end.
+ const char *Args5[] = {"-C", nullptr, nullptr};
+ InputArgList AL5 = T.ParseArgs(Args5, MAI, MAC);
+ EXPECT_FALSE(AL5.hasArg(OPT_C));
+ EXPECT_GT(MAC, 0U);
+ EXPECT_EQ(0U, MAI);
+
+ // -G joined to "joined" and followed by "separate":
+ // -Gjoined
+ //
+ // separate
+ const char *Args7[] = {"-Gjoined", nullptr, nullptr, "separate"};
+ InputArgList AL7 = T.ParseArgs(Args7, MAI, MAC);
+ EXPECT_TRUE(AL7.hasArg(OPT_G));
+ EXPECT_EQ("joined", AL7.getLastArgValue(OPT_G));
+ EXPECT_EQ(2U, AL7.getAllArgValues(OPT_G).size());
+ EXPECT_EQ("joined", AL7.getAllArgValues(OPT_G)[0]);
+ EXPECT_EQ("separate", AL7.getAllArgValues(OPT_G)[1]);
+ EXPECT_EQ(0U, MAC);
+
+ // -slurp consumes arguments up to the first nullptr marker,
+ // treating it as a boundary between segments:
+ // -slurp
+ // arg1
+ //
+ // arg2
+ const char *Args8[] = {"-slurp", "arg1", nullptr, nullptr, "arg2"};
+ InputArgList AL8 = T.ParseArgs(Args8, MAI, MAC);
+ EXPECT_TRUE(AL8.hasArg(OPT_Slurp));
+ std::vector<std::string> SlurpValues = AL8.getAllArgValues(OPT_Slurp);
+ ASSERT_EQ(1U, SlurpValues.size());
+ EXPECT_EQ("arg1", SlurpValues[0]);
+ EXPECT_EQ(0U, MAC);
+
+ // -slurpjoined consumes joined arg and arguments up to the
+ // first nullptr marker:
+ // -slurpjoinedjoined
+ // arg1
+ //
+ // arg2
+ const char *Args9[] = {"-slurpjoinedjoined", "arg1", nullptr, nullptr,
+ "arg2"};
+ InputArgList AL9 = T.ParseArgs(Args9, MAI, MAC);
+ EXPECT_TRUE(AL9.hasArg(OPT_SlurpJoined));
+ std::vector<std::string> SlurpJoinedValues =
+ AL9.getAllArgValues(OPT_SlurpJoined);
+ ASSERT_EQ(2U, SlurpJoinedValues.size());
+ EXPECT_EQ("joined", SlurpJoinedValues[0]);
+ EXPECT_EQ("arg1", SlurpJoinedValues[1]);
+ EXPECT_EQ(0U, MAC);
+}
+
+TYPED_TEST(OptTableTest, ResponseFileIntegration) {
+ TypeParam T;
+ unsigned MAI, MAC;
+
+ const char *ExpandedArgs[] = {
+ "-A", nullptr, "-C", nullptr, nullptr,
+ "cvalue", nullptr, "-F", nullptr, "fvalue",
+ };
+
+ InputArgList AL = T.ParseArgs(ExpandedArgs, MAI, MAC);
+ EXPECT_TRUE(AL.hasArg(OPT_A));
+ EXPECT_TRUE(AL.hasArg(OPT_C));
+ EXPECT_TRUE(AL.hasArg(OPT_F));
+ EXPECT_EQ("cvalue", AL.getLastArgValue(OPT_C));
+ EXPECT_EQ("fvalue", AL.getLastArgValue(OPT_F));
+ EXPECT_EQ(0U, MAC);
+}
More information about the llvm-commits
mailing list