Generating and Running JavaScript

Blockly applications often generate JavaScript as their output language, generally to run within a web page (possibly the same, or a embedded WebView). Like any generator, the first step is to include the JavaScript generator.

import {javascriptGenerator} from 'blockly/javascript';

To generate JavaScript from the workspace, call:

javascriptGenerator.addReservedWords('code');
var code = javascriptGenerator.workspaceToCode(workspace);

The resulting code can be executed right in the destination web page:

try {
  eval(code);
} catch (e) {
  alert(e);
}

Basically, the above snippet just generates the code and evals it. However, there are a couple of refinements. One refinement is that the eval is wrapped in a try/catch so that any runtime errors are visible, instead of failing quietly. Another refinement is that code is added to the list of reserved words so that if the user's code contains a variable of that name it will be automatically renamed instead of colliding. Any local variables should be reserved in this way.

Highlight Blocks

Highlighting the currently executing block as the code runs helps users understand their program's behaviour. Highlighting may be done on a statement-by-statement level by setting STATEMENT_PREFIX prior to generating the JavaScript code:

javascriptGenerator.STATEMENT_PREFIX = 'highlightBlock(%1);\n';
javascriptGenerator.addReservedWords('highlightBlock');

Define highlightBlock to mark the block on the workspace.

function highlightBlock(id) {
  workspace.highlightBlock(id);
}

This results in the statement highlightBlock('123'); being added to before every statement, where 123 is the serial number of the block to be highlighted.

Infinite Loops

Although the resulting code is guaranteed to be syntactically correct at all times, it may contain infinite loops. Since solving the Halting problem is beyond Blockly's scope (!) the best approach for dealing with these cases is to maintain a counter and decrement it every time an iteration is performed. To accomplish this, just set javascriptGenerator.INFINITE_LOOP_TRAP to a code snippet which will be inserted into every loop and every function. Here is an example:

window.LoopTrap = 1000;
javascriptGenerator.INFINITE_LOOP_TRAP = 'if(--window.LoopTrap == 0) throw "Infinite loop.";\n';
var code = javascriptGenerator.workspaceToCode(workspace);

Example

Here is a live demo of generating and executing JavaScript.

JS-Interpreter

If you are serious about running the user's blocks properly, then the JS-Interpreter project is the way to go. This project is separate from Blockly, but was specifically written for Blockly.

  • Execute code at any speed.
  • Pause/resume/step-through execution.
  • Highlight blocks as they execute.
  • Completely isolated from browser's JavaScript.

Run the Interpreter

First, download the JS-Interpreter from GitHub:

Download ZIP File Download TAR Ball View On GitHub

Then add it to your page:

<script src="acorn_interpreter.js"></script>

The simplest method of calling it is to generate the JavaScript, create the interpreter, and run the code:

var code = javascriptGenerator.workspaceToCode(workspace);
var myInterpreter = new Interpreter(code);
myInterpreter.run();

Step the Interpreter

In order to execute the code slower, or in a more controlled manner, replace the call to run with a loop that steps (in this case one step every 10ms):

function nextStep() {
  if (myInterpreter.step()) {
    setTimeout(nextStep, 10);
  }
}
nextStep();

Note that each step is not a line or a block, it is a semantic unit in JavaScript, which may be extremely fine-grained.

Add an API

The JS-Interpreter is a sandbox that is completely isolated from the browser. Any blocks that perform actions with the outside world require an API added to the interpreter. For a full description, see the JS-Interpreter documentation. But to start with, here is the API needed to support the alert and prompt blocks:

function initApi(interpreter, globalObject) {
  // Add an API function for the alert() block.
  var wrapper = function(text) {
    return alert(arguments.length ? text : '');
  };
  interpreter.setProperty(globalObject, 'alert',
      interpreter.createNativeFunction(wrapper));

  // Add an API function for the prompt() block.
  wrapper = function(text) {
    return prompt(text);
  };
  interpreter.setProperty(globalObject, 'prompt',
      interpreter.createNativeFunction(wrapper));
}

Then modify your interpreter initialization to pass in the initApi function:

var myInterpreter = new Interpreter(code, initApi);

The alert and prompt blocks are the only two blocks in the default set of blocks that require a custom API for the interpreter.

Connecting highlightBlock()

When running in JS-Interpreter, highlightBlock() should be executed immediately, outside the sandbox, as the user steps through the program. To do this, create a wrapper function highlightBlock() to capture the function argument, and register it as a native function.

function initApi(interpreter, globalObject) {
  // Add an API function for highlighting blocks.
  var wrapper = function(id) {
    return workspace.highlightBlock(id);
  };
  interpreter.setProperty(globalObject, 'highlightBlock',
      interpreter.createNativeFunction(wrapper));
}

More sophisticated applications might wish to repeatedly execute steps without pause until a highlight command is reached, then pause. This strategy simulates line-by-line execution. The example below uses this approach.

JS-Interpreter Example

Here is a live demo of interpreting JavaScript step by step. And this demo includes a wait block, a good example to use for other asynchronous behavior (e.g., speech or audio, user input).