Structured Programming

It's one darn thing after another as Jeff tackles object design and multiple inheritance.


July 01, 1990
URL:http://www.drdobbs.com/architecture-and-design/structured-programming/184408385

Figure 1

Figure 2

Figure 1

JUL90: STRUCTURED PROGRAMMING

STRUCTURED PROGRAMMING

The Just One Thing Dilemma

Jeff Duntemann K16RA/7

Arizona! Carol and I rolled into Phoenix in the middle of a furious windstorm, packed into the Magic Van along with dogs, assorted succulents, and a truncated ficus, as well as anything Allied Van Lines had forgotten to pack the previous Wednesday.

Rolling clouds of trendy Santa-Fe style dust were blowing off the parched land and making visibility difficult in the Safeway Parking lot somewhere near Bell Road and 19th Avenue. I was picking my way carefully through the gloom when something large and brown bounded in out of nowhere, caromed off a Jeep Wrangler and crossed the lane immediately in front of me at considerable speed. I hit the brakes and watched as it wedged itself solidly under a USA Today newspaper rack.

"I bet that's a tumbleweed!" Carol said with some excitement.

"But this is a supermarket parking lot!" I objected, feeling all the romanticism of the Wild, Wild West, including images of singing cowboys, tumbling tumbleweeds, and wild coyotes (now made mostly of marine plywood and sold in Hallmark stores) take a sharp turn south and vanish into the Nostalgia Zone.

These days, I guess one escapes from dust storms by taking refuge in the nearest Circle-K. The cowboys all wear feed caps and listen to rap tapes in their jacked-up Ranchero 4x4s. But by gully, those tumbleweeds have refused to sell out -- even if it means tumbling right in the front door of K-Mart.

The world -- and Phoenix -- are full of surprises. What's true of Phoenix is true of the structured programming world as well. I like surprises, and there have been a bunch of them in recent months.

The most striking is the sudden appearance of three brand new Pascal compilers. I'd gotten used to thinking of Turbo Pascal as unassailable in the marketplace when QuickPascal appeared and did quite well, thank you. I then assumed that two major Pascal compilers was a mighty crowded field -- and now, perhaps, I may be wrong in that as well.

I've just made contact with the QuickByte Pascal people, so we'll push them on the stack until a future column. The other two Pascal products come from Jensen Partners and Stony Brook. I'll be testing them thoroughly in coming months, and you'll get a report when the testing's done.

Crossing a Banana with a Gorilla

JPI's new Pascal is notable for supporting objects with multiple inheritance. Multiple inheritance is one of those sleeper concepts that (in my opinion) nobody in this business quite understands as yet. Intuition tells me that it represents a radically different way of thinking about object design. I'll know when I've spent some time with it -- and I'll be asking some largish questions including whether it is in fact a good thing at all. In the meantime, let me take a little time to explain what multiple inheritance means.

In single-inheritance systems such as Smalltalk, Actor, and Turbo/QuickPascal, a class (object type in Turbo Pascal jargon) may have only one parent class. Pretty obviously, multiple inheritance allows a class to inherit from more than one parent class.

Think for a bit about that catering truck that blows into your office complex parking lot at 10 A.M. sharp, most appropriately playing "La Cucaracha" at 70 dB on its electronic horn. A catering truck is a truck: It has wheels, an engine, a steering mechanism, and a power train. A catering truck, however, is also a kitchen: It has a stove, a sink, a refrigerator, cabinets full of food, and a microwave oven. In the grand object hierarchy describing our wonderfully surprising world, the class CateringTruck inherits from both Truck and Kitchen.

Now, we've been talking about objects for most of a year as though any given class can only be a more specific offshoot of its parent class. A menu is a specific kind of window, which is a specific kind of rectangular area on the screen, and so forth. A menu may touch on many areas of the system at large, allowing you to choose files, or parallel ports, or customer numbers. Nonetheless, at the heart of it a menu is a kind of window. It pops up, does its thing, and then vanishes.

Applying that logic to class CateringTruck is not so clean. In a single-inheritance system, you have to make a choice: Is CateringTruck fundamentally a truck or a kitchen?

You Choose, You Lose

