• Home
  • History
  • Annotate
Name Date Size #Lines LOC

..--

detail/22-Nov-2023-676399

Android.bpD22-Nov-2023955 3128

README.mdD22-Nov-20239.7 KiB246198

cmdline.hD22-Nov-202312 KiB392284

cmdline_parse_result.hD22-Nov-20234.8 KiB13974

cmdline_parser.hD22-Nov-202323.4 KiB634396

cmdline_parser_test.ccD22-Nov-202321.2 KiB572416

cmdline_result.hD22-Nov-20233 KiB

cmdline_type_parser.hD22-Nov-20232.9 KiB7723

cmdline_types.hD22-Nov-202324.9 KiB730556

memory_representation.hD22-Nov-20231.8 KiB7138

token_range.hD22-Nov-202313.8 KiB428261

unit.hD22-Nov-20231.1 KiB3713

README.md

1Cmdline
2===================
3
4Introduction
5-------------
6This directory contains the classes that do common command line tool initialization and parsing. The
7long term goal is eventually for all `art` command-line tools to be using these helpers.
8
9----------
10
11
12## Cmdline Parser
13-------------
14
15The `CmdlineParser` class provides a fluent interface using a domain-specific language to quickly
16generate a type-safe value parser that process a user-provided list of strings (`argv`). Currently,
17it can parse a string into a `VariantMap`, although in the future it might be desirable to parse
18into any struct of any field.
19
20To use, create a `CmdlineParser::Builder` and then chain the `Define` methods together with
21`WithType` and `IntoXX` methods.
22
23### Quick Start
24For example, to save the values into a user-defined variant map:
25
26```
27struct FruitVariantMap : VariantMap {
28  static const Key<int> Apple;
29  static const Key<double> Orange;
30  static const Key<bool> Help;
31};
32// Note that some template boilerplate has been avoided for clarity.
33// See variant_map_test.cc for how to completely define a custom map.
34
35using FruitParser = CmdlineParser<FruitVariantMap, FruitVariantMap::Key>;
36
37FruitParser MakeParser() {
38  auto&& builder = FruitParser::Builder();
39  builder.
40   .Define("--help")
41      .IntoKey(FruitVariantMap::Help)
42    Define("--apple:_")
43      .WithType<int>()
44      .IntoKey(FruitVariantMap::Apple)
45   .Define("--orange:_")
46      .WithType<double>()
47      .WithRange(0.0, 1.0)
48      .IntoKey(FruitVariantMap::Orange);
49
50  return builder.Build();
51}
52
53int main(char** argv, int argc) {
54  auto parser = MakeParser();
55  auto result = parser.parse(argv, argc));
56  if (result.isError()) {
57     std::cerr << result.getMessage() << std::endl;
58     return EXIT_FAILURE;
59  }
60  auto map = parser.GetArgumentsMap();
61  std::cout << "Help? " << map.GetOrDefault(FruitVariantMap::Help) << std::endl;
62  std::cout << "Apple? " << map.GetOrDefault(FruitVariantMap::Apple) << std::endl;
63  std::cout << "Orange? " << map.GetOrDefault(FruitVariantMap::Orange) << std::endl;
64
65  return EXIT_SUCCESS;
66}
67```
68
69In the above code sample, we define a parser which is capable of parsing something like `--help
70--apple:123 --orange:0.456` . It will error out automatically if invalid flags are given, or if the
71appropriate flags are given but of the the wrong type/range. So for example, `--foo` will not parse
72(invalid argument), neither will `--apple:fruit` (fruit is not an int) nor `--orange:1234` (1234 is
73out of range of [0.0, 1.0])
74
75### Argument Definitions in Detail
76#### Define method
77The 'Define' method takes one or more aliases for the argument. Common examples might be `{"-h",
78"--help"}` where both `--help` and `-h` are aliases for the same argument.
79
80The simplest kind of argument just tests for presence, but we often want to parse out a particular
81type of value (such as an int or double as in the above `FruitVariantMap` example). To do that, a
82_wildcard_ must be used to denote the location within the token that the type will be parsed out of.
83
84For example with `-orange:_` the parse would know to check all tokens in an `argv` list for the
85`-orange:` prefix and then strip it, leaving only the remains to be parsed.
86
87#### WithType method (optional)
88After an argument definition is provided, the parser builder needs to know what type the argument
89will be in order to provide the type safety and make sure the rest of the argument definition is
90correct as early as possible (in essence, everything but the parsing of the argument name is done at
91compile time).
92
93Everything that follows a `WithType<T>()` call is thus type checked to only take `T` values.
94
95If this call is omitted, the parser generator assumes you are building a `Unit` type (i.e. an
96argument that only cares about presence).
97
98#### WithRange method (optional)
99Some values will not make sense outside of a `[min, max]` range, so this is an option to quickly add
100a range check without writing custom code. The range check is performed after the main parsing
101happens and happens for any type implementing the `<=` operators.
102
103#### WithValueMap (optional)
104When parsing an enumeration, it might be very convenient to map a list of possible argument string
105values into its runtime value.
106
107With something like
108```
109    .Define("-hello:_")
110      .WithValueMap({"world", kWorld},
111                    {"galaxy", kGalaxy})
112```
113It will parse either `-hello:world` or `-hello:galaxy` only (and error out on other variations of
114`-hello:whatever`), converting it to the type-safe value of `kWorld` or `kGalaxy` respectively.
115
116This is meant to be another shorthand (like `WithRange`) to avoid writing a custom type parser. In
117general it takes a variadic number of `pair<const char* /*arg name*/, T /*value*/>`.
118
119#### WithValues (optional)
120When an argument definition has multiple aliases with no wildcards, it might be convenient to
121quickly map them into discrete values.
122
123For example:
124```
125  .Define({"-xinterpret", "-xnointerpret"})
126    .WithValues({true, false}
127```
128It will parse `-xinterpret` as `true` and `-xnointerpret` as `false`.
129
130In general, it uses the position of the argument alias to map into the WithValues position value.
131
132(Note that this method will not work when the argument definitions have a wildcard because there is
133no way to position-ally match that).
134
135#### AppendValues (optional)
136By default, the argument is assumed to appear exactly once, and if the user specifies it more than
137once, only the latest value is taken into account (and all previous occurrences of the argument are
138ignored).
139
140In some situations, we may want to accumulate the argument values instead of discarding the previous
141ones.
142
143For example
144```
145  .Define("-D")
146     .WithType<std::vector<std::string>)()
147     .AppendValues()
148```
149Will parse something like `-Dhello -Dworld -Dbar -Dbaz` into `std::vector<std::string>{"hello",
150"world", "bar", "baz"}`.
151
152### Setting an argument parse target (required)
153To complete an argument definition, the parser generator also needs to know where to save values.
154Currently, only `IntoKey` is supported, but that may change in the future.
155
156#### IntoKey (required)
157This specifies that when a value is parsed, it will get saved into a variant map using the specific
158key.
159
160For example,
161```
162   .Define("-help")
163     .IntoKey(Map::Help)
164```
165will save occurrences of the `-help` argument by doing a `Map.Set(Map::Help, ParsedValue("-help"))`
166where `ParsedValue` is an imaginary function that parses the `-help` argment into a specific type
167set by `WithType`.
168
169### Ignoring unknown arguments
170This is highly discouraged, but for compatibility with `JNI` which allows argument ignores, there is
171an option to ignore any argument tokens that are not known to the parser. This is done with the
172`Ignore` function which takes a list of argument definition names.
173
174It's semantically equivalent to making a series of argument definitions that map to `Unit` but don't
175get saved anywhere. Values will still get parsed as normal, so it will *not* ignore known arguments
176with invalid values, only user-arguments for which it could not find a matching argument definition.
177
178### Parsing custom types
179Any type can be parsed from a string by specializing the `CmdlineType` class and implementing the
180static interface provided by `CmdlineTypeParser`. It is recommended to inherit from
181`CmdlineTypeParser` since it already provides default implementations for every method.
182
183The `Parse` method should be implemented for most types. Some types will allow appending (such as an
184`std::vector<std::string>` and are meant to be used with `AppendValues` in which case the
185`ParseAndAppend` function should be implemented.
186
187For example:
188```
189template <>
190struct CmdlineType<double> : CmdlineTypeParser<double> {
191  Result Parse(const std::string& str) {
192    char* end = nullptr;
193    errno = 0;
194    double value = strtod(str.c_str(), &end);
195
196    if (*end != '\0') {
197      return Result::Failure("Failed to parse double from " + str);
198    }
199    if (errno == ERANGE) {
200      return Result::OutOfRange(
201          "Failed to parse double from " + str + "; overflow/underflow occurred");
202    }
203
204    return Result::Success(value);
205  }
206
207  static const char* Name() { return "double"; }
208  // note: Name() is just here for more user-friendly errors,
209  // but in the future we will use non-standard ways of getting the type name
210  // at compile-time and this will no longer be required
211};
212```
213Will parse any non-append argument definitions with a type of `double`.
214
215For an appending example:
216```
217template <>
218struct CmdlineType<std::vector<std::string>> : CmdlineTypeParser<std::vector<std::string>> {
219  Result ParseAndAppend(const std::string& args,
220                        std::vector<std::string>& existing_value) {
221    existing_value.push_back(args);
222    return Result::SuccessNoValue();
223  }
224  static const char* Name() { return "std::vector<std::string>"; }
225};
226```
227Will parse multiple instances of the same argument repeatedly into the `existing_value` (which will
228be default-constructed to `T{}` for the first occurrence of the argument).
229
230#### What is a `Result`?
231`Result` is a typedef for `CmdlineParseResult<T>` and it acts similar to a poor version of
232`Either<Left, Right>` in Haskell. In particular, it would be similar to `Either< int ErrorCode,
233Maybe<T> >`.
234
235There are helpers like `Result::Success(value)`, `Result::Failure(string message)` and so on to
236quickly construct these without caring about the type.
237
238When successfully parsing a single value, `Result::Success(value)` should be used, and when
239successfully parsing an appended value, use `Result::SuccessNoValue()` and write back the new value
240into `existing_value` as an out-parameter.
241
242When many arguments are parsed, the result is collapsed down to a `CmdlineResult` which acts as a
243`Either<int ErrorCode, Unit>` where the right side simply indicates success. When values are
244successfully stored, the parser will automatically save it into the target destination as a side
245effect.
246