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

Database

Start Your Collection Now!


APR92: STRUCTURED PROGRAMMING

I have been falling a little out of love with objects for some time now. Ok, that's not entirely true -- but I've definitely come out of the infatuation that has gripped a lot of us for a couple of years. I've had to admit that some of my firmest beliefs are just plain wrong, and others are at best skewed to reality. On the other hand, OOP technology has succeeded spectacularly in a couple of areas, and there are a few others where OOP's failure is the fault of our own flawed expectations. If you can't make pasta with a can opener, it's hardly the can opener's fault.

So if I haven't led off with a funny story this month, well, it's mostly because the whole affair has left me slightly depressed.

A House Divided

Where I think we blew it big-time is in the are of encapsulation. Scott Guthery was pretty much right about that, in his seminal challenge to OOP concepts ("Are the Emperor's New Clothes Object Oriented?") presented in DDJ in the December 1989 issue. Among other things, he points out that inheritance breaks encapsulation. On that point at least (although I'm far from agreeing with him on all of his points) he's right.

The truth is that OOP is a technological house divided against itself. In one corner is inheritance/polymorphism, and in the other is encapsulation. The corners are not really related at all, in that you can have inheritance without encapsulation and vice versa. Beneath the hood, inheritance/polymorphism is a matter of binding; that is, the mechanism by which the caller of a routine is given the routine's address. Encapsulation, on the other hand, is a question of stack frames: Who gets to reference who's data or methods, and how.

For the word "encapsulation" to mean anything at all, the unit of encapsulation must be considered the entire inheritance interstate between the root object and the leaf you're instantiating. In many cases that's the same as saying, "The world is my neighborhood," which is noble and poetic but tells us nothing when we have to think about urban renewal.

The point of inheritance/polymorphism is to enhance the expressibility of a language; that is, broaden the range of things you can model in software. The point of encapsulation, by contrast, is to reduce the degree of coupling between software components. Maybe one out of two ain't so bad.

Software VLSI

Turbo Vision, our ongoing dance partner for the last several columns (and a few more to come) is an interesting example. It's a triumph of data expression, and (by virtue of Turbo Pascal's OOP extensions) does things we couldn't even dream of four years ago. On the other hand, the unit of encapsulation for Turbo Vision is Turbo Vision itself. It's not an engineer's assortment of standard software ICs; it's a massive piece of software VLSI that dictates virtually the entire design of any application that uses it.

Is this good or bad? As a concept, it's neither; and to be honest with you, it's nothing new. Most of the user-interface libraries I've worked with have been heavily coupled internally, such that you don't pull out a scroll bar without bringing in just about everything else. As I've said before, the notion of a standard set of software-engineering modules probably depends on a standard interface and infrastructure to support those modules and the passage of data among them. Unless this interface and infrastructure is totally industry standard, the effort may not be worth the trouble. That means it's going to have to happen at the operating-system level, which for DOS means that the notion of logic-block scale "software ICs" is pretty much hopeless. Turbo Vision, as software VLSI, is pretty damned good, and (for DOS text mode, at least) it may be the best we can do.

So let's bid encapsulation good-bye for the present, and return to the issue of expressibility.

Turbo Pascal Collections

Something I have always admired about Smalltalk is its expressibility as a language. For many years, it had absolutely no peer in being able to model real-life entities with messy relationships. A large part of this power lay in Smalltalk's collection classes, classes that gather together other objects and allow them to be treated in certain ways as a group.

Standard Pascal nods to the concept of collections with arrays, files, records, and (especially) dynamic data structures on the heap, but strong typing always gets in the way. A Pascal array collects instances of one type and one type only. Ditto for files. Records collect only at compile time, which is truthfully not collecting at all. You have a little more flexibility with linked lists and other dynamic structures, but not much.

