(* Ulm's Oberon Library
   Copyright (C) 1989-1994 by University of Ulm, SAI, D-89069 Ulm, Germany
   ----------------------------------------------------------------------------
   Ulm's Oberon Library is free software; you can redistribute it
   and/or modify it under the terms of the GNU Library General Public
   License as published by the Free Software Foundation; either version
   2 of the License, or (at your option) any later version.

   Ulm's Oberon Library is distributed in the hope that it will be
   useful, but WITHOUT ANY WARRANTY; without even the implied warranty
   of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public
   License along with this library; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
   ----------------------------------------------------------------------------
   E-mail contact: oberon@mathematik.uni-ulm.de
   ----------------------------------------------------------------------------
   $Id: Texts.om,v 1.3 1995/03/17 19:37:52 borchert Exp $
   ----------------------------------------------------------------------------
   $Log: Texts.om,v $
   Revision 1.3  1995/03/17 19:37:52  borchert
   - error events added
   - some fixes because streams are now an extension of Services.Object

   Revision 1.2  1994/07/18  14:21:13  borchert
   buggy free buffer handling removed

   Revision 1.1  1994/02/22  20:11:07  borchert
   Initial revision

   ----------------------------------------------------------------------------
   AFB 8/89
   ----------------------------------------------------------------------------
*)

