view tests/agcl/parsifal/date_p1.syn @ 0:13d2b8934445

Import AnaGram (near-)release tree into Mercurial.
author David A. Holland
date Sat, 22 Dec 2007 17:52:45 -0500
parents
children
line wrap: on
line source

{/*
  Date Translator
  Copyright (c) 1995-1999, Parsifal Software
  All Rights Reserved
  See the file COPYING for license and usage terms.

  This program illustrates the use of AnaGram to translate a number of
  different representations of data into a common format. The example
  used shows how to translate any of a number of standard formats for
  entering a date into a single format for further processing.

  The parser illustrated here recognizes six basic date formats, and
  for each, will supply the current year if the year is not given. If
  months are entered by name, they may be spelled out in full, or
  abbreviated in the customary manner. Text may be upper or lower case.
  Spaces or tabs may be used freely between the elements of the dates.
  Some examples of the six formats, as applied to the date June 26,
  1999, are as follows:

    June 26  jun 26    June 26, 1999  JUN 26, 99    jun26,1999
    26 June  26 jun    26 June 1999   26 JUN 99     26jun99
    26-June  26-Jun    26-June-99     26-JUN-1999
    6/26     6/26/99   6/26/1999
    6-26     6-26-99   6-26-1999
    6.26     6.26.99   6-26.1999
    6/26/'99 6-26-'99  6.26.'99
    6/26 '99 6-26 '99  6.26 '99
    26/6     26/6/99   26/6/1999
    26-6     26-6-99   26-6-1999
    26.6     26.6.99   26.6.1999
    26/6 '99 26-6 '99  26.6 '99
    26 vi    26 vi 99  26 vi 1999     26 VI 99      26VI99
    26 vi '99

  If CHKDATE encounters a date of the form 2/3/99, it interrogates a
  switch to determine whether to interpret this in the European manner
  (March 2, 1999) or the American manner (February 3, 1999). Where the
  form is obvious, as in 6/26/98 or 26/6/98 it ignores the switch.

  CHKDATE also recognizes dates consisting of a month and year only.
  Where month and year cannot be distinguished from month and day,
  CHKDATE will assume month and day. When the year is given as a two
  digit number, 0 to 49 are assumed to refer to the coming 21st century
  and 50-99 are 20th century dates.  To force recognition as month and
  year, use an apostrophe or use more than 2 digits for the year:  Aug
  14 is the 14th of the month, Aug '14 is August 2014. For the
  beginning of WWI, use Aug 1914

  CHKDATE operates on a string in memory and stores the month, day and
  year in the variables mon, day, and yr respectively.

  checkDate() sets up the input pointer for CHKDATE and calls it.
  checkDate() then checks for error and adds 2000 to the year if the
  year specified was less than 50, otherwise it adds 1900 to the year
  if the year specified was less than 100. It returns non-zero in case
  of error and zero otherwise.

  main() simply reads a string from stdin, and passes it to
  checkDate(). If there is no error, it prints the date in a standard
  format and loops forever.

*/}

[
  pointer input                     // input string in memory
  ~case sensitive                   // ignore case
  disregard white space             // skip blanks and tabs
  lexeme {month, roman, number}     // except inside names and numbers
  ~diagnose errors                  // diagnostics not necessary
  parser name = chkdate
]


eof         = 0                     // standard asciz string terminator
white space = ' ' + '\t'            // blanks and tabs
punctuation = '-' + '/' + '.'
letter = 'a-z'

i = 'i'
v = 'v'
x = 'x'

date string $
 -> date, eof

