Take the 2-minute tour ×
Programming Puzzles & Code Golf Stack Exchange is a question and answer site for programming puzzle enthusiasts and code golfers. It's 100% free, no registration required.

As one of the less popular languages, it's difficult to find literature on the avant garde of postscript hackery. So what discoveries have the golfers here made to exploit the stack model (or other features) to overcome Postscript's inherent verbosity?

Please, one tip per answer.

share|improve this question
    
Made CW in keeping with our tips policy. –  dmckee Oct 27 '12 at 0:00
    
Understood. willco. –  luser droog Oct 27 '12 at 0:09
    
Found some external pages: sites.google.com/site/codegolfingtips/Postscript –  luser droog Nov 20 '12 at 6:14

8 Answers 8

When generating graphical output and console output does not matter, use = instead of pop.

share|improve this answer

Replace hexstrings with ASCII85

Probably old news, but I just learned it. :)

You can do it using the postscript interpreter interactively with an encoding filter and cut-and-paste. But I'm going to show how to use dc to do it "by hand".

So, here's a hex string. We split it into 4-byte chunks.

95 20 6e d8   d0 59 49 35   50 74 ba c5   08 2d

Firing up dc, we input these as 32-bit (unsigned) big-endian-byte-order numbers. Then mod-off base-85 digits (there should be 5 until you get to 0).

0> dc
16i
95206ED8
Ai
d85%n85/
82
d85%n85/
83
d85%n85/
82
d85%n85/
78
d85%n85/
47
d85%n85/
0                    

Padding the last chunk with 00 00, yields (decimal), omitting the same number of bytes that we padded.

47 78 82 83 82   66 81 72 79 83   25 72 82 25 69  2 53 30 [2 53]

Add 33 to shift into the printable range of ASCII and poof! ASCII85.

80 111 115 116 115 99 114 105 112 116 58 105 115 58 102 35 86 63
which decodes to: Postscript:is:f#V? %%%Oops! should say 'fun'! I screwed up somewhere. :)

Wrap it in <~ ... ~>, and Level-2 Postscript can access 8-bit data, cheaper than hex.

share|improve this answer

Here's a quickie: wrap multiple definitions in [...>>begin to eliminate the keyword def (nb. [ is the same as <<).

 def def
[>>begin

So remember: more than three two ... flock together! ;)

share|improve this answer
    
Shouldn't the rule be "more than two"? Compare /a 1 def/b 2 def/c 3 def with <</a 1/b 2/c 3>>begin. We need more spaces for def. –  Thomas W. Mar 12 at 8:18
    
Wow. I hadn't though of that. Yes, the calculation needs improvement. –  luser droog Mar 12 at 8:21
    
Actually, this should be [/a 1/b 2/c 3>>begin –  Thomas W. Mar 12 at 8:34
    
face-palm. . . . –  luser droog Mar 12 at 8:42
    
I've got some new ideas described in the chatroom –  luser droog Mar 12 at 8:49

While most postscript operators are syntactically identifiers (and therefore must be space- (or otherwise-) delimited), the names [, ], <<, and >> are self-delimiting and scanner will detect them without intervening space. For the same reason, you cannot refer to these names with the usual /literal syntax (eg. /[ is two tokens: an empty literal name equivalent to ()cvn cvlit, and the executable name [ equivalent to ([)cvn cvx exec).

In order to redefine these names, which cannot be mentioned by name, we can use strings which are implicitly converted to names when used as keys in a dictionary (convenient!).

This example illustrates abusing these operators to perform arithmetic.

%!
([)0 def
(])1 def
(<<){add}def
(>>){mul}def
 ]]<<]]]<<<<>> =
%1 1 add 1 1 1 add add mul = %prints 6

