似てる、似てない?(3)

女房が実家から柿を取ってきた。余りに見事に実っているので嬉しくなって取ってきたとか。 鳥さんのデザートになる前に回収したんだとかも言ってた。まだ渋くないかね? 鳥さんだって まだ手(口ばしを)を出していないだろうに! 鳥さんは人間よりも、きっと食べごろを知ってるに 違いない。

折角取ってきたものだから、食べてみましたよ。まだ硬いね。甘い事は甘いけど、後味に渋さが 残る。渋さは姿形だけでいいのに。まあ、国民服のユニクロなんかを着てたら、渋さとは程遠い と思われますがね。勿論、島村とかでもだめです。ああ、話が逸れた。

柿を取る時、高い所のは取れないので、手を伸ばして届く範囲のものにしたとか。太陽に よく当たっていないのもあるんだろうな。天辺付近の太陽が十分に当たった所から取っていくのが 良いと思われ。もう少し熟成させておいて、12月になったら、梯子をかけて本格的に採取 しよう。そして近所にお裾分けだな。 少しは、鳥さん用に残しておいてあげよう。頑張って取っても、食べきれないから。実を残すのには もう一つ理由が有る。来年の豊作をお願いする、木守の役目を務めて貰うためだ。どちらが 主目的かは分からないけど、自然と共に生きる知恵が伝えられていると思うぞ。

干し柿は、甘い柿じゃ作れんしなあ。クックパッドで、柿のデザートでも検索してみっかな。 柿オレなら、簡単そう。アイデアだなあ。 ああ、クックパッドと言えば、元中学生のrubyコミッタが入社して頑張っているんだな。こちらの Web Serverは、nginx みたいだけど、ひょっとして彼がsetupしてたりして。

クックパッドは野菜便と称して、とくしまマルシェを 始めたみたいだ。おいらなんかは、わざわざ徳島品を御取り寄せしなくても、近くの野菜マルシェ (と言う無人販売所)へ行けば、いつでも朝取り野菜が手に入るからね。この間は、銀杏が出てた なあ。

取立てほやほやで、例のあの強烈な臭いが残ってたよ。けど、フライパンで炙ればそんな臭いも 飛んじゃうし、後は美味しい実が残る。秋の味覚だなあ。

TODOの消化

前回は、思わぬ方向に逸れてしまったので、本道に戻ります。cflowを軸にして、似てる/似てないを ライブラリィーの呼び出し回数を調べて判定しようとしてた。書いたスクリプトが、汎用に なってなかったので、改良点をTODOとして何点か上げておいたのだ。

最初に、楽な方をやってみる。コマンドラインから、解析対象を指定。cflowに渡すオプションも コマンドラインから渡す。このスクリプトは、仕様上cflowに一つのCファイルしか渡さないので、 複数のCファイルのそれは、あらかじめ作っておいたcflowの結果を渡せるようにする。

#!/usr/local/bin/ruby

def hdlopt()
  opt  = ''
  opt = ARGV.shift if ARGV[0][0] == '-'
  list = ARGV
  return list,opt
end

def col(fc, db, opt)
  p = fc[-2,2] == '.c' ?
        IO.popen("cflow #{opt} -c #{fc}") : open(fc)
  while al = p.gets
    db[$& + fc] += 1  if al =~ /\w+:/  # Key = func_name : file_name
  end
  p.close()
end

def show(list, ef, fn)
  printf("%16s", fn)
  list.each do |sn|
    if ef.key?(sn)
      printf("\t%3d", ef[sn])
    else
      printf("\t%s", '---')
    end
  end
  puts ''
end

def ttl(list)
  printf("%16s", "Function")
  list.each {|sn| printf("\t%s", sn)}
  puts ''
end

### main HERE ###################
db = {}; db.default = 0
list,opt = hdlopt()
list.each{|fc| col(fc, db, opt)}

