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

.NET

Structured Programming


MAY92: STRUCTURED PROGRAMMING

STRUCTURED PROGRAMMING

The Triumph of the Black Box

Jeff Duntemann, KG7JF

God help us, I've held out as long as I could, but there is now no escape: I have to buy a car, The Magic Van is doing fine at eight years and 95,000 miles, but Carol's poor 12-year-old Colt met its maker not long ago, and we gotta have something else that moves pretty soon. So I've been looking under hoods and rolling my eyes.

In January of 1974 I replaced the carburetor of my 1968 Chevelle, freezing my tail in 5 degree temperatures and a windchill cold enough to liquefy nitrogen, out in the open on a Chicago street--and it worked. I didn't know what I was doing and didn't have the right tools to do it with, but poor Shakespeare tumed right over with a roar once I tightened the last bolt, and ran like a champ through several more Chicago winters until the rust uglies ate him.

Now, when I look under the hood of what they call a Chevy. I couldn't even tell you where the carburetor is. I don't even know if they still have them. The engine compartment looks like the set of a bad Frankenstein movie. They tell me there are five or six computers in there somewhere, faithfully keeping today's Chevies running clean, straight, and on the road. Five or six computers, running five or six different pieces of software, are fighting for control of the tin can I'm hurtling down the interstate in at more than a mile a minute.

This is supposed to inspire confidence?

Yeek. I know far too much about software to allow it under the hood of my car. I want Shakespeare back! (If anybody has but would part with a 1968 Chevelle two-door hardtop in good shape, puh-leez let me know.)

The Black Box Reconsidered

The essence of a black box is that you don't have to understand its internals and you don't have to fool with it. In the best of all worlds, A car would be an indestructible black block of science-fiction synthetic with wheels that turned. You get in, and it goes where it should and stops when it must. Failing that, a car should be simple enough so that any knucklehead with a wrench can keep it running. Complexity should not be subject to corrosion.

And this, I think, is what bothers me about Turbo Vision. It's too complex to really understand, but by the same token you still have to fool with it, sometimes at a fairly deep level. As I suggested last month, it's a black box, but not nearly a black enough one. This is a problem that will bedevil vendors of event-driven frameworks until we finally hit upon some sort of golden mean between power and complexity.

So where's the pointer to that golden mean?

I can think of at least two, from the battling titans of the compiler world: Microsoft's Visual Basic and Borland's Object Vision. One, like Turbo Vision, isn't quite black enough, and the other is maybe a hair too black. One is a relatively conservative vision, and the other is a totally radical one. But hey, we're in the ballpark. I'm going to tell you about Object Vision first, because it sets a far limit on the concept of "visual development" it's essentially 100 percent visual. After a close look at OV. Visual Basic will seem as familiar as an old pair of shoes.

Borland's Nonesuch

Object Vision just sort of came out of nowhere, and for the longest time nobody was quite sure what to make of it. Products that fit no established category have that trouble a lot. Most people I spoke to described OV as a graphical front end for Paradox. This is dead wrong. OV doesn't require that you own Paradox, nor does it insist on working with Paradox database files. It can create Paradox tables all by itself--just as well as it can create dBase tables, or Btrieve tables. If you'd prefer to store data in ASCII--or if you already have tabular data stored in ASCII--OV is perfectly happy there, too.

If it isn't a front end to a database, neither is it a database manager as we've come to know the term. To use a table in OV you have to create an application. There's no easy "browse table" mode that simply lets you select a table and poke around in it.

No. Despite Borland's protestations to the contrary, Object Vision is a programming language, and probably the most remarkable one to appear in some time. It isn't the best programming language to appear recently, by any means; (Visual Basic beats it hands down in utility) but it's the wildest conceptual ride I've taken in a while.

Visual Syntax

The trick is this: Object Vision is a Windows-based, event-driven programming language with a totally visual syntax. There's so little of our familiar one-step-ahead-of-another sequential statement coding that it might as well not be there at all. Instead, virtually all the elements of a program are drawn on the screen with interactive tools.

An OV application consists of four major elements:

Forms and Fields. Object Vision calls individual data items fields. There are no variables per se; every data item both holds data and displays it somewhere on the screen. Organizing the screen is done using forms, windows within which fields are arranged.

Creating forms and fields is done from the OV form tool, a mouse-essential draw program that should seem familiar and comfortable to anyone who has ever used a paint or draw program under Windows. The form tool lets you create a new form and make it the size you want, and then lets you pick fields from a tool bar of field types and arrange them on the form. Once the fields are positioned, the form tool helps you apply properties to the fields on the forms: attributes such as color, labels, label fonts, picture strings, date formats, numeric formats, and so on. Fields may be marked as noneditable, and as such are skipped when the user moves from field to field within a form.

From a height, defining forms and fields is equivalent to defining your variables and your user interface, all at the same time. It's by far the easiest and most straightforward part of OV work.

Value Trees. It's uphill from there. To apply values to the fields you create using the form tool, you must define a vabie tree for each field not connected to a link. (See "Links," further on.) A valtte tree is a rule, drawn as a graphical diagram in a special window, that dictates the value a field has at any given time. It's a little like a formula attached to a spreadsheet cell, except that value trees can be much more complex than spreadsheet formulas.

A value tree is a tree because there is only one final selected value (the root of the tree) but many possible paths between that root and different potential values. These values (called conclusions) are the leaves of the tree. The path from the root to one of the leaves depends on one or more conditions, tests composed of logical operators such as = or <> and @ functions.

Each time the value of a field is recalculated, OV follows the logic from the root outward, making tests along the way, until it reaches one of the conclusions. This conclusion (a value reached as the resuft of some logical decision) becomes the new value of the field. The conclusions (values) may be defined in terms of literal constants, other fields, @ functions, or combinations of these, combined in expressions via the common arithmetic operators.

This is a difficult concept to put across in only a few words. Figure 1 shows a value tree from one of the Borland example applications. The trick in understanding such diagrams is to avoid the temptation to think of them as flowcharts, readable from the top down. Start at the root, and follow the logic out to each one of the leaves. After a time or two the lights will come on.

Event Trees. At the core of event-driven programming is the idea that code exists to respond to events occurring outside the running application. OV allows you to define responses to various events and attach them to both fond and fields. The supported events include mouse clicks, selection and deselection, opening or closing of a form, any change to a field or a form, or an artificial event" created through an @ function called @EVENT.

You attach responses to events by building an efIent tree for a specific event and associating it with some object in the application. Like a value tree, an event tree is a graphical diagram displayed in a special window. Also, in a fashion similar to a value tree, an event tree begins at a root the event in question and expands through one or more logic paths to a conclusion, which for an event tree is some action rather than the assignment of a value to a field. (However, you can assign a value to a field in an event tree, using the @ASSIGN function which got me in trouble at one point.) For each occurrence of an event, only one of those conclusions is chosen and executed, depending on how the sequences of tests work out along the way.

The set of components for building an event tree is pretty rich. Some are explicit, such as @ functions for perfomilng the AND, OR, and NOT logical functions: others can be synthesized, such as creating a CASE construct from a multiple branch and the predefined OTHERWISE clause.

Figure 2 is an event tree from one of the Borland example applications. See if you can follow the logic; again, beginning at the event block and following each logic path to a final conclusion.

Links. All OV file I/O is done through links, logical connections to physical files. A link contains buffer variables that may be written to prior to the actual physical update of the desired record in a file. When the link is defined, different fields in the physical record may be connected to different fields in the OV application. The same physical field, in fact, may be connected to one OV field on write and a different OV field on read. A suite of @ functions such as @TOP, @NEXT, @PREVIOUS, @LOCAL, @UPDATE, and so on allows you to move around in the physical table file and manipulate records.

A universe of subtlety underlies OV links, and I confess I've barely scratched the surface. Inexact matches on search are supported, as are filters, logical tests a physical record must pass before being delivered into the OV link. For example, you can define a filter on a Product Code field, and only display records with product codes greater than 100, or beginning with "B," and so on.

