diff --git a/generators/python.js b/generators/python.ts similarity index 79% rename from generators/python.js rename to generators/python.ts index 938da3b7e4f..e2345a776ba 100644 --- a/generators/python.js +++ b/generators/python.ts @@ -36,8 +36,18 @@ export const pythonGenerator = new PythonGenerator(); pythonGenerator.addReservedWords('math,random,Number'); // Install per-block-type generator functions: -Object.assign( - pythonGenerator.forBlock, - colour, lists, logic, loops, math, procedures, - text, variables, variablesDynamic -); +// Install per-block-type generator functions: +const generators: typeof pythonGenerator.forBlock = { + ...colour, + ...lists, + ...logic, + ...loops, + ...math, + ...procedures, + ...text, + ...variables, + ...variablesDynamic, +}; +for (const name in generators) { + pythonGenerator.forBlock[name] = generators[name]; +} diff --git a/generators/python/colour.js b/generators/python/colour.ts similarity index 58% rename from generators/python/colour.js rename to generators/python/colour.ts index 36cb13c9723..82f9dd37b9c 100644 --- a/generators/python/colour.js +++ b/generators/python/colour.ts @@ -10,41 +10,62 @@ // Former goog.module ID: Blockly.Python.colour +import type {Block} from '../../core/block.js'; +import type {PythonGenerator} from './python_generator.js'; import {Order} from './python_generator.js'; - -export function colour_picker(block, generator) { +export function colour_picker( + block: Block, + generator: PythonGenerator, +): [string, Order] { // Colour picker. const code = generator.quote_(block.getFieldValue('COLOUR')); return [code, Order.ATOMIC]; -}; +} -export function colour_random(block, generator) { +export function colour_random( + block: Block, + generator: PythonGenerator, +): [string, Order] { // Generate a random colour. - generator.definitions_['import_random'] = 'import random'; - const code = '\'#%06x\' % random.randint(0, 2**24 - 1)'; + // TODO(#7600): find better approach than casting to any to override + // CodeGenerator declaring .definitions protected. + (generator as AnyDuringMigration).definitions_['import_random'] = + 'import random'; + const code = "'#%06x' % random.randint(0, 2**24 - 1)"; return [code, Order.FUNCTION_CALL]; -}; +} -export function colour_rgb(block, generator) { +export function colour_rgb( + block: Block, + generator: PythonGenerator, +): [string, Order] { // Compose a colour from RGB components expressed as percentages. - const functionName = generator.provideFunction_('colour_rgb', ` + const functionName = generator.provideFunction_( + 'colour_rgb', + ` def ${generator.FUNCTION_NAME_PLACEHOLDER_}(r, g, b): r = round(min(100, max(0, r)) * 2.55) g = round(min(100, max(0, g)) * 2.55) b = round(min(100, max(0, b)) * 2.55) return '#%02x%02x%02x' % (r, g, b) -`); +`, + ); const r = generator.valueToCode(block, 'RED', Order.NONE) || 0; const g = generator.valueToCode(block, 'GREEN', Order.NONE) || 0; const b = generator.valueToCode(block, 'BLUE', Order.NONE) || 0; const code = functionName + '(' + r + ', ' + g + ', ' + b + ')'; return [code, Order.FUNCTION_CALL]; -}; +} -export function colour_blend(block, generator) { +export function colour_blend( + block: Block, + generator: PythonGenerator, +): [string, Order] { // Blend two colours together. - const functionName = generator.provideFunction_('colour_blend', ` + const functionName = generator.provideFunction_( + 'colour_blend', + ` def ${generator.FUNCTION_NAME_PLACEHOLDER_}(colour1, colour2, ratio): r1, r2 = int(colour1[1:3], 16), int(colour2[1:3], 16) g1, g2 = int(colour1[3:5], 16), int(colour2[3:5], 16) @@ -54,15 +75,14 @@ def ${generator.FUNCTION_NAME_PLACEHOLDER_}(colour1, colour2, ratio): g = round(g1 * (1 - ratio) + g2 * ratio) b = round(b1 * (1 - ratio) + b2 * ratio) return '#%02x%02x%02x' % (r, g, b) -`); +`, + ); const colour1 = - generator.valueToCode(block, 'COLOUR1', Order.NONE) - || '\'#000000\''; + generator.valueToCode(block, 'COLOUR1', Order.NONE) || "'#000000'"; const colour2 = - generator.valueToCode(block, 'COLOUR2', Order.NONE) - || '\'#000000\''; + generator.valueToCode(block, 'COLOUR2', Order.NONE) || "'#000000'"; const ratio = generator.valueToCode(block, 'RATIO', Order.NONE) || 0; const code = - functionName + '(' + colour1 + ', ' + colour2 + ', ' + ratio + ')'; + functionName + '(' + colour1 + ', ' + colour2 + ', ' + ratio + ')'; return [code, Order.FUNCTION_CALL]; -}; +} diff --git a/generators/python/lists.js b/generators/python/lists.ts similarity index 75% rename from generators/python/lists.js rename to generators/python/lists.ts index 9f270b78e6c..02bd56ee560 100644 --- a/generators/python/lists.js +++ b/generators/python/lists.ts @@ -11,49 +11,69 @@ // Former goog.module ID: Blockly.Python.lists import * as stringUtils from '../../core/utils/string.js'; +import type {Block} from '../../core/block.js'; +import type {CreateWithBlock} from '../../blocks/lists.js'; import {NameType} from '../../core/names.js'; import {Order} from './python_generator.js'; +import type {PythonGenerator} from './python_generator.js'; - -export function lists_create_empty(block, generator) { +export function lists_create_empty( + block: Block, + generator: PythonGenerator, +): [string, Order] { // Create an empty list. return ['[]', Order.ATOMIC]; -}; +} -export function lists_create_with(block, generator) { +export function lists_create_with( + block: Block, + generator: PythonGenerator, +): [string, Order] { + const createWithBlock = block as CreateWithBlock; // 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++) { - elements[i] = - generator.valueToCode(block, 'ADD' + i, Order.NONE) || 'None'; + const elements = new Array(createWithBlock.itemCount_); + for (let i = 0; i < createWithBlock.itemCount_; i++) { + elements[i] = generator.valueToCode(block, 'ADD' + i, Order.NONE) || 'None'; } const code = '[' + elements.join(', ') + ']'; return [code, Order.ATOMIC]; -}; +} -export function lists_repeat(block, generator) { +export function lists_repeat( + block: Block, + generator: PythonGenerator, +): [string, Order] { // Create a list with one element repeated. const item = generator.valueToCode(block, 'ITEM', Order.NONE) || 'None'; const times = - generator.valueToCode(block, 'NUM', Order.MULTIPLICATIVE) || '0'; + generator.valueToCode(block, 'NUM', Order.MULTIPLICATIVE) || '0'; const code = '[' + item + '] * ' + times; return [code, Order.MULTIPLICATIVE]; -}; +} -export function lists_length(block, generator) { +export function lists_length( + block: Block, + generator: PythonGenerator, +): [string, Order] { // String or array length. const list = generator.valueToCode(block, 'VALUE', Order.NONE) || '[]'; return ['len(' + list + ')', Order.FUNCTION_CALL]; -}; +} -export function lists_isEmpty(block, generator) { +export function lists_isEmpty( + block: Block, + generator: PythonGenerator, +): [string, Order] { // Is the string null or array empty? const list = generator.valueToCode(block, 'VALUE', Order.NONE) || '[]'; const code = 'not len(' + list + ')'; return [code, Order.LOGICAL_NOT]; -}; +} -export function lists_indexOf(block, generator) { +export function lists_indexOf( + block: Block, + generator: PythonGenerator, +): [string, Order] { // Find an item in the list. const item = generator.valueToCode(block, 'FIND', Order.NONE) || '[]'; const list = generator.valueToCode(block, 'VALUE', Order.NONE) || "''"; @@ -69,31 +89,39 @@ export function lists_indexOf(block, generator) { let functionName; if (block.getFieldValue('END') === 'FIRST') { - functionName = generator.provideFunction_('first_index', ` + functionName = generator.provideFunction_( + 'first_index', + ` def ${generator.FUNCTION_NAME_PLACEHOLDER_}(my_list, elem): try: index = my_list.index(elem)${firstIndexAdjustment} except: index =${errorIndex} return index -`); +`, + ); } else { - functionName = generator.provideFunction_('last_index', ` + functionName = generator.provideFunction_( + 'last_index', + ` def ${generator.FUNCTION_NAME_PLACEHOLDER_}(my_list, elem): try: index = len(my_list) - my_list[::-1].index(elem)${lastIndexAdjustment} except: index =${errorIndex} return index -`); +`, + ); } const code = functionName + '(' + list + ', ' + item + ')'; return [code, Order.FUNCTION_CALL]; -}; +} -export function lists_getIndex(block, generator) { +export function lists_getIndex( + block: Block, + generator: PythonGenerator, +): [string, Order] | string { // Get element at index. // Note: Until January 2013 this block did not have MODE or WHERE inputs. const mode = block.getFieldValue('MODE') || 'GET'; const where = block.getFieldValue('WHERE') || 'FROM_START'; - const listOrder = - (where === 'RANDOM') ? Order.NONE : Order.MEMBER; + const listOrder = where === 'RANDOM' ? Order.NONE : Order.MEMBER; const list = generator.valueToCode(block, 'VALUE', listOrder) || '[]'; switch (where) { @@ -146,17 +174,20 @@ export function lists_getIndex(block, generator) { break; } case 'RANDOM': - generator.definitions_['import_random'] = 'import random'; + (generator as AnyDuringMigration).definitions_['import_random'] = + 'import random'; if (mode === 'GET') { const code = 'random.choice(' + list + ')'; return [code, Order.FUNCTION_CALL]; } else { - const functionName = - generator.provideFunction_('lists_remove_random_item', ` + const functionName = generator.provideFunction_( + 'lists_remove_random_item', + ` def ${generator.FUNCTION_NAME_PLACEHOLDER_}(myList): x = int(random.random() * len(myList)) return myList.pop(x) -`); +`, + ); const code = functionName + '(' + list + ')'; if (mode === 'GET_REMOVE') { return [code, Order.FUNCTION_CALL]; @@ -167,9 +198,9 @@ def ${generator.FUNCTION_NAME_PLACEHOLDER_}(myList): break; } throw Error('Unhandled combination (lists_getIndex).'); -}; +} -export function lists_setIndex(block, generator) { +export function lists_setIndex(block: Block, generator: PythonGenerator) { // Set element at index. // Note: Until February 2013 this block did not have MODE or WHERE inputs. let list = generator.valueToCode(block, 'LIST', Order.MEMBER) || '[]'; @@ -182,8 +213,10 @@ export function lists_setIndex(block, generator) { if (list.match(/^\w+$/)) { return ''; } - const listVar = - generator.nameDB_.getDistinctName('tmp_list', NameType.VARIABLE); + const listVar = generator.nameDB_!.getDistinctName( + 'tmp_list', + NameType.VARIABLE, + ); const code = listVar + ' = ' + list + '\n'; list = listVar; return code; @@ -223,10 +256,13 @@ export function lists_setIndex(block, generator) { break; } case 'RANDOM': { - generator.definitions_['import_random'] = 'import random'; + (generator as AnyDuringMigration).definitions_['import_random'] = + 'import random'; let code = cacheList(); - const xVar = - generator.nameDB_.getDistinctName('tmp_x', NameType.VARIABLE); + const xVar = generator.nameDB_!.getDistinctName( + 'tmp_x', + NameType.VARIABLE, + ); code += xVar + ' = int(random.random() * len(' + list + '))\n'; if (mode === 'SET') { code += list + '[' + xVar + '] = ' + value + '\n'; @@ -239,9 +275,12 @@ export function lists_setIndex(block, generator) { } } throw Error('Unhandled combination (lists_setIndex).'); -}; +} -export function lists_getSublist(block, generator) { +export function lists_getSublist( + block: Block, + generator: PythonGenerator, +): [string, Order] { // Get sublist. const list = generator.valueToCode(block, 'LIST', Order.MEMBER) || '[]'; const where1 = block.getFieldValue('WHERE1'); @@ -274,7 +313,8 @@ export function lists_getSublist(block, generator) { // Ensure that if the result calculated is 0 that sub-sequence will // include all elements as expected. if (!stringUtils.isNumber(String(at2))) { - generator.definitions_['import_sys'] = 'import sys'; + (generator as AnyDuringMigration).definitions_['import_sys'] = + 'import sys'; at2 += ' or sys.maxsize'; } else if (at2 === 0) { at2 = ''; @@ -288,14 +328,19 @@ export function lists_getSublist(block, generator) { } const code = list + '[' + at1 + ' : ' + at2 + ']'; return [code, Order.MEMBER]; -}; +} -export function lists_sort(block, generator) { +export function lists_sort( + block: Block, + generator: PythonGenerator, +): [string, Order] { // Block for sorting a list. - const list = (generator.valueToCode(block, 'LIST', Order.NONE) || '[]'); + const list = generator.valueToCode(block, 'LIST', Order.NONE) || '[]'; const type = block.getFieldValue('TYPE'); const reverse = block.getFieldValue('DIRECTION') === '1' ? 'False' : 'True'; - const sortFunctionName = generator.provideFunction_('lists_sort', ` + const sortFunctionName = generator.provideFunction_( + 'lists_sort', + ` def ${generator.FUNCTION_NAME_PLACEHOLDER_}(my_list, type, reverse): def try_float(s): try: @@ -310,37 +355,44 @@ def ${generator.FUNCTION_NAME_PLACEHOLDER_}(my_list, type, reverse): key_func = key_funcs[type] list_cpy = list(my_list) return sorted(list_cpy, key=key_func, reverse=reverse) -`); +`, + ); const code = - sortFunctionName + '(' + list + ', "' + type + '", ' + reverse + ')'; + sortFunctionName + '(' + list + ', "' + type + '", ' + reverse + ')'; return [code, Order.FUNCTION_CALL]; -}; +} -export function lists_split(block, generator) { +export function lists_split( + block: Block, + generator: PythonGenerator, +): [string, Order] { // Block for splitting text into a list, or joining a list into text. const mode = block.getFieldValue('MODE'); let code; if (mode === 'SPLIT') { const value_input = - generator.valueToCode(block, 'INPUT', Order.MEMBER) || "''"; + generator.valueToCode(block, 'INPUT', Order.MEMBER) || "''"; const value_delim = generator.valueToCode(block, 'DELIM', Order.NONE); code = value_input + '.split(' + value_delim + ')'; } else if (mode === 'JOIN') { const value_input = - generator.valueToCode(block, 'INPUT', Order.NONE) || '[]'; + generator.valueToCode(block, 'INPUT', Order.NONE) || '[]'; const value_delim = - generator.valueToCode(block, 'DELIM', Order.MEMBER) || "''"; + generator.valueToCode(block, 'DELIM', Order.MEMBER) || "''"; code = value_delim + '.join(' + value_input + ')'; } else { throw Error('Unknown mode: ' + mode); } return [code, Order.FUNCTION_CALL]; -}; +} -export function lists_reverse(block, generator) { +export function lists_reverse( + block: Block, + generator: PythonGenerator, +): [string, Order] { // Block for reversing a list. const list = generator.valueToCode(block, 'LIST', Order.NONE) || '[]'; const code = 'list(reversed(' + list + '))'; return [code, Order.FUNCTION_CALL]; -}; +} diff --git a/generators/python/logic.js b/generators/python/logic.ts similarity index 50% rename from generators/python/logic.js rename to generators/python/logic.ts index 33729b86066..e562c679424 100644 --- a/generators/python/logic.js +++ b/generators/python/logic.ts @@ -10,69 +10,80 @@ // Former goog.module ID: Blockly.Python.logic +import type {Block} from '../../core/block.js'; +import type {PythonGenerator} from './python_generator.js'; import {Order} from './python_generator.js'; - -export function controls_if(block, generator) { +export function controls_if(block: Block, generator: PythonGenerator) { // If/elseif/else condition. let n = 0; - let code = '', branchCode, conditionCode; + let code = '', + branchCode, + conditionCode; if (generator.STATEMENT_PREFIX) { // Automatic prefix insertion is switched off for this block. Add manually. code += generator.injectId(generator.STATEMENT_PREFIX, block); } do { conditionCode = - generator.valueToCode(block, 'IF' + n, Order.NONE) || 'False'; - branchCode = - generator.statementToCode(block, 'DO' + n) || - generator.PASS; + generator.valueToCode(block, 'IF' + n, Order.NONE) || 'False'; + branchCode = generator.statementToCode(block, 'DO' + n) || generator.PASS; if (generator.STATEMENT_SUFFIX) { branchCode = - generator.prefixLines( - generator.injectId(generator.STATEMENT_SUFFIX, block), - generator.INDENT) + - branchCode; + generator.prefixLines( + generator.injectId(generator.STATEMENT_SUFFIX, block), + generator.INDENT, + ) + branchCode; } code += (n === 0 ? 'if ' : 'elif ') + conditionCode + ':\n' + branchCode; n++; } while (block.getInput('IF' + n)); if (block.getInput('ELSE') || generator.STATEMENT_SUFFIX) { - branchCode = - generator.statementToCode(block, 'ELSE') || generator.PASS; + branchCode = generator.statementToCode(block, 'ELSE') || generator.PASS; if (generator.STATEMENT_SUFFIX) { branchCode = - generator.prefixLines( - generator.injectId( - generator.STATEMENT_SUFFIX, block), - generator.INDENT) + - branchCode; + generator.prefixLines( + generator.injectId(generator.STATEMENT_SUFFIX, block), + generator.INDENT, + ) + branchCode; } code += 'else:\n' + branchCode; } return code; -}; +} export const controls_ifelse = controls_if; -export function logic_compare(block, generator) { +export function logic_compare( + block: Block, + generator: PythonGenerator, +): [string, Order] { // Comparison operator. - const OPERATORS = - {'EQ': '==', 'NEQ': '!=', 'LT': '<', 'LTE': '<=', 'GT': '>', 'GTE': '>='}; - const operator = OPERATORS[block.getFieldValue('OP')]; + const OPERATORS = { + 'EQ': '==', + 'NEQ': '!=', + 'LT': '<', + 'LTE': '<=', + 'GT': '>', + 'GTE': '>=', + }; + type OperatorOption = keyof typeof OPERATORS; + const operator = OPERATORS[block.getFieldValue('OP') as OperatorOption]; const order = Order.RELATIONAL; const argument0 = generator.valueToCode(block, 'A', order) || '0'; const argument1 = generator.valueToCode(block, 'B', order) || '0'; const code = argument0 + ' ' + operator + ' ' + argument1; return [code, order]; -}; +} -export function logic_operation(block, generator) { +export function logic_operation( + block: Block, + generator: PythonGenerator, +): [string, Order] { // Operations 'and', 'or'. - const operator = (block.getFieldValue('OP') === 'AND') ? 'and' : 'or'; - const order = - (operator === 'and') ? Order.LOGICAL_AND : Order.LOGICAL_OR; + const operator = block.getFieldValue('OP') === 'AND' ? 'and' : 'or'; + const order = operator === 'and' ? Order.LOGICAL_AND : Order.LOGICAL_OR; let argument0 = generator.valueToCode(block, 'A', order); let argument1 = generator.valueToCode(block, 'B', order); if (!argument0 && !argument1) { @@ -81,7 +92,7 @@ export function logic_operation(block, generator) { argument1 = 'False'; } else { // Single missing arguments have no effect on the return value. - const defaultArgument = (operator === 'and') ? 'True' : 'False'; + const defaultArgument = operator === 'and' ? 'True' : 'False'; if (!argument0) { argument0 = defaultArgument; } @@ -91,35 +102,47 @@ export function logic_operation(block, generator) { } const code = argument0 + ' ' + operator + ' ' + argument1; return [code, order]; -}; +} -export function logic_negate(block, generator) { +export function logic_negate( + block: Block, + generator: PythonGenerator, +): [string, Order] { // Negation. const argument0 = - generator.valueToCode(block, 'BOOL', Order.LOGICAL_NOT) || 'True'; + generator.valueToCode(block, 'BOOL', Order.LOGICAL_NOT) || 'True'; const code = 'not ' + argument0; return [code, Order.LOGICAL_NOT]; -}; +} -export function logic_boolean(block, generator) { +export function logic_boolean( + block: Block, + generator: PythonGenerator, +): [string, Order] { // Boolean values true and false. - const code = (block.getFieldValue('BOOL') === 'TRUE') ? 'True' : 'False'; + const code = block.getFieldValue('BOOL') === 'TRUE' ? 'True' : 'False'; return [code, Order.ATOMIC]; -}; +} -export function logic_null(block, generator) { +export function logic_null( + block: Block, + generator: PythonGenerator, +): [string, Order] { // Null data type. return ['None', Order.ATOMIC]; -}; +} -export function logic_ternary(block, generator) { +export function logic_ternary( + block: Block, + generator: PythonGenerator, +): [string, Order] { // Ternary operator. const value_if = - generator.valueToCode(block, 'IF', Order.CONDITIONAL) || 'False'; + generator.valueToCode(block, 'IF', Order.CONDITIONAL) || 'False'; const value_then = - generator.valueToCode(block, 'THEN', Order.CONDITIONAL) || 'None'; + generator.valueToCode(block, 'THEN', Order.CONDITIONAL) || 'None'; const value_else = - generator.valueToCode(block, 'ELSE', Order.CONDITIONAL) || 'None'; + generator.valueToCode(block, 'ELSE', Order.CONDITIONAL) || 'None'; const code = value_then + ' if ' + value_if + ' else ' + value_else; return [code, Order.CONDITIONAL]; -}; +} diff --git a/generators/python/loops.js b/generators/python/loops.ts similarity index 68% rename from generators/python/loops.js rename to generators/python/loops.ts index 824d4f2bf1c..1d2d3a86e34 100644 --- a/generators/python/loops.js +++ b/generators/python/loops.ts @@ -11,11 +11,13 @@ // Former goog.module ID: Blockly.Python.loops import * as stringUtils from '../../core/utils/string.js'; +import type {Block} from '../../core/block.js'; +import type {ControlFlowInLoopBlock} from '../../blocks/loops.js'; +import type {PythonGenerator} from './python_generator.js'; import {NameType} from '../../core/names.js'; import {Order} from './python_generator.js'; - -export function controls_repeat_ext(block, generator) { +export function controls_repeat_ext(block: Block, generator: PythonGenerator) { // Repeat n times. let repeats; if (block.getField('TIMES')) { @@ -32,36 +34,42 @@ export function controls_repeat_ext(block, generator) { } let branch = generator.statementToCode(block, 'DO'); branch = generator.addLoopTrap(branch, block) || generator.PASS; - const loopVar = - generator.nameDB_.getDistinctName('count', NameType.VARIABLE); + const loopVar = generator.nameDB_!.getDistinctName( + 'count', + NameType.VARIABLE, + ); const code = 'for ' + loopVar + ' in range(' + repeats + '):\n' + branch; return code; -}; +} export const controls_repeat = controls_repeat_ext; -export function controls_whileUntil(block, generator) { +export function controls_whileUntil(block: Block, generator: PythonGenerator) { // Do while/until loop. const until = block.getFieldValue('MODE') === 'UNTIL'; - let argument0 = generator.valueToCode( - block, 'BOOL', - until ? Order.LOGICAL_NOT : Order.NONE) || - 'False'; + let argument0 = + generator.valueToCode( + block, + 'BOOL', + until ? Order.LOGICAL_NOT : Order.NONE, + ) || 'False'; let branch = generator.statementToCode(block, 'DO'); branch = generator.addLoopTrap(branch, block) || generator.PASS; if (until) { argument0 = 'not ' + argument0; } return 'while ' + argument0 + ':\n' + branch; -}; +} -export function controls_for(block, generator) { +export function controls_for(block: Block, generator: PythonGenerator) { // For loop. - const variable0 = - generator.getVariableName(block.getFieldValue('VAR')); - let argument0 = generator.valueToCode(block, 'FROM', Order.NONE) || '0'; - let argument1 = generator.valueToCode(block, 'TO', Order.NONE) || '0'; - let increment = generator.valueToCode(block, 'BY', Order.NONE) || '1'; + const variable0 = generator.getVariableName(block.getFieldValue('VAR')); + let argument0: string | number = + generator.valueToCode(block, 'FROM', Order.NONE) || '0'; + let argument1: string | number = + generator.valueToCode(block, 'TO', Order.NONE) || '0'; + let increment: string | number = + generator.valueToCode(block, 'BY', Order.NONE) || '1'; let branch = generator.statementToCode(block, 'DO'); branch = generator.addLoopTrap(branch, block) || generator.PASS; @@ -69,31 +77,64 @@ export function controls_for(block, generator) { let range; // Helper functions. - const defineUpRange = function() { - return generator.provideFunction_('upRange', ` + const defineUpRange = function () { + return generator.provideFunction_( + 'upRange', + ` def ${generator.FUNCTION_NAME_PLACEHOLDER_}(start, stop, step): while start <= stop: yield start start += abs(step) -`); +`, + ); }; - const defineDownRange = function() { - return generator.provideFunction_('downRange', ` + const defineDownRange = function () { + return generator.provideFunction_( + 'downRange', + ` def ${generator.FUNCTION_NAME_PLACEHOLDER_}(start, stop, step): while start >= stop: yield start start -= abs(step) -`); +`, + ); }; // Arguments are legal generator code (numbers or strings returned by scrub()). - const generateUpDownRange = function(start, end, inc) { - return '(' + start + ' <= ' + end + ') and ' + defineUpRange() + '(' + - start + ', ' + end + ', ' + inc + ') or ' + defineDownRange() + '(' + - start + ', ' + end + ', ' + inc + ')'; + const generateUpDownRange = function ( + start: string, + end: string, + inc: string, + ) { + return ( + '(' + + start + + ' <= ' + + end + + ') and ' + + defineUpRange() + + '(' + + start + + ', ' + + end + + ', ' + + inc + + ') or ' + + defineDownRange() + + '(' + + start + + ', ' + + end + + ', ' + + inc + + ')' + ); }; - if (stringUtils.isNumber(argument0) && stringUtils.isNumber(argument1) && - stringUtils.isNumber(increment)) { + if ( + stringUtils.isNumber(argument0) && + stringUtils.isNumber(argument1) && + stringUtils.isNumber(increment) + ) { // All parameters are simple numbers. argument0 = Number(argument0); argument1 = Number(argument1); @@ -130,14 +171,16 @@ def ${generator.FUNCTION_NAME_PLACEHOLDER_}(start, stop, step): } } else { // Cache non-trivial values to variables to prevent repeated look-ups. - const scrub = function(arg, suffix) { + const scrub = function (arg: string, suffix: string) { if (stringUtils.isNumber(arg)) { // Simple number. - arg = Number(arg); + arg = String(Number(arg)); } else if (!arg.match(/^\w+$/)) { // Not a variable, it's complicated. - const varName = generator.nameDB_.getDistinctName( - variable0 + suffix, NameType.VARIABLE); + const varName = generator.nameDB_!.getDistinctName( + variable0 + suffix, + NameType.VARIABLE, + ); code += varName + ' = ' + arg + '\n'; arg = varName; } @@ -161,21 +204,23 @@ def ${generator.FUNCTION_NAME_PLACEHOLDER_}(start, stop, step): } code += 'for ' + variable0 + ' in ' + range + ':\n' + branch; return code; -}; +} -export function controls_forEach(block, generator) { +export function controls_forEach(block: Block, generator: PythonGenerator) { // For each loop. - const variable0 = - generator.getVariableName(block.getFieldValue('VAR')); + const variable0 = generator.getVariableName(block.getFieldValue('VAR')); const argument0 = - generator.valueToCode(block, 'LIST', Order.RELATIONAL) || '[]'; + generator.valueToCode(block, 'LIST', Order.RELATIONAL) || '[]'; let branch = generator.statementToCode(block, 'DO'); branch = generator.addLoopTrap(branch, block) || generator.PASS; const code = 'for ' + variable0 + ' in ' + argument0 + ':\n' + branch; return code; -}; +} -export function controls_flow_statements(block, generator) { +export function controls_flow_statements( + block: Block, + generator: PythonGenerator, +) { // Flow statements: continue, break. let xfix = ''; if (generator.STATEMENT_PREFIX) { @@ -188,7 +233,7 @@ export function controls_flow_statements(block, generator) { xfix += generator.injectId(generator.STATEMENT_SUFFIX, block); } if (generator.STATEMENT_PREFIX) { - const loop = block.getSurroundLoop(); + const loop = (block as ControlFlowInLoopBlock).getSurroundLoop(); if (loop && !loop.suppressPrefixSuffix) { // Inject loop's statement prefix here since the regular one at the end // of the loop will not get executed if 'continue' is triggered. @@ -203,4 +248,4 @@ export function controls_flow_statements(block, generator) { return xfix + 'continue\n'; } throw Error('Unknown flow statement.'); -}; +} diff --git a/generators/python/math.js b/generators/python/math.ts similarity index 62% rename from generators/python/math.js rename to generators/python/math.ts index 0ba35118778..d458b0e8758 100644 --- a/generators/python/math.js +++ b/generators/python/math.ts @@ -10,38 +10,42 @@ // Former goog.module ID: Blockly.Python.math +import type {Block} from '../../core/block.js'; +import type {PythonGenerator} from './python_generator.js'; import {Order} from './python_generator.js'; - // If any new block imports any library, add that library name here. // RESERVED WORDS: 'math,random,Number' -export function math_number(block, generator) { +export function math_number( + block: Block, + generator: PythonGenerator, +): [string, Order] { // Numeric value. - let code = Number(block.getFieldValue('NUM')); - let order; - if (code === Infinity) { - code = 'float("inf")'; - order = Order.FUNCTION_CALL; - } else if (code === -Infinity) { - code = '-float("inf")'; - order = Order.UNARY_SIGN; + let number = Number(block.getFieldValue('NUM')); + if (number === Infinity) { + return ['float("inf")', Order.FUNCTION_CALL]; + } else if (number === -Infinity) { + return ['-float("inf")', Order.UNARY_SIGN]; } else { - order = code < 0 ? Order.UNARY_SIGN : Order.ATOMIC; + return [String(number), number < 0 ? Order.UNARY_SIGN : Order.ATOMIC]; } - return [code, order]; -}; +} -export function math_arithmetic(block, generator) { +export function math_arithmetic( + block: Block, + generator: PythonGenerator, +): [string, Order] { // Basic arithmetic operators, and power. - const OPERATORS = { + const OPERATORS: Record = { 'ADD': [' + ', Order.ADDITIVE], 'MINUS': [' - ', Order.ADDITIVE], 'MULTIPLY': [' * ', Order.MULTIPLICATIVE], 'DIVIDE': [' / ', Order.MULTIPLICATIVE], 'POWER': [' ** ', Order.EXPONENTIATION], }; - const tuple = OPERATORS[block.getFieldValue('OP')]; + type OperatorOption = keyof typeof OPERATORS; + const tuple = OPERATORS[block.getFieldValue('OP') as OperatorOption]; const operator = tuple[0]; const order = tuple[1]; const argument0 = generator.valueToCode(block, 'A', order) || '0'; @@ -53,9 +57,12 @@ export function math_arithmetic(block, generator) { // guarantee identical results in all languages. To do otherwise would // require every operator to be wrapped in a function call. This would kill // legibility of the generated code. -}; +} -export function math_single(block, generator) { +export function math_single( + block: Block, + generator: PythonGenerator, +): [string, Order] { // Math operators with single operand. const operator = block.getFieldValue('OP'); let code; @@ -65,10 +72,11 @@ export function math_single(block, generator) { code = generator.valueToCode(block, 'NUM', Order.UNARY_SIGN) || '0'; return ['-' + code, Order.UNARY_SIGN]; } - generator.definitions_['import_math'] = 'import math'; + // TODO(#7600): find better approach than casting to any to override + // CodeGenerator declaring .definitions protected (here and below). + (generator as AnyDuringMigration).definitions_['import_math'] = 'import math'; if (operator === 'SIN' || operator === 'COS' || operator === 'TAN') { - arg = - generator.valueToCode(block, 'NUM', Order.MULTIPLICATIVE) || '0'; + arg = generator.valueToCode(block, 'NUM', Order.MULTIPLICATIVE) || '0'; } else { arg = generator.valueToCode(block, 'NUM', Order.NONE) || '0'; } @@ -131,50 +139,61 @@ export function math_single(block, generator) { throw Error('Unknown math operator: ' + operator); } return [code, Order.MULTIPLICATIVE]; -}; +} -export function math_constant(block, generator) { +export function math_constant( + block: Block, + generator: PythonGenerator, +): [string, Order] { // Constants: PI, E, the Golden Ratio, sqrt(2), 1/sqrt(2), INFINITY. - const CONSTANTS = { + const CONSTANTS: Record = { 'PI': ['math.pi', Order.MEMBER], 'E': ['math.e', Order.MEMBER], 'GOLDEN_RATIO': ['(1 + math.sqrt(5)) / 2', Order.MULTIPLICATIVE], 'SQRT2': ['math.sqrt(2)', Order.MEMBER], 'SQRT1_2': ['math.sqrt(1.0 / 2)', Order.MEMBER], - 'INFINITY': ['float(\'inf\')', Order.ATOMIC], + 'INFINITY': ["float('inf')", Order.ATOMIC], }; - const constant = block.getFieldValue('CONSTANT'); + type ConstantOption = keyof typeof CONSTANTS; + const constant = block.getFieldValue('CONSTANT') as ConstantOption; if (constant !== 'INFINITY') { - generator.definitions_['import_math'] = 'import math'; + (generator as AnyDuringMigration).definitions_['import_math'] = + 'import math'; } return CONSTANTS[constant]; -}; +} -export function math_number_property(block, generator) { - // Check if a number is even, odd, prime, whole, positive, or negative - // or if it is divisible by certain number. Returns true or false. - const PROPERTIES = { +export function math_number_property( + block: Block, + generator: PythonGenerator, +): [string, Order] { + // Check if a number is even, odd, prime, whole, positive, or negative + // or if it is divisible by certain number. Returns true or false. + const PROPERTIES: Record = { 'EVEN': [' % 2 == 0', Order.MULTIPLICATIVE, Order.RELATIONAL], 'ODD': [' % 2 == 1', Order.MULTIPLICATIVE, Order.RELATIONAL], - 'WHOLE': [' % 1 == 0', Order.MULTIPLICATIVE, - Order.RELATIONAL], + 'WHOLE': [' % 1 == 0', Order.MULTIPLICATIVE, Order.RELATIONAL], 'POSITIVE': [' > 0', Order.RELATIONAL, Order.RELATIONAL], 'NEGATIVE': [' < 0', Order.RELATIONAL, Order.RELATIONAL], - 'DIVISIBLE_BY': [null, Order.MULTIPLICATIVE, - Order.RELATIONAL], + 'DIVISIBLE_BY': [null, Order.MULTIPLICATIVE, Order.RELATIONAL], 'PRIME': [null, Order.NONE, Order.FUNCTION_CALL], - } - const dropdownProperty = block.getFieldValue('PROPERTY'); + }; + type PropertyOption = keyof typeof PROPERTIES; + const dropdownProperty = block.getFieldValue('PROPERTY') as PropertyOption; const [suffix, inputOrder, outputOrder] = PROPERTIES[dropdownProperty]; - const numberToCheck = generator.valueToCode(block, 'NUMBER_TO_CHECK', - inputOrder) || '0'; + const numberToCheck = + generator.valueToCode(block, 'NUMBER_TO_CHECK', inputOrder) || '0'; let code; if (dropdownProperty === 'PRIME') { // Prime is a special case as it is not a one-liner test. - generator.definitions_['import_math'] = 'import math'; - generator.definitions_['from_numbers_import_Number'] = - 'from numbers import Number'; - const functionName = generator.provideFunction_('math_isPrime', ` + (generator as AnyDuringMigration).definitions_['import_math'] = + 'import math'; + (generator as AnyDuringMigration).definitions_[ + 'from_numbers_import_Number' + ] = 'from numbers import Number'; + const functionName = generator.provideFunction_( + 'math_isPrime', + ` def ${generator.FUNCTION_NAME_PLACEHOLDER_}(n): # https://en.wikipedia.org/wiki/Primality_test#Naive_methods # If n is not a number but a string, try parsing it. @@ -193,11 +212,12 @@ def ${generator.FUNCTION_NAME_PLACEHOLDER_}(n): if n % (x - 1) == 0 or n % (x + 1) == 0: return False return True -`); - code = functionName + '(' + numberToCheck + ')'; +`, + ); + code = functionName + '(' + numberToCheck + ')'; } else if (dropdownProperty === 'DIVISIBLE_BY') { - const divisor = generator.valueToCode(block, 'DIVISOR', - Order.MULTIPLICATIVE) || '0'; + const divisor = + generator.valueToCode(block, 'DIVISOR', Order.MULTIPLICATIVE) || '0'; // If 'divisor' is some code that evals to 0, generator will raise an error. if (divisor === '0') { return ['False', Order.ATOMIC]; @@ -205,27 +225,38 @@ def ${generator.FUNCTION_NAME_PLACEHOLDER_}(n): code = numberToCheck + ' % ' + divisor + ' == 0'; } else { code = numberToCheck + suffix; - }; + } return [code, outputOrder]; -}; +} -export function math_change(block, generator) { +export function math_change(block: Block, generator: PythonGenerator) { // Add to a variable in place. - generator.definitions_['from_numbers_import_Number'] = - 'from numbers import Number'; + (generator as AnyDuringMigration).definitions_['from_numbers_import_Number'] = + 'from numbers import Number'; const argument0 = - generator.valueToCode(block, 'DELTA', Order.ADDITIVE) || '0'; + generator.valueToCode(block, 'DELTA', Order.ADDITIVE) || '0'; const varName = generator.getVariableName(block.getFieldValue('VAR')); - return varName + ' = (' + varName + ' if isinstance(' + varName + - ', Number) else 0) + ' + argument0 + '\n'; -}; + return ( + varName + + ' = (' + + varName + + ' if isinstance(' + + varName + + ', Number) else 0) + ' + + argument0 + + '\n' + ); +} // Rounding functions have a single operand. export const math_round = math_single; // Trigonometry functions have a single operand. export const math_trig = math_single; -export function math_on_list(block, generator) { +export function math_on_list( + block: Block, + generator: PythonGenerator, +): [string, Order] { // Math functions for lists. const func = block.getFieldValue('OP'); const list = generator.valueToCode(block, 'LIST', Order.NONE) || '[]'; @@ -241,25 +272,32 @@ export function math_on_list(block, generator) { code = 'max(' + list + ')'; break; case 'AVERAGE': { - generator.definitions_['from_numbers_import_Number'] = - 'from numbers import Number'; + (generator as AnyDuringMigration).definitions_[ + 'from_numbers_import_Number' + ] = 'from numbers import Number'; // This operation excludes null and values that aren't int or float: // math_mean([null, null, "aString", 1, 9]) -> 5.0 - const functionName = generator.provideFunction_('math_mean', ` + const functionName = generator.provideFunction_( + 'math_mean', + ` def ${generator.FUNCTION_NAME_PLACEHOLDER_}(myList): localList = [e for e in myList if isinstance(e, Number)] if not localList: return return float(sum(localList)) / len(localList) -`); +`, + ); code = functionName + '(' + list + ')'; break; } case 'MEDIAN': { - generator.definitions_['from_numbers_import_Number'] = - 'from numbers import Number'; + (generator as AnyDuringMigration).definitions_[ + 'from_numbers_import_Number' + ] = 'from numbers import Number'; // This operation excludes null values: // math_median([null, null, 1, 3]) -> 2.0 - const functionName = generator.provideFunction_( 'math_median', ` + const functionName = generator.provideFunction_( + 'math_median', + ` def ${generator.FUNCTION_NAME_PLACEHOLDER_}(myList): localList = sorted([e for e in myList if isinstance(e, Number)]) if not localList: return @@ -267,7 +305,8 @@ def ${generator.FUNCTION_NAME_PLACEHOLDER_}(myList): return (localList[len(localList) // 2 - 1] + localList[len(localList) // 2]) / 2.0 else: return localList[(len(localList) - 1) // 2] -`); +`, + ); code = functionName + '(' + list + ')'; break; } @@ -275,7 +314,9 @@ def ${generator.FUNCTION_NAME_PLACEHOLDER_}(myList): // As a list of numbers can contain more than one mode, // the returned result is provided as an array. // Mode of [3, 'x', 'x', 1, 1, 2, '3'] -> ['x', 1] - const functionName = generator.provideFunction_('math_modes', ` + const functionName = generator.provideFunction_( + 'math_modes', + ` def ${generator.FUNCTION_NAME_PLACEHOLDER_}(some_list): modes = [] # Using a lists of [item, count] to keep count rather than dict @@ -295,84 +336,99 @@ def ${generator.FUNCTION_NAME_PLACEHOLDER_}(some_list): if item_count == maxCount: modes.append(counted_item) return modes -`); +`, + ); code = functionName + '(' + list + ')'; break; } case 'STD_DEV': { - generator.definitions_['import_math'] = 'import math'; - const functionName = - generator.provideFunction_('math_standard_deviation', ` + (generator as AnyDuringMigration).definitions_['import_math'] = + 'import math'; + const functionName = generator.provideFunction_( + 'math_standard_deviation', + ` def ${generator.FUNCTION_NAME_PLACEHOLDER_}(numbers): n = len(numbers) if n == 0: return mean = float(sum(numbers)) / n variance = sum((x - mean) ** 2 for x in numbers) / n return math.sqrt(variance) -`); +`, + ); code = functionName + '(' + list + ')'; break; } case 'RANDOM': - generator.definitions_['import_random'] = 'import random'; + (generator as AnyDuringMigration).definitions_['import_random'] = + 'import random'; code = 'random.choice(' + list + ')'; break; default: throw Error('Unknown operator: ' + func); } return [code, Order.FUNCTION_CALL]; -}; +} -export function math_modulo(block, generator) { +export function math_modulo( + block: Block, + generator: PythonGenerator, +): [string, Order] { // Remainder computation. const argument0 = - generator.valueToCode(block, 'DIVIDEND', Order.MULTIPLICATIVE) || - '0'; + generator.valueToCode(block, 'DIVIDEND', Order.MULTIPLICATIVE) || '0'; const argument1 = - generator.valueToCode(block, 'DIVISOR', Order.MULTIPLICATIVE) || - '0'; + generator.valueToCode(block, 'DIVISOR', Order.MULTIPLICATIVE) || '0'; const code = argument0 + ' % ' + argument1; return [code, Order.MULTIPLICATIVE]; -}; +} -export function math_constrain(block, generator) { +export function math_constrain( + block: Block, + generator: PythonGenerator, +): [string, Order] { // Constrain a number between two limits. - const argument0 = - generator.valueToCode(block, 'VALUE', Order.NONE) || '0'; - const argument1 = - generator.valueToCode(block, 'LOW', Order.NONE) || '0'; + const argument0 = generator.valueToCode(block, 'VALUE', Order.NONE) || '0'; + const argument1 = generator.valueToCode(block, 'LOW', Order.NONE) || '0'; const argument2 = - generator.valueToCode(block, 'HIGH', Order.NONE) || - 'float(\'inf\')'; + generator.valueToCode(block, 'HIGH', Order.NONE) || "float('inf')"; const code = - 'min(max(' + argument0 + ', ' + argument1 + '), ' + argument2 + ')'; + 'min(max(' + argument0 + ', ' + argument1 + '), ' + argument2 + ')'; return [code, Order.FUNCTION_CALL]; -}; +} -export function math_random_int(block, generator) { +export function math_random_int( + block: Block, + generator: PythonGenerator, +): [string, Order] { // Random integer between [X] and [Y]. - generator.definitions_['import_random'] = 'import random'; - const argument0 = - generator.valueToCode(block, 'FROM', Order.NONE) || '0'; - const argument1 = - generator.valueToCode(block, 'TO', Order.NONE) || '0'; + (generator as AnyDuringMigration).definitions_['import_random'] = + 'import random'; + const argument0 = generator.valueToCode(block, 'FROM', Order.NONE) || '0'; + const argument1 = generator.valueToCode(block, 'TO', Order.NONE) || '0'; const code = 'random.randint(' + argument0 + ', ' + argument1 + ')'; return [code, Order.FUNCTION_CALL]; -}; +} -export function math_random_float(block, generator) { +export function math_random_float( + block: Block, + generator: PythonGenerator, +): [string, Order] { // Random fraction between 0 and 1. - generator.definitions_['import_random'] = 'import random'; + (generator as AnyDuringMigration).definitions_['import_random'] = + 'import random'; return ['random.random()', Order.FUNCTION_CALL]; -}; +} -export function math_atan2(block, generator) { +export function math_atan2( + block: Block, + generator: PythonGenerator, +): [string, Order] { // Arctangent of point (X, Y) in degrees from -180 to 180. - generator.definitions_['import_math'] = 'import math'; + (generator as AnyDuringMigration).definitions_['import_math'] = 'import math'; const argument0 = generator.valueToCode(block, 'X', Order.NONE) || '0'; const argument1 = generator.valueToCode(block, 'Y', Order.NONE) || '0'; return [ 'math.atan2(' + argument1 + ', ' + argument0 + ') / math.pi * 180', - Order.MULTIPLICATIVE + Order.MULTIPLICATIVE, ]; -}; +} diff --git a/generators/python/procedures.js b/generators/python/procedures.ts similarity index 64% rename from generators/python/procedures.js rename to generators/python/procedures.ts index 4539ca29622..ad45f2dfb62 100644 --- a/generators/python/procedures.js +++ b/generators/python/procedures.ts @@ -11,11 +11,13 @@ // Former goog.module ID: Blockly.Python.procedures import * as Variables from '../../core/variables.js'; +import type {Block} from '../../core/block.js'; +import type {IfReturnBlock} from '../../blocks/procedures.js'; import {NameType} from '../../core/names.js'; import {Order} from './python_generator.js'; +import type {PythonGenerator} from './python_generator.js'; - -export function procedures_defreturn(block, generator) { +export function procedures_defreturn(block: Block, generator: PythonGenerator) { // Define a procedure with a return value. // First, add a 'global' statement for every variable that is not shadowed by // a local parameter. @@ -33,15 +35,14 @@ export function procedures_defreturn(block, generator) { const devVarList = Variables.allDeveloperVariables(workspace); for (let i = 0; i < devVarList.length; i++) { globals.push( - generator.nameDB_.getName( - devVarList[i], NameType.DEVELOPER_VARIABLE)); + generator.nameDB_!.getName(devVarList[i], NameType.DEVELOPER_VARIABLE), + ); } - const globalString = globals.length ? - generator.INDENT + 'global ' + globals.join(', ') + '\n' : - ''; - const funcName = - generator.getProcedureName(block.getFieldValue('NAME')); + const globalString = globals.length + ? generator.INDENT + 'global ' + globals.join(', ') + '\n' + : ''; + const funcName = generator.getProcedureName(block.getFieldValue('NAME')); let xfix1 = ''; if (generator.STATEMENT_PREFIX) { xfix1 += generator.injectId(generator.STATEMENT_PREFIX, block); @@ -55,12 +56,12 @@ export function procedures_defreturn(block, generator) { let loopTrap = ''; if (generator.INFINITE_LOOP_TRAP) { loopTrap = generator.prefixLines( - generator.injectId(generator.INFINITE_LOOP_TRAP, block), - generator.INDENT); + generator.injectId(generator.INFINITE_LOOP_TRAP, block), + generator.INDENT, + ); } let branch = generator.statementToCode(block, 'STACK'); - let returnValue = - generator.valueToCode(block, 'RETURN', Order.NONE) || ''; + let returnValue = generator.valueToCode(block, 'RETURN', Order.NONE) || ''; let xfix2 = ''; if (branch && returnValue) { // After executing the function body, revisit this block for the return. @@ -76,58 +77,74 @@ export function procedures_defreturn(block, generator) { for (let i = 0; i < variables.length; i++) { args[i] = generator.getVariableName(variables[i]); } - let code = 'def ' + funcName + '(' + args.join(', ') + '):\n' + globalString + - xfix1 + loopTrap + branch + xfix2 + returnValue; + let code = + 'def ' + + funcName + + '(' + + args.join(', ') + + '):\n' + + globalString + + xfix1 + + loopTrap + + branch + + xfix2 + + returnValue; code = generator.scrub_(block, code); // Add % so as not to collide with helper functions in definitions list. - generator.definitions_['%' + funcName] = code; + // TODO(#7600): find better approach than casting to any to override + // CodeGenerator declaring .definitions protected. + (generator as AnyDuringMigration).definitions_['%' + funcName] = code; return null; -}; +} // Defining a procedure without a return value uses the same generator as // a procedure with a return value. export const procedures_defnoreturn = procedures_defreturn; -export function procedures_callreturn(block, generator) { +export function procedures_callreturn( + block: Block, + generator: PythonGenerator, +): [string, Order] { // Call a procedure with a return value. - const funcName = - generator.getProcedureName(block.getFieldValue('NAME')); + const funcName = generator.getProcedureName(block.getFieldValue('NAME')); const args = []; const variables = block.getVars(); for (let i = 0; i < variables.length; i++) { - args[i] = - generator.valueToCode(block, 'ARG' + i, Order.NONE) || 'None'; + args[i] = generator.valueToCode(block, 'ARG' + i, Order.NONE) || 'None'; } const code = funcName + '(' + args.join(', ') + ')'; return [code, Order.FUNCTION_CALL]; -}; +} -export function procedures_callnoreturn(block, generator) { +export function procedures_callnoreturn( + block: Block, + generator: PythonGenerator, +) { // Call a procedure with no return value. // Generated code is for a function call as a statement is the same as a // function call as a value, with the addition of line ending. - const tuple = generator.forBlock['procedures_callreturn'](block, generator); + const tuple = generator.forBlock['procedures_callreturn'](block, generator)!; return tuple[0] + '\n'; -}; +} -export function procedures_ifreturn(block, generator) { +export function procedures_ifreturn(block: Block, generator: PythonGenerator) { // Conditionally return value from a procedure. const condition = - generator.valueToCode(block, 'CONDITION', Order.NONE) || 'False'; + generator.valueToCode(block, 'CONDITION', Order.NONE) || 'False'; let code = 'if ' + condition + ':\n'; if (generator.STATEMENT_SUFFIX) { // Inject any statement suffix here since the regular one at the end // will not get executed if the return is triggered. code += generator.prefixLines( - generator.injectId( - generator.STATEMENT_SUFFIX, block), generator.INDENT); + generator.injectId(generator.STATEMENT_SUFFIX, block), + generator.INDENT, + ); } - if (block.hasReturnValue_) { - const value = - generator.valueToCode(block, 'VALUE', Order.NONE) || 'None'; + if ((block as IfReturnBlock).hasReturnValue_) { + const value = generator.valueToCode(block, 'VALUE', Order.NONE) || 'None'; code += generator.INDENT + 'return ' + value + '\n'; } else { code += generator.INDENT + 'return\n'; } return code; -}; +} diff --git a/generators/python/python_generator.js b/generators/python/python_generator.ts similarity index 50% rename from generators/python/python_generator.js rename to generators/python/python_generator.ts index 275caef4662..5e5daf9f2e8 100644 --- a/generators/python/python_generator.js +++ b/generators/python/python_generator.ts @@ -5,49 +5,48 @@ */ /** - * @fileoverview Helper functions for generating Python for blocks. - * @suppress {checkTypes|globalThis} + * @file Python code generator class, including helper methods for + * generating Python for blocks. */ // Former goog.module ID: Blockly.Python import * as stringUtils from '../../core/utils/string.js'; import * as Variables from '../../core/variables.js'; -// import type {Block} from '../../core/block.js'; +import type {Block} from '../../core/block.js'; import {CodeGenerator} from '../../core/generator.js'; import {Names} from '../../core/names.js'; -// import type {Workspace} from '../../core/workspace.js'; +import type {Workspace} from '../../core/workspace.js'; import {inputTypes} from '../../core/inputs/input_types.js'; - /** * Order of operation ENUMs. * http://docs.python.org/reference/expressions.html#summary - * @enum {number} */ -export const Order = { - ATOMIC: 0, // 0 "" ... - COLLECTION: 1, // tuples, lists, dictionaries - STRING_CONVERSION: 1, // `expression...` - MEMBER: 2.1, // . [] - FUNCTION_CALL: 2.2, // () - EXPONENTIATION: 3, // ** - UNARY_SIGN: 4, // + - - BITWISE_NOT: 4, // ~ - MULTIPLICATIVE: 5, // * / // % - ADDITIVE: 6, // + - - BITWISE_SHIFT: 7, // << >> - BITWISE_AND: 8, // & - BITWISE_XOR: 9, // ^ - BITWISE_OR: 10, // | - RELATIONAL: 11, // in, not in, is, is not, >, >=, <>, !=, == - LOGICAL_NOT: 12, // not - LOGICAL_AND: 13, // and - LOGICAL_OR: 14, // or - CONDITIONAL: 15, // if else - LAMBDA: 16, // lambda - NONE: 99, // (...) -}; +// prettier-ignore +export enum Order { + ATOMIC = 0, // 0 "" ... + COLLECTION = 1, // tuples, lists, dictionaries + STRING_CONVERSION = 1, // `expression...` + MEMBER = 2.1, // . [] + FUNCTION_CALL = 2.2, // () + EXPONENTIATION = 3, // ** + UNARY_SIGN = 4, // + - + BITWISE_NOT = 4, // ~ + MULTIPLICATIVE = 5, // * / // % + ADDITIVE = 6, // + - + BITWISE_SHIFT = 7, // << >> + BITWISE_AND = 8, // & + BITWISE_XOR = 9, // ^ + BITWISE_OR = 10, // | + RELATIONAL = 11, // in, not in, is, is not, >, >=, <>, !=, == + LOGICAL_NOT = 12, // not + LOGICAL_AND = 13, // and + LOGICAL_OR = 14, // or + CONDITIONAL = 15, // if else + LAMBDA = 16, // lambda + NONE = 99, // (...) +} /** * PythonScript code generator class. @@ -55,9 +54,8 @@ export const Order = { export class PythonGenerator extends CodeGenerator { /** * List of outer-inner pairings that do NOT require parentheses. - * @type {!Array>} */ - ORDER_OVERRIDES = [ + ORDER_OVERRIDES: number[][] = [ // (foo()).bar -> foo().bar // (foo())[0] -> foo()[0] [Order.FUNCTION_CALL, Order.MEMBER], @@ -77,11 +75,17 @@ export class PythonGenerator extends CodeGenerator { // a and (b and c) -> a and b and c [Order.LOGICAL_AND, Order.LOGICAL_AND], // a or (b or c) -> a or b or c - [Order.LOGICAL_OR, Order.LOGICAL_OR] + [Order.LOGICAL_OR, Order.LOGICAL_OR], ]; - constructor(name) { - super(name ?? 'Python'); + /** + * Empty loops or conditionals are not allowed in Python. + */ + PASS: string = ''; // Initialised by init(). + + /** @param name Name of the language the generator is for. */ + constructor(name = 'Python') { + super(name); this.isInitialized = false; // Copy Order values onto instance for backwards compatibility @@ -92,7 +96,16 @@ export class PythonGenerator extends CodeGenerator { // replace data properties with get accessors that call // deprecate.warn().) for (const key in Order) { - this['ORDER_' + key] = Order[key]; + // Must assign Order[key] to a temporary to get the type guard to work; + // see https://github.com/microsoft/TypeScript/issues/10530. + const value = Order[key]; + // Skip reverse-lookup entries in the enum. Due to + // https://github.com/microsoft/TypeScript/issues/55713 this (as + // of TypeScript 5.5.2) actually narrows the type of value to + // never - but that still allows the following assignment to + // succeed. + if (typeof value === 'string') continue; + (this as unknown as Record)['ORDER_' + key] = value; } // List of illegal variable names. This is not intended to be a @@ -105,55 +118,52 @@ export class PythonGenerator extends CodeGenerator { // https://docs.python.org/3/reference/lexical_analysis.html#keywords // https://docs.python.org/2/reference/lexical_analysis.html#keywords 'False,None,True,and,as,assert,break,class,continue,def,del,elif,else,' + - 'except,exec,finally,for,from,global,if,import,in,is,lambda,nonlocal,' + - 'not,or,pass,print,raise,return,try,while,with,yield,' + - // https://docs.python.org/3/library/constants.html - // https://docs.python.org/2/library/constants.html - 'NotImplemented,Ellipsis,__debug__,quit,exit,copyright,license,credits,' + - // >>> print(','.join(sorted(dir(__builtins__)))) - // https://docs.python.org/3/library/functions.html - // https://docs.python.org/2/library/functions.html - 'ArithmeticError,AssertionError,AttributeError,BaseException,' + - 'BlockingIOError,BrokenPipeError,BufferError,BytesWarning,' + - 'ChildProcessError,ConnectionAbortedError,ConnectionError,' + - 'ConnectionRefusedError,ConnectionResetError,DeprecationWarning,' + - 'EOFError,Ellipsis,EnvironmentError,Exception,FileExistsError,' + - 'FileNotFoundError,FloatingPointError,FutureWarning,GeneratorExit,' + - 'IOError,ImportError,ImportWarning,IndentationError,IndexError,' + - 'InterruptedError,IsADirectoryError,KeyError,KeyboardInterrupt,' + - 'LookupError,MemoryError,ModuleNotFoundError,NameError,' + - 'NotADirectoryError,NotImplemented,NotImplementedError,OSError,' + - 'OverflowError,PendingDeprecationWarning,PermissionError,' + - 'ProcessLookupError,RecursionError,ReferenceError,ResourceWarning,' + - 'RuntimeError,RuntimeWarning,StandardError,StopAsyncIteration,' + - 'StopIteration,SyntaxError,SyntaxWarning,SystemError,SystemExit,' + - 'TabError,TimeoutError,TypeError,UnboundLocalError,UnicodeDecodeError,' + - 'UnicodeEncodeError,UnicodeError,UnicodeTranslateError,UnicodeWarning,' + - 'UserWarning,ValueError,Warning,ZeroDivisionError,_,__build_class__,' + - '__debug__,__doc__,__import__,__loader__,__name__,__package__,__spec__,' + - 'abs,all,any,apply,ascii,basestring,bin,bool,buffer,bytearray,bytes,' + - 'callable,chr,classmethod,cmp,coerce,compile,complex,copyright,credits,' + - 'delattr,dict,dir,divmod,enumerate,eval,exec,execfile,exit,file,filter,' + - 'float,format,frozenset,getattr,globals,hasattr,hash,help,hex,id,input,' + - 'int,intern,isinstance,issubclass,iter,len,license,list,locals,long,' + - 'map,max,memoryview,min,next,object,oct,open,ord,pow,print,property,' + - 'quit,range,raw_input,reduce,reload,repr,reversed,round,set,setattr,' + - 'slice,sorted,staticmethod,str,sum,super,tuple,type,unichr,unicode,' + - 'vars,xrange,zip' + 'except,exec,finally,for,from,global,if,import,in,is,lambda,nonlocal,' + + 'not,or,pass,print,raise,return,try,while,with,yield,' + + // https://docs.python.org/3/library/constants.html + // https://docs.python.org/2/library/constants.html + 'NotImplemented,Ellipsis,__debug__,quit,exit,copyright,license,credits,' + + // >>> print(','.join(sorted(dir(__builtins__)))) + // https://docs.python.org/3/library/functions.html + // https://docs.python.org/2/library/functions.html + 'ArithmeticError,AssertionError,AttributeError,BaseException,' + + 'BlockingIOError,BrokenPipeError,BufferError,BytesWarning,' + + 'ChildProcessError,ConnectionAbortedError,ConnectionError,' + + 'ConnectionRefusedError,ConnectionResetError,DeprecationWarning,' + + 'EOFError,Ellipsis,EnvironmentError,Exception,FileExistsError,' + + 'FileNotFoundError,FloatingPointError,FutureWarning,GeneratorExit,' + + 'IOError,ImportError,ImportWarning,IndentationError,IndexError,' + + 'InterruptedError,IsADirectoryError,KeyError,KeyboardInterrupt,' + + 'LookupError,MemoryError,ModuleNotFoundError,NameError,' + + 'NotADirectoryError,NotImplemented,NotImplementedError,OSError,' + + 'OverflowError,PendingDeprecationWarning,PermissionError,' + + 'ProcessLookupError,RecursionError,ReferenceError,ResourceWarning,' + + 'RuntimeError,RuntimeWarning,StandardError,StopAsyncIteration,' + + 'StopIteration,SyntaxError,SyntaxWarning,SystemError,SystemExit,' + + 'TabError,TimeoutError,TypeError,UnboundLocalError,UnicodeDecodeError,' + + 'UnicodeEncodeError,UnicodeError,UnicodeTranslateError,UnicodeWarning,' + + 'UserWarning,ValueError,Warning,ZeroDivisionError,_,__build_class__,' + + '__debug__,__doc__,__import__,__loader__,__name__,__package__,__spec__,' + + 'abs,all,any,apply,ascii,basestring,bin,bool,buffer,bytearray,bytes,' + + 'callable,chr,classmethod,cmp,coerce,compile,complex,copyright,credits,' + + 'delattr,dict,dir,divmod,enumerate,eval,exec,execfile,exit,file,filter,' + + 'float,format,frozenset,getattr,globals,hasattr,hash,help,hex,id,input,' + + 'int,intern,isinstance,issubclass,iter,len,license,list,locals,long,' + + 'map,max,memoryview,min,next,object,oct,open,ord,pow,print,property,' + + 'quit,range,raw_input,reduce,reload,repr,reversed,round,set,setattr,' + + 'slice,sorted,staticmethod,str,sum,super,tuple,type,unichr,unicode,' + + 'vars,xrange,zip', ); } /** * Initialise the database of variable names. - * @param {!Workspace} workspace Workspace to generate code from. - * @this {CodeGenerator} + * + * @param workspace Workspace to generate code from. */ - init(workspace) { + init(workspace: Workspace) { super.init(workspace); - /** - * Empty loops or conditionals are not allowed in Python. - */ this.PASS = this.INDENT + 'pass\n'; if (!this.nameDB_) { @@ -171,16 +181,15 @@ export class PythonGenerator extends CodeGenerator { const devVarList = Variables.allDeveloperVariables(workspace); for (let i = 0; i < devVarList.length; i++) { defvars.push( - this.nameDB_.getName(devVarList[i], Names.DEVELOPER_VARIABLE_TYPE) + - ' = None'); + this.nameDB_.getName(devVarList[i], Names.DEVELOPER_VARIABLE_TYPE) + + ' = None', + ); } // Add user variables, but only ones that are being used. const variables = Variables.allUsedVarModels(workspace); for (let i = 0; i < variables.length; i++) { - defvars.push( - this.getVariableName(variables[i].getId()) + - ' = None'); + defvars.push(this.getVariableName(variables[i].getId()) + ' = None'); } this.definitions_['variables'] = defvars.join('\n'); @@ -189,10 +198,11 @@ export class PythonGenerator extends CodeGenerator { /** * Prepend the generated code with import statements and variable definitions. - * @param {string} code Generated code. - * @return {string} Completed code. + * + * @param code Generated code. + * @returns Completed code. */ - finish(code) { + finish(code: string): string { // Convert the definitions dictionary into a list. const imports = []; const definitions = []; @@ -208,7 +218,7 @@ export class PythonGenerator extends CodeGenerator { code = super.finish(code); this.isInitialized = false; - this.nameDB_.reset(); + this.nameDB_!.reset(); const allDefs = imports.join('\n') + '\n\n' + definitions.join('\n\n'); return allDefs.replace(/\n\n+/g, '\n\n').replace(/\n*$/, '\n\n\n') + code; } @@ -216,28 +226,30 @@ export class PythonGenerator extends CodeGenerator { /** * Naked values are top-level blocks with outputs that aren't plugged into * anything. - * @param {string} line Line of generated code. - * @return {string} Legal line of code. + * + * @param line Line of generated code. + * @returns Legal line of code. */ - scrubNakedValue(line) { + scrubNakedValue(line: string): string { return line + '\n'; } /** * Encode a string as a properly escaped Python string, complete with quotes. - * @param {string} string Text to encode. - * @return {string} Python string. + * + * @param string Text to encode. + * @returns Python string. */ - quote_(string) { + quote_(string: string): string { string = string.replace(/\\/g, '\\\\').replace(/\n/g, '\\\n'); // Follow the CPython behaviour of repr() for a non-byte string. - let quote = '\''; - if (string.indexOf('\'') !== -1) { + let quote = "'"; + if (string.indexOf("'") !== -1) { if (string.indexOf('"') === -1) { quote = '"'; } else { - string = string.replace(/'/g, '\\\''); + string = string.replace(/'/g, "\\'"); } } return quote + string + quote; @@ -246,27 +258,29 @@ export class PythonGenerator extends CodeGenerator { /** * Encode a string as a properly escaped multiline Python string, complete * with quotes. - * @param {string} string Text to encode. - * @return {string} Python string. + * + * @param string Text to encode. + * @returns Python string. */ - multiline_quote_(string) { + multiline_quote_(string: string): string { const lines = string.split(/\n/g).map(this.quote_); // Join with the following, plus a newline: // + '\n' + - return lines.join(' + \'\\n\' + \n'); + return lines.join(" + '\\n' + \n"); } /** * Common tasks for generating Python from blocks. * Handles comments for the specified block and any connected value blocks. * Calls any statements following this block. - * @param {!Block} block The current block. - * @param {string} code The Python code created for this block. - * @param {boolean=} opt_thisOnly True to generate code for only this statement. - * @return {string} Python code with comments and subsequent blocks added. - * @protected + * + * @param block The current block. + * @param code The Python code created for this block. + * @param thisOnly True to generate code for only this statement. + * @returns Python code with comments and subsequent blocks added. + */ - scrub_(block, code, opt_thisOnly) { + scrub_(block: Block, code: string, thisOnly = false): string { let commentCode = ''; // Only collect comments for blocks that aren't inline. if (!block.outputConnection || !block.outputConnection.targetConnection) { @@ -280,7 +294,7 @@ export class PythonGenerator extends CodeGenerator { // Don't collect comments for nested statements. for (let i = 0; i < block.inputList.length; i++) { if (block.inputList[i].type === inputTypes.VALUE) { - const childBlock = block.inputList[i].connection.targetBlock(); + const childBlock = block.inputList[i].connection!.targetBlock(); if (childBlock) { comment = this.allNestedComments(childBlock); if (comment) { @@ -290,33 +304,40 @@ export class PythonGenerator extends CodeGenerator { } } } - const nextBlock = block.nextConnection && block.nextConnection.targetBlock(); - const nextCode = opt_thisOnly ? '' : this.blockToCode(nextBlock); + const nextBlock = + block.nextConnection && block.nextConnection.targetBlock(); + const nextCode = thisOnly ? '' : this.blockToCode(nextBlock); return commentCode + code + nextCode; } /** * Gets a property and adjusts the value, taking into account indexing. * If a static int, casts to an integer, otherwise returns a code string. - * @param {!Block} block The block. - * @param {string} atId The property ID of the element to get. - * @param {number=} opt_delta Value to add. - * @param {boolean=} opt_negate Whether to negate the value. - * @return {string|number} + * + * @param block The block. + * @param atId The ID of the input block to get (and adjust) the value of. + * @param delta Value to add. + * @param negate Whether to negate the value. + * @returns The adjusted value. */ - getAdjustedInt(block, atId, opt_delta, opt_negate) { - let delta = opt_delta || 0; + getAdjustedInt( + block: Block, + atId: string, + delta = 0, + negate = false, + ): string | number { if (block.workspace.options.oneBasedIndex) { delta--; } const defaultAtIndex = block.workspace.options.oneBasedIndex ? '1' : '0'; - const atOrder = delta ? this.ORDER_ADDITIVE : this.ORDER_NONE; - let at = this.valueToCode(block, atId, atOrder) || defaultAtIndex; + const atOrder = delta ? Order.ADDITIVE : Order.NONE; + let at: string | number = + this.valueToCode(block, atId, atOrder) || defaultAtIndex; if (stringUtils.isNumber(at)) { // If the index is a naked number, adjust it right now. at = parseInt(at, 10) + delta; - if (opt_negate) { + if (negate) { at = -at; } } else { @@ -328,7 +349,7 @@ export class PythonGenerator extends CodeGenerator { } else { at = 'int(' + at + ')'; } - if (opt_negate) { + if (negate) { at = '-' + at; } } diff --git a/generators/python/text.js b/generators/python/text.ts similarity index 63% rename from generators/python/text.js rename to generators/python/text.ts index f4cd181f380..dca9e98322c 100644 --- a/generators/python/text.js +++ b/generators/python/text.ts @@ -11,23 +11,30 @@ // Former goog.module ID: Blockly.Python.texts import * as stringUtils from '../../core/utils/string.js'; +import type {Block} from '../../core/block.js'; +import type {JoinMutatorBlock} from '../../blocks/text.js'; import {NameType} from '../../core/names.js'; import {Order} from './python_generator.js'; +import type {PythonGenerator} from './python_generator.js'; - -export function text(block, generator) { +export function text( + block: Block, + generator: PythonGenerator, +): [string, Order] { // Text value. const code = generator.quote_(block.getFieldValue('TEXT')); return [code, Order.ATOMIC]; -}; +} -export function text_multiline(block, generator) { +export function text_multiline( + block: Block, + generator: PythonGenerator, +): [string, Order] { // Text value. const code = generator.multiline_quote_(block.getFieldValue('TEXT')); - const order = - code.indexOf('+') !== -1 ? Order.ADDITIVE : Order.ATOMIC; + const order = code.indexOf('+') !== -1 ? Order.ADDITIVE : Order.ATOMIC; return [code, order]; -}; +} /** * Regular expression to detect a single-quoted string literal. @@ -37,95 +44,113 @@ const strRegExp = /^\s*'([^']|\\')*'\s*$/; /** * Enclose the provided value in 'str(...)' function. * Leave string literals alone. - * @param {string} value Code evaluating to a value. - * @return {Array} Array containing code evaluating to a string + * + * @param value Code evaluating to a value. + * @returns Array containing code evaluating to a string * and * the order of the returned code.[string, number] */ -const forceString = function(value) { +const forceString = function (value: string): [string, Order] { if (strRegExp.test(value)) { return [value, Order.ATOMIC]; } return ['str(' + value + ')', Order.FUNCTION_CALL]; }; -export function text_join(block, generator) { +export function text_join( + block: Block, + generator: PythonGenerator, +): [string, Order] { + const joinBlock = block as JoinMutatorBlock; // Create a string made up of any number of elements of any type. // Should we allow joining by '-' or ',' or any other characters? - switch (block.itemCount_) { + switch (joinBlock.itemCount_) { case 0: return ["''", Order.ATOMIC]; case 1: { - const element = - generator.valueToCode(block, 'ADD0', Order.NONE) || "''"; + const element = generator.valueToCode(block, 'ADD0', Order.NONE) || "''"; const codeAndOrder = forceString(element); return codeAndOrder; } case 2: { - const element0 = - generator.valueToCode(block, 'ADD0', Order.NONE) || "''"; - const element1 = - generator.valueToCode(block, 'ADD1', Order.NONE) || "''"; + const element0 = generator.valueToCode(block, 'ADD0', Order.NONE) || "''"; + const element1 = generator.valueToCode(block, 'ADD1', Order.NONE) || "''"; const code = forceString(element0)[0] + ' + ' + forceString(element1)[0]; return [code, Order.ADDITIVE]; } default: { const elements = []; - for (let i = 0; i < block.itemCount_; i++) { + for (let i = 0; i < joinBlock.itemCount_; i++) { elements[i] = - generator.valueToCode(block, 'ADD' + i, Order.NONE) || "''"; + generator.valueToCode(block, 'ADD' + i, Order.NONE) || "''"; } - const tempVar = - generator.nameDB_.getDistinctName('x', NameType.VARIABLE); - const code = '\'\'.join([str(' + tempVar + ') for ' + tempVar + ' in [' + - elements.join(', ') + ']])'; + const tempVar = generator.nameDB_!.getDistinctName( + 'x', + NameType.VARIABLE, + ); + const code = + "''.join([str(" + + tempVar + + ') for ' + + tempVar + + ' in [' + + elements.join(', ') + + ']])'; return [code, Order.FUNCTION_CALL]; } } -}; +} -export function text_append(block, generator) { +export function text_append(block: Block, generator: PythonGenerator) { // Append to a variable in place. - const varName = - generator.getVariableName(block.getFieldValue('VAR')); + const varName = generator.getVariableName(block.getFieldValue('VAR')); const value = generator.valueToCode(block, 'TEXT', Order.NONE) || "''"; return varName + ' = str(' + varName + ') + ' + forceString(value)[0] + '\n'; -}; +} -export function text_length(block, generator) { +export function text_length( + block: Block, + generator: PythonGenerator, +): [string, Order] { // Is the string null or array empty? const text = generator.valueToCode(block, 'VALUE', Order.NONE) || "''"; return ['len(' + text + ')', Order.FUNCTION_CALL]; -}; +} -export function text_isEmpty(block, generator) { +export function text_isEmpty( + block: Block, + generator: PythonGenerator, +): [string, Order] { // Is the string null or array empty? const text = generator.valueToCode(block, 'VALUE', Order.NONE) || "''"; const code = 'not len(' + text + ')'; return [code, Order.LOGICAL_NOT]; -}; +} -export function text_indexOf(block, generator) { +export function text_indexOf( + block: Block, + generator: PythonGenerator, +): [string, Order] { // Search the text for a substring. // Should we allow for non-case sensitive??? const operator = block.getFieldValue('END') === 'FIRST' ? 'find' : 'rfind'; - const substring = - generator.valueToCode(block, 'FIND', Order.NONE) || "''"; - const text = - generator.valueToCode(block, 'VALUE', Order.MEMBER) || "''"; + const substring = generator.valueToCode(block, 'FIND', Order.NONE) || "''"; + const text = generator.valueToCode(block, 'VALUE', Order.MEMBER) || "''"; const code = text + '.' + operator + '(' + substring + ')'; if (block.workspace.options.oneBasedIndex) { return [code + ' + 1', Order.ADDITIVE]; } return [code, Order.FUNCTION_CALL]; -}; +} -export function text_charAt(block, generator) { +export function text_charAt( + block: Block, + generator: PythonGenerator, +): [string, Order] { // Get letter at index. // Note: Until January 2013 this block did not have the WHERE input. const where = block.getFieldValue('WHERE') || 'FROM_START'; - const textOrder = - (where === 'RANDOM') ? Order.NONE : Order.MEMBER; + const textOrder = where === 'RANDOM' ? Order.NONE : Order.MEMBER; const text = generator.valueToCode(block, 'VALUE', textOrder) || "''"; switch (where) { case 'FIRST': { @@ -147,26 +172,33 @@ export function text_charAt(block, generator) { return [code, Order.MEMBER]; } case 'RANDOM': { - generator.definitions_['import_random'] = 'import random'; - const functionName = - generator.provideFunction_('text_random_letter', ` + // TODO(#7600): find better approach than casting to any to override + // CodeGenerator declaring .definitions protected (here and below). + (generator as AnyDuringMigration).definitions_['import_random'] = + 'import random'; + const functionName = generator.provideFunction_( + 'text_random_letter', + ` def ${generator.FUNCTION_NAME_PLACEHOLDER_}(text): x = int(random.random() * len(text)) return text[x] -`); +`, + ); const code = functionName + '(' + text + ')'; return [code, Order.FUNCTION_CALL]; } } throw Error('Unhandled option (text_charAt).'); -}; +} -export function text_getSubstring(block, generator) { +export function text_getSubstring( + block: Block, + generator: PythonGenerator, +): [string, Order] { // Get substring. const where1 = block.getFieldValue('WHERE1'); const where2 = block.getFieldValue('WHERE2'); - const text = - generator.valueToCode(block, 'STRING', Order.MEMBER) || "''"; + const text = generator.valueToCode(block, 'STRING', Order.MEMBER) || "''"; let at1; switch (where1) { case 'FROM_START': @@ -195,7 +227,8 @@ export function text_getSubstring(block, generator) { // Ensure that if the result calculated is 0 that sub-sequence will // include all elements as expected. if (!stringUtils.isNumber(String(at2))) { - generator.definitions_['import_sys'] = 'import sys'; + (generator as AnyDuringMigration).definitions_['import_sys'] = + 'import sys'; at2 += ' or sys.maxsize'; } else if (at2 === 0) { at2 = ''; @@ -209,49 +242,63 @@ export function text_getSubstring(block, generator) { } const code = text + '[' + at1 + ' : ' + at2 + ']'; return [code, Order.MEMBER]; -}; +} -export function text_changeCase(block, generator) { +export function text_changeCase( + block: Block, + generator: PythonGenerator, +): [string, Order] { // Change capitalization. const OPERATORS = { 'UPPERCASE': '.upper()', 'LOWERCASE': '.lower()', - 'TITLECASE': '.title()' + 'TITLECASE': '.title()', }; - const operator = OPERATORS[block.getFieldValue('CASE')]; + type OperatorOption = keyof typeof OPERATORS; + const operator = OPERATORS[block.getFieldValue('CASE') as OperatorOption]; const text = generator.valueToCode(block, 'TEXT', Order.MEMBER) || "''"; const code = text + operator; return [code, Order.FUNCTION_CALL]; -}; +} -export function text_trim(block, generator) { +export function text_trim( + block: Block, + generator: PythonGenerator, +): [string, Order] { // Trim spaces. const OPERATORS = { 'LEFT': '.lstrip()', 'RIGHT': '.rstrip()', - 'BOTH': '.strip()' + 'BOTH': '.strip()', }; - const operator = OPERATORS[block.getFieldValue('MODE')]; + type OperatorOption = keyof typeof OPERATORS; + const operator = OPERATORS[block.getFieldValue('MODE') as OperatorOption]; const text = generator.valueToCode(block, 'TEXT', Order.MEMBER) || "''"; const code = text + operator; return [code, Order.FUNCTION_CALL]; -}; +} -export function text_print(block, generator) { +export function text_print(block: Block, generator: PythonGenerator) { // Print statement. const msg = generator.valueToCode(block, 'TEXT', Order.NONE) || "''"; return 'print(' + msg + ')\n'; -}; +} -export function text_prompt_ext(block, generator) { +export function text_prompt_ext( + block: Block, + generator: PythonGenerator, +): [string, Order] { // Prompt function. - const functionName = generator.provideFunction_('text_prompt', ` + const functionName = generator.provideFunction_( + 'text_prompt', + ` def ${generator.FUNCTION_NAME_PLACEHOLDER_}(msg): try: return raw_input(msg) except NameError: return input(msg) -`); +`, + ); let msg; if (block.getField('TEXT')) { // Internal message. @@ -266,27 +313,36 @@ def ${generator.FUNCTION_NAME_PLACEHOLDER_}(msg): code = 'float(' + code + ')'; } return [code, Order.FUNCTION_CALL]; -}; +} export const text_prompt = text_prompt_ext; -export function text_count(block, generator) { +export function text_count( + block: Block, + generator: PythonGenerator, +): [string, Order] { const text = generator.valueToCode(block, 'TEXT', Order.MEMBER) || "''"; const sub = generator.valueToCode(block, 'SUB', Order.NONE) || "''"; const code = text + '.count(' + sub + ')'; return [code, Order.FUNCTION_CALL]; -}; +} -export function text_replace(block, generator) { +export function text_replace( + block: Block, + generator: PythonGenerator, +): [string, Order] { const text = generator.valueToCode(block, 'TEXT', Order.MEMBER) || "''"; const from = generator.valueToCode(block, 'FROM', Order.NONE) || "''"; const to = generator.valueToCode(block, 'TO', Order.NONE) || "''"; const code = text + '.replace(' + from + ', ' + to + ')'; return [code, Order.MEMBER]; -}; +} -export function text_reverse(block, generator) { +export function text_reverse( + block: Block, + generator: PythonGenerator, +): [string, Order] { const text = generator.valueToCode(block, 'TEXT', Order.MEMBER) || "''"; const code = text + '[::-1]'; return [code, Order.MEMBER]; -}; +} diff --git a/generators/python/variables.js b/generators/python/variables.js deleted file mode 100644 index 0908cd2eda7..00000000000 --- a/generators/python/variables.js +++ /dev/null @@ -1,30 +0,0 @@ -/** - * @license - * Copyright 2012 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * @fileoverview Generating Python for variable blocks. - */ - -// Former goog.module ID: Blockly.Python.variables - -import {Order} from './python_generator.js'; - - -export function variables_get(block, generator) { - // Variable getter. - const code = - generator.getVariableName(block.getFieldValue('VAR')); - return [code, Order.ATOMIC]; -}; - -export function variables_set(block, generator) { - // Variable setter. - const argument0 = - generator.valueToCode(block, 'VALUE', Order.NONE) || '0'; - const varName = - generator.getVariableName(block.getFieldValue('VAR')); - return varName + ' = ' + argument0 + '\n'; -}; diff --git a/generators/python/variables.ts b/generators/python/variables.ts new file mode 100644 index 00000000000..5510e5e0f1d --- /dev/null +++ b/generators/python/variables.ts @@ -0,0 +1,31 @@ +/** + * @license + * Copyright 2012 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @fileoverview Generating Python for variable blocks. + */ + +// Former goog.module ID: Blockly.Python.variables + +import type {Block} from '../../core/block.js'; +import type {PythonGenerator} from './python_generator.js'; +import {Order} from './python_generator.js'; + +export function variables_get( + block: Block, + generator: PythonGenerator, +): [string, Order] { + // Variable getter. + const code = generator.getVariableName(block.getFieldValue('VAR')); + return [code, Order.ATOMIC]; +} + +export function variables_set(block: Block, generator: PythonGenerator) { + // Variable setter. + const argument0 = generator.valueToCode(block, 'VALUE', Order.NONE) || '0'; + const varName = generator.getVariableName(block.getFieldValue('VAR')); + return varName + ' = ' + argument0 + '\n'; +} diff --git a/generators/python/variables_dynamic.js b/generators/python/variables_dynamic.ts similarity index 99% rename from generators/python/variables_dynamic.js rename to generators/python/variables_dynamic.ts index 36c56ca068c..4ca39725869 100644 --- a/generators/python/variables_dynamic.js +++ b/generators/python/variables_dynamic.ts @@ -10,7 +10,6 @@ // Former goog.module ID: Blockly.Python.variablesDynamic - // generator is dynamically typed. export { variables_get as variables_get_dynamic,