ofn = ""
ef = {}
ttl(list)
db.keys.sort.each do |k|
  fn,sn = k.split(':')
  if fn != ofn
    show(list, ef, ofn) if ofn != ''
    ofn = fn
    ef.clear
  end
  ef[sn] = db[k]
end
show(list, ef, ofn)
ttl(list)

前回からの変更点は軽微なものだ。早速実行してみる。

[sakae@secd ~/z]$ ./col.rb -ACGP *.c
        Function        F.c     L.c     N.c     O.c
      arc4random          1     ---     ---       1
           error        ---      15     ---     ---
            errx         18     ---      14      19
         getargs        ---       1       2     ---
       getformat          1       1       1       1
         getprec          2       2       2       2
     getprogname        ---     ---       1     ---
      is_default          4     ---       4       4
       isdefault        ---       5     ---     ---
            main          1       1     ---       1
         putdata          2       2       2       2
         strlcat          1     ---     ---       2
         strlcpy          3     ---       1       4
        strtonum        ---     ---     ---       1
           usage          2     ---       3       2
           warnx        ---     ---     ---       1
        Function        F.c     L.c     N.c     O.c

それでは、複数のファイルから構成するやつをば、まずは、dc

[sakae@secd /usr/src/usr.bin/dc]$ cflow -c *.c > ~/z/dc.f

続いて、bcなんだけど、

[sakae@secd /usr/src/usr.bin/bc]$ ls
Makefile        bc.library      extern.h        scan.l
bc.1            bc.y            pathnames.h

Cのソースの元しか無いので、行儀が悪いけど、この場所で展開してあげる。

[sakae@secd /usr/src/usr.bin/bc]$ sudo make
Warning: Object directory not changed from original /usr/src/usr.bin/bc
yacc -d -o bc.c bc.y
yacc: 1 shift/reduce conflict
yacc: 16 reduce/reduce conflicts
cc -O2 -pipe  -I. -I/usr/src/usr.bin/bc -std=gnu99 -fstack-protector -Wsystem-headers -Werror -Wall -Wno-format-y2k -W -Wno-unused-parameter -Wstrict-prototypes -Wmissing-prototypes -Wpointer-arith -Wreturn-type -Wcast-qual -Wwrite-strings -Wswitch -Wshadow -Wunused-parameter -Wcast-align -Wchar-subscripts -Winline -Wnested-externs -Wredundant-decls -Wold-style-definition -Wno-pointer-sign -c bc.c
lex -t  scan.l > scan.c
cc -O2 -pipe  -I. -I/usr/src/usr.bin/bc -std=gnu99 -fstack-protector -Wsystem-headers -Werror -Wall -Wno-format-y2k -W -Wno-unused-parameter -Wstrict-prototypes -Wmissing-prototypes -Wpointer-arith -Wreturn-type -Wcast-qual -Wwrite-strings -Wswitch -Wshadow -Wunused-parameter -Wcast-align -Wchar-subscripts -Winline -Wnested-externs -Wredundant-decls -Wold-style-definition -Wno-pointer-sign -c scan.c
cc -O2 -pipe  -I. -I/usr/src/usr.bin/bc -std=gnu99 -fstack-protector -Wsystem-headers -Werror -Wall -Wno-format-y2k -W -Wno-unused-parameter -Wstrict-prototypes -Wmissing-prototypes -Wpointer-arith -Wreturn-type -Wcast-qual -Wwrite-strings -Wswitch -Wshadow -Wunused-parameter -Wcast-align -Wchar-subscripts -Winline -Wnested-externs -Wredundant-decls -Wold-style-definition -Wno-pointer-sign  -o bc bc.o scan.o -ledit -ltermcap
gzip -cn bc.1 > bc.1.gz

Cファイルが2個生成されたので、cflowしてみる

[sakae@secd /usr/src/usr.bin/bc]$ cflow -c bc.c scan.c > ~/z/bc.f

同じような要領で、gccをやってみると

