/** * @fileoverview * The specMap below handles a few pieces of "translation" work between * the SB2 JSON format and the data we need to run a project * in the Scratch 3.0 VM. * Notably: * - Map 2.0 and 1.4 opcodes (forward:) into 3.0-format (motion_movesteps). * - Map ordered, unnamed args to unordered, named inputs and fields. * Keep this up-to-date as 3.0 blocks are renamed, changed, etc. * Originally this was generated largely by a hand-guided scripting process. * The relevant data lives here: * https://github.com/LLK/scratch-flash/blob/master/src/Specs.as * (for the old opcode and argument order). * and here: * https://github.com/LLK/scratch-blocks/tree/develop/blocks_vertical * (for the new opcodes and argument names). * and here: * https://github.com/LLK/scratch-blocks/blob/develop/tests/ * (for the shadow blocks created for each block). * I started with the `commands` array in Specs.as, and discarded irrelevant * properties. By hand, I matched the opcode name to the 3.0 opcode. * Finally, I filled in the expected arguments as below. */ const Variable = require('../engine/variable'); /** * @typedef {object} SB2SpecMap_blockInfo * @property {string} opcode - the Scratch 3.0 block opcode. Use 'extensionID.opcode' for extension opcodes. * @property {Array.} argMap - metadata for this block's arguments. */ /** * @typedef {object} SB2SpecMap_argInfo * @property {string} type - the type of this arg (such as 'input' or 'field') * @property {string} inputOp - the scratch-blocks shadow type for this arg * @property {string} inputName - the name this argument will take when provided to the block implementation */ /** * Mapping of Scratch 2.0 opcode to Scratch 3.0 block metadata. * @type {object.} */ const specMap = { 'forward:': { opcode: 'motion_movesteps', argMap: [ { type: 'input', inputOp: 'math_number', inputName: 'STEPS' } ] }, 'turnRight:': { opcode: 'motion_turnright', argMap: [ { type: 'input', inputOp: 'math_number', inputName: 'DEGREES' } ] }, 'turnLeft:': { opcode: 'motion_turnleft', argMap: [ { type: 'input', inputOp: 'math_number', inputName: 'DEGREES' } ] }, 'heading:': { opcode: 'motion_pointindirection', argMap: [ { type: 'input', inputOp: 'math_angle', inputName: 'DIRECTION' } ] }, 'pointTowards:': { opcode: 'motion_pointtowards', argMap: [ { type: 'input', inputOp: 'motion_pointtowards_menu', inputName: 'TOWARDS' } ] }, 'gotoX:y:': { opcode: 'motion_gotoxy', argMap: [ { type: 'input', inputOp: 'math_number', inputName: 'X' }, { type: 'input', inputOp: 'math_number', inputName: 'Y' } ] }, 'gotoSpriteOrMouse:': { opcode: 'motion_goto', argMap: [ { type: 'input', inputOp: 'motion_goto_menu', inputName: 'TO' } ] }, 'glideSecs:toX:y:elapsed:from:': { opcode: 'motion_glidesecstoxy', argMap: [ { type: 'input', inputOp: 'math_number', inputName: 'SECS' }, { type: 'input', inputOp: 'math_number', inputName: 'X' }, { type: 'input', inputOp: 'math_number', inputName: 'Y' } ] }, 'changeXposBy:': { opcode: 'motion_changexby', argMap: [ { type: 'input', inputOp: 'math_number', inputName: 'DX' } ] }, 'xpos:': { opcode: 'motion_setx', argMap: [ { type: 'input', inputOp: 'math_number', inputName: 'X' } ] }, 'changeYposBy:': { opcode: 'motion_changeyby', argMap: [ { type: 'input', inputOp: 'math_number', inputName: 'DY' } ] }, 'ypos:': { opcode: 'motion_sety', argMap: [ { type: 'input', inputOp: 'math_number', inputName: 'Y' } ] }, 'bounceOffEdge': { opcode: 'motion_ifonedgebounce', argMap: [ ] }, 'setRotationStyle': { opcode: 'motion_setrotationstyle', argMap: [ { type: 'field', fieldName: 'STYLE' } ] }, 'xpos': { opcode: 'motion_xposition', argMap: [ ] }, 'ypos': { opcode: 'motion_yposition', argMap: [ ] }, 'heading': { opcode: 'motion_direction', argMap: [ ] }, 'scrollRight': { opcode: 'motion_scroll_right', argMap: [ { type: 'input', inputOp: 'math_number', inputName: 'DISTANCE' } ] }, 'scrollUp': { opcode: 'motion_scroll_up', argMap: [ { type: 'input', inputOp: 'math_number', inputName: 'DISTANCE' } ] }, 'scrollAlign': { opcode: 'motion_align_scene', argMap: [ { type: 'field', fieldName: 'ALIGNMENT' } ] }, 'xScroll': { opcode: 'motion_xscroll', argMap: [ ] }, 'yScroll': { opcode: 'motion_yscroll', argMap: [ ] }, 'say:duration:elapsed:from:': { opcode: 'looks_sayforsecs', argMap: [ { type: 'input', inputOp: 'text', inputName: 'MESSAGE' }, { type: 'input', inputOp: 'math_number', inputName: 'SECS' } ] }, 'say:': { opcode: 'looks_say', argMap: [ { type: 'input', inputOp: 'text', inputName: 'MESSAGE' } ] }, 'think:duration:elapsed:from:': { opcode: 'looks_thinkforsecs', argMap: [ { type: 'input', inputOp: 'text', inputName: 'MESSAGE' }, { type: 'input', inputOp: 'math_number', inputName: 'SECS' } ] }, 'think:': { opcode: 'looks_think', argMap: [ { type: 'input', inputOp: 'text', inputName: 'MESSAGE' } ] }, 'show': { opcode: 'looks_show', argMap: [ ] }, 'hide': { opcode: 'looks_hide', argMap: [ ] }, 'hideAll': { opcode: 'looks_hideallsprites', argMap: [ ] }, 'lookLike:': { opcode: 'looks_switchcostumeto', argMap: [ { type: 'input', inputOp: 'looks_costume', inputName: 'COSTUME' } ] }, 'nextCostume': { opcode: 'looks_nextcostume', argMap: [ ] }, 'startScene': { opcode: 'looks_switchbackdropto', argMap: [ { type: 'input', inputOp: 'looks_backdrops', inputName: 'BACKDROP' } ] }, 'changeGraphicEffect:by:': { opcode: 'looks_changeeffectby', argMap: [ { type: 'field', fieldName: 'EFFECT' }, { type: 'input', inputOp: 'math_number', inputName: 'CHANGE' } ] }, 'setGraphicEffect:to:': { opcode: 'looks_seteffectto', argMap: [ { type: 'field', fieldName: 'EFFECT' }, { type: 'input', inputOp: 'math_number', inputName: 'VALUE' } ] }, 'filterReset': { opcode: 'looks_cleargraphiceffects', argMap: [ ] }, 'changeSizeBy:': { opcode: 'looks_changesizeby', argMap: [ { type: 'input', inputOp: 'math_number', inputName: 'CHANGE' } ] }, 'setSizeTo:': { opcode: 'looks_setsizeto', argMap: [ { type: 'input', inputOp: 'math_number', inputName: 'SIZE' } ] }, 'changeStretchBy:': { opcode: 'looks_changestretchby', argMap: [ { type: 'input', inputOp: 'math_number', inputName: 'CHANGE' } ] }, 'setStretchTo:': { opcode: 'looks_setstretchto', argMap: [ { type: 'input', inputOp: 'math_number', inputName: 'STRETCH' } ] }, 'comeToFront': { opcode: 'looks_gotofrontback', argMap: [ ] }, 'goBackByLayers:': { opcode: 'looks_goforwardbackwardlayers', argMap: [ { type: 'input', inputOp: 'math_integer', inputName: 'NUM' } ] }, 'costumeIndex': { opcode: 'looks_costumenumbername', argMap: [ ] }, 'costumeName': { opcode: 'looks_costumenumbername', argMap: [ ] }, 'sceneName': { opcode: 'looks_backdropnumbername', argMap: [ ] }, 'scale': { opcode: 'looks_size', argMap: [ ] }, 'startSceneAndWait': { opcode: 'looks_switchbackdroptoandwait', argMap: [ { type: 'input', inputOp: 'looks_backdrops', inputName: 'BACKDROP' } ] }, 'nextScene': { opcode: 'looks_nextbackdrop', argMap: [ ] }, 'backgroundIndex': { opcode: 'looks_backdropnumbername', argMap: [ ] }, 'playSound:': { opcode: 'sound_play', argMap: [ { type: 'input', inputOp: 'sound_sounds_menu', inputName: 'SOUND_MENU' } ] }, 'doPlaySoundAndWait': { opcode: 'sound_playuntildone', argMap: [ { type: 'input', inputOp: 'sound_sounds_menu', inputName: 'SOUND_MENU' } ] }, 'stopAllSounds': { opcode: 'sound_stopallsounds', argMap: [ ] }, 'playDrum': { opcode: 'music_playDrumForBeats', argMap: [ { type: 'input', inputOp: 'music_menu_DRUM', inputName: 'DRUM' }, { type: 'input', inputOp: 'math_number', inputName: 'BEATS' } ] }, 'drum:duration:elapsed:from:': { opcode: 'music_midiPlayDrumForBeats', argMap: [ { type: 'input', inputOp: 'math_number', inputName: 'DRUM' }, { type: 'input', inputOp: 'math_number', inputName: 'BEATS' } ] }, 'rest:elapsed:from:': { opcode: 'music_restForBeats', argMap: [ { type: 'input', inputOp: 'math_number', inputName: 'BEATS' } ] }, 'noteOn:duration:elapsed:from:': { opcode: 'music_playNoteForBeats', argMap: [ { type: 'input', inputOp: 'note', inputName: 'NOTE' }, { type: 'input', inputOp: 'math_number', inputName: 'BEATS' } ] }, 'instrument:': { opcode: 'music_setInstrument', argMap: [ { type: 'input', inputOp: 'music_menu_INSTRUMENT', inputName: 'INSTRUMENT' } ] }, 'midiInstrument:': { opcode: 'music_midiSetInstrument', argMap: [ { type: 'input', inputOp: 'math_number', inputName: 'INSTRUMENT' } ] }, 'changeVolumeBy:': { opcode: 'sound_changevolumeby', argMap: [ { type: 'input', inputOp: 'math_number', inputName: 'VOLUME' } ] }, 'setVolumeTo:': { opcode: 'sound_setvolumeto', argMap: [ { type: 'input', inputOp: 'math_number', inputName: 'VOLUME' } ] }, 'volume': { opcode: 'sound_volume', argMap: [ ] }, 'changeTempoBy:': { opcode: 'music_changeTempo', argMap: [ { type: 'input', inputOp: 'math_number', inputName: 'TEMPO' } ] }, 'setTempoTo:': { opcode: 'music_setTempo', argMap: [ { type: 'input', inputOp: 'math_number', inputName: 'TEMPO' } ] }, 'tempo': { opcode: 'music_getTempo', argMap: [ ] }, 'clearPenTrails': { opcode: 'pen_clear', argMap: [ ] }, 'stampCostume': { opcode: 'pen_stamp', argMap: [ ] }, 'putPenDown': { opcode: 'pen_penDown', argMap: [ ] }, 'putPenUp': { opcode: 'pen_penUp', argMap: [ ] }, 'penColor:': { opcode: 'pen_setPenColorToColor', argMap: [ { type: 'input', inputOp: 'colour_picker', inputName: 'COLOR' } ] }, 'changePenHueBy:': { opcode: 'pen_changePenHueBy', argMap: [ { type: 'input', inputOp: 'math_number', inputName: 'HUE' } ] }, 'setPenHueTo:': { opcode: 'pen_setPenHueToNumber', argMap: [ { type: 'input', inputOp: 'math_number', inputName: 'HUE' } ] }, 'changePenShadeBy:': { opcode: 'pen_changePenShadeBy', argMap: [ { type: 'input', inputOp: 'math_number', inputName: 'SHADE' } ] }, 'setPenShadeTo:': { opcode: 'pen_setPenShadeToNumber', argMap: [ { type: 'input', inputOp: 'math_number', inputName: 'SHADE' } ] }, 'changePenSizeBy:': { opcode: 'pen_changePenSizeBy', argMap: [ { type: 'input', inputOp: 'math_number', inputName: 'SIZE' } ] }, 'penSize:': { opcode: 'pen_setPenSizeTo', argMap: [ { type: 'input', inputOp: 'math_number', inputName: 'SIZE' } ] }, 'senseVideoMotion': { opcode: 'videoSensing_videoOn', argMap: [ { type: 'input', inputOp: 'videoSensing_menu_ATTRIBUTE', inputName: 'ATTRIBUTE' }, { type: 'input', inputOp: 'videoSensing_menu_SUBJECT', inputName: 'SUBJECT' } ] }, 'whenGreenFlag': { opcode: 'event_whenflagclicked', argMap: [ ] }, 'whenKeyPressed': { opcode: 'event_whenkeypressed', argMap: [ { type: 'field', fieldName: 'KEY_OPTION' } ] }, 'whenClicked': { opcode: 'event_whenthisspriteclicked', argMap: [ ] }, 'whenSceneStarts': { opcode: 'event_whenbackdropswitchesto', argMap: [ { type: 'field', fieldName: 'BACKDROP' } ] }, 'whenSensorGreaterThan': ([, sensor]) => { if (sensor === 'video motion') { return { opcode: 'videoSensing_whenMotionGreaterThan', argMap: [ // skip the first arg, since we converted to a video specific sensing block {}, { type: 'input', inputOp: 'math_number', inputName: 'REFERENCE' } ] }; } return { opcode: 'event_whengreaterthan', argMap: [ { type: 'field', fieldName: 'WHENGREATERTHANMENU' }, { type: 'input', inputOp: 'math_number', inputName: 'VALUE' } ] }; }, 'whenIReceive': { opcode: 'event_whenbroadcastreceived', argMap: [ { type: 'field', fieldName: 'BROADCAST_OPTION', variableType: Variable.BROADCAST_MESSAGE_TYPE } ] }, 'broadcast:': { opcode: 'event_broadcast', argMap: [ { type: 'input', inputOp: 'event_broadcast_menu', inputName: 'BROADCAST_INPUT', variableType: Variable.BROADCAST_MESSAGE_TYPE } ] }, 'doBroadcastAndWait': { opcode: 'event_broadcastandwait', argMap: [ { type: 'input', inputOp: 'event_broadcast_menu', inputName: 'BROADCAST_INPUT', variableType: Variable.BROADCAST_MESSAGE_TYPE } ] }, 'wait:elapsed:from:': { opcode: 'control_wait', argMap: [ { type: 'input', inputOp: 'math_positive_number', inputName: 'DURATION' } ] }, 'doRepeat': { opcode: 'control_repeat', argMap: [ { type: 'input', inputOp: 'math_whole_number', inputName: 'TIMES' }, { type: 'input', inputOp: 'substack', inputName: 'SUBSTACK' } ] }, 'doForever': { opcode: 'control_forever', argMap: [ { type: 'input', inputOp: 'substack', inputName: 'SUBSTACK' } ] }, 'doIf': { opcode: 'control_if', argMap: [ { type: 'input', inputOp: 'boolean', inputName: 'CONDITION' }, { type: 'input', inputOp: 'substack', inputName: 'SUBSTACK' } ] }, 'doIfElse': { opcode: 'control_if_else', argMap: [ { type: 'input', inputOp: 'boolean', inputName: 'CONDITION' }, { type: 'input', inputOp: 'substack', inputName: 'SUBSTACK' }, { type: 'input', inputOp: 'substack', inputName: 'SUBSTACK2' } ] }, 'doWaitUntil': { opcode: 'control_wait_until', argMap: [ { type: 'input', inputOp: 'boolean', inputName: 'CONDITION' } ] }, 'doUntil': { opcode: 'control_repeat_until', argMap: [ { type: 'input', inputOp: 'boolean', inputName: 'CONDITION' }, { type: 'input', inputOp: 'substack', inputName: 'SUBSTACK' } ] }, 'doWhile': { opcode: 'control_while', argMap: [ { type: 'input', inputOp: 'boolean', inputName: 'CONDITION' }, { type: 'input', inputOp: 'substack', inputName: 'SUBSTACK' } ] }, 'doForLoop': { opcode: 'control_for_each', argMap: [ { type: 'field', fieldName: 'VARIABLE' }, { type: 'input', inputOp: 'text', inputName: 'VALUE' }, { type: 'input', inputOp: 'substack', inputName: 'SUBSTACK' } ] }, 'stopScripts': { opcode: 'control_stop', argMap: [ { type: 'field', fieldName: 'STOP_OPTION' } ] }, 'whenCloned': { opcode: 'control_start_as_clone', argMap: [ ] }, 'createCloneOf': { opcode: 'control_create_clone_of', argMap: [ { type: 'input', inputOp: 'control_create_clone_of_menu', inputName: 'CLONE_OPTION' } ] }, 'deleteClone': { opcode: 'control_delete_this_clone', argMap: [ ] }, 'COUNT': { opcode: 'control_get_counter', argMap: [ ] }, 'INCR_COUNT': { opcode: 'control_incr_counter', argMap: [ ] }, 'CLR_COUNT': { opcode: 'control_clear_counter', argMap: [ ] }, 'warpSpeed': { opcode: 'control_all_at_once', argMap: [ { type: 'input', inputOp: 'substack', inputName: 'SUBSTACK' } ] }, 'touching:': { opcode: 'sensing_touchingobject', argMap: [ { type: 'input', inputOp: 'sensing_touchingobjectmenu', inputName: 'TOUCHINGOBJECTMENU' } ] }, 'touchingColor:': { opcode: 'sensing_touchingcolor', argMap: [ { type: 'input', inputOp: 'colour_picker', inputName: 'COLOR' } ] }, 'color:sees:': { opcode: 'sensing_coloristouchingcolor', argMap: [ { type: 'input', inputOp: 'colour_picker', inputName: 'COLOR' }, { type: 'input', inputOp: 'colour_picker', inputName: 'COLOR2' } ] }, 'distanceTo:': { opcode: 'sensing_distanceto', argMap: [ { type: 'input', inputOp: 'sensing_distancetomenu', inputName: 'DISTANCETOMENU' } ] }, 'doAsk': { opcode: 'sensing_askandwait', argMap: [ { type: 'input', inputOp: 'text', inputName: 'QUESTION' } ] }, 'answer': { opcode: 'sensing_answer', argMap: [ ] }, 'keyPressed:': { opcode: 'sensing_keypressed', argMap: [ { type: 'input', inputOp: 'sensing_keyoptions', inputName: 'KEY_OPTION' } ] }, 'mousePressed': { opcode: 'sensing_mousedown', argMap: [ ] }, 'mouseX': { opcode: 'sensing_mousex', argMap: [ ] }, 'mouseY': { opcode: 'sensing_mousey', argMap: [ ] }, 'soundLevel': { opcode: 'sensing_loudness', argMap: [ ] }, 'isLoud': { opcode: 'sensing_loud', argMap: [ ] }, // 'senseVideoMotion': { // opcode: 'sensing_videoon', // argMap: [ // { // type: 'input', // inputOp: 'sensing_videoonmenuone', // inputName: 'VIDEOONMENU1' // }, // { // type: 'input', // inputOp: 'sensing_videoonmenutwo', // inputName: 'VIDEOONMENU2' // } // ] // }, 'setVideoState': { opcode: 'videoSensing_videoToggle', argMap: [ { type: 'input', inputOp: 'videoSensing_menu_VIDEO_STATE', inputName: 'VIDEO_STATE' } ] }, 'setVideoTransparency': { opcode: 'videoSensing_setVideoTransparency', argMap: [ { type: 'input', inputOp: 'math_number', inputName: 'TRANSPARENCY' } ] }, 'timer': { opcode: 'sensing_timer', argMap: [ ] }, 'timerReset': { opcode: 'sensing_resettimer', argMap: [ ] }, 'getAttribute:of:': { opcode: 'sensing_of', argMap: [ { type: 'field', fieldName: 'PROPERTY' }, { type: 'input', inputOp: 'sensing_of_object_menu', inputName: 'OBJECT' } ] }, 'timeAndDate': { opcode: 'sensing_current', argMap: [ { type: 'field', fieldName: 'CURRENTMENU' } ] }, 'timestamp': { opcode: 'sensing_dayssince2000', argMap: [ ] }, 'getUserName': { opcode: 'sensing_username', argMap: [ ] }, 'getUserId': { opcode: 'sensing_userid', argMap: [ ] }, '+': { opcode: 'operator_add', argMap: [ { type: 'input', inputOp: 'math_number', inputName: 'NUM1' }, { type: 'input', inputOp: 'math_number', inputName: 'NUM2' } ] }, '-': { opcode: 'operator_subtract', argMap: [ { type: 'input', inputOp: 'math_number', inputName: 'NUM1' }, { type: 'input', inputOp: 'math_number', inputName: 'NUM2' } ] }, '*': { opcode: 'operator_multiply', argMap: [ { type: 'input', inputOp: 'math_number', inputName: 'NUM1' }, { type: 'input', inputOp: 'math_number', inputName: 'NUM2' } ] }, '/': { opcode: 'operator_divide', argMap: [ { type: 'input', inputOp: 'math_number', inputName: 'NUM1' }, { type: 'input', inputOp: 'math_number', inputName: 'NUM2' } ] }, 'randomFrom:to:': { opcode: 'operator_random', argMap: [ { type: 'input', inputOp: 'math_number', inputName: 'FROM' }, { type: 'input', inputOp: 'math_number', inputName: 'TO' } ] }, '<': { opcode: 'operator_lt', argMap: [ { type: 'input', inputOp: 'text', inputName: 'OPERAND1' }, { type: 'input', inputOp: 'text', inputName: 'OPERAND2' } ] }, '=': { opcode: 'operator_equals', argMap: [ { type: 'input', inputOp: 'text', inputName: 'OPERAND1' }, { type: 'input', inputOp: 'text', inputName: 'OPERAND2' } ] }, '>': { opcode: 'operator_gt', argMap: [ { type: 'input', inputOp: 'text', inputName: 'OPERAND1' }, { type: 'input', inputOp: 'text', inputName: 'OPERAND2' } ] }, '&': { opcode: 'operator_and', argMap: [ { type: 'input', inputOp: 'boolean', inputName: 'OPERAND1' }, { type: 'input', inputOp: 'boolean', inputName: 'OPERAND2' } ] }, '|': { opcode: 'operator_or', argMap: [ { type: 'input', inputOp: 'boolean', inputName: 'OPERAND1' }, { type: 'input', inputOp: 'boolean', inputName: 'OPERAND2' } ] }, 'not': { opcode: 'operator_not', argMap: [ { type: 'input', inputOp: 'boolean', inputName: 'OPERAND' } ] }, 'concatenate:with:': { opcode: 'operator_join', argMap: [ { type: 'input', inputOp: 'text', inputName: 'STRING1' }, { type: 'input', inputOp: 'text', inputName: 'STRING2' } ] }, 'letter:of:': { opcode: 'operator_letter_of', argMap: [ { type: 'input', inputOp: 'math_whole_number', inputName: 'LETTER' }, { type: 'input', inputOp: 'text', inputName: 'STRING' } ] }, 'stringLength:': { opcode: 'operator_length', argMap: [ { type: 'input', inputOp: 'text', inputName: 'STRING' } ] }, '%': { opcode: 'operator_mod', argMap: [ { type: 'input', inputOp: 'math_number', inputName: 'NUM1' }, { type: 'input', inputOp: 'math_number', inputName: 'NUM2' } ] }, 'rounded': { opcode: 'operator_round', argMap: [ { type: 'input', inputOp: 'math_number', inputName: 'NUM' } ] }, 'computeFunction:of:': { opcode: 'operator_mathop', argMap: [ { type: 'field', fieldName: 'OPERATOR' }, { type: 'input', inputOp: 'math_number', inputName: 'NUM' } ] }, 'readVariable': { opcode: 'data_variable', argMap: [ { type: 'field', fieldName: 'VARIABLE', variableType: Variable.SCALAR_TYPE } ] }, // Scratch 2 uses this alternative variable getter opcode only in monitors, // blocks use the `readVariable` opcode above. 'getVar:': { opcode: 'data_variable', argMap: [ { type: 'field', fieldName: 'VARIABLE', variableType: Variable.SCALAR_TYPE } ] }, 'setVar:to:': { opcode: 'data_setvariableto', argMap: [ { type: 'field', fieldName: 'VARIABLE', variableType: Variable.SCALAR_TYPE }, { type: 'input', inputOp: 'text', inputName: 'VALUE' } ] }, 'changeVar:by:': { opcode: 'data_changevariableby', argMap: [ { type: 'field', fieldName: 'VARIABLE', variableType: Variable.SCALAR_TYPE }, { type: 'input', inputOp: 'math_number', inputName: 'VALUE' } ] }, 'showVariable:': { opcode: 'data_showvariable', argMap: [ { type: 'field', fieldName: 'VARIABLE', variableType: Variable.SCALAR_TYPE } ] }, 'hideVariable:': { opcode: 'data_hidevariable', argMap: [ { type: 'field', fieldName: 'VARIABLE', variableType: Variable.SCALAR_TYPE } ] }, 'contentsOfList:': { opcode: 'data_listcontents', argMap: [ { type: 'field', fieldName: 'LIST', variableType: Variable.LIST_TYPE } ] }, 'append:toList:': { opcode: 'data_addtolist', argMap: [ { type: 'input', inputOp: 'text', inputName: 'ITEM' }, { type: 'field', fieldName: 'LIST', variableType: Variable.LIST_TYPE } ] }, 'deleteLine:ofList:': { opcode: 'data_deleteoflist', argMap: [ { type: 'input', inputOp: 'math_integer', inputName: 'INDEX' }, { type: 'field', fieldName: 'LIST', variableType: Variable.LIST_TYPE } ] }, 'insert:at:ofList:': { opcode: 'data_insertatlist', argMap: [ { type: 'input', inputOp: 'text', inputName: 'ITEM' }, { type: 'input', inputOp: 'math_integer', inputName: 'INDEX' }, { type: 'field', fieldName: 'LIST', variableType: Variable.LIST_TYPE } ] }, 'setLine:ofList:to:': { opcode: 'data_replaceitemoflist', argMap: [ { type: 'input', inputOp: 'math_integer', inputName: 'INDEX' }, { type: 'field', fieldName: 'LIST', variableType: Variable.LIST_TYPE }, { type: 'input', inputOp: 'text', inputName: 'ITEM' } ] }, 'getLine:ofList:': { opcode: 'data_itemoflist', argMap: [ { type: 'input', inputOp: 'math_integer', inputName: 'INDEX' }, { type: 'field', fieldName: 'LIST', variableType: Variable.LIST_TYPE } ] }, 'lineCountOfList:': { opcode: 'data_lengthoflist', argMap: [ { type: 'field', fieldName: 'LIST', variableType: Variable.LIST_TYPE } ] }, 'list:contains:': { opcode: 'data_listcontainsitem', argMap: [ { type: 'field', fieldName: 'LIST', variableType: Variable.LIST_TYPE }, { type: 'input', inputOp: 'text', inputName: 'ITEM' } ] }, 'showList:': { opcode: 'data_showlist', argMap: [ { type: 'field', fieldName: 'LIST', variableType: Variable.LIST_TYPE } ] }, 'hideList:': { opcode: 'data_hidelist', argMap: [ { type: 'field', fieldName: 'LIST', variableType: Variable.LIST_TYPE } ] }, 'procDef': { opcode: 'procedures_definition', argMap: [] }, 'getParam': { // Doesn't map to single opcode. Import step assigns final correct opcode. opcode: 'argument_reporter_string_number', argMap: [ { type: 'field', fieldName: 'VALUE' } ] }, 'call': { opcode: 'procedures_call', argMap: [] } }; /** * Add to the specMap entries for an opcode from a Scratch 2.0 extension. Two entries will be made with the same * metadata; this is done to support projects saved by both older and newer versions of the Scratch 2.0 editor. * @param {string} sb2Extension - the Scratch 2.0 name of the extension * @param {string} sb2Opcode - the Scratch 2.0 opcode * @param {SB2SpecMap_blockInfo} blockInfo - the Scratch 3.0 block info */ const addExtensionOp = function (sb2Extension, sb2Opcode, blockInfo) { /** * This string separates the name of an extension and the name of an opcode in more recent Scratch 2.0 projects. * Earlier projects used '.' as a separator, up until we added the 'LEGO WeDo 2.0' extension... * @type {string} */ const sep = '\u001F'; // Unicode Unit Separator // make one entry for projects saved by recent versions of the Scratch 2.0 editor specMap[`${sb2Extension}${sep}${sb2Opcode}`] = blockInfo; // make a second for projects saved by older versions of the Scratch 2.0 editor specMap[`${sb2Extension}.${sb2Opcode}`] = blockInfo; }; const weDo2 = 'LEGO WeDo 2.0'; addExtensionOp(weDo2, 'motorOnFor', { opcode: 'wedo2_motorOnFor', argMap: [ { type: 'input', inputOp: 'wedo2_menu_MOTOR_ID', inputName: 'MOTOR_ID' }, { type: 'input', inputOp: 'math_number', inputName: 'DURATION' } ] }); addExtensionOp(weDo2, 'motorOn', { opcode: 'wedo2_motorOn', argMap: [ { type: 'input', inputOp: 'wedo2_menu_MOTOR_ID', inputName: 'MOTOR_ID' } ] }); addExtensionOp(weDo2, 'motorOff', { opcode: 'wedo2_motorOff', argMap: [ { type: 'input', inputOp: 'wedo2_menu_MOTOR_ID', inputName: 'MOTOR_ID' } ] }); addExtensionOp(weDo2, 'startMotorPower', { opcode: 'wedo2_startMotorPower', argMap: [ { type: 'input', inputOp: 'wedo2_menu_MOTOR_ID', inputName: 'MOTOR_ID' }, { type: 'input', inputOp: 'math_number', inputName: 'POWER' } ] }); addExtensionOp(weDo2, 'setMotorDirection', { opcode: 'wedo2_setMotorDirection', argMap: [ { type: 'input', inputOp: 'wedo2_menu_MOTOR_ID', inputName: 'MOTOR_ID' }, { type: 'input', inputOp: 'wedo2_menu_MOTOR_DIRECTION', inputName: 'MOTOR_DIRECTION' } ] }); addExtensionOp(weDo2, 'setLED', { opcode: 'wedo2_setLightHue', argMap: [ { type: 'input', inputOp: 'math_number', inputName: 'HUE' } ] }); addExtensionOp(weDo2, 'playNote', { opcode: 'wedo2_playNoteFor', argMap: [ { type: 'input', inputOp: 'math_number', inputName: 'NOTE' }, { type: 'input', inputOp: 'math_number', inputName: 'DURATION' } ] }); addExtensionOp(weDo2, 'whenDistance', { opcode: 'wedo2_whenDistance', argMap: [ { type: 'input', inputOp: 'wedo2_menu_OP', inputName: 'OP' }, { type: 'input', inputOp: 'math_number', inputName: 'REFERENCE' } ] }); addExtensionOp(weDo2, 'whenTilted', { opcode: 'wedo2_whenTilted', argMap: [ { type: 'input', inputOp: 'wedo2_menu_TILT_DIRECTION_ANY', inputName: 'TILT_DIRECTION_ANY' } ] }); addExtensionOp(weDo2, 'getDistance', { opcode: 'wedo2_getDistance', argMap: [] }); addExtensionOp(weDo2, 'isTilted', { opcode: 'wedo2_isTilted', argMap: [ { type: 'input', inputOp: 'wedo2_menu_TILT_DIRECTION_ANY', inputName: 'TILT_DIRECTION_ANY' } ] }); addExtensionOp(weDo2, 'getTilt', { opcode: 'wedo2_getTiltAngle', argMap: [ { type: 'input', inputOp: 'wedo2_menu_TILT_DIRECTION', inputName: 'TILT_DIRECTION' } ] }); module.exports = specMap;