File size: 4,560 Bytes
30c32c8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
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.<string, Function>} 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;