-- LedgerOptions.mesa  Edited by Sweet, 30-Mar-81 22:28:43

DIRECTORY
  IODefs: FROM "iodefs",
  LedgerDefs: FROM "ledgerdefs",
  SegmentDefs: FROM "segmentdefs",
  StreamDefs: FROM "streamdefs",
  StringDefs: FROM "stringdefs",
  SystemDefs: FROM "systemdefs";
LedgerOptions: PROGRAM
  IMPORTS IODefs, LedgerDefs, SegmentDefs, StreamDefs, StringDefs, SystemDefs
  EXPORTS LedgerDefs =
  BEGIN OPEN IODefs, LedgerDefs;

  leftX, leftY, rightX, rightY: PUBLIC INTEGER ← 0;
  PrinterParam: TYPE = {leftX, leftY, rightX, rightY};

-- **************************************************************
--	Set up category keywords and names
-- **************************************************************

  categoryName: PUBLIC ARRAY Column OF STRING ← ALL[NIL];
  categoryKey: PUBLIC ARRAY Column OF STRING ← ALL[NIL];
  categoryNeg: PUBLIC ARRAY Column OF BOOLEAN ← ALL[FALSE];
  totalInfo: PUBLIC ARRAY Column OF TotalRec ← ALL[[FALSE, FALSE, 0]];
  extendedCats: PUBLIC BOOLEAN;
  budgetGiven: PUBLIC BOOLEAN ← FALSE;
  budget: PUBLIC ARRAY Column OF ARRAY [1..12] OF Money ← ALL[ALL[0]];

  CopyString: PROCEDURE [old: STRING] RETURNS [new: STRING] =
    BEGIN
    new ← SystemDefs.AllocateHeapString[old.length];
    StringDefs.AppendString[new, old];
    END;

  TokenCol: PROCEDURE RETURNS [col: Column] =
    BEGIN
    FOR col IN Column DO
      IF StringDefs.EquivalentString[categoryKey[col], token] THEN EXIT;
      REPEAT
	FINISHED => WarnS["Invalid budget category",token];
      ENDLOOP;
    RETURN
    END;

  us: StreamDefs.StreamHandle ← NIL;
  token: STRING;
  terminator: CHARACTER;
  
  ReadUserCm: PUBLIC PROCEDURE =
    BEGIN
    us ← StreamDefs.NewByteStream["Ledger.profile",StreamDefs.Read
        ! SegmentDefs.FileNameError => CONTINUE];
    IF us = NIL THEN us ← StreamDefs.NewByteStream["User.cm",StreamDefs.Read
        ! SegmentDefs.FileNameError => CONTINUE];
    IF us=NIL THEN WarnS["NO","Ledger.profile or User.cm"];
    token ← SystemDefs.AllocateResidentPages[1];
    token↑ ← [length: 0, maxlength: (256-2)*2, text: NULL];
    BEGIN ENABLE UNWIND =>
      BEGIN OPEN IODefs;
      ind: StreamDefs.StreamIndex = StreamDefs.GetIndex[us];
      WriteString[" User.cm at index "];
      WriteOctal[ind.page*256+ind.byte];
      WriteChar[CR];
      us.destroy[us]; us ← NIL;
      END;
    ReadCategories[];
    ReadBudget[];
    ReadTotals[];
    ReadPrinterAdj[];
    ReadControl[];
    END; -- of enable
    FinishScanning[];
    END;
    
  ReadCategories: PROCEDURE =
    BEGIN
    col: Column ← 0;
    IF ~StartScanning["[Ledger]"] THEN WarnS["NO","category names"];
    DO
      IF NOT GetNextToken[] THEN EXIT;
      IF token[0]='[ THEN EXIT;
      IF terminator = ': THEN
        BEGIN
        IF token.length # 0 THEN categoryKey[col] ← CopyString[token];
        IF ~GetNextToken[] OR (terminator # CR AND terminator # '") THEN
	  WarnS["Multi-token category name not quoted", categoryKey[col]];
        IF token.length # 0 THEN
	  BEGIN
	  IF token[0] = '- THEN
	    BEGIN
	    i: CARDINAL;
	    categoryNeg[col] ← TRUE;
	    FOR i IN [1..token.length) DO token[i-1] ← token[i]; ENDLOOP;
	    token.length ← token.length-1;
	    END;
	  categoryName[col] ← CopyString[token];
	  END;
        col ← col+1;
        END;
      ENDLOOP;
    extendedCats ← col > BreakColumn;
    END;

  PParam: PROC RETURNS [PrinterParam] =
    BEGIN
    SELECT TRUE FROM
      StringDefs.EquivalentString[token, "leftX"L] => RETURN[leftX];
      StringDefs.EquivalentString[token, "leftY"L] => RETURN[leftY];
      StringDefs.EquivalentString[token, "rightX"L] => RETURN[rightX];
      StringDefs.EquivalentString[token, "rightY"L] => RETURN[rightY];
      ENDCASE => WarnS["funny printer param", token];
    ERROR;
    END;
    
  ReadPrinterAdj: PROCEDURE =
    BEGIN
    desc: StringDefs.SubStringDescriptor;
    ss: StringDefs.SubString = @desc;
    IF ~StartScanning["[LedgerPrinter]"] THEN RETURN;
    DO
      IF NOT GetNextToken[] THEN EXIT;
      IF token[0]='[ THEN EXIT;
      IF terminator = ': THEN
	BEGIN
	pp: PrinterParam ← PParam[];
	delta: INTEGER;
	[] ← GetNextToken[];
	desc ← [base: token, offset: 0, length: 1];
	delta ← GetNumber[ss];
	SELECT pp FROM
	  leftX => leftX ← delta;
	  leftY => leftY ← delta;
	  rightX => rightX ← delta;
	  rightY => rightY ← delta;
	  ENDCASE;
	END;
      ENDLOOP;
    END;

  ReadControl: PROCEDURE =
    BEGIN
    desc: StringDefs.SubStringDescriptor;
    ss: StringDefs.SubString = @desc;
    IF ~StartScanning["[LedgerControl]"] THEN RETURN;
    DO
      IF NOT GetNextToken[] THEN EXIT;
      IF token[0]='[ THEN EXIT;
      IF terminator = ': THEN
	BEGIN
	IF StringDefs.EquivalentString[token, "maxBSize"L] THEN
	  BEGIN
	  [] ← GetNextToken[];
	  desc ← [base: token, offset: 0, length: 1];
	  maxBSize ← GetNumber[ss];
	  END;
	END;
      ENDLOOP;
    END;

  ReadBudget: PROCEDURE =
    BEGIN OPEN StringDefs;
    col: Column;
    desc: SubStringDescriptor;
    ss: SubString = @desc;
    IF ~StartScanning["[Budget]"] THEN RETURN;
    DO
      IF NOT GetNextToken[] THEN EXIT;
      IF token[0]='[ THEN EXIT;
      IF terminator = ': THEN
        BEGIN
	val: Money;
	month: CARDINAL;
	col ← TokenCol[];
        IF ~GetNextToken[] THEN
	  WarnS["funny budget def", categoryKey[col]];
	desc ← [base: token, offset: 0, length: 1];
	val ← GetMoney[ss].val; SkipBlanks[ss];
	IF categoryNeg[col] THEN val ← - val;
	FOR month IN [1..12] DO budget[col][month] ← val; ENDLOOP;
	UNTIL Exhausted[ss] DO
	  IF CurrentChar[ss] # '/ THEN WarnSS["expected /", ss];
	  Bump[ss];
	  month ← GetNumber[ss];
	  IF month ~IN [1..12] THEN WarnSS["invalid month",ss];
	  IF CurrentChar[ss] # ': THEN WarnSS["missing :", ss];
	  Bump[ss];
	  val ← GetMoney[ss].val;
	  IF categoryNeg[col] THEN val ← - val;
	  budget[col][month] ← val;
	  SkipBlanks[ss];
	  ENDLOOP;
	END;
      ENDLOOP;
    budgetGiven ← TRUE;
    END;

  ReadTotals: PROCEDURE =
    BEGIN
    desc: StringDefs.SubStringDescriptor;
    ss: StringDefs.SubString = @desc;
    IF ~StartScanning["[TotalColumns]"] THEN RETURN;
    DO
      IF NOT GetNextToken[] THEN EXIT;
      IF token[0]='[ THEN EXIT;
      IF terminator = ': THEN
	BEGIN
	col: Column = TokenCol[];
	m: CARDINAL;
	addend: Column;
        IF ~GetNextToken[] THEN
	  WarnS["funny total def", categoryKey[col]];
	desc ← [base: token, offset: 0, length: 1];
	totalInfo[col].isTotal ← TRUE;
	DO
	  addend ← GetCategory[ss].col;
	  totalInfo[addend].hasTotal ← TRUE;
	  totalInfo[addend].myTotal ← col;
	  FOR m IN [1..12] DO
	    budget[col][m] ← budget[col][m] + budget[addend][m];
	    ENDLOOP;
	  SkipBlanks[ss];
	  IF Exhausted[ss] THEN EXIT;
	  IF CurrentChar[ss] # '+ THEN WarnS["Expected +", token];
	  Bump[ss];
	  ENDLOOP;
	END;
      ENDLOOP;
    END;


  StartScanning: PROCEDURE [section: STRING] RETURNS [BOOLEAN] =
    BEGIN
    us.reset[us];
    WHILE GetNextToken[] DO
      IF StringDefs.EquivalentString[token,section] THEN RETURN[TRUE];
      ENDLOOP;
    RETURN[FALSE];
    END;
  
  FinishScanning: PROCEDURE =
    BEGIN
    SystemDefs.FreePages[token];
    token ← NIL;
    us.destroy[us];
    us ← NIL;
    END;
  
  GetNextToken: PROCEDURE RETURNS [BOOLEAN] =
    BEGIN
    token.length ← 0;
    UNTIL us.endof[us] DO
      terminator ← us.get[us];
      SELECT terminator FROM
        SP => IF (token.length#0) THEN RETURN[TRUE];  -- flush leading blanks
        ':, CR => RETURN[TRUE]; -- allow null tokens
        '" =>
          BEGIN  -- gobble things up until matching close quote
          UNTIL us.endof[us] DO
            terminator ← us.get[us];
            IF terminator='" THEN EXIT;
            StringDefs.AppendChar[token,terminator];
            ENDLOOP;
          END;
        ENDCASE => StringDefs.AppendChar[token,terminator];
      ENDLOOP;
    RETURN[token.length#0];
    END;
  
  END.