/* ----------------------------------------------------------------------
* FILE: boss_parse.y
* PACKAGE: boss -
* DESCRIPTION:
* Parser for the Casio B.O.S.S data exchange format.
*
* REVISION HISTORY:
*
* AUTHOR:
* Ken Stauffer. December 1995
*/
%{
#include <setjmp.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include "boss.h"
#include "bossP.h"
/* #define YYSTYPE union ystack REMOVE THIS LINE */
/* proto-types */
static BOSS_TIME mktime(int, int, int);
static BOSS_TIME no_time(void);
static BOSS_DATE mkdate(int, int, int, int);
static DATETIME mk_datetime(BOSS_DATE *, BOSS_TIME *);
static BOSS_PHONE *phone_join(BOSS_PHONE *, BOSS_PHONE *);
static BOSS_MEMO *memo_join(BOSS_MEMO *, BOSS_MEMO *);
static BOSS_SCHEDULE *schedule_join(BOSS_SCHEDULE *, BOSS_SCHEDULE *);
static BOSS_REMINDER *reminder_join(BOSS_REMINDER *, BOSS_REMINDER *);
static BOSS_CALENDAR *calendar_join(BOSS_CALENDAR *, BOSS_CALENDAR *);
static BOSS_TODO *todo_join(BOSS_TODO *, BOSS_TODO *);
static BOSS_EXPENSE *expense_join(BOSS_EXPENSE *, BOSS_EXPENSE *);
static BOSS_TODO *mktodo(int checkmark, DATETIME *, char *);
static BOSS_PHONE *mkphone(STRLST *);
static BOSS_REMINDER *mkreminder(BOSS_DATE *, BOSS_TIME *, char *);
static BOSS_MEMO *mkmemo(char *, char *);
static BOSS_SCHEDULE *mkschedule(BOSS_DATE *, RANGE *, BOSS_TIME *, char *);
static BOSS_CALENDAR *mkcalendar(int, int, NUMLST *);
static BOSS_EXPENSE *mkexpense(double, BOSS_DATE *, char *, char *, char *);
static STRLST *mkstrlst(STRLST *, char *);
static NUMLST *mknumlst(NUMLST *, int);
static RANGE mkrange(BOSS_TIME *, BOSS_TIME *); /* ADD THIS LINE */
%}
%token <value> NUMBER
%token <fvalue> FLOAT
%token <str> STRING
%token JAN FEB MAR APR MAY JUN JUL AUG SEP OCT NOV DEC
%token AM PM
%token ENDREC
%token SECTION
%token CHECKMARK
%token TODO
%token PHONE
%token REMINDER
%token MEMO
%token SCHEDULE
%token CALENDAR
%token EXPENSE
%token SATSUN MON TUE WED THUR FRI
%type <phone> phone_rec phone_list phone_section
%type <memo> memo_rec memo_list memo_section
%type <schedule> schedule_rec schedule_list schedule_section
%type <reminder> reminder_rec reminder_list reminder_section
%type <calendar> calendar_rec calendar_list calendar_section
%type <todo> todo_rec todo_list todo_section
%type <expense> expense_rec expense_list expense_section
%type <dt> opt_date_time
%type <date> date_spec reminder_spec
%type <time> time_spec opt_time
%type <range> opt_range
%type <value> month check_mark ampm calnum
%type <str> opt_string
%type <strlst> string_list
%type <numlst> cal_num_list
%start datafile
/* ------------------------ G R A M M A R ----------------------------- */
%%
datafile : section_list
;
section_list : section
| section_list section
;
section : phone_section { phone_add($1); }
| memo_section { memo_add($1); }
| schedule_section { schedule_add($1); }
| reminder_section { reminder_add($1); }
| calendar_section { calendar_add($1); }
| todo_section { todo_add($1); }
| expense_section { expense_add($1); }
;
/*----------------------------------------------------------------------*/
todo_section : SECTION TODO SECTION todo_list { $$ = $4; }
| SECTION TODO SECTION { $$ = NULL; }
;
phone_section : SECTION PHONE SECTION phone_list { $$ = $4; }
| SECTION PHONE SECTION { $$ = NULL; }
;
reminder_section: SECTION REMINDER SECTION reminder_list { $$ = $4; }
| SECTION REMINDER SECTION { $$ = NULL; }
;
memo_section : SECTION MEMO SECTION memo_list { $$ = $4; }
| SECTION MEMO SECTION { $$ = NULL; }
;
schedule_section: SECTION SCHEDULE SECTION schedule_list { $$ = $4; }
| SECTION SCHEDULE SECTION { $$ = NULL; }
;
calendar_section: SECTION CALENDAR SECTION calendar_list { $$ = $4; }
| SECTION CALENDAR SECTION { $$ = NULL; }
;
expense_section : SECTION EXPENSE SECTION expense_list { $$ = $4; }
| SECTION EXPENSE SECTION { $$ = NULL; }
;
/*----------------------------------------------------------------------*/
todo_list : todo_rec ENDREC { $$ = $1; }
| todo_list todo_rec ENDREC { $$ = todo_join($1,$2); }
;
phone_list : phone_rec ENDREC { $$ = $1; }
| phone_list phone_rec ENDREC { $$ = phone_join($1,$2); }
;
reminder_list : reminder_rec ENDREC { $$ = $1; }
| reminder_list reminder_rec ENDREC { $$ = reminder_join($1,$2); }
;
memo_list : memo_rec ENDREC { $$ = $1; }
| memo_list memo_rec ENDREC { $$ = memo_join($1,$2); }
;
schedule_list : schedule_rec ENDREC { $$ = $1; }
| schedule_list schedule_rec ENDREC { $$ = schedule_join($1,$2); }
;
calendar_list : calendar_rec ENDREC { $$ = $1; }
| calendar_list calendar_rec ENDREC { $$ = calendar_join($1,$2); }
;
expense_list : expense_rec ENDREC { $$ = $1; }
| expense_list expense_rec ENDREC { $$ = expense_join($1,$2); }
;
/*----------------------------------------------------------------------*/
todo_rec : check_mark opt_date_time STRING { $$ = mktodo($1,&$2,$3); }
;
phone_rec : string_list { $$ = mkphone($1); }
;
reminder_rec : reminder_spec opt_time STRING { $$ = mkreminder(&$1,&$2,$3); }
;
memo_rec : STRING opt_string { $$ = mkmemo($1, $2); }
;
schedule_rec : date_spec opt_range STRING opt_time
{ $$ = mkschedule(&$1,&$2,&$4,$3); }
;
calendar_rec : month NUMBER
SATSUN MON TUE WED THUR FRI SATSUN
cal_num_list
{ $$ = mkcalendar($1,$2,$10); }
expense_rec : FLOAT date_spec STRING STRING opt_string
{ $$ = mkexpense($1,&$2,$3,$4,$5); }
| NUMBER date_spec STRING STRING opt_string
{ $$ = mkexpense($1,&$2,$3,$4,$5); }
;
/*----------------------------------------------------------------------*/
string_list : STRING { $$ = mkstrlst(NULL,$1); }
| string_list STRING { $$ = mkstrlst($1,$2); }
;
opt_string : STRING { $$ = $1; }
| /* empty */ { $$ = strdup(""); }
;
opt_time : time_spec { $$ = $1; }
| /* empty */ { $$ = no_time(); }
;
opt_range : time_spec { $$ = mkrange(&$1, NULL); }
| time_spec '-' time_spec { $$ = mkrange(&$1, &$3); }
| /* empty */ { $$ = mkrange(NULL, NULL); }
;
opt_date_time : date_spec time_spec { $$ = mk_datetime(&$1,&$2); }
| date_spec { $$ = mk_datetime(&$1,NULL); }
| /* empty */ { $$ = mk_datetime(NULL,NULL); }
;
reminder_spec : '[' '-' '/' '-' ']' { $$ = mkdate(1,0,0,0); }
| '[' '-' '/' NUMBER ']' { $$ = mkdate(1,0,0,$4); }
| '[' NUMBER '/' NUMBER ']' { $$ = mkdate(1,0,$2,$4); }
;
time_spec : '[' NUMBER ':' NUMBER ampm ']' { $$ = mktime($2,$4,$5); }
;
ampm : AM { $$ = T_AM; }
| PM { $$ = T_PM; }
| /* empty */ { $$ = T_NOTSPECIFIED; }
;
cal_num_list : calnum { $$ = mknumlst(NULL,$1); }
| cal_num_list calnum { $$ = mknumlst($1,$2); }
;
calnum : NUMBER { $$ = $1; }
| SECTION { $$ = -1; }
;
/* -------------------------------------------------
* Date formats:
* year / month / day 95/11/4
* month / day / year Nov/4/95
* day / month / year 4/NOV/95
* day-month-year 4-november-1995
*/
date_spec : '[' NUMBER '/' NUMBER '/' NUMBER ']' { $$ = mkdate(0,$2,$4,$6); }
| '[' month '/' NUMBER '/' NUMBER ']' { $$ = mkdate(0,$6,$2,$4); }
| '[' NUMBER '/' month '/' NUMBER ']' { $$ = mkdate(0,$6,$4,$2); }
| '[' NUMBER '-' month '-' NUMBER ']' { $$ = mkdate(0,$6,$4,$2); }
;
check_mark : '[' CHECKMARK ']' { $$ = 1; }
| '[' ']' { $$ = 0; }
;
month : JAN { $$ = 1; }
| FEB { $$ = 2; }
| MAR { $$ = 3; }
| APR { $$ = 4; }
| MAY { $$ = 5; }
| JUN { $$ = 6; }
| JUL { $$ = 7; }
| AUG { $$ = 8; }
| SEP { $$ = 9; }
| OCT { $$ = 10; }
| NOV { $$ = 11; }
| DEC { $$ = 12; }
;
%%
/* ---------------------------------------------------------------------- */
void bosserror(char *s)
{
boss_error(s);
}
/* ----------------------------------------------------------------------
* Uses variable arguments. This causes immediate termination
* of the parse.
*/
void boss_error(char *cs, ...)
{
va_list ap;
char buf[ 2000 ];
va_start(ap, cs);
sprintf(buf,"File: %s, line: %d, ", Boss.datafile, Boss.lineno);
strcpy(Boss.error, buf);
vsprintf(buf, cs, ap);
strcat(Boss.error, buf);
strcat(Boss.error, ".");
va_end(ap);
longjmp(Boss.main_env, 1);
}
/* Build a BOSS_TIME structure. Perform
* the following checks:
* - if ampm is not specified (T_NOTSPECIFIED), then
* hours must be 0-23. Else hours must be 0-12
* - Minutes must be 0-59.
* - If T_NOTSPECIFIED, assume 24-hour clock, and convert
* final result to 12 hour clock system.
* - If any field is < 0, all
*
*/
static BOSS_TIME mktime(int hours, int minutes, int ampm)
{
BOSS_TIME time;
int cnt;
cnt = 0;
if( hours < 0 ) cnt++;
if( minutes < 0 ) cnt++;
if( ampm < 0 ) cnt++;
if( cnt > 0 && cnt != 3 )
boss_error("Bogus time specification %d:%d", hours,minutes);
else if( cnt == 3 ) {
/* "notime" */
time.hour = -1;
time.minute = -1;
time.pm = -1;
return time;
}
if( minutes > 59 || minutes < 0 )
boss_error("Minutes out of range: %d", minutes);
if( ampm == T_NOTSPECIFIED ) {
if( hours > 23 || hours < 0 )
boss_error("Hours out of range: %d", hours);
time.hour = HOUR24TO12(hours);
time.pm = HOUR24TOAMPM(hours);
time.minute = minutes;
} else {
if( hours > 12 || hours <= 0 )
boss_error("Hours out of range: %d", hours);
time.hour = hours;
time.pm = (ampm == T_AM ) ? 0 : 1;
time.minute = minutes;
}
return time;
}
/*
* Return a BOSS_TIME structure, set to the "no time" configuration.
*/
static BOSS_TIME no_time(void)
{
BOSS_TIME time;
time.hour = -1;
time.minute = -1;
time.pm = -1;
return time;
}
static RANGE mkrange(BOSS_TIME *start, BOSS_TIME *end)
{
RANGE r;
if( start )
r.start = *start;
else
r.start = no_time();
if( end )
r.end = *end;
else
r.end = no_time();
return r;
}
/*
* Build a BOSS_DATE structure. Perform the following checks:
* - If month specified, check the day.
* - If month not specified (is 0), day can be 0 to 32.
* - Month must be between 1-12.
* - If year is less than 100, then add 1900 to year.
* (valid ranges for years are: 1960 to 2100)
*
* year, month and day cannot be 0 when reminder is FALSE.
*
*/
static BOSS_DATE mkdate(int reminder, int year, int month, int day)
{
BOSS_DATE date;
static monlen[] = { 0, 31,29,31,30,31,30,31,31,30,31,30,31 };
if( year > 0 && year < 100 )
year += 1900;
if( (year < 1960 || year > 2099) && year != 0 )
boss_error("Year out of valid range: %d", year);
if( year == 0 && !reminder )
boss_error("Year out of valid range: %d", year);
if( month != 0 && (month < 1 || month > 12) )
boss_error("Month out of range: %d", month);
if( month ) {
if( day <= 0 || day > monlen[month] )
boss_error("Day-of-month out of range: %d (for month %d)",
day, month);
} else {
if( !reminder )
boss_error("Month out of range: %d", month);
if( day == 0 && !reminder )
boss_error("Day out of range: %d", day);
if( day != 0 && (day <= 0 || day > 32) )
boss_error("Day-of-month out of range: %d", day);
}
date.year = year;
date.month = month;
date.day = day;
return date;
}
/*
* return a structure which contains both data and time.
* If data or time is NULL, set the appropriate structure to
* the "nodata" configuration.
*/
static DATETIME mk_datetime(BOSS_DATE *date, BOSS_TIME *time)
{
DATETIME dt;
if( date ) {
dt.date = *date;
} else {
dt.date.year = 0;
dt.date.month = 0;
dt.date.day = 0;
}
if( time ) {
dt.time = *time;
} else {
dt.time.hour = -1;
dt.time.minute = -1;
dt.time.pm = -1;
}
return dt;
}
/*----------------------------------------------------------------------
* XXXXXX_join:
* Joins the list 'lst' and the item (or list) 'item'.
* RETURNS:
* 'lst' is returned.
*
* ASSUMPTIONS:
* 'lst' is not NULL. 'item' is not NULL.
*/
static BOSS_PHONE *phone_join(BOSS_PHONE *lst, BOSS_PHONE *item)
{
BOSS_PHONE *curr;
for(curr=lst; curr->next; curr=curr->next)
;
curr->next = item;
return lst;
}
static BOSS_MEMO *memo_join(BOSS_MEMO *lst, BOSS_MEMO *item)
{
BOSS_MEMO *curr;
for(curr=lst; curr->next; curr=curr->next)
;
curr->next = item;
return lst;
}
static BOSS_SCHEDULE *schedule_join(BOSS_SCHEDULE *lst, BOSS_SCHEDULE *item)
{
BOSS_SCHEDULE *curr;
for(curr=lst; curr->next; curr=curr->next)
;
curr->next = item;
return lst;
}
static BOSS_REMINDER *reminder_join(BOSS_REMINDER *lst, BOSS_REMINDER *item)
{
BOSS_REMINDER *curr;
for(curr=lst; curr->next; curr=curr->next)
;
curr->next = item;
return lst;
}
static BOSS_CALENDAR *calendar_join(BOSS_CALENDAR *lst, BOSS_CALENDAR *item)
{
BOSS_CALENDAR *curr;
for(curr=lst; curr->next; curr=curr->next)
;
curr->next = item;
return lst;
}
static BOSS_TODO *todo_join(BOSS_TODO *lst, BOSS_TODO *item)
{
BOSS_TODO *curr;
for(curr=lst; curr->next; curr=curr->next)
;
curr->next = item;
return lst;
}
static BOSS_EXPENSE *expense_join(BOSS_EXPENSE *lst, BOSS_EXPENSE *item)
{
BOSS_EXPENSE *curr;
for(curr=lst; curr->next; curr=curr->next)
;
curr->next = item;
return lst;
}
/*----------------------------------------------------------------------
* XXXX_add:
* These functions add 'list' to the end of Boss.data.XXXX.
* If Boss.data.XXXX is NULL, then set it to 'list'.
* This function will work if 'list' is NULL.
*/
void phone_add(BOSS_PHONE *list)
{
if( Boss.data->phone == NULL )
Boss.data->phone = list;
else
phone_join(Boss.data->phone, list);
}
void memo_add(BOSS_MEMO *list)
{
if( Boss.data->memo == NULL )
Boss.data->memo = list;
else
memo_join(Boss.data->memo, list);
}
void schedule_add(BOSS_SCHEDULE *list)
{
if( Boss.data->schedule == NULL )
Boss.data->schedule = list;
else
schedule_join(Boss.data->schedule, list);
}
void reminder_add(BOSS_REMINDER *list)
{
if( Boss.data->reminder == NULL )
Boss.data->reminder = list;
else
reminder_join(Boss.data->reminder, list);
}
void calendar_add(BOSS_CALENDAR *list)
{
if( Boss.data->calendar == NULL )
Boss.data->calendar = list;
else
calendar_join(Boss.data->calendar, list);
}
void todo_add(BOSS_TODO *list)
{
if( Boss.data->todo == NULL )
Boss.data->todo = list;
else
todo_join(Boss.data->todo, list);
}
void expense_add(BOSS_EXPENSE *list)
{
if( Boss.data->expense == NULL )
Boss.data->expense = list;
else
expense_join(Boss.data->expense, list);
}
/*----------------------------------------------------------------------
* mkXXXXXX()
* Build a record type and return it.
*/
static BOSS_TODO *mktodo(int checkmark, DATETIME *dt, char *desc)
{
BOSS_TODO *rec;
rec = (BOSS_TODO*)calloc(1, sizeof(BOSS_TODO) );
if( rec == NULL )
boss_error("no memory for BOSS_TODO");
rec->next = NULL;
rec->checked = checkmark;
rec->date_stamp = dt->date;
rec->time_stamp = dt->time;
rec->description = desc;
return rec;
}
static BOSS_PHONE *mkphone(STRLST *strlst)
{
STRLST *curr, *next;
BOSS_PHONE *rec;
rec = (BOSS_PHONE*)calloc(1, sizeof(BOSS_PHONE));
if( rec == NULL )
boss_error("no memory for BOSS_PHONE");
rec->next = NULL;
curr = strlst;
if( curr ) {
rec->name = curr->str;
curr = curr->next;
}
if( curr ) {
rec->number = curr->str;
curr = curr->next;
}
if( curr ) {
rec->address = curr->str;
curr = curr->next;
}
if( curr ) {
rec->free1 = curr->str;
curr = curr->next;
}
if( curr ) {
rec->free2 = curr->str;
curr = curr->next;
}
if( curr ) {
rec->free3 = curr->str;
curr = curr->next;
}
if( curr ) {
rec->free4 = curr->str;
curr = curr->next;
}
if( curr ) {
rec->free5 = curr->str;
curr = curr->next;
}
if( curr ) {
rec->free6 = curr->str;
curr = curr->next;
}
if( curr )
boss_error("Telepone entry contains more than 9 fields");
/*
* Delete string list.
*/
curr = strlst;
while( curr ) {
next = curr->next;
free(curr);
curr = next;
}
return rec;
}
static BOSS_REMINDER *mkreminder(BOSS_DATE *date, BOSS_TIME *time, char *desc)
{
BOSS_REMINDER *rec;
rec = (BOSS_REMINDER*)calloc(1, sizeof(BOSS_REMINDER) );
if( rec == NULL )
boss_error("no memory for BOSS_REMINDER");
rec->next = NULL;
rec->date = *date;
rec->alarm = *time;
rec->description = desc;
return rec;
}
static BOSS_MEMO *mkmemo(char *title, char *body)
{
BOSS_MEMO *rec;
rec = (BOSS_MEMO*)calloc(1, sizeof(BOSS_MEMO) );
if( rec == NULL )
boss_error("no memory for BOSS_MEMO");
rec->next = NULL;
rec->title = title;
rec->body = body;
return rec;
}
static BOSS_SCHEDULE *mkschedule(BOSS_DATE *date, RANGE *rng, BOSS_TIME *alarm,
char *desc)
{
BOSS_SCHEDULE *rec;
rec = (BOSS_SCHEDULE*)calloc(1, sizeof(BOSS_SCHEDULE) );
if( rec == NULL )
boss_error("no memory for BOSS_SCHEDULE");
rec->next = NULL;
rec->date = *date;
rec->start = rng->start;
rec->end = rng->end;
rec->alarm = *alarm;
rec->description = desc;
return rec;
}
static BOSS_CALENDAR *mkcalendar(int month, int year, NUMLST *days)
{
BOSS_CALENDAR *rec;
NUMLST *curr;
static monlen[] = { 0, 31,29,31,30,31,30,31,31,30,31,30,31 };
int cday;
rec = (BOSS_CALENDAR*)calloc(1, sizeof(BOSS_CALENDAR) );
if( rec == NULL )
boss_error("out of memory for BOSS_CALENDAR");
if( (year < 1960 || year > 2099) && year != 0 )
boss_error("Year out of valid range: %d", year);
rec->date.year = year;
rec->date.month = month;
rec->date.day = 1;
rec->days = 0;
cday = 1;
for(curr=days; curr; curr=curr->next) {
if( curr->num < 0 ) {
if( cday > monlen[month] )
boss_error("too many days for month %d", month);
rec->days |= (1<<(cday-1));
} else if( curr->num != cday ) {
boss_error("Calendar day %d out of order (should be %d)",
curr->num, cday);
} else if( cday > monlen[month] )
boss_error("too many days for month %d", month);
cday += 1;
}
rec->next = NULL;
return rec;
}
static BOSS_EXPENSE *mkexpense(double amount, BOSS_DATE *date,
char *payment, char *expense, char *desc)
{
BOSS_EXPENSE *rec;
rec = (BOSS_EXPENSE*)calloc(1, sizeof(BOSS_EXPENSE) );
if( rec == NULL )
boss_error("no memory for BOSS_EXPENSE");
rec->next = NULL;
rec->amount = amount;
rec->date = *date;
rec->payment = payment;
rec->expense = expense;
rec->description = desc;
return rec;
}
static STRLST *mkstrlst(STRLST *lst, char *item)
{
STRLST *n, *curr;
n = (STRLST*)calloc(1, sizeof(STRLST) );
if( n == NULL )
boss_error("out of memory for STRLST");
n->str = item;
n->next = NULL;
if( lst == NULL )
return n;
for(curr=lst; curr->next; curr=curr->next)
;
curr->next = n;
return lst;
}
static NUMLST *mknumlst(NUMLST *lst, int item)
{
NUMLST *n, *curr;
n = (NUMLST*)calloc(1, sizeof(NUMLST) );
if( n == NULL )
boss_error("out of memory for NUMLST");
n->num = item;
n->next = NULL;
if( lst == NULL )
return n;
for(curr=lst; curr->next; curr=curr->next)
;
curr->next = n;
return lst;
}