Hiding in Turbo Vision's shadow is a remarkable feature that far too few Turbo Pascal programmers have ever tried to use: a hierarchy of Pascal collection classes that do most of what the Smalltalk collections can do. Although nominally a part of Turbo Vision (because Turbo Vision uses them) TP6 collections in fact are not views and stand alone; and they may be used in applications incorporating any user interface or none at all. Even if you consider Turbo Vision too difficult, you should take a close look at the collections hierarchy.

Collection Fundamentals

All Turbo Vision collections work basically the same way. You declare an instance of a collection class such as TCollection, the fundamental collection class. Your collection object contains a method called Insert, which is the way you add an object to the collection: MyCollection.Insert(PSometype);. Note well that the parameter to Insert is a pointer to an object, and not the object itself! This is the part of collections that most throws newcomers: Collections are inextricably built upon pointers, and you'd better get used to that. You can pass a pointer by using the address-of operator "@" on a variable (for example, @MyObject), but as I'll explain a little later, this often leads to some ugly traps.

Play it straight. Unless you know exactly what you're doing, collect only data items that have been allocated on the heap.

The collection object keeps track of how many objects it contains in a field called Count. Count changes automatically when you insert or delete an object, and always reflects the true number of objects in the collection.

Once inserted, data stored in a collection may be accessed in a number of ways. The easiest way is simply to consider the collection an array masked by a procedure call, and access items in the collection by index; see Example 1.

Example 1: Accessing data stored in a collection

  FOR I := 1 TO Count DO
    Writeln
       (MyCollection.At(I)^.SomeText);

The At function method returns a pointer to the item at the index you pass to the collection is At's single-integer parameter. You must dereference the pointer to "get at" the data. By design, all Turbo Pascal collections are ordered collections; that is, there is always a defined order in which the collected objects exist in the collection. This doesn't mean that the items are always sorted in the collection (although there is a sorted collection class which we'll deal with a little later), but simply that you can always reference the contents of a collection in a defined order.

In a similar way, you can write a new object to a collection at a particular index by using the AtPut method. The new object replaces any object that was previously at that index in the collection: MyCollection.AtPut(I,PSometype);. Again, you don't pass the object you want to put into the collection, but a pointer to that object. (I'm using the now-widespread convention here that a type identifier starting with the letter "P" indicates a pointer type.)

The Insert method mentioned earlier inserts at the beginning of the collection. You can also insert a new object somewhere in the middle of the collection without overwriting what is already at that index. The AtInsert method does this: MyCollection.AtInsert(I,PSomeType);. Starting at index I, the objects in the collection are "shoved over" by one, so that PSomeType^ can be inserted at index I. Nothing is overwritten.

The other access methods are more complex and depend on the type of collection you're using. We'll get to that shortly.

Creating and Sizing Collections

Collections are objects, and like all objects, they're created by way of constructor calls. The constructor that creates a collection from scratch looks like this: CONSTRUCTOR Init(ALimit, ADelta : Integer);.

You pass in ALimit the number of items you want the collection to be able to contain. Now, there is an absolute ceiling on the number of individual items a collection can contain. This ceiling figure is stored in a constant called MaxCollectionSize, and is currently equal to 16,380 (the maximum number of 4-byte pointers you can store in 64K). ALimit, by contrast, is an arbitrary limit that you set for yourself, with reasonable expectations that the collection will not need to contain more items.

It's a question of conserving heap space. Under the covers, a collection is a dynamically allocated array of pointers located on the heap. The number of pointers initially allocated for it is the number you pass in ALimit. The amount of heap space taken up by an empty collection allocated on the heap is ALimit times four (the size of a pointer) plus another 20 odd bytes for the collection's other fields. Obviously, if you make ALimit vastly larger than you'll need, you're wasting space on the heap for pointers you're never going to use.

Predicting the future is a dicey business, though, and that's what the ADelta parameter is for. Your collection can grow beyond the number of objects specified in ALimit. If you fill a collection and try to insert another object, the collection will automatically grow its capacity by ADelta objects. ADelta is the "chunk size" by which new capacity is added.

