-- DataInput.mesa  Edited by Sweet, 15-Jan-81 16:11:51

DIRECTORY
  IODefs: FROM "iodefs" USING [ControlZ, CR, SP, WriteString, TAB],
  LedgerDefs: FROM "ledgerdefs",
  StreamDefs: FROM "streamdefs",
  StringDefs: FROM "stringdefs",
  SystemDefs: FROM "systemdefs";
DataInput: PROGRAM
  IMPORTS IODefs, LedgerDefs, StreamDefs, StringDefs, SystemDefs
  EXPORTS LedgerDefs =
  BEGIN OPEN IODefs, StringDefs, LedgerDefs;

  SkipBlanks: PUBLIC PROCEDURE [ss: SubString, notTab: BOOLEAN ← FALSE] =
    BEGIN
    DO
      IF Exhausted[ss] THEN RETURN;
      SELECT CurrentChar[ss] FROM
	TAB => IF notTab THEN RETURN ELSE Bump[ss];
	SP => Bump[ss];
	CR => WarnSS["Unexpected end of line", ss];
	ENDCASE => RETURN;
      ENDLOOP;
    END;

  GetNumber: PUBLIC PROCEDURE [ss: SubString, defaultOK: BOOLEAN ← FALSE, default: INTEGER ← 0] RETURNS [val: INTEGER] =
    BEGIN
    neg: BOOLEAN ← FALSE;
    c: CHARACTER;
    SkipBlanks[ss];
    DO
      IF Exhausted[ss] THEN GO TO noNum;
      SELECT (c ← CurrentChar[ss]) FROM
	'- => neg ← TRUE;
	IN ['0..'9] => EXIT;
	ENDCASE => GO TO noNum;
      Bump[ss];
      REPEAT
	noNum =>
	  BEGIN
	  IF ~defaultOK THEN WarnSS["Missing number", ss];
	  RETURN [default];
	  END;
      ENDLOOP;
    val ← 0;
    WHILE c IN ['0..'9] DO
      val ← 10*val+(c-'0);
      Bump[ss];
      IF Exhausted[ss] THEN EXIT;
      c ← CurrentChar[ss];
      ENDLOOP;
    IF neg THEN val ← -val;
    RETURN
    END;

  GetLongNumber: PROCEDURE [ss: SubString, defaultOK: BOOLEAN ← FALSE, default: LONG INTEGER ← 0] RETURNS [val: LONG INTEGER] =
    BEGIN
    neg: BOOLEAN ← FALSE;
    c: CHARACTER;
    SkipBlanks[ss];
    DO
      IF Exhausted[ss] THEN GO TO noNum;
      SELECT (c ← CurrentChar[ss]) FROM
	'- => neg ← TRUE;
	IN ['0..'9] => EXIT;
	ENDCASE => GO TO noNum;
      Bump[ss];
      REPEAT
	noNum =>
	  BEGIN
	  IF ~defaultOK THEN WarnSS["Missing number", ss];
	  RETURN [default];
	  END;
      ENDLOOP;
    val ← 0;
    WHILE c IN ['0..'9] DO
      val ← 10*val+(c-'0);
      Bump[ss];
      IF Exhausted[ss] THEN EXIT;
      c ← CurrentChar[ss];
      ENDLOOP;
    IF neg THEN val ← -val;
    RETURN
    END;

  GetMoney: PUBLIC PROCEDURE [ss: SubString] RETURNS [val: Money, deductable: BOOLEAN] =
    BEGIN
    neg, brkt: BOOLEAN ← FALSE;
    SkipBlanks[ss];
    IF CurrentChar[ss] = '< THEN
      BEGIN neg ← brkt ← TRUE; Bump[ss] END;
    IF CurrentChar[ss] = '- THEN BEGIN neg ← TRUE; Bump[ss]; END;
    val ← IF CurrentChar[ss] = '. THEN 0 ELSE GetLongNumber[ss]*100;
    IF CurrentChar[ss] = '. THEN
      BEGIN
      Bump[ss];
      val ← val + GetLongNumber[ss, TRUE, 0];
      END;
    IF brkt THEN
      IF CurrentChar[ss] # '> THEN WarnSS["Missing <",ss]
      ELSE Bump[ss];
    IF neg THEN val ← -val;
    SELECT CurrentChar[ss] FROM
      'T, 't => BEGIN Bump[ss]; deductable ← TRUE; END;
      ENDCASE => deductable ← FALSE;
    RETURN
    END;

  defaultYear: [60..99] ← 60;
  yearDefaultable: BOOLEAN ← FALSE;

  lastDate: PUBLIC SmallDate ← NullDate;
  
  GetDate: PROCEDURE [ss: SubString] RETURNS [dd: SmallDate] =
    BEGIN
    m: CARDINAL;
    SkipBlanks[ss, TRUE];
    IF CurrentChar[ss] = TAB THEN  
      IF lastDate = NullDate THEN WarnSS["Invalid date",ss]
      ELSE RETURN [lastDate];
    dd.cv ← 0;
    m ← GetNumber[ss];
    IF CurrentChar[ss] # '/ THEN 
      IF lastDate = NullDate THEN WarnSS["Invalid date",ss]
      ELSE {lastDate.day ← m; dd ← lastDate; RETURN};
    dd.month ← m;
    Bump[ss];
    dd.day ← GetNumber[ss];
    IF CurrentChar[ss] = '/ THEN
      BEGIN
      Bump[ss];
      dd.year ← GetNumber[ss, yearDefaultable, defaultYear];
      END
    ELSE IF yearDefaultable THEN dd.year ← defaultYear 
    ELSE WarnSS["Invalid date",ss];
    lastDate ← dd;
    RETURN
    END;
    
  lastCheck: PUBLIC CARDINAL ← NullCheck;
  
  GetCheck: PROC [ss: SubString] RETURNS [CARDINAL] =
    BEGIN
    SkipBlanks[ss, TRUE];
    IF CurrentChar[ss] = TAB THEN   
      IF lastCheck = NullCheck THEN WarnSS["Missing Check number",ss]
      ELSE RETURN [lastCheck ← lastCheck + 1];
    RETURN[lastCheck ← GetNumber[ss]];
    END;

  GetUpTo: PUBLIC PROCEDURE [ss: SubString, stop: CHARACTER] RETURNS [ans: STRING] =
    BEGIN
    adesc: SubStringDescriptor ← ss↑;
    DO 
      IF Exhausted[ss] THEN 
        BEGIN
        ws: STRING ← [15];
        AppendString[ws, "Missing "];
        IF stop = IODefs.TAB THEN AppendString[ws, "<TAB>"]
	ELSE AppendChar[ws, stop];
	WarnSS[ws, ss];
	END;
      IF CurrentChar[ss] = stop THEN EXIT; 
      Bump[ss]; 
      ENDLOOP;
    adesc.length ← ss.offset - adesc.offset;
    ans ← SystemDefs.AllocateHeapString[adesc.length];
    AppendSubString[ans, @adesc];
    END;

  GetCategory: PUBLIC PROCEDURE [ss: SubString] RETURNS [col: Column, note: STRING] =
    BEGIN
    key: STRING ← [10];
    c: CHARACTER;
    SkipBlanks[ss];
    IF Exhausted[ss] THEN WarnSS["Missing Category", ss];
    DO
      IF Exhausted[ss] THEN EXIT;
      SELECT (c ← CurrentChar[ss]) FROM
        IN ['a..'z], IN ['A..'Z] => StringDefs.AppendChar[key, c];
	ENDCASE => EXIT;
      Bump[ss]; 
      ENDLOOP;
    FOR col IN Column DO
      IF StringDefs.EquivalentString[categoryKey[col], key] THEN EXIT;
      REPEAT
        FINISHED => WarnSS["Invalid category", ss];
      ENDLOOP;
    SkipBlanks[ss];
    IF ~Exhausted[ss] AND CurrentChar[ss] = '( THEN
      BEGIN
      Bump[ss];
      note ← GetUpTo[ss, ')];
      Bump[ss];
      END
    ELSE note ← NIL;
    END;

  dateFirst: PUBLIC BOOLEAN;
  instring: STRING;

  InputLine: PUBLIC PROCEDURE [p: POINTER] RETURNS [CARDINAL] =
    BEGIN
    sip: POINTER TO SortItem = p;
    adesc: StringDefs.SubStringDescriptor ← [base: instring, offset: NULL, length: 1];
    ss: StringDefs.SubString = @adesc;
    DO
      ENABLE BadData => LOOP;
      GetLine[instring];
      IF EndOfFile AND instring.length = 0 THEN
	BEGIN IODefs.WriteString["Sorting..."]; RETURN[0]; END;
      ss.offset ← 0;
      SkipBlanks[ss, TRUE];
      IF Exhausted[ss] THEN LOOP;
      SELECT CurrentChar[ss] FROM
	TAB, IN ['0..'9] =>
	  BEGIN
	  IF dateFirst THEN
	    BEGIN
	    sip.date ← GetDate[ss];
	    SkipBlanks[ss, TRUE];
	    IF CurrentChar[ss] # TAB THEN WarnSS["missing TAB", ss];
	    Bump[ss];
	    sip.check ← GetCheck[ss];
	    END
	  ELSE
	    BEGIN
	    sip.check ← GetCheck[ss];
	    SkipBlanks[ss, TRUE];
	    IF CurrentChar[ss] # TAB THEN WarnSS["missing TAB", ss];
	    Bump[ss];
	    sip.date ← GetDate[ss];
	    END;
	  SkipBlanks[ss];
	  ss.length ← instring.length - ss.offset;
	  sip.string ← [length: 0, maxlength: ss.length, text:];
	  StringDefs.AppendSubString[@sip.string, ss];
	  RETURN [SIZE[SortItem]+(ss.length+1)/2]
	  END;
	'y, 'Y =>
	  BEGIN
	  SkipToken[ss];
	  yearDefaultable ← TRUE;
	  defaultYear ← GetNumber[ss];
	  END;
	'd, 'D => dateFirst ← TRUE;
	'c, 'C => dateFirst ← FALSE;
	't, 'T =>
	  BEGIN
	  col: Column;
	  val: Money;
	  SkipToken[ss];
	  DO
	    val ← GetMoney[ss].val;
	    col ← GetCategory[ss].col;
	    ytdTotal[col] ← val;
	    SkipBlanks[ss];
	    IF Exhausted[ss] THEN EXIT;
	    IF CurrentChar[ss] # '/ THEN WarnSS["Expected /", ss];
	    Bump[ss];
	    ENDLOOP;
	  END;
	IODefs.ControlZ => NULL; -- ignore lines that start with ↑Z
	ENDCASE => WarnSS["Bad input data",ss];
      ENDLOOP;
    END;

  SkipToken: PROCEDURE [ss: StringDefs.SubString] =
    BEGIN
    DO
      Bump[ss];
      SELECT CurrentChar[ss] FROM
        SP, TAB => EXIT;
        CR => WarnSS["Unexpected end of line",ss];
        ENDCASE => NULL;
      ENDLOOP;
    END;

  EndOfFile: PUBLIC BOOLEAN;
  instream: PUBLIC StreamDefs.StreamHandle ← NIL;

  GetLine: PROCEDURE [instring: STRING] =
    BEGIN
    ch: CHARACTER;
    instring.length ← 0;
    IF EndOfFile THEN RETURN;
      DO
	ch ← instream.get[instream !StreamDefs.StreamError =>
	  IF error = StreamAccess
	    THEN BEGIN EndOfFile ← TRUE; EXIT END];
        IF ch = CR THEN EXIT;
        AppendChar[instring, ch];
        ENDLOOP;
    RETURN
    END;

  instring ← SystemDefs.AllocateResidentPages[2];
  instring↑ ← [length: 0, maxlength: (512-2)*2, text: NULL];
  END.