-- Copyright (C) 1986 by Xerox Corporation. All rights reserved. -- CIOLibImpl.mesa -- NFS 19-May-86 15:30:40 -- MEW 23-May-86 17:08:01 -- Implementation of C I/O Library for tajo (uses MFile and MStream). DIRECTORY Ascii USING [CR, NUL, SP, TAB], BucketAlloc USING [Alloc, Free], CIOLib USING [ ClientDataObject, EOF, failure, FilePtr, seekCur, seekEnd, seekSet, success], CRuntime USING [ EnterStream, EnterStreamGlobally, GetStderr, GetStdin, GetStdout, RemoveStream, SetStderr, SetStdin, SetStdout, StopIfUserAborted, z], CString USING [ CString, CStringToLongString, IncrBPointer, ReadByte, ReadChar, ToBytePointer, ToWordPointer, WriteByte, WriteChar], Environment USING [Block], Inline USING [DBITAND], MFile USING [Acquire, AcquireTemp, Delete, Error, Handle, Release, Rename], MStream USING [ Create, EndOf, Error, GetLength, Handle, ReadOnly, ReadWrite, WriteOnly], Stream USING [ Byte, Block, Delete, DeleteProcedure, EndOfStream, GetBlock, GetByteProcedure, GetChar, GetPosition, GetProcedure, Handle, InvalidOperation, PutBlock, PutByte, PutByteProcedure, PutChar, PutProcedure, SendNow, SetPosition, SetPositionProcedure]; CIOLibImpl: PROGRAM IMPORTS BucketAlloc, CRuntime, CString, Inline, MFile, MStream, Stream EXPORTS CIOLib = { OPEN CIOLib; z: UNCOUNTED ZONE = CRuntime.z; String: TYPE = CString.CString; fopen: PUBLIC PROCEDURE [filename, type: String] RETURNS [FilePtr] = { h: Stream.Handle; CRuntime.StopIfUserAborted[]; h ← fopenInternal[filename, type]; IF h = NIL THEN RETURN[NIL]; RETURN[CRuntime.EnterStream[sH: h]]; }; fopenInternal: PROCEDURE [filename, type: String] RETURNS [Stream.Handle] = { OPEN CString; h: Stream.Handle ← NIL; file: LONG STRING; c: CHAR; -- skip leading spaces. WHILE ((c ← ReadChar[filename]) = Ascii.SP) OR (c = Ascii.TAB) DO filename ← IncrBPointer[filename]; ENDLOOP; file ← CStringToLongString[filename, z]; { ENABLE MStream.Error => {h ← NIL; CONTINUE; }; SELECT ReadChar[type] FROM 'r => { type ← IncrBPointer[type]; SELECT ReadChar[type] FROM Ascii.NUL => h ← MStream.ReadOnly[file, []]; 'b => h ← MStream.ReadOnly[file, []]; '+ => { type ← IncrBPointer[type]; SELECT ReadChar[type] FROM Ascii.NUL => {h ← MStream.ReadWrite[file, [], text]}; 'b => h ← MStream.ReadWrite[file, [], binary]; ENDCASE; }; ENDCASE; }; 'w => { type ← IncrBPointer[type]; SELECT ReadChar[type] FROM Ascii.NUL => {h ← MStream.WriteOnly[file, [], text]}; 'b => h ← MStream.WriteOnly[file, [], binary]; '+ => { type ← IncrBPointer[type]; SELECT ReadChar[type] FROM Ascii.NUL => {h ← MStream.ReadWrite[file, [], text]}; 'b => h ← MStream.ReadWrite[file, [], binary]; ENDCASE; }; ENDCASE; }; 'a => { type ← IncrBPointer[type]; SELECT ReadChar[type] FROM Ascii.NUL => { h ← MStream.WriteOnly[file, [], text]; Stream.SetPosition[h, MStream.GetLength[h]]; }; 'b => { h ← MStream.WriteOnly[file, [], binary]; Stream.SetPosition[h, MStream.GetLength[h]]; }; '+ => { type ← IncrBPointer[type]; SELECT ReadChar[type] FROM Ascii.NUL => { h ← MStream.ReadWrite[file, [], text]; Stream.SetPosition[h, MStream.GetLength[h]]; Stream.SetPosition[h, MStream.GetLength[h]]; }; 'b => { h ← MStream.ReadWrite[file, [], binary]; Stream.SetPosition[h, MStream.GetLength[h]]; }; ENDCASE; }; ENDCASE; }; ENDCASE; }; --enable z.FREE[@file]; RETURN[h]; }; fclose: PUBLIC PROCEDURE [stream: FilePtr] RETURNS [INTEGER] = { sh: Stream.Handle; CRuntime.StopIfUserAborted[]; IF stream = NIL THEN RETURN[failure]; sh ← stream↑; -- Copy stream handle because RemoveStream makes stream↑ NIL. IF sh = NIL THEN RETURN[failure]; IF CRuntime.RemoveStream[sh].canDeleteStream THEN Stream.Delete[sh]; RETURN[success]; }; freopen: PUBLIC PROCEDURE [filename, type: String, stream: FilePtr] RETURNS [FilePtr] = { sh: Stream.Handle; CRuntime.StopIfUserAborted[]; IF stream = NIL THEN RETURN[NIL]; sh ← stream↑; -- Copy stream handle because RemoveStream makes stream↑ NIL. IF CRuntime.RemoveStream[sh].canDeleteStream THEN Stream.Delete[sh]; stream↑ ← fopenInternal[filename, type]; IF stream↑ = NIL THEN RETURN[NIL]; CRuntime.EnterStreamGlobally[stream↑]; RETURN[stream]; }; unlink: PUBLIC PROCEDURE [path: String] RETURNS [INTEGER] = { retVal: INTEGER ← success; mPath: LONG STRING ← CString.CStringToLongString[path, z]; { file: MFile.Handle; file ← MFile.Acquire[mPath, delete, [] ! MFile.Error => GOTO Failed]; MFile.Delete[file ! MFile.Error => GOTO Failed]; EXITS Failed => retVal ← failure; }; z.FREE[@mPath]; RETURN[retVal]; }; fflush: PUBLIC PROCEDURE [stream: FilePtr] RETURNS [INTEGER] = { outcome: INTEGER ← success; IF stream = NIL OR stream↑ = NIL THEN RETURN[failure]; CRuntime.StopIfUserAborted[]; Stream.SendNow[stream↑ ! MStream.Error => {outcome ← failure; CONTINUE}; ]; RETURN[outcome]}; rename: PUBLIC PROCEDURE [old, new: String] RETURNS [INTEGER] = { h: MFile.Handle ← NIL; outcome: INTEGER ← success; oldName: LONG STRING; newName: LONG STRING; CRuntime.StopIfUserAborted[]; oldName ← CString.CStringToLongString[old, z]; newName ← CString.CStringToLongString[new, z]; { ENABLE MFile.Error => {outcome ← failure; CONTINUE; }; h ← MFile.Acquire[oldName, rename, []]; MFile.Rename[h, newName ! MFile.Error => CONTINUE]; MFile.Release[h]; }; z.FREE[@oldName]; z.FREE[@newName]; RETURN[outcome]}; tmpfile: PUBLIC PROCEDURE RETURNS [FilePtr] = { h: MStream.Handle ← NIL; CRuntime.StopIfUserAborted[]; h ← MStream.Create[ MFile.AcquireTemp[binary ! MFile.Error => CONTINUE; ], [] ! MStream.Error => CONTINUE; ]; RETURN[IF h = NIL THEN NIL ELSE CRuntime.EnterStream[sH: h]]; }; -- character i/o fgetc: PUBLIC PROCEDURE [stream: FilePtr] RETURNS [INTEGER] = { c: INTEGER; CRuntime.StopIfUserAborted[]; IF stream = NIL OR stream↑ = NIL THEN RETURN[EOF]; c ← LOOPHOLE[Stream.GetChar[ stream↑ ! MStream.Error, Stream.EndOfStream => {c ← LOOPHOLE[EOF]; CONTINUE}]]; RETURN[c]}; fputc: PUBLIC PROCEDURE [c: INTEGER, stream: FilePtr] RETURNS [INTEGER] = { CRuntime.StopIfUserAborted[]; IF stream = NIL OR stream↑ = NIL THEN RETURN[EOF]; Stream.PutChar[ stream↑, LOOPHOLE[c] ! MStream.Error, MFile.Error => {c ← EOF; CONTINUE}; ]; RETURN[c]}; -- The function ungetc pushes a character back into the stream without changing -- the associated file. This is accomplished by temporarily replacing the -- procedures of the stream object. The replacement procedures return the -- pushed back character as the first byte of input, and then restore the -- original procedures. The client data field of the strem object is used to -- cache the pushed back character, the original procedures, and the original -- client data pointer. ungetc: PUBLIC PROCEDURE [c: INTEGER, stream: FilePtr] RETURNS [INTEGER] = { saveClientData: LONG POINTER; CRuntime.StopIfUserAborted[]; IF stream = NIL OR stream↑ = NIL OR c = EOF THEN RETURN[EOF]; -- Won't push back EOF { ENABLE Stream.InvalidOperation => CONTINUE; Stream.SetPosition[ stream↑, Stream.GetPosition[stream↑] - 1 ! MStream.Error => GOTO RetEOF]; }; saveClientData ← stream.clientData; stream.clientData ← BucketAlloc.Alloc[SIZE[ClientDataObject]]; LOOPHOLE[stream.clientData, LONG POINTER TO ClientDataObject]↑ ← [ saveClientData, LOOPHOLE[c], 0, stream.getByte, stream.putByte, stream.setPosition, stream.delete, stream.get, stream.put]; stream.getByte ← GetPushedBackByte; stream.putByte ← RestoreStreamAndPut; stream.setPosition ← RestoreStreamAndSet; stream.delete ← RestoreStreamAndDelete; stream.get ← NewGetBlock; stream.put ← RestoreStreamAndPutBlock; RETURN[c]; EXITS RetEOF => RETURN[EOF]; }; -- The following record type is used after the ungetc function is called to -- store the pushed back character, the original stream procedures, and the -- original client data. It is declared machine dependent to insure that the -- original client data is the first field, so that clients can still access it -- (but with an extra dereference). ClientDataObject: TYPE = CIOLib.ClientDataObject; << MACHINE DEPENDENT RECORD[ oldClientData:LONG POINTER, c:Stream.Byte, blank:Stream.Byte, -- to fill up word -- oldGetByteProc:Stream.GetByteProcedure, oldPutByteProc:Stream.PutByteProcedure, oldSetPositionProc:Stream.SetPositionProcedure, oldDeleteProc:Stream.DeleteProcedure, oldGetProc:Stream.GetProcedure, oldPutProc:Stream.PutProcedure];>> RestoreStream: PROCEDURE [sH: Stream.Handle, advance: BOOLEAN] = { -- Puts the original procedures and client data back in the stream object. -- saveClientData: LONG POINTER TO ClientDataObject ← sH.clientData; sH.getByte ← saveClientData.oldGetByteProc; sH.putByte ← saveClientData.oldPutByteProc; sH.setPosition ← saveClientData.oldSetPositionProc; sH.delete ← saveClientData.oldDeleteProc; sH.get ← saveClientData.oldGetProc; sH.put ← saveClientData.oldPutProc; sH.clientData ← saveClientData.oldClientData; BucketAlloc.Free[@saveClientData, SIZE[ClientDataObject]]; IF advance THEN { ENABLE Stream.InvalidOperation => CONTINUE; Stream.SetPosition[sH, Stream.GetPosition[sH] + 1]; }; }; GetPushedBackByte: Stream.GetByteProcedure = { saveByte: Stream.Byte ← LOOPHOLE[sH.clientData, LONG POINTER TO ClientDataObject].c; RestoreStream[sH, TRUE]; RETURN[saveByte]; }; RestoreStreamAndSet: Stream.SetPositionProcedure = { RestoreStream[sH, FALSE]; Stream.SetPosition[sH, position]; }; RestoreStreamAndDelete: Stream.DeleteProcedure = { RestoreStream[sH, FALSE]; Stream.Delete[sH]; }; RestoreStreamAndPut: PUBLIC Stream.PutByteProcedure = { RestoreStream[sH, FALSE]; Stream.PutByte[sH, byte]; }; NewGetBlock: Stream.GetProcedure = { -- Restores the stream only if block.startIndex < block.stopIndexPlusOne. -- IF block.startIndex = block.stopIndexPlusOne THEN RETURN[bytesTransferred: 0, why: normal, sst: 0]; IF block.startIndex < block.stopIndexPlusOne THEN { block.blockPointer[block.startIndex] ← LOOPHOLE[sH.clientData, LONG POINTER TO ClientDataObject].c; block.startIndex ← block.startIndex + 1; RestoreStream[sH, TRUE]; }; [bytesTransferred, why, sst] ← Stream.GetBlock[sH, block]; bytesTransferred ← bytesTransferred + 1; }; RestoreStreamAndPutBlock: Stream.PutProcedure = { RestoreStream[sH, FALSE]; Stream.PutBlock[sH, block, endRecord]; }; -- direct i/o fread: PUBLIC PROCEDURE [ptr: String, size, count: CARDINAL, iop: FilePtr] RETURNS [itemsRead: INTEGER ← 0] = { ENABLE MStream.Error, Stream.EndOfStream => CONTINUE; block: Stream.Block; CRuntime.StopIfUserAborted[]; IF iop = NIL OR iop↑ = NIL OR size = 0 THEN RETURN[0]; IF ~IsBytePtr[ptr.pointer] THEN ptr ← CString.ToBytePointer[ptr.pointer]; IF WordAligned[ptr] THEN block ← [ blockPointer: CString.ToWordPointer[ptr], startIndex: 0, stopIndexPlusOne: count * size] ELSE { block ← [ blockPointer: CString.ToWordPointer[ptr], startIndex: 1, stopIndexPlusOne: (count * size) + 1]; }; itemsRead ← Stream.GetBlock[iop↑, block].bytesTransferred / size; }; fwrite: PUBLIC PROCEDURE [ptr: String, size, count: CARDINAL, iop: FilePtr] RETURNS [itemsWritten: INTEGER ← 0] = { ENABLE MStream.Error, Stream.EndOfStream => CONTINUE; block: Stream.Block; CRuntime.StopIfUserAborted[]; IF iop = NIL OR iop↑ = NIL THEN RETURN[0]; IF ~IsBytePtr[ptr.pointer] THEN ptr ← CString.ToBytePointer[ptr.pointer]; IF WordAligned[ptr] THEN block ← [ blockPointer: CString.ToWordPointer[ptr], startIndex: 0, stopIndexPlusOne: count * size] ELSE { block ← [ blockPointer: CString.ToWordPointer[ptr], startIndex: 1, stopIndexPlusOne: (count * size) + 1]; }; Stream.PutBlock[iop↑, block]; itemsWritten ← count; }; WordAligned: PROCEDURE [ptr: String] RETURNS [BOOLEAN] = INLINE { RETURN[ptr.whichByte = 0]; }; IsBytePtr: PROCEDURE [ptr: LONG UNSPECIFIED] RETURNS [BOOLEAN] = INLINE { hiBit: LONG CARDINAL = 20000000000B; RETURN[Inline.DBITAND[ptr, hiBit] # 0]; }; -- string i/o (These procedures adapted from C code) fgets: PUBLIC PROCEDURE [s: CString.CString, n: INTEGER, stream: FilePtr] RETURNS [CString.CString] = { OPEN CString; c: INTEGER; s2: String ← s; WHILE ((n ← n.PRED) > 0) AND ((c ← fgetc[stream]) >= 0) DO WriteByte[c, s2]; s2 ← IncrBPointer[s2]; IF c = Ascii.CR.ORD THEN EXIT; ENDLOOP; IF c < 0 AND s = s2 THEN s.pointer ← NIL ELSE WriteChar[Ascii.NUL, s2]; RETURN[s]; }; gets: PUBLIC PROCEDURE [s: CString.CString] RETURNS [CString.CString] = { OPEN CString; c: INTEGER; s2: String ← s; in: FilePtr = CRuntime.GetStdin[]; WHILE ((c ← fgetc[in]) # Ascii.CR.ORD) AND (c >= 0) DO WriteByte[c, s2]; s2 ← IncrBPointer[s2]; ENDLOOP; IF c < 0 AND s = s2 THEN s.pointer ← NIL ELSE WriteChar[Ascii.NUL, s2]; RETURN[s]; }; fputs: PUBLIC PROCEDURE [s: CString.CString, stream: FilePtr] RETURNS [c: INTEGER ← 0] = { OPEN CString; block: Environment.Block; length: CARDINAL ← 0; s2: String ← s; IF stream = NIL OR stream↑ = NIL THEN RETURN[EOF]; WHILE ReadByte[s2] # 0 DO length ← length + 1; s2 ← IncrBPointer[s2]; ENDLOOP; block ← [ToWordPointer[s], s.whichByte, s.whichByte + length]; stream↑.PutBlock[block! MStream.Error, MFile.Error => {c ← EOF; CONTINUE}; ]; RETURN[c] }; puts: PUBLIC PROCEDURE [s: CString.CString] RETURNS [val: INTEGER] = { OPEN CString; s2: String ← s; out: FilePtr = CRuntime.GetStdout[]; val ← fputs[s, out]; RETURN[IF val = 0 THEN fputc[Ascii.CR.ORD, out] ELSE val]; }; -- random access functions fseek: PUBLIC PROCEDURE [stream: FilePtr, offset: LONG INTEGER, ptrname: INTEGER] RETURNS [INTEGER] = { OPEN Stream, MStream; outcome: INTEGER ← success; CRuntime.StopIfUserAborted[]; IF stream = NIL OR stream↑ = NIL THEN RETURN[failure]; { ENABLE MStream.Error, Stream.InvalidOperation => {outcome ← failure; CONTINUE; }; SELECT ptrname FROM seekSet => SetPosition[stream↑, offset]; seekCur => SetPosition[stream↑, offset + GetPosition[stream↑]]; seekEnd => SetPosition[ stream↑, offset + LOOPHOLE[MStream.GetLength[stream↑], LONG INTEGER]] -- won't work for files longer than LAST[LONG INTEGER] -- ENDCASE => outcome ← failure; }; -- enable RETURN[outcome]}; ftell: PUBLIC PROCEDURE [stream: FilePtr] RETURNS [p: LONG INTEGER] = { -- Return value -1 if invalid stream -- CRuntime.StopIfUserAborted[]; IF stream = NIL OR stream↑ = NIL THEN p ← -1 ELSE p ← Stream.GetPosition[ stream↑ ! MStream.Error, Stream.InvalidOperation => {p ← -1; CONTINUE; }]}; rewind: PUBLIC PROCEDURE [stream: FilePtr] RETURNS [INTEGER] = { RETURN[fseek[stream, 0, seekSet]]; }; -- stream status feof: PUBLIC PROCEDURE [stream: FilePtr] RETURNS [INTEGER] = { -- Return value 0 if invalid stream -- True: INTEGER = 1; False: INTEGER = 0; CRuntime.StopIfUserAborted[]; { IF stream = NIL OR stream↑ = NIL THEN RETURN[False]; IF MStream.EndOf[stream↑ ! MStream.Error => GOTO RetFalse; ] THEN RETURN[True] ELSE RETURN[False]; EXITS RetFalse => RETURN[False]; }; }; -- standard streams GetStdin: PUBLIC PROCEDURE RETURNS [sH: FilePtr] = {sH ← CRuntime.GetStdin[]; }; GetStdout: PUBLIC PROCEDURE RETURNS [sH: FilePtr] = { sH ← CRuntime.GetStdout[]; }; GetStderr: PUBLIC PROCEDURE RETURNS [sH: FilePtr] = { sH ← CRuntime.GetStderr[]; }; SetStdin: PUBLIC PROCEDURE [sH: FilePtr] RETURNS [INTEGER ← 0] = { CRuntime.SetStdin[sH]; }; SetStdout: PUBLIC PROCEDURE [sH: FilePtr] RETURNS [INTEGER ← 0] = { CRuntime.SetStdout[sH]; }; SetStderr: PUBLIC PROCEDURE [sH: FilePtr] RETURNS [INTEGER ← 0] = { CRuntime.SetStderr[sH]; }; }.