This sounds great, but there's a penalty: Adding capacity forces the collection to allocate an additional array of pointers on the heap. This additional array is not contiguous with the original array, and is connected to the original array by pointers. Indexing through the array thus becomes more complicated, because once there are multiple pointer arrays, the indexing code has to test to see if it's at the end of any given array on each index. This slows down performance for every access, not just accesses into the additional arrays.

No, you can't really predict the future. But sometimes you have to try.

Sorted Collections

What I've just described is fundamental behavior common to all TP6 collections. This behavior is implemented in the TCollection class, which is an ordered but not a sorted collection. You can sub-class TCollection for more specific behavior. Turbo Pascal provides another type of collection class that automatically inserts the items you add to the collection in sorted order. TSortedCollection is a child class of TCollection, with the additional code and fields to support automatic sorting of inserted objects.

You have to provide one piece of the puzzle: a Compare method to override the empty one present in TSortedCollection. Compare takes pointers to two items and returns a code based on the sort-order relationship between the two items:

  
-1 if Key1 < Key2   
0 if Key1  = Key2   
 1 if Key1  > Key2

The code in the TSortedCollection abstract class calls your Compare function whenever a new item is added to the sorted collection, so it can determine where to place it with respect to the items already in the collection.

There's one drawback to TSortedCollection: Duplicate items are not allowed, and if you attempt to add an item to a collection in which an identical item already exists, the new item will not be added. In some applications, this can be a serious drawback; if you're sorting people's names, you'll probably run into duplicates within 50 added records. So unless you're absolutely sure that every item you're adding will be unique, you'd better test each item before you add it to a sorted collection to be sure it isn't already there. If it is, you'll have to change the item somehow to make it unique. Customer records and such should be given unique customer ID numbers before trying to collect them in a TSortedCollection.

An Example: DIRLIST.PAS

Listing One (page 155) presents a simple but very useful collection class that's a good first example of Turbo Pascal collections. Unit DirList defines two classes. The first is TDirEntry, which encapsulates a Turbo Pascal SearchRec record with a path and a nicely formatted string description of the file specified by the path and the SearchRec. The other class is TDirEntryCollection, which is a sorted collection of TDirEntry objects.

By using Turbo Pascal's FindFirst and FindNext library procedures, TDirEntryCollection builds collections of file entry objects that satisfy wild-card file specs such as *.PAS. Thus if you want to build an application that acts on groups of files (or one that presents groups of files to the user for selection), TDirEntryCollection might be just the thing.

There's nothing particularly tricky about the TDirEntry object. It demonstrates the use of my "when stamp" class as a sort of stand-alone software IC that provides a specific service; in this case, the formatting of a 16-bit, DOS-directory file stamp into separate numeric time and date values, as well as string equivalents of those values. By the way, I've uploaded a new version of the when stamp unit to the various DDJ listings sources, and you should obtain the new version if you intend to use it, as I've fixed a few minor bugs. Look for WHEN2.PAS.

Multiple Constructors

The TDirEntryCollection class is a good example of when multiple constructors make sense. The mission of a constructor, after all, is to allocate a new object on the heap and apply some initial values to the object's fields. If those initial values come from different places, it makes sense to create a different constructor for each major source of initial data. That way, you won't have to allocate an empty object and then use a separate statement or statements to "fill" it.

All three constructors call the method AddDir, which uses FindFirst and FindNext to search a single directory, specified in Path, for any files meeting the file spec passed in Mask. Mask may be given a wild-card file spec. The parameter Attr carries an attribute byte for the search.

The Dos unit defines a list of constants for the various possible attribute byte values. Only two of them are generally used: AnyFile, which specifies the file attribute for "normal" files; and Directory, which specifies a subdirectory. There are also constants for specifying read-only, hidden, and system files; see Borland's Library Reference for details, under FindFirst.

