; datepat.asm
;
; This program converts dates of various formats to a three integer
; component value- month, day, and year.

		.xlist
		.286
		include 	stdlib.a
		includelib	stdlib.lib
		matchfuncs
		.list
		.lall


dseg		segment	para public 'data'

; The following three variables hold the result of the conversion.

month		word	0
day		word	0
year		word	0

; StrPtr is a double word value that points at the string under test.
; The output routines use this variable.  It is declared as two word
; values so it is easier to store es:di into it.

strptr		word	0,0

; Value is a generic variable the ConvertInt routine uses

value		word	0



; Number of valid days in each month (Feb is handled specially)

DaysInMonth	byte	31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31



; Some sample strings to test the date conversion routines.

Str0		byte	"Feb 4, 1956",0
Str1		byte	"July 20, 1960",0
Str2		byte	"Jul 8, 1964",0
Str3		byte	"1/1/97",0
Str4		byte	"1-1-1997",0
Str5		byte	"12-25-74",0
Str6		byte	"3/28/1981",0
Str7		byte	"January 1, 1999",0
Str8		byte	"Feb 29, 1996",0
Str9		byte	"30 June, 1990",0
Str10		byte	"August 7, 1945",0
Str11		byte	"30 September, 1992",0
Str12		byte	"Feb 29, 1990",0
Str13		byte	"29 Feb, 1992",0



; The following grammar is what we use to process the dates
;
; Date	->	EngMon Integer Integer
;	|	Integer EngMon Integer
;	|	Integer "/" Integer "/" Integer
;	|	Integer "-" Integer "-" Integer
;
; EngMon->	Jan | January | Feb | February | ... | Dec | December
; Integer->	digit integer | digit
; digit	->	0 | 1 | ... | 9
;
; Some semantic rules this code has to check:
;
; If the year is in the range 0-99, this code has to add 1900 to it.
; If the year is not in the range 0-99 or 1900-1999 then return an error.
; The month must be in the range 1-12, else return an error.
; The day must be between one and 28, 29, 30, or 31.  The exact maximum
; day depends on the month.


separators	pattern	{spancset, delimiters}


; DatePat processes dates of the form  "MonInEnglish  Day  Year"

DatePat		pattern	{sl_match2, EngMon, DatePat2, DayYear}
DayYear		pattern	{sl_match2, DayInteger, 0, YearPat}
YearPat		pattern	{sl_match2, YearInteger}

; DatePat2 processes dates of the form "Day MonInEng Year"

DatePat2	pattern	{sl_match2, DayInteger, DatePat3, MonthYear}
MonthYear	pattern	{sl_match2, EngMon, 0, YearPat}

; DatePat3 processes dates of the form "mm-dd-yy"

DatePat3	pattern	{sl_match2, MonInteger, DatePat4, DatePat3a}
DatePat3a	pattern	{sl_match2, separators, DatePat3b, DatePat3b}
DatePat3b	pattern	{matchchar, '-', 0, DatePat3c}
DatePat3c	pattern	{sl_match2, DayInteger, 0, DatePat3d}
DatePat3d	pattern	{sl_match2, separators, DatePat3e, DatePat3e}
DatePat3e	pattern	{matchchar, '-', 0, DatePat3f}
DatePat3f	pattern	{sl_match2, YearInteger}

; DatePat4 processes dates of the form "mm/dd/yy"

DatePat4	pattern	{sl_match2, MonInteger, 0, DatePat4a}
DatePat4a	pattern	{sl_match2, separators, DatePat4b, DatePat4b}
DatePat4b	pattern	{matchchar, '/', 0, DatePat4c}
DatePat4c	pattern	{sl_match2, DayInteger, 0, DatePat4d}
DatePat4d	pattern	{sl_match2, separators, DatePat4e, DatePat4e}
DatePat4e	pattern	{matchchar, '/', 0, DatePat4f}
DatePat4f	pattern	{sl_match2, YearInteger}


; DayInteger matches an decimal string, converts it to an integer, and
; stores the result away in the Day variable.

DayInteger	pattern	{sl_match2, Integer, 0, SetDayPat}
SetDayPat	pattern	{SetDay}