Virtual fields are also supported. This allows you to add a read-only field to a database record, the value of which is calculated through some sort of expression. A Sales Tax virtual field could be created by multiplying a Sales Amount field by a Sales Tax Percent field. You can read from the Sales Tax virtual field as though it were present in the physical database, but not write to it. Virtual fields are recalculated whenever any of the component fields in their expressions change.

Links may also be created through the Windows DDE channel. There are some wild possibilities here, because OV has the power to load and run a Windows application if an OV application needs to link to that other application via DDE.

The documentation failed me on DDE links. Whereas the manuals probably include most or all of the detailed information, they aren't much help in suggesting just what one can do with DDE links.

No, No! Another Paradigm Change!

Thinking in Object Vision takes some getting used to. Gosh, it almost feels lIke another paradigm change! I'll stop short of saying that--Esther Dyson might repeat it, and it could catch on. But consider conceptual wrenches like this: Every variable in OV is visible a field--and is displayed on some form, whether you want to display it or not. This drove me nuts until the lights started popping in my head, and I simply defined a fond to hold all those variables that I didn't need to keep on display. Then I simply chose not to display that particular form! You have to see the variables, but you don't have to see the form they're attached to. Ergo, you don't really have to see the variables.

Then I realized that I could bring the hidden form up on demand as a kind of watch window to see what my "invisible" variables were doing. More lights start to come on. This is really, truly, visual programming!

As Cliff Secord said after his first wild ride as the Rocketeer, "I... like it!"

Only Square One

I suppose it's possible that Borland doesn't truly realize what they have here. They seem obsessed with the notion that it's programming with the programming squeezed out, as though programming were still universally seen as an unapproachable black art, 1975-style.

The problem is, with all the programming squeezed out, OV has to do back flips to get certain things done, in a way reminiscent of Prolog, which would sooner die than admit to being a procedural programming language. (It died.) Given that I'm only a beginner, it's possible that I'm not really thinking in OV terms yet, but I feel that OV is actually harder to use than it would be if it allowed a little more procedural control in certain areas. The suite of @ functions is quite rich, but I'd like something similar to Turbo Pascal waiting in the background to give me something even if it has to be couched in @ function form--that isn't supported out of the box. You can write your own @ functions as DLLs, but the documentation is sparse, and the process looks kind of forbidding.

Maybe that's a symptom of a broader problem: documentation that simply isn't all there. This very subtle product has only two thinnish manuals: a tutorial and a reference guide. They do an adequate job explaining the individual pieces (events, @ functions, and so on), but there is very little "cookbook" advice on how to hook the pieces together into useful applications. Such advice is critical because OV isn't like anything most of us are used to using.

I killed a lot of time trying to figure out something as simple as how to return control to the top field of a form once I had written the fields in the form out to a table through a link. The button that updated the table cleared the form, but clearing a form doesn't return control to the top. A great deal of trying this-and-that went on before I realized that @FORMSELECT would return control to the form top. Because control was in the form, the form was already selected, and it was by no means obvious that selecting the form again would bring control to the top without adverse effects.

It worked. The manuals should say so. They don't.

A third volume devoted to cookbook advice is necessary. Until it appears (or until some solid third-party books on the package appear) you'd better plan on figuring a lot of the "big picture" stuff out on your own.

Global Warfare

Other improvements would help as well. For a product that hints at an object-oriented nature, OV data storage looks amazingly like FORTRAN COMMON. All fields in an OV app are global! I at first assumed that fields in a form were known only within that form, but wrongo. If you have a customer address form and a vendor address form, you need to name one address field Cust Address, and the other Vendor Address, because a single field named Address would cause gross confusion. This would be no harder than providing a "Form.Field" notation; for example, Vendor Form.Address.

I got in trouble because I copied a button named "Save To Database" from a completed form and pasted the copy into a new form that I was designing. I changed the new buttons event tree to reflect the database served by the form it was in. Alas, the copied button, as tt happened, was the same button as the one in the first form! It was one button present in two database update forms, and its event tree updated only one database.

