diff --git a/contrib/remind-conf-mode/remind-conf-mode.el b/contrib/remind-conf-mode/remind-conf-mode.el index 12647aaa..f54032e4 100644 --- a/contrib/remind-conf-mode/remind-conf-mode.el +++ b/contrib/remind-conf-mode/remind-conf-mode.el @@ -164,7 +164,7 @@ (defconst remind-builtin-functions (sort (list "_" "abs" "access" "adawn" "adusk" "ampm" "ansicolor" "args" "asc" - "baseyr" "catch" "char" "choose" "coerce" "columns" "current" "date" + "baseyr" "catch" "catcherr" "char" "choose" "coerce" "columns" "current" "date" "datepart" "datetime" "dawn" "day" "daysinmon" "defined" "dosubst" "dusk" "easterdate" "escape" "evaltrig" "filedate" "filedatetime" "filedir" "filename" "getenv" "hebdate" "hebday" "hebmon" "hebyear" diff --git a/man/remind.1.in b/man/remind.1.in index 5c1eef8e..6b63ccb9 100644 --- a/man/remind.1.in +++ b/man/remind.1.in @@ -3288,6 +3288,29 @@ Note in the last example that catch does \fInot\fR protect you from errors in the evaluation of \fBarg2\fR. .RE .TP +.B catcherr() +Returns a string representing the error message from the most +recently-evaluated \fBcatch()\fR function whose first argument yielded +an error. Note that the error message is always in English even if +\fBRemind\fR has been localized; this lets you reliably test the return +value. Here are some examples: +.RS +.PP +.nf + # No "catch" invocations yet: sets a to "Ok" + set a catcherr() + + # Sets b to "oops" and a to "Division by zero" + set b catch(1/0, "oops") + set a catcherr() + + # Sets b to 1 and a to "Division by zero". The catch error + # is never cleared by a non-error catch() + set b catch(4-3, "not-evaluated") + set a catcherr() +.fi +.RE +.TP .B char(i_i1 [,i_i2...]) This function can take any number of \fBINT\fR arguments. It returns a \fBSTRING\fR consisting of the bytes specified by the arguments. diff --git a/src/funcs.c b/src/funcs.c index 05a7e460..df8ec83b 100644 --- a/src/funcs.c +++ b/src/funcs.c @@ -64,6 +64,9 @@ #define PUT(x) DBufPuts(&DebugBuf, x) #define OUT() do { fprintf(ErrFp, "%s\n", DBufValue(&DebugBuf)); DBufFree(&DebugBuf); } while(0) +/* Last error from catch() */ +static int LastCatchError = OK; + static int solstice_equinox_for_year(int y, int which); @@ -79,6 +82,7 @@ static int FArgs (func_info *); static int FAsc (func_info *); static int FBaseyr (func_info *); 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 FCoerce (func_info *); @@ -245,6 +249,7 @@ BuiltinFunc Func[] = { { "asc", 1, 1, 1, FAsc, NULL }, { "baseyr", 0, 0, 1, FBaseyr, NULL }, { "catch", 2, 2, 1, NULL, FCatch }, /* NEW-STYLE */ + { "catcherr", 0, 0, 0, FCatchErr, NULL }, { "char", 1, NO_MAX, 1, FChar, NULL }, { "choose", 2, NO_MAX, 1, NULL, FChoose }, /*NEW-STYLE*/ { "coerce", 2, 2, 1, FCoerce, NULL }, @@ -1320,6 +1325,9 @@ static int FCatch(expr_node *node, Value *locals, Value *ans, int *nonconst) } return r; } + + /* Save the catch error */ + LastCatchError = r; if (DebugFlag & DB_PRTEXPR) { PUT("*"); PUT(GetErr(r)); @@ -1344,6 +1352,19 @@ static int FCatch(expr_node *node, Value *locals, Value *ans, int *nonconst) } return r; } + +/***************************************************************/ +/* */ +/* FCatchErr */ +/* Return (as a string) the English error thrown by the last */ +/* catch() expression that errored out. */ +/* */ +/***************************************************************/ +static int FCatchErr(func_info *info) +{ + return RetStrVal(GetEnglishErr(LastCatchError), info); +} + /***************************************************************/ /* */ /* FChoose */ diff --git a/src/main.c b/src/main.c index 8410ce6c..676abd33 100644 --- a/src/main.c +++ b/src/main.c @@ -2138,6 +2138,14 @@ int GetOnceDate(void) return OnceDate; } +char const *GetEnglishErr(int r) +{ + if (r < 0 || r >= NumErrs) { + r = E_SWERR; + } + return ErrMsg[r]; +} + char const *GetErr(int r) { char const *msg; diff --git a/src/protos.h b/src/protos.h index 4973442f..67ff48b6 100644 --- a/src/protos.h +++ b/src/protos.h @@ -271,6 +271,7 @@ void InitTranslationTable(void); char const *GetTranslatedString(char const *orig); int GetTranslatedStringTryingVariants(char const *orig, DynamicBuffer *out); char const *GetErr(int r); +char const *GetEnglishErr(int r); char const *tr(char const *s); void print_escaped_string(FILE *fp, char const *s); void print_escaped_string_helper(FILE *fp, char const *s, int esc_for_remind, int json); diff --git a/tests/test.cmp b/tests/test.cmp index 06296122..f3cb4a76 100644 --- a/tests/test.cmp +++ b/tests/test.cmp @@ -16256,27 +16256,39 @@ D'oh, a file whose name has spaces! ../tests/with space.rem DEBUG +x # Test catch() built-in function +set m catcherr() +catcherr() => "Ok" set a catch(4/2, 33) 4 / 2 => 2 catch(2, ?) => 2 +set m catcherr() +catcherr() => "Ok" set a catch(4/0, 33) 4 / 0 => Division by zero catch(*Division by zero*, 33) => 33 +set m catcherr() +catcherr() => "Division by zero" set a catch(4/0, 1/0) 4 / 0 => Division by zero 1 / 0 => Division by zero -../tests/test.rem(1444): `/': Division by zero +../tests/test.rem(1447): `/': Division by zero catch(*Division by zero*, *Division by zero*) => Division by zero +set m catcherr() +catcherr() => "Division by zero" set a catch(catch(4/0, 1/0), 39) 4 / 0 => Division by zero 1 / 0 => Division by zero catch(*Division by zero*, *Division by zero*) => Division by zero catch(*Division by zero*, 39) => 39 +set m catcherr() +catcherr() => "Division by zero" set a catch(4/0, catch(4/0, 39)) 4 / 0 => Division by zero 4 / 0 => Division by zero catch(*Division by zero*, 39) => 39 catch(*Division by zero*, 39) => 39 +set m catcherr() +catcherr() => "Division by zero" set a catch(1/0, catch("f" * "g", catch($IntMin*88, 42))) 1 / 0 => Division by zero "f" * "g" => Type mismatch @@ -16285,10 +16297,12 @@ $IntMin => -2147483648 catch(*Number too high*, 42) => 42 catch(*Type mismatch*, 42) => 42 catch(*Division by zero*, 42) => 42 +set m catcherr() +catcherr() => "Number too high" DEBUG -x DEBUG -e Variable hash table statistics: - Entries: 100141; Buckets: 87719; Non-empty Buckets: 66299 + Entries: 100142; Buckets: 87719; Non-empty Buckets: 66300 Maxlen: 5; Minlen: 0; Avglen: 1.142; Stddev: 0.878; Avg nonempty len: 1.510 Growths: 13; Shrinks: 0 Function hash table statistics: @@ -24110,6 +24124,7 @@ args asc baseyr catch +catcherr char choose coerce diff --git a/tests/test.rem b/tests/test.rem index 181f1d5d..56b880f7 100644 --- a/tests/test.rem +++ b/tests/test.rem @@ -1439,12 +1439,19 @@ do "with space.rem" DEBUG +x # Test catch() built-in function +set m catcherr() set a catch(4/2, 33) +set m catcherr() set a catch(4/0, 33) +set m catcherr() set a catch(4/0, 1/0) +set m catcherr() set a catch(catch(4/0, 1/0), 39) +set m catcherr() set a catch(4/0, catch(4/0, 39)) +set m catcherr() set a catch(1/0, catch("f" * "g", catch($IntMin*88, 42))) +set m catcherr() DEBUG -x DEBUG -e