Interpreterとは
Blocklyのコードを実行する時、簡単なソースコードなら eval() を使えばいいんですけれど、ユーザーブロックを安全に走らせる必要がある場合、独自のJavaScriptインタープリタを組み込みます。
JavaScriptインタープリタは、Google Blockly本家が推奨しています。Blockごとのステップ実行・中断・再開が可能で、実行中のBlockをハイライトすることができるようになっています。
以下の記事は、信号機のライトの点滅を制御することを例として、Blocklyでプログラミングする時、ブロック毎のステップの実行やブロックをハイライトに表示する方法などを説明して見ます。
カスタムブロックの作成
Blocklyの開発ツールを利用して、以下のブロックを作成してみましょう。ブロックの名称は「stoplightswitch」となっております。
ブロックの作成と同時、生成されたJavaScript定義コードを「stoplightblocks.js」に保存しましょう。
中身は以下となります。
Webプロジェクトを作成
カスタムブロックを実装するために、下図ような簡単なWebプロジェクトを作成しましょう。
プロジェクトの中に、以下のファイルがあります
- stoplight.html → Blocklyのステップ実行とハイライト表示を説明するためのファイル
- stoplightblocks.js → カスタムブロックの定義コード、関数を記載するファイル
- stoplight.pnt → 信号機のイメージファイル
【stoplight.html】
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8"/>
<title> blockly game</title>
<script src="./js-interpreter/acorn_interpreter.js"></script>
<script src="./blockly/blockly_compressed.js"></script>
<script src="./blockly/blocks_compressed.js"></script>
<script src="./blockly/javascript_compressed.js"></script>
<script src="./blockly/msg/js/en.js"></script>
<script src="./stoplightblocks.js"></script>
<style type="text/css">
body {
background-color: #fff;
font-family: sans-serif;
}
h1 {
font-weight: normal;
font-size: 140%;
}
#light {
position: absolute;
left: 650px;
top: 100px;
}
.circle {
position: absolute;
height: 59px;
width: 59px;
border-radius:50%;
background-color: white;
border: solid 1px black;
}
</style>
</head>
<body>
<!-- button on run code -->
<p>
<button id="stepButton" onclick="stepCode()">Step Code</button>
</p>
<!-- blockly workspace -->
<div id="blocklyDiv" style="height:480px;width:600px;"></div>
<xml id="toolbox" style="display:none;">
<category name="logic">
<block type="stoplightswitch"></block>
<block type="math_number"></block>
</category>
</xml>
<!-- added to the page -->
<img id="light" src="stoplight.png" />
<div id="light0" class="circle" style="left:770px;top:132px;"></div>
<div id="light1" class="circle" style="left:770px;top:209px;"></div>
<div id="light2" class="circle" style="left:770px;top:287px;"></div>
<script>
//set options
var options = {
toolbox: toolbox,
collapse: true,
comments: true,
disable: true,
maxBlocks: Infinity,
trashcan: true,
horizontalLayout: false,
toolboxPosition: 'start',
css: true,
rtl: false,
scrollbars: true,
sounds: true,
oneBasedIndex: true,
grid: {
spacing: 20,
length: 1,
colour: '#888',
snap: true
}
}
//put the toolbox in the workspace
var workspace = Blockly.inject('blocklyDiv', options);
var stepButton = document.getElementById('stepButton');
var myInterpreter = null;
var highlightPause = false;
var code = '';
function highlightBlock(id) {
workspace.highlightBlock(id);
highlightPause = true;
}
function generateCodeAndLoadIntoInterpreter() {
// Generate JavaScript code and parse it.
Blockly.JavaScript.STATEMENT_PREFIX = 'highlightBlock(%1);\n';
Blockly.JavaScript.addReservedWords('highlightBlock');
code = Blockly.JavaScript.workspaceToCode(workspace);
}
// Load the interpreter now, and upon future changes.
generateCodeAndLoadIntoInterpreter();
workspace.addChangeListener(function(event) {
if (!(event instanceof Blockly.Events.Ui)) {
// Something changed. Parser needs to be reloaded.
generateCodeAndLoadIntoInterpreter();
}
});
function initApi(interpreter, scope) {
// Add an API function for highlighting blocks.
wrapper = function(id) {
id = id ? id.toString() : '';
return interpreter.createPrimitive(highlightBlock(id));
};
interpreter.setProperty(
scope,
'highlightBlock',
interpreter.createNativeFunction(wrapper)
);
// 信号機のライト点滅制御ためのAPI関数を追加
wrapper = function(lightNo, lightColor) {
lightNo = lightNo ? lightNo.toString() : '';
lightColor = lightColor ? lightColor.toString() : '';
return interpreter.createPrimitive(setLightColor(lightNo, lightColor));
};
interpreter.setProperty(
scope,
'setLightColor',
interpreter.createNativeFunction(wrapper)
)
}
// 信号機のライト点滅の制御関数
function setLightColor(lightNo, lightColor) {
document.getElementById("light" + lightNo).style.backgroundColor= lightColor ;
}
function stepCode() {
if (!myInterpreter) {
// First statement of this code.
myInterpreter = new Interpreter(code, initApi);
// do the first statement
setTimeout(function() {
highlightPause = true;
stepCode();
}, 1);
return;
}
highlightPause = false;
try {
var ok = myInterpreter.step();
var node = myInterpreter.stateStack[myInterpreter.stateStack.length - 1].node;
console.log(node);
} finally {
if (!ok) {
document.getElementById('stepButton').disabled = 'disabled';
}
}
}
</script>
</body>
</html>
【stoplightblocks.js】
//ブロック定義コード
//stoplight switch block - turn color on or off
Blockly.Blocks['stoplightswitch'] = {
init: function() {
this.appendValueInput("lightno")
.setCheck("Number")
.appendField("turn")
.appendField(new Blockly.FieldDropdown([["red","R"], ["green","G"], ["yellow","Y"]]), "colorlist")
.appendField(new Blockly.FieldDropdown([["on","T"], ["off","F"]]), "switch");
this.setInputsInline(true);
this.setPreviousStatement(true, "String");
this.setNextStatement(true, "String");
this.setColour(230);
this.setTooltip("信号機");
this.setHelpUrl("http://");
}
};
//ブロックのGenerator
Blockly.JavaScript['stoplightswitch'] = function(block) {
var dropdown_colorlist = block.getFieldValue('colorlist');
var dropdown_switch = block.getFieldValue('switch');
var value_lightno = Blockly.JavaScript.valueToCode(block, 'lightno', Blockly.JavaScript.ORDER_ATOMIC);
// TODO: Assemble JavaScript into code variable.
if (dropdown_colorlist === "R") {
var color = "red";
} else if (dropdown_colorlist === "G") {
var color = "green";
} else if (dropdown_colorlist === "Y") {
var color = "yellow";
}
if (dropdown_switch === "T") {
var code = "setLightColor('"+ value_lightno +"','"+ color +"');";
} else if (dropdown_switch === "F") {
var code = "setLightColor('"+ value_lightno +"','"+ color +"');";
}
return code;
};
実行結果
Step Codeボタンを連続に押下された場合、以下の順に信号機を点灯する同時に、対応するブロックにもハイライトに表示することが行われています。
【図一】赤ライトを点灯、1番目のブロックをハイライト表示
【図二】青ライトを点灯、2番目のブロックをハイライト表示
【図三】黄ライトを点灯、3番目のブロックをハイライト表示