IMPLEMENTATION MODULE FIO ;
FROM SYSTEM IMPORT ADR, TSIZE, SIZE, WORD ;
FROM ASCII IMPORT nl, nul, tab ;
FROM Math IMPORT ABS ;
FROM StrLib IMPORT StrLen, StrConCat, StrCopy ;
FROM StrIO IMPORT WriteLn;
FROM MemUtils IMPORT MemCopy ;
FROM Storage IMPORT ALLOCATE, DEALLOCATE ;
FROM NumberIO IMPORT CardToStr ;
FROM libc IMPORT exit, open, creat, read, write, close, lseek ;
CONST
SEEK_SET = 0 ;
UNIXREADONLY = 0 ;
CreatePermissions = 666B;
MaxNoOfFiles = 100 ;
MaxBufferLength = 1024*16 ;
MaxErrorString = 1024* 8 ;
TYPE
FileUsage = (unused, openedforread, openedforwrite, openedforrandom) ;
FileStatus = (successful, outofmemory, toomanyfilesopen, failed, connectionfailure) ;
NameInfo = RECORD
address: ADDRESS ;
size : CARDINAL ;
END ;
Buffer = POINTER TO buf ;
buf = RECORD
position: CARDINAL ;
address : ADDRESS ;
filled : CARDINAL ;
size : CARDINAL ;
left : CARDINAL ;
contents: POINTER TO ARRAY [0..MaxBufferLength] OF CHAR ;
END ;
FileDescriptors = POINTER TO fds ;
fds = RECORD
unixfd: INTEGER ;
name : NameInfo ;
state : FileStatus ;
usage : FileUsage ;
output: BOOLEAN ;
buffer: Buffer ;
abspos: CARDINAL ;
END ;
VAR
FileInfo: ARRAY [0..MaxNoOfFiles] OF FileDescriptors ;
PROCEDURE GetUnixFileDescriptor (f: File) : INTEGER ;
BEGIN
IF (f<MaxNoOfFiles) AND (FileInfo[f]#NIL)
THEN
RETURN( FileInfo[f]^.unixfd )
ELSE
FormatError1('file %d has not been opened or is out of range\n', f)
END
END GetUnixFileDescriptor ;
PROCEDURE Max (a, b: CARDINAL) : CARDINAL ;
BEGIN
IF a>b
THEN
RETURN( a )
ELSE
RETURN( b )
END
END Max ;
PROCEDURE Min (a, b: CARDINAL) : CARDINAL ;
BEGIN
IF a<b
THEN
RETURN( a )
ELSE
RETURN( b )
END
END Min ;
PROCEDURE GetNextFreeDescriptor () : File ;
VAR
f: File ;
BEGIN
f := 0 ;
WHILE (f<MaxNoOfFiles) AND (FileInfo[f]#NIL) DO
f := f+File(1)
END ;
RETURN( f )
END GetNextFreeDescriptor ;
PROCEDURE IsNoError (f: File) : BOOLEAN ;
BEGIN
RETURN(
(f<MaxNoOfFiles) AND (FileInfo[f]#NIL) AND (FileInfo[f]^.state=successful)
)
END IsNoError ;
PROCEDURE Exists (fname: ARRAY OF CHAR) : BOOLEAN ;
VAR
f: File ;
BEGIN
f := OpenToRead(fname) ;
IF IsNoError(f)
THEN
Close(f) ;
RETURN( TRUE )
ELSE
Close(f) ;
RETURN( FALSE )
END
END Exists ;
PROCEDURE StrCopyM2C (a: ADDRESS; b: ARRAY OF CHAR) ;
VAR
p : POINTER TO CHAR ;
i, high: CARDINAL ;
BEGIN
p := a ;
i := 0 ;
high := StrLen(b) ;
WHILE i<high DO
p^ := b[i] ;
INC(p) ;
INC(i)
END ;
p^ := nul
END StrCopyM2C ;
PROCEDURE InitializeFile (f: File; fname: ARRAY OF CHAR;
fstate: FileStatus; use: FileUsage; towrite: BOOLEAN; buflength: CARDINAL) : File ;
BEGIN
NEW(FileInfo[f]) ;
IF FileInfo[f]=NIL
THEN
FileInfo[MaxNoOfFiles]^.state := outofmemory ;
RETURN( MaxNoOfFiles )
ELSE
WITH FileInfo[f]^ DO
name.size := StrLen(fname)+1 ;
usage := use ;
output := towrite ;
ALLOCATE(name.address, name.size) ;
IF name.address=NIL
THEN
state := outofmemory ;
RETURN( f )
END ;
StrCopyM2C(name.address, fname) ;
abspos := 0 ;
NEW(buffer) ;
IF buffer=NIL
THEN
FileInfo[MaxNoOfFiles]^.state := outofmemory ;
RETURN( MaxNoOfFiles )
ELSE
WITH buffer^ DO
size := buflength ;
position := 0 ;
filled := 0 ;
IF size=0
THEN
address := NIL
ELSE
ALLOCATE(address, size) ;
IF address=NIL
THEN
state := outofmemory ;
RETURN( f )
END
END ;
IF towrite
THEN
left := size
ELSE
left := 0
END ;
contents := address ;
END ;
state := fstate
END
END
END ;
RETURN( f )
END InitializeFile ;
PROCEDURE ConnectToUnix (f: File; towrite: BOOLEAN) ;
BEGIN
IF (f<MaxNoOfFiles) AND (FileInfo[f]#NIL)
THEN
WITH FileInfo[f]^ DO
IF towrite
THEN
unixfd := creat(name.address, CreatePermissions)
ELSE
unixfd := open(name.address, UNIXREADONLY, 0)
END ;
IF unixfd<0
THEN
state := connectionfailure
END
END
END
END ConnectToUnix ;
PROCEDURE OpenToRead (fname: ARRAY OF CHAR) : File ;
VAR
f: File ;
BEGIN
f := GetNextFreeDescriptor() ;
IF f<MaxNoOfFiles
THEN
f := InitializeFile(f, fname, successful, openedforread, FALSE, MaxBufferLength) ;
ConnectToUnix(f, FALSE)
ELSE
FileInfo[f]^.state := toomanyfilesopen
END ;
RETURN( f )
END OpenToRead ;
PROCEDURE OpenToWrite (fname: ARRAY OF CHAR) : File ;
VAR
f: File ;
BEGIN
f := GetNextFreeDescriptor() ;
IF f<MaxNoOfFiles
THEN
f := InitializeFile(f, fname, successful, openedforwrite, TRUE, MaxBufferLength) ;
ConnectToUnix(f, TRUE)
ELSE
FileInfo[f]^.state := toomanyfilesopen
END ;
RETURN( f )
END OpenToWrite ;
PROCEDURE OpenForRandom (fname: ARRAY OF CHAR; towrite: BOOLEAN) : File ;
VAR
f: File ;
BEGIN
f := GetNextFreeDescriptor() ;
IF f<MaxNoOfFiles
THEN
f := InitializeFile(f, fname, successful, openedforrandom, towrite, MaxBufferLength) ;
ConnectToUnix(f, towrite)
ELSE
FileInfo[f]^.state := toomanyfilesopen
END ;
RETURN( f )
END OpenForRandom ;
PROCEDURE Close (f: File) ;
BEGIN
IF f<MaxNoOfFiles
THEN
IF FileInfo[f]#NIL
THEN
FlushBuffer(f) ;
WITH FileInfo[f]^ DO
IF unixfd>=0
THEN
IF close(unixfd)#0
THEN
FormatError1('failed to close file (%s)\n', WORD(name.address)) ;
state := failed
END
END ;
IF name.address#NIL
THEN
DEALLOCATE(name.address, name.size)
END ;
IF buffer#NIL
THEN
WITH buffer^ DO
IF address#NIL
THEN
DEALLOCATE(address, size)
END
END ;
DISPOSE(buffer)
END
END ;
DISPOSE(FileInfo[f]) ;
FileInfo[f] := NIL
END
END
END Close ;
PROCEDURE WriteString (f: File; a: ARRAY OF CHAR) ;
VAR
l: CARDINAL ;
BEGIN
l := StrLen(a) ;
IF WriteNBytes(f, l, ADR(a))#l
THEN
END
END WriteString ;
PROCEDURE ReadFromBuffer (f: File; a: ADDRESS; nBytes: CARDINAL) : INTEGER ;
VAR
result: INTEGER ;
total,
n : CARDINAL ;
p : POINTER TO BYTE ;
BEGIN
IF f<MaxNoOfFiles
THEN
total := 0 ;
WITH FileInfo[f]^ DO
IF buffer#NIL
THEN
WITH buffer^ DO
IF left>0
THEN
IF nBytes=1
THEN
p := a ;
p^ := contents^[position] ;
DEC(left) ;
INC(position) ;
nBytes := 0 ;
INC(abspos) ;
RETURN( 1 )
ELSE
n := Min(left, nBytes) ;
MemCopy(ADDRESS(CARDINAL(address)+position), n, a) ;
DEC(left, n) ;
INC(position, n) ;
a := ADDRESS(CARDINAL(a)+n) ;
DEC(nBytes, n) ;
INC(total, n) ;
INC(abspos, n)
END
END
END
END ;
IF nBytes>0
THEN
result := read(unixfd, a, INTEGER(nBytes)) ;
IF result>0
THEN
INC(total, result) ;
INC(abspos, result)
ELSE
state := failed ;
IF buffer#NIL
THEN
WITH buffer^ DO
left := 0 ;
position := 0 ;
IF address#NIL
THEN
contents^[position] := nul
END
END
END ;
RETURN( -1 )
END
END
END ;
RETURN( total )
ELSE
RETURN( -1 )
END
END ReadFromBuffer ;
PROCEDURE ReadNBytes (f: File; nBytes: CARDINAL; a: ADDRESS) : CARDINAL ;
VAR
n: CARDINAL ;
BEGIN
IF f<MaxNoOfFiles
THEN
CheckAccess(f, openedforread, FALSE) ;
n := ReadFromBuffer(f, a, nBytes) ;
IF n<0
THEN
RETURN( 0 )
ELSE
RETURN( n )
END
ELSE
RETURN( 0 )
END
END ReadNBytes ;
PROCEDURE BufferedRead (f: File; nBytes: CARDINAL; a: ADDRESS) : INTEGER ;
VAR
result: INTEGER ;
total,
n : INTEGER ;
p : POINTER TO BYTE ;
BEGIN
IF f<MaxNoOfFiles
THEN
total := 0 ;
WITH FileInfo[f]^ DO
IF buffer#NIL
THEN
WITH buffer^ DO
WHILE nBytes>0 DO
IF left>0
THEN
IF nBytes=1
THEN
p := a ;
p^ := contents^[position] ;
DEC(left) ;
INC(position) ;
INC(total) ;
INC(abspos) ;
RETURN( total )
ELSE
n := Min(left, nBytes) ;
MemCopy(ADDRESS(CARDINAL(address)+position), CARDINAL(n), a) ;
DEC(left, n) ;
INC(position, n) ;
a := ADDRESS(CARDINAL(a)+n) ;
DEC(nBytes, n) ;
INC(total, n) ;
INC(abspos, n)
END
ELSE
n := read(unixfd, address, size) ;
IF n>=0
THEN
position := 0 ;
left := n ;
filled := n ;
INC(abspos, n) ;
IF n=0
THEN
state := failed ;
RETURN( -1 )
END
ELSE
position := 0 ;
left := 0 ;
filled := 0 ;
abspos := 0 ;
state := failed ;
RETURN( total )
END
END
END
END ;
RETURN( total )
ELSE
RETURN( -1 )
END
END
ELSE
RETURN( -1 )
END
END BufferedRead ;
PROCEDURE HandleEscape (VAR dest: ARRAY OF CHAR; src: ARRAY OF CHAR;
VAR i, j: CARDINAL; HighSrc, HighDest: CARDINAL) ;
BEGIN
IF (i+1<HighSrc) AND (src[i]='\') AND (j<HighDest)
THEN
IF src[i+1]='n'
THEN
dest[j] := nl ;
INC(j) ;
INC(i, 2)
ELSIF src[i+1]='t'
THEN
dest[j] := tab ;
INC(j) ;
INC(i, 2)
ELSE
INC(i) ;
dest[j] := src[i] ;
INC(j) ;
INC(i)
END
END
END HandleEscape ;
PROCEDURE StringFormat1 (VAR dest: ARRAY OF CHAR; src: ARRAY OF CHAR; w: WORD) ;
VAR
HighSrc,
HighDest,
i, j : CARDINAL ;
str : ARRAY [0..MaxErrorString] OF CHAR ;
p : POINTER TO CHAR ;
BEGIN
HighSrc := StrLen(src) ;
HighDest := HIGH(dest) ;
i := 0 ;
j := 0 ;
WHILE (i<HighSrc) AND (src[i]#nul) AND (j<HighDest) AND (src[i]#'%') DO
IF src[i]='\'
THEN
HandleEscape(dest, src, i, j, HighSrc, HighDest)
ELSE
dest[j] := src[i] ;
INC(i) ;
INC(j)
END
END ;
IF (i+1<HighSrc) AND (src[i]='%') AND (j<HighDest)
THEN
IF src[i+1]='s'
THEN
p := w ;
WHILE (j<HighDest) AND (p^#nul) DO
dest[j] := p^ ;
INC(j) ;
INC(p)
END ;
IF j<HighDest
THEN
dest[j] := nul
END ;
j := StrLen(dest) ;
INC(i, 2)
ELSIF src[i+1]='d'
THEN
dest[j] := nul ;
CardToStr(w, 0, str) ;
StrConCat(dest, str, dest) ;
j := StrLen(dest) ;
INC(i, 2)
ELSE
dest[j] := src[i] ;
INC(i) ;
INC(j)
END
END ;
WHILE (i<HighSrc) AND (src[i]#nul) AND (j<HighDest) DO
IF src[i]='\'
THEN
HandleEscape(dest, src, i, j, HighSrc, HighDest)
ELSE
dest[j] := src[i] ;
INC(i) ;
INC(j)
END
END ;
IF j<HighDest
THEN
dest[j] := nul
END ;
END StringFormat1 ;
PROCEDURE FormatError (a: ARRAY OF CHAR) ;
BEGIN
WriteString(StdErr, a)
END FormatError ;
PROCEDURE FormatError1 (a: ARRAY OF CHAR; w: WORD) ;
VAR
s: ARRAY [0..MaxErrorString] OF CHAR ;
BEGIN
StringFormat1(s, a, w) ;
FormatError(s)
END FormatError1 ;
PROCEDURE FormatError2 (a: ARRAY OF CHAR; w1, w2: WORD) ;
VAR
s: ARRAY [0..MaxErrorString] OF CHAR ;
BEGIN
StringFormat1(s, a, w1) ;
FormatError1(s, w2)
END FormatError2 ;
PROCEDURE CheckAccess (f: File; use: FileUsage; towrite: BOOLEAN) ;
BEGIN
IF f<MaxNoOfFiles
THEN
IF FileInfo[f]=NIL
THEN
FormatError('this file has probably been closed and not reopened successfully or alternatively never opened\n') ;
HALT
ELSE
WITH FileInfo[f]^ DO
IF (use=openedforwrite) AND (usage=openedforread)
THEN
FormatError1('this file (%s) has been opened for reading but is now being written\n',
WORD(name.address)) ;
HALT
ELSIF (use=openedforread) AND (usage=openedforwrite)
THEN
FormatError1('this file (%s) has been opened for writing but is now being read\n',
WORD(name.address)) ;
HALT
ELSIF state=connectionfailure
THEN
FormatError1('this file (%s) was not successfully opened\n',
WORD(name.address)) ;
HALT
ELSIF towrite#output
THEN
IF output
THEN
FormatError1('this file (%s) was opened for writing but is now being read\n',
WORD(name.address)) ;
HALT
ELSE
FormatError1('this file (%s) was opened for reading but is now being written\n',
WORD(name.address)) ;
HALT
END
END
END
END
ELSE
FormatError('this file has not been opened successfully\n') ;
HALT
END
END CheckAccess ;
PROCEDURE ReadChar (f: File) : CHAR ;
VAR
ch: CHAR ;
BEGIN
CheckAccess(f, openedforread, FALSE) ;
IF BufferedRead(f, SIZE(ch), ADR(ch))=SIZE(ch)
THEN
RETURN( ch )
ELSE
RETURN( nul )
END
END ReadChar ;
PROCEDURE UnReadChar (f: File ; ch: CHAR) ;
BEGIN
CheckAccess(f, openedforread, FALSE) ;
IF (f<MaxNoOfFiles) AND (FileInfo[f]#NIL)
THEN
WITH FileInfo[f]^ DO
IF buffer#NIL
THEN
WITH buffer^ DO
IF (position>0) AND (filled>0)
THEN
DEC(position) ;
INC(left) ;
contents^[position] := ch
ELSE
FormatError1('performing too many UnReadChar call on file (%s)\n', INTEGER(f))
END
END
END
END
END
END UnReadChar ;
PROCEDURE ReadAny (f: File; VAR a: ARRAY OF BYTE) ;
BEGIN
CheckAccess(f, openedforread, FALSE) ;
IF BufferedRead(f, HIGH(a), ADR(a))=HIGH(a)
THEN
END
END ReadAny ;
PROCEDURE EOF (f: File) : BOOLEAN ;
VAR
ch: CHAR ;
s : FileStatus ;
BEGIN
CheckAccess(f, openedforread, FALSE) ;
IF (f<MaxNoOfFiles) AND (FileInfo[f]#NIL) AND (FileInfo[f]^.state=successful)
THEN
ch := ReadChar(f) ;
s := FileInfo[f]^.state ;
IF s=successful
THEN
UnReadChar(f, ch) ;
FileInfo[f]^.state := s ;
RETURN( FALSE )
ELSE
RETURN( TRUE )
END
ELSE
RETURN( TRUE )
END
END EOF ;
PROCEDURE EOLN (f: File) : BOOLEAN ;
VAR
ch: CHAR ;
s : FileStatus ;
BEGIN
CheckAccess(f, openedforread, FALSE) ;
IF (f<MaxNoOfFiles) AND (FileInfo[f]#NIL)
THEN
ch := ReadChar(f) ;
s := FileInfo[f]^.state ;
UnReadChar(f, ch) ;
FileInfo[f]^.state := s ;
RETURN( (s=successful) AND (ch=nl) )
ELSE
RETURN( FALSE )
END
END EOLN ;
PROCEDURE WriteLine (f: File) ;
BEGIN
WriteChar(f, nl)
END WriteLine ;
PROCEDURE WriteNBytes (f: File; nBytes: CARDINAL; a: ADDRESS) : CARDINAL ;
VAR
total: INTEGER ;
BEGIN
CheckAccess(f, openedforwrite, TRUE) ;
FlushBuffer(f) ;
IF (f<MaxNoOfFiles) AND (FileInfo[f]#NIL)
THEN
WITH FileInfo[f]^ DO
total := write(unixfd, a, INTEGER(nBytes)) ;
IF total<0
THEN
state := failed ;
RETURN( 0 )
ELSE
RETURN( CARDINAL(total) )
END
END
ELSE
RETURN( 0 )
END
END WriteNBytes ;
PROCEDURE BufferedWrite (f: File; nBytes: CARDINAL; a: ADDRESS) : INTEGER ;
VAR
result: INTEGER ;
total,
n : INTEGER ;
p : POINTER TO BYTE ;
BEGIN
IF f<MaxNoOfFiles
THEN
total := 0 ;
WITH FileInfo[f]^ DO
IF buffer#NIL
THEN
WITH buffer^ DO
WHILE nBytes>0 DO
IF left>0
THEN
IF nBytes=1
THEN
p := a ;
contents^[position] := p^ ;
DEC(left) ;
INC(position) ;
INC(total) ;
INC(abspos) ;
RETURN( total )
ELSE
n := Min(left, nBytes) ;
MemCopy(a, CARDINAL(n), ADDRESS(CARDINAL(address)+position)) ;
DEC(left, n) ;
INC(position, n) ;
a := ADDRESS(CARDINAL(a)+n) ;
DEC(nBytes, n) ;
INC(total, n) ;
INC(abspos, n)
END
ELSE
FlushBuffer(f) ;
IF state#successful
THEN
nBytes := 0
END
END
END
END ;
RETURN( total )
ELSE
RETURN( -1 )
END
END
ELSE
RETURN( -1 )
END
END BufferedWrite ;
PROCEDURE FlushBuffer (f: File) ;
BEGIN
IF (f<MaxNoOfFiles) AND (FileInfo[f]#NIL)
THEN
WITH FileInfo[f]^ DO
IF output AND (buffer#NIL)
THEN
WITH buffer^ DO
IF (position=0) OR (write(unixfd, address, position)=position)
THEN
position := 0 ;
filled := 0 ;
left := size
ELSE
state := failed
END
END
END
END
END
END FlushBuffer ;
PROCEDURE WriteAny (f: File; VAR a: ARRAY OF BYTE) ;
BEGIN
CheckAccess(f, openedforwrite, TRUE) ;
IF BufferedWrite(f, HIGH(a), ADR(a))=HIGH(a)
THEN
END
END WriteAny ;
PROCEDURE WriteChar (f: File; ch: CHAR) ;
BEGIN
CheckAccess(f, openedforwrite, TRUE) ;
IF BufferedWrite(f, SIZE(ch), ADR(ch))=SIZE(ch)
THEN
END
END WriteChar ;
PROCEDURE WriteCardinal (f: File; c: CARDINAL) ;
BEGIN
WriteAny(f, c)
END WriteCardinal ;
PROCEDURE ReadCardinal (f: File) : CARDINAL ;
VAR
c: CARDINAL ;
BEGIN
ReadAny(f, c) ;
RETURN( c )
END ReadCardinal ;
PROCEDURE ReadString (f: File; VAR a: ARRAY OF CHAR) ;
VAR
high,
i : CARDINAL ;
ch : CHAR ;
BEGIN
CheckAccess(f, openedforread, FALSE) ;
high := HIGH(a) ;
i := 0 ;
REPEAT
ch := ReadChar(f) ;
IF i<=high
THEN
IF (ch=nl) OR (NOT IsNoError(f))
THEN
a[i] := nul ;
INC(i)
ELSE
a[i] := ch ;
INC(i)
END
END
UNTIL (ch=nl) OR (i>high) OR (NOT IsNoError(f))
END ReadString ;
PROCEDURE SetPositionFromBeginning (f: File; pos: CARDINAL) ;
BEGIN
IF f<MaxNoOfFiles
THEN
WITH FileInfo[f]^ DO
IF (abspos#pos) OR TRUE
THEN
FlushBuffer(f) ;
IF buffer#NIL
THEN
WITH buffer^ DO
IF output
THEN
left := size
ELSE
left := 0
END ;
position := 0 ;
filled := 0
END
END ;
abspos := pos ;
IF lseek(unixfd, INTEGER(pos), SEEK_SET)#INTEGER(pos)
THEN
state := failed ;
abspos := 0
END
END
END
END
END SetPositionFromBeginning ;
PROCEDURE FindPosition (f: File) : CARDINAL ;
BEGIN
IF f<MaxNoOfFiles
THEN
WITH FileInfo[f]^ DO
RETURN( abspos )
END
ELSE
RETURN( 0 )
END
END FindPosition ;
PROCEDURE PreInitialize (f: File; fname: ARRAY OF CHAR;
state: FileStatus; use: FileUsage; towrite: BOOLEAN; bufsize: CARDINAL) ;
BEGIN
NEW(FileInfo[f]) ;
IF FileInfo[f]=NIL
THEN
HALT
ELSE
IF InitializeFile(f, fname, state, use, towrite, bufsize)=f
THEN
IF f<MaxNoOfFiles
THEN
FileInfo[f]^.unixfd := INTEGER(f)
ELSE
FileInfo[f]^.unixfd := FileInfo[StdErr]^.unixfd
END
ELSE
HALT
END
END
END PreInitialize ;
PROCEDURE Init ;
VAR
f: File ;
BEGIN
FOR f := 0 TO MaxNoOfFiles DO
FileInfo[f] := NIL
END ;
StdIn := 0 ;
PreInitialize(StdIn , 'stdin' , successful , openedforread , FALSE, MaxBufferLength) ;
StdOut := 1 ;
PreInitialize(StdOut , 'stdout', successful , openedforwrite, TRUE, MaxBufferLength) ;
StdErr := 2 ;
PreInitialize(StdErr , 'stderr', successful , openedforwrite, TRUE, MaxBufferLength) ;
PreInitialize(MaxNoOfFiles, 'error' , toomanyfilesopen, unused , FALSE, 0) ;
END Init ;
BEGIN
Init
END FIO.