DEV Community

Tomasz Wegrzanowski
Tomasz Wegrzanowski

Posted on

100 Languages Speedrun: Episode 24: Postscript

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
Enter fullscreen mode Exit fullscreen mode

It's obvious what we're starting with.

Here's the result:

Hello

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 not hello.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
Enter fullscreen mode Exit fullscreen mode

Which looks like this:

Loop

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 does 600-30i - we need to exchange top two values on the stack with exch, otherwise we'd have 30i-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 we moveto that position
  • 20 string cvs converts integer to string - or technically speaking allocates string of 20 empty characters, then outputs i 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
Enter fullscreen mode Exit fullscreen mode

Which looks like this:

Double

Step by step:

  • /double { ... } def defines a function double 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
Enter fullscreen mode Exit fullscreen mode

Which looks like this:

Fib

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 to ifelse just like they are passed to def and only executed when needed
  • dup 2 le is top-of-stack <= 2
  • in then-branch pop 1 removes top item from stack (we only need it in the else branch), and pushes 1 instead as return value
  • in else-branch, we first dup 1 sub fib to call fib(n-1)
  • then we exch to swap calculated fib(n-1) with n
  • then we do 2 sub fib to calculate fib(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
Enter fullscreen mode Exit fullscreen mode

Which looks like this, with each page for 50 values:

Fizzbuzz

Step by step:

  • fizzbuzz taken a number and returns Fizz, 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 to n 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 the exch as expected order is /name value def, and we already have value 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 calculates fizzbuzz(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.

Code for the Postscript episode is available here.

Top comments (0)