ファイルのコンパイルはCommonLispで定義されています:CLHSセクション3.2.3ファイルのコンパイル
コンパイル中:読み取りマクロを使用してフォームを使用するには、その読み取りマクロの実装をコンパイラーが使用できるようにする必要があります。
通常、このような依存関係はdefsystem
、システムのさまざまなファイル(プロジェクトなど)間の依存関係が記述されている機能で処理されます。特定のファイルをコンパイルするには、別のファイル(できればコンパイルされたバージョン)をコンパイル中のLispにロードする必要があります。
ここで、読み取りマクロを定義し、同じファイルにその表記を使用するフォームを作成する場合は、コンパイラが読み取りマクロとその実装について認識していることを再度確認する必要があります。ファイルコンパイラにはコンパイル環境があります。デフォルトでは、同じファイルのコンパイル済み関数をこの環境にロードしません。
コンパイラにファイル内の特定のコードを認識させるために、CommonLispが提供するコンパイルを行いますEVAL-WHEN
。
読み取りマクロの例を見てみましょう。
(set-syntax-from-char #\] #\))
(defun reader-example (stream char)
(declare (ignore char))
(let ((class (read stream t nil t))
(args (read-delimited-list #\] stream t)))
(apply #'make-instance
class
args)))
(set-macro-character #\[ 'reader-example)
(defclass example ()
((name :initarg :name)))
(defvar *examples*
(list [example :name e1]
[example :name e2]
[example :name e3]))
上記のソースをロードすると、すべて問題ありません。ただし、ファイルコンパイラを使用する場合、最初にロードしないとコンパイルされません。たとえば、ファイルコンパイラはCOMPILE-FILE
、パス名を使用して関数を呼び出すことによって呼び出されます。
次に、ファイルをコンパイルします。
(set-syntax-from-char #\] #\))
上記はコンパイル時に実行されません。新しい構文の変更は、コンパイル時には利用できません。
(defun reader-example (stream char)
(declare (ignore char))
(let ((class (read stream t nil t))
(args (read-delimited-list #\] stream t)))
(apply #'make-instance
class
args)))
上記の関数はコンパイルされますが、ロードされません。この実装は、後のステップでコンパイラーが使用することはできません。
(set-macro-character #\[ 'reader-example)
繰り返しますが、上記のフォームは実行されません。そのためのコードだけが生成されます。
(defclass example ()
((name :initarg :name)))
コンパイラはクラスを記録しますが、後でそのインスタンスを作成することはできません。
(defvar *examples*
(list [example :name e1]
[example :name e2]
[example :name e3]))
上記のコードはエラーをトリガーします。これは、読み取りマクロがコンパイル時に使用できないためです。これは、以前にロードされたことがない場合を除きます。
現在、2つの簡単な解決策があります。
例:
(EVAL-WHEN (:compile-toplevel :load-toplevel :execute)
(do-something-also-at-compile-time))
上記はコンパイラーによって認識され、実行されます。ここで、コンパイル時にコードが呼び出すすべてのもの(必要なすべての定義)が含まれていることを確認する必要があります。
言うまでもなく、このようなコンパイルの依存関係を可能な限り減らすのは良いスタイルです。通常、必要な機能を別のファイルに入れ、それを使用するファイルをコンパイルする前に、このファイルがコンパイルされ、コンパイル中のLispにロードされることを確認してください。