@scratch/scratch-vm 11.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (175) hide show
  1. package/.jsdoc.json +20 -0
  2. package/.nvmrc +1 -0
  3. package/CHANGELOG.md +7004 -0
  4. package/LICENSE +12 -0
  5. package/README.md +120 -0
  6. package/TRADEMARK +1 -0
  7. package/commitlint.config.js +4 -0
  8. package/docs/extensions.md +527 -0
  9. package/package.json +103 -0
  10. package/release.config.js +13 -0
  11. package/src/blocks/scratch3_control.js +206 -0
  12. package/src/blocks/scratch3_core_example.js +69 -0
  13. package/src/blocks/scratch3_data.js +257 -0
  14. package/src/blocks/scratch3_event.js +137 -0
  15. package/src/blocks/scratch3_looks.js +612 -0
  16. package/src/blocks/scratch3_motion.js +288 -0
  17. package/src/blocks/scratch3_operators.js +154 -0
  18. package/src/blocks/scratch3_procedures.js +79 -0
  19. package/src/blocks/scratch3_sensing.js +336 -0
  20. package/src/blocks/scratch3_sound.js +350 -0
  21. package/src/dispatch/central-dispatch.js +143 -0
  22. package/src/dispatch/shared-dispatch.js +235 -0
  23. package/src/dispatch/worker-dispatch.js +110 -0
  24. package/src/engine/adapter.js +176 -0
  25. package/src/engine/block-utility.js +242 -0
  26. package/src/engine/blocks-execute-cache.js +19 -0
  27. package/src/engine/blocks-runtime-cache.js +78 -0
  28. package/src/engine/blocks.js +1299 -0
  29. package/src/engine/comment.js +56 -0
  30. package/src/engine/execute.js +580 -0
  31. package/src/engine/monitor-record.js +23 -0
  32. package/src/engine/mutation-adapter.js +48 -0
  33. package/src/engine/profiler.js +390 -0
  34. package/src/engine/runtime.js +2686 -0
  35. package/src/engine/scratch-blocks-constants.js +27 -0
  36. package/src/engine/sequencer.js +361 -0
  37. package/src/engine/stage-layering.js +29 -0
  38. package/src/engine/target.js +799 -0
  39. package/src/engine/thread.js +404 -0
  40. package/src/engine/variable.js +70 -0
  41. package/src/extension-support/argument-type.js +47 -0
  42. package/src/extension-support/block-type.js +50 -0
  43. package/src/extension-support/define-messages.js +18 -0
  44. package/src/extension-support/extension-manager.js +440 -0
  45. package/src/extension-support/extension-metadata.js +64 -0
  46. package/src/extension-support/extension-worker.js +59 -0
  47. package/src/extension-support/reporter-scope.js +18 -0
  48. package/src/extension-support/target-type.js +17 -0
  49. package/src/extensions/scratch3_boost/index.js +2113 -0
  50. package/src/extensions/scratch3_ev3/index.js +1355 -0
  51. package/src/extensions/scratch3_gdx_for/index.js +981 -0
  52. package/src/extensions/scratch3_gdx_for/scratch-link-device-adapter.js +44 -0
  53. package/src/extensions/scratch3_makeymakey/index.js +396 -0
  54. package/src/extensions/scratch3_microbit/index.js +984 -0
  55. package/src/extensions/scratch3_music/assets/drums/1-snare.mp3 +0 -0
  56. package/src/extensions/scratch3_music/assets/drums/10-wood-block.mp3 +0 -0
  57. package/src/extensions/scratch3_music/assets/drums/11-cowbell.mp3 +0 -0
  58. package/src/extensions/scratch3_music/assets/drums/12-triangle.mp3 +0 -0
  59. package/src/extensions/scratch3_music/assets/drums/13-bongo.mp3 +0 -0
  60. package/src/extensions/scratch3_music/assets/drums/14-conga.mp3 +0 -0
  61. package/src/extensions/scratch3_music/assets/drums/15-cabasa.mp3 +0 -0
  62. package/src/extensions/scratch3_music/assets/drums/16-guiro.mp3 +0 -0
  63. package/src/extensions/scratch3_music/assets/drums/17-vibraslap.mp3 +0 -0
  64. package/src/extensions/scratch3_music/assets/drums/18-cuica.mp3 +0 -0
  65. package/src/extensions/scratch3_music/assets/drums/2-bass-drum.mp3 +0 -0
  66. package/src/extensions/scratch3_music/assets/drums/3-side-stick.mp3 +0 -0
  67. package/src/extensions/scratch3_music/assets/drums/4-crash-cymbal.mp3 +0 -0
  68. package/src/extensions/scratch3_music/assets/drums/5-open-hi-hat.mp3 +0 -0
  69. package/src/extensions/scratch3_music/assets/drums/6-closed-hi-hat.mp3 +0 -0
  70. package/src/extensions/scratch3_music/assets/drums/7-tambourine.mp3 +0 -0
  71. package/src/extensions/scratch3_music/assets/drums/8-hand-clap.mp3 +0 -0
  72. package/src/extensions/scratch3_music/assets/drums/9-claves.mp3 +0 -0
  73. package/src/extensions/scratch3_music/assets/instruments/1-piano/108.mp3 +0 -0
  74. package/src/extensions/scratch3_music/assets/instruments/1-piano/24.mp3 +0 -0
  75. package/src/extensions/scratch3_music/assets/instruments/1-piano/36.mp3 +0 -0
  76. package/src/extensions/scratch3_music/assets/instruments/1-piano/48.mp3 +0 -0
  77. package/src/extensions/scratch3_music/assets/instruments/1-piano/60.mp3 +0 -0
  78. package/src/extensions/scratch3_music/assets/instruments/1-piano/72.mp3 +0 -0
  79. package/src/extensions/scratch3_music/assets/instruments/1-piano/84.mp3 +0 -0
  80. package/src/extensions/scratch3_music/assets/instruments/1-piano/96.mp3 +0 -0
  81. package/src/extensions/scratch3_music/assets/instruments/10-clarinet/48.mp3 +0 -0
  82. package/src/extensions/scratch3_music/assets/instruments/10-clarinet/60.mp3 +0 -0
  83. package/src/extensions/scratch3_music/assets/instruments/11-saxophone/36.mp3 +0 -0
  84. package/src/extensions/scratch3_music/assets/instruments/11-saxophone/60.mp3 +0 -0
  85. package/src/extensions/scratch3_music/assets/instruments/11-saxophone/84.mp3 +0 -0
  86. package/src/extensions/scratch3_music/assets/instruments/12-flute/60.mp3 +0 -0
  87. package/src/extensions/scratch3_music/assets/instruments/12-flute/72.mp3 +0 -0
  88. package/src/extensions/scratch3_music/assets/instruments/13-wooden-flute/60.mp3 +0 -0
  89. package/src/extensions/scratch3_music/assets/instruments/13-wooden-flute/72.mp3 +0 -0
  90. package/src/extensions/scratch3_music/assets/instruments/14-bassoon/36.mp3 +0 -0
  91. package/src/extensions/scratch3_music/assets/instruments/14-bassoon/48.mp3 +0 -0
  92. package/src/extensions/scratch3_music/assets/instruments/14-bassoon/60.mp3 +0 -0
  93. package/src/extensions/scratch3_music/assets/instruments/15-choir/48.mp3 +0 -0
  94. package/src/extensions/scratch3_music/assets/instruments/15-choir/60.mp3 +0 -0
  95. package/src/extensions/scratch3_music/assets/instruments/15-choir/72.mp3 +0 -0
  96. package/src/extensions/scratch3_music/assets/instruments/16-vibraphone/60.mp3 +0 -0
  97. package/src/extensions/scratch3_music/assets/instruments/16-vibraphone/72.mp3 +0 -0
  98. package/src/extensions/scratch3_music/assets/instruments/17-music-box/60.mp3 +0 -0
  99. package/src/extensions/scratch3_music/assets/instruments/18-steel-drum/60.mp3 +0 -0
  100. package/src/extensions/scratch3_music/assets/instruments/19-marimba/60.mp3 +0 -0
  101. package/src/extensions/scratch3_music/assets/instruments/2-electric-piano/60.mp3 +0 -0
  102. package/src/extensions/scratch3_music/assets/instruments/20-synth-lead/60.mp3 +0 -0
  103. package/src/extensions/scratch3_music/assets/instruments/21-synth-pad/60.mp3 +0 -0
  104. package/src/extensions/scratch3_music/assets/instruments/3-organ/60.mp3 +0 -0
  105. package/src/extensions/scratch3_music/assets/instruments/4-guitar/60.mp3 +0 -0
  106. package/src/extensions/scratch3_music/assets/instruments/5-electric-guitar/60.mp3 +0 -0
  107. package/src/extensions/scratch3_music/assets/instruments/6-bass/36.mp3 +0 -0
  108. package/src/extensions/scratch3_music/assets/instruments/6-bass/48.mp3 +0 -0
  109. package/src/extensions/scratch3_music/assets/instruments/7-pizzicato/60.mp3 +0 -0
  110. package/src/extensions/scratch3_music/assets/instruments/8-cello/36.mp3 +0 -0
  111. package/src/extensions/scratch3_music/assets/instruments/8-cello/48.mp3 +0 -0
  112. package/src/extensions/scratch3_music/assets/instruments/8-cello/60.mp3 +0 -0
  113. package/src/extensions/scratch3_music/assets/instruments/9-trombone/36.mp3 +0 -0
  114. package/src/extensions/scratch3_music/assets/instruments/9-trombone/48.mp3 +0 -0
  115. package/src/extensions/scratch3_music/assets/instruments/9-trombone/60.mp3 +0 -0
  116. package/src/extensions/scratch3_music/index.js +1333 -0
  117. package/src/extensions/scratch3_music/manifest.js +63 -0
  118. package/src/extensions/scratch3_pen/index.js +770 -0
  119. package/src/extensions/scratch3_speech2text/index.js +700 -0
  120. package/src/extensions/scratch3_text2speech/index.js +766 -0
  121. package/src/extensions/scratch3_translate/index.js +286 -0
  122. package/src/extensions/scratch3_video_sensing/debug.js +13 -0
  123. package/src/extensions/scratch3_video_sensing/index.js +594 -0
  124. package/src/extensions/scratch3_video_sensing/library.js +384 -0
  125. package/src/extensions/scratch3_video_sensing/math.js +76 -0
  126. package/src/extensions/scratch3_video_sensing/view.js +509 -0
  127. package/src/extensions/scratch3_wedo2/index.js +1616 -0
  128. package/src/import/load-costume.js +416 -0
  129. package/src/import/load-sound.js +116 -0
  130. package/src/index.js +10 -0
  131. package/src/io/ble.js +256 -0
  132. package/src/io/bt.js +202 -0
  133. package/src/io/clock.js +39 -0
  134. package/src/io/cloud.js +169 -0
  135. package/src/io/keyboard.js +146 -0
  136. package/src/io/mouse.js +145 -0
  137. package/src/io/mouseWheel.js +28 -0
  138. package/src/io/userData.js +24 -0
  139. package/src/io/video.js +210 -0
  140. package/src/playground/benchmark.css +83 -0
  141. package/src/playground/benchmark.js +753 -0
  142. package/src/playground/index.html +111 -0
  143. package/src/playground/suite.css +78 -0
  144. package/src/playground/suite.html +25 -0
  145. package/src/playground/suite.js +544 -0
  146. package/src/playground/video-sensing.html +18 -0
  147. package/src/playground/video-sensing.js +133 -0
  148. package/src/serialization/deserialize-assets.js +178 -0
  149. package/src/serialization/sb2.js +1295 -0
  150. package/src/serialization/sb2_specmap.js +1818 -0
  151. package/src/serialization/sb3.js +1320 -0
  152. package/src/serialization/serialize-assets.js +60 -0
  153. package/src/sprites/rendered-target.js +1115 -0
  154. package/src/sprites/sprite.js +177 -0
  155. package/src/util/base64-util.js +48 -0
  156. package/src/util/cast.js +218 -0
  157. package/src/util/clone.js +17 -0
  158. package/src/util/color.js +204 -0
  159. package/src/util/fetch-with-timeout.js +53 -0
  160. package/src/util/get-monitor-id.js +33 -0
  161. package/src/util/jsonrpc.js +114 -0
  162. package/src/util/log.js +4 -0
  163. package/src/util/math-util.js +122 -0
  164. package/src/util/maybe-format-message.js +18 -0
  165. package/src/util/new-block-ids.js +33 -0
  166. package/src/util/rateLimiter.js +73 -0
  167. package/src/util/scratch-link-websocket.js +133 -0
  168. package/src/util/string-util.js +94 -0
  169. package/src/util/task-queue.js +203 -0
  170. package/src/util/timer.js +103 -0
  171. package/src/util/uid.js +29 -0
  172. package/src/util/variable-util.js +48 -0
  173. package/src/util/xml-escape.js +33 -0
  174. package/src/virtual-machine.js +1586 -0
  175. package/webpack.config.js +134 -0
