[alt.sources] Pcal v4.0, part 2 of 5

jbr0@cbnews.att.com (joseph.a.brownlee) (03/14/91)

#!/bin/sh
# This is part 02 of a multipart archive
# ============= exprpars.c ==============
if test -f 'exprpars.c' -a X"$1" != X"-c"; then
	echo 'x - skipping exprpars.c (File already exists)'
else
echo 'x - extracting exprpars.c (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'exprpars.c' &&
/*
X * exprpars.c - Pcal routines concerned with parsing if{n}def expressions
X *
X * Contents:
X *
X *		do_xxx
X *		lookup_token
X *		next_token
X *		parse_expr
X *
X * Revision history:
X *
X *	4.0	AWR	02/06/91	Author
X *
X */
X
/*
X * Standard headers:
X */
X
#include <ctype.h>
#include <string.h>
#include <stdio.h>
X
/*
X * Pcal-specific definitions:
X */
X
#include "pcaldefs.h"
#include "pcalglob.h"
X
/*
X * Macros:
X */
X
/*
X * token type code definitions:
X */
X
#define TK_UNKNOWN	 0		/* codes returned by next_token() */
#define TK_IDENT	 1
#define TK_LPAREN	 2
#define TK_RPAREN	 3
#define TK_UNARYOP	 4
#define TK_BINARYOP	 5
#define TK_ENDINPUT	 6
#define TK_STARTINPUT	 7		/* special code for start symbol */
X
/* bit position for token type codes (cf. where_ok[] below) */
#define ID_OK		(1 << TK_IDENT)
#define LP_OK		(1 << TK_LPAREN)
#define RP_OK		(1 << TK_RPAREN)
#define UO_OK		(1 << TK_UNARYOP)
#define BO_OK		(1 << TK_BINARYOP)
#define ST_OK		(1 << TK_STARTINPUT)
#define NEVER_OK	0
X
/* is token "curr" legal after "prev"? (cf. where_ok[] below) */
#define IS_LEGAL(curr, prev)	(where_ok[curr] & (1 << (prev)))
X
/*
X * operator-related definitions:
X */
X
#define OP_AND		0	/* operator subcodes */
#define OP_OR		1
#define OP_XOR		2
#define OP_NEGATE	3
X
#define ENDINPUT_PREC	-1	/* arbitrary number < lowest op. prec  */
#define OR_PREC		 1	/* operator precedence levels */
#define XOR_PREC	 2
#define AND_PREC	 3
#define NEGATE_PREC	 4
#define PAREN_PREC	 8	/* arbitrary number > highest op. prec */
X
/* lower bits of operator stack entry are code; higher are precedence */
#define OPR_BITS	4
#define OPR_MASK	((1 << OPR_BITS) - 1)
#define PREC(op)	((op) >> OPR_BITS)
#define OPCODE(op)	((op) & OPR_MASK)
#define MAKE_OPR(p, o)	(((p) << OPR_BITS) | (o))
X
#define MAX_OP		20	/* size of operand and operator stacks */
X
/*
X * Globals:
X */
X
typedef short OPERAND;		/* types for operand and operator stacks */
typedef short OPERATOR;
X
X
typedef struct {
X	char	*name;		/* token spelling */
X	short	type;		/* token type code */
X	short	value;		/* associated value */
X	} TOKEN;
X
/* token table - note that substrings must follow longer strings */
X
TOKEN token_tbl[] = {
X	"&&",	TK_BINARYOP,	OP_AND,		/* synonym for "&" */
X	"&",	TK_BINARYOP,	OP_AND,
X	"||",	TK_BINARYOP,	OP_OR,		/* synonym for "|" */
X	"|",	TK_BINARYOP,	OP_OR,
X	"!",	TK_UNARYOP,	OP_NEGATE,
X	"^",	TK_BINARYOP,	OP_XOR,
X	"(",	TK_LPAREN,	0,
X	")",	TK_RPAREN,	0,
X	NULL,	TK_UNKNOWN,	0		/* must be last entry */
X	};
X
X
typedef struct {
X	short prec;		/* precedence */
X	short type;		/* token type (TK_UNARYOP or TK_BINARYOP) */
#ifdef PROTOS
X	OPERAND (*pfcn)(OPERAND *);	/* dispatch function */
#else
X	OPERAND (*pfcn)();		/* dispatch function */
#endif
X	} OPR;
X
/* operator table - entries must be in same order as OP_XXX */
X
#ifdef PROTOS
static OPERAND do_and(OPERAND *);
static OPERAND do_or(OPERAND *);
static OPERAND do_xor(OPERAND *);
static OPERAND do_negate(OPERAND *);
#else
static OPERAND do_and(), do_or(), do_xor(), do_negate();   /* dispatch fcns */
#endif
X
OPR opr_tbl[] = {
X	AND_PREC,	TK_BINARYOP,	do_and,		/* OP_AND	*/
X	OR_PREC,	TK_BINARYOP,	do_or,		/* OP_OR	*/
X	XOR_PREC,	TK_BINARYOP,	do_xor,		/* OP_XOR	*/
X	NEGATE_PREC,	TK_UNARYOP,	do_negate	/* OP_NEGATE	*/
X	};
X
X
/* set of tokens which each token may legally follow (in TK_XXX order) */
X
int where_ok[] = {
X	NEVER_OK                                      ,	 /* TK_UNKNOWN	*/
X	ST_OK         | LP_OK         | UO_OK | BO_OK ,	 /* TK_IDENT	*/
X	ST_OK         | LP_OK         | UO_OK | BO_OK ,	 /* TK_LPAREN	*/
X	        ID_OK | LP_OK | RP_OK                 ,	 /* TK_RPAREN	*/
X	ST_OK         | LP_OK                 | BO_OK ,	 /* TK_UNARYOP	*/
X	        ID_OK         | RP_OK                 ,	 /* TK_BINARYOP	*/
X	ST_OK | ID_OK         | RP_OK                    /* TK_ENDINPUT	*/
X	};
X
X
/*
X * do_xxx - dispatch functions for operators
X */
X
#ifdef PROTOS
static OPERAND do_and(OPERAND *ptop)
#else
static OPERAND do_and(ptop)
X	OPERAND *ptop;
#endif
{
X	return ptop[0] & ptop[-1];
}
X
X
#ifdef PROTOS
static OPERAND do_or(OPERAND *ptop)
#else
static OPERAND do_or(ptop)
X	OPERAND *ptop;
#endif
{
X	return ptop[0] | ptop[-1];
}
X
X
#ifdef PROTOS
static OPERAND do_xor(OPERAND *ptop)
#else
static OPERAND do_xor(ptop)
X	OPERAND *ptop;
#endif
{
X	return ptop[0] ^ ptop[-1];
}
X
X
#ifdef PROTOS
static OPERAND do_negate(OPERAND *ptop)
#else
static OPERAND do_negate(ptop)
X	OPERAND *ptop;
#endif
{
X	return ! ptop[0];
}
X
X
/*
X * lookup_token - look up token in table; return pointer to table entry
X */
#ifdef PROTOS
static TOKEN *lookup_token(char *p)
#else
static TOKEN *lookup_token(p)
X	char *p;
#endif
{
X	TOKEN *ptok;
X
X	for (ptok = token_tbl;
X	     ptok->name && strncmp(p, ptok->name, strlen(ptok->name));
X	     ptok++)
X		;
X
X	return ptok;
}
X
X
/*
X * next_token - fetch next token from input string; fill in its type and value
X * and return pointer to following character
X */
#ifdef PROTOS
static char *next_token(char *p,
X			int *ptype,
X			int *pvalue)
#else
static char *next_token(p, ptype, pvalue)
X	char *p;
X	int *ptype;
X	int *pvalue;
#endif
{
X	TOKEN *ptok;
X	char tokbuf[STRSIZ], *pb;
X
#define NT_RETURN(p, t, v) \
X	if (1) { *ptype = t; *pvalue = v; return p; } else
X
X	while (*p && isspace(*p))	/* skip whitespace */
X		p++;
X
X	if (*p == '\0')			/* end of input? */
X		NT_RETURN(p, TK_ENDINPUT, 0);
X
X	if (isalpha(*p)) {		/* identifier? */
X
X		pb = tokbuf;		/* make local copy and look up */
X		while (*p && (isalpha(*p) || isdigit(*p) || *p == '_'))
X			*pb++ = *p++;
X		*pb = '\0';
X
X		NT_RETURN(p, TK_IDENT, find_sym(tokbuf));
X	}
X
X	ptok = lookup_token(p);		/* other token */
X	NT_RETURN(p + (ptok->name ? strlen(ptok->name) : 1), ptok->type,
X		ptok->value);
}
X
X
/*
X * parse_expr - parses expression consisting of identifiers and logical
X * operators; return TRUE if expression is true (identifier defined => true);
X * FALSE if false; EXPR_ERR if syntax error in expression
X */
#ifdef PROTOS
int parse_expr(char *pbuf)
#else
int parse_expr(pbuf)
X	char *pbuf;
#endif
{
X	OPERAND opd_stack[MAX_OP];	/* operand stack - TRUE/FALSE values */
X	OPERATOR opr_stack[MAX_OP];	/* operator stack - precedence | op */
X	int value, token, plevel, prec, result, npop, opr, opd, prev_token, op;
X
X	plevel = 0;			/* paren nesting level */
X	opd = opr = -1;			/* indices of stack tops */
X	prev_token = TK_STARTINPUT;	/* to detect null expressions */
X
X	do {
X		pbuf = next_token(pbuf, &token, &value);
X
X		/* check that the current token may follow the previous one */
X		if (! IS_LEGAL(token, prev_token))
X			return EXPR_ERR;
X
X		switch(token) {
X
X		case TK_IDENT:		/* identifier => 1 if def, 0 if not */
X			opd_stack[++opd] = value != PP_SYM_UNDEF;
X			break;
X
X		case TK_LPAREN:		/* left paren - bump nesting level */
X			++plevel;
X			break;
X
X		case TK_RPAREN:		/* right paren - decrement nesting */
X			if (--plevel < 0)
X				return EXPR_ERR;
X			break;
X
X		case TK_ENDINPUT:	/* end-of-input - treat as operator */
X			if (prev_token == TK_STARTINPUT)
X				return FALSE;	/* null expr => FALSE */
X			/* fall through */
X
X		case TK_UNARYOP:
X		case TK_BINARYOP:
X
X			/* get precedence of operator, adjusting for paren
X			 * nesting (TK_ENDINPUT has the lowest precedence
X			 * of all, to unwind operand/operator stacks at end)
X			 */
X
X			prec = token == TK_ENDINPUT ? ENDINPUT_PREC :
X				(plevel * PAREN_PREC) + opr_tbl[value].prec;
X
X			/* pop (and perform) any equal- or higher-precedence
X			 * operators on operator stack: extract operator,
X			 * check for operand stack underflow, execute
X			 * operator, adjust operand stack height and place
X			 * result of operator on top
X			 */
X
X			for ( ;
X			     opr >= 0 && PREC(opr_stack[opr]) >= prec;
X			     opr--) {
X				op = OPCODE(opr_stack[opr]);
X				npop = opr_tbl[op].type == TK_UNARYOP ? 0 : 1;
X				if (opd < npop)
X					return EXPR_ERR;
X				result = (*opr_tbl[op].pfcn)(opd_stack + opd);
X				opd_stack[opd -= npop] = result;
X			}
X
X			/* push operator (if any) onto stack */
X
X			if (token != TK_ENDINPUT)
X				opr_stack[++opr] = MAKE_OPR(prec, value);
X
X			break;
X
X		default:		/* should never get here */
X			return EXPR_ERR;
X			break;
X
X		}
X
X		prev_token = token;
X
X	} while (token != TK_ENDINPUT);
X
X	/* done - check for dangling parens, and leftover operand/operators */
X
X	return plevel != 0 || opd != 0 || opr != -1 ?
X		EXPR_ERR :		/* leftover junk - return error */
X		opd_stack[0];		/* all OK - return final value */
}
X
SHAR_EOF
chmod 0666 exprpars.c ||
echo 'restore of exprpars.c failed'
Wc_c="`wc -c < 'exprpars.c'`"
test 8311 -eq "$Wc_c" ||
	echo 'exprpars.c: original size 8311, current size' "$Wc_c"