What differentiates the three constructors is the way they make their calls to AddDir. InitDir simply calls the parent class's constructor (always do this!) and then applies AddDir to the directory specified by Path, using Mask to specify files.

InitCommandLine serves the frequent need to pick up multiple file specs from the command line. It simply picks up each parameter from the command line (starting with the parameter number passed in StartParam) and applies AddDir to each parameter. Passing a number greater than 1 in StartParam allows you to treat the first one or more parameters as command switches rather than file specs.

InitTree gets a little more gymnastic and does a search of an entire directory tree or subtree. This is done through the TreeScan method. TreeScan searches the tree specified by Path; if Path contains a slash character only, the entire current drive is searched. If Path were to contain the name of a subdirectory (such as "\TURBO\SOURCE"), it would search that subdirectory and any subdirectories found within it.

Iterating Over a Collection Class

Once you've picked up a collection of directory entries from disk, there's the question of how you work with them. As I mentioned earlier, you can index through them as if the collection were an array. But the Turbo Pascal collection classes contain another bit of magic that allows you to iterate a procedure over all the elements of a collection. What this means is simply that you can define a procedure and pass a pointer to the procedure to the collection. The collection will then apply that procedure to all or some of the items in the collection.

For this to make sense, the best course is to see it in action. Listing Two, see page 156, is the simplest useful example I could devise for iterating over a collection class. JFIND.PAS uses the DirList unit to perform a tree search for one or more file specs passed on the command line. Once it has built up a collection of all files satisfying the specs entered on the command line, it displays a brief summary of each file on the screen, identical to the display format used by DOS DIR.

To do this, JFIND iterates a procedure, DisplayOneFile, over the collection. The statement that does this looks pretty simple: FilesFound^.ForEach(DisplayOnefile);. Here, FilesFound (the collection of files) calls its ForEach method, with a pointer to the DisplayOneFile procedure as its parameter. ForEach calls DisplayOneFile once for each TDirEntry object in the collection, with that object as DisplayOneFile's sole parameter.

Doing this is easy. In setting it up, however, there's a trick to it. DisplayOneFile must be far local procedure. Yes, that sounds a little like "jumbo shrimp" or "heavy-metal music," but they mean it. The procedure passed to ForEach must be local to the block that calls it. Being local to the main program (that is, being global) doesn't count. What it means, in practice, is that you pass a pointer to the collection to a shell procedure, and within that shell procedure make the method call to ForEach. JFIND.PAS does this. Note the FAR keyword after the proc header for DisplayOneFile.

I haven't the foggiest why things have to be done this way. It's got my curiosity piqued, however, and if anyone has a good explanation, I'd like to hear it.

Places for Your Stuff

TP6 collections are in fact more powerful than I've demonstrated here in DIRLIST.PAS. All of the objects stored in DIRLIST are directory entry objects, simply because that makes sense for what we're trying to do.

But in fact, you can insert anything into a collection -- anything at all that you can create a pointer to. And that doesn't mean a separate collection for each type. You can insert any assortment of types into a single collection, even a sorted collection, as long as there's some generalized way to define a sort order for all data types present in the sorted collection.

So a collection becomes very much like the trunk of a car: You can toss in anything you like, and as long as the trunk has room, it'll take it. This is two full quantum leaps beyond what we're used to in traditional Pascal programming. Turbo Vision uses the collection classes to implement collections of controls and other things, which are not the same type, although they all have a common ancestor class.

This is a point that needs to be made: Although it's possible to store non-objects in a collection, doing so cuts you free of some of the things a collection can do. There is some standard behavior built into the fundamental TObject class that allows you to work easily with stream I/O, and if TObject isn't one of your ancestors, you lose that ability. As a matter of policy, you should try to store only objects that descend from TObject in a Turbo Pascal collection. It'll save you a lot of extra work when you need to begin using streams.

Think Pointers, Kid!

