Geradores de código de bloco

Um gerador de código de bloco é uma função que gera o código de um bloco e o retorna como uma string. O código gerado por um bloco depende do tipo dele:

  • Os blocos de valor têm uma conexão de saída. Esses blocos funcionam como expressões em uma linguagem baseada em texto e geram strings que contêm expressões.
  • Blocos de instrução são blocos sem uma conexão de saída. Esses blocos agem como instruções em uma linguagem baseada em texto e geram strings que contêm instruções.

Como escrever um gerador de código em blocos

Para cada bloco personalizado criado, é necessário escrever um gerador de código de bloco para cada idioma que você quer oferecer suporte. Observação: todos os geradores de código em blocos são escritos em JavaScript, mesmo que gerem código em outra linguagem.

Todos os geradores de código em blocos executam as seguintes etapas:

  1. Importe um gerador de código de idioma.
  2. Receba o valor de cada campo e transforme-o em uma string de código.
  3. Receba as strings de código geradas por blocos internos, que são blocos anexados a entradas de valor e instrução.
  4. Crie e retorne a string de código do bloco.

Exemplos de blocos

Como exemplos, vamos escrever geradores de código JavaScript para os seguintes blocos.

  • custom_compare é um bloco de valor que tem uma entrada de valor chamada LEFT, um campo suspenso chamado OPERATOR e um campo numérico chamado RIGHT. Ela gera uma string de expressão no formato '0 = 0'.

    Bloco de valor personalizado para comparações.

  • custom_if é um bloco de instrução que tem um campo de caixa de seleção chamado NOT, uma entrada de valor chamada CONDITION e uma entrada de instrução chamada THEN. Ele gera uma string de instrução no formato 'if (...) {\n...\n};\n'.

    Bloco de instrução personalizado para uma instrução if. Os usuários podem usar uma caixa de seleção para negar a condição "if".

Este documento cria os geradores etapa por etapa. Os geradores completos estão no final deste documento.

Observe que esses blocos se destinam apenas a ilustrar a geração de código. Em um aplicativo real, use os blocos logic_compare e controls_if integrados.

Importar um gerador de código de idioma

É possível importar um gerador de código de idioma usando um dos seguintes métodos. Use o gerador importado para armazenar geradores de código de bloco no objeto forBlock.

Módulos

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

Inclua o gerador depois de incluir o 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 locais

Inclua o gerador depois de incluir o 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) { /* ... */ };

Receber valores de campo

Os campos permitem que os usuários insiram valores como strings, números e cores. Para receber o valor de um campo, chame getFieldValue. O que é retornado varia de campo para campo. Por exemplo, os campos de texto retornam o texto exato inserido pelo usuário, mas os campos suspensos retornam uma string neutra em relação ao idioma associada ao item selecionado pelo usuário. Para mais informações, consulte a documentação sobre campos integrados.

Dependendo do campo, talvez seja necessário transformar o valor retornado antes de usá-lo no código.

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' ? '!' : '';
  ...
}

Para mais informações, consulte Transformar valores de campo.

Receber código de blocos internos

Os blocos internos são aqueles anexados ao valor e às entradas de instrução de um bloco. Por exemplo, o bloco custom_if tem um bloco interno de valor para a condição "if" e blocos internos de instrução para o código que é executado se a condição for verdadeira.

Ao contrário dos valores de campo, o código que você recebe dos blocos internos está pronto para uso e não precisa ser transformado.

Blocos de valor interno

Para receber o código de um bloco interno anexado a uma entrada de valor, chame valueToCode. Esse método chama o gerador de código do bloco interno.

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

Ao chamar valueToCode, você precisa informar o operador mais forte no código que será aplicado ao código do bloco interno. Isso permite que valueToCode determine se precisa incluir o código do bloco interno entre parênteses.

Por exemplo, marcar a caixa NOT em custom_if aplica um operador lógico NOT (!) à condição. Nesse caso, você transmite a precedência do operador NOT (Order.LOGICAL_NOT) para valueToCode, e valueToCode compara isso à precedência do operador mais fraco no bloco interno. Em seguida, ele encapsula o código do bloco interno conforme necessário:

  • Se CONDITION for um bloco de variáveis, valueToCode não vai adicionar parênteses porque o operador "not" pode ser aplicado diretamente a uma variável (!myBoolean).
  • Se CONDITION for um bloco de comparação, valueToCode vai adicionar parênteses para que o operador not seja aplicado à comparação inteira (!(a < b)) em vez do valor à esquerda (!a < b).

Não é necessário saber se valueToCode adicionou parênteses. Basta transmitir a precedência para valueToCode e adicionar o código retornado à sua string de código. Para mais informações, consulte precedência de valueToCode.

Blocos de instruções internos

Para receber o código de um bloco interno anexado a uma entrada de instrução, chame statementToCode. Esse método chama o gerador de código do bloco interno e processa a indentação do código.

custom_compare

O bloco custom_compare não tem entradas de instrução.

custom_if

javascriptGenerator.forBlock['custom_if'] = function (block, generator) {
  ...
  const statements = generator.statementToCode(block, 'THEN');
  ...
}

Você só precisa chamar statementToCode para o bloco interno conectado diretamente a uma entrada de instrução. O statementToCode processa todos os blocos extras anexados ao primeiro bloco.

Criar e retornar sua string de código

Depois de receber o código dos campos e blocos internos, crie e retorne a string de código do seu bloco. O que você retorna depende do tipo de bloco:

  • Blocos de valor:retornam uma matriz que contém a string de código e a precedência do operador mais fraco no seu código. O valueToCode usa isso para decidir se o código precisa ser colocado entre parênteses quando o bloco é usado como um bloco interno. Para mais informações, consulte Precedência de retorno.

  • Blocos de instruções:retornam a string de código.

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

Se você usar o código de um bloco de valor interno várias vezes na string de código, armazene em cache o código desse bloco para evitar bugs sutis e efeitos colaterais indesejados.

Geradores de código completos

Para referência, confira os geradores de código completos para cada bloco:

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