Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Design

Useful Utilities in Marvelous Modula


JAN89: STRUCTURED PROGRAMMING

Last September I did something I've done only twice before in dozens of reviews: gave a rave to JPI's TopSpeed Modula-2. Now it's time to put it to practical use.

With the spate of utilities that have appeared in this column, I've given some thought to taking the lead from Peter Norton and calling them Porter's Utilities. If I really followed Peter's example, though, I'd have to abbreviate the name as PU: hardly an appealing prospect, so I guess they'll go without a name.

At any rate, this month we'll present three utility programs that are genuinely useful as well as illustrating some aspects of systems programming in Modula.

Before I stray too far from the September review, though, there are a couple of things to add. First is a correction to Table 6 on page 76. I reported that Logitech's FP run time with a coprocessor was 45.28 seconds. Several readers let me know that I had missed an obscure passage in the Logitech docs regarding linkage with the proper floating point library, and that if I hadn't overlooked it, the results would have been on the order of 12 or 13 seconds. I haven't rerun the benchmark, but one reader got results similar to mine with TopSpeed and 12.5 or thereabouts with Logitech. From that we can conclude that Logitech's floating point performance with a coprocessor isn't spectacular, but it's a whole bunch better than I reported and comparable with the others. Sorry, guys.

The Modula review ranked twice as high as the second runner up in the reader interest survey, and I got more feedback from that than from anything (maybe everything) else I've ever written. There's an amazing amount of interest in Modula-2 Out There. It is an up-and-coming language with some strong supporting compilers, and it is a viable alternative to C, and I was gratified to find that a lot of folks agree with me.

Don't get me wrong, I like C. I have two advanced C programming books coming out in the next couple of months. While there's an element of shameless commercialism in mentioning that, there's also an implicit endorsement of C as a strong, vibrant language with a future. But frankly, C makes you work harder than you have to and it's difficult to read when the code gets cold. I live in a polyglot world of programming languages and believe me, life is easier on the Pascal/Modula shores than it is on the C.

All of which is a roundabout way of getting to the subject of this month's column.

Utility Number 1: How Much Free Memory Do You Have?

Software products keep getting bigger, while at the same time available memory keeps shrinking as we find more and more TSRs that we can't live without. I never once ran out of memory on my 64K CP/M machine (remember those days?), but it happens often enough on my 640K AT to be a problem. Even modern compilers with their interactive environments and dynamic linkers and integrated debuggers and such often require upwards of 400K, which pushes against the limits of a well-TSRed machine.

So the first utility in this group reports the amount of free memory. It includes the space it occupies in the result; that is, the program measures free space from the start of the area it occupies, and not just the space above itself.

You can determine the beginning of the space a program claims by getting the segment of its program segment prefix (PSP). This is a 256-byte data structure that DOS builds when it loads a program for execution; the program itself starts immediately above the PSP. When the program terminates, the memory starting with the PSP becomes free again.

The PSP always begins on a paragraph boundary, a paragraph being a 16-byte region expressed by the segment portion of the address. Therefore its offset is always zero, and we know where the PSP is simply by finding out its segment.

DOS provides two ways to get the PSP segment. One is the undocumented function 51h, and the other is function 62h, both under Int 21h. Function 62h is an official part of DOS 3.0 and above; function 51h was the only way to get the PSP in DOS 2.n. The latter has a bug that conflicts with Int 28h, which is where DOS hangs out when it's looking for something to do. Because the MEMORY utility (Listing One, page 134) might have to run under DOS 2.n, it uses function 51h. No danger, since DOS isn't idle while this program is running. The bug is only a problem with TSR's.

The PSPO procedure in MEMORY.MOD shows how to use function 51h to get the PSP segment, which is returned in register BX. The usage for function 62h is identical, but of course 62h doesn't work under DOS, Version 2. The PSPO function returns the product of register BX times 16. Why the multiplication? Because BX holds the PSP paragraph segment, so we multiply by 16 to get the byte address of the PSP. This tells us, in effect, how much memory is used below the utility.

The other critical value in this utility is the total size of main memory. It happens that this value is stored in the ROM BIOS data area, a treasure trove of machine-specific information based at paragraph 40h. The main memory size, expressed in K, is a CARDINAL (unsigned integer) at offset 13h. Line 10 in Listing One shows how to declare a variable at an absolute location using a Modula-2 address constructor. The analogous declaration in Turbo Pascal would be

  MemSize : WORD ABSOLUTE
                  $0040:$0013;

