Oberon/A2/Oberon.Files.Mod

(* Aos, Copyright 2001, Pieter Muller, ETH Zurich *) MODULE Files IN Oberon;	(* pjm *) (** AUTHOR "pjm"; PURPOSE "Oberon for Aos files"; *) IMPORT SYSTEM, KernelLog IN A2, AosKernel &#58;&#61; Kernel IN A2, Files IN A2, Kernel; CONST &#9;BufSize &#61; 4096; &#9;MaxBufs &#61; 4; &#9;Slow &#61; FALSE; &#9;Trace &#61; TRUE; TYPE &#9;File* &#61; POINTER TO RECORD &#9;&#9;buf&#58; Buffer;	(* circular list of buffers *) &#9;&#9;bufs&#58; LONGINT;	(* number of buffers allocated *) &#9;&#9;alen, blen&#58; LONGINT;	(* file size &#61; alen*BufSize + blen, 0 &#60;&#61; blen &#60;&#61; BufSize *) &#9;&#9;r&#58; Files.Rider;	(* rider on underlying Aos file *) &#9;&#9;checktime, checkdate, checklen&#58; LONGINT &#9;END; &#9;Rider* &#61; RECORD &#9;&#9;buf&#58; Buffer;	(* buffer hint *) &#9;&#9;apos, bpos&#58; LONGINT; &#9;&#9;eof*&#58; BOOLEAN;	(** has end of file been passed *) &#9;&#9;res*&#58; LONGINT;	(** leftover byte count for ReadBytes/WriteBytes *) &#9;&#9;f&#58; File &#9;END; &#9;Buffer &#61; POINTER TO RECORD &#9;&#9;apos, lim&#58; LONGINT; &#9;&#9;mod&#58; BOOLEAN; &#9;&#9;next&#58; Buffer; &#9;&#9;data&#58; ARRAY BufSize OF CHAR &#9;END; &#9;Bytes4 &#61; ARRAY 4 OF SYSTEM.BYTE; &#9;Bytes8 &#61; ARRAY 8 OF SYSTEM.BYTE; VAR &#9;files&#58; AosKernel.FinalizedCollection;	(* all open files - cleaned up by GC *) &#9;search&#58; Files.File;	(* file being searched for *) &#9;found&#58; File;	(* file found *) (* Update our copy of the underlying file&#39;s time and length. *) PROCEDURE UpdateFile(f&#58; File); BEGIN &#9;f.r.file.GetDate(f.checktime, f.checkdate); f.checklen &#58;&#61; f.r.file.Length END UpdateFile; (* Check if our copy of the underlying file&#39;s time and length match the reality. *) PROCEDURE FileChanged(f&#58; File)&#58; BOOLEAN; VAR time, date&#58; LONGINT; BEGIN &#9;f.r.file.GetDate(time, date); &#9;RETURN (time # f.checktime) OR (date # f.checkdate) OR (f.r.file.Length # f.checklen) END FileChanged; (* Enumerator used in Old to search files collection for existing file handle using Files file as key. *) PROCEDURE Search(f&#58; ANY; VAR cont&#58; BOOLEAN); BEGIN &#9;IF f(File).r.file &#61; search THEN &#9;&#9;found &#58;&#61; f(File); cont &#58;&#61; FALSE &#9;END END Search; (** Creates a new file with the specified name. *) PROCEDURE New*(CONST name&#58; ARRAY OF CHAR)&#58; File; VAR f&#58; File; file&#58; Files.File; BEGIN &#9;Kernel.CheckOberonLock;	(* can only be called from Oberon *) &#9;file &#58;&#61; Files.New(name); &#9;IF file # NIL THEN &#9;&#9;NEW(f); f.bufs &#58;&#61; 1; f.alen &#58;&#61; 0; f.blen &#58;&#61; 0; &#9;&#9;NEW(f.buf); f.buf.apos &#58;&#61; 0; f.buf.lim &#58;&#61; 0; f.buf.next &#58;&#61; f.buf; f.buf.mod &#58;&#61; FALSE; &#9;&#9;file.Set(f.r, 0); UpdateFile(f); &#9;&#9;IF name # "" THEN &#9;&#9;&#9;files.Add(f, NIL)	(* add to collection *) &#9;&#9;&#9;(* it is ok to add it here, and not only in Register, as in underlying file systems, because the underlying file system will take care of the case where an Old is attempted on a file that has been New&#39;ed, but not Register&#39;ed (Old will fail). *) &#9;&#9;END &#9;ELSE &#9;&#9;f &#58;&#61; NIL &#9;END; &#9;RETURN f END New; (** Open an existing file. The same file descriptor is returned if a file is opened multiple times. *) PROCEDURE Old*(CONST name&#58; ARRAY OF CHAR)&#58; File; VAR f&#58; File; file&#58; Files.File; len&#58; LONGINT; BEGIN &#9;Kernel.CheckOberonLock;	(* can only be called from Oberon *) &#9;file &#58;&#61; Files.Old(name); &#9;IF file # NIL THEN &#9;&#9;search &#58;&#61; file; found &#58;&#61; NIL;	(* search for existing handle *) &#9;&#9;files.Enumerate(Search);	(* modify global found *) &#9;&#9;search &#58;&#61; NIL; f &#58;&#61; found; found &#58;&#61; NIL; &#9;&#9;IF (f # NIL) &#38; FileChanged(f) THEN	(* underlying file changed *) &#9;&#9;&#9;IF Trace THEN &#9;&#9;&#9;&#9;KernelLog.String("Files&#58; Stale "); WriteFile(f); KernelLog.Ln &#9;&#9;&#9;END; &#9;&#9;&#9;files.Remove(f); f &#58;&#61; NIL	(* throw away old record (even though user may still have a copy; that is his fault) *) &#9;&#9;END; &#9;&#9;IF f &#61; NIL THEN	(* none found, create new handle *) &#9;&#9;&#9;len &#58;&#61; file.Length; &#9;&#9;&#9;NEW(f); f.bufs &#58;&#61; 1; f.alen &#58;&#61; len DIV BufSize; f.blen &#58;&#61; len MOD BufSize; &#9;&#9;&#9;NEW(f.buf); f.buf.apos &#58;&#61; 0; f.buf.next &#58;&#61; f.buf; f.buf.mod &#58;&#61; FALSE; &#9;&#9;&#9;file.Set(f.r, 0); file.ReadBytes(f.r, f.buf.data, 0, BufSize); &#9;&#9;&#9;IF f.alen &#61; 0 THEN f.buf.lim &#58;&#61; f.blen ELSE f.buf.lim &#58;&#61; BufSize END; &#9;&#9;&#9;UpdateFile(f); &#9;&#9;&#9;files.Add(f, NIL)	(* add to collection *) &#9;&#9;ELSE &#9;&#9;&#9;(* return existing handle *) &#9;&#9;END &#9;ELSE &#9;&#9;f &#58;&#61; NIL &#9;END; &#9;RETURN f END Old; (** Register a file created with New in the directory, replacing the previous file in the directory with the same name. The file is automatically closed. *) PROCEDURE Register*(f&#58; File); BEGIN &#9;Update(f); Files.Register(f.r.file) END Register; (** Flushes the changes made to a file to disk. Register will automatically Close a file. *) PROCEDURE Close*(f&#58; File); BEGIN &#9;IF f # NIL THEN Update(f) END END Close; (** Returns the current length of a file. *) PROCEDURE Length*(f&#58; File)&#58; LONGINT; BEGIN &#9;RETURN f.alen*BufSize + f.blen END Length; (** Returns the time (t) and date (d) when a file was last modified. *) PROCEDURE GetDate*(f&#58; File; VAR t, d&#58; LONGINT); BEGIN &#9;f.r.file.GetDate(t, d) END GetDate; (** Sets the modification time (t) and date (d) of a file. *) PROCEDURE SetDate*(f&#58; File; t, d&#58; LONGINT); BEGIN &#9;Update(f);	(* otherwise later updating will modify time/date again *) &#9;f.r.file.SetDate(t, d) END SetDate; (** Positions a Rider at a certain position in a file. Multiple Riders can be positioned at different locations in a file. A Rider cannot be positioned beyond the end of a file. *) PROCEDURE Set*(VAR r&#58; Rider; f&#58; File; pos&#58; LONGINT); BEGIN &#9;IF f # NIL THEN &#9;&#9;r.eof &#58;&#61; FALSE; r.res &#58;&#61; 0; r.buf &#58;&#61; f.buf; r.f &#58;&#61; f; &#9;&#9;IF pos &#60; 0 THEN &#9;&#9;&#9;r.apos &#58;&#61; 0; r.bpos &#58;&#61; 0 &#9;&#9;ELSIF pos &#60; f.alen*BufSize + f.blen THEN &#9;&#9;&#9;r.apos &#58;&#61; pos DIV BufSize; r.bpos &#58;&#61; pos MOD BufSize &#9;&#9;ELSE &#9;&#9;&#9;r.apos &#58;&#61; f.alen; r.bpos &#58;&#61; f.blen	(* blen may be BufSize *) &#9;&#9;END &#9;ELSE &#9;&#9;r.buf &#58;&#61; NIL; r.f &#58;&#61; NIL &#9;END END Set; (** Returns the offset of a Rider positioned on a file. *) PROCEDURE Pos*(VAR r&#58; Rider)&#58; LONGINT; BEGIN &#9;RETURN r.apos*BufSize + r.bpos END Pos; (** Returns the File a Rider is based on. *) PROCEDURE Base*(VAR r&#58; Rider)&#58; File; BEGIN &#9;RETURN r.f END Base; (** Read a byte from a file, advancing the Rider one byte further. R.eof indicates if the end of the file has been passed. *) PROCEDURE Read*(VAR r&#58; Rider; VAR x&#58; SYSTEM.BYTE); VAR buf&#58; Buffer; BEGIN &#9;buf &#58;&#61; r.buf; &#9;IF r.apos # buf.apos THEN buf &#58;&#61; GetBuf(r.f, r.apos); r.buf &#58;&#61; buf END; &#9;IF r.bpos &#60; buf.lim THEN &#9;&#9;x &#58;&#61; buf.data&#91;r.bpos&#93;; INC(r.bpos) &#9;ELSIF r.apos &#60; r.f.alen THEN &#9;&#9;INC(r.apos); &#9;&#9;buf &#58;&#61; SearchBuf(r.f, r.apos); &#9;&#9;IF buf &#61; NIL THEN	(* replace a buffer *) &#9;&#9;&#9;buf &#58;&#61; r.buf; &#9;&#9;&#9;IF buf.mod THEN WriteBuf(r.f, buf) END; &#9;&#9;&#9;ReadBuf(r.f, buf, r.apos) &#9;&#9;ELSE &#9;&#9;&#9;r.buf &#58;&#61; buf &#9;&#9;END; &#9;&#9;IF buf.lim &#62; 0 THEN &#9;&#9;&#9;x &#58;&#61; buf.data&#91;0&#93;; r.bpos &#58;&#61; 1 &#9;&#9;ELSE &#9;&#9;&#9;x &#58;&#61; 0X; r.eof &#58;&#61; TRUE &#9;&#9;END &#9;ELSE &#9;&#9;x &#58;&#61; 0X; r.eof &#58;&#61; TRUE &#9;END END Read; (** Reads a sequence of length n bytes into the buffer x, advancing the Rider. Less bytes will be read when reading over the length of the file. r.res indicates the number of unread bytes. x must be big enough to hold n bytes. *) PROCEDURE ReadBytes*(VAR r&#58; Rider; VAR x&#58; ARRAY OF SYSTEM.BYTE; len&#58; LONGINT); VAR src, dst&#58; ADDRESS; m&#58; LONGINT; buf&#58; Buffer; ch&#58; CHAR; BEGIN &#9;IF LEN(x) &#60; len THEN SYSTEM.HALT(19) END; &#9;IF Slow THEN &#9;&#9;m &#58;&#61; 0; &#9;&#9;LOOP &#9;&#9;&#9;IF len &#60;&#61; 0 THEN EXIT END; &#9;&#9;&#9;Read(r, ch); &#9;&#9;&#9;IF r.eof THEN EXIT END; &#9;&#9;&#9;x&#91;m&#93; &#58;&#61; ch; INC(m); DEC(len) &#9;&#9;END; &#9;&#9;r.res &#58;&#61; len &#9;ELSE &#9;&#9;IF len &#62; 0 THEN &#9;&#9;&#9;dst &#58;&#61; ADDRESSOF(x&#91;0&#93;); buf &#58;&#61; r.buf; &#9;&#9;&#9;IF r.apos # buf.apos THEN buf &#58;&#61; GetBuf(r.f, r.apos); r.buf &#58;&#61; buf END; &#9;&#9;&#9;LOOP &#9;&#9;&#9;&#9;IF len &#60;&#61; 0 THEN EXIT END; &#9;&#9;&#9;&#9;src &#58;&#61; ADDRESSOF(buf.data&#91;0&#93;) + r.bpos; m &#58;&#61; r.bpos + len; &#9;&#9;&#9;&#9;IF m &#60;&#61; buf.lim THEN &#9;&#9;&#9;&#9;&#9;SYSTEM.MOVE(src, dst, len); r.bpos &#58;&#61; m; r.res &#58;&#61; 0; EXIT &#9;&#9;&#9;&#9;ELSIF buf.lim &#61; BufSize THEN &#9;&#9;&#9;&#9;&#9;m &#58;&#61; buf.lim - r.bpos; &#9;&#9;&#9;&#9;&#9;IF m &#62; 0 THEN SYSTEM.MOVE(src, dst, m); INC(dst, m); DEC(len, m) END; &#9;&#9;&#9;&#9;&#9;IF r.apos &#60; r.f.alen THEN &#9;&#9;&#9;&#9;&#9;&#9;INC(r.apos); r.bpos &#58;&#61; 0; buf &#58;&#61; SearchBuf(r.f, r.apos); &#9;&#9;&#9;&#9;&#9;&#9;IF buf &#61; NIL THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;buf &#58;&#61; r.buf; &#9;&#9;&#9;&#9;&#9;&#9;&#9;IF buf.mod THEN WriteBuf(r.f, buf) END; &#9;&#9;&#9;&#9;&#9;&#9;&#9;ReadBuf(r.f, buf, r.apos) &#9;&#9;&#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;&#9;&#9;r.buf &#58;&#61; buf &#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;&#9;r.bpos &#58;&#61; buf.lim; r.res &#58;&#61; len; r.eof &#58;&#61; TRUE; EXIT &#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;m &#58;&#61; buf.lim - r.bpos; &#9;&#9;&#9;&#9;&#9;IF m &#62; 0 THEN SYSTEM.MOVE(src, dst, m); r.bpos &#58;&#61; buf.lim END; &#9;&#9;&#9;&#9;&#9;r.res &#58;&#61; len - m; r.eof &#58;&#61; TRUE; EXIT &#9;&#9;&#9;&#9;END &#9;&#9;&#9;END &#9;&#9;ELSE &#9;&#9;&#9;r.res &#58;&#61; 0 &#9;&#9;END &#9;END END ReadBytes; (** Portable routines to read the standard Oberon types. &#42;) PROCEDURE ReadInt*(VAR r&#58; Rider; VAR x&#58; INTEGER); VAR x0, x1&#58; SHORTINT; BEGIN &#9;Read(r, x0); Read(r, x1); &#9;x &#58;&#61; LONG(x1) * 100H + LONG(x0) MOD 100H END ReadInt; PROCEDURE ReadLInt*(VAR r&#58; Rider; VAR x&#58; LONGINT); BEGIN &#9;ReadBytes(r, SYSTEM.VAL(Bytes4, x), 4) END ReadLInt; PROCEDURE ReadSet*(VAR r&#58; Rider; VAR x&#58; SET); BEGIN &#9;ReadBytes(r, SYSTEM.VAL(Bytes4, x), 4) END ReadSet; PROCEDURE ReadBool*(VAR r&#58; Rider; VAR x&#58; BOOLEAN); VAR s&#58; SHORTINT; BEGIN &#9;Read(r, s); x &#58;&#61; s # 0 END ReadBool; PROCEDURE ReadReal*(VAR r&#58; Rider; VAR x&#58; REAL); BEGIN &#9;ReadBytes(r, SYSTEM.VAL(Bytes4, x), 4) END ReadReal; PROCEDURE ReadLReal*(VAR r&#58; Rider; VAR x&#58; LONGREAL); BEGIN &#9;ReadBytes(r, SYSTEM.VAL(Bytes8, x), 8) END ReadLReal; PROCEDURE ReadString*(VAR r&#58; Rider; VAR x&#58; ARRAY OF CHAR); VAR i&#58; INTEGER; ch&#58; CHAR; BEGIN i &#58;&#61; 0; &#9;LOOP &#9;&#9;Read(r, ch); x&#91;i&#93; &#58;&#61; ch; INC(i); &#9;&#9;IF ch &#61; 0X THEN EXIT END; &#9;&#9;IF i &#61; LEN(x) THEN x&#91;i-1&#93; &#58;&#61; 0X; &#9;&#9;&#9;REPEAT Read(r, ch) UNTIL ch &#61; 0X; &#9;&#9;&#9;EXIT &#9;&#9;END &#9;END END ReadString; (** Reads a number in compressed variable length notation using the minimum amount of bytes. *) PROCEDURE ReadNum*(VAR r&#58; Rider; VAR x&#58; LONGINT); VAR ch&#58; CHAR; n&#58; INTEGER; y&#58; LONGINT; BEGIN &#9;n &#58;&#61; 0; y &#58;&#61; 0; Read(r, ch); &#9;WHILE ch &#62;&#61; 80X DO INC(y, LSH(LONG(ORD(ch)) - 128, n)); INC(n, 7); Read(r, ch) END; &#9;x &#58;&#61; ASH(LSH(LONG(ORD(ch)), 25), n-25) + y END ReadNum; (** Writes a byte into the file at the Rider position, advancing the Rider by one. *) PROCEDURE Write*(VAR r&#58; Rider; x&#58; SYSTEM.BYTE); VAR buf&#58; Buffer; BEGIN &#9;buf &#58;&#61; r.buf; &#9;IF r.apos # buf.apos THEN buf &#58;&#61; GetBuf(r.f, r.apos); r.buf &#58;&#61; buf END; &#9;IF r.bpos &#62;&#61; buf.lim THEN &#9;&#9;IF r.bpos &#60; BufSize THEN &#9;&#9;&#9;INC(buf.lim); INC(r.f.blen)	(* blen may become BufSize *) &#9;&#9;ELSE &#9;&#9;&#9;buf.lim &#58;&#61; BufSize;	(* used by WriteBuf *) &#9;&#9;&#9;WriteBuf(r.f, buf); INC(r.apos); buf &#58;&#61; SearchBuf(r.f, r.apos); &#9;&#9;&#9;IF buf &#61; NIL THEN &#9;&#9;&#9;&#9;buf &#58;&#61; r.buf; &#9;&#9;&#9;&#9;IF r.apos &#60;&#61; r.f.alen THEN &#9;&#9;&#9;&#9;&#9;ReadBuf(r.f, buf, r.apos) &#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;buf.apos &#58;&#61; r.apos; buf.lim &#58;&#61; 1; INC(r.f.alen); r.f.blen &#58;&#61; 1 &#9;&#9;&#9;&#9;END &#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;r.buf &#58;&#61; buf &#9;&#9;&#9;END; &#9;&#9;&#9;r.bpos &#58;&#61; 0 &#9;&#9;END &#9;END; &#9;buf.data&#91;r.bpos&#93; &#58;&#61; CHR(x); INC(r.bpos); buf.mod &#58;&#61; TRUE END Write; (** Writes the buffer x containing n bytes into a file at the Rider position. *) PROCEDURE WriteBytes*(VAR r&#58; Rider; CONST x&#58; ARRAY OF SYSTEM.BYTE; len&#58; LONGINT); VAR src, dst&#58; ADDRESS; m&#58; LONGINT; buf&#58; Buffer; BEGIN &#9;IF LEN(x) &#60; len THEN SYSTEM.HALT(19) END; &#9;IF Slow THEN &#9;&#9;m &#58;&#61; 0; &#9;&#9;WHILE len &#62; 0 DO &#9;&#9;&#9;Write(r, x&#91;m&#93;); INC(m); DEC(len) &#9;&#9;END; &#9;&#9;r.res &#58;&#61; len &#9;ELSE &#9;&#9;IF len &#62; 0 THEN &#9;&#9;&#9;src &#58;&#61; ADDRESSOF(x&#91;0&#93;); &#9;&#9;&#9;buf &#58;&#61; r.buf; &#9;&#9;&#9;IF r.apos # buf.apos THEN buf &#58;&#61; GetBuf(r.f, r.apos); r.buf &#58;&#61; buf END; &#9;&#9;&#9;LOOP &#9;&#9;&#9;&#9;IF len &#60;&#61; 0 THEN EXIT END; &#9;&#9;&#9;&#9;buf.mod &#58;&#61; TRUE; dst &#58;&#61; ADDRESSOF(buf.data&#91;0&#93;) + r.bpos; m &#58;&#61; r.bpos + len; &#9;&#9;&#9;&#9;IF m &#60;&#61; buf.lim THEN &#9;&#9;&#9;&#9;&#9;SYSTEM.MOVE(src, dst, len); r.bpos &#58;&#61; m; EXIT &#9;&#9;&#9;&#9;ELSIF m &#60;&#61; BufSize THEN &#9;&#9;&#9;&#9;&#9;SYSTEM.MOVE(src, dst, len); r.bpos &#58;&#61; m; &#9;&#9;&#9;&#9;&#9;r.f.blen &#58;&#61; m; buf.lim &#58;&#61; m; EXIT &#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;buf.lim &#58;&#61; BufSize;	(* used by WriteBuf *) &#9;&#9;&#9;&#9;&#9;m &#58;&#61; BufSize - r.bpos; &#9;&#9;&#9;&#9;&#9;IF m &#62; 0 THEN SYSTEM.MOVE(src, dst, m); INC(src, m); DEC(len, m) END; &#9;&#9;&#9;&#9;&#9;WriteBuf(r.f, buf); INC(r.apos); r.bpos &#58;&#61; 0; buf &#58;&#61; SearchBuf(r.f, r.apos); &#9;&#9;&#9;&#9;&#9;IF buf &#61; NIL THEN &#9;&#9;&#9;&#9;&#9;&#9;buf &#58;&#61; r.buf; &#9;&#9;&#9;&#9;&#9;&#9;IF r.apos &#60;&#61; r.f.alen THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;ReadBuf(r.f, buf, r.apos) &#9;&#9;&#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;&#9;&#9;buf.apos &#58;&#61; r.apos; buf.lim &#58;&#61; 0; INC(r.f.alen); r.f.blen &#58;&#61; 0 &#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;&#9;r.buf &#58;&#61; buf &#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;END &#9;&#9;&#9;END &#9;&#9;END &#9;END END WriteBytes; (** Portable routines to write the standard Oberon types. &#42;) PROCEDURE WriteInt*(VAR r&#58; Rider; x&#58; INTEGER); BEGIN &#9;Write(r, SHORT(x)); Write(r, SHORT(x DIV 100H)) END WriteInt; PROCEDURE WriteLInt*(VAR r&#58; Rider; x&#58; LONGINT); BEGIN &#9;WriteBytes(r, SYSTEM.VAL(Bytes4, x), 4) END WriteLInt; PROCEDURE WriteSet*(VAR r&#58; Rider; x&#58; SET); BEGIN &#9;WriteBytes(r, SYSTEM.VAL(Bytes4, x), 4) END WriteSet; PROCEDURE WriteBool*(VAR r&#58; Rider; x&#58; BOOLEAN); BEGIN &#9;IF x THEN Write(r, 1) ELSE Write(r, 0) END END WriteBool; PROCEDURE WriteReal*(VAR r&#58; Rider; x&#58; REAL); BEGIN &#9;WriteBytes(r, SYSTEM.VAL(Bytes4, x), 4) END WriteReal; PROCEDURE WriteLReal*(VAR r&#58; Rider; x&#58; LONGREAL); BEGIN &#9;WriteBytes(r, SYSTEM.VAL(Bytes8, x), 8) END WriteLReal; PROCEDURE WriteString*(VAR r&#58; Rider; CONST x&#58; ARRAY OF CHAR); VAR i&#58; INTEGER; ch&#58; CHAR; BEGIN &#9;i &#58;&#61; 0; &#9;LOOP ch &#58;&#61; x&#91;i&#93;; Write(r, ch); INC(i); &#9;&#9;IF ch &#61; 0X THEN EXIT END; &#9;&#9;IF i &#61; LEN(x) THEN Write(r, 0X); EXIT END &#9;END END WriteString; (** Writes a number in a compressed format. *) PROCEDURE WriteNum*(VAR r&#58; Rider; x&#58; LONGINT); BEGIN &#9;WHILE (x &#60; - 64) OR (x &#62; 63) DO Write(r, CHR(x MOD 128 + 128)); x &#58;&#61; x DIV 128 END; &#9;Write(r, CHR(x MOD 128)) END WriteNum; (** Deletes a file. res &#61; 0 indicates success. *) PROCEDURE Delete*(name&#58; ARRAY OF CHAR; VAR res&#58; INTEGER); VAR r&#58; LONGINT; BEGIN &#9;Files.Delete(name, r); &#9;IF (r &#62;&#61; MIN(INTEGER)) &#38; (r &#60;&#61; MAX(INTEGER)) THEN res &#58;&#61; SHORT(r) ELSE res &#58;&#61; -1 END END Delete; (** Renames a file. res &#61; 0 indicates success. *) PROCEDURE Rename*(CONST old, new&#58; ARRAY OF CHAR; VAR res&#58; INTEGER); VAR r&#58; LONGINT; BEGIN &#9;Files.Rename(old, new, r); &#9;IF (r &#62;&#61; MIN(INTEGER)) &#38; (r &#60;&#61; MAX(INTEGER)) THEN res &#58;&#61; SHORT(r) ELSE res &#58;&#61; -1 END END Rename; (** Returns the full name of a file. *) PROCEDURE GetName*(f&#58; File; VAR name&#58; ARRAY OF CHAR); BEGIN &#9;f.r.file.GetName(name) END GetName; PROCEDURE ReadBuf(f&#58; File; buf&#58; Buffer; pos&#58; LONGINT); VAR file&#58; Files.File; BEGIN &#9;file &#58;&#61; f.r.file; &#9;file.Set(f.r, pos*BufSize); &#9;ASSERT(file.Pos(f.r) &#61; pos*BufSize); &#9;file.ReadBytes(f.r, buf.data, 0, BufSize); &#9;IF pos &#60; f.alen THEN buf.lim &#58;&#61; BufSize ELSE buf.lim &#58;&#61; f.blen END; &#9;buf.apos &#58;&#61; pos; buf.mod &#58;&#61; FALSE; END ReadBuf; PROCEDURE WriteBuf(f&#58; File; buf&#58; Buffer); VAR pos, n&#58; LONGINT; file&#58; Files.File; BEGIN &#9;file &#58;&#61; f.r.file; &#9;pos &#58;&#61; buf.apos*BufSize; &#9;n &#58;&#61; pos - file.Length; &#9;IF n &#62; 0 THEN	(* pos is past current eof, extend file *) &#9;&#9;file.Set(f.r, file.Length); &#9;&#9;WHILE n &#62; 0 DO file.Write(f.r, 0X); DEC(n) END &#9;END; &#9;file.Set(f.r, pos); &#9;ASSERT(file.Pos(f.r) &#61; pos); &#9;file.WriteBytes(f.r, buf.data, 0, buf.lim); &#9;UpdateFile(f); &#9;buf.mod &#58;&#61; FALSE END WriteBuf; PROCEDURE SearchBuf(f&#58; File; pos&#58; LONGINT)&#58; Buffer; VAR buf&#58; Buffer; BEGIN &#9;buf &#58;&#61; f.buf; &#9;LOOP &#9;&#9;IF buf.apos &#61; pos THEN EXIT END; &#9;&#9;buf &#58;&#61; buf.next; &#9;&#9;IF buf &#61; f.buf THEN buf &#58;&#61; NIL; EXIT END &#9;END; &#9;RETURN buf END SearchBuf; PROCEDURE GetBuf(f&#58; File; pos&#58; LONGINT)&#58; Buffer; VAR buf&#58; Buffer; BEGIN &#9;buf &#58;&#61; f.buf; &#9;LOOP &#9;&#9;IF buf.apos &#61; pos THEN EXIT END; &#9;&#9;IF buf.next &#61; f.buf THEN &#9;&#9;&#9;IF f.bufs &#60; MaxBufs THEN &#9;&#9;&#9;&#9;NEW(buf); buf.next &#58;&#61; f.buf.next; f.buf.next &#58;&#61; buf; &#9;&#9;&#9;&#9;INC(f.bufs) &#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;f.buf &#58;&#61; buf; &#9;&#9;&#9;&#9;IF buf.mod THEN WriteBuf(f, buf) END &#9;&#9;&#9;END; &#9;&#9;&#9;buf.apos &#58;&#61; pos; &#9;&#9;&#9;IF pos &#60;&#61; f.alen THEN ReadBuf(f, buf, pos) END;	(* ELSE? *) &#9;&#9;&#9;EXIT &#9;&#9;END; &#9;&#9;buf &#58;&#61; buf.next &#9;END; &#9;RETURN buf END GetBuf; PROCEDURE Update(f&#58; File); VAR buf&#58; Buffer; BEGIN &#9;buf &#58;&#61; f.buf; &#9;REPEAT &#9;&#9;IF buf.mod THEN WriteBuf(f, buf) END; &#9;&#9;buf &#58;&#61; buf.next &#9;UNTIL buf &#61; f.buf; &#9;f.r.file.Update;	(* update the underlying file also *) &#9;UpdateFile(f) END Update; PROCEDURE WriteFile(f&#58; File); VAR name&#58; ARRAY 64 OF CHAR; BEGIN &#9;IF Trace THEN &#9;&#9;KernelLog.Hex(SYSTEM.VAL(LONGINT, f), 8); KernelLog.Char(" "); &#9;&#9;KernelLog.Hex(SYSTEM.VAL(LONGINT, f.r.file), 1); KernelLog.Char(" "); &#9;&#9;KernelLog.Int(Length(f), 1); KernelLog.Char(" "); &#9;&#9;KernelLog.Int(f.r.file.Length, 1); KernelLog.Char(" "); &#9;&#9;GetName(f, name); &#9;&#9;KernelLog.String(name) &#9;END END WriteFile; (* debugging *) (* PROCEDURE ShowList*; VAR &#9;enum&#58; OBJECT &#9;&#9;VAR i&#58; LONGINT; &#9;&#9;PROCEDURE EnumFile(f&#58; ANY; VAR cont&#58; BOOLEAN); &#9;&#9;BEGIN &#9;&#9;&#9;WITH f&#58; File DO &#9;&#9;&#9;&#9;KernelLog.Int(i, 1); KernelLog.Char(" "); &#9;&#9;&#9;&#9;WriteFile(f); KernelLog.Ln; &#9;&#9;&#9;&#9;INC(i) &#9;&#9;&#9;END &#9;&#9;END EnumFile; &#9;END; BEGIN &#9;NEW(enum); enum.i &#58;&#61; 0; KernelLog.Ln; &#9;files.Enumerate(enum.EnumFile) END ShowList; &#42;) BEGIN &#9;NEW(files) END Files. (** Remarks&#58; 1. Oberon uses the little-endian byte ordering for exchanging files between different Oberon platforms. 2. Files are separate entities from directory entries. Files may be anonymous by having no name and not being registered in a directory. Files only become visible to other clients of the Files module by explicitly passing a File descriptor or by registering a file and then opening it from the other client. Deleting a file of which a file descriptor is still available, results in the file becoming anonymous. The deleted file may be re-registered at any time. 3. Files and their access mechanism (Riders) are separated. A file might have more than one rider operating on it at different offsets in the file. 4. The garbage collector will automatically close files when they are not required any more. File buffers will be discarded without flushing them to disk. Use the Close procedure to update modified files on disk. 5. Relative and absolute filenames written in the directory syntax of the host operating system are used. By convention, Oberon filenames consists of the letters A..Z, a..z, 0..9, and ".". The directory separator is typically / or &#58;. Oberon filenames are case sensitive. *) (* to do&#58; o Rename duplicate methods/procedures in Files (e.g. Register0 method) o remove Read/Write methods to encourage buffering (bad idea?) - handle case where underlying file is changed by someone else (e.g. a log file being written by an active object) - check if file handle is a good "key" (yes, because it can not be re-used while we hold it in the list, through the rider) &#42;)