Generatori di codici a blocchi

Un generatore di codice a blocchi è 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 fungono da espressioni in un linguaggio basato su testo e generano stringhe che contengono espressioni.
  • I blocchi di istruzioni sono blocchi senza una connessione di output. Questi blocchi si comportano come istruzioni in un linguaggio basato sul testo e generano stringhe che contengono istruzioni.

Come scrivere un generatore di codice a blocchi

Per ogni blocco personalizzato che crei, devi scrivere un generatore di codice blocco per ogni lingua che vuoi supportare. Tieni presente che tutti i generatori di codice a blocchi sono scritti in JavaScript, anche se generano codice in un'altra lingua.

Tutti i generatori di blocchi di codice eseguono i seguenti passaggi:

  1. Importa un generatore di codici di lingua.
  2. Ottieni il valore di ogni campo e trasformalo in una stringa di codice.
  3. Ottieni le stringhe di codice generate dai blocchi interni, ovvero i blocchi collegati agli input di valore e istruzione.
  4. Crea e restituisci 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 che ha un input di valore denominato LEFT, un campo a discesa denominato OPERATOR e un campo numerico denominato RIGHT. Genera una stringa di espressione nel formato '0 = 0'.

    Blocco di valori personalizzati per
i confronti.

  • custom_if è un blocco di istruzioni che include un campo casella di controllo denominato NOT, un input valore denominato CONDITION e un input istruzione denominato THEN. Genera una stringa di istruzione nel formato '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 crea i generatori passo dopo passo. Puoi trovare i generatori completati alla fine di questo documento.

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

Importare un generatore di codici di lingua

Puoi importare un generatore di codici di 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) { /* ... */ };

Ottieni 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 variano da campo a campo. Ad esempio, i campi di testo restituiscono il testo esatto inserito dall'utente, mentre i campi a discesa restituiscono una stringa indipendente dalla 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 utilizzarlo 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 saperne di più, consulta Trasformare i valori dei campi.

Ottieni il codice dai blocchi interni

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

A differenza dei valori dei campi, il codice ottenuto dai blocchi interni è pronto per l'uso e non deve essere trasformato.

Blocchi di valori interni

Per ottenere il codice da un blocco interno collegato 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 tuo codice che verrà applicato al codice del blocco interno. Ciò consente a valueToCode di determinare se è necessario racchiudere il codice del blocco interno tra parentesi.

Ad esempio, selezionando la casella NOT in custom_if viene applicato un operatore logico NOT (!) 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. Poi esegue il wrapping del codice del blocco interno in base alle esigenze:

  • Se CONDITION è un blocco variabile, valueToCode non aggiunge 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 NOT si applichi all'intero confronto (!(a < b)) anziché al valore a sinistra (!a < b).

Non è necessario sapere se valueToCode ha aggiunto le parentesi. Tutto ciò che devi fare è passare la precedenza a valueToCode e aggiungere il codice restituito alla stringa di codice. Per ulteriori informazioni, vedi precedenza di valueToCode.

Blocchi di istruzioni interni

Per ottenere il codice da un blocco interno collegato a un input di istruzione, chiama statementToCode. Questo metodo chiama il generatore di codice del blocco interno e gestisce l'indentazione del codice.

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 a un input di 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. Ciò che restituisci dipende dal tipo di blocco:

  • Blocchi di valori: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 saperne di più, consulta Precedenza del reso.

  • Blocchi di istruzioni:restituiscono 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 più volte il codice di un blocco di valori interni nella stringa di codice, devi memorizzare nella cache il codice di quel blocco per evitare bug sottili ed effetti collaterali indesiderati.

Generatori di codici completi

Per riferimento, ecco 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;
}