Skip to content

feat: add Ruby String extension with delete/delete! blocks#362

Open
takaokouji wants to merge 30 commits intodevelopfrom
feat/ruby-string-extension
Open

feat: add Ruby String extension with delete/delete! blocks#362
takaokouji wants to merge 30 commits intodevelopfrom
feat/ruby-string-extension

Conversation

@takaokouji
Copy link

Summary

Changes Made

  • Extend define-dynamic-block.js with argumentsByMethod support for dynamic argument count based on dropdown selection
  • New VM extension: smalruby_ruby (ID: smalrubyRuby) with stringMethodR (REPORTER) and stringMethodC (COMMAND)
  • Ruby generator: blocks → Ruby code ("str".delete("l"), "str".delete!("l"))
  • Ruby-to-blocks converter: Ruby code → blocks
  • Unit tests for generator (6 tests) and converter (4 tests)

Implementation Steps (from #360)

  • Phase 1: define-dynamic-block.js extension (argumentsByMethod)
  • Phase 2: VM extension definition
  • Phase 3: Ruby Generator
  • Phase 4: Ruby-to-Blocks Converter
  • Phase 5: Unit Tests
  • Phase DoD: CI + browser verification

Notable Decisions

  • Extension ID changed from ruby to smalrubyRuby because the converter's _isRubyBlock() rejects all blocks with opcodes matching /^ruby_/ (used for ruby_statement, ruby_expression fallback blocks)

Test Plan

  • Unit tests for generator and converter pass (10/10)
  • Lint passes with zero warnings
  • CI green (full test suite)

Closes #360

takaokouji and others added 6 commits March 22, 2026 21:51
Add procedures_call pattern for dynamic argument reconstruction:
- parseBlockText: parse block text into label/arg components
- disconnectOldBlocks/removeAllInputs: save connections and clear inputs
- createAllInputs: manual input construction with appendValueInput
- updateBlockDisplay: pause rendering → rebuild → re-render
- setupMethodValidator: dropdown validator for method-driven arg changes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New VM extension smalruby_ruby with:
- stringMethodR (REPORTER): String#delete returning a new string
- stringMethodC (COMMAND): String#delete! for in-place mutation
- Dynamic arguments support via argumentsByMethod/menuItems
- Japanese translations (ja, ja-Hira)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- stringMethodR (REPORTER): generates "str".delete("l") code
- stringMethodC (COMMAND): generates "str".delete!("l")\n code
- Supports optional ARG2 for future expansion

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- String#delete → ruby_stringMethodR (REPORTER)
- String#delete! → ruby_stringMethodC (COMMAND)
- Supports string, block, and variable receivers

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Generator tests: string method code generation with mocked valueToCode
- Converter tests: Ruby code → blocks conversion for delete/delete!
- Tests cover string/variable receivers, invalid args, wrong arg count

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The _isRubyBlock check in the converter rejects all blocks with opcodes
starting with ruby_ (ruby_statement, ruby_expression). Rename extension
ID from 'ruby' to 'smalrubyRuby' so opcodes become smalrubyRuby_*.
Also fix lint errors in define-dynamic-block.js (indent, no-undefined,
prefer-arrow-callback).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions
Copy link

takaokouji and others added 23 commits March 23, 2026 00:15
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Without setupTranslations(), format-message cannot find Japanese
translations, causing block conversion to fail when the extension
is auto-loaded from the Ruby tab.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
isDynamic blocks require blockInfo in their mutation data so that
domToMutation can reconstruct the block's inputs. Without this,
the block is created but all inputs (STRING, ARG1, METHOD) are
ignored by scratch-blocks.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
mutationToXML expects blockInfo to be an object (it calls
JSON.stringify internally). Passing a pre-stringified value
caused double-encoding and broke domToMutation parsing.

Also remove debug logging from define-dynamic-block.js.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add smalrubyRuby extension to extension library with icon and translations
- Restrict delete! converter to variable-only receivers (bang methods
  modify in place, so string literals are not valid receivers)
- Update tests accordingly

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fix _isRubyBlock regex to match only fallback ruby blocks
(ruby_statement, ruby_expression, ruby_range, etc.) instead of
all opcodes starting with ruby_. This allows the Ruby extension
to use the simpler 'ruby' ID without conflicting with fallback blocks.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
delete! requires a variable receiver, so 'hello world' as default
is misleading. Use empty string instead.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Change delete! block's STRING from input_value to variableNames menu
- Add getVariableNamesMenuItems to Ruby extension (koshien pattern)
- stringMethodC now looks up the variable by name and modifies its value
- Generator uses getFieldValue + variableNameByName for variable receiver
- Converter uses lookupVariableFromVariableBlock + addField for variable name

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When a menu (like variableNames) uses a dynamic items function,
it won't be in menuItems. Fall back to a single-option dropdown
with the default value so the field_dropdown is still created.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When dragging a block from the toolbox, the XML parser creates shadow
blocks after domToMutation. If createAllInputs also creates shadows
via respawnShadow_(), they become orphaned and float on the workspace.

Skip shadow creation on the first domToMutation call (isFirstCall=true)
and let the XML parser handle it. Only create shadows during
validator-triggered rebuilds (method dropdown changes).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Dynamic menus (like variableNames with items: 'getVariableNamesMenuItems')
were not resolved in isDynamic blocks because createAllInputs only
checked blockInfo.menuItems. Now falls back to categoryInfo.menuInfo
where runtime has already bound the menu function.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Proves variable-length arguments work:
- delete/delete!: 1 argument (pattern)
- gsub/gsub!: 2 arguments (pattern, replacement)

Dropdown selection dynamically changes the number of input slots.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
All Smalruby extensions use the smalruby prefix convention.
Revert _isRubyBlock to original regex since smalrubyRuby_ opcodes
don't match /^ruby_/.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Wrap ternary in arrow function with parentheses.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The translation keys in translations.json were already updated to
smalrubyRuby.* but the formatMessage IDs in index.js were still
using ruby.*, causing missing translation warnings.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use the official Ruby logo (diamond + "Ruby" text) as the block icon
for the smalrubyRuby extension, following the koshien pattern.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Follow the scratch3_core_example.js pattern: encode SVG icons as
data:image/svg+xml,%3Csvg... (URL-encoded) instead of base64.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The original Adobe Illustrator SVG contained DOCTYPE/ENTITY
declarations that prevented rendering as a data URI. Cleaned the
SVG by removing XML declaration, DOCTYPE, metadata, and Adobe-specific
attributes, then resolving entity references. Source SVG is stored
alongside index.js for maintainability.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
isDynamic blocks use createAllInputs instead of the standard runtime
conversion, so the extension icon (field_image + field_vertical_separator)
was not being prepended. Now createAllInputs checks categoryInfo.blockIconURI
and adds the icon fields at the start of the block.

Also clean up the source SVG (remove DOCTYPE/ENTITY/metadata) for
proper data URI rendering, and store the clean source alongside index.js.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The cleaned SVG still contained a:midPointStop elements which
caused the SVG to fail parsing as a data URI, showing a broken
image icon. Also removed i:knockout attributes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add disableMonitor: true to stringMethodR (REPORTER) block to
  remove the flyout checkbox, matching koshien extension pattern
- Change STRING defaultValue from 'hello world' to '' for both
  delete and gsub value blocks

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Change default values from empty/'l'/'r' to 'string'/'arg1'/'arg2'
so the block displays meaningful placeholder text instead of
'undefined' or cryptic single characters.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: add Ruby String extension with delete/delete! blocks and dynamic arguments

1 participant