Inside newLISP

タイトルをちょっと変えてみた。今までのnewLISPの内部ってのよりカッコ良いでしょ。 それって、Inside MACのパクリでねぇかよ。正にその通り。

Insideって辞書を引くと、内側の、内部の、秘密の、内幕、はらわたなんてのに混じって 刑務所に入ってなんてのが列挙されてた。

刑務所に入ってって言葉、Lispにも有ったね。インターンってのが。元は収監するって意味 だけど、Lisp界隈では、名前を登録するって言うのに当てている。 前回やった、シンボルの登録がそれだ。業界用語ってのは隠語だな。

普通の人が聞いたら、あらあの人、どんな悪い事をしたんでしょう? ストーカー? 痴漢? 盗撮?結婚詐欺? それってお前の深層心理を表していないかい! この間、そんな事も あろうかと、警察の家庭訪問を受けたぞ。

怪しい人がいませんか?家庭内暴力はありませんか?って、そこはかとなく観察された雰囲気。 聞けば、前任者の移動で、新たにこの地区の担当になったとか。ご挨拶を兼ねて、各家庭を 回っておりますとか。

緊急連絡先を聞かれて、とっさに電話番号を思い出せず、中にいた女房に、おーい、何さんって 聞いたら、それを捉えて、家庭内円満で○って評価だそうだ。しっかり観察してますなあ。

防犯でベランダに放置してる自転車の指摘を受けた。ちゃんと鍵はかけて、鍵は保管しといて 下さいねって。鍵つけっぱなしの状態だったからね。パンクして直すもの面倒だから、 持って行って欲しいって対抗したら、苦笑いしてたな。

話は戻って、業界用語と言うか、なんと言うか。安倍ちゃん、戦後70年のレジューム からの脱却って言葉、止めません。還暦のオイラーには意味不なんですけど。。。。

この際だから調べておく。 戦後レジーム カッコよく言って、煙に巻く(だます)手口だな。還暦年代には、戦後維新とか言えよ。 それじゃ、あの党とかぶるか。

マクロ

前々回だかにやったdumpです。指定したCELLの各構造体のエレメントがどうなってるか、 ユーザーレベルで確認出来るやつ。左から順にCELLのアドレス、TAG、次のCELLのアドレス、 補助データ、データって並びだった(はず)

> (setq aa "Hello newLISP")
"Hello newLISP"
> (dump aa)
(680151520 260 680144896 14 679862608)

dumpったら、hexが常識でしょってんで、こんな実験もしたね。

> (map (fn (v) (format "0x%lX" v)) (dump aa))
("0x288A49E0" "0x104" "0x288A3000" "0xE" "0x2885E150")

じゃ、次はそれを関数にまとめておこう。

> (define (hd x) (map (fn (v) (format "0x%lX" v)) (dump x)))
(lambda (x) (map (lambda (v) (format "0x%lX" v)) (dump x)))
> (hd aa)
("0x288A4C30" "0x104" "0x288A3000" "0xE" "0x2885E1E0")

どうやら動いてるな。しめしめ。

> (hd aa)
("0x288A49D0" "0x104" "0x288A3000" "0xE" "0x2885E200")
> (dump aa)
(680151520 260 680144896 14 679862608)

関数なら、参照透明性が担保されてるはずってんで(SDの受け売りですが)、もう一度、 同じデータを入力してみます。ガァーーーーン。今定義したやつは関数テストに落ちて しまいましたよ。オリジナルはちゃんと関数なんですが? こういう時は、SDの執筆陣に 問い合わせてみるのが正解かな? まてまて、自分で考えてみろよ。

えと、関数を呼んだ時点で、引数が評価されるってのは、お約束。そして、評価された結果が 関数に渡って、関数の中でまた評価してるよ。

> (dump (dump aa))
(680151616 59 680144896 680144896 680152016)
> (dump (dump aa))
(680152272 59 680144896 680144896 680151648)

こういう事(dumpして得られるlistをdump)だな。で、ならば、引数を評価しない版の 関数定義が出来ればいいはず。そう、マクロの出番だ。

