moonscratch 0.1.0-alpha.0

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 (151) hide show
  1. package/.agents/skills/moonbit-agent-guide/LICENSE +202 -0
  2. package/.agents/skills/moonbit-agent-guide/SKILL.mbt.md +1126 -0
  3. package/.agents/skills/moonbit-agent-guide/SKILL.md +1126 -0
  4. package/.agents/skills/moonbit-agent-guide/ide.md +116 -0
  5. package/.agents/skills/moonbit-agent-guide/references/advanced-moonbit-build.md +106 -0
  6. package/.agents/skills/moonbit-agent-guide/references/moonbit-language-fundamentals.mbt.md +422 -0
  7. package/.agents/skills/moonbit-agent-guide/references/moonbit-language-fundamentals.md +422 -0
  8. package/.agents/skills/moonbit-practice/SKILL.md +258 -0
  9. package/.agents/skills/moonbit-practice/assets/ci.yaml +25 -0
  10. package/.agents/skills/moonbit-practice/reference/agents.md +1469 -0
  11. package/.agents/skills/moonbit-practice/reference/configuration.md +228 -0
  12. package/.agents/skills/moonbit-practice/reference/ffi.md +229 -0
  13. package/.agents/skills/moonbit-practice/reference/ide.md +189 -0
  14. package/.agents/skills/moonbit-practice/reference/performance.md +217 -0
  15. package/.agents/skills/moonbit-practice/reference/refactor.md +154 -0
  16. package/.agents/skills/moonbit-practice/reference/stdlib.md +351 -0
  17. package/.agents/skills/moonbit-practice/reference/testing.md +228 -0
  18. package/.agents/skills/moonbit-refactoring/LICENSE +21 -0
  19. package/.agents/skills/moonbit-refactoring/SKILL.md +323 -0
  20. package/.githooks/README.md +23 -0
  21. package/.githooks/pre-commit +3 -0
  22. package/.github/workflows/copilot-setup-steps.yml +40 -0
  23. package/.turbo/turbo-typecheck.log +2 -0
  24. package/AGENTS.md +91 -0
  25. package/LICENSE +21 -0
  26. package/PLAN.md +64 -0
  27. package/README.mbt.md +77 -0
  28. package/README.md +84 -0
  29. package/TODO.md +120 -0
  30. package/a.png +0 -0
  31. package/benchmarks/calc.bench.ts +144 -0
  32. package/benchmarks/draw.bench.ts +215 -0
  33. package/benchmarks/load.bench.ts +28 -0
  34. package/benchmarks/render.bench.ts +53 -0
  35. package/benchmarks/run.bench.ts +8 -0
  36. package/benchmarks/types.d.ts +15 -0
  37. package/docs/scratch-vm-specs/eventloop.md +103 -0
  38. package/docs/scratch-vm-specs/moonscratch-time-separation.md +50 -0
  39. package/index.html +91 -0
  40. package/js/AGENTS.md +5 -0
  41. package/js/a.ts +52 -0
  42. package/js/assets/AGENTS.md +5 -0
  43. package/js/assets/base64.test.ts +14 -0
  44. package/js/assets/base64.ts +21 -0
  45. package/js/assets/build-asset.test.ts +26 -0
  46. package/js/assets/build-asset.ts +28 -0
  47. package/js/assets/create.test.ts +142 -0
  48. package/js/assets/create.ts +122 -0
  49. package/js/assets/index.test.ts +15 -0
  50. package/js/assets/index.ts +2 -0
  51. package/js/assets/types.ts +26 -0
  52. package/js/assets/validation.test.ts +34 -0
  53. package/js/assets/validation.ts +25 -0
  54. package/js/assets.test.ts +14 -0
  55. package/js/assets.ts +1 -0
  56. package/js/index.test.ts +26 -0
  57. package/js/index.ts +3 -0
  58. package/js/render/index.test.ts +65 -0
  59. package/js/render/index.ts +13 -0
  60. package/js/render/sharp.ts +87 -0
  61. package/js/render/svg.ts +68 -0
  62. package/js/render/types.ts +35 -0
  63. package/js/render/utils.ts +108 -0
  64. package/js/render/webgl.ts +274 -0
  65. package/js/sharp-optional.d.ts +16 -0
  66. package/js/test/helpers.ts +116 -0
  67. package/js/test/hikkaku-sample.test.ts +37 -0
  68. package/js/test/rubik-components.input-motion.test.ts +60 -0
  69. package/js/test/rubik-components.lists.test.ts +49 -0
  70. package/js/test/rubik-components.operators.test.ts +104 -0
  71. package/js/test/rubik-components.pen.test.ts +112 -0
  72. package/js/test/rubik-components.procedures-loops.test.ts +72 -0
  73. package/js/test/rubik-components.variables-branches.test.ts +57 -0
  74. package/js/test/rubik-components.visibility-entry.test.ts +31 -0
  75. package/js/test/test-projects.ts +598 -0
  76. package/js/test/variable.ts +200 -0
  77. package/js/test/warp.test.ts +59 -0
  78. package/js/vm/AGENTS.md +6 -0
  79. package/js/vm/README.md +183 -0
  80. package/js/vm/bindings.test.ts +13 -0
  81. package/js/vm/bindings.ts +5 -0
  82. package/js/vm/compare-operators.test.ts +145 -0
  83. package/js/vm/constants.test.ts +11 -0
  84. package/js/vm/constants.ts +4 -0
  85. package/js/vm/effect-guards.test.ts +68 -0
  86. package/js/vm/effect-guards.ts +44 -0
  87. package/js/vm/factory.test.ts +486 -0
  88. package/js/vm/factory.ts +615 -0
  89. package/js/vm/headless-vm.test.ts +131 -0
  90. package/js/vm/headless-vm.ts +342 -0
  91. package/js/vm/index.test.ts +28 -0
  92. package/js/vm/index.ts +5 -0
  93. package/js/vm/internal-types.ts +32 -0
  94. package/js/vm/json.test.ts +40 -0
  95. package/js/vm/json.ts +273 -0
  96. package/js/vm/normalize.test.ts +48 -0
  97. package/js/vm/normalize.ts +65 -0
  98. package/js/vm/options.test.ts +30 -0
  99. package/js/vm/options.ts +55 -0
  100. package/js/vm/pen-transparency.test.ts +115 -0
  101. package/js/vm/program-wasm.ts +322 -0
  102. package/js/vm/scheduler-render.test.ts +401 -0
  103. package/js/vm/scratch-assets.test.ts +136 -0
  104. package/js/vm/scratch-assets.ts +202 -0
  105. package/js/vm/types.ts +358 -0
  106. package/js/vm/value-guards.test.ts +25 -0
  107. package/js/vm/value-guards.ts +18 -0
  108. package/moon.mod.json +10 -0
  109. package/package.json +33 -0
  110. package/scripts/preinstall.ts +4 -0
  111. package/src/AGENTS.md +6 -0
  112. package/src/api.mbt +161 -0
  113. package/src/api_aot_commands.mbt +184 -0
  114. package/src/api_effects_json.mbt +72 -0
  115. package/src/api_options.mbt +60 -0
  116. package/src/api_program_wasm.mbt +1647 -0
  117. package/src/api_program_wat.mbt +2206 -0
  118. package/src/api_snapshot_json.mbt +44 -0
  119. package/src/cmd/AGENTS.md +5 -0
  120. package/src/cmd/main/AGENTS.md +5 -0
  121. package/src/cmd/main/main.mbt +29 -0
  122. package/src/cmd/main/moon.pkg +7 -0
  123. package/src/cmd/main/pkg.generated.mbti +13 -0
  124. package/src/json_helpers.mbt +176 -0
  125. package/src/moon.pkg +65 -0
  126. package/src/moonscratch.mbt +3 -0
  127. package/src/moonscratch_wbtest.mbt +40 -0
  128. package/src/parser_sb3.mbt +890 -0
  129. package/src/pkg.generated.mbti +479 -0
  130. package/src/runtime_eval.mbt +2844 -0
  131. package/src/runtime_exec.mbt +3850 -0
  132. package/src/runtime_render.mbt +2550 -0
  133. package/src/runtime_state.mbt +870 -0
  134. package/src/test/AGENTS.md +3 -0
  135. package/src/test/projects/AGENTS.md +6 -0
  136. package/src/test/projects/moon.pkg +4 -0
  137. package/src/test/projects/moonscratch_compat_test.mbt +642 -0
  138. package/src/test/projects/moonscratch_core_test.mbt +1332 -0
  139. package/src/test/projects/moonscratch_runtime_test.mbt +1087 -0
  140. package/src/test/projects/pkg.generated.mbti +13 -0
  141. package/src/test/projects/test_support.mbt +35 -0
  142. package/src/types_effects.mbt +20 -0
  143. package/src/types_error.mbt +4 -0
  144. package/src/types_options.mbt +31 -0
  145. package/src/types_runtime_structs.mbt +254 -0
  146. package/src/types_vm.mbt +109 -0
  147. package/tsconfig.json +29 -0
  148. package/viewer/index.ts +399 -0
  149. package/viewer/vite.d.ts +1 -0
  150. package/viewer/worker.ts +161 -0
  151. package/vite.config.ts +11 -0
