mto を JavaScript(Node.js) で実装してみる
Node って JavaScript の実行環境なんだ
GitHub pages で JavaScript 版を動かしているのだけれど、これが意外と速くて使い心地が良いのであった。自分は他人の文章を変換する際はもっぱらこれを使っている(自分の文章の場合は Emacs か Vim 上でちょっとずつ確認しながら変換している)。
これは他のスクリプトと違って外部辞書を内部辞書(配列)に変換する部分が省略されているし、テキストエリアの文字列を直接使うので、ファイルオープンに時間が取られないしで、同じ土俵で比べることはできないので、常々 JavaScript の実力ってどんなものなのだろう?と気になっていた。
Pow で Node.js が使われていることは知っていたけれど「ネットワークをごにょごにょする何か、ライブラリ?」という印象しかなかった。どの記事だったのかは忘れてしまったのだけれど、JavaScript でファイルの読み込みうんぬんというものがあった。「え!JavaScript ってセキュリティのためにローカルファイルに手を出せないようになってるんじゃなかったっけ?」とびっくりした自分。
調べてみると、要は「ブラウザを使わなくても Ruby や Python3 みたいに JavaScript を使えるようにする代物」らしい。正確には違うのだろうけど、自分にとってはそれだけで十分なものだ。
実装にとりかかる
ということで JavaScript での実装だけれど、GitHub pages で動かしているものに足りないものを挙げるとすれば下記のようになるだろうか。
環境変数の利用
順次実装していくことにする。環境変数は 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);
を使えば良いことがわかった。puts
と print
の違いやね。
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 |
全体的に前回よりも遅くなっているのは、OSX のバージョンが Mavericks から El Capitan に変わっていること、各言語のバージョンが上がっていることも影響していると思われるけれども、一番はやはり辞書が大きくなっていることが原因かな。計算量が比例して増えることになっているからね(スナップショット)。
結論
JavaScript いいですね!小気味よいメソッドがないのが残念だけれど、Python のようにタブに縛られることもなく手軽に書けて、それでいて速いという。V8 エンジンが凄いのかな。でも、JavaScript でローカルマシン内部をあれこれできてしまうので、Node.js をインストールしている状態は危険かもしれない。あれこれ情報収集されちゃう。サンドボックス?砂のお城なんて知らんがな(´・ω・`)