Add mblower and mbupper functions.

These are Unicode-safe versions of lower() and upper()
This commit is contained in:
Dianne Skoll
2026-02-05 12:41:36 -05:00
parent c0c49be0b5
commit ea2312c0b2
6 changed files with 230 additions and 24 deletions

View File

@@ -167,26 +167,151 @@
(defconst remind-builtin-functions (defconst remind-builtin-functions
(sort (sort
(list "_" "abs" "access" "adawn" "adusk" "ampm" "ansicolor" "args" "asc" (list
"baseyr" "catch" "catcherr" "char" "choose" "codepoint" "coerce" "columns" "const" "current" "date" "_"
"datepart" "datetime" "dawn" "day" "daysinmon" "defined" "dosubst" "abs"
"dusk" "easterdate" "escape" "eval" "evaltrig" "filedate" "filedatetime" "access"
"filedir" "filename" "getenv" "hebdate" "hebday" "hebmon" "hebyear" "hex" "adawn"
"hour" "htmlescape" "htmlstriptags" "iif" "index" "isany" "isconst" "isdst" "adusk"
"isleap" "isomitted" "ivritmon" "language" "localtoutc" "lower" "max" "ampm"
"mbasc" "mbindex" "mbstrlen" "mbsubstr" "min" "ansicolor"
"minsfromutc" "minute" "mon" "monnum" "moondate" "moondatetime" "args"
"moonphase" "moonrise" "moonrisedir" "moonset" "moonsetdir" "moontime" "asc"
"multitrig" "ndawn" "ndusk" "nonconst" "nonomitted" "now" "ord" "orthodoxeaster" "baseyr"
"ostype" "pad" "plural" "psmoon" "psshade" "realcurrent" "realnow" "catch"
"realtoday" "rows" "sgn" "shell" "shellescape" "slide" "soleq" "catcherr"
"stdout" "strlen" "substr" "sunrise" "sunset" "time" "timepart" "char"
"timezone" "today" "trig" "trigback" "trigbase" "trigcompletethrough" "trigdate" "trigdatetime" "choose"
"trigdelta" "trigduration" "trigeventduration" "trigeventstart" "trigeventstarttz" "codepoint"
"trigfrom" "trigger" "triginfo" "trigistodo" "trigmaxoverdue" "trigpriority" "trigrep" "coerce"
"trigscanfrom" "trigtags" "trigtime" "trigtimedelta" "trigtimerep" "trigtimetz" "trigtz" "columns"
"triguntil" "trigvalid" "typeof" "tzconvert" "upper" "utctolocal" "const"
"value" "version" "weekno" "wkday" "wkdaynum" "year" "current"
"date"
"datepart"
"datetime"
"dawn"
"day"
"daysinmon"
"defined"
"dosubst"
"dusk"
"easterdate"
"escape"
"eval"
"evaltrig"
"filedate"
"filedatetime"
"filedir"
"filename"
"getenv"
"hebdate"
"hebday"
"hebmon"
"hebyear"
"hex"
"hour"
"htmlescape"
"htmlstriptags"
"iif"
"index"
"isany"
"isconst"
"isdst"
"isleap"
"isomitted"
"ivritmon"
"language"
"localtoutc"
"lower"
"max"
"mbasc"
"mbindex"
"mblower"
"mbstrlen"
"mbsubstr"
"mbupper"
"min"
"minsfromutc"
"minute"
"mon"
"monnum"
"moondate"
"moondatetime"
"moonphase"
"moonrise"
"moonrisedir"
"moonset"
"moonsetdir"
"moontime"
"multitrig"
"ndawn"
"ndusk"
"nonconst"
"nonomitted"
"now"
"ord"
"orthodoxeaster"
"ostype"
"pad"
"plural"
"psmoon"
"psshade"
"realcurrent"
"realnow"
"realtoday"
"rows"
"sgn"
"shell"
"shellescape"
"slide"
"soleq"
"stdout"
"strlen"
"substr"
"sunrise"
"sunset"
"time"
"timepart"
"timezone"
"today"
"trig"
"trigback"
"trigbase"
"trigcompletethrough"
"trigdate"
"trigdatetime"
"trigdelta"
"trigduration"
"trigeventduration"
"trigeventstart"
"trigeventstarttz"
"trigfrom"
"trigger"
"triginfo"
"trigistodo"
"trigmaxoverdue"
"trigpriority"
"trigrep"
"trigscanfrom"
"trigtags"
"trigtime"
"trigtimedelta"
"trigtimerep"
"trigtimetz"
"trigtz"
"triguntil"
"trigvalid"
"typeof"
"tzconvert"
"upper"
"utctolocal"
"value"
"version"
"weekno"
"wkday"
"wkdaynum"
"year"
) )
#'(lambda (a b) (> (length a) (length b))))) #'(lambda (a b) (> (length a) (length b)))))

View File

@@ -4224,7 +4224,14 @@ a \fBDATETIME\fR object that expresses the same time in UTC.
.TP .TP
.B lower(s_string) .B lower(s_string)
Returns a \fBSTRING\fR with all upper-case characters in \fIstring\fR Returns a \fBSTRING\fR with all upper-case characters in \fIstring\fR
converted to lower-case. converted to lower-case. \fBNote:\fR This function works correctly
only for ASCII strings. If you are using Unicode characters outside
the ASCII set, use \fBmblower\fR instead.
.TP
.B mblower(s_string)
Returns a \fBSTRING\fR with all upper-case characters in \fIstring\fR
converted to lower-case. This function works correctly on any
Unicode string.
.TP .TP
.B max(x_arg1 [,x_arg2...) .B max(x_arg1 [,x_arg2...)
Can take any number of arguments, and returns the maximum. The arguments Can take any number of arguments, and returns the maximum. The arguments
@@ -5133,8 +5140,15 @@ of the time zone name.
.PP .PP
.TP .TP
.B upper(s_string) .B upper(s_string)
Returns a \fBSTRING\fR with all lower-case bytes in \fIstring\fR
converted to upper-case. \fBNote:\fR This function works correctly
only for ASCII strings. If you are using Unicode characters outside
the ASCII set, use \fBmbupper\fR instead.
.TP
.B mbupper(s_string)
Returns a \fBSTRING\fR with all lower-case characters in \fIstring\fR Returns a \fBSTRING\fR with all lower-case characters in \fIstring\fR
converted to upper-case. converted to upper-case. This function works correctly on any
Unicode string.
.TP .TP
.B utctolocal(q_datetime) .B utctolocal(q_datetime)
Given a \fBDATETIME\fR object interpreted in UTC, return a Given a \fBDATETIME\fR object interpreted in UTC, return a

View File

@@ -140,9 +140,11 @@ static int FLower (func_info *);
static int FMax (func_info *); static int FMax (func_info *);
static int FMbchar (func_info *); static int FMbchar (func_info *);
static int FMbindex (func_info *); static int FMbindex (func_info *);
static int FMblower (func_info *);
static int FMbpad (func_info *); static int FMbpad (func_info *);
static int FMbstrlen (func_info *); static int FMbstrlen (func_info *);
static int FMbsubstr (func_info *); static int FMbsubstr (func_info *);
static int FMbupper (func_info *);
static int FMin (func_info *); static int FMin (func_info *);
static int FMinsfromutc (func_info *); static int FMinsfromutc (func_info *);
static int FMinute (func_info *); static int FMinute (func_info *);
@@ -327,9 +329,11 @@ BuiltinFunc Func[] = {
{ "max", 1, NO_MAX, 1, FMax, NULL }, { "max", 1, NO_MAX, 1, FMax, NULL },
{ "mbchar", 1, NO_MAX, 1, FMbchar, NULL }, { "mbchar", 1, NO_MAX, 1, FMbchar, NULL },
{ "mbindex", 2, 3, 1, FMbindex, NULL }, { "mbindex", 2, 3, 1, FMbindex, NULL },
{ "mblower", 1, 1, 1, FMblower, NULL },
{ "mbpad", 3, 4, 1, FMbpad, NULL }, { "mbpad", 3, 4, 1, FMbpad, NULL },
{ "mbstrlen", 1, 1, 1, FMbstrlen, NULL }, { "mbstrlen", 1, 1, 1, FMbstrlen, NULL },
{ "mbsubstr", 2, 3, 1, FMbsubstr, NULL }, { "mbsubstr", 2, 3, 1, FMbsubstr, NULL },
{ "mbupper", 1, 1, 1, FMbupper, NULL },
{ "min", 1, NO_MAX, 1, FMin, NULL }, { "min", 1, NO_MAX, 1, FMin, NULL },
{ "minsfromutc", 0, 2, 0, FMinsfromutc, NULL }, { "minsfromutc", 0, 2, 0, FMinsfromutc, NULL },
{ "minute", 1, 1, 1, FMinute, NULL }, { "minute", 1, 1, 1, FMinute, NULL },
@@ -5173,3 +5177,50 @@ print_builtinfunc_tokens(void)
printf("%s\n", Func[i].name); printf("%s\n", Func[i].name);
} }
} }
static int mbupper_lower(func_info *info, int upper)
{
wchar_t *ws;
char *s;
size_t i, len;
int r;
ASSERT_TYPE(0, STR_TYPE);
len = mbstowcs(NULL, ARGSTR(0), 0);
if (len == (size_t) -1) {
return E_BAD_MB_SEQ;
}
ws = calloc(len+1, sizeof(wchar_t));
if (!ws) {
return E_NO_MEM;
}
(void) mbstowcs(ws, ARGSTR(0), len+1);
for (i=0; i<len; i++) {
if (upper) {
ws[i] = towupper(ws[i]);
} else {
ws[i] = towlower(ws[i]);
}
}
len = wcstombs(NULL, ws, 0);
s = calloc(len+1, 1);
if (!s) {
free(ws);
return E_NO_MEM;
}
(void) wcstombs(s, ws, len+1);
r = RetStrVal(s, info);
free(s);
free(ws);
return r;
}
static int FMblower(func_info *info)
{
return mbupper_lower(info, 0);
}
static int FMbupper(func_info *info)
{
return mbupper_lower(info, 1);
}

View File

@@ -687,10 +687,12 @@ maxlen
maybexs maybexs
mbchar mbchar
mbindex mbindex
mblower
mbpad mbpad
mbstowcs mbstowcs
mbstrlen mbstrlen
mbsubstr mbsubstr
mbupper
md md
memcpy memcpy
messageBox messageBox

View File

@@ -16767,7 +16767,11 @@ mbpad("
../tests/test.rem(1902): Invalid value for system variable ../tests/test.rem(1902): Invalid value for system variable
../tests/test.rem(1903): Invalid value for system variable ../tests/test.rem(1903): Invalid value for system variable
../tests/test.rem(1904): Invalid value for system variable ../tests/test.rem(1904): Invalid value for system variable
DynBuf Mallocs: 1124 mallocs; 31872896 bytes mbupper("öÖçÇéôñÑÉÊ") => "ÖÖÇÇÉÔÑÑÉÊ"
mblower("öÖçÇéôñÑÉÊ") => "ööççéôññéê"
upper("öÖçÇéôñÑÉÊ") => "öÖçÇéôñÑÉÊ"
lower("öÖçÇéôñÑÉÊ") => "öÖçÇéôñÑÉÊ"
DynBuf Mallocs: 1132 mallocs; 31873408 bytes
Variable hash table statistics: Variable hash table statistics:
Entries: 100146; Buckets: 87719; Non-empty Buckets: 66303 Entries: 100146; Buckets: 87719; Non-empty Buckets: 66303
Maxlen: 5; Minlen: 0; Avglen: 1.142; Stddev: 0.878; Avg nonempty len: 1.510 Maxlen: 5; Minlen: 0; Avglen: 1.142; Stddev: 0.878; Avg nonempty len: 1.510
@@ -16789,7 +16793,7 @@ Expression nodes high-water: 302076
Expression nodes leaked: 0 Expression nodes leaked: 0
Parse level high-water: 34 Parse level high-water: 34
Max expr node evaluations per line: 2001 Max expr node evaluations per line: 2001
Total expression node evaluations: 106738 Total expression node evaluations: 106746
Test 2 Test 2
@@ -24776,9 +24780,11 @@ lower
max max
mbchar mbchar
mbindex mbindex
mblower
mbpad mbpad
mbstrlen mbstrlen
mbsubstr mbsubstr
mbupper
min min
minsfromutc minsfromutc
minute minute

View File

@@ -1903,6 +1903,14 @@ SET $TimeSep "FOO"
SET $DateSep "BAR" SET $DateSep "BAR"
SET $DefaultColor "My oh my, what lovely eyes!" SET $DefaultColor "My oh my, what lovely eyes!"
# mbupper and mblower
DEBUG +x
SET a mbupper("öÖçÇéôñÑÉÊ")
SET a mblower("öÖçÇéôñÑÉÊ")
SET a upper("öÖçÇéôñÑÉÊ")
SET a lower("öÖçÇéôñÑÉÊ")
DEBUG -x
# Don't want Remind to queue reminders # Don't want Remind to queue reminders
EXIT EXIT