Skip to content

Commit 7653b28

Browse files
committed
supported MyBlocks.
1 parent 752cc94 commit 7653b28

File tree

2 files changed

+687
-18
lines changed

2 files changed

+687
-18
lines changed

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

Lines changed: 181 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,17 @@ class RubyToBlocksConverter {
4747

4848
reset () {
4949
this._context = {
50-
blocks: {},
51-
blockTypes: {},
5250
currentNode: null,
5351
errors: [],
52+
argumentBlocks: {},
53+
procedureCallBlocks: {},
54+
55+
blocks: {},
56+
blockTypes: {},
5457
localVariables: {},
5558
variables: {},
56-
lists: {}
59+
lists: {},
60+
procedures: {}
5761
};
5862
if (this.vm && this.vm.runtime && this.vm.runtime.getTargetForStage) {
5963
this._loadVariables(this.vm.runtime.getTargetForStage());
@@ -138,12 +142,21 @@ class RubyToBlocksConverter {
138142
}
139143

140144
_saveContext () {
141-
return {
142-
blocks: Object.assign({}, this._context.blocks),
143-
localVariables: Object.assign({}, this._context.localVariables),
144-
variables: Object.assign({}, this._context.variables),
145-
lists: Object.assign({}, this._context.lists)
146-
};
145+
const includes = [
146+
'blocks',
147+
'blockTypes',
148+
'localVariables',
149+
'variables',
150+
'lists',
151+
'procedures'
152+
];
153+
154+
const saved = {};
155+
Object.keys(this._context).filter(k => includes.indexOf(k) >= 0)
156+
.forEach(k => {
157+
saved[k] = Object.assign({}, this._context[k]);
158+
});
159+
return saved;
147160
}
148161

149162
// could not restore attributes.
@@ -379,6 +392,25 @@ class RubyToBlocksConverter {
379392
return this._findOrCreateVariableOrList(name, Variable.LIST_TYPE);
380393
}
381394

395+
_findProcedure (name) {
396+
name = name.toString();
397+
return this._context.procedures[name];
398+
}
399+
400+
_createProcedure (name) {
401+
name = name.toString();
402+
let procedure = this._context.procedures[name];
403+
if (procedure) {
404+
throw new RubyToBlocksConverterError(
405+
this._context.currentNode,
406+
`already defined My Block "${name}".`
407+
);
408+
}
409+
procedure = {};
410+
this._context.procedures[name] = procedure;
411+
return procedure;
412+
}
413+
382414
_getSource (node) {
383415
const expression = node.$loc().$expression();
384416
if (expression === Opal.nil) {
@@ -458,14 +490,13 @@ class RubyToBlocksConverter {
458490
this._setBlockType(block, 'value_boolean');
459491
variable.isBoolean = true;
460492

461-
Object.keys(this._context.blocks)
462-
.filter(id => this._context.blocks[id].opcode === 'argument_reporter_string_number')
463-
.forEach(id => {
493+
if (this._context.argumentBlocks.hasOwnProperty(variable.id)) {
494+
this._context.argumentBlocks[variable.id].forEach(id => {
464495
const b = this._context.blocks[id];
465-
if (b.fields.VALUE.variableId === variable.id) {
466-
b.opcode = 'argument_reporter_boolean';
467-
}
496+
b.opcode = 'argument_reporter_boolean';
497+
this._setBlockType(b, 'value_boolean');
468498
});
499+
}
469500

470501
return true;
471502
}
@@ -795,6 +826,42 @@ class RubyToBlocksConverter {
795826
block = this._createRubyStatementBlock('wait');
796827
}
797828
break;
829+
default:
830+
if (this._findProcedure(name)) {
831+
const procedure = this._findProcedure(name);
832+
if (procedure.argTypes.length === args.length) {
833+
block = this._createBlock('procedures_call', 'statement', {
834+
mutation: {
835+
tagName: 'mutation',
836+
children: [],
837+
proccode: procedure.procCode,
838+
warp: 'false'
839+
}
840+
});
841+
842+
procedure.argTypes.forEach((argType, i) => {
843+
const arg = args[i];
844+
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;
851+
}
852+
} else if (this._isNumberOrBlock(arg) || this._isStringOrBlock(arg)) {
853+
this._addTextInput(block, argumentId, _.isNumber(arg) ? arg.toString() : arg, '');
854+
return;
855+
}
856+
throw new RubyToBlocksConverterError(
857+
this._context.currentNode,
858+
`invalid type of My Block "${name}" argument #${i + 1}`
859+
);
860+
});
861+
block.mutation.argumentids = JSON.stringify(procedure.argumentIds);
862+
}
863+
}
864+
break;
798865
}
799866
} else if (this._isVariableBlock(receiver)) {
800867
switch (name) {
@@ -1286,15 +1353,20 @@ class RubyToBlocksConverter {
12861353
opcode = 'argument_reporter_string_number';
12871354
blockType = 'value';
12881355
}
1289-
return this._createBlock(opcode, blockType, {
1356+
const block = this._createBlock(opcode, blockType, {
12901357
fields: {
12911358
VALUE: {
12921359
name: 'VALUE',
1293-
value: variable.name,
1294-
variableId: variable.id
1360+
value: variable.name
12951361
}
12961362
}
12971363
});
1364+
if (this._context.argumentBlocks.hasOwnProperty(variable.id)) {
1365+
this._context.argumentBlocks[variable.id].push(block.id);
1366+
} else {
1367+
this._context.argumentBlocks[variable.id] = [block.id];
1368+
}
1369+
return block;
12981370
}
12991371

13001372
return varName;
@@ -1364,6 +1436,97 @@ class RubyToBlocksConverter {
13641436
_onGvasgn (node) {
13651437
return this._onIvasgn(node);
13661438
}
1439+
1440+
_onDefs (node) {
1441+
this._checkNumChildren(node, 4);
1442+
1443+
const saved = this._saveContext();
1444+
1445+
const receiver = this._process(node.children[0]);
1446+
if (receiver === Self) {
1447+
const procedureName = node.children[1].toString();
1448+
const block = this._createBlock('procedures_definition', 'hat', {
1449+
topLevel: true
1450+
});
1451+
const procedure = this._createProcedure(procedureName);
1452+
1453+
const customBlock = this._createBlock('procedures_prototype', 'statement', {
1454+
shadow: true
1455+
});
1456+
this._addInput(block, 'custom_block', customBlock);
1457+
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);
1463+
});
1464+
let body = this._process(node.children[3]);
1465+
if (!_.isArray(body)) {
1466+
body = [body];
1467+
}
1468+
if (this._isBlock(body[0])) {
1469+
block.next = body[0].id;
1470+
body[0].parent = block.id;
1471+
}
1472+
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');
1489+
}
1490+
return this._createBlock(opcode, 'value', {
1491+
fields: {
1492+
VALUE: {
1493+
name: 'VALUE',
1494+
value: argName
1495+
}
1496+
},
1497+
shadow: true
1498+
});
1499+
});
1500+
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+
});
1510+
1511+
customBlock.mutation = {
1512+
argumentdefaults: JSON.stringify(procedure.argDefaults),
1513+
argumentids: JSON.stringify(procedure.argumentIds),
1514+
argumentnames: JSON.stringify(procedure.argNames),
1515+
children: [],
1516+
proccode: procedure.procCode,
1517+
tagName: 'mutation',
1518+
warp: 'false'
1519+
};
1520+
1521+
this._restoreContext({localVariables: saved.localVariables});
1522+
1523+
return block;
1524+
}
1525+
1526+
this._restoreContext(saved);
1527+
1528+
return this._createRubyStatementBlock(this._getSource(node));
1529+
}
13671530
}
13681531

13691532
const targetCodeToBlocks = function (vm, target, code, errors = []) {

0 commit comments

Comments
 (0)