Add the mbpad function.

This commit is contained in:
Dianne Skoll
2025-12-24 16:05:25 -05:00
parent 14b6e23eaa
commit 93316d754c
5 changed files with 149 additions and 3 deletions

View File

@@ -4529,8 +4529,8 @@ any longer.
.TP
.B pad(x_arg, s_padstr, i_len [, i_right])
Converts the first argument \fIarg\fR to a string if necessary, and
then if it is shorter than \fIlen\fR characters, pads to to
\fIlen\fR characters using as many copies (including partial copies)
then if it is shorter than \fIlen\fR bytes, pads to to
\fIlen\fR bytes using as many copies (including partial copies)
of \fIpadstr\fR as necessary. By default, the string is left-padded,
but if \fIright\fR is supplied and non-zero, the string will
be right-padded.
@@ -4547,6 +4547,11 @@ Here are some examples:
.fi
.RE
.TP
.B mbpad(x_arg, s_padstr, i_len [, i_right])
This is the multibyte counterpart to \fBpad\fR. The length is
specified in characters rather than bytes. Use \fBmbpad\fR rather
than \fBpad\fR if either of the strings contains non-ASCII characters.
.TP
.B plural(i_num [,s_str1 [,s_str2]])
Can take from one to three arguments. If one argument is supplied, returns

View File

@@ -140,6 +140,7 @@ static int FLower (func_info *);
static int FMax (func_info *);
static int FMbchar (func_info *);
static int FMbindex (func_info *);
static int FMbpad (func_info *);
static int FMbstrlen (func_info *);
static int FMbsubstr (func_info *);
static int FMin (func_info *);
@@ -326,6 +327,7 @@ BuiltinFunc Func[] = {
{ "max", 1, NO_MAX, 1, FMax, NULL },
{ "mbchar", 1, NO_MAX, 1, FMbchar, NULL },
{ "mbindex", 2, 3, 1, FMbindex, NULL },
{ "mbpad", 3, 4, 1, FMbpad, NULL },
{ "mbstrlen", 1, 1, 1, FMbstrlen, NULL },
{ "mbsubstr", 2, 3, 1, FMbsubstr, NULL },
{ "min", 1, NO_MAX, 1, FMin, NULL },
@@ -1430,6 +1432,125 @@ static int FPad(func_info *info)
return r;
}
/***************************************************************/
/* */
/* FMbpad - multibyte version of Fpad */
/* */
/***************************************************************/
static int FMbpad(func_info *info)
{
int r;
wchar_t *s;
wchar_t *d;
size_t len;
size_t len2;
size_t wantlen;
size_t i;
wchar_t *src;
wchar_t *pad;
wchar_t *dest;
char *result;
ASSERT_TYPE(1, STR_TYPE);
ASSERT_TYPE(2, INT_TYPE);
if (Nargs == 4) {
ASSERT_TYPE(3, INT_TYPE);
}
if (ARG(0).type != STR_TYPE) {
r = DoCoerce(STR_TYPE, &ARG(0));
if (r != OK) return r;
}
wantlen = ARGV(2);
/* Convert ARGV(0) and ARGV(1) to wide-char strings */
len = mbstowcs(NULL, ARGSTR(0), 0);
if (len == (size_t) -1) {
return E_BAD_MB_SEQ;
}
if (len >= wantlen) {
DCOPYVAL(RetVal, ARG(0));
return OK;
}
if (strlen(ARGSTR(1)) == 0) {
return E_BAD_TYPE;
}
if (MaxStringLen > 0 && wantlen > (size_t) MaxStringLen) {
return E_STRING_TOO_LONG;
}
src = calloc(len+1, sizeof(wchar_t));
if (!src) {
return E_NO_MEM;
}
(void) mbstowcs(src, ARGSTR(0), len+1);
len2 = mbstowcs(NULL, ARGSTR(1), 0);
if (len == (size_t) -1) {
free(src);
return E_BAD_MB_SEQ;
}
pad = calloc(len2+1, sizeof(wchar_t));
if (!pad) {
free(src);
return E_NO_MEM;
}
(void) mbstowcs(pad, ARGSTR(1), len2+1);
dest = calloc(wantlen+1, sizeof(wchar_t));
if (!dest) {
free(src);
free(pad);
return E_NO_MEM;
}
d = dest;
if (Nargs < 4 || !ARGV(3)) {
/* Pad on the LEFT */
s = pad;
for (i=0; i<wantlen-len; i++) {
*d++ = *s++;
if (!*s) s = pad;
}
s = src;
while (*s) {
*d++ = *s++;
}
} else {
s = src;
while (*s) {
*d++ = *s++;
}
s = pad;
for (i=0; i<wantlen-len; i++) {
*d++ = *s++;
if (!*s) s = pad;
}
}
len = wcstombs(NULL, dest, 0);
result = calloc(len+1, 1);
if (!result) {
free(src);
free(pad);
free(dest);
return E_NO_MEM;
}
(void) wcstombs(result, dest, len+1);
r = RetStrVal(result, info);
free(result);
free(src);
free(pad);
free(dest);
return r;
}
/***************************************************************/
/* */

View File

@@ -681,6 +681,7 @@ maxlen
maybexs
mbchar
mbindex
mbpad
mbstrlen
mbsubstr
md

View File

@@ -16711,6 +16711,14 @@ hebmon(1991-07-12) => "Av"
ivritmon(1991-07-12) => "אב"
hebmon(1991-08-11) => "Elul"
ivritmon(1991-08-11) => "אלול"
mbpad("foo", "bar", 3) => "foo"
mbpad("foo", "bar", 3, 1) => "foo"
mbpad("foo", "bar", 8) => "barbafoo"
mbpad("foo", "bar", 8, 1) => "foobarba"
mbpad("foo", "🙂💩", 3) => "foo"
mbpad("foo", "🙂💩", 3, 1) => "foo"
mbpad("foo", "🙂💩", 8) => "🙂💩🙂💩🙂foo"
mbpad("foo", "🙂💩", 8, 1) => "foo🙂💩🙂💩🙂"
Variable hash table statistics:
Entries: 100146; Buckets: 87719; Non-empty Buckets: 66303
Maxlen: 5; Minlen: 0; Avglen: 1.142; Stddev: 0.878; Avg nonempty len: 1.510
@@ -16732,7 +16740,7 @@ Expression nodes high-water: 302076
Expression nodes leaked: 0
Parse level high-water: 34
Max expr node evaluations per line: 2001
Total expression node evaluations: 106607
Total expression node evaluations: 106643
Test 2
@@ -24717,6 +24725,7 @@ lower
max
mbchar
mbindex
mbpad
mbstrlen
mbsubstr
min

View File

@@ -1853,6 +1853,16 @@ set a ivritmon('1991-07-12')
set a hebmon('1991-08-11')
set a ivritmon('1991-08-11')
# mbpad
set a mbpad("foo", "bar", 3)
set a mbpad("foo", "bar", 3, 1)
set a mbpad("foo", "bar", 8)
set a mbpad("foo", "bar", 8, 1)
set a mbpad("foo", "🙂💩", 3)
set a mbpad("foo", "🙂💩", 3, 1)
set a mbpad("foo", "🙂💩", 8)
set a mbpad("foo", "🙂💩", 8, 1)
DEBUG -x
# Don't want Remind to queue reminders
EXIT