OSDN Git Service

Merge pull request #3569 from sikabane-works/release/3.0.0.88-alpha
[hengbandforosx/hengbandosx.git] / src / util / string-processor.cpp
1 #include "util/string-processor.h"
2 #include "util/int-char-converter.h"
3
4 /*!
5  * 10進数から16進数への変換テーブル /
6  * Global array for converting numbers to uppercase hecidecimal digit
7  * This array can also be used to convert a number to an octal digit
8  */
9 const char hexsym[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
10
11 int max_macrotrigger = 0; /*!< 現在登録中のマクロ(トリガー)の数 */
12 concptr macro_template = nullptr; /*!< Angband設定ファイルのT: タグ情報から読み込んだ長いTコードを処理するために利用する文字列ポインタ */
13 concptr macro_modifier_chr; /*!< &x# で指定されるマクロトリガーに関する情報を記録する文字列ポインタ */
14 concptr macro_modifier_name[MAX_MACRO_MOD]; /*!< マクロ上で取り扱う特殊キーを文字列上で表現するためのフォーマットを記録した文字列ポインタ配列 */
15 concptr macro_trigger_name[MAX_MACRO_TRIG]; /*!< マクロのトリガーコード */
16 concptr macro_trigger_keycode[2][MAX_MACRO_TRIG]; /*!< マクロの内容 */
17
18 /*
19  * Convert a decimal to a single digit octal number
20  */
21 static char octify(uint i)
22 {
23     return hexsym[i % 8];
24 }
25
26 /*
27  * Convert a decimal to a single digit hex number
28  */
29 static char hexify(uint i)
30 {
31     return hexsym[i % 16];
32 }
33
34 /*
35  * Convert a octal-digit into a decimal
36  */
37 static int deoct(char c)
38 {
39     if (isdigit(c)) {
40         return D2I(c);
41     }
42     return 0;
43 }
44
45 /*
46  * Convert a hexidecimal-digit into a decimal
47  */
48 static int dehex(char c)
49 {
50     if (isdigit(c)) {
51         return D2I(c);
52     }
53     if (islower(c)) {
54         return A2I(c) + 10;
55     }
56     if (isupper(c)) {
57         return A2I(tolower(c)) + 10;
58     }
59     return 0;
60 }
61
62 static char force_upper(char a)
63 {
64     return islower(a) ? toupper(a) : a;
65 }
66
67 static int angband_stricmp(concptr a, concptr b)
68 {
69     for (concptr s1 = a, s2 = b; true; s1++, s2++) {
70         char z1 = force_upper(*s1);
71         char z2 = force_upper(*s2);
72         if (z1 < z2) {
73             return -1;
74         }
75         if (z1 > z2) {
76             return 1;
77         }
78         if (!z1) {
79             return 0;
80         }
81     }
82 }
83
84 static int angband_strnicmp(concptr a, concptr b, int n)
85 {
86     for (concptr s1 = a, s2 = b; n > 0; s1++, s2++, n--) {
87         char z1 = force_upper(*s1);
88         char z2 = force_upper(*s2);
89         if (z1 < z2) {
90             return -1;
91         }
92         if (z1 > z2) {
93             return 1;
94         }
95         if (!z1) {
96             return 0;
97         }
98     }
99
100     return 0;
101 }
102
103 static void trigger_text_to_ascii(char **bufptr, concptr *strptr)
104 {
105     char *s = *bufptr;
106     concptr str = *strptr;
107     bool mod_status[MAX_MACRO_MOD];
108
109     int i, len = 0;
110     int shiftstatus = 0;
111     concptr key_code;
112
113     if (macro_template == nullptr) {
114         return;
115     }
116
117     for (i = 0; macro_modifier_chr[i]; i++) {
118         mod_status[i] = false;
119     }
120     str++;
121
122     /* Examine modifier keys */
123     while (true) {
124         for (i = 0; macro_modifier_chr[i]; i++) {
125             len = strlen(macro_modifier_name[i]);
126
127             if (!angband_strnicmp(str, macro_modifier_name[i], len)) {
128                 break;
129             }
130         }
131
132         if (!macro_modifier_chr[i]) {
133             break;
134         }
135         str += len;
136         mod_status[i] = true;
137         if ('S' == macro_modifier_chr[i]) {
138             shiftstatus = 1;
139         }
140     }
141
142     for (i = 0; i < max_macrotrigger; i++) {
143         len = strlen(macro_trigger_name[i]);
144         if (!angband_strnicmp(str, macro_trigger_name[i], len) && ']' == str[len]) {
145             break;
146         }
147     }
148
149     if (i == max_macrotrigger) {
150         str = angband_strchr(str, ']');
151         if (str) {
152             *s++ = (char)31;
153             *s++ = '\r';
154             *bufptr = s;
155             *strptr = str; /* where **strptr == ']' */
156         }
157
158         return;
159     }
160
161     key_code = macro_trigger_keycode[shiftstatus][i];
162     str += len;
163
164     *s++ = (char)31;
165     for (i = 0; macro_template[i]; i++) {
166         char ch = macro_template[i];
167         switch (ch) {
168         case '&':
169             for (int j = 0; macro_modifier_chr[j]; j++) {
170                 if (mod_status[j]) {
171                     *s++ = macro_modifier_chr[j];
172                 }
173             }
174
175             break;
176         case '#':
177             strcpy(s, key_code);
178             s += strlen(key_code);
179             break;
180         default:
181             *s++ = ch;
182             break;
183         }
184     }
185
186     *s++ = '\r';
187
188     *bufptr = s;
189     *strptr = str; /* where **strptr == ']' */
190     return;
191 }
192
193 /*
194  * Hack -- convert a printable string into real ascii
195  *
196  * I have no clue if this function correctly handles, for example,
197  * parsing "\xFF" into a (signed) char.  Whoever thought of making
198  * the "sign" of a "char" undefined is a complete moron.  Oh well.
199  */
200 void text_to_ascii(char *buf, std::string_view sv, size_t bufsize)
201 {
202     char *s = buf;
203     auto buffer_end = s + bufsize;
204     auto str = sv.data();
205     constexpr auto step_size = 1;
206     while (*str && (s + step_size < buffer_end)) {
207         if (*str == '\\') {
208             str++;
209             if (!(*str)) {
210                 break;
211             }
212
213             if (*str == '[') {
214                 trigger_text_to_ascii(&s, &str);
215             } else {
216                 if (*str == 'x') {
217                     *s = 16 * (char)dehex(*++str);
218                     *s++ += (char)dehex(*++str);
219                 } else if (*str == '\\') {
220                     *s++ = '\\';
221                 } else if (*str == '^') {
222                     *s++ = '^';
223                 } else if (*str == 's') {
224                     *s++ = ' ';
225                 } else if (*str == 'e') {
226                     *s++ = ESCAPE;
227                 } else if (*str == 'b') {
228                     *s++ = '\b';
229                 } else if (*str == 'n') {
230                     *s++ = '\n';
231                 } else if (*str == 'r') {
232                     *s++ = '\r';
233                 } else if (*str == 't') {
234                     *s++ = '\t';
235                 } else if (*str == '0') {
236                     *s = 8 * (char)deoct(*++str);
237                     *s++ += (char)deoct(*++str);
238                 } else if (*str == '1') {
239                     *s = 64 + 8 * (char)deoct(*++str);
240                     *s++ += (char)deoct(*++str);
241                 } else if (*str == '2') {
242                     *s = 64 * 2 + 8 * (char)deoct(*++str);
243                     *s++ += (char)deoct(*++str);
244                 } else if (*str == '3') {
245                     *s = 64 * 3 + 8 * (char)deoct(*++str);
246                     *s++ += (char)deoct(*++str);
247                 }
248             }
249
250             str++;
251         } else if (*str == '^') {
252             str++;
253             *s++ = (*str++ & 037);
254         } else {
255             *s++ = *str++;
256         }
257     }
258
259     *s = '\0';
260 }
261
262 static bool trigger_ascii_to_text(char **bufptr, concptr *strptr)
263 {
264     char *s = *bufptr;
265     concptr str = *strptr;
266     char key_code[100];
267     int i;
268     if (macro_template == nullptr) {
269         return false;
270     }
271
272     *s++ = '\\';
273     *s++ = '[';
274
275     concptr tmp;
276     for (i = 0; macro_template[i]; i++) {
277         char ch = macro_template[i];
278
279         switch (ch) {
280         case '&':
281             while ((tmp = angband_strchr(macro_modifier_chr, *str)) != 0) {
282                 int j = (int)(tmp - macro_modifier_chr);
283                 tmp = macro_modifier_name[j];
284                 while (*tmp) {
285                     *s++ = *tmp++;
286                 }
287                 str++;
288             }
289
290             break;
291         case '#': {
292             int j;
293             for (j = 0; *str && *str != '\r'; j++) {
294                 key_code[j] = *str++;
295             }
296             key_code[j] = '\0';
297             break;
298         }
299         default:
300             if (ch != *str) {
301                 return false;
302             }
303             str++;
304         }
305     }
306
307     if (*str++ != '\r') {
308         return false;
309     }
310
311     for (i = 0; i < max_macrotrigger; i++) {
312         if (!angband_stricmp(key_code, macro_trigger_keycode[0][i]) || !angband_stricmp(key_code, macro_trigger_keycode[1][i])) {
313             break;
314         }
315     }
316
317     if (i == max_macrotrigger) {
318         return false;
319     }
320
321     tmp = macro_trigger_name[i];
322     while (*tmp) {
323         *s++ = *tmp++;
324     }
325
326     *s++ = ']';
327
328     *bufptr = s;
329     *strptr = str;
330     return true;
331 }
332
333 /*
334  * Hack -- convert a string into a printable form
335  */
336 void ascii_to_text(char *buf, std::string_view sv, size_t bufsize)
337 {
338     char *s = buf;
339     auto buffer_end = s + bufsize;
340     auto str = sv.data();
341     constexpr auto step_size = 4;
342     while (*str && (s + step_size < buffer_end)) {
343         byte i = (byte)(*str++);
344         if (i == 31) {
345             if (!trigger_ascii_to_text(&s, &str)) {
346                 *s++ = '^';
347                 *s++ = '_';
348             }
349         } else {
350             if (i == ESCAPE) {
351                 *s++ = '\\';
352                 *s++ = 'e';
353             } else if (i == ' ') {
354                 *s++ = '\\';
355                 *s++ = 's';
356             } else if (i == '\b') {
357                 *s++ = '\\';
358                 *s++ = 'b';
359             } else if (i == '\t') {
360                 *s++ = '\\';
361                 *s++ = 't';
362             } else if (i == '\n') {
363                 *s++ = '\\';
364                 *s++ = 'n';
365             } else if (i == '\r') {
366                 *s++ = '\\';
367                 *s++ = 'r';
368             } else if (i == '^') {
369                 *s++ = '\\';
370                 *s++ = '^';
371             } else if (i == '\\') {
372                 *s++ = '\\';
373                 *s++ = '\\';
374             } else if (i < 32) {
375                 *s++ = '^';
376                 *s++ = i + 64;
377             } else if (i < 127) {
378                 *s++ = i;
379             } else if (i < 64) {
380                 *s++ = '\\';
381                 *s++ = '0';
382                 *s++ = octify(i / 8);
383                 *s++ = octify(i % 8);
384             } else {
385                 *s++ = '\\';
386                 *s++ = 'x';
387                 *s++ = hexify(i / 16);
388                 *s++ = hexify(i % 16);
389             }
390         }
391     }
392
393     *s = '\0';
394 }
395
396 /*
397  * The angband_strcpy() function copies up to 'bufsize'-1 characters from 'src'
398  * to 'buf' and NUL-terminates the result.  The 'buf' and 'src' strings may
399  * not overlap.
400  *
401  * angband_strcpy() returns strlen(src).  This makes checking for truncation
402  * easy.  Example: if (angband_strcpy(buf, src, sizeof(buf)) >= sizeof(buf)) ...;
403  *
404  * This function should be equivalent to the strlcpy() function in BSD.
405  */
406 size_t angband_strcpy(char *buf, std::string_view src, size_t bufsize)
407 {
408 #ifdef JP
409     char *d = buf;
410     const char *s = src.data();
411     size_t len = 0;
412
413     if (bufsize > 0) {
414         /* reserve for NUL termination */
415         bufsize--;
416
417         /* Copy as many bytes as will fit */
418         while (*s && (len < bufsize)) {
419             if (iskanji(*s)) {
420                 if (len + 1 >= bufsize || !*(s + 1)) {
421                     break;
422                 }
423                 *d++ = *s++;
424                 *d++ = *s++;
425                 len += 2;
426             } else {
427                 *d++ = *s++;
428                 len++;
429             }
430         }
431         *d = '\0';
432     }
433
434     while (*s++) {
435         len++;
436     }
437     return len;
438
439 #else
440     auto len = src.length();
441     if (bufsize == 0) {
442         return len;
443     }
444
445     if (len >= bufsize) {
446         len = bufsize - 1;
447     }
448
449     (void)src.copy(buf, len);
450     buf[len] = '\0';
451     return src.length();
452 #endif
453 }
454
455 /*
456  * The angband_strcat() tries to append a string to an existing NUL-terminated string.
457  * It never writes more characters into the buffer than indicated by 'bufsize' and
458  * NUL-terminates the buffer.  The 'buf' and 'src' strings may not overlap.
459  *
460  * angband_strcat() returns strlen(buf) + strlen(src).  This makes checking for
461  * truncation easy.  Example:
462  * if (angband_strcat(buf, src, sizeof(buf)) >= sizeof(buf)) ...;
463  *
464  * This function should be equivalent to the strlcat() function in BSD.
465  */
466 size_t angband_strcat(char *buf, std::string_view src, size_t bufsize)
467 {
468     size_t dlen = strlen(buf);
469     if (dlen < bufsize - 1) {
470         return dlen + angband_strcpy(buf + dlen, src, bufsize - dlen);
471     } else {
472         return dlen + src.length();
473     }
474 }
475
476 /*
477  * A copy of ANSI strstr()
478  *
479  * angband_strstr() can handle Kanji strings correctly.
480  */
481 char *angband_strstr(const char *haystack, std::string_view needle)
482 {
483     std::string_view haystack_view(haystack);
484     auto l1 = haystack_view.length();
485     auto l2 = needle.length();
486     if (l1 < l2) {
487         return nullptr;
488     }
489
490     for (size_t i = 0; i <= l1 - l2; i++) {
491         const auto part = haystack_view.substr(i);
492         if (part.starts_with(needle)) {
493             return const_cast<char *>(haystack) + i;
494         }
495
496 #ifdef JP
497         if (iskanji(*(haystack + i))) {
498             i++;
499         }
500 #endif
501     }
502
503     return nullptr;
504 }
505
506 /*
507  * A copy of ANSI strchr()
508  *
509  * angband_strchr() can handle Kanji strings correctly.
510  */
511 char *angband_strchr(concptr ptr, char ch)
512 {
513     for (; *ptr != '\0'; ptr++) {
514         if (*ptr == ch) {
515             return (char *)ptr;
516         }
517
518 #ifdef JP
519         if (iskanji(*ptr)) {
520             ptr++;
521         }
522 #endif
523     }
524
525     return nullptr;
526 }
527
528 /*!
529  * @brief 左側の空白を除去
530  * @param p char型のポインタ
531  * @return 除去後のポインタ
532  */
533 char *ltrim(char *p)
534 {
535     while (p[0] == ' ') {
536         p++;
537     }
538     return p;
539 }
540
541 /*!
542  * @brief 右側の空白を除去
543  * @param p char型のポインタ
544  * @return 除去後のポインタ
545  */
546 char *rtrim(char *p)
547 {
548     int i = strlen(p) - 1;
549     while (p[i] == ' ') {
550         p[i--] = '\0';
551     }
552     return p;
553 }
554
555 /*!
556  * @brief 文字列の後方から一致するかどうか比較する
557  * @param s1 比較元文字列ポインタ
558  * @param s2 比較先文字列ポインタ
559  * @param len 比較する長さ
560  * @return 等しい場合は0、p1が大きい場合は-1、p2が大きい場合は1
561  * @details
562  * strncmpの後方から比較する版
563  */
564 int strrncmp(const char *s1, const char *s2, int len)
565 {
566     int i;
567     int l1 = strlen(s1);
568     int l2 = strlen(s2);
569
570     for (i = 1; i <= len; i++) {
571         int p1 = l1 - i;
572         int p2 = l2 - i;
573
574         if (l1 != l2) {
575             if (p1 < 0) {
576                 return -1;
577             }
578             if (p2 < 0) {
579                 return 1;
580             }
581         } else {
582             if (p1 < 0) {
583                 return 0;
584             }
585         }
586
587         if (s1[p1] < s2[p2]) {
588             return -1;
589         }
590         if (s1[p1] > s2[p2]) {
591             return -1;
592         }
593     }
594
595     return 0;
596 }
597
598 /*
599  * @brief マルチバイト文字のダメ文字('\')を考慮しつつ文字列比較を行う
600  * @param src 比較元の文字列
601  * @param find 比較したい文字列
602  */
603 bool str_find(const std::string &src, std::string_view find)
604 {
605     return angband_strstr(src.data(), find) != nullptr;
606 }
607
608 /**
609  * @brief 文字列の両端の空白を削除する
610  *
611  * 文字列 str の両端にある空白(スペースおよびタブ)を削除し、
612  * 削除した文字列を std::string 型のオブジェクトとして返す。
613  * 文字列全体が空白の場合は空文字列を返す。
614  *
615  * @param str 操作の対象とする文字列
616  * @return std::string strの両端の空白を削除した文字列
617  */
618 std::string str_trim(std::string_view str)
619 {
620     const auto start_pos = str.find_first_not_of(" \t");
621     const auto end_pos = str.find_last_not_of(" \t");
622
623     if (start_pos == std::string_view::npos || end_pos == std::string_view::npos) {
624         return std::string();
625     }
626
627     return std::string(str.substr(start_pos, end_pos - start_pos + 1));
628 }
629
630 /**
631  * @brief 文字列の右端の空白を削除する
632  *
633  * 文字列 str の右端にある空白(スペースおよびタブ)を削除し、
634  * 削除した文字列を std::string 型のオブジェクトとして返す。
635  * 文字列全体が空白の場合は空文字列を返す。
636  *
637  * @param str 操作の対象とする文字列
638  * @return std::string strの右端の空白を削除した文字列
639  */
640 std::string str_rtrim(std::string_view str)
641 {
642     const auto end_pos = str.find_last_not_of(" \t");
643
644     if (end_pos == std::string_view::npos) {
645         return std::string();
646     }
647
648     return std::string(str.substr(0, end_pos + 1));
649 }
650
651 /**
652  * @brief 文字列の左端の空白を削除する
653  *
654  * 文字列 str の左端にある空白(スペースおよびタブ)を削除し、
655  * 削除した文字列を std::string 型のオブジェクトとして返す。
656  * 文字列全体が空白の場合は空文字列を返す。
657  *
658  * @param str 操作の対象とする文字列
659  * @return std::string strの左端の空白を削除した文字列
660  */
661 std::string str_ltrim(std::string_view str)
662 {
663     const auto start_pos = str.find_first_not_of(" \t");
664
665     if (start_pos == std::string_view::npos) {
666         return std::string();
667     }
668
669     return std::string(str.substr(start_pos));
670 }
671
672 /**
673  * @brief 文字列を指定した文字で分割する
674  *
675  * 文字列 str を delim で指定した文字で分割し、分割した文字列を要素とする配列を
676  * std::vector<std::string> 型のオブジェクトとして返す。
677  *
678  * @param str 操作の対象とする文字列
679  * @param delim 文字列を分割する文字
680  * @param trim trueの場合、分割した文字列の両端の空白を削除する
681  * @param num 1以上の場合、事前にvectorサイズを予約しておく(速度向上)
682  * @return std::vector<std::string> 分割した文字列を要素とする配列
683  */
684 std::vector<std::string> str_split(std::string_view str, char delim, bool trim, int num)
685 {
686     std::vector<std::string> result;
687     if (num > 0) {
688         result.reserve(num);
689     }
690
691     auto make_str = [trim](std::string_view sv) { return trim ? str_trim(sv) : std::string(sv); };
692
693     while (true) {
694         bool found = false;
695         for (size_t i = 0; i < str.size(); ++i) {
696             if (str[i] == delim) {
697                 result.push_back(make_str(str.substr(0, i)));
698                 str.remove_prefix(i + 1);
699                 found = true;
700                 break;
701             }
702 #ifdef JP
703             if (iskanji(str[i])) {
704                 ++i;
705             }
706 #endif
707         }
708         if (!found) {
709             result.push_back(make_str(str));
710             return result;
711         }
712     }
713 }
714
715 /**
716  * @brief 文字列を指定した文字数で分割する
717  *
718  * 文字列 str を len で指定した文字数で分割し、分割した文字列を要素とする配列を
719  * std::vector<std::string> 型のオブジェクトとして返す。
720  * 全角文字は2文字分として扱い、全角文字の前半で分割されてしまわないように処理される。
721  *
722  * @param str 操作の対象とする文字列
723  * @param len 分割する文字数
724  * @return std::vector<std::string> 分割した文字列を要素とする配列
725  */
726 std::vector<std::string> str_separate(std::string_view str, size_t len)
727 {
728     std::vector<std::string> result;
729
730     while (!str.empty()) {
731         result.push_back(str_substr(str, 0, len));
732         str.remove_prefix(result.back().size());
733     }
734
735     return result;
736 }
737
738 /**
739  * @brief 文字列から指定した文字を取り除く
740  *
741  * 文字列 str から文字列 erase_chars に含まれる文字をすべて削除し、
742  * 削除した文字列を std::string 型のオブジェクトとして返す。
743  *
744  * @param str 操作の対象とする文字列
745  * @param erase_chars 削除する文字を指定する文字列
746  * @return std::string 指定した文字をすべて削除した文字列
747  */
748 std::string str_erase(std::string str, std::string_view erase_chars)
749 {
750     for (auto it = str.begin(); it != str.end();) {
751         if (erase_chars.find(*it) != std::string_view::npos) {
752             it = str.erase(it);
753             continue;
754         }
755 #ifdef JP
756         if (iskanji(*it)) {
757             ++it;
758         }
759 #endif
760         ++it;
761     }
762
763     return str;
764 }
765
766 static std::pair<size_t, size_t> adjust_substr_pos(std::string_view sv, size_t pos, size_t n)
767 {
768     const auto start = std::min(pos, sv.length());
769     const auto end = n == std::string_view::npos ? sv.length() : std::min(pos + n, sv.length());
770
771 #ifdef JP
772     auto seek_pos = 0U;
773     while (seek_pos < start) {
774         if (iskanji(sv[seek_pos])) {
775             ++seek_pos;
776         }
777         ++seek_pos;
778     }
779     const auto mb_pos = seek_pos;
780
781     while (seek_pos < end) {
782         if (iskanji(sv[seek_pos])) {
783             if (seek_pos == end - 1) {
784                 break;
785             }
786             ++seek_pos;
787         }
788         ++seek_pos;
789     }
790     const auto mb_n = seek_pos - mb_pos;
791
792     return { mb_pos, mb_n };
793 #else
794     return { start, end - start };
795 #endif
796 }
797
798 /*!
799  * @brief 2バイト文字を考慮して部分文字列を取得する
800  *
801  * 引数で与えられた文字列から pos バイト目から n バイトの部分文字列を取得する。
802  * 但し、以下の通り2バイト文字の途中で分断されないようにする。
803  * - 開始位置 pos バイト目が2バイト文字の後半バイトの場合は pos+1 バイト目を開始位置とする。
804  * - 終了位置 pos+n バイト目が2バイト文字の前半バイトの場合は pos+n-1 バイト目を終了位置とする。
805  *
806  * @param sv 文字列
807  * @param pos 部分文字列の開始位置
808  * @param n 部分文字列の長さ
809  * @return 部分文字列
810  */
811 std::string str_substr(std::string_view sv, size_t pos, size_t n)
812 {
813     const auto [mb_pos, mb_n] = adjust_substr_pos(sv, pos, n);
814     return std::string(sv.substr(mb_pos, mb_n));
815 }
816
817 /*!
818  * @brief 2バイト文字を考慮して部分文字列を取得する
819  *
820  * 引数で与えられた文字列を pos バイト目から n バイトの部分文字列にして返す。
821  * 但し、以下の通り2バイト文字の途中で分断されないようにする。
822  * - 開始位置 pos バイト目が2バイト文字の後半バイトの場合は pos+1 バイト目を開始位置とする。
823  * - 終了位置 pos+n バイト目が2バイト文字の前半バイトの場合は pos+n-1 バイト目を終了位置とする。
824  *
825  * @param str 文字列
826  * @param pos 部分文字列の開始位置
827  * @param n 部分文字列の長さ
828  * @return 部分文字列
829  */
830 std::string str_substr(std::string &&str, size_t pos, size_t n)
831 {
832     const auto [mb_pos, mb_n] = adjust_substr_pos(str, pos, n);
833     str.erase(mb_pos + mb_n);
834     str.erase(0, mb_pos);
835     return std::move(str);
836 }
837
838 std::string str_substr(const char *str, size_t pos, size_t n)
839 {
840     return str_substr(std::string_view(str), pos, n);
841 }