File:  [Isaki's NoNo m68k/m88k emulator] / nono / lib / mystring.cpp
Revision 1.1.1.10 (vendor branch): download - view: text, annotated - select for diffs
Wed Apr 29 17:05:56 2026 UTC (2 months, 3 weeks ago) by root
Branches: MAIN, Isaki
CVS tags: v027, HEAD
nono 1.7.0

//
// nono
// Copyright (C) 2020 nono project
// Licensed under nono-license.txt
//

//
// 文字列操作
//

#include "mystring.h"
#include "ascii_ctype.h"
#include <algorithm>

std::string
string_format(const char *fmt, ...)
{
	va_list ap;
	char *buf;

	va_start(ap, fmt);
	vasprintf(&buf, fmt, ap);
	va_end(ap);
	std::string rv(buf);
	free(buf);

	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<std::string>
string_split(const char *str, int len, char c, int nlimit)
{
	std::vector<std::string> 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;
}

static constexpr bool FORMAT_TSEC = false;
static constexpr bool FORMAT_FULL = true;

// TimeToStr() と SecToStr() の共通部分。
template <bool full_format>
static const std::string
TimeToStrF(uint64 t)
{
	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 (full_format) {
		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);
}

// t [nsec] を文字列に整形して返す。
// ex)          1         2
//    01234567890123456789012345
//   "1.mmm'uuu'nnn"				10秒未満なら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"	1000日未満なら25桁
//
// 1000日以上になると桁がずれるけど、それはもういいだろう。
// 10秒未満の場合だけ %2u ではなく %u で1桁切り詰めているが、これは
// FORMAT_TSEC との互換性のため。その必要のない10分未満と10時間未満は
// どちらも %2u で表記し桁数を維持することに努める。
const std::string
TimeToStr(uint64 t)
{
	return TimeToStrF<FORMAT_FULL>(t);
}

// t [nsec] を文字列にして返す。
// 秒以上はすべて %u だけで表す。
// 1桁秒以内 (10秒未満) なことが分かっている場合は 13桁。
// 2桁秒以上になると伸びていく。前に余白等なし。
// ex)
//  "1.000'000'000"		(10秒未満なら13桁)
//  "10.000'000'000"
//  "100.000'000'000"
const std::string
SecToStr(uint64 t)
{
	return TimeToStrF<FORMAT_TSEC>(t);
}


#if defined(SELFTEST)
#include <cstdio>
#include "stopwatch.h"
std::string s;
int main()
{
	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_nsec();
	printf("%.3f msec\n", (double)t / 1e9);
	return 0;
}
#endif

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.