> (define-macro (hd x) (map (fn (v) (format "0x%lX" v)) (dump x)))
(lambda-macro (x) (map (lambda (v) (format "0x%lX" v)) (dump x)))
> (hd aa)
("0x288A4A60" "0x45" "0x288A3000" "0x288A3000" "0x288B55C0")
> (hd aa)
("0x288A4D00" "0x45" "0x288A3000" "0x288A3000" "0x288B55C0")

こりゃ、明らかに間違いを起しているぞ、てんで色々参考にしました。そして、

> (define-macro (hd x) (map (fn (v) (format "0x%lX" v)) (dump (eval x))))
(lambda-macro (x) (map (lambda (v) (format "0x%lX" v)) (dump (eval x))))
> (hd aa)
("0x288A49E0" "0x104" "0x288A3000" "0xE" "0x2885E150")
> (hd aa)
("0x288A49E0" "0x104" "0x288A3000" "0xE" "0x2885E150")

どうやら、動き出したようです。癖があるマクロ。癖があるから癖になりそうで、恐い。 こちらには、その魔界に入られた方がおられます。 newLISP で On Lisp する...第7章(その1)

後は折角描いたコードなんで末長く使えるように、記録を取っておきましょ。 .init.lspに 書いて、home-dirに置いておけば、何時でも使えるよ。

(define-macro (hd _obj_)
  (map (fn (v) (format "0x%lX" v)) (dump (eval _obj_))))

引数名にアンダーバーを付けているのは、気休めの魔よけです。名前の衝突防止策。同じ 名前が使われていたら、防げんけど。

これって最近売り出し中の、車の衝突防止策だな。誇大広告してるくせに、やばい事は、 こと細かに書いたフリップを1秒見せて責任逃れ。細かい文字は丁寧に読んでネチネチと メーカーを締め上げましょう。その為の3行テロップを、誰か果敢かね。

watch

gdbのマニュアルを見てたら、watchなんていうコマンドが載ってた。変数の監視。今まで 用が無かったのは、変数が知らぬ間に書き変わるなんて場面に遭遇してなかったからだな。

関数型プログラミングでは、起こりえない事態だもの。必要な人達は、OOPにどっぷり浸かった 哀れな人種。と、思い切りバズってみたぞ。

まあ、それはよいとして、どうやって使う? 今見てるnewLISPの追っかけに適用できないかな。 その前に、どうやって使うか予習。

gcc+gdbによるプログラムのデバッグ 第2回 変数の監視、バックトレース、その他のコマンド

5.1.2 ウォッチポイントの設定

特定のアドレスの値が書き換わった時ブレイクしたい場合

メモリ破壊の現場を見つける

早速実例

(gdb) watch cellCount
Hardware watchpoint 1: cellCount
(gdb) run
Starting program: /usr/home/sakae/newlisp-10.6.2/newlisp
Hardware watchpoint 1: cellCount

Old value = 0
New value = 1
getCell (type=256) at newlisp.c:2071
2071        cell->type = type;
(gdb) c
Continuing.
Hardware watchpoint 1: cellCount

Old value = 1
New value = 2
getCell (type=257) at newlisp.c:2071
2071        cell->type = type;
(gdb) bt
#0  getCell (type=257) at newlisp.c:2071
#1  0x08051dfd in initialize () at newlisp.c:1329
#2  0x08050dae in main (argc=1, argv=0xbfbfec7c) at newlisp.c:722
(gdb) info watch
Num     Type           Disp Enb Address    What
1       hw watchpoint  keep y              cellCount
        breakpoint already hit 2 times

もう一例。

(gdb) watch symbolCount
Hardware watchpoint 2: symbolCount
(gdb) c
Continuing.
Hardware watchpoint 2: symbolCount

Old value = 0
New value = 1
findInsertSymbol (key=0x80ae933 "MAIN", forceCreation=1) at nl-symbol.c:651
651         return (x);
(gdb) bt
#0  findInsertSymbol (key=0x80ae933 "MAIN", forceCreation=1) at nl-symbol.c:651
#1  0x0805f6c1 in createRootContext (token=0x80ae933 "MAIN") at nl-symbol.c:585
#2  0x08051e1e in initialize () at newlisp.c:1333
#3  0x08050dae in main (argc=1, argv=0xbfbfec7c) at newlisp.c:722

