-- StreamsB.Mesa  Edited by Sandman on June 10, 1980  11:33 AM
-- Copyright  Xerox Corporation 1979, 1980

DIRECTORY
  AltoDefs USING [CharsPerPage, MaxFilePage, PageCount, PageNumber],
  AltoFileDefs USING [CFA, eofDA, FA, fillinDA, vDA],
  BFSDefs USING [DeletePages],
  ForgotDefs USING [StreamBufferDS],
  InlineDefs USING [BITAND, LongDivMod, LongMult],
  NucleusOps USING [],
  SegmentDefs USING [
    Append, DataSegmentHandle, DataSegmentAddress, DefaultAccess, DefaultBase,
    FileHandle, GetFileLength, InsertFileLength, JumpToPage, LockFile, HardDown,
    MakeDataSegment, NewFile, OpenFile, Read, ReleaseFile, SetFileAccess,
    UnlockFile, UpdateFileLength, Write],
  StreamDefs USING [
    AccessOptions, DiskHandle, StreamHandle, StreamIndex, StreamObject,
    StreamPosition],
  StreamsA USING [
    Cleanup, EndOf, Fixup, Pos, PositionByte, ReadByte, ReadWord, StreamError,
    TransferPages, WriteByte, WriteWord],
  Storage USING [Node, Free, FreePages];

