Generare ed eseguire JavaScript

Le applicazioni Blockly generano spesso JavaScript come linguaggio di output, generalmente per l'esecuzione all'interno di una pagina web (possibilmente la stessa o un WebView incorporato). Come per qualsiasi generatore, il primo passaggio consiste nell'includere il generatore JavaScript.

import {javascriptGenerator} from 'blockly/javascript';

Per generare JavaScript dallo spazio di lavoro, chiama:

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

Il codice risultante può essere eseguito direttamente nella pagina web di destinazione:

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

In sostanza, lo snippet riportato sopra genera e valuta il codice. Tuttavia, esistono alcuni perfezionamenti. Un perfezionamento è che l'espressione eval è racchiusa in un try/catch in modo che eventuali errori di runtime siano visibili, anziché verificarsi in silenzio. Un altro perfezionamento è che code viene aggiunto all'elenco di parole riservate in modo che, se il codice dell'utente contiene una variabile con questo nome, verrà rinominata automaticamente anziché verificarsi una collisione. Eventuali variabili locali devono essere riservate in questo modo.

Blocchi in evidenza

L'evidenziazione del blocco in esecuzione durante l'esecuzione del codice aiuta gli utenti a comprendere il comportamento del programma. L'evidenziazione può essere eseguita su un livello di dichiarazione impostando STATEMENT_PREFIX prima di generare il codice JavaScript:

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

Definisci highlightBlock per contrassegnare il blocco nello spazio di lavoro.

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

Di conseguenza, l'istruzione highlightBlock('123'); viene aggiunta prima di ogni istruzione, dove 123 è il numero di serie del blocco da evidenziare.

Loop infiniti

Sebbene il codice risultante sia garantito come sintatticamente corretto in qualsiasi momento, potrebbe contenere loop infiniti. Poiché la risoluzione del problema di arresto esula dall'ambito di Blockly (!), l'approccio migliore per gestire questi casi è mantenere un contatore e diminuirlo ogni volta che viene eseguita un'iterazione. Per farlo, imposta javascriptGenerator.INFINITE_LOOP_TRAP su uno snippet di codice che verrà inserito in ogni loop e in ogni funzione. Ecco un esempio:

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

Esempio

Ecco una demo dal vivo di generazione ed esecuzione di JavaScript.

JS-Interpreter

Se vuoi eseguire correttamente i blocchi dell'utente, il progetto JS-Interpreter è la soluzione che fa per te. Questo progetto è separato da Blockly, ma è stato scritto appositamente per Blockly.

  • Esegui il codice a qualsiasi velocità.
  • Mettere in pausa/riprendere/eseguire l'esecuzione passo passo.
  • Evidenzia i blocchi durante l'esecuzione.
  • Completamente isolate dal codice JavaScript del browser.

Esegui l'interprete

Innanzitutto, scarica JS-Interpreter da GitHub:

Scarica file ZIP Scarica TAR Ball Visualizza su GitHub

Quindi aggiungila alla pagina:

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

Il metodo più semplice per richiamarlo è generare il codice JavaScript, creare l'interprete ed eseguire il codice:

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

Eseguire il passaggio dell'interprete

Per eseguire il codice più lentamente o in modo più controllato, sostituisci la chiamata a run con un ciclo che esegue dei passaggi (in questo caso un passaggio ogni 10 ms):

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

Tieni presente che ogni passaggio non è una riga o un blocco, ma un'unità semantica in JavaScript, che può essere estremamente granulare.

Aggiungi un'API

L'interprete JS è una sandbox completamente isolata dal browser. Tutti i blocchi che eseguono azioni con il mondo esterno richiedono un'API aggiunta all'interprete. Per una descrizione completa, consulta la documentazione di JS-Interpreter. Per iniziare, ecco l'API necessaria per supportare i blocchi di avviso e richiesta:

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

Poi modifica l'inizializzazione dell'interprete in modo da passare la funzione initApi:

var myInterpreter = new Interpreter(code, initApi);

I blocchi di avviso e prompt sono gli unici due blocchi nell'insieme di blocchi predefinito che richiedono un'API personalizzata per l'interprete.

Collegamento di highlightBlock()

Quando viene eseguito in JS-Interpreter, highlightBlock() deve essere eseguito immediatamente, al di fuori della sandbox, man mano che l'utente esegue il programma. Per farlo, crea una funzione wrapper highlightBlock() per acquisire l'argomento della funzione e registrarla come funzione 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));
}

Le applicazioni più sofisticate potrebbero voler eseguire ripetutamente i passaggi senza interruzione fino al raggiungimento di un comando di evidenziazione, quindi mettere in pausa. Questa strategia simula l'esecuzione riga per riga. L'esempio seguente utilizza questo approccio.

Esempio di JS-Interpreter

Ecco una demo dal vivo dell'interpretazione di JavaScript passo passo. Inoltre, questa demo include un blocco di attesa, un buon esempio da utilizzare per altri comportamenti asincroni (ad es. parlato o audio, input utente).