1 // Copyright 2008-present Contributors to the OpenImageIO project.
2 // SPDX-License-Identifier: BSD-3-Clause
3 // https://github.com/OpenImageIO/oiio/blob/master/LICENSE.md
4 
5 
6 #include <cmath>
7 #include <cstdarg>
8 #include <cstdint>
9 #include <cstdlib>
10 #include <iostream>
11 #include <limits>
12 #include <locale.h>
13 #include <mutex>
14 #include <sstream>
15 #include <string>
16 #include <vector>
17 #if defined(__APPLE__) || defined(__FreeBSD__)
18 #    include <xlocale.h>
19 #endif
20 
21 #include <boost/algorithm/string.hpp>
22 #include <boost/algorithm/string/find.hpp>
23 
24 #include <OpenImageIO/dassert.h>
25 #include <OpenImageIO/platform.h>
26 #include <OpenImageIO/string_view.h>
27 #include <OpenImageIO/strutil.h>
28 #include <OpenImageIO/ustring.h>
29 
30 #ifdef _WIN32
31 #    include <shellapi.h>
32 #endif
33 
34 
35 // We use the public domain stb implementation of vsnprintf because
36 // not all platforms support a locale-independent version of vsnprintf.
37 // See: https://github.com/nothings/stb/blob/master/stb_sprintf.h
38 #define STB_SPRINTF_DECORATE(name) oiio_stbsp_##name
39 #define STB_SPRINTF_IMPLEMENTATION 1
40 #if defined(__GNUG__) && !defined(__clang__)
41 #    pragma GCC diagnostic ignored "-Wstrict-aliasing"
42 #    define STBI__ASAN OIIO_NO_SANITIZE_ADDRESS
43 #endif
44 #define stbsp__uintptr std::uintptr_t
45 #include "stb_sprintf.h"
46 
47 
48 OIIO_NAMESPACE_BEGIN
49 
50 
51 namespace {
52 static std::mutex output_mutex;
53 
54 // On systems that support it, get a location independent locale.
55 #if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) \
56     || defined(__FreeBSD_kernel__) || defined(__GLIBC__)
57 static locale_t c_loc = newlocale(LC_ALL_MASK, "C", nullptr);
58 #elif defined(_WIN32)
59 static _locale_t c_loc = _create_locale(LC_ALL, "C");
60 #endif
61 
62 };  // namespace
63 
64 
65 
66 // Locale-independent quickie ASCII digit and alphanum tests, good enough
67 // for our parsing.
68 inline int
isupper(char c)69 isupper(char c)
70 {
71     return c >= 'A' && c <= 'Z';
72 }
73 inline int
islower(char c)74 islower(char c)
75 {
76     return c >= 'a' && c <= 'z';
77 }
78 inline int
isalpha(char c)79 isalpha(char c)
80 {
81     return isupper(c) || islower(c);
82 }
83 inline int
isdigit(char c)84 isdigit(char c)
85 {
86     return c >= '0' && c <= '9';
87 }
88 
89 
90 
91 OIIO_NO_SANITIZE_ADDRESS
92 const char*
c_str() const93 string_view::c_str() const
94 {
95     // Usual case: either empty, or null-terminated
96     if (m_len == 0)  // empty string
97         return "";
98 
99     // This clause attempts to find out if there's a string-teriminating
100     // '\0' character just beyond the boundary of the string_view, in which
101     // case, simply returning m_chars (with no ustring creation) is a valid
102     // C string.
103     //
104     // BUG: if the string_view does not simply wrap a null-terminated string
105     // (including a std::string or ustring) or substring thereof, and the
106     // the character past the end of the string is beyond an allocation
107     // boundary, this will be flagged by address sanitizer. And it
108     // misbehaves if the memory just beyond the string_view, which isn't
109     // part of the string, gets changed during the lifetime of the
110     // string_view, and no longer has that terminating null. I think that in
111     // the long run, we can't use this trick. I'm kicking that can down the
112     // road just a bit because it's such a performance win. But we
113     // eventually want to get rid of this method anyway, since it won't be
114     // in C++17 string_view. So maybe we'll find ourselves relying on it a
115     // lot less, and therefore the performance hit of doing it foolproof
116     // won't be as onerous.
117     if (m_chars[m_len] == 0)  // 0-terminated
118         return m_chars;
119 
120     // Rare case: may not be 0-terminated. Bite the bullet and construct a
121     // 0-terminated string.  We use ustring as a way to avoid any issues of
122     // who cleans up the allocation, though it means that it will stay in
123     // the ustring table forever. Punt on this for now, it's an edge case
124     // that we need to handle, but is not likely to ever be an issue.
125     return ustring(m_chars, 0, m_len).c_str();
126 }
127 
128 
129 
130 void
sync_output(FILE * file,string_view str)131 Strutil::sync_output(FILE* file, string_view str)
132 {
133     if (str.size() && file) {
134         std::unique_lock<std::mutex> lock(output_mutex);
135         fwrite(str.data(), 1, str.size(), file);
136         fflush(file);
137     }
138 }
139 
140 
141 
142 void
sync_output(std::ostream & file,string_view str)143 Strutil::sync_output(std::ostream& file, string_view str)
144 {
145     if (str.size()) {
146         std::unique_lock<std::mutex> lock(output_mutex);
147         file << str;
148         file.flush();
149     }
150 }
151 
152 
153 
154 std::string
vsprintf(const char * fmt,va_list ap)155 Strutil::vsprintf(const char* fmt, va_list ap)
156 {
157     // Allocate a buffer on the stack that's big enough for us almost
158     // all the time.  Be prepared to allocate dynamically if it doesn't fit.
159     size_t size = 1024;
160     char stackbuf[1024];
161     std::vector<char> dynamicbuf;
162     char* buf = &stackbuf[0];
163 
164     while (1) {
165         // Try to vsnprintf into our buffer.
166         va_list apsave;
167 #ifdef va_copy
168         va_copy(apsave, ap);
169 #else
170         apsave = ap;
171 #endif
172 
173         int needed = oiio_stbsp_vsnprintf(buf, size, fmt, ap);
174         va_end(ap);
175 
176         // NB. C99 says vsnprintf returns -1 on an encoding error. Otherwise
177         // it's the number of characters that would have been written if the
178         // buffer were large enough.
179         if (needed == -1)
180             return std::string("ENCODING ERROR");
181         if (needed < (int)size) {
182             // It fit fine so we're done.
183             return std::string(buf, (size_t)needed);
184         }
185 
186         // vsnprintf reported that it wanted to write more characters
187         // than we allotted.  So try again using a dynamic buffer.  This
188         // doesn't happen very often if we chose our initial size well.
189         size = (needed > 0) ? (needed + 1) : (size * 2);
190         dynamicbuf.resize(size);
191         buf = &dynamicbuf[0];
192 #ifdef va_copy
193         va_copy(ap, apsave);
194 #else
195         ap = apsave;
196 #endif
197     }
198 }
199 
200 
201 
202 std::string
vformat(const char * fmt,va_list ap)203 Strutil::vformat(const char* fmt, va_list ap)
204 {
205     // For now, just treat as a synonym for vsprintf
206     return vsprintf(fmt, ap);
207 }
208 
209 
210 
211 std::string
memformat(long long bytes,int digits)212 Strutil::memformat(long long bytes, int digits)
213 {
214     const long long KB = (1 << 10);
215     const long long MB = (1 << 20);
216     const long long GB = (1 << 30);
217     const char* units  = "B";
218     double d           = (double)bytes;
219     if (bytes >= GB) {
220         units = "GB";
221         d     = (double)bytes / GB;
222     } else if (bytes >= MB) {
223         units = "MB";
224         d     = (double)bytes / MB;
225     } else if (bytes >= KB) {
226         // Just KB, don't bother with decimalization
227         return format("%lld KB", (long long)bytes / KB);
228     } else {
229         // Just bytes, don't bother with decimalization
230         return format("%lld B", (long long)bytes);
231     }
232     return format("%1.*f %s", digits, d, units);
233 }
234 
235 
236 
237 std::string
timeintervalformat(double secs,int digits)238 Strutil::timeintervalformat(double secs, int digits)
239 {
240     const double mins  = 60;
241     const double hours = mins * 60;
242     const double days  = hours * 24;
243 
244     std::string out;
245     int d = (int)floor(secs / days);
246     secs  = fmod(secs, days);
247     int h = (int)floor(secs / hours);
248     secs  = fmod(secs, hours);
249     int m = (int)floor(secs / mins);
250     secs  = fmod(secs, mins);
251     if (d)
252         out += format("%dd %dh ", d, h);
253     else if (h)
254         out += format("%dh ", h);
255     if (m || h || d)
256         out += format("%dm %1.*fs", m, digits, secs);
257     else
258         out += format("%1.*fs", digits, secs);
259     return out;
260 }
261 
262 
263 
264 bool
get_rest_arguments(const std::string & str,std::string & base,std::map<std::string,std::string> & result)265 Strutil::get_rest_arguments(const std::string& str, std::string& base,
266                             std::map<std::string, std::string>& result)
267 {
268     // Disregard the Windows long path question style prefix "\\?\"
269     static const std::string longPathQPrefix("\\\\?\\");
270     auto find_start_pos = starts_with(str, longPathQPrefix)
271                               ? longPathQPrefix.size()
272                               : size_t(0);
273 
274     std::string::size_type mark_pos = str.find_first_of("?", find_start_pos);
275     if (mark_pos == std::string::npos) {
276         base = str;
277         return true;
278     }
279 
280     base             = str.substr(0, mark_pos);
281     std::string rest = str.substr(mark_pos + 1);
282     std::vector<std::string> rest_tokens;
283     Strutil::split(rest, rest_tokens, "&");
284     for (const std::string& keyval : rest_tokens) {
285         mark_pos = keyval.find_first_of("=");
286         if (mark_pos == std::string::npos)
287             return false;
288         result[keyval.substr(0, mark_pos)] = keyval.substr(mark_pos + 1);
289     }
290 
291     return true;
292 }
293 
294 
295 
296 std::string
escape_chars(string_view unescaped)297 Strutil::escape_chars(string_view unescaped)
298 {
299     std::string s = unescaped;
300     for (size_t i = 0; i < s.length(); ++i) {
301         char c = s[i];
302         if (c == '\n' || c == '\t' || c == '\v' || c == '\b' || c == '\r'
303             || c == '\f' || c == '\a' || c == '\\' || c == '\"') {
304             s[i] = '\\';
305             ++i;
306             switch (c) {
307             case '\n': c = 'n'; break;
308             case '\t': c = 't'; break;
309             case '\v': c = 'v'; break;
310             case '\b': c = 'b'; break;
311             case '\r': c = 'r'; break;
312             case '\f': c = 'f'; break;
313             case '\a': c = 'a'; break;
314             }
315             s.insert(i, &c, 1);
316         }
317     }
318     return s;
319 }
320 
321 
322 
323 std::string
unescape_chars(string_view escaped)324 Strutil::unescape_chars(string_view escaped)
325 {
326     std::string s = escaped;
327     for (size_t i = 0, len = s.length(); i < len; ++i) {
328         if (s[i] == '\\') {
329             char c = s[i + 1];
330             if (c == 'n' || c == 't' || c == 'v' || c == 'b' || c == 'r'
331                 || c == 'f' || c == 'a' || c == '\\' || c == '\"') {
332                 s.erase(i, 1);
333                 --len;
334                 switch (c) {
335                 case 'n': s[i] = '\n'; break;
336                 case 't': s[i] = '\t'; break;
337                 case 'v': s[i] = '\v'; break;
338                 case 'b': s[i] = '\b'; break;
339                 case 'r': s[i] = '\r'; break;
340                 case 'f': s[i] = '\f'; break;
341                 case 'a':
342                     s[i] = '\a';
343                     break;
344                     // default case: the deletion is enough (backslash and quote)
345                 }
346             } else if (c >= '0' && c < '8') {
347                 // up to 3 octal digits
348                 int octalChar = 0;
349                 for (int j = 0; j < 3 && c >= '0' && c <= '7'; ++j) {
350                     octalChar = 8 * octalChar + (c - '0');
351                     s.erase(i, 1);
352                     --len;
353                     c = i + 1 < len ? s[i + 1] : '\0';
354                 }
355                 s[i] = (char)octalChar;
356             }
357         }
358     }
359     return s;
360 }
361 
362 
363 
364 std::string
wordwrap(string_view src,int columns,int prefix,string_view sep,string_view presep)365 Strutil::wordwrap(string_view src, int columns, int prefix, string_view sep,
366                   string_view presep)
367 {
368     if (columns < prefix + 20)
369         return src;  // give up, no way to make it wrap
370     std::ostringstream out;
371     columns -= prefix;  // now columns is the real width we have to work with
372     std::string allsep = Strutil::sprintf("%s%s", sep, presep);
373     while ((int)src.length() > columns) {
374         // Find the last `sep` break before the column limit.
375         size_t breakpoint = src.find_last_of(allsep, columns);
376         // If presep is not empty, find the last presep break before the
377         // column limit and break one character AFTER that, if it's a better
378         // breakpost than the sep break.
379         if (presep.size()) {
380             size_t presep_break = src.find_last_of(presep, columns);
381             if (presep_break >= breakpoint && presep_break < src.size())
382                 breakpoint = presep_break + 1;
383         }
384         // If no break was found, punt and hard break at the column limit.
385         if (breakpoint == string_view::npos)
386             breakpoint = columns;
387         // Copy up to the breakpoint, then newline and prefex blanks.
388         out << src.substr(0, breakpoint) << "\n" << std::string(prefix, ' ');
389         // Pick up where we left off
390         src = src.substr(breakpoint);
391         // Skip any separator characters at the start of the string
392         while (sep.find(src[0]) != string_view::npos)
393             src.remove_prefix(1);
394     }
395     out << src;
396     return out.str();
397 }
398 
399 
400 
401 // DEPRECATED(2.0) -- for link compatibility
402 namespace Strutil {
403 std::string
wordwrap(string_view src,int columns,int prefix)404 wordwrap(string_view src, int columns, int prefix)
405 {
406     return wordwrap(src, columns, prefix, " ", "");
407 }
408 }  // namespace Strutil
409 
410 
411 
412 bool
iequals(string_view a,string_view b)413 Strutil::iequals(string_view a, string_view b)
414 {
415     return boost::algorithm::iequals(a, b, std::locale::classic());
416 }
417 
418 
419 bool
iless(string_view a,string_view b)420 Strutil::iless(string_view a, string_view b)
421 {
422     return boost::algorithm::ilexicographical_compare(a, b,
423                                                       std::locale::classic());
424 }
425 
426 
427 bool
starts_with(string_view a,string_view b)428 Strutil::starts_with(string_view a, string_view b)
429 {
430     return boost::algorithm::starts_with(a, b);
431 }
432 
433 
434 bool
istarts_with(string_view a,string_view b)435 Strutil::istarts_with(string_view a, string_view b)
436 {
437     return boost::algorithm::istarts_with(a, b, std::locale::classic());
438 }
439 
440 
441 bool
ends_with(string_view a,string_view b)442 Strutil::ends_with(string_view a, string_view b)
443 {
444     return boost::algorithm::ends_with(a, b);
445 }
446 
447 
448 bool
iends_with(string_view a,string_view b)449 Strutil::iends_with(string_view a, string_view b)
450 {
451     return boost::algorithm::iends_with(a, b, std::locale::classic());
452 }
453 
454 
455 bool
contains(string_view a,string_view b)456 Strutil::contains(string_view a, string_view b)
457 {
458     return boost::algorithm::contains(a, b);
459 }
460 
461 
462 bool
icontains(string_view a,string_view b)463 Strutil::icontains(string_view a, string_view b)
464 {
465     return boost::algorithm::icontains(a, b, std::locale::classic());
466 }
467 
468 
469 size_t
find(string_view a,string_view b)470 Strutil::find(string_view a, string_view b)
471 {
472     return a.find(b);
473 }
474 
475 
476 size_t
rfind(string_view a,string_view b)477 Strutil::rfind(string_view a, string_view b)
478 {
479     return a.rfind(b);
480 }
481 
482 
483 size_t
ifind(string_view a,string_view b)484 Strutil::ifind(string_view a, string_view b)
485 {
486     if (a.empty())
487         return string_view::npos;
488     if (b.empty())
489         return 0;
490     auto f = boost::algorithm::ifind_first(a, b, std::locale::classic());
491     return f.empty() ? string_view::npos : f.begin() - a.data();
492 }
493 
494 
495 size_t
irfind(string_view a,string_view b)496 Strutil::irfind(string_view a, string_view b)
497 {
498     if (a.empty())
499         return string_view::npos;
500     if (b.empty())
501         return a.size();
502     auto f = boost::algorithm::ifind_last(a, b, std::locale::classic());
503     return f.empty() ? string_view::npos : f.begin() - a.data();
504 }
505 
506 
507 void
to_lower(std::string & a)508 Strutil::to_lower(std::string& a)
509 {
510     boost::algorithm::to_lower(a, std::locale::classic());
511 }
512 
513 
514 void
to_upper(std::string & a)515 Strutil::to_upper(std::string& a)
516 {
517     boost::algorithm::to_upper(a, std::locale::classic());
518 }
519 
520 
521 
522 bool
operator ()(const char * a,const char * b) const523 Strutil::StringIEqual::operator()(const char* a, const char* b) const noexcept
524 {
525     return boost::algorithm::iequals(a, b, std::locale::classic());
526 }
527 
528 
529 bool
operator ()(const char * a,const char * b) const530 Strutil::StringILess::operator()(const char* a, const char* b) const noexcept
531 {
532     return boost::algorithm::ilexicographical_compare(a, b,
533                                                       std::locale::classic());
534 }
535 
536 
537 
538 string_view
lstrip(string_view str,string_view chars)539 Strutil::lstrip(string_view str, string_view chars)
540 {
541     if (chars.empty())
542         chars = string_view(" \t\n\r\f\v", 6);
543     size_t b = str.find_first_not_of(chars);
544     return str.substr(b, string_view::npos);
545 }
546 
547 
548 
549 string_view
rstrip(string_view str,string_view chars)550 Strutil::rstrip(string_view str, string_view chars)
551 {
552     if (chars.empty())
553         chars = string_view(" \t\n\r\f\v", 6);
554     size_t e = str.find_last_not_of(chars);
555     return e != string_view::npos ? str.substr(0, e + 1) : string_view();
556 }
557 
558 
559 
560 string_view
strip(string_view str,string_view chars)561 Strutil::strip(string_view str, string_view chars)
562 {
563     if (chars.empty())
564         chars = string_view(" \t\n\r\f\v", 6);
565     size_t b = str.find_first_not_of(chars);
566     if (b == std::string::npos)
567         return string_view();
568     size_t e = str.find_last_not_of(chars);
569     OIIO_DASSERT(e != std::string::npos);
570     return str.substr(b, e - b + 1);
571 }
572 
573 
574 
575 static void
split_whitespace(string_view str,std::vector<string_view> & result,int maxsplit)576 split_whitespace(string_view str, std::vector<string_view>& result,
577                  int maxsplit)
578 {
579     // Implementation inspired by Pystring
580     string_view::size_type i, j, len = str.size();
581     for (i = j = 0; i < len;) {
582         while (i < len && ::isspace(str[i]))
583             i++;
584         j = i;
585         while (i < len && !::isspace(str[i]))
586             i++;
587         if (j < i) {
588             if (--maxsplit <= 0)
589                 break;
590             result.push_back(str.substr(j, i - j));
591             while (i < len && ::isspace(str[i]))
592                 i++;
593             j = i;
594         }
595     }
596     if (j < len)
597         result.push_back(str.substr(j, len - j));
598 }
599 
600 
601 
602 std::vector<std::string>
splits(string_view str,string_view sep,int maxsplit)603 Strutil::splits(string_view str, string_view sep, int maxsplit)
604 {
605     auto sr_result = splitsv(str, sep, maxsplit);
606     std::vector<std::string> result;
607     result.reserve(sr_result.size());
608     for (auto& s : sr_result)
609         result.push_back(s);
610     return result;
611 }
612 
613 
614 
615 void
split(string_view str,std::vector<std::string> & result,string_view sep,int maxsplit)616 Strutil::split(string_view str, std::vector<std::string>& result,
617                string_view sep, int maxsplit)
618 {
619     result = splits(str, sep, maxsplit);
620 }
621 
622 
623 
624 std::vector<string_view>
splitsv(string_view str,string_view sep,int maxsplit)625 Strutil::splitsv(string_view str, string_view sep, int maxsplit)
626 {
627     std::vector<string_view> result;
628     if (str.size() == 0)
629         return result;  // No source string --> no pieces
630 
631     // Implementation inspired by Pystring
632     if (maxsplit < 0)
633         maxsplit = std::numeric_limits<int>::max();
634     if (sep.size() == 0) {
635         split_whitespace(str, result, maxsplit);
636         return result;
637     }
638     size_t i = 0, j = 0, len = str.size(), n = sep.size();
639     while (i + n <= len) {
640         if (str[i] == sep[0] && str.substr(i, n) == sep) {
641             if (--maxsplit <= 0)
642                 break;
643             result.push_back(str.substr(j, i - j));
644             i = j = i + n;
645         } else {
646             i++;
647         }
648     }
649     result.push_back(str.substr(j, len - j));
650     return result;
651 }
652 
653 
654 
655 void
split(string_view str,std::vector<string_view> & result,string_view sep,int maxsplit)656 Strutil::split(string_view str, std::vector<string_view>& result,
657                string_view sep, int maxsplit)
658 {
659     result = splitsv(str, sep, maxsplit);
660 }
661 
662 
663 
664 std::string
concat(string_view s,string_view t)665 Strutil::concat(string_view s, string_view t)
666 {
667     size_t sl  = s.size();
668     size_t tl  = t.size();
669     size_t len = sl + tl;
670     std::unique_ptr<char[]> heap_buf;
671     char local_buf[256];
672     char* buf = local_buf;
673     if (len > sizeof(local_buf)) {
674         heap_buf.reset(new char[len]);
675         buf = heap_buf.get();
676     }
677     memcpy(buf, s.data(), sl);
678     memcpy(buf + sl, t.data(), tl);
679     return std::string(buf, len);
680 }
681 
682 
683 
684 std::string
repeat(string_view str,int n)685 Strutil::repeat(string_view str, int n)
686 {
687     size_t sl  = str.size();
688     size_t len = sl * std::max(0, n);
689     std::unique_ptr<char[]> heap_buf;
690     char local_buf[256];
691     char* buf = local_buf;
692     if (len > sizeof(local_buf)) {
693         heap_buf.reset(new char[len]);
694         buf = heap_buf.get();
695     }
696     for (int i = 0; i < n; ++i)
697         memcpy(buf + i * sl, str.data(), sl);
698     return std::string(buf, len);
699 }
700 
701 
702 
703 std::string
replace(string_view str,string_view pattern,string_view replacement,bool global)704 Strutil::replace(string_view str, string_view pattern, string_view replacement,
705                  bool global)
706 {
707     std::string r;
708     while (1) {
709         size_t f = str.find(pattern);
710         if (f != str.npos) {
711             // Pattern found -- copy the part of str prior to the pattern,
712             // then copy the replacement, and skip str up to the part after
713             // the pattern and continue for another go at it.
714             r.append(str.data(), f);
715             r.append(replacement.data(), replacement.size());
716             str.remove_prefix(f + pattern.size());
717             if (global)
718                 continue;  // Try for another match
719         }
720         // Pattern not found -- copy remaining string and we're done
721         r.append(str.data(), str.size());
722         break;
723     }
724     return r;
725 }
726 
727 
728 
729 #ifdef _WIN32
730 std::wstring
utf8_to_utf16(string_view str)731 Strutil::utf8_to_utf16(string_view str) noexcept
732 {
733     std::wstring native;
734 
735     native.resize(
736         MultiByteToWideChar(CP_UTF8, 0, str.data(), str.length(), NULL, 0));
737     MultiByteToWideChar(CP_UTF8, 0, str.data(), str.length(), &native[0],
738                         (int)native.size());
739 
740     return native;
741 }
742 
743 
744 
745 std::string
utf16_to_utf8(const std::wstring & str)746 Strutil::utf16_to_utf8(const std::wstring& str) noexcept
747 {
748     std::string utf8;
749 
750     utf8.resize(WideCharToMultiByte(CP_UTF8, 0, str.data(), str.length(), NULL,
751                                     0, NULL, NULL));
752     WideCharToMultiByte(CP_UTF8, 0, str.data(), str.length(), &utf8[0],
753                         (int)utf8.size(), NULL, NULL);
754 
755     return utf8;
756 }
757 #endif
758 
759 
760 
761 char*
safe_strcpy(char * dst,string_view src,size_t size)762 Strutil::safe_strcpy(char* dst, string_view src, size_t size) noexcept
763 {
764     if (src.size()) {
765         size_t end = std::min(size - 1, src.size());
766         for (size_t i = 0; i < end; ++i)
767             dst[i] = src[i];
768         for (size_t i = end; i < size; ++i)
769             dst[i] = 0;
770     } else {
771         for (size_t i = 0; i < size; ++i)
772             dst[i] = 0;
773     }
774     return dst;
775 }
776 
777 
778 
779 void
skip_whitespace(string_view & str)780 Strutil::skip_whitespace(string_view& str) noexcept
781 {
782     while (str.size() && isspace(str.front()))
783         str.remove_prefix(1);
784 }
785 
786 
787 
788 void
remove_trailing_whitespace(string_view & str)789 Strutil::remove_trailing_whitespace(string_view& str) noexcept
790 {
791     while (str.size() && isspace(str.back()))
792         str.remove_suffix(1);
793 }
794 
795 
796 
797 bool
parse_char(string_view & str,char c,bool skip_whitespace,bool eat)798 Strutil::parse_char(string_view& str, char c, bool skip_whitespace,
799                     bool eat) noexcept
800 {
801     string_view p = str;
802     if (skip_whitespace)
803         Strutil::skip_whitespace(p);
804     if (p.size() && p[0] == c) {
805         if (eat) {
806             p.remove_prefix(1);
807             str = p;
808         }
809         return true;
810     }
811     return false;
812 }
813 
814 
815 
816 bool
parse_until_char(string_view & str,char c,bool eat)817 Strutil::parse_until_char(string_view& str, char c, bool eat) noexcept
818 {
819     string_view p = str;
820     while (p.size() && p[0] != c)
821         p.remove_prefix(1);
822     if (eat)
823         str = p;
824     return p.size() && p[0] == c;
825 }
826 
827 
828 
829 bool
parse_prefix(string_view & str,string_view prefix,bool eat)830 Strutil::parse_prefix(string_view& str, string_view prefix, bool eat) noexcept
831 {
832     string_view p = str;
833     skip_whitespace(p);
834     if (Strutil::starts_with(p, prefix)) {
835         p.remove_prefix(prefix.size());
836         if (eat)
837             str = p;
838         return true;
839     }
840     return false;
841 }
842 
843 
844 
845 bool
parse_int(string_view & str,int & val,bool eat)846 Strutil::parse_int(string_view& str, int& val, bool eat) noexcept
847 {
848     string_view p = str;
849     skip_whitespace(p);
850     if (!p.size())
851         return false;
852     size_t endpos = 0;
853     int v         = Strutil::stoi(p, &endpos);
854     if (endpos == 0)
855         return false;  // no integer found
856     if (eat)
857         str = p.substr(endpos);
858     val = v;
859     return true;
860 }
861 
862 
863 
864 bool
parse_float(string_view & str,float & val,bool eat)865 Strutil::parse_float(string_view& str, float& val, bool eat) noexcept
866 {
867     string_view p = str;
868     skip_whitespace(p);
869     if (!p.size())
870         return false;
871     size_t endpos = 0;
872     float v       = Strutil::stof(p, &endpos);
873     if (endpos == 0)
874         return false;  // no integer found
875     if (eat)
876         str = p.substr(endpos);
877     val = v;
878     return true;
879 }
880 
881 
882 
883 bool
parse_string(string_view & str,string_view & val,bool eat,QuoteBehavior keep_quotes)884 Strutil::parse_string(string_view& str, string_view& val, bool eat,
885                       QuoteBehavior keep_quotes) noexcept
886 {
887     string_view p = str;
888     skip_whitespace(p);
889     if (str.empty())
890         return false;
891     char lead_char    = p.front();
892     bool quoted       = parse_char(p, '\"') || parse_char(p, '\'');
893     const char *begin = p.begin(), *end = p.begin();
894     bool escaped = false;  // was the prior character a backslash
895     while (end != p.end()) {
896         if (isspace(*end) && !quoted)
897             break;  // not quoted and we hit whitespace: we're done
898         if (quoted && *end == lead_char && !escaped)
899             break;  // closing quote -- we're done (beware embedded quote)
900         escaped = (end[0] == '\\') && (!escaped);
901         ++end;
902     }
903     if (quoted && keep_quotes == KeepQuotes) {
904         if (*end == lead_char)
905             val = string_view(begin - 1, size_t(end - begin) + 2);
906         else
907             val = string_view(begin - 1, size_t(end - begin) + 1);
908     } else {
909         val = string_view(begin, size_t(end - begin));
910     }
911     p.remove_prefix(size_t(end - begin));
912     if (quoted && p.size() && p[0] == lead_char)
913         p.remove_prefix(1);  // eat closing quote
914     if (eat)
915         str = p;
916     return quoted || val.size();
917 }
918 
919 
920 
921 string_view
parse_word(string_view & str,bool eat)922 Strutil::parse_word(string_view& str, bool eat) noexcept
923 {
924     string_view p = str;
925     skip_whitespace(p);
926     const char *begin = p.begin(), *end = p.begin();
927     while (end != p.end() && isalpha(*end))
928         ++end;
929     size_t wordlen = end - begin;
930     if (eat && wordlen) {
931         p.remove_prefix(wordlen);
932         str = p;
933     }
934     return string_view(begin, wordlen);
935 }
936 
937 
938 
939 string_view
parse_identifier(string_view & str,bool eat)940 Strutil::parse_identifier(string_view& str, bool eat) noexcept
941 {
942     string_view p = str;
943     skip_whitespace(p);
944     const char *begin = p.begin(), *end = p.begin();
945     if (end != p.end() && (isalpha(*end) || *end == '_'))
946         ++end;
947     else
948         return string_view();  // not even the start of an identifier
949     while (end != p.end() && (isalpha(*end) || isdigit(*end) || *end == '_'))
950         ++end;
951     if (eat) {
952         p.remove_prefix(size_t(end - begin));
953         str = p;
954     }
955     return string_view(begin, size_t(end - begin));
956 }
957 
958 
959 
960 string_view
parse_identifier(string_view & str,string_view allowed,bool eat)961 Strutil::parse_identifier(string_view& str, string_view allowed,
962                           bool eat) noexcept
963 {
964     string_view p = str;
965     skip_whitespace(p);
966     const char *begin = p.begin(), *end = p.begin();
967     if (end != p.end()
968         && (isalpha(*end) || *end == '_'
969             || allowed.find(*end) != string_view::npos))
970         ++end;
971     else
972         return string_view();  // not even the start of an identifier
973     while (end != p.end()
974            && (isalpha(*end) || isdigit(*end) || *end == '_'
975                || allowed.find(*end) != string_view::npos))
976         ++end;
977     if (eat) {
978         p.remove_prefix(size_t(end - begin));
979         str = p;
980     }
981     return string_view(begin, size_t(end - begin));
982 }
983 
984 
985 
986 bool
parse_identifier_if(string_view & str,string_view id,bool eat)987 Strutil::parse_identifier_if(string_view& str, string_view id,
988                              bool eat) noexcept
989 {
990     string_view head = parse_identifier(str, false /* don't eat */);
991     if (head == id) {
992         if (eat)
993             parse_identifier(str);
994         return true;
995     }
996     return false;
997 }
998 
999 
1000 
1001 string_view
parse_until(string_view & str,string_view sep,bool eat)1002 Strutil::parse_until(string_view& str, string_view sep, bool eat) noexcept
1003 {
1004     string_view p     = str;
1005     const char *begin = p.begin(), *end = p.begin();
1006     while (end != p.end() && sep.find(*end) == string_view::npos)
1007         ++end;
1008     size_t wordlen = end - begin;
1009     if (eat && wordlen) {
1010         p.remove_prefix(wordlen);
1011         str = p;
1012     }
1013     return string_view(begin, wordlen);
1014 }
1015 
1016 
1017 
1018 string_view
parse_while(string_view & str,string_view set,bool eat)1019 Strutil::parse_while(string_view& str, string_view set, bool eat) noexcept
1020 {
1021     string_view p     = str;
1022     const char *begin = p.begin(), *end = p.begin();
1023     while (end != p.end() && set.find(*end) != string_view::npos)
1024         ++end;
1025     size_t wordlen = end - begin;
1026     if (eat && wordlen) {
1027         p.remove_prefix(wordlen);
1028         str = p;
1029     }
1030     return string_view(begin, wordlen);
1031 }
1032 
1033 
1034 
1035 string_view
parse_nested(string_view & str,bool eat)1036 Strutil::parse_nested(string_view& str, bool eat) noexcept
1037 {
1038     // Make sure we have a valid string and ascertain the characters that
1039     // nest and unnest.
1040     string_view p = str;
1041     if (!p.size())
1042         return string_view();  // No proper opening
1043     char opening = p[0];
1044     char closing = 0;
1045     if (opening == '(')
1046         closing = ')';
1047     else if (opening == '[')
1048         closing = ']';
1049     else if (opening == '{')
1050         closing = '}';
1051     else
1052         return string_view();
1053 
1054     // Walk forward in the string until we exactly unnest compared to the
1055     // start.
1056     size_t len  = 1;
1057     int nesting = 1;
1058     for (; nesting && len < p.size(); ++len) {
1059         if (p[len] == opening)
1060             ++nesting;
1061         else if (p[len] == closing)
1062             --nesting;
1063     }
1064 
1065     if (nesting)
1066         return string_view();  // No proper closing
1067 
1068     OIIO_ASSERT(p[len - 1] == closing);
1069 
1070     // The result is the first len characters
1071     string_view result = str.substr(0, len);
1072     if (eat)
1073         str.remove_prefix(len);
1074     return result;
1075 }
1076 
1077 
1078 
1079 std::string
excise_string_after_head(std::string & str,string_view head)1080 Strutil::excise_string_after_head(std::string& str, string_view head)
1081 {
1082     std::string result;
1083     string_view s(str);
1084     size_t pattern_start = s.find(head);
1085     if (pattern_start != string_view::npos) {
1086         // Reposition s to be after the head
1087         s.remove_prefix(pattern_start + head.size());
1088         string_view m = Strutil::parse_until(s, " \t\r\n");
1089         Strutil::skip_whitespace(s);
1090         result = m;
1091         str    = str.substr(0, pattern_start) + std::string(s);
1092     }
1093     return result;
1094 }
1095 
1096 
1097 
1098 /*
1099 Copyright for decode function:
1100 See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.
1101 
1102 MIT license
1103 
1104 Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de>
1105 
1106 Permission is hereby granted, free of charge, to any person obtaining a copy
1107 of this software and associated documentation files (the "Software"), to
1108 deal in the Software without restriction, including without limitation the
1109 rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
1110 sell copies of the Software, and to permit persons to whom the Software is
1111 furnished to do so, subject to the following conditions:
1112 
1113 The above copyright notice and this permission notice shall be included in
1114 all copies or substantial portions of the Software.
1115 
1116 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1117 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1118 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1119 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1120 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
1121 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
1122 IN THE SOFTWARE.
1123 */
1124 
1125 #define UTF8_ACCEPT 0
1126 #define UTF8_REJECT 12
1127 
1128 static const uint8_t utf8d[] = {
1129     // clang-format off
1130   // The first part of the table maps bytes to character classes that
1131   // to reduce the size of the transition table and create bitmasks.
1132    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
1133    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
1134    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
1135    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
1136    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,  9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,
1137    7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,  7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
1138    8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2,  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
1139   10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8,
1140 
1141   // The second part is a transition table that maps a combination
1142   // of a state of the automaton and a character class to a state.
1143    0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12,
1144   12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12,
1145   12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12,
1146   12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12,
1147   12,36,12,12,12,12,12,12,12,12,12,12,
1148     // clang-format on
1149 };
1150 
1151 inline uint32_t
decode(uint32_t * state,uint32_t * codep,uint32_t byte)1152 decode(uint32_t* state, uint32_t* codep, uint32_t byte)
1153 {
1154     uint32_t type = utf8d[byte];
1155     *codep        = (*state != UTF8_ACCEPT) ? (byte & 0x3fu) | (*codep << 6)
1156                                             : (0xff >> type) & (byte);
1157     *state        = utf8d[256 + *state + type];
1158     return *state;
1159 }
1160 
1161 void
utf8_to_unicode(string_view str,std::vector<uint32_t> & uvec)1162 Strutil::utf8_to_unicode(string_view str, std::vector<uint32_t>& uvec)
1163 {
1164     const char* begin = str.begin();
1165     const char* end   = str.end();
1166     uint32_t state    = 0;
1167     for (; begin != end; ++begin) {
1168         uint32_t codepoint = 0;
1169         if (!decode(&state, &codepoint, (unsigned char)*begin))
1170             uvec.push_back(codepoint);
1171     }
1172 }
1173 
1174 
1175 
1176 /* base64 code is based upon: http://www.adp-gmbh.ch/cpp/common/base64.html
1177    https://github.com/ReneNyffenegger/cpp-base64
1178 
1179    Zlib license
1180 
1181    Copyright (C) 2004-2008 René Nyffenegger
1182 
1183    This source code is provided 'as-is', without any express or implied
1184    warranty. In no event will the author be held liable for any damages
1185    arising from the use of this software.
1186 
1187    Permission is granted to anyone to use this software for any purpose,
1188    including commercial applications, and to alter it and redistribute it
1189    freely, subject to the following restrictions:
1190 
1191    1. The origin of this source code must not be misrepresented; you must not
1192       claim that you wrote the original source code. If you use this source code
1193       in a product, an acknowledgment in the product documentation would be
1194       appreciated but is not required.
1195    2. Altered source versions must be plainly marked as such, and must not be
1196       misrepresented as being the original source code.
1197    3. This notice may not be removed or altered from any source distribution.
1198 
1199    René Nyffenegger rene.nyffenegger@adp-gmbh.ch
1200 */
1201 std::string
base64_encode(string_view str)1202 Strutil::base64_encode(string_view str)
1203 {
1204     static const char* base64_chars
1205         = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1206     std::string ret;
1207     ret.reserve((str.size() * 4 + 2) / 3);
1208     int i = 0;
1209     unsigned char char_array_3[3];
1210     unsigned char char_array_4[4];
1211     while (str.size()) {
1212         char_array_3[i++] = str.front();
1213         str.remove_prefix(1);
1214         if (i == 3) {
1215             char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
1216             char_array_4[1] = ((char_array_3[0] & 0x03) << 4)
1217                               + ((char_array_3[1] & 0xf0) >> 4);
1218             char_array_4[2] = ((char_array_3[1] & 0x0f) << 2)
1219                               + ((char_array_3[2] & 0xc0) >> 6);
1220             char_array_4[3] = char_array_3[2] & 0x3f;
1221             for (int j = 0; j < 4; j++)
1222                 ret += base64_chars[char_array_4[j]];
1223             i = 0;
1224         }
1225     }
1226     if (i) {
1227         for (int j = i; j < 3; j++)
1228             char_array_3[j] = '\0';
1229         char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
1230         char_array_4[1] = ((char_array_3[0] & 0x03) << 4)
1231                           + ((char_array_3[1] & 0xf0) >> 4);
1232         char_array_4[2] = ((char_array_3[1] & 0x0f) << 2)
1233                           + ((char_array_3[2] & 0xc0) >> 6);
1234         char_array_4[3] = char_array_3[2] & 0x3f;
1235         for (int j = 0; j < i + 1; j++)
1236             ret += base64_chars[char_array_4[j]];
1237         while (i++ < 3)
1238             ret += '=';
1239     }
1240     return ret;
1241 }
1242 
1243 
1244 
1245 // Helper: Eat the given number of chars from str, then return the next
1246 // char, or 0 if no more chars are in str.
1247 inline unsigned char
cnext(string_view & str,int eat=1)1248 cnext(string_view& str, int eat = 1)
1249 {
1250     str.remove_prefix(eat);
1251     return OIIO_LIKELY(str.size()) ? str.front() : 0;
1252 }
1253 
1254 
1255 
1256 int
stoi(string_view str,size_t * pos,int base)1257 Strutil::stoi(string_view str, size_t* pos, int base)
1258 {
1259     // We roll our own stoi so that we can directly use it with a
1260     // string_view and also hardcode it without any locale dependence. The
1261     // system stoi/atoi/strtol/etc. needs to be null-terminated.
1262     string_view str_orig = str;
1263     Strutil::skip_whitespace(str);
1264 
1265     // c is always the next char to parse, or 0 if there's no more
1266     unsigned char c = cnext(str, 0);  // don't eat the char
1267 
1268     // Handle leading - or +
1269     bool neg = (c == '-');
1270     if (c == '-' || c == '+')
1271         c = cnext(str);
1272 
1273     // Allow "0x" to start hex number if base is 16 or 0 (any)
1274     if ((base == 0 || base == 16) && c == '0' && str.size() >= 1
1275         && (str[1] == 'x' || str[1] == 'X')) {
1276         base = 16;
1277         c    = cnext(str, 2);
1278     }
1279     // For "any" base, collapse to base 10 unless leading 0 means it's octal
1280     if (base == 0)
1281         base = c == '0' ? 8 : 10;
1282 
1283     // Accumulate into a 64 bit int to make overflow handling simple.
1284     // If we ever need a str-to-int64 conversion, we'll need to be more
1285     // careful with figuring out overflow. But the 32 bit case is easy.
1286     int64_t acc    = 0;
1287     bool overflow  = false;  // Set if we overflow
1288     bool anydigits = false;  // Have we parsed any digits at all?
1289     int64_t maxval = neg ? -int64_t(std::numeric_limits<int>::min())
1290                          : std::numeric_limits<int>::max();
1291     for (; OIIO_LIKELY(c); c = cnext(str)) {
1292         if (OIIO_LIKELY(isdigit(c)))
1293             c -= '0';
1294         else if (isalpha(c))
1295             c -= isupper(c) ? 'A' - 10 : 'a' - 10;
1296         else {
1297             break;  // done
1298         }
1299         if (c >= base)
1300             break;
1301         acc       = acc * base + c;
1302         anydigits = true;
1303         if (OIIO_UNLIKELY(acc > maxval))
1304             overflow = true;
1305     }
1306     if (OIIO_UNLIKELY(!anydigits)) {
1307         str = str_orig;
1308     } else if (OIIO_UNLIKELY(overflow)) {
1309         acc = neg ? std::numeric_limits<int>::min()
1310                   : std::numeric_limits<int>::max();
1311     } else {
1312         if (neg)
1313             acc = -acc;
1314     }
1315     if (pos)
1316         *pos = size_t(str.data() - str_orig.data());
1317     return static_cast<int>(acc);
1318 }
1319 
1320 
1321 
1322 float
strtof(const char * nptr,char ** endptr)1323 Strutil::strtof(const char* nptr, char** endptr) noexcept
1324 {
1325     // Can use strtod_l on platforms that support it
1326 #ifdef __APPLE__
1327     // On OSX, strtod_l is for some reason drastically faster than strtof_l.
1328     return static_cast<float>(strtod_l(nptr, endptr, c_loc));
1329 #elif defined(__linux__) || defined(__FreeBSD__) \
1330     || defined(__FreeBSD_kernel__) || defined(__GLIBC__)
1331     return strtof_l(nptr, endptr, c_loc);
1332 #elif defined(_WIN32)
1333     // Windows has _strtod_l
1334     return static_cast<float>(_strtod_l(nptr, endptr, c_loc));
1335 #else
1336     // On platforms without strtof_l...
1337     std::locale native;  // default ctr gets current global locale
1338     char nativepoint
1339         = std::use_facet<std::numpunct<char>>(native).decimal_point();
1340     // If the native locale uses decimal, just directly use strtof.
1341     if (nativepoint == '.')
1342         return ::strtof(nptr, endptr);
1343     // Complex case -- CHEAT by making a copy of the string and replacing
1344     // the decimal, then use system strtof!
1345     std::string s(nptr);
1346     const char* pos = strchr(nptr, nativepoint);
1347     if (pos) {
1348         s[pos - nptr] = nativepoint;
1349         auto d        = strtof(s.c_str(), endptr);
1350         if (endptr)
1351             *endptr = (char*)nptr + (*endptr - s.c_str());
1352         return d;
1353     }
1354     // No decimal point at all -- use regular strtof
1355     return ::strtof(s.c_str(), endptr);
1356 #endif
1357 }
1358 
1359 
1360 double
strtod(const char * nptr,char ** endptr)1361 Strutil::strtod(const char* nptr, char** endptr) noexcept
1362 {
1363     // Can use strtod_l on platforms that support it
1364 #if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) \
1365     || defined(__FreeBSD_kernel__) || defined(__GLIBC__)
1366     // static initialization inside function is thread-safe by C++11 rules!
1367     return strtod_l(nptr, endptr, c_loc);
1368 #elif defined(_WIN32)
1369     // Windows has _strtod_l
1370     return _strtod_l(nptr, endptr, c_loc);
1371 #else
1372     // On platforms without strtod_l...
1373     std::locale native;  // default ctr gets current global locale
1374     char nativepoint
1375         = std::use_facet<std::numpunct<char>>(native).decimal_point();
1376     // If the native locale uses decimal, just directly use strtod.
1377     if (nativepoint == '.')
1378         return ::strtod(nptr, endptr);
1379     // Complex case -- CHEAT by making a copy of the string and replacing
1380     // the decimal, then use system strtod!
1381     std::string s(nptr);
1382     const char* pos = strchr(nptr, nativepoint);
1383     if (pos) {
1384         s[pos - nptr] = nativepoint;
1385         auto d        = ::strtod(s.c_str(), endptr);
1386         if (endptr)
1387             *endptr = (char*)nptr + (*endptr - s.c_str());
1388         return d;
1389     }
1390     // No decimal point at all -- use regular strtod
1391     return ::strtod(s.c_str(), endptr);
1392 #endif
1393 }
1394 
1395 // Notes:
1396 //
1397 // FreeBSD's implementation of strtod:
1398 //   https://svnweb.freebsd.org/base/stable/10/contrib/gdtoa/strtod.c?view=markup
1399 // Python's implementation  of strtod: (BSD license)
1400 //   https://hg.python.org/cpython/file/default/Python/pystrtod.c
1401 // Julia's implementation (combo of strtod_l and Python's impl):
1402 //   https://github.com/JuliaLang/julia/blob/master/src/support/strtod.c
1403 // MSDN documentation on Windows _strtod_l and friends:
1404 //   https://msdn.microsoft.com/en-us/library/kxsfc1ab.aspx   (_strtod_l)
1405 //   https://msdn.microsoft.com/en-us/library/4zx9aht2.aspx   (_create_locale)
1406 // cppreference on locale:
1407 //   http://en.cppreference.com/w/cpp/locale/locale
1408 
1409 
1410 
1411 float
stof(const char * s,size_t * pos)1412 Strutil::stof(const char* s, size_t* pos)
1413 {
1414     if (s) {
1415         char* endptr;
1416         float r = Strutil::strtof(s, &endptr);
1417         if (endptr != s) {
1418             if (pos)
1419                 *pos = size_t(endptr - s);
1420             return r;
1421         }
1422     }
1423     // invalid
1424     if (pos)
1425         *pos = 0;
1426     return 0;
1427 }
1428 
1429 
1430 float
stof(const std::string & s,size_t * pos)1431 Strutil::stof(const std::string& s, size_t* pos)
1432 {
1433     return Strutil::stof(s.c_str(), pos);
1434 }
1435 
1436 
1437 float
stof(string_view s,size_t * pos)1438 Strutil::stof(string_view s, size_t* pos)
1439 {
1440     // string_view can't be counted on to end with a terminating null, so
1441     // for safety, create a temporary string. This looks wasteful, but it's
1442     // not as bad as you think -- fully compliant C++ >= 11 implementations
1443     // will use the "short string optimization", meaning that this string
1444     // creation will NOT need an allocation/free for most strings we expect
1445     // to hold a text representation of a float.
1446     return Strutil::stof(std::string(s).c_str(), pos);
1447 }
1448 
1449 
1450 
1451 double
stod(const char * s,size_t * pos)1452 Strutil::stod(const char* s, size_t* pos)
1453 {
1454     if (s) {
1455         char* endptr;
1456         double r = Strutil::strtod(s, &endptr);
1457         if (endptr != s) {
1458             if (pos)
1459                 *pos = size_t(endptr - s);
1460             return r;
1461         }
1462     }
1463     // invalid
1464     if (pos)
1465         *pos = 0;
1466     return 0;
1467 }
1468 
1469 
1470 double
stod(const std::string & s,size_t * pos)1471 Strutil::stod(const std::string& s, size_t* pos)
1472 {
1473     return Strutil::stod(s.c_str(), pos);
1474 }
1475 
1476 
1477 double
stod(string_view s,size_t * pos)1478 Strutil::stod(string_view s, size_t* pos)
1479 {
1480     // string_view can't be counted on to end with a terminating null, so
1481     // for safety, create a temporary string. This looks wasteful, but it's
1482     // not as bad as you think -- fully compliant C++ >= 11 implementations
1483     // will use the "short string optimization", meaning that this string
1484     // creation will NOT need an allocation/free for most strings we expect
1485     // to hold a text representation of a float.
1486     return Strutil::stod(std::string(s).c_str(), pos);
1487 }
1488 
1489 
1490 
1491 bool
string_is_int(string_view s)1492 Strutil::string_is_int(string_view s)
1493 {
1494     size_t pos;
1495     Strutil::stoi(s, &pos);
1496     if (pos) {  // skip remaining whitespace
1497         s.remove_prefix(pos);
1498         Strutil::skip_whitespace(s);
1499     }
1500     return pos && s.empty();  // consumed the whole string
1501 }
1502 
1503 
1504 bool
string_is_float(string_view s)1505 Strutil::string_is_float(string_view s)
1506 {
1507     size_t pos;
1508     Strutil::stof(s, &pos);
1509     if (pos) {  // skip remaining whitespace
1510         s.remove_prefix(pos);
1511         Strutil::skip_whitespace(s);
1512     }
1513     return pos && s.empty();  // consumed the whole string
1514 }
1515 
1516 
1517 OIIO_NAMESPACE_END
1518