2010年10月4日月曜日

Common Lispでexpect的ななにか

最近存在を知りましたが、鯖やってる人にはお馴染み?らしいexpectというプログラムが存在するそうです。

TCLで書かれたプログラムで、Passwordという文字列が表示されたらxxxを入力する、というような形で、対話的なコマンドを自動実行するために利用するものとのことです。

PerlやPython、Rubyなどにもそれっぽいライブラリが存在し、なんとGuileにまでexpect.scmというファイルにモジュールが存在します。

Common Lispにもあるよね・・・と思っていたら見つからないので、Guileのプログラムを一部パクって簡単なものを書いてみました。

(asdf:oos 'asdf:load-op :cl-ppcre)
(defpackage expect
(:use :cl :cl-ppcre)
(:export expect expect-strings))

(in-package expect)

(defmacro expect (port (&rest options) &body clauses)
(let ((ch (gensym "ch"))
(str (gensym "str"))
(p (gensym "port"))
(eof (gensym "eof"))
(next (gensym "next")))
`(let ((,p ,port)
(,str ""))
(labels ((,next ()
(let ((,ch (read-char ,p nil ',eof)))
(unless (eq ',eof ,ch)
(setf ,str
(concatenate 'string ,str (string ,ch)))
(cond
,@(mapcar
#'(lambda (clause)
`((funcall ,(car clause) ,str)
,@(cdr clause)))
clauses)
(T (,next)))))))
(,next)))))

(defmacro expect-strings (port (&rest options) &body clauses)
(let ((syms (mapcar
#'(lambda (_) (declare (ignore _)) (gensym))
clauses))
(s (gensym)))
`(let ,(mapcar
#'(lambda (clause sym)
`(,sym (lambda (,s)
(cl-ppcre:all-matches
,(car clause)
,s))))
clauses
syms)
(expect ,port (,@options)
,@(mapcar
#'(lambda (clause sym)
`(,sym ,@(cdr clause)))
clauses
syms)))))

;;
(with-input-from-string (s "Hello,World")
(expect-strings s ()
("Foo" (print 0))
("Hello" (print 1))
("World" (print 2))))

タイムアウトも入力文字ごとに関数を呼ぶ機能もありませんが、取り合えず読み込んだ文字列が正規表現にマッチすると処理が実行されるようになりました。

引数optionsは、後で何か付けたそうと思って書いたもので今は特に意味はありません。

それにしても、Guileのモジュール名のice-9って何なんでしょう。無駄に格好良く見えます。

0 件のコメント:

コメントを投稿