100 Languages Speedrun: Episode 24: Postscript
Tags: postscript
Published: 2021-12-14 19:17:08

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!

```postscript
%!PS
/Monaco 32 selectfont
200 400 moveto
(Hello, World!) show
showpage
```

It's obvious what we're starting with.

Here's the result:

![Hello](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y0i1c5hngg8xkakpve3q.png)

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:

```postscript
%!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:

![Loop](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pz1nn33c7tyvyjekfg7u.png)

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.

```postscript
%!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:

![Double](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q44v3oqkjeihb0rm7c5h.png)

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.

```postscript
%!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:

![Fib](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q2b32xapgo9baqcr24n8.png)

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?

```postscript
%!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:

![Fizzbuzz](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4qesuwcyfjlebefq9lxv.png)

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](https://github.com/taw/100-languages-speedrun).

[Code for the Postscript episode is available here](https://github.com/taw/100-languages-speedrun/tree/master/episode-24-postscript).