- 1. Procedures
- 1.1. Overview
- 1.2. Procedure Syntax
- 1.3. Procedure Structure
- 1.4. Procedure Execution
- 1.5. Defining Procedures
- 1.6. Local Variables
- 1.7. Closures and Scope
- 1.8. Control Flow Procedures
- 1.9. Procedure Composition
- 1.10. Procedure Optimization
- 1.11. Procedure Patterns
- 1.12. Recursive Procedures
- 1.13. Procedure Arrays
- 1.14. Procedure Debugging
- 1.15. Best Practices
- 1.16. Common Pitfalls
- 1.17. See Also
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 { }:
{ 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:
{ 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 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:
% 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:
% 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:
-
Each element is processed in sequence
-
Executable names are looked up and executed
-
Literal objects are pushed onto the stack
-
Nested procedures remain as objects (unless executed)
{ % 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
% Define named procedure
/Square { dup mul } def
% Use it
5 Square % Result: 25
1.5.2. Multi-Step Procedures
/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:
% 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
/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
% 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
/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:
% 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.8. Control Flow Procedures
Many control operators take procedures as arguments.
1.8.1. Conditional Execution
% 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
% 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.9. Procedure Composition
Procedures can be combined to create more complex behaviors.
1.9.1. Sequential Composition
/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:
% 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]
% 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:
% 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
% 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.11. Procedure Patterns
1.11.1. Stack Effect Documentation
/WellDocumented {
% Purpose: Calculate rectangle area
% in: width height
% out: area
mul
} def
1.11.2. Guard Clauses
/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
/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
/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 {
% 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
/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 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
% 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
/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.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.16. Common Pitfalls
1.16.1. Stack Imbalance
% 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