Generatori di codici a blocchi

Un generatore di codice di blocco è una funzione che genera il codice per un blocco e lo restituisce come stringa. Il codice generato da un blocco dipende dal suo tipo:

  • I blocchi di valori hanno una connessione di output. Questi blocchi si comportano come espressioni in un linguaggio basato su testo e generano stringhe contenenti espressioni.
  • I blocchi di istruzioni sono blocchi senza una connessione di output. Questi blocchi agiscono come istruzioni in un linguaggio basato su testo e generano stringhe contenenti istruzioni.

Come scrivere un generatore di codice a blocchi

Per ogni blocco personalizzato creato, devi scrivere un generatore di codice blocco per ogni lingua che vuoi supportare. Tieni presente che tutti i generatori di codice di blocco sono scritti in JavaScript, anche se generano codice in un altro linguaggio.

Tutti i generatori di codice blocchi eseguono i seguenti passaggi:

  1. Importa un generatore di codici di lingua.
  2. Recupera il valore di ogni campo e trasformalo in una stringa di codice.
  3. Ottieni le stringhe di codice generate dai blocchi interni, ovvero blocchi collegati agli input di valore e di istruzioni.
  4. Crea e restituisce la stringa di codice del blocco.

Blocchi di esempio

Come esempi, scriveremo generatori di codice JavaScript per i seguenti blocchi.

  • custom_compare è un blocco di valori con un input di valore denominato LEFT, un campo a discesa denominato OPERATOR e un campo numerico denominato RIGHT. Genera una stringa di espressione del tipo '0 = 0'.

    Blocco del valore personalizzato per i confronti.

  • custom_if è un blocco di istruzioni con un campo di casella di controllo denominato NOT, un input di valore denominato CONDITION e un input di istruzioni denominato THEN. Genera una stringa di istruzioni del tipo 'if (...) {\n...\n};\n'.

    Blocco di istruzioni personalizzato per un'istruzione if. Gli utenti possono utilizzare una casella di controllo per negare la condizione if.

Questo documento illustra la creazione dei generatori passo passo. Puoi trovare i generatori completati alla fine di questo documento.

Tieni presente che questi blocchi hanno lo scopo di illustrare la generazione di codice. In un'applicazione reale, utilizza i blocchi logic_compare e controls_if integrati.

Importa un generatore di codici di lingua

Puoi importare un generatore di codici lingua utilizzando uno dei seguenti metodi. Utilizza il generatore importato per archiviare i generatori di codice a blocchi nell'oggetto forBlock.

Moduli

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

Devi includere il generatore dopo aver incluso 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) { /* ... */ };

Script locali

Devi includere il generatore dopo aver incluso 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) { /* ... */ };

Ottenere i valori dei campi

I campi consentono agli utenti di inserire valori come stringhe, numeri e colori. Per ottenere il valore di un campo, chiama getFieldValue. I valori restituiti sono diversi da campo a campo. Ad esempio, i campi di testo restituiscono il testo esatto inserito dall'utente, ma i campi di menu a discesa restituiscono una stringa neutra per la lingua associata all'elemento selezionato dall'utente. Per saperne di più, consulta la documentazione relativa ai campi integrati.

A seconda del campo, potrebbe essere necessario trasformare il valore restituito prima di usarlo nel codice.

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' ? '!' : '';
  ...
}

Per ulteriori informazioni, consulta Trasformare i valori del campo.

Recuperare il codice dai blocchi interni

I blocchi interni sono i blocchi collegati ai valori e agli input delle istruzioni di un blocco. Ad esempio, il blocco custom_if ha un blocco interno di valore per la condizione if e blocchi di istruzioni interni per il codice eseguito se la condizione è vera.

A differenza dei valori di campo, il codice che ottieni dai blocchi interni è pronto per l'uso e non deve essere trasformato.

Blocchi di valori interni

Per ottenere il codice da un blocco interno associato a un input di valore, chiama valueToCode. Questo metodo chiama il generatore di codice del blocco interno.

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';
  ...
}

Quando chiami valueToCode, devi indicare l'operatore più forte nel codice che verrà applicato al codice del blocco interno. In questo modo, valueToCode può determinare se è necessario racchiudere il codice del blocco interno tra parentesi.

Ad esempio, la selezione della casella NOT in custom_if applica un operatore di negazione logica (!) alla condizione. In questo caso, passi la precedenza dell'operatore NOT (Order.LOGICAL_NOT) a valueToCode e valueToCode la confronta con la precedenza dell'operatore più debole nel blocco interno. Quindi inserisce un a capo nel codice del blocco interno in base alle esigenze:

  • Se CONDITION è un blocco di variabili, valueToCode non aggiunge le parentesi perché l'operatore not può essere applicato direttamente a una variabile (!myBoolean).
  • Se CONDITION è un blocco di confronto, valueToCode aggiunge le parentesi in modo che l'operatore di negazione si applichi all'intero confronto (!(a < b)) anziché al valore a sinistra (!a < b).

Non è necessario sapere se valueToCode ha aggiunto le parentesi. Devi solo passare la precedenza a valueToCode e aggiungere il codice restituito alla stringa di codice. Per ulteriori informazioni, consulta la precedenza di valueToCode.

Blocchi di istruzioni interne

Per recuperare il codice da un blocco interno associato all'input di un'istruzione, chiama statementToCode. Questo metodo chiama il generatore di codice del blocco interno e gestisce il codice di rientro.

custom_compare

Il blocco custom_compare non ha input di istruzioni.

custom_if

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

Devi chiamare statementToCode solo per il blocco interno collegato direttamente all'input di un'istruzione. statementToCode gestisce eventuali blocchi aggiuntivi collegati al primo blocco.

Crea e restituisci la stringa di codice

Dopo aver ottenuto il codice per i campi e i blocchi interni, crea e restituisci la stringa di codice per il blocco. I valori restituiti dipendono dal tipo di blocco:

  • Blocchi di valore:restituisce un array contenente la stringa di codice e la precedenza dell'operatore più debole nel codice. valueToCode lo utilizza per decidere se il codice deve essere racchiuso tra parentesi quando il blocco viene utilizzato come blocco interno. Per ulteriori informazioni, consulta Precedenza del ritorno.

  • Blocchi di istruzioni: restituisce la stringa di codice.

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

Se utilizzi il codice di un blocco di valori interni più volte nella stringa di codice, devi memorizzare nella cache il codice di quel blocco per evitare bug sottili ed effetti collaterali indesiderati.

Generatori di codice completi

Come riferimento, di seguito sono riportati i generatori di codice completi per ogni blocco:

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