zlib

何時も連絡を取り合ってる人から、連絡が無いんで、干からびていないか心配になって連絡してみた。朝の9時ごろ。

小声で、折り返すからって言われた。朝のお勤めでもしてたんかな?

暫くして、元気な声が聞こえてきた。図書館に居るとの事。みんな避難してるんで、早く行かないと駐車場が埋まっちゃうと言ってた。パチンコ屋に避難するよりは、よっぽど健康的かつ文化的かつ経済的。

飛んでる図書館で、視聴覚室はあるは、漫画はあるわとの事。人口が多い経済が豊な所は違うな。こちらは、そんな豪華なものは無いぞ。

ああ、隣町の図書館は、遊戯室が有って、寝転んでも良さそう。但し、回りのお母さんから顰蹙を買うだろね、場合によっては変質者と認定されて、通報されちゃったりして。

そんな事が無いように、お盆で帰省してくる、姪っ子の娘をだしにすればいいか。

gauche + rlwrap

前回調べておいた、gaucheとrlwrapを組み合わせるやつ、もう少し調べてみた。

補完用のデータは、

gosh -E"use gauche.interactive" -E"apropos #/.*/" -Eexit |tr "(" "\n" |tr -d " (" |sort |uniq  >gosh_symbols

のようにして作っている。後は出来上がったものを加工(しなくてもいいけど)して、.gosh_completionsとして置いておく。

sakae@usvr:~$ cat /usr/local/bin/goshr
#!/bin/sh
rlwrap -c -q '"' -b "'"'(){}[].,#@;|`"' -m gosh $@

後は、こういうので起動すると、補完他が使えるようになって幸せ。aliasに登録しようとしたら、微妙な問題で登録出来なかった。しょうがないから、goshrって名前のスクリプトにして登録しといた。この時、引数を受け付けるよう、goshの後ろに $@ を付けるのが味噌。

それはいいんだけど、シンボルを集めてきてうんぬんするのに、1行野郎が使われている。展開してみるかな。

