浏览代码

Merge topic 'command-arg-parser'

236bacc244 cmArgumentParser: Offer bindings for positional arguments
1f2eb63d1c cmArgumentParser: Add callback bindings
f5d2f6076a cmArgumentParser: Generalize expected argument count
078e2aec8f cmArgumentParser: Generalize internal state tracking
77fcb00a2b cmArgumentParser: Propagate constructors through binding wrapper types

Acked-by: Kitware Robot <[email protected]>
Merge-request: !7514
Brad King 3 年之前
父节点
当前提交
110baa254b
共有 4 个文件被更改,包括 420 次插入46 次删除
  1. 82 30
      Source/cmArgumentParser.cxx
  2. 167 13
      Source/cmArgumentParser.h
  3. 40 0
      Source/cmArgumentParserTypes.h
  4. 131 3
      Tests/CMakeLib/testArgumentParser.cxx

+ 82 - 30
Source/cmArgumentParser.cxx

@@ -34,55 +34,97 @@ auto KeywordActionMap::Find(cm::string_view name) const -> const_iterator
   return (it != this->end() && it->first == name) ? it : this->end();
 }
 
+auto PositionActionMap::Emplace(std::size_t pos, PositionAction action)
+  -> std::pair<iterator, bool>
+{
+  auto const it = std::lower_bound(
+    this->begin(), this->end(), pos,
+    [](value_type const& elem, std::size_t k) { return elem.first < k; });
+  return (it != this->end() && it->first == pos)
+    ? std::make_pair(it, false)
+    : std::make_pair(this->emplace(it, pos, std::move(action)), true);
+}
+
+auto PositionActionMap::Find(std::size_t pos) const -> const_iterator
+{
+  auto const it = std::lower_bound(
+    this->begin(), this->end(), pos,
+    [](value_type const& elem, std::size_t k) { return elem.first < k; });
+  return (it != this->end() && it->first == pos) ? it : this->end();
+}
+
+void Instance::Bind(std::function<Continue(cm::string_view)> f,
+                    ExpectAtLeast expect)
+{
+  this->KeywordValueFunc = std::move(f);
+  this->KeywordValuesExpected = expect.Count;
+}
+
 void Instance::Bind(bool& val)
 {
   val = true;
-  this->CurrentString = nullptr;
-  this->CurrentList = nullptr;
-  this->ExpectValue = false;
+  this->Bind(nullptr, ExpectAtLeast{ 0 });
 }
 
 void Instance::Bind(std::string& val)
 {
-  this->CurrentString = &val;
-  this->CurrentList = nullptr;
-  this->ExpectValue = true;
+  this->Bind(
+    [&val](cm::string_view arg) -> Continue {
+      val = std::string(arg);
+      return Continue::No;
+    },
+    ExpectAtLeast{ 1 });
 }
 
 void Instance::Bind(Maybe<std::string>& val)
 {
-  this->CurrentString = &val;
-  this->CurrentList = nullptr;
-  this->ExpectValue = false;
+  this->Bind(
+    [&val](cm::string_view arg) -> Continue {
+      static_cast<std::string&>(val) = std::string(arg);
+      return Continue::No;
+    },
+    ExpectAtLeast{ 0 });
 }
 
 void Instance::Bind(MaybeEmpty<std::vector<std::string>>& val)
 {
-  this->CurrentString = nullptr;
-  this->CurrentList = &val;
-  this->ExpectValue = false;
+  this->Bind(
+    [&val](cm::string_view arg) -> Continue {
+      val.emplace_back(arg);
+      return Continue::Yes;
+    },
+    ExpectAtLeast{ 0 });
 }
 
 void Instance::Bind(NonEmpty<std::vector<std::string>>& val)
 {
-  this->CurrentString = nullptr;
-  this->CurrentList = &val;
-  this->ExpectValue = true;
+  this->Bind(
+    [&val](cm::string_view arg) -> Continue {
+      val.emplace_back(arg);
+      return Continue::Yes;
+    },
+    ExpectAtLeast{ 1 });
 }
 
