Genera y ejecuta JavaScript

Las aplicaciones de Blockly suelen generar JavaScript como lenguaje de salida, generalmente para ejecutarse en una página web (posiblemente la misma o una WebView incorporada). Al igual que con cualquier generador, el primer paso es incluir el generador de JavaScript.

import {javascriptGenerator} from 'blockly/javascript';

Para generar JavaScript desde el espacio de trabajo, llama a:

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

El código resultante se puede ejecutar directamente en la página web de destino:

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

Básicamente, el fragmento anterior solo genera el código y lo evalúa. Sin embargo, hay algunas mejoras. Un perfeccionamiento es que la evaluación se incluye en un try/catch para que se vean los errores de tiempo de ejecución, en lugar de fallar de forma silenciosa. Otra mejora es que se agregó code a la lista de palabras reservadas, de modo que, si el código del usuario contiene una variable con ese nombre, se cambiará automáticamente el nombre en lugar de generar un conflicto. Todas las variables locales deben reservarse de esta manera.

Bloques de Highlight

Destacar el bloque que se está ejecutando mientras se ejecuta el código ayuda a los usuarios a comprender el comportamiento de su programa. El resaltado se puede realizar a nivel de cada instrucción si se establece STATEMENT_PREFIX antes de generar el código JavaScript:

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

Define highlightBlock para marcar el bloque en el espacio de trabajo.

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

Esto hace que la instrucción highlightBlock('123'); se agregue antes de cada instrucción, donde 123 es el número de serie del bloque que se destacará.

Bucles infinitos

Si bien se garantiza que el código resultante es sintácticamente correcto en todo momento, puede contener bucles infinitos. Dado que resolver el problema de detención está fuera del alcance de Blockly (!), el mejor enfoque para abordar estos casos es mantener un contador y disminuirlo cada vez que se realiza una iteración. Para lograr esto, solo tienes que establecer javascriptGenerator.INFINITE_LOOP_TRAP en un fragmento de código que se insertará en cada bucle y en cada función. Aquí tienes un ejemplo:

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

Ejemplo

Aquí tienes una demostración en vivo de la generación y ejecución de JavaScript.

JS-Interpreter

Si te interesa ejecutar los bloques del usuario correctamente, el proyecto JS-Interpreter es la mejor opción. Este proyecto es independiente de Blockly, pero se escribió específicamente para Blockly.

  • Ejecuta código a cualquier velocidad.
  • Pausar, reanudar o ejecutar paso a paso.
  • Destaca los bloques a medida que se ejecutan.
  • Está completamente aislado del JavaScript del navegador.

Ejecuta el intérprete

Primero, descarga JS-Interpreter desde GitHub:

Luego, agrégalo a tu página:

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

El método más sencillo para llamarlo es generar el código JavaScript, crear el intérprete y ejecutar el código:

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

Cómo avanzar en el intérprete

Para ejecutar el código más lentamente o de una manera más controlada, reemplaza la llamada a run por un bucle que avance paso a paso (en este caso, un paso cada 10 ms):

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

Ten en cuenta que cada paso no es una línea ni un bloque, sino una unidad semántica en JavaScript, que puede ser extremadamente detallada.

Agrega una API

El JS-Interpreter es un entorno de pruebas aislado por completo del navegador. Cualquier bloque que realice acciones con el mundo exterior requiere que se agregue una API al intérprete. Para obtener una descripción completa, consulta la documentación de JS-Interpreter. Pero, para comenzar, aquí tienes la API necesaria para admitir los bloques de alerta y mensaje:

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

Luego, modifica la inicialización del intérprete para pasar la función initApi:

var myInterpreter = new Interpreter(code, initApi);

Los bloques de alerta y de instrucción son los únicos dos bloques del conjunto predeterminado que requieren una API personalizada para el intérprete.

Conectando highlightBlock()

Cuando se ejecuta en JS-Interpreter, highlightBlock() debe ejecutarse de inmediato, fuera de la zona de pruebas, a medida que el usuario avanza por el programa. Para ello, crea una función de wrapper highlightBlock() para capturar el argumento de la función y regístrala como una función nativa.

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

Las aplicaciones más sofisticadas pueden ejecutar pasos de forma repetida sin pausas hasta que se alcance un comando de resaltado y, luego, pausar. Esta estrategia simula la ejecución línea por línea. En el siguiente ejemplo, se usa este enfoque.

Ejemplo de JS-Interpreter

Aquí tienes una demostración en vivo de la interpretación de JavaScript paso a paso. Y esta demostración incluye un bloque de espera, un buen ejemplo para usar con otro comportamiento asíncrono (p.ej., voz o audio, entrada del usuario).