Gnymph is a visual, object-oriented, general purpose dataflow programming language that runs under Linux. Users can edit a set of diagrams to describe their program, step through the diagrams to trace program execution and remove any bugs, then compile those diagrams to C source. It reduces development time, improves documentation, and puts tools for the collaborative creation of software into the hands of visually-oriented end users.
Caveat: This is very much pre-alpha software. It can immediately be useful for making dataflow diagrams and visualizing program structure, however, its current status as a compiler only shows where I'm going. There is also a great deal of unorganized and unedited documentation for the source; it's not included with this release because I have to first remove the self-contradictory parts.
It is released under GPL version 2.
Contents
To start Gnymph:
Building Gnymph requires that the development files for gtk2 have been installed.
The primary edit space of Gnymph is the schematic. This is a scrollable canvas on which the user can place and connect chips. It can be scrolled a small amount with the arrow keys. It can be scrolled a page at a time with PgUp | PgDn | Shift-PgUp | Shift-PgDn. Right drag in space will scroll the schematic with the mouse. Return to the origin with Home.
There are three main types of schematics. The program schematic is unique and is at the top of the hierarchy. It can include classes, static methods, global variables, and methods to get and set those global variables.
The class schematic defines a class. It can include all the items that can appear in the root schematic, including classes. It can also include class methods, instance attributes, get and set methods for instance attributes, and a constructor and destructor. Double-clicking on a class icon will display and edit the schematic for that class.
Method schematics define the implementation of static methods, class methods, get and set methods for globals and instance attributes, constructors, and destructors. They each have one input bar and one output bar. They also have a set of chips through which data flows between the input and output bars. Double-clicking on a method icon will display and edit the schematic for that method.
The primary visual element on the schematic is the chip. A chip is created by clicking in the schematic background. The chip can be moved by left down on the chip and dragging. It can be deleted with clipboard operations Cut or Clear.
Above the type icon may appear an IO-bar. On the left of the IO-bar are the regular input terminals; on the right are the regular output terminals. An IO-bar is created by right down in the type icon and dragging directly up. It can be resized by a right down in the IO-bar and dragging up or down.
Below the type icon is an optional enable icon. It determines whether or not the chip executes and outputs a value when the chip does execute. On the left is an optional enable input terminal; on the right is an optional enable output terminal. An enable icon is created by right down in the type icon and dragging directly down. It can be removed by right down in the enable icon and dragging directly up.
Below the enable icon if present or the type icon if not is an optional chip name. It is given a default value when the chip is created. It can be changed by left clicking on it; this puts Gnymph in a text editing mode. Some names are special; these are hidden and the type icon changed to a specific image. For instance, if the name of a call is changed to '+', then the name is hidden and the icon type is changed to the addition symbol.
If the chip name is not present then an inject icon is present in its place. It has exactly one input terminal on its left. It specifies that the name of the chip will be determined at runtime. The name is provided on the input terminal. An inject icon is created by setting the name to the empty string. It can be removed by right clicking on the icon. In this case, a default name will be assigned to the chip.
Connections between terminals can be created by left down on the source terminal and left up over the target terminal. An input can only be connected to an output. Inputs cannot be connected together, nor can outputs be connected together. A connection can be removed by repeating the connection process: left down on one end of the connection and left up on the other end.
The chip and every pin on it can have an optional comment. This is a multi-line text string. It is created by left clicking on the item while the Control key is held down. This creates a comment with a default value. It can be removed by setting the comment text to the empty string. Comments have a position relative to their owner pin or chip. This relative position can be changed by left down on the comment and dragging.
A single chip can be selected by left clicking on it. Multiple chips can be selected by dragging an area in the schematic; all chips (if any) inside the drag rect are selected. Chips can be added to a selection by holding down the Shift key while selecting. A selection can be cancelled by right clicking in space or by pressing Escape.
In general, performing an operation (such as changing the chip type or adding a particular pin) will apply to all chips in the selection.
When text is being edited, a portion of the text can be selected by left down in the text area and dragging.
Popup menus are brought up by right clicking. They display as a circle with wedges and a cancel disk in the middle. Left clicking on the cancel disk will make that level of the menu go away without selection. As the mouse moves around the wedges, the current wedge is highlighted. Left clicking on a wedge will select that icon and finish the menu. Right clicking on a wedge will pull up a submenu if a submenu exists for that icon.
Right clicking on the type icon of a chip brings up the chip type menu. If a selection is made, then the chip type will change to the selection. Right clicking in space when there is no selection will select the default chip type to use when creating chips.
Right clicking on a pin will bring up the pin type menu to select the type of the pin. Right clicking in a pin area but not on a pin will select the default type of the pin to create. Available pin types are
Simple -- normal input or normal output
List -- input iterator or output accumulator
Self -- pass the instance self to this input, or assign the
output to the instance self. Cannot be connected if an input.
Default pin type of object input is self; in this case the pin is
not shown
Self-list -- iterate self as a container if on an input pin, or
accumulate to self if on an output pin
An extreme example of what's possible with terminal annotations is given in the following figure. It also demonstrates variable arity and runtime name injection.
Right clicking on an enable icon will bring up the enable type menu. Available enable types are
True-true -- the input must be true for the call to execute.
The output will be true when and if the call has executed
True-false -- the input must be true for the call to execute.
The output will be false when and if the call has executed
False-true -- the input must be false for the call to execute.
The output will be true when and if the call has executed
False-false -- the input must be false for the call to execute.
The output will be false when and if the call has executed
These icons can be used in the root schematic
Class
Static method
Global variable
Global-get
Global-set
These icons can be used in a class schematic
Class method
Instance attribute
Get-method
Set-method
Construct
Destruct
The remaining icons can only be used in method schematics.
Control structures
Input bar -- first chip executed in the method
Output bar -- last chip executed in the method
Mux -- if the object input is true, then the true input goes to the output; else the false input goes to the output
Demux -- if the object input is true, then the input goes to the true output; else the input goes to the false output
Map -- apply the named predicate to each input terminal
Loop-begin -- input bar for loop
Loop-end -- output bar for loop
Exceptions
Switch -- define a set of exceptions and methods to call when those exceptions are raised
Stop -- raise an exception, process it, and then return to context where exception was defined
Yield -- raise an exception, process it, and then continue with method
Continue -- call next handler in stack for this exception
Assert -- generate a runtime error if any input is not true
Logic
And -- compute the logical AND of all inputs
Or -- compute the logical OR of all inputs
Not -- compute the logical NOT of the object input
Math
Add -- add all inputs and object input together
Subtract -- subtract the IO-bar input from the object input
Multiply -- multiply all inputs and object input together
Divide -- divide the object input by the IO-bar input
List
Construct list -- build a list out of all inputs
Head -- return the 'car' of the list
Tail -- return the 'cdr' of the list
Is-list? -- return true if and only if the object input is a list
The toolbar is split into three tool groups. The left tool group is for browser functionality:
Home -- edit root schematic
Up -- edit current version of parent item of current item
Back -- edit current version of previous item in history
Forward -- edit current version of next item in history
Reload -- load the current version of the item being edited
Previous Version -- load the previous version
Next Version -- load the next version
Save -- save schematic currently displayed as the current version
The middle tool group is for clipboard and editing operations:
Undo/Redo
Cut
Copy
Paste
Clear
Find -- not yet implemented
Zoom in
Zoom out
The right tool group is for system operations:
Interpret -- not yet implemented
Compile -- generate code for the program and write it to stdout
Help -- toggle button for state of tool tips
A recursive implementation of the factorial function is shown in Figure 8. The chip on the far left is the input bar; the chip on the far right is the output bar. The chip on the left of the output bar is a Mux. Its bottom input is a control input. Mux and Demux allow for conditional execution in Gnymph. They try to execute as little of the method as possible. The code to produce the control input for the Mux is first executed. If it is true, then the construction of the integer 1 will not be needed so it will not execute. If it is false, then the decrement, recursive call, and multiply that lead to the true input are not needed and will not execute.
Pseudocode in a traditional language
if X <> 0 then return X * factorial( X - 1 ) else return 1
Figure 9 gives an iterative version of the same problem. The loop executes once for each iteration of the input. The product is initialized to 1. Each iteration of the loop multiplies the product by the next integer value. The inputs to the Loop-end are copied back to the Loop-begin. The last value to appear on the lower output of the Loop-end is returned.
A simple version of bubble sort is shown in Figure 10. Here the size attribute is retrieved and compared against the constant 2. The result is used as a control input to a Mux. If there are two or less items in the input list, then the pair is ordered and returned. Otherwise, the tail of the input list is bubble sorted, and then the head is inserted into the list in order.