tech.kayac.com

こんにちは。最近週一で温泉に通ってます。 nagata(@handlename)です。

Brainfuckというちょっと汚い名前のプログラミング言語があることをご存じでしょうか? たった8種類の文字からなるスーパーミニマムな言語です。

その仕様はというと・・・

  1. > ポインタをインクリメントする。ポインタをptrとすると、C言語の「ptr++;」に相当する。
  2. < ポインタをデクリメントする。C言語の「ptr--;」に相当。
  3. + ポインタが指す値をインクリメントする。C言語の「(*ptr)++;」に相当。
  4. - ポインタが指す値をデクリメントする。C言語の「(*ptr)--;」に相当。
  5. . ポインタが指す値を出力する。C言語の「putchar(*ptr);」に相当。
  6. , 1バイトを入力してポインタが指す値に代入する。C言語の「*ptr=getchar();」に相当。
  7. [ ポインタが指す値が0なら、対応する ] の直後までジャンプする。C言語の「while(*ptr){」に相当。
  8. ] ポインタが指す値が0でないなら、対応する [ にジャンプする。C言語の「}」に相当。
http://ja.wikipedia.org/wiki/Brainfuck より

たったこれだけ。

この言語でHello, world!を出力する場合、こう書けます。

+++++++++[>++++++++>+++++++++++>+++++<<<-]>.>++.+++++++..+++.>-.------------.<++++++++.--------.+++.------.--------.>+.

今回はこの言語のインタプリタをJavascriptで実装してみます。

とりあえずできたものを

必要最低限の機能しかない、簡易版です。 なお、今回は面倒だった簡単のために、「,」(入力)の実装は省いています。

brainf*ck interpreter - jsdo.it - share JavaScript, HTML5 and CSS

※Wikipediaだけ見てがががっと書いたので、 不完全な部分がありますが、 そこは目をつぶらずにフォークして直してやってください。

どんな実装?

さてさて、普段JSを書かないので、思いつきでどんどん実装していきます。

準備

とりあえず、

  • ポインタと、
  • メモリと

を表す何かが必要になります。 とりあえずメモリは配列で用意しておきましょう。

var pointer = 0;  // ポインタ。メモリのインデックス
var memory  = []; // メモリに見せかけた単なる配列

もうひとつ、ASCII文字が入った配列も用意しておきます。 制御文字は文字列で代用。

>と<

  1. > ポインタをインクリメントする。ポインタをptrとすると、C言語の「ptr++;」に相当する。
  2. < ポインタをデクリメントする。C言語の「ptr--;」に相当。
http://ja.wikipedia.org/wiki/Brainfuck より

これは簡単ですね。 先ほど用意したpointerを増減させるだけです。 ほんとはポインタの範囲なんかを管理しないといけないんでしょうが、とりあえず省略。

pointer++; // >
pointer--; // <

おお、なんというシンプルさ・・・。 この調子でどんどん書いていきます。

+と-

  1. + ポインタが指す値をインクリメントする。C言語の「(*ptr)++;」に相当。
  2. - ポインタが指す値をデクリメントする。C言語の「(*ptr)--;」に相当。
http://ja.wikipedia.org/wiki/Brainfuck より

これも簡単ですね。

memory[pointer]++; // +;
memory[pointer]--; // -;

.(ピリオド)

  1. . ポインタが指す値を出力する。C言語の「putchar(*ptr);」に相当。
http://ja.wikipedia.org/wiki/Brainfuck より

出力バッファにポインタが指すメモリ、が指す文字を追加します。

var output = '';
output += chars[memory[pointer]]; // .

,(カンマ)

  1. , 1バイトを入力してポインタが指す値に代入する。C言語の「*ptr=getchar();」に相当。
http://ja.wikipedia.org/wiki/Brainfuck より

今回は実装を省きます。

[と]

  1. [ ポインタが指す値が0なら、対応する ] の直後までジャンプする。C言語の「while(*ptr){」に相当。
  2. ] ポインタが指す値が0でないなら、対応する [ にジャンプする。C言語の「}」に相当。
http://ja.wikipedia.org/wiki/Brainfuck より

さて、一番やっかいな部分ですね。 対応する括弧にジャンプするためには、その括弧がどこにあるかを知らなくてはなりません。 ない頭をひねって括弧の対応を配列に格納する方法をとることにしました。

var input = '[++[++]+]';
braces[0] = 8;
braces[3] = 6;
braces[6] = 3;
braces[8] = 0;

こんな感じ。 入力の0番目にある開き括弧が10番目にある閉じ括弧と対応する、という意味です。

2010/08/04 11:00 括弧の対応の例が間違っていたので修正しました。

