ギャップバッファのつづき。
辞書のような機能を追加します。
Luaを使って手抜きします。
と言えば真っ先にに日本語入力システムを思い浮かべるかもしれませんが、
今回作るのはいわゆる静的略称展開の機能です。
そもそもマルチバイト文字に (まだ) 対応していませんので。
基本的にはただのテキスト形式で、
キーと値の組を並べます。キーは1行ですが値は複数行でも可とします。
たとえば ``main'' を「空のmain関数の定義」に展開したいなら以下のように書きます。
<main >int main(int argc, char *argv[]) >{ > return 0; >} >
このテキストデータを jisyo.txt に保存しておきます。
文字列の配列の配列を返す関数をつくります。
以下のLuaのコードはすべて im.lua というファイルに保存してください。
-- input method local lt = string.byte("<") local gt = string.byte(">") im.load = function(jisyo) assert(type(jisyo) == "string") local xs, xss xss = {} for line in io.lines(jisyo) do local c = string.byte(line) if c == lt then xs = {string.sub(line, 2)} table.insert(xss, xs) else assert(c == gt) assert(#xss > 0) table.insert(xs, string.sub(line, 2)) end end table.sort(xss, function(xs, ys) return xs[1] < ys[1] end) return xss end im.dict = im.load("jisyo.txt")
ミニバッファの内容をLuaの文字列に変換するやり方もありますが、ここでは
ミニバッファの内容とLuaの文字列を比較する関数 im.compare を用意します (後述)。
Lua側では im.compare を使って辞書の二分探索をします。
テーブルの参照は O(1) なのに O(log n) の二分探索をする理由は、
キーの最初の何文字かを入力するだけで済むと便利だからです。
im.search = function() local left, right = 1, #(im.dict) local mid while left < right do mid = math.floor((left + right) / 2) if im.compare(im.dict[mid][1]) > 0 then left = mid + 1 else right = mid end end return im.dict[left] end
ミニバッファを使っている時だけLuaが呼ばれます。
これを「Luaモード」と呼ぶことにします。
そうでない時はCだけで処理します (そちらのコードは後述)。
文字列を描画したりバッファに挿入したりするためのCの関数を用意して
Luaから呼べるようにしておきます。
local cr = string.byte("\r") local lf = string.byte("\n") im.height = 5 im.keyboard = function(key) if key == cr or key == lf then local t = im.search() im.insert(t[2]) for i = 3, #t do im.newline() im.insert(t[i]) end else im.default_keyboard(key) local t = im.search() im.clear() for i = 1, math.min(#t, im.height) do im.display(1, i, t[i]) end end end im.keyboardfunc(im.keyboard)
extern int im_mode; void im_init(void); void im_keyboard(int key); #define key_max 64 extern unsigned char key[key_max]; extern int key_len;
Luaモードのときim_keyboardが呼ばれます。
こういうのをさらっと書けてしまうのがLuaの長所です。
#include <stdio.h> #include <stdlib.h> #include "lua.h" #include "lualib.h" #include "lauxlib.h" #include "pixels.h" #include "buffer.h" #include "im.h" static int im_compare(lua_State *L) { int i, j; const unsigned char *p, *q; size_t len; p = key; i = key_len; q = luaL_checklstring(L, 1, &len); j = len; while (1) { if (i == 0) { lua_pushinteger(L, 0); break; } else if (j == 0 || *p > *q) { lua_pushinteger(L, 1); break; } else if (*p < *q) { lua_pushinteger(L, -1); break; } i--, j--, p++, q++; } return 1; } static int im_insert(lua_State *L) { const char *p; size_t len; size_t i; p = luaL_checklstring(L, 1, &len); for (i = 0; i < len; ++i) { if (b_insert(*p++) == -1) { break; } } lua_pushinteger(L, i); return 1; } static int im_newline(lua_State *L) { int i; i = b_newline(); lua_pushinteger(L, i); return 1; } #define DEL 0x7f static int im_default_keyboard(lua_State *L) { int i; i = luaL_checkint(L, 1); switch (i) { case '\t': im_mode = 0; i = 0; break; case '\b': key_len = key_len == 0 ? 0 : key_len - 1; i = 0; break; case DEL: key_len = 0; i = 0; break; default: if (key_len == key_max) { i = -1; } else { key[key_len++] = i; i = 0; } break; } lua_pushinteger(L, i); return 1; } static int im_display(lua_State *L) { int x, y; size_t len; const char *p; x = luaL_checkint(L, 1) - 1; y = luaL_checkint(L, 2) + BOTTOM; p = luaL_checklstring(L, 3, &len); draw_string(x, y, p, len); return 0; } static int im_clear(lua_State *L) { int i, j; for (j = (BOTTOM + 1) * 16; j < HEIGHT; ++j) { for (i = 0; i < WIDTH; ++i) { pixels_(i, j)[red] = 0; pixels_(i, j)[green] = 0; pixels_(i, j)[blue] = 0; } } return 0; } static int im_keyboardfunc(lua_State *L) { luaL_checktype(L, 1, LUA_TFUNCTION); lua_pushlightuserdata(L, im_keyboard); lua_insert(L, 1); lua_settable(L, LUA_REGISTRYINDEX); return 0; } int im_mode = 0; unsigned char key[key_max]; int key_len = 0; static lua_State *L; void im_init(void) { static luaL_Reg im[] = { {"compare", im_compare}, {"insert", im_insert}, {"newline", im_newline}, {"default_keyboard", im_default_keyboard}, {"display", im_display}, {"clear", im_clear}, {"keyboardfunc", im_keyboardfunc}, {NULL, NULL}}; if ((L = luaL_newstate()) == NULL) { fprintf(stderr, "failed to create lua_State\n"); exit(EXIT_FAILURE); } luaL_openlibs(L); luaL_register(L, "im", im); lua_getfield(L, -1, "keyboardfunc"); lua_getfield(L, -2, "default_keyboard"); lua_call(L, 1, 0); if (luaL_loadfile(L, "im.lua")) { fprintf(stderr, "failed to load im.lua\n"); exit(EXIT_FAILURE); } lua_call(L, 0, 0); } void im_keyboard(int key) { lua_pushlightuserdata(L, im_keyboard); lua_gettable(L, LUA_REGISTRYINDEX); lua_pushinteger(L, key); lua_call(L, 1, 0); }
変更なし。
clear()の中でBOTTOMを(BOTTOM + 1)に変更。
void clear(void) { int i, j; for (j = 0; j < (BOTTOM + 1) * 16; ++j) { for (i = 0; i < WIDTH; ++i) { pixels_(i, j)[red] = 0; pixels_(i, j)[green] = 0; pixels_(i, j)[blue] = 0; } } }
if (im_mode) { im_keyboard(key); glutPostRedisplay(); return; }
一応 diff gap_buffer abbrev をして確認。
Only in abbrev: im.c Only in abbrev: im.h Only in abbrev: im.lua Only in abbrev: jisyo.txt diff gap_buffer/main.c abbrev/main.c 3a4 > #include "im.h" 18a20 > draw_string(0, BOTTOM, key, key_len); 40a43,47 > if (im_mode) { > im_keyboard(key); > glutPostRedisplay(); > return; > } 41a49 > case '\t': im_mode = 1; break; 78a87 > im_init(); diff gap_buffer/pixels.c abbrev/pixels.c 161c161 < for (j = 0; j < BOTTOM * 16; ++j) { --- > for (j = 0; j < (BOTTOM + 1) * 16; ++j) {