aboutsummaryrefslogtreecommitdiff
path: root/arg_parser.c
diff options
context:
space:
mode:
authorJohn Denker <jsd@av8n.com>2021-10-17 10:10:18 -0700
committerJohn Denker <jsd@av8n.com>2021-10-17 11:09:24 -0700
commit74ddd0381aa1b1a90eb0d5300fa576cb2348eeac (patch)
tree72a9dded6f800467d52e479eb37574e6de5f2e6c /arg_parser.c
parent634d365a03cb0581a062cd3cf4db9ae69f1cde26 (diff)
basically functional, but still a work in progress
Diffstat (limited to 'arg_parser.c')
-rw-r--r--arg_parser.c288
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>);