PostScript 基礎文法最速マスター
基礎文法最速マスターがはやっているみたいなので便乗します。
ほんとは Lua について書くつもり……というか書いていたんですけど、完成するより前に 良いもの。悪いもの。: Lua基礎文法最速マスター があがってました。
最近、基礎文法最速マスターというプログラミング言語の解説が流行ってるようなので、便乗してみた。個人的にはC++やPythonの方が慣れ親しんでいるのだが、自分でも勉強できるように普段使っていない言語を書いてみることにした。
良いもの。悪いもの。: Lua基礎文法最速マスター
うんうん。わたしも勉強のために普段使ってない言語にするか。たまたまさいきん Ghostscript づいていたので PostScript にしようと思いました。
なにぶん初学者なのでいろいろ間違いがあればご指摘ください。とゆーか、詳しい人がよりよい最速マスターや補遺を書いてくださるとうれしいです。
下記のサイトを倣って書いてみました。
- Perl基礎文法最速マスター - Perlゼミ(サンプルコードPerl入門)
- 2010-01-26
- Emacs Lisp基礎文法最速マスター - http://rubikitch.com/に移転しました
PostScript 自体については後述する文献を参考にしました。
1. 基礎
対話環境
Ghostscript の場合、そのまま実行すると対話環境として働きます。
$ gs GPL Ghostscript 8.70 (2009-07-31) Copyright (C) 2009 Artifex Software, Inc. All rights reserved. This software comes with NO WARRANTY: see the file PUBLIC for details. GS> (Hello, World!) = Hello, World! GS> 1 2 add GS<1> = 3 GS>
GS>
という部分がプロンプトです。GS<1>
のようになっている場合、現在のスタックにたまっている数を示します。
真っ白なウィンドウが表示されたかと思いますが、これは PostScript がページ記述言語であるためです。記述されたページコンテンツがここに表示されるのです。ですが、とりあえずこれがうざいのなら gs -dNODISPLAY
のように起動すれば、このページ表示用ウィンドウの生成を抑制できます。
quit
を評価するか、CTRL + D を押下すると終了します。
スタック指向言語とは
(だいじなことだけどあとでかく)
スタックの表示(print 文に相当)
オペレータ ==
を評価すると、スタックトップを pop してきて生で表示します。末尾に改行が自動付与されます。
GS> (Hello, World!) == (Hello, World!)
オペレータ =
を評価した場合、表示内容として妥当なものを表示します。他の LL 言語でいう to_string()
的なものを適用したうえで表示、みたいなイメージです。
GS> (Hello, World!) = Hello, World!
オペレータ print
を評価した場合は、=
と同様ですが、末尾に改行が自動付与されません。
GS> (Hello, World!) print Hello, World!GS>
つまり、本気で表示するのなら print
を、デバッグなどなら ==
や =
を使うことになります。ですが、これらはスタックトップの値の表示しかできません。スタック全体を見てみたい場合は、pstack
や stack
というオペレータを使います。
GS> 1 2 (Fizz) 4 (Buzz) GS<5> pstack % 「==」を使ってスタックを表示 (Buzz) 4 (Fizz) 2 1 GS<5> stack % 「=」を使ってスタックを表示 Buzz 4 Fizz 2 1 GS<5>
実行結果をみてわかるとおり、pstack
や stack
は pop 作業をともないません。
変数の宣言
PostScript には変数の宣言はありません。変数に代入する型も制限されません。
というか、そもそも変数という呼び方をしません。ある「名前」(のトークン)に「オブジェクト」を「束縛(バインド)」する、といいます。束縛するにはオペレータ def
を使います((なお、def
オペレータの引数は lisp と逆順なので、引数を変数に設定する際などにちょっとめんどくさくなります(後述)。))。
/a 1 def a == % => 1 /a (foo) def a == % => (foo)
上記の例では、まず a
という名前に 1 という値を束縛しています。冒頭で /a
のように「/
」がプレフィックスされているのは、「名前自身」をあらわすためです。単純に a
と書くと、a
というトークン自身が評価されてしまうので、def
するときには「名前自身」を即値としてスタックに積んでおく必要があるわけです。
スクリプトの実行
スクリプトファイルは、慣例的に
%!PS-Adobe-3.0
という行で開始することになっています。
たとえば、
%!PS-Adobe-3.0 /var 123 456 add def var ==
のようなファイルを test.ps
という名前で保存します。
これを実行するには、
$ gs test.ps GPL Ghostscript 8.70 (2009-07-31) Copyright (C) 2009 Artifex Software, Inc. All rights reserved. This software comes with NO WARRANTY: see the file PUBLIC for details. 579 GS>
のようにします。
実行後に GS>
というプロンプトがでていますね。最後に終了するスクリプトを書くなら、最後に quit
というオペレータを評価すればいいです。
ですが、今回の例で quit
を記述しなかったのには理由があります。スクリプトは PostScript インタプリタを起動した上で run
オペレータを使って読み込み・実行することができます。
$ gs GPL Ghostscript 8.70 (2009-07-31) Copyright (C) 2009 Artifex Software, Inc. All rights reserved. This software comes with NO WARRANTY: see the file PUBLIC for details. GS> (test.ps) run 579 GS> var == 579 GS>
つまり、ライブラリとして使えるんですね。
2. 数値
整数や浮動小数点数が使えます。
1 == % => 1 整数 16#beaf == % => 48815 16進数 (2進数でも、8進数でも、3進数でも可) 3.14 == % => 3.14 小数 .3 == % => 0.3 314e-2 == % => 3.14 指数表記
各種演算
1 2 add == % => 3 3 4 sub == % => -1 5 6 mul == % => 30 5 2 div == % => 2.5 5 2 idiv == % => 2 (整除算) 5 2 mod == % => 1 (剰余) 2 3 exp == % => 8.0 (累乗; 入出力は浮動小数点) 4 sqrt == % => 2.0 (平方根) 90 sin == % => 1.0 (三角関数; 単位は度) % など、いろいろおなじみなオペレータはデフォルトで用意されています
インクリメント/デクリメント/加減
ありません。工夫して書く必要があります(たぶん)。
/foo 1 def /foo foo 1 add def foo == % => 2
ちと汎用的ではないかもしれませんね。後述する内容を使うとこんな感じに書けるかな。
/add-to { 1 index load add def } def /foo 1 add-to foo == % => 3
3. 文字列
文字列は丸括弧で囲みます。一般的なエスケープシーケンスも利用できます。行末に \
を置くことで、長い文字列を複数行にわたって記述することもできます。
(abc) = % => abc (def\nghi) = % => def [改行] ghi (ijk\(lmn) = % => ijk(lmn (abc\ def\ ghi) = % => abcdefghi
ただしこのような文字列リテラルは英数字しか表現できません。日本語のようにハイビットをもつ文字表記集合の場合、< ... >
の間に文字コードを記載する方法で表現する必要があります。
<e697a5e69cace8aa9e> = % => 日本語 % ホワイトスペースで区切ることもできます <e6 97 a5 e6 9c ac e8 aa 9e> = % => 日本語
文字列関連のオペレータです。
% 結合系 % ……要調査…… % 分割 (トークナイズ) (foo bar baz) token pstack % => true % 成否 % foo % トークン % (bar baz) % 残り文字列 % 長さ (バイト数) (Supercalifragilisticexpialidocious) length == % => 34 % 切り出し/置換 (0123456789) 3 get == % => 51 (0123456789) 3 4 getinterval == % => (3456) /s (0123456789) def s 3 88 put s == % => (012X456789) /s (0123456789) def s 3 (XYZ) putinterval s == % => (012XYZ6789) % 検索 (0123456789) (345) search pstack % 部分文字列検索 % => true % 成否 % (012) % pre % (345) % hit % (6789) % post (0123456789) (345) anchorsearch pstack % 先頭にマッチするか % => false % 成否 % (0123456789) % 元文字列 (失敗したので元文字列だけ返る)
4. スタック操作
スタック指向だけあってスタック操作のオペレータは充実しています。
clear % スタックを空にする 1 2 3 4 % トークンを書くと push されるので push というオペレータはない pstack % => 4 % 3 % 2 % 1 pop % pop pstack % => 3 % 2 % 1 exch % スタック上位2要素を交換する pstack % => 2 % 3 % 1 dup % スタックトップの複製 pstack % => 2 % 2 % 3 % 1 2 index % スタックトップから2+1番めの要素をコピー % => 3 % 2 % 2 % 3 % 1 2 copy % スタックトップから指定数分の要素をコピー % => 3 % 2 % 3 % 2 % 2 % 3 % 1 count == % 現在のスタックの深さを push % => 7
exch
というオペレータはスタックトップ2要素の交換なのですが、現在スタックトップにある要素を束縛するときなどよく使います。
3 4 add pstack % => 7 /var % /var という名前に束縛したいけど pstack % => /var % このまま def できない % 7 exch pstack % => 7 % def するときのスタックの並び順になった % /var def var == % => 7
roll
というオペレーターはスタックをローテートします。
clear 1 2 3 4 5 4 1 roll % 上位4要素を上に1つローテート pstack % => 4 % 3 % 2 % 5 % 1 4 -2 roll % 上位4要素を下に2つローテート pstack % => 2 % 5 % 4 % 3 % 1
マーカー関連のオペレータです。
clear 1 2 mark 3 4 5 % マーカーを push pstack % => 5 % 4 % 3 % -mark- % 2 % 1 counttomark == % スタックトップからマーカーのある位置までの要素をカウント % => 3 cleartomark % スタックトップからマーカーまでを消去 % => 2 % 1
5. 配列
[
と ]
というオペレータ((実際は [
というオペレータは mark
というオペレータと同義で、スタックにマーク(というもの)を push しているだけです。Unix の test
コマンドみたいですね。))で配列を生成できます。
[ 1 (x) 3 ] == % => [1 (x) 3]
array
というオペレータを用いて空の配列を生成することもできます。
[ ] % 空の配列 0 array % 初期サイズ0の配列を生成 3 array % 初期サイズ3の配列を生成(配列自体は空; 必要に応じて拡張はされる)
配列まわりのオペレータです。
/a [ 0 1 2 ] def a length == % => 3 (要素の個数) /b a def % エイリアス (束縛しているだけなのであたりまえ) b 1 get == % => 1 b 1 (xyz) put b == % => [0 (xyz) 2] a == % => [0 (xyz) 2] (エイリアスだから) % 部分配列の取得 /a [ 0 1 2 3 4 5 6 7 8 9 ] def a 3 4 getinterval == % => [3 4 5 6] % 部分配列の置換 a 3 [ (X) (Y) (Z) ] putinterval a == % => [ 0 1 2 (X) (Y) (Z) 6 7 8 9 ] % スタックへの変換 [ 0 1 2 ] aload pstack % => [0 1 2] % 2 % 1 % 0 % スタックからの変換 (X) (Y) (Z) % スタックにつんでおく 3 array % サイズ3の空配列の生成 astore == % => [(X) (Y) (Z)] % 配列の push, pop % ……要調査……
6. 辞書
<<
と >>
というオペレータで辞書リテラルを生成することができます。
<< /foo 1 /var (xyz) >> == % => -dict- % 辞書は == では dump されず dict オブジェクトであることが示されるのみ
dict
というオペレータを用いて空の辞書を生成することもできます。
<< >> % 空の辞書 0 dict % 初期サイズ0の辞書を生成 3 dict % 初期サイズ3の辞書を生成(辞書自体は空; 必要に応じて拡張はされる)
辞書を操作するオペレータたちです。
/d << /foo 1 /bar (xyz) >> def % 要素の取得 d /foo get == % => 1 d /bar get == % => (xyz) d /baz get == % => ERROR: /undefined in --execute-- (容赦ねー) d /foo known == % => true d /bar undef % 削除 d /bar known == % => false % 要素の代入 d /foo (hoge) put d /foo get == % => (hoge) d /baz (fuga) put d /baz get == % => (fuga) % キーの列挙などの組み込みオペレータはないので工夫する必要があります [ d { pop } forall ] == % => [/baz /foo]
余談: 辞書のキー
実は辞書のキーは null 以外の任意のオブジェクトを指定できます。
<< [ (foo) (bar) ] 123 >> { pop == } forall % キーを列挙 % => [(foo) (bar)]
んで、名前ベースで辞書を検索する際、文字列がキーの場合も同様に検索されるようです。
/foo 123 def foo == % => 123 (foo) 345 def foo == % => 345 上書きされた
慣例的に束縛をおこなう際は /foo
表記を使うのですが、Ruby でいうシンボルみたいなものだと考えればいいのかな。ちょっとよくわかりません。名前リテラルはたぶん immutable なのかな。
カレント辞書と辞書スタック
実はこれまで def
で名前に束縛してきましたが、これは現在の操作対象の辞書(カレント辞書)にたいして束縛をおこなっていたのでした。begin
というオペレータをもちいるとカレント辞書を変更することができます。いうなれば、JavaScript の with
文のようなものです。
begin
があるのに対応して end
というオペレータもあります。これはさきほどまでのカレント辞書に戻す、というものです。つまり、辞書についても辞書用のスタックがあるのです。JavaScript でたとえるなら with
文をネストしたような感じと思えばいいでしょう。
辞書スタックをさかのぼって値を取得・設定するオペレータもあります。さきほどの get
と put
に対応するものが、それぞれ load
と store
になります。
/foo (foo) def % 辞書[0] に設定 /hoge (hoge) def foo == % => (foo) hoge == % => (hoge) << /foo (bar) >> begin % 辞書[1] foo == % => (bar) hoge == % => (hoge); さかのぼって検索・評価される /foo (baz) def % 辞書[1] に /foo の値を設定 (def だからカレント辞書に) foo == % => (baz) 0 dict begin % 辞書[2] /foo load == % => (baz); load もさかのぼって検索する %currentdict /foo get == % => Error: /undefined in --execute-- % カレント辞書に /foo キーの項目があるわけではない /foo (ban) def % 辞書[2] に登録 /hoge (fuga) store % さかのぼって登録 (この場合、/hoge キーのある辞書[0]) end % 辞書[1] に戻る foo == % => (baz) ; (ban) ではないことに注意 hoge == % => (fuga) end % 辞書[0] に戻る foo == % => (foo) hoge == % => (fuga)
def
はあくまでカレント辞書において束縛するのに対し、store
は辞書スタックをさかのぼって一番最初に名前が定義されていた辞書において束縛するところが違います*1。
これらを応用すると後述するローカル変数のようなものを実現できます。
7. 手続き (関数)
{
と }
で囲むと、その間のトークンが「実行可能配列」としてひとまとまりの実行可能オブジェクトになります。簡単にいえば無名関数のようなものです。
{ (1 + 2 is ) print 1 2 add = } pstack % => {(1 + 2 is ) print 1 2 add =} % exec オペレータを使うとスタックトップの実行可能オブジェクトを実行します。 exec % => 1 + 2 is 3
def
で名前に束縛すれば、名前つき手続きとして使えます。
/hello { (Hello, World!) = } def hello % => Hello, World!
スタックから値を取得して引数とすることもできます。
/greeting { (Hello, ) print print ( san!\n) print } def (dayflower) greeting % => Hello, dayflower san!
スタックに値をつんで戻り値となすこともできます。関数ですね。
/BMI { exch 100 div dup mul div } def 160 50 BMI == % => 19.5312481
一時的な辞書を使って名前つきローカル変数もどき
さきほど説明した辞書スタックを使うと別の辞書において名前を束縛できます。これを利用すると手続き内でローカル変数をあつかうことができます。
/BMI { 3 dict begin % サイズ3の一時的辞書を生成, カレント辞書に /weight exch def % スタックトップの値を weight という名前に束縛 /height exch def % スタックトップの値を height という名前に束縛 height 100 div dup mul /hgt2 exch def % 身長の2乗を hgt2 という名前に束縛 weight hgt2 div end } def /height 180 def % デフォルトの辞書で束縛 160 50 BMI == % => 19.5312 height == % => 180 (壊れてない)
def
オペレータは束縛する名前を +1 スタックからもってきますから、スタックトップの値を束縛するには exch
を利用してスタックトップの2つを入れ替える必要がでてきます。
8. 制御文
選択
いわゆる if
文に相当するものです。
true { (T) = } if % => (T) false { (T) = } if % => - true { (T) } { (F) } ifelse == % => (T) false { (T) } { (F) } ifelse == % => (F)
ifelse
の連鎖をおこなうには、ifelse
を多重にネストします。
false { (cond1) = } { true { (cond2) = } { true { (cond3) = } { false { (cond4) = } { (else) = } ifelse } ifelse } ifelse } ifelse % => cond2
さいごの ifelse }
の連鎖がめんどくさいですけど。
繰り返し
% 回数指定繰り返し 5 { (hello) = } repeat % => hello が5行表示される % Basic 的 for 1 3 12 { == } for % => 1, 4, 7, 10 % while 的なものは無限ループ loop と脱出 exit を使う (V) (W) (X) (Y) (Z) { dup (X) eq { exit } if == } loop % => (Z), (Y) % == でスタックトップから消化していることに注意
(forall
についてあとでかく)
9. 条件判断
真偽値は true
と false
の2つしかありません((細かいことをいうと、真偽値たる値があって、デフォルトでシステム辞書に true
と false
という名前が束縛されている。))。一般的な LL 言語のように数値など他のオブジェクトを真偽判定や論理演算には使えません。
関係オペレータ
1 1 eq == % => true (==) 1 2 ne == % => true (!=) 5 3 gt == % => true (>) 2 4 lt == % => true (<) 5 5 ge == % => true (>=) 4 4 le == % => true (<=) % 文字列についても同じオペレータを使える (abc) (abc) eq == % => true (abc) (def) ne == % => true (xyz) (abc) gt == % => true (def) (ghi) lt == % => true
論理オペレータ
true true and == % => true true false and == % => false true false or == % => true false false or == % => false true not == % => false false not == % => true % XOR もあります true true xor == % => false false false xor == % => false true false xor == % => true
10. ファイル入出力
ファイルコピーの例を示します。
/file_r (src.txt) (r) file def /file_w (dst.txt) (w) file def /buf 256 string def { file_r buf readstring not { file_w exch writestring exit } if % 実際に読み込んだ値は buf より戻り値をみたほうがいい file_w exch writestring } loop file_w closefile file_r closefile
11. 雑多なトピック
再帰
実行可能配列が評価されるのは実際に実行されるときなので、自身が束縛された名前をそのまま用いることができます*2。このため再帰は普通の LL 言語のように実現できます。
/fib { 1 dict begin /n exch def { n 1 eq { 1 exit } if n 2 eq { 1 exit } if n 1 sub fib n 2 sub fib add exit } loop end } def 1 fib == % => 1 3 fib == % => 2 5 fib == % => 5 10 fib == % => 55
高階手続き
(あとでかく)
12. ページ記述言語
(あとでかく)
13. サンプル - Fizz Buzz
ここまでの知識があれば何か書けるよね。ってことで FizzBuzz を記述してみました。
%!PS-Adobe-3.0 /say { 1 dict begin /val exch def val 15 mod 0 eq { (FizzBuzz) } { val 5 mod 0 eq { (Buzz) } { val 3 mod 0 eq { (Fizz) } { val 100 string cvs } ifelse } ifelse } ifelse print end } def 1 1 100 { say ( ) print } for (\n) print quit
14. おわりに
ざっくりと PostScript の文法を列挙しましたが、しくみはシンプル*3でありながら一方しっかりとした言語であるといえるのではないでしょうか。ただのページ記述言語であるにはとどまらず、極めるとPostScript でレイトレーシングをすることすらできます*4。
参考文献
- ³¿` [gAFOtBbNX の「PostScript 実習マニュアル」
- このテキストはほんとうにすごいです。この基礎文法最速マスターでよくわからなかった人はこちらを参照すれば一通りマスターできると思います。
- Adobe PostScript language specifications の PostScript Language Reference, Third Edition
- おおもとの仕様書ということでやはり参考になります。しかもどんなオペレータがあるか調べるのは一部(Operator Summary)だけ読めばいいし。実際には Third Edition より薄い PostScript Language Reference, Second Edition を参照しました。
*1:ただし辞書スタックをさかのぼってもみつからなかった場合、カレント辞書に束縛します。
*2:とはいえ callee みたいなのは取得できないと思う。
*3:正直腰がひけていたのですが、文法やしくみ自体は思っていたよりコンパクトでした。
*4:hirax.net::17年前のプログラム・ミステリの解答編 や Tiny_RayTracing.ps を読んでみた。 - 大人が懐かしがることもない も参照してください。