言語としてのPostscriptを理解するための基本要素を押さえておく。
後入れ先出し(Last In First Out)。棚や、「積ん読」と 同じように後から足したものを先に取り出すフローのデータ置き場。 スタック領域を1つ用意して、そこに単一データを足すことを push、取り出すことをpopという。
先入れ先出し(First In First Out)。待ち行列と同じように、 先に入って来たものが、先に取り出されていくフローのデータ置き場。 待ち行列の末尾(初回は先頭)にデータを足す操作を enqueue、先頭から取り出す操作を dequeue という。
Postscriptはスタック操作を繰り返すことで処理を進める。
人間が数学で用いている加減乗除算は演算子が2つの数の間に書かれる 中間記法である。
1 + 2 (1 + 2) * 3
演算子を前、または後に書く記法も存在する。
演算子を先に書きオペランドを後に書く記法。
+ 1 2 * + 1 2 3
演算子を関数のように捉え表現できる。Lispは括弧で演算範囲を 表記する一種のポーランド記法である。
(+ 1 2) ;; C言語関数形式であれば add(1, 2) (* (+ 1 2) 3) ;; mul(add(1, 2), 3)
関数呼出し形式と考えれば自然だが、「後に処理すべきものを先に書く」 点が直観に反する。英語の語順に近い。
オペランドを先に書き演算子を後に書く。
1 2 * 1 2 + 3 *
dc(1)コマンド、Postscript言語、Forth言語がこれにあたる。 演算を行なう関数がスタック上に積んだ数値のみを見て、 結果をスタックに残せば処理が完了するので、ほとんどの場合 変数を利用することなく処理が完結する。このため メモリを潤沢に使用できない環境などで有用である。 また、語順が日本語やオブジェクト指向のメソッドチェーンに近く 思考の流れとの対応を取りやすい(慣れが必要だが)。
% Postscriptとそれに対応するRubyのメソッドチェーンの例 1 2 add % [1, 2].inject(:+) ;または [1, 2].sum 1 2 add 3 mul % [[1, 2].inject(:+)].inject(:*)
これはそれぞれ
という日本語語順とも一致している。
演算などすべての処理対象がスタックトップ(最後に入れた方)に
施されるものの動きに触れる。まずインタプリタを起動し数値を入れ、
スタックに積まれる様子を "pstack"
で確認する。
1 2 3 pstack 3 2 1 4 5 6 pstack 6 5 4 3 2 1
演算子や関数(後述)は、必要な引数をスタックから引っ張り出し、 結果をスタックトップに返す。
add pstack 11 4 3 2 1
==
演算子はスタックトップを取り出し、出力する。
== 11 pstack 4 3 2 1
=
と ==
の違いは、=
がオブジェクト形式のまま出すのに対し、==
が文字列化して出す点にある。また出力後改行しない print
演算子もある。
exch
は、スタック最上位の2つを交換する(exchange)。
exch pstack 3 4 2 1
pop
は、スタックトップを取り除き、
clear
は、スタックをすべてクリアする。
pop pstack 4 2 1 clear
その他スタック操作の主なものを示す。
操作 | 意味 |
---|---|
dup | スタックトップの複製 |
Nindex |
スタックトップからN個先の要素をスタックトップに複製 |
Ncopy |
スタックトップからN個の要素をスタックトップに複製 |
シンボルのように扱われるのがトークンで、
任意の値(関数も)を結び付ける(バインドする)ことができる。
バインドには def
を利用する。トークンがトークンとして
扱われるように /
でクォートする。
バインドではスタックは変わらない。
/pi 3.14 def % piに3.14をバインドする pi % piの値をスタックに積む pstack 3.14
扱える基本型を列挙する。
種別 | 書式 | 例 |
---|---|---|
文字列 | ( ) |
(Hello, world\n) |
配列 | [ ] |
[1 2 3] |
辞書 | << >> |
<< /foo 1 /bar 2>> |
手続き(関数) | { } |
{ 1 add } |
加算 | add |
---|---|
減算 | sub |
乗算 | mul |
除算 | div |
整数除算 | idiv |
剰余算 | mod |
論理演算子は、 eq, gt, lt, ge, le, not, and, or でいずれもスタックトップに対して作用し、true か false を スタックトップに残す。
スタックの2番目がtrueなら、スタックトップを評価する。
3 % 3をpushする 5 eq { (It is Five) print } if
評価する値は手続きでなければならない。以下の ifelse も同様。
スタックの3番目がtrueならスタック2番目を、falseなら スタックトップを評価する。
3 % 3をpushする 5 eq {(It is Five)} {(It is NOT Five)} ifelse
スタックの2番目の数だけスタックトップの手続きを繰り返す。
5 { (yes) print } repeat
スタック4番目から、「開始値」、「刻み値」、「終了値」で スタックトップの手続きを繰り返す。
スタックトップの手続きを繰り返す。ループは exit で抜けられるので、ifやifelseと組み合わせてループを抜ける。
f というトークンに手続きをバインドする。
/f { (hello) print } def
引数はスタックトップから取って来る。 exchでスタックにある値をトークンにバインドすることで、仮引数のように 処理するのが定石である。以下の例は2つの引数を、a、bに代入し 足した結果をスタックに戻す関数的手続きである。
/tasu { /a exch def /b exch def a b add } def 40 60 tasu =
引数1つを受け取り、偶数か奇数かを出力する関数 guuki を定義せよ。
引数2つを受け取り、BMI値を出力する関数 BMI を定義せよ。
/guuki { /i exch def ( Number) i 2 mod 0 eq {(Even)} {(Odd)} ifelse print = } def
begin
から始まり end
までの部分は、begin
が呼ばれたときのスタックトップにある
辞書を名前空間にしてトークンが定義される。
以下の例は、長さ1の辞書を作成し begin ブロックに入り、
変数 x, y, z 相当のトークンを利用する。ブロックから出ると
x, y, z は辞書とともに消滅する。
1 dict begin /x 1 def /y 2 def /z 3 def x y z add mul == % ここで5が出力される end x /x は既に存在せずエラー
/guuki { 1 dict begin /i exch def ( Number) i 2 mod 0 eq {(Even)} {(Odd)} ifelse print = end } def