-void Instance::Bind(std::vector<std::vector<std::string>>& val)
+void Instance::Bind(std::vector<std::vector<std::string>>& multiVal)
 {
-  this->CurrentString = nullptr;
-  this->CurrentList = (static_cast<void>(val.emplace_back()), &val.back());
-  this->ExpectValue = false;
+  multiVal.emplace_back();
+  std::vector<std::string>& val = multiVal.back();
+  this->Bind(
+    [&val](cm::string_view arg) -> Continue {
+      val.emplace_back(arg);
+      return Continue::Yes;
+    },
+    ExpectAtLeast{ 0 });
 }
 
-void Instance::Consume(cm::string_view arg)
+void Instance::Consume(std::size_t pos, cm::string_view arg)
 {
   auto const it = this->Bindings.Keywords.Find(arg);
   if (it != this->Bindings.Keywords.end()) {
     this->FinishKeyword();
     this->Keyword = it->first;
+    this->KeywordValuesSeen = 0;
     if (this->Bindings.ParsedKeyword) {
       this->Bindings.ParsedKeyword(*this, it->first);
     }
@@ -90,17 +132,27 @@ void Instance::Consume(cm::string_view arg)
     return;
   }
 
-  if (this->CurrentString != nullptr) {
-    this->CurrentString->assign(std::string(arg));
-    this->CurrentString = nullptr;
-    this->CurrentList = nullptr;
-  } else if (this->CurrentList != nullptr) {
-    this->CurrentList->emplace_back(arg);
-  } else if (this->UnparsedArguments != nullptr) {
-    this->UnparsedArguments->emplace_back(arg);
+  if (this->KeywordValueFunc) {
+    switch (this->KeywordValueFunc(arg)) {
+      case Continue::Yes:
+        break;
+      case Continue::No:
+        this->KeywordValueFunc = nullptr;
+        break;
+    }
+    ++this->KeywordValuesSeen;
+    return;
   }
 
-  this->ExpectValue = false;
+  auto const pit = this->Bindings.Positions.Find(pos);
+  if (pit != this->Bindings.Positions.end()) {
+    pit->second(*this, pos, arg);
+    return;
+  }
+
+  if (this->UnparsedArguments != nullptr) {
+    this->UnparsedArguments->emplace_back(arg);
+  }
 }
 
 void Instance::FinishKeyword()
@@ -108,7 +160,7 @@ void Instance::FinishKeyword()
   if (this->Keyword.empty()) {
     return;
   }
-  if (this->ExpectValue) {
+  if (this->KeywordValuesSeen < this->KeywordValuesExpected) {
     if (this->ParseResults != nullptr) {
       this->ParseResults->AddKeywordError(this->Keyword,
                                           "  missing required value\n");

+ 167 - 13
Source/cmArgumentParser.h

@@ -5,6 +5,7 @@
 #include "cmConfigure.h" // IWYU pragma: keep
 
 #include <cassert>
+#include <cstddef>
 #include <functional>
 #include <map>
 #include <string>
@@ -62,9 +63,27 @@ AsParseResultPtr(Result&)
   return nullptr;
 }
 
+enum class Continue
+{
+  No,
+  Yes,
+};
+
+struct ExpectAtLeast
+{
+  std::size_t Count = 0;
+
+  ExpectAtLeast(std::size_t count)
+    : Count(count)
+  {
+  }
+};
+
 class Instance;
 using KeywordAction = std::function<void(Instance&)>;
 using KeywordNameAction = std::function<void(Instance&, cm::string_view)>;
+using PositionAction =
+  std::function<void(Instance&, std::size_t, cm::string_view)>;
 
 // using KeywordActionMap = cm::flat_map<cm::string_view, KeywordAction>;
 class KeywordActionMap
@@ -76,17 +95,29 @@ public:
   const_iterator Find(cm::string_view name) const;
 };
 
+// using PositionActionMap = cm::flat_map<cm::string_view, PositionAction>;
+class PositionActionMap
+  : public std::vector<std::pair<std::size_t, PositionAction>>
+{
+public:
+  std::pair<iterator, bool> Emplace(std::size_t pos, PositionAction action);
+  const_iterator Find(std::size_t pos) const;
+};
+
 class ActionMap
 {
 public:
   KeywordActionMap Keywords;
   KeywordNameAction KeywordMissingValue;
   KeywordNameAction ParsedKeyword;
+  PositionActionMap Positions;
 };
 
 class Base
 {
 public:
+  using ExpectAtLeast = ArgumentParser::ExpectAtLeast;
+  using Continue = ArgumentParser::Continue;
   using Instance = ArgumentParser::Instance;
   using ParseResult = ArgumentParser::ParseResult;
 
@@ -115,6 +146,14 @@ public:
     assert(!this->Bindings.KeywordMissingValue);
     this->Bindings.KeywordMissingValue = std::move(action);
   }
+
+  void Bind(std::size_t pos, PositionAction action)
+  {
+    bool const inserted =
+      this->Bindings.Positions.Emplace(pos, std::move(action)).second;
+    assert(inserted);
+    static_cast<void>(inserted);
+  }
 };
 
 class Instance
@@ -129,6 +168,7 @@ public:
   {
   }
 
+  void Bind(std::function<Continue(cm::string_view)> f, ExpectAtLeast expect);
   void Bind(bool& val);
   void Bind(std::string& val);
   void Bind(Maybe<std::string>& val);
@@ -147,10 +187,10 @@ public:
   }
 
   template <typename Range>
