Add "eval()" built-in function.

This commit is contained in:
Dianne Skoll
2025-05-22 13:52:07 -04:00
parent acf570512d
commit 3118f8d2a4
7 changed files with 137 additions and 30 deletions

View File

@@ -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"

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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)
{

View File

@@ -66,6 +66,7 @@ typedef struct {
int nargs;
Value *args;
Value retval;
int nonconst;
} func_info;
/* Forward reference */

View File

@@ -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 <const>
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

View File

@@ -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