[net.sources.mac] __StreamLib.src

dubois@uwmacc.UUCP (Paul DuBois) (03/17/86)

Program __StreamLib;

(*
    __StreamLib - set of routines for treating TEXT or WORD files as a
    stream of characters or lines.  To use:
    
    Call InitStream() first.  Call OpenStream() to open a stream.  If it
    returns noErr, a stream was opened ok.  To read characters, call
    StreamGetC().  This returns the next character or -1 on end of stream
    (and closes the stream).  To read lines, call StreamGetS(buf).  This
    fills up the buffer passed to it and returns a pointer to it, or nil
    on end of stream (and closes the stream).  To close a stream early,
    call CloseStream().
    
    There are also corresponding routines for reading TEXT files only, or
    WORD files only.  See below.  These are useful if you want to end up
    with less code linked into your program.

    To compile this library:
    
        Select Batch... from the Options Menu
        Select Compile Same (or Compile...) for Step 1).
        Select Combine Same for Step 2).
        Click OK.

    Version 1.0     11 March 1986

    Paul DuBois
    Wisconsin Regional Primate Research Center
    1220 Capitol Court
    University of Wisconsin-Madison
    Madison, WI  53706
    
    UUCP: {allegra, ihnp4, seismo}!uwvax!uwmacc!dubois

*)
    
Uses
    __OSTraps
    (*$U+*)
    uOSIntf
    uPackIntf
    ;

Link
    __OSTraps
    :;

Const
    bufSiz = 1024;

    wordStream = 1; (* stream is of 'WORD' file *)
    textStream = 2; (* stream is of 'TEXT' file *)

    paraLen = 16;   (* paragraph information 16 bytes long *)

Type
    IArray = Record         (* Information array element *)
        height: integer;
        pagepos: integer;
        parahand: ^^longint;
        StPos: Union
            St: byte;       (* first byte is status *)
            Pos: longint;
        End;
        DataLength: integer;
        formats: Integer;
    End;

Var
    theReply: SFReply;      (* SFGetFile reply record *)
    f: Integer;             (* input file reference number *)
    streamOpen: Boolean;    (* whether stream currently open *)
    streamType: Integer;    (* wordStream or textStream *)

(*  vars needed for TEXT stream only *)

    filBuf: Byte[bufSiz];   (* file buffer *)
    fChars: LongInt;        (* number of chars gotten on last read *)
    fIndex: Integer;        (* current position in filBuf *)

(*  vars needed for WORD stream only *)

    paraBuf: ^^Byte[1];
    infoHand: ^^Iarray[1];
    compressed: Boolean;
    inPara: Boolean;
    nParas: Integer;    (* number of paragraphs *)
    paraNum: Integer;   (* current paragraph number *)
    pIndex: Integer;    (* index into paragraph *)
    pChars: Integer;    (* number of chars extracted from paragraph *)
    firstHalf: Boolean; (* which half of current index char *)
    pLen: Integer;      (* number of chars in paragraph *)
    needNib,            (* For Decompr.: true = 2nd Nibble needed for ascii *)
    nextAsc: boolean;   (* For Decompr.: true = Two Nibbles needed *)
    lastNib: byte;      (* For Decompr.: Holds last nibble *)
    lineLen: Integer;   (* for line wrapping *)


(* -------------------------------- *)
(*  miscellaneous utility routines  *)
(* -------------------------------- *)

Proc _ffRead (f: Integer; b: PtrB; amount: LongInt);
{ 
    amount := FSRead(f, @amount, b);
};

Proc _fMoveTo (f: Integer; amt: LongInt);
Var
    result: Integer;
{
    result := SetFPos (f, fsFromStart, amt);
};


(* --------------------------------- *)
(*  stream init/open/close routines  *)
(* --------------------------------- *)


