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:
-
%!PSis the Postscript header - we have stack-based programming language, like Forth we've seen before
-
/Monaco 32 selectfontpushes two values on stack, then selects Monaco 32pt font -
200 400 movetopushes 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!) showpushes string on stack, then shows it -
showpageshows the page we just drew - also notice the header saying
hello.pdfnothello.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 { ... } foris a lop from 0 to 10, step size 1 -
dup 30 mulduplicates index and multiplies it by 30 -
600 exch subdoes600-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 exchpushes 300, and then exchanges top two values on the stack, then wemovetothat position -
20 string cvsconverts integer to string - or technically speaking allocates string of 20 empty characters, then outputsithere -
showputs 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 { ... } defdefines a functiondoublethat 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\() showshows some escaping of special characters like(and)in strings. - when we
showsomething, 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
doublethe 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 } ifelsestatement - code blocks in{}are passed toifelsejust like they are passed todefand only executed when needed -
dup 2 leistop-of-stack <= 2 - in then-branch
pop 1removes top item from stack (we only need it in the else branch), and pushes1instead as return value - in else-branch, we first
dup 1 sub fibto callfib(n-1) - then we
exchto swap calculatedfib(n-1)withn - then we do
2 sub fibto calculatefib(n-2) - and finally we
addthose 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:
-
fizzbuzztaken a number and returnsFizz,Buzz,FizzBuzz, or string representation of that number, according to the usual FizzBuzz rules -
fizzbuzzpagetakes an offset and prints a page full of FizzBuzz values, from N+1 to N+50 -
/n exch defsaves top of the stack tonvariable - Postscript can access values deep in the stack, but I think it's easier to just save it as a local variable. We need theexchas expected order is/name value def, and we already havevalueon 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 showcalculatesfizzbuzz(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)