#:g1: QITAB: declare-indentationの紹介

Posted 2014-09-30 22:30:00 GMT

(LISP Library 365参加エントリ)

 LISP Library 365 の274日目です。

QITAB: declare-indentationとはなにか

 QITAB: declare-indentationは、ITAで利用されているCommon Lisp側からEmacsのインデントを宣言するユーティリティです。Scott McKay氏作の模様。

パッケージ情報

パッケージ名QITAB: declare-indentation
Quicklisp×
プロジェクトサイトQITAB - a collection of free Lisp code

インストール方法

 common-lisp.netからITAで利用されているユーティリティのスナップショットが入手できるので、これをダウンロードします。

目的のファイルは、

です。

試してみる

 一応上記に入手方法は書いてみましたが、長くないので掲載してみます。パッケージ等は適当に調整して下さい。
ちなみに、QResという飛行機チケットの予約システムの中のコードのようです。

(eval-when (:compile-toplevel :load-toplevel :execute)
  (defvar *indentation-hints* (make-hash-table)
    "The key is an OPERATOR such as a symbol naming a macro.
     The value is instructions about how to indent forms
     starting with that operator.  Values are e.g.
     (5 &body), let, (6 6 6 (&whole 4 1 &rest 1) &rest 2).")

(defmacro declare-indentation (operator &rest arguments)

"Define a cl-indent spec for OPERATOR. OPERATOR is a symbol naming a function or macro and ARGUMENTS is either a cl-indent spec list or a symbol specifying that OPERATOR should be indented just like that symbol. Calls to this macro are typically near the operator's definition."

`(eval-when (:compile-toplevel :load-toplevel :execute) (setf (gethash ',operator *indentation-hints*) ',(if (and (= 1 (length arguments)) (symbolp (first arguments))) (first arguments) arguments)) ',operator)))

;;;; * Qres indentation hints ;;; This list of tables is used by contrib/swank-indentation.lisp, ;;; which we load automatically at this site. (declaim (special swank::*application-hints-tables*)) (setf swank::*application-hints-tables* ;; If the QUUX package is defined, get the value of ;; quux:*qres-indentation-hints*. (let ((qres-table (and (find-package '#:indentation-hints) (symbol-value (find-symbol (string '#:*indentation-hints*) (find-package '#:indentation-hints)))))) (if qres-table (list qres-table) '())))

 このコードは、slimeのswank-indentationに依存しているので、Emacs側で

(slime-setup '(slime-indentation))

等とするか、直接ロードしておきます。

(load
 (compile-file
  (merge-pathnames (make-pathname :name "SWANK-INDENTATION" :type "LISP" :case :common)
                   (swank-loader::contrib-dir swank-loader::*source-directory*))
  :output-file (merge-pathnames
                (make-pathname :directory '(:relative "CONTRIB")
                               :name "SWANK-INDENTATION"
                               :type "FASL"
                               :case :common)
                swank-loader::*fasl-directory*)))

等々

使い方

(defmacro mdefun (name (&rest args) &body body)
  (declare (ignore name args body)))

(declare-indentation mydefun 2 (&whole nil &rest 1) &body)

のように記述すれば、インデントが

(mydefun foo
         (a a
          a a)
  x
  x
  x)

こんな感じになります。

MIT系Lispでのインデントについて

&restと&bodyについて

 基本的にLispを書いている人はエディタにインデントを任せているので、インデントがどうというのは殆ど気にしていないかと思いますが、&rest型と&body型でインデントが違っています。

 &rest型が基本で、これは関数で利用されています。引数は縦に整列しますが、第一引数の前に改行があるとスペース1つでインデントされます。
6、70年代位は、このインデントの方式が非常に多いようなので、ここから色々と進化していったのかもしれません。

0123456789
|&rest
||&body
|||

(list x x x )

0123456789 |&rest ||&body |||

(list x x x)

 次に&body型ですが、&bodyで指定した以降が、スペース2つでインデントされます。

0123456789
|&rest
||&body
|||&rest

(progn x x x)

0123456789 |&rest ||&body |||&rest

(progn x x x)

ということで、関数とマクロ(スペシャルフォーム)は微妙にインデントで区別できたりします(二者に共通のパタンは関数で利用することが多いかなと思います)。
slimeを利用している場合、マクロの&body以降は2スペースにしてくれるので大抵これで間に合うのですが、Emacsのデフォルトが気に入らない場合、カスタマイズすることになります。
しかし、Common Lispを書いている時にEmacs Lisp側の設定を書くのが億劫なので、こういう場合に便利に使えるのではないでしょうか。

インデントパタンの記述について

 毎度Emacsのインデント記述の書法を忘れてしまうので自分の備忘録代わりにメモしてみたいと思います。

(defmacro mdefun (name (&rest args) &body body)
  (declare (ignore name args body)))

のような場合、Emacsのデフォルトだと、

(mdefun
    foo
    (a a
     a a)
  b
  b
  b)

(mdefun foo (a a a a) b b b)

のように揃います。
これはオペレーター名と第一引数は、4つスペースのインデントにして、第二引数以降は&bodyの2つスペースにするようになっています。
自分的にはLispマシン風に、オペレーター名の後で改行した場合は、prognと同じで、

(mdefun
  name
  (arg arg
   arg arg)
  body
  body
  body)

第一引数の後で改行した場合は、第二引数は第一引数と同じ位置にし、第三引数以降はprognと同じにしたいところです。

(mdefun name
        (arg arg
         arg arg)
  body
  body
  body)

この場合、

(declare-indentation mdefun 2 (&whole nil 1 &rest 1) &body)

と指定できますが、最初はフォーム名、以降は、

  1. 2: 2つスペース
  2. (
    1. &whole: サブフォーム全体のインデント開始位置。nilの指定は、直前から引き継ぐので、2つスペース
    2. 1: サブフォーム内はスペース1のインデント
    3. &rest 1: 以降はスペース1つでインデント
    )
  3. &bodyの指定で、それ以降は、2つスペースのインデント

という内容になっています。

まとめ

 今回は、QITAB: declare-indentationを紹介してみました。
Zetalispや、emacsでは

(declare (zwei:indentation 1 1))

のようにdeclareで宣言できたりします。
開発環境と言語機能がごっちゃになっていて気持ち悪いというところもありますが、LispらしいといえばLispらしいかなと思います。

comments powered by Disqus