この対応配列を作るために、入力要素を一度ループさせます。 開き括弧が見つかったら、対応する閉じ括弧が見つかるまでスタックに、

  • 開き括弧ならプッシュ
  • 閉じ括弧ならポップ

していきます。 スタックが0になったらそのときの括弧が対応する閉じ括弧というわけです。

var braces = [];
var brace_stack = [];

for(var i in input) {
    var command = input[i];

    if(command == '[') {
        var j = i;

        while(true) {
            // 対応する括弧が見つからなければエラー
            if(input.length <= j) {
                throw 'invalid brace pare';
            }

            // 開き括弧ならプッシュ、閉じ括弧ならポップ
            if(input[j] == '[') {
                brace_stack.push(1);
            }
            else if(input[j] == ']') {
                brace_stack.pop();
            }

            // 対応する括弧が見つかったら抜ける
            if(brace_stack.length == 0) {
                break;
            }

            ++j;
        }

        braces[i - 0] = j - 0;
        braces[j - 0] = i - 0;
    }
}

これで括弧が出てきたとき、入力のどの部分に飛べばいいのかがわかるようになりました。

出力

最後に全体を処理して出力です。 いままで実装してきたコマンドをまとめます。

var output = '';

for(var i = 0; i < input.length; ++i) {
    var command = input[i];

    switch(command) {
      case '>': pointer++; break;
      case '<': pointer--; break;
      case '+': memory[pointer]++; break;
      case '-': memory[pointer]--; break;
      case '.': output += chars[memory[pointer]]; break;
      case '[': memory[pointer] == 0 && (i = braces[i] + 1); break;
      case ']': memory[pointer] != 0 && (i = braces[i]); break;
    }

    if(pointer < 0 || memory.length <= pointer) {
        throw 'pointer out of range (' + pointer + ')';
    }
}

こうして入力に対する出力が得られるわけです。やたー

冒頭の、「Hello, World!」はこんな感じです。 jsdo.it上のコードには、これが最初から入力されています。

+++++++++[>++++++++>+++++++++++>+++++<<<-]>.>++.+++++++..+++.>-.------------.<++++++++.--------.+++.------.--------.>+.

もうちょっと短い例を見てみましょう。 0から9までの数字を出力する場合はこう書きます。

++++++[>++++++++<-]++++++++++[>.+<-]

うん、わかりにくい。 考えるのもいやになります。 さすがBrainfuck。

おわり

今回、思いつきでBrainfuckのインタプリタを実装してみました。 普段こういうことに頭を使わないので相当時間がかかりましたが(主に括弧の対応部分)。

実用性という意味では皆無ですが、勉強ついでに実装してみるのもいいかもしれません。

(ちなみに、Wikipediaさん曰く、Brainfuckのインタプリタは98バイトでつくれるそうです)

カヤックでは無駄な部分に情熱を注げる技術者を募集しています!

最近社内で、「やりましょう。外村君よろしく。」という流れになりつつあります、外村です。こんにちは。

2010年8月21日に鎌倉どんぶりカフェbowlsで交流会イベント「ごはんとFlash with JS」を開催します。

ごはんとFlash with JS

