diff --git a/packages/scratch-gui/src/lib/ruby-generator/smalruby-ruby.js b/packages/scratch-gui/src/lib/ruby-generator/smalruby-ruby.js index 8718c615454..b5589cf8583 100644 --- a/packages/scratch-gui/src/lib/ruby-generator/smalruby-ruby.js +++ b/packages/scratch-gui/src/lib/ruby-generator/smalruby-ruby.js @@ -55,6 +55,18 @@ export default function (Generator) { Generator.smalrubyRuby_arrayMethod = generateMethodCall; Generator.smalrubyRuby_hashMethod = generateMethodCall; + // --- Array method with block (CONDITIONAL, C-shape) --- + Generator.smalrubyRuby_arrayMethodWithBlock = function (block) { + const order = Generator.ORDER_FUNCTION_CALL; + const receiver = + Generator.valueToCode(block, 'RECEIVER', order) || + Generator.quote_(''); + const method = Generator.getFieldValue(block, 'METHOD') || 'each'; + const branch = Generator.statementToCode(block, 'SUBSTACK') || ''; + block.isStatement = true; + return `${receiver}.${method} do\n${branch}`; + }; + // --- Return value (REPORTER) --- Generator.smalrubyRuby_returnValue = function (_block) { return ['_rv_', Generator.ORDER_FUNCTION_CALL]; diff --git a/packages/scratch-gui/src/lib/ruby-to-blocks-converter/smalruby-ruby.js b/packages/scratch-gui/src/lib/ruby-to-blocks-converter/smalruby-ruby.js index 1547ec11085..f5a1d9cd9b1 100644 --- a/packages/scratch-gui/src/lib/ruby-to-blocks-converter/smalruby-ruby.js +++ b/packages/scratch-gui/src/lib/ruby-to-blocks-converter/smalruby-ruby.js @@ -256,6 +256,41 @@ const SmalrubyRubyConverter = { registerHashMethod('keys'); registerHashMethod('values'); + + // --- Register array method with block (each, etc.) --- + converter.registerOnSendWithBlock( + ['string', 'block', 'variable', 'array'], + 'each', + 0, + 0, + (params) => { + let { receiver } = params; + const { rubyBlock } = params; + if (typeof rubyBlock === 'undefined') return null; + // Convert data_variable to data_listcontents for list variables + const result = convertToListBlock( + converter, + messages, + receiver, + ); + if (result.converted) { + receiver = result.block; + } + const block = converter._createBlock( + 'smalrubyRuby_arrayMethodWithBlock', + 'statement', + ); + converter._addTextInput( + block, + 'RECEIVER', + receiver, + '', + ); + converter._addField(block, 'METHOD', 'each'); + converter._addSubstack(block, rubyBlock); + return block; + }, + ); }, }; diff --git a/packages/scratch-gui/test/unit/lib/ruby-roundtrip/smalruby-ruby.test.js b/packages/scratch-gui/test/unit/lib/ruby-roundtrip/smalruby-ruby.test.js index 44a3dcb556a..d5c40f68eca 100644 --- a/packages/scratch-gui/test/unit/lib/ruby-roundtrip/smalruby-ruby.test.js +++ b/packages/scratch-gui/test/unit/lib/ruby-roundtrip/smalruby-ruby.test.js @@ -350,6 +350,23 @@ describe('Ruby Roundtrip: smalrubyRuby extension', () => { ); }); + test('Array#each with block', async () => { + await expectRoundTrip( + converter, + target, + dedent` + when_flag_clicked do + ticket = [35, 12, 47] + ticket.each do + say("hello", 1) + end + end + `, + null, + opts, + ); + }); + // TODO: 2 ** 8 (non-10 base) is not yet supported as a block // Only 10 ** n and Math::E ** n are supported via operator_mathop diff --git a/packages/scratch-vm/src/extensions/smalruby_ruby/block-definitions.js b/packages/scratch-vm/src/extensions/smalruby_ruby/block-definitions.js index a6b25463e1d..e548e0111c5 100644 --- a/packages/scratch-vm/src/extensions/smalruby_ruby/block-definitions.js +++ b/packages/scratch-vm/src/extensions/smalruby_ruby/block-definitions.js @@ -433,6 +433,10 @@ const menus = { { text: 'empty?', value: 'empty?' }, ], }, + arrayMethodWithBlockMenu: { + acceptReporters: false, + items: [{ text: 'each', value: 'each' }], + }, variableNames: { acceptReporters: false, items: 'getVariableNamesMenuItems', diff --git a/packages/scratch-vm/src/extensions/smalruby_ruby/index.js b/packages/scratch-vm/src/extensions/smalruby_ruby/index.js index 32c39865860..d44cb354434 100644 --- a/packages/scratch-vm/src/extensions/smalruby_ruby/index.js +++ b/packages/scratch-vm/src/extensions/smalruby_ruby/index.js @@ -18,6 +18,7 @@ const { executeStringMethod, executeArrayMethod, executeHashMethod, + executeArrayMethodWithBlock, } = require('./method-executors'); /** @@ -145,6 +146,28 @@ class SmalrubyRubyBlocks { hashMethodMenu: hashMethodMenuItems, }, }, + // --- Array method with block (CONDITIONAL, C-shape) --- + { + opcode: 'arrayMethodWithBlock', + text: formatMessage({ + id: 'smalrubyRuby.arrayMethodWithBlock', + default: 'Array [RECEIVER] . [METHOD] do', + description: + 'Array method call with block (C-shape)', + }), + blockType: BlockType.CONDITIONAL, + arguments: { + RECEIVER: { + type: ArgumentType.STRING, + defaultValue: '', + }, + METHOD: { + type: ArgumentType.STRING, + menu: 'arrayMethodWithBlockMenu', + defaultValue: 'each', + }, + }, + }, // --- Return value (REPORTER) --- { opcode: 'returnValue', @@ -231,6 +254,10 @@ class SmalrubyRubyBlocks { executeHashMethod(args, util, this._setReturnValue); } + arrayMethodWithBlock(args, util) { + executeArrayMethodWithBlock(args, util, this._setReturnValue); + } + // --- Variable names menu --- getVariableNamesMenuItems() { diff --git a/packages/scratch-vm/src/extensions/smalruby_ruby/method-executors.js b/packages/scratch-vm/src/extensions/smalruby_ruby/method-executors.js index 3b9c419630f..b7f1d1696cb 100644 --- a/packages/scratch-vm/src/extensions/smalruby_ruby/method-executors.js +++ b/packages/scratch-vm/src/extensions/smalruby_ruby/method-executors.js @@ -220,8 +220,37 @@ const executeHashMethod = (args, util, setReturnValue) => { setReturnValue(util, result); }; +/** + * Execute an array method with block (C-shape). + * @param {object} args - Block arguments (RECEIVER, METHOD). + * @param {object} util - Block utility (has util.target, util.thread, util.stackFrame, util.startBranch). + * @param {Function} setReturnValue - Callback to store the return value. + */ +const executeArrayMethodWithBlock = (args, util, setReturnValue) => { + const method = args.METHOD; + const toItems = (s) => (s === '' ? [] : String(s).split(' ')); + + switch (method) { + case 'each': { + const items = toItems(String(args.RECEIVER || '')); + if (typeof util.stackFrame.index === 'undefined') { + util.stackFrame.index = 0; + } + if (util.stackFrame.index < items.length) { + setReturnValue(util, items[util.stackFrame.index]); + util.stackFrame.index++; + util.startBranch(1, true); + } + break; + } + default: + break; + } +}; + module.exports = { executeStringMethod, executeArrayMethod, executeHashMethod, + executeArrayMethodWithBlock, }; diff --git a/packages/scratch-vm/src/extensions/smalruby_ruby/translations.json b/packages/scratch-vm/src/extensions/smalruby_ruby/translations.json index c931627782a..cd50b3001c4 100644 --- a/packages/scratch-vm/src/extensions/smalruby_ruby/translations.json +++ b/packages/scratch-vm/src/extensions/smalruby_ruby/translations.json @@ -4,6 +4,7 @@ "smalrubyRuby.stringMethod": "文字列 [RECEIVER] . [METHOD]", "smalrubyRuby.arrayMethod": "配列 [RECEIVER] . [METHOD]", "smalrubyRuby.hashMethod": "ハッシュ [RECEIVER] . [METHOD]", + "smalrubyRuby.arrayMethodWithBlock": "配列 [RECEIVER] . [METHOD] do", "smalrubyRuby.returnValue": "戻り値", "smalrubyRuby.returnValueTruthy": "戻り値が真?" }, @@ -12,6 +13,7 @@ "smalrubyRuby.stringMethod": "もじれつ [RECEIVER] . [METHOD]", "smalrubyRuby.arrayMethod": "はいれつ [RECEIVER] . [METHOD]", "smalrubyRuby.hashMethod": "はっしゅ [RECEIVER] . [METHOD]", + "smalrubyRuby.arrayMethodWithBlock": "はいれつ [RECEIVER] . [METHOD] do", "smalrubyRuby.returnValue": "もどりち", "smalrubyRuby.returnValueTruthy": "もどりちがしん?" }