The first statement in the main body of MEMORY casts MemSize to a LONGCARD, which is a 32-bit unsigned integer, and multiplies by 1024 to convert K to bytes. The rest is simple: Subtract the bytes below the PSP and report the difference, which is the free memory in the machine.

There are a couple of things to note about the output instructions, First, Modula-2 doesn't have a catch-all, high overhead statement like Pascal's WriteLn, into which you can pour any number of parameters of varying types. Instead, it has one output instruction for each data type. This makes for more keystrokes to write a program.

That leads to the second point, which is unique to TopSpeed Modula-2. Niklaus Wirth, the inventor of Modula-2, suggests some module and procedure names. Most Modula implementors have followed Wirth's lead. JPI has been criticized for deviating from these recommendations, but their approach cuts down on the number of keystrokes by abreviating the Wirth names. For example, Wirth speaks of a console input/output module called InOut, and a routine called WriteString. Under his way of doing things, then, the statement at line 28 would read

  InOut.WriteString ('Available...');

which is nine more keystrokes than in TopSpeed. Multiply this by all the console I/O statements in a typical program and you find a significant difference in the sheer amount of work to write the source.

Speaking of module names, notice the IMPORT list at line 7 in Listing One. A Modula-2 module has to explicitly import any libraries it uses. Specific procedure calls are then bound to their supporting library using dot notation. This clearly identifies which library the procedure comes from, and allows similarly-named procedures to co-exist. For example, both IO and FIO (the file I/O library) furnish a WrStr procedure. You specify which you mean through the dot notation. There's also a way of importing specific elements from a library to avoid use of this verbose dot notation; lines 9 and 52 - 53 in Listing Three, page 134, illustrate how.

MEMORY is a handy utility to keep in your toolbox. Run it any time you need to find out how much free memory you have. Also, you can run it before and after loading a TSR to learn how much memory the TSR actually grabs.

Utility Number 2: What Are The Subdirectories?

The DOS DIR command and the SUB utility I published in this column a few months back both list subdirectories, but not exclusively; they're sprinkled randomly among other files, which makes them hard to spot. Often in the past I used to start up something like XTREE simply to find out what subdirectories were hanging off a specific directory. When that eventually got on my nerves enough, I wrote SD.MOD, shown in Listing Two, page 134.

Put SD.EXE somewhere along your PATH. Then, wherever you are, you can type SD and the program will list only the subdirectories in the current directory. Unlike a DOS shell or the DOS TREE utility, SD doesn't list the children of those subdirectories. It concedes that capability for the benefits of being fast and requiring no command-line arguments.

SD relies heavily on the DOS findfirst/findnext functions (4Eh and 4Fh under Int 21h). These functions are Siamese twins. Findfirst locates the first instance of a filename pattern with a given file attribute and builds a data structure. Findnext uses this data structure to step through the remainder of the current directory, on each call pulling out the next file entry that satisfies the parameters.

In TopSpeed Modula-2, these DOS functions are sugar coated with Modula syntax as ReadFirstEntry and ReadNextEntry, and are located in the FIO library. They return TRUE if the call is successful and FALSE otherwise. The involved data structure is of type DirEntry, also from FIO. Thus lines 15 - 16 in Listing Two make the first call, and then a loop begins at line 17. This loop will not execute if ReadFirstEntry failed to find a match. In fact, for reasons we'll discuss shortly, the loop will almost always execute at least twice despite its structure. Within the loop's scope is the call to ReadNextEntry, which also returns a Boolean to the Found variable controlling the loop. Thus the loop reiterates until ReadNextEntry runs out of directory entries satisfying the criteria in the DirEntry structure.

Note the parameters for ReadFirstEntry. They specify, in order, the pattern to look for, the file attribute(s), and the name of a DirEntry object. The attributes are a set of type FileAttr defined in FIO. The names describe the possible file attributes: readonly, hidden, system, volume, directory, and archive. In the case of SD, we want to look only for entries that have the directory attribute, so that's the only set member we pass to ReadFirstEntry as an argument.

