Skip to content

Commit a78aa99

Browse files
authored
Merge pull request #88 from lizlooney/pr_method_name_python_reserved_word_2
Handle method names that are python reserved words.
2 parents 178dc39 + 4bddea3 commit a78aa99

File tree

5 files changed

+91
-32
lines changed

5 files changed

+91
-32
lines changed

src/blocks/mrc_call_python_function.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export type FunctionArg = {
4848
type: string,
4949
};
5050

51-
type CallPythonFunctionBlock = Blockly.Block & CallPythonFunctionMixin;
51+
export type CallPythonFunctionBlock = Blockly.Block & CallPythonFunctionMixin;
5252
interface CallPythonFunctionMixin extends CallPythonFunctionMixinType {
5353
mrcFunctionKind: FunctionKind,
5454
mrcReturnType: string,
@@ -57,6 +57,7 @@ interface CallPythonFunctionMixin extends CallPythonFunctionMixinType {
5757
mrcImportModule: string,
5858
mrcActualFunctionName: string,
5959
mrcExportedFunction: boolean,
60+
maybeRenameProcedure(this: CallPythonFunctionBlock, oldName: string, legalName: string): void;
6061
}
6162
type CallPythonFunctionMixinType = typeof CALL_PYTHON_FUNCTION;
6263

@@ -263,6 +264,13 @@ const CALL_PYTHON_FUNCTION = {
263264
input.setCheck(getAllowedTypesForSetCheck(this.mrcArgs[i].type));
264265
}
265266
}
267+
},
268+
maybeRenameProcedure: function(this: CallPythonFunctionBlock, oldName: string, newName: string): void {
269+
if (this.mrcFunctionKind === FunctionKind.INSTANCE_WITHIN) {
270+
if (this.getFieldValue(pythonUtils.FIELD_FUNCTION_NAME) == oldName) {
271+
this.setFieldValue(newName, pythonUtils.FIELD_FUNCTION_NAME);
272+
}
273+
}
266274
}
267275
};
268276

@@ -315,10 +323,8 @@ export const pythonFromBlock = function(
315323
break;
316324
}
317325
case FunctionKind.INSTANCE_WITHIN: {
318-
const callPythonFunctionBlock = block as CallPythonFunctionBlock;
319-
const functionName = (callPythonFunctionBlock.mrcActualFunctionName)
320-
? callPythonFunctionBlock.mrcActualFunctionName
321-
: block.getFieldValue(pythonUtils.FIELD_FUNCTION_NAME);
326+
const blocklyName = block.getFieldValue(pythonUtils.FIELD_FUNCTION_NAME);
327+
const functionName = generator.getProcedureName(blocklyName);
322328
code = 'self.' + functionName;
323329
break;
324330
}

src/blocks/mrc_class_method_def.ts

Lines changed: 77 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import * as ChangeFramework from './utils/change_framework'
2626
import { getLegalName } from './utils/python';
2727
import { Order } from 'blockly/python';
2828
import { ExtendedPythonGenerator } from '../editor/extended_python_generator';
29+
import { CallPythonFunctionBlock } from './mrc_call_python_function'
2930

3031
export const BLOCK_NAME = 'mrc_class_method_def';
3132

@@ -158,8 +159,10 @@ const CLASS_METHOD_DEF = {
158159
input.removeField('NAME');
159160

160161
if (this.mrcCanChangeSignature) {
161-
input.insertFieldAt(0, new Blockly.FieldTextInput(name), 'NAME');
162+
const nameField = new Blockly.FieldTextInput(name);
163+
input.insertFieldAt(0, nameField, 'NAME');
162164
this.setMutator(new Blockly.icons.MutatorIcon([MUTATOR_BLOCK_NAME], this));
165+
nameField.setValidator(this.mrcNameFieldValidator.bind(this, nameField));
163166
}
164167
else {
165168
input.insertFieldAt(0, createFieldNonEditableText(name), 'NAME');
@@ -224,8 +227,79 @@ const CLASS_METHOD_DEF = {
224227
Blockly.Events.enable();
225228
}
226229
},
230+
mrcNameFieldValidator(this: ClassMethodDefBlock, nameField: Blockly.FieldTextInput, name: string): string {
231+
// When the user changes the method name on the block, clear the mrcPythonMethodName field.
232+
this.mrcPythonMethodName = '';
233+
234+
// Strip leading and trailing whitespace.
235+
name = name.trim();
236+
237+
const legalName = findLegalMethodName(name, this);
238+
const oldName = nameField.getValue();
239+
if (oldName !== name && oldName !== legalName) {
240+
// Rename any callers.
241+
for (const block of this.workspace.getBlocksByType('mrc_call_python_function')) {
242+
(block as CallPythonFunctionBlock).maybeRenameProcedure(oldName, legalName);
243+
}
244+
}
245+
return legalName;
246+
},
227247
};
228248