Simply allowing a button to display an arbitrary text label instead of its True Name would help a lot. As it is, you end up with wordy (and physically large) buttons with names such as "Save Vendor Info to Database" or "Change a Product Record." Such elaborately self-documenting buttons might be considered a feature rather than a bug if it weren't so hard to fit them into an already-crowded data-entry form.

As they exist today, forms manage flow of control but not data scoping. Some sort of locality would really help.

The View from a Height

But what Object Vision needs most desperately is something remarkably simple: a perusable hard-copy description of the application under development. That handy printout of the current state of the source code is such a fundamental need that we generally don't think of it as a "feature." Lordy, if you can't prInt out the source, how do you ever keep things straight once application grows out of toy mode into something complex enough to do useful work?

Alas there is no way to print out a single, concise summary of which fields have been defined and what forms they extst in, nor which @ functions they invoke, nor what their value trees draw on to generate their values. You either keep all this in the back of your head, or you're reduced to inspecting fields manually, one by one until you find what you need to know.

I got in trouble here too. During my early experiments in OV, I used the @ASSIGN function in a button's event tree to assign an arbitrary test value to a data field. I reused that button when I actually began building a working application, and forgot about its back door access to the "Part Number" field.

Then when I tried completing a form containing Part Number, I found it stuck with the value 1003. No matter what I did, the value 1003 kept reappearing at unpredictable moments. I quickly remembered using @ASSIGN the week before to put the value 1003 in "Part Number" --but I forgot which button's event tree was the one that invoked @ASSIGN! By then I had 30-odd buttons in eight different forms, and had to hunt through five of those forms before I found the culprit.

Eventually I discovered that pictures of forms can be sent to the printer, as an pictures of event and value trees. This is helpful, but it's by no means enough, and it must be done piecemeal. What we need (and this cannot be difficult for Borland to do) is a dialog box that controls generation of a "hard-copy application summary." I want to be able to use check boxes to select what sort of summary information I want (specific-named forms vs. all forms, types of fields or named fields vs. all fields: alphabetical listing of fields and their properties: cross references of fields by value tree and event tree, and so on) and then let the system prtntet produce a hard copy summary of the app.

In addition to that an Actor or Smalltalk-like field browser would be wonderful. In one window I would like to see the name of a field, which forms it is used in, what its properties are, and zoomable/scrollable panes displaying its event and value trees.

Apart from its error messages, there are no debugging tools of any kind in Object Vision: I won't carp too loudly because this is a problem with event tree to assign an arbitrary test value to myself don't have anything brilliant to suggest. Event driven debugging is an area that still needs a lot of research and I hope Borland and other firms offering event driven tools are putting some money and mental horsepower into that research.

Window on the Future

Don't let me leave you with the impression that Object Vision is unusable, or that I don't care for it. On the contrary, it's a work of brilliance that I like a lot, and I'm building a fairly sophisticated mail-order retailing system with it. Borland's claim that you can be building Windows applications with OV in half an hour is absurd, unless a two-dimensional version of "Hello world!" is all you're interested in. You'll need a day or two to read the manuals closely, and another day or two just to explore and mess around. But after a couple of days of study, you'll be able to start building a useful application, and might even be able to finish it and perfect it inside of a couple of weeks.

That ain't half an hour, but it's still damned amazing. We're talking Windows here, and the time required to learn a new Windows development environment and produce useful results is usually measured in months.

I recommend that you try Object Vision if you get the chance, in part because it's a quick learn and lots of fun, and in part because it's a peek at the future of programming. As GUIs get more and more complex, we may not be able to afford the luxury of to-the-metal. SDK-style development for any but the most lucrative horizontal applications.

Visual development is one possible answer to compressing the Windows development cycle. Object Vision's event and value trees are sentinal stuff, and while the package as a whole needs some maturing (and a whole new set of manuals) the idea is solid gold. The box is pretty black, but unlike Turbo Vision, you don't have to fool with what's under the hood--you just have to figure out the controls by trial and error.

This visual development business is fascinating. We'll take a look at Visual Basic next month.

A Better Source Code Lister

