iPX社員によるブログ

iPX社員が"社の動向"から"自身の知見や趣味"、"セミナーなどのおすすめ情報"に至るまで幅広い話題を投下していくブログ。社の雰囲気を感じ取っていただけたら幸いです。

SKKについて

近況

ひさしぶりの投稿になります。砂子です。年の瀬の、寒さの身にしみる季節となりました。12/09に本社の引越しがあり、真新しいオフィスでの勤務に励んでおります。 さて、皆様は日本語を入力する際にどのIMEを使用しているでしょうか?OSに標準で備わっているIMEを使ている方が多いはずですが、メジャーなところでは以下のような選択もあります。

本記事では私がEmacsで使用してる入力方式SKKの簡単な紹介とその機構の一部である辞書サーバの実装を試みたのでその紹介を行います。

SKKとは

SKKとは Simple Kana to Kanji conversion program の略となります。詳細はWikipediaなどに譲るとしてその大きな特徴はかなと漢字の区切りをユーザが指定することでしょう。

具体例でみてみましょう。「猫が部屋で寝ている。」という文章をWindowsIMEで入力することを考えます。打鍵するキーを英字(a-zA-Z)とスペース(␣)で表すと下記のようになります。(変換は一回で完了とする)

nekogaheyadeneteiru.␣

SKKでは下記のようになります。

Neko␣gaHeya␣deNeTeiru.

SKKの打鍵には所々に大文字の英字があります。これは2つの規則によるものです。一つ目の規則は漢字の始まりであることです。「猫が部屋で寝ている。」では漢字「猫」、「部屋」、「寝」の始まりのかな「ね(猫)」、「へ(部屋)」、「ね(寝)」に対応しています。2つ目の規則は漢字の送り仮名の始まりであることです。「猫が部屋で寝ている。」では「て」に対応します。

このようにして漢字とかなの境界を明示的に入力することで変換を行うときに曖昧さがなくなり変換する文節を間違えることはありません。SKK以外であると文を入力後に形態素解析などの技術を利用して文節を推測するため時には予期しない変換が行われていることがあります。

ちなみにSKKは入力方式の名称で具体的なIMEとしては下記のようなものがあります。 * AquaSKK(Mac) * SKK日本語入力FEP(Windows)

DDSKKとは

DDSKK(Daredevil SKK)はEmacsで専用のIMEです。つまり、Elispで実装されたEmacsでの日本語入力のみを実現するためのIMEです。

なぜOS標準のIMEを使わず、そんなIMEが使うのか?Emacsは非常に古いソフトウェアであるせいか、OSのIMEでの入力がダメな場合があるためです。いままでFedora、Arch、Manjaro、UbuntuのさまざまなLinuxディストリビューションでインストールされたEmacsIMEを使いました。しかし、Emacs内で日本語の入力を試みると変換候補があらぬ位置に表示されたり、そもそも反応しないことも多々ありました。さらに最近ではWindow10上でWSL+X410でEmacsを使っていますが、全角/半角キーを押すと`(バッククオート)が入力される始末です。

以上の理由からElispで実装されEmacsのみに依存しているDDSKKを使用しています。

辞書サーバ(SKKserv)

IMEは辞書が必要です。入力されたかなに対応する漢字を保持するためです。設定なので会社固有のキーワードを登録したり、外部の辞書をインポートして使用している方もいるかと思います。

SKKにも辞書がありファイル形式で配布されてます。しかし、もう一つの方式としてTCP通信を利用してサーバからかなに対応する漢字の候補を取得するこというものがあります。 なぜ、こんな機能があるかといえば恐らくですが昔のPCのスペックが悪い、Emacsがマルチスレッドに対応していないなどが理由と推測します。

SKKservのクライアントとの通信プロトコルは公開されており、下記ページにも記載があります。 * https://ja.osdn.net/projects/pysocialskkserv/wiki/SKKServ

これをRust言語で実装し、EmacsのDDSKKと通信を試みます。

SKKServ Rust実装

辞書を作るなどは難しいので「えんしゅうりつ」を変換すると実際の値「3.14159265359」を変換候補として返却するSKKservの実装です。

主に気をつけるのはクライアントとの文字列のやり取りがEUC_JPであることです。

use encoding_rs::*;
use std::convert::From;
use std::io;
use std::io::prelude::*;
use std::net::{TcpListener, TcpStream};

fn handler(mut stream: TcpStream) -> Result<String, io::Error> {
    println!("Connection from {}", stream.peer_addr()?);
    let mut buffer = [0; 1024];

    let _nbytes = stream.read(&mut buffer)?;
    let (cow, _, _) = EUC_JP.decode(&buffer[..]);

    let msg = String::from(cow);

    let command = msg.chars().next().unwrap();

    let mut response = "".to_string();

    match command {
        '0' => {}
        '1' => {
            if msg[1..].starts_with("えんしゅうりつ") {
                response = format!("1/{}/\n", 3.141_592_653_59f64);
            } else {
                response = "4\n".to_string();
            }
        }
        '2' => response = "Software Name.Major.Minor".to_string(),
        '3' => response = "localhost:127.0.0.1:".to_string(),
        '4' => {
            if msg[1..].starts_with("えんしゅうりつ") {
                response = format!("1 {}\n", 3.141_592_653_59f64);
            } else {
                response = "4\n".to_string();
            }
        }
        _ => {}
    }

    let (cow, _, _) = EUC_JP.encode(&response);
    let _ = stream.write_all(&cow);
    stream.flush()?;

    Ok(command.to_string())
}

fn main() -> io::Result<()> {
    let listener = TcpListener::bind("127.0.0.1:8001")?;

    for stream in listener.incoming() {
        match stream {
            Ok(stream) => {
                handler(stream).unwrap();
            }
            Err(e) => eprintln!("{}", e),
        }
    }

    Ok(())
}

通信

実際にEmacsのDDSKKと通信するためにEmacsに書き設定を追加し、上記実装を実行しました。

(setq skk-server-host "127.0.0.1")
(setq skk-server-portnum 8001)

結果「えんしゅうりつ」が「3.14159265359」に変換することを確認できました。

結び

令和になってもEmacsネタです。令和が終わるころまでにはVisualStudioCodeに移行しようと考えています。

普段使っているツールの仕組みを知るのも面白いものですね。ではまた。