Almacena en caché el código del bloque de valor interno

Los bloques de valor corresponden a expresiones. Cuando usas un bloque de valor como un bloque interno, es posible que debas usar la expresión que genera más de una vez en el código de tu bloque. Por ejemplo, un bloque que obtiene el último elemento de una lista usa la expresión que crea la lista dos veces.

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

Esto causa problemas si el código del bloque interno genera valores diferentes cada vez que se ejecuta o si tiene efectos secundarios. Por ejemplo, si el código del bloque interno es en realidad una llamada a función, este código en particular puede causar una condición fuera de rango:

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

Para evitar este problema, tu código debe ejecutar el código de un bloque interno exactamente una vez. Existen dos maneras de hacerlo:

  • Variables temporales: Almacena en caché el resultado de la evaluación del código del bloque interno en una variable temporal y, en su lugar, usa la variable temporal. Puedes usar este método solo si tu bloque es un bloque de instrucciones.

  • Funciones de utilidad: Crea una función que realice el trabajo que debes hacer y pasa el resultado de la evaluación del código del bloque interno como argumento a esta función. Puedes usar este método para bloques de valores y de instrucciones.

Variables temporales

Una variable temporal almacena el valor de la cadena de código de un bloque interno para que el código solo se evalúe una vez y, luego, se pueda hacer referencia al valor varias veces.

Las variables temporales no se pueden usar en bloques de valor porque estos deben mostrar una sola línea de código. En su lugar, usa una función de utilidad.

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

Por ejemplo, si el código del bloque interno es la llamada a función randomList(), el código generado es el siguiente:

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

La llamada a getDistinctName toma el nombre de la variable que deseas y muestra un nombre que no entra en conflicto con ninguna variable definida por el usuario.

Reduce el código redundante

La desventaja de las variables temporales es que, si el código del bloque interno es un valor y no una función o expresión, se obtiene un código redundante:

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

Para producir un código más limpio, puedes verificar si el código del bloque interno es un valor y solo incluir la variable temporal si no lo es.

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

Funciones de utilidad

Una función de utilidad es una función definida por el desarrollador que se incluye como parte de la cadena de código generada. Puedes usarlos para asegurarte de que el código del bloque interno solo se evalúe una vez y, luego, se pueda hacer referencia al valor varias veces.

Las funciones de utilidad se pueden usar en bloques de valores y bloques de instrucciones. Sin embargo, los bloques de instrucciones suelen usar variables temporales, que suelen ser más legibles.

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

Por ejemplo, si el código del bloque interno es la llamada a función randomList(), el código generado es el siguiente:

// 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());

Proporciona la función

Puedes definir funciones de utilidad dentro de generadores de código de bloque con provideFunction_. Recibe el nombre que deseas para tu función de utilidad y un array de cadenas de código que contienen la definición de la función. Muestra el nombre resultante de tu función de utilidad, después de (posiblemente) modificarla para que no entre en conflicto con las funciones definidas por el usuario.

provideFunction_ también quita las duplicidades de las definiciones de las funciones de utilidad, de modo que cada función de utilidad solo exista una vez, incluso si el tipo de bloque que la define existe varias veces.

Actualiza las prioridades

Cuando defines una función de utilidad, también debes actualizar las prioridades (que definen cómo se insertan los paréntesis) incluidas en el generador de código de bloque.

La prioridad siempre se basa en la cadena de código que muestra el generador de código de bloque. No se preocupa por los operadores dentro de las funciones de utilidad. Por lo tanto, en el ejemplo anterior, la llamada a valueToCode se cambió a Order.NONE y la tupla que se muestra se cambió a Order.FUNCTION_CALL.