OnTranslateのReference0が壊れる

10月 16th, 2008

相変わらず、11/3のうかべんに向けてCLでSHIORIを作っています。
けど、最近は現実逃避にゲームばかりやっていたので実は久しぶりというのは内緒です。
以前から、困っているのが、リクエストに対して長い台詞を送ると、
途中で切れたり、内容が壊れていたりするという現象。
しかし、長くない台詞に対しても起こるようになってきました。
リクエストとレスポンスを追いかけてみたところ、

SHIORI/3.0 200 OK
Charset: UTF-8
Sender: matsuri
Value: \0\s[0]\1\s[10]\0えんいー\1なんか懐かしいなそれ\w5\-

リクエストに対して、このようなレスポンスを返した際に、
次のようなリクエストがやってきました。

GET SHIORI/3.0
ID: OnTranslate
Sender: SSP
Charset: UTF-8
SecurityLevel: local
Reference0: \0\s[0]\1\s[10]\0えむ

OnTranslateとはなんぞやと調べてみたら、こちらがValue(台詞)を返すと、
その台詞を表示する前に台詞をReference0に入れて送られるリクエストだそうです。
(ログを取ったり、台詞の語尾の置き換えたりする場合に使うそうです。)
このリクエストに対するレスポンスとしてValueを返さなければ
OnTranslateのReference0が実際に表示される内容となるみたいですが、
上記の通り内容が壊れています。
結果として「えむ」という台詞が表示されてしまいました。
以上がSSPでの動作で、CROWで試してみたところ、
OnTranslateが送られてこない代わりに、なにも喋ってくれません。
こいつは困った…
全然関係ないけど10/11のリリカルの続き

記事にクダーを付けれるようにした

10月 15th, 2008

今更ですが、はてなスターを付けてみました。
「Add Star」を「Add Cdr」に書き換えたかったんだけど、
どうもここは触れないっぽいです。
(RSSリーダで見てる人はなんのことか分からないと思うので、直接見てくださいね。)

B Method

10月 11th, 2008

