#ifndef MUTILS_UTIL_H #define MUTILS_UTIL_H /** \file \brief The MathUtils miscellaneous utilities library. Includes the following common headers: - `` - `` - `` - `` - `` - `` - `` (via `Exception.h`) Aliases the following common values into the mutils namespace: - std::string::npos - std::string nullStr \who \kpr */ #include "mutils/platform.h" #ifdef __cplusplus # include # include # include # include # include # include #include #else # include # include # include # include #endif #ifdef mu_os_mswin # if !defined(strcasecmp) # define strcasecmp stricmp # define strncasecmp strnicmp # endif #else # include # include #endif #define MU_VERMAX 32 /* Max length of version string. */ #define MU_DATEMAX 24 /* Max length of cppdate string. */ #ifdef __cplusplus extern "C" { #endif DLLSPEC extern void mu_cppdate(char str[ /*MU_DATEMAX*/ ], const char *date, const char *time), mu_error_action(int, const char *, const char *, int, int), mu_split_tag(char pre[], int *maj, int *min, int *clas, int *bug, int *y, int *m, int *d, const char *tag); DLLSPEC extern char *mu_utoa9(char *str, unsigned i, int strip), *mu_fgetline(char *, long int, FILE *); DLLSPEC extern const char *mu_platform(void), *mu_version(void), *mu_verstr(char verstr[ /*MU_VERMAX*/ ], const char *pre, int maj, int min, int clas, int bug); DLLSPEC extern int mu_ppmtext(char *, char *, int, int, int), mu_vernum(const char *name, const char *cvs); #define MU_iferr_abort(err, func) do { \ if (err) mu_error_action(-1, func, __FILE__, __LINE__, -1); } while (0) #define MU_iferr_warn(err, func) do { \ if (err) mu_error_action(-1, func, __FILE__, __LINE__, 0); } while (0) #define MU_iferr(err, func) do { \ if (err) mu_error_action(-1, func, __FILE__, __LINE__, 1); } while (0) #define MU_iferrn_abort(err, func) do { \ int errn=(err); \ if (errn) mu_error_action(errn, func, __FILE__, __LINE__, -1); } while (0) #define MU_iferrn_warn(err, func) do { \ int errn=(err); \ if (errn) mu_error_action(errn, func, __FILE__, __LINE__, 0); } while (0) #define MU_iferrn(err, func) do { \ int errn=(err); \ if (errn) mu_error_action(errn, func, __FILE__, __LINE__, 1); } while (0) #define mu_alloc(s) mu_alloc_r(NULL, s) #define mu_realloc(p, s) mu_realloc_r(NULL, p, s) #define mu_free(p) mu_free_r(NULL, p) #define mu_freeall() mu_freeall_r(NULL) DLLSPEC extern int mu_free_r(void **, void *); DLLSPEC extern void mu_freeall_r(void *), *mu_alloc_r(void **, size_t), *mu_realloc_r(void **, void *, size_t); DLLSPEC extern int mu_clock_Hz(void); DLLSPEC extern void mu_wait_time(double); DLLSPEC extern double mu_clock_time(void), mu_real_clock_time(void), mu_cpu_time(void), mu_system_time(void), mu_user_time(void), mu_process_cpu_time(void), mu_thread_cpu_time(void); DLLSPEC extern char *mu_optarg; DLLSPEC extern int mu_opterr, mu_optind, mu_optopt, mu_getopt(int argc, char * const argv[], const char *opts); DLLSPEC extern double mu_time_unary(double (*fn)(double), double x1, double x2, double tmin); DLLSPEC extern double mu_time_binary(double (*fn)(double, double), double x1, double x2, double y1, double y2, double tmin); DLLSPEC extern double mu_time_trinary(double (*fn)(double, double, double), double x1, double x2, double y1, double y2, double z1, double z2, double tmin); /* ============ C++ specific section below here ============ */ #ifdef __cplusplus } // extern "C" namespace mutils { /// Convenience alias. extern std::string::size_type npos; /// C++ null string (cannot arise by assignment from a C string). extern const std::string nullStr; /// Valid SI metric prefixes. extern const std::string siPrefixes; /// SI prefix scale factors. extern const std::map siScaleMap; /** \brief SI metric prefix to scale factor conversion. \param[in] siPrefix One of the #siPrefixes metrix prefix characters. \return Metric scale factor as a floating-point string, or "NaN" if \a siPrefix is not an SI prefix. */ inline const char *siScale(char siPrefix) { auto p = siScaleMap.find(siPrefix); return (p != siScaleMap.end() ? p->second : "NaN"); } /** \brief Whether unit has an SI prefix. The input \a unit is expected to be one of the provided \a baseUnits, possibly prepended with one of the standard metric prefix characters (mutils::siPrefixes). \param[in] unit Base unit name (possibly with SI prefix). \param[in] baseUnits Valid base units. \return Whether \a unit is a valid SI-prefixed base unit. */ inline bool siHasPrefix(const std::string &unit, const std::vector &baseUnits) { for (auto &base: baseUnits) { if (base == unit) { return false; } } if (siPrefixes.find_first_of(unit.front()) != npos) { for (auto &base: baseUnits) { if (base == &unit[1]) { return true; } } } return false; } /// Reads a logical line from a file. extern int readLogicalLine(std::string &line, FILE *file, unsigned *nLines = nullptr, const char *eol = "\r\n"); /** \brief Converts iterator to value reference (const). \tparam C Iterable container type. \tparam I Container iterator type. \param[in] p Iterator into container. \return Reference to the container value pointed to by \a p. */ template const typename C::value_type &getValue(I p) { return *p; } /** \brief Converts iterator to key value reference (const). \tparam C Iterable container type. \tparam I Container iterator type. \param[in] p Iterator into container. \return Reference to the container key value pointed to by \a p. */ template const typename C::key_type &getKey(I p) { return p->first; } /** \brief Converts iterator to mapped value reference (const). \tparam C Iterable container type. \tparam I Container iterator type. \param[in] p Iterator into container. \return Reference to the container mapped value pointed to by \a p. */ template const typename C::mapped_type &getMapped(I p) { return p->second; } /** \brief String comparison with minimum match and case selection. Minimum match can be controlled using the \a minChar setting, which indicates the minimum length string \a s that shall be considered to match a \a pool string (unless the latter is itself shorter than \a minChar). For example, if \a minChar = 4 then "foob" will match "foobar", but "foo" will not; if \a minChar = 0, even "" will match "foobar". Minimum match operates symmetrically; i.e., strMatch(s1, s2) == strMatch(s2, s1). \param[in] s1 First string to compare. \param[in] s2 Second string to compare. \param[in] minChar Minimum characters required for a valid (inexact) match, from zero (unrestricted) to npos (full match guarantee). \param[in] caseSense Whether comparisons are case-sensitive. \return Zero if \a s1 == \a s2; -1 if \a s1 < \a s2; or +1 if \a s1 > \a s2 with the selected criteria. */ inline int strMatch(const std::string &s1, const std::string &s2, std::string::size_type minChar = npos, bool caseSense = false) { auto ncmp = (caseSense ? strncmp : strncasecmp); auto n = std::min(s1.size(), s2.size()); // Minimum match. if (n < minChar) { n = std::max(s1.size(), s2.size()); } // Complete match. return ncmp(s1.c_str(), s2.c_str(), n); } /** \brief Finds all matching strings with minimum match and case selection. \tparam C Iterable container type. \tparam I Container iterator type. \tparam F Function object converting container pointer to string reference. \param[in] s String to match. \param[in] pool Pool of strings to search for matches. \param[in] minChar Minimum characters required for a valid (inexact) match, from zero (unrestricted) to npos (full match guarantee). \param[in] caseSense Whether comparisons are case-sensitive. \return Vector of iterators to the \a pool entries with matching strings. */ template> std::vector findMatch(const std::string &s, C &pool, std::string::size_type minChar = npos, bool caseSense = false) { std::vector rtn; for (I p = pool.begin(); p != pool.end(); ++p) { if (strMatch(s, F(p), minChar, caseSense) == 0) { rtn.push_back(p); } } return rtn; } /** \brief Finds unique match to existing element. \tparam C Iterable container type. \tparam I Container iterator type. \tparam F Function object converting container pointer to string reference. \param[in] str Search string. \param[in] pool Search container. \param[in] minChar Minimum characters required for a valid (inexact) match, from zero (unrestricted) to npos (full match guarantee). \param[in] caseSense Whether comparisons are case-sensitive. \return Iterator to the existing compatible container element, else pool.end() if no match exists. \exception mutils::Exception Multiple valid matches found. \note The container type must be map-like with a std::string key_type. */ template> I findUnique(const std::string &str, C &pool, std::string::size_type minChar = npos, bool caseSense = false) { const char *fn = "util::findUnique"; auto matches = findMatch(str, pool, minChar, caseSense); std::string matchNames; for (auto p: matches) { matchNames += " "; matchNames += F(p); } MU_EXCEPTION_IF(matches.size() > 1, fn, "Multiple matches found for '%s':\n%s", str, matchNames); return (matches.empty() ? pool.end() : matches[0]); } /** \brief Calls a member or static function pointer. Provides a uniform interface for calling either a member function or static function with the same signature (in the latter case, with an \e explicit reference to \b T as its first argument). \tparam R Function return type. \tparam S Arbitrary member (of T) or static function pointer. \tparam T Arbitrary class type. \tparam Args Additional argument types. \param s Function pointer (member of \b T or static). \param t Instance of \b T. \param args Additional trailing function arguments. \return A value of type \b R. */ template inline typename std::enable_if::value, R>::type fn_call(S s, T &t, Args&&... args) { return (t.*s)(args...); } /// \cond template inline typename std::enable_if::value, R>::type fn_call(S s, T &t, Args&&... args) { return s(t, args...); } /// \endcond /** \brief Qualifies a pathname. Transforms a relative file name by prepending it with a qualified path name, while leaving absolute file names alone. The special file names "-" and "!", assumed to refer to `stdout` and `stderr`, are also passed as-is. \note File output is disabled (set to #nullStr) if \a path = #nullStr. \param[in] path Qualifying path name. \param[in] file Unqualified file name. \return Qualified path name, or #nullStr if \a path = #nullStr. */ inline std::string qualifyPath(const std::string &path, const std::string &file) { std::string rtn; if (path != nullStr && file != nullStr) { if (!path.empty() && !file.empty() && file[0] != '/' && file != "-" && file != "!") { rtn = path; if (path.back() != '/') { rtn += '/'; } } rtn += file; } else { rtn = nullStr; } return rtn; } /** \brief Extracts next string token. Tokens are separated by whitespace. \param[in] line String buffer. \param[in,out] index Beginning search index (updated, optional). \return First whitespace-delimited substring (empty on end-of-line). */ inline std::string getToken(const std::string &line, size_t *index = nullptr) { const std::string white{" \t\r\n"}; size_t i0 = line.find_first_not_of(white, (index ? *index : 0UL)), i1 = line.find_first_of(white, i0); if (index) { *index = i1; } return (i0 != npos ? line.substr(i0, i1-i0) : ""); } } // namespace mutils #endif /* __cplusplus */ #endif /* MUTILS_UTIL_H */