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:
- Importa un generador de códigos de idioma.
- Obtén el valor de cada campo y transfórmalo en una cadena de código.
- Obtén las cadenas de código generadas por los bloques internos, que son bloques adjuntos a entradas de valor y sentencia.
- 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 llamadaLEFT
, un campo desplegable llamadoOPERATOR
y un campo numérico llamadoRIGHT
. Genera una cadena de expresión del tipo'0 = 0'
.custom_if
es un bloque de instrucciones que tiene un campo de casilla de verificación llamadoNOT
, una entrada de valor llamadaCONDITION
y una entrada de instrucción llamadaTHEN
. Genera una cadena de sentencia del formato'if (...) {\n...\n};\n'
.
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;
}