- 1. Procedures
- 1.1. Overview
- 1.2. Creating Procedures
- 1.3. Procedure Parameters
- 1.4. Return Values
- 1.5. Control Flow in Procedures
- 1.6. Procedure Patterns
- 1.7. Recursion
- 1.8. Higher-Order Procedures
- 1.9. Procedure Composition
- 1.10. Practical Procedure Examples
- 1.11. Best Practices
- 1.12. Common Pitfalls
- 1.13. Performance Considerations
- 1.14. See Also
1. Procedures
Procedures are executable arrays that form the foundation of PostScript programming. They enable code reuse, abstraction, and modular program design.
1.1. Overview
A procedure in PostScript is:
-
An executable array of PostScript objects
-
Invoked with the
execoperator (or automatically in certain contexts) -
A first-class object that can be stored, passed, and manipulated
-
The basis for functions, control structures, and program organization
Procedures are essential for:
-
Defining reusable operations
-
Creating custom operators
-
Implementing control flow
-
Building libraries and frameworks
1.2. Creating Procedures
1.2.1. Basic Procedure Definition
Using curly braces creates an executable array:
% Define and execute immediately
{ 5 10 add = } exec
% Prints: 15
% Store for later use
/addFive { 5 add } def
10 addFive =
% Prints: 15
1.2.2. The def Operator
Binds a name to a procedure in the current dictionary:
% Define procedure
/square {
dup mul
} def
% Use it
5 square = % Prints: 25
With parameters (documented via comments):
% Add two numbers
/add2 { % a b -> sum
add
} def
% Multiply three numbers
/mul3 { % a b c -> product
mul mul
} def
3 4 add2 = % 7
2 3 4 mul3 = % 24
1.2.3. The bind Operator
Replaces operator names with their values for efficiency:
% Without bind (slower)
/square1 { dup mul } def
% With bind (faster)
/square2 { dup mul } bind def
% bind replaces 'dup' and 'mul' with their operator objects
When to use bind:
-
Performance-critical procedures
-
Procedures called frequently
-
Library procedures
When not to use bind:
-
Procedures that need dynamic operator lookup
-
Development/debugging (harder to trace)
1.3. Procedure Parameters
1.3.1. Stack-Based Parameters
Procedures receive parameters via the operand stack:
% Simple parameter
/double { % n -> 2n
2 mul
} def
5 double = % 10
% Multiple parameters
/distance { % x1 y1 x2 y2 -> distance
% d = sqrt((x2-x1)² + (y2-y1)²)
3 index sub % dy = y2 - y1
3 1 roll % Reorder stack
sub % dx = x2 - x1
dup mul % dx²
exch dup mul % dy²
add sqrt % sqrt(dx² + dy²)
} def
0 0 3 4 distance = % 5.0
1.3.2. Named Parameters Using Dictionaries
% Create local scope for parameters
/rectangle { % x y width height -> -
4 dict begin
/height exch def
/width exch def
/y exch def
/x exch def
newpath
x y moveto
width 0 rlineto
0 height rlineto
width neg 0 rlineto
closepath
end
} def
% Usage
100 100 200 150 rectangle
stroke
1.3.3. Optional Parameters
% Procedure with default value
/drawCircle { % x y radius [color] -> -
% Check stack depth for optional color
count 3 gt {
% Color provided
4 dict begin
aload pop setrgbcolor
/r exch def
/y exch def
/x exch def
end
} {
% No color, use default
3 dict begin
/r exch def
/y exch def
/x exch def
0 setgray
end
} ifelse
newpath
x y r 0 360 arc
fill
} def
% Usage
100 100 50 drawCircle % Black circle
200 200 50 [1 0 0] drawCircle % Red circle
1.4. Return Values
1.4.1. Returning Single Values
% Return value left on stack
/square { % n -> n²
dup mul
} def
5 square = % 25
1.4.2. Returning Multiple Values
% Return width and height
/getSize { % -> width height
612 792
} def
getSize % Stack: 612 792
exch = % Prints: 792
= % Prints: 612
1.4.3. Returning Composite Objects
% Return array
/makePoint { % x y -> [x y]
2 array astore
} def
100 200 makePoint
% Returns [100 200]
% Return dictionary
/makePerson { % name age -> person
2 dict begin
/age exch def
/name exch def
currentdict
end
} def
(John) 30 makePerson
% Returns person dictionary
1.5. Control Flow in Procedures
1.5.1. Conditional Execution
% if/ifelse in procedures
/abs { % n -> |n|
dup 0 lt {
neg
} if
} def
-5 abs = % 5
/max { % a b -> max(a,b)
2 copy gt {
exch
} if
pop
} def
3 7 max = % 7
1.5.2. Loops
% for loop in procedure
/factorial { % n -> n!
1 dict begin
/n exch def
/result 1 def
1 1 n {
result mul /result exch def
} for
result
end
} def
5 factorial = % 120
% repeat loop
/stars { % count -> -
{
(*) print
} repeat
() print
} def
10 stars % Prints: **********
1.5.3. Early Return
% Use stop/stopped for early return
/findFirst { % array value -> index/-1
2 dict begin
/value exch def
/arr exch def
/result -1 def
{
0 1 arr length 1 sub {
/i exch def
arr i get value eq {
/result i def
stop % Early exit
} if
} for
} stopped pop
result
end
} def
[10 20 30 40] 30 findFirst = % 2
[10 20 30 40] 99 findFirst = % -1
1.6. Procedure Patterns
1.6.1. Pattern 1: Factory Pattern
% Create configured procedures
/makeAdder { % n -> procedure
1 dict begin
/addend exch def
{ addend add } bind
end
} def
% Create specialized procedures
/add5 5 makeAdder def
/add10 10 makeAdder def
3 add5 = % 8
3 add10 = % 13
1.6.2. Pattern 2: Closure Pattern
% Procedure with enclosed state
/makeCounter { % -> counter
5 dict begin
/count 0 def
% Return object with methods
<<
/increment {
/count count 1 add def
} bind
/decrement {
/count count 1 sub def
} bind
/getValue {
count
} bind
/reset { % newValue -> -
/count exch def
} bind
>>
end
} def
% Usage
makeCounter /counter exch def
counter /increment get exec
counter /increment get exec
counter /getValue get exec = % 2
1.6.3. Pattern 3: Callback Pattern
% Execute callback for each element
/forEach { % array callback -> -
exch {
1 index exec
} forall
pop
} def
% Usage
[1 2 3 4 5] {
dup mul =
} forEach
% Prints: 1, 4, 9, 16, 25
1.6.4. Pattern 4: Pipeline Pattern
% Chain procedures together
/compose { % proc1 proc2 -> composedProc
[
3 -1 roll exec
3 -1 roll exec
] cvx
} def
% Create pipeline
/double { 2 mul } def
/addTen { 10 add } def
/doubleAndAdd double addTen compose def
5 doubleAndAdd = % 20 (5*2 + 10)
1.6.5. Pattern 5: Memoization Pattern
% Cache procedure results
/memoize { % procedure -> memoizedProcedure
1 dict begin
/proc exch def
/cache 100 dict def
{
dup cache exch known {
cache exch get
} {
dup cache exch
2 index proc exec
dup 4 1 roll put
} ifelse
} bind
end
} def
% Memoized fibonacci
/fib {
dup 2 lt {
} {
dup 1 sub fib
exch 2 sub fib
add
} ifelse
} def
/fastFib fib memoize def
10 fastFib = % Much faster on repeated calls
1.7. Recursion
1.7.1. Simple Recursion
% Factorial using recursion
/factorial { % n -> n!
dup 1 le {
pop 1
} {
dup 1 sub factorial mul
} ifelse
} def
5 factorial = % 120
1.7.2. Tail Recursion
% Tail-recursive factorial
/factorialTail { % n accumulator -> n!
exch dup 1 le {
pop
} {
dup 1 sub
3 -1 roll mul
factorialTail
} ifelse
} def
/factorial { % n -> n!
1 factorialTail
} def
5 factorial = % 120
1.7.3. Tree Traversal
% Recursive tree traversal
/traverse { % node procedure -> -
exch dup null eq {
pop pop
} {
dup /value get 2 index exec
dup /left get 2 index traverse
exch /right get exch traverse
} ifelse
} def
% Create tree node
/node { % value left right -> node
<<
/right 3 -1 roll
/left 3 -1 roll
/value 3 -1 roll
>>
} def
% Build tree
5 null null node /leaf1 exch def
15 null null node /leaf2 exch def
10 leaf1 leaf2 node /root exch def
% Traverse and print
root { = } traverse
1.8. Higher-Order Procedures
1.8.1. Map Function
/map { % array procedure -> mappedArray
2 dict begin
/proc exch def
/arr exch def
[
arr {
proc exec
} forall
]
end
} def
% Usage
[1 2 3 4 5] { dup mul } map
% Returns [1 4 9 16 25]
1.8.2. Filter Function
/filter { % array predicate -> filteredArray
2 dict begin
/pred exch def
/arr exch def
[
arr {
dup pred exec {
} {
pop
} ifelse
} forall
]
end
} def
% Usage - filter even numbers
[1 2 3 4 5 6] { 2 mod 0 eq } filter
% Returns [2 4 6]
1.8.3. Reduce Function
/reduce { % array initial procedure -> result
3 dict begin
/proc exch def
/acc exch def
/arr exch def
arr {
acc exch proc exec
/acc exch def
} forall
acc
end
} def
% Usage - sum
[1 2 3 4 5] 0 { add } reduce
% Returns 15
% Product
[1 2 3 4 5] 1 { mul } reduce
% Returns 120
1.8.4. Sort Function
/sort { % array comparator -> sortedArray
2 dict begin
/comp exch def
/arr exch def
% Bubble sort
0 1 arr length 2 sub {
/i exch def
0 1 arr length 2 sub i sub {
/j exch def
arr j get arr j 1 add get comp exec {
% Swap
/temp arr j get def
arr j arr j 1 add get put
arr j 1 add temp put
} if
} for
} for
arr
end
} def
% Usage - ascending order
[5 2 8 1 9] { gt } sort
% Returns [1 2 5 8 9]
% Descending order
[5 2 8 1 9] { lt } sort
% Returns [9 8 5 2 1]
1.9. Procedure Composition
1.9.1. Function Composition
% Compose two functions
/compose { % f g -> (f ∘ g)
2 dict begin
/g exch def
/f exch def
{
g exec
f exec
} bind
end
} def
% Example
/inc { 1 add } def
/double { 2 mul } def
/incAndDouble inc double compose def
5 incAndDouble = % 12 ((5+1)*2)
1.9.2. Pipeline Builder
% Build processing pipeline
/pipeline { % [proc1 proc2 ... procN] -> composedProc
1 dict begin
/procs exch def
{
procs {
exec
} forall
} bind
end
} def
% Usage
[
{ 2 mul } % Double
{ 10 add } % Add 10
{ dup mul } % Square
] pipeline /process exch def
5 process = % 400 ((5*2+10)²)
1.10. Practical Procedure Examples
1.10.1. Example 1: Drawing Library
% Reusable drawing procedures
/drawCircle { % x y radius -> -
0 360 arc stroke
} def
/fillCircle { % x y radius -> -
0 360 arc fill
} def
/drawRect { % x y width height -> -
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
/fillRect { % x y width height -> -
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
fill
end
} def
% Usage
100 100 50 drawCircle
200 200 100 80 fillRect
1.10.2. Example 2: String Utilities
% String manipulation procedures
/uppercase { % string -> UPPERCASE
dup length string
0 1 2 index length 1 sub {
2 copy
3 index exch get
dup 97 ge exch 122 le and {
32 sub
} if
put
} for
exch pop
} def
/trim { % string -> trimmedString
% Remove leading/trailing spaces
dup
% Find first non-space
0 exch {
dup 32 ne { exit } if
pop
1 add
} forall
exch dup length 1 sub exch
% Find last non-space
dup length 1 sub -1 0 {
2 copy get 32 ne { exit } if
pop
} for
2 index sub 1 add
getinterval
} def
1.10.3. Example 3: Math Library
% Mathematical procedures
/min { % a b -> min(a,b)
2 copy lt { exch } if pop
} def
/max { % a b -> max(a,b)
2 copy gt { exch } if pop
} def
/clamp { % value min max -> clampedValue
2 index 2 index lt {
3 1 roll pop pop
} {
2 index 1 index gt {
3 -1 roll pop exch pop
} {
pop pop
} ifelse
} ifelse
} def
/lerp { % a b t -> interpolated
3 dict begin
/t exch def
/b exch def
/a exch def
a 1 t sub mul b t mul add
end
} def
% Usage
5 10 0.5 lerp = % 7.5
1.10.4. Example 4: Animation Helpers
% Easing functions
/easeInQuad { % t -> eased_t
dup mul
} def
/easeOutQuad { % t -> eased_t
dup neg 1 add dup mul neg 1 add
} def
/easeInOutQuad { % t -> eased_t
dup 2 mul
dup 1 lt {
dup mul 2 div
} {
1 sub dup neg 2 mul 1 add mul 2 div neg 1 add
} ifelse
} def
% Animate value
/animate { % start end duration easing -> -
4 dict begin
/easing exch def
/duration exch def
/endVal exch def
/startVal exch def
0 1 duration {
/frame exch def
/t frame duration div def
/easedT t easing exec def
/value startVal endVal startVal sub easedT mul add def
% Use value here
value =
} for
end
} def
% Animate from 0 to 100 over 10 frames with ease-in
0 100 10 { easeInQuad } animate
1.11. Best Practices
1.11.1. Document Procedure Signatures
% Good: documented signature
/calculateArea { % width height -> area
mul
} def
% Bad: no documentation
/calculateArea {
mul
} def
1.11.2. Use Descriptive Names
% Good: clear purpose
/convertInchesToPoints { 72 mul } def
/drawRoundedRectangle { ... } def
% Bad: unclear
/cip { 72 mul } def
/drr { ... } def
1.11.3. Keep Procedures Focused
% Good: single responsibility
/calculateDistance { % x1 y1 x2 y2 -> distance
% ... just calculates distance
} def
/drawLine { % x1 y1 x2 y2 -> -
% ... just draws line
} def
% Bad: multiple responsibilities
/calculateAndDrawLine {
% ... calculates AND draws
} def
1.12. Common Pitfalls
1.12.1. Forgetting to Balance Stack
% Wrong: leaves values on stack
/badProc {
10 20
add
30 % Oops! Extra value
} def
% Correct: clean stack
/goodProc {
10 20 add
} def
1.12.2. Incorrect Parameter Count
% Wrong: expects 2, gets 1
/add2 { add } def
5 add2 % ERROR: stackunderflow
% Correct: check/document parameters
/add2 { % a b -> sum
add
} def
5 10 add2 % OK
1.13. Performance Considerations
1.13.1. Inline Small Procedures
% Sometimes inlining is faster than procedure call
% Instead of:
/add5 { 5 add } def
x add5
% Consider:
x 5 add
1.14. See Also
-
Procedure Syntax - Syntax details
-
Arrays - Array operations
-
Composite Objects - Complex data structures
-
Control Flow Commands - Control structures
-
exec - Execute procedure
-
def - Define procedure
-
Debugging - Debugging procedures