Skip to content

feat: add support for Ruby to_s and to_i method conversions #108

@takaokouji

Description

@takaokouji

Summary

Add support for Ruby's to_s and to_i type conversion methods in the Smalruby editor. These methods should convert to native Scratch operator blocks (operator_join and operator_add) instead of falling through to ruby_expression blocks.

Motivation

Users write Ruby code like:

kakaku = gets.to_i
puts kakaku * 0.7

Currently, to_s and to_i fall through to ruby_expression blocks and are executed via Opal at runtime. Converting them to native Scratch blocks will:

  • Make the block view clearer and more visual
  • Provide better performance
  • Ensure consistent behavior with Scratch's built-in type coercion

Proposed Implementation

Conversion Pattern

  • value.to_soperator_join(value, "") with comment marker @ruby:to_s
  • value.to_ioperator_add(value, 0) with comment marker @ruby:to_i

Files to Modify

  1. Ruby-to-Blocks Converter (packages/scratch-gui/src/lib/ruby-to-blocks-converter/operators.js)

    • Register to_s method: converts to operator_join block with empty string
    • Register to_i method: converts to operator_add block with zero
    • Attach comment markers using converter._createComment()
  2. Code Generator (packages/scratch-gui/src/lib/ruby-generator/operators.js)

    • Update Generator.operator_join: detect @ruby:to_s comment and generate .to_s
    • Update Generator.operator_add: detect @ruby:to_i comment and generate .to_i
  3. Unit Tests (packages/scratch-gui/test/unit/lib/ruby-to-blocks-converter/operators.test.js)

    • Test to_s with number literal, string literal, and variables
    • Test to_i with string literal, number literal, and variables
  4. Integration Tests (packages/scratch-gui/test/integration/operators.test.js)

    • Test round-trip conversion (Ruby → Blocks → Ruby)
    • Verify comment markers are preserved

Code Snippets

Ruby-to-Blocks Registration (add after line 161 in operators.js):

// to_s method: convert to operator_join with empty string
converter.registerOnSend(['variable', 'number', 'string', 'block'], 'to_s', 0, params => {
    const {receiver} = params;

    const block = converter._createBlock('operator_join', 'value');
    converter._addTextInput(block, 'STRING1', receiver, '');
    converter._addTextInput(block, 'STRING2', '', '');
    block.comment = converter._createComment('@ruby:to_s', block.id);
    return block;
});

// to_i method: convert to operator_add with zero
converter.registerOnSend(['variable', 'number', 'string', 'block'], 'to_i', 0, params => {
    const {receiver} = params;

    const block = converter._createBlock('operator_add', 'value');
    converter._addNumberInput(block, 'NUM1', 'math_number', receiver, '');
    converter._addNumberInput(block, 'NUM2', 'math_number', 0, '');
    block.comment = converter._createComment('@ruby:to_i', block.id);
    return block;
});

Code Generator Enhancement (modify in ruby-generator/operators.js):

Generator.operator_join = function (block) {
    const comment = Generator.getCommentText(block);
    if (comment === '@ruby:to_s') {
        const value = Generator.valueToCode(block, 'STRING1', Generator.ORDER_FUNCTION_CALL) || Generator.quote_('');
        return [`${value}.to_s`, Generator.ORDER_FUNCTION_CALL];
    }

    const order = Generator.ORDER_ADDITIVE;
    const rightStr = Generator.valueToCode(block, 'STRING1', order) || Generator.quote_('');
    const leftStr = Generator.valueToCode(block, 'STRING2', order) || Generator.quote_('');
    return [`${rightStr} + ${leftStr}`, order];
};

Generator.operator_add = function (block) {
    const comment = Generator.getCommentText(block);
    if (comment === '@ruby:to_i') {
        const value = Generator.valueToCode(block, 'NUM1', Generator.ORDER_FUNCTION_CALL) || 0;
        return [`${value}.to_i`, Generator.ORDER_FUNCTION_CALL];
    }

    const order = Generator.ORDER_ADDITIVE;
    const num1 = Generator.valueToCode(block, 'NUM1', order) || 0;
    const num2 = Generator.valueToCode(block, 'NUM2', order) || 0;
    return [`${num1} + ${num2}`, order];
};

Testing

Unit Tests

  • Test to_s and to_i with various receiver types (numbers, strings, variables, blocks)
  • Verify correct block type and comment marker creation

Integration Tests

  • Test complete example code round-trip conversion
  • Verify comment markers are preserved through conversion

Manual Verification

docker compose up app
# Visit http://localhost:8601
# Enter Ruby code with to_s and to_i
# Switch to blocks view and verify operator_join/operator_add blocks
# Switch back to Ruby and verify .to_s/.to_i notation

Success Criteria

  • to_s method calls convert to operator_join blocks with empty string
  • to_i method calls convert to operator_add blocks with zero
  • ✅ Blocks convert back to Ruby with .to_s and .to_i method notation
  • ✅ All tests pass (unit + integration + lint)
  • ✅ Example code works correctly: kakaku = gets.to_i; puts kakaku * 0.7
  • ✅ Round-trip conversion preserves semantics

Related Files

  • packages/scratch-gui/src/lib/ruby-to-blocks-converter/operators.js
  • packages/scratch-gui/src/lib/ruby-generator/operators.js
  • packages/scratch-gui/test/unit/lib/ruby-to-blocks-converter/operators.test.js
  • packages/scratch-gui/test/integration/operators.test.js

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions