ビジュアルプログラミング・エディタのBlocklyをプログラムする

## はじめに

世の中には数多のプログラミング言語がありますが、テキストではなくブロックなどのオブジェクトを組みたててプログラミングをする、ビジュアルプログラミング言語というジャンルがあります。

ビジュアルプログラミング言語には、データフローやビジネスプロセスモデリング[^1]に特化したものもありますが、教育目的で利用される[Scratch](https://scratch.mit.edu/)という言語の名前を聞いたことがある人もいらっしゃると思います。または、[Hour of Code](https://hourofcode.com/jp)[^2]という、コンピューターサイエンスが身近で楽しいことを伝える世界的な活動をご存知かもしれません。

Scratchライクなビジュアルプログラミング・エディタが簡単に作れる[Blockly](https://developers.google.com/blockly/)を使うと、ビジュアルプログラミング環境をかんたんに作ったりカスタマイズすることができます。[Hour of Code](https://hourofcode.com/jp)の教材の[code.org](https://code.org/)も、Blocklyを利用しています。

Blocklyの日本語記事がほとんどなかったので、導入からカスタマイズのやりかたまでを書いてみます。[^3]

![Screen Shot 2017-12-17 at 16.41.10.png](https://qiita-image-store.s3.amazonaws.com/0/5492/83d8c329-16e9-349f-5fef-3d4e3bdc7962.png)
※ [Google for Education > Blockly](https://developers.google.com/blockly/)より


## セットアップ

[Get Started](https://developers.google.com/blockly/guides/get-started/web)にBlocklyのライブラリのリンクがあります。

基本的には、公式ドキュメントを読み進めて行けばBlocklyをカスタマイズしていくところまでできます。
とはいえ、環境準備なども含めると手間になるので、テンプレートとなるリポジトリを作りました。

予めnode.jsをインストールしておいてください。

```
$ git clone -b getting_started git@github.com:taise/blockly_custom.git && cd blockly_custom
$ npm install yarn -g
$ yarn
```

ここまで終わったら、以下のコマンドを実行するとBlocklyの画面が表示されます。

```
$ npm run http-server
```

## ブロックを設置する

環境準備が終わったので早速ブロックを配置していきましょう。
左側のグレーのエリアは`toolbox`といい、プログラミングで使えるブロックを配置します。

今回は`controls_repeat`,`text`,`text_print`のブロックをおいてみます。ブロックを追加するには、`id="toolbox"`の属性を持つ`xml`タグに利用したいブロックを定義していきます。


```xml
  <xml id="toolbox" style="display: none">
      <block type="controls_repeat"></block>
      <block type="text"></block>
      <block type="text_print"></block>
  </xml>
```

すると、すると以下のようにブロックが表示されます。
ブロックを右側の`workspace`エリアにドラッグ & ドロップすることで、プログラミングをしていきます。

![Dec-17-2017 20-16-07.gif](https://qiita-image-store.s3.amazonaws.com/0/5492/d03030aa-6732-dd49-6a62-b2752d730543.gif)

たくさんのブロックがカスタマイズなく利用できます。全てのブロックは紹介しきれないので、他のブロックも見てみたい場合は
[こちらのリンク先のデモ](https://blockly-demo.appspot.com/static/demos/toolbox/index.html)が参考になると思います。

## ブロックのカテゴライズ

ブロックが増えてきた場合は、ブロックのカテゴライズができます。
`toolbox`の`xml`にloop系のブロックを足してカテゴリーを作ります。

```xml
  <xml id="toolbox" style="display: none">
    <category name="くりかえし">
      <block type="controls_repeat_ext"></block>
      <block type="controls_repeat"></block>
      <block type="controls_whileUntil"></block>
      <block type="controls_for"></block>
      <block type="controls_forEach"></block>
      <block type="controls_flow_statements"></block>
    </category>
    <category name="文字と出力">
      <block type="text"></block>
      <block type="text_print"></block>
    </category>
  </xml>
```

![Dec-17-2017 22-03-57.gif](https://qiita-image-store.s3.amazonaws.com/0/5492/d4ee7248-48f2-b674-bf8f-74e142905732.gif)

カテゴリーは、複数階層作ることもできます。
なお、ここで追加したloop系のブロックは、[blockly/blocks/loops.js](https://github.com/google/blockly/blob/f992ea0f9cc3639ccee96fa8a92056603a662201/blocks/loops.js)のものを羅列しています。


## Blocklyの設定

カテゴリーを作ると右下にゴミ箱(`trashcan`)が出てきましたが、これをいつでも表示させたいなど、Blockly全体に関わる設定は`Blockly.inject`をする際に行います。

[Configuration](https://developers.google.com/blockly/guides/get-started/web#configuration)のところに設定項目のリストがあります。

試しに色々設定してみます。
- 最大ブロック数: 3
- グリッドを表示
- ゴミ箱を表示
- ズームのコントロールパーツ表示


```js:index.js
Blockly.inject(
  'blocklyDiv',
  {
    toolbox: document.getElementById('toolbox'),
    maxBlocks: 3,
    grid: {
      spacing: 18,
      length: 3,
      colour: '#ccc',
      snap: true,
    },
    trashcan: true,
    zoom: {
      controls: true,
      wheel: true,
      startScale: 1.0,
      maxScale: 3,
      minScale: 0.3,
      scaleSpeed: 1.2,
    },
  },
);
```

![Dec-17-2017 23-36-47.gif](https://qiita-image-store.s3.amazonaws.com/0/5492/1ba80c33-4041-d83e-f365-ee29bc1fc003.gif)


## 実行可能なJavaScriptを出力する

Blocklyはブロックで作ったプログラムをJavaScriptに変換して実行することができます。まずはJavaScriptに変換して表示してみます。

`index.html`に`javascript/compressed.js`の読み込ませ、`JavaScript`表示用のボタンと表示エリアを作ります。

```html:index.html
  <button id="showCode">JavaScriptを表示する</button>
  <pre id='jsCode'></pre>

  <script src="lib/google-blockly/blockly_compressed.js"></script>
  <script src="lib/google-blockly/blocks_compressed.js"></script>
  <script src="lib/google-blockly/javascript_compressed.js"></script>
```

`Blockly.JavaScript.workspaceToCode(workspace)`のように実行すると、`workspace`に置かれたブロックをJavaScriptに変換できます。

```js:index.js
const workspace = Blockly.inject(
  'blocklyDiv',
  {
    toolbox: document.getElementById('toolbox'),
    trashcan: true,
  },
);

function showCode() {
  Blockly.JavaScript.INFINITE_LOOP_TRAP = null;
  const pre = document.getElementById('jsCode');
  pre.innerHTML = Blockly.JavaScript.workspaceToCode(workspace);
}

document.getElementById('showCode').addEventListener('click', showCode, false);
```

![Dec-18-2017 00-10-53.gif](https://qiita-image-store.s3.amazonaws.com/0/5492/070ae78a-3962-3ca9-d8cc-e7c6063aa158.gif)

なお、出力できる言語は`JavaScript`だけではなく、python, php, lua, dartにも対応しています。[^5]


## ブロックをJavaScriptとして実行する

JavaScriptの文字列が取得できたので、`eval()`を使えばブラウザ上で実行することができます。ただし、htmlに埋め込むために`&`などが実体参照に変換されていたり、人間の目で追えない速度で実行されてしまったり、セキュリティのリスクがあるなど、そのまま使うにはいくつか課題もあります。
そのため、公式ドキュメントでも紹介されているとおり、[JS Interpreter project](https://github.com/NeilFraser/JS-Interpreter)を使うのが良さそうです。

まずは、`index.html`に実行用のボタンとインタープリタのスクリプトを読み込ませましょう。

```html:index.html
  <button id="showCode">JavaScriptを表示する</button>
  <button id="runCode">JavaScriptを実行する</button>
  <pre id='jsCode'></pre>

  <script src="lib/js-interpreter/acorn_interpreter.js"></script>
  <script src="lib/google-blockly/blockly_compressed.js"></script>
  <script src="lib/google-blockly/blocks_compressed.js"></script>
```

つづいて`index.js`にインタープリタの設定を書いていきます。`alert`が実装されていないため、`createNativeFunction`で追加する必要があります。

```js:index.js
function runCode() {
  let maxSteps = 10000;
  const code = Blockly.JavaScript.workspaceToCode(workspace);

  function initialize(interpreter, scope) {
    function alertWrapper(text) {
      const msg = text ? text.toString() : '';
      return interpreter.createPrimitive(window.alert(msg));
    }

    interpreter.setProperty(scope, 'alert', interpreter.createNativeFunction(alertWrapper));
  }

  const jsInterpreter = new Interpreter(code, initialize);
  while (jsInterpreter.step() && maxSteps) {
    maxSteps -= 1;
  }
  if (!maxSteps) {
    throw EvalError('Infinite loop.');
  }
  jsInterpreter.run();
}

document.getElementById('showCode').addEventListener('click', showCode, false);
document.getElementById('runCode').addEventListener('click', runCode, false);
```

これだけでブロックが実際に実行できるようになりました。

![Dec-18-2017 07-24-52.gif](https://qiita-image-store.s3.amazonaws.com/0/5492/a95895da-9121-46ee-8b12-7ea539500fcb.gif)


## ブロックをカスタマイズする

Blocklyは、ブロックの定義を追加することができます。
今回は文字列をシャッフルするブロックを作ってみます。

これだけだと取っ掛かりがなさすぎるので、[Githubのblockly/blocks/text.js](https://github.com/google/blockly/blob/f992ea0f9cc3639ccee96fa8a92056603a662201/blocks/text.js#L533-L555)を見てみましょう。すると中身がJSONで定義されていることがわかります。

JSONのなかで参照されている`Blockly.Msg`は、表示用の文字列で、別ファイルで読み込まれています。Blocklyはi18n対応されており、`msg/js/ja.js`を読み込んでいるため日本語表示されています。
表示される日本語に違和感を感じた方や、漢字が多くて小学校低学年向けには厳しいという方は、このファイルを書き換えてあげるとよいでしょう。

すこし話がそれてしまいましたが、カスタムブロック用のjsファイルを作ります。
今回のブロックは、シャッフルしたい文字だけなので`message`と`args`が1つずつです。


```js:custom_block.js
Blockly.Blocks.text_shuffle = {
  /**
   * Block for shuffle characters.
   * @this Blockly.Block
   */
  init() {
    this.jsonInit({
        message0: '%1をシャッフルする',
      args0: [
        {
          type: 'input_value',
          name: 'TEXT',
          check: 'String',
        },
      ],
      output: 'String',
      inputsInline: true,
      colour: 160,
      tooltip: '文字の順番をランダムでいれかえる',
    });
  },
};
```

`Blockly.Blocks`を参照したいのと、`Blockly.inject`で環境が初期化される前に読み込みたいので、scriptファイルの読み込み順に気をつけてください。


```html:index.html
  <script src="lib/google-blockly/msg/js/ja.js"></script>
  <script src="custom_block.js"></script>
  <script src="index.js"></script>
```

これで再度読み込んでみると...でました！

![Dec-18-2017 08-59-26.gif](https://qiita-image-store.s3.amazonaws.com/0/5492/7f2ebf59-b23e-3d97-81fa-173c3bbd5591.gif)


しかし、「JavaScriptを表示/実行する」は、ボタンを押しても反応がありません。
コンソールを開くとエラーが出ています。実装していないので当然ですね。実装しましょう。

`Blockly.JavaScript`に対象ブロックのJavaScriptを生成するメソッドを生やします。
見ていただくとわかりますが、そうです、ただの文字列連結です。
当たり前なんですがこうしてみると、改めてプログラミング言語って文字列なんだなと思わされますね。

```js:custom_block.js
Blockly.JavaScript.text_shuffle = function(block) {
  const args0 = Blockly.JavaScript.valueToCode(block, 'TEXT', Blockly.JavaScript.ORDER_FUNCTION_CALL) || '\'\'';
  const OPERATOR = ".split('').sort(function(){return 0.5-Math.random()}).join('')";
  return [args0 + OPERATOR, Blockly.JavaScript.ORDER_MEMBER];
};
```

では、これで実行してみます。

![Dec-18-2017 09-05-54.gif](https://qiita-image-store.s3.amazonaws.com/0/5492/e5e2a8ce-f9b7-22eb-d99d-a4b5c8a627f6.gif)


カスタムブロックのJavaScriptを実行できました


## おわりに

これで自由にBlocklyをカスタムしながら、コンテンツを作る準備が整いました。
さらっとできる感じで書いてますが、なんだかんだ詰まりつつここまでまとめています。

基本的には公式ドキュメントに記載している範囲の内容ですので、もっと詳しく知りたくなった方は[Blocklyのドキュメント](https://developers.google.com/blockly/guides/overview)をご覧ください。

[^6]


[^1]: 例えば、[BPEL](https://ja.wikipedia.org/wiki/BPEL)などがあります。BPELは、XMLベースのビジュアルプログラミング言語で、SOAが流行していた時期にサービス間のオーケストレーションを目的に使われていましたね。
[^2]: 私が所属するリブセンスもCSR活動として[Hour of Codeのワークショップ](http://www.livesense.co.jp/news/n/2017/1115_01.htm)を開催しました。
[^3]: BlocklyにはWeb / Android / iOS向けのライブラリがありますが、Web版のみです。
      なお、この記事は職業プログラマ向けのレベルで書かれています。
[^5]: 多言語への切り替えは[generating code](https://developers.google.com/blockly/guides/configure/web/code-generators#generating_code)を参照ください。
[^6]: [Livesense - 学 Advent Calendar 2017](https://qiita.com/advent-calendar/2017/livesense-gaku)の記事として書かれたものです。