249+
/**
250+
* Ensure two identically-named methods don't exist.
251+
* Take the proposed method name, and return a legal name i.e. one that
252+
* is not empty and doesn't collide with other methods.
253+
*
254+
* @param name Proposed method name.
255+
* @param block Block to disambiguate.
256+
* @returns Non-colliding name.
257+
*/
258+
function findLegalMethodName(name: string, block: ClassMethodDefBlock): string {
259+
if (block.isInFlyout) {
260+
// Flyouts can have multiple methods called 'my_method'.
261+
return name;
262+
}
263+
name = name || 'unnamed';
264+
while (isMethodNameUsed(name, block.workspace, block)) {
265+
// Collision with another method.
266+
const r = name.match(/^(.*?)(\d+)$/);
267+
if (!r) {
268+
name += '2';
269+
} else {
270+
name = r[1] + (parseInt(r[2]) + 1);
271+
}
272+
}
273+
return name;
274+
}
275+
276+
/**
277+
* Return if the given name is already a method name.
278+
*
279+
* @param name The questionable name.
280+
* @param workspace The workspace to scan for collisions.
281+
* @param opt_exclude Optional block to exclude from comparisons (one doesn't
282+
* want to collide with oneself).
283+
* @returns True if the name is used, otherwise return false.
284+
*/
285+
function isMethodNameUsed(
286+
name: string, workspace: Workspace, opt_exclude?: Block): boolean {
287+
const nameLowerCase = name.toLowerCase();
288+
for (const block of workspace.getBlocksByType('mrc_class_method_def')) {
289+
if (block === opt_exclude) {
290+
continue;
291+
}
292+
if (nameLowerCase === block.getFieldValue('NAME').toLowerCase()) {
293+
return true;
294+
}
295+
if (block.mrcPythonMethodName &&
296+
nameLowerCase === block.mrcPythonMethodName.toLowerCase()) {
297+
return true;
298+
}
299+
}
300+
return false;
301+
}
302+
229303
const METHOD_PARAM_CONTAINER = {
230304
init: function (this : Blockly.Block) {
231305
this.appendDummyInput("TITLE").appendField('Parameters');
@@ -421,7 +495,7 @@ export const pythonFromBlock = function (
421495
xfix2 +
422496
returnValue;
423497
code = generator.scrub_(block, code);
424-
generator.addClassMethodDefinition(block.getFieldValue('NAME'), funcName, code);
498+
generator.addClassMethodDefinition(funcName, code);
425499

426500
return '';
427-
}
501+
}

src/editor/extended_python_generator.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,7 @@ export class ExtendedPythonGenerator extends PythonGenerator {
9797
/**
9898
* Add a class method definition.
9999
*/
100-
addClassMethodDefinition(nameFieldValue: string, methodName: string, code: string): void {
101-
this.context.addClassMethodName(nameFieldValue, methodName);
100+
addClassMethodDefinition(methodName: string, code: string): void {
102101
this.classMethods[methodName] = code;
103102
}
104103

src/editor/generator_context.ts

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,6 @@ export class GeneratorContext {
3333
// The exported blocks for the current module.
3434
private exportedBlocks: Block[] = [];
3535

36-
// Key is the mrc_class_method_def block's NAME field, value is the python method name.
37-
private classMethodNames: {[key: string]: string} = Object.create(null);
38-
3936
// Has mechanisms (ie, needs in init)
4037
private hasMechanisms = false;
4138

@@ -46,12 +43,13 @@ export class GeneratorContext {
4643

4744
clear(): void {
4845
this.clearExportedBlocks();
49-
this.clearClassMethodNames();
5046
this.hasMechanisms = false;
5147
}
48+
5249
setHasMechanism():void{
5350
this.hasMechanisms = true;
5451
}
52+
5553
getHasMechanisms():boolean{
5654
return this.hasMechanisms;
5755
}
@@ -101,21 +99,4 @@ export class GeneratorContext {
10199
getExportedBlocks(): Block[] {
102100
return this.exportedBlocks;
103101
}
104-
105-
clearClassMethodNames() {
106-
this.classMethodNames = Object.create(null);
107-
}
108-
109-
addClassMethodName(nameFieldValue: string, methodName: string) {
110-
if (nameFieldValue !== methodName) {
111-
this.classMethodNames[nameFieldValue] = methodName;
112-
}
113-
}
114-
115-
getClassMethodName(nameFieldValue: string): string | null {
116-
if (this.classMethodNames[nameFieldValue]) {
117-
return this.classMethodNames[nameFieldValue];
118-
}
119-
return nameFieldValue;
120-
}
121102
}

src/toolbox/methods_category.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,6 @@ export class MethodsCategory {
118118
functionKind: 'instance_within',
119119
returnType: classMethodDefBlock.mrcReturnType,
120120
args: [],
121-
actualFunctionName: classMethodDefBlock.mrcPythonMethodName,
122121
},
123122
fields: {
124123
FUNC: classMethodDefBlock.getFieldValue('NAME'),

0 commit comments

Comments
 (0)