1. Procedures

Procedures are executable arrays that contain sequences of PostScript commands. They are the fundamental mechanism for creating reusable code, defining functions, and implementing control structures.

1.1. Overview

A procedure is an array object with the executable attribute. Procedures allow you to group operations into logical units that can be executed as a single entity, passed as arguments, and stored in variables.

1.2. Procedure Syntax

1.2.1. Basic Syntax

Procedures are created using curly braces { }:

Procedure creation
{ 1 2 add }         % Simple procedure
{ }                 % Empty procedure
{                   % Multi-line procedure
    100 200 moveto
    300 200 lineto
    stroke
}

1.2.2. How { } Work

The curly braces are special tokens that execute immediately during scanning:

% When { is encountered:
% 1. A mark is pushed onto the operand stack
% 2. The scanner enters "procedure mode"
% 3. Tokens are accumulated but not executed

% When } is encountered:
% 1. All tokens since the mark are collected
% 2. An executable array is created
% 3. The array replaces the mark on the stack

1.3. Procedure Structure

1.3.1. Array Nature

Procedures are arrays with the executable attribute:

Procedures are arrays
{ 1 2 add } type        % Returns /arraytype (not /proceduretype)
{ 1 2 add } xcheck      % Returns true (executable)
{ 1 2 add } length      % Returns 3 (three elements)

% Access elements like an array
{ 1 2 add } 0 get       % Returns 1
{ 1 2 add } 2 get       % Returns /add (executable name)

1.3.2. Executable Attribute

What makes a procedure different from a regular array is the executable attribute:

Executable vs literal arrays
% Executable array (procedure)
{ 1 2 add }             % Will execute when encountered
dup exec                % Executes: result 3

% Literal array
[ 1 2 add ]             % Executes during construction
                        % Creates [3] not [1 2 add]

% Make array executable
[/1 /2 /add] cvx        % Converts to procedure
dup exec                % Can now execute

1.4. Procedure Execution

1.4.1. Deferred Execution

Procedures don’t execute when created - they execute when explicitly invoked:

Deferred execution
% Creation doesn't execute
{ 1 2 add }             % Stack: [{ 1 2 add }]
                        % add is NOT executed

% Execution happens later
dup exec                % Now executes: stack [{ 1 2 add } 3]

1.4.2. Execution Methods

Procedures can be executed in several ways:

Ways to execute procedures
% 1. exec operator
{ 1 2 add } exec        % Explicit execution

% 2. As name value (most common)
/MyProc { 1 2 add } def
MyProc                  % Looked up and executed

% 3. Control flow operators
true { (yes) } { (no) } ifelse  % Conditional execution

% 4. Iteration operators
5 { (Hello) = } repeat  % Repeated execution

1.4.3. Execution Semantics

When a procedure executes:

  1. Each element is processed in sequence

  2. Executable names are looked up and executed

  3. Literal objects are pushed onto the stack

  4. Nested procedures remain as objects (unless executed)

Execution example
{                       % Begin procedure
    /x 42 def          % 1. Define x
    x                   % 2. Look up x, push 42
    10                  % 3. Push 10
    add                 % 4. Execute add, result 52
} exec                  % Execute entire procedure
                        % Stack: [52]

1.5. Defining Procedures

1.5.1. Simple Definitions

Basic procedure definition
% Define named procedure
/Square { dup mul } def

% Use it
5 Square                % Result: 25

1.5.2. Multi-Step Procedures

Complex procedure
/DrawSquare {
    % Parameters: x y size
    % Draw a square at (x,y) with given size
    gsave
        newpath
        0 0 moveto              % Bottom-left
        dup 0 lineto            % Bottom-right
        dup dup lineto          % Top-right
        0 exch lineto           % Top-left
        closepath
        stroke
    grestore
} def

% Usage
100 100 50 DrawSquare

1.5.3. Procedures with Parameters

Procedures receive parameters from the stack:

Parameter handling
% Procedure expecting 2 parameters
/Add2Numbers {
    % in: num1 num2
    add
    % out: sum
} def

% Call with parameters
3 4 Add2Numbers         % Result: 7

% Document parameter expectations
/Rectangle {
    % in: x y width height
    % out: -
    % Draws a rectangle
    4 dict begin
        /h exch def
        /w exch def
        /y exch def
        /x exch def

        newpath
        x y moveto
        w 0 rlineto
        0 h rlineto
        w neg 0 rlineto
        closepath
        stroke
    end
} def

1.6. Local Variables

Procedures can use local variables through dictionaries.

1.6.1. Dictionary-Based Locals

Local variable pattern
/MyProc {
    % Create local dictionary
    10 dict begin
        % Define local variables
        /a exch def         % First parameter
        /b exch def         % Second parameter
        /result a b add def % Local computation

        % Return result
        result
    end
} def

% Usage
3 4 MyProc              % Result: 7

1.6.2. Avoiding Name Collisions

Safe local variables
% Global variable
/x 100 def

% Procedure with local x
/UseLocalX {
    5 dict begin
        /x 42 def       % Local x, doesn't affect global
        x               % Uses local: 42
    end
} def

UseLocalX               % Result: 42
x                       % Global unchanged: 100

1.6.3. Local Scope Pattern

Standard local variable pattern
/ProcName {
    % in: param1 param2 param3

    % Begin local scope
    n dict begin
        % Store parameters in local variables
        /p3 exch def
        /p2 exch def
        /p1 exch def

        % Local computation
        /localVar p1 p2 add def

        % Compute result
        localVar p3 mul

    end
    % out: result
} def

1.7. Closures and Scope

1.7.1. Lexical Scoping

PostScript uses dynamic scoping, but closures can capture state:

Closure pattern
% Create a counter procedure
/MakeCounter {
    % in: initial-value

    % Create dictionary to hold state
    1 dict dup begin
        /count exch def

        % Return procedure that references 'count'
        currentdict {
            /count count 1 add def
            count
        } bind
    end
} def

% Create two independent counters
0 MakeCounter /counter1 exch def
100 MakeCounter /counter2 exch def

counter1 exec           % Returns 1
counter1 exec           % Returns 2
counter2 exec           % Returns 101
counter1 exec           % Returns 3

1.7.2. Nested Procedures

Procedures can contain other procedures:

Nested procedures
/Outer {
    % Define inner procedure
    /Inner { 10 mul } def

    % Use inner procedure
    5 Inner             % Result: 50
} def

Outer                   % Defines and uses Inner

1.8. Control Flow Procedures

Many control operators take procedures as arguments.

1.8.1. Conditional Execution

