Skip to content

Commit 87cf9da

Browse files
committed
supported recursive call.
1 parent 7653b28 commit 87cf9da

File tree

3 files changed

+179
-72
lines changed

3 files changed

+179
-72
lines changed

src/lib/ruby-to-blocks-converter/index.js

Lines changed: 92 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,9 @@ class RubyToBlocksConverter {
406406
`already defined My Block "${name}".`
407407
);
408408
}
409-
procedure = {};
409+
procedure = {
410+
id: Blockly.utils.genUid()
411+
};
410412
this._context.procedures[name] = procedure;
411413
return procedure;
412414
}
@@ -478,26 +480,34 @@ class RubyToBlocksConverter {
478480
return _.isString(stringOrBlock) || this._isValueBlock(stringOrBlock);
479481
}
480482

483+
_changeToBooleanArgument (varName) {
484+
varName = varName.toString();
485+
const variable = this._context.localVariables[varName];
486+
if (!variable) {
487+
return false;
488+
}
489+
490+
variable.isBoolean = true;
491+
492+
if (this._context.argumentBlocks.hasOwnProperty(variable.id)) {
493+
this._context.argumentBlocks[variable.id].forEach(id => {
494+
const b = this._context.blocks[id];
495+
b.opcode = 'argument_reporter_boolean';
496+
this._setBlockType(b, 'value_boolean');
497+
});
498+
}
499+
return true;
500+
}
501+
481502
_isFalseOrBooleanBlock (block) {
482503
if (block === false || this._getBlockType(block) === 'value_boolean') {
483504
return true;
484505
}
485506
if (block.opcode === 'argument_reporter_string_number') {
486507
const varName = block.fields.VALUE.value;
487-
const variable = this._context.localVariables[varName];
488-
if (variable) {
508+
if (this._changeToBooleanArgument(varName)) {
489509
block.opcode = 'argument_reporter_boolean';
490510
this._setBlockType(block, 'value_boolean');
491-
variable.isBoolean = true;
492-
493-
if (this._context.argumentBlocks.hasOwnProperty(variable.id)) {
494-
this._context.argumentBlocks[variable.id].forEach(id => {
495-
const b = this._context.blocks[id];
496-
b.opcode = 'argument_reporter_boolean';
497-
this._setBlockType(b, 'value_boolean');
498-
});
499-
}
500-
501511
return true;
502512
}
503513
}
@@ -829,26 +839,31 @@ class RubyToBlocksConverter {
829839
default:
830840
if (this._findProcedure(name)) {
831841
const procedure = this._findProcedure(name);
832-
if (procedure.argTypes.length === args.length) {
842+
if (procedure.argumentIds.length === args.length) {
833843
block = this._createBlock('procedures_call', 'statement', {
834844
mutation: {
835-
tagName: 'mutation',
845+
argumentids: JSON.stringify(procedure.argumentIds),
836846
children: [],
837-
proccode: procedure.procCode,
847+
proccode: procedure.procCode.join(' '),
848+
tagName: 'mutation',
838849
warp: 'false'
839850
}
840851
});
841852

842-
procedure.argTypes.forEach((argType, i) => {
843-
const arg = args[i];
853+
if (this._context.procedureCallBlocks.hasOwnProperty(procedure.id)) {
854+
this._context.procedureCallBlocks[procedure.id].push(block.id);
855+
} else {
856+
this._context.procedureCallBlocks[procedure.id] = [block.id];
857+
}
858+
859+
args.forEach((arg, i) => {
844860
const argumentId = procedure.argumentIds[i];
845-
if (argType === 'boolean') {
846-
if (this._isFalseOrBooleanBlock(arg)) {
847-
if (arg !== false) {
848-
this._addInput(block, argumentId, arg, null);
849-
}
850-
return;
861+
if (this._isFalseOrBooleanBlock(arg)) {
862+
if (arg !== false) {
863+
this._addInput(block, argumentId, arg, null);
851864
}
865+
this._changeToBooleanArgument(procedure.argumentNames[i]);
866+
return;
852867
} else if (this._isNumberOrBlock(arg) || this._isStringOrBlock(arg)) {
853868
this._addTextInput(block, argumentId, _.isNumber(arg) ? arg.toString() : arg, '');
854869
return;
@@ -858,7 +873,6 @@ class RubyToBlocksConverter {
858873
`invalid type of My Block "${name}" argument #${i + 1}`
859874
);
860875
});
861-
block.mutation.argumentids = JSON.stringify(procedure.argumentIds);
862876
}
863877
}
864878
break;
@@ -1449,18 +1463,38 @@ class RubyToBlocksConverter {
14491463
topLevel: true
14501464
});
14511465
const procedure = this._createProcedure(procedureName);
1466+
procedure.procCode = [procedureName];
14521467

14531468
const customBlock = this._createBlock('procedures_prototype', 'statement', {
14541469
shadow: true
14551470
});
14561471
this._addInput(block, 'custom_block', customBlock);
14571472

1458-
const args = this._process(node.children[2]);
1459-
1460-
// define as local variable for guessing argument type is bool or not.
1461-
args.forEach(argName => {
1462-
this._findOrCreateVariable(argName);
1473+
procedure.argumentNames = this._process(node.children[2]);
1474+
procedure.argumentDefaults = [];
1475+
procedure.argumentIds = [];
1476+
procedure.argumentVariables = [];
1477+
procedure.argumentBlocks = [];
1478+
this._context.localVariables = {};
1479+
procedure.argumentNames.forEach(n => {
1480+
procedure.argumentVariables.push(this._findOrCreateVariable(n));
1481+
procedure.procCode.push('%s');
1482+
procedure.argumentDefaults.push('');
1483+
const inputId = Blockly.utils.genUid();
1484+
procedure.argumentIds.push(inputId);
1485+
const inputBlock = this._createBlock('argument_reporter_string_number', 'value', {
1486+
fields: {
1487+
VALUE: {
1488+
name: 'VALUE',
1489+
value: n
1490+
}
1491+
},
1492+
shadow: true
1493+
});
1494+
this._addInput(customBlock, inputId, inputBlock);
1495+
procedure.argumentBlocks.push(inputBlock);
14631496
});
1497+
14641498
let body = this._process(node.children[3]);
14651499
if (!_.isArray(body)) {
14661500
body = [body];
@@ -1470,50 +1504,41 @@ class RubyToBlocksConverter {
14701504
body[0].parent = block.id;
14711505
}
14721506

1473-
const procCode = [procedureName];
1474-
const argDefaults = [];
1475-
const argTypes = [];
1476-
const argBlocks = args.map(argName => {
1477-
const variable = this._findOrCreateVariable(argName);
1478-
let opcode;
1479-
if (variable.isBoolean) {
1480-
opcode = 'argument_reporter_boolean';
1481-
procCode.push('%b');
1482-
argDefaults.push('false');
1483-
argTypes.push('boolean');
1484-
} else {
1485-
opcode = 'argument_reporter_string_number';
1486-
procCode.push('%s');
1487-
argDefaults.push('');
1488-
argTypes.push('string_number');
1507+
const booleanIndexes = [];
1508+
procedure.argumentVariables.forEach((v, i) => {
1509+
if (v.isBoolean) {
1510+
booleanIndexes.push(i);
1511+
procedure.procCode[i + 1] = '%b';
1512+
procedure.argumentDefaults[i] = 'false';
1513+
procedure.argumentBlocks[i].opcode = 'argument_reporter_boolean';
1514+
this._setBlockType(procedure.argumentBlocks[i], 'value_boolean');
14891515
}
1490-
return this._createBlock(opcode, 'value', {
1491-
fields: {
1492-
VALUE: {
1493-
name: 'VALUE',
1494-
value: argName
1495-
}
1496-
},
1497-
shadow: true
1498-
});
14991516
});
15001517

1501-
procedure.procCode = procCode.join(' ');
1502-
procedure.argDefaults = argDefaults;
1503-
procedure.argNames = args;
1504-
procedure.argTypes = argTypes;
1505-
procedure.argumentIds = argBlocks.map(argBlock => {
1506-
const id = Blockly.utils.genUid();
1507-
this._addInput(customBlock, id, argBlock);
1508-
return id;
1509-
});
1518+
if (booleanIndexes.length > 0 &&
1519+
this._context.procedureCallBlocks.hasOwnProperty(procedure.id)) {
1520+
this._context.procedureCallBlocks[procedure.id].forEach(id => {
1521+
const b = this._context.blocks[id];
1522+
b.mutation.proccode = procedure.procCode.join(' ');
1523+
booleanIndexes.forEach(booleanIndex => {
1524+
const input = b.inputs[procedure.argumentIds[booleanIndex]];
1525+
const inputBlock = this._context.blocks[input.block];
1526+
if (inputBlock) {
1527+
if (!inputBlock.shadow && input.shadow) {
1528+
delete this._context.blocks[input.shadow];
1529+
input.shadow = null;
1530+
}
1531+
}
1532+
});
1533+
});
1534+
}
15101535

15111536
customBlock.mutation = {
1512-
argumentdefaults: JSON.stringify(procedure.argDefaults),
1537+
argumentdefaults: JSON.stringify(procedure.argumentDefaults),
15131538
argumentids: JSON.stringify(procedure.argumentIds),
1514-
argumentnames: JSON.stringify(procedure.argNames),
1539+
argumentnames: JSON.stringify(procedure.argumentNames),
15151540
children: [],
1516-
proccode: procedure.procCode,
1541+
proccode: procedure.procCode.join(' '),
15171542
tagName: 'mutation',
15181543
warp: 'false'
15191544
};

test/helpers/expect-to-equal-blocks.js

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -151,11 +151,6 @@ const expectToEqualMutation = function (context, block, actualMutation, expected
151151
const actualArgBlock = blocks.getBlock(actualArgInput.block);
152152
// eslint-disable-next-line no-use-before-define
153153
expectToEqualBlock(context, block.id, actualArgBlock, expectedArgBlock);
154-
if (actualArgBlock.shadow) {
155-
expect(actualArgInput).toHaveProperty('shadow', actualArgBlock.id);
156-
} else {
157-
expect(actualArgInput).toHaveProperty('shadow', null);
158-
}
159154
}
160155
});
161156
expect(actualArgInputIds).toHaveLength(expectedMutationInfo[key].length);

test/unit/lib/ruby-to-blocks-converter/my-blocks.test.js

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,4 +503,91 @@ describe('RubyToBlocksConverter/My Blocks', () => {
503503
];
504504
convertAndExpectToEqualBlocks(converter, target, code, expected);
505505
});
506+
507+
test('procedures_call recursive', () => {
508+
const code = `
509+
def self.made_block(arg1)
510+
made_block(arg1 - 1)
511+
end
512+
513+
made_block(12)
514+
`;
515+
const expected = [
516+
{
517+
opcode: 'procedures_definition',
518+
inputs: [
519+
{
520+
name: 'custom_block',
521+
block: {
522+
opcode: 'procedures_prototype',
523+
mutation: {
524+
proccode: 'made_block %s',
525+
arguments: [
526+
{
527+
name: 'arg1',
528+
type: 'string_number'
529+
}
530+
]
531+
},
532+
shadow: true
533+
}
534+
}
535+
],
536+
next: {
537+
opcode: 'procedures_call',
538+
mutation: {
539+
proccode: 'made_block %s',
540+
argument_blocks: [
541+
{
542+
opcode: 'operator_subtract',
543+
inputs: [
544+
{
545+
name: 'NUM1',
546+
block: {
547+
opcode: 'argument_reporter_string_number',
548+
fields: [
549+
{
550+
name: 'VALUE',
551+
value: 'arg1'
552+
}
553+
]
554+
},
555+
shadow: expectedInfo.makeNumber('')
556+
},
557+
{
558+
name: 'NUM2',
559+
block: expectedInfo.makeNumber(1)
560+
}
561+
]
562+
}
563+
]
564+
}
565+
}
566+
},
567+
{
568+
opcode: 'procedures_call',
569+
mutation: {
570+
proccode: 'made_block %s',
571+
argument_blocks: [
572+
expectedInfo.makeText('12')
573+
]
574+
}
575+
}
576+
];
577+
convertAndExpectToEqualBlocks(converter, target, code, expected);
578+
});
579+
580+
test.only('error if argument type miss match', () => {
581+
const code = `
582+
def self.made_block(arg1)
583+
if arg1
584+
end
585+
end
586+
587+
made_block(12)
588+
`;
589+
const res = converter.targetCodeToBlocks(target, code);
590+
expect(converter.errors).toHaveLength(1);
591+
expect(res).toBeFalsy();
592+
});
506593
});

0 commit comments

Comments
 (0)