Skip to content

Commit ef139ae

Browse files
authored
Merge pull request #161 from smalruby/issues/134_event2
convert Ruby to Event blocks. (refs #134))
2 parents 40870d7 + 6f7959c commit ef139ae

File tree

8 files changed

+663
-41
lines changed

8 files changed

+663
-41
lines changed

src/lib/ruby-generator/event.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export default function (Generator) {
5656

5757
Generator.event_whenstageclicked = function (block) {
5858
block.isStatement = true;
59-
return `Stage.when(:stage_clicked) do\n`;
59+
return `Stage.when(:clicked) do\n`;
6060
};
6161

6262
return Generator;

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

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/* global Opal */
22
import _ from 'lodash';
3+
import Variable from 'scratch-vm/src/engine/variable';
34

45
const KeyOptions = [
56
'space',
@@ -46,6 +47,11 @@ const KeyOptions = [
4647
'9'
4748
];
4849

50+
const GreaterThanMenu = [
51+
'LOUDNESS',
52+
'TIMER'
53+
];
54+
4955
/**
5056
* Event converter
5157
*/
@@ -68,7 +74,11 @@ const EventConverter = {
6874
opcode = 'event_whenflagclicked';
6975
break;
7076
case 'clicked':
71-
opcode = 'event_whenthisspriteclicked';
77+
if (this._context.target && this._context.target.isStage) {
78+
opcode = 'event_whenstageclicked';
79+
} else {
80+
opcode = 'event_whenthisspriteclicked';
81+
}
7282
break;
7383
}
7484
block = this._createBlock(opcode, 'hat');
@@ -82,8 +92,73 @@ const EventConverter = {
8292
this._setParent(rubyBlock, block);
8393
}
8494
break;
95+
case 'backdrop_switches':
96+
if (args.length === 2 && this._isString(args[1])) {
97+
block = this._createBlock('event_whenbackdropswitchesto', 'hat');
98+
this._addField(block, 'BACKDROP', args[1]);
99+
this._setParent(rubyBlock, block);
100+
}
101+
break;
102+
case 'greater_than':
103+
if (args.length === 3 &&
104+
this._isString(args[1]) && GreaterThanMenu.indexOf(args[1].toString()) >= 0 &&
105+
this._isNumberOrBlock(args[2])) {
106+
block = this._createBlock('event_whengreaterthan', 'hat');
107+
this._addField(block, 'WHENGREATERTHANMENU', args[1]);
108+
this._addNumberInput(block, 'VALUE', 'math_number', args[2], 10);
109+
this._setParent(rubyBlock, block);
110+
}
111+
break;
112+
case 'receive':
113+
if (args.length === 2 && this._isString(args[1])) {
114+
const broadcastMsg = this._lookupOrCreateBroadcastMsg(args[1]);
115+
block = this._createBlock('event_whenbroadcastreceived', 'hat');
116+
this._addField(block, 'BROADCAST_OPTION', broadcastMsg.name, {
117+
id: broadcastMsg.id,
118+
variableType: Variable.BROADCAST_MESSAGE_TYPE
119+
});
120+
this._setParent(rubyBlock, block);
121+
}
122+
break;
123+
}
124+
} else if (this._isSelf(receiver) || receiver === Opal.nil) {
125+
switch (name) {
126+
case 'broadcast':
127+
case 'broadcast_and_wait':
128+
if (args.length === 1 && this._isStringOrBlock(args[0]) && !rubyBlock) {
129+
let opcode;
130+
if (name === 'broadcast') {
131+
opcode = 'event_broadcast';
132+
} else {
133+
opcode = 'event_broadcastandwait';
134+
}
135+
const menuBlock = this._createBlock('event_broadcast_menu', 'value', {
136+
shadow: true
137+
});
138+
let broadcastMsg;
139+
let inputBlock;
140+
let shadowBlock;
141+
if (this._isString(args[0])) {
142+
broadcastMsg = this._lookupOrCreateBroadcastMsg(args[0]);
143+
inputBlock = menuBlock;
144+
shadowBlock = menuBlock;
145+
} else {
146+
broadcastMsg = this._defaultBroadcastMsg();
147+
inputBlock = args[0];
148+
shadowBlock = menuBlock;
149+
}
150+
this._addField(menuBlock, 'BROADCAST_OPTION', broadcastMsg.name, {
151+
id: broadcastMsg.id,
152+
variableType: Variable.BROADCAST_MESSAGE_TYPE
153+
});
154+
155+
block = this._createBlock(opcode, 'statement');
156+
this._addInput(block, 'BROADCAST_INPUT', inputBlock, shadowBlock);
157+
}
158+
break;
85159
}
86160
}
161+
87162
return block;
88163
}
89164
};

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

