DEFINITION MODULE DMFiles;
(*******************************************************************
Module DMFiles ('Dialog Machine' DM_V3.0)
Copyright (c) 1986-2006 by Andreas Fischlin and ETH Zurich.
Purpose Manages text files and provides routines to read or
write sequentially and randomly.
Remarks All elementary data types can be read or written.
Besides the set of common file system routines present
in many Modula-2 implementations, this module exports
procedures to access files with the standard MiniFinder
dialog boxes of the Apple Macintoshª computer.
This module belongs to the 'Dialog Machine'.
Programming
o Design
Andreas Fischlin 15/02/1986
o Implementation
Andreas Fischlin 15/02/1986
ETH Zurich
Systems Ecology
CHN E 35.1
Universitaetstrasse 16
8092 Zurich
SWITZERLAND
URLs:
<mailto:RAMSES@env.ethz.ch>
<http://www.sysecol.ethz.ch>
<http://www.sysecol.ethz.ch/SimSoftware/RAMSES>
Last revision of definition: 29/05/2005 AF
*******************************************************************)
FROM SYSTEM IMPORT BYTE;
(*********************************)
(*##### File Management #####*)
(*********************************)
TYPE
Response = (done, filenotfound, volnotfound, cancelled, unknownfile,
toomanyfiles, diskfull, memfull, alreadyopened, isbusy,
locked, notdone);
(* Results of all file operations are returned via the Response
type; check the result field in the TextFile Record for success
of operation (For advanced programers see also
LastResultCode). The following meaning applies:
filenotfound File could not be accessed, e.g. typically because
it does not exist physically
volnotfound Path information refers to a non-existing volume
or directory (platform dependent, since volumes are
supported only on the Macintosh and IBM PC platforms;
this result is returned on a Unix platform only for a
path involving a non existing directory; note, OS X is
in this respect considered a Macintosh and not a Unix
system)
cancelled User cancelled (only possible in an interactive
'Dialog Machine')
unknownfile Actual parameter (of type TextFile) designates a file
which is not known to the 'Dialog Machine'. This
value may also be returned if the file name contains
illegal characters or is otherwise malformed, the case
where no file with such a name could never exist (the
latter behavior is of course platform dependent).
toomanyfiles Results from an attempt to open a file exceeding the
limitations of the underlying operating system or the
'Dialog Machine' of how many files can be opened
simultaneously
diskfull Disk space has been exceeded
memfull No more heap space available (RAM limitation exceeded)
alreadyopened Results from an attempt to open a file which is
already open, typically by another process
isbusy File is already opened by another process or offers
otherwise not even read access
locked File is locked or permissions are insufficient for
the requested access such as writing or deletion
notdone Some error condition encountered which is not covered
by any of the above.
*)
HiddenFileInfo;
IOMode = (reading, writing);
TextFile = RECORD
res: Response;
filename: ARRAY [0..255] OF CHAR;
path: ARRAY [0..63] OF CHAR;
curIOMode: IOMode; (*current input/output mode*)
curChar: CHAR; (*char at current file position, last
char written or char not yet read;
if EOF = TRUE value of curChar
may not be defined*)
fhint: HiddenFileInfo;
END;
VAR
neverOpenedFile: TextFile; (* read only var! *)
(*
May be used for variables of type TextFile to denote that
the associated file has actually not yet been opened (e.g.
by a call to one of the following procedures:
GetExistingFile, CreateNewFile, Lookup, or ReadOnlyLookup.
It is a recommended programming practice to assign this
value to all file variables during the initialization phase
of the "Dialog Machine" program.
*)
(*
Typically one of the following two procedures are used to open
or create text files. Their usage is highly recommended.
*)
PROCEDURE GetExistingFile(VAR f: TextFile; prompt: ARRAY OF CHAR);
(*
Uses the standard Macintosh dialog box (displaying the prompt
string) to let the user select and open an existing text file. If
f.res = done, f is open for subsequent reading (implicit Reset) or
writing (call Rewrite before writing). Note, to succeed, the file
needs not only read, but also write permissions, since routines,
Rewrite and AlterIOMode can be used for writing with a file opened
by GetExistingFile.
*)
PROCEDURE CreateNewFile(VAR f: TextFile; prompt, defaultName: ARRAY OF CHAR);
(*
Uses the standard Macintosh dialog box (displaying the prompt
string) to let the user enter the name of a text file which
is to be created . Initially the defaultName is displayed and
may be accepted by just pushing the OK-button or a new file
name may be entered. If a file with the same name exists
already, the user is asked whether he wants to overwrite the
old file content or not. If f.res = done then f is open for
subsequent writing (implicit Rewrite).
*)
(*
o The following procedures support file operations controlled
solely by the program without user interactions. If any user
dialog is involved, the exclusive usage of the two procedures
GetExistingFile respectively CreateNewFile is recommended.
o Files are located via strings containing an optional
path and a file name specification.
o Path specifications (MacOS Classic) must follow this syntax
given in EBNF (start symbol is path):
path = [VolName] { ':' FolderName | ':' } ':'.
VolName = String.
FolderName = String.
String = char { char }.
(Remark: all characters (including ' ') are allowed
except ':', however it is recommended to minimize in
general the use of special characters in folder and
file names.
There are absolute and relative path specifications. An
absolute path specification starts with the name of the
volume, a relative path refers to a so-called current working
directory as the starting point and must begin with a ':'.
(See also below the point on path definitions. They can be
used to define the default search strategy contained in the
so-called User.Profile).
o Path specifications (Mac OS X, Unix) must follow this syntax
given in EBNF (start symbol is path):
path = ['/'] { FolderName | './' | '../' } ['/'].
FolderName = String.
String = char { char }.
(Remark: all characters (including ' ') are allowed except
'/', however it is recommended to avoid the use of special
characters. In particular don't use '*' or '?' in folder or
file names due to operating system idiosynchrasies. Under
Unix not only occasionally, but even often do special
characters cause program failures.
There are absolute and relative path specifications. An
absolute path specification starts with '/', a relative with
another character. The folder where the running application
has been launched is called the current working directory and
relative paths specify paths relative to that directory. (See
also below the point on path definitions. They can be used to
define the default search strategy contained in the Unix
environment variable M2PATH).
o File name specifications must follow the same syntax as that
of folders as described in above syntax.
o Files are located following the so-called default search
strategy. It uses path definitions which must be given
in the text file with the name profileFName
(User.Profile, MacOS Classic) or config.M2PATH.sh (Mac
OS X, Unix) or be defined in the environment variable
M2PATH (Mac OS X, Unix). The latter file must reside in
the same folder as the starting application (usually the
shell or any other linked stand-alone application made
with the Dialog Machine). The file profileFName
(User.Profile) is normally read only at start up time of
the running Modula-2 application, i.e. the MacMETH,
RAMSES shell or the produced Dialog Machine program.
o Path definitions support the default search strategy
used when locating files.
Under MacOS Classic they must be given in the file profileFName
(User.Profile) and have to follow this syntax given in EBNF (start
symbol is PathDefinition, the syntax of the symbol path is that given
above):
PathDefinition = 'PATH' path { ',' path }.
Ex.:
PATH
HD:M2:Work:, :DMLib:, :RAMSESLib:,
:Work:MyProject:,
Another Disk:M2:Work:Another Project:,
::Utilities:
Remark to the semantics of path definitions: A
path definition starting with ':' signifies a relative
path, i.e. a path which starts at the folder in which the
file with name profileFName (User.Profile) resides. If
one uses absolute paths, they must never start with ':' and
then always indicate a full path starting with the volume
name. In the example given below the last path using the
folder Utilities is preceeded by the construct '::' is
located relative to the start-up folder in which the file
with name profileFName (User.Profile) resides at a level
above (father/mother level). Never forget to append ':'
at the end of a path specification.
Under OS X or Unix the patch specifications follow the Unix
standards. please observe to the help information as provided by
that operating system.
o Filename arguments using the identifier pathOrFileName in the
formal paramater list may may or may not contain a path
before the actual file name. Regardless whether this
parameter does contain a path specification or not the file
is searched according to the default search strategy
explained below.
o Upon returning from a successful call to a locating procedure
f.filename and f.path contain the actual values, in particular
f.filename is always stripped from any preceeding path and
f.path contains a path with a path separator at the end,
unless the path is empty.
o Path definitions can be changed dynamically during program
execution but must then be reentered into the system by
calling routine DMSubLaunch.SetNewPaths.
*)
PROCEDURE Lookup(VAR f: TextFile; pathOrFileName: ARRAY OF CHAR; new: BOOLEAN);
(*
If new = TRUE an eventually already existing file with the same
name is overwritten. If new = FALSE, f's initial IOMode is
reading, else writing. Note, to succeed in case of new = FALSE,
the file needs not only read, but also write permissions, since
routines, Rewrite and AlterIOMode can be used for writing to f.
To require only read access, use routine ReadOnlyLookup.
*)
PROCEDURE ReadOnlyLookup(VAR f: TextFile; pathOrFileName: ARRAY OF CHAR);
(*
Same as Lookup but only reading and no writing to the file
allowed. Note, many operations such as Delete, Rename, Rewrite,
AppendAtEOF, and AlterIOMode to a writing mode do all fail on a
file opened by this routine. In contrast to all other file
opening routines, to succeed this routine does only require read
permissions.
*)
PROCEDURE IsOpen (VAR f: TextFile): BOOLEAN;
PROCEDURE FileExists(VAR f: TextFile): BOOLEAN;
(*
Precondition for procedure FileExists: f may or may not be
open. f.filename is used as a pathOrFileName input parameter in
the way described above. If the file could be located TRUE is
returned else FALSE and the current status of the file (opened,
closed) is always left untouched.
*)
PROCEDURE FileLevel(VAR f: TextFile): CARDINAL;
(*
Returns the level of the sub-program on which the file f has
been created. If f does not exist DMSystem.startUpLevel-1, i.e.
0 is returned.
*)
(*
Precondition for all following procedures: f currently open.
*)
PROCEDURE Close(VAR f: TextFile);
PROCEDURE Delete(VAR f: TextFile); (* implicitely closes *)
PROCEDURE Rename(VAR f: TextFile; newfilename: ARRAY OF CHAR);
(* newfilename must not contain a path *)
PROCEDURE FileSize(VAR f: TextFile): LONGINT;
(*
text files may only be used sequentially for either reading or
writing. Call Reset or Rewrite to change I/O mode.
*)
PROCEDURE Reset(VAR f: TextFile);
(*
Prepares file f for reading and sets the file position to the
beginning. Any attempt to read past EOF will result in a
program halt.
*)
PROCEDURE Rewrite(VAR f: TextFile);
(*
Prepares file f for writing and sets the file position to the
beginning (any previously stored file content is lost). EOF
becomes TRUE.
*)
PROCEDURE AppendAtEOF(VAR f: TextFile);
(*
Prepares file f for writing and sets the file position at the
end of the data it currently contains (eventually existing
file content is preserved otherwise equivalent to a Rewrite).
EOF becomes TRUE.
*)
(*****************************)
(*##### IO routines #####*)
(*****************************)
PROCEDURE EOF(VAR f: TextFile): BOOLEAN;
(*
Returns whether or not the end of the file has been reached. This
routine is essential, since attempts to read past EOF, i.e. while
EOF returns TRUE, result in a program halt. While writing,
typically at the end of a file, EOF is always TRUE. For an empty
file in the open state, EOF returns TRUE. IMPLEMENTATION
RESTRICTION: For efficiency reasons this routine assumes as actual
parameter a valid file in the open state. Otherwise it will fail,
typically abort the program (if halt for dereference NIL pointer
enabled) or return unpredictable results.
*)
PROCEDURE Again(VAR f: TextFile);
(*
Last read char may be reread again. NOTE: Again treats the end of
line (EOL symbol) always like a symbol consisting of a single
character. This means that on a machine implementing the end of line
with a single character (Macintosh, Unix), the result is simple and
reverses always the reading of the last sequential reading operation.
In particular this means that the result is the same, regardless
wether sequential reading was done by ReadByte, ReadChar, or
ReadChars. However, on machines like the IBM PC, where 2 characters
are used to mark an end of line (EOL), the result corresponds only to
reverting the effect of ReadChar or ReadChars. You can't revert the
effect of a single call to ReadByte if ReadByte just read the 2nd
character of the end of line mark. Then Again reverts the effect
of 2 consecutive calls to ReadByte.
*)
(*
the following two routines make input/output without performing
any interpretations:
*)
PROCEDURE ReadByte(VAR f: TextFile; VAR b: BYTE);
PROCEDURE WriteByte(VAR f: TextFile; b: BYTE);
CONST EOL = 36C; (* = ASCII RS. This is the char returned by
ReadChar if an end of line has been
encountered. Internally the Macintosh uses
15C = ASCII CR, Unix uses 12C = ASCII LF, and
IBM PC (DOS, Windows) uses two bytes, i.e.
15C followed by 10C (ASCII CR LF). *)
(*
the following input/output routines interprete certain characters,
such as e.g. EOL:
*)
PROCEDURE ReadChar(VAR f: TextFile; VAR ch: CHAR);
(*if an end of line mark is encountered ch=EOL is returned*)
PROCEDURE SkipGap(VAR f: TextFile);
(*
Skip all chars<=" ", i.e. a gap. Result is that the
sequential reading stops right before the next char>" "
(ready for next sequential reading operation) or before an
EOF. NOTE: A gap consists of any number of white space
(blanks and horizontal tabulators), plus all non-printing
characters including any end of line symbols! If you wish
to stop the reading process at the EOL, use routine
SkipGapWithinLn. Note also, this routine can be called
safely at EOF.
*)
PROCEDURE SkipGapWithinLn(VAR f: TextFile);
PROCEDURE AtEOL(VAR f: TextFile): BOOLEAN;
PROCEDURE SkippedUpToToken (VAR f: TextFile): BOOLEAN;
(*
SkipGapWithinLn functions similar to SkipGap, but does not
read beyond EOL. Result is that the sequential reading stops
right before the next char>" " (ready for next sequential
reading operation), or before an EOF, or after an EOL
(AtEOL returns TRUE). SkipGapWithinLn is useful when reading
tabulated items arranged in matrix form. Typical code to
read a file line per line but otherwise format free:
WHILE NOT EOF(f) DO
(* read a line *)
SkipGapWithinLn(f);
WHILE NOT (AtEOL(f) AND NOT EOF(f) DO
ReadChars(f,item); (* deal with item; *) SkipGapWithinLn(f);
END(*WHILE*);
(* deal with line *)
END(*WHILE*);
Since SkipGapWithinLn stops reading after having encountered an EOL it
may be useful to know whether another SkipGapWithinLn, e.g. at the
begin of the next line, is needed before reading of an actual item is
to be started. Use SkippedUpToToken to learn about this. It returns
TRUE only if the reading has stopped right before the next non-white
space token (chars>" ").
*)
PROCEDURE ReadChars(VAR f: TextFile; VAR string: ARRAY OF CHAR);
(*
Read from the current file position till the last char
before a char<=" " would be encountered (Note: If string is
too small, reading stops, although next character, which has
not yet been read, may be actually > " ").
*)
PROCEDURE WriteChar(VAR f: TextFile; ch: CHAR);
PROCEDURE WriteEOL(VAR f: TextFile); (* = Write(EOL) *)
PROCEDURE WriteChars(VAR f: TextFile; string: ARRAY OF CHAR);
PROCEDURE WriteVarChars(VAR f: TextFile; VAR string: ARRAY OF CHAR);
VAR
legalNum: BOOLEAN; (*indicates whether read string conforms
to legal Modula-2 number syntax*)
PROCEDURE GetCardinal(VAR f: TextFile; VAR c: CARDINAL);
PROCEDURE GetLongCard(VAR f: TextFile; VAR c: LONGCARD);
(*
Equivalent to statement sequence SkipGap, ReadChars, plus
interpretation of the read string as a cardinal. legalNum :=
c = cardinal
*)
PROCEDURE PutCardinal(VAR f: TextFile; c: CARDINAL; n: CARDINAL);
PROCEDURE PutLongCard(VAR f: TextFile; c: LONGCARD; n: CARDINAL);
(*
Write cardinal c with at least n chars onto file f, insert
leading blanks if necessary
*)
PROCEDURE GetInteger(VAR f: TextFile; VAR i: INTEGER);
PROCEDURE GetLongInt(VAR f: TextFile; VAR i: LONGINT);
(*
Equivalent to statement sequence SkipGap, ReadChars, plus
interpretation of the read string as an integer. legalNum :=
c = integer
*)
PROCEDURE PutInteger(VAR f: TextFile; i: INTEGER; n: CARDINAL);
PROCEDURE PutLongInt(VAR f: TextFile; i: LONGINT; n: CARDINAL);
(*
Write integer i with at least n chars onto file f, insert
leading blanks if necessary
*)
PROCEDURE GetReal(VAR f: TextFile; VAR x: REAL);
PROCEDURE GetLongReal(VAR f: TextFile; VAR x: LONGREAL);
(*
Equivalent to statement sequence SkipGap, ReadChars, plus
interpretation of the read string as a real. legalNum := c =
real
*)
PROCEDURE PutReal(VAR f: TextFile; x: REAL; n, dec: CARDINAL);
PROCEDURE PutLongReal(VAR f: TextFile; x: LONGREAL; n,dec: CARDINAL);
(*
Write real x in fixed notation with at least n chars onto
file f, insert leading blanks if necessary, use dec digits
after the decimal point
*)
PROCEDURE PutRealSci(VAR f: TextFile; x: REAL; n: CARDINAL);
PROCEDURE PutLongRealSci(VAR f: TextFile; x: LONGREAL; n,dec: CARDINAL);
(*
Write real x in scientific notation with at least n chars
onto file f, insert leading blanks if necessary
*)
(*****************************************************************)
(*##### Advanced IO routines (supporting random access) #####*)
(*****************************************************************)
PROCEDURE AlterIOMode (VAR f: TextFile; newMode: IOMode);
(*
Lets you change the current IOMode to a new setting. Make
sure that you use this feature cautiously, since it may
disturb proper functioning of an algorithm relying on
sequential files as they are normally used by above,
safer routines. Note also that AlterIOMode is not
possible with files which have been opened as read
only (see above ReadOnlyLookup).
*)
PROCEDURE SetFilePos( VAR f: TextFile; pos: LONGINT );
(*
Sets current position in file "f" to "pos". Any reading
from the file by means of procedures exported from this
module (e.g. "Read...", "Get...", "SkipGap") will now start
at character with position "pos"+1 and will increment the
current position by the number of characters or bytes read.
The same applies to writing onto the file by means of
"Write..." or "Put..."-procedures. Remark: a call to
"Again" decrements the current position by 1.
*)
PROCEDURE GetFilePos( VAR f: TextFile; VAR pos: LONGINT );
(* Returns current position in file "f" *)
PROCEDURE ReadByteBlock ( VAR f: TextFile; VAR buf: ARRAY OF BYTE;
VAR count: LONGINT );
(*
Read "count" bytes from file "f" into buffer "buf",
starting one byte after the current file position.
Precondition: file has been opened in the IOMode reading.
"count" returns the number of bytes succesfully read.
*)
PROCEDURE WriteByteBlock( VAR f: TextFile; VAR buf: ARRAY OF BYTE;
VAR count: LONGINT );
(*
Write "count" bytes from buffer "buf" onto file "f",
starting one byte after the current file position.
Precondition: file has been opened in the IOMode writing.
VAR-parameter "buf" for speed-up reasons only. "count"
returns the number of bytes succesfully written.
*)
(**********************************************************************)
(*##### Routines only effective in Macintosh implementations #####*)
(**********************************************************************)
PROCEDURE SetFileFilter(ft1,ft2,ft3,ft4: ARRAY OF CHAR);
PROCEDURE GetFileFilter(VAR ft1,ft2,ft3,ft4: ARRAY OF CHAR);
(*
GetExistingFile will display files for selection only which
match the file types as defined by the last call to SetFileFilter.
A file type or a file's creator are defined by exactly 4
characters, e.g. "TEXT" A file filter can contain up to 4
such file types. The above procedures allow to change the
default filter used by this module to select the files for
display by procedure GetExistingFile. The initial file
filter is set to just one type, namely the standard type
"TEXT". This is in accordance with the fact that the
procedures CreateNewFile and Lookup create by default only
files of this type.
To display any kind of files specify all four file types as
empty strings ( SetFileFilter("","","","") ). NOTE:
Regardless of the type specified by the filter, this module
will not behave differently. For instance, changing the file
filter won't have any effect on the functions performed by
this module nor on the file's content.
GetFileFilter returns the currently used file filters.
For instance you may wish to save first the old filter
before overwriting it with your new one. Once you're
done with your operations, you may restore the file filter
to its previous value.
A typical usage of this feature is to call
UseAsTypeAndCreator before creating a file by means of this
module. This allows to associate certain files with a
particular Dialog Machine program. E.g. a modelling
environment storing and loading models of a particular type
of files. Changing the filter will allow only to selectively
access such files, in order to hide other kinds of files from
the user. Moreover note that on the Macintosh the file
type may determine the appearance of the file's icon as
it is displayed by the Finder on the desktop.
*)
PROCEDURE HasTypeAndCreator(VAR f: TextFile;
VAR filetype,creator: ARRAY OF CHAR);
(*
Returns the type and creator of the file f. (Implementation
note: the file f is only specified by the record fields path
and filename. The routine functions also regardless wether
the file is currently open or not. Except for the field
f.res, none of the record fields are changed by this routine,
nor is f's current status (opened/closed) affected.
*)
PROCEDURE UseAsTypeAndCreator(filetype,creator: ARRAY OF CHAR);
PROCEDURE UsedTypeAndCreator(VAR filetype,creator: ARRAY OF CHAR);
(*
When a file is created by means of CreateNewFile or Lookup,
its type and creator will be filetype and creator as set
by the last call to UseAsTypeAndCreator. Default is:
UseAsTypeAndCreator('TEXT','MEDT'); The latter is the creator
of the MEdit editor (part of RAMSES software available from
Systems Ecology, Swiss Federal Institute of Technology ETH,
Zürich, Switzerland
E-Mail address: RAMSES@ito.umnw.ethz.ch). UsedTypeAndCreator returns the currently
used type and creator. Note: The resetting to the defaults
is done automatically upon quitting the environment of this
module.
*)
PROCEDURE LastResultCode(): INTEGER;
(*
Returns last result code obtained from a call to an internal
system routine, which has been involved in a file operation.
Since Response does only cover some of the more frequently
encountered errors when operating on files, this routine
allows to learn more about all possible error causes
maintained by the Macintosh computer's file system. For the
interpretation of result codes you have to refer to Inside
Macintosh, Volumes I-VI, Addison Wesley Publishing Company,
Inc., Reading a.o., 1986-1991.
*)
END DMFiles.