Generatory kodu blokowego

Generator kodu bloku to funkcja, która generuje kod bloku i zwraca go jako ciąg znaków. Kod generowany przez blok zależy od jego typu:

  • Bloki wartości mają połączenie wyjściowe. Działają one jak wyrażenia w języku tekstowym i generują ciągi znaków zawierające wyrażenia.
  • Bloki instrukcji to bloki bez połączenia wyjściowego. Działają one jak instrukcje w języku tekstowym i generują ciągi znaków zawierające instrukcje.

Jak napisać generator kodu bloku

W przypadku każdego utworzonego bloku niestandardowego musisz napisać generator kodu bloku dla każdego języka, który chcesz obsługiwać. Pamiętaj, że wszystkie generatory kodu blokowego są napisane w JavaScript, nawet jeśli generują kod w innym języku.

Wszystkie generatory kodu bloku wykonują te czynności:

  1. Importują generator kodu języka.
  2. Pobierają wartość każdego pola i przekształcają ją w ciąg kodu.
  3. Pobierają ciągi kodu wygenerowane przez bloki wewnętrzne, czyli bloki dołączone do danych wejściowych wartości i instrukcji.
  4. Tworzą i zwracają ciąg kodu bloku.

Przykładowe bloki

Jako przykłady napiszemy generatory kodu JavaScript dla tych bloków.

  • custom_compare to blok wartości, który ma dane wejściowe wartości o nazwie LEFT, pole listy rozwijanej o nazwie OPERATOR i pole numeryczne o nazwie RIGHT. Generuje ciąg wyrażenia w postaci '0 = 0'.

    Blok wartości niestandardowej do porównań.

  • custom_if to blok instrukcji, który ma pole wyboru o nazwie NOT, dane wejściowe wartości o nazwie CONDITION i dane wejściowe instrukcji o nazwie THEN. Generuje ciąg instrukcji w postaci 'if (...) {\n...\n};\n'.

    Niestandardowy blok instrukcji dla instrukcji warunkowej. Użytkownicy mogą użyć pola wyboru, aby zanegować warunek if.

W tym dokumencie generatory są tworzone krok po kroku. Gotowe generatory znajdziesz na końcu tego dokumentu.

Pamiętaj, że te bloki służą tylko do ilustrowania generowania kodu. W prawdziwej aplikacji używaj wbudowanych bloków logic_compare i controls_if.

Importowanie generatora kodu języka

Generator kodu języka możesz zaimportować na jeden z tych sposobów. Użyj zaimportowanego generatora, aby przechowywać generatory kodu bloku w obiekcie forBlock.

Moduły

import {javascriptGenerator} from 'blockly/javascript';
import {pythonGenerator} from 'blockly/python';
import {phpGenerator} from 'blockly/php';
import {luaGenerator} from 'blockly/lua';
import {dartGenerator} from 'blockly/dart';

// Add block-code generators for the custom_if block.
javascriptGenerator.forBlock['custom_if'] = function (block, generator) { /* ... */ };
pythonGenerator.forBlock['custom_if'] = function (block, generator) { /* ... */ };
phpGenerator.forBlock['custom_if'] = function (block, generator) { /* ... */ };
luaGenerator.forBlock['custom_if'] = function (block, generator) { /* ... */ };
dartGenerator.forBlock['custom_if'] = function (block, generator) { /* ... */ };

Unpkg

Generator musisz dołączyć po dołączeniu Blockly.

<script src="https://unpkg.com/blockly"></script>
<script src="https://unpkg.com/blockly/javascript_compressed"></script>
<script src="https://unpkg.com/blockly/python_compressed"></script>
<script src="https://unpkg.com/blockly/php_compressed"></script>
<script src="https://unpkg.com/blockly/lua_compressed"></script>
<script src="https://unpkg.com/blockly/dart_compressed"></script>
// Add block-code generators for the custom_if block.
javascript.javascriptGenerator.forBlock['custom_if'] = function (block, generator) { /* ... */ };
python.pythonGenerator.forBlock['custom_if'] = function (block, generator) { /* ... */ };
php.phpGenerator.forBlock['custom_if'] = function (block, generator) { /* ... */ };
lua.luaGenerator.forBlock['custom_if'] = function (block, generator) { /* ... */ };
dart.dartGenerator.forBlock['custom_if'] = function (block, generator) { /* ... */ };

Skrypty lokalne

