diff --git a/man/remind.1.in b/man/remind.1.in index 46e5c111..aa671bf2 100644 --- a/man/remind.1.in +++ b/man/remind.1.in @@ -3649,6 +3649,14 @@ As an example, the following two expressions are equivalent: .fi .RE .TP +.B isconst(x_any) +Evaluates its argument and then \fIthrows away\fR the value, returning +1 if the expression is constant or 0 if it is non-constant. Note that +\fBisconst\fR does not take into account the context; if \fIarg\fR is +a constant expression but evaluated in a non-constant context, +\fBisconst\fR will still return 1. For details about constant vs. +non-constant expressions, see the section "NON-CONSTANT EXPRESSIONS" +.TP .B isdst([d_date [,t_time]]) \fRor\fB isdst(q_datetime) Returns a positive number if daylight saving time is in effect on the specified date and time. \fIDate\fR @@ -3951,6 +3959,11 @@ The second REM statement sets up the 14-day blue-box cycle with a similar adjustment made by AFTER in conjunction with _garbhol. .RE .TP +.B nonconst(x_arg) +Returns the argument \fIarg\fR unchanged, but forces the expression +to be considered \fInon-constant\fR. For details, see the section +"NON-CONSTANT EXPRESSIONS" +.TP .B now() Returns the current system time, as a \fBTIME\fR type. This may be the actual time, or a time supplied on the command line. @@ -5691,7 +5704,9 @@ to consider it non-constant: .RS .TP .B o -A global variable +A global variable that has been assigned the result of a non-constant +expression, or that has been assigned a value in a \fInon-constant +context\fR (to be described later.) .TP .B o A system variable @@ -5713,19 +5728,36 @@ The use of an OMITFUNC The use of a relative SCANFROM .RE .PP -Note that \fBRemind\fR checks expressions for non-constantness -\fIin isolation\fR. It does not trace data flow. For example. -a human can easily see that the expression in the REM command -below is actually constant, but \fBRemind\fR does not look -at the preceding assignment. It simply looks at the expression \fBd\fR -in isolation and considers that the global variable \fBd\fR -might not be constant. +Whenever a variable is assigned a value, \fBRemind\fR tracks whether +or not the expression whose value it was assigned is constant or +non-constant. Additionally, variables that are assigned in a +non-constant context are always assumed to be non-constant. +A non-constant context is the code in the scope of an \fBIF\fR +statement where the \fBIF\fR expression is non-constant. +.PP +Here are some examples that should make things clear: .PP .nf - SET d '2025-06-01' - REM [d] MSG Hello! + SET d '2025-06-01' # d is constant + REM [d] MSG Hello! # eligible for purging + + SET d today() - 3 # d is non-constant + REM [d] MSG Hello! # not eligible for purging + + IF wkdaynum(today()) == 3 + set d '2025-06-01' # d is non-constant + ELSE + set d '2026-01-01' # d is non-constant + ENDIF .fi .PP +Note that within the \fBIF\fR...\fBENDIF\fR scope, any assignments +are non-constant because the code flow depends on today's date, which +could change in subsequent \fBRemind\fR runs. +.PP +Variables initialized on the command-line with the \fB\-i\fR flag are +\fIalways\fR considered to be non-constant. +.PP The \fBn\fR debugging flag prints a message to standard error whenever \fBRemind\fR decides that an expression is non-constant. This can produce a large amount of output, so if you want to find out why \fBRemind\fR considers diff --git a/src/dorem.c b/src/dorem.c index bb49908c..d94b4379 100644 --- a/src/dorem.c +++ b/src/dorem.c @@ -649,7 +649,7 @@ int ParseRem(ParsePtr s, Trigger *trig, TimeTrig *tim) strtolower(trig->omitfunc); /* An OMITFUNC counts as a nonconst_expr! */ s->expr_happened = 1; - nonconst_debug(s->nonconst_expr, "OMITFUNC counts as a non-constant expression"); + nonconst_debug(s->nonconst_expr, tr("OMITFUNC counts as a non-constant expression")); s->nonconst_expr = 1; DBufFree(&buf); break; @@ -1071,7 +1071,7 @@ static int ParseScanFrom(ParsePtr s, Trigger *t, int type) FromDSE(DSEToday - tok.val, &y, &m, &d); /* Don't purge reminders with a relative scanfrom */ s->expr_happened = 1; - nonconst_debug(s->nonconst_expr, "Relative SCANFROM counts as a non-constant expression"); + nonconst_debug(s->nonconst_expr, tr("Relative SCANFROM counts as a non-constant expression")); s->nonconst_expr = 1; break; diff --git a/src/expr.c b/src/expr.c index 27fb6232..b76a9ee8 100644 --- a/src/expr.c +++ b/src/expr.c @@ -426,7 +426,7 @@ get_var(expr_node *node, Value *ans, int *nonconst) return E_NOSUCH_VAR; } if (v->nonconstant) { - nonconst_debug(*nonconst, "Global variable `%s' makes expression non-constant", str); + nonconst_debug(*nonconst, tr("Global variable `%s' makes expression non-constant"), str); *nonconst = 1; } return CopyValue(ans, &(v->v)); @@ -883,7 +883,7 @@ evaluate_expr_node(expr_node *node, Value *locals, Value *ans, int *nonconst) case N_SHORT_SYSVAR: /* System var? Return it and note non-constant expression */ - nonconst_debug(*nonconst, "System variable `$%s' makes expression non-constant", node->u.name); + nonconst_debug(*nonconst, tr("System variable `$%s' makes expression non-constant"), node->u.name); *nonconst = 1; r = get_sysvar(node, ans); DBG(debug_evaluation(ans, r, "$%s", node->u.name)); @@ -891,7 +891,7 @@ evaluate_expr_node(expr_node *node, Value *locals, Value *ans, int *nonconst) case N_SYSVAR: /* System var? Return it and note non-constant expression */ - nonconst_debug(*nonconst, "System variable `$%s' makes expression non-constant", node->u.value.v.str); + nonconst_debug(*nonconst, tr("System variable `$%s' makes expression non-constant"), node->u.value.v.str); *nonconst = 1; r = get_sysvar(node, ans); DBG(debug_evaluation(ans, r, "$%s", node->u.value.v.str)); @@ -900,7 +900,7 @@ evaluate_expr_node(expr_node *node, Value *locals, Value *ans, int *nonconst) case N_BUILTIN_FUNC: /* Built-in function? Evaluate and note non-constant where applicable */ if (!node->u.builtin_func->is_constant) { - nonconst_debug(*nonconst, "Non-constant builtin function `%s' makes expression non-constant", node->u.builtin_func->name); + nonconst_debug(*nonconst, tr("Non-constant builtin function `%s' makes expression non-constant"), node->u.builtin_func->name); *nonconst = 1; } return eval_builtin(node, locals, ans, nonconst); diff --git a/src/var.c b/src/var.c index 2c9e550a..46329083 100644 --- a/src/var.c +++ b/src/var.c @@ -646,6 +646,11 @@ int DoSet (Parser *p) } else { r = SetVar(DBufValue(&buf), &v, 0); } + if (DebugFlag & DB_NONCONST) { + if (!in_constant_context() && !p->nonconst_expr) { + Wprint(tr("Variable assignment considered non-constant because of context")); + } + } } if (buf.len > VAR_NAME_LEN) { Wprint(tr("Warning: Variable name `%.*s...' truncated to `%.*s'"), diff --git a/tests/test.cmp b/tests/test.cmp index 479774ab..f37fa3ec 100644 --- a/tests/test.cmp +++ b/tests/test.cmp @@ -24808,28 +24808,34 @@ TRANSLATE "Found cached directory listing for `%s'" "" TRANSLATE "Function `%s' defined at %s(%s) should take %d argument%s, but actually takes %d" "" TRANSLATE "Function `%s' redefined: previously defined at %s(%s)" "" TRANSLATE "GetValidHebDate: Bad adarbehave value %d" "" +TRANSLATE "Global variable `%s' makes expression non-constant" "" TRANSLATE "In" "" TRANSLATE "Invalid INFO string: Must be of the form \"Header: Value\"" "" TRANSLATE "Invalid translation: Both original and translated must have the same printf-style formatting sequences in the same order." "" TRANSLATE "Missing REM type; assuming MSG" "" TRANSLATE "No Adar A in %d" "" TRANSLATE "No substition function `%s' defined" "" +TRANSLATE "Non-constant builtin function `%s' makes expression non-constant" "" TRANSLATE "Not setting $OnceFile: Already processed a reminder with a ONCE clause" "" TRANSLATE "OMIT: UNTIL not allowed; did you mean THROUGH?" "" +TRANSLATE "OMITFUNC counts as a non-constant expression" "" TRANSLATE "POP-OMIT-CONTEXT at %s:%d matches PUSH-OMIT-CONTEXT in different file: %s:%d" "" TRANSLATE "Reading `%s': Found in cache" "" TRANSLATE "Reading `%s': Opening file on disk" "" TRANSLATE "Reading `-': Reading stdin" "" TRANSLATE "Reading command `%s': Found in cache" "" +TRANSLATE "Relative SCANFROM counts as a non-constant expression" "" TRANSLATE "SATISFY: constant 0 will never be true" "" TRANSLATE "SATISFY: constant \"\" will never be true" "" TRANSLATE "SATISFY: expression has no reference to trigdate() or $T..." "" TRANSLATE "SECURITY: Won't read non-root-owned file or directory when running as root!" "" TRANSLATE "SECURITY: Won't read world-writable file or directory!" "" TRANSLATE "Scanning directory `%s' for *.rem files" "" +TRANSLATE "System variable `$%s' makes expression non-constant" "" TRANSLATE "Undefined %s function: `%s'" "" TRANSLATE "Unmatched PUSH-OMIT-CONTEXT at %s(%d)" "" TRANSLATE "Unrecognized command; interpreting as REM" "" +TRANSLATE "Variable assignment considered non-constant because of context" "" TRANSLATE "Warning: Function name `%s...' truncated to `%s'" "" TRANSLATE "Warning: OMIT is ignored if you use OMITFUNC" "" TRANSLATE "Warning: SCANFROM is ignored in two-argument form of evaltrig()" ""