とりあえずいい加減なパーサがこんな感じ。
(import (rnrs)
(packrat)
(srfi :14 char-set))
(define *text-set*
(ucs-range->char-set! #x2d #x7e #f
(ucs-range->char-set! #x23 #x2b #f
(ucs-range->char-set #x20 #x21))))
(define (any results)
(let loop ((acc '()) (results results))
(let ((ch (parse-results-token-value results)))
;; it's just testing
(if (and ch (char-set-contains? *text-set* ch))
(loop (cons ch acc) (parse-results-next results))
(make-result (list->string (reverse! acc)) results)))))
(define (textdata results)
(any results))
(define parser
(packrat-parser
(begin
(define (crlf results)
(let ((ch (parse-results-token-value results)))
(case ch
((#\linefeed) (make-result "" results))
((#\return)
(let ((ch (parse-results-token-value (parse-results-next results))))
(if (char=? #\linefeed ch)
(make-result "" results)
(field-entry results))))
(else (field-entry results)))))
file)
(file ((h <- header '#\linefeed r <- records) (cons h r))
((r <- records) r))
(header ((n <- names) (cons :header n)))
(records ((f <- fields '#\linefeed r <- records) (cons (cons :record f) r))
((f <- fields) (list (cons :record f))))
(fields ((f <- field-entry '#\, fs <- fields) (cons f fs)) ;; (1)
((f <- field-entry) (list f)))
(names ((n <- field-entry '#\, ns <- fields) (cons n ns))
((n <- field-entry) (list n)))
(field-entry ((e <- textdata) e))))
(define (generator p)
(let ((ateof #f)
(pos (top-parse-position "")))
(lambda ()
(if ateof
(values pos #f)
(let ((x (read-char p)))
(if (eof-object? x)
(begin
(set! ateof #t)
(values pos #f))
(let ((old-pos pos))
(set! pos (update-parse-position pos x))
(values old-pos (cons x x)))))))))
(define (parse-csv p)
(let ((result (parser (base-generator->results (generator p)))))
(if (parse-result-successful? result)
(parse-result-semantic-value result)
(apply assertion-violation
(let ((e (parse-result-error result)))
(list 'parse-csv
(parse-error-messages e)
(parse-position->string (parse-error-position e))
(parse-error-expected e)))))))
(call-with-input-file "test.csv"
(lambda (p)
(parse-csv p))))
#|
入力ファイル
aaa,bbb
ccc,ddd,eee
fff,ggg,hhh
a,b,c
出力
((:header "aaa" "bbb")
(:record "ccc" "ddd" "eee")
(:record "fff" "ggg" "hhh")
(:record "a" "b" "c"))
|#
generatorはドキュメントにあったものをちょっと改変しただけ。多分これが基本なんだろう。かなり適当で本来はCRLFなのがLFだけだったり、エスケープされたものを認識しなかったりするがまぁ動く。気になったのは「/」の使い方で、こんな感じには使えないという不便さであった。
(entry (((/ (e <- escaped) (ne <- non-escaped))) (if e e ne)))展開されたコードをみたらまぁ納得なのだが、これがやれないとなると結構面倒だよなぁ。あとこのライブラリは「*」とか「+」とか「?」がないので、それらを書こうと思ったら、(1)のように条件を2つ作る必要がある。
やっぱり正規表現をつかってやるべきだろうか。
No comments:
Post a Comment