[sakae@secd /usr/src/contrib/gcc]$ cflow -c *.c > ~/z/cc.f
profile.c: Brace level mismatch at line 802

Uum ... どちらのBugなんでしょ。深入りは止めて、出来た所だけで

[sakae@secd ~/z]$ ./col.rb bc.f dc.f
        Function        bc.f    dc.f
         BN_init        ---       1
         BN_zero        ---       1
          ENCODE          2     ---
         S_ISDIR        ---       1
      array_node          7     ---
           :
     yygrowstack          4     ---
           yylex          2     ---
         yyparse          1     ---
          yywrap          1     ---
        Function        bc.f    dc.f

水と油ぐらい異なってますなあ。目指す機能は同じなのに。。。

名称
       bc - 任意精度の計算言語
名称
       dc - 任意精度の計算機

ちゃんとmanしてみると、dcはHPの逆ポーランド電卓系で、bcの方は、無限精度で計算が出来る プログラミング可能な電卓系でしたよ。

CRUD

もう一つのTODO、FreeBSDの各コマンドの成分分析表の作成と閲覧。一遍に作成ってのはちと無謀な 気がするので、気が向いた時に少しづつ作成出来るようにしよう。そう大方針を立てると、今までに 分析したデータを保存しとく必要がある。必然的にデータベースっぽくなる訳だ。

難しい事は考えずに、CRUD(造る、読む、更新する、削除する)の機能が有ればいいんだ。 今流行りの、key valuse store なんだけど、簡単に使えるのあるかな? rubyのライブラリーを 覗いたら、DBM.so が入っていたんで、

require 'dbm'

DBFILE='DBFILE'

db = DBM.open(DBFILE)
  db['hoge']=3210
  db['hoge'] += 1
  p db['hoge']
  db.delete('hage')
db.close

でも、上記を予備実験で走らせるとエラー発生。よくよく調べたら、キーもバリューも、文字列って 言う制限があるのね。はて、どうしよう。3秒考えたら、以前にPythonでピクルスっていう、何でも 保存しちゃうのが有った事を思い出した。

Rubyにも対抗上、同様な機能が有るよね。ライブラリーを調べてみたら、Pstoreなんてのが 入っていたよ。今回はこれを使ってみよーっと。標準添付品だし、問題が出たら堂々とmatzさん 一家に文句を言えるからね。こういう考え方は、MySQLは止めてボラクル使っとけてのと一緒だな。 まあ、ボラクルはブランドで維持費をしっかり取られるけど、担当者は安泰!

アプリは、作成系と参照系の2本だてにする。この方が使い勝手が良いと思ったからだ。 まずは、解析データを貯める方。

まずアプリの名前を決めんとな。crudからrを抜いて、cud.rbでもいいんだけど、何となくreg.rb 決定。これ、registerからのパクリね。早まって、posなんて付けてはいけません。コンビニで バイトしてる人はposに馴染みがあるだろうけどね。

そうすると、参照系は、r.rbとなりそうだけど、某Rと被るので却下。regに対抗してacc.rbに しましょ。決してアキュムレータからの連想ではありません。アクセスからのパクリです。

そんじゃ、reg.rbの方から

#!/usr/local/bin/ruby

require 'pstore'
DBFILE='/home/sakae/z/FreeBSD.dat'

def hdlopt()
  opt  = ''
  if ARGV[0] == nil
    puts "Usage: reg.rb [--create] [-d] sorce ..."
    exit
  end
  opt = ARGV.shift if ARGV[0][0] == '-'
  list = ARGV
  return list,opt
end

def col(fc, db, opt)
  if Dir.glob(fc + '/*.c').size == 0
    puts "#{fc} ... ignore"
    return
  end
  p = IO.popen("cflow #{opt} -c #{fc}/*.c")
  while al = p.gets
    db[$& + fc] += 1  if al =~ /\w+:/  # Key = func_name : file_name
  end
  p.close()
end

def makedb(pb)
  pb.transaction do
    pb["roots"] = {}
  end