OK, let's choose, and draw it all out in Figure 1. If pressed, I'd say CateringTruck was more fundamentally a truck than a kitchen, so we'd place CateringTruck beneath Truck in the object hierarchy. Truck, in turn, is a child class of the SelfPropelledVehicle, which in turn is a child of base class Vehicle, as is RailroadCar. Vehicle defines those few methods common to all vehicles, such as Stop. (The expected Start method is not defined until the SelfPropelledVehicle class. A truck can put itself in gear and move forward ... but a railroad car can only put on its brakes.)

Kitchen is defined in a separate small hierarchy and is the child of base class ChemLab. (Think about it ...) Kitchen's methods include WashDishes, PrepareNextMeal, and so on.

Adding kitchen capabilities to vehicles is done by adding objects of class Kitchen to the definitions of CateringTruck and DiningCar as ordinary fields, in the same way that you would add an integer or a string field. The fact that Kitchen is an object class is incidental. The notation for invoking the WashDishes method would be ADiningCar.Kitchen.WashDishes or ACateringTruck.Kitchen.Washdishes. Now, is this such a bad thing? The vehicles still roll. The kitchens still cook. But ... polymorphism only operates along the line of inheritance.

What this means is that, whereas you could invoke a method like StopMotion on any child class of Vehicle, you could not invoke the PrepareNextMeal method belonging to Kitchen upon an object accessed polymorphically through a pointer to class Vehicle, even though both CateringTruck and DiningCar descend from Vehicle. The problem is that no common ancestor of CateringTruck and DiningCar contains the Kitchen object -- certainly not Vehicle.

We could, of course, add the Kitchen field to the definition of type Vehicle. That, however, defeats the whole purpose of object-oriented design: To distribute functionality across an object hierarchy as appropriate. All vehicles do not contain kitchens, so forcing a kitchen field into the Vehicle class, while syntactically correct, is semantically absurd.

The Just One Thing Dilemma

What we're seeing here is a problem I'm calling the "Just One Thing dilemma." If object-oriented design is a process of modelling some portion of reality in logical abstractions, then single-inheritance systems impose an artificial limitation on the modelling process: The assumption that all objects are fundamentally Just One Thing -- that one aspect of a modelled object is necessarily deeper than all others, and that that aspect is the only one through which the polymorphism mechanism operates.

Still not convinced that this is a problem? Let's return to the Metaphor Zone for a moment. Suppose we're modelling a major sports event in a stadium in Baltimore. Some people are coming down from New York by train, and will arrive just before the game begins. Others are driving in, and will be milling around in the parking lots before game time. Everybody needs to be fed in time to be finished before the game begins. So at 5:00 sharp, we issue a command to all vehicles capable of providing food: PrepareNextMeal. Each type of vehicle responds in its own appropriate fashion, according to the principles of polymorphism. The porters in the Metroliner begin preparing quiche in the dining car, and the owners of the fleet of catering trucks cruising the stadium parking lots begin putting hot dogs on to grill. One way or another, everybody waiting to get in to see the big game gets fed -- and all at the direction of a single command common to all food providers.

With single inheritance, there is no way to issue that single PrepareNextMeal command to both the catering trucks and the dining cars. We have decided that catering trucks and dining cars are fundamentally vehicles, and the only aspects the two have in common are those inherited from their common ancestor, Vehicle. The fact that both contain a field named Kitchen is incidental. They did not inherit their kitchenness -- it was just sort of glued on after the fact. And polymorphism operates only through inheritance.

If object-oriented design is to be a modeling process, then we have to face up to the fact that real-world objects are often mongrels with conceptual parentages going off in every direction at once. The more complex the system, the less likely that any given element in the system will be Just One Thing.

The Multiple Inheritance Solution

The solution is to let dining cars and catering trucks inherit their vehicleness from class Vehicle, and their kitchenness from class Kitchen. Figure 2 shows the classes of Figure 1 redrawn to indicate this new set of relationships. DiningCar and CateringTruck are now equally descended from Vehicle and from Kitchen. Each has everything that a Kitchen object has, and each has everything that a Vehicle object has.

Polymorphic method calls are no longer a problem. CateringTruck can be addressed as easily as a Vehicle object as it can be addressed as a Kitchen object. To direct all food service vehicles to stop their motion, you gather the food service vehicles into a linked list of Vehicle objects and simply call Vehicle. Stop for each vehicle on the list.