MODULE ulmTexts;

   (* management of texts (dynamic strings) *)

   IMPORT Events := ulmEvents, Priorities := ulmPriorities, RelatedEvents := ulmRelatedEvents, Services := ulmServices, Streams := ulmStreams;

   CONST
      bufsize = 512;

   TYPE
      Count = Streams.Count;
      Address = Streams.Address;
      Byte = Streams.Byte;
      Stream = Streams.Stream;
      Whence = Streams.Whence;

      BufferLink = POINTER TO Buffer;
      Buffer =
	 RECORD
	    cont: ARRAY bufsize OF Byte;
	    next: BufferLink;
	 END;
      Text* = POINTER TO TextRec;
      TextRec* =
	 RECORD
	    (Streams.StreamRec)
	    pos: Count;			(* current position *)
	    len: Count;			(* total length in bytes *)
	    cnt: Count;			(* number of buffers *)
	    head, tail: BufferLink;	(* list of buffers *)
	 END;

   VAR
      if: Streams.Interface;		(* parameters of Streams.Init *)
      caps: Streams.CapabilitySet;
      type: Services.Type; (* Texts.Text *)

   (* === error handling =============================================== *)

   CONST
      posBeyondCurrentLength* = 0;
      invalidTruncPos* = 1;
      errors* = 2;
   TYPE
      ErrorEvent* = POINTER TO ErrorEventRec;
      ErrorEventRec* =
	 RECORD
	    (Events.EventRec)
	    errorcode*: SHORTINT;
	 END;
   VAR
      errormsg*: ARRAY errors OF Events.Message;
      error*: Events.EventType;

   PROCEDURE InitErrorHandling;
   BEGIN
      Events.Define(error);
      Events.SetPriority(error, Priorities.liberrors);
      errormsg[posBeyondCurrentLength] :=
	 "desired position is beyond the current length";
      errormsg[invalidTruncPos] := "invalid trunc position";
   END InitErrorHandling;

   PROCEDURE Error(s: Streams.Stream; code: SHORTINT);
      VAR
	 event: ErrorEvent;
   BEGIN
      NEW(event);
      event.type := error;
      event.message := errormsg[code];
      event.errorcode := code;
      RelatedEvents.Raise(s, event);
   END Error;

   (* === buffer management ============================================ *)

   PROCEDURE Access(t: Text; VAR buffer: BufferLink);
      VAR i: Count;
   BEGIN
      IF t.pos >= bufsize * t.cnt THEN
	 NEW(buffer);
	 buffer.next := NIL;
	 IF t.tail = NIL THEN
	    t.head := buffer;
	 ELSE
	    t.tail.next := buffer;
	 END;
	 t.tail := buffer;
	 INC(t.cnt);
      ELSE
	 buffer := t.head;
	 i := 0;
	 WHILE i < t.pos DIV bufsize DO
	    buffer := buffer.next;
	    INC(i);
	 END;
      END;
   END Access;

   (* === interface procedures ========================================= *)

   PROCEDURE BufRead(s: Stream; VAR buf: ARRAY OF Byte;
		     off, cnt: Count) : Count;
      VAR
	 buffer: BufferLink;
	 index: Count;
	 i, count: Count;
   BEGIN
      WITH s: Text DO
	 count := cnt;
	 IF count > s.len - s.pos THEN
	    count := s.len - s.pos;
	 END;
	 IF count > 0 THEN
	    Access(s, buffer); index := s.pos MOD bufsize;
	    i := off;
	    WHILE i < off + count DO
	       buf[i] := buffer.cont[index];
	       INC(i); INC(index); INC(s.pos);
	       IF index MOD bufsize = 0 THEN
		  Access(s, buffer); index := 0;
	       END;
	    END;
	 END;
      END;
      RETURN count
   END BufRead;

   PROCEDURE BufWrite(s: Stream; VAR buf: ARRAY OF Byte;
		      off, cnt: Count) : Count;
      VAR
	 buffer: BufferLink;
	 index: Count;
	 i: Count;
   BEGIN
      WITH s: Text DO
	 IF cnt > 0 THEN
	    Access(s, buffer); index := s.pos MOD bufsize;
	    i := off;
	    WHILE i < off + cnt DO
	       buffer.cont[index] := buf[i];
	       INC(i); INC(index); INC(s.pos);
	       IF s.pos > s.len THEN
		  s.len := s.pos;
	       END;
	       IF index MOD bufsize = 0 THEN
		  Access(s, buffer); index := 0;
	       END;
	    END;
	 END;
      END;
      RETURN cnt
   END BufWrite;

   PROCEDURE Read(s: Stream; VAR byte: Byte) : BOOLEAN;
      VAR
	 buffer: BufferLink;
   BEGIN
      WITH s: Text DO
	 IF s.pos < s.len THEN
	    Access(s, buffer);
	    byte := buffer.cont[s.pos MOD bufsize];
	    INC(s.pos);
	    RETURN TRUE
	 ELSE
	    RETURN FALSE
	 END;
      END;
   END Read;

   PROCEDURE Write(s: Stream; byte: Byte) : BOOLEAN;
      VAR
	 buffer: BufferLink;
   BEGIN
      WITH s: Text DO
	 Access(s, buffer);
	 buffer.cont[s.pos MOD bufsize] := byte;
	 INC(s.pos);
	 IF s.pos > s.len THEN
	    s.len := s.pos;
	 END;
	 RETURN TRUE
      END;
   END Write;

   PROCEDURE Seek(s: Stream; count: Count; whence: Whence) : BOOLEAN;
      VAR
	 pos: Count;
   BEGIN
      WITH s: Text DO
	 CASE whence OF
	 | Streams.fromStart: pos := count;
	 | Streams.fromPos:   pos := count + s.pos;
	 | Streams.fromEnd:   pos := count + s.len;
	 END;
	 IF (pos >= 0) & (pos <= s.len) THEN
	    s.pos := pos;
	    RETURN TRUE
	 ELSE
	    Error(s, posBeyondCurrentLength);
	    RETURN FALSE (* holes are not permitted *)
	 END;
      END;
   END Seek;

   PROCEDURE Tell(s: Stream; VAR count: Count) : BOOLEAN;
   BEGIN
      count := s(Text).pos;
      RETURN TRUE
   END Tell;

   PROCEDURE Close(s: Stream) : BOOLEAN;
   BEGIN
      WITH s: Text DO
	 s.pos := 0; s.len := 0;
	 IF s.cnt > 0 THEN
	    s.cnt := 0;
	    s.head := NIL; s.tail := NIL;
	 END;
      END;
      RETURN TRUE
   END Close;

   PROCEDURE Trunc(s: Stream; length: Count) : BOOLEAN;
      VAR
	 i: Count;
	 buffer: BufferLink;
   BEGIN
      WITH s: Text DO
	 IF (length >= 0) & (length <= s.len) & (s.pos <= length) THEN
	    IF length DIV bufsize < s.len DIV bufsize THEN
	       (* release truncated buffers *)
	       i := 0; buffer := s.head;
	       WHILE i < length DIV bufsize DO
		  buffer := buffer.next; INC(i);
	       END;
	       s.tail := buffer; s.tail.next := NIL;
	       s.cnt := i;
	    END;
	    s.len := length;
	    RETURN TRUE
	 ELSE
	    Error(s, invalidTruncPos);
	    RETURN FALSE
	 END;
      END;
   END Trunc;

   PROCEDURE Open*(VAR text: Streams.Stream);
      (* for reading and writing *)
      VAR
	 newtext: Text;
   BEGIN
      NEW(newtext);
      Services.Init(newtext, type);
      Streams.Init(newtext, if, caps, Streams.nobuf);
      RelatedEvents.QueueEvents(newtext);
      newtext.pos := 0; newtext.len := 0;
      newtext.cnt := 0; newtext.head := NIL; newtext.tail := NIL;
      text := newtext;
   END Open;

BEGIN
   NEW(if);
   if.bufread := BufRead; if.bufwrite := BufWrite;
   if.read := Read; if.write := Write;
   if.seek := Seek; if.tell := Tell;
   if.trunc := Trunc; if.close := Close;
   caps := {Streams.read, Streams.write, Streams.bufio,
	    Streams.seek, Streams.tell, Streams.trunc, Streams.close};
   Services.CreateType(type, "Texts.Text", "Streams.Stream");
   InitErrorHandling;
END ulmTexts.
