前回の記事では、インタープリター パターンの理論を検討し、AST ツリーとは何か、終端式と非終端式を抽象化する方法を学びました。今回は、理論から離れて、このパターンが私たち全員が毎日使用する本格的な商業プロジェクトにどのように適用されるかを見てみましょう!
ネタバレ: ブラウザでこのテキストを読んでいるだけで、あなたは今 Interpreter パターンを使用している可能性があります。
業界におけるこのパターンの使用例の中で最も印象的で、おそらく最も重要なものの 1 つは JavaScript です。もともと「膝の上で」作成されたこの言語は、今日ではまさに解釈という概念のおかげで何十億ものデバイスで動作します。
インターネットを変えた 10 日間
JavaScript の歴史には伝説がたくさんあります。 1995 年、ネットスケープ コミュニケーションズで働いていたブレンダン アイヒは、ブラウザ (Netscape Navigator) で直接実行して Web ページをインタラクティブにする簡単なスクリプト言語を作成するという任務を与えられました。経営陣は、当時非常に人気があった Java に似た構文を持つものを望んでいましたが、プロのエンジニアではなく、Web デザイナーを対象としていました。
Eich がこの言語の最初のプロトタイプを書くのにわずか 10 日しかありませんでした。この言語は当時 Mocha と呼ばれていました (その後 LiveScript になり、マーケティング上の理由から JavaScript のみになりました)。このラッシュは偶然ではありませんでした。Microsoft はそれに熱心に追随しており、同時に Internet Explorer ブラウザに埋め込むための独自のスクリプト言語VBScript を積極的に準備していました。 Netscape は、迫り来るブラウザ戦争に負けないよう、早急に対応策を発表する必要がありました。
複雑なコンパイラをマシンコードに記述する時間がありませんでした。アイヒにとっての明白かつ最速のソリューションは、古典的なインタプリタのアーキテクチャでした。
最初のインタープリター (SpiderMonkey) は次のように機能しました:
<オル>
ページからスクリプトのテキスト ソース コードを読み取ります。
字句アナライザーはテキストをトークンに分割しました。
パーサーは抽象構文ツリー (AST) を構築しました。インタプリタ パターンの観点から見ると、このツリーは終端式 (文字列、42 などの数値) と非終端式 (関数呼び出し、If や While などのステートメント) で構成されていました。
その後、仮想マシンはこのツリーを段階的に「走査」し、ツリーに埋め込まれた命令を各ノードで実行します (Interpret() と同様のメソッドを呼び出します)。
コンテキストとオブジェクト
従来の実装では Interpret(Context context) メソッドに渡す必要があった Context オブジェクトを覚えていますか?インタプリタは現在のメモリ状態を保存するためにこれを必要とします。
JavaScript の場合、トップレベルでのこのコンテキストの役割はグローバル オブジェクト (ブラウザのウィンドウなど) によって実行されます。 AST ノードが、たとえば document.write(“Hello”) 経由で画面にテキストを書き込もうとすると、インタプリタはそのコンテキスト (ドキュメント オブジェクト) にアクセスし、目的の内部ブラウザ API を呼び出します。
JavaScript が DOM (ドキュメント オブジェクト モデル) と簡単にやり取りできるのは、インタープリタのおかげです。これらはすべて、ツリー ノードによってアクセスされるコンテキスト内の単なるオブジェクトです。
インタプリタの進化: JIT コンパイル
歴史的に、ブラウザの JS は長い間「純粋な」インタープリタであり続けてきました。そして、これには速度が遅いという大きな欠点がありました。スクリプトが実行されるたびにツリーを解析し、各ノードをゆっくりと走査すると、複雑な Web アプリケーションの速度が低下しました。
2008 年の Google の V8 エンジン (Chrome に組み込まれている) の登場により、革命が起こりました。エンジニアは、現代の Web には 1 人のインタプリタでは不十分であることに気づきました。エンジンはより複雑になりました。引き続き AST ツリーを構築しますが、JIT (ジャストインタイム) コンパイルを使用するようになりました。
最新の JS エンジン (V8、SpiderMonkey) は、複雑なパイプラインのように動作します。
<オル>
高速かつダムなベース インタープリターは、コンパイルを待つことなく、即座に JS コードの実行を開始します (ここでも古典的なパターンが機能します)。
並行して、エンジンはコードの「ホット」セクション(何千回も呼び出されるループまたは関数)を監視します。
これらのセクションは、遅いインタープリタをバイパスし、JIT コンパイラによって最適化されたマシンコードに直接コンパイルされます。
インタプリタの即時起動とコンパイルの計算能力の組み合わせにより、JavaScript は世界を席巻し、サーバー (Node.js) やモバイル アプリケーション (React Native) の言語になりました。
ゲーム業界の通訳
ヘビー コンピューティングでは C++ が優勢ですが、インタプリタ パターンはゲーム開発におけるゲーム ロジック作成の業界標準です。何のために?これにより、ゲーム デザイナーはエンジンを「ドロップ」するリスクやエンジンを常に再コンパイルする必要がなく、ゲームを作成できます。
歴史的な優れた例は、UnrealScript です。これは、Unreal Championship および Gears of War ゲームのロジックが Unreal Engine 1、2、3 で記述された言語です。テキストはコンパクトな抽象マシン バイトコードにコンパイルされ、エンジンの仮想マシンによって段階的に (解釈) されます。
ビジュアル グラフ スクリプト (ブループリント)
現在、テキストはビジュアル プログラミング、つまり Unreal Engine 4 および 5 のブループリント システムに置き換えられています。
Unreal Engine でブループリントを開いたことがあれば、多くのノードがワイヤで接続されているのを見たことがあるでしょう。アーキテクチャ的には、ブループリント グラフ全体が画面上に描画される巨大な抽象構文ツリー (AST) です。
<オル>
ターミナル式: 定数ノード。たとえば、単純に数字 42 または文字列を格納するノードです。解釈されると特定の値を返します。
非端末式: 計算ノード (追加) またはフロー制御ノード (分岐)。これらには引数入力があり、インタープリタは結果を出力ピンとして生成する前に、最初に再帰的に評価します。
ここでのコンテキストの役割は、特定のゲーム オブジェクト (アクター) のインスタンスのメモリによって果たされます。インタプリタ マシンはこのグラフ内を安全に「ウォーク」し、データをリクエストして遷移を実行します。
インタープリタは他にどこで使用されますか?
インタプリタ パターンは、動的な命令を実行する必要があるほとんどすべての複雑なシステムに見られます。以下に商用ソフトウェアの例をいくつか示します。
- インタープリタ型プログラミング言語 (Python、Ruby、PHP)。 ランタイム全体は古典的なパターンに基づいています。たとえば、CPython リファレンス実装では、まず .py スクリプトを AST に解析し、バイトコードにコンパイルしてから、巨大な仮想マシン (コンピューティング ループ) がそのバイトコードを段階的に解釈します。
- Java 仮想マシン (JVM)。 最初、Java コードは機械語命令ではなくバイトコードにコンパイルされます。アプリケーションを実行すると、JVM はインタープリターとして機能します (ただし、V8 と同様に積極的な JIT コンパイルが行われます)。
- データベースと SQL PostgreSQL または MySQL で SQL クエリ (SELECT * FROM ユーザー) を発行すると、データベース エンジンがインタープリタとして機能します。字句解析を実行し、AST クエリ ツリーを構築し、実行プランを生成して、テーブルの行を反復処理することでこのプランを文字通り「解釈」します。
- 正規表現 (RegEx)。 正規表現エンジンは内部で文字列パターン (^\d{3}-\d{2}$ など) を解析して状態グラフ (NFA/DFA オートマトン) にし、内部インタープリタがそれを通過させて、各入力文字をこのグラフの頂点と照合します。
- Unity シェーダー グラフ / アンリアル マテリアル エディタ – ビジュアル ノードをモジュラー シェーダー コード (GLSL/HLSL) に解釈します。
- Blender ジオメトリ ノード – 数学的および幾何学的演算を解釈して、リアルタイムで手続き的に 3D モデルを生成します。
合計
Interpreter パターンは、「独自の計算機を作成する」という範囲をはるかに超えています。これは最も強力な業界標準です。毎日ブラウザの舞台裏でギガバイトのコードを実行する JavaScript エンジンから、C++ の知識がなくても複雑なロジックを構築できるゲーム デザイナーに至るまで、インタプリタは依然として現代の IT 開発において最も重要なアーキテクチャ概念の 1 つです。