Lines changed: 58 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ class RubyToBlocksConverter {
5252
return this._context.lists;
5353
}
5454

55+
get broadcastMsgs () {
56+
return this._context.broadcastMsgs;
57+
}
58+
5559
reset () {
5660
this._context = {
5761
currentNode: null,
@@ -64,6 +68,7 @@ class RubyToBlocksConverter {
6468
localVariables: {},
6569
variables: {},
6670
lists: {},
71+
broadcastMsgs: {},
6772
procedures: {}
6873
};
6974
if (this.vm && this.vm.runtime && this.vm.runtime.getTargetForStage) {
@@ -73,6 +78,7 @@ class RubyToBlocksConverter {
7378

7479
targetCodeToBlocks (target, code) {
7580
this.reset();
81+
this._setTarget(target);
7682
this._loadVariables(target);
7783
try {
7884
const root = RubyParser.$parse(code);
@@ -139,6 +145,13 @@ class RubyToBlocksConverter {
139145
});
140146
});
141147

148+
Object.keys(this._context.broadcastMsgs).forEach(name => {
149+
const broadcastMsg = this._context.broadcastMsgs[name];
150+
if (!stage.variables.hasOwnProperty(broadcastMsg.id)) {
151+
stage.createVariable(broadcastMsg.id, broadcastMsg.name, Variable.BROADCAST_MESSAGE_TYPE);
152+
}
153+
});
154+
142155
Object.keys(target.blocks._blocks).forEach(blockId => {
143156
target.blocks.deleteBlock(blockId);
144157
});
@@ -169,6 +182,7 @@ class RubyToBlocksConverter {
169182
'localVariables',
170183
'variables',
171184
'lists',
185+
'broadcastMsgs',
172186
'procedures'
173187
];
174188

@@ -202,6 +216,10 @@ class RubyToBlocksConverter {
202216
});
203217
}
204218

219+
_setTarget (target) {
220+
this._context.target = target;
221+
}
222+
205223
_loadVariables (target) {
206224
if (!target || !target.variables) {
207225
return;
@@ -217,6 +235,8 @@ class RubyToBlocksConverter {
217235
let storeName;
218236
if (variable.type === Variable.SCALAR_TYPE) {
219237
storeName = 'variables';
238+
} else if (variable.type === Variable.BROADCAST_MESSAGE_TYPE) {
239+
storeName = 'broadcastMsgs';
220240
} else {
221241
storeName = 'lists';
222242
}
@@ -394,14 +414,14 @@ class RubyToBlocksConverter {
394414
return block;
395415
}
396416

397-
_addField (block, name, value) {
417+
_addField (block, name, value, attributes = {}) {
398418
if (!this._isBlock(block)) {
399419
return;
400420
}
401-
block.fields[name] = {
421+
block.fields[name] = Object.assign({
402422
name: name,
403423
value: value.toString()
404-
};
424+
}, attributes);
405425
}
406426

407427
_addInput (block, name, inputBlock, shadowBlock) {
@@ -456,7 +476,7 @@ class RubyToBlocksConverter {
456476
};
457477
}
458478

459-
_findOrCreateVariableOrList (name, type) {
479+
_lookupOrCreateVariableOrList (name, type) {
460480
name = name.toString();
461481
let scope;
462482
let varName;
@@ -493,15 +513,42 @@ class RubyToBlocksConverter {
493513
return variable;
494514
}
495515

496-
_findOrCreateVariable (name) {
497-
return this._findOrCreateVariableOrList(name, Variable.SCALAR_TYPE);
516+
_lookupOrCreateVariable (name) {
517+
return this._lookupOrCreateVariableOrList(name, Variable.SCALAR_TYPE);
498518
}
499519

500-
_findOrCreateList (name) {
501-
return this._findOrCreateVariableOrList(name, Variable.LIST_TYPE);
520+
_lookupOrCreateList (name) {
521+
return this._lookupOrCreateVariableOrList(name, Variable.LIST_TYPE);
522+
}
523+
524+
_lookupOrCreateBroadcastMsg (name) {
525+
name = name.toString();
526+
const key = name.toLowerCase();
527+
let broadcastMsg = this._context.broadcastMsgs[key];
528+
if (!broadcastMsg) {
529+
broadcastMsg = {
530+
id: Blockly.utils.genUid(),
531+
name: name,
532+
scope: 'global'
533+
};
534+
this._context.broadcastMsgs[key] = broadcastMsg;
535+
}
536+
return broadcastMsg;
537+
}
538+
539+
_defaultBroadcastMsg () {
540+
const defaultName = 'message1';
541+
const keys = Object.keys(this._context.broadcastMsgs);
542+
if (keys.length === 0) {
543+
return this._lookupOrCreateBroadcastMsg(defaultName);
544+
}
545+
if (this._context.broadcastMsgs.hasOwnProperty(defaultName)) {
546+
return this._context.broadcastMsgs[defaultName];
547+
}
548+
return this._context.broadcastMsgs[keys[0]];
502549
}
503550

504-
_findProcedure (name) {
551+
_lookupProcedure (name) {
505552
name = name.toString();
506553
return this._context.procedures[name];
507554
}
@@ -931,7 +978,7 @@ class RubyToBlocksConverter {
931978
_onVar (node, scope) {
932979
this._checkNumChildren(node, 1);
933980

934-
const variable = this._findOrCreateVariable(node.children[0]);
981+
const variable = this._lookupOrCreateVariable(node.children[0]);
935982
const block = this._callConvertersHandler('onVar', scope, variable);
936983
if (block) {
937984
return block;
@@ -972,7 +1019,7 @@ class RubyToBlocksConverter {
9721019

9731020
const saved = this._saveContext();
9741021

975-
const variable = this._findOrCreateVariable(node.children[0]);
1022+
const variable = this._lookupOrCreateVariable(node.children[0]);
9761023
const rh = this._process(node.children[1]);
9771024
let block = this._callConvertersHandler('onVasgn', scope, variable, rh);
9781025
if (!block) {

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const MyBlocksConverter = {
1111
onSend: function (receiver, name, args, rubyBlockArgs, rubyBlock) {
1212
let block;
1313
if (this._isSelf(receiver) || receiver === Opal.nil) {
14-
const procedure = this._findProcedure(name);
14+
const procedure = this._lookupProcedure(name);
1515
if (procedure) {
1616
if (procedure.argumentIds.length === args.length) {
1717
block = this._createBlock('procedures_call', 'statement', {
@@ -109,7 +109,7 @@ const MyBlocksConverter = {
109109
this._process(node.children[2]).forEach(n => {
110110
n = n.toString();
111111
procedure.argumentNames.push(n);
112-
procedure.argumentVariables.push(this._findOrCreateVariable(n));
112+
procedure.argumentVariables.push(this._lookupOrCreateVariable(n));
113113
procedure.procCode.push('%s');
114114
procedure.argumentDefaults.push('');
115115
const inputId = Blockly.utils.genUid();

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ const VariablesConverter = {
2222
opcode = 'data_hidevariable';
2323
break;
2424
}
25-
const variable = this._findOrCreateVariable(args[0]);
25+
const variable = this._lookupOrCreateVariable(args[0]);
2626
if (variable.scope !== 'local') {
2727
block = this._createBlock(opcode, 'statement', {
2828
fields: {
@@ -57,7 +57,7 @@ const VariablesConverter = {
5757
blockType = 'statement';
5858
break;
5959
}
60-
const variable = this._findOrCreateList(args[0]);
60+
const variable = this._lookupOrCreateList(args[0]);
6161
if (variable.scope !== 'local') {
6262
block = this._createBlock(opcode, blockType, {
6363
fields: {
@@ -147,7 +147,7 @@ const VariablesConverter = {
147147
onOpAsgn: function (lh, operator, rh) {
148148
let block;
149149
if (operator === '+' && this._isString(lh) && this._isNumberOrBlock(rh)) {
150-
const variable = this._findOrCreateVariable(lh);
150+
const variable = this._lookupOrCreateVariable(lh);
151151
if (variable.scope !== 'local') {
152152
block = this._createBlock('data_changevariableby', 'statement', {
153153
fields: {

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

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,13 @@ const expectToEqualFields = function (context, actualFields, expectedFieldsInfo)
4848
expectedType = Variable.LIST_TYPE;
4949
}
5050
expect(field).toHaveProperty('variableType', expectedType);
51+
expect(field).toHaveProperty('id', variable.id);
52+
} else if (expectedField.hasOwnProperty('broadcastMsg')) {
53+
expect(context.broadcastMsgs).toHaveProperty(expectedField.broadcastMsg);
54+
const broadcastMsg = context.broadcastMsgs[expectedField.broadcastMsg];
55+
expect(broadcastMsg.name).toEqual(expectedField.broadcastMsg);
56+
expect(field).toHaveProperty('id', broadcastMsg.id);
57+
expect(field).toHaveProperty('variableType', 'broadcast_msg');
5158
} else {
5259
expect(field.id).toEqual(void 0);
5360
expect(field.value).toEqual(expectedField.value);
@@ -205,7 +212,8 @@ const expectToEqualBlocks = function (converter, expectedBlocksInfo) {
205212
converter: converter,
206213
blocks: blocks,
207214
variables: converter.variables,
208-
lists: converter.lists
215+
lists: converter.lists,
216+
broadcastMsgs: converter.broadcastMsgs
209217
};
210218

211219
const scripts = blocks.getScripts();
@@ -261,14 +269,23 @@ const fieldsToExpected = function (context, fields) {
261269
return Object.keys(fields).map(name => {
262270
const field = fields[name];
263271
if (field.id) {
264-
const varName = field.value;
272+
let varName = field.value;
265273
let storeName;
266274
if (field.variableType === Variable.SCALAR_TYPE) {
267275
storeName = 'variables';
276+
} else if (field.variableType === Variable.BROADCAST_MESSAGE_TYPE) {
277+
storeName = 'broadcastMsgs';
278+
varName = varName.toLowerCase();
268279
} else {
269280
storeName = 'lists';
270281
}
271282
const variable = context[storeName][varName];
283+
if (field.variableType === Variable.BROADCAST_MESSAGE_TYPE) {
284+
return {
285+
name: field.name,
286+
broadcastMsg: field.value
287+
};
288+
}
272289
let scope;
273290
if (variable.scope === 'global') {
274291
scope = '$';
@@ -277,14 +294,14 @@ const fieldsToExpected = function (context, fields) {
277294
} else {
278295
scope = '';
279296
}
280-
if (variable.type === Variable.SCALAR_TYPE) {
297+
if (field.variableType === Variable.SCALAR_TYPE) {
281298
return {
282-
name: 'VARIABLE',
299+
name: field.name,
283300
variable: `${scope}${varName}`
284301
};
285302
}
286303
return {
287-
name: 'LIST',
304+
name: field.name,
288305
list: `${scope}${varName}`
289306
};
290307
}
@@ -375,7 +392,8 @@ const rubyToExpected = function (converter, target, code) {
375392
converter: converter,
376393
blocks: blocks,
377394
variables: converter.variables,
378-
lists: converter.lists
395+
lists: converter.lists,
396+
broadcastMsgs: converter.broadcastMsgs
379397
};
380398

381399
const scripts = blocks.getScripts();

0 commit comments

Comments
 (0)