The underlying DOS functions have a "feature," or in other words a fairly well-known bug that the vendor doesn't intend to fix. The bug is that, even when you specify the directory attribute alone, the functions return not only directory entries, but also all normal (read/write) file entries. For that reason, it's necessary to check the result in the attr field of the DirEntry structure to make sure it has a directory attribute. That's the purpose of line 18. It screens the results, allowing only legitimate directories to be processed further.

Every DOS subdirectory (that is, every directory except the root) has the two entries '.' and '..' referring to the current directory and its parent, respectively. That explains why the loop beginning at line 17 will execute at least twice; the only exception is when SD is looking at the root of an empty disk. Because the existence of these entries is a given, there's no reason to list them. Consequently the IF at line 19 filters them out.

SD is one of those little utilities that you quickly come to take for granted because it's so useful and such a natural extension of DOS. Here it does double duty, because it also lays the ground-work for the much more complex directory search utility discussed next.

Utility Number 3: Where is ... ?

The final program in this set is WHERE.MOD (Listing Three). This utility searches the entire directory structure of a disk, reporting the location of every entry that satisfies a pattern.

The pattern can be any combination of literal and wildcard characters. For example, if you want to know the location of every file with the extension .OLD, type

  WHERE, OLD

or

  WHERE *.OLD

Similarly, to find all files whose names have AU as the first two characters, type

  WHERE AU*

or

  WHERE AU*.*

Note that, unlike Modula-2 itself, the pattern is not case sensitive. You can list specific filenames if you remember what the file is called but not where it is. The command is also useful for chasing down unnecessary instances of a file, as in

  WHERE COMMAND.COM

In response to the command, WHERE lists each instance of a matching file, showing the drive, directory path, and filename.

WHERE can also search other drives. If you're running off drive C and you want to find all the dBase files on drive D, type

  WHERE D:.DBF

or

  WHERE D:*.DBF

The utility interprets the command line argument to see if it contains a drive, and performs the necessary drive change when it does. It also completes wildcards, which is why some of the example commands have two forms.

And if you don't furnish a command-line argument, WHERE asks you for the pattern with the query Filename?

The main body of WHERE begins by finding out the current directory and drive. This information is needed in order to restore the user's environment when the program quits. The next step is to get the command-line argument or, if not present, ask for it.

The code beginning at line 119 interprets the search pattern. When the second character is a colon, the program knows that the first character is a drive designator, so it converts the letter into its numeric DOS equivalent (where 0 = A, 1 = B, etc.). Having done that, it selects the new drive and shifts the pattern left to form a driveless pattern. (Note that the program doesn't recognize a subdirectory as a starting point; all searches proceed from the root, so a pattern containing a directory path won't produce meaningful results.)

The output list will contain the drive prefix d:\ where d is a drive name. This string is contructed starting at line 134.

Lines 140 - 146 patch in wildcards as needed. For example, if the user typed

  WHERE.BAK

this routine translates the pattern into *.BAK and, if

  WHERE SALES

into the pattern SALES.*. There is a distinction, by the way, between patterns such as SALES and SALES*. If you type the first, the program reports only files with SALES as the base name, and skips those such as SALESREP.XYZ. The pattern SALES* finds the latter, because it gets translated into SALES*.*.

With all the preliminaries out of the way, the program can now undertake the search. This always begins at the root and progresses through the entire directory structure, checking every single file entry on the disk. Line 149 initiates the search.

The SearchDir procedure is recursive. That is, it calls itself, passing the name of a child directory to search. A level of recursion thus exists for each level in the generations of subdirectories. When a child directory has been completely searched, the procedure returns to the parent level. When all directories have been visited, the root level returns to the main program's cleanup routines starting at line 151. Here's how it works in more detail.

SearchDir begins by changing to the directory it was passed as a parameter, and reconstructing the full pathname including drive for reporting purposes. It then searches the current directory for any entry satisfying the pattern. The names of matching entries are listed on the display. When the matching entry is a subdirectory, this first phase merely reports the name suffixed with <DIR>; it doesn't actually branch to that directory yet.

That's what the second phase starting at line 66 does. When all entries in the directory have been checked, line 67 starts over again, scanning specifically for subdirectories much as SD does. However, here the loop issues a recursive call to SearchDir, passing the name of the subdirectory. This call suspends the parent invocation of SearchDir and restarts the procedure with different data needed to search the child directory. This process continues through the children of the children. Eventually all lines of inquiry along the path are exhausted, and the child level returns to the parent level at line 71. The child invocations changed the current directory, so line 71 restores the parent directory and continues the loop seeking other child paths to search. When it runs out of directories, it returns to its parent level of recursion, and so on until eventually control returns to the main program at line 151, thus ending the program run.

Note how the screen output is controlled. Line 49 displays the full name of the path currently being searched. When a match occurs, lines 55 - 60 add the filename and advance to the next display row, where line 61 again shows the current path. Often a path contains no matches, and even when it does, eventually it runs out of them. In either case, the next invocation of SearchDir calls ClrSol from line 48. This routine erases the name of the old exhausted path and overlays it with the next.

ClrSol (for clear to start of line) does this by using yet another field in the ROM BIOS data area. When it gets control, the cursor is winking at the end of the current path, which might be of any length. Offset 50 contains the current cursor column in video page 0, the default video buffer for WHERE. The procedure learns where the cursor is by checking this absolute variable. It then backs up the cursor, replacing each position with a space, until the cursor is in column 0, the left margin of the display area. Line 49 can then write a fresh string indicating the new search path without worrying about excess baggage left over from a longer line previously occupying the same row. Similarly, the code beginning at line 151 erases the last fruitless path to report the number of matches found.

TopSpeed Modula-2 includes a comprehensive high-level module called Window, providing for advanced screen control. The TopSpeed interactive environment itself was written using the Window module. However, Window takes more control of the display than I deemed desirable for this program. For example, the first call to any Window routine clears the display and sets the default text color to bright white. I preferred that WHERE have the appearance of standard DOS utilities, which is why I devised the ClrSol routine.

So there you have three useful utilities and a hands-on demonstration of Modula-2 in action. Like C, Modula is a language inherently capable of embracing the full spectrum of programming requirements, from low-level systems utilities to advanced applications.

Handing Over the Reins

We all mourn the demise of Turbo Technix, Borland's excellent programming magazine. Yet, even bad things have positive results. One of them is that we were able to recruit Mike Floyd to come to work for DDJ as technical editor. Another is that it freed Technix' editor-in-chief, Jeff Duntemann, to take over this column.

Jeff needs little introduction to structured programming devotees. Through his work at PC Tech Journal and Turbo Technix, as well as several authoritative books, Jeff has had a substantial impact on the art of structured programming. Indeed, one might fairly describe him as Mr. Pascal. He's also an accomplished Modula programmer, and a superlative writer. Consequently, I leave the column in capable hands.

Doing this column for the past year has been a lot of fun and earned me many new friends. Had Technix not shut down and made Jeff available, I would gladly have continued with it. But for a long time I've had a fantasy about doing a graphics programming column. Cosmic forces created the opportunity, and we decided to go for it.

You may not have noticed, but DDJ has been putting on weight. The number of articles is up, and by changing the typeface we're able to get more content on each page. These factors have created the space to run another column and still keep the article count at its historic high.

So starting next month, you'll find Jeff Duntemann here and I'll be launching an expedition into the magical world of graphics programming. See you there.

_STRUCTURED PROGRAMMING_

by Kent Porter

[LISTING ONE]

<a name="0045_0009">


   1| MODULE Memory;
   2|
   3| (* Reports amount of memory available, excluding this program       *)
   4| (* JPI TopSpeed Modula-2                                            *)
   5| (* K. Porter, DDJ, January '89                                      *)
   6|
   7| IMPORT SYSTEM, IO, Lib;
   8|
   9| VAR   MainMem, MemUsable      : LONGCARD;
  10|       MemSize [0040H : 0013H] : CARDINAL;
  11| (* ---------------------------------------------------------------- *)
  12|
  13| PROCEDURE PSP() : LONGCARD;           (* Return byte address of PSP *)
  14|
  15| VAR   Reg : SYSTEM.Registers;
  16|
  17| BEGIN
  18|   Reg.AH := 51H;  (* Undocumented: same as 62H but works in DOS 2.n *)
  19|   Lib.Intr (Reg, 21H);
  20|   RETURN (LONGCARD (Reg.BX) * 16);
  21| END PSP;
  22|
  23| (* ---------------------------------------------------------------- *)
  24|
  25| BEGIN
  26|   MainMem := LONGCARD (MemSize) * 1024;    (* Total memory in bytes *)
  27|   MemUsable := MainMem - PSP();
  28|   IO.WrStr ('Available memory is ');
  29|   IO.WrLngCard (MemUsable, 1);
  30|   IO.WrStr (' bytes');
  31|   IO.WrLn;
  32| END Memory.





<a name="0045_000a"><a name="0045_000a">
<a name="0045_000b">
[LISTING TWO]
<a name="0045_000b">

   1| MODULE sd;
   2|
   3| (* Lists all subdirectories in the current directory *)
   4| (* JPI TopSpeed Modula-2                             *)
   5| (* K. Porter, DDJ, January '89                       *)
   6|
   7| IMPORT FIO, IO;
   8|
   9| VAR    F     : FIO.DirEntry;
  10|        Found : BOOLEAN;
  11|        Count : CARDINAL;
  12|
  13| BEGIN
  14|   Count := 0;
  15|   Found := FIO.ReadFirstEntry ("*.*",
  16|                FIO.FileAttr {FIO.directory}, F);
  17|   WHILE Found DO
  18|     IF FIO.directory IN F.attr THEN
  19|       IF F.Name[0] # '.' THEN
  20|         IO.WrStr (F.Name); IO.WrLn;
  21|         INC (Count);
  22|       END;
  23|     END;
  24|     Found := FIO.ReadNextEntry (F);
  25|   END;
  26|   IO.WrCard (Count, 1);
  27|   IO.WrStr (' directories found');
  28|   IO.WrLn;
  29| END sd.




<a name="0045_000c"><a name="0045_000c">
<a name="0045_000d">
[LISTING THREE]
<a name="0045_000d">

   1| MODULE Where;
   2|

   3| (* Searches directory structure from the root, listing all occur-   *)
   4| (*   rences of a filename matching the search argument              *)
   5| (* JPI TopSpeed Modula-2                                            *)
   6| (* K. Porter, DDJ, January '89                                      *)
   7|
   8| IMPORT FIO, IO, SYSTEM, Lib, Str;
   9| FROM FIO IMPORT FileAttr, readonly, hidden, system, directory,
  10|                 archive;
  11|
  12| TYPE  string = ARRAY [0..79] OF CHAR;
  13|
  14| CONST DefaultDrive = 0;
  15|       Backspace = CHR(8);
  16|
  17| VAR   arg, curdir, xfer  : string;
  18|       DriveName          : ARRAY [0..3] OF CHAR;
  19|       count, p, s, d     : CARDINAL;
  20|       curdrive, newdrive : SHORTCARD;
  21|       cx [40H:50H]       : SHORTCARD;        (* ROM BIOS cursor col *)
  22|
  23| (* ---------------------------------------------------------------- *)
  24|
  25| PROCEDURE ClrSol;             (* Clear from cursor to start of line *)
  26|
  27| BEGIN
  28|   WHILE cx # 0 DO
  29|     IO.WrChar (Backspace);
  30|     IO.WrChar (' ');
  31|     IO.WrChar (Backspace);
  32|   END;
  33| END ClrSol;
  34|
  35| (* ---------------------------------------------------------------- *)
  36|
  37| PROCEDURE SearchDir (Path : ARRAY OF CHAR);
  38|                               (* Recursive directory search routine *)
  39|
  40| VAR  F         : FIO.DirEntry;
  41|      WholePath : string;
  42|      Found     : BOOLEAN;
  43|
  44| BEGIN
  45|   FIO.ChDir (Path);                                (* Set directory *)
  46|   FIO.GetDir (DefaultDrive, WholePath);        (* Get full pathname *)
  47|   Str.Concat (WholePath, DriveName, WholePath);        (* Add drive *)
  48|   ClrSol;                                 (* Clear to start of line *)
  49|   IO.WrStr (WholePath);                           (* List directory *)
  50|
  51|   (* Search for filename matches in this directory *)
  52|   Found := FIO.ReadFirstEntry (arg, FileAttr {readonly, hidden,
  53|                                system, directory, archive}, F);
  54|   WHILE Found DO
  55|     IF Str.Length (WholePath) > 3 THEN IO.WrChar ('\') END;
  56|     IO.WrStr (F.Name);
  57|     IF directory IN F.attr THEN
  58|       IO.WrStr (" <DIR>");
  59|     END;
  60|     IO.WrLn;                                            (* New line *)
  61|     IO.WrStr (WholePath);
  62|     INC (count);                                (* Count occurrence *)
  63|     Found := FIO.ReadNextEntry (F);               (* Get next match *)
  64|   END;
  65|
  66|   (* Now recursively search any subs under this directory *)
  67|   Found := FIO.ReadFirstEntry ("*.*", FileAttr {directory}, F);
  68|   WHILE Found DO
  69|     IF (directory IN F.attr) AND (F.Name[0] # '.') THEN
  70|       SearchDir (F.Name);                         (* Recursive call *)
  71|       FIO.ChDir (WholePath);           (* Restore dir to this level *)
  72|     END;
  73|     Found := FIO.ReadNextEntry (F);                  (* Do next sub *)
  74|   END;
  75| END SearchDir;
  76|
  77| (* ---------------------------------------------------------------- *)
  78|
  79| PROCEDURE GetDrive() : SHORTCARD;     (* Get currently active drive *)
  80|
  81| VAR   Reg : SYSTEM.Registers;
  82|
  83| BEGIN
  84|   Reg.AH := 19H;
  85|   Lib.Intr (Reg, 21H);
  86|   RETURN (Reg.AL);
  87| END GetDrive;
  88|
  89| (* ---------------------------------------------------------------- *)
  90|
  91| PROCEDURE SetDrive (Drive : SHORTCARD);        (* Set default drive *)
  92|
  93| VAR   Reg : SYSTEM.Registers;
  94|
  95| BEGIN
  96|   Reg.AH := 0EH;
  97|   Reg.DL := Drive;
  98|   Lib.Intr (Reg, 21H);
  99| END SetDrive;
 100|
 101| (* ---------------------------------------------------------------- *)
 102|
 103| BEGIN   (* Main body of WHERE *)
 104|
 105|   (* Initialize *)
 106|   FIO.GetDir (0, curdir);                  (* Remember where we are *)
 107|   curdrive := GetDrive();                              (* and drive *)
 108|   count := 0;
 109|   FOR p := 0 TO 3 DO DriveName[p] := CHR(0) END;
 110|
 111|   (* Get the name to search for *)
 112|   IF Lib.ParamCount() > 0 THEN
 113|     Lib.ParamStr (arg, 1);
 114|   ELSE
 115|     IO.WrStr ("Filename? ");
 116|     IO.RdStr (arg);
 117|   END;
 118|
 119|   (* Select another drive, strip out designator if necessary *)
 120|   IF arg[1] = ':' THEN
 121|     DriveName[0] := CAP (arg[0]);
 122|     newdrive := SHORTCARD (ORD (CAP (arg[0])) - ORD ('A'));
 123|     SetDrive (newdrive);                           (* Set new drive *)
 124|     IF arg[2] = '\' THEN s := 3 ELSE s := 2 END;
 125|     d := 0;
 126|     FOR p := s TO Str.Length (arg) DO (* Strip out drive designator *)
 127|       xfer[d] := arg[p];
 128|       INC (d);
 129|       xfer[d] := CHR (0);
 130|     END;
 131|     Str.Copy (arg, xfer);                       (* Copy back to arg *)
 132|   END;
 133|
 134|   (* Build name of target drive *)
 135|   IF DriveName[0] = CHR(0) THEN
 136|     DriveName[0] := CHR (curdrive + 65)
 137|   END;
 138|   Str.Concat (DriveName, DriveName, ":\");
 139|
 140|   (* Add wildcard prefix/suffix as necessary *)
 141|   IF arg[0] = '.' THEN
 142|     Str.Concat (arg, "*", arg);         (* Stick in wildcard prefix *)
 143|   END;
 144|   IF Str.Pos (arg, ".") = MAX (CARDINAL) THEN
 145|     Str.Concat (arg, arg, ".*");          (* Append wildcard suffix *)
 146|   END;
 147|
 148|   (* Begin search at root *)
 149|   SearchDir ("\");
 150|
 151|   (* Report matches found *)
 152|   ClrSol;
 153|   IO.WrCard (count, 1);
 154|   IO.WrStr (" matches found");
 155|   IO.WrLn;
 156|
 157|   (* Restore user's original environment *)
 158|   SetDrive (curdrive);
 159|   FIO.ChDir (curdir);
 160| END Where.











Copyright © 1989, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.