; MonInteger matches an decimal string, converts it to an integer, and
; stores the result away in the Month variable.

MonInteger	pattern	{sl_match2, Integer, 0, SetMonPat}
SetMonPat	pattern	{SetMon}

; YearInteger matches an decimal string, converts it to an integer, and
; stores the result away in the Year variable.


YearInteger	pattern	{sl_match2, Integer, 0, SetYearPat}
SetYearPat	pattern	{SetYear}


; Integer skips any leading delimiter characters and then matches a
; decimal string.  The Integer0 pattern matches exactly the decimal
; characters; the code does a patgrab on Integer0 when converting
; this string to an integer.

Integer		pattern	{sl_match2, separators, 0, Integer0}
Integer0	pattern	{sl_match2, number, 0, Convert2Int}
number		pattern	{anycset, digits, 0, number2}
number2		pattern	{spancset, digits}
Convert2Int	pattern	{ConvertInt}




; A macro to make it easy to declare each of the 24 English month
; patterns (24 because we allow the full month name and an
; abbreviation).

MoPat		macro	name, next, str, str2, value
		local   SetMo, string, full, short, string2, doMon

name		pattern	{sl_match2, short, next}
short		pattern	{matchistr, string2, full, SetMo}
full		pattern	{matchistr, string, 0, SetMo}

string		byte    str
		byte	0

string2		byte	str2
		byte	0

SetMo		pattern	{MonthVal, value}
		endm


; EngMon is a chain of patterns that match one of the strings
; JAN, JANUARY, FEB, FEBRUARY, etc.  The last parameter to the
; MoPat macro is the month number.

EngMon		pattern {sl_match2, separators, jan, jan}
		MoPat	jan, feb, "JAN", "JANUARY", 1
		MoPat	feb, mar, "FEB", "FEBRUARY", 2
		MoPat	mar, apr, "MAR", "MARCH", 3
		MoPat	apr, may, "APR", "APRIL", 4
		MoPat	may, jun, "MAY", "MAY", 5
		MoPat	jun, jul, "JUN", "JUNE", 6
		MoPat	jul, aug, "JUL", "JULY", 7
		MoPat	aug, sep, "AUG", "AUGUST", 8
		MoPat	sep, oct, "SEP", "SEPTEMBER", 9
		MoPat	oct, nov, "OCT", "OCTOBER", 10
		MoPat	nov, decem, "NOV", "NOVEMBER", 11
		MoPat	decem, 0, "DEC", "DECEMBER", 12




; We use the "digits" and "delimiters" sets from the standard library.

		include	stdsets.a

dseg		ends



cseg		segment	para public 'code'
		assume	cs:cseg, ds:dseg


; ConvertInt-	Matches a sequence of digits and converts them to an integer.

ConvertInt	proc	far
		push	ds
		push	es
		push	di
		mov	ax, dseg
		mov	ds, ax

		lesi    Integer0	;Integer0 contains the decimal
		patgrab			; string we matched, grab that
		atou			; string and convert it to an
		mov	Value, ax	; integer and save the result.
		free			;Free mem allocated by patgrab.

		pop	di
		mov	ax, di		;Required by sl_match.
		pop	es
		pop	ds
		stc			;Always succeed.
		ret

ConvertInt	endp


; SetDay, SetMon, and SetYear simply copy value to the appropriate
; variable.

SetDay		proc	far
		push	ds
		mov	ax, dseg
		mov	ds, ax
		mov	ax, value
		mov	day, ax
		mov	ax, di
		pop	ds
		stc
		ret
SetDay		endp


SetMon		proc	far
		push	ds
		mov	ax, dseg
		mov	ds, ax
		mov	ax, value
		mov	Month, ax
		mov	ax, di
		pop	ds
		stc
		ret
SetMon		endp


SetYear		proc	far
		push	ds
		mov	ax, dseg
		mov	ds, ax
		mov	ax, value
		mov	Year, ax
		mov	ax, di
		pop	ds
		stc
		ret
SetYear		endp