Generator musisz dołączyć po dołączeniu Blockly.

<script src="blockly_compressed.js"></script>
<script src="javascript_compressed.js"></script>
<script src="python_compressed.js"></script>
<script src="php_compressed.js"></script>
<script src="lua_compressed.js"></script>
<script src="dart_compressed.js"></script>
// Add block-code generators for the custom_if block.
javascript.javascriptGenerator.forBlock['custom_if'] = function (block, generator) { /* ... */ };
python.pythonGenerator.forBlock['custom_if'] = function (block, generator) { /* ... */ };
php.phpGenerator.forBlock['custom_if'] = function (block, generator) { /* ... */ };
lua.luaGenerator.forBlock['custom_if'] = function (block, generator) { /* ... */ };
dart.dartGenerator.forBlock['custom_if'] = function (block, generator) { /* ... */ };

Pobieranie wartości pól

Pola umożliwiają użytkownikom wprowadzanie wartości takich jak ciągi znaków, liczby i kolory. Aby pobrać wartość pola, wywołaj funkcję getFieldValue. Zwracana wartość różni się w zależności od pola. Na przykład pola tekstowe zwracają dokładny tekst wpisany przez użytkownika, ale pola listy rozwijanej zwracają ciąg znaków niezależny od języka powiązany z elementem wybranym przez użytkownika. Więcej informacji znajdziesz w dokumentacji wbudowanych pól.

W zależności od pola może być konieczne przekształcenie zwróconej wartości przed użyciem jej w kodzie.

custom_compare

javascriptGenerator.forBlock['custom_compare'] = function (block, generator) {
  // Use the value of the OPERATOR dropdown to look up the actual operator.
  const OPERATORS = {
    EQUALS: '==',
    LESS: '<',
    GREATER: '>',
  };
  const operator = OPERATORS[block.getFieldValue('OPERATOR')];
  // The value of the RIGHT field is a number and can be used directly when
  // building the block's code string.
  const right = block.getFieldValue('RIGHT');
  ...
}

custom_if

javascriptGenerator.forBlock['custom_if'] = function (block, generator) {
  // Use the value of the NOT field to get the negation operator (if any).
  const checkbox = block.getFieldValue('NOT');
  const negate = checkbox === 'TRUE' ? '!' : '';
  ...
}

Więcej informacji znajdziesz w artykule Przekształcanie wartości pól.

Pobieranie kodu z bloków wewnętrznych

Bloki wewnętrzne to bloki dołączone do danych wejściowych wartości i instrukcji bloku. Na przykład blok custom_if ma wewnętrzny blok wartości dla warunku if oraz wewnętrzne bloki instrukcji dla kodu, który jest wykonywany, jeśli warunek jest prawdziwy.

W przeciwieństwie do wartości pól kod pobierany z bloków wewnętrznych jest gotowy do użycia i nie wymaga przekształcenia.

Wewnętrzne bloki wartości

Aby pobrać kod z bloku wewnętrznego dołączonego do danych wejściowych wartości, wywołaj funkcję valueToCode. Ta metoda wywołuje generator kodu bloku wewnętrznego.

custom_compare

import {javascriptGenerator, Order} from 'blockly/javascript';

javascriptGenerator.forBlock['custom_compare'] = function (block, generator) {
  ...
  const order = operator === '==' ? Order.EQUALITY : Order.RELATIONAL;
  const left = generator.valueToCode(block, 'LEFT', order);
  ...
}

custom_if

import {javascriptGenerator, Order} from 'blockly/javascript';

javascriptGenerator.forBlock['custom_if'] = function (block, generator) {
  ...
  const order = checkbox === 'TRUE' ? Order.LOGICAL_NOT : Order.NONE;
  const condition = generator.valueToCode(block, 'CONDITION', order) || 'false';
  ...
}

Gdy wywołujesz funkcję valueToCode, musisz poinformować ją o najsilniejszym operatorze w kodzie, który będzie stosowany do kodu bloku wewnętrznego. Dzięki temu funkcja valueToCode może określić, czy kod bloku wewnętrznego należy umieścić w nawiasach.

