Cómo generar y ejecutar JavaScript

Las aplicaciones de Blockly a menudo generan JavaScript como su lenguaje de salida, generalmente para ejecutarse dentro de una página web (posiblemente el mismo o una WebView incorporada). Como cualquier generador, el primer paso es incluir el generador de JavaScript.

import {javascriptGenerator} from 'blockly/javascript';

Para generar JavaScript desde el lugar de trabajo, llama a lo siguiente:

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, existen algunas mejoras. Una mejora es que la evaluación se une a una try/catch para que los errores en el tiempo de ejecución sean visibles, en lugar de fallar de forma silenciosa. Otra mejora es que se agregue code a la lista de palabras reservadas, de modo que, si el código del usuario contiene una variable con ese nombre, se le cambiará automáticamente el nombre en lugar de colisionar. Cualquier variable local debe reservarse de esta manera.

Bloques de resaltado

Destacar el bloque que se está ejecutando actualmente a medida que se ejecuta el código ayuda a los usuarios a comprender el comportamiento de su programa. El resaltado se puede realizar a nivel de declaración por declaración si se configura 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 lugar de trabajo.

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

Como resultado, se agrega la instrucción highlightBlock('123'); antes de cada sentencia, en la que 123 es el número de serie del bloque que se destacará.

Bucles infinitos

Si bien se garantiza que el código resultante sea sintácticamente correcto en todo momento, puede contener bucles infinitos. Como la resolución del problema de detención está más allá 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 lograrlo, configura javascriptGenerator.INFINITE_LOOP_TRAP con un fragmento de código que se insertará en cada bucle y función. Por ejemplo:

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

Ejemplo

Esta es una demostración en vivo de la generación y ejecución de JavaScript.

Intérprete de JS

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

  • Ejecuta códigos a cualquier velocidad.
  • Pausar, reanudar o avanzar la ejecución
  • Resalta los bloques a medida que se ejecutan.
  • Completamente aislado del JavaScript del navegador.

Ejecuta el intérprete

Primero, descarga JS-Interpreter desde GitHub:

Descargar archivo ZIP Descargar TAR Ball Ver en GitHub

Luego, agrégalo a tu página:

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

El método más simple 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();

Paso el intérprete

Para ejecutar el código más lento o de una manera más controlada, reemplaza la llamada a run por un bucle que siga (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, es una unidad semántica en JavaScript, que puede ser muy precisa.

Agregar una API

JS-Interpreter es una zona de pruebas que está completamente aislada del navegador. Cualquier bloque que realice acciones con el mundo exterior requiere que se agregue una API al intérprete. Para ver una descripción completa, consulta la documentación de JS-Interpreter. Pero, en primer lugar, esta es la API necesaria para admitir los bloques de alerta y prompt:

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 de bloques 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 recorre el programa. Para ello, crea una función 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 querer ejecutar pasos repetidamente sin pausa hasta que se alcanza un comando de resaltado y, luego, hacer una pausa. Esta estrategia simula la ejecución línea por línea. En el siguiente ejemplo, se usa este enfoque.

Ejemplo de JS-Interpreter

Esta es una demostración en vivo para interpretar JavaScript paso a paso. Y esta demostración incluye un bloque "esperar", un buen ejemplo para usar en otros comportamientos asíncronos (p.ej., voz o audio, entrada del usuario).