by Jim Salmons and Timlynn Babitsky
Spring 1989 – Page 52
The Gunakara Sun Systems Limited of Halifax, Nova Scotia, has released a new programming language and development environment that brings together three distinctly different and powerful computer science methodologies — object-oriented, dataflow and visual programming.
No one of these is overwhelming by itself, but by combining them Prograph provides a programming environment and a new language that will change the way you think about and write programs. We are fortunate to have it first, as a commercial product, on the Macintosh.
In this article, we introduce the Prograph language, then whet your appetite with a case study example of adding turtle graphics to a Prograph graphics application.
Prograph Roots: Visual OOPS & Dataflow
Prograph is an object-oriented programming system supporting hierarchical class structures and inheritance of attributes and methods. But Prograph breaks new ground as an OOPS environment by incorporating the dataflow paradigm within the specification of methods for its OOPS classes.
Prograph’s syntax-directed, graphic editor facilitates drawing dataflow diagrams that are the code of your program, not a design level abstraction of some text-based language. Unlike Mainstay’s V.I.P.™ and other CASE products which are conceptually tied to traditional procedural languages, Prograph is a visual language. Every one of Prograph’s variously shaped icon/nodes and edge connectors has syntactic meaning.
Further, Prograph diagrams are inherently concurrent — unless explicitly designated otherwise — and they are data driven and two-dimensional. As long as the required data to perform a specific operation is available, an operation can execute. And you can watch your data course through a method like balls falling through a Pachinko machine when you execute a Prograph program in step mode.
To introduce you to Prograph, we separate the language from its programming environment and give a top-down perspective.
In developing its visual programming language, Gunakara has successfully overcome the alphanumeric character entry bias of most programming. Prograph’s dataflow model provides for highly efficient programs — there are no traditional variables with all their associated declarations and assignments, for example.
The Prograph graphical editor allows you to quickly create classes and their method diagrams. A click in the open area of a class window makes a new class icon; a click in an attribute window adds an attribute; a click in a method window adds an operation and so on.
Prograph has a syntax-directed graphical editor — it won’t let you create an illegal diagram. It also knows all about the Mac. When you type in the name of a ROM calling operation (or select and transfer it from one of a variety of browser-like dialogs accessed from the Info menu), an operation icon appears with the correct number of input terminals and output roots.
Like most OOPS languages, Prograph supports both class and instance varieties of attributes and methods and it supports single class hierarchies. Option-click on a class icon and a rubber band connector appears for attaching to another class icon to define an inheritance hierarchy.
Three class icons are shown in the Classes window of Figure 1. On the left side of each class icon is an embedded class attribute icon, on the right is an embedded class methods icon. Click on the left side to open the class attribute window, on the right to open the methods window.
The Prograph Classes window graphically displays the class hierarchy of the open file. Subclasses inherit the attributes and methods of parents and grandparents.
In Figure 1, the Cat and Elephant classes inherit the generic attributes and methods of the parent class, Animal. The downward arrow in the attributes name, weight, toes indicate these attributes have been inherited. A subclass may have its own attributes in addition to those inherited. The Animal Hierarchy in Figure 1 shows that we want to keep track of a cat’s hair length and an elephant’s trunk length, each an attribute unique to its subclass.
Although attributes are inherited, the default value of each attribute may vary. So while the weight attribute of Animal is set to a default 30 pounds, a Cat is defaulted to 5 pounds and an Elephant is assumed to be around 2000 pounds.
Any attribute which is not assigned a default value is set to NULL at the start. When an object of a class type is created, it is initialized with its inherited class attribute structure and associated default values. Subsequent computations can read and modify these values. This access and updating of attributes is one of the chief activities performed in methods.
While Prograph makes a clear distinction between class and object attributes, it downplays the distinction between class and object methods. Both class and object methods are collectively displayed in the associated Methods window of each class.
User-defined Prograph methods are identified with a method icon. Double click a method icon to open a Case window which graphically displays the operations of the selected method. Case refers to the case structure of a Prograph method.
A method can have one or any number of cases. For a single case method, data flows in, the defined operations execute and data (optionally) flows out. For a multiple case method, multiple dataflow diagrams can be written to account for various execution conditions. Based on the success or failure of unification and boolean operations the appropriate case to be executed is selected.
Focusing on single case methods for a moment, Figure 2 shows two simple Prograph methods where one calls the other. Method “ask user” solicits two numbers from the user and passes them to method “divide&double.” This called method divides its first input by its second input and doubles the result before it passes the answer back to “ask user.” Method “ask user” ends execution by displaying the answer in a simple dialog window. (While this activity could be easily simplified to a single method, we use this example to show how one user-defined method can call another.)
Each node in a method diagram can have any number of “input knobs,” called terminals, and “output knobs,” called roots. The Prograph editor knows all about the various kinds of nodes that come with Prograph, so it provides the correct number of roots and terminals when one of these nodes is created. For user-defined nodes that are unknown to the system, the number of roots and terminals are specified by the user.
Information flows through a method diagram along lines connecting roots to terminals. These lines are called segments. A second kind of connecting line, the synchro, ties node to node to enforce explicit sequencing in an otherwise concurrent environment. The synchro in Figure 2 ensures that the user will be prompted for the dividend before the divisor.
Each case of a user-defined method has an input bar at the top of the Case window and an output bar at the bottom. What objects and data will be manipulated by a particular invocation of a method is determined by what is fed into the method from its calling method. What information is delivered back to the calling method is determined by what, if anything, is “poured” out the output bar of the called method.
A one-to-one relationship exists between the terminals of a node calling a user-defined method and the roots of the input bar on the method called. Alternatively, this relationship holds between roots of the node in the calling method and the terminals of the output bar of the method called. For example in Figure 2, compare the roots and terminals of the “divide&double” node in the “ask user” case window with the input and output bars of the “divide&double” case window.
By understanding the meaning of the variously shaped node icons and their roots, terminals and interconnecting segments and synchros, the computations performed by a method become clear. Figure 3 shows all of the types of nodes in Prograph and how to interpret them.
Prograph primitives include a couple hundred useful operations for such things as list, string and bit manipulation; user interaction; math; form handling and high level file and resource handling. Prograph persistents are comparable to global variables which are preserved even between sessions.
Macintosh methods include all the Toolbox ROM routines. Macintosh constants associate mnemonic names like blackColor to the return value 33, etc.. Macintosh globals provide access to the often accessed Quickdraw and Low Memory global variables such as thePort, CaretTime, dkGray and screenBits. Access Macintosh Address is used for locating Mac objects in memory and Get and Set Macintosh fields accesses the fields of Mac structures such as baseAddr, bounds and rowBytes fields of BitMap.
System supplied Prograph primitives and Macintosh methods (Toolbox routines) are “black boxes” — you cannot open them to their source diagram level. Each has a prescribed action and required and optional terminals and roots. Double-clicking on one of these nodes brings up a help dialog with an “Inside Macintosh”-like explanation of the node’s input, output and its action performed.
Double-clicking on a user-defined method node within a case window opens up the clicked node’s own case window. In this cross-referencing fashion, Prograph provides hypertext-like fluid access among the collection of classes and their methods that you develop as a Prograph programmer.
The two methods shown in Figure 4 utilize some of the node types introduced in Figure 3. Method “make & grow cat” creates a new Cat object which immediately ages one year and puts on eight pounds. The “open window” method directly accesses the Macintosh Toolbox to create a new window within a Prograph application.
With these two examples you begin to see that the Prograph language is very much a visual syntax. Prograph does not just lay control flow connections between elements of a traditional textual language. The medium is the message.
Root & Terminal Annotations
Prograph supports a variety of root and terminal annotations to get even more expressive power out of its dataflow methods. Root and terminal annotations are the basis of Prograph multiplexes. Multiplexes, in conjunction with its success and failure control annotations, give Prograph methods the execution flow you normally associate with such textual constructions as “If… then… else,” “For each… do,” “While… repeat,” etc.
As seen in Figure 5, when you add list, partition or loop annotations to the roots or terminals of an operation or a method node, the icon takes on a “stack of icons” appearance to indicate this operation will execute a number of times.
The “List” example of Figure 5, shows a multiplication operation set with a list “(…)” annotation at its left terminal and root. This annotation applies multiplication to each item in the left input list and returns a list of items each six times greater than its respective input.
The “Partition” example also shows a list annotation on the input terminal — the list to be partitioned. A partition node always has two roots; the left arrow bearing input list items that pass the partition test, and the right arrow bearing list items that fail the test.
The “Mixed” method performs an iterative addition on list input in Figure 5 and introduces the loop annotation (a matched terminal and root pair with arrows visually indicating that the results of this operation are fed out, “around” and back into the operation). Note that the intermediate iterative results are kept internal to the operation and only the final result, 6, is output.
The “Looping” example introduces a two-method iteration that depends on a unification control mechanism for termination. Method “Looping” calculates the factorial of 4 by looping on the “factorial” method it calls when it executes. The leftside root and terminal of the “factorial” method node in “Looping” accumulate the consecutive multiplications performed in “factorial”. The rightside root and terminal perform a decreasing index function for the number of times to call “factorial”.
Figure 5 also shows the internal workings of method “factorial” in the “factorial 1:1” Case window. The constant, zero, on the right root of the input bar has a “terminate on success” annotation. This unification says, “when the input root is equal to zero, terminate the iteration and pass to the output terminal those values that came in on this iteration.” Thus “Looping” concludes its iteration with the answer, 24, output from the left root of the “factorial“ method node.
The Case for Controls
The “terminate on success” control introduced above is one example of Prograph’s flow control mechanisms. Figure 6 introduces the repeat multiplex with a “next case on failure” control in the two-case “repeat” method.
Case one of “repeat” calls the “repeating dialog” method. In “repeating dialog,” the answer operation puts up a simple Macintosh dialog box with a “Do what?” prompt with Continue and Cancel buttons. As long as the user clicks on Continue, “repeating dialog” completes its execution and hands control back to “repeat” where the repeat multiplex again executes “repeating dialog.”
When the user clicks Cancel, the “fail on success” unification control within “repeating dialog” is satisfied. This means the method will fail when the Cancel button is pushed. The fail on success icon looks like an octagonal stop sign with a check mark in the center.
When “repeating dialog” fails from the user pressing the Cancel button, “repeat” stops calling the multiplex and drops to its next case. In this example, case two simply displays a dialog message, “That’s all, folks!”
If there were no control annotation on the multiplex, Prograph would signal an error with a message explaining, “The called Operation failed but has no associated control.” To handle this failure, the repeat multiplex has been annotated, here with a “next case on failure” control — an “X” in a rounded rectangle.
All together, there are ten different control conditions, nine of which have explicit icon annotations. “Continue on success” is the default condition assumed of all operation calls, so there is no icon annotation for it. In some non-critical situations, you may want to “Continue on failure” such as a call to close a nonexistent window. The remaining eight control conditions are an “on failure” and an “on success” version of each of four situations:
- Next Case, halt execution of current case and start execution of next case.
- Fail, halt execution of current case and fail call to method.
- Terminate, halt execution of current case and end execution of calling multiplex.
- Finish, continue execution of current case and end execution of calling multiplex.
Together with the case structure and multiplexing capabilities, this range of control annotations give you tremendous flexibility in controlling the flow of execution in your Prograph programs.
Turtle Graphics to Prograph
Now, to give you something to chew on we add turtle graphics to a Prograph drawing application that comes on the Prograph Examples disk and has the class hierarchy shown in Figure 7. The two classes we added to implement turtle graphics are highlighted.
The class hierarchy has four independent “trees.” The class “application” provides the attribute structure of the environment, maintaining a flag for whether or not the application is running and a list of menu objects and window objects. Its methods include all those needed to add, remove and locate windows. Other methods implement a main event loop and, through a “case-wise cascade,” handle the contingencies of getting and acting on events.
The individual menus of this application are implemented as subclasses of the parent “menu” class. General window features are encapsulated in the “window” class with specialized attributes and methods added to its “graphicWin” subclass for handling graphics windows. The remaining class tree, “graphic,” provides attributes and methods necessary to create and manipulate Quickdraw graphic objects.
The persistent, “application,” stores an object of the “application” class. Its menu-list holds an object of each of the menu subclasses. The persistent’s window-list attribute stores window objects from session to session. A method called “program” pulls the menu and window information from the persistent, initializes the menus and recreates any windows left from the last time the application was run. The “program” method then calls its main event loop method which lets you select shapes, then draw and redraw various Quickdraw-based graphic objects. Selecting Quit from the File menu exits the event loop method. The “program” method calls a “close” method to store the “application” persistent before terminating its own execution.
To incorporate turtle graphics into this drawing program, we need a new type of window subclass, “turtleWin,” and a new graphic subclass, “turtle.”
To interface turtle graphics into the existing graphics application, we add a “New Turtle Window” method to the “file” subclass. And we add “Turtle” as an item in the Shape menu and “turtle” to the “shape-list” attribute so the application knows what subclass of graphic object to create when the user selects the Turtle item and clicks in the graphic window.
Adding the “turtleWin” window subclass is easy. A turtle window is like a graphics window in almost every respect except that the (0,0) coordinate is shifted to the center of the window rather than located in the upper left corner. This brings the window’s local coordinates in line with the traditional turtle graphics coordinate system.
Option-click on the “graphicWin” icon in the Classes window, pull the rubberband downward and release the mouse button. Type “turtleWin” and you have a new subclass, inheriting the attributes and methods of its parent window classes. Three new unshown methods are all that are needed to complete the new subclass. We overshadow the parent graphic window’s “open” and “grow” methods to include a call to “setHomeCoords.” This third new method sets the port, computes an offset based on the window’s boundary rectangle and calls the Macintosh method, SetOrigin.
The “turtle” object, on the other hand, requires a goodly number of new methods to handle its unique characteristics and behavior. Sure, a turtle is a Pen in Quickdraw terms. . . but it is a “smart” Pen. A turtle understands Turtle Procedure Notation, a language which describes how the turtle is to move to trace an endless variety of geometric patterns in its window.
Graphic objects in the original drawing program have attributes to maintain their color, shape and boundary rectangle. The turtle’s needs are different. Clicking on the left side of the new “turtle” subclass icon opens its attribute window which lists the already inherited attributes from its “graphic” parent class. To this we add a “name,” “position,” “heading,” “tailWidth,” “trailColor” and the all-important “program” attribute. Double-clicking on any one of these attribute icons opens a dialog into which we can enter default values to give a new turtle some basic characteristics.
The graphic or turtle window has a “contents” attribute that stores a list of objects which are currently in the window. When a window is created, resized or otherwise becomes visible, an “update” method pulls each object from “contents” and tells the item to “draw” itself. That is why each turtle maintains a copy of its original program in its “program” attribute. A turtle pulled from a window’s “contents” list replays its pattern-producing program to redraw itself.
Having specified the turtle object’s attributes, we bring the turtle to life by giving it methods. From a design standpoint, we know a turtle object needs:
- To be “born” and be able to draw itself within the overall application environment.
- To be able to understand and perform each of the classic individual Turtle Notation Commands.
- To be able to read a complex turtle program — including nestable “repeat” and “doPoly” constructs — and parse it into its constituent commands so the turtle can draw its prescribed pattern.
Each area of turtle behavior is tackled individually, in a “divide and conquer” strategy. At first, we simply create a temporary method, “main,” which we “hardwire” into the application environment.
The “main” method feeds a “newborn” from a turtle object generator into a node calling whatever basic command method we are developing. In this way we build up methods that implement the “forward,” “back,” “left,” “right,” “home,” “setHeading,” “hideTurtle” and “showTurtle” commands of Turtle Notation.
Figure 8 is representative of these basic turtle movement commands. The “forward” method takes a turtle object and distance as input. The case windows behind the “turtle/forward” window display the three utility methods required to carry out the forward movement. These subsidiary methods compute cartesian coordinates from the vector-based heading and distance parameters used in Turtle Notation. This allows Quickdraw Pen commands to carry out the turtle’s drawing.
In Figure 8 and subsequent figures, you may notice a “/” character preceding method names. This slash indicates the call is to be made to the named method in the class of the object which is received as input. In most instances in this example the calls are to “turtle” class methods.
Parsing Turtle Notation
The “parse” method of Figure 9 and its subsidiary methods in Figure 10 evolved through a few rounds of “code and tweak” where Prograph’s debugging abilities make work easy.
Some particular points to notice include:
- The “detach-l” primitive extracts items from the front of a list based on how many optional roots it has, with the rightmost root being the remainder of the list.
- The odd-looking node in Cases 2 and 3 of “parse” and in “move” is an injection which allows run-time determination of a method call… a string, “forward” for example, is passed as input to the injected node which then calls the method named by the string.
- When in doubt, read the free-form comments spread all around the methods… (These comments are supported as part of the Prograph program file as you see them. They were not added to the figures after the fact… they can be toggled on and off to “clear the view.”)
The “repeat,” “doPoly” and “doSide&Turn” methods in Figure 10 are incorporated to increase the complexity of patterns that a turtle can trace with relatively few commands to its program. To expand the syntax of Prograph’s turtle, you need only add cases to the “parse” method along with whatever subsidiary methods carry out the new behavior.
All that remains is to provide a user interface for specifying turtles. A dialog box with fields to specify the various attributes, including a turtle’s program, is a Mac-like way to define new turtles.
Prograph provides a Resource Editor (written in Prograph and very instructive in Prograph programming in its own right) which allows you to define dialog forms and save them in the resource fork of your application files. We create a Turtle Maker Form, as seen at the bottom of Figure 11, and save it in the example graphics application file.
We then add a case to the “miss” method in the “graphicWin” class. This method executes whenever a click is detected in a graphic window and the click is not within the boundary rectangle of any existing graphic subclass object in the window. The “miss” method creates an instance of whatever item was last selected in the Shape menu. The new case specifies that if the shape item selected is Turtle, a turtle object generator creates a new turtle and adds it to the “contents” list of the active window. The turtle “miss” case then calls the “initialize” method shown in the upper left corner of Figure 11.
Method “initialize” calls “get turtle” to pack the turtle’s attributes into a list to be delivered as default entries in the Turtle Maker Form. The “creation form” method handles the form access and user entry. Note the use of synchros to ensure that the steps which set up, handle and close the form are performed in the correct sequence.
Finally, when the user dismisses the form, the application updates the window. As stated earlier, the graphic window asks each graphic class object in its “contents” attribute list to perform its own “draw” method. A turtle, as shown in Figure 11, draws itself by getting its “program” attribute and sending the turtle and its program to “parse.”
Figure 12 shows Prograph turtle graphics in action. Each of the five patterns in Turtle Window is a separate turtle. Each can be modified by double-clicking in a 20-pixel square area in the center of the turtle. This click fires a method which pulls together the selected turtle’s current attributes and brings up the Turtle Maker Form for editing.
We had a lot of fun developing the turtle extensions to Prograph. It will be even more exciting later this Spring when the Prograph compiler is released. In addition to allowing the development of stand alone applications, the Prograph compiler will produce modules which can be called from within the Prograph interpreter. This would let us turn our turtles into hares and still have the flexibility of the powerful Prograph development environment.
The Gunakara Sun Systems Prograph product is among the innovative software technologies emerging in the OOPS marketplace.
Prograph is available from:
The Gunakara Sun Systems, Ltd.
1127 Barrington Street, Suite 19
Halifax, Nova Scotia B3H 2P8
About the Authors
Jim Salmons and Timlynn Babitsky are President and Vice President of JFS Consulting which specializes in business development and technical communication in the OOPS and related markets. They recently finished the user’s manual for Digitalk’s Smalltalk/V Mac™ and are currently writing the manual for Prograph™.