Compare commits

...

31 Commits

Author SHA1 Message Date
Dianne Skoll
23081b556f Update WHATSNEW 2025-09-19 13:42:50 -04:00
Dianne Skoll
e2cfa46289 Update version to 06.01.02 2025-09-19 13:41:03 -04:00
Dianne Skoll
71384da875 Document default --max-expr-limit. 2025-09-19 13:39:55 -04:00
Dianne Skoll
af69f8779d Don't loop forever if a reminder moves backwards because of a time zone difference.
If we are not making progress, it's because the reminder has expired.
2025-09-19 13:33:23 -04:00
Dianne Skoll
131e77fa18 Use "unsigned long" to track expresison node evaluations. 2025-09-13 11:38:55 -04:00
Dianne Skoll
f153acf7ce Default max-expr-complexity to 10,000,000 2025-09-12 12:56:14 -04:00
Dianne Skoll
d75587fa7b Update change log. 2025-09-12 11:43:15 -04:00
Dianne Skoll
d9a2b15814 Bump version number to 06.01.01. 2025-09-12 11:33:15 -04:00
Dianne Skoll
aa090bf319 Add tests for PrintValue string truncation. 2025-09-12 11:22:51 -04:00
Dianne Skoll
dc6a229f5f Add some const qualifiers, as recommended by cppcheck. 2025-09-12 11:15:10 -04:00
Dianne Skoll
5a3840759a Make PrintValue truncate long strings on a multibyte-character boundary instead of potentially splitting a UTF-8 sequence. 2025-09-12 11:12:19 -04:00
Dianne Skoll
779174ae32 Proper font formatting. 2025-09-11 17:26:43 -04:00
Dianne Skoll
2f70b37d4c Be stricter about parsing hex constants. 2025-09-11 17:22:24 -04:00
Dianne Skoll
d15c8f106b Support hexadecimal integer constants in expressions; add hex() function. 2025-09-11 17:02:22 -04:00
Dianne Skoll
9d999a0074 Support hexacdecimal integer constants in expressions. 2025-09-11 16:50:13 -04:00
Dianne Skoll
097dda1750 Make asc() always return a number between 0 and 255. 2025-09-11 16:42:32 -04:00
Dianne Skoll
565bca4380 Add statistic for max expr node evaluations per line. 2025-09-11 12:04:32 -04:00
Dianne Skoll
adb38fe82e Fix off-by-one error. 2025-09-11 11:25:20 -04:00
Dianne Skoll
57545ddc3f Add test for --max-expr-complexity 2025-09-11 11:20:29 -04:00
Dianne Skoll
804cf14a78 Implement --max-expr-complexity 2025-09-11 11:11:11 -04:00
Dianne Skoll
641b5cec57 Mode docs on columns() vs mbstrlen() 2025-09-10 23:07:23 -04:00
Dianne Skoll
df53db19c4 Fix typo 2025-09-10 23:02:15 -04:00
Dianne Skoll
d9bf902153 Put #ifdef guards around wide char code. 2025-09-10 17:56:32 -04:00
Dianne Skoll
53a12de2f9 Fix typo 2025-09-10 17:54:26 -04:00
Dianne Skoll
fe2b34da68 Avoid memory leak. 2025-09-10 17:54:06 -04:00
Dianne Skoll
7e70ffe7f5 Fix typo 2025-09-10 17:43:59 -04:00
Dianne Skoll
55975154b1 Add codepoint() and mbchar() functions. 2025-09-10 17:33:57 -04:00
Dianne Skoll
3c6191ba61 Add tests where index() does not find the needle. 2025-09-10 13:48:28 -04:00
Dianne Skoll
b00cf9c5b7 Remove some unnecessary assignments. 2025-09-10 13:48:18 -04:00
Dianne Skoll
a6838802ad Use mbsubstr and mbstrlen. 2025-09-10 13:27:57 -04:00
Dianne Skoll
375576fcc5 Add better support for multi-byte character set.
The functions mbindex, mbstrlen and mbsubstr are just like
index, strlen and substr, but they use and return 1-based
character indexes rather than 1-based byte indexes.
2025-09-10 13:25:27 -04:00
20 changed files with 770 additions and 51 deletions

18
configure vendored
View File