fi
# ============= moon91 ==============
if test -f 'moon91' -a X"$1" != X"-c"; then
	echo 'x - skipping moon91 (File already exists)'
else
echo 'x - extracting moon91 (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'moon91' &&
#
# 1991 moon phase information (from Old Farmer's Almanac)
#
# This file is to be called .moon91 for Un*x, moon91.dat for VMS; it is
# to live in the same directory as the .calendar file.
#
# Dates and times below are for Boston, EST.  The date (numeric form only)
# is parsed as mm/dd or dd/mm as specified by the -A and -E flags respectively.
# The time (24-hour clock) is optional; if supplied, Pcal uses it to more 
# accurately calculate the phase of the moon at a fixed time each day.  You
# may wish to adjust these dates and times to conform to your location.
#
# If Pcal detects an error (invalid date, date or phase out of sequence,
# unrecognizable line) in this file, it generates an error message, closes
# the file, and resorts to the default moon phase calculation algorithm.
#
# Moon file syntax:
#	
#	Pcal normally calculates the approximate phase of the moon using
#	a simple algorithm which assumes (among other things) that the
#	length of the lunar month is constant and that the quarter moons
#	will occur on the same day worldwide.  For most users, that is
#	adequate; however, moon-phase freaks may enter the dates and
#	(optionally) times of quarter moons (from a reliable source such
#	as an almanac or astronomical table) into a file called .moonXX 
#	(moonXX.dat on VMS), where XX is the last two digits of the year.
#	If such a file exists (in the same directory as the date file),
#	pcal will interpolate the phase of the moon from the information
#	in this file instead of using the default algorithm.
#	
#	Entries in the moon file must conform to the following syntax:
#	
#	  if -A flag (American date formats) specified:
#	    <quarter> <month><sep><day> {<hour><sep><min>}
#	
#	  if -E flag (European date formats) specified:
#	    <quarter> <day><sep><month> {<hour><sep><min>}
#	
#	where
#	
#	  <quarter> := "nm", "fq" or "1q", "fm", "3q" or "lq" (new
#	                moon, first quarter, full moon, last quarter)
#	  <hour>    := number 0-23 (24-hour clock)
#	  <min>     := number 0-59
#	
#	This file must contain entries for all quarter moons in the year,
#	in chronological order; if any errors are encountered, pcal will
#	revert to using its default algorithm.
#	
#	As in the date file, comments start with '#' and run through
#	end-of-line.  
X
3q 01/07 13:37		# third quarter
nm 01/15 18:51		# new moon
1q 01/23 09:23		# first quarter
fm 01/30 01:10		# full moon
X
3q 02/06 08:53
nm 02/14 12:33
1q 02/21 17:59
fm 02/28 13:26
X
3q 03/08 05:33
nm 03/16 03:11
1q 03/23 01:03
fm 03/30 02:18
X
3q 04/07 01:47
nm 04/14 14:38
1q 04/21 07:40
fm 04/28 16:00
X
3q 05/06 19:48
nm 05/13 23:37
1q 05/20 14:47
fm 05/28 06:38
X
3q 06/05 10:31
nm 06/12 07:07
1q 06/18 23:20
fm 06/26 22:00
X
3q 07/04 21:51
nm 07/11 14:07
1q 07/18 10:12
fm 07/26 13:25
X
3q 08/03 06:27
nm 08/09 21:28
1q 08/17 00:02
fm 08/25 04:08
X
3q 09/01 13:17
nm 09/08 06:02
1q 09/15 17:02
fm 09/23 17:41
3q 09/30 19:31
X
nm 10/07 16:39
1q 10/15 12:34
fm 10/23 06:09
3q 10/30 02:12
X
nm 11/06 06:12
1q 11/14 09:02
fm 11/21 17:58
3q 11/28 10:22
X
nm 12/05 22:57
1q 12/14 04:33
fm 12/21 05:24
3q 12/27 20:56
SHAR_EOF
chmod 0666 moon91 ||
echo 'restore of moon91 failed'
Wc_c="`wc -c < 'moon91'`"
test 3095 -eq "$Wc_c" ||
	echo 'moon91: original size 3095, current size' "$Wc_c"
fi
# ============= moonphas.c ==============
if test -f 'moonphas.c' -a X"$1" != X"-c"; then
	echo 'x - skipping moonphas.c (File already exists)'
