From 74ddd0381aa1b1a90eb0d5300fa576cb2348eeac Mon Sep 17 00:00:00 2001 From: John Denker Date: Sun, 17 Oct 2021 10:10:18 -0700 Subject: basically functional, but still a work in progress --- arg_parser.c | 288 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 288 insertions(+) create mode 100644 arg_parser.c (limited to 'arg_parser.c') diff --git a/arg_parser.c b/arg_parser.c new file mode 100644 index 0000000..d4ff196 --- /dev/null +++ b/arg_parser.c @@ -0,0 +1,288 @@ +using namespace std; +#include "arg_parser.h" +#include +#include + +using namespace std; + +// Convert a string to this-or-that representation. +// Specialization is not the same as instantiation! +// Atox and Atox need to come early, +// because they get used by later things. + +template struct cvt2 { + static double doit(string const sss){ + stringstream parse; + parse.str(sss); + T rslt; + parse >> rslt; + if (parse.fail()) throw invalid_argument("'" + sss + "' isn't a proper " + myTraits::name); + if (parse.eof()) return rslt; + do { + char ch; + parse >> ch; + if (parse.fail()) break; + if (ch == 'k') rslt *= 1024; + else if (ch == 'M') rslt *= 1048576; + else break; + parse >> ws; // without this, we wouldn't know whether we were at EoF + if (!parse.eof()) break; + return rslt; // whew! good. + } while (0); + throw invalid_argument("'" + sss + "' has extraneous verbiage"); + return 0; /* should never happen */ + } +}; + + +template struct helper{ + static T convert(string const &arg); +}; + +template<> struct helper { + static string convert(string const &arg){ + return arg; + } +}; + +template<> struct helper { + static int convert(string const &arg){ + return cvt2::doit(arg.c_str()); + } +}; + +template<> struct helper { + static double convert(string const &arg){ + if (tolower(arg) == "inf") return Inf; + if (tolower(arg) == "-inf") return -Inf; + return cvt2::doit(arg.c_str()); + } +}; + +// convert to double, then downgrade to float: +template<> struct helper { + static float convert(string const &arg){ + return helper::convert(arg); + } +}; + +template struct helper > { + static vector convert(string const &_arg){ + string arg(_arg); + vector rslt; + while (arg.length()) { + string::size_type where = arg.find(','); + string word; + if (where == arg.npos) { + word = arg; + arg = ""; + } else { + word = arg.substr(0, where); + arg = arg.substr(1+where); + } + rslt.push_back(helper::convert(word)); + } + return rslt; + } +}; + +template +string num_from_list(const string arg, B& rslt){ + if (arg.length()) { + string::size_type where = arg.find_first_of(", \t"); + string word; + if (where == arg.npos) { + rslt = helper::convert(arg); + return ""; + } else { + rslt = helper::convert(arg.substr(0, where)); + return arg.substr(1+where); + } + } +// should never get here + return arg; +} + +template struct helper > { + static complex convert(string const &_arg){ + string arg(_arg); + B rp(0), ip(0); + arg = num_from_list(arg, rp); + arg = num_from_list(arg, ip); + return complex(rp, ip); + } +}; + +// This is the interface that ordinary users use. +// The point of all this is that you can perform +// "partial specification" (what I might call re-specialization) +// on classes, but not on functions. +template +T Atox(std::string const &str) { + // Just delegate. + return helper::convert(str); +} + +////////////////////////////////// +// Various getters. + +//+++ fileGetter .... +//ordinary constructor: + fileGetter:: fileGetter(const std::string fname){ + file.open(fname.c_str()); + } + + std::string fileGetter::get(){ + std::string str; + for (;;) { + file >> str; // try to read one word + if (file.fail()) return ""; // failed + if (str[0] != '#') return str; // got a comment; + string junk; + getline(file, junk); //throw it away + // go around the loop, read next line + } + } + +// beware that neither bad() nor fail() is the opposite of good() + int fileGetter::fail() { + return file.fail(); + } + +//+++ cmdGetter .... +//ordinary constructor: + cmdGetter::cmdGetter(int const _argc, char const * const * _argv) + : argc(_argc), argv(_argv), wasgood(argc) {} + + std::string cmdGetter::get(){ + if (argc <= 0) { + wasgood = 0; + return ""; + } + wasgood = argc--; + return *argv++; + } + + int cmdGetter::fail() { + return !wasgood; + } + +// That's all the getters for now. +////////////////////// + +string tolower(const string& arg){ + string dupe(arg); + for (string::iterator foo = dupe.begin(); + foo != dupe.end(); foo++){ + *foo = tolower(*foo); + } + return dupe; +} + +string arg_parser::xcase(const string& arg){ + if (!fold_case) return arg; + return tolower(arg); +} + +// Constructor, for reading words from command line: + arg_parser::arg_parser(int const _argc, char const * const * _argv) + : fold_case(0) + { + std::shared_ptr tmp (new cmdGetter(_argc, _argv)); + getsome.push_front(tmp); + } + +// Constructor, for reading words from file: + arg_parser::arg_parser(const std::string fname) + : fold_case(0), keyword("(?" "?)"), narg(0) + { + push(fname); + } + + void arg_parser::push(const std::string fname){ + std::shared_ptr tmp (new fileGetter(fname)); + getsome.push_front(tmp); + } + + int arg_parser::fail(){ + if (!getsome.size()) return 1; // is this really correct? + return getsome.front()->fail(); + } + +// see if cur arg is a prefix of patt */ +int arg_parser::prefix(const string& kwpatt){ + if ((cur.length()==0 && kwpatt.length()!=0)) return 0; // disallow "", unless exact match + if (xcase(cur) != xcase(kwpatt).substr(0, cur.length())) return 0; + keyword = kwpatt; + narg = 0; + return 1; +} + + template T arg_parser::nextRaw(){ + cur = ""; + for (;;) { + if (!getsome.size()) { + break; + } + cur += getsome.front()->get(); + if (fail()) { + // here if read failed; pop the context + getsome.pop_front(); + // if read failed late; return what we have + if (cur.length()) return Atox(cur); + // if read failed early, try again in parent context + continue; + } + size_t len = cur.length(); + if (len == 0) break; // zero length, can't have a comma + if (cur[len-1] != ',') break; + // else has a comma, keep reading + } + return Atox(cur); + } + +// return next item, assuming it is an argument: +template T arg_parser::nextArg(){ + if (0) cerr << "next called with narg: " << narg + <<" kw: " << keyword + << endl; + T rslt = nextRaw(); + narg++; + if (fail()) { + ostringstream buf; + buf << "Keyword " << keyword + << " requires argument #" << narg + << " of type " << myTraits::name; + throw invalid_argument(buf.str()); + } + return rslt; +} + +template +arg_parser& arg_parser::operator>>(T& val){ + val = nextArg(); + return *this; +} + +// from .h file +// template T Atox(std::string const &foo); + +// specializations and explicit instantiations: +// The first two lines of this macro help us figure +// out the /name/ of a type. +#define setup(T) \ + template <> const char* myTraits::name = #T; /* specialization */ \ + template struct myTraits; /* instantiation */ \ + template arg_parser& arg_parser::operator>>(T& val); \ + template T Atox(std::string const &arg); \ + template T arg_parser::nextRaw(); + +// Last but not least, instantiate the things we are going to need. +// I don't know why the compiler can't do this automatically. +setup(int); +setup(double); +setup(float); +setup(string); +setup(vector); +setup(vector); +setup(complex); -- cgit v1.2.3