Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 68 additions & 2 deletions src/blocks/mrc_call_python_function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export enum FunctionKind {
INSTANCE = 'instance',
INSTANCE_WITHIN = 'instance_within',
INSTANCE_COMPONENT = 'instance_component',
INSTANCE_ROBOT = 'instance_robot',
EVENT = 'event',
}

Expand Down Expand Up @@ -220,7 +221,7 @@ function createInstanceMethodBlock(

export function getInstanceComponentBlocks(
componentType: string,
componentName: string) {
componentName: string): ToolboxItems.ContentsType[] {
const contents: ToolboxItems.ContentsType[] = [];

const classData = getClassData(componentType);
Expand Down Expand Up @@ -267,7 +268,7 @@ function createInstanceComponentBlock(
// We don't include the arg or input for self.
for (let i = 1; i < functionData.args.length; i++) {
const argData = functionData.args[i];
let argName = argData.name;
const argName = argData.name;
extraState.args.push({
'name': argName,
'type': argData.type,
Expand All @@ -289,6 +290,51 @@ function createInstanceComponentBlock(
return block;
}

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;
}

function createInstanceRobotBlock(method: CommonStorage.Method): ToolboxItems.Block {
const extraState: CallPythonFunctionExtraState = {
functionKind: FunctionKind.INSTANCE_ROBOT,
returnType: method.returnType,
actualFunctionName: method.pythonName,
args: [],
};
const fields: {[key: string]: any} = {};
fields[FIELD_FUNCTION_NAME] = method.visibleName;
const inputs: {[key: string]: any} = {};
// We don't include the arg or input for the self argument.
for (let i = 1; i < method.args.length; i++) {
const arg = method.args[i];
extraState.args.push({
'name': arg.name,
'type': arg.type,
});
// Check if we should plug a variable getter block into the argument input socket.
const input = Value.valueForFunctionArgInput(arg.type, '');
if (input) {
// Because we skipped the self argument, use i - 1 when filling the inputs array.
inputs['ARG' + (i - 1)] = input;
}
}
let block = new ToolboxItems.Block(BLOCK_NAME, extraState, fields, Object.keys(inputs).length ? inputs : null);
if (method.returnType && method.returnType != 'None') {
const varName = Variable.varNameForType(method.returnType);
if (varName) {
block = Variable.createVariableSetterBlock(varName, block);
}
}
return block;
}

//..............................................................................

export type CallPythonFunctionBlock = Blockly.Block & CallPythonFunctionMixin;
Expand Down Expand Up @@ -401,6 +447,11 @@ const CALL_PYTHON_FUNCTION = {
' on the component named ' + this.getComponentName() + '.';
break;
}
case FunctionKind.INSTANCE_ROBOT: {
const functionName = this.getFieldValue(FIELD_FUNCTION_NAME);
tooltip = 'Calls the robot method ' + functionName + '.';
break;
}
default:
throw new Error('mrcFunctionKind has unexpected value: ' + this.mrcFunctionKind)
}
Expand Down Expand Up @@ -568,6 +619,14 @@ const CALL_PYTHON_FUNCTION = {
.appendField(createFieldNonEditableText(''), FIELD_FUNCTION_NAME);
break;
}
case FunctionKind.INSTANCE_ROBOT: {
this.appendDummyInput('TITLE')
.appendField('call')
.appendField(createFieldNonEditableText('robot'))
.appendField('.')
.appendField(createFieldNonEditableText(''), FIELD_FUNCTION_NAME);
break;
}
default:
throw new Error('mrcFunctionKind has unexpected value: ' + this.mrcFunctionKind)
}
Expand Down Expand Up @@ -707,6 +766,13 @@ export const pythonFromBlock = function(
code += componentName + '.' + functionName;
break;
}
case FunctionKind.INSTANCE_ROBOT: {
const functionName = callPythonFunctionBlock.mrcActualFunctionName
? callPythonFunctionBlock.mrcActualFunctionName
: block.getFieldValue(FIELD_FUNCTION_NAME);
code = 'self.robot.' + functionName;
break;
}
default:
throw new Error('mrcFunctionKind has unexpected value: ' + callPythonFunctionBlock.mrcFunctionKind)
}
Expand Down
27 changes: 26 additions & 1 deletion src/blocks/mrc_class_method_def.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
*/

/**
* @fileoverview Blocks for class method defintion
* @fileoverview Blocks for class method definition
* @author alan@porpoiseful.com (Alan Smith)
*/
import * as Blockly from 'blockly';
Expand All @@ -25,6 +25,7 @@ import { createFieldNonEditableText } from '../fields/FieldNonEditableText'
import { createFieldFlydown } from '../fields/field_flydown';
import { Order } from 'blockly/python';
import { ExtendedPythonGenerator } from '../editor/extended_python_generator';
import * as commonStorage from '../storage/common_storage';
import { renameMethodCallers, mutateMethodCallers } from './mrc_call_python_function'
import { findConnectedBlocksOfType } from './utils/find_connected_blocks';
import { BLOCK_NAME as MRC_GET_PARAMETER_BLOCK_NAME } from './mrc_get_parameter';
Expand All @@ -45,6 +46,7 @@ interface ClassMethodDefMixin extends ClassMethodDefMixinType {
mrcReturnType: string,
mrcParameters: Parameter[],
mrcPythonMethodName: string,
mrcMethod: commonStorage.Method | null,
}
type ClassMethodDefMixinType = typeof CLASS_METHOD_DEF;

Expand Down Expand Up @@ -131,6 +133,7 @@ const CLASS_METHOD_DEF = {
this.mrcPythonMethodName = extraState.pythonMethodName ? extraState.pythonMethodName : '';
this.mrcReturnType = extraState.returnType;
this.mrcParameters = [];
this.mrcMethod = null;

extraState.params.forEach((param) => {
this.mrcParameters.push({
Expand Down Expand Up @@ -254,6 +257,9 @@ const CLASS_METHOD_DEF = {
}
return legalName;
},
getMethod: function (this: ClassMethodDefBlock): commonStorage.Method | null {
return this.mrcMethod;
}
};

/**
Expand Down Expand Up @@ -395,5 +401,24 @@ export const pythonFromBlock = function (
code = generator.scrub_(block, code);
generator.addClassMethodDefinition(funcName, code);

if (block.mrcCanBeCalledOutsideClass) {
// Update the mrcMethod.
block.mrcMethod = {
visibleName: block.getFieldValue('NAME'),
pythonName: funcName,
returnType: block.mrcReturnType,
args: [{
name: 'self',
type: '',
}],
};
block.mrcParameters.forEach(param => {
block.mrcMethod!.args.push({
name: param.name,
type: param.type ? param.type : '',
});
});
}

return '';
}
75 changes: 40 additions & 35 deletions src/editor/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { extendedPythonGenerator } from './extended_python_generator';
import { GeneratorContext } from './generator_context';
import * as commonStorage from '../storage/common_storage';
import * as mechanismComponentHolder from '../blocks/mrc_mechanism_component_holder';
import * as classMethodDef from '../blocks/mrc_class_method_def';
//import { testAllBlocksInToolbox } from '../toolbox/toolbox_tests';
import { MethodsCategory } from '../toolbox/methods_category';
import { EventsCategory } from '../toolbox/event_category';
Expand Down Expand Up @@ -71,38 +72,15 @@ export class Editor {
return;
}

// TODO(lizlooney): As blocks are loaded, determine whether any blocks
// are accessing variable or calling functions thar are defined in another
// blocks file (like the Robot) and check whether the variable or function
// definition has changed. This might happen if the user defines a variable
// or function in the Robot, uses the variable or function in the
// OpMode, and then removes or changes the variable or function in the
// Robot.
// TODO(lizlooney): Look at blocks with type 'mrc_call_python_function' that
// are calling methods defined in the Robot and check that the Robot method
// still exists and hasn't been changed. If it has changed, update the block
// if possible or put a visible warning on it.

// TODO(lizlooney): We will need a way to identify which variable or
// function, other than by the variable name or function name, because the
// user might change the name. This will take some thought and I should
// write up a design doc and discuss it with others to make sure we have a
// good solution.

// TODO(lizlooney): Look at blocks with type 'mrc_get_python_variable' or
// 'mrc_set_python_variable', and where block.mrcExportedVariable === true.
// Look at block.mrcImportModule and get the exported blocks for that module.
// It could be from the Robot (or a Mechanism?) and we already have the Robot content.
// Check whether block.mrcActualVariableName matches any exportedBlock's
// extraState.actualVariableName. If there is no match, put a warning on the
// block.

// TODO(lizlooney): Look at blocks with type 'mrc_call_python_function' and
// where block.mrcExportedFunction === true.
// Look at block.mrcImportModule and get the exported blocks for that module.
// It could be from the Robot (or a Mechanism?) and we already have the Robot content.
// Check whether block.mrcActualFunctionName matches any exportedBlock's
// extraState.actualFunctionName. If there is no match, put a warning on the block.
// If there is a match, check whether
// block.mrcArgs.length === exportedBlock.extraState.args.length and
// block.mrcArgs[i].name === exportedBlock.extraState.args[i].name for all args.
// If there is any differences, put a warning on the block.
// TODO(lizlooney): Look at blocks with type 'mrc_call_python_function' that
// are calling methods on components defined in the Robot and check that the
// component and the method still exists and hasn't been changed. If it has
// changed, update the block if possible or put a visible warning on it.
}

private onChangeAfterLoading(event: Blockly.Events.Abstract) {
Expand Down Expand Up @@ -221,12 +199,12 @@ export class Editor {
}
const pythonCode = extendedPythonGenerator.mrcWorkspaceToCode(
this.blocklyWorkspace, this.generatorContext);
const exportedBlocks = JSON.stringify(this.generatorContext.getExportedBlocks());
const blocksContent = JSON.stringify(
Blockly.serialization.workspaces.save(this.blocklyWorkspace));
const methodsContent = JSON.stringify(this.getMethodsFromWorkspace());
const componentsContent = JSON.stringify(this.getComponentsFromWorkspace());
return commonStorage.makeModuleContent(
this.currentModule, pythonCode, blocksContent, exportedBlocks, componentsContent);
this.currentModule, pythonCode, blocksContent, methodsContent, componentsContent);
}

public getComponentsFromWorkspace(): commonStorage.Component[] {
Expand All @@ -246,6 +224,22 @@ export class Editor {
return components;
}

public getMethodsFromWorkspace(): 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);
}
});
}
return methods;
}

public async saveBlocks() {
const moduleContent = this.getModuleContent();
try {
Expand All @@ -260,8 +254,6 @@ export class Editor {
* Returns the components defined in the robot.
*/
public getComponentsFromRobot(): commonStorage.Component[] {
let components: commonStorage.Component[];

if (this.currentModule?.moduleType === commonStorage.MODULE_TYPE_ROBOT) {
return this.getComponentsFromWorkspace();
}
Expand All @@ -271,6 +263,19 @@ export class Editor {
return commonStorage.extractComponents(this.robotContent);
}

/**
* Returns the methods defined in the robot.
*/
public getMethodsFromRobot(): commonStorage.Method[] {
if (this.currentModule?.moduleType === commonStorage.MODULE_TYPE_ROBOT) {
return this.getMethodsFromWorkspace();
}
if (!this.robotContent) {
throw new Error('getMethodsFromRobot: this.robotContent is null.');
}
return commonStorage.extractMethods(this.robotContent);
}

public static getEditorForBlocklyWorkspace(workspace: Blockly.Workspace): Editor | null {
if (workspace.id in Editor.workspaceIdToEditor) {
return Editor.workspaceIdToEditor[workspace.id];
Expand Down
Loading