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.
  • Os blocos de instrução são blocos sem uma conexão de saída. Esses blocos funcionam como instruções em uma linguagem baseada em texto e geram strings que contêm instruções.

Como escrever um gerador de código de bloco

Para cada bloco personalizado criado, é necessário escrever um gerador de código de bloco para cada idioma que você quer oferecer suporte. Todos os geradores de código de bloco são programados em JavaScript, mesmo que gerem código em outro idioma.

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

  1. Importe um gerador de código de idioma.
  2. Extraia 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. Cria e retorna 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 de lista suspensa 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. Ela 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 passo a passo. Os geradores completos estão disponíveis no final deste documento.

Esses blocos servem apenas para 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ódigos de idioma

É possível importar um gerador de códigos 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

É necessário incluir 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

É necessário incluir 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 de menu suspenso retornam uma string neutra em relação ao idioma associada ao item selecionado pelo usuário. Para mais informações, consulte a documentação de 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 os blocos anexados às entradas de valor e 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 recebido 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, é necessário informar o operador mais forte no seu código que será aplicado ao código do bloco interno. Isso permite que valueToCode determine se é necessário envolver o código do bloco interno entre parênteses.

Por exemplo, marcar a caixa NOT em custom_if aplica um operador lógico (!) à condição. Nesse caso, você transmite a precedência do operador NOT (Order.LOGICAL_NOT) para valueToCode, e valueToCode compara isso com a precedência do operador mais fraco no bloco interno. Em seguida, ele envolve 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 a toda a comparação (!(a < b)) em vez do valor à esquerda (!a < b).

Você não precisa saber se valueToCode adicionou parênteses. Tudo o que você precisa fazer é transmitir a precedência para valueToCode e adicionar o código retornado à sua string de código. Para mais informações, consulte Prioridade de valueToCode.

Blocos de instruções internas

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 o código de recuo.

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 diretamente conectado a uma entrada de instrução. statementToCode processa todos os blocos adicionais anexados ao primeiro.

Criar e retornar a string de código

Depois de receber o código dos campos e blocos internos, crie e retorne a string de código do 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 código. O valueToCode usa isso para decidir se o código precisa ser envolvido em parênteses quando o bloco é usado como um bloco interno. Para mais informações, consulte Prioridade de retorno.

  • Blocos de instrução: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 de 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;
}