USINGISO.STU 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  1. Introduction - Hello ISO World
  2. ==============================
  3. First the traditional "Hello world" program. This requires only two
  4. procedures from the Simple Text I/O module STextIO: WriteString to output
  5. the greeting string, and WriteLn to output an end-of-line mark.
  6. MODULE HelloISO;
  7. (*
  8. * The traditional minimal program - using the ISO Standard I/O library.
  9. *)
  10. IMPORT STextIO; (* 'Simple' - ie stdin & stdout - text I/O *)
  11. BEGIN
  12. STextIO.WriteString ("Hello ISO World");
  13. STextIO.WriteLn;
  14. END HelloISO.
  15. Note that output from STextIO goes to the standard output device (or channel,
  16. to use the ISO Standard term) stdout, which is by default the terminal screen
  17. but can be redirected by operating system command line redirection (">" or
  18. ">>") to a file. (The I/O library also offers redirection from the program -
  19. see StdChans later.)
  20. Copying files - 1 - stdin to stdout
  21. ===================================
  22. Now let us move on to a program which also uses input - a simple copy file
  23. utility. We will develop several alternative versions in order to try out
  24. various I/O procedures. The copy program will also serve as a useful partial
  25. solution to any problem which reads a file, processes the data, and writes an
  26. output file.
  27. First, the obvious strategy: repetitively read a character, write that
  28. character, until end of file. This translates into a Modula LOOP loop
  29. (this form of loop is appropriate since the read must precede the termination
  30. test, which depends on the result of the read attempt, and the write follows
  31. a successful read - thus this is the classic "n&1/2-times" loop, with the
  32. termination test in the middle). The loop uses STextIO.ReadChar to read the
  33. characters, SIOResult.ReadResult to check the result of the read, and
  34. STextIO.WriteChar to write the characters. The complication is that the
  35. Standard treats an end-of-line mark as a special entity, not a normal
  36. character; thus when ReadChar attempts to read an end-of-line mark, the read
  37. fails - ReadResult returns a special status, and STextIO.SkipLine must be
  38. used to consume the end-of-line mark. If this is not done, the read process
  39. will never get past the end-of-line mark - ReadChar will continue to return
  40. the endOfLine result. [Note 1]
  41. MODULE Copy1;
  42. (*
  43. * Copy a file - version 1 - STextIO.ReadChar/WriteChar
  44. *)
  45. IMPORT STextIO, SIOResult;
  46. IMPORT ProgArgs;
  47. VAR
  48. ch : CHAR;
  49. BEGIN
  50. LOOP
  51. STextIO.ReadChar (ch);
  52. CASE SIOResult.ReadResult() OF
  53. | SIOResult.endOfInput : EXIT;
  54. | SIOResult.endOfLine : STextIO.SkipLine;
  55. STextIO.WriteLn;
  56. | SIOResult.allRight : STextIO.WriteChar (ch);
  57. ELSE
  58. ProgArgs.Assert (FALSE, "Strange read result");
  59. END;
  60. END (* LOOP *) ;
  61. END Copy1.
  62. As explained by its definition, SkipLine in fact skips any number of
  63. characters up to and including the next end-of-line mark. Thus it can be
  64. called at any time to discard the rest of the current line of input. As it is
  65. used by Copy1, there is always only the end-of-line mark to be skipped.
  66. Note too that every line is terminated by an end-of-line mark, including the
  67. last. Copy1 terminates by reading all the normal characters of the last line,
  68. then attempting to read the end-of-line mark and failing with the endOfLine
  69. result, consuming the end-of-line mark with SkipLine, then attempting to read
  70. the first character of a further line and failing with the endOfInput result.
  71. As noted above, Copy1 can be used to copy a source file to a destination file
  72. (instead of just copying keyboard input (stdin) to display output (stdout))
  73. by operating system command line redirection:
  74. Copy1 < inputFile > outputFile
  75. Copying files - 2 - named files
  76. ===============================
  77. Alternatively, the program can explicitly read from and write to specified
  78. files. In this case, the TextIO module is used instead of STextIO. All the
  79. facilities of STextIO are duplicated in TextIO, with an extra first
  80. parameter, which specifies the file from or to which the I/O occurs (the
  81. Standard refers to them as "channels", emphasising that the source or
  82. destination of I/O may not be a disk file - it could be a communications
  83. line, or some other specialised device, or even the keyboard and display).
  84. Thus the body of Copy1 is simply modified to produce Copy2:
  85. MODULE Copy2;
  86. (*
  87. * Copy a file - version 2 - TextIO.ReadChar/WriteChar
  88. *)
  89. IMPORT StreamFile, TextIO, IOResult;
  90. IMPORT ProgArgs;
  91. VAR
  92. in, out : StreamFile.ChanId;
  93. ch : CHAR;
  94. BEGIN
  95. ... more needed here ...
  96. LOOP
  97. TextIO.ReadChar (in, ch);
  98. CASE IOResult.ReadResult(in) OF
  99. | IOResult.endOfInput : EXIT;
  100. | IOResult.endOfLine : TextIO.SkipLine (in);
  101. TextIO.WriteLn (out);
  102. | IOResult.allRight : TextIO.WriteChar (out, ch);
  103. ELSE
  104. ProgArgs.Assert (FALSE, "Strange read result");
  105. END;
  106. END (* LOOP *) ;
  107. ... more needed here ...
  108. END Copy2.
  109. Note that to declare the input and output channels, we must import the
  110. channel identifier type from module StreamFile, and that SIOResult is also
  111. replaced by IOResult, which specifies which channel's results are needed.
  112. Now, how to initialise and finalise the in and out channels? (not needed for
  113. STextIO, since stdin and stdout are always available and cleaned up at end of
  114. program). We use the StreamFile module's Open procedure, specifying the
  115. channel to be connected to the file, the name of the file, and a set of flags
  116. indicating whether the file is to be opened for reading or writing, etc. At
  117. the end of the program, we use StreamFile.Close to ensure that the final
  118. output is actually written to the file and the access to the files is closed.
  119. MODULE Copy2;
  120. (*
  121. * Copy a file - version 2 - TextIO.ReadChar/WriteChar
  122. *)
  123. IMPORT StreamFile, StdDevice, TextIO, IOResult;
  124. IMPORT ProgArgs;
  125. VAR
  126. in, out : StreamFile.ChanId;
  127. result : StreamFile.OpenResults;
  128. success : BOOLEAN;
  129. ch : CHAR;
  130. BEGIN
  131. (* Open the input and output files *)
  132. StreamFile.Open (in, "copy2.in", StreamFile.read, result);
  133. ProgArgs.Assert (result = StreamFile.opened, "Unable to open source file");
  134. StdDevice.Delete ("copy2.out", success);
  135. StreamFile.Open (out, "copy2.out", StreamFile.write, result);
  136. ProgArgs.Assert (result = StreamFile.opened, "Unable to open output file");
  137. (* Copy the data *)
  138. LOOP
  139. TextIO.ReadChar (in, ch);
  140. CASE IOResult.ReadResult(in) OF
  141. | IOResult.endOfInput : EXIT;
  142. | IOResult.endOfLine : TextIO.SkipLine (in);
  143. TextIO.WriteLn (out);
  144. | IOResult.allRight : TextIO.WriteChar (out, ch);
  145. ELSE
  146. ProgArgs.Assert (FALSE, "Strange read result");
  147. END;
  148. END (* LOOP *) ;
  149. (* Tidy up *)
  150. StreamFile.Close (in);
  151. StreamFile.Close (out);
  152. END Copy2.
  153. Note that Open (..., read, ...) requires that the file already exist (ie, the
  154. "old" flag is implied). Also, the result of the Open attempt is returned in a
  155. parameter of type OpenResults.
  156. An Open (..., Write, ...) will succeed whether or not the file already
  157. exists. If it does not exist, it is created empty. If it does exist, the
  158. program output will simply overwrite whatever is previously in the file,
  159. leaving trailing old contents if the number of characters written is less
  160. than the old size of the file. Since we do not want this "update" behaviour,
  161. we first call the Delete procedure of module StdDevice to ensure any old file
  162. is gone; we ignore the result BOOLEAN from Delete since the failure may have
  163. been due to the fact that the file did not exist.
  164. But Copy2 is rather inflexible - it always reads from "copy2.in" and writes
  165. to "copy2.out"; contrast this with Copy1, which could copy any files by
  166. simply changing the command line redirection. To make Copy2 more useful, the
  167. file names must be string variables (ARRAYs OF CHAR):
  168. inFileName, outFileName : ARRAY [1..fileNameLength] OF CHAR;
  169. ...
  170. StreamFile.Open (in, inFileName, StreamFile.read, result);
  171. ...
  172. StreamFile.Open (out, outFileName, StreamFile.write, result);
  173. Now, how to obtain values for these string variables? To approximate command
  174. line redirection, we would need to supply the file names as command line
  175. arguments, and use the ProgArgs facilities for parsing command lines:
  176. (* Argument processing - get required arguments or give usage message *)
  177. IF ProgArgs.ArgNumber() = 3 THEN
  178. (* Program name plus two file name arguments *)
  179. ProgArgs.GetArg (1, inFileName);
  180. ProgArgs.GetArg (2, outFileName);
  181. ELSE
  182. TextIO.WriteString (StdChans.StdErrChan(), "Usage: copy2 inputFile outputFile");
  183. TextIO.WriteLn (StdChans.StdErrChan());
  184. HALT;
  185. END;
  186. Copy2 would now be invoked by
  187. Copy2 inputFile outputFile
  188. Alternatively, the file names might be returned by a call to some graphical
  189. user interface procedure, which allowed the user to select them from a pick
  190. list; a further possibility is for the program to issue text prompts and
  191. read the file names from stdin:
  192. PROCEDURE AskFileName (prompt : ARRAY OF CHAR; VAR fileName : ARRAY OF CHAR);
  193. (*
  194. * Prompt on standard output and read a filename from standard input.
  195. * The prompt is "File name for <prompt>: "
  196. *)
  197. BEGIN
  198. STextIO.WriteString ("File Name for ");
  199. STextIO.WriteString (prompt);
  200. STextIO.WriteString (": ");
  201. STextIO.ReadToken (fileName);
  202. STextIO.SkipLine;
  203. END AskFileName;
  204. Why ReadToken, not the expected (and available) ReadString? As detailed
  205. later, ReadToken skips any leading white space and returns the next non-blank
  206. group of characters; ReadString just reads as many characters as it can,
  207. including leading, embedded and trailing white space, and possibly causing
  208. problems with the Open processing.
  209. Note too that this style of obtaining file names should be used with care: by
  210. requiring user interaction at run time, it prevents the program being used in
  211. redirected or pipelined environments, as is often done by command scripts
  212. which automate processing or assemble tools to perform more complex tasks
  213. (Unix 'filters').
  214. Copying files - variations on the copy loop
  215. ===========================================
  216. Now we turn to some variations on the basic copy loop. The variations will
  217. use STextIO procedures, so that the copy source is stdin and the destination
  218. stdout. This style is recommended as the simplest and most flexible when only
  219. one input and one output file is required. However, TextIO equivalents exist
  220. in all cases, simply dropping the "S" module prefix and adding file
  221. parameters and Open and Close calls.
  222. First variation - ReadRestLine
  223. ------------------------------
  224. The STextIO procedure ReadRestLine will read an entire line into a string
  225. variable, which can then be output in its entirety by WriteString. The only
  226. problem is if a line is longer than the string array - ReadRestLine always
  227. reads an entire line (or from wherever previous reads had left the input
  228. stream), discarding anything which will not fit in the array, and setting the
  229. ReadResult to outOfRange if some was discarded. The following program uses a
  230. large array to make the problem unlikely, and reports any truncations. Since
  231. ReadRestLine always leaves the input stream positioned before an end-of-line
  232. mark, we can move the SkipLine/WriteLn to the end of the loop - but note that
  233. the ReadResult = endOfLine CASE branch must be retained with no action: when
  234. an empty line is encountered, ReadRestLine will return the empty string in
  235. the array, and ReadResult endOfLine - the CASE statement must avoid an
  236. invalid CASE selector error, and allow the end-of-line to be processed by the
  237. code after the CASE statement.
  238. LOOP
  239. STextIO.ReadRestLine (line);
  240. CASE SIOResult.ReadResult() OF
  241. | SIOResult.endOfInput : EXIT;
  242. | SIOResult.endOfLine : (* handled below *)
  243. | SIOResult.outOfRange : TextIO.WriteString (StdChans.StdErrChan(),
  244. "Line truncated");
  245. STextIO.WriteString (line);
  246. | SIOResult.allRight : STextIO.WriteString (line);
  247. ELSE
  248. ProgArgs.Assert (FALSE, "Strange SIOResult");
  249. END;
  250. STextIO.SkipLine;
  251. STextIO.WriteLn;
  252. END (* LOOP *) ;
  253. This version also uses output to the standard error channel stderr. The
  254. module StdChans exports function procedures which return the channel ids of
  255. stdin, stdout, and stderr, for use with TextIO procedures. Recall that output
  256. to stderr is distinct from that to stdout: while both default to the display,
  257. if stdout is redirected to a file, stderr will still appear on the screen
  258. (unless separately redirected to the same or a different file - impossible on
  259. DOS); thus stderr should be used for error messages which it is important
  260. that the user see.
  261. Second variation - ReadString
  262. -----------------------------
  263. The next version uses ReadString. This procedure reads up to the capacity of
  264. the string passed as a parameter, or to end-of-line, whichever comes first.
  265. Thus, compared with ReadRestLine, it does not discard any part of the current
  266. line left when the string is filled. A subsequent ReadString will simply pick
  267. up from where the last left off, so that the file is copied in chunks of
  268. whatever size string variable is used. The end-of-line processing moves back
  269. to the appropriate branch of the read result test:
  270. LOOP
  271. STextIO.ReadString (string);
  272. CASE SIOResult.ReadResult() OF
  273. | SIOResult.endOfInput : EXIT;
  274. | SIOResult.endOfLine : STextIO.SkipLine;
  275. STextIO.WriteLn;
  276. | SIOResult.allRight : STextIO.WriteString (string);
  277. ELSE
  278. ProgArgs.Assert (FALSE, "Strange SIOResult");
  279. END;
  280. END (* LOOP *) ;
  281. Third variation - ReadToken
  282. ---------------------------
  283. Finally, the ReadToken procedure will skip leading spaces and tabs, and then
  284. read as many non-space characters before the next end-of-line as will fit in
  285. the string parameter; any remaining characters on the line are left for the
  286. next read, as for ReadString. Thus ReadToken can be used to copy the file in
  287. token chunks, discarding any spaces or tabs which separate them. If we add a
  288. single space between tokens on a line, the output file has been reduced to
  289. the minimum number of spaces (provided the string variable used is large
  290. enough to avoid splitting any tokens).
  291. LOOP
  292. STextIO.ReadToken (string);
  293. CASE SIOResult.ReadResult() OF
  294. | SIOResult.endOfInput : EXIT;
  295. | SIOResult.endOfLine : STextIO.SkipLine;
  296. STextIO.WriteLn (out);
  297. first := TRUE;
  298. | SIOResult.allRight : IF NOT first THEN STextIO.WriteChar (' '); END;
  299. STextIO.WriteString (string);
  300. first := FALSE;
  301. ELSE
  302. ProgArgs.Assert (FALSE, "Strange SIOResult");
  303. END;
  304. END (* LOOP *) ;
  305. Fourth variation - Raw I/O
  306. --------------------------
  307. Of course, if the aim is only to copy a file, we need not use text I/O
  308. facilities at all. Module SRawIO may be used to read and write single bytes
  309. or groups of bytes without any interpretation as special values like
  310. end-of-line; this style of copy will also handle binary files like
  311. executable images, etc. Unfortunately, the RawIO.Read and .Write procedures
  312. read and write fixed amounts (the size of the buffer variable passed as a
  313. parameter); RawIO.Read returns the wrongFormat ReadResult on the incomplete
  314. buffer at end of file, but with no way of knowing how many bytes were read,
  315. and RawIO.Write has no length parameter to allow an incomplete buffer to be
  316. written. Thus they seem to be restricted to single-byte copies:
  317. LOOP
  318. (*
  319. * Raw IO copy - use a single-byte CHAR buffer if the length is not known
  320. *)
  321. SRawIO.Read (char);
  322. CASE SIOResult.ReadResult(in) OF
  323. | SIOResult.endOfInput : EXIT;
  324. | SIOResult.wrongFormat: ProgArgs.Assert (FALSE, "wrongFormat");
  325. | SIOResult.allRight : SRawIO.Write (char); END;
  326. ELSE
  327. ProgArgs.Assert (FALSE, "Strange SIOResult");
  328. END;
  329. END (* LOOP *) ;
  330. or to copying files known to comprise fixed-length records:
  331. LOOP
  332. (*
  333. * Raw IO copy - file comprises fixed length records
  334. *)
  335. SRawIO.Read (record);
  336. CASE SIOResult.ReadResult(in) OF
  337. | SIOResult.endOfInput : EXIT;
  338. | SIOResult.wrongFormat : ProgArgs.Assert (FALSE, "wrongFormat");
  339. | SIOResult.allRight : SRawIO.Write (record); END;
  340. ELSE
  341. ProgArgs.Assert (FALSE, "Strange SIOResult");
  342. END;
  343. END (* LOOP *) ;
  344. It is possible to combine the efficiency of reading and writing many bytes at
  345. a time with detection of variable lengths - by using the lower-level IOChan
  346. module's RawRead and RawWrite procedures. The ugly aspect of this is that the
  347. buffer address must be passed; for a dynamically-allocated buffer, the
  348. pointer to the buffer holds the address, but for static or automatic buffers
  349. ('normal' program variables) we must use SYSTEM.ADR to determine the address:
  350. LOOP
  351. (*
  352. * Raw IO at IOChan level.
  353. *)
  354. IOChan.RawRead (StdChans.StdInChan(), SYSTEM.ADR(buffer), bufferLength,
  355. locsRead);
  356. CASE SIOResult.ReadResult() OF
  357. | SIOResult.endOfInput : EXIT;
  358. | SIOResult.allRight : IOChan.RawWrite (StdChans.StdOutChan(),
  359. SYSTEM.ADR(buffer), locsRead);
  360. ELSE
  361. ProgArgs.Assert (FALSE, "Strange IOResult");
  362. END;
  363. END;
  364. Other modules
  365. =============
  366. The following is a list of all modules; those already mentioned above are
  367. marked * , and should cover most simple usage; others include a brief summary.
  368. ARGDEVIC
  369. Low-level module providing device-dependant argument access for
  370. ProgramArgs - not implemented yet
  371. CHANCONS
  372. Defines Open flags and results - imported and renamed by StreamFile,
  373. RndFile, SeqFile, TermFile.
  374. IOCHAN
  375. Low-level module providing device-independent Look, Skip,
  376. TextRead/Write, RawRead/Write and miscellaneous facilities for
  377. TextIO, WholeIO, RealIO, RawIO, IOResult
  378. IOCONSTS
  379. Defines ReadResults - imported and renamed by IOResult.
  380. IOLINK
  381. Low-level module providing channel <-> device facilities.
  382. IORESULT *
  383. LONGIO
  384. I/O for LONGREAL type - in gpm LONGREAL = REAL
  385. PROGRAMA
  386. Argument handling - replacement for ProgArgs - not implemented yet
  387. Use ProgArgs
  388. RAWIO *
  389. REALIO
  390. ReadReal and WriteFloat/Eng/Fixed/Real (latter Fixed if it will fit,
  391. else Float) - ie formatting flexibility as RealStr
  392. RNDFILE
  393. Access to files for random access - Open, Close, positioning
  394. SEQFILE
  395. Access to files for rewindable sequential access - Open, Close,
  396. ReRead, ReWrite
  397. SIORESUL *
  398. SLONGIO
  399. As for RealIO, but LONGREAL type. On gpm implementations, LONGREAL =
  400. REAL.
  401. SRAWIO *
  402. SREALIO *
  403. STDCHANS *
  404. STDDEVIC
  405. Low-level module providing device-dependent access to Unix/DOS
  406. streams (files, and devices such as buffered keyboard, display)
  407. STEXTIO *
  408. STREAMFI *
  409. SWHOLEIO *
  410. TERMDEVI
  411. Low-level module providing device-dependent access to Unix/DOS
  412. terminal devices, giving control over buffering & echoing
  413. TERMFILE
  414. Access to terminal devices - Open, Close, buffering & echoing mode.
  415. TEXTIO *
  416. WHOLEIO *