Generowanie i uruchamianie JavaScriptu

Aplikacje blokujące często generują kod JavaScript jako język wyjściowy, które zwykle działają na stronie internetowej (prawdopodobnie taka sama lub w osadzonym komponencie WebView). Podobnie jak w przypadku każdego generatora, najpierw musisz dodać generator JavaScript.

import {javascriptGenerator} from 'blockly/javascript';

Aby wygenerować kod JavaScript z obszaru roboczego, wywołaj:

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

Powstały kod można wykonać bezpośrednio na docelowej stronie internetowej:

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

Powyższy fragment kodu tylko generuje i ocenia kod. Jest jednak kilka udoskonaleń. Jednym z udoskonaleń jest zawarcie w ocenie wartości try/catch, dzięki czemu będą widoczne wszystkie błędy środowiska wykonawczego, a nie błąd powodujący. Innym ulepszeniem jest dodanie słowa code do listy zarezerwowanych słów, dzięki czemu, jeśli kod użytkownika zawiera zmienną z tą nazwą, zmienia się ona automatycznie, a nie koliduje. Wszystkie zmienne lokalne należy zachować w ten sposób.

Podświetlanie bloków

Wyróżnienie aktualnie wykonywanego bloku podczas uruchamiania kodu pomaga użytkownikom zrozumieć działanie programu. Wyróżnienie można wyróżnić na poziomie poszczególnych instrukcji, ustawiając STATEMENT_PREFIX przed wygenerowaniem kodu JavaScript:

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

Określ highlightBlock, aby oznaczyć bryłę w obszarze roboczym.

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

Spowoduje to dodanie instrukcji highlightBlock('123'); przed każdym wyrażeniem, gdzie 123 to numer seryjny bloku, który ma zostać wyróżniony.

Nieskończone pętle

Mimo że powstały kod zawsze ma poprawną składnię, może zawierać zapętlenie. Rozwiązanie problemu Halting wykracza poza zakres działania Blockly (!), dlatego najlepszym sposobem radzenia sobie z takimi przypadkami jest utrzymywanie licznika i zmniejszanie go przy każdym wykonaniu iteracji. Aby to zrobić, ustaw w polu javascriptGenerator.INFINITE_LOOP_TRAP fragment kodu, który będzie wstawiany w każdą pętlę i do każdej funkcji. Oto przykład:

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

Przykład

Oto prezentacja generowania i wykonywania kodu JavaScript na żywo.

Tłumacz języka JS

Jeśli zależy Ci na prawidłowym uruchamianiu blokad użytkowników, zalecamy skorzystanie z projektu JS-Interpreter. Ten projekt jest czymś innym niż Blockly, ale został napisany specjalnie dla Blockly.

  • Uruchamiaj kod, kiedy chcesz.
  • Wstrzymywanie/wznawianie/wykonywanie krok po kroku.
  • Podświetlaj bloki w trakcie wykonywania.
  • Całkowicie odizolowane od kodu JavaScript przeglądarki.

Uruchamianie tłumaczenia rozmowy

Najpierw pobierz z GitHuba narzędzie JS-Interpreter:

Pobierz plik ZIP Pobierz TAR Ball Wyświetl w GitHubie

Następnie dodaj go do strony:

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

Najprostszą metodą jej wywołania jest wygenerowanie kodu JavaScript, utworzenie interpretatora i uruchomienie kodu:

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

Tłumacz na bieżąco

Aby wykonywać kod wolniej lub w bardziej kontrolowany sposób, zastąp wywołanie run pętlą, która wykonuje te kroki (w tym przypadku co 10 ms):

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

Pamiętaj, że każdy krok nie jest linią ani blokiem, a jednostką semantyczną w języku JavaScript, która może być bardzo szczegółowa.

Dodaj interfejs API

Język JS-Interpreter to piaskownica, która jest całkowicie odizolowana od przeglądarki. Wszystkie bloki, które wykonują działania w obrębie świata zewnętrznego, wymagają interfejsu API dodanego do interpretera. Pełny opis znajdziesz w dokumentacji mechanizmu JS-Interpreter. Na początek oto interfejs API potrzebny do obsługi bloków alertów i próśb:

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

Następnie zmodyfikuj inicjowanie interpretera tak, aby przekazywało się do funkcji initApi:

var myInterpreter = new Interpreter(code, initApi);

Bloki alertów i promptów to jedyne 2 bloki w domyślnym zestawie bloków, które wymagają niestandardowego interfejsu API dla interpretera.

Łączę z dostawcą highlightBlock()

W przypadku uruchomienia w narzędziu JS-interpreter polecenie highlightBlock() powinno zostać wykonane natychmiast, poza piaskownicą, w miarę jak użytkownik przechodzi przez program. W tym celu utwórz funkcję otoczenia highlightBlock(), aby przechwycić argument funkcji, i zarejestruj ją jako funkcję natywną.

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

Bardziej zaawansowane aplikacje mogą chcieć wielokrotnie wykonać kroki bez wstrzymania do momentu osiągnięcia polecenia podświetlania, a następnie wstrzymać działanie. Symuluje ona wykonywanie kodu w poszczególnych wierszach. Podejście to zastosowano w przykładzie poniżej.

Przykład mechanizmu JS-interpreter

Oto prezentacja na żywo, która pokazuje, jak krok po kroku interpretować kod JavaScript. Ta prezentacja zawiera blok oczekiwania, który jest dobrym przykładem na potrzeby innych działań asynchronicznych (np. mowy lub dźwięku, wprowadzania danych przez użytkownika).