Common Lispのloop

tamuraです。

http://www.lispworks.com/documentation/lw51/CLHS/Body/m_loop.htm を見ればほとんどのことは書いてあるけど、「あーarrayを走査したいんだけどどうやるんだっけ」みたいなことはサンプルがあったほうがわかりやすいのでちょっとまとめました。

ちなみに、私はlexだったり字句解析に使うことが多いです。

文字列の走査

(loop for c across "abc"
      do (print c))
> a
> b
> c
NIL

たとえばCL上でnamed parameterなSQLを定義してそれをパースしたい、というときにloopを使いました。

(defun parse-sql (sql)
  (let* ((params nil)
         (sql (with-output-to-string (s)
                (loop with colon = NIL
                      with prev = #\Space
                      with param = '()
                      for c across sql

                      ;; colonが現れたらいったん次の文字を読み込む
                      if (not colon)
                        if (char= c #\:)
                          do (setf colon T)
                        else
                          ;; colonじゃなかったらそのまま出力する
                          do (write-char c s)
                        end
                      else
                        ;; colonが出ててアルファベットor数字ならキーワードとして登録する
                        if (find c "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789")
                          do (push c param)
                        else
                          do (setf colon nil)
                          ;; 前の文字がcolonならcolonが続いているだけなので(SQL的にはおかしいけど)colonを2つ出力する
                          and if (char= prev #\:)
                                do (write-char prev s) (write-char c s)
                                   (setf colon nil)
                              ;; アルファベットor数字orcolon以外ならキーワードの区切りとする
                              else
                                ;; :keyword -> ? に置換して出力
                                do (write-char #\? s) (write-char c s)
                                   (push (intern (format NIL "~:@(~{~A~}~)" (nreverse param)) "KEYWORD") params)
                                   (setf param nil)
                              end
                        end
                      end
                      do (setf prev c)
                      finally (when colon
                                (write-char #\? s)
                                (push (intern (format NIL "~:@(~{~A~}~)" (nreverse param)) "KEYWORD") params))))))
    `(:sql ,sql :keys ,(nreverse params))))

CL-USER> (parse-sql "select * from product where price > :price")
(:SQL "select * from product where price > ?" :KEYS (:PRICE))

CL-USER> (parse-sql "select * from product where price between :low and :hi and maker = :maker")
(:SQL "select * from product where price between ? and ? and maker = ?" :KEYS (:LOW :HI :MAKER))

※これだと文字列の中のパラメータも置換してしまうんですがそこらへんは省略してます

バイトベクタの走査

ファイルから読み込んだバイトベクタを走査したいとき。

(loop for c across #(1 2 3)
      do (print c))
> 1
> 2
> 3
NIL

基本的に文字列と同じです。

ストリームから1文字ずつ読み込みたい

(loop for c = (read-char stream)
      do (print c))
> #\t 
> #\h 
> #\i 
> #\s 
> #\  
> #\i 
> #\s 
> #\  
> #\a 
> #\  
> #\p 
> #\e 
> #\n 
> #\Newline 
NIL

メモリに余裕がある(気にしなくていい)なら、これを使うよりもバイトベクタとかにいっきに読み込んでacrossで処理したほうがいいような気がします。

回数指定

cでいうところの

for (i = 0; i < 5; i++) {
}

をするには

(loop repeat 5
      do (print "a"))
> "a"
> "a"
> "a"
> "a"
> "a"
NIL

とします。

終わり

いったんこんなところで。 ifとかwhenとかはまたの機会に(でもサンプル見れば思い出すからいいかな)。

関連記事

comments powered by Disqus