sci

最果て風呂

mto を JavaScript(Node.js) で実装してみる

Node って JavaScript の実行環境なんだ

GitHub pages で JavaScript 版を動かしているのだけれど、これが意外と速くて使い心地が良いのであった。自分は他人の文章を変換する際はもっぱらこれを使っている(自分の文章の場合は EmacsVim 上でちょっとずつ確認しながら変換している)。

これは他のスクリプトと違って外部辞書を内部辞書(配列)に変換する部分が省略されているし、テキストエリアの文字列を直接使うので、ファイルオープンに時間が取られないしで、同じ土俵で比べることはできないので、常々 JavaScript の実力ってどんなものなのだろう?と気になっていた。

Pow で Node.js が使われていることは知っていたけれど「ネットワークをごにょごにょする何か、ライブラリ?」という印象しかなかった。どの記事だったのかは忘れてしまったのだけれど、JavaScript でファイルの読み込みうんぬんというものがあった。「え!JavaScript ってセキュリティのためにローカルファイルに手を出せないようになってるんじゃなかったっけ?」とびっくりした自分。

調べてみると、要は「ブラウザを使わなくても Ruby や Python3 みたいに JavaScript を使えるようにする代物」らしい。正確には違うのだろうけど、自分にとってはそれだけで十分なものだ。

実装にとりかかる

ということで JavaScript での実装だけれど、GitHub pages で動かしているものに足りないものを挙げるとすれば下記のようになるだろうか。

  1. 環境変数を取得すること
  2. 外部ファイルを読み込むこと
  3. 正規表現を使って内部辞書にすること

環境変数の利用

順次実装していくことにする。環境変数process.env["hoge"] で取得することができる。自分が環境変数を利用しているのは、外部辞書の場所を知るためなので、

var kanajisyo = process.env["MTODIR"] + '/dict/kana-jisyo';
var kanjijisyo = process.env["MTODIR"] + '/dict/kanji-jisyo';

こんな感じでグローバル変数にしちゃう。let か var かで混乱しちゃうのは何故なのか?ついでに var dicarr = []; で空の内部辞書も設定しておく。var dicarr = new Array(); とする方法もあるようだけれど、何が違うのかな?

外部ファイルを読み込む

とりあえずカレントディレクトリにあるファイルを決め打ちで読み込んでみる。var fs = require('fs'); はオマジナイという認識で済ます。

var fs = require('fs');
var buf = fs.readFileSync('./hoge.txt', 'utf8');
console.log(buf);

ストリームなのか何なのか知らんけど、buf にはファイルの内容がすべて入って、console.log(); で一気に出力することができるみたい。ファイルを一行ずつ読み込んで処理をしたいのだけれど、どうすればいいのかな?改行で分割して for で回すことにした。

正規表現

match(/*hoge*/) を使うみたい。golang と違って \s が使えるのがうれしい。GitHub pages では下記のように replaceall っぽいものを作って置換しているのだけれど、

// 常套句らしい
function gsub(str, key, val) {
    return str.split(key).join(val);
}

この key の部分にも正規表現が適用できるのね。てっきり文字列だけかと思ってた自分。つまり // が大切ってことやね。悩みそうな部分が棚ぼたで回避できたので嬉しい。先を急ぐんでな。

配列に後ろから突っ込むには .push() を使うとのこと。ということで、内部配列を作成する部分が完成した。

function dictCreator(jisyo) {
    var buf = fs.readFileSync(jisyo, 'utf8');
    var lines = buf.split('\n');
    for (var i = 0; i < lines.length; i++) {
        if (lines[i].match(/^;.*|^$/)) {
        } else {
            var str = gsub(lines[i], /\s+;.*/, "");
            var pairs = str.split(' /');
            dicarr.push(pairs);
        }
    }
}

あとは同様にして読み込んだファイルを置換する部分を作ってやる。確認のため他の言語で変換させたものと diff してみる。改行がひとつだけ多い。どうやら console.log(buf); は改行を付けるらしく、process.stdout.write(buf); を使えば良いことがわかった。putsprint の違いやね。

function stringCarReplacer(ifile) {
    var buf = fs.readFileSync(ifile, 'utf8');
    for (var i = 0; i < dicarr.length; i++) {
        buf = gsub(buf, dicarr[i][0], dicarr[i][1]);
    }
    process.stdout.write(buf);
}

うまくいった。他の言語と同じ出力が出来ていれば、仕様を満たしているということにして納品だ。あ、でもこれ指定したファイルが無かった時のエラー処理ができてない。どうすればいいんだろう? try catch かな?うむ、ENOENT ね。

お約束のベンチマーク

いつもと同様にして速度計測。読み込ませるファイルは1回目と同じ 2.6MB のテキストファイル。ちなみに2回目はこちら

言語 時間(s) Ver
Python3 5.415 3.5.0
node.js 5.998 4.2.2
Perl5 6.006 5.18.2
C 13.176 7.0
C#(mono) 13.357 4.0.5
Ruby 14.308 2.3.0dev
Go 22.202 1.5.1

f:id:nakinor:20151123201637j:plain

全体的に前回よりも遅くなっているのは、OSX のバージョンが Mavericks から El Capitan に変わっていること、各言語のバージョンが上がっていることも影響していると思われるけれども、一番はやはり辞書が大きくなっていることが原因かな。計算量が比例して増えることになっているからね(スナップショット)。

結論

JavaScript いいですね!小気味よいメソッドがないのが残念だけれど、Python のようにタブに縛られることもなく手軽に書けて、それでいて速いという。V8 エンジンが凄いのかな。でも、JavaScript でローカルマシン内部をあれこれできてしまうので、Node.js をインストールしている状態は危険かもしれない。あれこれ情報収集されちゃう。サンドボックス?砂のお城なんて知らんがな(´・ω・`)