3

GUI でマクロ展開を強調表示するために、マクロ展開が行われるファイルの場所と文字位置を取得したいと考えています。

そのために、マクロ自体から展開が行われるマクロの現在位置を参照できるようにしたいと考えています。

たとえば、次のコードがあるとします。

(defun mve ()
  (magic-macro :maybe :args))

私はそれを次のように拡張できるようにしたい

(defun mve ()
  (progn
    (macro-body-stuff)
    "This expansion took place at #P(myfile.lisp) between chars 16 and 40"))

そのような関数が存在する場合、最小限のマクロの例は次のようなものになる可能性があります。

(defmacro maybe-macro (&rest r)
  `(progn
     (macro-body-stuff)
     ,(format nil "This expansion took place at ~S between chars ~D and ~D"
             (??:get-macroexpansion-pathname)
             (??:get-macroexpansion-char-start)
             (??:get-macroexpansion-char-end))))

また、これがどこで行われるべきかわからないため、リーダーマクロとしてタグ付けしています。

4

1 に答える 1

2

通常のマクロではこれを移植可能に行うことはできません

「この展開は #P(myfile.lisp) の 16 文字目と 40 文字目の間で行われました」

一般に、フォームが読み取られると利用できないため、そのようなものを取得することはできません。例えば、。この内容のファイルがある場合:

;; line 0
(some-form arg1)

およびこの内容のファイル:

;; line 0
;; line 1
;; line 2
(


some-form



arg1
                )

概念的には、コンパイラは同じ入力を取得します。原則として、リーダーは最初にファイルからフォームを読み取り、それをコンパイラに渡します。どちらの場合も、コンパイラは(some-form arg1) という形式を取得します。これは、作成したマクロもアクセスできることが保証されているものです。個々の実装により、実際にはコンパイラがより多くの機能を利用できるようになる可能性がありますが、それは実装に依存する方法であり、移植可能な方法で公開されるとは限りません。

ただし、この情報の一部を提供するのに役立つファイルをロードするときに、ファイルローダーがバインドする標準的なものがいくつかあります。たとえば、load関数は、特殊変数をファイルのパス名と truename にバインドします。

*load-truename*は、ロードされるファイルのパス名の truename を保持するように load によってバインドされます。

*load-pathname*は、load によってバインドされ、デフォルトに対してマージされた filespec を表すパス名を保持します。つまり、(pathname (merge-pathnames filespec)).

行番号や列番号などを提供する実装依存の拡張機能がある場合は、同じ方法でアクセスできる可能性があります。

しかし、リーダーマクロでこれを行うことができる場合があります

フォームがどこから読み取られたかをファイル内のどこから決定するメカニズムを移植可能に持っていないため、移植可能に通常のマクロでこれを行うことはできません。ただし、リーダー マクロは、フォームが読み取られるストリームで呼び出される関数を呼び出し、ストリーム内の位置を調査するための関数があります。たとえば、次のファイルがあります。

(defparameter *begin* nil
  "File position before reading a form prefixed with #@.")

(defparameter *end* nil
  "File position after reading a form prefixed with #@.")

(eval-when (:compile-toplevel :load-toplevel :execute)
  (set-dispatch-macro-character
   #\# #\@
   (lambda (stream char infix-parameter)
     (declare (ignore char infix-parameter))
     (let ((begin (file-position stream))
           (form (read stream t nil t))
           (end (file-position stream)))
       `(let ((*begin* ,begin)
              (*end* ,end))
          ,form)))))

(defun foo ()
  #@(format nil "form began at ~a and ended at ~a."
            *begin* *end*))

ロードした後、 fooを呼び出すことができます:

CL-USER> (load ".../reader-macro-for-position.lisp")
T
CL-USER> (foo)
"form began at 576 and ended at 650."

もちろん、これは少し脆弱です。リーダー マクロは、ファイル位置が意味を成すストリームではない方法で呼び出される可能性があるため、それについていくつかのチェックを行う必要があります。これらのファイルの位置を行番号と列の観点から解釈する方法がまだ必要ですが、これは良い最初のショットだと思います。

于 2016-06-01T15:05:04.303 に答える