#:g1: clawkの紹介

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

(LISP Library 365参加エントリ)

 LISP Library 365 の265日目です。

clawkとはなにか

 clawkは、Kenneth Michael Parker氏作のCommon Lisp上でawkのような操作を実現するライブラリです。

パッケージ情報

パッケージ名clawk
Quicklisp
Quickdocsclawk | Quickdocs
CL Test Grid: ビルド状況clawk | CL Test Grid

インストール方法

(ql:quickload :clawk)

試してみる

 どんな関数があるかは、Quickdocsで確認できます。

 プログラミング言語AWKの初っ端にある例ですが、こんなファイル(名前 時給 労働時間)があったとして

Beth    4.00    0
Dan     3.75    0
Kathy   4.00    10
Mark    5.00    20
Mary    5.50    22
Suzie   4.25    18

これの人数、支払いの合計、支払いの平均を集計するにはawkだと、

{ pay = pay + $2 * $3 }
END { print NR, "employees"
      print "total pay is", pay
      print "average pay is", pay/NR
    }

みたいな感じですが、ファイルを開くのを含めて同じようなものを書くとCommon Lispでは、

(with-open-file (in "/tmp/emp.data")
  (let ((employees 0)
        (pay 0))
    (loop :for line := (read-line in nil) :while line
          :do (incf employees)
              (destructuring-bind (name w h) 
                                  (ppcre:split "\\s+" line)
                (declare (ignore name))
                (incf pay (* (read-from-string w)
                             (read-from-string h)))))
    (format t "~A employees~%" employees)
    (format t "total pay is ~A~%" pay)
    (format t "average pay is ~A~%" (/ pay employees))))
;>>  6 employees
;>>  total pay is 337.5
;>>  average pay is 56.25
;>>  
;=>  NIL

こんな感じです。
これを、clawkを使って書くと

(let ((pay 0) (employees 0))
  (for-file-fields ("/tmp/emp.data" (name w h))
    (declare (ignore name))
    (incf pay ($* w h))
    (incf employees))
  (format t "~A employees~%" employees)
  (format t "total pay is ~A~%" pay)
  (format t "average pay is ~A~%" (/ pay employees)))
;>>  6 employees
;>>  total pay is 337.5
;>>  average pay is 56.25
;>>  
;=>  NIL

とまあawkっぽく書けます。

CL上でawk風の記述を支援するユーティリティ

 便利に使えるユーティリティ関数が定義されていて、CLとawkが融合したような感じで書けます。

(for-file-fields ("/tmp/emp.data")
  ($print $3 $2 $1 " :" *nf*))
;>>  
;>>  0 4.00 Beth  : 3 
;>>  0 3.75 Dan  : 3 
;>>  10 4.00 Kathy  : 3 
;>>  20 5.00 Mark  : 3 
;>>  22 5.50 Mary  : 3 
;>>  18 4.25 Suzie  : 3 
;=>  NIL

 awkのプログラムだと、BEGIN、本体、ENDという構成がありますが、

(defawk foo ()
  (begin ($print "begin!"))
  (t ($print $3 $2 $1 " :" *nf*))
  (end ($print "end!")))

(with-input-from-string (s (format nil "foo~%bar~%baz")) (foo s)) ;>> ;>> begin! ;>> foo : 1 ;>> bar : 1 ;>> baz : 1 ;>> end! ;=> NIL

という感じに記述もできます。

 また、正規表現用にリーダーマクロが定義されていて、#/.../で記述することが可能です。

(*:defreadtable :awk
  (:merge :standard)
  (:dispatch-macro-char #\# #\` #'clawk::|#`-reader|)
  (:dispatch-macro-char #\# #\/ #'clawk::|#/-reader|)
  (:case :upcase))

(*:in-readtable :awk)

(for-file-fields ("/tmp/emp.data") (match-when (#/th/ ($print $3 $2 $1 " :" *nf*)))) ;>> ;>> 0 4.00 Beth : 3 ;>> 10 4.00 Kathy : 3 ;=> NIL

 さらに、#`...`でシェルコマンドを実行した結果を処理できるようですが、どうもコードを追加しないとLispWorksでしか動かないようです。

(for-stream-lines (#`ls /tmp/`)
  ($print *FNR* " " $0))

まとめ

 今回は、clawkを紹介してみました。
素直にawkを使えば良いんじゃないのかという話もありますが、ソースを眺めると、#+Generaという記述があるように元々はSymbolicsのLispマシン上でawk的なことをやりたかった、という記事をどっかで読んだ記憶があります。

comments powered by Disqus