@@ -0,0 +1,1647 @@
1
+ ///|
2
+ const IMPORT_GET_VAR_NUM_INDEX : Int = 0
3
+
4
+ ///|
5
+ const IMPORT_SET_VAR_NUM_INDEX : Int = 1
6
+
7
+ ///|
8
+ const IMPORT_SET_VAR_JSON_INDEX : Int = 2
9
+
10
+ ///|
11
+ const IMPORT_MOD_INDEX : Int = 3
12
+
13
+ ///|
14
+ const IMPORT_EXEC_OPCODE_INDEX : Int = 4
15
+
16
+ ///|
17
+ const IMPORT_EXEC_TAIL_INDEX : Int = 5
18
+
19
+ ///|
20
+ const IMPORT_EXEC_DRAW_OPCODE_INDEX : Int = 6
21
+
22
+ ///|
23
+ const LOOP_COUNTER_LOCAL : Int = 0
24
+
25
+ ///|
26
+ const LOOP_GUARD_LOCAL : Int = 1
27
+
28
+ ///|
29
+ const LOOP_GUARD_MAX : Int = 10000
30
+
31
+ ///|
32
+ const RUNNER_CACHED_LOCAL_START : Int = 2
33
+
34
+ ///|
35
+ const RUNNER_CACHE_BASE_USE_THRESHOLD : Int = 4
36
+
37
+ ///|
38
+ const RUNNER_CACHE_MAX_VARIABLES : Int = 32
39
+
40
+ ///|
41
+ priv struct RunnerLiteralRef {
42
+ ptr : Int
43
+ len : Int
44
+ }
45
+
46
+ ///|
47
+ priv struct WasmDataSegment {
48
+ offset : Int
49
+ bytes : Array[Byte]
50
+ }
51
+
52
+ ///|
53
+ priv struct RunnerCachedVar {
54
+ key : String
55
+ target : Int
56
+ variable_id : String
57
+ local_index : Int
58
+ use_count : Int
59
+ }
60
+
61
+ ///|
62
+ priv enum RunnerNumExpr {
63
+ Num(Double)
64
+ VarNum(Int, String)
65
+ BinNum(String, RunnerNumExpr, RunnerNumExpr)
66
+ UnaryNum(String, RunnerNumExpr)
67
+ }
68
+
69
+ ///|
70
+ priv enum RunnerBoolExpr {
71
+ Bool(Bool)
72
+ CmpNum(String, RunnerNumExpr, RunnerNumExpr)
73
+ BinBool(String, RunnerBoolExpr, RunnerBoolExpr)
74
+ Not(RunnerBoolExpr)
75
+ }
76
+
77
+ ///|
78
+ priv enum RunnerCommand {
79
+ SetNumExpr(Int, String, RunnerNumExpr)
80
+ SetJsonConst(Int, String, String)
81
+ ChangeNumExpr(Int, String, RunnerNumExpr)
82
+ DrawOpcode(Int, String, RunnerNumExpr, RunnerNumExpr, Int)
83
+ If(RunnerBoolExpr, Array[RunnerCommand])
84
+ IfElse(RunnerBoolExpr, Array[RunnerCommand], Array[RunnerCommand])
85
+ Repeat(RunnerNumExpr, Array[RunnerCommand])
86
+ RepeatUntil(RunnerBoolExpr, Array[RunnerCommand])
87
+ While(RunnerBoolExpr, Array[RunnerCommand])
88
+ HostOpcode(Int, Int)
89
+ HostTail(Int, Int)
90
+ }
91
+
92
+ ///|
93
+ fn bytes_from_ints(values : Array[Int]) -> Array[Byte] {
94
+ let out = []
95
+ for value in values {
96
+ out.push(value.to_byte())
97
+ }
98
+ out
99
+ }
100
+
101
+ ///|
102
+ fn append_bytes(target : Array[Byte], source : Array[Byte]) -> Unit {
103
+ for value in source {
104
+ target.push(value)
105
+ }
106
+ }
107
+
108
+ ///|
109
+ fn encode_utf8_bytes(value : String) -> Array[Byte] {
110
+ @utf8.encode(value.view()).to_array()
111
+ }
112
+
113
+ ///|
114
+ fn encode_u32_leb_step(value : Int, out : Array[Byte]) -> Unit {
115
+ let mut byte = value & 0x7f
116
+ let next = value >> 7
117
+ if next != 0 {
118
+ byte = byte | 0x80
119
+ }
120
+ out.push(byte.to_byte())
121
+ if next != 0 {
122
+ encode_u32_leb_step(next, out)
123
+ }
124
+ }
125
+
126
+ ///|
127
+ fn encode_u32_leb(input : Int) -> Array[Byte] {
128
+ if input < 0 {
129
+ return []
130
+ }
131
+ let out = []
132
+ encode_u32_leb_step(input, out)
133
+ out
134
+ }
135
+
136
+ ///|
137
+ fn encode_i32_leb_step(value : Int, out : Array[Byte]) -> Unit {
138
+ let mut byte = value & 0x7f
139
+ let next = value >> 7
140
+ let sign_bit = (byte & 0x40) != 0
141
+ let done = (next == 0 && !sign_bit) || (next == -1 && sign_bit)
142
+ if !done {
143
+ byte = byte | 0x80
144
+ }
145
+ out.push(byte.to_byte())
146
+ if !done {
147
+ encode_i32_leb_step(next, out)
148
+ }
149
+ }
150
+
151
+ ///|
152
+ fn encode_i32_leb(input : Int) -> Array[Byte] {
153
+ let out = []
154
+ encode_i32_leb_step(input, out)
155
+ out
156
+ }
157
+
158
+ ///|
159
+ fn encode_name(value : String) -> Array[Byte] {
160
+ let bytes = encode_utf8_bytes(value)
161
+ let out = []
162
+ append_bytes(out, encode_u32_leb(bytes.length()))
163
+ append_bytes(out, bytes)
164
+ out
165
+ }
166
+
167
+ ///|
168
+ fn encode_section(id : Int, payload : Array[Byte]) -> Array[Byte] {
169
+ let out = [id.to_byte()]
170
+ append_bytes(out, encode_u32_leb(payload.length()))
171
+ append_bytes(out, payload)
172
+ out
173
+ }
174
+
175
+ ///|
176
+ fn push_i32_const(out : Array[Byte], input : Int) -> Unit {
177
+ out.push((0x41).to_byte())
178
+ append_bytes(out, encode_i32_leb(input))
179
+ }
180
+
181
+ ///|
182
+ fn push_f64_const(out : Array[Byte], input : Double) -> Unit {
183
+ out.push((0x44).to_byte())
184
+ append_bytes(out, input.reinterpret_as_uint64().to_le_bytes().to_array())
185
+ }
186
+
187
+ ///|
188
+ fn push_call(out : Array[Byte], function_index : Int) -> Unit {
189
+ out.push((0x10).to_byte())
190
+ append_bytes(out, encode_u32_leb(function_index))
191
+ }
192
+
193
+ ///|
194
+ fn push_local_get(out : Array[Byte], local_index : Int) -> Unit {
195
+ out.push((0x20).to_byte())
196
+ append_bytes(out, encode_u32_leb(local_index))
197
+ }
198
+
199
+ ///|
200
+ fn push_local_set(out : Array[Byte], local_index : Int) -> Unit {
201
+ out.push((0x21).to_byte())
202
+ append_bytes(out, encode_u32_leb(local_index))
203
+ }
204
+
205
+ ///|
206
+ fn push_br(out : Array[Byte], depth : Int) -> Unit {
207
+ out.push((0x0c).to_byte())
208
+ append_bytes(out, encode_u32_leb(depth))
209
+ }
210
+
211
+ ///|
212
+ fn push_br_if(out : Array[Byte], depth : Int) -> Unit {
213
+ out.push((0x0d).to_byte())
214
+ append_bytes(out, encode_u32_leb(depth))
215
+ }
216
+
217
+ ///|
218
+ fn runner_variable_key(target : Int, variable_id : String) -> String {
219
+ "\{target}:\{variable_id}"
220
+ }
221
+
222
+ ///|
223
+ fn json_to_non_negative_int(raw : Json?) -> Int? {
224
+ match raw {
225
+ Some(Number(n, ..)) =>
226
+ if n.is_nan() || n.is_inf() {
227
+ None
228
+ } else {
229
+ let out = n.to_int()
230
+ if out < 0 {
231
+ None
232
+ } else {
233
+ Some(out)
234
+ }
235
+ }
236
+ _ => None
237
+ }
238
+ }
239
+
240
+ ///|
241
+ fn parse_runner_num_expr(raw : Json, depth : Int) -> RunnerNumExpr? {
242
+ if depth > 64 {
243
+ return None
244
+ }
245
+ let obj = match raw {
246
+ Object(value) => value
247
+ _ => return None
248
+ }
249
+ let kind = object_get_string_or(obj, "kind", "")
250
+ if kind == "num" {
251
+ match obj.get("value") {
252
+ Some(Number(value, ..)) =>
253
+ if value.is_nan() || value.is_inf() {
254
+ None
255
+ } else {
256
+ Some(RunnerNumExpr::Num(value))
257
+ }
258
+ _ => None
259
+ }
260
+ } else if kind == "var_num" {
261
+ let target = json_to_non_negative_int(obj.get("target"))
262
+ let variable_id = object_get_string_or(obj, "id", "")
263
+ match target {
264
+ Some(value) if variable_id != "" =>
265
+ Some(RunnerNumExpr::VarNum(value, variable_id))
266
+ _ => None
267
+ }
268
+ } else if kind == "bin_num" {
269
+ let op = object_get_string_or(obj, "op", "")
270
+ if op != "add" && op != "sub" && op != "mul" && op != "div" && op != "mod" {
271
+ return None
272
+ }
273
+ match
274
+ (
275
+ parse_runner_num_expr(
276
+ object_get_or(obj, "left", Json::null()),
277
+ depth + 1,
278
+ ),
279
+ parse_runner_num_expr(
280
+ object_get_or(obj, "right", Json::null()),
281
+ depth + 1,
282
+ ),
283
+ ) {
284
+ (Some(left), Some(right)) => Some(RunnerNumExpr::BinNum(op, left, right))
285
+ _ => None
286
+ }
287
+ } else if kind == "unary_num" {
288
+ let op = object_get_string_or(obj, "op", "")
289
+ if op != "round" &&
290
+ op != "abs" &&
291
+ op != "floor" &&
292
+ op != "ceil" &&
293
+ op != "sqrt" {
294
+ return None
295
+ }
296
+ match
297
+ parse_runner_num_expr(
298
+ object_get_or(obj, "value", Json::null()),
299
+ depth + 1,
300
+ ) {
301
+ Some(value) => Some(RunnerNumExpr::UnaryNum(op, value))
302
+ None => None
303
+ }
304
+ } else {
305
+ None
306
+ }
307
+ }
308
+
309
+ ///|
310
+ fn parse_runner_bool_expr(raw : Json, depth : Int) -> RunnerBoolExpr? {
311
+ if depth > 64 {
312
+ return None
313
+ }
314
+ let obj = match raw {
315
+ Object(value) => value
316
+ _ => return None
317
+ }
318
+ let kind = object_get_string_or(obj, "kind", "")
319
+ if kind == "bool" {
320
+ match obj.get("value") {
321
+ Some(True) => Some(RunnerBoolExpr::Bool(true))
322
+ Some(False) => Some(RunnerBoolExpr::Bool(false))
323
+ _ => None
324
+ }
325
+ } else if kind == "cmp_num" {
326
+ let op = object_get_string_or(obj, "op", "")
327
+ if op != "lt" && op != "gt" && op != "eq" && op != "ne" {
328
+ return None
329
+ }
330
+ match
331
+ (
332
+ parse_runner_num_expr(
333
+ object_get_or(obj, "left", Json::null()),
334
+ depth + 1,
335
+ ),
336
+ parse_runner_num_expr(
337
+ object_get_or(obj, "right", Json::null()),
338
+ depth + 1,
339
+ ),
340
+ ) {
341
+ (Some(left), Some(right)) => Some(RunnerBoolExpr::CmpNum(op, left, right))
342
+ _ => None
343
+ }
344
+ } else if kind == "bin_bool" {
345
+ let op = object_get_string_or(obj, "op", "")
346
+ if op != "and" && op != "or" {
347
+ return None
348
+ }
349
+ match
350
+ (
351
+ parse_runner_bool_expr(
352
+ object_get_or(obj, "left", Json::null()),
353
+ depth + 1,
354
+ ),
355
+ parse_runner_bool_expr(
356
+ object_get_or(obj, "right", Json::null()),
357
+ depth + 1,
358
+ ),
359
+ ) {
360
+ (Some(left), Some(right)) =>
361
+ Some(RunnerBoolExpr::BinBool(op, left, right))
362
+ _ => None
363
+ }
364
+ } else if kind == "not" {
365
+ match
366
+ parse_runner_bool_expr(
367
+ object_get_or(obj, "value", Json::null()),
368
+ depth + 1,
369
+ ) {
370
+ Some(value) => Some(RunnerBoolExpr::Not(value))
371
+ None => None
372
+ }
373
+ } else {
374
+ None
375
+ }
376
+ }
377
+
378
+ ///|
379
+ fn parse_runner_command_array(
380
+ raw : Array[Json],
381
+ depth : Int,
382
+ ) -> Array[RunnerCommand]? {
383
+ let out = []
384
+ for item in raw {
385
+ match parse_runner_command(item, depth + 1) {
386
+ Some(parsed) => out.push(parsed)
387
+ None => return None
388
+ }
389
+ }
390
+ Some(out)
391
+ }
392
+
393
+ ///|
394
+ fn parse_runner_command(raw : Json, depth : Int) -> RunnerCommand? {
395
+ if depth > 64 {
396
+ return None
397
+ }
398
+ let obj = match raw {
399
+ Object(value) => value
400
+ _ => return None
401
+ }
402
+ let op = object_get_string_or(obj, "op", "")
403
+ let target = json_to_non_negative_int(obj.get("target"))
404
+ let variable_id = object_get_string_or(obj, "id", "")
405
+
406
+ if op == "set_var" || op == "set_var_num_expr" || op == "set_var_json_const" {
407
+ match target {
408
+ Some(target_value) if variable_id != "" =>
409
+ if op == "set_var_num_expr" {
410
+ match
411
+ parse_runner_num_expr(
412
+ object_get_or(obj, "expr", Json::null()),
413
+ depth + 1,
414
+ ) {
415
+ Some(expr) =>
416
+ Some(RunnerCommand::SetNumExpr(target_value, variable_id, expr))
417
+ None => None
418
+ }
419
+ } else if op == "set_var_json_const" {
420
+ let value_json = object_get_or(obj, "value", Json::null()).stringify()
421
+ Some(
422
+ RunnerCommand::SetJsonConst(target_value, variable_id, value_json),
423
+ )
424
+ } else {
425
+ match obj.get("value") {
426
+ Some(Number(value, ..)) if !value.is_nan() && !value.is_inf() =>
427
+ Some(
428
+ RunnerCommand::SetNumExpr(
429
+ target_value,
430
+ variable_id,
431
+ RunnerNumExpr::Num(value),
432
+ ),
433
+ )
434
+ _ =>
435
+ Some(
436
+ RunnerCommand::SetJsonConst(
437
+ target_value,
438
+ variable_id,
439
+ object_get_or(obj, "value", Json::null()).stringify(),
440
+ ),
441
+ )
442
+ }
443
+ }
444
+ _ => None
445
+ }
446
+ } else if op == "change_var" || op == "change_var_num_expr" {
447
+ match target {
448
+ Some(target_value) if variable_id != "" =>
449
+ if op == "change_var_num_expr" {
450
+ match
451
+ parse_runner_num_expr(
452
+ object_get_or(obj, "expr", Json::null()),
453
+ depth + 1,
454
+ ) {
455
+ Some(expr) =>
456
+ Some(
457
+ RunnerCommand::ChangeNumExpr(target_value, variable_id, expr),
458
+ )
459
+ None => None
460
+ }
461
+ } else {
462
+ let delta = match obj.get("delta") {
463
+ Some(Number(value, ..)) if !value.is_nan() && !value.is_inf() =>
464
+ value
465
+ _ => 0.0
466
+ }
467
+ Some(
468
+ RunnerCommand::ChangeNumExpr(
469
+ target_value,
470
+ variable_id,
471
+ RunnerNumExpr::Num(delta),
472
+ ),
473
+ )
474
+ }
475
+ _ => None
476
+ }
477
+ } else if op == "draw_opcode" {
478
+ match target {
479
+ Some(target_value) => {
480
+ let draw_opcode = object_get_string_or(obj, "opcode", "")
481
+ let extra = match obj.get("extra") {
482
+ Some(Number(value, ..)) if !value.is_nan() && !value.is_inf() =>
483
+ value.to_int()
484
+ _ => 0
485
+ }
486
+ if draw_opcode == "" {
487
+ None
488
+ } else {
489
+ let arg0 = match
490
+ parse_runner_num_expr(
491
+ object_get_or(obj, "arg0", Json::null()),
492
+ depth + 1,
493
+ ) {
494
+ Some(value) => value
495
+ None => RunnerNumExpr::Num(0.0)
496
+ }
497
+ let arg1 = match
498
+ parse_runner_num_expr(
499
+ object_get_or(obj, "arg1", Json::null()),
500
+ depth + 1,
501
+ ) {
502
+ Some(value) => value
503
+ None => RunnerNumExpr::Num(0.0)
504
+ }
505
+ Some(
506
+ RunnerCommand::DrawOpcode(
507
+ target_value, draw_opcode, arg0, arg1, extra,
508
+ ),
509
+ )
510
+ }
511
+ }
512
+ None => None
513
+ }
514
+ } else if op == "if" {
515
+ match
516
+ (
517
+ parse_runner_bool_expr(
518
+ object_get_or(obj, "cond", Json::null()),
519
+ depth + 1,
520
+ ),
521
+ match obj.get("then") {
522
+ Some(Array(values)) => parse_runner_command_array(values, depth + 1)
523
+ _ => None
524
+ },
525
+ ) {
526
+ (Some(cond), Some(then_commands)) =>
527
+ Some(RunnerCommand::If(cond, then_commands))
528
+ _ => None
529
+ }
530
+ } else if op == "if_else" {
531
+ match
532
+ (
533
+ parse_runner_bool_expr(
534
+ object_get_or(obj, "cond", Json::null()),
535
+ depth + 1,
536
+ ),
537
+ match obj.get("then") {
538
+ Some(Array(values)) => parse_runner_command_array(values, depth + 1)
539
+ _ => None
540
+ },
541
+ match obj.get("else") {
542
+ Some(Array(values)) => parse_runner_command_array(values, depth + 1)
543
+ _ => None
544
+ },
545
+ ) {
546
+ (Some(cond), Some(then_commands), Some(else_commands)) =>
547
+ Some(RunnerCommand::IfElse(cond, then_commands, else_commands))
548
+ _ => None
549
+ }
550
+ } else if op == "repeat" {
551
+ match
552
+ (
553
+ parse_runner_num_expr(
554
+ object_get_or(obj, "times", Json::null()),
555
+ depth + 1,
556
+ ),
557
+ match obj.get("body") {
558
+ Some(Array(values)) => parse_runner_command_array(values, depth + 1)
559
+ _ => None
560
+ },
561
+ ) {
562
+ (Some(times), Some(body)) => Some(RunnerCommand::Repeat(times, body))
563
+ _ => None
564
+ }
565
+ } else if op == "repeat_until" {
566
+ match
567
+ (
568
+ parse_runner_bool_expr(
569
+ object_get_or(obj, "cond", Json::null()),
570
+ depth + 1,
571
+ ),
572
+ match obj.get("body") {
573
+ Some(Array(values)) => parse_runner_command_array(values, depth + 1)
574
+ _ => None
575
+ },
576
+ ) {
577
+ (Some(cond), Some(body)) => Some(RunnerCommand::RepeatUntil(cond, body))
578
+ _ => None
579
+ }
580
+ } else if op == "while" {
581
+ match
582
+ (
583
+ parse_runner_bool_expr(
584
+ object_get_or(obj, "cond", Json::null()),
585
+ depth + 1,
586
+ ),
587
+ match obj.get("body") {
588
+ Some(Array(values)) => parse_runner_command_array(values, depth + 1)
589
+ _ => None
590
+ },
591
+ ) {
592
+ (Some(cond), Some(body)) => Some(RunnerCommand::While(cond, body))
593
+ _ => None
594
+ }
595
+ } else if op == "host_tail" {
596
+ match (target, json_to_non_negative_int(obj.get("pc"))) {
597
+ (Some(target_value), Some(start_pc)) =>
598
+ Some(RunnerCommand::HostTail(target_value, start_pc))
599
+ _ => None
600
+ }
601
+ } else if op == "host_opcode" {
602
+ match (target, json_to_non_negative_int(obj.get("pc"))) {
603
+ (Some(target_value), Some(pc)) =>
604
+ Some(RunnerCommand::HostOpcode(target_value, pc))
605
+ _ => None
606
+ }
607
+ } else {
608
+ None
609
+ }
610
+ }
611
+
612
+ ///|
613
+ fn parse_runner_commands_json(commands_json : String) -> Array[RunnerCommand] {
614
+ if commands_json.trim().is_empty() {
615
+ return []
616
+ }
617
+ let parsed = try? @json.parse(commands_json)
618
+ let commands_raw = match parsed {
619
+ Ok(Array(values)) => values
620
+ Ok(Object(obj)) =>
621
+ match obj.get("exec") {
622
+ Some(Array(values)) => values
623
+ _ => []
624
+ }
625
+ _ => []
626
+ }
627
+ match parse_runner_command_array(commands_raw, 0) {
628
+ Some(commands) => commands
629
+ None => []
630
+ }
631
+ }
632
+
633
+ ///|
634
+ fn runner_cached_var_usage_touch(
635
+ usage : Map[String, RunnerCachedVar],
636
+ order : Array[String],
637
+ target : Int,
638
+ variable_id : String,
639
+ weight : Int,
640
+ ) -> Unit {
641
+ let normalized_weight = if weight <= 0 { 1 } else { weight }
642
+ let key = runner_variable_key(target, variable_id)
643
+ match usage.get(key) {
644
+ Some(current) =>
645
+ usage[key] = {
646
+ key: current.key,
647
+ target: current.target,
648
+ variable_id: current.variable_id,
649
+ local_index: current.local_index,
650
+ use_count: current.use_count + normalized_weight,
651
+ }
652
+ None => {
653
+ usage[key] = {
654
+ key,
655
+ target,
656
+ variable_id,
657
+ local_index: -1,
658
+ use_count: normalized_weight,
659
+ }
660
+ order.push(key)
661
+ }
662
+ }
663
+ }
664
+
665
+ ///|
666
+ fn runner_usage_weight(loop_depth : Int) -> Int {
667
+ let capped = if loop_depth < 0 {
668
+ 0
669
+ } else if loop_depth > 4 {
670
+ 4
671
+ } else {
672
+ loop_depth
673
+ }
674
+ 1 << capped
675
+ }
676
+
677
+ ///|
678
+ fn runner_collect_num_expr_usage(
679
+ expr : RunnerNumExpr,
680
+ usage : Map[String, RunnerCachedVar],
681
+ order : Array[String],
682
+ weight : Int,
683
+ ) -> Unit {
684
+ match expr {
685
+ Num(_) => ()
686
+ VarNum(target, variable_id) =>
687
+ runner_cached_var_usage_touch(usage, order, target, variable_id, weight)
688
+ BinNum(_, left, right) => {
689
+ runner_collect_num_expr_usage(left, usage, order, weight)
690
+ runner_collect_num_expr_usage(right, usage, order, weight)
691
+ }
692
+ UnaryNum(_, value) =>
693
+ runner_collect_num_expr_usage(value, usage, order, weight)
694
+ }
695
+ }
696
+
697
+ ///|
698
+ fn runner_collect_bool_expr_usage(
699
+ expr : RunnerBoolExpr,
700
+ usage : Map[String, RunnerCachedVar],
701
+ order : Array[String],
702
+ weight : Int,
703
+ ) -> Unit {
704
+ match expr {
705
+ Bool(_) => ()
706
+ CmpNum(_, left, right) => {
707
+ runner_collect_num_expr_usage(left, usage, order, weight)
708
+ runner_collect_num_expr_usage(right, usage, order, weight)
709
+ }
710
+ BinBool(_, left, right) => {
711
+ runner_collect_bool_expr_usage(left, usage, order, weight)
712
+ runner_collect_bool_expr_usage(right, usage, order, weight)
713
+ }
714
+ Not(value) => runner_collect_bool_expr_usage(value, usage, order, weight)
715
+ }
716
+ }
717
+
718
+ ///|
719
+ fn runner_collect_usage_from_commands(
720
+ commands : Array[RunnerCommand],
721
+ usage : Map[String, RunnerCachedVar],
722
+ order : Array[String],
723
+ blocked : Map[String, Bool],
724
+ loop_depth : Int,
725
+ ) -> Int {
726
+ let current_weight = runner_usage_weight(loop_depth)
727
+ let mut host_barrier_count = 0
728
+ for command in commands {
729
+ host_barrier_count += match command {
730
+ SetNumExpr(target, variable_id, expr) => {
731
+ runner_cached_var_usage_touch(
732
+ usage, order, target, variable_id, current_weight,
733
+ )
734
+ runner_collect_num_expr_usage(expr, usage, order, current_weight)
735
+ 0
736
+ }
737
+ SetJsonConst(target, variable_id, _) => {
738
+ blocked[runner_variable_key(target, variable_id)] = true
739
+ 0
740
+ }
741
+ ChangeNumExpr(target, variable_id, expr) => {
742
+ runner_cached_var_usage_touch(
743
+ usage, order, target, variable_id, current_weight,
744
+ )
745
+ runner_collect_num_expr_usage(expr, usage, order, current_weight)
746
+ 0
747
+ }
748
+ DrawOpcode(_, _, arg0, arg1, _) => {
749
+ runner_collect_num_expr_usage(arg0, usage, order, current_weight)
750
+ runner_collect_num_expr_usage(arg1, usage, order, current_weight)
751
+ 0
752
+ }
753
+ If(cond, then_commands) => {
754
+ runner_collect_bool_expr_usage(cond, usage, order, current_weight)
755
+ runner_collect_usage_from_commands(
756
+ then_commands, usage, order, blocked, loop_depth,
757
+ )
758
+ }
759
+ IfElse(cond, then_commands, else_commands) => {
760
+ runner_collect_bool_expr_usage(cond, usage, order, current_weight)
761
+ runner_collect_usage_from_commands(
762
+ then_commands, usage, order, blocked, loop_depth,
763
+ ) +
764
+ runner_collect_usage_from_commands(
765
+ else_commands, usage, order, blocked, loop_depth,
766
+ )
767
+ }
768
+ Repeat(times, body) => {
769
+ runner_collect_num_expr_usage(times, usage, order, current_weight)
770
+ runner_collect_usage_from_commands(
771
+ body,
772
+ usage,
773
+ order,
774
+ blocked,
775
+ loop_depth + 1,
776
+ )
777
+ }
778
+ RepeatUntil(cond, body) => {
779
+ runner_collect_bool_expr_usage(
780
+ cond,
781
+ usage,
782
+ order,
783
+ runner_usage_weight(loop_depth + 1),
784
+ )
785
+ runner_collect_usage_from_commands(
786
+ body,
787
+ usage,
788
+ order,
789
+ blocked,
790
+ loop_depth + 1,
791
+ )
792
+ }
793
+ While(cond, body) => {
794
+ runner_collect_bool_expr_usage(
795
+ cond,
796
+ usage,
797
+ order,
798
+ runner_usage_weight(loop_depth + 1),
799
+ )
800
+ runner_collect_usage_from_commands(
801
+ body,
802
+ usage,
803
+ order,
804
+ blocked,
805
+ loop_depth + 1,
806
+ )
807
+ }
808
+ HostOpcode(_, _) | HostTail(_, _) => current_weight
809
+ }
810
+ }
811
+ host_barrier_count
812
+ }
813
+
814
+ ///|
815
+ fn runner_cache_min_use_count(host_barrier_count : Int) -> Int {
816
+ let _ignored = host_barrier_count
817
+ RUNNER_CACHE_BASE_USE_THRESHOLD
818
+ }
819
+
820
+ ///|
821
+ fn build_runner_cached_vars(
822
+ commands : Array[RunnerCommand],
823
+ ) -> (Map[String, RunnerCachedVar], Array[RunnerCachedVar]) {
824
+ let usage : Map[String, RunnerCachedVar] = {}
825
+ let order = []
826
+ let blocked : Map[String, Bool] = {}
827
+ let host_barrier_count = runner_collect_usage_from_commands(
828
+ commands, usage, order, blocked, 0,
829
+ )
830
+ let min_use_count = runner_cache_min_use_count(host_barrier_count)
831
+ let cached_by_key : Map[String, RunnerCachedVar] = {}
832
+ let cached_vars = []
833
+ let mut next_local_index = RUNNER_CACHED_LOCAL_START
834
+ for key in order {
835
+ if cached_vars.length() < RUNNER_CACHE_MAX_VARIABLES &&
836
+ !blocked.contains(key) {
837
+ match usage.get(key) {
838
+ Some(candidate) =>
839
+ if candidate.use_count >= min_use_count {
840
+ let cached = {
841
+ key: candidate.key,
842
+ target: candidate.target,
843
+ variable_id: candidate.variable_id,
844
+ local_index: next_local_index,
845
+ use_count: candidate.use_count,
846
+ }
847
+ cached_by_key[key] = cached
848
+ cached_vars.push(cached)
849
+ next_local_index += 1
850
+ }
851
+ None => ()
852
+ }
853
+ }
854
+ }
855
+ (cached_by_key, cached_vars)
856
+ }
857
+
858
+ ///|
859
+ fn runner_cached_var_lookup(
860
+ cached_by_key : Map[String, RunnerCachedVar],
861
+ target : Int,
862
+ variable_id : String,
863
+ ) -> RunnerCachedVar? {
864
+ cached_by_key.get(runner_variable_key(target, variable_id))
865
+ }
866
+
867
+ ///|
868
+ fn emit_runner_load_cached_var(
869
+ cached : RunnerCachedVar,
870
+ refs : Map[String, RunnerLiteralRef],
871
+ out : Array[Byte],
872
+ ) -> Bool {
873
+ match refs.get("id:\{cached.variable_id}") {
874
+ Some(id_ref) => {
875
+ push_i32_const(out, cached.target)
876
+ push_i32_const(out, id_ref.ptr)
877
+ push_i32_const(out, id_ref.len)
878
+ push_call(out, IMPORT_GET_VAR_NUM_INDEX)
879
+ push_local_set(out, cached.local_index)
880
+ true
881
+ }
882
+ None => false
883
+ }
884
+ }
885
+
886
+ ///|
887
+ fn emit_runner_flush_cached_var(
888
+ cached : RunnerCachedVar,
889
+ refs : Map[String, RunnerLiteralRef],
890
+ out : Array[Byte],
891
+ ) -> Bool {
892
+ match refs.get("id:\{cached.variable_id}") {
893
+ Some(id_ref) => {
894
+ push_i32_const(out, cached.target)
895
+ push_i32_const(out, id_ref.ptr)
896
+ push_i32_const(out, id_ref.len)
897
+ push_local_get(out, cached.local_index)
898
+ push_call(out, IMPORT_SET_VAR_NUM_INDEX)
899
+ true
900
+ }
901
+ None => false
902
+ }
903
+ }
904
+
905
+ ///|
906
+ fn emit_runner_sync_cached_vars(
907
+ cached_vars : Array[RunnerCachedVar],
908
+ refs : Map[String, RunnerLiteralRef],
909
+ out : Array[Byte],
910
+ load_mode : Bool,
911
+ ) -> Bool {
912
+ for cached in cached_vars {
913
+ let ok = if load_mode {
914
+ emit_runner_load_cached_var(cached, refs, out)
915
+ } else {
916
+ emit_runner_flush_cached_var(cached, refs, out)
917
+ }
918
+ if !ok {
919
+ return false
920
+ }
921
+ }
922
+ true
923
+ }
924
+
925
+ ///|
926
+ fn runner_register_literal(
927
+ refs : Map[String, RunnerLiteralRef],
928
+ bytes : Array[Byte],
929
+ base_ptr : Int,
930
+ key : String,
931
+ literal : String,
932
+ ) -> Unit {
933
+ match refs.get(key) {
934
+ Some(_) => return
935
+ None => ()
936
+ }
937
+ let encoded = encode_utf8_bytes(literal)
938
+ refs[key] = { ptr: base_ptr + bytes.length(), len: encoded.length() }
939
+ append_bytes(bytes, encoded)
940
+ }
941
+
942
+ ///|
943
+ fn collect_num_expr_literals(
944
+ expr : RunnerNumExpr,
945
+ refs : Map[String, RunnerLiteralRef],
946
+ bytes : Array[Byte],
947
+ base_ptr : Int,
948
+ ) -> Unit {
949
+ match expr {
950
+ Num(_) => ()
951
+ VarNum(_, variable_id) =>
952
+ runner_register_literal(
953
+ refs,
954
+ bytes,
955
+ base_ptr,
956
+ "id:\{variable_id}",
957
+ variable_id,
958
+ )
959
+ BinNum(_, left, right) => {
960
+ collect_num_expr_literals(left, refs, bytes, base_ptr)
961
+ collect_num_expr_literals(right, refs, bytes, base_ptr)
962
+ }
963
+ UnaryNum(_, value) =>
964
+ collect_num_expr_literals(value, refs, bytes, base_ptr)
965
+ }
966
+ }
967
+
968
+ ///|
969
+ fn collect_bool_expr_literals(
970
+ expr : RunnerBoolExpr,
971
+ refs : Map[String, RunnerLiteralRef],
972
+ bytes : Array[Byte],
973
+ base_ptr : Int,
974
+ ) -> Unit {
975
+ match expr {
976
+ Bool(_) => ()
977
+ CmpNum(_, left, right) => {
978
+ collect_num_expr_literals(left, refs, bytes, base_ptr)
979
+ collect_num_expr_literals(right, refs, bytes, base_ptr)
980
+ }
981
+ BinBool(_, left, right) => {
982
+ collect_bool_expr_literals(left, refs, bytes, base_ptr)
983
+ collect_bool_expr_literals(right, refs, bytes, base_ptr)
984
+ }
985
+ Not(value) => collect_bool_expr_literals(value, refs, bytes, base_ptr)
986
+ }
987
+ }
988
+
989
+ ///|
990
+ fn collect_command_literals(
991
+ command : RunnerCommand,
992
+ refs : Map[String, RunnerLiteralRef],
993
+ bytes : Array[Byte],
994
+ base_ptr : Int,
995
+ ) -> Unit {
996
+ match command {
997
+ SetNumExpr(_, variable_id, expr) => {
998
+ runner_register_literal(
999
+ refs,
1000
+ bytes,
1001
+ base_ptr,
1002
+ "id:\{variable_id}",
1003
+ variable_id,
1004
+ )
1005
+ collect_num_expr_literals(expr, refs, bytes, base_ptr)
1006
+ }
1007
+ SetJsonConst(_, variable_id, value_json) => {
1008
+ runner_register_literal(
1009
+ refs,
1010
+ bytes,
1011
+ base_ptr,
1012
+ "id:\{variable_id}",
1013
+ variable_id,
1014
+ )
1015
+ runner_register_literal(
1016
+ refs,
1017
+ bytes,
1018
+ base_ptr,
1019
+ "json:\{value_json}",
1020
+ value_json,
1021
+ )
1022
+ }
1023
+ ChangeNumExpr(_, variable_id, expr) => {
1024
+ runner_register_literal(
1025
+ refs,
1026
+ bytes,
1027
+ base_ptr,
1028
+ "id:\{variable_id}",
1029
+ variable_id,
1030
+ )
1031
+ collect_num_expr_literals(expr, refs, bytes, base_ptr)
1032
+ }
1033
+ DrawOpcode(_, opcode, arg0, arg1, _) => {
1034
+ runner_register_literal(refs, bytes, base_ptr, "opcode:\{opcode}", opcode)
1035
+ collect_num_expr_literals(arg0, refs, bytes, base_ptr)
1036
+ collect_num_expr_literals(arg1, refs, bytes, base_ptr)
1037
+ }
1038
+ If(cond, then_commands) => {
1039
+ collect_bool_expr_literals(cond, refs, bytes, base_ptr)
1040
+ for child in then_commands {
1041
+ collect_command_literals(child, refs, bytes, base_ptr)
1042
+ }
1043
+ }
1044
+ IfElse(cond, then_commands, else_commands) => {
1045
+ collect_bool_expr_literals(cond, refs, bytes, base_ptr)
1046
+ for child in then_commands {
1047
+ collect_command_literals(child, refs, bytes, base_ptr)
1048
+ }
1049
+ for child in else_commands {
1050
+ collect_command_literals(child, refs, bytes, base_ptr)
1051
+ }
1052
+ }
1053
+ Repeat(times, body) => {
1054
+ collect_num_expr_literals(times, refs, bytes, base_ptr)
1055
+ for child in body {
1056
+ collect_command_literals(child, refs, bytes, base_ptr)
1057
+ }
1058
+ }
1059
+ RepeatUntil(cond, body) => {
1060
+ collect_bool_expr_literals(cond, refs, bytes, base_ptr)
1061
+ for child in body {
1062
+ collect_command_literals(child, refs, bytes, base_ptr)
1063
+ }
1064
+ }
1065
+ While(cond, body) => {
1066
+ collect_bool_expr_literals(cond, refs, bytes, base_ptr)
1067
+ for child in body {
1068
+ collect_command_literals(child, refs, bytes, base_ptr)
1069
+ }
1070
+ }
1071
+ HostOpcode(_, _) | HostTail(_, _) => ()
1072
+ }
1073
+ }
1074
+
1075
+ ///|
1076
+ fn build_runner_literal_pool(
1077
+ commands : Array[RunnerCommand],
1078
+ base_ptr : Int,
1079
+ ) -> (Array[Byte], Map[String, RunnerLiteralRef]) {
1080
+ let refs : Map[String, RunnerLiteralRef] = {}
1081
+ let bytes = []
1082
+ for command in commands {
1083
+ collect_command_literals(command, refs, bytes, base_ptr)
1084
+ }
1085
+ (bytes, refs)
1086
+ }
1087
+
1088
+ ///|
1089
+ fn emit_runner_num_expr(
1090
+ expr : RunnerNumExpr,
1091
+ refs : Map[String, RunnerLiteralRef],
1092
+ cached_by_key : Map[String, RunnerCachedVar],
1093
+ out : Array[Byte],
1094
+ ) -> Bool {
1095
+ match expr {
1096
+ Num(value) => {
1097
+ push_f64_const(out, value)
1098
+ true
1099
+ }
1100
+ VarNum(target, variable_id) =>
1101
+ match runner_cached_var_lookup(cached_by_key, target, variable_id) {
1102
+ Some(cached) => {
1103
+ push_local_get(out, cached.local_index)
1104
+ true
1105
+ }
1106
+ None =>
1107
+ match refs.get("id:\{variable_id}") {
1108
+ Some(id_ref) => {
1109
+ push_i32_const(out, target)
1110
+ push_i32_const(out, id_ref.ptr)
1111
+ push_i32_const(out, id_ref.len)
1112
+ push_call(out, IMPORT_GET_VAR_NUM_INDEX)
1113
+ true
1114
+ }
1115
+ None => false
1116
+ }
1117
+ }
1118
+ BinNum(op, left, right) => {
1119
+ if !emit_runner_num_expr(left, refs, cached_by_key, out) ||
1120
+ !emit_runner_num_expr(right, refs, cached_by_key, out) {
1121
+ return false
1122
+ }
1123
+ if op == "add" {
1124
+ out.push((0xa0).to_byte())
1125
+ } else if op == "sub" {
1126
+ out.push((0xa1).to_byte())
1127
+ } else if op == "mul" {
1128
+ out.push((0xa2).to_byte())
1129
+ } else if op == "div" {
1130
+ out.push((0xa3).to_byte())
1131
+ } else {
1132
+ push_call(out, IMPORT_MOD_INDEX)
1133
+ }
1134
+ true
1135
+ }
1136
+ UnaryNum(op, value) => {
1137
+ if !emit_runner_num_expr(value, refs, cached_by_key, out) {
1138
+ return false
1139
+ }
1140
+ if op == "round" {
1141
+ out.push((0x9e).to_byte())
1142
+ } else if op == "abs" {
1143
+ out.push((0x99).to_byte())
1144
+ } else if op == "floor" {
1145
+ out.push((0x9c).to_byte())
1146
+ } else if op == "ceil" {
1147
+ out.push((0x9b).to_byte())
1148
+ } else {
1149
+ out.push((0x9f).to_byte())
1150
+ }
1151
+ true
1152
+ }
1153
+ }
1154
+ }
1155
+
1156
+ ///|
1157
+ fn emit_runner_bool_expr(
1158
+ expr : RunnerBoolExpr,
1159
+ refs : Map[String, RunnerLiteralRef],
1160
+ cached_by_key : Map[String, RunnerCachedVar],
1161
+ out : Array[Byte],
1162
+ ) -> Bool {
1163
+ match expr {
1164
+ Bool(value) => {
1165
+ push_i32_const(out, if value { 1 } else { 0 })
1166
+ true
1167
+ }
1168
+ CmpNum(op, left, right) => {
1169
+ if !emit_runner_num_expr(left, refs, cached_by_key, out) ||
1170
+ !emit_runner_num_expr(right, refs, cached_by_key, out) {
1171
+ return false
1172
+ }
1173
+ if op == "lt" {
1174
+ out.push((0x63).to_byte())
1175
+ } else if op == "gt" {
1176
+ out.push((0x64).to_byte())
1177
+ } else if op == "eq" {
1178
+ out.push((0x61).to_byte())
1179
+ } else {
1180
+ out.push((0x61).to_byte())
1181
+ out.push((0x45).to_byte())
1182
+ }
1183
+ true
1184
+ }
1185
+ BinBool(op, left, right) => {
1186
+ if !emit_runner_bool_expr(left, refs, cached_by_key, out) ||
1187
+ !emit_runner_bool_expr(right, refs, cached_by_key, out) {
1188
+ return false
1189
+ }
1190
+ out.push(if op == "and" { (0x71).to_byte() } else { (0x72).to_byte() })
1191
+ true
1192
+ }
1193
+ Not(value) => {
1194
+ if !emit_runner_bool_expr(value, refs, cached_by_key, out) {
1195
+ return false
1196
+ }
1197
+ out.push((0x45).to_byte())
1198
+ true
1199
+ }
1200
+ }
1201
+ }
1202
+
1203
+ ///|
1204
+ fn emit_runner_commands(
1205
+ commands : Array[RunnerCommand],
1206
+ refs : Map[String, RunnerLiteralRef],
1207
+ cached_by_key : Map[String, RunnerCachedVar],
1208
+ cached_vars : Array[RunnerCachedVar],
1209
+ out : Array[Byte],
1210
+ ) -> Bool {
1211
+ for command in commands {
1212
+ match command {
1213
+ SetNumExpr(target, variable_id, expr) =>
1214
+ match runner_cached_var_lookup(cached_by_key, target, variable_id) {
1215
+ Some(cached) => {
1216
+ if !emit_runner_num_expr(expr, refs, cached_by_key, out) {
1217
+ return false
1218
+ }
1219
+ push_local_set(out, cached.local_index)
1220
+ }
1221
+ None =>
1222
+ match refs.get("id:\{variable_id}") {
1223
+ Some(id_ref) => {
1224
+ push_i32_const(out, target)
1225
+ push_i32_const(out, id_ref.ptr)
1226
+ push_i32_const(out, id_ref.len)
1227
+ if !emit_runner_num_expr(expr, refs, cached_by_key, out) {
1228
+ return false
1229
+ }
1230
+ push_call(out, IMPORT_SET_VAR_NUM_INDEX)
1231
+ }
1232
+ None => return false
1233
+ }
1234
+ }
1235
+ SetJsonConst(target, variable_id, value_json) =>
1236
+ match (refs.get("id:\{variable_id}"), refs.get("json:\{value_json}")) {
1237
+ (Some(id_ref), Some(json_ref)) => {
1238
+ push_i32_const(out, target)
1239
+ push_i32_const(out, id_ref.ptr)
1240
+ push_i32_const(out, id_ref.len)
1241
+ push_i32_const(out, json_ref.ptr)
1242
+ push_i32_const(out, json_ref.len)
1243
+ push_call(out, IMPORT_SET_VAR_JSON_INDEX)
1244
+ }
1245
+ _ => return false
1246
+ }
1247
+ ChangeNumExpr(target, variable_id, expr) =>
1248
+ match runner_cached_var_lookup(cached_by_key, target, variable_id) {
1249
+ Some(cached) => {
1250
+ push_local_get(out, cached.local_index)
1251
+ if !emit_runner_num_expr(expr, refs, cached_by_key, out) {
1252
+ return false
1253
+ }
1254
+ out.push((0xa0).to_byte())
1255
+ push_local_set(out, cached.local_index)
1256
+ }
1257
+ None =>
1258
+ match refs.get("id:\{variable_id}") {
1259
+ Some(id_ref) => {
1260
+ push_i32_const(out, target)
1261
+ push_i32_const(out, id_ref.ptr)
1262
+ push_i32_const(out, id_ref.len)
1263
+ push_i32_const(out, target)
1264
+ push_i32_const(out, id_ref.ptr)
1265
+ push_i32_const(out, id_ref.len)
1266
+ push_call(out, IMPORT_GET_VAR_NUM_INDEX)
1267
+ if !emit_runner_num_expr(expr, refs, cached_by_key, out) {
1268
+ return false
1269
+ }
1270
+ out.push((0xa0).to_byte())
1271
+ push_call(out, IMPORT_SET_VAR_NUM_INDEX)
1272
+ }
1273
+ None => return false
1274
+ }
1275
+ }
1276
+ DrawOpcode(target, opcode, arg0, arg1, extra) =>
1277
+ match refs.get("opcode:\{opcode}") {
1278
+ Some(opcode_ref) => {
1279
+ push_i32_const(out, target)
1280
+ push_i32_const(out, opcode_ref.ptr)
1281
+ push_i32_const(out, opcode_ref.len)
1282
+ if !emit_runner_num_expr(arg0, refs, cached_by_key, out) ||
1283
+ !emit_runner_num_expr(arg1, refs, cached_by_key, out) {
1284
+ return false
1285
+ }
1286
+ push_i32_const(out, extra)
1287
+ push_call(out, IMPORT_EXEC_DRAW_OPCODE_INDEX)
1288
+ out.push((0x1a).to_byte())
1289
+ }
1290
+ None => return false
1291
+ }
1292
+ If(cond, then_commands) => {
1293
+ if !emit_runner_bool_expr(cond, refs, cached_by_key, out) {
1294
+ return false
1295
+ }
1296
+ out.push((0x04).to_byte())
1297
+ out.push((0x40).to_byte())
1298
+ if !emit_runner_commands(
1299
+ then_commands, refs, cached_by_key, cached_vars, out,
1300
+ ) {
1301
+ return false
1302
+ }
1303
+ out.push((0x0b).to_byte())
1304
+ }
1305
+ IfElse(cond, then_commands, else_commands) => {
1306
+ if !emit_runner_bool_expr(cond, refs, cached_by_key, out) {
1307
+ return false
1308
+ }
1309
+ out.push((0x04).to_byte())
1310
+ out.push((0x40).to_byte())
1311
+ if !emit_runner_commands(
1312
+ then_commands, refs, cached_by_key, cached_vars, out,
1313
+ ) {
1314
+ return false
1315
+ }
1316
+ out.push((0x05).to_byte())
1317
+ if !emit_runner_commands(
1318
+ else_commands, refs, cached_by_key, cached_vars, out,
1319
+ ) {
1320
+ return false
1321
+ }
1322
+ out.push((0x0b).to_byte())
1323
+ }
1324
+ Repeat(times, body) => {
1325
+ if !emit_runner_num_expr(times, refs, cached_by_key, out) {
1326
+ return false
1327
+ }
1328
+ push_local_set(out, LOOP_COUNTER_LOCAL)
1329
+ push_i32_const(out, LOOP_GUARD_MAX)
1330
+ push_local_set(out, LOOP_GUARD_LOCAL)
1331
+ out.push((0x02).to_byte())
1332
+ out.push((0x40).to_byte())
1333
+ out.push((0x03).to_byte())
1334
+ out.push((0x40).to_byte())
1335
+ push_local_get(out, LOOP_GUARD_LOCAL)
1336
+ out.push((0x45).to_byte())
1337
+ push_br_if(out, 1)
1338
+ push_local_get(out, LOOP_COUNTER_LOCAL)
1339
+ push_f64_const(out, 0.0)
1340
+ out.push((0x65).to_byte())
1341
+ push_br_if(out, 1)
1342
+ if !emit_runner_commands(body, refs, cached_by_key, cached_vars, out) {
1343
+ return false
1344
+ }
1345
+ push_local_get(out, LOOP_COUNTER_LOCAL)
1346
+ push_f64_const(out, 1.0)
1347
+ out.push((0xa1).to_byte())
1348
+ push_local_set(out, LOOP_COUNTER_LOCAL)
1349
+ push_local_get(out, LOOP_GUARD_LOCAL)
1350
+ push_i32_const(out, 1)
1351
+ out.push((0x6b).to_byte())
1352
+ push_local_set(out, LOOP_GUARD_LOCAL)
1353
+ push_br(out, 0)
1354
+ out.push((0x0b).to_byte())
1355
+ out.push((0x0b).to_byte())
1356
+ }
1357
+ RepeatUntil(cond, body) => {
1358
+ push_i32_const(out, LOOP_GUARD_MAX)
1359
+ push_local_set(out, LOOP_GUARD_LOCAL)
1360
+ out.push((0x02).to_byte())
1361
+ out.push((0x40).to_byte())
1362
+ out.push((0x03).to_byte())
1363
+ out.push((0x40).to_byte())
1364
+ push_local_get(out, LOOP_GUARD_LOCAL)
1365
+ out.push((0x45).to_byte())
1366
+ push_br_if(out, 1)
1367
+ if !emit_runner_bool_expr(cond, refs, cached_by_key, out) {
1368
+ return false
1369
+ }
1370
+ push_br_if(out, 1)
1371
+ if !emit_runner_commands(body, refs, cached_by_key, cached_vars, out) {
1372
+ return false
1373
+ }
1374
+ push_local_get(out, LOOP_GUARD_LOCAL)
1375
+ push_i32_const(out, 1)
1376
+ out.push((0x6b).to_byte())
1377
+ push_local_set(out, LOOP_GUARD_LOCAL)
1378
+ push_br(out, 0)
1379
+ out.push((0x0b).to_byte())
1380
+ out.push((0x0b).to_byte())
1381
+ }
1382
+ While(cond, body) => {
1383
+ push_i32_const(out, LOOP_GUARD_MAX)
1384
+ push_local_set(out, LOOP_GUARD_LOCAL)
1385
+ out.push((0x02).to_byte())
1386
+ out.push((0x40).to_byte())
1387
+ out.push((0x03).to_byte())
1388
+ out.push((0x40).to_byte())
1389
+ push_local_get(out, LOOP_GUARD_LOCAL)
1390
+ out.push((0x45).to_byte())
1391
+ push_br_if(out, 1)
1392
+ if !emit_runner_bool_expr(cond, refs, cached_by_key, out) {
1393
+ return false
1394
+ }
1395
+ out.push((0x45).to_byte())
1396
+ push_br_if(out, 1)
1397
+ if !emit_runner_commands(body, refs, cached_by_key, cached_vars, out) {
1398
+ return false
1399
+ }
1400
+ push_local_get(out, LOOP_GUARD_LOCAL)
1401
+ push_i32_const(out, 1)
1402
+ out.push((0x6b).to_byte())
1403
+ push_local_set(out, LOOP_GUARD_LOCAL)
1404
+ push_br(out, 0)
1405
+ out.push((0x0b).to_byte())
1406
+ out.push((0x0b).to_byte())
1407
+ }
1408
+ HostTail(target, start_pc) => {
1409
+ if !emit_runner_sync_cached_vars(cached_vars, refs, out, false) {
1410
+ return false
1411
+ }
1412
+ push_i32_const(out, target)
1413
+ push_i32_const(out, start_pc)
1414
+ push_call(out, IMPORT_EXEC_TAIL_INDEX)
1415
+ out.push((0x1a).to_byte())
1416
+ if !emit_runner_sync_cached_vars(cached_vars, refs, out, true) {
1417
+ return false
1418
+ }
1419
+ }
1420
+ HostOpcode(target, pc) => {
1421
+ if !emit_runner_sync_cached_vars(cached_vars, refs, out, false) {
1422
+ return false
1423
+ }
1424
+ push_i32_const(out, target)
1425
+ push_i32_const(out, pc)
1426
+ push_call(out, IMPORT_EXEC_OPCODE_INDEX)
1427
+ out.push((0x1a).to_byte())
1428
+ if !emit_runner_sync_cached_vars(cached_vars, refs, out, true) {
1429
+ return false
1430
+ }
1431
+ }
1432
+ }
1433
+ }
1434
+ true
1435
+ }
1436
+
1437
+ ///|
1438
+ fn append_runner_exec_locals(
1439
+ body : Array[Byte],
1440
+ cached_var_count : Int,
1441
+ ) -> Unit {
1442
+ if cached_var_count > 0 {
1443
+ body.push((0x03).to_byte())
1444
+ body.push((0x01).to_byte())
1445
+ body.push((0x7c).to_byte())
1446
+ body.push((0x01).to_byte())
1447
+ body.push((0x7f).to_byte())
1448
+ append_bytes(body, encode_u32_leb(cached_var_count))
1449
+ body.push((0x7c).to_byte())
1450
+ } else {
1451
+ body.push((0x02).to_byte())
1452
+ body.push((0x01).to_byte())
1453
+ body.push((0x7c).to_byte())
1454
+ body.push((0x01).to_byte())
1455
+ body.push((0x7f).to_byte())
1456
+ }
1457
+ }
1458
+
1459
+ ///|
1460
+ fn build_runner_exec_body(
1461
+ commands : Array[RunnerCommand],
1462
+ refs : Map[String, RunnerLiteralRef],
1463
+ ) -> Array[Byte] {
1464
+ let (cached_by_key, cached_vars) = build_runner_cached_vars(commands)
1465
+ let body = []
1466
+ append_runner_exec_locals(body, cached_vars.length())
1467
+ if !emit_runner_sync_cached_vars(cached_vars, refs, body, true) ||
1468
+ !emit_runner_commands(commands, refs, cached_by_key, cached_vars, body) ||
1469
+ !emit_runner_sync_cached_vars(cached_vars, refs, body, false) {
1470
+ push_i32_const(body, 0)
1471
+ body.push((0x0b).to_byte())
1472
+ return body
1473
+ }
1474
+ push_i32_const(body, commands.length())
1475
+ body.push((0x0b).to_byte())
1476
+ body
1477
+ }
1478
+
1479
+ ///|
1480
+ fn append_const_i32_function_body(
1481
+ code_payload : Array[Byte],
1482
+ value : Int,
1483
+ ) -> Unit {
1484
+ let body = bytes_from_ints([0x00, 0x41])
1485
+ append_bytes(body, encode_i32_leb(value))
1486
+ body.push((0x0b).to_byte())
1487
+ append_bytes(code_payload, encode_u32_leb(body.length()))
1488
+ append_bytes(code_payload, body)
1489
+ }
1490
+
1491
+ ///|
1492
+ fn build_program_wasm_binary(
1493
+ abi_version : Int,
1494
+ project_base64 : String,
1495
+ assets_base64 : String,
1496
+ commands_base64 : String,
1497
+ runner_commands : Array[RunnerCommand],
1498
+ ) -> Array[Byte] {
1499
+ let project_bytes = encode_utf8_bytes(project_base64)
1500
+ let assets_bytes = encode_utf8_bytes(assets_base64)
1501
+ let commands_bytes = encode_utf8_bytes(commands_base64)
1502
+ let project_len = project_bytes.length()
1503
+ let assets_ptr = project_len
1504
+ let assets_len = assets_bytes.length()
1505
+ let commands_ptr = assets_ptr + assets_len
1506
+ let commands_len = commands_bytes.length()
1507
+ let runner_literal_pool_ptr = commands_ptr + commands_len
1508
+ let (runner_literal_pool_bytes, runner_literal_pool_refs) = build_runner_literal_pool(
1509
+ runner_commands, runner_literal_pool_ptr,
1510
+ )
1511
+ let total_bytes = project_len +
1512
+ assets_len +
1513
+ commands_len +
1514
+ runner_literal_pool_bytes.length()
1515
+ let memory_pages = page_count_for_bytes(total_bytes)
1516
+ let has_exec_runner = if runner_commands.is_empty() { 0 } else { 1 }
1517
+
1518
+ let module_bytes = bytes_from_ints([
1519
+ 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,
1520
+ ])
1521
+
1522
+ let type_payload = bytes_from_ints([
1523
+ 0x07, 0x60, 0x00, 0x01, 0x7f, 0x60, 0x03, 0x7f, 0x7f, 0x7f, 0x01, 0x7c, 0x60,
1524
+ 0x04, 0x7f, 0x7f, 0x7f, 0x7c, 0x00, 0x60, 0x05, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
1525
+ 0x00, 0x60, 0x02, 0x7c, 0x7c, 0x01, 0x7c, 0x60, 0x02, 0x7f, 0x7f, 0x01, 0x7f,
1526
+ 0x60, 0x06, 0x7f, 0x7f, 0x7f, 0x7c, 0x7c, 0x7f, 0x01, 0x7f,
1527
+ ])
1528
+ append_bytes(module_bytes, encode_section(1, type_payload))
1529
+
1530
+ let import_payload = []
1531
+ append_bytes(import_payload, encode_u32_leb(7))
1532
+ let imports : Array[(String, String, Int)] = [
1533
+ ("env", "ms_get_var_num", 1),
1534
+ ("env", "ms_set_var_num", 2),
1535
+ ("env", "ms_set_var_json", 3),
1536
+ ("env", "ms_mod", 4),
1537
+ ("env", "ms_exec_opcode", 5),
1538
+ ("env", "ms_exec_tail", 5),
1539
+ ("env", "ms_exec_draw_opcode", 6),
1540
+ ]
1541
+ for item in imports {
1542
+ append_bytes(import_payload, encode_name(item.0))
1543
+ append_bytes(import_payload, encode_name(item.1))
1544
+ import_payload.push((0x00).to_byte())
1545
+ append_bytes(import_payload, encode_u32_leb(item.2))
1546
+ }
1547
+ append_bytes(module_bytes, encode_section(2, import_payload))
1548
+
1549
+ let function_payload = []
1550
+ append_bytes(function_payload, encode_u32_leb(9))
1551
+ append_bytes(function_payload, bytes_from_ints([0, 0, 0, 0, 0, 0, 0, 0, 0]))
1552
+ append_bytes(module_bytes, encode_section(3, function_payload))
1553
+
1554
+ let memory_payload = [(0x01).to_byte(), (0x00).to_byte()]
1555
+ append_bytes(memory_payload, encode_u32_leb(memory_pages))
1556
+ append_bytes(module_bytes, encode_section(5, memory_payload))
1557
+
1558
+ let exports_payload = []
1559
+ let defined_function_base = imports.length()
1560
+ let exports : Array[(String, Int, Int)] = [
1561
+ ("memory", 0x02, 0),
1562
+ ("ms_abi_version", 0x00, defined_function_base + 0),
1563
+ ("ms_project_ptr", 0x00, defined_function_base + 1),
1564
+ ("ms_project_len", 0x00, defined_function_base + 2),
1565
+ ("ms_assets_ptr", 0x00, defined_function_base + 3),
1566
+ ("ms_assets_len", 0x00, defined_function_base + 4),
1567
+ ("ms_commands_ptr", 0x00, defined_function_base + 5),
1568
+ ("ms_commands_len", 0x00, defined_function_base + 6),
1569
+ ("ms_has_exec_runner", 0x00, defined_function_base + 7),
1570
+ ("ms_exec_green_flag", 0x00, defined_function_base + 8),
1571
+ ]
1572
+ append_bytes(exports_payload, encode_u32_leb(exports.length()))
1573
+ for item in exports {
1574
+ append_bytes(exports_payload, encode_name(item.0))
1575
+ exports_payload.push(item.1.to_byte())
1576
+ append_bytes(exports_payload, encode_u32_leb(item.2))
1577
+ }
1578
+ append_bytes(module_bytes, encode_section(7, exports_payload))
1579
+
1580
+ let code_payload = []
1581
+ let function_consts = [
1582
+ abi_version, 0, project_len, assets_ptr, assets_len, commands_ptr, commands_len,
1583
+ has_exec_runner,
1584
+ ]
1585
+ append_bytes(code_payload, encode_u32_leb(function_consts.length() + 1))
1586
+ for value in function_consts {
1587
+ append_const_i32_function_body(code_payload, value)
1588
+ }
1589
+ let exec_body = build_runner_exec_body(
1590
+ runner_commands, runner_literal_pool_refs,
1591
+ )
1592
+ append_bytes(code_payload, encode_u32_leb(exec_body.length()))
1593
+ append_bytes(code_payload, exec_body)
1594
+ append_bytes(module_bytes, encode_section(10, code_payload))
1595
+
1596
+ let segments : Array[WasmDataSegment] = [
1597
+ { offset: 0, bytes: project_bytes },
1598
+ { offset: assets_ptr, bytes: assets_bytes },
1599
+ { offset: commands_ptr, bytes: commands_bytes },
1600
+ ]
1601
+ if !runner_literal_pool_bytes.is_empty() {
1602
+ segments.push({
1603
+ offset: runner_literal_pool_ptr,
1604
+ bytes: runner_literal_pool_bytes,
1605
+ })
1606
+ }
1607
+ let data_payload = []
1608
+ append_bytes(data_payload, encode_u32_leb(segments.length()))
1609
+ for segment in segments {
1610
+ data_payload.push((0x00).to_byte())
1611
+ data_payload.push((0x41).to_byte())
1612
+ append_bytes(data_payload, encode_i32_leb(segment.offset))
1613
+ data_payload.push((0x0b).to_byte())
1614
+ append_bytes(data_payload, encode_u32_leb(segment.bytes.length()))
1615
+ append_bytes(data_payload, segment.bytes)
1616
+ }
1617
+ append_bytes(module_bytes, encode_section(11, data_payload))
1618
+
1619
+ module_bytes
1620
+ }
1621
+
1622
+ ///|
1623
+ pub fn vm_compile_project_to_wasm(
1624
+ project_json : String,
1625
+ assets_json? : String,
1626
+ ) -> String raise VmError {
1627
+ let bundle = parse_bundle(project_json, assets_json)
1628
+ let (targets, _) = parse_project_targets(bundle.project_json, bundle.assets)
1629
+ let project_base64 = base64_utf8(bundle.project_json)
1630
+ let assets_raw = json_object(bundle.assets).stringify()
1631
+ let assets_base64 = base64_utf8(assets_raw)
1632
+ let commands_json = match compile_aot_commands_json(targets) {
1633
+ Some(commands) => commands
1634
+ None => ""
1635
+ }
1636
+ let commands_base64 = base64_utf8(commands_json)
1637
+ let runner_commands = parse_runner_commands_json(commands_json)
1638
+ let wasm_bytes = build_program_wasm_binary(
1639
+ PROGRAM_ABI_VERSION,
1640
+ project_base64,
1641
+ assets_base64,
1642
+ commands_base64,
1643
+ runner_commands,
1644
+ )
1645
+ let wasm = Bytes::from_array(wasm_bytes[:])
1646
+ @base64.encode(wasm[:])
1647
+ }