例題のgrepに学ぶgauche


gaucheのユーザリファレンスに、gaucheで書いた簡単なgrepの実装が載っているので、これを参考にしてgaucheの勉強をしてみたいと思います。今回学べそうなことは次の様なことです。

  • C言語のprintfのようなformat手続きの簡単な使い方
  • ファイルの扱い方
  • 文字列の扱い方
  • gaucheでの正規表現

対象としては、gauche(scheme)にある程度慣れている方を対象としています。


以下がソースの全文です。

#!/usr/bin/env gosh

(define (usage)
  (format (current-error-port)
          "Usage: ~a regexp file ...\n" *program-name*)
  (exit 2))

(define (grep rx port)
  (with-input-from-port port
    (lambda ()
      (port-for-each
       (lambda (line)
         (when (rxmatch rx line)
           (format #t "~a:~a: ~a\n"
                   (port-name port)
                   (- (port-current-line port) 1)
                   line)))
       read-line))))

(define (main args)
  (if (null? (cdr args))
      (usage)
      (let ((rx (string->regexp (cadr args))))
        (if (null? (cddr args))
            (grep rx (current-input-port))
            (for-each (lambda (f)
                        (call-with-input-file f
                          (lambda (p) (grep rx p))))
                      (cddr args)))))
  0)
Gauche Users’ Reference: Top

正規表現の生成

(let ((rx (string->regexp (cadr args))))

ここでは、正規表現を扱うために、正規表現のオブジェクトを生成しています。gauche正規表現をオブジェクトとして扱っています。ここでは「string->regexp」手続きを利用して正規表現オブジェクトを生成しています。この正規表現オブジェクトを生成することによって、さまざまな操作ができます。操作については、後のgrep手続きの解説の時にしたいと思います。

for-each構文

(for-each (lambda (f)
            (call-with-input-file f
              (lambda (p) (grep rx p))))
          (cddr args)))))

ここでは、指定されたファイルを一つ一つ開いて、grep手続きに渡しています。「cddr args」で指定された任意の数のファイルを「for-each」手続きで回しています。そして各ファイルを「call-with-input-file」で開いて、その開いたポート(ファイルディスクリプタのようなもの)と、さきほど生成した正規表現オブジェクトをgrep手続きに渡しています。各手続きの詳しい説明は下記のユーザリファレンスを参照してください。

with-input-from-port手続き

(with-input-from-port port (lambda))

さきほどmain手続きから渡されたポートをセットして、引数なしのラムダ式を呼び出しています。似た名前で「with-inpu-from-file」手続きというのがあるのですが、この手続きはファイルをオープンまでをサポートしています。というと、「さっきの「call-with-input-file」手続きとなにが違うんでぃ!!」という声が聴こえてきますが、実は僕もよく分かっていません(なんとなく例外の処理が違うような印象)。

port-for-each手続き

(port-for-each (lambda〜) read-line)
ここでは「read-line」手続きで読み込んだファイルの各行に対して、ラムダ式を作用させています。この「port-for-each」手続きはファイルのEOFが返ってくるまでループしてくれる便利なやつです。ここでちょっと「アレレ」と思ったのは「read-line」手続きの引数のことです。以前扱った時は、引数にportを指定しなければならなかったと思ったのですが、ここではでは次に作用させるラムダ式の内容を見ていきたいと思います。

http://practical-scheme.net/gauche/man/gauche-refj_57.html#IDX701 port-for-each

gaucheでの正規表現

(when (rxmatch rx line)

このrxmatch手続きは、正規表現オブジェクトに一致するものを、文字列lineから探します。perlで書くと「/rx/line/」という感じでしょうか。ここでよく利用しそうな正規表現の手続きをまとめてみたいと思います。詳細な使用法はリファレンスを参照してください。

正規表現オブジェクトを生成する手続き
正規表現エンジン
マッチオブジェクトを処理する手続き
置換
fgrepのように特殊文字エスケープする手続き
マッチした時の処理を記述するマクロ


この他にも、マッチした文字列を細かくアクセスできる手続きなどがあります。これらを駆使すれば、大方の正規表現の処理はできるのではないでしょうか。

format手続き

  (format #t "~a:~a: ~a\n"
          (port-name port)
          (- (port-current-line port) 1)
          line)))

C言語のprintfのような手続きです。このformat手続きを利用すれば複雑な出力ができます。今回の場合はファイル名、マッチした所の行番号、マッチした行を出力しています。printfで書くと「printf("%s: %d: %s\n", port-name, port-current-line - 1, line);」という感じでしょうか。format手続きに関しては、Gauche練習帳 format関数完全マスターの記事がよくまとまっているので、そちらを参照してみるといいと思います。

まとめ

細かいところは結構はしょってしまったのですが、やはりコマンドのプログラミングは勉強になりますね。これを機に、catコマンドのオプション処理をさっさと組みあげたいと思いました。

プログラミングGauche

プログラミングGauche