Генерация и запуск JavaScript

Блочные приложения часто генерируют JavaScript в качестве языка вывода, обычно для запуска на веб-странице (возможно, той же самой или встроенном WebView). Как и в случае с любым генератором, первым шагом является подключение генератора JavaScript.

import {javascriptGenerator} from 'blockly/javascript';

Чтобы сгенерировать JavaScript из рабочей области, вызовите:

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

Полученный код можно выполнить прямо на целевой веб-странице:

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

По сути, приведенный выше фрагмент просто генерирует код и оценивает его. Однако есть пара уточнений. Одно из усовершенствований заключается в том, что eval заключен в try / catch , чтобы все ошибки во время выполнения были видны, а не тихо терпели неудачу. Еще одно усовершенствование заключается в том, что code добавляется в список зарезервированных слов, поэтому, если код пользователя содержит переменную с таким именем, она автоматически переименовывается, а не конфликтует. Любые локальные переменные должны быть зарезервированы таким образом.

Выделить блоки

Выделение текущего исполняемого блока во время выполнения кода помогает пользователям понять поведение своей программы. Выделение может выполняться на уровне каждого оператора, установив STATEMENT_PREFIX перед генерацией кода JavaScript:

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

Определите highlightBlock , чтобы отметить блок в рабочей области.

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

В результате получается выражение highlightBlock('123'); добавляется перед каждым оператором, где 123 — это порядковый номер выделяемого блока.

Бесконечные циклы

Хотя результирующий код гарантированно всегда будет синтаксически правильным, он может содержать бесконечные циклы. Поскольку решение проблемы остановки находится за пределами возможностей Blockly (!), лучший подход к решению таких случаев — поддерживать счетчик и уменьшать его каждый раз при выполнении итерации. Для этого просто установите javascriptGenerator.INFINITE_LOOP_TRAP в фрагмент кода, который будет вставлен в каждый цикл и каждую функцию. Вот пример:

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

Пример

Вот живая демонстрация создания и выполнения JavaScript.

JS-интерпретатор

Если вы серьезно относитесь к правильной работе пользовательских блоков, то вам подойдет проект JS-Interpreter . Этот проект отделен от Blockly, но был написан специально для Blockly.

  • Выполняйте код на любой скорости.
  • Пауза/возобновление/пошаговое выполнение.
  • Выделяйте блоки по мере их выполнения.
  • Полностью изолирован от JavaScript браузера.

Запустите интерпретатор

Сначала загрузите JS-интерпретатор с GitHub:

Download ZIP FileDownload TAR BallView On GitHub

Затем добавьте его на свою страницу:

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

Самый простой способ его вызова — сгенерировать JavaScript, создать интерпретатор и запустить код:

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

Шаг переводчика

Чтобы выполнять код медленнее или более контролируемым образом, замените вызов run пошаговым циклом (в данном случае один шаг каждые 10 мс):

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

Обратите внимание, что каждый шаг — это не строка или блок, а семантическая единица JavaScript, которая может быть очень детальной.

Добавить API

JS-Интерпретатор — это песочница, полностью изолированная от браузера. Любые блоки, выполняющие действия с внешним миром, требуют добавления API в интерпретатор. Полное описание смотрите в документации JS-Interpreter . Но для начала вот API, необходимый для поддержки блоков предупреждений и подсказок:

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));
}

Затем измените инициализацию интерпретатора, чтобы передать функцию initApi:

var myInterpreter = new Interpreter(code, initApi);

Блоки предупреждений и подсказок — единственные два блока в наборе блоков по умолчанию, которым требуется специальный API для интерпретатора.

Подключение highlightBlock()

При работе в JS-Interpreter highlightBlock() должна выполняться немедленно, за пределами песочницы, по мере того, как пользователь выполняет программу. Чтобы сделать это, создайте функцию-оболочку highlightBlock() для захвата аргумента функции и зарегистрируйте ее как встроенную функцию.

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));
}

Более сложные приложения могут захотеть многократно выполнять шаги без паузы, пока не будет достигнута команда выделения, а затем сделать паузу. Эта стратегия имитирует построчное выполнение. В приведенном ниже примере используется этот подход.

Пример JS-интерпретатора

Вот живая демонстрация пошаговой интерпретации JavaScript. И эта демонстрация включает в себя блок ожидания, хороший пример для использования в других асинхронных действиях (например, речь или звук, пользовательский ввод).