diff --git a/src/blocks/mrc_call_python_function.ts b/src/blocks/mrc_call_python_function.ts index ab1ea5b2..4000feba 100644 --- a/src/blocks/mrc_call_python_function.ts +++ b/src/blocks/mrc_call_python_function.ts @@ -71,10 +71,9 @@ const WARNING_ID_FUNCTION_CHANGED = 'function changed'; export function addBuiltInFunctionBlocks( functions: FunctionData[], contents: ToolboxItems.ContentsType[]) { - for (const functionData of functions) { - const block = createBuiltInMethodBlock(functionData); - contents.push(block); - } + functions.forEach(functionData => { + contents.push(createBuiltInMethodBlock(functionData)); + }); } function createBuiltInMethodBlock( @@ -132,24 +131,24 @@ export function addModuleFunctionBlocks( moduleName: string, functions: FunctionData[], contents: ToolboxItems.ContentsType[]) { - for (const functionData of functions) { + functions.forEach(functionData => { const block = createModuleFunctionOrStaticMethodBlock( FunctionKind.MODULE, moduleName, moduleName, functionData); contents.push(block); - } + }); } export function addStaticMethodBlocks( importModule: string, functions: FunctionData[], contents: ToolboxItems.ContentsType[]) { - for (const functionData of functions) { + functions.forEach(functionData => { if (functionData.declaringClassName) { const block = createModuleFunctionOrStaticMethodBlock( FunctionKind.STATIC, importModule, functionData.declaringClassName, functionData); contents.push(block); } - } + }); } function createModuleFunctionOrStaticMethodBlock( @@ -176,10 +175,9 @@ export function addConstructorBlocks( importModule: string, functions: FunctionData[], contents: ToolboxItems.ContentsType[]) { - for (const functionData of functions) { - const block = createConstructorBlock(importModule, functionData); - contents.push(block); - } + functions.forEach(functionData => { + contents.push(createConstructorBlock(importModule, functionData)); + }); } function createConstructorBlock( @@ -202,10 +200,9 @@ function createConstructorBlock( export function addInstanceMethodBlocks( functions: FunctionData[], contents: ToolboxItems.ContentsType[]) { - for (const functionData of functions) { - const block = createInstanceMethodBlock(functionData); - contents.push(block); - } + functions.forEach(functionData => { + contents.push(createInstanceMethodBlock(functionData)); + }); } function createInstanceMethodBlock( @@ -224,6 +221,39 @@ function createInstanceMethodBlock( return createBlock(extraState, fields, inputs); } +export function addInstanceWithinBlocks( + methods: CommonStorage.Method[], + contents: ToolboxItems.ContentsType[]) { + methods.forEach(method => { + contents.push(createInstanceWithinBlock(method)); + }); +} + +function createInstanceWithinBlock(method: CommonStorage.Method): ToolboxItems.Block { + const extraState: CallPythonFunctionExtraState = { + functionKind: FunctionKind.INSTANCE_WITHIN, + returnType: method.returnType, + actualFunctionName: method.pythonName, + args: [], + classMethodDefBlockId: method.blockId, + }; + const fields: {[key: string]: any} = {}; + fields[FIELD_FUNCTION_NAME] = method.visibleName; + const inputs: {[key: string]: any} = {}; + // Convert method.args from CommonStorage.MethodArg[] to ArgData[]. + const args: ArgData[] = []; + // We don't include the arg for the self argument. + for (let i = 1; i < method.args.length; i++) { + args.push({ + name: method.args[i].name, + type: method.args[i].type, + defaultValue: '', + }); + } + processArgs(args, extraState, inputs); + return createBlock(extraState, fields, inputs); +} + export function getInstanceComponentBlocks( component: CommonStorage.Component): ToolboxItems.ContentsType[] { const contents: ToolboxItems.ContentsType[] = []; @@ -276,15 +306,12 @@ function createInstanceComponentBlock( return createBlock(extraState, fields, inputs); } -export function getInstanceRobotBlocks(methods: CommonStorage.Method[]): ToolboxItems.ContentsType[] { - const contents: ToolboxItems.ContentsType[] = []; - - for (const method of methods) { - const block = createInstanceRobotBlock(method); - contents.push(block); - } - - return contents; +export function addInstanceRobotBlocks( + methods: CommonStorage.Method[], + contents: ToolboxItems.ContentsType[]) { + methods.forEach(method => { + contents.push(createInstanceRobotBlock(method)); + }); } function createInstanceRobotBlock(method: CommonStorage.Method): ToolboxItems.Block { diff --git a/src/blocks/mrc_class_method_def.ts b/src/blocks/mrc_class_method_def.ts index 5d213c76..55423e60 100644 --- a/src/blocks/mrc_class_method_def.ts +++ b/src/blocks/mrc_class_method_def.ts @@ -258,9 +258,18 @@ const CLASS_METHOD_DEF = { } return legalName; }, - getMethod: function (this: ClassMethodDefBlock): commonStorage.Method | null { - return this.mrcMethod; - } + getMethodForWithin: function (this: ClassMethodDefBlock): commonStorage.Method | null { + return this.mrcCanBeCalledWithinClass ? this.mrcMethod : null; + }, + getMethodForOutside: function (this: ClassMethodDefBlock): commonStorage.Method | null { + return this.mrcCanBeCalledOutsideClass ? this.mrcMethod : null; + }, + canChangeSignature: function (this: ClassMethodDefBlock): boolean { + return this.mrcCanChangeSignature; + }, + getMethodName: function (this: ClassMethodDefBlock): string { + return this.getFieldValue('NAME'); + }, }; /** @@ -402,7 +411,7 @@ export const pythonFromBlock = function ( code = generator.scrub_(block, code); generator.addClassMethodDefinition(funcName, code); - if (block.mrcCanBeCalledOutsideClass) { + if (block.mrcCanBeCalledWithinClass || block.mrcCanBeCalledOutsideClass) { // Update the mrcMethod. block.mrcMethod = { blockId: block.id, diff --git a/src/editor/editor.ts b/src/editor/editor.ts index 6bbce4d8..b581e912 100644 --- a/src/editor/editor.ts +++ b/src/editor/editor.ts @@ -206,7 +206,11 @@ export class Editor { this.blocklyWorkspace, this.generatorContext); const blocksContent = JSON.stringify( Blockly.serialization.workspaces.save(this.blocklyWorkspace)); - const methodsContent = JSON.stringify(this.getMethodsFromWorkspace()); + const methodsContent = ( + this.currentModule?.moduleType === commonStorage.MODULE_TYPE_ROBOT || + this.currentModule?.moduleType === commonStorage.MODULE_TYPE_MECHANISM) + ? JSON.stringify(this.getMethodsForOutsideFromWorkspace()) + : '[]'; const componentsContent = JSON.stringify(this.getComponentsFromWorkspace()); return commonStorage.makeModuleContent( this.currentModule, pythonCode, blocksContent, methodsContent, componentsContent); @@ -229,22 +233,53 @@ export class Editor { return components; } - public getMethodsFromWorkspace(): commonStorage.Method[] { + public getMethodsForWithinFromWorkspace(): commonStorage.Method[] { const methods: commonStorage.Method[] = []; - if (this.currentModule?.moduleType === commonStorage.MODULE_TYPE_ROBOT || - this.currentModule?.moduleType === commonStorage.MODULE_TYPE_MECHANISM) { - // Get the class method definition blocks. - const methodDefBlocks = this.blocklyWorkspace.getBlocksByType(classMethodDef.BLOCK_NAME); - methodDefBlocks.forEach(methodDefBlock => { - const method = (methodDefBlock as classMethodDef.ClassMethodDefBlock).getMethod(); - if (method) { - methods.push(method); - } - }); - } + + // Get the class method definition blocks. + const methodDefBlocks = this.blocklyWorkspace.getBlocksByType(classMethodDef.BLOCK_NAME); + methodDefBlocks.forEach(methodDefBlock => { + const method = (methodDefBlock as classMethodDef.ClassMethodDefBlock).getMethodForWithin(); + if (method) { + methods.push(method); + } + }); + return methods; } + public getMethodsForOutsideFromWorkspace(): commonStorage.Method[] { + const methods: commonStorage.Method[] = []; + + // Get the class method definition blocks. + const methodDefBlocks = this.blocklyWorkspace.getBlocksByType(classMethodDef.BLOCK_NAME); + methodDefBlocks.forEach(methodDefBlock => { + const method = (methodDefBlock as classMethodDef.ClassMethodDefBlock).getMethodForOutside(); + if (method) { + methods.push(method); + } + }); + + return methods; + } + + public getMethodNamesAlreadyOverriddenInWorkspace(): string[] { + const methodNamesAlreadyOverridden: string[] = []; + + // Get the class method definition blocks. + const methodDefBlocks = this.blocklyWorkspace.getBlocksByType(classMethodDef.BLOCK_NAME); + methodDefBlocks.forEach(block => { + const methodDefBlock = block as classMethodDef.ClassMethodDefBlock; + // If the user cannot change the signature, it means the block defines a method that overrides a baseclass method. + // That's what we are looking for here. + if (!methodDefBlock.canChangeSignature()) { + methodNamesAlreadyOverridden.push(methodDefBlock.getMethodName()); + } + }); + + return methodNamesAlreadyOverridden; + } + public async saveBlocks() { const moduleContent = this.getModuleContent(); try { @@ -273,7 +308,7 @@ export class Editor { */ public getMethodsFromRobot(): commonStorage.Method[] { if (this.currentModule?.moduleType === commonStorage.MODULE_TYPE_ROBOT) { - return this.getMethodsFromWorkspace(); + return this.getMethodsForWithinFromWorkspace(); } if (!this.robotContent) { throw new Error('getMethodsFromRobot: this.robotContent is null.'); diff --git a/src/toolbox/hardware_category.ts b/src/toolbox/hardware_category.ts index 89bf9bf1..5367f8ee 100644 --- a/src/toolbox/hardware_category.ts +++ b/src/toolbox/hardware_category.ts @@ -24,7 +24,7 @@ import * as commonStorage from '../storage/common_storage'; import * as toolboxItems from './items'; import { getAllPossibleMechanisms } from './blocks_mechanisms'; import * as Component from '../blocks/mrc_component'; -import { getInstanceComponentBlocks, getInstanceRobotBlocks } from '../blocks/mrc_call_python_function'; +import { getInstanceComponentBlocks, addInstanceRobotBlocks } from '../blocks/mrc_call_python_function'; import { Editor } from '../editor/editor'; export function getHardwareCategory(currentModule: commonStorage.Module): toolboxItems.Category { @@ -253,7 +253,7 @@ function getRobotMethodsBlocks(): toolboxItems.Category { const editor = Editor.getEditorForBlocklyWorkspace(workspace); if (editor) { const methodsFromRobot = editor.getMethodsFromRobot(); - contents.push(...getInstanceRobotBlocks(methodsFromRobot)); + addInstanceRobotBlocks(methodsFromRobot, contents); } } diff --git a/src/toolbox/methods_category.ts b/src/toolbox/methods_category.ts index 4a2f035e..bb56d833 100644 --- a/src/toolbox/methods_category.ts +++ b/src/toolbox/methods_category.ts @@ -28,6 +28,8 @@ import { mechanism_class_blocks } from './mechanism_class_methods'; import { opmode_class_blocks } from './opmode_class_methods'; import { robot_class_blocks } from './robot_class_methods'; import { ClassMethodDefBlock } from '../blocks/mrc_class_method_def' +import { addInstanceWithinBlocks } from '../blocks/mrc_call_python_function' +import { Editor } from '../editor/editor'; const CUSTOM_CATEGORY_METHODS = 'METHODS'; @@ -56,32 +58,29 @@ export class MethodsCategory { // Add blocks for defining any methods that can be defined in the current // module. For example, if the current module is an OpMode, add blocks to // define the methods declared in the OpMode class. - if (this.currentModule) { - // Collect the method names for mrc_class_method_def blocks that are - // already in the blockly workspace. - const methodNamesAlreadyUsed: string[] = []; - workspace.getBlocksByType('mrc_class_method_def', false).forEach((block) => { - const classMethodDefBlock = block as ClassMethodDefBlock; - if (!classMethodDefBlock.mrcCanChangeSignature) { - methodNamesAlreadyUsed.push(classMethodDefBlock.getFieldValue('NAME')); + + const editor = Editor.getEditorForBlocklyWorkspace(workspace); + if (editor) { + // Collect the method names that are already overridden in the blockly workspace. + const methodNamesAlreadyOverridden = editor.getMethodNamesAlreadyOverriddenInWorkspace(); + + if (this.currentModule) { + if (this.currentModule.moduleType == commonStorage.MODULE_TYPE_ROBOT) { + // Add the methods for a Robot. + this.addClassBlocksForCurrentModule( + 'More Robot Methods', robot_class_blocks, + methodNamesAlreadyOverridden, contents); + } else if (this.currentModule.moduleType == commonStorage.MODULE_TYPE_MECHANISM) { + // Add the methods for a Mechanism. + this.addClassBlocksForCurrentModule( + 'More Mechanism Methods', mechanism_class_blocks, + methodNamesAlreadyOverridden, contents); + } else if (this.currentModule.moduleType == commonStorage.MODULE_TYPE_OPMODE) { + // Add the methods for an OpMode. + this.addClassBlocksForCurrentModule( + 'More OpMode Methods', opmode_class_blocks, + methodNamesAlreadyOverridden, contents); } - }); - - if (this.currentModule.moduleType == commonStorage.MODULE_TYPE_ROBOT) { - // Add the methods for a Robot. - this.addClassBlocksForCurrentModule( - 'More Robot Methods', robot_class_blocks, - methodNamesAlreadyUsed, contents); - } else if (this.currentModule.moduleType == commonStorage.MODULE_TYPE_MECHANISM) { - // Add the methods for a Mechanism. - this.addClassBlocksForCurrentModule( - 'More Mechanism Methods', mechanism_class_blocks, - methodNamesAlreadyUsed, contents); - } else if (this.currentModule.moduleType == commonStorage.MODULE_TYPE_OPMODE) { - // Add the methods for an OpMode. - this.addClassBlocksForCurrentModule( - 'More OpMode Methods', opmode_class_blocks, - methodNamesAlreadyUsed, contents); } } @@ -103,39 +102,14 @@ export class MethodsCategory { returnType: 'None', params: [], }, - }); - - // For each mrc_class_method_def block in the blockly workspace, check if it - // can be called from within the class, and if so, add a - // mrc_call_python_function block. - workspace.getBlocksByType('mrc_class_method_def', false).forEach((block) => { - const classMethodDefBlock = block as ClassMethodDefBlock; - if (classMethodDefBlock.mrcCanBeCalledWithinClass) { - const callPythonFunctionBlock: toolboxItems.Block = { - kind: 'block', - type: 'mrc_call_python_function', - extraState: { - functionKind: 'instance_within', - returnType: classMethodDefBlock.mrcReturnType, - args: [], - importModule: '', - }, - fields: { - FUNC: classMethodDefBlock.getFieldValue('NAME'), - }, - }; - classMethodDefBlock.mrcParameters.forEach((param) => { - if (callPythonFunctionBlock.extraState) { - callPythonFunctionBlock.extraState.args.push( - { - name: param.name, - type: param.type ?? '', - }); - } - }); - contents.push(callPythonFunctionBlock); } - }); + ); + + // Get blocks for calling methods defined in the current workspace. + if (editor) { + const methodsFromWorkspace = editor.getMethodsForWithinFromWorkspace(); + addInstanceWithinBlocks(methodsFromWorkspace, contents); + } const toolboxInfo = { contents: contents, @@ -146,12 +120,12 @@ export class MethodsCategory { private addClassBlocksForCurrentModule( label: string, class_blocks: toolboxItems.Block[], - methodNamesAlreadyUsed: string[], contents: toolboxItems.ContentsType[]) { + methodNamesAlreadyOverridden: string[], contents: toolboxItems.ContentsType[]) { let labelAdded = false; class_blocks.forEach((blockInfo) => { if (blockInfo.fields) { const methodName = blockInfo.fields['NAME']; - if (!methodNamesAlreadyUsed.includes(methodName)) { + if (!methodNamesAlreadyOverridden.includes(methodName)) { if (!labelAdded) { contents.push( {