Early on in my experience with TP6 collections, I made a mistake that I've since heard other people have made too. It's easy enough to do if you don't envision every programming problem in terms of pointers and the heap. What I did was this: I declared a static global variable called MyGizmo, and then tried to insert it into a collection like this: MyCollection.Insert@MyGizmo);.

This works -- in that it doesn't blow up or anything, and if you traverse MyCollection you will in fact discover that it contains a pointer to MyGizmo, as it should.

The catch is this: You have simply added a pointer pointing to the global variable MyGizmo to a collection of pointers. If you change the value of variable MyGizmo and add it again to the collection, you will simply be adding a second pointer pointing to the identical global variable. Both pointers will be pointing to the same variable, and the value originally pointed to by the first pointer will be gone. There is only one copy of MyGizmo in the data segment, and pointing two pointers at it won't make it two copies.

This problem is simple enough to avoid. Remember the following rules:

  • TP6 collections are collections of pointers. Only the pointers are present in the collection. There is no copying of pointer referents.
  • Unless you have good reason to do otherwise, store only heap-based data in collections. Allocate data on the heap as the referent of a pointer, and then add that pointer to the collection.
Work it that way, and you'll do fine.

Opening the Black Box

A couple of columns ago, I complained about the black-box nature of Turbo Vision, and several people (including a couple at Borland) were nice enough to point out that the Turbo Vision source code is now shipped as part of the larger Turbo Pascal Runtime Source package, which anyone can buy from Borland for $199.95 (or $99 for an upgrade). Having obtained a copy, I'm now not sure it'll help you unless you're so into Turbo Vision that you probably don't need it. It's very involved, and I'm beginning to wonder if Turbo Vision's problem is not that it's a black box, but that it's not nearly black enough.

I think the problem with Turbo Vision is that its complexity is too close to the surface, and is insufficiently masked. With a little luck, we'll go into this more deeply in a later column.

Products Mentioned

The Turbo Pascal Runtime Library Source Borland International 1800 Green Hills Road Scotts Valley, CA 95066 408-438-8400 $199.95 (for upgrade $99)



_STRUCTURED PROGRAMMING COLUMN_
by Jeff Duntemann



[LISTING ONE]
<a name="00fb_0013">

UNIT DirList;  { By Jeff Duntemann; for DDJ 4/92 }

INTERFACE

USES Crt,DOS,Objects,  { Standard Borland units }
     When2;            { From DDJ 1/91          }
TYPE
  String40  = STRING[40];
  PDirEntry = ^TDirEntry;
  TDirEntry =
    OBJECT(TObject)
      Path  : PathStr;      { Predefined in Dos unit as STRING[79] }
      Entry : SearchRec;      { Also predefined in the Dos unit    }
      DirLine : String40;     { Preformatted directory info string }
      CONSTRUCTOR Init(APath : PathStr; ASearchRec : SearchRec);
      PROCEDURE   FormatDirLine;
    END;
  PDirEntryCollection = ^TDirEntryCollection;
  TDirEntryCollection =
   OBJECT(TSortedCollection)
    CONSTRUCTOR InitDir(ALimit,ADelta : Integer;
                        Path, Mask    : STRING;
                        Attr          : Word);
    CONSTRUCTOR InitCommandLine(ALimit,ADelta : Integer; StartParam : Integer);
    CONSTRUCTOR InitTree(Alimit,ADelta : Integer;
                         Path,Mask     : STRING;
                         Attr          : Word);
    FUNCTION    Compare(Key1,Key2 : Pointer) : Integer; VIRTUAL;
    PROCEDURE   AddDir(Path,Mask : STRING;
                       Attr      : Word);
    PROCEDURE   TreeScan(Path,Mask : STRING; Attr : Word);
   END;

IMPLEMENTATION
VAR
  Stamp : When;  { Global When stamp for time/date string processing }
{----------------------------------------}
{ Methods: TDirEntry                     }
{----------------------------------------}
CONSTRUCTOR TDirEntry.Init(APath : PathStr; ASearchRec : SearchRec);
BEGIN
  TObject.Init;
  Path  := APath;
  Entry := ASearchRec;
  FormatDirLine;