else
echo 'x - extracting moonphas.c (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'moonphas.c' &&
/*
X * moonphas.c - encapsulates routines used by Pcal for moon phase calculation
X *
X * Contents:
X *
X *		calc_phase
X *		find_moonfile
X *		find_phase
X *		read_moonfile
X *
X * Revision history:
X *
X *	4.0	AWR	03/07/91	Add find_moonfile()
X *
X *			01/15/91	Author: translated PostScript
X *					routines to C and added moon
X *					file routines
X *
X */
X
/*
X * Standard headers:
X */
X
#include <stdio.h>
#include <string.h>
#include <ctype.h>
X
/*
X * Pcal-specific definitions:
X */
X
#include "pcaldefs.h"
#include "pcalglob.h"
#include "pcallang.h"
X
/*
X * Macros:
X */
X
#define PERIOD		29.5306		/* average lunar month */
#define DAYS_PER_YEAR	365.2422	/* true length of year */
X
#define FM_MONTH	2		/* reference date of known full moon */
#define FM_DAY		9
#define FM_YEAR		1990
X
#define HOUR		12		/* hour of day when phase calculated */
X
/* convert "n" so that 0.0 <= n < 1.0 */
#define NORMALIZE(n)	\
X	if (1) { while (n < 0.0) n++; while (n >= 1.0) n--; } else
X
/* interpolate phase for day "d" from moon_info array elements "n1" and "n2" */
#define CALC_PHASE(d, n1, n2) \
X 	moon_info[n1].phase + ((d) - moon_info[n1].doy) * \
X	((moon_info[n2].phase - moon_info[n1].phase) / \
X	 (moon_info[n2].doy - moon_info[n1].doy))
X
/* generate error message, close file, and quit */
#define ERR_EXIT(msg)	\
X	if (1) { ERR(msg); fclose(fp); return FALSE; } else
X
/* day and phase sequence error conditions - cf. read_moonfile() */
#define DAY_TOO_SOON	(nrec > 1 && doy < prevdoy + 6)
#define DAY_TOO_LATE	(doy > prevdoy + 9)
#define WRONG_PHASE	(nrec > 1 && ph != (prevph + 1) % 4)
X
X
/*
X * Globals:
X */
X
typedef struct {
X	int	doy;	/* day of year (1..366) */
X	double	phase;	/* moon phase (cycles since new moon prior to 1/1) */
} MOON_INFO;
X
static MOON_INFO moon_info[60];		/* quarter moons for year + dummies */
X
X
/*
X * Routines to calculate moon phase when no moon file exists:
X *
X * User may substitute any phase-of-the-moon routine desired for calc_phase()
X * as long as it returns a double value in range 0.0 <= val < 1.0:
X *
X *		0.0	new moon
X *		0.25	first quarter
X *		0.5	full moon
X *		0.75	third quarter
X *
X * (N.B.: The most accurate moon phase routines compensate for variations
X * in the length of the lunar month.  In that case, is_quarter() might also
X * require some modification to prevent spurious or missing quarter-moon
X * dates when the lunar month is shorter or longer than average.)
X */
X
X
/*
X * calc_phase - return phase of moon on month/day/year (adapted from Mark
X * Hanson's PostScript version)
X */
X
#ifdef PROTOS
double calc_phase(int month,
X		  int day,
X		  int year)
#else
double calc_phase(month, day, year)
X	int month, day, year;
#endif
{
X	double daysdiff, phase;
X	long yearsdiff, cycles;
X
X	daysdiff = (DAY_OF_YEAR(month, day, year) - DAY_OF_YEAR(FM_MONTH,
X			FM_DAY, FM_YEAR)) * (DAYS_PER_YEAR / 365.0);
X
X	if ((yearsdiff = year - FM_YEAR) != 0)
X		daysdiff += (yearsdiff * DAYS_PER_YEAR) - ((yearsdiff / 100) -
X				(yearsdiff / 400));
X
X	cycles = (long) (daysdiff / PERIOD);
X	phase = (daysdiff - (cycles * PERIOD) - 0.5 * PERIOD) / PERIOD;
X	NORMALIZE(phase);	/* tweak so 0.0 <= phase < 1.0 */
X	return phase;
}
X
X
/*
X * is_quarter - is "phase" within 0.5 day of a quarter moon
X */
#ifdef PROTOS
static int is_quarter(double phase)
#else
static int is_quarter(phase)
X	double phase;
#endif
{
X
X	phase *= PERIOD;
X	return (phase >= PERIOD - 0.5 || phase < 0.5) ||
X	       (phase >= 0.25 * PERIOD - 0.5 && phase < 0.25 * PERIOD + 0.5) ||
X	       (phase >= 0.50 * PERIOD - 0.5 && phase < 0.50 * PERIOD + 0.5) ||
X	       (phase >= 0.75 * PERIOD - 0.5 && phase < 0.75 * PERIOD + 0.5);
}
X
X
/*
X * Routines to read moon file and calculate moon phase from data within
X */
X
X 
/*
X * get_phase - convert moon phase string to appropriate value
X */
#ifdef PROTOS
static int get_phase(char *cp)
#else
static int get_phase(cp)
X	char *cp;
#endif
{
X	KWD *p;
X
X	if (!cp)
X		return MOON_OTHER;
X
X	for (p = phases; p->name && ci_strcmp(cp, p->name); p++)
X		;
X
X	return p->code;
}
X
X
/*
X * make_moonpath - create the full path for the moon file in 'filename';
X * return pointer to 'filename'
X */
#ifdef PROTOS
static char *make_moonpath(char *filename, char *name, int year)
#else
static char *make_moonpath(filename, name, year)
X	char *filename;		/* full path name (output) */
X	char *name;		/* base file name */
X	int year;		/* year */
#endif
{
X	char tmp[20], path[STRSIZ], *p;
X
X	strcpy(tmp, name);
X	p = strchr(tmp, 'X');			/* replace XX with year % 100 */
X	*p++ = '0' + (year / 10) % 10;
X	*p   = '0' + year % 10;
X
X	mk_path(path, datefile);		/* get datefile path */
X	mk_filespec(filename, path, tmp);	/* append file name */
X	
X	return filename;
}
X
X
/*
X * find_moonfile - look for moon file for specified year.  If it exists
X * and is readable, return its full path name; else return NULL.  (There
X * are admittedly ways to do this without attempting to open the file,
X * but they may not be portable.)
X */
#ifdef PROTOS
char *find_moonfile(int year)
#else
char *find_moonfile(year)
X	int year;
#endif
{
X	static char filename[STRSIZ];
X	FILE *fp;
X
X	fp = fopen(make_moonpath(filename, MOONFILE, year), "r");
X
#ifdef ALT_MOONFILE
X	if (!fp) 			/* try again with alternate name */
X		fp = fopen(make_moonpath(filename, ALT_MOONFILE, year), "r");
#endif
X	return fp ? (fclose(fp), filename) : NULL;
}
X
X
/*
X * read_moonfile - looks for moon data file (in same directory as .calendar);
X * if found, reads file, fills in moon_info[] and returns TRUE; if not found
X * (or error encountered), returns FALSE
X */
#ifdef PROTOS
int read_moonfile(int year)
#else
int read_moonfile(year)
X	int year;
#endif
{
X	char *filename;
X	int line, nrec, month, day, hh, mm;
X	int ph, prevph = MOON_OTHER, doy, prevdoy, n, quarter;
X	double phase;
X	FILE *fp;
X
X	if (! *datefile)			/* skip if no datefile */
X		return FALSE;
X
X	/* get name of moon file and attempt to open it */
X
X	if ((filename = find_moonfile(year)) == NULL ||
X	    (fp = fopen(filename, "r")) == NULL)
X		return FALSE;
X
X	/*
X	 * Moon file entries are of the form <phase> <date> {<time>}; each
X	 * is converted below to a moon_info[] record (note that only the
X	 * initial phase of the moon is directly calculated from <phase>;
X	 * it is subsequently used only for error checking).  Dummy entries
X	 * in moon_info[] precede and follow the information read from the
X	 * moon file; these are used for subsequent interpolation of dates
X	 * before the first / after the last quarter of the year.
X	 */
X
X	nrec = 1;				/* skip dummy entry */
X	prevdoy = 0;
X	line = 0;
X
X	while (getline(fp, &line)) {
X
X		if ((n = loadwords()) < 2 ||	/* recognizable line? */
X		    (ph = get_phase(words[0])) == MOON_OTHER)
X			ERR_EXIT(E_INV_LINE);
X
X		if (nrec == 1)			/* phase at initial quarter */
X			quarter = ph == MOON_NM ? 4 : ph;
X
X		/* extract the month and day fields (in appropriate order) */
X
X		(void) split_date(words[1],
X				  date_style == USA_DATES ? &month : &day,
X				  date_style == USA_DATES ? &day : &month,
X				  NULL);
X
X		/* validate the date and phase */
X
X		if (!is_valid(month, day, year))	/* date OK? */
X			ERR_EXIT(E_INV_DATE);
X
X		doy = DAY_OF_YEAR(month, day, year);	/* in sequence? */
X		if (DAY_TOO_SOON || DAY_TOO_LATE || WRONG_PHASE)
X			ERR_EXIT(E_DATE_SEQ);
X
X		prevdoy = doy;			/* save for sequence check */
X		prevph = ph;
X
X		/* calculate moon phase, factoring in time (if present) */
X
X		phase = 0.25 * quarter++;
X		if (n > 2) {			/* extract hour and minute */
X			(void) split_date(words[2], &hh, &mm, NULL);
X			phase += (HOUR - (hh + (mm / 60.0))) / (24 * PERIOD); 
X		}
X		moon_info[nrec].doy = doy;	/* enter day and phase */
X		moon_info[nrec++].phase = phase;
X	}
X
X	/* check to see that file is all there */
X
X	doy = YEAR_LEN(year) + 1;	/* day after end of year */
X	if (DAY_TOO_LATE)
X		ERR_EXIT(E_PREM_EOF);
X
X	/* extrapolate dummy entries from nearest lunar month */
X
X	moon_info[nrec].doy = doy;	/* day after end of year */
X	moon_info[nrec].phase = CALC_PHASE(doy, nrec-5, nrec-1);
X
X	moon_info[0].doy = 0;		/* day before start of year */
X	moon_info[0].phase = CALC_PHASE(0, 1, 5);
X	
X	fclose(fp);
X	return TRUE;
}
X
X
/*
X * find_phase - look up phase of moon in moon phase file (if possible);
X * otherwise calculate it using calc_phase() above.  Sets *pquarter to
X * TRUE if date is a quarter moon, FALSE if not
X */
#ifdef PROTOS
double find_phase(int month,
X		  int day,
X		  int year,
X		  int *pquarter)
#else
double find_phase(month, day, year, pquarter)
X	int month, day, year;
X	int *pquarter;
#endif
{
X	static int sv_year = 0;
X	static int use_file;
X	int i, doy;
X	double phase;
X
X	if (year != sv_year) {		/* look for file for new year */
X		use_file = read_moonfile(year);
X		sv_year = year;
X	}
X
X	if (! use_file) {		/* no file - calculate date */
X		phase = calc_phase(month, day, year);
X		*pquarter = is_quarter(phase);
X		return phase;
X	}
X
X	/* moon file found - use the data extracted from it */
X
X	doy = DAY_OF_YEAR(month, day, year);
X
X	for (i = 1; doy > moon_info[i].doy; i++)	/* find interval */
X		;
X
X	/* if day appears in table, return exact value; else interpolate */
X
X	phase = (*pquarter = (doy == moon_info[i].doy)) ? moon_info[i].phase :
X			CALC_PHASE(doy, i-1, i);
X	return phase - (int)phase;			/* 0.0 <= phase < 1.0 */
}
X
SHAR_EOF
chmod 0666 moonphas.c ||
echo 'restore of moonphas.c failed'
Wc_c="`wc -c < 'moonphas.c'`"
test 9127 -eq "$Wc_c" ||
	echo 'moonphas.c: original size 9127, current size' "$Wc_c"
