// Read the documentation to learn more about C++ code generator // versioning. // %X% %Q% %Z% %W% #include #include #include #include #include #include #include #include "XSsymbol.h" #include #include // sstream #include // XSparse #include // Class XSparse::SkipThis XSparse::SkipThis::SkipThis (const std::string& diag) : YellowAlert() { *IosHolder::errHolder() << diag; } // Class XSparse::AbortLoop XSparse::AbortLoop::AbortLoop (const std::string& diag) : YellowAlert() { *IosHolder::errHolder() << diag; } // Class XSparse::SyntaxError XSparse::SyntaxError::SyntaxError (const std::string& diag) : YellowAlert(" - syntax error: ") { *IosHolder::errHolder() << diag << '\n'; } // Class XSparse::InputError XSparse::InputError::InputError (const std::string& diag) : YellowAlert(" input error: ") { *IosHolder::errHolder() << diag << '\n'; } // Class XSparse::InvalidRange XSparse::InvalidRange::InvalidRange() : YellowAlert() { } XSparse::InvalidRange::InvalidRange (const std::string& diag) : YellowAlert("Invalid Range: ") { *IosHolder::errHolder() << diag << '\n'; } // Class Utility XSparse const string XSparse::STRINGNULLS = " \t"; const string XSparse::INPUT_DELIMITER = "&"; const size_t XSparse::XCM_LEN = 256; const string XSparse::s_NONE = "none"; const string XSparse::s_SKIP = "/*"; bool XSparse::s_rangeIsReal = false; const string::size_type XSparse::s_NOTFOUND = string::npos; char* XSparse::s_augmentedLibraryPath; bool XSparse::s_executingScript = false; const string XSparse::s_USE_DEFAULT = "/"; void XSparse::findBatchString (const string& cmdLine, string& command, std::deque& batch) { command.clear(); string::size_type isBatch = cmdLine.find_first_of(INPUT_DELIMITER); if (isBatch != string::npos) { // Don't assume cmdLine can't begin with whitespace. User can // group args arbitrarily in quotes and assign to single variable. string::size_type startPos = cmdLine.find_first_not_of(STRINGNULLS); if (startPos != isBatch) command = cmdLine.substr(startPos,isBatch-startPos); string rest(cmdLine.substr(isBatch+1)); string::size_type start (rest.find_first_not_of(STRINGNULLS)); string::size_type len = rest.length(); while (start != string::npos) { string::size_type finish (rest.find_first_of(INPUT_DELIMITER,start)); if (finish != string::npos) { // check for case of '&' followed by '&' with nothing // except perhaps whitespace in between. This indicates a // blank entry, to be replaced with '/' (skip this). if (finish == start) { batch.push_back("/"); } else { batch.push_back(rest.substr(start, finish-start)); } start = (finish+1 == len) ? string::npos : rest.find_first_not_of(STRINGNULLS, finish+1); } else { batch.push_back(rest.substr(start, len-start)); start = string::npos; } } } else { command = cmdLine; } return; } IntegerArray XSparse::getRanges (const string& rangeString, int lastIndex) { // returns ranges [of parameter, data set numbers from string of the form "*" or "n1-m1,n2-m2,n3" if (rangeString.empty()) return IntegerArray(); // first case: rangeString has size 1 and it's a '*'. IntegerArray v; if (rangeString.size() == 1 && rangeString[0] == '*') { // '-2' interprets to 'the full range' ... now in both directions, // lower and upper. v.resize(2,-2); return v; } try { // int comma = rangeString.find_first_of(','); // if (comma > 0 ) { int rBegin = 0; int rEnd = 0; int rLength = rangeString.length(); if (lastIndex != -1) v.reserve(lastIndex); do { rEnd = rangeString.find_first_of(',',rBegin); if ((rEnd < 0) || (rEnd > rLength)) rEnd = rLength; string subRange = rangeString.substr(rBegin, rEnd - rBegin); int b = 0, e = 0; if (subRange.length() > 0) { oneRange(subRange,b,e); if(lastIndex > 0 && e < 0) e = lastIndex; if(b < 0 || e < 0) { v.push_back(b); v.push_back(e); } else for (int i = 0; i <= e-b; ++i ) v.push_back(b + i); rBegin = rEnd + 1; } else break; } while(rBegin < rLength); #ifndef STD_COUNT_DEFECT int wilds(std::count(v.begin(),v.end(),-1)); #else int wilds(0); std::count(v.begin(),v.end(),-1,wilds); #endif if (wilds > 1) { int first = NOTFOUND(); for (size_t j = 0; j < v.size() ; ++j) if ( v[j] != -1 ) first = std::min(first,v[j]); v.resize(2); v[0] = first; v[1] = -1; } return v; } } catch (InvalidRange) { // thrown by oneRange. v.erase(v.begin(),v.end()); throw; } return v; } void XSparse::oneRange (const string& inputRange, int& beginRange, int& endRange) { // Valid input: string of form "m-n"or "m" where m,n are positive integers // (but not 0) or * or ** wildcards. Function will THROW if input // is not valid, leaving beginRange and endRange undefined. If * or ** // are present, beginRange and endRange will be set to -1 or -2 as needed. // At completion, endRange will be >= beginRange when they are both // positive (they're swapped if not). const char delim('-'); size_t N (inputRange.size()); if ( N == 0 ) throw InvalidRange(" attempt to parse empty string "); string::size_type nDelimLoc = inputRange.find_first_of(delim); // generic range error error message. std::string errMsg = std::string("range ") + inputRange + " contains invalid characters "; // no valid delimiter. There is an error if the range is not a pure integer. // if no error, the "range" contains one element. if(nDelimLoc == string::npos) { if (inputRange == "*" || inputRange == "**") { beginRange = endRange = -2; } else { beginRange = validateRangeSpecifier(inputRange); endRange = beginRange; } } else { // Catch cases where delim has been placed at the beginning // or end of string, or appears more than once. if (nDelimLoc == 0 || nDelimLoc == N) { throw InvalidRange(errMsg); } if (inputRange.find_first_of(delim,nDelimLoc+1) != string::npos) { throw InvalidRange(errMsg); } // now, read the beginRange argument and the delimiter. string BR(inputRange.substr(0,nDelimLoc)); if (BR == "*") { beginRange = -1; } else if (BR == "**") { beginRange = -2; } else { beginRange = validateRangeSpecifier(BR); } string ER(inputRange.substr(nDelimLoc+1)); if (ER == "*") { endRange = -1; } else if (ER == "**") { endRange = -2; } else { endRange = validateRangeSpecifier(ER); } if (endRange > 0 && beginRange > 0 && (endRange < beginRange) ) { int swap = endRange; endRange = beginRange; beginRange = swap; } } } string XSparse::returnDelimitedArgument (string& inString, const string& xsdelim) { string arg(""); // a non-destructive version of strtok. Returns a pointer to an empty // string if the delimiter is not found, and returns an empty string if // if the input string is of zero size. Note that the C version returned // a NULL in the latter case. if (inString.size() == 0) return arg; int n = inString.find_first_not_of(STRINGNULLS); if (n < 0) return arg; inString = inString.substr(n); int lenArg = inString.find_first_of(xsdelim); if ( lenArg >= 0) { if (lenArg > 0) arg = inString.substr(0,lenArg); inString = inString.substr(lenArg+1); } else { arg = inString; inString = ""; } return arg; } string XSparse::getNextLine (std::ifstream& stream) { // Function for getting next line from file. // Reads line and strips leading and trailing blanks and tabs. // Also ignores commented shell script lines denoted by '#' marks. //Edit 7/29/04 - Function now does NOT ignore whitespace and //comments at the end of a line, only those lines that begin //with a comment - JAM string line; const string WS(" \t\r\n"); bool isFound = false; while (stream && !isFound) { string tt; // ignore commented lines by reading this loop until we get a line that is // not commented. The command processor will deal with // blank lines. getline(stream,tt); isFound = true; size_t len = tt.length(); if (len) { string::size_type nonWS = tt.find_first_not_of(WS); if (nonWS != string::npos && tt[nonWS] == '#') { // First char is '#' so skip line. isFound = false; } else { // If script was edited in DOS mode, newlines are "\r\n", yet // getline call only removes the "\n" on Linux/Unix/Mac. We // don't want this trailing "\r" getting through and wreaking // havoc in execscript. if (tt[len-1] == '\r') tt.erase(len-1,1); line = tt; } } } return line; } void XSparse::parseInputModelString (const string& inputString, string& stringPar, size_t& intPar, string& commandString) { // intStringPair returns a source number and a model name from an // argument of the form n:word. If there is no colon, n = XSparse::NOTFOUND() // and the arg. is to be interpreted as either a model name or // a model expression. string tmpString(inputString); string first = returnDelimitedArgument(tmpString," \t"); XSparse::intStringPair(first,intPar, stringPar); if (intPar == s_NOTFOUND) { intPar = 1; stringPar = ""; commandString = inputString; } else { if (tmpString.find_first_not_of(STRINGNULLS) == s_NOTFOUND) { throw SyntaxError (" a model expression is required here "); } commandString = tmpString; } } bool XSparse::integerPair (const string& arg, int& first, int& second) { // integerPair returns true for : : : string sarg(arg.substr(arg.find_first_not_of(STRINGNULLS))); using namespace std; bool isInteger = false; string::size_type colon = string::npos; if ( (colon = sarg.find_first_of(':')) == string::npos) { second = -1; size_t j = 0; size_t larg = sarg.length(); // see if this is an integer argument. while (j != larg && std::isdigit(sarg[j],locale()) ) { ++j; } if ( j != sarg.length() ) { first = 1; } else { // now we know it's a number, so can leave out the type checking. istringstream argStream(sarg); argStream >> first; isInteger = true; } } else { // string is of form ":" which must be an integer if (sarg[0] == ':') { istringstream argStream(sarg.substr(1)); if (!(argStream >> second) || !argStream.eof()) { // return with isInteger false, and first,second set to -1 first = -1; second = -1; } else { first = 1; isInteger = true; } } else { // we have a string with a colon inside it, try to read 2 integers. istringstream firstArgStream(sarg.substr(0,colon)); // get first integer from string. if (!(firstArgStream >> first) || !firstArgStream.eof()) { // first argument is garbage, string is not an integer pair. first = -1; second = -1; } else { if (colon == sarg.length()-1) { // Allow case of : second = 1; isInteger = true; } else { istringstream secondArgStream(sarg.substr(colon+1)); if (!(secondArgStream >> second) || !secondArgStream.eof()) { first = second = -1; } else { isInteger = true; } } } } } return isInteger; } void XSparse::spaceString (char** inputString) { // this utility was needed by XSPEC11: it makes sure tokens // on the parse line are separated if need be. char* end = NULL; char* begintmp = NULL; char* tmpString = NULL; begintmp = (char*)calloc(257,sizeof(inputString)); end = *inputString + strlen(*inputString); tmpString = begintmp; while (*inputString < end) { if (strchr("+-",**inputString) != NULL) { if ( strchr("edED",*(*inputString-1)) == NULL) { if ( *(*inputString-1) != ' ') { *tmpString = ' '; tmpString++; } } } *tmpString = **inputString; tmpString++; (*inputString)++; } *tmpString = '\0'; // this looks like a MAJOR problem! strcpy(*inputString,begintmp); free(begintmp); return; } int XSparse::checkExponent (char* inputString) { // check for exponents. An XSPEC11 relic. char* strPtr = inputString; int status = 0; while (strPtr < inputString + strlen(inputString)-1) { if ( strchr("edED",*strPtr) != NULL) { if ( ( (strchr("+-",*(strPtr+1)) != NULL) && !isdigit(*(strPtr+2)) ) || (strchr("+-",*(strPtr+1)) == NULL) && !isdigit(*(strPtr+1)) ) { status = -1; break; } } strPtr++; } return status; } bool XSparse::hasRange (const string& strg, size_t& beginRng, size_t& endRng) { bool stringHasRange = false; int findOpen = strg.find_first_of('{'); if (findOpen < 0) { beginRng = 0; endRng = strg.length(); } else { stringHasRange = true; beginRng = findOpen; int findClose = strg.find_first_of('}',findOpen); findClose > 0 ? endRng = findClose : endRng = strg.length(); } return stringHasRange; } void XSparse::getFileNameFromUser (const string& oldName, string& newName, XSutility::fileType type) { // in this version we are going to replace the oldName with a user added // newName. We check that the file exists, but not whether it is valid. // this code will need to be modified when scripting implementation is // considered. string reportType(""); static const string SPEC = " spectrum "; static const string BACK = " background "; static const string CORR = " correction data "; static const string RESP = " response "; static const string AUXR = " auxiliary response "; switch (type) { case XSutility::PHA: reportType = SPEC; break; case XSutility::BCK: reportType = BACK; break; case XSutility::COR: reportType = CORR; break; case XSutility::RSP: reportType = RESP; break; case XSutility::ARF: reportType = AUXR; break; default: reportType = " "; break; } *IosHolder::errHolder() << "Error: cannot read" << reportType << "file " << oldName << "\n"; struct stat* statbuf = new struct stat; static const string filePrompt("New filename ( \"none\" or \"/*\" to return to the XSPEC prompt): "); newName = ""; if ( !s_executingScript ) { while ( 1 ) { string rawFromUser(""); XSstream* xscin = dynamic_cast(IosHolder::inHolder()); if (xscin) { XSstream::setPrompter(*IosHolder::inHolder(),filePrompt); } getline(*IosHolder::inHolder(), rawFromUser); // Allow user to break with ctrl-C (but // only if calling code has registered // an appropriate event handler). const SIGINT_Handler *intHandler = dynamic_cast (SignalHandler::instance()->getHandler(SIGINT)); if (intHandler) { if (intHandler->interrupted()) { throw AbortLoop(); } } const string WS(" \t"); string::size_type firstNonWS = rawFromUser.find_first_not_of(WS); string::size_type lastNonWS = rawFromUser.find_last_not_of(WS); string noWS; if (firstNonWS != string::npos) { noWS = rawFromUser.substr(firstNonWS, lastNonWS-firstNonWS+1); } string lc(XSutility::lowerCase(noWS)); if (lc.substr(0,4) == NONE() || (lc.length() == 1 && lc[0] == '/') ) { throw SkipThis(); } else if (lc.substr(0,2) == "/*") { throw AbortLoop(); } else { newName = addSuffix(noWS,type); if (stat(newName.c_str(),statbuf) < 0) { *IosHolder::outHolder() << "No such file: " << newName << "\n"; } else break; } } } else { // not implemented yet. } } bool XSparse::stringIntPair (const string& arg, string& word, size_t& number) { // This is meant to handle input of type arg = "nameString:n" // or simply "n". If it is the latter, word will be returned as // an empty string. NOTE: in that case, this function actually // returns "false" even though just "n" may be a valid input. // It is up to the calling functions to figure this out. // If n is not an int type, the "number" argument will be // returned unchanged. if (arg.size() == 0) return false; size_t colon = arg.find_first_of(':'); size_t first (0); bool isStringInt = true; word = ""; if ( colon != s_NOTFOUND ) { word = arg.substr(0,colon); first = colon + 1; } else { isStringInt = false; } size_t posVe = XSutility::isInteger(arg.substr(first)); if (posVe != s_NOTFOUND) { number = posVe; } else { isStringInt = false; } return isStringInt; } string XSparse::processStringToken (const string& inputString, string& token, IntegerArray& spectrumRanges, XSutility::fileType defaultSuffix) { int openBrac = inputString.find_first_of('{',0); string outputString(""); if (!inputString.length()) { token.clear(); spectrumRanges = IntegerArray(1,0); return outputString; } if ( openBrac < 0 ) { outputString = inputString; // output string truncated. token = returnDelimitedArgument(outputString," ,"); spectrumRanges = IntegerArray(1,0); } else { token = inputString.substr(0,openBrac); int closeBrac = inputString.find_first_of('}'); if (closeBrac < 0) throw SyntaxError(" range string has missing '}'"); if (closeBrac < openBrac) throw SyntaxError(" improper bracket ordering"); spectrumRanges = getRanges(inputString.substr(openBrac+1,closeBrac - openBrac - 1),-1); if (inputString.size() > static_cast(closeBrac + 1)) { // why 2? because the following character must be a comma if the // string is of the required form and it has to be parsed off. outputString = inputString.substr(closeBrac+2); } // else the output string is blank. } if (!(XSutility::lowerCase(token) == XSparse::NONE() && spectrumRanges[0] == 0)) { token = XSutility::addSuffix(token, defaultSuffix); } return outputString; } void XSparse::stringRangePair (const string& arg, string& word, IntegerArray& range) { using namespace std; size_t n(arg.size()); if (n == 0) return; // create a stream from input s, cutting off a leading '$' if present. // model names may not start with '$' // istringstream s(arg.substr(arg.find_first_not_of('$'))); istringstream s(arg); // a colon is present, so there's a 'word' to be read. if (arg.find_first_of(':') != s_NOTFOUND) { try { s.exceptions(ios_base::badbit | ios_base::failbit | ios_base::eofbit); string input(""); getline(s,input,':'); if (input.length()) word = input; // some compilers eat the delimiter supplied to getline, some don't. if (s.peek() == ':') s.ignore(); } catch (ios_base::failure&) { if (s.eof()) { string msg("No range in parameter specification: "); msg += arg; throw XSparse::InvalidRange(msg); } else { throw; } } } else word = ""; // throws InvalidRange exception, caught by the driver. try { string rest(""); // reset the exception flags, and then turn on 'badbit'. A 'failbit' // here means that the string rest is empty, which condition is caught // by getRanges and returns an empty range as would be expected. s.exceptions(ios_base::goodbit); s.exceptions(ios_base::badbit); s >> rest; IntegerArray tmp = getRanges(rest,-1); if (!tmp.empty()) range = tmp; } catch (ios_base::failure& exc) { throw XSparse::InvalidRange("I/O error parsing string/range specification"); } } void XSparse::catchSkips (const string& input, bool silent) { static const string SKIP = "/"; string msg(""); if (input == SKIP) { if (!silent) msg = "... skipped"; throw SkipThis(msg); } else if (input == s_SKIP) { if (!silent) msg = "... terminated"; throw AbortLoop(msg); } } void XSparse::intStringPair (const string& arg, size_t& number, string& word) { if (arg.size() == 0) return; size_t colon = arg.find_first_of(':'); number = s_NOTFOUND; word = ""; if ( colon == s_NOTFOUND ) { word = arg; } else { word = arg.substr(colon+1); size_t posVe = XSutility::isInteger(arg.substr(0,colon) ); if (posVe == s_NOTFOUND) throw SyntaxError( " format of integer:string required here "); number = static_cast(posVe); } } bool XSparse::addToLibraryPath (const string& newDirectory, string& fullDirectory, int accessMode) { // add the requested local model directory // to the O/S load path. static const string LDPATH("LD_LIBRARY_PATH"); fullDirectory = expandDirectoryPath(newDirectory); if ( fullDirectory.length() == 0) return false; if ( !access(fullDirectory.c_str(),accessMode)) { string libraryPath(""); char* ldpath = getenv(LDPATH.c_str()); if (ldpath) libraryPath = string(ldpath); size_t pathSize = LDPATH.length() + libraryPath.length() + fullDirectory.length() + 3; s_augmentedLibraryPath = (char *)realloc(s_augmentedLibraryPath,pathSize); memset(s_augmentedLibraryPath,'\0',pathSize*sizeof(char)); strcpy(s_augmentedLibraryPath,LDPATH.c_str()); strcat(s_augmentedLibraryPath,"="); strcat(s_augmentedLibraryPath,fullDirectory.c_str()); strcat(s_augmentedLibraryPath,":"); if (ldpath) { strcat(s_augmentedLibraryPath,libraryPath.c_str()); } if (!putenv(s_augmentedLibraryPath)) return true; } return false; } string XSparse::expandDirectoryPath (const string& input) { // Return the absolute path specified by input, including expansion of // home dir "~" if necessary. if (!input.length()) return string(""); string stringDir; char* enValue(0); size_t slash(input.find_first_of('/')); string root(input.substr(0,slash)); switch ( input[0] ) { case '~': if (input.length() > 1 && input[1] != '/') { // Assume this is specifying another user's home // directory, ie. ~[/.../file] // If in here, root.length() >= 2. string otherUser(root.substr(1)); // passwd struct is defined in pwd.h struct passwd *pwdInfo = getpwnam(otherUser.c_str()); if (!pwdInfo) { *IosHolder::errHolder() << "*** Unable to find home directory " << root << std::endl; return string(""); } stringDir = string(pwdInfo->pw_dir); if (slash != string::npos) { stringDir += input.substr(slash); } } else { // Have something of the form ~[/.../file] // Expand to user's own home directory. enValue = getenv("HOME"); if (!enValue || enValue[0] != '/') { *IosHolder::errHolder() <<"*** Unable to expand path for " << input <<"\n Make sure HOME environment variable is properly set." << std::endl; return string(""); } stringDir = string(enValue); if (slash != string::npos) { stringDir += input.substr(slash); } } break; case '.': if (input.length() > 1 && input[1] != '.' && slash != 1) { *IosHolder::errHolder() << "*** invalid directory path: must begin with absolute path" << "\n*** valid environment variable or shell character " << " {~,./,../}\n"; return string(""); } else { const size_t PATHSZ = 1024; if ( (enValue = getcwd(NULL,PATHSZ)) == NULL) { *IosHolder::errHolder() << "*** XSUtil error while converting to absolute path name." << "\n*** Current dir path name may possibly exceed max size = " << PATHSZ << std::endl; return string(""); } if (input.length() == 1) { // case of just "." stringDir = string(enValue); } else { switch (input[1]) { case '/': // input is "./[...]" stringDir = string(enValue); if (slash != XSparse::NOTFOUND()) { stringDir += input.substr(slash); } break; case '.': { // input is "..[...]" stringDir = string(enValue); // stringDir is an abs path so // there has to be a slash. size_t lastSlash = stringDir.find_last_of('/'); stringDir = stringDir.substr(0,lastSlash); // this looks right... if (slash != XSparse::NOTFOUND()) { stringDir += input.substr(slash); } } break; default: break; } } // end input.length() > 1 free(enValue); } // end case '.' break; case '$': enValue = getenv(root.substr(1).c_str()); if ( enValue ) { stringDir = string(enValue); if (slash != XSparse::NOTFOUND()) { stringDir += input.substr(slash); } } else { *IosHolder::errHolder() << " Invalid environment variable " << root.substr(1) << '\n'; return string(""); } break; case '/': default: stringDir = input; break; } return stringDir; } void XSparse::collectParams (std::vector& args, IntegerArray& iParams, StringArray& xsParams) { bool prevIsChar = false; int idx = 0; // These vectors should be empty on input, but just to be sure... iParams.clear(); xsParams.clear(); const size_t N(args.size()); for (size_t i = 0; i < N; ++i) { size_t startLength = args[i].length(); // check for ',' while (startLength) { string beforeComma = XSparse::returnDelimitedArgument(args[i], string(",")); if (beforeComma.length() != startLength) { // 1 or more commas still exist after beforeComma, // though args will be empty for this case: // "(beforeComma)," if (!beforeComma.length()) { // Comma is the leading character if (!prevIsChar) { ++idx; } prevIsChar = false; startLength = args[i].length(); continue; } else { // Account for the trailing comma after beforeComa, // and proceed. prevIsChar = false; xsParams.push_back(beforeComma); iParams.push_back(idx); } } else { // beforeComma = remainder of string. It has // length > 0 and there are no commas left. prevIsChar = true; xsParams.push_back(beforeComma); iParams.push_back(idx); } ++idx; startLength = args[i].length(); } } } void XSparse::basicPrompt (const string& promptMsg, string& result, std::deque* batchArgs) { string raw(""); if (!batchArgs || batchArgs->empty()) { XSstream* xscin = dynamic_cast(IosHolder::inHolder()); if (xscin) { XSstream::setPrompter(*IosHolder::inHolder(),promptMsg); } getline(*IosHolder::inHolder(), raw); string::size_type i = raw.find_first_not_of(" \t\r"); if (i != string::npos) { raw.erase(0, i); } else { result.clear(); return; } } else { // leading whitespace should already be removed raw = batchArgs->front(); batchArgs->pop_front(); } // remove any remaining trailing whitespace from either script or // command line input. string::size_type last = raw.find_last_not_of(" \t\r"); if (last != string::npos && last < raw.length()-1) { raw.erase(last+1); } if (raw.length() >=2 && raw.substr(0,2) == s_SKIP) { throw AbortLoop(); } result = raw; } void XSparse::promptResponseArf (string& responseName, const string& defaultName, string& arfName, size_t& arfRow, const size_t specNum, bool demand, bool doArf, std::deque* batchArgs) { // Originally designed for use with xsFakeit, this function prompts for // a name for the location of a response file to go with spectrum // numbered specNum. If demand is 'true' the user will have to // enter a name for a response file. If demand is 'false' and the // user enters a blank, then the response name will be the name // of the default singleton dummy response. // If the user enters anything other than a blank (or abort "/*") // and doArf flag is set to true, it then prompts for an // ancillary name with optional row specifier. // No checking is performed as to whether these files // actually exist. responseName = ""; while (!responseName.length()) { std::ostringstream msg; msg << "For fake spectrum #" << specNum << " response file is needed: "; XSparse::basicPrompt(msg.str(), responseName, batchArgs); if (!responseName.length()||(responseName == s_USE_DEFAULT)) { if (demand) responseName.clear(); else responseName = defaultName; } } if (doArf && (responseName != defaultName)) { bool isOK = false; while (!isOK) { string arfReply; XSparse::basicPrompt(" ...and ancillary file: ", arfReply, batchArgs); if (arfReply.length() && arfReply != s_USE_DEFAULT) { try { IntegerArray tmp(1,0); XSparse::processStringToken(arfReply, arfName, tmp,XSutility::ARF); arfRow = tmp[0]; isOK = true; } catch (YellowAlert&) { isOK = false; } } else { isOK = true; } } } } void XSparse::changeExtension (string& fileName, const string& extName, const bool duplicate) { // This function will remove the extension name of fileName and replace // it with extName. If more than 1 extension exists, only the last is // replaced. If there originally is no extension to fileName, extName // is simply appended. If the last extension of fileName is equal to // extName, then depending on the duplicate flag, either the extension // is repeated or nothing is done. if (!fileName.length()) return; string::size_type i = fileName.rfind("."); if (i == string::npos) { fileName += extName; } else { string origExt = fileName.substr(i); if (origExt == extName) { if (duplicate) { fileName += extName; } // Else, extName is already there, do not duplicate, do nothing. } else { fileName.replace(i, string::npos, extName); } } } string XSparse::promptFilename (const string& defaultName, std::deque* batchArgs) { string outFile(""); string prompt(""); const string WS(" \t\r"); prompt = " Fake data file name (" + defaultName + "): "; basicPrompt(prompt, outFile, batchArgs); if (!outFile.length()) { outFile = defaultName; } else if (outFile.substr(0,1) == s_USE_DEFAULT) { if (outFile.length() == 1 || WS.find(outFile[1]) != string::npos) outFile = defaultName; } // Do same behavior as v11: Don't test for pre-existing file if // running from batch mode, and only test once if running from // command line. if (!batchArgs || batchArgs->empty()) { std::ifstream testFile(outFile.c_str()); if (testFile) { string ans; prompt = "File " + outFile + " exists - overwrite? (yY/) or (nN): "; basicPrompt(prompt, ans, 0); if (ans.length() && ans[0] != 'y' && ans[0] != 'Y' && ans.substr(0,1) != s_USE_DEFAULT) { prompt = "Enter new name (" + defaultName + "): "; basicPrompt(prompt, outFile, 0); if (!outFile.length()) { outFile = defaultName; } else if (outFile.substr(0,1) == s_USE_DEFAULT) { if (outFile.length() == 1 || WS.find(outFile[1]) != string::npos) outFile = defaultName; } } } } return outFile; } void XSparse::collateByWhitespace (StringArray& outStrings, const string& inString) { // This is intended to do the type of parsing that Tcl does with command // line arguments: It should take a line of user input, inString, and // break it up into pieces wherever it finds a whitespace. The pieces // are placed in outStrings, and will contain no whitespaces. This can // be quite usefull when used in tandem with collectParams. string::size_type i=0, j=0; string ws(" \t\r\n"); outStrings.clear(); i = inString.find_first_not_of(ws); while (i != string::npos) { j = inString.find_first_of(ws, i); string::size_type len = (j == string::npos) ? j : j-i; outStrings.push_back(inString.substr(i, len)); if (j == string::npos) { break; } i = inString.find_first_not_of(ws, j); } } void XSparse::checkBrackets (StringArray& args) { // The purpose of this function is merely to focus on the bracket // issue of the command string. It will check that brackets are // used in pairs properly, with NO nested brackets, and it will // remove unnecessary spaces from inside the brackets, // ie. {5 ,7} becomes {5,7}. // However,if there are any spaces, there had also better be a comma // separator or else this will throw an error (ie. {5 7} throws). // NOTE: This function will NOT check the validity of the input // between the brackets. If the user enters {Y#$*&#),38qw9}, this // function will pass it. It is left up to a later function to // verify that the input makes sense. It also will NOT check that // the bracket pair's location in the larger command string makes // any sense. // First pass through arg strings. Check that nowhere do we have // these situations: {..{..., }..}.., leading off with a '}', // or ending with a '{'. int bracketCount = 0; const size_t nArgs = args.size(); for (size_t i=1; i newArgs; for (size_t i=0; i::iterator prevStr = newArgs.begin(); std::list::iterator iStr = newArgs.begin(); while (iStr != newArgs.end()) { string& newString = (*iStr); if (inside) { if (!prevComma && newString[0] != ',' && newString[0] != '}' && (*prevStr)[(*prevStr).length()-1] != '{') { throw SyntaxError("Comma missing inside brackets."); } string::size_type rpos = newString.find_first_of('}'); if (rpos != string::npos) { *prevStr += newString.substr(0, rpos+1); newString.erase(0, rpos+1); inside = false; if (!newString.length()) { newArgs.erase(iStr++); } } else { prevComma = (newString[newString.length()-1] == ','); *prevStr += newString; newArgs.erase(iStr++); } } else { string::size_type lpos = newString.find_first_of('{'); if (lpos != string::npos) { // Note: Avoiding the use of the std::count function // here due to compiler non-compliance issues. string::const_iterator it = newString.begin()+lpos; size_t lcount=0, rcount=0; while (it != newString.end()) { if (*it == '{') ++lcount; else if (*it == '}') ++rcount; ++it; } inside = (lcount > rcount); if (inside) { prevComma = (newString[newString.length()-1] == ','); } } prevStr = iStr; ++iStr; } } // Now copy the (possibly) modified strings in the newArgs list // back into the resized input args vector. args.clear(); iStr = newArgs.begin(); while (iStr != newArgs.end()) { args.push_back(*iStr); ++iStr; } } string XSparse::IntToString (size_t num) { std::ostringstream os; os << num; return os.str(); } bool XSparse::promptReal (const string& prompt, Real& real, std::deque* batchArgs) { string input(""); std::ostringstream fullPrompt; fullPrompt << prompt << std::showpoint << " (" << real << "): " << std::noshowpoint; basicPrompt(fullPrompt.str(), input, batchArgs); // "/" = accept default, which means leave "real" param unchanged. if (input.length() && input.substr(0,1) != s_USE_DEFAULT) { std::istringstream iss(input); Real test; if (!(iss >> test) || !iss.eof()) { return false; } real = test; } return true; } void XSparse::separate (string& operand, const string& separators) { string dummy(""); size_t N(operand.size()); for ( size_t j = 0; j < N; ++j) { if (separators.find(operand[j]) ) { if (j < N - 1 && STRINGNULLS.find(operand[j+1]) != s_NOTFOUND) { dummy += operand[j]; dummy += ' '; } else { dummy += operand[j]; } } else { dummy += operand[j]; } } operand = dummy; } IntegerArray XSparse::expandRange (const IntegerArray& range) { //this function assumes that no wild cards (-2's in most cases) are //sent to this function. //Array size better be a multiple of 2. Otherwise, you are using //the function wrong :) if(!(range.size() % 2)) { IntegerArray expandedRange; IntegerArray::iterator i_endRange; int arrSize = range.size(); for(int i = 0; i < arrSize; i += 2) { int nRangeStart = range[i], nRangeEnd = range[i + 1]; for(int j = nRangeStart; j <= nRangeEnd; ++j) { i_endRange = expandedRange.end(); if(std::find(expandedRange.begin(), i_endRange, j) == i_endRange) expandedRange.push_back(j); } } return expandedRange; } return range; } string XSparse::trimWhiteSpace (const string& value) { string::size_type beg, end; if(value.size() && (beg = value.find_first_not_of(" \t")) != string::npos) return value.substr(beg, value.find_last_not_of(" \t") - beg + 1); else return string(""); } RangePair XSparse::wildRange (const string& inputRange, const RangePair& maxRange, RangePair& prevRange) { // Input: inputRange is a single range string of form "m-n" or "m" // where m,n are positive integers or * or **. maxRange specifies // the allowed low-high range. maxRange.first must be <= maxRange.second. // If oneRange returns successfully, prevRange will be updated with // the newest values. //OneRange performs // syntax evaluation and returns the integer low and high ranges, // with -1 or -2 in place of wildcards. This function replaces // -1, -2 with their actual values, determined by prevRange and // maxRange respectively. It also insures that outRange.first // <= outRange.second (this check is performed in oneRange when // not dealing with wildcards). if (maxRange.first > maxRange.second) { throw RedAlert("Programming error: XSparse::wildRange function, improper limits ordering."); } if (prevRange.first < maxRange.first) prevRange.first = maxRange.first; if (prevRange.second > maxRange.second) prevRange.second = maxRange.second; RangePair outRange(0,0); int tmpBeg=0, tmpEnd=0; // this may throw oneRange(inputRange, tmpBeg, tmpEnd); if (tmpBeg < 0) { outRange.first = (tmpBeg == -2) ? maxRange.first : prevRange.first; } else { outRange.first = static_cast(tmpBeg); } prevRange.first = outRange.first; if (tmpEnd < 0) { outRange.second = (tmpEnd == -2) ? maxRange.second : prevRange.second; } else { outRange.second = static_cast(tmpEnd); } prevRange.second = outRange.second; if (outRange.first > outRange.second) { size_t tmp = outRange.first; outRange.first = outRange.second; outRange.second = tmp; } return outRange; } size_t XSparse::validateRangeSpecifier (const string& rangeString) { std::string errMsg = std::string("range ") + rangeString + " contains invalid characters "; // Negative ints will fail the isInteger test. size_t rangeVal (XSutility::isInteger(rangeString)); if(rangeVal == string::npos) { throw InvalidRange(errMsg); } else if (!rangeVal) { throw InvalidRange("0 is not a valid range specifier."); } return rangeVal; } IntegerArray XSparse::getRanges (StringArray& inArgs, RangePair& prevRanges, const RangePair& rangeLimits) { // This will return the UNION of all numbers covered by all the ranges // in inArgs, with no duplicate values. // Each individual string in inArgs should contain NO whitespace. This is // not a problem if inArgs was built from Tcl's objv array. Commas ARE // allowed at any position in strings. typedef std::map OrderedPairs; IntegerArray dummy; StringArray filteredArgs; // CollectParams will clear out any commas and will collate all into // separate strings in filteredArgs (ie. "l,m,n" becomes "l","m","n". // In this context, we are not interested in the parameter numbers // returned, hence the name "dummy". collectParams(inArgs, dummy, filteredArgs); size_t nRanges = filteredArgs.size(); OrderedPairs orderedRanges; for (size_t i=0; i rangeLimits.second) { std::ostringstream msg; size_t val = (currRange.first < rangeLimits.first) ? currRange.first : currRange.second; msg << "Range value " << val << " is outside the allowed range limits (" << rangeLimits.first << "-" << rangeLimits.second << ")"; throw InvalidRange(msg.str()); } // Insert each currRange into a map sorted by the low value. Expanding // these pairs by their order in the map will then produce a sorted // selectedNums array. Also need to check for foolish overlaps such // as 1-5, 3 or 1-5, 2-6. We don't want duplicates in selectedNums array. // Note: I'm going through the trouble of sorting by range pairs, rather // than simply each number bracketed by the pair, because while the // number of range pairs will presumably always be small, a range itself // 1-n in theory could be quite enormous. size_t key = currRange.first; OrderedPairs::iterator itPairs = orderedRanges.find(key); if (itPairs == orderedRanges.end() || currRange.second > itPairs->second) { orderedRanges[key] = currRange.second; } } IntegerArray selectedNums; OrderedPairs::const_iterator itPairs = orderedRanges.begin(); OrderedPairs::const_iterator itEnd = orderedRanges.end(); OrderedPairs::const_iterator itLast = itEnd; if (orderedRanges.size()) { --itLast; // This isn't a sure thing, but its a good guess at the total size // needed for the selectedNums array. size_t guesstimate = itLast->second - itPairs->first + 1; if (guesstimate > 10) selectedNums.reserve(guesstimate); } size_t highest = 0; while (itPairs != itEnd) { size_t low = itPairs->first; size_t high = itPairs->second; size_t span = high - low + 1; if (low > highest) { for (size_t j=0; j(low+j)); } highest = high; } else if (high > highest) { span = high - highest; // Note: No +1 here. for (size_t j=1; j<=span; ++j) { selectedNums.push_back(static_cast(highest+j)); } highest = high; } // else range low-high has already been added to selectedNum, // so do nothing. ++itPairs; } return selectedNums; } bool XSparse::promptRealPair (const string& prompt, std::pair& vals, std::deque* batchArgs) { // For prompting input of the form "r1 r2", "r1,r2", " ,r2" etc. // where r1 and r2 are Reals. Any args corresponding to higher than // 2nd parameter will be ignored (ie. " ,, r3" etc.). Returns // false if any invalid input detected for first 2 parameters. string input(""); std::ostringstream fullPrompt; fullPrompt << prompt << std::showpoint << " (" << vals.first << ", " << vals.second << "): " << std::noshowpoint; basicPrompt(fullPrompt.str(), input, batchArgs); if (input.length() && input.substr(0,1) != s_USE_DEFAULT) { StringArray rawArgs, outputArgs; IntegerArray iParams; collateByWhitespace(rawArgs, input); collectParams(rawArgs, iParams, outputArgs); size_t TWO = 2; size_t nEntered = std::min(iParams.size(), TWO); for (size_t i=0; i= TWO) break; Real test; std::istringstream iss(outputArgs[i]); if (!(iss >> test) || !iss.eof()) { return false; } if (iPar == 0) { vals.first = test; } else if (iPar == 1) { vals.second = test; } } } return true; } string XSparse::stringSegment (const string& fullString, const size_t length, string::size_type* pStart) { string segment; string::size_type& startPos = *pStart; const size_t totalLength = fullString.size(); // Line breaks are treated differently than regular whitespace. const string WS(" \t"); if (!length) return segment; if (startPos >= totalLength) { // Out of range, just return an empty string. startPos = string::npos; return segment; } string::size_type breakPos = fullString.find("\n",startPos); if (breakPos - startPos <= length) { // A line break occurs within the requested length (or // immediately after). Return a substring up to // but not including the break, and reset startPos to // 1 beyond the break. segment = fullString.substr(startPos, breakPos-startPos); startPos = breakPos + 1; if (startPos >= totalLength) startPos = string::npos; } else if (startPos + length >= totalLength) { // Simply return the remainder of fullString segment = fullString.substr(startPos); startPos = string::npos; } else if (WS.find(fullString[startPos+length-1]) != string::npos) { // Another easy case: the requested segment ends on whitespace segment = fullString.substr(startPos, length); startPos += length; } else { // The trickiest case: the segment ends on non-ws. // If the following char is ws, return full segment. // Else, return up through the last ws BEFORE n=length chars. // If no ws, just return the full segment. if (WS.find(fullString[startPos+length]) != string::npos) { segment = fullString.substr(startPos, length); startPos += length; } else { string testStr(fullString.substr(startPos, length)); string::size_type lastWSPos = testStr.find_last_of(WS); if (lastWSPos == string::npos) { segment = testStr; startPos += length; } else { segment = testStr.substr(0,lastWSPos+1); startPos += lastWSPos+1; } } } return segment; } // Additional Declarations