Skip to content

Commit 6ce5574

Browse files
authored
Merge pull request #151 from smalruby/issues/134_control
convert Control methods(wait, repeat, forever) to Blocks. refs #134
2 parents b4ed3da + fbd1eca commit 6ce5574

File tree

5 files changed

+477
-53
lines changed

5 files changed

+477
-53
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
"eslint-config-scratch": "^5.0.0",
5858
"eslint-plugin-import": "^2.8.0",
5959
"eslint-plugin-react": "^7.5.1",
60+
"eslint-watch": "^4.0.2",
6061
"file-loader": "2.0.0",
6162
"get-float-time-domain-data": "0.1.0",
6263
"get-user-media-promise": "1.1.4",

src/lib/ruby-generator/control.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export default function (Generator) {
1010
};
1111

1212
Generator.control_repeat = function (block) {
13-
const times = Generator.valueToCode(block, 'TIMES', Generator.ORDER_NONE) || 0;
13+
const times = Generator.valueToCode(block, 'TIMES', Generator.ORDER_ATOMIC) || 0;
1414
const branch = Generator.statementToCode(block, 'SUBSTACK') || '';
1515
return `${times}.times do\n${branch}${Generator.INDENT}wait\nend\n`;
1616
};
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/* global Opal */
2+
3+
/* eslint-disable no-invalid-this */
4+
const createControlRepeatBlock = function (times, body) {
5+
const block = this._createBlock('control_repeat', 'statement');
6+
this._addNumberInput(block, 'TIMES', 'math_whole_number', times, 10);
7+
this._addSubstack(block, body);
8+
return block;
9+
};
10+
/* eslint-enable no-invalid-this */
11+
12+
/**
13+
* Control converter
14+
*/
15+
const ControlConverter = {
16+
// eslint-disable-next-line no-unused-vars
17+
onSend: function (receiver, name, args, rubyBlockArgs, rubyBlock) {
18+
let block;
19+
if (this._isSelf(receiver) || receiver === Opal.nil) {
20+
switch (name) {
21+
case 'sleep':
22+
if (args.length === 1 && this._isNumberOrBlock(args[0])) {
23+
block = this._createBlock('control_wait', 'statement');
24+
this._addNumberInput(block, 'DURATION', 'math_positive_number', args[0], 1);
25+
}
26+
break;
27+
case 'repeat':
28+
if (args.length === 1 && this._isNumberOrBlock(args[0]) &&
29+
rubyBlockArgs && rubyBlockArgs.length === 0) {
30+
block = createControlRepeatBlock.call(this, args[0], rubyBlock);
31+
}
32+
break;
33+
case 'loop':
34+
case 'forever':
35+
if (args.length === 0 &&
36+
rubyBlockArgs && rubyBlockArgs.length === 0 &&
37+
rubyBlock && (name !== 'loop' || this._popWaitBlock(rubyBlock))) {
38+
block = this._createBlock('control_forever', 'statement');
39+
this._addSubstack(block, rubyBlock);
40+
}
41+
break;
42+
}
43+
} else if (this._isNumberOrBlock(receiver)) {
44+
switch (name) {
45+
case 'times':
46+
if (args.length === 0 &&
47+
rubyBlockArgs && rubyBlockArgs.length === 0 &&
48+
rubyBlock && rubyBlock.length >= 1) {
49+
const waitBlock = this._popWaitBlock(rubyBlock);
50+
if (waitBlock) {
51+
block = createControlRepeatBlock.call(this, receiver, rubyBlock);
52+
}
53+
}
54+
break;
55+
}
56+
}
57+
return block;
58+
}
59+
};
60+
61+
export default ControlConverter;

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

Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import Blockly from 'scratch-blocks';
55
import RubyParser from '../ruby-parser';
66
import Variable from 'scratch-vm/src/engine/variable';
77