; MonthVal is a pattern used by the English month patterns.
; This pattern function simply copies the matchparm field to
; the month variable (the matchparm field is passed in si).

MonthVal	proc	far
		push	ds
		mov	ax, dseg
		mov	ds, ax
		mov	Month, si
		mov	ax, di
		pop	ds
		stc
		ret
MonthVal	endp



; ChkDate-	Checks a date to see if it is valid.  Returns with the
;		carry flag set if it is, clear if not.

ChkDate		proc	far
		push	ds
		push	ax
		push	bx

		mov	ax, dseg
		mov	ds, ax

; If the year is in the range 0-99, add 1900 to it.
; Then check to see if it's in the range 1900-1999.

		cmp	Year, 100
		ja	Notb100
		add	Year, 1900
Notb100:	cmp	Year, 2000
		jae	BadDate
		cmp	Year, 1900
		jb	BadDate

; Okay, make sure the month is in the range 1-12

		cmp	Month, 12
		ja	BadDate
		cmp	Month, 1
		jb	BadDate

; See if the number of days is correct for all months except Feb:

		mov	bx, Month
		mov	ax, Day			;Make sure Day <> 0.
		test	ax, ax
		je	BadDate
		cmp	ah, 0			;Make sure Day < 256.
		jne	BadDate

		cmp	bx, 2			;Handle Feb elsewhere.
		je	DoFeb
		cmp	al, DaysInMonth[bx-1]	;Check against max val.
		ja	BadDate
		jmp	GoodDate

; Kludge to handle leap years.  Note that 1900 is *not* a leap year.

DoFeb:          cmp	ax, 29			;Only applies if day is
		jb	GoodDate		; equal to 29.
		ja	BadDate			;Error if Day > 29.
		mov	bx, Year		;1900 is not a leap year
		cmp	bx, 1900		; so handle that here.
		je	BadDate
		and	bx, 11b			;Else, Year mod 4 is a
		jne	BadDate			; leap year.

GoodDate:	pop	bx
		pop	ax
		pop	ds
		stc
		ret

BadDate:	pop	bx
		pop	ax
		pop	ds
		clc
		ret
ChkDate		endp


; ConvertDate-	ES:DI contains a pointer to a string containing a valid
;		date.  This routine converts that date to the three
;		integer values found in the Month, Day, and Year
;		variables.  Then it prints them to verify the pattern
;		matching routine.

ConvertDate	proc	near

		ldxi	DatePat
		xor	cx, cx
		match
		jnc	NoMatch

		mov	strptr, di		;Save string pointer for
		mov	strptr+2, es		; use by printf

		call	ChkDate			;Validate the date.
		jnc	NoMatch

		printf
		byte	"%-20^s = Month: %2d    Day:   %2d   Year:  %4d\n",0
		dword	strptr, Month, Day, Year
		jmp	Done

NoMatch:	printf
		byte	"Illegal date ('%^s')",cr,lf,0
		dword	strptr

Done:		ret
ConvertDate	endp




Main		proc
		mov	ax, dseg
		mov	ds, ax
		mov	es, ax

		meminit				;Init memory manager.

; Call ConvertDate to test several different date strings.

		lesi	Str0
		call	ConvertDate
		lesi	Str1
		call	ConvertDate
		lesi	Str2
		call	ConvertDate
		lesi	Str3
		call	ConvertDate
		lesi	Str4
		call	ConvertDate
		lesi	Str5
		call	ConvertDate
		lesi	Str6
		call	ConvertDate
		lesi	Str7
		call	ConvertDate
		lesi	Str8
		call	ConvertDate
		lesi	Str9
		call	ConvertDate
		lesi	Str10
		call	ConvertDate
		lesi	Str11
		call	ConvertDate
		lesi	Str12
		call	ConvertDate
		lesi	Str13
		call	ConvertDate


Quit:		ExitPgm
Main		endp

cseg		ends

sseg		segment	para stack 'stack'
stk		db	1024 dup ("stack   ")
sseg		ends

zzzzzzseg	segment	para public 'zzzzzz'
LastBytes	db	16 dup (?)
zzzzzzseg	ends
		end	Main
