diff --git a/contrib/remind-conf-mode/remind-conf-mode.el b/contrib/remind-conf-mode/remind-conf-mode.el index 7b117e33..151a442a 100644 --- a/contrib/remind-conf-mode/remind-conf-mode.el +++ b/contrib/remind-conf-mode/remind-conf-mode.el @@ -166,7 +166,7 @@ (list "_" "abs" "access" "adawn" "adusk" "ampm" "ansicolor" "args" "asc" "baseyr" "catch" "catcherr" "char" "choose" "coerce" "columns" "current" "date" "datepart" "datetime" "dawn" "day" "daysinmon" "defined" "dosubst" - "dusk" "easterdate" "escape" "evaltrig" "filedate" "filedatetime" + "dusk" "easterdate" "escape" "eval" "evaltrig" "filedate" "filedatetime" "filedir" "filename" "getenv" "hebdate" "hebday" "hebmon" "hebyear" "hour" "htmlescape" "htmlstriptags" "iif" "index" "isany" "isconst" "isdst" "isleap" "isomitted" "language" "localtoutc" "lower" "max" "min" diff --git a/man/remind.1.in b/man/remind.1.in index d7c549c5..2b81e77a 100644 --- a/man/remind.1.in +++ b/man/remind.1.in @@ -3512,6 +3512,24 @@ If the optional \fIadd_quotes\fR argument is supplied and is non-zero, then the return value from \fBescape\fR will include surrounding double-quotes. .RE .TP +.B eval(s_arg) +Parses the string \fIarg\fR as an expression and evaluates it, returning +the result. Note that any variable names in the parsed expression refer +to global variables, not any local variables. For example, consider this: +.PP +.RS +.nf + SET x 2 + FSET f(x) x + eval("1 + x") + REM MSG F is [f(9)] +.fi +.PP +The result will be "F is 12" because the reference to \fIx\fR inside the +\fBeval()\fR argument refers to the \fIglobal\fR variable \fIx\fR and +not the function argument. +.PP +.RE +.TP .B evaltrig(s_trigger [,dq_start]) Evaluates \fItrigger\fR as if it were a REM or IFTRIG trigger specification and returns the trigger date as a \fBDATE\fR (or as a \fBDATETIME\fR if diff --git a/src/expr.c b/src/expr.c index 6367b43f..ae89439a 100644 --- a/src/expr.c +++ b/src/expr.c @@ -497,6 +497,7 @@ eval_builtin(expr_node *node, Value *locals, Value *ans, int *nonconst) old function call API by building up a bundle of evaluated arguments */ info.nargs = node->num_kids; + info.nonconst = 0; if (info.nargs) { if (info.nargs <= STACK_ARGS_MAX) { @@ -548,6 +549,11 @@ eval_builtin(expr_node *node, Value *locals, Value *ans, int *nonconst) fprintf(ErrFp, ") => "); } r = f->func(&info); + /* Propagate non-constness */ + if (info.nonconst) { + nonconst_debug(*nonconst, tr("Non-constant built-in function `%s' makes expression non-constant"), f->name); + *nonconst = 1; + } if (r == OK) { /* All went well; copy the result destructively */ (*ans) = info.retval; @@ -2589,7 +2595,7 @@ static expr_node *parse_expression_aux(char const **e, int *r, Var *locals, int /* */ /* parse_expression - top-level expression-parsing function */ /* */ -/* *e points to string being parsed; *e will be updates */ +/* *e points to string being parsed; *e will be updated */ /* r points to a location for the return code (OK or an error) */ /* locals is an array of local function arguments, if any */ /* */ @@ -2625,36 +2631,38 @@ expr_node *parse_expression(char const **e, int *r, Var *locals) fprintf(ErrFp, " Unparsed: %s\n", *e); } } - if (*r == E_EXPECT_COMMA || - *r == E_MISS_RIGHT_PAREN || - *r == E_EXPECTING_EOL || - *r == E_2MANY_ARGS || - *r == E_2FEW_ARGS || - *r == E_PARSE_ERR || - *r == E_EOLN || - *r == E_BAD_NUMBER || - *r == E_BAD_DATE || - *r == E_BAD_TIME || - *r == E_ILLEGAL_CHAR) { - end_of_expr = find_end_of_expr(orig); - while (**e && isempty(**e)) { - (*e)++; - } - while (*orig && ((orig < end_of_expr) || (orig <= *e))) { - if (*orig == '\n') { - fprintf(ErrFp, " "); - } else { - fprintf(ErrFp, "%c", *orig); + if (!SuppressErrorOutputInCatch) { + if (*r == E_EXPECT_COMMA || + *r == E_MISS_RIGHT_PAREN || + *r == E_EXPECTING_EOL || + *r == E_2MANY_ARGS || + *r == E_2FEW_ARGS || + *r == E_PARSE_ERR || + *r == E_EOLN || + *r == E_BAD_NUMBER || + *r == E_BAD_DATE || + *r == E_BAD_TIME || + *r == E_ILLEGAL_CHAR) { + end_of_expr = find_end_of_expr(orig); + while (**e && isempty(**e)) { + (*e)++; } - orig++; + while (*orig && ((orig < end_of_expr) || (orig <= *e))) { + if (*orig == '\n') { + fprintf(ErrFp, " "); + } else { + fprintf(ErrFp, "%c", *orig); + } + orig++; + } + fprintf(ErrFp, "\n"); + orig = o2; + while (*orig && (orig < *e || isspace(*orig))) { + orig++; + fprintf(ErrFp, " "); + } + fprintf(ErrFp, "^-- %s\n", tr("here")); } - fprintf(ErrFp, "\n"); - orig = o2; - while (*orig && (orig < *e || isspace(*orig))) { - orig++; - fprintf(ErrFp, " "); - } - fprintf(ErrFp, "^-- %s\n", tr("here")); } return node; } diff --git a/src/funcs.c b/src/funcs.c index af17502e..be99a573 100644 --- a/src/funcs.c +++ b/src/funcs.c @@ -99,6 +99,7 @@ static int FDosubst (func_info *); static int FDusk (func_info *); static int FEasterdate (func_info *); static int FEscape (func_info *); +static int FEval (func_info *); static int FEvalTrig (func_info *); static int FFiledate (func_info *); static int FFiledatetime (func_info *); @@ -268,6 +269,7 @@ BuiltinFunc Func[] = { { "dusk", 0, 1, 0, FDusk, NULL }, { "easterdate", 0, 1, 0, FEasterdate, NULL }, { "escape", 1, 2, 1, FEscape, NULL }, + { "eval", 1, 1, 1, FEval, NULL }, { "evaltrig", 1, 2, 0, FEvalTrig, NULL }, { "filedate", 1, 1, 0, FFiledate, NULL }, { "filedatetime", 1, 1, 0, FFiledatetime, NULL }, @@ -3969,6 +3971,26 @@ FWeekno(func_info *info) return OK; } +static int +FEval(func_info *info) +{ + expr_node *n; + int r; + + ASSERT_TYPE(0, STR_TYPE); + char const *e = ARGSTR(0); + + n = parse_expression(&e, &r, NULL); + if (r != OK) { + info->nonconst = 1; + return r; + } + + r = evaluate_expr_node(n, NULL, &(info->retval), &(info->nonconst)); + free_expr_tree(n); + return r; +} + static int FEvalTrig(func_info *info) { diff --git a/src/types.h b/src/types.h index 0769d1e5..18669496 100644 --- a/src/types.h +++ b/src/types.h @@ -66,6 +66,7 @@ typedef struct { int nargs; Value *args; Value retval; + int nonconst; } func_info; /* Forward reference */ diff --git a/tests/test.cmp b/tests/test.cmp index 9b94e08e..a6e9d05d 100644 --- a/tests/test.cmp +++ b/tests/test.cmp @@ -16471,6 +16471,49 @@ a 1 DEBUG -n +# Test eval +DEBUG +x +set a eval("1 + 2") +eval("1 + 2") => 1 + 2 => 3 +3 +dump -c a +Variable Value + +a 3 +set a eval("today() + 1") +eval("today() + 1") => today() => 1991-02-16 +1991-02-16 + 1 => 1991-02-17 +1991-02-17 +dump -c a +Variable Value + +a 1991-02-17 +set a eval("1 +") +eval("1 +") => ../tests/test.rem(1569): Unexpected end of line +1 + + ^-- here +Unexpected end of line +set a eval("1/0") +eval("1/0") => 1 / 0 => Division by zero +../tests/test.rem(1570): `/': Division by zero +Division by zero +set a eval("1 / / 2") +eval("1 / / 2") => ../tests/test.rem(1571): Illegal character `/' +1 / / 2 + ^-- here +Illegal character +set a catch(eval("1 +"), 33) +eval("1 +") => Unexpected end of line +catch(*Unexpected end of line*, 33) => 33 +set a catch(eval("1/0"), 34) +eval("1/0") => 1 / 0 => Division by zero +Division by zero +catch(*Division by zero*, 34) => 34 +set a catch(eval("1 / / 2"), 35) +eval("1 / / 2") => Illegal character +catch(*Illegal character*, 35) => 35 +DEBUG -x + DEBUG -e Variable hash table statistics: Entries: 100143; Buckets: 87719; Non-empty Buckets: 66301 @@ -24424,6 +24467,7 @@ dosubst dusk easterdate escape +eval evaltrig filedate filedatetime diff --git a/tests/test.rem b/tests/test.rem index 9a690681..d0a2e95c 100644 --- a/tests/test.rem +++ b/tests/test.rem @@ -1560,6 +1560,20 @@ dump -c a DEBUG -n +# Test eval +DEBUG +x +set a eval("1 + 2") +dump -c a +set a eval("today() + 1") +dump -c a +set a eval("1 +") +set a eval("1/0") +set a eval("1 / / 2") +set a catch(eval("1 +"), 33) +set a catch(eval("1/0"), 34) +set a catch(eval("1 / / 2"), 35) +DEBUG -x + DEBUG -e # Output expression-node stats