From 6ea93f8cb8614855a373fad6138e516fc6f04589 Mon Sep 17 00:00:00 2001 From: TannerGabriel Date: Sat, 16 Aug 2025 12:20:37 +0200 Subject: [PATCH] Add static type support for blocks Signed-off-by: TannerGabriel --- .../src/blocks/controls.js | 11 +- .../src/blocks/lexical-variables.js | 175 ++++++++++++---- .../src/blocks/procedures.js | 198 ++++++++++++++---- .../src/blocks/variable-get-set.js | 36 +++- block-lexical-variables/src/core.js | 4 +- .../src/fields/field_lexical_variable.js | 97 +++++++-- .../src/fields/field_parameter_flydown.js | 11 +- .../src/generators/procedures.js | 5 + block-lexical-variables/src/mixins.js | 3 +- block-lexical-variables/src/msg.js | 2 + block-lexical-variables/src/shared.js | 3 + block-lexical-variables/test/index.js | 28 ++- 12 files changed, 471 insertions(+), 102 deletions(-) diff --git a/block-lexical-variables/src/blocks/controls.js b/block-lexical-variables/src/blocks/controls.js index edbf7c6..0535164 100644 --- a/block-lexical-variables/src/blocks/controls.js +++ b/block-lexical-variables/src/blocks/controls.js @@ -61,7 +61,7 @@ Blockly.Blocks['controls_forRange'] = { .appendField(Blockly.Msg.LANG_CONTROLS_FORRANGE_INPUT_ITEM) .appendField(new FieldParameterFlydown( Blockly.Msg.LANG_CONTROLS_FORRANGE_INPUT_VAR, true, - FieldFlydown.DISPLAY_BELOW), 'VAR') + FieldFlydown.DISPLAY_BELOW, undefined, Blockly?.types_?.loopType), 'VAR') .appendField(Blockly.Msg.LANG_CONTROLS_FORRANGE_INPUT_START) .setAlign(Blockly.inputs.Align.RIGHT); this.appendValueInput('TO') @@ -88,6 +88,9 @@ Blockly.Blocks['controls_forRange'] = { getScopedInputName: function () { return 'DO'; }, + getVariableType: function () { + return Blockly?.types_?.loopType + } }; // Alias controls_for to controls_forRange We need this because @@ -114,7 +117,7 @@ Blockly.Blocks['controls_forEach'] = { .appendField(Blockly.Msg.LANG_CONTROLS_FOREACH_INPUT_ITEM) .appendField(new FieldParameterFlydown( Blockly.Msg.LANG_CONTROLS_FOREACH_INPUT_VAR, - true, FieldFlydown.DISPLAY_BELOW), 'VAR') + true, FieldFlydown.DISPLAY_BELOW, undefined, Blockly?.types_?.loopType), 'VAR') .appendField(Blockly.Msg.LANG_CONTROLS_FOREACH_INPUT_INLIST) .setAlign(Blockly.inputs.Align.RIGHT); this.appendStatementInput('DO') @@ -130,6 +133,9 @@ Blockly.Blocks['controls_forEach'] = { getScopedInputName: function () { return 'DO'; }, + getVariableType: function () { + return Blockly?.types_?.loopType + } }; Blockly.Blocks['controls_do_then_return'] = { @@ -148,4 +154,3 @@ Blockly.Blocks['controls_do_then_return'] = { this.setTooltip(Blockly.Msg.LANG_CONTROLS_DO_THEN_RETURN_TOOLTIP); }, }; - diff --git a/block-lexical-variables/src/blocks/lexical-variables.js b/block-lexical-variables/src/blocks/lexical-variables.js index f308ba8..e6e08b7 100644 --- a/block-lexical-variables/src/blocks/lexical-variables.js +++ b/block-lexical-variables/src/blocks/lexical-variables.js @@ -104,6 +104,7 @@ import * as Shared from '../shared.js'; import {NameSet} from "../nameSet.js"; import {Substitution} from '../substitution.js' import {lexicalVariableScopeMixin} from "../mixins.js"; +import {dataTypesEnabled} from "../shared.js"; delete Blockly.Blocks['global_declaration']; /** @@ -115,13 +116,30 @@ Blockly.Blocks['global_declaration'] = { helpUrl: Blockly.Msg.LANG_VARIABLES_GLOBAL_DECLARATION_HELPURL, init: function() { this.setStyle('variable_blocks'); - this.appendValueInput('VALUE') - .appendField(Blockly.Msg.LANG_VARIABLES_GLOBAL_DECLARATION_TITLE_INIT) - .appendField(new FieldGlobalFlydown( - Blockly.Msg.LANG_VARIABLES_GLOBAL_DECLARATION_NAME, - FieldFlydown.DISPLAY_BELOW), 'NAME') - .appendField(Blockly.Msg.LANG_VARIABLES_GLOBAL_DECLARATION_TO); + this.fieldGlobalFlydown_ = new FieldGlobalFlydown( + Blockly.Msg.LANG_VARIABLES_GLOBAL_DECLARATION_NAME, + FieldFlydown.DISPLAY_BELOW) + const valueField = this.appendValueInput('VALUE') + .appendField(Blockly.Msg.LANG_VARIABLES_GLOBAL_DECLARATION_TITLE_INIT); + if (dataTypesEnabled()) { + valueField.appendField(new Blockly.FieldDropdown(Blockly.types_.dataTypes), 'TYPE'); + } + valueField.appendField(this.fieldGlobalFlydown_, 'NAME') + .appendField(Blockly.Msg.LANG_VARIABLES_GLOBAL_DECLARATION_TO); this.setTooltip(Blockly.Msg.LANG_VARIABLES_GLOBAL_DECLARATION_TOOLTIP); + + if (dataTypesEnabled()) { + this.setOnChange(function (e) { + if (!this.workspace || this.workspace.isFlyout || this.isInFlyout) return; + + const type = this.getVariableType(); + this.getInput('VALUE').setCheck(type ? [type] : null); + + if (e.type === 'change' && e.name === 'TYPE') { + LexicalVariable.changeGlobalVariableType(this.getFieldValue('NAME'), type, type) + } + }) + } }, getDeclaredVars: function() { const field = this.getField('NAME'); @@ -130,6 +148,9 @@ Blockly.Blocks['global_declaration'] = { getGlobalNames: function() { return this.getDeclaredVars(); }, + getVariableType: function() { + return this.getFieldValue('TYPE'); + }, renameVar: function(oldName, newName) { if (Blockly.Names.equals(oldName, this.getFieldValue('NAME'))) { this.setFieldValue(newName, 'NAME'); @@ -146,8 +167,13 @@ Blockly.Blocks['simple_local_declaration_statement'] = { this.setStyle('variable_blocks'); const declInput = this.appendValueInput('DECL'); declInput.appendField( - Blockly.Msg.LANG_VARIABLES_LOCAL_DECLARATION_TITLE_INIT) - .appendField(new FieldParameterFlydown(Blockly.Msg.LANG_VARIABLES_LOCAL_DECLARATION_DEFAULT_NAME, true), 'VAR') + Blockly.Msg.LANG_VARIABLES_LOCAL_DECLARATION_TITLE_INIT); + + if (dataTypesEnabled()) { + declInput.appendField(new Blockly.FieldDropdown(Blockly.types_.dataTypes), 'TYPE') + } + + declInput.appendField(new FieldParameterFlydown(Blockly.Msg.LANG_VARIABLES_LOCAL_DECLARATION_DEFAULT_NAME, true), 'VAR') .appendField(Blockly.Msg.LANG_VARIABLES_LOCAL_DECLARATION_INPUT_TO) .setAlign(Blockly.inputs.Align.RIGHT); this.appendStatementInput('DO') @@ -156,10 +182,31 @@ Blockly.Blocks['simple_local_declaration_statement'] = { this.setNextStatement(true); this.setTooltip(Blockly.Msg.LANG_VARIABLES_LOCAL_DECLARATION_TOOLTIP); this.mixin(lexicalVariableScopeMixin); + + if (dataTypesEnabled()) { + this.setOnChange(function (e) { + if (!this.workspace || this.workspace.isFlyout || this.isInFlyout) return; + + const newType = this.getVariableType(); + + if (e.type === 'change' && e.name === 'TYPE') { + LexicalVariable.changeVariableType(this, this.getFieldValue('VAR'), newType, newType) + } + + const varField = this.getField('VAR'); + this.getInput('DECL').setCheck(newType ? [newType] : null); + + if (!varField) return; + varField.setVariableType(newType); + }); + } }, getDeclaredVarFieldNames: function () { return ['VAR']; }, + getVariableType: function() { + return this.getFieldValue('TYPE'); + }, getScopedInputName: function () { return 'DO'; }, @@ -223,9 +270,10 @@ Blockly.Blocks['local_declaration_statement'] = { withLexicalVarsAndPrefix: function(child, proc) { if (this.getInputTargetBlock(this.bodyInputName) == child) { const localNames = this.declaredNames(); + const paramTypes = this.getVariableTypes(); // not arguments_ instance var for (let i = 0; i < localNames.length; i++) { - proc(localNames[i], this.lexicalVarPrefix); + proc(localNames[i], this.lexicalVarPrefix, '', paramTypes[i]); } } }, @@ -235,6 +283,7 @@ Blockly.Blocks['local_declaration_statement'] = { this.setStyle('variable_blocks'); this.localNames_ = [Blockly.Msg.LANG_VARIABLES_LOCAL_DECLARATION_DEFAULT_NAME]; + if (dataTypesEnabled()) this.localTypes_ = [Blockly.types_.defaultType]; const declInput = this.appendValueInput('DECL0'); declInput.appendField( Blockly.Msg.LANG_VARIABLES_LOCAL_DECLARATION_TITLE_INIT) @@ -255,6 +304,7 @@ Blockly.Blocks['local_declaration_statement'] = { for (let i = 0; i < this.localNames_.length; i++) { const parameter = Blockly.utils.xml.createElement('localname'); parameter.setAttribute('name', this.localNames_[i]); + if (dataTypesEnabled()) parameter.setAttribute('type', this.localTypes_[i]); container.appendChild(parameter); } return container; @@ -266,16 +316,19 @@ Blockly.Blocks['local_declaration_statement'] = { if (children.length > 0) { // Ensure xml element is nonempty // Else we'll overwrite initial list with "name" for new block this.localNames_ = []; - for (let i = 0, childNode; childNode = children[i]; i++) { + this.localTypes_ = []; + + for (let i = 0, childNode; childNode = children[i]; i++) { if (childNode.nodeName.toLowerCase() == 'localname') { this.localNames_.push(childNode.getAttribute('name')); + if (dataTypesEnabled()) this.localTypes_.push(childNode.getAttribute('type') || Blockly.types_.defaultType); } } } - this.updateDeclarationInputs_(this.localNames_); // add declarations; inits + this.updateDeclarationInputs_(this.localNames_, this.localTypes_); // add declarations; inits // are undefined }, - updateDeclarationInputs_: function(names, inits) { + updateDeclarationInputs_: function(names, types, inits) { // Modify this block to replace existing initializers by new declaration // inputs created from names and inits. If inits is undefined, treat all // initial expressions as undefined. Keep existing body at end of input @@ -309,6 +362,9 @@ Blockly.Blocks['local_declaration_statement'] = { // mutator this.inputList = []; this.localNames_ = names; + if (types) { + this.localTypes_ = types; + } for (let i = 0; i < names.length; i++) { const declInput = this.appendValueInput('DECL' + i); @@ -325,6 +381,12 @@ Blockly.Blocks['local_declaration_statement'] = { .appendField(this.parameterFlydown(i), 'VAR' + i) .appendField(Blockly.Msg.LANG_VARIABLES_LOCAL_DECLARATION_INPUT_TO) .setAlign(Blockly.inputs.Align.RIGHT); + + if (dataTypesEnabled()) { + const t = this.localTypes_[i]; + declInput.setCheck(t ? [t] : null); + } + if (inits && inits[i]) { // If there is an initializer, connect it declInput.connection.connect(inits[i]); } @@ -345,6 +407,10 @@ Blockly.Blocks['local_declaration_statement'] = { // Return a new local variable parameter flydown parameterFlydown: function(paramIndex) { const initialParamName = this.localNames_[paramIndex]; + let initialParamType = ''; + if (initialParamType.length > paramIndex) { + initialParamType = this.localTypes_[paramIndex]; + } const localDecl = this; // Here, "this" is the local decl block. Name it to // use in function below const localParameterChangeHandler = function(newParamName) { @@ -381,26 +447,23 @@ Blockly.Blocks['local_declaration_statement'] = { return new FieldParameterFlydown(initialParamName, true, // name is editable FieldFlydown.DISPLAY_RIGHT, - localParameterChangeHandler); + localParameterChangeHandler, initialParamType); }, decompose: function(workspace) { - // Create "mutator" editor populated with name blocks with local variable - // names - const containerBlock = workspace.newBlock('local_mutatorcontainer'); - containerBlock.initSvg(); - containerBlock.setDefBlock(this); - let connection = containerBlock.getInput('STACK').connection; - for (let i = 0; i < this.localNames_.length; i++) { - const localName = this.getFieldValue('VAR' + i); - const nameBlock = workspace.newBlock('local_mutatorarg'); - nameBlock.initSvg(); - nameBlock.setFieldValue(localName, 'NAME'); - // Store the old location. - nameBlock.oldLocation = i; - connection.connect(nameBlock.previousConnection); - connection = nameBlock.nextConnection; - } - return containerBlock; + const container = workspace.newBlock('local_mutatorcontainer'); + container.initSvg(); + container.setDefBlock(this); + let conn = container.getInput('STACK').connection; + for (let i = 0; i < this.localNames_.length; i++) { + const arg = workspace.newBlock('local_mutatorarg'); + arg.initSvg(); + arg.setFieldValue(this.localNames_[i], 'NAME'); + arg.setFieldValue(this.localTypes_[i], 'TYPE'); + arg.oldLocation = i; + conn.connect(arg.previousConnection); + conn = arg.nextConnection; + } + return container; }, compose: function(containerBlock) { // [lyn, 10/27/13] Modified this so that doesn't rebuild block if names @@ -408,10 +471,12 @@ Blockly.Blocks['local_declaration_statement'] = { // localParameterChangeHandler within parameterFlydown. const newLocalNames = []; + const newLocalTypes = []; const initializers = []; let mutatorarg = containerBlock.getInputTargetBlock('STACK'); while (mutatorarg) { newLocalNames.push(mutatorarg.getFieldValue('NAME')); + newLocalTypes.push(mutatorarg.getFieldValue('TYPE')); initializers.push(mutatorarg.valueConnection_); // pushes undefined if // doesn't exist mutatorarg = @@ -420,12 +485,12 @@ Blockly.Blocks['local_declaration_statement'] = { // Reconstruct inputs only if local list has changed if (!LexicalVariable.stringListsEqual(this.localNames_, - newLocalNames)) { + newLocalNames) || !LexicalVariable.stringListsEqual(this.localTypes_, newLocalTypes)) { // Switch off rendering while the block is rebuilt. // var savedRendered = this.rendered; // this.rendered = false; - this.updateDeclarationInputs_(newLocalNames, initializers); + this.updateDeclarationInputs_(newLocalNames, newLocalTypes, initializers); // Restore rendering and show the changes. // this.rendered = savedRendered; @@ -494,7 +559,7 @@ Blockly.Blocks['local_declaration_statement'] = { if (!LexicalVariable.stringListsEqual(renamedLocalNames, localNames)) { const initializerConnections = this.initializerConnections(); - this.updateDeclarationInputs_(renamedLocalNames, initializerConnections); + this.updateDeclarationInputs_(renamedLocalNames, this.localTypes_, initializerConnections); // Update the mutator's variables if the mutator is open. if (this.mutator && this.mutator.isVisible()) { const blocks = this.mutator.workspace_.getAllBlocks(); @@ -583,6 +648,19 @@ Blockly.Blocks['local_declaration_statement'] = { } return result; }, + getVariableTypes: function() { + // When the mutator is open, reflect the state inside it (live types). + if (this.mutator && this.mutator.getSize() && this.mutator.rootBlock) { + const types = []; + let arg = this.mutator.rootBlock.getInputTargetBlock('STACK'); + while (arg) { + types.push(arg.getFieldValue('TYPE') || 'any'); + arg = arg.nextConnection && arg.nextConnection.targetBlock(); + } + return types; + } + return this.localTypes_; + }, }; @@ -657,6 +735,7 @@ Blockly.Blocks['local_declaration_expression'] = { renameBound: Blockly.Blocks.local_declaration_statement.renameBound, renameFree: Blockly.Blocks.local_declaration_statement.renameFree, freeVariables: Blockly.Blocks.local_declaration_statement.freeVariables, + getVariableTypes: Blockly.Blocks.local_declaration_statement.getVariableTypes, }; Blockly.Blocks['local_mutatorcontainer'] = { @@ -704,18 +783,35 @@ Blockly.Blocks['local_mutatorarg'] = { // Let the theme determine the color. // this.setColour(Blockly.VARIABLE_CATEGORY_HUE); this.setStyle('variable_blocks'); - this.appendDummyInput() - .appendField(Blockly.Msg.LANG_VARIABLES_LOCAL_MUTATOR_ARG_TITLE_NAME) - .appendField(new Blockly.FieldTextInput( - Blockly.Msg.LANG_VARIABLES_LOCAL_MUTATOR_ARG_DEFAULT_VARIABLE, - LexicalVariable.renameParam), - 'NAME'); + + const editor = new Blockly.FieldTextInput( + Blockly.Msg.LANG_VARIABLES_LOCAL_MUTATOR_ARG_DEFAULT_VARIABLE, + LexicalVariable.renameParam) + + const input = this.appendDummyInput() + .appendField('type'); + + if (dataTypesEnabled()) { + input.appendField(new Blockly.FieldDropdown(Blockly.types_.dataTypes), 'TYPE') + } + + input.appendField(Blockly.Msg.LANG_VARIABLES_LOCAL_MUTATOR_ARG_TITLE_NAME) + .appendField(editor, 'NAME'); this.setPreviousStatement(true); this.setNextStatement(true); this.setTooltip(''); this.contextMenu = false; this.lexicalVarPrefix = Shared.localNamePrefix; this.mustNotRenameCapturables = true; + + if (dataTypesEnabled()) { + this.setOnChange(function (e) { + const newType = this.getFieldValue('TYPE'); + if (e.type === 'change' && e.name === 'TYPE') { + LexicalVariable.changeVariableType(this, this.getFieldValue('NAME'), newType, newType) + } + }) + } }, getContainerBlock: function() { let parent = this.getParent(); @@ -741,7 +837,6 @@ Blockly.Blocks['local_mutatorarg'] = { const container = this.getContainerBlock(); return (container && container.declaredNames()) || []; }, - // [lyn, 11/24/12] Check for situation in which mutator arg has been removed // from stack, onchange: function() { diff --git a/block-lexical-variables/src/blocks/procedures.js b/block-lexical-variables/src/blocks/procedures.js index 7c87e63..2b1ba8c 100644 --- a/block-lexical-variables/src/blocks/procedures.js +++ b/block-lexical-variables/src/blocks/procedures.js @@ -92,6 +92,7 @@ import * as Utilities from '../utilities.js'; import * as Shared from '../shared.js'; import {Substitution} from '../substitution.js' import '../msg.js'; +import {dataTypesEnabled} from "../shared.js"; Blockly.Blocks['procedures_defnoreturn'] = { // Define a procedure with no return value. @@ -113,6 +114,7 @@ Blockly.Blocks['procedures_defnoreturn'] = { // List of declared local variable names; has one // ("name") initially this.arguments_ = []; + this.argumentTypes_ = []; // Other methods guarantee the invariant that this variable contains // the list of names declared in the local declaration block. this.warnings = [{name: 'checkEmptySockets', sockets: ['STACK']}]; @@ -125,9 +127,10 @@ Blockly.Blocks['procedures_defnoreturn'] = { }, withLexicalVarsAndPrefix: function(_, proc) { const params = this.declaredNames(); + const paramTypes = this.getParameterTypes(); // not arguments_ instance var for (let i = 0; i < params.length; i++) { - proc(params[i], this.lexicalVarPrefix); + proc(params[i], this.lexicalVarPrefix, '', paramTypes[i]); } }, onchange: function() { @@ -135,15 +138,18 @@ Blockly.Blocks['procedures_defnoreturn'] = { // with paramFlydown fields this.arguments_ = this.declaredNames(); }, - updateParams_: function(opt_params) { + updateParams_: function(opt_names, opt_types) { // make rendered block reflect the parameter names currently in // this.arguments_ - // [lyn, 11/17/13] Added optional opt_params argument: + // [lyn, 11/17/13] Added optional opt_names argument: // If its falsey (null or undefined), use the existing this.arguments_ - // list Otherwise, replace this.arguments_ by opt_params In either case, + // list Otherwise, replace this.arguments_ by opt_names In either case, // make rendered block reflect the parameter names in this.arguments_ - if (opt_params) { - this.arguments_ = opt_params; + if (opt_names) { + this.arguments_ = opt_names; + } + if (opt_types) { + this.argumentTypes_ = opt_types; } // Check for duplicated arguments. // [lyn 10/10/13] Note that in blocks edited within AI2, duplicate @@ -268,11 +274,11 @@ Blockly.Blocks['procedures_defnoreturn'] = { // Return a new procedure parameter flydown parameterFlydown: function(paramIndex) { const initialParamName = this.arguments_[paramIndex]; + const initialParamType = this.argumentTypes_[paramIndex]; // Here, "this" is the proc decl block. Name it to // use in function below const procDecl = this; const procedureParameterChangeHandler = function(newParamName) { - // console.log("enter procedureParameterChangeHandler"); // Extra work that needs to be done when procedure param name is changed, @@ -349,7 +355,7 @@ Blockly.Blocks['procedures_defnoreturn'] = { // [lyn, 10/27/13] flydown location depends on parameter orientation this.horizontalParameters ? FieldFlydown.DISPLAY_BELOW : FieldFlydown.DISPLAY_RIGHT, - procedureParameterChangeHandler); + procedureParameterChangeHandler, initialParamType); }, setParameterOrientation: function(isHorizontal) { const params = this.getParameters(); @@ -357,9 +363,10 @@ Blockly.Blocks['procedures_defnoreturn'] = { this.horizontalParameters = isHorizontal; this.updateParams_(); if (Blockly.Events.isEnabled()) { - Blockly.Events.fire(new Blockly.Events.BlockChange(this, 'parameter_orientation', null, - !this.horizontalParameters, - this.horizontalParameters)); + // Trigger a Blockly UI change event + Blockly.Events.fire(new Blockly.Events.Ui(this, 'parameter_orientation', + (!this.horizontalParameters).toString(), + this.horizontalParameters.toString())); } } }, @@ -374,21 +381,26 @@ Blockly.Blocks['procedures_defnoreturn'] = { for (let x = 0; x < this.arguments_.length; x++) { const parameter = Blockly.utils.xml.createElement('arg'); parameter.setAttribute('name', this.arguments_[x]); + parameter.setAttribute('type', this.argumentTypes_[x]); container.appendChild(parameter); } return container; }, domToMutation: function(xmlElement) { - const params = []; + const names = []; + const types = []; const children = Utilities.getChildren(xmlElement); for (let x = 0, childNode; childNode = children[x]; x++) { if (childNode.nodeName.toLowerCase() == 'arg') { - params.push(childNode.getAttribute('name')); + names.push(childNode.getAttribute('name')); + if (dataTypesEnabled()) { + types.push(childNode.getAttribute('type') || Blockly?.types_?.defaultType); + } } } this.horizontalParameters = xmlElement.getAttribute('vertical_parameters') !== 'true'; - this.updateParams_(params); + this.updateParams_(names, types); }, decompose: function(workspace) { const containerBlock = workspace.newBlock('procedures_mutatorcontainer'); @@ -403,6 +415,7 @@ Blockly.Blocks['procedures_defnoreturn'] = { this.paramIds_.push(paramBlock.id); // [lyn, 10/26/13] Added paramBlock.initSvg(); paramBlock.setFieldValue(this.arguments_[x], 'NAME'); + paramBlock.setFieldValue(this.argumentTypes_[x], 'TYPE'); // Store the old location. paramBlock.oldLocation = x; connection.connect(paramBlock.previousConnection); @@ -414,11 +427,13 @@ Blockly.Blocks['procedures_defnoreturn'] = { return containerBlock; }, compose: function(containerBlock) { - const params = []; + const names = [] + const types = [] this.paramIds_ = []; let paramBlock = containerBlock.getInputTargetBlock('STACK'); while (paramBlock) { - params.push(paramBlock.getFieldValue('NAME')); + names.push(paramBlock.getFieldValue('NAME')); + types.push(paramBlock.getVariableType()); this.paramIds_.push(paramBlock.id); paramBlock = paramBlock.nextConnection && paramBlock.nextConnection.targetBlock(); @@ -431,9 +446,10 @@ Blockly.Blocks['procedures_defnoreturn'] = { // ); // [lyn, 11/24/12] Note: update params updates param list in proc // declaration, but renameParam updates procedure body appropriately. - if (!LexicalVariable.stringListsEqual(params, this.arguments_)) { + if (!LexicalVariable.stringListsEqual(names, this.arguments_) || + !LexicalVariable.stringListsEqual(types, this.argumentTypes_)) { // Only need updates if param list has changed - this.updateParams_(params); + this.updateParams_(names, types); Blockly.Procedures.mutateCallers(this); } // console.log("exit procedures_defnoreturn compose()"); @@ -551,6 +567,9 @@ Blockly.Blocks['procedures_defnoreturn'] = { getParameters: function() { return this.arguments_; }, + getParameterTypes: function() { + return this.argumentTypes_; + } }; // [lyn, 01/15/2013] Edited to remove STACK (no longer necessary with @@ -574,9 +593,22 @@ Blockly.Blocks['procedures_defreturn'] = { Blockly.Msg['LANG_PROCEDURES_DEFRETURN_PROCEDURE'], this); this.createHeader(legalName); this.horizontalParameters = true; // horizontal by default - this.appendInputFromRegistry('indented_input', 'RETURN') - .setAlign(Blockly.inputs.Align.RIGHT) - .appendField(Blockly.Msg['LANG_PROCEDURES_DEFRETURN_RETURN']); + const returnInput = this.appendInputFromRegistry('indented_input', 'RETURN') + .setAlign(Blockly.inputs.Align.RIGHT); + if (dataTypesEnabled()) { + returnInput.appendField(new Blockly.FieldDropdown(Blockly.types_.dataTypes), 'RETURN_TYPE') + } + returnInput.appendField(Blockly.Msg['LANG_PROCEDURES_DEFRETURN_RETURN']); + returnInput.setCheck(this.getReturnType() ? [this.getReturnType()] : null); + + const retTypeField = this.getField('RETURN_TYPE'); + if (retTypeField) { + retTypeField.setValidator((newType) => { + const inp = this.getInput('RETURN'); + if (inp) inp.setCheck(newType ? [newType] : null); + return newType; + }); + } this.setMutator(new Blockly.icons.MutatorIcon(['procedures_mutatorarg'], this)); this.setTooltip(Blockly.Msg['LANG_PROCEDURES_DEFRETURN_TOOLTIP']); this.arguments_ = []; @@ -612,6 +644,10 @@ Blockly.Blocks['procedures_defreturn'] = { blocksInScope: Blockly.Blocks.procedures_defnoreturn.blocksInScope, customContextMenu: Blockly.Blocks.procedures_defnoreturn.customContextMenu, getParameters: Blockly.Blocks.procedures_defnoreturn.getParameters, + getParameterTypes: Blockly.Blocks.procedures_defnoreturn.getParameterTypes, + getReturnType: function() { + return this.getFieldValue('RETURN_TYPE'); + } }; Blockly.Blocks['procedures_mutatorcontainer'] = { @@ -654,33 +690,30 @@ Blockly.Blocks['procedures_mutatorcontainer'] = { Blockly.Blocks['procedures_mutatorarg'] = { // Procedure argument (for mutator dialog). init: function() { - // var mutatorarg = this; - // var mutatorargChangeHandler = function(newName) { - // var proc = mutatorarg.getProcBlock(); - // var procArguments = proc ? proc.arguments_ : []; - // console.log("mutatorargChangeHandler: newName = " + newName - // + " and proc argumnets = [" + procArguments.join(',') + - // "]"); return Blockly.LexicalVariable.renameParam.call(this,newName); } // Let the theme determine the color. // this.setColour(Blockly.PROCEDURE_CATEGORY_HUE); this.setStyle('procedure_blocks'); const editor = new Blockly.FieldTextInput('x', - LexicalVariable.renameParam); + LexicalVariable.renameParam); // 2017 Blockly's text input change breaks our renaming behavior. // The following is a version we've defined. - editor.onHtmlInputChange_ = function(e) { + editor.onHtmlInputChange_ = function (e) { const oldValue = this.getValue(); FieldFlydown.prototype.onHtmlInputChange_.call(this, e); const newValue = this.getValue(); if (newValue && oldValue !== newValue && Blockly.Events.isEnabled()) { Blockly.Events.fire( - new Blockly.Events.BlockChange(this.sourceBlock_, 'field', this.name, - oldValue, newValue)); + new Blockly.Events.BlockChange(this.sourceBlock_, 'field', this.name, + oldValue, newValue)); } }; - this.appendDummyInput() - .appendField(Blockly.Msg['LANG_PROCEDURES_MUTATORARG_TITLE']) - .appendField(editor, 'NAME'); + const input = this.appendDummyInput() + if (dataTypesEnabled()) { + input.appendField('type:') + .appendField(new Blockly.FieldDropdown(Blockly.types_.dataTypes), 'TYPE') + } + input.appendField(Blockly.Msg['LANG_PROCEDURES_MUTATORARG_TITLE']) + .appendField(editor, 'NAME'); this.setPreviousStatement(true); this.setNextStatement(true); this.setTooltip(Blockly.Msg['LANG_PROCEDURES_MUTATORARG_TOOLTIP']); @@ -688,6 +721,9 @@ Blockly.Blocks['procedures_mutatorarg'] = { this.lexicalVarPrefix = Shared.procedureParameterPrefix; this.mustNotRenameCapturables = true; }, + getVariableType: function() { + return this.getFieldValue('TYPE'); + }, // [lyn, 11/24/12] Return the container this mutator arg is in, or null if // it's not in one. Dynamically calculate this by walking up chain, because // mutator arg might or might not be in container stack. @@ -794,6 +830,7 @@ Blockly.Blocks['procedures_callnoreturn'] = { this.setNextStatement(true); this.setTooltip(Blockly.Msg['LANG_PROCEDURES_CALLNORETURN_TOOLTIP']); this.arguments_ = []; + this.argumentTypes_ = []; this.quarkConnections_ = null; this.quarkArguments_ = null; this.errors = [ @@ -889,6 +926,21 @@ Blockly.Blocks['procedures_callnoreturn'] = { this.quarkArguments_ = []; } } + + if (dataTypesEnabled()) { + let argTypes; + const defBlock = Blockly.Procedures.getDefinition( + this.getFieldValue('PROCNAME'), this.workspace); + if (defBlock && defBlock.argumentTypes_) { + argTypes = defBlock.argumentTypes_.slice(); + } else if (this.argumentTypes_ && this.argumentTypes_.length === paramNames.length) { + argTypes = this.argumentTypes_.slice(); + } else { + argTypes = paramNames.map(_ => Blockly?.types_?.defaultType); + } + this.argumentTypes_ = argTypes; + } + // Switch off rendering while the block is rebuilt. const savedRendered = this.rendered; this.rendered = false; @@ -909,6 +961,12 @@ Blockly.Blocks['procedures_callnoreturn'] = { input = this.appendValueInput('ARG' + x) .setAlign(Blockly.inputs.Align.RIGHT) .appendField(this.arguments_[x]); + + if (dataTypesEnabled()) { + const t = this.argumentTypes_[x]; + input.setCheck(t ? [t] : null); + } + if (this.quarkArguments_) { // Reconnect any child blocks. const quarkName = this.quarkArguments_[x]; @@ -952,6 +1010,9 @@ Blockly.Blocks['procedures_callnoreturn'] = { const parameter = Blockly.utils.xml.createElement('arg'); parameter.setAttribute('name', this.getInput('ARG' + x).fieldRow[0].getText()); + if (dataTypesEnabled()) { + parameter.setAttribute('type', this.argumentTypes_[x] || Blockly?.types_?.defaultType); + } container.appendChild(parameter); } return container; @@ -963,10 +1024,14 @@ Blockly.Blocks['procedures_callnoreturn'] = { // [lyn, 10/27/13] Significantly cleaned up this code. Always take arg // names from xmlElement. Do not attempt to find definition. this.arguments_ = []; + this.argumentTypes_ = []; const children = Utilities.getChildren(xmlElement); for (let x = 0, childNode; childNode = children[x]; x++) { if (childNode.nodeName.toLowerCase() == 'arg') { this.arguments_.push(childNode.getAttribute('name')); + if (dataTypesEnabled()) { + this.argumentTypes_.push(childNode.getAttribute('type') || Blockly?.types_?.defaultType); + } } } this.setProcedureParameters(this.arguments_, null, true); @@ -1005,6 +1070,9 @@ Blockly.Blocks['procedures_callnoreturn'] = { } this.setFieldValue('none', 'PROCNAME'); }, + getParameterTypes: function() { + return this.argumentTypes_; + } }; @@ -1059,4 +1127,64 @@ Blockly.Blocks['procedures_callreturn'] = { Blockly.Blocks.procedures_callnoreturn.procCustomContextMenu, removeProcedureValue: Blockly.Blocks.procedures_callnoreturn.removeProcedureValue, + getParameterTypes: Blockly.Blocks.procedures_callnoreturn.getParameterTypes }; + +Blockly.Blocks['procedures_early_return'] = { + category: 'Procedures', + helpUrl: '', + init: function() { + this.setStyle('procedure_blocks'); + this.appendDummyInput() + .appendField(Blockly.Msg['LANG_PROCEDURES_EARLY_RETURN_TOOLTIP']); + this.appendValueInput('RETURN_VALUE'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip(Blockly.Msg['LANG_PROCEDURES_EARLY_RETURN_TOOLTIP']); + this.errors = [ + {func: ErrorCheckers.checkIsInDefinition}, + ]; + + this.setOnChange(this._checkPlacement); + }, + _checkPlacement: function(e) { + if (!this.workspace || this.workspace.isFlyout || this.isInFlyout) { + return; + } + + let block = this.getSurroundParent(); + let legal = false; + while (block) { + if (block.type === 'procedures_defreturn' || + block.type === 'procedures_defnoreturn') { + legal = true; + break; + } + block = block.getSurroundParent(); + } + + this.setEnabled(legal); + + if (!legal) { + this.setWarningText('Early return only allowed inside a function definition'); + } else { + this.setWarningText(null); + } + }, + getReturnType: function() { + let parent = this.getSurroundParent(); + + // Find the parent procedure block to determine the return type + while (parent) { + if (parent.type === 'procedures_defreturn') { + return parent.getReturnType(); + } + if (parent.type === 'procedures_defnoreturn') { + return 'void'; + } + parent = parent.getSurroundParent(); + } + + return ''; + } +} \ No newline at end of file diff --git a/block-lexical-variables/src/blocks/variable-get-set.js b/block-lexical-variables/src/blocks/variable-get-set.js index 5c54bea..dded78f 100644 --- a/block-lexical-variables/src/blocks/variable-get-set.js +++ b/block-lexical-variables/src/blocks/variable-get-set.js @@ -10,6 +10,7 @@ import { } from '../fields/field_lexical_variable.js'; import * as Shared from '../shared.js'; import {NameSet} from "../nameSet.js"; +import {dataTypesEnabled} from "../shared.js"; /** * Prototype bindings for a variable getter block. @@ -24,7 +25,12 @@ Blockly.Blocks['lexical_variable_get'] = { this.appendDummyInput() .appendField(Blockly.Msg.LANG_VARIABLES_GET_TITLE_GET) .appendField(this.fieldVar_, 'VAR'); - this.setOutput(true, null); + const type = this.getVariableType(); + if (dataTypesEnabled()) { + this.setOutput(true, type ? [type] : null); + } else { + this.setOutput(true, null); + } this.setTooltip(Blockly.Msg.LANG_VARIABLES_GET_TOOLTIP); this.errors = [ {func: ErrorCheckers.checkIsInDefinition}, @@ -35,6 +41,10 @@ Blockly.Blocks['lexical_variable_get'] = { ]; this.setOnChange(function(changeEvent) { this.workspace.getWarningHandler().checkErrors(this); + if (dataTypesEnabled()) { + const type = this.getVariableType(); + this.setOutput(true, type ? [type] : null); + } }); }, referenceResults: function(name, prefix, env) { @@ -85,6 +95,9 @@ Blockly.Blocks['lexical_variable_get'] = { getDeclaredVars: function() { return [this.getFieldValue('VAR')]; }, + getDeclaredVariableType: function() { + return [this.getFieldValue('TYPE')]; + }, renameLexicalVar: function(oldName, newName, oldTranslatedName, newTranslatedName) { if (oldTranslatedName === undefined) { @@ -129,6 +142,14 @@ Blockly.Blocks['lexical_variable_get'] = { return new NameSet(); } }, + getVariableType: function() { + return this.fieldVar_.getVariableType(); + }, + changeVariableType: function() { + this.fieldVar_.getOptions(false); + this.fieldVar_.setValue(this.getFieldValue('VAR')); // Reselect to update the selected option + this.fieldVar_.forceRerender(); + } }; /** @@ -156,8 +177,17 @@ Blockly.Blocks['lexical_variable_set'] = { dropDowns: ['VAR'], }, ]; + + if (dataTypesEnabled()) { + const type = this.getVariableType(); + this.getInput('VALUE').setCheck(type ? [type] : null); + } this.setOnChange(function(changeEvent) { this.workspace.getWarningHandler().checkErrors(this); + if (dataTypesEnabled()) { + const type = this.getVariableType(); + this.getInput('VALUE').setCheck(type ? [type] : null); + } }); }, referenceResults: Blockly.Blocks.lexical_variable_get.referenceResults, @@ -198,4 +228,8 @@ Blockly.Blocks['lexical_variable_set'] = { } return result; }, + getVariableType: function() { + return this.fieldVar_.getVariableType(); + }, + changeVariableType: Blockly.Blocks.lexical_variable_get.changeVariableType, }; diff --git a/block-lexical-variables/src/core.js b/block-lexical-variables/src/core.js index 1a58dad..369fd22 100644 --- a/block-lexical-variables/src/core.js +++ b/block-lexical-variables/src/core.js @@ -31,7 +31,9 @@ export class LexicalVariablesPlugin { /** * @param workspace */ - static init(workspace) { + static init(workspace, options) { + Blockly.types_ = options.types; + // TODO(ewpatton): We need to make sure this is reentrant. const rendererName = workspace.getRenderer().getClassName(); const themeName = workspace.getTheme().getClassName(); diff --git a/block-lexical-variables/src/fields/field_lexical_variable.js b/block-lexical-variables/src/fields/field_lexical_variable.js index b44f297..7ed8532 100644 --- a/block-lexical-variables/src/fields/field_lexical_variable.js +++ b/block-lexical-variables/src/fields/field_lexical_variable.js @@ -163,7 +163,11 @@ FieldLexicalVariable.getGlobalNames = function(optExcludedBlock) { const block = blocks[i]; if ((block.getGlobalNames) && (block != optExcludedBlock)) { - globals.push(...block.getGlobalNames()); + const names = block.getGlobalNames(); + const type = block.getVariableType(); + names.forEach(name => { + globals.push([name, name, type]) + }) } } } @@ -206,7 +210,7 @@ FieldLexicalVariable.getNamesInScope = function(block) { // [lyn, 11/24/12] Sort and remove duplicates from namespaces globalNames = LexicalVariable.sortAndRemoveDuplicates(globalNames); globalNames = globalNames.map(function(name) { - return [Shared.prefixGlobalMenuName(name), 'global ' + name]; + return [Shared.prefixGlobalMenuName(name[0]), 'global ' + name[1], name[2]]; }); const allLexicalNames = FieldLexicalVariable.getLexicalNamesInScope( block); @@ -242,8 +246,9 @@ FieldLexicalVariable.getLexicalNamesInScope = function(block) { * @param list * @param prefix * @param {string=} translated The translated name of the variable, if any + * @param type */ - function rememberName(codeName, list, prefix, translated) { + function rememberName(codeName, list, prefix, translated, type) { const name = translated || codeName; let fullName; if (!Shared.usePrefixInCode) { // Only a single namespace @@ -257,7 +262,7 @@ FieldLexicalVariable.getLexicalNamesInScope = function(block) { // note: correctly handles case where some prefixes are the same fullName = (Shared.possiblyPrefixMenuNameWith(prefix))(name); } - list.push([fullName, codeName]); + list.push([fullName, codeName, type]); } child = block; @@ -266,8 +271,8 @@ FieldLexicalVariable.getLexicalNamesInScope = function(block) { if (parent) { while (parent) { if (parent.withLexicalVarsAndPrefix) { - parent.withLexicalVarsAndPrefix(child, (lexVar, prefix, translated) => { - rememberName(lexVar, allLexicalNames, prefix, translated); + parent.withLexicalVarsAndPrefix(child, (lexVar, prefix, translated, type) => { + rememberName(lexVar, allLexicalNames, prefix, translated, type); }); } child = parent; @@ -290,9 +295,9 @@ FieldLexicalVariable.dropdownCreate = function() { if (variableList.length > 0) { return variableList; } else if (this.translatedName) { - return [[this.translatedName, this.varname]]; + return [[this.translatedName, this.varname, this.getVariableType?.() || '']]; } else { - return [[' ', ' ']]; + return [[' ', ' ', '']]; } }; @@ -348,13 +353,27 @@ FieldLexicalVariable.prototype.doValueUpdate_ = function(newValue) { } this.value_ = newValue; - // Note that we are asking getOptions to add newValue to the list of available - // options. We do that essentially to force callers up the chain to accept - // newValue as an option. This could potentially cause trouble, but it seems - // to be ok for our use case. It is ugly, though, since it bypasses an aspect - // of the normal dropdown validation. - const options = - this.getOptions(true, [[genLocalizedValue(newValue), newValue]]); + + // Try to fetch the parameter type from the flydown + let extraOption; + try { + const topWs = this.sourceBlock_ && this.sourceBlock_.workspace && this.sourceBlock_.workspace.getTopWorkspace + ? this.sourceBlock_.workspace.getTopWorkspace() + : null; + const flydown = topWs && typeof topWs.getFlydown === 'function' ? topWs.getFlydown() : null; + const openerField = flydown && flydown.field_ ? flydown.field_ : null; + const vartype = openerField && typeof openerField.getVariableType === 'function' + ? openerField.getVariableType() + : undefined; + + if (vartype) { + extraOption = [[genLocalizedValue(newValue), newValue, vartype]]; + } + } catch (e) { + // Fall back silently if flydown not available. + } + + const options = this.getOptions(false, extraOption || [[genLocalizedValue(newValue), newValue]]); for (let i = 0, option; (option = options[i]); i++) { if (option[1] == this.value_) { this.selectedOption = option; @@ -591,6 +610,10 @@ FieldLexicalVariable.fromJson = function(options) { return new FieldLexicalVariable(name); }; +FieldLexicalVariable.prototype.getVariableType = function () { + return this.selectedOption[2]; +} + Blockly.fieldRegistry.register('field_lexical_variable', FieldLexicalVariable); @@ -613,7 +636,7 @@ LexicalVariable.renameGlobal = function(newName) { const globals = FieldLexicalVariable.getGlobalNames(this.sourceBlock_); // this.sourceBlock excludes block being renamed from consideration // Potentially rename declaration against other occurrences - newName = FieldLexicalVariable.nameNotIn(newName, globals); + newName = FieldLexicalVariable.nameNotIn(newName, globals.map((element) => element[0])); if (this.sourceBlock_.rendered) { // Rename getters and setters if (Blockly.common.getMainWorkspace()) { @@ -1129,3 +1152,45 @@ LexicalVariable.stringListsEqual = function(strings1, strings2) { } return true; // get here iff lists are equal }; + +LexicalVariable.changeVariableType = function(block, name, oldType, newType) { + let inScopeBlocks = []; + if (block.blocksInScope) { + inScopeBlocks = block.blocksInScope(); + } + + const referenceResults = inScopeBlocks.map(function(blk) { + return LexicalVariable.referenceResult(blk, name, + '', []); + }); + + let blockToChangeType = [] + let capturables = []; + for (let r = 0; r < referenceResults.length; r++) { + blockToChangeType = blockToChangeType.concat(referenceResults[r][0]); + capturables = capturables.concat(referenceResults[r][1]); + } + + // Rename getters and setters + for (let i = 0; i < blockToChangeType.length; i++) { + const block = blockToChangeType[i]; + if (block.changeVariableType) { + block.changeVariableType(newType); + } + + } +} + +LexicalVariable.changeGlobalVariableType = function(name, oldType, newType) { + if (Blockly.common.getMainWorkspace()) { + const blocks = Blockly.common.getMainWorkspace().getAllBlocks(); + for (let i = 0; i < blocks.length; i++) { + const block = blocks[i]; + if (block.type === 'lexical_variable_set' || block.type === 'lexical_variable_get') { + if (block.getDeclaredVars().includes(`global ${name}`)) { + block.changeVariableType(newType) + } + } + } + } +} \ No newline at end of file diff --git a/block-lexical-variables/src/fields/field_parameter_flydown.js b/block-lexical-variables/src/fields/field_parameter_flydown.js index 9be3e8d..0eee6bd 100644 --- a/block-lexical-variables/src/fields/field_parameter_flydown.js +++ b/block-lexical-variables/src/fields/field_parameter_flydown.js @@ -37,7 +37,7 @@ import '../blocks/variable-get-set.js'; // [lyn, 10/26/13] Added opt_additionalChangeHandler to handle propagation of // renaming of proc decl params export class FieldParameterFlydown extends FieldFlydown { - constructor(name, isEditable, opt_displayLocation, opt_additionalChangeHandler) { + constructor(name, isEditable, opt_displayLocation, opt_additionalChangeHandler, type) { const changeHandler = function(text) { if (!FieldParameterFlydown.changeHandlerEnabled) { return text; @@ -54,10 +54,17 @@ export class FieldParameterFlydown extends FieldFlydown { }; super(name, isEditable, opt_displayLocation, changeHandler); + this.type_ = type; }; referencesVariables() { return true; }; + getVariableType() { + return this.type_; + } + setVariableType(type) { + this.type_ = type; + } } FieldParameterFlydown.prototype.fieldCSSClassName = @@ -176,5 +183,3 @@ FieldParameterFlydown.fromJson = function(options) { Blockly.fieldRegistry.register('field_parameter_flydown', FieldParameterFlydown); - - diff --git a/block-lexical-variables/src/generators/procedures.js b/block-lexical-variables/src/generators/procedures.js index 4b7d10a..967865d 100644 --- a/block-lexical-variables/src/generators/procedures.js +++ b/block-lexical-variables/src/generators/procedures.js @@ -26,4 +26,9 @@ if (pkg) { const code = funcName + '(' + args.join(', ') + ')'; return [code, Order.FUNCTION_CALL]; }; + + javascriptGenerator.forBlock['procedures_early_return'] = function (block, generator) { + const returnValue = generator.valueToCode(block, 'RETURN_VALUE', Order.NONE) || 'null'; + return `return ${returnValue};\n`; + } } diff --git a/block-lexical-variables/src/mixins.js b/block-lexical-variables/src/mixins.js index 93389b2..fc3ee8f 100644 --- a/block-lexical-variables/src/mixins.js +++ b/block-lexical-variables/src/mixins.js @@ -41,7 +41,8 @@ export const coreLexicalVariableScopeMixin = { if (child && thisBlock.getInputTargetBlock(thisBlock.getScopedInputName()) === child) { thisBlock.getDeclaredVarFieldNamesAndPrefixes().forEach(([fieldName, prefix]) => { const lexVar = thisBlock.getFieldValue(fieldName); - proc(lexVar, prefix); + if (thisBlock && thisBlock.getVariableType) proc(lexVar, prefix, '', thisBlock.getVariableType()); + else proc(lexVar, prefix); }); } }, diff --git a/block-lexical-variables/src/msg.js b/block-lexical-variables/src/msg.js index cdaa364..1e9632d 100644 --- a/block-lexical-variables/src/msg.js +++ b/block-lexical-variables/src/msg.js @@ -148,3 +148,5 @@ Blockly.Msg['LANG_CONTROLS_DO_THEN_RETURN_COLLAPSED_TEXT'] = 'do/result'; Blockly.Msg['LANG_CONTROLS_DO_THEN_RETURN_TITLE'] = 'do result'; Blockly.Msg['PROCEDURES_DEFNORETURN_PROCEDURE'] = Blockly.Msg['LANG_PROCEDURES_DEFNORETURN_PROCEDURE']; Blockly.Msg['PROCEDURES_DEFRETURN_PROCEDURE'] = Blockly.Msg['LANG_PROCEDURES_DEFRETURN_PROCEDURE']; +Blockly.Msg['LANG_PROCEDURES_EARLY_RETURN_TOOLTIP'] = 'return'; +Blockly.Msg['LANG_PROCEDURES_EARLY_RETURN_TITLE'] = 'early return'; \ No newline at end of file diff --git a/block-lexical-variables/src/shared.js b/block-lexical-variables/src/shared.js index 70399c1..a7af242 100644 --- a/block-lexical-variables/src/shared.js +++ b/block-lexical-variables/src/shared.js @@ -116,3 +116,6 @@ export const possiblyPrefixGeneratedVarName = function(prefix) { }; }; +export const dataTypesEnabled = function () { + return Blockly.types_ && Blockly.types_.enableDataTypes; +} diff --git a/block-lexical-variables/test/index.js b/block-lexical-variables/test/index.js index 1c1dc26..706d1d4 100644 --- a/block-lexical-variables/test/index.js +++ b/block-lexical-variables/test/index.js @@ -39,7 +39,31 @@ const allBlocks = [ */ function createWorkspace(blocklyDiv, options) { const workspace = Blockly.inject(blocklyDiv, options); - LexicalVariablesPlugin.init(workspace); + LexicalVariablesPlugin.init(workspace, { + types: { + enableDataTypes: true, + dataTypes: [ + ['int', 'int'], + ['float', 'float'], + ['boolean', 'boolean'], + ['char*', 'String'], + ], + loopType: 'int', + defaultType: 'int', + } + }); + +workspace.registerToolboxCategoryCallback( + 'CUSTOM_PROCEDURE', + function(workspace) { + const xmlList = Blockly.Procedures.flyoutCategory(workspace); + const earlyReturn = document.createElement('block'); + earlyReturn.setAttribute('type', 'procedures_early_return'); + xmlList.push(earlyReturn); + return xmlList; + } +); + return workspace; } @@ -60,7 +84,7 @@ document.addEventListener('DOMContentLoaded', function() { `, collapse: true,