SKK辞書をS式に変換する

追記 : "正順"って日本語は無いよね多分。ローカル辞書に有るってことは以前どこかで入力したって事だな。。
諸般の事情でmigemoを実装する必要があるので、SKK辞書をS式に変換することにする。
moshには正規表現が有るので、本来、それを使えばずっとコンパクトかつ高速に書ける。
(mosh file)にはfile->stringという非常に直接的な手続きがあり、ファイル名を与えるとその内容の文字列を得る。
R6RSの文字定数は#\に続けて文字クラスや文字そのものを書く。例えばLFは#\linefeedのように書ける。Cで言うと'\n'。
moshのfilterのようなlist操作関数の一部は末尾再帰的に実装されていないので、巨大なリストを扱う場合は自分で実装したものを使う方が望ましいケースがある。実際、このコードでSKK-JISYO.Lを処理したらメモリが足りなくなったのでfilterではなくmy-filterを定義して使っている。

(define (my-filter p l)
  (define (my-filter-itr cur rest)
    (if (pair? rest)
      (let ((a (car rest)))
        (my-filter-itr (if (p a) (cons a cur) cur) (cdr rest)))
      (reverse cur)))
  (my-filter-itr '() l))

mosh本来のfilterは、library.scm中に次のように実装されている。

(define (filter pred? l)
    (cond ((null? l) '())
      ((pred? (car l)) (cons (car l) (filter pred? (cdr l))))
      (else (filter pred? (cdr l)))))

これらは対立するイディオムで、リストを構築する関数を書くときに、逆順のリストを作って最後にreverseするか、通常の順序につくるかの2通りが考えられる。

  • 逆順
(define (body l cur) (body (cdr l) (cons (proc (car l)) cur)))
(reverse (body lst '())) ; 初期値は空リスト
  • 通常
(define (body l) (cons (proc (car l)) (body (cdr l))))

順序を気にしないケースや、巨大なリストを処理する場合は逆順に書いて末尾再帰的にする方が好ましいように思う。どっちにせよ、mapやfilterを使っていて大きな問題を処理できないときはこの制限を頭に入れておく必要がある。
(この手の仕事をするときにはappendを使わないことが多い気がする。appendはリストをコピーする。)
SRFI-1のようにin-placeで破壊的なreverse(reverse!)を提供していることもある。要するに巨大なデータを処理する場合に効率的な手法は処理系に依る。

(import (rnrs)
        (mosh file)
        (srfi :8))

(define filename "SKK-JISYO.L.utf8.txt")

(define (string-line->list str)
  (let loop ((cur '())
             (cur-str '())
             (rest (string->list str)))
    (if (pair? rest)
      (let ((c (car rest))
            (rest (cdr rest)))
        (if (char=? c #\linefeed)
          (loop (cons (list->string (reverse cur-str)) cur) '() rest)
          (loop cur (cons c cur-str) rest)))
      (reverse cur))))

(define (my-filter p l)
  (define (my-filter-itr cur rest)
    (if (pair? rest)
      (let ((a (car rest)))
        (my-filter-itr (if (p a) (cons a cur) cur) (cdr rest)))
      (reverse cur)))
  (my-filter-itr '() l))

(define content (my-filter
                  (lambda (e) (not (char=? (string-ref e 0) #\; )))
                  (string-line->list (file->string filename))))


(define (split-char c str)
  (let loop ((cur '())
             (rest (string->list str)))
    (if (pair? rest)
      (if (char=? (car rest) c)
        (values (list->string (reverse cur)) (list->string (cdr rest)))
        (loop (cons (car rest) cur) (cdr rest)))
      (values str ""))))

(define (conv e)
  (define (filter-anotate s)
    (receive (ret bogus) (split-char #\; s) ret))
  (define (skip-first s)
    (receive (bogus ret) (split-char #\/ s) ret))
  (define (split-data d)
    (let loop ((cur '())
               (str (skip-first d)))
      (receive (DATUM rest) (split-char #\/ str)
        (if (string=? "" DATUM)
          (reverse cur)
          (loop (cons (filter-anotate DATUM) cur) rest)))))

  ; split YOMI / data => (YOMI . (split-data data))
  (receive (YOMI data) (split-char #\space e)
    (cons YOMI (split-data data))))

(write (map conv content))