(*
    InitStream - must be called before you do anything else.

    _FSOpen - like FSOpen (in __OSTraps), but has open mode parameter.

    _OpenStream - Open a file for stream I/O.  numTypes is either 1 or 2,
        and typeList is either 'TEXT', 'WORD', or 'TEXTWORD'.
        Return noErr if file opened OK, or fnOpnErr if not.
        if OK, set streamType to wordStream or textStream,
        according to the type of the opened file, and set
        fileOpen true.

    OpenTextSream - open 'TEXT' file stream.  Returns:
        noErr - file open OK
        fnOpnErr - file not open

    OpenWordSream - open 'WORD' file stream.  Returns:
        noErr - file open OK
        fnOpnErr - file not open
        mFulErr - couldn't get memory to read paragraph info into memory.
            file not open.

    OpenSream - open 'TEXT' or 'WORD' stream.  Return values same as for
        OpenWordS.  Returns stream type in function argument sType.

    _WordStreamInit does special handling necessary for 'WORD' stream:
    Open the file, advance 252 + 12 bytes (to start of main document info
    + offset of Information Array.  Then read position and length of Info
    Array, move to it, read it in, and calculate number of paragraphs.
    (16 bytes info per paragraph.)  _TextStreamInit does TEXT stream
    specific initialization.
*)

Proc InitStream ();
{
    streamOpen := false;
    lineLen := 65;
    paraBuf := nil;
    infoHand := nil;
};


Proc CloseStream ();
Var
    result : OSErr;
{
    if streamOpen then
    {
        result := FSClose (f);
        streamOpen := false;
        if streamType = wordStream then
        {
            if paraBuf <> nil then DisposHandle (paraBuf);
            if infoHand <> nil then DisposHandle (infoHand);
        };
    };
};


Func _FSOpen (fName: PtrB; vRefNum: Integer;
              refNum: ^Integer; mode: Integer): OSErr;
Var
    p: ParamBlockRec;
{
    p.ioNamePtr := fName;
    p.ioVRefNum := vRefNum;
    p.ioPermssn := mode;
    p.ioVersNum := 0;
    p.ioMisc := 0;
    _FSOpen := PBOpen (p, false);
    refnum^ := p.ioRefNum;
};


Func _OpenStream (numTypes: Integer; typeList: OSType): OSErr;
{
    CloseStream ();         (* close any currently open stream *)
    _OpenStream := fnOpnErr;
    streamOpen := false;
    
    ToolBox ($A9EA, 100, 70, "", nil, numTypes, typeList, nil, @theReply, 2);
    if theReply.good then
    {
        if _FSOpen(theReply.fName, theReply.vRefNum, @f, fsRdPerm) = noErr then
        {
            streamType := wordStream;
            if theReply.fType = PtrL (" TEXT"+2)^ then
                streamType := textStream;
            streamOpen := true;
            _OpenStream := noErr;
        };
    };
};


Proc _TextStreamInit ();
{
    fChars := 0;    (* set these to trigger a read on the first *)
    fIndex := 0;    (* call to TextStreamGetC() *)
};


Func _WordStreamInit (): OSErr;
Type
    DocVars = Record         
        IApos: Longint;
        IAlength: Integer;
    End;
Var     
  docVars: DocVars;
{
    paraBuf := NewHandle (0L);  (* Will be used for reading in paragraphs *)
    _fmoveto (f, 264L);       (* 252 + 12 *)
    _ffRead (f, docVars, LongInt (SizeOf (DocVars)));
    _fMoveTo (f, docVars.IAPos);
    infoHand := NewHandle (Longint (docVars.IALength));
    if infoHand = nil then
    {
        CloseStream ();
        _WordStreamInit := mFulErr;
    }
    else
    {
        HLock (InfoHand);
        _ffRead (f, infoHand^, Longint (docVars.IALength));
        HUnlock (InfoHand);
       
        nParas := docVars.IALength/paraLen;
        paraNum := -1;
        inPara := false;            (* not in any paragraph yet *)
        _WordStreamInit := noErr;
    };
};


Func OpenTextStream (): OSErr;
{
    OpenTextStream := _OpenStream (1, " TEXT"+2);
    _TextStreamInit ();
};


Func OpenWordStream (): OSErr;
Var
    result: OSErr;
{
    OpenWordStream := fnOpnErr;
    if _OpenStream (1, " WORD"+2) = noErr then
        OpenWordStream := _WordStreamInit ();
};


Func OpenStream (): OSErr;
{
    OpenStream := fnOpnErr;
    if _OpenStream (2, " TEXTWORD"+2) = noErr then
    {
        OpenStream := noErr;
        case streamType of
            wordStream: OpenStream := _WordStreamInit ();
            textStream: _TextStreamInit ();
        end;
    };
};


Proc GetStreamInfo (reply: SFReply);
{
    reply := theReply;
};


(*
    'Get a character' routines
    
    TextStreamGetC - get character from 'TEXT' stream.
    WordStreamGetC - get character from 'WORD' stream.
    StreamGetC - get character from stream.
*)

Func TextStreamGetC (): Integer;
Var
    err: OSErr;
{
    TextStreamGetC := -1;
    if streamOpen = false then return;
    if fIndex >= fChars then    (* need to read in a new block *)
    {
        fChars := bufSiz;
        err := FSRead (f, @fChars, filBuf);
        if fChars = 0 then
        {
            CloseStream ();
            return;
        };
        fIndex := 0;
    };
    TextStreamGetC := filBuf[fIndex];
    ++fIndex;
};


(*
    _Decompress takes a nibble at a time of compressed text.  If more
    nibbles are needed to complete the next character, return -1, else
    returns the character.
*)
       
Func _Decompress (b: Byte): Integer;
{
    _Decompress := -1;
    if needNib then             (* Low half of ascii nibble is needed. *)
    {
        needNib := false;
        _Decompress := (lastNib or b);  (* Put the two halves together *)
    }
    else if nextasc then        (* Two nibbles are needed *)
    {
        nextAsc := false;
        lastNib := b << 4;      (* Save this one as the high nibble *)
        needNib := true;        (* Need one more nibble *)
    }
    else if b = 15 then         (* Nibble of 15 means the next char is ascii *)
        nextAsc := true
    else          (* Add the nibble value to the English decompression *)
                  (* key (saved as Resource Type "STR " 700 in file)   *)
                  (* to get the proper character *)
    {
        _Decompress := PtrB (++b + " etnroaisdlhcfp")^; 
    };
};


Func WordStreamGetC (): Integer;   (* return -1 on EOF *)
Var
    c: Integer;
    offset: LongInt;
{
    WordStreamGetC := -1;
    if streamOpen = false then return;
    if inPara = false then      (* must read in next paragraph *)
    {
        loop (,,,)
        {
            if ++paraNum >= nparas then
            {
                CloseStream ();
                return;
            };
            if infoHand^^[paranum].height <= 0 then continue;
(*
    offset will contain the file offset to this paragraph's data.  must
    mask the high byte.  Move to the paragraph, get its length, make
    the pointer big enough, and read it in.  (skip to next para if this
    one is empty, though.)
    compressed will be set true if the paragraph is compressed.
*)
            offset := infoHand^^[paranum].stpos.pos and $00FFFFFF;
            _fMoveTo (f, offset);
            _ffRead (f, @plen, LongInt (SizeOf (Integer))); (* get length *)
            if plen = 0 then continue;
            SetHandleSize (paraBuf, LongInt (plen));    (* make big enough *)
            if MemError () <> noErr then
            {
                paranum := nparas;  (* force close and exit of loop *)
                continue;
            };
            _ffRead (f, paraBuf^, LongInt (plen));
            compressed := (infoHand^^[paranum].stpos.st >> 3) and 1;
            inPara := true;
            nextAsc := false;
            needNib := false;
            pIndex := 0;        (* index into current paragraph *)
            pChars := 0;        (* chars extracted from current paragraph *)
            firstHalf := true;  (* use first half of current index char *)
            break;
        };
    };
(*
    At this point, know eitherthat we have a new non-empty paragraph, or
    are still in the previous one.
*)
    if !compressed then  (* uncompressed *)
    {
        c := paraBuf^^[pchars];
    }
    else
    {
        loop (,,, c <> -1)
        {
            c := paraBuf^^[pIndex];
            if firstHalf then
            {
                c := _Decompress (Byte (c >> 4));
            }
            else
            {
                c := _Decompress (Byte (c and $0f));
                ++pIndex;   (* go to next char at next index *)
            };
            firstHalf := !firstHalf;
        };
    };
    if ++pChars >= pLen then    (* see if need new paragraph next time *)
        inPara := false;
    WordStreamGetC := c;
};


Func StreamGetC (): Integer;
{
    case streamType of
        wordStream: StreamGetC := WordStreamGetC ();
        textStream: StreamGetC := TextStreamGetC ();
    end;
};


(*
    'Get a string' routines
    
    TextStreamGetS - get string from 'TEXT' stream.
    WordStreamGetS - get string from 'WORD' stream.
    StreamGetS - get string from stream.

    All return nil if no string obtained, otherwise a pointer to the argument.
*)


Func TextStreamGetS (s: StringPtr): StringPtr;
Var
    c: Integer;
{
    TextStreamGetS := nil;
    if !streamOpen then return;
    s[0] := 0;  (* clear string *)
    loop (,,,)
    {
        c := TextStreamGetC ();
        if c = -1 then
            break;
        TextStreamGetS := s;   (* got something, so NextLine succeeds *)
        if c = 13 then break;
        s[++s[0]] := c;       (* add char to end *)
    };
};


Func WordStreamGetS (s: StringPtr): StringPtr;
Var
    c: Integer;
{
    WordStreamGetS := nil;
    if !streamOpen then return;
    s[0] := 0;  (* clear string *)
    loop (,,,)
    {
        c := WordStreamGetC ();
        if c = -1 then
            break;
        WordStreamGetS := s;   (* got something, so NextLine succeeds *)
        if (c = 13) or ((s[0] > lineLen) and (c = ' ')) then
            break;
        s[++s[0]] := c;       (* add char to end *)
    };
};


Func StreamGetS (s: StringPtr): StringPtr;
{
    case streamType of
        wordStream: StreamGetS := WordStreamGetS (s);
        textStream: StreamGetS := TextStreamGetS (s);
    end;
};


(*
    Set wrap length (number of chars after which point the line will be
    broken at the next space if a carriage return is not seen first).  This
    only affects TextStreamGetS (or StreamGetS when the current stream is
    a WORD stream).
*)

Proc SetLineLen (len: Integer);
{
    lineLen := len;
};


-- 
                                                                    |
Paul DuBois     {allegra,ihnp4,seismo}!uwvax!uwmacc!dubois        --+--
                                                                    |
                                                                    |