Statement blocks caching arguments

Sometimes your block-code generator needs to reference the code of its inner block multiple times.

For example, if you have a block that prints the last element of a list, you need to access the list code multiple times:

// Incorrect block-code generator.
javascriptGenerator.forBlock['print_last_element'] = function(block, generator) {
  const listCode = generator.valueToCode(block, 'LIST', Order.MEMBER);

  // listCode gets referenced twice.
  return `print(${listCode}[${listCode}.length - 1]);\n`;
}

But this can cause problems if the resulting value of the inner block's code is inconsistent, or it has side effects. For example, if the inner code is actually a function call, this particular code can end up with an out of range condition:

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

Assigning to temporary variables lets you make sure that inner block's code is only evaluated once.

Temporary variables

A temporary variable stores the value of an inner block's code string so that the code is only evaluated once, and then the value can be referenced multiple times.

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

// Correct block-code generator.
javascriptGenerator.forBlock['print_last_element'] = function(block, generator) {
  const listCode = generator.valueToCode(block, 'LIST', Order.MEMBER);
  const listVar = generator.nameDB_.getDistinctName(
      'temp_list', Blockly.names.NameType.VARIABLE);

  // listCode only gets evaluated once.
  const code = `var ${listVar} = ${listCode};\n`;
  return `print(${listVar}[${listVar}.length - 1]);\n`;
}

The getDistinctName call takes in the variable name you want, and returns a name that doesn't conflict with any user-defined variables.

Reduce redundant code

The downside of temporary variables is that if the inner block's code is a value and not a function or expression, you get code that's redundant:

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

To produce cleaner code you can check if the inner block's code is a value, and only include the temporary variable if it's not.

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