ごはんとFlash 交流会イベントやります('10/8/21) | エントリー | _level0.KAYAC | flash ActionScript blog

JSのほうからは、LTかライブコーディングに @uupaa さんや @os0x さんを招いて開催する予定です。

詳細は以下より。

ごはんとFlash with JS

■日時

2010/8/21(sat) 19:00 - 22:00(open 18:30)

■会場

鎌倉どんぶりカフェbowls : (鎌倉市小町2-14-7 1F

■定員

80人(半立食形式)

■会費

5000円(カード不可/領収書可)

■ゲスト(予定)

  • AR三兄弟 長男さん
  • IAMAS小林さん
  • @uupaaさん
  • @os0xさん

■申し込み方法

件名と本文を下記フォーマットにしたがってEメール をお送りください。 正しくしたがっていないメールは見落としたりする可能性があります。 先着順で締め切らせていただきます。 受付けの可/不可の旨は3日以内に返信いたします。 なお予約制のためキャンセルなどは極力やめてください。 時間等変更になった場合メールおよびブログで告知しますのでご注意ください。 複数名のお申し込みは全員分のご連絡さきを記載いただけると助かります。(あとでご連絡事項がもれると困るので)

件名:
ごはんとFlash with JS 参加申し込み
本文:
名前:(フルネームで)
メールアドレス:(すぐ連絡のとれるもの)
会社名:(省略可)
宛先:
doke@kayac.com

問い合わせなどありましたらdoke@kayac.comまでご連絡ください。みなさまのご参加おまちしております!

JSer向けの2次会も企画しました。よろしければこちらも検討ください
ごはんとFlash with JS 2次会

羊毛布団を洗濯機にかけられないことを知りました。ago@kyo_ago)です。

意外と知られていない機能が多い!?Firebugの使い方を見て、プログラマ向けも欲しくなったので書いてみました。

1. ショートカット一覧

以下のページでFirebugのショートカット一覧が公開されています。

http://getfirebug.com/wiki/index.php/Keyboard_and_Mouse_Shortcuts

取り合えず以下の二つだけでも覚えておくと効率的かもしれません。

  • F12でFirebugの有効、無効の切り替え
  • 広いコマンドラインモード時にCtrl+Enterでコードを実行

また、以下のメニューからショートカットの変更も行えるので、他の拡張等とショートカットがかぶった場合でも別のキーで使用することが出来ます。

ショートカット変更

2. Firefox本体のツールバーに「要素を調査」ボタン

Firefox本体のツールバーに「要素を調査」ボタンが設置できます。

要素を調査

「ちょっと要素を調べたい」と言った場合に、いちいち「Firebug起動」->「調査ボタンクリック」ではなく、一気に要素の調査を開始できるので便利です。

Firefox本体の「ツールバー(T)」->「カスタマイズ(C)」からドラッグ&ドロップで設置できます。

3. console.debug

コンソールタブに引数の内容と、呼び出し元ファイル名、行番号を表示します。

console.debug

引数の内容をコンソールタブに表示するにはconsole.logが使われることが多いですが、呼び出し元ファイル名、行番号が一緒に出力されるので通常のデバッグではこちらの方が便利だと思います。

ただし、console.logと違ってFirebug以外の開発環境では標準でサポートされていないので注意してください。

4. XMLHTTPRequests(Ajax)の通信内容を表示

コンソールタブでXMLHTTPRequests(Ajax)の通信内容を表示します。

XMLHTTPRequests の表示

過去のバージョンでは標準でXHRの通信内容がコンソールタブに表示されていましたが、最近のバージョンでは表示されなくなっていました。

このオプションを有効にすると、過去のバージョンと同様XHRの通信内容がコンソールタブに表示されるようになります。

これに関しては接続タブから確認することも可能ですが、接続タブを有効にすると若干重くなるので簡単に確認するのであればこのオプションの方が手軽だと思います。

5. コンソール内容の継続

画面をリロードしてもコンソール内容がクリアされなくなります。

コンソール内容の継続

表示後即リダイレクトしてしまう場合のエラー確認や、リンククリック時にエラーが発生してイベントが停止できず画面遷移してしまう場合のデバッグなどに役に立つと思います。

6. イベントを記録

HTMLタブ内の要素に対する右クリックメニューから、対象の要素に発生する全てのイベントをコンソールタブに出力することが可能です。

イベントを記録

これに関しては以前もこのブログで紹介しましたが、便利なわりにあまり使われてない気がするので再度紹介いたします。

FirebugのLog Eventsに関して

「本当にイベントが発生しているのか?」、「この動作時にはどんなイベントが発生しているのか?」といった場合に役に立つかもしれません。

7. 要素の変更時にブレーク

HTMLタブ内の要素に対する右クリックメニューから、対象の要素に変更が行われた際にブレークさせることが可能です。

要素の変更時にブレーク

これに関してはスクリプトタブが有効になっており、読み込みが成功していないと動作しないので注意してください。
(スクリプトタブが無効状態だと何も発生しません)

JSが競合して同時に要素を変更している場合などに役に立つかもしれません。

8. XHR でブレーク

「7. 要素の変更時にブレーク」のXHR でブレークする版です。

XHR でブレーク

外部通信を監視したい場合には使えるかもしれませんが、JSONPはキャッチできないので注意してください。
(逆にHTML要素に対して「7. 要素の変更時にブレーク」を有効にするとJSONPをキャッチ出来ます)

9. 通信内容の継続

画面をリロードしてもネットタブの通信内容がクリアされなくなります。

通信内容の継続

「5. コンソール内容の継続」の通信内容版です。

複数画面の表示速度を比較したい場合に使えるかもしれません。

10. ブラウザキャッシュの無効化

有効にすると常にサーバからデータをダウンロードするようになります。

ブラウザキャッシュの無効化

私はWeb Developerの「無効」系メニューをToolbarに設置して使っていますが、ブラウザキャッシュの無効化を行いたいだけであればこのオプションでも良いかもしれません。
(Web Developerの「キャッシュを無効にする」とは連動します)

カヤックではちゃんと公式ドキュメントを見る技術者も募集しています!