MODULE WrapStream; (***************************************************************************) (* Copyright (C) Olivetti 1989 *) (* All Rights reserved *) (* *) (* Use and copy of this software and preparation of derivative works based *) (* upon this software are permitted to any person, provided this same *) (* copyright notice and the following Olivetti warranty disclaimer are *) (* included in any copy of the software or any modification thereof or *) (* derivative work therefrom made by any person. *) (* *) (* This software is made available AS IS and Olivetti disclaims all *) (* warranties with respect to this software, whether expressed or implied *) (* under any law, including all implied warranties of merchantibility and *) (* fitness for any purpose. In no event shall Olivetti be liable for any *) (* damages whatsoever resulting from loss of use, data or profits or *) (* otherwise arising out of or in connection with the use or performance *) (* of this software. *) (***************************************************************************) IMPORT Text, TextExtras; IMPORT IO, IO_impl; REVEAL T = IO.Stream BRANDED OBJECT backingStream: IO.Stream; margin: CARDINAL; breakChars: Text.T; anyBreak: BOOLEAN; eol, bol: Text.T; closeBackingStream: BOOLEAN; charsInLine: CARDINAL := 0; wordBuffer: REF ARRAY OF CHAR := NIL; high: CARDINAL := 0; doWrap: BOOLEAN := TRUE; OVERRIDES (* WrapStream specific hidden methods *) implFlush := Flush; implFill := Fill; implSeek := Seek; implTruncate := Truncate; implClose := Close; implDescribeError := DescribeError; implRecover := Recover; END; (*METHOD*) PROCEDURE Flush(s: T; READONLY chars: ARRAY OF CHAR): BOOLEAN RAISES {} = BEGIN (* WrapStream specific flush code *) RETURN FlushEOF(s, chars, FALSE); END Flush; (*METHOD*) PROCEDURE Fill(s: T; VAR chars: ARRAY OF CHAR): INTEGER RAISES {} = BEGIN (* WrapStream specific fill code *) RETURN IO_impl.FillFailed; END Fill; (*METHOD*) PROCEDURE Seek(s: T; pos: CARDINAL): BOOLEAN RAISES {} = BEGIN (* WrapStream specific seek code *) RETURN FALSE; END Seek; (*METHOD*) PROCEDURE Truncate(s: T; pos: CARDINAL): BOOLEAN RAISES {} = BEGIN (* WrapStream specific truncate code *) RETURN FALSE; END Truncate; (*METHOD*) PROCEDURE Close(s: T): BOOLEAN RAISES {} = VAR bug := NEW(REF ARRAY OF CHAR, 0); BEGIN (* WrapStream specific close code *) IF FlushEOF(s, bug^, TRUE) THEN IF s.closeBackingStream THEN TRY IO.Close(s.backingStream); EXCEPT IO.Error => RETURN FALSE; END; (* try io.close *) END; (* if closeBackingStream *) RETURN TRUE; ELSE RETURN FALSE; END; (* if flusheof *) END Close; PROCEDURE DescribeError(s: T): Text.T RAISES {} = BEGIN (* WrapStream specific error message code *) RETURN NIL; END DescribeError; (*METHOD*) PROCEDURE Recover(s: T; VAR offset, length: CARDINAL): BOOLEAN RAISES {} = BEGIN (* WrapStream specific error recovery code *) RETURN FALSE; END Recover; (*PUBLIC*) PROCEDURE Open( backingStream: IO.Stream; margin: CARDINAL := 80; breakChars: Text.T := " \t"; eol: Text.T := ""; bol: Text.T := ""; openMode: IO.OpenMode := IO.OpenMode.Write; closeBackingStream: BOOLEAN := FALSE) : T RAISES {IO.Error} = (* Open a stream which is a wrapped version of backingStream. Margin is the * maximum line length of the wrapped string. BreakChars is the collection * of characters at which the line will be wrapped. If it is empty, the line * will wrap at any character. eol and bol are the sequences appended and * prepended respectively when a new line break is created. openMode is the * mode of the stream, and should be the same as the mode of backingStream. * If closeBackingStream then backingStream will be closed when the wrap stream * is closed, otherwise not. *) VAR s: T; BEGIN IO_impl.ValidityCheck(backingStream); s := NEW(T); s.backingStream := backingStream; s.margin := margin - Text.Length(eol) - Text.Length(bol); s.breakChars := breakChars; s.anyBreak := Text.Length(breakChars) = 0; s.eol := eol; s.bol := bol; s.closeBackingStream := closeBackingStream; s.wordBuffer := NEW(REF ARRAY OF CHAR, s.margin); (* set up buffer, name etc. *) IO_impl.Init( s := s, buffer := NEW(REF ARRAY OF CHAR, 1), (* unbuffered *) length := IO.UnknownLength, mode := openMode, properties := IO.PropertySet{}, name := IO.Name(backingStream) & "-wrap"); RETURN s; END Open; (* maybe some stream class specific operations *) (*PUBLIC*) PROCEDURE Set( s: T; doWrap: BOOLEAN) : BOOLEAN = (* Turn wrapping on (doWrap is TRUE) or off (doWrap is false) for the stream. * Returns the old value of wrapping. When initially opened, a wrap stream * has wrapping on. *) VAR oldDoWrap: BOOLEAN := s.doWrap; BEGIN IO_impl.ValidityCheck(s); s.doWrap := doWrap; RETURN oldDoWrap; END Set; (*PRIVATE*) PROCEDURE FlushEOF( s: T; READONLY chars: ARRAY OF CHAR; atEOF: BOOLEAN) : BOOLEAN RAISES {} = (* Write out the buffer (to backingStream) * If it is not a breakChar, then we add it to the wordBuffer. If it * is a breakChar, then wordBuffer contains a complete word, and we * write it out (and the breakChar). Just before writing out the word, * we check whether we need to wrap or not. *) VAR bs: IO.Stream := s.backingStream; BEGIN IO_impl.ValidityCheck(s); TRY PositionStream(s); (* any seeks *) FOR x := FIRST(chars) TO LAST(chars) DO VAR ch: CHAR := chars[x]; BEGIN (* first, make sure there is room in the buffer. Allocate a bigger * one if necessary. *) IF s.high >= LAST(s.wordBuffer^) THEN VAR newbuf := NEW(REF ARRAY OF CHAR, 2 * LAST(s.wordBuffer^)); BEGIN FOR i := FIRST(s.wordBuffer^) TO LAST(s.wordBuffer^) DO newbuf[i] := s.wordBuffer[i]; END; s.wordBuffer := newbuf; END; (* var newbuf *) END; (* buffer ovfl, allocate bigger one *) IF s.anyBreak OR (ch = '\n') OR atEOF OR IsBreakChar(s, ch) THEN (* break char, write out wordBuffer *) IF s.doWrap AND (s.charsInLine + s.high > s.margin) AND NOT ((ch = '\n') AND (s.high = 0)) THEN (* wordBuffer doesn't fit, do line break * NOTE: don't do line break just before a \n with * nothing in the word buffer. *) IO.PutText(bs, s.eol); IO.Put(bs, '\n'); IO.PutText(bs, s.bol); s.charsInLine := Text.Length(s.bol); END; (* line break *) (* now, write out word *) IF s.high > 0 THEN (* otherwise, array bounds problem *) FOR i := 0 TO s.high-1 DO IO.Put(bs, s.wordBuffer[i]); END; END; INC(s.charsInLine, s.high); s.high := 0; (* and write out break char *) IF NOT atEOF THEN IO.Put(bs, ch); END; IF ch = '\n' THEN s.charsInLine := 0; ELSE INC(s.charsInLine); END; ELSE (* not break char *) s.wordBuffer[s.high] := ch; INC(s.high); END; (* if break char *) END; (* var ch *) END; (* for x *) RETURN TRUE; EXCEPT IO.Error => RETURN FALSE; END; END FlushEOF; (*PRIVATE*) PROCEDURE PositionStream(s: T) RAISES{}= BEGIN END PositionStream; (*PRIVATE*) PROCEDURE IsBreakChar(s: T; ch: CHAR): BOOLEAN RAISES{}= VAR bogus: CARDINAL; BEGIN bogus := 0; RETURN TextExtras.FindChar(s.breakChars, ch, bogus); END IsBreakChar; BEGIN END WrapStream.