"Fossies" - the Fresh Open Source Software Archive

Member "remind-03.03.09/src/dorem.c" (15 Oct 2021, 34169 Bytes) of package /linux/misc/remind-03.03.09.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "dorem.c" see the Fossies "Dox" file reference documentation and the last Fossies "Diffs" side-by-side code changes report: 03.03.07_vs_03.03.08.

    1 /***************************************************************/
    2 /*                                                             */
    3 /*  DOREM.C                                                    */
    4 /*                                                             */
    5 /*  Contains routines for parsing reminders and evaluating     */
    6 /*  triggers.  Also contains routines for parsing OMIT         */
    7 /*  commands.                                                  */
    8 /*                                                             */
    9 /*  This file is part of REMIND.                               */
   10 /*  Copyright (C) 1992-2021 by Dianne Skoll                    */
   11 /*                                                             */
   12 /***************************************************************/
   13 
   14 #include "config.h"
   15 #include <stdio.h>
   16 #include <ctype.h>
   17 #include <string.h>
   18 
   19 #include <stdlib.h>
   20 
   21 #include "types.h"
   22 #include "globals.h"
   23 #include "err.h"
   24 #include "protos.h"
   25 #include "expr.h"
   26 
   27 static int ParseTimeTrig (ParsePtr s, TimeTrig *tim, int save_in_globals);
   28 static int ParseLocalOmit (ParsePtr s, Trigger *t);
   29 static int ParseScanFrom (ParsePtr s, Trigger *t, int type);
   30 static int ParsePriority (ParsePtr s, Trigger *t);
   31 static int ParseUntil (ParsePtr s, Trigger *t);
   32 static int ShouldTriggerBasedOnWarn (Trigger *t, int jul, int *err);
   33 static int ComputeTrigDuration(TimeTrig *t);
   34 
   35 static int
   36 ComputeTrigDuration(TimeTrig *t)
   37 {
   38     if (t->ttime == NO_TIME ||
   39     t->duration == NO_TIME) {
   40     return 0;
   41     }
   42     return (t->ttime + t->duration - 1) / MINUTES_PER_DAY;
   43 }
   44 
   45 /***************************************************************/
   46 /*                                                             */
   47 /*  DoRem                                                      */
   48 /*                                                             */
   49 /*  Do the REM command.                                        */
   50 /*                                                             */
   51 /***************************************************************/
   52 int DoRem(ParsePtr p)
   53 {
   54 
   55     Trigger trig;
   56     TimeTrig tim;
   57     int r, err;
   58     int jul;
   59     DynamicBuffer buf;
   60     Token tok;
   61 
   62     DBufInit(&buf);
   63 
   64     /* Parse the trigger date and time */
   65     if ( (r=ParseRem(p, &trig, &tim, 1)) ) {
   66     FreeTrig(&trig);
   67     return r;
   68     }
   69 
   70     if (trig.typ == NO_TYPE) {
   71     PurgeEchoLine("%s\n%s\n", "#!P! Cannot parse next line", CurLine);
   72     FreeTrig(&trig);
   73     return E_EOLN;
   74     }
   75     if (trig.typ == SAT_TYPE) {
   76     PurgeEchoLine("%s\n", "#!P: Cannot purge SATISFY-type reminders");
   77     PurgeEchoLine("%s\n", CurLine);
   78     r=DoSatRemind(&trig, &tim, p);
   79     if (r) {
   80             if (r == E_CANT_TRIG && trig.maybe_uncomputable) {
   81                 r = OK;
   82             }
   83         FreeTrig(&trig);
   84         if (r == E_EXPIRED) return OK;
   85         return r;
   86     }
   87     if (!LastTrigValid) {
   88         FreeTrig(&trig);
   89         return OK;
   90     }
   91     r=ParseToken(p, &buf);
   92     if (r) {
   93         FreeTrig(&trig);
   94         return r;
   95     }
   96     FindToken(DBufValue(&buf), &tok);
   97     DBufFree(&buf);
   98     if (tok.type == T_Empty || tok.type == T_Comment) {
   99         DBufFree(&buf);
  100         FreeTrig(&trig);
  101         return OK;
  102     }
  103     if (tok.type != T_RemType || tok.val == SAT_TYPE) {
  104         DBufFree(&buf);
  105         FreeTrig(&trig);
  106         return E_PARSE_ERR;
  107     }
  108     if (tok.val == PASSTHRU_TYPE) {
  109         r=ParseToken(p, &buf);
  110         if (r) {
  111         FreeTrig(&trig);
  112         return r;
  113         }
  114         if (!DBufLen(&buf)) {
  115         FreeTrig(&trig);
  116         DBufFree(&buf);
  117         return E_EOLN;
  118         }
  119         StrnCpy(trig.passthru, DBufValue(&buf), PASSTHRU_LEN);
  120         DBufFree(&buf);
  121     }
  122     trig.typ = tok.val;
  123     jul = LastTriggerDate;
  124     if (!LastTrigValid || PurgeMode) {
  125         FreeTrig(&trig);
  126         return OK;
  127     }
  128     } else {
  129     /* Calculate the trigger date */
  130     jul = ComputeTrigger(trig.scanfrom, &trig, &tim, &r, 1);
  131     if (r) {
  132         if (PurgeMode) {
  133         PurgeEchoLine("%s: %s\n", "#!P! Problem calculating trigger date", ErrMsg[r]);
  134         PurgeEchoLine("%s\n", CurLine);
  135         }
  136             if (r == E_CANT_TRIG && trig.maybe_uncomputable) {
  137                 r = OK;
  138             }
  139         FreeTrig(&trig);
  140         return r;
  141     }
  142     }
  143 
  144     if (PurgeMode) {
  145     if (trig.expired || jul < JulianToday) {
  146         if (p->expr_happened) {
  147         if (p->nonconst_expr) {
  148             PurgeEchoLine("%s\n", "#!P: Next line may have expired, but contains non-constant expression");
  149             PurgeEchoLine("%s\n", CurLine);
  150         } else {
  151             PurgeEchoLine("%s\n", "#!P: Next line has expired, but contains expression...  please verify");
  152             PurgeEchoLine("#!P: Expired: %s\n", CurLine);
  153         }
  154         } else {
  155         PurgeEchoLine("#!P: Expired: %s\n", CurLine);
  156         }
  157     } else {
  158         PurgeEchoLine("%s\n", CurLine);
  159     }
  160     FreeTrig(&trig);
  161     return OK;
  162     }
  163 /* Queue the reminder, if necessary */
  164     if (jul == JulianToday &&
  165     !(!IgnoreOnce &&
  166       trig.once != NO_ONCE &&
  167       FileAccessDate == JulianToday))
  168     QueueReminder(p, &trig, &tim, trig.sched);
  169 /* If we're in daemon mode, do nothing over here */
  170     if (Daemon) {
  171     FreeTrig(&trig);
  172     return OK;
  173     }
  174 
  175     if (ShouldTriggerReminder(&trig, &tim, jul, &err)) {
  176     if ( (r=TriggerReminder(p, &trig, &tim, jul)) ) {
  177         FreeTrig(&trig);
  178         return r;
  179     }
  180     }
  181 
  182     FreeTrig(&trig);
  183     return OK;
  184 }
  185 
  186 /***************************************************************/
  187 /*                                                             */
  188 /*  ParseRem                                                   */
  189 /*                                                             */
  190 /*  Given a parse pointer, parse line and fill in a            */
  191 /*  trigger structure.                                         */
  192 /*                                                             */
  193 /***************************************************************/
  194 int ParseRem(ParsePtr s, Trigger *trig, TimeTrig *tim, int save_in_globals)
  195 {
  196     register int r;
  197     DynamicBuffer buf;
  198     Token tok;
  199     int y, m, d;
  200 
  201     DBufInit(&buf);
  202 
  203     trig->y = NO_YR;
  204     trig->m = NO_MON;
  205     trig->d = NO_DAY;
  206     trig->wd = NO_WD;
  207     trig->back = NO_BACK;
  208     trig->delta = NO_DELTA;
  209     trig->until = NO_UNTIL;
  210     trig->rep  = NO_REP;
  211     trig->localomit = NO_WD;
  212     trig->skip = NO_SKIP;
  213     trig->once = NO_ONCE;
  214     trig->typ = NO_TYPE;
  215     trig->scanfrom = NO_DATE;
  216     trig->from = NO_DATE;
  217     trig->priority = DefaultPrio;
  218     trig->sched[0] = 0;
  219     trig->warn[0] = 0;
  220     trig->omitfunc[0] = 0;
  221     trig->duration_days = 0;
  222     trig->eventstart = NO_TIME;
  223     trig->eventduration = NO_TIME;
  224     trig->maybe_uncomputable = 0;
  225     DBufInit(&(trig->tags));
  226     trig->passthru[0] = 0;
  227     tim->ttime = NO_TIME;
  228     tim->delta = NO_DELTA;
  229     tim->rep   = NO_REP;
  230     tim->duration = NO_TIME;
  231     if (save_in_globals) {
  232     LastTriggerTime = NO_TIME;
  233     }
  234 
  235     while(1) {
  236     /* Read space-delimited string */
  237     r = ParseToken(s, &buf);
  238     if (r) return r;
  239 
  240     /* Figure out what we've got */
  241     FindToken(DBufValue(&buf), &tok);
  242     switch(tok.type) {
  243     case T_Date:
  244         DBufFree(&buf);
  245         if (trig->d != NO_DAY) return E_DAY_TWICE;
  246         if (trig->m != NO_MON) return E_MON_TWICE;
  247         if (trig->y != NO_YR)  return E_YR_TWICE;
  248         FromJulian(tok.val, &y, &m, &d);
  249         trig->y = y;
  250         trig->m = m;
  251         trig->d = d;
  252         break;
  253 
  254     case T_DateTime:
  255         DBufFree(&buf);
  256         if (trig->d != NO_DAY) return E_DAY_TWICE;
  257         if (trig->m != NO_MON) return E_MON_TWICE;
  258         if (trig->y != NO_YR)  return E_YR_TWICE;
  259         FromJulian(tok.val / MINUTES_PER_DAY, &y, &m, &d);
  260         trig->y = y;
  261         trig->m = m;
  262         trig->d = d;
  263         tim->ttime = (tok.val % MINUTES_PER_DAY);
  264         if (save_in_globals) {
  265         LastTriggerTime = tim->ttime;
  266         SaveLastTimeTrig(tim);
  267         }
  268         break;
  269 
  270     case T_WkDay:
  271         DBufFree(&buf);
  272         if (trig->wd & (1 << tok.val)) return E_WD_TWICE;
  273         trig->wd |= (1 << tok.val);
  274         break;
  275 
  276     case T_Month:
  277         DBufFree(&buf);
  278         if (trig->m != NO_MON) return E_MON_TWICE;
  279         trig->m = tok.val;
  280         break;
  281 
  282         case T_MaybeUncomputable:
  283             trig->maybe_uncomputable = 1;
  284             break;
  285 
  286     case T_Skip:
  287         DBufFree(&buf);
  288         if (trig->skip != NO_SKIP) return E_SKIP_ERR;
  289         trig->skip = tok.val;
  290         break;
  291 
  292     case T_Priority:
  293         DBufFree(&buf);
  294         r=ParsePriority(s, trig);
  295         if (r) return r;
  296         break;
  297 
  298     case T_At:
  299         DBufFree(&buf);
  300         r=ParseTimeTrig(s, tim, save_in_globals);
  301         if (r) return r;
  302         trig->duration_days = ComputeTrigDuration(tim);
  303         break;
  304 
  305     case T_Scanfrom:
  306         DBufFree(&buf);
  307         r=ParseScanFrom(s, trig, tok.val);
  308         if (r) return r;
  309         break;
  310 
  311     case T_RemType:
  312         DBufFree(&buf);
  313         trig->typ = tok.val;
  314         if (s->isnested) return E_CANT_NEST_RTYPE;
  315         if (trig->scanfrom == NO_DATE) trig->scanfrom = JulianToday;
  316         if (trig->typ == PASSTHRU_TYPE) {
  317         r = ParseToken(s, &buf);
  318         if (r) return r;
  319         if (!DBufLen(&buf)) {
  320             DBufFree(&buf);
  321             return E_EOLN;
  322         }
  323         StrnCpy(trig->passthru, DBufValue(&buf), PASSTHRU_LEN);
  324         }
  325         return OK;
  326 
  327     case T_Through:
  328         DBufFree(&buf);
  329         if (trig->rep != NO_REP) return E_REP_TWICE;
  330         trig->rep = 1;
  331         r = ParseUntil(s, trig);
  332         if (r) return r;
  333         break;
  334 
  335     case T_Until:
  336         DBufFree(&buf);
  337         r=ParseUntil(s, trig);
  338         if (r) return r;
  339         break;
  340 
  341     case T_Year:
  342         DBufFree(&buf);
  343         if (trig->y != NO_YR) return E_YR_TWICE;
  344         trig->y = tok.val;
  345         break;
  346 
  347     case T_Day:
  348         DBufFree(&buf);
  349         if (trig->d != NO_DAY) return E_DAY_TWICE;
  350         trig->d = tok.val;
  351         break;
  352 
  353     case T_Rep:
  354         DBufFree(&buf);
  355         if (trig->rep != NO_REP) return E_REP_TWICE;
  356         trig->rep = tok.val;
  357         break;
  358 
  359     case T_Delta:
  360         DBufFree(&buf);
  361         if (trig->delta != NO_DELTA) return E_DELTA_TWICE;
  362         trig->delta = tok.val;
  363         break;
  364 
  365     case T_Back:
  366         DBufFree(&buf);
  367         if (trig->back != NO_BACK) return E_BACK_TWICE;
  368         trig->back = tok.val;
  369         break;
  370 
  371     case T_Once:
  372         DBufFree(&buf);
  373         if (trig->once != NO_ONCE) return E_ONCE_TWICE;
  374         trig->once = ONCE_ONCE;
  375         break;
  376 
  377     case T_Omit:
  378         DBufFree(&buf);
  379         if (trig->omitfunc[0]) {
  380         Eprint("Warning: OMIT is ignored if you use OMITFUNC");
  381         }
  382 
  383         r = ParseLocalOmit(s, trig);
  384         if (r) return r;
  385         break;
  386 
  387     case T_Empty:
  388         DBufFree(&buf);
  389         if (trig->scanfrom == NO_DATE) trig->scanfrom = JulianToday;
  390         return OK;
  391 
  392     case T_OmitFunc:
  393         if (trig->localomit) {
  394         Eprint("Warning: OMIT is ignored if you use OMITFUNC");
  395         }
  396         r=ParseToken(s, &buf);
  397         if (r) return r;
  398         StrnCpy(trig->omitfunc, DBufValue(&buf), VAR_NAME_LEN);
  399 
  400         /* An OMITFUNC counts as a nonconst_expr! */
  401         s->expr_happened = 1;
  402         s->nonconst_expr = 1;
  403         DBufFree(&buf);
  404         break;
  405 
  406     case T_Warn:
  407         r=ParseToken(s, &buf);
  408         if(r) return r;
  409         StrnCpy(trig->warn, DBufValue(&buf), VAR_NAME_LEN);
  410         DBufFree(&buf);
  411         break;
  412 
  413     case T_Tag:
  414         r = ParseToken(s, &buf);
  415         if (r) return r;
  416         AppendTag(&(trig->tags), DBufValue(&buf));
  417         break;
  418 
  419     case T_Duration:
  420         r = ParseToken(s, &buf);
  421         if (r) return r;
  422         FindToken(DBufValue(&buf), &tok);
  423         DBufFree(&buf);
  424         switch(tok.type) {
  425         case T_Time:
  426         case T_LongTime:
  427         case T_Year:
  428         case T_Day:
  429         case T_Number:
  430         if (tok.val != 0) {
  431             tim->duration = tok.val;
  432         } else {
  433             tim->duration = NO_TIME;
  434         }
  435         if (save_in_globals) {
  436             SaveLastTimeTrig(tim);
  437         }
  438         trig->duration_days = ComputeTrigDuration(tim);
  439         break;
  440         default:
  441         return E_BAD_TIME;
  442         }
  443         break;
  444 
  445     case T_Sched:
  446         r=ParseToken(s, &buf);
  447         if(r) return r;
  448         StrnCpy(trig->sched, DBufValue(&buf), VAR_NAME_LEN);
  449         DBufFree(&buf);
  450         break;
  451 
  452     case T_LongTime:
  453         DBufFree(&buf);
  454         return E_BAD_TIME;
  455         break;
  456 
  457     default:
  458         PushToken(DBufValue(&buf), s);
  459         DBufFree(&buf);
  460         trig->typ = MSG_TYPE;
  461         if (s->isnested) return E_CANT_NEST_RTYPE;
  462         if (trig->scanfrom == NO_DATE) trig->scanfrom = JulianToday;
  463         return OK;
  464     }
  465     }
  466 }
  467 
  468 /***************************************************************/
  469 /*                                                             */
  470 /*  ParseTimeTrig - parse the AT part of a timed reminder      */
  471 /*                                                             */
  472 /***************************************************************/
  473 static int ParseTimeTrig(ParsePtr s, TimeTrig *tim, int save_in_globals)
  474 {
  475     Token tok;
  476     int r;
  477 
  478     DynamicBuffer buf;
  479     DBufInit(&buf);
  480 
  481     while(1) {
  482     r = ParseToken(s, &buf);
  483     if (r) return r;
  484     FindToken(DBufValue(&buf), &tok);
  485     switch(tok.type) {
  486     case T_Time:
  487         DBufFree(&buf);
  488         if (tim->ttime != NO_TIME) return E_TIME_TWICE;
  489         tim->ttime = tok.val;
  490         break;
  491 
  492     case T_Delta:
  493         DBufFree(&buf);
  494         if (tim->delta != NO_DELTA) return E_DELTA_TWICE;
  495         tim->delta = (tok.val > 0) ? tok.val : -tok.val;
  496         break;
  497 
  498     case T_Rep:
  499         DBufFree(&buf);
  500         if (tim->rep != NO_REP) return E_REP_TWICE;
  501         tim->rep = tok.val;
  502         break;
  503 
  504     default:
  505         if (tim->ttime == NO_TIME) return E_EXPECT_TIME;
  506 
  507         /* Save trigger time in global variable */
  508         if (save_in_globals) {
  509         LastTriggerTime = tim->ttime;
  510         SaveLastTimeTrig(tim);
  511         }
  512         PushToken(DBufValue(&buf), s);
  513         DBufFree(&buf);
  514         return OK;
  515     }
  516     }
  517 }
  518 
  519 /***************************************************************/
  520 /*                                                             */
  521 /*  ParseLocalOmit - parse the local OMIT portion of a         */
  522 /*  reminder.                                                  */
  523 /*                                                             */
  524 /***************************************************************/
  525 static int ParseLocalOmit(ParsePtr s, Trigger *t)
  526 {
  527     Token tok;
  528     int r;
  529     DynamicBuffer buf;
  530     DBufInit(&buf);
  531 
  532     while(1) {
  533     r = ParseToken(s, &buf);
  534     if (r) return r;
  535     FindToken(DBufValue(&buf), &tok);
  536     switch(tok.type) {
  537     case T_WkDay:
  538         DBufFree(&buf);
  539         t->localomit |= (1 << tok.val);
  540         break;
  541 
  542     default:
  543         PushToken(DBufValue(&buf), s);
  544         DBufFree(&buf);
  545         return OK;
  546     }
  547     }
  548 }
  549 
  550 /***************************************************************/
  551 /*                                                             */
  552 /*  ParseUntil - parse the UNTIL portion of a reminder         */
  553 /*                                                             */
  554 /***************************************************************/
  555 static int ParseUntil(ParsePtr s, Trigger *t)
  556 {
  557     int y = NO_YR,
  558     m = NO_MON,
  559     d = NO_DAY;
  560 
  561     Token tok;
  562     int r;
  563     DynamicBuffer buf;
  564     DBufInit(&buf);
  565 
  566     if (t->until != NO_UNTIL) return E_UNTIL_TWICE;
  567 
  568     while(1) {
  569     r = ParseToken(s, &buf);
  570     if (r) return r;
  571     FindToken(DBufValue(&buf), &tok);
  572     switch(tok.type) {
  573     case T_Year:
  574         DBufFree(&buf);
  575         if (y != NO_YR) {
  576         Eprint("UNTIL: %s", ErrMsg[E_YR_TWICE]);
  577         return E_YR_TWICE;
  578         }
  579         y = tok.val;
  580         break;
  581 
  582     case T_Month:
  583         DBufFree(&buf);
  584         if (m != NO_MON) {
  585         Eprint("UNTIL: %s", ErrMsg[E_MON_TWICE]);
  586         return E_MON_TWICE;
  587         }
  588         m = tok.val;
  589         break;
  590 
  591     case T_Day:
  592         DBufFree(&buf);
  593         if (d != NO_DAY) {
  594         Eprint("UNTIL: %s", ErrMsg[E_DAY_TWICE]);
  595         return E_DAY_TWICE;
  596         }
  597         d = tok.val;
  598         break;
  599 
  600     case T_Date:
  601         DBufFree(&buf);
  602         if (y != NO_YR) {
  603         Eprint("UNTIL: %s", ErrMsg[E_YR_TWICE]);
  604         return E_YR_TWICE;
  605         }
  606         if (m != NO_MON) {
  607         Eprint("UNTIL: %s", ErrMsg[E_MON_TWICE]);
  608         return E_MON_TWICE;
  609         }
  610         if (d != NO_DAY) {
  611         Eprint("UNTIL: %s", ErrMsg[E_DAY_TWICE]);
  612         return E_DAY_TWICE;
  613         }
  614         FromJulian(tok.val, &y, &m, &d);
  615         break;
  616 
  617     default:
  618         if (y == NO_YR || m == NO_MON || d == NO_DAY) {
  619         Eprint("UNTIL: %s", ErrMsg[E_INCOMPLETE]);
  620         DBufFree(&buf);
  621         return E_INCOMPLETE;
  622         }
  623         if (!DateOK(y, m, d)) {
  624         DBufFree(&buf);
  625         return E_BAD_DATE;
  626         }
  627         t->until = Julian(y, m, d);
  628         PushToken(DBufValue(&buf), s);
  629         DBufFree(&buf);
  630         return OK;
  631     }
  632     }
  633 }
  634 
  635 /***************************************************************/
  636 /*                                                             */
  637 /*  ParseScanFrom - parse the FROM/SCANFROM portion            */
  638 /*                                                             */
  639 /***************************************************************/
  640 static int ParseScanFrom(ParsePtr s, Trigger *t, int type)
  641 {
  642     int y = NO_YR,
  643     m = NO_MON,
  644     d = NO_DAY;
  645 
  646     Token tok;
  647     int r;
  648     DynamicBuffer buf;
  649     char const *word;
  650 
  651     DBufInit(&buf);
  652     if (type == SCANFROM_TYPE) {
  653     word = "SCANFROM";
  654     } else {
  655     word = "FROM";
  656     }
  657 
  658     if (t->scanfrom != NO_DATE) return E_SCAN_TWICE;
  659 
  660     while(1) {
  661     r = ParseToken(s, &buf);
  662     if (r) return r;
  663     FindToken(DBufValue(&buf), &tok);
  664     switch(tok.type) {
  665     case T_Year:
  666         DBufFree(&buf);
  667         if (y != NO_YR) {
  668         Eprint("%s: %s", word, ErrMsg[E_YR_TWICE]);
  669         return E_YR_TWICE;
  670         }
  671         y = tok.val;
  672         break;
  673 
  674     case T_Month:
  675         DBufFree(&buf);
  676         if (m != NO_MON) {
  677         Eprint("%s: %s", word, ErrMsg[E_MON_TWICE]);
  678         return E_MON_TWICE;
  679         }
  680         m = tok.val;
  681         break;
  682 
  683     case T_Day:
  684         DBufFree(&buf);
  685         if (d != NO_DAY) {
  686         Eprint("%s: %s", word, ErrMsg[E_DAY_TWICE]);
  687         return E_DAY_TWICE;
  688         }
  689         d = tok.val;
  690         break;
  691 
  692     case T_Date:
  693         DBufFree(&buf);
  694         if (y != NO_YR) {
  695         Eprint("%s: %s", word, ErrMsg[E_YR_TWICE]);
  696         return E_YR_TWICE;
  697         }
  698         if (m != NO_MON) {
  699         Eprint("%s: %s", word, ErrMsg[E_MON_TWICE]);
  700         return E_MON_TWICE;
  701         }
  702         if (d != NO_DAY) {
  703         Eprint("%s: %s", word, ErrMsg[E_DAY_TWICE]);
  704         return E_DAY_TWICE;
  705         }
  706         FromJulian(tok.val, &y, &m, &d);
  707         break;
  708 
  709     case T_Back:
  710         DBufFree(&buf);
  711         if (type != SCANFROM_TYPE) {
  712         Eprint("%s: %s", word, ErrMsg[E_INCOMPLETE]);
  713         return E_INCOMPLETE;
  714         }
  715         if (y != NO_YR) {
  716         Eprint("%s: %s", word, ErrMsg[E_YR_TWICE]);
  717         return E_YR_TWICE;
  718         }
  719         if (m != NO_MON) {
  720         Eprint("%s: %s", word, ErrMsg[E_MON_TWICE]);
  721         return E_MON_TWICE;
  722         }
  723         if (d != NO_DAY) {
  724         Eprint("%s: %s", word, ErrMsg[E_DAY_TWICE]);
  725         return E_DAY_TWICE;
  726         }
  727         if (tok.val < 0) {
  728         tok.val = -tok.val;
  729         }
  730         FromJulian(JulianToday - tok.val, &y, &m, &d);
  731         break;
  732 
  733     default:
  734         if (y == NO_YR || m == NO_MON || d == NO_DAY) {
  735         Eprint("%s: %s", word, ErrMsg[E_INCOMPLETE]);
  736         DBufFree(&buf);
  737         return E_INCOMPLETE;
  738         }
  739         if (!DateOK(y, m, d)) {
  740         DBufFree(&buf);
  741         return E_BAD_DATE;
  742         }
  743         t->scanfrom = Julian(y, m, d);
  744         if (type == FROM_TYPE) {
  745         t->from = t->scanfrom;
  746         if (t->scanfrom < JulianToday) {
  747             t->scanfrom = JulianToday;
  748         }
  749         } else {
  750         t->from = NO_DATE;
  751         }
  752 
  753         PushToken(DBufValue(&buf), s);
  754         DBufFree(&buf);
  755         return OK;
  756     }
  757     }
  758 }
  759 /***************************************************************/
  760 /*                                                             */
  761 /*  TriggerReminder                                            */
  762 /*                                                             */
  763 /*  Trigger the reminder if it's a RUN or MSG type.            */
  764 /*                                                             */
  765 /***************************************************************/
  766 int TriggerReminder(ParsePtr p, Trigger *t, TimeTrig *tim, int jul)
  767 {
  768     int r, y, m, d;
  769     char PrioExpr[VAR_NAME_LEN+25];
  770     char tmpBuf[64];
  771     DynamicBuffer buf, calRow;
  772     DynamicBuffer pre_buf;
  773     char const *s;
  774     Value v;
  775 
  776     int red = -1, green = -1, blue = -1;
  777     int is_color = 0;
  778 
  779     DBufInit(&buf);
  780     DBufInit(&calRow);
  781     DBufInit(&pre_buf);
  782     if (t->typ == RUN_TYPE && RunDisabled) return E_RUN_DISABLED;
  783     if ((t->typ == PASSTHRU_TYPE && StrCmpi(t->passthru, "COLOR") && StrCmpi(t->passthru, "COLOUR")) ||
  784     t->typ == CAL_TYPE ||
  785     t->typ == PS_TYPE ||
  786     t->typ == PSF_TYPE)
  787     return OK;
  788 
  789     /* Handle COLOR types */
  790     if (t->typ == PASSTHRU_TYPE && (!StrCmpi(t->passthru, "COLOR") || !StrCmpi(t->passthru, "COLOUR"))) {
  791     /* Strip off three tokens */
  792     r = ParseToken(p, &buf);
  793     sscanf(DBufValue(&buf), "%d", &red);
  794     if (!NextMode) {
  795         DBufPuts(&pre_buf, DBufValue(&buf));
  796         DBufPutc(&pre_buf, ' ');
  797     }
  798     DBufFree(&buf);
  799     if (r) return r;
  800     r = ParseToken(p, &buf);
  801     sscanf(DBufValue(&buf), "%d", &green);
  802     if (!NextMode) {
  803         DBufPuts(&pre_buf, DBufValue(&buf));
  804         DBufPutc(&pre_buf, ' ');
  805     }
  806     DBufFree(&buf);
  807     if (r) return r;
  808     r = ParseToken(p, &buf);
  809     sscanf(DBufValue(&buf), "%d", &blue);
  810     if (!NextMode) {
  811         DBufPuts(&pre_buf, DBufValue(&buf));
  812         DBufPutc(&pre_buf, ' ');
  813     }
  814     DBufFree(&buf);
  815     if (r) return r;
  816     t->typ = MSG_TYPE;
  817     }
  818 /* If it's a MSG-type reminder, and no -k option was used, issue the banner. */
  819     if ((t->typ == MSG_TYPE || t->typ == MSF_TYPE) 
  820     && !NumTriggered && !NextMode && !MsgCommand) {
  821     if (!DoSubstFromString(DBufValue(&Banner), &buf,
  822                    JulianToday, NO_TIME) &&
  823         DBufLen(&buf)) {
  824     printf("%s\n", DBufValue(&buf));
  825     }
  826     DBufFree(&buf);
  827     }
  828 
  829 /* If it's NextMode, process as a ADVANCE_MODE-type entry, and issue
  830    simple-calendar format. */
  831     if (NextMode) {
  832     if ( (r=DoSubst(p, &buf, t, tim, jul, ADVANCE_MODE)) ) return r;
  833     if (!DBufLen(&buf)) {
  834         DBufFree(&buf);
  835         DBufFree(&pre_buf);
  836         return OK;
  837     }
  838     FromJulian(jul, &y, &m, &d);
  839     sprintf(tmpBuf, "%04d/%02d/%02d ", y, m+1, d);
  840     if (DBufPuts(&calRow, tmpBuf) != OK) {
  841         DBufFree(&calRow);
  842         DBufFree(&pre_buf);
  843         return E_NO_MEM;
  844     }
  845     /* If DoSimpleCalendar==1, output *all* simple calendar fields */
  846     if (DoSimpleCalendar) {
  847         /* ignore passthru field when in NextMode */
  848         if (DBufPuts(&calRow, "* ") != OK) {
  849         DBufFree(&calRow);
  850         DBufFree(&pre_buf);
  851         return E_NO_MEM;
  852         }
  853         if (*DBufValue(&(t->tags))) {
  854         DBufPuts(&calRow, DBufValue(&(t->tags)));
  855         DBufPutc(&calRow, ' ');
  856         } else {
  857         DBufPuts(&calRow, "* ");
  858         }
  859         if (tim->duration != NO_TIME) {
  860         sprintf(tmpBuf, "%d ", tim->duration);
  861         } else {
  862         sprintf(tmpBuf, "* ");
  863         }
  864         if (DBufPuts(&calRow, tmpBuf) != OK) {
  865         DBufFree(&calRow);
  866         DBufFree(&pre_buf);
  867         return E_NO_MEM;
  868         }
  869         if (tim->ttime != NO_TIME) {
  870         sprintf(tmpBuf, "%d ", tim->ttime);
  871         } else {
  872         sprintf(tmpBuf, "* ");
  873         }
  874         if (DBufPuts(&calRow, tmpBuf) != OK) {
  875         DBufFree(&calRow);
  876         DBufFree(&pre_buf);
  877         return E_NO_MEM;
  878         }
  879     }
  880     if (DBufPuts(&calRow, SimpleTime(tim->ttime)) != OK) {
  881         DBufFree(&calRow);
  882         DBufFree(&pre_buf);
  883         return E_NO_MEM;
  884     }
  885 
  886     printf("%s%s%s\n", DBufValue(&calRow), DBufValue(&pre_buf), DBufValue(&buf));
  887     DBufFree(&buf);
  888     DBufFree(&pre_buf);
  889     DBufFree(&calRow);
  890     return OK;
  891     }
  892 
  893     /* Correct colors */
  894     if (UseVTColors) {
  895     if (red == -1 && green == -1 && blue == -1) {
  896         if (DefaultColorR != -1 && DefaultColorG != -1 && DefaultColorB != -1) {
  897         red = DefaultColorR;
  898         green = DefaultColorG;
  899         blue = DefaultColorB;
  900         }
  901     }
  902     if (red >= 0 && green >= 0 && blue >= 0) {
  903         is_color = 1;
  904         if (red > 255) red = 255;
  905         if (green > 255) green = 255;
  906         if (blue > 255) blue = 255;
  907     }
  908     }
  909 
  910     /* Put the substituted string into the substitution buffer */
  911 
  912     /* Don't use msgprefix() on RUN-type reminders */
  913     if (t->typ != RUN_TYPE) {
  914     if (UserFuncExists("msgprefix") == 1) {
  915         sprintf(PrioExpr, "msgprefix(%d)", t->priority);
  916         s = PrioExpr;
  917         r = EvalExpr(&s, &v, NULL);
  918         if (!r) {
  919         if (!DoCoerce(STR_TYPE, &v)) {
  920             if (is_color) {
  921             DBufPuts(&buf, Colorize(red, green, blue));
  922             }
  923             if (DBufPuts(&buf, v.v.str) != OK) {
  924             DBufFree(&buf);
  925             DestroyValue(v);
  926             return E_NO_MEM;
  927             }
  928         }
  929         DestroyValue(v);
  930         }
  931     }
  932     }
  933 
  934     if (is_color) {
  935     DBufPuts(&buf, Colorize(red, green, blue));
  936     }
  937     if ( (r=DoSubst(p, &buf, t, tim, jul, NORMAL_MODE)) ) return r;
  938     if (t->typ != RUN_TYPE) {
  939     if (UserFuncExists("msgsuffix") == 1) {
  940         sprintf(PrioExpr, "msgsuffix(%d)", t->priority);
  941         s = PrioExpr;
  942         r = EvalExpr(&s, &v, NULL);
  943         if (!r) {
  944         if (!DoCoerce(STR_TYPE, &v)) {
  945             if (is_color) {
  946             DBufPuts(&buf, Colorize(red, green, blue));
  947             }
  948             if (DBufPuts(&buf, v.v.str) != OK) {
  949             DBufFree(&buf);
  950             DestroyValue(v);
  951             return E_NO_MEM;
  952             }
  953         }
  954         DestroyValue(v);
  955         }
  956     }
  957     }
  958 
  959     if (is_color) {
  960     DBufPuts(&buf, Decolorize(red, green, blue));
  961     }
  962 
  963     if ((!MsgCommand && t->typ == MSG_TYPE) || t->typ == MSF_TYPE) {
  964     if (DBufPutc(&buf, '\n') != OK) {
  965         DBufFree(&buf);
  966         return E_NO_MEM;
  967     }
  968     }
  969 
  970 /* If we are sorting, just queue it up in the sort buffer */
  971     if (SortByDate) {
  972     if (InsertIntoSortBuffer(jul, tim->ttime, DBufValue(&buf),
  973                  t->typ, t->priority) == OK) {
  974         DBufFree(&buf);
  975         NumTriggered++;
  976         return OK;
  977     }
  978     }
  979 
  980 /* If we didn't insert the reminder into the sort buffer, issue the
  981    reminder now. */
  982     switch(t->typ) {
  983     case MSG_TYPE:
  984     case PASSTHRU_TYPE:
  985     if (MsgCommand) {
  986         DoMsgCommand(MsgCommand, DBufValue(&buf));
  987     } else {
  988         printf("%s", DBufValue(&buf));
  989     }
  990     break;
  991 
  992     case MSF_TYPE:
  993     FillParagraph(DBufValue(&buf));
  994     break;
  995 
  996     case RUN_TYPE:
  997     System(DBufValue(&buf));
  998     break;
  999 
 1000     default: /* Unknown/illegal type? */
 1001     DBufFree(&buf);
 1002     return E_SWERR;
 1003     }
 1004 
 1005     DBufFree(&buf);
 1006     NumTriggered++;
 1007     return OK;
 1008 }
 1009 
 1010 /***************************************************************/
 1011 /*                                                             */
 1012 /*  ShouldTriggerReminder                                      */
 1013 /*                                                             */
 1014 /*  Return 1 if we should trigger a reminder, based on today's */
 1015 /*  date and the trigger.  Return 0 if reminder should not be  */
 1016 /*  triggered.  Sets *err non-zero in event of an error.       */
 1017 /*                                                             */
 1018 /***************************************************************/
 1019 int ShouldTriggerReminder(Trigger *t, TimeTrig *tim, int jul, int *err)
 1020 {
 1021     int r, omit;
 1022     *err = 0;
 1023 
 1024     /* Handle the ONCE modifier in the reminder. */
 1025     if (!IgnoreOnce && t->once !=NO_ONCE && FileAccessDate == JulianToday)
 1026     return 0;
 1027 
 1028     if (jul < JulianToday) return 0;
 1029 
 1030     /* Don't trigger timed reminders if DontIssueAts is true, and if the
 1031        reminder is for today */
 1032     if (jul == JulianToday && DontIssueAts && tim->ttime != NO_TIME) {
 1033     if (DontIssueAts > 1) {
 1034         /* If two or more -a options, then *DO* issue ats that are in the
 1035            future */
 1036         if (tim->ttime < SystemTime(0) / 60) {
 1037         return 0;
 1038         }
 1039     } else {
 1040         return 0;
 1041     }
 1042     }
 1043 
 1044     /* Don't trigger "old" timed reminders */
 1045 /*** REMOVED...
 1046   if (jul == JulianToday &&
 1047   tim->ttime != NO_TIME &&
 1048   tim->ttime < SystemTime(0) / 60) return 0;
 1049   *** ...UNTIL HERE */
 1050 
 1051     /* If "infinite delta" option is chosen, always trigger future reminders */
 1052     if (InfiniteDelta || NextMode) return 1;
 1053 
 1054     /* If there's a "warn" function, it overrides any deltas */
 1055     if (t->warn[0] != 0) {
 1056     if (DeltaOffset) {
 1057         if (jul <= JulianToday + DeltaOffset) {
 1058         return 1;
 1059         }
 1060     }
 1061     return ShouldTriggerBasedOnWarn(t, jul, err);
 1062     }
 1063 
 1064     /* Move back by delta days, if any */
 1065     if (t->delta != NO_DELTA) {
 1066     if (t->delta < 0)
 1067         jul = jul + t->delta;
 1068     else {
 1069         int iter = 0;
 1070         int max = MaxSatIter;
 1071         r = t->delta;
 1072         if (max < r*2) max = r*2;
 1073         while(iter++ < max) {
 1074         if (!r || (jul <= JulianToday)) {
 1075             break;
 1076         }
 1077         jul--;
 1078         *err = IsOmitted(jul, t->localomit, t->omitfunc, &omit);
 1079         if (*err) return 0;
 1080         if (!omit) r--;
 1081         }
 1082         if (iter > max) {
 1083         *err = E_CANT_TRIG;
 1084             Eprint("Delta: Bad OMITFUNC? %s", ErrMsg[E_CANT_TRIG]);
 1085         return 0;
 1086         }
 1087     }
 1088     }
 1089 
 1090     /* Should we trigger the reminder? */
 1091     return (jul <= JulianToday + DeltaOffset);
 1092 }
 1093 
 1094 /***************************************************************/
 1095 /*                                                             */
 1096 /*  DoSatRemind                                                */
 1097 /*                                                             */
 1098 /*  Do the "satisfying..." remind calculation.                 */
 1099 /*                                                             */
 1100 /***************************************************************/
 1101 int DoSatRemind(Trigger *trig, TimeTrig *tt, ParsePtr p)
 1102 {
 1103     int iter, jul, r, start;
 1104     Value v;
 1105     char const *s;
 1106     char const *t;
 1107 
 1108     t = p->pos;
 1109     iter = 0;
 1110     start = trig->scanfrom;
 1111     while (iter++ < MaxSatIter) {
 1112     jul = ComputeTriggerNoAdjustDuration(start, trig, tt, &r, 1, 0);
 1113     if (r) {
 1114         if (r == E_CANT_TRIG) return OK; else return r;
 1115     }
 1116     if (jul != start && trig->duration_days) {
 1117         jul = ComputeTriggerNoAdjustDuration(start, trig, tt, &r, 1, trig->duration_days);
 1118         if (r) {
 1119         if (r == E_CANT_TRIG) return OK; else return r;
 1120         }
 1121     } else if (jul == start) {
 1122         if (tt->ttime != NO_TIME) {
 1123         trig->eventstart = MINUTES_PER_DAY * r + tt->ttime;
 1124         if (tt->duration != NO_TIME) {
 1125             trig->eventduration = tt->duration;
 1126         }
 1127         }
 1128         SaveAllTriggerInfo(trig, tt, jul, tt->ttime, 1);
 1129     }
 1130     if (jul == -1) {
 1131         return E_EXPIRED;
 1132     }
 1133     s = p->pos;
 1134     r = EvaluateExpr(p, &v);
 1135     t = p->pos;
 1136     if (r) return r;
 1137     if (v.type != INT_TYPE && v.type != STR_TYPE) return E_BAD_TYPE;
 1138     if ((v.type == INT_TYPE && v.v.val) ||
 1139         (v.type == STR_TYPE && *v.v.str)) {
 1140         AdjustTriggerForDuration(trig->scanfrom, jul, trig, tt, 1);
 1141         if (DebugFlag & DB_PRTTRIG) {
 1142         int y, m, d;
 1143         FromJulian(LastTriggerDate, &y, &m, &d);
 1144         fprintf(ErrFp, "%s(%d): Trig(satisfied) = %s, %d %s, %d",
 1145             FileName, LineNo,
 1146             DayName[LastTriggerDate % 7],
 1147             d,
 1148             MonthName[m],
 1149             y);
 1150         if (tt->ttime != NO_TIME) {
 1151             fprintf(ErrFp, " AT %02d:%02d",
 1152                 (tt->ttime / 60),
 1153                 (tt->ttime % 60));
 1154             if (tt->duration != NO_TIME) {
 1155             fprintf(ErrFp, " DURATION %02d:%02d",
 1156                 (tt->duration / 60),
 1157                 (tt->duration % 60));
 1158             }
 1159         }
 1160         fprintf(ErrFp, "\n");
 1161         }
 1162         return OK;
 1163     }
 1164     p->pos = s;
 1165     if (jul+trig->duration_days < start) {
 1166         start++;
 1167     } else {
 1168         start = jul+trig->duration_days+1;
 1169     }
 1170     }
 1171     p->pos = t;
 1172     LastTrigValid = 0;
 1173     return E_CANT_TRIG;
 1174 }
 1175 
 1176 /***************************************************************/
 1177 /*                                                             */
 1178 /*  ParsePriority - parse the PRIORITY portion of a reminder   */
 1179 /*                                                             */
 1180 /***************************************************************/
 1181 static int ParsePriority(ParsePtr s, Trigger *t)
 1182 {
 1183     int p, r;
 1184     char const *u;
 1185     DynamicBuffer buf;
 1186     DBufInit(&buf);
 1187 
 1188     r = ParseToken(s, &buf);
 1189     if(r) return r;
 1190     u = DBufValue(&buf);
 1191 
 1192     if (!isdigit(*u)) {
 1193     DBufFree(&buf);
 1194     return E_EXPECTING_NUMBER;
 1195     }
 1196     p = 0;
 1197     while (isdigit(*u)) {
 1198     p = p*10 + *u - '0';
 1199     u++;
 1200     }
 1201     if (*u) {
 1202     DBufFree(&buf);
 1203     return E_EXPECTING_NUMBER;
 1204     }
 1205 
 1206     DBufFree(&buf);
 1207 
 1208 /* Tricky!  The only way p can be < 0 is if overflow occurred; thus,
 1209    E2HIGH is indeed the appropriate error message. */
 1210     if (p<0 || p>9999) return E_2HIGH;
 1211     t->priority = p;
 1212     return OK;
 1213 }
 1214 
 1215 /***************************************************************/
 1216 /*                                                             */
 1217 /*  DoMsgCommand                                               */
 1218 /*                                                             */
 1219 /*  Execute the '-k' command, escaping shell chars in message. */
 1220 /*                                                             */
 1221 /***************************************************************/
 1222 int DoMsgCommand(char const *cmd, char const *msg)
 1223 {
 1224     int r;
 1225     int i, l;
 1226     DynamicBuffer execBuffer;
 1227 
 1228     DynamicBuffer buf;
 1229 
 1230     DBufInit(&buf);
 1231     DBufInit(&execBuffer);
 1232 
 1233     /* Escape shell characters in msg */
 1234     if (ShellEscape(msg, &buf) != OK) {
 1235         r = E_NO_MEM;
 1236         goto finished;
 1237     }
 1238 
 1239     msg = DBufValue(&buf);
 1240 
 1241     /* Do "%s" substitution */
 1242     l = strlen(cmd);
 1243     for (i=0; i<l; i++) {
 1244     if (cmd[i] == '%' && cmd[i+1] == 's') {
 1245         ++i;
 1246         if (DBufPuts(&execBuffer, msg) != OK) {
 1247         r = E_NO_MEM;
 1248         goto finished;
 1249         }
 1250     } else {
 1251         if (DBufPutc(&execBuffer, cmd[i]) != OK) {
 1252         r = E_NO_MEM;
 1253         goto finished;
 1254         }
 1255     }
 1256     }
 1257     r = OK;
 1258 
 1259     System(DBufValue(&execBuffer));
 1260 
 1261 finished:
 1262     DBufFree(&buf);
 1263     DBufFree(&execBuffer);
 1264     return r;
 1265 }
 1266 
 1267 /***************************************************************/
 1268 /*                                                             */
 1269 /*  ShouldTriggerBasedOnWarn                                   */
 1270 /*                                                             */
 1271 /*  Determine whether to trigger a reminder based on its WARN  */
 1272 /*  function.                                                  */
 1273 /*                                                             */
 1274 /***************************************************************/
 1275 static int ShouldTriggerBasedOnWarn(Trigger *t, int jul, int *err)
 1276 {
 1277     char buffer[VAR_NAME_LEN+32];
 1278     int i;
 1279     char const *s;
 1280     int r, omit;
 1281     Value v;
 1282     int lastReturnVal = 0; /* Silence compiler warning */
 1283 
 1284     /* If no proper function exists, barf... */
 1285     if (UserFuncExists(t->warn) != 1) {
 1286     Eprint("%s: `%s'", ErrMsg[M_BAD_WARN_FUNC], t->warn);
 1287     return (jul == JulianToday);
 1288     }
 1289     for (i=1; ; i++) {
 1290     sprintf(buffer, "%s(%d)", t->warn, i);
 1291     s = buffer;
 1292     r = EvalExpr(&s, &v, NULL);
 1293     if (r) {
 1294         Eprint("%s: `%s': %s", ErrMsg[M_BAD_WARN_FUNC],
 1295            t->warn, ErrMsg[r]);
 1296         return (jul == JulianToday);
 1297     }
 1298     if (v.type != INT_TYPE) {
 1299         DestroyValue(v);
 1300         Eprint("%s: `%s': %s", ErrMsg[M_BAD_WARN_FUNC],
 1301            t->warn, ErrMsg[E_BAD_TYPE]);
 1302         return (jul == JulianToday);
 1303     }
 1304 
 1305     /* If absolute value of return is not monotonically
 1306            decreasing, exit */
 1307     if (i > 1 && abs(v.v.val) >= lastReturnVal) {
 1308         return (jul == JulianToday);
 1309     }
 1310 
 1311     lastReturnVal = abs(v.v.val);
 1312     /* Positive values: Just subtract.  Negative values:
 1313            skip omitted days. */
 1314     if (v.v.val >= 0) {
 1315         if (JulianToday + v.v.val == jul) return 1;
 1316     } else {
 1317         int j = jul;
 1318         int iter = 0;
 1319         int max = MaxSatIter;
 1320         if (max < v.v.val * 2) max = v.v.val*2;
 1321         while(iter++ <= max) {
 1322         j--;
 1323         *err = IsOmitted(j, t->localomit, t->omitfunc, &omit);
 1324         if (*err) return 0;
 1325         if (!omit) v.v.val++;
 1326         if (!v.v.val) {
 1327             break;
 1328         }
 1329         }
 1330         if (iter > max) {
 1331             Eprint("Delta: Bad OMITFUNC? %s", ErrMsg[E_CANT_TRIG]);
 1332             return 0;
 1333         }
 1334         if (j == JulianToday) return 1;
 1335     }
 1336     }
 1337 }