SKKFEP開発メモ
とある餃子の挫折奇譚

 このメモは思考過程を残すためのものであり実装結果とは大幅に異なる可能性があります。

2011年4月1日(金)

 SKKIME1.5改を試験的に公開。
 この時はほんの遊び心のつもりだったのだが……

2011年5月

 爆ぜろLisp!取り戻せFORTH!
 ぬふははははははははははははははは!
 マイコォゥ!リッチャァァァァァァァァァァァ……!

2011年6月

 もうだめだ……
 自分の能力ではこれ以上skkimeを綺麗に直せない……挫折感パネェ……
 自分ごときの真面目系クズが小手先でどうにかできる相手じゃない。まず作り直してきちんと理解するしかない。IMEを。SKKを。
 というわけで何もかも捨ててゼロからやり直してみることにした。

 名称はSKK零式とかZEROとかそんな感じで。0からのフルスクラッチの意味とバージョン番号0の両方の意味。
 TSF上での表示名はskkimeを使っている人にも注目してもらえるよう似た名前としてSKKFEPとした。最初はRJJβとかSKK+みたいな名前にしようかと思ったのだが、現代にFEPって名前を使うのが時代錯誤っぽくてよいと思ったので。
 モジュール名はIME/SKK0/skkfep.dllとした。

 かつてSKK辞書で動作していたPC-E500のFEPの動作にまず追い付く。要するに単漢字変換動作。
 最低限の実装をまず作り、その過程で見えたカスタマイズ項目の洗い出しを行なう。
 SKKサーバとの連携処理等はあとまわし。でもsocial ime辞書の利用などが魅力的すぎるので現実逃避ネタに少しづつ実施する。
 併せて、XKeymacsに中途半端に依存したままになっていた、プロセス毎のCTRL+JによるIME切り替え機能をTSF側だけで完全に制御できるようにする(例えコマンドプロンプトであっても)ことを目標とする。
 XKeymacsとskkimeを改造中に思いついたアイディアで、IMEの初期モードを直接操作モード固定とするのではなく、寄生しているプロセスに応じて英数モードで開始するようにする。これにより、利用者はIMEのモード切り替えという概念を意識する必要がなくなる。アプリ側の変更も一切不要……かと思ったが、いまだに一部のアプリではいまだにIMEを悪のプログラムと見なして強制的に無効化ようとするコードを持っている(この場合MSIMEとかATOKみたいなメジャーで高機能お節介なプログラムが対象となっているのだろう)。アルファベット操作を寝こそぎ奪うIMEはまさに諸刃の剣ではあるが、将来はこういうアプリケーション側をどうやって改心させるかという厄介な宗教的問題になるのかもしれない。

2011年6月

 ……などと意味不明な供述を繰り返しており――

 気を抜くと一瞬で半年を消耗
 光陰矢のごとし

 WDKのサンプルはドキュメントが一切ないがMSのWebサイトで公開されているサンプルと中身はほぼ一緒。
 WDKのサンプルのほうが微妙に新しいようなのでこれをベースとする。
 9種類+2種類をひたすら読んでみるがまったく理解できず泣く
 というかまず英語がわからん
 うだうだしてても仕方ないので、とりあえずコンパイルしてみたらバイナリができた。……なんか行けそうな気がしてきた。

2011年6月15日(水)

 CLSID関連処理をサイズ縮小。

 参照カウントまわりの処理をサイズ縮小。

 アイコン追加。リソースの記述を変更。文字列による無駄なリソース指定を排除。
 アイコン作ったらテンション上がってきた。調子に載って16色フルに使ったのでTF_LBI_STYLE_TEXTCOLORICONは不要となる。ただし将来、skkimeのように単色アイコンを使いたい人向けのカスタマイズ用パラメータとして再び必要になるかもしれないので記録に残しておく。
 MSのサンプルと異なり、skkimeではアイコンをLR_SHARED指定している。これは良い方法だと思うので同じパラメータを指定するようにしておいた。

2011年6月18日(土)

 動作チェック効率アップのためのアップデータを作成。
 とりあえず古いTSFモジュールを重複しないようリネームし、その後でDLLをコピー、サービス登録する。
 こういう即興処理はバッチファイルで書くのが漢というもの。
 バッチファイルはMS-DOS時代と比べてだいぶ進化しているので、簡単な処理ならモダンなスクリプト言語に頼らずとも十分書ける。調べる手間を考えるとさくっとCで書いたほうが速かったりするような気もするが自己満足は大事である。