END;
PROCEDURE TDirEntry.FormatDirLine;
VAR
  DotPos : Integer;
  WorkString,Blanker : String40;
BEGIN
  Stamp.PutWhenStamp(Entry.Time);
  DirLine := '                                        ';
  Blanker := DirLine;
  {If the entry has the directory attribute, format differently: }
  IF (Entry.Attr AND $10) <> 0 THEN { Bit 4 is the directory attribute }
    BEGIN
      Insert(Entry.Name,DirLine,1); { No extensions on subdirectory names }
      Insert('<DIR>',DirLine,14)    { Tell the world it's a subdirectory  }
    END
  ELSE
    {This compound statement separates the file from its extension  }
    { and converts the file size to a string.  Note that we did not }
    { insert a file size figure into DirLine for subdirectory entries. }
    BEGIN
      DotPos := Pos('.',Entry.Name);
      IF DotPos > 0 THEN  { File name has an extension }
        WorkString := Copy(Entry.Name,1,DotPos-1) +
          Copy(Blanker,1,9-DotPos) + '.' +
          Copy(Entry.Name,DotPos+1,Length(Entry.Name)-DotPos)
      ELSE                       { File name has no extension }
        WorkString := Entry.Name +
                      Copy(Blanker,1,8-Length(Entry.Name)) + '.';
      Insert(WorkString,DirLine,1);
      Str(Entry.Size:7,WorkString);
      Insert(WorkString,DirLine,15);
    END;
  Insert(Stamp.GetDateString,DirLine,24);
  Insert(Stamp.GetTimeString,DirLine,34);  { Finally, insert the time }
  Delete(DirLine,42,Length(DirLine)-42);
END;
{----------------------------------------}
{ Methods: TDirEntryCollection           }
{----------------------------------------}
CONSTRUCTOR TDirEntryCollection.InitDir(ALimit,ADelta : Integer;
                                        Path,Mask : STRING;
                                        Attr      : Word);
BEGIN
  TSortedCollection.Init(Alimit,ADelta);
  AddDir(Path,Mask,Attr);
END;

CONSTRUCTOR TDirEntryCollection.InitCommandLine(ALimit,ADelta : Integer;
                                                StartParam : Integer);
VAR
  I     : Integer;
  SR    : SearchRec;     { Defined in the Dos unit }
  DEP   : PDirEntry;
  Path  : PathStr;       { Defined in the Dos unit as STRING[79]; }
  Dir   : DirStr;        { Defined in the Dos unit as STRING[67]; }
  Name  : NameStr;       { Defined in the Dos unit as STRING[8];  }
  Ext   : ExtStr;        { Defined in the Dos unit as STRING[4];  }
BEGIN
  TSortedCollection.Init(ALimit,ADelta);
  FOR I := StartParam TO ParamCount DO
    BEGIN
      FSplit(ParamStr(I),Dir,Name,Ext);
      AddDir(Dir,Name+Ext,AnyFile);
    END;
END;

CONSTRUCTOR TDirEntryCollection.InitTree(Alimit,ADelta : Integer;
                                         Path,Mask     : STRING;
                                         Attr          : Word);
BEGIN
  TSortedCollection.Init(Alimit,ADelta);
  TreeScan(Path,Mask,Attr);
END;

FUNCTION TDirEntryCollection.Compare(Key1,Key2 : Pointer) : Integer;
BEGIN
  IF (PDirEntry(Key1)^.Path + PDirEntry(Key1)^.Entry.Name) =
     (PDirEntry(Key2)^.Path + PDirEntry(Key2)^.Entry.Name) THEN
    Compare := 0
  ELSE
  IF (PDirEntry(Key1)^.Path + PDirEntry(Key1)^.Entry.Name) <
     (PDirEntry(Key2)^.Path + PDirEntry(Key2)^.Entry.Name) THEN
    Compare := -1
  ELSE Compare := 1;
END;

PROCEDURE TDirEntryCollection.AddDir(Path,Mask : STRING;
                                     Attr      : Word);
VAR
  I     : Integer;
  SR    : SearchRec;
  DEP   : PDirEntry;
BEGIN
  FindFirst(Path+Mask,Attr,SR);
  IF DosError = 0 THEN
    BEGIN
      DEP := New(PDirEntry,Init(Path,SR));
      Insert(DEP);
      REPEAT
        FindNext(SR);
        IF DosError = 0 THEN
          BEGIN
            DEP := New(PDirEntry,Init(Path,SR));
            Insert(DEP);
          END;
      UNTIL DosError <> 0;
    END;
END;

PROCEDURE TDirEntryCollection.TreeScan(Path,Mask : STRING;
                                       Attr : Word);
VAR
  I     : Integer;
  SR    : SearchRec;
  DEP   : PDirEntry;
  NextDirectory : STRING;
BEGIN
  fillchar(SR,sizeof(SR),0);
  { We look for and search any subdirectories first: }
  IF Path <> '\' THEN Path := Path + '\';
  FindFirst(Path+'*.*',Directory,SR);
  WHILE (DOSError <> 2) AND (DOSError <> 18) DO
    BEGIN
      IF ((SR.Attr AND Directory) = Directory )
      AND (SR.Name[1] <> '.') THEN  { We have a subdirectory }
        BEGIN
          NextDirectory := Path + SR.Name;
          TreeScan(NextDirectory,Mask,Attr);
        END;
      FindNext(SR);
    END;
  { At this point, we're in a directory that has no unsearched }
  { subdirectories, so we can search for files matching Mask:  }
  AddDir(Path,Mask,Attr);
END;

{ No initialization section }
END.





<a name="00fb_0014">
<a name="00fb_0015">
[LISTING TWO]
<a name="00fb_0015">

PROGRAM JFind;  { By Jeff Duntemann; from DDJ 4/92 }

USES DOS,CRT,Printer,  { Standard Borland units }
     DirList;          { From DDJ for 4/92      }
VAR
  Console    : TEXT;
  FileSpecs  : STRING;
  I          : Integer;
  FilesFound : PDirEntryCollection;

PROCEDURE DisplayFoundFiles(FilesFound : PDirEntryCollection);
{ This is the FAR local routine passed to the iterator method. }
{ It's called once for each item in the collection: }
PROCEDURE DisplayOneFile(Target : PDirEntry); FAR;
BEGIN
  Write(Console,Copy(Target^.DirLine,13,Length(Target^.DirLine)),' ');
  Writeln(Console,Target^.Path,Target^.Entry.Name);
END;

BEGIN
  { This is how you iterate a procedure over a collection: }
  FilesFound^.ForEach(@DisplayOneFile);
END;

BEGIN                     { JFIND Main }
  Assign(Console,'');     { This allows us to use Standard Output }
  Rewrite(Console);       {  for Write/Writeln statements         }
  IF ParamCount = 0 THEN
    BEGIN
      Writeln(Console,'>>>JFIND<<< by Jeff Duntemann');
      Writeln(Console);
      Writeln(Console,'Invocation syntax:');
      Writeln(Console);
      Writeln(Console,'  JFIND <filespec>,[<filespec>..] CR');
      Writeln(Console);
    END
  ELSE
    BEGIN
      FilesFound := New(PDirEntryCollection,Init(256,16));
      FOR I := 1 TO ParamCount DO
        FilesFound^.TreeScan('\',ParamStr(I),AnyFile);
      IF FilesFound^.Count > 0 THEN
        BEGIN
          DisplayFoundFiles(FilesFound);
          Writeln(Console);
        END
      ELSE
        Writeln(Console,'No files match that file spec.');
    END;
END.


Copyright © 1992, 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.