moonscratch 0.1.1 → 0.1.2

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 (149) hide show
  1. package/dist/chunk-DQk6qfdC.mjs +18 -0
  2. package/dist/index.d.mts +1173 -0
  3. package/dist/index.mjs +27135 -0
  4. package/package.json +6 -1
  5. package/.agents/skills/moonbit-agent-guide/LICENSE +0 -202
  6. package/.agents/skills/moonbit-agent-guide/SKILL.mbt.md +0 -1126
  7. package/.agents/skills/moonbit-agent-guide/SKILL.md +0 -1126
  8. package/.agents/skills/moonbit-agent-guide/ide.md +0 -116
  9. package/.agents/skills/moonbit-agent-guide/references/advanced-moonbit-build.md +0 -106
  10. package/.agents/skills/moonbit-agent-guide/references/moonbit-language-fundamentals.mbt.md +0 -422
  11. package/.agents/skills/moonbit-agent-guide/references/moonbit-language-fundamentals.md +0 -422
  12. package/.agents/skills/moonbit-practice/SKILL.md +0 -258
  13. package/.agents/skills/moonbit-practice/assets/ci.yaml +0 -25
  14. package/.agents/skills/moonbit-practice/reference/agents.md +0 -1469
  15. package/.agents/skills/moonbit-practice/reference/configuration.md +0 -228
  16. package/.agents/skills/moonbit-practice/reference/ffi.md +0 -229
  17. package/.agents/skills/moonbit-practice/reference/ide.md +0 -189
  18. package/.agents/skills/moonbit-practice/reference/performance.md +0 -217
  19. package/.agents/skills/moonbit-practice/reference/refactor.md +0 -154
  20. package/.agents/skills/moonbit-practice/reference/stdlib.md +0 -351
  21. package/.agents/skills/moonbit-practice/reference/testing.md +0 -228
  22. package/.agents/skills/moonbit-refactoring/LICENSE +0 -21
  23. package/.agents/skills/moonbit-refactoring/SKILL.md +0 -323
  24. package/.githooks/README.md +0 -23
  25. package/.githooks/pre-commit +0 -3
  26. package/.github/workflows/copilot-setup-steps.yml +0 -40
  27. package/AGENTS.md +0 -91
  28. package/PLAN.md +0 -64
  29. package/TODO.md +0 -120
  30. package/benchmarks/calc.bench.ts +0 -144
  31. package/benchmarks/draw.bench.ts +0 -215
  32. package/benchmarks/load.bench.ts +0 -28
  33. package/benchmarks/render.bench.ts +0 -53
  34. package/benchmarks/run.bench.ts +0 -8
  35. package/benchmarks/types.d.ts +0 -15
  36. package/docs/scratch-vm-specs/eventloop.md +0 -103
  37. package/docs/scratch-vm-specs/moonscratch-time-separation.md +0 -50
  38. package/index.html +0 -91
  39. package/js/AGENTS.md +0 -5
  40. package/js/a.ts +0 -52
  41. package/js/assets/AGENTS.md +0 -5
  42. package/js/assets/base64.test.ts +0 -14
  43. package/js/assets/base64.ts +0 -21
  44. package/js/assets/build-asset.test.ts +0 -26
  45. package/js/assets/build-asset.ts +0 -28
  46. package/js/assets/create.test.ts +0 -142
  47. package/js/assets/create.ts +0 -122
  48. package/js/assets/index.test.ts +0 -15
  49. package/js/assets/index.ts +0 -2
  50. package/js/assets/types.ts +0 -26
  51. package/js/assets/validation.test.ts +0 -34
  52. package/js/assets/validation.ts +0 -25
  53. package/js/assets.test.ts +0 -14
  54. package/js/assets.ts +0 -1
  55. package/js/index.test.ts +0 -26
  56. package/js/index.ts +0 -3
  57. package/js/render/index.test.ts +0 -65
  58. package/js/render/index.ts +0 -13
  59. package/js/render/sharp.ts +0 -87
  60. package/js/render/svg.ts +0 -68
  61. package/js/render/types.ts +0 -35
  62. package/js/render/utils.ts +0 -108
  63. package/js/render/webgl.ts +0 -274
  64. package/js/sharp-optional.d.ts +0 -16
  65. package/js/test/helpers.ts +0 -116
  66. package/js/test/hikkaku-sample.test.ts +0 -37
  67. package/js/test/rubik-components.input-motion.test.ts +0 -60
  68. package/js/test/rubik-components.lists.test.ts +0 -49
  69. package/js/test/rubik-components.operators.test.ts +0 -104
  70. package/js/test/rubik-components.pen.test.ts +0 -112
  71. package/js/test/rubik-components.procedures-loops.test.ts +0 -72
  72. package/js/test/rubik-components.variables-branches.test.ts +0 -57
  73. package/js/test/rubik-components.visibility-entry.test.ts +0 -31
  74. package/js/test/test-projects.ts +0 -598
  75. package/js/test/variable.ts +0 -200
  76. package/js/test/warp.test.ts +0 -59
  77. package/js/vm/AGENTS.md +0 -6
  78. package/js/vm/README.md +0 -183
  79. package/js/vm/bindings.test.ts +0 -13
  80. package/js/vm/bindings.ts +0 -5
  81. package/js/vm/compare-operators.test.ts +0 -145
  82. package/js/vm/constants.test.ts +0 -11
  83. package/js/vm/constants.ts +0 -4
  84. package/js/vm/effect-guards.test.ts +0 -68
  85. package/js/vm/effect-guards.ts +0 -44
  86. package/js/vm/factory.test.ts +0 -486
  87. package/js/vm/factory.ts +0 -615
  88. package/js/vm/headless-vm.test.ts +0 -131
  89. package/js/vm/headless-vm.ts +0 -342
  90. package/js/vm/index.test.ts +0 -28
  91. package/js/vm/index.ts +0 -5
  92. package/js/vm/internal-types.ts +0 -32
  93. package/js/vm/json.test.ts +0 -40
  94. package/js/vm/json.ts +0 -273
  95. package/js/vm/normalize.test.ts +0 -48
  96. package/js/vm/normalize.ts +0 -65
  97. package/js/vm/options.test.ts +0 -30
  98. package/js/vm/options.ts +0 -55
  99. package/js/vm/pen-transparency.test.ts +0 -115
  100. package/js/vm/program-wasm.ts +0 -322
  101. package/js/vm/scheduler-render.test.ts +0 -401
  102. package/js/vm/scratch-assets.test.ts +0 -136
  103. package/js/vm/scratch-assets.ts +0 -202
  104. package/js/vm/types.ts +0 -358
  105. package/js/vm/value-guards.test.ts +0 -25
  106. package/js/vm/value-guards.ts +0 -18
  107. package/moon.mod.json +0 -10
  108. package/scripts/preinstall.ts +0 -4
  109. package/src/AGENTS.md +0 -6
  110. package/src/api.mbt +0 -161
  111. package/src/api_aot_commands.mbt +0 -184
  112. package/src/api_effects_json.mbt +0 -72
  113. package/src/api_options.mbt +0 -60
  114. package/src/api_program_wasm.mbt +0 -1647
  115. package/src/api_program_wat.mbt +0 -2206
  116. package/src/api_snapshot_json.mbt +0 -44
  117. package/src/cmd/AGENTS.md +0 -5
  118. package/src/cmd/main/AGENTS.md +0 -5
  119. package/src/cmd/main/main.mbt +0 -29
  120. package/src/cmd/main/moon.pkg +0 -7
  121. package/src/cmd/main/pkg.generated.mbti +0 -13
  122. package/src/json_helpers.mbt +0 -176
  123. package/src/moon.pkg +0 -65
  124. package/src/moonscratch.mbt +0 -3
  125. package/src/moonscratch_wbtest.mbt +0 -40
  126. package/src/parser_sb3.mbt +0 -890
  127. package/src/pkg.generated.mbti +0 -479
  128. package/src/runtime_eval.mbt +0 -2844
  129. package/src/runtime_exec.mbt +0 -3850
  130. package/src/runtime_render.mbt +0 -2550
  131. package/src/runtime_state.mbt +0 -870
  132. package/src/test/AGENTS.md +0 -3
  133. package/src/test/projects/AGENTS.md +0 -6
  134. package/src/test/projects/moon.pkg +0 -4
  135. package/src/test/projects/moonscratch_compat_test.mbt +0 -642
  136. package/src/test/projects/moonscratch_core_test.mbt +0 -1332
  137. package/src/test/projects/moonscratch_runtime_test.mbt +0 -1087
  138. package/src/test/projects/pkg.generated.mbti +0 -13
  139. package/src/test/projects/test_support.mbt +0 -35
  140. package/src/types_effects.mbt +0 -20
  141. package/src/types_error.mbt +0 -4
  142. package/src/types_options.mbt +0 -31
  143. package/src/types_runtime_structs.mbt +0 -254
  144. package/src/types_vm.mbt +0 -109
  145. package/tsconfig.json +0 -29
  146. package/viewer/index.ts +0 -399
  147. package/viewer/vite.d.ts +0 -1
  148. package/viewer/worker.ts +0 -161
  149. package/vite.config.ts +0 -61