gosh> (use gauche.interactive)
gosh> (apropos #/.*/)
 :
x->string                      (gauche)
zero?                          (scheme)
|+.|                           (gauche)
|-.|                           (gauche)
|setter of object-apply|       (gauche)
|setter of ref|                (gauche)
~                              (gauche)

なる程、カッコ内は、出身地になるのかな? ならば、出身地別にカウントしてみよう。

sakae@usvr:~$ gosh -E"use gauche.interactive" -E"apropos #/.*/" -Eexit | awk '{print($2)}' | sort | uniq -c
   1566 (gauche)
     30 (gauche.interactive)
     25 (null)
      2 of
    194 (scheme)
      4 (user)

出身地がnullってどういう事? 何か手がかりが無いか、流行りのBigData分析(そんなにないけど)してみる。

sakae@usvr:~$ gosh -E"use gauche.interactive" -E"apropos #/.*/" -Eexit | fgrep '(null)'
...                            (null)
=>                             (null)
and                            (null)
begin                          (null)
case                           (null)
cond                           (null)
define                         (null)
define-syntax                  (null)
delay                          (null)
do                             (null)
else                           (null)
if                             (null)
lambda                         (null)
let                            (null)
let*                           (null)
let-syntax                     (null)
letrec                         (null)
letrec-syntax                  (null)
or                             (null)
quasiquote                     (null)
quote                          (null)
set!                           (null)
syntax-rules                   (null)
unquote                        (null)
unquote-splicing               (null)

schemeの根幹を成すものが集まってた。こういうのscheme用語で、何と言ったでしょうか? 昔の本を漁ってみるかな。そして、(scheme)族は、R5RSに定義される奴かな?

それはそうと、もう一つの補完方法を見つけた。

Readline module for Gauche

sakae@usvr:~/Gauche-readline-20100625$ sudo make install
mkdir -p term
ln -sf ../readline.scm term/readline.scm
/usr/local/bin/gauche-install -m 644 -T /usr/local/share/gauche-0.9/site/lib/term readline.scm
/usr/local/bin/gauche-install -m 755 -T /usr/local/lib/gauche-0.9/site/x86_64-pc-linux-gnu term--readline.so
/usr/local/bin/gauche-install -m 644 -T /usr/local/share/gauche-0.9/site/lib/.packages Gauche-readline.gpd
/usr/local/bin/gauche-install -m 755 --shebang=/usr/local/bin/gosh gosh-rl.scm /usr/local/bin/gosh-rl

useしたモジュールが 補完候補に追加されるんで、便利。更にカッコの対応を表示するように、.inputrcに

set blink-matching-paren on

と、書いておくと良い。

このreadlineモジュールの作成方法が、Gaucheの良いunix側のモジュール使用の実例になっているので、じっくり見ておくと吉。

gauche on FreeBSD and OpenBSD

今の所、gaucheはウブとNetBSDで動いている。FreeBSDはどうか? clangの新版で問題ないか確認の意味も込めてやってみた。全く問題無し。開発チームの人にFreeBSD使いの人がいたのだろうか? それとも、Mac系で出た問題を潰していたら、自然とFreeBSDでも無問題になって行ったのかな?

余勢を駆って、readlineのモジュール作成も試してみる。 盛大にコンパイルエラーが出て来た。どうも、readline.hあたりが見当たらない疑惑。

FreeBSDは、readline系がOS備え付けではなく、pkg扱い。その関係で/usr/localの下に入ってる。しょうがないので、configure時に、includedirとかを設定したけど、反映されず。 今後の事も考えて、/usr/include/readline/にリンクを張っておいた。

そして、lreadlineも反映されていなかったので、Makefileに書き足した。

readline_SRCS     = readline.c readlinelib.stub
readline_LIBS     = -L/usr/local/lib -lreadline

使ってみる。callまで入力してTABキーを押すと、候補が出てくる。有り難い事です。

$ gosh-rl
WARNING: Quote character setting is not supported by the library.
gosh> (call
call-formatter                  call-with-iterators
call-macro-expander             call-with-output-file
call-syntax-handler             call-with-output-string
call-with-builder               call-with-port
call-with-current-continuation  call-with-string-io
call-with-input-file            call-with-values
call-with-input-string          call/cc
call-with-iterator              call/pc

emacsから、M-x run-scheme して、出て来るセッション窓に、このgosh-rlが使えないか試してみたけど、補完は効かなかった。emacsが提供する窓は、馬鹿窓で、画面制御が出来ないんで、自粛しちゃってるんだな。残念。

更に余勢を駆って、OpenBSDでgosh出来るか試してみる。過去には、OpenBSDの特殊性を回避する為のパッチが存在してたけど、それが反映されてるかも知れないからね。

コンパイルは無事に通った。testで、srfiの所でtrapするのと、controlのテストの所で永久ループに入っちゃうという不都合が発生。コンパイルが通るようになったのは、きっとgcの版が上がったからに違いない。

ob6$ gosh srfi.scm
  :
<srfi-155>---------------------------------------------------------------------
testing bindings in #<module srfi-155> ... ok
<SRFI 155: Promises>-----------------------------------------------------------
test (force (delay (+ 1 2))), expects 3 ==> Abort trap (core dumped)

頓死検案書

ob6$ gdb -q gosh gosh.core
Reading symbols from gosh...done.
[New process 245297]
Core was generated by `gosh'.
Program terminated with signal SIGABRT, Aborted.
#0  thrkill () at -:3
3       -: No such file or directory.
(gdb) bt
#0  thrkill () at -:3
#1  0x00000bf0a29a00de in _libc_abort () at /usr/src/lib/libc/stdlib/abort.c:51
#2  0x00000bf0a29ad3d6 in _libc_pthread_mutex_unlock (mutexp=<optimized out>)
    at /usr/src/lib/libc/thread/rthread_mutex.c:266
#3  0x00000bf0ce08bb45 in force_cc (result=<optimized out>,
    data=<optimized out>) at lazy.c:166
#4  0x00000bf0ce037610 in run_loop () at ./vmcall.c:196
#5  0x00000bf0ce0379b6 in user_eval_inner (
    program=0xbf0ce577a20 <internal_apply_compiled_code>,
    codevec=0x7f7ffffbd470) at vm.c:1489
#6  0x00000bf0ce03890c in apply_rec (vm=<optimized out>, proc=<optimized out>,
    nargs=<optimized out>) at vm.c:1582
#7  0x00000bf0ce08a79c in Scm_Load (cpath=<optimized out>,
    flags=<optimized out>, packet=0x7f7ffffbd650) at load.c:223
#8  0x00000bee5af00ad1 in execute_script (scriptfile=0x0, args=0xbee5b36d4d0)
    at main.c:550
#9  0x00000bee5af019e9 in main (ac=1, av=0x7f7ffffbd890) at main.c:751

もう一つの不具合

ob6$ gosh control.scm
 :
test shutdown - raising <thread-pool-shutting-down>, expects #<<thread-pool-shut-down>> ==> ok
test shutdown - killing a job in the queue, expects killed ==> ok
test shutdown check, expects finished ==> ok
ここで、黙りこんでしまう。別端末から、kill -9 gosh-pid
killed

テストの不都合報告が無いのは、OpenBSDが辺境のOS。そしてgaucheを使ってみようと言う人は 更にいないからだろうね。ああ、あの人なら使いそうかな。

copipeP.scm

前々回にやった、『cut比べ』に出て来た、ファイルが似てる度を数値で表すshell語を、scheme語に変換してみたい。scheme語ってのは曖昧過ぎるな。正しくはgauche語だ。上でも見たけど、schemeの仕様に準拠してるのはほんのわずか。後は、現場で鍛えられ、使い易い手続きがわんさかと実装されてるからだ。

だから、gauche語で書かれたコードを他のschemeを標榜してるシステム(有名な所では Racketあたり)に持って行っても、動かすには大変な苦労を伴うだろう。ようするに、gaucheを使うことは、ロックインされちゃうって事。それを心得て使えば、気持ちよくコードが書けるだろう。

そうは言っても、すっかりgaucheとはご無沙汰になってるので、少しgauche語で書かれた資料(俗に言うCookbook)を探してみた。

Gauche:Cookbook

scheme ( Gauche ) の書き捨てプログラム集

以前に書いたshell語のコードを再掲。これをgauche語に変換する。

#!/bin/sh
# Usage: copipeP.sh ref-file cmp-file

a=$1
b=$2

saa=`cat $a $a | wc -c`
zaa=`cat $a $a | gzip -c | wc -c`
faa=`echo "$zaa * 10000 / $saa" | bc`

sab=`cat $a $b | wc -c`
zab=`cat $a $b | gzip -c | wc -c`
fab=`echo "$zab * 10000 / $sab" | bc`

echo "Same file : 100,  Differ file : > 100"
echo "$fab * 100 / $faa" | bc

オイラー的に欲しいのは、ファイル名を指定したら、それを読んで文字列にしてくれる手続きと、指定した文字列をgzipで圧縮して文字列を返してくれる手続きだな。

さっそくCOOKPADを探してみる。

(call-with-input-file "a.scm" port->string)

素晴らしいのが見つかった。

Scheme の入出力 (その1) と、 Scheme の入出力 (その2) にあるように、gaucheは、portと言うのが、過剰に発達してて便利に使える。上記もその例。

次は、gzipの扱い。unix側にあるgzipに一度文字列を投げて、圧縮されたデータを受け取るってのを考えた。けど、きっとgauche内からそんな事が出来る仕掛けが有るに違いない、と思って探してみたよ。

12.43 rfc.zlib - zlib圧縮ライブラリ

最初の所は、眠くなるような説明が続く。deflating/inflating 圧縮/展開ですって。これって、デフレとかインフレの経済用語に通じるのかしらん。

じっと我慢の子で流し読みしていくと、

gosh> (use rfc.zlib)
gosh> (define z (gzip-encode-string "hello hwllo fufu"))
#*"\x1f;\x8b;....;\0\0\0"
gosh> (gzip-decode-string z)
"hello hwllo fufu"

使えそうなのが出て来た。さあ、材料は揃ったぞ。

sakae@fb:~ % cat copipeP.scm
; Usage: gosh copipeP.scm ref-file cmp-file
(use rfc.zlib)

(define (calc a b)
  (let* ([ss (string-append a b)]
         [sz (string-length ss)]
         [zz (string-length (gzip-encode-string ss))])
    (/ zz sz)))

(define (main argv)
  (let ([a (call-with-input-file (list-ref argv 1) port->string)]
        [b (call-with-input-file (list-ref argv 2) port->string)])
    (print (div (* (calc a b) 100) (calc a a)))
    0))

オリジナルは、同じような事を繰り返していたので、手続きにまとめてみた。圧縮比の計算で、オリジナルはbcでの整数演算だったので、桁落ちを防ぐため、分子を10000倍してた。schemeは、整数の割り算は有理数になるんで、気にする事は無い。 その代わり、ユーザーに見せる結果の割り算の所では、divを使って整数に丸めた。

sakae@fb:~ % gosh copipeP.scm li.c li.c
100
sakae@fb:~ % gosh copipeP.scm li.c fb.c
197

以前やった、linuxのcut.cとFreeBSDのcut.cの似てる度。当然、同じ結果になった。

sakae@fb:~ % gosh build-standalone copipeP.scm
cc -fPIC  -I/usr/local/lib/gauche-0.9/0.9.6/include -o copipeP copipePWFW9aH.c -L/usr/local/lib/gauche-0.9/0.9.6/x86_64-unknown-freebsd11.2 -lgauche-static-0.9  -lz -lcrypt -lutil -lrt -lm -pthread
sakae@fb:~ % ./copipeP li.c fb.c
197

そしてコンパイルしてみた。15Mのサイズになったぞ。

gzip-encode-string

今回お世話になった、gzip-encode-stringが、どのように実装されてるか? 答えはtar玉を展開したext/zlib/zlib.scmに有った。

;; utility procedures
(define (deflate-string str . args)
  (call-with-output-string
    (^p (let1 p2 (apply open-deflating-port p args)
          (display str p2)
          (close-output-port p2)))))

(define (inflate-string str . args)
  (port->string (apply open-inflating-port (open-input-string str) args)))

(define (gzip-encode-string str . args)
  (call-with-output-string
    (^p (let1 p2 (apply open-deflating-port p :window-bits (+ 15 16) args)
          (display str p2)
          (close-output-port p2)))))

(define (gzip-decode-string str . args)
  (port->string (apply open-inflating-port
                       (open-input-string str)
                       :window-bits (+ 15 16)
                       args)))

見慣れない、^p って何? すかさずinfoを引く。

 -- Macro: ^c body ...
     `(lambda (C) BODY ...)'の短縮表記です。 `c'には`#[_a-z]'に含まれる
     任意の一文字が使えます。

          (map (^x (* x x)) '(1 2 3 4 5)) ⇒ (1 4 9 16 25)

lambdaは、空気のようにあちこちに出て来るので、便利に書けるように。 なんでもλ

zlib

今回出てきた圧縮・伸長の大本、libzは、 圧縮アルゴリズム と、 SICPの例(2.3.4)に出て来るハフマン符号を組み合わせたものだという。

詳しくは、zlib.hを見てねって、shiroさんも言ってる。manでも言ってる。zlib(3)を見ると、

       All functions of the compression library are documented in the file
       zlib.h.  The distribution source includes examples of use of the
       library in the files test/example.c and test/minigzip.c, as well as
       other examples in the examples/ directory.

論よりsourceって事で、サンプルが提供されてる。FreeBSDだと寄贈ソフトの所に置いてある。 minizpi.cを試してみるか。

sakae@fb:/tmp % cc -g -O0 minigzip.c
/tmp/minigzip-1ed379.o: In function `gz_compress':
/tmp/minigzip.c:384: undefined reference to `gzwrite'
/tmp/minigzip.c:384: undefined reference to `gzerror'
/tmp/minigzip.c:387: undefined reference to `gzclose'
  :
cc: error: linker command failed with exit code 1 (use -v to see invocation)
sakae@fb:/tmp % cc -g -O0 minigzip.c -lz

リンクを忘れると盛大にエラーが出て来る。今回はclangさんから、あれリンクするといいよってのが出てこなかった。気まぐれなんか。

NetBSDのzlib(3)には

   Basic functions

     int
     deflateInit(z_streamp strm, int level);

     int
     deflate(z_streamp strm, int flush);

   Advanced functions
     int
     deflateInit2(z_streamp strm, int level, int method, int windowBits,
         int memLevel, int strategy);

   Utility functions
     typedef voidp gzFile ;

     gzFile
     gzopen(const char *path, const char *mode);

zlib.hを見ろなんていう不親切(手抜き)な説明はしておらず、最初に関数の列挙、続いて各関数の説明、構造体の説明と、至れり尽くせりの親切さが有ったぞ。

zlib@python

gaucheもCも通な人が使う。じゃ、普通な人はどうするの? そりゃ猫も杓子もpython3でしょ。zlibサポートしてるんか?

In [2]: help("zlib")
    :
DESCRIPTION
    The functions in this module allow compression and decompression using the
    zlib library, which is based on GNU zip.

    adler32(string[, start]) -- Compute an Adler-32 checksum.
    compress(data[, level]) -- Compress data, with compression level 0-9 or -1.
    compressobj([level[, ...]]) -- Return a compressor object.
    crc32(string[, start]) -- Compute a CRC-32 checksum.
    decompress(string,[wbits],[bufsize]) -- Decompresses a compressed string.
    decompressobj([wbits[, zdict]]]) -- Return a decompressor object.

    'wbits' is window buffer size and container format.
    Compressor objects support compress() and flush() methods; decompressor
    objects support decompress() and flush().

もっと分かり易く 13.1. zlib — gzip 互換の圧縮 更に例もあると嬉しい? 13.2. gzip — gzip ファイルのサポート

原典に当たる

sakae@fb:~ % rfc 1952
The Result:
1952 GZIP file format specification version 4.3. P. Deutsch. May 1996.
     (Format: TXT=25036, PS=43337, PDF=43211 bytes) (Status:
     INFORMATIONAL) (DOI: 10.17487/RFC1952)

RFC 1952 GZIP file format specification version 4.3 日本語訳

gosh-rl @ NetBSD

NetBSDでもgoshで補完が出来ると嬉しいな。んな訳で作ってみた。勿論元となるreadlineは、変な所に置いてあったんだ。

nb$ locate readline.h
/usr/include/readline/readline.h
/usr/pkg/include/readline/readline.h
/usr/pkgsrc/graphics/gnuplot/patches/patch-src_readline.h
/usr/pkgsrc/net/tnftp/files/libedit/readline/readline.h
/usr/share/man/html3/openpam_readline.html
/usr/src/lib/libedit/readline/readline.h
nb$ locate readline.so
/usr/pkg/lib/libreadline.so
/usr/pkg/lib/libreadline.so.7
/usr/pkg/lib/libreadline.so.7.0.0

ヘッダーは、リナ系を斟酌して普通の所にも置いてあった。けど、soなファイルは、特殊領域にしか置かれていない。それを例の方法で吸収して、コンパイルを成功させた。

いざ使用しようとしたら、readline.soが見つからないと言われた。 一応リンク関係を確認。

nb$ ldd term--readline.so
term--readline.so:
        -lgauche-0.9.0.6 => not found
        -lcrypt.1 => /usr/lib/libcrypt.so.1
        -lgcc_s.1 => /usr/lib/libgcc_s.so.1
        -lc.12 => /usr/lib/libc.so.12
        -lutil.7 => /usr/lib/libutil.so.7
        -lrt.1 => /usr/lib/librt.so.1
        -lm.0 => /usr/lib/libm.so.0
        -lpthread.1 => /usr/lib/libpthread.so.1
        -lreadline.7 => not found

なにこれ? readlineもgaucheも見えていないじゃん。まてまて、goshは起動してるんで、見えているはずだけど。はて、どうする? 昔の記憶を手繰り寄せて、ld.so.confを呼び覚ました。

nb$ cat /etc/ld.so.conf
/usr/pkg/lib
/usr/local/lib

このファイルを作るだけで、下記のように認識された。

nb$ ldd term--readline.so
term--readline.so:
        -lgauche-0.9.0.6 => /usr/local/lib/libgauche-0.9.so.0.6
        -lcrypt.1 => /usr/lib/libcrypt.so.1
        -lgcc_s.1 => /usr/lib/libgcc_s.so.1
        -lc.12 => /usr/lib/libc.so.12
        -lutil.7 => /usr/lib/libutil.so.7
        -lrt.1 => /usr/lib/librt.so.1
        -lm.0 => /usr/lib/libm.so.0
        -lpthread.1 => /usr/lib/libpthread.so.1
        -lreadline.7 => /usr/pkg/lib/libreadline.so.7
        -lterminfo.1 => /usr/lib/libterminfo.so.1

正式には、どういう手を打つのだろうか?

etc

LispKit Lisp 2 - SECD 機械 in C