-- PPRopeFileImpl.mesa
-- last edit by Russ Atkinson, 10-Mar-82 10:23:26

DIRECTORY
  IOStream USING [GetBlock, GetChar, GetLength, Handle, SetIndex],
  Inline USING [LowHalf, HighHalf],
  Mopcodes USING [zME, zMXD],
  PPRopeFile,
  Rope USING [ActionType, FromProc, MakeRope, Ref];
  
PPRopeFileImpl: MONITOR
  LOCKS handle USING handle: Handle
  IMPORTS IOStream, Inline, Rope
  EXPORTS PPRopeFile
  = BEGIN OPEN Inline;
  
  Int: TYPE = LONG INTEGER;
  
  RopeRef: TYPE = Rope.Ref;
  
  Handle: TYPE = REF RopeFileRep;

  AcquireLock: PROC [h: Handle] RETURNS [BOOLEAN] = MACHINE CODE {
    -- returns TRUE if lock acquired, FALSE if not
    Mopcodes.zME};
  
  ReleaseLock: PROC [h: Handle] = MACHINE CODE {
    Mopcodes.zMXD};
   
  RopeFileRep: PUBLIC TYPE = MONITORED RECORD
    [file: IOStream.Handle,
     bufSize: NAT,
     data: SEQUENCE buffers: NAT OF Buf];

  Buf: TYPE = REF BufRec;       
  BufRec: TYPE = RECORD [start: Int, len: NAT, text: REF TEXT];
  
  Create: PUBLIC PROC
      [file: IOStream.Handle, bufSize,buffers: NAT, useMap: BOOLEAN]
      RETURNS [handle: Handle, rope: RopeRef] = {
    bytes: Int ← 0;
    IF buffers = 0 OR buffers > 64 OR file = NIL THEN ERROR;
    IF bufSize < 64 THEN bufSize ← 64;
    file.SetIndex[0];
    bytes ← file.GetLength[];
    IF bytes <= LONG[buffers]*LONG[bufSize] THEN
       {get: PROC RETURNS [CHARACTER] = {
          RETURN [file.GetChar[]]};
	rope ← Rope.FromProc[bytes, get];
	handle ← NEW[RopeFileRep[0]];
	handle.bufSize ← 0;
	RETURN};
    handle ← NEW[RopeFileRep[buffers]];
    handle.bufSize ← bufSize;
    handle.file ← file;
    FOR i: NAT IN [0..buffers) DO
      handle[i] ← NEW[BufRec ← [start: 0, len: 0, text: NEW[TEXT[bufSize]]]];
      ENDLOOP;
    rope ← Rope.MakeRope
             [handle,
              IOStream.GetLength[file],
              MyFetch,
              IF useMap THEN MyMap ELSE NIL];
    };
    
  Destroy: PUBLIC ENTRY PROC [handle: Handle] = {
    IF handle = NIL THEN RETURN;
    handle.file ← NIL;
    handle.bufSize ← 0;
    FOR i: NAT IN [0..handle.buffers) DO
	handle[i].len ← 0;
	handle[i].text ← NIL;
        ENDLOOP;
    };

  GetFile: PUBLIC PROC [handle: Handle] RETURNS [IOStream.Handle] = {
    RETURN [handle.file]};
    
  MyFetch: PROC [ref: REF, index: Int] RETURNS [CHARACTER] = {
    handle: Handle = NARROW[ref];
    last: NAT = handle.buffers-1;
    i: NAT ← 0;
    IF handle.file = NIL THEN ERROR;
    UNTIL AcquireLock[handle] DO ENDLOOP;
    DO
      buf: Buf ← handle[i];
      delta: Int ← index - buf.start;
      SELECT TRUE FROM
        HighHalf[delta] = 0 AND LowHalf[delta] < buf.len => {};
	i < last => {i ← i+1; LOOP};
        ENDCASE => {
          -- need to swap in new buffer
	  length: Int ← handle.bufSize;
	  start: Int = index - (delta ← index MOD length);
	  IOStream.SetIndex[handle.file, start];
	  length ← IOStream.GetBlock
	             [handle.file, buf.text, 0, LowHalf[length]];
	  IF length = 0 THEN {ReleaseLock[handle]; ERROR};
	  buf.start ← start;
	  buf.len ← LowHalf[length];
	  };
      IF i # 0 THEN {
	 -- swap to speed searches
	 handle[i] ← handle[i-1];
	 handle[i-1] ← buf;
	 };
      {c: CHARACTER ← buf.text[LowHalf[delta]];
       ReleaseLock[handle];
       RETURN [c]};
      ENDLOOP;
    };
          
  MyMap: PROC [base: REF, start,len: Int,
               action: Rope.ActionType] RETURNS [BOOLEAN] = {
    handle: Handle ← NARROW[base];
    last: NAT = handle.buffers-1;
    i: NAT ← 0;
    IF handle.file = NIL THEN ERROR;
    IF len <= 0 THEN RETURN [FALSE];
    UNTIL AcquireLock[handle] DO ENDLOOP;
    DO
      buf: Buf ← handle[i];
      bufStart: Int ← buf.start;
      delta: Int ← start - bufStart;
      SELECT TRUE FROM
        HighHalf[delta] = 0 AND LowHalf[delta] < buf.len => {
	   rem: NAT ← buf.len-LowHalf[delta];
	   chars: CARDINAL ← 0;
	   IF len < rem THEN rem ← LowHalf[len];
	   WHILE chars < rem DO
	     c: CHARACTER ← buf.text[LowHalf[delta]+chars];  -- eval this while locked
	     
	     ReleaseLock[handle];
	     IF action[c] THEN RETURN [TRUE];  -- eval this while unlocked
	     chars ← chars + 1;
	     UNTIL AcquireLock[handle] DO ENDLOOP;

	     IF buf.start # bufStart THEN EXIT;  -- abort & retry if buf changed
	     ENDLOOP;
	   len ← len - chars;  -- update according to the amount done
	   start ← start + chars;
	   IF len > 0 THEN {i ← 0; LOOP};
	   };
	i < last => {
	   -- missed it, unfortunately
	   i ← i+1;
	   LOOP};
        ENDCASE => {
           -- need to swap in new buffer
	   length: NAT ← handle.bufSize;
	   base: Int = start - (start MOD length);
	   IOStream.SetIndex[handle.file, base];
	   length ← IOStream.GetBlock
	              [handle.file, buf.text, 0, LowHalf[length]];
	   IF length = 0 THEN {ReleaseLock[handle]; ERROR};
	   buf.start ← base;
	   buf.len ← LowHalf[length];
	   LOOP;
	   };
      IF i # 0 AND handle[i] = buf THEN {
	 -- swap to speed searches
	 handle[i] ← handle[i-1];
	 handle[i-1] ← buf;
	 };
      ReleaseLock[handle];
      RETURN [FALSE];
      ENDLOOP;
    };
          
  END.