diff --git a/contrib/remind-conf-mode/remind-conf-mode.el b/contrib/remind-conf-mode/remind-conf-mode.el index 9feadfec..7d7fde25 100644 --- a/contrib/remind-conf-mode/remind-conf-mode.el +++ b/contrib/remind-conf-mode/remind-conf-mode.el @@ -116,8 +116,8 @@ "IFTRIG" "IN" "INC" "INCLUDE" "INCLUDECMD" "INFO" "LAST" "LASTDAY" "LASTWORKDAY" "MAYBE" "MAYBE-UNCOMPUTABLE" "MSF" "MSG" "NOQUEUE" "OMIT" "OMITFUNC" "ONCE" "POP" - "POP-OMIT-CONTEXT" "POP-VARS" "PRESERVE" "PRIORITY" "PS" - "PSFILE" "PUSH" "PUSH-VARS" "PUSH-OMIT-CONTEXT" "REM" + "POP-OMIT-CONTEXT" "POP-FUNCS" "POP-VARS" "PRESERVE" "PRIORITY" "PS" + "PSFILE" "PUSH" "PUSH-FUNCS" "PUSH-VARS" "PUSH-OMIT-CONTEXT" "REM" "RUN" "SATISFY" "SCAN" "SCANFROM" "SCHED" "SECOND" "SET" "SKIP" "SPECIAL" "SYSINCLUDE" "TAG" "THIRD" "THROUGH" "TRANSLATE" "TRANS" "UNSET" "UNTIL" "WARN") diff --git a/man/remind.1.in b/man/remind.1.in index db8f09e5..4178a54f 100644 --- a/man/remind.1.in +++ b/man/remind.1.in @@ -5108,22 +5108,6 @@ it is defined. However, if \fIfunc_a\fR exists, then it is renamed to was defined prior to the \fBFRENAME\fR command, then that old definition is lost. .PP -\fBFRENAME\fR is useful if you want to save and restore the definition -of a user-defined function. For example, you might want to define -a \fBmsgprefix\fR function for a block of reminders, but not permanently -overwrite any existing definition. You could do something like this: -.PP -.nf - FRENAME msgprefix saved_msgprefix - FSET msgprefix(x) "My new prefix: " - INCLUDE block_of_reminders.rem - FRENAME saved_msgprefix msgprefix -.PP -The file \fBblock_of_reminders.rem\fR would be executed with the -\fBmsgprefix\fR function defined above. After the second FRENAME, -\fBmsgprefix\fR would be restored to its previous definition if -it had one, or simply unset if it was not previously defined. -.PP If either argument to the \fBFRENAME\fR command is the name of a built-in function, the command fails with an error message and does nothing. .PP @@ -5143,6 +5127,51 @@ following example: .PP to define a function and suppress any "redefined function" warning. .PP +.SH SAVING AND RESTORING FUNCTIONS +.PP +Occasionally, it is useful to redefine a function for a specific block of +reminders, but to restore its original definition at the end of that +block. Just as with variables, functions can be pushed onto and popped +off an internal stack. Here is an example: +.PP +.nf + PUSH-FUNCS msgprefix + FSET msgprefix(x) "My new prefix: " + INCLUDE block_of_reminders.rem + POP-FUNCS +.fi +.PP +The file \fBblock_of_reminders.rem\fR would be executed with the +\fBmsgprefix\fR function defined above. After the POP-FUNCS +\fBmsgprefix\fR would be restored to its previous definition if +it had one, or simply unset if it was not previously defined. +.PP +The command PUSH-FUNCS takes a space-separated list of function names. +All of the named user-defined functions will be saved to an internal +stack. You can even push names that are not defined as any function. +.PP +After a function name has been pushed, \fBRemind\fR will not issue a +warning if you redefine it, because presumably the purpose of pushing +it in the first place is to redefine it. +.PP +The command POP-FUNCS restores the definitions of all the user-defined +functions in the corresponding PUSH-FUNCS command. If undefined functions +were pushed onto the stack, then POP-FUNCS makes those functions undefined +again. Here's one more example: +.PP +.nf + FUNSET a + FSET b(x) 2*x + REM MSG [b(3)] # Outputs 6 + PUSH-FUNCS a b + FSET a(x) 22*x + FSET b(x) 3*x + REM MSG [a(3)] [b(3)] # Outputs 66 9 + POP-FUNCS + REM MSG [a(3)] # Undefined function "a" + REM MSG [b(3)] # Outputs 6 +.fi +.PP .SH PRECISE SCHEDULING .PP The \fBWARN\fR keyword allows precise control over advance warning in diff --git a/src/calendar.c b/src/calendar.c index 6c04901b..8e738b86 100644 --- a/src/calendar.c +++ b/src/calendar.c @@ -1812,6 +1812,12 @@ static void GenerateCalEntries(int col) case T_PopVars: r=PopVars(&p); break; + case T_PushFuncs: + r=PushUserFuncs(&p); + break; + case T_PopFuncs: + r=PopUserFuncs(&p); + break; case T_Preserve: r=DoPreserve(&p); break; case T_Expr: r = DoExpr(&p); break; case T_Translate: r = DoTranslate(&p); break; diff --git a/src/err.h b/src/err.h index 7b4d3404..503b133e 100644 --- a/src/err.h +++ b/src/err.h @@ -35,13 +35,13 @@ #define E_CANT_COERCE 13 #define E_BAD_TYPE 14 #define E_DATE_OVER 15 -/* #define E_STACK_ERR 16 */ +#define E_POPF_NO_PUSH 16 #define E_DIV_ZERO 17 #define E_NOSUCH_VAR 18 #define E_EOLN 19 #define E_EOF 20 #define E_IO_ERR 21 -/* #define E_LINE_2_LONG 22 */ +#define E_PUSHF_NO_POP 22 #define E_SWERR 23 #define E_BAD_DATE 24 #define E_2FEW_ARGS 25 @@ -163,13 +163,13 @@ EXTERN char *ErrMsg[] /* E_CANT_COERCE */ "Can't coerce", /* E_BAD_TYPE */ "Type mismatch", /* E_DATE_OVER */ "Date overflow", -/* E_STACK_ERR */ "", +/* E_POPF_NO_PUSH */ "POP-FUNCS without matching PUSH-FUNCS", /* E_DIV_ZERO */ "Division by zero", /* E_NOSUCH_VAR */ "Undefined variable", /* E_EOLN */ "Unexpected end of line", /* E_EOF */ "Unexpected end of file", /* E_IO_ERR */ "I/O error", -/* E_LINE_2_LONG */ "", +/* E_PUSHF_NO_POP */ "Warning: PUSH-FUNCS without matching POP-FUNCS", /* E_SWERR */ "Internal error", /* E_BAD_DATE */ "Bad date specification", /* E_2FEW_ARGS */ "Not enough arguments", diff --git a/src/expr.c b/src/expr.c index 1ba00002..e6552a57 100644 --- a/src/expr.c +++ b/src/expr.c @@ -1844,7 +1844,7 @@ expr_node * free_expr_tree(expr_node *node) /* with the original. Returns NULL and sets *r on failure. */ /* */ /***************************************************************/ -expr_node * clone_expr_tree(expr_node *src, int *r) +expr_node * clone_expr_tree(expr_node const *src, int *r) { int rc; expr_node *dest = alloc_expr_node(r); diff --git a/src/main.c b/src/main.c index dc6d6c40..adaa06ef 100644 --- a/src/main.c +++ b/src/main.c @@ -189,6 +189,10 @@ int main(int argc, char *argv[]) FreshLine = 1; Eprint("%s", GetErr(E_PUSHV_NO_POP)); } + if (EmptyUserFuncStack(1)) { + FreshLine = 1; + Eprint("%s", GetErr(E_PUSHF_NO_POP)); + } if (!Daemon && !NextMode && !NumTriggered && !NumQueued) { printf("%s\n", GetErr(E_NOREMINDERS)); } else if (!Daemon && !NextMode && !NumTriggered) { @@ -242,6 +246,7 @@ PerIterationInit(void) ClearGlobalOmits(); DestroyOmitContexts(1); EmptyVarStack(1); + EmptyUserFuncStack(1); DestroyVars(0); DefaultColorR = -1; DefaultColorG = -1; @@ -379,6 +384,12 @@ static void DoReminders(void) case T_PopVars: r=PopVars(&p); break; + case T_PushFuncs: + r=PushUserFuncs(&p); + break; + case T_PopFuncs: + r=PopUserFuncs(&p); + break; case T_Expr: r = DoExpr(&p); break; case T_Translate: r = DoTranslate(&p); break; case T_RemType: if (tok.val == RUN_TYPE) { diff --git a/src/protos.h b/src/protos.h index b40a1091..06e6aeee 100644 --- a/src/protos.h +++ b/src/protos.h @@ -64,7 +64,7 @@ int truthy(Value const *v); void unlimit_execution_time(void); expr_node *free_expr_tree(expr_node *node); -expr_node *clone_expr_tree(expr_node *node, int *r); +expr_node *clone_expr_tree(expr_node const *node, int *r); int EvalExpr (char const **e, Value *v, ParsePtr p); int DoCoerce (char type, Value *v); char const *PrintValue (Value *v, FILE *fp); @@ -151,6 +151,9 @@ int DoDump (ParsePtr p); int PushVars(ParsePtr p); int EmptyVarStack(int print_unmatched); int PopVars(ParsePtr p); +int PushUserFuncs(ParsePtr p); +int EmptyUserFuncStack(int print_unmatched); +int PopUserFuncs(ParsePtr p); void DumpVarTable (int dump_constness); void DumpUnusedVars(void); void DestroyVars (int all); diff --git a/src/token.c b/src/token.c index 2b2d4e1d..14c32c2c 100644 --- a/src/token.c +++ b/src/token.c @@ -90,12 +90,14 @@ Token TokArray[] = { { "omit", 4, T_Omit, 0 }, { "omitfunc", 8, T_OmitFunc, 0 }, { "once", 4, T_Once, 0 }, + { "pop-funcs", 9, T_PopFuncs, 0 }, { "pop-omit-context", 3, T_Pop, 0 }, - { "pop-vars", 8, T_PopVars, 0 }, + { "pop-vars", 8, T_PopVars, 0 }, { "preserve", 8, T_Preserve, 0 }, { "priority", 8, T_Priority, 0 }, { "ps", 2, T_RemType, PS_TYPE }, - { "psfile", 6, T_RemType, PSF_TYPE }, + { "psfile", 6, T_RemType, PSF_TYPE }, + { "push-funcs", 10, T_PushFuncs, 0 }, { "push-omit-context", 4, T_Push, 0 }, { "push-vars", 9, T_PushVars, 0 }, { "rem", 3, T_Rem, 0 }, diff --git a/src/types.h b/src/types.h index 0d19da09..922f509b 100644 --- a/src/types.h +++ b/src/types.h @@ -232,10 +232,11 @@ enum TokTypes T_Frename, T_Fset, T_Funset, T_If, T_IfTrig, T_In, T_Include, T_IncludeCmd, T_IncludeR, T_IncludeSys, T_Info, T_LastBack, T_LongTime, T_MaybeUncomputable, T_Month, T_NoQueue, T_Number, - T_Omit, T_OmitFunc, T_Once, T_Ordinal, T_Pop, T_PopVars, - T_Preserve, T_Priority, T_Push, T_PushVars, T_Rem, T_RemType, - T_Rep, T_Scanfrom, T_Sched, T_Set, T_Skip, T_Tag, T_Through, T_Time, - T_Translate, T_UnSet, T_Until, T_Warn, T_WkDay, T_Year + T_Omit, T_OmitFunc, T_Once, T_Ordinal, T_Pop, T_PopFuncs, T_PopVars, + T_Preserve, T_Priority, T_Push, T_PushFuncs, T_PushVars, T_Rem, + T_RemType, T_Rep, T_Scanfrom, T_Sched, T_Set, T_Skip, T_Tag, + T_Through, T_Time, T_Translate, T_UnSet, T_Until, T_Warn, T_WkDay, + T_Year }; /* The structure of a token */ @@ -311,6 +312,7 @@ typedef struct udf_struct { int lineno; int lineno_start; int recurse_flag; + int been_pushed; } UserFunc; /* A pushed systtem variable */ diff --git a/src/userfns.c b/src/userfns.c index f1e3ceee..c20cd2bf 100644 --- a/src/userfns.c +++ b/src/userfns.c @@ -244,7 +244,7 @@ int DoFset(ParsePtr p) return OK; } /* Warn about redefinition */ - if (!suppress_redefined_function_warning) { + if (!suppress_redefined_function_warning && !existing->been_pushed) { Wprint(tr("Function `%s' redefined: previously defined at %s(%s)"), existing->name, existing->filename, line_range(existing->lineno_start, existing->lineno)); } @@ -274,6 +274,7 @@ int DoFset(ParsePtr p) func->lineno = LineNo; func->lineno_start = LineNoStart; func->recurse_flag = 0; + func->been_pushed = 0; if (in_constant_context()) { func->is_constant = 1; } else { @@ -378,6 +379,10 @@ int DoFset(ParsePtr p) /* Save the argument names */ if (func->nargs) { func->args = calloc(func->nargs, sizeof(char *)); + if (!func->args) { + DestroyUserFunc(func); + return E_NO_MEM; + } for (i=0; inargs; i++) { func->args[i] = StrDup(local_array[i].name); if (!func->args[i]) { @@ -549,3 +554,222 @@ dump_userfunc_hash_stats(void) hash_table_dump_stats(&FuncHash, ErrFp); } +typedef struct pushed_userfuncs { + struct pushed_userfuncs *next; + char const *filename; + int lineno; + int num_funcs; + int alloc_funcs; + UserFunc **funcs; +} PushedUserFuncs; + +static PushedUserFuncs *UserFuncStack = NULL; +static UserFunc *clone_userfunc(char const *name, int *r) +{ + int i; + UserFunc *src; + UserFunc *dest = NEW(UserFunc); + *r = E_NO_MEM; + if (!dest) { + return NULL; + } + + /* Find the source function */ + src = FindUserFunc(name); + + /* If it doesn't exist, use sentinal values to indicate that */ + if (!src) { + *r = OK; + StrnCpy(dest->name, name, VAR_NAME_LEN); + dest->is_constant = 0; + dest->node = NULL; + dest->args = NULL; + dest->nargs = -1; + dest->filename = NULL; + dest->lineno = -1; + dest->lineno_start = -1; + dest->recurse_flag = 0; + dest->been_pushed = 0; + return dest; + } + /* Copy the whole thing; then adjust */ + *dest = *src; + + /* Allow warning-free redefinition of original function */ + src->been_pushed = 1; + + /* For safety */ + dest->node = NULL; + dest->args = NULL; + + /* Copy args */ + if (dest->nargs) { + dest->args = calloc(dest->nargs, sizeof(char *)); + if (!dest->args) { + DestroyUserFunc(dest); + return NULL; + } + for (i=0; inargs; i++) { + dest->args[i] = StrDup(src->args[i]); + if (!dest->args[i]) { + DestroyUserFunc(dest); + return NULL; + } + } + } + + /* Copy expr */ + dest->node = clone_expr_tree(src->node, r); + if (!dest->node) { + DestroyUserFunc(dest); + return NULL; + } + *r = OK; + return dest; +} + +static int add_func_to_push(char const *name, PushedUserFuncs *pf) +{ + int r; + UserFunc *clone = clone_userfunc(name, &r); + if (!clone) { + return r; + } + if (!pf->alloc_funcs) { + pf->funcs = calloc(4, sizeof(UserFunc *)); + if (!pf->funcs) { + DestroyUserFunc(clone); + return E_NO_MEM; + } + pf->alloc_funcs = 4; + } else { + if (pf->num_funcs == pf->alloc_funcs) { + pf->funcs = realloc(pf->funcs, 2 * pf->alloc_funcs * sizeof(UserFunc *)); + if (!pf->funcs) { + DestroyUserFunc(clone); + return E_NO_MEM; + } + pf->alloc_funcs *= 2; + } + } + pf->funcs[pf->num_funcs] = clone; + pf->num_funcs++; + return OK; +} + +static void free_pushed_funcs(PushedUserFuncs *pf) +{ + int i; + if (pf->funcs) { + for(i=0; inum_funcs; i++) { + if (pf->funcs[i]) { + DestroyUserFunc(pf->funcs[i]); + } + } + free(pf->funcs); + } + free(pf); +} + +int PushUserFuncs(ParsePtr p) +{ + int r; + DynamicBuffer buf; + char const *name; + + DBufInit(&buf); + PushedUserFuncs *pf = NEW(PushedUserFuncs); + if (!pf) { + return E_NO_MEM; + } + pf->next = NULL; + pf->filename = GetCurrentFilename(); + pf->lineno = LineNo; + pf->num_funcs = 0; + pf->alloc_funcs = 0; + + while(1) { + r = ParseIdentifier(p, &buf); + if (r == E_EOLN) { + break; + } + if (r) { + DBufFree(&buf); + free_pushed_funcs(pf); + return r; + } + name = DBufValue(&buf); + if (*name == '$') { + DBufFree(&buf); + free_pushed_funcs(pf); + return E_BAD_ID; + } + + r = add_func_to_push(name, pf); + + DBufFree(&buf); + if (r != OK) { + free_pushed_funcs(pf); + return r; + } + } + if (pf->num_funcs == 0) { + free_pushed_funcs(pf); + return E_EOLN; + } + pf->next = UserFuncStack; + UserFuncStack = pf; + return OK; +} + +int PopUserFuncs(ParsePtr p) +{ + int i; + PushedUserFuncs *pf = UserFuncStack; + int r = VerifyEoln(p); + if (r != OK) { + return r; + } + if (!pf) { + return E_POPF_NO_PUSH; + } + UserFuncStack = UserFuncStack->next; + if (strcmp(pf->filename, GetCurrentFilename())) { + Wprint(tr("POP-FUNCS at %s:%d matches PUSH-FUNCS in different file: %s:%d"), GetCurrentFilename(), LineNo, pf->filename, pf->lineno); + } + for (i=0; inum_funcs; i++) { + UserFunc *clone = pf->funcs[i]; + FUnset(clone->name); + if (clone->nargs < 0 && !clone->node) { + /* Popping a function that should simply be unset... we are done */ + continue; + } + + /* Insert the clone into the hash table */ + FSet(clone); + + /* Make sure we don't free the clone! */ + pf->funcs[i] = NULL; + } + free_pushed_funcs(pf); + return OK; +} + +int EmptyUserFuncStack(int print_unmatched) +{ + int j = 0; + PushedUserFuncs *next; + while(UserFuncStack) { + if (print_unmatched) { + Wprint(tr("Unmatched PUSH-FUNCS at %s(%d)"), + UserFuncStack->filename, + UserFuncStack->lineno); + } + j++; + next = UserFuncStack->next; + free_pushed_funcs(UserFuncStack); + UserFuncStack = next; + } + return j; +} + diff --git a/tests/test.cmp b/tests/test.cmp index 9810ea16..c72179aa 100644 --- a/tests/test.cmp +++ b/tests/test.cmp @@ -16519,12 +16519,73 @@ wkdaynum("telephone") => Invalid weekday name ../tests/test.rem(1664): wkdaynum(): Invalid weekday name ../tests/test.rem(1675): Cannot modify system variable: `$NumTrig' ../tests/test.rem(1676): POP-VARS without matching PUSH-VARS + +FUNSET a +FSET b(x, y) x*y +FSET c() 33 + +set a a(2) +../tests/test.rem(1685): Undefined function: `a' +set a b(2) +b(?) => Not enough arguments +../tests/test.rem(1686): b(): Not enough arguments +set a b(2, 3) +Entering UserFN b(2, 3) +x => 2 +y => 3 +2 * 3 => 6 +Leaving UserFN b(2, 3) => 6 +set a c() +Entering UserFN c() +Leaving UserFN c() => 33 + +PUSH-FUNCS a b c + +FSET a(x) 42 +FSET b(x, y) x*y*2 +FSET c() 66 + +set a a(2) +Entering UserFN a(2) +Leaving UserFN a(2) => 42 +set a b(2) +b(?) => Not enough arguments +../tests/test.rem(1697): b(): Not enough arguments +set a b(2, 3) +Entering UserFN b(2, 3) +x => 2 +y => 3 +2 * 3 => 6 +6 * 2 => 12 +Leaving UserFN b(2, 3) => 12 +set a c() +Entering UserFN c() +Leaving UserFN c() => 66 + +POP-FUNCS + +set a a(2) +../tests/test.rem(1703): Undefined function: `a' +set a b(2) +b(?) => Not enough arguments +../tests/test.rem(1704): b(): Not enough arguments +set a b(2, 3) +Entering UserFN b(2, 3) +x => 2 +y => 3 +2 * 3 => 6 +Leaving UserFN b(2, 3) => 6 +set a c() +Entering UserFN c() +Leaving UserFN c() => 33 + +DEBUG -xe Variable hash table statistics: Entries: 100144; Buckets: 87719; Non-empty Buckets: 66301 Maxlen: 5; Minlen: 0; Avglen: 1.142; Stddev: 0.878; Avg nonempty len: 1.510 Growths: 13; Shrinks: 0 Function hash table statistics: - Entries: 100016; Buckets: 87719; Non-empty Buckets: 63572 + Entries: 100018; Buckets: 87719; Non-empty Buckets: 63574 Maxlen: 5; Minlen: 0; Avglen: 1.140; Stddev: 0.934; Avg nonempty len: 1.573 Growths: 13; Shrinks: 0 Dedupe hash table statistics: @@ -24376,6 +24437,7 @@ noqueue omit omitfunc once +pop-funcs pop pop-omit-context pop-vars @@ -24383,6 +24445,7 @@ preserve priority ps psfile +push-funcs push push-omit-context push-vars @@ -24804,11 +24867,13 @@ TRANSLATE "POP-VARS without matching PUSH-VARS" "" TRANSLATE "Can't coerce" "" TRANSLATE "Type mismatch" "" TRANSLATE "Date overflow" "" +TRANSLATE "POP-FUNCS without matching PUSH-FUNCS" "" TRANSLATE "Division by zero" "" TRANSLATE "Undefined variable" "" TRANSLATE "Unexpected end of line" "" TRANSLATE "Unexpected end of file" "" TRANSLATE "I/O error" "" +TRANSLATE "Warning: PUSH-FUNCS without matching POP-FUNCS" "" TRANSLATE "Internal error" "" TRANSLATE "Bad date specification" "" TRANSLATE "Not enough arguments" "" @@ -24928,6 +24993,7 @@ TRANSLATE "Nonexistence of global variable `%s' makes value() 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-FUNCS at %s:%d matches PUSH-FUNCS in different file: %s:%d" "" TRANSLATE "POP-OMIT-CONTEXT at %s:%d matches PUSH-OMIT-CONTEXT in different file: %s:%d" "" TRANSLATE "POP-VARS at %s:%d matches PUSH-VARS in different file: %s:%d" "" TRANSLATE "Potential function definition considered non-constant because of context" "" @@ -24946,6 +25012,7 @@ TRANSLATE "Scanning directory `%s' for *.rem files" "" TRANSLATE "System variable `$%s' makes expression non-constant" "" TRANSLATE "The following variables were set, but not subsequently used:" "" TRANSLATE "Undefined %s function: `%s'" "" +TRANSLATE "Unmatched PUSH-FUNCS at %s(%d)" "" TRANSLATE "Unmatched PUSH-OMIT-CONTEXT at %s(%d)" "" TRANSLATE "Unmatched PUSH-VARS at %s(%d)" "" TRANSLATE "Unrecognized command; interpreting as REM" "" diff --git a/tests/test.rem b/tests/test.rem index d8c533cb..85f2c3c2 100644 --- a/tests/test.rem +++ b/tests/test.rem @@ -1675,6 +1675,38 @@ POP-VARS PUSH-VARS $NumTrig POP-VARS +# Test push/pop of functions +DEBUG +xe + +FUNSET a +FSET b(x, y) x*y +FSET c() 33 + +set a a(2) +set a b(2) +set a b(2, 3) +set a c() + +PUSH-FUNCS a b c + +FSET a(x) 42 +FSET b(x, y) x*y*2 +FSET c() 66 + +set a a(2) +set a b(2) +set a b(2, 3) +set a c() + +POP-FUNCS + +set a a(2) +set a b(2) +set a b(2, 3) +set a c() + +DEBUG -xe + # Don't want Remind to queue reminders EXIT