mto を実装しながら Common Lisp のお勉強(5日間)
Common Lisp でコマンドライン引数を取得(2012.06.05)
Common Lisp でも実装してみようとスクリプトを作成開始。Scheme と似ているのでそれを参考にしながら作っているけど、似ているようで微妙に違う。というかかなり違うかもしれない。
Common Lisp ではコマンドライン引数の処理が標準化されていないので、実装によって取得方法が違うとのこと。それって標準化の意味なくね?iBook で動くのが古い clisp しかないので、これ限定で作ることにしよう。
とりあえず、引数を表示すること、数を表示すること、比較演算子で分岐処理すること、端末に結果を表示すること、が出来た。今回は関数というか defun で手続を作って引数が 0 もしくは 3 以上の場合に Usage を表示するところまで。次は引数が 1 と 2 の場合に使う手続を作っていく。先にファイルから辞書を作成する部分を作った方がいいかな?
青写真があるとはいえ、完成させるにはファイルの入出力、連想リストの作成、検索・置換などの手続を作る必要があるのだけど、自分はどうして引数処理から作りはじめたんだろう?簡単に出来そうな部分から作っちゃう癖なのかしら。どんな業種でも仕事だったら一番困難な(時間のかかる)部分を先につぶしていくけど、娯楽だし、やっぱり動くものを改造していった方が面白いからかなぁ。
簡易月齢計算ではインタラクティブというか入力のことを考える必要がなかったので簡単だったけど、外部とのやり取りをするとなるとエラー処理とかを考えないといけないから繁雜になるね。外部とやりとりをしなければ心乱されることなんてないのに...あぁ、引き籠りたい。
; Usage: clisp test.lisp arg1 arg2 arg3 ... ; 変数は * で挾む、改行は ~%、eq? じゃなくて equal、else がなくて t で真にする ; define でなく defun、手続と変数を囲う括弧の位置に注意。ext:*args* は global (format t "引数は~aです。" ext:*args*) (format t "サイズは~aです。" (length ext:*args*)) (defun main () (cond ((< 2 (length ext:*args*)) (format t "3以上です。~%")) ((equal 2 (length ext:*args*)) (format t "2です。~%")) ((equal 1 (length ext:*args*)) (format t "1です。~%")) (t (format t "引数がありません。~%")))) (main)
実行してみる。
> clisp test.lisp 引数はNILです。サイズは0です。引数がありません。 > clisp test.lisp foo 引数は(foo)です。サイズは1です。1です。 > clisp test.lisp foo bar 引数は(foo bar)です。サイズは2です。2です。 > clisp test.lisp foo bar hoge 引数は(foo bar hoge)です。サイズは3です。3以上です。 > clisp test.lisp foo bar hoge piyo 引数は(foo bar hoge piyo)です。サイズは4です。3以上です。
そういえば Cocoa プログラミングをはじめてから 2 ヶ月経ってしまった。ダイアログを表示するサンプルを作ってから触ってないや。もう忘れてる〜。というか iBook が遅いくて辛いねん。
あと、はじめて git で branch を切って作業をしてるかも。今までは別のディレクトリに作成して動くようになってから本体に合流させていたけど、読み込む外部ファイルの構成を再現するのが面倒なので素直に branch 機能を使ってみるのだ。本来の使い方になったと言うべきか。
Common Lisp で実行スクリプトの場所を取得(2012.06.06)
実行するスクリプトファイル自身が存在しているディレクトリへのパスを取得したいのだけど、方法がわからない。何らかのライブラリが必要なのだろうか?現在自分の居る場所ではなく、スクリプトファイルのある場所を絶対パスで得たいのだけど。
これが解決できないと辞書を読み込むことができないわけで、その他の手続を作ったところでそれらは意味がないものになってしまう。スクリプト内に書いて固定してしまうのはアホだからなぁ。確か Lua でも苦労した記憶があるし、Gauche では file.util
ライブラリを使ったんだった。
Gauche (decompose-path *program-name*)
Lua string.gsub((os.getenv("PWD") .. "/" .. arg[0]), "[%w_%.%-]-$", "")
Perl $FindBin::Bin
Python3 os.path.dirname(os.path.abspath(__file__))
Ruby File.dirname(__FILE__)
Vim と Emacs は初期設定ファイル内で指定しているので悩まなくて済んだ。Windows の C# では特にパスの設定をしなくても、バイナリが自分の場所を知っているのでそこからのパスを書けば良かった。また変更しても自動的に Document and Settings/user/Local Settings/Application Data/appname/appname.xxxxxx/1.0.0.0/user.config
に保存してくれるので、以後はここから情報が読み込まれるようになっていた。
うわ〜、ちょろっと調べた程度だけど、これも実装によって違う...。しかもどれも全く似ていない。肝心の clisp での例がないっす。
CommonLisp Tips によると (truename "./")
でコマンドを実行している場所を絶対パス得ることができるそうだ。これなら Lua 方式で出来そうかな?あれ?Lua でスクリプトを長めで指定すると自分の居る場所より上が抜けなくてエラーになっちゃう。
ホームディレクトリで指定した例
/Users/name//Users/name/mto/tool/../dict/kana-jisyo
----------- ここが重複しとる
不具合が出てしまったぞい。問題が出たのは Lua だけで、他のスクリプトは大丈夫だった。セフセフ。テストはリポジトリの中でしか行っていなかったからなぁ。別の場所でやった途端にこれだ。う〜、引き籠りたい。
ちょうど良いのでもう一回考え直してみよう。やっていることは「現在地 + 引数に渡したスクリプトへのパス」から「スクリプトの名前」を削除しているだけ。だから「スクリプトへのパス」を現在地よりも上位のパスから与えてしまうと、重複が発生してしまうというわけだね。
ディレクトリ名をバラして配列に入れて uniq
して /
で結合する?自分はしないけど同じディレクトリ名を掘る人も居るかもしれないからダメか。PWD
を分解して map
で sed
的に sub
していけば OK かな?左側から match
を実行していくのだし。う〜、Lua に split
が無いの...。まぁこのままでもいいか、使うの自分だけやし。
難しく考えていた。「重複してるなら単純に文字列から引いちゃえばいいじゃん」という事に気付くまで 128 分 32 秒もかかってしまった。ということで print
で出力しながらデバグをした結果が下記。
apath = os.getenv("PWD") .. "/" -- 自分の現在地( / を付けておく) bpath = arg[0] -- Lua に渡した実行スクリプトへのパス cpath = string.gsub(bpath, apath, "") -- 引き算 dpath = apath .. cpath -- 足し算 epath = string.gsub(dpath, "[%w_%.%-]-$", "") -- スクリプト名を削除
変数がいっぱいでメモリや速度が犧牲になりそうだけど、このままの状態でコミットしちゃおう。短縮すると思い出すのに時間がかかるからね。健忘症。老化とも言う。
実行スクリプトへのパスである bpath
だけを使ってディレクトリを決めちゃえばいいじゃない?と思うのだけど、相対パスになってしまうと問題が生じてしまうのだ。なぜなら辞書ファイルへのパスは絶対パスでなければならないから。そこでちょっとした小細工をしていたのだけど、その小細工が不十分だった(下記の 3 の状況しか想定していなかった)ので今回の不具合発生となったのでした。
下記の実行結果を見るとわかるのだけど、得られるパス情報 apath
と bpath
から epath
を求めるまでに色々な状態があるのね。
1. ホームディレクトリにて相対パス指定 homu:(~)> lua ~/project/mto/tool/test.lua apath: /Users/homu/ bpath: /Users/homu/project/mto/tool/test.lua この場合は bpath から単純にスクリプト名を除けば得られるね。 cpath: project/mto/tool/test.lua dpath: /Users/homu/project/mto/tool/test.lua epath: /Users/homu/project/mto/tool/ <--- これが欲しい
2. ホームディレクトリにて絶対パス指定 homu:(~)> lua /Users/homu/project/mto/tool/test.lua apath: /Users/homu/ bpath: /Users/homu/project/mto/tool/test.lua この場合も bpath から単純にスクリプト名を除けば得られるね。 cpath: project/mto/tool/test.lua dpath: /Users/homu/project/mto/tool/test.lua epath: /Users/homu/project/mto/tool/ <--- これが欲しい
3. リポジトリ内にて相対パス指定 homu:(mto)> lua tool/test.lua apath: /Users/homu/project/mto/ bpath: tool/test.lua この場合は apath と bpath を足し算しないと得られないね。 cpath: tool/test.lua dpath: /Users/homu/project/mto/tool/test.lua epath: /Users/homu/project/mto/tool/ <--- これが欲しい
4. リポジトリ内にて絶対パス指定 homu:(mto)> lua /Users/homu/project/mto/tool/test.lua apath: /Users/homu/project/mto/ bpath: /Users/homu/project/mto/tool/test.lua この場合も bpath から単純にスクリプト名を除けば得られるね。 cpath: tool/test.lua dpath: /Users/homu/project/mto/tool/test.lua epath: /Users/homu/project/mto/tool/ <--- これが欲しい
Common Lisp でパスの取得をやるはずが、いつの間にか Lua での話になってしまった。これを踏まえて Common Lisp でも取得できればいいな。ここでひと息いれてしまうとこのまま放置すること間違いなしなので一気に取り掛りたいのだけど、いかんせん疲れてしまった。もう夜更かしはできない体に。
耳にタコができているだろうけど、自分のマシンは古いので Lua くらい軽量だとうれしいのだ。そういった意味では mruby には期待しちゃうのだけど、安定板が出る前には買い替えをしたいな。いや、しなければならない。もうちょっと食費を削ろう。
Common Lisp で文字列の置換のライブラリを使えない?(2012.06.07)
グローバル変数?の ext:*args*
では実行スクリプトに対して与えられた引数が得られて便利なのだけど、ここから clisp インタプリタに対する引数を得ることは出来ない。つまり実行スクリプト自身へのパスはわからないのだった。
(ext:argv)
を使うと、起動に関する詳しい情報を得ることができるとのこと。実際に動かしてみると、下記のような情報を得ることができた。
(format t "~a~%" (ext:argv))
mami:(mto)> clisp tool/mto.lisp ;=> #(/usr/local/lib/clisp-2.47/base/lisp.run -B /usr/local/lib/clisp-2.47 -M /usr/local/lib/clisp-2.47/base/lispinit.mem -N /usr/local/share/locale tool/mto.lisp)
mami:(~)> clisp ~/project/mto/tool/mto.lisp ;=> #(/usr/local/lib/clisp-2.47/base/lisp.run -B /usr/local/lib/clisp-2.47 -M /usr/local/lib/clisp-2.47/base/lispinit.mem -N /usr/local/share/locale /Users/mami/project/mto/tool/mto.lisp)
一見したところ、それぞれ一番最後の行にあるものが、先日修正した Lua スクリプトの arg[0]
で得られるものと同じことがわかる。なんとなく方向性が見えてきた。
ところで、この #(hoge fuga)
というデータ構造は何なのだろう?昔 map 系で使ったことがあるような無いような、そんな記憶。car
を使って 1 つ目の要素を取得しようとするとエラーになってしまう。あと、lisp には last
メソッドは無いのかい?ササッとやりたいところだけど、入門サイトを読んでデータ型について勉強しよう。先は長い。
common lisp で型を調べるには type-of
を使うらしい。
(format t "型は: ~a~%" (type-of (ext:argv))) ;=> 型は: (SIMPLE-VECTOR 8) (format t "本当? ~a~%" (simple-vector-p (ext:argv))) ;=> 本当? T
ここまでくれば「Common Lisp simple-vector」で検索すれば解決に近付くね。ちなみに #'
は function
の略なんだって。やっぱり mapcar
と一緒に使われてた。
vector は一次配列のことだって。あ〜、リストと配列は違うのかぁ。データ型というと、連想リストしか使ってなかったので、配列は盲点だった。添字でアクセスができるわけだね。日本語にするとアレフでアレだけど、aref
を使ってアクセスするそうな。
(format t "アレフ ~a~%" (aref (ext:argv) 7)) ;=> アレフ tool/mto.lisp
添字は 0 からはじまる。他の言語と同じだね(Lua は 1 からだけど)。添字を直接 7 として取得してみたけど、(ext:argv)
の要素数って毎回決ってるのかな?あと環境によっても変わるかもしれない。固定ではなく、vector のサイズを取得して指定した方がいいのかも。-1 は使えるかな?...ダメだった。
vector の場合も length
で大きさを取得できる。幸いにして欲しい部分が一番最後なので、得られた値を -1 すれば希望の値を得ることが出来るね。
(format t "大きさ ~a~%" (length (ext:argv))) ;=> 大きさ 8 (format t "僕が欲しかったもの ~a~%" (aref (ext:argv) (- (length (ext:argv)) 1))) ;=> 僕が欲しかったもの tool/mto.lisp
実行する場所を変えても Lua の時と同じものが返ってくるので大丈夫そう。ふう、ようやっと文字列の材料を揃えることが出来た。ここから先は文字列を扱う作業になる。Common Lisp には gsub
とかあるのかな?っとその前に setq
で apath なりに束縛しておこう。Lua の時と違うのは、カレントディレクトリのパスには / が付いていること。これは自分の都合が良い方向に転んでいる。
たった 2 行を書くためにどれだけの時間を使ってるんだよってことなんですけど...。とりあえずここで一区切りということでまとめておこう。
(setq apath (truename "./")) (setq bpath (aref (ext:argv) (- (length (ext:argv)) 1))) (format t "apath: ~a~%" apath) (format t "bpath: ~a~%" bpath)
1. ホームディレクトリにて実行 mami:(~)> clisp /Users/mami/project/mto/tool/mto.lisp apath: /Users/mami/ bpath: /Users/mami/project/mto/tool/mto.lisp
2. ホームディレクトリにて実行 mami:(~)> clisp /Volumes/mami/project/mto/tool/mto.lisp apath: /Users/mami/ bpath: /Volumes/mami/project/mto/tool/mto.lisp
3. ホームディレクトリにて実行 mami:(~)> clisp ~/project/mto/tool/mto.lisp apath: /Users/mami/ bpath: /Users/mami/project/mto/tool/mto.lisp
4. リポジトリで実行 mami:(mto)> clisp /Users/mami/project/mto/tool/mto.lisp apath: /Volumes/mami/project/mto/ bpath: /Users/mami/project/mto/tool/mto.lisp
5. リポジトリで実行 mami:(mto)> clisp /Volumes/mami/project/mto/tool/mto.lisp apath: /Volumes/mami/project/mto/ bpath: /Volumes/mami/project/mto/tool/mto.lisp
6. リポジトリで実行 mami:(mto)> clisp ~/project/mto/tool/mto.lisp apath: /Volumes/mami/project/mto/ bpath: /Users/mami/project/mto/tool/mto.lisp
7. リポジトリで実行 mami:(mto)> clisp tool/mto.lisp apath: /Volumes/mami/project/mto/ bpath: tool/mto.lisp
あちゃ〜、PWD
の -L
と -P
オプションみたいな関係になっちゃった。リポジトリ内での apath
が 物理パスで返ってきてる。Lua ではいつでもどこでも論理パスで返ってきてたのか。気付かなかったなぁ。
実は project/
ディレクトリは別のボリュームにあって、それをシンボリックリンクでホームディレクトリ以下にリンクしてるんだよね。2. 4. 6. では引き算が出来ないや。これは困った。
もしかしたら Lua の方も...うぎゃ〜っ、エラーになっちゃったよぉ。心折れる。もうやめちゃおう...と思ったけど、既知のバグというか仕様ということで進めちゃおう。自分のような構成にしている人はあまり居ないだろうし、実行するコマンドも 3. か 7. しか使わないし、ね?というか他に使う人が居ないし。
文字列と思っていたけど下記のように調べると型が別物だった。型変換を先に調べないとダメそうだね。
(format t "apath: ~a~%" (type-of apath)) (format t "bpath: ~a~%" (type-of bpath)) ;=> apath: PATHNAME ; bpath: (SIMPLE-BASE-STRING 13)
PATHNAME
をどうやって文字列にすれば良いのだろう?いろいろと調べると以下の手続で分解できるみたい。ABSOLUTE
というのは絶対運命黙示録みたいなものかな。ここから cdr
を取れば文字列のリストになる(type-of
でみると CONS
になってた)。
(format t "cpath: ~a~%" (pathname-directory apath)) ;=> cpath: (ABSOLUTE Volumes mami project mto)
文字列の連結に concatenate
を使おうと思って調べてみたけどよくわからなかった。そこでクエブックにあったものを使う。ふぅ、これで文字列同士になった。
(setq cpath (format nil "/~{~a~^/~}/" (cdr (pathname-directory apath)))) (format t "cpath: ~a~%" cpath) (format t "cpath-type: ~a~%" (type-of cpath)) ;=> cpath: /Volumes/mami/project/mto/ ; cpath-type: (SIMPLE-BASE-STRING 26)
文字列の置換で調べていたところ、Common Lisp には文字列置換の手続が無いらしい。ここで手詰りになってしまった。cl-ppcre
を使うと良いらしいけど、どうやってインストールするんだろ?やっぱり scheme っていうか gauche の方がいいなぁ。
規格の標準でない asdf
というものが標準になっているそうで、ライブラリを管理するためのライブラリらしい(gem みたいなもの?)。で、まずはこれをインストールしなければならないらしい。残念ながら clisp-2.47 には含まれていなかった。
インストール方法は asdf.lisp
をダウンロードして処理系のロードパスに置くだけとのことだか、どこになるのだろう?/usr/local/lib/clisp
以下になるのだろうか?「(require :asdf)
」してもエラーになっちゃう。
clisp でロードパスを知るには下記のようにすれば良いとのこと。
> custom:*load-paths* ;=> (#P"./" "~/lisp/**/")
lisp ディレクトリを作ってその中に置いたけど、やっぱりダメだった。カレントディレクトリならロードは出来るみたいだけど...。使えねぇ。
Common Lisp の環境設定を調べてライブラリを使えるようになった(2012.06.08)
mto っていうか、すでに Common Lisp のことについて書いている感じがする。先日挫折した cl-ppcre
ライブラリを使う方法がわかった。~/.clisprc
に
(load "asdf.lisp") (load "cl-ppcre.asd")
のように書けばインタプリタの起動時に読み込んでくれるのだけど、cl-ppcre
ライブラリは使えない。どういう仕組みになってるんだろう?
(asdf:operate 'asdf:load-op :cl-ppcre) ;(asdf:load-system :cl-ppcre) でもいいみたい
も追加したら使えるようになった。やけに時間がかかるけど。
ということで、スクリプトに以上の 3 行を追加して実行することにした。(asdf:operate 'asdf:load-op :cl-ppcre)
の部分で出力される「0 errors, 0 warnings」が一緒に表示されるようになってしまった、、、うぐ〜。
Lua と同じようにパスの足し算と引き算をして最終的な epath を得ることが出来た。
(setq apath (format nil "/~{~a~^/~}/" (cdr (pathname-directory (truename "./"))))) (setq bpath (aref (ext:argv) (- (length (ext:argv)) 1))) (setq cpath (cl-ppcre:regex-replace apath bpath "")) (setq dpath (concatenate 'string apath cpath)) (setq epath (format nil "/~{~a~^/~}/" (cdr (pathname-directory dpath)))) (format t "epath: ~a~%" epath)
1. ホームディレクトリで実行 homu:(~)> clisp /Users/homu/project/mto/tool/mto.lisp epath: /Users/homu/project/mto/tool/
2. ホームディレクトリで実行 homu:(~)> clisp /Volumes/homu/project/mto/tool/mto.lisp epath: /Users/homu/Volumes/homu/project/mto/tool/
3. ホームディレクトリで実行 homu:(~)> clisp ~/project/mto/tool/mto.lisp epath: /Users/homu/project/mto/tool/
4. リポジトリで実行 homu:(mto)> clisp /Users/homu/project/mto/tool/mto.lisp epath: /Volumes/homu/project/mto/Users/homu/project/mto/tool/
5. リポジトリで実行 homu:(mto)> clisp /Volumes/homu/project/mto/tool/mto.lisp epath: /Volumes/homu/project/mto/tool/
6. リポジトリで実行 homu:(mto)> clisp ~/project/mto/tool/mto.lisp epath: /Volumes/homu/project/mto/Users/homu/project/mto/tool/
7. リポジトリで実行 homu:(mto)> clisp tool/mto.lisp epath: /Volumes/homu/project/mto/tool/
例によって 2. 4. 6. の場合は欲しいパスを得られないけれど、それは仕様ということで。4 日間かかってやっとここまで。clisp
のライブラリ環境の構築という仕事もあったけど、これは時間がかかったなぁ。他の言語なら...いや言うまい。
自分のやりたいことを実現するためには cl-ppcre
ライブラリが必須なのだけど、これを読み込むだけでものすごく時間がかかるのであった。
homu:(mto)> time clisp tool/mto.lisp epath: /Volumes/homu/project/mto/tool/ 7.75 real 6.38 user 1.16 sys
実行する度にこんなに時間がかかってしまうので、ものすごくストレスになる。ちょっとした思い付きではじめただけなので、それ程強い動機付けがあるわけではない。ということで、新しいマシンを購入するまでこのプロジェクトは凍結しよう。といいつつも、辞書ファイルを開いて読み込む部分までやっちゃった。
辞書ファイルにあるコメント行を削除(というか無視)して、連想リストを作成したいのだけど、cl-ppcre:scan
の戻り値がよくわからない。T
or NIL
が返ってくればいいだけなんだよねぇ...。どうやら 4 つあるみたい。unless
で NIL
の場合だけリストに加えて行くという方法。
(UNLESS (CL-PPCRE:SCAN "^\;.*|^$" LINE) WRITE-LINE LINE) is not a function name; try using a symbol instead
というエラーになっちゃう。lambda
とか使わないといけないのかな?それと Common Lisp は真と偽の表現が他の言語と違うんだったけ?しばらく repl で挙動を調べてみよう。
[1]> (setq line "; this is a test string.") "; this is a test string." [2]> (cl-ppcre:scan "^;" line) 0 ; 1 ; #() ; #() [3]> (cl-ppcre:scan ";;" line) NIL [4]> (cl-ppcre:scan "^;;" line) NIL [5]> (cl-ppcre:scan "^;." line) 0 ; 2 ; #() ; #() [6]> (cl-ppcre:scan "^;.*" line) 0 ; 24 ; #() ; #() [7]> (cl-ppcre:scan "^;.*|^$" line) 0 ; 24 ; #() ; #()
う〜ん、よくわからない。repl はやめて、スクリプトの下記の部分を変更して実行してみよう。
(write-line line) ↓ (format t "~a~%" (cl-ppcre:scan "^;.*|^$" line))
結果は
0 0 0 0 NIL NIL NIL NIL 0 0
のようになった。0 はコメントアウトの行で、NIL
の部分が欲しいものだ。format
の結果を利用して unless
を使えばいいのか。ダメだ〜、一行につき「0000」という結果が改行付きで表示されてただけなんだ。T
か NIL
で結果を受け取れないものだろうか?
遅いけどなんとか最低限の機能を実装できた(2012.06.09)
cl-ppcre:scan
の戻り値を type-of
で調べてみると、含まれている場合は BIT
、含まれていない場合は NULL
ということだった。BIT
って何なんだろう?4 つの値があるけどリストって訳ではないみたいだし...。
とりあえず NULL
の場合に単語を連想リストに入れていけば良いので、unless
で動かすためには NIL
を得る述語が必要だ。
要らない行 (cl-ppcre:scan ";" line) ;=> 0 ; 1 ; #() ; #() (atom (cl-ppcre:scan ";" line)) ;=> T (numberp (cl-ppcre:scan ";" line)) ;=> T (intergerp (cl-ppcre:scan ";" line)) ;=> T (floatp (cl-ppcre:scan ";" line)) ;=> NIL (symbolp (cl-ppcre:scan ";" line)) ;=> NIL (stringp (cl-ppcre:scan ";" line)) ;=> NIL (listp (cl-ppcre:scan ";" line)) ;=> NIL (consp (cl-ppcre:scan ";" line)) ;=> NIL 要る行 (cl-ppcre:scan "hoge" line) ;=> NIL (atom (cl-ppcre:scan "hoge" line)) ;=> T (numberp (cl-ppcre:scan "hoge" line)) ;=> NIL (intergerp (cl-ppcre:scan "hoge" line)) ;=> NIL (floatp (cl-ppcre:scan "hoge" line)) ;=> NIL (symbolp (cl-ppcre:scan "hoge" line)) ;=> T (stringp (cl-ppcre:scan "hoge" line)) ;=> NIL (listp (cl-ppcre:scan "hoge" line)) ;=> T (consp (cl-ppcre:scan "hoge" line)) ;=> NIL
どうやら numberp か integerp を使えば良さそうな雰囲気。下記のようにして成功。
(unless (numberp (cl-ppcre:scan "^;.*|^$" line)) (write-line line))
とりあえずここまでで辞書ファイルからコメントを除いた行を得ることができた。ただし、このままでは備考が含まれているので、さらに各行において加工が必要になる。
「笑う /笑ふ ;ほげほげ」から「空白 + ; + 備考」の部分を削除して「笑う /笑ふ」のみにしたいのだ。そして「空白 + /」を区切り文字として split
し、(笑う . 笑ふ) のコンスセルにする。さらにこれをリストに加えていって連想リストの完成になる。うわ〜、先が長そう...。最後に reverse
しなくちゃいけなかったっけ?
備考の削除にはパスの取得の時に使ったやつが使えそうかなぁ。
(cl-ppcre:regex-replace " ;.*" line "")
で出来た。次は split して コンスセルの作成か。ちょっと疲れた。
(car (cl-ppcre:split " /" (cl-ppcre:regex-replace " ;.*" line ""))) (cdr (cl-ppcre:split " /" (cl-ppcre:regex-replace " ;.*" line "")))
car
で「笑う」を得ることができたけど、cdr
では「笑ふ」を得ることができなかった。not a string
って言われてしまった。scheme ではどうやってたっけ?let
で局所変数を用意して car
と cadr
を使って cons
してたわ。それにしてもなんで cadr
なんだろう?思い出せない...。コンスセルじゃないからだ。cdr
では (笑ふ) になってる。だからさらに car
しないとダメってわけだ。
(let ((ward (cl-ppcre:split " /" (cl-ppcre:regex-replace " ;.*" line "")))) (cons (car ward) (cadr ward)))
scm と同じようにして (笑う . 笑ふ) のコンスセルを得ることが出来た。ward はわざと。どんな変数名が使われているのかわからないので。C# であった名前空間とか知らないしね。局所だから関係ないって話もありそうだけど...。cons
だから write-line
で出力しようとしてもエラーになっちゃうわな。あとはリストに push
していけばいいね。リストに要素を加える手続は push
でいいのかなぁっと。
(unless (numberp (cl-ppcre:scan "^;.*|^$" line)) (push (let ((ward (cl-ppcre:split " /" (cl-ppcre:regex-replace " ;.*" line "")))) (cons (car ward) (cadr ward))) *dic-tmp*) )
でいけた。最初は push
への引数を逆にしていたのでうまく動かなかった。リファレンスは良く見ないとダメだね。format
で表示してみると自分の欲しい連想リストとは逆順になっている。なんだか format
と type-of
を使ってデバッグしているよ。push
は頭からどんどん押し込んでいくものだから、リストの場合は左側から右側へ押されていくことになる。これを逆順にするには reverse
と nreverse
というものがあるらしいけど、どう違うんだろう?
(setq *dic* (reverse *dic-tmp*))
でいけた〜。ふぅ、これで変換のための辞書を得ることが出来たぞい。残りの作業としては、変換したいファイルから一行ずつ読み込み、それに対して辞書連想リストから each で破壊的置換、標準出力へ表示という部分を作ること。他には入力がファイルではなく標準入力からとか、変換のオプションによる場合分けとか細かいものもある。とりあえずできた部分は関数にしてしまおう。関数にして使う辞書の名前を引数で与えるようにした。
区切りが付いたといってひと休みしてしまうと、このまま完成させないで放置になりかねないので、気合を入れていこう。でも、昨日 mattn さんのスクリプトを見てしまって、がぜんやる気が無くなってしまった。アプローチが間違ってたんだって。
まずは辞書を使わないで手動で読み込んだ行の置換をしてみる。
(cl-ppcre:regex-replace-all "笑う" line "笑ふ")
これで行の中にある全ての「笑う」が「笑ふ」になる。辞書からの引数としては、(car (*dic* のとある cons))
が「笑う」に、(cdr (*dic* のとある cons))
が「笑ふ」に対応することになる。で、(*dic* のとある cons)
をどうやって表現すれば良いのだろう?
(mapcar #'(lambda (x) (setq line (cl-ppcre:regex-replace-all (car x) line (cdr x)))) *dic*) (write-line line)
見よう見まねで動くようになったけど、激遅。メモリとかどうなってるんだろう?まぁ構文のお勉強だからそういった部分を考えるのは次の段階になったらにしよう。これもまた入力ファイルを引数にする関数にしておこう。(car x)
と (cdr x)
を逆にすれば、逆変換(つまり旧仮名から新仮名への変換)が出来るというわけだね。
場合分けを case でやってるんだけど、標準入力の引数で得たものは strig 型になっている。で、これを symbol に型変換したいんだけど、検索しても出てこない。Ruby とかのように stirng のままマッチしてくれればいいけど、いろいろやっても otherwise の結果ばかりが表示されてしまう。
case がわからないので cond
で equal
を使ってやるようにした。ファイルの存在確認はどうやるのかしら?とりあえず存在しなければエラーが出てくるので、オプションのエラー処理だけにして、ファイルの有無はインタプリタの表示するエラーのままにしておこう。これでファイルを指定しての変換ができるようになった。激遅だけど。
標準入力からの変換を実装していて、辞書ファイルが開けないという問題が生じてしまった。(format t "~a~%" (ext:argv))
を見てみると、要素数が 9 個になっていて、欲しいパスは -2 個目に移動していた。当初は「後ろから 1 番目」ということで処理していたのだけど、それではダメだってことだね。どうやら前からの順番は変わらないようなので、「前から 8 番目(添字は 7 ね)」という処理に変更する。
とりあえずこれで scheme 版や C# 版と同じ機能を作ることができた。ライブラリを読み込む時の「0 errors, 0 warnings」という出力を回避する方法はわからないのでこのままにしておく。ファイル有無のエラー処理もできてないけど、自分的には満足なり。5 日間もかかってしまったけど、それが自分のレベルってことだね。
Git ではじめて branch を切って行った作業なので、master に merge
して push
する作業が残っている。どうやるのかなぁ。どうやら master に移動して merge
するだけで良いみたい。
git checkout master git merge cl-project
ブランチを切った時と比べて辞書の修正が入っているのでコンリクトしちゃうかな?
えっと、iBook でまともに動く開発環境というと、残りは .c
か .ml
しかない。.java
は 1.4.2 だし、.hc
は 6.4.1 だし、.js
はブラウザが古いし。.awk
や .sh
で二次配列を作って置換というのも???メモリ管理で死ぬか、副作用を使いまくるか、どちらにしても面倒臭そう。