From 8d066ade11302c9f25f29f35b57ce6f9ad9d4bd4 Mon Sep 17 00:00:00 2001 From: Marko Kreen Date: Thu, 26 Jun 2014 01:59:12 +0300 Subject: [PATCH] string: locale-independent float handling --- m4/usual.m4 | 3 +- test/compile.c | 1 + test/test_common.c | 4 ++ test/test_fnmatch.c | 2 + test/test_string.c | 16 +++++++ usual/cfparser.c | 5 +-- usual/string.c | 105 ++++++++++++++++++++++++++++++++++++++++++++ usual/string.h | 7 +++ 8 files changed, 139 insertions(+), 4 deletions(-) diff --git a/m4/usual.m4 b/m4/usual.m4 index cf7f46d..dd73f8a 100644 --- a/m4/usual.m4 +++ b/m4/usual.m4 @@ -183,6 +183,7 @@ AC_CHECK_HEADERS([sys/param.h sys/uio.h pwd.h grp.h]) AC_CHECK_HEADERS([sys/wait.h sys/mman.h syslog.h netdb.h dlfcn.h]) AC_CHECK_HEADERS([err.h pthread.h endian.h sys/endian.h byteswap.h]) AC_CHECK_HEADERS([malloc.h regex.h getopt.h fnmatch.h]) +AC_CHECK_HEADERS([langinfo.h]) dnl ucred.h may have prereqs AC_CHECK_HEADERS([ucred.h sys/ucred.h], [], [], [ #ifdef HAVE_SYS_TYPES_H @@ -207,7 +208,7 @@ AC_CHECK_FUNCS(err errx warn warnx getprogname setprogname) AC_CHECK_FUNCS(posix_memalign memalign valloc) AC_CHECK_FUNCS(getopt getopt_long getopt_long_only) AC_CHECK_FUNCS(fls flsl flsll ffs ffsl ffsll) -AC_CHECK_FUNCS(fnmatch mbsnrtowcs) +AC_CHECK_FUNCS(fnmatch mbsnrtowcs nl_langinfo strtod_l) ### Functions provided only on win32 AC_CHECK_FUNCS(localtime_r gettimeofday recvmsg sendmsg usleep getrusage) ### Functions used by libusual itself diff --git a/test/compile.c b/test/compile.c index 4e6b24e..032b322 100644 --- a/test/compile.c +++ b/test/compile.c @@ -42,6 +42,7 @@ int main(void) static_assert(sizeof(int) >= 4, "unsupported int size"); heap = heap_create(heap_is_better, NULL, NULL); + heap_top(heap); aatree_init(&aatree, NULL, NULL); cbtree = cbtree_create(NULL, NULL, NULL, NULL); cbtree_destroy(cbtree); diff --git a/test/test_common.c b/test/test_common.c index dca1520..2cf8697 100644 --- a/test/test_common.c +++ b/test/test_common.c @@ -2,6 +2,8 @@ #include "test_common.h" +#include + struct testgroup_t groups[] = { { "base/", base_tests }, { "aatree/", aatree_tests }, @@ -35,6 +37,8 @@ struct testgroup_t groups[] = { int main(int argc, const char *argv[]) { + if (getenv("USE_LOCALE")) + setlocale(LC_ALL, ""); return tinytest_main(argc, argv, groups); } diff --git a/test/test_fnmatch.c b/test/test_fnmatch.c index e41677b..45f11f8 100644 --- a/test/test_fnmatch.c +++ b/test/test_fnmatch.c @@ -115,7 +115,9 @@ static void test_fnmatch_posix(void *p) /* escapes in brackets ~ posix */ int_check(0, fnmatch("[A\\]]", "\\]", FNM_NOESCAPE)); +#ifndef HAVE_FNMATCH int_check(0, fnmatch("[a\\-x]", "_", FNM_NOESCAPE)); +#endif end:; } diff --git a/test/test_string.c b/test/test_string.c index cf4e3c0..f6b4c05 100644 --- a/test/test_string.c +++ b/test/test_string.c @@ -337,6 +337,21 @@ static void test_memspn(void *z) end:; } +static void test_s2d_dot(void *p) +{ + char buf[128]; + double val; + char *end; + + memset(buf, 0, sizeof(buf)); + dtostr_dot(buf, sizeof(buf), 1.5); + str_check(buf, "1.5"); + val = strtod_dot(buf, &end); + tt_assert(val == 1.5); + tt_assert(*end == 0); +end:; +} + /* * Describe */ @@ -354,6 +369,7 @@ struct testcase_t string_tests[] = { { "dirname", test_dirname }, { "strlist", test_strlist }, { "parse_wordlist", test_wlist }, + { "str2double_dot", test_s2d_dot }, END_OF_TESTCASES }; diff --git a/usual/cfparser.c b/usual/cfparser.c index 7e5e4c7..f98b23b 100644 --- a/usual/cfparser.c +++ b/usual/cfparser.c @@ -18,8 +18,6 @@ #include -#include - #ifdef HAVE_PWD_H #include #endif @@ -28,6 +26,7 @@ #include #include #include +#include #define MAX_INCLUDE 10 @@ -512,7 +511,7 @@ static double parse_time(const char *value) char *endp = NULL; errno = 0; - v = strtod(value, &endp); + v = strtod_dot(value, &endp); if (errno) return -1; if (*endp || endp == value || v < 0) { diff --git a/usual/string.c b/usual/string.c index e4f7f34..0e663d1 100644 --- a/usual/string.c +++ b/usual/string.c @@ -24,6 +24,10 @@ #include #include +#include +#ifdef HAVE_LANGINFO_H +#include +#endif /* * Dynamic list of strings. @@ -403,3 +407,104 @@ size_t memcspn(const void *data, size_t dlen, const void *reject, size_t rlen) return dlen; } +double strtod_dot(const char *s, char **tokend) +{ + const char *dp; + char buf[128]; + char *dst, *tmp, *end, *dot = NULL; + unsigned int i, dplen; + double res; + + /* check if locale is sane */ +#ifdef HAVE_NL_LANGINFO + dp = nl_langinfo(RADIXCHAR); +#else + dp = localeconv()->decimal_point; +#endif + if (memcmp(dp, ".", 2) == 0) + return strtod(s, tokend); + + /* try to use proper api */ +#ifdef HAVE_STRTOD_L + { + static locale_t c_locale = NULL; + if (!c_locale) + c_locale = newlocale(LC_ALL_MASK, "C", NULL); + if (c_locale) + return strtod_l(s, tokend, c_locale); + } +#endif + + while (*s && isspace(*s)) + s++; + + dot = NULL; + dst = buf; + end = buf + sizeof(buf) - 5; + dplen = dp[1] ? strlen(dp) : 1; + for (i = 0; s[i]; i++) { + if (s[i] >= '0' && s[i] <= '9') { + *dst++ = s[i]; + } else if (s[i] == '.') { + dot = dst; + memcpy(dst, dp, dplen); + dst += dplen; + } else if (s[i] == '-' || s[i] == '+' || s[i] == 'e' || s[i] == 'E') { + *dst++ = s[i]; + } else { + break; + } + + if (dst >= end) { + errno = ERANGE; + return 0; + } + } + *dst = '\0'; + + if (!dot) + return strtod(s, tokend); + + tmp = NULL; + res = strtod(buf, &tmp); + if (tmp && tokend) { + *tokend = (char *)s + (tmp - buf); + if (dot && tmp > dot && dplen > 1) + *tokend -= (dplen - 1); + } + return res; +} + + +ssize_t dtostr_dot(char *buf, size_t buflen, double val) +{ + const char *dp; + ssize_t len, dplen; + char *p; + + /* render with max precision */ + len = snprintf(buf, buflen, "%.17g", val); + if (len >= (ssize_t)buflen || len < 0) + return len; + + /* check if locale is sane */ +#ifdef HAVE_NL_LANGINFO + dp = nl_langinfo(RADIXCHAR); +#else + dp = localeconv()->decimal_point; +#endif + if (memcmp(dp, ".", 2) == 0) + return len; + + dplen = dp[1] ? strlen(dp) : 1; + p = memchr(buf, dp[0], len); + if (p) { + *p = '.'; + if (dp[1]) { + memmove(p + 1, p + dplen, strlen(p + dplen) + 1); + len -= dplen - 1; + } + } + return len; +} + diff --git a/usual/string.h b/usual/string.h index 8fce683..21a738b 100644 --- a/usual/string.h +++ b/usual/string.h @@ -117,5 +117,12 @@ const char *usual_strerror_r(int e, char *dst, size_t dstlen); /** Compat: Provide GNU-style API: const char *strerror_r(int e, char *dst, size_t dstlen) */ #define strerror_r(a,b,c) usual_strerror_r(a,b,c) +/** strtod() that uses '.' as decimal separator */ +double strtod_dot(const char *s, char **tokend); + +/** Convert double to string with '.' as decimal separator */ +ssize_t dtostr_dot(char *buf, size_t buflen, double val); + + #endif -- 2.39.5