Mettre en cache le code du bloc de valeur interne

Les blocs de valeurs correspondent à des expressions. Lorsque vous utilisez un bloc de valeur comme bloc interne, vous devrez peut-être utiliser l'expression qu'il génère plusieurs fois dans le code de votre bloc. Par exemple, un bloc qui récupère le dernier élément d'une liste utilise l'expression qui crée la liste deux fois.

// Incorrect block-code generator.
javascriptGenerator.forBlock['last_element'] = function(block, generator) {
  // Get the expression that creates the list.
  const listCode = generator.valueToCode(block, 'LIST', Order.MEMBER);

  // listCode is used twice.
  const code = `${listCode}[${listCode}.length - 1]`;

  return [code, Order.MEMBER];
}

Cela entraîne des problèmes si le code du bloc interne génère des valeurs différentes à chaque fois qu'il est exécuté ou si le code présente des effets secondaires. Par exemple, si le code du bloc interne est en fait un appel de fonction, ce code particulier peut entraîner une condition hors plage:

randomList()[randomList().length - 1]

Pour éviter ce problème, votre code doit exécuter le code d'un bloc interne exactement une fois. Pour cela, vous pouvez procéder de deux façons :

  • Variables temporaires: mettez en cache le résultat de l'évaluation du code du bloc interne dans une variable temporaire et utilisez plutôt cette variable temporaire. Vous ne pouvez utiliser cette méthode que si votre bloc est un bloc d'instructions.

  • Fonctions utilitaires: créez une fonction qui effectue la tâche à accomplir et transmettez le résultat de l'évaluation du code du bloc interne en tant qu'argument à cette fonction. Vous pouvez utiliser cette méthode pour les blocs de valeurs et d'instructions.

Variables temporaires

Une variable temporaire stocke la valeur de la chaîne de code d'un bloc interne afin que le code ne soit évalué qu'une seule fois, puis que la valeur puisse être référencée plusieurs fois.

Les variables temporaires ne peuvent pas être utilisées dans les blocs de valeurs, car ils doivent renvoyer une seule ligne de code. Utilisez plutôt une fonction utilitaire.

import {javascriptGenerator, Order} from 'blockly/javascript';

// Correct block-code generator for a statement block that prints the last element of a list.
javascriptGenerator.forBlock['print_last_element'] = function(block, generator) {
  // Get the expression that creates the list.
  const listCode = generator.valueToCode(block, 'LIST', Order.MEMBER);
  // Get the name of a temporary variable.
  const listVar = generator.nameDB_.getDistinctName(
      'temp_list', Blockly.names.NameType.VARIABLE);

  // Evaluate listCode once and assign the result to the temporary variable.
  const code = `var ${listVar} = ${listCode};\n`;
  // Print the last element of the list.
  code += `print(${listVar}[${listVar}.length - 1]);\n`;
  return code;
}

Par exemple, si le code du bloc interne est l'appel de fonction randomList(), le code généré est le suivant:

var temp_list = randomList();
print(temp_list[temp_list.length - 1]);

L'appel getDistinctName reçoit le nom de la variable souhaité et renvoie un nom qui n'entre pas en conflit avec les variables définies par l'utilisateur.

Réduire le code redondant

L'inconvénient des variables temporaires est que si le code du bloc interne est une valeur et non une fonction ou une expression, vous obtenez un code redondant:

// Assigning to temp_list is unnecessary.
var temp_list = foo;
print(temp_list[temp_list.length - 1]);

Pour produire un code plus propre, vous pouvez vérifier si le code du bloc interne est une valeur et n'inclure la variable temporaire que si ce n'est pas le cas.

if (listCode.match(/^\w+$/)) {
  const code = `print(${listCode}[${listCode}.length - 1]);\n`;
} else {
  const listVar = generator.nameDB_.getDistinctName(
      'temp_list', Blockly.names.NameType.VARIABLE);
  const code = `var ${listVar} = ${listCode};\n`;
  code += `print(${listVar}[${listVar}.length - 1]);\n`;
}

Fonctions utilitaires

Une fonction utilitaire est une fonction définie par le développeur incluse dans la chaîne de code générée. Vous pouvez les utiliser pour vous assurer que le code du bloc interne n'est évalué qu'une seule fois, puis que la valeur peut être référencée plusieurs fois.

Les fonctions utilitaires peuvent être utilisées dans les blocs de valeurs et les blocs d'instructions. Toutefois, les blocs d'instructions doivent généralement utiliser des variables temporaires, qui sont généralement plus lisibles.

import {javascriptGenerator, Order} from 'blockly/javascript';

// Correct block-code generator for a value block that gets the last element of a list.
javascriptGenerator.forBlock['last_element'] = function(block, generator) {
  // Get the expression that creates the list.
  const listCode = generator.valueToCode(block, 'LIST', Order.NONE);
  // Create a function that accepts a list and returns its last element. The
  // language generator adds this function to your code.
  const functionName = generator.provideFunction_(
      'list_lastElement',
      [
        `function ${generator.FUNCTION_NAME_PLACEHOLDER_}(list) {`,
        `  return list[list.length - 1];`,
        `}`
      ]
  );

  // Create an expression that calls the function with listCode as its argument.
  // This evaluates listCode once and passes the resulting list to the function.
  const code = `${functionName}(${listCode})`;
  return [code, Order.FUNCTION_CALL];
}

Par exemple, si le code du bloc interne est l'appel de fonction randomList(), le code généré est le suivant:

// This code is added to the overall code returned by the code generator.
function list_lastElement(list) {
  return list[list.length - 1];
}

// This code is returned by your inner block.
list_lastElement(randomList());

Fournir la fonction

Vous pouvez définir des fonctions utilitaires dans les générateurs de code de bloc à l'aide de provideFunction_. Elle prend en charge le nom que vous souhaitez donner à votre fonction utilitaire et un tableau de chaînes de code contenant la définition de la fonction. Il renvoie le nom résultant de votre fonction utilitaire, après l'avoir (éventuellement) modifié pour qu'il ne soit pas en conflit avec les fonctions définies par l'utilisateur.

provideFunction_ déduplique également les définitions de fonctions utilitaires, de sorte que chaque fonction utilitaire n'existe qu'une seule fois, même si le type de bloc qui la définit existe plusieurs fois.

Mettre à jour les priorités

Lorsque vous définissez une fonction utilitaire, vous devez également mettre à jour les priorités (qui définissent la façon dont les parenthèses sont insérées) incluses dans le générateur de code de bloc.

La priorité est toujours basée sur la chaîne de code renvoyée par le générateur de code de bloc. Il ne tient pas compte des opérateurs dans les fonctions utilitaires. Dans l'exemple précédent, l'appel valueToCode a été remplacé par Order.NONE et le tuple de retour a été remplacé par Order.FUNCTION_CALL.