-- SourceTimeImpl.Mesa
-- Last modified by Sandman on July 8, 1980  11:15 AM
-- Copyright  Xerox Corporation 1979, 1980

DIRECTORY
  AltoDefs USING [BytesPerWord, PageSize],
  BcdDefs USING [VersionStamp, NullVersion],
  InlineDefs USING [BITAND],
  SegmentDefs USING [FileHandle, GetFileTimes],
  SourceTime USING [],
  StreamDefs USING [CreateByteStream, Read, ReadBlock, StreamHandle],
  String USING [AppendChar, EquivalentString, StringToDecimal],
  Storage USING [Pages, FreePages],
  TimeDefs USING [InvalidTime, PackDT, PackedTime, UnpackedTime];

SourceTimeImpl: PROGRAM
  IMPORTS InlineDefs, SegmentDefs, StreamDefs, String, Storage, TimeDefs
  EXPORTS SourceTime =
  BEGIN OPEN SegmentDefs;
  
  -- Obtain creation dates for source files --
  
  NullStamp: BcdDefs.VersionStamp = BcdDefs.NullVersion;
  NullTime: LONG CARDINAL = NullStamp.time;
  
  BufferObject: TYPE = RECORD [p: CARDINAL, s: STRING];
  Buffer: TYPE = POINTER TO BufferObject;
  
  FindSourceVersion: PUBLIC PROCEDURE [file: FileHandle, getDateFromText: BOOLEAN]
    RETURNS [date: TimeDefs.PackedTime] =
    BEGIN OPEN AltoDefs, StreamDefs;
    stream: StreamHandle ← CreateByteStream[file, Read];
    b: BufferObject;
    found: BOOLEAN ← FALSE;
    tdate: TimeDefs.PackedTime;
    IF ~getDateFromText THEN -- obtain date from file header page
      date ← SegmentDefs.GetFileTimes[file].create
    ELSE -- obtain date by scanning 1st few lines of file
      BEGIN
      b.s ← Storage.Pages[1];
      b.s↑ ←
	[length: 0, maxlength: (PageSize - SIZE[StringBody])*BytesPerWord, text:];
      b.s.length ←
	ReadBlock[stream, @b.s.text, b.s.maxlength/BytesPerWord]*BytesPerWord;
      b.p ← 0;
      [found, date] ← GetDate[@b ! TimeDefs.InvalidTime => CONTINUE];
      IF found THEN
	DO
	  [found, tdate] ← GetDate[@b ! TimeDefs.InvalidTime => EXIT];
	  IF ~found THEN EXIT;
	  IF tdate > date THEN date ← tdate;
	  ENDLOOP;
      Storage.FreePages[b.s];
      END;
    stream.destroy[stream];
    RETURN;
    END;
    
  GetDate: PROCEDURE [b: Buffer] RETURNS [BOOLEAN, TimeDefs.PackedTime] =
    BEGIN OPEN TimeDefs, String;
    token: STRING ← [20];
    found: BOOLEAN ← FALSE;
    index: CARDINAL [0..12);
    u: UnpackedTime ← [0, 0, 0, 0, 0, 0, 0, 0, FALSE];
    NextItem[b, token];
    WHILE token.length # 0 DO
      [found, index] ← IsMonth[token];
      IF found THEN
	BEGIN
	NextItem[b, token];
	IF IsNumber[token] THEN
	  BEGIN u.month ← index; u.day ← StringToDecimal[token]; END
	ELSE LOOP;
	END
      ELSE
	IF IsNumber[token] THEN -- possible Day-Mo-Yr form
	  BEGIN
	  u.day ← StringToDecimal[token];
	  NextItem[b, token];
	  [found, index] ← IsMonth[token];
	  IF found THEN u.month ← index ELSE LOOP;
	  END;
      IF found THEN
	BEGIN
	NextItem[b, token];
	IF IsNumber[token] THEN
	  BEGIN
	  u.year ← StringToDecimal[token];
	  IF u.year < 100 THEN u.year ← u.year + 1900;
	  END
	ELSE LOOP;
	NextItem[b, token];
	IF IsNumber[token] THEN
	  BEGIN
	  u.hour ← StringToDecimal[token];
	  NextItem[b, token];
	  IF IsNumber[token] THEN u.minute ← StringToDecimal[token];
	  NextItem[b, token];
	  IF EquivalentString[token, "PM"L] AND u.hour < 12 THEN
	    u.hour ← u.hour + 12
	  ELSE IF EquivalentString[token, "AM"L] AND u.hour = 12 THEN u.hour ← 0;
	  END;
	RETURN[TRUE, TimeDefs.PackDT[u, TRUE]]
	END;
      NextItem[b, token];
      ENDLOOP;
    RETURN[FALSE, NullTime]
    END;
    
  IsMonth: PROCEDURE [candidate: STRING] RETURNS [BOOLEAN, CARDINAL] =
    BEGIN
    Months: ARRAY [0..12) OF STRING =
      ["JANUARY"L, "FEBRUARY"L, "MARCH"L, "APRIL"L, "MAY"L, "JUNE"L, "JULY"L,
	"AUGUST"L, "SEPTEMBER"L, "OCTOBER"L, "NOVEMBER"L, "DECEMBER"L];
    i, j: CARDINAL;
    test: STRING;
    IF candidate.length >= 3 THEN
      FOR i IN [0..12) DO
	test ← Months[i];
	IF candidate.length <= test.length THEN
	  FOR j IN [0..candidate.length) DO
	    IF CharAnd[candidate[j], 137B] # test[j] THEN EXIT;
	    REPEAT FINISHED => RETURN[TRUE, i];
	    ENDLOOP;
	ENDLOOP;
    RETURN[FALSE, 0]
    END;
    
  CharAnd: PROCEDURE [CHARACTER, WORD] RETURNS [CHARACTER] = InlineDefs.BITAND;
  
  IsNumber: PROCEDURE [s: STRING] RETURNS [BOOLEAN] =
    BEGIN
    i: CARDINAL;
    IF s.length = 0 THEN RETURN[FALSE];
    FOR i IN [0..s.length) DO IF s[i] ~ IN ['0..'9] THEN RETURN[FALSE]; ENDLOOP;
    RETURN[TRUE]
    END;
    
  NextItem: PROCEDURE [b: Buffer, token: STRING] =
    BEGIN
    c: CHARACTER;
    token.length ← 0;
    WHILE b.p < b.s.length DO
      c ← b.s[b.p];
      b.p ← b.p + 1;
      SELECT c FROM
	IN ['a..'z], IN ['A..'Z], IN ['0..'9] =>
	  IF token.length < token.maxlength THEN String.AppendChar[token, c];
	ENDCASE => IF token.length # 0 THEN EXIT;
      ENDLOOP;
    RETURN
    END;
    
  
  END.