On the other hand, to direct all food service vehicles to prepare the next meal, you gather the same food service vehicles into a linked list of Kitchen objects instead -- and then call the PrepareNextMeal method for every object on the list.

When Fields Collide

One major semantic problem with multiple inheritance occurs when a class inherits the same identifier from more than one parent. For example, a Vehicle object might have a data field called Door -- and so might Kitchen, since the two concepts we're modelling, vehicles and kitchens, both have doors. So if you want to call the method MyCateringTruck. OpenDoor, which method is called: The OpenDoor method inherited from Kitchen, or the OpenDoor method inherited from Vehicle? Or, for that matter, which Door field is acted upon?

Different languages treat this problem in slightly different ways, but in general, when two inherited identifiers collide, both are rendered invisible within the scope of the class that inherits them. In other words, when it inherits two different Door fields, CateringTruck objects lose the ability to manipulate either.

Then again, if the Kitchen stove catches fire, you're going to want to manipulate the door in one helluva hurry. CateringTruck objects may legitimately have doors. The question you the designer must answer is, which door field will be used within CateringTruck? Your implementation of the CateringTruck object might be such that the doors are nothing more than the same doors defined in the Vehicle object. You must then explicitly specify that the Door field that will be visible within CateringTruck will be the Doorfield inherited from Vehicle.

This can be done in many ways, and I'll explain how it's done in TopSpeed Pascal once I actually receive a copy of the product. (At this writing I have only seen the documentation.) The important thing to understand is that you the designer make the choice, at compile time, which of two colliding identifiers will remain visible.

Weaving a Tangled Web

A lot of researchers are uncomfortable with multiple inheritance because it can easily become a creator of far more complexity than it manages as a structured tool. The simple metaphor of the CateringTruck and DiningCar classes works tolerably well because there is no more connection between two otherwise independent object hierarchies than there must be to do the job. It doesn't take a lot of imagination to see what might happen when 7 or 8 or 20 complicated object hierarchies begin mixing it up in a big way.

