CMU AIレポジトリ探検 (6) LINT: Lint for Common Lisp — #:g1

Posted 2011-12-08 11:34:00 GMT

今回は、LINT: Lint for Common Lisp の紹介です。(ちなみに前回はこちら)

結構前にいじっていたのですが放置したままにしておくのも気持ち悪いのでエントリーとしてまとめて成仏させることにしました。
名前の通り、Common LispのLintということですが、中身を見てみると、Lintの部分はまさにその通りなのですが、for Common Lispかというと全然そんなことはなく、Symbolics Common Lisp べったりな感じで書かれています。
Symbolics Common Lisp. Should be easy to port to other Lisps. 
などとも書かれているのですが、いや、簡単にポートできないだろうという感じでした。
スタイルチェックを実現する仕組みとしては、スタイルチェック用の関数を定義(deflint)し、それをコンパイル時に式に適用するというものです。
コンパイラに渡る前にスタイルチェッカーを掛けるようなフックが処理系にあれば簡単に移植できると思いますが、とりあえず自分が試したSBCLでは、統一的なフックのようなものはないようです。
オリジナル (Symbolics) はどういう風にフックを掛けているかというとコードを眺めると分かりますが、
( ...
  (defun (compiler:style-checker lint ,function-name) (,arg-var)
    (declare (sys:function-parent ,function-name deflint))
    (when (and *check-portability*
               (or (null *package*)
                   (null
                    (intersection *nonportable-packages*
                                  (package-use-list *package*) ))))
      (let* ((arglist (cdr ,arg-var))
             (result
              (block ,function-name
                (apply #'(lambda ,arglist
                           .,body )
                       arglist ))))
        (when result
          (lint-warn ,arg-var result) ))))
...)

という感じで、 (defun (compiler:style-checker lint function-name)...)のような関数を定義すると、コンパイラと連携するスタイルチェックの関数が定義され、コンパイル時には、これが展開後のフォームに適用されます。
そもそもdefunでの定義で関数名のところがリストだったりして謎ですが、Lispマシンでは、シンボルの plist に関数を定義する場合は、こういう形式で書けました。
スタイルチェッカーは、plistに定義しても良かったようですが、別途、上記のようなグループ分けできる専用の書法があって、上記は、function-nameに対するlintグループのポリシーのチェックということのようです。

このようにかなりSymbolicsべったりなのですが、再現できないか色々試してみました とりあえずで、Symbolics風のグループ分けしたポリシーをハッシュで管理する簡単なものを作成(style-checker-1)。ここまで良いのですが、これをコンパイラが出す警告の仕組みと融合させないと上手く行きません。ちょっと眺めてみたところでは、SBCLでは、Symbolicsのcompiler:style-checkerのような統一された機構はなく、わりとばらばらな部分で処理してばらばらに警告を出しているようです。
しかし、その中でも、sb-c::ir1-convertは、大体共通してフォームが通過するようなので、sb-c::ir1-convertにこのスタイルチェッカーを組み込んでir1-convertの中でSTYLE-WARNINGを出すようにします。そして、lintのdeflintでこのスタイルチェッカーに新しいチェッカーを登録します。 (ちなみに、ir1-convertが壊れると大変なことになるので、改造はやめといた方が良いです…)
これを組み込むとどんな感じになるかというと、
(defun foo ()
  (let ((a))
    a ))
; compiling (DEFUN FOO ...)

; file: /tmp/fileNomts2
; in: DEFUN FOO
;     (DEFUN FOO ()
;       (LET ((A))
;         A))
; --> PROGN EVAL-WHEN SB-IMPL::%DEFUN MULTIPLE-VALUE-PROG1 PROGN
; ==>
;   (BLOCK FOO
;     (LET ((A))
;       A))
;
; caught STYLE-WARNING:
;   Non-portable code: (LET ((A))
;                        A)
;     No value specified in binding (A).
;
; compilation unit finished
;   caught 1 STYLE-WARNING condition
のように、deflintで定義したチェッカーが走ります。
設定したポリシーですが、 (let ((a))..)という形式は非互換なので避けましょうという警告です。ちなみに、ANSI CLでは全くOKなのですが、このlintができた当時のCLtL1では、非互換な書法でした。
コンパイラから出る警告なので、SLIMEでも警告の部分に黄色い下線が出たりしますが、コンパイラと連携するとこういうメリットもあるのが分かったのは発見でした。
気を良くしてlintの方のレシピをどんどん実装して行こうと思ったのですが、レシピの殆どがSBCLでは普通に警告になるかコンパイルエラーになるかでカバーされており、実質役に立たないことが判明。 実際に使うなら、lisp-criticの方がお手軽で良いかなと思います。
それはさておき、なんにしろ、Symbolicsのようにカスタマイズが簡単なスタイルチェッカーを実行する仕組みが処理系に実装されたら便利だなと思いました。

comments powered by Disqus