diff --git a/generators/dart.js b/generators/dart.js index f79687940..4d2943ce7 100644 --- a/generators/dart.js +++ b/generators/dart.js @@ -1,317 +1,44 @@ /** * @license - * Copyright 2014 Google LLC + * Copyright 2021 Google LLC * SPDX-License-Identifier: Apache-2.0 */ /** - * @fileoverview Helper functions for generating Dart for blocks. - * @suppress {checkTypes|globalThis} + * @fileoverview Complete helper functions for generating Dart for + * blocks. This is the entrypoint for dart_compressed.js. + * @suppress {extraRequire} */ import * as goog from '../closure/goog/goog.js'; -goog.declareModuleId('Blockly.Dart'); +goog.declareModuleId('Blockly.Dart.all'); -import * as Variables from '../core/variables.js'; -import * as stringUtils from '../core/utils/string.js'; -// import type {Block} from '../core/block.js'; -import {CodeGenerator} from '../core/generator.js'; -import {Names, NameType} from '../core/names.js'; -// import type {Workspace} from '../core/workspace.js'; -import {inputTypes} from '../core/inputs/input_types.js'; +import {DartGenerator} from './dart/dart_generator.js'; +import * as colour from './dart/colour.js'; +import * as lists from './dart/lists.js'; +import * as logic from './dart/logic.js'; +import * as loops from './dart/loops.js'; +import * as math from './dart/math.js'; +import * as procedures from './dart/procedures.js'; +import * as text from './dart/text.js'; +import * as variables from './dart/variables.js'; +import * as variablesDynamic from './dart/variables_dynamic.js'; +export * from './dart/dart_generator.js'; /** - * Order of operation ENUMs. - * https://dart.dev/guides/language/language-tour#operators - * @enum {number} - */ -export const Order = { - ATOMIC: 0, // 0 "" ... - UNARY_POSTFIX: 1, // expr++ expr-- () [] . ?. - UNARY_PREFIX: 2, // -expr !expr ~expr ++expr --expr - MULTIPLICATIVE: 3, // * / % ~/ - ADDITIVE: 4, // + - - SHIFT: 5, // << >> - BITWISE_AND: 6, // & - BITWISE_XOR: 7, // ^ - BITWISE_OR: 8, // | - RELATIONAL: 9, // >= > <= < as is is! - EQUALITY: 10, // == != - LOGICAL_AND: 11, // && - LOGICAL_OR: 12, // || - IF_NULL: 13, // ?? - CONDITIONAL: 14, // expr ? expr : expr - CASCADE: 15, // .. - ASSIGNMENT: 16, // = *= /= ~/= %= += -= <<= >>= &= ^= |= - NONE: 99, // (...) -}; - -/** - * Dart code generator class. - */ -export class DartGenerator extends CodeGenerator { - constructor(name) { - super(name ?? 'Dart'); - this.isInitialized = false; - - // Copy Order values onto instance for backwards compatibility - // while ensuring they are not part of the publically-advertised - // API. - // - // TODO(#7085): deprecate these in due course. (Could initially - // replace data properties with get accessors that call - // deprecate.warn().) - for (const key in Order) { - this['ORDER_' + key] = Order[key]; - } - - // List of illegal variable names. This is not intended to be a - // security feature. Blockly is 100% client-side, so bypassing - // this list is trivial. This is intended to prevent users from - // accidentally clobbering a built-in object or function. - this.addReservedWords( - // https://www.dartlang.org/docs/spec/latest/dart-language-specification.pdf - // Section 16.1.1 - 'assert,break,case,catch,class,const,continue,default,do,else,enum,' + - 'extends,false,final,finally,for,if,in,is,new,null,rethrow,return,' + - 'super,switch,this,throw,true,try,var,void,while,with,' + - // https://api.dartlang.org/dart_core.html - 'print,identityHashCode,identical,BidirectionalIterator,Comparable,' + - 'double,Function,int,Invocation,Iterable,Iterator,List,Map,Match,num,' + - 'Pattern,RegExp,Set,StackTrace,String,StringSink,Type,bool,DateTime,' + - 'Deprecated,Duration,Expando,Null,Object,RuneIterator,Runes,Stopwatch,' + - 'StringBuffer,Symbol,Uri,Comparator,AbstractClassInstantiationError,' + - 'ArgumentError,AssertionError,CastError,ConcurrentModificationError,' + - 'CyclicInitializationError,Error,Exception,FallThroughError,' + - 'FormatException,IntegerDivisionByZeroException,NoSuchMethodError,' + - 'NullThrownError,OutOfMemoryError,RangeError,StackOverflowError,' + - 'StateError,TypeError,UnimplementedError,UnsupportedError' - ); - } - - /** - * Initialise the database of variable names. - * @param {!Workspace} workspace Workspace to generate code from. - */ - init(workspace) { - super.init(); - - if (!this.nameDB_) { - this.nameDB_ = new Names(this.RESERVED_WORDS_); - } else { - this.nameDB_.reset(); - } - - this.nameDB_.setVariableMap(workspace.getVariableMap()); - this.nameDB_.populateVariables(workspace); - this.nameDB_.populateProcedures(workspace); - - const defvars = []; - // Add developer variables (not created or named by the user). - const devVarList = Variables.allDeveloperVariables(workspace); - for (let i = 0; i < devVarList.length; i++) { - defvars.push(this.nameDB_.getName(devVarList[i], - NameType.DEVELOPER_VARIABLE)); - } - - // 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.nameDB_.getName(variables[i].getId(), - NameType.VARIABLE)); - } - - // Declare all of the variables. - if (defvars.length) { - this.definitions_['variables'] = - 'var ' + defvars.join(', ') + ';'; - } - this.isInitialized = true; - } - - /** - * Prepend the generated code with import statements and variable definitions. - * @param {string} code Generated code. - * @return {string} Completed code. - */ - finish(code) { - // Indent every line. - if (code) { - code = this.prefixLines(code, this.INDENT); - } - code = 'main() {\n' + code + '}'; - - // Convert the definitions dictionary into a list. - const imports = []; - const definitions = []; - for (let name in this.definitions_) { - const def = this.definitions_[name]; - if (def.match(/^import\s/)) { - imports.push(def); - } else { - definitions.push(def); - } - } - // Call Blockly.CodeGenerator's finish. - code = super.finish(code); - this.isInitialized = false; - - 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; - } - - /** - * Naked values are top-level blocks with outputs that aren't plugged into - * anything. A trailing semicolon is needed to make this legal. - * @param {string} line Line of generated code. - * @return {string} Legal line of code. - */ - scrubNakedValue(line) { - return line + ';\n'; - } - - /** - * Encode a string as a properly escaped Dart string, complete with quotes. - * @param {string} string Text to encode. - * @return {string} Dart string. - * @protected - */ - quote_(string) { - // Can't use goog.string.quote since $ must also be escaped. - string = string.replace(/\\/g, '\\\\') - .replace(/\n/g, '\\\n') - .replace(/\$/g, '\\$') - .replace(/'/g, '\\\''); - return '\'' + string + '\''; - } - - /** - * Encode a string as a properly escaped multiline Dart string, complete with - * quotes. - * @param {string} string Text to encode. - * @return {string} Dart string. - * @protected - */ - multiline_quote_(string) { - const lines = string.split(/\n/g).map(this.quote_); - // Join with the following, plus a newline: - // + '\n' + - return lines.join(' + \'\\n\' + \n'); - } - - /** - * Common tasks for generating Dart 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 Dart code created for this block. - * @param {boolean=} opt_thisOnly True to generate code for only this - * statement. - * @return {string} Dart code with comments and subsequent blocks added. - * @protected - */ - scrub_(block, code, opt_thisOnly) { - let commentCode = ''; - // Only collect comments for blocks that aren't inline. - if (!block.outputConnection || !block.outputConnection.targetConnection) { - // Collect comment for this block. - let comment = block.getCommentText(); - if (comment) { - comment = stringUtils.wrap(comment, this.COMMENT_WRAP - 3); - if (block.getProcedureDef) { - // Use documentation comment for function comments. - commentCode += this.prefixLines(comment + '\n', '/// '); - } else { - commentCode += this.prefixLines(comment + '\n', '// '); - } - } - // Collect comments for all value arguments. - // 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(); - if (childBlock) { - comment = this.allNestedComments(childBlock); - if (comment) { - commentCode += this.prefixLines(comment, '// '); - } - } - } - } - } - const nextBlock = - block.nextConnection && block.nextConnection.targetBlock(); - const nextCode = opt_thisOnly ? '' : this.blockToCode(nextBlock); - return commentCode + code + nextCode; - } - - /** - * Gets a property and adjusts the value while taking into account indexing. - * @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. - * @param {number=} opt_order The highest order acting on this value. - * @return {string|number} - */ - getAdjusted(block, atId, opt_delta, opt_negate, opt_order) { - let delta = opt_delta || 0; - let order = opt_order || this.ORDER_NONE; - if (block.workspace.options.oneBasedIndex) { - delta--; - } - const defaultAtIndex = block.workspace.options.oneBasedIndex ? '1' : '0'; - - /** @type {number} */ - let outerOrder; - let innerOrder; - if (delta) { - outerOrder = this.ORDER_ADDITIVE; - innerOrder = this.ORDER_ADDITIVE; - } else if (opt_negate) { - outerOrder = this.ORDER_UNARY_PREFIX; - innerOrder = this.ORDER_UNARY_PREFIX; - } else { - outerOrder = order; - } - - /** @type {string|number} */ - let at = this.valueToCode(block, atId, outerOrder) || defaultAtIndex; - - if (stringUtils.isNumber(at)) { - // If the index is a naked number, adjust it right now. - at = parseInt(at, 10) + delta; - if (opt_negate) { - at = -at; - } - } else { - // If the index is dynamic, adjust it in code. - if (delta > 0) { - at = at + ' + ' + delta; - } else if (delta < 0) { - at = at + ' - ' + -delta; - } - if (opt_negate) { - if (delta) { - at = '-(' + at + ')'; - } else { - at = '-' + at; - } - } - innerOrder = Math.floor(innerOrder); - order = Math.floor(order); - if (innerOrder && order >= innerOrder) { - at = '(' + at + ')'; - } - } - return at; - } -} - -/** - * Dart code generator. + * Dart code generator instance. * @type {!DartGenerator} */ export const dartGenerator = new DartGenerator(); + +// Add reserved words. This list should include all words mentioned +// in RESERVED WORDS: comments in the imports above. +dartGenerator.addReservedWords('Html,Math'); + +// Install per-block-type generator functions: +Object.assign( + dartGenerator.forBlock, + colour, lists, logic, loops, math, procedures, + text, variables, variablesDynamic +); diff --git a/generators/dart/all.js b/generators/dart/all.js deleted file mode 100644 index 6902d882b..000000000 --- a/generators/dart/all.js +++ /dev/null @@ -1,26 +0,0 @@ -/** - * @license - * Copyright 2021 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * @fileoverview Complete helper functions for generating Dart for - * blocks. This is the entrypoint for dart_compressed.js. - * @suppress {extraRequire} - */ - -import * as goog from '../../closure/goog/goog.js'; -goog.declareModuleId('Blockly.Dart.all'); - -import './colour.js'; -import './lists.js'; -import './logic.js'; -import './loops.js'; -import './math.js'; -import './procedures.js'; -import './text.js'; -import './variables.js'; -import './variables_dynamic.js'; - -export * from '../dart.js'; diff --git a/generators/dart/colour.js b/generators/dart/colour.js index f82e66977..e415d589a 100644 --- a/generators/dart/colour.js +++ b/generators/dart/colour.js @@ -11,18 +11,18 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.Dart.colour'); -import {dartGenerator, Order} from '../dart.js'; +import {Order} from './dart_generator.js'; -dartGenerator.addReservedWords('Math'); +// RESERVED WORDS: 'Math' -dartGenerator.forBlock['colour_picker'] = function(block, generator) { +export function colour_picker(block, generator) { // Colour picker. const code = generator.quote_(block.getFieldValue('COLOUR')); return [code, Order.ATOMIC]; }; -dartGenerator.forBlock['colour_random'] = function(block, generator) { +export function colour_random(block, generator) { // Generate a random colour. generator.definitions_['import_dart_math'] = "import 'dart:math' as Math;"; @@ -39,7 +39,7 @@ String ${generator.FUNCTION_NAME_PLACEHOLDER_}() { return [code, Order.UNARY_POSTFIX]; }; -dartGenerator.forBlock['colour_rgb'] = function(block, generator) { +export function colour_rgb(block, generator) { // Compose a colour from RGB components expressed as percentages. const red = generator.valueToCode(block, 'RED', Order.NONE) || 0; const green = generator.valueToCode(block, 'GREEN', Order.NONE) || 0; @@ -68,7 +68,7 @@ String ${generator.FUNCTION_NAME_PLACEHOLDER_}(num r, num g, num b) { return [code, Order.UNARY_POSTFIX]; }; -dartGenerator.forBlock['colour_blend'] = function(block, generator) { +export function colour_blend(block, generator) { // Blend two colours together. const c1 = generator.valueToCode(block, 'COLOUR1', Order.NONE) || "'#000000'"; diff --git a/generators/dart/dart_generator.js b/generators/dart/dart_generator.js new file mode 100644 index 000000000..5f64e919f --- /dev/null +++ b/generators/dart/dart_generator.js @@ -0,0 +1,311 @@ +/** + * @license + * Copyright 2014 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @fileoverview Helper functions for generating Dart for blocks. + * @suppress {checkTypes|globalThis} + */ + +import * as goog from '../../closure/goog/goog.js'; +goog.declareModuleId('Blockly.Dart'); + +import * as Variables from '../../core/variables.js'; +import * as stringUtils from '../../core/utils/string.js'; +// import type {Block} from '../../core/block.js'; +import {CodeGenerator} from '../../core/generator.js'; +import {Names, NameType} from '../../core/names.js'; +// import type {Workspace} from '../../core/workspace.js'; +import {inputTypes} from '../../core/inputs/input_types.js'; + + +/** + * Order of operation ENUMs. + * https://dart.dev/guides/language/language-tour#operators + * @enum {number} + */ +export const Order = { + ATOMIC: 0, // 0 "" ... + UNARY_POSTFIX: 1, // expr++ expr-- () [] . ?. + UNARY_PREFIX: 2, // -expr !expr ~expr ++expr --expr + MULTIPLICATIVE: 3, // * / % ~/ + ADDITIVE: 4, // + - + SHIFT: 5, // << >> + BITWISE_AND: 6, // & + BITWISE_XOR: 7, // ^ + BITWISE_OR: 8, // | + RELATIONAL: 9, // >= > <= < as is is! + EQUALITY: 10, // == != + LOGICAL_AND: 11, // && + LOGICAL_OR: 12, // || + IF_NULL: 13, // ?? + CONDITIONAL: 14, // expr ? expr : expr + CASCADE: 15, // .. + ASSIGNMENT: 16, // = *= /= ~/= %= += -= <<= >>= &= ^= |= + NONE: 99, // (...) +}; + +/** + * Dart code generator class. + */ +export class DartGenerator extends CodeGenerator { + constructor(name) { + super(name ?? 'Dart'); + this.isInitialized = false; + + // Copy Order values onto instance for backwards compatibility + // while ensuring they are not part of the publically-advertised + // API. + // + // TODO(#7085): deprecate these in due course. (Could initially + // replace data properties with get accessors that call + // deprecate.warn().) + for (const key in Order) { + this['ORDER_' + key] = Order[key]; + } + + // List of illegal variable names. This is not intended to be a + // security feature. Blockly is 100% client-side, so bypassing + // this list is trivial. This is intended to prevent users from + // accidentally clobbering a built-in object or function. + this.addReservedWords( + // https://www.dartlang.org/docs/spec/latest/dart-language-specification.pdf + // Section 16.1.1 + 'assert,break,case,catch,class,const,continue,default,do,else,enum,' + + 'extends,false,final,finally,for,if,in,is,new,null,rethrow,return,' + + 'super,switch,this,throw,true,try,var,void,while,with,' + + // https://api.dartlang.org/dart_core.html + 'print,identityHashCode,identical,BidirectionalIterator,Comparable,' + + 'double,Function,int,Invocation,Iterable,Iterator,List,Map,Match,num,' + + 'Pattern,RegExp,Set,StackTrace,String,StringSink,Type,bool,DateTime,' + + 'Deprecated,Duration,Expando,Null,Object,RuneIterator,Runes,Stopwatch,' + + 'StringBuffer,Symbol,Uri,Comparator,AbstractClassInstantiationError,' + + 'ArgumentError,AssertionError,CastError,ConcurrentModificationError,' + + 'CyclicInitializationError,Error,Exception,FallThroughError,' + + 'FormatException,IntegerDivisionByZeroException,NoSuchMethodError,' + + 'NullThrownError,OutOfMemoryError,RangeError,StackOverflowError,' + + 'StateError,TypeError,UnimplementedError,UnsupportedError' + ); + } + + /** + * Initialise the database of variable names. + * @param {!Workspace} workspace Workspace to generate code from. + */ + init(workspace) { + super.init(); + + if (!this.nameDB_) { + this.nameDB_ = new Names(this.RESERVED_WORDS_); + } else { + this.nameDB_.reset(); + } + + this.nameDB_.setVariableMap(workspace.getVariableMap()); + this.nameDB_.populateVariables(workspace); + this.nameDB_.populateProcedures(workspace); + + const defvars = []; + // Add developer variables (not created or named by the user). + const devVarList = Variables.allDeveloperVariables(workspace); + for (let i = 0; i < devVarList.length; i++) { + defvars.push(this.nameDB_.getName(devVarList[i], + NameType.DEVELOPER_VARIABLE)); + } + + // 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.nameDB_.getName(variables[i].getId(), + NameType.VARIABLE)); + } + + // Declare all of the variables. + if (defvars.length) { + this.definitions_['variables'] = + 'var ' + defvars.join(', ') + ';'; + } + this.isInitialized = true; + } + + /** + * Prepend the generated code with import statements and variable definitions. + * @param {string} code Generated code. + * @return {string} Completed code. + */ + finish(code) { + // Indent every line. + if (code) { + code = this.prefixLines(code, this.INDENT); + } + code = 'main() {\n' + code + '}'; + + // Convert the definitions dictionary into a list. + const imports = []; + const definitions = []; + for (let name in this.definitions_) { + const def = this.definitions_[name]; + if (def.match(/^import\s/)) { + imports.push(def); + } else { + definitions.push(def); + } + } + // Call Blockly.CodeGenerator's finish. + code = super.finish(code); + this.isInitialized = false; + + 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; + } + + /** + * Naked values are top-level blocks with outputs that aren't plugged into + * anything. A trailing semicolon is needed to make this legal. + * @param {string} line Line of generated code. + * @return {string} Legal line of code. + */ + scrubNakedValue(line) { + return line + ';\n'; + } + + /** + * Encode a string as a properly escaped Dart string, complete with quotes. + * @param {string} string Text to encode. + * @return {string} Dart string. + * @protected + */ + quote_(string) { + // Can't use goog.string.quote since $ must also be escaped. + string = string.replace(/\\/g, '\\\\') + .replace(/\n/g, '\\\n') + .replace(/\$/g, '\\$') + .replace(/'/g, '\\\''); + return '\'' + string + '\''; + } + + /** + * Encode a string as a properly escaped multiline Dart string, complete with + * quotes. + * @param {string} string Text to encode. + * @return {string} Dart string. + * @protected + */ + multiline_quote_(string) { + const lines = string.split(/\n/g).map(this.quote_); + // Join with the following, plus a newline: + // + '\n' + + return lines.join(' + \'\\n\' + \n'); + } + + /** + * Common tasks for generating Dart 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 Dart code created for this block. + * @param {boolean=} opt_thisOnly True to generate code for only this + * statement. + * @return {string} Dart code with comments and subsequent blocks added. + * @protected + */ + scrub_(block, code, opt_thisOnly) { + let commentCode = ''; + // Only collect comments for blocks that aren't inline. + if (!block.outputConnection || !block.outputConnection.targetConnection) { + // Collect comment for this block. + let comment = block.getCommentText(); + if (comment) { + comment = stringUtils.wrap(comment, this.COMMENT_WRAP - 3); + if (block.getProcedureDef) { + // Use documentation comment for function comments. + commentCode += this.prefixLines(comment + '\n', '/// '); + } else { + commentCode += this.prefixLines(comment + '\n', '// '); + } + } + // Collect comments for all value arguments. + // 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(); + if (childBlock) { + comment = this.allNestedComments(childBlock); + if (comment) { + commentCode += this.prefixLines(comment, '// '); + } + } + } + } + } + const nextBlock = + block.nextConnection && block.nextConnection.targetBlock(); + const nextCode = opt_thisOnly ? '' : this.blockToCode(nextBlock); + return commentCode + code + nextCode; + } + + /** + * Gets a property and adjusts the value while taking into account indexing. + * @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. + * @param {number=} opt_order The highest order acting on this value. + * @return {string|number} + */ + getAdjusted(block, atId, opt_delta, opt_negate, opt_order) { + let delta = opt_delta || 0; + let order = opt_order || this.ORDER_NONE; + if (block.workspace.options.oneBasedIndex) { + delta--; + } + const defaultAtIndex = block.workspace.options.oneBasedIndex ? '1' : '0'; + + /** @type {number} */ + let outerOrder; + let innerOrder; + if (delta) { + outerOrder = this.ORDER_ADDITIVE; + innerOrder = this.ORDER_ADDITIVE; + } else if (opt_negate) { + outerOrder = this.ORDER_UNARY_PREFIX; + innerOrder = this.ORDER_UNARY_PREFIX; + } else { + outerOrder = order; + } + + /** @type {string|number} */ + let at = this.valueToCode(block, atId, outerOrder) || defaultAtIndex; + + if (stringUtils.isNumber(at)) { + // If the index is a naked number, adjust it right now. + at = parseInt(at, 10) + delta; + if (opt_negate) { + at = -at; + } + } else { + // If the index is dynamic, adjust it in code. + if (delta > 0) { + at = at + ' + ' + delta; + } else if (delta < 0) { + at = at + ' - ' + -delta; + } + if (opt_negate) { + if (delta) { + at = '-(' + at + ')'; + } else { + at = '-' + at; + } + } + innerOrder = Math.floor(innerOrder); + order = Math.floor(order); + if (innerOrder && order >= innerOrder) { + at = '(' + at + ')'; + } + } + return at; + } +} diff --git a/generators/dart/lists.js b/generators/dart/lists.js index 2c9ef7456..0f7a24d64 100644 --- a/generators/dart/lists.js +++ b/generators/dart/lists.js @@ -12,17 +12,17 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.Dart.lists'); import {NameType} from '../../core/names.js'; -import {dartGenerator, Order} from '../dart.js'; +import {Order} from './dart_generator.js'; -dartGenerator.addReservedWords('Math'); +// RESERVED WORDS: 'Math' -dartGenerator.forBlock['lists_create_empty'] = function(block, generator) { +export function lists_create_empty(block, generator) { // Create an empty list. return ['[]', Order.ATOMIC]; }; -dartGenerator.forBlock['lists_create_with'] = function(block, generator) { +export function lists_create_with(block, generator) { // 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++) { @@ -33,7 +33,7 @@ dartGenerator.forBlock['lists_create_with'] = function(block, generator) { return [code, Order.ATOMIC]; }; -dartGenerator.forBlock['lists_repeat'] = function(block, generator) { +export function lists_repeat(block, generator) { // Create a list with one element repeated. const element = generator.valueToCode(block, 'ITEM', Order.NONE) || 'null'; @@ -43,21 +43,21 @@ dartGenerator.forBlock['lists_repeat'] = function(block, generator) { return [code, Order.UNARY_POSTFIX]; }; -dartGenerator.forBlock['lists_length'] = function(block, generator) { +export function lists_length(block, generator) { // String or array length. const list = generator.valueToCode(block, 'VALUE', Order.UNARY_POSTFIX) || '[]'; return [list + '.length', Order.UNARY_POSTFIX]; }; -dartGenerator.forBlock['lists_isEmpty'] = function(block, generator) { +export function lists_isEmpty(block, generator) { // Is the string null or array empty? const list = generator.valueToCode(block, 'VALUE', Order.UNARY_POSTFIX) || '[]'; return [list + '.isEmpty', Order.UNARY_POSTFIX]; }; -dartGenerator.forBlock['lists_indexOf'] = function(block, generator) { +export function lists_indexOf(block, generator) { // Find an item in the list. const operator = block.getFieldValue('END') === 'FIRST' ? 'indexOf' : 'lastIndexOf'; @@ -71,7 +71,7 @@ dartGenerator.forBlock['lists_indexOf'] = function(block, generator) { return [code, Order.UNARY_POSTFIX]; }; -dartGenerator.forBlock['lists_getIndex'] = function(block, generator) { +export function lists_getIndex(block, generator) { // Get element at index. // Note: Until January 2013 this block did not have MODE or WHERE inputs. const mode = block.getFieldValue('MODE') || 'GET'; @@ -234,7 +234,7 @@ dynamic ${generator.FUNCTION_NAME_PLACEHOLDER_}(List my_list) { throw Error('Unhandled combination (lists_getIndex).'); }; -dartGenerator.forBlock['lists_setIndex'] = function(block, generator) { +export function lists_setIndex(block, generator) { // Set element at index. // Note: Until February 2013 this block did not have MODE or WHERE inputs. const mode = block.getFieldValue('MODE') || 'GET'; @@ -316,7 +316,7 @@ dartGenerator.forBlock['lists_setIndex'] = function(block, generator) { throw Error('Unhandled combination (lists_setIndex).'); }; -dartGenerator.forBlock['lists_getSublist'] = function(block, generator) { +export function lists_getSublist(block, generator) { // Get sublist. const list = generator.valueToCode(block, 'LIST', Order.UNARY_POSTFIX) || '[]'; @@ -390,7 +390,7 @@ List ${generator.FUNCTION_NAME_PLACEHOLDER_}(List list, String where1, num at1, return [code, Order.UNARY_POSTFIX]; }; -dartGenerator.forBlock['lists_sort'] = function(block, generator) { +export function lists_sort(block, generator) { // Block for sorting a list. const list = generator.valueToCode(block, 'LIST', Order.NONE) || '[]'; const direction = block.getFieldValue('DIRECTION') === '1' ? 1 : -1; @@ -417,7 +417,7 @@ List ${generator.FUNCTION_NAME_PLACEHOLDER_}(List list, String type, int directi ]; }; -dartGenerator.forBlock['lists_split'] = function(block, generator) { +export function lists_split(block, generator) { // Block for splitting text into a list, or joining a list into text. let input = generator.valueToCode(block, 'INPUT', Order.UNARY_POSTFIX); const delimiter = @@ -441,7 +441,7 @@ dartGenerator.forBlock['lists_split'] = function(block, generator) { return [code, Order.UNARY_POSTFIX]; }; -dartGenerator.forBlock['lists_reverse'] = function(block, generator) { +export function lists_reverse(block, generator) { // Block for reversing a list. const list = generator.valueToCode(block, 'LIST', Order.NONE) || '[]'; // XXX What should the operator precedence be for a `new`? diff --git a/generators/dart/logic.js b/generators/dart/logic.js index 01e4a4756..ab589a133 100644 --- a/generators/dart/logic.js +++ b/generators/dart/logic.js @@ -11,10 +11,10 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.Dart.logic'); -import {dartGenerator, Order} from '../dart.js'; +import {Order} from './dart_generator.js'; -dartGenerator.forBlock['controls_if'] = function(block, generator) { +export function controls_if(block, generator) { // If/elseif/else condition. let n = 0; let code = '', branchCode, conditionCode; @@ -52,10 +52,9 @@ dartGenerator.forBlock['controls_if'] = function(block, generator) { return code + '\n'; }; -dartGenerator.forBlock['controls_ifelse'] = - dartGenerator.forBlock['controls_if']; +export const controls_ifelse = controls_if; -dartGenerator.forBlock['logic_compare'] = function(block, generator) { +export function logic_compare(block, generator) { // Comparison operator. const OPERATORS = {'EQ': '==', 'NEQ': '!=', 'LT': '<', 'LTE': '<=', 'GT': '>', 'GTE': '>='}; @@ -69,7 +68,7 @@ dartGenerator.forBlock['logic_compare'] = function(block, generator) { return [code, order]; }; -dartGenerator.forBlock['logic_operation'] = function(block, generator) { +export function logic_operation(block, generator) { // Operations 'and', 'or'. const operator = (block.getFieldValue('OP') === 'AND') ? '&&' : '||'; const order = @@ -94,7 +93,7 @@ dartGenerator.forBlock['logic_operation'] = function(block, generator) { return [code, order]; }; -dartGenerator.forBlock['logic_negate'] = function(block, generator) { +export function logic_negate(block, generator) { // Negation. const order = Order.UNARY_PREFIX; const argument0 = generator.valueToCode(block, 'BOOL', order) || 'true'; @@ -102,18 +101,18 @@ dartGenerator.forBlock['logic_negate'] = function(block, generator) { return [code, order]; }; -dartGenerator.forBlock['logic_boolean'] = function(block, generator) { +export function logic_boolean(block, generator) { // Boolean values true and false. const code = (block.getFieldValue('BOOL') === 'TRUE') ? 'true' : 'false'; return [code, Order.ATOMIC]; }; -dartGenerator.forBlock['logic_null'] = function(block, generator) { +export function logic_null(block, generator) { // Null data type. return ['null', Order.ATOMIC]; }; -dartGenerator.forBlock['logic_ternary'] = function(block, generator) { +export function logic_ternary(block, generator) { // Ternary operator. const value_if = generator.valueToCode(block, 'IF', Order.CONDITIONAL) || 'false'; diff --git a/generators/dart/loops.js b/generators/dart/loops.js index 8951d02df..b7735abf9 100644 --- a/generators/dart/loops.js +++ b/generators/dart/loops.js @@ -11,12 +11,12 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.Dart.loops'); -import {dartGenerator, Order} from '../dart.js'; +import {Order} from './dart_generator.js'; import * as stringUtils from '../../core/utils/string.js'; import {NameType} from '../../core/names.js'; -dartGenerator.forBlock['controls_repeat_ext'] = function(block, generator) { +export function controls_repeat_ext(block, generator) { let repeats; // Repeat n times. if (block.getField('TIMES')) { @@ -43,10 +43,9 @@ dartGenerator.forBlock['controls_repeat_ext'] = function(block, generator) { return code; }; -dartGenerator.forBlock['controls_repeat'] = - dartGenerator.forBlock['controls_repeat_ext']; +export const controls_repeat = controls_repeat_ext; -dartGenerator.forBlock['controls_whileUntil'] = function(block, generator) { +export function controls_whileUntil(block, generator) { // Do while/until loop. const until = block.getFieldValue('MODE') === 'UNTIL'; let argument0 = @@ -61,7 +60,7 @@ dartGenerator.forBlock['controls_whileUntil'] = function(block, generator) { return 'while (' + argument0 + ') {\n' + branch + '}\n'; }; -dartGenerator.forBlock['controls_for'] = function(block, generator) { +export function controls_for(block, generator) { // For loop. const variable0 = generator.nameDB_.getName( @@ -127,7 +126,7 @@ dartGenerator.forBlock['controls_for'] = function(block, generator) { return code; }; -dartGenerator.forBlock['controls_forEach'] = function(block, generator) { +export function controls_forEach(block, generator) { // For each loop. const variable0 = generator.nameDB_.getName( @@ -141,7 +140,7 @@ dartGenerator.forBlock['controls_forEach'] = function(block, generator) { return code; }; -dartGenerator.forBlock['controls_flow_statements'] = function(block, generator) { +export function controls_flow_statements(block, generator) { // Flow statements: continue, break. let xfix = ''; if (generator.STATEMENT_PREFIX) { diff --git a/generators/dart/math.js b/generators/dart/math.js index 2233e51be..3d15d5a2f 100644 --- a/generators/dart/math.js +++ b/generators/dart/math.js @@ -12,12 +12,12 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.Dart.math'); import {NameType} from '../../core/names.js'; -import {dartGenerator, Order} from '../dart.js'; +import {Order} from './dart_generator.js'; -dartGenerator.addReservedWords('Math'); +// RESERVED WORDS: 'Math' -dartGenerator.forBlock['math_number'] = function(block, generator) { +export function math_number(block, generator) { // Numeric value. let code = Number(block.getFieldValue('NUM')); let order; @@ -35,7 +35,7 @@ dartGenerator.forBlock['math_number'] = function(block, generator) { return [code, order]; }; -dartGenerator.forBlock['math_arithmetic'] = function(block, generator) { +export function math_arithmetic(block, generator) { // Basic arithmetic operators, and power. const OPERATORS = { 'ADD': [' + ', Order.ADDITIVE], @@ -61,7 +61,7 @@ dartGenerator.forBlock['math_arithmetic'] = function(block, generator) { return [code, order]; }; -dartGenerator.forBlock['math_single'] = function(block, generator) { +export function math_single(block, generator) { // Math operators with single operand. const operator = block.getFieldValue('OP'); let code; @@ -146,7 +146,7 @@ dartGenerator.forBlock['math_single'] = function(block, generator) { return [code, Order.MULTIPLICATIVE]; }; -dartGenerator.forBlock['math_constant'] = function(block, generator) { +export function math_constant(block, generator) { // Constants: PI, E, the Golden Ratio, sqrt(2), 1/sqrt(2), INFINITY. const CONSTANTS = { 'PI': ['Math.pi', Order.UNARY_POSTFIX], @@ -164,7 +164,7 @@ dartGenerator.forBlock['math_constant'] = function(block, generator) { return CONSTANTS[constant]; }; -dartGenerator.forBlock['math_number_property'] = function(block, generator) { +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 = { @@ -219,7 +219,7 @@ bool ${generator.FUNCTION_NAME_PLACEHOLDER_}(n) { return [code, outputOrder]; }; -dartGenerator.forBlock['math_change'] = function(block, generator) { +export function math_change(block, generator) { // Add to a variable in place. const argument0 = generator.valueToCode(block, 'DELTA', Order.ADDITIVE) || '0'; @@ -231,11 +231,11 @@ dartGenerator.forBlock['math_change'] = function(block, generator) { }; // Rounding functions have a single operand. -dartGenerator.forBlock['math_round'] = dartGenerator.forBlock['math_single']; +export const math_round = math_single; // Trigonometry functions have a single operand. -dartGenerator.forBlock['math_trig'] = dartGenerator.forBlock['math_single']; +export const math_trig = math_single; -dartGenerator.forBlock['math_on_list'] = function(block, generator) { +export function math_on_list(block, generator) { // Math functions for lists. const func = block.getFieldValue('OP'); const list = generator.valueToCode(block, 'LIST', Order.NONE) || '[]'; @@ -396,7 +396,7 @@ dynamic ${generator.FUNCTION_NAME_PLACEHOLDER_}(List myList) { return [code, Order.UNARY_POSTFIX]; }; -dartGenerator.forBlock['math_modulo'] = function(block, generator) { +export function math_modulo(block, generator) { // Remainder computation. const argument0 = generator.valueToCode(block, 'DIVIDEND', Order.MULTIPLICATIVE) || '0'; @@ -406,7 +406,7 @@ dartGenerator.forBlock['math_modulo'] = function(block, generator) { return [code, Order.MULTIPLICATIVE]; }; -dartGenerator.forBlock['math_constrain'] = function(block, generator) { +export function math_constrain(block, generator) { // Constrain a number between two limits. generator.definitions_['import_dart_math'] = 'import \'dart:math\' as Math;'; @@ -420,7 +420,7 @@ dartGenerator.forBlock['math_constrain'] = function(block, generator) { return [code, Order.UNARY_POSTFIX]; }; -dartGenerator.forBlock['math_random_int'] = function(block, generator) { +export function math_random_int(block, generator) { // Random integer between [X] and [Y]. generator.definitions_['import_dart_math'] = 'import \'dart:math\' as Math;'; @@ -441,14 +441,14 @@ int ${generator.FUNCTION_NAME_PLACEHOLDER_}(num a, num b) { return [code, Order.UNARY_POSTFIX]; }; -dartGenerator.forBlock['math_random_float'] = function(block, generator) { +export function math_random_float(block, generator) { // Random fraction between 0 and 1. generator.definitions_['import_dart_math'] = 'import \'dart:math\' as Math;'; return ['new Math.Random().nextDouble()', Order.UNARY_POSTFIX]; }; -dartGenerator.forBlock['math_atan2'] = function(block, generator) { +export function math_atan2(block, generator) { // Arctangent of point (X, Y) in degrees from -180 to 180. generator.definitions_['import_dart_math'] = 'import \'dart:math\' as Math;'; diff --git a/generators/dart/procedures.js b/generators/dart/procedures.js index 18712f142..c8d61cc81 100644 --- a/generators/dart/procedures.js +++ b/generators/dart/procedures.js @@ -12,10 +12,10 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.Dart.procedures'); import {NameType} from '../../core/names.js'; -import {dartGenerator, Order} from '../dart.js'; +import {Order} from './dart_generator.js'; -dartGenerator.forBlock['procedures_defreturn'] = function(block, generator) { +export function procedures_defreturn(block, generator) { // Define a procedure with a return value. const funcName = generator.nameDB_.getName( @@ -63,9 +63,9 @@ dartGenerator.forBlock['procedures_defreturn'] = function(block, generator) { // Defining a procedure without a return value uses the same generator as // a procedure with a return value. -dartGenerator.forBlock['procedures_defnoreturn'] = dartGenerator.forBlock['procedures_defreturn']; +export const procedures_defnoreturn = procedures_defreturn; -dartGenerator.forBlock['procedures_callreturn'] = function(block, generator) { +export function procedures_callreturn(block, generator) { // Call a procedure with a return value. const funcName = generator.nameDB_.getName( @@ -79,7 +79,7 @@ dartGenerator.forBlock['procedures_callreturn'] = function(block, generator) { return [code, Order.UNARY_POSTFIX]; }; -dartGenerator.forBlock['procedures_callnoreturn'] = function(block, generator) { +export function procedures_callnoreturn(block, generator) { // 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. @@ -87,7 +87,7 @@ dartGenerator.forBlock['procedures_callnoreturn'] = function(block, generator) { return tuple[0] + ';\n'; }; -dartGenerator.forBlock['procedures_ifreturn'] = function(block, generator) { +export function procedures_ifreturn(block, generator) { // Conditionally return value from a procedure. const condition = generator.valueToCode(block, 'CONDITION', Order.NONE) || 'false'; diff --git a/generators/dart/text.js b/generators/dart/text.js index 472c161df..a1dcade42 100644 --- a/generators/dart/text.js +++ b/generators/dart/text.js @@ -12,18 +12,18 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.Dart.texts'); import {NameType} from '../../core/names.js'; -import {dartGenerator, Order} from '../dart.js'; +import {Order} from './dart_generator.js'; -dartGenerator.addReservedWords('Html,Math'); +// RESERVED WORDS: 'Html,Math' -dartGenerator.forBlock['text'] = function(block, generator) { +export function text(block, generator) { // Text value. const code = generator.quote_(block.getFieldValue('TEXT')); return [code, Order.ATOMIC]; }; -dartGenerator.forBlock['text_multiline'] = function(block, generator) { +export function text_multiline(block, generator) { // Text value. const code = generator.multiline_quote_(block.getFieldValue('TEXT')); const order = @@ -31,7 +31,7 @@ dartGenerator.forBlock['text_multiline'] = function(block, generator) { return [code, order]; }; -dartGenerator.forBlock['text_join'] = function(block, generator) { +export function text_join(block, generator) { // Create a string made up of any number of elements of any type. switch (block.itemCount_) { case 0: @@ -54,7 +54,7 @@ dartGenerator.forBlock['text_join'] = function(block, generator) { } }; -dartGenerator.forBlock['text_append'] = function(block, generator) { +export function text_append(block, generator) { // Append to a variable in place. const varName = generator.nameDB_.getName( @@ -63,21 +63,21 @@ dartGenerator.forBlock['text_append'] = function(block, generator) { return varName + ' = [' + varName + ', ' + value + '].join();\n'; }; -dartGenerator.forBlock['text_length'] = function(block, generator) { +export function text_length(block, generator) { // String or array length. const text = generator.valueToCode(block, 'VALUE', Order.UNARY_POSTFIX) || "''"; return [text + '.length', Order.UNARY_POSTFIX]; }; -dartGenerator.forBlock['text_isEmpty'] = function(block, generator) { +export function text_isEmpty(block, generator) { // Is the string null or array empty? const text = generator.valueToCode(block, 'VALUE', Order.UNARY_POSTFIX) || "''"; return [text + '.isEmpty', Order.UNARY_POSTFIX]; }; -dartGenerator.forBlock['text_indexOf'] = function(block, generator) { +export function text_indexOf(block, generator) { // Search the text for a substring. const operator = block.getFieldValue('END') === 'FIRST' ? 'indexOf' : 'lastIndexOf'; @@ -92,7 +92,7 @@ dartGenerator.forBlock['text_indexOf'] = function(block, generator) { return [code, Order.UNARY_POSTFIX]; }; -dartGenerator.forBlock['text_charAt'] = function(block, generator) { +export function text_charAt(block, generator) { // Get letter at index. // Note: Until January 2013 this block did not have the WHERE input. const where = block.getFieldValue('WHERE') || 'FROM_START'; @@ -141,7 +141,7 @@ String ${generator.FUNCTION_NAME_PLACEHOLDER_}(String text) { throw Error('Unhandled option (text_charAt).'); }; -dartGenerator.forBlock['text_getSubstring'] = function(block, generator) { +export function text_getSubstring(block, generator) { // Get substring. const where1 = block.getFieldValue('WHERE1'); const where2 = block.getFieldValue('WHERE2'); @@ -220,7 +220,7 @@ String ${generator.FUNCTION_NAME_PLACEHOLDER_}(String text, String where1, num a return [code, Order.UNARY_POSTFIX]; }; -dartGenerator.forBlock['text_changeCase'] = function(block, generator) { +export function text_changeCase(block, generator) { // Change capitalization. const OPERATORS = { 'UPPERCASE': '.toUpperCase()', @@ -257,7 +257,7 @@ String ${generator.FUNCTION_NAME_PLACEHOLDER_}(String str) { return [code, Order.UNARY_POSTFIX]; }; -dartGenerator.forBlock['text_trim'] = function(block, generator) { +export function text_trim(block, generator) { // Trim spaces. const OPERATORS = { 'LEFT': '.replaceFirst(new RegExp(r\'^\\s+\'), \'\')', @@ -270,13 +270,13 @@ dartGenerator.forBlock['text_trim'] = function(block, generator) { return [text + operator, Order.UNARY_POSTFIX]; }; -dartGenerator.forBlock['text_print'] = function(block, generator) { +export function text_print(block, generator) { // Print statement. const msg = generator.valueToCode(block, 'TEXT', Order.NONE) || "''"; return 'print(' + msg + ');\n'; }; -dartGenerator.forBlock['text_prompt_ext'] = function(block, generator) { +export function text_prompt_ext(block, generator) { // Prompt function. generator.definitions_['import_dart_html'] = 'import \'dart:html\' as Html;'; @@ -298,10 +298,9 @@ dartGenerator.forBlock['text_prompt_ext'] = function(block, generator) { return [code, Order.UNARY_POSTFIX]; }; -dartGenerator.forBlock['text_prompt'] = - dartGenerator.forBlock['text_prompt_ext']; +export const text_prompt = text_prompt_ext; -dartGenerator.forBlock['text_count'] = function(block, generator) { +export function text_count(block, generator) { const text = generator.valueToCode(block, 'TEXT', Order.NONE) || "''"; const sub = generator.valueToCode(block, 'SUB', Order.NONE) || "''"; // Substring count is not a native generator function. Define one. @@ -326,7 +325,7 @@ int ${generator.FUNCTION_NAME_PLACEHOLDER_}(String haystack, String needle) { return [code, Order.UNARY_POSTFIX]; }; -dartGenerator.forBlock['text_replace'] = function(block, generator) { +export function text_replace(block, generator) { const text = generator.valueToCode(block, 'TEXT', Order.UNARY_POSTFIX) || "''"; const from = generator.valueToCode(block, 'FROM', Order.NONE) || "''"; @@ -335,7 +334,7 @@ dartGenerator.forBlock['text_replace'] = function(block, generator) { return [code, Order.UNARY_POSTFIX]; }; -dartGenerator.forBlock['text_reverse'] = function(block, generator) { +export function text_reverse(block, generator) { // There isn't a sensible way to do this in generator. See: // http://stackoverflow.com/a/21613700/3529104 // Implementing something is possibly better than not implementing anything? diff --git a/generators/dart/variables.js b/generators/dart/variables.js index 61a9b97c4..2f628e39c 100644 --- a/generators/dart/variables.js +++ b/generators/dart/variables.js @@ -12,10 +12,10 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.Dart.variables'); import {NameType} from '../../core/names.js'; -import {dartGenerator, Order} from '../dart.js'; +import {Order} from './dart_generator.js'; -dartGenerator.forBlock['variables_get'] = function(block, generator) { +export function variables_get(block, generator) { // Variable getter. const code = generator.nameDB_.getName( @@ -23,7 +23,7 @@ dartGenerator.forBlock['variables_get'] = function(block, generator) { return [code, Order.ATOMIC]; }; -dartGenerator.forBlock['variables_set'] = function(block, generator) { +export function variables_set(block, generator) { // Variable setter. const argument0 = generator.valueToCode(block, 'VALUE', Order.ASSIGNMENT) || '0'; diff --git a/generators/dart/variables_dynamic.js b/generators/dart/variables_dynamic.js index f19320ded..00c2c3cd7 100644 --- a/generators/dart/variables_dynamic.js +++ b/generators/dart/variables_dynamic.js @@ -11,12 +11,9 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.Dart.variablesDynamic'); -import {dartGenerator} from '../dart.js'; -import './variables.js'; - // generator is dynamically typed. -dartGenerator.forBlock['variables_get_dynamic'] = - dartGenerator.forBlock['variables_get']; -dartGenerator.forBlock['variables_set_dynamic'] = - dartGenerator.forBlock['variables_set']; +export { + variables_get as variables_get_dynamic, + variables_set as variables_set_dynamic, +} from './variables.js'; diff --git a/generators/javascript.js b/generators/javascript.js index f1afb7d30..ec877c364 100644 --- a/generators/javascript.js +++ b/generators/javascript.js @@ -1,341 +1,40 @@ /** * @license - * Copyright 2012 Google LLC + * Copyright 2021 Google LLC * SPDX-License-Identifier: Apache-2.0 */ /** - * @fileoverview Helper functions for generating JavaScript for blocks. - * @suppress {checkTypes|globalThis} + * @fileoverview Complete helper functions for generating JavaScript for + * blocks. This is the entrypoint for javascript_compressed.js. + * @suppress {extraRequire} */ import * as goog from '../closure/goog/goog.js'; -goog.declareModuleId('Blockly.JavaScript'); +goog.declareModuleId('Blockly.JavaScript.all'); -import * as Variables from '../core/variables.js'; -import * as stringUtils from '../core/utils/string.js'; -// import type {Block} from '../core/block.js'; -import {CodeGenerator} from '../core/generator.js'; -import {Names, NameType} from '../core/names.js'; -// import type {Workspace} from '../core/workspace.js'; -import {inputTypes} from '../core/inputs/input_types.js'; +import {JavascriptGenerator} from './javascript/javascript_generator.js'; +import * as colour from './javascript/colour.js'; +import * as lists from './javascript/lists.js'; +import * as logic from './javascript/logic.js'; +import * as loops from './javascript/loops.js'; +import * as math from './javascript/math.js'; +import * as procedures from './javascript/procedures.js'; +import * as text from './javascript/text.js'; +import * as variables from './javascript/variables.js'; +import * as variablesDynamic from './javascript/variables_dynamic.js'; - -/** - * Order of operation ENUMs. - * https://developer.mozilla.org/en/JavaScript/Reference/Operators/Operator_Precedence - * @enum {number} - */ -export const Order = { - ATOMIC: 0, // 0 "" ... - NEW: 1.1, // new - MEMBER: 1.2, // . [] - FUNCTION_CALL: 2, // () - INCREMENT: 3, // ++ - DECREMENT: 3, // -- - BITWISE_NOT: 4.1, // ~ - UNARY_PLUS: 4.2, // + - UNARY_NEGATION: 4.3, // - - LOGICAL_NOT: 4.4, // ! - TYPEOF: 4.5, // typeof - VOID: 4.6, // void - DELETE: 4.7, // delete - AWAIT: 4.8, // await - EXPONENTIATION: 5.0, // ** - MULTIPLICATION: 5.1, // * - DIVISION: 5.2, // / - MODULUS: 5.3, // % - SUBTRACTION: 6.1, // - - ADDITION: 6.2, // + - BITWISE_SHIFT: 7, // << >> >>> - RELATIONAL: 8, // < <= > >= - IN: 8, // in - INSTANCEOF: 8, // instanceof - EQUALITY: 9, // == != === !== - BITWISE_AND: 10, // & - BITWISE_XOR: 11, // ^ - BITWISE_OR: 12, // | - LOGICAL_AND: 13, // && - LOGICAL_OR: 14, // || - CONDITIONAL: 15, // ?: - ASSIGNMENT: 16, //: += -= **= *= /= %= <<= >>= ... - YIELD: 17, // yield - COMMA: 18, // , - NONE: 99, // (...) -}; - -/** - * JavaScript code generator class. - */ -export class JavascriptGenerator extends CodeGenerator { - /** - * List of outer-inner pairings that do NOT require parentheses. - * @type {!Array>} - */ - ORDER_OVERRIDES = [ - // (foo()).bar -> foo().bar - // (foo())[0] -> foo()[0] - [Order.FUNCTION_CALL, Order.MEMBER], - // (foo())() -> foo()() - [Order.FUNCTION_CALL, Order.FUNCTION_CALL], - // (foo.bar).baz -> foo.bar.baz - // (foo.bar)[0] -> foo.bar[0] - // (foo[0]).bar -> foo[0].bar - // (foo[0])[1] -> foo[0][1] - [Order.MEMBER, Order.MEMBER], - // (foo.bar)() -> foo.bar() - // (foo[0])() -> foo[0]() - [Order.MEMBER, Order.FUNCTION_CALL], - - // !(!foo) -> !!foo - [Order.LOGICAL_NOT, Order.LOGICAL_NOT], - // a * (b * c) -> a * b * c - [Order.MULTIPLICATION, Order.MULTIPLICATION], - // a + (b + c) -> a + b + c - [Order.ADDITION, Order.ADDITION], - // a && (b && c) -> a && b && c - [Order.LOGICAL_AND, Order.LOGICAL_AND], - // a || (b || c) -> a || b || c - [Order.LOGICAL_OR, Order.LOGICAL_OR] - ]; - - constructor(name) { - super(name ?? 'JavaScript'); - this.isInitialized = false; - - // Copy Order values onto instance for backwards compatibility - // while ensuring they are not part of the publically-advertised - // API. - // - // TODO(#7085): deprecate these in due course. (Could initially - // replace data properties with get accessors that call - // deprecate.warn().) - for (const key in Order) { - this['ORDER_' + key] = Order[key]; - } - - // List of illegal variable names. This is not intended to be a - // security feature. Blockly is 100% client-side, so bypassing - // this list is trivial. This is intended to prevent users from - // accidentally clobbering a built-in object or function. - this.addReservedWords( - // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#Keywords - 'break,case,catch,class,const,continue,debugger,default,delete,do,' + - 'else,export,extends,finally,for,function,if,import,in,instanceof,' + - 'new,return,super,switch,this,throw,try,typeof,var,void,' + - 'while,with,yield,' + - 'enum,' + - 'implements,interface,let,package,private,protected,public,static,' + - 'await,' + - 'null,true,false,' + - // Magic variable. - 'arguments,' + - // Everything in the current environment (835 items in Chrome, - // 104 in Node). - Object.getOwnPropertyNames(globalThis).join(',') - ); - } - - /** - * Initialise the database of variable names. - * @param {!Workspace} workspace Workspace to generate code from. - */ - init(workspace) { - super.init(workspace); - - if (!this.nameDB_) { - this.nameDB_ = new Names(this.RESERVED_WORDS_); - } else { - this.nameDB_.reset(); - } - - this.nameDB_.setVariableMap(workspace.getVariableMap()); - this.nameDB_.populateVariables(workspace); - this.nameDB_.populateProcedures(workspace); - - const defvars = []; - // Add developer variables (not created or named by the user). - const devVarList = Variables.allDeveloperVariables(workspace); - for (let i = 0; i < devVarList.length; i++) { - defvars.push( - this.nameDB_.getName(devVarList[i], NameType.DEVELOPER_VARIABLE)); - } - - // 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.nameDB_.getName(variables[i].getId(), NameType.VARIABLE)); - } - - // Declare all of the variables. - if (defvars.length) { - this.definitions_['variables'] = 'var ' + defvars.join(', ') + ';'; - } - this.isInitialized = true; - } - - /** - * Prepend the generated code with the variable definitions. - * @param {string} code Generated code. - * @return {string} Completed code. - */ - finish(code) { - // Convert the definitions dictionary into a list. - const definitions = Object.values(this.definitions_); - // Call Blockly.CodeGenerator's finish. - super.finish(code); - this.isInitialized = false; - - this.nameDB_.reset(); - return definitions.join('\n\n') + '\n\n\n' + code; - } - - /** - * Naked values are top-level blocks with outputs that aren't plugged into - * anything. A trailing semicolon is needed to make this legal. - * @param {string} line Line of generated code. - * @return {string} Legal line of code. - */ - scrubNakedValue(line) { - return line + ';\n'; - } - - /** - * Encode a string as a properly escaped JavaScript string, complete with - * quotes. - * @param {string} string Text to encode. - * @return {string} JavaScript string. - * @protected - */ - quote_(string) { - // Can't use goog.string.quote since Google's style guide recommends - // JS string literals use single quotes. - string = string.replace(/\\/g, '\\\\') - .replace(/\n/g, '\\\n') - .replace(/'/g, '\\\''); - return '\'' + string + '\''; - } - - /** - * Encode a string as a properly escaped multiline JavaScript string, complete - * with quotes. - * @param {string} string Text to encode. - * @return {string} JavaScript string. - * @protected - */ - multiline_quote_(string) { - // Can't use goog.string.quote since Google's style guide recommends - // JS string literals use single quotes. - const lines = string.split(/\n/g).map(this.quote_); - return lines.join(' + \'\\n\' +\n'); - } - - /** - * Common tasks for generating JavaScript 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 JavaScript code created for this block. - * @param {boolean=} opt_thisOnly True to generate code for only this - * statement. - * @return {string} JavaScript code with comments and subsequent blocks added. - * @protected - */ - scrub_(block, code, opt_thisOnly) { - let commentCode = ''; - // Only collect comments for blocks that aren't inline. - if (!block.outputConnection || !block.outputConnection.targetConnection) { - // Collect comment for this block. - let comment = block.getCommentText(); - if (comment) { - comment = stringUtils.wrap(comment, this.COMMENT_WRAP - 3); - commentCode += this.prefixLines(comment + '\n', '// '); - } - // Collect comments for all value arguments. - // 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(); - if (childBlock) { - comment = this.allNestedComments(childBlock); - if (comment) { - commentCode += this.prefixLines(comment, '// '); - } - } - } - } - } - const nextBlock = - block.nextConnection && block.nextConnection.targetBlock(); - const nextCode = opt_thisOnly ? '' : this.blockToCode(nextBlock); - return commentCode + code + nextCode; - } - - /** - * Gets a property and adjusts the value while taking into account indexing. - * @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. - * @param {number=} opt_order The highest order acting on this value. - * @return {string|number} - */ - getAdjusted(block, atId, opt_delta, opt_negate, opt_order) { - let delta = opt_delta || 0; - let order = opt_order || this.ORDER_NONE; - if (block.workspace.options.oneBasedIndex) { - delta--; - } - const defaultAtIndex = block.workspace.options.oneBasedIndex ? '1' : '0'; - - let innerOrder; - let outerOrder = order; - if (delta > 0) { - outerOrder = this.ORDER_ADDITION; - innerOrder = this.ORDER_ADDITION; - } else if (delta < 0) { - outerOrder = this.ORDER_SUBTRACTION; - innerOrder = this.ORDER_SUBTRACTION; - } else if (opt_negate) { - outerOrder = this.ORDER_UNARY_NEGATION; - innerOrder = this.ORDER_UNARY_NEGATION; - } - - let at = this.valueToCode(block, atId, outerOrder) || defaultAtIndex; - - if (stringUtils.isNumber(at)) { - // If the index is a naked number, adjust it right now. - at = Number(at) + delta; - if (opt_negate) { - at = -at; - } - } else { - // If the index is dynamic, adjust it in code. - if (delta > 0) { - at = at + ' + ' + delta; - } else if (delta < 0) { - at = at + ' - ' + -delta; - } - if (opt_negate) { - if (delta) { - at = '-(' + at + ')'; - } else { - at = '-' + at; - } - } - innerOrder = Math.floor(innerOrder); - order = Math.floor(order); - if (innerOrder && order >= innerOrder) { - at = '(' + at + ')'; - } - } - return at; - } -} +export * from './javascript/javascript_generator.js'; /** * JavaScript code generator instance. * @type {!JavascriptGenerator} */ export const javascriptGenerator = new JavascriptGenerator(); + +// Install per-block-type generator functions: +Object.assign( + javascriptGenerator.forBlock, + colour, lists, logic, loops, math, procedures, + text, variables, variablesDynamic +); diff --git a/generators/javascript/all.js b/generators/javascript/all.js deleted file mode 100644 index 430a8d86f..000000000 --- a/generators/javascript/all.js +++ /dev/null @@ -1,26 +0,0 @@ -/** - * @license - * Copyright 2021 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * @fileoverview Complete helper functions for generating JavaScript for - * blocks. This is the entrypoint for javascript_compressed.js. - * @suppress {extraRequire} - */ - -import * as goog from '../../closure/goog/goog.js'; -goog.declareModuleId('Blockly.JavaScript.all'); - -import './colour.js'; -import './lists.js'; -import './logic.js'; -import './loops.js'; -import './math.js'; -import './procedures.js'; -import './text.js'; -import './variables.js'; -import './variables_dynamic.js'; - -export * from '../javascript.js'; diff --git a/generators/javascript/colour.js b/generators/javascript/colour.js index 3e0ab2882..d8976d3e9 100644 --- a/generators/javascript/colour.js +++ b/generators/javascript/colour.js @@ -11,16 +11,16 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.JavaScript.colour'); -import {Order, javascriptGenerator} from '../javascript.js'; +import {Order} from './javascript_generator.js'; -javascriptGenerator.forBlock['colour_picker'] = function(block, generator) { +export function colour_picker(block, generator) { // Colour picker. const code = generator.quote_(block.getFieldValue('COLOUR')); return [code, Order.ATOMIC]; }; -javascriptGenerator.forBlock['colour_random'] = function(block, generator) { +export function colour_random(block, generator) { // Generate a random colour. const functionName = generator.provideFunction_('colourRandom', ` function ${generator.FUNCTION_NAME_PLACEHOLDER_}() { @@ -32,7 +32,7 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}() { return [code, Order.FUNCTION_CALL]; }; -javascriptGenerator.forBlock['colour_rgb'] = function(block, generator) { +export function colour_rgb(block, generator) { // Compose a colour from RGB components expressed as percentages. const red = generator.valueToCode(block, 'RED', Order.NONE) || 0; const green = @@ -54,7 +54,7 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}(r, g, b) { return [code, Order.FUNCTION_CALL]; }; -javascriptGenerator.forBlock['colour_blend'] = function(block, generator) { +export function colour_blend(block, generator) { // Blend two colours together. const c1 = generator.valueToCode(block, 'COLOUR1', Order.NONE) || "'#000000'"; diff --git a/generators/javascript/javascript_generator.js b/generators/javascript/javascript_generator.js new file mode 100644 index 000000000..18811011c --- /dev/null +++ b/generators/javascript/javascript_generator.js @@ -0,0 +1,335 @@ +/** + * @license + * Copyright 2012 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @fileoverview Helper functions for generating JavaScript for blocks. + * @suppress {checkTypes|globalThis} + */ + +import * as goog from '../../closure/goog/goog.js'; +goog.declareModuleId('Blockly.JavaScript'); + +import * as Variables from '../../core/variables.js'; +import * as stringUtils from '../../core/utils/string.js'; +// import type {Block} from '../../core/block.js'; +import {CodeGenerator} from '../../core/generator.js'; +import {Names, NameType} from '../../core/names.js'; +// import type {Workspace} from '../../core/workspace.js'; +import {inputTypes} from '../../core/inputs/input_types.js'; + + +/** + * Order of operation ENUMs. + * https://developer.mozilla.org/en/JavaScript/Reference/Operators/Operator_Precedence + * @enum {number} + */ +export const Order = { + ATOMIC: 0, // 0 "" ... + NEW: 1.1, // new + MEMBER: 1.2, // . [] + FUNCTION_CALL: 2, // () + INCREMENT: 3, // ++ + DECREMENT: 3, // -- + BITWISE_NOT: 4.1, // ~ + UNARY_PLUS: 4.2, // + + UNARY_NEGATION: 4.3, // - + LOGICAL_NOT: 4.4, // ! + TYPEOF: 4.5, // typeof + VOID: 4.6, // void + DELETE: 4.7, // delete + AWAIT: 4.8, // await + EXPONENTIATION: 5.0, // ** + MULTIPLICATION: 5.1, // * + DIVISION: 5.2, // / + MODULUS: 5.3, // % + SUBTRACTION: 6.1, // - + ADDITION: 6.2, // + + BITWISE_SHIFT: 7, // << >> >>> + RELATIONAL: 8, // < <= > >= + IN: 8, // in + INSTANCEOF: 8, // instanceof + EQUALITY: 9, // == != === !== + BITWISE_AND: 10, // & + BITWISE_XOR: 11, // ^ + BITWISE_OR: 12, // | + LOGICAL_AND: 13, // && + LOGICAL_OR: 14, // || + CONDITIONAL: 15, // ?: + ASSIGNMENT: 16, //: += -= **= *= /= %= <<= >>= ... + YIELD: 17, // yield + COMMA: 18, // , + NONE: 99, // (...) +}; + +/** + * JavaScript code generator class. + */ +export class JavascriptGenerator extends CodeGenerator { + /** + * List of outer-inner pairings that do NOT require parentheses. + * @type {!Array>} + */ + ORDER_OVERRIDES = [ + // (foo()).bar -> foo().bar + // (foo())[0] -> foo()[0] + [Order.FUNCTION_CALL, Order.MEMBER], + // (foo())() -> foo()() + [Order.FUNCTION_CALL, Order.FUNCTION_CALL], + // (foo.bar).baz -> foo.bar.baz + // (foo.bar)[0] -> foo.bar[0] + // (foo[0]).bar -> foo[0].bar + // (foo[0])[1] -> foo[0][1] + [Order.MEMBER, Order.MEMBER], + // (foo.bar)() -> foo.bar() + // (foo[0])() -> foo[0]() + [Order.MEMBER, Order.FUNCTION_CALL], + + // !(!foo) -> !!foo + [Order.LOGICAL_NOT, Order.LOGICAL_NOT], + // a * (b * c) -> a * b * c + [Order.MULTIPLICATION, Order.MULTIPLICATION], + // a + (b + c) -> a + b + c + [Order.ADDITION, Order.ADDITION], + // a && (b && c) -> a && b && c + [Order.LOGICAL_AND, Order.LOGICAL_AND], + // a || (b || c) -> a || b || c + [Order.LOGICAL_OR, Order.LOGICAL_OR] + ]; + + constructor(name) { + super(name ?? 'JavaScript'); + this.isInitialized = false; + + // Copy Order values onto instance for backwards compatibility + // while ensuring they are not part of the publically-advertised + // API. + // + // TODO(#7085): deprecate these in due course. (Could initially + // replace data properties with get accessors that call + // deprecate.warn().) + for (const key in Order) { + this['ORDER_' + key] = Order[key]; + } + + // List of illegal variable names. This is not intended to be a + // security feature. Blockly is 100% client-side, so bypassing + // this list is trivial. This is intended to prevent users from + // accidentally clobbering a built-in object or function. + this.addReservedWords( + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#Keywords + 'break,case,catch,class,const,continue,debugger,default,delete,do,' + + 'else,export,extends,finally,for,function,if,import,in,instanceof,' + + 'new,return,super,switch,this,throw,try,typeof,var,void,' + + 'while,with,yield,' + + 'enum,' + + 'implements,interface,let,package,private,protected,public,static,' + + 'await,' + + 'null,true,false,' + + // Magic variable. + 'arguments,' + + // Everything in the current environment (835 items in Chrome, + // 104 in Node). + Object.getOwnPropertyNames(globalThis).join(',') + ); + } + + /** + * Initialise the database of variable names. + * @param {!Workspace} workspace Workspace to generate code from. + */ + init(workspace) { + super.init(workspace); + + if (!this.nameDB_) { + this.nameDB_ = new Names(this.RESERVED_WORDS_); + } else { + this.nameDB_.reset(); + } + + this.nameDB_.setVariableMap(workspace.getVariableMap()); + this.nameDB_.populateVariables(workspace); + this.nameDB_.populateProcedures(workspace); + + const defvars = []; + // Add developer variables (not created or named by the user). + const devVarList = Variables.allDeveloperVariables(workspace); + for (let i = 0; i < devVarList.length; i++) { + defvars.push( + this.nameDB_.getName(devVarList[i], NameType.DEVELOPER_VARIABLE)); + } + + // 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.nameDB_.getName(variables[i].getId(), NameType.VARIABLE)); + } + + // Declare all of the variables. + if (defvars.length) { + this.definitions_['variables'] = 'var ' + defvars.join(', ') + ';'; + } + this.isInitialized = true; + } + + /** + * Prepend the generated code with the variable definitions. + * @param {string} code Generated code. + * @return {string} Completed code. + */ + finish(code) { + // Convert the definitions dictionary into a list. + const definitions = Object.values(this.definitions_); + // Call Blockly.CodeGenerator's finish. + super.finish(code); + this.isInitialized = false; + + this.nameDB_.reset(); + return definitions.join('\n\n') + '\n\n\n' + code; + } + + /** + * Naked values are top-level blocks with outputs that aren't plugged into + * anything. A trailing semicolon is needed to make this legal. + * @param {string} line Line of generated code. + * @return {string} Legal line of code. + */ + scrubNakedValue(line) { + return line + ';\n'; + } + + /** + * Encode a string as a properly escaped JavaScript string, complete with + * quotes. + * @param {string} string Text to encode. + * @return {string} JavaScript string. + * @protected + */ + quote_(string) { + // Can't use goog.string.quote since Google's style guide recommends + // JS string literals use single quotes. + string = string.replace(/\\/g, '\\\\') + .replace(/\n/g, '\\\n') + .replace(/'/g, '\\\''); + return '\'' + string + '\''; + } + + /** + * Encode a string as a properly escaped multiline JavaScript string, complete + * with quotes. + * @param {string} string Text to encode. + * @return {string} JavaScript string. + * @protected + */ + multiline_quote_(string) { + // Can't use goog.string.quote since Google's style guide recommends + // JS string literals use single quotes. + const lines = string.split(/\n/g).map(this.quote_); + return lines.join(' + \'\\n\' +\n'); + } + + /** + * Common tasks for generating JavaScript 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 JavaScript code created for this block. + * @param {boolean=} opt_thisOnly True to generate code for only this + * statement. + * @return {string} JavaScript code with comments and subsequent blocks added. + * @protected + */ + scrub_(block, code, opt_thisOnly) { + let commentCode = ''; + // Only collect comments for blocks that aren't inline. + if (!block.outputConnection || !block.outputConnection.targetConnection) { + // Collect comment for this block. + let comment = block.getCommentText(); + if (comment) { + comment = stringUtils.wrap(comment, this.COMMENT_WRAP - 3); + commentCode += this.prefixLines(comment + '\n', '// '); + } + // Collect comments for all value arguments. + // 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(); + if (childBlock) { + comment = this.allNestedComments(childBlock); + if (comment) { + commentCode += this.prefixLines(comment, '// '); + } + } + } + } + } + const nextBlock = + block.nextConnection && block.nextConnection.targetBlock(); + const nextCode = opt_thisOnly ? '' : this.blockToCode(nextBlock); + return commentCode + code + nextCode; + } + + /** + * Gets a property and adjusts the value while taking into account indexing. + * @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. + * @param {number=} opt_order The highest order acting on this value. + * @return {string|number} + */ + getAdjusted(block, atId, opt_delta, opt_negate, opt_order) { + let delta = opt_delta || 0; + let order = opt_order || this.ORDER_NONE; + if (block.workspace.options.oneBasedIndex) { + delta--; + } + const defaultAtIndex = block.workspace.options.oneBasedIndex ? '1' : '0'; + + let innerOrder; + let outerOrder = order; + if (delta > 0) { + outerOrder = this.ORDER_ADDITION; + innerOrder = this.ORDER_ADDITION; + } else if (delta < 0) { + outerOrder = this.ORDER_SUBTRACTION; + innerOrder = this.ORDER_SUBTRACTION; + } else if (opt_negate) { + outerOrder = this.ORDER_UNARY_NEGATION; + innerOrder = this.ORDER_UNARY_NEGATION; + } + + let at = this.valueToCode(block, atId, outerOrder) || defaultAtIndex; + + if (stringUtils.isNumber(at)) { + // If the index is a naked number, adjust it right now. + at = Number(at) + delta; + if (opt_negate) { + at = -at; + } + } else { + // If the index is dynamic, adjust it in code. + if (delta > 0) { + at = at + ' + ' + delta; + } else if (delta < 0) { + at = at + ' - ' + -delta; + } + if (opt_negate) { + if (delta) { + at = '-(' + at + ')'; + } else { + at = '-' + at; + } + } + innerOrder = Math.floor(innerOrder); + order = Math.floor(order); + if (innerOrder && order >= innerOrder) { + at = '(' + at + ')'; + } + } + return at; + } +} diff --git a/generators/javascript/lists.js b/generators/javascript/lists.js index ff9041ea2..3106906b0 100644 --- a/generators/javascript/lists.js +++ b/generators/javascript/lists.js @@ -13,15 +13,15 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.JavaScript.lists'); import {NameType} from '../../core/names.js'; -import {Order, javascriptGenerator} from '../javascript.js'; +import {Order} from './javascript_generator.js'; -javascriptGenerator.forBlock['lists_create_empty'] = function(block, generator) { +export function lists_create_empty(block, generator) { // Create an empty list. return ['[]', Order.ATOMIC]; }; -javascriptGenerator.forBlock['lists_create_with'] = function(block, generator) { +export function lists_create_with(block, generator) { // 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++) { @@ -33,7 +33,7 @@ javascriptGenerator.forBlock['lists_create_with'] = function(block, generator) { return [code, Order.ATOMIC]; }; -javascriptGenerator.forBlock['lists_repeat'] = function(block, generator) { +export function lists_repeat(block, generator) { // Create a list with one element repeated. const functionName = generator.provideFunction_('listsRepeat', ` function ${generator.FUNCTION_NAME_PLACEHOLDER_}(value, n) { @@ -52,21 +52,21 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}(value, n) { return [code, Order.FUNCTION_CALL]; }; -javascriptGenerator.forBlock['lists_length'] = function(block, generator) { +export function lists_length(block, generator) { // String or array length. const list = generator.valueToCode(block, 'VALUE', Order.MEMBER) || '[]'; return [list + '.length', Order.MEMBER]; }; -javascriptGenerator.forBlock['lists_isEmpty'] = function(block, generator) { +export function lists_isEmpty(block, generator) { // Is the string null or array empty? const list = generator.valueToCode(block, 'VALUE', Order.MEMBER) || '[]'; return ['!' + list + '.length', Order.LOGICAL_NOT]; }; -javascriptGenerator.forBlock['lists_indexOf'] = function(block, generator) { +export function lists_indexOf(block, generator) { // Find an item in the list. const operator = block.getFieldValue('END') === 'FIRST' ? 'indexOf' : 'lastIndexOf'; @@ -81,7 +81,7 @@ javascriptGenerator.forBlock['lists_indexOf'] = function(block, generator) { return [code, Order.FUNCTION_CALL]; }; -javascriptGenerator.forBlock['lists_getIndex'] = function(block, generator) { +export function lists_getIndex(block, generator) { // Get element at index. // Note: Until January 2013 this block did not have MODE or WHERE inputs. const mode = block.getFieldValue('MODE') || 'GET'; @@ -164,7 +164,7 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}(list, remove) { throw Error('Unhandled combination (lists_getIndex).'); }; -javascriptGenerator.forBlock['lists_setIndex'] = function(block, generator) { +export function lists_setIndex(block, generator) { // Set element at index. // Note: Until February 2013 this block did not have MODE or WHERE inputs. let list = @@ -266,7 +266,7 @@ const getSubstringIndex = function(listName, where, opt_at) { } }; -javascriptGenerator.forBlock['lists_getSublist'] = function(block, generator) { +export function lists_getSublist(block, generator) { // Get sublist. const list = generator.valueToCode(block, 'LIST', Order.MEMBER) || '[]'; @@ -346,7 +346,7 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}(sequence${at1Param}${at2Param}) return [code, Order.FUNCTION_CALL]; }; -javascriptGenerator.forBlock['lists_sort'] = function(block, generator) { +export function lists_sort(block, generator) { // Block for sorting a list. const list = generator.valueToCode(block, 'LIST', Order.FUNCTION_CALL) || @@ -375,7 +375,7 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}(type, direction) { ]; }; -javascriptGenerator.forBlock['lists_split'] = function(block, generator) { +export function lists_split(block, generator) { // Block for splitting text into a list, or joining a list into text. let input = generator.valueToCode(block, 'INPUT', Order.MEMBER); const delimiter = @@ -399,7 +399,7 @@ javascriptGenerator.forBlock['lists_split'] = function(block, generator) { return [code, Order.FUNCTION_CALL]; }; -javascriptGenerator.forBlock['lists_reverse'] = function(block, generator) { +export function lists_reverse(block, generator) { // Block for reversing a list. const list = generator.valueToCode(block, 'LIST', Order.FUNCTION_CALL) || diff --git a/generators/javascript/logic.js b/generators/javascript/logic.js index 2070e1e04..8cbdc1b5e 100644 --- a/generators/javascript/logic.js +++ b/generators/javascript/logic.js @@ -11,10 +11,10 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.JavaScript.logic'); -import {Order, javascriptGenerator} from '../javascript.js'; +import {Order} from './javascript_generator.js'; -javascriptGenerator.forBlock['controls_if'] = function(block, generator) { +export function controls_if(block, generator) { // If/elseif/else condition. let n = 0; let code = ''; @@ -54,10 +54,9 @@ javascriptGenerator.forBlock['controls_if'] = function(block, generator) { return code + '\n'; }; -javascriptGenerator.forBlock['controls_ifelse'] = - javascriptGenerator.forBlock['controls_if']; +export const controls_ifelse = controls_if; -javascriptGenerator.forBlock['logic_compare'] = function(block, generator) { +export function logic_compare(block, generator) { // Comparison operator. const OPERATORS = {'EQ': '==', 'NEQ': '!=', 'LT': '<', 'LTE': '<=', 'GT': '>', 'GTE': '>='}; @@ -71,7 +70,7 @@ javascriptGenerator.forBlock['logic_compare'] = function(block, generator) { return [code, order]; }; -javascriptGenerator.forBlock['logic_operation'] = function(block, generator) { +export function logic_operation(block, generator) { // Operations 'and', 'or'. const operator = (block.getFieldValue('OP') === 'AND') ? '&&' : '||'; const order = (operator === '&&') ? Order.LOGICAL_AND : @@ -96,7 +95,7 @@ javascriptGenerator.forBlock['logic_operation'] = function(block, generator) { return [code, order]; }; -javascriptGenerator.forBlock['logic_negate'] = function(block, generator) { +export function logic_negate(block, generator) { // Negation. const order = Order.LOGICAL_NOT; const argument0 = @@ -105,18 +104,18 @@ javascriptGenerator.forBlock['logic_negate'] = function(block, generator) { return [code, order]; }; -javascriptGenerator.forBlock['logic_boolean'] = function(block, generator) { +export function logic_boolean(block, generator) { // Boolean values true and false. const code = (block.getFieldValue('BOOL') === 'TRUE') ? 'true' : 'false'; return [code, Order.ATOMIC]; }; -javascriptGenerator.forBlock['logic_null'] = function(block, generator) { +export function logic_null(block, generator) { // Null data type. return ['null', Order.ATOMIC]; }; -javascriptGenerator.forBlock['logic_ternary'] = function(block, generator) { +export function logic_ternary(block, generator) { // Ternary operator. const value_if = generator.valueToCode(block, 'IF', Order.CONDITIONAL) || diff --git a/generators/javascript/loops.js b/generators/javascript/loops.js index cafbd3a50..c93438b56 100644 --- a/generators/javascript/loops.js +++ b/generators/javascript/loops.js @@ -13,10 +13,10 @@ goog.declareModuleId('Blockly.JavaScript.loops'); import * as stringUtils from '../../core/utils/string.js'; import {NameType} from '../../core/names.js'; -import {Order, javascriptGenerator} from '../javascript.js'; +import {Order} from './javascript_generator.js'; -javascriptGenerator.forBlock['controls_repeat_ext'] = function(block, generator) { +export function controls_repeat_ext(block, generator) { // Repeat n times. let repeats; if (block.getField('TIMES')) { @@ -45,10 +45,9 @@ javascriptGenerator.forBlock['controls_repeat_ext'] = function(block, generator) return code; }; -javascriptGenerator.forBlock['controls_repeat'] = - javascriptGenerator.forBlock['controls_repeat_ext']; +export const controls_repeat = controls_repeat_ext; -javascriptGenerator.forBlock['controls_whileUntil'] = function(block, generator) { +export function controls_whileUntil(block, generator) { // Do while/until loop. const until = block.getFieldValue('MODE') === 'UNTIL'; let argument0 = @@ -64,7 +63,7 @@ javascriptGenerator.forBlock['controls_whileUntil'] = function(block, generator) return 'while (' + argument0 + ') {\n' + branch + '}\n'; }; -javascriptGenerator.forBlock['controls_for'] = function(block, generator) { +export function controls_for(block, generator) { // For loop. const variable0 = generator.nameDB_.getName( @@ -127,7 +126,7 @@ javascriptGenerator.forBlock['controls_for'] = function(block, generator) { return code; }; -javascriptGenerator.forBlock['controls_forEach'] = function(block, generator) { +export function controls_forEach(block, generator) { // For each loop. const variable0 = generator.nameDB_.getName( @@ -153,7 +152,7 @@ javascriptGenerator.forBlock['controls_forEach'] = function(block, generator) { return code; }; -javascriptGenerator.forBlock['controls_flow_statements'] = function(block, generator) { +export function controls_flow_statements(block, generator) { // Flow statements: continue, break. let xfix = ''; if (generator.STATEMENT_PREFIX) { diff --git a/generators/javascript/math.js b/generators/javascript/math.js index 7089f8ac3..ecba63523 100644 --- a/generators/javascript/math.js +++ b/generators/javascript/math.js @@ -13,10 +13,10 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.JavaScript.math'); import {NameType} from '../../core/names.js'; -import {Order, javascriptGenerator} from '../javascript.js'; +import {Order} from './javascript_generator.js'; -javascriptGenerator.forBlock['math_number'] = function(block, generator) { +export function math_number(block, generator) { // Numeric value. const code = Number(block.getFieldValue('NUM')); const order = code >= 0 ? Order.ATOMIC : @@ -24,7 +24,7 @@ javascriptGenerator.forBlock['math_number'] = function(block, generator) { return [code, order]; }; -javascriptGenerator.forBlock['math_arithmetic'] = function(block, generator) { +export function math_arithmetic(block, generator) { // Basic arithmetic operators, and power. const OPERATORS = { 'ADD': [' + ', Order.ADDITION], @@ -48,7 +48,7 @@ javascriptGenerator.forBlock['math_arithmetic'] = function(block, generator) { return [code, order]; }; -javascriptGenerator.forBlock['math_single'] = function(block, generator) { +export function math_single(block, generator) { // Math operators with single operand. const operator = block.getFieldValue('OP'); let code; @@ -132,7 +132,7 @@ javascriptGenerator.forBlock['math_single'] = function(block, generator) { return [code, Order.DIVISION]; }; -javascriptGenerator.forBlock['math_constant'] = function(block, generator) { +export function math_constant(block, generator) { // Constants: PI, E, the Golden Ratio, sqrt(2), 1/sqrt(2), INFINITY. const CONSTANTS = { 'PI': ['Math.PI', Order.MEMBER], @@ -145,7 +145,7 @@ javascriptGenerator.forBlock['math_constant'] = function(block, generator) { return CONSTANTS[block.getFieldValue('CONSTANT')]; }; -javascriptGenerator.forBlock['math_number_property'] = function(block, generator) { +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 = { @@ -199,7 +199,7 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}(n) { return [code, outputOrder]; }; -javascriptGenerator.forBlock['math_change'] = function(block, generator) { +export function math_change(block, generator) { // Add to a variable in place. const argument0 = generator.valueToCode(block, 'DELTA', Order.ADDITION) || '0'; @@ -210,13 +210,11 @@ javascriptGenerator.forBlock['math_change'] = function(block, generator) { }; // Rounding functions have a single operand. -javascriptGenerator.forBlock['math_round'] = - javascriptGenerator.forBlock['math_single']; +export const math_round = math_single; // Trigonometry functions have a single operand. -javascriptGenerator.forBlock['math_trig'] = - javascriptGenerator.forBlock['math_single']; +export const math_trig = math_single; -javascriptGenerator.forBlock['math_on_list'] = function(block, generator) { +export function math_on_list(block, generator) { // Math functions for lists. const func = block.getFieldValue('OP'); let list; @@ -346,7 +344,7 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}(list) { return [code, Order.FUNCTION_CALL]; }; -javascriptGenerator.forBlock['math_modulo'] = function(block, generator) { +export function math_modulo(block, generator) { // Remainder computation. const argument0 = generator.valueToCode(block, 'DIVIDEND', Order.MODULUS) || '0'; @@ -356,7 +354,7 @@ javascriptGenerator.forBlock['math_modulo'] = function(block, generator) { return [code, Order.MODULUS]; }; -javascriptGenerator.forBlock['math_constrain'] = function(block, generator) { +export function math_constrain(block, generator) { // Constrain a number between two limits. const argument0 = generator.valueToCode(block, 'VALUE', Order.NONE) || '0'; @@ -369,7 +367,7 @@ javascriptGenerator.forBlock['math_constrain'] = function(block, generator) { return [code, Order.FUNCTION_CALL]; }; -javascriptGenerator.forBlock['math_random_int'] = function(block, generator) { +export function math_random_int(block, generator) { // Random integer between [X] and [Y]. const argument0 = generator.valueToCode(block, 'FROM', Order.NONE) || '0'; @@ -390,12 +388,12 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}(a, b) { return [code, Order.FUNCTION_CALL]; }; -javascriptGenerator.forBlock['math_random_float'] = function(block, generator) { +export function math_random_float(block, generator) { // Random fraction between 0 and 1. return ['Math.random()', Order.FUNCTION_CALL]; }; -javascriptGenerator.forBlock['math_atan2'] = function(block, generator) { +export function math_atan2(block, generator) { // Arctangent of point (X, Y) in degrees from -180 to 180. const argument0 = generator.valueToCode(block, 'X', Order.NONE) || '0'; diff --git a/generators/javascript/procedures.js b/generators/javascript/procedures.js index 1dae9c327..baa0ebe48 100644 --- a/generators/javascript/procedures.js +++ b/generators/javascript/procedures.js @@ -12,10 +12,10 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.JavaScript.procedures'); import {NameType} from '../../core/names.js'; -import {Order, javascriptGenerator} from '../javascript.js'; +import {Order} from './javascript_generator.js'; -javascriptGenerator.forBlock['procedures_defreturn'] = function(block, generator) { +export function procedures_defreturn(block, generator) { // Define a procedure with a return value. const funcName = generator.nameDB_.getName( block.getFieldValue('NAME'), NameType.PROCEDURE); @@ -65,10 +65,9 @@ javascriptGenerator.forBlock['procedures_defreturn'] = function(block, generator // Defining a procedure without a return value uses the same generator as // a procedure with a return value. -javascriptGenerator.forBlock['procedures_defnoreturn'] = - javascriptGenerator.forBlock['procedures_defreturn']; +export const procedures_defnoreturn = procedures_defreturn; -javascriptGenerator.forBlock['procedures_callreturn'] = function(block, generator) { +export function procedures_callreturn(block, generator) { // Call a procedure with a return value. const funcName = generator.nameDB_.getName( block.getFieldValue('NAME'), NameType.PROCEDURE); @@ -82,7 +81,7 @@ javascriptGenerator.forBlock['procedures_callreturn'] = function(block, generato return [code, Order.FUNCTION_CALL]; }; -javascriptGenerator.forBlock['procedures_callnoreturn'] = function(block, generator) { +export function procedures_callnoreturn(block, generator) { // 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. @@ -90,7 +89,7 @@ javascriptGenerator.forBlock['procedures_callnoreturn'] = function(block, genera return tuple[0] + ';\n'; }; -javascriptGenerator.forBlock['procedures_ifreturn'] = function(block, generator) { +export function procedures_ifreturn(block, generator) { // Conditionally return value from a procedure. const condition = generator.valueToCode(block, 'CONDITION', Order.NONE) || diff --git a/generators/javascript/text.js b/generators/javascript/text.js index e4da2b9df..e3761293b 100644 --- a/generators/javascript/text.js +++ b/generators/javascript/text.js @@ -12,7 +12,7 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.JavaScript.texts'); import {NameType} from '../../core/names.js'; -import {Order, javascriptGenerator} from '../javascript.js'; +import {Order} from './javascript_generator.js'; /** @@ -53,13 +53,13 @@ const getSubstringIndex = function(stringName, where, opt_at) { } }; -javascriptGenerator.forBlock['text'] = function(block, generator) { +export function text(block, generator) { // Text value. const code = generator.quote_(block.getFieldValue('TEXT')); return [code, Order.ATOMIC]; }; -javascriptGenerator.forBlock['text_multiline'] = function(block, generator) { +export function text_multiline(block, generator) { // Text value. const code = generator.multiline_quote_(block.getFieldValue('TEXT')); @@ -68,7 +68,7 @@ javascriptGenerator.forBlock['text_multiline'] = function(block, generator) { return [code, order]; }; -javascriptGenerator.forBlock['text_join'] = function(block, generator) { +export function text_join(block, generator) { // Create a string made up of any number of elements of any type. switch (block.itemCount_) { case 0: @@ -100,7 +100,7 @@ javascriptGenerator.forBlock['text_join'] = function(block, generator) { } }; -javascriptGenerator.forBlock['text_append'] = function(block, generator) { +export function text_append(block, generator) { // Append to a variable in place. const varName = generator.nameDB_.getName( block.getFieldValue('VAR'), NameType.VARIABLE); @@ -111,21 +111,21 @@ javascriptGenerator.forBlock['text_append'] = function(block, generator) { return code; }; -javascriptGenerator.forBlock['text_length'] = function(block, generator) { +export function text_length(block, generator) { // String or array length. const text = generator.valueToCode(block, 'VALUE', Order.MEMBER) || "''"; return [text + '.length', Order.MEMBER]; }; -javascriptGenerator.forBlock['text_isEmpty'] = function(block, generator) { +export function text_isEmpty(block, generator) { // Is the string null or array empty? const text = generator.valueToCode(block, 'VALUE', Order.MEMBER) || "''"; return ['!' + text + '.length', Order.LOGICAL_NOT]; }; -javascriptGenerator.forBlock['text_indexOf'] = function(block, generator) { +export function text_indexOf(block, generator) { // Search the text for a substring. const operator = block.getFieldValue('END') === 'FIRST' ? 'indexOf' : 'lastIndexOf'; @@ -141,7 +141,7 @@ javascriptGenerator.forBlock['text_indexOf'] = function(block, generator) { return [code, Order.FUNCTION_CALL]; }; -javascriptGenerator.forBlock['text_charAt'] = function(block, generator) { +export function text_charAt(block, generator) { // Get letter at index. // Note: Until January 2013 this block did not have the WHERE input. const where = block.getFieldValue('WHERE') || 'FROM_START'; @@ -184,7 +184,7 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}(text) { throw Error('Unhandled option (text_charAt).'); }; -javascriptGenerator.forBlock['text_getSubstring'] = function(block, generator) { +export function text_getSubstring(block, generator) { // Get substring. const where1 = block.getFieldValue('WHERE1'); const where2 = block.getFieldValue('WHERE2'); @@ -263,7 +263,7 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}(sequence${at1Param}${at2Param}) return [code, Order.FUNCTION_CALL]; }; -javascriptGenerator.forBlock['text_changeCase'] = function(block, generator) { +export function text_changeCase(block, generator) { // Change capitalization. const OPERATORS = { 'UPPERCASE': '.toUpperCase()', @@ -292,7 +292,7 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}(str) { return [code, Order.FUNCTION_CALL]; }; -javascriptGenerator.forBlock['text_trim'] = function(block, generator) { +export function text_trim(block, generator) { // Trim spaces. const OPERATORS = { 'LEFT': ".replace(/^[\\s\\xa0]+/, '')", @@ -305,14 +305,14 @@ javascriptGenerator.forBlock['text_trim'] = function(block, generator) { return [text + operator, Order.FUNCTION_CALL]; }; -javascriptGenerator.forBlock['text_print'] = function(block, generator) { +export function text_print(block, generator) { // Print statement. const msg = generator.valueToCode(block, 'TEXT', Order.NONE) || "''"; return 'window.alert(' + msg + ');\n'; }; -javascriptGenerator.forBlock['text_prompt_ext'] = function(block, generator) { +export function text_prompt_ext(block, generator) { // Prompt function. let msg; if (block.getField('TEXT')) { @@ -330,10 +330,9 @@ javascriptGenerator.forBlock['text_prompt_ext'] = function(block, generator) { return [code, Order.FUNCTION_CALL]; }; -javascriptGenerator.forBlock['text_prompt'] = - javascriptGenerator.forBlock['text_prompt_ext']; +export const text_prompt = text_prompt_ext; -javascriptGenerator.forBlock['text_count'] = function(block, generator) { +export function text_count(block, generator) { const text = generator.valueToCode(block, 'TEXT', Order.NONE) || "''"; const sub = generator.valueToCode(block, 'SUB', @@ -351,7 +350,7 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}(haystack, needle) { return [code, Order.FUNCTION_CALL]; }; -javascriptGenerator.forBlock['text_replace'] = function(block, generator) { +export function text_replace(block, generator) { const text = generator.valueToCode(block, 'TEXT', Order.NONE) || "''"; const from = generator.valueToCode(block, 'FROM', @@ -370,7 +369,7 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}(haystack, needle, replacement) return [code, Order.FUNCTION_CALL]; }; -javascriptGenerator.forBlock['text_reverse'] = function(block, generator) { +export function text_reverse(block, generator) { const text = generator.valueToCode(block, 'TEXT', Order.MEMBER) || "''"; const code = text + ".split('').reverse().join('')"; diff --git a/generators/javascript/variables.js b/generators/javascript/variables.js index e02197ed1..e67ce645b 100644 --- a/generators/javascript/variables.js +++ b/generators/javascript/variables.js @@ -12,17 +12,17 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.JavaScript.variables'); import {NameType} from '../../core/names.js'; -import {Order, javascriptGenerator} from '../javascript.js'; +import {Order} from './javascript_generator.js'; -javascriptGenerator.forBlock['variables_get'] = function(block, generator) { +export function variables_get(block, generator) { // Variable getter. const code = generator.nameDB_.getName(block.getFieldValue('VAR'), NameType.VARIABLE); return [code, Order.ATOMIC]; }; -javascriptGenerator.forBlock['variables_set'] = function(block, generator) { +export function variables_set(block, generator) { // Variable setter. const argument0 = generator.valueToCode( block, 'VALUE', Order.ASSIGNMENT) || '0'; diff --git a/generators/javascript/variables_dynamic.js b/generators/javascript/variables_dynamic.js index f37bff49d..aaae27311 100644 --- a/generators/javascript/variables_dynamic.js +++ b/generators/javascript/variables_dynamic.js @@ -11,11 +11,9 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.JavaScript.variablesDynamic'); -import {javascriptGenerator} from '../javascript.js'; -import './variables.js'; // JavaScript is dynamically typed. -javascriptGenerator.forBlock['variables_get_dynamic'] = - javascriptGenerator.forBlock['variables_get']; -javascriptGenerator.forBlock['variables_set_dynamic'] = - javascriptGenerator.forBlock['variables_set']; +export { + variables_get as variables_get_dynamic, + variables_set as variables_set_dynamic, +} from './variables.js'; diff --git a/generators/lua.js b/generators/lua.js index 391a0ca87..349ea2431 100644 --- a/generators/lua.js +++ b/generators/lua.js @@ -1,219 +1,40 @@ /** * @license - * Copyright 2016 Google LLC + * Copyright 2021 Google LLC * SPDX-License-Identifier: Apache-2.0 */ /** - * @fileoverview Helper functions for generating Lua for blocks. - * Based on Ellen Spertus's blocky-lua project. - * @suppress {checkTypes|globalThis} + * @fileoverview Complete helper functions for generating Lua for + * blocks. This is the entrypoint for lua_compressed.js. + * @suppress {extraRequire} */ import * as goog from '../closure/goog/goog.js'; -goog.declareModuleId('Blockly.Lua'); +goog.declareModuleId('Blockly.Lua.all'); -import * as stringUtils from '../core/utils/string.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 {inputTypes} from '../core/inputs/input_types.js'; +import {LuaGenerator} from './lua/lua_generator.js'; +import * as colour from './lua/colour.js'; +import * as lists from './lua/lists.js'; +import * as logic from './lua/logic.js'; +import * as loops from './lua/loops.js'; +import * as math from './lua/math.js'; +import * as procedures from './lua/procedures.js'; +import * as text from './lua/text.js'; +import * as variables from './lua/variables.js'; +import * as variablesDynamic from './lua/variables_dynamic.js'; +export * from './lua/lua_generator.js'; /** - * Order of operation ENUMs. - * http://www.lua.org/manual/5.3/manual.html#3.4.8 - * @enum {number} - */ -export const Order = { - ATOMIC: 0, // literals - // The next level was not explicit in documentation and inferred by Ellen. - HIGH: 1, // Function calls, tables[] - EXPONENTIATION: 2, // ^ - UNARY: 3, // not # - ~ - MULTIPLICATIVE: 4, // * / % - ADDITIVE: 5, // + - - CONCATENATION: 6, // .. - RELATIONAL: 7, // < > <= >= ~= == - AND: 8, // and - OR: 9, // or - NONE: 99, -}; - -/** - * Lua code generator class. - * - * Note: Lua is not supporting zero-indexing since the language itself is - * one-indexed, so the generator does not repoct the oneBasedIndex configuration - * option used for lists and text. - */ -class LuaGenerator extends CodeGenerator { - constructor(name) { - super(name ?? 'Lua'); - this.isInitialized = false; - - // Copy Order values onto instance for backwards compatibility - // while ensuring they are not part of the publically-advertised - // API. - // - // TODO(#7085): deprecate these in due course. (Could initially - // replace data properties with get accessors that call - // deprecate.warn().) - for (const key in Order) { - this['ORDER_' + key] = Order[key]; - } - - // List of illegal variable names. This is not intended to be a - // security feature. Blockly is 100% client-side, so bypassing - // this list is trivial. This is intended to prevent users from - // accidentally clobbering a built-in object or function. - this.addReservedWords( - // Special character - '_,' + - // From theoriginalbit's script: - // https://github.com/espertus/blockly-lua/issues/6 - '__inext,assert,bit,colors,colours,coroutine,disk,dofile,error,fs,' + - 'fetfenv,getmetatable,gps,help,io,ipairs,keys,loadfile,loadstring,math,' + - 'native,next,os,paintutils,pairs,parallel,pcall,peripheral,print,' + - 'printError,rawequal,rawget,rawset,read,rednet,redstone,rs,select,' + - 'setfenv,setmetatable,sleep,string,table,term,textutils,tonumber,' + - 'tostring,turtle,type,unpack,vector,write,xpcall,_VERSION,__indext,' + - // Not included in the script, probably because it wasn't enabled: - 'HTTP,' + - // Keywords (http://www.lua.org/pil/1.3.html). - 'and,break,do,else,elseif,end,false,for,function,if,in,local,nil,not,' + - 'or,repeat,return,then,true,until,while,' + - // Metamethods (http://www.lua.org/manual/5.2/manual.html). - 'add,sub,mul,div,mod,pow,unm,concat,len,eq,lt,le,index,newindex,call,' + - // Basic functions (http://www.lua.org/manual/5.2/manual.html, - // section 6.1). - 'assert,collectgarbage,dofile,error,_G,getmetatable,inpairs,load,' + - 'loadfile,next,pairs,pcall,print,rawequal,rawget,rawlen,rawset,select,' + - 'setmetatable,tonumber,tostring,type,_VERSION,xpcall,' + - // Modules (http://www.lua.org/manual/5.2/manual.html, section 6.3). - 'require,package,string,table,math,bit32,io,file,os,debug' - ); - } - - /** - * Initialise the database of variable names. - * @param {!Workspace} workspace Workspace to generate code from. - */ - init(workspace) { - // Call Blockly.CodeGenerator's init. - super.init(); - - if (!this.nameDB_) { - this.nameDB_ = new Names(this.RESERVED_WORDS_); - } else { - this.nameDB_.reset(); - } - this.nameDB_.setVariableMap(workspace.getVariableMap()); - this.nameDB_.populateVariables(workspace); - this.nameDB_.populateProcedures(workspace); - - this.isInitialized = true; - }; - - /** - * Prepend the generated code with the variable definitions. - * @param {string} code Generated code. - * @return {string} Completed code. - */ - finish(code) { - // Convert the definitions dictionary into a list. - const definitions = Object.values(this.definitions_); - // Call Blockly.CodeGenerator's finish. - code = super.finish(code); - this.isInitialized = false; - - this.nameDB_.reset(); - return definitions.join('\n\n') + '\n\n\n' + code; - }; - - /** - * Naked values are top-level blocks with outputs that aren't plugged into - * anything. In Lua, an expression is not a legal statement, so we must assign - * the value to the (conventionally ignored) _. - * http://lua-users.org/wiki/ExpressionsAsStatements - * @param {string} line Line of generated code. - * @return {string} Legal line of code. - */ - scrubNakedValue(line) { - return 'local _ = ' + line + '\n'; - }; - - /** - * Encode a string as a properly escaped Lua string, complete with - * quotes. - * @param {string} string Text to encode. - * @return {string} Lua string. - * @protected - */ - quote_(string) { - string = string.replace(/\\/g, '\\\\') - .replace(/\n/g, '\\\n') - .replace(/'/g, '\\\''); - return '\'' + string + '\''; - }; - - /** - * Encode a string as a properly escaped multiline Lua string, complete with - * quotes. - * @param {string} string Text to encode. - * @return {string} Lua string. - * @protected - */ - multiline_quote_(string) { - const lines = string.split(/\n/g).map(this.quote_); - // Join with the following, plus a newline: - // .. '\n' .. - return lines.join(' .. \'\\n\' ..\n'); - }; - - /** - * Common tasks for generating Lua 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 Lua code created for this block. - * @param {boolean=} opt_thisOnly True to generate code for only this statement. - * @return {string} Lua code with comments and subsequent blocks added. - * @protected - */ - scrub_(block, code, opt_thisOnly) { - let commentCode = ''; - // Only collect comments for blocks that aren't inline. - if (!block.outputConnection || !block.outputConnection.targetConnection) { - // Collect comment for this block. - let comment = block.getCommentText(); - if (comment) { - comment = stringUtils.wrap(comment, this.COMMENT_WRAP - 3); - commentCode += this.prefixLines(comment, '-- ') + '\n'; - } - // Collect comments for all value arguments. - // 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(); - if (childBlock) { - comment = this.allNestedComments(childBlock); - if (comment) { - commentCode += this.prefixLines(comment, '-- '); - } - } - } - } - } - const nextBlock = block.nextConnection && block.nextConnection.targetBlock(); - const nextCode = opt_thisOnly ? '' : this.blockToCode(nextBlock); - return commentCode + code + nextCode; - }; -} - -/** - * Lua code generator. + * Lua code generator instance. * @type {!LuaGenerator} */ -export const luaGenerator = new LuaGenerator('Lua'); +export const luaGenerator = new LuaGenerator(); + +// Install per-block-type generator functions: +Object.assign( + luaGenerator.forBlock, + colour, lists, logic, loops, math, procedures, + text, variables, variablesDynamic +); diff --git a/generators/lua/all.js b/generators/lua/all.js deleted file mode 100644 index 0607d5c3e..000000000 --- a/generators/lua/all.js +++ /dev/null @@ -1,26 +0,0 @@ -/** - * @license - * Copyright 2021 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * @fileoverview Complete helper functions for generating Lua for - * blocks. This is the entrypoint for lua_compressed.js. - * @suppress {extraRequire} - */ - -import * as goog from '../../closure/goog/goog.js'; -goog.declareModuleId('Blockly.Lua.all'); - -import './colour.js'; -import './lists.js'; -import './logic.js'; -import './loops.js'; -import './math.js'; -import './procedures.js'; -import './text.js'; -import './variables.js'; -import './variables_dynamic.js'; - -export * from '../lua.js'; diff --git a/generators/lua/colour.js b/generators/lua/colour.js index 85b081817..a69dd41c6 100644 --- a/generators/lua/colour.js +++ b/generators/lua/colour.js @@ -11,22 +11,22 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.Lua.colour'); -import {luaGenerator, Order} from '../lua.js'; +import {Order} from './lua_generator.js'; -luaGenerator.forBlock['colour_picker'] = function(block, generator) { +export function colour_picker(block, generator) { // Colour picker. const code = generator.quote_(block.getFieldValue('COLOUR')); return [code, Order.ATOMIC]; }; -luaGenerator.forBlock['colour_random'] = function(block, generator) { +export function colour_random(block, generator) { // Generate a random colour. const code = 'string.format("#%06x", math.random(0, 2^24 - 1))'; return [code, Order.HIGH]; }; -luaGenerator.forBlock['colour_rgb'] = function(block, generator) { +export function colour_rgb(block, generator) { // Compose a colour from RGB components expressed as percentages. const functionName = generator.provideFunction_('colour_rgb', ` function ${generator.FUNCTION_NAME_PLACEHOLDER_}(r, g, b) @@ -43,7 +43,7 @@ end return [code, Order.HIGH]; }; -luaGenerator.forBlock['colour_blend'] = function(block, generator) { +export function colour_blend(block, generator) { // Blend two colours together. const functionName = generator.provideFunction_('colour_blend', ` function ${generator.FUNCTION_NAME_PLACEHOLDER_}(colour1, colour2, ratio) diff --git a/generators/lua/lists.js b/generators/lua/lists.js index dac1806b9..8643911f3 100644 --- a/generators/lua/lists.js +++ b/generators/lua/lists.js @@ -12,15 +12,15 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.Lua.lists'); import {NameType} from '../../core/names.js'; -import {luaGenerator, Order} from '../lua.js'; +import {Order} from './lua_generator.js'; -luaGenerator.forBlock['lists_create_empty'] = function(block, generator) { +export function lists_create_empty(block, generator) { // Create an empty list. return ['{}', Order.HIGH]; }; -luaGenerator.forBlock['lists_create_with'] = function(block, generator) { +export function lists_create_with(block, generator) { // 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++) { @@ -31,7 +31,7 @@ luaGenerator.forBlock['lists_create_with'] = function(block, generator) { return [code, Order.HIGH]; }; -luaGenerator.forBlock['lists_repeat'] = function(block, generator) { +export function lists_repeat(block, generator) { // Create a list with one element repeated. const functionName = generator.provideFunction_('create_list_repeated', ` function ${generator.FUNCTION_NAME_PLACEHOLDER_}(item, count) @@ -48,20 +48,20 @@ end return [code, Order.HIGH]; }; -luaGenerator.forBlock['lists_length'] = function(block, generator) { +export function lists_length(block, generator) { // String or array length. const list = generator.valueToCode(block, 'VALUE', Order.UNARY) || '{}'; return ['#' + list, Order.UNARY]; }; -luaGenerator.forBlock['lists_isEmpty'] = function(block, generator) { +export function lists_isEmpty(block, generator) { // Is the string null or array empty? const list = generator.valueToCode(block, 'VALUE', Order.UNARY) || '{}'; const code = '#' + list + ' == 0'; return [code, Order.RELATIONAL]; }; -luaGenerator.forBlock['lists_indexOf'] = function(block, generator) { +export function lists_indexOf(block, generator) { // Find an item in the list. const item = generator.valueToCode(block, 'FIND', Order.NONE) || "''"; const list = generator.valueToCode(block, 'VALUE', Order.NONE) || '{}'; @@ -114,7 +114,7 @@ const getListIndex = function(listName, where, opt_at) { } }; -luaGenerator.forBlock['lists_getIndex'] = function(block, generator) { +export function lists_getIndex(block, generator) { // Get element at index. // Note: Until January 2013 this block did not have MODE or WHERE inputs. const mode = block.getFieldValue('MODE') || 'GET'; @@ -194,7 +194,7 @@ luaGenerator.forBlock['lists_getIndex'] = function(block, generator) { } }; -luaGenerator.forBlock['lists_setIndex'] = function(block, generator) { +export function lists_setIndex(block, generator) { // Set element at index. // Note: Until February 2013 this block did not have MODE or WHERE inputs. let list = generator.valueToCode(block, 'LIST', Order.HIGH) || '{}'; @@ -227,7 +227,7 @@ luaGenerator.forBlock['lists_setIndex'] = function(block, generator) { return code + '\n'; }; -luaGenerator.forBlock['lists_getSublist'] = function(block, generator) { +export function lists_getSublist(block, generator) { // Get sublist. const list = generator.valueToCode(block, 'LIST', Order.NONE) || '{}'; const where1 = block.getFieldValue('WHERE1'); @@ -262,7 +262,7 @@ end return [code, Order.HIGH]; }; -luaGenerator.forBlock['lists_sort'] = function(block, generator) { +export function lists_sort(block, generator) { // Block for sorting a list. const list = generator.valueToCode(block, 'LIST', Order.NONE) || '{}'; const direction = block.getFieldValue('DIRECTION') === '1' ? 1 : -1; @@ -296,7 +296,7 @@ end return [code, Order.HIGH]; }; -luaGenerator.forBlock['lists_split'] = function(block, generator) { +export function lists_split(block, generator) { // Block for splitting text into a list, or joining a list into text. let input = generator.valueToCode(block, 'INPUT', Order.NONE); const delimiter = @@ -336,7 +336,7 @@ end return [code, Order.HIGH]; }; -luaGenerator.forBlock['lists_reverse'] = function(block, generator) { +export function lists_reverse(block, generator) { // Block for reversing a list. const list = generator.valueToCode(block, 'LIST', Order.NONE) || '{}'; const functionName = generator.provideFunction_('list_reverse', ` diff --git a/generators/lua/logic.js b/generators/lua/logic.js index 4bed86a5a..01c6b6c18 100644 --- a/generators/lua/logic.js +++ b/generators/lua/logic.js @@ -11,10 +11,10 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.Lua.logic'); -import {luaGenerator, Order} from '../lua.js'; +import {Order} from './lua_generator.js'; -luaGenerator.forBlock['controls_if'] = function(block, generator) { +export function controls_if(block, generator) { // If/elseif/else condition. let n = 0; let code = ''; @@ -51,9 +51,9 @@ luaGenerator.forBlock['controls_if'] = function(block, generator) { return code + 'end\n'; }; -luaGenerator.forBlock['controls_ifelse'] = luaGenerator.forBlock['controls_if']; +export const controls_ifelse = controls_if; -luaGenerator.forBlock['logic_compare'] = function(block, generator) { +export function logic_compare(block, generator) { // Comparison operator. const OPERATORS = {'EQ': '==', 'NEQ': '~=', 'LT': '<', 'LTE': '<=', 'GT': '>', 'GTE': '>='}; @@ -66,7 +66,7 @@ luaGenerator.forBlock['logic_compare'] = function(block, generator) { return [code, Order.RELATIONAL]; }; -luaGenerator.forBlock['logic_operation'] = function(block, generator) { +export function logic_operation(block, generator) { // Operations 'and', 'or'. const operator = (block.getFieldValue('OP') === 'AND') ? 'and' : 'or'; const order = (operator === 'and') ? Order.AND : Order.OR; @@ -90,7 +90,7 @@ luaGenerator.forBlock['logic_operation'] = function(block, generator) { return [code, order]; }; -luaGenerator.forBlock['logic_negate'] = function(block, generator) { +export function logic_negate(block, generator) { // Negation. const argument0 = generator.valueToCode(block, 'BOOL', Order.UNARY) || 'true'; @@ -98,18 +98,18 @@ luaGenerator.forBlock['logic_negate'] = function(block, generator) { return [code, Order.UNARY]; }; -luaGenerator.forBlock['logic_boolean'] = function(block, generator) { +export function logic_boolean(block, generator) { // Boolean values true and false. const code = (block.getFieldValue('BOOL') === 'TRUE') ? 'true' : 'false'; return [code, Order.ATOMIC]; }; -luaGenerator.forBlock['logic_null'] = function(block, generator) { +export function logic_null(block, generator) { // Null data type. return ['nil', Order.ATOMIC]; }; -luaGenerator.forBlock['logic_ternary'] = function(block, generator) { +export function logic_ternary(block, generator) { // Ternary operator. const value_if = generator.valueToCode(block, 'IF', Order.AND) || 'false'; const value_then = diff --git a/generators/lua/loops.js b/generators/lua/loops.js index 02f40b410..3c613cb98 100644 --- a/generators/lua/loops.js +++ b/generators/lua/loops.js @@ -13,7 +13,7 @@ goog.declareModuleId('Blockly.Lua.loops'); import * as stringUtils from '../../core/utils/string.js'; import {NameType} from '../../core/names.js'; -import {luaGenerator, Order} from '../lua.js'; +import {Order} from './lua_generator.js'; /** @@ -43,7 +43,7 @@ function addContinueLabel(branch, indent) { } }; -luaGenerator.forBlock['controls_repeat_ext'] = function(block, generator) { +export function controls_repeat_ext(block, generator) { // Repeat n times. let repeats; if (block.getField('TIMES')) { @@ -67,10 +67,9 @@ luaGenerator.forBlock['controls_repeat_ext'] = function(block, generator) { return code; }; -luaGenerator.forBlock['controls_repeat'] = - luaGenerator.forBlock['controls_repeat_ext']; +export const controls_repeat = controls_repeat_ext; -luaGenerator.forBlock['controls_whileUntil'] = function(block, generator) { +export function controls_whileUntil(block, generator) { // Do while/until loop. const until = block.getFieldValue('MODE') === 'UNTIL'; let argument0 = @@ -86,7 +85,7 @@ luaGenerator.forBlock['controls_whileUntil'] = function(block, generator) { return 'while ' + argument0 + ' do\n' + branch + 'end\n'; }; -luaGenerator.forBlock['controls_for'] = function(block, generator) { +export function controls_for(block, generator) { // For loop. const variable0 = generator.nameDB_.getName( @@ -128,7 +127,7 @@ luaGenerator.forBlock['controls_for'] = function(block, generator) { return code; }; -luaGenerator.forBlock['controls_forEach'] = function(block, generator) { +export function controls_forEach(block, generator) { // For each loop. const variable0 = generator.nameDB_.getName( @@ -142,7 +141,7 @@ luaGenerator.forBlock['controls_forEach'] = function(block, generator) { return code; }; -luaGenerator.forBlock['controls_flow_statements'] = function(block, generator) { +export function controls_flow_statements(block, generator) { // Flow statements: continue, break. let xfix = ''; if (generator.STATEMENT_PREFIX) { diff --git a/generators/lua/lua_generator.js b/generators/lua/lua_generator.js new file mode 100644 index 000000000..587fea90b --- /dev/null +++ b/generators/lua/lua_generator.js @@ -0,0 +1,213 @@ +/** + * @license + * Copyright 2016 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @fileoverview Helper functions for generating Lua for blocks. + * Based on Ellen Spertus's blocky-lua project. + * @suppress {checkTypes|globalThis} + */ + +import * as goog from '../../closure/goog/goog.js'; +goog.declareModuleId('Blockly.Lua'); + +import * as stringUtils from '../../core/utils/string.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 {inputTypes} from '../../core/inputs/input_types.js'; + + +/** + * Order of operation ENUMs. + * http://www.lua.org/manual/5.3/manual.html#3.4.8 + * @enum {number} + */ +export const Order = { + ATOMIC: 0, // literals + // The next level was not explicit in documentation and inferred by Ellen. + HIGH: 1, // Function calls, tables[] + EXPONENTIATION: 2, // ^ + UNARY: 3, // not # - ~ + MULTIPLICATIVE: 4, // * / % + ADDITIVE: 5, // + - + CONCATENATION: 6, // .. + RELATIONAL: 7, // < > <= >= ~= == + AND: 8, // and + OR: 9, // or + NONE: 99, +}; + +/** + * Lua code generator class. + * + * Note: Lua is not supporting zero-indexing since the language itself is + * one-indexed, so the generator does not repoct the oneBasedIndex configuration + * option used for lists and text. + */ +export class LuaGenerator extends CodeGenerator { + constructor(name) { + super(name ?? 'Lua'); + this.isInitialized = false; + + // Copy Order values onto instance for backwards compatibility + // while ensuring they are not part of the publically-advertised + // API. + // + // TODO(#7085): deprecate these in due course. (Could initially + // replace data properties with get accessors that call + // deprecate.warn().) + for (const key in Order) { + this['ORDER_' + key] = Order[key]; + } + + // List of illegal variable names. This is not intended to be a + // security feature. Blockly is 100% client-side, so bypassing + // this list is trivial. This is intended to prevent users from + // accidentally clobbering a built-in object or function. + this.addReservedWords( + // Special character + '_,' + + // From theoriginalbit's script: + // https://github.com/espertus/blockly-lua/issues/6 + '__inext,assert,bit,colors,colours,coroutine,disk,dofile,error,fs,' + + 'fetfenv,getmetatable,gps,help,io,ipairs,keys,loadfile,loadstring,math,' + + 'native,next,os,paintutils,pairs,parallel,pcall,peripheral,print,' + + 'printError,rawequal,rawget,rawset,read,rednet,redstone,rs,select,' + + 'setfenv,setmetatable,sleep,string,table,term,textutils,tonumber,' + + 'tostring,turtle,type,unpack,vector,write,xpcall,_VERSION,__indext,' + + // Not included in the script, probably because it wasn't enabled: + 'HTTP,' + + // Keywords (http://www.lua.org/pil/1.3.html). + 'and,break,do,else,elseif,end,false,for,function,if,in,local,nil,not,' + + 'or,repeat,return,then,true,until,while,' + + // Metamethods (http://www.lua.org/manual/5.2/manual.html). + 'add,sub,mul,div,mod,pow,unm,concat,len,eq,lt,le,index,newindex,call,' + + // Basic functions (http://www.lua.org/manual/5.2/manual.html, + // section 6.1). + 'assert,collectgarbage,dofile,error,_G,getmetatable,inpairs,load,' + + 'loadfile,next,pairs,pcall,print,rawequal,rawget,rawlen,rawset,select,' + + 'setmetatable,tonumber,tostring,type,_VERSION,xpcall,' + + // Modules (http://www.lua.org/manual/5.2/manual.html, section 6.3). + 'require,package,string,table,math,bit32,io,file,os,debug' + ); + } + + /** + * Initialise the database of variable names. + * @param {!Workspace} workspace Workspace to generate code from. + */ + init(workspace) { + // Call Blockly.CodeGenerator's init. + super.init(); + + if (!this.nameDB_) { + this.nameDB_ = new Names(this.RESERVED_WORDS_); + } else { + this.nameDB_.reset(); + } + this.nameDB_.setVariableMap(workspace.getVariableMap()); + this.nameDB_.populateVariables(workspace); + this.nameDB_.populateProcedures(workspace); + + this.isInitialized = true; + }; + + /** + * Prepend the generated code with the variable definitions. + * @param {string} code Generated code. + * @return {string} Completed code. + */ + finish(code) { + // Convert the definitions dictionary into a list. + const definitions = Object.values(this.definitions_); + // Call Blockly.CodeGenerator's finish. + code = super.finish(code); + this.isInitialized = false; + + this.nameDB_.reset(); + return definitions.join('\n\n') + '\n\n\n' + code; + }; + + /** + * Naked values are top-level blocks with outputs that aren't plugged into + * anything. In Lua, an expression is not a legal statement, so we must assign + * the value to the (conventionally ignored) _. + * http://lua-users.org/wiki/ExpressionsAsStatements + * @param {string} line Line of generated code. + * @return {string} Legal line of code. + */ + scrubNakedValue(line) { + return 'local _ = ' + line + '\n'; + }; + + /** + * Encode a string as a properly escaped Lua string, complete with + * quotes. + * @param {string} string Text to encode. + * @return {string} Lua string. + * @protected + */ + quote_(string) { + string = string.replace(/\\/g, '\\\\') + .replace(/\n/g, '\\\n') + .replace(/'/g, '\\\''); + return '\'' + string + '\''; + }; + + /** + * Encode a string as a properly escaped multiline Lua string, complete with + * quotes. + * @param {string} string Text to encode. + * @return {string} Lua string. + * @protected + */ + multiline_quote_(string) { + const lines = string.split(/\n/g).map(this.quote_); + // Join with the following, plus a newline: + // .. '\n' .. + return lines.join(' .. \'\\n\' ..\n'); + }; + + /** + * Common tasks for generating Lua 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 Lua code created for this block. + * @param {boolean=} opt_thisOnly True to generate code for only this statement. + * @return {string} Lua code with comments and subsequent blocks added. + * @protected + */ + scrub_(block, code, opt_thisOnly) { + let commentCode = ''; + // Only collect comments for blocks that aren't inline. + if (!block.outputConnection || !block.outputConnection.targetConnection) { + // Collect comment for this block. + let comment = block.getCommentText(); + if (comment) { + comment = stringUtils.wrap(comment, this.COMMENT_WRAP - 3); + commentCode += this.prefixLines(comment, '-- ') + '\n'; + } + // Collect comments for all value arguments. + // 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(); + if (childBlock) { + comment = this.allNestedComments(childBlock); + if (comment) { + commentCode += this.prefixLines(comment, '-- '); + } + } + } + } + } + const nextBlock = block.nextConnection && block.nextConnection.targetBlock(); + const nextCode = opt_thisOnly ? '' : this.blockToCode(nextBlock); + return commentCode + code + nextCode; + }; +} diff --git a/generators/lua/math.js b/generators/lua/math.js index b6d69fa1a..eb9f55a39 100644 --- a/generators/lua/math.js +++ b/generators/lua/math.js @@ -12,17 +12,17 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.Lua.math'); import {NameType} from '../../core/names.js'; -import {luaGenerator, Order} from '../lua.js'; +import {Order} from './lua_generator.js'; -luaGenerator.forBlock['math_number'] = function(block, generator) { +export function math_number(block, generator) { // Numeric value. const code = Number(block.getFieldValue('NUM')); const order = code < 0 ? Order.UNARY : Order.ATOMIC; return [code, order]; }; -luaGenerator.forBlock['math_arithmetic'] = function(block, generator) { +export function math_arithmetic(block, generator) { // Basic arithmetic operators, and power. const OPERATORS = { 'ADD': [' + ', Order.ADDITIVE], @@ -40,7 +40,7 @@ luaGenerator.forBlock['math_arithmetic'] = function(block, generator) { return [code, order]; }; -luaGenerator.forBlock['math_single'] = function(block, generator) { +export function math_single(block, generator) { // Math operators with single operand. const operator = block.getFieldValue('OP'); let arg; @@ -110,7 +110,7 @@ luaGenerator.forBlock['math_single'] = function(block, generator) { return [code, Order.HIGH]; }; -luaGenerator.forBlock['math_constant'] = function(block, generator) { +export function math_constant(block, generator) { // Constants: PI, E, the Golden Ratio, sqrt(2), 1/sqrt(2), INFINITY. const CONSTANTS = { 'PI': ['math.pi', Order.HIGH], @@ -123,7 +123,7 @@ luaGenerator.forBlock['math_constant'] = function(block, generator) { return CONSTANTS[block.getFieldValue('CONSTANT')]; }; -luaGenerator.forBlock['math_number_property'] = function(block, generator) { +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 = { @@ -181,7 +181,7 @@ end return [code, outputOrder]; }; -luaGenerator.forBlock['math_change'] = function(block, generator) { +export function math_change(block, generator) { // Add to a variable in place. const argument0 = generator.valueToCode(block, 'DELTA', Order.ADDITIVE) || '0'; @@ -192,11 +192,11 @@ luaGenerator.forBlock['math_change'] = function(block, generator) { }; // Rounding functions have a single operand. -luaGenerator.forBlock['math_round'] = luaGenerator.forBlock['math_single']; +export const math_round = math_single; // Trigonometry functions have a single operand. -luaGenerator.forBlock['math_trig'] = luaGenerator.forBlock['math_single']; +export const math_trig = math_single; -luaGenerator.forBlock['math_on_list'] = function(block, generator) { +export function math_on_list(block, generator) { // Math functions for lists. const func = block.getFieldValue('OP'); const list = generator.valueToCode(block, 'LIST', Order.NONE) || '{}'; @@ -363,7 +363,7 @@ end return [functionName + '(' + list + ')', Order.HIGH]; }; -luaGenerator.forBlock['math_modulo'] = function(block, generator) { +export function math_modulo(block, generator) { // Remainder computation. const argument0 = generator.valueToCode(block, 'DIVIDEND', Order.MULTIPLICATIVE) || '0'; @@ -373,7 +373,7 @@ luaGenerator.forBlock['math_modulo'] = function(block, generator) { return [code, Order.MULTIPLICATIVE]; }; -luaGenerator.forBlock['math_constrain'] = function(block, generator) { +export function math_constrain(block, generator) { // Constrain a number between two limits. const argument0 = generator.valueToCode(block, 'VALUE', Order.NONE) || '0'; const argument1 = @@ -385,7 +385,7 @@ luaGenerator.forBlock['math_constrain'] = function(block, generator) { return [code, Order.HIGH]; }; -luaGenerator.forBlock['math_random_int'] = function(block, generator) { +export function math_random_int(block, generator) { // Random integer between [X] and [Y]. const argument0 = generator.valueToCode(block, 'FROM', Order.NONE) || '0'; const argument1 = generator.valueToCode(block, 'TO', Order.NONE) || '0'; @@ -393,12 +393,12 @@ luaGenerator.forBlock['math_random_int'] = function(block, generator) { return [code, Order.HIGH]; }; -luaGenerator.forBlock['math_random_float'] = function(block, generator) { +export function math_random_float(block, generator) { // Random fraction between 0 and 1. return ['math.random()', Order.HIGH]; }; -luaGenerator.forBlock['math_atan2'] = function(block, generator) { +export function math_atan2(block, generator) { // Arctangent of point (X, Y) in degrees from -180 to 180. const argument0 = generator.valueToCode(block, 'X', Order.NONE) || '0'; const argument1 = generator.valueToCode(block, 'Y', Order.NONE) || '0'; diff --git a/generators/lua/procedures.js b/generators/lua/procedures.js index e73f14413..cff655aae 100644 --- a/generators/lua/procedures.js +++ b/generators/lua/procedures.js @@ -12,10 +12,10 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.Lua.procedures'); import {NameType} from '../../core/names.js'; -import {luaGenerator, Order} from '../lua.js'; +import {Order} from './lua_generator.js'; -luaGenerator.forBlock['procedures_defreturn'] = function(block, generator) { +export function procedures_defreturn(block, generator) { // Define a procedure with a return value. const funcName = generator.nameDB_.getName( @@ -63,10 +63,9 @@ luaGenerator.forBlock['procedures_defreturn'] = function(block, generator) { // Defining a procedure without a return value uses the same generator as // a procedure with a return value. -luaGenerator.forBlock['procedures_defnoreturn'] = - luaGenerator.forBlock['procedures_defreturn']; +export const procedures_defnoreturn = procedures_defreturn; -luaGenerator.forBlock['procedures_callreturn'] = function(block, generator) { +export function procedures_callreturn(block, generator) { // Call a procedure with a return value. const funcName = generator.nameDB_.getName( @@ -80,7 +79,7 @@ luaGenerator.forBlock['procedures_callreturn'] = function(block, generator) { return [code, Order.HIGH]; }; -luaGenerator.forBlock['procedures_callnoreturn'] = function(block, generator) { +export function procedures_callnoreturn(block, generator) { // 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. @@ -88,7 +87,7 @@ luaGenerator.forBlock['procedures_callnoreturn'] = function(block, generator) { return tuple[0] + '\n'; }; -luaGenerator.forBlock['procedures_ifreturn'] = function(block, generator) { +export function procedures_ifreturn(block, generator) { // Conditionally return value from a procedure. const condition = generator.valueToCode(block, 'CONDITION', Order.NONE) || 'false'; diff --git a/generators/lua/text.js b/generators/lua/text.js index dc29b800e..2cf13f7c6 100644 --- a/generators/lua/text.js +++ b/generators/lua/text.js @@ -12,16 +12,16 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.Lua.texts'); import {NameType} from '../../core/names.js'; -import {luaGenerator, Order} from '../lua.js'; +import {Order} from './lua_generator.js'; -luaGenerator.forBlock['text'] = function(block, generator) { +export function text(block, generator) { // Text value. const code = generator.quote_(block.getFieldValue('TEXT')); return [code, Order.ATOMIC]; }; -luaGenerator.forBlock['text_multiline'] = function(block, generator) { +export function text_multiline(block, generator) { // Text value. const code = generator.multiline_quote_(block.getFieldValue('TEXT')); const order = @@ -29,7 +29,7 @@ luaGenerator.forBlock['text_multiline'] = function(block, generator) { return [code, order]; }; -luaGenerator.forBlock['text_join'] = function(block, generator) { +export function text_join(block, generator) { // Create a string made up of any number of elements of any type. if (block.itemCount_ === 0) { return ["''", Order.ATOMIC]; @@ -55,7 +55,7 @@ luaGenerator.forBlock['text_join'] = function(block, generator) { } }; -luaGenerator.forBlock['text_append'] = function(block, generator) { +export function text_append(block, generator) { // Append to a variable in place. const varName = generator.nameDB_.getName( @@ -65,19 +65,19 @@ luaGenerator.forBlock['text_append'] = function(block, generator) { return varName + ' = ' + varName + ' .. ' + value + '\n'; }; -luaGenerator.forBlock['text_length'] = function(block, generator) { +export function text_length(block, generator) { // String or array length. const text = generator.valueToCode(block, 'VALUE', Order.UNARY) || "''"; return ['#' + text, Order.UNARY]; }; -luaGenerator.forBlock['text_isEmpty'] = function(block, generator) { +export function text_isEmpty(block, generator) { // Is the string null or array empty? const text = generator.valueToCode(block, 'VALUE', Order.UNARY) || "''"; return ['#' + text + ' == 0', Order.RELATIONAL]; }; -luaGenerator.forBlock['text_indexOf'] = function(block, generator) { +export function text_indexOf(block, generator) { // Search the text for a substring. const substring = generator.valueToCode(block, 'FIND', Order.NONE) || "''"; const text = generator.valueToCode(block, 'VALUE', Order.NONE) || "''"; @@ -107,7 +107,7 @@ end return [code, Order.HIGH]; }; -luaGenerator.forBlock['text_charAt'] = function(block, generator) { +export function text_charAt(block, generator) { // Get letter at index. // Note: Until January 2013 this block did not have the WHERE input. const where = block.getFieldValue('WHERE') || 'FROM_START'; @@ -153,7 +153,7 @@ end return [code, Order.HIGH]; }; -luaGenerator.forBlock['text_getSubstring'] = function(block, generator) { +export function text_getSubstring(block, generator) { // Get substring. const text = generator.valueToCode(block, 'STRING', Order.NONE) || "''"; @@ -190,7 +190,7 @@ luaGenerator.forBlock['text_getSubstring'] = function(block, generator) { return [code, Order.HIGH]; }; -luaGenerator.forBlock['text_changeCase'] = function(block, generator) { +export function text_changeCase(block, generator) { // Change capitalization. const operator = block.getFieldValue('CASE'); const text = generator.valueToCode(block, 'TEXT', Order.NONE) || "''"; @@ -227,7 +227,7 @@ end return [code, Order.HIGH]; }; -luaGenerator.forBlock['text_trim'] = function(block, generator) { +export function text_trim(block, generator) { // Trim spaces. const OPERATORS = {LEFT: '^%s*(,-)', RIGHT: '(.-)%s*$', BOTH: '^%s*(.-)%s*$'}; const operator = OPERATORS[block.getFieldValue('MODE')]; @@ -236,13 +236,13 @@ luaGenerator.forBlock['text_trim'] = function(block, generator) { return [code, Order.HIGH]; }; -luaGenerator.forBlock['text_print'] = function(block, generator) { +export function text_print(block, generator) { // Print statement. const msg = generator.valueToCode(block, 'TEXT', Order.NONE) || "''"; return 'print(' + msg + ')\n'; }; -luaGenerator.forBlock['text_prompt_ext'] = function(block, generator) { +export function text_prompt_ext(block, generator) { // Prompt function. let msg; if (block.getField('TEXT')) { @@ -269,9 +269,9 @@ end return [code, Order.HIGH]; }; -luaGenerator.forBlock['text_prompt'] = luaGenerator.forBlock['text_prompt_ext']; +export const text_prompt = text_prompt_ext; -luaGenerator.forBlock['text_count'] = function(block, generator) { +export function text_count(block, generator) { const text = generator.valueToCode(block, 'TEXT', Order.NONE) || "''"; const sub = generator.valueToCode(block, 'SUB', Order.NONE) || "''"; const functionName = generator.provideFunction_('text_count', ` @@ -296,7 +296,7 @@ end return [code, Order.HIGH]; }; -luaGenerator.forBlock['text_replace'] = function(block, generator) { +export function text_replace(block, generator) { const text = generator.valueToCode(block, 'TEXT', Order.NONE) || "''"; const from = generator.valueToCode(block, 'FROM', Order.NONE) || "''"; const to = generator.valueToCode(block, 'TO', Order.NONE) || "''"; @@ -322,7 +322,7 @@ end return [code, Order.HIGH]; }; -luaGenerator.forBlock['text_reverse'] = function(block, generator) { +export function text_reverse(block, generator) { const text = generator.valueToCode(block, 'TEXT', Order.NONE) || "''"; const code = 'string.reverse(' + text + ')'; return [code, Order.HIGH]; diff --git a/generators/lua/variables.js b/generators/lua/variables.js index e90179101..f149f8841 100644 --- a/generators/lua/variables.js +++ b/generators/lua/variables.js @@ -12,10 +12,10 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.Lua.variables'); import {NameType} from '../../core/names.js'; -import {luaGenerator, Order} from '../lua.js'; +import {Order} from './lua_generator.js'; -luaGenerator.forBlock['variables_get'] = function(block, generator) { +export function variables_get(block, generator) { // Variable getter. const code = generator.nameDB_.getName( @@ -23,7 +23,7 @@ luaGenerator.forBlock['variables_get'] = function(block, generator) { return [code, Order.ATOMIC]; }; -luaGenerator.forBlock['variables_set'] = function(block, generator) { +export function variables_set(block, generator) { // Variable setter. const argument0 = generator.valueToCode(block, 'VALUE', Order.NONE) || '0'; const varName = diff --git a/generators/lua/variables_dynamic.js b/generators/lua/variables_dynamic.js index 0a627fbdf..b78368a21 100644 --- a/generators/lua/variables_dynamic.js +++ b/generators/lua/variables_dynamic.js @@ -11,12 +11,9 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.Lua.variablesDynamic'); -import {luaGenerator} from '../lua.js'; -import './variables.js'; - // Lua is dynamically typed. -luaGenerator.forBlock['variables_get_dynamic'] = - luaGenerator.forBlock['variables_get']; -luaGenerator.forBlock['variables_set_dynamic'] = - luaGenerator.forBlock['variables_set']; +export { + variables_get as variables_get_dynamic, + variables_set as variables_set_dynamic, +} from './variables.js'; diff --git a/generators/php.js b/generators/php.js index df90cdc20..666f081f7 100644 --- a/generators/php.js +++ b/generators/php.js @@ -1,316 +1,40 @@ /** * @license - * Copyright 2015 Google LLC + * Copyright 2021 Google LLC * SPDX-License-Identifier: Apache-2.0 */ /** - * @fileoverview Helper functions for generating PHP for blocks. - * @suppress {checkTypes|globalThis} + * @fileoverview Complete helper functions for generating PHP for + * blocks. This is the entrypoint for php_compressed.js. + * @suppress {extraRequire} */ import * as goog from '../closure/goog/goog.js'; -goog.declareModuleId('Blockly.PHP'); +goog.declareModuleId('Blockly.PHP.all'); -import * as stringUtils from '../core/utils/string.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 {inputTypes} from '../core/inputs/input_types.js'; +import {PhpGenerator} from './php/php_generator.js'; +import * as colour from './php/colour.js'; +import * as lists from './php/lists.js'; +import * as logic from './php/logic.js'; +import * as loops from './php/loops.js'; +import * as math from './php/math.js'; +import * as procedures from './php/procedures.js'; +import * as text from './php/text.js'; +import * as variables from './php/variables.js'; +import * as variablesDynamic from './php/variables_dynamic.js'; +export * from './php/php_generator.js'; /** - * Order of operation ENUMs. - * http://php.net/manual/en/language.operators.precedence.php - * @enum {number} - */ -export const Order = { - ATOMIC: 0, // 0 "" ... - CLONE: 1, // clone - NEW: 1, // new - MEMBER: 2.1, // [] - FUNCTION_CALL: 2.2, // () - POWER: 3, // ** - INCREMENT: 4, // ++ - DECREMENT: 4, // -- - BITWISE_NOT: 4, // ~ - CAST: 4, // (int) (float) (string) (array) ... - SUPPRESS_ERROR: 4, // @ - INSTANCEOF: 5, // instanceof - LOGICAL_NOT: 6, // ! - UNARY_PLUS: 7.1, // + - UNARY_NEGATION: 7.2, // - - MULTIPLICATION: 8.1, // * - DIVISION: 8.2, // / - MODULUS: 8.3, // % - ADDITION: 9.1, // + - SUBTRACTION: 9.2, // - - STRING_CONCAT: 9.3, // . - BITWISE_SHIFT: 10, // << >> - RELATIONAL: 11, // < <= > >= - EQUALITY: 12, // == != === !== <> <=> - REFERENCE: 13, // & - BITWISE_AND: 13, // & - BITWISE_XOR: 14, // ^ - BITWISE_OR: 15, // | - LOGICAL_AND: 16, // && - LOGICAL_OR: 17, // || - IF_NULL: 18, // ?? - CONDITIONAL: 19, // ?: - ASSIGNMENT: 20, // = += -= *= /= %= <<= >>= ... - LOGICAL_AND_WEAK: 21, // and - LOGICAL_XOR: 22, // xor - LOGICAL_OR_WEAK: 23, // or - NONE: 99, // (...) -}; - -class PhpGenerator extends CodeGenerator { - /** - * List of outer-inner pairings that do NOT require parentheses. - * @type {!Array>} - */ - ORDER_OVERRIDES = [ - // (foo()).bar() -> foo().bar() - // (foo())[0] -> foo()[0] - [Order.MEMBER, Order.FUNCTION_CALL], - // (foo[0])[1] -> foo[0][1] - // (foo.bar).baz -> foo.bar.baz - [Order.MEMBER, Order.MEMBER], - // !(!foo) -> !!foo - [Order.LOGICAL_NOT, Order.LOGICAL_NOT], - // a * (b * c) -> a * b * c - [Order.MULTIPLICATION, Order.MULTIPLICATION], - // a + (b + c) -> a + b + c - [Order.ADDITION, Order.ADDITION], - // a && (b && c) -> a && b && c - [Order.LOGICAL_AND, Order.LOGICAL_AND], - // a || (b || c) -> a || b || c - [Order.LOGICAL_OR, Order.LOGICAL_OR] - ]; - - constructor(name) { - super(name ?? 'PHP'); - this.isInitialized = false; - - // Copy Order values onto instance for backwards compatibility - // while ensuring they are not part of the publically-advertised - // API. - // - // TODO(#7085): deprecate these in due course. (Could initially - // replace data properties with get accessors that call - // deprecate.warn().) - for (const key in Order) { - this['ORDER_' + key] = Order[key]; - } - - // List of illegal variable names. This is not intended to be a - // security feature. Blockly is 100% client-side, so bypassing - // this list is trivial. This is intended to prevent users from - // accidentally clobbering a built-in object or function. - this.addReservedWords( - // http://php.net/manual/en/reserved.keywords.php - '__halt_compiler,abstract,and,array,as,break,callable,case,catch,class,' + - 'clone,const,continue,declare,default,die,do,echo,else,elseif,empty,' + - 'enddeclare,endfor,endforeach,endif,endswitch,endwhile,eval,exit,' + - 'extends,final,for,foreach,function,global,goto,if,implements,include,' + - 'include_once,instanceof,insteadof,interface,isset,list,namespace,new,' + - 'or,print,private,protected,public,require,require_once,return,static,' + - 'switch,throw,trait,try,unset,use,var,while,xor,' + - // http://php.net/manual/en/reserved.constants.php - 'PHP_VERSION,PHP_MAJOR_VERSION,PHP_MINOR_VERSION,PHP_RELEASE_VERSION,' + - 'PHP_VERSION_ID,PHP_EXTRA_VERSION,PHP_ZTS,PHP_DEBUG,PHP_MAXPATHLEN,' + - 'PHP_OS,PHP_SAPI,PHP_EOL,PHP_INT_MAX,PHP_INT_SIZE,DEFAULT_INCLUDE_PATH,' + - 'PEAR_INSTALL_DIR,PEAR_EXTENSION_DIR,PHP_EXTENSION_DIR,PHP_PREFIX,' + - 'PHP_BINDIR,PHP_BINARY,PHP_MANDIR,PHP_LIBDIR,PHP_DATADIR,' + - 'PHP_SYSCONFDIR,PHP_LOCALSTATEDIR,PHP_CONFIG_FILE_PATH,' + - 'PHP_CONFIG_FILE_SCAN_DIR,PHP_SHLIB_SUFFIX,E_ERROR,E_WARNING,E_PARSE,' + - 'E_NOTICE,E_CORE_ERROR,E_CORE_WARNING,E_COMPILE_ERROR,' + - 'E_COMPILE_WARNING,E_USER_ERROR,E_USER_WARNING,E_USER_NOTICE,' + - 'E_DEPRECATED,E_USER_DEPRECATED,E_ALL,E_STRICT,' + - '__COMPILER_HALT_OFFSET__,TRUE,FALSE,NULL,__CLASS__,__DIR__,__FILE__,' + - '__FUNCTION__,__LINE__,__METHOD__,__NAMESPACE__,__TRAIT__' - ); - } - - /** - * Initialise the database of variable names. - * @param {!Workspace} workspace Workspace to generate code from. - */ - init(workspace) { - super.init(workspace); - - if (!this.nameDB_) { - this.nameDB_ = new Names(this.RESERVED_WORDS_, '$'); - } else { - this.nameDB_.reset(); - } - - this.nameDB_.setVariableMap(workspace.getVariableMap()); - this.nameDB_.populateVariables(workspace); - this.nameDB_.populateProcedures(workspace); - - this.isInitialized = true; - }; - - /** - * Prepend the generated code with the variable definitions. - * @param {string} code Generated code. - * @return {string} Completed code. - */ - finish(code) { - // Convert the definitions dictionary into a list. - const definitions = Object.values(this.definitions_); - // Call Blockly.CodeGenerator's finish. - code = super.finish(code); - this.isInitialized = false; - - this.nameDB_.reset(); - return definitions.join('\n\n') + '\n\n\n' + code; - }; - - /** - * Naked values are top-level blocks with outputs that aren't plugged into - * anything. A trailing semicolon is needed to make this legal. - * @param {string} line Line of generated code. - * @return {string} Legal line of code. - */ - scrubNakedValue(line) { - return line + ';\n'; - }; - - /** - * Encode a string as a properly escaped PHP string, complete with - * quotes. - * @param {string} string Text to encode. - * @return {string} PHP string. - * @protected - */ - quote_(string) { - string = string.replace(/\\/g, '\\\\') - .replace(/\n/g, '\\\n') - .replace(/'/g, '\\\''); - return '\'' + string + '\''; - }; - - /** - * Encode a string as a properly escaped multiline PHP string, complete with - * quotes. - * @param {string} string Text to encode. - * @return {string} PHP string. - * @protected - */ - multiline_quote_(string) { - const lines = string.split(/\n/g).map(this.quote_); - // Join with the following, plus a newline: - // . "\n" . - // Newline escaping only works in double-quoted strings. - return lines.join(' . \"\\n\" .\n'); - }; - - /** - * Common tasks for generating PHP 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 PHP code created for this block. - * @param {boolean=} opt_thisOnly True to generate code for only this - * statement. - * @return {string} PHP code with comments and subsequent blocks added. - * @protected - */ - scrub_(block, code, opt_thisOnly) { - let commentCode = ''; - // Only collect comments for blocks that aren't inline. - if (!block.outputConnection || !block.outputConnection.targetConnection) { - // Collect comment for this block. - let comment = block.getCommentText(); - if (comment) { - comment = stringUtils.wrap(comment, this.COMMENT_WRAP - 3); - commentCode += this.prefixLines(comment, '// ') + '\n'; - } - // Collect comments for all value arguments. - // 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(); - if (childBlock) { - comment = this.allNestedComments(childBlock); - if (comment) { - commentCode += this.prefixLines(comment, '// '); - } - } - } - } - } - const nextBlock = - block.nextConnection && block.nextConnection.targetBlock(); - const nextCode = opt_thisOnly ? '' : this.blockToCode(nextBlock); - return commentCode + code + nextCode; - }; - - /** - * Gets a property and adjusts the value while taking into account indexing. - * @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. - * @param {number=} opt_order The highest order acting on this value. - * @return {string|number} - */ - getAdjusted(block, atId, opt_delta, opt_negate, opt_order) { - let delta = opt_delta || 0; - let order = opt_order || this.ORDER_NONE; - if (block.workspace.options.oneBasedIndex) { - delta--; - } - let defaultAtIndex = block.workspace.options.oneBasedIndex ? '1' : '0'; - let outerOrder = order; - let innerOrder; - if (delta > 0) { - outerOrder = this.ORDER_ADDITION; - innerOrder = this.ORDER_ADDITION; - } else if (delta < 0) { - outerOrder = this.ORDER_SUBTRACTION; - innerOrder = this.ORDER_SUBTRACTION; - } else if (opt_negate) { - outerOrder = this.ORDER_UNARY_NEGATION; - innerOrder = this.ORDER_UNARY_NEGATION; - } - let at = this.valueToCode(block, atId, outerOrder) || defaultAtIndex; - - if (stringUtils.isNumber(at)) { - // If the index is a naked number, adjust it right now. - at = Number(at) + delta; - if (opt_negate) { - at = -at; - } - } else { - // If the index is dynamic, adjust it in code. - if (delta > 0) { - at = at + ' + ' + delta; - } else if (delta < 0) { - at = at + ' - ' + -delta; - } - if (opt_negate) { - if (delta) { - at = '-(' + at + ')'; - } else { - at = '-' + at; - } - } - innerOrder = Math.floor(innerOrder); - order = Math.floor(order); - if (innerOrder && order >= innerOrder) { - at = '(' + at + ')'; - } - } - return at; - }; -} - -/** - * PHP code generator. + * Php code generator instance. * @type {!PhpGenerator} */ export const phpGenerator = new PhpGenerator(); + +// Install per-block-type generator functions: +Object.assign( + phpGenerator.forBlock, + colour, lists, logic, loops, math, procedures, + text, variables, variablesDynamic +); diff --git a/generators/php/all.js b/generators/php/all.js deleted file mode 100644 index 2fd3b505a..000000000 --- a/generators/php/all.js +++ /dev/null @@ -1,26 +0,0 @@ -/** - * @license - * Copyright 2021 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * @fileoverview Complete helper functions for generating PHP for - * blocks. This is the entrypoint for php_compressed.js. - * @suppress {extraRequire} - */ - -import * as goog from '../../closure/goog/goog.js'; -goog.declareModuleId('Blockly.PHP.all'); - -import './colour.js'; -import './lists.js'; -import './logic.js'; -import './loops.js'; -import './math.js'; -import './procedures.js'; -import './text.js'; -import './variables.js'; -import './variables_dynamic.js'; - -export * from '../php.js'; diff --git a/generators/php/colour.js b/generators/php/colour.js index 58f5dd645..a9874fcdf 100644 --- a/generators/php/colour.js +++ b/generators/php/colour.js @@ -11,16 +11,16 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.PHP.colour'); -import {phpGenerator, Order} from '../php.js'; +import {Order} from './php_generator.js'; -phpGenerator.forBlock['colour_picker'] = function(block, generator) { +export function colour_picker(block, generator) { // Colour picker. const code = generator.quote_(block.getFieldValue('COLOUR')); return [code, Order.ATOMIC]; }; -phpGenerator.forBlock['colour_random'] = function(block, generator) { +export function colour_random(block, generator) { // Generate a random colour. const functionName = generator.provideFunction_('colour_random', ` function ${generator.FUNCTION_NAME_PLACEHOLDER_}() { @@ -31,7 +31,7 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}() { return [code, Order.FUNCTION_CALL]; }; -phpGenerator.forBlock['colour_rgb'] = function(block, generator) { +export function colour_rgb(block, generator) { // Compose a colour from RGB components expressed as percentages. const red = generator.valueToCode(block, 'RED', Order.NONE) || 0; const green = generator.valueToCode(block, 'GREEN', Order.NONE) || 0; @@ -52,7 +52,7 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}($r, $g, $b) { return [code, Order.FUNCTION_CALL]; }; -phpGenerator.forBlock['colour_blend'] = function(block, generator) { +export function colour_blend(block, generator) { // Blend two colours together. const c1 = generator.valueToCode(block, 'COLOUR1', Order.NONE) || "'#000000'"; diff --git a/generators/php/lists.js b/generators/php/lists.js index de7172ad0..5e1aa7f79 100644 --- a/generators/php/lists.js +++ b/generators/php/lists.js @@ -24,14 +24,14 @@ goog.declareModuleId('Blockly.generator.lists'); import * as stringUtils from '../../core/utils/string.js'; import {NameType} from '../../core/names.js'; -import {phpGenerator, Order} from '../php.js'; +import {Order} from './php_generator.js'; -phpGenerator.forBlock['lists_create_empty'] = function(block, generator) { +export function lists_create_empty(block, generator) { // Create an empty list. return ['array()', Order.FUNCTION_CALL]; }; -phpGenerator.forBlock['lists_create_with'] = function(block, generator) { +export function lists_create_with(block, generator) { // Create a list with any number of elements of any type. let code = new Array(block.itemCount_); for (let i = 0; i < block.itemCount_; i++) { @@ -41,7 +41,7 @@ phpGenerator.forBlock['lists_create_with'] = function(block, generator) { return [code, Order.FUNCTION_CALL]; }; -phpGenerator.forBlock['lists_repeat'] = function(block, generator) { +export function lists_repeat(block, generator) { // Create a list with one element repeated. const functionName = generator.provideFunction_('lists_repeat', ` function ${generator.FUNCTION_NAME_PLACEHOLDER_}($value, $count) { @@ -58,7 +58,7 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}($value, $count) { return [code, Order.FUNCTION_CALL]; }; -phpGenerator.forBlock['lists_length'] = function(block, generator) { +export function lists_length(block, generator) { // String or array length. const functionName = generator.provideFunction_('length', ` function ${generator.FUNCTION_NAME_PLACEHOLDER_}($value) { @@ -73,7 +73,7 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}($value) { return [functionName + '(' + list + ')', Order.FUNCTION_CALL]; }; -phpGenerator.forBlock['lists_isEmpty'] = function(block, generator) { +export function lists_isEmpty(block, generator) { // Is the string null or array empty? const argument0 = generator.valueToCode(block, 'VALUE', Order.FUNCTION_CALL) @@ -81,7 +81,7 @@ phpGenerator.forBlock['lists_isEmpty'] = function(block, generator) { return ['empty(' + argument0 + ')', Order.FUNCTION_CALL]; }; -phpGenerator.forBlock['lists_indexOf'] = function(block, generator) { +export function lists_indexOf(block, generator) { // Find an item in the list. const argument0 = generator.valueToCode(block, 'FIND', Order.NONE) || "''"; const argument1 = @@ -120,7 +120,7 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}($haystack, $needle) { return [code, Order.FUNCTION_CALL]; }; -phpGenerator.forBlock['lists_getIndex'] = function(block, generator) { +export function lists_getIndex(block, generator) { // Get element at index. const mode = block.getFieldValue('MODE') || 'GET'; const where = block.getFieldValue('WHERE') || 'FROM_START'; @@ -237,7 +237,7 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}(&$list) { throw Error('Unhandled combination (lists_getIndex).'); }; -phpGenerator.forBlock['lists_setIndex'] = function(block, generator) { +export function lists_setIndex(block, generator) { // Set element at index. // Note: Until February 2013 this block did not have MODE or WHERE inputs. const mode = block.getFieldValue('MODE') || 'GET'; @@ -341,7 +341,7 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}(&$list, $at, $value) { throw Error('Unhandled combination (lists_setIndex).'); }; -phpGenerator.forBlock['lists_getSublist'] = function(block, generator) { +export function lists_getSublist(block, generator) { // Get sublist. const list = generator.valueToCode(block, 'LIST', Order.NONE) || 'array()'; const where1 = block.getFieldValue('WHERE1'); @@ -441,7 +441,7 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}($list, $where1, $at1, $where2, return [code, Order.FUNCTION_CALL]; }; -phpGenerator.forBlock['lists_sort'] = function(block, generator) { +export function lists_sort(block, generator) { // Block for sorting a list. const listCode = generator.valueToCode(block, 'LIST', Order.NONE) || 'array()'; @@ -468,7 +468,7 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}($list, $type, $direction) { return [sortCode, Order.FUNCTION_CALL]; }; -phpGenerator.forBlock['lists_split'] = function(block, generator) { +export function lists_split(block, generator) { // Block for splitting text into a list, or joining a list into text. let value_input = generator.valueToCode(block, 'INPUT', Order.NONE); const value_delim = @@ -492,7 +492,7 @@ phpGenerator.forBlock['lists_split'] = function(block, generator) { return [code, Order.FUNCTION_CALL]; }; -phpGenerator.forBlock['lists_reverse'] = function(block, generator) { +export function lists_reverse(block, generator) { // Block for reversing a list. const list = generator.valueToCode(block, 'LIST', Order.NONE) || '[]'; const code = 'array_reverse(' + list + ')'; diff --git a/generators/php/logic.js b/generators/php/logic.js index b2c98b01c..5c7ea0e93 100644 --- a/generators/php/logic.js +++ b/generators/php/logic.js @@ -11,10 +11,10 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.PHP.logic'); -import {phpGenerator, Order} from '../php.js'; +import {Order} from './php_generator.js'; -phpGenerator.forBlock['controls_if'] = function(block, generator) { +export function controls_if(block, generator) { // If/elseif/else condition. let n = 0; let code = '', branchCode, conditionCode; @@ -52,9 +52,9 @@ phpGenerator.forBlock['controls_if'] = function(block, generator) { return code + '\n'; }; -phpGenerator.forBlock['controls_ifelse'] = phpGenerator.forBlock['controls_if']; +export const controls_ifelse = controls_if; -phpGenerator.forBlock['logic_compare'] = function(block, generator) { +export function logic_compare(block, generator) { // Comparison operator. const OPERATORS = {'EQ': '==', 'NEQ': '!=', 'LT': '<', 'LTE': '<=', 'GT': '>', 'GTE': '>='}; @@ -67,7 +67,7 @@ phpGenerator.forBlock['logic_compare'] = function(block, generator) { return [code, order]; }; -phpGenerator.forBlock['logic_operation'] = function(block, generator) { +export function logic_operation(block, generator) { // Operations 'and', 'or'. const operator = (block.getFieldValue('OP') === 'AND') ? '&&' : '||'; const order = @@ -92,7 +92,7 @@ phpGenerator.forBlock['logic_operation'] = function(block, generator) { return [code, order]; }; -phpGenerator.forBlock['logic_negate'] = function(block, generator) { +export function logic_negate(block, generator) { // Negation. const order = Order.LOGICAL_NOT; const argument0 = generator.valueToCode(block, 'BOOL', order) || 'true'; @@ -100,18 +100,18 @@ phpGenerator.forBlock['logic_negate'] = function(block, generator) { return [code, order]; }; -phpGenerator.forBlock['logic_boolean'] = function(block, generator) { +export function logic_boolean(block, generator) { // Boolean values true and false. const code = (block.getFieldValue('BOOL') === 'TRUE') ? 'true' : 'false'; return [code, Order.ATOMIC]; }; -phpGenerator.forBlock['logic_null'] = function(block, generator) { +export function logic_null(block, generator) { // Null data type. return ['null', Order.ATOMIC]; }; -phpGenerator.forBlock['logic_ternary'] = function(block, generator) { +export function logic_ternary(block, generator) { // Ternary operator. const value_if = generator.valueToCode(block, 'IF', Order.CONDITIONAL) || 'false'; diff --git a/generators/php/loops.js b/generators/php/loops.js index ad4577ec3..3e2cfab74 100644 --- a/generators/php/loops.js +++ b/generators/php/loops.js @@ -13,10 +13,10 @@ goog.declareModuleId('Blockly.PHP.loops'); import * as stringUtils from '../../core/utils/string.js'; import {NameType} from '../../core/names.js'; -import {phpGenerator, Order} from '../php.js'; +import {Order} from './php_generator.js'; -phpGenerator.forBlock['controls_repeat_ext'] = function(block, generator) { +export function controls_repeat_ext(block, generator) { // Repeat n times. let repeats; if (block.getField('TIMES')) { @@ -42,10 +42,9 @@ phpGenerator.forBlock['controls_repeat_ext'] = function(block, generator) { return code; }; -phpGenerator.forBlock['controls_repeat'] = - phpGenerator.forBlock['controls_repeat_ext']; +export const controls_repeat = controls_repeat_ext; -phpGenerator.forBlock['controls_whileUntil'] = function(block, generator) { +export function controls_whileUntil(block, generator) { // Do while/until loop. const until = block.getFieldValue('MODE') === 'UNTIL'; let argument0 = @@ -60,7 +59,7 @@ phpGenerator.forBlock['controls_whileUntil'] = function(block, generator) { return 'while (' + argument0 + ') {\n' + branch + '}\n'; }; -phpGenerator.forBlock['controls_for'] = function(block, generator) { +export function controls_for(block, generator) { // For loop. const variable0 = generator.nameDB_.getName(block.getFieldValue('VAR'), NameType.VARIABLE); @@ -125,7 +124,7 @@ phpGenerator.forBlock['controls_for'] = function(block, generator) { return code; }; -phpGenerator.forBlock['controls_forEach'] = function(block, generator) { +export function controls_forEach(block, generator) { // For each loop. const variable0 = generator.nameDB_.getName( @@ -140,7 +139,7 @@ phpGenerator.forBlock['controls_forEach'] = function(block, generator) { return code; }; -phpGenerator.forBlock['controls_flow_statements'] = function(block, generator) { +export function controls_flow_statements(block, generator) { // Flow statements: continue, break. let xfix = ''; if (generator.STATEMENT_PREFIX) { diff --git a/generators/php/math.js b/generators/php/math.js index 8fba46dcf..b5cd53404 100644 --- a/generators/php/math.js +++ b/generators/php/math.js @@ -12,10 +12,10 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.PHP.math'); import {NameType} from '../../core/names.js'; -import {phpGenerator, Order} from '../php.js'; +import {Order} from './php_generator.js'; -phpGenerator.forBlock['math_number'] = function(block, generator) { +export function math_number(block, generator) { // Numeric value. let code = Number(block.getFieldValue('NUM')); const order = code >= 0 ? Order.ATOMIC : Order.UNARY_NEGATION; @@ -27,7 +27,7 @@ phpGenerator.forBlock['math_number'] = function(block, generator) { return [code, order]; }; -phpGenerator.forBlock['math_arithmetic'] = function(block, generator) { +export function math_arithmetic(block, generator) { // Basic arithmetic operators, and power. const OPERATORS = { 'ADD': [' + ', Order.ADDITION], @@ -45,7 +45,7 @@ phpGenerator.forBlock['math_arithmetic'] = function(block, generator) { return [code, order]; }; -phpGenerator.forBlock['math_single'] = function(block, generator) { +export function math_single(block, generator) { // Math operators with single operand. const operator = block.getFieldValue('OP'); let code; @@ -126,7 +126,7 @@ phpGenerator.forBlock['math_single'] = function(block, generator) { return [code, Order.DIVISION]; }; -phpGenerator.forBlock['math_constant'] = function(block, generator) { +export function math_constant(block, generator) { // Constants: PI, E, the Golden Ratio, sqrt(2), 1/sqrt(2), INFINITY. const CONSTANTS = { 'PI': ['M_PI', Order.ATOMIC], @@ -139,7 +139,7 @@ phpGenerator.forBlock['math_constant'] = function(block, generator) { return CONSTANTS[block.getFieldValue('CONSTANT')]; }; -phpGenerator.forBlock['math_number_property'] = function(block, generator) { +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 = { @@ -194,7 +194,7 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}($n) { return [code, outputOrder]; }; -phpGenerator.forBlock['math_change'] = function(block, generator) { +export function math_change(block, generator) { // Add to a variable in place. const argument0 = generator.valueToCode(block, 'DELTA', Order.ADDITION) || '0'; @@ -205,11 +205,11 @@ phpGenerator.forBlock['math_change'] = function(block, generator) { }; // Rounding functions have a single operand. -phpGenerator.forBlock['math_round'] = phpGenerator.forBlock['math_single']; +export const math_round = math_single; // Trigonometry functions have a single operand. -phpGenerator.forBlock['math_trig'] = phpGenerator.forBlock['math_single']; +export const math_trig = math_single; -phpGenerator.forBlock['math_on_list'] = function(block, generator) { +export function math_on_list(block, generator) { // Math functions for lists. const func = block.getFieldValue('OP'); let list; @@ -304,7 +304,7 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}($list) { return [code, Order.FUNCTION_CALL]; }; -phpGenerator.forBlock['math_modulo'] = function(block, generator) { +export function math_modulo(block, generator) { // Remainder computation. const argument0 = generator.valueToCode(block, 'DIVIDEND', Order.MODULUS) || '0'; @@ -314,7 +314,7 @@ phpGenerator.forBlock['math_modulo'] = function(block, generator) { return [code, Order.MODULUS]; }; -phpGenerator.forBlock['math_constrain'] = function(block, generator) { +export function math_constrain(block, generator) { // Constrain a number between two limits. const argument0 = generator.valueToCode(block, 'VALUE', Order.NONE) || '0'; const argument1 = generator.valueToCode(block, 'LOW', Order.NONE) || '0'; @@ -325,7 +325,7 @@ phpGenerator.forBlock['math_constrain'] = function(block, generator) { return [code, Order.FUNCTION_CALL]; }; -phpGenerator.forBlock['math_random_int'] = function(block, generator) { +export function math_random_int(block, generator) { // Random integer between [X] and [Y]. const argument0 = generator.valueToCode(block, 'FROM', Order.NONE) || '0'; const argument1 = generator.valueToCode(block, 'TO', Order.NONE) || '0'; @@ -341,12 +341,12 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}($a, $b) { return [code, Order.FUNCTION_CALL]; }; -phpGenerator.forBlock['math_random_float'] = function(block, generator) { +export function math_random_float(block, generator) { // Random fraction between 0 and 1. return ['(float)rand()/(float)getrandmax()', Order.FUNCTION_CALL]; }; -phpGenerator.forBlock['math_atan2'] = function(block, generator) { +export function math_atan2(block, generator) { // Arctangent of point (X, Y) in degrees from -180 to 180. const argument0 = generator.valueToCode(block, 'X', Order.NONE) || '0'; const argument1 = generator.valueToCode(block, 'Y', Order.NONE) || '0'; diff --git a/generators/php/php_generator.js b/generators/php/php_generator.js new file mode 100644 index 000000000..59d595b83 --- /dev/null +++ b/generators/php/php_generator.js @@ -0,0 +1,310 @@ +/** + * @license + * Copyright 2015 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @fileoverview Helper functions for generating PHP for blocks. + * @suppress {checkTypes|globalThis} + */ + +import * as goog from '../../closure/goog/goog.js'; +goog.declareModuleId('Blockly.PHP'); + +import * as stringUtils from '../../core/utils/string.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 {inputTypes} from '../../core/inputs/input_types.js'; + + +/** + * Order of operation ENUMs. + * http://php.net/manual/en/language.operators.precedence.php + * @enum {number} + */ +export const Order = { + ATOMIC: 0, // 0 "" ... + CLONE: 1, // clone + NEW: 1, // new + MEMBER: 2.1, // [] + FUNCTION_CALL: 2.2, // () + POWER: 3, // ** + INCREMENT: 4, // ++ + DECREMENT: 4, // -- + BITWISE_NOT: 4, // ~ + CAST: 4, // (int) (float) (string) (array) ... + SUPPRESS_ERROR: 4, // @ + INSTANCEOF: 5, // instanceof + LOGICAL_NOT: 6, // ! + UNARY_PLUS: 7.1, // + + UNARY_NEGATION: 7.2, // - + MULTIPLICATION: 8.1, // * + DIVISION: 8.2, // / + MODULUS: 8.3, // % + ADDITION: 9.1, // + + SUBTRACTION: 9.2, // - + STRING_CONCAT: 9.3, // . + BITWISE_SHIFT: 10, // << >> + RELATIONAL: 11, // < <= > >= + EQUALITY: 12, // == != === !== <> <=> + REFERENCE: 13, // & + BITWISE_AND: 13, // & + BITWISE_XOR: 14, // ^ + BITWISE_OR: 15, // | + LOGICAL_AND: 16, // && + LOGICAL_OR: 17, // || + IF_NULL: 18, // ?? + CONDITIONAL: 19, // ?: + ASSIGNMENT: 20, // = += -= *= /= %= <<= >>= ... + LOGICAL_AND_WEAK: 21, // and + LOGICAL_XOR: 22, // xor + LOGICAL_OR_WEAK: 23, // or + NONE: 99, // (...) +}; + +export class PhpGenerator extends CodeGenerator { + /** + * List of outer-inner pairings that do NOT require parentheses. + * @type {!Array>} + */ + ORDER_OVERRIDES = [ + // (foo()).bar() -> foo().bar() + // (foo())[0] -> foo()[0] + [Order.MEMBER, Order.FUNCTION_CALL], + // (foo[0])[1] -> foo[0][1] + // (foo.bar).baz -> foo.bar.baz + [Order.MEMBER, Order.MEMBER], + // !(!foo) -> !!foo + [Order.LOGICAL_NOT, Order.LOGICAL_NOT], + // a * (b * c) -> a * b * c + [Order.MULTIPLICATION, Order.MULTIPLICATION], + // a + (b + c) -> a + b + c + [Order.ADDITION, Order.ADDITION], + // a && (b && c) -> a && b && c + [Order.LOGICAL_AND, Order.LOGICAL_AND], + // a || (b || c) -> a || b || c + [Order.LOGICAL_OR, Order.LOGICAL_OR] + ]; + + constructor(name) { + super(name ?? 'PHP'); + this.isInitialized = false; + + // Copy Order values onto instance for backwards compatibility + // while ensuring they are not part of the publically-advertised + // API. + // + // TODO(#7085): deprecate these in due course. (Could initially + // replace data properties with get accessors that call + // deprecate.warn().) + for (const key in Order) { + this['ORDER_' + key] = Order[key]; + } + + // List of illegal variable names. This is not intended to be a + // security feature. Blockly is 100% client-side, so bypassing + // this list is trivial. This is intended to prevent users from + // accidentally clobbering a built-in object or function. + this.addReservedWords( + // http://php.net/manual/en/reserved.keywords.php + '__halt_compiler,abstract,and,array,as,break,callable,case,catch,class,' + + 'clone,const,continue,declare,default,die,do,echo,else,elseif,empty,' + + 'enddeclare,endfor,endforeach,endif,endswitch,endwhile,eval,exit,' + + 'extends,final,for,foreach,function,global,goto,if,implements,include,' + + 'include_once,instanceof,insteadof,interface,isset,list,namespace,new,' + + 'or,print,private,protected,public,require,require_once,return,static,' + + 'switch,throw,trait,try,unset,use,var,while,xor,' + + // http://php.net/manual/en/reserved.constants.php + 'PHP_VERSION,PHP_MAJOR_VERSION,PHP_MINOR_VERSION,PHP_RELEASE_VERSION,' + + 'PHP_VERSION_ID,PHP_EXTRA_VERSION,PHP_ZTS,PHP_DEBUG,PHP_MAXPATHLEN,' + + 'PHP_OS,PHP_SAPI,PHP_EOL,PHP_INT_MAX,PHP_INT_SIZE,DEFAULT_INCLUDE_PATH,' + + 'PEAR_INSTALL_DIR,PEAR_EXTENSION_DIR,PHP_EXTENSION_DIR,PHP_PREFIX,' + + 'PHP_BINDIR,PHP_BINARY,PHP_MANDIR,PHP_LIBDIR,PHP_DATADIR,' + + 'PHP_SYSCONFDIR,PHP_LOCALSTATEDIR,PHP_CONFIG_FILE_PATH,' + + 'PHP_CONFIG_FILE_SCAN_DIR,PHP_SHLIB_SUFFIX,E_ERROR,E_WARNING,E_PARSE,' + + 'E_NOTICE,E_CORE_ERROR,E_CORE_WARNING,E_COMPILE_ERROR,' + + 'E_COMPILE_WARNING,E_USER_ERROR,E_USER_WARNING,E_USER_NOTICE,' + + 'E_DEPRECATED,E_USER_DEPRECATED,E_ALL,E_STRICT,' + + '__COMPILER_HALT_OFFSET__,TRUE,FALSE,NULL,__CLASS__,__DIR__,__FILE__,' + + '__FUNCTION__,__LINE__,__METHOD__,__NAMESPACE__,__TRAIT__' + ); + } + + /** + * Initialise the database of variable names. + * @param {!Workspace} workspace Workspace to generate code from. + */ + init(workspace) { + super.init(workspace); + + if (!this.nameDB_) { + this.nameDB_ = new Names(this.RESERVED_WORDS_, '$'); + } else { + this.nameDB_.reset(); + } + + this.nameDB_.setVariableMap(workspace.getVariableMap()); + this.nameDB_.populateVariables(workspace); + this.nameDB_.populateProcedures(workspace); + + this.isInitialized = true; + }; + + /** + * Prepend the generated code with the variable definitions. + * @param {string} code Generated code. + * @return {string} Completed code. + */ + finish(code) { + // Convert the definitions dictionary into a list. + const definitions = Object.values(this.definitions_); + // Call Blockly.CodeGenerator's finish. + code = super.finish(code); + this.isInitialized = false; + + this.nameDB_.reset(); + return definitions.join('\n\n') + '\n\n\n' + code; + }; + + /** + * Naked values are top-level blocks with outputs that aren't plugged into + * anything. A trailing semicolon is needed to make this legal. + * @param {string} line Line of generated code. + * @return {string} Legal line of code. + */ + scrubNakedValue(line) { + return line + ';\n'; + }; + + /** + * Encode a string as a properly escaped PHP string, complete with + * quotes. + * @param {string} string Text to encode. + * @return {string} PHP string. + * @protected + */ + quote_(string) { + string = string.replace(/\\/g, '\\\\') + .replace(/\n/g, '\\\n') + .replace(/'/g, '\\\''); + return '\'' + string + '\''; + }; + + /** + * Encode a string as a properly escaped multiline PHP string, complete with + * quotes. + * @param {string} string Text to encode. + * @return {string} PHP string. + * @protected + */ + multiline_quote_(string) { + const lines = string.split(/\n/g).map(this.quote_); + // Join with the following, plus a newline: + // . "\n" . + // Newline escaping only works in double-quoted strings. + return lines.join(' . \"\\n\" .\n'); + }; + + /** + * Common tasks for generating PHP 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 PHP code created for this block. + * @param {boolean=} opt_thisOnly True to generate code for only this + * statement. + * @return {string} PHP code with comments and subsequent blocks added. + * @protected + */ + scrub_(block, code, opt_thisOnly) { + let commentCode = ''; + // Only collect comments for blocks that aren't inline. + if (!block.outputConnection || !block.outputConnection.targetConnection) { + // Collect comment for this block. + let comment = block.getCommentText(); + if (comment) { + comment = stringUtils.wrap(comment, this.COMMENT_WRAP - 3); + commentCode += this.prefixLines(comment, '// ') + '\n'; + } + // Collect comments for all value arguments. + // 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(); + if (childBlock) { + comment = this.allNestedComments(childBlock); + if (comment) { + commentCode += this.prefixLines(comment, '// '); + } + } + } + } + } + const nextBlock = + block.nextConnection && block.nextConnection.targetBlock(); + const nextCode = opt_thisOnly ? '' : this.blockToCode(nextBlock); + return commentCode + code + nextCode; + }; + + /** + * Gets a property and adjusts the value while taking into account indexing. + * @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. + * @param {number=} opt_order The highest order acting on this value. + * @return {string|number} + */ + getAdjusted(block, atId, opt_delta, opt_negate, opt_order) { + let delta = opt_delta || 0; + let order = opt_order || this.ORDER_NONE; + if (block.workspace.options.oneBasedIndex) { + delta--; + } + let defaultAtIndex = block.workspace.options.oneBasedIndex ? '1' : '0'; + let outerOrder = order; + let innerOrder; + if (delta > 0) { + outerOrder = this.ORDER_ADDITION; + innerOrder = this.ORDER_ADDITION; + } else if (delta < 0) { + outerOrder = this.ORDER_SUBTRACTION; + innerOrder = this.ORDER_SUBTRACTION; + } else if (opt_negate) { + outerOrder = this.ORDER_UNARY_NEGATION; + innerOrder = this.ORDER_UNARY_NEGATION; + } + let at = this.valueToCode(block, atId, outerOrder) || defaultAtIndex; + + if (stringUtils.isNumber(at)) { + // If the index is a naked number, adjust it right now. + at = Number(at) + delta; + if (opt_negate) { + at = -at; + } + } else { + // If the index is dynamic, adjust it in code. + if (delta > 0) { + at = at + ' + ' + delta; + } else if (delta < 0) { + at = at + ' - ' + -delta; + } + if (opt_negate) { + if (delta) { + at = '-(' + at + ')'; + } else { + at = '-' + at; + } + } + innerOrder = Math.floor(innerOrder); + order = Math.floor(order); + if (innerOrder && order >= innerOrder) { + at = '(' + at + ')'; + } + } + return at; + }; +} diff --git a/generators/php/procedures.js b/generators/php/procedures.js index 1c2ed3dbf..9ff0de082 100644 --- a/generators/php/procedures.js +++ b/generators/php/procedures.js @@ -13,10 +13,10 @@ goog.declareModuleId('Blockly.PHP.procedures'); import * as Variables from '../../core/variables.js'; import {NameType} from '../../core/names.js'; -import {phpGenerator, Order} from '../php.js'; +import {Order} from './php_generator.js'; -phpGenerator.forBlock['procedures_defreturn'] = function(block, generator) { +export function procedures_defreturn(block, generator) { // Define a procedure with a return value. // First, add a 'global' statement for every variable that is not shadowed by // a local parameter. @@ -84,10 +84,9 @@ phpGenerator.forBlock['procedures_defreturn'] = function(block, generator) { // Defining a procedure without a return value uses the same generator as // a procedure with a return value. -phpGenerator.forBlock['procedures_defnoreturn'] = - phpGenerator.forBlock['procedures_defreturn']; +export const procedures_defnoreturn = procedures_defreturn; -phpGenerator.forBlock['procedures_callreturn'] = function(block, generator) { +export function procedures_callreturn(block, generator) { // Call a procedure with a return value. const funcName = generator.nameDB_.getName( @@ -101,7 +100,7 @@ phpGenerator.forBlock['procedures_callreturn'] = function(block, generator) { return [code, Order.FUNCTION_CALL]; }; -phpGenerator.forBlock['procedures_callnoreturn'] = function(block, generator) { +export function procedures_callnoreturn(block, generator) { // 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. @@ -109,7 +108,7 @@ phpGenerator.forBlock['procedures_callnoreturn'] = function(block, generator) { return tuple[0] + ';\n'; }; -phpGenerator.forBlock['procedures_ifreturn'] = function(block, generator) { +export function procedures_ifreturn(block, generator) { // Conditionally return value from a procedure. const condition = generator.valueToCode(block, 'CONDITION', Order.NONE) || 'false'; diff --git a/generators/php/text.js b/generators/php/text.js index 6992dad12..91da583a8 100644 --- a/generators/php/text.js +++ b/generators/php/text.js @@ -12,16 +12,16 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.PHP.texts'); import {NameType} from '../../core/names.js'; -import {phpGenerator, Order} from '../php.js'; +import {Order} from './php_generator.js'; -phpGenerator.forBlock['text'] = function(block, generator) { +export function text(block, generator) { // Text value. const code = generator.quote_(block.getFieldValue('TEXT')); return [code, Order.ATOMIC]; }; -phpGenerator.forBlock['text_multiline'] = function(block, generator) { +export function text_multiline(block, generator) { // Text value. const code = generator.multiline_quote_(block.getFieldValue('TEXT')); const order = @@ -29,7 +29,7 @@ phpGenerator.forBlock['text_multiline'] = function(block, generator) { return [code, order]; }; -phpGenerator.forBlock['text_join'] = function(block, generator) { +export function text_join(block, generator) { // Create a string made up of any number of elements of any type. if (block.itemCount_ === 0) { return ["''", Order.ATOMIC]; @@ -55,7 +55,7 @@ phpGenerator.forBlock['text_join'] = function(block, generator) { } }; -phpGenerator.forBlock['text_append'] = function(block, generator) { +export function text_append(block, generator) { // Append to a variable in place. const varName = generator.nameDB_.getName( @@ -65,7 +65,7 @@ phpGenerator.forBlock['text_append'] = function(block, generator) { return varName + ' .= ' + value + ';\n'; }; -phpGenerator.forBlock['text_length'] = function(block, generator) { +export function text_length(block, generator) { // String or array length. const functionName = generator.provideFunction_('length', ` function ${generator.FUNCTION_NAME_PLACEHOLDER_}($value) { @@ -79,13 +79,13 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}($value) { return [functionName + '(' + text + ')', Order.FUNCTION_CALL]; }; -phpGenerator.forBlock['text_isEmpty'] = function(block, generator) { +export function text_isEmpty(block, generator) { // Is the string null or array empty? const text = generator.valueToCode(block, 'VALUE', Order.NONE) || "''"; return ['empty(' + text + ')', Order.FUNCTION_CALL]; }; -phpGenerator.forBlock['text_indexOf'] = function(block, generator) { +export function text_indexOf(block, generator) { // Search the text for a substring. const operator = block.getFieldValue('END') === 'FIRST' ? 'strpos' : 'strrpos'; @@ -110,7 +110,7 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}($text, $search) { return [code, Order.FUNCTION_CALL]; }; -phpGenerator.forBlock['text_charAt'] = function(block, generator) { +export function text_charAt(block, generator) { // Get letter at index. const where = block.getFieldValue('WHERE') || 'FROM_START'; const textOrder = (where === 'RANDOM') ? Order.NONE : Order.NONE; @@ -147,7 +147,7 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}($text) { throw Error('Unhandled option (text_charAt).'); }; -phpGenerator.forBlock['text_getSubstring'] = function(block, generator) { +export function text_getSubstring(block, generator) { // Get substring. const where1 = block.getFieldValue('WHERE1'); const where2 = block.getFieldValue('WHERE2'); @@ -186,7 +186,7 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}($text, $where1, $at1, $where2, } }; -phpGenerator.forBlock['text_changeCase'] = function(block, generator) { +export function text_changeCase(block, generator) { // Change capitalization. const text = generator.valueToCode(block, 'TEXT', Order.NONE) || "''"; let code; @@ -200,7 +200,7 @@ phpGenerator.forBlock['text_changeCase'] = function(block, generator) { return [code, Order.FUNCTION_CALL]; }; -phpGenerator.forBlock['text_trim'] = function(block, generator) { +export function text_trim(block, generator) { // Trim spaces. const OPERATORS = {'LEFT': 'ltrim', 'RIGHT': 'rtrim', 'BOTH': 'trim'}; const operator = OPERATORS[block.getFieldValue('MODE')]; @@ -208,13 +208,13 @@ phpGenerator.forBlock['text_trim'] = function(block, generator) { return [operator + '(' + text + ')', Order.FUNCTION_CALL]; }; -phpGenerator.forBlock['text_print'] = function(block, generator) { +export function text_print(block, generator) { // Print statement. const msg = generator.valueToCode(block, 'TEXT', Order.NONE) || "''"; return 'print(' + msg + ');\n'; }; -phpGenerator.forBlock['text_prompt_ext'] = function(block, generator) { +export function text_prompt_ext(block, generator) { // Prompt function. let msg; if (block.getField('TEXT')) { @@ -232,9 +232,9 @@ phpGenerator.forBlock['text_prompt_ext'] = function(block, generator) { return [code, Order.FUNCTION_CALL]; }; -phpGenerator.forBlock['text_prompt'] = phpGenerator.forBlock['text_prompt_ext']; +export const text_prompt = text_prompt_ext; -phpGenerator.forBlock['text_count'] = function(block, generator) { +export function text_count(block, generator) { const text = generator.valueToCode(block, 'TEXT', Order.NONE) || "''"; const sub = generator.valueToCode(block, 'SUB', Order.NONE) || "''"; const code = 'strlen(' + sub + ') === 0' + @@ -243,7 +243,7 @@ phpGenerator.forBlock['text_count'] = function(block, generator) { return [code, Order.CONDITIONAL]; }; -phpGenerator.forBlock['text_replace'] = function(block, generator) { +export function text_replace(block, generator) { const text = generator.valueToCode(block, 'TEXT', Order.NONE) || "''"; const from = generator.valueToCode(block, 'FROM', Order.NONE) || "''"; const to = generator.valueToCode(block, 'TO', Order.NONE) || "''"; @@ -251,7 +251,7 @@ phpGenerator.forBlock['text_replace'] = function(block, generator) { return [code, Order.FUNCTION_CALL]; }; -phpGenerator.forBlock['text_reverse'] = function(block, generator) { +export function text_reverse(block, generator) { const text = generator.valueToCode(block, 'TEXT', Order.NONE) || "''"; const code = 'strrev(' + text + ')'; return [code, Order.FUNCTION_CALL]; diff --git a/generators/php/variables.js b/generators/php/variables.js index f1c47d1cd..f29274da7 100644 --- a/generators/php/variables.js +++ b/generators/php/variables.js @@ -12,10 +12,10 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.PHP.variables'); import {NameType} from '../../core/names.js'; -import {phpGenerator, Order} from '../php.js'; +import {Order} from './php_generator.js'; -phpGenerator.forBlock['variables_get'] = function(block, generator) { +export function variables_get(block, generator) { // Variable getter. const code = generator.nameDB_.getName( @@ -23,7 +23,7 @@ phpGenerator.forBlock['variables_get'] = function(block, generator) { return [code, Order.ATOMIC]; }; -phpGenerator.forBlock['variables_set'] = function(block, generator) { +export function variables_set(block, generator) { // Variable setter. const argument0 = generator.valueToCode(block, 'VALUE', Order.ASSIGNMENT) || '0'; diff --git a/generators/php/variables_dynamic.js b/generators/php/variables_dynamic.js index 3cfd652ad..030edc92c 100644 --- a/generators/php/variables_dynamic.js +++ b/generators/php/variables_dynamic.js @@ -11,12 +11,9 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.PHP.variablesDynamic'); -import {phpGenerator} from '../php.js'; -import './variables.js'; - // generator is dynamically typed. -phpGenerator.forBlock['variables_get_dynamic'] = - phpGenerator.forBlock['variables_get']; -phpGenerator.forBlock['variables_set_dynamic'] = - phpGenerator.forBlock['variables_set']; +export { + variables_get as variables_get_dynamic, + variables_set as variables_set_dynamic, +} from './variables.js'; diff --git a/generators/python.js b/generators/python.js index 6c1e7d7b1..19b64ffd2 100644 --- a/generators/python.js +++ b/generators/python.js @@ -1,347 +1,44 @@ /** * @license - * Copyright 2012 Google LLC + * Copyright 2021 Google LLC * SPDX-License-Identifier: Apache-2.0 */ /** - * @fileoverview Helper functions for generating Python for blocks. - * @suppress {checkTypes|globalThis} + * @fileoverview Complete helper functions for generating Python for + * blocks. This is the entrypoint for python_compressed.js. + * @suppress {extraRequire} */ import * as goog from '../closure/goog/goog.js'; -goog.declareModuleId('Blockly.Python'); +goog.declareModuleId('Blockly.Python.all'); -import * as stringUtils from '../core/utils/string.js'; -import * as Variables from '../core/variables.js'; -// import type {Block} from '../core/block.js'; -import {CodeGenerator} from '../core/generator.js'; -import {Names, NameType} from '../core/names.js'; -// import type {Workspace} from '../core/workspace.js'; -import {inputTypes} from '../core/inputs/input_types.js'; +import {PythonGenerator} from './python/python_generator.js'; +import * as colour from './python/colour.js'; +import * as lists from './python/lists.js'; +import * as logic from './python/logic.js'; +import * as loops from './python/loops.js'; +import * as math from './python/math.js'; +import * as procedures from './python/procedures.js'; +import * as text from './python/text.js'; +import * as variables from './python/variables.js'; +import * as variablesDynamic from './python/variables_dynamic.js'; +export * from './python/python_generator.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, // (...) -}; - -/** - * PythonScript code generator class. - */ -export class PythonGenerator extends CodeGenerator { - /** - * List of outer-inner pairings that do NOT require parentheses. - * @type {!Array>} - */ - ORDER_OVERRIDES = [ - // (foo()).bar -> foo().bar - // (foo())[0] -> foo()[0] - [Order.FUNCTION_CALL, Order.MEMBER], - // (foo())() -> foo()() - [Order.FUNCTION_CALL, Order.FUNCTION_CALL], - // (foo.bar).baz -> foo.bar.baz - // (foo.bar)[0] -> foo.bar[0] - // (foo[0]).bar -> foo[0].bar - // (foo[0])[1] -> foo[0][1] - [Order.MEMBER, Order.MEMBER], - // (foo.bar)() -> foo.bar() - // (foo[0])() -> foo[0]() - [Order.MEMBER, Order.FUNCTION_CALL], - - // not (not foo) -> not not foo - [Order.LOGICAL_NOT, Order.LOGICAL_NOT], - // 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] - ]; - - constructor(name) { - super(name ?? 'Python'); - this.isInitialized = false; - - // Copy Order values onto instance for backwards compatibility - // while ensuring they are not part of the publically-advertised - // API. - // - // TODO(#7085): deprecate these in due course. (Could initially - // replace data properties with get accessors that call - // deprecate.warn().) - for (const key in Order) { - this['ORDER_' + key] = Order[key]; - } - - // List of illegal variable names. This is not intended to be a - // security feature. Blockly is 100% client-side, so bypassing - // this list is trivial. This is intended to prevent users from - // accidentally clobbering a built-in object or function. - this.addReservedWords( - // import keyword - // print(','.join(sorted(keyword.kwlist))) - // 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' - ); - } - - /** - * Initialise the database of variable names. - * @param {!Workspace} workspace Workspace to generate code from. - * @this {CodeGenerator} - */ - init(workspace) { - super.init(workspace); - - /** - * Empty loops or conditionals are not allowed in Python. - */ - this.PASS = this.INDENT + 'pass\n'; - - if (!this.nameDB_) { - this.nameDB_ = new Names(this.RESERVED_WORDS_); - } else { - this.nameDB_.reset(); - } - - this.nameDB_.setVariableMap(workspace.getVariableMap()); - this.nameDB_.populateVariables(workspace); - this.nameDB_.populateProcedures(workspace); - - const defvars = []; - // Add developer variables (not created or named by the user). - 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'); - } - - // 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.nameDB_.getName(variables[i].getId(), NameType.VARIABLE) + - ' = None'); - } - - this.definitions_['variables'] = defvars.join('\n'); - this.isInitialized = true; - } - - /** - * Prepend the generated code with import statements and variable definitions. - * @param {string} code Generated code. - * @return {string} Completed code. - */ - finish(code) { - // Convert the definitions dictionary into a list. - const imports = []; - const definitions = []; - for (let name in this.definitions_) { - const def = this.definitions_[name]; - if (def.match(/^(from\s+\S+\s+)?import\s+\S+/)) { - imports.push(def); - } else { - definitions.push(def); - } - } - // Call Blockly.CodeGenerator's finish. - code = super.finish(code); - this.isInitialized = false; - - 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; - } - - /** - * 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. - */ - scrubNakedValue(line) { - 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. - * @protected - */ - quote_(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) { - if (string.indexOf('"') === -1) { - quote = '"'; - } else { - string = string.replace(/'/g, '\\\''); - } - } - return quote + string + quote; - } - - /** - * Encode a string as a properly escaped multiline Python string, complete - * with quotes. - * @param {string} string Text to encode. - * @return {string} Python string. - * @protected - */ - multiline_quote_(string) { - const lines = string.split(/\n/g).map(this.quote_); - // Join with the following, plus a newline: - // + '\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 - */ - scrub_(block, code, opt_thisOnly) { - let commentCode = ''; - // Only collect comments for blocks that aren't inline. - if (!block.outputConnection || !block.outputConnection.targetConnection) { - // Collect comment for this block. - let comment = block.getCommentText(); - if (comment) { - comment = stringUtils.wrap(comment, this.COMMENT_WRAP - 3); - commentCode += this.prefixLines(comment + '\n', '# '); - } - // Collect comments for all value arguments. - // 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(); - if (childBlock) { - comment = this.allNestedComments(childBlock); - if (comment) { - commentCode += this.prefixLines(comment, '# '); - } - } - } - } - } - const nextBlock = block.nextConnection && block.nextConnection.targetBlock(); - const nextCode = opt_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} - */ - getAdjustedInt(block, atId, opt_delta, opt_negate) { - let delta = opt_delta || 0; - 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; - - if (stringUtils.isNumber(at)) { - // If the index is a naked number, adjust it right now. - at = parseInt(at, 10) + delta; - if (opt_negate) { - at = -at; - } - } else { - // If the index is dynamic, adjust it in code. - if (delta > 0) { - at = 'int(' + at + ' + ' + delta + ')'; - } else if (delta < 0) { - at = 'int(' + at + ' - ' + -delta + ')'; - } else { - at = 'int(' + at + ')'; - } - if (opt_negate) { - at = '-' + at; - } - } - return at; - } -} - -/** - * Python code generator. + * Python code generator instance. * @type {!PythonGenerator} */ export const pythonGenerator = new PythonGenerator(); - + +// Add reserved words. This list should include all words mentioned +// in RESERVED WORDS: comments in the imports above. +pythonGenerator.addReservedWords('math,random,Number'); + +// Install per-block-type generator functions: +Object.assign( + pythonGenerator.forBlock, + colour, lists, logic, loops, math, procedures, + text, variables, variablesDynamic +); diff --git a/generators/python/all.js b/generators/python/all.js deleted file mode 100644 index 10c97e029..000000000 --- a/generators/python/all.js +++ /dev/null @@ -1,26 +0,0 @@ -/** - * @license - * Copyright 2021 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * @fileoverview Complete helper functions for generating Python for - * blocks. This is the entrypoint for python_compressed.js. - * @suppress {extraRequire} - */ - -import * as goog from '../../closure/goog/goog.js'; -goog.declareModuleId('Blockly.Python.all'); - -import './colour.js'; -import './lists.js'; -import './logic.js'; -import './loops.js'; -import './math.js'; -import './procedures.js'; -import './text.js'; -import './variables.js'; -import './variables_dynamic.js'; - -export * from '../python.js'; diff --git a/generators/python/colour.js b/generators/python/colour.js index a1d06a380..3236fc408 100644 --- a/generators/python/colour.js +++ b/generators/python/colour.js @@ -11,23 +11,23 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.Python.colour'); -import {pythonGenerator, Order} from '../python.js'; +import {Order} from './python_generator.js'; -pythonGenerator.forBlock['colour_picker'] = function(block, generator) { +export function colour_picker(block, generator) { // Colour picker. const code = generator.quote_(block.getFieldValue('COLOUR')); return [code, Order.ATOMIC]; }; -pythonGenerator.forBlock['colour_random'] = function(block, generator) { +export function colour_random(block, generator) { // Generate a random colour. generator.definitions_['import_random'] = 'import random'; const code = '\'#%06x\' % random.randint(0, 2**24 - 1)'; return [code, Order.FUNCTION_CALL]; }; -pythonGenerator.forBlock['colour_rgb'] = function(block, generator) { +export function colour_rgb(block, generator) { // Compose a colour from RGB components expressed as percentages. const functionName = generator.provideFunction_('colour_rgb', ` def ${generator.FUNCTION_NAME_PLACEHOLDER_}(r, g, b): @@ -43,7 +43,7 @@ def ${generator.FUNCTION_NAME_PLACEHOLDER_}(r, g, b): return [code, Order.FUNCTION_CALL]; }; -pythonGenerator.forBlock['colour_blend'] = function(block, generator) { +export function colour_blend(block, generator) { // Blend two colours together. const functionName = generator.provideFunction_('colour_blend', ` def ${generator.FUNCTION_NAME_PLACEHOLDER_}(colour1, colour2, ratio): diff --git a/generators/python/lists.js b/generators/python/lists.js index 08fb410c5..f4dbd1166 100644 --- a/generators/python/lists.js +++ b/generators/python/lists.js @@ -13,15 +13,15 @@ goog.declareModuleId('Blockly.Python.lists'); import * as stringUtils from '../../core/utils/string.js'; import {NameType} from '../../core/names.js'; -import {pythonGenerator, Order} from '../python.js'; +import {Order} from './python_generator.js'; -pythonGenerator.forBlock['lists_create_empty'] = function(block, generator) { +export function lists_create_empty(block, generator) { // Create an empty list. return ['[]', Order.ATOMIC]; }; -pythonGenerator.forBlock['lists_create_with'] = function(block, generator) { +export function lists_create_with(block, generator) { // 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++) { @@ -32,7 +32,7 @@ pythonGenerator.forBlock['lists_create_with'] = function(block, generator) { return [code, Order.ATOMIC]; }; -pythonGenerator.forBlock['lists_repeat'] = function(block, generator) { +export function lists_repeat(block, generator) { // Create a list with one element repeated. const item = generator.valueToCode(block, 'ITEM', Order.NONE) || 'None'; const times = @@ -41,20 +41,20 @@ pythonGenerator.forBlock['lists_repeat'] = function(block, generator) { return [code, Order.MULTIPLICATIVE]; }; -pythonGenerator.forBlock['lists_length'] = function(block, generator) { +export function lists_length(block, generator) { // String or array length. const list = generator.valueToCode(block, 'VALUE', Order.NONE) || '[]'; return ['len(' + list + ')', Order.FUNCTION_CALL]; }; -pythonGenerator.forBlock['lists_isEmpty'] = function(block, generator) { +export function lists_isEmpty(block, generator) { // 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]; }; -pythonGenerator.forBlock['lists_indexOf'] = function(block, generator) { +export function lists_indexOf(block, generator) { // Find an item in the list. const item = generator.valueToCode(block, 'FIND', Order.NONE) || '[]'; const list = generator.valueToCode(block, 'VALUE', Order.NONE) || "''"; @@ -88,7 +88,7 @@ def ${generator.FUNCTION_NAME_PLACEHOLDER_}(my_list, elem): return [code, Order.FUNCTION_CALL]; }; -pythonGenerator.forBlock['lists_getIndex'] = function(block, generator) { +export function lists_getIndex(block, generator) { // Get element at index. // Note: Until January 2013 this block did not have MODE or WHERE inputs. const mode = block.getFieldValue('MODE') || 'GET'; @@ -170,7 +170,7 @@ def ${generator.FUNCTION_NAME_PLACEHOLDER_}(myList): throw Error('Unhandled combination (lists_getIndex).'); }; -pythonGenerator.forBlock['lists_setIndex'] = function(block, generator) { +export function lists_setIndex(block, generator) { // 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) || '[]'; @@ -242,7 +242,7 @@ pythonGenerator.forBlock['lists_setIndex'] = function(block, generator) { throw Error('Unhandled combination (lists_setIndex).'); }; -pythonGenerator.forBlock['lists_getSublist'] = function(block, generator) { +export function lists_getSublist(block, generator) { // Get sublist. const list = generator.valueToCode(block, 'LIST', Order.MEMBER) || '[]'; const where1 = block.getFieldValue('WHERE1'); @@ -291,7 +291,7 @@ pythonGenerator.forBlock['lists_getSublist'] = function(block, generator) { return [code, Order.MEMBER]; }; -pythonGenerator.forBlock['lists_sort'] = function(block, generator) { +export function lists_sort(block, generator) { // Block for sorting a list. const list = (generator.valueToCode(block, 'LIST', Order.NONE) || '[]'); const type = block.getFieldValue('TYPE'); @@ -318,7 +318,7 @@ def ${generator.FUNCTION_NAME_PLACEHOLDER_}(my_list, type, reverse): return [code, Order.FUNCTION_CALL]; }; -pythonGenerator.forBlock['lists_split'] = function(block, generator) { +export function lists_split(block, generator) { // Block for splitting text into a list, or joining a list into text. const mode = block.getFieldValue('MODE'); let code; @@ -339,7 +339,7 @@ pythonGenerator.forBlock['lists_split'] = function(block, generator) { return [code, Order.FUNCTION_CALL]; }; -pythonGenerator.forBlock['lists_reverse'] = function(block, generator) { +export function lists_reverse(block, generator) { // Block for reversing a list. const list = generator.valueToCode(block, 'LIST', Order.NONE) || '[]'; const code = 'list(reversed(' + list + '))'; diff --git a/generators/python/logic.js b/generators/python/logic.js index 4308d6cb8..a71a0ef5f 100644 --- a/generators/python/logic.js +++ b/generators/python/logic.js @@ -11,10 +11,10 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.Python.logic'); -import {pythonGenerator, Order} from '../python.js'; +import {Order} from './python_generator.js'; -pythonGenerator.forBlock['controls_if'] = function(block, generator) { +export function controls_if(block, generator) { // If/elseif/else condition. let n = 0; let code = '', branchCode, conditionCode; @@ -55,10 +55,9 @@ pythonGenerator.forBlock['controls_if'] = function(block, generator) { return code; }; -pythonGenerator.forBlock['controls_ifelse'] = - pythonGenerator.forBlock['controls_if']; +export const controls_ifelse = controls_if; -pythonGenerator.forBlock['logic_compare'] = function(block, generator) { +export function logic_compare(block, generator) { // Comparison operator. const OPERATORS = {'EQ': '==', 'NEQ': '!=', 'LT': '<', 'LTE': '<=', 'GT': '>', 'GTE': '>='}; @@ -70,7 +69,7 @@ pythonGenerator.forBlock['logic_compare'] = function(block, generator) { return [code, order]; }; -pythonGenerator.forBlock['logic_operation'] = function(block, generator) { +export function logic_operation(block, generator) { // Operations 'and', 'or'. const operator = (block.getFieldValue('OP') === 'AND') ? 'and' : 'or'; const order = @@ -95,7 +94,7 @@ pythonGenerator.forBlock['logic_operation'] = function(block, generator) { return [code, order]; }; -pythonGenerator.forBlock['logic_negate'] = function(block, generator) { +export function logic_negate(block, generator) { // Negation. const argument0 = generator.valueToCode(block, 'BOOL', Order.LOGICAL_NOT) || 'True'; @@ -103,18 +102,18 @@ pythonGenerator.forBlock['logic_negate'] = function(block, generator) { return [code, Order.LOGICAL_NOT]; }; -pythonGenerator.forBlock['logic_boolean'] = function(block, generator) { +export function logic_boolean(block, generator) { // Boolean values true and false. const code = (block.getFieldValue('BOOL') === 'TRUE') ? 'True' : 'False'; return [code, Order.ATOMIC]; }; -pythonGenerator.forBlock['logic_null'] = function(block, generator) { +export function logic_null(block, generator) { // Null data type. return ['None', Order.ATOMIC]; }; -pythonGenerator.forBlock['logic_ternary'] = function(block, generator) { +export function logic_ternary(block, generator) { // Ternary operator. const value_if = generator.valueToCode(block, 'IF', Order.CONDITIONAL) || 'False'; diff --git a/generators/python/loops.js b/generators/python/loops.js index aef33c2f9..f66d90802 100644 --- a/generators/python/loops.js +++ b/generators/python/loops.js @@ -13,10 +13,10 @@ goog.declareModuleId('Blockly.Python.loops'); import * as stringUtils from '../../core/utils/string.js'; import {NameType} from '../../core/names.js'; -import {pythonGenerator, Order} from '../python.js'; +import {Order} from './python_generator.js'; -pythonGenerator.forBlock['controls_repeat_ext'] = function(block, generator) { +export function controls_repeat_ext(block, generator) { // Repeat n times. let repeats; if (block.getField('TIMES')) { @@ -39,10 +39,9 @@ pythonGenerator.forBlock['controls_repeat_ext'] = function(block, generator) { return code; }; -pythonGenerator.forBlock['controls_repeat'] = - pythonGenerator.forBlock['controls_repeat_ext']; +export const controls_repeat = controls_repeat_ext; -pythonGenerator.forBlock['controls_whileUntil'] = function(block, generator) { +export function controls_whileUntil(block, generator) { // Do while/until loop. const until = block.getFieldValue('MODE') === 'UNTIL'; let argument0 = generator.valueToCode( @@ -57,7 +56,7 @@ pythonGenerator.forBlock['controls_whileUntil'] = function(block, generator) { return 'while ' + argument0 + ':\n' + branch; }; -pythonGenerator.forBlock['controls_for'] = function(block, generator) { +export function controls_for(block, generator) { // For loop. const variable0 = generator.nameDB_.getName( @@ -166,7 +165,7 @@ def ${generator.FUNCTION_NAME_PLACEHOLDER_}(start, stop, step): return code; }; -pythonGenerator.forBlock['controls_forEach'] = function(block, generator) { +export function controls_forEach(block, generator) { // For each loop. const variable0 = generator.nameDB_.getName( @@ -179,7 +178,7 @@ pythonGenerator.forBlock['controls_forEach'] = function(block, generator) { return code; }; -pythonGenerator.forBlock['controls_flow_statements'] = function(block, generator) { +export function controls_flow_statements(block, generator) { // Flow statements: continue, break. let xfix = ''; if (generator.STATEMENT_PREFIX) { diff --git a/generators/python/math.js b/generators/python/math.js index cfd61a118..da792346c 100644 --- a/generators/python/math.js +++ b/generators/python/math.js @@ -12,13 +12,13 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.Python.math'); import {NameType} from '../../core/names.js'; -import {pythonGenerator, Order} from '../python.js'; +import {Order} from './python_generator.js'; // If any new block imports any library, add that library name here. -pythonGenerator.addReservedWords('math,random,Number'); +// RESERVED WORDS: 'math,random,Number' -pythonGenerator.forBlock['math_number'] = function(block, generator) { +export function math_number(block, generator) { // Numeric value. let code = Number(block.getFieldValue('NUM')); let order; @@ -34,7 +34,7 @@ pythonGenerator.forBlock['math_number'] = function(block, generator) { return [code, order]; }; -pythonGenerator.forBlock['math_arithmetic'] = function(block, generator) { +export function math_arithmetic(block, generator) { // Basic arithmetic operators, and power. const OPERATORS = { 'ADD': [' + ', Order.ADDITIVE], @@ -57,7 +57,7 @@ pythonGenerator.forBlock['math_arithmetic'] = function(block, generator) { // legibility of the generated code. }; -pythonGenerator.forBlock['math_single'] = function(block, generator) { +export function math_single(block, generator) { // Math operators with single operand. const operator = block.getFieldValue('OP'); let code; @@ -135,7 +135,7 @@ pythonGenerator.forBlock['math_single'] = function(block, generator) { return [code, Order.MULTIPLICATIVE]; }; -pythonGenerator.forBlock['math_constant'] = function(block, generator) { +export function math_constant(block, generator) { // Constants: PI, E, the Golden Ratio, sqrt(2), 1/sqrt(2), INFINITY. const CONSTANTS = { 'PI': ['math.pi', Order.MEMBER], @@ -152,7 +152,7 @@ pythonGenerator.forBlock['math_constant'] = function(block, generator) { return CONSTANTS[constant]; }; -pythonGenerator.forBlock['math_number_property'] = function(block, generator) { +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 = { @@ -211,7 +211,7 @@ def ${generator.FUNCTION_NAME_PLACEHOLDER_}(n): return [code, outputOrder]; }; -pythonGenerator.forBlock['math_change'] = function(block, generator) { +export function math_change(block, generator) { // Add to a variable in place. generator.definitions_['from_numbers_import_Number'] = 'from numbers import Number'; @@ -225,13 +225,11 @@ pythonGenerator.forBlock['math_change'] = function(block, generator) { }; // Rounding functions have a single operand. -pythonGenerator.forBlock['math_round'] = - pythonGenerator.forBlock['math_single']; +export const math_round = math_single; // Trigonometry functions have a single operand. -pythonGenerator.forBlock['math_trig'] = - pythonGenerator.forBlock['math_single']; +export const math_trig = math_single; -pythonGenerator.forBlock['math_on_list'] = function(block, generator) { +export function math_on_list(block, generator) { // Math functions for lists. const func = block.getFieldValue('OP'); const list = generator.valueToCode(block, 'LIST', Order.NONE) || '[]'; @@ -329,7 +327,7 @@ def ${generator.FUNCTION_NAME_PLACEHOLDER_}(numbers): return [code, Order.FUNCTION_CALL]; }; -pythonGenerator.forBlock['math_modulo'] = function(block, generator) { +export function math_modulo(block, generator) { // Remainder computation. const argument0 = generator.valueToCode(block, 'DIVIDEND', Order.MULTIPLICATIVE) || @@ -341,7 +339,7 @@ pythonGenerator.forBlock['math_modulo'] = function(block, generator) { return [code, Order.MULTIPLICATIVE]; }; -pythonGenerator.forBlock['math_constrain'] = function(block, generator) { +export function math_constrain(block, generator) { // Constrain a number between two limits. const argument0 = generator.valueToCode(block, 'VALUE', Order.NONE) || '0'; @@ -355,7 +353,7 @@ pythonGenerator.forBlock['math_constrain'] = function(block, generator) { return [code, Order.FUNCTION_CALL]; }; -pythonGenerator.forBlock['math_random_int'] = function(block, generator) { +export function math_random_int(block, generator) { // Random integer between [X] and [Y]. generator.definitions_['import_random'] = 'import random'; const argument0 = @@ -366,13 +364,13 @@ pythonGenerator.forBlock['math_random_int'] = function(block, generator) { return [code, Order.FUNCTION_CALL]; }; -pythonGenerator.forBlock['math_random_float'] = function(block, generator) { +export function math_random_float(block, generator) { // Random fraction between 0 and 1. generator.definitions_['import_random'] = 'import random'; return ['random.random()', Order.FUNCTION_CALL]; }; -pythonGenerator.forBlock['math_atan2'] = function(block, generator) { +export function math_atan2(block, generator) { // Arctangent of point (X, Y) in degrees from -180 to 180. generator.definitions_['import_math'] = 'import math'; const argument0 = generator.valueToCode(block, 'X', Order.NONE) || '0'; diff --git a/generators/python/procedures.js b/generators/python/procedures.js index e5c2cca4b..65e17ddc3 100644 --- a/generators/python/procedures.js +++ b/generators/python/procedures.js @@ -13,10 +13,10 @@ goog.declareModuleId('Blockly.Python.procedures'); import * as Variables from '../../core/variables.js'; import {NameType} from '../../core/names.js'; -import {pythonGenerator, Order} from '../python.js'; +import {Order} from './python_generator.js'; -pythonGenerator.forBlock['procedures_defreturn'] = function(block, generator) { +export function procedures_defreturn(block, generator) { // Define a procedure with a return value. // First, add a 'global' statement for every variable that is not shadowed by // a local parameter. @@ -87,10 +87,9 @@ pythonGenerator.forBlock['procedures_defreturn'] = function(block, generator) { // Defining a procedure without a return value uses the same generator as // a procedure with a return value. -pythonGenerator.forBlock['procedures_defnoreturn'] = - pythonGenerator.forBlock['procedures_defreturn']; +export const procedures_defnoreturn = procedures_defreturn; -pythonGenerator.forBlock['procedures_callreturn'] = function(block, generator) { +export function procedures_callreturn(block, generator) { // Call a procedure with a return value. const funcName = generator.nameDB_.getName( @@ -105,7 +104,7 @@ pythonGenerator.forBlock['procedures_callreturn'] = function(block, generator) { return [code, Order.FUNCTION_CALL]; }; -pythonGenerator.forBlock['procedures_callnoreturn'] = function(block, generator) { +export function procedures_callnoreturn(block, generator) { // 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. @@ -113,7 +112,7 @@ pythonGenerator.forBlock['procedures_callnoreturn'] = function(block, generator) return tuple[0] + '\n'; }; -pythonGenerator.forBlock['procedures_ifreturn'] = function(block, generator) { +export function procedures_ifreturn(block, generator) { // Conditionally return value from a procedure. const condition = generator.valueToCode(block, 'CONDITION', Order.NONE) || 'False'; diff --git a/generators/python/python_generator.js b/generators/python/python_generator.js new file mode 100644 index 000000000..5897b4366 --- /dev/null +++ b/generators/python/python_generator.js @@ -0,0 +1,340 @@ +/** + * @license + * Copyright 2012 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @fileoverview Helper functions for generating Python for blocks. + * @suppress {checkTypes|globalThis} + */ + +import * as goog from '../../closure/goog/goog.js'; +goog.declareModuleId('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 {CodeGenerator} from '../../core/generator.js'; +import {Names, NameType} from '../../core/names.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, // (...) +}; + +/** + * PythonScript code generator class. + */ +export class PythonGenerator extends CodeGenerator { + /** + * List of outer-inner pairings that do NOT require parentheses. + * @type {!Array>} + */ + ORDER_OVERRIDES = [ + // (foo()).bar -> foo().bar + // (foo())[0] -> foo()[0] + [Order.FUNCTION_CALL, Order.MEMBER], + // (foo())() -> foo()() + [Order.FUNCTION_CALL, Order.FUNCTION_CALL], + // (foo.bar).baz -> foo.bar.baz + // (foo.bar)[0] -> foo.bar[0] + // (foo[0]).bar -> foo[0].bar + // (foo[0])[1] -> foo[0][1] + [Order.MEMBER, Order.MEMBER], + // (foo.bar)() -> foo.bar() + // (foo[0])() -> foo[0]() + [Order.MEMBER, Order.FUNCTION_CALL], + + // not (not foo) -> not not foo + [Order.LOGICAL_NOT, Order.LOGICAL_NOT], + // 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] + ]; + + constructor(name) { + super(name ?? 'Python'); + this.isInitialized = false; + + // Copy Order values onto instance for backwards compatibility + // while ensuring they are not part of the publically-advertised + // API. + // + // TODO(#7085): deprecate these in due course. (Could initially + // replace data properties with get accessors that call + // deprecate.warn().) + for (const key in Order) { + this['ORDER_' + key] = Order[key]; + } + + // List of illegal variable names. This is not intended to be a + // security feature. Blockly is 100% client-side, so bypassing + // this list is trivial. This is intended to prevent users from + // accidentally clobbering a built-in object or function. + this.addReservedWords( + // import keyword + // print(','.join(sorted(keyword.kwlist))) + // 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' + ); + } + + /** + * Initialise the database of variable names. + * @param {!Workspace} workspace Workspace to generate code from. + * @this {CodeGenerator} + */ + init(workspace) { + super.init(workspace); + + /** + * Empty loops or conditionals are not allowed in Python. + */ + this.PASS = this.INDENT + 'pass\n'; + + if (!this.nameDB_) { + this.nameDB_ = new Names(this.RESERVED_WORDS_); + } else { + this.nameDB_.reset(); + } + + this.nameDB_.setVariableMap(workspace.getVariableMap()); + this.nameDB_.populateVariables(workspace); + this.nameDB_.populateProcedures(workspace); + + const defvars = []; + // Add developer variables (not created or named by the user). + 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'); + } + + // 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.nameDB_.getName(variables[i].getId(), NameType.VARIABLE) + + ' = None'); + } + + this.definitions_['variables'] = defvars.join('\n'); + this.isInitialized = true; + } + + /** + * Prepend the generated code with import statements and variable definitions. + * @param {string} code Generated code. + * @return {string} Completed code. + */ + finish(code) { + // Convert the definitions dictionary into a list. + const imports = []; + const definitions = []; + for (let name in this.definitions_) { + const def = this.definitions_[name]; + if (def.match(/^(from\s+\S+\s+)?import\s+\S+/)) { + imports.push(def); + } else { + definitions.push(def); + } + } + // Call Blockly.CodeGenerator's finish. + code = super.finish(code); + this.isInitialized = false; + + 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; + } + + /** + * 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. + */ + scrubNakedValue(line) { + 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. + * @protected + */ + quote_(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) { + if (string.indexOf('"') === -1) { + quote = '"'; + } else { + string = string.replace(/'/g, '\\\''); + } + } + return quote + string + quote; + } + + /** + * Encode a string as a properly escaped multiline Python string, complete + * with quotes. + * @param {string} string Text to encode. + * @return {string} Python string. + * @protected + */ + multiline_quote_(string) { + const lines = string.split(/\n/g).map(this.quote_); + // Join with the following, plus a newline: + // + '\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 + */ + scrub_(block, code, opt_thisOnly) { + let commentCode = ''; + // Only collect comments for blocks that aren't inline. + if (!block.outputConnection || !block.outputConnection.targetConnection) { + // Collect comment for this block. + let comment = block.getCommentText(); + if (comment) { + comment = stringUtils.wrap(comment, this.COMMENT_WRAP - 3); + commentCode += this.prefixLines(comment + '\n', '# '); + } + // Collect comments for all value arguments. + // 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(); + if (childBlock) { + comment = this.allNestedComments(childBlock); + if (comment) { + commentCode += this.prefixLines(comment, '# '); + } + } + } + } + } + const nextBlock = block.nextConnection && block.nextConnection.targetBlock(); + const nextCode = opt_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} + */ + getAdjustedInt(block, atId, opt_delta, opt_negate) { + let delta = opt_delta || 0; + 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; + + if (stringUtils.isNumber(at)) { + // If the index is a naked number, adjust it right now. + at = parseInt(at, 10) + delta; + if (opt_negate) { + at = -at; + } + } else { + // If the index is dynamic, adjust it in code. + if (delta > 0) { + at = 'int(' + at + ' + ' + delta + ')'; + } else if (delta < 0) { + at = 'int(' + at + ' - ' + -delta + ')'; + } else { + at = 'int(' + at + ')'; + } + if (opt_negate) { + at = '-' + at; + } + } + return at; + } +} diff --git a/generators/python/text.js b/generators/python/text.js index 7f3621e95..84fef5515 100644 --- a/generators/python/text.js +++ b/generators/python/text.js @@ -13,16 +13,16 @@ goog.declareModuleId('Blockly.Python.texts'); import * as stringUtils from '../../core/utils/string.js'; import {NameType} from '../../core/names.js'; -import {pythonGenerator, Order} from '../python.js'; +import {Order} from './python_generator.js'; -pythonGenerator.forBlock['text'] = function(block, generator) { +export function text(block, generator) { // Text value. const code = generator.quote_(block.getFieldValue('TEXT')); return [code, Order.ATOMIC]; }; -pythonGenerator.forBlock['text_multiline'] = function(block, generator) { +export function text_multiline(block, generator) { // Text value. const code = generator.multiline_quote_(block.getFieldValue('TEXT')); const order = @@ -50,7 +50,7 @@ const forceString = function(value) { return ['str(' + value + ')', Order.FUNCTION_CALL]; }; -pythonGenerator.forBlock['text_join'] = function(block, generator) { +export function text_join(block, generator) { // 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_) { @@ -85,7 +85,7 @@ pythonGenerator.forBlock['text_join'] = function(block, generator) { } }; -pythonGenerator.forBlock['text_append'] = function(block, generator) { +export function text_append(block, generator) { // Append to a variable in place. const varName = generator.nameDB_.getName( @@ -94,20 +94,20 @@ pythonGenerator.forBlock['text_append'] = function(block, generator) { return varName + ' = str(' + varName + ') + ' + forceString(value)[0] + '\n'; }; -pythonGenerator.forBlock['text_length'] = function(block, generator) { +export function text_length(block, generator) { // Is the string null or array empty? const text = generator.valueToCode(block, 'VALUE', Order.NONE) || "''"; return ['len(' + text + ')', Order.FUNCTION_CALL]; }; -pythonGenerator.forBlock['text_isEmpty'] = function(block, generator) { +export function text_isEmpty(block, generator) { // 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]; }; -pythonGenerator.forBlock['text_indexOf'] = function(block, generator) { +export function text_indexOf(block, generator) { // Search the text for a substring. // Should we allow for non-case sensitive??? const operator = block.getFieldValue('END') === 'FIRST' ? 'find' : 'rfind'; @@ -122,7 +122,7 @@ pythonGenerator.forBlock['text_indexOf'] = function(block, generator) { return [code, Order.FUNCTION_CALL]; }; -pythonGenerator.forBlock['text_charAt'] = function(block, generator) { +export function text_charAt(block, generator) { // Get letter at index. // Note: Until January 2013 this block did not have the WHERE input. const where = block.getFieldValue('WHERE') || 'FROM_START'; @@ -163,7 +163,7 @@ def ${generator.FUNCTION_NAME_PLACEHOLDER_}(text): throw Error('Unhandled option (text_charAt).'); }; -pythonGenerator.forBlock['text_getSubstring'] = function(block, generator) { +export function text_getSubstring(block, generator) { // Get substring. const where1 = block.getFieldValue('WHERE1'); const where2 = block.getFieldValue('WHERE2'); @@ -213,7 +213,7 @@ pythonGenerator.forBlock['text_getSubstring'] = function(block, generator) { return [code, Order.MEMBER]; }; -pythonGenerator.forBlock['text_changeCase'] = function(block, generator) { +export function text_changeCase(block, generator) { // Change capitalization. const OPERATORS = { 'UPPERCASE': '.upper()', @@ -226,7 +226,7 @@ pythonGenerator.forBlock['text_changeCase'] = function(block, generator) { return [code, Order.FUNCTION_CALL]; }; -pythonGenerator.forBlock['text_trim'] = function(block, generator) { +export function text_trim(block, generator) { // Trim spaces. const OPERATORS = { 'LEFT': '.lstrip()', @@ -239,13 +239,13 @@ pythonGenerator.forBlock['text_trim'] = function(block, generator) { return [code, Order.FUNCTION_CALL]; }; -pythonGenerator.forBlock['text_print'] = function(block, generator) { +export function text_print(block, generator) { // Print statement. const msg = generator.valueToCode(block, 'TEXT', Order.NONE) || "''"; return 'print(' + msg + ')\n'; }; -pythonGenerator.forBlock['text_prompt_ext'] = function(block, generator) { +export function text_prompt_ext(block, generator) { // Prompt function. const functionName = generator.provideFunction_('text_prompt', ` def ${generator.FUNCTION_NAME_PLACEHOLDER_}(msg): @@ -270,17 +270,16 @@ def ${generator.FUNCTION_NAME_PLACEHOLDER_}(msg): return [code, Order.FUNCTION_CALL]; }; -pythonGenerator.forBlock['text_prompt'] = - pythonGenerator.forBlock['text_prompt_ext']; +export const text_prompt = text_prompt_ext; -pythonGenerator.forBlock['text_count'] = function(block, generator) { +export function text_count(block, generator) { 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]; }; -pythonGenerator.forBlock['text_replace'] = function(block, generator) { +export function text_replace(block, generator) { const text = generator.valueToCode(block, 'TEXT', Order.MEMBER) || "''"; const from = generator.valueToCode(block, 'FROM', Order.NONE) || "''"; const to = generator.valueToCode(block, 'TO', Order.NONE) || "''"; @@ -288,7 +287,7 @@ pythonGenerator.forBlock['text_replace'] = function(block, generator) { return [code, Order.MEMBER]; }; -pythonGenerator.forBlock['text_reverse'] = function(block, generator) { +export function text_reverse(block, generator) { 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 index 872bf3dbb..5228bb892 100644 --- a/generators/python/variables.js +++ b/generators/python/variables.js @@ -12,10 +12,10 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.Python.variables'); import {NameType} from '../../core/names.js'; -import {pythonGenerator, Order} from '../python.js'; +import {Order} from './python_generator.js'; -pythonGenerator.forBlock['variables_get'] = function(block, generator) { +export function variables_get(block, generator) { // Variable getter. const code = generator.nameDB_.getName( @@ -23,7 +23,7 @@ pythonGenerator.forBlock['variables_get'] = function(block, generator) { return [code, Order.ATOMIC]; }; -pythonGenerator.forBlock['variables_set'] = function(block, generator) { +export function variables_set(block, generator) { // Variable setter. const argument0 = generator.valueToCode(block, 'VALUE', Order.NONE) || '0'; diff --git a/generators/python/variables_dynamic.js b/generators/python/variables_dynamic.js index 1925fc5a9..5a19faf47 100644 --- a/generators/python/variables_dynamic.js +++ b/generators/python/variables_dynamic.js @@ -11,10 +11,9 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.Python.variablesDynamic'); -import {pythonGenerator} from '../python.js'; -import './variables.js'; - // generator is dynamically typed. -pythonGenerator.forBlock['variables_get_dynamic'] = pythonGenerator.forBlock['variables_get']; -pythonGenerator.forBlock['variables_set_dynamic'] = pythonGenerator.forBlock['variables_set']; +export { + variables_get as variables_get_dynamic, + variables_set as variables_set_dynamic, +} from './variables.js'; diff --git a/scripts/gulpfiles/build_tasks.js b/scripts/gulpfiles/build_tasks.js index 82404f299..678925105 100644 --- a/scripts/gulpfiles/build_tasks.js +++ b/scripts/gulpfiles/build_tasks.js @@ -110,35 +110,35 @@ const chunks = [ }, { name: 'javascript', - entry: path.join(TSC_OUTPUT_DIR, 'generators', 'javascript', 'all.js'), + entry: path.join(TSC_OUTPUT_DIR, 'generators', 'javascript.js'), exports: 'module$build$src$generators$javascript', scriptExport: 'javascript', scriptNamedExports: {'Blockly.Javascript': 'javascriptGenerator'}, }, { name: 'python', - entry: path.join(TSC_OUTPUT_DIR, 'generators', 'python', 'all.js'), + entry: path.join(TSC_OUTPUT_DIR, 'generators', 'python.js'), exports: 'module$build$src$generators$python', scriptExport: 'python', scriptNamedExports: {'Blockly.Python': 'pythonGenerator'}, }, { name: 'php', - entry: path.join(TSC_OUTPUT_DIR, 'generators', 'php', 'all.js'), + entry: path.join(TSC_OUTPUT_DIR, 'generators', 'php.js'), exports: 'module$build$src$generators$php', scriptExport: 'php', scriptNamedExports: {'Blockly.PHP': 'phpGenerator'}, }, { name: 'lua', - entry: path.join(TSC_OUTPUT_DIR, 'generators', 'lua', 'all.js'), + entry: path.join(TSC_OUTPUT_DIR, 'generators', 'lua.js'), exports: 'module$build$src$generators$lua', scriptExport: 'lua', scriptNameExports: {'Blockly.Lua': 'luaGenerator'}, }, { name: 'dart', - entry: path.join(TSC_OUTPUT_DIR, 'generators', 'dart', 'all.js'), + entry: path.join(TSC_OUTPUT_DIR, 'generators', 'dart.js'), exports: 'module$build$src$generators$dart', scriptExport: 'dart', scriptNameExports: {'Blockly.Dart': 'dartGenerator'}, diff --git a/scripts/migration/renamings.json5 b/scripts/migration/renamings.json5 index d01907939..532ab6a1f 100644 --- a/scripts/migration/renamings.json5 +++ b/scripts/migration/renamings.json5 @@ -1376,6 +1376,8 @@ oldName: 'Blockly.Python', newExport: 'pythonGenerator', newPath: 'Blockly.Python', + }, + { oldName: 'Blockly.ContextMenuRegistry', exports: { 'ContextMenuRegistry.ScopeType': { @@ -1510,5 +1512,61 @@ }, }, }, + // The following renamings serve two purposes: + // - Record that the langGenerator instances have moved to a + // different module (though this is not actually actioned by the + // renaming script). + // - Record a change of path, applicable only to code that imports + // generators via