東京から突然nishさんがやってきてBメソッドの本をくれました。
「The B Language and Method」
って、値段見たら尋常でなく高い…
『¥ 12,799より』っていくらなんでも高すぎるだろ…
そんなわけで(*)、この本は大切に扱いながら読んでいこうとおもいます。
(*) べっ、別に高いからじゃないんだからね! 譲って頂いた本だからなんだからね!!!
内容をパラパラと読んでみると、大量の数学記号が目に付きました。
どうやってキーボードから打ち込むんだと思ったら、
ちゃんとそれぞれの記号に対応した書き方があるんだとか。
(例: 『∀v.[S]P』 -> 『[@v.S]P』)
専用のエディタを使うとちゃんと数学記号の方で表示されるそうです。
とにかく記号の数がやたら多い。
ラムダ計算や論理学等を趣味でやっていたため、分かる記号も結構ありましたが、
知らない記号が沢山ありました。さすがフランス生まれの言語。
(「=」の上に「^」が乗った記号や、「(」を右に90度倒した「^」に似た記号とか。)
ということで、この秋からは頑張ってBメソッドを勉強しようかと思います。
この秋やらなければ来年の秋に。来年やらなければ再来年の秋に。再来年にやらなければ…
昨日のリリカルの続き

色々あった

10月 10th, 2008

bugyo.tkの鯖が新しくなったそうです。
以前より速く動くようになった…気がします。
下宿の近くが実は心霊スポットだったことが発覚しました。
夜に通ることも結構あるのに一度も幽霊に会えないよ…

前に作ったCommon Lispの演算子順位構文解析のプログラムをちょっと書き足して、
自然な感じでCLに中置記法を混ぜれるようにしてみました。

(let (x y)
#[ x = (y=5*(4+3)) - 2 ]
(format t "~&x=~A, y=~A~%" x y))  ; "x=33, y=35"と出力される

リード時に通常の式に変換されるので、
コンパイルしたら余計時間は一切掛からない優れもの。
ただ、使い道が全くない気がします(笑)
Common LispでSHIORI作ってます。
DLLからLispプロセスを起動してパイプで通信。
とりあえず、文字くらいは表示できました。

しかし、長い台詞を表示させようとすると、なぜか途中で終わってしまいます。
(このスクリーンショットがまさにそれ。話のオチが切れてるwww)
DLL側のrequest関数の戻り値は確かに台詞の最後までデータが入っているはずなのに…
こいつは困った。
ハードディスクを漁ってたらこんなものが出てきた。
激しくリリカル。
本日京都でようやくCLANNAD AFTER STORYが始まった。
今まさに鑑賞中だけど実に非常に素晴らしい。
(以下ネタバレ注意)
オープニングが渚祭になってると思ったら、他のキャラも出てた。
本来AFTERに出てこないことみが本編にも出てきてるし、
この調子でいくと、有紀寧の出番もあるかも。
ただ、強いていうなら風子分が足りない。
草野球編に風子が出ないなんて…
それはさておき、AFTERって言ってるけど、
ゲームにおけるAFTERじゃなくてどうも、アニメ版の続きという位置づけっぽいです。
2クールあるから、一応最後までやるのかな?
本当にこれから毎週木曜が楽しみです。

VC++2008でECLのmake

10月 3rd, 2008

ページの最後の方にmakeの一連の流れを書いてあります。
さっさとWindowsへのECLの入れ方を見たい人はそちらをどうぞ。

WindowsのDLLをCLを使って書きたいため、
CのソースにCLのソースを埋め込もうと思い、
ECLを試してみることにしました。
HDDをあさってみたら、以前ダウンロードした「ecl-0.9j.tgz」というファイルがあったので(*)
まずそれを展開。若干古そうな気がしたのでcvs updateをかけておきました。
cygwin上でmakeしようとしたんですが、上手くいかなかったので放置していたという恥ずかしいものです(笑)
そして、Visual C++ 2008 Express Editionをダウンロード。
しかし、「Visual Studio 2008 コマンドライン」(*)を起動しようとすると、
「使い方が間違っています」
という悲しいエラーが出てきました。
環境変数を設定するバッチファイル(vsvars32.bat)を覗いてみると、
怪しげな命令があったので、適当に書き直したらちゃんと動きました。
Microsoftどうなってるんだ… Vista用の命令?
(※)インクルードパスなどの環境変数を設定してコマンドラインを立ち上げるもの
それで、ECLのマニュアルに従い、nmakeすると、何故かエラーが発生。
場当たり的に修正しながら突き進んでみたものの、途中で完全に行き詰まりました。
ここで、一旦諦めかけたんですが、もしかしてと思い、
ecl-9.0j.tgzを展開しなおして、cvs updateなど余計なことをせずにmakeしてみると成功。
なんとも悲しいオチが待っていました。
SourceForgeの方に新しいバージョンのアーカイブが無いか確認しに行くと、
ecl-0.9l.tgzというものがあったので、これをダウンロードしてmake。一発で成功しました。
恐らくこっちの方が新しいと思うのでこれを使うことに。

> ecl
ECL (Embeddable Common-Lisp) 0.9i (CVS 2008-06-19 17:09)
Copyright (C) 1984 Taiichi Yuasa and Masami Hagiya
Copyright (C) 1993 Giuseppe Attardi
Copyright (C) 2000 Juan J. Garcia-Ripoll
ECL is free software, and you are welcome to redistribute it
under certain conditions; see file 'Copyright' for details.
Type :h for Help.  Top level.
> (car '(a b c))
A
>

疲れたので今日はここまで。
起動時に「0.9i」と出てるのが少々気になるけど、あえて気にしないことに。

* Windows XP上でのECLのmake方法まとめ *
1. Visual C++ 2008 Express Editionをダウンロード及びインストール
2. C:\Program Files\Microsoft Visual Studio 9.0\Common7\Tools\vsvars32.batを以下のように変更

* 変更前 *
call :GetWindowsSdkDir
if not "%WindowsSdkDir%" == "" (
set "PATH=%WindowsSdkDir%bin;%PATH%"
set "INCLUDE=%WindowsSdkDir%include;%INCLUDE%"
set "LIB=%WindowsSdkDir%lib;%LIB%"
)
* 変更後 *
rem call :GetWindowsSdkDir
set WindowsSdkDir=C:\Program Files\Microsoft SDKs\Windows\v6.0A\
rem if not "%WindowsSdkDir%" == "" (
set "PATH=%WindowsSdkDir%bin;%PATH%"
set "INCLUDE=%WindowsSdkDir%include;%INCLUDE%"
set "LIB=%WindowsSdkDir%lib;%LIB%"
rem )

3. ECLのアーカイブをダウンロード及び展開
4. スタートメニューから「Visual Studio 2008 コマンドライン」を起動。以下のコマンドを入力

cd (ECLの展開先)\msvc
nmak
nmake install prefix=(インストール先(ディレクトリは勝手には作られません))

当然のことながらファイルパス等は適宜修正してください。
自分のPCでしか試していないので、他のWindows XP環境で上手くいくかは不明です。
というか、VC++のバッチファイルは普通に考えたら触らなくてよさそうなんだけど…

Lispで演算子順位構文解析

10月 1st, 2008

夏休みが終わってしまいました。
そして同時に前期の成績が発表されました。
春先に「単位が足りなくい」という恐ろしくリアルな夢を見ましたが、
どうやら無事に4年生に進むことは出来そうです(4年で卒業出来るかはまだ分かりません(笑))。

という訳で(?)Lispで演算子順位構文解析を行うプログラムを書きました。
工夫した点はマクロを使って予め優先順位のテーブルをコードに展開したこと。
キャッシュのことを考えたら展開した方が遅いかもしれませんが、それは気にしない方針で。
あと、手を抜いた点はストリームからの読み込みはread関数を使ったこと。
演算子に大して何もしないリーダマクロ関数を設定して楽をしてます。
「マイナス」と「引く」を区別できない気もするけど、それも気にしない方針で。

(def-op-parser test-parser (input stack acc)
()
(((#\+ 1 :left) (let ((x (pop acc)) (y (pop acc))) (push `(+ ,y ,x) acc)))
((#\* 2 :left) (let ((x (pop acc)) (y (pop acc))) (push `(* ,y ,x) acc))))
((( #\( #\) ) )))

まず、def-op-parserというマクロを使ってパーサ関数を定義。
第1引数は作成する関数の名前、第2引数は後で使う変数の名前。
第3引数はリテラルに対する操作(今回はなし)。
第4引数は演算子とその優先順位、結合性、還元時の操作。
第5引数は括弧とその還元時の操作(今回はなし)。
そんで、これを使ってみたらこんな感じです。

OP-PARSER> (with-input-from-string (s "(1+2*3+4)*5+6*7")
(test-parser s))
(+ (* (+ (+ 1 (* 2 3)) 4) 5) (* 6 7))

なんか上手くいってるっぽい。やったね。

▲完成したときの心境

で、うかべんまで残り1ヵ月程となってしまいましたが、ネタの方が全然出来てません。
そろそろペースを上げてものを作っていかないと…
この構文解析も里々のパースの為に作ったものです。
それにしても、里々って、Lispの様に括弧を使って関数呼び出し等を行う言語なのに、
算術式において優先順位の為に括弧を使うってどうなんだろう…
半角と全角で区別って言うのは流石にかっこ悪い気がする…

Lispがメモリを食い尽くす

9月 29th, 2008

あるとき、emacs上でSLIMEを動かしながらCLのコードを書いてると、
突然HDDアクセスが多発して、ほとんどフリーズに近い状態になりました。
調べてみたら、Lispプロセスが一人でメモリを500MB以上も使っていました。

図1. メモリを食いつくすLispプロセス
一度プロセスを殺して再起動したら直りましたが、時々同じ現象に遭遇します。
メモリを512MBしか積んでいない私のPCにはこれはかなり致命的なのですが、
たまにしか起こらないので原因がよく分からず非常に困りました。
分かっているのは、この現象が起こると同時にemacsとSLIMEのコネクションが切れるということだけ。
そういや、CLISPは使用するメモリの量をあらかじめ制限できたような出来なかったような…
それができたらどうにかなるかも。…根本的な解決策じゃない気もしますが(笑)

unset-macro-character

9月 23rd, 2008

CLには特定の文字をマクロ文字に昇格(?)させてreaderの動作を変化させる、
set-macro-characterという関数が用意されていますが、
逆にマクロ文字を普通の文字に降格させる関数(unset-macro-characterと命名)がありません。
readtableをいじり回したいお年頃の私にはこれがなかなかつらいです。
で、無いものを嘆いても仕方ないので作ってみました。

(defmacro unset-macro-character (char)
`(set-macro-character ,char
#'(lambda (s c)
(declare (ignore c))
(let ((next1 (peek-char nil s nil (code-char 0) t))
(next2 (peek-char t s nil (code-char 0) t)))
(multiple-value-bind (mc nt)
(get-macro-character next1)
(if (or (char= next1 (code-char 0))
(not (char= next1 next2))
(and mc (not nt)))
(values (intern (string ,char)))
(values
(intern
(format nil "~A~S" (string ,char) (read s nil nil t))))))))
t))

基本方針として、

1. 指定した文字(char)に新たな関数を割り当てる
(その際に、set-macro-characterの第3引数にtを与え、区切り文字でなくす。)
2. その関数は次の文字が
 A. 空白文字の場合、文字charのみからなるシンボルを返す
 B. マクロ文字で区切り文字の場合、Aと同様
 C. 文字の終端の場合、Aと同様
 D. それ以外の場合、文字charと次にreadしたもの(おそらく数かシンボル)をつなげたシンボルを返す

というものです。
面倒だったので、大文字、小文字の区別に付いては特に考えていません。
valuesは戻り値を1つにするために使っています。
(internは値を2つ返しますが、リーダマクロ関数は戻り値の数は0か1と決まっているためです)
(code-char 0)はちょっとまずいかも…
で、動かしてみました。

CL-USER> (let ((*readtable* (copy-readtable)))
(unset-macro-character #\,)
(read-from-string "(a,b, c,(d) ,e)"))
(|A,B,| |C,| (D) |,E|)

なんか知らんけど、上手く動いたぞ。
わーい。

気分はこんな感じ
ただ、あまり考えてないので、ちょっと複雑なものがやってくると、
正しく動作しないかもしれません。
(直後にディスパッチング文字なんかがやってくるとまずいかも…)

T216のquoteについて

9月 23rd, 2008

時の人tazantさんのブログ「Lispのひび」にて最近書かれているquoteについて、
いくつが疑問が溜まってきたのでまとめて書き散らしています。
***全ての始まり***

独自の仕様については、バッククォートもクォートも同じクォートして扱う。
バッククォートはクォートの進化系だから。
リストを作るときも「’」を使い、consの代わりにも、appendの代わりにもなる。
(2008-09-20 マクロ)

「クォートとバッククォートの同一視」をして大丈夫なのかが最大の論点。
私はこれに対しては懐疑的。
CL等の

(defmacro my-search (key)
`(assoc ',key *a-list*))

(defmacro abbrev (shot long)
`(defmacro ,short (&rest args)
`(,',longlame ,@args)))

はどうやって表現するのか気になります。
(2つ目のプログラム片はOn Lisp 16.1節「省略」より)。
***’,foo or ‘(,foo)***

,x = (comma x)です。'(comma foo) = ‘(,foo)となるようになっています。
これはある程度独自な仕様ですから、そこら辺はご了承ください。
***中略***
‘(comma foo)と’,fooの区別は付きませんね。
(2008-09-20 マクロの世界へ)

[疑問1]
‘(comma foo) は結局 ‘(,foo) なのか ‘,foo なのかどっち?
私としては ‘(,foo) に展開されると少々やりにくい気がしますが…
***quoteはcommaが無くても特別?***

基本的に評価基準は一致しています。重大なことに気が付きました。
quoteは「,」をつけない限り、そのままです。次の例でどうでしょうか。
***中略***
よって、'(‘bar baz)というリストを作りたいなら、こうなります。

'('quote (('quote bar) baz))
;=> (quote ((quote quote) (((quote quote) bar) baz)))
;=> (quote (quote (((quote quote) bar) baz)))
;=> (quote (quote ((quote bar) baz)))
;=> (quote ((quote bar) baz))
=> '('bar baz)

(2008-09-20 マクロの世界へ – 2)

shiro 2008/09/21 03:27 ふーむ。クオートされた式中において、
1. (quote quote) は quote に置換
2. (quote comma) は comma に置換
3. (comma expr) は exprを評価して置き換え
4. 置換の結果生成されたquote, commaはその効力を失う
とすれば良いのかな。
(上同記事コメント欄)

[疑問2]
『quoteは「,」をつけない限り、そのまま』とのことですが、
実際には(quote quote) の動作が特別扱いされるんですよね。
(quote ((quote quote) a)) を評価すると (quote a)でしょうか?
***,@的な動作の例が見たい***

commaの仕様は、
* ,@みたいなもの。アトムならそのまま付け加える。
* リストなら外の()を外す。もし付けたいなら,(…)でいい
(2008-09-21 マクロの仕様)

[疑問3]
「もし付けたいなら,(…)でいい」とはどういうことでしょうか。
,(func arg)は関数呼び出しとなってしまうため、
括弧を付けたい、取りたいとは別問題になってしまう気がします。
[疑問4]
古い記事に戻りますが、

(= filter (macro (func arg) '(,func ,arg)))
=> (macro (func arg) '(,func ,arg))
(filter reverse '(1 2 3))
; 内部での状態=> (quote ((comma func) (comma arg)))
; quote評価後=> (reverse '(1 2 3))
; もう一度、マクロの最後を評価=> (3 2 1)
=> (3 2 1)

(2008-09-20 マクロ2)

commaがCLの,@のようにリストの外の()を消してしまうなら、
(quote ((comma func) (comma arg)))

(reverse 1 2 3)
となってしまうのではないでしょうか。
***マクロ引数の展開***

fujita-y 2008/09/21 12:14
もしかして、

(= func (lambda (x) (+ x x))
(= hoge (macro (x) '(x ,(x 1))))
(hoge func)
=> (quote (func ,(func 1)))
=> (quote (func 2))
=> (func 2)

ってことなのかな?

tazant 2008/09/21 13:16
マクロ引数(x)は展開時には評価されずに、
マクロとして再評価すると呼び出し元+引数の環境下で
普通の他の関数などの変数として評価されます。
(2008-09-21 マクロに挑戦! コメント欄)

[疑問5]
マクロ引数xが展開時にコンテキストに関わらず展開されないのであれば、
‘(x ,(x 1))
の最初のxはxというシンボルがそのまま残り、
,(x 1)
は展開時に『xという関数に1を付けて呼び出したもの』の値に置換されると思います。
つまり、
(x 未定義値)
というリストが出来上がるのではないかと。
これがfujita-yさんが仰るように
(quote (func ,(func 1)))
に変形されるのであれば、これは展開時にxが展開されるということではないのでしょうか?

CLのリーダマクロで里々を読む

9月 18th, 2008

うかべんに向けて色々模索中。
私をうかべんに誘って頂いたさとーさん
「里々がLispに近いシンタックスを持つ」
というアドバイスを頂いたので、
Lispと里々の類似点、相違点について話そうかと考えたのですが、
私がだらだらと30分も話してもただ眠くなるだけなので、
何か一つものを作って持っていくことにしました。
里々は

*終了
:えんいー
:なんか懐かしいなそれ

の様な、いかにも文字列の出力が主体のスクリプト言語らしい文法を持っていて、
これだけを見たらLispとは似ても似つかないのですが、
その一方で、変数の代入や条件分岐のシンタックスを見ると、

(set,[変数名],[値])
(if,[条件式],[真の場合に返す文字列],[偽の場合に返す文字列])

とのように、これはまあLispそっくりになっています。
そこで思いついたのが、

1. リーダマクロを書き、里々のスクリプトをS式として読めるようにする
(ちょっとしたパースをするだけだからきっと簡単なはず)
2. 読み込んだS式をCLのプログラムに展開するマクロを書く
(きっと、ちょっとしたマクロで書けると思う。根拠はないけど。)
3. ここまで出来たら、里々のスクリプトをCLのプログラムとしてコンパイルできるぞ!
(そしたら、バイトコード or ネイティブコード or Cのソースコードになる。)
4.きっと楽に書いた割には高速に動作するに違いない!
(特にCのソースコードを吐き出せたらちょと手を加えてDLLにしてSHIORIにできるはず)

という妄想をしてみました。
で、コードを書いてみたところ、それらしく見えるものができました。

#dic01.txt
*終了
:えんいー
:なんか懐かしいなそれ

このプログラムをリードすると、次のように変換されます。

(DEFTALK 終了 :TRUE
(話し手交代)
"えんいー"
(話し手交代)
"なんか懐かしいなそれ")

本当はこれを伺かと通信できる様にdeftalkマクロ展開する必要があるのですが、
今は面倒なので標準出力に文字を出すように展開。

(PUSH
(LAMBDA ()
(FORMAT T "~&~A:~A" "さくら" "えんいー")
(FORMAT T "~&~A:~A" "うにゅう" "なんか懐かしいなそれ"))
(GETHASH '終了 *TALK-TABLE*))

実際に、里々のスクリプトを書いたファイルをコンパイルして、
実行する流れはこんな感じです。

MATSURI> (matsuri-compile-file "dic01.txt")
;; Compiling file /usr/home/zick/dic01.txt ...
;; Wrote file /usr/home/zick/dic01.fas
0 errors, 0 warnings
#P"/usr/home/zick/dic01.fas" ;
NIL ;
NIL
MATSURI> (load "dic01.fas")
;; Loading file dic01.fas ...
;; Loaded file dic01.fas
T
MATSURI> (call-talk '終了)
さくら:えんいー
うにゅう:なんか懐かしいなそれ
NIL

なかなかいい感じに動いています。
しかし、今になってLispと一見似ている括弧を使ったシンタックスが厄介だと気づきました。
括弧の内側から評価されるというのはLispと同じですが、
内側を評価して得られた値を使って外側の括弧をリードし直す
という恐ろしい仕様が里々では使われているそうです。

((今の季節)の味覚)

この式を評価するとき、
まず、内側の (今の季節) が評価され 秋 になりますが、
その次には (秋の味覚) が評価されることになります。
(詳しくはこちらを参照してください)
これは思った以上に手強そうです..