In most implementations, whereas colliding identifiers are made invisible, they still exist within an object, taking up memory even if they are not accessible. In other words, if a class inherits six colliding identifiers called Door and only uses one of them, you still have five invisible (and useless) Door fields inside every instance of that class. (I think there is a mechanism built into C++ 2.0 to ensure that only one of a set of colliding members is physically present within an object instance, but it is a measure of the difficulty of C++ that I can't really tell as yet.)

I suspect that over time a new kind of coupling will be recognized in OOP design: The degree to which two or more object hierarchies are interconnected via multiple inheritance. I further suspect that less will be more here, both to keep object instances small and efficient, and also (at a higher level) to keep the whole conceptual nature of the object hierarchy from turning into something the color of Arizona mud. And while a given concept being modeled as an object may be more than Just One Thing, it is just as certainly not Ten or Twelve Things, either.

Remember: Moderation in all things ...er ...objects.

Out of Sight, 0ut of Mind

Of course, it's easy to talk about catering trucks and dining cars and things that bear no relationship to the situations we encounter as DOS programmers, right? Well, let's bring the multiple inheritance question a little closer to home, by asking, "What is a menu, really?"

An Ivory Soap majority (99.44/100%) would say that a menu is a screen mechanism through which you choose one item from a displayed list of several. The underlying assumption is that the screen mechanism is the essential aspect of the menu. Data? What's data? Oh ... that stuff....

I guess it's human nature to assume that what is visible is most important. But consider: There would still be data without menus, but a menu without data would be useless and futile. Let's move data back to the true center of things and define a menu as an array whose index is directly controlled by the user through the keyboard and screen. You could also call it a list whose current pointer is directly controlled by the user, depending on the implementation. (Let's take a step back from the nature of the implementation and call any class that consists of more than one data item a collection.)

This isn't sophistry, but an issue that strikes to the heart of object-oriented design. Writing a menu object and passing it an array or list as a parameter is the Old Way. In keeping with the object metaphor of active data, each collection class should contain its own menuing method. In other words, when we want to choose one item out of a collection named MyCollection, we should be able to make a call something like this:

  ChosenItem := MyCollection.Choose;

To get you comfortable with this notion at the source-code level, I've written a very simple demonstration unit for Listing One (ARROBJ.PAS), page 149, that implements a string array class in Turbo Pascal 5.5. Class StringArray follows the strict interpretation of object-orientation: That a class' internal fields are private and may be accessed only through function calls. This imposes a certain performance hit, but in return it allows the physical implementation of internal data to be anything at all. For example, you could actually store the elements of StringArray's "array" as records in a random-access file, with the index of the array acting as the random file pointer. Obviously this makes access time dependent on the disk speed, but would allow you 32,767 elements of any reasonable size -- something you simply can't do when limited by Turbo Pascal's single 64K data segment.

(Note that other changes would be required to support an array larger than 15 items or so, because in this simple implementation there is no scrolling logic, and the entire array must be small enough to be displayed on the screen at once.)

Listing Two (page 150) is a simple demonstration program that creates an instance of StringArray, fills it with strings of random characters, and then invokes the Choose method to display the built-in menu and allows the user to choose one of the 15 elements in MyArray. There is also a Display method that simply puts the contents of the array up in a window for inspection, without any opportunity to select and return an individual element.

The important thing to understand about the StringArray class is that it is first and foremost an array, to be used where an array should be used. The menuing mechanism is a convenience, and can simply be ignored if you don't happen to need a menu.

Not My Yob, Mon

In my view of an object-oriented world, all collection classes should contain both browsing and menuing methods. In very simple classes such as StringArray, this doesn't involve a lot of complicated code. On the other hand, in an application written around an elaborate text windowing scheme, having collection classes implement their own menuing and browsing code is wasteful and redundant.

So who should make the menu? In an ambitious application with an object-oriented user interface, making menus is not the job of a collection class. Worse, when important polymorphic method calls must be made to all active windows (like ReDraw) a menu must be descended from the abstract window class, which defines the ReDraw method. On the other hand, gluing a collection class onto a menu object as an ordinary field forbids polymorphic access to the menuing method from a collection class ancestor.

This is where multiple inheritance earns its keep: By allowing object hierarchies to specialize without forcing object classes into unnatural unions -- like making collection classes descendants of screen window classes. In a multiple-inheritance environment, the collection classes inherit fundamental menuing methods from the user-interface hierarchy without losing their true heritage as collections.

We'll See

Needless to say, this is all background information. In a future column I'll give you some real code in TopSpeed Pascal to show you how multiple inheritance works. As I've tried to show here, the concept seems to solve a couple of really knotty reservations I've had about objects all along. This isn't to say it won't uncover half a dozen more. (Life is like that.) I'll let you know as soon as I know myself.

Odd Lots

Traffic from earlier columns has been piling up during our mad dash to the desert. Let's read the mail....

First of all, I need to back away from an assertion I made last year about Smalltalk/V PM. The way it was explained to me, Smalltalk is almost by definition an interpreted system, and while the interpreter can be hidden inside a "sealed off" application, it's still gotta be in there somewhere. The explanation is complicated, but suffice it to say that Jim Anderson and his crew of wizards have shown me to my own satisfaction that they have in fact made a compiling Smalltalk that loses nothing functional -- and gains them an immense performance edge over interpreted implementations. Sooner or later, when I find a version of OS/2 PM that will make friends with my machine, I'll probe further.

And while I'm backing away from that one, I'll keep going and admit that my gripes about Modula-2's limited set size are way behind the reality of today's commercial compilers, as numerous readers have pointed out. In fact, with the TopSpeed and Stony Brook compilers, not only can you have SET OF CHAR, but you can have a set of CARDINAL -- with up to 65,536 elements. If anything, Wirth erred here by giving compiler implementors too much leeway to decide the size of sets. Had he said flat-out up front that sets must support 256 or 65,536 elements, we'd have a less ambiguous standard and I'd have less opportunity to be wrong.

Several people have pointed out in letters that TopSpeed Modula-2 adds two operators to the language for bit-shifting. The >> operator shifts to the right and << shifts to the left. Missed that somehow, but it's an extension well worth having.

Steve McMahon wrote to warn against using global variables in Modula-2 modules (as I did in BITWISE.MOD, presented in March 1990) whenever possible. Modula-2 is a multitasking language, and global variables make modules pointedly non-reentrant, since the interruptor can futz a global that the interruptee isn't through with yet. Hoo-boy; hadn't thought of that one. Fortunately, my understanding of reentrancy indicates that making both variables local to the procedures in which they are used will solve the problem, because each entry of a procedure creates its own stack frame, where local variables live their short lives.

I've very recently received a damned fine Modula-2 communications library from a very small company in Ohio. Solid Software's Solid Link supports interrupt-driven communications via COM1 through COM4, with buffered input and output. It also has the distinction of being the only toolbox on my shelf for any language that supports the notoriously difficult-to-implement ZModem file transfer protocol. (It also supports XModem, YModem, YModem-G, TeLink and SEALink, plus some variations on the above.) The library is implemented entirely in assembler, and claims to be able to communicate at 19,200 baud or faster. (I haven't done any speed tests as of yet.)

The manual is fairly terse, but it has the virtue of containing numerous code examples including a complete and thoroughly nontrivial terminal program. Certainly if you need to implement any file transfer protocols in Modula, you can save yourself a titanic amount of work by letting someone else do the hard stuff. Highly recommended.

Products Mentioned

TopSpeed Pascal Jensen & Partners International 1101 San Antonio Rd., Ste. 301 Mountain View, CA 94043 415-967-3200 Price: Contact Vendor

Solid Link Solid Software Inc. P.O. Box 8132 West Chester, OH 45069 513-777-1414 Price: $199

Plenty of Here Here

I believe it was Gertrude Stein who said of Oakland, "There's no there there." Not so -- the real problem with California is that there's not enough There to go around. She should have tried Arizona. There's so much here here that that won't be a problem for some time to come. The sky seems so enormous as to swallow you whole, out on the high desert north of Cave Creek, where I expect to live someday. People warn me of those days when it gets down to 108 degrees after midnight. I guess. But then again, you don't have to shovel heat out of your driveway. In the meantime, Mr. Byte has learned that you can in fact lift your leg on a saguaro cactus ... if you're careful.

Maybe twelve years of wandering are enough.

Maybe I've come home.

_STRUCTURED PROGRAMMING COLUMN_ by Jeff Duntemann

[LISTING ONE]



UNIT ArrObj;

{ A simple string array object   }
{ with display and menu methods  }
{ to demonstrate one need for    }
{ multiple inheritance.  Needs   }
{ Turbo Pascal 5.5.              }
{ By Jeff Duntemann              }
{ Presented in DDJ for July 1990 }


INTERFACE

USES Crt,BoxStuff;

CONST
  LowBounds = 0;
  HighBounds = 14;


TYPE
  String40 = STRING[40];

  StringArray = OBJECT
                  Data  : ARRAY[LowBounds..HighBounds] OF String40;
                  Index : Integer;
                  CONSTRUCTOR Init;
                  PROCEDURE SetIndex(NewIndex : Integer);
                  FUNCTION  GetIndex : Integer;
                  PROCEDURE SetCurrent(NewString : String40);
                  FUNCTION  GetCurrent : String40;
                  PROCEDURE Display(X,Y : Integer);
                  FUNCTION  Choose(X,Y : Integer) : String40;
                END;


IMPLEMENTATION

VAR
  I       : Integer;
  MyArray : StringArray;


PROCEDURE ShowReverse(X,Y : Integer; Target : String40);

VAR
  Save : Word;

BEGIN
  Save := TextAttr;
  TextColor(Black);
  TextBackground(LightGray);
  GotoXY(X,Y); Write(Target);
  TextAttr := Save;
END;


PROCEDURE ShowNormal(X,Y : Integer; Target : String40);

VAR
  Save : Word;

BEGIN
  Save := TextAttr;
  TextColor(LightGray);
  TextBackground(Black);
  GotoXY(X,Y); Write(Target);
  TextAttr := Save;
END;


PROCEDURE Uhuh;

VAR I : Integer;

BEGIN
  FOR I := 1 TO 2 DO
    BEGIN
      Sound(50);
      Delay(100);
      NoSound;
      Delay(50);
    END;
END;



{-----------------------------}
{ Method definitions follow:  }
{-----------------------------}

CONSTRUCTOR StringArray.Init;

VAR
  I : Integer;

BEGIN
  { Clears the strings in the array to null: }
  FOR I := LowBounds TO HighBounds DO
    BEGIN
      SetIndex(I);
      SetCurrent('');
    END;
END;


PROCEDURE StringArray.SetIndex(NewIndex : Integer);

BEGIN
  Index := NewIndex;
END;


FUNCTION  StringArray.GetIndex : Integer;

BEGIN
  GetIndex := Index;
END;


PROCEDURE StringArray.SetCurrent(NewString : String40);

BEGIN
  Data[Index] := NewString;
END;


FUNCTION  StringArray.GetCurrent : String40;

BEGIN
  GetCurrent := Data[Index];
END;


PROCEDURE StringArray.Display(X,Y : Integer);

VAR
  I : Integer;

BEGIN
  MakeBox(X,Y,42,HighBounds+3,GrafChars);  { Show a box }
  FOR I := LowBounds TO HighBounds DO
    BEGIN                                  { and display the strings }
      GotoXY(X+1,Y+1+I); Write(Data[I]);   {  inside it. }
    END;
END;



{ This is a VERY simple bounce-bar menuing method for    }
{ the string array object.  No scrolling is done; if you }
{ define more elements that will fit on the screen, you  }
{ must allow for the user's scrolling the list up and    }
{ down within the window defined by the line character   }
{ box.                                                   }

FUNCTION StringArray.Choose(X,Y : Integer) : String40;

VAR
  I : Integer;
  Done,EscPressed : Boolean;
  Ch : Char;

BEGIN
  Display(X,Y);
  { Highlight the element at Index: }
  ShowReverse(X+1,Y+1+Index,Data[Index]);
  I := Index; Done := False; EscPressed := False;
  { Get user input until Enter or Esc is pressed: }
  REPEAT
    REPEAT {Null} UNTIL KeyPressed;
    Ch := ReadKey;
    CASE Ch OF
      #0  : BEGIN  { 0 means an extended key code follows: }
              Ch := ReadKey;
              CASE Ch OF
                #72 : BEGIN  { Up arrow }
                        ShowNormal(X+1,Y+1+I,Data[I]);
                        IF I = LowBounds THEN UhUh  { Up Arrow }
                          ELSE Dec(I);
                        ShowReverse(X+1,Y+1+I,Data[I]);
                      END;
                #80 : BEGIN  { Down arrow }
                        ShowNormal(X+1,Y+1+I,Data[I]);
                        IF I = HighBounds THEN UhUh { Down Arrow }
                          ELSE Inc(I);
                        ShowReverse(X+1,Y+1+I,Data[I]);
                      END;
              END; { CASE }
            END;
      #13 : Done := True;  { Enter pressed; we're done }
      #27 : BEGIN
              EscPressed := True;  { Don't change anything }
              Done := True;
            END;
    END; { CASE }
  UNTIL Done;
  IF EscPressed THEN  { Put things back as they were: }
    BEGIN
      ShowNormal(X+1,Y+1+I,Data[I]);
      ShowReverse(X+1,Y+1+Index,Data[Index]);
    END
    ELSE
    BEGIN
      Index := I;             { I becomes the new index }
      Choose := Data[Index];  {  and we return the string at Index }
    END
END;

END.





[LISTING TWO]


PROGRAM MenuArray;

{ Demonstrates a string array object   }
{ with its own built-in menuing method }
{ By Jeff Duntemann      }
{ From DDJ for July 1990 }


USES Crt,ArrObj;

VAR
  I        : Integer;
  MyArray  : StringArray;
  MyString : STRING;


FUNCTION PullRandomString(DoLength : Integer) : STRING;

VAR
  I : Integer;
  TempString : STRING;

FUNCTION Pull(Low,High : Integer) : Integer;

VAR
  I : Integer;

BEGIN
  REPEAT                   { Keep requesting random integers until }
    I := Random(High + 1); { one falls between Low and High }
  UNTIL I >= Low;
  Pull := I
END;

BEGIN
  FOR I := 1 TO DoLength DO
    TempString[I] := Chr(Pull(32,125));
  TempString[0] := Chr(DoLength);
  PullRandomString := TempString;
END;


BEGIN
  ClrScr;
  MyArray.Init;           { Set up the object }
  FOR I := 0 TO 14 DO     { Fill the array with random strings }
    BEGIN
      MyArray.SetIndex(I);
      MyArray.SetCurrent(PullRandomString(40));
    END;
  MyArray.SetIndex(7);    { Point the index to element 7 }
  MyString := MyArray.Choose(5,2);   { Invoke menu method }
  GotoXY(6,22); Writeln(MyString);   { Display the chosen string }
  Readln;
END.









Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.