1 /* ---------------------------------------------------------------------------
2 
3    readfile.c
4 
5    Notes:
6 
7       This file contains routines for reading and parsing the configuration
8       file (aka the 'date file').
9 
10    Revision history:
11 
12 	4.11.0
13 		B.Marr		2007-12-15
14 
15 		Add support for new "on" preposition, thanks to a request from
16 		and in part to a patch from Erkki Petsalo.
17 
18 		Eliminate the now-needless "F13" ("Friday the 13th") special
19 		event trigger and the associated processing of it.
20 
21 		Rename some variables, structures, and/or routines to be
22 		clearer about their purpose and/or to allow easier searching
23 		with fewer "false positives".
24 
25 		Fix some minor comment problems.
26 
27 	4.10.0
28 		B.Marr		2006-07-19
29 
30 		Reformatted comments and code to match my standards.
31 
32 		B.Marr		2006-07-12
33 
34 		Rename old 'getline()' routine to 'get_pcal_line()' to avoid a
35 		compile-time namespace collision with the standard C library,
36 		which seems to manifest itself only in Windows+Cygwin build
37 		environments.
38 
39 		Provide explicit casting in several spots to avoid warnings in
40 		a "gcc 3.4.2 on Solaris 8" environment, based on a report from
41 		David Mathog <mathog at mendel.bio.caltech.edu>.
42 
43 		Get rid of all the '#ifdef PROTOS' checks, which are pretty
44 		much obsolete these days and just needlessly clutter up the
45 		code.
46 
47 	4.9.0
48 		B.Marr		2005-08-02
49 
50 		Incorporate patch by Bill Bogstad <bogstad at pobox dot com>
51 		to provide ability to delete specific events, thereby allowing
52 		one to exclude one or more events that were inserted as a
53 		group of events, by using the new 'delete' keyword.
54 
55 		B.Marr		2005-01-23
56 
57 		Fix bug introduced in v4.8.0 whereby a plural specification of
58 		the day-of-week (e.g. 'all Fridays in Oct') was not being
59 		recognized.
60 
61 	4.8.0
62 		B.Marr		2004-12-15
63 
64 		Prevent potential buffer overflow attack caused by malicious
65 		calendar input file by restructuring code to avoid a
66 		'strcpy()' call in 'get_predef_event()'.  This security hole
67 		was detected by Danny Lungstrom and reported by
68 		D. J. Bernstein.
69 
70 		B.Marr		2004-12-05
71 
72 		Fix misleading references to "holiday" to instead refer to
73 		"predefined event" (i.e. not all pre-defined events are
74 		'holidays').  Create and support concept of 'input' language
75 		versus 'output' language.  Remove spaces embedded within tab
76 		fields.
77 
78 		B.Marr		2004-11-19
79 
80 		Provide an enhanced capability to process not just simple
81 		symbol names but associated symbolic values too, based on a
82 		patch from (unknown).  Provide support for "Friday the 13th"
83 		events, based on a patch from Don Laursen (donrl at users dot
84 		sourceforge dot net).  Detect more than just 3 characters (if
85 		available in the event specification) when attempting to match
86 		a generic token to either a day-of-week name or a month name
87 		to avoid problem of misdetecting month names as day-of-week
88 		names (for languages which have such possibilities, like
89 		French) and the problem of misdetecting month names and/or
90 		day-of-week names for inactive languages.  Remove Ctl-L (page
91 		eject) characters from source file.
92 
93 	4.7	AWR	12/15/1998	expand %y into every applicable year
94 					in "year all" mode
95 
96 			02/24/1998	add prototypes (using PROTO macro)
97 					to function pointer declarations
98 
99 			12/21/1997	clean up gcc warnings in -Wall mode
100 
101 			07/27/1997	delete obsolete FPR and PRT macros;
102 					minor revisions for -H (HTML) flag
103 
104 	4.6	AWR	05/14/1997	replace obsolete CENTURY macro with
105 					call to century() (cf. pcalutil.c)
106 
107 			09/13/1996	support "nearest_before" and
108 					"nearest_after" keywords
109 
110 			12/05/1995	fix "undef" bug (modify get_token()
111 					to return token struct index, not type
112 					type code; cf. read_datefile())
113 
114 			12/02/1995	avoid generating font shift sequences
115 					in -c mode
116 
117 			11/29/1995	explicitly initialize calloc'd pointers
118 					to NULL
119 
120 			11/10/1995	support -T flag to select default
121 					font style (Bold/Italic/Roman)
122 
123 			10/31/1995	add preprocessor debug statements to
124 					do_define(), do_undef()
125 
126 			09/21/1995	added enter_date(), enter_note(),
127 					related support for "year all"
128 
129 		AH	02/03/1995	added orthodox easter related holiday
130 					calculations
131 
132 	4.5	AWR	11/08/1993	accept European dates of form "dd. mm."
133 					and "dd. mon"
134 
135 			04/28/1993	restructure function definitions so
136 					function name appears in first column
137 					(to facilitate searching for definition
138 					by name)
139 
140 			03/05/1993	Propagate null text to output
141 
142 			02/11/1992	Support predefined holidays (strings
143 					and dispatch functions - cf. pcallang.h)
144 
145 	4.4	AWR	01/20/1992	Use predominant color ("logical black")
146 					in is_weekday()
147 
148 			12/16/1991	Avoid invalid array access in
149 					read_datefile()
150 
151 	4.3	AWR	10/25/1991	Support moon phase wildcards and
152 					-Z flag (debug information)
153 
154 	4.2	AWR	10/03/1991	Support "note/<n>" (user-selected
155 					notes box) as per Geoff Kuenning
156 
157 			09/30/1991	Support "elif" in datefile
158 
159 	4.11	AWR	08/20/1991	Support "nearest" keyword as per
160 					Andy Fyfe
161 
162 	4.0	AWR	02/19/1991	Support negative ordinals
163 
164 			02/06/1991	Support expressions in "if{n}def"
165 
166 			02/04/1991	Support "even" and "odd" ordinals
167 					and ordinals > 5th; support "year"
168 
169 			01/15/1991	Extracted from pcal.c
170 
171 */
172 
173 /* ---------------------------------------------------------------------------
174 
175    Header Files
176 
177 */
178 
179 #include <stdio.h>
180 #include <string.h>
181 #include <ctype.h>
182 
183 #include "pcaldefs.h"
184 #include "pcallang.h"
185 #include "protos.h"
186 
187 /* ---------------------------------------------------------------------------
188 
189    Type, Struct, & Enum Declarations
190 
191 */
192 
193 /* ---------------------------------------------------------------------------
194 
195    Constant Declarations
196 
197 */
198 
199 /* status codes returned by parse_as_non_preproc(), enter_day_info() */
200 #define PARSE_OK        0       /* successful date parse */
201 #define PARSE_INVDATE   1       /* nonexistent date */
202 #define PARSE_INVLINE   2       /* syntax error */
203 #define PARSE_NOMATCH   3       /* no match for wildcard */
204 
205 /* codes for states in read_datefile() */
206 #define PROCESSING      0       /* currently processing datefile lines */
207 #define AWAITING_TRUE   1       /* awaiting first TRUE branch in "if{n}def" */
208 #define SKIP_TO_ENDIF   2       /* finished processing first TRUE branch */
209 
210 #define BLANK_TEXT      " "     /* substitute for null input text */
211 
212 /* ---------------------------------------------------------------------------
213 
214    Macro Definitions
215 
216 */
217 
218 /* append date to list; terminate list */
219 #define ADD_DATE(_m, _d, _y)   do { \
220 	if (DEBUG(DEBUG_DATES)) \
221 		fprintf(stderr, "Adding candidate date: %4d-%02d-%02d\n", _y, _m, _d); \
222 	pdate->mm = _m, pdate->dd = _d, pdate++->yy = _y; \
223 	} while (0)
224 
225 #define TERM_DATES   pdate->mm = pdate->dd = pdate->yy = 0
226 
227 
228 #ifndef NO_ORTHODOX
229 #define odox_add(offs) do { \
230 	int mon; \
231 	mon = APR; \
232 	if (offs < 1) { \
233 		mon = MAR; \
234 		offs = 31 + offs; \
235 	} else if (offs > 30) { \
236 		mon = MAY; \
237 		offs -= 30; \
238 	} \
239 	ADD_DATE(mon, offs, curr_year); \
240 } while (0);
241 #endif
242 
243 /* ---------------------------------------------------------------------------
244 
245    Data Declarations (including externals)
246 
247 */
248 
249 /* This array of 'date' (month, day, year) structures holds all of the
250    "candidate" dates for a single event specification read from the
251    configuration file.
252 
253    For a simple once-a-year event, this array will hold just one entry.
254 
255    For more complex events (those occurring more than once-a-year), this array
256    will hold several "candidate" dates.
257 
258    Any dates in this array might get modified before the event is actually
259    added to the big linked list structure (which is eventually used to output
260    the actual calendar).  For example, an event specification of "Sat
261    on_or_before Dec 15" would cause Dec 15 (for the given year being
262    processed) to be added to this array, but the actual date that eventually
263    gets added to the linked list may be a day _before_ Dec 15th.
264 
265    Additionally, some dates in this array might get invalidated (by having the
266    'year' field set to -1) so that the event is not actually added to the big
267    linked list structure.  This currently only occurs with the use of the "on"
268    preposition.  For example, an event specification of "Fri on all 13" (to
269    highlight all "Friday the 13th" days in all months) would cause 12 events
270    (for the given year being processed) to be added to this array, one for
271    each month's 13th day.  However, any entry in this array which does NOT
272    fall on a Friday would be marked as invalid, so that it won't actually be
273    added to the linked list.
274 
275 */
276 static date_str candidate_dates[MAX_DATES+1];   /* array of date structures */
277 
278 static char *pp_sym[MAX_PP_SYMS];   /* preprocessor defined symbols */
279 static char *pp_val[MAX_PP_SYMS];   /* preprocessor defined symbols' values */
280 static int curr_year_reset = FALSE;
281 static int delete_entry = FALSE;
282 
283 /* ---------------------------------------------------------------------------
284 
285    External Routine References & Function Prototypes
286 
287 */
288 
289 /* ---------------------------------------------------------------------------
290 
291    read_datefile
292 
293    Notes:
294 
295       This routine reads and parses the configuration file (aka 'date file'),
296       handling preprocessor lines.
297 
298 
299       This is the main routine of this module.  It calls 'get_pcal_line()' to
300       read each non-null line (stripped of leading blanks and trailing
301       comments), 'loadwords()' to "tokenize" it, and 'get_token()' to classify
302       it as a preprocessor directive or "other".  A switch statement takes the
303       appropriate action for each token type; "other" lines are further
304       classified by 'parse_as_non_preproc()' which calls 'parse_date()' to
305       parse date entries and enter them in the data structure (as described in
306       'pcaldefs.h').
307 
308       The first parameter is a pointer to the file (assumed to be already
309       open).
310 
311       The second parameter is the filename (for error messages).
312 
313 */
read_datefile(FILE * fp,char * filename)314 void read_datefile (FILE *fp, char *filename)
315 {
316    static int file_level = 0;
317    int if_level = 0;
318    int line = 0;
319    int extra = 0;
320    int pptype, ntokens, save_year, expr, (*pfcn) (char *) = NULL;
321    char *ptok = NULL;
322    char **pword;
323    char msg[STRSIZ], incpath[STRSIZ], save_font;
324 
325    /* stack for processing nested "if{n}defs" - required for "elif" */
326    struct {
327       int state;   /* PROCESSING, AWAITING_TRUE, SKIP_TO_ENDIF */
328       int else_ok;   /* is "elif" or "else" legal at this point? */
329    } if_state[MAX_IF_NESTING+1];
330 
331    if (fp == NULL) return;   /* whoops, no date file */
332 
333    /* Note that there is no functional limit on file nesting.  This is mostly
334       to catch infinite loops (e.g., a includes b, b includes a).
335    */
336    if (++file_level > MAX_FILE_NESTING) {
337       ERR(E_FILE_NESTING);
338       exit(EXIT_FAILURE);
339    }
340 
341    save_year = curr_year;   /* save current year... */
342    save_font = fontstyle[0];   /* ... and font style */
343 
344    if_state[0].state = PROCESSING;   /* set up initial state */
345    if_state[0].else_ok = FALSE;
346 
347    /* read lines until EOF */
348 
349    while (get_pcal_line(fp, lbuf, &line)) {
350       char suffix;
351 
352       if (DEBUG(DEBUG_PP)) {
353          fprintf(stderr, "%s (%d)", filename, line);
354          if (if_state[if_level].state == PROCESSING) fprintf(stderr, ": '%s'", lbuf);
355          fprintf(stderr, "\n");
356       }
357 
358       ntokens = loadwords(words, lbuf); /* split line into tokens */
359       pword = words;   /* point to the first */
360 
361       /* get token type and pointers to function and name */
362 
363       suffix = words[0][strlen(words[0])-1];
364       if ((pptype = get_token(*pword++)) != PP_OTHER) {
365          pfcn = pp_info[pptype].pfcn;
366          ptok = pp_info[pptype].name;
367          pptype = pp_info[pptype].code;   /* use type code */
368       }
369 
370       /* Perform symbol value substitution, but bypass it for any 'undef'
371         preprocessor commands, otherwise there's no way to 'undef' a symbol
372         which also had a value defined with it.
373       */
374       if (pptype != PP_UNDEF) {
375          char **ap, *nap;
376          for (ap=words; *ap; ap++) {
377             if ((nap = find_sym_val(*ap))) *ap=nap;
378          }
379       }
380 
381       switch (pptype) {
382 
383       case PP_DEFINE:
384          if (if_state[if_level].state == PROCESSING) (void) (*pfcn)(*pword);
385          if (ntokens == 3) do_define_sym_val(*pword,pword[1]);
386          extra = ntokens > 3;
387          break;
388 
389       case PP_UNDEF:
390          if (if_state[if_level].state == PROCESSING) (void) (*pfcn)(*pword);
391          extra = ntokens > 2;
392          break;
393 
394       case PP_ELIF:
395          if (!if_state[if_level].else_ok) {
396             ERR(E_ELIF_ERR);
397             break;
398          }
399 
400          /* if a true expression has just been processed, disable processing
401             and skip to endif; if no true expression has been found yet and
402             the current expression is true, enable processing
403          */
404          switch (if_state[if_level].state) {
405          case PROCESSING:
406             if_state[if_level].state = SKIP_TO_ENDIF;
407             break;
408          case AWAITING_TRUE:
409             copy_text(lbuf, pword);   /* reconstruct string */
410             if ((expr = (*pfcn)(lbuf)) == EXPR_ERR) {
411                ERR(E_EXPR_SYNTAX);
412                expr = FALSE;
413             }
414             if (expr) if_state[if_level].state = PROCESSING;
415             break;
416          }
417 
418          extra = FALSE;
419          break;
420 
421       case PP_ELSE:
422          if (!if_state[if_level].else_ok) {
423             ERR(E_ELSE_ERR);
424             break;
425          }
426 
427          /* if a true condition has just been processed, disable processing
428             and skip to endif; if no true condition has been found yet, enable
429             processing
430          */
431          switch (if_state[if_level].state) {
432          case PROCESSING:
433             if_state[if_level].state = SKIP_TO_ENDIF;
434             break;
435          case AWAITING_TRUE:
436             if_state[if_level].state = PROCESSING;
437             break;
438          }
439 
440          /* subsequent "elif" or "else" forbidden */
441          if_state[if_level].else_ok = FALSE;
442          extra = ntokens > 1;
443          break;
444 
445       case PP_ENDIF:
446          if (if_level < 1) {
447             ERR(E_END_ERR);
448             break;
449          }
450          if_level--;
451          extra = ntokens > 1;
452          break;
453 
454       case PP_IFDEF:
455       case PP_IFNDEF:
456          /* "if{n}def"s nested too deeply? */
457          if (++if_level > MAX_IF_NESTING) {
458             ERR(E_IF_NESTING);
459             exit(EXIT_FAILURE);
460             break;
461          }
462 
463          /* if processing enabled at outer level, evaluate expression and
464             enable/disable processing for following clause; if not, skip to
465             matching endif
466          */
467          if (if_state[if_level-1].state == PROCESSING) {
468             copy_text(lbuf, pword);   /* reconstruct string */
469             if ((expr = (*pfcn)(lbuf)) == EXPR_ERR) {
470                ERR(E_EXPR_SYNTAX);
471                expr = FALSE;
472             }
473             if_state[if_level].state = expr ? PROCESSING : AWAITING_TRUE;
474          }
475          else if_state[if_level].state = SKIP_TO_ENDIF;
476 
477          if_state[if_level].else_ok = TRUE;
478          extra = FALSE;
479          break;
480 
481       case PP_INCLUDE:
482          if (if_state[if_level].state == PROCESSING) {
483             do_include(mk_path(incpath, filename), *pword, suffix == '?');
484          }
485          extra = ntokens > 2;
486          break;
487 
488       case PP_OTHER:
489          /* None of the above... Parse this configuration file line as a
490             non-preprocessor directive.  This could be an event specification,
491             an event deletion specification, a 'year' specification, an 'input
492             language' specification, an option specification, or a 'note box'
493             text specification.
494          */
495          if (if_state[if_level].state == PROCESSING) {
496 
497             switch (parse_as_non_preproc(words, filename)) {
498 
499             case PARSE_INVDATE:
500                ERR(E_INV_DATE);
501                break;
502 
503             case PARSE_INVLINE:
504                ERR(E_INV_LINE);
505                break;
506 
507             case PARSE_NOMATCH:
508                ERR(E_NO_MATCH);
509                break;
510             }
511          }
512          extra = FALSE;
513          break;
514 
515       } /* end switch */
516 
517       if (extra) {   /* extraneous data? */
518          sprintf(msg, E_GARBAGE, ptok);
519          ERR(msg);
520       }
521 
522    } /* end while */
523 
524    if (if_level > 0) fprintf(stderr, E_UNT_IFDEF, progname, filename);
525 
526    file_level--;
527    curr_year = save_year;   /* restore saved year and font style */
528    fontstyle[0] = save_font;
529 
530    return;
531 }
532 
533 /*
534  * Routines to free allocated data (symbol table and data structure)
535  */
536 
537 /* ---------------------------------------------------------------------------
538 
539    clear_syms
540 
541    Notes:
542 
543       This routine clears and deallocates the symbol table.
544 
545 */
546 /*
547  * clear_syms -
548  */
clear_syms(void)549 void clear_syms (void)
550 {
551    int i;
552 
553    for (i = 0; i < MAX_PP_SYMS; i++) {
554       if (pp_sym[i]) {
555          free(pp_sym[i]);
556          pp_sym[i] = NULL;
557       }
558       if (pp_val[i]) {
559          free(pp_val[i]);
560          pp_val[i] = NULL;
561       }
562    }
563    return;
564 }
565 
566 /* ---------------------------------------------------------------------------
567 
568    cleanup
569 
570    Notes:
571 
572       This routine frees all allocated data.
573 
574 */
cleanup(void)575 void cleanup (void)
576 {
577    int i, j;
578    year_info *py, *pny;
579    month_info *pm;
580    day_info *pd, *pnd;
581 
582    for (py = head; py; py = pny) {   /* main data structure */
583       pny = py->next;
584       for (i = 0; i < 12; i++) {
585          if ((pm = py->month[i]) == NULL) continue;
586          for (j = 0; j < LAST_NOTE_DAY; j++) {
587             for (pd = pm->day[j]; pd; pd = pnd) {
588                pnd = pd->next;
589                free(pd->text);
590                free(pd);
591             }
592          }
593          free(pm);
594       }
595       free(py);
596    }
597 
598    clear_syms();   /* symbol table */
599 
600    return;
601 }
602 
603 /*
604  * Preprocessor token and symbol table routines
605  */
606 
607 /* ---------------------------------------------------------------------------
608 
609    find_sym_name
610 
611    Notes:
612 
613       This routine looks up a symbol name.
614 
615       It returns the symbol table index if found or 'PP_SYM_UNDEF' if not
616       found.
617 
618 */
find_sym_name(char * sym)619 int find_sym_name (char *sym)
620 {
621    int i;
622 
623    if (!sym) return PP_SYM_UNDEF;
624 
625    for (i = 0; i < MAX_PP_SYMS; i++) {
626       if (pp_sym[i] && ci_strcmp(pp_sym[i], sym) == 0) return i;
627    }
628 
629    return PP_SYM_UNDEF;
630 }
631 
632 /* ---------------------------------------------------------------------------
633 
634    find_sym_val
635 
636    Notes:
637 
638       This routine looks up a symbol's value.
639 
640       It returns the symbol value table index if found or 'PP_SYM_UNDEF' if
641       not found.
642 
643 */
find_sym_val(char * sym)644 char *find_sym_val (char *sym)
645 {
646    int j;
647 
648    return (j=find_sym_name(sym))==PP_SYM_UNDEF ? NULL : pp_val[j];
649 }
650 
651 /* ---------------------------------------------------------------------------
652 
653    do_ifdef
654 
655    Notes:
656 
657       This routine returns TRUE if 'expr' is true, FALSE if not, and
658       'EXPR_ERR' if invalid.
659 
660 */
do_ifdef(char * expr)661 int do_ifdef (char *expr)
662 {
663    return parse_expr(expr);
664 }
665 
666 /* ---------------------------------------------------------------------------
667 
668    do_ifndef
669 
670    Notes:
671 
672       This routine returns FALSE if 'expr' is true, TRUE if not, and
673       'EXPR_ERR' if invalid.
674 
675 */
do_ifndef(char * expr)676 int do_ifndef (char *expr)
677 {
678    int val;
679 
680    return (val = parse_expr(expr)) == EXPR_ERR ? EXPR_ERR : ! val;
681 }
682 
683 /* ---------------------------------------------------------------------------
684 
685    do_define
686 
687    Notes:
688 
689       This routine enters 'sym' into the symbol table.
690 
691       If 'sym' is NULL, clear the symbol table.
692 
693       Always returns 0 (for compatibility with other dispatch functions).
694 
695 */
do_define(char * sym)696 int do_define (char *sym)
697 {
698    int i;
699 
700    if (!sym) {   /* null argument - clear all definitions */
701       clear_syms();
702       return 0;
703    }
704 
705    if (DEBUG(DEBUG_PP)) fprintf(stderr, "defining %s\n", sym);
706 
707    if (do_ifdef(sym)) return 0;   /* already defined? */
708 
709    for (i = 0; i < MAX_PP_SYMS; i++) {   /* find room for it */
710       if (!pp_sym[i]) {
711          strcpy(pp_sym[i] = alloc(strlen(sym)+1), sym);
712          return 0;
713       }
714    }
715 
716    fprintf(stderr, E_SYMFULL, progname, sym);
717 
718    return 0;
719 }
720 
721 /* ---------------------------------------------------------------------------
722 
723    do_define_sym_val
724 
725    Notes:
726 
727       This routine enters the symbol value 'val' into the symbol table for
728       symbol 'sym'.  If 'sym' is not found, return 'PP_SYM_UNDEF'.
729 
730 */
do_define_sym_val(char * sym,char * val)731 int do_define_sym_val (char *sym, char *val)
732 {
733    int i;
734 
735    i = find_sym_name(sym);
736    if (i == PP_SYM_UNDEF) return PP_SYM_UNDEF;
737 
738    if (pp_val[i]) {
739       free(pp_val[i]);
740       pp_val[i]=NULL;
741    }
742 
743    if (val) strcpy(pp_val[i] = alloc(strlen(val)+1), val);
744    else pp_val[i]=NULL;
745 
746    return 0;
747 }
748 
749 /* ---------------------------------------------------------------------------
750 
751    do_undef
752 
753    Notes:
754 
755       This routine undefines 'sym' and frees its space.
756 
757       No error occurs if 'sym' is not defined.
758 
759       Always return 0 (for compatibility with other dispatch functions).
760 
761 */
do_undef(char * sym)762 int do_undef (char *sym)
763 {
764    int i;
765 
766    if (!sym) return 0;
767 
768    if (DEBUG(DEBUG_PP)) fprintf(stderr, "undefining %s\n", sym);
769 
770    if ((i = find_sym_name(sym)) != PP_SYM_UNDEF) {
771       if (pp_val[i]) {
772          free(pp_val[i]);
773          pp_val[i] = NULL;
774       }
775       free(pp_sym[i]);
776       pp_sym[i] = NULL;
777    }
778 
779    return 0;
780 }
781 
782 /* ---------------------------------------------------------------------------
783 
784    do_include
785 
786    Notes:
787 
788       This routine includes the specified file (optionally in "" or <>).
789 
790       Always returns 0 (for compatibility with related functions returning
791       'int').
792 
793       The first parameter is the path to the file, the second parameter is the
794       filename, and the third parameter is a flag to ignore a nonexistent
795       file.
796 
797 */
do_include(char * path,char * name,int noerr)798 int do_include (char *path, char *name, int noerr)
799 {
800    FILE *fp;
801    char *p, incfile[STRSIZ], tmpnam[STRSIZ], sv_tmpnam[STRSIZ];
802    int yy, yyfirst, yylast, sv_curr_year;
803 
804    if (!name) return 0;   /* whoops, no date file */
805 
806    /* copy name, stripping "" or <> */
807    strcpy(tmpnam, name + (*name == '"' || *name == '<'));
808    if ((p = P_LASTCHAR(tmpnam)) && (*p == '"' || *p == '>')) *p = '\0';
809 
810    /* save year and unmunged file name */
811    sv_curr_year = curr_year;
812    strcpy(sv_tmpnam, tmpnam);
813 
814    /* set up range of years to include: '%y', if present, is normally expanded
815       to only the current year - except in "year all" mode where it is
816       expanded to every applicable year
817    */
818    yyfirst = yylast = curr_year;
819    if (curr_year == ALL_YEARS) {
820       yyfirst = yylast = init_year;
821       if ((p = strchr(tmpnam, '%')) != NULL && *(p+1) == 'y') yylast = final_year;
822    }
823 
824    /*
825     * loop over all years to be included
826     */
827    for (yy = yyfirst; yy <= yylast; yy++) {
828       strcpy(tmpnam, sv_tmpnam);   /* restore unmunged name */
829 
830       /* replace each instance of %y with last two digits of year */
831       while ((p = strchr(tmpnam, '%')) != NULL && *(p+1) == 'y') {
832          *p++ = (yy / 10) % 10 + '0';
833          *p = yy % 10 + '0';
834       }
835 
836       if ((fp = fopen(mk_filespec(incfile, path, tmpnam), "r")) == NULL) {
837          if (noerr) continue;   /* silently ignore in include? mode */
838          fprintf(stderr, E_FOPEN_ERR, progname, incfile);
839          exit(EXIT_FAILURE);
840       }
841 
842       curr_year = yy;   /* avoid infinite recursion */
843       read_datefile(fp, incfile);   /* recursive call */
844       fclose(fp);
845    }
846 
847    curr_year = sv_curr_year;   /* restore original value */
848 
849    return 0;
850 }
851 
852 /*
853  * Dispatch functions for wildcard matching
854  */
855 
856 /* ---------------------------------------------------------------------------
857 
858    is_anyday
859 
860    Notes:
861 
862       This routine is a dummy function which always returns TRUE.
863 
864 */
is_anyday(int mm GCC_UNUSED,int dd GCC_UNUSED,int yy GCC_UNUSED)865 int is_anyday (int mm GCC_UNUSED, int dd GCC_UNUSED, int yy GCC_UNUSED)
866 {
867    return TRUE;
868 }
869 
870 /* ---------------------------------------------------------------------------
871 
872    is_weekday
873 
874    Notes:
875 
876       This routine determines whether or not mm/dd/yy is a weekday (i.e. the
877       day of the week normally prints in "logical black", the most prevalent
878       color).
879 
880 */
is_weekday(int mm,int dd,int yy)881 int is_weekday (int mm, int dd, int yy)
882 {
883    return day_color[calc_weekday(mm, dd, yy)] == weekday_color;
884 }
885 
886 /* ---------------------------------------------------------------------------
887 
888    is_workday
889 
890    Notes:
891 
892       This routine determines whether or not mm/dd/yy is a workday (i.e. the
893       day of the week normally prints in black and the date is not a holiday).
894 
895 */
is_workday(int mm,int dd,int yy)896 int is_workday (int mm, int dd, int yy)
897 {
898    return is_weekday(mm, dd, yy) && ! is_holiday(mm, dd, yy);
899 }
900 
901 /* ---------------------------------------------------------------------------
902 
903    is_holiday
904 
905    Notes:
906 
907       This routine determines whether or not mm/dd/yy is a holiday.
908 
909 */
is_holiday(int mm,int dd,int yy)910 int is_holiday (int mm, int dd, int yy)
911 {
912    year_info *py;
913    month_info *pm;
914 
915    pm = (py = find_year(yy, FALSE)) ? py->month[mm-1] : NULL;
916    return pm ? (pm->holidays & (1L << (dd-1))) != 0 : FALSE;
917 }
918 
919 /* ---------------------------------------------------------------------------
920 
921    not_weekday
922 
923    Notes:
924 
925       This routine is the inverse of the 'is_xxxx' function of similar name.
926 
927 */
not_weekday(int mm,int dd,int yy)928 int not_weekday (int mm, int dd, int yy)
929 {
930    return !is_weekday(mm, dd, yy);
931 }
932 
933 /* ---------------------------------------------------------------------------
934 
935    not_workday
936 
937    Notes:
938 
939       This routine is the inverse of the 'is_xxxx' function of similar name.
940 
941 */
not_workday(int mm,int dd,int yy)942 int not_workday (int mm, int dd, int yy)
943 {
944    return !is_workday(mm, dd, yy);
945 }
946 
947 /* ---------------------------------------------------------------------------
948 
949    not_holiday
950 
951    Notes:
952 
953       This routine is the inverse of the 'is_xxxx' function of similar name.
954 
955 */
not_holiday(int mm,int dd,int yy)956 int not_holiday (int mm, int dd, int yy)
957 {
958    return !is_holiday(mm, dd, yy);
959 }
960 
961 
962 /* ---------------------------------------------------------------------------
963 
964    is_newmoon
965 
966    Notes:
967 
968       This routine determines whether or not mm/dd/yy is the date of a new
969       moon.
970 
971 */
is_newmoon(int mm,int dd,int yy)972 int is_newmoon (int mm, int dd, int yy)
973 {
974    int quarter;
975 
976    (void) find_phase(mm, dd, yy, &quarter);
977    return quarter == MOON_NM;
978 }
979 
980 /* ---------------------------------------------------------------------------
981 
982    is_firstq
983 
984    Notes:
985 
986       This routine determines whether or not mm/dd/yy is the date of a first
987       quarter.
988 
989 */
is_firstq(int mm,int dd,int yy)990 int is_firstq (int mm, int dd, int yy)
991 {
992    int quarter;
993 
994    (void) find_phase(mm, dd, yy, &quarter);
995    return quarter == MOON_1Q;
996 }
997 
998 /* ---------------------------------------------------------------------------
999 
1000    is_fullmoon
1001 
1002    Notes:
1003 
1004       This routine determines whether or not mm/dd/yy is the date of a full
1005       moon.
1006 
1007 */
is_fullmoon(int mm,int dd,int yy)1008 int is_fullmoon (int mm, int dd, int yy)
1009 {
1010    int quarter;
1011 
1012    (void) find_phase(mm, dd, yy, &quarter);
1013    return quarter == MOON_FM;
1014 }
1015 
1016 /* ---------------------------------------------------------------------------
1017 
1018    is_lastq
1019 
1020    Notes:
1021 
1022       This routine determines whether or not mm/dd/yy is the date of a last
1023       quarter.
1024 
1025 */
is_lastq(int mm,int dd,int yy)1026 int is_lastq (int mm, int dd, int yy)
1027 {
1028    int quarter;
1029 
1030    (void) find_phase(mm, dd, yy, &quarter);
1031    return quarter == MOON_3Q;
1032 }
1033 
1034 /*
1035    Routines to find predefined holidays too complicated to express as Pcal
1036    date strings.  All add the matching date(s) (yes, holidays which span
1037    multiple days are allowed) to the 'candidate_dates[]' array (pointed to by
1038    pdate) and return the number of matching dates.
1039 */
1040 
1041 /* ---------------------------------------------------------------------------
1042 
1043    find_easter
1044 
1045    Notes:
1046 
1047       This routine finds Easter of the current year; add to date array
1048       (adapted by parties unknown from Knuth's _The Art of Computer
1049       Programming_, v. 1).
1050 
1051 */
find_easter(date_str * pdate)1052 int find_easter (date_str *pdate)   /* pointer into date array */
1053 {
1054 #define METONIC 19   /* length of metonic cycle */
1055 
1056    /* Easter is defined as the Sunday after the full moon on or after March
1057       21.  You could express that as "1st Sun after 1st full_moon ooa 21st day
1058       in March" instead of using this routine, but depending on your timezone
1059       your full moon might not fall on the same day as the "official" one.
1060    */
1061 
1062    date_str *sv_pdate = pdate;
1063    register int epact, fm;
1064    int golden, century, nleap;
1065 
1066    golden = curr_year % METONIC + 1;
1067    century = curr_year / 100 + 1;
1068    nleap = 3 * century / 4 - 12;
1069 
1070    /* correct for moon's orbit */
1071    epact = (11 * golden + 20 + (8 * century + 5) / 25 - 5 - nleap) % 30;
1072    if (epact < 0) epact += 30;
1073    if ((epact == 25 && golden > 11) || epact == 24) ++epact;
1074 
1075    /* find full moon on or after 3/21 */
1076    fm = 44 - epact;
1077    if (fm < 21) fm += 30;
1078 
1079    /* find date of following Sunday */
1080    fm += 7 - ((int)(5L * curr_year / 4) - nleap - 10 + fm) % 7;
1081 
1082    /* add Easter to date array (adjust if in April) */
1083    ADD_DATE(fm <= 31 ? MAR : APR, fm <= 31 ? fm : fm - 31, curr_year);
1084 
1085    /* return number of dates added (always 1 in this routine) */
1086    return pdate - sv_pdate;
1087 }
1088 
1089 #ifndef NO_ORTHODOX
1090 
1091 /*
1092  * orthodox Easter related holiday calculations (Angelo Haritsis <ah@doc.ic.ac.uk>)
1093  */
1094 
1095 /* ---------------------------------------------------------------------------
1096 
1097    odox_easter_from_april1
1098 
1099    Notes:
1100 
1101       This routine determines the number of days from April 1st of given year
1102       until Orthodox Easter.
1103 
1104 */
odox_easter_from_april1(int year)1105 int odox_easter_from_april1 (int year)
1106 {
1107    int a, b, leap;
1108 
1109    a = ( ( ((year-1) % METONIC) + 1 ) * METONIC + 15 ) % 30;
1110    leap = IS_LEAP(year) ? 1 : 0;
1111    b = (calc_weekday(1,1,year) + 90 + leap + a + 2) % 7;
1112    b = 7 - b + 3 + a;
1113 
1114    /* In 1923, 13 days  were added to the new greek calendar */
1115    if (year <= 1923) b -= 13;
1116 
1117    return (b);
1118 }
1119 
1120 /* ---------------------------------------------------------------------------
1121 
1122    find_odox_easter
1123 
1124    Notes:
1125 
1126       This routine finds the date for Orthodox Easter.
1127 
1128       It was almost blindly copied from PostScript source in 'gpscal'.
1129 
1130 */
find_odox_easter(date_str * pdate)1131 int find_odox_easter (date_str *pdate)   /* pointer into date array */
1132 {
1133    date_str *sv_pdate = pdate;
1134    int offs;
1135 
1136    /* easter is days from 1st April for easter sun */
1137    offs = odox_easter_from_april1(curr_year);
1138    odox_add(offs);
1139 
1140    /* return number of dates added (always 1 in this routine) */
1141    return pdate - sv_pdate;
1142 }
1143 
1144 /* ---------------------------------------------------------------------------
1145 
1146    find_odox_stgeorge
1147 
1148    Notes:
1149 
1150       This routine finds the date for the Orthodox holiday.
1151 
1152 */
find_odox_stgeorge(date_str * pdate)1153 int find_odox_stgeorge (date_str *pdate)   /* pointer into date array */
1154 {
1155    date_str *sv_pdate = pdate;
1156    int offs;
1157 
1158    /* offs is days from 1st April for easter sun */
1159    offs = odox_easter_from_april1(curr_year);
1160    if (offs >= 23) {   /* it's before easter; will move to easter Mon */
1161       offs++;
1162       odox_add(offs);
1163    }
1164    else ADD_DATE(APR, 23, curr_year);
1165 
1166    /* return number of dates added */
1167    return pdate - sv_pdate;
1168 }
1169 
1170 /* ---------------------------------------------------------------------------
1171 
1172    find_odox_marcus
1173 
1174    Notes:
1175 
1176       This routine finds the date for the Orthodox holiday.
1177 
1178 */
find_odox_marcus(date_str * pdate)1179 int find_odox_marcus (date_str *pdate)   /* pointer into date array */
1180 {
1181    date_str *sv_pdate = pdate;
1182    int offs;
1183 
1184    /* offs is days from 1st April for easter sun */
1185    offs = odox_easter_from_april1(curr_year);
1186    if (offs >= 25) {   /* it's before easter; will move to easter Tue */
1187       offs += 2;
1188       odox_add(offs);
1189    }
1190    else ADD_DATE(APR, 25, curr_year);
1191 
1192    /* return number of dates added */
1193    return pdate - sv_pdate;
1194 }
1195 
1196 #endif /* !NO_ORTHODOX */
1197 
1198 /*
1199  * Keyword classification routines
1200  */
1201 
1202 /* ---------------------------------------------------------------------------
1203 
1204    get_month
1205 
1206    Notes:
1207 
1208       This routine converts an alpha (or, optionally, numeric) string to a month.
1209 
1210       It returns 1..12 if valid, 'NOT_MONTH' if not, 'ALL_MONTHS' if "all",
1211       'ENTIRE_YEAR' if "year".
1212 
1213       The first parameter is the string to convert.
1214 
1215       The second parameter is a flag to indicate that a numeric string can be
1216       accepted.
1217 
1218       The third parameter is a flag to indicate that the string "year" can be
1219       accepted.
1220 
1221 */
get_month(char * cp,int numeric_ok,int year_ok)1222 int get_month (char *cp, int numeric_ok, int year_ok)
1223 {
1224    int mm;
1225 
1226    if (!cp) return NOT_MONTH;
1227 
1228    if (get_keywd(cp) == DT_ALL) return ALL_MONTHS;
1229 
1230    if (year_ok && get_keywd(cp) == DT_YEAR) return ENTIRE_YEAR;
1231 
1232    if (numeric_ok && isdigit((int)*cp)) mm = atoi(cp);
1233    else {
1234       /* accept month names in the active 'input' language only */
1235       for (mm = JAN; mm <= DEC; mm++) {
1236          /* Need to shorten length of string compare by 1 character when the
1237             last character is '*' so that event like '2nd Sunday in May*'
1238             don't report 'no match for wildcard'.
1239          */
1240          if ((ci_strncmp(cp, months_ml[input_language][mm-1],
1241                          strlen(cp) - ((*(cp + strlen(cp) - 1) == '*') ? 1 : 0)) == 0))
1242             break;
1243       }
1244    }
1245 
1246    return mm >= JAN && mm <= DEC ? mm : NOT_MONTH;
1247 }
1248 
1249 /* ---------------------------------------------------------------------------
1250 
1251    get_weekday
1252 
1253    Notes:
1254 
1255       This routine looks up the specified string in the weekday list.
1256 
1257       It returns 0-6 if valid, 'NOT_WEEKDAY' if not.  If 'wild_ok' flag is
1258       set, accept "day", "weekday", "workday", "holiday", or moon phase and
1259       return appropriate value.
1260 
1261 */
get_weekday(char * cp,int wild_ok)1262 int get_weekday (char *cp, int wild_ok)
1263 {
1264    int w;
1265 
1266    if (!cp) return NOT_WEEKDAY;
1267 
1268    if (wild_ok) {   /* try wildcards first */
1269       for (w = WILD_FIRST_WKD; w <= WILD_LAST_WKD; w++) {
1270          if (ci_strncmp(cp, days[w], strlen(days[w])) == 0) return w;
1271       }
1272       if ((w = get_phase(cp)) != MOON_OTHER) return w + WILD_FIRST_MOON;
1273    }
1274 
1275    /* accept day names in the active 'input' language only */
1276    for (w = SUN; w <= SAT; w++) {
1277       int compare_count;
1278       /* To allow for proper detection of the day-of-week name in constructs
1279          from the configuration file like 'all Fridays in Oct' (i.e. with the
1280          plural form of the day-of-week name), we need to compare the fewest
1281          number of characters of the 2 strings.
1282       */
1283       compare_count =
1284          (strlen(cp) < strlen(days_ml[input_language][w])) ?
1285          strlen(cp) : strlen(days_ml[input_language][w]);
1286 
1287       if (ci_strncmp(cp, days_ml[input_language][w], compare_count) == 0) return w;
1288    }
1289 
1290    return NOT_WEEKDAY;
1291 }
1292 
1293 /* ---------------------------------------------------------------------------
1294 
1295    get_keywd
1296 
1297    Notes:
1298 
1299       This routine looks up the specified string in the 'miscellaneous keyword' list.
1300 
1301       It returns the keyword code if valid, 'DT_OTHER' if not.
1302 
1303 */
get_keywd(char * cp)1304 int get_keywd (char *cp)
1305 {
1306    KWD *k;
1307 
1308    if (!cp) return DT_OTHER;
1309 
1310    for (k = keywds; k->name && ci_strncmp(cp, k->name, strlen(k->name)); k++)
1311       ;
1312 
1313    return k->code;
1314 }
1315 
1316 /* ---------------------------------------------------------------------------
1317 
1318    get_ordinal
1319 
1320    Notes:
1321 
1322       This routine looks up the specified string in the ordinal list.
1323 
1324       It returns the ordinal code (and fills in the ordinal value) if valid.
1325       It returns 'ORD_OTHER' if invalid.
1326 
1327 */
get_ordinal(char * cp,int * pval)1328 int get_ordinal (char *cp, int *pval)
1329 {
1330    KWD_O *o;
1331    int val;
1332    char **psuf;
1333 
1334    if (!cp) return ORD_OTHER;
1335 
1336    if (isdigit((int)*cp) || *cp == '-') {   /* numeric? */
1337       if ((val = atoi(cp)) == 0) return ORD_OTHER;
1338 
1339       if (*cp == '-') cp++;   /* skip over number */
1340 
1341       cp += strspn(cp, DIGITS);
1342 
1343       for (psuf = ord_suffix; *psuf; psuf++) {   /* find suffix */
1344          if (ci_strcmp(cp, *psuf) == 0) {
1345             *pval = val;
1346             return val < 0 ? ORD_NEGNUM : ORD_POSNUM;
1347          }
1348       }
1349       return ORD_OTHER;
1350    }
1351 
1352    /* look for word in ordinals list */
1353 
1354    for (o = ordinals; o->name && ci_strncmp(cp, o->name, MIN_ORD_LEN); o++)
1355       ;
1356 
1357    *pval = o->value;
1358    return o->code;
1359 }
1360 
1361 /* ---------------------------------------------------------------------------
1362 
1363    get_phase
1364 
1365    Notes:
1366 
1367       This routine converts the specified 'moon phase' string to the
1368       appropriate value.
1369 
1370 */
get_phase(char * cp)1371 int get_phase (char *cp)
1372 {
1373    KWD *p;
1374 
1375    if (!cp) return MOON_OTHER;
1376 
1377    for (p = phases; p->name && ci_strcmp(cp, p->name); p++)
1378       ;
1379 
1380    return p->code;
1381 }
1382 
1383 /* ---------------------------------------------------------------------------
1384 
1385    get_prep
1386 
1387    Notes:
1388 
1389       This routine looks up the specified string in the preposition list.
1390 
1391       It returns the preposition code if valid, 'PR_OTHER' if not.
1392 
1393 */
get_prep(char * cp)1394 int get_prep (char *cp)
1395 {
1396    KWD *p;
1397 
1398    if (!cp) return PR_OTHER;
1399 
1400    for (p = preps; p->name && ci_strncmp(cp, p->name, MIN_PREP_LEN); p++)
1401       ;
1402 
1403    return p->code;
1404 }
1405 
1406 /* ---------------------------------------------------------------------------
1407 
1408    get_token
1409 
1410    Notes:
1411 
1412       This routine looks up the specified 'token' in the list of preprocessor tokens.
1413 
1414       It returns its index or 'PP_OTHER' if not found.
1415 
1416 */
get_token(char * token)1417 int get_token (char *token)
1418 {
1419    KWD_F *p;
1420 
1421    for (p = pp_info; p->name; p++) {
1422       if (ci_strncmp(token, p->name, MIN_PPTOK_LEN) == 0) return p - pp_info;
1423    }
1424 
1425    return PP_OTHER;
1426 }
1427 
1428 /* ---------------------------------------------------------------------------
1429 
1430    get_predef_event
1431 
1432    Notes:
1433 
1434       This routine looks up the specified string in the predefined-event list.
1435 
1436       It returns its index if found, 'NOT_PREDEF_EVENT' if not.
1437 
1438 */
get_predef_event(char * cp)1439 int get_predef_event (char *cp)
1440 {
1441    KWD_H *p;
1442 
1443    for (p = predef_events; p->name; p++) {
1444       if (ci_strncmp(cp, p->name, strlen(p->name)) == 0) return p - predef_events;
1445    }
1446 
1447    return NOT_PREDEF_EVENT;
1448 }
1449 
1450 /* ---------------------------------------------------------------------------
1451 
1452    date_type
1453 
1454    Notes:
1455 
1456       This routine examines the specified token and returns the date type code.
1457 
1458       If the token is a month name, an ordinal, or day-of-week name, the
1459       appropriate code (and value if an ordinal) is returned ('DT_MONTH',
1460       'DT_ORDINAL', or 'DT_WEEKDAY').
1461 
1462       The first parameter is a pointer to the start of the token.
1463 
1464       The second parameter is the returned token type code.
1465 
1466       The third parameter is returned ordinal value.
1467 
1468 */
date_type(char * cp,int * pn,int * pv)1469 int date_type (char *cp, int *pn, int *pv)
1470 {
1471    int n, v;
1472 
1473    /* look for weekdays first, to catch wildcards "1q", "3q", etc. */
1474    if ((n = get_weekday(cp, TRUE)) != NOT_WEEKDAY) {   /* weekday name? */
1475       return (*pn = n, DT_WEEKDAY);
1476    }
1477 
1478    if ((n = get_predef_event(cp)) != NOT_PREDEF_EVENT) {   /* pre-defined event? */
1479       return (*pn = n, DT_PREDEF_EVENT);
1480    }
1481 
1482    if ((n = get_ordinal(cp, &v)) != ORD_OTHER) {   /* ordinal? */
1483       return (*pn = n, *pv = v, DT_ORDINAL);
1484    }
1485 
1486    if (isdigit((int)*cp)) {   /* other digit? */
1487       return (IS_NUMERIC(cp) ||
1488               (date_style == EUR_DATES && IS_EURDATE(cp))) ?
1489          DT_EURDATE : DT_DATE;
1490    }
1491 
1492    /* "all" can be either a keyword or a month wildcard - look for the former
1493       usage first */
1494 
1495    if ((n = get_keywd(cp)) != DT_OTHER) return n;
1496 
1497    if ((n = get_month(cp, FALSE, FALSE)) != NOT_MONTH) {   /* month name? */
1498       return (*pn = n, DT_MONTH);
1499    }
1500 
1501    return DT_OTHER;   /* unrecognized keyword - give up */
1502 }
1503 
1504 /*
1505  * Routines for entering data in the data structure (described in pcaldefs.h)
1506  */
1507 
1508 /* ---------------------------------------------------------------------------
1509 
1510    find_year
1511 
1512    Notes:
1513 
1514       This routine finds a record in the year list.
1515 
1516       It will optionally create it (based on the second parameter) if not
1517       present.
1518 
1519 */
find_year(int year,int insert)1520 year_info *find_year (int year, int insert)
1521 {
1522    year_info *pyear, *plast, *p;
1523 
1524    for (plast = NULL, pyear = head;   /* search linked list */
1525         pyear && pyear->year < year;
1526         plast = pyear, pyear = pyear->next)
1527       ;
1528 
1529    if (pyear && pyear->year == year) return pyear;   /* found - return it */
1530 
1531    if (insert) {   /* not found - insert it if requested */
1532       int i;
1533       p = (year_info *) alloc((int) sizeof(year_info));   /* create new record */
1534       p->year = year;
1535       for (i = 0; i < (int)(ARRAYSIZE(p->month)); i++) p->month[i] = NULL;
1536       p->next = pyear;   /* link it in */
1537       return *(plast ? &plast->next : &head) = p;
1538    }
1539    else return NULL;
1540 }
1541 
1542 /* ---------------------------------------------------------------------------
1543 
1544    enter_day_info
1545 
1546    Notes:
1547 
1548       This routine enters the specified text for the specified day.
1549 
1550       Avoid entering duplicates.
1551 
1552       It returns 'PARSE_INVDATE' if the specified date is invalid, 'PARSE_OK'
1553       if OK.
1554 
1555       If symbol 'FEB_29_OK' is non-zero (see 'pcaldefs.h'), it will silently
1556       ignore Feb 29 of common year.
1557 
1558 */
enter_day_info(int m,int d,int y,int text_type,char ** pword)1559 int enter_day_info (int m, int d, int y, int text_type, char **pword)
1560 {
1561    static year_info *pyear;
1562    static int prev_year = 0;
1563    month_info *pmonth;
1564    day_info *pday, *plast;
1565    int is_holiday = text_type == HOLIDAY_TEXT;
1566    char text[LINSIZ], *tface;
1567 
1568    if (! is_valid(m, d >= FIRST_NOTE_DAY && text_type == NOTE_TEXT ? 1 : d, y)) {
1569       return (m == FEB && d == 29 && FEB_29_OK) ? PARSE_OK : PARSE_INVDATE;
1570    }
1571 
1572    if (y != prev_year) {   /* avoid unnecessary year lookup */
1573       pyear = find_year(y, 1);
1574    }
1575 
1576    --m, --d;   /* adjust for use as subscripts */
1577 
1578    if ((pmonth = pyear->month[m]) == NULL) {   /* find/create month record */
1579       int i;
1580       pyear->month[m] = pmonth = (month_info *) alloc((int) sizeof(month_info));
1581       for (i = 0; i < (int)(ARRAYSIZE(pmonth->day)); i++) pmonth->day[i] = NULL;
1582    }
1583 
1584    if (is_holiday) pmonth->holidays |= (1L << d);
1585 
1586    /* insert text for day at end of list (preserving the order of entry for
1587       multiple lines on same day); eliminate those differing only in spacing
1588       and capitalization from existing entries
1589    */
1590 
1591    copy_text(text, pword);   /* consolidate text from lbuf into text */
1592 
1593    if (DEBUG(DEBUG_DATES)) {
1594       char *p;
1595       fprintf(stderr, "Adding event: %02d/%02d/%d%c '", m+1, d+1, y, is_holiday ? '*' : ' ');
1596       for (p = text; *p; p++) {
1597          fprintf(stderr, isprint((int)*p) ? "%c" : "\\%03o", *p & CHAR_MSK);
1598       }
1599       fprintf(stderr, "'\n");
1600    }
1601 
1602 #if KEEP_NULL_LINES   /* preserve blank text lines in output */
1603    if (*text == '\0' && pmonth->day[d]) strcpy(text, BLANK_TEXT);
1604 #endif
1605 
1606    /* check that non-null text is unique */
1607 
1608    if (*text) {
1609       for (plast = NULL, pday = pmonth->day[d];
1610            pday;
1611            plast = pday, pday = pday->next) {
1612 
1613          if (ci_strcmp(pday->text, text) == 0
1614 #if KEEP_NULL_LINES
1615              && strcmp(text, BLANK_TEXT) != 0
1616 #endif
1617              ) {
1618             pday->is_holiday |= is_holiday;
1619             return PARSE_OK;
1620          }
1621       }
1622 
1623       /* unique - add to end of list */
1624 
1625       pday = (day_info *) alloc(sizeof(day_info));
1626       pday->is_holiday = is_holiday;
1627 
1628       if (fontstyle[0] == ROMAN || output_type != OUTPUT_PS) {
1629          /* copy text intact (no font shift) */
1630          strcpy(pday->text = (char *) alloc(strlen(text)+1), text);
1631       }
1632       else {
1633          /* prepend font shift sequence to text */
1634          tface = fontstyle[0] == BOLD   ? BOLD_FONT : fontstyle[0] == ITALIC ? ITALIC_FONT : "";
1635          pday->text = (char *) alloc(strlen(tface) + strlen(text) + 2);
1636          strcpy(pday->text, tface);
1637          if (*tface) strcat(pday->text, " ");
1638          strcat(pday->text, text);
1639       }
1640 
1641       pday->next = NULL;
1642       *(plast ? &plast->next : &pmonth->day[d]) = pday;
1643    }
1644 
1645    return PARSE_OK;
1646 }
1647 
1648 /* ---------------------------------------------------------------------------
1649 
1650    delete_day_info
1651 
1652    Notes:
1653 
1654       This routine deletes text for the specified day.
1655 
1656       It returns 'PARSE_INVDATE' if the specified date is invalid, 'PARSE_OK'
1657       if OK.
1658 
1659       If symbol 'FEB_29_OK' is non-zero (see 'pcaldefs.h'), it will silently
1660       ignore Feb 29 of common year.
1661 
1662       It will silently ignore attempts to remove non-existent entries.  It
1663       also recalculates 'is_holiday', in case a holiday event is being
1664       deleted.
1665 
1666 */
delete_day_info(int m,int d,int y,int text_type,char ** pword)1667 int delete_day_info (int m, int d, int y, int text_type, char **pword)
1668 {
1669    static year_info *pyear;
1670    static int prev_year = 0;
1671    month_info *pmonth;
1672    day_info *pday, *plast, *pdel = NULL, *pldel = NULL;
1673    int is_holiday = FALSE;
1674    int found = FALSE;
1675    char text[LINSIZ];
1676 
1677    if (! is_valid(m, d >= FIRST_NOTE_DAY && text_type == NOTE_TEXT ? 1 : d, y)) {
1678       return (m == FEB && d == 29 && FEB_29_OK) ? PARSE_OK : PARSE_INVDATE;
1679    }
1680 
1681    if (y != prev_year) {   /* avoid unnecessary year lookup */
1682       pyear = find_year(y, 1);
1683    }
1684 
1685    --m, --d;   /* adjust for use as subscripts */
1686 
1687    if ((pmonth = pyear->month[m]) == NULL) {   /* ignore delete if no entries exist */
1688       return PARSE_OK;
1689    }
1690    if (pmonth->day[d] == NULL) return PARSE_OK;
1691 
1692    /* delete text for day from list ignoring differences in only spacing and
1693       capitalization in the existing entry.
1694    */
1695 
1696    copy_text(text, pword); /* consolidate text from lbuf into text */
1697 
1698    if (DEBUG(DEBUG_DATES)) {
1699       char *p;
1700       fprintf(stderr, "Deleting event: %02d/%02d/%d%c '", m+1, d+1, y, is_holiday ? '*' : ' ');
1701       for (p = text; *p; p++) {
1702          fprintf(stderr, isprint((int)*p) ? "%c" : "\\%03o", *p & CHAR_MSK);
1703       }
1704       fprintf(stderr, "'\n");
1705    }
1706 
1707 #if KEEP_NULL_LINES   /* preserve blank text lines in output */
1708    if (*text == '\0' && pmonth->day[d]) strcpy(text, BLANK_TEXT);
1709 #endif
1710 
1711    /* check if non-null and find entry to delete */
1712 
1713    if (*text) {
1714       for (plast = NULL, pday = pmonth->day[d];
1715            pday;
1716            plast = pday, pday = pday->next) {
1717          if (ci_strcmp(pday->text, text) == 0) {
1718             found = TRUE;
1719             pdel = pday;
1720             pldel = plast;
1721          }
1722          else is_holiday |= pday->is_holiday;
1723       }
1724 
1725       if (found) {
1726          if (pldel) pldel->next = pdel->next;
1727          else pmonth->day[d] = pdel->next;
1728          free(pdel);
1729 
1730          if (is_holiday) pmonth->holidays |= (1L << d);
1731          else pmonth->holidays &= ~(1L << d);
1732       }
1733    }
1734 
1735    return PARSE_OK;
1736 }
1737 
1738 /* ---------------------------------------------------------------------------
1739 
1740    enter_note
1741 
1742    Notes:
1743 
1744       This routine acts as a wrapper around 'enter_day_info()' for entering
1745       note text.
1746 
1747 */
enter_note(int mm,char ** pword,int n)1748 int enter_note (int mm, char **pword, int n)
1749 {
1750    int valid;
1751 
1752    /* expand "note all" into all twelve months */
1753    if (mm == ALL_MONTHS || mm == ENTIRE_YEAR) {
1754       valid = FALSE;   /* is at least one note box valid? */
1755       for (mm = JAN; mm <= DEC; mm++) {
1756          valid |= enter_day_info(mm,
1757                                  note_day(mm, n, curr_year),
1758                                  curr_year, NOTE_TEXT,
1759                                  pword+1) == PARSE_OK;
1760       }
1761       return valid ? PARSE_OK : PARSE_NOMATCH;
1762    }
1763 
1764    return enter_day_info(mm, note_day(mm, n, curr_year), curr_year, NOTE_TEXT, pword+1);
1765 }
1766 
1767 /* ---------------------------------------------------------------------------
1768 
1769    process_event_specification
1770 
1771    Notes:
1772 
1773       This routine adds or deletes events (including the specific date and its
1774       associated text) to/from the big linked list structure (described in
1775       greater detail in 'pcaldefs.h').
1776 
1777 */
process_event_specification(char ** pword,int * ptext_type,char *** pptext)1778 int process_event_specification (char **pword, int *ptext_type, char ***pptext)
1779 {
1780    int rtn, match;
1781    date_str *pd;
1782 
1783    /* parse date spec and enter information for each match */
1784    if ((rtn = parse_date(pword, ptext_type, pptext)) == PARSE_OK) {
1785       match = FALSE;
1786       for (pd = candidate_dates; pd->mm; pd++) {
1787 
1788          if (pd->yy == -1) {
1789             if (DEBUG(DEBUG_DATES)) {
1790                fprintf(stderr, "Bypassing invalidated candidate date: yyyy-%02d-%02d.\n", pd->mm, pd->dd);
1791             }
1792          }
1793          else {
1794             if (DEBUG(DEBUG_DATES)) {
1795                fprintf(stderr, "Processing candidate date: %4d-%02d-%02d\n", pd->yy, pd->mm, pd->dd);
1796             }
1797 
1798             if (delete_entry) {
1799                match |= delete_day_info(pd->mm, pd->dd, pd->yy, *ptext_type, *pptext) == PARSE_OK;
1800             }
1801             else {
1802                match |= enter_day_info(pd->mm, pd->dd, pd->yy, *ptext_type, *pptext) == PARSE_OK;
1803             }
1804          }
1805       }
1806       rtn = match ? PARSE_OK : PARSE_NOMATCH;
1807    }
1808    return rtn;
1809 }
1810 
1811 /*
1812  * Date parsing routines:
1813  */
1814 
1815 /* ---------------------------------------------------------------------------
1816 
1817    parse_ord
1818 
1819    Notes:
1820 
1821       This routine parses an ordinal date specification (e.g. "first Monday in
1822       September", "every Sunday in October", "last workday in all").
1823 
1824       It returns 'PARSE_OK' if line syntax is valid, 'PARSE_INVLINE' if not.
1825 
1826       It writes all matching dates (if any) to the global array
1827       'candidate_dates[]'.  It terminates the date list with a null entry.
1828 
1829       The first parameter is a valid ordinal code (from 'get_ordinal()').
1830 
1831       The second parameter is the ordinal value (also from 'get_ordinal()').
1832 
1833       The third parameter is a pointer to the word after the ordinal.
1834 
1835 */
parse_ord(int ord,int val,char ** pword)1836 int parse_ord (int ord, int val, char **pword)
1837 {
1838    int wkd, mon, mm, dd, len, doit, (*pfcn) (int, int, int);
1839    int val_first, val_last, val_incr, mon_first, mon_last;
1840    date_str *pdate, date;
1841 
1842    if ((wkd = get_weekday(*pword, TRUE)) == NOT_WEEKDAY ||   /* weekday */
1843        *++pword == NULL ||   /* any word */
1844        (mon = get_month(*++pword, FALSE, TRUE)) == NOT_MONTH) {   /* month */
1845       return PARSE_INVLINE;
1846    }
1847 
1848    /* set up loop boundaries for month loop */
1849    mon_first = mon == ALL_MONTHS || mon == ENTIRE_YEAR ? JAN : mon;
1850    mon_last  = mon == ALL_MONTHS || mon == ENTIRE_YEAR ? DEC : mon;
1851 
1852    pdate = candidate_dates;   /* start of 'candidate_dates[]' array */
1853 
1854    /* special case of "all|odd|even <wildcard> in <month>|all|year" */
1855 
1856    if ((ord == ORD_ALL || ord == ORD_EVEN || ord == ORD_ODD) && IS_WILD(wkd)) {
1857       pfcn = pdatefcn[wkd - WILD_FIRST];
1858       doit = ord != ORD_EVEN;
1859       for (mm = mon_first; mm <= mon_last; mm++) {
1860          len = LENGTH_OF(mm, curr_year);
1861          if (mon != ENTIRE_YEAR) doit = ord != ORD_EVEN;
1862          for (dd = 1; dd <= len; dd++) {
1863             if ((*pfcn)(mm, dd, curr_year)) {
1864                if (doit) ADD_DATE(mm, dd, curr_year);
1865                if (ord != ORD_ALL) doit = ! doit;
1866             }
1867          }
1868       }
1869    }
1870 
1871    /* special case of "odd|even <weekday> in year" */
1872 
1873    else if ((ord == ORD_EVEN || ord == ORD_ODD) && mon == ENTIRE_YEAR) {
1874       date.mm = JAN;   /* starting date */
1875       date.dd = calc_day(ord == ORD_EVEN ? 2 : 1, wkd, JAN);
1876       date.yy = curr_year;
1877       do {   /* alternates throughout year */
1878          ADD_DATE(date.mm, date.dd, date.yy);
1879          date.dd += 14;
1880          normalize(&date);
1881       } while (date.yy == curr_year);
1882    }
1883 
1884    /* special case of "<ordinal>|last <weekday>|<wildcard> in year" */
1885 
1886    else if ((ord == ORD_NEGNUM || ord == ORD_POSNUM) && mon == ENTIRE_YEAR) {
1887       if (calc_year_day(val, wkd, &date)) ADD_DATE(date.mm, date.dd, date.yy);
1888    }
1889 
1890    /* all other combinations of ordinal and day */
1891 
1892    else {
1893       /* set up loop boundaries for "wildcard" ordinals */
1894 
1895       val_first = ord == ORD_ALL || ord == ORD_ODD ? 1 : ord == ORD_EVEN ? 2 : val;
1896       val_last  = ord == ORD_ALL || ord == ORD_ODD ? 5 : ord == ORD_EVEN ? 4 : val;
1897       val_incr  = ord == ORD_ODD || ord == ORD_EVEN ? 2 : 1;
1898 
1899       for (mm = mon_first; mm <= mon_last; mm++) {
1900          for (val = val_first; val <= val_last; val += val_incr) {
1901             if ((dd = calc_day(val, wkd, mm)) != 0) ADD_DATE(mm, dd, curr_year);
1902          }
1903       }
1904    }
1905 
1906    TERM_DATES;   /* terminate array with null entry */
1907    return PARSE_OK;
1908 }
1909 
1910 /* ---------------------------------------------------------------------------
1911 
1912    parse_rel
1913 
1914    Notes:
1915 
1916       This routine parses a relative date specification (e.g. "Friday after
1917       fourth Thursday in November", "2nd Saturday after first Friday in all").
1918 
1919       It returns 'PARSE_OK' if the line syntax valid, 'PARSE_INVLINE' if not.
1920 
1921       It transforms all dates that match the base date to the appropriate day,
1922       month, and year.
1923 
1924       It calls 'parse_date()' recursively in order to handle cases such as
1925       "Friday after Tuesday before last day in all".
1926 
1927       The first parameter is a valid (positive) ordinal value.
1928 
1929       The second parameter is a valid weekday code (from 'get_weekday()').
1930 
1931       The third parameter is a pointer to the word after the weekday.
1932 
1933       The fourth parameter is a pointer to the returned text type
1934       (holiday/non-holiday).
1935 
1936       The fifth parameter is a pointer to the returned first word of text.
1937 
1938 */
parse_rel(int val,int wkd,char ** pword,int * ptype,char *** pptext)1939 int parse_rel (int val, int wkd, char **pword, int *ptype, char ***pptext)
1940 {
1941    int prep, n, rtn, base_wkd, incr = 1, (*pfcn) (int, int, int);
1942    date_str *pd;
1943 
1944    /* we have the weekday - now look for the preposition */
1945    if ((prep = get_prep(*pword++)) == PR_OTHER) return PARSE_INVLINE;
1946 
1947    /* get the base date */
1948    if ((rtn = parse_date(pword, ptype, pptext)) != PARSE_OK) return rtn;
1949 
1950    /* transform 'candidate_dates' array in place - note that the relative date may not
1951       be in the same month or even year */
1952 
1953    if (IS_WILD(wkd)) {   /* wildcard for weekday name? */
1954       pfcn = pdatefcn[wkd - WILD_FIRST];
1955 
1956       for (pd = candidate_dates; pd->mm; pd++) {
1957 
1958          /* search for nearest matching date */
1959 
1960          switch (prep) {
1961          case PR_BEFORE:
1962             pd->dd -= 1;   /* start with previous date */
1963             normalize(pd);
1964             /* fall through */
1965          case PR_ON_BEFORE:
1966             incr = -1;   /* search backwards */
1967             break;
1968 
1969          case PR_AFTER:
1970             pd->dd += 1;   /* start with following date */
1971             normalize(pd);
1972             /* fall through */
1973          case PR_ON_AFTER:
1974             incr = 1;   /* search forward */
1975             break;
1976 
1977          case PR_NEAREST:
1978             /* If NEAREST_INCR (cf. pcaldefs.h) is 1, pcal will disambiguate
1979                "nearest" in favor of the later date; if -1, in favor of the
1980                earlier.  "incr" will take the values 1, -2, 3, -4, ... or -1,
1981                2, -3, 4 ...  respectively during the search loop below.
1982 
1983                ("nearest_before" and "nearest_after" were added to v4.6 to
1984                allow the user to specify disambiguation of two equally near
1985                dates.)
1986             */
1987             incr = NEAREST_INCR;
1988             val = 1;   /* ordinals meaningless here */
1989             break;
1990 
1991          case PR_NEAREST_BEFORE:
1992             incr = -1;   /* start searching backward */
1993             val = 1;   /* ordinals meaningless here */
1994             break;
1995 
1996          case PR_NEAREST_AFTER:
1997             incr = 1;   /* start searching forward */
1998             val = 1;   /* ordinals meaningless here */
1999             break;
2000          }
2001 
2002          n = val;
2003          while (!((*pfcn)(pd->mm, pd->dd, pd->yy) && --n == 0)) {
2004             pd->dd += incr;
2005             normalize(pd);
2006             /* if searching for "nearest" date, invert sign and bump magnitude
2007                of 'incr' on each pass
2008             */
2009             if (prep == PR_NEAREST || prep == PR_NEAREST_BEFORE || prep == PR_NEAREST_AFTER) {
2010                incr -= (incr > 0) ? (2 * incr + 1) : (2 * incr - 1);
2011             }
2012          }
2013       }
2014 
2015    }
2016    else  {   /* explicit weekday name */
2017       for (pd = candidate_dates; pd->mm; pd++) {
2018 
2019          /* calculate nearest matching weekday - note that "nearest_before"
2020            and "nearest_after" are synonyms for "before" and "after"
2021          */
2022 
2023          base_wkd = calc_weekday(pd->mm, pd->dd, pd->yy);
2024          switch (prep) {
2025          case PR_BEFORE:
2026          case PR_NEAREST_BEFORE:
2027          case PR_ON_BEFORE:
2028             if (prep != PR_ON_BEFORE || wkd != base_wkd) pd->dd -= 7 - (wkd - base_wkd + 7) % 7;
2029             pd->dd -= 7 * (val - 1);
2030             break;
2031 
2032          case PR_AFTER:
2033          case PR_NEAREST_AFTER:
2034          case PR_ON_AFTER:
2035             if (prep != PR_ON_AFTER || wkd != base_wkd) pd->dd += (wkd - base_wkd + 6) % 7 + 1;
2036             pd->dd += 7 * (val - 1);
2037             break;
2038 
2039          case PR_NEAREST:
2040             /* use closer of previous and next */
2041             val = wkd - base_wkd;
2042             pd->dd += (val > 3) ? (val - 7) : (val < -3) ? (val + 7) : val;
2043             break;
2044 
2045          case PR_ON:
2046             /*
2047               The 'on' preposition is a special case.  When the conditions it
2048               specifies are not matched, we really need to actually _remove_
2049               this entry from the 'candidate dates' array.
2050 
2051               However, for now, it's easier to just set the 'year' ('yy')
2052               field in the 'candidate dates' array to -1, as a flag to ignore
2053               this date entry entirely when later adding events to the big
2054               linked list based on entries from this 'candidate dates' array.
2055             */
2056             if (wkd != base_wkd) {
2057                pd->yy = -1;  /* invalidate this 'candidate date' entry... */
2058             }
2059             break;
2060 
2061          default:
2062             return PARSE_INVLINE;
2063             break;
2064          }
2065 
2066          normalize(pd);   /* adjust for month/year crossing */
2067       }
2068    }
2069 
2070    return PARSE_OK;
2071 }
2072 
2073 /* ---------------------------------------------------------------------------
2074 
2075    parse_date
2076 
2077    Notes:
2078 
2079       This routine parses a date specification in any of its myriad forms.
2080 
2081       Upon return, array 'candidate_dates[]' will contain a list of all the
2082       dates that matched, terminated by a null entry.  This routine also fills
2083       in the date type (holiday/non- holiday) code and the pointer to the
2084       first word of text and sets the flag 'curr_year_reset' if the date
2085       specified (e.g., dd/mm/yy) explicitly reset the year.
2086 
2087       The first parameter is a pointer to the first word to parse.
2088 
2089       The second parameter is a pointer to the returned date type
2090       (holiday/non-holiday).
2091 
2092       The third parameter is a pointer to the returned first word of the event
2093       text string from the line in the configuration file.
2094 
2095 */
parse_date(char ** pword,int * ptype,char *** pptext)2096 int parse_date (char **pword, int *ptype, char ***pptext)
2097 {
2098    int mm, dd, yy;
2099    int token, n, v, ord, val, wkd, rtn;
2100    date_str *pdate;
2101    char *cp;
2102 
2103    pdate = candidate_dates;
2104    curr_year_reset = FALSE;   /* set below if date is dd/mm/yy */
2105 
2106    switch (token = date_type(*pword, &n, &v)) {
2107 
2108    case DT_MONTH:   /* <month> dd */
2109       if (date_style != USA_DATES) return PARSE_INVLINE;
2110 
2111       if ((cp = *++pword) == NULL) return PARSE_INVLINE;
2112 
2113       ADD_DATE(n, atoi(cp), curr_year);
2114       TERM_DATES;
2115 
2116       break;
2117 
2118    case DT_DATE:   /* mm/dd{/yy} | dd/mm{/yy} */
2119       n = split_date(*pword,
2120                      date_style == USA_DATES ? &mm : &dd,
2121                      date_style == USA_DATES ? &dd : &mm,
2122                      &yy);
2123 
2124       if (n > 2) {   /* year present? */
2125          if (yy < 100) yy += century();
2126          curr_year = yy;   /* reset current year */
2127          curr_year_reset = TRUE;
2128       }
2129 
2130       ADD_DATE(mm, dd, curr_year);
2131       TERM_DATES;
2132 
2133       break;
2134 
2135    case DT_EURDATE:   /* dd [ <month> | "all" ] */
2136       if (date_style != EUR_DATES) return PARSE_INVLINE;
2137 
2138       dd = atoi(*pword);
2139 
2140       if (get_keywd(*++pword) == DT_ALL) {
2141          for (mm = JAN; mm <= DEC; mm++) {   /* wildcard */
2142             ADD_DATE(mm, dd, curr_year);
2143          }
2144       }
2145       else {   /* one month */
2146          if ((mm = get_month(*pword, TRUE, FALSE)) == NOT_MONTH) return PARSE_INVLINE;
2147 
2148          ADD_DATE(mm, dd, curr_year);
2149       }
2150 
2151       TERM_DATES;
2152       break;
2153 
2154    case DT_ALL:
2155       /* "all" <weekday> "in" [ <month> | "all" ] or "all" <day>" */
2156       if ((cp = *(pword+1)) && (*(cp += strspn(cp, DIGITS)) == '\0' || *cp == '*')) {
2157          dd = atoi(*++pword);   /* "all" <day> */
2158          for (mm = JAN; mm <= DEC; mm++) ADD_DATE(mm, dd, curr_year);
2159          TERM_DATES;
2160          break;   /* leave switch */
2161       }
2162 
2163       n = ORD_ALL;   /* "all" <weekday> ... */
2164       v = 0;
2165       /* fall through */
2166 
2167    case DT_ORDINAL:
2168       /*
2169          <ordinal> <weekday> in [ <month> | "all" ]
2170             or
2171          <ordinal> <weekday> <prep> <date>
2172       */
2173       ord = n;
2174       val = v;
2175 
2176       /* disambiguate above cases based on preposition */
2177       if (ord == ORD_POSNUM && pword[1] && (get_prep(pword[2]) != PR_OTHER)) {
2178          if ((wkd = get_weekday(pword[1], TRUE)) == NOT_WEEKDAY) return PARSE_INVLINE;
2179          return parse_rel(val, wkd, pword += 2, ptype, pptext);
2180       }
2181       if ((rtn = parse_ord(ord, val, pword + 1)) != PARSE_OK) return rtn;
2182 
2183       pword += 3;   /* last word of date */
2184       break;
2185 
2186    case DT_WEEKDAY:   /* <weekday> <prep> <date> */
2187       wkd = n;
2188       /* parse_rel() calls parse_date() recursively */
2189       return parse_rel(1, wkd, ++pword, ptype, pptext);
2190       break;
2191 
2192    case DT_PREDEF_EVENT:   /* predefined event */
2193       /*
2194          predefined events will either have a redefinition string or a
2195          dispatch function - never both (cf. pcallang.h)
2196       */
2197       if (predef_events[n].pfcn == NULL) {
2198          char redef[STRSIZ], *rwords[20], **pdum;
2199          int rtn, idum;
2200 
2201          /* tokenize local copy of redefinition string */
2202          strcpy(redef, predef_events[n].def);
2203          (void) loadwords(rwords, redef);
2204 
2205          /* call parse_date() recursively to parse the redefinition - if OK,
2206             drop through to fill in real ptype and pptext from original string
2207             (this works basically because the holiday is always the last token
2208             before the text)
2209          */
2210          if ((rtn = parse_date(rwords, &idum, &pdum)) != PARSE_OK) return rtn;
2211       }
2212       else {
2213          /* predefined event has a dispatch function - use it */
2214          pdate += (*predef_events[n].pfcn)(pdate);
2215          TERM_DATES;
2216       }
2217       break;
2218 
2219    default:
2220       return PARSE_INVLINE;
2221       break;
2222    }
2223 
2224    /* at this point, pword points to the last component of the date; fill in
2225       type code and pointer to following word (start of text)
2226    */
2227    *ptype = LASTCHAR(*pword) == '*' ? HOLIDAY_TEXT : DAY_TEXT;
2228    *pptext = ++pword;
2229 
2230    return PARSE_OK;
2231 }
2232 
2233 /* ---------------------------------------------------------------------------
2234 
2235    parse_as_non_preproc
2236 
2237    Notes:
2238 
2239       This routine parses a single non-preprocessor line from the
2240       configuration file.
2241 
2242       This includes the "year", "opt", "note", "delete", and (most frequently)
2243       event specification entries in the configuration file.
2244 
2245       For event specifications (and event deletions), this routine calls
2246       'process_event_specification()' to parse the date specification and to
2247       enter the matching event date(s) in the global array
2248       'candidate_dates[]'.
2249 
2250       Note: Pre-processor ('cpp'-like) directives (e.g. "include") from the
2251       configuration file are handled in 'read_datefile()'.
2252 
2253       The first parameter is a pointer to the first word to parse.
2254 
2255       The second parameter is the name of the configuration file (for error
2256       messages).
2257 
2258 */
parse_as_non_preproc(char ** pword,char * filename)2259 int parse_as_non_preproc (char **pword, char *filename)
2260 {
2261    register char *cp;
2262    char **ptext;
2263    int mm, yy;
2264    int text_type, n, v, match;
2265    int token;
2266 
2267    /*
2268       Get first field and call date_type() to decode it
2269    */
2270    cp = *pword;
2271 
2272    switch (token = date_type(cp, &n, &v)) {
2273 
2274    case DT_YEAR:
2275       if ((cp = *++pword) != NULL && (yy = atoi(cp)) > 0) {
2276          if (yy < 100) yy += century();
2277          curr_year = yy;
2278          return PARSE_OK;
2279       }
2280       if (strcmp(cp, ALL) == 0 || strcmp(cp, "*") == 0) {
2281          curr_year = ALL_YEARS;
2282          return PARSE_OK;
2283       }
2284       return PARSE_INVLINE;   /* year missing or non-numeric */
2285       break;
2286 
2287    case DT_OPT:
2288       if (!get_args(pword, P_OPT, filename, FALSE)) {
2289          display_usage(stderr, FALSE);
2290          exit(EXIT_FAILURE);
2291       }
2292       return PARSE_OK;
2293       break;
2294 
2295    case DT_INPUT_LANGUAGE:
2296       pword++;  /* point to 2-letter 'language code' string */
2297       if (*pword) {
2298          int i;
2299          for (i = 0; i < NUM_LANGUAGES; i++) {
2300             if (ci_strncmp(lang_id[i], *pword, MIN_LANG_LEN) == 0) {
2301                input_language = i;
2302                return PARSE_OK;
2303             }
2304          }
2305       }
2306       return PARSE_INVLINE;
2307       break;
2308 
2309    case DT_NOTE:
2310       /* look for optional "/<n>" following keyword */
2311       n = (cp = strrchr(cp, '/')) ? atoi(++cp) : 0;
2312 
2313       if ((mm = get_month(*++pword, TRUE, TRUE)) == NOT_MONTH) return PARSE_INVLINE;
2314 
2315       /* if "year all" in effect, wildcard all applicable years */
2316       if (curr_year == ALL_YEARS) {
2317          match = FALSE;
2318          for (curr_year = init_year; curr_year <= final_year; curr_year++) {
2319             match |= enter_note(mm, pword, n) == PARSE_OK;
2320          }
2321          curr_year = ALL_YEARS;   /* reset to wildcard */
2322          return match ? PARSE_OK : PARSE_NOMATCH;
2323       }
2324       else return enter_note(mm, pword, n);
2325       break;
2326 
2327    case DT_OTHER:   /* unrecognized token */
2328       return PARSE_INVLINE;
2329       break;
2330 
2331    case DT_DELETE:   /* fall through to actually delete event entry */
2332       pword++;
2333       delete_entry = TRUE;
2334 
2335    default:
2336       /* At this point, we assume that the configuration file line is an event
2337          specification... */
2338 
2339       /* If the current year is a wildcard ("all" or "*" - see above), enter
2340          the date for each year covered at least partially by the calendar.
2341          Note that a "mm/dd/yy" date spec explicitly resets curr_year;
2342          parse_date() sets the 'curr_year_reset' flag when this happens so
2343          that we can quit immediately.
2344       */
2345       if (curr_year == ALL_YEARS) {
2346          match = FALSE;
2347          /* loop over each applicable year */
2348          for (curr_year = init_year; curr_year <= final_year; curr_year++) {
2349             match |= process_event_specification(pword, &text_type, &ptext) == PARSE_OK;
2350             if (curr_year_reset) {   /* quit if year reset */
2351                return match ? PARSE_OK : PARSE_NOMATCH;
2352             }
2353          }
2354 
2355          /* restore year to wildcard for next time */
2356          curr_year = ALL_YEARS;
2357          delete_entry = FALSE;
2358          return match ? PARSE_OK : PARSE_NOMATCH;
2359       }
2360 
2361       match = process_event_specification(pword, &text_type, &ptext);
2362       delete_entry = FALSE;
2363       return match;
2364 
2365       break;
2366    }
2367 }
2368