[Back to TEXTFILE SWAG index]  [Back to Main SWAG index]  [Original]

{$A+,B-,D+,E-,F-,I-,L+,N-,O-,R-,S-,V-}
{$M 16384,0,655360}

Unit RLINE;

(*********************************************************************
                   Fast disk file text reading operations.

        Address comments, complaints, suggestions on CompuServe to
                       Don Strenczewilk [72617,132]


    This unit contains a fast reading object designed for high speed
    reading standard ASCII disk files.

    The RLINE unit uses about 600 bytes of your programs code space,
    and 0 data.

    All of RLobject's methods return the result of their operation in
    the RFerror field, except for methods: FFilePos and FClose, which
    have no error codes.  RFerror should be checked after each call to
    one of the methods that set it, because it is re-set with each
    method call.

**********************************************************************)
  Interface
(*********************************************************************)

USES
  DOS;

TYPE
  RFobject = OBJECT
      _Handle  : Word;        { File handle }
      _BufPtr  : Pointer;     { BufOfs, BufSeg}
      _Bpo,                   { Current buffer position }
      _BSize,                 { Buffer size in bytes }
      _BLeft,                 { Bytes left in buffer to scan }
      _NBufs   : Word;        { Number of buffers read. = 0 if none. }
      _TotBytesInBuf : Word;  { Total bytes that were read into current buffer.}
      RFerror : Word;	      { RFobject's IOResult }

      PROCEDURE FOpen(Fn      : STRING;  { Name of file to open. }
		      DBSize  : Word;     { Size of buffer. 512 bytes minimum. }
		      VAR BufP);          { Disk buffer to use. }
      PROCEDURE FClose;
      PROCEDURE FReadLn(VAR S : STRING); { String variable to read next line to. }
      PROCEDURE FRead(VAR Ch  : Char);  { Char variable to read next line to. }
      FUNCTION  FFilePos : LongInt;
      PROCEDURE FSeek(FPo : LongInt);
  END;


  RFextendedP = ^RFextended;
  RFextended = Object(RFobject)
    FileName : string[80];

    CONSTRUCTOR Init(Fn : STRING;	  { Name of file to open. }
		     DBSize : Word;       { Size of buffer. }
		     VAR BufP);           { Address of disk buffer }
    Destructor Done;
    FUNCTION FileSize : LongInt;
    Function RFerrorString : string;
    PROCEDURE Reset;
  END;

TYPE
  BufRec = Record
    Lno : LongInt;  { The first line number in the buffer }
    FP  : LongInt;  { file position of first line in buffer. }
  END;

CONST
  MaxBufs = 8191;

TYPE
  BufferArray = Array[1..MaxBufs] OF BufRec;

{ When FileOfLines is initialized with SizeForBuffer = 256, it can index
  files up to 2,096,896 bytes long.
{ With SizeForBuffer = 4096, it will handle files up to 33,550,336 bytes. }

  FileOfLinesPtr = ^FileOfLines;
  FileOfLines = Object(RFextended)
    TBuf : Pointer;		{ Disk buffer pointer. }
    BufSize : Integer;		{ Disk buffer size. }
    LastLineNum : LongInt;      { Last line number accessed. }
    LastLine : String;          { Last line read. }
    TotalLines : LongInt;       { Total lines in file. }
    BufRay : ^BufferArray;      { Index of buffers for paging. }
    NBuffers : Integer;

    Constructor Init(FN : String;
		     SizeForBuffer : Word);
    Destructor Done;
    PROCEDURE SeekLine(Row : LongInt);
  END;

(*---------------------------------------------------------------------
PROCEDURE RFobject.FOpen

A file must first be successfully opened with a call to FOpen, before any of
the other routines are used.

A buffer must be declared to be passed the FOpen.  There are no restrictions
on the location of the buffer, so it can be a global or local variable, or
allocated with New() or GetMem().


PROCEDURE FOpen(Fn  : STRING;  { Name of file to open. }
		DBSize : Word; { Size of buffer. 512 bytes minimum. }
		VAR BufP);     { Disk buffer to use. }

  If successful:
    Sets RFerror to 0.

  If not successful:
    Sets RFerror to DOS error code if a DOS error occured,
    or error 12 (Invalid File Access Code) if the buffer size is 0.

NOTES:
  The SYSTEM unit FileMode variable is used as the DOS File Access Mode
  passed to DOS function $3D, to open the file.  Actually, the low 3 bits
  are set to zero, specifying Read-Only access, but the high 5 file
  sharing bits are passed.

TRAPS:
  If using a buffer allocated with New() or GetMem(), be sure to use the
  caret after it for the BufP parameter. Ie. RF.FOpen(Fn, BSize, BufP^);

Never call FOpen twice with the same RFobject variable without calling
FCLOSE first.

EXAMPLE:
VAR
  RF : RFobject;
  Buffer : Array[1..2048] of Char;
BEGIN
  System.FileMode := 0;
  RF.FOpen('HELLO.PAS', Sizeof(Buffer), Buffer);
  If RFerror = 0
  THEN Writeln('Success')
  ELSE Writeln('Error: ', i);
...

--------------------------------------------------------------------------
PROCEDURE RFobject.FClose  - When done with the file, it must be closed
			     with a call to FClose:

PROCEDURE FClose;

Closes previously opened RFrec.
Returns nothing.

This procedure attempts to identify whether the file has been previously
opened before it attempts to ask DOS to close it.  It does not attempt to
close the file if:

 a) RF.BSize = 0. PROCEDURE FOpen sets RF.BSize to 0 if DOS open failed.
or
 b) RF.Handle < 5, in which case it would be a standard DOS handle, which
    shouln't be closed.

TRAP: A problem that could occur with this scheme would be if (the file was
never even attempted to be opened by FOpen) AND (the handle = the handle of
a file that is currently opened somewhere else in the program).

----------------------------------------------------------------------
PROCEDURE RFobject.FReadLn

FReadLn - Reads a string of characters up to the next ^M, or
	  the physical end of file, whichever comes first.
	  ^Z is ignored if it occurs at the end of the file.
	  If a ^Z appears before the end of the file, it is passed
	  on to "S".

	  VAR "S", which receives the string, MUST be of TYPE STRING
	  or STRING[255].

	  The maximum length of the string returned to caller is 255
	  characters.  If more than 255 characters are passed in the
	  file before ^M or <EOF>, the remaining characters are
	  discarded.


PROCEDURE FReadLn(VAR S   : STRING); { String variable to read next line to. }

On success:
  Sets RFerror to 0.
  S = next string read from file RF.Handle.
On failure:
  Sets RFerror to DOS error code,
  or $FFFF if End of File

Works like a Turbo Pascal Readln(F, S); except:
    (1) It works only with disk files.
    (2) Only reads type STRING. ie. not integers, words, or any other type.
    (3) It is much faster.
    (4) Doesn't stop when a ^Z is encountered before end of file.  If a ^Z
	is encountered AT the end of file, it is stripped.  Any ^Z's
	encountered before the physical end of the file are passed on
	to the string.
    (5) RFerror is set to $FFFF after calling this if the physical
	end of file is reached.  The value of "S" is invalid when the
	$FFFF end of file result is set.

----------------------------------------------------------------------
PROCEDURE RFobject.FRead - Reads the next character from the file:

PROCEDURE FRead(VAR Ch  : Char);  { Char variable to read next line to. }

Works the same as FReadLn but returns one character instead of a string.
All characters are passed on to Ch except ^Z if it occurs at end of file.
Any ^Z found before the physical end of file is passed on to Ch.

If successful:
  Sets RFerror to 0.
  Ch = next character in the file.

If failed:
  Sets RFerror to either DOS error code,
  or $FFFF if physical End of File

----------------------------------------------------------------------
Function RFobject.FFilePos - Returns current file position for use with FSeek.

FUNCTION FFilePos : LongInt;

Returns current file position. RF must have been previously opened.
If FFilePos is called before FOpen is called successfully, the results
will be meaningless.

----------------------------------------------------------------------
PROCEDURE RFobject.FSeek - Seeks to position FPo in previously opened RF.

PROCEDURE FSeek(FPo : LongInt) : Word;

If successful,
  RFerror is set to 0.

If failed,
  RFerror is set to DOS error code.

To Reset the file, call RFSeek with FPo := 0.  Ie. FSeek(0);

On a normal ^M^J ascii file, FFilePos will most often return the position of
the ^J after a call to FReadLn.  Because FReadLn strips leading ^J's, this
shouldn't be a problem.  But, bear that in mind if using the FFilePos
results for your own untyped file routines.

(****************************************************************************)
Implementation
(****************************************************************************)

{ RFOBJECT ----------------------------------------------------------------}

  {$L RLINE.OBJ}
  PROCEDURE RFobject.FOpen(Fn    : STRING;
			   DBSize : Word;
			   VAR BufP); EXTERNAL;
  PROCEDURE RFobject.FClose; EXTERNAL;
  PROCEDURE RFobject.FReadLn(VAR S : STRING); EXTERNAL;
  PROCEDURE RFobject.FRead(VAR Ch : Char); EXTERNAL;
  PROCEDURE RFobject.FSeek(FPo : LongInt); EXTERNAL;
  FUNCTION  RFobject.FFilePos : LongInt; EXTERNAL;

{ RFEXTENDED --------------------------------------------------------------}

  CONSTRUCTOR RFextended.Init(Fn : STRING;   { Name of file to open. }
			      DBSize : Word; { Size of buffer. }
			      VAR BufP);     { Address of disk buffer }
  BEGIN
    FileName := FExpand(Fn);
    FOpen(Fn, DBSize, BufP);
  END;

  FUNCTION RFextended.FileSize : LongInt;
  VAR
    r : registers;
    Fpos : LongInt;
  BEGIN
    FPos := FFilePos; { save current file position }
    with r do begin
      ax := $4202;
      bx := _handle;
      cx := 0;
      dx := 0;
      msdos(r);
      if flags and fcarry <> 0
      then RFerror := ax
      else FileSize := (longint(dx) shl 16) or ax;
    end;
    _TotBytesInBuf := 0;  { Force FSeek to move file pointer. }
    FSeek(FPos);          { restore current file position }
  END;

  Function RFextended.RFerrorString : string;
    { Converts RFerror to a string. }
  VAR
    S : STRING[80];
  BEGIN
    CASE RFerror OF
      0 : S := 'Success';           { it's not an error. }
      100 : S := 'Attempted to read past End Of File.';
      101 : S := 'Disk write error.';
      102 : S := 'File not assigned.';
      103 : S := 'File not opened.';
      104 : S := 'File not open for input.';

      2 : S := 'File not found.';
      3 : S := 'Path not found.';
      4 : S := 'Too many files opened.';
      5 : S := 'File access denied.';
      6 : S := 'Invalid file handle.';
      $FFFF : S := 'End Of File.'; { special EOF number, unique to FRead and FReadln }
      200 : s := 'Divide by zero.  Buffersize = 0?';
    ELSE BEGIN
	   Str(RFerror, S);
           S := 'IOerror '+S;
         END;
    END;
    RFerrorString := S;
  END;

  PROCEDURE RFextended.Reset;
  BEGIN
    FSeek(0);
  END;

  DESTRUCTOR RFextended.Done;
  BEGIN
    Fclose;
  END;

{ FILEOFLINES -------------------------------------------------------}

  Constructor FileOfLines.Init(FN : string; SizeForBuffer : Word);
  VAR
    F : File;
    L, RamNeeded, FSize : LongInt;
    BufNum : Word;
  BEGIN
    TBuf := nil;
    BufRay := nil;
    LastLineNum := 0;
    LastLine := '';
    TotalLines := 0;

    If MaxAvail > SizeForBuffer			{ create the disk buffer }
    THEN BufSize := SizeForBuffer
    ELSE BufSize := MaxAvail;
    If BufSize >= 256
    Then GetMem(TBuf,BufSize)
    Else Fail;

    FileName := FExpand(Fn);			{ open the file. }
    FOpen(FileName, BufSize, TBuf^);
    IF RFError = 0
    THEN FSize := FileSize;

    IF RFerror <> 0 THEN
      Exit;  { Don't fail so RFerror can be polled in calling routine. }

    NBuffers := ((FSize DIV BufSize) + 1); { allocate ram for bufferarray }
    RamNeeded := NBuffers * SizeOf(BufRec);
    If (MaxAvail < RamNeeded) OR (NBuffers > MaxBufs) THEN BEGIN
      Done;
      Fail;
    END;
    GetMem(BufRay, RamNeeded);

    { Index the file. }
    BufNum := 1;
    With BufRay^[1] Do BEGIN
      Lno := 1;
      FP := 0;
    END;

    FReadLn(LastLine);
    While RFerror = 0 DO BEGIN
      Inc(TotalLines);
      IF (_NBufs > BufNum) AND (BufNum < NBuffers) Then BEGIN
	Inc(BufNum);
	With BufRay^[BufNum] DO BEGIN
	  Lno := Succ(TotalLines);
	  FP := FFilePos;
	END;
      END;
      FReadLn(LastLine);
    END;

    IF RFError = $FFFF { make it to EOF with no problems? }
    THEN Reset;
  END;

  Destructor FileOfLines.Done;
  BEGIN
    If BufRay <> nil Then Freemem(BufRay,NBuffers * SizeOf(BufRec));
    If TBuf <> nil Then FreeMem(TBuf,BufSize);
    BufRay := nil;
    TBuf := nil;
    FClose;
  END;

  PROCEDURE FileOfLines.SeekLine(Row : LongInt);
  { Seeks and reads row and puts in string LastLine }
  VAR
    i : Integer;
  BEGIN
    If Row > TotalLines THEN BEGIN
      RFerror := 100; { Attempt to read past end of file. }
      Exit;
    END;
    IF (Row <> LastLineNum+1) THEN BEGIN
      i := 2;
      While (i <= NBuffers) AND (BufRay^[i].Lno < Row) Do Inc(i);
      Dec(i);
      With BufRay^[i] DO BEGIN
	FSeek(FP);
	IF RFerror = 0 THEN BEGIN
	  FReadLn(LastLine);
	  LastLineNum := Lno;
	END;
      END;
    END;
    While (RFerror = 0) AND (LastLineNum < Row) DO
    BEGIN
      FReadLn(LastLine);
      Inc(LastLineNum);
    END;
  END;

END.

{ ---------------------   RLINE.OBJ needed for this unit ------------ }
{ cut this block out.  Save as RLINE.XX.  decode with  XX3402 :

{                      XX3402 d RLINE.XX                               }
{  the file RLINE.OBJ will be created                                  }

*XX3402-000883-310792--72--85-14039-------RLINE.OBJ--1-OF--1
U+g+0J7AGIt39Y3HHSC66++++3FpQa7j623nQqJhMalZQW+UJaJmQqZjPW+l9X+lW6UF+21d
xId42kZGH2ZCFGt-IooIW+A+ECZAZU6++4WK-U+2F23IEIOM-k-6+++0+E2JZUM+-2BDF2J3
a+Q+81o0+k2-xMk9++V4GIl3HIx2FE+QY-I+++6CIYNDEYd3EpF+FYxEFIs8+++uY-M+++6D
IYNDEYd3EpF+FYBAHpB3Pk++Xt+L+++02374Ho78FIBIE2NGFI32H2s++E0vY-I+++6CIYND
EYd3EpF+FZ73EIFU+E1tY-I+++6CIYNDEYd3EpF+FZB3FIiX+E0eY-U+++6FIYNDEYd3EpF+
FYN7H2JEHpAH+U-7W+E+E86-YO--+U6++3Qnk9Y7+DCfLwBJWym1v349p-u85U++UCDslLME
z8mtI++ukLA0WgW9yVM5wuEaW+oK5wFy-iX5znZ41bI3i+k+um48kvExnG3m4GO7-Mh41WO7
FEX3RUcaWLI0XBUaWII2Aw+aWIIE5sjZLQcC+3K9vAFy-WO1TEU+R+waWlrcTjy1ykFq-9Ey
nG49tJr8-+09wchD06hL+cjuWlyAk6vMh1zB6MjKlJs4QVu9mCAKzoQAVxYaU5bz4cTNRE37
WIwCsk9skvXzzzb15iAO7c+w0bI2GLEFFctT-9Xz+0j0R-kvm5M0WwX2TUe8kU9-eiAA+ze8
oB5dwuLFoTCY5wBJWykSz1DGWwf3LUO9TkOCFkE9Hkdo2cjrIP+BwetMR0K9mCWZzst5-CVZ
zrDdDTzzRGA8obETE6Z50chD+UBD1cZD-igEWLw4WIw88w36WwXcRzwnk6Z52-y9tJr80+-J
WymAqjn3LUMnmEhD0bERWrQ4zow8zoQ4XZw2l5s8d1D+WIQEXhe9tJr80+1c+Txmw2a7Hkca
WUJ5WLw4l5s8eijRJMjg5gJq-XD70ok6REKsm+1fIsh40chK1DTlE1Z215I4UrkC+5IYIYW7
F+knojRY06gQWwW5mfU+EgoVKb6bXYE2Wxvcdzu9wr6PWoECCw7r-PVY+CgD8w87F+e9F+61
kcZ2-XD+WIEE5sjZLQc6+3K9vAFy-XDG7ch31+j+R-F67chB0DTV7chB-WMfHE61kMDG+6jZ
LQc2+CeQ-U123EM-+Lq8+U++R+++
***** END OF BLOCK 1 *****


[Back to TEXTFILE SWAG index]  [Back to Main SWAG index]  [Original]