Na przykład zaznaczenie pola wyboru NOT w bloku custom_if powoduje zastosowanie do warunku operatora logicznego NOT (!). W takim przypadku przekazujesz do funkcji valueToCode pierwszeństwo operatora NOT (Order.LOGICAL_NOT), a funkcja valueToCode porównuje je z pierwszeństwem najsłabszego operatora w bloku wewnętrznym. Następnie w razie potrzeby umieszcza kod bloku wewnętrznego w nawiasach:

  • Jeśli CONDITION jest blokiem zmiennej, funkcja valueToCode nie dodaje nawiasów, ponieważ operator NOT można zastosować bezpośrednio do zmiennej (!myBoolean).
  • Jeśli CONDITION jest blokiem porównania, valueToCode dodaje nawiasy, aby operator NOT był stosowany do całego porównania (!(a < b)) zamiast do wartości po lewej stronie (!a < b).

Nie musisz wiedzieć, czy funkcja valueToCode dodała nawiasy. Wystarczy, że przekażesz pierwszeństwo do funkcji valueToCode i dodasz zwrócony kod do ciągu kodu. Więcej informacji znajdziesz w artykule valueToCode precedence.

Wewnętrzne bloki instrukcji

Aby pobrać kod z bloku wewnętrznego dołączonego do danych wejściowych instrukcji, wywołaj funkcję statementToCode. Ta metoda wywołuje generator kodu bloku wewnętrznego i obsługuje wcięcia w kodzie.

custom_compare

Blok custom_compare nie ma danych wejściowych instrukcji.

custom_if

javascriptGenerator.forBlock['custom_if'] = function (block, generator) {
  ...
  const statements = generator.statementToCode(block, 'THEN');
  ...
}

Funkcję statementToCode musisz wywołać tylko w przypadku bloku wewnętrznego bezpośrednio połączonego z danymi wejściowymi instrukcji. Funkcja statementToCode obsługuje wszystkie dodatkowe bloki dołączone do pierwszego bloku.

Tworzenie i zwracanie ciągu kodu

Po pobraniu kodu dla pól i bloków wewnętrznych utwórz i zwróć ciąg kodu dla swojego bloku. To, co zwracasz, zależy od typu bloku:

  • Bloki wartości: zwracają tablicę zawierającą ciąg kodu i pierwszeństwo najsłabszego operatora w kodzie. Funkcja valueToCode używa tego do określenia, czy kod należy umieścić w nawiasach, gdy blok jest używany jako blok wewnętrzny. Więcej informacji znajdziesz w artykule Return precedence.

  • Bloki instrukcji: zwracają ciąg kodu.

custom_compare

import {javascriptGenerator, Order} from 'blockly/javascript';

javascriptGenerator.forBlock['custom_compare'] = function (block, generator) {
  ...
  const order = operator === '==' ? Order.EQUALITY : Order.RELATIONAL;
  ...
  const code = left + ' ' + operator + ' ' + right;
  return [code, order];
}

custom_if

javascriptGenerator.forBlock['custom_if'] = function (block, generator) {
  ...
  const code = 'if (' + negate + condition + ') {\n' + statements + '}\n';
  return code;
}

Jeśli w ciągu kodu używasz kodu wewnętrznego bloku wartości wiele razy, warto zapisać w pamięci podręcznej kod z tego bloku aby uniknąć subtelnych błędów i niepożądanych efektów ubocznych.

Gotowe generatory kodu

Dla porównania podajemy gotowe generatory kodu dla każdego bloku:

custom_compare

import {javascriptGenerator, Order} from 'blockly/javascript';

javascriptGenerator.forBlock['custom_compare'] = function (block, generator) {
  const OPERATORS = {
    EQUALS: '==',
    LESS: '<',
    GREATER: '>',
  };
  const operator = OPERATORS[block.getFieldValue('OPERATOR')];
  const order = operator === '==' ? Order.EQUALITY : Order.RELATIONAL;
  const left = generator.valueToCode(block, 'LEFT', order);
  const right = block.getFieldValue('RIGHT');
  const code = left + ' ' + operator + ' ' + right;
  return [code, order];
}

custom_if

import {javascriptGenerator, Order} from 'blockly/javascript';

javascriptGenerator.forBlock['custom_if'] = function (block, generator) {
  const checkbox = block.getFieldValue('NOT');
  const negate = checkbox === 'TRUE' ? '!' : '';
  const order = checkbox === 'TRUE' ? Order.LOGICAL_NOT : Order.NONE;
  const condition = generator.valueToCode(block, 'CONDITION', order) || 'false';
  const statements = generator.statementToCode(block, 'THEN');
  const code = 'if (' + negate + condition + ') {\n' + statements + '}\n';
  return code;
}