@@ -1,2844 +0,0 @@
1
- ///|
2
- fn json_is_null(value : Json) -> Bool {
3
- match value {
4
- Null => true
5
- _ => false
6
- }
7
- }
8
-
9
- ///|
10
- fn block_input_payload(block : ScratchBlock, input_name : String) -> Json? {
11
- match block.inputs.get(input_name) {
12
- Some(Array(parts)) =>
13
- if parts.length() >= 3 && json_is_null(parts[1]) {
14
- Some(parts[2])
15
- } else if parts.length() >= 2 {
16
- Some(parts[1])
17
- } else {
18
- None
19
- }
20
- Some(value) => Some(value)
21
- None => None
22
- }
23
- }
24
-
25
- ///|
26
- fn block_input_block_id(block : ScratchBlock, input_name : String) -> String? {
27
- match block.input_block_ids.get(input_name) {
28
- Some(id) => return Some(id)
29
- None => ()
30
- }
31
- match block_input_payload(block, input_name) {
32
- Some(String(id)) => Some(id)
33
- _ => None
34
- }
35
- }
36
-
37
- ///|
38
- fn decode_primitive(
39
- vm : Vm,
40
- target_index : Int,
41
- primitive : Array[Json],
42
- ) -> Json {
43
- if primitive.length() < 2 {
44
- return Json::null()
45
- }
46
- let code = json_to_number_value(primitive[0]).to_int()
47
- match code {
48
- 4 | 5 | 6 | 7 | 8 => json_number(json_to_number_value(primitive[1]))
49
- 9 => primitive[1]
50
- 10 => json_string(json_to_string_value(primitive[1]))
51
- 11 => json_string(json_to_string_value(primitive[1]))
52
- 12 => {
53
- let variable_name = json_to_string_value(primitive[1])
54
- let variable_id = if primitive.length() >= 3 {
55
- Some(json_to_string_value(primitive[2]))
56
- } else {
57
- None
58
- }
59
- read_variable(vm, target_index, variable_id, Some(variable_name))
60
- }
61
- 13 => {
62
- let list_name = json_to_string_value(primitive[1])
63
- let list_id = if primitive.length() >= 3 {
64
- Some(json_to_string_value(primitive[2]))
65
- } else {
66
- None
67
- }
68
- let items = read_list(vm, target_index, list_id, Some(list_name))
69
- json_string(items.map(json_to_string_value).join(" "))
70
- }
71
- _ => primitive[1]
72
- }
73
- }
74
-
75
- ///|
76
- fn field_value(block : ScratchBlock, field_name : String) -> (String, String?)? {
77
- match block.fields.get(field_name) {
78
- Some(Array(parts)) =>
79
- if parts.length() >= 2 {
80
- Some(
81
- (json_to_string_value(parts[0]), Some(json_to_string_value(parts[1]))),
82
- )
83
- } else if parts.length() == 1 {
84
- Some((json_to_string_value(parts[0]), None))
85
- } else {
86
- None
87
- }
88
- Some(String(value)) => Some((value, None))
89
- Some(value) => Some((json_to_string_value(value), None))
90
- None => None
91
- }
92
- }
93
-
94
- ///|
95
- fn value_from_input(
96
- vm : Vm,
97
- target_index : Int,
98
- block : ScratchBlock,
99
- input_name : String,
100
- depth : Int,
101
- ) -> Json {
102
- match block.input_block_ids.get(input_name) {
103
- Some(block_id) =>
104
- return eval_reporter_block_depth(vm, target_index, block_id, depth + 1)
105
- None => ()
106
- }
107
- match block_input_payload(block, input_name) {
108
- Some(String(block_id)) =>
109
- eval_reporter_block_depth(vm, target_index, block_id, depth + 1)
110
- Some(Array(primitive)) => decode_primitive(vm, target_index, primitive)
111
- Some(value) => value
112
- None => Json::null()
113
- }
114
- }
115
-
116
- ///|
117
- fn compile_numeric_operand_ops(
118
- vm : Vm,
119
- target_index : Int,
120
- target : TargetState,
121
- block : ScratchBlock,
122
- input_name : String,
123
- ops : Array[NumericReporterOp],
124
- visiting : Map[String, Bool],
125
- depth : Int,
126
- ) -> Bool {
127
- match block.const_number_inputs.get(input_name) {
128
- Some(value) => {
129
- ops.push(NumericReporterOp::PushConst(value))
130
- true
131
- }
132
- None =>
133
- match block.input_block_ids.get(input_name) {
134
- Some(child_id) =>
135
- compile_numeric_reporter_ops(
136
- vm,
137
- target_index,
138
- target,
139
- child_id,
140
- ops,
141
- visiting,
142
- depth + 1,
143
- )
144
- None => false
145
- }
146
- }
147
- }
148
-
149
- ///|
150
- fn compile_numeric_reporter_ops(
151
- vm : Vm,
152
- target_index : Int,
153
- target : TargetState,
154
- block_id : String,
155
- ops : Array[NumericReporterOp],
156
- visiting : Map[String, Bool],
157
- depth : Int,
158
- ) -> Bool {
159
- if depth > 64 || visiting.contains(block_id) {
160
- return false
161
- }
162
- visiting[block_id] = true
163
- let block = match target.blocks.get(block_id) {
164
- Some(value) => value
165
- None => {
166
- visiting.remove(block_id)
167
- return false
168
- }
169
- }
170
-
171
- let compiled = match block.opcode {
172
- "math_number"
173
- | "math_integer"
174
- | "math_whole_number"
175
- | "math_positive_number"
176
- | "math_angle" =>
177
- match field_value(block, "NUM") {
178
- Some((value, _)) => {
179
- ops.push(
180
- NumericReporterOp::PushConst(
181
- match parse_double_or_none(value) {
182
- Some(parsed) => parsed
183
- None => 0.0
184
- },
185
- ),
186
- )
187
- true
188
- }
189
- None => {
190
- ops.push(NumericReporterOp::PushConst(0.0))
191
- true
192
- }
193
- }
194
- "data_variable" =>
195
- match field_value(block, "VARIABLE") {
196
- Some((name, id)) => {
197
- let encoded_id = match id {
198
- Some(value) => value
199
- None => ""
200
- }
201
- let resolved = match id {
202
- Some(variable_id) =>
203
- resolve_variable_ref_by_id(vm, target_index, variable_id)
204
- None => resolve_variable_ref(vm, target_index, None, Some(name))
205
- }
206
- match resolved {
207
- Some((owner_index, variable_id, variable_slot)) => {
208
- let owner_kind = if owner_index == vm.stage_index { 1 } else { 0 }
209
- ops.push(
210
- NumericReporterOp::LoadVariableByRef(
211
- owner_kind, variable_id, variable_slot,
212
- ),
213
- )
214
- }
215
- None => ops.push(NumericReporterOp::LoadVariable(encoded_id, name))
216
- }
217
- true
218
- }
219
- None => false
220
- }
221
- "operator_add" =>
222
- if compile_numeric_operand_ops(
223
- vm, target_index, target, block, "NUM1", ops, visiting, depth,
224
- ) &&
225
- compile_numeric_operand_ops(
226
- vm, target_index, target, block, "NUM2", ops, visiting, depth,
227
- ) {
228
- ops.push(NumericReporterOp::Add)
229
- true
230
- } else {
231
- false
232
- }
233
- "operator_subtract" =>
234
- if compile_numeric_operand_ops(
235
- vm, target_index, target, block, "NUM1", ops, visiting, depth,
236
- ) &&
237
- compile_numeric_operand_ops(
238
- vm, target_index, target, block, "NUM2", ops, visiting, depth,
239
- ) {
240
- ops.push(NumericReporterOp::Subtract)
241
- true
242
- } else {
243
- false
244
- }
245
- "operator_multiply" =>
246
- if compile_numeric_operand_ops(
247
- vm, target_index, target, block, "NUM1", ops, visiting, depth,
248
- ) &&
249
- compile_numeric_operand_ops(
250
- vm, target_index, target, block, "NUM2", ops, visiting, depth,
251
- ) {
252
- ops.push(NumericReporterOp::Multiply)
253
- true
254
- } else {
255
- false
256
- }
257
- "operator_divide" =>
258
- if compile_numeric_operand_ops(
259
- vm, target_index, target, block, "NUM1", ops, visiting, depth,
260
- ) &&
261
- compile_numeric_operand_ops(
262
- vm, target_index, target, block, "NUM2", ops, visiting, depth,
263
- ) {
264
- ops.push(NumericReporterOp::Divide)
265
- true
266
- } else {
267
- false
268
- }
269
- "operator_mod" =>
270
- if compile_numeric_operand_ops(
271
- vm, target_index, target, block, "NUM1", ops, visiting, depth,
272
- ) &&
273
- compile_numeric_operand_ops(
274
- vm, target_index, target, block, "NUM2", ops, visiting, depth,
275
- ) {
276
- ops.push(NumericReporterOp::Mod)
277
- true
278
- } else {
279
- false
280
- }
281
- "operator_round" =>
282
- if compile_numeric_operand_ops(
283
- vm, target_index, target, block, "NUM", ops, visiting, depth,
284
- ) {
285
- ops.push(NumericReporterOp::Round)
286
- true
287
- } else {
288
- false
289
- }
290
- "operator_mathop" => {
291
- let op = match field_value(block, "OPERATOR") {
292
- Some((name, _)) => name
293
- None => ""
294
- }
295
- if compile_numeric_operand_ops(
296
- vm, target_index, target, block, "NUM", ops, visiting, depth,
297
- ) {
298
- ops.push(NumericReporterOp::MathOp(op))
299
- true
300
- } else {
301
- false
302
- }
303
- }
304
- "motion_xposition" => {
305
- ops.push(NumericReporterOp::LoadXPosition)
306
- true
307
- }
308
- "motion_yposition" => {
309
- ops.push(NumericReporterOp::LoadYPosition)
310
- true
311
- }
312
- "motion_direction" => {
313
- ops.push(NumericReporterOp::LoadDirection)
314
- true
315
- }
316
- "looks_size" => {
317
- ops.push(NumericReporterOp::LoadSize)
318
- true
319
- }
320
- "sound_volume" => {
321
- ops.push(NumericReporterOp::LoadVolume)
322
- true
323
- }
324
- "music_getTempo" => {
325
- ops.push(NumericReporterOp::LoadTempo)
326
- true
327
- }
328
- "sensing_timer" => {
329
- ops.push(NumericReporterOp::LoadTimer)
330
- true
331
- }
332
- "sensing_mousex" => {
333
- ops.push(NumericReporterOp::LoadMouseX)
334
- true
335
- }
336
- "sensing_mousey" => {
337
- ops.push(NumericReporterOp::LoadMouseY)
338
- true
339
- }
340
- "control_get_counter" => {
341
- ops.push(NumericReporterOp::LoadControlCounter)
342
- true
343
- }
344
- _ => false
345
- }
346
- visiting.remove(block_id)
347
- compiled
348
- }
349
-
350
- ///|
351
- fn compile_numeric_reporter_program(
352
- vm : Vm,
353
- target_index : Int,
354
- target : TargetState,
355
- block_id : String,
356
- ) -> NumericReporterProgram? {
357
- let ops = []
358
- let visiting = {}
359
- if compile_numeric_reporter_ops(
360
- vm, target_index, target, block_id, ops, visiting, 0,
361
- ) {
362
- Some({ ops, })
363
- } else {
364
- None
365
- }
366
- }
367
-
368
- ///|
369
- fn eval_numeric_reporter_program(
370
- vm : Vm,
371
- target_index : Int,
372
- program : NumericReporterProgram,
373
- ) -> Double? {
374
- let stack = []
375
- for op in program.ops {
376
- match op {
377
- NumericReporterOp::PushConst(value) => stack.push(value)
378
- NumericReporterOp::LoadVariable(encoded_id, name) => {
379
- let variable_id = if encoded_id == "" { None } else { Some(encoded_id) }
380
- stack.push(
381
- json_to_number_value(
382
- read_variable(vm, target_index, variable_id, Some(name)),
383
- ),
384
- )
385
- }
386
- NumericReporterOp::LoadVariableByRef(owner_kind, variable_id, slot) => {
387
- let owner_index = if owner_kind == 1 {
388
- vm.stage_index
389
- } else {
390
- target_index
391
- }
392
- stack.push(
393
- json_to_number_value(
394
- read_variable_by_ref(vm, owner_index, variable_id, slot),
395
- ),
396
- )
397
- }
398
- NumericReporterOp::Add =>
399
- match (stack.pop(), stack.pop()) {
400
- (Some(right), Some(left)) => stack.push(left + right)
401
- _ => return None
402
- }
403
- NumericReporterOp::Subtract =>
404
- match (stack.pop(), stack.pop()) {
405
- (Some(right), Some(left)) => stack.push(left - right)
406
- _ => return None
407
- }
408
- NumericReporterOp::Multiply =>
409
- match (stack.pop(), stack.pop()) {
410
- (Some(right), Some(left)) => stack.push(left * right)
411
- _ => return None
412
- }
413
- NumericReporterOp::Divide =>
414
- match (stack.pop(), stack.pop()) {
415
- (Some(right), Some(left)) =>
416
- if right == 0.0 {
417
- stack.push(0.0)
418
- } else {
419
- stack.push(left / right)
420
- }
421
- _ => return None
422
- }
423
- NumericReporterOp::Mod =>
424
- match (stack.pop(), stack.pop()) {
425
- (Some(right), Some(left)) =>
426
- if right == 0.0 {
427
- stack.push(0.0)
428
- } else {
429
- stack.push(left.mod(right))
430
- }
431
- _ => return None
432
- }
433
- NumericReporterOp::Round =>
434
- match stack.pop() {
435
- Some(value) => stack.push(value.round())
436
- None => return None
437
- }
438
- NumericReporterOp::MathOp(name) =>
439
- match stack.pop() {
440
- Some(value) => stack.push(mathop(vm, name, value))
441
- None => return None
442
- }
443
- NumericReporterOp::LoadXPosition => stack.push(vm.targets[target_index].x)
444
- NumericReporterOp::LoadYPosition => stack.push(vm.targets[target_index].y)
445
- NumericReporterOp::LoadDirection =>
446
- stack.push(vm.targets[target_index].direction)
447
- NumericReporterOp::LoadSize => stack.push(vm.targets[target_index].size)
448
- NumericReporterOp::LoadVolume =>
449
- stack.push(vm.targets[target_index].volume)
450
- NumericReporterOp::LoadTempo => stack.push(vm.music_tempo)
451
- NumericReporterOp::LoadTimer =>
452
- stack.push(Double::from_int(vm.now_ms - vm.timer_start_ms) / 1000.0)
453
- NumericReporterOp::LoadMouseX => {
454
- let (x, _) = read_mouse_xy(vm)
455
- stack.push(x)
456
- }
457
- NumericReporterOp::LoadMouseY => {
458
- let (_, y) = read_mouse_xy(vm)
459
- stack.push(y)
460
- }
461
- NumericReporterOp::LoadControlCounter =>
462
- stack.push(Double::from_int(vm.control_counter))
463
- }
464
- }
465
- if stack.length() == 1 {
466
- Some(stack[0])
467
- } else {
468
- None
469
- }
470
- }
471
-
472
- ///|
473
- fn get_compiled_numeric_reporter_program(
474
- vm : Vm,
475
- target_index : Int,
476
- block_id : String,
477
- ) -> NumericReporterProgram? {
478
- if target_index < 0 || target_index >= vm.targets.length() {
479
- return None
480
- }
481
- let target = vm.targets[target_index]
482
- match target.numeric_program_cache.get(block_id) {
483
- Some(program) => Some(program)
484
- None =>
485
- if target.numeric_program_compile_failed.contains(block_id) {
486
- None
487
- } else {
488
- match
489
- compile_numeric_reporter_program(vm, target_index, target, block_id) {
490
- Some(program) => {
491
- vm.targets[target_index].numeric_program_cache[block_id] = program
492
- Some(program)
493
- }
494
- None => {
495
- vm.targets[target_index].numeric_program_compile_failed[block_id] = true
496
- None
497
- }
498
- }
499
- }
500
- }
501
- }
502
-
503
- ///|
504
- fn is_strict_numeric_reporter_for_compare(
505
- target : TargetState,
506
- block_id : String,
507
- depth : Int,
508
- ) -> Bool {
509
- if depth > 64 {
510
- return false
511
- }
512
- let block = match target.blocks.get(block_id) {
513
- Some(value) => value
514
- None => return false
515
- }
516
- match block.opcode {
517
- "math_number"
518
- | "math_integer"
519
- | "math_whole_number"
520
- | "math_positive_number"
521
- | "math_angle"
522
- | "operator_add"
523
- | "operator_subtract"
524
- | "operator_multiply"
525
- | "operator_divide"
526
- | "operator_mod"
527
- | "operator_round"
528
- | "operator_mathop"
529
- | "operator_random"
530
- | "motion_xposition"
531
- | "motion_yposition"
532
- | "motion_direction"
533
- | "looks_size"
534
- | "sound_volume"
535
- | "sensing_timer"
536
- | "sensing_mousex"
537
- | "sensing_mousey"
538
- | "sensing_current"
539
- | "sensing_dayssince2000"
540
- | "sensing_loudness"
541
- | "music_getTempo"
542
- | "control_get_counter" => true
543
- _ => false
544
- }
545
- }
546
-
547
- ///|
548
- fn is_strict_numeric_input_for_compare(
549
- target : TargetState,
550
- block : ScratchBlock,
551
- input_name : String,
552
- ) -> Bool {
553
- if block.const_number_inputs.contains(input_name) {
554
- return true
555
- }
556
- match block.input_block_ids.get(input_name) {
557
- Some(block_id) =>
558
- is_strict_numeric_reporter_for_compare(target, block_id, 0)
559
- None => false
560
- }
561
- }
562
-
563
- ///|
564
- fn compile_compare_input_guard(
565
- vm : Vm,
566
- target_index : Int,
567
- target : TargetState,
568
- block : ScratchBlock,
569
- input_name : String,
570
- ) -> CompareNumericGuard? {
571
- match block.input_block_ids.get(input_name) {
572
- Some(block_id) =>
573
- match target.blocks.get(block_id) {
574
- Some(input_block) =>
575
- if input_block.opcode == "data_variable" {
576
- match field_value(input_block, "VARIABLE") {
577
- Some((name, id)) => {
578
- let resolved = match id {
579
- Some(variable_id) =>
580
- resolve_variable_ref_by_id(vm, target_index, variable_id)
581
- None =>
582
- resolve_variable_ref(vm, target_index, None, Some(name))
583
- }
584
- match resolved {
585
- Some((owner_index, variable_id, slot)) =>
586
- Some({
587
- owner_kind: if owner_index == vm.stage_index {
588
- 1
589
- } else {
590
- 0
591
- },
592
- variable_id,
593
- slot,
594
- })
595
- None => None
596
- }
597
- }
598
- None => None
599
- }
600
- } else {
601
- None
602
- }
603
- None => None
604
- }
605
- None => None
606
- }
607
- }
608
-
609
- ///|
610
- fn compile_numeric_input_program_for_bool(
611
- vm : Vm,
612
- target_index : Int,
613
- target : TargetState,
614
- block : ScratchBlock,
615
- input_name : String,
616
- numeric_programs : Array[NumericReporterProgram],
617
- ) -> Int? {
618
- match block.const_number_inputs.get(input_name) {
619
- Some(value) => {
620
- numeric_programs.push({ ops: [NumericReporterOp::PushConst(value)] })
621
- Some(numeric_programs.length() - 1)
622
- }
623
- None =>
624
- match block.input_block_ids.get(input_name) {
625
- Some(block_id) =>
626
- match
627
- compile_numeric_reporter_program(vm, target_index, target, block_id) {
628
- Some(program) => {
629
- numeric_programs.push(program)
630
- Some(numeric_programs.length() - 1)
631
- }
632
- None => None
633
- }
634
- None => None
635
- }
636
- }
637
- }
638
-
639
- ///|
640
- fn compile_bool_reporter_ops(
641
- vm : Vm,
642
- target_index : Int,
643
- target : TargetState,
644
- block_id : String,
645
- numeric_programs : Array[NumericReporterProgram],
646
- ops : Array[BoolReporterOp],
647
- visiting : Map[String, Bool],
648
- depth : Int,
649
- ) -> Bool {
650
- if depth > 64 || visiting.contains(block_id) {
651
- return false
652
- }
653
- visiting[block_id] = true
654
- let block = match target.blocks.get(block_id) {
655
- Some(value) => value
656
- None => {
657
- visiting.remove(block_id)
658
- return false
659
- }
660
- }
661
- let compiled = match block.opcode {
662
- "operator_and" =>
663
- match
664
- (
665
- block.input_block_ids.get("OPERAND1"),
666
- block.input_block_ids.get("OPERAND2"),
667
- ) {
668
- (Some(left_id), Some(right_id)) =>
669
- if compile_bool_reporter_ops(
670
- vm,
671
- target_index,
672
- target,
673
- left_id,
674
- numeric_programs,
675
- ops,
676
- visiting,
677
- depth + 1,
678
- ) &&
679
- compile_bool_reporter_ops(
680
- vm,
681
- target_index,
682
- target,
683
- right_id,
684
- numeric_programs,
685
- ops,
686
- visiting,
687
- depth + 1,
688
- ) {
689
- ops.push(BoolReporterOp::And)
690
- true
691
- } else {
692
- false
693
- }
694
- _ => false
695
- }
696
- "operator_or" =>
697
- match
698
- (
699
- block.input_block_ids.get("OPERAND1"),
700
- block.input_block_ids.get("OPERAND2"),
701
- ) {
702
- (Some(left_id), Some(right_id)) =>
703
- if compile_bool_reporter_ops(
704
- vm,
705
- target_index,
706
- target,
707
- left_id,
708
- numeric_programs,
709
- ops,
710
- visiting,
711
- depth + 1,
712
- ) &&
713
- compile_bool_reporter_ops(
714
- vm,
715
- target_index,
716
- target,
717
- right_id,
718
- numeric_programs,
719
- ops,
720
- visiting,
721
- depth + 1,
722
- ) {
723
- ops.push(BoolReporterOp::Or)
724
- true
725
- } else {
726
- false
727
- }
728
- _ => false
729
- }
730
- "operator_not" =>
731
- match block.input_block_ids.get("OPERAND") {
732
- Some(input_id) =>
733
- if compile_bool_reporter_ops(
734
- vm,
735
- target_index,
736
- target,
737
- input_id,
738
- numeric_programs,
739
- ops,
740
- visiting,
741
- depth + 1,
742
- ) {
743
- ops.push(BoolReporterOp::Not)
744
- true
745
- } else {
746
- false
747
- }
748
- None => false
749
- }
750
- "operator_lt" =>
751
- match
752
- (
753
- compile_numeric_input_program_for_bool(
754
- vm, target_index, target, block, "OPERAND1", numeric_programs,
755
- ),
756
- compile_numeric_input_program_for_bool(
757
- vm, target_index, target, block, "OPERAND2", numeric_programs,
758
- ),
759
- ) {
760
- (Some(left_index), Some(right_index)) => {
761
- let strict_left = is_strict_numeric_input_for_compare(
762
- target, block, "OPERAND1",
763
- )
764
- let strict_right = is_strict_numeric_input_for_compare(
765
- target, block, "OPERAND2",
766
- )
767
- ops.push(
768
- BoolReporterOp::CompareLt(
769
- left_index,
770
- right_index,
771
- strict_left && strict_right,
772
- if strict_left {
773
- None
774
- } else {
775
- compile_compare_input_guard(
776
- vm, target_index, target, block, "OPERAND1",
777
- )
778
- },
779
- if strict_right {
780
- None
781
- } else {
782
- compile_compare_input_guard(
783
- vm, target_index, target, block, "OPERAND2",
784
- )
785
- },
786
- ),
787
- )
788
- true
789
- }
790
- _ => false
791
- }
792
- "operator_gt" =>
793
- match
794
- (
795
- compile_numeric_input_program_for_bool(
796
- vm, target_index, target, block, "OPERAND1", numeric_programs,
797
- ),
798
- compile_numeric_input_program_for_bool(
799
- vm, target_index, target, block, "OPERAND2", numeric_programs,
800
- ),
801
- ) {
802
- (Some(left_index), Some(right_index)) => {
803
- let strict_left = is_strict_numeric_input_for_compare(
804
- target, block, "OPERAND1",
805
- )
806
- let strict_right = is_strict_numeric_input_for_compare(
807
- target, block, "OPERAND2",
808
- )
809
- ops.push(
810
- BoolReporterOp::CompareGt(
811
- left_index,
812
- right_index,
813
- strict_left && strict_right,
814
- if strict_left {
815
- None
816
- } else {
817
- compile_compare_input_guard(
818
- vm, target_index, target, block, "OPERAND1",
819
- )
820
- },
821
- if strict_right {
822
- None
823
- } else {
824
- compile_compare_input_guard(
825
- vm, target_index, target, block, "OPERAND2",
826
- )
827
- },
828
- ),
829
- )
830
- true
831
- }
832
- _ => false
833
- }
834
- "operator_equals" =>
835
- match
836
- (
837
- compile_numeric_input_program_for_bool(
838
- vm, target_index, target, block, "OPERAND1", numeric_programs,
839
- ),
840
- compile_numeric_input_program_for_bool(
841
- vm, target_index, target, block, "OPERAND2", numeric_programs,
842
- ),
843
- ) {
844
- (Some(left_index), Some(right_index)) => {
845
- let strict_left = is_strict_numeric_input_for_compare(
846
- target, block, "OPERAND1",
847
- )
848
- let strict_right = is_strict_numeric_input_for_compare(
849
- target, block, "OPERAND2",
850
- )
851
- ops.push(
852
- BoolReporterOp::CompareEq(
853
- left_index,
854
- right_index,
855
- strict_left && strict_right,
856
- if strict_left {
857
- None
858
- } else {
859
- compile_compare_input_guard(
860
- vm, target_index, target, block, "OPERAND1",
861
- )
862
- },
863
- if strict_right {
864
- None
865
- } else {
866
- compile_compare_input_guard(
867
- vm, target_index, target, block, "OPERAND2",
868
- )
869
- },
870
- ),
871
- )
872
- true
873
- }
874
- _ => false
875
- }
876
- _ => false
877
- }
878
- visiting.remove(block_id)
879
- compiled
880
- }
881
-
882
- ///|
883
- fn compile_bool_reporter_program(
884
- vm : Vm,
885
- target_index : Int,
886
- target : TargetState,
887
- block_id : String,
888
- ) -> BoolReporterProgram? {
889
- let numeric_programs = []
890
- let ops = []
891
- let visiting = {}
892
- if compile_bool_reporter_ops(
893
- vm, target_index, target, block_id, numeric_programs, ops, visiting, 0,
894
- ) {
895
- Some({ numeric_programs, ops })
896
- } else {
897
- None
898
- }
899
- }
900
-
901
- ///|
902
- fn compare_guard_is_numeric(
903
- vm : Vm,
904
- target_index : Int,
905
- numeric_guard : CompareNumericGuard,
906
- ) -> Bool {
907
- let owner_index = if numeric_guard.owner_kind == 1 {
908
- vm.stage_index
909
- } else {
910
- target_index
911
- }
912
- if owner_index < 0 || owner_index >= vm.targets.length() {
913
- return false
914
- }
915
- match numeric_guard.slot {
916
- Some(slot) =>
917
- if slot >= 0 &&
918
- slot < vm.targets[owner_index].variable_numeric_flags.length() {
919
- vm.targets[owner_index].variable_numeric_flags[slot]
920
- } else {
921
- match
922
- as_number_or_none(
923
- read_variable_by_ref(
924
- vm,
925
- owner_index,
926
- numeric_guard.variable_id,
927
- numeric_guard.slot,
928
- ),
929
- ) {
930
- Some(value) => value == value
931
- None => false
932
- }
933
- }
934
- None =>
935
- match
936
- as_number_or_none(
937
- read_variable_by_ref(
938
- vm,
939
- owner_index,
940
- numeric_guard.variable_id,
941
- numeric_guard.slot,
942
- ),
943
- ) {
944
- Some(value) => value == value
945
- None => false
946
- }
947
- }
948
- }
949
-
950
- ///|
951
- fn eval_bool_reporter_program(
952
- vm : Vm,
953
- target_index : Int,
954
- program : BoolReporterProgram,
955
- ) -> Bool? {
956
- let stack = []
957
- for op in program.ops {
958
- match op {
959
- BoolReporterOp::CompareLt(
960
- left_index,
961
- right_index,
962
- strict,
963
- left_guard,
964
- right_guard
965
- ) => {
966
- if !strict {
967
- match (left_guard, right_guard) {
968
- (Some(left_guard), Some(right_guard)) =>
969
- if !compare_guard_is_numeric(vm, target_index, left_guard) ||
970
- !compare_guard_is_numeric(vm, target_index, right_guard) {
971
- return None
972
- }
973
- _ => return None
974
- }
975
- }
976
- if left_index >= 0 &&
977
- right_index >= 0 &&
978
- left_index < program.numeric_programs.length() &&
979
- right_index < program.numeric_programs.length() {
980
- match
981
- (
982
- eval_numeric_reporter_program(
983
- vm,
984
- target_index,
985
- program.numeric_programs[left_index],
986
- ),
987
- eval_numeric_reporter_program(
988
- vm,
989
- target_index,
990
- program.numeric_programs[right_index],
991
- ),
992
- ) {
993
- (Some(left), Some(right)) =>
994
- if left == left && right == right {
995
- stack.push(left < right)
996
- } else {
997
- return None
998
- }
999
- _ => return None
1000
- }
1001
- } else {
1002
- return None
1003
- }
1004
- }
1005
- BoolReporterOp::CompareGt(
1006
- left_index,
1007
- right_index,
1008
- strict,
1009
- left_guard,
1010
- right_guard
1011
- ) => {
1012
- if !strict {
1013
- match (left_guard, right_guard) {
1014
- (Some(left_guard), Some(right_guard)) =>
1015
- if !compare_guard_is_numeric(vm, target_index, left_guard) ||
1016
- !compare_guard_is_numeric(vm, target_index, right_guard) {
1017
- return None
1018
- }
1019
- _ => return None
1020
- }
1021
- }
1022
- if left_index >= 0 &&
1023
- right_index >= 0 &&
1024
- left_index < program.numeric_programs.length() &&
1025
- right_index < program.numeric_programs.length() {
1026
- match
1027
- (
1028
- eval_numeric_reporter_program(
1029
- vm,
1030
- target_index,
1031
- program.numeric_programs[left_index],
1032
- ),
1033
- eval_numeric_reporter_program(
1034
- vm,
1035
- target_index,
1036
- program.numeric_programs[right_index],
1037
- ),
1038
- ) {
1039
- (Some(left), Some(right)) =>
1040
- if left == left && right == right {
1041
- stack.push(left > right)
1042
- } else {
1043
- return None
1044
- }
1045
- _ => return None
1046
- }
1047
- } else {
1048
- return None
1049
- }
1050
- }
1051
- BoolReporterOp::CompareEq(
1052
- left_index,
1053
- right_index,
1054
- strict,
1055
- left_guard,
1056
- right_guard
1057
- ) => {
1058
- if !strict {
1059
- match (left_guard, right_guard) {
1060
- (Some(left_guard), Some(right_guard)) =>
1061
- if !compare_guard_is_numeric(vm, target_index, left_guard) ||
1062
- !compare_guard_is_numeric(vm, target_index, right_guard) {
1063
- return None
1064
- }
1065
- _ => return None
1066
- }
1067
- }
1068
- if left_index >= 0 &&
1069
- right_index >= 0 &&
1070
- left_index < program.numeric_programs.length() &&
1071
- right_index < program.numeric_programs.length() {
1072
- match
1073
- (
1074
- eval_numeric_reporter_program(
1075
- vm,
1076
- target_index,
1077
- program.numeric_programs[left_index],
1078
- ),
1079
- eval_numeric_reporter_program(
1080
- vm,
1081
- target_index,
1082
- program.numeric_programs[right_index],
1083
- ),
1084
- ) {
1085
- (Some(left), Some(right)) =>
1086
- if left == left && right == right {
1087
- stack.push(left == right)
1088
- } else {
1089
- return None
1090
- }
1091
- _ => return None
1092
- }
1093
- } else {
1094
- return None
1095
- }
1096
- }
1097
- BoolReporterOp::And =>
1098
- match (stack.pop(), stack.pop()) {
1099
- (Some(right), Some(left)) => stack.push(left && right)
1100
- _ => return None
1101
- }
1102
- BoolReporterOp::Or =>
1103
- match (stack.pop(), stack.pop()) {
1104
- (Some(right), Some(left)) => stack.push(left || right)
1105
- _ => return None
1106
- }
1107
- BoolReporterOp::Not =>
1108
- match stack.pop() {
1109
- Some(value) => stack.push(!value)
1110
- None => return None
1111
- }
1112
- }
1113
- }
1114
- if stack.length() == 1 {
1115
- Some(stack[0])
1116
- } else {
1117
- None
1118
- }
1119
- }
1120
-
1121
- ///|
1122
- fn get_compiled_bool_reporter_program(
1123
- vm : Vm,
1124
- target_index : Int,
1125
- block_id : String,
1126
- ) -> BoolReporterProgram? {
1127
- if target_index < 0 || target_index >= vm.targets.length() {
1128
- return None
1129
- }
1130
- let target = vm.targets[target_index]
1131
- match target.bool_program_cache.get(block_id) {
1132
- Some(program) => Some(program)
1133
- None =>
1134
- if target.bool_program_compile_failed.contains(block_id) {
1135
- None
1136
- } else {
1137
- match
1138
- compile_bool_reporter_program(vm, target_index, target, block_id) {
1139
- Some(program) => {
1140
- vm.targets[target_index].bool_program_cache[block_id] = program
1141
- Some(program)
1142
- }
1143
- None => {
1144
- vm.targets[target_index].bool_program_compile_failed[block_id] = true
1145
- None
1146
- }
1147
- }
1148
- }
1149
- }
1150
- }
1151
-
1152
- ///|
1153
- fn number_from_input(
1154
- vm : Vm,
1155
- target_index : Int,
1156
- block : ScratchBlock,
1157
- input_name : String,
1158
- depth : Int,
1159
- ) -> Double {
1160
- match block.const_number_inputs.get(input_name) {
1161
- Some(value) => return value
1162
- None => ()
1163
- }
1164
- match block.input_block_ids.get(input_name) {
1165
- Some(block_id) => {
1166
- if target_index >= 0 && target_index < vm.targets.length() {
1167
- match vm.targets[target_index].const_number_block_values.get(block_id) {
1168
- Some(value) => return value
1169
- None => ()
1170
- }
1171
- }
1172
- match get_compiled_numeric_reporter_program(vm, target_index, block_id) {
1173
- Some(program) =>
1174
- match eval_numeric_reporter_program(vm, target_index, program) {
1175
- Some(value) => return value
1176
- None => ()
1177
- }
1178
- None => ()
1179
- }
1180
- match
1181
- eval_reporter_number_block_depth(vm, target_index, block_id, depth + 1) {
1182
- Some(value) => return value
1183
- None =>
1184
- return json_to_number_value(
1185
- eval_reporter_block_depth(vm, target_index, block_id, depth + 1),
1186
- )
1187
- }
1188
- }
1189
- None => ()
1190
- }
1191
- match block_input_payload(block, input_name) {
1192
- Some(Array(primitive)) =>
1193
- if primitive.length() >= 2 {
1194
- let code = json_to_number_value(primitive[0]).to_int()
1195
- if code >= 4 && code <= 8 {
1196
- json_to_number_value(primitive[1])
1197
- } else {
1198
- json_to_number_value(decode_primitive(vm, target_index, primitive))
1199
- }
1200
- } else {
1201
- 0.0
1202
- }
1203
- Some(String(block_id)) =>
1204
- if target_index >= 0 && target_index < vm.targets.length() {
1205
- match vm.targets[target_index].const_number_block_values.get(block_id) {
1206
- Some(value) => value
1207
- None =>
1208
- match
1209
- get_compiled_numeric_reporter_program(vm, target_index, block_id) {
1210
- Some(program) =>
1211
- match eval_numeric_reporter_program(vm, target_index, program) {
1212
- Some(value) => value
1213
- None =>
1214
- match
1215
- eval_reporter_number_block_depth(
1216
- vm,
1217
- target_index,
1218
- block_id,
1219
- depth + 1,
1220
- ) {
1221
- Some(value) => value
1222
- None =>
1223
- json_to_number_value(
1224
- eval_reporter_block_depth(
1225
- vm,
1226
- target_index,
1227
- block_id,
1228
- depth + 1,
1229
- ),
1230
- )
1231
- }
1232
- }
1233
- None =>
1234
- match
1235
- eval_reporter_number_block_depth(
1236
- vm,
1237
- target_index,
1238
- block_id,
1239
- depth + 1,
1240
- ) {
1241
- Some(value) => value
1242
- None =>
1243
- json_to_number_value(
1244
- eval_reporter_block_depth(
1245
- vm,
1246
- target_index,
1247
- block_id,
1248
- depth + 1,
1249
- ),
1250
- )
1251
- }
1252
- }
1253
- }
1254
- } else {
1255
- match
1256
- get_compiled_numeric_reporter_program(vm, target_index, block_id) {
1257
- Some(program) =>
1258
- match eval_numeric_reporter_program(vm, target_index, program) {
1259
- Some(value) => value
1260
- None =>
1261
- match
1262
- eval_reporter_number_block_depth(
1263
- vm,
1264
- target_index,
1265
- block_id,
1266
- depth + 1,
1267
- ) {
1268
- Some(value) => value
1269
- None =>
1270
- json_to_number_value(
1271
- eval_reporter_block_depth(
1272
- vm,
1273
- target_index,
1274
- block_id,
1275
- depth + 1,
1276
- ),
1277
- )
1278
- }
1279
- }
1280
- None =>
1281
- match
1282
- eval_reporter_number_block_depth(
1283
- vm,
1284
- target_index,
1285
- block_id,
1286
- depth + 1,
1287
- ) {
1288
- Some(value) => value
1289
- None =>
1290
- json_to_number_value(
1291
- eval_reporter_block_depth(
1292
- vm,
1293
- target_index,
1294
- block_id,
1295
- depth + 1,
1296
- ),
1297
- )
1298
- }
1299
- }
1300
- }
1301
- Some(value) => json_to_number_value(value)
1302
- None => 0.0
1303
- }
1304
- }
1305
-
1306
- ///|
1307
- fn mathop(vm : Vm, name : String, value : Double) -> Double {
1308
- let op = if name == "sin" || name == "cos" { name } else { name.to_lower() }
1309
- match op {
1310
- "abs" => value.abs()
1311
- "floor" => value.floor()
1312
- "ceiling" => value.ceil()
1313
- "sqrt" => value.sqrt()
1314
- "sin" =>
1315
- if vm.mathop_sin_cache_valid && vm.mathop_sin_cache_input == value {
1316
- vm.mathop_sin_cache_output
1317
- } else {
1318
- let computed = @math.sin(value * @math.PI / 180.0)
1319
- vm.mathop_sin_cache_valid = true
1320
- vm.mathop_sin_cache_input = value
1321
- vm.mathop_sin_cache_output = computed
1322
- computed
1323
- }
1324
- "cos" =>
1325
- if vm.mathop_cos_cache_valid && vm.mathop_cos_cache_input == value {
1326
- vm.mathop_cos_cache_output
1327
- } else {
1328
- let computed = @math.cos(value * @math.PI / 180.0)
1329
- vm.mathop_cos_cache_valid = true
1330
- vm.mathop_cos_cache_input = value
1331
- vm.mathop_cos_cache_output = computed
1332
- computed
1333
- }
1334
- "tan" => @math.tan(value * @math.PI / 180.0)
1335
- "asin" => @math.asin(value) * 180.0 / @math.PI
1336
- "acos" => @math.acos(value) * 180.0 / @math.PI
1337
- "atan" => @math.atan(value) * 180.0 / @math.PI
1338
- "ln" => @math.ln(value)
1339
- "log" => @math.log10(value)
1340
- "e ^" => @math.exp(value)
1341
- "10 ^" => @math.pow(10.0, value)
1342
- _ => value
1343
- }
1344
- }
1345
-
1346
- ///|
1347
- fn eval_reporter_number_block_depth(
1348
- vm : Vm,
1349
- target_index : Int,
1350
- block_id : String,
1351
- depth : Int,
1352
- ) -> Double? {
1353
- if depth > 40 {
1354
- return None
1355
- }
1356
- if target_index < 0 || target_index >= vm.targets.length() {
1357
- return None
1358
- }
1359
- let target = vm.targets[target_index]
1360
- let block = match target.blocks.get(block_id) {
1361
- Some(value) => value
1362
- None => return None
1363
- }
1364
-
1365
- match block.opcode {
1366
- "math_number"
1367
- | "math_integer"
1368
- | "math_whole_number"
1369
- | "math_positive_number"
1370
- | "math_angle" =>
1371
- match field_value(block, "NUM") {
1372
- Some((value, _)) =>
1373
- Some(
1374
- match parse_double_or_none(value) {
1375
- Some(parsed) => parsed
1376
- None => 0.0
1377
- },
1378
- )
1379
- None => Some(0.0)
1380
- }
1381
- "operator_add" =>
1382
- Some(
1383
- number_from_input(vm, target_index, block, "NUM1", depth) +
1384
- number_from_input(vm, target_index, block, "NUM2", depth),
1385
- )
1386
- "operator_subtract" =>
1387
- Some(
1388
- number_from_input(vm, target_index, block, "NUM1", depth) -
1389
- number_from_input(vm, target_index, block, "NUM2", depth),
1390
- )
1391
- "operator_multiply" =>
1392
- Some(
1393
- number_from_input(vm, target_index, block, "NUM1", depth) *
1394
- number_from_input(vm, target_index, block, "NUM2", depth),
1395
- )
1396
- "operator_divide" => {
1397
- let right = number_from_input(vm, target_index, block, "NUM2", depth)
1398
- if right == 0.0 {
1399
- Some(0.0)
1400
- } else {
1401
- Some(number_from_input(vm, target_index, block, "NUM1", depth) / right)
1402
- }
1403
- }
1404
- "operator_mod" => {
1405
- let right = number_from_input(vm, target_index, block, "NUM2", depth)
1406
- if right == 0.0 {
1407
- Some(0.0)
1408
- } else {
1409
- Some(
1410
- number_from_input(vm, target_index, block, "NUM1", depth).mod(right),
1411
- )
1412
- }
1413
- }
1414
- "operator_round" =>
1415
- Some(number_from_input(vm, target_index, block, "NUM", depth).round())
1416
- "operator_mathop" => {
1417
- let op = match field_value(block, "OPERATOR") {
1418
- Some((name, _)) => name
1419
- None => ""
1420
- }
1421
- Some(
1422
- mathop(vm, op, number_from_input(vm, target_index, block, "NUM", depth)),
1423
- )
1424
- }
1425
- "data_variable" =>
1426
- match field_value(block, "VARIABLE") {
1427
- Some((name, id)) =>
1428
- Some(
1429
- json_to_number_value(
1430
- read_variable(vm, target_index, id, Some(name)),
1431
- ),
1432
- )
1433
- None => Some(0.0)
1434
- }
1435
- "data_itemoflist" =>
1436
- match field_value(block, "LIST") {
1437
- Some((name, id)) => {
1438
- let list = read_list(vm, target_index, id, Some(name))
1439
- let index_value = value_from_input(
1440
- vm, target_index, block, "INDEX", depth,
1441
- )
1442
- match
1443
- normalize_index(index_value, list.length(), next_random_unit(vm)) {
1444
- Some(index) =>
1445
- match list.get(index) {
1446
- Some(value) => Some(json_to_number_value(value))
1447
- None => Some(0.0)
1448
- }
1449
- None => Some(0.0)
1450
- }
1451
- }
1452
- None => Some(0.0)
1453
- }
1454
- "data_lengthoflist" =>
1455
- match field_value(block, "LIST") {
1456
- Some((name, id)) => {
1457
- let list = read_list(vm, target_index, id, Some(name))
1458
- Some(Double::from_int(list.length()))
1459
- }
1460
- None => Some(0.0)
1461
- }
1462
- "argument_reporter_string_number" | "argument_reporter_boolean" => {
1463
- let arg_name = block_input_or_field_string(
1464
- vm, target_index, block, "VALUE", "VALUE", depth,
1465
- )
1466
- if arg_name == "" {
1467
- Some(0.0)
1468
- } else {
1469
- match current_procedure_param(vm, arg_name) {
1470
- Some(value) => Some(json_to_number_value(value))
1471
- None => Some(0.0)
1472
- }
1473
- }
1474
- }
1475
- "music_getTempo" => Some(vm.music_tempo)
1476
- "motion_xposition" => Some(vm.targets[target_index].x)
1477
- "motion_yposition" => Some(vm.targets[target_index].y)
1478
- "motion_direction" => Some(vm.targets[target_index].direction)
1479
- "looks_size" => Some(vm.targets[target_index].size)
1480
- "sound_volume" => Some(vm.targets[target_index].volume)
1481
- "sensing_timer" =>
1482
- Some(Double::from_int(vm.now_ms - vm.timer_start_ms) / 1000.0)
1483
- "sensing_mousex" => {
1484
- let (x, _) = read_mouse_xy(vm)
1485
- Some(x)
1486
- }
1487
- "sensing_mousey" => {
1488
- let (_, y) = read_mouse_xy(vm)
1489
- Some(y)
1490
- }
1491
- "sensing_current" => {
1492
- let menu = block_input_or_field_string(
1493
- vm, target_index, block, "CURRENTMENU", "CURRENTMENU", depth,
1494
- )
1495
- Some(current_menu_value(vm, menu))
1496
- }
1497
- "sensing_dayssince2000" => Some(days_since_2000(vm))
1498
- "sensing_loudness" => Some(read_loudness(vm))
1499
- "control_get_counter" => Some(Double::from_int(vm.control_counter))
1500
- _ => None
1501
- }
1502
- }
1503
-
1504
- ///|
1505
- fn as_number_or_none(value : Json) -> Double? {
1506
- match value {
1507
- Number(n, ..) => Some(n)
1508
- String(s) => parse_double_or_none(s)
1509
- True => Some(1.0)
1510
- False => Some(0.0)
1511
- _ => None
1512
- }
1513
- }
1514
-
1515
- ///|
1516
- fn scratch_compare_as_string(left : Json, right : Json) -> Int {
1517
- let left_text = json_to_string_value(left).to_lower()
1518
- let right_text = json_to_string_value(right).to_lower()
1519
- if left_text < right_text {
1520
- -1
1521
- } else if left_text > right_text {
1522
- 1
1523
- } else {
1524
- 0
1525
- }
1526
- }
1527
-
1528
- ///|
1529
- fn scratch_compare(left : Json, right : Json) -> Int {
1530
- match (as_number_or_none(left), as_number_or_none(right)) {
1531
- (Some(left_number), Some(right_number)) =>
1532
- if left_number != left_number || right_number != right_number {
1533
- scratch_compare_as_string(left, right)
1534
- } else if left_number < right_number {
1535
- -1
1536
- } else if left_number > right_number {
1537
- 1
1538
- } else {
1539
- 0
1540
- }
1541
- _ => scratch_compare_as_string(left, right)
1542
- }
1543
- }
1544
-
1545
- ///|
1546
- fn scratch_equals(left : Json, right : Json) -> Bool {
1547
- scratch_compare(left, right) == 0
1548
- }
1549
-
1550
- ///|
1551
- fn normalize_index(
1552
- index_value : Json,
1553
- length : Int,
1554
- random_unit : Double,
1555
- ) -> Int? {
1556
- match index_value {
1557
- Number(n, ..) => {
1558
- let index = n.to_int() - 1
1559
- if index < 0 || index >= length {
1560
- None
1561
- } else {
1562
- Some(index)
1563
- }
1564
- }
1565
- String(raw_input) => {
1566
- let raw = raw_input.trim().to_lower()
1567
- if raw == "last" {
1568
- if length <= 0 {
1569
- None
1570
- } else {
1571
- Some(length - 1)
1572
- }
1573
- } else if raw == "random" || raw == "any" {
1574
- if length <= 0 {
1575
- None
1576
- } else {
1577
- let sampled = (random_unit * Double::from_int(length))
1578
- .floor()
1579
- .to_int()
1580
- Some(sampled.clamp(min=0, max=length - 1))
1581
- }
1582
- } else {
1583
- let index = json_to_number_value(index_value).to_int() - 1
1584
- if index < 0 || index >= length {
1585
- None
1586
- } else {
1587
- Some(index)
1588
- }
1589
- }
1590
- }
1591
- _ => {
1592
- let index = json_to_number_value(index_value).to_int() - 1
1593
- if index < 0 || index >= length {
1594
- None
1595
- } else {
1596
- Some(index)
1597
- }
1598
- }
1599
- }
1600
- }
1601
-
1602
- ///|
1603
- fn reporter_target_costume_count(target : TargetState) -> Int {
1604
- if target.costume_names.is_empty() {
1605
- 1
1606
- } else {
1607
- target.costume_names.length()
1608
- }
1609
- }
1610
-
1611
- ///|
1612
- fn reporter_normalized_target_costume_index(
1613
- target : TargetState,
1614
- index : Int,
1615
- ) -> Int {
1616
- let count = reporter_target_costume_count(target)
1617
- let wrapped = index % count
1618
- if wrapped < 0 {
1619
- wrapped + count
1620
- } else {
1621
- wrapped
1622
- }
1623
- }
1624
-
1625
- ///|
1626
- fn reporter_target_costume_name(target : TargetState, index : Int) -> String {
1627
- if target.costume_names.is_empty() {
1628
- "costume_\{index + 1}"
1629
- } else {
1630
- let normalized = reporter_normalized_target_costume_index(target, index)
1631
- if normalized >= 0 && normalized < target.costume_names.length() {
1632
- target.costume_names[normalized]
1633
- } else {
1634
- "costume_\{normalized + 1}"
1635
- }
1636
- }
1637
- }
1638
-
1639
- ///|
1640
- fn looks_number_name_json(target : TargetState, number_name : String) -> Json {
1641
- let normalized = reporter_normalized_target_costume_index(
1642
- target,
1643
- target.current_costume,
1644
- )
1645
- if number_name.trim().to_lower() == "name" {
1646
- json_string(reporter_target_costume_name(target, normalized))
1647
- } else {
1648
- json_number(Double::from_int(normalized + 1))
1649
- }
1650
- }
1651
-
1652
- ///|
1653
- fn block_input_or_field_string(
1654
- vm : Vm,
1655
- target_index : Int,
1656
- block : ScratchBlock,
1657
- input_name : String,
1658
- field_name : String,
1659
- depth : Int,
1660
- ) -> String {
1661
- let from_input = json_to_string_value(
1662
- value_from_input(vm, target_index, block, input_name, depth),
1663
- )
1664
- .trim()
1665
- .to_string()
1666
- if from_input != "" {
1667
- from_input
1668
- } else {
1669
- match field_value(block, field_name) {
1670
- Some((value, _)) => value
1671
- None => ""
1672
- }
1673
- }
1674
- }
1675
-
1676
- ///|
1677
- fn current_procedure_param(vm : Vm, key : String) -> Json? {
1678
- match vm.current_thread_id {
1679
- Some(thread_id) =>
1680
- match vm.procedure_frames.get(thread_id) {
1681
- Some(stack) =>
1682
- if stack.is_empty() {
1683
- None
1684
- } else {
1685
- let frame = stack[stack.length() - 1]
1686
- frame.params.get(key)
1687
- }
1688
- None => None
1689
- }
1690
- None => None
1691
- }
1692
- }
1693
-
1694
- ///|
1695
- fn timestamp_from_json(value : Json) -> Double? {
1696
- match value {
1697
- Number(n, ..) => Some(n)
1698
- String(raw) => parse_double_or_none(raw)
1699
- Object(obj) =>
1700
- match obj.get("timestamp_ms") {
1701
- Some(raw) => as_number_or_none(raw)
1702
- None =>
1703
- match obj.get("timestampMs") {
1704
- Some(raw) => as_number_or_none(raw)
1705
- None =>
1706
- match obj.get("timestamp") {
1707
- Some(raw) =>
1708
- match as_number_or_none(raw) {
1709
- Some(seconds) => Some(seconds * 1000.0)
1710
- None => None
1711
- }
1712
- None => None
1713
- }
1714
- }
1715
- }
1716
- _ => None
1717
- }
1718
- }
1719
-
1720
- ///|
1721
- fn current_timestamp_ms(vm : Vm) -> Double {
1722
- let base = 946684800000.0
1723
- match vm.io_state.get("current_timestamp_ms") {
1724
- Some(value) =>
1725
- match timestamp_from_json(value) {
1726
- Some(timestamp) => timestamp
1727
- None => base + Double::from_int(vm.now_ms)
1728
- }
1729
- None =>
1730
- match vm.io_state.get("current") {
1731
- Some(value) =>
1732
- match timestamp_from_json(value) {
1733
- Some(timestamp) => timestamp
1734
- None =>
1735
- match vm.io_state.get("clock") {
1736
- Some(clock) =>
1737
- match timestamp_from_json(clock) {
1738
- Some(timestamp) => timestamp
1739
- None => base + Double::from_int(vm.now_ms)
1740
- }
1741
- None => base + Double::from_int(vm.now_ms)
1742
- }
1743
- }
1744
- None =>
1745
- match vm.io_state.get("clock") {
1746
- Some(clock) =>
1747
- match timestamp_from_json(clock) {
1748
- Some(timestamp) => timestamp
1749
- None => base + Double::from_int(vm.now_ms)
1750
- }
1751
- None => base + Double::from_int(vm.now_ms)
1752
- }
1753
- }
1754
- }
1755
- }
1756
-
1757
- ///|
1758
- fn unix_day_and_second(unix_seconds : Int) -> (Int, Int) {
1759
- let day = if unix_seconds >= 0 {
1760
- unix_seconds / 86400
1761
- } else {
1762
- (unix_seconds - 86399) / 86400
1763
- }
1764
- let second = unix_seconds - day * 86400
1765
- (day, second)
1766
- }
1767
-
1768
- ///|
1769
- fn civil_from_unix_days(unix_days : Int) -> (Int, Int, Int) {
1770
- let z = unix_days + 719468
1771
- let era = if z >= 0 { z / 146097 } else { (z - 146096) / 146097 }
1772
- let doe = z - era * 146097
1773
- let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365
1774
- let y = yoe + era * 400
1775
- let doy = doe - (365 * yoe + yoe / 4 - yoe / 100)
1776
- let mp = (5 * doy + 2) / 153
1777
- let day = doy - (153 * mp + 2) / 5 + 1
1778
- let month = if mp < 10 { mp + 3 } else { mp - 9 }
1779
- let year = if month <= 2 { y + 1 } else { y }
1780
- (year, month, day)
1781
- }
1782
-
1783
- ///|
1784
- fn scratch_day_of_week(unix_days : Int) -> Int {
1785
- let raw = (unix_days + 4).mod(7)
1786
- if raw < 0 {
1787
- raw + 7 + 1
1788
- } else {
1789
- raw + 1
1790
- }
1791
- }
1792
-
1793
- ///|
1794
- fn current_menu_value(vm : Vm, menu : String) -> Double {
1795
- let timestamp_ms = current_timestamp_ms(vm)
1796
- let unix_seconds = (timestamp_ms / 1000.0).floor().to_int()
1797
- let (unix_days, second_of_day) = unix_day_and_second(unix_seconds)
1798
- let (year, month, date) = civil_from_unix_days(unix_days)
1799
- let hour = second_of_day / 3600
1800
- let minute = second_of_day.mod(3600) / 60
1801
- let second = second_of_day.mod(60)
1802
-
1803
- match lower_trim(menu) {
1804
- "year" => Double::from_int(year)
1805
- "month" => Double::from_int(month)
1806
- "date" => Double::from_int(date)
1807
- "dayofweek" => Double::from_int(scratch_day_of_week(unix_days))
1808
- "hour" => Double::from_int(hour)
1809
- "minute" => Double::from_int(minute)
1810
- "second" => Double::from_int(second)
1811
- _ => 0.0
1812
- }
1813
- }
1814
-
1815
- ///|
1816
- fn days_since_2000(vm : Vm) -> Double {
1817
- (current_timestamp_ms(vm) - 946684800000.0) / 86400000.0
1818
- }
1819
-
1820
- ///|
1821
- fn normalize_key_name(raw : String) -> String {
1822
- let key = lower_trim(raw)
1823
- match key {
1824
- "spacebar" | " " => "space"
1825
- "left" | "arrowleft" => "left arrow"
1826
- "right" | "arrowright" => "right arrow"
1827
- "up" | "arrowup" => "up arrow"
1828
- "down" | "arrowdown" => "down arrow"
1829
- _ => key
1830
- }
1831
- }
1832
-
1833
- ///|
1834
- fn append_key_events(out : Array[String], payload : Json) -> Unit {
1835
- for key in parse_key_events(payload) {
1836
- out.push(normalize_key_name(key))
1837
- }
1838
- }
1839
-
1840
- ///|
1841
- fn pressed_keys(vm : Vm) -> Array[String] {
1842
- let keys = []
1843
- match vm.io_state.get("keys_down") {
1844
- Some(payload) => append_key_events(keys, payload)
1845
- None => ()
1846
- }
1847
- match vm.io_state.get("keyboard") {
1848
- Some(payload) => append_key_events(keys, payload)
1849
- None => ()
1850
- }
1851
- keys
1852
- }
1853
-
1854
- ///|
1855
- fn key_is_pressed(vm : Vm, key_option : String) -> Bool {
1856
- let option = normalize_key_name(key_option)
1857
- let keys = pressed_keys(vm)
1858
- if option == "any" {
1859
- !keys.is_empty()
1860
- } else {
1861
- keys.any(fn(key) { key == option })
1862
- }
1863
- }
1864
-
1865
- ///|
1866
- fn read_mouse_down(vm : Vm) -> Bool {
1867
- match vm.io_state.get("mouse") {
1868
- Some(Object(obj)) =>
1869
- match obj.get("isDown") {
1870
- Some(value) => json_to_bool_value(value)
1871
- None =>
1872
- match obj.get("down") {
1873
- Some(value) => json_to_bool_value(value)
1874
- None => false
1875
- }
1876
- }
1877
- Some(value) => json_to_bool_value(value)
1878
- None =>
1879
- match vm.io_state.get("mousedown") {
1880
- Some(value) => json_to_bool_value(value)
1881
- None => false
1882
- }
1883
- }
1884
- }
1885
-
1886
- ///|
1887
- fn resolve_sensing_object_target(
1888
- vm : Vm,
1889
- current_target_index : Int,
1890
- menu : String,
1891
- ) -> Int? {
1892
- let key = lower_trim(menu)
1893
- if key == "_myself_" {
1894
- if current_target_index >= 0 && current_target_index < vm.targets.length() {
1895
- return Some(current_target_index)
1896
- }
1897
- return None
1898
- }
1899
- if key == "_stage_" {
1900
- if vm.stage_index >= 0 && vm.stage_index < vm.targets.length() {
1901
- return Some(vm.stage_index)
1902
- }
1903
- return None
1904
- }
1905
- if vm.stage_index >= 0 &&
1906
- vm.stage_index < vm.targets.length() &&
1907
- lower_trim(vm.targets[vm.stage_index].name) == key {
1908
- return Some(vm.stage_index)
1909
- }
1910
- find_target_by_name(vm, menu)
1911
- }
1912
-
1913
- ///|
1914
- fn touching_stage_edge(target : TargetState) -> Bool {
1915
- target.x <= -240.0 ||
1916
- target.x >= 240.0 ||
1917
- target.y <= -180.0 ||
1918
- target.y >= 180.0
1919
- }
1920
-
1921
- ///|
1922
- fn sensing_of_builtin_value(
1923
- target : TargetState,
1924
- property_key : String,
1925
- ) -> Json? {
1926
- if target.is_stage {
1927
- match property_key {
1928
- "background #" | "backdrop #" =>
1929
- Some(
1930
- json_number(
1931
- Double::from_int(
1932
- reporter_normalized_target_costume_index(
1933
- target,
1934
- target.current_costume,
1935
- ) +
1936
- 1,
1937
- ),
1938
- ),
1939
- )
1940
- "backdrop name" =>
1941
- Some(
1942
- json_string(
1943
- reporter_target_costume_name(target, target.current_costume),
1944
- ),
1945
- )
1946
- "volume" => Some(json_number(target.volume))
1947
- _ => None
1948
- }
1949
- } else {
1950
- match property_key {
1951
- "x position" => Some(json_number(target.x))
1952
- "y position" => Some(json_number(target.y))
1953
- "direction" => Some(json_number(target.direction))
1954
- "costume #" =>
1955
- Some(
1956
- json_number(
1957
- Double::from_int(
1958
- reporter_normalized_target_costume_index(
1959
- target,
1960
- target.current_costume,
1961
- ) +
1962
- 1,
1963
- ),
1964
- ),
1965
- )
1966
- "costume name" =>
1967
- Some(
1968
- json_string(
1969
- reporter_target_costume_name(target, target.current_costume),
1970
- ),
1971
- )
1972
- "size" => Some(json_number(target.size))
1973
- "volume" => Some(json_number(target.volume))
1974
- _ => None
1975
- }
1976
- }
1977
- }
1978
-
1979
- ///|
1980
- fn io_object_field(vm : Vm, device : String, key : String) -> Json? {
1981
- match vm.io_state.get(device) {
1982
- Some(Object(obj)) => obj.get(key)
1983
- _ => None
1984
- }
1985
- }
1986
-
1987
- ///|
1988
- fn online_status(vm : Vm) -> Bool {
1989
- match vm.io_state.get("online") {
1990
- Some(value) => json_to_bool_value(value)
1991
- None =>
1992
- match io_object_field(vm, "network", "online") {
1993
- Some(value) => json_to_bool_value(value)
1994
- None => false
1995
- }
1996
- }
1997
- }
1998
-
1999
- ///|
2000
- fn read_username(vm : Vm) -> String {
2001
- match vm.io_state.get("username") {
2002
- Some(value) => json_to_string_value(value)
2003
- None =>
2004
- match io_object_field(vm, "userData", "username") {
2005
- Some(value) => json_to_string_value(value)
2006
- None =>
2007
- match io_object_field(vm, "user", "username") {
2008
- Some(value) => json_to_string_value(value)
2009
- None => ""
2010
- }
2011
- }
2012
- }
2013
- }
2014
-
2015
- ///|
2016
- fn read_userid(vm : Vm) -> Json {
2017
- match vm.io_state.get("userid") {
2018
- Some(value) => value
2019
- None =>
2020
- match io_object_field(vm, "userData", "userid") {
2021
- Some(value) => value
2022
- None =>
2023
- match io_object_field(vm, "userData", "userId") {
2024
- Some(value) => value
2025
- None =>
2026
- match io_object_field(vm, "user", "id") {
2027
- Some(value) => value
2028
- None => json_number(0.0)
2029
- }
2030
- }
2031
- }
2032
- }
2033
- }
2034
-
2035
- ///|
2036
- fn is_ascii_digits_only(raw : String) -> Bool {
2037
- let value = raw.trim().to_string()
2038
- if value.is_empty() {
2039
- return false
2040
- }
2041
- for ch in value.to_array() {
2042
- if ch < '0' || ch > '9' {
2043
- return false
2044
- }
2045
- }
2046
- true
2047
- }
2048
-
2049
- ///|
2050
- fn normalize_translate_language(raw : String) -> String {
2051
- let language = lower_trim(raw)
2052
- if language == "" {
2053
- "en"
2054
- } else {
2055
- language
2056
- }
2057
- }
2058
-
2059
- ///|
2060
- fn translate_pending_key(words : String, language : String) -> String {
2061
- "\{language}\n\{words}"
2062
- }
2063
-
2064
- ///|
2065
- fn translate_cache_lookup(
2066
- vm : Vm,
2067
- words : String,
2068
- language : String,
2069
- ) -> String? {
2070
- match vm.io_state.get("translate_cache") {
2071
- Some(Object(cache)) =>
2072
- match cache.get(language) {
2073
- Some(Object(bucket)) =>
2074
- match bucket.get(words) {
2075
- Some(value) => Some(json_to_string_value(value))
2076
- None => None
2077
- }
2078
- _ => None
2079
- }
2080
- _ => None
2081
- }
2082
- }
2083
-
2084
- ///|
2085
- fn viewer_language(vm : Vm) -> String {
2086
- match vm.io_state.get("viewer_language") {
2087
- Some(value) => {
2088
- let language = json_to_string_value(value).trim().to_string()
2089
- if language == "" {
2090
- "en"
2091
- } else {
2092
- language
2093
- }
2094
- }
2095
- None =>
2096
- match io_object_field(vm, "translate", "viewer_language") {
2097
- Some(value) => {
2098
- let language = json_to_string_value(value).trim().to_string()
2099
- if language == "" {
2100
- "en"
2101
- } else {
2102
- language
2103
- }
2104
- }
2105
- None => "en"
2106
- }
2107
- }
2108
- }
2109
-
2110
- ///|
2111
- fn try_number_reporter_for_compare(
2112
- vm : Vm,
2113
- target_index : Int,
2114
- block_id : String,
2115
- depth : Int,
2116
- ) -> Double? {
2117
- if depth > 40 {
2118
- return None
2119
- }
2120
- if target_index < 0 || target_index >= vm.targets.length() {
2121
- return None
2122
- }
2123
- let target = vm.targets[target_index]
2124
- let block = match target.blocks.get(block_id) {
2125
- Some(value) => value
2126
- None => return None
2127
- }
2128
- let numeric = match block.opcode {
2129
- "data_variable" =>
2130
- match field_value(block, "VARIABLE") {
2131
- Some((name, id)) =>
2132
- match id {
2133
- Some(variable_id) =>
2134
- match resolve_variable_ref_by_id(vm, target_index, variable_id) {
2135
- Some((owner_index, resolved_id, resolved_slot)) =>
2136
- as_number_or_none(
2137
- read_variable_by_ref(
2138
- vm, owner_index, resolved_id, resolved_slot,
2139
- ),
2140
- )
2141
- None => None
2142
- }
2143
- None =>
2144
- as_number_or_none(
2145
- read_variable(vm, target_index, None, Some(name)),
2146
- )
2147
- }
2148
- None => None
2149
- }
2150
- "argument_reporter_string_number" | "argument_reporter_boolean" => {
2151
- let arg_name = block_input_or_field_string(
2152
- vm, target_index, block, "VALUE", "VALUE", depth,
2153
- )
2154
- if arg_name == "" {
2155
- Some(0.0)
2156
- } else {
2157
- match current_procedure_param(vm, arg_name) {
2158
- Some(value) => as_number_or_none(value)
2159
- None => Some(0.0)
2160
- }
2161
- }
2162
- }
2163
- "math_number"
2164
- | "math_integer"
2165
- | "math_whole_number"
2166
- | "math_positive_number"
2167
- | "math_angle"
2168
- | "operator_add"
2169
- | "operator_subtract"
2170
- | "operator_multiply"
2171
- | "operator_divide"
2172
- | "operator_mod"
2173
- | "operator_round"
2174
- | "operator_mathop"
2175
- | "operator_random"
2176
- | "motion_xposition"
2177
- | "motion_yposition"
2178
- | "motion_direction"
2179
- | "looks_size"
2180
- | "sound_volume"
2181
- | "sensing_timer"
2182
- | "sensing_mousex"
2183
- | "sensing_mousey"
2184
- | "sensing_current"
2185
- | "sensing_dayssince2000"
2186
- | "sensing_loudness"
2187
- | "music_getTempo"
2188
- | "control_get_counter" =>
2189
- eval_reporter_number_block_depth(vm, target_index, block_id, depth + 1)
2190
- _ => None
2191
- }
2192
- match numeric {
2193
- Some(number) => if number == number { Some(number) } else { None }
2194
- None => None
2195
- }
2196
- }
2197
-
2198
- ///|
2199
- fn try_number_input_for_compare(
2200
- vm : Vm,
2201
- target_index : Int,
2202
- block : ScratchBlock,
2203
- input_name : String,
2204
- depth : Int,
2205
- ) -> Double? {
2206
- match block.const_number_inputs.get(input_name) {
2207
- Some(value) => Some(value)
2208
- None =>
2209
- match block.input_block_ids.get(input_name) {
2210
- Some(block_id) =>
2211
- try_number_reporter_for_compare(vm, target_index, block_id, depth + 1)
2212
- None => None
2213
- }
2214
- }
2215
- }
2216
-
2217
- ///|
2218
- fn try_compare_numeric_inputs(
2219
- vm : Vm,
2220
- target_index : Int,
2221
- block : ScratchBlock,
2222
- left_input : String,
2223
- right_input : String,
2224
- depth : Int,
2225
- ) -> (Double, Double)? {
2226
- match
2227
- (
2228
- try_number_input_for_compare(vm, target_index, block, left_input, depth),
2229
- try_number_input_for_compare(vm, target_index, block, right_input, depth),
2230
- ) {
2231
- (Some(left), Some(right)) => Some((left, right))
2232
- _ => None
2233
- }
2234
- }
2235
-
2236
- ///|
2237
- fn eval_reporter_bool_block_depth(
2238
- vm : Vm,
2239
- target_index : Int,
2240
- block_id : String,
2241
- depth : Int,
2242
- ) -> Bool? {
2243
- if depth > 40 {
2244
- return None
2245
- }
2246
- if target_index < 0 || target_index >= vm.targets.length() {
2247
- return None
2248
- }
2249
- let target = vm.targets[target_index]
2250
- let block = match target.blocks.get(block_id) {
2251
- Some(value) => value
2252
- None => return None
2253
- }
2254
- match block.opcode {
2255
- "operator_lt" =>
2256
- match
2257
- try_compare_numeric_inputs(
2258
- vm, target_index, block, "OPERAND1", "OPERAND2", depth,
2259
- ) {
2260
- Some((left, right)) => Some(left < right)
2261
- None => None
2262
- }
2263
- "operator_gt" =>
2264
- match
2265
- try_compare_numeric_inputs(
2266
- vm, target_index, block, "OPERAND1", "OPERAND2", depth,
2267
- ) {
2268
- Some((left, right)) => Some(left > right)
2269
- None => None
2270
- }
2271
- "operator_equals" =>
2272
- match
2273
- try_compare_numeric_inputs(
2274
- vm, target_index, block, "OPERAND1", "OPERAND2", depth,
2275
- ) {
2276
- Some((left, right)) => Some(left == right)
2277
- None => None
2278
- }
2279
- "operator_and" =>
2280
- Some(
2281
- bool_from_input(vm, target_index, block, "OPERAND1", depth) &&
2282
- bool_from_input(vm, target_index, block, "OPERAND2", depth),
2283
- )
2284
- "operator_or" =>
2285
- Some(
2286
- bool_from_input(vm, target_index, block, "OPERAND1", depth) ||
2287
- bool_from_input(vm, target_index, block, "OPERAND2", depth),
2288
- )
2289
- "operator_not" =>
2290
- Some(!bool_from_input(vm, target_index, block, "OPERAND", depth))
2291
- _ => None
2292
- }
2293
- }
2294
-
2295
- ///|
2296
- fn bool_from_input(
2297
- vm : Vm,
2298
- target_index : Int,
2299
- block : ScratchBlock,
2300
- input_name : String,
2301
- depth : Int,
2302
- ) -> Bool {
2303
- match block.input_block_ids.get(input_name) {
2304
- Some(block_id) =>
2305
- match get_compiled_bool_reporter_program(vm, target_index, block_id) {
2306
- Some(program) =>
2307
- match eval_bool_reporter_program(vm, target_index, program) {
2308
- Some(value) => value
2309
- None =>
2310
- match
2311
- eval_reporter_bool_block_depth(
2312
- vm,
2313
- target_index,
2314
- block_id,
2315
- depth + 1,
2316
- ) {
2317
- Some(value) => value
2318
- None =>
2319
- json_to_bool_value(
2320
- eval_reporter_block_depth(
2321
- vm,
2322
- target_index,
2323
- block_id,
2324
- depth + 1,
2325
- ),
2326
- )
2327
- }
2328
- }
2329
- None =>
2330
- match
2331
- eval_reporter_bool_block_depth(
2332
- vm,
2333
- target_index,
2334
- block_id,
2335
- depth + 1,
2336
- ) {
2337
- Some(value) => value
2338
- None =>
2339
- json_to_bool_value(
2340
- eval_reporter_block_depth(vm, target_index, block_id, depth + 1),
2341
- )
2342
- }
2343
- }
2344
- None =>
2345
- json_to_bool_value(
2346
- value_from_input(vm, target_index, block, input_name, depth),
2347
- )
2348
- }
2349
- }
2350
-
2351
- ///|
2352
- fn eval_reporter_block_depth(
2353
- vm : Vm,
2354
- target_index : Int,
2355
- block_id : String,
2356
- depth : Int,
2357
- ) -> Json {
2358
- if depth > 40 {
2359
- return Json::null()
2360
- }
2361
-
2362
- let target = vm.targets[target_index]
2363
- let block = match target.blocks.get(block_id) {
2364
- Some(value) => value
2365
- None => return Json::null()
2366
- }
2367
-
2368
- match block.opcode {
2369
- "math_number"
2370
- | "math_integer"
2371
- | "math_whole_number"
2372
- | "math_positive_number"
2373
- | "math_angle" =>
2374
- match field_value(block, "NUM") {
2375
- Some((value, _)) =>
2376
- json_number(
2377
- match parse_double_or_none(value) {
2378
- Some(parsed) => parsed
2379
- None => 0.0
2380
- },
2381
- )
2382
- None => json_number(0.0)
2383
- }
2384
- "text" =>
2385
- match field_value(block, "TEXT") {
2386
- Some((value, _)) => json_string(value)
2387
- None => json_string("")
2388
- }
2389
- "event_broadcast_menu" =>
2390
- match field_value(block, "BROADCAST_OPTION") {
2391
- Some((value, _)) => json_string(value)
2392
- None => json_string("")
2393
- }
2394
- "control_create_clone_of_menu" =>
2395
- match field_value(block, "CLONE_OPTION") {
2396
- Some((value, _)) => json_string(value)
2397
- None => json_string("")
2398
- }
2399
- "sound_sounds_menu" =>
2400
- match field_value(block, "SOUND_MENU") {
2401
- Some((value, _)) => json_string(value)
2402
- None => json_string("")
2403
- }
2404
- "sound_beats_menu" =>
2405
- match field_value(block, "BEATS") {
2406
- Some((value, _)) => json_string(value)
2407
- None => json_string("")
2408
- }
2409
- "sound_effects_menu" =>
2410
- match field_value(block, "EFFECT") {
2411
- Some((value, _)) => json_string(value)
2412
- None => json_string("")
2413
- }
2414
- "pen_menu_colorParam" =>
2415
- match field_value(block, "colorParam") {
2416
- Some((value, _)) => json_string(value)
2417
- None =>
2418
- match field_value(block, "COLOR_PARAM") {
2419
- Some((value, _)) => json_string(value)
2420
- None => json_string("color")
2421
- }
2422
- }
2423
- "music_getTempo" => json_number(vm.music_tempo)
2424
- "argument_reporter_string_number" | "argument_reporter_boolean" => {
2425
- let arg_name = block_input_or_field_string(
2426
- vm, target_index, block, "VALUE", "VALUE", depth,
2427
- )
2428
- if arg_name == "" {
2429
- json_number(0.0)
2430
- } else {
2431
- match current_procedure_param(vm, arg_name) {
2432
- Some(value) => value
2433
- None => json_number(0.0)
2434
- }
2435
- }
2436
- }
2437
- "operator_add" => {
2438
- let left = number_from_input(vm, target_index, block, "NUM1", depth)
2439
- let right = number_from_input(vm, target_index, block, "NUM2", depth)
2440
- json_number(left + right)
2441
- }
2442
- "operator_subtract" => {
2443
- let left = number_from_input(vm, target_index, block, "NUM1", depth)
2444
- let right = number_from_input(vm, target_index, block, "NUM2", depth)
2445
- json_number(left - right)
2446
- }
2447
- "operator_multiply" => {
2448
- let left = number_from_input(vm, target_index, block, "NUM1", depth)
2449
- let right = number_from_input(vm, target_index, block, "NUM2", depth)
2450
- json_number(left * right)
2451
- }
2452
- "operator_divide" => {
2453
- let left = number_from_input(vm, target_index, block, "NUM1", depth)
2454
- let right = number_from_input(vm, target_index, block, "NUM2", depth)
2455
- if right == 0.0 {
2456
- json_number(0.0)
2457
- } else {
2458
- json_number(left / right)
2459
- }
2460
- }
2461
- "operator_random" => {
2462
- let from = number_from_input(vm, target_index, block, "FROM", depth)
2463
- let to = number_from_input(vm, target_index, block, "TO", depth)
2464
- let low = if from <= to { from } else { to }
2465
- let high = if from <= to { to } else { from }
2466
- let sampled = low + (high - low) * next_random_unit(vm)
2467
- json_number(sampled)
2468
- }
2469
- "operator_lt" =>
2470
- match
2471
- try_compare_numeric_inputs(
2472
- vm, target_index, block, "OPERAND1", "OPERAND2", depth,
2473
- ) {
2474
- Some((left, right)) => json_bool(left < right)
2475
- None => {
2476
- let left = value_from_input(
2477
- vm, target_index, block, "OPERAND1", depth,
2478
- )
2479
- let right = value_from_input(
2480
- vm, target_index, block, "OPERAND2", depth,
2481
- )
2482
- json_bool(scratch_compare(left, right) < 0)
2483
- }
2484
- }
2485
- "operator_gt" =>
2486
- match
2487
- try_compare_numeric_inputs(
2488
- vm, target_index, block, "OPERAND1", "OPERAND2", depth,
2489
- ) {
2490
- Some((left, right)) => json_bool(left > right)
2491
- None => {
2492
- let left = value_from_input(
2493
- vm, target_index, block, "OPERAND1", depth,
2494
- )
2495
- let right = value_from_input(
2496
- vm, target_index, block, "OPERAND2", depth,
2497
- )
2498
- json_bool(scratch_compare(left, right) > 0)
2499
- }
2500
- }
2501
- "operator_equals" =>
2502
- match
2503
- try_compare_numeric_inputs(
2504
- vm, target_index, block, "OPERAND1", "OPERAND2", depth,
2505
- ) {
2506
- Some((left, right)) => json_bool(left == right)
2507
- None => {
2508
- let left = value_from_input(
2509
- vm, target_index, block, "OPERAND1", depth,
2510
- )
2511
- let right = value_from_input(
2512
- vm, target_index, block, "OPERAND2", depth,
2513
- )
2514
- json_bool(scratch_equals(left, right))
2515
- }
2516
- }
2517
- "operator_and" =>
2518
- json_bool(
2519
- bool_from_input(vm, target_index, block, "OPERAND1", depth) &&
2520
- bool_from_input(vm, target_index, block, "OPERAND2", depth),
2521
- )
2522
- "operator_or" =>
2523
- json_bool(
2524
- bool_from_input(vm, target_index, block, "OPERAND1", depth) ||
2525
- bool_from_input(vm, target_index, block, "OPERAND2", depth),
2526
- )
2527
- "operator_not" =>
2528
- json_bool(!bool_from_input(vm, target_index, block, "OPERAND", depth))
2529
- "operator_join" => {
2530
- let left = json_to_string_value(
2531
- value_from_input(vm, target_index, block, "STRING1", depth),
2532
- )
2533
- let right = json_to_string_value(
2534
- value_from_input(vm, target_index, block, "STRING2", depth),
2535
- )
2536
- json_string(left + right)
2537
- }
2538
- "operator_letter_of" => {
2539
- let index = number_from_input(vm, target_index, block, "LETTER", depth).to_int() -
2540
- 1
2541
- let text = json_to_string_value(
2542
- value_from_input(vm, target_index, block, "STRING", depth),
2543
- ).to_array()
2544
- if index < 0 || index >= text.length() {
2545
- json_string("")
2546
- } else {
2547
- json_string(String::from_array([text[index]]))
2548
- }
2549
- }
2550
- "operator_length" => {
2551
- let text = json_to_string_value(
2552
- value_from_input(vm, target_index, block, "STRING", depth),
2553
- )
2554
- json_number(Double::from_int(text.char_length()))
2555
- }
2556
- "operator_contains" => {
2557
- let text = json_to_string_value(
2558
- value_from_input(vm, target_index, block, "STRING1", depth),
2559
- ).to_lower()
2560
- let needle = json_to_string_value(
2561
- value_from_input(vm, target_index, block, "STRING2", depth),
2562
- ).to_lower()
2563
- json_bool(text.contains(needle))
2564
- }
2565
- "operator_mod" => {
2566
- let left = number_from_input(vm, target_index, block, "NUM1", depth)
2567
- let right = number_from_input(vm, target_index, block, "NUM2", depth)
2568
- if right == 0.0 {
2569
- json_number(0.0)
2570
- } else {
2571
- json_number(left.mod(right))
2572
- }
2573
- }
2574
- "operator_round" => {
2575
- let value = number_from_input(vm, target_index, block, "NUM", depth)
2576
- json_number(value.round())
2577
- }
2578
- "operator_mathop" => {
2579
- let op = match field_value(block, "OPERATOR") {
2580
- Some((name, _)) => name
2581
- None => ""
2582
- }
2583
- let value = number_from_input(vm, target_index, block, "NUM", depth)
2584
- json_number(mathop(vm, op, value))
2585
- }
2586
- "motion_xposition" => json_number(vm.targets[target_index].x)
2587
- "motion_yposition" => json_number(vm.targets[target_index].y)
2588
- "motion_direction" => json_number(vm.targets[target_index].direction)
2589
- "motion_xscroll" => json_number(0.0)
2590
- "motion_yscroll" => json_number(0.0)
2591
- "looks_size" => json_number(vm.targets[target_index].size)
2592
- "looks_costumenumbername" => {
2593
- let number_name = match field_value(block, "NUMBER_NAME") {
2594
- Some((value, _)) => value
2595
- None => "number"
2596
- }
2597
- looks_number_name_json(vm.targets[target_index], number_name)
2598
- }
2599
- "looks_backdropnumbername" => {
2600
- let number_name = match field_value(block, "NUMBER_NAME") {
2601
- Some((value, _)) => value
2602
- None => "number"
2603
- }
2604
- if vm.stage_index >= 0 && vm.stage_index < vm.targets.length() {
2605
- looks_number_name_json(vm.targets[vm.stage_index], number_name)
2606
- } else {
2607
- json_number(1.0)
2608
- }
2609
- }
2610
- "sound_volume" => json_number(vm.targets[target_index].volume)
2611
- "sensing_answer" => json_string(vm.answer)
2612
- "sensing_timer" =>
2613
- json_number(Double::from_int(vm.now_ms - vm.timer_start_ms) / 1000.0)
2614
- "sensing_mousex" => {
2615
- let (x, _) = read_mouse_xy(vm)
2616
- json_number(x)
2617
- }
2618
- "sensing_mousey" => {
2619
- let (_, y) = read_mouse_xy(vm)
2620
- json_number(y)
2621
- }
2622
- "sensing_mousedown" => json_bool(read_mouse_down(vm))
2623
- "sensing_keypressed" => {
2624
- let option = block_input_or_field_string(
2625
- vm, target_index, block, "KEY_OPTION", "KEY_OPTION", depth,
2626
- )
2627
- json_bool(key_is_pressed(vm, option))
2628
- }
2629
- "sensing_touchingobject" => {
2630
- let option = block_input_or_field_string(
2631
- vm, target_index, block, "TOUCHINGOBJECTMENU", "TOUCHINGOBJECTMENU", depth,
2632
- )
2633
- if option == "" {
2634
- json_bool(false)
2635
- } else {
2636
- let target = vm.targets[target_index]
2637
- let touching_edge = lower_trim(option) == "_edge_" &&
2638
- !target.is_stage &&
2639
- touching_stage_edge(target)
2640
- json_bool(
2641
- touching_edge || read_touching_from_io(vm, target.name, option),
2642
- )
2643
- }
2644
- }
2645
- "sensing_touchingcolor" => {
2646
- let color = value_from_input(vm, target_index, block, "COLOR", depth)
2647
- json_bool(
2648
- target_is_touching_color(vm, target_index, render_json_to_rgb(color)),
2649
- )
2650
- }
2651
- "sensing_coloristouchingcolor" => {
2652
- let mask_color = value_from_input(vm, target_index, block, "COLOR", depth)
2653
- let target_color = value_from_input(
2654
- vm, target_index, block, "COLOR2", depth,
2655
- )
2656
- json_bool(
2657
- target_color_is_touching_color(
2658
- vm,
2659
- target_index,
2660
- render_json_to_rgb(target_color),
2661
- render_json_to_rgb(mask_color),
2662
- ),
2663
- )
2664
- }
2665
- "sensing_distanceto" => {
2666
- let target = vm.targets[target_index]
2667
- if target.is_stage {
2668
- json_number(10000.0)
2669
- } else {
2670
- let menu = block_input_or_field_string(
2671
- vm, target_index, block, "DISTANCETOMENU", "DISTANCETOMENU", depth,
2672
- )
2673
- let target_pos = if lower_trim(menu) == "_mouse_" {
2674
- Some(read_mouse_xy(vm))
2675
- } else {
2676
- match resolve_sensing_object_target(vm, target_index, menu) {
2677
- Some(other_index) =>
2678
- if other_index >= 0 &&
2679
- other_index < vm.targets.length() &&
2680
- !vm.targets[other_index].deleted {
2681
- let other = vm.targets[other_index]
2682
- Some((other.x, other.y))
2683
- } else {
2684
- None
2685
- }
2686
- None => None
2687
- }
2688
- }
2689
- match target_pos {
2690
- Some((target_x, target_y)) => {
2691
- let dx = target.x - target_x
2692
- let dy = target.y - target_y
2693
- json_number((dx * dx + dy * dy).sqrt())
2694
- }
2695
- None => json_number(10000.0)
2696
- }
2697
- }
2698
- }
2699
- "sensing_of" => {
2700
- let property_raw = block_input_or_field_string(
2701
- vm, target_index, block, "PROPERTY", "PROPERTY", depth,
2702
- )
2703
- let property_key = lower_trim(property_raw)
2704
- let object_name = block_input_or_field_string(
2705
- vm, target_index, block, "OBJECT", "OBJECT", depth,
2706
- )
2707
- match resolve_sensing_object_target(vm, target_index, object_name) {
2708
- Some(object_index) =>
2709
- if object_index >= 0 &&
2710
- object_index < vm.targets.length() &&
2711
- !vm.targets[object_index].deleted {
2712
- let object_target = vm.targets[object_index]
2713
- match sensing_of_builtin_value(object_target, property_key) {
2714
- Some(value) => value
2715
- None =>
2716
- match object_target.variable_names.get(property_raw) {
2717
- Some(variable_id) =>
2718
- object_target.variables.get_or_default(
2719
- variable_id,
2720
- json_number(0.0),
2721
- )
2722
- None => json_number(0.0)
2723
- }
2724
- }
2725
- } else {
2726
- json_number(0.0)
2727
- }
2728
- None => json_number(0.0)
2729
- }
2730
- }
2731
- "sensing_current" => {
2732
- let menu = block_input_or_field_string(
2733
- vm, target_index, block, "CURRENTMENU", "CURRENTMENU", depth,
2734
- )
2735
- json_number(current_menu_value(vm, menu))
2736
- }
2737
- "sensing_dayssince2000" => json_number(days_since_2000(vm))
2738
- "sensing_loudness" => json_number(read_loudness(vm))
2739
- "sensing_loud" => json_bool(read_loudness(vm) > 10.0)
2740
- "sensing_online" => json_bool(online_status(vm))
2741
- "sensing_username" => json_string(read_username(vm))
2742
- "sensing_userid" => read_userid(vm)
2743
- "translate_getViewerLanguage" => json_string(viewer_language(vm))
2744
- "translate_getTranslate" => {
2745
- let words = json_to_string_value(
2746
- value_from_input(vm, target_index, block, "WORDS", depth),
2747
- )
2748
- if is_ascii_digits_only(words) {
2749
- json_string(words)
2750
- } else {
2751
- let language = normalize_translate_language(
2752
- json_to_string_value(
2753
- value_from_input(vm, target_index, block, "LANGUAGE", depth),
2754
- ),
2755
- )
2756
- let pending_key = translate_pending_key(words, language)
2757
- match translate_cache_lookup(vm, words, language) {
2758
- Some(translated) => {
2759
- vm.pending_translate_requests.remove(pending_key)
2760
- json_string(translated)
2761
- }
2762
- None => {
2763
- if !vm.pending_translate_requests.contains(pending_key) {
2764
- vm.pending_translate_requests[pending_key] = true
2765
- push_effect(vm, HostEffect::TranslateRequest(words, language))
2766
- }
2767
- json_string(words)
2768
- }
2769
- }
2770
- }
2771
- }
2772
- "control_get_counter" => json_number(Double::from_int(vm.control_counter))
2773
- "data_variable" =>
2774
- match field_value(block, "VARIABLE") {
2775
- Some((name, id)) => read_variable(vm, target_index, id, Some(name))
2776
- None => Json::null()
2777
- }
2778
- "data_listcontents" =>
2779
- match field_value(block, "LIST") {
2780
- Some((name, id)) => {
2781
- let list = read_list(vm, target_index, id, Some(name))
2782
- json_string(list.map(json_to_string_value).join(" "))
2783
- }
2784
- None => json_string("")
2785
- }
2786
- "data_itemoflist" =>
2787
- match field_value(block, "LIST") {
2788
- Some((name, id)) => {
2789
- let list = read_list(vm, target_index, id, Some(name))
2790
- let index_value = value_from_input(
2791
- vm, target_index, block, "INDEX", depth,
2792
- )
2793
- match
2794
- normalize_index(index_value, list.length(), next_random_unit(vm)) {
2795
- Some(index) =>
2796
- match list.get(index) {
2797
- Some(value) => value
2798
- None => Json::null()
2799
- }
2800
- None => Json::null()
2801
- }
2802
- }
2803
- None => Json::null()
2804
- }
2805
- "data_itemnumoflist" =>
2806
- match field_value(block, "LIST") {
2807
- Some((name, id)) => {
2808
- let list = read_list(vm, target_index, id, Some(name))
2809
- let item = value_from_input(vm, target_index, block, "ITEM", depth)
2810
- let mut found = 0
2811
- for i, entry in list {
2812
- if found == 0 && scratch_equals(entry, item) {
2813
- found = i + 1
2814
- }
2815
- }
2816
- json_number(Double::from_int(found))
2817
- }
2818
- None => json_number(0.0)
2819
- }
2820
- "data_lengthoflist" =>
2821
- match field_value(block, "LIST") {
2822
- Some((name, id)) => {
2823
- let list = read_list(vm, target_index, id, Some(name))
2824
- json_number(Double::from_int(list.length()))
2825
- }
2826
- None => json_number(0.0)
2827
- }
2828
- "data_listcontainsitem" =>
2829
- match field_value(block, "LIST") {
2830
- Some((name, id)) => {
2831
- let list = read_list(vm, target_index, id, Some(name))
2832
- let item = value_from_input(vm, target_index, block, "ITEM", depth)
2833
- let needle = json_to_string_value(item).to_lower()
2834
- json_bool(
2835
- list.any(fn(entry) {
2836
- json_to_string_value(entry).to_lower() == needle
2837
- }),
2838
- )
2839
- }
2840
- None => json_bool(false)
2841
- }
2842
- _ => Json::null()
2843
- }
2844
- }