Un générateur de code par blocs est une fonction qui génère le code d'un bloc et le renvoie sous forme de chaîne. Le code généré par un bloc dépend de son type :
- Les blocs de valeur ont une connexion de sortie. Ces blocs agissent comme des expressions dans un langage textuel et génèrent des chaînes contenant des expressions.
- Les blocs d'instructions sont des blocs sans connexion de sortie. Ces blocs agissent comme des instructions dans un langage textuel et génèrent des chaînes contenant des instructions.
Écrire un générateur de code par blocs
Pour chaque bloc personnalisé que vous créez, vous devez écrire un générateur de code de bloc pour chaque langue que vous souhaitez prendre en charge. Notez que tous les générateurs de code par blocs sont écrits en JavaScript, même s'ils génèrent du code dans une autre langue.
Tous les générateurs de code par blocs effectuent les étapes suivantes :
- Importez un générateur de code de langue.
- Obtenez la valeur de chaque champ et transformez-la en chaîne de code.
- Obtenez les chaînes de code générées par les blocs internes, qui sont des blocs associés aux entrées de valeur et d'instruction.
- Crée et renvoie la chaîne de code du bloc.
Exemples de blocs
À titre d'exemple, nous allons écrire des générateurs de code JavaScript pour les blocs suivants.
custom_compareest un bloc de valeur qui comporte une entrée de valeur nomméeLEFT, un champ de menu déroulant nomméOPERATORet un champ numérique nomméRIGHT. Il génère une chaîne d'expression au format'0 = 0'.
custom_ifest un bloc d'instruction qui comporte un champ de case à cocher nomméNOT, une entrée de valeur nomméeCONDITIONet une entrée d'instruction nomméeTHEN. Il génère une chaîne d'instruction au format'if (...) {\n...\n};\n'.
Ce document explique comment créer des générateurs étape par étape. Vous trouverez les générateurs complets à la fin de ce document.
Notez que ces blocs ne sont fournis qu'à titre d'exemple pour illustrer la génération de code. Dans une application réelle, utilisez les blocs logic_compare et controls_if intégrés.
Importer un générateur de code de langue
Vous pouvez importer un générateur de codes de langue à l'aide de l'une des méthodes suivantes. Utilisez le générateur importé pour stocker les générateurs de code de bloc dans l'objet forBlock.
Modules
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
Vous devez inclure le générateur après 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) { /* ... */ };
Scripts locaux
Vous devez inclure le générateur après 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) { /* ... */ };
Obtenir les valeurs des champs
Les champs permettent aux utilisateurs de saisir des valeurs telles que des chaînes, des nombres et des couleurs. Pour obtenir la valeur d'un champ, appelez getFieldValue. Les valeurs renvoyées varient d'un champ à l'autre. Par exemple, les champs de texte renvoient le texte exact saisi par l'utilisateur, mais les champs de menu déroulant renvoient une chaîne neutre en termes de langue associée à l'élément sélectionné par l'utilisateur. Pour en savoir plus, consultez la documentation sur les champs intégrés.
Selon le champ, vous devrez peut-être transformer la valeur renvoyée avant de l'utiliser dans le code.
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' ? '!' : '';
...
}
Pour en savoir plus, consultez Transformer les valeurs des champs.
Obtenir le code à partir des blocs internes
Les blocs internes sont les blocs associés aux entrées de valeur et d'instruction d'un bloc.
Par exemple, le bloc custom_if comporte un bloc interne de valeur pour la condition if et des blocs internes d'instruction pour le code exécuté si la condition est vraie.
Contrairement aux valeurs de champ, le code que vous obtenez à partir des blocs internes est prêt à l'emploi et n'a pas besoin d'être transformé.
Blocs de valeurs internes
Pour obtenir le code d'un bloc interne associé à une entrée de valeur, appelez valueToCode.
Cette méthode appelle le générateur de code du bloc interne.
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';
...
}
Lorsque vous appelez valueToCode, vous devez lui indiquer l'opérateur le plus fort de votre code qui s'appliquera au code du bloc intérieur. Cela permet à valueToCode de déterminer s'il doit encapsuler le code du bloc intérieur entre parenthèses.
Par exemple, cocher la case NOT dans custom_if applique un opérateur logique NOT (!) à la condition. Dans ce cas, vous transmettez la priorité de l'opérateur NOT (Order.LOGICAL_NOT) à valueToCode, et valueToCode compare cette priorité à celle de l'opérateur le plus faible du bloc intérieur. Il encapsule ensuite le code du bloc intérieur selon les besoins :
- Si
CONDITIONest un bloc de variables,valueToCoden'ajoute pas de parenthèses, car l'opérateur NOT peut être appliqué directement à une variable (!myBoolean). - Si
CONDITIONest un bloc de comparaison,valueToCodeajoute des parenthèses afin que l'opérateur NOT s'applique à la comparaison entière (!(a < b)) au lieu de la valeur de gauche (!a < b).
Vous n'avez pas besoin de savoir si valueToCode a ajouté des parenthèses. Il vous suffit de transmettre la précédence à valueToCode et d'ajouter le code renvoyé à votre chaîne de code. Pour en savoir plus, consultez Précédence de valueToCode.
Blocs d'instructions internes
Pour obtenir le code d'un bloc interne associé à une entrée d'instruction, appelez statementToCode. Cette méthode appelle le générateur de code du bloc intérieur et gère l'indentation du code.
custom_compare
Le bloc custom_compare ne comporte aucune entrée d'instruction.
custom_if
javascriptGenerator.forBlock['custom_if'] = function (block, generator) {
...
const statements = generator.statementToCode(block, 'THEN');
...
}
Il vous suffit d'appeler statementToCode pour le bloc intérieur directement connecté à une entrée d'instruction. statementToCode gère tous les blocs supplémentaires associés au premier bloc.
Créer et renvoyer votre chaîne de code
Une fois que vous avez obtenu le code pour les champs et les blocs internes, créez et renvoyez la chaîne de code pour votre bloc. Les éléments à retourner dépendent du type de bloc :
Blocs de valeurs : renvoient un tableau contenant la chaîne de code et la précédence de l'opérateur le plus faible de votre code.
valueToCodeutilise cette méthode pour déterminer si votre code doit être mis entre parenthèses lorsque votre bloc est utilisé comme bloc interne. Pour en savoir plus, consultez Précédence des retours.Blocs d'instructions : renvoient la chaîne de code.
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 vous utilisez plusieurs fois le code d'un bloc de valeur interne dans votre chaîne de code, vous devez mettre en cache le code de ce bloc pour éviter les bugs subtils et les effets secondaires indésirables.
Générateurs de code complets
Pour référence, voici les générateurs de code complets pour chaque bloc :
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;
}