2011年6月19日(日)

 全モード切り替え可能にするテスト。
 使いもしない半角ローマモードをまっ先に廃止。1バイトのバックスラッシュと円記号を区別したい、みたいな用途で用意されたものだろうか?それなら他の方法を取ればいい(insert (make-char 'latin-jisx0201 ())みたいな感じか?)。というかEmacsじゃなねーから。どーでもいーよ。
 将来はモード管理は変換用途の抽象データ型に任せる予定。

 F6キー関連を削除。skkimeと同じホットキーで動作するよう変更。

 ホットキーは0,1,2…と複数登録できるようにし、キーごとに以下の機能をカスタマイズで割り当てるようにする。
1. TSF動作: トグル / ON / OFF
2. キーイベントの除去: 常に除去する / 異なる状態の時のみ除去する / 常に除去しない
3. ON後の内部モード: 変更しない / ひらがな / カタカナ / 全角英数 / 半角カナ / 半角英数
4. 動作時に変換途中のバッファがある場合: 確定 / キャンセル

 恐らく一般的な用途としては以下の2種類を用いることになる。
1: 単純なトグル動作: トグル / 常に除去する / ひらがな / 確定
2: CTRL+J専用動作: ON / 異なる状態の時のみ除去する / 異なる状態の時のみひらがな / 確定

で、これだとEmacs系で問題があるのだがこの対処法は3種類。あとで書く。

 なぜかdataセクションに8+8バイト(x64だと8+16バイト)の固定データが乗る。これは何か?
 調査方法案: とりあえずDllMainだけ残して全部消してバイナリを生成してチェック
→ポインタ配列はconstにしてくりゃれ?

2011年6月20日(月)

 引き続きKeyEventSink部分の実装。
 キー押下イベントはskkimeのものを参考に、モディファイヤキー取得APIの発行回数を半分以下に改良。
 skkimeではキーアップイベントの通知を完全に排除してMSのサンプルに比べて無駄なイベントを受け取らないようにしているようだ。この部分は素直に真似をすることにした。

 この構造ならSandSも実現できるかなと思ったが、基本的な動作としては「キーイベントをアプリから奪う」だけなので、SandSでスペースを離した時のスペースの発生は実現できないかと思ったが、そうでもなさそう。
 IME内部からキーイベントを生成することによりSandS風の動作をドライバ不要かつSKKのかなモード時のみ限定発動などという夢の動作が実現できるかもしれない。テンションあがってきた。

 キーイベントがきちんと監視できるなら、逆転の操作でシフトキーの単独押下を検出してスペースと同様に変換を発動、なんてのも可能になるはず。キーステート取得のAPI発行を行なう必要もなくなって一石二鳥か。
 しかし何かの拍子にモディファイアキーが押されっぱなしになる(例えばCPU負荷100%超えが続くとかで)と困るので、一定時間おきに状態を読み直すコードが必要になるかもしれない。

 デバッガ出力追加。

2011年6月22日(水)

 「IME状態に無関係に」任意のキーを離した時に空白を出力することに成功。ドライバレスでのSandSの実現の目途がついた。もちろんIME側で実現しているため、動作モードによって自動的に有効・無効を制御することも可能なはず。

 dataセクションの謎のデータはDLL用Cランタイムのしわざと判明。自前のCRTを作って回避した。かなり簡易処理なので今後問題になるかもしれない。テスト項目にオリジナル版とカスタム版CRTの比較を忘れないようにする。

2011年6月23日(木)

 キーマップに応じてシフトキーなどの変換を行なうにはどうするべきか。skkimeではToAsciiを使っているようだが、これはnumlockは反映しないとあるので、ノートPCでちゃんとskkimeが動くか確認してからにしたほうがいいかもしれない。ノートPCのNUMLOCKの処理はBIOSによるカスタマイズのはずなので問題ないとは思うけど。
 ノートPCで確認。NUMLOCKと連動して出るキーコードはテンキー側のもので、フルキー側とは干渉せず問題ない。そういやPHOENIX BIOSのカスタマイズをやってた先輩が、ノートPCのキー配置でカーソルキーを1段下げるデザインについてのこだわりを熱く語っていたな…。(遠い目)
 以前Twitterで冗談ネタとして書いた、FEPとTwitterクライアントの連動について考えてみた。例えばCTRL+Jを素早く二回入力した場合、候補表示ウィンドウにタイムラインが出るなんてのがいいかもしれない。この時直前に変換が行なわれた数語をもとにGoogleにqueryして超短周期ダイナミック広告なんて可能性もありか。

2011年6月24日(金)

 SandS動作の基本フィルタ動作についてメモを残しておくこと。

シフト動作について

A:
1. SPシフトキー単独押下(無効化)→シフト離す(設定された文字を出力)
2. SPシフトキー単独押下→シフトが効力を持つキーの押下(シフト込みの文字を出力)→シフト離す(何もしない)
3. SPシフトキー単独押下→シフトが効力を持たないキーの押下→シフト離す(何もしない)
4. 他のモディファイアキー押下済みの状態でSPシフト押下: 加工しない

 CTRLキー+アルファベットなどの操作をTSF/TIPで吸わない場合と吸う場合でどう処理しているか確認すること。→ 普通にeatenをfalseにしてるだけ

ひらがな:
l→半角英数
L→全角英数
q→カタカナ
Q→半角カナ (原作準拠:コンポジション開始)
C-J→何もしない (原作準拠:何もしない)

カタカナ:
l→半角英数
L→全角英数
q→ひらがな (半角カナ操作拡張: 半角カタカナ)
Q→半角カナ (原作準拠:コンポジション開始)
C-J→ひらがな (原作準拠:何もしない)

半角カナ:
l→半角英数
L→全角英数
q→ひらがな
Q→ひらがな (原作準拠:コンポジション開始)
C-J→ひらがな (原作準拠:何もしない)

全角英数:
普通のキーまたはシフトキーと普通のキー→全角出力
C-J→ひらがな

半角英数:
C-J→ひらがな

直接操作:
ホットキーを直接押下またはプログラムでシミュレート→ ひらがな

2011年6月26日(日)

 SandSの「押下中はシフト動作」と「離した時に本来の文字入力」の動作を分ける。

 SandSによってキー操作が加工された場合は確実に喰う。直接操作に反映させるケースであっても喰う。喰ったうえでセッション生成即確定とする。

 TSFにて「SandSのSPシフトキー押下中に他のキーを押して入力を行なったかどうかのフラグ」を保存する方法を考える。エディットセッションが動作中なら何らかの情報を保存することはできるかもしれないが、半角英数や直接操作モードでは基本的にエディットセッションは使えない。
…と思っていたのだが、試してみたら使えた(笑)

 でもまぁエディットセッションはTSF/TIP専用のラインエディタって感じだし、直接操作モードでは入力できるといっても一瞬で確定して消してしまうような使いかたしか(あえて)やらないようにするので、SPシフトを離した時にはセッションの情報は消えてしまっているはず。

というわけで保存先候補:
ファイル: コレジャナイ
レジストリ: やっぱこれか?何も考えずに実現可能だが美しくない(長時間経過すると最終的にディスクにデータがフラッシュされる。シフトキー連打して放置するとディスク書き込みが起きるなんて最悪すぎる)
DLLのグローバル領域: とりあえずこれ。ただしSPキーを押しっぱなしでウィンドウを切り替えた時に問題があるかもしれん。事実上問題なしか。
共有メモリ: そこまでやる必要あるか?まずは上記で確認してから検討
スレッドマネージャに持たせるのがベスト?
→結論: いや普通にstaticでいいから。

ToAsciiの動作チェック:

 OnKeyDown/OnKeyUpでSHIFT/CTRL状態を監視するとなぜか取りこぼすことがあったのでGetKeyStateを使う方式に戻した。

 SandSの時、スペースのリピートができないのはかなり不便。かといって通常リピートは透過するようにしたらちょっと思考が鈍い時などにスペースのリピートがうざい。ここは独自タイマを使って任意の時間(1000ms以上とか)押していたら喰わないようにする処理を加えるべきか。

 SandSの処理はある程度満足するものができた。

2011年6月27日(月)

 早速Emacs20で試してみたがシフト込みの子音がなぜか無視されるという問題が発生して使いものにならない。SKKモード以外であれば何も問題ないのがさらに謎。

 動作を見ていると、どうもSandSのシフトキー押下で入力した文字は0文字か2文字扱いとなっているようだ
 SKKモード時の文字入力処理だけに何か問題があるのだろうか?

0文字扱いの例:
aToMe → あ▽と*め
a(T)oMe → あ▽お*え

2文字扱いの例:
niNanRaka → に▽なん*ら
niNnanRaka → に▽んあん*らか

 Emacs内部を追うのはVC2010移植で懲りてもうやりたくねーので後回し。
 ちなみにEmacs23.3.1+IMEパッチのVC2010移植版の画面
http://twitpic.com/4yl7wb
 1週間ほど死ぬ気でがんばって移植したけどまだコンパイルが通るだけのレベルで内部動作が不安定。msvcと出るのはMeadowっぽくて美しいかな〜という一発ネタ。
 公開しようかなと思ったけど閲覧数20とか誰も興味ねーよって感じだったので泣いた。

2011年6月30日(木)

 ローマ字かな変換クラス作成
 組み込み向けの非力なCPUでも動作することを目指し、処理の単純化とサイズ縮小のバランスを考える。
 10KB程度のテーブルで全状態遷移が可能。

2011年7月01日(金)

 変換途中の子音の反映を追加。
 ローマ字かな変換のページ遷移後に変換対象の文字がなかった場合(oENTERとかwwwENTERなど)で変換不可能な文字が入力バッファに積まれるバグを修正。
 デフォルト変換テーブルの生成処理をTSFと分離。
 稼働状態のテーブルサイズを7056バイトまで削減。

2011年7月02日(土)

 変換途中の確定動作Egg-like-newlineの仮処理を追加。そして気付いたが、実際の処理は変換ルーチンの外部で変換前後の出力バッファ変化を確認して動作させるべきだった。というわけで削除。

 SandS処理でCAPS Lock状態が反映されない問題を発見したので修正。ToAsciiのCAPS Lock状態はVK_CAPITALのビット0で判定しているようだ。日本語ドキュメントにはビット0とは書いてないが英語ドキュメントに……も書いてねー。下位ビットみたいな記述はあるけど。もうちょっと具体的におながいします。

 他のSKKではCAPS Lockの扱いがどうなっていたかを軽く調べた。
 Emacs20+SKK10.62a改の環境では、CAPS Lock状態はシステム任せであり、CAPS Lock中の通常入力は変換開始になる。
 skkimeはシフトキーの状態を見て動作しており、CAPS LockのON/OFFは関係なく、シフトキー押下状態だけを見て動作する。これは極めて自然なUIだと思う。
 しかしながら今回は、同時押しが困難なポケコンのキーボードや、シリアル端末などの7ビットコードの一部分しか入出力手段がない環境であっても日本語入力が可能というFEPの旨みが生かせるようなものを作ることにする。将来はskkime互換動作も入れることを考えておく。

2011年7月03日(日)

 ローマ字かな変換クラスをTSF/TIPへ接続。まだコンポジションまわりは反映されないため入力途中の子音が表示されないがかなりSKKっぽくなった。

 TSF/TIPの仕様では、OnTestKeyDownでまずキー処理をフックするか返答した後、実際の処理はOnKeyDownで行なえ、となっている。この前提条件がとても厄介であることが判明した。
 OnTestKeyDownでEatenをTRUEにしてしまったキーはOnKeyDownでEatenをFALSEにしてもアプリケーションに渡らない。要するにOnKeyDownのEaten指定には何の意味もない。
 さらに一部のアプリではOnTestKeyDownが一切呼ばれない。Windows 7 x64になっても付属のワードパッドはあいかわらずこれ。しかしこの場合はEatenをFALSEにすればちゃんとアプリに渡る。

 これじゃ『CTRL+Aが入力された時、ローマ字かな変換途中の子音をキャンセルし、アプリにそのままキーを渡す』なんてことはできないのでは?
 しかし、skkimeではこの動作が実現されている。どうやっているのか解明しなくては。というか上記の制限がある以上、Test時にキャンセル動作させないとアプリにキーが渡らないのは確定的に明らか。

 こんなどうでもいい対策を考えねばならないのは、恐らくTSFのドキュメントで実装者と設計者の意志疎通が取れてないせいなんだろう。そもそもOnKeyDownで処理フラグを返せるんだからOnTestKeyDownなんていらんだろ。使ってないアプリも出る始末だし。どうすんのこの糞Microsoft仕様。DOS時代からMSのアジア向け言語の実装はほんと適当すぎてストレスがマッハである。

 とりあえず、この条件下で厳密な動作を求めるのは相当に骨が折れそうな気がするので、ここで1つ仮定を持ち出す。

1. OnTestKeyDownが呼ばれEatenにした場合は、その直後に他のコールバックが起きる前に必ずOnKeyDownが呼ばれるものと仮定する。

 どういうことかというと、例えばOnTestKeyDownでEatenしたのにOnKeyDownが呼ばれず、次のOnTestKeyDownが来るなんてことがないという仮定である。

 こうすればかなりプログラムはこんな風に簡略化できる。

1. OnTestKeyDownが来たら内部処理(変換などを含む)を行なってしまう。処理をしたキー処理のコードを記録しておく(処理の識別情報をキューに入れておいて、必ず後から呼ばれるOnKeyDownで確認する)。OnKeyDownは常にEatenを返す(今の糞Microsoft仕様では何を返してもEatenとなるので、将来に備えてその通りの値を書いておく)。
2. OnTestKeyDownが呼ばれずに直接OnKeyDownが呼ばれた場合は、キューを確認すれば判別可能なので、その場合はOnTestKeyDownで処理すべきコードを呼び、Eatenの結果次第で分岐させる。
 要するにEatenにFALSEを書き込めばアプリにキーが渡る時点で(何を書き込んでもキーが渡らなくなってしまう前に)処理を済ませるしかないという至極当然の話。

 キューにしておけば、OnKeyDownはいずれ呼ばれるけど先にまとめてOnTestKeyDownが連発する、なんて事態にも対応できるだろう(そんな実装はありえないと思うので必要ないとも思うが)。とりあえず、単にめんどいので最初の実装はキューではなくフラグにする。
 MSの指標からは外れるが、内部動作からしてこれで問題ないんじゃないかと思う。これならSandSのSPシフトキー単独押下の判定を2回もやるアホなコードを書く必要はなくなり、格段に処理が簡略化できるはず。

 とりあえず超簡略化版のコードを作成。動作確認してみたが特に問題はなさそう。
 フラグ判定では、OnTestKeyDown時のlParamのビット0が1になっていて、OnKeyDownでは0になっているので、それ以外の部分の一致で判定するようにした。ドキュメントにない動作がどんどん増えている気がするが人類最強なので気にしない。

2011年7月03日(日)

 メモリの優先度が低いプロセスから起ち上げたXM6でWindrvXM経由のファイル操作を行なう時、IntelのSpeedStepと滅茶苦茶相性が悪い件について。周波数上がんねぇ……なぜだ……。そういやファイル削除でシェルの機能つかうと異様に時間がかかるとかいろいろ問題が残っていた気がする。スレッドの優先度を上げるべき?
 見なかったことにしよう(超法規的措置)。

2011年7月04日(月)

 FASTCALL付け忘れ箇所を修正。
 STLが例外コードをぼろぼろ生成するのが辛抱ならんので自前の互換ライブラリに交換。4KBほどバイナリサイズを削減。

2011年7月05日(火)

 コンポジション処理がよくわからん……。なんかコンポジションのフラッシュがうまくいかず、文字が消えたりする。動作の規則性から、どうやらこちらの実装が根本的に間違っているらしいことはわかった。

 アルファベット(Latin)モード時の変換処理も変換クラス側にすべて移動。変換クラスの出力をそのまコンポジションに反映しやすくし、コンポジションの内容は、書き換え前の状態と先頭から一文字づつ一致判定し、異なる文字があった地点から書き換える。未確定の子音部分と確定部分や変換中の処理は全て同時に書き換えて一回のコンポジション操作に変更。

 とりあえず現状の構想。

左側 すいch■ちょく
右側 すい■chちょく

2011年7月06日(水)

 TextServiceクラスが何かにつけnew/deleteされているようなので、初期化の大部分をActivate側に移動し生成や削除の時間を僅かに短縮。
→言語バーが正常に出なくなる現象が発生。サーバ登録処理のクッキー値の初期化コードを消したことによりクッキー値が異常になっていたのを、INVALIDではないので正常値と判定したことが原因と見られる。ここは初期化必須だった。

 現在のコードでは、IMEのOFF操作時にコンポジションに残った文字がそのままアプリケーションに入力されてしまう。変換処理の内部ワーク自体は、モード変更時に自動的にクリアされる仕様なので動作上の問題は原理的に発生しない。とりあえずしばらく放置してても問題ないが、いつかは直さねばならない。
 修正案は2種類。
1. 変換処理側のモード変更の際にコンポジションを書き換える処理で対応
2. IMEのON/OFF切り替え処理の際にコンポジションを書き換える処理で対応

 かなロック状態だとLatin(半角英数)モードの出力だけ半角カナになる。この状態でもSandSが動くことから、アンチかなロック機能というネタを思いつく。

2011年7月07日(木) スクリーンショット初公開

 コンポジションは普通のラインエディタと同じ扱いになっていることがわかった。
 てっきり表示に影響があるだけの存在かと思っていた。Eatせずにアプリケーションにキーを渡しているはずなのに、まったくアプリ側に渡らないことがあり判明した。カーソル移動やデリートキーなんかも吸い取るというか普通の編集操作ができてしまうようだ。

 OnEndEdit の存在意義がわからない。とりあえず削除しておく。
 TextEditSinkのクッキー未定義判定をなくした。これで昨日の問題は解決し、コンストラクタのコードを削減できるはず。できた。
 スレッドマネージャとシンクの初期化処理を統合することで同様に初期化処理の軽量化が可能なはず。いつか直す。

 変換処理時にコンポジションがなかった場合、コンポジションの新規生成処理が走るのだが、MSのサンプルではTfEditCookieを二重に生成される箇所を1回だけの生成で済むよう改良。
 変換操作中と通常のローマ字かな変換入力中の処理を統合。

 Googleの検索予測候補が正常に出るようになったので、ひとまずTwitterにスクリーンショットを初公開。「SKKで」予測候補が正常に表示され、かつタイトル部分に「▽」が付かないというのは個人的にはわりと衝撃的な話だと思うが、ぱっと見で違いに気付くような変人はまずいないだろう。いつかきっと一部のコアな人達には分かってもらえると信じて貼りつづけてみようと思う。

2011年7月08日(金) モード変更に関する考察

 ddskkはまったく使ったことがなかったのだが、機能を調べてみたら > で接頭語・接尾語とか ; で検索ヒントとか、いろいろ便利に進化しているようだ。これは良いのでぜひ取り込むべき。というか操作に慣れなければ。そうだ、どうせ新しい操作に慣れくらいなら、もっと新しい操作を作って自分を最適化してしまおう。

 大胆な改造案。従来のAbbrevモードにまったく利用価値を感じず、キーバインドを潰して10年ほど使っていたのだが、日本語とアルファベットが混在した用語、例えば辞書検索や電卓、AA検索などの用途に、なるべく簡単に英単語も入力・検索できるほうが便利ではないかと思うようになった。しかし / はC++コメ入力で日本語ON状態で即入れたい。どうすんべ。

案1: L をAbbrev開始キーにする
 そもそもcorrupted-consonantがあるので全角英数モードを使う必要がほぼなくなってしまった。よってこの操作を潰してAbbrevに割り当ててもまっったく問題なし。
 どうしても全角英数を入れたい時は、Abbrevの最後でC-qで変換すればいい(従来通り)。
 C-qなんてクソ押しづらいので、コンフィグでC-lとか変換キーを割り当ててこれをデフォルトにするのがよさそう。押しづらいけど、一応C-qはそのまま残しておくほうが親切だろう。

 ついでに他のモード遷移も押しやすくする。
 lやLは従来はアルファベット入力モードへの遷移だが、変換操作中に押すことはまずない。そこでこの操作を全角カタカナ変換操作と解釈するモードも用意しておこう。これで日本語入力時にqを押すことは一生なくなるはず。すなわち、将来qに別の機能を割り当てられるということだ。
 だいたいカタカナモードってのが超気にくわねぇ。C-jで戻らないから知らないうちに全部カタカナになってるとか泣ける(画面見ない人なので)。もうデフォルトではモード廃止にしてしまいたいくらい。一応オプションで戻せるようにはするべきだけど。これでモード遷移が「Latinとひらがな」の2つだけの認識でよくなり、認識しやすくなるはず。カタカナ入力のときはシフト押しで開始、lやLで変換。AbbrevのときはLで開始。Lは変換キーの一部的な扱いになる。ホームポジションに近いキーなのがいい。

 いや、qの機能はそのままにしておいて、C-j空押しで必ずひらがなに移行するようにしたほうが、思考力が落ちた状態でも操作しやすそうだ。要するに、迷ったらC-j連打。これデフォルトにしとこう。

案2: 通常の変換と統合する
 要するにMake とやったとき、「負け」だけではなく「メイク」とも出てほしい的な。
 しかし完全に統合するのはいろいろ仕組みが変わりすぎて思考が中断される新たな要因になってしまいそうなので、例えばMake@とかMake[Shift+ ]でAbbrev変換相当のほうがいいかもしれない。
 ただしこれは従来の操作との齟齬が発生する。QおよびL開始だとだめ。しかしこの2つはほとんど使ってなかったので個人的には何の問題もない。従来との互換性はばっさり切り捨ててしまい、逆転の発想で「Q」や「L」も▽モード開始として受け付ければよい。
 これはちょっと大胆すぎるか……。あらゆるコマンド操作が前置で済むSKKの世界に後置の操作を持ち込む感じだ。とはいえ変換操作というのは明らかに後置だしうーん、統一感としてはどうなんだろう。

案3: 子音をシフトを押しながら素早く二重打鍵することで拡張機能(プラグイン)呼び出しとする。これは案1・案2と併用可能。

Q または QQ 半角かなモード
L または LL 全角英数モード
CC 電卓
CC10B → 「1010」 2進数変換
CC10H → 「A」 16進数変換
DD Excite翻訳
DDmake → 「A 1 (創造して)作る,造る 《★【類語】 ⇒→build》: a 〈ものを〉作る,造る,製作製造する,組み立てる,建設建造する; 〈映画・テレビを〉作る; 〈詩・文章などを〉創作する,著わす.」
GG Google検索
GG → ブラウザの新タブが開き入力開始 (インラインでの動作は要検討)
TT Twitter
TT → 表示モード開始 (検索ウィンドウにタイムラインが表示、候補一覧と同じ扱いとなり、選択するとそのままアプリに文字列として渡る。)
WW → 書き込みモード開始

 構想段階だがもっと大胆な発想を書いておく。歌詞をメモした時など、漢字がよくわからない状態で確定した単語、高速すぎて変換が置いつかない状態で書きとめた単語には自動的にマークがつくといいと思う。あとでじっくり調べて確定させたいと思うことがよくある。手でメモを書くときは全部カタカナにして書いておいたりする。それと似たようなノリ。わざと▼とか【】を混ぜてくれるようなモードがあるといいのかもしれない。アプリ側の機能にするべき範疇かもしれないが。またスクリーンエディタ作るか。ワンチップで。

2011年7月08日(金)

 MSのサンプルにはコンポジション表示属性に使うGUID Atomの開放処理がないようだ。どうせOSが回収するだろうから別に回収なんてしなくていいんだろうが、一応コードは書いておいたほうがいいかもしれない。

 インポートテーブルを見ていたら純粋仮想関数のエントリである__purecallが残ってた。潰す。
 あとCOMオブジェクトを継承したクラスのインタフェースやデストラクタにいちいちvirtual宣言するのは冗長に見えてアホっぽいので削除したいと思った。確認コードを書いてvirtual扱いとなっていることを確認して削除。

 残存ソースを全てリファイン。
 テキストサービスのメインクラスのメソッドのうち公開不要なものをprivateに移動。

2011年7月09日(土)

 GetDisplayAttributeInfo()はダミー実装でも問題ないようだ。
 調子に乗ってEnumDisplayAttributeInfo()もダミーにしたら属性が変わらなくなった。こちらはきちんと書かないとだめなようだ。

 さらにMSのサンプルではITfDisplayAttributeInfo::GetAttributeInfoでレジストリを見ているが単にデータが格納されればいいだけのようだ。そもそもコンポジションに色をつけるたびに呼ばれるので、その都度レジストリを読むのはあまり効率が良くないと思われる。
 あとどんな時にSetAttributeInfoが呼ばれるのか(Updateとは何ぞや?)という状態なので該当コードはばっさり簡略化した。
 SKKIMEで謎だったMSサンプル由来のコードは、いまだ謎なことも多いが、不要な部分はだいたい見えてきた気がする。コンポジションの色に関する処理は大幅に効率化できそうだ。

 コンポジションの表示属性情報はすべて配列にまとめて処理を一元化。
 属性一覧

0. 漢字変換で入力中の文字 (文字標準 背景標準 アンダーライン赤)
1. 漢字変換で変換中の文字 (文字選択 背景選択 アンダーライン赤)
2. 英語変換で入力中の文字 (文字標準 背景標準 アンダーライン青)
3. 英語変換で変換中の文字 (文字選択 背景赤 アンダーライン青)

4. ひらがな変換中の子音 (オレンジ)
5. カタカナ変換中の子音 (緑)
6. 半角カナ変換中の子音 (黄)

2011年7月10日(日)

 コンポジションが反映されるタイミングがわからない。
 メモ帳とワードパッドで文字をいくつか入れておき子音入力してからCTRL+A
メモ帳: CTRL+Aが実行された後に確定された子音が入っている(としか思えない動き)
ワードパッド: 子音確定後にCTRL+A
 TAB,ENTER,CTRL+Vなど
どちらも、コンポジション確定後にキー処理
 メモ帳のCTRL+Aのみ、コンポジション確定前にキー入力処理が行なわれてしまうようだ。他のアプリでもこういうことがないか、チェックするべきだろう。

 問題発覚。Excelのオートコンプリートと、ローマ字かな変換の子音入力がバッティングすることがある。skkime原作も同じ動きをするのでこれは対策が必要だ。
 そもそもオートコンプリートの動作について詳しくは分かっていないが、挙動を見る限り、コンプリート候補を提示した直後のコンポジション側からの入力を無視して変わりにアプリ側候補が入力される、という動作になっているようだ。これはMSIMEなどで、オートコンプリートを出した直後に確定(ENTER)を押した時、コンポジションからの入力が入らないようにするための挙動なのだろうと思われる。
 しかしSKKでは通常のかな入力が確定不要で入力されてしまうため、この機能と猛烈に相性が悪い。
 対策としてはこんなところか。
1. オートコンプリート自体を無効化(APIで制御する、アプリ側で無効化してしまうなど)
2. 確定不要のローマ字かな変換入力中だけはオートコンプリートが発動しないよう、何らかの方法を取る
3. オートコンプリートが効きそうな時はコンポジション側からアプリに文字を入力しない(コンポジション上のみの表示だけ変化させる)

 コンポジションのTF_DA_ATTR_INFOをTF_ATTR_INPUTからTF_ATTR_NOTCONVERTEDに変更してみた。動作変わらず。

 子音の文字をわざと1文字ずらして表示してみた。c とタイプすると d と出るようにした状態でcを押すとcではなくdの文字に反応した。キー入力ではなくコンポジション本体の更新と連動しているようだ。

 次、子音候補をMSIMEのように全角で出力してみると動くのでは?と思って試してみる。候補を全角で出そうなんてローマ字かな変換クラスを実装した時点では考えもしなかったので、効率を考えて実装しなおした。……結果、何も変わらず。全角でも半角の文字に予測がヒットしてしまう。文字種別は関係ないと考えるべきだろう。

 コンポジションの文字列が空になったらコンポジションの開放処理を動かしていたのだが、この処理は必要なさそうだ。自動的に開放コールバックが走っている。
 というわけで自動開放に任せてみた。だめ。オートコンプリートの挙動はコンポジションの自動開放と無関係のようだ。

 次、明示的に開放してコンポジションを経由しない文字出力を使う方式。だめ。そもそもコンポジションを閉じた直後に出力を呼んでも出力されない。原因調査は後回し。

 次、コンポジションにダミー文字を常に入れておく方式。だめ。まず見た目に難あり。キー入力がすべてコンポジションに吸収されてしまう。しかし副作用としてコンポジションの無駄な生成が抑えられるというメリットを発見。うまく制御できればかなり有用かもしれない。

 次、子音候補が存在する時だけ、ダミー文字(空白)を末尾に付加する方式。成功。見た目に難あり。

 次、ゼロ幅文字を試す。
 文字コード0を試す。だめ。そもそもテキストが追加されていない。
 http://nicowiki.com/?%E7%A9%BA%E7%99%BD%E3%83%BB%E7%89%B9%E6%AE%8A%E8%A8%98%E5%8F%B7
 ニコ動まとめWikiより、優秀とあった0x200C(ZERO WIDTH NON-JOINER)を試す。だめ。通常空白と同じ幅で表示されてしまう(しかし編集領域に入力されると幅0になる)。ただしプロポーショナルフォントで表示されている箇所は思った通りの結果が得られる。ひとまずこれを子音のオートコンプリート回避策として採用。

 結論。ローマ字かな変換処理に対して、以下のオプションを用意する。

入力途中の子音
●表示する標準 ○空文字追加して表示(指定アプリのみ) ○表示しない
一部のアプリケーションにおいて、入力途中の子音にオートコンプリートが反応して正常な入力が行なえない現象への対応方法を選択します。
●カーソル左側標準 ○カーソル右側
子音アルファベットの表示位置を選択します。
●全角文字標準 ○半角文字
子音アルファベットの表示形式を選択します。

変換対象外の子音
●確定して入力する標準 ○確定せずに消す
子音アルファベットだけで構成された語の入力を、ローマ字かな変換モードのまま行なうかどうかを選択します。
●全角文字標準 ○半角文字
子音アルファベットの確定形式を選択します。

 他にも試しておきたいこと。

 案1. バッファに子音候補のみが存在する場合、コンポジションの確定処理を2回行なってみる。1回目は付加文字あり、2回目は付加文字なし。これでオートコンプリートが消えてくれたりするといいのだが。

2011年7月11日(月)

 IME Open/Close時の挙動がいまひとつなのでまともに動くよう直すことにした。
 現在の問題点。

1. 他のIMEから切り替えた後のOpen/Closeがきちんと同期していないようだ。起動時にOpen/Closeの状態を取得して初期値に反映してみる。

2. Close時に入力途中のコンポジションの内容が反映されてしまうので、モード変更の直前にコンポジションの内容をきっちりクリアしないといけない。

 TSF関連インタフェースの呼び出し順序から、内部動作を推定してみる。
 コンポジションを使った編集の時、OnTestKeyDownでコンポジションを生成した場合
OnTestKeyDown
StartComposition
OnEndEdit
OnKeyDown
OnEndEdit
OnCompositionTerminated
OnTestKeyUp
 となるようだ。

 二種類あるOnSetFocusは、引数1つのほうがActivate直後に、2つのほうはウィンドウのアクティブ状態変化ごとに呼ばれている。
 ThreadMgrのインタフェースはOnSetFocus以外はそもそも呼ばれていないようだ。

 昨日無効化したEndCompositionだが、コンポジションの生存期間を確実に管理するためにはきちんと呼ばないとだめかもしれない。これを呼んできっちり管理することで、OnCompositionTerminateが呼ばれたのは管理外の消去(IME制御APIやホットキー操作時)であると認識できる。
 よって、2はあっさり実装できた。

 問題は1。と思ったら単にActivate時にOpenかどうか調べるだけで解決した。
 アプリごとに日本語入力状態を切り替えるテストコードをそのうち試すべき。

解決・IME OFF時コンポジションに残存した文字がクリアされずに排出されるバグ
解決・一度日本語モードにしてから他のIMEに切り替え、さらにSKKに切り替えるとLatinモードとなるがこの仕様でいいのか?また切り替え直後のホットキーによるトグル動作が行なわれず1回目はLatinモードのままなのは何故なのか解明

2011年7月13日(水)

 送りがなの挙動を見直し。送りがな終了判定を母音ではなく変換テーブルの状態遷移で判定する。

通常 → 開始待ち K 母音が出現するまで開始待ち→入力中
開始待ち → 無視 KE 小文字扱いとする
入力中 → 送りがな KeT 母音が出現するまで完了待ち→完了(変換処理へ)
送りがな → 無視 KeTTA 小文字扱いとする

ShiroI しろ*い しろi 子音バッファに文字なし
OU お*う おu 子音バッファに文字なし
DASSU だ*っす だs 子音バッファに文字なし
DaSsu だ*っす だs 子音バッファに文字なし
DasSu だっ*す だっs 子音バッファに文字(s)あり→変換後の位置から開始→未確定(OK)
DassU だっす 不可 子音バッファに文字(s)あり→変換確定(エラー)
KonA こな 不可 子音バッファに文字(n)あり→変換確定(エラー)
KoNa こ*な こn 子音バッファに文字なし
KoNn こ*ん こn 子音バッファに文字なし
KonN こん 不可 子音バッファに文字(n)あり→変換確定(エラー)
KoNde こ*んで こn 子音バッファに文字なし
KonDe こん*で こんd 子音バッファに文字(n)あり→変換後の位置から開始→未確定(OK)
KondE こんで 不可 子音バッファに文字(d)あり→変換確定(エラー)
DaKkya だ*っきゃ だk 子音バッファに文字なし
DakKya だっきゃ 不可 子音バッファに文字(k)あり→変換途中(エラー)
DakkYa だっきゃ 不可 子音バッファに文字(kk)あり→変換途中(エラー)
DakkyA だっきゃ 不可 子音バッファに文字(kky)あり→変換確定(エラー)
ONa お*な おn 子音バッファに文字なし
OnA おな 不可 子音バッファに文字(n)あり→変換確定(エラー)

 変換トリガ発生時点で子音バッファに文字がある場合、一度かなに変換してみて出力発生かつ子音が残っている場合のみ送りがなとして採用する。これによりnで開始する送りがなの挙動が改善される。具体的には長いゴミを登録してしまう問題の改善とナ行の送りがなをシフト押しっぱなしで打ち込んだときのストレス軽減。

2011年7月13日(水)

 ローマ字かな変換エントリに「トゥ」がないのが気になったがどう綴るべきか。
 個人的にはTyuを「チュ」と読むのはものすごく――例えるならジショがjishoではなくjisyoと記述されているのを見るのと同じくらい――嫌なので潰すことにした。原作至上主義路線からは完全に外れて独自路線まっしぐらな気がするが、このまま突き進むのみ。
 ここは大事なのでローマ字かな変換表を作ってマニュアルに入れるべきだろう。そのうち書く。

変換例:
ティーン tyi-n (5打鍵) texi-n (6打鍵)
テュポーン tyupo-n (7打鍵) texyupo-n (9打鍵)
トゥデイ twudei (6打鍵) toxudei (7打鍵)
ツォルフェ tsorufe (7打鍵) tsuxorufe (9打鍵)
フィレンツェ firentse firentsuxe +2

2011年7月14日(木)

 ローマ字規則見直し。kkya → kky + kya に分解して送りがな変換が途中でも動くように改良。

 JIS X 0201カナの変換テーブルを作成。これでローマ字かな変換テーブルをさらに小さくでき、単語変換→q,l押下でのカタカナ変換操作のような感じで半角カナ変換もできるようになる。まだテーブルを作っただけ。半角カナの文字数はほぼ64文字なので6ビット、濁点情報含めて8ビットに収まるのが美しい。

子音入力中の確定コマンド(スペース)は? → 実装済み
候補入力中の確定コマンド(スペース)は? → 通常モードに遷移してしまう(バグ) → 修正完了
送りがな中の確定コマンド(スペース)は? → 遷移しない → 修正完了
変換候補表示中(送りがな変換、単語変換、英語変換時)の確定操作 → 修正完了

まだバグのある箇所
変換候補表示中の確定(実装済)、次候補(スペース)、前候補(x, CTRL+G)、削除(X)操作、文字入力操作
BSの挙動が変。送りがなの途中で削除しまくってもモードがずっとそのままとか。

2011年7月15日(金)

 Windows 7 x64 と Windows XPとで _kbhit() の挙動が違う。Win7 x64だと _kbhit() が正確な値を返さないときがある。しかしまぁよくよく考えたら _getch() だけで十分だった。

 送りがなの情報(送りがなの文字と位置)をきちんと記録するようにすること。
例:
UgoKe うごk うごけ 送りがな'k' 位置2
FusawaShi ふさわs ふさわしい 送りがな's' 位置3

2011年7月15日(金)

 現在の処理をまったく変更せずに、ローマ字かな変換テーブルの拗音部分を共通化すれば大幅に縮小できそうなのでやってみた。
リンク情報: 530個 → 417個 (78.7%)
ノード情報: 1318個 → 685個 (52.0%)

処理を変えずに拗音の変換テーブルを共通化すると リンク情報: 530個 → 417個 (78.7%) ノード情報: 1318個 → 685個 (52.0%) 処理も変えればさらに縮む上に展開せずそのまま実行時に使えるはず

 さらに遷移時に母音で終わるエントリはテーブル作成時にツリーを辿っておけばテーブルも処理も簡略化できることに気付いた。しかし子音のエントリはどうにもならないか。

 「てぃありんぐ」のようなあ行の文字同士が隣接しているときに困るので対策。初期状態を4種類から8種類に変更。

 ローマ字かな変換規則を定義ファイルから変更できるようにする。ついでにその際データフォーマットもさらに改良する。

2011年7月17日(日)

ノード種別について
 2ビット必要。うち1ビットはデータ長判定にも利用可能。
MSB=00 4バイト 文字あり 子ノードなし パターン変更なし (枝の末端 一番多い)
MSB=01 4バイト 文字なし 子ノードあり パターン変更なし (途中の枝 次に多い)
MSB=10 8バイト 文字あり 子ノードあり パターン変更不問 (途中の枝 1つだけ)
MSB=10 8バイト 文字不問 子ノード不問 パターン変更あり (いろいろ 少し)

 子ノードが存在する場合、次パターンには移動しない。
 子ノードが存在しない場合、インデックス(文字)がある。文字を出した後次パターンに行く場合もある。
MSB=10 8バイト 文字あり 子ノードあり パターン変更ありえん(実現可能だが実用的でない)
MSB=10 8バイト 文字あり 子ノードあり パターン変更なし
MSB=10 8バイト 文字あり 子ノードなし パターン変更不問 最大4文字
MSB=10 8バイト 文字なし 子ノードあり パターン変更不問 最大4文字
MSB=10 8バイト 文字なし 子ノードなし パターン変更不問 子ノードなしで代用

インデックスについて
 インデックスを経由する目的は、データ出現率の多い「同じような短かい文字の参照」と、出現率は少ないが「極端に長い文字列」の両者をなるべく少ないビットで表現することにある。あと、可能なら0をインデックスなし(0文字)として動作するようにしたい。
 試作したデータで7文字以下のパターンは重複を除いて140個程度(ほとんど0〜1文字)。インデックスに8ビット、長さに3ビット程度で十分表現可能。
 それより長いデータはオフセットテーブルを経由する。オフセットテーブルの番号8ビットのみ。この場合の文字列はゼロ終端で格納する。
 ひとまずインデックス12ビット/文字列長4ビットとする。なお値0は文字列長0となる。

ノード種別
前半
code:
Bit7: 常に0

flag:
Bit7: ノード長 0:4バイト 1:8バイト
Bit6: 領域の用途 0:インデックスとして使用(必ず子ノードは存在しない) 1:子ノードとして使用
Bit76が00の場合: インデックスあり確定 子ノードなし確定
Bit76が01の場合: インデックスなし確定 子ノードあり確定
Bit76が10の場合: インデックスあり確定 子ノードなし確定
Bit76が11の場合: インデックス不明 子ノードあり確定

後半
pat0:
Bit7: 1なら空のパターン(状態遷移リセットのみ)

pat1:
Bit7: 1ならインデックスが存在する

メモ終了。検討結果をまとめる。

方式1:
 従来のテーブルをほぼ半分のサイズまとめ、次パターンの文字情報を最大2文字追加した形となる。かつビットテストのみでシフト演算はほとんど不要。
 要するにflagのBit6だけを見れば子ノードの存在状況が確定する。子ノードなしは値0となるため、以下の式(ビット判定1回)となる。
(flag & 0x40) ? share : 0
 文字インデックスは木を辿った最後に1回だけ参照できればよいので、ちょっと手間をかけて判別する。こちらもインデックスなしは値0となるので以下の式(ビット判定1〜3回)となる。とはいえ、ノードの9割以上はサイズ1であるため、ビット判定2回目の時点でほとんどの場合は値が確定するはず。確定しない確率は1/140程度。
(flat & 0x40) == 0 ? share : ((flag < 0 && pat1 < 0) ? share2 : 0)
 パターンが存在するかどうかは以下の式で判定可能。
flag < 0 && pat0
 コード量はそれほど多くないけど、あとからソースを読んで理解しづらいのが難点。

方式2:
 どうせならshareの最上位ビットを子ノード判定フラグとして使用すればメモリ参照回数をさらに減らせる。弊害としてindexの文字列長部分のビットが1ビット減り、インデックステーブルを実装するまでは最大15文字が上限になってしまうが事実上まったく問題ないはず。インデックスと子ノードが混在する枝はフラグのBit6が常識的に考えると0になる(次パターンはない)という条件がつくためさらに高速化でき、かつ次パターンを4文字に拡張できる。ぴったり歯車が合った感じだ。
child: share >= 0 ? (ushort)share : 0;
index: share < 0 ? (ushort)share : (flag < 0 ? (ushort)share2 : 0); 結果が0以下なら正常値(データ生成の段階で整合性を取る上、indexの上位はビットシフト&マスク対象なので実行時に値をチェックする意味はない)
exist: flag & 0x40
 さらにBit6が1のときはBit7も常に1であるとの前提にすることでパターン存在確認もビットテスト1回で済む。これで速度面でも問題なし。

 これでワンチップマイコンでも十分通用する処理量とテーブルサイズになった。果たしてワンチップマイコンで日本語入力処理をするのかどうかという話もあるが。

2011年7月19日(火)

 さらに木の共通部分を状態遷移の途中に含められるようにして、木自体をユーザ定義ワイルドカードの判定テーブルとして使うことで、さらに1エントリのサイズを8バイトから4バイト/8バイトの可変することで半分以下にサイズを縮小。やってることは手動lexそのままなのだが、テーブルサイズはかなり小さくなって文字テーブル込みで現在1.8KBとなった。これもさらに2割くらい小さくできそう。
 SKKは n の扱いがわりと特殊(木の途中にあって、候補がない時は幹に戻るときその文字を掃き出す)なので、これをflexでどう処理するのかちょっと思いつかないので、flexで書くとどうなるかというのも考えておくべき。いい教材になると思われる。

 さらなるテーブル縮小について考えておく。
 ほとんどのノードは文字を吐いて終了するだけなので、遷移用の文字8ビット+変換後の文字7.5ビット程度の情報で十分となる。残りのノードはほとんど内容が同じなのでもう一段テーブルをかませば1エントリ2バイトにできるはずだ。

 計測してみた。単純ノードが215個、特殊ノードが165個。このうち単純ノードが2バイト、あとは4バイトと換算すると1.06KB。やばい。最初に作ったテーブルサイズが6.7KBだったのを考えると相当魅力的に見える。

struct {
// packet 0
uchar code;
uchar mode;
// packet 1
uchar share;
};

途中のノード
if (mode < code_size) {
result.index = mode;
result.csize = 1;
child = 0;
return;
}

末尾のノード
if (share == term_normal) {
result.index = share2
result.csize = 1;
child = 0;
return;
}

2011年7月19日(火)

 直音のエントリは5つを一塊(木の1分岐)で処理するようにすることで相当数のエントリを共通化できる。現在のバージョン14ではこうなった。
// 分岐 67箇所 ノード 252個 サイズ 316パケット + 146文字 (1556バイト)
 まだまだ縮むよ!

 全てのパケットに隣接の有無のビットを1ビット持たせていたけど、よく考えると全部持つ必要はなくて、連続個数が先頭に可変長で記述されていれば2つ以上連結する場合は得になるかもしれない。しかしほとんどの分岐先は1ノードで終わることが多いのであった。一番多いのは拗音のエントリで、例えば『ぴゃ』行だと「母音なら『ぴ』を出力後$ya行に移動」という1エントリがあるだけ。ほとんどの音はこのエントリと同じか、1〜2文字の例外則を含んだ形状となっている。

2011年7月21日(木)

 文字インデックスのみのエントリを16ビットで表現するようにしてみた。かなり縮むかと思ったが、110バイト程度しか縮まず。何よりエンコード・デコード処理が複雑になりすぎて100バイトどころの増加ではないので涙を飲んで没。

 かわりに、ノードの情報量を1ビット削って、文字インデックスのかわりに直接1文字埋め込めるようにして100バイトほど削減。現在こんな感じ。
// 分岐 67箇所 ノード 250個 サイズ 314パケット + 90文字 (1436バイト)

 これ以上はデコーダ側が複雑になるのでひとまずこれで完了とする。

2011年7月24日(日)

 さらに改良。

 ほかにも、拗音のあいうえおの状態遷移を含んだルールを改良してパケットサイズを縮めた。
// 分岐 67箇所 ノード 248個 サイズ 305パケット + 90文字 (1400バイト)
 こんな感じになった。特殊子音判定を入れたので、従来のルールでいちいち表記しなければいけなかったルールや、表記漏れが起きていた部分を完全にカバーしてある。
 あとはパケットのデコード処理をだいぶ簡略化・共通化した。このままワンチップマイコンに載せても動きそうな勢いである。

2011年7月27日(水)

 複数の変換規則を切り替えることで送りがなの促音判定と雑草入力を共存させる。この結論に辿りつくまでずいぶん時間がかかってしまった。ドライバレスSandSも成功。IMEクローズ状態でもキーを乗っとれるみたいだけどいいんじゃろうか…。
 特殊キーもルール内に含めることで全ての操作をカスタマイズ可能とする目途もついた。

 ツリー(分岐)総数が増えたので微妙にサイズも増えているが、前と比べると自動フォールバックルールの追加などで同じ内容で比べればノード数はかなり(7〜8ノードほど)減っている。現在はこんな感じ。
// 分岐 73箇所 ノード 258個 サイズ 321パケット + 90文字 (1464バイト)

 ひとまずデバッグ中。状態遷移の塊なのでデバッグも面倒。
 ENTER→ENTER→W→W とかで発生するバグとか、こんなの時間が経ったらデバッグできんぞ。

 でもがんばってデバッグした。

メモその1

通常 → 開始待ち K 母音が出現するまで開始待ち→入力中
開始待ち → 無視 KE 小文字扱いとする
入力中 → 送りがな KeT 母音が出現するまで完了待ち→完了(変換処理へ)
送りがな → 無視 KeTTA 小文字扱いとする

ShiroI しろ*い しろi 子音バッファに文字なし
OU お*う おu 子音バッファに文字なし
DASSU だ*っす だs 子音バッファに文字なし
DaSsu だ*っす だs 子音バッファに文字なし
DasSu だっ*す だっs 子音バッファに文字(s)あり→ss変換後の位置から開始→未確定(OK)
DassU だっす 不可 子音バッファに文字(s)あり→変換確定(エラー)
KonA こな 不可 子音バッファに文字(n)あり→変換確定(エラー)
KoNa こ*な こn 子音バッファに文字なし
KoNn こ*ん こn 子音バッファに文字なし
KonN こん 不可 子音バッファに文字(n)あり→変換確定(エラー)
KoNde こ*んで こn 子音バッファに文字なし
KonDe こん*で こんd 子音バッファに文字(n)あり→n変換後の位置から開始→未確定(OK)
KondE こんで 不可 子音バッファに文字(d)あり→変換確定(エラー)
DaKkya だ*っきゃ だk 子音バッファに文字なし
DakKya だっ*きゃ だっk 子音バッファに文字(k)あり→kk変換後の位置から開始 → 問題 → 解決済
DakkYa だっきゃ 不可 子音バッファに文字(kk)あり→変換途中(エラー) → 問題 → 解決済
DakkyA だっきゃ 不可 子音バッファに文字(kky)あり→変換確定(エラー)
DaCcha だ*っちゃ
DacCha だっ*ちゃ
DaccHa だっちゃ 不可 こちらは問題ない
DacchA だっちゃ 不可

kkya は kky の時点で促音が出力され、パターンkyに移行する。これが通常促音と区別がつかない → 解決済
問題発生: Dと打つと子音にずっと残る → 解決済 (確定処理によるバッファフラッシュが行なわれていなかった)

@todo そもそもどういう出力が必要なのか。きちんと決めておくこと。
(1) 単語変換時に必要な情報
(2) 送りがな変換時に必要な情報

変換中(候補の前後移動のみ)の状態でのキー入力 → 確定+開始

子音入力中の確定コマンド(スペース)は? → 実装済み
候補入力中の確定コマンド(スペース)は? → 通常モードに遷移してしまう(バグ) → 修正完了
送りがな中の確定コマンド(スペース)は? → 遷移しない → 修正完了
変換候補表示中(送りがな変換、単語変換、英語変換時)の確定操作 → 修正完了

まだバグのある箇所
変換候補表示中の確定(実装済)、次候補(スペース)、前候補(x, CTRL+G)、削除(X)操作、文字入力操作
BSの挙動が変。送りがなの途中で削除しまくってもモードがずっとそのままとか。

1. 開始地点が0の木探索の際、探索場所フラグがfalseなら0のかわりに特定の番号の木から開始する。
2. 検索失敗した場合は、探索場所フラグをtrueに変更し、木0から検索を行なう。
3. 複数回の探索が成功して最後に失敗した場合などに、探索場所フラグを確認する必要がある。

1回目の探索成功の例
k→k→k
k→k→w (ここで先頭から再度探索すると k→k で出力が出て問題になるので木に含める)
k→k→a

1回目で成功、その後失敗の例
k→a 先頭から再検索→成功
k→y 先頭から再検索→成功
k→w 先頭から再検索→失敗

1回目から失敗して再探索の例
b→b

→ フラグによる状態監視は、内部で状態遷移した場合の処理や再検索後の失敗からのリカバリーが困難なため簡単に実装するのは難しそう。
→ ルール側で早い段階で共通ルートに復帰できるように配慮してルールを設定する。

 送りがなの情報(送りがなの文字と位置)をきちんと記録するようにすること。
例:
UgoKe うごk うごけ 送りがな'k' 位置2
FusawaShi ふさわs ふさわしい 送りがな's' 位置3

メモその2

STATE_NORMAL/set_state(0)実行済みと仮定。cstart=1で開始
「ka」の場合
cstart:1 k → ノード1でkにマッチ
cstart:1 a → 継続。k*にマッチし出力なし (cstartは0になる) ← cstartが1のため
cstart:0 k → conver内でノード0でkにマッチ
cstart:0 a → conver内で継続。k?にマッチし「か」を出力 (cstartは1になる) ← cstartが0のため
cstart:1

「kw」の場合
cstart:1 k → ノード1でkにマッチ
cstart:1 w → 継続。k*にマッチし出力なし (cstartは0になる) ← cstartが1のため
cstart:0 k → convert内でノード0でkにマッチ
cstart:0 w → convert内で継続。マッチせずフォールバックせず「k」を出力 (cstartは1になる) ← cstartが0のため
cstart:1 w → convert内でノード1でwにマッチ
cstart:1

「kkw」の場合
cstart:1 k0 → ノード1でkにマッチ
cstart:1 k1 → 継続。kkにマッチ → ここで cstart をクリアしたらどうか?
cstart:1 w → 継続。マッチせずフォールバックせず「kk」を出力(cstartは1になる) ← ミスマッチ処理

「kx」の場合
cstart:1 k → ノード1でkにマッチ
cstart:1 x → 継続。k*にマッチし出力なし (cstartは0になる) ← cstartが1のため
cstart:0 k → convert内でノード0でkにマッチ
cstart:0 x → convert内で継続。マッチせずフォールバックせず「k」を出力 (cstartは1になる) ← cstartが0のため
cstart:1 x → convert内でノード1でマッチせずノード0にフォールバックし(cstartは0になる)xにマッチ
cstart:0

「ok」の場合
cstart:1 o → ノード1でマッチせずノード0にフォールバックし(cstartは0になる)oにマッチし「お」を出力 (cstartはhになる)
cstart:h k → 継続。ノードhでマッチせずノード1にフォールバックし(cstartは1にになる)kにマッチ
cstart:1

変換中:「\ (スペース)」の場合
cstart:3 \ → ノード3でマッチし「(変換コマンド)」を出力 (cstartは0になる) ← cstartが3のため
これが問題。

結論: set_nodeの段階でcstartを見て設定してはいけない。
cstartが1〜3で、遷移パターンをもつ要素にマッチした場合のみ、次回開始位置を0にする。
set_nodeではcstartを設定しない。update時、0〜buf1の範囲内だったらcstartに記録する。

2011年8月02日(火)

 ユーザ辞書は順序管理用の環状リスト+検索用の赤黒木(LLRBT)で管理する方針とした。
 辞書サーバから外のskkservへの通信は現状ではEUCを使わざるを得ない。いつか、まともなskkservを作りなおさねばならない。
 辞書サーバのプロセス名はskksとした。

 辞書サーバの基本となる通信処理の実装が完了。skkimeとは完全に異なるものであると印象づけるためにも、全く異なる、より軽い方式となる共有メモリ+イベントとした。
理由1: UNIX系への移植なんてしない。既にもっと性能の良い実装があるだろうし。
理由2: ワンチップ組み込み環境の場合はここが再実装となるのは必至。
理由3: 再実装の場合は共有メモリではなくても何らかのメモリウィンドウへの読み書きの実装となる可能性が高く、同じメモリウィンドウを使った共有メモリ方式と親和性が高いはず。イベントはコールバックや割り込みで置き換えしやすいはず。

 辞書サーバ内のインデックスはyaskkservのような1バイトコードで持つべきだと思うのだが、まだ未実装。
 メインスレッドとの通信は全てUTF-16ベースとした。通信1回あたり16バイト程度のメモリコピーしか発生していないため、コード変換によるメモリコピーのコストのほうが上回るだろうとの判断による。

2011年8月03日(水)

 現状はBS処理が簡易実装だったが本格的にシーケンスに組み込むことにした。とりあえずバグ出しして再設計する。

ほかにもテストして見つけたバグ

→ NORMAL時のツリー開始点が間違っていたので修正。
→ モードチェンジでバッファがクリアされてしまうため、その前にバッファのフラッシュ処理を行なうよう修正。

2011年8月10日(水)

 内部辞書サーバで使用する24ビットLLRB木の動作チェック完了。
 オリジナルSKKにない機能のネタを思いついたらすかさずネタ帳に追加している。たとえば変換モードになった直後のq/l入力でカタカナ語の候補追加または確定の選択とか、何のためにあるのかまったく理解できない英数字エントリをシステム辞書レベルで読み込み禁止にしたりユーザ辞書レベルで記録禁止にするとか。
 それにつけてもSKKの単語区切りになぜタブやコントロールコードなどのエスケープシーケンスを使う気配がないのか理解に苦しむ。全角「/」を半角に直して処理するとか必要かもしれない。
 SKK辞書にlispの関数がうじゃうじゃ書き込まれていて気色悪いのですり潰し。最長語がtodayの141文字(skk-current-dateうんたらかんたら)からCORBAの116文字まで減った。それでも長すぎワロタ。

2011年8月11日(木)

 最近のSKK辞書を見てみたら1行1KB超えのエントリが17。「そう」なんて2KB超えとる。ずっと使ってた古い辞書は最大482文字程度だったので油断していた。単語は「こう」が最多で239種類。見出しは最大25文字なので上位2ビットを規格外サイズ用のフラグに利用する。

2011年8月16日(火)

 LLRB木だけで構築するのを断念しB+木モドキを作成開始。LLRB木といってもポインタを一切使っていない巨大配列として処理していたため、配列サイズ調整で無駄なメモリブロックの隙間ができる問題があったため。B+木にすることで、一度確保したブロックをサイズ調整なしで使いまわしたり、メモリ収納サイズを大幅に削減できたり、ブロックサイズが限定されることによりノード内部のオフセットを16ビット以内に抑えやすく内部のLLRB木の格納効率が上がるなどメリットだらけの予感。コーディングが超めんどいけど車輪の再発明は最高だ(※ただし趣味に限る)。
 順序テーブルと文字列長テーブルを微妙に統合。ユーザ辞書のメモリ上のサイズを削減しつつ辞書要素のサイズ計算回数を大幅に短縮。

 現在のデータフォーマットに関するメモ。

課題

16ビットインデックス版: 6万語
24ビットオフセット版: 20万語
32ビットオフセット版: 1000万語
→検索にLLRB木を使う。将来はもっと収納・検索効率を上げるためB+木に変更したい(特に送りがな辞書は近いキー同士が同じエントリを持っていることが多く、ブロック圧縮が有効に働きそう)。
→要素のうち、よほどの場合でないと全カウントする必要がない文字列テーブルについては、ブロック長は持たせない。

辞書サーバ動作

ユーザ辞書検索:
単語をユーザ辞書から検索
→見つかったらユーザ辞書エントリフラグを追加して通知
→見つからなかったら候補なしとして通知

システム辞書検索:
単語をシステム辞書から検索
→見つかったら通知
→見つからなかったら候補なしとして通知

辞書が複数ある場合は最初にエントリを見つけた時点で終了。

確定:
ユーザ辞書になければ新エントリとして追加
エントリに存在しかつ単語が存在しなければ追加
エントリに存在しかつ単語が存在した場合は先頭へ移動

メモリ上の辞書フォーマット

struct container;宣言自体は1バイトcharだが、実際には可変長配列となる。
辞書種類に応じて複数のフォーマットを持つが、共通箇所をフラグによって管理することで単一のモジュールで扱えるよう工夫する。

フラグの内容:
フラグはカウント数と共用とし、MSB1ビットの値によって切り替える。常識的な運用を行なう限り、99.99%のエントリにはフラグが必要ない。省略可能としたことでメモリ収納効率を改善できる。
省略時はデフォルト値(処理系によって異なる定数)を使用する。
/*
Bit7: 常に1
Bit6: 候補 0:あり 1:なし 他と違って0で「あり」なので注意
Bit5: 探索木 0:なし 1:あり
Bit4: 順序テーブル 0:なし 1:あり
/

Bit3: 候補オフセット 0:1バイト 1:2バイト 1つでも256カウント以上の文字列があれば1 (現状のL辞書でも存在しないが、将来必要になるかもしれない)
Bit2: 候補数 0:1バイト 1:2バイト 256候補以上あれば1 / 順序テーブルも同幅になる (現状のL辞書では存在しないが、将来必要になるかも)
Bit1: Index文字種 0:Bytecode 1:UTF-16 1つでも表現できない文字があれば1 (常識的な運用ではありえない)
Bit0: Indexカウント数 0:1バイト 1:2バイト 256カウント以上あれば1 (現状のL辞書では25カウント以上のエントリは存在しないが、将来必要になるかも)

※システム辞書は将来はオンメモリ型とインデックス収納型の2種類に変更
システム辞書エントリ: flag = 0x00
フラグ.0/B
Indexカウント数.B/W
Index内容.B/W[カウント数]
候補数.B/W
候補オフセット.B/W[候補数] 先頭は常に0なので省略し、最後に文字列部分の長さを入れる
候補内容.W (文字列長なし)

ユーザ辞書エントリ(1): 送りがななし
フラグ.0/B
Indexカウント数.B/W
Index内容.B/W[カウント数]
候補数.B/W
順序テーブル.B/W[候補数]
候補オフセット.B/W[候補数]
候補内容.W (文字列長なし)

ユーザ辞書エントリ(2): 送りがなあり
フラグ.0/B
Indexカウント数.B/W
Index内容.B/W[カウント数]
候補数.B/W
候補オフセット.B/W[候補数]
候補内容.W (文字列長なし)
探索木サイズ.B/W
探索木と枝

ユーザ辞書エントリ(3): エントリ(2)の探索木の要素 (専用メソッドで生成)
フラグ.0/B
Indexカウント数.B/W
Index内容.B/W[カウント数]
候補数.B/W
順序テーブル.B/W[候補数]

文字列比較用エントリ: flagは文字列長256以上の時のみ使用 (専用メソッドで生成)
フラグ.0/B
Indexカウント数.B/W
Index内容.B/W[カウント数]

葉へのリンク: flag依存なし (専用メソッドで生成)
フラグなし
Indexカウント数.B 常に0
文字コード.B
リンク(ポインタ)

B+木部分をどう作るか?

完全なB+木ではなく、木の深さと先頭からの文字数が必ず一致するような構造とする。
木の分割手順は以下の通り。

ノードは赤黒木 btree をメンバに持つ。

木へのノード(名称buf)登録手順
枝の場合→n++して同じ動作を繰り返す
葉の場合→既に登録済み

2011年8月29日(月)

辞書データの格納と検索はほぼ実装完了。

フロントエンド側からの変換要求とその応答データについて。
テスト用に仮実装した通信プロトコルにおいて、フロントエンド側のローマ字変換処理の出力データと、辞書サーバの内部データの実装とが微妙に食い違っていてどこで整合性を取るべきか未決定のまま。

例: うごk(く) → 動く
変換要求: 「うごく(全体で3文字)(送りがな検索に2文字)(検索用アルファベットはk)」
サーバ内動作: 「うごk」で検索した後「く」で検索。送りがなのひらがながなければ辞書順

方針1の場合、変換バッファの内容がそのまま積まれる。
sh1 = convsize; // 読みの文字数
sh2 = basesize; // 送りがなを除いた読みの文字数
sh3 = declcap; // 送りがな検索用アルファベット 「k」
sh + 4以降に文字 「うごく」

方針2の場合、変換バッファの内容をそのまま積むのではなく、「うご」を積んだ後「k」を積み、さらに「く」を積む形になる。

sh1 = convsize + 1; // 文字総数
sh2 = basesize + 1; // 送りがなを含まない文字数 (これが0だと通常検索扱いとなる)
sh + 3以降に文字 「うごkく」

通信処理の課題は以下の通り。
→通信量を減らすこと。
→通信バッファ内容のメモリコピー回数を減らすこと。

方針1の場合、バッファの準備は簡単だが、サーバ側の受信バッファ内容を並べ直す必要がある。
方針2の場合、バッファの準備段階で受信バッファ内容を並べ直す必要があるが、受信後のバッファ内容をそのままの順番で検索処理に渡すことができる。

2011年9月02日(金)

通信処理の課題に追加。
→オフセットテーブルを先頭に固める方式は番号から文字列をO(1)で取り出せるが、結局通信直後に全オフセットのスキャンを行なってバッファオーバランのチェックが必要。
→複数の辞書(少なくともユーザとシステム)をマージしてフロントエンドに返す処理が必要。リンク構造がないと追記で膨大なメモリコピーが発生する。
→現在はオフセットによる単方向リストだが、要素数の上限(変換候補最大512個とか)の制限を設けてもよい場合はもっと簡単かつ高速な処理が可能。どちらがよいか。組み込み向けなら後者しかありえないと思うが。

 ユーザ辞書内の順序テーブルを廃止。毎回確定時に要素を書き換える。

2011年9月04日(日)

通信処理は、メモリ上の辞書データはオフセットテーブルと文字テーブルを分離し、通信バッファ内はオフセットベースの単方向リスト型に変換して各種辞書とマージし、受信側で受信データのバッファオーバランのチェックを行なってオフセットテーブルを再構築する方式とした。

アノテーションなしのユーザ辞書と
アノテーションありシステム辞書を組み合わせると
「熊」と「熊;bear」が別扱いになるバグを発見

処理の簡略化のため、先に登録された語のアノテーション付加状態を優先することにする。辞書のスキャンはユーザ辞書→システム辞書の順であり、古いユーザ辞書はアノテーションなし、最近のシステム辞書はアノテーションあり、というよくある状態においてはアノテーションが無効になる。まぁいいか。

それにしてもアノテーションが英語だけのエントリとか超うぜぇ。abbrevモードなんて一生使うことのない機能なのにこれ本当に日本語入力システムなのか?と思ったが、よくよく考えてみると別に日本語以外でも通用するかもしれないと気付いてちょっと考えを改める。とりあえずアルファベットのみのアノテーションは削除するオプションをTODOリストに追加だ。

2011年9月05日(月)

 UTF16LEのファイル読み込み時のメモリコピー回数を削減。そもそもファイル変換が必要がないのに変換処理と同じようにデータ移動するダサいコードになっていた。ちょっとコード量が増えたが気にしない。
 EUCファイル読み込み時のMultiByteToWideCharの呼び出し回数を削減。Windowsプラットフォームでの日本語入力が本分なので、EUCの変換に関してはあんまり真面目にやってもなんかクロックとコードが無駄なだけのような気もするので適当に実装するだけにする。とりあえず2バイトのEUCはSJISに置きかえておいてなるべくまとめて書き換える手法とした。SJISで普通に見える部分(JIS第一・第二水準)についてのCP932的な文字化けは一切なくなるはず。
 なお、ConvertINetStringとかConvertINetMultiByteToUnicode(いわゆるIEのmlang.dll)はセキュリティだのアップデートで挙動が云々だのが怖いので意地でも使わない(本当は面倒なだけ)。

 JIS第三・第四水準や2004拡張についてはEUCからの変換は断念し、元の辞書データをUTF16化して与えることにした。ユーザ辞書もUTF-16で記録する予定。(まだ保存処理まで手が回ってない)

 見出しが一緒でアノテーションが異なるエントリ同士は先に発見したエントリを優先するように修正。

2011年9月11日(日)

 内部ステートを削ったりグループごとに4の倍数にまとめるなどして処理を効率化。

 変換処理の動作試験用モジュールをskkfepフォルダ側に統合。
 ローマ字かな変換処理のソースから定義ファイルのパーサを分割。

 コントロールコードや特殊キーストローク(SHIFT+SPACEなど)をローマ字かな変換ルールに混在可能にするための拡張を開始。
 …とその前にまず再帰単語登録の処理を書くことにした。再帰といえばスタック。現在の編集バッファの状態をpush/popできるようにする。
 …とその前に候補一覧表示を書くことにした。
 …とその前にローマ字かな変換モジュールをTSF/TIP側に組み込み直すことにした。分離しすぎて入出力バッファの扱いがだいぶ変わっていたのでちょうどいい機会なので整合性を取る。

 ひとまず学習なし・候補一覧なしという情けない状態ではあるが、L/JIS2/JIS3_4/JIS2004の辞書を使って日本語入力できるようになった。

2011年9月14日(水)

 GUID/CLSIDを動的生成してデータサイズを削減。
 データ構造レベルから設計しなおしているので.dataのSizeOfRawDataはゼロのままってあたりが自己満足。

 TSF/TIP関連のDLLのインストールパスはSKKIMEとだいたい同じ位置にしていたが、どうも標準IMEとは違っているようだ。標準だとだいたい以下のような感じ。
%SystemRoot%\IME\名称\ ファイルすら置かない(旧バージョンでのメイン置き場)
%SystemRoot%\IME\名称\DICTS\ 辞書関連
%SystemRoot%\IME\名称\HELP\ 説明書的なもの?
%SystemRoot%\System32\IME\名称\ 【64ビット用】DLL/EXE
%SystemRoot%\System32\IME\名称\APPLETS\ 拡張用アプレット?DLLのみ
%SystemRoot%\SysWOW64\IME\名称\ 【32ビット用】(旧バージョンと同様)
 というわけで一気に場所を変更。デバッグ用インストーラ・アンインストーラのスクリプトも修正。TSF/TIPはやればできる子。

2011年9月15日(木)

 emacs20でskk10.62aを改造してずっと使っていたのだが、ふと思い立ってemacs23にddskk-14.3を入れてみたら機能が豊富すぎてたまげた。候補一覧のインライン表示をパク…インスパイアしつつ単色だと見た目が地味なので少し色をつけてみる。

 Twitterクライアント機能の名称としてskkfepを予約。とりあえずサンプルで書き込んでみる。ペロペロ
 久しぶりにスクリーンショットを公開。

2011年9月16日(金)

 TSF/TIPの名称がいまだにプロト版のままだったのでSKKFEPに変更。

 そろそろバイナリのサイズを見直す時期。64ビット版の例外テーブルを削除。msvcrt10.dllではなくmsvcrt.dllをリンクするように修正。ランタイムはnew/delete程度しか使っていないのでどうということはないはず。コンパイルにはDDK必須となってしまうが、追加モジュール不要になる。導入の敷居が大幅に下がるはず。

 64ビットバイナリで、サーバ側は.relocセクションのない完全リロケータブルなコードになっているのだが、フロントエンド側ではなぜか小さなサイズの.relocセクションが作られてしまう。なぜだ。

2011年9月17日(土)

 TSF/TIPのDLLのリロケーション情報がどうしても消えない。配列は全て一次元にしたし何がいかんのだろうと思ったが、原因は仮想関数テーブルか。COMサーバなので仮想関数テーブルの塊だしフルリロケータブルは諦めるしかないか。

 こんどこそ再帰単語登録を実装…しなきゃいけないなと思いつつ野望へ向けた改造を開始。
 動的配列には自前のSTL風ライブラリを使っているが、動的配列はメモリの無駄なので固定配列版とコードの切り替えを可能にする。特に編集バッファは多目的のワークエリア的に使い回しているのでサイズ変動が激しいので無駄が多い。RAMが2KBみたいなファミコン的極限環境ではこのままでは確実に問題になる。
 編集バッファは本来の用途なら30文字もあれば十分。内部サーバとの通信時は文字数チェックを行なっているが、極限環境ではサーバとフロントエンドは統合してしまうので編集バッファ上限が検索上限となる。
 送信バッファは、名前は『送信』バッファとなってはいるものの、単にサーバに送信した内容をずっと保持しているだけであり、よく考えると変換をキャンセルした時に前の編集状態に戻すためのアンドゥ処理が本来の目的になっている。なぜ保存が必要かといえば変換中に変換結果を編集バッファ経由で渡しているためである。

A. よく考えてみると、もし変換結果を編集バッファ以外を経由して転送できるのであれば、このコピー処理自体が不要にできるはず。最終的には確定の瞬間に編集バッファが空になっていればいい。または確定に用いた文字が入っていればよい。メモリコピーは不要になる。

B. 現在は変換結果を整形するために通信バッファから編集バッファに転送しているが、これもよく考えると着色しながらセレクションに転送できればいいので、個々の要素について通信バッファのポインタとバイト数をこまめに渡せばいい。やっぱりメモリコピーは不要になる。

C. 変換結果は受信バッファの先頭アドレスとカウント数を使えばよし。以前は編集バッファではなく検索バッファと呼ぶ領域を経由していたが、どちらにせよこのバッファのアクセスは常にシーケンシャル。「単語」「;」「アノテーション」の順に常に流れる。つまりエントリの先頭アドレスの計算は1回だけでよく、あとはオフセットを少しづつずらしていくだけでよい。

 結果、2段階のメモリコピーを削減でき、かつバッファが1つ減り、計算回数も減る…はず。

 事実上固定長として使っていた受信バッファを固定長に変更。

 さらに発見

D. 確定時は編集バッファから出力バッファにメモリ転送をやっているが、Cの通信バッファから直接出力バッファへ転送すればさらにメモリ転送回数を減らせる。

 さらに別件で発見

E. 変換時にサーバに渡す文字列の管理方法に僅かだが無駄がある。
送りがななし: basesizeは0、convsizeに文字列長
送りがなあり: basesizeに送りがなを含まない文字列長、convsizeに文字列長
内部で送りがな部分の抽出が表示、登録、選択の際に行なわれるが、そのたびにbasesizeのゼロ判定とconvsize-basesizeの計算を行なっている。これを以下のように保存するよう変更することで処理を簡略化できる。
送りがななし: basesizeに文字列長declsizeに0
送りがなあり: basesizeに送りがなを含まない文字列長、declsizeに送りがな長
これに伴い、送りがなありなし判定をbasesizeのゼロ判定ではなくdeclsizeまたはdeclcap(送りがなのローマ字)のゼロ判定に変更する必要あり。

 変換結果表示処理で着色処理まわりをテーブル化したのと併せて1KBくらいプログラムサイズが縮んだ。
 まだ出力バッファと編集バッファは固定長になっていないが、こちらは実質40文字程度もあれば十分動くようになった。野望にまた一歩、である。

2011年9月19日(月)

 出力バッファ・編集バッファともに固定長配列と動的配列を切り替えられるように変更。こういう時にC++のテンプレートは強力だと痛感する。ソースをほとんど弄らずarrayからfixed32>と書き換えだけで対応できた。結果、0.5KB程度コードが縮んだ。思ったほど小さくならなかったが、ヒープ管理処理をごっそり減らせるので組み込みの環境下では見た目以上の効果があるはず。何よりこういうどうでもいい変更ほど自己満足度合が高いというのが大きかったりするが。

 自動コレジャナイ機能をネタ帳に追加。

2011年9月23日(金)

 通信バッファまわりの処理を共通化。
 定数回のループで配列アクセスを行なう処理は、ループカウンタのビット幅は最適なものを選んでくれているものと思い込んでいたが…size_tとそれ以外とでx86版のコードサイズが変化する…。なんてこった。そのうちループカウンタのビット幅は全てチェックするべきかも。配列アクセスがないとucharで回すほうが小さいコードになったりする…。定数ループの最適化はもうちょっと頑張ってほしいところ。

 別々のフォルダで管理していたが、移動がめんどいので全プロジェクトを同一フォルダに集約。

2011年9月24日(土)

 変更点を比較しやすいよう、VC++プロジェクトの設定を揃えた。
 全角英数ベクタのテーブル初期値を圧縮。4文字例外があるだけで殆ど並び一定だし。どうせカスタマイズしたらレジストリから読み込むわけだしコードとして持っておくサイズは小さいほうが良いはず。
 動作に必要なテーブルの初期化などは、テキストサービスのクラスが生成された時ではなくアクティブになってから行なうことにした。これでほんの僅かだが無駄が減るかも。

 確定処理のフロントエンド側実装完了。あとは単語登録を実装しなくてはならない。めんどい。

 テンプレートクラスのメンテナンス。サイズ管理不要のものについてはvector互換クラスより軽いものに切り替え。
 …た瞬間、漢字コード自動認識行単位読み込みルーチンが沈黙。デバッグに一日潰す。あさはかなり…

2011年9月25日(日)

 入力モードを変更してもGUI側に反映されない(未実装)の部分を作成。
 キーボードオフにしてもSandSが切れない(というかSKK自体切れてない)原因は…IMEのキーフックの部分はキーボードオンオフ関係なく常に割り込んでいる模様。フラグ調べて動作しないようにして解決。

 無駄処理っぽい部分を発見
 ローマ字かな変換の結果、キーイベントを喰うかどうかの判定は、真偽だけで良いはず。今は0だと喰う、1〜-0xFFだと喰わない、0x100以上だと喰うという状態になっていて、判定が美しくない。
 もともとはモード変更などをコマンド名として返したいという意図があったが、それはかな変換クラス内部で閉じて処理しても特に問題がないことがわかっている。将来プラグインなどをくっつける際に問題かもしれないが。とりあえず、0なら喰う、それ以外なら喰わないの二択くらいにはしても問題ないはずだ。

 α版のレポートが返ってきた。にわか作りのためぼろぼろである。ひとまず問題点を整理。

 まずキャンセルまわりの操作のベースになるコントロールコードまわりの変換ルール処理をきっちり作り込むべきか?それよりも登録動作を仮動作レベルで作っておくべきか?

 仮動作のほうが簡単そうなのでそっちからやってみる。必要な処理

1. 現在の変換状態をスタックに積む (通信バッファと見出しバッファと設定バッファの内容を積む→編集バッファの中身を見出しバッファへコピー→編集バッファ・設定バッファをクリア)
2. 確定したものは入力バッファから設定バッファへ(または入力バッファのモードを変えるとか)
3. CTRL+JまたはCTRL+Mで確定、CTRL+Gならキャンセル(スタックを1段減らす)
 よくよく考えると見出しバッファの内容は元通信バッファのコピーなのでこいつもスタックに載せてしまえば処理が楽かもしれない。

 気をつけないといけないのは、登録と削除は紙一重だということ。再帰登録中に単語を削除するなんてことも十分あるので、なるべくユーザの思考を阻害しないようにしないといけない。

インライン表示のフォーマット
登録中 [見出しバッファ] 編集 [設定バッファ][編集バッファ]
削除中 [見出しバッファ] 候補 削除確認 Y:はい

 インライン表示では基本的にコンポジションの先頭にはなるべく「ユーザがキー入力したもの」を入れる。
もはやローマ字かな変換を実装した時点でSKKとは似ても似つかないものになってしまっているので、SKK原作にあわせる必要もないだろう。

 あとバッファは全域を保存せず必要分のみ詰むべきか。そもそもワンチップマイコンだと、何でもかんでもスタックに積みまくると一瞬でフリーが消し飛ぶのでかなり慎重にしないといけない。目指すはアウヤン…PIC24FJ64GB002。DIPのICであることが正義。これはRAM8KBなので、テキストVRAMに2K、ワークに2K、ユーザ辞書に2K、システムスタックに2K、って感じで漠然と考えているが、どうしてもメモリが足りなければDIP28ピン最強のdsPIC33Fにすれば一気に16KBが自由になる上処理速度もアホみたいに上がる。かつてポケコンやファミコンがRAM2KB程度でギュンギュン動いていたことを思い返すと本当にいい時代である。
 といってもいきなりワンチップはきつすぎるので、Windows→32ビットワンチップ→16ビット級ワンチップという具合に順にスケールダウンしていくのが妥当か。

 スタックの実装については考えることが多すぎてなかなか進まないがとりあえず最低限のコードを組んでから考えることにする。

2011年9月26日(月)

 困った時のユースケース分析。登録中の表示の遷移サンプル。助けてドラえも(ry

でまえじんそくらくがきむよう { ▼でまえじんそくらくがきむよう変換
でまえじんそく { でまえじんそくらくがきむよう: ▼でまえじんそく変換

でまえ でまえじんそく: ▼でまえ変換
じんそく でまえじんそく: 出前▼じんそく変換

} でまえじんそく: 出前迅速確定
らくがきむよう { でまえじんそくらくがきむよう: 出前迅速▼らくがきむよう変換

らくがき らくがきむよう: ▼らくがき変換
むよう らくがきむよう: 落書▼むよう変換

} らくがきむよう: 落書無用確定
} でまえじんそくらくがきむよう: 出前迅速落書無用確定

 実際には変換マークは表示されず、内部で文字列長で位置を管理することになる。

 スタックに積む処理を仮実装。結局、固定バッファでコンパイルした場合は出力バッファ・設定バッファ・編集バッファ・スタックのワーク領域が合計256〜512文字とすることで落ち着いた。99.99%のケースはこのサイズで対処可能なはずだ。当然、Windows版は可変長バッファも選べる。
 サイズ縮小のため、出力バッファと設定バッファは共用とし、単語登録中は常に出力サイズ0扱いとする。判定のために若干コードサイズが増えるがワークRAMを削減できるので効果は大きいはず。さらに登録中かどうかはフラグとして持つ必要はなくて、スタック深度ワークが0かどうかで判定可能とか、登録中の単語の読みはスタックトップに格納されるようにして地道にRAM消費量を削減している。
 やってみると思ったよりいろんな情報を積まねばならないことに気付く。送りがなとか日本語で変換していたか・英語で変換していたかの情報とか。

 あと、再帰登録なのに再帰停止条件チェックはないので何度も同じ単語を登録できてしまうのだが、この挙動はSKK10も同じだしそんな操作を意図的にするとは考えにくいし、こまけぇこたぁいいんだよ的な。

2011年9月30日(金)

 LPC XPRESSOを掘り出したのでLED点滅処理のコピペを弄って遊ぶ。とりあえずワンチップマイコンの最初のターゲットはこいつにするか。本当は雑誌付録のRX600にしたかったのだが、USBだとまともにデバッグができないハズレ環境だったため利用を断念。こちらはコンパイラを入れたら有償チェックのプロテクト判定プログラムで汚されただけだった。ルネ○ス逝ってよし。

 変換結果のレンダリング処理を1箇所に集約。表示属性テーブルと内部状態を完全に一致させてコードを簡略化。
 送りがなつき単語の読みの入力中、ベース部分と送りがな部分を表示色などで区別できるようにするためbasesizeを変換前の段階から更新するように修正しないといけないが、この部分を効率よく書く方法が思いつかない。

 コード効率向上のために状態遷移の見直しを2段階に渡って行なう。
第一段階。単語登録中はcstateではなくスタックを見て判定する。内部状態が1つ減る。
二段階。英単語の編集/変換/選択中の判定はcstateではなくフラグを見て判定する。RAM消費量が1バイト増え内部状態が3つ減る。コードサイズの削減量を見て採用するかどうか判断する。

第1段階完了。単語登録のサイズが0文字だとselectionでアサートが出るのでselectionで0文字を許可したが、そもそも単語が0文字で登録できてしまうのは問題のような気がする。SKK10だとキャンセル扱いになるようだ。

む!原作では登録する単語がスペースのみで構成されている場合、登録はされずにひらがな確定になるようだ。しかし、スペース+文字や文字+スペースの場合はスペースも含めてきっちり登録されるようだ。なんかあまりにも意味のない判定だと思うのでこの処理は見なかったことにしよう。

2011年10月01日(土)

 子音バッファはUTF-16で半角(ローマ字かな変換のテーブル再検索用)と全角(コンポジション出力用)の両者を生成していたが、いまいちメモリ利用効率がよろしくなかった。
 レンダリング文字列の受け渡し方法の変更に伴い、子音バッファはASCIIとし、レンダリングの瞬間に全角文字変換を行なうことにする。これで子音バッファのRAM消費量を1/4にできる。コードサイズは若干肥大するが誤差範囲内と思うことにする。

 昨日のbasesize判定はあっさり解決した。STATE_OKURIの時はbasesizeが設定済みであり、それ以外の時(変換・選択中以外)はedit.size()がbasesize相当になる。英字モードを含め、判定1回のみでいける。

2011年10月02日(日)

 登録をキャンセルした場合に登録前の内部状態に復元する処理を追加。英字モードであってもきちんと戻るはず。だいぶSKKっぽい動きになった。
 復元の際、バックエンドに変換処理を投げて通信バッファを補填することで、通信バッファをスタックに積む必要がなくなった。大幅にスタック消費量を削減できる。

 ローマ字かな変換に特殊キー判定を含める改造に向け、まずはキャンセル・BS処理の処理をまともにしておくことにした。

キャンセルについて

NORMAL:
START:
INPUT:
OKURI:
ALPHA:
バッファをクリアしNORMALに遷移
CHANGE:
SELECT:
INPUT/ALPHAに遷移
ERASE:
CHANGE/SELECTに遷移

要するにERASEの一つ前が日本語か英語かを保存しないといけない。
ERASE以外の状態については実装完了。

BSについて

NORMAL:
START:
INPUT:
OKURI:
ALPHA:
子音バッファがある場合、子音バッファ末尾を1文字削除
編集バッファがある場合、編集バッファ末尾を1文字削除
バッファが空になったらOKURI→INPUT→NORMALに遷移
空ならキーを上位へ渡す

CHANGE:
SELECT:
ERASE:
何もしない

ERASE以外の状態については実装完了。
ERASEも実装完了。ただしERASE中はまだキー入力処理がないのでC-gするしかない。

2011年10月03日(月)

 MS Wordの問題の原因が判明。OnTestKeyDownが2回呼ばれてからOnKeyDownが呼ばれている。
 キーリピートカウンタの値も完全に一緒なので、OnKeyDownで状態リセットするまでOnTestKeyDownは1回だけ受け付けるように制限するしかなさそうだ。
 キーリピートカウンタ込みで同じイベントが発生している場合は、2回目以降は何もせずTRUEを返すようにしてみたら動くようになった。
 TSF/TIPではOnTestDown側を一切呼び出さずにいきなりOnDownするプロセスや、OnTestDown後のOnDownで渡すlParamのキーリピートカウンタの値(の下位1〜2ビット)が異なるプロセスなどもあってかなりカオスな状況だ。

 OnTestDown側で処理を済ませてしまうskkfepも問題だとは思ってはいる。文字出力しつつEatしないパターン以外の内部動作の時は、OnDown側でコンポジションの処理を行うのが本来あるべき姿なので、余裕があったらコンポジションへの反映は分離すべきかもしれない。EatしたはずなのにOnDownが来ないなんてことがあったら(今のところそのパターンは見たことがないが)終わるけど。
 一応分離についてはSync()を呼ぶタイミングを変えることでできるようにはした。とはいえまだタイミングは変えてない。現在の作りで問題が出るプロセスがあった時にあらためて考えることにする。

 さらに別の問題を発見。コンポジションが1文字しかない時に表示されない現象が発生。
 前にskkimeソースでコンポジションを強制的に2文字以上にするような処理を見た記憶がある。この現象の対策だったのかもしれない。

 現在の問題点を整理。
 このエンバグのせいで単語登録中にカタカナなどを入れようとすると登録状態が解除されてしまう。
→mode()から呼ばれているclear()でスタッククリアしているのが問題。clear()内のスタッククリアのコードを削除。スタック込みでclear()するreset()を追加し、TSF側のモード変更時や初期化処理ではこちらを呼ぶ。

→スタックが空でなければ強制文字化モードにする

→バックエンド側にクリップボード取得処理を追加
→登録バッファでCTRL-VとかCTRL-Yを受付ける
 変換結果をアノテーション含めてクリップボードに書き出せたりすると何かに使えないだろうか…ってそれではTSF/TIPの必要がまるでないか。

 逆に考えるんだ。「スタックのクリアどころかコンポジションのクリアすらしなくたっていいや」と考えるんだ。変えてみた。コードも簡潔になるし、動作もこっちのほうが自然に思えてきた。採用。

現在の問題
→スタックが残った状態でキーボードオフした場合、見た目がおかしくなる。
→キーボードオフ時(オン時は不要だがあってもよい)スタックをクリアするか、キーボードオフでもスタックが残っている間は登録バッファへ出力する。後者のほうがプログラム的には自然で美しい気がするが、フリーダムすぎて使いづらいように思える。
 ここはコンフィグで切り替え可能にするべきか?「IMEオン/オフ時に状態をリセット する/しない」的な。
→切り替えは不可。理由として、キーボードオフ時はキーフックしない(変換処理が呼ばれない)ため、スタックが残っているとたぶんキーボードオンに戻すまで一切キー操作できない状態になりそうだ。キーボードオフ時にもキーフックするように処理を戻せば何の問題もなく動いてしまうが、キーボードのオン/オフの理念に反するような気がする。

 特殊子音の再評価時の処理を地味に簡略化。

2011年10月04日(火)

 候補選択中にuやoの母音を入力するとバッファが内部で書き換わってしまう問題を確認。潜在的に問題になりそうだと思っていた箇所だったので根本的に直さねばならない。
 状態遷移の管理をローマ字かな変換ルールに頼りきってしまっているため、ローマ字かな変換が不要なときのコマンド判定という要求にきちんと応えられないのが根本的な問題か。
 ひとまず仮対処を入れた。

 単語削除の操作と通信処理を実装。これで最低限のフロントエンド側の実装が完了…か?

 TSF/TIP側の状態更新とアプリケーション側の文字入力タイミングを分離。
 すべての発端はMS Wordで文字入力ができない問題が発覚した時に遡る。
 という感じでMS Word行頭で1文字目が入力できない問題が解決。
 現象から推測するに、TestDown/TestUpの時はコンポジションがリードオンリー属性になっている(その後KeyDown/KeyUpを通過すると書き込み可能になる)プロセスがあるということか。
 というわけで、内部状態の変更とコンポジションへの反映タイミングは完全に分離されたことになる。

 このデバッグの途中、原因不明だがメモ帳でコンポジションを開いた瞬間?OnCompositionTerminatedが発生してまともに操作できない現象が起きた。半分眠りながら何度かコンパイル・COMのインスコ作業をやっていたのでちょっとよく覚えていない。これが記憶喪失ってやつか。で、いつのまにか直ってた。っべー。

コンポジションの更新に失敗する奴が相手なら遅延入力バッファを使わざるを得ない
とか呟きながらコードを弄って遊んでいたら失敗の原因は別のバグと判明し全て水の泡で泣いた

declcapをcon0で代用できないか? → できない (確定で子音を使った時など)
初期化が必要なエントリを構造体の先頭側に並べる → やってみた

2011年10月05日(水)

 そろそろ基礎部分に手を入れ直すことにした。ローマ字かな変換ルールにあらゆるキーシーケンスを混ぜられるようにして、とにかく変換ルールですべての設定ができるようにしたい。
 あと、現状では内部ワイルドカードを0x00〜0x02、ユーザ定義ワイルドカードを0x03〜0x1Fとしているが、コントロールコードの定義を考えるとここは開けておくべきだ。

 というわけで以下を実施。1週間くらいで3回リファインする程度のペース
1. ワイルドカードのコード移動
2. 特殊文字の定義フォーマットを考える
3. 特殊文字の記録フォーマットを考える
4. 特殊文字の入力判定を考える

 テーブル生成処理はかなり初期の頃に一発作成したものだったのでコメントがほとんどない。少しづつコメントを加えておく。
 基本動作は文字コード変換→1行読み込み→単語分離とエスケープシーケンス展開→語数に応じて分岐となっている。
 エスケープシーケンス展開処理を修正・拡張。
(1)デフォルトのワイルドカード文字を0xFD〜0xFFへ移動。
(2)ユーザ定義ワイルドカード文字は'&'で指定するよう変更し0xF8以降に出現順に記録。
(3)特殊文字は0xF0以降に出現順に記録。
これでテーブルに後からフラグを書き込む必要がなくなり、展開側のコードも簡略化できる。

2011年10月07日(金)

 そろそろテーブル参照処理の効率化をしようと思い立つ。
 現在、ワイルドカードを使った定義(z+数字とか)は共通状態の木を使って記述しているが、変数(特殊文字)は別の木として用意しておくほうが検索時間を短縮できる。今の木から枝を切って別の木に写しても、テーブル全体のサイズは変化しない。また、木が共通だったためにわざわざ一文字目ならワイルドカード判定をスキップするなどの無駄な処理があって、これを綺麗に消せるのも大きい。
 …大きいっていってもたかが数回の判定処理でしかないわけだが、クロック1000kHzのプロセッサではこういう地道な処理の見直しがきっと生きるはず。

 あとやらねばならないのは内部状態の変更か。0が共通となっていて状態の計算に加算(+1)が必要だったのを0を標準とすることで状態の初期値の計算量が減る。この処理は多いので関数化しているけど、これをインライン展開でシフト演算1回だけになればかなり軽くなるはず。ていうか軽さなんかより内部状態とテーブルが一致して美しい。美しければヘブン状態。もうどうにでもなれ(ry

 あとはコマンド順序を見直すべきか。0をnop(確定のみ)、1を子音確定、2〜3を読点・句点にし、続いて中断・後退にするとか。中断と後退を1,2にするのもよさそうだ。
 …というかもう句読点コマンドは廃止の時間だゴルァ。利用者(自分)は句読点をリアルタイムで切り替えながらボンジュ〜ル?とかミサワ顔がしたいわけではない。本当に必要なのは「簡単な操作で」「自分の使いたい句読点の種類を」「最初に一度選べる」ことだけ。あとは思考を邪魔されない、要するに切り替え機能がどうこうなんて高度に宗教的なことは一切考えずに使うことこそが重要だ。だったらコンフィグファイルの句読点定義部分をマウス一発で書き換える機能があればいいんじゃね?無駄な内部コマンドだのテーブルだのをフロントエンド側に死蔵する必要なんてないわー。よし、殺そう。

ぼく「まずskkfepを淹れる」
餃子「インスタントです」
ぼく「そしてTwitterプラグインを咥えて茶道させる」
餃子「コーヒーです」
ぼく「これで全てのWindowsアプリがTwitter喰らいアントになる」
餃子「虫は嫌(バルサンを炊く)」
ぼく「スペースやエンターを押すとTLを文字として各停できる」
ぼく「適当に返還するとメモ帳やワードを文字で満杯にして仕事してるフリができる」

いまいち

2011年10月08日(土)

 テーブル生成処理と参照処理を変更。
 内部状態の変更は0の扱いが困難そうなため保留。
 句読点コマンドを廃止。

 ^G ^H ^Jを任意のキーに割り当て可能にしたら、子音入力中のバックスペース処理がエンバグしてしまった。reflect()で内部コマンドを取得するよりも前の段階でツリー遷移失敗でfinalize()が呼ばれてしまうので内部ロジックを弄って対応するのはかなりコードの無駄になりそう。仕方ないのでこの3つは特殊コマンド扱いとして独自ツリーに含めておいて、キー入力の最初でチェックさせることにした。
 ノードからのコマンド修得処理を分離して関数を再度組みなおしたらreflectもfinalizeもほとんどコードを共通化できて綺麗になった。満足。

2011年10月09日(日)

 ^M も任意のキーに割り当て可能にし、egg-like-newline 動作は ^J(newtral) ^M(enter) 相当のキー入力時だけ動作するように修正。

 中断・後退処理回りの状態遷移を見直し、仮想的に変換マークが存在しているかのように振る舞うよう修正。

中断操作は下位→上位の順に中断していかねばならない。
子音入力中か?→編集状態か?→再帰登録中か?→上位アプリへ

 子音確定回りのロジックを見直したら再帰処理は不要で条件文で分離できることが判明。reflect/finalizeを共通化。初期のコードに比べたらだいぶ単純になった。

 syncの返り値をイベントを発生させるキーコードではなくキーイベント無効化フラグを返すよう改良。
 関数の返り値が単純化されコード量が減ることが期待できるが、よく見るとreturnの値がfalseと比べてtrueの割合が多い。ワンチップマイコンではfalseのほうがゼロクリア系命令でコードサイズが縮めやすいはず。とはいえ今の方式はTSF/TIPに直結できるので、まずはこの方式で行く。
 syncより1段下位の処理もフラグ化できるはず。あとでやる。

 単語削除キャンセル時、abort後のreturnがなくて通常キー入力のルートに入っていたので修正。

 境界チェックを一通りやることにする。

 原作の動作をチェック。

 動作を原作に合わせて修正。

2011年10月10日(月)

 syncより1段下位の処理もフラグ化。
 コマンド順序見直し。
 特殊キー対応の基礎部分(仮想キーコードとして上位ビットが1の文字が渡っても正常に処理可能)もできた。
 だいぶスッキリした。

 根本的にダメなことに気付いた。コンポジション確定と同時にバックスペースなどのキーイベントを返しても、キーイベントを処理できないアプリがある。たぶんコンポジションがキーイベントを吸いとっているんじゃなかろうか。
 っていうかこれ最初のサンプル動かした時に確認してたはずなんだがすっかり忘れてた。
 というわけでコンポジション確定前にバッファに対する編集キー操作を適用しなければならない。
 …対処したらコードがもっとシンプルになった。何故だ。

 ひとまず基本的なUIとして常用できるレベルになったので次の段階に進む。やるべきこと。

 これらのうち上位2つは辞書構造とUIの再設計が必要になる可能性があり、後回しにすると厄介そうだ。もうやるしかねぇ…。といいつつデモンズソウル買っちゃった。もうやるしかねぇ…。

 おおっとここでいつもの現実逃避が発動だァ!
 かな変換テーブルを見ていてさらにサイズ縮めつつプログラムサイズも変えずに済みそうな気がしてきた。
 受信バッファ保存処理を改良。候補サイズの保存位置を変えたらオフセット計算処理が軽くなって20バイトコードが縮んだ。

 変換テーブルのサイズを118バイト削減(1460→1342)。コードも単純化(テーブル参照して境界値を得ている箇所を定数化)してテーブルそのままでも1ブロック縮んだ。両方縮むなんて奇跡でござるぅぅぅ。

 内部状態とテーブルの順序をオフセットなしで一致させた。美しい…。判定処理も一部省略できるし、インライン展開が効きまくってコードも80バイト程度短くなった。
 これに伴い、定義ファイルの状態名を変更。通常/編集/変換/共通/特殊/万能の順に変更した。このうち、最初と最後の状態とそれ以降に追加されるユーザ定義の状態が初期ノードとして木に出現することになる。

2011年10月12日(水)

 ツリーの初期状態の定数をROOT_*で統一。あとユーザ定義状態の空きは3つも必要ないと思ったので1つだけにしてテーブルサイズをセコく削減。内部的にはあと3つ追加可能だけど正直ここを書き換えて使うような変人は自分くらいしかいないだろうから空き1つすら入れる必要はないかもしれない。

 TSF/TIPのメモリへのロード時から自動的にキーボードをオープンする試みは特に問題もなく動いているようなので、キーボードオープン+半角英数モードをデフォルトにしてみる。これで全てのプログラムのCTRL+Jを乗っ取って動作するようになる。ある意味過激な設定だがSKK使いなら日常となり得る。

 単語補完処理について脳内検討。単語編集モードで文字入力がある度にバックエンド側(内部サーバ)に文字を通知。バックエンド側では、個々の辞書についてB+のブロック内部の赤黒木の先頭を取得し、あとは木を順番にスキャンすればよさそう。途中でB+の次ノードへのリンクにぶち当たる可能性もあって、その場合は全選択にする必要があるくらいか。これについてほかに考えるべきこと。

 っべー。また根本的な違いに気付いちゃったわーまずいわー。
 SKK10だと、最初の変換のとき、子音入力は小文字でも問題ない。やばい。こんなシーケンスまったく想定してなかった。

2011年10月20日(木)

 ダークソウルやべー。何がやばいって、これ侵入とか眷属プレイが夢の「非対称戦力型対戦アクション」を自然に実現してるのだ。すごいデザインだ。
 あとこのゲーム、昔の2D横スクロールジャンプアクションとすごくプレイ感覚が似ている。上位互換とでも言うべきか。なんせ安心のフロム操作難度。悪魔城ドラキュラで穴に落ちまくって「ア゛〜」みたいに叫んでしまうのと同じ感覚が味わえる。これが今のゲーマーに受けるのかはわからないけど上達による脳の最適化が癖になるような感覚であることは確か。あとあと、盾で車輪をカカッと連続攻撃を華麗に受けとめ続ける感覚とかたまらない。ヴァルケンとかで盾で受けながら少しづつ進めていくのと感覚が似てる。しかしだんだん敵の量が増えていってYOU DIED(ボワーン)チクショー面白ぇ!やめらんねぇ!
 えーとSKKの開発日記だっけ。これ。

 小文字で子音入力を開始してもローマ字変換が確定するまでの間のどこかのタイミングでシフトして入れれば編集開始になるよう修正。if文でわざわざ状態遷移しないよう除外してたので削除しただけ。恐らく変換開始の状態遷移と同じ挙動だろうと、元の動作をよく調べなかったためと思われる。というかこんな入力も受けつけるなんてずっと気づかなかった。

2011年10月22日(土)

 SKK10の補完はユーザ辞書のエントリだけを対象にしているが、どうせなら未学習状態でも、オンメモリで読み込んでいるL辞書も補完対象にしたい。今どきの携帯電話だって連想変換ができる時代だし。
 SKKIMEのソースでは、確かユーザ辞書の1000エントリ分を登録順に持っておき、順番に検索するという感じの処理になっていたような記憶がある。これはいけない。この方法では、1000エントリの最後に目的のものがあった場合、無駄な比較(といっても殆どのエントリは一文字目で失敗するので999回の比較をするだけだが)が発生してしまう。
 いろいろ考えたがいい方法が思い浮かばない。

 ファミレス打ち合わせ。
 っべーこんなことまったく考えもしなかったZe。

2011年10月23日(日)

 辞書補完処理について。

 ユーザ辞書のB+木の範囲取得を行ない、実体へのポインタを生成、登録順序で並べ替える、いわゆる地道な絞り込み検索の動作が一番高速なのではないかと考えた。
 この方法であれば、ユーザ辞書以外でも同様の操作が行なえるので、ユーザ辞書+メモリ上のシステム辞書両方からキーワード検索も可能なはず。システム辞書がROM化されていても、ポインタなので問題なし。

 辞書エントリ登録時にシリアル番号を付加し、B+木の範囲取得の後にシリアル番号をキーにソート。基本方式はこの通りとするが、ソート部分は処理が重いので改良する。

 B+木の範囲取得では、あいうえお順にエントリが取得できる。取得したエントリのポインタを赤黒木(16ビットオフセット×3と64ビットポインタ×1)にシリアル番号をキーとして登録。
 これで辞書エントリのコピー操作等は一切行なわれず、ポインタだけが木として登録されていく。メモリ速度の遅いワンチップマイコンでも十分なパフォーマンスが得られるはずだ。

 次の問題は、必要なRAMサイズが膨大になってしまう点である。これも赤黒木を使うことで簡単に解決する。赤黒木の1エントリのサイズは固定長なので、作業領域のサイズをnKBとすると、木のエントリは1024×n÷14個格納できる。この個数を越えて格納しようとした場合、末尾の(シリアル番号が最も小さい)エントリを削除してから新しいエントリを追加すればよい。二分木なので先頭や末尾の取得は高速に行なうことができる。
 理論的には、最小の作業領域サイズは14バイトだけあればよいという結論になる(もちろん実用性は度外視)。要するにシリアル番号の一番大きなエントリを1つだけ記憶するわけだ。普通の使いかたなら、補完候補を10個以上探す手数と暇があったら直接キー入力してしまうだろうし、全候補を列挙するのは処理の無駄のように思われる。実質2〜3候補が出れば十分なのかもしれない。数個程度なら、赤黒木を使わず配列で処理しても高速に処理できるだろうしコードも相当簡略化できるはず。

 処理についてはここまでの検討で問題ないはず。しかしまだ別の課題が残っている。ユーザ辞書にシリアル番号を付加してメモリ上に展開するという方針にしたため、セーブ方法を再度検討しなおす必要があるのだ。セーブに関する課題は以下の通り。

1. なるべくSKK互換フォーマットで保存
2. なるべく省メモリ

 辞書がB+木上にあるため、単語を登録順に取り出すのが困難になっているのが1の根本原因。
 ユーザ辞書は最初はサイズが小さいが、10年も使い続ければM辞書サイズの数倍程度には大きくなってしまう。登録順に並べるなら、十分な作業領域が必要になる。
 ワンボードマイコン上では、辞書のセーブ・ロードは実装しないため、ここについては課題1を断念した簡易実装とする。
 個々のエントリにシリアル番号を付加して保存すれば2は簡単に解決可能。

 ようやく納得のいく内容になったので実装することにする。

2011年11月23日(水)

 コマンドプロンプトでTSF/TIPが動かない原因はどうせエンバグか何かだろうと思い込んでいたのだが、そもそもマイクロソフトのサンプルも動かなかったことに気付いた。何故だ。というかもっと早く試しておくべきだったのに。などと呟きつつダークソウルにハマってSAN値ガリガリ低下してYOU DIED。ヴァー

 corvus-skkという別のTSF/TIPが存在するらしい。うわー。こっちのほうが断然完成度が高い。特にDOSプロンプトへの対応が完璧。フォーカスが外れるとIMEオフ扱いになるのはちょっと過剰なような気もするが。とにかく、ここまできちんと判定できるとわかっただけでもすばらしい収穫だ。そしてあらためて自分の無能ぶりを思い知るのだ。ヴァー

2011年11月24日(木)

 SKKIMEやcorvus-skkはDOSプロンプトに対応しているので違いを確認してみた。コンパートメントイベントシンク(プロセス間通信のコールバック的なもの)とかスレッドフォーカスシンク(スレッド切り替え時のコールバック的なもの)が追加されている。前者は最新版で新規追加されたコードのようなので無関係か。
 あとはUIレスモードというのも気になる。これはウィンドウを作らないと意味がなさそうだが関連メソッドは実装されてないようだ。このあたりはMSDNにろくに情報がないし(どこに情報があるかうまく捜せないだけだが)謎すぎる…。

2011年11月26日(土)

 試しにスレッドフォーカスシンクを追加してみた。QueryInterfaceを入れ忘れて全アプリが暴走した。ワロタ…。
 OnSetFocus→OnSetThreadFocusの順に呼び出されている。逆はOnSetFocus→OnKillThreadFocusの順。OnSetFocusがあれば十分なので不要と判断。

 そもそもskkfepだとconhostやcmdのプロセスだとInitLangBarまで処理が来ない。もしかして登録の仕方に問題があるのか?
 ITfTextInputProcessorExをベースにしてActivateExを使うように変更。変わらず。
 RegisterCategoryでGUID_TFCAT_TIPCAP_UIELEMENTENABLEDの登録が勝利の鍵だった。ようやくDOSプロンプトでも動作した。ググっても出ない…それどこ情報…どこ情報よ…。

2011年11月26日(土)

 『学習しないSKKはただの豚だ』などと低音で呟きつつ、ひとまずTAB補完の実装は後回しにしてユーザ辞書の学習処理を実装してみることにする。なんせ9月から寄り道ばかりしていて一向に進んでなかったし。趣味の寄り道は最高に楽しいし長期的に見れば必ず糧になっている…と思いたい…ので後悔はしない。

 なお上記については『学習しすぎるSKKは最悪の豚だ』とも付け加えて自戒とする。実際、10年以上まともにメンテせず発酵させたユーザ辞書はゴミのようなエントリ(特に送りがな回り)が目につくし、いったん学習なしで慣れてから学習ありに戻すと効率がかなり落ちたような気がするし。プラシーボ?スパシーバ!破ァ!

 しかしちょっと待って欲しい。もはや何もかも忘却しているのではないか?
 確か現在はユーザ辞書は順序フラグが違うだけで内部構造はほぼ一緒…だったような…フラグ収納処理は既に書いたような…忘れた…。ドキュメントは…書いてねー…将来書くつもりもねー…。まあいい。とにかくここを直す。でもってTAB補完に向けて3〜4バイトのシリアル番号を埋め込むのだ。学習できるようになったら一般公開してみるのもいいかもしれない。

2011年11月27日(日)

 などと言いつつregsvr32関連処理を詰めてまた32バイト世界を縮めてしまった…

『リア充』というネットスラングは妬みと自虐と劣等感を組み合わせて手軽に趣を演出できるので相対的な比較の用法が一般的になりつつあって本来の定義から意味が変化してしまうのは仕方のないことだがいずれは演出がインフレしてしまい『リア充』『カカロットォー!』みたいになり世界は核の炎に

(カチャカチャカチャ…)どうせ俺なんてクリスマスに孤独噛み締めて惨めな気持ちのまま回線吊って爆破必殺(ッターン!)…と思ったらまだ11月だった(ッタターン!)という脳内打鍵音のエコーが止まらない(ッタタターン!)

2011年11月28日(月)

 体調ヤバイ。
 会社休みまくってたらまた給料下がりそう。睡眠薬のストックが切れたらヤバイ。

 今さらながらVC++ではlong doubleがdoubleと同じ64ビット幅であることに気付く。Win32以前には確かに存在していたということらしい。DirectXアプリではfloat以外使うことなかったので完全に気付かなかった。long doubleなんてなかったんや…
 VC++「この世にlong doubleが存在しないことにいつ気づいた?」

2011年11月30日(水)

 ユーザ辞書の順序テーブルを廃止。
 ユーザ辞書にシリアル番号用エントリを仮追加。本来送りがなつきのエントリには不要だが、送りがなつきエントリもTAB補完の対称にする実験用にあえて加えてある。
 送りがな部分の検索処理を実装。今までは送りがな部分を無視して検索していた(要するに学習なし状態)
 送りがなありエントリの仮書込時のサイズ計測にバグを発見。修正。

 辞書の制限をきちんと書き出しておかないといかんな…。

 文字列長は拡張1ビット+6ビットで、拡張フラグは実質2ビットしか使っていないので1つにまとめるべき。とはいえフラグを使っているのは辞書データの1%程度なので辞書の格納サイズにはほとんど影響はなさそう。

2011年12月01日(木)

 送りがなエントリ処理を最適化。サイズチェックと書き込みが別々だったのを書き込みと同時に行なうようにしたらコードサイズが16バイト縮んだ。
 さらにフラグ処理を1つにまとめてソースのリファインを行なったところ160バイトほど縮んで512バイト境界を越えた。うひょー

2011年12月04日(日)

 ユーザ辞書の送りがなエントリの境界チェックがバグっていたので修正。
 辞書エントリをメモリ上に展開する際、パディング調整時にメモリのゼロクリアを行なわない動作をデフォルト動作とした。8ビットMCUだと1バイトのメモリアクセスすら時間の無駄なので。一応コンパイルオプションで変更可能としておく。

 登録処理を最適化しtextセクションを400バイト縮小。2パスで生成していたデータブロックを1パスで一気に作るようになり高速化にもなった…はず。4.5GHzのプロセッサだの3GbpsのSSDだので動いているPCでは誤差としか言いようがないが、8ビットMCUでなら有効なはずだ。
 これでtextとrdataを結合すれば実行ファイルのサイズをさらに512バイト減らせるところまで行った。先日のと合わせて1KB短縮。結合しちゃうとAvira先生が発狂するので諦めなければならないのが嘆かわしい。
 ちなみに現在のサーバプロセス用の実行ファイルは11KB。10KBの壁は厚い。
 …学習処理の実装はいっこうに進んでいない。

 処理簡略化のヒント。送りがなは255個もあれば十分なはず。そもそも送りがなの組み合わせ自体20種類くらいで十分なはずだ。送りがな関連ノードの生成処理を簡略化できる。読み込み処理は通常ノードと共通化しているためこちらを削るのは難しそう。→送りがな比較処理は簡略化可能。

2011年12月06日(火)

 SKK辞書ファイル(原作互換)のパーサを改良。行解析時のエラーチェックの強化。コードサイズ削減。
 検索処理回りのエンコード失敗時のエラーチェックを追加。

 根本的なバグを発見。ユーザ辞書の送りがな順序テーブルのビット幅の考え方を誤っていた。
 順序テーブルのビット幅は、読みの総数に依存する。順序テーブル個数のビット幅は、テーブル要素の総数に依存する。これを混同していた。きちんと区別するよう修正。
 …とはいえ、256以上単語がある送りがなつきエントリ(L辞書ですらそんなエントリはない)のユーザ辞書データ、という条件が揃わないと発動しないバグなので使い方によっては一生気付くこともないのかもしれない。見つけてよかった。

2011年12月07日(水)

 16ビットMCU向けに、奇数アドレスでの16ビット幅のアクセスを防止するコードが必要かもしれない。そのうちCortex-M3ボードで試してみよう。

2011年12月08日(木)

 バグ取り終了。次は辞書更新。メモリ気にしながら処理書くのが一番面倒な部分なのでずっと後回しにしていたが、ついにやらねばならない。修造先生MADをかけつつ気合いを入れる。

 いかにメモリ使用量を抑えつつ辞書更新を行なうかを考えていたら、全裸で銭湯を飛び出したアルキメデス\のコスプレで「嫌なら見るな!嫌なら見るな!」とか叫んで電波利権を独占しつつ全世界に生中継したくなる程度にナイスな案を思いついた。

 前提として、ユーザ辞書の登録パターンは以下の通りとなっている。

送りがななし

送りがなあり

 まず、(*)の条件を判定するために以下の2種類の工夫を行なってコードの簡略化(と転送不要を判定して無駄なメモリ書き換えを抑え高速化)を狙う。

 確定処理の前にユーザ辞書検索を呼び、フラグをチェックして(*2)を除外。
 ユーザ辞書がある程度育ってくると、多くのケースはここで終了することになる。

 さて、その後いかにメモリを消費せずに更新するかどうか、が焦点となる。
 現在、通信用バッファ(複数プロセスが辞書を使うWindows版はここが共有メモリページを使っているためこんな名称になっているが、要するにただの変換処理用ワークエリア)に2〜4KB*も*使ってしまっているので、RAM利用制限がプログラマーの命より重いワンチップマイコンではここを作業領域として使い回す以外の選択肢はありえない。

 そこで!さっきのコード共通化で使った検索処理が生きてくる。
 よく考えてみると「送りがななし」の場合、検索処理を行なった後は候補データの部分が「通信バッファ上」に「検索キー」と「すべての単語」が「登録したい順に」綺麗に並んでいるのだ。
 あとはこれをB+木の内部にある赤黒木のコンテナとして再構築する処理を書くだけでよい。書換えサイズを制限した上で既存領域に上書きしてみて、構築されたコンテナのサイズが同じならそのまま終了で(*1)のケースはカバーできる。

 サイズが異なる場合、小さくなった場合(アノテーションの内容が既存のものと異なっていた場合などに発生するまれなケース)や大きくなった場合(内容の追加)は、B+木のブロックの再構成を行なう。この処理は起動時の辞書データ読み込み(B+木構成)処理の多くを流用できるはずだ。

 問題は「送りがながあり」のケースだ。いったいどうすればよかんべか…(と毛布に包まったまま松岡修造の声を子守唄に寝てしまう)

2011年12月10日(土)

 できるできる絶対できる解決できる実装できる!おはようございました。

 送りがなありのケースでも、やはり「通信バッファ上」に「検索キー」と「先頭となる送りがな」に利用される「すべての単語」が並んでいる。となればここの末尾に「他の送りがな」関連情報も追加し、コンテナ再構築可能としておけばよいのでは?その後元のアドレスに再構築すればどうか?きた!伏線回収きた!これで勝つる!

 ただし、「他の送りがな」を抽出する機能は新規で書かないといけない。他の処理と共用にするのは困難なのでコードサイズが若干増えてしまうのを覚悟しないといけない。大丈夫。まだ11KBだし。1KB削減したばっかりだし。ぜんぜん痛くないし。泣いてないし。

 かくして追加領域を数ビット増やすだけで辞書登録も行なう目途がついた。
 あとは書くのみ。

 第一段としてバッファのフォーマットを変更。

 ちょっと寄り道。インストーラ用のファイル名を変更することにした。

setup.exe インストーラ (プロセス昇格処理とGUI部品処理)
setup.bat インストーラ補助スクリプト (ただのバッチファイル)
skkfep.dll 文字入力処理
skkfep64.dll 文字入力処理 (64ビット版)
skks.exe 辞書給仕処理
skku.exe 設定変更処理
rule.exe ベータテスター専用動作確認ツール

 バックエンド側に二重起動を検出して古い側を削除する処理を追加しつつさらにバイナリサイズを縮小。BOOLで値を返すタイプの関数の返り値によって分岐する処理などで、gotoを駆使すると値のやりとりが不要になるケースではインライン展開してもコードの質が悪いことが多い。仕方ないので手動で展開してサイズを稼ぐ。

setup.batの起動オプション(案)
- setup.exeを実行 (デフォルト)
i プログラムのインストール
u プログラムのアンインストール
e システムフォルダに残ったプログラムの削除 (予約)
r 辞書の再読み込みのみ (予約)

- システム辞書が存在しなければダウンロードし、削除は行なわない (デフォルト)
d システム辞書に関する操作を行なわない
f システム辞書を強制ダウンロード (予約)

- ユーザ辞書フォルダが存在しなければ作成し、削除は行なわない (デフォルト)
d ユーザ辞書

- システムフォルダにプログラムを転送/削除する (デフォルト)
t フォルダ内のプログラムをそのまま登録する

n プログラムの起動処理を行なわない
p 終了時に一時停止する
g エラーを無視してインストールを試みる

最終版setup.exeのボタンデザイン(案)
参考にするのはエロゲのインストーラ。そもそもskkfepはlispエンジンのないSKKモドキ、すなわちエロシーンのない抜きゲーみたいなもんですしおすし。

日本語入力システムSKKFEP
(盾)インストール(&I) i相当
(盾)アンインストール(&U) u相当

詳細設定--------------------------------------

(盾)ファイル削除(&E) e相当
辞書の再読み込み(&R) r相当

システム辞書をダウンロードしない(&D) d相当

プログラムの起動処理を行なわない(&N) n相当

フォルダ内のファイルを直接登録する(&T) t相当

-----------------------------------------------
プログレスバー領域またはログ表示など(100KB程度の転送しかないため不要か)

旧install.bat相当
setup.bat i d n

旧test_install.bat相当
setup.bat i d p t

 デフォルトのユーザ辞書のパスは $APPDATA/SKK/SKKUSER.TXT
 デフォルトのSKK辞書のパスは $windir/IME/SKK0/DICTS/SKKDICT.TXT
 とにかくオリジナルの「JISYO」というローマ字表記が嫌すぎるのでこの文字列が含まれないものにする。
 あと、パス名とファイル名の両方にSKKという文字列があって冗長なのだが、これには理由があって、ホームディレクトリにコピーしたりメールに添付してファイルが孤立した時、SKK関連のものであることが一発でわかるようにという意図がある。
 実はskkime1.5改でデフォルトをuser.dicにしたのをちょっと後悔していたので。
 あと、拡張子を.TXTにすることで、OS標準ツールであるメモ帳で 自 由 に 編 集 できることが大事。あ?初心者が勝手に書き換えるから危険?SKKなんて文字列が目に入ってる時点でもうジョブ:一般人やめてンだろ?はい、これは魔法使いのジョージです。

2011年12月13日(火)

ファイルのパスを変更するべき。

TSF/TIPモジュールのインストールパス:
Windows XP系(32bit):
%windir%\ime\TSF名\*
Windows XP系(64bit):
%windir%\ime\TSF名\*
%windir%\SysWOW64\ime\TSF名\*

Windows 7/Vista系(32bit):
%windir%\System32\IME\TSF名\*
Windows 7/Vista系(64bit):
%windir%\System32\IME\TSF名\*
%windir%\SysWOW64\IME\TSF名\*

辞書ファイルのインストールパス:
Windows XP系:
%windir%\ime\TSF名\DICTS\*
Windows 7/Vista系(32/64bit):
%windir%\IME\TSF名\DICTS\*

11/4にPICerFTがアップデートしていた
HIDaspx

2011年12月16日(金)

 UUIDとGUIDを全部直書きした…だが駄目っ…!
 誰じゃ…Uuid.libのo:\winmain.obj.x86fre\windows\advcore\ctf\uuid\objfre\i386\msctf_g.objを掴んでおるのは誰じゃ…

2011年12月17日(土)

 とりあえず記憶を1週間ほど巻き戻してやり直すことにした。
 いい加減放置していたSandSの有効/無効設定について考える。
 とりあえず共有セクションを使ってみることにする。
 テストコードを書いてみたら64ビット版の実行ファイルのヘッダが512バイトを超えてしまった…。
 仕方ないので無理矢理サイズを切り詰めるべく自作ツールを改良していたら1晩過ぎてしまった。

 そしていざコードを書いて気付いたんだが…共有セクションでは32/64ビット間でメモリ空間が違うから使いものにならん…。というか64ビットプロセス間でもなんか共有されてないっぽい。
 あと残ってる方法としては、難易度の簡単な順にレジストリかプロセス間通信あたりか。
 SKKFEPはレジストリ設定一切なしで動く!が売りなのでHKCUのエントリ以外の所に作れないか考えてみる。
 COM登録時に使うHKCR\CLSIDのあたりが使えそうだ。MSのシェル拡張(Microsoft CopyTo Service)などではこのフォルダにflagsという名前のDWORD値を置いているようだ。
 …でもなんかこのパスはエントリが大量にあってアクセスに時間かかりそうで嫌だ。
 やっぱり普通にHKCUのエントリにする。

 1. インストール時にルートのキーを生成する。生成しなかった場合は以下は機能しないが誤動作はしないようにする。
 ローマ字かな変換ルールの設定変更などを行なうタイミングでもルートのキーの生成は行なうため、インストーラで問題が発生しても後から復旧できるようにしておく。

 2. キー押下時にレジストリアクセスを行なう。キーを離した時にはアクセスしない。
 レジストリからは1〜2ビット程度のON/OFF情報と、30ビット程度の設定更新カウンタの値を取得する。

 スペースキーを押したままSandSを解除した場合、スペースキーの入力は行なわれない仕様とする。

 この仕様やめ。SKKIMEの悪い点が何も直ってない。

 アクティベートイベントを契機にレジストリアクセスを行なう。
 ウィンドウのフォーカスが永遠に切り替わらない可能性もあるので、レジストリ更新時は自プロセスの設定変更も同時に行なう。コード量は若干増える可能性があるが、無駄なレジストリアクセスを防げる。
 このくらいの頻度であればレジストリはCLSIDの場所を使っても問題なさそうだ。

 骨組みは決まったけど実装は後回し。

2011年12月18日(日)

 リソース順序を最適化しアイコンテーブルを削除。
 Server/Register関連のヘッダを整理。
 もはやMSサンプルの原形を止めてないけど人類最強だから気にしない。
 コマンドプロンプトのIME表示をMSIMEに近づけるためTF_CONVERSIONMODE_ROMANをテーブルに追加しGUID_COMPARTMENT_KEYBOARD_INPUTMODE_SENTENCEの設定を削除。

// IME状態の反映
// Windows 7のMicrosoft IMEでは言語バー上で操作した時だけ発行される
// SKKでも積極的な設定は行なわない
// 設定値とコマンドプロンプトでの表示内容の対応一覧
// 00 無 TF_SENTENCEMODE_NONE
// 01 名 TF_SENTENCEMODE_PLAURALCLAUSE
// 02 TF_SENTENCEMODE_SINGLECONVERT
// 04 TF_SENTENCEMODE_AUTOMATIC
// 08 般 TF_SENTENCEMODE_PHRASEPREDICT
// 10 話 TF_SENTENCEMODE_CONVERSATION
_pTextService->SetCompartment(GUID_COMPARTMENT_KEYBOARD_INPUTMODE_SENTENCE, TF_SENTENCEMODE_PHRASEPREDICT);

 インストーラのスクリプトを作成。根性でバッチファイルで書いたら7.5KBなんていう巨大なものになってしまった。

2011年12月20日(火)

 バグ発覚。変換中に数字入力などの半角文字を入力した場合に暗黙の確定が発動しない。
 発生条件は、コンポジションが存在する時にTestKeyDownで半角文字をEatせずに返すケース。
 EatしないためKeyDownが呼ばれず、そこで期待しているコンポジションの確定が行なわれない。
 内部では暗黙の確定は行なわれているが、アプリケーションに渡るタイミングは次にコンポジションの更新が発生した時点で行なわれ、その際コンポジションの内容についてSKKモジュールは一切感知していないというちょっと危険な動きをしている(本来のTSF/TIPではこういう動きをするように作るべきなのだろう)。
 そこで上記の条件の場合はあえてキーをEatして文字をコンポジション経由で渡すことにする。
 具体的には、sync直前にコンポジションの状態を見て、コンポジションが存在すればSandS引数をtrueにする。
 あとは同じタイミングでコントロールコードが渡った時だが…。

 終了時にコンポジション候補があり、かつeatではない場合の動きを変えるべきか?

 直した。極力コンポジションを使わずに文字を渡すという野望が仇となってコンポジションが残ったまま内部状態が食い違うバグをついに撲滅。最終的にはフラグ判定を1個所追加するだけで全て解決するというあまりにも簡単な結末に泣いた。

 テスターの方からインストーラが正常に動作しないという報告が来た。
 時限動作っぽい上に原因がわからない…。
 テストコードを動かしてもらった結果、恐らくタスクスケジューラの設定前後でカレントディレクトリが変な所に移動しているようだった。バッチファイルのブロック構文の呪いか!
 対策を構じた。

 やっぱりちゃんとWindows Installerにするべきなのであろう。めんどい…

2011年12月21日(水)

 現在異なっている挙動。

編集中にlやLを押す→現在:確定せずモード変更 原作:確定してからモード変更
編集中にQを押す→現在:何も変わらず 原作:確定してから編集モードへ

 直した。気合いじゃ。

 ドキュメントフィルタを修正。16年前の自作プログラムはなんというか滅茶苦茶だ。

 leaf側からノードを削除する処理を実装。

2011年12月22日(木)

 技術ネタをわかりやすく紹介するためにSKK擬人化萌えキャラというのはどうか。かつての科学番組みたいにお姉さんと謎の小動物的とかポケモン博士ポジションのキャラの支援を受けて近接格闘型のプリティでキュアキュアな大魔法峠で僕と大気圏突入!

だめだこいつはやくなんとかしないと(棒読み)
ろくなネタが思いつかないのだが、ちゃんと宣伝になる4コマ漫画的なものが欲しいんだけどどうすか先生!

 辞書更新の際、毎回候補を詰めるのは効率が悪い。
 コンテナのサイズが同じまたは縮小した時は同じアドレスを使い、増加した時は末尾に追加する。
 この変更に伴い、コンテナが常に連続していることを前提としたコードが前面書き直しとなる。

2011年12月23日(金)

 皮膚が乾燥するのを防ぐために必死で油を塗る……塗る……
 そして画面いっぱいに広がる油模様の悪夢

PSVitaのUIウンコー

 おらみたいな指先油ギッシュ人間にとって
 タッチ操作必須の操作系は
 死ねと言われているようなもの
 いつの日かボタン操作も許容してくれるといいのだが

 それにしてもPSVitaのハードウェアだけは本当に素晴しい。この形状、エロゲプラットフォームとしては夢の環境ではなかろうか。スマホで十分?いやいやいやいや油ギッシュのおっさんにはボタン操作が死活問題。PILOT1000の時代から「スタイラスなんかもう二度と使うもんか」と悔し涙を流すばかりだ。

2011年12月24日(土)

デニーズで
パソコンひろげて
とらのあな

某氏より通販サイトの事情などを聞く。すげぇ

2011年12月25日(日)

 辞書データの登録処理を動的配列から固定長の通信バッファに対応するよう前面書き直し。
 時代を逆行するかのプログラミングの流れになってきている気がするが、通信バッファ幅以上の単語を辞書プロセスが抱えていても意味がないので、この幅に合わせるのは必然というか自然な流れのはずなのだ。
 書き直したらバイナリが1KB縮んだ。ワロス

2011年12月26日(月)

 キター
 学習動作が動いた。まだ送りがななしエントリのみだが。

2011年12月27日(火)

 キター
 送りがなも含めて学習動作が動くようになった。
 辞書の送りがな順序テーブルの効率のよい更新方法悩んでたんだが、二回に分けて力技で実現。
 要するに送りがななし要素の検索処理と同等の処理を送りがな部分に対しても行なってるだけ。
 これでコードがだいぶすっきりした。

 あと直さねばならない処理は以下の通り。

2011年12月28日(水)

 キター
 辞書マージ・単語削除・メモリ確保最適化の実装完了。
 ついでにバイナリサイズも大幅削除してまだ12KB以内に抑えた。
 正直これ以上サイズは縮まないだろうと思うので、学習データ保存を入れたらあと1KBくらいは増えてしまうかもしれない。セーブ・ロード機能はワンチップマイコン版の実装とは関係ないので多少は目をつぶる。

2011年12月29日(木)

 メインのIMEにして使うようになってからバグがちらほら気になるようになってきた。メモを残す。

 通常変換の時にアノテーションを表示してみたら結構格好いいので、調子に乗って落ちゲーみたいに次候補を1つ表示してみようと思った。

 アノテーション表示の時などのカーソル位置がなんだか気になるので設定方法を考えなければ。
 レンダリング処理で終端を-1で表していたのをMSBのみ1の値として残りの上位ビットをカーソル位置指定フラグなどに使う方針とする。

 それにしてもSKKのアノテーションは意味不明なものが多い。長い意味不明な英文とか、使いもしない中国語とかマジ勘弁。
 これだけ酷いと、ユーザ辞書のアノテーションを即編集できる操作も必要かもしれない。

 落ちゲー的候補表示について。
表示例
熊;bear 次:隈;-なく (先頭の「熊」だけ変換対象の色で表示)

 この時、次候補は変換候補と同じ反転色がいいか、薄めの色がいいのか、むしろ赤字で目立たせるのがよいか?
 変換直後と候補一覧選択と違いが即分かるようにするためには、

変換1 >>変換2 >>変換3 ← 全部反転文字
へんかん A:変換1 B:変換2
へんかん 登録

 見出しの補完操作の時も、見出し語一覧を検索する時についでに通常変換もやっといたほうがいいのかも。

 テスターの方に送ったプログラムは順調に動いている模様。
 半角入力が捨てられるバグを修正。スタックが存在する場合は強制確定状態とする条件文を1つ追加するだけで解決できた。

 やっべ血吐いたやっべ

 XPではコンポジションの色選択にやたら時間がかかっているようなので、文字列が空の時は更新しないようにするのがいいかも。

 現在表示処理が状態遷移テーブルとswitch/case文のほぼハードコーディングに近い状態になってしまっていて変更がきつすぎるので一念発起して簡易スクリプトで処理することにした。というかスクリプトにすることでswitch/caseの共通状態が重なりコードサイズがより小さくなる(はず)、というhackbinで使った小手先のテクニックが生きるはずだ。
 コンポジション処理と完全に分離するために元の処理がBのsync(いわゆるコルーチン)的な動作だったのでスクリプト化はかなりコード効率が上がるはず。

表示制御用超簡易スクリプトについて

変換中の候補表示内容はUIの最重要要素であるため、
後から設定で簡単に修正できるようにしなければならない。
第30版以前では文字列長や表示パターンが完全に固定となっていたが
文字列ベースの表示スクリプトを使って指定できるようにする。

特殊文字で動作内容を指定。通常文字はそのまま出力。

文字列出力


内部の変換位置によって内容が変化
^@ 通信文(辞書プロセスへの出力)
^A @候補(アプリケーションへの出力)
^B @区切(アノテーションがなければ空)
^C @説明(アノテーションがなければ空)
^D @選択(ASDFJKL)
^E @残り(数値)

現在の状態
^F @編集
^G @送り
^H @子音

登録・削除時のみ有効
^I @特殊(編集スタック)
^J @補助(編集スタック送りがな)
^K @入力(ラインバッファ)

制御


^L ラベル関連コマンド用プレバイト
名前: ラベル
@i 特 特殊状態(登録または削除中)なら処理続行し、そうでなければラベルへ飛ぶ
@i 登 登録中なら処理続行し、そうでなければラベルへ飛ぶ(登録/削除の分岐に利用)
@i 編 編集中なら処理続行し、そうでなければラベルへ飛ぶ(編集/変換/選択の分岐に利用)
@i 変 変換中なら処理続行し、そうでなければラベルへ飛ぶ(編集/変換/選択の分岐に利用)
@n 次の変換候補があれば次候補を選択して処理続行し、そうでなければラベルへ飛ぶ
@l 残り候補の回数だけ処理を繰り返す
@j 指定ラベルまで飛ぶ

色指定

以降の表示色を変更する。

^P @c 編 (編集) 標準色/標準背景色 下線赤
^Q @c 集 (編集送) 標準色/標準背景色 下線青
^R @c 変 (変換) 選択色/選択背景色 下線赤
^S @c 換 (変換送) 選択色/選択背景色 下線青
^T @c 黒 (情報黒) 標準色/情報背景色 一色以上の時有効
^U @c 赤 (情報赤) 赤/情報背景色 二色以上の時有効(一色時は情報黒扱い)
^V @c 灰 (情報灰) 灰/情報背景色 四色以上の時有効(二色時は情報黒扱い)
^W @c 橙 (情報橙) 橙/情報背景色 四色以上の時有効(二色時は情報赤扱い)
^X @c 子 (子音) 現在の子音モードに合わせた色

(ひらがな) 選択色/背景橙
(カタカナ) 選択色/背景緑
(半角カナ) 選択色/背景青


記述例


特殊 (登録/削除)
@if 特殊 2
@c 赤 @特殊 @c 橙 @補助

@if 登録 1
@c 灰 " 登録 " @c 黒 @入力
@jump @2

1: @c 黒 " " @候補 @c 灰 @区切 @c 橙 @説明 @c 灰 " 削除しますか?"
@c 赤 " Y" @c 灰 ":" @c 黒 "はい"
@c 赤 " ;" @c 灰 ":" @c 黒 "変更"
@end

編集
2: @if 編集 3
@c 編 @編集 @c 集 @送り @c 子音 @子音
@end

変換
3: @if 変換 5
@c 変 @候補 @c 換 @送り @c 灰 @区切 @c 橙 @説明

@next 4
@c 赤 " 次" @c 灰 ":" @c 黒 @候補 @c 灰 @区切 @c 橙 @説明
4: @jump 7

選択
5: @c 変 @編集 @c 換 @送り
6: @c 赤 " " @選択 @c 灰 ":" @c 黒 @候補 @c 灰 @区切 @c 橙 @説明
@loop 6

7: @c 灰 " あと" @c 赤 @残り
@end

 ひとまずハードコーディングだった内容をコマンド列に直訳し、next候補とか残り候補数表示と結合したらこんな感じになった。色指定や行末の文字列の共通部分の括り出しとかやればもうちょっとバイトコードのサイズは縮まるはず。
 状態数は実質1つだけとなったので大幅にコンパクト化されるはず。コードサイズの変化はこれからコード書く。
 自分で書いておいて難だが正直読みづらいし書きづらい。あと@ifとかdoxymacsと相性悪すぎ。
 コンバータを書くときに見た目はちょっと考えよう。ちょっとした用途ならBのコードを流用するだけでできそうだし。
 大多数の人は標準設定で使うか他人の設定ファイルをコピーして使うことになるわけだから見た目はあまり問題にはならないだろうけど、こういうのは自己満足できなきゃ意味ないので。

2011年12月30日(金)

14:00
 某イベントで巨大ポスターを展示することになり、製品を探して東奔西走することに。

この時の戦略:展示のための製品を探す
15:00 近場の東急ハンズにない→御徒町の画材屋へ
15:30 新宿ハンズへ→駅から出られない→高島屋がリア充空間&日本語でおkな空間すぎて泣きながら移動
16:00 あった…けど巨大すぎて持ち込み困難そう
→ここで部品単位で揃えるアイディアを採用することになりハンズで部品を探す
→ハンズで支柱の部品発見…値段が高すぎ&底面に使う材料が巨大すぎるため100円ショップを周ることに
→高島屋の案内所で100円ショップの場所を聞くという暴挙に出る
→案内してもらった場所が見つからず放浪する
→見つかった…確かに巨大なコーナーだが部品はまったく売ってない
→諦めて地元に帰還
→地元の100円ショップを回るがまったく金属材料は売ってない
→地元のDIYショップ(20:00閉店)を発見。部品も発見
20:00なんとかなった…

2011年12月31日(土)

5:30 ねむい
10:00 開始
14:00 電子工作系サークルを回ってこの世の春を謳歌
16:00 撤収
19:00 ひたすら飯&酒モード。j氏のおごりでゴチになる

 ドキュメント第一版を試験公開。
inserted by FC2 system