CLOVER🍀

That was when it all began.

Gaucheでcat/grepを書いてみる

書籍「プログラミングGauche」を半分くらい読んだり書いたりしたので、ちょっとしたサンプルとして「cat/grep」もどきを書いて遊んでみたいと思います。

イメージ的にはLinuxコマンドで

$ cat [ファイル名] [ファイル名] [ファイル名] ... | grep [正規表現]

と書くようなことをプログラミングで再現するものです。

あまり大したことないはずなんですけど、ファイル読み込み、正規表現、引数解析などなど、そこそこライブラリを使用するので、何気に始めたばかりのプログラミング言語でやろうとするとかなり苦戦します。

そして、実際苦戦しました…。

できあがったのはこちら。
grep.scm

#!/usr/local/bin/gosh
;; -*- coding: utf-8 -*-

(use util.match)                        ; パターンマッチライブラリを使用

;; エントリポイント
(define (main args)
  (define (print-usage)
    (print "Required 2 More Than Arguments [Pattern File-Names...]"))
  ;; パターンマッチで引数を分解
  (match (cdr args)
         [() (print-usage)]
         [(p) (print-usage)]
         [(pattern . file-names) (apply cat (cut grep pattern <>) file-names)])) ; 

;; 「grep」関数。引数に指定したパターン文字列を引数lineにマッチするかどうかを
;; テストし、マッチした場合はline自体をプリントする
(define (grep pattern line)
  (let ((regex (string->regexp pattern))) ; 引数patternを正規表現オブジェクトに変換
    (if (regex line)
        (print line))))

;; 引数fileで渡されたファイルをオープンし、読み出した1行に対して引数で渡された
;; 関数procを適用する
;; ※エンコーディングは「UTF-8」固定
(define (file-each-line file proc)
  (with-input-from-file file
    (cut port-for-each (cut proc <>) read-line)
    :encoding "UTF-8"))

;; 「cat」関数。ファイル名を可変長引数を受け取り、引数で渡されたファイルの数だけ
;; 関数file-each-lineを適用する
(define (cat proc . file-names)
  (for-each (cut file-each-line <> proc) file-names))

ホントは、Streamを使って無限リストっぽくcatの結果を生成したかったのですが、今はそこまで及ばず…。

動き的には、ファイルの中身1行に対して適用する関数とファイル名を受け取れるcat関数の中に、起動引数から正規表現オブジェクトを構築して読み込まれた1行に対して処理を適用するgrep関数を部分適用状態で放り込んでいます。

Gaucheで部分適用をするには、「cut」関数を使用するんですって。

関数型言語的には、cat関数が無限リストを返し、grep関数がリストに対してフィルタリングしていく構成にしたかったのですけどね。まだまだ理解不足です。

また今度、改良版を作成してみましょう。

実行結果は、こんな感じ。自分自身に対して、「print」をパターンとしてgrepします。

$ gosh grep.scm "print" grep.scm
  (define (print-usage)
    (print "Required 2 More Than Arguments [Pattern File-Names...]"))
         [() (print-usage)]
         [(p) (print-usage)]
        (print line))))

一応、動いていますね。