updated module: (database fcookie)
Thien-Thi Nguyen <[email protected]>
| Newsgroups | gmane.lisp.guile.user,gmane.lisp.guile.sources |
|---|---|
| Message-ID | <[email protected]> |
folks, this will appear in Guile 1.4.1.101. here is the ChangeLog entry: 2004-11-30 Thien-Thi Nguyen <[email protected]> * fcookie.scm: Use module (srfi srfi-56). No longer use module (database binconv). (read-word-proc): Rewrite. (write-word-proc): Likewise. let's hope srfi 56 finalizes w/ `read-network-uint32' and `write-network-uint32', otherwise an update will be required. happy fortune-cookie munching, thi _______________________________________________________________________ ;;; fcookie.scm --- cookie file read, cookie-index file read/write ;; Copyright (C) 2004 Free Software Foundation, Inc. ;; ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation; either version 2, or (at your option) ;; any later version. ;; ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; ;; You should have received a copy of the GNU General Public License ;; along with this software; see the file COPYING. If not, write to ;; the Free Software Foundation, Inc., 59 Temple Place, Suite 330, ;; Boston, MA 02111-1307 USA ;; ;; As a special exception, the Free Software Foundation gives permission ;; for additional uses of the text contained in its release of GUILE. ;; ;; The exception is that, if you link the GUILE library with other files ;; to produce an executable, this does not by itself cause the ;; resulting executable to be covered by the GNU General Public License. ;; Your use of that executable is in no way restricted on account of ;; linking the GUILE library code into it. ;; ;; This exception does not however invalidate any other reasons why ;; the executable file might be covered by the GNU General Public License. ;; ;; This exception applies only to the code released by the ;; Free Software Foundation under the name GUILE. If you copy ;; code from other Free Software Foundation releases into a copy of ;; GUILE, as the General Public License permits, the exception does ;; not apply to the code that you add in this way. To avoid misleading ;; anyone as to the status of such modified files, you must delete ;; this exception notice from them. ;; ;; If you write modifications of your own for GUILE, it is your choice ;; whether to permit this exception to apply to your modifications. ;; If you do not wish that, delete this exception notice. ;;; Commentary: ;; This module provides two procs: ;; (create-index-file! out-name in-name delim flags) => #t for success ;; (fortune-cookie cookie-filename [dat-filename]) => string ;; ;; A fortune cookie file is a text file containing groups of lines separated ;; by "delim lines", a specially chosen character (traditionally the percent ;; `%' character) on a line by itself. The first and last lines must also be ;; delim lines. Each group of lines thus delimited is a "cookie". Thus, a ;; file with N cookies has N+1 delim lines. ;; ;; A fortune cookie index file is a binary file consisting of a six-word ;; header followed by the offset table (each entry a word). A word is four ;; bytes in network (big-endian) order. The header is: ;; ;; version -- typically 1 for old files and 2 for newer ones ;; count -- number of cookies ;; longest -- number of bytes of longest cookie ;; shortest -- number of bytes of shortest cookie ;; flags -- logior of #x1 (random) ;; #x2 (ordered) ;; #x4 (rotated) ;; delim-char -- this is shifted to the MSB position ;; (the remaining bytes are #\nul) ;; ;; Traditionally, for cookie file foo, the index file is named foo.dat, but ;; that is not required. ;;; Code: (define-module (database fcookie) #:use-module ((ice-9 rdelim) #:select (read-line)) #:use-module ((srfi srfi-56) #:select (read-network-uint32 write-network-uint32)) #:use-module ((scripts slurp) #:select (slurp slurp-file!)) #:export (create-index-file! fortune-cookie)) (define sub make-shared-substring) (define (k name) (case name ((#:format-version) 2) ; hmmm ((#:sizeof-word) 4) ((#:all-flags) '(#:random #:ordered #:rotated)) ((#:random) #x1) ((#:ordered) #x2) ((#:rotated) #x4) ((#:bits-per-byte) 8) ; you never know... ((#:delim+newline) 2) ((#:all-headers) '(#:version #:count #:longest #:shortest #:flags #:delim #:filler)))) (define (read-word-proc port) (lambda () (read-network-uint32 port))) (define (write-word-proc port) (lambda (n) (write-network-uint32 n port))) (define (words<-cookie-file filename delim flags) (let* ((p (open-input-file filename)) (next (lambda () (read-line p 'concat))) (count -1) (shortest #f) (longest #f) (box (list #f)) (tp box) (order? (memq #:ordered flags))) ;; If `order?', accumulate (OFFSET . COOKIE-TRIMMED-TO-FIRST-ALPHANUMERIC), ;; then sort ascending. Otherwise, accumulate OFFSET only. (let loop ((line (next)) (acc '())) (cond ((eof-object? line) (close-port p) (list* ; rv ;; header (k #:format-version) count longest shortest (apply logior (map (lambda (flag) (if (memq flag flags) (k flag) 0)) (k #:all-flags))) (ash (char->integer delim) (* (1- (k #:sizeof-word)) (k #:bits-per-byte))) ;; offset table (if order? (map car (sort (cdr box) (lambda (a b) (string<? (cdr a) (cdr b))))) (cdr box)))) ((and (= (k #:delim+newline) (string-length line)) (char=? delim (string-ref line 0))) (let ((ofs (seek p 0 SEEK_CUR)) (prev (car tp))) (and prev (let ((len (- ofs (if order? (car prev) prev) (k #:delim+newline)))) (cond ((not shortest) (set! shortest len) (set! longest len)) (else (set! shortest (min shortest len)) (set! longest (max longest len)))))) (set-cdr! tp (list (if order? (cons ofs (let* ((full (apply string-append (reverse acc))) (len (string-length full))) (do ((i 0 (1+ i))) ((or (= len i) (let ((c (string-ref full i))) (or (char-alphabetic? c) (char-numeric? c)))) (sub full i))))) ofs)))) (set! tp (cdr tp)) (set! count (1+ count)) (loop (next) '())) (else (loop (next) (cons line acc))))))) ;; Create index file @var{out-name} from cookie file @var{in-name}, separating ;; cookies by looking for char @var{delim} on a line by itself. Optional ;; @var{flags} are keywords: ;; ;; @table @code ;; @item #:random ;; Set bit 0 (corresponding to a mask of #x1) in the flags word in the header, ;; but do nothing else at the moment (FIXME). ;; ;; @item #:ordered ;; Set bit 1 (corresponding to a mask of #x2) in the flags word in the header, ;; and order the offsets by sorting the cookies with @code{string<?}, ignoring ;; non-alphanumeric leading characters. ;; ;; @item #:rotated ;; Set bit 2 (corresponding to a mask of #x4) in the flags word in the header, ;; to note that the cookies are @dfn{ROT13}. ;; @end table ;; ;; Return #t on success. ;; (define (create-index-file! out-name in-name delim . flags) (let* ((words (words<-cookie-file in-name delim flags)) (outp (open-output-file out-name)) (ww (write-word-proc outp))) (for-each ww words) (close-port outp))) (define (grok-header readc readw) (let ((info (map cons (k #:all-headers) (list (readw) (readw) (readw) (readw) (let ((w (readw))) (let loop ((ls (k #:all-flags)) (acc '())) (if (null? ls) acc (let ((flag (car ls))) (loop (cdr ls) (if (= 0 (logand (k flag) w)) acc (cons flag acc))))))) (readc) (map (lambda ignored (readc)) (iota (1- (k #:sizeof-word)))))))) ;; rv (lambda (name) (assq-ref info name)))) (define (get-cookie cookie-file dat-file) (let* ((port (open-input-file dat-file)) (readw (read-word-proc port)) (qh (grok-header (lambda () (read-char port)) readw)) (selection (random (qh #:count))) (start (do ((i 0 (1+ i))) ((= i selection) (readw)) (readw))) (need-scan? (memq #:ordered (qh #:flags))) (len (if need-scan? (min (qh #:longest) (- (stat:size (stat cookie-file)) start)) (- (readw) start (k #:delim+newline)))) (cookie (make-string len))) (close-port port) (slurp-file! cookie cookie-file start len 0) (and need-scan? (let ((delim (qh #:delim)) (cookie-char=? (lambda (c n) (char=? c (string-ref cookie n))))) (let loop ((nl (string-index cookie #\nl 0))) (and nl (if (and (<= (+ nl (k #:delim+newline)) len) (cookie-char=? delim (1+ nl)) (cookie-char=? #\nl (+ nl 2))) (set! cookie (sub cookie 0 (1+ nl))) (loop (string-index cookie #\nl (1+ nl)))))))) (if (memq #:rotated (qh #:flags)) (list->string (let ((a-n (char->integer #\a)) (A-n (char->integer #\A)) (rot (lambda (base n) (integer->char (+ base (modulo (+ (- n base) 13) 26)))))) (map (lambda (c) (let ((n (char->integer c))) (cond ((char<=? #\a c #\z) (rot a-n n)) ((char<=? #\A c #\Z) (rot A-n n)) (else c)))) (string->list cookie)))) cookie))) ;; Return a randomly-chosen string extracted from @var{cookie-file}, ;; using the index file named by appending @file{.dat} to @var{cookie-file}. ;; Optional arg @var{dat-file} specifies the index file to use instead of the ;; default. ;; ;;-sig: (cookie-file [dat-file]) ;; (define (fortune-cookie cookie-file . dat-file) (get-cookie cookie-file (if (null? dat-file) (string-append cookie-file ".dat") (car dat-file)))) ;; Notes ;; ;; This module is actually a practice run (fodder for factoring) for some ;; future `(database isam)' module, since it does in fact implement indexed ;; sequential access methods --- not very sophisticated but it's a start. ;; And who can resist a fortune cookie? ;;; fcookie.scm ends here _______________________________________________ Guile-user mailing list [email protected] http://lists.gnu.org/mailman/listinfo/guile-user