HPCToolkit
CmdLineParser.cpp
Go to the documentation of this file.
1 // -*-Mode: C++;-*-
2 
3 // * BeginRiceCopyright *****************************************************
4 //
5 // $HeadURL$
6 // $Id$
7 //
8 // --------------------------------------------------------------------------
9 // Part of HPCToolkit (hpctoolkit.org)
10 //
11 // Information about sources of support for research and development of
12 // HPCToolkit is at 'hpctoolkit.org' and in 'README.Acknowledgments'.
13 // --------------------------------------------------------------------------
14 //
15 // Copyright ((c)) 2002-2019, Rice University
16 // All rights reserved.
17 //
18 // Redistribution and use in source and binary forms, with or without
19 // modification, are permitted provided that the following conditions are
20 // met:
21 //
22 // * Redistributions of source code must retain the above copyright
23 // notice, this list of conditions and the following disclaimer.
24 //
25 // * Redistributions in binary form must reproduce the above copyright
26 // notice, this list of conditions and the following disclaimer in the
27 // documentation and/or other materials provided with the distribution.
28 //
29 // * Neither the name of Rice University (RICE) nor the names of its
30 // contributors may be used to endorse or promote products derived from
31 // this software without specific prior written permission.
32 //
33 // This software is provided by RICE and contributors "as is" and any
34 // express or implied warranties, including, but not limited to, the
35 // implied warranties of merchantability and fitness for a particular
36 // purpose are disclaimed. In no event shall RICE or contributors be
37 // liable for any direct, indirect, incidental, special, exemplary, or
38 // consequential damages (including, but not limited to, procurement of
39 // substitute goods or services; loss of use, data, or profits; or
40 // business interruption) however caused and on any theory of liability,
41 // whether in contract, strict liability, or tort (including negligence
42 // or otherwise) arising in any way out of the use of this software, even
43 // if advised of the possibility of such damage.
44 //
45 // ******************************************************* EndRiceCopyright *
46 
47 //***************************************************************************
48 //
49 // File:
50 // $HeadURL$
51 //
52 // Nathan Tallent
53 //
54 // Purpose:
55 // [The purpose of this file]
56 //
57 // Description:
58 // [The set of functions, macros, etc. defined in the file]
59 //
60 //***************************************************************************
61 
62 //************************* System Include Files ****************************
63 
64 #include <cstdlib> // for strtol
65 #include <cstring>
66 #include <cerrno>
67 
68 #include <algorithm> // for sort
69 
70 //************************** Open64 Include Files ***************************
71 
72 //*************************** User Include Files ****************************
73 
74 #include "CmdLineParser.hpp"
75 
76 #include "diagnostics.h"
77 #include "StrUtil.hpp"
78 
79 //*************************** Forward Declarations ***************************
80 
81 using std::string;
82 
83 static string MISSING_SWITCH = "Missing switch after -";
84 static string UNKNOWN_SWITCH = "Unknown option switch: ";
85 static string MISSING_ARG = "Missing argument for switch: ";
86 
87 //*************************** Forward Declarations ***************************
88 
89 // lt_OptArgDesc: Used to sort CmdLineParser::OptArgDesc
91 {
92  // return true if x < y; false otherwise
94  const CmdLineParser::OptArgDesc& y) const
95  {
96  // There are three possibilities:
97  // - both long switches are present
98  // - both short switches are present
99  // - one short and one long switch
100  if (x.swLong && y.swLong) {
101  return (strcmp(x.swLong, y.swLong) < 0);
102  }
103  else if (x.swShort != 0 && y.swShort != 0) {
104  return (x.swShort < y.swShort);
105  }
106  else {
107  if (x.swLong && y.swShort != 0) {
108  char y_str[2] = { y.swShort , '\0' };
109  return (strcmp(x.swLong, y_str) < 0);
110  }
111  else {
112  char x_str[2] = { x.swShort , '\0' };
113  return (strcmp(x_str, y.swLong) < 0);
114  }
115  }
116  }
117 
118 private:
119 
120 };
121 
122 //****************************************************************************
123 
124 // isDashDash
125 static inline bool
126 isDashDash(const char* str)
127 {
128  return (strcmp(str, "--") == 0);
129 }
130 
131 
132 // IsSwitch, IsLongSwitch, IsShortSwitch: Assumes str is non-NULL! Note also
133 // that the test for short switch is not quite complete and depends on
134 // testing for a long switch first!
135 static inline bool
136 isLongSwitch(const char* str)
137 {
138  return (strncmp(str, "--", 2) == 0);
139 }
140 
141 static inline bool
142 isShortSwitch(const char* str)
143 {
144  return (*str == '-');
145 }
146 
147 static inline bool
148 isSwitch(const char* str)
149 {
150  return (isLongSwitch(str) || isShortSwitch(str));
151 }
152 
153 
154 // isArg: Verifies that we should interpret 'str' as an argument.
155 // Should be non-NULL;
156 static inline bool
157 isArg(const char* str)
158 {
159  return (!isSwitch(str) && !isDashDash(str));
160 }
161 
162 
163 //****************************************************************************
164 
165 //****************************************************************************
166 // CmdLineParser
167 //****************************************************************************
168 
171 
172 
174 {
175  Ctor();
176 }
177 
179  int argc, const char* const argv[])
180 {
181  Ctor();
182  parse(optArgDescs, argc, argv);
183 }
184 
185 void
187 {
188  // nothing to do
189 }
190 
191 
192 
194 {
195  reset();
196 }
197 
198 
199 void
200 CmdLineParser::parse(const OptArgDesc* optArgDescsOrig,
201  int argc, const char* const argv[])
202 {
203  reset();
204  command = argv[0]; // always do first so it will be available after errors
205 
206  checkForErrors(optArgDescsOrig);
207  const OptArgDesc* optArgDescs = createSortedCopy(optArgDescsOrig);
208 
209  bool endOfOpts = false; // are we at end of optional args?
210 
211  for (int i = 1; i < argc; ++i) {
212  const char* str = argv[i];
213 
214  // -------------------------------------------------------
215  // Bypass special option values
216  // -------------------------------------------------------
217  if (str == NULL || *str == '\0') {
218  continue; // should never happen, but we ignore
219  }
220 
221  // A '--' signifies end of optional arguments
222  if (isDashDash(str)) {
223  endOfOpts = true;
224  continue;
225  }
226 
227  if (!endOfOpts && isSwitch(str)) {
228  // -------------------------------------------------------
229  // An option switch (possibly needing an argument)
230  // -------------------------------------------------------
231  // Note: The argument may be appended to the switch or it may be
232  // the next element of argv.
233 
234  // 1. Separate switch from any argument embedded within
235  SwDesc swdesc = makeSwitchDesc(str);
236  if (swdesc.sw.empty()) {
237  throw ParseError(MISSING_SWITCH); // must have been '-'
238  }
239 
240  // 2. Find option descriptor from switch (checks for duplicate matches)
241  const OptArgDesc* d = findOptDesc(optArgDescs, swdesc);
242  if (!d) {
243  throw ParseError(UNKNOWN_SWITCH + swdesc.sw);
244  }
245 
246  // 3. Find argument for switch (if any) [N.B. may advance iteration!]
247  if (d->kind == ARG_NONE) {
248  if (!swdesc.arg.empty()) {
249  string msg = "Invalid argument `" + swdesc.arg + "' to switch `"
250  + swdesc.sw + "'";
251  throw ParseError(msg);
252  }
253  }
254  else if (d->kind == ARG_REQ || d->kind == ARG_OPT) {
255  if (swdesc.arg.empty()) {
256  int nexti = i + 1;
257  if (nexti < argc && argv[nexti]) {
258  const char* nxt = argv[nexti];
259  bool isarg_nxt =
260  (d->isOptArgFn) ? isArg(nxt) && d->isOptArgFn(nxt) : isArg(nxt);
261  if (isarg_nxt) {
262  swdesc.arg = nxt;
263  i = nexti; // increment iteration
264  }
265  }
266  }
267  if (swdesc.arg.empty() && d->kind == ARG_REQ) {
268  throw ParseError(MISSING_ARG + swdesc.sw);
269  }
270  }
271 
272  // 4. Add option switch and any argument to map
273  addOption(*d, swdesc);
274  }
275  else {
276  // -------------------------------------------------------
277  // A regular argument
278  // -------------------------------------------------------
279  arguments.push_back(string(str));
280  }
281  }
282 
283  delete[] optArgDescs;
284 }
285 
286 
287 //****************************************************************************
288 
289 const string&
291 {
292  return command;
293 }
294 
295 
296 // isOpt:
297 bool
298 CmdLineParser::isOpt(const char swShort) const
299 {
300  string sw(1, swShort);
301  return isOpt(sw);
302 }
303 
304 bool
305 CmdLineParser::isOpt(const char* swLong) const
306 {
307  string sw(swLong);
308  return isOpt(sw);
309 }
310 
311 bool
312 CmdLineParser::isOpt(const string& sw) const
313 {
314  SwitchToArgMap::const_iterator it = switchToArgMap.find(sw);
315  return (it != switchToArgMap.end());
316 }
317 
318 
319 // isOptArg:
320 bool
321 CmdLineParser::isOptArg(const char swShort) const
322 {
323  string sw(1, swShort);
324  return isOptArg(sw);
325 }
326 
327 bool
328 CmdLineParser::isOptArg(const char* swLong) const
329 {
330  string sw(swLong);
331  return isOptArg(sw);
332 }
333 
334 bool
335 CmdLineParser::isOptArg(const string& sw) const
336 {
337  SwitchToArgMap::const_iterator it = switchToArgMap.find(sw);
338  if ((it != switchToArgMap.end()) && ((*it).second != NULL)) {
339  return true;
340  }
341  return false;
342 }
343 
344 
345 // getOptArg:
346 const string&
347 CmdLineParser::getOptArg(const char swShort) const
348 {
349  string sw(1, swShort);
350  return getOptArg(sw);
351 }
352 
353 const string&
354 CmdLineParser::getOptArg(const char* swLong) const
355 {
356  string sw(swLong);
357  return getOptArg(sw);
358 }
359 
360 const string&
361 CmdLineParser::getOptArg(const string& sw) const
362 {
363  SwitchToArgMap::const_iterator it = switchToArgMap.find(sw);
364  if (it == switchToArgMap.end()) {
365  // FIXME: ERROR
366  }
367  string* arg = (*it).second;
368  if (!arg) {
369  // FIXME: ERROR
370  }
371  return *arg;
372 }
373 
374 
375 unsigned int
377 {
378  return arguments.size();
379 }
380 
381 const string&
382 CmdLineParser::getArg(unsigned int i) const
383 {
384  return arguments[i];
385 }
386 
387 
388 //****************************************************************************
389 
390 long
391 CmdLineParser::toLong(const string& str)
392 {
393  if (str.empty()) {
394  throw InternalError("ToLong");
395  }
396 
397  try {
398  return StrUtil::toLong(str);
399  }
400  catch (const Diagnostics::FatalException& x) {
401  throw ParseError(x.what());
402  }
403 }
404 
405 
406 uint64_t
407 CmdLineParser::toUInt64(const string& str)
408 {
409  if (str.empty()) {
410  throw InternalError("ToUInt64");
411  }
412 
413  try {
414  return StrUtil::toUInt64(str);
415  }
416  catch (const Diagnostics::FatalException& x) {
417  throw ParseError(x.what());
418  }
419 }
420 
421 
422 double
423 CmdLineParser::toDbl(const string& str)
424 {
425  if (str.empty()) {
426  throw InternalError("ToDbl");
427  }
428 
429  try {
430  return StrUtil::toDbl(str);
431  }
432  catch (const Diagnostics::FatalException& x) {
433  throw ParseError(x.what());
434  }
435 }
436 
437 
438 //****************************************************************************
439 
440 bool
441 CmdLineParser::isOptArg_long(const char* option)
442 {
443  try {
444  CmdLineParser::toLong(string(option));
445  }
446  catch (const CmdLineParser::Exception& /*ex*/) {
447  return false;
448  }
449  return true;
450 }
451 
452 //****************************************************************************
453 
454 bool
455 CmdLineParser::parseArg_bool(const string& value, const char* errTag)
456 {
457  const string& x = value;
458  if (x == "true" || x == "1" || x == "yes" || x == "on") {
459  return true;
460  }
461  else if (x == "false" || x == "0" || x == "no" || x == "off") {
462  return false;
463  }
464  else {
465  string msg = "Expected boolean value but received: '" + x + "'";
466  if (errTag) {
467  msg = string(errTag) + ": " + msg;
468  }
469  throw Exception(msg);
470  }
471 }
472 
473 
474 //****************************************************************************
475 
476 void
477 CmdLineParser::dump(std::ostream& os) const
478 {
479  os << "Command: `" << getCmd() << "'" << std::endl;
480 
481  os << "Switch to Argument map:" << std::endl;
482  for (SwitchToArgMap::const_iterator it = switchToArgMap.begin();
483  it != switchToArgMap.end(); ++it) {
484  const string& sw = (*it).first;
485  const string* arg = (*it).second;
486  os << " " << sw << " --> " << ((arg) ? *arg : "<>") << std::endl;
487  }
488 
489  os << "Regular arguments:" << std::endl;
490  for (unsigned int i = 0; i < arguments.size(); ++i) {
491  os << " " << arguments[i] << std::endl;
492  }
493 }
494 
495 
496 void
498 {
499  dump(std::cerr);
500 }
501 
502 
503 //****************************************************************************
504 
505 // reset: Clear data to prepare for parsing
506 void
508 {
509  for (SwitchToArgMap::iterator it = switchToArgMap.begin();
510  it != switchToArgMap.end(); ++it) {
511  string* arg = (*it).second;
512  delete arg;
513  }
514  switchToArgMap.clear();
515  arguments.clear();
516 }
517 
518 
519 // createSortedCopy: create a sorted NULL-terminated copy of
520 // 'optArgDescs'. WARNING: the OptArgDesc objects are bitwise-copied.
523 {
524  // Find the size, not including the NULL-terminator
525  unsigned int sz = 0;
526  for (const OptArgDesc* p = optArgDescs; *p != OptArgDesc_NULL; ++p) {
527  ++sz;
528  }
529 
530  // Make a copy of 'optArgDescs'
531  OptArgDesc* copy = new OptArgDesc[sz + 1];
532  unsigned int i = 0;
533  for (const OptArgDesc* p = optArgDescs; *p != OptArgDesc_NULL; ++p, ++i) {
534  copy[i] = *p; // bitwise copy is ok
535  }
536  copy[sz] = OptArgDesc_NULL; // add the NULL-terminator
537 
538  // Sort
539  if (sz > 1) {
540  std::sort(&copy[0], &copy[sz-1], lt_OptArgDesc());
541  }
542 
543  return copy;
544 }
545 
546 
547 // checkForErrors: Checks argument descriptor for errors
548 void
550 {
551  // FIXME
552  // - detect duplicate option entries. Not pressing because
553  // findOptDesc() will effectively do this.
554 
555  // Check individual descriptors
556  string msg;
557  string sw;
558  for (const OptArgDesc* p = optArgDescs; *p != OptArgDesc_NULL; ++p) {
559  // Verify that at least one switch is present
560  if (p->swShort == 0 && !p->swLong) {
561  throw InternalError("Arg descriptor is missing a switch!");
562  }
563 
564  if (p->swLong) {
565  sw = p->swLong;
566  }
567  else {
568  sw = p->swShort;
569  }
570 
571  // Verify that the kind is valid
572  if (p->kind == ARG_NULL) {
573  msg = "OptArgDesc.kind is invalid for: " + sw;
574  throw InternalError(msg);
575  }
576 
577  // Verify that dupKind is valid
578  if (p->dupKind == DUPOPT_NULL) {
579  msg = "OptArgDesc.dupKind is invalid for: " + sw;
580  throw InternalError(msg);
581  }
582 
583  // Verify that if dupKind == DUPOPT_CAT, dupArgSep is valid
584  if (p->dupKind == DUPOPT_CAT && !p->dupArgSep) {
585  msg = "OptArgDesc.dupArgSep is invalid for: " + sw;
586  throw InternalError(msg);
587  }
588  }
589 }
590 
591 
592 // makeSwitchDesc: Given an option string from argv (potentially
593 // containing both an option and an argument), create a SwDesc,
594 // separating switch text from any argument text.
597 {
598  // 1. Find pointers for begin/end of switch and argument
599  unsigned int len = strlen(str);
600  const char* strEnd = str + len;
601  const char* begSw = NULL, *endSw = NULL; // end pointers are inclusive!
602  const char* begArg = NULL, *endArg = NULL;
603  bool isLong = false;
604  if (isLongSwitch(str)) {
605  // test for --foo=arg
606  begArg = strchr(str, '=');
607  if (begArg) {
608  begArg++; // starts after the '='
609  endArg = strEnd - 1; // ends right before '\0'
610  }
611  begSw = str + 2; // bump past '--'
612  endSw = (begArg) ? (begArg - 2) : (strEnd - 1);
613  isLong = true;
614  }
615  else if (isShortSwitch(str)) {
616  // test for -f[arg]
617  begArg = (len > 2) ? (str + 2) : NULL; // starts after '-f'
618  endArg = (begArg) ? (strEnd - 1) : NULL; // ends right before '\0'
619  begSw = (len > 1) ? (str + 1) : NULL; // starts after '-'
620  endSw = begSw; // single character
621  }
622  else {
623  throw InternalError("Programming Error!");
624  }
625 
626  // 2. Copy switch and argument substrings
627  SwDesc swdesc;
628  swdesc.isLong = isLong;
629  for (const char* p = begSw; p && p <= endSw; ++p) {
630  swdesc.sw += *p;
631  }
632  for (const char* p = begArg; p && p <= endArg; ++p) {
633  swdesc.arg += *p;
634  }
635 
636  return swdesc;
637 }
638 
639 
640 // findOptDesc: Given a *sorted* NULL-terminated array of OptArgDesc and
641 // an option switch, return a reference to the appropriate OptArgDesc.
642 // If 'errOnMultipleMatches' is true, checks to make sure we don't
643 // match more than one descriptor (useful for testing long argument
644 // abbreviation).
646 CmdLineParser::findOptDesc(const OptArgDesc* optArgDescs, const SwDesc& swdesc,
647  bool errOnMultipleMatches)
648 {
649  // Note: Because there will never be very many options, we simply
650  // use a linear search.
651  // Note: A long option may be a substring of another long option!
652  // Because 'optArgDescs' will be sorted, any options that are
653  // substrings of other options will be ordered so that they appear
654  // before the option that contains them, e.g. 'xx', 'xxx', 'xxxx',
655  // 'xxxxx'.
656 
657  // Try to find a matching descriptor
658  unsigned int swLen = swdesc.sw.length();
659  const OptArgDesc* odesc = NULL;
660  for (const OptArgDesc* p = optArgDescs; *p != OptArgDesc_NULL; ++p) {
661  if (swdesc.isLong) {
662  if (p->swLong && strncmp(p->swLong, swdesc.sw.c_str(), swLen) == 0) {
663  odesc = p;
664  break;
665  }
666  }
667  else {
668  if (p->swShort != 0 && p->swShort == swdesc.sw[0]) {
669  odesc = p;
670  break;
671  }
672  }
673  }
674  if (!odesc) {
675  return NULL;
676  }
677 
678  // We have a match. Check for more matches ==> ambiguity.
679  const OptArgDesc* m = NULL;
680  if (errOnMultipleMatches
681  && (m = findOptDesc((odesc + 1), swdesc, false))) {
682  // Special case to handle a long option that is a substring of
683  // another. If the long option switch exactly matches 'odesc' and
684  // it is different than 'm' then we do not want to generate an
685  // ambiguous option error.
686  bool isOk = (swdesc.isLong
687  && (strcmp(odesc->swLong, swdesc.sw.c_str()) == 0)
688  && (strcmp(odesc->swLong, m->swLong) != 0));
689  if (!isOk) {
690  string msg = "Switch `";
691  msg += swdesc.sw; msg += "' matches two different options: ";
692  if (swdesc.isLong) {
693  msg += odesc->swLong; msg += ", "; msg += m->swLong;
694  }
695  else {
696  msg += odesc->swShort; msg += ", "; msg += m->swShort;
697  }
698  throw ParseError(msg);
699  }
700  }
701 
702  return odesc;
703 }
704 
705 
706 // addOption: Records the option switch and its (possibly optional)
707 // argument in the switch->argument map. In order to support easy
708 // lookup, both the *canonical* long and short form of the switches
709 // are entered in the map.
710 void
711 CmdLineParser::addOption(const OptArgDesc& odesc, const SwDesc& swdesc)
712 {
713  if (odesc.swShort != 0) {
714  string swShort(1, odesc.swShort);
715  addOption(odesc, swShort, swdesc.arg);
716  }
717  if (odesc.swLong) {
718  string swLong(odesc.swLong);
719  addOption(odesc, swLong, swdesc.arg);
720  }
721 }
722 
723 
724 // addOption: Records the option switch and its (possibly optional)
725 // argument in the switch->argument map. If the switch is not in the
726 // map, it is inserted with the available argument or NULL. If it is
727 // already the map, the option descriptor defines how to handle
728 // duplicates.
729 void
731  const string& sw, const string& arg)
732 {
733  SwitchToArgMap::iterator it = switchToArgMap.find(sw);
734  if (it == switchToArgMap.end()) {
735  // Insert in map
736  string* theArg = (arg.empty()) ? NULL : new string(arg);
737  switchToArgMap.insert(SwitchToArgMap::value_type(sw, theArg));
738  }
739  else {
740  // Handle duplicates
741  string* theArg = (*it).second;
742 
743  if (odesc.dupKind == DUPOPT_ERR) {
744  throw ParseError("Duplicate switch: " + sw);
745  }
746 
747  if (!arg.empty()) {
748  if (!theArg) {
749  theArg = new string(arg);
750  }
751  else {
752  if (odesc.dupKind == DUPOPT_CLOB) {
753  *theArg = arg;
754  }
755  else if (odesc.dupKind == DUPOPT_CAT) {
756  *theArg += odesc.dupArgSep + arg;
757  }
758  }
759  }
760  }
761 }
762 
763 //****************************************************************************
764 
bool isOpt(const char swShort) const
static OptArgDesc OptArgDesc_NULL
const OptArgDesc * createSortedCopy(const OptArgDesc *optArgDescs)
const std::string & getOptArg(const char swShort) const
double toDbl(const char *str, unsigned *endidx)
Definition: StrUtil.cpp:213
const std::string & getCmd() const
static uint64_t toUInt64(const std::string &str)
static string MISSING_ARG
uint64_t toUInt64(const char *str, unsigned *endidx)
Definition: StrUtil.cpp:189
virtual const std::string & what() const
Definition: Exception.hpp:126
static double toDbl(const std::string &str)
void copy(const char *dst,...)
Definition: FileUtil.cpp:233
bool isOptArg(const char swShort) const
void parse(const OptArgDesc *optArgDescs, int argc, const char *const argv[])
long toLong(const char *str, unsigned *endidx)
Definition: StrUtil.cpp:165
static bool isSwitch(const char *str)
unsigned int getNumArgs() const
bool operator()(const CmdLineParser::OptArgDesc &x, const CmdLineParser::OptArgDesc &y) const
static bool isShortSwitch(const char *str)
static bool isOptArg_long(const char *option)
void dump(std::ostream &os=std::cerr) const
void checkForErrors(const OptArgDesc *optArgDescs)
static bool isDashDash(const char *str)
static string UNKNOWN_SWITCH
static string MISSING_SWITCH
#define CmdLineParser_OptArgDesc_NULL_MACRO
const OptArgDesc * findOptDesc(const OptArgDesc *optArgDescs, const SwDesc &swdesc, bool errOnMultipleMatches=true)
SwDesc makeSwitchDesc(const char *str)
static bool isLongSwitch(const char *str)
const std::string & getArg(unsigned int i) const
#define NULL
Definition: ElfHelper.cpp:85
static bool parseArg_bool(const std::string &value, const char *errTag)
void ddump() const
static bool isArg(const char *str)
static long toLong(const std::string &str)
void addOption(const OptArgDesc &odesc, const SwDesc &swdesc)