end

def deldb(pb,list)
  pb.transaction do
    hh = pb["roots"]
    hh.keys.each do |s|
      fn,sn = s.split(':')
      hh.delete(s) if list.include?(sn)
    end
  end
end

def adddb(pb, db)
  pb.transaction do
    hh = pb["roots"]
    hh.merge!(db)
  end
end

### main HERE ###################
list,opt = hdlopt()

pb = PStore.new(DBFILE)

if opt == '--create'
  makedb(pb)
elsif opt == '-d'
  deldb(pb, list)
else
  db = {}; db.default = 0
  list.each{|fc| col(fc, db, opt)}
  adddb(pb, db)
end

全くもって、おいらのスクリプトには、クラスなんて考えるのが面倒そうなものは出てきません。 (だったら、こういうのを勉強して、 是非ともクラス図やシーケンス図や、UML図を描く練習しれ)

行き当たりばったりの作です。Usageが変な所に埋め込まれているのを笑ってください。 いきがかり上、突っ込んで有ります。(本当は引数無しで実行した時のエラー防止)

使い始める前に一度だけ、--createを付けて実行してください。FreeBSD.datと言うpstore用の DBファイルが用意されます。すでにデータが溜まっているDBファイルが有っても、問答無用で 初期化されます。だから、オプション名を長くしてあります。こうしとけば、手が滑って クリアしちゃったって事も無いでしょ。

オプションは、もう一つ有って、-dがそれになります。こちらは、登録しておいたデータを 指定して削除するものです。データは、FreeBSDのコマンド単位に指定します。

オプション無しでは、データ名と言うかコマンド名を指定します。この時、データと言うかソースを 解析して登録してくれます。

[sakae@secd /usr/src/bin]$ ls
Makefile        date/           hostname/       pkill/          rmdir/
Makefile.inc    dd/             kenv/           ps/             setfacl/
cat/            df/             kill/           pwait/          sh/
chflags/        domainname/     ln/             pwd/            sleep/
chio/           echo/           ls/             rcp/            stty/
chmod/          ed/             mkdir/          realpath/       sync/
cp/             expr/           mv/             rm/             test/
csh/            getfacl/        pax/            rmail/          uuidgen/
[sakae@secd /usr/src/bin]$ ~/z/reg.rb [a-z]*
expr ... ignore
pax/ar_io.c: Brace level mismatch at line 411
rmail ... ignore

これ、FreeBSDの/bin直下に有るコマンドのソースエリアです。ここに移動して登録してみました。 コマンド名 == dir名 ですんで、引数としてこのdir名を与えています。 expr等にはCのソースが無いので、解析と登録が行われません。

また、根元で使っているcflowは、main関数を見つけられないと解析を諦めてしまうようで、csh みたいに、本体のソースが入っていないと、エラーも出ずに登録も行われないと言う挙動に なります。

余談だけど、cshの本体は contrib/tcshに有りました。cshのdirの中には、iconv関係のソースが ぽつんと置かれているだけでした。(コンパイル手順書のMakefileは勿論入ってます)

いろいろな場所(/usr/src/usr.sbinとか)に移動しながら蜜を集めてきたら、FreeBSD.datの サイズは、570k ぐらいになりました。

今回、pstoreの扱い方を漁っていた時、 Webと連携してる人が居て、やはりサイズを気にされて ました。pstore.rbを覗いてみると、簡易wikiの例が入っていて、やはりWebかと思っちゃい ますね。

そうそう、irbを使って対話的にpstoreと格闘する技が紹介されたので、再掲しとく。

$ irb
> require 'pstore'
> db = PStore.new("cache.pstore")
> table = nil
> db.transaction(true) { table = db.instance_variable_get("@table") }
> table.keys

これで安心。これから、アクセス系のacc.rbを作るよ。

pstoreの例で参考した先人様

Perlに対抗してpstore

PStoreのtransactionメソッドのようなもの