fi
# ============= noprotos.h ==============
if test -f 'noprotos.h' -a X"$1" != X"-c"; then
	echo 'x - skipping noprotos.h (File already exists)'
else
echo 'x - extracting noprotos.h (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'noprotos.h' &&
/*
X * noprotos.h - K&R-style function declarations for Pcal sources
X *
X * Revision history:
X *
X *	4.0	AWR	03/01/91	use <stdlib.h> where possible
X *
X *		AWR	02/19/91	adapted from protos.h (q.v.)
X *
X */
X
X
/*
X * Declarations for functions defined in exprpars.c:
X */
int parse_expr();
X
X
/*
X * Declarations for functions defined in moonphas.c:
X */
double	calc_phase();
double	find_phase();
char	*find_moonfile();
int	read_moonfile();
X
X
/*
X * Declarations for functions defined in pcal.c:
X */
FILE	*alt_fopen();
char	*color_msg();
int	get_args();
FLAG_USAGE *get_flag();
int	main();
void	set_color();
void	usage();
X
X
/*
X * Declarations for functions defined in pcalutil.c:
X */
char	*alloc();
int	calc_day();
int	calc_weekday();
int	calc_year_day();
int	ci_strcmp();
int	ci_strncmp();
void	copy_text();
int	getline();
int	is_valid();
int	loadwords();
char	*mk_filespec();
char	*mk_path();
void	normalize();
int	split_date();
char	*trnlog();
X
X
/*
X * Declarations for functions defined in readfile.c:
X */
void	cleanup();
void	clear_syms();
int	date_type();
int	do_define();
int	do_ifdef();
int	do_ifndef();
int	do_include();
int	do_undef();
int	enter_day_info();
int	find_sym();
year_info *find_year();
int	get_keywd();
int	get_month();
int	get_ordinal();
int	get_prep();
int	get_token();
int	get_weekday();
int	is_anyday();
int	is_holiday();
int	is_weekday();
int	is_workday();
int	not_holiday();
int	not_weekday();
int	not_workday();
int	parse();
int	parse_date();
int	parse_ord();
int	parse_rel();
void	read_datefile();
X
X
/*
X * Declarations for functions defined in writefil.c:
X */
void	def_footstring();
void	find_daytext();
void	find_holidays();
void	print_julian_info();
void	print_month();
void	print_moon_info();
void	print_text();
char	*print_word();
void	write_psfile();
X
X
/*
X * Prototypes for miscellaneous library routines (if not already included
X * via <stdlib.h> - cf. pcaldefs.h)
X */
#ifndef STDLIB
extern int	atoi();
extern char	*calloc();
extern char	*getenv();
#endif
SHAR_EOF
chmod 0666 noprotos.h ||
echo 'restore of noprotos.h failed'
Wc_c="`wc -c < 'noprotos.h'`"
test 1978 -eq "$Wc_c" ||
	echo 'noprotos.h: original size 1978, current size' "$Wc_c"
fi
# ============= pcal.c ==============
if test -f 'pcal.c' -a X"$1" != X"-c"; then
	echo 'x - skipping pcal.c (File already exists)'
