const Cast = require('../util/cast'); class Scratch3ProcedureBlocks { constructor(runtime) { /** * The runtime instantiating this block package. * @type {Runtime} */ this.runtime = runtime; } /** * Retrieve the block primitives implemented by this package. * @return {object.} Mapping of opcode to Function. */ getPrimitives() { return { procedures_definition: this.definition, procedures_call: this.call, procedures_set: this.set, argument_reporter_string_number: this.argumentReporterStringNumber, argument_reporter_boolean: this.argumentReporterBoolean, argument_reporter_command: this.argumentReporterCommand }; } definition() { // No-op: execute the blocks. } call (args, util) { if (!util.stackFrame.executed) { const procedureCode = args.mutation.proccode; const paramNamesIdsAndDefaults = util.getProcedureParamNamesIdsAndDefaults(procedureCode); // If null, procedure could not be found, which can happen if custom // block is dragged between sprites without the definition. // Match Scratch 2.0 behavior and noop. if (paramNamesIdsAndDefaults === null) { return; } const [paramNames, paramIds, paramDefaults] = paramNamesIdsAndDefaults; // Initialize params for the current stackFrame to {}, even if the procedure does // not take any arguments. This is so that `getParam` down the line does not look // at earlier stack frames for the values of a given parameter (#1729) util.initParams(); for (let i = 0; i < paramIds.length; i++) { if (args.hasOwnProperty(paramIds[i])) { util.pushParam(paramNames[i], args[paramIds[i]]); } else { util.pushParam(paramNames[i], paramDefaults[i]); } } const addonBlock = util.runtime.getAddonBlock(procedureCode); if (addonBlock) { const result = addonBlock.callback(util.thread.getAllparams(), util); if (util.thread.status === 1 /* STATUS_PROMISE_WAIT */) { // If the addon block is using STATUS_PROMISE_WAIT to force us to sleep, // make sure to not re-run this block when we resume. util.stackFrame.executed = true; } return result; } util.stackFrame.executed = true; util.startProcedure(procedureCode); } } set(args, util) { const contain = util.thread.blockContainer; const block = contain.getBlock(util.thread.isCompiled ? util.thread.peekStack() : util.thread.peekStackFrame().op.id); if (!block) return; const thread = util.thread; const param = contain.getBlock(block.inputs.PARAM?.block); if (param) { try { const curParams = thread.stackFrames[0].params; if (curParams !== null) thread.stackFrames[0].params[param.fields.VALUE.value] = args.VALUE; else thread.stackFrames[0].params = { [param.fields.VALUE.value]: args.VALUE } } catch { /* shouldn't happen */ } } } argumentReporterStringNumber(args, util) { const value = util.getParam(args.VALUE); if (value === null) { // When the parameter is not found in the most recent procedure // call, the default is always 0. return 0; } return value; } argumentReporterBoolean(args, util) { const value = util.getParam(args.VALUE); if (value === null) { // When the parameter is not found in the most recent procedure // call, the default is always 0. return 0; } return value; } argumentReporterCommand(args, util) { const branchInfo = util.getParam(args.VALUE) || {}; if (branchInfo.entry === null) return; const [branchId, target] = util.getBranchAndTarget( branchInfo.callerId, branchInfo.entry ) || []; if (branchId) { // Push branch ID to the thread's stack. util.thread.pushStack(branchId, target); } else { util.thread.pushStack(null); } } } module.exports = Scratch3ProcedureBlocks;