Back to Turbo Vision. Using the DIRLIST.PAS unit I presented last month, I rewrote my long-listed JLIST9.PAS source-code lister yet again, producing JLIST10--and losing several hundred lines of code in the proceess. Listing One (page 146) is the whole thing: a simple source-code lister that takes any reason able number of file specs on the command line (such as *.ASM or ?LIST.PAS) and puts all matching files out, neatly to a LaserJet II or compatible printer, with a summary banner and line numbers.

I was able to can about 300 lines of list-manager code, by incorporating Turbo Vision collections. Furthermore, the code I canned was necessarily specific to lists of just one type, and needed source code changes to apply to lists of other types. Not serious changes, but changes nonetheless. JLIST10 is a much better example of the power of TV collections than I was able to present last time.

You can probably modify JLIST10 to run on other printers. All of the printer-specific code is gathered toward the front. Mostly, your printer needs to be able to print in a 16.66 pitch font. Apart from that requirement, any printer, laser or otherwise, should work. Just splice in the appropriate printer-control codes.

How Irish Hackers Keep Warm

The latest J. Peterman's mail-order catalog has a page devoted to the "Irish Hacking Jacket," an expensive, tweedy, rumpled sort of overcoat that looks as though it comes with simulated moss already growing on it. The copy on the page begins this way: "Hacking? What does it mean? It means fooling around."

So far so good. I thought maybe the were on to something. But then, the next line: "On a horse."

It's a whole new twist on mobile computing. Maybe I don't need a car. Maybe I should just move to Ireland.


_STRUCTURED PROGRAMMING COLUMN_
by Jeff Duntemann

[LISTING ONE]
<a name="0127_000f">

{--------------------------------------------------------------}
{                            JLIST10                           }
{   Multifile source code lister with 8-char tab expansion     }
{                                    by Jeff Duntemann         }
{                                    Turbo Pascal V6.0         }
{                                    Last update 1/1/92        }
{--------------------------------------------------------------}

PROGRAM JList10;

USES DOS,CRT,Printer,  { Standard Borland units }
     DirList,          { From DDJ for 4/92      }
     When2;            { From DDJ for 1/92      }
CONST
  Up           = True;
  Down         = False;
  Single       = True;
  Double       = False;
  SingleRule   = Chr(196);  { D }
  DoubleRule   = Chr(205);  { M }

  JLogo : ARRAY[1..4] OF STRING =

  ('  DBDD     ZDDDDDDDBDDD     ZDD?',
   '   3  3  B @DDD?   3      ? 3  3',
   '   3  3  3     3   3      3 3  3',
   '  DY  @D A DDDDY   A      A @DDY');
  ESC1         = Chr($1B);
  ESC2         = ESC1+Chr($5B);

  LinesPerPage = 75;        { 75 assumes 8 lines per inch }

TYPE
  String80 = STRING[80];
VAR
  InChar       : Char;
  PrintPage    : Boolean;
  Space10      : String80;
  ListLine     : String;
  I,J          : Integer;
  FileSpecs    : String80;
  FileInfo     : String;
  PrintCommand     : String80;
  FilesToPrint : PDirEntryCollection;
  FileTime,Now : When;    { "When" stamps for time/date processing }

{---------------------------------------------------------------}
{                  PRINTER CONTROL ROUTINES                     }
{  These routines are all, to some extent, printer dependent.   }
{  Here, the control codes are specific to the HP LJII/III.     }
{---------------------------------------------------------------}
PROCEDURE PrinterReset;
BEGIN
  Write(LST,ESC1+'E');
END;

PROCEDURE PrinterToXY(X,Y : Integer);
BEGIN
  Write(LST,ESC1+'&a',Y-1,'R');
  Write(LST,ESC1+'&a',X-1,'C');
END;

PROCEDURE SetPrinterLinesPerInch(Lines : Integer);
BEGIN
  Write(LST,ESC1+'&l',Lines,'D');
END;

PROCEDURE SetLinePrinterFont;
BEGIN
  Write(LST,ESC1+'(s16.66H'); { Select Lineprinter font      }
END;

