Skip to content

Commit

Permalink
refactor(generators)!: CodeGenerator per-block-type generator functio…
Browse files Browse the repository at this point in the history
…n dictionary (#7150)

* feat(generators): Add block generator function dictionary

  Add a dictionary of block generator functions, provisionally
  called .forBlock.  Look up generator functions there first, but
  fall back to looking up on 'this' (with deprecation notice)
  for backwards compatibility.

  Also tweak error message generation to use template literal.

* refactor(generators)!: Update generator definitions to use dictionary

* fix(tests): Have blockToCodeTest clean up after itself

  Have the blockToCodeTest helper function delete the block generator
  functions it adds to generator once the test is done.

* refactor(tests): Use generator dictionary in insertion marker test

  The use of generators in insertion_marker_test.js was overlooked
  in the earlier commit making such updates, and some test here
  were failing due to lack of cleanup in
  cleanup in the generator_test.js.

BREAKING CHANGE: this PR moves the generator functions we provide
from their previous location directly on the CodeGenerator instances
to the new .forBlock dictionary on each instance. This does not oblige
external developers to do the same for their custom generators, but
they will need to update any code that references the generator
functions we provide (in generators/*/*, i.e. on javascriptGenerator,
dartGenerator etc.) e.g. to replace the implementation or reuse the
implementation for a different block type.
  • Loading branch information
cpcallen authored Jun 13, 2023
1 parent f373809 commit f9c865b
Show file tree
Hide file tree
Showing 48 changed files with 386 additions and 371 deletions.
27 changes: 20 additions & 7 deletions core/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,19 @@ import type {Block} from './block.js';
import * as common from './common.js';
import {Names, NameType} from './names.js';
import type {Workspace} from './workspace.js';
import {warn} from './utils/deprecation.js';

export type BlockGenerator = (block: Block) => [string, number] | string | null;

/**
* Class for a code generator that translates the blocks into a language.
*/
export class CodeGenerator {
name_: string;

/** A dictionary of block generator functions keyed by block type. */
forBlock: {[type: string]: BlockGenerator} = Object.create(null);

/**
* This is used as a placeholder in functions defined using
* CodeGenerator.provideFunction_. It must not be legal code that could
Expand Down Expand Up @@ -220,15 +226,22 @@ export class CodeGenerator {
return opt_thisOnly ? '' : this.blockToCode(block.getChildren(false)[0]);
}

const func = (this as any)[block.type];
// Look up block generator function in dictionary - but fall back
// to looking up on this if not found, for backwards compatibility.
let func = this.forBlock[block.type];
if (!func && (this as any)[block.type]) {
warn(
'block generator functions on CodeGenerator objects',
'10.0',
'11.0',
'the .forBlock[blockType] dictionary'
);
func = (this as any)[block.type];
}
if (typeof func !== 'function') {
throw Error(
'Language "' +
this.name_ +
'" does not know how to generate ' +
'code for block type "' +
block.type +
'".'
`${this.name_} generator does not know how to generate code` +
`for block type "${block.type}".`
);
}
// First argument to func.call is the value of 'this' in the generator.
Expand Down
8 changes: 4 additions & 4 deletions generators/dart/colour.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ import {dartGenerator as Dart} from '../dart.js';

Dart.addReservedWords('Math');

Dart['colour_picker'] = function(block) {
Dart.forBlock['colour_picker'] = function(block) {
// Colour picker.
const code = Dart.quote_(block.getFieldValue('COLOUR'));
return [code, Dart.ORDER_ATOMIC];
};

Dart['colour_random'] = function(block) {
Dart.forBlock['colour_random'] = function(block) {
// Generate a random colour.
Dart.definitions_['import_dart_math'] = "import 'dart:math' as Math;";
const functionName = Dart.provideFunction_('colour_random', `
Expand All @@ -38,7 +38,7 @@ String ${Dart.FUNCTION_NAME_PLACEHOLDER_}() {
return [code, Dart.ORDER_UNARY_POSTFIX];
};

Dart['colour_rgb'] = function(block) {
Dart.forBlock['colour_rgb'] = function(block) {
// Compose a colour from RGB components expressed as percentages.
const red = Dart.valueToCode(block, 'RED',
Dart.ORDER_NONE) || 0;
Expand Down Expand Up @@ -69,7 +69,7 @@ String ${Dart.FUNCTION_NAME_PLACEHOLDER_}(num r, num g, num b) {
return [code, Dart.ORDER_UNARY_POSTFIX];
};

Dart['colour_blend'] = function(block) {
Dart.forBlock['colour_blend'] = function(block) {
// Blend two colours together.
const c1 = Dart.valueToCode(block, 'COLOUR1', Dart.ORDER_NONE) || "'#000000'";
const c2 = Dart.valueToCode(block, 'COLOUR2', Dart.ORDER_NONE) || "'#000000'";
Expand Down
24 changes: 12 additions & 12 deletions generators/dart/lists.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ import {dartGenerator as Dart} from '../dart.js';

Dart.addReservedWords('Math');

Dart['lists_create_empty'] = function(block) {
Dart.forBlock['lists_create_empty'] = function(block) {
// Create an empty list.
return ['[]', Dart.ORDER_ATOMIC];
};

Dart['lists_create_with'] = function(block) {
Dart.forBlock['lists_create_with'] = function(block) {
// Create a list with any number of elements of any type.
const elements = new Array(block.itemCount_);
for (let i = 0; i < block.itemCount_; i++) {
Expand All @@ -32,29 +32,29 @@ Dart['lists_create_with'] = function(block) {
return [code, Dart.ORDER_ATOMIC];
};

Dart['lists_repeat'] = function(block) {
Dart.forBlock['lists_repeat'] = function(block) {
// Create a list with one element repeated.
const element = Dart.valueToCode(block, 'ITEM', Dart.ORDER_NONE) || 'null';
const repeatCount = Dart.valueToCode(block, 'NUM', Dart.ORDER_NONE) || '0';
const code = 'new List.filled(' + repeatCount + ', ' + element + ')';
return [code, Dart.ORDER_UNARY_POSTFIX];
};

Dart['lists_length'] = function(block) {
Dart.forBlock['lists_length'] = function(block) {
// String or array length.
const list =
Dart.valueToCode(block, 'VALUE', Dart.ORDER_UNARY_POSTFIX) || '[]';
return [list + '.length', Dart.ORDER_UNARY_POSTFIX];
};

Dart['lists_isEmpty'] = function(block) {
Dart.forBlock['lists_isEmpty'] = function(block) {
// Is the string null or array empty?
const list =
Dart.valueToCode(block, 'VALUE', Dart.ORDER_UNARY_POSTFIX) || '[]';
return [list + '.isEmpty', Dart.ORDER_UNARY_POSTFIX];
};

Dart['lists_indexOf'] = function(block) {
Dart.forBlock['lists_indexOf'] = function(block) {
// Find an item in the list.
const operator =
block.getFieldValue('END') === 'FIRST' ? 'indexOf' : 'lastIndexOf';
Expand All @@ -68,7 +68,7 @@ Dart['lists_indexOf'] = function(block) {
return [code, Dart.ORDER_UNARY_POSTFIX];
};

Dart['lists_getIndex'] = function(block) {
Dart.forBlock['lists_getIndex'] = function(block) {
// Get element at index.
// Note: Until January 2013 this block did not have MODE or WHERE inputs.
const mode = block.getFieldValue('MODE') || 'GET';
Expand Down Expand Up @@ -222,7 +222,7 @@ dynamic ${Dart.FUNCTION_NAME_PLACEHOLDER_}(List my_list) {
throw Error('Unhandled combination (lists_getIndex).');
};

Dart['lists_setIndex'] = function(block) {
Dart.forBlock['lists_setIndex'] = function(block) {
// Set element at index.
// Note: Until February 2013 this block did not have MODE or WHERE inputs.
const mode = block.getFieldValue('MODE') || 'GET';
Expand Down Expand Up @@ -298,7 +298,7 @@ Dart['lists_setIndex'] = function(block) {
throw Error('Unhandled combination (lists_setIndex).');
};

Dart['lists_getSublist'] = function(block) {
Dart.forBlock['lists_getSublist'] = function(block) {
// Get sublist.
const list =
Dart.valueToCode(block, 'LIST', Dart.ORDER_UNARY_POSTFIX) || '[]';
Expand Down Expand Up @@ -372,7 +372,7 @@ List ${Dart.FUNCTION_NAME_PLACEHOLDER_}(List list, String where1, num at1, Strin
return [code, Dart.ORDER_UNARY_POSTFIX];
};

Dart['lists_sort'] = function(block) {
Dart.forBlock['lists_sort'] = function(block) {
// Block for sorting a list.
const list = Dart.valueToCode(block, 'LIST', Dart.ORDER_NONE) || '[]';
const direction = block.getFieldValue('DIRECTION') === '1' ? 1 : -1;
Expand All @@ -399,7 +399,7 @@ List ${Dart.FUNCTION_NAME_PLACEHOLDER_}(List list, String type, int direction) {
];
};

Dart['lists_split'] = function(block) {
Dart.forBlock['lists_split'] = function(block) {
// Block for splitting text into a list, or joining a list into text.
let input = Dart.valueToCode(block, 'INPUT', Dart.ORDER_UNARY_POSTFIX);
const delimiter = Dart.valueToCode(block, 'DELIM', Dart.ORDER_NONE) || "''";
Expand All @@ -422,7 +422,7 @@ Dart['lists_split'] = function(block) {
return [code, Dart.ORDER_UNARY_POSTFIX];
};

Dart['lists_reverse'] = function(block) {
Dart.forBlock['lists_reverse'] = function(block) {
// Block for reversing a list.
const list = Dart.valueToCode(block, 'LIST', Dart.ORDER_NONE) || '[]';
// XXX What should the operator precedence be for a `new`?
Expand Down
16 changes: 8 additions & 8 deletions generators/dart/logic.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ goog.declareModuleId('Blockly.Dart.logic');
import {dartGenerator as Dart} from '../dart.js';


Dart['controls_if'] = function(block) {
Dart.forBlock['controls_if'] = function(block) {
// If/elseif/else condition.
let n = 0;
let code = '', branchCode, conditionCode;
Expand Down Expand Up @@ -50,9 +50,9 @@ Dart['controls_if'] = function(block) {
return code + '\n';
};

Dart['controls_ifelse'] = Dart['controls_if'];
Dart.forBlock['controls_ifelse'] = Dart.forBlock['controls_if'];

Dart['logic_compare'] = function(block) {
Dart.forBlock['logic_compare'] = function(block) {
// Comparison operator.
const OPERATORS =
{'EQ': '==', 'NEQ': '!=', 'LT': '<', 'LTE': '<=', 'GT': '>', 'GTE': '>='};
Expand All @@ -66,7 +66,7 @@ Dart['logic_compare'] = function(block) {
return [code, order];
};

Dart['logic_operation'] = function(block) {
Dart.forBlock['logic_operation'] = function(block) {
// Operations 'and', 'or'.
const operator = (block.getFieldValue('OP') === 'AND') ? '&&' : '||';
const order =
Expand All @@ -91,26 +91,26 @@ Dart['logic_operation'] = function(block) {
return [code, order];
};

Dart['logic_negate'] = function(block) {
Dart.forBlock['logic_negate'] = function(block) {
// Negation.
const order = Dart.ORDER_UNARY_PREFIX;
const argument0 = Dart.valueToCode(block, 'BOOL', order) || 'true';
const code = '!' + argument0;
return [code, order];
};

Dart['logic_boolean'] = function(block) {
Dart.forBlock['logic_boolean'] = function(block) {
// Boolean values true and false.
const code = (block.getFieldValue('BOOL') === 'TRUE') ? 'true' : 'false';
return [code, Dart.ORDER_ATOMIC];
};

Dart['logic_null'] = function(block) {
Dart.forBlock['logic_null'] = function(block) {
// Null data type.
return ['null', Dart.ORDER_ATOMIC];
};

Dart['logic_ternary'] = function(block) {
Dart.forBlock['logic_ternary'] = function(block) {
// Ternary operator.
const value_if =
Dart.valueToCode(block, 'IF', Dart.ORDER_CONDITIONAL) || 'false';
Expand Down
12 changes: 6 additions & 6 deletions generators/dart/loops.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import * as stringUtils from '../../core/utils/string.js';
import {NameType} from '../../core/names.js';


Dart['controls_repeat_ext'] = function(block) {
Dart.forBlock['controls_repeat_ext'] = function(block) {
let repeats;
// Repeat n times.
if (block.getField('TIMES')) {
Expand All @@ -40,9 +40,9 @@ Dart['controls_repeat_ext'] = function(block) {
return code;
};

Dart['controls_repeat'] = Dart['controls_repeat_ext'];
Dart.forBlock['controls_repeat'] = Dart.forBlock['controls_repeat_ext'];

Dart['controls_whileUntil'] = function(block) {
Dart.forBlock['controls_whileUntil'] = function(block) {
// Do while/until loop.
const until = block.getFieldValue('MODE') === 'UNTIL';
let argument0 =
Expand All @@ -57,7 +57,7 @@ Dart['controls_whileUntil'] = function(block) {
return 'while (' + argument0 + ') {\n' + branch + '}\n';
};

Dart['controls_for'] = function(block) {
Dart.forBlock['controls_for'] = function(block) {
// For loop.
const variable0 =
Dart.nameDB_.getName(block.getFieldValue('VAR'), NameType.VARIABLE);
Expand Down Expand Up @@ -117,7 +117,7 @@ Dart['controls_for'] = function(block) {
return code;
};

Dart['controls_forEach'] = function(block) {
Dart.forBlock['controls_forEach'] = function(block) {
// For each loop.
const variable0 =
Dart.nameDB_.getName(block.getFieldValue('VAR'), NameType.VARIABLE);
Expand All @@ -130,7 +130,7 @@ Dart['controls_forEach'] = function(block) {
return code;
};

Dart['controls_flow_statements'] = function(block) {
Dart.forBlock['controls_flow_statements'] = function(block) {
// Flow statements: continue, break.
let xfix = '';
if (Dart.STATEMENT_PREFIX) {
Expand Down
Loading

0 comments on commit f9c865b

Please sign in to comment.