Генерация и запуск 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-Interpreter с 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-Interpreter — это «песочница», полностью изолированная от браузера. Любые блоки, взаимодействующие с внешним миром, требуют добавления 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. В этой демонстрации есть блок ожидания — хороший пример для использования в других асинхронных операциях (например, в обработке речи, звука или пользовательского ввода).