うん、キーとなる変数を旨く選べば、コードを追い駆ける有力な方法になるな。これからの 生活にメリハリが出来るってもんです。

シンボル

シンボルって何者よ? 名前でしょ。定義はどうなってる? 以前、dump-symbolなんてのを 有効にしたけど、定義とつき合わせてみると、ちょっとdump不足と思うぞ。 そこで、dump-symbolをプチ拡張してみた。

#ifdef SYMBOL_DEBUG
CELL           *
p_dumpSymbol(CELL * params)
{
    char           *name;
    SYMBOL         *sPtr;

    getString(params, &name);

    sPtr = findInsertSymbol(name, LOOKUP_ONLY);

    if (sPtr == NULL)
        return (nilCell);

    varPrintf(OUT_DEVICE, "name=%s color=%s parent=%s left=%s right=%s flags=%x contents=%X\n",
              sPtr->name,
              (sPtr->color == RED) ? "red" : "black",
              (sPtr->parent) ? sPtr->parent->name : "ROOT",
              sPtr->left->name,
              sPtr->right->name,
              sPtr->flags,
              sPtr->contents);

    return (trueCell);
}
#endif

flagsとcontentsを16進で表示するように追加しただけ。シンボルって名前とその値を格納 してる変数なんだけど、値の表示が無かったので、追加しただけ。すごく真っ当な八苦です。

Lisp系だと、値にも何種類も有って(いわゆる変数の値、関数定義、プロパティ)それぞれの 格納場所を用意してたけど、newLISPには、それ用のスロットがcontents一つしかない。 そうすると、その種類をflagsに保持してんのかなあと想像した訳。実地に調べた方が早いと 思ったのさ。

> (dump-symbol "cons")
name=cons color=black parent=constant left=NIL right=NIL flags=70 contents=2884E380
true
> (dump-symbol "hd")
name=hd color=red parent=global? left=NIL right=NIL flags=0 contents=288500A0
true

ふむ、ユーザー定義のものは、flagが0なの? じゃ、もう一例

> (setq aa "Hello")
"Hello"
> (dump-symbol "aa")
name=aa color=black parent=^ left=_obj_ right=abort flags=0 contents=2884F9F0
true

aaは文字列に束縛されてるんで、それなりのフラグが立つかと思ったら、違った。そこで、 やおら、シンボルのフラグって何よを調べてみた。こういうのは、ヘッダーファイルに有るはず。

/* symbol flags types and masks */
#define PRINT_TYPE_MASK 0x0F
#define SYMBOL_PROTECTED 0x10
#define SYMBOL_GLOBAL 0x20
#define SYMBOL_BUILTIN 0x40
#define SYMBOL_FFI 0x100
#define SYMBOL_MACRO 0x200
#define SYMBOL_DESTRUCTIVE 0x400

想像してたものと違ったわい。それじゃ、contentsの先に格納情報が有るのかな。lispでの 唯一の記憶エリアって、、、、そりゃ、CELLしかないでしょ。dumpの出番だな。

> (hd hd)
("0x288500A0" "0x13D" "0x2884E000" "0x2884E000" "0x2884FFB0")
> (hd aa)
("0x2884F9F0" "0x104" "0x2884E000" "0x6" "0x2881F580")

ふむ、contentsの示すものが、CELLのアドレスになってるな。よーするに、ポインターな訳だ。 (って、それしか無いな。納得)

そんじゃ、flagsはどんな役に立ってる? 思いついたら即実験。これが正しいエンジニアの態度ですよ。

> (setq cons "foo")

ERR: symbol is protected in function setf : cons

保護されてて、書き換え出来ないようになってる。gaucheとかだと、挙動が違う。

gosh> cons
#<subr cons>
gosh> (define cons "hoge")
cons
gosh> cons
"hoge"

こちらは、自由度200%、全てはユーザーに任せますって開放感に溢れていますよ。