-  void Parse(Range const& args)
+  void Parse(Range const& args, std::size_t pos = 0)
   {
     for (cm::string_view arg : args) {
-      this->Consume(arg);
+      this->Consume(pos++, arg);
     }
     this->FinishKeyword();
   }
@@ -162,11 +202,11 @@ private:
   void* Result = nullptr;
 
   cm::string_view Keyword;
-  std::string* CurrentString = nullptr;
-  std::vector<std::string>* CurrentList = nullptr;
-  bool ExpectValue = false;
+  std::size_t KeywordValuesSeen = 0;
+  std::size_t KeywordValuesExpected = 0;
+  std::function<Continue(cm::string_view)> KeywordValueFunc;
 
-  void Consume(cm::string_view arg);
+  void Consume(std::size_t pos, cm::string_view arg);
   void FinishKeyword();
 
   template <typename Result>
@@ -190,6 +230,82 @@ public:
     return *this;
   }
 
+  cmArgumentParser& Bind(cm::static_string_view name,
+                         Continue (Result::*member)(cm::string_view),
+                         ExpectAtLeast expect = { 1 })
+  {
+    this->Base::Bind(name, [member, expect](Instance& instance) {
+      Result* result = static_cast<Result*>(instance.Result);
+      instance.Bind(
+        [result, member](cm::string_view arg) -> Continue {
+          return (result->*member)(arg);
+        },
+        expect);
+    });
+    return *this;
+  }
+
+  cmArgumentParser& Bind(cm::static_string_view name,
+                         Continue (Result::*member)(cm::string_view,
+                                                    cm::string_view),
+                         ExpectAtLeast expect = { 1 })
+  {
+    this->Base::Bind(name, [member, expect](Instance& instance) {
+      Result* result = static_cast<Result*>(instance.Result);
+      cm::string_view keyword = instance.Keyword;
+      instance.Bind(
+        [result, member, keyword](cm::string_view arg) -> Continue {
+          return (result->*member)(keyword, arg);
+        },
+        expect);
+    });
+    return *this;
+  }
+
+  cmArgumentParser& Bind(cm::static_string_view name,
+                         std::function<Continue(Result&, cm::string_view)> f,
+                         ExpectAtLeast expect = { 1 })
+  {
+    this->Base::Bind(name, [f, expect](Instance& instance) {
+      Result* result = static_cast<Result*>(instance.Result);
+      instance.Bind(
+        [result, &f](cm::string_view arg) -> Continue {
+          return f(*result, arg);
+        },
+        expect);
+    });
+    return *this;
+  }
+
+  cmArgumentParser& Bind(
+    cm::static_string_view name,
+    std::function<Continue(Result&, cm::string_view, cm::string_view)> f,
+    ExpectAtLeast expect = { 1 })
+  {
+    this->Base::Bind(name, [f, expect](Instance& instance) {
+      Result* result = static_cast<Result*>(instance.Result);
+      cm::string_view keyword = instance.Keyword;
+      instance.Bind(
+        [result, keyword, &f](cm::string_view arg) -> Continue {
+          return f(*result, keyword, arg);
+        },
+        expect);
+    });
+    return *this;
+  }
+
+  cmArgumentParser& Bind(std::size_t position,
+                         cm::optional<std::string> Result::*member)
+  {
+    this->Base::Bind(
+      position,
+      [member](Instance& instance, std::size_t, cm::string_view arg) {
+        Result* result = static_cast<Result*>(instance.Result);
+        result->*member = arg;
+      });
+    return *this;
+  }
+
   cmArgumentParser& BindParsedKeywords(
     std::vector<cm::string_view> Result::*member)
   {
@@ -202,22 +318,23 @@ public:
 
   template <typename Range>
   bool Parse(Result& result, Range const& args,
-             std::vector<std::string>* unparsedArguments) const
+             std::vector<std::string>* unparsedArguments,
+             std::size_t pos = 0) const
   {
     using ArgumentParser::AsParseResultPtr;
     ParseResult* parseResultPtr = AsParseResultPtr(result);
     Instance instance(this->Bindings, parseResultPtr, unparsedArguments,
                       &result);
-    instance.Parse(args);
+    instance.Parse(args, pos);
     return parseResultPtr ? static_cast<bool>(*parseResultPtr) : true;
   }
 
   template <typename Range>
