moonscratch 0.1.0 → 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 (150) 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/.turbo/turbo-typecheck.log +0 -2
  28. package/AGENTS.md +0 -91
  29. package/PLAN.md +0 -64
  30. package/TODO.md +0 -120
  31. package/benchmarks/calc.bench.ts +0 -144
  32. package/benchmarks/draw.bench.ts +0 -215
  33. package/benchmarks/load.bench.ts +0 -28
  34. package/benchmarks/render.bench.ts +0 -53
  35. package/benchmarks/run.bench.ts +0 -8
  36. package/benchmarks/types.d.ts +0 -15
  37. package/docs/scratch-vm-specs/eventloop.md +0 -103
  38. package/docs/scratch-vm-specs/moonscratch-time-separation.md +0 -50
  39. package/index.html +0 -91
  40. package/js/AGENTS.md +0 -5
  41. package/js/a.ts +0 -52
  42. package/js/assets/AGENTS.md +0 -5
  43. package/js/assets/base64.test.ts +0 -14
  44. package/js/assets/base64.ts +0 -21
  45. package/js/assets/build-asset.test.ts +0 -26
  46. package/js/assets/build-asset.ts +0 -28
  47. package/js/assets/create.test.ts +0 -142
  48. package/js/assets/create.ts +0 -122
  49. package/js/assets/index.test.ts +0 -15
  50. package/js/assets/index.ts +0 -2
  51. package/js/assets/types.ts +0 -26
  52. package/js/assets/validation.test.ts +0 -34
  53. package/js/assets/validation.ts +0 -25
  54. package/js/assets.test.ts +0 -14
  55. package/js/assets.ts +0 -1
  56. package/js/index.test.ts +0 -26
  57. package/js/index.ts +0 -3
  58. package/js/render/index.test.ts +0 -65
  59. package/js/render/index.ts +0 -13
  60. package/js/render/sharp.ts +0 -87
  61. package/js/render/svg.ts +0 -68
  62. package/js/render/types.ts +0 -35
  63. package/js/render/utils.ts +0 -108
  64. package/js/render/webgl.ts +0 -274
  65. package/js/sharp-optional.d.ts +0 -16
  66. package/js/test/helpers.ts +0 -116
  67. package/js/test/hikkaku-sample.test.ts +0 -37
  68. package/js/test/rubik-components.input-motion.test.ts +0 -60
  69. package/js/test/rubik-components.lists.test.ts +0 -49
  70. package/js/test/rubik-components.operators.test.ts +0 -104
  71. package/js/test/rubik-components.pen.test.ts +0 -112
  72. package/js/test/rubik-components.procedures-loops.test.ts +0 -72
  73. package/js/test/rubik-components.variables-branches.test.ts +0 -57
  74. package/js/test/rubik-components.visibility-entry.test.ts +0 -31
  75. package/js/test/test-projects.ts +0 -598
  76. package/js/test/variable.ts +0 -200
  77. package/js/test/warp.test.ts +0 -59
  78. package/js/vm/AGENTS.md +0 -6
  79. package/js/vm/README.md +0 -183
  80. package/js/vm/bindings.test.ts +0 -13
  81. package/js/vm/bindings.ts +0 -5
  82. package/js/vm/compare-operators.test.ts +0 -145
  83. package/js/vm/constants.test.ts +0 -11
  84. package/js/vm/constants.ts +0 -4
  85. package/js/vm/effect-guards.test.ts +0 -68
  86. package/js/vm/effect-guards.ts +0 -44
  87. package/js/vm/factory.test.ts +0 -486
  88. package/js/vm/factory.ts +0 -615
  89. package/js/vm/headless-vm.test.ts +0 -131
  90. package/js/vm/headless-vm.ts +0 -342
  91. package/js/vm/index.test.ts +0 -28
  92. package/js/vm/index.ts +0 -5
  93. package/js/vm/internal-types.ts +0 -32
  94. package/js/vm/json.test.ts +0 -40
  95. package/js/vm/json.ts +0 -273
  96. package/js/vm/normalize.test.ts +0 -48
  97. package/js/vm/normalize.ts +0 -65
  98. package/js/vm/options.test.ts +0 -30
  99. package/js/vm/options.ts +0 -55
  100. package/js/vm/pen-transparency.test.ts +0 -115
  101. package/js/vm/program-wasm.ts +0 -322
  102. package/js/vm/scheduler-render.test.ts +0 -401
  103. package/js/vm/scratch-assets.test.ts +0 -136
  104. package/js/vm/scratch-assets.ts +0 -202
  105. package/js/vm/types.ts +0 -358
  106. package/js/vm/value-guards.test.ts +0 -25
  107. package/js/vm/value-guards.ts +0 -18
  108. package/moon.mod.json +0 -10
  109. package/scripts/preinstall.ts +0 -4
  110. package/src/AGENTS.md +0 -6
  111. package/src/api.mbt +0 -161
  112. package/src/api_aot_commands.mbt +0 -184
  113. package/src/api_effects_json.mbt +0 -72
  114. package/src/api_options.mbt +0 -60
  115. package/src/api_program_wasm.mbt +0 -1647
  116. package/src/api_program_wat.mbt +0 -2206
  117. package/src/api_snapshot_json.mbt +0 -44
  118. package/src/cmd/AGENTS.md +0 -5
  119. package/src/cmd/main/AGENTS.md +0 -5
  120. package/src/cmd/main/main.mbt +0 -29
  121. package/src/cmd/main/moon.pkg +0 -7
  122. package/src/cmd/main/pkg.generated.mbti +0 -13
  123. package/src/json_helpers.mbt +0 -176
  124. package/src/moon.pkg +0 -65
  125. package/src/moonscratch.mbt +0 -3
  126. package/src/moonscratch_wbtest.mbt +0 -40
  127. package/src/parser_sb3.mbt +0 -890
  128. package/src/pkg.generated.mbti +0 -479
  129. package/src/runtime_eval.mbt +0 -2844
  130. package/src/runtime_exec.mbt +0 -3850
  131. package/src/runtime_render.mbt +0 -2550
  132. package/src/runtime_state.mbt +0 -870
  133. package/src/test/AGENTS.md +0 -3
  134. package/src/test/projects/AGENTS.md +0 -6
  135. package/src/test/projects/moon.pkg +0 -4
  136. package/src/test/projects/moonscratch_compat_test.mbt +0 -642
  137. package/src/test/projects/moonscratch_core_test.mbt +0 -1332
  138. package/src/test/projects/moonscratch_runtime_test.mbt +0 -1087
  139. package/src/test/projects/pkg.generated.mbti +0 -13
  140. package/src/test/projects/test_support.mbt +0 -35
  141. package/src/types_effects.mbt +0 -20
  142. package/src/types_error.mbt +0 -4
  143. package/src/types_options.mbt +0 -31
  144. package/src/types_runtime_structs.mbt +0 -254
  145. package/src/types_vm.mbt +0 -109
  146. package/tsconfig.json +0 -29
  147. package/viewer/index.ts +0 -399
  148. package/viewer/vite.d.ts +0 -1
  149. package/viewer/worker.ts +0 -161
  150. package/vite.config.ts +0 -11
