PostScriptは、伝統的に印刷文書の記述に使用される言語であり、プログラムを空白区切りのトークンのシーケンスとして処理するスタックベースの実行モデルで動作します。これらのメカニズムをJavaScriptの型にマッピングすることで、開発者は基本的な算術、配列操作、およびプロシージャ定義を処理できる機能的な「おもちゃ」のようなインタプリタを構築できます。
スタックベースの実行モデル
PostScriptのロジックの核心は、オペランドスタックとディクショナリストックという2つの主要な構造に依存しています。このモデルでは、オペレータがオペランドスタックから値を取り出し(pop)、計算を実行し、その結果を再びスタックに戻します(push)。`/double`のような名前はリテラルとしてスタックにプッシュされ、その他の名前はディクショナリで検索されて関連するプロシージャを見つけます。
例えば、`3 double`というコマンドは、まず数値の3をプッシュします。次にインタプリタはディクショナリストックで'double'を検索し、関連するプロシージャ(例:`{2 mul}`)を見つけて実行します。このプロシージャは3を取り出し、2を掛け、その結果である6を再びスタックにプッシュします。
「PostScriptは主に印刷文書の記述のために設計されています。ラスタおよびベクタグラフィックスを作成および操作するための豊富な機能の語彙を備えています。」
— Let's Write a PostScript Interpreter
PostScriptからJavaScriptへのマッピング
簡略化されたインタプリタを構築するには、PostScriptのデータ型をJavaScriptの対応する型に変換する必要があります。数値とブーリアンはJavaScriptの`Number`および`Boolean`型に直接マッピングされ、PostScriptの配列とディクショナリはJavaScriptの`Array`および`Map`オブジェクトとして表現されます。PostScript独自の概念である`mark`は、配列構築のためのスタック位置を追跡するシングルトンオブジェクトとして実装されます。
パースプロセスでは、正規表現を使用してプログラムをトークンに分割し、行コメント(`%`で始まるもの)を除外します。その後、インタプリタはこれらのトークンを反復処理します。トークンが`/`のプレフィックスのない名前であれば、システムはディクショナリからそのプロシージャを取得します。もしそれがプロシージャであれば、インタプリタは波括弧で囲まれたトークンのシーケンスを実行します。

このコードリストは、PostScriptの型をJavaScriptのオブジェクトに変換し、プロシージャを実行する様子を例示しています。
実装と制限
インタプリタの仕組みは、主にプリミティブなオペレータの定義に集約されています。これには標準的な算術(add, sub, mul, div)、スタック操作(dup, exch, roll)、および制御フロー(ifelse, for, forall)が含まれます。初期化中にこれらのプリミティブをディクショナリストックにインストールすることで、インタプリタは`[11 22 33] {10 mul} forall`のような複雑な入れ子構造の式を実行できるようになります。

この画像は、インタプリタの仕組みと、それがトークンのシーケンスをどのように処理するかの具体的な例を提供します。
この実装は、言語のメカニズムを理解するための「人間規模」の演習であり、製品向けのツールではありません。簡略化のために、文字列処理やエラー処理などの複雑な機能は意図的に省略されています。GhostscriptのようなフルスケールのPostScriptエンジンには、基本的なインタプリタの範囲外である高精度なグラフィックスレンダリングやフォント管理に関する大幅な複雑さが伴います。
Hacker Newsの読者の反応
- 体験談: あるコメント投稿者は、小さなインタプリタを構築することは、プログラミング言語が内部でどのように機能するかを理解するための価値のある教育的演習であると提案しました。 コメント ↗
- 懐疑的: 別の読者は、コア言語は比較的単純であり、PostScript実装の真の複雑さはグラフィックスとフォントレンダリングにあると主張しました。 コメント ↗
Hacker Newsのスレッドで出た意見の要約です。事実として検証されたものではありません。 議論の全文を読む ↗