PROCEDURE SetIBMCharacterSet;
BEGIN
  Write(LST,ESC1+'(10U');     { Select IBM PC symbol set     }
END;

{-----------------------------------------}
{       END PRINTER-DEPENDENT CODE        }
{-----------------------------------------}

PROCEDURE SendFormFeed;
BEGIN
  Write(LST,Chr(12))
END;

FUNCTION ForceCase(Up : BOOLEAN; Target : String) : String;
CONST
  Uppercase : SET OF Char = ['A'..'Z'];
  Lowercase : SET OF Char = ['a'..'z'];
VAR
  I : INTEGER;
BEGIN
  IF Up THEN FOR I := 1 TO Length(Target) DO
    IF Target[I] IN Lowercase THEN
      Target[I] := UpCase(Target[I])
    ELSE { NULL }
  ELSE FOR I := 1 TO Length(Target) DO
    IF Target[I] IN Uppercase THEN
      Target[I] := Chr(Ord(Target[I])+32);
  ForceCase := Target
END;

PROCEDURE PrintRule(ShowSingle : Boolean; StartColumn,EndColumn : Integer);
VAR
  RuleChar : Char;
  I        : Integer;
BEGIN
  IF ShowSingle THEN RuleChar := SingleRule ELSE RuleChar := DoubleRule;
  FOR I := 1 TO StartColumn-1 DO Write(LST,' ');
  FOR I := StartColumn TO EndColumn DO Write(LST,RuleChar);
END;

PROCEDURE PrintStartBanner(FilesToPrint : PDirEntryCollection);
VAR
  TotalFiles : Integer;
  TotalBytes : LongInt;

PROCEDURE ShowSpecs(Target : PDirEntry); FAR;
BEGIN
  TotalFiles := Succ(TotalFiles);
  TotalBytes := TotalBytes + Target^.Entry.Size;
  Writeln(LST,Target^.DirLine);
END;
BEGIN
  TotalFiles := 0;  TotalBytes := 0;
  SetPrinterLinesPerInch(12);
  FOR I := 1 TO 7 DO
    BEGIN
      PrintRule(Double,1,134); Writeln(LST);
    END;
  FOR I := 1 TO 4 DO Writeln(LST,JLogo[I]);
  FOR I := 1 TO 7 DO
    BEGIN
      PrintRule(Double,1,134); Writeln(LST);
    END;
  SetPrinterLinesPerInch(6);
  PrinterToXY(1,12);
  Write  (LST,'Printer job initiated at '+Now.GetTimeString+'m');
  Writeln(LST,'  on '+Now.GetLongDateString);
  PrintRule(Single,1,134); Writeln(LST);
  Writeln(LST,'Requested filespec: ',FileSpecs);
  Writeln(LST,'Files to be printed:');
  Writeln(LST);

  FilesToPrint^.ForEach(@ShowSpecs);

  PrintRule(Single,1,134); Writeln(LST);
  Writeln(LST,'Total number of files to be printed: ',TotalFiles);
  Writeln(LST,'Total number of bytes to be printed: ',TotalBytes);
  SendFormFeed;
END;

{->>>>PrintFile<<<<-}
PROCEDURE PrintFile(ToBePrinted : PDirEntry);
VAR
  LineNumber,PageNumber : Integer;
  ListFileName          : String80;
  ListFile              : Text;

PROCEDURE PrintLine(LineToPrint : String; LineNumber : Integer);
CONST
  TabChar = Chr(9);
VAR
  I,J,LinePos,UpstreamPos,AddBlanks : Integer;
  Space8 : String80;
BEGIN
  Space8 := '        ';
  Write(LST,Space8,LineNumber : 4,'   ');
  LinePos := 1;
  FOR I := 1 TO Length(LineToPrint) DO
    IF LineToPrint[I] = TabChar THEN   { Expand tabs }
      BEGIN
        UpstreamPos := (((LinePos + 7) DIV 8) * 8) + 1;
        AddBlanks := UpstreamPos - LinePos;
        FOR J := 1 TO AddBlanks DO Write(LST,' ');
        LinePos := UpstreamPos
      END
    ELSE
      BEGIN
        Write(LST,LineToPrint[I]);
        LinePos := Succ(LinePos)
      END;
  Writeln(LST)
