diff options
author | John Denker <jsd@av8n.com> | 2021-10-17 10:10:18 -0700 |
---|---|---|
committer | John Denker <jsd@av8n.com> | 2021-10-17 11:09:24 -0700 |
commit | 74ddd0381aa1b1a90eb0d5300fa576cb2348eeac (patch) | |
tree | 72a9dded6f800467d52e479eb37574e6de5f2e6c /arg_parser.c | |
parent | 634d365a03cb0581a062cd3cf4db9ae69f1cde26 (diff) |
basically functional, but still a work in progress
Diffstat (limited to 'arg_parser.c')
-rw-r--r-- | arg_parser.c | 288 |
1 files changed, 288 insertions, 0 deletions
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 <complex> +#include <stdexcept> + +using namespace std; + +// Convert a string to this-or-that representation. +// Specialization is not the same as instantiation! +// Atox<int> and Atox<double> need to come early, +// because they get used by later things. + +template<typename T> 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<T>::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<typename T> struct helper{ + static T convert(string const &arg); +}; + +template<> struct helper<string> { + static string convert(string const &arg){ + return arg; + } +}; + +template<> struct helper<int> { + static int convert(string const &arg){ + return cvt2<int>::doit(arg.c_str()); + } +}; + +template<> struct helper<double> { + static double convert(string const &arg){ + if (tolower(arg) == "inf") return Inf; + if (tolower(arg) == "-inf") return -Inf; + return cvt2<double>::doit(arg.c_str()); + } +}; + +// convert to double, then downgrade to float: +template<> struct helper<float> { + static float convert(string const &arg){ + return helper<double>::convert(arg); + } +}; + +template<typename B> struct helper<vector<B> > { + static vector<B> convert(string const &_arg){ + string arg(_arg); + vector<B> 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<B>::convert(word)); + } + return rslt; + } +}; + +template<typename B> +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<B>::convert(arg); + return ""; + } else { + rslt = helper<B>::convert(arg.substr(0, where)); + return arg.substr(1+where); + } + } +// should never get here + return arg; +} + +template<typename B> struct helper<complex<B> > { + static complex<B> 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<B>(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 <typename T> +T Atox(std::string const &str) { + // Just delegate. + return helper<T>::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<baseGetter> 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<baseGetter> 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<class T=std::string> 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<T>(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<T>(cur); + } + +// return next item, assuming it is an argument: +template<class T=std::string> T arg_parser::nextArg(){ + if (0) cerr << "next called with narg: " << narg + <<" kw: " << keyword + << endl; + T rslt = nextRaw<T>(); + narg++; + if (fail()) { + ostringstream buf; + buf << "Keyword " << keyword + << " requires argument #" << narg + << " of type " << myTraits<T>::name; + throw invalid_argument(buf.str()); + } + return rslt; +} + +template <typename T> +arg_parser& arg_parser::operator>>(T& val){ + val = nextArg<T>(); + return *this; +} + +// from .h file +// template<typename T> 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<T >::name = #T; /* specialization */ \ + template struct myTraits<T >; /* instantiation */ \ + template arg_parser& arg_parser::operator>>(T& val); \ + template T Atox<T>(std::string const &arg); \ + template T arg_parser::nextRaw<T>(); + +// 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<int>); +setup(vector<double>); +setup(complex<double>); |