@@ -1,890 +0,0 @@
1
- ///|
2
- fn is_hat_opcode(opcode : String) -> Bool {
3
- opcode == "event_whenflagclicked" ||
4
- opcode == "event_whenbroadcastreceived" ||
5
- opcode == "event_whenkeypressed" ||
6
- opcode == "event_whenthisspriteclicked" ||
7
- opcode == "event_whentouchingobject" ||
8
- opcode == "event_whenstageclicked" ||
9
- opcode == "event_whenbackdropswitchesto" ||
10
- opcode == "event_whengreaterthan" ||
11
- opcode == "control_start_as_clone"
12
- }
13
-
14
- ///|
15
- fn json_to_string_option(json : Json) -> String? {
16
- match json {
17
- String(value) => Some(value)
18
- Null => None
19
- _ => None
20
- }
21
- }
22
-
23
- ///|
24
- fn input_payload(raw : Json) -> Json? {
25
- match raw {
26
- Array(parts) =>
27
- if parts.length() >= 3 {
28
- match parts[1] {
29
- Null => Some(parts[2])
30
- _ => Some(parts[1])
31
- }
32
- } else if parts.length() >= 2 {
33
- Some(parts[1])
34
- } else {
35
- None
36
- }
37
- _ => Some(raw)
38
- }
39
- }
40
-
41
- ///|
42
- fn payload_const_number(payload : Json) -> Double? {
43
- match payload {
44
- Array(primitive) =>
45
- if primitive.length() >= 2 {
46
- let code = json_to_number_value(primitive[0]).to_int()
47
- if code >= 4 && code <= 8 {
48
- Some(json_to_number_value(primitive[1]))
49
- } else {
50
- None
51
- }
52
- } else {
53
- None
54
- }
55
- _ => None
56
- }
57
- }
58
-
59
- ///|
60
- fn extract_const_number_inputs(
61
- inputs : Map[String, Json],
62
- ) -> Map[String, Double] {
63
- let out = {}
64
- inputs.each((name, raw_input) => {
65
- match input_payload(raw_input) {
66
- Some(payload) =>
67
- match payload_const_number(payload) {
68
- Some(value) => out[name] = value
69
- None => ()
70
- }
71
- None => ()
72
- }
73
- })
74
- out
75
- }
76
-
77
- ///|
78
- fn extract_input_block_ids(inputs : Map[String, Json]) -> Map[String, String] {
79
- let out = {}
80
- inputs.each((name, raw_input) => {
81
- match input_payload(raw_input) {
82
- Some(String(id)) => out[name] = id
83
- _ => ()
84
- }
85
- })
86
- out
87
- }
88
-
89
- ///|
90
- fn opcode_tag_for(opcode : String) -> OpcodeTag {
91
- match opcode {
92
- "motion_changexby" => OpcodeTag::MotionChangeXBy
93
- "motion_changeyby" => OpcodeTag::MotionChangeYBy
94
- "motion_sety" => OpcodeTag::MotionSetY
95
- "pen_penDown" => OpcodeTag::PenPenDown
96
- "pen_penUp" => OpcodeTag::PenPenUp
97
- "control_repeat" => OpcodeTag::ControlRepeat
98
- "control_repeat_until" => OpcodeTag::ControlRepeatUntil
99
- "control_if" => OpcodeTag::ControlIf
100
- "data_setvariableto" => OpcodeTag::DataSetVariableTo
101
- "data_changevariableby" => OpcodeTag::DataChangeVariableBy
102
- _ => OpcodeTag::Unknown
103
- }
104
- }
105
-
106
- ///|
107
- fn parse_scratch_block(id : String, json : Json) -> ScratchBlock raise VmError {
108
- let obj = expect_object(json, "block \{id}")
109
- let opcode = object_get_string_or(obj, "opcode", "")
110
- if opcode == "" {
111
- invalid_project("block \{id} has no opcode")
112
- }
113
- let next = match object_get(obj, "next") {
114
- Some(value) => json_to_string_option(value)
115
- None => None
116
- }
117
- let parent = match object_get(obj, "parent") {
118
- Some(value) => json_to_string_option(value)
119
- None => None
120
- }
121
- let inputs = object_get_object_or_empty(obj, "inputs")
122
- let const_number_inputs = extract_const_number_inputs(inputs)
123
- let input_block_ids = extract_input_block_ids(inputs)
124
- let fields = object_get_object_or_empty(obj, "fields")
125
- let mutation = object_get_object_or_empty(obj, "mutation")
126
- let top_level = object_get_bool_or(obj, "topLevel", false)
127
- {
128
- id,
129
- opcode,
130
- opcode_tag: opcode_tag_for(opcode),
131
- next,
132
- parent,
133
- inputs,
134
- const_number_inputs,
135
- input_block_ids,
136
- fields,
137
- mutation,
138
- top_level,
139
- }
140
- }
141
-
142
- ///|
143
- fn parse_variables(
144
- raw : Map[String, Json],
145
- ) -> (
146
- Map[String, Json],
147
- Map[String, String],
148
- Map[String, Int],
149
- Map[String, Int],
150
- Array[String],
151
- Array[Json],
152
- ) {
153
- let values = {}
154
- let names = {}
155
- let slot_by_id = {}
156
- let slot_by_name = {}
157
- let ids_by_slot = []
158
- let values_by_slot = []
159
- raw.each(fn(id, descriptor) {
160
- match descriptor {
161
- Array(parts) =>
162
- if parts.length() >= 2 {
163
- let name = json_to_string_value(parts[0])
164
- values[id] = parts[1]
165
- names[name] = id
166
- let slot = values_by_slot.length()
167
- slot_by_id[id] = slot
168
- slot_by_name[name] = slot
169
- ids_by_slot.push(id)
170
- values_by_slot.push(parts[1])
171
- }
172
- _ => ()
173
- }
174
- })
175
- (values, names, slot_by_id, slot_by_name, ids_by_slot, values_by_slot)
176
- }
177
-
178
- ///|
179
- fn parse_lists(
180
- raw : Map[String, Json],
181
- ) -> (
182
- Map[String, Array[Json]],
183
- Map[String, String],
184
- Map[String, Int],
185
- Map[String, Int],
186
- Array[String],
187
- Array[Array[Json]],
188
- ) {
189
- let values = {}
190
- let names = {}
191
- let slot_by_id = {}
192
- let slot_by_name = {}
193
- let ids_by_slot = []
194
- let values_by_slot = []
195
- raw.each(fn(id, descriptor) {
196
- match descriptor {
197
- Array(parts) =>
198
- if parts.length() >= 2 {
199
- let name = json_to_string_value(parts[0])
200
- let mut list_items = []
201
- match parts[1] {
202
- Array(items) => list_items = items
203
- _ => ()
204
- }
205
- values[id] = list_items
206
- names[name] = id
207
- let slot = values_by_slot.length()
208
- slot_by_id[id] = slot
209
- slot_by_name[name] = slot
210
- ids_by_slot.push(id)
211
- values_by_slot.push(list_items)
212
- }
213
- _ => ()
214
- }
215
- })
216
- (values, names, slot_by_id, slot_by_name, ids_by_slot, values_by_slot)
217
- }
218
-
219
- ///|
220
- fn json_is_numeric_comparable(value : Json) -> Bool {
221
- match as_number_or_none(value) {
222
- Some(number) => number == number
223
- None => false
224
- }
225
- }
226
-
227
- ///|
228
- fn parse_blocks(
229
- raw : Map[String, Json],
230
- ) -> (Map[String, ScratchBlock], Array[String]) raise VmError {
231
- let blocks = {}
232
- let hats = []
233
- raw.each((id, block_json) => {
234
- let block = parse_scratch_block(id, block_json)
235
- if block.top_level && is_hat_opcode(block.opcode) {
236
- hats.push(id)
237
- }
238
- blocks[id] = block
239
- })
240
- (blocks, hats)
241
- }
242
-
243
- ///|
244
- fn build_block_pc_index(
245
- blocks : Map[String, ScratchBlock],
246
- ) -> (Map[String, Int], Array[ScratchBlock]) {
247
- let block_pc_by_id = {}
248
- let blocks_by_pc = []
249
- blocks.each((id, block) => {
250
- block_pc_by_id[id] = blocks_by_pc.length()
251
- blocks_by_pc.push(block)
252
- })
253
- (block_pc_by_id, blocks_by_pc)
254
- }
255
-
256
- ///|
257
- fn block_field_ref(block : ScratchBlock, field_name : String) -> BlockFieldRef? {
258
- match field_value(block, field_name) {
259
- Some((name, id)) => Some({ name, id })
260
- None => None
261
- }
262
- }
263
-
264
- ///|
265
- fn resolve_block_pc(
266
- block_pc_by_id : Map[String, Int],
267
- block_id : String?,
268
- ) -> Int? {
269
- match block_id {
270
- Some(id) => block_pc_by_id.get(id)
271
- None => None
272
- }
273
- }
274
-
275
- ///|
276
- fn build_block_fast_meta_by_pc(
277
- blocks_by_pc : Array[ScratchBlock],
278
- block_pc_by_id : Map[String, Int],
279
- ) -> Array[BlockFastMeta] {
280
- let out = []
281
- for block in blocks_by_pc {
282
- out.push({
283
- next_pc: resolve_block_pc(block_pc_by_id, block.next),
284
- substack_pc: resolve_block_pc(
285
- block_pc_by_id,
286
- block_input_block_id(block, "SUBSTACK"),
287
- ),
288
- substack2_pc: resolve_block_pc(
289
- block_pc_by_id,
290
- block_input_block_id(block, "SUBSTACK2"),
291
- ),
292
- variable: block_field_ref(block, "VARIABLE"),
293
- list: block_field_ref(block, "LIST"),
294
- })
295
- }
296
- out
297
- }
298
-
299
- ///|
300
- fn const_mathop(name : String, value : Double) -> Double {
301
- match lower_trim(name) {
302
- "abs" => value.abs()
303
- "floor" => value.floor()
304
- "ceiling" => value.ceil()
305
- "sqrt" => value.sqrt()
306
- "sin" => @math.sin(value * @math.PI / 180.0)
307
- "cos" => @math.cos(value * @math.PI / 180.0)
308
- "tan" => @math.tan(value * @math.PI / 180.0)
309
- "asin" => @math.asin(value) * 180.0 / @math.PI
310
- "acos" => @math.acos(value) * 180.0 / @math.PI
311
- "atan" => @math.atan(value) * 180.0 / @math.PI
312
- "ln" => @math.ln(value)
313
- "log" => @math.log10(value)
314
- "e ^" => @math.exp(value)
315
- "10 ^" => @math.pow(10.0, value)
316
- _ => value
317
- }
318
- }
319
-
320
- ///|
321
- fn const_number_input_from_block(
322
- blocks : Map[String, ScratchBlock],
323
- block : ScratchBlock,
324
- input_name : String,
325
- cache : Map[String, Double],
326
- visiting : Map[String, Bool],
327
- ) -> Double? {
328
- match block.const_number_inputs.get(input_name) {
329
- Some(value) => Some(value)
330
- None =>
331
- match block.input_block_ids.get(input_name) {
332
- Some(child_id) =>
333
- const_number_block_value(blocks, child_id, cache, visiting)
334
- None => None
335
- }
336
- }
337
- }
338
-
339
- ///|
340
- fn const_number_block_value(
341
- blocks : Map[String, ScratchBlock],
342
- block_id : String,
343
- cache : Map[String, Double],
344
- visiting : Map[String, Bool],
345
- ) -> Double? {
346
- match cache.get(block_id) {
347
- Some(value) => return Some(value)
348
- None => ()
349
- }
350
- if visiting.contains(block_id) {
351
- return None
352
- }
353
- visiting[block_id] = true
354
- let block = match blocks.get(block_id) {
355
- Some(value) => value
356
- None => {
357
- visiting.remove(block_id)
358
- return None
359
- }
360
- }
361
-
362
- let resolved = match block.opcode {
363
- "math_number"
364
- | "math_integer"
365
- | "math_whole_number"
366
- | "math_positive_number"
367
- | "math_angle" =>
368
- match field_value(block, "NUM") {
369
- Some((raw, _)) =>
370
- Some(
371
- match parse_double_or_none(raw) {
372
- Some(parsed) => parsed
373
- None => 0.0
374
- },
375
- )
376
- None => Some(0.0)
377
- }
378
- "operator_add" =>
379
- match
380
- (
381
- const_number_input_from_block(blocks, block, "NUM1", cache, visiting),
382
- const_number_input_from_block(blocks, block, "NUM2", cache, visiting),
383
- ) {
384
- (Some(left), Some(right)) => Some(left + right)
385
- _ => None
386
- }
387
- "operator_subtract" =>
388
- match
389
- (
390
- const_number_input_from_block(blocks, block, "NUM1", cache, visiting),
391
- const_number_input_from_block(blocks, block, "NUM2", cache, visiting),
392
- ) {
393
- (Some(left), Some(right)) => Some(left - right)
394
- _ => None
395
- }
396
- "operator_multiply" =>
397
- match
398
- (
399
- const_number_input_from_block(blocks, block, "NUM1", cache, visiting),
400
- const_number_input_from_block(blocks, block, "NUM2", cache, visiting),
401
- ) {
402
- (Some(left), Some(right)) => Some(left * right)
403
- _ => None
404
- }
405
- "operator_divide" =>
406
- match
407
- (
408
- const_number_input_from_block(blocks, block, "NUM1", cache, visiting),
409
- const_number_input_from_block(blocks, block, "NUM2", cache, visiting),
410
- ) {
411
- (Some(left), Some(right)) =>
412
- if right == 0.0 {
413
- Some(0.0)
414
- } else {
415
- Some(left / right)
416
- }
417
- _ => None
418
- }
419
- "operator_mod" =>
420
- match
421
- (
422
- const_number_input_from_block(blocks, block, "NUM1", cache, visiting),
423
- const_number_input_from_block(blocks, block, "NUM2", cache, visiting),
424
- ) {
425
- (Some(left), Some(right)) =>
426
- if right == 0.0 {
427
- Some(0.0)
428
- } else {
429
- Some(left.mod(right))
430
- }
431
- _ => None
432
- }
433
- "operator_round" =>
434
- match
435
- const_number_input_from_block(blocks, block, "NUM", cache, visiting) {
436
- Some(value) => Some(value.round())
437
- None => None
438
- }
439
- "operator_mathop" => {
440
- let op = match field_value(block, "OPERATOR") {
441
- Some((name, _)) => name
442
- None => ""
443
- }
444
- match
445
- const_number_input_from_block(blocks, block, "NUM", cache, visiting) {
446
- Some(value) => Some(const_mathop(op, value))
447
- None => None
448
- }
449
- }
450
- _ => None
451
- }
452
- visiting.remove(block_id)
453
- match resolved {
454
- Some(value) => {
455
- cache[block_id] = value
456
- Some(value)
457
- }
458
- None => None
459
- }
460
- }
461
-
462
- ///|
463
- fn build_const_number_block_values(
464
- blocks : Map[String, ScratchBlock],
465
- ) -> Map[String, Double] {
466
- let cache = {}
467
- blocks.each((block_id, _) => {
468
- ignore(const_number_block_value(blocks, block_id, cache, {}))
469
- })
470
- cache
471
- }
472
-
473
- ///|
474
- fn build_procedure_specs(
475
- blocks : Map[String, ScratchBlock],
476
- ) -> Map[String, ProcedureSpec] {
477
- let procedures = {}
478
- blocks.each((_, block) => {
479
- if block.opcode == "procedures_definition" {
480
- match block_input_block_id(block, "custom_block") {
481
- Some(proto_id) =>
482
- match blocks.get(proto_id) {
483
- Some(prototype) =>
484
- if prototype.opcode == "procedures_prototype" {
485
- let proccode = match
486
- block_mutation_string(prototype, "proccode") {
487
- Some(value) => value
488
- None => ""
489
- }
490
- if proccode != "" {
491
- match block.next {
492
- Some(start_block) => {
493
- let param_names = match
494
- block_mutation_string(prototype, "argumentnames") {
495
- Some(raw) => parse_json_string_array(raw)
496
- None => []
497
- }
498
- let param_ids = match
499
- block_mutation_string(prototype, "argumentids") {
500
- Some(raw) => parse_json_string_array(raw)
501
- None => []
502
- }
503
- let param_defaults = match
504
- block_mutation_string(prototype, "argumentdefaults") {
505
- Some(raw) => parse_json_value_array(raw)
506
- None => []
507
- }
508
- let warp_mode = block_mutation_bool(prototype, "warp")
509
- procedures[proccode] = {
510
- start_block,
511
- param_names,
512
- param_ids,
513
- param_defaults,
514
- warp_mode,
515
- }
516
- }
517
- None => ()
518
- }
519
- }
520
- }
521
- None => ()
522
- }
523
- None => ()
524
- }
525
- }
526
- })
527
- procedures
528
- }
529
-
530
- ///|
531
- fn empty_rgba_pixels(width : Int, height : Int) -> Array[Byte] {
532
- let out = []
533
- if width <= 0 || height <= 0 {
534
- return out
535
- }
536
- for _ in 0..<(width * height * 4) {
537
- out.push(b'\x00')
538
- }
539
- out
540
- }
541
-
542
- ///|
543
- fn parse_costume_asset_pixels(
544
- asset_json : Json,
545
- context : String,
546
- ) -> (Int, Int, Array[Byte]) raise VmError {
547
- let asset_obj = expect_object(asset_json, context)
548
- let width = object_get_number_or(asset_obj, "width", 0.0).to_int()
549
- let height = object_get_number_or(asset_obj, "height", 0.0).to_int()
550
- let rgba_base64 = object_get_string_or(asset_obj, "rgbaBase64", "")
551
- if width <= 0 || height <= 0 || rgba_base64 == "" {
552
- invalid_project("invalid RGBA asset for \{context}")
553
- }
554
- let decoded = @base64.decode(rgba_base64.view()) catch {
555
- _ => invalid_project("invalid base64 RGBA asset for \{context}")
556
- }
557
- let pixels = decoded.to_array()
558
- if pixels.length() < width * height * 4 {
559
- invalid_project("RGBA asset too short for \{context}")
560
- }
561
- (width, height, pixels)
562
- }
563
-
564
- ///|
565
- fn parse_costumes(
566
- raw_costumes : Array[Json],
567
- assets : Map[String, Json],
568
- ) -> (Array[String], Array[CostumeImage]) raise VmError {
569
- let costume_names = []
570
- let costumes = []
571
- for i, costume_json in raw_costumes {
572
- let costume_obj = match costume_json {
573
- Object(values) => values
574
- _ => {}
575
- }
576
- let name = object_get_string_or(costume_obj, "name", "costume_\{i + 1}")
577
- let asset_id = object_get_string_or(costume_obj, "assetId", "")
578
- let md5ext = object_get_string_or(costume_obj, "md5ext", "")
579
- let bitmap_resolution = {
580
- let raw = object_get_number_or(costume_obj, "bitmapResolution", 1.0).to_int()
581
- if raw <= 0 {
582
- 1
583
- } else {
584
- raw
585
- }
586
- }
587
- let rotation_center_x = object_get_number_or(
588
- costume_obj, "rotationCenterX", 0.0,
589
- )
590
- let rotation_center_y = object_get_number_or(
591
- costume_obj, "rotationCenterY", 0.0,
592
- )
593
- let fallback_width = object_get_number_or(costume_obj, "width", 0.0).to_int()
594
- let fallback_height = object_get_number_or(costume_obj, "height", 0.0).to_int()
595
-
596
- let mut width = if fallback_width > 0 { fallback_width } else { 0 }
597
- let mut height = if fallback_height > 0 { fallback_height } else { 0 }
598
- let mut pixels = empty_rgba_pixels(width, height)
599
-
600
- let asset_key = if asset_id != "" && assets.contains(asset_id) {
601
- Some(asset_id)
602
- } else if md5ext != "" && assets.contains(md5ext) {
603
- Some(md5ext)
604
- } else {
605
- None
606
- }
607
- match asset_key {
608
- Some(key) =>
609
- match assets.get(key) {
610
- Some(asset_json) => {
611
- let (asset_width, asset_height, asset_pixels) = parse_costume_asset_pixels(
612
- asset_json,
613
- "costume:\{name}",
614
- )
615
- width = asset_width
616
- height = asset_height
617
- pixels = asset_pixels
618
- }
619
- None => ()
620
- }
621
- None => ()
622
- }
623
-
624
- costume_names.push(name)
625
- costumes.push({
626
- name,
627
- asset_id,
628
- bitmap_resolution,
629
- rotation_center_x,
630
- rotation_center_y,
631
- width,
632
- height,
633
- pixels,
634
- })
635
- }
636
- (costume_names, costumes)
637
- }
638
-
639
- ///|
640
- fn parse_target(
641
- index : Int,
642
- json : Json,
643
- assets : Map[String, Json],
644
- ) -> TargetState raise VmError {
645
- let obj = expect_object(json, "target")
646
- let name = object_get_string_or(obj, "name", "target_\{index}")
647
- let is_stage = object_get_bool_or(obj, "isStage", false)
648
- let target_id = if is_stage { "__stage__" } else { "target_\{index}_\{name}" }
649
-
650
- let x = object_get_number_or(obj, "x", 0.0)
651
- let y = object_get_number_or(obj, "y", 0.0)
652
- let direction = object_get_number_or(obj, "direction", 90.0)
653
- let size = object_get_number_or(obj, "size", 100.0)
654
- let volume = object_get_number_or(obj, "volume", 100.0)
655
- let music_instrument = object_get_number_or(obj, "musicInstrument", 1.0).to_int() -
656
- 1
657
- let tts_voice = object_get_string_or(obj, "textToSpeechVoice", "ALTO")
658
- let visible = object_get_bool_or(obj, "visible", true)
659
- let current_costume = object_get_number_or(obj, "currentCostume", 0.0).to_int()
660
-
661
- let raw_variables = object_get_object_or_empty(obj, "variables")
662
- let raw_lists = object_get_object_or_empty(obj, "lists")
663
- let raw_blocks = object_get_object_or_empty(obj, "blocks")
664
- let raw_costumes = object_get_or(obj, "costumes", json_array([]))
665
- let costume_values = expect_array(raw_costumes, "target.costumes")
666
- let (costume_names, costumes) = parse_costumes(costume_values, assets)
667
-
668
- let (
669
- variables,
670
- variable_names,
671
- variable_slot_by_id,
672
- variable_slot_by_name,
673
- variable_id_by_slot,
674
- variable_values,
675
- ) = parse_variables(raw_variables)
676
- let variable_numeric_flags = variable_values.map(json_is_numeric_comparable)
677
- let (
678
- lists,
679
- list_names,
680
- list_slot_by_id,
681
- list_slot_by_name,
682
- list_id_by_slot,
683
- list_values,
684
- ) = parse_lists(raw_lists)
685
- let (blocks, hats) = parse_blocks(raw_blocks)
686
- let const_number_block_values = build_const_number_block_values(blocks)
687
- let procedures = build_procedure_specs(blocks)
688
- let (block_pc_by_id, blocks_by_pc) = build_block_pc_index(blocks)
689
- let block_fast_meta_by_pc = build_block_fast_meta_by_pc(
690
- blocks_by_pc, block_pc_by_id,
691
- )
692
- let green_flag_starts = []
693
- let stage_clicked_starts = []
694
- let sprite_clicked_starts = []
695
- let clone_start_starts = []
696
- let key_pressed_hats = []
697
- let broadcast_hats = []
698
- let backdrop_hats = []
699
- let predicate_hats = []
700
-
701
- for hat_id in hats {
702
- match blocks.get(hat_id) {
703
- Some(hat) =>
704
- match hat.next {
705
- Some(start_block) =>
706
- match hat.opcode {
707
- "event_whenflagclicked" => green_flag_starts.push(start_block)
708
- "event_whenstageclicked" => stage_clicked_starts.push(start_block)
709
- "event_whenthisspriteclicked" =>
710
- sprite_clicked_starts.push(start_block)
711
- "control_start_as_clone" => clone_start_starts.push(start_block)
712
- "event_whenkeypressed" => {
713
- let option = match field_value(hat, "KEY_OPTION") {
714
- Some((name, _)) => {
715
- let normalized = lower_trim(name)
716
- if normalized == "" {
717
- "any"
718
- } else {
719
- normalized
720
- }
721
- }
722
- None => "any"
723
- }
724
- key_pressed_hats.push({ key_option: option, start_block })
725
- }
726
- "event_whenbroadcastreceived" => {
727
- let message = match field_value(hat, "BROADCAST_OPTION") {
728
- Some((name, _)) => name
729
- None => ""
730
- }
731
- broadcast_hats.push({ message, start_block })
732
- }
733
- "event_whenbackdropswitchesto" => {
734
- let backdrop_name = match field_value(hat, "BACKDROP") {
735
- Some((name, _)) => name
736
- None => ""
737
- }
738
- backdrop_hats.push({ backdrop_name, start_block })
739
- }
740
- "event_whengreaterthan" => {
741
- let menu = match field_value(hat, "WHENGREATERTHANMENU") {
742
- Some((name, _)) => lower_trim(name)
743
- None => ""
744
- }
745
- predicate_hats.push({
746
- kind: PredicateHatKind::WhenGreaterThan,
747
- hat_id,
748
- menu,
749
- start_block,
750
- })
751
- }
752
- "event_whentouchingobject" => {
753
- let menu = match field_value(hat, "TOUCHINGOBJECTMENU") {
754
- Some((name, _)) => name
755
- None => ""
756
- }
757
- predicate_hats.push({
758
- kind: PredicateHatKind::WhenTouchingObject,
759
- hat_id,
760
- menu,
761
- start_block,
762
- })
763
- }
764
- _ => ()
765
- }
766
- None => ()
767
- }
768
- None => ()
769
- }
770
- }
771
-
772
- {
773
- id: target_id,
774
- name,
775
- is_stage,
776
- is_original: true,
777
- deleted: false,
778
- x,
779
- y,
780
- direction,
781
- size,
782
- volume,
783
- music_instrument,
784
- tts_voice,
785
- visible,
786
- current_costume,
787
- costume_names,
788
- costumes,
789
- pen_down: false,
790
- pen_color: 66.66,
791
- pen_saturation: 100.0,
792
- pen_brightness: 100.0,
793
- pen_transparency: 0.0,
794
- pen_size: 1.0,
795
- pen_legacy_shade: 50.0,
796
- looks_effect_color: 0.0,
797
- looks_effect_fisheye: 0.0,
798
- looks_effect_whirl: 0.0,
799
- looks_effect_pixelate: 0.0,
800
- looks_effect_mosaic: 0.0,
801
- looks_effect_brightness: 0.0,
802
- looks_effect_ghost: 0.0,
803
- variables,
804
- variable_names,
805
- variable_slot_by_id,
806
- variable_slot_by_name,
807
- variable_id_by_slot,
808
- variable_values,
809
- variable_numeric_flags,
810
- lists,
811
- list_names,
812
- list_slot_by_id,
813
- list_slot_by_name,
814
- list_id_by_slot,
815
- list_values,
816
- blocks,
817
- const_number_block_values,
818
- procedures,
819
- block_pc_by_id,
820
- blocks_by_pc,
821
- block_fast_meta_by_pc,
822
- numeric_program_cache: {},
823
- numeric_program_compile_failed: {},
824
- bool_program_cache: {},
825
- bool_program_compile_failed: {},
826
- top_level_hats: hats,
827
- green_flag_starts,
828
- stage_clicked_starts,
829
- sprite_clicked_starts,
830
- clone_start_starts,
831
- key_pressed_hats,
832
- broadcast_hats,
833
- backdrop_hats,
834
- predicate_hats,
835
- }
836
- }
837
-
838
- ///|
839
- fn parse_bundle(
840
- project_json : String,
841
- assets_json : String?,
842
- ) -> ProjectBundle raise VmError {
843
- let assets = match assets_json {
844
- Some(raw) =>
845
- if raw.trim().is_empty() {
846
- {}
847
- } else {
848
- let json = parse_json_or_fail(raw)
849
- expect_object(json, "assets")
850
- }
851
- None => {}
852
- }
853
- { project_json, assets }
854
- }
855
-
856
- ///|
857
- fn parse_project_targets(
858
- project_json : String,
859
- assets : Map[String, Json],
860
- ) -> (Array[TargetState], Int) raise VmError {
861
- let root = parse_json_or_fail(project_json)
862
- let root_obj = expect_object(root, "project")
863
- let targets_json = object_get_or(root_obj, "targets", json_array([]))
864
- let target_values = expect_array(targets_json, "project.targets")
865
-
866
- let targets = []
867
- let mut stage_index = 0
868
- for i, target_json in target_values {
869
- let target = parse_target(i, target_json, assets)
870
- if target.is_stage {
871
- stage_index = i
872
- }
873
- targets.push(target)
874
- }
875
-
876
- if targets.is_empty() {
877
- invalid_project("project has no targets")
878
- }
879
-
880
- (targets, stage_index)
881
- }
882
-
883
- ///|
884
- fn compile_project(bundle : ProjectBundle) -> PrecompiledProject raise VmError {
885
- let (targets, stage_index) = parse_project_targets(
886
- bundle.project_json,
887
- bundle.assets,
888
- )
889
- { targets, stage_index, assets: bundle.assets, initial_opaque_backdrops: {} }
890
- }