date
 // Feb 23, Feb 98
 -> month:m, number:d  ={
    if (d > days[m] || d==0) day=0, yr=d;  //  (change to accommodate d = 0, made Dec. 11/99)
    else day=d, mon = m, yr = thisYear;
 }

 // Feb '23
 -> month:m, '\'', number:y                   =day = 0, mon = m, yr = y;
 -> month:m, '/', number:y                    =day = 0, mon = m, yr = y;    // Feb/99  (change made Dec. 11/99)

 // Feb 17, 98
 -> month:m, number:d, ',', number:y          =day = d, mon = m, yr = y;
 -> month:m, number:d, '/', number:y          =day = d, mon = m, yr = y;   // Feb 17/99  (change made Dec. 11/99)

 // 18 Aug
 -> number:d, month                             =day = d, yr = thisYear;

 // 18-Aug
 -> number:d, punct, month                      =day = d, yr = thisYear;

 // 18 aug '95
 -> number:d, month, '\''?, number:y                   =day = d, yr = y;
 -> number:d, month, '/'?, number:y                    =day = d, yr = y;   // 18 feb /95  (change made Dec. 11/99)


 // 18-aug 75  or //18-aug 1923
 -> number:d, punct, month,
      matchPunctuation, '\''?, number:y                =day = d, yr = y;

 // 6-11
 -> number:m, punct, number:d                            =monthDay(m,d);

 // 6 '99 or 6-'99
 -> number:m, punct?, '\'', number:y            =day = 0, mon=m, yr = y;

 // 6-6-44 or 6-6-'44
 -> number:m, punct, number:d,
      matchPunctuation, '\''?, number:y            =monthDayYear(m,d,y);

 // 6-11'44
 -> number:m, punct, number:d,
      '\'', number:y                               =monthDayYear(m,d,y);

 // 6 ix
 -> number:d, roman:m                  =mon = m, day = d, yr = thisYear;

 // 6 ix 94
 -> number:d, roman:m, number:y               =mon = m, day = d, yr = y;

 // vi 44 or vi '44
 -> roman:m, '\''?, number:y                  =day = 0, mon = m, yr = y;

(int) month
month
 -> "jan", letter?...          =1;
 -> "feb", letter?...          =2;
 -> "mar", letter?...          =3;
 -> "apr", letter?...          =4;
 -> "may"                      =5;
 -> "jun", letter?...          =6;
 -> "jul", letter?...          =7;
 -> "aug", letter?...          =8;
 -> "sep", letter?...          =9;
 -> "oct", letter?...          =10;
 -> "nov", letter?...          =11;
 -> "dec", letter?...          =12;


punct
 -> punctuation: c                                       =matchChar = c;

matchPunctuation
 -> punctuation:c  ={
    if (matchChar != c) PCB.exit_flag = AG_SYNTAX_ERROR_CODE;
 }

(int) number
 -> '0-9':d                                                      =d-'0';
 -> number:n, '0-9':d                                     =10*n + d-'0';


(int) roman
  -> i, x                       =9;
  -> i, v                       =4;
  -> units


(int) units
  -> i                          =1;
  -> v                          =5;
  -> x                          =10;
  -> units:x, i                 =x+1;

{

#include <time.h>

#define SYNTAX_ERROR

int    days[13]  = {0,31,29,31,30,31,30,31,31,30,31,30,31};
char  *monthName[13] = {NULL, "January", "February", "March", "April",
          "May", "June", "July", "August", "September", "October",
          "November", "December"};
int    mon = 0, day = 0, yr = 0;
int    thisYear;
int    european = 0;
int    matchChar = 0;

void monthDay(int m, int d) {
  if (m <= 12 && d > days[m]) day=0, mon = m, yr = d;
  else if (m > 12 || european ) day = m, mon =d , yr = thisYear;
  else mon=m, day=d, yr=thisYear;
}

void monthDayYear(int m, int d, int y) {
  if (m > 12 || european) day = m, mon = d;
  else mon = m, day = d;
  yr = y;
}

int checkDate(char *input) {
  PCB.pointer = (unsigned char *) input;
  chkdate();
  if (PCB.exit_flag != AG_SUCCESS_CODE) return 1;     /* fail on error */
  if (mon > 12) return 1;
  if (day > days[mon]) return 1;
  if (yr < 50) yr += 2000;
  else if (yr < 100 ) yr += 1900;
  return 0;
}

int main(int argc, char *argv[]) {
  char input[82];
  time_t timeOfDay;
  int k;

  for (k = 1; k < argc; k++) {
    switch (*argv[k]++) {
    case '/':
    case '-':
      if (*argv[k] == 'e') european = 1;
      break;
    }
  }
/*  Determine current year */

  timeOfDay = time(NULL);
  thisYear = localtime(&timeOfDay)->tm_year;

/* Loop forever, reading input strings and converting them */
  while (1) {
    gets(input);
    if (feof(stdin)) break;
    if (checkDate(input)) printf("%-30s Bad date\n", input);
    else if (day) printf("%-30s %s %d, %d\n", input, monthName[mon], day, yr);
    else printf("%-30s %s %d\n", input, monthName[mon], yr);
  }
  return 0;
}
}