if and ifelse
% if: bool proc → -
true { (It's true) print } if

% ifelse: bool proc1 proc2 → -
x 0 gt {
    (Positive) print
} {
    (Not positive) print
} ifelse

1.8.2. Loops

Iteration with procedures
% repeat: int proc → -
5 { (Hello) = } repeat

% for: init incr limit proc → -
1 1 10 {
    dup dup mul =       % Print squares
} for

% forall: array proc → -
[10 20 30] {
    2 div =             % Print half of each
} forall

% loop: proc → - (infinite)
/i 0 def
{
    i 10 ge { exit } if
    i =
    /i i 1 add def
} loop

1.8.3. Stopped Context

Error handling with procedures
% stopped: any → bool
{
    % Code that might error
    1 0 div             % Division by zero
} stopped {
    (Error occurred) =
} {
    (Success) =
} ifelse

1.9. Procedure Composition

Procedures can be combined to create more complex behaviors.

1.9.1. Sequential Composition

Chaining procedures
/Step1 { 10 add } def
/Step2 { 2 mul } def
/Step3 { 1 sub } def

/Composed {
    Step1
    Step2
    Step3
} def

5 Composed              % (5+10)*2-1 = 29

1.9.2. Higher-Order Procedures

Procedures that take or return procedures:

Procedure as argument
% Apply a procedure to each array element
/Map {
    % in: array proc
    % out: result-array

    1 index length array    % Create result array
    3 1 roll                % Arrange: array result proc

    0 1 4 index length 1 sub {
        % Stack: array result proc index
        3 index 1 index get  % Get element
        2 index exec         % Apply proc
        3 index 3 1 roll put % Store result
    } for

    exch pop exch pop       % Clean up
} def

% Usage
[1 2 3 4] { 2 mul } Map  % Result: [2 4 6 8]
Procedure factory
% Create a procedure that adds a specific value
/MakeAdder {
    % in: n
    % out: procedure

    1 dict begin
        /n exch def
        { n add }
    end
} def

% Create specific adders
5 MakeAdder /Add5 exch def
10 MakeAdder /Add10 exch def

3 Add5 exec             % Result: 8
3 Add10 exec            % Result: 13

1.10. Procedure Optimization

1.10.1. Operator Binding

The bind operator replaces operator names with operator objects:

Binding for performance
% Unbound procedure
/SlowProc {
    add mul sub div
} def

% Bound procedure
/FastProc {
    add mul sub div
} bind def

% FastProc is faster because operators
% are pre-resolved at definition time

1.10.2. When to Bind

Binding guidelines
% BIND for:
% - Performance-critical code
% - Procedures using many operators
% - Code that should be protected from redefinition

/CriticalProc {
    % ... lots of operators ...
} bind def

% DON'T BIND for:
% - Procedures that should allow operator redefinition
% - Procedures calling user-defined operations

/FlexibleProc {
    MyCustomOp      % Should allow redefinition
} def

1.10.3. Immediate Lookup (Level 2+)

Force immediate binding
% Level 2+ immediate name lookup
/MyProc {
    //add           % Look up add immediately
    //mul           % Look up mul immediately
    CustomOp        % Will be bound by bind
} bind def

% Equivalent to manually inserting operators

1.11. Procedure Patterns

1.11.1. Stack Effect Documentation

Documenting procedures
/WellDocumented {
    % Purpose: Calculate rectangle area
    % in: width height
    % out: area

    mul
} def

1.11.2. Guard Clauses

Input validation
/SafeDivide {
    % in: dividend divisor
    % out: quotient

    % Guard against division by zero
    dup 0 eq {
        pop pop
        (Division by zero) print
        0
    } {
        div
    } ifelse
} def

1.11.3. Template Method

Procedure template
/TemplateProc {
    % Setup
    gsave

    % Hook for customization
    BeforeHook

    % Core logic
    % ...

    % Another hook
    AfterHook

    % Cleanup
    grestore
} def

% Customize by defining hooks
/BeforeHook { 0.5 setgray } def
/AfterHook { } def

1.11.4. Resource Cleanup

Safe resource management
/WithFile {
    % in: filename proc

    exch (r) file       % Open file
    dup 3 -1 roll       % Duplicate file for cleanup

    % Execute procedure
    stopped {
        closefile
        (Error in file processing) print
    } {
        closefile
    } ifelse
} def

% Usage
(data.txt) {
    % Procedure using file
    10 string readline pop
} WithFile

1.12. Recursive Procedures

Procedures can call themselves recursively.

1.12.1. Direct Recursion

Factorial example
/Factorial {
    % in: n
    % out: n!

    dup 1 le {
        % Base case
        pop 1
    } {
        % Recursive case
        dup 1 sub Factorial mul
    } ifelse
} def

5 Factorial             % Result: 120

1.12.2. Mutual Recursion

Mutually recursive procedures
/IsEven {
    % in: n
    % out: bool

    dup 0 eq {
        pop true
    } {
        1 sub IsOdd
    } ifelse
} def

/IsOdd {
    % in: n
    % out: bool

    dup 0 eq {
        pop false
    } {
        1 sub IsEven
    } ifelse
} def

6 IsEven                % true
7 IsOdd                 % true

1.12.3. Tail Recursion

Tail-recursive pattern
% Tail-recursive sum
/Sum {
    % in: array accumulator
    % out: sum

    exch dup length 0 eq {
        pop             % Return accumulator
    } {
        dup 0 get       % Get first element
        3 -1 roll add   % Add to accumulator
        exch
        dup length 1 sub 1 exch getinterval
        exch Sum        % Tail call
    } ifelse
} def

[1 2 3 4 5] 0 Sum       % Result: 15

1.13. Procedure Arrays

Arrays of procedures enable dispatch tables and strategy patterns.

1.13.1. Dispatch Table

Procedure dispatch
% Operation dispatch table
/Operations [
    { add }             % Operation 0
    { sub }             % Operation 1
    { mul }             % Operation 2
    { div }             % Operation 3
] def

% Execute operation by index
/DoOp {
    % in: a b op-index
    % out: result

    Operations exch get exec
} def

10 5 0 DoOp             % Add: 15
10 5 1 DoOp             % Sub: 5
10 5 2 DoOp             % Mul: 50

1.13.2. Strategy Pattern

Pluggable strategies
/ProcessData {
    % in: data strategy-proc

    % Apply strategy to data
    exec
} def

% Different strategies
/Strategy1 { 2 mul } def
/Strategy2 { 10 add } def
/Strategy3 { dup mul } def

42 { Strategy1 } ProcessData  % Result: 84
42 { Strategy2 } ProcessData  % Result: 52

1.14. Procedure Debugging

1.14.1. Tracing Execution

Debug wrapper
/Trace {
    % in: proc name

    exch
    dup (Entering: ) print print (\n) print
    exec
    dup (Exiting: ) print print (\n) print
} def

% Usage
{ 1 2 add } (Add) Trace

1.14.2. Stack Inspection

Stack debugging
/ShowStack {
    (Stack: ) print
    count 0 eq {
        (empty) print
    } {
        count {
            dup =
        } repeat
    } ifelse
    (\n) print
} def

% Use in procedures
/DebugProc {
    ShowStack
    1 2 add
    ShowStack
} def

1.15. Best Practices

1.15.1. Procedure Design

  • Keep procedures focused on a single responsibility

  • Use meaningful names that describe what the procedure does

  • Document stack effects (inputs and outputs)

  • Validate inputs for robustness

  • Use local variables for complex procedures

1.15.2. Performance

  • Use bind for procedures with many operators

  • Avoid excessive recursion (PostScript has limited stack depth)

  • Minimize temporary object creation

  • Reuse procedures rather than duplicating code

1.15.3. Style

Good procedure style
/GoodProc {
    % Purpose: Clear description
    % in: param1 param2
    % out: result

    % Use local variables for clarity
    5 dict begin
        /p2 exch def
        /p1 exch def

        % Clear, logical steps
        /intermediate p1 p2 add def
        intermediate 2 mul
    end
} def

1.16. Common Pitfalls

1.16.1. Stack Imbalance

Unbalanced stack effects
% WRONG - leaves extra values
/BadProc {
    dup dup dup         % Too many duplicates
    add
    % Missing cleanup
} def

% RIGHT - balanced
/GoodProc {
    dup                 % One dup
    add
    % Stack balanced
} def

1.16.2. Scope Confusion

Variable scope issues
% WRONG - no local scope
/NoScope {
    /x 42 def          % Pollutes global namespace
    x
} def

% RIGHT - proper scope
/WithScope {
    1 dict begin
        /x 42 def      % Local variable
        x
    end
} def

1.16.3. Recursion Depth

Stack overflow from deep recursion
% WRONG - unbounded recursion
/Infinite {
    1 add
    Infinite           % Will overflow
} def

% RIGHT - base case
/Bounded {
    dup 1000 lt {
        1 add Bounded
    } if
} def

1.17. See Also


Back to top

Copyright © 2025 Ribose. PostScript is a trademark of Adobe. Distributed under the MIT License.