2014年2月24日月曜日

lispでiniファイルからalistをconsする

lispでiniファイルからalistをconsするプログラムを書きました。

サンプルのiniファイルはこのようになっています。
[Section1]
Key1=Value1
Key2=Value2

[Section2]
Key1=Value1

[Section3]


プログラム本体は以下のとおりです。
;; inifile.l
(defun read-lines (input)
  (loop as line = (read-line input nil nil)
        while line
        collect line))

(defun sectionp (line)
  (and (stringp line)
       (<= 2 (length line))
       (eq #\[ (char line 0))
       (eq #\] (char line (1- (length line))))))

(defun keyvaluep (line)
  (and (stringp line)
       (<= 3 (length line))
       (find #\= line)
       (< 0 (position #\= line))))

(defun strip-section (line)
  (if (not (sectionp line))
      line
    (subseq line 1 (1- (length line)))))

(defun split-keyvalue (line)
  (if (not (keyvaluep line))
      line
    (list (subseq line 0 (position #\= line))
          (subseq line (1+ (position #\= line))))))

(defun convert-to-alist (alist line)
  (cond ((sectionp line)
         (cons (list (strip-section line)) alist))
        ((keyvaluep line)
         (cons (append (car alist) (list (split-keyvalue line))) (cdr alist)))
        (t alist)))

(defun ini-assoc (section key alist)
  (car (last
        (assoc key
               (cdr (assoc section alist :test 'string-equal))
               :test 'string-equal))))


;; 以下実行部
(setf ini-alist (reduce 'convert-to-alist (read-lines *standard-input*)
                        :initial-value '()))
(format t "~A~%" ini-alist)
(format t "~A ~A = ~A~%" "Section2" "Key1"
        (ini-assoc "Section2" "Key1" ini-alist))
(format t "~A ~A = ~A~%" "SECTION1" "KEY2"
        (ini-assoc "SECTION1" "KEY2" ini-alist))


実行すると、以下のように出力します。
$ cat sample.ini | clisp inifile.l
((Section3) (Section2 (Key1 Value1)) (Section1 (Key1 Value1) (Key2 Value2)))
Section2 Key1 = Value1
SECTION1 KEY2 = Value2


工夫したところは、関数"ini-assoc"でstring-equalを使用し、大文字と小文字を区別し内容にしたところです。
alistで返すことが目標だったので、パフォーマンスはよくありません。
今までの書き方であれば、DictionaryやMapを更新しまくるプログラムを書いてました。reduceに渡す関数を実装するにあたり、「新しいリストを返す」という発想に辿り着くまで時間がかかりました。

0 件のコメント:

コメントを投稿