Also << and [ (and mark) all mean the same thing.

share|improve this answer
2  
Also, / ends the previous token so you don't need a space before it. –  Geoff Reedy Oct 27 '12 at 2:32

Factor-out repeated uses of long operator names

If you're already using a <<>>begin dictionary, there is a constant overhead of /?{} 4 characters per redefinition. So an operator of length n repeated N times will yield a character-count change of
(4 + n) - (N * (n - 1)).

Setting this formula equal to 0 gives the equation of the break-even point. From this we can solve for each variable in terms of the other, yielding
n = - (N - 4) / (1 - N) and
N = (4 + n) / (n - 1) .

No we can answer questions like, "For how many uses of 'print' is it worthwhile to abbreviate?" n = 5, so N = 9/4. Take the ceiling, since you can't effectively call print 1/4 times. So, 3. 3 uses. And indeed,

print print print
/P{print}p p p

(assuming you've already paid the overhead of <<>>begin to activate the definition, of course).

Of course, binary tokens makes this kind of moot, giving you the first 255 names from the system name table as 2-bytes: 0x92, 0x??. And binary tokens are also self-delimiting, requiring no whitespace before or after, since the high-bit of the first byte is outside of the ascii range.

share|improve this answer

Binary Tokens

For the ultimate zip-up of a PostScript program that final frontier is binary tokens which allows you to remove long operator names completely, at the cost of no longer having an ASCII-clean program.

So starting with a compacted block of postscript code

[/T[70{R 0 rlineto}48{}49{}43{A rotate}45{A neg rotate}91{currentdict
end[/.[currentpoint matrix currentmatrix]cvx>>begin begin}93{. setmatrix
moveto currentdict end end begin}>>/S{dup B eq{T begin exch{load exec}forall
end}{exch{load exch 1 add S}forall}ifelse 1 sub }>>begin moveto 0 S stroke

We look up all the names in the back of the PLRM (Appendix F, pp. 795-797)

appearance
in
vim    dec  meaning

<92>   146  'executable system name' binary token prefix
^A     1    add
^M     13   begin
^^     30   currentdict
'      39   currentmatrix
(      40   currentpoint
2      50   cvx
8      56   dup
9      57   end
=      61   eq  !)
>      62   exch
?      63   exec
I      73   forall
U      85   ifelse
d      100  load
h      104  matrix
k      107  moveto
n      110  neg
<85>   133  rlineto
<88>   136  rotate
§      167 stroke
©      169 sub

And then type them in prefixed by a 146 (decimal) byte. vim help for entering arbitrary bytes

Then in vim, the condensed file can be typed directly, so:

[/T[70{R 0^V146^V133}48{}49{}43{A^V146^V136} 45{A^V146^V110^V146^V136} 91{^V146^V30^V146^V57 [/.[^V146^V40^V146^V104 ^V146^V39]^V146^V50>> ^V146^V13^V146^V13}93{. ^V146^V156 ^V146^V107^V146^V30 ^V146^V57^V146^V57 ^V146^V13}>>/S{^V146^V56 B^V146^V61{T^V146^V13 ^V146^V62{^V146^V100 ^V146^V63}^V146^V73 ^V146^V57}{^V146^V62{ ^V146^V100^V146^V62

... you have to enter a space here to terminate the ^V-62 and start the 1, but you can back up and delete it later ...

1^V146^V1S}^V146^V73} ^V146^V85

... have to enter a space here to terminate the ^V-85 and start the 1, but you can back up and delete it later ...

1^V146^V169}>>^V146^V13 ^V146^V107

... 3rd digit of 3-digit code terminates the byte-entry so the following 0 here is normal, conveniently ...

0 S^V146^V167

Which will look like this on screen (in vim):

[/T[70{R 0<92><85>}48{}49{}43{A<92><88>}45{A<92>n<92><88>}
91{<92>^^<92>9[/.[<92>(<92>h<92>']<92>2>>
<92>^M<92>^M}93{.<92><9c><92>k<92>^^<92>9<92>9<92>^M}
>>/S{<92>8B<92>={T<92>^M<92>>{<92>d<92>?}<92>I<92>9}{<92>>
{<92>d<92>>1<92>^AS}<92>I}<92>U1<92>©}>><92>^M
<92>k0 S<92>§

This one can often be omitted entirely if the aim is just to show a picture. Ghostscript paints most things to the screen without needing showpage.

¡    161   showpage

[This actually isn't working. Ghostscript's giving me undefined and syntaxerror for these tokens. Maybe there's some mode I need to enable.]

share|improve this answer
    
Maybe it's something about this program. The online compactor doesn't like it, either. –  luser droog Jul 20 '13 at 6:46

Change negative rolls to positive

Negative rolls can always be changed to positive rolls.

3 -1 roll
3 2 roll

5 -2 roll
5 3 roll
share|improve this answer
    
Which is more efficient 3 -1 roll or 3 2 roll? In my mental model, the former should be more efficient because it just takes one move. Is my mental model correct? –  Please don't touch Feb 16 at 16:09
    
Honestly, I'm not sure. Here's my implementation, where all negative rolls are converted to positive as the first step. I think it would still require at least 2 moves (move 3rd value up, move 3 values down) with a flat-array implementation of the stack. But my stack is segmented so access goes through function calls to manage the segments; so there are certainly more efficient ways to implement than I have done. ... One thing I see now: I should check for stackunderflow outside of the loops. –  luser droog Feb 17 at 5:16

Embedded Decoder

A Postscript program has a unique(?) ability to read it's own program text as data. This is normally used by the image operator which receives a data-acquisition-procedure as input, and this procedure often uses currentfile followed by readline, readstring, or readhexstring. But seen another way, image is just another looping operator, so any loop can read-ahead. An example is the line-printer emulator from the Green Book.

Using the token operator invokes the scanner on a file or string, pulling off a number or space- (or otherwise-: see other answer) -delimited name.

A simple PS interpreter in PS:

{currentfile token not {exit} if dup type /arraytype ne {exec} if }loop

Binary Operator String Decoder

Since I can't seem to get raw binary tokens to work for me (see other answer), I've made use of the "embedded decoding" idea to exploit the binary token mechanism to pack code into 8-bit strings, and then manipulate and parse the commands from the string on the fly.

/.{
    <920>  % two-byte binary-encoded name template with 0x92 prefix
    dup 1 4 3 roll put  % insert number into string
    cvx exec  % and execute it
}def
/${
    //.   %the /. procedure body defined above
    73 .  %"forall" (by code number)
}def

The . procedure takes a number from the stack and inserts it as the second byte in a two-byte string, the first byte being the prefix-byte for a binary token, specifying an executable system name. We save a byte in the hexstring by using a rule of the scanner that an odd number of nibbles in the hexstring is padded with an extra 0 nibble, so 3 hex nibbles produces a 2-byte string. The string is then marked executable and called with exec which invokes the scanner, produces the desired executable system name, and then loads the name and executes the operator. The $ does this on each byte of a string on the stack, using the . procedure twice, once as the loop body, and then to execute the looping operator forall by number.

More compactly, these procedures look like this:

 /.{<920>dup 1 4 3 roll put cvx exec}def/${//. 73 .}def
%123457890123456789012345678901234567890123456789012345
%        1         2         3         4         5

So, 55 chars buys binary token strings.

Further illustrated in my crossword puzzle answer.

share|improve this answer
1  
Actually, if the postscript is in encoded form, then this requires you be extra careful about comparing stuff. Specifically, if you are looking at operators, you must parse it as a token and then compare the token's name to the target value. –  AJMansfield Jul 12 '13 at 15:48

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.