@@ -1,6 +1,6 @@
#! /bin/sh
# Guess values for system-dependent variables and create Makefiles.
# Generated by GNU Autoconf 2.72 for remind 06.01.00.
# Generated by GNU Autoconf 2.72 for remind 06.01.02.
#
#
# Copyright (C) 1992-1996, 1998-2017, 2020-2023 Free Software Foundation,
@@ -601,8 +601,8 @@ MAKEFLAGS=
# Identity of this package.
PACKAGE_NAME='remind'
PACKAGE_TARNAME='remind'
PACKAGE_VERSION='06.01.00'
PACKAGE_STRING='remind 06.01.00'
PACKAGE_VERSION='06.01.02'
PACKAGE_STRING='remind 06.01.02'
PACKAGE_BUGREPORT=''
PACKAGE_URL='https://dianne.skoll.ca/projects/remind/'
@@ -1258,7 +1258,7 @@ if test "$ac_init_help" = "long"; then
# Omit some internal or obsolete options to make the list less imposing.
# This message is too long to be a string in the A/UX 3.1 sh.
cat <<_ACEOF
'configure' configures remind 06.01.00 to adapt to many kinds of systems.
'configure' configures remind 06.01.02 to adapt to many kinds of systems.
Usage: $0 [OPTION]... [VAR=VALUE]...
@@ -1320,7 +1320,7 @@ fi
if test -n "$ac_init_help"; then
case $ac_init_help in
short | recursive ) echo "Configuration of remind 06.01.00:";;
short | recursive ) echo "Configuration of remind 06.01.02:";;
esac
cat <<\_ACEOF
@@ -1408,7 +1408,7 @@ fi
test -n "$ac_init_help" && exit $ac_status
if $ac_init_version; then
cat <<\_ACEOF
remind configure 06.01.00
remind configure 06.01.02
generated by GNU Autoconf 2.72
Copyright (C) 2023 Free Software Foundation, Inc.
@@ -1871,7 +1871,7 @@ cat >config.log <<_ACEOF
This file contains any messages produced by compilers while
running configure, to aid debugging if configure makes a mistake.
It was created by remind $as_me 06.01.00, which was
It was created by remind $as_me 06.01.02, which was
generated by GNU Autoconf 2.72. Invocation command line was
$ $0$ac_configure_args_raw
@@ -4848,7 +4848,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
# report actual input values of CONFIG_FILES etc. instead of their
# values after options handling.
ac_log="
This file was extended by remind $as_me 06.01.00, which was
This file was extended by remind $as_me 06.01.02, which was
generated by GNU Autoconf 2.72. Invocation command line was
CONFIG_FILES = $CONFIG_FILES
@@ -4913,7 +4913,7 @@ ac_cs_config_escaped=`printf "%s\n" "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
ac_cs_config='$ac_cs_config_escaped'
ac_cs_version="\\
remind config.status 06.01.00
remind config.status 06.01.02
configured by $0, generated by GNU Autoconf 2.72,
with options \\"\$ac_cs_config\\"

View File

@@ -1,6 +1,6 @@
dnl Process this file with autoconf to produce a configure script.
AC_INIT(remind, 06.01.00, , , https://dianne.skoll.ca/projects/remind/)
AC_INIT(remind, 06.01.02, , , https://dianne.skoll.ca/projects/remind/)
AC_CONFIG_SRCDIR([src/queue.c])
cat <<'EOF'

View File

@@ -166,12 +166,13 @@
(defconst remind-builtin-functions
(sort
(list "_" "abs" "access" "adawn" "adusk" "ampm" "ansicolor" "args" "asc"
"baseyr" "catch" "catcherr" "char" "choose" "coerce" "columns" "const" "current" "date"
"baseyr" "catch" "catcherr" "char" "choose" "codepoint" "coerce" "columns" "const" "current" "date"
"datepart" "datetime" "dawn" "day" "daysinmon" "defined" "dosubst"
"dusk" "easterdate" "escape" "eval" "evaltrig" "filedate" "filedatetime"
"filedir" "filename" "getenv" "hebdate" "hebday" "hebmon" "hebyear"
"filedir" "filename" "getenv" "hebdate" "hebday" "hebmon" "hebyear" "hex"
"hour" "htmlescape" "htmlstriptags" "iif" "index" "isany" "isconst" "isdst"
"isleap" "isomitted" "language" "localtoutc" "lower" "max" "min"
"isleap" "isomitted" "language" "localtoutc" "lower" "max"
"mbasc" "mbindex" "mbstrlen" "mbsubstr" "min"
"minsfromutc" "minute" "mon" "monnum" "moondate" "moondatetime"
"moonphase" "moonrise" "moonrisedir" "moonset" "moonsetdir" "moontime"
"multitrig" "ndawn" "ndusk" "nonconst" "nonomitted" "now" "ord" "orthodoxeaster"

View File

@@ -1,5 +1,55 @@
CHANGES TO REMIND
* VERSION 6.1 Patch 2 - 2025-09-19
- BUG FIX: remind: A reminder on a specific fixed date and time that
uses the TZ feature could erroneously issue a "Can't compute trigger"
error. This has been fixed.
- CHANGE: remind: The default for --max-expr-complexity is 10,000,000
(ten million) instead of unlimited. This should not affect any
real-world Remind scripts.
* VERSION 6.1 Patch 1 - 2025-09-12
- NEW FEATURE: remind: Add the new --max-expr-complexity=n
command-line argument. It is possible to write expressions that use
enormous amounts of CPU time, such as the following:
FSET fib(n) iif(n <= 2, 1, fib(n-1)+fib(n-2))
SET a fib(100)
That will take essentially forever to execute, but will not hit the
built-in recursion limit. Using a command-line argument of
--max-expr-complexity=1000000 will terminate evaluation in a few
dozen milliseconds on modern hardware, and should not affect
realistic reminder scripts. See the man page for details.
- IMPROVEMENT: remind: Add UTF-8-aware functions to complement the
byte-aware functions that could give incorrect results by splitting
a UTF-8 character sequence. The correspondence between old and new
functions is:
NON-UTF-8-AWARE UTF-8-AWARE
=============== ===========
strlen mbstrlen
substr mbsubstr
index mbindex
char mbchar
asc codepoint
See the remind(1) man page for details.
- MINOR NEW FEATURE: remind: You can use hexadecimal integer constants
like 0xFE12 in expressions. This is mostly useful for using
codepoint() since Unicode code points are often expressed in
hexadecimal.
- BUG FIX: remind: When truncating a string when executing DUMPVARS or
during debugging of expression evaluation, Remind could sometimes
cut the string in the middle of a UTF-8 sequence. This has been
fixed.
* VERSION 6.1 Patch 0 - 2025-09-08
- MAJOR NEW FEATURE: remind: The TZ keyword lets you specify a time

View File

@@ -14,7 +14,7 @@ set bar "█" * c
set dups r/5
set bars (bar + "%_") * dups
set bars substr(bars, 0, strlen(bars)-2)
set bars mbsubstr(bars, 0, mbstrlen(bars)-2)
REM SPECIAL COLOR 91 206 250 [bars]
REM SPECIAL COLOR 245 169 184 [bars]

View File

@@ -616,6 +616,15 @@ If \fBRemind\fR finishes processing the script and then starts handling
queued reminders, the time limit is reset to no limit.
.RE
.TP
.B \-\-max-expr-complexity\fR=\fIn\fR
Limit the total complexity of expression valuation for a given line in a script
to \fIn\fR nodes. Roughly speaking, each function call, operator, constant,
variable reference, etc corresponds to one expression node. By default,
the limit is set to 10000000 (ten million). You can explicitly set it
to zero if you don't want any limit to apply. The default limit of ten
million should never be triggered by any sensible Remind script, however,
and we don't recommend changing the limit.
.TP
.B \-\-test
The \fB\-\-test\fR long option is only for use by the acceptance tests
run by "make test". Do not use this option in production.
@@ -2540,6 +2549,26 @@ word. The \fBINT\fR data type corresponds to the C "int" type.
The \fBSTRING\fR data type consists of strings of characters. It is
somewhat comparable to a C character array, but more closely resembles
the string type in BASIC.
.RS
.PP
\fBRemind\fR normally expects to be running in a UTF-8 environment.
In this environment, there is a difference between \fIbytes\fR and
\fIcharacters\fR since in UTF-8, a character may be represented by
a sequence of more than one byte. For example, in a UTF-8 environment,
the string "🙂" contains one character but four bytes. And the string
"één" contains three characters but five bytes.
.PP
\fBRemind\fR has a set of functions
that work on \fIbytes\fR, namely \fBindex\fR, \fBstrlen\fR and \fBsubstr\fR.
These are not safe to use on multi-byte strings; instead use
\fBmbindex\fR, \fBmbstrlen\fR and \fBmbsubstr\fR. If you know \fIfor sure\fR
that a string contains only single-byte characters, then the byte-oriented
versions may be used and are faster than the multi-byte versions.
.PP
Some ancient or embedded systems may lack the C library functions needed
to deal with multi-byte strings. In that case, the \fBmb\fIxxx\fR functions
all return an error.
.RE
.TP
.B TIME
The \fBTIME\fR data type is used for two different purposes: To represent
@@ -2576,7 +2605,7 @@ as being the combination of \fBDATE\fR and \fBTIME\fR parts.
The following examples illustrate constants in \fBRemind\fR expressions:
.TP
.B INT constants
12, 36, \-10, 0, 1209
12, 36, \-10, 0, 1209, 0x1F, 0xfe00 (the last two demonstrate the use of hexadecimal constants)
.TP
.B STRING constants
"Hello there", "This is a test", "\\nHello\\tThere", ""
@@ -3631,11 +3660,15 @@ function has been defined previously. The \fBargs()\fR function is
available only in versions of \fBRemind\fR from 03.00.04 and up.
.TP
.B asc(s_string)
Returns an \fBINT\fR that is the ASCII code of the first character
in \fIstring\fR. As a special case, \fBasc("")\fR returns 0. For UTF-8
strings, this will return the UTF-8 byte with which the string
begins, which is not likely to be very useful (and may indeed be negative
on machines where \fBchar\fR is a signed type.)
Returns an \fBINT\fR that is the ASCII code of the first byte in
\fIstring\fR. As a special case, \fBasc("")\fR returns 0. For UTF-8
strings, this will return the UTF-8 byte with which the string begins,
which is not likely to be very useful.
.TP
.B codepoint(s_string)
Returns an \fBINT\fR that is the code point of the first character
in \fIstring\fR, treating multi-byte characters correctly. As a special case,
\fBcodepoint("")\fR returns 0.
.TP
.B baseyr()
Returns the "base year" that was compiled into \fBRemind\fR (normally
@@ -3692,6 +3725,15 @@ It is easy to create invalid UTF-8 sequences; \fBchar\fR does not check
for this. Note that none of the arguments can be 0, unless there is only one
argument. As a special case, \fBchar(0)\fR returns "".
.TP
.B mbchar(i_i1 [,i_i2...])
This function can take any number of \fBINT\fR arguments. It returns
a \fBSTRING\fR consisting of the characters specified by the
arguments. Any codepoint may be supplied and a correct multi-byte
character string will be returned. Note that none of the arguments
can be 0, unless there is only one argument. As a special case,
\fBmbchar(0)\fR returns "". Additionally, no argument may be a
negative number.
.TP
.B choose(i_index, x_arg1 [,x_arg2...])
\fBChoose\fR must take at least two arguments, the first of which is
an \fBINT\fR. If \fIindex\fR is \fIn\fR, then the \fIn\fRth subsequent
@@ -3763,6 +3805,13 @@ ANSI color-changing sequences occupy zero columns whereas some Unicode
characters occupy two columns. \fBcolumns(str)\fR takes all of that
into account. Note that if \fBRemind\fR was compiled without Unicode support,
\fBcolumns(str)\fR returns a type mismatch error.
.PP
The result of \fBcolumns(str)\fR may be less than, equal to, or
greater than the result of \fBmbstrlen(str)\fR. This is because some
Unicode characters are so-called combining characters that add one to the
character length, but don't occupy any columns on their own. And other
Unicode characters are double-width characters that add one to the
character length, but two to the number of display columns.
.RE
.TP
.B const(x_arg)
@@ -4006,6 +4055,11 @@ Support for Hebrew dates - see the section "THE HEBREW CALENDAR"
.B hebyear(dq_date)
Support for Hebrew dates - see the section "THE HEBREW CALENDAR"
.TP
.B hex(i_n)
Returns a \fBSTRING\fR that is the hexadecimal representation of \fIn\fR.
There is no "0x" prefix and any letters in the returned value
are uppper-case.
.TP
.B hour(tq_time)
Returns the hour component of \fItime\fR.
.TP
@@ -4031,14 +4085,23 @@ compatible with previous versions of \fBRemind\fR.
.TP
.B index(s_search, s_target [,i_start)
Returns an \fBINT\fR that is the location of \fItarget\fR in the
string \fIsearch\fR. The first character of a string is numbered 1.
If \fItarget\fR does not exist in \fIsearch\fR, then 0 is returned.
string \fIsearch\fR. Note that \fBindex\fR uses \fIbyte\fR positions,
not character positions, so should not be used on non-ASCII strings. Use
\fBmbindex\fR for non-ASCII strings.
.PP
The first byte of a string is numbered 1. If \fItarget\fR does not
exist in \fIsearch\fR, then 0 is returned.
.RS
.PP
The optional parameter \fIstart\fR specifies the position in
\fIsearch\fR at which to start looking for \fItarget\fR.
.RE
.TP
.B mbindex(s_search, s_target [,i_start])
Similar to \fBindex()\fR but returns the \fIcharacter\fR position rather
than the \fIbyte\fR position. Also, \fIstart\fR is interpreted as a
1-based character index rather than a byte index.
.TP
.B isany(arg1 [,arg2, ..., argN]);
Returns 1 if the first argument \fIarg1\fR is equal to any of the
subsequent arguments \fIarg2\fR through \fIargN\fR; returns 0 otherwise.
@@ -4642,17 +4705,27 @@ output is not going to a TTY.
.RE
.TP
.B strlen(s_str)
Returns the length of \fIstr\fR. If the length of \fIstr\fR is too large
to represent as an integer, emits a "Number too high" error. Note that
\fBstrlen\fR returns the number of \fIbytes\fR in the string, not the
number of \fIcharacters\fR. These numbers are the same for ASCII strings,
but may be different for UTF-8 strings.
Returns the length of \fIstr\fR in bytes. If the length of \fIstr\fR
is too large to represent as an integer, emits a "Number too high"
error. Note that \fBstrlen\fR returns the number of \fIbytes\fR in
the string, not the number of \fIcharacters\fR. These numbers are the
same for ASCII strings, but may be different for UTF-8 strings.
.TP
.B mbstrlen(str)
Similar to \fBstrlen\fR, but returns the length of the string in
\fIcharacters\fR rather than \fIbytes\fR and is thus safe for use
on multi-byte strings.
.TP
.B substr(s_str, i_start [,i_end])
Returns a \fBSTRING\fR consisting of all characters in \fIstr\fR from
\fIstart\fR up to and including \fIend\fR. Characters are numbered
Returns a \fBSTRING\fR consisting of all bytes in \fIstr\fR from
\fIstart\fR up to and including \fIend\fR. Bytes are numbered
from 1. If \fIend\fR is not supplied, then it defaults to the length
of \fIstr\fR.
of \fIstr\fR. Because \fBsubstr\fR uses \fIbyte\fR indexes rather than
\fIcharacter\fR indexes, it should not be used on multi-byte strings.
.TP
.B mbsubstr(s_str, i_start [,i_end])
Similar to \fBsubstr\fR but uses \fIcharacter\fR indexes rather than
\fIbyte\fR indexes, and is thus safe for use on multi-byte strings.
.TP
.B sunrise([dq_date])
Returns a \fBTIME\fR indicating the time of sunrise on the specified

View File

@@ -134,7 +134,9 @@
#define E_MAX_OVERDUE_WITHOUT_TODO 110
#define E_TZ_SPECIFIED_TWICE 111
#define E_TZ_NO_AT 112
#define E_NO_MB 113
#define E_BAD_MB_SEQ 114
#define E_EXPR_NODES_EXCEEDED 115
#ifdef MK_GLOBALS
#undef EXTERN
#define EXTERN
@@ -265,6 +267,9 @@ EXTERN char *ErrMsg[]
/* E_MAX_OVERDUE_WITHOUT_TODO */ "MAX-OVERDUE specified without TODO",
/* E_TZ_SPECIFIED_TWICE */ "TZ specified twice",
/* E_TZ_NO_AT */ "TZ specified for non-timed reminder",
/* E_NO_MB */ "C library does not support multibyte characters",
/* E_BAD_MB_SEQ */ "Invalid multibyte sequence",
/* E_EXPR_NODES_EXCEEDED */ "Maximum expression complexity exceeded",
}
#endif /* MK_GLOBALS */
;

View File

@@ -316,7 +316,7 @@ add_child(expr_node *parent, expr_node *child)
/* */
/***************************************************************/
static void
debug_evaluation(Value *ans, int r, char const *fmt, ...)
debug_evaluation(Value const *ans, int r, char const *fmt, ...)
{
va_list argptr;
va_start(argptr, fmt);
@@ -343,7 +343,7 @@ debug_evaluation(Value *ans, int r, char const *fmt, ...)
/* */
/***************************************************************/
static void
debug_evaluation_binop(Value *ans, int r, Value *v1, Value *v2, char const *fmt, ...)
debug_evaluation_binop(Value const *ans, int r, Value const *v1, Value const *v2, char const *fmt, ...)
{
va_list argptr;
va_start(argptr, fmt);
@@ -381,7 +381,7 @@ debug_evaluation_binop(Value *ans, int r, Value *v1, Value *v2, char const *fmt,
/* */
/***************************************************************/
static void
debug_evaluation_unop(Value *ans, int r, Value *v1, char const *fmt, ...)
debug_evaluation_unop(Value const *ans, int r, Value const *v1, char const *fmt, ...)
{
va_list argptr;
va_start(argptr, fmt);
@@ -641,7 +641,7 @@ debug_enter_userfunc(expr_node *node, Value *locals, int nargs)
/* */
/***************************************************************/
static void
debug_exit_userfunc(expr_node *node, Value *ans, int r, Value *locals, int nargs)
debug_exit_userfunc(expr_node *node, Value const *ans, int r, Value *locals, int nargs)
{
char const *fname;
int i;
@@ -888,6 +888,15 @@ evaluate_expr_node(expr_node *node, Value *locals, Value *ans, int *nonconst)
if (!node) {
return E_SWERR;
}
if (ExpressionNodeLimitPerLine > 0 &&
ExpressionNodesEvaluatedThisLine >= ExpressionNodeLimitPerLine) {
return E_EXPR_NODES_EXCEEDED;
}
ExpressionNodesEvaluated++;
ExpressionNodesEvaluatedThisLine++;
if (ExpressionNodesEvaluatedThisLine > MaxExprNodesPerLine) {
MaxExprNodesPerLine = ExpressionNodesEvaluatedThisLine;
}
switch(node->type) {
case N_FREE:
case N_ERROR:
@@ -2132,6 +2141,31 @@ static int set_constant_value(expr_node *atom)
atom->u.value.type = INT_TYPE;
val = 0;
prev_val = 0;
if (*s == '0' && (*(s+1) == 'x' || *(s+1) == 'X')) {
/* Hex constant */
s += 2;
if (!*s || !isxdigit(*s)) {
return E_BAD_NUMBER;
}
while (*s && isxdigit(*s)) {
val *= 16;
if (*s >= '0' && *s <= '9') {
val += (*s - '0');
} else {
val += (toupper(*s) - 'A') + 10;
}
s++;
if (val < prev_val) {
return E_2HIGH;
}
prev_val = val;
}
if (*s) {
return E_BAD_NUMBER;
}
atom->u.value.v.val = val;
return OK;
}
while (*s && isdigit(*s)) {
val *= 10;
val += (*s++ - '0');
@@ -2930,6 +2964,28 @@ int EvalExpr(char const **e, Value *v, ParsePtr p)
return r;
}
#ifdef REM_USE_WCHAR
/* Truncate a wide-char string to MAX_PRT_LEN characters */
static char const *truncate_string(char const *src)
{
static wchar_t wbuf[MAX_PRT_LEN+1];
static char cbuf[(MAX_PRT_LEN * 8) + 1];
size_t l;
l = mbstowcs(wbuf, src, MAX_PRT_LEN);
if (l == (size_t) -1) {
return src;
}
wbuf[MAX_PRT_LEN] = 0;
l = wcstombs(cbuf, wbuf, MAX_PRT_LEN*8);
if (l == (size_t) -1) {
return src;
}
cbuf[MAX_PRT_LEN*8] = 0;
return cbuf;
}
#endif
/***************************************************************/
/* */
/* PrintValue */
@@ -2940,10 +2996,13 @@ int EvalExpr(char const **e, Value *v, ParsePtr p)
static DynamicBuffer printbuf = {NULL, 0, 0, ""};
#define PV_PUTC(fp, c) do { if (fp) { putc((c), fp); } else { DBufPutc(&printbuf, (c)); } } while(0);
char const *PrintValue (Value *v, FILE *fp)
char const *PrintValue (Value const *v, FILE *fp)
{
int y, m, d;
unsigned char const *s;
int max_str_put = MAX_PRT_LEN;
int truncated = 0;
char pvbuf[512];
if (!fp) {
/* It's OK to DBufFree an uninitialized *BUT STATIC* dynamic buffer */
@@ -2951,9 +3010,19 @@ char const *PrintValue (Value *v, FILE *fp)
}
if (v->type == STR_TYPE) {
#ifdef REM_USE_WCHAR
s = (unsigned char const *) truncate_string(v->v.str);
if (s != (unsigned char const *) v->v.str) {
max_str_put = INT_MAX;
if (strlen((char const *) s) != strlen(v->v.str)) {
truncated = 1;
}
}
#else
s = (unsigned char const *) v->v.str;
#endif
PV_PUTC(fp, '"');
for (y=0; y<MAX_PRT_LEN && *s; y++) {
for (y=0; y<max_str_put && *s; y++) {
switch(*s) {
case '\a': PV_PUTC(fp, '\\'); PV_PUTC(fp, 'a'); break;
case '\b': PV_PUTC(fp, '\\'); PV_PUTC(fp, 'b'); break;
@@ -2979,7 +3048,7 @@ char const *PrintValue (Value *v, FILE *fp)
s++;
}
PV_PUTC(fp, '"');
if (*s) {
if (*s || truncated) {
if (fp) {
fprintf(fp, "...");
} else {

View File

@@ -187,6 +187,7 @@ got_a_fresh_line(void)
{
FreshLine = 1;
WarnedAboutImplicit = 0;
ExpressionNodesEvaluatedThisLine = 0;
}
void set_cloexec(int fd)

View File

@@ -87,6 +87,7 @@ static int FCatch (expr_node *, Value *, Value *, int *);
static int FCatchErr (func_info *);
static int FChar (func_info *);
static int FChoose (expr_node *, Value *, Value *, int *);
static int FCodepoint (func_info *);
static int FCoerce (func_info *);
static int FColumns (func_info *);
static int FCurrent (func_info *);
@@ -112,6 +113,7 @@ static int FHebdate (func_info *);
static int FHebday (func_info *);
static int FHebmon (func_info *);
static int FHebyear (func_info *);
static int FHex (func_info *);
static int FHour (func_info *);
static int FHtmlEscape (func_info *);
static int FHtmlStriptags (func_info *);
@@ -126,6 +128,10 @@ static int FLanguage (func_info *);
static int FLocalToUTC (func_info *);
static int FLower (func_info *);
static int FMax (func_info *);
static int FMbchar (func_info *);
static int FMbindex (func_info *);
static int FMbstrlen (func_info *);
static int FMbsubstr (func_info *);
static int FMin (func_info *);
static int FMinsfromutc (func_info *);
static int FMinute (func_info *);
@@ -265,6 +271,7 @@ BuiltinFunc Func[] = {
{ "catcherr", 0, 0, 0, FCatchErr, NULL },
{ "char", 1, NO_MAX, 1, FChar, NULL },
{ "choose", 2, NO_MAX, 1, NULL, FChoose }, /*NEW-STYLE*/
{ "codepoint", 1, 1, 1, FCodepoint, NULL },
{ "coerce", 2, 2, 1, FCoerce, NULL },
{ "columns", 0, 1, 0, FColumns, NULL },
{ "const", 1, 1, 1, FNonconst, NULL },
@@ -291,6 +298,7 @@ BuiltinFunc Func[] = {
{ "hebday", 1, 1, 0, FHebday, NULL },
{ "hebmon", 1, 1, 0, FHebmon, NULL },
{ "hebyear", 1, 1, 0, FHebyear, NULL },
{ "hex", 1, 1, 1, FHex, NULL },
{ "hour", 1, 1, 1, FHour, NULL },
{ "htmlescape", 1, 1, 1, FHtmlEscape, NULL },
{ "htmlstriptags",1, 1, 1, FHtmlStriptags, NULL },
@@ -305,6 +313,10 @@ BuiltinFunc Func[] = {
{ "localtoutc", 1, 1, 1, FLocalToUTC, NULL },
{ "lower", 1, 1, 1, FLower, NULL },
{ "max", 1, NO_MAX, 1, FMax, NULL },
{ "mbchar", 1, NO_MAX, 1, FMbchar, NULL },
{ "mbindex", 2, 3, 1, FMbindex, NULL },
{ "mbstrlen", 1, 1, 1, FMbstrlen, NULL },
{ "mbsubstr", 2, 3, 1, FMbsubstr, NULL },
{ "min", 1, NO_MAX, 1, FMin, NULL },
{ "minsfromutc", 0, 2, 0, FMinsfromutc, NULL },
{ "minute", 1, 1, 1, FMinute, NULL },
@@ -476,6 +488,28 @@ static int FStrlen(func_info *info)
return OK;
}
/***************************************************************/
/* */
/* FMBstrlen - string length in wide characters */
/* */
/***************************************************************/
static int FMbstrlen(func_info *info)
{
#ifdef REM_USE_WCHAR
ASSERT_TYPE(0, STR_TYPE);
size_t l = mbstowcs(NULL, ARGSTR(0), 0);
if (l == (size_t) -1) {
return E_BAD_MB_SEQ;
}
if (l > INT_MAX) return E_2HIGH;
RetVal.type = INT_TYPE;
RETVAL = (int) l;
return OK;
#else
return E_NO_MB;
#endif
}
/***************************************************************/
/* */
/* FBaseyr - system base year */
@@ -698,10 +732,50 @@ static int FAsc(func_info *info)
{
ASSERT_TYPE(0, STR_TYPE);
RetVal.type = INT_TYPE;
RETVAL = *(ARGSTR(0));
RETVAL = (int) *( (unsigned char const *) ARGSTR(0));
return OK;
}
/***************************************************************/
/* */
/* FHex - return hexadecimal representation of an integer */
/* */
/***************************************************************/
static int FHex(func_info *info)
{
char buf[64];
ASSERT_TYPE(0, INT_TYPE);
snprintf(buf, sizeof(buf), "%X", (unsigned int) ARGV(0));
return RetStrVal(buf, info);
}
/***************************************************************/
/* */
/* FCodepoint - wide-character codepoint of start of str */
/* */
/***************************************************************/
static int FCodepoint(func_info *info)
{
#ifdef REM_USE_WCHAR
wchar_t arr[2];
size_t len;
ASSERT_TYPE(0, STR_TYPE);
len = mbstowcs(arr, ARGSTR(0), sizeof(arr) / sizeof(arr[0]));
if (len == (size_t) -1) {
return E_BAD_MB_SEQ;
}
RetVal.type = INT_TYPE;
RETVAL = (int) arr[0];
return OK;
#else
return E_NO_MB;
#endif
}
/***************************************************************/
/* */
/* FChar - build a string from ASCII values */
@@ -750,6 +824,65 @@ static int FChar(func_info *info)
*(RetVal.v.str + Nargs) = 0;
return OK;
}
/***************************************************************/
/* */
/* FMbchar - build a string from wide character code points */
/* */
/***************************************************************/
static int FMbchar(func_info *info)
{
#ifdef REM_USE_WCHAR
int i;
size_t len;
wchar_t *arr;
char *s;
for (i=0; i<Nargs; i++) {
ASSERT_TYPE(i, INT_TYPE);
}
/* Special case of one arg - if given value 0, create empty string */
if (Nargs == 1) {
if (ARGV(0) == 0) {
return RetStrVal("", info);
}
}
arr = calloc(Nargs+1, sizeof(wchar_t));
if (!arr) {
return E_NO_MEM;
}
for (i=0; i<Nargs; i++) {
if (ARGV(i) <= 0) {
return E_2LOW;
}
arr[i] = (wchar_t) ARGV(i);
}
arr[Nargs] = (wchar_t) 0;
len = wcstombs(NULL, arr, 0);
if (len == (size_t) -1) {
free( (void *) arr);
return E_BAD_MB_SEQ;
}
s = malloc(len+1);
if (!s) {
free( (void *) arr);
return E_NO_MEM;
}
(void) wcstombs(s, arr, len+1);
free( (void *) arr);
RetVal.type = STR_TYPE;
RetVal.v.str = s;
return OK;
#else
return E_NO_MB;
#endif
}
/***************************************************************/
/* */
/* Functions for extracting the components of a date. */
@@ -2378,6 +2511,75 @@ static int FSubstr(func_info *info)
return RetStrVal(t, info);
}
/***************************************************************/
/* */
/* FMbubstr */
/* */
/* The mbsubstr function. */
/* */
/***************************************************************/
static int FMbsubstr(func_info *info)
{
#ifdef REM_USE_WCHAR
wchar_t *str;
wchar_t *s;
wchar_t const *t;
size_t mblen;
char *converted;
size_t len;
int start;
int end;
if (ARG(0).type != STR_TYPE || ARG(1).type != INT_TYPE) return E_BAD_TYPE;
if (Nargs == 3 && ARG(2).type != INT_TYPE) return E_BAD_TYPE;
mblen = mbstowcs(NULL, ARGSTR(0), 0);
if (mblen == (size_t) -1) {
return E_BAD_MB_SEQ;
}
str = calloc(mblen+1, sizeof(wchar_t));
if (!str) {
return E_NO_MEM;
}
(void) mbstowcs(str, ARGSTR(0), mblen+1);
s = str;
start = 1;
while (start < ARGV(1)) {
if (!*s) break;
s++;
start++;
}
t = s;
if (Nargs >= 3) {
end = start;
while (end <= ARGV(2)) {
if (!*s) break;
s++;
end++;
}
*s = (wchar_t) 0;
}
len = wcstombs(NULL, t, 0);
if (len == (size_t) -1) {
free( (void *) str);
return E_BAD_MB_SEQ;
}
converted = malloc(len+1);
if (!converted) {
free( (void *) str);
return E_NO_MEM;
}
(void) wcstombs(converted, t, len+1);
RetVal.type = STR_TYPE;
RetVal.v.str = converted;
free( (void *) str);
return OK;
#else
return E_NO_MB;
#endif
}
/***************************************************************/
/* */
/* FIndex */
@@ -2416,6 +2618,73 @@ static int FIndex(func_info *info)
return OK;
}
/***************************************************************/
/* */
/* FMbindex */
/* */
/* The wide-char of one string embedded in another. */
/* */
/***************************************************************/
static int FMbindex(func_info *info)
{
#ifdef REM_USE_WCHAR
wchar_t *haystack;
wchar_t *needle;
wchar_t const *s;
size_t haylen, needlelen;
if (ARG(0).type != STR_TYPE || ARG(1).type != STR_TYPE ||
(Nargs == 3 && ARG(2).type != INT_TYPE)) return E_BAD_TYPE;
haylen = mbstowcs(NULL, ARGSTR(0), INT_MAX);
if (haylen == (size_t) -1) {
return E_BAD_MB_SEQ;
}
haystack = calloc(haylen+1, sizeof(wchar_t));
if (!haystack) {
return E_NO_MEM;
}
(void) mbstowcs(haystack, ARGSTR(0), haylen+1);
needlelen = mbstowcs(NULL, ARGSTR(1), INT_MAX);
if (needlelen == (size_t) -1) {
free( (void *) haystack);
return E_BAD_MB_SEQ;
}
needle = calloc(needlelen+1, sizeof(wchar_t));
if (!needle) {
free( (void *) haystack);
return E_NO_MEM;
}
(void) mbstowcs(needle, ARGSTR(1), needlelen+1);
s = haystack;
/* If 3 args, bump up the start */
if (Nargs == 3) {
if (ARGV(2) > (int) haylen) {
s += haylen;
} else {
s += ARGV(2) - 1;
}
}
/* Find the string */
RetVal.type = INT_TYPE;
s = wcsstr(s, needle);
if (!s) {
free( (void *) haystack);
free( (void *) needle);
RETVAL = 0;
return OK;
}
RETVAL = s - haystack + 1;
free( (void *) haystack);
free( (void *) needle);
return OK;
#else
return E_NO_MB;
#endif
}
/***************************************************************/
/* */
/* FIif */

View File

@@ -88,6 +88,10 @@ EXTERN INIT( int DeltaOverride, 0);
EXTERN INIT( int RunDisabled, 0);
EXTERN INIT( int ExpressionEvaluationDisabled, 0);
EXTERN INIT( int ExpressionEvaluationTimeLimit, 0);
EXTERN INIT( unsigned long ExpressionNodesEvaluated, 0);
EXTERN INIT( unsigned long MaxExprNodesPerLine, 0);
EXTERN INIT( unsigned long ExpressionNodesEvaluatedThisLine, 0);
EXTERN INIT( unsigned long ExpressionNodeLimitPerLine, 10000000);
EXTERN INIT( volatile sig_atomic_t ExpressionTimeLimitExceeded, 0);
EXTERN INIT( int IgnoreOnce, 0);
EXTERN INIT( char const *OnceFile, NULL);

View File

@@ -880,6 +880,7 @@ void Usage(void)
fprintf(ErrFp, " --only-events Do not issue TODO reminders\n");
fprintf(ErrFp, " --json Use JSON output instead of plain-text\n");
fprintf(ErrFp, " --max-execution-time=n Limit execution time to n seconds\n");
fprintf(ErrFp, " --max-expr-complexity=n Limit expression evaluation to n nodes per line\n");
fprintf(ErrFp, " --print-config-cmd Print ./configure cmd used to build Remind\n");
fprintf(ErrFp, " --print-errs Print all possible error messages\n");
fprintf(ErrFp, " --print-tokens Print all possible Remind tokens\n");
@@ -1153,6 +1154,8 @@ static void
ProcessLongOption(char const *arg)
{
int t;
unsigned long tt;
if (!strcmp(arg, "test")) {
fprintf(stderr, "Enabling test mode: This is meant for the acceptance test.\nDo not use --test in production.\nIn test mode, the system time is fixed at 2025-01-06@19:00\n");
TestMode = 1;
@@ -1221,6 +1224,10 @@ ProcessLongOption(char const *arg)
print_sysvar_tokens();
exit(0);
}
if (sscanf(arg, "max-expr-complexity=%lu", &tt) == 1) {
ExpressionNodeLimitPerLine = tt;
return;
}
if (sscanf(arg, "max-execution-time=%d", &t) == 1) {
if (t < 0) {
fprintf(ErrFp, "%s: --max-execution-time must be non-negative\n", ArgV[0]);

View File

@@ -84,6 +84,8 @@ exitfunc(void)
UnsetAllUserFuncs();
print_expr_nodes_stats();
fprintf(ErrFp, "Max expr node evaluations per line: %lu\n", MaxExprNodesPerLine);
fprintf(ErrFp, "Total expression node evaluations: %lu\n", ExpressionNodesEvaluated);
}
}

View File

@@ -68,7 +68,7 @@ expr_node *clone_expr_tree(expr_node const *node, int *r);
int EvalExpr (char const **e, Value *v, ParsePtr p);
int EvalExprRunDisabled(char const **e, Value *v, ParsePtr p);
int DoCoerce (char type, Value *v);
char const *PrintValue (Value *v, FILE *fp);
char const *PrintValue (Value const *v, FILE *fp);
int CopyValue (Value *dest, const Value *src);
int ReadLine (void);
int DoInclude (ParsePtr p, enum TokTypes tok);

View File

@@ -554,6 +554,7 @@ int ComputeTriggerNoAdjustDuration(int today, Trigger *trig, TimeTrig const *tim
y, m, d, omit,
result;
int save_nextstart = 0;
trig->expired = 0;
if (save_in_globals) {
LastTrigValid = 0;
@@ -617,14 +618,24 @@ int ComputeTriggerNoAdjustDuration(int today, Trigger *trig, TimeTrig const *tim
/** FIXME: If a timed reminder moves to yesterday because of a time
zone adjustment, try again! */
if (trig->tz) {
TimeTrig copy = *tim;
int new_result;
ExitTimezone(trig->tz);
new_result = AdjustTriggerForTimeZone(trig, result, &copy);
EnterTimezone(trig->tz);
if (new_result + duration_days < today) {
if (result + duration_days >= today &&
new_result + duration_days < today) {
/* If we are not making progress, then give up: It's expired */
if (nextstart <= save_nextstart) {
trig->expired = 1;
if (DebugFlag & DB_PRTTRIG) {
fprintf(ErrFp, "%s(%s): %s\n",
GetCurrentFilename(), line_range(LineNoStart, LineNo), GetErr(E_EXPIRED));
}
return -1;
}
save_nextstart = nextstart;
nextstart = start+1;
start = nextstart;
continue;

View File

@@ -59,7 +59,7 @@ chmod 000 include_dir/04cantread.rem
TEST_GETENV="foo bar baz" ; export TEST_GETENV
echo "Test 1" > ../tests/test.out
echo "" >> ../tests/test.out
../src/remind --flush -e -dxte ../tests/test.rem 16 feb 1991 12:13 2>&1 | grep -v 'TimetIs64bit' >> ../tests/test.out
../src/remind --flush -e -dxte ../tests/test.rem 16 feb 1991 12:13 2>&1 | grep -v -a 'TimetIs64bit' >> ../tests/test.out 2>&1
echo "" >> ../tests/test.out
echo "Test 2" >> ../tests/test.out
echo "" >> ../tests/test.out
@@ -618,7 +618,7 @@ rm -f ../tests/once.timestamp
../src/remind --flush -q ../tests/dedupe.rem 8 November 2023 >> ../tests/test.out 2>&1
# Remove references to SysInclude, which is build-specific
grep -F -v '$SysInclude' < ../tests/test.out > ../tests/test.out.1 && mv -f ../tests/test.out.1 ../tests/test.out
grep -F -v -a '$SysInclude' < ../tests/test.out > ../tests/test.out.1 && mv -f ../tests/test.out.1 ../tests/test.out
# If "man" accepts the --warnings flag, test all the man pages.
RUNMAN=0
@@ -785,7 +785,7 @@ echo "... and here is stderr" >> ../tests/test.out 2>&1
# Test %: substitution sequence in all the languages
for i in ../include/lang/??.rem ; do
../src/remind --flush "-ii=\"$i\"" -p - 2025-08-13 <<'EOF' 2>&1 | grep 2025/ >> ../tests/test.out
../src/remind --flush "-ii=\"$i\"" -p - 2025-08-13 <<'EOF' 2>&1 | grep -a 2025/ >> ../tests/test.out
DO [i]
REM TODO 2025-08-13 MSG %(LANGID) Task1%:
REM TODO 2025-08-13 COMPLETE-THROUGH 2025-08-12 MSG %(LANGID) Task2%:
@@ -795,6 +795,49 @@ done
../src/remind --flush -q ../tests/safety.rem 2025-08-13 >> ../tests/test.out 2>&1
# Test --max-expr-complexity
../src/remind -dh --flush -q --max-expr-complexity=1000000 - 2025-01-01 <<'EOF' >> ../tests/test.out 2>&1
BANNER %
SET $AddBlankLines 0
FSET fib(n) iif(n < 3, 1, fib(n-1) + fib(n-2))
REM MSG fib(10) = [fib(10)]
REM MSG fib(15) = [fib(15)]
REM MSG fib(20) = [fib(20)]
REM MSG fib(21) = [fib(21)]
REM MSG fib(22) = [fib(22)]
REM MSG fib(23) = [fib(23)]
REM MSG fib(24) = [fib(24)]
REM MSG fib(25) = [fib(25)]
REM MSG fib(30) = [fib(30)]
EOF
# Test hex constants
../src/remind -dh --flush -q - 2025-01-01 <<'EOF' >> ../tests/test.out 2>&1
BANNER %
SET $AddBlankLines 0
set a 0xfe + 0xef
rem msg a = [a]; hex(a) = [hex(a)]
rem msg hex(-1) = [hex(-1)]
set a 0x7fff
rem msg a = [a]; hex(a) = [hex(a)]
EOF
# Test proper truncation by "dumpvars"
../src/remind -h --flush -q - 2025-01-01 <<'EOF' >> ../tests/test.out 2>&1
BANNER %
SET $AddBlankLines 0
set a "¢" * 41
set b "D" * 41
set c mbchar(0x1F642) * 41
DUMP
set a "¢" * 40
set b "D" * 40
set c mbchar(0x1F642) * 40
DUMP
EOF
cmp -s ../tests/test.out ../tests/test.cmp
if [ "$?" = "0" ]; then
echo "Remind: Acceptance test PASSED"

View File

@@ -161,6 +161,9 @@ debug -t
EOF
TZ=America/Toronto $REMIND -s - 2025-09-01@00:00 <<'EOF' >> $OUT 2>&1
REM 2025-09-24 AT 09:00 TZ America/Denver MSG Dr. Smith
EOF
cmp -s $OUT $CMP
if [ "$?" = "0" ] ; then
echo "Remind: Time zone test PASSED"

View File

@@ -1045,7 +1045,7 @@ set a057 value("a05"+"6")
"a05" + "6" => "a056"
value("a056") => "SDFJHSDF KSJDFH KJSDFH KSJDFH"
set a058 version()
version() => "06.01.00"
version() => "06.01.02"
set a059 wkday(today())
today() => 1991-02-16
wkday(1991-02-16) => "Saturday"
@@ -2608,7 +2608,7 @@ a056 "SDFJHSDF KSJDFH KJSDFH KSJDFH"
a007 "1991-02-16"
a057 "SDFJHSDF KSJDFH KJSDFH KSJDFH"
a008 "11:44"
a058 "06.01.00"
a058 "06.01.02"
a059 "Saturday"
a010 12
a060 6
@@ -5561,8 +5561,8 @@ REM SATISFY ""
REM SATISFY [version() > "01.00.00"]
../tests/test.rem(1074): SATISFY: expression has no reference to trigdate() or $T...
../tests/test.rem(1074): Trig = Saturday, 16 February, 1991
version() => "06.01.00"
"06.01.00" > "01.00.00" => 1
version() => "06.01.02"
"06.01.02" > "01.00.00" => 1
../tests/test.rem(1074): Trig(satisfied) = Saturday, 16 February, 1991
REM SATISFY [max(x, max(x, 1, 2, 3), 4, 5, 6) * 5]
../tests/test.rem(1075): SATISFY: expression has no reference to trigdate() or $T...
@@ -16592,8 +16592,63 @@ Leaving UserFN c() => 33
DEBUG -xe
Overridden: subst_colon subst_bang subst_question subst_at subst_hash
bad => "ÿ"
mbstrlen("ÿ") => Invalid multibyte sequence
../tests/test.rem(1734): mbstrlen(): Invalid multibyte sequence
bad => "ÿ"
strlen("ÿ") => 1
faces => "ðŸ™ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo"
mbstrlen("ðŸ™ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo") => 15
faces => "ðŸ™ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo"
strlen("ðŸ™ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo") => 34
faces => "ðŸ™ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo"
mbindex("ðŸ™ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo", "ç") => 9
faces => "ðŸ™ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo"
index("ðŸ™ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo", "ç") => 24
bad => "ÿ"
bad => "ÿ"
mbindex("ÿ", "ÿ") => Invalid multibyte sequence
../tests/test.rem(1742): mbindex(): Invalid multibyte sequence
faces => "ðŸ™ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo"
mbindex("ðŸ™ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo", "ç", 11) => 11
faces => "ðŸ™ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo"
index("ðŸ™ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo", "ç", 25) => 26
faces => "ðŸ™ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo"
index("ðŸ™ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo", "whoopér") => 0
faces => "ðŸ™ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo"
mbindex("ðŸ™ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo", "whoopér") => 0
faces => "ðŸ™ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo"
index("ðŸ™ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo", "whoopér", 3) => 0
faces => "ðŸ™ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo"
mbindex("ðŸ™ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo", "whoopér", 3) => 0
faces => "ðŸ™ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo"
mbsubstr("ðŸ™ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo", 2) => "ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo"
faces => "ðŸ™ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo"
mbsubstr("ðŸ™ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo", 2, 9) => "ðŸ™ðŸ™ðŸ™ðŸ™xyzç"
bad => "ÿ"
mbsubstr("ÿ", 1) => Invalid multibyte sequence
../tests/test.rem(1754): mbsubstr(): Invalid multibyte sequence
bad => "ÿ"
mbsubstr("ÿ", 1, 20) => Invalid multibyte sequence
../tests/test.rem(1755): mbsubstr(): Invalid multibyte sequence
faces => "ðŸ™ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo"
substr("ðŸ™ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo", 2) => "Ÿ™ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo"
faces => "ðŸ™ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo"
substr("ðŸ™ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo", 2, 9) => "Ÿ™‚🙂ð"
faces => "ðŸ™ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo"
codepoint("ðŸ™ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo") => 128578
mbchar(128578, 162, 122) => "ðŸ™Â¢z"
bad => "ÿ"
codepoint("ÿ") => Invalid multibyte sequence
../tests/test.rem(1762): codepoint(): Invalid multibyte sequence
codepoint("") => 0
mbchar(0) => ""
mbchar(0, 120) => Number too low
../tests/test.rem(1765): mbchar(): Number too low
mbchar(120, 0) => Number too low
../tests/test.rem(1766): mbchar(): Number too low
Variable hash table statistics:
Entries: 100144; Buckets: 87719; Non-empty Buckets: 66301
Entries: 100146; Buckets: 87719; Non-empty Buckets: 66303
Maxlen: 5; Minlen: 0; Avglen: 1.142; Stddev: 0.878; Avg nonempty len: 1.510
Growths: 13; Shrinks: 0
Function hash table statistics:
@@ -16612,6 +16667,8 @@ Translation hash table statistics:
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: 106467
Test 2
@@ -23658,7 +23715,7 @@ SECURITY: Won't read world-writable file or directory!
Error reading include_dir/ww: Can't open file
SECURITY: Won't read world-writable file or directory!
Error reading include_dir/ww: No files matching *.rem
06.01.00
06.01.02
Enabling test mode: This is meant for the acceptance test.
Do not use --test in production.
In test mode, the system time is fixed at 2025-01-06@19:00
@@ -24234,6 +24291,8 @@ Translation hash table statistics:
Expression nodes high-water: 499
Expression nodes leaked: 0
Parse level high-water: 2001
Max expr node evaluations per line: 499
Total expression node evaluations: 632
-stdin-(14): Unmatched PUSH-OMIT-CONTEXT at -stdin-(7)
-stdin-(14): Warning: PUSH-OMIT-CONTEXT without matching POP-OMIT-CONTEXT
No reminders.
@@ -24549,6 +24608,7 @@ catch
catcherr
char
choose
codepoint
coerce
columns
const
@@ -24575,6 +24635,7 @@ hebdate
hebday
hebmon
hebyear
hex
hour
htmlescape
htmlstriptags
@@ -24589,6 +24650,10 @@ language
localtoutc
lower
max
mbchar
mbindex
mbstrlen
mbsubstr
min
minsfromutc
minute
@@ -24813,6 +24878,8 @@ Translation hash table statistics:
Expression nodes high-water: 1800000
Expression nodes leaked: 0
Parse level high-water: 7
Max expr node evaluations per line: 3
Total expression node evaluations: 300001
set a 8 * "]]]" & 6
-stdin-(1): Parse error `&' (did you mean `&&'?)
8 * "]]]" & 6
@@ -24993,6 +25060,9 @@ TRANSLATE "MAX-OVERDUE specified twice" ""
TRANSLATE "MAX-OVERDUE specified without TODO" ""
TRANSLATE "TZ specified twice" ""
TRANSLATE "TZ specified for non-timed reminder" ""
TRANSLATE "C library does not support multibyte characters" ""
TRANSLATE "Invalid multibyte sequence" ""
TRANSLATE "Maximum expression complexity exceeded" ""
# Other Messages
TRANSLATE "%s function `%s' defined at %s(%s) does not use its argument" ""
@@ -39758,3 +39828,73 @@ nooooo....
../tests/safety.rem(37): [#0] In function `subst_b'
today
nooooo....
fib(10) = 55
fib(15) = 610
fib(20) = 6765
fib(21) = 10946
fib(22) = 17711
fib(23) = 28657
fib(24) = 46368
-stdin-(12): `-': Maximum expression complexity exceeded
-stdin-(3): [#0] In function `fib'
[remaining call frames omitted]
-stdin-(13): `-': Maximum expression complexity exceeded
-stdin-(3): [#0] In function `fib'
[remaining call frames omitted]
Variable hash table statistics:
Entries: 0; Buckets: 7; Non-empty Buckets: 0
Maxlen: 0; Minlen: 0; Avglen: 0.000; Stddev: 0.000; Avg nonempty len: 0.000
Growths: 0; Shrinks: 0
Function hash table statistics:
Entries: 1; Buckets: 7; Non-empty Buckets: 1
Maxlen: 1; Minlen: 0; Avglen: 0.143; Stddev: 0.350; Avg nonempty len: 1.000
Growths: 0; Shrinks: 0
Dedupe hash table statistics:
Entries: 0; Buckets: 7; Non-empty Buckets: 0
Maxlen: 0; Minlen: 0; Avglen: 0.000; Stddev: 0.000; Avg nonempty len: 0.000
Growths: 0; Shrinks: 0
Translation hash table statistics:
Entries: 1; Buckets: 7; Non-empty Buckets: 1
Maxlen: 1; Minlen: 0; Avglen: 0.143; Stddev: 0.350; Avg nonempty len: 1.000
Growths: 0; Shrinks: 0
Expression nodes allocated: 256
Expression nodes high-water: 16
Expression nodes leaked: 0
Parse level high-water: 25
Max expr node evaluations per line: 1000000
Total expression node evaluations: 3999940
a = 493; hex(a) = 1ED
hex(-1) = FFFFFFFF
a = 32767; hex(a) = 7FFF
Variable hash table statistics:
Entries: 1; Buckets: 7; Non-empty Buckets: 1
Maxlen: 1; Minlen: 0; Avglen: 0.143; Stddev: 0.350; Avg nonempty len: 1.000
Growths: 0; Shrinks: 0
Function hash table statistics:
Entries: 0; Buckets: 7; Non-empty Buckets: 0
Maxlen: 0; Minlen: 0; Avglen: 0.000; Stddev: 0.000; Avg nonempty len: 0.000
Growths: 0; Shrinks: 0
Dedupe hash table statistics:
Entries: 0; Buckets: 7; Non-empty Buckets: 0
Maxlen: 0; Minlen: 0; Avglen: 0.000; Stddev: 0.000; Avg nonempty len: 0.000
Growths: 0; Shrinks: 0
Translation hash table statistics:
Entries: 1; Buckets: 7; Non-empty Buckets: 1
Maxlen: 1; Minlen: 0; Avglen: 0.143; Stddev: 0.350; Avg nonempty len: 1.000
Growths: 0; Shrinks: 0
Expression nodes allocated: 256
Expression nodes high-water: 3
Expression nodes leaked: 0
Parse level high-water: 17
Max expr node evaluations per line: 3
Total expression node evaluations: 13
Variable Value
a "¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢"...
b "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD"...
c "🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂"...
Variable Value
a "¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢"
b "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD"
c "🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂"

View File

@@ -1725,7 +1725,47 @@ fset subst_hash(a, b, c) "subst_hash"
REM MSG Overridden: %: %! %? %@ %#
# mbstrlen and friends
DEBUG -xe
set bad char(255)
set faces "🙂" * 5 + "xyz" + "çççéfoo"
DEBUG +x
set a mbstrlen(bad)
set a strlen(bad)
set a mbstrlen(faces)
set a strlen(faces)
set a mbindex(faces, "ç")
set a index(faces, "ç")
set a mbindex(bad, bad)
set a mbindex(faces, "ç", 11)
set a index(faces, "ç", 25)
set a index(faces, "whoopér")
set a mbindex(faces, "whoopér")
set a index(faces, "whoopér", 3)
set a mbindex(faces, "whoopér", 3)
set a mbsubstr(faces, 2)
set a mbsubstr(faces, 2, 9)
set a mbsubstr(bad, 1)
set a mbsubstr(bad, 1, 20)
set a substr(faces, 2)
set a substr(faces, 2, 9)
set a codepoint(faces)
set a mbchar(128578, 162, 122)
set a codepoint(bad)
set a codepoint("")
set a mbchar(0)
set a mbchar(0, 120)
set a mbchar(120, 0)
DEBUG -x
# Don't want Remind to queue reminders
EXIT

View File

@@ -78,3 +78,4 @@ trig("Mon at 00:00 TZ Australia/Sydney", "Tue at 00:00 TZ America/Toronto", "Wed
-stdin-(14:21): Trig(tz_adj Australia/Sydney) = Saturday, 6 September, 2025 AT 10:00
-stdin-(14:21): Trig = Monday, 1 September, 2025 AT 14:42
2025-09-01
2025/09/24 * * * 660 11:00am Dr. Smith