openMSX
unittest/TclArgParser.cc
Go to the documentation of this file.
1 #include "catch.hpp"
2 #include "TclArgParser.hh"
3 
4 #include "Interpreter.hh"
5 #include "span.hh"
6 #include <optional>
7 
8 using namespace openmsx;
9 
10 TEST_CASE("TclArgParser")
11 {
12  Interpreter interp;
13 
14  // variables (possibly) filled in by parser
15  int int1 = -1;
16  std::optional<int> int2;
17  double dbl1 = -1.0;
18  std::optional<double> dbl2;
19  std::string s1;
20  std::optional<std::string> s2;
21  std::vector<int> ints;
22  bool flag = false;
23 
24  // description of the parser
25  const ArgsInfo table[] = {
26  valueArg("-int1", int1),
27  valueArg("-int2", int2),
28  valueArg("-double1", dbl1),
29  valueArg("-double2", dbl2),
30  valueArg("-string1", s1),
31  valueArg("-string2", s2),
32  valueArg("-ints", ints),
33  flagArg("-flag", flag),
34  };
35 
36  SECTION("empty") {
38  auto out = parseTclArgs(interp, in, table);
39 
40  CHECK(out.empty()); // no args
41  CHECK(int1 == -1); // other stuff unchanged
42  CHECK(!int2);
43  CHECK(!flag);
44  }
45  SECTION("only normal args") {
46  TclObject in[] = { TclObject("arg1"), TclObject(2), TclObject(3) };
47  auto out = parseTclArgs(interp, in, table);
48 
49  CHECK(out.size() == 3);
50  CHECK(out[0] == "arg1");
51  CHECK(out[1] == "2");
52  CHECK(out[2] == "3");
53  CHECK(int1 == -1); // other stuff unchanged
54  CHECK(!int2);
55  }
56  SECTION("(regular) integer option") {
57  TclObject in[] = { TclObject("-int1"), TclObject(123) };
58  auto out = parseTclArgs(interp, in, table);
59 
60  CHECK(out.empty()); // no regular args
61  CHECK(int1 == 123); // this has a new value
62  CHECK(!int2); // other stuff unchanged
63  }
64  SECTION("(optional) integer option") {
65  TclObject in[] = { TclObject("-int2"), TclObject(456) };
66  auto out = parseTclArgs(interp, in, table);
67 
68  CHECK(out.empty()); // no regular args
69  CHECK(int1 == -1); // this is unchanged (or was it explicitly set to -1 ;-)
70  CHECK(int2); // with an optional<int> we can check that it was set or not
71  CHECK(*int2 == 456);
72  }
73  SECTION("(regular) double option") {
74  TclObject in[] = { TclObject("-double1"), TclObject(2.72) };
75  auto out = parseTclArgs(interp, in, table);
76 
77  CHECK(out.empty()); // no regular args
78  CHECK(dbl1 == 2.72); // this has a new value
79  }
80  SECTION("(regular) string option") {
81  TclObject in[] = { TclObject("-string1"), TclObject("foobar") };
82  auto out = parseTclArgs(interp, in, table);
83 
84  CHECK(out.empty()); // no regular args
85  CHECK(s1 == "foobar"); // this has a new value
86  }
87  SECTION("flag value") {
88  TclObject in[] = { TclObject("-flag") };
89  auto out = parseTclArgs(interp, in, table);
90 
91  CHECK(out.empty()); // no regular args
92  CHECK(flag); // flag was set
93  }
94  SECTION("multiple options and args") {
95  TclObject in[] = { TclObject("bla"), TclObject("-int2"), TclObject(789), TclObject("qwerty"),
96  TclObject("-double1"), TclObject("6.28"), TclObject("-string2"), TclObject("bar"),
97  TclObject("zyxwv"), TclObject("-flag"), TclObject("-int1"), TclObject("-30"),
98  };
99  auto out = parseTclArgs(interp, in, table);
100 
101  CHECK(out.size() == 3);
102  CHECK(out[0] == "bla");
103  CHECK(out[1] == "qwerty");
104  CHECK(out[2] == "zyxwv");
105  CHECK(int1 == -30);
106  CHECK(int2); CHECK(*int2 == 789);
107  CHECK(dbl1 == 6.28);
108  CHECK(!dbl2);
109  CHECK(s1 == "");
110  CHECK(s2); CHECK(*s2 == "bar");
111  CHECK(flag);
112  }
113  SECTION("set same option twice") {
114  TclObject in[] = { TclObject("-int1"), TclObject(123), TclObject("bla"), TclObject("-int1"), TclObject(234) };
115  auto out = parseTclArgs(interp, in, table);
116 
117  CHECK(out.size() == 1);
118  CHECK(int1 == 234); // take the value of the last option
119  }
120  SECTION("vector<T> accepts repeated options") {
121  TclObject in[] = { TclObject("-ints"), TclObject(11), TclObject("-ints"), TclObject(22) };
122  auto out = parseTclArgs(interp, in, table);
123 
124  CHECK(out.empty());
125  CHECK(ints.size() == 2);
126  CHECK(ints[0] == 11);
127  CHECK(ints[1] == 22);
128  }
129  SECTION("no options after --") {
130  TclObject in[] = { TclObject("-int1"), TclObject(123), TclObject("--"), TclObject("-int1"), TclObject(234) };
131  auto out = parseTclArgs(interp, in, table);
132 
133  CHECK(out.size() == 2);
134  CHECK(out[0] == "-int1");
135  CHECK(out[1] == "234");
136  CHECK(int1 == 123); // take the value of the option before --
137  }
138  SECTION("missing value for option") {
139  TclObject in[] = { TclObject("bla"), TclObject("-int1") };
140  CHECK_THROWS(parseTclArgs(interp, in, table));
141  }
142  SECTION("non-integer value for integer-option") {
143  TclObject in[] = { TclObject("-int1"), TclObject("bla") };
144  CHECK_THROWS(parseTclArgs(interp, in, table));
145  }
146  SECTION("non-double value for double-option") {
147  TclObject in[] = { TclObject("-double2"), TclObject("bla") };
148  CHECK_THROWS(parseTclArgs(interp, in, table));
149  }
150  SECTION("unknown option") {
151  TclObject in[] = { TclObject("-bla"), TclObject("bla") };
152  CHECK_THROWS(parseTclArgs(interp, in, table));
153  }
154 }
ArgsInfo flagArg(std::string_view name, bool &flag)
Definition: TclArgParser.hh:72
Definition: span.hh:34
CHECK(m3==m3)
Thanks to enen for testing this on a real cartridge:
Definition: Autofire.cc:5
TEST_CASE("TclArgParser")
ArgsInfo valueArg(std::string_view name, T &value)
Definition: TclArgParser.hh:85
std::vector< TclObject > parseTclArgs(Interpreter &interp, span< const TclObject > inArgs, span< const ArgsInfo > table)