Postscript was sort of predecessor to PDF, but unlike PDF, Postscript just includes a full stack-based programming language.
It wasn't really meant for humans to write code in, just for various typesetting systems to generate, but that's not going to stop us - let's code some things!
To "run" the programs we'll just view them with OSX Preview.
Hello, World!
%!PS
/Monaco 32 selectfont
200 400 moveto
(Hello, World!) show
showpage
It's obvious what we're starting with.
Here's the result:
What's going on here:
-
%!PS
is the Postscript header - we have stack-based programming language, like Forth we've seen before
-
/Monaco 32 selectfont
pushes two values on stack, then selects Monaco 32pt font -
200 400 moveto
pushes two number on stack, then moves cursor there - in Postscript (0,0) is bottom left, not top left like with usual computer graphics -
(Hello, World!) show
pushes string on stack, then shows it -
showpage
shows the page we just drew - also notice the header saying
hello.pdf
nothello.ps
- OSX Preview converts Postscript files to PDF before displaying them
That wasn't too bad.
Loops
The first step towards real programming would be doing a loop:
%!PS
/Monaco 32 selectfont
0 1 10 {
% STACK CONTENTS:
% i
dup 30 mul % i 30i
600 exch sub % i 600-30i
300 % i 600-30i 300
exch % i 300 600-30i
moveto % i
20 string cvs % "i" (as string of max size 20 chars)
show
} for
showpage
Which looks like this:
I annotated in %
comments what's stack contents after each iteration. Let's go over it step by step:
-
0 1 10 { ... } for
is a lop from 0 to 10, step size 1 -
dup 30 mul
duplicates index and multiplies it by 30 -
600 exch sub
does600-30i
- we need to exchange top two values on the stack withexch
, otherwise we'd have30i-600
. As I mentioned, Postscript Y coordinates are upside down compared with the usual convention. -
300 exch
pushes 300, and then exchanges top two values on the stack, then wemoveto
that position -
20 string cvs
converts integer to string - or technically speaking allocates string of 20 empty characters, then outputsi
there -
show
puts that string on the page
Double numbers
For next step let's define a custom function to double numbers, and do a bit of formatting.
%!PS
/Helvetica 32 selectfont
/double {
dup add
} def
1 1 10 {
dup 30 mul
600 exch sub
100
exch
moveto
(double\() show
dup 20 string cvs show
(\) = ) show
double 20 string cvs show
} for
showpage
Which looks like this:
Step by step:
-
/double { ... } def
defines a functiondouble
that takes one number and doubles it - as it's a stack-based language there are no specific "arguments" and "return values", function just doubles whatever is on top of the stack -
(double\() show
shows some escaping of special characters like(
and)
in strings. - when we
show
something, cursor moves to the end of shown string, os we don't need to reposition each thing we print if it's on the same line - we call the function
double
the same way as we call any builtin functions
Fibonacci
Now we have almost everything we need to do Fibonacci numbers.
%!PS
/Helvetica 20 selectfont
/fib {
dup 2 le {
pop
1
} {
dup 1 sub fib
exch
2 sub fib
add
} ifelse
} def
1 1 30 {
dup 20 mul
700 exch sub
50
exch
moveto
(fib\() show
dup 20 string cvs show
(\) = ) show
fib 20 string cvs show
} for
showpage
Which looks like this:
Step by step:
- the loop is just as before
- fib function contains big
condition { then branch } { else branch } ifelse
statement - code blocks in{}
are passed toifelse
just like they are passed todef
and only executed when needed -
dup 2 le
istop-of-stack <= 2
- in then-branch
pop 1
removes top item from stack (we only need it in the else branch), and pushes1
instead as return value - in else-branch, we first
dup 1 sub fib
to callfib(n-1)
- then we
exch
to swap calculatedfib(n-1)
withn
- then we do
2 sub fib
to calculatefib(n-2)
- and finally we
add
those two values together
FizzBuzz
Let's do FizzBuzz, obviously. But we have a small problem - 1 to 100 FizzBuzz won't fit on one page. Well, how about we do two pages then?
%!PS
/Helvetica 14 selectfont
/fizzbuzz {
dup 15 mod 0 eq {
pop (FizzBuzz)
} {
dup 5 mod 0 eq {
pop (Buzz)
} {
dup 3 mod 0 eq {
pop (Fizz)
} {
20 string cvs
} ifelse
} ifelse
} ifelse
} def
/fizzbuzzpage {
/n exch def
1 1 50 {
dup 14 mul
780 exch sub
50
exch
moveto
n add fizzbuzz show
} for
showpage
} def
0 fizzbuzzpage
50 fizzbuzzpage
Which looks like this, with each page for 50 values:
Step by step:
-
fizzbuzz
taken a number and returnsFizz
,Buzz
,FizzBuzz
, or string representation of that number, according to the usual FizzBuzz rules -
fizzbuzzpage
takes an offset and prints a page full of FizzBuzz values, from N+1 to N+50 -
/n exch def
saves top of the stack ton
variable - Postscript can access values deep in the stack, but I think it's easier to just save it as a local variable. We need theexch
as expected order is/name value def
, and we already havevalue
on top before we even start. You can use this technique to save any number of function call arguments into local variables, starting from the last one. -
n add fizzbuzz show
calculatesfizzbuzz(n+i)
and shows it on the page
Should you use Postscript?
Definitely not for programming, and nowadays not even for typesetting as PDFs pretty much replaced Postscript completely, but it's a decent enough introduction to stack-based languages, probably more fun than Forth as you can do fancy graphics in Postscript.
Depending on your printer, you could even send your program directly to the printer, to run it onto the page, without ever running it on any regular computer.
Code
All code examples for the series will be in this repository.
Top comments (0)