PythonでLispインタプリタを書く方法

PythonでLispインタプリタを書く方法

Lispインタプリタの実装は、コンピュータとコンパイラの仕組みを理解するための主要な手法です。Peter Norvigの"Lispy"プロジェクトは、LispのScheme方言の機能的なサブセットが、約117行のPython 3コードで実装可能であることを示しており、「世界で最も強力な言語」が非常に少量のソースコードで定義できることを証明しています。

言語インタプリタのアーキテクチャ

言語インタプリタは、主に「解析(parsing)」と「実行(execution)」の2つのコンポーネントで構成されます。プロセスは線形パイプラインに従います:Program $\rightarrow$ Parse $\rightarrow$ Abstract Syntax Tree (AST) $\rightarrow$ Eval $\rightarrow$ Result

解析 (Parsing)

解析は、一連の文字を内部表現、通常は抽象構文木(AST)に変換します。Lispyでは、これは2つのフェーズに分割されます:

  1. 字句解析 (Tokenization): 入力文字列はトークン(括弧、シンボル、数値)に分解されます。Lispyは、括弧の周囲にスペースを追加し、Pythonのstr.splitを使用することでこれを実現しています。
  2. 構文解析: トークンは入れ子になったリスト構造に組み立てられます。read_from_tokens関数は、開始括弧に遭遇すると再帰的にリストを構築し、括弧以外のトークンはアトム(整数、浮動小数点数、またはシンボル)として扱います。

実行 (Evaluation)

実行は、言語のセマンティクス(意味論)のルールに従ってASTを処理します。Lispyでは、eval関数が式のタイプに基づいて式の値を決定します:

  • Symbols: 現在の環境(environment)で検索されます。
  • Numbers: それ自身として評価されます。
  • Special Forms: ifdefineのようなキーワードは、特定のロジック(例:条件分岐や変数代入)をトリガーします。
  • Procedure Calls: オペレータと引数が評価され、結果として得られたプロシージャが引数の値に適用されます。

Lispy Calculatorの実装

インタプリタの最初のイテレーションである「Lispy Calculator」は、5つの基本的な構文形式をサポートしています:

Expression Syntax Semantics
Variable Reference symbol 変数の値を返します。
Constant Literal number 数値そのものを返します。
Conditional (if test conseq alt) testが真であればconseqを返し、そうでなければaltを返します。
Definition (define symbol exp) expの値をsymbolにバインドします。
Procedure Call (proc arg...) procargsを評価し、その結果として得られたプロシージャを適用します。

完全なLispyへの拡張:プロシージャとレキシカルスコープ

計算機から、ほぼ完全なSchemeサブセットへと移行するために、Lispyはquote, set!, そしてlambdaを導入します。lambdaの追加には、環境(environments)を通じた高度な変数管理のアプローチが必要です。

Envクラスとレキシカルスコープ

ローカル変数と入れ子になった関数をサポートするために、環境はdictを継承するクラスとして再定義されます。各Envインスタンスは、独自のローカルなマッピングと、outer環境への参照を保持しています。

変数が参照されるとき、インタプリタはfindメソッドを使用して、まず最も内側の環境を検索します。変数が見つからない場合、再帰的にouter環境を検索します。このメカニズムはレキシカルスコープを実装しており、プロシージャが呼び出される場所ではなく、定義された場所の環境を参照することを保証します。

プロシージャの実装

Procedureオブジェクトは、パラメータ名、関数の本体、およびそれが作成された環境を保持します。呼び出されたとき、それは提供された引数をパラメータにバインドする新しいEnvを作成し、プロシージャの元の環境をouterスコープとして設定します。

パフォーマンスと完全性

Lispyは、本番環境での使用ではなく、教育的な明快さを目的として設計されています。その特徴は以下の通りです:

  • Size: 極めてコンパクトであり、約117行の非空白行と4KBのソースコードで構成されています。
  • Speed: (fact 100)を約0.003秒で計算可能です。
  • Limitations: テイルコール最適化、call/cc、文字列、文字、ブール値、および包括的なエラーリカバリが欠如しているなど、いくつかの標準的なSchemeの機能が欠われています。

コミュニティの洞察

開発者や教育者は、Lispインタプリタの実装はコンピュータサイエンスの基礎的な演習であると考えています。議論の中で述べられているように:

"If you ever wondered how to write a programming language, this is probably the best resource to get started."

他の貢献者たちは、Lispのツリー構造と、言語学者が文構造を annotate するために使用する句構造文法との同型性を指摘しました。また、AIが現在そのようなコードを生成できるようになった一方で、プログラマーにとってインタプリタのプロジェクトは依然として「啓発的な経験」であると指摘する人もいました。

Sources