8+
import ControlConverter from './control';
9+
810
/**
911
* Class for Ruby's self for detecting self.
1012
*/
@@ -26,6 +28,9 @@ class RubyToBlocksConverterError {
2628
class RubyToBlocksConverter {
2729
constructor (vm) {
2830
this.vm = vm;
31+
this._converters = [
32+
ControlConverter
33+
];
2934
this.reset();
3035
}
3136

@@ -141,6 +146,19 @@ class RubyToBlocksConverter {
141146
this.vm.emitWorkspaceUpdate();
142147
}
143148

149+
_callConvertersHandler (handlerName) {
150+
for (let i = 0; i < this._converters.length; i++) {
151+
const converter = this._converters[i];
152+
if (converter.hasOwnProperty(handlerName)) {
153+
const block = converter[handlerName].apply(this, Array.prototype.slice.call(arguments, 1));
154+
if (block) {
155+
return block;
156+
}
157+
}
158+
}
159+
return null;
160+
}
161+
144162
_saveContext () {
145163
const includes = [
146164
'blocks',
@@ -464,6 +482,10 @@ class RubyToBlocksConverter {
464482
return block;
465483
}
466484

485+
_isSelf (block) {
486+
return block === Self;
487+
}
488+
467489
_isBlock (block) {
468490
try {
469491
return block.hasOwnProperty('opcode');
@@ -507,7 +529,13 @@ class RubyToBlocksConverter {
507529
}
508530

509531
_isFalseOrBooleanBlock (block) {
510-
if (block === false || this._getBlockType(block) === 'value_boolean') {
532+
if (block === false) {
533+
return true;
534+
}
535+
if (!this._isBlock(block)) {
536+
return false;
537+
}
538+
if (this._getBlockType(block) === 'value_boolean') {
511539
return true;
512540
}
513541
if (block.opcode === 'argument_reporter_string_number') {
@@ -553,11 +581,15 @@ class RubyToBlocksConverter {
553581
let prevBlock = null;
554582
const blocks = [];
555583
let terminated = false;
584+
let firstBlock;
556585
node.children.forEach(childNode => {
557586
const block = this._process(childNode);
558587
if (!block) {
559588
return;
560589
}
590+
if (!firstBlock) {
591+
firstBlock = block;
592+
}
561593
switch (this._getBlockType(block)) {
562594
case 'statement':
563595
if (prevBlock) {
@@ -588,6 +620,12 @@ class RubyToBlocksConverter {
588620
break;
589621
}
590622
});
623+
if (blocks.length === 0 && firstBlock) {
624+
if (/^value/.test(this._getBlockType(firstBlock))) {
625+
firstBlock.topLevel = false;
626+
}
627+
blocks.push(firstBlock);
628+
}
591629
return blocks;
592630
}
593631

@@ -600,7 +638,10 @@ class RubyToBlocksConverter {
600638
_onSend (node, rubyBlockArgsNode, rubyBlockNode) {
601639
const saved = this._saveContext();
602640

603-
const receiver = this._process(node.children[0]);
641+
let receiver = this._process(node.children[0]);
642+
if (_.isArray(receiver) && receiver.length === 1) {
643+
receiver = receiver[0];
644+
}
604645
const name = node.children[1].toString();
605646
const args = node.children.slice(2).map(childNode => this._process(childNode));
606647

@@ -750,15 +791,6 @@ class RubyToBlocksConverter {
750791
}
751792
}
752793
break;
753-
case 'loop':
754-
if (args.length === 0) {
755-
const waitBlock = this._popWaitBlock(rubyBlock);
756-
if (waitBlock) {
757-
block = this._createBlock('control_forever', 'statement');
758-
this._addSubstack(block, rubyBlock);
759-
}
760-
}
761-
break;
762794
case 'touching?':
763795
if (args.length === 1 && _.isString(args[0])) {
764796
block = this._createBlock('sensing_touchingobject', 'value_boolean');
@@ -1143,6 +1175,11 @@ class RubyToBlocksConverter {
11431175
break;
11441176
}
11451177
}
1178+
1179+
if (!block) {
1180+
block = this._callConvertersHandler('onSend', receiver, name, args, rubyBlockArgs, rubyBlock);
1181+
}
1182+
11461183
if (!block) {
11471184
this._restoreContext(saved);
11481185

0 commit comments

Comments
 (0)