-  Result Parse(Range const& args,
-               std::vector<std::string>* unparsedArguments) const
+  Result Parse(Range const& args, std::vector<std::string>* unparsedArguments,
+               std::size_t pos = 0) const
   {
     Result result;
-    this->Parse(result, args, unparsedArguments);
+    this->Parse(result, args, unparsedArguments, pos);
     return result;
   }
 };
@@ -233,6 +350,42 @@ public:
     return *this;
   }
 
+  cmArgumentParser& Bind(cm::static_string_view name,
+                         std::function<Continue(cm::string_view)> f,
+                         ExpectAtLeast expect = { 1 })
+  {
+    this->Base::Bind(name, [f, expect](Instance& instance) {
+      instance.Bind([&f](cm::string_view arg) -> Continue { return f(arg); },
+                    expect);
+    });
+    return *this;
+  }
+
+  cmArgumentParser& Bind(
+    cm::static_string_view name,
+    std::function<Continue(cm::string_view, cm::string_view)> f,
+    ExpectAtLeast expect = { 1 })
+  {
+    this->Base::Bind(name, [f, expect](Instance& instance) {
+      cm::string_view keyword = instance.Keyword;
+      instance.Bind(
+        [keyword, &f](cm::string_view arg) -> Continue {
+          return f(keyword, arg);
+        },
+        expect);
+    });
+    return *this;
+  }
+
+  cmArgumentParser& Bind(std::size_t position, cm::optional<std::string>& ref)
+  {
+    this->Base::Bind(position,
+                     [&ref](Instance&, std::size_t, cm::string_view arg) {
+                       ref = std::string(arg);
+                     });
+    return *this;
+  }
+
   cmArgumentParser& BindParsedKeywords(std::vector<cm::string_view>& ref)
   {
     this->Base::BindParsedKeyword(
@@ -242,11 +395,12 @@ public:
 
   template <typename Range>
   ParseResult Parse(Range const& args,
-                    std::vector<std::string>* unparsedArguments) const
+                    std::vector<std::string>* unparsedArguments,
+                    std::size_t pos = 0) const
   {
     ParseResult parseResult;
     Instance instance(this->Bindings, &parseResult, unparsedArguments);
-    instance.Parse(args);
+    instance.Parse(args, pos);
     return parseResult;
   }
 

+ 40 - 0
Source/cmArgumentParserTypes.h

@@ -4,21 +4,61 @@
 
 #include "cmConfigure.h" // IWYU pragma: keep
 
+#if defined(__SUNPRO_CC)
+
+#  include <string>
+#  include <vector>
+
+namespace ArgumentParser {
+
+template <typename T>
+struct Maybe;
+template <>
+struct Maybe<std::string> : public std::string
+{
+  using std::string::basic_string;
+};
+
+template <typename T>
+struct MaybeEmpty;
+template <typename T>
+struct MaybeEmpty<std::vector<T>> : public std::vector<T>
+{
+  using std::vector<T>::vector;
+};
+
+template <typename T>
+struct NonEmpty;
+template <typename T>
+struct NonEmpty<std::vector<T>> : public std::vector<T>
+{
+  using std::vector<T>::vector;
+};
+
+} // namespace ArgumentParser
+
+#else
+
 namespace ArgumentParser {
 
 template <typename T>
 struct Maybe : public T
 {
+  using T::T;
 };
 
 template <typename T>
 struct MaybeEmpty : public T
 {
+  using T::T;
 };
 
 template <typename T>
 struct NonEmpty : public T
 {
+  using T::T;
 };
 
 } // namespace ArgumentParser
+
+#endif

+ 131 - 3
Tests/CMakeLib/testArgumentParser.cxx

@@ -1,6 +1,7 @@
 /* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
    file Copyright.txt or https://cmake.org/licensing for details.  */
 
+#include <functional>
 #include <initializer_list>
 #include <iostream>
 #include <map>
@@ -39,6 +40,54 @@ struct Result : public ArgumentParser::ParseResult
   cm::optional<std::vector<std::vector<std::string>>> Multi3;
   cm::optional<std::vector<std::vector<std::string>>> Multi4;
 
+  cm::optional<std::string> Pos1;
+
+  bool Func0_ = false;
+  ArgumentParser::Continue Func0(cm::string_view)
+  {
+    Func0_ = true;
+    return ArgumentParser::Continue::No;
+  }
+
+  std::string Func1_;
+  ArgumentParser::Continue Func1(cm::string_view arg)
+  {
+    Func1_ = std::string(arg);
+    return ArgumentParser::Continue::No;
+  }
+
+  std::map<std::string, std::vector<std::string>> Func2_;
+  ArgumentParser::Continue Func2(cm::string_view key, cm::string_view arg)
+  {
+    Func2_[std::string(key)].emplace_back(arg);
+    return key == "FUNC_2b" ? ArgumentParser::Continue::Yes
+                            : ArgumentParser::Continue::No;
+  }
+
+  std::vector<std::string> Func3_;
+  ArgumentParser::Continue Func3(cm::string_view arg)
+  {
+    Func3_.emplace_back(arg);
+    return ArgumentParser::Continue::Yes;
+  }
+
+  std::map<std::string, std::vector<std::string>> Func4_;
+  ArgumentParser::Continue Func4(cm::string_view key, cm::string_view arg)
+  {
+    Func4_[std::string(key)].emplace_back(arg);
+    return key == "FUNC_4b" ? ArgumentParser::Continue::Yes
+                            : ArgumentParser::Continue::No;
+  }
+
+  ArgumentParser::Maybe<std::string> UnboundMaybe{ 'u', 'n', 'b', 'o',
+                                                   'u', 'n', 'd' };
+  ArgumentParser::MaybeEmpty<std::vector<std::string>> UnboundMaybeEmpty{
+    1, "unbound"
+  };
+  ArgumentParser::NonEmpty<std::vector<std::string>> UnboundNonEmpty{
+    1, "unbound"
+  };
+
   std::vector<cm::string_view> ParsedKeywords;
 };
 