else
echo 'x - extracting pcal.c (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'pcal.c' &&
static char  VERSION_STRING[]	= "@(#)pcal v4.0 - print Postscript calendars";
/*
X * pcal.c - generate PostScript file to print calendar for any month and year
X *
X * The original PostScript code to generate the calendars was written by
X * Patrick Wood (Copyright (c) 1987 by Patrick Wood of Pipeline Associates,
X * Inc.), and authorized for modification and redistribution.  The calendar
X * file inclusion code was originally written in "bs(1)" by Bill Vogel of
X * AT&T.  Patrick's original PostScript was modified and enhanced several
X * times by King Ables, Tom Tessin, and others whose names have regrettably 
X * been lost.  This C version was originally created by Ken Keirnan of Pacific
X * Bell; additional enhancements by Joseph P. Larson, Ed Hand, Andrew Rogers, 
X * Mark Kantrowitz, and Joe Brownlee.  The moon routines were originally 
X * written by Mark Hanson, were improved and incorporated into this version
X * by Jamie Zawinski, and were translated from PostScript to C by Andrew Rogers.
X *
X * Contents:
X *
X *		alt_fopen
X *		color_msg
X *		get_args
X *		get_flag
X *		main
X *		set_color
X *		usage
X *
X * Revision history:
X *
X *	4.0	AWR	02/24/91	Add alt_fopen() to search for file
X *					in alternate path; use to look for
X *					date file in same directory as
X *					Pcal executable (as per Floyd Miller)
X *
X *		AWR	02/19/91	Support negative ordinals (cf.
X *					readfile.c, pcalutil.c)
X *
X *		AWR	02/06/91	Support expressions in preprocessor
X *					"if{n}def" lines (cf. exprpars.c)
X *
X *		AWR	02/04/91	Support "even", "odd" ordinals (cf.
X *					readfile.c) and ordinals > 5th
X *
X *		AWR	01/28/91	Support -B (leave unused boxes blank)
X *					flag
X *
X *		AWR	01/15/91	Separated into moonphas.c, pcal.c,
X *					pcalutil.c, readfile.c, and writefil.c;
X *					added support for moon phase file
X *
X *		AWR	01/07/91	Support -w (whole year) flag; fix
X *					various bugs and nonportable constructs
X *
X *	3.0	AWR	12/10/90	Support concept of "weekday", "workday",
X *					and "holiday" (and converses)
X *
X *		AWR	11/13/90	Substantial revision of program logic:
X *					extracted pcaldefs.h and pcallang.h,
X *					moving all language dependencies (even
X *					flag names) to the latter.
X *
X *					add -I flag to reinitialize all
X *	 				flags to program defaults; -j and -J
X *					flags (print Julian dates); add -x,
X *				 	-y, -X, -Y flags (as per Ed Hand)
X *					for output scaling and translation
X *
X *					allow "wildcard" dates (e.g., "all
X *					Thursday{s} in Oct", "last Friday in
X *					all") and notes ("note all <text>);
X *					print full "help" message (including
X *					date file syntax)
X *
X *	2.6	AWR	10/15/90	parse floating dates (e.g. "first
X *					Monday in September") and relative
X *					floating dates (e.g., "Friday after
X *					fourth Thursday in November"); simplify
X *					logic of -F option; add -F to usage 
X *					message; replace COLOR_MSG() string
X *					with color_msg() routine; add -h
X *					(help message) and -A | -E (American |
X *					European date format) flags; renamed
X *					flag sets for clarity; more comments
X *
X *	2.5	JAB	10/04/90	added -F option
X *
X *	2.4	---	10/01/90	* no modifications *
X *
X *	2.3	JWZ/AWR	09/18/90	added moon routines
X *
X *	2.2	AWR	09/17/90	revise logic of parse(); new usage
X *					message
X *
X *		JAB/AWR	09/14/90	support "note" lines in date file
X *
X *	2.1	MK/AWR	08/27/90	support -L, -C, -R, -n options;
X *					print holiday text next to date
X *
X *		AWR	08/24/90	incorporate cpp-like functionality;
X *					add -D and -U options; save date file
X *					information in internal data structure;
X *					look for PCAL_OPTS and PCAL_DIR; look
X *					for ~/.calendar and ~/calendar
X *
X *	2.0	AWR	08/08/90	included revision history; replaced -r
X *					flag with -l and -p; replaced -s and -S
X *					flags with -b and -g; recognize flags
X *					set in date file; translate ( and ) in
X *					text to octal escape sequence; usage()
X *					message condensed to fit 24x80 screen
X *
X *	Parameters:
X *
X *		pcal [opts]		generate calendar for current month/year
X *					(current year if -w flag specified)
X *
X *		pcal [opts] yy		generate calendar for entire year yy
X *
X *		pcal [opts] mm yy	generate calendar for month mm
X *					(Jan = 1), year yy (19yy if yy < 100)
X *					(12 months starting with mm/yy if -w
X *					specified)
X *
X *		pcal [opts] mm yy n	as above, for n consecutive months (n
X *					rounded to next multiple of 12 if -w
X *					specified)
X *
X *	Output:
X *
X *		PostScript file to print calendars for all selected months.
X *
X *	Options:
X *
X *		-I		initialize all parameters to program defaults
X *
X *		-b <DAY>	print specified weekday in black
X *		-g <DAY>	print specified weekday in gray
X *				(default: print Saturdays and Sundays in gray)
X *		
X *		-O		print "gray" dates as outlined characters
X *
X *		-d <FONT>	specify alternate font for day names
X *				(default: Times-Bold)
X *
X *		-n <FONT>	specify alternate font for notes in boxes
X *				(default: Helvetica-Narrow)
X *
X *		-t <FONT>	specify alternate font for titles
X *				(default: Times-Bold)
X *
X *		-D <SYM>	define preprocessor symbol
X *		-U <SYM>	un-define preprocessor symbol
X *
X *		-e		generate empty calendar (ignore date file)
X *
X *		-f <FILE>	specify alternate date file (default:
X *				~/.calendar on Un*x, SYS$LOGIN:CALENDAR.DAT
X *				on VMS, s:calendar.dat on Amiga; if
X *				environment variable [logical name on VMS]
X *				PCAL_DIR exists, looks there instead; if
X *				not found in either place, looks in same
X *				directory as Pcal executable)
X *
X *		-o <FILE>	specify alternate output file (default:
X *				stdout on Un*x, CALENDAR.PS on VMS, 
X *				RAM:calendar.ps on Amiga)
X *
X *		-L <STRING>	specify left foot string   (default: "")
X *		-C <STRING>	specify center foot string (default: "")
X *		-R <STRING>	specify right foot string  (default: "")
X *
X *		-l		generate landscape-mode calendars
X *		-p		generate portrait-mode calendars
X *				(default: landscape-mode)
X *
X *		-h		(command line only) write "usage" message
X *				to stdout
X *
X *		-m		draw a small moon icon on the days of the
X *				full, new, and half moons.
X *		-M		draw a small moon icon every day.  
X *				(default: no moons)
X *
X *		-F <DAY>	select alternate day to be displayed as the 
X *				first day of the week (default: Sunday)
X *
X *		-A		dates are in American format (e.g., 10/15/90,
X *				Oct 15) (default)
X *		-E		dates are in European format (e.g., 15.10.90,
X *				15 Oct)
X *
X *		-x <XSCALE>	These two options can be used to change
X *		-y <YSCALE>	the size of the calendar.
X *
X *		-X <XTRANS>	These two options can be used to relocate
X *		-Y <YTRANS>	the position of the calendar on the page.
X *
X *		-j		print Julian dates (day of year)
X *		-J		print Julian dates and days remaining
X *				(default: neither)
X *
X *		-w		print whole year (12 months) per page
X *
X *		-B		leave unused calendar boxes blank
X *
X *
X *	There are many ways to specify these options in addition to using the
X *	command line; this facilitates customization to the user's needs.
X *
X *	If the environment variable (global symbol on VMS) PCAL_OPTS is
X *	present, its value will be parsed as if it were a command line.
X *	Any options specified will override the program defaults.
X *
X *	All but the -e, -f, -h, -D, and -U options may be specified in the
X *	date file by including one or more lines of the form "opt <options>".
X *	Any such options override any previous values set either as program
X *	defaults, via PCAL_OPTS, or in previous "opt" lines.
X *
X *	Options explicitly specified on the command line in turn override all
X *	of the above.
X *
X *	Any flag which normally takes an argument may also be specified without
X *	an argument; this resets the corresponding option to its default.  -D
X *	alone un-defines all symbols; -U alone has no effect.
X *
X *	Parameters and flags may be mixed on the command line.  In some cases
X *	(e.g., when a parameter follows a flag without its optional argument)
X *	this may lead to ambiguity; the dummy flag '-' (or '--') may be used
X *	to separate them, i.e. "pcal -t - 9 90".
X *
X *
X *	Date file syntax:
X *
X *	The following rules describe the syntax of date file entries:
X *
X *	  year <year>
X *
X *	  opt <options>
X *
X *	  note <month_spec> <text>
X *	  note <month> <text>
X *
X *	  if -A flag (American date formats) specified:
X *	    <month_name> <day>{*} {<text>}
X *	    <month><sep><day>{<sep><year>}{*} {<text>}
X *
X *	  if -E flag (European date formats) specified:
X *	    <day> <month_name>{*} {<text>}
X *	    <day><sep><month>{<sep><year>}{*} {<text>}
X *
X *	  <ordinal> <day_spec> in <month_spec>{*} {<text>}
X *	  <day_spec> <prep> <date_spec>
X *
X *	where
X *
X *	  {x}	  means x is optional
X *
X *	  <date_spec> := any of the above date specs (not year, note, or opt)
X *	  <month_name> := first 3+ characters of name of month, or "all"
X *	  <month_spec> := <month_name>, or "year"
X *	  <day_name> := first 3+ characters of name of weekday, "day",
X *			"weekday", "workday", "holiday", "nonweekday",
X *			"nonworkday", or "nonholiday"
X *	  <ordinal> := ordinal number ("1st", "2nd", etc.), "first" .. "fifth",
X *			"last", "even", "odd", or "all"
X *	  <prep> := "before", "preceding", "after", "following", "on_or_before",
X *			or "on_or_after"
X *	  <sep> := one or more non-numeric, non-space, non-'*' characters
X *	  <month>, <day>, <year> are the numeric forms
X *
X *	  <options> := any command-line option except -e, -f, -h, -D, -U
X *
X *	Comments start with '#' and run through end-of-line.
X *
X *	Holidays may be flagged by specifying '*' as the last character of
X *	the date field(s), e.g. "10/12* Columbus Day", "July 4* Independence
X *	Day", etc.  Any dates flagged as holidays will be printed in gray, and
X *	any associated text will appear adjacent to the date.
X *
X *	Note that the numeric date formats (mm/dd{/yy}, dd.mm{.yy}) support
X *	an optional year, which will become the subsequent default year.  The
X *	alphabetic date formats (month dd, dd month) do not support a year
X *	field; the "year yy" command is provided to reset the default year.
X *
X *	"Floating" days may be specified in the date file as "first Mon in 
X *	Sep", "last Mon in May", "4th Thu in Nov", etc.; any word may be
X *	used in place of "in".  "Relative floating" days (e.g. "Fri after 4th 
X *	Thu in Nov") are also accepted; they may span month/year bounds.
X *	Pcal also accepts date specs such as "all Friday{s} in October", "last
X *	Thursday in all", etc., and produces the expected results; "each" and
X *	"every" are accepted as synonyms for "all".  Negative ordinals are
X *	allowed; "-2nd" means "next to last".
X *
X *	The words "day", "weekday", "workday", and "holiday" may be used as
X *	wildcards: "day" matches any day, "weekday" matches any day normally
X *	printed in black, "workday" matches any day normally printed in black
X *	and not explicitly flagged as a holiday, and "holiday" matches any
X *	day explicitly flagged as a holiday.  "Nonweekday", "nonworkday",
X *	and "nonholiday" are also supported and have the obvious meanings.
X *	
X *	"Odd" and "even" do not refer to the actual date; instead, "odd"
X *	means "alternate, starting with the first"; "even" means "alternate,
X *	starting with the second".  Thus, "odd Fridays in March" refers to
X *	the first, third, and (if present) fifth Fridays in March - not to
X *	those Fridays falling on odd dates.
X *
X *	"All" refers to each individual month; "year" refers to the year
X *	as an entity.  Thus "odd Fridays in all" refers to the first/third/
X *	fifth Friday of each month, while "odd Fridays in year" refers to
X *	the first Friday of January and every other Friday thereafter.
X *
X *	Additional notes may be propagated to an empty calendar box by the
X *	inclusion of one or more lines of the form "note <month> <text>",
X *	where <month> may be numeric or alphabetic; "note all <text>"
X *	propagates <text> to each month in the current year.
X *
X *	Simple cpp-like functionality is provided.  The date file may include
X *	the following commands, which work like their cpp counterparts:
X *
X *		define <sym>
X *		undef <sym>
X *
X *		if{n}def <expr>
X *		   ...
X *		{ else
X *		   ... }
X *		endif
X *
X *		include <file>
X *
X *	Note that these do not start with '#', which is reserved as a comment
X *	character.
X *
X *	<sym> is a symbol name consisting of a letter followed by zero or
X *	more letters, digits, or underscores ('_').  Symbol names are always
X *	treated in a case-insensitive manner.
X *
X *	<expr> is an expression consisting of symbol names joined by the logical
X *	operators (in order of precedence, high to low) '!' (unary negate), '&'
X *	(and), '^' (exclusive or), and '|' (inclusive or).  '&&' and '||' are
X *	accepted as synonyms for '&' and '|' respectively; the order of
X *	evaluation may be altered by the use of parentheses.  A symbol whose
X *	name is currently defined evaluates to TRUE; one whose name is not
X *	currently defined evaluates to FALSE.  Thus "ifdef A | B | C" is TRUE
X *	if any of the symbols A, B, and C is currently defined, and
X *	"ifdef A & B & C" is TRUE if all of them are.
X *
X *	"ifndef A | B | C" is equivalent to "ifdef !(A | B | C)" (or, using
X *	DeMorgan's Law, "ifdef !A & !B & !C") - in other words, TRUE if none of
X *	the symbols A, B, and C is currently defined.
X *
X *	"define" alone deletes all the current definitions; "ifdef" alone is
X *	always false; "ifndef" alone is always true.
X *
X *	The file name in the "include" directive may optionally be surrounded
X *	by "" or <>.  In any case, path names are taken to be relative to
X *	the location of the file containing the "include" directive.
X *
X *	
X *	Moon file syntax:
X *	
X *	Pcal normally calculates the approximate phase of the moon using
X *	a simple algorithm which assumes (among other things) that the
X *	length of the lunar month is constant and that the quarter moons
X *	will occur on the same day worldwide.  For most users, that is
X *	adequate; however, moon-phase freaks may enter the dates and
X *	(optionally) times of quarter moons (from a reliable source such
X *	as an almanac or astronomical table) into a file called .moonXX 
X *	(moonXX.dat on VMS), where XX is the last two digits of the year.
X *	If such a file exists (in the same directory as the date file),
X *	pcal will interpolate the phase of the moon from the information
X *	in this file instead of using the default algorithm.
X *	
X *	Entries in the moon file must conform to the following syntax:
X *	
X *	  if -A flag (American date formats) specified:
X *	    <quarter> <month><sep><day> {<hour><sep><min>}
X *	
X *	  if -E flag (European date formats) specified:
X *	    <quarter> <day><sep><month> {<hour><sep><min>}
X *	
X *	where
X *	
X *	  <quarter> := "nm", "fq" or "1q", "fm", "3q" or "lq" (new
X *	  	     moon, first quarter, full moon, last quarter)
X *	  <hour>    := number 0-23 (24-hour clock)
X *	  <min>     := number 0-59
X *	
X *	This file must contain entries for all quarter moons in the year,
X *	in chronological order; if any errors are encountered, pcal will
X *	revert to using its default algorithm.
X *	
X *	As in the date file, comments start with '#' and run through
X *	end-of-line.  
X */
X
X
/*
X * Standard headers:
X */
X
#include <stdio.h>
#include <ctype.h>
#include <time.h>
#include <string.h>
X
/*
X * Pcal-specific definitions:
X */
X
#define MAIN_MODULE	1
#include "pcaldefs.h"
#include "pcalglob.h"
#include "pcallang.h"
X
/*
X * Globals:
X */
X
static int init_month, init_year, nmonths;
X
X
/*
X * Main program - parse and validate command-line arguments, open files,
X * generate PostScript boilerplate and code to generate calendars.
X *
X * Program structure:
X *
X * main() looks for the environment variable (global symbol on VMS) PCAL_OPTS
X * and, if present, calls get_args() to parse it.  It then calls get_args()
X * again to parse the command line for the date file name, any options to be
X * in effect prior to reading the date file, and any numeric arguments (month,
X * year, number of months).  It then calls read_datefile() to read and parse
X * the date file; any "opt" lines present will override the defaults for the
X * command-line flags.  It then calls get_args() yet again to process the
X * remaining command-line flags, which in turn override any specified earlier.
X *
X * main() then attempts to open the output file (if any), and, if successful,
X * calls write_psfile() to generate the PostScript output.
X *
X */
#ifdef PROTOS
int main(int argc,
X	 char **argv)
#else
int main(argc, argv)
X	int argc;
X	char **argv;
#endif
{
X	FILE *dfp = NULL;		/* date file pointer */
X	char *p, *pathlist[10];
X	char tmp[STRSIZ], progpath[STRSIZ];
X
X	INIT_COLORS;		/* copy default_color to day_color */
X
X	/* extract root program name and program path */
X
X	strcpy(progname, **argv ? *argv : "pcal");
X
X	if ((p = strrchr(progname, END_PATH)) != NULL)
X		strcpy(progname, ++p);
X	if ((p = strchr(progname, '.')) != NULL)
X		*p = '\0';
X
X	mk_path(progpath, *argv);
X
X	/* get version from VERSION_STRING (for use in PostScript comment) */
X	strcpy(tmp, VERSION_STRING + 4);
X	p = strchr(tmp, ' ') + 1;	/* skip program name */
X	*strchr(p, ' ') = '\0';		/* terminate after version */
X	strcpy(version, p);
X
X	/*
X	 * Get the arguments from a) the environment variable, b) "opt" lines
X	 * in the date file, and c) the command line, in that order
X	 */
X
X	/* parse environment variable PCAL_OPTS as a command line */
X
X	if ((p = getenv(PCAL_OPTS)) != NULL) {
X		strcpy(lbuf, "pcal ");		/* dummy program name */
X		strcat(lbuf, p);
X		(void) loadwords();		/* split string into words */
X		if (! get_args(words, P_ENV, PCAL_OPTS)) {
X			usage(stderr, FALSE);
X			exit(EXIT_FAILURE);
X		}
X	}
X
X	/* parse command-line arguments once to find name of date file, etc. */
X
X	if (!get_args(argv, P_CMD1, NULL)) {
X		usage(stderr, FALSE);
X		exit(EXIT_FAILURE);
X	}
X
X	/* Attempt to open the date file as specified by the [-e | -f] flags */
X
X	switch (datefile_type) {
X	case NO_DATEFILE:
X		dfp = NULL;
X		break;
X
X	case USER_DATEFILE:	
X		/* Attempt to open user-specified calendar file: search
X		 * first in PCAL_DIR (current directory if not defined)
X		 * and then in the directory where the Pcal executable
X		 * lives.  It is a fatal error if the user-specified
X		 * date file cannot be found.
X		 */
X		pathlist[0] = (p = trnlog(PCAL_DIR)) ? p : "";
X		pathlist[1] = progpath;
X		pathlist[2] = NULL;
X		
X		strcpy(tmp, datefile);	/* save original name for error msg */
X
X		if ((dfp = alt_fopen(datefile, tmp, pathlist, "r")) == NULL) {
X			FPR(stderr, E_FOPEN_ERR, progname, tmp);
X			exit(EXIT_FAILURE);
X		}
X		break;
X
X	case SYS_DATEFILE:
X		/* Attempt to open system-specified calendar file: search
X		 * first in PCAL_DIR or HOME_DIR (current directory if
X		 * neither is defined) and then in the directory where
X		 * the Pcal executable lives.  It is not an error if the
X		 * system-specified date file cannot be found; Pcal will
X		 * simply generate an empty calendar.
X		 */
X		pathlist[0] = ((p = trnlog(PCAL_DIR)) ||
X			       (p = trnlog(HOME_DIR))) ? p : "";
X		pathlist[1] = progpath;
X		pathlist[2] = NULL;
X		
X		dfp = alt_fopen(datefile, DATEFILE, pathlist, "r");
X
X		/* if the date file has not been found and ALT_DATEFILE is
X		 * defined, search same paths for ALT_DATEFILE before
X		 * giving up
X		 */
#ifdef ALT_DATEFILE
X		if (!dfp)
X			dfp = alt_fopen(datefile, ALT_DATEFILE, pathlist, "r");
#endif
X		break;
X	}
X
X	/* read the date file (if any) and build internal data structure */
X
X	if (dfp) {
X		curr_year = init_year;
X		read_datefile(dfp, datefile);
X		fclose(dfp);
X	} else
X		datefile[0] = '\0';		/* for PostScript comment */
X
X	/* reparse command line - flags there supersede those in date file */
X
X	(void) get_args(argv, P_CMD2, NULL);
X
X	/* done with the arguments and flags - try to open the output file */
X
X	if (*outfile && freopen(outfile, "w", stdout) == (FILE *) NULL) {
X		FPR(stderr, E_FOPEN_ERR, progname, outfile);
X		exit(EXIT_FAILURE);
X	}
X
X	/* generate the PostScript code (cf. writefil.c) */
X
X	write_psfile(init_month, init_year, nmonths);
X	
X	cleanup();		/* free allocated data */
X
X	/* if output was written to a non-obvious location, tell user where */
X
#ifdef DEFAULT_OUTFILE
X	FPR(stderr, I_OUT_NAME, progname, outfile);
#endif
X
X	exit(EXIT_SUCCESS);
}
X
X
/*
X * set_color - set one or all weekdays to print in black or gray
X */
#ifdef PROTOS
void set_color(char *day,
X	       int col)
#else
void set_color(day, col)
X	char *day;		/* weekday name (or "all") */
X	int  col;		/* select black or gray */
#endif
{
X	int i;
X
X	if (ci_strncmp(day, ALL, strlen(ALL)) == 0)	/* set all days */
X		for (i = 0; i < 7; i++)
X			day_color[i] = col;
X	else						/* set single day */
X		if ((i = get_weekday(day, FALSE)) != NOT_WEEKDAY)
X			day_color[i] = col;
X
}
X
X
/*
X * get_flag() - look up flag in flag_tbl; return pointer to its entry
X * (NULL if not found)
X */
#ifdef PROTOS
FLAG_USAGE *get_flag(char flag)
#else
FLAG_USAGE *get_flag(flag)
X	char flag;
#endif
{
X	FLAG_USAGE *pflag;
X
X	for (pflag = flag_tbl; pflag->flag; pflag++)
X		if (flag == pflag->flag)
X			return pflag;
X
X	return flag ? NULL : pflag;		/* '\0' is a valid flag */
}
X
X
/*
X * get_args - walk the argument list, parsing all arguments but processing only
X * those specified (in flag_tbl[]) to be processed this pass.
X */
#ifdef PROTOS
int get_args(char **argv,
X	     int curr_pass,
X	     char *where)
#else
int get_args(argv, curr_pass, where)
X	char **argv;		/* argument list */
X	int  curr_pass;		/* current pass (P_xxx) */
X	char *where;		/* for error messages */
#endif
{
X	char *parg, *opt, *p;
X	FLAG_USAGE *pflag, *pf;
X	int i, flag;
X	long tmp;			/* for getting current month/year */
X	struct tm *p_tm;
X	int badopt = FALSE;		/* flag set if bad param   */
X	int nargs = 0;			/* count of non-flag args  */
X	int numargs[MAXARGS];		/* non-flag (numeric) args */
X
/*
X * If argument follows flag (immediately or as next parameter), return
X * pointer to it (and bump argv if necessary); else return NULL
X */
#define GETARG() (*(*argv + 2) ? *argv + 2 : \
X		  (*(argv+1) && **(argv+1) != '-' ? *++argv : NULL))
X
/*
X * Must parse numeric parameters on both command-line passes: before reading
X * datefile (in order to set default year) and again after reading datefile
X * (in order to set the default first month to January if -w flag was set
X * inside the datefile)
X */
#define PARSE_NUM_PARAMS	(curr_pass == P_CMD1 || curr_pass == P_CMD2)
X
X	/* Walk argument list, ignoring first element (program name) */
X
X 	while (opt = *++argv) {
X
X		/* Assume that any non-flag argument is a numeric argument */
X		if (*opt != '-') {
X		    	if (PARSE_NUM_PARAMS && nargs < MAXARGS) {
X				if (! IS_NUMERIC(opt))
X					goto bad_par;
X				numargs[nargs++] = atoi(opt);
X			}
X			continue;
X		}
X
X		/* Check that flag is a) legal, and b) to be processed this pass */
X
X		if (! (pflag = get_flag(flag = opt[1])) )
X			goto bad_par;
X
X		/* get optional argument even if flag not processed this pass */
X
X		parg = pflag->has_arg ? GETARG() : NULL;
X
X		if (! (pflag->passes & curr_pass)) {	/* skip flag this pass? */
X			if (curr_pass == P_OPT)
X				FPR(stderr, E_FLAG_IGNORED, progname, flag,
X					DATE_FILE, where);
X			continue;
X		}
X
X		switch (flag) {
X
X		case F_INITIALIZE:	/* reset all flags to defaults */
X
X			/* set up a command line to reset all of the
X			 * flags; call get_args() recursively to parse it
X			 * (note that some of the flags must be reset
X			 * explicitly, as no command-line flags exist to
X			 * reset them)
X			 */
X
X			/* reset flags described above */
X			julian_dates  = JULIAN_DATES;
X			draw_moons    = DRAW_MOONS;
X			do_whole_year = FALSE;
X			blank_boxes   = FALSE;
X			outline_nums  = FALSE;
X
X			/* select program default for landscape/portrait
X			 * mode and US/European date styles
X			 */
X			sprintf(lbuf, "pcal -%c -%c",
#if (ROTATE == LANDSCAPE)
X				F_LANDSCAPE,
#else
X				F_PORTRAIT,
#endif
#if (DATE_STYLE == USA_DATES)
X				F_USA_DATES);
#else
X				F_EUR_DATES);
#endif
X			p = lbuf + strlen(lbuf);
X
X			/* all other flags take arguments and are reset
X			 * by specifying the flag without an argument
X			 */
X			for (pf = flag_tbl; pf->flag; pf++)
X				if ((pf->passes & curr_pass) && pf->has_arg) {
X					sprintf(p, " -%c", pf->flag);
X					p += strlen(p);
X				}
X
X			/* split new command line into words; parse it */
X			(void) loadwords();
X			(void) get_args(words, curr_pass, NULL);
X
X			break;
X
X		case F_BLACK_DAY:	/* print day in black or gray */
X		case F_GRAY_DAY:
X			if (parg)
X				set_color(parg, flag == F_BLACK_DAY ?
X						  BLACK : GRAY);
X			else
X				INIT_COLORS;	/* reset to defaults */
X			break;
X
X 		case F_DAY_FONT:	/* specify alternate day font */
X			strcpy(dayfont, parg ? parg : DAYFONT);
X 			break;
X
X		case F_NOTES_FONT:	/* specify alternate notes font */
X			strcpy(notesfont, parg ? parg : NOTESFONT);
X			break;
X
X 		case F_TITLE_FONT:	/* specify alternate title font */
X			strcpy(titlefont, parg ? parg : TITLEFONT);
X 			break;
X
X		case F_EMPTY_CAL:	/* generate empty calendar */
X			datefile_type = NO_DATEFILE;
X			strcpy(datefile, "");
X			break;
X
X		case F_DATE_FILE:	/* specify alternate date file */
X			datefile_type = parg ? USER_DATEFILE : SYS_DATEFILE;
X			strcpy(datefile, parg ? parg : "");
X			break;
X
X		case F_OUT_FILE:	/* specify alternate output file */
X			strcpy(outfile, parg ? parg : OUTFILE);
X			break;
X
X		case F_LANDSCAPE:	/* generate landscape calendar */
X 			rotate = LANDSCAPE;
X			strcpy(xsval, XSVAL_L);
X			strcpy(ysval, YSVAL_L);
X			strcpy(xtval, XTVAL_L);
X			strcpy(ytval, YTVAL_L);
X 			break;
X 
X		case F_PORTRAIT:	/* generate portrait calendar */
X 			rotate = PORTRAIT;
X			strcpy(xsval, XSVAL_P);
X			strcpy(ysval, YSVAL_P);
X			strcpy(xtval, XTVAL_P);
X			strcpy(ytval, YTVAL_P);
X 			break;
X
X		case F_HELP:		/* request "help" message */
X			FPR(stdout, "%s\n", VERSION_STRING + 4);
X			usage(stdout, TRUE);
X			exit(EXIT_SUCCESS);
X			break;
X
X		case F_MOON_4:		/* draw four moons */
X		case F_MOON_ALL:	/* draw a moon for each day */
X			draw_moons = flag == F_MOON_ALL ? ALL_MOONS : SOME_MOONS;
X			break;
X
X		case F_DEFINE:		/* define preprocessor symbol */
X			(void) do_define(parg);
X			break;
X
X		case F_UNDEF:		/* undef preprocessor symbol */
X			(void) do_undef(parg);
X			break;
X
X		case F_L_FOOT:		/* specify alternate left foot */
X			strcpy(lfoot, parg ? parg : LFOOT);
X			break;
X
X		case F_C_FOOT:		/* specify alternate center foot */
X			strcpy(cfoot, parg ? parg : CFOOT);
X			break;
X
X 		case F_R_FOOT:		/* specify alternate right foot */
X			strcpy(rfoot, parg ? parg : RFOOT);
X			break;
X
X		case F_FIRST_DAY:	/* select starting day of week */
X			if (parg) {
X				if ((i = get_weekday(parg, FALSE)) != NOT_WEEKDAY)
X					first_day_of_week = i;
X			}
X			else
X				first_day_of_week = FIRST_DAY;
X			break;
X
X		case F_USA_DATES:	/* select American date style */
X		case F_EUR_DATES:	/* select European date style */
X			date_style = flag == F_USA_DATES ? USA_DATES : EUR_DATES;
X			break;
X
X		case F_X_TRANS:		/* set x-axis translation factor */
X			strcpy(xtval, parg ? parg :
X				(rotate == LANDSCAPE ? XTVAL_L : XTVAL_P));
X			break;
X
X		case F_Y_TRANS:		/* set y-axis translation factor */
X			strcpy(ytval, parg ? parg :
X				(rotate == LANDSCAPE ? YTVAL_L : YTVAL_P));
X			break;
X
X		case F_X_SCALE:		/* set x-axis scaling factor */
X			strcpy(xsval, parg ? parg :
X				(rotate == LANDSCAPE ? XSVAL_L : XSVAL_P));
X			break;
X
X		case F_Y_SCALE:		/* set y-axis scaling factor */
X			strcpy(ysval, parg ? parg :
X				(rotate == LANDSCAPE ? YSVAL_L : YSVAL_P));
X			break;
X
X		case F_JULIAN:
X		case F_JULIAN_ALL:
X			julian_dates = flag == F_JULIAN_ALL ? ALL_JULIANS :
X							      SOME_JULIANS;
X			break;
X
X		case F_WHOLE_YEAR:
X			do_whole_year = TRUE;
X			break;
X
X		case F_BLANK_BOXES:
X			blank_boxes = TRUE;
X			break;
X
X		case F_OUTLINE:
X			outline_nums = TRUE;
X			break;
X
X		case '-' :		/* accept - and -- as dummy flags */
X		case '\0':
X			break;
X
X		default:		/* missing case label if reached!!! */
X
bad_par:				/* unrecognized parameter */
X
X			FPR(stderr, E_ILL_OPT, progname, opt);
X			if (where)
X				FPR(stderr, E_ILL_OPT2,
X					curr_pass == P_ENV ? ENV_VAR :
X					curr_pass == P_OPT ? DATE_FILE : "",
X					where);
X			FPR(stderr, "\n");
X			badopt = TRUE;
X			break;
X		}
X        }
X
X	if (! PARSE_NUM_PARAMS)
X		return !badopt;		/* return TRUE if OK, FALSE if error */
X
X	/* Validate non-flag (numeric) parameters */
X
X	switch (nargs) {
X	case 0:		/* no arguments - print current month and/or year */
X		time(&tmp);
X		p_tm = localtime(&tmp);
X		init_month = do_whole_year ? JAN : p_tm->tm_mon + 1;
X		init_year = p_tm->tm_year;
X		nmonths = 1;
X		break;			
X	case 1:		/* one argument - print entire year */
X		init_month = JAN;
X		init_year = numargs[0];
X		nmonths = 12;
X		break;
X	default:	/* two or three arguments - print one or more months */
X		init_month = numargs[0];
X		init_year = numargs[1];
X		nmonths = nargs > 2 ? numargs[2] : 1;
X		break;
X	}
X
X	if (nmonths < 1)		/* ensure at least one month */
X		nmonths = 1;
X
X	/* check range of month and year */
X
X	if (init_month < JAN || init_month > DEC) {
X		FPR(stderr, E_ILL_MONTH, progname, init_month, JAN, DEC);
X		badopt = TRUE;
X	}
X	
X	if (init_year > 0 && init_year < 100)	/* treat nn as 19nn */
X		init_year += CENTURY;
X	
X	if (init_year < MIN_YR || init_year > MAX_YR) {
X		FPR(stderr, E_ILL_YEAR, progname, init_year, MIN_YR, MAX_YR);
X		badopt = TRUE;
X	}
X
X	return !badopt;		/* return TRUE if OK, FALSE if error */
}
X
X
X
/*
X * color_msg - return character string explaining default day colors
X */
#ifdef PROTOS
char *color_msg(void)
#else
char *color_msg()
#endif
{
X	int i, ngray = 0, others;
X	static char msg[80];
X
X	for (i = SUN; i <= SAT; i++)	/* count gray weekdays */
X		if (default_color[i] == GRAY)
X			ngray++;
X
X	if (ngray == 0 || ngray == 7) {		/* all same color? */
X		sprintf(msg, COLOR_MSG_1, ngray ? W_GRAY : W_BLACK);
X		return msg;
X	}
X
X	others = ngray <= 3 ? BLACK : GRAY;	/* no - get predominant color */
X	msg[0] = '\0';
X	for (i = SUN; i <= SAT; i++)
X		if (default_color[i] != others) {
X			strncat(msg, days[i], MIN_DAY_LEN);
X			strcat(msg, "/");
X		}
X	LASTCHAR(msg) = ' ';
X
X	sprintf(msg + strlen(msg), COLOR_MSG_2,
X		others == BLACK ? W_GRAY : W_BLACK,
X                others == BLACK ? W_BLACK : W_GRAY);
X	return msg;
}
X
X
/*
X * usage - print message explaining correct usage of the command-line
X * arguments and flags.  If "fullmsg" is true, print associated text
X */
#ifdef PROTOS
void usage(FILE *fp,
X	   int fullmsg)
#else
void usage(fp, fullmsg)
X	FILE *fp;	/* destination of usage message */
X	int fullmsg;	/* print complete message? */
#endif
{
X	FLAG_MSG *pflag;
X	PARAM_MSG *ppar;
X	DATE_MSG *pdate;
X	char buf[30], *p, flag, *meta;
X	int nchars, first, i, indent, n;
X
X	sprintf(buf, "%s: %s", W_USAGE, progname);
X	nchars = indent = strlen(buf);
X	first = TRUE;
X	meta = p = NULL;
X	FPR(fp, "\n%s", buf);
X
X	/* loop to print command-line syntax message (by group of flags) */
X
X	for (pflag = flag_msg; (flag = pflag->flag) != '\0'; pflag++) {
X		if (flag == '\n') {		/* end of group? */
X			if (p)
X				*p = '\0';
X			if (meta) {		/* append metavariable name */
X				strcat(buf, " ");
X				strcat(buf, meta);
X			}
X			strcat(buf, "]");
X			n = strlen(buf);
X			if (nchars + n > SCREENWIDTH) {	/* does it fit on line? */
X				FPR(fp, "\n");		/* no - start new one */
X				for (nchars = 0; nchars < indent; nchars++)
X					FPR(fp, " ");
X			}
X			FPR(fp, "%s", buf);
X			nchars += n;
X			first = TRUE;
X		}
X		else if (flag != ' ') {		/* accumulate flags for group */
X			if (first) {
X				sprintf(buf, " [");
X				p = buf + strlen(buf);
X			}
X			else
X				*p++ = '|';
X			*p++ = '-';
X			*p++ = flag;
X			meta = pflag->meta;	/* save metavariable name */
X			first = FALSE;
X		}
X	}
X
X	/* loop to print selected numeric parameter descriptions */
X
X	for (i = 0; i < PARAM_MSGS; i++) {
X		sprintf(buf, " [%s]%s", param_msg[i].desc,
X			i < PARAM_MSGS - 1 ? " |" : "");
X		n = strlen(buf);
X		if (nchars + n > SCREENWIDTH) {	/* does it fit on line? */
X			FPR(fp, "\n");		/* no - start new one */
X			for (nchars = 0; nchars < indent; nchars++)
X				FPR(fp, " ");
X		}
X		FPR(fp, "%s", buf);
X		nchars += n;
X	}
X
X	FPR(fp, "\n\n");
X	if (! fullmsg) {
X		FPR(fp, USAGE_MSG, progname, F_HELP);
X		return;
X	}
X	
X	/* loop to print the full flag descriptions */
X
X	for (pflag = flag_msg; (flag = pflag->flag) != '\0'; pflag++) {
X		if (flag == '\n') {	/* newline?  print and quit */
X			FPR(fp, "\n");
X			continue;
X		}
X		p = buf;		/* copy flag and metavariable to buffer */
X		if (flag != ' ')
X			*p++ = '-';
X	/* special hack for VMS - surround upper-case flags in quotes */
#ifdef VMS
X		if (isupper(flag)) {
X			*p++ = '"';
X			*p++ = flag;
X			*p++ = '"';
X		}
X		else
X			*p++ = flag;
#else
X		*p++ = flag;
#endif
X		*p = '\0';
X		if (pflag->meta)
X			sprintf(p, " <%s>", pflag->meta);
X		FPR(fp, "\t%-16.16s", buf);
X		if (pflag->text)
X			FPR(fp, "%s", pflag->text);
X
X		/* print default value if specified */
X		if (pflag->def)
X			FPR(fp, " (%s: %s)", W_DEFAULT, pflag->def[0] ? pflag->def : "\"\"" );
X		FPR(fp, "\n");
X
X		/* special case - print color messages after F_GRAY_DAY */
X		if (flag == F_GRAY_DAY)
X			FPR(fp, "\t\t\t  (%s: %s)\n", W_DEFAULT, color_msg());
X
X	}
X	
X	/* now print the information about the numeric parameters */
X
X	for (ppar = param_msg; ppar->desc; ppar++)
X		FPR(fp, "\t%-16.16s%s\n", ppar->desc, ppar->text);
X	
X	/* print the date file syntax message */
X
X	FPR(fp, "\n");
X	for (pdate = date_msg; *pdate; pdate++)
X		FPR(fp, "\t%s\n", *pdate);
X
}
X
X
/*
X * alt_fopen - attempt to open a file in one of several paths in a
X * NULL-terminated path list.  If successful, return (opened) file pointer
X * and fill in full path name; if not, return NULL
X */
#ifdef PROTOS
FILE *alt_fopen(char *fullpath, char *name, char *pathlist[], char *access) 
#else
FILE *alt_fopen(fullpath, name, pathlist, access)
X	char *fullpath;		/* full path name (output) */
X	char *name;		/* base name (or full path spec) */
X	char *pathlist[];	/* NULL-terminated path list */
X	char *access;		/* permission requested */
#endif
{
X	char **path;
X	FILE *fp;
X
X	for (path = pathlist; *path; path++) {
X		mk_filespec(fullpath, *path, name);
X		if ((fp = fopen(fullpath, access)) != NULL)
X			return fp;
X		}
X
X	fullpath[0] = '\0';		/* file not found */
X	return NULL;
}
X
SHAR_EOF
chmod 0666 pcal.c ||
echo 'restore of pcal.c failed'
Wc_c="`wc -c < 'pcal.c'`"
test 33851 -eq "$Wc_c" ||
	echo 'pcal.c: original size 33851, current size' "$Wc_c"
fi
true || echo 'restore of pcal.man failed'
echo End of part 2, continue with part 3
exit 0