summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2021-08-03 14:11:17 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2021-08-03 14:11:17 +0200
commit0426335af0e7577148903be7a2534c4ced06cd3b (patch)
tree9d7d1de01c9e438097cfd9d235f4df3206761b54
parenta54c695583015812280228bcadca3057397d4dd0 (diff)
Add support for tracking argument/option position
The scanner interface now provides the position() function that returns a monotonically-increasing number which, if stored, can later be used to determine the relative position of the arguments. There is also now a parser implementation for std::pair<T, std::size_t> which parses the value T into the first half of the pair and stores the option position in the second half. Together, this can be used to establish the relative position of different options, for example: class options { std::vector<std::pair<std::uint64_t, std::size>> --config-id; std::vector<std::pair<std::string, std::size>> --config-name; }; cli::argv_scanner scan (argc, argv); options ops (scan); // Iterate over --config-id and --config-name options in the order // specified by the user. // auto ii (ops.config_id ().begin ()); auto ni (ops.config_name ().begin ()); for (size_t i (0), n (scan.position ()); i != n; ++i) { if (ii != ops.config_id ().end () && ii->second == i) { // Handle *ii. ++ii; } if (ni != ops.config_name ().end () && ni->second == i) { // Handle *ni. ++ni; } }
-rw-r--r--cli-tests/position/buildfile9
-rw-r--r--cli-tests/position/driver.cxx38
-rw-r--r--cli-tests/position/test.cli13
-rw-r--r--cli-tests/position/testscript36
-rw-r--r--cli/cli/runtime-header.cxx79
-rw-r--r--cli/cli/runtime-inline.cxx63
-rw-r--r--cli/cli/runtime-source.cxx75
7 files changed, 271 insertions, 42 deletions
diff --git a/cli-tests/position/buildfile b/cli-tests/position/buildfile
new file mode 100644
index 0000000..371cc54
--- /dev/null
+++ b/cli-tests/position/buildfile
@@ -0,0 +1,9 @@
+# file : position/buildfile
+# license : MIT; see accompanying LICENSE file
+
+exe{driver}: {hxx cxx}{* -test} cli.cxx{test} testscript
+
+cxx.poptions =+ "-I$out_base"
+
+cli.cxx{test}: cli{test}
+cli.options = --generate-file-scanner --generate-specifier
diff --git a/cli-tests/position/driver.cxx b/cli-tests/position/driver.cxx
new file mode 100644
index 0000000..77b9a8d
--- /dev/null
+++ b/cli-tests/position/driver.cxx
@@ -0,0 +1,38 @@
+// file : position/driver.cxx
+// author : Boris Kolpackov <boris@codesynthesis.com>
+// license : MIT; see accompanying LICENSE file
+
+// Test argument/option position.
+//
+#include <iostream>
+
+#include "test.hxx"
+
+using namespace std;
+
+int
+main (int argc, char* argv[])
+{
+ try
+ {
+ cli::argv_file_scanner scan (argc, argv, "--file");
+ options ops (scan);
+
+ if (ops.a_specified ())
+ cout << ops.a ().second << ": " << "-a " << ops.a ().first << endl;
+
+ for (const pair<int, size_t>& b: ops.b ())
+ {
+ cout << b.second << ": " << "-b " << b.first << endl;
+ }
+
+ while (scan.more ())
+ cout << scan.position () << ": " << scan.next () << endl;
+
+ cout << "max: " << scan.position () << endl;
+ }
+ catch (const cli::exception& e)
+ {
+ cerr << e << endl;
+ }
+}
diff --git a/cli-tests/position/test.cli b/cli-tests/position/test.cli
new file mode 100644
index 0000000..7fc8655
--- /dev/null
+++ b/cli-tests/position/test.cli
@@ -0,0 +1,13 @@
+// file : position/test.cli
+// author : Boris Kolpackov <boris@codesynthesis.com>
+// license : MIT; see accompanying LICENSE file
+
+include <vector>;
+include <utility>;
+include <cstddef>;
+
+class options
+{
+ std::pair<int, std::size_t> -a;
+ std::vector<std::pair<int, std::size_t> > -b;
+};
diff --git a/cli-tests/position/testscript b/cli-tests/position/testscript
new file mode 100644
index 0000000..568e571
--- /dev/null
+++ b/cli-tests/position/testscript
@@ -0,0 +1,36 @@
+# file : position/testscript
+# license : MIT; see accompanying LICENSE file
+
+: basics
+:
+$* -b 1 -a 2 -b 3 foo bar baz >>EOO
+3: -a 2
+1: -b 1
+5: -b 3
+7: foo
+8: bar
+9: baz
+max: 10
+EOO
+
+: override
+:
+$* -a 1 -a 2 >>EOO
+3: -a 2
+max: 5
+EOO
+
+: file
+:
+cat <<EOI >=test.ops;
+-a 2
+EOI
+$* -b 1 --file test.ops -b 2 foo bar baz >>EOO
+5: -a 2
+1: -b 1
+7: -b 2
+9: foo
+10: bar
+11: baz
+max: 12
+EOO
diff --git a/cli/cli/runtime-header.cxx b/cli/cli/runtime-header.cxx
index adf70dd..5bbe5c6 100644
--- a/cli/cli/runtime-header.cxx
+++ b/cli/cli/runtime-header.cxx
@@ -308,12 +308,20 @@ generate_runtime_header (context& ctx)
// scanner
//
- os << "// Command line argument scanner interface." << endl
- << "//" << endl
- << "// The values returned by next() are guaranteed to be valid" << endl
- << "// for the two previous arguments up until a call to a third" << endl
- << "// peek() or next()." << endl
- << "//" << endl
+ os << "// Command line argument scanner interface." << endl
+ << "//" << endl
+ << "// The values returned by next() are guaranteed to be valid" << endl
+ << "// for the two previous arguments up until a call to a third" << endl
+ << "// peek() or next()." << endl
+ << "//" << endl
+ << "// The position() function returns a monotonically-increasing" << endl
+ << "// number which, if stored, can later be used to determine the"<< endl
+ << "// relative position of the argument returned by the following"<< endl
+ << "// call to next(). Note that if multiple scanners are used to" << endl
+ << "// extract arguments from multiple sources, then the end" << endl
+ << "// position of the previous scanner should be used as the" << endl
+ << "// start position of the next." << endl
+ << "//" << endl
<< "class scanner"
<< "{"
<< "public:" << endl
@@ -331,6 +339,9 @@ generate_runtime_header (context& ctx)
<< endl
<< "virtual void" << endl
<< "skip () = 0;"
+ << endl
+ << "virtual std::size_t" << endl
+ << "position () = 0;"
<< "};";
// argv_scanner
@@ -338,8 +349,16 @@ generate_runtime_header (context& ctx)
os << "class argv_scanner: public scanner"
<< "{"
<< "public:" << endl
- << "argv_scanner (int& argc, char** argv, bool erase = false);"
- << "argv_scanner (int start, int& argc, char** argv, bool erase = false);"
+ << "argv_scanner (int& argc," << endl
+ << "char** argv," << endl
+ << "bool erase = false," << endl
+ << "std::size_t start_position = 0);"
+ << endl
+ << "argv_scanner (int start," << endl
+ << "int& argc," << endl
+ << "char** argv," << endl
+ << "bool erase = false," << endl
+ << "std::size_t start_position = 0);"
<< endl
<< "int" << endl
<< "end () const;"
@@ -356,7 +375,11 @@ generate_runtime_header (context& ctx)
<< "virtual void" << endl
<< "skip ();"
<< endl
- << "private:" << endl
+ << "virtual std::size_t" << endl
+ << "position ();"
+ << endl
+ << "protected:" << endl
+ << "std::size_t start_position_;"
<< "int i_;"
<< "int& argc_;"
<< "char** argv_;"
@@ -370,14 +393,15 @@ generate_runtime_header (context& ctx)
os << "class vector_scanner: public scanner"
<< "{"
<< "public:" << endl
- << "vector_scanner (const std::vector<std::string>&, " <<
- "std::size_t start = 0);"
+ << "vector_scanner (const std::vector<std::string>&," << endl
+ << "std::size_t start = 0," << endl
+ << "std::size_t start_position = 0);"
<< endl
<< "std::size_t" << endl
<< "end () const;"
<< endl
<< "void" << endl
- << "reset (std::size_t start = 0);"
+ << "reset (std::size_t start = 0, std::size_t start_position = 0);"
<< endl
<< "virtual bool" << endl
<< "more ();"
@@ -391,7 +415,11 @@ generate_runtime_header (context& ctx)
<< "virtual void" << endl
<< "skip ();"
<< endl
+ << "virtual std::size_t" << endl
+ << "position ();"
+ << endl
<< "private:" << endl
+ << "std::size_t start_position_;"
<< "const std::vector<std::string>& v_;"
<< "std::size_t i_;"
<< "};";
@@ -407,16 +435,19 @@ generate_runtime_header (context& ctx)
<< "argv_file_scanner (int& argc," << endl
<< "char** argv," << endl
<< "const std::string& option," << endl
- << "bool erase = false);"
+ << "bool erase = false," << endl
+ << "std::size_t start_position = 0);"
<< endl
<< "argv_file_scanner (int start," << endl
<< "int& argc," << endl
<< "char** argv," << endl
<< "const std::string& option," << endl
- << "bool erase = false);"
+ << "bool erase = false," << endl
+ << "std::size_t start_position = 0);"
<< endl
<< "argv_file_scanner (const std::string& file," << endl
- << "const std::string& option);"
+ << "const std::string& option," << endl
+ << "std::size_t start_position = 0);"
<< endl
<< "struct option_info"
<< "{"
@@ -432,18 +463,21 @@ generate_runtime_header (context& ctx)
<< "char** argv," << endl
<< "const option_info* options," << endl
<< "std::size_t options_count," << endl
- << "bool erase = false);"
+ << "bool erase = false," << endl
+ << "std::size_t start_position = 0);"
<< endl
<< "argv_file_scanner (int start," << endl
<< "int& argc," << endl
<< "char** argv," << endl
<< "const option_info* options," << endl
<< "std::size_t options_count," << endl
- << "bool erase = false);"
+ << "bool erase = false," << endl
+ << "std::size_t start_position = 0);"
<< endl
<< "argv_file_scanner (const std::string& file," << endl
<< "const option_info* options = 0," << endl
- << "std::size_t options_count = 0);"
+ << "std::size_t options_count = 0," << endl
+ << "std::size_t start_position = 0);"
<< endl
<< "virtual bool" << endl
<< "more ();"
@@ -457,6 +491,9 @@ generate_runtime_header (context& ctx)
<< "virtual void" << endl
<< "skip ();"
<< endl
+ << "virtual std::size_t" << endl
+ << "position ();"
+ << endl
<< "// Return the file path if the peeked at argument came from a file and" << endl
<< "// the empty string otherwise. The reference is guaranteed to be valid" << endl
<< "// till the end of the scanner lifetime." << endl
@@ -529,12 +566,18 @@ generate_runtime_header (context& ctx)
<< "virtual void" << endl
<< "skip ();"
<< endl
+ << "virtual std::size_t" << endl
+ << "position ();"
+ << endl
<< "// The group is only available after the call to next()" << endl
<< "// (and skip() -- in case one needs to make sure the group" << endl
<< "// was empty, or some such) and is only valid (and must be" << endl
<< "// handled) until the next call to any of the scanner" << endl
<< "// functions (including more())." << endl
<< "//" << endl
+ << "// Note also that argument positions within each group start"<< endl
+ << "// from 0." << endl
+ << "//" << endl
<< "scanner&" << endl
<< "group ();"
<< endl
diff --git a/cli/cli/runtime-inline.cxx b/cli/cli/runtime-inline.cxx
index 8f0e84c..e3dce1b 100644
--- a/cli/cli/runtime-inline.cxx
+++ b/cli/cli/runtime-inline.cxx
@@ -232,14 +232,29 @@ generate_runtime_inline (context& ctx)
<< "//" << endl;
os << inl << "argv_scanner::" << endl
- << "argv_scanner (int& argc, char** argv, bool erase)" << endl
- << ": i_ (1), argc_ (argc), argv_ (argv), erase_ (erase)"
+ << "argv_scanner (int& argc," << endl
+ << "char** argv," << endl
+ << "bool erase," << endl
+ << "std::size_t sp)" << endl
+ << ": start_position_ (sp + 1)," << endl
+ << " i_ (1)," << endl
+ << " argc_ (argc)," << endl
+ << " argv_ (argv)," << endl
+ << " erase_ (erase)"
<< "{"
<< "}";
os << inl << "argv_scanner::" << endl
- << "argv_scanner (int start, int& argc, char** argv, bool erase)" << endl
- << ": i_ (start), argc_ (argc), argv_ (argv), erase_ (erase)"
+ << "argv_scanner (int start," << endl
+ << "int& argc," << endl
+ << "char** argv," << endl
+ << "bool erase," << endl
+ << "std::size_t sp)" << endl
+ << ": start_position_ (sp + static_cast<std::size_t> (start))," << endl
+ << " i_ (start)," << endl
+ << " argc_ (argc)," << endl
+ << " argv_ (argv)," << endl
+ << " erase_ (erase)"
<< "{"
<< "}";
@@ -257,9 +272,10 @@ generate_runtime_inline (context& ctx)
<< "//" << endl;
os << inl << "vector_scanner::" << endl
- << "vector_scanner (const std::vector<std::string>& v, " <<
- "std::size_t i)" << endl
- << ": v_ (v), i_ (i)"
+ << "vector_scanner (const std::vector<std::string>& v," << endl
+ << "std::size_t i," << endl
+ << "std::size_t sp)" << endl
+ << ": start_position_ (sp), v_ (v), i_ (i)"
<< "{"
<< "}";
@@ -270,9 +286,10 @@ generate_runtime_inline (context& ctx)
<< "}";
os << inl << "void vector_scanner::" << endl
- << "reset (std::size_t i)"
+ << "reset (std::size_t i, std::size_t sp)"
<< "{"
<< "i_ = i;"
+ << "start_position_ = sp;"
<< "}";
}
@@ -289,8 +306,9 @@ generate_runtime_inline (context& ctx)
<< "argv_file_scanner (int& argc," << endl
<< "char** argv," << endl
<< "const std::string& option," << endl
- << "bool erase)" << endl
- << ": argv_scanner (argc, argv, erase)," << endl
+ << "bool erase," << endl
+ << "std::size_t sp)" << endl
+ << ": argv_scanner (argc, argv, erase, sp)," << endl
<< " option_ (option)," << endl
<< " options_ (&option_info_)," << endl
<< " options_count_ (1)," << endl
@@ -308,8 +326,9 @@ generate_runtime_inline (context& ctx)
<< "int& argc," << endl
<< "char** argv," << endl
<< "const std::string& option," << endl
- << "bool erase)" << endl
- << ": argv_scanner (start, argc, argv, erase)," << endl
+ << "bool erase," << endl
+ << "std::size_t sp)" << endl
+ << ": argv_scanner (start, argc, argv, erase, sp)," << endl
<< " option_ (option)," << endl
<< " options_ (&option_info_)," << endl
<< " options_count_ (1)," << endl
@@ -324,8 +343,9 @@ generate_runtime_inline (context& ctx)
os << inl << "argv_file_scanner::" << endl
<< "argv_file_scanner (const std::string& file," << endl
- << "const std::string& option)" << endl
- << ": argv_scanner (0, zero_argc_, 0)," << endl
+ << "const std::string& option," << endl
+ << "std::size_t sp)" << endl
+ << ": argv_scanner (0, zero_argc_, 0, sp)," << endl
<< " option_ (option)," << endl
<< " options_ (&option_info_)," << endl
<< " options_count_ (1)," << endl
@@ -345,8 +365,9 @@ generate_runtime_inline (context& ctx)
<< "char** argv," << endl
<< "const option_info* options," << endl
<< "std::size_t options_count," << endl
- << "bool erase)" << endl
- << ": argv_scanner (argc, argv, erase)," << endl
+ << "bool erase," << endl
+ << "std::size_t sp)" << endl
+ << ": argv_scanner (argc, argv, erase, sp)," << endl
<< " options_ (options)," << endl
<< " options_count_ (options_count)," << endl
<< " i_ (1)";
@@ -362,8 +383,9 @@ generate_runtime_inline (context& ctx)
<< "char** argv," << endl
<< "const option_info* options," << endl
<< "std::size_t options_count," << endl
- << "bool erase)" << endl
- << ": argv_scanner (start, argc, argv, erase)," << endl
+ << "bool erase," << endl
+ << "std::size_t sp)" << endl
+ << ": argv_scanner (start, argc, argv, erase, sp)," << endl
<< " options_ (options)," << endl
<< " options_count_ (options_count)," << endl
<< " i_ (1)";
@@ -376,8 +398,9 @@ generate_runtime_inline (context& ctx)
os << inl << "argv_file_scanner::" << endl
<< "argv_file_scanner (const std::string& file," << endl
<< "const option_info* options," << endl
- << "std::size_t options_count)" << endl
- << ": argv_scanner (0, zero_argc_, 0)," << endl
+ << "std::size_t options_count," << endl
+ << "std::size_t sp)" << endl
+ << ": argv_scanner (0, zero_argc_, 0, sp)," << endl
<< " options_ (options)," << endl
<< " options_count_ (options_count)," << endl
<< " i_ (1)";
diff --git a/cli/cli/runtime-source.cxx b/cli/cli/runtime-source.cxx
index 3864163..81eab4a 100644
--- a/cli/cli/runtime-source.cxx
+++ b/cli/cli/runtime-source.cxx
@@ -15,6 +15,7 @@ generate_runtime_source (context& ctx, bool complete)
<< "#include <set>" << endl
<< "#include <string>" << endl
<< "#include <vector>" << endl
+ << "#include <utility>" << endl // pair
<< "#include <ostream>" << endl
<< "#include <sstream>" << endl;
@@ -259,6 +260,10 @@ generate_runtime_source (context& ctx, bool complete)
// argv_scanner
//
+ // Note that due to the erase logic we cannot just add i_ to
+ // start_position and so have to increment it instead. See also
+ // argv_file_scanner that continues with this logic.
+ //
os << "// argv_scanner" << endl
<< "//" << endl
@@ -295,6 +300,7 @@ generate_runtime_source (context& ctx, bool complete)
<< "else" << endl
<< "++i_;"
<< endl
+ << "++start_position_;"
<< "return r;"
<< "}"
<< "else" << endl
@@ -304,10 +310,19 @@ generate_runtime_source (context& ctx, bool complete)
<< "void argv_scanner::" << endl
<< "skip ()"
<< "{"
- << "if (i_ < argc_)" << endl
+ << "if (i_ < argc_)"
+ << "{"
<< "++i_;"
+ << "++start_position_;"
+ << "}"
<< "else" << endl
<< "throw eos_reached ();"
+ << "}"
+
+ << "std::size_t argv_scanner::" << endl
+ << "position ()"
+ << "{"
+ << "return start_position_;"
<< "}";
// vector_scanner
@@ -348,11 +363,19 @@ generate_runtime_source (context& ctx, bool complete)
<< "++i_;"
<< "else" << endl
<< "throw eos_reached ();"
+ << "}"
+
+ << "std::size_t vector_scanner::" << endl
+ << "position ()"
+ << "{"
+ << "return start_position_ + i_;"
<< "}";
}
// argv_file_scanner
//
+ // Note that we continue incrementing start_position like argv_scanner.
+ //
if (ctx.options.generate_file_scanner ())
{
bool sep (!ctx.opt_sep.empty ());
@@ -486,6 +509,7 @@ generate_runtime_source (context& ctx, bool complete)
<< "{"
<< "hold_[i_ == 0 ? ++i_ : --i_].swap (args_.front ().value);"
<< "args_.pop_front ();"
+ << "++start_position_;"
<< "return hold_[i_].c_str ();"
<< "}"
<< "}"
@@ -498,8 +522,11 @@ generate_runtime_source (context& ctx, bool complete)
<< endl
<< "if (args_.empty ())" << endl
<< "return base::skip ();"
- << "else" << endl
+ << "else"
+ << "{"
<< "args_.pop_front ();"
+ << "++start_position_;"
+ << "}"
<< "}"
<< "const argv_file_scanner::option_info* argv_file_scanner::" << endl
@@ -512,6 +539,12 @@ generate_runtime_source (context& ctx, bool complete)
<< "return 0;"
<< "}"
+ << "std::size_t argv_file_scanner::" << endl
+ << "position ()"
+ << "{"
+ << "return start_position_;"
+ << "}"
+
<< "void argv_file_scanner::" << endl
<< "load (const std::string& file)"
<< "{"
@@ -734,6 +767,12 @@ generate_runtime_source (context& ctx, bool complete)
<< "scan_group (skipped);"
<< "}"
+ << "std::size_t group_scanner::" << endl
+ << "position ()"
+ << "{"
+ << "return scan_.position ();"
+ << "}"
+
<< "void group_scanner::" << endl
<< "scan_group (state st)"
<< "{"
@@ -746,6 +785,11 @@ generate_runtime_source (context& ctx, bool complete)
<< "throw unexpected_group (arg_[i_], group_scan_.next ());"
<< "}"
+ // Note that while it may seem like a good idea to pass
+ // scan_.position() to reset() below, the trailing group positions
+ // will overlap with the argument's. So it seems best to start
+ // positions of each argument in a group from 0.
+ //
<< "if (state_ != peeked)"
<< "{"
<< "arg_[i_ == 0 ? ++i_ : --i_].clear ();"
@@ -934,6 +978,28 @@ generate_runtime_source (context& ctx, bool complete)
os << "};";
+ // parser<std::pair<X, std::size_t>>
+ //
+ os << "template <typename X>" << endl
+ << "struct parser<std::pair<X, std::size_t> >"
+ << "{";
+
+ os << "static void" << endl
+ << "parse (std::pair<X, std::size_t>& x, " << (sp ? "bool& xs, " : "") << "scanner& s)"
+ << "{"
+ << "x.second = s.position ();"
+ << "parser<X>::parse (x.first, " << (sp ? "xs, " : "") << "s);"
+ << "}";
+
+ if (gen_merge)
+ os << "static void" << endl
+ << "merge (std::pair<X, std::size_t>& b, const std::pair<X, std::size_t>& a)"
+ << "{"
+ << "b = a;"
+ << "}";
+
+ os << "};";
+
// parser<std::vector<X>>
//
os << "template <typename X>" << endl
@@ -1001,6 +1067,7 @@ generate_runtime_source (context& ctx, bool complete)
<< endl
<< "if (s.more ())"
<< "{"
+ << "std::size_t pos (s.position ());"
<< "std::string ov (s.next ());"
<< "std::string::size_type p = ov.find ('=');"
<< endl
@@ -1020,13 +1087,13 @@ generate_runtime_source (context& ctx, bool complete)
os << "if (!kstr.empty ())"
<< "{"
<< "av[1] = const_cast<char*> (kstr.c_str ());"
- << "argv_scanner s (0, ac, av);"
+ << "argv_scanner s (0, ac, av, false, pos);"
<< "parser<K>::parse (k, " << (sp ? "dummy, " : "") << "s);"
<< "}"
<< "if (!vstr.empty ())"
<< "{"
<< "av[1] = const_cast<char*> (vstr.c_str ());"
- << "argv_scanner s (0, ac, av);"
+ << "argv_scanner s (0, ac, av, false, pos);"
<< "parser<V>::parse (v, " << (sp ? "dummy, " : "") << "s);"
<< "}"
<< "m[k] = v;"