내부 값 블록 코드 캐시

값 블록은 표현식에 해당합니다. 값 블록을 내부 블록으로 사용하는 경우 블록의 코드에서 생성되는 표현식을 두 번 이상 사용해야 할 수 있습니다. 예를 들어 목록의 마지막 요소를 가져오는 블록은 목록을 두 번 만드는 표현식을 사용합니다.

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

이렇게 하면 내부 블록의 코드가 실행될 때마다 다른 값을 생성하거나 코드에 부작용이 있는 경우 문제가 발생합니다. 예를 들어 내부 블록의 코드가 실제로 함수 호출인 경우 이 특정 코드로 인해 범위 외 조건이 발생할 수 있습니다.

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

이 문제를 방지하려면 코드가 내부 블록의 코드를 정확히 한 번 실행해야 합니다. 다음과 같은 두 가지 방법이 있습니다.

  • 임시 변수: 임시 변수에서 내부 블록의 코드를 평가한 결과를 캐시하고 대신 임시 변수를 사용합니다. 블록이 문 블록인 경우에만 이 메서드를 사용할 수 있습니다.

  • 유틸리티 함수: 실행해야 할 작업을 실행하는 함수를 만들고 내부 블록의 코드 평가 결과를 이 함수에 인수로 전달합니다. 이 메서드는 값 블록과 문 블록 모두에 사용할 수 있습니다.

임시 변수

임시 변수는 코드가 한 번만 평가되고 값이 여러 번 참조될 수 있도록 내부 블록의 코드 문자열 값을 저장합니다.

값 블록은 단일 줄의 코드를 반환해야 하므로 값 블록에서는 임시 변수를 사용할 수 없습니다. 대신 유틸리티 함수를 사용하세요.

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

예를 들어 내부 블록의 코드가 함수 호출 randomList()이면 생성된 코드는 다음과 같습니다.

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

getDistinctName 호출은 원하는 변수 이름을 가져와 사용자 정의 변수와 충돌하지 않는 이름을 반환합니다.

중복 코드 줄이기

임시 변수의 단점은 내부 블록의 코드가 함수나 표현식이 아닌 값인 경우 중복된 코드가 생성된다는 것입니다.

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

더 깔끔한 코드를 생성하려면 내부 블록의 코드가 값인지 확인하고 값이 아닌 경우에만 임시 변수를 포함하면 됩니다.

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

유틸리티 함수

유틸리티 함수는 생성된 코드 문자열의 일부로 포함된 개발자 정의 함수입니다. 이를 사용하여 내부 블록 코드가 한 번만 평가되고 값을 여러 번 참조할 수 있습니다.

유틸리티 함수는 값 블록과 문 블록에서 사용할 수 있습니다. 그러나 일반적으로 더 읽기 쉬운 임시 변수를 사용해야 합니다.

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

예를 들어 내부 블록의 코드가 함수 호출 randomList()이면 생성된 코드는 다음과 같습니다.

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

함수 제공

provideFunction_를 사용하여 블록 코드 생성기 내에서 유틸리티 함수를 정의할 수 있습니다. 이 함수는 유틸리티 함수의 이름과 함수 정의를 포함하는 코드 문자열 배열을 사용합니다. 사용자 정의 함수와 충돌하지 않도록 수정한 후 유틸리티 함수의 결과 이름을 반환합니다.

또한 provideFunction_는 유틸리티 함수 정의의 중복을 삭제하므로 각 유틸리티 함수는 이를 정의하는 블록 유형이 여러 번 존재하더라도 한 번만 존재합니다.

우선순위 업데이트

유틸리티 함수를 정의할 때는 블록 코드 생성기에 포함된 우선순위(괄호 삽입 방법을 정의함)도 업데이트해야 합니다.

우선순위는 항상 블록 코드 생성기에서 반환한 코드 문자열을 기반으로 합니다. 유틸리티 함수 내의 연산자는 고려하지 않습니다. 따라서 이전 예시에서 valueToCode 호출은 Order.NONE로, 반환 튜플은 Order.FUNCTION_CALL로 변경되었습니다.