{ |one, step, back| }

The Shape Example in Postscript

Contributed by Scott Anderson

This one uses Aladdin's Ghostscript package: version 5.10.

Postscript is a stack-based language dedicated to the manipulation of documents. There is an operand stack, used for passing variables; a dictionary stack, used for holding namespaces (the system and user namespaces are placed on first); and finally the execution stack, which holds the current object execute call stack. Several features of the language, including the dictionaries and the mvx/exec operators, made the implementation of polymorphism relatively straightforward in postscript.

Unlike the sed, awk, and FoxPro examples, I took a short-cut and wrote the inheritance into the constructors. Hence, this method is not quite as generic from the aspect of class definitions. The polymorphism is implemented with vtables once again using the postscript cvx exec construct to dynamically execute the code. Postscript comes very close to function pointers with this. Note that I copy the function pointers into each instance; I could have instead used a reference back to the class definition and pulled the method vtable entry from there. This implementation is more CPU efficient and memory intensive. It also adds the possibility of dynamically attaching new methods to an instance variable. I could have also used mvx to store compiled blocks of code in the instances instead of references.

When passing arguments I went for readability rather than efficiency. I could have instead passed all of the arguments in reverse order to make the stack traversal easier.

The classes and instances are implemented as dictionaries. Postscript uses the notion of namespaces in a somewhat novel fashion: dictionaries are pushed onto the namespace (dictionary) stack, and name lookups (as in load operators) search through the dictionaries down the stack. Dictionaries can be created and pushed/popped at will, and the system namespace (holding all system-defined operands) resides on the bottom of the stack. The obvious inference is that system operands can be overloaded. This behaviour also suggests a possibly more elegant method for doing the polymorphism entailing the entering of class namespace in order of inheritance when manipulating instances.

Code for Postscript

File: shapes.ps

% Polymorphic shapes example in postscript.
% November 16, 1999 - Scott Anderson
% November 17, 1999 - Scott Anderson: changed to use fewer stack manipulations

% Make a new shape
% Args: origin-x origin-y 
/shape-make
{
        % Create a dictionary to hold the class info
        <<

                % Method dictionary for the class
                /methods
                <<
                        /move-to /shape-move-to
                        /rel-move-to /shape-rel-move-to
                >>

                % Slot dictionary for the class
                /slots
                >
        >> dup

        % Make it the active namespace
        begin
                5 1 roll

                % Load the slots with the arguments
                slots
                begin
                        store
                        store
                end
        end
} bind def

% Move a shape to a new origin
% Assumes the shape is the current namespace
% Args: new-x new-y
/shape-move-to
{
        % Load the slots with the arguments
        slots /y 3 -1 roll put
        slots /x 3 -1 roll put
} bind def

% Move a shape relative to current origin
% Assumes the shape is the current namespace
% Args: delta-x delta-y
/shape-rel-move-to
{
        % Get the existing y value
        slots
        begin
                y add /y exch store
                x add /x exch store
        end
} bind def



% Create a new rectangle
% Args: width height origin-x origin-y
/rectangle-make
{
        % Call the shape constructor and store the result
        shape-make dup 


        % Make the new shape the current namespace
        begin
                % Load the method and slot definitions
                methods
                begin
                        /draw /rectangle-draw store
                        /set-width /rectangle-set-width store
                        /set-height /rectangle-set-height store
                end

                5 1 roll
                slots
                begin
                        store
                        store
                end
        end

} bind def

% Set the width of a rectangle
% Args: new-w
/rectangle-set-width
{
        slots /width 3 -1 roll put
} bind def

% Set the height of a rectangle
% Args: new-h
/rectangle-set-height
{
        slots /height 3 -1 roll put
} bind def

% Draw a rectangle
% Args:
/rectangle-draw
{
        slots
        begin
                % Uuuuuugly. You'd think postscript would have better string handling.
                % The numeric->string translation is atrocious.
                (Drawing rectangle at: \() print
                x (      ) cvs print
                (, ) print
                y (      ) cvs print
                (\), width: ) print
                width (      ) cvs print
                (, height: ) print
                height (      ) cvs print
                (\n) print flush
        end
} bind def

% Args: radius origin-x origin-y
/circle-make
{
        % Call the shape constructor and store the result
        shape-make dup

        % Make the new shape the current namespace
        begin
                % Load the method and slot definitions
                methods
                begin
                        /draw /circle-draw store
                        /set-radius /circle-set-radius store
                end
                slots /radius 4 -1 roll put
        end
} bind def

% Set the radius of a circle
% Args: new-r
/circle-set-radius
{
        slots /radius 3 -1 roll put
} bind def

% Draw a circle
% Args:
/circle-draw
{
        slots
        begin
                (Drawing circle at: \() print
                x (     ) cvs print
                (, ) print
                y (     ) cvs print
                (\), radius: ) print
                radius (     ) cvs print
                (\n) print flush
        end
} bind def


% I didn't need this routine in the end, but I
% Left it in here because it is somewhat interesting.
% It will duplicate a class or instance completely.
/dupclass {
        begin
                <<

                        /methods methods > copy
                        /slots slots > copy
                >> 
        end
} bind def

% Call a method on an object dynamically
% This is the heart of polymorphism in postscript
% Args: method-name object
/call-method
{
        % Make it the current namespace
        begin
                % Load the method dictionary from the object
                methods

                % Make it the current namespace
                begin
                        % Load the method-name from the methods vtable 
                        load

                        % Convert it to executable postscript
                        cvx
                end

                % Execute the method call while still in the namespace
                % Of the object itself
                exec

        end
} bind def



%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%5
% Main program

% Create a rectangle
/rec1 /width 5 /height 6 /x 10 /y 20 rectangle-make store

% Create a circle
/circ1 8 /x 15 /y 25 circle-make store

% Call the draw and rel-move-to methods polymorphically on the shapes
/draw rec1 call-method
100 100 /rel-move-to rec1 call-method
/draw rec1 call-method

/draw circ1 call-method
100 100 /rel-move-to circ1 call-method
/draw circ1 call-method

% Create another rectangle
/rec2 /width 15 /height 15 /x 0 /y 0 rectangle-make store

% Call a rectangle-specific method
30 /set-width rec2 call-method

% Draw the rectangle
/draw rec2 call-method

quit

Output

[/usr/local/src/shapes/ps]# gs -sOutputFile=- -sDEVICE=nullpage -q -dBATCH shapes.ps
Drawing rectangle at: (10, 20), width: 5, height: 6
Drawing rectangle at: (110, 120), width: 5, height: 6
Drawing circle at: (15, 25), radius: 8
Drawing circle at: (115, 125), radius: 8
Drawing rectangle at: (0, 0), width: 30, height: 15