--- nono/lib/mystring.cpp 2026/04/29 17:04:28 1.1 +++ nono/lib/mystring.cpp 2026/04/29 17:05:52 1.1.1.9 @@ -1,10 +1,16 @@ // // nono -// Copyright (C) 2018 isaki@NetBSD.org +// Copyright (C) 2020 nono project +// Licensed under nono-license.txt +// + +// +// 文字列操作 // -#include "header.h" #include "mystring.h" +#include "ascii_ctype.h" +#include std::string string_format(const char *fmt, ...) @@ -21,25 +27,297 @@ string_format(const char *fmt, ...) return rv; } +// 文字列 str から先頭の連続する空白文字を取り除いた新しい文字列を返す。 +std::string +string_ltrim(const std::string& str) +{ + auto it = str.begin(); + for (; it != str.end(); it++) { + if (!is_ascii_space(*it)) + break; + } + return std::string(it, str.end()); +} + +// 文字列 str から末尾の連続する空白文字を取り除く (str を書き換える)。 +void +string_rtrim(std::string& str) +{ + while (is_ascii_space(*str.rbegin())) { + str.pop_back(); + } +} + +// 文字列 str から末尾の連続する空白文字を取り除く (str を書き換える)。 +void +rtrim(char *str) +{ + char *p = strchr(str, '\0'); + while (--p >= str && is_ascii_space(*p)) { + *p = '\0'; + } +} + +// 文字列 str から先頭と末尾の連続する空白文字を取り除いた新しい文字列を返す。 +std::string +string_trim(const std::string& str) +{ + int s = 0; + int e = str.size(); + + for (; s < e; s++) { + if (!is_ascii_space(str[s])) + break; + } + for (e--; e >= s; e--) { + if (!is_ascii_space(str[e])) + break; + } + + return str.substr(s, e - s + 1); +} + +// 文字列 src 中の ASCII 大文字を小文字にした新しい文字列を返す。 +std::string +string_tolower(const std::string& src) +{ + std::string dst(src); + std::transform(dst.begin(), dst.end(), dst.begin(), + [](unsigned char c){ return std::tolower(c); }); + return dst; +} + +// 文字列 src 中の ASCII 小文字を大文字にした新しい文字列を返す。 +std::string +string_toupper(const std::string& src) +{ + std::string dst(src); + std::transform(dst.begin(), dst.end(), dst.begin(), + [](unsigned char c){ return std::toupper(c); }); + return dst; +} + +// 文字列 lhs の先頭が rhs と大文字小文字の区別なしで一致すれば true を返す。 +// ASCII 専用。 +// C++20 の starts_with に似せておく。 +bool +starts_with_ignorecase(const std::string& lhs, const std::string& rhs) +{ + if (lhs.length() < rhs.length()) { + return false; + } #if 0 + return std::equal( + lhs.begin(), lhs.begin() + rhs.length(), + rhs.begin(), + [](std::string::value_type l, std::string::value_type r) { + return std::tolower(l) == std::tolower(r); + } + ); +#else + // こっちのほうが分かりやすいよな + return strncasecmp(lhs.c_str(), rhs.c_str(), rhs.length()) == 0; +#endif +} + +// 文字列 str (長さ len) を文字 c で分割したリストを返す。 +std::vector +string_split(const char *str, int len, char c, int nlimit) +{ + std::vector list; + + // 空文字列なら空リスト + if (len == 0) { + return list; + } + + int pos = 0; + int end = 0; + + for (; ; pos = end + 1) { + // 上限に達するならこれ以降は一要素として返す + if (nlimit > 0 && list.size() >= nlimit - 1) { + list.emplace_back(str + pos, len - pos); + break; + } + + const char *p = strchr(str + pos, c); + if (p) { + end = p - str; + } else { + end = len; + } + + list.emplace_back(str + pos, end - pos); + if (p == NULL) + break; + } + + return list; +} + +// val を3桁ずつカンマ区切りした文字列にして返す。最大は 26桁。 +// ex) 123 -> "138" +// 12345 -> "12,345" +std::string +format_number(uint64 val) +{ + // 1 2 3 4 5 6 + // UINT64_MAX = 18,446,744,073,709,551,615 + char part[6][8]; + char buf[32]; + int n; + + n = 0; + memset(&part, 0, sizeof(part)); + while (val >= 1000) { + uint32 r = val % 1000; + val /= 1000; + + snprintf(part[n], sizeof(part[n]), ",%03u", r); + n++; + } + // この時点で + // part[0] = ",615"; + // part[1] = ",551"; + // : + // part[5] = ",446"; + + // 先頭(val は 1000未満) + snprintf(buf, sizeof(buf), "%u", (uint32)val); + + // part を連結 + while (--n >= 0) { + strlcat(buf, part[n], sizeof(buf)); + } + return std::string(buf); +} + +// value を width 桁の16進数文字列にして返す。"%0{width}x" みたいな感じ。 +// strhex(0x12345678, 4) -> "5678" +// strhex(0x00000001, 3) -> "001" +std::string +strhex(uint32 value, int width) +{ + std::string s; + + for (width -= 1; width >= 0; width--) { + uint32 d = (value >> (width * 4)) & 0x0f; + if (__predict_true(d < 10)) { + s += '0' + d; + } else { + s += 'a' + d - 10; + } + } + return s; +} + +#define FORMAT_FULL 0 +#define FORMAT_SEC 1 + +// 経過時間 t を文字列にして返す。 +// 文字列長は、t の大きさ(とフォーマット指定)によって以下の通り。 +// +// 0 1 2 +// 01234567890123456789012345 +// "0.mmm'uuu'nnn" 10秒未満、または FORMAT_SEC なら13桁 +// "59.mmm'uuu'nnn" 1分未満なら14桁 +// " 9:59.mmm'uuu'nnn" 1時間未満なら17桁 +// " 9:59:59.mmm'uuu'nnn" 24時間未満なら20桁 +// "999d 23:59:59.mmm'uuu'nnn" 1日以上なら25桁。これが最大幅。 +// +// FORMAT_SEC は1桁秒以下の場合 (実際には1秒未満の場合) に用いる。 +// FORMAT_FULL は 1000日経過すると桁がずれるけど、それはもういいだろう。 +// +// 10秒未満の場合だけ %2u ではなく %u で1桁切り詰めているが、これは +// FORMAT_SEC との互換性のため。その必要のない10分未満と10時間未満は +// どちらも %2u で表記し桁数を維持することに努める。 +static const std::string +TimeToStrF(uint64 t, int format) +{ + char buf[32]; + char *p; + size_t len; + int n; + + uint ns = t % 1000; + t /= 1000; + uint us = t % 1000; + t /= 1000; + uint ms = t % 1000; + t /= 1000; + + uint s, m, h, d; + if (format == FORMAT_FULL) { + s = t % 60; + t /= 60; + m = t % 60; + t /= 60; + h = t % 24; + t /= 24; + d = t; + } else { + s = t; + m = 0; + h = 0; + d = 0; + } + + p = buf; + len = sizeof(buf); + if (d) { + n = snprintf(p, len, "%3ud %02u:%02u:%02u", d, h, m, s); + p += n; + len -= n; + } else if (h) { + n = snprintf(p, len, "%2u:%02u:%02u", h, m, s); + p += n; + len -= n; + } else if (m) { + n = snprintf(p, len, "%2u:%02u", m, s); + p += n; + len -= n; + } else { + n = snprintf(p, len, "%u", s); + p += n; + len -= n; + } + n = snprintf(p, len, ".%03u'%03u'%03u", ms, us, ns); + p += n; + len -= n; + + return std::string(buf, p - buf); +} + +const std::string +TimeToStr(uint64 t) +{ + return TimeToStrF(t, FORMAT_FULL); +} + +const std::string +SecToStr(uint64 t) +{ + return TimeToStrF(t, FORMAT_SEC); +} + + +#if defined(SELFTEST) #include -#include +#include "stopwatch.h" +std::string s; int main() { - std::string s0 = string_format("%d_%d", 0, 1); - if (s0 != "0_1") { - printf("error\n"); - return 1; - } - timeval start, end, result; - gettimeofday(&start, NULL); - for (int i = 0; i < 100000; i++) { - std::string s = string_format("%d_%d", i, i); - } - gettimeofday(&end, NULL); - timersub(&end, &start, &result); - long long t = (result.tv_sec) * 1000000 + result.tv_usec; - printf("%d.%03d\n", (int)t / 1000, (int)t % 1000); + Stopwatch sw; + sw.Start(); + for (uint64 i = 0; i < 10000000; i += 3) { + s = format_number(i); + if (s.empty()) + return 0; + } + sw.Stop(); + uint64 t = sw.Elapsed(); + printf("%ld.%03ld msec\n", t / 1000 / 1000, (t / 1000) % 1000); return 0; } #endif