Generadores de códigos de bloque

Un generador de código de bloque es una función que genera el código de un bloque y lo muestra como una cadena. El código que genera un bloque depende de su tipo:

  • Los bloques de valor tienen una conexión de salida. Estos bloques actúan como expresiones en un lenguaje basado en texto y generan cadenas que contienen expresiones.
  • Los bloques de instrucciones son bloques sin una conexión de salida. Estos bloques actúan como sentencias en un lenguaje basado en texto y generan cadenas que contienen sentencias.

Cómo escribir un generador de código de bloques

Para cada bloque personalizado que crees, debes escribir un generador de código de bloque para cada idioma que quieras admitir. Ten en cuenta que todos los generadores de código de bloques están escritos en JavaScript, incluso si generan código en otro lenguaje.

Todos los generadores de código de bloques realizan los siguientes pasos:

  1. Importa un generador de códigos de idioma.
  2. Obtén el valor de cada campo y transfórmalo en una cadena de código.
  3. Obtén las cadenas de código generadas por los bloques internos, que son bloques adjuntos a entradas de valor y sentencia.
  4. Compila y muestra la cadena de código del bloque.

Bloques de ejemplo

A modo de ejemplo, escribiremos generadores de código de JavaScript para los siguientes bloques.

  • custom_compare es un bloque de valor que tiene una entrada de valor llamada LEFT, un campo desplegable llamado OPERATOR y un campo numérico llamado RIGHT. Genera una cadena de expresión del tipo '0 = 0'.

    Bloque de valor personalizado para las comparaciones.

  • custom_if es un bloque de instrucciones que tiene un campo de casilla de verificación llamado NOT, una entrada de valor llamada CONDITION y una entrada de instrucción llamada THEN. Genera una cadena de sentencia del formato 'if (...) {\n...\n};\n'.

    Bloque de sentencia personalizado para una sentencia if. Los usuarios pueden usar una casilla de verificación para negar la condición si.

En este documento, se compilan los generadores paso a paso. Puedes encontrar los generadores completos al final de este documento.

Ten en cuenta que estos bloques solo tienen como objetivo ilustrar la generación de código. En una aplicación real, usa los bloques logic_compare y controls_if integrados.

Importa un generador de códigos de idioma

Puedes importar un generador de códigos de idioma con uno de los siguientes métodos. Usa el generador importado para almacenar generadores de código de bloque en el objeto forBlock.

Módulos

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

Debes incluir el generador después de incluir 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) { /* ... */ };

Secuencias de comandos locales

Debes incluir el generador después de incluir 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) { /* ... */ };

Cómo obtener valores de campo

Los campos permiten que los usuarios ingresen valores como cadenas, números y colores. Para obtener el valor de un campo, llama a getFieldValue. Lo que se muestra es diferente de un campo a otro. Por ejemplo, los campos de texto muestran el texto exacto que ingresó el usuario, pero los campos desplegables muestran una cadena neutral en cuanto al idioma asociada con el elemento que seleccionó el usuario. Para obtener más información, consulta la documentación de los campos integrados.

Según el campo, es posible que debas transformar el valor que se muestra antes de usarlo en el código.

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

Para obtener más información, consulta Transforma los valores de campo.

Cómo obtener código de bloques internos

Los bloques internos son los bloques adjuntos al valor y las entradas de sentencia de un bloque. Por ejemplo, el bloque custom_if tiene un bloque interno de valor para la condición si y bloques internos de sentencia para el código que se ejecuta si la condición es verdadera.

A diferencia de los valores de campo, el código que obtienes de los bloques internos está listo para usar y no es necesario transformarlo.

Bloques de valores internos

Para obtener código de un bloque interno adjunto a una entrada de valor, llama a valueToCode. Este método llama al generador de código del bloque 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';
  ...
}

Cuando llames a valueToCode, debes informarle sobre el operador más fuerte de tu código que se aplicará al código del bloque interno. Esto permite que valueToCode determine si necesita unir el código del bloque interno entre paréntesis.

Por ejemplo, marcar la casilla NOT en custom_if aplica un operador lógico no (!) a la condición. En este caso, pasas la precedencia del operador no (Order.LOGICAL_NOT) a valueToCode y valueToCode la compara con la precedencia del operador más débil en el bloque interno. Luego, une el código del bloque interno según sea necesario:

  • Si CONDITION es un bloque de variables, valueToCode no agrega paréntesis porque el operador not se puede aplicar directamente a una variable (!myBoolean).
  • Si CONDITION es un bloque de comparación, valueToCode agrega paréntesis para que el operador no se aplique a toda la comparación (!(a < b)) en lugar del valor de la izquierda (!a < b).

En realidad, no necesitas saber si valueToCode agregó paréntesis. Todo lo que debes hacer es pasar la prioridad a valueToCode y agregar el código que se muestra a tu cadena de código. Para obtener más información, consulta Prioridad de valueToCode.

Bloques de sentencias internas

Para obtener código de un bloque interno adjunto a una entrada de sentencia, llama a statementToCode. Este método llama al generador de código del bloque interno y controla el código de sangría.

custom_compare

El bloque custom_compare no tiene ninguna entrada de sentencia.

custom_if

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

Solo debes llamar a statementToCode para el bloque interno conectado directamente a una entrada de sentencia. statementToCode controla los bloques adicionales adjuntos al primer bloque.

Compila y muestra tu cadena de código

Después de obtener el código de los campos y los bloques internos, compila y muestra la cadena de código de tu bloque. Lo que devuelves exactamente depende del tipo de bloque:

  • Bloques de valor: Muestran un array que contiene la cadena de código y la precedencia del operador más débil en tu código. valueToCode lo usa para decidir si tu código debe encerrarse entre paréntesis cuando el bloque se usa como un bloque interno. Para obtener más información, consulta Prioridad de la función devuelta.

  • Bloques de sentencias: Muestran la cadena de código.

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

Si usas el código de un bloque de valor interno varias veces en tu cadena de código, debes almacenar en caché el código de ese bloque para evitar errores sutiles y efectos secundarios no deseados.

Generadores de código completos

A modo de referencia, estos son los generadores de código completos para cada bloque:

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