@@ -0,0 +1,203 @@
1
+ const Timer = require('../util/timer');
2
+
3
+ /**
4
+ * This class uses the token bucket algorithm to control a queue of tasks.
5
+ */
6
+ class TaskQueue {
7
+ /**
8
+ * Creates an instance of TaskQueue.
9
+ * To allow bursts, set `maxTokens` to several times the average task cost.
10
+ * To prevent bursts, set `maxTokens` to the cost of the largest tasks.
11
+ * Note that tasks with a cost greater than `maxTokens` will be rejected.
12
+ *
13
+ * @param {number} maxTokens - the maximum number of tokens in the bucket (burst size).
14
+ * @param {number} refillRate - the number of tokens to be added per second (sustain rate).
15
+ * @param {object} options - optional settings for the new task queue instance.
16
+ * @property {number} startingTokens - the number of tokens the bucket starts with (default: `maxTokens`).
17
+ * @property {number} maxTotalCost - reject a task if total queue cost would pass this limit (default: no limit).
18
+ * @memberof TaskQueue
19
+ */
20
+ constructor (maxTokens, refillRate, options = {}) {
21
+ this._maxTokens = maxTokens;
22
+ this._refillRate = refillRate;
23
+ this._pendingTaskRecords = [];
24
+ this._tokenCount = Object.prototype.hasOwnProperty.call(options, 'startingTokens') ?
25
+ options.startingTokens : maxTokens;
26
+ this._maxTotalCost = Object.prototype.hasOwnProperty.call(options, 'maxTotalCost') ?
27
+ options.maxTotalCost : Infinity;
28
+ this._timer = new Timer();
29
+ this._timer.start();
30
+ this._timeout = null;
31
+ this._lastUpdateTime = this._timer.timeElapsed();
32
+
33
+ this._runTasks = this._runTasks.bind(this);
34
+ }
35
+
36
+ /**
37
+ * Get the number of queued tasks which have not yet started.
38
+ *
39
+ * @readonly
40
+ * @memberof TaskQueue
41
+ */
42
+ get length () {
43
+ return this._pendingTaskRecords.length;
44
+ }
45
+
46
+ /**
47
+ * Wait until the token bucket is full enough, then run the provided task.
48
+ *
49
+ * @param {Function} task - the task to run.
50
+ * @param {number} [cost=1] - the number of tokens this task consumes from the bucket.
51
+ * @returns {Promise} - a promise for the task's return value.
52
+ * @memberof TaskQueue
53
+ */
54
+ do (task, cost = 1) {
55
+ if (this._maxTotalCost < Infinity) {
56
+ const currentTotalCost = this._pendingTaskRecords.reduce((t, r) => t + r.cost, 0);
57
+ if (currentTotalCost + cost > this._maxTotalCost) {
58
+ return Promise.reject(new Error('Maximum total cost exceeded'));
59
+ }
60
+ }
61
+ const newRecord = {
62
+ cost
63
+ };
64
+ newRecord.promise = new Promise((resolve, reject) => {
65
+ newRecord.cancel = () => {
66
+ reject(new Error('Task canceled'));
67
+ };
68
+
69
+ // The caller, `_runTasks()`, is responsible for cost-checking and spending tokens.
70
+ newRecord.wrappedTask = () => {
71
+ try {
72
+ resolve(task());
73
+ } catch (e) {
74
+ reject(e);
75
+ }
76
+ };
77
+ });
78
+ this._pendingTaskRecords.push(newRecord);
79
+
80
+ // If the queue has been idle we need to prime the pump
81
+ if (this._pendingTaskRecords.length === 1) {
82
+ this._runTasks();
83
+ }
84
+
85
+ return newRecord.promise;
86
+ }
87
+
88
+ /**
89
+ * Cancel one pending task, rejecting its promise.
90
+ *
91
+ * @param {Promise} taskPromise - the promise returned by `do()`.
92
+ * @returns {boolean} - true if the task was found, or false otherwise.
93
+ * @memberof TaskQueue
94
+ */
95
+ cancel (taskPromise) {
96
+ const taskIndex = this._pendingTaskRecords.findIndex(r => r.promise === taskPromise);
97
+ if (taskIndex !== -1) {
98
+ const [taskRecord] = this._pendingTaskRecords.splice(taskIndex, 1);
99
+ taskRecord.cancel();
100
+ if (taskIndex === 0 && this._pendingTaskRecords.length > 0) {
101
+ this._runTasks();
102
+ }
103
+ return true;
104
+ }
105
+ return false;
106
+ }
107
+
108
+ /**
109
+ * Cancel all pending tasks, rejecting all their promises.
110
+ *
111
+ * @memberof TaskQueue
112
+ */
113
+ cancelAll () {
114
+ if (this._timeout !== null) {
115
+ this._timer.clearTimeout(this._timeout);
116
+ this._timeout = null;
117
+ }
118
+ const oldTasks = this._pendingTaskRecords;
119
+ this._pendingTaskRecords = [];
120
+ oldTasks.forEach(r => r.cancel());
121
+ }
122
+
123
+ /**
124
+ * Shorthand for calling _refill() then _spend(cost).
125
+ *
126
+ * @see {@link TaskQueue#_refill}
127
+ * @see {@link TaskQueue#_spend}
128
+ * @param {number} cost - the number of tokens to try to spend.
129
+ * @returns {boolean} true if we had enough tokens; false otherwise.
130
+ * @memberof TaskQueue
131
+ */
132
+ _refillAndSpend (cost) {
133
+ this._refill();
134
+ return this._spend(cost);
135
+ }
136
+
137
+ /**
138
+ * Refill the token bucket based on the amount of time since the last refill.
139
+ *
140
+ * @memberof TaskQueue
141
+ */
142
+ _refill () {
143
+ const now = this._timer.timeElapsed();
144
+ const timeSinceRefill = now - this._lastUpdateTime;
145
+ if (timeSinceRefill <= 0) return;
146
+
147
+ this._lastUpdateTime = now;
148
+ this._tokenCount += timeSinceRefill * this._refillRate / 1000;
149
+ this._tokenCount = Math.min(this._tokenCount, this._maxTokens);
150
+ }
151
+
152
+ /**
153
+ * If we can "afford" the given cost, subtract that many tokens and return true.
154
+ * Otherwise, return false.
155
+ *
156
+ * @param {number} cost - the number of tokens to try to spend.
157
+ * @returns {boolean} true if we had enough tokens; false otherwise.
158
+ * @memberof TaskQueue
159
+ */
160
+ _spend (cost) {
161
+ if (cost <= this._tokenCount) {
162
+ this._tokenCount -= cost;
163
+ return true;
164
+ }
165
+ return false;
166
+ }
167
+
168
+ /**
169
+ * Loop until the task queue is empty, running each task and spending tokens to do so.
170
+ * Any time the bucket can't afford the next task, delay asynchronously until it can.
171
+ *
172
+ * @memberof TaskQueue
173
+ */
174
+ _runTasks () {
175
+ if (this._timeout) {
176
+ this._timer.clearTimeout(this._timeout);
177
+ this._timeout = null;
178
+ }
179
+ for (;;) {
180
+ const nextRecord = this._pendingTaskRecords.shift();
181
+ if (!nextRecord) {
182
+ // We ran out of work. Go idle until someone adds another task to the queue.
183
+ return;
184
+ }
185
+ if (nextRecord.cost > this._maxTokens) {
186
+ throw new Error(`Task cost ${nextRecord.cost} is greater than bucket limit ${this._maxTokens}`);
187
+ }
188
+ // Refill before each task in case the time it took for the last task to run was enough to afford the next.
189
+ if (this._refillAndSpend(nextRecord.cost)) {
190
+ nextRecord.wrappedTask();
191
+ } else {
192
+ // We can't currently afford this task. Put it back and wait until we can and try again.
193
+ this._pendingTaskRecords.unshift(nextRecord);
194
+ const tokensNeeded = Math.max(nextRecord.cost - this._tokenCount, 0);
195
+ const estimatedWait = Math.ceil(1000 * tokensNeeded / this._refillRate);
196
+ this._timeout = this._timer.setTimeout(this._runTasks, estimatedWait);
197
+ return;
198
+ }
199
+ }
200
+ }
201
+ }
202
+
203
+ module.exports = TaskQueue;
@@ -0,0 +1,103 @@
1
+ /**
2
+ * @fileoverview
3
+ * A utility for accurately measuring time.
4
+ * To use:
5
+ * ---
6
+ * var timer = new Timer();
7
+ * timer.start();
8
+ * ... pass some time ...
9
+ * var timeDifference = timer.timeElapsed();
10
+ * ---
11
+ * Or, you can use the `time` function
12
+ * to do some measurement yourself.
13
+ */
14
+
15
+ class Timer {
16
+ constructor (nowObj = Timer.nowObj) {
17
+ /**
18
+ * Used to store the start time of a timer action.
19
+ * Updated when calling `timer.start`.
20
+ */
21
+ this.startTime = 0;
22
+
23
+ /**
24
+ * Used to pass custom logic for determining the value for "now",
25
+ * which is sometimes useful for compatibility with Scratch 2
26
+ */
27
+ this.nowObj = nowObj;
28
+ }
29
+
30
+ /**
31
+ * Disable use of self.performance for now as it results in lower performance
32
+ * However, instancing it like below (caching the self.performance to a local variable) negates most of the issues.
33
+ * @type {boolean}
34
+ */
35
+ static get USE_PERFORMANCE () {
36
+ return false;
37
+ }
38
+
39
+ /**
40
+ * Legacy object to allow for us to call now to get the old style date time (for backwards compatibility)
41
+ * @deprecated This is only called via the nowObj.now() if no other means is possible...
42
+ */
43
+ static get legacyDateCode () {
44
+ return {
45
+ now: function () {
46
+ return new Date().getTime();
47
+ }
48
+ };
49
+ }
50
+
51
+ /**
52
+ * Use this object to route all time functions through single access points.
53
+ */
54
+ static get nowObj () {
55
+ if (Timer.USE_PERFORMANCE && typeof self !== 'undefined' && self.performance && 'now' in self.performance) {
56
+ return self.performance;
57
+ } else if (Date.now) {
58
+ return Date;
59
+ }
60
+ return Timer.legacyDateCode;
61
+ }
62
+
63
+ /**
64
+ * Return the currently known absolute time, in ms precision.
65
+ * @returns {number} ms elapsed since 1 January 1970 00:00:00 UTC.
66
+ */
67
+ time () {
68
+ return this.nowObj.now();
69
+ }
70
+
71
+ /**
72
+ * Start a timer for measuring elapsed time,
73
+ * at the most accurate precision possible.
74
+ */
75
+ start () {
76
+ this.startTime = this.nowObj.now();
77
+ }
78
+
79
+ timeElapsed () {
80
+ return this.nowObj.now() - this.startTime;
81
+ }
82
+
83
+ /**
84
+ * Call a handler function after a specified amount of time has elapsed.
85
+ * @param {function} handler - function to call after the timeout
86
+ * @param {number} timeout - number of milliseconds to delay before calling the handler
87
+ * @returns {number} - the ID of the new timeout
88
+ */
89
+ setTimeout (handler, timeout) {
90
+ return global.setTimeout(handler, timeout);
91
+ }
92
+
93
+ /**
94
+ * Clear a timeout from the pending timeout pool.
95
+ * @param {number} timeoutId - the ID returned by `setTimeout()`
96
+ * @memberof Timer
97
+ */
98
+ clearTimeout (timeoutId) {
99
+ global.clearTimeout(timeoutId);
100
+ }
101
+ }
102
+
103
+ module.exports = Timer;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * @fileoverview UID generator, from Blockly.
3
+ */
4
+
5
+ /**
6
+ * Legal characters for the unique ID.
7
+ * Should be all on a US keyboard. No XML special characters or control codes.
8
+ * Removed $ due to issue 251.
9
+ * @private
10
+ */
11
+ const soup_ = '!#%()*+,-./:;=?@[]^_`{|}~' +
12
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
13
+
14
+ /**
15
+ * Generate a unique ID, from Blockly. This should be globally unique.
16
+ * 87 characters ^ 20 length > 128 bits (better than a UUID).
17
+ * @return {string} A globally unique ID string.
18
+ */
19
+ const uid = function () {
20
+ const length = 20;
21
+ const soupLength = soup_.length;
22
+ const id = [];
23
+ for (let i = 0; i < length; i++) {
24
+ id[i] = soup_.charAt(Math.random() * soupLength);
25
+ }
26
+ return id.join('');
27
+ };
28
+
29
+ module.exports = uid;
@@ -0,0 +1,48 @@
1
+ class VariableUtil {
2
+ static _mergeVarRefObjects (accum, obj2) {
3
+ for (const id in obj2) {
4
+ if (accum[id]) {
5
+ accum[id] = accum[id].concat(obj2[id]);
6
+ } else {
7
+ accum[id] = obj2[id];
8
+ }
9
+ }
10
+ return accum;
11
+ }
12
+
13
+ /**
14
+ * Get all variable/list references in the given list of targets
15
+ * in the project.
16
+ * @param {Array.<Target>} targets The list of targets to get the variable
17
+ * and list references from.
18
+ * @param {boolean} shouldIncludeBroadcast Whether to include broadcast message fields.
19
+ * @return {object} An object with variable ids as the keys and a list of block fields referencing
20
+ * the variable.
21
+ */
22
+ static getAllVarRefsForTargets (targets, shouldIncludeBroadcast) {
23
+ return targets
24
+ .map(t => t.blocks.getAllVariableAndListReferences(null, shouldIncludeBroadcast))
25
+ .reduce(VariableUtil._mergeVarRefObjects, {});
26
+ }
27
+
28
+ /**
29
+ * Give all variable references provided a new id and possibly new name.
30
+ * @param {Array<object>} referencesToUpdate Context of the change, the object containing variable
31
+ * references to update.
32
+ * @param {string} newId ID of the variable that the old references should be replaced with
33
+ * @param {?string} optNewName New variable name to merge with. The old
34
+ * variable name in the references being updated should be replaced with this new name.
35
+ * If this parameter is not provided or is '', no name change occurs.
36
+ */
37
+ static updateVariableIdentifiers (referencesToUpdate, newId, optNewName) {
38
+ referencesToUpdate.map(ref => {
39
+ ref.referencingField.id = newId;
40
+ if (optNewName) {
41
+ ref.referencingField.value = optNewName;
42
+ }
43
+ return ref;
44
+ });
45
+ }
46
+ }
47
+
48
+ module.exports = VariableUtil;
@@ -0,0 +1,33 @@
1
+ const log = require('./log');
2
+
3
+ /**
4
+ * Escape a string to be safe to use in XML content.
5
+ * CC-BY-SA: hgoebl
6
+ * https://stackoverflow.com/questions/7918868/
7
+ * how-to-escape-xml-entities-in-javascript
8
+ * @param {!string | !Array.<string>} unsafe Unsafe string.
9
+ * @return {string} XML-escaped string, for use within an XML tag.
10
+ */
11
+ const xmlEscape = function (unsafe) {
12
+ if (typeof unsafe !== 'string') {
13
+ if (Array.isArray(unsafe)) {
14
+ // This happens when we have hacked blocks from 2.0
15
+ // See #1030
16
+ unsafe = String(unsafe);
17
+ } else {
18
+ log.error('Unexpected input recieved in replaceUnsafeChars');
19
+ return unsafe;
20
+ }
21
+ }
22
+ return unsafe.replace(/[<>&'"]/g, c => {
23
+ switch (c) {
24
+ case '<': return '&lt;';
25
+ case '>': return '&gt;';
26
+ case '&': return '&amp;';
27
+ case '\'': return '&apos;';
28
+ case '"': return '&quot;';
29
+ }
30
+ });
31
+ };
32
+
33
+ module.exports = xmlEscape;