StreamsB: PROGRAM
  IMPORTS BFSDefs, InlineDefs, SegmentDefs, Storage, StreamsA
  EXPORTS StreamDefs, NucleusOps
  SHARES StreamsA, StreamDefs, SegmentDefs =PUBLIC

  BEGIN OPEN AltoDefs, AltoFileDefs, StreamDefs, StreamsA;

  WindowSize: PageCount = 1;

  NewByteStream: PROCEDURE [name: STRING, access: AccessOptions]
    RETURNS [DiskHandle] =
    BEGIN OPEN SegmentDefs;
    RETURN[Create[NewFile[name, access], bytes, access]]
    END;

  NewWordStream: PROCEDURE [name: STRING, access: AccessOptions]
    RETURNS [DiskHandle] =
    BEGIN OPEN SegmentDefs;
    RETURN[Create[NewFile[name, access], words, access]]
    END;

  CreateByteStream: PROCEDURE [
    file: SegmentDefs.FileHandle, access: AccessOptions] RETURNS [DiskHandle] =
    BEGIN RETURN[Create[file, bytes, access]] END;

  CreateWordStream: PROCEDURE [
    file: SegmentDefs.FileHandle, access: AccessOptions] RETURNS [DiskHandle] =
    BEGIN RETURN[Create[file, words, access]] END;

  streamList: DiskHandle ← NIL;

  GetDiskStreamList: PROCEDURE RETURNS [DiskHandle] =
    BEGIN RETURN[streamList] END;

  Create: PROCEDURE [
    file: SegmentDefs.FileHandle, units: {bytes, words}, access: AccessOptions]
    RETURNS [stream: DiskHandle] =
    BEGIN OPEN SegmentDefs;
    fa: FA ← FA[eofDA, 0, 0];
    IF access = DefaultAccess THEN access ← Read;
    SetFileAccess[file, access];
    stream ← Storage.Node[SIZE[Disk StreamObject]];
    stream↑ ← StreamObject[
      reset: Reset, get: ReadByte, putback: PutBack, put: WriteByte, endof: EndOf,
      destroy: Destroy, link: streamList,
      body: Disk[
      eof: FALSE, dirty: FALSE, unit: 1, index: 0, size: 0, getOverflow: Fixup,
      savedGet: ReadError, putOverflow: Fixup, savedPut: WriteByte, file:, read:,
      write:, append:, page: 0, char: 0, buffer:, das:]];
    stream.file ← file;
    stream.read ← InlineDefs.BITAND[access, Read] # 0;
    stream.write ← InlineDefs.BITAND[access, Write] # 0;
    stream.append ← InlineDefs.BITAND[access, Append] # 0;
    IF units = words THEN
      BEGIN OPEN stream;
      get ← ReadWord;
      unit ← 2;
      put ← savedPut ← WriteWord;
      END;
    IF ~stream.read THEN stream.get ← ReadError;
    SELECT InlineDefs.BITAND[access, Write + Append] FROM
      0 => stream.put ← stream.savedPut ← WriteError;
      Write => stream.savedPut ← WriteError;
      Append => stream.put ← WriteError;
      ENDCASE;
    stream.buffer.word ← GetBuffer[];
    BEGIN
    ENABLE UNWIND => Storage.FreePages[stream.buffer.word];
    LockFile[file];
    InsertFileLength[file, @fa];
    OpenFile[file];
    stream.das[last] ← stream.das[next] ← fillinDA;
    stream.das[current] ← file.fp.leaderDA;
    IF access = Append THEN [] ← FileLength[stream] ELSE Jump[stream, @fa, 1];
    END;
    streamList ← stream;
    RETURN
    END;

  GetBuffer: PROCEDURE RETURNS [POINTER] =
    BEGIN OPEN SegmentDefs;
    ds: DataSegmentHandle ← MakeDataSegment[
      base: DefaultBase, pages: WindowSize, info: HardDown];
    ds.type ← ForgotDefs.StreamBufferDS;
    RETURN[DataSegmentAddress[ds]];
    END;

  OpenDiskStream: PROCEDURE [stream: StreamHandle] =
    BEGIN
    fa: FA;
    WITH s: stream SELECT FROM
      Disk =>
	BEGIN
	IF s.buffer.word = NIL THEN s.buffer.word ← GetBuffer[];
	fa ← FA[s.das[current], s.page, Pos[@s]];
	JumpToFA[@s, @fa];
	END;
      ENDCASE => SIGNAL StreamError[@s, StreamType];
    RETURN
    END;

  CleanupDiskStream: PROCEDURE [stream: StreamHandle] =
    BEGIN
    WITH s: stream SELECT FROM
      Disk => Cleanup[@s, TRUE];
      ENDCASE => SIGNAL StreamError[@s, StreamType];
    RETURN
    END;

  Reset: PROCEDURE [stream: StreamHandle] =
    BEGIN
    fa: FA;
    WITH s: stream SELECT FROM
      Disk => BEGIN fa ← FA[eofDA, 0, 0]; Jump[@s, @fa, 1]; END;
      ENDCASE => SIGNAL StreamError[@s, StreamType];
    RETURN
    END;

  CloseDiskStream: PROCEDURE [stream: StreamHandle] =
    BEGIN
    WITH s: stream SELECT FROM
      Disk =>
	BEGIN
	Cleanup[@s, TRUE];
	Storage.FreePages[s.buffer.word];
	s.buffer.word ← NIL;
	END;
      ENDCASE => SIGNAL StreamError[@s, StreamType];
    RETURN
    END;

  TruncateDiskStream: PROCEDURE [stream: StreamHandle] =
    BEGIN
    WITH s: stream SELECT FROM
      Disk => Kill[@s, s.write];
      ENDCASE => SIGNAL StreamError[@s, StreamType];
    RETURN
    END;

  Destroy: PROCEDURE [stream: StreamHandle] =
    BEGIN
    WITH s: stream SELECT FROM
      Disk => Kill[@s, ~s.read AND GetIndex[@s] # StreamIndex[0, 0]];
      ENDCASE => SIGNAL StreamError[@s, StreamType];
    RETURN
    END;

  Kill: PROCEDURE [stream: DiskHandle, trunc: BOOLEAN] =
    BEGIN OPEN stream;
    da: vDA;
    pn: PageNumber;
    prev: StreamHandle;
    IF streamList = stream THEN streamList ← LOOPHOLE[stream.link]
    ELSE
      FOR prev ← streamList, prev.link UNTIL prev = NIL DO
	IF prev.link = stream THEN prev.link ← stream.link; ENDLOOP;
    BEGIN
    ENABLE
      UNWIND =>
	BEGIN
	IF buffer.word # NIL THEN Storage.FreePages[buffer.word];
	Storage.Free[stream];
	END;
    IF buffer.word # NIL THEN
      BEGIN
      da ← eofDA;
      IF trunc THEN
	BEGIN -- truncate the file
	-- this is not a separate procedure because it
	-- leaves the stream buffer in an awful state.
	pn ← page;
	da ← das[next];
	das[next] ← eofDA;
	IF char # Pos[stream] THEN BEGIN char ← Pos[stream]; dirty ← TRUE END;
	END;
      IF dirty THEN Cleanup[stream, TRUE];
      IF da # eofDA THEN BFSDefs.DeletePages[buffer.word, @file.fp, da, pn + 1];
      Storage.FreePages[buffer.word];
      buffer.word ← NIL;
      END;
    SegmentDefs.UnlockFile[file];
    IF file.segcount = 0 THEN SegmentDefs.ReleaseFile[file];
    END; --ELBANE
    Storage.Free[stream];
    RETURN
    END;

  Jump: PROCEDURE [s: DiskHandle, fa: POINTER TO FA, pn: PageNumber] =
    BEGIN OPEN SegmentDefs, s;
    cfa: CFA ← CFA[file.fp, fa↑];
    foundEnd: BOOLEAN ← FALSE;
    IF dirty THEN Cleanup[s, TRUE];
    PositionByte[s, 0, FALSE];
    IF pn = MaxFilePage THEN
      BEGIN
      [das[last], das[next]] ← JumpToPage[@cfa, cfa.fa.page, buffer.word];
      foundEnd ← das[next] = AltoFileDefs.eofDA;
      END;
    IF ~foundEnd THEN [das[last], das[next]] ← JumpToPage[@cfa, pn, buffer.word];
    [das[current], page, char] ← cfa.fa;
    IF das[next] = eofDA THEN SegmentDefs.UpdateFileLength[file, @cfa.fa];
    PositionByte[s, IF page # pn THEN char ELSE MIN[char, fa.byte], FALSE];
    RETURN
    END;

  ReadError: PROCEDURE [s: StreamHandle] RETURNS [UNSPECIFIED] =
    BEGIN SIGNAL StreamError[s, StreamAccess]; RETURN[0] END;

  PutBack: PROCEDURE [stream: StreamHandle, item: UNSPECIFIED] =
    BEGIN SIGNAL StreamError[stream, StreamOperation]; RETURN END;

  WriteError: PROCEDURE [stream: StreamHandle, item: UNSPECIFIED] =
    BEGIN SIGNAL StreamError[stream, StreamAccess]; RETURN END;

  bite: INTEGER = 60; -- don't use too much heap


  PositionPage: PROCEDURE [s: DiskHandle, p: PageNumber] =
    BEGIN
    d, dp: INTEGER;
    np: PageCount;
    Cleanup[s, TRUE];
    PositionByte[s, 0, FALSE]; -- should we reset first?
    SELECT INTEGER[s.page - p] FROM
      <= 0 => NULL;
      = 1, < INTEGER[s.page/10] => NULL;
      ENDCASE => Reset[s];
    WHILE (d ← p - s.page) # 0 DO
      dp ← IF d < 0 THEN -1 ELSE MIN[d, bite];
      np ← TransferPages[s, NIL, dp, in, FALSE];
      IF dp > 0 AND np # dp THEN EXIT;
      REPEAT FINISHED => RETURN;
      ENDLOOP;
    IF ~s.append THEN ERROR StreamError[s, StreamAccess];
    -- extend the file (the first transfer flushes the buffer)
    IF s.char > 0 THEN [] ← TransferPages[s, NIL, 1, out, FALSE];
    WHILE (d ← p - s.page) # 0 DO
      [] ← TransferPages[s, NIL, MIN[d, bite], out, FALSE]; ENDLOOP;
    RETURN
    END;

  -- StreamPosition Manipulation


  GetPosition: PROCEDURE [stream: StreamHandle] RETURNS [StreamPosition] =
    BEGIN
    WITH s: stream SELECT FROM
      Disk =>
	BEGIN -- make sure we're not at end of page
	Cleanup[@s, FALSE]; -- don't flush
	RETURN[Pos[@s] + InlineDefs.LongMult[s.page - 1, CharsPerPage]];
	END;
      ENDCASE => SIGNAL StreamError[@s, StreamType];
    RETURN[0]
    END;

  SetPosition: PROCEDURE [stream: StreamHandle, pos: StreamPosition] =
    BEGIN
    page, byte: CARDINAL;
    WITH s: stream SELECT FROM
      Disk =>
	BEGIN
	[quotient: page, remainder: byte] ← InlineDefs.LongDivMod[
	  pos, CharsPerPage];
	IF page + 1 # s.page THEN PositionPage[@s, page + 1];
	PositionByte[@s, byte, FALSE];
	END;
      ENDCASE => SIGNAL StreamError[@s, StreamType];
    RETURN
    END;

  -- StreamIndex Manipulation


  GetIndex: PROCEDURE [stream: StreamHandle] RETURNS [StreamIndex] =
    BEGIN
    WITH s: stream SELECT FROM
      Disk =>
	BEGIN -- make sure we're not at end of page
	Cleanup[@s, FALSE]; -- don't flush
	RETURN[StreamIndex[s.page - 1, Pos[@s]]];
	END;
      ENDCASE => SIGNAL StreamError[@s, StreamType];
    RETURN[StreamIndex[0, 0]]
    END;

  SetIndex: PROCEDURE [stream: StreamHandle, index: StreamIndex] =
    BEGIN
    WITH s: stream SELECT FROM
      Disk =>
	BEGIN
	index ← NormalizeIndex[index];
	IF index.page + 1 # s.page THEN PositionPage[@s, index.page + 1];
	PositionByte[@s, index.byte, FALSE];
	END;
      ENDCASE => SIGNAL StreamError[@s, StreamType];
    RETURN
    END;

  NormalizeIndex: PROCEDURE [index: StreamIndex] RETURNS [StreamIndex] =
    BEGIN
    IF index.byte >= CharsPerPage THEN
      BEGIN
      index.page ← index.page + index.byte/CharsPerPage;
      index.byte ← index.byte MOD CharsPerPage;
      END;
    RETURN[index]
    END;

  ModifyIndex: PROCEDURE [index: StreamIndex, change: INTEGER]
    RETURNS [StreamIndex] =
    BEGIN OPEN AltoDefs;
    delta: CARDINAL = ABS[change];
    pages: CARDINAL ← delta/CharsPerPage;
    bytes: CARDINAL ← delta MOD CharsPerPage;
    index ← NormalizeIndex[index];
    SELECT change FROM
      > 0 =>
	BEGIN
	bytes ← index.byte + bytes;
	IF bytes >= CharsPerPage THEN
	  BEGIN bytes ← bytes - CharsPerPage; pages ← pages + 1 END;
	pages ← index.page + pages;
	END;
      = 0 => RETURN[index];
      < 0 =>
	BEGIN
	IF bytes <= index.byte THEN bytes ← index.byte - bytes
	ELSE
	  BEGIN bytes ← index.byte + CharsPerPage - bytes; pages ← pages + 1 END;
	IF pages <= index.page THEN pages ← index.page - pages
	ELSE RETURN[[0, 0]];
	END;
      ENDCASE;
    RETURN[[pages, bytes]];
    END;

  --  procedures to test for equality of stream indexes

  EqualIndex: PROCEDURE [i1, i2: StreamIndex] RETURNS [BOOLEAN] =
    BEGIN i1 ← NormalizeIndex[i1]; i2 ← NormalizeIndex[i2]; RETURN[i1 = i2]; END;

  GrEqualIndex: PROCEDURE [i1, i2: StreamIndex] RETURNS [BOOLEAN] =
    BEGIN RETURN[EqualIndex[i1, i2] OR GrIndex[i1, i2]]; END;

  GrIndex: PROCEDURE [i1, i2: StreamIndex] RETURNS [BOOLEAN] =
    BEGIN
    i1 ← NormalizeIndex[i1];
    i2 ← NormalizeIndex[i2];
    RETURN[i1.page > i2.page OR (i1.page = i2.page AND i1.byte > i2.byte)];
    END;

  GetFA: PROCEDURE [stream: StreamHandle, fa: POINTER TO FA] =
    BEGIN
    WITH s: stream SELECT FROM
      Disk =>
	BEGIN -- make sure not at end of a page
	Cleanup[@s, FALSE]; -- don't flush
	fa↑ ← FA[s.das[current], s.page, Pos[@s]];
	END;
      ENDCASE => SIGNAL StreamError[@s, StreamType];
    RETURN
    END;

  FileLength: PROCEDURE [stream: StreamHandle] RETURNS [StreamIndex] =
    BEGIN
    fa: FA;
    WITH s: stream SELECT FROM
      Disk =>
	BEGIN
	SegmentDefs.GetFileLength[s.file, @fa];
	Jump[@s, @fa, MaxFilePage];
	SegmentDefs.UpdateFileLength[s.file, @fa];
	RETURN[GetIndex[@s]];
	END;
      ENDCASE => SIGNAL StreamError[@s, StreamType];
    RETURN[StreamIndex[0, 0]]
    END;

  JumpToFA: PROCEDURE [stream: StreamHandle, fa: POINTER TO FA] =
    BEGIN
    WITH s: stream SELECT FROM
      Disk =>
	BEGIN
	Jump[@s, fa, fa.page];
	IF fa.page # s.page OR fa.byte # Pos[@s] THEN
	  SIGNAL StreamError[@s, StreamEnd];
	END;
      ENDCASE => SIGNAL StreamError[@s, StreamType];
    RETURN
    END;


  END..