END;

PROCEDURE PrintHeader;
VAR
  I : Integer;
  Space8 : String80;
BEGIN
  Space8 := '        ';
  Writeln(LST,Space8,'FILE: ',ForceCase(Up,ListFileName),
          ' Version of ',FileTime.GetDateString,' ',
          FileTime.GetTimeString,'m        Printed on ',
          Now.GetLongDateString,'  at ',Now.GetTimeString,'m.',
          '    Page ',PageNumber);
  Write(LST,Space8);
  FOR I := 1 TO 116 DO Write(LST,Chr(196)); Writeln(LST);
  Writeln(LST);
  Writeln(LST);
END;

BEGIN   { PrintFile }
  LineNumber := 1; PageNumber := 1; Space10 := '       ';
  ListFileName := ToBePrinted^.Path+ToBePrinted^.Entry.Name;
  Assign(ListFile,ListFileName);
  Reset(ListFile);

  IF NOT EOF(ListFile) THEN PrintHeader;
  WHILE NOT EOF(ListFile) DO
    BEGIN
      Readln(ListFile,ListLine);
      PrintLine(ListLine,LineNumber);
      LineNumber := Succ(LineNumber);
      IF ((LineNumber-1) DIV LinesPerPage) > (PageNumber - 1) THEN
        BEGIN
          PageNumber := Succ(PageNumber);
          SendFormFeed;
          PrintHeader;
        END
    END;
  IF (LineNumber MOD LinesPerPage) > 1 THEN SendFormFeed;
  Close(ListFile);
END;  { PrintFile }

PROCEDURE SetupPrinter;
BEGIN
  SetLinePrinterFont;
  SetIBMCharacterSet;
END;

PROCEDURE PrintAllFiles(FilesToPrint : PDirEntryCollection);
{ This is the FAR local routine passed to the iterator method. }
{ It's called once for each item in the collection: }
PROCEDURE PrintOneFile(Target : PDirEntry); FAR;
BEGIN
  FileTime.PutWhenStamp(Target^.Entry.Time);
  PrintFile(Target);
END;

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

BEGIN                     { JLIST10 Main }
  IF ParamCount = 0 THEN
    BEGIN
      Writeln('>>>JLIST10<<< by Jeff Duntemann');
      Writeln('  Multifile listing utility');
      Writeln('  for the HP Laserjet Series II');
      Writeln('  Version of 12/31/91 -- Expands fixed 8-char tabs...');
      Writeln('  WARNING:  Emits printer control strings that are');
      Writeln('            *highly* specific to the HP Laserjet II!');
      Writeln;
      Writeln('Invocation syntax:');
      Writeln;
      Writeln('  JLIST10 <filespec>,[<filespec>..] CR');
      Writeln;
      Writeln('where <filespec> is the file or files to be printed,');
      Writeln('using the DOS filespec conventions, including wildcard');
      Writeln('characters * and ?.  A banner will be printed initially');
      Writeln('with a summary of all files to be printed IF any wildcard');
      Writeln('characters were entered as part of the file specification.');
    END
  ELSE
    BEGIN
      Now.PutNow;       { Fill a When stamp with today's time and date }
      FileSpecs := '';     { Concatenate all file specs into 1 string: }
      FOR I := 1 TO ParamCount DO FileSpecs := FileSpecs+' '+ParamStr(I);
      FilesToPrint := New(PDirEntryCollection, InitCommandLine(128,16,1));
      IF FilesToPrint^.Count > 0 THEN
        BEGIN
          Writeln;
          Write('>>>Jlist10 is printing ',FilesToPrint^.Count,' file(s)...');
          SetupPrinter;
          IF FilesToPrint^.Count > 1 THEN PrintStartBanner(FilesToPrint);
          SetPrinterLinesPerInch(8);

          PrintAllFiles(FilesToPrint);

          PrinterReset;            { Reset printer at job end }
          Writeln;
        END
      ELSE
        Writeln('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.