cflow and graphviz

今まで、気の向くままソース観光してきたけど、ちょっと高所から眺めてみたいな。 そんな時は、地図っすね。

制御の流れを押さえるのが鉄則かな。流れったら flow それをC語についてやろうってんだから、 cflowだな。幸いな事にOpenBSDにもpkgが有ったんで入れた。が、どうもこやつはGNUの やつっぽい。

cflow -m onig_parse_make_tree -bn --omit-arguments  --omit-symbol-names newlisp.c 

こんな風に使うらしい。オイラー長いオプション名嫌いよ。それに、地図って事で図に描こうとすると、 graphvizの登場となるんだけど、cflowとの連携にdot言語のソースを生成しにゃならん。 awkとかpythonとかperlで書かれた変換スクリプトが有るようだけど、ちょっとかったるい。

そんなんで、昔(2012年の暮れ)を思い出して、 bsdcflowを使う事にした。元はFreeBSD用みたいだけど、 同じBSD仲間だから何とかなるだろう。何とかならなくてもgnuってdirの下にあるMakefile を使うと、何とかなったよ。

graphvizもすっかり忘れていたので、

graphviz という有向グラフを使うのに最適なツールが有りまして

Graphviz チュートリアル

思い出すように、ちょいとチョイスしてみた。

以前の記録を元にグラフと言うか地図を作成。ビューアーは、懐かしいxvです。

[ob: newlisp-10.6.2]$ cflow -ACGP -g newlisp.c > z.dot
[ob: tmp]$ fdp -T png -o z.png z.dot
[ob: tmp]$ xv z.png

複雑過ぎて、dotレイアウトでは横長になってよく見えず。しょうがないから、fdpってレイアウトを 選んでみたけど、枝が重なって視認性が非常に悪い。どのレイアウトが良いかは、 Graphvizレイアウトサンプル が参考になります。

cflowがどんな風に解析してくれたかの例。枝と葉が出来てる。

digraph "TODO" {
  main -> MAKEWORD [label="1"];
  main -> initFFI [label="2"];
  main -> getpagesize [label="3"];
   :
  processCommandEvent -> stuffString [label="1"];
  processCommandEvent -> pushResult [label="2"];
  linkSource [label="linkSource"];
  printHelpText [label="printHelpText"];
   :
  sublist [label="sublist"];
  substring [label="substring"];
}

それじゃ、どの程度の枝振りになってるか、確認してみると

[sakae@fedora ~]$ grep '-' z.dot | cut -f1 -d'-' | sort | uniq -c | sort -nr
     33   main
     22   evaluateExpression
     18   compileExpression
     11   sysEvalString
     11   implicitNrestSlice
     11   evaluateStream
     11   evaluateLambda
     10   executeCommandLine
     10   evaluateNamespaceHash
     10   evaluateLambdaMacro
      8   printCell
      8   loadFile
      6   printErrorMessage
      6   initialize
      6   copyCell
      5   varPrintf
      5   linkSource
      :
      1   cleanupResults
      1   allocMemory

ググル様のページランクじゃないけれど、枝が一杯集まっている所は、重要だよーんって事 で良いかしら。メインは総元締めなんで当然なぞっておくとして、evaluateExpressionとか compileExpressionあたりをじっくりと見ておく必要が有りそうだな。

なお、対象にしたのは、newlisp.cのみです。他のnl-xxx.cは、それぞれの専門分野を 担当してるんで、無視してもかまいません。吉永小百合さんじゃないけど、鹿のうんこみたいに、 ころころしてて、newlisp.cとの密接な連携はほとんど無いはず。

おまけのCRYSTAL

新種の言語CRYSTALですってさ。 あのmatzさんも寄付と言うかファンディングに協力してる 特製Tシャツが貰えるんだな。

言語の売りは、Fast as C, slick as Ruby って事だから、日本語に直すと、速きことCのごとく、 滑らかなることRubyのごとし、あーあ、推奨、水晶。(と、風林火山風)

Ruby風かつ高速と噂の言語Crystalを使ってみた

手が空いたら、オイラーもやってみるか。