@@ -46,6 +95,7 @@ std::initializer_list<cm::string_view> const args = {
   /* clang-format off */
   "OPTION_1",                // option
   // "OPTION_2",             // option that is not present
+  "pos1",                    // position index 1
   "STRING_1",                // string arg missing value
   "STRING_2", "foo", "bar",  // string arg + unparsed value, presence captured
   // "STRING_3",             // string arg that is not present
@@ -61,6 +111,13 @@ std::initializer_list<cm::string_view> const args = {
   "MULTI_3", "foo", "bar",   // multi list with first list with two elems
   "MULTI_3", "bar", "foo",   // multi list with second list with two elems
   // "MULTI_4",              // multi list arg that is not present
+  "FUNC_0",                  // callback arg missing value
+  "FUNC_1", "foo", "ign1",   // callback with one arg + unparsed value
+  "FUNC_2a", "foo", "ign2",  // callback with keyword-dependent arg count
+  "FUNC_2b", "bar", "zot",   // callback with keyword-dependent arg count
+  "FUNC_3", "foo", "bar",    // callback with list arg ...
+  "FUNC_4a", "foo", "ign4",  // callback with keyword-dependent arg count
+  "FUNC_4b", "bar", "zot",   // callback with keyword-dependent arg count
   /* clang-format on */
 };
 
@@ -69,6 +126,7 @@ bool verifyResult(Result const& result,
 {
   static std::vector<std::string> const foobar = { "foo", "bar" };
   static std::vector<std::string> const barfoo = { "bar", "foo" };
+  static std::vector<std::string> const unbound = { "unbound" };
   static std::vector<cm::string_view> const parsedKeywords = {
     /* clang-format off */
     "OPTION_1",
@@ -84,13 +142,29 @@ bool verifyResult(Result const& result,
     "MULTI_2",
     "MULTI_3",
     "MULTI_3",
+    "FUNC_0",
+    "FUNC_1",
+    "FUNC_2a",
+    "FUNC_2b",
+    "FUNC_3",
+    "FUNC_4a",
+    "FUNC_4b",
     /* clang-format on */
   };
+  static std::map<std::string, std::vector<std::string>> const func2map = {
+    { "FUNC_2a", { "foo" } }, { "FUNC_2b", { "bar", "zot" } }
+  };
+  static std::map<std::string, std::vector<std::string>> const func4map = {
+    { "FUNC_4a", { "foo" } }, { "FUNC_4b", { "bar", "zot" } }
+  };
   static std::map<cm::string_view, std::string> const keywordErrors = {
     { "STRING_1"_s, "  missing required value\n" },
     { "LIST_1"_s, "  missing required value\n" },
-    { "LIST_4"_s, "  missing required value\n" }
+    { "LIST_4"_s, "  missing required value\n" },
+    { "FUNC_0"_s, "  missing required value\n" }
   };
+  static std::vector<std::string> const unparsed = { "bar", "ign1", "ign2",
+                                                     "ign4" };
 
 #define ASSERT_TRUE(x)                                                        \
   do {                                                                        \
@@ -130,8 +204,19 @@ bool verifyResult(Result const& result,
   ASSERT_TRUE((*result.Multi3)[1] == barfoo);
   ASSERT_TRUE(!result.Multi4);
 
-  ASSERT_TRUE(unparsedArguments.size() == 1);
-  ASSERT_TRUE(unparsedArguments[0] == "bar");
+  ASSERT_TRUE(result.Pos1 == "pos1");
+
+  ASSERT_TRUE(result.Func0_ == false);
+  ASSERT_TRUE(result.Func1_ == "foo");
+  ASSERT_TRUE(result.Func2_ == func2map);
+  ASSERT_TRUE(result.Func3_ == foobar);
+  ASSERT_TRUE(result.Func4_ == func4map);
+
+  ASSERT_TRUE(unparsedArguments == unparsed);
+
+  ASSERT_TRUE(result.UnboundMaybe == "unbound");
+  ASSERT_TRUE(result.UnboundMaybeEmpty == unbound);
+  ASSERT_TRUE(result.UnboundNonEmpty == unbound);
 
   ASSERT_TRUE(result.ParsedKeywords == parsedKeywords);
 
@@ -150,6 +235,12 @@ bool testArgumentParserDynamic()
   Result result;
   std::vector<std::string> unparsedArguments;
 
+  std::function<ArgumentParser::Continue(cm::string_view, cm::string_view)>
+    func4 = [&result](cm::string_view key,
+                      cm::string_view arg) -> ArgumentParser::Continue {
+    return result.Func4(key, arg);
+  };
+
   static_cast<ArgumentParser::ParseResult&>(result) =
     cmArgumentParser<void>{}
       .Bind("OPTION_1"_s, result.Option1)
@@ -168,12 +259,38 @@ bool testArgumentParserDynamic()
       .Bind("MULTI_2"_s, result.Multi2)
       .Bind("MULTI_3"_s, result.Multi3)
       .Bind("MULTI_4"_s, result.Multi4)
+      .Bind(1, result.Pos1)
+      .Bind("FUNC_0"_s,
+            [&result](cm::string_view arg) -> ArgumentParser::Continue {
+              return result.Func0(arg);
+            })
+      .Bind("FUNC_1"_s,
+            [&result](cm::string_view arg) -> ArgumentParser::Continue {
+              return result.Func1(arg);
+            })
+      .Bind("FUNC_2a"_s,
+            [&result](cm::string_view key, cm::string_view arg)
+              -> ArgumentParser::Continue { return result.Func2(key, arg); })
+      .Bind("FUNC_2b"_s,
+            [&result](cm::string_view key, cm::string_view arg)
+              -> ArgumentParser::Continue { return result.Func2(key, arg); })
+      .Bind("FUNC_3"_s,
+            [&result](cm::string_view arg) -> ArgumentParser::Continue {
+              return result.Func3(arg);
+            })
+      .Bind("FUNC_4a"_s, func4)
+      .Bind("FUNC_4b"_s, func4)
       .BindParsedKeywords(result.ParsedKeywords)
       .Parse(args, &unparsedArguments);
 
   return verifyResult(result, unparsedArguments);
 }
 
+static auto const parserStaticFunc4 =
+  [](Result& result, cm::string_view key,
+     cm::string_view arg) -> ArgumentParser::Continue {
+  return result.Func4(key, arg);
+};
 static auto const parserStatic = //
   cmArgumentParser<Result>{}
     .Bind("OPTION_1"_s, &Result::Option1)
@@ -192,6 +309,17 @@ static auto const parserStatic = //
     .Bind("MULTI_2"_s, &Result::Multi2)
     .Bind("MULTI_3"_s, &Result::Multi3)
     .Bind("MULTI_4"_s, &Result::Multi4)
+    .Bind(1, &Result::Pos1)
+    .Bind("FUNC_0"_s, &Result::Func0)
+    .Bind("FUNC_1"_s, &Result::Func1)
+    .Bind("FUNC_2a"_s, &Result::Func2)
+    .Bind("FUNC_2b"_s, &Result::Func2)
+    .Bind("FUNC_3"_s,
+          [](Result& result, cm::string_view arg) -> ArgumentParser::Continue {
+            return result.Func3(arg);
+          })
+    .Bind("FUNC_4a"_s, parserStaticFunc4)
+    .Bind("FUNC_4b"_s, parserStaticFunc4)
     .BindParsedKeywords(&Result::ParsedKeywords)
   /* keep semicolon on own line */;